From 179fa413b047bede6e32109e2ce82437c5fb8d34 Mon Sep 17 00:00:00 2001 From: MenTaLguY Date: Mon, 16 Jan 2006 02:36:01 +0000 Subject: moving trunk for module inkscape (bzr r1) --- src/.cvsignore | 19 + src/Doxyfile | 269 + src/Makefile.am | 261 + src/Makefile.mingw | 123 + src/Makefile.mingw.old | 53 + src/Makefile_insert | 339 + src/algorithms/.cvsignore | 2 + src/algorithms/Makefile_insert | 5 + src/algorithms/find-if-before.h | 51 + src/algorithms/find-last-if.h | 51 + src/algorithms/longest-common-suffix.h | 114 + src/algorithms/makefile.in | 17 + src/application/.cvsignore | 3 + src/application/Makefile_insert | 14 + src/application/app-prototype.cpp | 43 + src/application/app-prototype.h | 53 + src/application/application.cpp | 163 + src/application/application.h | 65 + src/application/editor.cpp | 416 + src/application/editor.h | 139 + src/application/makefile.in | 17 + src/approx-equal.h | 25 + src/arc-context.cpp | 447 + src/arc-context.h | 62 + src/attributes-test.cpp | 473 + src/attributes.cpp | 348 + src/attributes.h | 318 + src/bad-uri-exception.h | 34 + src/brokenimage.xpm | 88 + src/check-header-compile.in | 41 + src/color-rgba.h | 194 + src/color.cpp | 468 + src/color.h | 95 + src/composite-undo-stack-observer.cpp | 136 + src/composite-undo-stack-observer.h | 165 + src/conn-avoid-ref.cpp | 176 + src/conn-avoid-ref.h | 57 + src/connector-context.cpp | 1350 ++ src/connector-context.h | 112 + src/context-fns.cpp | 192 + src/context-fns.h | 27 + src/debug/.cvsignore | 3 + src/debug/Makefile_insert | 15 + src/debug/event-tracker.h | 224 + src/debug/event.h | 75 + src/debug/gc-heap.h | 52 + src/debug/heap.cpp | 65 + src/debug/heap.h | 63 + src/debug/logger.cpp | 204 + src/debug/logger.h | 171 + src/debug/makefile.in | 17 + src/debug/simple-event.h | 51 + src/debug/sysv-heap.cpp | 79 + src/debug/sysv-heap.h | 47 + src/decimal-round.h | 44 + src/desktop-affine.cpp | 40 + src/desktop-affine.h | 36 + src/desktop-events.cpp | 452 + src/desktop-events.h | 55 + src/desktop-handles.cpp | 122 + src/desktop-handles.h | 71 + src/desktop-style.cpp | 1006 + src/desktop-style.h | 87 + src/desktop.cpp | 1400 ++ src/desktop.h | 289 + src/dialogs/.cvsignore | 5 + src/dialogs/Makefile_insert | 75 + src/dialogs/clonetiler.cpp | 2578 +++ src/dialogs/clonetiler.h | 31 + src/dialogs/debugdialog.cpp | 348 + src/dialogs/debugdialog.h | 104 + src/dialogs/dialog-events.cpp | 247 + src/dialogs/dialog-events.h | 66 + src/dialogs/display-settings.cpp | 1575 ++ src/dialogs/display-settings.h | 69 + src/dialogs/eek-preview.cpp | 522 + src/dialogs/eek-preview.h | 112 + src/dialogs/export.cpp | 1753 ++ src/dialogs/export.h | 24 + src/dialogs/extensions.cpp | 128 + src/dialogs/extensions.h | 61 + src/dialogs/filedialog-win32.cpp | 381 + src/dialogs/filedialog.cpp | 1439 ++ src/dialogs/filedialog.h | 172 + src/dialogs/fill-style.cpp | 546 + src/dialogs/fill-style.h | 35 + src/dialogs/find.cpp | 763 + src/dialogs/find.h | 31 + src/dialogs/iconpreview.cpp | 320 + src/dialogs/iconpreview.h | 84 + src/dialogs/in-dt-coordsys.cpp | 37 + src/dialogs/in-dt-coordsys.h | 19 + src/dialogs/input.cpp | 273 + src/dialogs/input.h | 32 + src/dialogs/item-properties.cpp | 497 + src/dialogs/item-properties.h | 38 + src/dialogs/layer-properties.cpp | 199 + src/dialogs/layer-properties.h | 110 + src/dialogs/makefile.in | 17 + src/dialogs/object-attributes.cpp | 140 + src/dialogs/object-attributes.h | 37 + src/dialogs/object-properties.cpp | 318 + src/dialogs/object-properties.h | 34 + src/dialogs/rdf.cpp | 1002 + src/dialogs/rdf.h | 124 + src/dialogs/sp-attribute-widget.cpp | 802 + src/dialogs/sp-attribute-widget.h | 128 + src/dialogs/stroke-style.cpp | 1726 ++ src/dialogs/stroke-style.h | 35 + src/dialogs/swatches.cpp | 516 + src/dialogs/swatches.h | 93 + src/dialogs/text-edit.cpp | 915 + src/dialogs/text-edit.h | 24 + src/dialogs/tiledialog.cpp | 872 + src/dialogs/tiledialog.h | 190 + src/dialogs/unclump.cpp | 371 + src/dialogs/unclump.h | 20 + src/dialogs/xml-tree.cpp | 1575 ++ src/dialogs/xml-tree.h | 29 + src/dir-util-test.cpp | 48 + src/dir-util.cpp | 278 + src/dir-util.h | 33 + src/display/.cvsignore | 7 + src/display/Makefile_insert | 66 + src/display/bezier-utils-test.cpp | 334 + src/display/bezier-utils-work.txt | 34 + src/display/bezier-utils.cpp | 983 + src/display/bezier-utils.h | 49 + src/display/canvas-arena.cpp | 451 + src/display/canvas-arena.h | 58 + src/display/canvas-bpath.cpp | 485 + src/display/canvas-bpath.h | 99 + src/display/canvas-grid.cpp | 308 + src/display/canvas-grid.h | 49 + src/display/curve.cpp | 1289 ++ src/display/curve.h | 133 + src/display/display-forward.h | 46 + src/display/gnome-canvas-acetate.cpp | 100 + src/display/gnome-canvas-acetate.h | 41 + src/display/guideline.cpp | 198 + src/display/guideline.h | 55 + src/display/makefile.in | 17 + src/display/nr-arena-forward.h | 51 + src/display/nr-arena-glyphs.cpp | 679 + src/display/nr-arena-glyphs.h | 109 + src/display/nr-arena-group.cpp | 256 + src/display/nr-arena-group.h | 46 + src/display/nr-arena-image.cpp | 258 + src/display/nr-arena-image.h | 51 + src/display/nr-arena-item.cpp | 1155 ++ src/display/nr-arena-item.h | 188 + src/display/nr-arena-shape.cpp | 1298 ++ src/display/nr-arena-shape.h | 213 + src/display/nr-arena.cpp | 131 + src/display/nr-arena.h | 57 + src/display/nr-gradient-gpl.cpp | 306 + src/display/nr-gradient-gpl.h | 41 + src/display/nr-plain-stuff-gdk.cpp | 46 + src/display/nr-plain-stuff-gdk.h | 32 + src/display/nr-plain-stuff.cpp | 94 + src/display/nr-plain-stuff.h | 33 + src/display/sodipodi-ctrl.cpp | 494 + src/display/sodipodi-ctrl.h | 65 + src/display/sodipodi-ctrlrect.cpp | 330 + src/display/sodipodi-ctrlrect.h | 70 + src/display/sp-canvas-util.cpp | 307 + src/display/sp-canvas-util.h | 61 + src/display/sp-canvas.cpp | 2074 ++ src/display/sp-canvas.h | 186 + src/display/sp-ctrlline.cpp | 217 + src/display/sp-ctrlline.h | 45 + src/display/sp-ctrlquadr.cpp | 210 + src/display/sp-ctrlquadr.h | 43 + src/display/testnr.cpp | 24 + src/document-private.h | 67 + src/document-undo.cpp | 322 + src/document.cpp | 1093 + src/document.h | 218 + src/dom/.cvsignore | 3 + src/dom/001.css | 214 + src/dom/Filt.java | 77 + src/dom/ImplGen.java | 180 + src/dom/Makefile.static | 71 + src/dom/Makefile_insert | 57 + src/dom/README | 25 + src/dom/acid.css | 110 + src/dom/base.css | 32 + src/dom/charclass.cpp | 451 + src/dom/charclass.h | 185 + src/dom/css.h | 4121 ++++ src/dom/css.idl | 633 + src/dom/cssparser.cpp | 1669 ++ src/dom/cssparser.h | 286 + src/dom/cssprop.txt | 123 + src/dom/dom.h | 2204 +++ src/dom/dom.idl | 548 + src/dom/domimpl.cpp | 2984 +++ src/dom/domimpl.h | 2002 ++ src/dom/domstream.cpp | 874 + src/dom/domstream.h | 674 + src/dom/domstring.cpp | 414 + src/dom/domstring.h | 309 + src/dom/domstringimpl.h | 107 + src/dom/events.h | 1351 ++ src/dom/events.idl | 298 + src/dom/inkscape.css | 493 + src/dom/inkscape.logo.svg | 52 + src/dom/js/Makefile | 1471 ++ src/dom/js/README | 20 + src/dom/js/fdlibm/e_acos.c | 147 + src/dom/js/fdlibm/e_acosh.c | 105 + src/dom/js/fdlibm/e_asin.c | 156 + src/dom/js/fdlibm/e_atan2.c | 165 + src/dom/js/fdlibm/e_atanh.c | 110 + src/dom/js/fdlibm/e_cosh.c | 133 + src/dom/js/fdlibm/e_exp.c | 202 + src/dom/js/fdlibm/e_fmod.c | 184 + src/dom/js/fdlibm/e_gamma.c | 71 + src/dom/js/fdlibm/e_gamma_r.c | 70 + src/dom/js/fdlibm/e_hypot.c | 173 + src/dom/js/fdlibm/e_j0.c | 524 + src/dom/js/fdlibm/e_j1.c | 523 + src/dom/js/fdlibm/e_jn.c | 315 + src/dom/js/fdlibm/e_lgamma.c | 71 + src/dom/js/fdlibm/e_lgamma_r.c | 347 + src/dom/js/fdlibm/e_log.c | 184 + src/dom/js/fdlibm/e_log10.c | 134 + src/dom/js/fdlibm/e_pow.c | 386 + src/dom/js/fdlibm/e_rem_pio2.c | 221 + src/dom/js/fdlibm/e_remainder.c | 120 + src/dom/js/fdlibm/e_scalb.c | 89 + src/dom/js/fdlibm/e_sinh.c | 122 + src/dom/js/fdlibm/e_sqrt.c | 497 + src/dom/js/fdlibm/fdlibm.h | 273 + src/dom/js/fdlibm/k_cos.c | 134 + src/dom/js/fdlibm/k_rem_pio2.c | 354 + src/dom/js/fdlibm/k_sin.c | 114 + src/dom/js/fdlibm/k_standard.c | 785 + src/dom/js/fdlibm/k_tan.c | 170 + src/dom/js/fdlibm/s_asinh.c | 101 + src/dom/js/fdlibm/s_atan.c | 175 + src/dom/js/fdlibm/s_cbrt.c | 133 + src/dom/js/fdlibm/s_ceil.c | 120 + src/dom/js/fdlibm/s_copysign.c | 72 + src/dom/js/fdlibm/s_cos.c | 118 + src/dom/js/fdlibm/s_erf.c | 356 + src/dom/js/fdlibm/s_expm1.c | 267 + src/dom/js/fdlibm/s_fabs.c | 70 + src/dom/js/fdlibm/s_finite.c | 71 + src/dom/js/fdlibm/s_floor.c | 121 + src/dom/js/fdlibm/s_frexp.c | 99 + src/dom/js/fdlibm/s_ilogb.c | 85 + src/dom/js/fdlibm/s_isnan.c | 74 + src/dom/js/fdlibm/s_ldexp.c | 66 + src/dom/js/fdlibm/s_lib_version.c | 73 + src/dom/js/fdlibm/s_log1p.c | 211 + src/dom/js/fdlibm/s_logb.c | 79 + src/dom/js/fdlibm/s_matherr.c | 64 + src/dom/js/fdlibm/s_modf.c | 132 + src/dom/js/fdlibm/s_nextafter.c | 124 + src/dom/js/fdlibm/s_rint.c | 131 + src/dom/js/fdlibm/s_scalbn.c | 107 + src/dom/js/fdlibm/s_signgam.c | 40 + src/dom/js/fdlibm/s_significand.c | 68 + src/dom/js/fdlibm/s_sin.c | 118 + src/dom/js/fdlibm/s_tan.c | 112 + src/dom/js/fdlibm/s_tanh.c | 122 + src/dom/js/fdlibm/w_acos.c | 78 + src/dom/js/fdlibm/w_acosh.c | 78 + src/dom/js/fdlibm/w_asin.c | 80 + src/dom/js/fdlibm/w_atan2.c | 79 + src/dom/js/fdlibm/w_atanh.c | 81 + src/dom/js/fdlibm/w_cosh.c | 77 + src/dom/js/fdlibm/w_exp.c | 88 + src/dom/js/fdlibm/w_fmod.c | 78 + src/dom/js/fdlibm/w_gamma.c | 85 + src/dom/js/fdlibm/w_gamma_r.c | 81 + src/dom/js/fdlibm/w_hypot.c | 78 + src/dom/js/fdlibm/w_j0.c | 105 + src/dom/js/fdlibm/w_j1.c | 106 + src/dom/js/fdlibm/w_jn.c | 128 + src/dom/js/fdlibm/w_lgamma.c | 85 + src/dom/js/fdlibm/w_lgamma_r.c | 81 + src/dom/js/fdlibm/w_log.c | 78 + src/dom/js/fdlibm/w_log10.c | 81 + src/dom/js/fdlibm/w_pow.c | 99 + src/dom/js/fdlibm/w_remainder.c | 77 + src/dom/js/fdlibm/w_scalb.c | 95 + src/dom/js/fdlibm/w_sinh.c | 77 + src/dom/js/fdlibm/w_sqrt.c | 77 + src/dom/js/js.c | 2333 +++ src/dom/js/js.mak | 4025 ++++ src/dom/js/js.msg | 251 + src/dom/js/jsapi.c | 4187 ++++ src/dom/js/jsapi.h | 1752 ++ src/dom/js/jsarena.c | 565 + src/dom/js/jsarena.h | 302 + src/dom/js/jsarray.c | 1429 ++ src/dom/js/jsarray.h | 77 + src/dom/js/jsatom.c | 911 + src/dom/js/jsatom.h | 409 + src/dom/js/jsautocfg.h | 50 + src/dom/js/jsbit.h | 113 + src/dom/js/jsbool.c | 244 + src/dom/js/jsbool.h | 62 + src/dom/js/jsclist.h | 139 + src/dom/js/jscntxt.c | 702 + src/dom/js/jscntxt.h | 496 + src/dom/js/jscompat.h | 57 + src/dom/js/jsconfig.h | 489 + src/dom/js/jscpucfg.c | 377 + src/dom/js/jscpucfg.h | 200 + src/dom/js/jsdate.c | 2234 +++ src/dom/js/jsdate.h | 118 + src/dom/js/jsdbgapi.c | 1240 ++ src/dom/js/jsdbgapi.h | 345 + src/dom/js/jsdhash.c | 763 + src/dom/js/jsdhash.h | 573 + src/dom/js/jsdtoa.c | 3155 +++ src/dom/js/jsdtoa.h | 130 + src/dom/js/jsemit.c | 4471 +++++ src/dom/js/jsemit.h | 547 + src/dom/js/jsexn.c | 1081 + src/dom/js/jsexn.h | 102 + src/dom/js/jsfile.c | 2610 +++ src/dom/js/jsfile.h | 50 + src/dom/js/jsfun.c | 2059 ++ src/dom/js/jsfun.h | 151 + src/dom/js/jsgc.c | 1423 ++ src/dom/js/jsgc.h | 230 + src/dom/js/jshash.c | 479 + src/dom/js/jshash.h | 152 + src/dom/js/jsinterp.c | 4284 ++++ src/dom/js/jsinterp.h | 292 + src/dom/js/jslibmath.h | 290 + src/dom/js/jslock.c | 1241 ++ src/dom/js/jslock.h | 289 + src/dom/js/jslocko.asm | 59 + src/dom/js/jslog2.c | 83 + src/dom/js/jslong.c | 281 + src/dom/js/jslong.h | 437 + src/dom/js/jsmath.c | 477 + src/dom/js/jsmath.h | 55 + src/dom/js/jsnum.c | 1058 + src/dom/js/jsnum.h | 280 + src/dom/js/jsobj.c | 3900 ++++ src/dom/js/jsobj.h | 464 + src/dom/js/jsopcode.c | 2660 +++ src/dom/js/jsopcode.h | 273 + src/dom/js/jsopcode.tbl | 333 + src/dom/js/jsosdep.h | 127 + src/dom/js/jsotypes.h | 211 + src/dom/js/jsparse.c | 3547 ++++ src/dom/js/jsparse.h | 337 + src/dom/js/jsprf.c | 1212 ++ src/dom/js/jsprf.h | 148 + src/dom/js/jsprvtd.h | 174 + src/dom/js/jspubtd.h | 564 + src/dom/js/jsregexp.c | 3773 ++++ src/dom/js/jsregexp.h | 168 + src/dom/js/jsscan.c | 1315 ++ src/dom/js/jsscan.h | 264 + src/dom/js/jsscope.c | 1581 ++ src/dom/js/jsscope.h | 386 + src/dom/js/jsscript.c | 1287 ++ src/dom/js/jsscript.h | 178 + src/dom/js/jsshell.msg | 50 + src/dom/js/jsstddef.h | 83 + src/dom/js/jsstr.c | 4502 +++++ src/dom/js/jsstr.h | 439 + src/dom/js/jstypes.h | 388 + src/dom/js/jsutil.c | 157 + src/dom/js/jsutil.h | 106 + src/dom/js/jsxdrapi.c | 690 + src/dom/js/jsxdrapi.h | 193 + src/dom/js/prmjtime.c | 646 + src/dom/js/prmjtime.h | 95 + src/dom/js/resource.h | 15 + src/dom/knut.svg | 51 + src/dom/ls.h | 940 + src/dom/ls.idl | 171 + src/dom/lsimpl.cpp | 437 + src/dom/lsimpl.cpp.orig | 671 + src/dom/lsimpl.h | 376 + src/dom/makefile.in | 17 + src/dom/meyerweb.css | 181 + src/dom/mingwenv.bat | 2 + src/dom/phoebedom.h | 86 + src/dom/prop-css.cpp | 1161 ++ src/dom/prop-css.txt | 1082 + src/dom/prop-css2.cpp | 1304 ++ src/dom/prop-svg.cpp | 735 + src/dom/prop-svg.txt | 651 + src/dom/ranges.idl | 122 + src/dom/sandb1.css | 149 + src/dom/smil.h | 2167 ++ src/dom/smil.idl | 369 + src/dom/smilimpl.cpp | 854 + src/dom/smilimpl.h | 765 + src/dom/stringstream.cpp | 161 + src/dom/stringstream.h | 126 + src/dom/stylesheets.h | 465 + src/dom/stylesheets.idl | 71 + src/dom/svg.h | 4474 +++++ src/dom/svg.idl | 1751 ++ src/dom/svgimpl.cpp | 1870 ++ src/dom/svgimpl.h | 5108 +++++ src/dom/svglsimpl.cpp | 90 + src/dom/svglsimpl.h | 217 + src/dom/svgparser.cpp | 762 + src/dom/svgparser.h | 151 + src/dom/svgtypes.h | 6889 +++++++ src/dom/testdom.cpp | 110 + src/dom/testsvg.cpp | 97 + src/dom/transforms.svg | 39 + src/dom/traversal.h | 444 + src/dom/traversal.idl | 102 + src/dom/uri.cpp | 454 + src/dom/uri.h | 186 + src/dom/uristream.cpp | 456 + src/dom/uristream.h | 207 + src/dom/uritest.cpp | 76 + src/dom/views.h | 1961 ++ src/dom/views.idl | 254 + src/dom/xmlreader.cpp | 981 + src/dom/xmlreader.h | 129 + src/dom/xpath.h | 349 + src/dom/xpath.idl | 115 + src/dom/xpathimpl.cpp | 236 + src/dom/xpathimpl.h | 263 + src/dom/xpathparser.cpp | 1910 ++ src/dom/xpathparser.h | 845 + src/dom/xpathtest.cpp | 1399 ++ src/dom/xpathtests.cpp | 1290 ++ src/draw-anchor.cpp | 99 + src/draw-anchor.h | 44 + src/draw-context.cpp | 664 + src/draw-context.h | 100 + src/dropper-context.cpp | 428 + src/dropper-context.h | 58 + src/dyna-draw-context.cpp | 958 + src/dyna-draw-context.h | 116 + src/enums.h | 77 + src/event-context.cpp | 1033 + src/event-context.h | 139 + src/extension/.cvsignore | 5 + src/extension/Makefile_insert | 37 + src/extension/api.cpp | 92 + src/extension/db.cpp | 239 + src/extension/db.h | 81 + src/extension/dependency.cpp | 261 + src/extension/dependency.h | 83 + src/extension/dxf2svg/GPL.txt | 340 + src/extension/dxf2svg/LGPL.txt | 504 + src/extension/dxf2svg/Makefile | 21 + src/extension/dxf2svg/README | 41 + src/extension/dxf2svg/aci2rgb.cpp | 114 + src/extension/dxf2svg/blocks.cpp | 88 + src/extension/dxf2svg/blocks.h | 38 + src/extension/dxf2svg/dxf2svg.cpp | 106 + src/extension/dxf2svg/dxf_input.inx | 16 + src/extension/dxf2svg/dxf_input_windows.inx | 17 + src/extension/dxf2svg/entities.cpp | 1031 + src/extension/dxf2svg/entities.h | 256 + src/extension/dxf2svg/entities2elements.cpp | 681 + src/extension/dxf2svg/entities2elements.h | 53 + src/extension/dxf2svg/read_dxf.cpp | 273 + src/extension/dxf2svg/read_dxf.h | 31 + src/extension/dxf2svg/tables.cpp | 211 + src/extension/dxf2svg/tables.h | 71 + src/extension/dxf2svg/tables2svg_info.cpp | 35 + src/extension/dxf2svg/tables2svg_info.h | 9 + src/extension/dxf2svg/test_dxf.cpp | 99 + src/extension/effect.cpp | 200 + src/extension/effect.h | 91 + src/extension/error-file.cpp | 109 + src/extension/error-file.h | 45 + src/extension/extension-forward.h | 34 + src/extension/extension.cpp | 637 + src/extension/extension.h | 207 + src/extension/implementation/.cvsignore | 5 + src/extension/implementation/Makefile_insert | 13 + src/extension/implementation/implementation.cpp | 181 + src/extension/implementation/implementation.h | 127 + src/extension/implementation/makefile.in | 17 + src/extension/implementation/plugin-link.h | 68 + src/extension/implementation/plugin.cpp | 328 + src/extension/implementation/plugin.h | 121 + src/extension/implementation/script.cpp | 1053 + src/extension/implementation/script.h | 86 + src/extension/init.cpp | 243 + src/extension/init.h | 36 + src/extension/input.cpp | 261 + src/extension/input.h | 60 + src/extension/internal/.cvsignore | 5 + src/extension/internal/Makefile_insert | 42 + src/extension/internal/bluredge.cpp | 157 + src/extension/internal/bluredge.h | 43 + src/extension/internal/eps-out.cpp | 108 + src/extension/internal/eps-out.h | 47 + src/extension/internal/gdkpixbuf-input.cpp | 176 + src/extension/internal/gdkpixbuf-input.h | 31 + src/extension/internal/gimpgrad.cpp | 233 + src/extension/internal/gimpgrad.h | 49 + src/extension/internal/gnome.cpp | 443 + src/extension/internal/gnome.h | 58 + src/extension/internal/grid.cpp | 272 + src/extension/internal/grid.h | 43 + src/extension/internal/latex-pstricks-out.cpp | 128 + src/extension/internal/latex-pstricks-out.h | 49 + src/extension/internal/latex-pstricks.cpp | 362 + src/extension/internal/latex-pstricks.h | 70 + src/extension/internal/makefile.in | 17 + src/extension/internal/pov-out.cpp | 482 + src/extension/internal/pov-out.h | 52 + src/extension/internal/ps-out.cpp | 94 + src/extension/internal/ps-out.h | 45 + src/extension/internal/ps.cpp | 1264 ++ src/extension/internal/ps.h | 108 + src/extension/internal/svg.cpp | 248 + src/extension/internal/svg.h | 48 + src/extension/internal/svgz.cpp | 99 + src/extension/internal/svgz.h | 41 + src/extension/internal/win32.cpp | 499 + src/extension/internal/win32.h | 91 + src/extension/makefile.in | 17 + src/extension/output.cpp | 262 + src/extension/output.h | 58 + src/extension/parameter.cpp | 778 + src/extension/parameter.h | 71 + src/extension/plugin/.cvsignore | 7 + src/extension/plugin/Makefile_insert | 30 + src/extension/plugin/makefile.in | 17 + src/extension/prefdialog.cpp | 48 + src/extension/prefdialog.h | 44 + src/extension/print.cpp | 125 + src/extension/print.h | 84 + src/extension/script/.cvsignore | 3 + src/extension/script/InkscapeBinding.cpp | 199 + src/extension/script/InkscapeBinding.h | 168 + src/extension/script/InkscapeInterpreter.cpp | 93 + src/extension/script/InkscapeInterpreter.h | 68 + src/extension/script/InkscapePerl.cpp | 80 + src/extension/script/InkscapePerl.h | 73 + src/extension/script/InkscapePython.cpp | 120 + src/extension/script/InkscapePython.h | 74 + src/extension/script/InkscapeScript.cpp | 177 + src/extension/script/InkscapeScript.h | 87 + src/extension/script/Makefile.tmp | 89 + src/extension/script/Makefile_insert | 36 + src/extension/script/README.txt | 41 + src/extension/script/bindtest.cpp | 86 + src/extension/script/cpptest.cpp | 22 + src/extension/script/inkscape_perl.i | 77 + src/extension/script/inkscape_perl.pm | 178 + src/extension/script/inkscape_perl.pm.h | 187 + src/extension/script/inkscape_perl_wrap.cpp | 1334 ++ src/extension/script/inkscape_py.i | 6 + src/extension/script/inkscape_py.py | 130 + src/extension/script/inkscape_py.py.h | 139 + src/extension/script/inkscape_py_wrap.cpp | 1408 ++ src/extension/script/js/Makefile | 1471 ++ src/extension/script/js/README | 20 + src/extension/script/js/fdlibm/e_acos.c | 147 + src/extension/script/js/fdlibm/e_acosh.c | 105 + src/extension/script/js/fdlibm/e_asin.c | 156 + src/extension/script/js/fdlibm/e_atan2.c | 165 + src/extension/script/js/fdlibm/e_atanh.c | 110 + src/extension/script/js/fdlibm/e_cosh.c | 133 + src/extension/script/js/fdlibm/e_exp.c | 202 + src/extension/script/js/fdlibm/e_fmod.c | 184 + src/extension/script/js/fdlibm/e_gamma.c | 71 + src/extension/script/js/fdlibm/e_gamma_r.c | 70 + src/extension/script/js/fdlibm/e_hypot.c | 173 + src/extension/script/js/fdlibm/e_j0.c | 524 + src/extension/script/js/fdlibm/e_j1.c | 523 + src/extension/script/js/fdlibm/e_jn.c | 315 + src/extension/script/js/fdlibm/e_lgamma.c | 71 + src/extension/script/js/fdlibm/e_lgamma_r.c | 347 + src/extension/script/js/fdlibm/e_log.c | 184 + src/extension/script/js/fdlibm/e_log10.c | 134 + src/extension/script/js/fdlibm/e_pow.c | 386 + src/extension/script/js/fdlibm/e_rem_pio2.c | 221 + src/extension/script/js/fdlibm/e_remainder.c | 120 + src/extension/script/js/fdlibm/e_scalb.c | 89 + src/extension/script/js/fdlibm/e_sinh.c | 122 + src/extension/script/js/fdlibm/e_sqrt.c | 497 + src/extension/script/js/fdlibm/fdlibm.h | 273 + src/extension/script/js/fdlibm/k_cos.c | 134 + src/extension/script/js/fdlibm/k_rem_pio2.c | 354 + src/extension/script/js/fdlibm/k_sin.c | 114 + src/extension/script/js/fdlibm/k_standard.c | 785 + src/extension/script/js/fdlibm/k_tan.c | 170 + src/extension/script/js/fdlibm/s_asinh.c | 101 + src/extension/script/js/fdlibm/s_atan.c | 175 + src/extension/script/js/fdlibm/s_cbrt.c | 133 + src/extension/script/js/fdlibm/s_ceil.c | 120 + src/extension/script/js/fdlibm/s_copysign.c | 72 + src/extension/script/js/fdlibm/s_cos.c | 118 + src/extension/script/js/fdlibm/s_erf.c | 356 + src/extension/script/js/fdlibm/s_expm1.c | 267 + src/extension/script/js/fdlibm/s_fabs.c | 70 + src/extension/script/js/fdlibm/s_finite.c | 71 + src/extension/script/js/fdlibm/s_floor.c | 121 + src/extension/script/js/fdlibm/s_frexp.c | 99 + src/extension/script/js/fdlibm/s_ilogb.c | 85 + src/extension/script/js/fdlibm/s_isnan.c | 74 + src/extension/script/js/fdlibm/s_ldexp.c | 66 + src/extension/script/js/fdlibm/s_lib_version.c | 73 + src/extension/script/js/fdlibm/s_log1p.c | 211 + src/extension/script/js/fdlibm/s_logb.c | 79 + src/extension/script/js/fdlibm/s_matherr.c | 64 + src/extension/script/js/fdlibm/s_modf.c | 132 + src/extension/script/js/fdlibm/s_nextafter.c | 124 + src/extension/script/js/fdlibm/s_rint.c | 131 + src/extension/script/js/fdlibm/s_scalbn.c | 107 + src/extension/script/js/fdlibm/s_signgam.c | 40 + src/extension/script/js/fdlibm/s_significand.c | 68 + src/extension/script/js/fdlibm/s_sin.c | 118 + src/extension/script/js/fdlibm/s_tan.c | 112 + src/extension/script/js/fdlibm/s_tanh.c | 122 + src/extension/script/js/fdlibm/w_acos.c | 78 + src/extension/script/js/fdlibm/w_acosh.c | 78 + src/extension/script/js/fdlibm/w_asin.c | 80 + src/extension/script/js/fdlibm/w_atan2.c | 79 + src/extension/script/js/fdlibm/w_atanh.c | 81 + src/extension/script/js/fdlibm/w_cosh.c | 77 + src/extension/script/js/fdlibm/w_exp.c | 88 + src/extension/script/js/fdlibm/w_fmod.c | 78 + src/extension/script/js/fdlibm/w_gamma.c | 85 + src/extension/script/js/fdlibm/w_gamma_r.c | 81 + src/extension/script/js/fdlibm/w_hypot.c | 78 + src/extension/script/js/fdlibm/w_j0.c | 105 + src/extension/script/js/fdlibm/w_j1.c | 106 + src/extension/script/js/fdlibm/w_jn.c | 128 + src/extension/script/js/fdlibm/w_lgamma.c | 85 + src/extension/script/js/fdlibm/w_lgamma_r.c | 81 + src/extension/script/js/fdlibm/w_log.c | 78 + src/extension/script/js/fdlibm/w_log10.c | 81 + src/extension/script/js/fdlibm/w_pow.c | 99 + src/extension/script/js/fdlibm/w_remainder.c | 77 + src/extension/script/js/fdlibm/w_scalb.c | 95 + src/extension/script/js/fdlibm/w_sinh.c | 77 + src/extension/script/js/fdlibm/w_sqrt.c | 77 + src/extension/script/js/js.c | 2333 +++ src/extension/script/js/js.mak | 4025 ++++ src/extension/script/js/js.msg | 251 + src/extension/script/js/jsapi.c | 4187 ++++ src/extension/script/js/jsapi.h | 1752 ++ src/extension/script/js/jsarena.c | 565 + src/extension/script/js/jsarena.h | 302 + src/extension/script/js/jsarray.c | 1429 ++ src/extension/script/js/jsarray.h | 77 + src/extension/script/js/jsatom.c | 911 + src/extension/script/js/jsatom.h | 409 + src/extension/script/js/jsautocfg.h | 50 + src/extension/script/js/jsbit.h | 113 + src/extension/script/js/jsbool.c | 244 + src/extension/script/js/jsbool.h | 62 + src/extension/script/js/jsclist.h | 139 + src/extension/script/js/jscntxt.c | 702 + src/extension/script/js/jscntxt.h | 496 + src/extension/script/js/jscompat.h | 57 + src/extension/script/js/jsconfig.h | 489 + src/extension/script/js/jscpucfg.c | 377 + src/extension/script/js/jscpucfg.h | 200 + src/extension/script/js/jsdate.c | 2234 +++ src/extension/script/js/jsdate.h | 118 + src/extension/script/js/jsdbgapi.c | 1240 ++ src/extension/script/js/jsdbgapi.h | 345 + src/extension/script/js/jsdhash.c | 763 + src/extension/script/js/jsdhash.h | 573 + src/extension/script/js/jsdtoa.c | 3155 +++ src/extension/script/js/jsdtoa.h | 130 + src/extension/script/js/jsemit.c | 4471 +++++ src/extension/script/js/jsemit.h | 547 + src/extension/script/js/jsexn.c | 1081 + src/extension/script/js/jsexn.h | 102 + src/extension/script/js/jsfile.c | 2610 +++ src/extension/script/js/jsfile.h | 50 + src/extension/script/js/jsfun.c | 2059 ++ src/extension/script/js/jsfun.h | 151 + src/extension/script/js/jsgc.c | 1423 ++ src/extension/script/js/jsgc.h | 230 + src/extension/script/js/jshash.c | 479 + src/extension/script/js/jshash.h | 152 + src/extension/script/js/jsinterp.c | 4284 ++++ src/extension/script/js/jsinterp.h | 292 + src/extension/script/js/jslibmath.h | 290 + src/extension/script/js/jslock.c | 1241 ++ src/extension/script/js/jslock.h | 289 + src/extension/script/js/jslocko.asm | 59 + src/extension/script/js/jslog2.c | 83 + src/extension/script/js/jslong.c | 281 + src/extension/script/js/jslong.h | 437 + src/extension/script/js/jsmath.c | 477 + src/extension/script/js/jsmath.h | 55 + src/extension/script/js/jsnum.c | 1058 + src/extension/script/js/jsnum.h | 280 + src/extension/script/js/jsobj.c | 3900 ++++ src/extension/script/js/jsobj.h | 464 + src/extension/script/js/jsopcode.c | 2660 +++ src/extension/script/js/jsopcode.h | 273 + src/extension/script/js/jsopcode.tbl | 333 + src/extension/script/js/jsosdep.h | 127 + src/extension/script/js/jsotypes.h | 211 + src/extension/script/js/jsparse.c | 3547 ++++ src/extension/script/js/jsparse.h | 337 + src/extension/script/js/jsprf.c | 1212 ++ src/extension/script/js/jsprf.h | 148 + src/extension/script/js/jsprvtd.h | 174 + src/extension/script/js/jspubtd.h | 564 + src/extension/script/js/jsregexp.c | 3773 ++++ src/extension/script/js/jsregexp.h | 168 + src/extension/script/js/jsscan.c | 1315 ++ src/extension/script/js/jsscan.h | 264 + src/extension/script/js/jsscope.c | 1581 ++ src/extension/script/js/jsscope.h | 386 + src/extension/script/js/jsscript.c | 1287 ++ src/extension/script/js/jsscript.h | 178 + src/extension/script/js/jsshell.msg | 50 + src/extension/script/js/jsstddef.h | 83 + src/extension/script/js/jsstr.c | 4502 +++++ src/extension/script/js/jsstr.h | 439 + src/extension/script/js/jstypes.h | 388 + src/extension/script/js/jsutil.c | 157 + src/extension/script/js/jsutil.h | 106 + src/extension/script/js/jsxdrapi.c | 690 + src/extension/script/js/jsxdrapi.h | 193 + src/extension/script/js/prmjtime.c | 646 + src/extension/script/js/prmjtime.h | 95 + src/extension/script/js/resource.h | 15 + src/extension/script/makefile.in | 17 + src/extension/script/quotefile.pl | 82 + src/extension/script/runme.py | 8 + src/extension/script/wrap_swig_module.sh | 27 + src/extension/system.cpp | 476 + src/extension/system.h | 44 + src/extension/timer.cpp | 214 + src/extension/timer.h | 72 + src/extract-uri-test.cpp | 56 + src/extract-uri.cpp | 40 + src/extract-uri.h | 20 + src/file.cpp | 1337 ++ src/file.h | 171 + src/fill-or-stroke.h | 9 + src/fixes.cpp | 193 + src/fontsize-expansion.cpp | 30 + src/fontsize-expansion.h | 9 + src/forward.h | 208 + src/gc-alloc.h | 89 + src/gc-anchored.cpp | 39 + src/gc-anchored.h | 185 + src/gc-core.h | 212 + src/gc-finalized.h | 155 + src/gc-managed.h | 84 + src/gc.cpp | 279 + src/geom.cpp | 172 + src/geom.h | 30 + src/gnuc-attribute.h | 11 + src/gradient-chemistry.cpp | 1133 ++ src/gradient-chemistry.h | 82 + src/gradient-context.cpp | 468 + src/gradient-context.h | 56 + src/gradient-drag.cpp | 1108 ++ src/gradient-drag.h | 143 + src/grid-snapper.cpp | 78 + src/grid-snapper.h | 46 + src/guide-snapper.cpp | 52 + src/guide-snapper.h | 52 + src/help.cpp | 59 + src/help.h | 35 + src/helper/.cvsignore | 9 + src/helper/HACKING | 35 + src/helper/Makefile_insert | 51 + src/helper/action.cpp | 228 + src/helper/action.h | 91 + src/helper/gnome-utils.cpp | 108 + src/helper/gnome-utils.h | 36 + src/helper/helper-forward.h | 35 + src/helper/makefile.in | 17 + src/helper/png-write.cpp | 205 + src/helper/png-write.h | 23 + src/helper/sp-marshal.cpp.mingw | 520 + src/helper/sp-marshal.h.mingw | 125 + src/helper/sp-marshal.list | 16 + src/helper/stlport.h | 26 + src/helper/stock-items.cpp | 267 + src/helper/stock-items.h | 20 + src/helper/unit-menu.cpp | 372 + src/helper/unit-menu.h | 59 + src/helper/units-test.cpp | 115 + src/helper/units.cpp | 260 + src/helper/units.h | 146 + src/helper/window.cpp | 47 + src/helper/window.h | 28 + src/inkjar/.cvsignore | 5 + src/inkjar/Makefile_insert | 10 + src/inkjar/jar.cpp | 541 + src/inkjar/jar.h | 151 + src/inkjar/makefile.in | 17 + src/inkscape-private.h | 62 + src/inkscape-stock.cpp | 51 + src/inkscape-stock.h | 150 + src/inkscape.cpp | 1413 ++ src/inkscape.h | 86 + src/inkscape.rc | 3 + src/inkscape_version.h.mingw | 1 + src/inkview.cpp | 491 + src/interface.cpp | 1197 ++ src/interface.h | 85 + src/io/.cvsignore | 5 + src/io/Makefile.tst | 47 + src/io/Makefile_insert | 29 + src/io/base64stream.cpp | 315 + src/io/base64stream.h | 122 + src/io/crystalegg.xml | 769 + src/io/doc2html.xsl | 63 + src/io/ftos.cpp | 482 + src/io/ftos.h | 54 + src/io/gzipstream.cpp | 458 + src/io/gzipstream.h | 121 + src/io/inkscapestream.cpp | 842 + src/io/inkscapestream.h | 669 + src/io/makefile.in | 17 + src/io/simple-sax.cpp | 1493 ++ src/io/simple-sax.h | 97 + src/io/streamtest.cpp | 244 + src/io/stringstream.cpp | 128 + src/io/stringstream.h | 96 + src/io/sys.cpp | 300 + src/io/sys.h | 51 + src/io/uristream.cpp | 499 + src/io/uristream.h | 173 + src/io/xsltstream.cpp | 220 + src/io/xsltstream.h | 124 + src/isnan.h | 57 + src/jabber_whiteboard/.cvsignore | 2 + src/jabber_whiteboard/Makefile_insert | 77 + src/jabber_whiteboard/buddy-list-manager.cpp | 84 + src/jabber_whiteboard/buddy-list-manager.h | 59 + src/jabber_whiteboard/callbacks.cpp | 206 + src/jabber_whiteboard/callbacks.h | 79 + src/jabber_whiteboard/chat-handler.cpp | 249 + src/jabber_whiteboard/chat-handler.h | 63 + src/jabber_whiteboard/connection-establishment.cpp | 307 + src/jabber_whiteboard/defines.h | 98 + src/jabber_whiteboard/deserializer.cpp | 417 + src/jabber_whiteboard/deserializer.h | 285 + src/jabber_whiteboard/empty.cpp | 14 + src/jabber_whiteboard/error-codes.h | 41 + src/jabber_whiteboard/internal-constants.cpp | 76 + src/jabber_whiteboard/internal-constants.h | 83 + .../invitation-confirm-dialog.cpp | 66 + src/jabber_whiteboard/invitation-confirm-dialog.h | 69 + src/jabber_whiteboard/jabber-handlers.cpp | 65 + src/jabber_whiteboard/jabber-handlers.h | 86 + src/jabber_whiteboard/makefile.in | 17 + src/jabber_whiteboard/message-aggregator.cpp | 64 + src/jabber_whiteboard/message-aggregator.h | 135 + src/jabber_whiteboard/message-contexts.cpp | 134 + src/jabber_whiteboard/message-contexts.h | 39 + src/jabber_whiteboard/message-handler.cpp | 456 + src/jabber_whiteboard/message-handler.h | 88 + src/jabber_whiteboard/message-node.h | 134 + src/jabber_whiteboard/message-processors.cpp | 330 + src/jabber_whiteboard/message-processors.h | 176 + src/jabber_whiteboard/message-queue.cpp | 138 + src/jabber_whiteboard/message-queue.h | 177 + src/jabber_whiteboard/message-tags.cpp | 67 + src/jabber_whiteboard/message-tags.h | 139 + src/jabber_whiteboard/message-utilities.cpp | 452 + src/jabber_whiteboard/message-utilities.h | 84 + .../node-tracker-event-tracker.cpp | 56 + src/jabber_whiteboard/node-tracker-event-tracker.h | 66 + src/jabber_whiteboard/node-tracker-observer.h | 119 + src/jabber_whiteboard/node-tracker.cpp | 368 + src/jabber_whiteboard/node-tracker.h | 300 + src/jabber_whiteboard/node-utilities.cpp | 119 + src/jabber_whiteboard/node-utilities.h | 61 + src/jabber_whiteboard/pedrodom.cpp | 784 + src/jabber_whiteboard/pedrodom.h | 308 + src/jabber_whiteboard/pedroxmpp.cpp | 4786 +++++ src/jabber_whiteboard/pedroxmpp.h | 1023 + src/jabber_whiteboard/serializer.cpp | 304 + src/jabber_whiteboard/serializer.h | 118 + src/jabber_whiteboard/session-file-player.cpp | 176 + src/jabber_whiteboard/session-file-player.h | 99 + src/jabber_whiteboard/session-file-selector.cpp | 90 + src/jabber_whiteboard/session-file-selector.h | 61 + src/jabber_whiteboard/session-file.cpp | 140 + src/jabber_whiteboard/session-file.h | 78 + src/jabber_whiteboard/session-manager.cpp | 1118 ++ src/jabber_whiteboard/session-manager.h | 568 + src/jabber_whiteboard/tracker-node.h | 94 + src/jabber_whiteboard/typedefs.h | 159 + src/jabber_whiteboard/undo-stack-observer.cpp | 181 + src/jabber_whiteboard/undo-stack-observer.h | 75 + src/knot-enums.h | 58 + src/knot-holder-entity.h | 62 + src/knot.cpp | 926 + src/knot.h | 127 + src/knotholder.cpp | 229 + src/knotholder.h | 79 + src/layer-fns.cpp | 186 + src/layer-fns.h | 37 + src/libavoid/.cvsignore | 3 + src/libavoid/Makefile_insert | 33 + src/libavoid/README | 5 + src/libavoid/connector.cpp | 408 + src/libavoid/connector.h | 93 + src/libavoid/debug.h | 61 + src/libavoid/geometry.cpp | 260 + src/libavoid/geometry.h | 75 + src/libavoid/geomtypes.h | 61 + src/libavoid/graph.cpp | 986 + src/libavoid/graph.h | 127 + src/libavoid/incremental.cpp | 139 + src/libavoid/incremental.h | 39 + src/libavoid/libavoid.h | 40 + src/libavoid/makefile.in | 17 + src/libavoid/makepath.cpp | 462 + src/libavoid/makepath.h | 42 + src/libavoid/polyutil.cpp | 86 + src/libavoid/polyutil.h | 41 + src/libavoid/shape.cpp | 176 + src/libavoid/shape.h | 73 + src/libavoid/static.cpp | 76 + src/libavoid/static.h | 38 + src/libavoid/timer.cpp | 168 + src/libavoid/timer.h | 92 + src/libavoid/vertices.cpp | 438 + src/libavoid/vertices.h | 124 + src/libavoid/visibility.cpp | 653 + src/libavoid/visibility.h | 57 + src/libcroco/.cvsignore | 3 + src/libcroco/Makefile_insert | 64 + src/libcroco/README | 11 + src/libcroco/cr-additional-sel.c | 468 + src/libcroco/cr-additional-sel.h | 112 + src/libcroco/cr-attr-sel.c | 221 + src/libcroco/cr-attr-sel.h | 74 + src/libcroco/cr-cascade.c | 197 + src/libcroco/cr-cascade.h | 74 + src/libcroco/cr-declaration.c | 775 + src/libcroco/cr-declaration.h | 136 + src/libcroco/cr-doc-handler.c | 252 + src/libcroco/cr-doc-handler.h | 298 + src/libcroco/cr-enc-handler.c | 177 + src/libcroco/cr-enc-handler.h | 96 + src/libcroco/cr-fonts.c | 761 + src/libcroco/cr-fonts.h | 314 + src/libcroco/cr-input.c | 1112 ++ src/libcroco/cr-input.h | 174 + src/libcroco/cr-libxml-node-iface.c | 81 + src/libcroco/cr-libxml-node-iface.h | 14 + src/libcroco/cr-node-iface.h | 35 + src/libcroco/cr-num.c | 299 + src/libcroco/cr-num.h | 127 + src/libcroco/cr-om-parser.c | 1107 ++ src/libcroco/cr-om-parser.h | 98 + src/libcroco/cr-parser.c | 4408 +++++ src/libcroco/cr-parser.h | 128 + src/libcroco/cr-parsing-location.c | 153 + src/libcroco/cr-parsing-location.h | 70 + src/libcroco/cr-prop-list.c | 360 + src/libcroco/cr-prop-list.h | 80 + src/libcroco/cr-pseudo.c | 153 + src/libcroco/cr-pseudo.h | 64 + src/libcroco/cr-rgb.c | 621 + src/libcroco/cr-rgb.h | 94 + src/libcroco/cr-sel-eng.c | 1541 ++ src/libcroco/cr-sel-eng.h | 112 + src/libcroco/cr-selector.c | 277 + src/libcroco/cr-selector.h | 95 + src/libcroco/cr-simple-sel.c | 308 + src/libcroco/cr-simple-sel.h | 130 + src/libcroco/cr-statement.c | 2608 +++ src/libcroco/cr-statement.h | 440 + src/libcroco/cr-string.c | 168 + src/libcroco/cr-string.h | 76 + src/libcroco/cr-style.c | 2851 +++ src/libcroco/cr-style.h | 339 + src/libcroco/cr-stylesheet.c | 178 + src/libcroco/cr-stylesheet.h | 102 + src/libcroco/cr-term.c | 791 + src/libcroco/cr-term.h | 190 + src/libcroco/cr-tknzr.c | 2760 +++ src/libcroco/cr-tknzr.h | 115 + src/libcroco/cr-token.c | 635 + src/libcroco/cr-token.h | 212 + src/libcroco/cr-utils.c | 1343 ++ src/libcroco/cr-utils.h | 249 + src/libcroco/libcroco.h | 48 + src/libcroco/makefile.in | 17 + src/libnr/.cvsignore | 13 + src/libnr/Makefile_insert | 167 + src/libnr/have_mmx.S | 47 + src/libnr/in-svg-plane-test.cpp | 58 + src/libnr/in-svg-plane-test.h | 81 + src/libnr/in-svg-plane.h | 33 + src/libnr/libnr.def | 89 + src/libnr/makefile.in | 17 + src/libnr/n-art-bpath.h | 61 + src/libnr/nr-blit.cpp | 300 + src/libnr/nr-blit.h | 32 + src/libnr/nr-compose-transform.cpp | 360 + src/libnr/nr-compose-transform.h | 43 + src/libnr/nr-compose.cpp | 986 + src/libnr/nr-compose.h | 69 + src/libnr/nr-convex-hull-ops.h | 29 + src/libnr/nr-convex-hull.h | 49 + src/libnr/nr-coord.h | 29 + src/libnr/nr-dim2.h | 22 + src/libnr/nr-forward.h | 42 + src/libnr/nr-gradient.cpp | 317 + src/libnr/nr-gradient.h | 47 + src/libnr/nr-i-coord.h | 25 + src/libnr/nr-macros.h | 71 + src/libnr/nr-matrix-div.cpp | 22 + src/libnr/nr-matrix-div.h | 21 + src/libnr/nr-matrix-fns.cpp | 54 + src/libnr/nr-matrix-fns.h | 50 + src/libnr/nr-matrix-ops.h | 45 + src/libnr/nr-matrix-rotate-ops.cpp | 18 + src/libnr/nr-matrix-rotate-ops.h | 20 + src/libnr/nr-matrix-scale-ops.cpp | 37 + src/libnr/nr-matrix-scale-ops.h | 14 + src/libnr/nr-matrix-test.cpp | 197 + src/libnr/nr-matrix-test.h | 221 + src/libnr/nr-matrix-translate-ops.cpp | 26 + src/libnr/nr-matrix-translate-ops.h | 36 + src/libnr/nr-matrix.cpp | 607 + src/libnr/nr-matrix.h | 453 + src/libnr/nr-maybe.h | 140 + src/libnr/nr-object.cpp | 295 + src/libnr/nr-object.h | 157 + src/libnr/nr-path-code.h | 28 + src/libnr/nr-path.cpp | 461 + src/libnr/nr-path.h | 62 + src/libnr/nr-pixblock-line.cpp | 92 + src/libnr/nr-pixblock-line.h | 28 + src/libnr/nr-pixblock-pattern.cpp | 126 + src/libnr/nr-pixblock-pattern.h | 28 + src/libnr/nr-pixblock-pixel.cpp | 230 + src/libnr/nr-pixblock-pixel.h | 28 + src/libnr/nr-pixblock.cpp | 411 + src/libnr/nr-pixblock.h | 95 + src/libnr/nr-pixops.h | 46 + src/libnr/nr-point-fns-test.cpp | 107 + src/libnr/nr-point-fns-test.h | 141 + src/libnr/nr-point-fns.cpp | 74 + src/libnr/nr-point-fns.h | 104 + src/libnr/nr-point-l.h | 95 + src/libnr/nr-point-matrix-ops.h | 47 + src/libnr/nr-point-ops.h | 88 + src/libnr/nr-point.h | 159 + src/libnr/nr-rect-l.cpp | 22 + src/libnr/nr-rect-l.h | 131 + src/libnr/nr-rect-ops.h | 51 + src/libnr/nr-rect.cpp | 233 + src/libnr/nr-rect.h | 255 + src/libnr/nr-render.h | 25 + src/libnr/nr-rotate-fns-test.cpp | 43 + src/libnr/nr-rotate-fns-test.h | 54 + src/libnr/nr-rotate-fns.cpp | 66 + src/libnr/nr-rotate-fns.h | 29 + src/libnr/nr-rotate-matrix-ops.cpp | 19 + src/libnr/nr-rotate-matrix-ops.h | 21 + src/libnr/nr-rotate-ops.h | 43 + src/libnr/nr-rotate-test.cpp | 89 + src/libnr/nr-rotate-test.h | 112 + src/libnr/nr-rotate.h | 66 + src/libnr/nr-scale-matrix-ops.cpp | 26 + src/libnr/nr-scale-matrix-ops.h | 13 + src/libnr/nr-scale-ops.h | 40 + src/libnr/nr-scale-test.cpp | 71 + src/libnr/nr-scale-test.h | 92 + src/libnr/nr-scale-translate-ops.cpp | 19 + src/libnr/nr-scale-translate-ops.h | 20 + src/libnr/nr-scale.h | 50 + src/libnr/nr-svp-private.h | 30 + src/libnr/nr-svp-render.cpp | 615 + src/libnr/nr-svp-render.h | 30 + src/libnr/nr-svp.cpp | 180 + src/libnr/nr-svp.h | 65 + src/libnr/nr-translate-matrix-ops.cpp | 27 + src/libnr/nr-translate-matrix-ops.h | 22 + src/libnr/nr-translate-ops.h | 43 + src/libnr/nr-translate-rotate-ops.cpp | 20 + src/libnr/nr-translate-rotate-ops.h | 21 + src/libnr/nr-translate-scale-ops.cpp | 25 + src/libnr/nr-translate-scale-ops.h | 20 + src/libnr/nr-translate-test.cpp | 65 + src/libnr/nr-translate-test.h | 87 + src/libnr/nr-translate.h | 34 + src/libnr/nr-types-test.cpp | 105 + src/libnr/nr-types-test.h | 145 + src/libnr/nr-types.cpp | 68 + src/libnr/nr-types.h | 47 + src/libnr/nr-values.cpp | 23 + src/libnr/nr-values.h | 45 + src/libnr/nr_config.h.mingw | 12 + src/libnr/nr_config.h.win32 | 14 + src/libnr/nr_mmx_R8G8B8A8_P_EMPTY_A8_RGBAP.S | 125 + src/libnr/nr_mmx_R8G8B8A8_P_R8G8B8A8_P_A8_RGBAP.S | 231 + ...mx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM.S | 414 + src/libnr/nr_mmx_R8G8B8_R8G8B8_R8G8B8A8_P.S | 227 + src/libnr/testnr.cpp | 92 + src/libnrtype/.cvsignore | 5 + src/libnrtype/FontFactory.cpp | 630 + src/libnrtype/FontFactory.h | 112 + src/libnrtype/FontInstance.cpp | 763 + src/libnrtype/Layout-TNG-Compute.cpp | 1514 ++ src/libnrtype/Layout-TNG-Input.cpp | 270 + src/libnrtype/Layout-TNG-OutIter.cpp | 994 + src/libnrtype/Layout-TNG-Output.cpp | 469 + src/libnrtype/Layout-TNG-Scanline-Maker.h | 169 + src/libnrtype/Layout-TNG-Scanline-Makers.cpp | 189 + src/libnrtype/Layout-TNG.cpp | 45 + src/libnrtype/Layout-TNG.h | 1029 + src/libnrtype/Makefile_insert | 42 + src/libnrtype/RasterFont.cpp | 431 + src/libnrtype/RasterFont.h | 66 + src/libnrtype/TextWrapper.cpp | 937 + src/libnrtype/TextWrapper.h | 139 + src/libnrtype/boundary-type.h | 31 + src/libnrtype/font-glyph.h | 29 + src/libnrtype/font-instance.h | 121 + src/libnrtype/font-style-to-pos.cpp | 120 + src/libnrtype/font-style-to-pos.h | 20 + src/libnrtype/font-style.h | 38 + src/libnrtype/libnrtype.def | 59 + src/libnrtype/makefile.in | 17 + src/libnrtype/nr-type-pos-def.cpp | 268 + src/libnrtype/nr-type-pos-def.h | 102 + src/libnrtype/nr-type-primitives.cpp | 167 + src/libnrtype/nr-type-primitives.h | 50 + src/libnrtype/nrtype-forward.h | 23 + src/libnrtype/one-box.h | 28 + src/libnrtype/one-glyph.h | 46 + src/libnrtype/one-para.h | 20 + src/libnrtype/raster-glyph.h | 49 + src/libnrtype/raster-position.h | 46 + src/libnrtype/text-boundary.h | 49 + src/line-snapper.cpp | 80 + src/line-snapper.h | 44 + src/livarot/.cvsignore | 7 + src/livarot/AVL.cpp | 965 + src/livarot/AVL.h | 95 + src/livarot/AlphaLigne.cpp | 304 + src/livarot/AlphaLigne.h | 90 + src/livarot/BitLigne.cpp | 172 + src/livarot/BitLigne.h | 66 + src/livarot/Livarot.h | 34 + src/livarot/LivarotDefs.h | 170 + src/livarot/Makefile_insert | 45 + src/livarot/MyMath.h | 281 + src/livarot/MySeg.cpp | 867 + src/livarot/MySeg.h | 147 + src/livarot/Path.cpp | 908 + src/livarot/Path.h | 387 + src/livarot/PathConversion.cpp | 1575 ++ src/livarot/PathCutting.cpp | 1487 ++ src/livarot/PathOutline.cpp | 1493 ++ src/livarot/PathSimplify.cpp | 1397 ++ src/livarot/PathStroke.cpp | 756 + src/livarot/Shape.cpp | 2282 +++ src/livarot/Shape.h | 562 + src/livarot/ShapeDraw.cpp | 103 + src/livarot/ShapeMisc.cpp | 1228 ++ src/livarot/ShapeRaster.cpp | 2009 ++ src/livarot/ShapeSweep.cpp | 3327 ++++ src/livarot/float-line.cpp | 918 + src/livarot/float-line.h | 136 + src/livarot/int-line.cpp | 1066 + src/livarot/int-line.h | 107 + src/livarot/livarot-forward.h | 28 + src/livarot/makefile.in | 17 + src/livarot/path-description.cpp | 171 + src/livarot/path-description.h | 165 + src/livarot/sweep-event-queue.h | 54 + src/livarot/sweep-event.cpp | 275 + src/livarot/sweep-event.h | 45 + src/livarot/sweep-tree-list.cpp | 47 + src/livarot/sweep-tree-list.h | 37 + src/livarot/sweep-tree.cpp | 558 + src/livarot/sweep-tree.h | 82 + src/macros.h | 54 + src/main.cpp | 1543 ++ src/make.dep | 19683 +++++++++++++++++++ src/make.exclude | 80 + src/make.files | 1339 ++ src/make.ofiles | 1307 ++ src/makedef.pl | 61 + src/marker-status.cpp | 23 + src/marker-status.h | 19 + src/media.cpp | 27 + src/media.h | 24 + src/memeq.h | 25 + src/menus-skeleton.h | 252 + src/message-context.cpp | 94 + src/message-context.h | 118 + src/message-stack.cpp | 160 + src/message-stack.h | 177 + src/message.h | 49 + src/mkdep.pl | 565 + src/mkfiles.pl | 226 + src/mod360-test.cpp | 70 + src/mod360.cpp | 29 + src/mod360.h | 17 + src/modifier-fns.h | 64 + src/node-context.cpp | 964 + src/node-context.h | 69 + src/nodepath.cpp | 3675 ++++ src/nodepath.h | 266 + src/object-edit.cpp | 1074 + src/object-edit.h | 29 + src/object-hierarchy.cpp | 213 + src/object-hierarchy.h | 119 + src/object-snapper.cpp | 176 + src/object-snapper.h | 68 + src/object-ui.cpp | 351 + src/object-ui.h | 32 + src/path-chemistry.cpp | 369 + src/path-chemistry.h | 35 + src/path-prefix.h | 83 + src/pen-context.cpp | 1084 + src/pen-context.h | 62 + src/pencil-context.cpp | 591 + src/pencil-context.h | 52 + src/pixmaps/cursor-arc.xpm | 38 + src/pixmaps/cursor-arrow.xpm | 38 + src/pixmaps/cursor-calligraphy.xpm | 38 + src/pixmaps/cursor-connector.xpm | 39 + src/pixmaps/cursor-dropper.xpm | 38 + src/pixmaps/cursor-ellipse.xpm | 38 + src/pixmaps/cursor-gradient.xpm | 38 + src/pixmaps/cursor-node-d.xpm | 38 + src/pixmaps/cursor-node-m.xpm | 38 + src/pixmaps/cursor-node.xpm | 38 + src/pixmaps/cursor-pen.xpm | 38 + src/pixmaps/cursor-pencil.xpm | 38 + src/pixmaps/cursor-rect.xpm | 38 + src/pixmaps/cursor-select-d.xpm | 38 + src/pixmaps/cursor-select-m.xpm | 38 + src/pixmaps/cursor-spiral.xpm | 38 + src/pixmaps/cursor-star.xpm | 38 + src/pixmaps/cursor-text-insert.xpm | 38 + src/pixmaps/cursor-text.xpm | 38 + src/pixmaps/cursor-zoom-out.xpm | 38 + src/pixmaps/cursor-zoom.xpm | 38 + src/pixmaps/handles.xpm | 260 + src/plugin.def | 7 + src/preferences-skeleton.h | 266 + src/preferences.cpp | 92 + src/preferences.h | 44 + src/prefix.cpp | 427 + src/prefix.h | 122 + src/prefs-utils.cpp | 186 + src/prefs-utils.h | 42 + src/print.cpp | 228 + src/print.h | 58 + src/proofs | 332 + src/rect-context.cpp | 501 + src/rect-context.h | 51 + src/registrytool.cpp | 211 + src/registrytool.h | 56 + src/remove-last.h | 31 + src/removeoverlap/.cvsignore | 5 + src/removeoverlap/Makefile_insert | 31 + src/removeoverlap/block.cpp | 279 + src/removeoverlap/block.h | 59 + src/removeoverlap/blocks.cpp | 190 + src/removeoverlap/blocks.h | 49 + src/removeoverlap/constraint.cpp | 29 + src/removeoverlap/constraint.h | 52 + src/removeoverlap/generate-constraints.cpp | 302 + src/removeoverlap/generate-constraints.h | 78 + src/removeoverlap/makefile.in | 17 + src/removeoverlap/pairingheap/.cvsignore | 5 + src/removeoverlap/pairingheap/PairingHeap.cpp | 309 + src/removeoverlap/pairingheap/PairingHeap.h | 111 + src/removeoverlap/pairingheap/dsexceptions.h | 9 + src/removeoverlap/placement_SolveVPSC.cpp | 130 + src/removeoverlap/placement_SolveVPSC.h | 53 + .../remove_rectangle_overlap-test.cpp | 308 + src/removeoverlap/remove_rectangle_overlap.cpp | 109 + src/removeoverlap/remove_rectangle_overlap.h | 21 + src/removeoverlap/removeoverlap.cpp | 80 + src/removeoverlap/removeoverlap.h | 17 + src/removeoverlap/solve_VPSC.cpp | 265 + src/removeoverlap/solve_VPSC.h | 41 + src/removeoverlap/variable.cpp | 20 + src/removeoverlap/variable.h | 46 + src/require-config.h | 3 + src/round-test.cpp | 91 + src/round.h | 32 + src/rubberband.cpp | 82 + src/rubberband.h | 64 + src/satisfied-guide-cns.cpp | 33 + src/satisfied-guide-cns.h | 25 + src/selcue.cpp | 153 + src/selcue.h | 64 + src/select-context.cpp | 823 + src/select-context.h | 52 + src/selection-chemistry.cpp | 2217 +++ src/selection-chemistry.h | 108 + src/selection-describer.cpp | 121 + src/selection-describer.h | 46 + src/selection.cpp | 450 + src/selection.h | 347 + src/seltrans-handles.cpp | 42 + src/seltrans-handles.h | 57 + src/seltrans.cpp | 1332 ++ src/seltrans.h | 151 + src/shortcuts-default-xml.cpp | 243 + src/shortcuts.cpp | 195 + src/shortcuts.h | 39 + src/slideshow.cpp | 105 + src/slideshow.h | 31 + src/snap.cpp | 406 + src/snap.h | 114 + src/snapped-point.cpp | 61 + src/snapped-point.h | 56 + src/snapper.cpp | 180 + src/snapper.h | 113 + src/sp-anchor.cpp | 220 + src/sp-anchor.h | 34 + src/sp-animation.cpp | 290 + src/sp-animation.h | 75 + src/sp-clippath.cpp | 408 + src/sp-clippath.h | 61 + src/sp-conn-end-pair.cpp | 275 + src/sp-conn-end-pair.h | 91 + src/sp-conn-end.cpp | 298 + src/sp-conn-end.h | 51 + src/sp-cursor.cpp | 115 + src/sp-cursor.h | 20 + src/sp-defs.cpp | 172 + src/sp-defs.h | 44 + src/sp-ellipse.cpp | 863 + src/sp-ellipse.h | 105 + src/sp-flowdiv.cpp | 730 + src/sp-flowdiv.h | 84 + src/sp-flowregion.cpp | 544 + src/sp-flowregion.h | 50 + src/sp-flowtext.cpp | 681 + src/sp-flowtext.h | 56 + src/sp-gradient-fns.h | 76 + src/sp-gradient-reference.cpp | 22 + src/sp-gradient-reference.h | 31 + src/sp-gradient-spread.h | 22 + src/sp-gradient-test.cpp | 139 + src/sp-gradient-units.h | 21 + src/sp-gradient-vector.h | 42 + src/sp-gradient.cpp | 1809 ++ src/sp-gradient.h | 95 + src/sp-guide-attachment.h | 43 + src/sp-guide-constraint.h | 44 + src/sp-guide.cpp | 326 + src/sp-guide.h | 64 + src/sp-image.cpp | 1200 ++ src/sp-image.h | 60 + src/sp-item-group.cpp | 699 + src/sp-item-group.h | 61 + src/sp-item-notify-moveto.cpp | 76 + src/sp-item-notify-moveto.h | 22 + src/sp-item-rm-unsatisfied-cns.cpp | 40 + src/sp-item-rm-unsatisfied-cns.h | 19 + src/sp-item-transform.cpp | 148 + src/sp-item-transform.h | 29 + src/sp-item-update-cns.cpp | 45 + src/sp-item-update-cns.h | 19 + src/sp-item.cpp | 1320 ++ src/sp-item.h | 246 + src/sp-line.cpp | 227 + src/sp-line.h | 44 + src/sp-linear-gradient-fns.h | 40 + src/sp-linear-gradient.h | 36 + src/sp-marker-loc.h | 29 + src/sp-marker.cpp | 639 + src/sp-marker.h | 93 + src/sp-mask.cpp | 375 + src/sp-mask.h | 63 + src/sp-metadata.cpp | 223 + src/sp-metadata.h | 39 + src/sp-metric.h | 26 + src/sp-metrics.cpp | 108 + src/sp-metrics.h | 21 + src/sp-namedview.cpp | 989 + src/sp-namedview.h | 136 + src/sp-object-group.cpp | 132 + src/sp-object-group.h | 33 + src/sp-object-repr.cpp | 208 + src/sp-object-repr.h | 41 + src/sp-object.cpp | 1548 ++ src/sp-object.h | 539 + src/sp-offset.cpp | 1238 ++ src/sp-offset.h | 108 + src/sp-paint-server.cpp | 163 + src/sp-paint-server.h | 66 + src/sp-path.cpp | 325 + src/sp-path.h | 46 + src/sp-pattern.cpp | 979 + src/sp-pattern.h | 98 + src/sp-polygon.cpp | 225 + src/sp-polygon.h | 33 + src/sp-polyline.cpp | 177 + src/sp-polyline.h | 28 + src/sp-radial-gradient-fns.h | 40 + src/sp-radial-gradient.h | 39 + src/sp-rect.cpp | 561 + src/sp-rect.h | 65 + src/sp-root.cpp | 678 + src/sp-root.h | 81 + src/sp-shape.cpp | 919 + src/sp-shape.h | 62 + src/sp-skeleton.cpp | 215 + src/sp-skeleton.h | 48 + src/sp-spiral.cpp | 617 + src/sp-spiral.h | 91 + src/sp-star.cpp | 544 + src/sp-star.h | 59 + src/sp-stop-fns.h | 17 + src/sp-stop.h | 56 + src/sp-string.cpp | 178 + src/sp-string.h | 30 + src/sp-style-elem-test.cpp | 118 + src/sp-style-elem.cpp | 407 + src/sp-style-elem.h | 36 + src/sp-symbol.cpp | 467 + src/sp-symbol.h | 53 + src/sp-text.cpp | 913 + src/sp-text.h | 88 + src/sp-textpath.h | 51 + src/sp-tspan.cpp | 572 + src/sp-tspan.h | 49 + src/sp-use-reference.cpp | 248 + src/sp-use-reference.h | 76 + src/sp-use.cpp | 712 + src/sp-use.h | 64 + src/spiral-context.cpp | 473 + src/spiral-context.h | 51 + src/splivarot.cpp | 1607 ++ src/splivarot.h | 58 + src/star-context.cpp | 484 + src/star-context.h | 57 + src/streams-gzip.cpp | 149 + src/streams-gzip.h | 66 + src/streams-handles.cpp | 119 + src/streams-handles.h | 103 + src/streams-jar.cpp | 146 + src/streams-jar.h | 69 + src/streams-zlib.cpp | 223 + src/streams-zlib.h | 97 + src/streq.h | 25 + src/strneq.h | 25 + src/style-test.cpp | 770 + src/style.cpp | 3703 ++++ src/style.h | 509 + src/svg-profile.h | 287 + src/svg-view-widget.cpp | 249 + src/svg-view-widget.h | 62 + src/svg-view.cpp | 230 + src/svg-view.h | 75 + src/svg/.cvsignore | 5 + src/svg/HACKING | 7 + src/svg/Makefile_insert | 58 + src/svg/css-ostringstream-test.h | 73 + src/svg/css-ostringstream.cpp | 76 + src/svg/css-ostringstream.h | 80 + src/svg/ftos.cpp | 485 + src/svg/ftos.h | 54 + src/svg/gnome-canvas-bpath-util.cpp | 179 + src/svg/gnome-canvas-bpath-util.h | 48 + src/svg/itos.cpp | 77 + src/svg/makefile.in | 17 + src/svg/round.cpp | 47 + src/svg/sp-svg.def | 26 + src/svg/stringstream-test.h | 70 + src/svg/stringstream.cpp | 69 + src/svg/stringstream.h | 76 + src/svg/strip-trailing-zeros.cpp | 42 + src/svg/strip-trailing-zeros.h | 20 + src/svg/svg-affine.cpp | 265 + src/svg/svg-color.cpp | 324 + src/svg/svg-length.cpp | 515 + src/svg/svg-length.h | 66 + src/svg/svg-path.cpp | 710 + src/svg/svg.h | 85 + src/text-chemistry.cpp | 390 + src/text-chemistry.h | 34 + src/text-context.cpp | 1537 ++ src/text-context.h | 89 + src/text-editing.cpp | 1660 ++ src/text-editing.h | 52 + src/text-tag-attributes.h | 145 + src/tools-switch.cpp | 248 + src/tools-switch.h | 52 + src/trace/.cvsignore | 4 + src/trace/Makefile_insert | 34 + src/trace/filterset.cpp | 959 + src/trace/filterset.h | 82 + src/trace/imagemap-gdk.cpp | 206 + src/trace/imagemap-gdk.h | 46 + src/trace/imagemap.cpp | 331 + src/trace/imagemap.h | 292 + src/trace/makefile.in | 17 + src/trace/potrace/.cvsignore | 3 + src/trace/potrace/auxiliary.h | 60 + src/trace/potrace/bitmap.h | 97 + src/trace/potrace/curve.cpp | 107 + src/trace/potrace/curve.h | 74 + src/trace/potrace/decompose.cpp | 481 + src/trace/potrace/decompose.h | 17 + src/trace/potrace/greymap.cpp | 889 + src/trace/potrace/greymap.h | 58 + src/trace/potrace/inkscape-potrace.cpp | 712 + src/trace/potrace/inkscape-potrace.h | 231 + src/trace/potrace/lists.h | 286 + src/trace/potrace/potracelib.cpp | 109 + src/trace/potrace/potracelib.h | 131 + src/trace/potrace/progress.h | 79 + src/trace/potrace/render.cpp | 242 + src/trace/potrace/render.h | 28 + src/trace/potrace/trace.cpp | 1231 ++ src/trace/potrace/trace.h | 15 + src/trace/trace.cpp | 322 + src/trace/trace.h | 246 + src/traits/.cvsignore | 2 + src/traits/Makefile_insert | 5 + src/traits/copy.h | 48 + src/traits/function.h | 122 + src/traits/list-copy.h | 45 + src/traits/makefile.in | 17 + src/traits/reference.h | 49 + src/ui/.cvsignore | 3 + src/ui/Makefile_insert | 18 + src/ui/dialog/.cvsignore | 3 + src/ui/dialog/Makefile_insert | 63 + src/ui/dialog/aboutbox.cpp | 682 + src/ui/dialog/aboutbox.h | 50 + src/ui/dialog/align-and-distribute.cpp | 1051 + src/ui/dialog/align-and-distribute.h | 120 + src/ui/dialog/dialog-manager.cpp | 191 + src/ui/dialog/dialog-manager.h | 72 + src/ui/dialog/dialog.cpp | 359 + src/ui/dialog/dialog.h | 94 + src/ui/dialog/document-metadata.cpp | 306 + src/ui/dialog/document-metadata.h | 84 + src/ui/dialog/document-properties.cpp | 503 + src/ui/dialog/document-properties.h | 103 + src/ui/dialog/export.cpp | 58 + src/ui/dialog/export.h | 59 + src/ui/dialog/extension-editor.cpp | 48 + src/ui/dialog/extension-editor.h | 52 + src/ui/dialog/fill-and-stroke.cpp | 62 + src/ui/dialog/fill-and-stroke.h | 61 + src/ui/dialog/find.cpp | 58 + src/ui/dialog/find.h | 59 + src/ui/dialog/inkscape-preferences.cpp | 525 + src/ui/dialog/inkscape-preferences.h | 140 + src/ui/dialog/layer-editor.cpp | 48 + src/ui/dialog/layer-editor.h | 52 + src/ui/dialog/makefile.in | 17 + src/ui/dialog/memory.cpp | 244 + src/ui/dialog/memory.h | 54 + src/ui/dialog/messages.cpp | 185 + src/ui/dialog/messages.h | 98 + src/ui/dialog/scriptdialog.cpp | 248 + src/ui/dialog/scriptdialog.h | 65 + src/ui/dialog/session-player.cpp | 229 + src/ui/dialog/session-player.h | 124 + src/ui/dialog/text-properties.cpp | 59 + src/ui/dialog/text-properties.h | 60 + src/ui/dialog/tracedialog.cpp | 562 + src/ui/dialog/tracedialog.h | 63 + src/ui/dialog/transformation.cpp | 955 + src/ui/dialog/transformation.h | 239 + src/ui/dialog/tree-editor.cpp | 53 + src/ui/dialog/tree-editor.h | 61 + src/ui/dialog/whiteboard-connect.cpp | 192 + src/ui/dialog/whiteboard-connect.h | 88 + src/ui/dialog/whiteboard-sharewithchat.cpp | 147 + src/ui/dialog/whiteboard-sharewithchat.h | 93 + src/ui/dialog/whiteboard-sharewithuser.cpp | 214 + src/ui/dialog/whiteboard-sharewithuser.h | 110 + src/ui/dialog/xml-editor.cpp | 48 + src/ui/dialog/xml-editor.h | 52 + src/ui/icons.cpp | 696 + src/ui/icons.h | 36 + src/ui/makefile.in | 17 + src/ui/previewable.h | 62 + src/ui/previewfillable.h | 49 + src/ui/previewholder.cpp | 153 + src/ui/previewholder.h | 60 + src/ui/stock-items.cpp | 215 + src/ui/stock-items.h | 37 + src/ui/stock.cpp | 187 + src/ui/stock.h | 192 + src/ui/view/.cvsignore | 3 + src/ui/view/Makefile_insert | 27 + src/ui/view/desktop-affine.cpp | 0 src/ui/view/desktop-affine.h | 0 src/ui/view/desktop-events.cpp | 0 src/ui/view/desktop-events.h | 0 src/ui/view/desktop-handles.cpp | 0 src/ui/view/desktop-handles.h | 0 src/ui/view/desktop-style.cpp | 0 src/ui/view/desktop-style.h | 0 src/ui/view/desktop.cpp | 0 src/ui/view/desktop.h | 0 src/ui/view/edit-widget-interface.h | 134 + src/ui/view/edit-widget.cpp | 1701 ++ src/ui/view/edit-widget.h | 211 + src/ui/view/edit.cpp | 28 + src/ui/view/edit.h | 27 + src/ui/view/makefile.in | 17 + src/ui/view/view-widget.cpp | 135 + src/ui/view/view-widget.h | 97 + src/ui/view/view.cpp | 163 + src/ui/view/view.h | 148 + src/ui/widget/.cvsignore | 3 + src/ui/widget/Makefile_insert | 62 + src/ui/widget/button.cpp | 51 + src/ui/widget/button.h | 56 + src/ui/widget/color-picker.cpp | 167 + src/ui/widget/color-picker.h | 65 + src/ui/widget/color-preview.cpp | 122 + src/ui/widget/color-preview.h | 50 + src/ui/widget/combo-text.cpp | 92 + src/ui/widget/combo-text.h | 66 + src/ui/widget/entity-entry.cpp | 161 + src/ui/widget/entity-entry.h | 84 + src/ui/widget/handlebox.cpp | 48 + src/ui/widget/handlebox.h | 50 + src/ui/widget/icon-widget.cpp | 165 + src/ui/widget/icon-widget.h | 63 + src/ui/widget/imageicon.cpp | 442 + src/ui/widget/imageicon.h | 138 + src/ui/widget/labelled.cpp | 102 + src/ui/widget/labelled.h | 65 + src/ui/widget/licensor.cpp | 146 + src/ui/widget/licensor.h | 59 + src/ui/widget/makefile.in | 17 + src/ui/widget/notebook-page.cpp | 49 + src/ui/widget/notebook-page.h | 50 + src/ui/widget/page-sizer.cpp | 357 + src/ui/widget/page-sizer.h | 67 + src/ui/widget/panel.cpp | 248 + src/ui/widget/panel.h | 80 + src/ui/widget/preferences-widget.cpp | 278 + src/ui/widget/preferences-widget.h | 117 + src/ui/widget/registered-widget.cpp | 409 + src/ui/widget/registered-widget.h | 175 + src/ui/widget/registry.cpp | 58 + src/ui/widget/registry.h | 54 + src/ui/widget/ruler.cpp | 197 + src/ui/widget/ruler.h | 88 + src/ui/widget/scalar-unit.cpp | 252 + src/ui/widget/scalar-unit.h | 85 + src/ui/widget/scalar.cpp | 217 + src/ui/widget/scalar.h | 86 + src/ui/widget/selected-style.cpp | 977 + src/ui/widget/selected-style.h | 212 + src/ui/widget/style-swatch.cpp | 244 + src/ui/widget/style-swatch.h | 88 + src/ui/widget/svg-canvas.cpp | 92 + src/ui/widget/svg-canvas.h | 56 + src/ui/widget/tolerance-slider.cpp | 142 + src/ui/widget/tolerance-slider.h | 61 + src/ui/widget/toolbox.cpp | 269 + src/ui/widget/toolbox.h | 73 + src/ui/widget/unit-menu.cpp | 181 + src/ui/widget/unit-menu.h | 70 + src/ui/widget/zoom-status.cpp | 127 + src/ui/widget/zoom-status.h | 61 + src/undo-stack-observer.h | 68 + src/unit-constants.h | 39 + src/uri-references.cpp | 165 + src/uri-references.h | 138 + src/uri.cpp | 263 + src/uri.h | 91 + src/utest/.cvsignore | 4 + src/utest/Makefile_insert | 5 + src/utest/makefile.in | 17 + src/utest/test-1ary-cases.h | 43 + src/utest/test-2ary-cases.h | 44 + src/utest/utest.h | 134 + src/util/.cvsignore | 5 + src/util/Makefile_insert | 25 + src/util/compose.hpp | 393 + src/util/filter-list.h | 65 + src/util/forward-pointer-iterator.h | 120 + src/util/glib-list-iterators.h | 238 + src/util/list-container-test.cpp | 204 + src/util/list-container.h | 352 + src/util/list.h | 407 + src/util/makefile.in | 17 + src/util/map-list.h | 68 + src/util/reverse-list.h | 68 + src/util/shared-c-string-ptr.cpp | 50 + src/util/shared-c-string-ptr.h | 77 + src/util/tuple.h | 182 + src/util/ucompose.hpp | 438 + src/util/units.cpp | 344 + src/util/units.h | 88 + src/verbs.cpp | 2254 +++ src/verbs.h | 375 + src/version.cpp | 64 + src/version.h | 58 + src/widgets/.cvsignore | 5 + src/widgets/Makefile_insert | 75 + src/widgets/button.cpp | 324 + src/widgets/button.h | 69 + src/widgets/dash-selector.cpp | 379 + src/widgets/dash-selector.h | 48 + src/widgets/desktop-widget.cpp | 1270 ++ src/widgets/desktop-widget.h | 201 + src/widgets/font-selector.cpp | 722 + src/widgets/font-selector.h | 67 + src/widgets/gradient-image.cpp | 277 + src/widgets/gradient-image.h | 46 + src/widgets/gradient-selector.cpp | 342 + src/widgets/gradient-selector.h | 79 + src/widgets/gradient-toolbar.cpp | 665 + src/widgets/gradient-toolbar.h | 21 + src/widgets/gradient-vector.cpp | 1115 ++ src/widgets/gradient-vector.h | 61 + src/widgets/icon.cpp | 924 + src/widgets/icon.h | 51 + src/widgets/layer-selector.cpp | 588 + src/widgets/layer-selector.h | 110 + src/widgets/makefile.in | 17 + src/widgets/paint-selector.cpp | 963 + src/widgets/paint-selector.h | 110 + src/widgets/ruler.cpp | 663 + src/widgets/ruler.h | 84 + src/widgets/select-toolbar.cpp | 540 + src/widgets/select-toolbar.h | 34 + src/widgets/shrink-wrap-button.cpp | 55 + src/widgets/shrink-wrap-button.h | 35 + src/widgets/sp-color-gtkselector.cpp | 170 + src/widgets/sp-color-gtkselector.h | 55 + src/widgets/sp-color-notebook.cpp | 625 + src/widgets/sp-color-notebook.h | 105 + src/widgets/sp-color-preview.cpp | 196 + src/widgets/sp-color-preview.h | 47 + src/widgets/sp-color-scales.cpp | 741 + src/widgets/sp-color-scales.h | 108 + src/widgets/sp-color-selector.cpp | 351 + src/widgets/sp-color-selector.h | 95 + src/widgets/sp-color-slider.cpp | 687 + src/widgets/sp-color-slider.h | 67 + src/widgets/sp-color-wheel-selector.cpp | 295 + src/widgets/sp-color-wheel-selector.h | 90 + src/widgets/sp-color-wheel.cpp | 1163 ++ src/widgets/sp-color-wheel.h | 80 + src/widgets/sp-widget.cpp | 260 + src/widgets/sp-widget.h | 52 + src/widgets/sp-xmlview-attr-list.cpp | 181 + src/widgets/sp-xmlview-attr-list.h | 54 + src/widgets/sp-xmlview-content.cpp | 165 + src/widgets/sp-xmlview-content.h | 53 + src/widgets/sp-xmlview-tree.cpp | 424 + src/widgets/sp-xmlview-tree.h | 55 + src/widgets/spinbutton-events.cpp | 137 + src/widgets/spinbutton-events.h | 30 + src/widgets/spw-utilities.cpp | 255 + src/widgets/spw-utilities.h | 56 + src/widgets/toolbox.cpp | 3028 +++ src/widgets/toolbox.h | 57 + src/widgets/widget-sizes.h | 52 + src/winmain.cpp | 34 + src/xml/.cvsignore | 10 + src/xml/Makefile_insert | 86 + src/xml/attribute-record.h | 39 + src/xml/comment-node.h | 52 + src/xml/composite-node-observer.cpp | 308 + src/xml/composite-node-observer.h | 86 + src/xml/croco-node-iface.cpp | 68 + src/xml/croco-node-iface.h | 12 + src/xml/document.h | 50 + src/xml/element-node.h | 49 + src/xml/event-fns.h | 28 + src/xml/event.cpp | 489 + src/xml/event.h | 150 + src/xml/invalid-operation-exception.h | 47 + src/xml/log-builder.cpp | 78 + src/xml/log-builder.h | 66 + src/xml/makefile.in | 17 + src/xml/node-event-vector.h | 47 + src/xml/node-fns.cpp | 103 + src/xml/node-fns.h | 44 + src/xml/node-iterators.h | 61 + src/xml/node-observer.h | 68 + src/xml/node.h | 131 + src/xml/quote-test.cpp | 82 + src/xml/quote-test.h | 31 + src/xml/quote.cpp | 86 + src/xml/quote.h | 7 + src/xml/repr-action-test.cpp | 77 + src/xml/repr-action-test.h | 31 + src/xml/repr-css.cpp | 261 + src/xml/repr-io.cpp | 786 + src/xml/repr-sorting.cpp | 53 + src/xml/repr-sorting.h | 11 + src/xml/repr-util.cpp | 553 + src/xml/repr.cpp | 107 + src/xml/repr.h | 261 + src/xml/session.h | 56 + src/xml/simple-document.cpp | 40 + src/xml/simple-document.h | 57 + src/xml/simple-node.cpp | 731 + src/xml/simple-node.h | 167 + src/xml/simple-session.cpp | 122 + src/xml/simple-session.h | 84 + src/xml/sp-css-attr.h | 33 + src/xml/text-node.h | 52 + src/xml/transaction-logger.h | 52 + src/zoom-context.cpp | 241 + src/zoom-context.h | 35 + 1823 files changed, 606349 insertions(+) create mode 100644 src/.cvsignore create mode 100644 src/Doxyfile create mode 100644 src/Makefile.am create mode 100644 src/Makefile.mingw create mode 100644 src/Makefile.mingw.old create mode 100644 src/Makefile_insert create mode 100644 src/algorithms/.cvsignore create mode 100644 src/algorithms/Makefile_insert create mode 100644 src/algorithms/find-if-before.h create mode 100644 src/algorithms/find-last-if.h create mode 100644 src/algorithms/longest-common-suffix.h create mode 100644 src/algorithms/makefile.in create mode 100644 src/application/.cvsignore create mode 100644 src/application/Makefile_insert create mode 100644 src/application/app-prototype.cpp create mode 100644 src/application/app-prototype.h create mode 100644 src/application/application.cpp create mode 100644 src/application/application.h create mode 100644 src/application/editor.cpp create mode 100644 src/application/editor.h create mode 100644 src/application/makefile.in create mode 100644 src/approx-equal.h create mode 100644 src/arc-context.cpp create mode 100644 src/arc-context.h create mode 100644 src/attributes-test.cpp create mode 100644 src/attributes.cpp create mode 100644 src/attributes.h create mode 100644 src/bad-uri-exception.h create mode 100644 src/brokenimage.xpm create mode 100755 src/check-header-compile.in create mode 100644 src/color-rgba.h create mode 100644 src/color.cpp create mode 100644 src/color.h create mode 100644 src/composite-undo-stack-observer.cpp create mode 100644 src/composite-undo-stack-observer.h create mode 100644 src/conn-avoid-ref.cpp create mode 100644 src/conn-avoid-ref.h create mode 100644 src/connector-context.cpp create mode 100644 src/connector-context.h create mode 100644 src/context-fns.cpp create mode 100644 src/context-fns.h create mode 100644 src/debug/.cvsignore create mode 100644 src/debug/Makefile_insert create mode 100644 src/debug/event-tracker.h create mode 100644 src/debug/event.h create mode 100644 src/debug/gc-heap.h create mode 100644 src/debug/heap.cpp create mode 100644 src/debug/heap.h create mode 100644 src/debug/logger.cpp create mode 100644 src/debug/logger.h create mode 100644 src/debug/makefile.in create mode 100644 src/debug/simple-event.h create mode 100644 src/debug/sysv-heap.cpp create mode 100644 src/debug/sysv-heap.h create mode 100644 src/decimal-round.h create mode 100644 src/desktop-affine.cpp create mode 100644 src/desktop-affine.h create mode 100644 src/desktop-events.cpp create mode 100644 src/desktop-events.h create mode 100644 src/desktop-handles.cpp create mode 100644 src/desktop-handles.h create mode 100644 src/desktop-style.cpp create mode 100644 src/desktop-style.h create mode 100644 src/desktop.cpp create mode 100644 src/desktop.h create mode 100644 src/dialogs/.cvsignore create mode 100644 src/dialogs/Makefile_insert create mode 100644 src/dialogs/clonetiler.cpp create mode 100644 src/dialogs/clonetiler.h create mode 100644 src/dialogs/debugdialog.cpp create mode 100644 src/dialogs/debugdialog.h create mode 100644 src/dialogs/dialog-events.cpp create mode 100644 src/dialogs/dialog-events.h create mode 100644 src/dialogs/display-settings.cpp create mode 100644 src/dialogs/display-settings.h create mode 100644 src/dialogs/eek-preview.cpp create mode 100644 src/dialogs/eek-preview.h create mode 100644 src/dialogs/export.cpp create mode 100644 src/dialogs/export.h create mode 100644 src/dialogs/extensions.cpp create mode 100644 src/dialogs/extensions.h create mode 100644 src/dialogs/filedialog-win32.cpp create mode 100644 src/dialogs/filedialog.cpp create mode 100644 src/dialogs/filedialog.h create mode 100644 src/dialogs/fill-style.cpp create mode 100644 src/dialogs/fill-style.h create mode 100644 src/dialogs/find.cpp create mode 100644 src/dialogs/find.h create mode 100644 src/dialogs/iconpreview.cpp create mode 100644 src/dialogs/iconpreview.h create mode 100644 src/dialogs/in-dt-coordsys.cpp create mode 100644 src/dialogs/in-dt-coordsys.h create mode 100644 src/dialogs/input.cpp create mode 100644 src/dialogs/input.h create mode 100644 src/dialogs/item-properties.cpp create mode 100644 src/dialogs/item-properties.h create mode 100644 src/dialogs/layer-properties.cpp create mode 100644 src/dialogs/layer-properties.h create mode 100644 src/dialogs/makefile.in create mode 100644 src/dialogs/object-attributes.cpp create mode 100644 src/dialogs/object-attributes.h create mode 100644 src/dialogs/object-properties.cpp create mode 100644 src/dialogs/object-properties.h create mode 100644 src/dialogs/rdf.cpp create mode 100644 src/dialogs/rdf.h create mode 100644 src/dialogs/sp-attribute-widget.cpp create mode 100644 src/dialogs/sp-attribute-widget.h create mode 100644 src/dialogs/stroke-style.cpp create mode 100644 src/dialogs/stroke-style.h create mode 100644 src/dialogs/swatches.cpp create mode 100644 src/dialogs/swatches.h create mode 100644 src/dialogs/text-edit.cpp create mode 100644 src/dialogs/text-edit.h create mode 100644 src/dialogs/tiledialog.cpp create mode 100644 src/dialogs/tiledialog.h create mode 100644 src/dialogs/unclump.cpp create mode 100644 src/dialogs/unclump.h create mode 100644 src/dialogs/xml-tree.cpp create mode 100644 src/dialogs/xml-tree.h create mode 100644 src/dir-util-test.cpp create mode 100644 src/dir-util.cpp create mode 100644 src/dir-util.h create mode 100644 src/display/.cvsignore create mode 100644 src/display/Makefile_insert create mode 100644 src/display/bezier-utils-test.cpp create mode 100644 src/display/bezier-utils-work.txt create mode 100644 src/display/bezier-utils.cpp create mode 100644 src/display/bezier-utils.h create mode 100644 src/display/canvas-arena.cpp create mode 100644 src/display/canvas-arena.h create mode 100644 src/display/canvas-bpath.cpp create mode 100644 src/display/canvas-bpath.h create mode 100644 src/display/canvas-grid.cpp create mode 100644 src/display/canvas-grid.h create mode 100644 src/display/curve.cpp create mode 100644 src/display/curve.h create mode 100644 src/display/display-forward.h create mode 100644 src/display/gnome-canvas-acetate.cpp create mode 100644 src/display/gnome-canvas-acetate.h create mode 100644 src/display/guideline.cpp create mode 100644 src/display/guideline.h create mode 100644 src/display/makefile.in create mode 100644 src/display/nr-arena-forward.h create mode 100644 src/display/nr-arena-glyphs.cpp create mode 100644 src/display/nr-arena-glyphs.h create mode 100644 src/display/nr-arena-group.cpp create mode 100644 src/display/nr-arena-group.h create mode 100644 src/display/nr-arena-image.cpp create mode 100644 src/display/nr-arena-image.h create mode 100644 src/display/nr-arena-item.cpp create mode 100644 src/display/nr-arena-item.h create mode 100644 src/display/nr-arena-shape.cpp create mode 100644 src/display/nr-arena-shape.h create mode 100644 src/display/nr-arena.cpp create mode 100644 src/display/nr-arena.h create mode 100644 src/display/nr-gradient-gpl.cpp create mode 100644 src/display/nr-gradient-gpl.h create mode 100644 src/display/nr-plain-stuff-gdk.cpp create mode 100644 src/display/nr-plain-stuff-gdk.h create mode 100644 src/display/nr-plain-stuff.cpp create mode 100644 src/display/nr-plain-stuff.h create mode 100644 src/display/sodipodi-ctrl.cpp create mode 100644 src/display/sodipodi-ctrl.h create mode 100644 src/display/sodipodi-ctrlrect.cpp create mode 100644 src/display/sodipodi-ctrlrect.h create mode 100644 src/display/sp-canvas-util.cpp create mode 100644 src/display/sp-canvas-util.h create mode 100644 src/display/sp-canvas.cpp create mode 100644 src/display/sp-canvas.h create mode 100644 src/display/sp-ctrlline.cpp create mode 100644 src/display/sp-ctrlline.h create mode 100644 src/display/sp-ctrlquadr.cpp create mode 100644 src/display/sp-ctrlquadr.h create mode 100644 src/display/testnr.cpp create mode 100644 src/document-private.h create mode 100644 src/document-undo.cpp create mode 100644 src/document.cpp create mode 100644 src/document.h create mode 100644 src/dom/.cvsignore create mode 100755 src/dom/001.css create mode 100755 src/dom/Filt.java create mode 100755 src/dom/ImplGen.java create mode 100755 src/dom/Makefile.static create mode 100644 src/dom/Makefile_insert create mode 100644 src/dom/README create mode 100755 src/dom/acid.css create mode 100755 src/dom/base.css create mode 100755 src/dom/charclass.cpp create mode 100755 src/dom/charclass.h create mode 100755 src/dom/css.h create mode 100755 src/dom/css.idl create mode 100755 src/dom/cssparser.cpp create mode 100755 src/dom/cssparser.h create mode 100755 src/dom/cssprop.txt create mode 100755 src/dom/dom.h create mode 100755 src/dom/dom.idl create mode 100755 src/dom/domimpl.cpp create mode 100755 src/dom/domimpl.h create mode 100755 src/dom/domstream.cpp create mode 100755 src/dom/domstream.h create mode 100755 src/dom/domstring.cpp create mode 100755 src/dom/domstring.h create mode 100755 src/dom/domstringimpl.h create mode 100755 src/dom/events.h create mode 100755 src/dom/events.idl create mode 100755 src/dom/inkscape.css create mode 100755 src/dom/inkscape.logo.svg create mode 100644 src/dom/js/Makefile create mode 100644 src/dom/js/README create mode 100644 src/dom/js/fdlibm/e_acos.c create mode 100644 src/dom/js/fdlibm/e_acosh.c create mode 100644 src/dom/js/fdlibm/e_asin.c create mode 100644 src/dom/js/fdlibm/e_atan2.c create mode 100644 src/dom/js/fdlibm/e_atanh.c create mode 100644 src/dom/js/fdlibm/e_cosh.c create mode 100644 src/dom/js/fdlibm/e_exp.c create mode 100644 src/dom/js/fdlibm/e_fmod.c create mode 100644 src/dom/js/fdlibm/e_gamma.c create mode 100644 src/dom/js/fdlibm/e_gamma_r.c create mode 100644 src/dom/js/fdlibm/e_hypot.c create mode 100644 src/dom/js/fdlibm/e_j0.c create mode 100644 src/dom/js/fdlibm/e_j1.c create mode 100644 src/dom/js/fdlibm/e_jn.c create mode 100644 src/dom/js/fdlibm/e_lgamma.c create mode 100644 src/dom/js/fdlibm/e_lgamma_r.c create mode 100644 src/dom/js/fdlibm/e_log.c create mode 100644 src/dom/js/fdlibm/e_log10.c create mode 100644 src/dom/js/fdlibm/e_pow.c create mode 100644 src/dom/js/fdlibm/e_rem_pio2.c create mode 100644 src/dom/js/fdlibm/e_remainder.c create mode 100644 src/dom/js/fdlibm/e_scalb.c create mode 100644 src/dom/js/fdlibm/e_sinh.c create mode 100644 src/dom/js/fdlibm/e_sqrt.c create mode 100644 src/dom/js/fdlibm/fdlibm.h create mode 100644 src/dom/js/fdlibm/k_cos.c create mode 100644 src/dom/js/fdlibm/k_rem_pio2.c create mode 100644 src/dom/js/fdlibm/k_sin.c create mode 100644 src/dom/js/fdlibm/k_standard.c create mode 100644 src/dom/js/fdlibm/k_tan.c create mode 100644 src/dom/js/fdlibm/s_asinh.c create mode 100644 src/dom/js/fdlibm/s_atan.c create mode 100644 src/dom/js/fdlibm/s_cbrt.c create mode 100644 src/dom/js/fdlibm/s_ceil.c create mode 100644 src/dom/js/fdlibm/s_copysign.c create mode 100644 src/dom/js/fdlibm/s_cos.c create mode 100644 src/dom/js/fdlibm/s_erf.c create mode 100644 src/dom/js/fdlibm/s_expm1.c create mode 100644 src/dom/js/fdlibm/s_fabs.c create mode 100644 src/dom/js/fdlibm/s_finite.c create mode 100644 src/dom/js/fdlibm/s_floor.c create mode 100644 src/dom/js/fdlibm/s_frexp.c create mode 100644 src/dom/js/fdlibm/s_ilogb.c create mode 100644 src/dom/js/fdlibm/s_isnan.c create mode 100644 src/dom/js/fdlibm/s_ldexp.c create mode 100644 src/dom/js/fdlibm/s_lib_version.c create mode 100644 src/dom/js/fdlibm/s_log1p.c create mode 100644 src/dom/js/fdlibm/s_logb.c create mode 100644 src/dom/js/fdlibm/s_matherr.c create mode 100644 src/dom/js/fdlibm/s_modf.c create mode 100644 src/dom/js/fdlibm/s_nextafter.c create mode 100644 src/dom/js/fdlibm/s_rint.c create mode 100644 src/dom/js/fdlibm/s_scalbn.c create mode 100644 src/dom/js/fdlibm/s_signgam.c create mode 100644 src/dom/js/fdlibm/s_significand.c create mode 100644 src/dom/js/fdlibm/s_sin.c create mode 100644 src/dom/js/fdlibm/s_tan.c create mode 100644 src/dom/js/fdlibm/s_tanh.c create mode 100644 src/dom/js/fdlibm/w_acos.c create mode 100644 src/dom/js/fdlibm/w_acosh.c create mode 100644 src/dom/js/fdlibm/w_asin.c create mode 100644 src/dom/js/fdlibm/w_atan2.c create mode 100644 src/dom/js/fdlibm/w_atanh.c create mode 100644 src/dom/js/fdlibm/w_cosh.c create mode 100644 src/dom/js/fdlibm/w_exp.c create mode 100644 src/dom/js/fdlibm/w_fmod.c create mode 100644 src/dom/js/fdlibm/w_gamma.c create mode 100644 src/dom/js/fdlibm/w_gamma_r.c create mode 100644 src/dom/js/fdlibm/w_hypot.c create mode 100644 src/dom/js/fdlibm/w_j0.c create mode 100644 src/dom/js/fdlibm/w_j1.c create mode 100644 src/dom/js/fdlibm/w_jn.c create mode 100644 src/dom/js/fdlibm/w_lgamma.c create mode 100644 src/dom/js/fdlibm/w_lgamma_r.c create mode 100644 src/dom/js/fdlibm/w_log.c create mode 100644 src/dom/js/fdlibm/w_log10.c create mode 100644 src/dom/js/fdlibm/w_pow.c create mode 100644 src/dom/js/fdlibm/w_remainder.c create mode 100644 src/dom/js/fdlibm/w_scalb.c create mode 100644 src/dom/js/fdlibm/w_sinh.c create mode 100644 src/dom/js/fdlibm/w_sqrt.c create mode 100644 src/dom/js/js.c create mode 100644 src/dom/js/js.mak create mode 100644 src/dom/js/js.msg create mode 100644 src/dom/js/jsapi.c create mode 100644 src/dom/js/jsapi.h create mode 100644 src/dom/js/jsarena.c create mode 100644 src/dom/js/jsarena.h create mode 100644 src/dom/js/jsarray.c create mode 100644 src/dom/js/jsarray.h create mode 100644 src/dom/js/jsatom.c create mode 100644 src/dom/js/jsatom.h create mode 100644 src/dom/js/jsautocfg.h create mode 100644 src/dom/js/jsbit.h create mode 100644 src/dom/js/jsbool.c create mode 100644 src/dom/js/jsbool.h create mode 100644 src/dom/js/jsclist.h create mode 100644 src/dom/js/jscntxt.c create mode 100644 src/dom/js/jscntxt.h create mode 100644 src/dom/js/jscompat.h create mode 100644 src/dom/js/jsconfig.h create mode 100644 src/dom/js/jscpucfg.c create mode 100644 src/dom/js/jscpucfg.h create mode 100644 src/dom/js/jsdate.c create mode 100644 src/dom/js/jsdate.h create mode 100644 src/dom/js/jsdbgapi.c create mode 100644 src/dom/js/jsdbgapi.h create mode 100644 src/dom/js/jsdhash.c create mode 100644 src/dom/js/jsdhash.h create mode 100644 src/dom/js/jsdtoa.c create mode 100644 src/dom/js/jsdtoa.h create mode 100644 src/dom/js/jsemit.c create mode 100644 src/dom/js/jsemit.h create mode 100644 src/dom/js/jsexn.c create mode 100644 src/dom/js/jsexn.h create mode 100644 src/dom/js/jsfile.c create mode 100644 src/dom/js/jsfile.h create mode 100644 src/dom/js/jsfun.c create mode 100644 src/dom/js/jsfun.h create mode 100644 src/dom/js/jsgc.c create mode 100644 src/dom/js/jsgc.h create mode 100644 src/dom/js/jshash.c create mode 100644 src/dom/js/jshash.h create mode 100644 src/dom/js/jsinterp.c create mode 100644 src/dom/js/jsinterp.h create mode 100644 src/dom/js/jslibmath.h create mode 100644 src/dom/js/jslock.c create mode 100644 src/dom/js/jslock.h create mode 100644 src/dom/js/jslocko.asm create mode 100644 src/dom/js/jslog2.c create mode 100644 src/dom/js/jslong.c create mode 100644 src/dom/js/jslong.h create mode 100644 src/dom/js/jsmath.c create mode 100644 src/dom/js/jsmath.h create mode 100644 src/dom/js/jsnum.c create mode 100644 src/dom/js/jsnum.h create mode 100644 src/dom/js/jsobj.c create mode 100644 src/dom/js/jsobj.h create mode 100644 src/dom/js/jsopcode.c create mode 100644 src/dom/js/jsopcode.h create mode 100644 src/dom/js/jsopcode.tbl create mode 100644 src/dom/js/jsosdep.h create mode 100644 src/dom/js/jsotypes.h create mode 100644 src/dom/js/jsparse.c create mode 100644 src/dom/js/jsparse.h create mode 100644 src/dom/js/jsprf.c create mode 100644 src/dom/js/jsprf.h create mode 100644 src/dom/js/jsprvtd.h create mode 100644 src/dom/js/jspubtd.h create mode 100644 src/dom/js/jsregexp.c create mode 100644 src/dom/js/jsregexp.h create mode 100644 src/dom/js/jsscan.c create mode 100644 src/dom/js/jsscan.h create mode 100644 src/dom/js/jsscope.c create mode 100644 src/dom/js/jsscope.h create mode 100644 src/dom/js/jsscript.c create mode 100644 src/dom/js/jsscript.h create mode 100644 src/dom/js/jsshell.msg create mode 100644 src/dom/js/jsstddef.h create mode 100644 src/dom/js/jsstr.c create mode 100644 src/dom/js/jsstr.h create mode 100644 src/dom/js/jstypes.h create mode 100644 src/dom/js/jsutil.c create mode 100644 src/dom/js/jsutil.h create mode 100644 src/dom/js/jsxdrapi.c create mode 100644 src/dom/js/jsxdrapi.h create mode 100644 src/dom/js/prmjtime.c create mode 100644 src/dom/js/prmjtime.h create mode 100644 src/dom/js/resource.h create mode 100755 src/dom/knut.svg create mode 100755 src/dom/ls.h create mode 100755 src/dom/ls.idl create mode 100755 src/dom/lsimpl.cpp create mode 100755 src/dom/lsimpl.cpp.orig create mode 100755 src/dom/lsimpl.h create mode 100644 src/dom/makefile.in create mode 100755 src/dom/meyerweb.css create mode 100755 src/dom/mingwenv.bat create mode 100755 src/dom/phoebedom.h create mode 100755 src/dom/prop-css.cpp create mode 100755 src/dom/prop-css.txt create mode 100755 src/dom/prop-css2.cpp create mode 100755 src/dom/prop-svg.cpp create mode 100755 src/dom/prop-svg.txt create mode 100755 src/dom/ranges.idl create mode 100755 src/dom/sandb1.css create mode 100755 src/dom/smil.h create mode 100755 src/dom/smil.idl create mode 100755 src/dom/smilimpl.cpp create mode 100755 src/dom/smilimpl.h create mode 100755 src/dom/stringstream.cpp create mode 100755 src/dom/stringstream.h create mode 100755 src/dom/stylesheets.h create mode 100755 src/dom/stylesheets.idl create mode 100755 src/dom/svg.h create mode 100755 src/dom/svg.idl create mode 100755 src/dom/svgimpl.cpp create mode 100755 src/dom/svgimpl.h create mode 100755 src/dom/svglsimpl.cpp create mode 100755 src/dom/svglsimpl.h create mode 100755 src/dom/svgparser.cpp create mode 100755 src/dom/svgparser.h create mode 100755 src/dom/svgtypes.h create mode 100755 src/dom/testdom.cpp create mode 100755 src/dom/testsvg.cpp create mode 100755 src/dom/transforms.svg create mode 100755 src/dom/traversal.h create mode 100755 src/dom/traversal.idl create mode 100755 src/dom/uri.cpp create mode 100755 src/dom/uri.h create mode 100755 src/dom/uristream.cpp create mode 100755 src/dom/uristream.h create mode 100755 src/dom/uritest.cpp create mode 100755 src/dom/views.h create mode 100755 src/dom/views.idl create mode 100755 src/dom/xmlreader.cpp create mode 100755 src/dom/xmlreader.h create mode 100755 src/dom/xpath.h create mode 100755 src/dom/xpath.idl create mode 100755 src/dom/xpathimpl.cpp create mode 100755 src/dom/xpathimpl.h create mode 100755 src/dom/xpathparser.cpp create mode 100755 src/dom/xpathparser.h create mode 100755 src/dom/xpathtest.cpp create mode 100755 src/dom/xpathtests.cpp create mode 100644 src/draw-anchor.cpp create mode 100644 src/draw-anchor.h create mode 100644 src/draw-context.cpp create mode 100644 src/draw-context.h create mode 100644 src/dropper-context.cpp create mode 100644 src/dropper-context.h create mode 100644 src/dyna-draw-context.cpp create mode 100644 src/dyna-draw-context.h create mode 100644 src/enums.h create mode 100644 src/event-context.cpp create mode 100644 src/event-context.h create mode 100644 src/extension/.cvsignore create mode 100644 src/extension/Makefile_insert create mode 100644 src/extension/api.cpp create mode 100644 src/extension/db.cpp create mode 100644 src/extension/db.h create mode 100644 src/extension/dependency.cpp create mode 100644 src/extension/dependency.h create mode 100644 src/extension/dxf2svg/GPL.txt create mode 100644 src/extension/dxf2svg/LGPL.txt create mode 100644 src/extension/dxf2svg/Makefile create mode 100644 src/extension/dxf2svg/README create mode 100644 src/extension/dxf2svg/aci2rgb.cpp create mode 100644 src/extension/dxf2svg/blocks.cpp create mode 100644 src/extension/dxf2svg/blocks.h create mode 100644 src/extension/dxf2svg/dxf2svg.cpp create mode 100644 src/extension/dxf2svg/dxf_input.inx create mode 100644 src/extension/dxf2svg/dxf_input_windows.inx create mode 100644 src/extension/dxf2svg/entities.cpp create mode 100644 src/extension/dxf2svg/entities.h create mode 100644 src/extension/dxf2svg/entities2elements.cpp create mode 100644 src/extension/dxf2svg/entities2elements.h create mode 100644 src/extension/dxf2svg/read_dxf.cpp create mode 100644 src/extension/dxf2svg/read_dxf.h create mode 100644 src/extension/dxf2svg/tables.cpp create mode 100644 src/extension/dxf2svg/tables.h create mode 100644 src/extension/dxf2svg/tables2svg_info.cpp create mode 100644 src/extension/dxf2svg/tables2svg_info.h create mode 100644 src/extension/dxf2svg/test_dxf.cpp create mode 100644 src/extension/effect.cpp create mode 100644 src/extension/effect.h create mode 100644 src/extension/error-file.cpp create mode 100644 src/extension/error-file.h create mode 100644 src/extension/extension-forward.h create mode 100644 src/extension/extension.cpp create mode 100644 src/extension/extension.h create mode 100644 src/extension/implementation/.cvsignore create mode 100644 src/extension/implementation/Makefile_insert create mode 100644 src/extension/implementation/implementation.cpp create mode 100644 src/extension/implementation/implementation.h create mode 100644 src/extension/implementation/makefile.in create mode 100644 src/extension/implementation/plugin-link.h create mode 100644 src/extension/implementation/plugin.cpp create mode 100644 src/extension/implementation/plugin.h create mode 100644 src/extension/implementation/script.cpp create mode 100644 src/extension/implementation/script.h create mode 100644 src/extension/init.cpp create mode 100644 src/extension/init.h create mode 100644 src/extension/input.cpp create mode 100644 src/extension/input.h create mode 100644 src/extension/internal/.cvsignore create mode 100644 src/extension/internal/Makefile_insert create mode 100644 src/extension/internal/bluredge.cpp create mode 100644 src/extension/internal/bluredge.h create mode 100644 src/extension/internal/eps-out.cpp create mode 100644 src/extension/internal/eps-out.h create mode 100644 src/extension/internal/gdkpixbuf-input.cpp create mode 100644 src/extension/internal/gdkpixbuf-input.h create mode 100644 src/extension/internal/gimpgrad.cpp create mode 100644 src/extension/internal/gimpgrad.h create mode 100644 src/extension/internal/gnome.cpp create mode 100644 src/extension/internal/gnome.h create mode 100644 src/extension/internal/grid.cpp create mode 100644 src/extension/internal/grid.h create mode 100644 src/extension/internal/latex-pstricks-out.cpp create mode 100644 src/extension/internal/latex-pstricks-out.h create mode 100644 src/extension/internal/latex-pstricks.cpp create mode 100644 src/extension/internal/latex-pstricks.h create mode 100644 src/extension/internal/makefile.in create mode 100644 src/extension/internal/pov-out.cpp create mode 100644 src/extension/internal/pov-out.h create mode 100644 src/extension/internal/ps-out.cpp create mode 100644 src/extension/internal/ps-out.h create mode 100644 src/extension/internal/ps.cpp create mode 100644 src/extension/internal/ps.h create mode 100644 src/extension/internal/svg.cpp create mode 100644 src/extension/internal/svg.h create mode 100644 src/extension/internal/svgz.cpp create mode 100644 src/extension/internal/svgz.h create mode 100644 src/extension/internal/win32.cpp create mode 100644 src/extension/internal/win32.h create mode 100644 src/extension/makefile.in create mode 100644 src/extension/output.cpp create mode 100644 src/extension/output.h create mode 100644 src/extension/parameter.cpp create mode 100644 src/extension/parameter.h create mode 100644 src/extension/plugin/.cvsignore create mode 100644 src/extension/plugin/Makefile_insert create mode 100644 src/extension/plugin/makefile.in create mode 100644 src/extension/prefdialog.cpp create mode 100644 src/extension/prefdialog.h create mode 100644 src/extension/print.cpp create mode 100644 src/extension/print.h create mode 100644 src/extension/script/.cvsignore create mode 100644 src/extension/script/InkscapeBinding.cpp create mode 100644 src/extension/script/InkscapeBinding.h create mode 100644 src/extension/script/InkscapeInterpreter.cpp create mode 100644 src/extension/script/InkscapeInterpreter.h create mode 100644 src/extension/script/InkscapePerl.cpp create mode 100644 src/extension/script/InkscapePerl.h create mode 100644 src/extension/script/InkscapePython.cpp create mode 100644 src/extension/script/InkscapePython.h create mode 100644 src/extension/script/InkscapeScript.cpp create mode 100644 src/extension/script/InkscapeScript.h create mode 100644 src/extension/script/Makefile.tmp create mode 100644 src/extension/script/Makefile_insert create mode 100644 src/extension/script/README.txt create mode 100644 src/extension/script/bindtest.cpp create mode 100644 src/extension/script/cpptest.cpp create mode 100644 src/extension/script/inkscape_perl.i create mode 100644 src/extension/script/inkscape_perl.pm create mode 100644 src/extension/script/inkscape_perl.pm.h create mode 100644 src/extension/script/inkscape_perl_wrap.cpp create mode 100644 src/extension/script/inkscape_py.i create mode 100644 src/extension/script/inkscape_py.py create mode 100644 src/extension/script/inkscape_py.py.h create mode 100644 src/extension/script/inkscape_py_wrap.cpp create mode 100644 src/extension/script/js/Makefile create mode 100644 src/extension/script/js/README create mode 100644 src/extension/script/js/fdlibm/e_acos.c create mode 100644 src/extension/script/js/fdlibm/e_acosh.c create mode 100644 src/extension/script/js/fdlibm/e_asin.c create mode 100644 src/extension/script/js/fdlibm/e_atan2.c create mode 100644 src/extension/script/js/fdlibm/e_atanh.c create mode 100644 src/extension/script/js/fdlibm/e_cosh.c create mode 100644 src/extension/script/js/fdlibm/e_exp.c create mode 100644 src/extension/script/js/fdlibm/e_fmod.c create mode 100644 src/extension/script/js/fdlibm/e_gamma.c create mode 100644 src/extension/script/js/fdlibm/e_gamma_r.c create mode 100644 src/extension/script/js/fdlibm/e_hypot.c create mode 100644 src/extension/script/js/fdlibm/e_j0.c create mode 100644 src/extension/script/js/fdlibm/e_j1.c create mode 100644 src/extension/script/js/fdlibm/e_jn.c create mode 100644 src/extension/script/js/fdlibm/e_lgamma.c create mode 100644 src/extension/script/js/fdlibm/e_lgamma_r.c create mode 100644 src/extension/script/js/fdlibm/e_log.c create mode 100644 src/extension/script/js/fdlibm/e_log10.c create mode 100644 src/extension/script/js/fdlibm/e_pow.c create mode 100644 src/extension/script/js/fdlibm/e_rem_pio2.c create mode 100644 src/extension/script/js/fdlibm/e_remainder.c create mode 100644 src/extension/script/js/fdlibm/e_scalb.c create mode 100644 src/extension/script/js/fdlibm/e_sinh.c create mode 100644 src/extension/script/js/fdlibm/e_sqrt.c create mode 100644 src/extension/script/js/fdlibm/fdlibm.h create mode 100644 src/extension/script/js/fdlibm/k_cos.c create mode 100644 src/extension/script/js/fdlibm/k_rem_pio2.c create mode 100644 src/extension/script/js/fdlibm/k_sin.c create mode 100644 src/extension/script/js/fdlibm/k_standard.c create mode 100644 src/extension/script/js/fdlibm/k_tan.c create mode 100644 src/extension/script/js/fdlibm/s_asinh.c create mode 100644 src/extension/script/js/fdlibm/s_atan.c create mode 100644 src/extension/script/js/fdlibm/s_cbrt.c create mode 100644 src/extension/script/js/fdlibm/s_ceil.c create mode 100644 src/extension/script/js/fdlibm/s_copysign.c create mode 100644 src/extension/script/js/fdlibm/s_cos.c create mode 100644 src/extension/script/js/fdlibm/s_erf.c create mode 100644 src/extension/script/js/fdlibm/s_expm1.c create mode 100644 src/extension/script/js/fdlibm/s_fabs.c create mode 100644 src/extension/script/js/fdlibm/s_finite.c create mode 100644 src/extension/script/js/fdlibm/s_floor.c create mode 100644 src/extension/script/js/fdlibm/s_frexp.c create mode 100644 src/extension/script/js/fdlibm/s_ilogb.c create mode 100644 src/extension/script/js/fdlibm/s_isnan.c create mode 100644 src/extension/script/js/fdlibm/s_ldexp.c create mode 100644 src/extension/script/js/fdlibm/s_lib_version.c create mode 100644 src/extension/script/js/fdlibm/s_log1p.c create mode 100644 src/extension/script/js/fdlibm/s_logb.c create mode 100644 src/extension/script/js/fdlibm/s_matherr.c create mode 100644 src/extension/script/js/fdlibm/s_modf.c create mode 100644 src/extension/script/js/fdlibm/s_nextafter.c create mode 100644 src/extension/script/js/fdlibm/s_rint.c create mode 100644 src/extension/script/js/fdlibm/s_scalbn.c create mode 100644 src/extension/script/js/fdlibm/s_signgam.c create mode 100644 src/extension/script/js/fdlibm/s_significand.c create mode 100644 src/extension/script/js/fdlibm/s_sin.c create mode 100644 src/extension/script/js/fdlibm/s_tan.c create mode 100644 src/extension/script/js/fdlibm/s_tanh.c create mode 100644 src/extension/script/js/fdlibm/w_acos.c create mode 100644 src/extension/script/js/fdlibm/w_acosh.c create mode 100644 src/extension/script/js/fdlibm/w_asin.c create mode 100644 src/extension/script/js/fdlibm/w_atan2.c create mode 100644 src/extension/script/js/fdlibm/w_atanh.c create mode 100644 src/extension/script/js/fdlibm/w_cosh.c create mode 100644 src/extension/script/js/fdlibm/w_exp.c create mode 100644 src/extension/script/js/fdlibm/w_fmod.c create mode 100644 src/extension/script/js/fdlibm/w_gamma.c create mode 100644 src/extension/script/js/fdlibm/w_gamma_r.c create mode 100644 src/extension/script/js/fdlibm/w_hypot.c create mode 100644 src/extension/script/js/fdlibm/w_j0.c create mode 100644 src/extension/script/js/fdlibm/w_j1.c create mode 100644 src/extension/script/js/fdlibm/w_jn.c create mode 100644 src/extension/script/js/fdlibm/w_lgamma.c create mode 100644 src/extension/script/js/fdlibm/w_lgamma_r.c create mode 100644 src/extension/script/js/fdlibm/w_log.c create mode 100644 src/extension/script/js/fdlibm/w_log10.c create mode 100644 src/extension/script/js/fdlibm/w_pow.c create mode 100644 src/extension/script/js/fdlibm/w_remainder.c create mode 100644 src/extension/script/js/fdlibm/w_scalb.c create mode 100644 src/extension/script/js/fdlibm/w_sinh.c create mode 100644 src/extension/script/js/fdlibm/w_sqrt.c create mode 100644 src/extension/script/js/js.c create mode 100644 src/extension/script/js/js.mak create mode 100644 src/extension/script/js/js.msg create mode 100644 src/extension/script/js/jsapi.c create mode 100644 src/extension/script/js/jsapi.h create mode 100644 src/extension/script/js/jsarena.c create mode 100644 src/extension/script/js/jsarena.h create mode 100644 src/extension/script/js/jsarray.c create mode 100644 src/extension/script/js/jsarray.h create mode 100644 src/extension/script/js/jsatom.c create mode 100644 src/extension/script/js/jsatom.h create mode 100644 src/extension/script/js/jsautocfg.h create mode 100644 src/extension/script/js/jsbit.h create mode 100644 src/extension/script/js/jsbool.c create mode 100644 src/extension/script/js/jsbool.h create mode 100644 src/extension/script/js/jsclist.h create mode 100644 src/extension/script/js/jscntxt.c create mode 100644 src/extension/script/js/jscntxt.h create mode 100644 src/extension/script/js/jscompat.h create mode 100644 src/extension/script/js/jsconfig.h create mode 100644 src/extension/script/js/jscpucfg.c create mode 100644 src/extension/script/js/jscpucfg.h create mode 100644 src/extension/script/js/jsdate.c create mode 100644 src/extension/script/js/jsdate.h create mode 100644 src/extension/script/js/jsdbgapi.c create mode 100644 src/extension/script/js/jsdbgapi.h create mode 100644 src/extension/script/js/jsdhash.c create mode 100644 src/extension/script/js/jsdhash.h create mode 100644 src/extension/script/js/jsdtoa.c create mode 100644 src/extension/script/js/jsdtoa.h create mode 100644 src/extension/script/js/jsemit.c create mode 100644 src/extension/script/js/jsemit.h create mode 100644 src/extension/script/js/jsexn.c create mode 100644 src/extension/script/js/jsexn.h create mode 100644 src/extension/script/js/jsfile.c create mode 100644 src/extension/script/js/jsfile.h create mode 100644 src/extension/script/js/jsfun.c create mode 100644 src/extension/script/js/jsfun.h create mode 100644 src/extension/script/js/jsgc.c create mode 100644 src/extension/script/js/jsgc.h create mode 100644 src/extension/script/js/jshash.c create mode 100644 src/extension/script/js/jshash.h create mode 100644 src/extension/script/js/jsinterp.c create mode 100644 src/extension/script/js/jsinterp.h create mode 100644 src/extension/script/js/jslibmath.h create mode 100644 src/extension/script/js/jslock.c create mode 100644 src/extension/script/js/jslock.h create mode 100644 src/extension/script/js/jslocko.asm create mode 100644 src/extension/script/js/jslog2.c create mode 100644 src/extension/script/js/jslong.c create mode 100644 src/extension/script/js/jslong.h create mode 100644 src/extension/script/js/jsmath.c create mode 100644 src/extension/script/js/jsmath.h create mode 100644 src/extension/script/js/jsnum.c create mode 100644 src/extension/script/js/jsnum.h create mode 100644 src/extension/script/js/jsobj.c create mode 100644 src/extension/script/js/jsobj.h create mode 100644 src/extension/script/js/jsopcode.c create mode 100644 src/extension/script/js/jsopcode.h create mode 100644 src/extension/script/js/jsopcode.tbl create mode 100644 src/extension/script/js/jsosdep.h create mode 100644 src/extension/script/js/jsotypes.h create mode 100644 src/extension/script/js/jsparse.c create mode 100644 src/extension/script/js/jsparse.h create mode 100644 src/extension/script/js/jsprf.c create mode 100644 src/extension/script/js/jsprf.h create mode 100644 src/extension/script/js/jsprvtd.h create mode 100644 src/extension/script/js/jspubtd.h create mode 100644 src/extension/script/js/jsregexp.c create mode 100644 src/extension/script/js/jsregexp.h create mode 100644 src/extension/script/js/jsscan.c create mode 100644 src/extension/script/js/jsscan.h create mode 100644 src/extension/script/js/jsscope.c create mode 100644 src/extension/script/js/jsscope.h create mode 100644 src/extension/script/js/jsscript.c create mode 100644 src/extension/script/js/jsscript.h create mode 100644 src/extension/script/js/jsshell.msg create mode 100644 src/extension/script/js/jsstddef.h create mode 100644 src/extension/script/js/jsstr.c create mode 100644 src/extension/script/js/jsstr.h create mode 100644 src/extension/script/js/jstypes.h create mode 100644 src/extension/script/js/jsutil.c create mode 100644 src/extension/script/js/jsutil.h create mode 100644 src/extension/script/js/jsxdrapi.c create mode 100644 src/extension/script/js/jsxdrapi.h create mode 100644 src/extension/script/js/prmjtime.c create mode 100644 src/extension/script/js/prmjtime.h create mode 100644 src/extension/script/js/resource.h create mode 100644 src/extension/script/makefile.in create mode 100644 src/extension/script/quotefile.pl create mode 100644 src/extension/script/runme.py create mode 100644 src/extension/script/wrap_swig_module.sh create mode 100644 src/extension/system.cpp create mode 100644 src/extension/system.h create mode 100644 src/extension/timer.cpp create mode 100644 src/extension/timer.h create mode 100644 src/extract-uri-test.cpp create mode 100644 src/extract-uri.cpp create mode 100644 src/extract-uri.h create mode 100644 src/file.cpp create mode 100644 src/file.h create mode 100644 src/fill-or-stroke.h create mode 100644 src/fixes.cpp create mode 100644 src/fontsize-expansion.cpp create mode 100644 src/fontsize-expansion.h create mode 100644 src/forward.h create mode 100644 src/gc-alloc.h create mode 100644 src/gc-anchored.cpp create mode 100644 src/gc-anchored.h create mode 100644 src/gc-core.h create mode 100644 src/gc-finalized.h create mode 100644 src/gc-managed.h create mode 100644 src/gc.cpp create mode 100644 src/geom.cpp create mode 100644 src/geom.h create mode 100644 src/gnuc-attribute.h create mode 100644 src/gradient-chemistry.cpp create mode 100644 src/gradient-chemistry.h create mode 100644 src/gradient-context.cpp create mode 100644 src/gradient-context.h create mode 100644 src/gradient-drag.cpp create mode 100644 src/gradient-drag.h create mode 100644 src/grid-snapper.cpp create mode 100644 src/grid-snapper.h create mode 100644 src/guide-snapper.cpp create mode 100644 src/guide-snapper.h create mode 100644 src/help.cpp create mode 100644 src/help.h create mode 100644 src/helper/.cvsignore create mode 100644 src/helper/HACKING create mode 100644 src/helper/Makefile_insert create mode 100644 src/helper/action.cpp create mode 100644 src/helper/action.h create mode 100644 src/helper/gnome-utils.cpp create mode 100644 src/helper/gnome-utils.h create mode 100644 src/helper/helper-forward.h create mode 100644 src/helper/makefile.in create mode 100644 src/helper/png-write.cpp create mode 100644 src/helper/png-write.h create mode 100644 src/helper/sp-marshal.cpp.mingw create mode 100644 src/helper/sp-marshal.h.mingw create mode 100644 src/helper/sp-marshal.list create mode 100644 src/helper/stlport.h create mode 100644 src/helper/stock-items.cpp create mode 100644 src/helper/stock-items.h create mode 100644 src/helper/unit-menu.cpp create mode 100644 src/helper/unit-menu.h create mode 100644 src/helper/units-test.cpp create mode 100644 src/helper/units.cpp create mode 100644 src/helper/units.h create mode 100644 src/helper/window.cpp create mode 100644 src/helper/window.h create mode 100644 src/inkjar/.cvsignore create mode 100644 src/inkjar/Makefile_insert create mode 100644 src/inkjar/jar.cpp create mode 100644 src/inkjar/jar.h create mode 100644 src/inkjar/makefile.in create mode 100644 src/inkscape-private.h create mode 100644 src/inkscape-stock.cpp create mode 100644 src/inkscape-stock.h create mode 100644 src/inkscape.cpp create mode 100644 src/inkscape.h create mode 100644 src/inkscape.rc create mode 100644 src/inkscape_version.h.mingw create mode 100644 src/inkview.cpp create mode 100644 src/interface.cpp create mode 100644 src/interface.h create mode 100644 src/io/.cvsignore create mode 100644 src/io/Makefile.tst create mode 100644 src/io/Makefile_insert create mode 100644 src/io/base64stream.cpp create mode 100644 src/io/base64stream.h create mode 100644 src/io/crystalegg.xml create mode 100644 src/io/doc2html.xsl create mode 100644 src/io/ftos.cpp create mode 100644 src/io/ftos.h create mode 100644 src/io/gzipstream.cpp create mode 100644 src/io/gzipstream.h create mode 100644 src/io/inkscapestream.cpp create mode 100644 src/io/inkscapestream.h create mode 100644 src/io/makefile.in create mode 100644 src/io/simple-sax.cpp create mode 100644 src/io/simple-sax.h create mode 100644 src/io/streamtest.cpp create mode 100644 src/io/stringstream.cpp create mode 100644 src/io/stringstream.h create mode 100644 src/io/sys.cpp create mode 100644 src/io/sys.h create mode 100644 src/io/uristream.cpp create mode 100644 src/io/uristream.h create mode 100644 src/io/xsltstream.cpp create mode 100644 src/io/xsltstream.h create mode 100644 src/isnan.h create mode 100644 src/jabber_whiteboard/.cvsignore create mode 100644 src/jabber_whiteboard/Makefile_insert create mode 100644 src/jabber_whiteboard/buddy-list-manager.cpp create mode 100644 src/jabber_whiteboard/buddy-list-manager.h create mode 100644 src/jabber_whiteboard/callbacks.cpp create mode 100644 src/jabber_whiteboard/callbacks.h create mode 100644 src/jabber_whiteboard/chat-handler.cpp create mode 100644 src/jabber_whiteboard/chat-handler.h create mode 100644 src/jabber_whiteboard/connection-establishment.cpp create mode 100644 src/jabber_whiteboard/defines.h create mode 100644 src/jabber_whiteboard/deserializer.cpp create mode 100644 src/jabber_whiteboard/deserializer.h create mode 100644 src/jabber_whiteboard/empty.cpp create mode 100644 src/jabber_whiteboard/error-codes.h create mode 100644 src/jabber_whiteboard/internal-constants.cpp create mode 100644 src/jabber_whiteboard/internal-constants.h create mode 100644 src/jabber_whiteboard/invitation-confirm-dialog.cpp create mode 100644 src/jabber_whiteboard/invitation-confirm-dialog.h create mode 100644 src/jabber_whiteboard/jabber-handlers.cpp create mode 100644 src/jabber_whiteboard/jabber-handlers.h create mode 100644 src/jabber_whiteboard/makefile.in create mode 100644 src/jabber_whiteboard/message-aggregator.cpp create mode 100644 src/jabber_whiteboard/message-aggregator.h create mode 100644 src/jabber_whiteboard/message-contexts.cpp create mode 100644 src/jabber_whiteboard/message-contexts.h create mode 100644 src/jabber_whiteboard/message-handler.cpp create mode 100644 src/jabber_whiteboard/message-handler.h create mode 100644 src/jabber_whiteboard/message-node.h create mode 100644 src/jabber_whiteboard/message-processors.cpp create mode 100644 src/jabber_whiteboard/message-processors.h create mode 100644 src/jabber_whiteboard/message-queue.cpp create mode 100644 src/jabber_whiteboard/message-queue.h create mode 100644 src/jabber_whiteboard/message-tags.cpp create mode 100644 src/jabber_whiteboard/message-tags.h create mode 100644 src/jabber_whiteboard/message-utilities.cpp create mode 100644 src/jabber_whiteboard/message-utilities.h create mode 100644 src/jabber_whiteboard/node-tracker-event-tracker.cpp create mode 100644 src/jabber_whiteboard/node-tracker-event-tracker.h create mode 100644 src/jabber_whiteboard/node-tracker-observer.h create mode 100644 src/jabber_whiteboard/node-tracker.cpp create mode 100644 src/jabber_whiteboard/node-tracker.h create mode 100644 src/jabber_whiteboard/node-utilities.cpp create mode 100644 src/jabber_whiteboard/node-utilities.h create mode 100644 src/jabber_whiteboard/pedrodom.cpp create mode 100644 src/jabber_whiteboard/pedrodom.h create mode 100644 src/jabber_whiteboard/pedroxmpp.cpp create mode 100644 src/jabber_whiteboard/pedroxmpp.h create mode 100644 src/jabber_whiteboard/serializer.cpp create mode 100644 src/jabber_whiteboard/serializer.h create mode 100644 src/jabber_whiteboard/session-file-player.cpp create mode 100644 src/jabber_whiteboard/session-file-player.h create mode 100644 src/jabber_whiteboard/session-file-selector.cpp create mode 100644 src/jabber_whiteboard/session-file-selector.h create mode 100644 src/jabber_whiteboard/session-file.cpp create mode 100644 src/jabber_whiteboard/session-file.h create mode 100644 src/jabber_whiteboard/session-manager.cpp create mode 100644 src/jabber_whiteboard/session-manager.h create mode 100644 src/jabber_whiteboard/tracker-node.h create mode 100644 src/jabber_whiteboard/typedefs.h create mode 100644 src/jabber_whiteboard/undo-stack-observer.cpp create mode 100644 src/jabber_whiteboard/undo-stack-observer.h create mode 100644 src/knot-enums.h create mode 100644 src/knot-holder-entity.h create mode 100644 src/knot.cpp create mode 100644 src/knot.h create mode 100644 src/knotholder.cpp create mode 100644 src/knotholder.h create mode 100644 src/layer-fns.cpp create mode 100644 src/layer-fns.h create mode 100644 src/libavoid/.cvsignore create mode 100644 src/libavoid/Makefile_insert create mode 100644 src/libavoid/README create mode 100644 src/libavoid/connector.cpp create mode 100644 src/libavoid/connector.h create mode 100644 src/libavoid/debug.h create mode 100644 src/libavoid/geometry.cpp create mode 100644 src/libavoid/geometry.h create mode 100644 src/libavoid/geomtypes.h create mode 100644 src/libavoid/graph.cpp create mode 100644 src/libavoid/graph.h create mode 100644 src/libavoid/incremental.cpp create mode 100644 src/libavoid/incremental.h create mode 100644 src/libavoid/libavoid.h create mode 100644 src/libavoid/makefile.in create mode 100644 src/libavoid/makepath.cpp create mode 100644 src/libavoid/makepath.h create mode 100644 src/libavoid/polyutil.cpp create mode 100644 src/libavoid/polyutil.h create mode 100644 src/libavoid/shape.cpp create mode 100644 src/libavoid/shape.h create mode 100644 src/libavoid/static.cpp create mode 100644 src/libavoid/static.h create mode 100644 src/libavoid/timer.cpp create mode 100644 src/libavoid/timer.h create mode 100644 src/libavoid/vertices.cpp create mode 100644 src/libavoid/vertices.h create mode 100644 src/libavoid/visibility.cpp create mode 100644 src/libavoid/visibility.h create mode 100644 src/libcroco/.cvsignore create mode 100644 src/libcroco/Makefile_insert create mode 100644 src/libcroco/README create mode 100644 src/libcroco/cr-additional-sel.c create mode 100644 src/libcroco/cr-additional-sel.h create mode 100644 src/libcroco/cr-attr-sel.c create mode 100644 src/libcroco/cr-attr-sel.h create mode 100644 src/libcroco/cr-cascade.c create mode 100644 src/libcroco/cr-cascade.h create mode 100644 src/libcroco/cr-declaration.c create mode 100644 src/libcroco/cr-declaration.h create mode 100644 src/libcroco/cr-doc-handler.c create mode 100644 src/libcroco/cr-doc-handler.h create mode 100644 src/libcroco/cr-enc-handler.c create mode 100644 src/libcroco/cr-enc-handler.h create mode 100644 src/libcroco/cr-fonts.c create mode 100644 src/libcroco/cr-fonts.h create mode 100644 src/libcroco/cr-input.c create mode 100644 src/libcroco/cr-input.h create mode 100644 src/libcroco/cr-libxml-node-iface.c create mode 100644 src/libcroco/cr-libxml-node-iface.h create mode 100644 src/libcroco/cr-node-iface.h create mode 100644 src/libcroco/cr-num.c create mode 100644 src/libcroco/cr-num.h create mode 100644 src/libcroco/cr-om-parser.c create mode 100644 src/libcroco/cr-om-parser.h create mode 100644 src/libcroco/cr-parser.c create mode 100644 src/libcroco/cr-parser.h create mode 100644 src/libcroco/cr-parsing-location.c create mode 100644 src/libcroco/cr-parsing-location.h create mode 100644 src/libcroco/cr-prop-list.c create mode 100644 src/libcroco/cr-prop-list.h create mode 100644 src/libcroco/cr-pseudo.c create mode 100644 src/libcroco/cr-pseudo.h create mode 100644 src/libcroco/cr-rgb.c create mode 100644 src/libcroco/cr-rgb.h create mode 100644 src/libcroco/cr-sel-eng.c create mode 100644 src/libcroco/cr-sel-eng.h create mode 100644 src/libcroco/cr-selector.c create mode 100644 src/libcroco/cr-selector.h create mode 100644 src/libcroco/cr-simple-sel.c create mode 100644 src/libcroco/cr-simple-sel.h create mode 100644 src/libcroco/cr-statement.c create mode 100644 src/libcroco/cr-statement.h create mode 100644 src/libcroco/cr-string.c create mode 100644 src/libcroco/cr-string.h create mode 100644 src/libcroco/cr-style.c create mode 100644 src/libcroco/cr-style.h create mode 100644 src/libcroco/cr-stylesheet.c create mode 100644 src/libcroco/cr-stylesheet.h create mode 100644 src/libcroco/cr-term.c create mode 100644 src/libcroco/cr-term.h create mode 100644 src/libcroco/cr-tknzr.c create mode 100644 src/libcroco/cr-tknzr.h create mode 100644 src/libcroco/cr-token.c create mode 100644 src/libcroco/cr-token.h create mode 100644 src/libcroco/cr-utils.c create mode 100644 src/libcroco/cr-utils.h create mode 100644 src/libcroco/libcroco.h create mode 100644 src/libcroco/makefile.in create mode 100644 src/libnr/.cvsignore create mode 100644 src/libnr/Makefile_insert create mode 100644 src/libnr/have_mmx.S create mode 100644 src/libnr/in-svg-plane-test.cpp create mode 100644 src/libnr/in-svg-plane-test.h create mode 100644 src/libnr/in-svg-plane.h create mode 100644 src/libnr/libnr.def create mode 100644 src/libnr/makefile.in create mode 100644 src/libnr/n-art-bpath.h create mode 100644 src/libnr/nr-blit.cpp create mode 100644 src/libnr/nr-blit.h create mode 100644 src/libnr/nr-compose-transform.cpp create mode 100644 src/libnr/nr-compose-transform.h create mode 100644 src/libnr/nr-compose.cpp create mode 100644 src/libnr/nr-compose.h create mode 100644 src/libnr/nr-convex-hull-ops.h create mode 100644 src/libnr/nr-convex-hull.h create mode 100644 src/libnr/nr-coord.h create mode 100644 src/libnr/nr-dim2.h create mode 100644 src/libnr/nr-forward.h create mode 100644 src/libnr/nr-gradient.cpp create mode 100644 src/libnr/nr-gradient.h create mode 100644 src/libnr/nr-i-coord.h create mode 100644 src/libnr/nr-macros.h create mode 100644 src/libnr/nr-matrix-div.cpp create mode 100644 src/libnr/nr-matrix-div.h create mode 100644 src/libnr/nr-matrix-fns.cpp create mode 100644 src/libnr/nr-matrix-fns.h create mode 100644 src/libnr/nr-matrix-ops.h create mode 100644 src/libnr/nr-matrix-rotate-ops.cpp create mode 100644 src/libnr/nr-matrix-rotate-ops.h create mode 100644 src/libnr/nr-matrix-scale-ops.cpp create mode 100644 src/libnr/nr-matrix-scale-ops.h create mode 100644 src/libnr/nr-matrix-test.cpp create mode 100644 src/libnr/nr-matrix-test.h create mode 100644 src/libnr/nr-matrix-translate-ops.cpp create mode 100644 src/libnr/nr-matrix-translate-ops.h create mode 100644 src/libnr/nr-matrix.cpp create mode 100644 src/libnr/nr-matrix.h create mode 100644 src/libnr/nr-maybe.h create mode 100644 src/libnr/nr-object.cpp create mode 100644 src/libnr/nr-object.h create mode 100644 src/libnr/nr-path-code.h create mode 100644 src/libnr/nr-path.cpp create mode 100644 src/libnr/nr-path.h create mode 100644 src/libnr/nr-pixblock-line.cpp create mode 100644 src/libnr/nr-pixblock-line.h create mode 100644 src/libnr/nr-pixblock-pattern.cpp create mode 100644 src/libnr/nr-pixblock-pattern.h create mode 100644 src/libnr/nr-pixblock-pixel.cpp create mode 100644 src/libnr/nr-pixblock-pixel.h create mode 100644 src/libnr/nr-pixblock.cpp create mode 100644 src/libnr/nr-pixblock.h create mode 100644 src/libnr/nr-pixops.h create mode 100644 src/libnr/nr-point-fns-test.cpp create mode 100644 src/libnr/nr-point-fns-test.h create mode 100644 src/libnr/nr-point-fns.cpp create mode 100644 src/libnr/nr-point-fns.h create mode 100644 src/libnr/nr-point-l.h create mode 100644 src/libnr/nr-point-matrix-ops.h create mode 100644 src/libnr/nr-point-ops.h create mode 100644 src/libnr/nr-point.h create mode 100644 src/libnr/nr-rect-l.cpp create mode 100644 src/libnr/nr-rect-l.h create mode 100644 src/libnr/nr-rect-ops.h create mode 100644 src/libnr/nr-rect.cpp create mode 100644 src/libnr/nr-rect.h create mode 100644 src/libnr/nr-render.h create mode 100644 src/libnr/nr-rotate-fns-test.cpp create mode 100644 src/libnr/nr-rotate-fns-test.h create mode 100644 src/libnr/nr-rotate-fns.cpp create mode 100644 src/libnr/nr-rotate-fns.h create mode 100644 src/libnr/nr-rotate-matrix-ops.cpp create mode 100644 src/libnr/nr-rotate-matrix-ops.h create mode 100644 src/libnr/nr-rotate-ops.h create mode 100644 src/libnr/nr-rotate-test.cpp create mode 100644 src/libnr/nr-rotate-test.h create mode 100644 src/libnr/nr-rotate.h create mode 100644 src/libnr/nr-scale-matrix-ops.cpp create mode 100644 src/libnr/nr-scale-matrix-ops.h create mode 100644 src/libnr/nr-scale-ops.h create mode 100644 src/libnr/nr-scale-test.cpp create mode 100644 src/libnr/nr-scale-test.h create mode 100644 src/libnr/nr-scale-translate-ops.cpp create mode 100644 src/libnr/nr-scale-translate-ops.h create mode 100644 src/libnr/nr-scale.h create mode 100644 src/libnr/nr-svp-private.h create mode 100644 src/libnr/nr-svp-render.cpp create mode 100644 src/libnr/nr-svp-render.h create mode 100644 src/libnr/nr-svp.cpp create mode 100644 src/libnr/nr-svp.h create mode 100644 src/libnr/nr-translate-matrix-ops.cpp create mode 100644 src/libnr/nr-translate-matrix-ops.h create mode 100644 src/libnr/nr-translate-ops.h create mode 100644 src/libnr/nr-translate-rotate-ops.cpp create mode 100644 src/libnr/nr-translate-rotate-ops.h create mode 100644 src/libnr/nr-translate-scale-ops.cpp create mode 100644 src/libnr/nr-translate-scale-ops.h create mode 100644 src/libnr/nr-translate-test.cpp create mode 100644 src/libnr/nr-translate-test.h create mode 100644 src/libnr/nr-translate.h create mode 100644 src/libnr/nr-types-test.cpp create mode 100644 src/libnr/nr-types-test.h create mode 100644 src/libnr/nr-types.cpp create mode 100644 src/libnr/nr-types.h create mode 100644 src/libnr/nr-values.cpp create mode 100644 src/libnr/nr-values.h create mode 100644 src/libnr/nr_config.h.mingw create mode 100644 src/libnr/nr_config.h.win32 create mode 100644 src/libnr/nr_mmx_R8G8B8A8_P_EMPTY_A8_RGBAP.S create mode 100644 src/libnr/nr_mmx_R8G8B8A8_P_R8G8B8A8_P_A8_RGBAP.S create mode 100644 src/libnr/nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM.S create mode 100644 src/libnr/nr_mmx_R8G8B8_R8G8B8_R8G8B8A8_P.S create mode 100644 src/libnr/testnr.cpp create mode 100644 src/libnrtype/.cvsignore create mode 100644 src/libnrtype/FontFactory.cpp create mode 100644 src/libnrtype/FontFactory.h create mode 100644 src/libnrtype/FontInstance.cpp create mode 100755 src/libnrtype/Layout-TNG-Compute.cpp create mode 100755 src/libnrtype/Layout-TNG-Input.cpp create mode 100755 src/libnrtype/Layout-TNG-OutIter.cpp create mode 100755 src/libnrtype/Layout-TNG-Output.cpp create mode 100755 src/libnrtype/Layout-TNG-Scanline-Maker.h create mode 100755 src/libnrtype/Layout-TNG-Scanline-Makers.cpp create mode 100755 src/libnrtype/Layout-TNG.cpp create mode 100755 src/libnrtype/Layout-TNG.h create mode 100644 src/libnrtype/Makefile_insert create mode 100644 src/libnrtype/RasterFont.cpp create mode 100644 src/libnrtype/RasterFont.h create mode 100644 src/libnrtype/TextWrapper.cpp create mode 100644 src/libnrtype/TextWrapper.h create mode 100644 src/libnrtype/boundary-type.h create mode 100644 src/libnrtype/font-glyph.h create mode 100644 src/libnrtype/font-instance.h create mode 100644 src/libnrtype/font-style-to-pos.cpp create mode 100644 src/libnrtype/font-style-to-pos.h create mode 100644 src/libnrtype/font-style.h create mode 100644 src/libnrtype/libnrtype.def create mode 100644 src/libnrtype/makefile.in create mode 100644 src/libnrtype/nr-type-pos-def.cpp create mode 100644 src/libnrtype/nr-type-pos-def.h create mode 100644 src/libnrtype/nr-type-primitives.cpp create mode 100644 src/libnrtype/nr-type-primitives.h create mode 100644 src/libnrtype/nrtype-forward.h create mode 100644 src/libnrtype/one-box.h create mode 100644 src/libnrtype/one-glyph.h create mode 100644 src/libnrtype/one-para.h create mode 100644 src/libnrtype/raster-glyph.h create mode 100644 src/libnrtype/raster-position.h create mode 100644 src/libnrtype/text-boundary.h create mode 100644 src/line-snapper.cpp create mode 100644 src/line-snapper.h create mode 100644 src/livarot/.cvsignore create mode 100644 src/livarot/AVL.cpp create mode 100644 src/livarot/AVL.h create mode 100644 src/livarot/AlphaLigne.cpp create mode 100644 src/livarot/AlphaLigne.h create mode 100644 src/livarot/BitLigne.cpp create mode 100644 src/livarot/BitLigne.h create mode 100644 src/livarot/Livarot.h create mode 100644 src/livarot/LivarotDefs.h create mode 100644 src/livarot/Makefile_insert create mode 100644 src/livarot/MyMath.h create mode 100644 src/livarot/MySeg.cpp create mode 100644 src/livarot/MySeg.h create mode 100644 src/livarot/Path.cpp create mode 100644 src/livarot/Path.h create mode 100644 src/livarot/PathConversion.cpp create mode 100644 src/livarot/PathCutting.cpp create mode 100644 src/livarot/PathOutline.cpp create mode 100644 src/livarot/PathSimplify.cpp create mode 100644 src/livarot/PathStroke.cpp create mode 100644 src/livarot/Shape.cpp create mode 100644 src/livarot/Shape.h create mode 100644 src/livarot/ShapeDraw.cpp create mode 100644 src/livarot/ShapeMisc.cpp create mode 100644 src/livarot/ShapeRaster.cpp create mode 100644 src/livarot/ShapeSweep.cpp create mode 100644 src/livarot/float-line.cpp create mode 100644 src/livarot/float-line.h create mode 100644 src/livarot/int-line.cpp create mode 100644 src/livarot/int-line.h create mode 100644 src/livarot/livarot-forward.h create mode 100644 src/livarot/makefile.in create mode 100644 src/livarot/path-description.cpp create mode 100644 src/livarot/path-description.h create mode 100644 src/livarot/sweep-event-queue.h create mode 100644 src/livarot/sweep-event.cpp create mode 100644 src/livarot/sweep-event.h create mode 100644 src/livarot/sweep-tree-list.cpp create mode 100644 src/livarot/sweep-tree-list.h create mode 100644 src/livarot/sweep-tree.cpp create mode 100644 src/livarot/sweep-tree.h create mode 100644 src/macros.h create mode 100644 src/main.cpp create mode 100644 src/make.dep create mode 100644 src/make.exclude create mode 100644 src/make.files create mode 100644 src/make.ofiles create mode 100644 src/makedef.pl create mode 100644 src/marker-status.cpp create mode 100644 src/marker-status.h create mode 100644 src/media.cpp create mode 100644 src/media.h create mode 100644 src/memeq.h create mode 100644 src/menus-skeleton.h create mode 100644 src/message-context.cpp create mode 100644 src/message-context.h create mode 100644 src/message-stack.cpp create mode 100644 src/message-stack.h create mode 100644 src/message.h create mode 100755 src/mkdep.pl create mode 100755 src/mkfiles.pl create mode 100644 src/mod360-test.cpp create mode 100644 src/mod360.cpp create mode 100644 src/mod360.h create mode 100644 src/modifier-fns.h create mode 100644 src/node-context.cpp create mode 100644 src/node-context.h create mode 100644 src/nodepath.cpp create mode 100644 src/nodepath.h create mode 100644 src/object-edit.cpp create mode 100644 src/object-edit.h create mode 100644 src/object-hierarchy.cpp create mode 100644 src/object-hierarchy.h create mode 100644 src/object-snapper.cpp create mode 100644 src/object-snapper.h create mode 100644 src/object-ui.cpp create mode 100644 src/object-ui.h create mode 100644 src/path-chemistry.cpp create mode 100644 src/path-chemistry.h create mode 100644 src/path-prefix.h create mode 100644 src/pen-context.cpp create mode 100644 src/pen-context.h create mode 100644 src/pencil-context.cpp create mode 100644 src/pencil-context.h create mode 100644 src/pixmaps/cursor-arc.xpm create mode 100644 src/pixmaps/cursor-arrow.xpm create mode 100644 src/pixmaps/cursor-calligraphy.xpm create mode 100644 src/pixmaps/cursor-connector.xpm create mode 100644 src/pixmaps/cursor-dropper.xpm create mode 100644 src/pixmaps/cursor-ellipse.xpm create mode 100644 src/pixmaps/cursor-gradient.xpm create mode 100644 src/pixmaps/cursor-node-d.xpm create mode 100644 src/pixmaps/cursor-node-m.xpm create mode 100644 src/pixmaps/cursor-node.xpm create mode 100644 src/pixmaps/cursor-pen.xpm create mode 100644 src/pixmaps/cursor-pencil.xpm create mode 100644 src/pixmaps/cursor-rect.xpm create mode 100644 src/pixmaps/cursor-select-d.xpm create mode 100644 src/pixmaps/cursor-select-m.xpm create mode 100644 src/pixmaps/cursor-spiral.xpm create mode 100644 src/pixmaps/cursor-star.xpm create mode 100644 src/pixmaps/cursor-text-insert.xpm create mode 100644 src/pixmaps/cursor-text.xpm create mode 100644 src/pixmaps/cursor-zoom-out.xpm create mode 100644 src/pixmaps/cursor-zoom.xpm create mode 100644 src/pixmaps/handles.xpm create mode 100644 src/plugin.def create mode 100644 src/preferences-skeleton.h create mode 100644 src/preferences.cpp create mode 100644 src/preferences.h create mode 100644 src/prefix.cpp create mode 100644 src/prefix.h create mode 100644 src/prefs-utils.cpp create mode 100644 src/prefs-utils.h create mode 100644 src/print.cpp create mode 100644 src/print.h create mode 100644 src/proofs create mode 100644 src/rect-context.cpp create mode 100644 src/rect-context.h create mode 100644 src/registrytool.cpp create mode 100644 src/registrytool.h create mode 100644 src/remove-last.h create mode 100644 src/removeoverlap/.cvsignore create mode 100644 src/removeoverlap/Makefile_insert create mode 100644 src/removeoverlap/block.cpp create mode 100644 src/removeoverlap/block.h create mode 100644 src/removeoverlap/blocks.cpp create mode 100644 src/removeoverlap/blocks.h create mode 100644 src/removeoverlap/constraint.cpp create mode 100644 src/removeoverlap/constraint.h create mode 100644 src/removeoverlap/generate-constraints.cpp create mode 100644 src/removeoverlap/generate-constraints.h create mode 100644 src/removeoverlap/makefile.in create mode 100644 src/removeoverlap/pairingheap/.cvsignore create mode 100644 src/removeoverlap/pairingheap/PairingHeap.cpp create mode 100644 src/removeoverlap/pairingheap/PairingHeap.h create mode 100644 src/removeoverlap/pairingheap/dsexceptions.h create mode 100755 src/removeoverlap/placement_SolveVPSC.cpp create mode 100755 src/removeoverlap/placement_SolveVPSC.h create mode 100644 src/removeoverlap/remove_rectangle_overlap-test.cpp create mode 100755 src/removeoverlap/remove_rectangle_overlap.cpp create mode 100755 src/removeoverlap/remove_rectangle_overlap.h create mode 100644 src/removeoverlap/removeoverlap.cpp create mode 100644 src/removeoverlap/removeoverlap.h create mode 100644 src/removeoverlap/solve_VPSC.cpp create mode 100644 src/removeoverlap/solve_VPSC.h create mode 100644 src/removeoverlap/variable.cpp create mode 100644 src/removeoverlap/variable.h create mode 100644 src/require-config.h create mode 100644 src/round-test.cpp create mode 100644 src/round.h create mode 100644 src/rubberband.cpp create mode 100644 src/rubberband.h create mode 100644 src/satisfied-guide-cns.cpp create mode 100644 src/satisfied-guide-cns.h create mode 100644 src/selcue.cpp create mode 100644 src/selcue.h create mode 100644 src/select-context.cpp create mode 100644 src/select-context.h create mode 100644 src/selection-chemistry.cpp create mode 100644 src/selection-chemistry.h create mode 100644 src/selection-describer.cpp create mode 100644 src/selection-describer.h create mode 100644 src/selection.cpp create mode 100644 src/selection.h create mode 100644 src/seltrans-handles.cpp create mode 100644 src/seltrans-handles.h create mode 100644 src/seltrans.cpp create mode 100644 src/seltrans.h create mode 100644 src/shortcuts-default-xml.cpp create mode 100644 src/shortcuts.cpp create mode 100644 src/shortcuts.h create mode 100644 src/slideshow.cpp create mode 100644 src/slideshow.h create mode 100644 src/snap.cpp create mode 100644 src/snap.h create mode 100644 src/snapped-point.cpp create mode 100644 src/snapped-point.h create mode 100644 src/snapper.cpp create mode 100644 src/snapper.h create mode 100644 src/sp-anchor.cpp create mode 100644 src/sp-anchor.h create mode 100644 src/sp-animation.cpp create mode 100644 src/sp-animation.h create mode 100644 src/sp-clippath.cpp create mode 100644 src/sp-clippath.h create mode 100644 src/sp-conn-end-pair.cpp create mode 100644 src/sp-conn-end-pair.h create mode 100644 src/sp-conn-end.cpp create mode 100644 src/sp-conn-end.h create mode 100644 src/sp-cursor.cpp create mode 100644 src/sp-cursor.h create mode 100644 src/sp-defs.cpp create mode 100644 src/sp-defs.h create mode 100644 src/sp-ellipse.cpp create mode 100644 src/sp-ellipse.h create mode 100644 src/sp-flowdiv.cpp create mode 100644 src/sp-flowdiv.h create mode 100644 src/sp-flowregion.cpp create mode 100644 src/sp-flowregion.h create mode 100644 src/sp-flowtext.cpp create mode 100644 src/sp-flowtext.h create mode 100644 src/sp-gradient-fns.h create mode 100644 src/sp-gradient-reference.cpp create mode 100644 src/sp-gradient-reference.h create mode 100644 src/sp-gradient-spread.h create mode 100644 src/sp-gradient-test.cpp create mode 100644 src/sp-gradient-units.h create mode 100644 src/sp-gradient-vector.h create mode 100644 src/sp-gradient.cpp create mode 100644 src/sp-gradient.h create mode 100644 src/sp-guide-attachment.h create mode 100644 src/sp-guide-constraint.h create mode 100644 src/sp-guide.cpp create mode 100644 src/sp-guide.h create mode 100644 src/sp-image.cpp create mode 100644 src/sp-image.h create mode 100644 src/sp-item-group.cpp create mode 100644 src/sp-item-group.h create mode 100644 src/sp-item-notify-moveto.cpp create mode 100644 src/sp-item-notify-moveto.h create mode 100644 src/sp-item-rm-unsatisfied-cns.cpp create mode 100644 src/sp-item-rm-unsatisfied-cns.h create mode 100644 src/sp-item-transform.cpp create mode 100644 src/sp-item-transform.h create mode 100644 src/sp-item-update-cns.cpp create mode 100644 src/sp-item-update-cns.h create mode 100644 src/sp-item.cpp create mode 100644 src/sp-item.h create mode 100644 src/sp-line.cpp create mode 100644 src/sp-line.h create mode 100644 src/sp-linear-gradient-fns.h create mode 100644 src/sp-linear-gradient.h create mode 100644 src/sp-marker-loc.h create mode 100644 src/sp-marker.cpp create mode 100644 src/sp-marker.h create mode 100644 src/sp-mask.cpp create mode 100644 src/sp-mask.h create mode 100644 src/sp-metadata.cpp create mode 100644 src/sp-metadata.h create mode 100644 src/sp-metric.h create mode 100644 src/sp-metrics.cpp create mode 100644 src/sp-metrics.h create mode 100644 src/sp-namedview.cpp create mode 100644 src/sp-namedview.h create mode 100644 src/sp-object-group.cpp create mode 100644 src/sp-object-group.h create mode 100644 src/sp-object-repr.cpp create mode 100644 src/sp-object-repr.h create mode 100644 src/sp-object.cpp create mode 100644 src/sp-object.h create mode 100644 src/sp-offset.cpp create mode 100644 src/sp-offset.h create mode 100644 src/sp-paint-server.cpp create mode 100644 src/sp-paint-server.h create mode 100644 src/sp-path.cpp create mode 100644 src/sp-path.h create mode 100644 src/sp-pattern.cpp create mode 100644 src/sp-pattern.h create mode 100644 src/sp-polygon.cpp create mode 100644 src/sp-polygon.h create mode 100644 src/sp-polyline.cpp create mode 100644 src/sp-polyline.h create mode 100644 src/sp-radial-gradient-fns.h create mode 100644 src/sp-radial-gradient.h create mode 100644 src/sp-rect.cpp create mode 100644 src/sp-rect.h create mode 100644 src/sp-root.cpp create mode 100644 src/sp-root.h create mode 100644 src/sp-shape.cpp create mode 100644 src/sp-shape.h create mode 100644 src/sp-skeleton.cpp create mode 100644 src/sp-skeleton.h create mode 100644 src/sp-spiral.cpp create mode 100644 src/sp-spiral.h create mode 100644 src/sp-star.cpp create mode 100644 src/sp-star.h create mode 100644 src/sp-stop-fns.h create mode 100644 src/sp-stop.h create mode 100644 src/sp-string.cpp create mode 100644 src/sp-string.h create mode 100644 src/sp-style-elem-test.cpp create mode 100644 src/sp-style-elem.cpp create mode 100644 src/sp-style-elem.h create mode 100644 src/sp-symbol.cpp create mode 100644 src/sp-symbol.h create mode 100644 src/sp-text.cpp create mode 100644 src/sp-text.h create mode 100644 src/sp-textpath.h create mode 100644 src/sp-tspan.cpp create mode 100644 src/sp-tspan.h create mode 100644 src/sp-use-reference.cpp create mode 100644 src/sp-use-reference.h create mode 100644 src/sp-use.cpp create mode 100644 src/sp-use.h create mode 100644 src/spiral-context.cpp create mode 100644 src/spiral-context.h create mode 100644 src/splivarot.cpp create mode 100644 src/splivarot.h create mode 100644 src/star-context.cpp create mode 100644 src/star-context.h create mode 100644 src/streams-gzip.cpp create mode 100644 src/streams-gzip.h create mode 100644 src/streams-handles.cpp create mode 100644 src/streams-handles.h create mode 100644 src/streams-jar.cpp create mode 100644 src/streams-jar.h create mode 100644 src/streams-zlib.cpp create mode 100644 src/streams-zlib.h create mode 100644 src/streq.h create mode 100644 src/strneq.h create mode 100644 src/style-test.cpp create mode 100644 src/style.cpp create mode 100644 src/style.h create mode 100644 src/svg-profile.h create mode 100644 src/svg-view-widget.cpp create mode 100644 src/svg-view-widget.h create mode 100644 src/svg-view.cpp create mode 100644 src/svg-view.h create mode 100644 src/svg/.cvsignore create mode 100644 src/svg/HACKING create mode 100644 src/svg/Makefile_insert create mode 100644 src/svg/css-ostringstream-test.h create mode 100644 src/svg/css-ostringstream.cpp create mode 100644 src/svg/css-ostringstream.h create mode 100644 src/svg/ftos.cpp create mode 100644 src/svg/ftos.h create mode 100644 src/svg/gnome-canvas-bpath-util.cpp create mode 100644 src/svg/gnome-canvas-bpath-util.h create mode 100644 src/svg/itos.cpp create mode 100644 src/svg/makefile.in create mode 100644 src/svg/round.cpp create mode 100644 src/svg/sp-svg.def create mode 100644 src/svg/stringstream-test.h create mode 100644 src/svg/stringstream.cpp create mode 100644 src/svg/stringstream.h create mode 100644 src/svg/strip-trailing-zeros.cpp create mode 100644 src/svg/strip-trailing-zeros.h create mode 100644 src/svg/svg-affine.cpp create mode 100644 src/svg/svg-color.cpp create mode 100644 src/svg/svg-length.cpp create mode 100644 src/svg/svg-length.h create mode 100644 src/svg/svg-path.cpp create mode 100644 src/svg/svg.h create mode 100644 src/text-chemistry.cpp create mode 100644 src/text-chemistry.h create mode 100644 src/text-context.cpp create mode 100644 src/text-context.h create mode 100644 src/text-editing.cpp create mode 100644 src/text-editing.h create mode 100644 src/text-tag-attributes.h create mode 100644 src/tools-switch.cpp create mode 100644 src/tools-switch.h create mode 100644 src/trace/.cvsignore create mode 100644 src/trace/Makefile_insert create mode 100644 src/trace/filterset.cpp create mode 100644 src/trace/filterset.h create mode 100644 src/trace/imagemap-gdk.cpp create mode 100644 src/trace/imagemap-gdk.h create mode 100644 src/trace/imagemap.cpp create mode 100644 src/trace/imagemap.h create mode 100644 src/trace/makefile.in create mode 100644 src/trace/potrace/.cvsignore create mode 100644 src/trace/potrace/auxiliary.h create mode 100644 src/trace/potrace/bitmap.h create mode 100644 src/trace/potrace/curve.cpp create mode 100644 src/trace/potrace/curve.h create mode 100644 src/trace/potrace/decompose.cpp create mode 100644 src/trace/potrace/decompose.h create mode 100644 src/trace/potrace/greymap.cpp create mode 100644 src/trace/potrace/greymap.h create mode 100644 src/trace/potrace/inkscape-potrace.cpp create mode 100644 src/trace/potrace/inkscape-potrace.h create mode 100644 src/trace/potrace/lists.h create mode 100644 src/trace/potrace/potracelib.cpp create mode 100644 src/trace/potrace/potracelib.h create mode 100644 src/trace/potrace/progress.h create mode 100644 src/trace/potrace/render.cpp create mode 100644 src/trace/potrace/render.h create mode 100644 src/trace/potrace/trace.cpp create mode 100644 src/trace/potrace/trace.h create mode 100644 src/trace/trace.cpp create mode 100644 src/trace/trace.h create mode 100644 src/traits/.cvsignore create mode 100644 src/traits/Makefile_insert create mode 100644 src/traits/copy.h create mode 100644 src/traits/function.h create mode 100644 src/traits/list-copy.h create mode 100644 src/traits/makefile.in create mode 100644 src/traits/reference.h create mode 100644 src/ui/.cvsignore create mode 100644 src/ui/Makefile_insert create mode 100644 src/ui/dialog/.cvsignore create mode 100644 src/ui/dialog/Makefile_insert create mode 100644 src/ui/dialog/aboutbox.cpp create mode 100644 src/ui/dialog/aboutbox.h create mode 100644 src/ui/dialog/align-and-distribute.cpp create mode 100644 src/ui/dialog/align-and-distribute.h create mode 100644 src/ui/dialog/dialog-manager.cpp create mode 100644 src/ui/dialog/dialog-manager.h create mode 100644 src/ui/dialog/dialog.cpp create mode 100644 src/ui/dialog/dialog.h create mode 100644 src/ui/dialog/document-metadata.cpp create mode 100644 src/ui/dialog/document-metadata.h create mode 100644 src/ui/dialog/document-properties.cpp create mode 100644 src/ui/dialog/document-properties.h create mode 100644 src/ui/dialog/export.cpp create mode 100644 src/ui/dialog/export.h create mode 100644 src/ui/dialog/extension-editor.cpp create mode 100644 src/ui/dialog/extension-editor.h create mode 100644 src/ui/dialog/fill-and-stroke.cpp create mode 100644 src/ui/dialog/fill-and-stroke.h create mode 100644 src/ui/dialog/find.cpp create mode 100644 src/ui/dialog/find.h create mode 100644 src/ui/dialog/inkscape-preferences.cpp create mode 100644 src/ui/dialog/inkscape-preferences.h create mode 100644 src/ui/dialog/layer-editor.cpp create mode 100644 src/ui/dialog/layer-editor.h create mode 100644 src/ui/dialog/makefile.in create mode 100644 src/ui/dialog/memory.cpp create mode 100644 src/ui/dialog/memory.h create mode 100644 src/ui/dialog/messages.cpp create mode 100644 src/ui/dialog/messages.h create mode 100644 src/ui/dialog/scriptdialog.cpp create mode 100644 src/ui/dialog/scriptdialog.h create mode 100644 src/ui/dialog/session-player.cpp create mode 100644 src/ui/dialog/session-player.h create mode 100644 src/ui/dialog/text-properties.cpp create mode 100644 src/ui/dialog/text-properties.h create mode 100644 src/ui/dialog/tracedialog.cpp create mode 100644 src/ui/dialog/tracedialog.h create mode 100644 src/ui/dialog/transformation.cpp create mode 100644 src/ui/dialog/transformation.h create mode 100644 src/ui/dialog/tree-editor.cpp create mode 100644 src/ui/dialog/tree-editor.h create mode 100644 src/ui/dialog/whiteboard-connect.cpp create mode 100644 src/ui/dialog/whiteboard-connect.h create mode 100644 src/ui/dialog/whiteboard-sharewithchat.cpp create mode 100644 src/ui/dialog/whiteboard-sharewithchat.h create mode 100644 src/ui/dialog/whiteboard-sharewithuser.cpp create mode 100644 src/ui/dialog/whiteboard-sharewithuser.h create mode 100644 src/ui/dialog/xml-editor.cpp create mode 100644 src/ui/dialog/xml-editor.h create mode 100644 src/ui/icons.cpp create mode 100644 src/ui/icons.h create mode 100644 src/ui/makefile.in create mode 100644 src/ui/previewable.h create mode 100644 src/ui/previewfillable.h create mode 100644 src/ui/previewholder.cpp create mode 100644 src/ui/previewholder.h create mode 100644 src/ui/stock-items.cpp create mode 100644 src/ui/stock-items.h create mode 100644 src/ui/stock.cpp create mode 100644 src/ui/stock.h create mode 100644 src/ui/view/.cvsignore create mode 100644 src/ui/view/Makefile_insert create mode 100644 src/ui/view/desktop-affine.cpp create mode 100644 src/ui/view/desktop-affine.h create mode 100644 src/ui/view/desktop-events.cpp create mode 100644 src/ui/view/desktop-events.h create mode 100644 src/ui/view/desktop-handles.cpp create mode 100644 src/ui/view/desktop-handles.h create mode 100644 src/ui/view/desktop-style.cpp create mode 100644 src/ui/view/desktop-style.h create mode 100644 src/ui/view/desktop.cpp create mode 100644 src/ui/view/desktop.h create mode 100644 src/ui/view/edit-widget-interface.h create mode 100644 src/ui/view/edit-widget.cpp create mode 100644 src/ui/view/edit-widget.h create mode 100644 src/ui/view/edit.cpp create mode 100644 src/ui/view/edit.h create mode 100644 src/ui/view/makefile.in create mode 100644 src/ui/view/view-widget.cpp create mode 100644 src/ui/view/view-widget.h create mode 100644 src/ui/view/view.cpp create mode 100644 src/ui/view/view.h create mode 100644 src/ui/widget/.cvsignore create mode 100644 src/ui/widget/Makefile_insert create mode 100644 src/ui/widget/button.cpp create mode 100644 src/ui/widget/button.h create mode 100644 src/ui/widget/color-picker.cpp create mode 100644 src/ui/widget/color-picker.h create mode 100644 src/ui/widget/color-preview.cpp create mode 100644 src/ui/widget/color-preview.h create mode 100644 src/ui/widget/combo-text.cpp create mode 100644 src/ui/widget/combo-text.h create mode 100644 src/ui/widget/entity-entry.cpp create mode 100644 src/ui/widget/entity-entry.h create mode 100644 src/ui/widget/handlebox.cpp create mode 100644 src/ui/widget/handlebox.h create mode 100644 src/ui/widget/icon-widget.cpp create mode 100644 src/ui/widget/icon-widget.h create mode 100644 src/ui/widget/imageicon.cpp create mode 100644 src/ui/widget/imageicon.h create mode 100644 src/ui/widget/labelled.cpp create mode 100644 src/ui/widget/labelled.h create mode 100644 src/ui/widget/licensor.cpp create mode 100644 src/ui/widget/licensor.h create mode 100644 src/ui/widget/makefile.in create mode 100644 src/ui/widget/notebook-page.cpp create mode 100644 src/ui/widget/notebook-page.h create mode 100644 src/ui/widget/page-sizer.cpp create mode 100644 src/ui/widget/page-sizer.h create mode 100644 src/ui/widget/panel.cpp create mode 100644 src/ui/widget/panel.h create mode 100644 src/ui/widget/preferences-widget.cpp create mode 100644 src/ui/widget/preferences-widget.h create mode 100644 src/ui/widget/registered-widget.cpp create mode 100644 src/ui/widget/registered-widget.h create mode 100644 src/ui/widget/registry.cpp create mode 100644 src/ui/widget/registry.h create mode 100644 src/ui/widget/ruler.cpp create mode 100644 src/ui/widget/ruler.h create mode 100644 src/ui/widget/scalar-unit.cpp create mode 100644 src/ui/widget/scalar-unit.h create mode 100644 src/ui/widget/scalar.cpp create mode 100644 src/ui/widget/scalar.h create mode 100644 src/ui/widget/selected-style.cpp create mode 100644 src/ui/widget/selected-style.h create mode 100644 src/ui/widget/style-swatch.cpp create mode 100644 src/ui/widget/style-swatch.h create mode 100644 src/ui/widget/svg-canvas.cpp create mode 100644 src/ui/widget/svg-canvas.h create mode 100644 src/ui/widget/tolerance-slider.cpp create mode 100644 src/ui/widget/tolerance-slider.h create mode 100644 src/ui/widget/toolbox.cpp create mode 100644 src/ui/widget/toolbox.h create mode 100644 src/ui/widget/unit-menu.cpp create mode 100644 src/ui/widget/unit-menu.h create mode 100644 src/ui/widget/zoom-status.cpp create mode 100644 src/ui/widget/zoom-status.h create mode 100644 src/undo-stack-observer.h create mode 100644 src/unit-constants.h create mode 100644 src/uri-references.cpp create mode 100644 src/uri-references.h create mode 100644 src/uri.cpp create mode 100644 src/uri.h create mode 100644 src/utest/.cvsignore create mode 100644 src/utest/Makefile_insert create mode 100644 src/utest/makefile.in create mode 100644 src/utest/test-1ary-cases.h create mode 100644 src/utest/test-2ary-cases.h create mode 100644 src/utest/utest.h create mode 100644 src/util/.cvsignore create mode 100644 src/util/Makefile_insert create mode 100644 src/util/compose.hpp create mode 100644 src/util/filter-list.h create mode 100644 src/util/forward-pointer-iterator.h create mode 100644 src/util/glib-list-iterators.h create mode 100644 src/util/list-container-test.cpp create mode 100644 src/util/list-container.h create mode 100644 src/util/list.h create mode 100644 src/util/makefile.in create mode 100644 src/util/map-list.h create mode 100644 src/util/reverse-list.h create mode 100644 src/util/shared-c-string-ptr.cpp create mode 100644 src/util/shared-c-string-ptr.h create mode 100644 src/util/tuple.h create mode 100644 src/util/ucompose.hpp create mode 100644 src/util/units.cpp create mode 100644 src/util/units.h create mode 100644 src/verbs.cpp create mode 100644 src/verbs.h create mode 100644 src/version.cpp create mode 100644 src/version.h create mode 100644 src/widgets/.cvsignore create mode 100644 src/widgets/Makefile_insert create mode 100644 src/widgets/button.cpp create mode 100644 src/widgets/button.h create mode 100644 src/widgets/dash-selector.cpp create mode 100644 src/widgets/dash-selector.h create mode 100644 src/widgets/desktop-widget.cpp create mode 100644 src/widgets/desktop-widget.h create mode 100644 src/widgets/font-selector.cpp create mode 100644 src/widgets/font-selector.h create mode 100644 src/widgets/gradient-image.cpp create mode 100644 src/widgets/gradient-image.h create mode 100644 src/widgets/gradient-selector.cpp create mode 100644 src/widgets/gradient-selector.h create mode 100644 src/widgets/gradient-toolbar.cpp create mode 100644 src/widgets/gradient-toolbar.h create mode 100644 src/widgets/gradient-vector.cpp create mode 100644 src/widgets/gradient-vector.h create mode 100644 src/widgets/icon.cpp create mode 100644 src/widgets/icon.h create mode 100644 src/widgets/layer-selector.cpp create mode 100644 src/widgets/layer-selector.h create mode 100644 src/widgets/makefile.in create mode 100644 src/widgets/paint-selector.cpp create mode 100644 src/widgets/paint-selector.h create mode 100644 src/widgets/ruler.cpp create mode 100644 src/widgets/ruler.h create mode 100644 src/widgets/select-toolbar.cpp create mode 100644 src/widgets/select-toolbar.h create mode 100644 src/widgets/shrink-wrap-button.cpp create mode 100644 src/widgets/shrink-wrap-button.h create mode 100644 src/widgets/sp-color-gtkselector.cpp create mode 100644 src/widgets/sp-color-gtkselector.h create mode 100644 src/widgets/sp-color-notebook.cpp create mode 100644 src/widgets/sp-color-notebook.h create mode 100644 src/widgets/sp-color-preview.cpp create mode 100644 src/widgets/sp-color-preview.h create mode 100644 src/widgets/sp-color-scales.cpp create mode 100644 src/widgets/sp-color-scales.h create mode 100644 src/widgets/sp-color-selector.cpp create mode 100644 src/widgets/sp-color-selector.h create mode 100644 src/widgets/sp-color-slider.cpp create mode 100644 src/widgets/sp-color-slider.h create mode 100644 src/widgets/sp-color-wheel-selector.cpp create mode 100644 src/widgets/sp-color-wheel-selector.h create mode 100644 src/widgets/sp-color-wheel.cpp create mode 100644 src/widgets/sp-color-wheel.h create mode 100644 src/widgets/sp-widget.cpp create mode 100644 src/widgets/sp-widget.h create mode 100644 src/widgets/sp-xmlview-attr-list.cpp create mode 100644 src/widgets/sp-xmlview-attr-list.h create mode 100644 src/widgets/sp-xmlview-content.cpp create mode 100644 src/widgets/sp-xmlview-content.h create mode 100644 src/widgets/sp-xmlview-tree.cpp create mode 100644 src/widgets/sp-xmlview-tree.h create mode 100644 src/widgets/spinbutton-events.cpp create mode 100644 src/widgets/spinbutton-events.h create mode 100644 src/widgets/spw-utilities.cpp create mode 100644 src/widgets/spw-utilities.h create mode 100644 src/widgets/toolbox.cpp create mode 100644 src/widgets/toolbox.h create mode 100644 src/widgets/widget-sizes.h create mode 100644 src/winmain.cpp create mode 100644 src/xml/.cvsignore create mode 100644 src/xml/Makefile_insert create mode 100644 src/xml/attribute-record.h create mode 100644 src/xml/comment-node.h create mode 100644 src/xml/composite-node-observer.cpp create mode 100644 src/xml/composite-node-observer.h create mode 100644 src/xml/croco-node-iface.cpp create mode 100644 src/xml/croco-node-iface.h create mode 100644 src/xml/document.h create mode 100644 src/xml/element-node.h create mode 100644 src/xml/event-fns.h create mode 100644 src/xml/event.cpp create mode 100644 src/xml/event.h create mode 100644 src/xml/invalid-operation-exception.h create mode 100644 src/xml/log-builder.cpp create mode 100644 src/xml/log-builder.h create mode 100644 src/xml/makefile.in create mode 100644 src/xml/node-event-vector.h create mode 100644 src/xml/node-fns.cpp create mode 100644 src/xml/node-fns.h create mode 100644 src/xml/node-iterators.h create mode 100644 src/xml/node-observer.h create mode 100644 src/xml/node.h create mode 100644 src/xml/quote-test.cpp create mode 100644 src/xml/quote-test.h create mode 100644 src/xml/quote.cpp create mode 100644 src/xml/quote.h create mode 100644 src/xml/repr-action-test.cpp create mode 100644 src/xml/repr-action-test.h create mode 100644 src/xml/repr-css.cpp create mode 100644 src/xml/repr-io.cpp create mode 100644 src/xml/repr-sorting.cpp create mode 100644 src/xml/repr-sorting.h create mode 100644 src/xml/repr-util.cpp create mode 100644 src/xml/repr.cpp create mode 100644 src/xml/repr.h create mode 100644 src/xml/session.h create mode 100644 src/xml/simple-document.cpp create mode 100644 src/xml/simple-document.h create mode 100644 src/xml/simple-node.cpp create mode 100644 src/xml/simple-node.h create mode 100644 src/xml/simple-session.cpp create mode 100644 src/xml/simple-session.h create mode 100644 src/xml/sp-css-attr.h create mode 100644 src/xml/text-node.h create mode 100644 src/xml/transaction-logger.h create mode 100644 src/zoom-context.cpp create mode 100644 src/zoom-context.h (limited to 'src') diff --git a/src/.cvsignore b/src/.cvsignore new file mode 100644 index 000000000..4cdeae202 --- /dev/null +++ b/src/.cvsignore @@ -0,0 +1,19 @@ +*.la +*.lo +*.o +*.a +.deps +.libs +Makefile +Makefile.in +TAGS +tags +check-header-compile +doxygen-log +inkscape +inkscape_version.h +inkview +spsvgview +inkscape.def +*.dll +*-test diff --git a/src/Doxyfile b/src/Doxyfile new file mode 100644 index 000000000..fae65d053 --- /dev/null +++ b/src/Doxyfile @@ -0,0 +1,269 @@ +# Doxyfile: default configuration for `doxygen'. + +# We'll explicitly list inputs (for faster doxygen runs), but will probably +# switch to recursive scan once we have a large number of inputs. +#FILE_PATTERNS = *.cpp *.h +#RECURSIVE = yes +# find -name '*.cpp' -o -name '*.h'|xargs grep -l '\\file'|sort|sed 's,^./, ,;s,$, \\,' +# (and remove the non source files sp-skeleton.*). +INPUT = \ + application/application.cpp \ + application/application.h \ + application/editor.cpp \ + application/editor.h \ + arc-context.cpp \ + attributes.cpp \ + attributes.h \ + color-rgba.h \ + color.cpp \ + color.h \ + composite-undo-stack-observer.h \ + desktop-style.cpp \ + desktop.cpp \ + desktop.h \ + dialogs/color-picker.h \ + dialogs/desktop-properties.cpp \ + dialogs/export.cpp \ + dialogs/filedialog.h \ + dialogs/unclump.h \ + dir-util.cpp \ + display/bezier-utils.cpp \ + display/curve.cpp \ + display/curve.h \ + display/sp-canvas.cpp \ + display/sp-canvas.h \ + document-undo.cpp \ + document.cpp \ + document.h \ + draw-anchor.cpp \ + draw-anchor.h \ + event-context.cpp \ + event-context.h \ + extension/extension.cpp \ + extension/extension.h \ + extension/implementation/plugin-link.h \ + extension/implementation/plugin.cpp \ + extension/implementation/plugin.h \ + extension/implementation/script.cpp \ + extension/internal/bluredge.cpp \ + extension/internal/gimpgrad.cpp \ + extension/internal/gimpgrad.h \ + extension/internal/grid.cpp \ + extension/internal/ps.cpp \ + extension/internal/ps.h \ + extension/parameter.cpp \ + extension/parameter.h \ + fill-or-stroke.h \ + gc-anchored.h \ + gc-managed.h \ + geom.cpp \ + geom.h \ + grid-snapper.cpp \ + grid-snapper.h \ + guide-snapper.cpp \ + guide-snapper.h \ + helper/action.cpp \ + helper/action.h \ + jabber_whiteboard/buddy-list-manager.h \ + jabber_whiteboard/callbacks.h \ + jabber_whiteboard/chat-handler.h \ + jabber_whiteboard/defines.h \ + jabber_whiteboard/deserializer.h \ + jabber_whiteboard/error-codes.h \ + jabber_whiteboard/internal-constants.h \ + jabber_whiteboard/invitation-confirm-dialog.h \ + jabber_whiteboard/jabber-handlers.h \ + jabber_whiteboard/message-aggregator.h \ + jabber_whiteboard/message-contexts.h \ + jabber_whiteboard/message-handler.h \ + jabber_whiteboard/message-node.h \ + jabber_whiteboard/message-processors.cpp \ + jabber_whiteboard/message-processors.h \ + jabber_whiteboard/message-queue.h \ + jabber_whiteboard/message-tags.h \ + jabber_whiteboard/message-utilities.h \ + jabber_whiteboard/node-tracker-event-tracker.h \ + jabber_whiteboard/node-tracker-observer.h \ + jabber_whiteboard/node-tracker.cpp \ + jabber_whiteboard/node-tracker.h \ + jabber_whiteboard/node-utilities.h \ + jabber_whiteboard/session-file-player.h \ + jabber_whiteboard/session-file-selector.h \ + jabber_whiteboard/session-file.h \ + jabber_whiteboard/session-manager.h \ + jabber_whiteboard/tracker-node.h \ + jabber_whiteboard/typedefs.h \ + jabber_whiteboard/undo-stack-observer.h \ + knot-enums.h \ + knot-holder-entity.h \ + knot.cpp \ + knot.h \ + libnr/n-art-bpath.h \ + libnr/nr-matrix-scale-ops.h \ + libnr/nr-matrix-translate-ops.h \ + libnr/nr-matrix.cpp \ + libnr/nr-matrix.h \ + libnr/nr-path-code.h \ + libnr/nr-pixblock.cpp \ + libnr/nr-pixblock.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-fns.cpp \ + libnr/nr-rotate-fns.h \ + libnr/nr-rotate.h \ + libnr/nr-scale-matrix-ops.h \ + libnr/nr-types.cpp \ + libnrtype/boundary-type.h \ + libnrtype/one-box.h \ + libnrtype/one-glyph.h \ + libnrtype/text-boundary.h \ + livarot/float-line.cpp \ + livarot/float-line.h \ + livarot/int-line.cpp \ + livarot/int-line.h \ + livarot/sweep-event-queue.h \ + livarot/sweep-event.h \ + livarot/sweep-tree-list.h \ + main.cpp \ + message-context.h \ + message-stack.h \ + modifier-fns.h \ + nodepath.cpp \ + nodepath.h \ + object-hierarchy.cpp \ + object-hierarchy.h \ + pen-context.cpp \ + pen-context.h \ + pencil-context.cpp \ + pencil-context.h \ + preferences.cpp \ + preferences.h \ + print.cpp \ + print.h \ + removeoverlap/remove_rectangle_overlap.h \ + selection.cpp \ + selection.h \ + shortcuts.cpp \ + snap.cpp \ + snap.h \ + snapper.cpp \ + snapper.h \ + sp-animation.cpp \ + sp-gradient-fns.h \ + sp-gradient.cpp \ + sp-gradient.h \ + sp-item-notify-moveto.cpp \ + sp-item.cpp \ + sp-item.h \ + sp-linear-gradient-fns.h \ + sp-linear-gradient.h \ + sp-object.cpp \ + sp-object.h \ + sp-offset.cpp \ + sp-offset.h \ + sp-radial-gradient-fns.h \ + sp-radial-gradient.h \ + sp-root.cpp \ + sp-root.h \ + sp-spiral.cpp \ + sp-spiral.h \ + sp-stop.h \ + style.cpp \ + style.h \ + svg-view-widget.cpp \ + svg-view-widget.h \ + svg-view.cpp \ + svg-view.h \ + ui/dialog/document-preferences.cpp \ + ui/dialog/document-preferences.h \ + ui/view/edit-widget-interface.h \ + ui/view/view-widget.cpp \ + ui/view/view-widget.h \ + ui/view/view.cpp \ + ui/view/view.h \ + ui/widget/color-picker.cpp \ + ui/widget/color-picker.h \ + ui/widget/color-preview.cpp \ + ui/widget/color-preview.h \ + ui/widget/entity-entry.cpp \ + ui/widget/entity-entry.h \ + ui/widget/licensor.cpp \ + ui/widget/licensor.h \ + ui/widget/page-sizer.cpp \ + ui/widget/page-sizer.h \ + ui/widget/registered-widget.cpp \ + ui/widget/registered-widget.h \ + ui/widget/registry.cpp \ + ui/widget/registry.h \ + ui/widget/ruler.cpp \ + ui/widget/ruler.h \ + ui/widget/svg-canvas.cpp \ + ui/widget/svg-canvas.h \ + ui/widget/zoom-status.cpp \ + ui/widget/zoom-status.h \ + undo-stack-observer.h \ + uri.cpp \ + uri.h \ + util/list.h \ + verbs.cpp \ + verbs.h \ + widgets/desktop-widget.cpp \ + widgets/desktop-widget.h \ + widgets/icon.cpp \ + widgets/paint-selector.cpp \ + widgets/paint-selector.h \ + widgets/toolbox.cpp \ + xml/quote.cpp \ + xml/repr-sorting.h \ + xml/repr-util.cpp \ + xml/repr.cpp \ + xml/repr.h + + +# Uncomment this to treat undocumented things as if they had an empty +# documentation string; comment it out to suppress all undocumented things from +# the output. +# Leaving it uncommented allows using doxygen output as the primary information +# source about a class (without needing to look at the source code to look for +# undocumented things). +# OTOH, you may find it annoying to have reems of relatively unhelpful +# information: commenting it out gives more compact display of the helpful +# bits. +# I'm commenting it out for now to facilitate checking existing doc comments +# for doxygen correctness. +#EXTRACT_ALL = yes + +# I'll disable this for the moment, to reduce the number of files in the output. +SOURCE_BROWSER = no + +# Keep the output out of the src directory so that it doesn't get in the way of +# `rgrep'. +OUTPUT_DIRECTORY = ../doxygen + +# In absence of explicit `\brief', treat the first "sentence" as the brief part +# and the rest as detail. (With explicit `\brief', the first _paragraph_ is +# considered the brief part.) +# +# It's unclear whether programmers should deliberately use this facility. +# Advantage: Less clutter. +# Disadvantage: Absence of `\brief' may indicate that the comment was written +# by someone unfamiliar with doxygen and not giving thought to what the brief +# description should be. Using explicit `\brief' may facilitate checking +# non-\briefed comments for doxygen correctness. +# OTOH, using `\brief' may be parrot-like: it doesn't necessarily indicate +# doxygen familiarity. +JAVADOC_AUTOBRIEF = yes + +WARN_IF_UNDOCUMENTED = yes + +GENERATE_TODOLIST = yes + +GENERATE_BUGLIST = yes + +REFERENCED_BY_RELATION = yes + +EXTRACT_STATIC = yes + +WARN_LOGFILE = doxygen-log + diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 000000000..73e6deb36 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,261 @@ +## Process this file with automake to produce Makefile.in + + +# ################################################ +# +# G L O B A L +# +# ################################################ + +# Should work in either automake1.7 or 1.8, but 1.6 doesn't +# handle foo/libfoo_a_CPPFLAGS properly (if at all). +# Update: We now avoid setting foo/libfoo_a_CPPFLAGS, +# so perhaps 1.6 will work. +AUTOMAKE_OPTIONS = 1.7 subdir-objects + +INCLUDES = \ + $(PERL_CFLAGS) $(PYTHON_CFLAGS) \ + $(FREETYPE_CFLAGS) \ + $(GNOME_PRINT_CFLAGS) \ + $(GNOME_VFS_CFLAGS) \ + $(LIBLOUDMOUTH_CFLAGS) \ + $(XFT_CFLAGS) \ + -DPOTRACE=\"potrace\" \ + $(INKSCAPE_CFLAGS) \ + -I$(top_srcdir)/cxxtest + +include Makefile_insert +include application/Makefile_insert +include dialogs/Makefile_insert +include display/Makefile_insert +include dom/Makefile_insert +include extension/Makefile_insert +include extension/implementation/Makefile_insert +include extension/internal/Makefile_insert +include extension/script/Makefile_insert +include helper/Makefile_insert +include inkjar/Makefile_insert +include io/Makefile_insert +include jabber_whiteboard/Makefile_insert +include libcroco/Makefile_insert +include libnr/Makefile_insert +include libnrtype/Makefile_insert +include libavoid/Makefile_insert +include livarot/Makefile_insert +include removeoverlap/Makefile_insert +include svg/Makefile_insert +include utest/Makefile_insert +include widgets/Makefile_insert +include debug/Makefile_insert +include xml/Makefile_insert +include traits/Makefile_insert +include algorithms/Makefile_insert +include ui/Makefile_insert +include ui/dialog/Makefile_insert +include ui/view/Makefile_insert +include ui/widget/Makefile_insert +include util/Makefile_insert +include trace/Makefile_insert + +bin_PROGRAMS = inkscape inkview + +noinst_LIBRARIES = \ + libinkpre.a \ + application/libinkapp.a \ + dialogs/libspdialogs.a \ + jabber_whiteboard/libjabber_whiteboard.a \ + display/libspdisplay.a \ + dom/libdom.a \ + extension/implementation/libimplementation.a \ + extension/internal/libinternal.a \ + extension/libextension.a \ + extension/script/libscript.a \ + helper/libspchelp.a \ + io/libio.a \ + libcroco/libcroco.a \ + ui/libui.a \ + ui/dialog/libuidialog.a \ + ui/view/libuiview.a \ + ui/widget/libuiwidget.a \ + util/libinkutil.a \ + debug/libinkdebug.a \ + $(inkjar_libs) \ + libnr/libnr.a \ + libnrtype/libnrtype.a \ + libavoid/libavoid.a \ + livarot/libvarot.a \ + removeoverlap/libremoveoverlap.a \ + svg/libspsvg.a \ + widgets/libspwidgets.a \ + trace/libtrace.a \ + xml/libspxml.a \ + libinkpost.a + +check_LIBRARIES = \ + libnr/libtest-nr.a \ + svg/libtest-svg.a \ + xml/libtest-xml.a + +DISTCLEANFILES = \ + helper/sp-marshal.cpp \ + helper/sp-marshal.h \ + inkscape_version.h + +EXTRA_DIST = \ + mkdep.pl \ + mkfiles.pl \ + make.exclude \ + make.dep \ + make.files \ + make.ofiles \ + Doxyfile \ + sp-skeleton.cpp sp-skeleton.h \ + algorithms/makefile.in \ + application/makefile.in \ + debug/makefile.in \ + dialogs/makefile.in \ + dialogs/filedialog-win32.cpp \ + display/makefile.in \ + dom/makefile.in \ + extension/implementation/makefile.in \ + extension/internal/makefile.in \ + extension/makefile.in \ + extension/plugin/makefile.in \ + extension/script/makefile.in \ + helper/makefile.in \ + inkjar/makefile.in \ + io/makefile.in \ + io/crystalegg.xml \ + io/doc2html.xsl \ + jabber_whiteboard/makefile.in \ + libcroco/makefile.in \ + libnr/makefile.in \ + libnrtype/makefile.in \ + libavoid/makefile.in \ + livarot/makefile.in \ + removeoverlap/makefile.in \ + svg/makefile.in \ + trace/makefile.in \ + traits/makefile.in \ + utest/makefile.in \ + ui/makefile.in \ + ui/dialog/makefile.in \ + ui/view/makefile.in \ + ui/widget/makefile.in \ + util/makefile.in \ + widgets/makefile.in \ + xml/makefile.in \ + extension/internal/gnome.cpp \ + extension/internal/gnome.h \ + extension/internal/win32.cpp \ + extension/internal/win32.h \ + helper/sp-marshal.list \ + utest/utest.h \ + utest/test-1ary-cases.h \ + traits/copy.h \ + traits/function.h \ + traits/list-copy.h \ + traits/reference.h \ + $(jabber_whiteboard_SOURCES) + +EXTRA_PROGRAMS = \ + inkview \ + libnr/testnr + +TESTS = \ + test-all$(EXEEXT) \ + attributes-test$(EXEEXT) \ + dir-util-test$(EXEEXT) \ + extract-uri-test$(EXEEXT) \ + mod360-test$(EXEEXT) \ + round-test$(EXEEXT) \ + sp-gradient-test$(EXEEXT) \ + sp-style-elem-test$(EXEEXT) \ + style-test$(EXEEXT) \ + display/bezier-utils-test$(EXEEXT) \ + helper/units-test$(EXEEXT) \ + libnr/in-svg-plane-test$(EXEEXT) \ + libnr/nr-matrix-test$(EXEEXT) \ + libnr/nr-point-fns-test$(EXEEXT) \ + libnr/nr-rotate-test$(EXEEXT) \ + libnr/nr-rotate-fns-test$(EXEEXT) \ + libnr/nr-scale-test$(EXEEXT) \ + libnr/nr-translate-test$(EXEEXT) \ + libnr/nr-types-test$(EXEEXT) \ + libnr/test-nr$(EXEEXT) \ + removeoverlap/remove_rectangle_overlap-test$(EXEEXT) \ + svg/test-svg$(EXEEXT) \ + util/list-container-test$(EXEEXT) \ + xml/test-xml$(EXEEXT) \ + xml/quote-test$(EXEEXT) \ + xml/repr-action-test$(EXEEXT) + +# streamtest is unfinished and can't handle the relocations done during +# "make distcheck". Not needed for the 0.41 release. +# io/streamtest$(EXEEXT) + +# automake adds $(EXEEXT) to check_PROGRAMS items but not to TESTS items: +# TESTS items can be scripts etc. + +check_PROGRAMS = \ + test-all \ + attributes-test \ + dir-util-test \ + extract-uri-test \ + mod360-test \ + round-test \ + sp-gradient-test \ + sp-style-elem-test \ + style-test \ + display/bezier-utils-test \ + helper/units-test \ + libnr/in-svg-plane-test \ + libnr/nr-matrix-test \ + libnr/nr-point-fns-test \ + libnr/nr-rotate-test \ + libnr/nr-rotate-fns-test \ + libnr/nr-scale-test \ + libnr/nr-translate-test \ + libnr/nr-types-test \ + libnr/test-nr \ + removeoverlap/remove_rectangle_overlap-test \ + svg/test-svg \ + util/list-container-test \ + xml/test-xml \ + xml/quote-test \ + xml/repr-action-test + +# io/streamtest + + +test-all.cpp: \ + $(libnr_test_nr_a_SOURCES) \ + $(svg_test_svg_a_SOURCES) \ + $(xml_test_xml_a_SOURCES) \ + $(libnr_test_nr_includes) \ + $(svg_test_svg_includes) \ + $(xml_test_xml_includes) + $(top_srcdir)/cxxtest/cxxtestgen.pl --error-printer -root -o test-all.cpp \ + $(libnr_test_nr_includes) \ + $(svg_test_svg_includes) \ + $(xml_test_xml_includes) + +test_all_SOURCES = \ + test-all.cpp + +test_all_LDADD = \ + $(libnr_test_nr_LDADD) \ + $(svg_test_svg_LDADD) \ + $(xml_test_xml_LDADD) + + +# ################################################ +# +# D I S T +# +# ################################################ + +dist-hook: + mkdir $(distdir)/pixmaps + cp $(srcdir)/pixmaps/*xpm $(distdir)/pixmaps + diff --git a/src/Makefile.mingw b/src/Makefile.mingw new file mode 100644 index 000000000..b3afb5cfe --- /dev/null +++ b/src/Makefile.mingw @@ -0,0 +1,123 @@ +########################################################################### +# $Id$ +########################################################################### +# Makefile for building with MinGW +########################################################################### + +include ../Makefile.mingw.common + +all: generated outputs + +################################### +# G E N E R A T E D F I L E S +################################### + +generated: helper/sp-marshal.h helper/sp-marshal.cpp inkscape_version.h + + + +helper/sp-marshal.h: helper/sp-marshal.h.mingw + $(CP) $(subst /,$(S), $<) $(subst /,$(S), $@) + +helper/sp-marshal.cpp: helper/sp-marshal.cpp.mingw + $(CP) $(subst /,$(S), $<) $(subst /,$(S), $@) + +inkscape_version.h: inkscape_version.h.mingw + $(CP) inkscape_version.h.mingw inkscape_version.h + + +################################### +# D E P E N D E N C I E S +################################### + +include ./make.ofiles +include ./make.dep + +INC += $(INCLUDEPATH) + +OBJ = $(OBJECTS) + + + +################################### +# O U T P U T S +################################### + +outputs: inkscape.exe inkview.exe + + +RES=inkres.o + +inkscape.exe: libinkscape.a main.o winmain.o $(RES) + $(CXX) --export-dynamic -o inkscape.exe main.o winmain.o $(RES) libinkscape.a $(LIBS) +# strip inkscape.exe + +# DLL version. we need to make this work +#inkscape.exe: inkscape.dll main.o winmain.o $(RES) +# $(CXX) -o inkscape.exe main.o winmain.o $(RES) inkscape.la $(LIBS) +# strip inkscape.exe + +inkview.exe: libinkscape.a inkview.o $(RES) + $(CXX) -o inkview.exe inkview.o $(RES) libinkscape.a $(LIBS) +# strip inkview.exe + +# DLL version. we need to make this work +# inkview.exe: inkscape.dll inkview.o $(RES) +# $(CXX) -o inkview.exe inkview.o $(RES) libinkscapedll.a $(LIBS) +# strip inkview.exe + +inkres.o: inkscape.rc + $(WINDRES) inkscape.rc $(RES) + +inkscape.dll: libinkscape.a inkscape.def + $(DLLWRAP) --output-lib=inkscape.la \ + --def=inkscape.def --driver-name=g++ \ + -o inkscape.dll libinkscape.a $(LIBS) + +inkscape.def: libinkscape.a + perl makedef.pl + +libinkscape.a: $(OBJ) + -$(RM) libinkscape.a + ar crv libinkscape.a $(OBJ) + $(RANLIB) libinkscape.a + +inkscape.la: inkscape.dll + + + +################################### +# P L U G I N S +################################### + +.o.dll: $< + $(DLLWRAP) --def=plugin.def --driver-name=g++ \ + -o $@ $< $(LIBS) + +PLUGS = extension/plugin/gimpgrad.dll + +plugins: $(PLUGS) + +extension/plugin/gimpgrad.dll: extension/plugin/gimpgrad.o inkscape.la + $(DLLWRAP) --def=plugin.def --driver-name=g++ \ + -dllname $@ $< inkscape.la $(LIBS) -lgc + strip $@ + + + + +################################### +# C L E A N U P +################################### + +clean: + $(foreach a, $(OBJ), $(shell $(RM) $(subst /,$(S), $(a)))) + -$(RM) main.o winmain.o inkview.o + -$(RM) *.a + -$(RM) *.la + -$(RM) inkscape.def + -$(RM) *.dll + -$(RM) extension$(S)plugin$(S)*.o + -$(RM) extension$(S)plugin$(S)*.dll + + diff --git a/src/Makefile.mingw.old b/src/Makefile.mingw.old new file mode 100644 index 000000000..1483746c8 --- /dev/null +++ b/src/Makefile.mingw.old @@ -0,0 +1,53 @@ +########################################################################### +# $Id$ +########################################################################### +# Makefile for building with MinGW +########################################################################### + +include ../Makefile.mingw.common + + + +#Check for 'generated' files +all: helper/sp-marshal.h helper/sp-marshal.cpp inkscape_version.h inkscape.exe + +helper/sp-marshal.h: helper/sp-marshal.h.mingw + $(CP) $(subst /,$(S), $<) $(subst /,$(S), $@) + +helper/sp-marshal.cpp: helper/sp-marshal.cpp.mingw + $(CP) $(subst /,$(S), $<) $(subst /,$(S), $@) + +inkscape_version.h: inkscape_version.h.mingw + $(CP) inkscape_version.h.mingw inkscape_version.h + +include ./make.ofiles +include ./make.dep + +INC += $(INCLUDEPATH) + +OBJ = $(OBJECTS) + +RES=inkres.o + +inkscape.exe: libinkscape.a main.o winmain.o $(RES) + $(CXX) -o inkscape.exe main.o winmain.o $(RES) libinkscape.a $(LIBS) +# strip inkscape.exe + +inkview.exe: libinkscape.a inkview.o $(RES) + $(CXX) -o inkview.exe inkview.o $(RES) libinkscape.a $(LIBS) + strip inkview.exe + +inkres.o: inkscape.rc + $(WINDRES) inkscape.rc $(RES) + +libinkscape.a: $(OBJ) + $(RM) libinkscape.a + ar crv libinkscape.a $(OBJ) + $(RANLIB) libinkscape.a + + +clean: + $(foreach a, $(OBJ), $(shell $(RM) $(subst /,$(S), $(a)))) + $(RM) *.a + + diff --git a/src/Makefile_insert b/src/Makefile_insert new file mode 100644 index 000000000..44439f164 --- /dev/null +++ b/src/Makefile_insert @@ -0,0 +1,339 @@ +## Makefile.am fragment, included by src/Makefile.am. + + +# ################################################ +# +# E X T R A +# +# ################################################ + +if PLATFORM_WIN32 +win32_sources = winmain.cpp +win32ldflags = -lcomdlg32 +endif + +if INKJAR +inkjar_dir = inkjar +inkjar_libs = inkjar/libinkjar.a +endif + + +# ################################################ +# +# I N K S C A P E +# +# ################################################ + +# libinkpre.a: any object that's sharable between inkscape & inkview, +# and isn't needed by object files in subdirectories (i.e. libinkpre.a +# comes before subdirectory libraries on the link line). +# +# Excludes winmain.cpp (a gui wrapper around main): I'm guessing that +# it needs to be explicitly listed as a source of each graphical +# binary: it isn't (to my knowledge) called by main (whether directly +# or indirectly), so I don't think that putting it in a library will +# suffice to get it linked in. Windows devel please confirm. -- pjrm. + +libinkpre_a_SOURCES = \ + algorithms/find-last-if.h \ + algorithms/longest-common-suffix.h \ + approx-equal.h remove-last.h \ + arc-context.cpp arc-context.h \ + attributes.cpp attributes.h \ + bad-uri-exception.h \ + brokenimage.xpm \ + color-rgba.h \ + conn-avoid-ref.cpp conn-avoid-ref.h \ + connector-context.cpp connector-context.h \ + context-fns.cpp context-fns.h \ + desktop-affine.cpp desktop-affine.h \ + desktop-events.cpp desktop-events.h \ + desktop-handles.cpp desktop-handles.h \ + desktop-style.cpp desktop-style.h \ + desktop.cpp desktop.h \ + document-undo.cpp \ + document.cpp document.h document-private.h \ + draw-anchor.cpp \ + draw-anchor.h \ + draw-context.cpp draw-context.h \ + dropper-context.cpp dropper-context.h \ + dyna-draw-context.cpp dyna-draw-context.h \ + enums.h \ + event-context.cpp event-context.h \ + extract-uri.cpp extract-uri.h \ + file.cpp file.h \ + fontsize-expansion.cpp fontsize-expansion.h \ + forward.h \ + geom.cpp geom.h \ + gnuc-attribute.h \ + gradient-context.cpp gradient-context.h \ + gradient-drag.cpp gradient-drag.h \ + help.cpp help.h \ + inkscape-stock.cpp inkscape-stock.h\ + inkscape.cpp inkscape.h inkscape-private.h \ + interface.cpp interface.h \ + isnan.h \ + knot-enums.h \ + knot-holder-entity.h \ + knot.cpp knot.h \ + knotholder.cpp knotholder.h \ + layer-fns.cpp layer-fns.h \ + macros.h \ + marker-status.cpp marker-status.h \ + media.cpp media.h \ + message-context.cpp message-context.h \ + message-stack.cpp message-stack.h \ + message.h \ + mod360.cpp mod360.h \ + modifier-fns.h \ + node-context.cpp node-context.h \ + nodepath.cpp nodepath.h \ + object-edit.cpp object-edit.h \ + object-hierarchy.cpp object-hierarchy.h \ + object-ui.cpp object-ui.h \ + path-chemistry.cpp path-chemistry.h \ + path-prefix.h \ + pen-context.cpp \ + pen-context.h \ + pencil-context.cpp \ + pencil-context.h \ + preferences.cpp preferences.h \ + preferences-skeleton.h \ + menus-skeleton.h \ + prefix.cpp \ + prefix.h \ + prefs-utils.cpp \ + prefs-utils.h \ + print.cpp print.h \ + rect-context.cpp rect-context.h \ + require-config.h \ + rubberband.cpp rubberband.h \ + satisfied-guide-cns.cpp satisfied-guide-cns.h \ + selcue.cpp selcue.h \ + select-context.cpp select-context.h \ + selection-chemistry.cpp selection-chemistry.h \ + selection-describer.cpp selection-describer.h \ + selection.cpp selection.h \ + seltrans-handles.cpp seltrans-handles.h \ + seltrans.cpp seltrans.h \ + shortcuts-default-xml.cpp \ + shortcuts.cpp shortcuts.h \ + slideshow.cpp slideshow.h \ + snap.cpp snap.h \ + snapped-point.cpp snapped-point.h \ + snapper.cpp snapper.h \ + line-snapper.cpp line-snapper.h \ + grid-snapper.cpp grid-snapper.h \ + guide-snapper.cpp guide-snapper.h \ + object-snapper.cpp object-snapper.h \ + sp-anchor.cpp sp-anchor.h \ + sp-clippath.cpp sp-clippath.h \ + sp-conn-end-pair.cpp sp-conn-end-pair.h \ + sp-conn-end.cpp sp-conn-end.h \ + sp-cursor.cpp sp-cursor.h \ + sp-defs.cpp sp-defs.h \ + sp-ellipse.cpp sp-ellipse.h \ + sp-flowdiv.h sp-flowdiv.cpp \ + sp-flowregion.h sp-flowregion.cpp \ + sp-flowtext.h sp-flowtext.cpp \ + sp-gradient-fns.h \ + sp-gradient-reference.cpp \ + sp-gradient-reference.h \ + sp-gradient-spread.h \ + sp-gradient-units.h \ + sp-gradient-vector.h \ + sp-gradient.cpp sp-gradient.h \ + sp-guide-attachment.h \ + sp-guide-constraint.h \ + sp-guide.cpp sp-guide.h \ + sp-image.cpp sp-image.h \ + sp-item-group.cpp sp-item-group.h \ + sp-item-notify-moveto.cpp sp-item-notify-moveto.h \ + sp-item-rm-unsatisfied-cns.cpp sp-item-rm-unsatisfied-cns.h \ + sp-item-transform.cpp sp-item-transform.h \ + sp-item-update-cns.cpp sp-item-update-cns.h \ + sp-item.cpp sp-item.h \ + sp-line.cpp sp-line.h \ + sp-linear-gradient-fns.h \ + sp-linear-gradient.h \ + sp-marker-loc.h \ + sp-marker.cpp sp-marker.h \ + sp-mask.cpp sp-mask.h \ + sp-metadata.cpp sp-metadata.h \ + sp-metric.h \ + sp-metrics.cpp sp-metrics.h \ + sp-namedview.cpp sp-namedview.h \ + sp-object-group.cpp sp-object-group.h \ + sp-object-repr.cpp sp-object-repr.h \ + sp-object.cpp sp-object.h \ + sp-offset.cpp sp-offset.h \ + sp-paint-server.cpp sp-paint-server.h \ + sp-path.cpp sp-path.h \ + sp-pattern.cpp sp-pattern.h \ + sp-polygon.cpp sp-polygon.h \ + sp-polyline.cpp sp-polyline.h \ + sp-radial-gradient-fns.h \ + sp-radial-gradient.h \ + sp-rect.cpp sp-rect.h \ + sp-root.cpp sp-root.h \ + sp-shape.cpp sp-shape.h \ + sp-spiral.cpp sp-spiral.h \ + sp-star.cpp sp-star.h \ + sp-stop-fns.h \ + sp-stop.h \ + sp-string.cpp sp-string.h \ + sp-symbol.cpp sp-symbol.h \ + sp-text.cpp sp-text.h \ + sp-textpath.h \ + sp-tspan.cpp sp-tspan.h \ + sp-use-reference.cpp sp-use-reference.h \ + sp-use.cpp sp-use.h \ + spiral-context.cpp spiral-context.h \ + splivarot.cpp splivarot.h \ + star-context.cpp star-context.h \ + streams-gzip.h streams-gzip.cpp \ + streams-handles.h streams-handles.cpp \ + streams-jar.h streams-jar.cpp \ + streams-zlib.h streams-zlib.cpp \ + style.cpp style.h \ + sp-style-elem.cpp sp-style-elem.h \ + svg-profile.h \ + svg-view.cpp svg-view.h \ + svg-view-widget.cpp svg-view-widget.h \ + text-chemistry.cpp text-chemistry.h \ + text-context.cpp text-context.h \ + text-editing.cpp text-editing.h \ + text-tag-attributes.h \ + tools-switch.cpp tools-switch.h\ + uri-references.cpp uri-references.h \ + verbs.cpp verbs.h \ + version.cpp version.h \ + zoom-context.cpp zoom-context.h + +# Force libinkpost.a to be rebuilt if we add files to libinkpost_a_SOURCES. +libinkpost_a_DEPENDENCIES = Makefile_insert + +# libinkpost.a: Any object file that needs to be near the end of the link line. +# gradient-chemistry.o is called by some things in display/. +libinkpost_a_SOURCES = \ + color.cpp color.h \ + decimal-round.h \ + dir-util.cpp dir-util.h \ + fill-or-stroke.h \ + fixes.cpp \ + gc-alloc.h \ + gc-anchored.h gc-anchored.cpp \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + gc.cpp \ + gradient-chemistry.cpp gradient-chemistry.h \ + memeq.h \ + round.h \ + streq.h \ + strneq.h \ + composite-undo-stack-observer.h \ + composite-undo-stack-observer.cpp \ + undo-stack-observer.h \ + unit-constants.h \ + uri.cpp uri.h + +inkscape_private_libs = \ + libinkpre.a \ + application/libinkapp.a \ + ui/dialog/libuidialog.a \ + dialogs/libspdialogs.a \ + dom/libdom.a \ + jabber_whiteboard/libjabber_whiteboard.a \ + trace/libtrace.a \ + svg/libspsvg.a \ + widgets/libspwidgets.a \ + display/libspdisplay.a \ + helper/libspchelp.a \ + libcroco/libcroco.a \ + libnrtype/libnrtype.a \ + libnr/libnr.a \ + libavoid/libavoid.a \ + livarot/libvarot.a \ + ui/view/libuiview.a \ + ui/libui.a \ + ui/widget/libuiwidget.a \ + removeoverlap/libremoveoverlap.a \ + extension/libextension.a \ + extension/implementation/libimplementation.a \ + extension/internal/libinternal.a \ + extension/script/libscript.a \ + xml/libspxml.a \ + util/libinkutil.a \ + io/libio.a \ + $(inkjar_libs) \ + libinkpost.a \ + debug/libinkdebug.a + +all_libs = \ + $(inkscape_private_libs) \ + $(INKSCAPE_LIBS) \ + $(GNOME_PRINT_LIBS) \ + $(GNOME_VFS_LIBS) \ + $(XFT_LIBS) \ + $(FREETYPE_LIBS) \ + $(kdeldadd) \ + $(win32ldflags) \ + $(PERL_LDFLAGS) \ + $(PYTHON_LIBS) \ + $(LIBLOUDMOUTH_LIBS) + +desktop.$(OBJEXT): helper/sp-marshal.h +document.$(OBJEXT): helper/sp-marshal.h inkscape_version.h +extension/internal/latex-pstricks.$(OBJEXT): inkscape_version.h +extension/internal/ps.$(OBJEXT): inkscape_version.h +inkscape.$(OBJEXT): helper/sp-marshal.h inkscape_version.h +knot.$(OBJEXT): helper/sp-marshal.h +main.$(OBJEXT): inkscape_version.h +selection.$(OBJEXT): helper/sp-marshal.h +sp-object.$(OBJEXT): helper/sp-marshal.h +sp-root.$(OBJEXT): inkscape_version.h +view.$(OBJEXT): helper/sp-marshal.h +help.$(OBJEXT): inkscape_version.h + + +# ################################################ +# +# B I N A R I E S +# +# ################################################ + + +inkscape_SOURCES = main.cpp $(win32_sources) +inkscape_LDADD = $(all_libs) +inkscape_LDFLAGS = --export-dynamic $(kdeldflags) + +inkview_SOURCES = inkview.cpp $(win32_sources) +inkview_LDADD = $(all_libs) + +attributes_test_SOURCES = attributes-test.cpp +attributes_test_LDADD = attributes.$(OBJEXT) -lglib-2.0 + +dir_util_test_SOURCES = dir-util-test.cpp utest/test-2ary-cases.h +dir_util_test_LDADD = dir-util.$(OBJEXT) -lglib-2.0 + +mod360_test_SOURCES = mod360-test.cpp +mod360_test_LDADD = libinkpre.a -lglib-2.0 + +extract_uri_test_SOURCES = extract-uri-test.cpp +extract_uri_test_LDADD = libinkpre.a -lglib-2.0 + +round_test_SOURCES = round-test.cpp +round_test_LDADD = libinkpost.a + +sp_gradient_test_SOURCES = sp-gradient-test.cpp +sp_gradient_test_LDADD = $(all_libs) + +sp_style_elem_test_SOURCES = sp-style-elem-test.cpp +sp_style_elem_test_LDADD = $(all_libs) + +style_test_SOURCES = style-test.cpp +style_test_LDADD = $(all_libs) + +inkscape_version.h: ../configure.ac + echo '#define INKSCAPE_VERSION "$(VERSION)"' > inkscape_version.h diff --git a/src/algorithms/.cvsignore b/src/algorithms/.cvsignore new file mode 100644 index 000000000..b4fcd8525 --- /dev/null +++ b/src/algorithms/.cvsignore @@ -0,0 +1,2 @@ +makefile + diff --git a/src/algorithms/Makefile_insert b/src/algorithms/Makefile_insert new file mode 100644 index 000000000..dff5b578d --- /dev/null +++ b/src/algorithms/Makefile_insert @@ -0,0 +1,5 @@ + +algorithms/all: + +algorithms/clean: + diff --git a/src/algorithms/find-if-before.h b/src/algorithms/find-if-before.h new file mode 100644 index 000000000..6a0f63be6 --- /dev/null +++ b/src/algorithms/find-if-before.h @@ -0,0 +1,51 @@ +/* + * Inkscape::Algorithms::find_if_before - finds the position before + * the first value that satisifes + * the predicate + * + * Authors: + * MenTaLguY + * + * Copyright (C) 2005 MenTaLguY + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SEEN_INKSCAPE_ALGORITHMS_FIND_IF_BEFORE_H +#define SEEN_INKSCAPE_ALGORITHMS_FIND_IF_BEFORE_H + +#include + +namespace Inkscape { + +namespace Algorithms { + +template +inline ForwardIterator find_if_before(ForwardIterator start, + ForwardIterator end, + UnaryPredicate pred) +{ + ForwardIterator before=end; + while ( start != end && !pred(*start) ) { + before = start; + ++start; + } + return ( start != end ) ? before : end; +} + +} + +} + +#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 : diff --git a/src/algorithms/find-last-if.h b/src/algorithms/find-last-if.h new file mode 100644 index 000000000..1ffd63b9c --- /dev/null +++ b/src/algorithms/find-last-if.h @@ -0,0 +1,51 @@ +/* + * Inkscape::Algorithms::find_last_if + * + * Authors: + * MenTaLguY + * + * Copyright (C) 2004 MenTaLguY + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SEEN_INKSCAPE_ALGORITHMS_FIND_LAST_IF_H +#define SEEN_INKSCAPE_ALGORITHMS_FIND_LAST_IF_H + +#include + +namespace Inkscape { + +namespace Algorithms { + +template +inline ForwardIterator find_last_if(ForwardIterator start, ForwardIterator end, + UnaryPredicate pred) +{ + ForwardIterator last_found(end); + while ( start != end ) { + start = std::find_if(start, end, pred); + if ( start != end ) { + last_found = start; + ++start; + } + } + return last_found; +} + +} + +} + +#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 : diff --git a/src/algorithms/longest-common-suffix.h b/src/algorithms/longest-common-suffix.h new file mode 100644 index 000000000..04d2b179d --- /dev/null +++ b/src/algorithms/longest-common-suffix.h @@ -0,0 +1,114 @@ +/* + * Inkscape::Algorithms::longest_common_suffix + * + * Authors: + * MenTaLguY + * + * Copyright (C) 2004 MenTaLguY + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SEEN_INKSCAPE_ALGORITHMS_LONGEST_COMMON_SUFFIX_H +#define SEEN_INKSCAPE_ALGORITHMS_LONGEST_COMMON_SUFFIX_H + +#include +#include +#include "util/list.h" + +namespace Inkscape { + +namespace Algorithms { + +/** + * Time costs: + * + * The case of sharing a common successor is handled in O(1) time. + * + * If \a a is the longest common suffix, then runs in O(len(rest of b)) time. + * + * Otherwise, runs in O(len(a) + len(b)) time. + */ + +template +ForwardIterator longest_common_suffix(ForwardIterator a, ForwardIterator b, + ForwardIterator end) +{ + typedef typename std::iterator_traits::value_type value_type; + return longest_common_suffix(a, b, end, std::equal_to()); +} + +template +ForwardIterator longest_common_suffix(ForwardIterator a, ForwardIterator b, + ForwardIterator end, BinaryPredicate pred) +{ + if ( a == end || b == end ) { + return end; + } + + /* Handle in O(1) time the common cases of identical lists or tails. */ + { + /* identical lists? */ + if ( a == b ) { + return a; + } + + /* identical tails? */ + ForwardIterator tail_a(a); + ForwardIterator tail_b(b); + if ( ++tail_a == ++tail_b ) { + return tail_a; + } + } + + /* Build parallel lists of suffixes, ordered by increasing length. */ + + using Inkscape::Util::List; + using Inkscape::Util::cons; + ForwardIterator lists[2] = { a, b }; + List suffixes[2]; + + for ( int i=0 ; i < 2 ; i++ ) { + for ( ForwardIterator iter(lists[i]) ; iter != end ; ++iter ) { + if ( iter == lists[1-i] ) { + // the other list is a suffix of this one + return lists[1-i]; + } + + suffixes[i] = cons(iter, suffixes[i]); + } + } + + /* Iterate in parallel through the lists of suffix lists from shortest to + * longest, stopping before the first pair of suffixes that differs + */ + + ForwardIterator longest_common(end); + + while ( suffixes[0] && suffixes[1] && + pred(**suffixes[0], **suffixes[1]) ) + { + longest_common = *suffixes[0]; + ++suffixes[0]; + ++suffixes[1]; + } + + return longest_common; +} + +} + +} + +#endif /* !SEEN_INKSCAPE_ALGORITHMS_LONGEST_COMMON_SUFFIX_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 : diff --git a/src/algorithms/makefile.in b/src/algorithms/makefile.in new file mode 100644 index 000000000..293b5e8ca --- /dev/null +++ b/src/algorithms/makefile.in @@ -0,0 +1,17 @@ +# Convenience stub makefile to call the real Makefile. + +@SET_MAKE@ + +# Explicit so that it's the default rule. +all: + cd .. && $(MAKE) algorithms/all + +clean %.a %.o: + cd .. && $(MAKE) algorithms/$@ + +.PHONY: all clean + +OBJEXT = @OBJEXT@ + +.SUFFIXES: +.SUFFIXES: .a .$(OBJEXT) diff --git a/src/application/.cvsignore b/src/application/.cvsignore new file mode 100644 index 000000000..38efca7bc --- /dev/null +++ b/src/application/.cvsignore @@ -0,0 +1,3 @@ +.deps +.dirstamp +makefile diff --git a/src/application/Makefile_insert b/src/application/Makefile_insert new file mode 100644 index 000000000..9a5306ec7 --- /dev/null +++ b/src/application/Makefile_insert @@ -0,0 +1,14 @@ +## Makefile.am fragment sourced by src/Makefile.am. + +application/all: application/libinkapp.a + +application/clean: + rm -f application/libinkapp.a $(application_libinkapp_a_OBJECTS) + +application_libinkapp_a_SOURCES = \ + application/editor.cpp \ + application/editor.h \ + application/application.cpp \ + application/application.h \ + application/app-prototype.cpp \ + application/app-prototype.h diff --git a/src/application/app-prototype.cpp b/src/application/app-prototype.cpp new file mode 100644 index 000000000..b94f5ea05 --- /dev/null +++ b/src/application/app-prototype.cpp @@ -0,0 +1,43 @@ +/** + * \brief Base class for different application modes + * + * Author: + * Bryce W. Harrington + * + * Copyright (C) 2005 Bryce Harrington + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ + +#include "app-prototype.h" + +namespace Inkscape { +namespace NSApplication { + +AppPrototype::AppPrototype() +{ +} + +AppPrototype::AppPrototype(int argc, const char **argv) +{ +} + +AppPrototype::~AppPrototype() +{ +} + + +} // namespace NSApplication +} // namespace Inkscape + + +/* + 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 : diff --git a/src/application/app-prototype.h b/src/application/app-prototype.h new file mode 100644 index 000000000..a7c8ad86f --- /dev/null +++ b/src/application/app-prototype.h @@ -0,0 +1,53 @@ +/** + * \brief Base class for different application modes + * + * Author: + * Bryce W. Harrington + * + * Copyright (C) 2005 Bryce Harrington + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_APPLICATION_APP_PROTOTYPE_H +#define INKSCAPE_APPLICATION_APP_PROTOTYPE_H + +namespace Gtk { +class Window; +} + + +namespace Inkscape { +namespace NSApplication { + +class AppPrototype +{ +public: + AppPrototype(); + AppPrototype(int argc, const char **argv); + virtual ~AppPrototype(); + + virtual void* getWindow() = 0; + +protected: + AppPrototype(AppPrototype const &); + AppPrototype& operator=(AppPrototype const &); + +}; + +} // namespace NSApplication +} // namespace Inkscape + + +#endif /* !INKSCAPE_APPLICATION_APP_PROTOTYPE_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:encoding=utf-8:textwidth=99 : diff --git a/src/application/application.cpp b/src/application/application.cpp new file mode 100644 index 000000000..4b1ea03df --- /dev/null +++ b/src/application/application.cpp @@ -0,0 +1,163 @@ +/** \file + * \brief The top level class for managing the application. + * + * Authors: + * Bryce W. Harrington + * Ralf Stephan + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +#include "preferences.h" +#include "application.h" +#include "editor.h" + +int sp_main_gui(int argc, char const **argv); +int sp_main_console(int argc, char const **argv); + +static Gtk::Main *_gtk_main; +static bool _use_gui, _new_gui; + +namespace Inkscape { +namespace NSApplication { + +Application::Application(int argc, char **argv, bool use_gui, bool new_gui) + : _argc(argc), + _argv(NULL), + _app_impl(NULL), + _path_home(NULL) +{ + _use_gui = use_gui; + _new_gui = new_gui; + + if (argv != NULL) { + _argv = argv; // TODO: Is this correct? + } + + Inkscape::Preferences::loadSkeleton(); + if (new_gui) { + _gtk_main = new Gtk::Main(argc, argv, true); + + // TODO: Determine class by arguments + g_warning("Creating new Editor"); + _app_impl = (AppPrototype*) Editor::create(_argc, _argv); + + } else if (use_gui) { + // No op - we'll use the old interface + } else { + _app_impl = NULL; // = Cmdline(_argc, _argv); + } + + /// \todo Install segv handler here? + +// Inkscape::Extension::init(); +} + +Application::~Application() +{ + g_free(_path_home); +} + + +/** Returns the current home directory location */ +gchar const* +Application::homedir() const +{ + if ( !_path_home ) { + _path_home = g_strdup(g_get_home_dir()); + gchar* utf8Path = g_filename_to_utf8( _path_home, -1, NULL, NULL, NULL ); + if ( utf8Path ) { + _path_home = utf8Path; + if ( !g_utf8_validate(_path_home, -1, NULL) ) { + g_warning( "Home directory is non-UTF-8" ); + } + } + } + if ( !_path_home && _argv != NULL) { + gchar * path = g_path_get_dirname(_argv[0]); + gchar * utf8Path = g_filename_to_utf8( path, -1, NULL, NULL, NULL ); + g_free(path); + if (utf8Path) { + _path_home = utf8Path; + if ( !g_utf8_validate(_path_home, -1, NULL) ) { + g_warning( "Application run directory is non-UTF-8" ); + } + } + } + return _path_home; +} + +gint +Application::run() +{ + gint result = 0; + + /* Note: This if loop should be replaced by calls to the + * various subclasses of I::A::AppPrototype. + */ + if (_gtk_main != NULL) { + g_assert(_app_impl != NULL); + + Inkscape::Preferences::load(); + g_warning("Running main window"); + Gtk::Window *win = static_cast(_app_impl->getWindow()); + g_assert(win != NULL); + _gtk_main->run(*win); + result = 0; + + } else if (_use_gui) { + result = sp_main_gui(_argc, (const char**)_argv); + + } else { + result = sp_main_console(_argc, (const char**)_argv); + } + + return result; +} + +void +Application::exit() +{ + Inkscape::Preferences::save(); + + if (_gtk_main != NULL) { + _gtk_main->quit(); + } + +} + +bool +Application::getUseGui() +{ + return _use_gui; +} + +bool +Application::getNewGui() +{ + return _new_gui; +} + + +} // namespace NSApplication +} // namespace Inkscape + + +/* + 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 : diff --git a/src/application/application.h b/src/application/application.h new file mode 100644 index 000000000..212add9e8 --- /dev/null +++ b/src/application/application.h @@ -0,0 +1,65 @@ +/** \file + * \brief The top level class for managing the application. + * + * Authors: + * Bryce W. Harrington + * Ralf Stephan + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_APPLICATION_APPLICATION_H +#define INKSCAPE_APPLICATION_APPLICATION_H + +#include + +namespace Gtk { +class Main; +} + +namespace Inkscape { +namespace NSApplication { +class AppPrototype; + +class Application +{ +public: + Application(int argc, char **argv, bool use_gui=true, bool new_gui=false); + virtual ~Application(); + + gchar const *homedir() const; + + gint run(); + + static bool getUseGui(); + static bool getNewGui(); + static void exit(); + +protected: + Application(Application const &); + Application& operator=(Application const &); + + gint _argc; + char **_argv; + AppPrototype *_app_impl; + + mutable gchar *_path_home; +}; + +} // namespace NSApplication +} // namespace Inkscape + +#endif /* !INKSCAPE_APPLICATION_APPLICATION_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:encoding=utf-8:textwidth=99 : diff --git a/src/application/editor.cpp b/src/application/editor.cpp new file mode 100644 index 000000000..6c3dbf896 --- /dev/null +++ b/src/application/editor.cpp @@ -0,0 +1,416 @@ +/** \file + * \brief Editor Implementation class declaration for Inkscape. This + * singleton class implements much of the functionality of the former + * 'inkscape' object and its services and signals. + * + * Authors: + * Bryce W. Harrington + * Derek P. Moore + * Ralf Stephan + * + * Copyright (C) 2004-2005 Authors + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* + TODO: Replace SPDocument with the new Inkscape::Document + TODO: Change 'desktop's to 'view*'s + TODO: Add derivation from Inkscape::Application::RunMode +*/ + + +#include "path-prefix.h" +#include "io/sys.h" +#include "sp-object-repr.h" +#include +#include "document.h" +#include "sp-namedview.h" +#include "event-context.h" +#include "sp-guide.h" +#include "selection.h" +#include "editor.h" +#include "application/application.h" +#include "preferences.h" +#include "ui/view/edit-widget.h" + +namespace Inkscape { +namespace NSApplication { + +static Editor *_instance = 0; +static void *_window; + +Editor* +Editor::create (gint argc, char **argv) +{ + if (_instance == 0) + { + _instance = new Editor (argc, argv); + _instance->init(); + } + return _instance; +} + +Editor::Editor (gint argc, char **argv) +: _documents (0), + _desktops (0), + _argv0 (argv[0]), + _dialogs_toggle (true) + +{ + sp_object_type_register ("sodipodi:namedview", SP_TYPE_NAMEDVIEW); + sp_object_type_register ("sodipodi:guide", SP_TYPE_GUIDE); + + Inkscape::Preferences::load(); +} + +bool +Editor::init() +{ + // Load non-local template until we have everything right + // This code formerly lived in file.cpp + // + gchar const *tmpl = g_build_filename ((INKSCAPE_TEMPLATESDIR), "default.svg", NULL); + bool have_default = Inkscape::IO::file_test (tmpl, G_FILE_TEST_IS_REGULAR); + SPDocument *doc = sp_document_new (have_default? tmpl:0, true, true); + g_return_val_if_fail (doc != 0, false); + Inkscape::UI::View::EditWidget *ew = new Inkscape::UI::View::EditWidget (doc); + sp_document_unref (doc); + _window = ew->getWindow(); + return ew != 0; +} + +Editor::~Editor() +{ +} + +/// Returns the Window representation of this application object +void* +Editor::getWindow() +{ + return _window; +} + +/// Returns the active document +SPDocument* +Editor::getActiveDocument() +{ + if (getActiveDesktop()) { + return SP_DT_DOCUMENT (getActiveDesktop()); + } + + return NULL; +} + +void +Editor::addDocument (SPDocument *doc) +{ + g_assert (!g_slist_find (_instance->_documents, doc)); + _instance->_documents = g_slist_append (_instance->_documents, doc); +} + +void +Editor::removeDocument (SPDocument *doc) +{ + g_assert (g_slist_find (_instance->_documents, doc)); + _instance->_documents = g_slist_remove (_instance->_documents, doc); +} + +SPDesktop* +Editor::createDesktop (SPDocument* doc) +{ + g_assert (doc != 0); + (new Inkscape::UI::View::EditWidget (doc))->present(); + sp_document_unref (doc); + SPDesktop *dt = getActiveDesktop(); + reactivateDesktop (dt); + return dt; +} + +/// Returns the currently active desktop +SPDesktop* +Editor::getActiveDesktop() +{ + if (_instance->_desktops == NULL) { + return NULL; + } + + return (SPDesktop *) _instance->_desktops->data; +} + +/// Add desktop to list of desktops +void +Editor::addDesktop (SPDesktop *dt) +{ + g_return_if_fail (dt != 0); + g_assert (!g_slist_find (_instance->_desktops, dt)); + + _instance->_desktops = g_slist_append (_instance->_desktops, dt); + + if (isDesktopActive (dt)) { + _instance->_desktop_activated_signal.emit (dt); + _instance->_event_context_set_signal.emit (SP_DT_EVENTCONTEXT (dt)); + _instance->_selection_set_signal.emit (SP_DT_SELECTION (dt)); + _instance->_selection_changed_signal.emit (SP_DT_SELECTION (dt)); + } +} + +/// Remove desktop from list of desktops +void +Editor::removeDesktop (SPDesktop *dt) +{ + g_return_if_fail (dt != 0); + g_assert (g_slist_find (_instance->_desktops, dt)); + + if (dt == _instance->_desktops->data) { // is it the active desktop? + _instance->_desktop_deactivated_signal.emit (dt); + if (_instance->_desktops->next != 0) { + SPDesktop * new_desktop = (SPDesktop *) _instance->_desktops->next->data; + _instance->_desktops = g_slist_remove (_instance->_desktops, new_desktop); + _instance->_desktops = g_slist_prepend (_instance->_desktops, new_desktop); + _instance->_desktop_activated_signal.emit (new_desktop); + _instance->_event_context_set_signal.emit (SP_DT_EVENTCONTEXT (new_desktop)); + _instance->_selection_set_signal.emit (SP_DT_SELECTION (new_desktop)); + _instance->_selection_changed_signal.emit (SP_DT_SELECTION (new_desktop)); + } else { + _instance->_event_context_set_signal.emit (0); + if (SP_DT_SELECTION(dt)) + SP_DT_SELECTION(dt)->clear(); + } + } + + _instance->_desktops = g_slist_remove (_instance->_desktops, dt); + + // if this was the last desktop, shut down the program + if (_instance->_desktops == NULL) { + _instance->_shutdown_signal.emit(); + Inkscape::NSApplication::Application::exit(); + } +} + +void +Editor::activateDesktop (SPDesktop* dt) +{ + g_assert (dt != 0); + if (isDesktopActive (dt)) + return; + + g_assert (g_slist_find (_instance->_desktops, dt)); + SPDesktop *curr = (SPDesktop*)_instance->_desktops->data; + _instance->_desktop_deactivated_signal.emit (curr); + + _instance->_desktops = g_slist_remove (_instance->_desktops, dt); + _instance->_desktops = g_slist_prepend (_instance->_desktops, dt); + + _instance->_desktop_activated_signal.emit (dt); + _instance->_event_context_set_signal.emit (SP_DT_EVENTCONTEXT(dt)); + _instance->_selection_set_signal.emit (SP_DT_SELECTION(dt)); + _instance->_selection_changed_signal.emit (SP_DT_SELECTION(dt)); +} + +void +Editor::reactivateDesktop (SPDesktop* dt) +{ + g_assert (dt != 0); + if (isDesktopActive(dt)) + _instance->_desktop_activated_signal.emit (dt); +} + +bool +Editor::isDuplicatedView (SPDesktop* dt) +{ + SPDocument const* document = dt->doc(); + if (!document) { + return false; + } + for ( GSList *iter = _instance->_desktops ; iter ; iter = iter->next ) { + SPDesktop *other_desktop=(SPDesktop *)iter->data; + SPDocument *other_document=other_desktop->doc(); + if ( other_document == document && other_desktop != dt ) { + return true; + } + } + return false; +} + + /// Returns the event context +//SPEventContext* +//Editor::getEventContext() +//{ +// if (getActiveDesktop()) { +// return SP_DT_EVENTCONTEXT (getActiveDesktop()); +// } +// +// return NULL; +//} + + +void +Editor::selectionModified (Inkscape::Selection* sel, guint flags) +{ + g_return_if_fail (sel != NULL); + if (isDesktopActive (sel->desktop())) + _instance->_selection_modified_signal.emit (sel, flags); +} + +void +Editor::selectionChanged (Inkscape::Selection* sel) +{ + g_return_if_fail (sel != NULL); + if (isDesktopActive (sel->desktop())) + _instance->_selection_changed_signal.emit (sel); +} + +void +Editor::subSelectionChanged (SPDesktop* dt) +{ + g_return_if_fail (dt != NULL); + if (isDesktopActive (dt)) + _instance->_subselection_changed_signal.emit (dt); +} + +void +Editor::selectionSet (Inkscape::Selection* sel) +{ + g_return_if_fail (sel != NULL); + if (isDesktopActive (sel->desktop())) { + _instance->_selection_set_signal.emit (sel); + _instance->_selection_changed_signal.emit (sel); + } +} + +void +Editor::eventContextSet (SPEventContext* ec) +{ + g_return_if_fail (ec != NULL); + g_return_if_fail (SP_IS_EVENT_CONTEXT (ec)); + if (isDesktopActive (ec->desktop)) + _instance->_event_context_set_signal.emit (ec); +} + +void +Editor::hideDialogs() +{ + _instance->_dialogs_hidden_signal.emit(); + _instance->_dialogs_toggle = false; +} + +void +Editor::unhideDialogs() +{ + _instance->_dialogs_unhidden_signal.emit(); + _instance->_dialogs_toggle = true; +} + +void +Editor::toggleDialogs() +{ + if (_dialogs_toggle) { + hideDialogs(); + } else { + unhideDialogs(); + } +} + +void +Editor::refreshDisplay() +{ + // TODO +} + +void +Editor::exit() +{ + //emit shutdown signal so that dialogs could remember layout + _shutdown_signal.emit(); + Inkscape::Preferences::save(); +} + +//================================================================== + +sigc::connection +Editor::connectSelectionModified +(const sigc::slot &slot) +{ + return _instance->_selection_modified_signal.connect (slot); +} + +sigc::connection +Editor::connectSelectionChanged +(const sigc::slot &slot) +{ + return _instance->_selection_changed_signal.connect (slot); +} + +sigc::connection +Editor::connectSubselectionChanged (const sigc::slot &slot) +{ + return _instance->_subselection_changed_signal.connect (slot); +} + +sigc::connection +Editor::connectSelectionSet (const sigc::slot &slot) +{ + return _instance->_selection_set_signal.connect (slot); +} + +sigc::connection +Editor::connectEventContextSet (const sigc::slot &slot) +{ + return _instance->_event_context_set_signal.connect (slot); +} + +sigc::connection +Editor::connectDesktopActivated (const sigc::slot &slot) +{ + return _instance->_desktop_activated_signal.connect (slot); +} + +sigc::connection +Editor::connectDesktopDeactivated (const sigc::slot &slot) +{ + return _instance->_desktop_deactivated_signal.connect (slot); +} + +sigc::connection +Editor::connectShutdown (const sigc::slot &slot) +{ + return _instance->_shutdown_signal.connect (slot); +} + +sigc::connection +Editor::connectDialogsHidden (const sigc::slot &slot) +{ + return _instance->_dialogs_hidden_signal.connect (slot); +} + +sigc::connection +Editor::connectDialogsUnhidden (const sigc::slot &slot) +{ + return _instance->_dialogs_unhidden_signal.connect (slot); +} + +sigc::connection +Editor::connectExternalChange (const sigc::slot &slot) +{ + return _instance->_external_change_signal.connect (slot); +} + + +} // namespace NSApplication +} // namespace Inkscape + +/* + 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 : diff --git a/src/application/editor.h b/src/application/editor.h new file mode 100644 index 000000000..9d2ce493c --- /dev/null +++ b/src/application/editor.h @@ -0,0 +1,139 @@ +/** \file + * \brief Class to manage an application used for editing SVG documents + * using GUI views + * + * \note This class is a Singleton + * + * Authors: + * Bryce W. Harrington + * Ralf Stephan + * + * Copyright (C) 2004 Bryce Harrington + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_APPLICATION_EDITOR_H +#define INKSCAPE_APPLICATION_EDITOR_H + +#include +#include +#include +#include "app-prototype.h" + +class SPDesktop; +class SPDocument; +class SPEventContext; + +namespace Inkscape { + class Selection; + namespace XML { + class Document; + } + namespace UI { + namespace View { + class Edit; + } + } + namespace NSApplication { + +class Editor : public AppPrototype +{ +public: + static Editor *create (int argc, char **argv); + virtual ~Editor(); + + void* getWindow(); + + void toggleDialogs(); + void nextDesktop(); + void prevDesktop(); + + void refreshDisplay(); + void exit(); + + bool lastViewOfDocument(SPDocument* doc, SPDesktop* view) const; + + bool addView(SPDesktop* view); + bool deleteView(SPDesktop* view); + + static Inkscape::XML::Document *getPreferences(); + static SPDesktop* getActiveDesktop(); + static bool isDesktopActive (SPDesktop* dt) { return getActiveDesktop()==dt; } + static SPDesktop* createDesktop (SPDocument* doc); + static void addDesktop (SPDesktop* dt); + static void removeDesktop (SPDesktop* dt); + static void activateDesktop (SPDesktop* dt); + static void reactivateDesktop (SPDesktop* dt); + static bool isDuplicatedView (SPDesktop* dt); + + static SPDocument* getActiveDocument(); + static void addDocument (SPDocument* doc); + static void removeDocument (SPDocument* doc); + + static void selectionModified (Inkscape::Selection*, guint); + static void selectionChanged (Inkscape::Selection*); + static void subSelectionChanged (SPDesktop*); + static void selectionSet (Inkscape::Selection*); + static void eventContextSet (SPEventContext*); + static void hideDialogs(); + static void unhideDialogs(); + + static sigc::connection connectSelectionModified (const sigc::slot &slot); + static sigc::connection connectSelectionChanged (const sigc::slot &slot); + static sigc::connection connectSubselectionChanged (const sigc::slot &slot); + static sigc::connection connectSelectionSet (const sigc::slot &slot); + static sigc::connection connectEventContextSet (const sigc::slot &slot); + static sigc::connection connectDesktopActivated (const sigc::slot &slot); + static sigc::connection connectDesktopDeactivated (const sigc::slot &slot); + static sigc::connection connectShutdown (const sigc::slot &slot); + static sigc::connection connectDialogsHidden (const sigc::slot &slot); + static sigc::connection connectDialogsUnhidden (const sigc::slot &slot); + static sigc::connection connectExternalChange (const sigc::slot &slot); + + +protected: + Editor(Editor const &); + Editor& operator=(Editor const &); + + GSList *_documents; + GSList *_desktops; + gchar *_argv0; + + bool _dialogs_toggle; + + sigc::signal _selection_modified_signal; + sigc::signal _selection_changed_signal; + sigc::signal _subselection_changed_signal; + sigc::signal _selection_set_signal; + sigc::signal _event_context_set_signal; + sigc::signal _desktop_activated_signal; + sigc::signal _desktop_deactivated_signal; + sigc::signal _shutdown_signal; + sigc::signal _dialogs_hidden_signal; + sigc::signal _dialogs_unhidden_signal; + sigc::signal _external_change_signal; + +private: + Editor(int argc, char **argv); + bool init(); +}; + +#define ACTIVE_DESKTOP Inkscape::NSApplication::Editor::getActiveDesktop() + +} // namespace NSApplication +} // namespace Inkscape + + +#endif /* !INKSCAPE_APPLICATION_EDITOR_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:encoding=utf-8:textwidth=99 : diff --git a/src/application/makefile.in b/src/application/makefile.in new file mode 100644 index 000000000..d35d0b1f3 --- /dev/null +++ b/src/application/makefile.in @@ -0,0 +1,17 @@ +# Convenience stub makefile to call the real Makefile. + +@SET_MAKE@ + +# Explicit so that it's the default rule. +all: + cd .. && $(MAKE) application/all + +clean %.a %.o: + cd .. && $(MAKE) application/$@ + +.PHONY: all clean + +OBJEXT = @OBJEXT@ + +.SUFFIXES: +.SUFFIXES: .a .$(OBJEXT) diff --git a/src/approx-equal.h b/src/approx-equal.h new file mode 100644 index 000000000..3f5ebf109 --- /dev/null +++ b/src/approx-equal.h @@ -0,0 +1,25 @@ +#ifndef __APROX_EQUAL_H__ +#define __APROX_EQUAL_H__ + +#include + +inline bool approx_equal(double const a, double const b) +{ + return ( (a == b) + || ( fabs( a - b ) < 1e-2 ) + || ( fabs( a / b - 1.0 ) < 1e-2 ) ); +} + + +#endif /* !__APROX_EQUAL_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:encoding=utf-8:textwidth=99 : diff --git a/src/arc-context.cpp b/src/arc-context.cpp new file mode 100644 index 000000000..48e153638 --- /dev/null +++ b/src/arc-context.cpp @@ -0,0 +1,447 @@ +#define __SP_ARC_CONTEXT_C__ + +/** \file Ellipse drawing context. */ + +/* + * Authors: + * Mitsuru Oka + * Lauris Kaplinski + * bulia byak + * + * Copyright (C) 2000-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2002 Mitsuru Oka + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include +#endif +#include + +#include "macros.h" +#include +#include "display/sp-canvas.h" +#include "sp-ellipse.h" +#include "document.h" +#include "sp-namedview.h" +#include "selection.h" +#include "desktop-handles.h" +#include "snap.h" +#include "pixmaps/cursor-ellipse.xpm" +#include "sp-metrics.h" +#include "xml/repr.h" +#include "xml/node-event-vector.h" +#include "object-edit.h" +#include "prefs-utils.h" +#include "message-context.h" +#include "desktop.h" +#include "desktop-style.h" +#include "context-fns.h" + +#include "arc-context.h" + +static void sp_arc_context_class_init(SPArcContextClass *klass); +static void sp_arc_context_init(SPArcContext *arc_context); +static void sp_arc_context_dispose(GObject *object); + +static void sp_arc_context_setup(SPEventContext *ec); +static gint sp_arc_context_root_handler(SPEventContext *event_context, GdkEvent *event); +static gint sp_arc_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event); + +static void sp_arc_drag(SPArcContext *ec, NR::Point pt, guint state); +static void sp_arc_finish(SPArcContext *ec); + +static SPEventContextClass *parent_class; + +GtkType sp_arc_context_get_type() +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPArcContextClass), + NULL, NULL, + (GClassInitFunc) sp_arc_context_class_init, + NULL, NULL, + sizeof(SPArcContext), + 4, + (GInstanceInitFunc) sp_arc_context_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPArcContext", &info, (GTypeFlags) 0); + } + return type; +} + +static void sp_arc_context_class_init(SPArcContextClass *klass) +{ + + GObjectClass *object_class = (GObjectClass *) klass; + SPEventContextClass *event_context_class = (SPEventContextClass *) klass; + + parent_class = (SPEventContextClass*) g_type_class_peek_parent(klass); + + object_class->dispose = sp_arc_context_dispose; + + event_context_class->setup = sp_arc_context_setup; + event_context_class->root_handler = sp_arc_context_root_handler; + event_context_class->item_handler = sp_arc_context_item_handler; +} + +static void sp_arc_context_init(SPArcContext *arc_context) +{ + SPEventContext *event_context = SP_EVENT_CONTEXT(arc_context); + + event_context->cursor_shape = cursor_ellipse_xpm; + event_context->hot_x = 4; + event_context->hot_y = 4; + event_context->xp = 0; + event_context->yp = 0; + event_context->tolerance = 0; + event_context->within_tolerance = false; + event_context->item_to_select = NULL; + + event_context->shape_repr = NULL; + event_context->shape_knot_holder = NULL; + + arc_context->item = NULL; + + new (&arc_context->sel_changed_connection) sigc::connection(); +} + +static void sp_arc_context_dispose(GObject *object) +{ + SPEventContext *ec = SP_EVENT_CONTEXT(object); + SPArcContext *ac = SP_ARC_CONTEXT(object); + + ec->enableGrDrag(false); + + ac->sel_changed_connection.disconnect(); + ac->sel_changed_connection.~connection(); + + if (ec->shape_knot_holder) { + sp_knot_holder_destroy(ec->shape_knot_holder); + ec->shape_knot_holder = NULL; + } + + if (ec->shape_repr) { // remove old listener + sp_repr_remove_listener_by_data(ec->shape_repr, ec); + Inkscape::GC::release(ec->shape_repr); + ec->shape_repr = 0; + } + + /* fixme: This is necessary because we do not grab */ + if (ac->item) { + sp_arc_finish(ac); + } + + delete ac->_message_context; + + G_OBJECT_CLASS(parent_class)->dispose(object); +} + +static Inkscape::XML::NodeEventVector ec_shape_repr_events = { + NULL, /* child_added */ + NULL, /* child_removed */ + ec_shape_event_attr_changed, + NULL, /* content_changed */ + NULL /* order_changed */ +}; + +/** +\brief Callback that processes the "changed" signal on the selection; +destroys old and creates new knotholder. +*/ +void sp_arc_context_selection_changed(Inkscape::Selection * selection, gpointer data) +{ + SPArcContext *ac = SP_ARC_CONTEXT(data); + SPEventContext *ec = SP_EVENT_CONTEXT(ac); + + if (ec->shape_knot_holder) { // desktroy knotholder + sp_knot_holder_destroy(ec->shape_knot_holder); + ec->shape_knot_holder = NULL; + } + + if (ec->shape_repr) { // remove old listener + sp_repr_remove_listener_by_data(ec->shape_repr, ec); + Inkscape::GC::release(ec->shape_repr); + ec->shape_repr = 0; + } + + SPItem *item = selection ? selection->singleItem() : NULL; + if (item) { + ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop); + Inkscape::XML::Node *shape_repr = SP_OBJECT_REPR(item); + if (shape_repr) { + ec->shape_repr = shape_repr; + Inkscape::GC::anchor(shape_repr); + sp_repr_add_listener(shape_repr, &ec_shape_repr_events, ec); + sp_repr_synthesize_events(shape_repr, &ec_shape_repr_events, ec); + } + } +} + +static void sp_arc_context_setup(SPEventContext *ec) +{ + SPArcContext *ac = SP_ARC_CONTEXT(ec); + Inkscape::Selection *selection = SP_DT_SELECTION(ec->desktop); + + if (((SPEventContextClass *) parent_class)->setup) { + ((SPEventContextClass *) parent_class)->setup(ec); + } + + SPItem *item = selection->singleItem(); + + if (item) { + ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop); + Inkscape::XML::Node *shape_repr = SP_OBJECT_REPR(item); + if (shape_repr) { + ec->shape_repr = shape_repr; + Inkscape::GC::anchor(shape_repr); + sp_repr_add_listener(shape_repr, &ec_shape_repr_events, ec); + sp_repr_synthesize_events(shape_repr, &ec_shape_repr_events, ec); + } + } + + ac->sel_changed_connection.disconnect(); + ac->sel_changed_connection = selection->connectChanged( + sigc::bind(sigc::ptr_fun(&sp_arc_context_selection_changed), (gpointer) ac) + ); + + if (prefs_get_int_attribute("tools.shapes", "selcue", 0) != 0) { + ec->enableSelectionCue(); + } + + if (prefs_get_int_attribute("tools.shapes", "gradientdrag", 0) != 0) { + ec->enableGrDrag(); + } + + ac->_message_context = new Inkscape::MessageContext(ec->desktop->messageStack()); +} + +static gint sp_arc_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event) +{ + SPDesktop *desktop = event_context->desktop; + gint ret = FALSE; + + switch (event->type) { + case GDK_BUTTON_PRESS: + if (event->button.button == 1) { + Inkscape::setup_for_drag_start(desktop, event_context, event); + ret = TRUE; + } + break; + // motion and release are always on root (why?) + default: + break; + } + + if (((SPEventContextClass *) parent_class)->item_handler) { + ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event); + } + + return ret; +} + +static gint sp_arc_context_root_handler(SPEventContext *event_context, GdkEvent *event) +{ + static bool dragging; + + SPDesktop *desktop = event_context->desktop; + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + SPArcContext *ac = SP_ARC_CONTEXT(event_context); + + event_context->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100); + + gint ret = FALSE; + + switch (event->type) { + case GDK_BUTTON_PRESS: + if (event->button.button == 1) { + + dragging = true; + ac->center = Inkscape::setup_for_drag_start(desktop, event_context, event); + + SnapManager const m(desktop->namedview); + ac->center = m.freeSnap(Inkscape::Snapper::SNAP_POINT, ac->center, ac->item).getPoint(); + sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), + GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK, + NULL, event->button.time); + ret = TRUE; + } + break; + case GDK_MOTION_NOTIFY: + if (dragging && event->motion.state && GDK_BUTTON1_MASK) { + + if ( event_context->within_tolerance + && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance ) + && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) { + break; // do not drag if we're within tolerance from origin + } + // Once the user has moved farther than tolerance from the original location + // (indicating they intend to draw, not click), then always process the + // motion notify coordinates as given (no snapping back to origin) + event_context->within_tolerance = false; + + NR::Point const motion_w(event->motion.x, event->motion.y); + NR::Point const motion_dt(desktop->w2d(motion_w)); + sp_arc_drag(ac, motion_dt, event->motion.state); + ret = TRUE; + } + break; + case GDK_BUTTON_RELEASE: + event_context->xp = event_context->yp = 0; + if (event->button.button == 1) { + dragging = false; + if (!event_context->within_tolerance) { + // we've been dragging, finish the arc + sp_arc_finish(ac); + } else if (event_context->item_to_select) { + // no dragging, select clicked item if any + if (event->button.state & GDK_SHIFT_MASK) { + selection->toggle(event_context->item_to_select); + } else { + selection->set(event_context->item_to_select); + } + } else { + // click in an empty space + selection->clear(); + } + event_context->xp = 0; + event_context->yp = 0; + event_context->item_to_select = NULL; + ret = TRUE; + } + sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time); + break; + case GDK_KEY_PRESS: + switch (get_group0_keyval (&event->key)) { + case GDK_Alt_L: + case GDK_Alt_R: + case GDK_Control_L: + case GDK_Control_R: + case GDK_Shift_L: + case GDK_Shift_R: + case GDK_Meta_L: // Meta is when you press Shift+Alt (at least on my machine) + case GDK_Meta_R: + sp_event_show_modifier_tip(event_context->defaultMessageContext(), event, + _("Ctrl: make circle or integer-ratio ellipse, snap arc/segment angle"), + _("Shift: draw around the starting point"), + NULL); + break; + case GDK_Up: + case GDK_Down: + case GDK_KP_Up: + case GDK_KP_Down: + // prevent the zoom field from activation + if (!MOD__CTRL_ONLY) + ret = TRUE; + break; + case GDK_x: + case GDK_X: + if (MOD__ALT_ONLY) { + desktop->setToolboxFocusTo ("altx-arc"); + ret = TRUE; + } + break; + case GDK_Escape: + SP_DT_SELECTION(desktop)->clear(); + //TODO: make dragging escapable by Esc + default: + break; + } + break; + case GDK_KEY_RELEASE: + switch (event->key.keyval) { + case GDK_Alt_L: + case GDK_Alt_R: + case GDK_Control_L: + case GDK_Control_R: + case GDK_Shift_L: + case GDK_Shift_R: + case GDK_Meta_L: // Meta is when you press Shift+Alt + case GDK_Meta_R: + event_context->defaultMessageContext()->clear(); + break; + default: + break; + } + break; + default: + break; + } + + if (!ret) { + if (((SPEventContextClass *) parent_class)->root_handler) { + ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event); + } + } + + return ret; +} + +static void sp_arc_drag(SPArcContext *ac, NR::Point pt, guint state) +{ + SPDesktop *desktop = SP_EVENT_CONTEXT(ac)->desktop; + + if (!ac->item) { + + if (Inkscape::have_viable_layer(desktop, ac->_message_context) == false) { + return; + } + + /* Create object */ + Inkscape::XML::Node *repr = sp_repr_new("svg:path"); + repr->setAttribute("sodipodi:type", "arc"); + + /* Set style */ + sp_desktop_apply_style_tool(desktop, repr, "tools.shapes.arc", false); + + ac->item = SP_ITEM(desktop->currentLayer()->appendChildRepr(repr)); + Inkscape::GC::release(repr); + ac->item->transform = SP_ITEM(desktop->currentRoot())->getRelativeTransform(desktop->currentLayer()); + ac->item->updateRepr(); + } + + NR::Rect const r = Inkscape::snap_rectangular_box(desktop, ac->item, pt, ac->center, state); + + sp_arc_position_set(SP_ARC(ac->item), + r.midpoint()[NR::X], r.midpoint()[NR::Y], + r.dimensions()[NR::X] / 2, r.dimensions()[NR::Y] / 2); + + GString *xs = SP_PX_TO_METRIC_STRING(r.dimensions()[NR::X], desktop->namedview->getDefaultMetric()); + GString *ys = SP_PX_TO_METRIC_STRING(r.dimensions()[NR::Y], desktop->namedview->getDefaultMetric()); + ac->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("Ellipse: %s × %s; with Ctrl to make circle or integer-ratio ellipse; with Shift to draw around the starting point"), xs->str, ys->str); + g_string_free(xs, FALSE); + g_string_free(ys, FALSE); +} + +static void sp_arc_finish(SPArcContext *ac) +{ + ac->_message_context->clear(); + + if (ac->item != NULL) { + SPDesktop *desktop = SP_EVENT_CONTEXT(ac)->desktop; + + SP_OBJECT(ac->item)->updateRepr(); + + SP_DT_SELECTION(desktop)->set(ac->item); + sp_document_done(SP_DT_DOCUMENT(desktop)); + + ac->item = NULL; + } +} + + +/* + 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 : diff --git a/src/arc-context.h b/src/arc-context.h new file mode 100644 index 000000000..2ce008b43 --- /dev/null +++ b/src/arc-context.h @@ -0,0 +1,62 @@ +#ifndef SEEN_ARC_CONTEXT_H +#define SEEN_ARC_CONTEXT_H + +/* + * Ellipse drawing context + * + * Authors: + * Mitsuru Oka + * Lauris Kaplinski + * + * Copyright (C) 2000-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2002 Mitsuru Oka + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +#include +#include "event-context.h" +struct SPKnotHolder; + +#define SP_TYPE_ARC_CONTEXT (sp_arc_context_get_type()) +#define SP_ARC_CONTEXT(obj) (GTK_CHECK_CAST((obj), SP_TYPE_ARC_CONTEXT, SPArcContext)) +#define SP_ARC_CONTEXT_CLASS(klass) (GTK_CHECK_CLASS_CAST((klass), SP_TYPE_ARC_CONTEXT, SPArcContextClass)) +#define SP_IS_ARC_CONTEXT(obj) (GTK_CHECK_TYPE((obj), SP_TYPE_ARC_CONTEXT)) +#define SP_IS_ARC_CONTEXT_CLASS(klass) (GTK_CHECK_CLASS_TYPE((klass), SP_TYPE_ARC_CONTEXT)) + +class SPArcContext; +class SPArcContextClass; + +struct SPArcContext : public SPEventContext { + SPItem *item; + NR::Point center; + + sigc::connection sel_changed_connection; + + Inkscape::MessageContext *_message_context; +}; + +struct SPArcContextClass { + SPEventContextClass parent_class; +}; + +/* Standard Gtk function */ + +GtkType sp_arc_context_get_type(void); + + +#endif /* !SEEN_ARC_CONTEXT_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 : diff --git a/src/attributes-test.cpp b/src/attributes-test.cpp new file mode 100644 index 000000000..50d2e81bd --- /dev/null +++ b/src/attributes-test.cpp @@ -0,0 +1,473 @@ +#include +#include "utest/utest.h" +#include "attributes.h" +#include "streq.h" + +/* Extracted mechanically from http://www.w3.org/TR/SVG11/attindex.html: + + tidy -wrap 999 -asxml < attindex.html 2>/dev/null | + tr -d \\n | + sed 's,,@,g' | + tr @ \\n | + sed 's,.*,,;s,^,,;1,/^%/d;/^%/d;s,^, {",;s/$/", false},/' | + uniq + + attindex.html lacks attributeName, begin, additive, font, marker; + I've added these manually. +*/ +static struct {char const *attr; bool supported;} const all_attrs[] = { + {"attributeName", true}, + {"begin", true}, + {"additive", true}, + {"font", true}, + {"marker", true}, + {"line-height", true}, + + {"accent-height", false}, + {"accumulate", true}, + {"alignment-baseline", true}, + {"alphabetic", false}, + {"amplitude", false}, + {"animate", false}, + {"arabic-form", false}, + {"ascent", false}, + {"attributeType", true}, + {"azimuth", false}, + {"baseFrequency", false}, + {"baseline-shift", true}, + {"baseProfile", false}, + {"bbox", false}, + {"bias", false}, + {"block-progression", true}, + {"by", true}, + {"calcMode", true}, + {"cap-height", false}, + {"class", false}, + {"clip", true}, + {"clip-path", true}, + {"clip-rule", true}, + {"clipPathUnits", true}, + {"color", true}, + {"color-interpolation", true}, + {"color-interpolation-filters", true}, + {"color-profile", true}, + {"color-rendering", true}, + {"contentScriptType", false}, + {"contentStyleType", false}, + {"cursor", true}, + {"cx", true}, + {"cy", true}, + {"d", true}, + {"descent", false}, + {"diffuseConstant", false}, + {"direction", true}, + {"display", true}, + {"divisor", false}, + {"dominant-baseline", true}, + {"dur", true}, + {"dx", true}, + {"dy", true}, + {"edgeMode", false}, + {"elevation", false}, + {"enable-background", true}, + {"end", true}, + {"exponent", false}, + {"externalResourcesRequired", false}, + {"feColorMatrix", false}, + {"feComposite", false}, + {"feGaussianBlur", false}, + {"feMorphology", false}, + {"feTile", false}, + {"fill", true}, + {"fill-opacity", true}, + {"fill-rule", true}, + {"filter", true}, + {"filterRes", false}, + {"filterUnits", false}, + {"flood-color", true}, + {"flood-opacity", true}, + {"font-family", true}, + {"font-size", true}, + {"font-size-adjust", true}, + {"font-stretch", true}, + {"font-style", true}, + {"font-variant", true}, + {"font-weight", true}, + {"format", false}, + {"from", true}, + {"fx", true}, + {"fy", true}, + {"g1", false}, + {"g2", false}, + {"glyph-name", false}, + {"glyph-orientation-horizontal", true}, + {"glyph-orientation-vertical", true}, + {"glyphRef", false}, + {"gradientTransform", true}, + {"gradientUnits", true}, + {"hanging", false}, + {"height", true}, + {"horiz-adv-x", false}, + {"horiz-origin-x", false}, + {"horiz-origin-y", false}, + {"ideographic", false}, + {"image-rendering", true}, + {"in", false}, + {"in2", false}, + {"intercept", false}, + {"k", false}, + {"k1", false}, + {"k2", false}, + {"k3", false}, + {"k4", false}, + {"kernelMatrix", false}, + {"kernelUnitLength", false}, + {"kerning", true}, + {"keyPoints", false}, + {"keySplines", true}, + {"keyTimes", true}, + {"lang", false}, + {"lengthAdjust", false}, + {"letter-spacing", true}, + {"lighting-color", true}, + {"limitingConeAngle", false}, + {"local", false}, + {"marker-end", true}, + {"marker-mid", true}, + {"marker-start", true}, + {"markerHeight", true}, + {"markerUnits", true}, + {"markerWidth", true}, + {"mask", true}, + {"maskContentUnits", true}, + {"maskUnits", true}, + {"mathematical", false}, + {"max", true}, + {"media", false}, + {"method", false}, + {"min", true}, + {"mode", false}, + {"name", false}, + {"numOctaves", false}, + {"offset", true}, + {"onabort", false}, + {"onactivate", false}, + {"onbegin", false}, + {"onclick", false}, + {"onend", false}, + {"onerror", false}, + {"onfocusin", false}, + {"onfocusout", false}, + {"onload", false}, + {"onmousedown", false}, + {"onmousemove", false}, + {"onmouseout", false}, + {"onmouseover", false}, + {"onmouseup", false}, + {"onrepeat", false}, + {"onresize", false}, + {"onscroll", false}, + {"onunload", false}, + {"onzoom", false}, + {"opacity", true}, + {"operator", false}, + {"order", false}, + {"orient", true}, + {"orientation", true}, + {"origin", false}, + {"overflow", true}, + {"overline-position", false}, + {"overline-thickness", false}, + {"panose-1", false}, + {"path", false}, + {"pathLength", false}, + {"patternContentUnits", true}, + {"patternTransform", true}, + {"patternUnits", true}, + {"pointer-events", true}, + {"points", true}, + {"pointsAtX", false}, + {"pointsAtY", false}, + {"pointsAtZ", false}, + {"preserveAlpha", false}, + {"preserveAspectRatio", true}, + {"primitiveUnits", false}, + {"r", true}, + {"radius", false}, + {"refX", true}, + {"refY", true}, + {"rendering-intent", false}, + {"repeatCount", true}, + {"repeatDur", true}, + {"requiredExtensions", false}, + {"restart", true}, + {"result", false}, + {"rotate", true}, + {"rx", true}, + {"ry", true}, + {"scale", false}, + {"seed", false}, + {"shape-rendering", true}, + {"slope", false}, + {"spacing", false}, + {"specularConstant", false}, + {"specularExponent", false}, + {"spreadMethod", true}, + {"startOffset", true}, + {"stdDeviation", false}, + {"stemh", false}, + {"stemv", false}, + {"stitchTiles", false}, + {"stop-color", true}, + {"stop-opacity", true}, + {"strikethrough-position", false}, + {"strikethrough-thickness", false}, + {"stroke", true}, + {"stroke-dasharray", true}, + {"stroke-dashoffset", true}, + {"stroke-linecap", true}, + {"stroke-linejoin", true}, + {"stroke-miterlimit", true}, + {"stroke-opacity", true}, + {"stroke-width", true}, + {"style", true}, + {"surfaceScale", false}, + {"systemLanguage", false}, + {"tableValues", false}, + {"target", true}, + {"targetX", false}, + {"targetY", false}, + {"text-align", true}, + {"text-anchor", true}, + {"text-decoration", true}, + {"text-indent", true}, + {"text-rendering", true}, + {"text-transform", true}, + {"textLength", false}, + {"title", false}, + {"to", true}, + {"transform", true}, + {"type", true}, + {"u1", false}, + {"u2", false}, + {"underline-position", false}, + {"underline-thickness", false}, + {"unicode", false}, + {"unicode-bidi", true}, + {"unicode-range", false}, + {"units-per-em", false}, + {"v-alphabetic", false}, + {"v-hanging", false}, + {"v-ideographic", false}, + {"v-mathematical", false}, + {"values", true}, + {"version", true}, + {"vert-adv-y", false}, + {"vert-origin-x", false}, + {"vert-origin-y", false}, + {"viewBox", true}, + {"viewTarget", false}, + {"visibility", true}, + {"width", true}, + {"widths", false}, + {"word-spacing", true}, + {"writing-mode", true}, + {"x", true}, + {"x-height", false}, + {"x1", true}, + {"x2", true}, + {"xChannelSelector", false}, + {"xlink:actuate", true}, + {"xlink:arcrole", true}, + {"xlink:href", true}, + {"xlink:role", true}, + {"xlink:show", true}, + {"xlink:title", true}, + {"xlink:type", true}, + {"xml:base", false}, + {"xml:space", true}, + {"xmlns", false}, + {"xmlns:xlink", false}, + {"y", true}, + {"y1", true}, + {"y2", true}, + {"yChannelSelector", false}, + {"z", false}, + {"zoomAndPan", false}, + + /* Extra attributes. */ + {"id", true}, + {"inkscape:collect", true}, + {"inkscape:document-units", true}, + {"inkscape:label", true}, + {"sodipodi:insensitive", true}, + {"sodipodi:nonprintable", true}, + {"inkscape:groupmode", true}, + {"sodipodi:version", true}, + {"inkscape:version", true}, + {"inkscape:pageopacity", true}, + {"inkscape:pageshadow", true}, + {"inkscape:zoom", true}, + {"inkscape:cx", true}, + {"inkscape:cy", true}, + {"inkscape:window-width", true}, + {"inkscape:window-height", true}, + {"inkscape:window-x", true}, + {"inkscape:window-y", true}, + {"inkscape:grid-bbox", true}, + {"inkscape:guide-bbox", true}, + {"inkscape:grid-points", true}, + {"inkscape:guide-points", true}, + {"inkscape:current-layer", true}, + {"inkscape:connector-type", true}, + {"inkscape:connection-start", true}, + {"inkscape:connection-end", true}, + {"inkscape:connector-avoid", true}, + {"sodipodi:cx", true}, + {"sodipodi:cy", true}, + {"sodipodi:rx", true}, + {"sodipodi:ry", true}, + {"sodipodi:start", true}, + {"sodipodi:end", true}, + {"sodipodi:open", true}, + {"sodipodi:sides", true}, + {"sodipodi:r1", true}, + {"sodipodi:r2", true}, + {"sodipodi:arg1", true}, + {"sodipodi:arg2", true}, + {"inkscape:flatsided", true}, + {"inkscape:rounded", true}, + {"inkscape:randomized", true}, + {"sodipodi:expansion", true}, + {"sodipodi:revolution", true}, + {"sodipodi:radius", true}, + {"sodipodi:argument", true}, + {"sodipodi:t0", true}, + {"sodipodi:original", true}, + {"inkscape:original", true}, + {"inkscape:href", true}, + {"inkscape:radius", true}, + {"sodipodi:role", true}, + {"sodipodi:linespacing", true}, + {"inkscape:srcNoMarkup", true}, + {"inkscape:srcPango", true}, + {"inkscape:dstShape", true}, + {"inkscape:dstPath", true}, + {"inkscape:dstBox", true}, + {"inkscape:dstColumn", true}, + {"inkscape:excludeShape", true}, + {"inkscape:layoutOptions", true}, + + /* SPNamedView */ + {"viewonly", true}, + {"showgrid", true}, + {"showguides", true}, + {"gridtolerance", true}, + {"guidetolerance", true}, + {"gridoriginx", true}, + {"gridoriginy", true}, + {"gridspacingx", true}, + {"gridspacingy", true}, + {"gridcolor", true}, + {"gridopacity", true}, + {"gridempcolor", true}, + {"gridempopacity", true}, + {"gridempspacing", true}, + {"guidecolor", true}, + {"guideopacity", true}, + {"guidehicolor", true}, + {"guidehiopacity", true}, + {"showborder", true}, + {"inkscape:showpageshadow", true}, + {"borderlayer", true}, + {"bordercolor", true}, + {"borderopacity", true}, + {"pagecolor", true}, + + /* SPGuide */ + {"position", true} + +}; + +static bool +test_attributes() +{ + utest_start("attributes"); + + std::vector ids; + ids.reserve(256); + UTEST_TEST("attribute lookup") { + for (unsigned i = 0; i < G_N_ELEMENTS(all_attrs); ++i) { + char const *const attr_str = all_attrs[i].attr; + unsigned const id = sp_attribute_lookup(attr_str); + bool const recognized(id); + UTEST_ASSERT(recognized == all_attrs[i].supported); + if (recognized) { + if (ids.size() <= id) { + ids.resize(id + 1); + } + UTEST_ASSERT(!ids[id]); + ids[id] = true; + + unsigned char const *reverse_ustr = sp_attribute_name(id); + char const *reverse_str = reinterpret_cast(reverse_ustr); + UTEST_ASSERT(streq(reverse_str, attr_str)); + } + } + + /* Test for any attributes that this test program doesn't know about. + * + * If any are found, then: + * + * If it is in the `inkscape:' namespace then simply add it to all_attrs with + * `true' as the second field (`supported'). + * + * If it is in the `sodipodi:' namespace then check the spelling against sodipodi + * sources. If you don't have sodipodi sources, then don't add it: leave to someone + * else. + * + * Otherwise, it's probably a bug: ~all SVG 1.1 attributes should already be + * in the all_attrs table. However, the comment above all_attrs does mention + * some things missing from attindex.html, so there may be more. Check the SVG + * spec. Another possibility is that the attribute is new in SVG 1.2. In this case, + * check the spelling against the [draft] SVG 1.2 spec before adding to all_attrs. + * (If you can't be bothered checking the spec, then don't update all_attrs.) + * + * If the attribute isn't in either SVG 1.1 or 1.2 then it's probably a mistake + * for it not to be in the inkscape namespace. (Not sure about attributes used only + * on elements in the inkscape namespace though.) + * + * In any case, make sure that the attribute's source is documented accordingly. + */ + bool found = false; + unsigned const n_ids = ids.size(); + for (unsigned id = 1; id < n_ids; ++id) { + if (!ids[id]) { + unsigned char const *str = sp_attribute_name(id); + printf("%s\n", (const char *)str); /* Apparently printf doesn't like unsigned strings -- Ted */ + found = true; + } + } + UTEST_ASSERT(!found); + } + + return utest_end(); +} + +int main() +{ + return ( test_attributes() + ? EXIT_SUCCESS + : EXIT_FAILURE ); +} + +/* + 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 : diff --git a/src/attributes.cpp b/src/attributes.cpp new file mode 100644 index 000000000..4d182179c --- /dev/null +++ b/src/attributes.cpp @@ -0,0 +1,348 @@ +#define __SP_ATTRIBUTES_C__ + +/** \file + * Lookup dictionary for attributes/properties. + */ +/* + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include "attributes.h" + +typedef struct { + gint code; + gchar const *name; +} SPStyleProp; + +static SPStyleProp const props[] = { + {SP_ATTR_INVALID, NULL}, + /* SPObject */ + {SP_ATTR_ID, "id"}, + {SP_ATTR_INKSCAPE_COLLECT, "inkscape:collect"}, + {SP_ATTR_INKSCAPE_LABEL, "inkscape:label"}, + /* SPItem */ + {SP_ATTR_TRANSFORM, "transform"}, + {SP_ATTR_SODIPODI_INSENSITIVE, "sodipodi:insensitive"}, + {SP_ATTR_SODIPODI_NONPRINTABLE, "sodipodi:nonprintable"}, + {SP_ATTR_CONNECTOR_AVOID, "inkscape:connector-avoid"}, + {SP_ATTR_STYLE, "style"}, + /* SPAnchor */ + {SP_ATTR_XLINK_HREF, "xlink:href"}, + {SP_ATTR_XLINK_TYPE, "xlink:type"}, + {SP_ATTR_XLINK_ROLE, "xlink:role"}, + {SP_ATTR_XLINK_ARCROLE, "xlink:arcrole"}, + {SP_ATTR_XLINK_TITLE, "xlink:title"}, + {SP_ATTR_XLINK_SHOW, "xlink:show"}, + {SP_ATTR_XLINK_ACTUATE, "xlink:actuate"}, + {SP_ATTR_TARGET, "target"}, + {SP_ATTR_INKSCAPE_GROUPMODE, "inkscape:groupmode"}, + /* SPRoot */ + {SP_ATTR_VERSION, "version"}, + {SP_ATTR_WIDTH, "width"}, + {SP_ATTR_HEIGHT, "height"}, + {SP_ATTR_VIEWBOX, "viewBox"}, + {SP_ATTR_PRESERVEASPECTRATIO, "preserveAspectRatio"}, + {SP_ATTR_SODIPODI_VERSION, "sodipodi:version"}, + {SP_ATTR_INKSCAPE_VERSION, "inkscape:version"}, + /* SPNamedView */ + {SP_ATTR_VIEWONLY, "viewonly"}, + {SP_ATTR_SHOWGRID, "showgrid"}, + {SP_ATTR_SHOWGUIDES, "showguides"}, + {SP_ATTR_GRIDTOLERANCE, "gridtolerance"}, + {SP_ATTR_GUIDETOLERANCE, "guidetolerance"}, + {SP_ATTR_OBJECTTOLERANCE, "objecttolerance"}, + {SP_ATTR_ABS_TOLERANCE, "has_abs_tolerance"}, + {SP_ATTR_GRIDORIGINX, "gridoriginx"}, + {SP_ATTR_GRIDORIGINY, "gridoriginy"}, + {SP_ATTR_GRIDSPACINGX, "gridspacingx"}, + {SP_ATTR_GRIDSPACINGY, "gridspacingy"}, + {SP_ATTR_GRIDCOLOR, "gridcolor"}, + {SP_ATTR_GRIDOPACITY, "gridopacity"}, + {SP_ATTR_GRIDEMPCOLOR, "gridempcolor"}, + {SP_ATTR_GRIDEMPOPACITY, "gridempopacity"}, + {SP_ATTR_GRIDEMPSPACING, "gridempspacing"}, + {SP_ATTR_GUIDECOLOR, "guidecolor"}, + {SP_ATTR_GUIDEOPACITY, "guideopacity"}, + {SP_ATTR_GUIDEHICOLOR, "guidehicolor"}, + {SP_ATTR_GUIDEHIOPACITY, "guidehiopacity"}, + {SP_ATTR_SHOWBORDER, "showborder"}, + {SP_ATTR_SHOWPAGESHADOW, "inkscape:showpageshadow"}, + {SP_ATTR_BORDERLAYER, "borderlayer"}, + {SP_ATTR_BORDERCOLOR, "bordercolor"}, + {SP_ATTR_BORDEROPACITY, "borderopacity"}, + {SP_ATTR_PAGECOLOR, "pagecolor"}, + {SP_ATTR_INKSCAPE_PAGEOPACITY, "inkscape:pageopacity"}, + {SP_ATTR_INKSCAPE_PAGESHADOW, "inkscape:pageshadow"}, + {SP_ATTR_INKSCAPE_ZOOM, "inkscape:zoom"}, + {SP_ATTR_INKSCAPE_CX, "inkscape:cx"}, + {SP_ATTR_INKSCAPE_CY, "inkscape:cy"}, + {SP_ATTR_INKSCAPE_WINDOW_WIDTH, "inkscape:window-width"}, + {SP_ATTR_INKSCAPE_WINDOW_HEIGHT, "inkscape:window-height"}, + {SP_ATTR_INKSCAPE_WINDOW_X, "inkscape:window-x"}, + {SP_ATTR_INKSCAPE_WINDOW_Y, "inkscape:window-y"}, + {SP_ATTR_INKSCAPE_GRID_BBOX, "inkscape:grid-bbox"}, + {SP_ATTR_INKSCAPE_GUIDE_BBOX, "inkscape:guide-bbox"}, + {SP_ATTR_INKSCAPE_OBJECT_BBOX, "inkscape:object-bbox"}, + {SP_ATTR_INKSCAPE_GRID_POINTS, "inkscape:grid-points"}, + {SP_ATTR_INKSCAPE_GUIDE_POINTS, "inkscape:guide-points"}, + {SP_ATTR_INKSCAPE_OBJECT_POINTS, "inkscape:object-points"}, + {SP_ATTR_INKSCAPE_OBJECT_PATHS, "inkscape:object-paths"}, + {SP_ATTR_INKSCAPE_OBJECT_NODES, "inkscape:object-nodes"}, + {SP_ATTR_INKSCAPE_CURRENT_LAYER, "inkscape:current-layer"}, + {SP_ATTR_INKSCAPE_DOCUMENT_UNITS, "inkscape:document-units"}, + /* SPGuide */ + {SP_ATTR_ORIENTATION, "orientation"}, + {SP_ATTR_POSITION, "position"}, + /* SPImage */ + {SP_ATTR_X, "x"}, + {SP_ATTR_Y, "y"}, + /* SPPath */ + {SP_ATTR_D, "d"}, + /* (Note: XML representation of connectors may change in future.) */ + {SP_ATTR_CONNECTOR_TYPE, "inkscape:connector-type"}, + {SP_ATTR_CONNECTION_START, "inkscape:connection-start"}, + {SP_ATTR_CONNECTION_END, "inkscape:connection-end"}, + /* SPRect */ + {SP_ATTR_RX, "rx"}, + {SP_ATTR_RY, "ry"}, + /* SPEllipse */ + {SP_ATTR_R, "r"}, + {SP_ATTR_CX, "cx"}, + {SP_ATTR_CY, "cy"}, + {SP_ATTR_SODIPODI_CX, "sodipodi:cx"}, + {SP_ATTR_SODIPODI_CY, "sodipodi:cy"}, + {SP_ATTR_SODIPODI_RX, "sodipodi:rx"}, + {SP_ATTR_SODIPODI_RY, "sodipodi:ry"}, + {SP_ATTR_SODIPODI_START, "sodipodi:start"}, + {SP_ATTR_SODIPODI_END, "sodipodi:end"}, + {SP_ATTR_SODIPODI_OPEN, "sodipodi:open"}, + /* SPStar */ + {SP_ATTR_SODIPODI_SIDES, "sodipodi:sides"}, + {SP_ATTR_SODIPODI_R1, "sodipodi:r1"}, + {SP_ATTR_SODIPODI_R2, "sodipodi:r2"}, + {SP_ATTR_SODIPODI_ARG1, "sodipodi:arg1"}, + {SP_ATTR_SODIPODI_ARG2, "sodipodi:arg2"}, + {SP_ATTR_INKSCAPE_FLATSIDED, "inkscape:flatsided"}, + {SP_ATTR_INKSCAPE_ROUNDED, "inkscape:rounded"}, + {SP_ATTR_INKSCAPE_RANDOMIZED, "inkscape:randomized"}, + /* SPSpiral */ + {SP_ATTR_SODIPODI_EXPANSION, "sodipodi:expansion"}, + {SP_ATTR_SODIPODI_REVOLUTION, "sodipodi:revolution"}, + {SP_ATTR_SODIPODI_RADIUS, "sodipodi:radius"}, + {SP_ATTR_SODIPODI_ARGUMENT, "sodipodi:argument"}, + {SP_ATTR_SODIPODI_T0, "sodipodi:t0"}, + /* SPOffset */ + {SP_ATTR_SODIPODI_ORIGINAL, "sodipodi:original"}, + {SP_ATTR_INKSCAPE_ORIGINAL, "inkscape:original"}, + {SP_ATTR_INKSCAPE_HREF, "inkscape:href"}, + {SP_ATTR_INKSCAPE_RADIUS, "inkscape:radius"}, + /* SPLine */ + {SP_ATTR_X1, "x1"}, + {SP_ATTR_Y1, "y1"}, + {SP_ATTR_X2, "x2"}, + {SP_ATTR_Y2, "y2"}, + /* SPPolyline */ + {SP_ATTR_POINTS, "points"}, + /* SPTSpan */ + {SP_ATTR_DX, "dx"}, + {SP_ATTR_DY, "dy"}, + {SP_ATTR_ROTATE, "rotate"}, + {SP_ATTR_SODIPODI_ROLE, "sodipodi:role"}, + /* SPText */ + {SP_ATTR_SODIPODI_LINESPACING, "sodipodi:linespacing"}, + /* SPTextPath */ + {SP_ATTR_STARTOFFSET, "startOffset"}, + /* SPStop */ + {SP_ATTR_OFFSET, "offset"}, + /* SPGradient */ + {SP_ATTR_GRADIENTUNITS, "gradientUnits"}, + {SP_ATTR_GRADIENTTRANSFORM, "gradientTransform"}, + {SP_ATTR_SPREADMETHOD, "spreadMethod"}, + /* SPRadialGradient */ + {SP_ATTR_FX, "fx"}, + {SP_ATTR_FY, "fy"}, + /* SPPattern */ + {SP_ATTR_PATTERNUNITS, "patternUnits"}, + {SP_ATTR_PATTERNCONTENTUNITS, "patternContentUnits"}, + {SP_ATTR_PATTERNTRANSFORM, "patternTransform"}, + /* SPClipPath */ + {SP_ATTR_CLIPPATHUNITS, "clipPathUnits"}, + /* SPMask */ + {SP_ATTR_MASKUNITS, "maskUnits"}, + {SP_ATTR_MASKCONTENTUNITS, "maskContentUnits"}, + /* SPMarker */ + {SP_ATTR_MARKERUNITS, "markerUnits"}, + {SP_ATTR_REFX, "refX"}, + {SP_ATTR_REFY, "refY"}, + {SP_ATTR_MARKERWIDTH, "markerWidth"}, + {SP_ATTR_MARKERHEIGHT, "markerHeight"}, + {SP_ATTR_ORIENT, "orient"}, + /* SPStyleElem */ + {SP_ATTR_TYPE, "type"}, + /* Animations */ + {SP_ATTR_ATTRIBUTENAME, "attributeName"}, + {SP_ATTR_ATTRIBUTETYPE, "attributeType"}, + {SP_ATTR_BEGIN, "begin"}, + {SP_ATTR_DUR, "dur"}, + {SP_ATTR_END, "end"}, + {SP_ATTR_MIN, "min"}, + {SP_ATTR_MAX, "max"}, + {SP_ATTR_RESTART, "restart"}, + {SP_ATTR_REPEATCOUNT, "repeatCount"}, + {SP_ATTR_REPEATDUR, "repeatDur"}, + /* Interpolating animations */ + {SP_ATTR_CALCMODE, "calcMode"}, + {SP_ATTR_VALUES, "values"}, + {SP_ATTR_KEYTIMES, "keyTimes"}, + {SP_ATTR_KEYSPLINES, "keySplines"}, + {SP_ATTR_FROM, "from"}, + {SP_ATTR_TO, "to"}, + {SP_ATTR_BY, "by"}, + {SP_ATTR_ADDITIVE, "additive"}, + {SP_ATTR_ACCUMULATE, "accumulate"}, + + /* XML */ + {SP_ATTR_XML_SPACE, "xml:space"}, + + /* typeset */ + {SP_ATTR_TEXT_NOMARKUP, "inkscape:srcNoMarkup"}, + {SP_ATTR_TEXT_PANGOMARKUP, "inkscape:srcPango" }, + {SP_ATTR_TEXT_INSHAPE, "inkscape:dstShape"}, + {SP_ATTR_TEXT_ONPATH, "inkscape:dstPath"}, + {SP_ATTR_TEXT_INBOX,"inkscape:dstBox"}, + {SP_ATTR_TEXT_INCOLUMN,"inkscape:dstColumn"}, + {SP_ATTR_TEXT_EXCLUDE,"inkscape:excludeShape"}, + {SP_ATTR_LAYOUT_OPTIONS,"inkscape:layoutOptions"}, + + /* CSS2 */ + /* Font */ + {SP_PROP_FONT, "font"}, + {SP_PROP_FONT_FAMILY, "font-family"}, + {SP_PROP_FONT_SIZE, "font-size"}, + {SP_PROP_FONT_SIZE_ADJUST, "font-size-adjust"}, + {SP_PROP_FONT_STRETCH, "font-stretch"}, + {SP_PROP_FONT_STYLE, "font-style"}, + {SP_PROP_FONT_VARIANT, "font-variant"}, + {SP_PROP_FONT_WEIGHT, "font-weight"}, + /* Text */ + {SP_PROP_TEXT_INDENT, "text-indent"}, + {SP_PROP_TEXT_ALIGN, "text-align"}, + {SP_PROP_TEXT_DECORATION, "text-decoration"}, + {SP_PROP_LINE_HEIGHT, "line-height"}, + {SP_PROP_LETTER_SPACING, "letter-spacing"}, + {SP_PROP_WORD_SPACING, "word-spacing"}, + {SP_PROP_TEXT_TRANSFORM, "text-transform"}, + /* Text (css3) */ + {SP_PROP_DIRECTION, "direction"}, + {SP_PROP_BLOCK_PROGRESSION, "block-progression"}, + {SP_PROP_WRITING_MODE, "writing-mode"}, + {SP_PROP_UNICODE_BIDI, "unicode-bidi"}, + {SP_PROP_ALIGNMENT_BASELINE, "alignment-baseline"}, + {SP_PROP_BASELINE_SHIFT, "baseline-shift"}, + {SP_PROP_DOMINANT_BASELINE, "dominant-baseline"}, + {SP_PROP_GLYPH_ORIENTATION_HORIZONTAL, "glyph-orientation-horizontal"}, + {SP_PROP_GLYPH_ORIENTATION_VERTICAL, "glyph-orientation-vertical"}, + {SP_PROP_KERNING, "kerning"}, + {SP_PROP_TEXT_ANCHOR, "text-anchor"}, + /* Misc */ + {SP_PROP_CLIP, "clip"}, + {SP_PROP_COLOR, "color"}, + {SP_PROP_CURSOR, "cursor"}, + {SP_PROP_DISPLAY, "display"}, + {SP_PROP_OVERFLOW, "overflow"}, + {SP_PROP_VISIBILITY, "visibility"}, + /* SVG */ + /* Clip/Mask */ + {SP_PROP_CLIP_PATH, "clip-path"}, + {SP_PROP_CLIP_RULE, "clip-rule"}, + {SP_PROP_MASK, "mask"}, + {SP_PROP_OPACITY, "opacity"}, + /* Filter */ + {SP_PROP_ENABLE_BACKGROUND, "enable-background"}, + {SP_PROP_FILTER, "filter"}, + {SP_PROP_FLOOD_COLOR, "flood-color"}, + {SP_PROP_FLOOD_OPACITY, "flood-opacity"}, + {SP_PROP_LIGHTING_COLOR, "lighting-color"}, + /* Gradient */ + {SP_PROP_STOP_COLOR, "stop-color"}, + {SP_PROP_STOP_OPACITY, "stop-opacity"}, + /* Interactivity */ + {SP_PROP_POINTER_EVENTS, "pointer-events"}, + /* Paint */ + {SP_PROP_COLOR_INTERPOLATION, "color-interpolation"}, + {SP_PROP_COLOR_INTERPOLATION_FILTERS, "color-interpolation-filters"}, + {SP_PROP_COLOR_PROFILE, "color-profile"}, + {SP_PROP_COLOR_RENDERING, "color-rendering"}, + {SP_PROP_FILL, "fill"}, + {SP_PROP_FILL_OPACITY, "fill-opacity"}, + {SP_PROP_FILL_RULE, "fill-rule"}, + {SP_PROP_IMAGE_RENDERING, "image-rendering"}, + {SP_PROP_MARKER, "marker"}, + {SP_PROP_MARKER_END, "marker-end"}, + {SP_PROP_MARKER_MID, "marker-mid"}, + {SP_PROP_MARKER_START, "marker-start"}, + {SP_PROP_SHAPE_RENDERING, "shape-rendering"}, + {SP_PROP_STROKE, "stroke"}, + {SP_PROP_STROKE_DASHARRAY, "stroke-dasharray"}, + {SP_PROP_STROKE_DASHOFFSET, "stroke-dashoffset"}, + {SP_PROP_STROKE_LINECAP, "stroke-linecap"}, + {SP_PROP_STROKE_LINEJOIN, "stroke-linejoin"}, + {SP_PROP_STROKE_MITERLIMIT, "stroke-miterlimit"}, + {SP_PROP_STROKE_OPACITY, "stroke-opacity"}, + {SP_PROP_STROKE_WIDTH, "stroke-width"}, + {SP_PROP_TEXT_RENDERING, "text-rendering"}, +}; + +#define n_attrs (sizeof(props) / sizeof(props[0])) + +/** Returns an SPAttributeEnum; SP_ATTR_INVALID (of value 0) if key isn't recognized. */ +unsigned +sp_attribute_lookup(gchar const *key) +{ + static GHashTable *propdict = NULL; + + if (!propdict) { + unsigned int i; + propdict = g_hash_table_new(g_str_hash, g_str_equal); + for (i = 1; i < n_attrs; i++) { + g_assert(props[i].code == static_cast< gint >(i) ); + g_hash_table_insert(propdict, + const_cast(static_cast(props[i].name)), + GINT_TO_POINTER(props[i].code)); + } + } + + return GPOINTER_TO_UINT(g_hash_table_lookup(propdict, key)); +} + +unsigned char const * +sp_attribute_name(unsigned int id) +{ + if (id >= n_attrs) { + return NULL; + } + + return (unsigned char*)props[id].name; +} + + +/* + 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 : diff --git a/src/attributes.h b/src/attributes.h new file mode 100644 index 000000000..24b87fc1c --- /dev/null +++ b/src/attributes.h @@ -0,0 +1,318 @@ +#ifndef __SP_ATTRIBUTES_H__ +#define __SP_ATTRIBUTES_H__ + +/** \file + * Lookup dictionary for attributes/properties. + */ +/* + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ +#include +#include + +unsigned int sp_attribute_lookup(gchar const *key); +unsigned char const *sp_attribute_name(unsigned int id); + +/** + * True iff k is a property in SVG, i.e. something that can be written either in a style attribute + * or as its own XML attribute. + */ +#define SP_ATTRIBUTE_IS_CSS(k) (((k) >= SP_PROP_FONT) && ((k) <= SP_PROP_TEXT_RENDERING)) + +enum SPAttributeEnum { + SP_ATTR_INVALID, ///< Must have value 0. + /* SPObject */ + SP_ATTR_ID, + SP_ATTR_INKSCAPE_COLLECT, + SP_ATTR_INKSCAPE_LABEL, + /* SPItem */ + SP_ATTR_TRANSFORM, + SP_ATTR_SODIPODI_INSENSITIVE, + SP_ATTR_SODIPODI_NONPRINTABLE, + SP_ATTR_CONNECTOR_AVOID, + SP_ATTR_STYLE, + /* SPAnchor */ + SP_ATTR_XLINK_HREF, + SP_ATTR_XLINK_TYPE, + SP_ATTR_XLINK_ROLE, + SP_ATTR_XLINK_ARCROLE, + SP_ATTR_XLINK_TITLE, + SP_ATTR_XLINK_SHOW, + SP_ATTR_XLINK_ACTUATE, + SP_ATTR_TARGET, + /* SPGroup */ + SP_ATTR_INKSCAPE_GROUPMODE, + /* SPRoot */ + SP_ATTR_VERSION, + SP_ATTR_WIDTH, + SP_ATTR_HEIGHT, + SP_ATTR_VIEWBOX, + SP_ATTR_PRESERVEASPECTRATIO, + SP_ATTR_SODIPODI_VERSION, + SP_ATTR_INKSCAPE_VERSION, + /* SPNamedView */ + SP_ATTR_VIEWONLY, + SP_ATTR_SHOWGRID, + SP_ATTR_SHOWGUIDES, + SP_ATTR_GRIDTOLERANCE, + SP_ATTR_GUIDETOLERANCE, + SP_ATTR_OBJECTTOLERANCE, + SP_ATTR_ABS_TOLERANCE, + SP_ATTR_GRIDORIGINX, + SP_ATTR_GRIDORIGINY, + SP_ATTR_GRIDSPACINGX, + SP_ATTR_GRIDSPACINGY, + SP_ATTR_GRIDCOLOR, + SP_ATTR_GRIDOPACITY, + SP_ATTR_GRIDEMPCOLOR, + SP_ATTR_GRIDEMPOPACITY, + SP_ATTR_GRIDEMPSPACING, + SP_ATTR_GUIDECOLOR, + SP_ATTR_GUIDEOPACITY, + SP_ATTR_GUIDEHICOLOR, + SP_ATTR_GUIDEHIOPACITY, + SP_ATTR_SHOWBORDER, + SP_ATTR_SHOWPAGESHADOW, + SP_ATTR_BORDERLAYER, + SP_ATTR_BORDERCOLOR, + SP_ATTR_BORDEROPACITY, + SP_ATTR_PAGECOLOR, + SP_ATTR_INKSCAPE_PAGEOPACITY, + SP_ATTR_INKSCAPE_PAGESHADOW, + SP_ATTR_INKSCAPE_ZOOM, + SP_ATTR_INKSCAPE_CX, + SP_ATTR_INKSCAPE_CY, + SP_ATTR_INKSCAPE_WINDOW_WIDTH, + SP_ATTR_INKSCAPE_WINDOW_HEIGHT, + SP_ATTR_INKSCAPE_WINDOW_X, + SP_ATTR_INKSCAPE_WINDOW_Y, + SP_ATTR_INKSCAPE_GRID_BBOX, + SP_ATTR_INKSCAPE_GUIDE_BBOX, + SP_ATTR_INKSCAPE_OBJECT_BBOX, + SP_ATTR_INKSCAPE_GRID_POINTS, + SP_ATTR_INKSCAPE_GUIDE_POINTS, + SP_ATTR_INKSCAPE_OBJECT_POINTS, + SP_ATTR_INKSCAPE_OBJECT_PATHS, + SP_ATTR_INKSCAPE_OBJECT_NODES, + SP_ATTR_INKSCAPE_CURRENT_LAYER, + SP_ATTR_INKSCAPE_DOCUMENT_UNITS, + /* SPGuide */ + SP_ATTR_ORIENTATION, + SP_ATTR_POSITION, + /* SPImage */ + SP_ATTR_X, + SP_ATTR_Y, + /* SPPath */ + SP_ATTR_D, + SP_ATTR_CONNECTOR_TYPE, + SP_ATTR_CONNECTION_START, + SP_ATTR_CONNECTION_END, + /* SPRect */ + SP_ATTR_RX, + SP_ATTR_RY, + /* SPEllipse */ + SP_ATTR_R, + SP_ATTR_CX, + SP_ATTR_CY, + SP_ATTR_SODIPODI_CX, + SP_ATTR_SODIPODI_CY, + SP_ATTR_SODIPODI_RX, + SP_ATTR_SODIPODI_RY, + SP_ATTR_SODIPODI_START, + SP_ATTR_SODIPODI_END, + SP_ATTR_SODIPODI_OPEN, + /* SPStar */ + SP_ATTR_SODIPODI_SIDES, + SP_ATTR_SODIPODI_R1, + SP_ATTR_SODIPODI_R2, + SP_ATTR_SODIPODI_ARG1, + SP_ATTR_SODIPODI_ARG2, + SP_ATTR_INKSCAPE_FLATSIDED, + SP_ATTR_INKSCAPE_ROUNDED, + SP_ATTR_INKSCAPE_RANDOMIZED, + /* SPSpiral */ + SP_ATTR_SODIPODI_EXPANSION, + SP_ATTR_SODIPODI_REVOLUTION, + SP_ATTR_SODIPODI_RADIUS, + SP_ATTR_SODIPODI_ARGUMENT, + SP_ATTR_SODIPODI_T0, + /* SPOffset */ + SP_ATTR_SODIPODI_ORIGINAL, + SP_ATTR_INKSCAPE_ORIGINAL, + SP_ATTR_INKSCAPE_HREF, + SP_ATTR_INKSCAPE_RADIUS, + /* SPLine */ + SP_ATTR_X1, + SP_ATTR_Y1, + SP_ATTR_X2, + SP_ATTR_Y2, + /* SPPolyline */ + SP_ATTR_POINTS, + /* SPTSpan */ + SP_ATTR_DX, + SP_ATTR_DY, + SP_ATTR_ROTATE, + SP_ATTR_SODIPODI_ROLE, + /* SPText */ + SP_ATTR_SODIPODI_LINESPACING, + /* SPTextPath */ + SP_ATTR_STARTOFFSET, + /* SPStop */ + SP_ATTR_OFFSET, + /* SPGradient */ + SP_ATTR_GRADIENTUNITS, + SP_ATTR_GRADIENTTRANSFORM, + SP_ATTR_SPREADMETHOD, + /* SPRadialGradient */ + SP_ATTR_FX, + SP_ATTR_FY, + /* SPPattern */ + SP_ATTR_PATTERNUNITS, + SP_ATTR_PATTERNCONTENTUNITS, + SP_ATTR_PATTERNTRANSFORM, + /* SPClipPath */ + SP_ATTR_CLIPPATHUNITS, + /* SPMask */ + SP_ATTR_MASKUNITS, + SP_ATTR_MASKCONTENTUNITS, + /* SPMarker */ + SP_ATTR_MARKERUNITS, + SP_ATTR_REFX, + SP_ATTR_REFY, + SP_ATTR_MARKERWIDTH, + SP_ATTR_MARKERHEIGHT, + SP_ATTR_ORIENT, + /* SPStyleElem */ + SP_ATTR_TYPE, + /* Animations */ + SP_ATTR_ATTRIBUTENAME, + SP_ATTR_ATTRIBUTETYPE, + SP_ATTR_BEGIN, + SP_ATTR_DUR, + SP_ATTR_END, + SP_ATTR_MIN, + SP_ATTR_MAX, + SP_ATTR_RESTART, + SP_ATTR_REPEATCOUNT, + SP_ATTR_REPEATDUR, + /* Interpolating animations */ + SP_ATTR_CALCMODE, + SP_ATTR_VALUES, + SP_ATTR_KEYTIMES, + SP_ATTR_KEYSPLINES, + SP_ATTR_FROM, + SP_ATTR_TO, + SP_ATTR_BY, + SP_ATTR_ADDITIVE, + SP_ATTR_ACCUMULATE, + + /* XML */ + SP_ATTR_XML_SPACE, + + /* typeset */ + SP_ATTR_TEXT_NOMARKUP, + SP_ATTR_TEXT_PANGOMARKUP, + SP_ATTR_TEXT_INSHAPE, + SP_ATTR_TEXT_ONPATH, + SP_ATTR_TEXT_INBOX, + SP_ATTR_TEXT_INCOLUMN, + SP_ATTR_TEXT_EXCLUDE, + SP_ATTR_LAYOUT_OPTIONS, + + /* CSS2 */ + /* Font */ + SP_PROP_FONT, + SP_PROP_FONT_FAMILY, + SP_PROP_FONT_SIZE, + SP_PROP_FONT_SIZE_ADJUST, + SP_PROP_FONT_STRETCH, + SP_PROP_FONT_STYLE, + SP_PROP_FONT_VARIANT, + SP_PROP_FONT_WEIGHT, + /* Text */ + SP_PROP_TEXT_INDENT, + SP_PROP_TEXT_ALIGN, + SP_PROP_TEXT_DECORATION, + SP_PROP_LINE_HEIGHT, + SP_PROP_LETTER_SPACING, + SP_PROP_WORD_SPACING, + SP_PROP_TEXT_TRANSFORM, + /* text (css3) */ + SP_PROP_DIRECTION, + SP_PROP_BLOCK_PROGRESSION, + SP_PROP_WRITING_MODE, + SP_PROP_UNICODE_BIDI, + SP_PROP_ALIGNMENT_BASELINE, + SP_PROP_BASELINE_SHIFT, + SP_PROP_DOMINANT_BASELINE, + SP_PROP_GLYPH_ORIENTATION_HORIZONTAL, + SP_PROP_GLYPH_ORIENTATION_VERTICAL, + SP_PROP_KERNING, + SP_PROP_TEXT_ANCHOR, + /* Misc */ + SP_PROP_CLIP, + SP_PROP_COLOR, + SP_PROP_CURSOR, + SP_PROP_DISPLAY, + SP_PROP_OVERFLOW, + SP_PROP_VISIBILITY, + /* SVG */ + /* Clip/Mask */ + SP_PROP_CLIP_PATH, + SP_PROP_CLIP_RULE, + SP_PROP_MASK, + SP_PROP_OPACITY, + /* Filter */ + SP_PROP_ENABLE_BACKGROUND, + SP_PROP_FILTER, + SP_PROP_FLOOD_COLOR, + SP_PROP_FLOOD_OPACITY, + SP_PROP_LIGHTING_COLOR, + /* Gradient */ + SP_PROP_STOP_COLOR, + SP_PROP_STOP_OPACITY, + /* Interactivity */ + SP_PROP_POINTER_EVENTS, + /* Paint */ + SP_PROP_COLOR_INTERPOLATION, + SP_PROP_COLOR_INTERPOLATION_FILTERS, + SP_PROP_COLOR_PROFILE, + SP_PROP_COLOR_RENDERING, + SP_PROP_FILL, + SP_PROP_FILL_OPACITY, + SP_PROP_FILL_RULE, + SP_PROP_IMAGE_RENDERING, + SP_PROP_MARKER, + SP_PROP_MARKER_END, + SP_PROP_MARKER_MID, + SP_PROP_MARKER_START, + SP_PROP_SHAPE_RENDERING, + SP_PROP_STROKE, + SP_PROP_STROKE_DASHARRAY, + SP_PROP_STROKE_DASHOFFSET, + SP_PROP_STROKE_LINECAP, + SP_PROP_STROKE_LINEJOIN, + SP_PROP_STROKE_MITERLIMIT, + SP_PROP_STROKE_OPACITY, + SP_PROP_STROKE_WIDTH, + SP_PROP_TEXT_RENDERING +}; + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/bad-uri-exception.h b/src/bad-uri-exception.h new file mode 100644 index 000000000..322b9ae58 --- /dev/null +++ b/src/bad-uri-exception.h @@ -0,0 +1,34 @@ +#ifndef SEEN_BAD_URI_EXCEPTION_H +#define SEEN_BAD_URI_EXCEPTION_H + +#include + +namespace Inkscape { + +class BadURIException : public std::exception {}; + +class UnsupportedURIException : public BadURIException { +public: + char const *what() const throw() { return "Unsupported URI"; } +}; + +class MalformedURIException : public BadURIException { +public: + char const *what() const throw() { return "Malformed URI"; } +}; + +} + + +#endif /* !SEEN_BAD_URI_EXCEPTION_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 : diff --git a/src/brokenimage.xpm b/src/brokenimage.xpm new file mode 100644 index 000000000..39d23548e --- /dev/null +++ b/src/brokenimage.xpm @@ -0,0 +1,88 @@ +/* XPM */ +static char * brokenimage_xpm[] = { +"64 64 21 1", +" c None", +". c #FAB1BE", +"+ c #FDD2D8", +"@ c #FEFBFA", +"# c #F98FA1", +"$ c #F66F85", +"% c #F54F6A", +"& c #F32D4E", +"* c #F01237", +"= c #9F6F77", +"- c #9F8F92", +"; c #D0CAC9", +"> c #110609", +", c #4C3237", +"' c #594E4F", +") c #BAAFB1", +"! c #9C2D40", +"~ c #727172", +"{ c #580F1B", +"] c #950E24", +"^ c #A54E5D", +".+.++@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+++##$%%%&%$", +"##..+++@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+..#$%&&*&&%", +"$$$#..++@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+++..#$%%&***&%", +"&%%$#.+++@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+++##%%&&*****%", +"*&&%$$..+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@++++.##$%&******&%", +"**&&%$#.++@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@++++..#$%%&&*****&&%", +"****&%%#..+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+@++.##$$%%&&*****&&%$", +"****&&%$#.++@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@++..#$$%%%&&*****&&%$#", +"*****&&%##.++@@@@@@@@@@@@@@@@@@@@@@@@@@@++..##$%%&&********&%$#.", +"******&%%#=---;@@@@@@@@@@@@@@@@@@;+@@@+++..#$%%&&&*******&%%$$..", +"&******&%$>,,'>,@@@@@@@@@@@@@@@@@,)@@+++.##$%%&*********&&%$#..+", +"%&******&&>#..;>)@@@@@@@@@@@@@@@@>;+++..#$%%&&*********&&%$$.+++", +"%%&*****&!>$$..>)@';'~@@;,>>~@@@;>+++='#$%{>>>]***{*{>>{$$#..+@@", +"$%&&*****{{%$#,>++>,~+@->;@;>'@@-'+.=>$%%>{**]>]**>{]%^>!#.+++@@", +"#$%&*****{{{{>>#+->)@@)>@@@@;>)+''.=>%%%>]&***]{*]>&%%$^>.++@@@@", +".#$%&&***>]]!,>^#~'+@@>-@@@@@>-+,=$>^%&*>******>&!{$$#.=>+++@@@@", +"+.#$%&***>**&&!>#,~+@)>@@@@@+>-.>^>!&&&{>>>>>>>>&,!##..~,@@@@@@@", +"+..$$%&*]>****!>${=++->@@@@+->.$>>{>&**{{****&&%$>=.+++''@@@@@@@", +"@+..#$%&]{****{{%>$.+)>;@@++>'#^>!&>]**]>**&&%>$#>).++@>)@@@@@@@", +"@++.##$%{{]]]>{*!>$#.+'>-@-,,#${{**]>***>{]!!>^#=>++@@@>;@@@@@@@", +"@@@+..$$!{{{{]***{%$#++~'>,=#%%!]***{]**&{>{,$..)=+@@@;'@@@@@@@@", +"@@@++..#$%&******&%$#.+++..#$%&&******&&%%$##..++@@@@@@@@@@@@@@@", +"@@@@@+.##%%&******&%$#...##%%&&******&&%$$#..++@@@@@@@@@@@@@@@@@", +"@@@@@@+..$$%&******%%$###$$%&*******&%%$##.+++@+@@@@@@@@@@@@@@@@", +"@@@@@+@+.##%%&****&&%$$$$$%&&*****&&%%$#..++@@@@@@@@@@@@@@@@@@@@", +"@@@@@@+@+.#$%&&*****&%%%%%&&******&%%##.+++@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@++.#$%&*****&&&%&&*******&%$$..++@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@++.$%&******&*&*******&&%$$..++@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@++.#$&&**************&&%$#..+@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@++#$%***************&%$$..+@+@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@++.#%&*************&%%#..++@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@+.#%%*************%%$#.+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@++.#%%************&%$#.++@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@;;..#%&************&%$#.++@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@>;.#$%%************&%%#.++@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@;>..#$&&*************%%$#.++@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@++-,.^!!>>{**{>>]****]{>>!#,++++~>>,@~;@@+'>>,@@@@@@@@@@@@", +"@@@@@@++',#^>{**>{>]*]>]**{>]*&,{>.+-,-@;',>+@)>-@@'>+@@@@@@@@@@", +"@@@@+++.,^${>&**]>]**&{{*]>***&&>,$#>)+@@@,>@@>@@@@@'-@@@@@@@@@@", +"@@@@++.#>^%{]***]>*&&&{{*>]*****{{$',.++@@-'@~~@@@@@-'@@@@@@@@@@", +"@@++..#$>&&>****]{&&%%>!!>******{!%>^..++@~'@>>>>>>>>'@@@@@@@@@@", +"@++..$$!>&*>****{{%$$$>=!>&&****>]&>^##.++>-@>)@@@@@@@@@@@@@@@@@", +"++..$$%!{*]>****{!%$#=>#=>!&***{>*&>]$$#.~>)@,~@@@@-,@@@@@@@@@@@", +"+.##%%&{]*{{***&>%##.=,.#,>!&]{>>**]>!%^,,>++->-;)''+@@@@@@@@@@@", +".#$$%&*]]*]**&&%,$..+~=++.^>>{]*]***]{>{!^,..+)'>'-@@@@@@@@@@@@@", +"#$%&&*******&%%$##+++@@++..#$%&***]>**&&%>!#..+++@@@@@@@@@@@@@@@", +"$%%&******&&%%$#..+@@@@@++..#$%&***>{**!>{%$$#..++@@@@@@@@@@@@@@", +"%&&*******&%$##.++@+@@@@@@+..$$%&***{>>{]&&%%$##.;++@@@@@@@@@@@@", +"&********&%$$#+++@@@@@@@@@++..#$%&&********&&%%$#..++@@@@@@@@@@@", +"&******&&%$$..+++@@@@@@@@@@@+..#$%&&********&&%%$$..+++@@@@@@@@@", +"******&&%$#..++@@@@@@@@@@@@@+++.#$%%&&********&&%$$#.+++@@@@@@@@", +"*****&%%$##++@@@@@@@@@@@@@@@@+++..$$%%&*********&%$$#.+++@@@@@@@", +"****&%%$#.+++@@@@@@@@@@@@@@@@@+++.##$%%&&&*******&&%$##+++@@@@@@", +"**&&%%##.+++@@@@@@@@@@@@@@@@@@@@+++.##$%%&&*******&&%$$.+++@@@@@", +"*&&%$$#.++@@@@@@@@@@@@@@@@@@@@@@@+++..#$$%%&*******&&%$#.+@@@@@@", +"&%%$#..++@@@@@@@@@@@@@@@@@@@@@@@@@@+++.##$%%&&*******&%$#.++@@@@", +"$$$#..++@@@@@@@@@@@@@@@@@@@@@@@@@@@@+++..#$%%&********%$#++@@@@@", +"##..+++@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+++##%%&*******&$#.++@@@@", +"..+++@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+++.##$%&&****&%$#++@@@@@", +"++++@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+@+.##$%%&&*&&%##+++@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+++.#$$%%%%$$#.+@@@@@@", +"+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+++..##$$$#..+++@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@++++...#...++@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+++++.+++++@@@@@@@"}; diff --git a/src/check-header-compile.in b/src/check-header-compile.in new file mode 100755 index 000000000..6d0e10789 --- /dev/null +++ b/src/check-header-compile.in @@ -0,0 +1,41 @@ +#! /bin/sh +# Check that each .h file has all the includes it needs. + +# Probably requires gnu find (for -printf '%P\n'). + +# This script hereby placed into the public domain. + +set -e +mydir=`dirname "$0"` +cd "$mydir" +srcdir="@srcdir@" +CXX="@CXX@" +INKSCAPE_CFLAGS="@INKSCAPE_CFLAGS@" +OBJEXT="@OBJEXT@" +config_h_dir=.. + +check_compile () { + (echo "#include "; echo "#include <$1>"; echo "int header_tst_dummy;") > header-tst.cpp + $CXX -c -I. -I"$srcdir" -I$config_h_dir $INKSCAPE_CFLAGS header-tst.cpp +} + +if [ $# = 0 ]; then + for i in `find "$srcdir" \ + -name bonobo -prune \ + -o -name ecma -prune \ + -o -name render -prune \ + -o -name xpath -prune \ + -o -path '*/extension/script/js' -prune \ + -o -name '*.h' \ + \! -name gnome.h \! -name win32.h \! -name nr-type-gnome.h \! -name nr-type-w32.h \! -name Livarot.h \! -name radial.h \ + \! -name '*-test.h' \ + -printf '%P\n'` + do + check_compile "$i" + done +else + for i in "$@"; do + check_compile "$i" + done +fi +rm header-tst.cpp header-tst.$OBJEXT diff --git a/src/color-rgba.h b/src/color-rgba.h new file mode 100644 index 000000000..24bc63b52 --- /dev/null +++ b/src/color-rgba.h @@ -0,0 +1,194 @@ +/** \file color-rgba.h + + A class to handle a RGBA color as one unit. + + Authors: + bulia byak + + Copyright (C) 2004 Authors + + Released under GNU GPL, read the file 'COPYING' for more information +*/ +#ifndef SEEN_COLOR_RGBA_H +#define SEEN_COLOR_RGBA_H + +#include +#include "libnr/nr-pixops.h" +#include "decimal-round.h" + +/** + \brief A class to contain a floating point RGBA color. +*/ +class ColorRGBA { +public: + /** + \brief A constructor to create the color from four floating + point values. + \param c0 Red + \param c1 Green + \param c2 Blue + \param c3 Alpha + + Load the values into the array of floats in this object. + */ + ColorRGBA(float c0, float c1, float c2, float c3) + { + _c[0] = c0; _c[1] = c1; + _c[2] = c2; _c[3] = c3; + } + + /** + \brief Create a quick ColorRGBA with all zeros + */ + ColorRGBA(void) + { + for (int i = 0; i < 4; i++) + _c[i] = 0.0; + } + + /** + \brief A constructor to create the color from an unsigned + int, as found everywhere when dealing with colors + \param intcolor rgba32 "unsigned int representation (0xRRGGBBAA) + + Separate the values and load them into the array of floats in this object. + TODO : maybe get rid of the NR_RGBA32_x C-style functions and replace + the calls with the bitshifting they do + */ + ColorRGBA(unsigned int intcolor) + { + _c[0] = NR_RGBA32_R(intcolor)/255.0; + _c[1] = NR_RGBA32_G(intcolor)/255.0; + _c[2] = NR_RGBA32_B(intcolor)/255.0; + _c[3] = NR_RGBA32_A(intcolor)/255.0; + + } + + /** + \brief Create a ColorRGBA using an array of floats + \param in_array The values to be placed into the object + + Go through each entry in the array and put it into \c _c. + */ + ColorRGBA(float in_array[4]) + { + for (int i = 0; i < 4; i++) + _c[i] = in_array[i]; + } + + /** + \brief Overwrite the values in this object with another \c ColorRGBA. + \param m Values to use for the array + \return This ColorRGBA object + + Copy all the values from \c m into \c this. + */ + ColorRGBA &operator=(ColorRGBA const &m) { + for (unsigned i = 0 ; i < 4 ; ++i) { + _c[i] = m._c[i]; + } + return *this; + } + + /** + \brief Grab a particular value from the ColorRGBA object + \param i Which value to grab + \return The requested value. + + First checks to make sure that the value is within the array, + and then return the value if it is. + */ + float operator[](unsigned int const i) const { + g_assert( unsigned(i) < 4 ); + return _c[i]; + } + + /** + \brief Check to ensure that two \c ColorRGBA's are equal + \param other The guy to check against + \return Whether or not they are equal + + Check each value to see if they are equal. If they all are, + return TRUE. + */ + bool operator== (const ColorRGBA other) const { + for (int i = 0; i < 4; i++) { + if (_c[i] != other[i]) + return FALSE; + } + return TRUE; + } + + /** + \brief Average two \c ColorRGBAs to create another one. + \param second The second RGBA, with this being the first + \param weight How much of each should be used. Zero is all + this while one is all the second. Default is + half and half. + + This function goes through all the points in the two objects and + merges them together based on the weighting. The current objects + value are multiplied by 1.0 - weight and the second object by weight. + This means that they should always be balanced by the parameter. + */ + ColorRGBA average (const ColorRGBA second, const float weight = 0.5) const { + float returnval[4]; + + for (int i = 0; i < 4; i++) { + returnval[i] = _c[i] * (1.0 - weight) + second[i] * weight; + } + + return ColorRGBA(returnval[0], returnval[1], returnval[2], returnval[3]); + } + + /** + \brief Create a ColorRGBA with the inverse color of the current ColorRGBA + + do 1 minus each color components (but not the alpha) and put it into \c _c. + */ + ColorRGBA getInverse() const { + return ColorRGBA( (1.0 - _c[0]), (1.0 - _c[1]), (1.0 - _c[2]), _c[3] ); + } + + /** + \brief Create a ColorRGBA with the inverse color of a given ColorRGBA + + do 1 minus each color components (but not the alpha) and put it into \c _c. + */ + ColorRGBA getInverse(const ColorRGBA ref) const { + return getInverse(ref); + } + + /** + \brief Give the rgba32 "unsigned int" representation of the color + + round each components*255 and combine them (RRGGBBAA). + WARNING : this reduces color precision (from float to 0->255 int per component) + but it should be expected since we request this kind of output + */ + unsigned int getIntValue() const { + + return (int(Inkscape::decimal_round(_c[0]*255, 0)) << 24) | + (int(Inkscape::decimal_round(_c[1]*255, 0)) << 16) | + (int(Inkscape::decimal_round(_c[2]*255, 0)) << 8) | + (int(Inkscape::decimal_round(_c[3]*255, 0))); + } + +private: + /** \brief Array of values that are stored. */ + float _c[4]; +}; + + +#endif /* !SEEN_COLOR_RGBA_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 : diff --git a/src/color.cpp b/src/color.cpp new file mode 100644 index 000000000..c968d334f --- /dev/null +++ b/src/color.cpp @@ -0,0 +1,468 @@ +#define __SP_COLOR_C__ + +/** \file + * Colors and colorspaces. + * + * Author: + * Lauris Kaplinski + * bulia byak + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include "color.h" + +/// A color space is just a name. +struct SPColorSpace { + gchar const *name; +}; + +static SPColorSpace const RGB = {"RGB"}; +static SPColorSpace const CMYK = {"CMYK"}; + +/** + * Returns one of three values depending on if color is valid and if it + * has a valid color space. Likely redundant as soon as this is C++. + */ +SPColorSpaceClass +sp_color_get_colorspace_class(SPColor const *color) +{ + g_return_val_if_fail (color != NULL, SP_COLORSPACE_CLASS_INVALID); + + if (color->colorspace == &RGB) return SP_COLORSPACE_CLASS_PROCESS; + if (color->colorspace == &CMYK) return SP_COLORSPACE_CLASS_PROCESS; + + return SP_COLORSPACE_CLASS_UNKNOWN; +} + +/** + * Returns the SPColorSpaceType corresponding to color's color space. + * \todo color->colorspace should simply be a named enum. + */ +SPColorSpaceType +sp_color_get_colorspace_type(SPColor const *color) +{ + g_return_val_if_fail (color != NULL, SP_COLORSPACE_TYPE_INVALID); + + if (color->colorspace == &RGB) return SP_COLORSPACE_TYPE_RGB; + if (color->colorspace == &CMYK) return SP_COLORSPACE_TYPE_CMYK; + + return SP_COLORSPACE_TYPE_UNKNOWN; +} + +/** + * Shallow copy of color struct with NULL check. + */ +void +sp_color_copy(SPColor *dst, SPColor const *src) +{ + g_return_if_fail (dst != NULL); + g_return_if_fail (src != NULL); + + *dst = *src; +} + +/** + * Returns TRUE if c0 or c1 is NULL, or if colors/opacities differ. + */ +gboolean +sp_color_is_equal (SPColor const *c0, SPColor const *c1) +{ + g_return_val_if_fail (c0 != NULL, TRUE); + g_return_val_if_fail (c1 != NULL, TRUE); + + if (c0->colorspace != c1->colorspace) return FALSE; + if (c0->v.c[0] != c1->v.c[0]) return FALSE; + if (c0->v.c[1] != c1->v.c[1]) return FALSE; + if (c0->v.c[2] != c1->v.c[2]) return FALSE; + if ((c0->colorspace == &CMYK) && (c0->v.c[3] != c1->v.c[3])) return FALSE; + + return TRUE; +} + +/** + * Returns TRUE if no RGB value differs epsilon or more in both colors, + * with CMYK aditionally comparing opacity, or if c0 or c1 is NULL. + * \note Do we want the latter? + */ +gboolean +sp_color_is_close (SPColor const *c0, SPColor const *c1, float epsilon) +{ + g_return_val_if_fail (c0 != NULL, TRUE); + g_return_val_if_fail (c1 != NULL, TRUE); + + if (c0->colorspace != c1->colorspace) return FALSE; + if (fabs ((c0->v.c[0]) - (c1->v.c[0])) >= epsilon) return FALSE; + if (fabs ((c0->v.c[1]) - (c1->v.c[1])) >= epsilon) return FALSE; + if (fabs ((c0->v.c[2]) - (c1->v.c[2])) >= epsilon) return FALSE; + if ((c0->colorspace == &CMYK) && (fabs ((c0->v.c[3]) - (c1->v.c[3])) >= epsilon)) return FALSE; + + return TRUE; +} + +/** + * Sets RGB values and colorspace in color. + * \pre color != NULL && 0 <={r,g,b}<=1 + */ +void +sp_color_set_rgb_float(SPColor *color, float r, float g, float b) +{ + g_return_if_fail(color != NULL); + g_return_if_fail(r >= 0.0); + g_return_if_fail(r <= 1.0); + g_return_if_fail(g >= 0.0); + g_return_if_fail(g <= 1.0); + g_return_if_fail(b >= 0.0); + g_return_if_fail(b <= 1.0); + + color->colorspace = &RGB; + color->v.c[0] = r; + color->v.c[1] = g; + color->v.c[2] = b; + color->v.c[3] = 0; +} + +/** + * Converts 32bit value to RGB floats and sets color. + * \pre color != NULL + */ +void +sp_color_set_rgb_rgba32(SPColor *color, guint32 value) +{ + g_return_if_fail (color != NULL); + + color->colorspace = &RGB; + color->v.c[0] = (value >> 24) / 255.0F; + color->v.c[1] = ((value >> 16) & 0xff) / 255.0F; + color->v.c[2] = ((value >> 8) & 0xff) / 255.0F; + color->v.c[3] = 0; +} + +/** + * Sets CMYK values and colorspace in color. + * \pre color != NULL && 0 <={c,m,y,k}<=1 + */ +void +sp_color_set_cmyk_float(SPColor *color, float c, float m, float y, float k) +{ + g_return_if_fail(color != NULL); + g_return_if_fail(c >= 0.0); + g_return_if_fail(c <= 1.0); + g_return_if_fail(m >= 0.0); + g_return_if_fail(m <= 1.0); + g_return_if_fail(y >= 0.0); + g_return_if_fail(y <= 1.0); + g_return_if_fail(k >= 0.0); + g_return_if_fail(k <= 1.0); + + color->colorspace = &CMYK; + color->v.c[0] = c; + color->v.c[1] = m; + color->v.c[2] = y; + color->v.c[3] = k; +} + +/** + * Convert SPColor with integer alpha value to 32bit RGBA value. + * \pre color != NULL && alpha < 256 + */ +guint32 +sp_color_get_rgba32_ualpha(SPColor const *color, guint32 alpha) +{ + guint32 rgba; + + g_return_val_if_fail (color != NULL, 0x0); + g_return_val_if_fail (alpha <= 0xff, 0x0); + + if (color->colorspace == &RGB) { + rgba = SP_RGBA32_U_COMPOSE(SP_COLOR_F_TO_U(color->v.c[0]), + SP_COLOR_F_TO_U(color->v.c[1]), + SP_COLOR_F_TO_U(color->v.c[2]), + alpha); + } else { + float rgb[3]; + sp_color_get_rgb_floatv (color, rgb); + rgba = SP_RGBA32_U_COMPOSE(SP_COLOR_F_TO_U(rgb[0]), + SP_COLOR_F_TO_U(rgb[1]), + SP_COLOR_F_TO_U(rgb[2]), + alpha); + } + + return rgba; +} + +/** + * Convert SPColor with float alpha value to 32bit RGBA value. + * \pre color != NULL && 0 <= alpha <= 1 + */ +guint32 +sp_color_get_rgba32_falpha(SPColor const *color, float alpha) +{ + g_return_val_if_fail(color != NULL, 0x0); + g_return_val_if_fail(alpha >= 0.0, 0x0); + g_return_val_if_fail(alpha <= 1.0, 0x0); + + return sp_color_get_rgba32_ualpha(color, SP_COLOR_F_TO_U(alpha)); +} + +/** + * Fill rgb float array with values from SPColor. + * \pre color != NULL && rgb != NULL && rgb[0-2] is meaningful + */ +void +sp_color_get_rgb_floatv(SPColor const *color, float *rgb) +{ + g_return_if_fail (color != NULL); + g_return_if_fail (rgb != NULL); + + if (color->colorspace == &RGB) { + rgb[0] = color->v.c[0]; + rgb[1] = color->v.c[1]; + rgb[2] = color->v.c[2]; + } else if (color->colorspace == &CMYK) { + sp_color_cmyk_to_rgb_floatv(rgb, + color->v.c[0], + color->v.c[1], + color->v.c[2], + color->v.c[3]); + } +} + +/** + * Fill cmyk float array with values from SPColor. + * \pre color != NULL && cmyk != NULL && cmyk[0-3] is meaningful + */ +void +sp_color_get_cmyk_floatv(SPColor const *color, float *cmyk) +{ + g_return_if_fail (color != NULL); + g_return_if_fail (cmyk != NULL); + + if (color->colorspace == &CMYK) { + cmyk[0] = color->v.c[0]; + cmyk[1] = color->v.c[1]; + cmyk[2] = color->v.c[2]; + cmyk[3] = color->v.c[3]; + } else if (color->colorspace == &RGB) { + sp_color_rgb_to_cmyk_floatv(cmyk, + color->v.c[0], + color->v.c[1], + color->v.c[2]); + } +} + +/* Plain mode helpers */ + +/** + * Fill hsv float array from r,g,b float values. + */ +void +sp_color_rgb_to_hsv_floatv (float *hsv, float r, float g, float b) +{ + float max, min, delta; + + max = MAX (MAX (r, g), b); + min = MIN (MIN (r, g), b); + delta = max - min; + + hsv[2] = max; + + if (max > 0) { + hsv[1] = delta / max; + } else { + hsv[1] = 0.0; + } + + if (hsv[1] != 0.0) { + if (r == max) { + hsv[0] = (g - b) / delta; + } else if (g == max) { + hsv[0] = 2.0 + (b - r) / delta; + } else { + hsv[0] = 4.0 + (r - g) / delta; + } + + hsv[0] = hsv[0] / 6.0; + + if (hsv[0] < 0) hsv[0] += 1.0; + } +} + +/** + * Fill rgb float array from h,s,v float values. + */ +void +sp_color_hsv_to_rgb_floatv (float *rgb, float h, float s, float v) +{ + gdouble f, w, q, t, d; + + d = h * 5.99999999; + f = d - floor (d); + w = v * (1.0 - s); + q = v * (1.0 - (s * f)); + t = v * (1.0 - (s * (1.0 - f))); + + if (d < 1.0) { + *rgb++ = v; + *rgb++ = t; + *rgb++ = w; + } else if (d < 2.0) { + *rgb++ = q; + *rgb++ = v; + *rgb++ = w; + } else if (d < 3.0) { + *rgb++ = w; + *rgb++ = v; + *rgb++ = t; + } else if (d < 4.0) { + *rgb++ = w; + *rgb++ = q; + *rgb++ = v; + } else if (d < 5.0) { + *rgb++ = t; + *rgb++ = w; + *rgb++ = v; + } else { + *rgb++ = v; + *rgb++ = w; + *rgb++ = q; + } +} + +/** + * Fill hsl float array from r,g,b float values. + */ +void +sp_color_rgb_to_hsl_floatv (float *hsl, float r, float g, float b) +{ + float max = MAX (MAX (r, g), b); + float min = MIN (MIN (r, g), b); + float delta = max - min; + + hsl[2] = (max + min)/2.0; + + if (delta == 0) { + hsl[0] = 0; + hsl[1] = 0; + } else { + if (hsl[2] <= 0.5) + hsl[1] = delta / (max + min); + else + hsl[1] = delta / (2 - max - min); + + if (r == max) hsl[0] = (g - b) / delta; + else if (g == max) hsl[0] = 2.0 + (b - r) / delta; + else if (b == max) hsl[0] = 4.0 + (r - g) / delta; + + hsl[0] = hsl[0] / 6.0; + + if (hsl[0] < 0) hsl[0] += 1; + if (hsl[0] > 1) hsl[0] -= 1; + } +} + +float +hue_2_rgb (float v1, float v2, float h) +{ + if (h < 0) h += 6.0; + if (h > 6) h -= 6.0; + + if (h < 1) return v1 + (v2 - v1) * h; + if (h < 3) return v2; + if (h < 4) return v1 + (v2 - v1) * (4 - h); + return v1; +} + +/** + * Fill rgb float array from h,s,l float values. + */ +void +sp_color_hsl_to_rgb_floatv (float *rgb, float h, float s, float l) +{ + if (s == 0) { + rgb[0] = l; + rgb[1] = l; + rgb[2] = l; + } else { + float v2; + if (l < 0.5) { + v2 = l * (1 + s); + } else { + v2 = l + s - l*s; + } + float v1 = 2*l - v2; + + rgb[0] = hue_2_rgb (v1, v2, h*6 + 2.0); + rgb[1] = hue_2_rgb (v1, v2, h*6); + rgb[2] = hue_2_rgb (v1, v2, h*6 - 2.0); + } +} + +/** + * Fill cmyk float array from r,g,b float values. + */ +void +sp_color_rgb_to_cmyk_floatv (float *cmyk, float r, float g, float b) +{ + float c, m, y, k, kd; + + c = 1.0 - r; + m = 1.0 - g; + y = 1.0 - b; + k = MIN (MIN (c, m), y); + + c = c - k; + m = m - k; + y = y - k; + + kd = 1.0 - k; + + if (kd > 1e-9) { + c = c / kd; + m = m / kd; + y = y / kd; + } + + cmyk[0] = c; + cmyk[1] = m; + cmyk[2] = y; + cmyk[3] = k; +} + +/** + * Fill rgb float array from c,m,y,k float values. + */ +void +sp_color_cmyk_to_rgb_floatv (float *rgb, float c, float m, float y, float k) +{ + float kd; + + kd = 1.0 - k; + + c = c * kd; + m = m * kd; + y = y * kd; + + c = c + k; + m = m + k; + y = y + k; + + rgb[0] = 1.0 - c; + rgb[1] = 1.0 - m; + rgb[2] = 1.0 - y; +} + +/* + 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 : diff --git a/src/color.h b/src/color.h new file mode 100644 index 000000000..2c3f91fe7 --- /dev/null +++ b/src/color.h @@ -0,0 +1,95 @@ +#ifndef __SP_COLOR_H__ +#define __SP_COLOR_H__ + +/** \file + * Colors and colorspaces + * + * Author: + * Lauris Kaplinski + * bulia byak + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +struct SPColorSpace; + +/* Useful composition macros */ + +#define SP_RGBA32_R_U(v) (((v) >> 24) & 0xff) +#define SP_RGBA32_G_U(v) (((v) >> 16) & 0xff) +#define SP_RGBA32_B_U(v) (((v) >> 8) & 0xff) +#define SP_RGBA32_A_U(v) ((v) & 0xff) +#define SP_COLOR_U_TO_F(v) ((v) / 255.0) +#define SP_COLOR_F_TO_U(v) ((unsigned int) ((v) * 255.9999)) +#define SP_RGBA32_R_F(v) SP_COLOR_U_TO_F (SP_RGBA32_R_U (v)) +#define SP_RGBA32_G_F(v) SP_COLOR_U_TO_F (SP_RGBA32_G_U (v)) +#define SP_RGBA32_B_F(v) SP_COLOR_U_TO_F (SP_RGBA32_B_U (v)) +#define SP_RGBA32_A_F(v) SP_COLOR_U_TO_F (SP_RGBA32_A_U (v)) +#define SP_RGBA32_U_COMPOSE(r,g,b,a) ((((r) & 0xff) << 24) | (((g) & 0xff) << 16) | (((b) & 0xff) << 8) | ((a) & 0xff)) +#define SP_RGBA32_F_COMPOSE(r,g,b,a) SP_RGBA32_U_COMPOSE (SP_COLOR_F_TO_U (r), SP_COLOR_F_TO_U (g), SP_COLOR_F_TO_U (b), SP_COLOR_F_TO_U (a)) + +typedef enum { + SP_COLORSPACE_CLASS_INVALID, + SP_COLORSPACE_CLASS_NONE, + SP_COLORSPACE_CLASS_UNKNOWN, + SP_COLORSPACE_CLASS_PROCESS, + SP_COLORSPACE_CLASS_SPOT +} SPColorSpaceClass; + +typedef enum { + SP_COLORSPACE_TYPE_INVALID, + SP_COLORSPACE_TYPE_NONE, + SP_COLORSPACE_TYPE_UNKNOWN, + SP_COLORSPACE_TYPE_RGB, + SP_COLORSPACE_TYPE_CMYK +} SPColorSpaceType; + +/** + * An RGBA color in an SPColorSpace + */ +struct SPColor { + const SPColorSpace *colorspace; + union { + float c[4]; + } v; +}; + +SPColorSpaceClass sp_color_get_colorspace_class (const SPColor *color); +SPColorSpaceType sp_color_get_colorspace_type (const SPColor *color); + +void sp_color_copy (SPColor *dst, const SPColor *src); + +gboolean sp_color_is_equal (const SPColor *c0, const SPColor *c1); +gboolean sp_color_is_close (const SPColor *c0, const SPColor *c1, float epsilon); + +void sp_color_set_rgb_float (SPColor *color, float r, float g, float b); +void sp_color_set_rgb_rgba32 (SPColor *color, guint32 value); + +void sp_color_set_cmyk_float (SPColor *color, float c, float m, float y, float k); + +guint32 sp_color_get_rgba32_ualpha (const SPColor *color, guint32 alpha); +guint32 sp_color_get_rgba32_falpha (const SPColor *color, float alpha); + +void sp_color_get_rgb_floatv (const SPColor *color, float *rgb); +void sp_color_get_cmyk_floatv (const SPColor *color, float *cmyk); + +/* Plain mode helpers */ + +void sp_color_rgb_to_hsv_floatv (float *hsv, float r, float g, float b); +void sp_color_hsv_to_rgb_floatv (float *rgb, float h, float s, float v); + +void sp_color_rgb_to_hsl_floatv (float *hsl, float r, float g, float b); +void sp_color_hsl_to_rgb_floatv (float *rgb, float h, float s, float l); + +void sp_color_rgb_to_cmyk_floatv (float *cmyk, float r, float g, float b); +void sp_color_cmyk_to_rgb_floatv (float *rgb, float c, float m, float y, float k); + + + +#endif + diff --git a/src/composite-undo-stack-observer.cpp b/src/composite-undo-stack-observer.cpp new file mode 100644 index 000000000..04890711b --- /dev/null +++ b/src/composite-undo-stack-observer.cpp @@ -0,0 +1,136 @@ +/** + * Aggregates undo stack observers for convenient management and triggering in SPDocument + * + * Heavily inspired by Inkscape::XML::CompositeNodeObserver. + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + +#include "composite-undo-stack-observer.h" +#include "xml/event.h" + +namespace Inkscape { + +CompositeUndoStackObserver::CompositeUndoStackObserver() : _iterating(0) { } +CompositeUndoStackObserver::~CompositeUndoStackObserver() { } + +void +CompositeUndoStackObserver::add(UndoStackObserver& observer) +{ + if (!this->_iterating) { + this->_active.push_back(UndoStackObserverRecord(observer)); + } else { + this->_pending.push_back(UndoStackObserverRecord(observer)); + } +} + +void +CompositeUndoStackObserver::remove(UndoStackObserver& observer) +{ + if (!this->_iterating) { + // logical-or operator short-circuits + this->_remove_one(this->_active, observer) || this->_remove_one(this->_pending, observer); + } else { + this->_mark_one(this->_active, observer) || this->_mark_one(this->_pending, observer); + } +} + +void +CompositeUndoStackObserver::notifyUndoEvent(XML::Event* log) +{ + this->_lock(); + for(UndoObserverRecordList::iterator i = this->_active.begin(); i != _active.end(); ++i) { + if (!i->to_remove) { + i->issueUndo(log); + } + } + this->_unlock(); +} + +void +CompositeUndoStackObserver::notifyRedoEvent(XML::Event* log) +{ + + this->_lock(); + for(UndoObserverRecordList::iterator i = this->_active.begin(); i != _active.end(); ++i) { + if (!i->to_remove) { + i->issueRedo(log); + } + } + this->_unlock(); +} + +void +CompositeUndoStackObserver::notifyUndoCommitEvent(XML::Event* log) +{ + this->_lock(); + for(UndoObserverRecordList::iterator i = this->_active.begin(); i != _active.end(); ++i) { + if (!i->to_remove) { + i->issueUndoCommit(log); + } + } + this->_unlock(); +} + +bool +CompositeUndoStackObserver::_remove_one(UndoObserverRecordList& list, UndoStackObserver& o) +{ + UndoStackObserverRecord eq_comp(o); + + UndoObserverRecordList::iterator i = std::find_if(list.begin(), list.end(), std::bind1st(std::equal_to< UndoStackObserverRecord >(), eq_comp)); + + if (i != list.end()) { + list.erase(i); + return true; + } else { + return false; + } +} + +bool +CompositeUndoStackObserver::_mark_one(UndoObserverRecordList& list, UndoStackObserver& o) +{ + UndoStackObserverRecord eq_comp(o); + + UndoObserverRecordList::iterator i = std::find_if(list.begin(), list.end(), std::bind1st(std::equal_to< UndoStackObserverRecord >(), eq_comp)); + + if (i != list.end()) { + (*i).to_remove = true; + return true; + } else { + return false; + } +} + +void +CompositeUndoStackObserver::_unlock() +{ + if (!--this->_iterating) { + // Remove marked observers + UndoObserverRecordList::iterator i = this->_active.begin(); + for(; i != this->_active.begin(); i++) { + if (i->to_remove) { + this->_active.erase(i); + } + } + + i = this->_pending.begin(); + for(; i != this->_pending.begin(); i++) { + if (i->to_remove) { + this->_active.erase(i); + } + } + + // Merge pending and active + this->_active.insert(this->_active.end(), this->_pending.begin(), this->_pending.end()); + this->_pending.clear(); + } +} + +} diff --git a/src/composite-undo-stack-observer.h b/src/composite-undo-stack-observer.h new file mode 100644 index 000000000..908610135 --- /dev/null +++ b/src/composite-undo-stack-observer.h @@ -0,0 +1,165 @@ +/** + * Aggregates undo stack observers for management and triggering in SPDocument + * + * Heavily inspired by Inkscape::XML::CompositeNodeObserver. + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __COMPOSITE_UNDO_COMMIT_OBSERVER_H__ +#define __COMPOSITE_UNDO_COMMIT_OBSERVER_H__ + +#include "undo-stack-observer.h" + +#include + +namespace Inkscape { + +namespace XML { + +class Event; + +} + +class UndoStackObserver; + +/** + * Aggregates UndoStackObservers for management and triggering in an SPDocument's undo/redo + * system. + */ +class CompositeUndoStackObserver { +public: + + /** + * Structure for tracking UndoStackObservers. + */ + struct UndoStackObserverRecord { + public: + /** + * Constructor. + * + * \param o Reference to the UndoStackObserver that this UndoStackObserverRecord + * will track. + */ + UndoStackObserverRecord(UndoStackObserver& o) : to_remove(false), _observer(o) { } + bool to_remove; + + /** + * Overloaded equality test operator to facilitate usage of STL find algorithms. + */ + bool operator==(UndoStackObserverRecord const& _x) const + { + return &(this->_observer) == &(_x._observer); + } + + /** + * Issue a redo event to the UndoStackObserver that is associated with this UndoStackObserverRecord. + * + * \param log The event log generated by the redo event. + */ + void issueRedo(XML::Event* log) + { + this->_observer.notifyRedoEvent(log); + } + + /** + * Issue an undo event to the UndoStackObserver that is associated with this + * UndoStackObserverRecord. + * + * \param log The event log generated by the undo event. + */ + void issueUndo(XML::Event* log) + { + this->_observer.notifyUndoEvent(log); + } + + /** + * Issues a committed event to the UndoStackObserver that is associated with this + * UndoStackObserverRecord. + * + * \param log The event log being committed to the undo stack. + */ + void issueUndoCommit(XML::Event* log) + { + this->_observer.notifyUndoCommitEvent(log); + } + + private: + UndoStackObserver& _observer; + }; + + /// A list of UndoStackObserverRecords, used to aggregate multiple UndoStackObservers. + typedef std::list< UndoStackObserverRecord > UndoObserverRecordList; + + /** + * Constructor. + */ + CompositeUndoStackObserver(); + + ~CompositeUndoStackObserver(); + + /** + * Add an UndoStackObserver. + * + * \param observer Reference to an UndoStackObserver to add. + */ + void add(UndoStackObserver& observer); + + /** + * Remove an UndoStackObserver. + * + * \param observer Reference to an UndoStackObserver to remove. + */ + void remove(UndoStackObserver& observer); + + /** + * Notify all registered UndoStackObservers of an undo event. + * + * \param log The event log generated by the undo event. + */ + void notifyUndoEvent(XML::Event* log); + + /** + * Notify all registered UndoStackObservers of a redo event. + * + * \param log The event log generated by the redo event. + */ + void notifyRedoEvent(XML::Event* log); + + /** + * Notify all registered UndoStackObservers of an event log being committed to the undo stack. + * + * \param log The event log being committed to the undo stack. + */ + void notifyUndoCommitEvent(XML::Event* log); + +private: + // Remove an observer from a given list + bool _remove_one(UndoObserverRecordList& list, UndoStackObserver& rec); + + // Mark an observer for removal from a given list + bool _mark_one(UndoObserverRecordList& list, UndoStackObserver& rec); + + // Keep track of whether or not we are notifying observers + unsigned int _iterating; + + // Observers in the active list + UndoObserverRecordList _active; + + // Observers to be added + UndoObserverRecordList _pending; + + // Prevents the observer vector from modifications during + // iteration through the vector + void _lock() { this->_iterating++; } + void _unlock(); +}; + +} + +#endif diff --git a/src/conn-avoid-ref.cpp b/src/conn-avoid-ref.cpp new file mode 100644 index 000000000..0dbd8c730 --- /dev/null +++ b/src/conn-avoid-ref.cpp @@ -0,0 +1,176 @@ +/* + * A class for handling shape interaction with libavoid. + * + * Authors: + * Michael Wybrow + * + * Copyright (C) 2005 Michael Wybrow + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + + +#include "sp-item.h" +#include "conn-avoid-ref.h" +#include "libnr/nr-rect-ops.h" +#include "libavoid/polyutil.h" +#include "libavoid/incremental.h" +#include "xml/simple-node.cpp" +#include "document.h" + + +static Avoid::Polygn avoid_item_poly(SPItem const *item); +static void avoid_item_move(NR::Matrix const *mp, SPItem *moved_item); + + +SPAvoidRef::SPAvoidRef(SPItem *spitem) + : shapeRef(NULL) + , item(spitem) + , setting(false) + , new_setting(false) + , _transformed_connection() +{ +} + + +SPAvoidRef::~SPAvoidRef() +{ + _transformed_connection.disconnect(); + if (shapeRef) { + // shapeRef is finalised by delShape, + // so no memory is lost here. + Avoid::delShape(shapeRef); + shapeRef = NULL; + } +} + + +void SPAvoidRef::setAvoid(char const *value) +{ + if (SP_OBJECT_IS_CLONED(item)) { + // Don't keep avoidance information for cloned objects. + return; + } + new_setting = false; + if (value && (strcmp(value, "true") == 0)) { + new_setting = true; + } +} + + +void SPAvoidRef::handleSettingChange(void) +{ + if (new_setting == setting) { + // Don't need to make any changes + return; + } + + _transformed_connection.disconnect(); + if (new_setting) { + _transformed_connection = item->connectTransformed( + sigc::ptr_fun(&avoid_item_move)); + + Avoid::Polygn poly = avoid_item_poly(item); + if (poly.pn > 0) { + const char *id = SP_OBJECT_REPR(item)->attribute("id"); + g_assert(id != NULL); + + // Get a unique ID for the item. + GQuark itemID = g_quark_from_string(id); + + shapeRef = new Avoid::ShapeRef(itemID, poly); + Avoid::freePoly(poly); + + Avoid::addShape(shapeRef); + } + } + else + { + g_assert(shapeRef); + + // shapeRef is finalised by delShape, + // so no memory is lost here. + Avoid::delShape(shapeRef); + shapeRef = NULL; + } + setting = new_setting; +} + + +static Avoid::Polygn avoid_item_poly(SPItem const *item) +{ + Avoid::Polygn poly; + + // TODO: The right way to do this is to return the convex hull of + // the object, or an approximation in the case of a rounded + // object. Specific SPItems will need to have a new + // function that returns points for the convex hull. + // For some objects it is enough to feed the snappoints to + // some convex hull code, though not NR::ConvexHull as this + // only keeps the bounding box of the convex hull currently. + + // TODO: SPItem::invokeBbox gives the wrong result for some objects + // that have internal representations that are updated later + // by the sp_*_update functions, e.g., text. + sp_document_ensure_up_to_date(item->document); + + NR::Rect rHull = item->invokeBbox(sp_item_i2doc_affine(item)); + + // Add a little buffer around the edge of each object. + NR::Rect rExpandedHull = NR::expand(rHull, -10.0); + poly = Avoid::newPoly(4); + + for (unsigned n = 0; n < 4; ++n) { + // TODO: I think the winding order in libavoid or inkscape might + // be backwards, probably due to the inverse y co-ordinates + // used for the screen. The '3 - n' reverses the order. + /* On "correct" winding order: Winding order of NR::Rect::corner is in a positive + * direction, like libart. "Positive direction" means the same as in most of Inkscape and + * SVG: if you visualize y as increasing upwards, as is the convention in mathematics, then + * positive angle is visualized as anticlockwise, as in mathematics; so if you visualize y + * as increasing downwards, as is common outside of mathematics, then positive angle + * direction is visualized as clockwise, as is common outside of mathematics. This + * convention makes it easier mix pure mathematics code with graphics code: the important + * thing when mixing code is that the number values stored in variables (representing y + * coordinate, angle) match up; variables store numbers, not visualized positions, and the + * programmer is free to switch between visualizations when thinking about a given piece of + * code. + * + * MathWorld, libart and NR::Rect::corner all seem to take positive winding (i.e. winding + * that yields +1 winding number inside a simple closed shape) to mean winding in a + * positive angle. This, together with the observation that variables store numbers rather + * than positions, suggests that NR::Rect::corner uses the right direction. + */ + NR::Point hullPoint = rExpandedHull.corner(3 - n); + poly.ps[n].x = hullPoint[NR::X]; + poly.ps[n].y = hullPoint[NR::Y]; + } + + return poly; +} + + +static void avoid_item_move(NR::Matrix const *mp, SPItem *moved_item) +{ + Avoid::ShapeRef *shapeRef = moved_item->avoidRef->shapeRef; + g_assert(shapeRef); + + Avoid::Polygn poly = avoid_item_poly(moved_item); + if (poly.pn > 0) { + // moveShape actually destroys the old shapeRef and returns a new one. + moved_item->avoidRef->shapeRef = Avoid::moveShape(shapeRef, &poly); + Avoid::freePoly(poly); + } +} + +/* + 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/conn-avoid-ref.h b/src/conn-avoid-ref.h new file mode 100644 index 000000000..3b5e6d3b5 --- /dev/null +++ b/src/conn-avoid-ref.h @@ -0,0 +1,57 @@ +#ifndef SEEN_CONN_AVOID_REF +#define SEEN_CONN_AVOID_REF + +/* + * A class for handling shape interaction with libavoid. + * + * Authors: + * Michael Wybrow + * + * Copyright (C) 2005 Michael Wybrow + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + +#include + +struct SPItem; +namespace Avoid { + class ShapeRef; +} + +class SPAvoidRef { +public: + SPAvoidRef(SPItem *spitem); + ~SPAvoidRef(); + + // libavoid's internal representation of the item. + Avoid::ShapeRef *shapeRef; + + void setAvoid(char const *value); + void handleSettingChange(void); + +private: + SPItem *item; + + // true if avoiding, false if not. + bool setting; + bool new_setting; + + // A sigc connection for transformed signal. + sigc::connection _transformed_connection; +}; + + +#endif /* !SEEN_CONN_AVOID_REF */ + +/* + 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/connector-context.cpp b/src/connector-context.cpp new file mode 100644 index 000000000..34f5ba3ed --- /dev/null +++ b/src/connector-context.cpp @@ -0,0 +1,1350 @@ +/* + * Connector creation tool + * + * Authors: + * Michael Wybrow + * + * Copyright (C) 2005 Michael Wybrow + * + * Released under GNU GPL, read the file 'COPYING' for more information + * + * TODO: + * o Have shapes avoid coonvex hulls of objects, rather than their + * bounding box. Possibly implement the unfinished ConvexHull + * class in libnr. + * (HOWEVER, using the convex hull C of a shape S does the wrong thing if a + * connector starts outside of S but inside C, or if the best route around + * an object involves going inside C but without entering S.) + * o Draw connectors to shape edges rather than bounding box. + * o Show a visual indicator for objects with the 'avoid' property set. + * o Create an interface for setting markers (arrow heads). + * o Better distinguish between paths and connectors to prevent problems + * in the node tool and paths accidently being turned into connectors + * in the connector tool. Perhaps have a way to convert between. + * o Only call libavoid's updateEndPoint as required. Currently we do it + * for both endpoints, even if only one is moving. + * o Cleanup to remove unecessary borrowed DrawContext code. + * o Allow user-placeable connection points. + * o Deal sanely with connectors with both endpoints attached to the + * same connection point, and drawing of connectors attaching + * overlaping shapes (currently tries to adjust connector to be + * outside both bounding boxes). + * o Fix many special cases related to connectors updating, + * e.g., copying a couple of shapes and a connector that are + * attached to each other. + * e.g., detach connector when it is moved or transformed in + * one of the other contexts. + * o Cope with shapes whose ids change when they have attached + * connectors. + * o gobble_motion_events(GDK_BUTTON1_MASK)?; + * + */ + +#include + +#include "connector-context.h" +#include "pixmaps/cursor-connector.xpm" +#include "xml/node-event-vector.h" +#include "xml/repr.h" +#include "svg/svg.h" +#include "desktop.h" +#include "desktop-style.h" +#include "desktop-affine.h" +#include "desktop-handles.h" +#include "document.h" +#include "message-context.h" +#include "message-stack.h" +#include "selection.h" +#include "inkscape.h" +#include "prefs-utils.h" +#include "sp-path.h" +#include "display/canvas-bpath.h" +#include "display/sodipodi-ctrl.h" +#include +#include "snap.h" +#include "knot.h" +#include "sp-conn-end.h" +#include "conn-avoid-ref.h" +#include "libavoid/vertices.h" +#include "context-fns.h" + + + +static void sp_connector_context_class_init(SPConnectorContextClass *klass); +static void sp_connector_context_init(SPConnectorContext *conn_context); +static void sp_connector_context_dispose(GObject *object); + +static void sp_connector_context_setup(SPEventContext *ec); +static void sp_connector_context_finish(SPEventContext *ec); +static gint sp_connector_context_root_handler(SPEventContext *ec, GdkEvent *event); +static gint sp_connector_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event); + +// Stuff borrowed from DrawContext +static void spcc_connector_set_initial_point(SPConnectorContext *cc, NR::Point const p); +static void spcc_connector_set_subsequent_point(SPConnectorContext *cc, NR::Point const p); +static void spcc_connector_finish_segment(SPConnectorContext *cc, NR::Point p); +static void spcc_reset_colors(SPConnectorContext *cc); +static void spcc_connector_finish(SPConnectorContext *cc); +static void spcc_concat_colors_and_flush(SPConnectorContext *cc); +static void spcc_flush_white(SPConnectorContext *cc, SPCurve *gc); + +// Context event handlers +static gint connector_handle_button_press(SPConnectorContext *const cc, GdkEventButton const &bevent); +static gint connector_handle_motion_notify(SPConnectorContext *const cc, GdkEventMotion const &mevent); +static gint connector_handle_button_release(SPConnectorContext *const cc, GdkEventButton const &revent); +static gint connector_handle_key_press(SPConnectorContext *const cc, guint const keyval); + +static void cc_set_active_shape(SPConnectorContext *cc, SPItem *item); +static void cc_clear_active_shape(SPConnectorContext *cc); +static void cc_set_active_conn(SPConnectorContext *cc, SPItem *item); +static void cc_clear_active_conn(SPConnectorContext *cc); +static gchar *conn_pt_handle_test(SPConnectorContext *cc, NR::Point& w); +static bool cc_item_is_shape(SPItem *item); +static void cc_selection_changed(Inkscape::Selection *selection, gpointer data); + +static void shape_event_attr_deleted(Inkscape::XML::Node *repr, + Inkscape::XML::Node *child, Inkscape::XML::Node *ref, gpointer data); +static void shape_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name, + gchar const *old_value, gchar const *new_value, bool is_interactive, + gpointer data); + + +static NR::Point connector_drag_origin_w(0, 0); +static bool connector_within_tolerance = false; +static SPEventContextClass *parent_class; + + +static Inkscape::XML::NodeEventVector shape_repr_events = { + NULL, /* child_added */ + NULL, /* child_added */ + shape_event_attr_changed, + NULL, /* content_changed */ + NULL /* order_changed */ +}; + +static Inkscape::XML::NodeEventVector layer_repr_events = { + NULL, /* child_added */ + shape_event_attr_deleted, + NULL, /* child_added */ + NULL, /* content_changed */ + NULL /* order_changed */ +}; + + +GType +sp_connector_context_get_type(void) +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPConnectorContextClass), + NULL, NULL, + (GClassInitFunc) sp_connector_context_class_init, + NULL, NULL, + sizeof(SPConnectorContext), + 4, + (GInstanceInitFunc) sp_connector_context_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPConnectorContext", &info, (GTypeFlags)0); + } + return type; +} + +static void +sp_connector_context_class_init(SPConnectorContextClass *klass) +{ + GObjectClass *object_class; + SPEventContextClass *event_context_class; + + object_class = (GObjectClass *) klass; + event_context_class = (SPEventContextClass *) klass; + + parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass); + + object_class->dispose = sp_connector_context_dispose; + + event_context_class->setup = sp_connector_context_setup; + event_context_class->finish = sp_connector_context_finish; + event_context_class->root_handler = sp_connector_context_root_handler; + event_context_class->item_handler = sp_connector_context_item_handler; +} + + +static void +sp_connector_context_init(SPConnectorContext *cc) +{ + SPEventContext *ec = SP_EVENT_CONTEXT(cc); + + ec->cursor_shape = cursor_connector_xpm; + ec->hot_x = 1; + ec->hot_y = 1; + ec->xp = 0; + ec->yp = 0; + + cc->red_color = 0xff00007f; + + cc->newconn = NULL; + cc->newConnRef = NULL; + + cc->sel_changed_connection = sigc::connection(); + + cc->active_shape = NULL; + cc->active_shape_repr = NULL; + cc->active_shape_layer_repr = NULL; + + cc->active_conn = NULL; + cc->active_conn_repr = NULL; + + cc->active_handle = NULL; + + cc->clickeditem = NULL; + cc->clickedhandle = NULL; + + cc->connpthandle = NULL; + for (int i = 0; i < 2; ++i) { + cc->endpt_handle[i] = NULL; + cc->endpt_handler_id[i] = 0; + } + cc->sid = NULL; + cc->eid = NULL; + cc->npoints = 0; + cc->state = SP_CONNECTOR_CONTEXT_IDLE; +} + + +static void +sp_connector_context_dispose(GObject *object) +{ + SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(object); + + cc->sel_changed_connection.disconnect(); + + if (cc->connpthandle) { + g_object_unref(cc->connpthandle); + cc->connpthandle = NULL; + } + for (int i = 0; i < 2; ++i) { + if (cc->endpt_handle[1]) { + g_object_unref(cc->endpt_handle[i]); + cc->endpt_handle[i] = NULL; + } + } + if (cc->sid) { + g_free(cc->sid); + cc->sid = NULL; + } + if (cc->eid) { + g_free(cc->eid); + cc->eid = NULL; + } + g_assert( cc->newConnRef == NULL ); + + G_OBJECT_CLASS(parent_class)->dispose(object); +} + + +static void +sp_connector_context_setup(SPEventContext *ec) +{ + SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(ec); + SPDesktop *dt = ec->desktop; + + if (((SPEventContextClass *) parent_class)->setup) { + ((SPEventContextClass *) parent_class)->setup(ec); + } + + cc->selection = SP_DT_SELECTION(dt); + + cc->sel_changed_connection.disconnect(); + cc->sel_changed_connection = cc->selection->connectChanged( + sigc::bind(sigc::ptr_fun(&cc_selection_changed), + (gpointer) cc)); + + /* Create red bpath */ + cc->red_bpath = sp_canvas_bpath_new(SP_DT_SKETCH(ec->desktop), NULL); + sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cc->red_bpath), cc->red_color, + 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); + sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(cc->red_bpath), 0x00000000, + SP_WIND_RULE_NONZERO); + /* Create red curve */ + cc->red_curve = sp_curve_new_sized(4); + + /* Create green curve */ + cc->green_curve = sp_curve_new_sized(64); + + // Notice the initial selection. + cc_selection_changed(cc->selection, (gpointer) cc); + + if (prefs_get_int_attribute("tools.connector", "selcue", 0) != 0) { + ec->enableSelectionCue(); + } +} + + +static void +sp_connector_context_finish(SPEventContext *ec) +{ + SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(ec); + + spcc_connector_finish(cc); + + if (((SPEventContextClass *) parent_class)->finish) { + ((SPEventContextClass *) parent_class)->finish(ec); + } + + if (cc->selection) { + cc->selection = NULL; + } + cc_clear_active_shape(cc); + cc_clear_active_conn(cc); +} + + +//----------------------------------------------------------------------------- + + +static void +cc_clear_active_shape(SPConnectorContext *cc) +{ + if (cc->active_shape == NULL) { + return; + } + g_assert( cc->active_shape_repr ); + g_assert( cc->active_shape_layer_repr ); + + cc->active_shape = NULL; + + if (cc->active_shape_repr) { + sp_repr_remove_listener_by_data(cc->active_shape_repr, cc); + Inkscape::GC::release(cc->active_shape_repr); + cc->active_shape_repr = NULL; + + sp_repr_remove_listener_by_data(cc->active_shape_layer_repr, cc); + Inkscape::GC::release(cc->active_shape_layer_repr); + cc->active_shape_layer_repr = NULL; + } + + // Hide the center connection point if it exists. + if (cc->connpthandle) { + sp_knot_hide(cc->connpthandle); + } +} + + +static void +cc_clear_active_conn(SPConnectorContext *cc) +{ + if (cc->active_conn == NULL) { + return; + } + g_assert( cc->active_conn_repr ); + + cc->active_conn = NULL; + + if (cc->active_conn_repr) { + sp_repr_remove_listener_by_data(cc->active_conn_repr, cc); + Inkscape::GC::release(cc->active_conn_repr); + cc->active_conn_repr = NULL; + } + + // Hide the endpoint handles. + for (int i = 0; i < 2; ++i) { + if (cc->endpt_handle[i]) { + sp_knot_hide(cc->endpt_handle[i]); + } + } +} + + +static gchar * +conn_pt_handle_test(SPConnectorContext *cc, NR::Point& p) +{ + // TODO: this will need to change when there are more connection + // points available for each shape. + + SPKnot *centerpt = cc->connpthandle; + if (cc->active_handle && (cc->active_handle == centerpt)) + { + p = centerpt->pos; + return g_strdup_printf("#%s", SP_OBJECT_ID(cc->active_shape)); + } + return NULL; +} + + + +static gint +sp_connector_context_item_handler(SPEventContext *ec, SPItem *item, GdkEvent *event) +{ + gint ret = FALSE; + + SPDesktop *desktop = ec->desktop; + + SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(ec); + + NR::Point p(event->button.x, event->button.y); + + switch (event->type) { + case GDK_BUTTON_RELEASE: + if (event->button.button == 1) { + if ((cc->state == SP_CONNECTOR_CONTEXT_DRAGGING) && + (connector_within_tolerance)) + { + spcc_reset_colors(cc); + cc->state = SP_CONNECTOR_CONTEXT_IDLE; + } + if (cc->state != SP_CONNECTOR_CONTEXT_IDLE) { + // Doing simething else like rerouting. + break; + } + // find out clicked item, disregarding groups, honoring Alt + SPItem *item_ungrouped = sp_event_context_find_item(desktop, + p, event->button.state & GDK_MOD1_MASK, TRUE); + + if (event->button.state & GDK_SHIFT_MASK) { + cc->selection->toggle(item_ungrouped); + } else { + cc->selection->set(item_ungrouped); + } + ret = TRUE; + } + break; + case GDK_ENTER_NOTIFY: + { + if (cc_item_is_shape(item)) { + // This is a shape, so show connection point(s). + if (!(cc->active_shape) || + // Don't show handle for another handle. + (item != ((SPItem *) cc->connpthandle))) { + cc_set_active_shape(cc, item); + } + } + ret = TRUE; + break; + } + default: + break; + } + + return ret; +} + + +gint +sp_connector_context_root_handler(SPEventContext *ec, GdkEvent *event) +{ + SPConnectorContext *const cc = SP_CONNECTOR_CONTEXT(ec); + + gint ret = FALSE; + + switch (event->type) { + case GDK_BUTTON_PRESS: + ret = connector_handle_button_press(cc, event->button); + break; + + case GDK_MOTION_NOTIFY: + ret = connector_handle_motion_notify(cc, event->motion); + break; + + case GDK_BUTTON_RELEASE: + ret = connector_handle_button_release(cc, event->button); + break; + case GDK_KEY_PRESS: + ret = connector_handle_key_press(cc, get_group0_keyval (&event->key)); + break; + + default: + break; + } + + if (!ret) { + gint (*const parent_root_handler)(SPEventContext *, GdkEvent *) + = ((SPEventContextClass *) parent_class)->root_handler; + if (parent_root_handler) { + ret = parent_root_handler(ec, event); + } + } + + return ret; +} + + +static gint +connector_handle_button_press(SPConnectorContext *const cc, GdkEventButton const &bevent) +{ + NR::Point const event_w(bevent.x, bevent.y); + /* Find desktop coordinates */ + NR::Point p = cc->desktop->w2d(event_w); + + gint ret = FALSE; + if ( bevent.button == 1 ) { + + SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc); + + if (Inkscape::have_viable_layer(desktop, cc->_message_context) == false) { + return TRUE; + } + + NR::Point const event_w(bevent.x, + bevent.y); + connector_drag_origin_w = event_w; + connector_within_tolerance = true; + + NR::Point const event_dt = cc->desktop->w2d(event_w); + switch (cc->state) { + case SP_CONNECTOR_CONTEXT_STOP: + /* This is allowed, if we just cancelled curve */ + case SP_CONNECTOR_CONTEXT_IDLE: + { + if ( cc->npoints == 0 ) { + /* Set start anchor */ + NR::Point p; + + cc_clear_active_conn(cc); + + SP_EVENT_CONTEXT_DESKTOP(cc)->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Creating new connector")); + + /* Create green anchor */ + p = event_dt; + + // Test whether we clicked on a connection point + cc->sid = conn_pt_handle_test(cc, p); + + if (!cc->sid) { + // This is the first point, so just snap it to the grid + // as there's no other points to go off. + SnapManager const m(cc->desktop->namedview); + p = m.freeSnap(Inkscape::Snapper::SNAP_POINT | Inkscape::Snapper::BBOX_POINT, + p, NULL).getPoint(); + } + spcc_connector_set_initial_point(cc, p); + + } + cc->state = SP_CONNECTOR_CONTEXT_DRAGGING; + ret = TRUE; + break; + } + case SP_CONNECTOR_CONTEXT_DRAGGING: + { + // This is the second click of a connector creation. + + spcc_connector_set_subsequent_point(cc, p); + spcc_connector_finish_segment(cc, p); + // Test whether we clicked on a connection point + cc->eid = conn_pt_handle_test(cc, p); + if (cc->npoints != 0) { + spcc_connector_finish(cc); + } + cc_set_active_conn(cc, cc->newconn); + cc->state = SP_CONNECTOR_CONTEXT_IDLE; + ret = TRUE; + break; + } + case SP_CONNECTOR_CONTEXT_CLOSE: + { + g_warning("Button down in CLOSE state"); + break; + } + default: + break; + } + } else if (bevent.button == 3) { + if (cc->npoints != 0) { + spcc_connector_finish(cc); + ret = TRUE; + } + } + return ret; +} + + +static gint +connector_handle_motion_notify(SPConnectorContext *const cc, GdkEventMotion const &mevent) +{ + gint ret = FALSE; + + if (mevent.state & GDK_BUTTON2_MASK || mevent.state & GDK_BUTTON3_MASK) { + // allow middle-button scrolling + return FALSE; + } + + NR::Point const event_w(mevent.x, mevent.y); + + if (connector_within_tolerance) { + gint const tolerance = prefs_get_int_attribute_limited("options.dragtolerance", + "value", 0, 0, 100); + if ( NR::LInfty( event_w - connector_drag_origin_w ) < tolerance ) { + return FALSE; // Do not drag if we're within tolerance from origin. + } + } + // Once the user has moved farther than tolerance from the original location + // (indicating they intend to move the object, not click), then always process + // the motion notify coordinates as given (no snapping back to origin) + connector_within_tolerance = false; + + SPDesktop *const dt = cc->desktop; + + /* Find desktop coordinates */ + NR::Point p = dt->w2d(event_w); + + switch (cc->state) { + case SP_CONNECTOR_CONTEXT_DRAGGING: + { + // This is movement during a connector creation. + + if ( cc->npoints > 0 ) { + cc->selection->clear(); + spcc_connector_set_subsequent_point(cc, p); + ret = TRUE; + } + break; + } + case SP_CONNECTOR_CONTEXT_REROUTING: + { + g_assert( SP_IS_PATH(cc->clickeditem)); + + // Update the hidden path + NR::Matrix i2d = sp_item_i2d_affine(cc->clickeditem); + NR::Matrix d2i = i2d.inverse(); + SPPath *path = SP_PATH(cc->clickeditem); + SPCurve *curve = (SP_SHAPE(path))->curve; + if (cc->clickedhandle == cc->endpt_handle[0]) { + NR::Point o = cc->endpt_handle[1]->pos; + sp_curve_stretch_endpoints(curve, p * d2i, o * d2i); + } + else { + NR::Point o = cc->endpt_handle[0]->pos; + sp_curve_stretch_endpoints(curve, o * d2i, p * d2i); + } + sp_conn_adjust_path(path); + + // Copy this to the temporary visible path + cc->red_curve = sp_curve_copy(SP_SHAPE(path)->curve); + sp_curve_transform(cc->red_curve, i2d); + + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), cc->red_curve); + ret = TRUE; + break; + } + case SP_CONNECTOR_CONTEXT_STOP: + /* This is perfectly valid */ + break; + default: + break; + } + + return ret; +} + + +static gint +connector_handle_button_release(SPConnectorContext *const cc, GdkEventButton const &revent) +{ + gint ret = FALSE; + if ( revent.button == 1 ) { + + SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc); + SPDocument *doc = SP_DT_DOCUMENT(desktop); + + NR::Point const event_w(revent.x, revent.y); + + /* Find desktop coordinates */ + NR::Point p = cc->desktop->w2d(event_w); + + switch (cc->state) { + //case SP_CONNECTOR_CONTEXT_POINT: + case SP_CONNECTOR_CONTEXT_DRAGGING: + { + if (connector_within_tolerance) + { + spcc_connector_finish_segment(cc, p); + return TRUE; + } + // Connector has been created via a drag, end it now. + spcc_connector_set_subsequent_point(cc, p); + spcc_connector_finish_segment(cc, p); + // Test whether we clicked on a connection point + cc->eid = conn_pt_handle_test(cc, p); + if (cc->npoints != 0) { + spcc_connector_finish(cc); + } + cc_set_active_conn(cc, cc->newconn); + cc->state = SP_CONNECTOR_CONTEXT_IDLE; + break; + } + case SP_CONNECTOR_CONTEXT_REROUTING: + { + // Clear the temporary path: + sp_curve_reset(cc->red_curve); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL); + + // Test whether we clicked on a connection point + gchar *shape_label = conn_pt_handle_test(cc, p); + + if (shape_label) { + if (cc->clickedhandle == cc->endpt_handle[0]) { + sp_object_setAttribute(cc->clickeditem, + "inkscape:connection-start",shape_label, false); + } + else { + sp_object_setAttribute(cc->clickeditem, + "inkscape:connection-end",shape_label, false); + } + g_free(shape_label); + } + cc->clickeditem->setHidden(false); + sp_conn_adjust_path(SP_PATH(cc->clickeditem)); + cc->clickeditem->updateRepr(); + sp_document_done(doc); + cc_set_active_conn(cc, cc->clickeditem); + sp_document_ensure_up_to_date(doc); + cc->state = SP_CONNECTOR_CONTEXT_IDLE; + return TRUE; + break; + } + case SP_CONNECTOR_CONTEXT_STOP: + /* This is allowed, if we just cancelled curve */ + break; + default: + break; + } + ret = TRUE; + } + + return ret; +} + + +static gint +connector_handle_key_press(SPConnectorContext *const cc, guint const keyval) +{ + gint ret = FALSE; + /* fixme: */ + switch (keyval) { + case GDK_Return: + case GDK_KP_Enter: + if (cc->npoints != 0) { + spcc_connector_finish(cc); + ret = TRUE; + } + break; + case GDK_Escape: + if (cc->npoints != 0) { + // if drawing, cancel, otherwise pass it up for deselecting + cc->state = SP_CONNECTOR_CONTEXT_STOP; + spcc_reset_colors(cc); + ret = TRUE; + } + break; + default: + break; + } + return ret; +} + + +static void +spcc_reset_colors(SPConnectorContext *cc) +{ + /* Red */ + sp_curve_reset(cc->red_curve); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL); + + sp_curve_reset(cc->green_curve); + cc->npoints = 0; +} + + +static void +spcc_connector_set_initial_point(SPConnectorContext *const cc, NR::Point const p) +{ + g_assert( cc->npoints == 0 ); + + cc->p[0] = p; + cc->p[1] = p; + cc->npoints = 2; + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL); +} + + +static void +spcc_connector_set_subsequent_point(SPConnectorContext *const cc, NR::Point const p) +{ + g_assert( cc->npoints != 0 ); + + SPDesktop *dt = cc->desktop; + NR::Point o = dt->dt2doc(cc->p[0]); + NR::Point d = dt->dt2doc(p); + Avoid::Point src = { o[NR::X], o[NR::Y] }; + Avoid::Point dst = { d[NR::X], d[NR::Y] }; + + if (!cc->newConnRef) { + cc->newConnRef = new Avoid::ConnRef(0, src, dst); + cc->newConnRef->updateEndPoint(Avoid::VertID::src, src); + } + cc->newConnRef->updateEndPoint(Avoid::VertID::tar, dst); + + cc->newConnRef->makePathInvalid(); + cc->newConnRef->generatePath(src, dst); + + Avoid::PolyLine route = cc->newConnRef->route(); + cc->newConnRef->calcRouteDist(); + + sp_curve_reset(cc->red_curve); + NR::Point pt(route.ps[0].x, route.ps[0].y); + sp_curve_moveto(cc->red_curve, pt); + + for (int i = 1; i < route.pn; ++i) { + NR::Point p(route.ps[i].x, route.ps[i].y); + sp_curve_lineto(cc->red_curve, p); + } + sp_curve_transform(cc->red_curve, dt->doc2dt()); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), cc->red_curve); +} + + +/** + * Concats red, blue and green. + * If any anchors are defined, process these, optionally removing curves from white list + * Invoke _flush_white to write result back to object. + */ +static void +spcc_concat_colors_and_flush(SPConnectorContext *cc) +{ + SPCurve *c = cc->green_curve; + cc->green_curve = sp_curve_new_sized(64); + + sp_curve_reset(cc->red_curve); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL); + + if (sp_curve_empty(c)) { + sp_curve_unref(c); + return; + } + + spcc_flush_white(cc, c); + + sp_curve_unref(c); +} + + +/* + * Flushes white curve(s) and additional curve into object + * + * No cleaning of colored curves - this has to be done by caller + * No rereading of white data, so if you cannot rely on ::modified, do it in caller + * + */ + +static void +spcc_flush_white(SPConnectorContext *cc, SPCurve *gc) +{ + SPCurve *c; + + if (gc) { + c = gc; + sp_curve_ref(c); + } else { + return; + } + + /* Now we have to go back to item coordinates at last */ + sp_curve_transform(c, + sp_desktop_dt2root_affine(SP_EVENT_CONTEXT_DESKTOP(cc))); + + SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc); + SPDocument *doc = SP_DT_DOCUMENT(desktop); + + if ( c && !sp_curve_empty(c) ) { + /* We actually have something to write */ + + Inkscape::XML::Node *repr = sp_repr_new("svg:path"); + /* Set style */ + sp_desktop_apply_style_tool(desktop, repr, "tools.connector", false); + + gchar *str = sp_svg_write_path(SP_CURVE_BPATH(c)); + g_assert( str != NULL ); + repr->setAttribute("d", str); + g_free(str); + + /* Attach repr */ + cc->newconn = SP_ITEM(desktop->currentLayer()->appendChildRepr(repr)); + cc->selection->set(repr); + Inkscape::GC::release(repr); + cc->newconn->transform = i2i_affine(desktop->currentRoot(), desktop->currentLayer()); + cc->newconn->updateRepr(); + + bool connection = false; + sp_object_setAttribute(cc->newconn, "inkscape:connector-type", + "polyline", false); + if (cc->sid) + { + sp_object_setAttribute(cc->newconn, "inkscape:connection-start", + cc->sid, false); + connection = true; + } + + if (cc->eid) + { + sp_object_setAttribute(cc->newconn, "inkscape:connection-end", + cc->eid, false); + connection = true; + } + cc->newconn->updateRepr(); + if (connection) { + // Adjust endpoints to shape edge. + sp_conn_adjust_path(SP_PATH(cc->newconn)); + } + cc->newconn->updateRepr(); + } + + sp_curve_unref(c); + + /* Flush pending updates */ + sp_document_done(doc); + sp_document_ensure_up_to_date(doc); +} + + +static void +spcc_connector_finish_segment(SPConnectorContext *const cc, NR::Point const p) +{ + if (!sp_curve_empty(cc->red_curve)) { + sp_curve_append_continuous(cc->green_curve, cc->red_curve, 0.0625); + + cc->p[0] = cc->p[3]; + cc->p[1] = cc->p[4]; + cc->npoints = 2; + + sp_curve_reset(cc->red_curve); + } +} + + +static void +spcc_connector_finish(SPConnectorContext *const cc) +{ + SPDesktop *const desktop = cc->desktop; + desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Finishing connector")); + + sp_curve_reset(cc->red_curve); + spcc_concat_colors_and_flush(cc); + + cc->npoints = 0; + + if (cc->newConnRef) { + cc->newConnRef->removeFromGraph(); + delete cc->newConnRef; + cc->newConnRef = NULL; + } + cc->state = SP_CONNECTOR_CONTEXT_IDLE; +} + + +static gboolean +cc_generic_knot_handler(SPCanvasItem *, GdkEvent *event, SPKnot *knot) +{ + g_assert (knot != NULL); + + SPConnectorContext *cc = SP_CONNECTOR_CONTEXT( + knot->desktop->event_context); + + gboolean consumed = FALSE; + + switch (event->type) { + case GDK_ENTER_NOTIFY: + gtk_object_set (GTK_OBJECT (knot->item), "fill_color", + knot->fill [SP_KNOT_STATE_MOUSEOVER], NULL); + gtk_object_set (GTK_OBJECT (knot->item), "stroke_color", + knot->stroke [SP_KNOT_STATE_MOUSEOVER], NULL); + + cc->active_handle = knot; + + if (knot->tip) + { + knot->desktop->event_context->defaultMessageContext()->set( + Inkscape::NORMAL_MESSAGE, knot->tip); + } + + consumed = TRUE; + break; + case GDK_LEAVE_NOTIFY: + gtk_object_set (GTK_OBJECT (knot->item), "fill_color", + knot->fill [SP_KNOT_STATE_NORMAL], NULL); + gtk_object_set (GTK_OBJECT (knot->item), "stroke_color", + knot->stroke [SP_KNOT_STATE_NORMAL], NULL); + + cc->active_handle = NULL; + + if (knot->tip) { + knot->desktop->event_context->defaultMessageContext()->clear(); + } + + consumed = TRUE; + break; + default: + break; + } + + return consumed; +} + + +static gboolean +endpt_handler(SPKnot *knot, GdkEvent *event, SPConnectorContext *cc) +{ + g_assert( SP_IS_CONNECTOR_CONTEXT(cc) ); + + gboolean consumed = FALSE; + + switch (event->type) { + case GDK_BUTTON_PRESS: + g_assert( (cc->active_handle == cc->endpt_handle[0]) || + (cc->active_handle == cc->endpt_handle[1]) ); + if (cc->state == SP_CONNECTOR_CONTEXT_IDLE) { + cc->clickeditem = cc->active_conn; + cc->clickedhandle = cc->active_handle; + cc_clear_active_conn(cc); + cc->state = SP_CONNECTOR_CONTEXT_REROUTING; + + // Disconnect from attached shape + unsigned ind = (cc->active_handle == cc->endpt_handle[0]) ? 0 : 1; + sp_conn_end_detach(cc->clickeditem, ind); + + NR::Point origin; + if (cc->clickedhandle == cc->endpt_handle[0]) { + origin = cc->endpt_handle[1]->pos; + } + else { + origin = cc->endpt_handle[0]->pos; + } + + // Show the red path for dragging. + cc->red_curve = sp_curve_copy(SP_PATH(cc->clickeditem)->curve); + NR::Matrix i2d = sp_item_i2d_affine(cc->clickeditem); + sp_curve_transform(cc->red_curve, i2d); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), + cc->red_curve); + + cc->clickeditem->setHidden(true); + + // The rest of the interaction rerouting the connector is + // handled by the context root handler. + consumed = TRUE; + } + break; + default: + break; + } + + return consumed; +} + + +static void cc_set_active_shape(SPConnectorContext *cc, SPItem *item) +{ + g_assert(item != NULL ); + + cc->active_shape = item; + + // Remove existing active shape listeners + if (cc->active_shape_repr) { + sp_repr_remove_listener_by_data(cc->active_shape_repr, cc); + Inkscape::GC::release(cc->active_shape_repr); + + sp_repr_remove_listener_by_data(cc->active_shape_layer_repr, cc); + Inkscape::GC::release(cc->active_shape_layer_repr); + } + + // Listen in case the active shape changes + cc->active_shape_repr = SP_OBJECT_REPR(item); + if (cc->active_shape_repr) { + Inkscape::GC::anchor(cc->active_shape_repr); + sp_repr_add_listener(cc->active_shape_repr, &shape_repr_events, cc); + + cc->active_shape_layer_repr = cc->active_shape_repr->parent(); + Inkscape::GC::anchor(cc->active_shape_layer_repr); + sp_repr_add_listener(cc->active_shape_layer_repr, &layer_repr_events, cc); + } + + + // Set center connection point. + if ( cc->connpthandle == NULL ) { + SPKnot * knot = (SPKnot*)g_object_new (SP_TYPE_KNOT, 0); + + knot->desktop = cc->desktop; + knot->flags = SP_KNOT_VISIBLE; + + knot->item = sp_canvas_item_new (SP_DT_CONTROLS(cc->desktop), + SP_TYPE_CTRL, + "anchor", GTK_ANCHOR_CENTER, + "filled", TRUE, + "stroked", TRUE, + "mode", SP_KNOT_MODE_XOR, + NULL); + + gtk_signal_connect (GTK_OBJECT (knot->item), "event", + GTK_SIGNAL_FUNC (cc_generic_knot_handler), knot); + + knot->fill [SP_KNOT_STATE_NORMAL] = 0xffffff00; + knot->fill [SP_KNOT_STATE_MOUSEOVER] = 0xff0000ff; + knot->stroke [SP_KNOT_STATE_NORMAL] = 0x01000000; + + g_object_set(G_OBJECT(knot), + "shape", SP_KNOT_SHAPE_SQUARE, + "size", 8, + "anchor", GTK_ANCHOR_CENTER, + "tip", _("Connection point: click or drag to create a new connector"), + NULL); + + cc->connpthandle = knot; + } + + + NR::Rect bbox = sp_item_bbox_desktop(cc->active_shape); + NR::Point center = bbox.midpoint(); + sp_knot_set_position(cc->connpthandle, ¢er, 0); + + sp_knot_show(cc->connpthandle); + +} + + +static void +cc_set_active_conn(SPConnectorContext *cc, SPItem *item) +{ + g_assert( SP_IS_PATH(item) ); + + SPCurve *curve = SP_SHAPE(SP_PATH(item))->curve; + NR::Matrix i2d = sp_item_i2d_affine(item); + + if (cc->active_conn == item) + { + // Just adjust handle positions. + NR::Point startpt = sp_curve_first_point(curve) * i2d; + sp_knot_set_position(cc->endpt_handle[0], &startpt, 0); + + NR::Point endpt = sp_curve_last_point(curve) * i2d; + sp_knot_set_position(cc->endpt_handle[1], &endpt, 0); + + return; + } + + cc->active_conn = item; + + // Remove existing active conn listeners + if (cc->active_conn_repr) { + sp_repr_remove_listener_by_data(cc->active_conn_repr, cc); + Inkscape::GC::release(cc->active_conn_repr); + cc->active_conn_repr = NULL; + } + + // Listen in case the active conn changes + cc->active_conn_repr = SP_OBJECT_REPR(item); + if (cc->active_conn_repr) { + Inkscape::GC::anchor(cc->active_conn_repr); + sp_repr_add_listener(cc->active_conn_repr, &shape_repr_events, cc); + } + + for (int i = 0; i < 2; ++i) { + + // Create the handle if it doesn't exist + if ( cc->endpt_handle[i] == NULL ) { + SPKnot * knot = (SPKnot*) g_object_new (SP_TYPE_KNOT, 0); + + knot->desktop = cc->desktop; + knot->flags = SP_KNOT_VISIBLE; + + knot->item = sp_canvas_item_new (SP_DT_CONTROLS (cc->desktop), + SP_TYPE_CTRL, + "anchor", GTK_ANCHOR_CENTER, + "filled", TRUE, + "stroked", TRUE, + "mode", SP_KNOT_MODE_XOR, + NULL); + + knot->fill [SP_KNOT_STATE_NORMAL] = 0xffffff00; + knot->stroke [SP_KNOT_STATE_NORMAL] = 0x000000ff; + knot->stroke [SP_KNOT_STATE_DRAGGING] = 0x000000ff; + knot->stroke [SP_KNOT_STATE_MOUSEOVER] = 0x000000ff; + + g_object_set(G_OBJECT(knot), + "shape", SP_KNOT_SHAPE_DIAMOND, + "size", 10, + "tip", _("Connector endpoint: drag to reroute or connect to new shapes"), + NULL); + + gtk_signal_connect (GTK_OBJECT (knot->item), "event", + GTK_SIGNAL_FUNC (cc_generic_knot_handler), knot); + + cc->endpt_handle[i] = knot; + } + + // Remove any existing handlers + if (cc->endpt_handler_id[i]) { + g_signal_handlers_disconnect_by_func( + G_OBJECT(cc->endpt_handle[i]->item), + (void*)G_CALLBACK(endpt_handler), (gpointer) cc ); + cc->endpt_handler_id[i] = 0; + } + + // Setup handlers for connector endpoints, this is + // is as 'after' so that cc_generic_knot_handler is + // triggered first for any endpoint. + cc->endpt_handler_id[i] = g_signal_connect_after( + G_OBJECT(cc->endpt_handle[i]->item), "event", + G_CALLBACK(endpt_handler), cc); + } + + NR::Point startpt = sp_curve_first_point(curve) * i2d; + sp_knot_set_position(cc->endpt_handle[0], &startpt, 0); + + NR::Point endpt = sp_curve_last_point(curve) * i2d; + sp_knot_set_position(cc->endpt_handle[1], &endpt, 0); + + sp_knot_show(cc->endpt_handle[0]); + sp_knot_show(cc->endpt_handle[1]); +} + + +static bool cc_item_is_shape(SPItem *item) +{ + if (SP_IS_PATH(item)) { + SPCurve *curve = (SP_SHAPE(item))->curve; + if ( curve && !(curve->closed) ) { + // Open paths are connectors. + return false; + } + } + return true; +} + + +bool cc_item_is_connector(SPItem *item) +{ + if (SP_IS_PATH(item)) { + if (SP_PATH(item)->connEndPair.isAutoRoutingConn()) { + g_assert( !(SP_SHAPE(item)->curve->closed) ); + return true; + } + } + return false; +} + + +void cc_selection_set_avoid(bool const set_avoid) +{ + SPDesktop *desktop = inkscape_active_desktop(); + if (desktop == NULL) { + return; + } + + SPDocument *document = SP_DT_DOCUMENT(desktop); + + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + + GSList *l = (GSList *) selection->itemList(); + + int changes = 0; + + while (l) { + SPItem *item = (SPItem *) l->data; + + char const *value = (set_avoid) ? "true" : NULL; + + if (cc_item_is_shape(item)) { + sp_object_setAttribute(item, "inkscape:connector-avoid", + value, false); + item->avoidRef->handleSettingChange(); + changes++; + } + + l = l->next; + } + + if (changes == 0) { + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, + _("Select at least one non-connector object.")); + return; + } + + sp_document_done(document); +} + + +static void +cc_selection_changed(Inkscape::Selection *selection, gpointer data) +{ + SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(data); + //SPEventContext *ec = SP_EVENT_CONTEXT(cc); + + SPItem *item = selection->singleItem(); + + if (cc->active_conn == item) + { + // Noting to change. + return; + } + if (item == NULL) + { + cc_clear_active_conn(cc); + return; + } + + if (cc_item_is_connector(item)) { + cc_set_active_conn(cc, item); + } +} + + +static void +shape_event_attr_deleted(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, + Inkscape::XML::Node *ref, gpointer data) +{ + g_assert(data); + SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(data); + + if (child == cc->active_shape_repr) { + // The active shape has been deleted. Clear active shape. + cc_clear_active_shape(cc); + } +} + + +static void +shape_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name, + gchar const *old_value, gchar const *new_value, + bool is_interactive, gpointer data) +{ + g_assert(data); + SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(data); + + // Look for changes than result in onscreen movement. + if (!strcmp(name, "d") || !strcmp(name, "x") || !strcmp(name, "y") || + !strcmp(name, "width") || !strcmp(name, "height") || + !strcmp(name, "transform")) + { + if (repr == cc->active_shape_repr) { + // Active shape has moved. Clear active shape. + cc_clear_active_shape(cc); + } + else if (repr == cc->active_conn_repr) { + // The active conn has been moved. + // Set it again, which just sets new handle positions. + cc_set_active_conn(cc, cc->active_conn); + } + } +} + + +/* + 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 : diff --git a/src/connector-context.h b/src/connector-context.h new file mode 100644 index 000000000..49c4c2a81 --- /dev/null +++ b/src/connector-context.h @@ -0,0 +1,112 @@ +#ifndef SEEN_CONNECTOR_CONTEXT_H +#define SEEN_CONNECTOR_CONTEXT_H + +/* + * Connector creation tool + * + * Authors: + * Michael Wybrow + * + * Copyright (C) 2005 Michael Wybrow + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#include "display/curve.h" +#include "event-context.h" +#include +#include +#include +#include "libavoid/connector.h" + + +#define SP_TYPE_CONNECTOR_CONTEXT (sp_connector_context_get_type()) +#define SP_CONNECTOR_CONTEXT(o) (G_TYPE_CHECK_INSTANCE_CAST((o), SP_TYPE_CONNECTOR_CONTEXT, SPConnectorContext)) +#define SP_CONNECTOR_CONTEXT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), SP_TYPE_CONNECTOR_CONTEXT, SPConnectorContextClass)) +#define SP_IS_CONNECTOR_CONTEXT(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), SP_TYPE_CONNECTOR_CONTEXT)) +#define SP_IS_CONNECTOR_CONTEXT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE((k), SP_TYPE_CONNECTOR_CONTEXT)) + +struct SPKnot; +namespace Inkscape +{ + class Selection; +} + +enum { + SP_CONNECTOR_CONTEXT_IDLE, + SP_CONNECTOR_CONTEXT_DRAGGING, + SP_CONNECTOR_CONTEXT_CLOSE, + SP_CONNECTOR_CONTEXT_STOP, + SP_CONNECTOR_CONTEXT_REROUTING +}; + + +struct SPConnectorContext : public SPEventContext { + Inkscape::Selection *selection; + NR::Point p[5]; + + /** \invar npoints in {0, 2}. */ + gint npoints; + + unsigned int mode : 1; + unsigned int state : 4; + + // Red curve + SPCanvasItem *red_bpath; + SPCurve *red_curve; + guint32 red_color; + + // Green curve + SPCurve *green_curve; + + // The new connector + SPItem *newconn; + Avoid::ConnRef *newConnRef; + + // The active shape + SPItem *active_shape; + Inkscape::XML::Node *active_shape_repr; + Inkscape::XML::Node *active_shape_layer_repr; + + // Same as above, but for the active connector + SPItem *active_conn; + Inkscape::XML::Node *active_conn_repr; + sigc::connection sel_changed_connection; + + + // The activehandle + SPKnot *active_handle; + + SPItem *clickeditem; + SPKnot *clickedhandle; + + SPKnot *connpthandle; + SPKnot *endpt_handle[2]; + guint endpt_handler_id[2]; + gchar *sid; + gchar *eid; + SPCanvasItem *c0, *c1, *cl0, *cl1; +}; + +struct SPConnectorContextClass : public SPEventContextClass { }; + +GType sp_connector_context_get_type(); + +void cc_selection_set_avoid(bool const set_ignore); +bool cc_item_is_connector(SPItem *item); + + +#endif /* !SEEN_CONNECTOR_CONTEXT_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:encoding=utf-8:textwidth=99 : diff --git a/src/context-fns.cpp b/src/context-fns.cpp new file mode 100644 index 000000000..42100d49a --- /dev/null +++ b/src/context-fns.cpp @@ -0,0 +1,192 @@ +#include +#include "sp-item.h" +#include "desktop.h" +#include "message-context.h" +#include "message-stack.h" +#include "context-fns.h" +#include "snap.h" +#include "desktop-affine.h" +#include "event-context.h" + +/* FIXME: could probably use a template here */ + +/** + * Check to see if the current layer is both unhidden and unlocked. If not, + * set a message about it on the given context. + * + * \param desktop Desktop. + * \param message Message context to put messages on. + * \return true if the current layer is both unhidden and unlocked, otherwise false. + */ + +bool Inkscape::have_viable_layer(SPDesktop *desktop, MessageContext *message) +{ + SPItem const *layer = SP_ITEM(desktop->currentLayer()); + + if ( !layer || desktop->itemIsHidden(layer) ) { + message->flash(Inkscape::ERROR_MESSAGE, + _("Current layer is hidden. Unhide it to be able to draw on it.")); + return false; + } + + if ( !layer || layer->isLocked() ) { + message->flash(Inkscape::ERROR_MESSAGE, + _("Current layer is locked. Unlock it to be able to draw on it.")); + return false; + } + + return true; +} + + +/** + * Check to see if the current layer is both unhidden and unlocked. If not, + * set a message about it on the given context. + * + * \param desktop Desktop. + * \param message Message context to put messages on. + * \return true if the current layer is both unhidden and unlocked, otherwise false. + */ + +bool Inkscape::have_viable_layer(SPDesktop *desktop, MessageStack *message) +{ + SPItem const *layer = SP_ITEM(desktop->currentLayer()); + + if ( !layer || desktop->itemIsHidden(layer) ) { + message->flash(Inkscape::WARNING_MESSAGE, + _("Current layer is hidden. Unhide it to be able to draw on it.")); + return false; + } + + if ( !layer || layer->isLocked() ) { + message->flash(Inkscape::WARNING_MESSAGE, + _("Current layer is locked. Unlock it to be able to draw on it.")); + return false; + } + + return true; +} + + +NR::Rect Inkscape::snap_rectangular_box(SPDesktop const *desktop, SPItem *item, + NR::Point const &pt, NR::Point const ¢er, int state) +{ + NR::Point p[2]; + + bool const shift = state & GDK_SHIFT_MASK; + bool const control = state & GDK_CONTROL_MASK; + + SnapManager const m(desktop->namedview); + + if (control) { + + /* Control is down: we are constrained to producing integer-ratio rectangles */ + + /* Vector from the centre of the box to the point we are dragging to */ + NR::Point delta = pt - center; + + /* Round it so that we have an integer-ratio box */ + if (fabs(delta[NR::X]) > fabs(delta[NR::Y]) && (delta[NR::Y] != 0.0)) { + delta[NR::X] = floor(delta[NR::X] / delta[NR::Y] + 0.5) * delta[NR::Y]; + } else if (delta[NR::X] != 0.0) { + delta[NR::Y] = floor(delta[NR::Y] / delta[NR::X] + 0.5) * delta[NR::X]; + } + + /* p[1] is the dragged point with the integer-ratio constraint */ + p[1] = center + delta; + + if (shift) { + + /* Shift is down, so our origin is the centre point rather than the corner + ** point; this means that corner-point movements are bound to each other. + */ + + /* p[0] is the opposite corner of our box */ + p[0] = center - delta; + + Inkscape::SnappedPoint s[2]; + + /* Try to snap p[0] (the opposite corner) along the constraint vector */ + s[0] = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, + p[0], p[0] - p[1], item); + + /* Try to snap p[1] (the dragged corner) along the constraint vector */ + s[1] = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, + p[1], p[1] - p[0], item); + + /* Choose the best snap and update points accordingly */ + if (s[0].getDistance() < s[1].getDistance()) { + p[0] = s[0].getPoint(); + p[1] = 2 * center - s[0].getPoint(); + } else { + p[0] = 2 * center - s[1].getPoint(); + p[1] = s[1].getPoint(); + } + + } else { + + /* Our origin is the opposite corner. Snap the drag point along the constraint vector */ + p[0] = center; + p[1] = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, p[1], p[1] - p[0], item).getPoint(); + } + + } else if (shift) { + + /* Shift is down, so our origin is the centre point rather than the corner point; + ** this means that corner-point movements are bound to each other. + */ + + p[1] = pt; + p[0] = 2 * center - p[1]; + + Inkscape::SnappedPoint s[2]; + + s[0] = m.freeSnap(Inkscape::Snapper::SNAP_POINT, p[0], item); + s[1] = m.freeSnap(Inkscape::Snapper::SNAP_POINT, p[1], item); + + if (s[0].getDistance() < s[1].getDistance()) { + p[0] = s[0].getPoint(); + p[1] = 2 * center - s[0].getPoint(); + } else { + p[0] = 2 * center - s[1].getPoint(); + p[1] = s[1].getPoint(); + } + + } else { + + /* There's no constraint on the corner point, so just snap it to anything */ + p[0] = center; + p[1] = m.freeSnap(Inkscape::Snapper::SNAP_POINT, pt, item).getPoint(); + } + + p[0] = sp_desktop_dt2root_xy_point(desktop, p[0]); + p[1] = sp_desktop_dt2root_xy_point(desktop, p[1]); + + return NR::Rect(NR::Point(MIN(p[0][NR::X], p[1][NR::X]), MIN(p[0][NR::Y], p[1][NR::Y])), + NR::Point(MAX(p[0][NR::X], p[1][NR::X]), MAX(p[0][NR::Y], p[1][NR::Y]))); +} + + + +NR::Point Inkscape::setup_for_drag_start(SPDesktop *desktop, SPEventContext* ec, GdkEvent *ev) +{ + ec->xp = static_cast(ev->button.x); + ec->yp = static_cast(ev->button.y); + ec->within_tolerance = true; + + NR::Point const p(ev->button.x, ev->button.y); + ec->item_to_select = sp_event_context_find_item(desktop, p, ev->button.state & GDK_MOD1_MASK, TRUE); + return ec->desktop->w2d(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 : diff --git a/src/context-fns.h b/src/context-fns.h new file mode 100644 index 000000000..beb132ca9 --- /dev/null +++ b/src/context-fns.h @@ -0,0 +1,27 @@ +#include +struct SPDesktop; + +namespace Inkscape +{ + +class MessageContext; +class MessageStack; + +extern bool have_viable_layer(SPDesktop *desktop, MessageContext *message); +extern bool have_viable_layer(SPDesktop *desktop, MessageStack *message); +NR::Rect snap_rectangular_box(SPDesktop const *desktop, SPItem *item, + NR::Point const &pt, NR::Point const ¢er, int state); +NR::Point setup_for_drag_start(SPDesktop *desktop, SPEventContext* ec, GdkEvent *ev); + +} + +/* + 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 : diff --git a/src/debug/.cvsignore b/src/debug/.cvsignore new file mode 100644 index 000000000..38efca7bc --- /dev/null +++ b/src/debug/.cvsignore @@ -0,0 +1,3 @@ +.deps +.dirstamp +makefile diff --git a/src/debug/Makefile_insert b/src/debug/Makefile_insert new file mode 100644 index 000000000..ec4e0ceea --- /dev/null +++ b/src/debug/Makefile_insert @@ -0,0 +1,15 @@ + +debug/all: debug/libinkdebug.a + +debug/clean: + rm -f debug/libinkdebug.a $(debug_libinkdebug_a_OBJECTS) + +debug_libinkdebug_a_SOURCES = \ + debug/event.h \ + debug/event-tracker.h \ + debug/heap.cpp debug/heap.h \ + debug/gc-heap.h \ + debug/logger.cpp debug/logger.h \ + debug/simple-event.h \ + debug/sysv-heap.cpp debug/sysv-heap.h + diff --git a/src/debug/event-tracker.h b/src/debug/event-tracker.h new file mode 100644 index 000000000..362175f94 --- /dev/null +++ b/src/debug/event-tracker.h @@ -0,0 +1,224 @@ +/* + * Inkscape::Debug::EventTracker - semi-automatically track event lifetimes + * + * Authors: + * MenTaLguY + * + * Copyright (C) 2005 MenTaLguY + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SEEN_INKSCAPE_DEBUG_EVENT_TRACKER_H +#define SEEN_INKSCAPE_DEBUG_EVENT_TRACKER_H + +#include "debug/logger.h" + +namespace Inkscape { + +namespace Debug { + +struct NoInitialEvent {}; + +template class EventTracker; + +class EventTrackerBase { +public: + ~EventTrackerBase() { + if (_active) { + Logger::finish(); + } + } + + template + inline void set() { + if (_active) { + Logger::finish(); + } + Logger::start(); + _active = true; + } + + template + inline void set(A const &a) { + if (_active) { + Logger::finish(); + } + Logger::start(a); + _active = true; + } + + template + inline void set(A const &a, B const &b) { + if (_active) { + Logger::finish(); + } + Logger::start(a, b); + _active = true; + } + + template + inline void set(A const &a, B const &b, C const &c) { + if (_active) { + Logger::finish(); + } + Logger::start(a, b, c); + _active = true; + } + + template + inline void set(A const &a, B const &b, C const &c, D const &d) { + if (_active) { + Logger::finish(); + } + Logger::start(a, b, c, d); + _active = true; + } + + template + inline void set(A const &a, B const &b, C const &c, D const &d, E const &e) + { + if (_active) { + Logger::finish(); + } + Logger::start(a, b, c, d, e); + _active = true; + } + + template + inline void set(A const &a, B const &b, C const &c, + D const &d, E const &e, F const &f) + { + if (_active) { + Logger::finish(); + } + Logger::start(a, b, c, d, e, f); + _active = true; + } + + template + inline void set(A const &a, B const &b, C const &c, D const &d, + E const &e, F const &f, G const &g) + { + if (_active) { + Logger::finish(); + } + Logger::start(a, b, c, d, e, f, g); + _active = true; + } + + template + inline void set(A const &a, B const &b, C const &c, D const &d, + E const &e, F const &f, G const &g, H const &h) + { + if (_active) { + Logger::finish(); + } + Logger::start(a, b, c, d, e, f, g, h); + _active = true; + } + + void clear() { + if (_active) { + Logger::finish(); + _active = false; + } + } + +protected: + EventTrackerBase(bool active) : _active(active) {} + +private: + EventTrackerBase(EventTrackerBase const &); // no copy + void operator=(EventTrackerBase const &); // no assign + bool _active; +}; + +template class EventTracker : public EventTrackerBase { +public: + EventTracker() : EventTrackerBase(true) { Logger::start(); } + + template + EventTracker(A const &a) : EventTrackerBase(true) { + Logger::start(a); + } + + template + EventTracker(A const &a, B const &b) : EventTrackerBase(true) { + Logger::start(a, b); + } + + template + EventTracker(A const &a, B const &b, C const &c) : EventTrackerBase(true) { + Logger::start(a, b, c); + } + + template + EventTracker(A const &a, B const &b, C const &c, D const &d) + : EventTrackerBase(true) + { + Logger::start(a, b, c, d); + } + + template + EventTracker(A const &a, B const &b, C const &c, D const &d, E const &e) + : EventTrackerBase(true) + { + Logger::start(a, b, c, d, e); + } + + template + EventTracker(A const &a, B const &b, C const &c, D const &d, + E const &e, F const &f) + : EventTrackerBase(true) + { + Logger::start(a, b, c, d, e, f); + } + + template + EventTracker(A const &a, B const &b, C const &c, D const &d, + E const &e, F const &f, G const &g) + : EventTrackerBase(true) + { + Logger::start(a, b, c, d, e, f, g); + } + + template + EventTracker(A const &a, B const &b, C const &c, D const &d, + E const &e, F const &f, G const &g, H const &h) + : EventTrackerBase(true) + { + Logger::start(a, b, c, d, e, f, g, h); + } +}; + +template <> class EventTracker : public EventTrackerBase { +public: + EventTracker() : EventTrackerBase(false) {} +}; + +} + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/debug/event.h b/src/debug/event.h new file mode 100644 index 000000000..c3b3a83fb --- /dev/null +++ b/src/debug/event.h @@ -0,0 +1,75 @@ +/* + * Inkscape::Debug::Event - event for debug tracing + * + * Authors: + * MenTaLguY + * + * Copyright (C) 2005 MenTaLguY + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SEEN_INKSCAPE_DEBUG_EVENT_H +#define SEEN_INKSCAPE_DEBUG_EVENT_H + +#include +#include "util/shared-c-string-ptr.h" + +namespace Inkscape { + +namespace Debug { + +class Event { +public: + virtual ~Event() {} + + enum Category { + CORE=0, + XML, + SPOBJECT, + DOCUMENT, + REFCOUNT, + EXTENSION, + OTHER + }; + enum { N_CATEGORIES=OTHER+1 }; + + struct PropertyPair { + public: + PropertyPair() {} + PropertyPair(Util::SharedCStringPtr n, Util::SharedCStringPtr v) + : name(n), value(v) {} + PropertyPair(char const *n, Util::SharedCStringPtr v) + : name(Util::SharedCStringPtr::copy(n)), value(v) {} + PropertyPair(Util::SharedCStringPtr n, char const *v) + : name(n), value(Util::SharedCStringPtr::copy(v)) {} + PropertyPair(char const *n, char const *v) + : name(Util::SharedCStringPtr::copy(n)), + value(Util::SharedCStringPtr::copy(v)) {} + + Util::SharedCStringPtr name; + Util::SharedCStringPtr value; + }; + + static Category category() { return OTHER; } + + virtual Util::SharedCStringPtr name() const=0; + virtual unsigned propertyCount() const=0; + virtual PropertyPair property(unsigned property) const=0; +}; + +} + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/debug/gc-heap.h b/src/debug/gc-heap.h new file mode 100644 index 000000000..b3042432e --- /dev/null +++ b/src/debug/gc-heap.h @@ -0,0 +1,52 @@ +/* + * Inkscape::Debug::GCHeap - heap statistics for libgc heap + * + * Authors: + * MenTaLguY + * + * Copyright (C) 2004 MenTaLguY + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SEEN_INKSCAPE_DEBUG_GC_HEAP_H +#define SEEN_INKSCAPE_DEBUG_GC_HEAP_H + +#include "gc-core.h" +#include "debug/heap.h" + +namespace Inkscape { +namespace Debug { + +class GCHeap : public Debug::Heap { +public: + int features() const { + return SIZE_AVAILABLE | USED_AVAILABLE | GARBAGE_COLLECTED; + } + Util::SharedCStringPtr name() const { + return Util::SharedCStringPtr::coerce("libgc"); + } + Heap::Stats stats() const { + Stats stats; + stats.size = GC::Core::get_heap_size(); + stats.bytes_used = stats.size - GC::Core::get_free_bytes(); + return stats; + } + void force_collect() { GC::Core::gcollect(); } +}; + +} +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/debug/heap.cpp b/src/debug/heap.cpp new file mode 100644 index 000000000..c0452f26b --- /dev/null +++ b/src/debug/heap.cpp @@ -0,0 +1,65 @@ +/* + * Inkscape::Debug::Heap - interface for gathering heap statistics + * + * Authors: + * MenTaLguY + * + * Copyright (C) 2005 MenTaLguY + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "gc-alloc.h" +#include "debug/gc-heap.h" +#include "debug/sysv-heap.h" +#include + +namespace Inkscape { +namespace Debug { + +namespace { + +typedef std::vector > HeapCollection; + +HeapCollection &heaps() { + static bool is_initialized=false; + static HeapCollection heaps; + if (!is_initialized) { + heaps.push_back(new SysVHeap()); + heaps.push_back(new GCHeap()); + is_initialized = true; + } + return heaps; +} + +} + +unsigned heap_count() { + return heaps().size(); +} + +Heap *get_heap(unsigned i) { + return heaps()[i]; +} + +void register_extra_heap(Heap &heap) { + heaps().push_back(&heap); +} + +} +} + +/* + 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 : diff --git a/src/debug/heap.h b/src/debug/heap.h new file mode 100644 index 000000000..d07cf74a1 --- /dev/null +++ b/src/debug/heap.h @@ -0,0 +1,63 @@ +/* + * Inkscape::Debug::Heap - interface for gathering heap statistics + * + * Authors: + * MenTaLguY + * + * Copyright (C) 2005 MenTaLguY + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SEEN_INKSCAPE_DEBUG_HEAP_H +#define SEEN_INKSCAPE_DEBUG_HEAP_H + +#include +#include "util/shared-c-string-ptr.h" + +namespace Inkscape { + +namespace Debug { + +class Heap { +public: + virtual ~Heap() {} + + struct Stats { + std::size_t size; + std::size_t bytes_used; + }; + + enum { + SIZE_AVAILABLE = ( 1 << 0 ), + USED_AVAILABLE = ( 1 << 1 ), + GARBAGE_COLLECTED = ( 1 << 2 ) + }; + + virtual int features() const=0; + + virtual Util::SharedCStringPtr name() const=0; + virtual Stats stats() const=0; + virtual void force_collect()=0; +}; + +unsigned heap_count(); +Heap *get_heap(unsigned i); + +void register_extra_heap(Heap &heap); + +} + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/debug/logger.cpp b/src/debug/logger.cpp new file mode 100644 index 000000000..a3c7b0430 --- /dev/null +++ b/src/debug/logger.cpp @@ -0,0 +1,204 @@ +/* + * Inkscape::Debug::Logger - debug logging facility + * + * Authors: + * MenTaLguY + * + * Copyright (C) 2005 MenTaLguY + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#include +#include "debug/logger.h" +#include "debug/simple-event.h" +#include "gc-alloc.h" + +namespace Inkscape { + +namespace Debug { + +bool Logger::_enabled=false; +bool Logger::_category_mask[Event::N_CATEGORIES]; + +namespace { + +static void write_escaped_value(std::ostream &os, Util::SharedCStringPtr value) { + for ( char const *current=value ; *current ; ++current ) { + switch (*current) { + case '&': + os << "&"; + break; + case '"': + os << """; + break; + case '\'': + os << "'"; + break; + case '<': + os << "<"; + break; + case '>': + os << ">"; + break; + default: + os.put(*current); + } + } +} + +static void write_indent(std::ostream &os, unsigned depth) { + for ( unsigned i = 0 ; i < depth ; i++ ) { + os.write(" ", 2); + } +} + +static std::ofstream log_stream; +static bool empty_tag=false; +typedef std::vector > TagStack; +static TagStack &tag_stack() { + static TagStack stack; + return stack; +} + +static void do_shutdown() { + Debug::Logger::shutdown(); +} + +static bool equal_range(char const *c_string, + char const *start, char const *end) +{ + return !std::strncmp(start, c_string, end - start) && + !c_string[end - start]; +} + +static void set_category_mask(bool * const mask, char const *filter) { + if (!filter) { + for ( unsigned i = 0 ; i < Event::N_CATEGORIES ; i++ ) { + mask[i] = true; + } + return; + } else { + for ( unsigned i = 0 ; i < Event::N_CATEGORIES ; i++ ) { + mask[i] = false; + } + mask[Event::CORE] = true; + } + + char const *start; + char const *end; + start = end = filter; + while (*end) { + while ( *end && *end != ',' ) { end++; } + if ( start != end ) { + if (equal_range("CORE", start, end)) { + mask[Event::CORE] = true; + } else if (equal_range("XML", start, end)) { + mask[Event::XML] = true; + } else if (equal_range("SPOBJECT", start, end)) { + mask[Event::SPOBJECT] = true; + } else if (equal_range("DOCUMENT", start, end)) { + mask[Event::DOCUMENT] = true; + } else if (equal_range("REFCOUNT", start, end)) { + mask[Event::REFCOUNT] = true; + } else if (equal_range("EXTENSION", start, end)) { + mask[Event::EXTENSION] = true; + } else { + g_warning("Unknown debugging category %*s", end - start, start); + } + } + if (*end) { + start = end = end + 1; + } + } +} + +} + +void Logger::init() { + if (!_enabled) { + char const *log_filename=std::getenv("INKSCAPE_DEBUG_LOG"); + if (log_filename) { + log_stream.open(log_filename); + if (log_stream.is_open()) { + char const *log_filter=std::getenv("INKSCAPE_DEBUG_FILTER"); + set_category_mask(_category_mask, log_filter); + log_stream << "\n"; + log_stream.flush(); + _enabled = true; + start >(Util::SharedCStringPtr::coerce("session")); + std::atexit(&do_shutdown); + } + } + } +} + +void Logger::_start(Event const &event) { + Util::SharedCStringPtr name=event.name(); + + if (empty_tag) { + log_stream << ">\n"; + } + + write_indent(log_stream, tag_stack().size()); + + log_stream << "<" << name.cString(); + + unsigned property_count=event.propertyCount(); + for ( unsigned i = 0 ; i < property_count ; i++ ) { + Event::PropertyPair property=event.property(i); + log_stream << " " << property.name.cString() << "=\""; + write_escaped_value(log_stream, property.value); + log_stream << "\""; + } + + log_stream.flush(); + + tag_stack().push_back(name); + empty_tag = true; +} + +void Logger::_skip() { + tag_stack().push_back(Util::SharedCStringPtr()); +} + +void Logger::_finish() { + if (tag_stack().back()) { + if (empty_tag) { + log_stream << "/>\n"; + } else { + write_indent(log_stream, tag_stack().size() - 1); + log_stream << "\n"; + } + log_stream.flush(); + + empty_tag = false; + } + + tag_stack().pop_back(); +} + +void Logger::shutdown() { + if (_enabled) { + while (!tag_stack().empty()) { + finish(); + } + } +} + +} + +} + +/* + 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 : diff --git a/src/debug/logger.h b/src/debug/logger.h new file mode 100644 index 000000000..61f9c2764 --- /dev/null +++ b/src/debug/logger.h @@ -0,0 +1,171 @@ +/* + * Inkscape::Debug::Logger - debug logging facility + * + * Authors: + * MenTaLguY + * + * Copyright (C) 2005 MenTaLguY + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SEEN_INKSCAPE_DEBUG_LOGGER_H +#define SEEN_INKSCAPE_DEBUG_LOGGER_H + +#include "debug/event.h" + +namespace Inkscape { + +namespace Debug { + +class Logger { +public: + static void init(); + + template + inline static void start() { + if (_enabled) { + if (_category_mask[EventType::category()]) { + _start(EventType()); + } else { + _skip(); + } + } + } + + template + inline static void start(A const &a) { + if (_enabled) { + if (_category_mask[EventType::category()]) { + _start(EventType(a)); + } else { + _skip(); + } + } + } + + template + inline static void start(A const &a, B const &b) { + if (_enabled) { + if (_category_mask[EventType::category()]) { + _start(EventType(a, b)); + } else { + _skip(); + } + } + } + + template + inline static void start(A const &a, B const &b, C const &c) { + if (_enabled) { + if (_category_mask[EventType::category()]) { + _start(EventType(a, b, c)); + } else { + _skip(); + } + } + } + + template + inline static void start(A const &a, B const &b, C const &c, D const &d) { + if (_enabled) { + if (_category_mask[EventType::category()]) { + _start(EventType(a, b, c, d)); + } else { + _skip(); + } + } + } + + template + inline static void start(A const &a, B const &b, C const &c, + D const &d, E const &e) + { + if (_enabled) { + if (_category_mask[EventType::category()]) { + _start(EventType(a, b, c, d, e)); + } else { + _skip(); + } + } + } + + template + inline static void start(A const &a, B const &b, C const &c, + D const &d, E const &e, F const &f) + { + if (_enabled) { + if (_category_mask[EventType::category()]) { + _start(EventType(a, b, c, d, e, f)); + } else { + _skip(); + } + } + } + + template + inline static void start(A const &a, B const &b, C const &c, D const &d, + E const &e, F const &f, G const &g) + { + if (_enabled) { + if (_category_mask[EventType::category()]) { + _start(EventType(a, b, c, d, e, f, g)); + } else { + _skip(); + } + } + } + + template + inline static void start(A const &a, B const &b, C const &c, D const &d, + E const &e, F const &f, G const &g, H const &h) + { + if (_enabled) { + if (_category_mask[EventType::category()]) { + _start(EventType(a, b, c, d, e, f, g, h)); + } else { + _skip(); + } + } + } + + inline static void finish() { + if (_enabled) { + _finish(); + } + } + + static void shutdown(); + +private: + static bool _enabled; + + static void _start(Event const &event); + static void _skip(); + static void _finish(); + + static bool _category_mask[Event::N_CATEGORIES]; +}; + +} + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/debug/makefile.in b/src/debug/makefile.in new file mode 100644 index 000000000..89533f16d --- /dev/null +++ b/src/debug/makefile.in @@ -0,0 +1,17 @@ +# Convenience stub makefile to call the real Makefile. + +@SET_MAKE@ + +# Explicit so that it's the default rule. +all: + cd .. && $(MAKE) debug/all + +clean %.a %.o: + cd .. && $(MAKE) debug/$@ + +.PHONY: all clean + +OBJEXT = @OBJEXT@ + +.SUFFIXES: +.SUFFIXES: .a .$(OBJEXT) diff --git a/src/debug/simple-event.h b/src/debug/simple-event.h new file mode 100644 index 000000000..3a3adae3c --- /dev/null +++ b/src/debug/simple-event.h @@ -0,0 +1,51 @@ +/* + * Inkscape::Debug::SimpleEvent - trivial implementation of Debug::Event + * + * Authors: + * MenTaLguY + * + * Copyright (C) 2005 MenTaLguY + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SEEN_INKSCAPE_DEBUG_SIMPLE_EVENT_H +#define SEEN_INKSCAPE_DEBUG_SIMPLE_EVENT_H + +#include "debug/event.h" + +namespace Inkscape { + +namespace Debug { + +template +class SimpleEvent : public Event { +public: + SimpleEvent(Util::SharedCStringPtr name) : _name(name) {} + SimpleEvent(char const *name) : _name(Util::SharedCStringPtr::copy(name)) {} + + static Category category() { return C; } + + Util::SharedCStringPtr name() const { return _name; } + unsigned propertyCount() const { return 0; } + PropertyPair property(unsigned property) const { return PropertyPair(); } + +private: + Util::SharedCStringPtr _name; +}; + +} + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/debug/sysv-heap.cpp b/src/debug/sysv-heap.cpp new file mode 100644 index 000000000..9ca6ea549 --- /dev/null +++ b/src/debug/sysv-heap.cpp @@ -0,0 +1,79 @@ +/* + * Inkscape::Debug::SysVHeap - malloc() statistics via System V interface + * + * Authors: + * MenTaLguY + * + * Copyright (C) 2005 MenTaLguY + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef HAVE_MALLOC_H +# include +#endif + +#include "debug/sysv-heap.h" + +namespace Inkscape { +namespace Debug { + +int SysVHeap::features() const { +#ifdef HAVE_MALLINFO + return SIZE_AVAILABLE | USED_AVAILABLE; +#else + return 0; +#endif +} + +Heap::Stats SysVHeap::stats() const { + Stats stats = { 0, 0 }; + +#ifdef HAVE_MALLINFO + struct mallinfo info=mallinfo(); + +#ifdef HAVE_STRUCT_MALLINFO_USMBLKS + stats.size += info.usmblks; + stats.bytes_used += info.usmblks; +#endif + +#ifdef HAVE_STRUCT_MALLINFO_FSMBLKS + stats.size += info.fsmblks; +#endif + +#ifdef HAVE_STRUCT_MALLINFO_UORDBLKS + stats.size += info.uordblks; + stats.bytes_used += info.uordblks; +#endif + +#ifdef HAVE_STRUCT_MALLINFO_FORDBLKS + stats.size += info.fordblks; +#endif + +#ifdef HAVE_STRUCT_MALLINFO_HBLKHD + stats.size += info.hblkhd; + stats.bytes_used += info.hblkhd; +#endif + +#endif + + return stats; +} + +} +} + +/* + 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 : diff --git a/src/debug/sysv-heap.h b/src/debug/sysv-heap.h new file mode 100644 index 000000000..2ba24b2b5 --- /dev/null +++ b/src/debug/sysv-heap.h @@ -0,0 +1,47 @@ +/* + * Inkscape::Debug::SysVHeap - malloc() statistics via System V interface + * + * Authors: + * MenTaLguY + * + * Copyright (C) 2005 MenTaLguY + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SEEN_INKSCAPE_DEBUG_SYSV_HEAP_H +#define SEEN_INKSCAPE_DEBUG_SYSV_HEAP_H + +#include "debug/heap.h" + +namespace Inkscape { +namespace Debug { + +class SysVHeap : public Heap { +public: + SysVHeap() {} + + int features() const; + + Util::SharedCStringPtr name() const { + return Util::SharedCStringPtr::coerce("standard malloc()"); + } + Stats stats() const; + void force_collect() {} +}; + +} +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/decimal-round.h b/src/decimal-round.h new file mode 100644 index 000000000..6b412685e --- /dev/null +++ b/src/decimal-round.h @@ -0,0 +1,44 @@ +#ifndef SEEN_DECIMAL_ROUND_H +#define SEEN_DECIMAL_ROUND_H + +#include + +#include "round.h" + + +namespace Inkscape { + +/** Returns x rounded to the nearest \a nplaces decimal places. + + Implemented in terms of Inkscape::round, i.e. we make no guarantees as to what happens if x is + half way between two rounded numbers. Add a note to the documentation if you care which + candidate is chosen for such case (round towards -infinity, +infinity, 0, or "even"). + + Note: nplaces is the number of decimal places without using scientific (e) notation, not the + number of significant figures. This function may not be suitable for values of x whose + magnitude is so far from 1 that one would want to use scientific (e) notation. + + The current implementation happens to allow nplaces to be negative: e.g. nplaces=-2 means + rounding to a multiple of 100. May or may not be useful. +**/ +inline double +decimal_round(double const x, int const nplaces) +{ + double const multiplier = std::pow(10.0, nplaces); + return Inkscape::round( x * multiplier ) / multiplier; +} + +} + +#endif /* !SEEN_DECIMAL_ROUND_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 : diff --git a/src/desktop-affine.cpp b/src/desktop-affine.cpp new file mode 100644 index 000000000..7ad2c7bfe --- /dev/null +++ b/src/desktop-affine.cpp @@ -0,0 +1,40 @@ +#define __SP_DESKTOP_AFFINE_C__ + +/* + * Editable view and widget implementation + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "desktop.h" +#include "document.h" +#include "sp-root.h" +#include "libnr/nr-matrix-ops.h" + +NR::Matrix const sp_desktop_root2dt_affine (SPDesktop const *dt) +{ + SPRoot const *root = SP_ROOT(SP_DOCUMENT_ROOT(dt->doc())); + return root->c2p * dt->doc2dt(); +} + +NR::Matrix const sp_desktop_dt2root_affine (SPDesktop const *dt) +{ + return sp_desktop_root2dt_affine(dt).inverse(); +} + +NR::Point sp_desktop_root2dt_xy_point(SPDesktop const *dt, NR::Point const p) +{ + return p * sp_desktop_root2dt_affine(dt); +} + +NR::Point sp_desktop_dt2root_xy_point(SPDesktop const *dt, NR::Point const p) +{ + return p * sp_desktop_dt2root_affine(dt); +} + diff --git a/src/desktop-affine.h b/src/desktop-affine.h new file mode 100644 index 000000000..00dc9faf0 --- /dev/null +++ b/src/desktop-affine.h @@ -0,0 +1,36 @@ +#ifndef __SP_DESKTOP_AFFINE_H__ +#define __SP_DESKTOP_AFFINE_H__ + +/* + * Desktop transformations + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "forward.h" +#include + +NR::Matrix const sp_desktop_root2dt_affine(SPDesktop const *dt); +NR::Matrix const sp_desktop_dt2root_affine(SPDesktop const *dt); + +NR::Point sp_desktop_root2dt_xy_point(SPDesktop const *dt, const NR::Point p); +NR::Point sp_desktop_dt2root_xy_point(SPDesktop const *dt, const NR::Point p); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/desktop-events.cpp b/src/desktop-events.cpp new file mode 100644 index 000000000..226b9d997 --- /dev/null +++ b/src/desktop-events.cpp @@ -0,0 +1,452 @@ +#define __SP_DESKTOP_EVENTS_C__ + +/* + * Event handlers for SPDesktop + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include +#endif +#include +#include +#include +#include +#include +#include +#include "display/guideline.h" +#include "helper/unit-menu.h" +#include "helper/units.h" +#include "desktop.h" +#include "document.h" +#include "sp-guide.h" +#include "sp-namedview.h" +#include "desktop-handles.h" +#include "event-context.h" +#include "widgets/desktop-widget.h" +#include "sp-metrics.h" +#include +#include "dialogs/dialog-events.h" +#include "message-context.h" +#include "xml/repr.h" + +static void sp_dt_simple_guide_dialog(SPGuide *guide, SPDesktop *desktop); + + +/* Root item handler */ + + +int sp_desktop_root_handler(SPCanvasItem *item, GdkEvent *event, SPDesktop *desktop) +{ + return sp_event_context_root_handler(desktop->event_context, event); +} + +/* + * fixme: this conatins a hack, to deal with deleting a view, which is + * completely on another view, in which case active_desktop will not be updated + * + */ + +int sp_desktop_item_handler(SPCanvasItem *item, GdkEvent *event, gpointer data) +{ + gpointer ddata = gtk_object_get_data(GTK_OBJECT(item->canvas), "SPDesktop"); + g_return_val_if_fail(ddata != NULL, FALSE); + + SPDesktop *desktop = static_cast(ddata); + + return sp_event_context_item_handler(desktop->event_context, SP_ITEM(data), event); +} + + +static gint sp_dt_ruler_event(GtkWidget *widget, GdkEvent *event, SPDesktopWidget *dtw, bool horiz) +{ + static bool dragging = false; + static SPCanvasItem *guide = NULL; + int wx, wy; + + SPDesktop *desktop = dtw->desktop; + Inkscape::XML::Node *repr = SP_OBJECT_REPR(desktop->namedview); + + gdk_window_get_pointer(GTK_WIDGET(dtw->canvas)->window, &wx, &wy, NULL); + NR::Point const event_win(wx, wy); + + switch (event->type) { + case GDK_BUTTON_PRESS: + if (event->button.button == 1) { + dragging = true; + NR::Point const event_w(sp_canvas_window_to_world(dtw->canvas, event_win)); + NR::Point const event_dt(desktop->w2d(event_w)); + + // explicitly show guidelines; if I draw a guide, I want them on + sp_repr_set_boolean(repr, "showguides", TRUE); + sp_repr_set_boolean(repr, "inkscape:guide-bbox", TRUE); + + double const guide_pos_dt = event_dt[ horiz + ? NR::Y + : NR::X ]; + guide = sp_guideline_new(desktop->guides, guide_pos_dt, !horiz); + sp_guideline_set_color(SP_GUIDELINE(guide), desktop->namedview->guidehicolor); + gdk_pointer_grab(widget->window, FALSE, + (GdkEventMask)(GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK), + NULL, NULL, + event->button.time); + } + break; + case GDK_MOTION_NOTIFY: + if (dragging) { + NR::Point const event_w(sp_canvas_window_to_world(dtw->canvas, event_win)); + NR::Point const event_dt(desktop->w2d(event_w)); + double const guide_pos_dt = event_dt[ horiz + ? NR::Y + : NR::X ]; + sp_guideline_set_position(SP_GUIDELINE(guide), guide_pos_dt); + desktop->set_coordinate_status(event_dt); + desktop->setPosition (event_dt); + } + break; + case GDK_BUTTON_RELEASE: + if (dragging && event->button.button == 1) { + gdk_pointer_ungrab(event->button.time); + NR::Point const event_w(sp_canvas_window_to_world(dtw->canvas, event_win)); + NR::Point const event_dt(desktop->w2d(event_w)); + dragging = false; + gtk_object_destroy(GTK_OBJECT(guide)); + guide = NULL; + if ( ( horiz + ? wy + : wx ) + >= 0 ) + { + Inkscape::XML::Node *repr = sp_repr_new("sodipodi:guide"); + repr->setAttribute("orientation", (horiz) ? "horizontal" : "vertical"); + double const guide_pos_dt = event_dt[ horiz + ? NR::Y + : NR::X ]; + sp_repr_set_svg_double(repr, "position", guide_pos_dt); + SP_OBJECT_REPR(desktop->namedview)->appendChild(repr); + Inkscape::GC::release(repr); + sp_document_done(SP_DT_DOCUMENT(desktop)); + } + desktop->set_coordinate_status(event_dt); + } + default: + break; + } + + return FALSE; +} + +int sp_dt_hruler_event(GtkWidget *widget, GdkEvent *event, SPDesktopWidget *dtw) +{ + return sp_dt_ruler_event(widget, event, dtw, true); +} + +int sp_dt_vruler_event(GtkWidget *widget, GdkEvent *event, SPDesktopWidget *dtw) +{ + return sp_dt_ruler_event(widget, event, dtw, false); +} + +/* Guides */ + +gint sp_dt_guide_event(SPCanvasItem *item, GdkEvent *event, gpointer data) +{ + static bool dragging = false; + static bool moved = false; + gint ret = FALSE; + + SPGuide *guide = SP_GUIDE(data); + SPDesktop *desktop = static_cast(gtk_object_get_data(GTK_OBJECT(item->canvas), "SPDesktop")); + + switch (event->type) { + case GDK_2BUTTON_PRESS: + if (event->button.button == 1) { + dragging = false; + sp_canvas_item_ungrab(item, event->button.time); + sp_dt_simple_guide_dialog(guide, desktop); + ret = TRUE; + } + break; + case GDK_BUTTON_PRESS: + if (event->button.button == 1) { + dragging = true; + sp_canvas_item_grab(item, + ( GDK_BUTTON_RELEASE_MASK | + GDK_BUTTON_PRESS_MASK | + GDK_POINTER_MOTION_MASK | + GDK_POINTER_MOTION_HINT_MASK ), + NULL, + event->button.time); + ret = TRUE; + } + break; + case GDK_MOTION_NOTIFY: + if (dragging) { + NR::Point const motion_w(event->motion.x, + event->motion.y); + NR::Point const motion_dt(desktop->w2d(motion_w)); + sp_guide_moveto(*guide, sp_guide_position_from_pt(guide, motion_dt), false); + moved = true; + desktop->set_coordinate_status(motion_dt); + desktop->setPosition (motion_dt); + ret = TRUE; + } + break; + case GDK_BUTTON_RELEASE: + if (dragging && event->button.button == 1) { + if (moved) { + NR::Point const event_w(event->button.x, + event->button.y); + NR::Point const event_dt(desktop->w2d(event_w)); + if (sp_canvas_world_pt_inside_window(item->canvas, event_w)) { + sp_guide_moveto(*guide, sp_guide_position_from_pt(guide, event_dt), true); + } else { + /* Undo movement of any attached shapes. */ + sp_guide_moveto(*guide, guide->position, false); + sp_guide_remove(guide); + } + moved = false; + sp_document_done(SP_DT_DOCUMENT(desktop)); + desktop->set_coordinate_status(event_dt); + desktop->setPosition (event_dt); + } + dragging = false; + sp_canvas_item_ungrab(item, event->button.time); + ret=TRUE; + } + case GDK_ENTER_NOTIFY: + { + + sp_guideline_set_color(SP_GUIDELINE(item), guide->hicolor); + + GString *position_string = SP_PX_TO_METRIC_STRING(guide->position, desktop->namedview->getDefaultMetric()); + char *guide_description = sp_guide_description(guide); + + desktop->guidesMessageContext()->setF(Inkscape::NORMAL_MESSAGE, _("%s at %s"), guide_description, position_string->str); + + g_free(guide_description); + g_string_free(position_string, TRUE); + break; + } + case GDK_LEAVE_NOTIFY: + sp_guideline_set_color(SP_GUIDELINE(item), guide->color); + desktop->guidesMessageContext()->clear(); + break; + default: + break; + } + + return ret; +} + + + +/* + * simple guideline dialog + */ + +static GtkWidget *d = NULL; +static GtkWidget *l1; +static GtkWidget *l2; +static GtkWidget *e; +static GtkWidget *u; +static GtkWidget *m; +static gdouble oldpos; +static bool mode; +static gpointer g; + + +static void guide_dialog_mode_changed(GtkWidget *widget) +{ + if (mode) { + // TRANSLATORS: This string appears when double-clicking on a guide. + // This is the distance by which the guide is to be moved. + gtk_label_set_text(GTK_LABEL(m), _(" relative by ")); + mode = false; + } else { + // TRANSLATORS: This string appears when double-clicking on a guide. + // This is the target location where the guide is to be moved. + gtk_label_set_text(GTK_LABEL(m), _(" absolute to ")); + mode = true; + } +} + +static void guide_dialog_close(GtkWidget *widget, gpointer data) +{ + gtk_widget_hide(GTK_WIDGET(d)); +} + +static void guide_dialog_apply(SPGuide &guide) +{ + gdouble const raw_dist = gtk_spin_button_get_value_as_float(GTK_SPIN_BUTTON(e)); + SPUnit const &unit = *sp_unit_selector_get_unit(SP_UNIT_SELECTOR(u)); + gdouble const points = sp_units_get_pixels(raw_dist, unit); + gdouble const newpos = ( mode + ? points + : guide.position + points ); + sp_guide_moveto(guide, newpos, true); + sp_document_done(SP_OBJECT_DOCUMENT(&guide)); +} + +static void guide_dialog_ok(GtkWidget *widget, gpointer g) +{ + SPGuide &guide = **static_cast(g); + guide_dialog_apply(guide); + guide_dialog_close(NULL, GTK_DIALOG(widget)); +} + +static void guide_dialog_delete(GtkWidget *widget, SPGuide **guide) +{ + SPDocument *doc = SP_OBJECT_DOCUMENT(*guide); + sp_guide_remove(*guide); + sp_document_done(doc); + guide_dialog_close(NULL, GTK_DIALOG(widget)); +} + +static void guide_dialog_response(GtkDialog *dialog, gint response, gpointer data) +{ + GtkWidget *widget = GTK_WIDGET(dialog); + + switch (response) { + case GTK_RESPONSE_OK: + guide_dialog_ok(widget, data); + break; + case -12: + guide_dialog_delete(widget, (SPGuide**) data); + break; + case GTK_RESPONSE_CLOSE: + guide_dialog_close(widget, (GtkDialog*) data); + break; + case GTK_RESPONSE_DELETE_EVENT: + break; +/* case GTK_RESPONSE_APPLY: + guide_dialog_apply(widget, data); + break; +*/ + default: + g_assert_not_reached(); + } +} + +static void sp_dt_simple_guide_dialog(SPGuide *guide, SPDesktop *desktop) +{ + if (!GTK_IS_WIDGET(d)) { + // create dialog + d = gtk_dialog_new_with_buttons(_("Guideline"), + NULL, + GTK_DIALOG_MODAL, + GTK_STOCK_OK, + GTK_RESPONSE_OK, + GTK_STOCK_DELETE, + -12, /* DELETE */ + GTK_STOCK_CLOSE, + GTK_RESPONSE_CLOSE, + NULL); + sp_transientize(d); + gtk_widget_hide(d); + + GtkWidget *b1 = gtk_hbox_new(FALSE, 4); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(d)->vbox), b1, FALSE, FALSE, 0); + gtk_container_set_border_width(GTK_CONTAINER(b1), 4); + gtk_widget_show(b1); + + GtkWidget *b2 = gtk_vbox_new(FALSE, 4); + gtk_box_pack_end(GTK_BOX(b1), b2, TRUE, TRUE, 0); + gtk_widget_show(b2); + + //labels + GtkWidget *b3 = gtk_hbox_new(FALSE, 4); + gtk_box_pack_start(GTK_BOX(b2), b3, TRUE, TRUE, 0); + gtk_widget_show(b3); + + l1 = gtk_label_new("foo1"); + gtk_box_pack_start(GTK_BOX(b3), l1, TRUE, TRUE, 0); + gtk_misc_set_alignment(GTK_MISC(l1), 1.0, 0.5); + gtk_widget_show(l1); + + l2 = gtk_label_new("foo2"); + gtk_box_pack_start(GTK_BOX(b3), l2, TRUE, TRUE, 0); + gtk_misc_set_alignment(GTK_MISC(l2), 0.0, 0.5); + gtk_widget_show(l2); + + GtkWidget *b4 = gtk_hbox_new(FALSE, 4); + gtk_box_pack_start(GTK_BOX(b2), b4, FALSE, FALSE, 0); + gtk_widget_show(b4); + // mode button + GtkWidget *but = gtk_button_new(); + gtk_button_set_relief(GTK_BUTTON(but), GTK_RELIEF_NONE); + gtk_box_pack_start(GTK_BOX(b4), but, FALSE, TRUE, 0); + gtk_signal_connect_while_alive(GTK_OBJECT(but), "clicked", GTK_SIGNAL_FUNC(guide_dialog_mode_changed), + NULL , GTK_OBJECT(but)); + gtk_widget_show(but); + m = gtk_label_new(_(" absolute to ")); + mode = true; + gtk_container_add(GTK_CONTAINER(but), m); + gtk_widget_show(m); + + // unitmenu + /* fixme: We should allow percents here too, as percents of the canvas size */ + u = sp_unit_selector_new(SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE); + sp_unit_selector_set_unit(SP_UNIT_SELECTOR(u), desktop->namedview->doc_units); + + // spinbutton + GtkObject *a = gtk_adjustment_new(0.0, -SP_DESKTOP_SCROLL_LIMIT, SP_DESKTOP_SCROLL_LIMIT, 1.0, 10.0, 10.0); + sp_unit_selector_add_adjustment(SP_UNIT_SELECTOR(u), GTK_ADJUSTMENT(a)); + e = gtk_spin_button_new(GTK_ADJUSTMENT(a), 1.0 , 2); + gtk_widget_show(e); + gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(e), TRUE); + gtk_box_pack_start(GTK_BOX(b4), e, TRUE, TRUE, 0); + gtk_signal_connect_object(GTK_OBJECT(e), "activate", + GTK_SIGNAL_FUNC(gtk_window_activate_default), + GTK_OBJECT(d)); +/* gnome_dialog_editable_enters(GNOME_DIALOG(d), GTK_EDITABLE(e)); */ + + gtk_widget_show(u); + gtk_box_pack_start(GTK_BOX(b4), u, FALSE, FALSE, 0); + + + // dialog + gtk_dialog_set_default_response(GTK_DIALOG(d), GTK_RESPONSE_OK); + gtk_signal_connect(GTK_OBJECT(d), "response", GTK_SIGNAL_FUNC(guide_dialog_response), &g); + gtk_signal_connect(GTK_OBJECT(d), "delete_event", GTK_SIGNAL_FUNC(gtk_widget_hide_on_delete), GTK_WIDGET(d)); + } + + // initialize dialog + g = guide; + oldpos = guide->position; + { + char *guide_description = sp_guide_description(guide); + char *label = g_strdup_printf(_("Move %s"), guide_description); + g_free(guide_description); + gtk_label_set(GTK_LABEL(l1), label); + g_free(label); + } + + SPUnit const &unit = *sp_unit_selector_get_unit(SP_UNIT_SELECTOR(u)); + gdouble const val = sp_pixels_get_units(oldpos, unit); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(e), val); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(e), val); + gtk_widget_grab_focus(e); + gtk_editable_select_region(GTK_EDITABLE(e), 0, 20); + gtk_window_set_position(GTK_WINDOW(d), GTK_WIN_POS_MOUSE); + + gtk_widget_show(d); +} + + +/* + 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 : + diff --git a/src/desktop-events.h b/src/desktop-events.h new file mode 100644 index 000000000..3964b5d07 --- /dev/null +++ b/src/desktop-events.h @@ -0,0 +1,55 @@ +#ifndef __DESKTOP_EVENTS_H__ +#define __DESKTOP_EVENTS_H__ + +/* + * Entry points for event distribution + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include + +class SPDesktop; +class SPDesktopWidget; +class SPCanvasItem; + +/* Item handlers */ + +int sp_desktop_root_handler (SPCanvasItem *item, GdkEvent *event, SPDesktop *desktop); +int sp_desktop_item_handler (SPCanvasItem *item, GdkEvent *event, gpointer data); + +/* Default handlers */ + +gint sp_canvas_enter_notify (GtkWidget *widget, GdkEventCrossing *event, SPDesktop *desktop); +gint sp_canvas_leave_notify (GtkWidget *widget, GdkEventCrossing *event, SPDesktop *desktop); +gint sp_canvas_motion_notify (GtkWidget *widget,GdkEventMotion *motion, SPDesktop *desktop); + +/* Rulers */ + +int sp_dt_hruler_event (GtkWidget *widget, GdkEvent *event, SPDesktopWidget *dtw); +int sp_dt_vruler_event (GtkWidget *widget, GdkEvent *event, SPDesktopWidget *dtw); + +/* Guides */ + +gint sp_dt_guide_event (SPCanvasItem *item, GdkEvent *event, gpointer data); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/desktop-handles.cpp b/src/desktop-handles.cpp new file mode 100644 index 000000000..75584c6ee --- /dev/null +++ b/src/desktop-handles.cpp @@ -0,0 +1,122 @@ +#define __SP_DESKTOP_HANDLES_C__ + +/* + * Frontends + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "display/sp-canvas.h" +#include "desktop.h" + +SPEventContext * +sp_desktop_event_context (SPDesktop const * desktop) +{ + g_return_val_if_fail (desktop != NULL, NULL); + + return desktop->event_context; +} + +Inkscape::Selection * +sp_desktop_selection (SPDesktop const * desktop) +{ + g_assert(desktop != NULL); + + return desktop->selection; +} + +SPDocument * +sp_desktop_document (SPDesktop const * desktop) +{ + g_return_val_if_fail (desktop != NULL, NULL); + + return desktop->doc(); +} + +SPCanvas * +sp_desktop_canvas (SPDesktop const * desktop) +{ + g_return_val_if_fail (desktop != NULL, NULL); + + return ((SPCanvasItem *) desktop->main)->canvas; +} + +SPCanvasItem * +sp_desktop_acetate (SPDesktop const * desktop) +{ + g_return_val_if_fail (desktop != NULL, NULL); + + return desktop->acetate; +} + +SPCanvasGroup * +sp_desktop_main (SPDesktop const * desktop) +{ + g_return_val_if_fail (desktop != NULL, NULL); + + return desktop->main; +} + +SPCanvasGroup * +sp_desktop_grid (SPDesktop const * desktop) +{ + g_return_val_if_fail (desktop != NULL, NULL); + + return desktop->grid; +} + +SPCanvasGroup * +sp_desktop_guides (SPDesktop const * desktop) +{ + g_return_val_if_fail (desktop != NULL, NULL); + + return desktop->guides; +} + +SPCanvasItem * +sp_desktop_drawing (SPDesktop const *desktop) +{ + g_return_val_if_fail (desktop != NULL, NULL); + + return desktop->drawing; +} + +SPCanvasGroup * +sp_desktop_sketch (SPDesktop const * desktop) +{ + g_return_val_if_fail (desktop != NULL, NULL); + + return desktop->sketch; +} + +SPCanvasGroup * +sp_desktop_controls (SPDesktop const * desktop) +{ + g_return_val_if_fail (desktop != NULL, NULL); + + return desktop->controls; +} + +Inkscape::MessageStack * +sp_desktop_message_stack (SPDesktop const * desktop) +{ + g_return_val_if_fail (desktop != NULL, NULL); + + return desktop->messageStack(); +} + +SPNamedView * +sp_desktop_namedview (SPDesktop const * desktop) +{ + g_return_val_if_fail (desktop != NULL, NULL); + + return desktop->namedview; +} + + diff --git a/src/desktop-handles.h b/src/desktop-handles.h new file mode 100644 index 000000000..0a7e5e7be --- /dev/null +++ b/src/desktop-handles.h @@ -0,0 +1,71 @@ +#ifndef __SP_DESKTOP_HANDLES_H__ +#define __SP_DESKTOP_HANDLES_H__ + +/* + * Frontends + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "display/display-forward.h" +#include "forward.h" + +namespace Inkscape { + class MessageStack; + class Selection; +} + +#define SP_DESKTOP_SCROLL_LIMIT 4000.0 +#define SP_DESKTOP_ZOOM_MAX 256.0 +#define SP_DESKTOP_ZOOM_MIN 0.01 + +#define SP_COORDINATES_UNDERLINE_NONE (0) +#define SP_COORDINATES_UNDERLINE_X (1 << NR::X) +#define SP_COORDINATES_UNDERLINE_Y (1 << NR::Y) + +#define SP_DT_EVENTCONTEXT(d) sp_desktop_event_context (d) +#define SP_DT_SELECTION(d) sp_desktop_selection (d) +#define SP_DT_DOCUMENT(d) sp_desktop_document (d) +#define SP_DT_CANVAS(d) sp_desktop_canvas (d) +#define SP_DT_ACETATE(d) sp_desktop_acetate (d) +#define SP_DT_MAIN(d) sp_desktop_main (d) +#define SP_DT_GRID(d) sp_desktop_grid (d) +#define SP_DT_GUIDES(d) sp_desktop_guides (d) +#define SP_DT_DRAWING(d) sp_desktop_drawing (d) +#define SP_DT_SKETCH(d) sp_desktop_sketch (d) +#define SP_DT_CONTROLS(d) sp_desktop_controls (d) +#define SP_DT_MSGSTACK(d) sp_desktop_message_stack (d) +#define SP_DT_NAMEDVIEW(d) sp_desktop_namedview (d) + +SPEventContext * sp_desktop_event_context (SPDesktop const * desktop); +Inkscape::Selection * sp_desktop_selection (SPDesktop const * desktop); +SPDocument * sp_desktop_document (SPDesktop const * desktop); +SPCanvas * sp_desktop_canvas (SPDesktop const * desktop); +SPCanvasItem * sp_desktop_acetate (SPDesktop const * desktop); +SPCanvasGroup * sp_desktop_main (SPDesktop const * desktop); +SPCanvasGroup * sp_desktop_grid (SPDesktop const * desktop); +SPCanvasGroup * sp_desktop_guides (SPDesktop const * desktop); +SPCanvasItem *sp_desktop_drawing (SPDesktop const *desktop); +SPCanvasGroup * sp_desktop_sketch (SPDesktop const * desktop); +SPCanvasGroup * sp_desktop_controls (SPDesktop const * desktop); +Inkscape::MessageStack * sp_desktop_message_stack (SPDesktop const * desktop); +SPNamedView * sp_desktop_namedview (SPDesktop const * desktop); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/desktop-style.cpp b/src/desktop-style.cpp new file mode 100644 index 000000000..540fd1637 --- /dev/null +++ b/src/desktop-style.cpp @@ -0,0 +1,1006 @@ +#define __SP_DESKTOP_STYLE_C__ + +/** \file + * Desktop style management + * + * Authors: + * bulia byak + * + * Copyright (C) 2004 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "desktop.h" +#include "color-rgba.h" +#include "svg/css-ostringstream.h" +#include "svg/svg.h" +#include "selection.h" +#include "sp-tspan.h" +#include "sp-textpath.h" +#include "inkscape.h" +#include "style.h" +#include "prefs-utils.h" +#include "sp-use.h" +#include "sp-flowtext.h" +#include "sp-flowregion.h" +#include "sp-flowdiv.h" +#include "sp-linear-gradient.h" +#include "sp-radial-gradient.h" +#include "sp-pattern.h" +#include "xml/repr.h" +#include "libnrtype/font-style-to-pos.h" + +#include "desktop-style.h" + +/** + * Set color on selection on desktop. + */ +void +sp_desktop_set_color(SPDesktop *desktop, ColorRGBA const &color, bool is_relative, bool fill) +{ + /// \todo relative color setting + if (is_relative) { + g_warning("FIXME: relative color setting not yet implemented"); + return; + } + + guint32 rgba = SP_RGBA32_F_COMPOSE(color[0], color[1], color[2], color[3]); + gchar b[64]; + sp_svg_write_color(b, 64, rgba); + SPCSSAttr *css = sp_repr_css_attr_new(); + if (fill) { + sp_repr_css_set_property(css, "fill", b); + Inkscape::CSSOStringStream osalpha; + osalpha << color[3]; + sp_repr_css_set_property(css, "fill-opacity", osalpha.str().c_str()); + } else { + sp_repr_css_set_property(css, "stroke", b); + Inkscape::CSSOStringStream osalpha; + osalpha << color[3]; + sp_repr_css_set_property(css, "stroke-opacity", osalpha.str().c_str()); + } + + sp_desktop_set_style(desktop, css); + + sp_repr_css_attr_unref(css); +} + +/** + * Apply style on object and children, recursively. + */ +void +sp_desktop_apply_css_recursive(SPObject *o, SPCSSAttr *css, bool skip_lines) +{ + // non-items should not have style + if (!SP_IS_ITEM(o)) + return; + + // 1. tspans with role=line are not regular objects in that they are not supposed to have style of their own, + // but must always inherit from the parent text. Same for textPath. + // However, if the line tspan or textPath contains some style (old file?), we reluctantly set our style to it too. + + // 2. Generally we allow setting style on clones, but when it's inside flowRegion, do not touch + // it, be it clone or not; it's just styleless shape (because that's how Inkscape does + // flowtext). + + if (!(skip_lines + && ((SP_IS_TSPAN(o) && SP_TSPAN(o)->role == SP_TSPAN_ROLE_LINE) + || SP_IS_FLOWDIV(o) + || SP_IS_FLOWPARA(o) + || SP_IS_TEXTPATH(o)) + && !SP_OBJECT_REPR(o)->attribute("style")) + && + !(SP_IS_FLOWREGION(o) || + SP_IS_FLOWREGIONEXCLUDE(o) || + (SP_IS_USE(o) && + SP_OBJECT_PARENT(o) && + (SP_IS_FLOWREGION(SP_OBJECT_PARENT(o)) || + SP_IS_FLOWREGIONEXCLUDE(SP_OBJECT_PARENT(o)) + ) + ) + ) + ) { + + SPCSSAttr *css_set = sp_repr_css_attr_new(); + sp_repr_css_merge(css_set, css); + + // Scale the style by the inverse of the accumulated parent transform in the paste context. + { + NR::Matrix const local(sp_item_i2doc_affine(SP_ITEM(o))); + double const ex(NR::expansion(local)); + if ( ( ex != 0. ) + && ( ex != 1. ) ) { + sp_css_attr_scale(css_set, 1/ex); + } + } + + sp_repr_css_change(SP_OBJECT_REPR(o), css_set, "style"); + + sp_repr_css_attr_unref(css_set); + } + + // setting style on child of clone spills into the clone original (via shared repr), don't do it! + if (SP_IS_USE(o)) + return; + + for (SPObject *child = sp_object_first_child(SP_OBJECT(o)) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) { + if (sp_repr_css_property(css, "opacity", NULL) != NULL) { + // Unset properties which are accumulating and thus should not be set recursively. + // For example, setting opacity 0.5 on a group recursively would result in the visible opacity of 0.25 for an item in the group. + SPCSSAttr *css_recurse = sp_repr_css_attr_new(); + sp_repr_css_merge(css_recurse, css); + sp_repr_css_set_property(css_recurse, "opacity", NULL); + sp_desktop_apply_css_recursive(child, css_recurse, skip_lines); + sp_repr_css_attr_unref(css_recurse); + } else { + sp_desktop_apply_css_recursive(child, css, skip_lines); + } + } +} + +/** + * Apply style on selection on desktop. + */ +void +sp_desktop_set_style(SPDesktop *desktop, SPCSSAttr *css, bool change, bool write_current) +{ + if (write_current) { +// 1. Set internal value + sp_repr_css_merge(desktop->current, css); + +// 1a. Write to prefs; make a copy and unset any URIs first + SPCSSAttr *css_write = sp_repr_css_attr_new(); + sp_repr_css_merge(css_write, css); + sp_css_attr_unset_uris(css_write); + sp_repr_css_change(inkscape_get_repr(INKSCAPE, "desktop"), css_write, "style"); + sp_repr_css_attr_unref(css_write); + } + + if (!change) + return; + +// 2. Emit signal + bool intercepted = desktop->_set_style_signal.emit(css); + +/** \todo + * FIXME: in set_style, compensate pattern and gradient fills, stroke width, + * rect corners, font size for the object's own transform so that pasting + * fills does not depend on preserve/optimize. + */ + +// 3. If nobody has intercepted the signal, apply the style to the selection + if (!intercepted) { + for (GSList const *i = desktop->selection->itemList(); i != NULL; i = i->next) { + /// \todo if the style is text-only, apply only to texts? + sp_desktop_apply_css_recursive(SP_OBJECT(i->data), css, true); + } + } +} + +/** + * Return the desktop's current style. + */ +SPCSSAttr * +sp_desktop_get_style(SPDesktop *desktop, bool with_text) +{ + SPCSSAttr *css = sp_repr_css_attr_new(); + sp_repr_css_merge(css, desktop->current); + if (!css->attributeList()) { + sp_repr_css_attr_unref(css); + return NULL; + } else { + if (!with_text) { + css = sp_css_attr_unset_text(css); + } + return css; + } +} + +/** + * Return the desktop's current color. + */ +guint32 +sp_desktop_get_color(SPDesktop *desktop, bool is_fill) +{ + guint32 r = 0; // if there's no color, return black + gchar const *property = sp_repr_css_property(desktop->current, + is_fill ? "fill" : "stroke", + "#000"); + + if (desktop->current && property) { // if there is style and the property in it, + if (strncmp(property, "url", 3)) { // and if it's not url, + // read it + r = sp_svg_read_color(property, r); + } + } + + return r; +} + +/** + * Apply the desktop's current style or the tool style to repr. + */ +void +sp_desktop_apply_style_tool(SPDesktop *desktop, Inkscape::XML::Node *repr, char const *tool, bool with_text) +{ + SPCSSAttr *css_current = sp_desktop_get_style(desktop, with_text); + if ((prefs_get_int_attribute(tool, "usecurrent", 0) != 0) && css_current) { + sp_repr_css_set(repr, css_current, "style"); + } else { + Inkscape::XML::Node *tool_repr = inkscape_get_repr(INKSCAPE, tool); + if (tool_repr) { + SPCSSAttr *css = sp_repr_css_attr_inherited(tool_repr, "style"); + sp_repr_css_set(repr, css, "style"); + sp_repr_css_attr_unref(css); + } + } + if (css_current) { + sp_repr_css_attr_unref(css_current); + } +} + +/** + * Returns the font size (in SVG pixels) of the text tool style (if text + * tool uses its own style) or desktop style (otherwise). +*/ +double +sp_desktop_get_font_size_tool(SPDesktop *desktop) +{ + gchar const *desktop_style = inkscape_get_repr(INKSCAPE, "desktop")->attribute("style"); + gchar const *style_str = NULL; + if ((prefs_get_int_attribute("tools.text", "usecurrent", 0) != 0) && desktop_style) { + style_str = desktop_style; + } else { + Inkscape::XML::Node *tool_repr = inkscape_get_repr(INKSCAPE, "tools.text"); + if (tool_repr) { + style_str = tool_repr->attribute("style"); + } + } + + double ret = 12; + if (style_str) { + SPStyle *style = sp_style_new(); + sp_style_merge_from_style_string(style, style_str); + ret = style->font_size.computed; + sp_style_unref(style); + } + return ret; +} + +/** Determine average stroke width, simple method */ +// see TODO in dialogs/stroke-style.cpp on how to get rid of this eventually +gdouble +stroke_average_width (GSList const *objects) +{ + if (g_slist_length ((GSList *) objects) == 0) + return NR_HUGE; + + gdouble avgwidth = 0.0; + bool notstroked = true; + int n_notstroked = 0; + + for (GSList const *l = objects; l != NULL; l = l->next) { + if (!SP_IS_ITEM (l->data)) + continue; + + NR::Matrix i2d = sp_item_i2d_affine (SP_ITEM(l->data)); + + SPObject *object = SP_OBJECT(l->data); + + if ( object->style->stroke.type == SP_PAINT_TYPE_NONE ) { + ++n_notstroked; // do not count nonstroked objects + continue; + } else { + notstroked = false; + } + + avgwidth += SP_OBJECT_STYLE (object)->stroke_width.computed * i2d.expansion(); + } + + if (notstroked) + return NR_HUGE; + + return avgwidth / (g_slist_length ((GSList *) objects) - n_notstroked); +} + +/** + * Write to style_res the average fill or stroke of list of objects, if applicable. + */ +int +objects_query_fillstroke (GSList *objects, SPStyle *style_res, bool const isfill) +{ + if (g_slist_length(objects) == 0) { + /* No objects, set empty */ + return QUERY_STYLE_NOTHING; + } + + SPIPaint *paint_res = isfill? &style_res->fill : &style_res->stroke; + paint_res->type = SP_PAINT_TYPE_IMPOSSIBLE; + paint_res->set = TRUE; + + gfloat c[4]; + c[0] = c[1] = c[2] = c[3] = 0.0; + gint num = 0; + + gfloat prev[4]; + prev[0] = prev[1] = prev[2] = prev[3] = 0.0; + bool same_color = true; + + for (GSList const *i = objects; i != NULL; i = i->next) { + SPObject *obj = SP_OBJECT (i->data); + SPStyle *style = SP_OBJECT_STYLE (obj); + if (!style) continue; + + SPIPaint *paint = isfill? &style->fill : &style->stroke; + + // We consider paint "effectively set" for anything within text hierarchy + SPObject *parent = SP_OBJECT_PARENT (obj); + bool paint_effectively_set = paint->set || (SP_IS_TEXT(parent) || SP_IS_TEXTPATH(parent) || SP_IS_TSPAN(parent) || SP_IS_FLOWTEXT(parent) || SP_IS_FLOWDIV(parent) || SP_IS_FLOWPARA(parent) || SP_IS_FLOWTSPAN(parent) || SP_IS_FLOWLINE(parent)); + + // 1. Bail out with QUERY_STYLE_MULTIPLE_DIFFERENT if necessary + + if ((paint_res->type != SP_PAINT_TYPE_IMPOSSIBLE) && (paint->type != paint_res->type || (paint_res->set != paint_effectively_set))) { + return QUERY_STYLE_MULTIPLE_DIFFERENT; // different types of paint + } + + if (paint_res->set && paint->set && paint_res->type == SP_PAINT_TYPE_PAINTSERVER) { + // both previous paint and this paint were a server, see if the servers are compatible + + SPPaintServer *server_res = isfill? SP_STYLE_FILL_SERVER (style_res) : SP_STYLE_STROKE_SERVER (style_res); + SPPaintServer *server = isfill? SP_STYLE_FILL_SERVER (style) : SP_STYLE_STROKE_SERVER (style); + + if (SP_IS_LINEARGRADIENT (server_res)) { + + if (!SP_IS_LINEARGRADIENT(server)) + return QUERY_STYLE_MULTIPLE_DIFFERENT; // different kind of server + + SPGradient *vector = sp_gradient_get_vector ( SP_GRADIENT (server), FALSE ); + SPGradient *vector_res = sp_gradient_get_vector ( SP_GRADIENT (server_res), FALSE ); + if (vector_res != vector) + return QUERY_STYLE_MULTIPLE_DIFFERENT; // different gradient vectors + + } else if (SP_IS_RADIALGRADIENT (server_res)) { + + if (!SP_IS_RADIALGRADIENT(server)) + return QUERY_STYLE_MULTIPLE_DIFFERENT; // different kind of server + + SPGradient *vector = sp_gradient_get_vector ( SP_GRADIENT (server), FALSE ); + SPGradient *vector_res = sp_gradient_get_vector ( SP_GRADIENT (server_res), FALSE ); + if (vector_res != vector) + return QUERY_STYLE_MULTIPLE_DIFFERENT; // different gradient vectors + + } else if (SP_IS_PATTERN (server_res)) { + + if (!SP_IS_PATTERN(server)) + return QUERY_STYLE_MULTIPLE_DIFFERENT; // different kind of server + + SPPattern *pat = pattern_getroot (SP_PATTERN (server)); + SPPattern *pat_res = pattern_getroot (SP_PATTERN (server_res)); + if (pat_res != pat) + return QUERY_STYLE_MULTIPLE_DIFFERENT; // different pattern roots + } + } + + // 2. Sum color, copy server from paint to paint_res + + if (paint_res->set && paint_effectively_set && paint->type == SP_PAINT_TYPE_COLOR) { + + gfloat d[3]; + sp_color_get_rgb_floatv (&paint->value.color, d); + + // Check if this color is the same as previous + if (paint_res->type == SP_PAINT_TYPE_IMPOSSIBLE) { + prev[0] = d[0]; + prev[1] = d[1]; + prev[2] = d[2]; + prev[3] = d[3]; + } else { + if (same_color && (prev[0] != d[0] || prev[1] != d[1] || prev[2] != d[2] || prev[3] != d[3])) + same_color = false; + } + + // average color + c[0] += d[0]; + c[1] += d[1]; + c[2] += d[2]; + c[3] += SP_SCALE24_TO_FLOAT (isfill? style->fill_opacity.value : style->stroke_opacity.value); + num ++; + } + + if (paint_res->set && paint->set && paint->type == SP_PAINT_TYPE_PAINTSERVER) { // copy the server + if (isfill) { + SP_STYLE_FILL_SERVER (style_res) = SP_STYLE_FILL_SERVER (style); + } else { + SP_STYLE_STROKE_SERVER (style_res) = SP_STYLE_STROKE_SERVER (style); + } + } + paint_res->type = paint->type; + paint_res->set = paint_effectively_set; + style_res->fill_rule.computed = style->fill_rule.computed; // no averaging on this, just use the last one + } + + // After all objects processed, divide the color if necessary and return + if (paint_res->set && paint_res->type == SP_PAINT_TYPE_COLOR) { // set the color + g_assert (num >= 1); + + c[0] /= num; + c[1] /= num; + c[2] /= num; + c[3] /= num; + sp_color_set_rgb_float(&paint_res->value.color, c[0], c[1], c[2]); + if (isfill) { + style_res->fill_opacity.value = SP_SCALE24_FROM_FLOAT (c[3]); + } else { + style_res->stroke_opacity.value = SP_SCALE24_FROM_FLOAT (c[3]); + } + if (num > 1) { + if (same_color) + return QUERY_STYLE_MULTIPLE_SAME; + else + return QUERY_STYLE_MULTIPLE_AVERAGED; + } else { + return QUERY_STYLE_SINGLE; + } + } + + // Not color + if (g_slist_length(objects) > 1) { + return QUERY_STYLE_MULTIPLE_SAME; + } else { + return QUERY_STYLE_SINGLE; + } +} + +/** + * Write to style_res the average opacity of a list of objects. + */ +int +objects_query_opacity (GSList *objects, SPStyle *style_res) +{ + if (g_slist_length(objects) == 0) { + /* No objects, set empty */ + return QUERY_STYLE_NOTHING; + } + + gdouble opacity_sum = 0; + gdouble opacity_prev = -1; + bool same_opacity = true; + guint opacity_items = 0; + + + for (GSList const *i = objects; i != NULL; i = i->next) { + SPObject *obj = SP_OBJECT (i->data); + SPStyle *style = SP_OBJECT_STYLE (obj); + if (!style) continue; + + double opacity = SP_SCALE24_TO_FLOAT(style->opacity.value); + opacity_sum += opacity; + if (opacity_prev != -1 && opacity != opacity_prev) + same_opacity = false; + opacity_prev = opacity; + opacity_items ++; + } + if (opacity_items > 1) + opacity_sum /= opacity_items; + + style_res->opacity.value = SP_SCALE24_FROM_FLOAT(opacity_sum); + + if (opacity_items == 0) { + return QUERY_STYLE_NOTHING; + } else if (opacity_items == 1) { + return QUERY_STYLE_SINGLE; + } else { + if (same_opacity) + return QUERY_STYLE_MULTIPLE_SAME; + else + return QUERY_STYLE_MULTIPLE_AVERAGED; + } +} + +/** + * Write to style_res the average stroke width of a list of objects. + */ +int +objects_query_strokewidth (GSList *objects, SPStyle *style_res) +{ + if (g_slist_length(objects) == 0) { + /* No objects, set empty */ + return QUERY_STYLE_NOTHING; + } + + gdouble avgwidth = 0.0; + + gdouble prev_sw = -1; + bool same_sw = true; + + int n_stroked = 0; + + for (GSList const *i = objects; i != NULL; i = i->next) { + SPObject *obj = SP_OBJECT (i->data); + if (!SP_IS_ITEM(obj)) continue; + SPStyle *style = SP_OBJECT_STYLE (obj); + if (!style) continue; + + if ( style->stroke.type == SP_PAINT_TYPE_NONE ) { + continue; + } + + n_stroked ++; + + NR::Matrix i2d = sp_item_i2d_affine (SP_ITEM(obj)); + double sw = style->stroke_width.computed * i2d.expansion(); + + if (prev_sw != -1 && fabs(sw - prev_sw) > 1e-3) + same_sw = false; + prev_sw = sw; + + avgwidth += sw; + } + + if (n_stroked > 1) + avgwidth /= (n_stroked); + + style_res->stroke_width.computed = avgwidth; + style_res->stroke_width.set = true; + + if (n_stroked == 0) { + return QUERY_STYLE_NOTHING; + } else if (n_stroked == 1) { + return QUERY_STYLE_SINGLE; + } else { + if (same_sw) + return QUERY_STYLE_MULTIPLE_SAME; + else + return QUERY_STYLE_MULTIPLE_AVERAGED; + } +} + +/** + * Write to style_res the average miter limit of a list of objects. + */ +int +objects_query_miterlimit (GSList *objects, SPStyle *style_res) +{ + if (g_slist_length(objects) == 0) { + /* No objects, set empty */ + return QUERY_STYLE_NOTHING; + } + + gdouble avgml = 0.0; + int n_stroked = 0; + + gdouble prev_ml = -1; + bool same_ml = true; + + for (GSList const *i = objects; i != NULL; i = i->next) { + SPObject *obj = SP_OBJECT (i->data); + if (!SP_IS_ITEM(obj)) continue; + SPStyle *style = SP_OBJECT_STYLE (obj); + if (!style) continue; + + if ( style->stroke.type == SP_PAINT_TYPE_NONE ) { + continue; + } + + n_stroked ++; + + if (prev_ml != -1 && fabs(style->stroke_miterlimit.value - prev_ml) > 1e-3) + same_ml = false; + prev_ml = style->stroke_miterlimit.value; + + avgml += style->stroke_miterlimit.value; + } + + if (n_stroked > 1) + avgml /= (n_stroked); + + style_res->stroke_miterlimit.value = avgml; + style_res->stroke_miterlimit.set = true; + + if (n_stroked == 0) { + return QUERY_STYLE_NOTHING; + } else if (n_stroked == 1) { + return QUERY_STYLE_SINGLE; + } else { + if (same_ml) + return QUERY_STYLE_MULTIPLE_SAME; + else + return QUERY_STYLE_MULTIPLE_AVERAGED; + } +} + +/** + * Write to style_res the stroke cap of a list of objects. + */ +int +objects_query_strokecap (GSList *objects, SPStyle *style_res) +{ + if (g_slist_length(objects) == 0) { + /* No objects, set empty */ + return QUERY_STYLE_NOTHING; + } + + int cap = -1; + gdouble prev_cap = -1; + bool same_cap = true; + int n_stroked = 0; + + for (GSList const *i = objects; i != NULL; i = i->next) { + SPObject *obj = SP_OBJECT (i->data); + if (!SP_IS_ITEM(obj)) continue; + SPStyle *style = SP_OBJECT_STYLE (obj); + if (!style) continue; + + if ( style->stroke.type == SP_PAINT_TYPE_NONE ) { + continue; + } + + n_stroked ++; + + if (prev_cap != -1 && style->stroke_linecap.value != prev_cap) + same_cap = false; + prev_cap = style->stroke_linecap.value; + + cap = style->stroke_linecap.value; + } + + style_res->stroke_linecap.value = cap; + style_res->stroke_linecap.set = true; + + if (n_stroked == 0) { + return QUERY_STYLE_NOTHING; + } else if (n_stroked == 1) { + return QUERY_STYLE_SINGLE; + } else { + if (same_cap) + return QUERY_STYLE_MULTIPLE_SAME; + else + return QUERY_STYLE_MULTIPLE_DIFFERENT; + } +} + +/** + * Write to style_res the stroke join of a list of objects. + */ +int +objects_query_strokejoin (GSList *objects, SPStyle *style_res) +{ + if (g_slist_length(objects) == 0) { + /* No objects, set empty */ + return QUERY_STYLE_NOTHING; + } + + int join = -1; + gdouble prev_join = -1; + bool same_join = true; + int n_stroked = 0; + + for (GSList const *i = objects; i != NULL; i = i->next) { + SPObject *obj = SP_OBJECT (i->data); + if (!SP_IS_ITEM(obj)) continue; + SPStyle *style = SP_OBJECT_STYLE (obj); + if (!style) continue; + + if ( style->stroke.type == SP_PAINT_TYPE_NONE ) { + continue; + } + + n_stroked ++; + + if (prev_join != -1 && style->stroke_linejoin.value != prev_join) + same_join = false; + prev_join = style->stroke_linejoin.value; + + join = style->stroke_linejoin.value; + } + + style_res->stroke_linejoin.value = join; + style_res->stroke_linejoin.set = true; + + if (n_stroked == 0) { + return QUERY_STYLE_NOTHING; + } else if (n_stroked == 1) { + return QUERY_STYLE_SINGLE; + } else { + if (same_join) + return QUERY_STYLE_MULTIPLE_SAME; + else + return QUERY_STYLE_MULTIPLE_DIFFERENT; + } +} + +/** + * Write to style_res the average font size and spacing of objects. + */ +int +objects_query_fontnumbers (GSList *objects, SPStyle *style_res) +{ + bool different = false; + + double size = 0; + double letterspacing = 0; + double linespacing = 0; + bool linespacing_normal = false; + bool letterspacing_normal = false; + + double size_prev = 0; + double letterspacing_prev = 0; + double linespacing_prev = 0; + + /// \todo FIXME: add word spacing, kerns? rotates? + + int texts = 0; + + for (GSList const *i = objects; i != NULL; i = i->next) { + SPObject *obj = SP_OBJECT (i->data); + + if (!SP_IS_TEXT(obj) && !SP_IS_FLOWTEXT(obj) + && !SP_IS_TSPAN(obj) && !SP_IS_TEXTPATH(obj) + && !SP_IS_FLOWDIV(obj) && !SP_IS_FLOWPARA(obj) && !SP_IS_FLOWTSPAN(obj)) + continue; + + SPStyle *style = SP_OBJECT_STYLE (obj); + if (!style) continue; + + texts ++; + size += style->font_size.computed * NR::expansion(sp_item_i2d_affine(SP_ITEM(obj))); /// \todo FIXME: we assume non-% units here + + if (style->letter_spacing.normal) { + if (!different && (letterspacing_prev == 0 || letterspacing_prev == letterspacing)) + letterspacing_normal = true; + } else { + letterspacing += style->letter_spacing.computed; /// \todo FIXME: we assume non-% units here + letterspacing_normal = false; + } + + double linespacing_current; + if (style->line_height.normal) { + linespacing_current = Inkscape::Text::Layout::LINE_HEIGHT_NORMAL; + if (!different && (linespacing_prev == 0 || linespacing_prev == linespacing_current)) + linespacing_normal = true; + } else if (style->line_height.unit == SP_CSS_UNIT_PERCENT || style->font_size.computed == 0) { + linespacing_current = style->line_height.value; + linespacing_normal = false; + } else { // we need % here + linespacing_current = style->line_height.computed / style->font_size.computed; + linespacing_normal = false; + } + linespacing += linespacing_current; + + if ((size_prev != 0 && style->font_size.computed != size_prev) || + (letterspacing_prev != 0 && style->letter_spacing.computed != letterspacing_prev) || + (linespacing_prev != 0 && linespacing_current != linespacing_prev)) { + different = true; + } + + size_prev = style->font_size.computed; + letterspacing_prev = style->letter_spacing.computed; + linespacing_prev = linespacing_current; + + // FIXME: we must detect MULTIPLE_DIFFERENT for these too + style_res->text_anchor.computed = style->text_anchor.computed; + style_res->writing_mode.computed = style->writing_mode.computed; + } + + if (texts == 0) + return QUERY_STYLE_NOTHING; + + if (texts > 1) { + size /= texts; + letterspacing /= texts; + linespacing /= texts; + } + + style_res->font_size.computed = size; + style_res->font_size.type = SP_FONT_SIZE_LENGTH; + + style_res->letter_spacing.normal = letterspacing_normal; + style_res->letter_spacing.computed = letterspacing; + + style_res->line_height.normal = linespacing_normal; + style_res->line_height.computed = linespacing; + style_res->line_height.value = linespacing; + style_res->line_height.unit = SP_CSS_UNIT_PERCENT; + + if (texts > 1) { + if (different) { + return QUERY_STYLE_MULTIPLE_AVERAGED; + } else { + return QUERY_STYLE_MULTIPLE_SAME; + } + } else { + return QUERY_STYLE_SINGLE; + } +} + +/** + * Write to style_res the average font style of objects. + */ +int +objects_query_fontstyle (GSList *objects, SPStyle *style_res) +{ + bool different = false; + bool set = false; + + int texts = 0; + + for (GSList const *i = objects; i != NULL; i = i->next) { + SPObject *obj = SP_OBJECT (i->data); + + if (!SP_IS_TEXT(obj) && !SP_IS_FLOWTEXT(obj) + && !SP_IS_TSPAN(obj) && !SP_IS_TEXTPATH(obj) + && !SP_IS_FLOWDIV(obj) && !SP_IS_FLOWPARA(obj) && !SP_IS_FLOWTSPAN(obj)) + continue; + + SPStyle *style = SP_OBJECT_STYLE (obj); + if (!style) continue; + + texts ++; + + if (set && + font_style_to_pos(*style_res).signature() != font_style_to_pos(*style).signature() ) { + different = true; // different styles + } + + set = TRUE; + style_res->font_weight.value = style_res->font_weight.computed = style->font_weight.computed; + style_res->font_style.value = style_res->font_style.computed = style->font_style.computed; + style_res->font_stretch.value = style_res->font_stretch.computed = style->font_stretch.computed; + style_res->font_variant.value = style_res->font_variant.computed = style->font_variant.computed; + } + + if (texts == 0 || !set) + return QUERY_STYLE_NOTHING; + + if (texts > 1) { + if (different) { + return QUERY_STYLE_MULTIPLE_DIFFERENT; + } else { + return QUERY_STYLE_MULTIPLE_SAME; + } + } else { + return QUERY_STYLE_SINGLE; + } +} + +/** + * Write to style_res the average font family of objects. + */ +int +objects_query_fontfamily (GSList *objects, SPStyle *style_res) +{ + bool different = false; + int texts = 0; + + if (style_res->text->font_family.value) { + g_free(style_res->text->font_family.value); + style_res->text->font_family.value = NULL; + } + style_res->text->font_family.set = FALSE; + + for (GSList const *i = objects; i != NULL; i = i->next) { + SPObject *obj = SP_OBJECT (i->data); + + if (!SP_IS_TEXT(obj) && !SP_IS_FLOWTEXT(obj) + && !SP_IS_TSPAN(obj) && !SP_IS_TEXTPATH(obj) + && !SP_IS_FLOWDIV(obj) && !SP_IS_FLOWPARA(obj) && !SP_IS_FLOWTSPAN(obj)) + continue; + + SPStyle *style = SP_OBJECT_STYLE (obj); + if (!style) continue; + + texts ++; + + if (style_res->text->font_family.value && style->text->font_family.value && + strcmp (style_res->text->font_family.value, style->text->font_family.value)) { + different = true; // different fonts + } + + if (style_res->text->font_family.value) { + g_free(style_res->text->font_family.value); + style_res->text->font_family.value = NULL; + } + + style_res->text->font_family.set = TRUE; + style_res->text->font_family.value = g_strdup(style->text->font_family.value); + } + + if (texts == 0 || !style_res->text->font_family.set) + return QUERY_STYLE_NOTHING; + + if (texts > 1) { + if (different) { + return QUERY_STYLE_MULTIPLE_DIFFERENT; + } else { + return QUERY_STYLE_MULTIPLE_SAME; + } + } else { + return QUERY_STYLE_SINGLE; + } +} + +/** + * Query the given list of objects for the given property, write + * the result to style, return appropriate flag. + */ +int +sp_desktop_query_style_from_list (GSList *list, SPStyle *style, int property) +{ + if (property == QUERY_STYLE_PROPERTY_FILL) { + return objects_query_fillstroke (list, style, true); + } else if (property == QUERY_STYLE_PROPERTY_STROKE) { + return objects_query_fillstroke (list, style, false); + + } else if (property == QUERY_STYLE_PROPERTY_STROKEWIDTH) { + return objects_query_strokewidth (list, style); + } else if (property == QUERY_STYLE_PROPERTY_STROKEMITERLIMIT) { + return objects_query_miterlimit (list, style); + } else if (property == QUERY_STYLE_PROPERTY_STROKECAP) { + return objects_query_strokecap (list, style); + } else if (property == QUERY_STYLE_PROPERTY_STROKEJOIN) { + return objects_query_strokejoin (list, style); + + } else if (property == QUERY_STYLE_PROPERTY_MASTEROPACITY) { + return objects_query_opacity (list, style); + + } else if (property == QUERY_STYLE_PROPERTY_FONTFAMILY) { + return objects_query_fontfamily (list, style); + } else if (property == QUERY_STYLE_PROPERTY_FONTSTYLE) { + return objects_query_fontstyle (list, style); + } else if (property == QUERY_STYLE_PROPERTY_FONTNUMBERS) { + return objects_query_fontnumbers (list, style); + } + + return QUERY_STYLE_NOTHING; +} + + +/** + * Query the subselection (if any) or selection on the given desktop for the given property, write + * the result to style, return appropriate flag. + */ +int +sp_desktop_query_style(SPDesktop *desktop, SPStyle *style, int property) +{ + int ret = desktop->_query_style_signal.emit(style, property); + + if (ret != QUERY_STYLE_NOTHING) + return ret; // subselection returned a style, pass it on + + // otherwise, do querying and averaging over selection + return sp_desktop_query_style_from_list ((GSList *) desktop->selection->itemList(), style, property); +} + +/** + * Do the same as sp_desktop_query_style for all (defined) style properties, return true if none of + * the properties returned QUERY_STYLE_NOTHING. + */ +bool +sp_desktop_query_style_all (SPDesktop *desktop, SPStyle *query) +{ + int result_family = sp_desktop_query_style (desktop, query, QUERY_STYLE_PROPERTY_FONTFAMILY); + int result_fstyle = sp_desktop_query_style (desktop, query, QUERY_STYLE_PROPERTY_FONTSTYLE); + int result_fnumbers = sp_desktop_query_style (desktop, query, QUERY_STYLE_PROPERTY_FONTNUMBERS); + int result_fill = sp_desktop_query_style (desktop, query, QUERY_STYLE_PROPERTY_FILL); + int result_stroke = sp_desktop_query_style (desktop, query, QUERY_STYLE_PROPERTY_STROKE); + int result_strokewidth = sp_desktop_query_style (desktop, query, QUERY_STYLE_PROPERTY_STROKEWIDTH); + int result_strokemiterlimit = sp_desktop_query_style (desktop, query, QUERY_STYLE_PROPERTY_STROKEMITERLIMIT); + int result_strokecap = sp_desktop_query_style (desktop, query, QUERY_STYLE_PROPERTY_STROKECAP); + int result_strokejoin = sp_desktop_query_style (desktop, query, QUERY_STYLE_PROPERTY_STROKEJOIN); + int result_opacity = sp_desktop_query_style (desktop, query, QUERY_STYLE_PROPERTY_MASTEROPACITY); + + return (result_family != QUERY_STYLE_NOTHING && result_fstyle != QUERY_STYLE_NOTHING && result_fnumbers != QUERY_STYLE_NOTHING && result_fill != QUERY_STYLE_NOTHING && result_stroke != QUERY_STYLE_NOTHING && result_opacity != QUERY_STYLE_NOTHING && result_strokewidth != QUERY_STYLE_NOTHING && result_strokemiterlimit != QUERY_STYLE_NOTHING && result_strokecap != QUERY_STYLE_NOTHING && result_strokejoin != QUERY_STYLE_NOTHING); +} + + +/* + 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/desktop-style.h b/src/desktop-style.h new file mode 100644 index 000000000..3ebf4f238 --- /dev/null +++ b/src/desktop-style.h @@ -0,0 +1,87 @@ +#ifndef __SP_DESKTOP_STYLE_H__ +#define __SP_DESKTOP_STYLE_H__ + +/* + * Desktop style management + * + * Authors: + * bulia byak + * + * Copyright (C) 2004 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +class ColorRGBA; +struct SPCSSAttr; +struct SPDesktop; +struct SPObject; +struct SPStyle; +namespace Inkscape { +namespace XML { +struct Node; +} +} + +enum { // what kind of a style the query is returning + QUERY_STYLE_NOTHING, // nothing was queried - e.g. no selection + QUERY_STYLE_SINGLE, // single object was queried + QUERY_STYLE_MULTIPLE_SAME, // multiple objects were queried, the results were the same + QUERY_STYLE_MULTIPLE_DIFFERENT, // multiple objects were queried, the results could NOT be meaningfully averaged + QUERY_STYLE_MULTIPLE_AVERAGED // multiple objects were queried, the results were meaningfully averaged +}; + +enum { // which property was queried (add when you need more) + QUERY_STYLE_PROPERTY_EVERYTHING, + QUERY_STYLE_PROPERTY_FILL, // fill, fill-opacity + QUERY_STYLE_PROPERTY_STROKE, // stroke, stroke-opacity + QUERY_STYLE_PROPERTY_STROKEWIDTH, // stroke-width + QUERY_STYLE_PROPERTY_STROKEMITERLIMIT, // miter limit + QUERY_STYLE_PROPERTY_STROKEJOIN, // stroke join + QUERY_STYLE_PROPERTY_STROKECAP, // stroke cap + QUERY_STYLE_PROPERTY_STROKESTYLE, // markers, dasharray, miterlimit, stroke-width, stroke-cap, stroke-join + QUERY_STYLE_PROPERTY_FONTFAMILY, // font-family + QUERY_STYLE_PROPERTY_FONTSTYLE, // font style + QUERY_STYLE_PROPERTY_FONTNUMBERS, // size, spacings + QUERY_STYLE_PROPERTY_MASTEROPACITY // opacity +}; + +void sp_desktop_apply_css_recursive(SPObject *o, SPCSSAttr *css, bool skip_lines); +void sp_desktop_set_color(SPDesktop *desktop, ColorRGBA const &color, bool is_relative, bool fill); +void sp_desktop_set_style(SPDesktop *desktop, SPCSSAttr *css, bool change = true, bool write_current = true); +SPCSSAttr *sp_desktop_get_style(SPDesktop *desktop, bool with_text); +guint32 sp_desktop_get_color (SPDesktop *desktop, bool is_fill); +double sp_desktop_get_font_size_tool (SPDesktop *desktop); +void sp_desktop_apply_style_tool(SPDesktop *desktop, Inkscape::XML::Node *repr, char const *tool, bool with_text); + +gdouble stroke_average_width (GSList const *objects); + +int objects_query_fillstroke (GSList *objects, SPStyle *style_res, bool const isfill); +int objects_query_fontnumbers (GSList *objects, SPStyle *style_res); +int objects_query_fontstyle (GSList *objects, SPStyle *style_res); +int objects_query_fontfamily (GSList *objects, SPStyle *style_res); +int objects_query_opacity (GSList *objects, SPStyle *style_res); +int objects_query_strokewidth (GSList *objects, SPStyle *style_res); +int objects_query_miterlimit (GSList *objects, SPStyle *style_res); +int objects_query_strokecap (GSList *objects, SPStyle *style_res); +int objects_query_strokejoin (GSList *objects, SPStyle *style_res); + +int sp_desktop_query_style_from_list (GSList *list, SPStyle *style, int property); +int sp_desktop_query_style(SPDesktop *desktop, SPStyle *style, int property); +bool sp_desktop_query_style_all (SPDesktop *desktop, SPStyle *query); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/desktop.cpp b/src/desktop.cpp new file mode 100644 index 000000000..9a2274bea --- /dev/null +++ b/src/desktop.cpp @@ -0,0 +1,1400 @@ +#define __SP_DESKTOP_C__ + +/** \file + * Editable view implementation + * + * Authors: + * Lauris Kaplinski + * MenTaLguY + * bulia byak + * Ralf Stephan + * + * Copyright (C) 2004 MenTaLguY + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +/** \class SPDesktop + * SPDesktop is a subclass of View, implementing an editable document + * canvas. It is extensively used by many UI controls that need certain + * visual representations of their own. + * + * SPDesktop provides a certain set of SPCanvasItems, serving as GUI + * layers of different control objects. The one containing the whole + * document is the drawing layer. In addition to it, there are grid, + * guide, sketch and control layers. The sketch layer is used for + * temporary drawing objects, before the real objects in document are + * created. The control layer contains editing knots, rubberband and + * similar non-document UI objects. + * + * Each SPDesktop is associated with a SPNamedView node of the document + * tree. Currently, all desktops are created from a single main named + * view, but in the future there may be support for different ones. + * SPNamedView serves as an in-document container for desktop-related + * data, like grid and guideline placement, snapping options and so on. + * + * Associated with each SPDesktop are the two most important editing + * related objects - SPSelection and SPEventContext. + * + * Sodipodi keeps track of the active desktop and invokes notification + * signals whenever it changes. UI elements can use these to update their + * display to the selection of the currently active editing window. + * (Lauris Kaplinski) + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include "macros.h" +#include "inkscape-private.h" +#include "desktop.h" +#include "desktop-events.h" +#include "desktop-handles.h" +#include "document.h" +#include "message-stack.h" +#include "selection.h" +#include "select-context.h" +#include "sp-namedview.h" +#include "color.h" +#include "sp-item-group.h" +#include "prefs-utils.h" +#include "object-hierarchy.h" +#include "helper/units.h" +#include "display/canvas-arena.h" +#include "display/nr-arena.h" +#include "display/gnome-canvas-acetate.h" +#include "display/sodipodi-ctrlrect.h" +#include "display/sp-canvas-util.h" +#include "libnr/nr-matrix-div.h" +#include "libnr/nr-rect-ops.h" +#include "ui/dialog/dialog-manager.h" +#include "xml/repr.h" +#include "message-context.h" + +#ifdef WITH_INKBOARD +#include "jabber_whiteboard/session-manager.h" +#endif + +namespace Inkscape { namespace XML { class Node; }} + +// Callback declarations +static void _onSelectionChanged (Inkscape::Selection *selection, SPDesktop *desktop); +static gint _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop); +static void _layer_activated(SPObject *layer, SPDesktop *desktop); +static void _layer_deactivated(SPObject *layer, SPDesktop *desktop); +static void _layer_hierarchy_changed(SPObject *top, SPObject *bottom, SPDesktop *desktop); +static void _reconstruction_start(SPDesktop * desktop); +static void _reconstruction_finish(SPDesktop * desktop); +static void _namedview_modified (SPNamedView *nv, guint flags, SPDesktop *desktop); +static void _update_snap_distances (SPDesktop *desktop); + +/** + * Return new desktop object. + * \pre namedview != NULL. + * \pre canvas != NULL. + */ +SPDesktop::SPDesktop() +{ + _dlg_mgr = NULL; + _widget = 0; + namedview = NULL; + selection = NULL; + acetate = NULL; + main = NULL; + grid = NULL; + guides = NULL; + drawing = NULL; + sketch = NULL; + controls = NULL; + event_context = 0; + + _d2w.set_identity(); + _w2d.set_identity(); + _doc2dt = NR::Matrix(NR::scale(1, -1)); + + guides_active = false; + + zooms_past = NULL; + zooms_future = NULL; + + is_fullscreen = false; + + gr_item = NULL; + gr_point_num = 0; + gr_fill_or_stroke = true; + + _layer_hierarchy = NULL; + _active = false; + + selection = Inkscape::GC::release (new Inkscape::Selection (this)); +} + +void +SPDesktop::init (SPNamedView *nv, SPCanvas *aCanvas) + +{ + _guides_message_context = new Inkscape::MessageContext(const_cast(messageStack())); + + current = sp_repr_css_attr_inherited (inkscape_get_repr (INKSCAPE, "desktop"), "style"); + + namedview = nv; + canvas = aCanvas; + + SPDocument *document = SP_OBJECT_DOCUMENT (namedview); + /* Kill flicker */ + sp_document_ensure_up_to_date (document); + + /* Setup Dialog Manager */ + _dlg_mgr = new Inkscape::UI::Dialog::DialogManager(); + + dkey = sp_item_display_key_new (1); + + /* Connect document */ + setDocument (document); + + number = namedview->getViewCount(); + + + /* Setup Canvas */ + g_object_set_data (G_OBJECT (canvas), "SPDesktop", this); + + SPCanvasGroup *root = sp_canvas_root (canvas); + + /* Setup adminstrative layers */ + acetate = sp_canvas_item_new (root, GNOME_TYPE_CANVAS_ACETATE, NULL); + g_signal_connect (G_OBJECT (acetate), "event", G_CALLBACK (sp_desktop_root_handler), this); + main = (SPCanvasGroup *) sp_canvas_item_new (root, SP_TYPE_CANVAS_GROUP, NULL); + g_signal_connect (G_OBJECT (main), "event", G_CALLBACK (sp_desktop_root_handler), this); + + table = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL); + SP_CTRLRECT(table)->setRectangle(NR::Rect(NR::Point(-80000, -80000), NR::Point(80000, 80000))); + SP_CTRLRECT(table)->setColor(0x00000000, true, 0x00000000); + sp_canvas_item_move_to_z (table, 0); + + page = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL); + ((CtrlRect *) page)->setColor(0x00000000, FALSE, 0x00000000); + page_border = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL); + + drawing = sp_canvas_item_new (main, SP_TYPE_CANVAS_ARENA, NULL); + g_signal_connect (G_OBJECT (drawing), "arena_event", G_CALLBACK (_arena_handler), this); + + SP_CANVAS_ARENA (drawing)->arena->delta = prefs_get_double_attribute ("options.cursortolerance", "value", 1.0); // default is 1 px + + // Start always in normal mode + SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL; + canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size + + grid = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL); + guides = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL); + sketch = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL); + controls = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL); + + /* Push select tool to the bottom of stack */ + /** \todo + * FIXME: this is the only call to this. Everything else seems to just + * call "set" instead of "push". Can we assume that there is only one + * context ever? + */ + push_event_context (SP_TYPE_SELECT_CONTEXT, "tools.select", SP_EVENT_CONTEXT_STATIC); + + // display rect and zoom are now handled in sp_desktop_widget_realize() + + NR::Rect const d(NR::Point(0.0, 0.0), + NR::Point(sp_document_width(document), sp_document_height(document))); + + SP_CTRLRECT(page)->setRectangle(d); + SP_CTRLRECT(page_border)->setRectangle(d); + + /* the following sets the page shadow on the canvas + It was originally set to 5, which is really cheesy! + It now is an attribute in the document's namedview. If a value of + 0 is used, then the constructor for a shadow is not initialized. + */ + + if ( namedview->pageshadow != 0 && namedview->showpageshadow ) { + SP_CTRLRECT(page_border)->setShadow(namedview->pageshadow, 0x3f3f3fff); + } + + + /* Connect event for page resize */ + _doc2dt[5] = sp_document_height (document); + sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt); + + g_signal_connect (G_OBJECT (namedview), "modified", G_CALLBACK (_namedview_modified), this); + + + NRArenaItem *ai = sp_item_invoke_show (SP_ITEM (sp_document_root (document)), + SP_CANVAS_ARENA (drawing)->arena, + dkey, + SP_ITEM_SHOW_DISPLAY); + if (ai) { + nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL); + nr_arena_item_unref (ai); + } + + namedview->show(this); + /* Ugly hack */ + activate_guides (true); + /* Ugly hack */ + _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this); + + /* Construct SessionManager + * + * SessionManager construction needs to be done after document connection + */ +#ifdef WITH_INKBOARD + _whiteboard_session_manager = new Inkscape::Whiteboard::SessionManager(this); +#endif + +/* Set up notification of rebuilding the document, this allows + for saving object related settings in the document. */ + _reconstruction_start_connection = + document->connectReconstructionStart(sigc::bind(sigc::ptr_fun(_reconstruction_start), this)); + _reconstruction_finish_connection = + document->connectReconstructionFinish(sigc::bind(sigc::ptr_fun(_reconstruction_finish), this)); + _reconstruction_old_layer_id = NULL; + + // ? + // sp_active_desktop_set (desktop); + _inkscape = INKSCAPE; + + _activate_connection = _activate_signal.connect( + sigc::bind( + sigc::ptr_fun(_onActivate), + this + ) + ); + _deactivate_connection = _deactivate_signal.connect( + sigc::bind( + sigc::ptr_fun(_onDeactivate), + this + ) + ); + + _sel_modified_connection = selection->connectModified( + sigc::bind( + sigc::ptr_fun(&_onSelectionModified), + this + ) + ); + _sel_changed_connection = selection->connectChanged( + sigc::bind( + sigc::ptr_fun(&_onSelectionChanged), + this + ) + ); + +} + + +void SPDesktop::destroy() +{ + _activate_connection.disconnect(); + _deactivate_connection.disconnect(); + _sel_modified_connection.disconnect(); + _sel_changed_connection.disconnect(); + + while (event_context) { + SPEventContext *ec = event_context; + event_context = ec->next; + sp_event_context_finish (ec); + g_object_unref (G_OBJECT (ec)); + } + + if (_layer_hierarchy) { + delete _layer_hierarchy; + } + + if (_inkscape) { + _inkscape = NULL; + } + + if (drawing) { + sp_item_invoke_hide (SP_ITEM (sp_document_root (doc())), dkey); + drawing = NULL; + } + +#ifdef WITH_INKBOARD + if (_whiteboard_session_manager) { + delete _whiteboard_session_manager; + } +#endif + + delete _guides_message_context; + _guides_message_context = NULL; + + sp_signal_disconnect_by_data (G_OBJECT (namedview), this); + + g_list_free (zooms_past); + g_list_free (zooms_future); +} + +SPDesktop::~SPDesktop() {} + +//-------------------------------------------------------------------- +/* Public methods */ + +void SPDesktop::setDisplayModeNormal() +{ + SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL; + canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size + sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw +} + +void SPDesktop::setDisplayModeOutline() +{ + SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_OUTLINE; + canvas->rendermode = RENDERMODE_OUTLINE; // canvas needs that for choosing the best buffer size + sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw +} + +/** + * Returns current root (=bottom) layer. + */ +SPObject *SPDesktop::currentRoot() const +{ + return _layer_hierarchy ? _layer_hierarchy->top() : NULL; +} + +/** + * Returns current top layer. + */ +SPObject *SPDesktop::currentLayer() const +{ + return _layer_hierarchy ? _layer_hierarchy->bottom() : NULL; +} + +/** + * Make \a object the top layer. + */ +void SPDesktop::setCurrentLayer(SPObject *object) { + g_return_if_fail(SP_IS_GROUP(object)); + g_return_if_fail( currentRoot() == object || currentRoot()->isAncestorOf(object)); + // printf("Set Layer to ID: %s\n", SP_OBJECT_ID(object)); + _layer_hierarchy->setBottom(object); +} + +/** + * Return layer that contains \a object. + */ +SPObject *SPDesktop::layerForObject(SPObject *object) { + g_return_val_if_fail(object != NULL, NULL); + + SPObject *root=currentRoot(); + object = SP_OBJECT_PARENT(object); + while ( object && object != root && !isLayer(object) ) { + object = SP_OBJECT_PARENT(object); + } + return object; +} + +/** + * True if object is a layer. + */ +bool SPDesktop::isLayer(SPObject *object) const { + return ( SP_IS_GROUP(object) + && ( SP_GROUP(object)->effectiveLayerMode(this->dkey) + == SPGroup::LAYER ) ); +} + +/** + * True if desktop viewport fully contains \a item's bbox. + */ +bool SPDesktop::isWithinViewport (SPItem *item) const +{ + NR::Rect const viewport = get_display_area(); + NR::Rect const bbox = sp_item_bbox_desktop(item); + return viewport.contains(bbox); +} + +/// +bool SPDesktop::itemIsHidden(SPItem const *item) const { + return item->isHidden(this->dkey); +} + +/** + * Set activate property of desktop; emit signal if changed. + */ +void +SPDesktop::set_active (bool new_active) +{ + if (new_active != _active) { + _active = new_active; + if (new_active) { + _activate_signal.emit(); + } else { + _deactivate_signal.emit(); + } + } +} + +/** + * Set activate status of current desktop's named view. + */ +void +SPDesktop::activate_guides(bool activate) +{ + guides_active = activate; + namedview->activateGuides(this, activate); +} + +/** + * Make desktop switch documents. + */ +void +SPDesktop::change_document (SPDocument *theDocument) +{ + g_return_if_fail (theDocument != NULL); + + /* unselect everything before switching documents */ + selection->clear(); + + setDocument (theDocument); + _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this); + _document_replaced_signal.emit (this, theDocument); +} + +/** + * Make desktop switch event contexts. + */ +void +SPDesktop::set_event_context (GtkType type, const gchar *config) +{ + SPEventContext *ec; + while (event_context) { + ec = event_context; + sp_event_context_deactivate (ec); + event_context = ec->next; + sp_event_context_finish (ec); + g_object_unref (G_OBJECT (ec)); + } + + Inkscape::XML::Node *repr = (config) ? inkscape_get_repr (_inkscape, config) : NULL; + ec = sp_event_context_new (type, this, repr, SP_EVENT_CONTEXT_STATIC); + ec->next = event_context; + event_context = ec; + sp_event_context_activate (ec); + _event_context_changed_signal.emit (this, ec); +} + +/** + * Push event context onto desktop's context stack. + */ +void +SPDesktop::push_event_context (GtkType type, const gchar *config, unsigned int key) +{ + SPEventContext *ref, *ec; + Inkscape::XML::Node *repr; + + if (event_context && event_context->key == key) return; + ref = event_context; + while (ref && ref->next && ref->next->key != key) ref = ref->next; + if (ref && ref->next) { + ec = ref->next; + ref->next = ec->next; + sp_event_context_finish (ec); + g_object_unref (G_OBJECT (ec)); + } + + if (event_context) sp_event_context_deactivate (event_context); + repr = (config) ? inkscape_get_repr (INKSCAPE, config) : NULL; + ec = sp_event_context_new (type, this, repr, key); + ec->next = event_context; + event_context = ec; + sp_event_context_activate (ec); + _event_context_changed_signal.emit (this, ec); +} + +void +SPDesktop::set_coordinate_status (NR::Point p) { + _widget->setCoordinateStatus(p); +} + + +SPItem * +SPDesktop::item_from_list_at_point_bottom (const GSList *list, NR::Point const p) const +{ + g_return_val_if_fail (doc() != NULL, NULL); + return sp_document_item_from_list_at_point_bottom (dkey, SP_GROUP (doc()->root), list, p); +} + +SPItem * +SPDesktop::item_at_point (NR::Point const p, bool into_groups, SPItem *upto) const +{ + g_return_val_if_fail (doc() != NULL, NULL); + return sp_document_item_at_point (doc(), dkey, p, into_groups, upto); +} + +SPItem * +SPDesktop::group_at_point (NR::Point const p) const +{ + g_return_val_if_fail (doc() != NULL, NULL); + return sp_document_group_at_point (doc(), dkey, p); +} + +/** + * \brief Returns the mouse point in document coordinates; if mouse is + * outside the canvas, returns the center of canvas viewpoint + */ +NR::Point +SPDesktop::point() const +{ + NR::Point p = _widget->getPointer(); + NR::Point pw = sp_canvas_window_to_world (canvas, p); + p = w2d(pw); + + NR::Rect const r = canvas->getViewbox(); + + NR::Point r0 = w2d(r.min()); + NR::Point r1 = w2d(r.max()); + + if (p[NR::X] >= r0[NR::X] && + p[NR::X] <= r1[NR::X] && + p[NR::Y] >= r1[NR::Y] && + p[NR::Y] <= r0[NR::Y]) + { + return p; + } else { + return (r0 + r1) / 2; + } +} + +/** + * Put current zoom data in history list. + */ +void +SPDesktop::push_current_zoom (GList **history) +{ + NR::Rect const area = get_display_area(); + + NRRect *old_zoom = g_new(NRRect, 1); + old_zoom->x0 = area.min()[NR::X]; + old_zoom->x1 = area.max()[NR::X]; + old_zoom->y0 = area.min()[NR::Y]; + old_zoom->y1 = area.max()[NR::Y]; + if ( *history == NULL + || !( ( ((NRRect *) ((*history)->data))->x0 == old_zoom->x0 ) && + ( ((NRRect *) ((*history)->data))->x1 == old_zoom->x1 ) && + ( ((NRRect *) ((*history)->data))->y0 == old_zoom->y0 ) && + ( ((NRRect *) ((*history)->data))->y1 == old_zoom->y1 ) ) ) + { + *history = g_list_prepend (*history, old_zoom); + } +} + +/** + * Set viewbox. + */ +void +SPDesktop::set_display_area (double x0, double y0, double x1, double y1, double border, bool log) +{ + g_assert(_widget); + + // save the zoom + if (log) { + push_current_zoom(&zooms_past); + // if we do a logged zoom, our zoom-forward list is invalidated, so delete it + g_list_free (zooms_future); + zooms_future = NULL; + } + + double const cx = 0.5 * (x0 + x1); + double const cy = 0.5 * (y0 + y1); + + NR::Rect const viewbox = NR::expand(canvas->getViewbox(), border); + + double scale = expansion(_d2w); + double newscale; + if (((x1 - x0) * viewbox.dimensions()[NR::Y]) > ((y1 - y0) * viewbox.dimensions()[NR::X])) { + newscale = viewbox.dimensions()[NR::X] / (x1 - x0); + } else { + newscale = viewbox.dimensions()[NR::Y] / (y1 - y0); + } + + newscale = CLAMP(newscale, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX); + + int clear = FALSE; + if (!NR_DF_TEST_CLOSE (newscale, scale, 1e-4 * scale)) { + /* Set zoom factors */ + _d2w = NR::Matrix(NR::scale(newscale, -newscale)); + _w2d = NR::Matrix(NR::scale(1/newscale, 1/-newscale)); + sp_canvas_item_affine_absolute(SP_CANVAS_ITEM(main), _d2w); + clear = TRUE; + } + + /* Calculate top left corner */ + x0 = cx - 0.5 * viewbox.dimensions()[NR::X] / newscale; + y1 = cy + 0.5 * viewbox.dimensions()[NR::Y] / newscale; + + /* Scroll */ + sp_canvas_scroll_to (canvas, x0 * newscale - border, y1 * -newscale - border, clear); + + _widget->updateRulers(); + _widget->updateScrollbars(expansion(_d2w)); + _widget->updateZoom(); +} + +void SPDesktop::set_display_area(NR::Rect const &a, NR::Coord b, bool log) +{ + set_display_area(a.min()[NR::X], a.min()[NR::Y], a.max()[NR::X], a.max()[NR::Y], b, log); +} + +/** + * Return viewbox dimensions. + */ +NR::Rect SPDesktop::get_display_area() const +{ + NR::Rect const viewbox = canvas->getViewbox(); + + double const scale = _d2w[0]; + + return NR::Rect(NR::Point(viewbox.min()[NR::X] / scale, viewbox.max()[NR::Y] / -scale), + NR::Point(viewbox.max()[NR::X] / scale, viewbox.min()[NR::Y] / -scale)); +} + +/** + * Revert back to previous zoom if possible. + */ +void +SPDesktop::prev_zoom() +{ + if (zooms_past == NULL) { + messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No previous zoom.")); + return; + } + + // push current zoom into forward zooms list + push_current_zoom (&zooms_future); + + // restore previous zoom + set_display_area (((NRRect *) zooms_past->data)->x0, + ((NRRect *) zooms_past->data)->y0, + ((NRRect *) zooms_past->data)->x1, + ((NRRect *) zooms_past->data)->y1, + 0, false); + + // remove the just-added zoom from the past zooms list + zooms_past = g_list_remove (zooms_past, ((NRRect *) zooms_past->data)); +} + +/** + * Set zoom to next in list. + */ +void +SPDesktop::next_zoom() +{ + if (zooms_future == NULL) { + this->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No next zoom.")); + return; + } + + // push current zoom into past zooms list + push_current_zoom (&zooms_past); + + // restore next zoom + set_display_area (((NRRect *) zooms_future->data)->x0, + ((NRRect *) zooms_future->data)->y0, + ((NRRect *) zooms_future->data)->x1, + ((NRRect *) zooms_future->data)->y1, + 0, false); + + // remove the just-used zoom from the zooms_future list + zooms_future = g_list_remove (zooms_future, ((NRRect *) zooms_future->data)); +} + +/** + * Zoom to point with absolute zoom factor. + */ +void +SPDesktop::zoom_absolute_keep_point (double cx, double cy, double px, double py, double zoom) +{ + zoom = CLAMP (zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX); + + // maximum or minimum zoom reached, but there's no exact equality because of rounding errors; + // this check prevents "sliding" when trying to zoom in at maximum zoom; + /// \todo someone please fix calculations properly and remove this hack + if (fabs(expansion(_d2w) - zoom) < 0.0001*zoom && (fabs(SP_DESKTOP_ZOOM_MAX - zoom) < 0.01 || fabs(SP_DESKTOP_ZOOM_MIN - zoom) < 0.000001)) + return; + + NR::Rect const viewbox = canvas->getViewbox(); + + double const width2 = viewbox.dimensions()[NR::X] / zoom; + double const height2 = viewbox.dimensions()[NR::Y] / zoom; + + set_display_area(cx - px * width2, + cy - py * height2, + cx + (1 - px) * width2, + cy + (1 - py) * height2, + 0.0); +} + +/** + * Zoom to center with absolute zoom factor. + */ +void +SPDesktop::zoom_absolute (double cx, double cy, double zoom) +{ + zoom_absolute_keep_point (cx, cy, 0.5, 0.5, zoom); +} + +/** + * Zoom to point with relative zoom factor. + */ +void +SPDesktop::zoom_relative_keep_point (double cx, double cy, double zoom) +{ + NR::Rect const area = get_display_area(); + + if (cx < area.min()[NR::X]) { + cx = area.min()[NR::X]; + } + if (cx > area.max()[NR::X]) { + cx = area.max()[NR::X]; + } + if (cy < area.min()[NR::Y]) { + cy = area.min()[NR::Y]; + } + if (cy > area.max()[NR::Y]) { + cy = area.max()[NR::Y]; + } + + gdouble const scale = expansion(_d2w) * zoom; + double const px = (cx - area.min()[NR::X]) / area.dimensions()[NR::X]; + double const py = (cy - area.min()[NR::Y]) / area.dimensions()[NR::Y]; + + zoom_absolute_keep_point(cx, cy, px, py, scale); +} + +/** + * Zoom to center with relative zoom factor. + */ +void +SPDesktop::zoom_relative (double cx, double cy, double zoom) +{ + gdouble scale = expansion(_d2w) * zoom; + zoom_absolute (cx, cy, scale); +} + +/** + * Set display area to origin and current document dimensions. + */ +void +SPDesktop::zoom_page() +{ + NR::Rect d(NR::Point(0, 0), + NR::Point(sp_document_width(doc()), sp_document_height(doc()))); + + if (d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0) { + return; + } + + set_display_area(d, 10); +} + +/** + * Set display area to current document width. + */ +void +SPDesktop::zoom_page_width() +{ + NR::Rect const a = get_display_area(); + + if (sp_document_width(doc()) < 1.0) { + return; + } + + NR::Rect d(NR::Point(0, a.midpoint()[NR::Y]), + NR::Point(sp_document_width(doc()), a.midpoint()[NR::Y])); + + set_display_area(d, 10); +} + +/** + * Zoom to selection. + */ +void +SPDesktop::zoom_selection() +{ + NR::Rect const d = selection->bounds(); + + if (d.dimensions()[NR::X] < 0.1 || d.dimensions()[NR::Y] < 0.1) { + return; + } + + set_display_area(d, 10); +} + +/** + * Tell widget to let zoom widget grab keyboard focus. + */ +void +SPDesktop::zoom_grab_focus() +{ + _widget->letZoomGrabFocus(); +} + +/** + * Zoom to whole drawing. + */ +void +SPDesktop::zoom_drawing() +{ + g_return_if_fail (doc() != NULL); + SPItem *docitem = SP_ITEM (sp_document_root (doc())); + g_return_if_fail (docitem != NULL); + + NR::Rect d = sp_item_bbox_desktop(docitem); + + /* Note that the second condition here indicates that + ** there are no items in the drawing. + */ + if ( d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0 ) { + return; + } + + set_display_area(d, 10); +} + +/** + * Scroll canvas by specific coordinate amount. + */ +void +SPDesktop::scroll_world (double dx, double dy) +{ + g_assert(_widget); + + NR::Rect const viewbox = canvas->getViewbox(); + + sp_canvas_scroll_to(canvas, viewbox.min()[NR::X] - dx, viewbox.min()[NR::Y] - dy, FALSE); + + _widget->updateRulers(); + _widget->updateScrollbars(expansion(_d2w)); +} + +bool +SPDesktop::scroll_to_point (NR::Point const *p, gdouble autoscrollspeed) +{ + gdouble autoscrolldistance = (gdouble) prefs_get_int_attribute_limited ("options.autoscrolldistance", "value", 0, -1000, 10000); + + // autoscrolldistance is in screen pixels, but the display area is in document units + autoscrolldistance /= expansion(_d2w); + NR::Rect const dbox = NR::expand(get_display_area(), -autoscrolldistance); + + if (!((*p)[NR::X] > dbox.min()[NR::X] && (*p)[NR::X] < dbox.max()[NR::X]) || + !((*p)[NR::Y] > dbox.min()[NR::Y] && (*p)[NR::Y] < dbox.max()[NR::Y]) ) { + + NR::Point const s_w( (*p) * _d2w ); + + gdouble x_to; + if ((*p)[NR::X] < dbox.min()[NR::X]) + x_to = dbox.min()[NR::X]; + else if ((*p)[NR::X] > dbox.max()[NR::X]) + x_to = dbox.max()[NR::X]; + else + x_to = (*p)[NR::X]; + + gdouble y_to; + if ((*p)[NR::Y] < dbox.min()[NR::Y]) + y_to = dbox.min()[NR::Y]; + else if ((*p)[NR::Y] > dbox.max()[NR::Y]) + y_to = dbox.max()[NR::Y]; + else + y_to = (*p)[NR::Y]; + + NR::Point const d_dt(x_to, y_to); + NR::Point const d_w( d_dt * _d2w ); + NR::Point const moved_w( d_w - s_w ); + + if (autoscrollspeed == 0) + autoscrollspeed = prefs_get_double_attribute_limited ("options.autoscrollspeed", "value", 1, 0, 10); + + if (autoscrollspeed != 0) + scroll_world (autoscrollspeed * moved_w); + + return true; + } + return false; +} + +void +SPDesktop::fullscreen() +{ + _widget->setFullscreen(); +} + +void +SPDesktop::getWindowGeometry (gint &x, gint &y, gint &w, gint &h) +{ + _widget->getGeometry (x, y, w, h); +} + +void +SPDesktop::setWindowPosition (NR::Point p) +{ + _widget->setPosition (p); +} + +void +SPDesktop::setWindowSize (gint w, gint h) +{ + _widget->setSize (w, h); +} + +void +SPDesktop::setWindowTransient (void *p, int transient_policy) +{ + _widget->setTransient (p, transient_policy); +} + +void +SPDesktop::presentWindow() +{ + _widget->present(); +} + +bool +SPDesktop::warnDialog (gchar *text) +{ + return _widget->warnDialog (text); +} + +void +SPDesktop::toggleRulers() +{ + _widget->toggleRulers(); +} + +void +SPDesktop::toggleScrollbars() +{ + _widget->toggleScrollbars(); +} + +void +SPDesktop::layoutWidget() +{ + _widget->layout(); +} + +void +SPDesktop::destroyWidget() +{ + _widget->destroy(); +} + +bool +SPDesktop::shutdown() +{ + return _widget->shutdown(); +} + +void +SPDesktop::setToolboxFocusTo (gchar const *label) +{ + _widget->setToolboxFocusTo (label); +} + +void +SPDesktop::setToolboxAdjustmentValue (gchar const* id, double val) +{ + _widget->setToolboxAdjustmentValue (id, val); +} + +bool +SPDesktop::isToolboxButtonActive (gchar const *id) +{ + return _widget->isToolboxButtonActive (id); +} + +void +SPDesktop::emitToolSubselectionChanged(gpointer data) +{ + _tool_subselection_changed.emit(data); + inkscape_subselection_changed (this); +} + +//---------------------------------------------------------------------- +// Callback implementations. The virtual ones are connected by the view. + +void +SPDesktop::onPositionSet (double x, double y) +{ + _widget->viewSetPosition (NR::Point(x,y)); +} + +void +SPDesktop::onResized (double x, double y) +{ + // Nothing called here +} + +/** + * Redraw callback; queues Gtk redraw; connected by View. + */ +void +SPDesktop::onRedrawRequested () +{ + if (main) { + _widget->requestCanvasUpdate(); + } +} + +/** + * Associate document with desktop. + */ +void +SPDesktop::setDocument (SPDocument *doc) +{ + if (this->doc() && doc) { + namedview->hide(this); + sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey); + } + + if (_layer_hierarchy) { + _layer_hierarchy->clear(); + delete _layer_hierarchy; + } + _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL); + _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this)); + _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this)); + _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this)); + _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc)); + + /// \todo fixme: This condition exists to make sure the code + /// inside is called only once on initialization. But there + /// are surely more safe methods to accomplish this. + if (drawing) { + NRArenaItem *ai; + + namedview = sp_document_namedview (doc, NULL); + g_signal_connect (G_OBJECT (namedview), "modified", G_CALLBACK (_namedview_modified), this); + number = namedview->getViewCount(); + + ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)), + SP_CANVAS_ARENA (drawing)->arena, + dkey, + SP_ITEM_SHOW_DISPLAY); + if (ai) { + nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL); + nr_arena_item_unref (ai); + } + namedview->show(this); + /* Ugly hack */ + activate_guides (true); + /* Ugly hack */ + _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this); + } + + _document_replaced_signal.emit (this, doc); + + View::setDocument (doc); +} + +void +SPDesktop::onStatusMessage +(Inkscape::MessageType type, gchar const *message) +{ + if (_widget) { + _widget->setMessage(type, message); + } +} + +void +SPDesktop::onDocumentURISet (gchar const* uri) +{ + _widget->setTitle(uri); +} + +/** + * Resized callback. + */ +void +SPDesktop::onDocumentResized (gdouble width, gdouble height) +{ + _doc2dt[5] = height; + sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt); + NR::Rect const a(NR::Point(0, 0), NR::Point(width, height)); + SP_CTRLRECT(page)->setRectangle(a); + SP_CTRLRECT(page_border)->setRectangle(a); +} + + +void +SPDesktop::_onActivate (SPDesktop* dt) +{ + if (!dt->_widget) return; + dt->_widget->activateDesktop(); +} + +void +SPDesktop::_onDeactivate (SPDesktop* dt) +{ + if (!dt->_widget) return; + dt->_widget->deactivateDesktop(); +} + +void +SPDesktop::_onSelectionModified +(Inkscape::Selection *selection, guint flags, SPDesktop *dt) +{ + if (!dt->_widget) return; + dt->_widget->updateScrollbars (expansion(dt->_d2w)); +} + +static void +_onSelectionChanged +(Inkscape::Selection *selection, SPDesktop *desktop) +{ + /** \todo + * only change the layer for single selections, or what? + * This seems reasonable -- for multiple selections there can be many + * different layers involved. + */ + SPItem *item=selection->singleItem(); + if (item) { + SPObject *layer=desktop->layerForObject(item); + if ( layer && layer != desktop->currentLayer() ) { + desktop->setCurrentLayer(layer); + } + } +} + +/** + * Calls event handler of current event context. + * \param arena Unused + * \todo fixme + */ +static gint +_arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop) +{ + if (ai) { + SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai); + return sp_event_context_item_handler (desktop->event_context, spi, event); + } else { + return sp_event_context_root_handler (desktop->event_context, event); + } +} + +static void +_layer_activated(SPObject *layer, SPDesktop *desktop) { + g_return_if_fail(SP_IS_GROUP(layer)); + SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER); +} + +/// Callback +static void +_layer_deactivated(SPObject *layer, SPDesktop *desktop) { + g_return_if_fail(SP_IS_GROUP(layer)); + SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP); +} + +/// Callback +static void +_layer_hierarchy_changed(SPObject *top, SPObject *bottom, + SPDesktop *desktop) +{ + desktop->_layer_changed_signal.emit (bottom); +} + +/// Called when document is starting to be rebuilt. +static void +_reconstruction_start (SPDesktop * desktop) +{ + // printf("Desktop, starting reconstruction\n"); + desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer())); + desktop->_layer_hierarchy->setBottom(desktop->currentRoot()); + + /* + GSList const * selection_objs = desktop->selection->list(); + for (; selection_objs != NULL; selection_objs = selection_objs->next) { + + } + */ + desktop->selection->clear(); + + // printf("Desktop, starting reconstruction end\n"); +} + +/// Called when document rebuild is finished. +static void +_reconstruction_finish (SPDesktop * desktop) +{ + // printf("Desktop, finishing reconstruction\n"); + if (desktop->_reconstruction_old_layer_id == NULL) + return; + + SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id); + if (newLayer != NULL) + desktop->setCurrentLayer(newLayer); + + g_free(desktop->_reconstruction_old_layer_id); + desktop->_reconstruction_old_layer_id = NULL; + // printf("Desktop, finishing reconstruction end\n"); + return; +} + +/** + * Namedview_modified callback. + */ +static void +_namedview_modified (SPNamedView *nv, guint flags, SPDesktop *desktop) +{ + if (flags & SP_OBJECT_MODIFIED_FLAG) { + + /* Recalculate snap distances */ + _update_snap_distances (desktop); + + /* Show/hide page background */ + if (nv->pagecolor & 0xff) { + sp_canvas_item_show (desktop->table); + ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor); + sp_canvas_item_move_to_z (desktop->table, 0); + } else { + sp_canvas_item_hide (desktop->table); + } + + /* Show/hide page border */ + if (nv->showborder) { + // show + sp_canvas_item_show (desktop->page_border); + // set color and shadow + ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000); + if (nv->pageshadow) { + ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor); + } + // place in the z-order stack + if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) { + sp_canvas_item_move_to_z (desktop->page_border, 2); + } else { + int order = sp_canvas_item_order (desktop->page_border); + int morder = sp_canvas_item_order (desktop->drawing); + if (morder > order) sp_canvas_item_raise (desktop->page_border, + morder - order); + } + } else { + sp_canvas_item_hide (desktop->page_border); + if (nv->pageshadow) { + ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000); + } + } + + /* Show/hide page shadow */ + if (nv->showpageshadow && nv->pageshadow) { + ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor); + } else { + ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000); + } + + if (SP_RGBA32_A_U(nv->pagecolor) < 128 || + (SP_RGBA32_R_U(nv->pagecolor) + + SP_RGBA32_G_U(nv->pagecolor) + + SP_RGBA32_B_U(nv->pagecolor)) >= 384) { + // the background color is light or transparent, use black outline + SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xff; + } else { // use white outline + SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xffffffff; + } + } +} + +/** + * Callback to reset snapper's distances. + */ +static void +_update_snap_distances (SPDesktop *desktop) +{ + SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX); + + SPNamedView &nv = *desktop->namedview; + + nv.grid_snapper.setDistance(sp_convert_distance_full(nv.gridtolerance, + *nv.gridtoleranceunit, + px)); + nv.guide_snapper.setDistance(sp_convert_distance_full(nv.guidetolerance, + *nv.guidetoleranceunit, + px)); + nv.object_snapper.setDistance(sp_convert_distance_full(nv.objecttolerance, + *nv.objecttoleranceunit, + px)); +} + + +NR::Matrix SPDesktop::w2d() const +{ + return _w2d; +} + +NR::Point SPDesktop::w2d(NR::Point const &p) const +{ + return p * _w2d; +} + +NR::Point SPDesktop::d2w(NR::Point const &p) const +{ + return p * _d2w; +} + +NR::Matrix SPDesktop::doc2dt() const +{ + return _doc2dt; +} + +NR::Point SPDesktop::doc2dt(NR::Point const &p) const +{ + return p * _doc2dt; +} + +NR::Point SPDesktop::dt2doc(NR::Point const &p) const +{ + return p / _doc2dt; +} + + +/** + * Pop event context from desktop's context stack. Never used. + */ +// void +// SPDesktop::pop_event_context (unsigned int key) +// { +// SPEventContext *ec = NULL; +// +// if (event_context && event_context->key == key) { +// g_return_if_fail (event_context); +// g_return_if_fail (event_context->next); +// ec = event_context; +// sp_event_context_deactivate (ec); +// event_context = ec->next; +// sp_event_context_activate (event_context); +// _event_context_changed_signal.emit (this, ec); +// } +// +// SPEventContext *ref = event_context; +// while (ref && ref->next && ref->next->key != key) +// ref = ref->next; +// +// if (ref && ref->next) { +// ec = ref->next; +// ref->next = ec->next; +// } +// +// if (ec) { +// sp_event_context_finish (ec); +// g_object_unref (G_OBJECT (ec)); +// } +// } + +/* + 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/desktop.h b/src/desktop.h new file mode 100644 index 000000000..55d824e0a --- /dev/null +++ b/src/desktop.h @@ -0,0 +1,289 @@ +#ifndef __SP_DESKTOP_H__ +#define __SP_DESKTOP_H__ + +/** \file + * SPDesktop: an editable view. + * + * Author: + * Lauris Kaplinski + * Frank Felfe + * bulia byak + * Ralf Stephan + * + * Copyright (C) 1999-2005 authors + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include "libnr/nr-matrix.h" +#include "libnr/nr-matrix-fns.h" +#include "libnr/nr-rect.h" +#include "ui/view/view.h" +#include "ui/view/edit-widget-interface.h" + +class NRRect; +class SPCSSAttr; +struct SPCanvas; +struct SPCanvasItem; +struct SPCanvasGroup; +struct SPDesktopWidget; +struct SPEventContext; +struct SPItem; +struct SPNamedView; +struct SPObject; +struct SPStyle; +struct SPViewWidget; + +typedef int sp_verb_t; + +namespace Inkscape { + class Application; + class MessageContext; + class Selection; + class ObjectHierarchy; + namespace UI { + namespace Dialog { + class DialogManager; + } + } + namespace Whiteboard { + class SessionManager; + } +} + +/** + * Editable view. + * + * @see \ref desktop-handles.h for desktop macros. + */ +struct SPDesktop : public Inkscape::UI::View::View +{ + Inkscape::UI::Dialog::DialogManager *_dlg_mgr; + SPNamedView *namedview; + SPCanvas *canvas; + /// current selection; will never generally be NULL + Inkscape::Selection *selection; + SPEventContext *event_context; + + SPCanvasItem *acetate; + SPCanvasGroup *main; + SPCanvasGroup *grid; + SPCanvasGroup *guides; + SPCanvasItem *drawing; + SPCanvasGroup *sketch; + SPCanvasGroup *controls; + SPCanvasItem *table; ///< outside-of-page background + SPCanvasItem *page; ///< page background + SPCanvasItem *page_border; ///< page border + SPCSSAttr *current; ///< current style + + GList *zooms_past; + GList *zooms_future; + unsigned int dkey; + unsigned int number; + bool is_fullscreen; + + /// \todo fixme: This has to be implemented in different way */ + guint guides_active : 1; + + // storage for selected dragger used by GrDrag as it's + // created and deleted by tools + SPItem *gr_item; + guint gr_point_num; + bool gr_fill_or_stroke; + + Inkscape::ObjectHierarchy *_layer_hierarchy; + gchar * _reconstruction_old_layer_id; + + sigc::signal _tool_changed; + sigc::signal _layer_changed_signal; + sigc::signal::accumulated _set_style_signal; + sigc::signal::accumulated _query_style_signal; + sigc::connection connectDocumentReplaced (const sigc::slot & slot) + { + return _document_replaced_signal.connect (slot); + } + + sigc::connection connectEventContextChanged (const sigc::slot & slot) + { + return _event_context_changed_signal.connect (slot); + } + sigc::connection connectSetStyle (const sigc::slot & slot) + { + return _set_style_signal.connect (slot); + } + sigc::connection connectQueryStyle (const sigc::slot & slot) + { + return _query_style_signal.connect (slot); + } + // subselection is some sort of selection which is specific to the tool, such as a handle in gradient tool, or a text selection + sigc::connection connectToolSubselectionChanged(const sigc::slot & slot) { + return _tool_subselection_changed.connect(slot); + } + void emitToolSubselectionChanged(gpointer data); + sigc::connection connectCurrentLayerChanged(const sigc::slot & slot) { + return _layer_changed_signal.connect(slot); + } + + // Whiteboard changes + +#ifdef WITH_INKBOARD + Inkscape::Whiteboard::SessionManager* whiteboard_session_manager() { + return _whiteboard_session_manager; + } + + Inkscape::Whiteboard::SessionManager* _whiteboard_session_manager; +#endif + + SPDesktop(); + void init (SPNamedView* nv, SPCanvas* canvas); + ~SPDesktop(); + void destroy(); + + Inkscape::MessageContext *guidesMessageContext() const { + return _guides_message_context; + } + + void setDisplayModeNormal(); + void setDisplayModeOutline(); + + void set_active (bool new_active); + SPObject *currentRoot() const; + SPObject *currentLayer() const; + void setCurrentLayer(SPObject *object); + SPObject *layerForObject(SPObject *object); + bool isLayer(SPObject *object) const; + bool isWithinViewport(SPItem *item) const; + bool itemIsHidden(SPItem const *item) const; + + void activate_guides (bool activate); + void change_document (SPDocument *document); + + void set_event_context (GtkType type, const gchar *config); + void push_event_context (GtkType type, const gchar *config, unsigned int key); + + void set_coordinate_status (NR::Point p); + SPItem *item_from_list_at_point_bottom (const GSList *list, NR::Point const p) const; + SPItem *item_at_point (NR::Point const p, bool into_groups, SPItem *upto = NULL) const; + SPItem *group_at_point (NR::Point const p) const; + NR::Point point() const; + + NR::Rect get_display_area() const; + void set_display_area (double x0, double y0, double x1, double y1, double border, bool log = true); + void set_display_area(NR::Rect const &a, NR::Coord border, bool log = true); + void zoom_absolute (double cx, double cy, double zoom); + void zoom_relative (double cx, double cy, double zoom); + void zoom_absolute_keep_point (double cx, double cy, double px, double py, double zoom); + void zoom_relative_keep_point (double cx, double cy, double zoom); + void zoom_relative_keep_point (NR::Point const &c, double const zoom) + { + using NR::X; + using NR::Y; + zoom_relative_keep_point (c[X], c[Y], zoom); + } + + void zoom_page(); + void zoom_page_width(); + void zoom_drawing(); + void zoom_selection(); + void zoom_grab_focus(); + double current_zoom() const { return _d2w.expansion(); } + void prev_zoom(); + void next_zoom(); + + bool scroll_to_point (NR::Point const *s_dt, gdouble autoscrollspeed = 0); + void scroll_world (double dx, double dy); + void scroll_world (NR::Point const scroll) + { + using NR::X; + using NR::Y; + scroll_world(scroll[X], scroll[Y]); + } + + void getWindowGeometry (gint &x, gint &y, gint &w, gint &h); + void setWindowPosition (NR::Point p); + void setWindowSize (gint w, gint h); + void setWindowTransient (void* p, int transient_policy=1); + void presentWindow(); + bool warnDialog (gchar *text); + void toggleRulers(); + void toggleScrollbars(); + void layoutWidget(); + void destroyWidget(); + void setToolboxFocusTo (gchar const* label); + void setToolboxAdjustmentValue (gchar const* id, double val); + bool isToolboxButtonActive (gchar const *id); + + void fullscreen(); + + void registerEditWidget (Inkscape::UI::View::EditWidgetInterface *widget) + { _widget = widget; } + + NR::Matrix w2d() const; + NR::Point w2d(NR::Point const &p) const; + NR::Point d2w(NR::Point const &p) const; + NR::Matrix doc2dt() const; + NR::Point doc2dt(NR::Point const &p) const; + NR::Point dt2doc(NR::Point const &p) const; + + virtual void setDocument (SPDocument* doc); + virtual bool shutdown(); + virtual void mouseover() {} + virtual void mouseout() {} + +private: + Inkscape::UI::View::EditWidgetInterface *_widget; + Inkscape::Application *_inkscape; + Inkscape::MessageContext *_guides_message_context; + bool _active; + NR::Matrix _w2d; + NR::Matrix _d2w; + NR::Matrix _doc2dt; + + void push_current_zoom (GList**); + + sigc::signal _document_replaced_signal; + sigc::signal _activate_signal; + sigc::signal _deactivate_signal; + sigc::signal _event_context_changed_signal; + sigc::signal _tool_subselection_changed; + + sigc::connection _activate_connection; + sigc::connection _deactivate_connection; + sigc::connection _sel_modified_connection; + sigc::connection _sel_changed_connection; + sigc::connection _reconstruction_start_connection; + sigc::connection _reconstruction_finish_connection; + + virtual void onPositionSet (double, double); + virtual void onResized (double, double); + virtual void onRedrawRequested(); + virtual void onStatusMessage (Inkscape::MessageType type, gchar const *message); + virtual void onDocumentURISet (gchar const* uri); + virtual void onDocumentResized (double, double); + + static void _onActivate (SPDesktop* dt); + static void _onDeactivate (SPDesktop* dt); + static void _onSelectionModified (Inkscape::Selection *selection, guint flags, SPDesktop *dt); +}; + +#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 : diff --git a/src/dialogs/.cvsignore b/src/dialogs/.cvsignore new file mode 100644 index 000000000..e8014d011 --- /dev/null +++ b/src/dialogs/.cvsignore @@ -0,0 +1,5 @@ +Makefile +Makefile.in +.deps +makefile +.dirstamp diff --git a/src/dialogs/Makefile_insert b/src/dialogs/Makefile_insert new file mode 100644 index 000000000..06ef2975c --- /dev/null +++ b/src/dialogs/Makefile_insert @@ -0,0 +1,75 @@ +## Makefile.am fragment sourced by src/Makefile.am. +# +# Several object property dialogs +# Author: Lauris Kaplinski +# +# Here should be things, that use only xml interface, not objects themselves +# Currently we still have selection_changed functions, but these will be +# replaced by selection 'changed' signal handlers +# + +dialogs/all: dialogs/libspdialogs.a + +dialogs/clean: + rm -f dialogs/libspdialogs.a $(dialogs_libspdialogs_a_OBJECTS) + +dialogs_libspdialogs_a_SOURCES = \ + dialogs/debugdialog.cpp \ + dialogs/debugdialog.h \ + dialogs/dialog-events.cpp \ + dialogs/dialog-events.h \ + dialogs/display-settings.cpp \ + dialogs/display-settings.h \ + dialogs/eek-preview.h \ + dialogs/eek-preview.cpp \ + dialogs/export.cpp \ + dialogs/export.h \ + dialogs/extensions.cpp \ + dialogs/extensions.h \ + dialogs/filedialog.cpp \ + dialogs/filedialog.h \ + dialogs/fill-style.cpp \ + dialogs/fill-style.h \ + dialogs/in-dt-coordsys.cpp \ + dialogs/in-dt-coordsys.h \ + dialogs/input.cpp \ + dialogs/input.h \ + dialogs/item-properties.cpp \ + dialogs/item-properties.h \ + dialogs/layer-properties.cpp \ + dialogs/layer-properties.h \ + dialogs/object-attributes.cpp \ + dialogs/object-attributes.h \ + dialogs/object-properties.cpp \ + dialogs/object-properties.h \ + dialogs/sp-attribute-widget.cpp \ + dialogs/sp-attribute-widget.h \ + dialogs/stroke-style.cpp \ + dialogs/stroke-style.h \ + dialogs/swatches.cpp \ + dialogs/swatches.h \ + dialogs/text-edit.cpp \ + dialogs/text-edit.h \ + dialogs/tiledialog.cpp \ + dialogs/tiledialog.h \ + dialogs/xml-tree.cpp \ + dialogs/xml-tree.h \ + dialogs/rdf.cpp \ + dialogs/rdf.h \ + dialogs/find.cpp \ + dialogs/find.h \ + dialogs/clonetiler.cpp \ + dialogs/clonetiler.h \ + dialogs/unclump.cpp \ + dialogs/unclump.h \ + dialogs/iconpreview.cpp \ + dialogs/iconpreview.h + + +# dialogs/sp-widget.c \ +# dialogs/sp-widget.h \ +# dialogs/gradient-vector.c \ +# dialogs/gradient-vector.h \ +# dialogs/gradient-selector.c \ +# dialogs/gradient-selector.h + diff --git a/src/dialogs/clonetiler.cpp b/src/dialogs/clonetiler.cpp new file mode 100644 index 000000000..72cae3c47 --- /dev/null +++ b/src/dialogs/clonetiler.cpp @@ -0,0 +1,2578 @@ +#define __SP_CLONE_TILER_C__ + +/* + * Clone tiling dialog + * + * Authors: + * bulia byak + * + * Copyright (C) 2004-2005 Authors + * Released under GNU GPL + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include +#include + +#include "application/application.h" +#include "application/editor.h" +#include "helper/window.h" +#include "helper/unit-menu.h" +#include "helper/units.h" +#include "widgets/icon.h" +#include "../inkscape.h" +#include "../prefs-utils.h" +#include "dialog-events.h" +#include "../macros.h" +#include "../verbs.h" +#include "../interface.h" +#include "../selection.h" +#include "../style.h" +#include "../desktop-handles.h" +#include "../sp-namedview.h" +#include "../document.h" +#include "../message-stack.h" +#include "../sp-use.h" +#include "unclump.h" + +#include "xml/repr.h" + +#include "svg/svg.h" + +#include "libnr/nr-matrix-fns.h" +#include "libnr/nr-matrix-ops.h" + +#include "libnr/nr-matrix-translate-ops.h" +#include "libnr/nr-translate-ops.h" +#include "libnr/nr-translate-rotate-ops.h" +#include "libnr/nr-translate-scale-ops.h" + +#include "display/nr-arena.h" +#include "display/nr-arena-item.h" + +#include "ui/widget/color-picker.h" + +static GtkWidget *dlg = NULL; +static win_data wd; + +// impossible original values to make sure they are read from prefs +static gint x = -1000, y = -1000, w = 0, h = 0; +static gchar *prefs_path = "dialogs.clonetiler"; + +#define SB_MARGIN 1 +#define VB_MARGIN 4 + +enum { + PICK_COLOR, + PICK_OPACITY, + PICK_R, + PICK_G, + PICK_B, + PICK_H, + PICK_S, + PICK_L +}; + +static GtkSizeGroup* table_row_labels = NULL; + +static sigc::connection _shutdown_connection; +static sigc::connection _dialogs_hidden_connection; +static sigc::connection _dialogs_unhidden_connection; +static sigc::connection _desktop_activated_connection; +static sigc::connection _color_changed_connection; + +static Inkscape::UI::Widget::ColorPicker *color_picker; + +static void +clonetiler_dialog_destroy (GtkObject *object, gpointer data) +{ + if (Inkscape::NSApplication::Application::getNewGui()) + { + _shutdown_connection.disconnect(); + _dialogs_hidden_connection.disconnect(); + _dialogs_unhidden_connection.disconnect(); + _desktop_activated_connection.disconnect(); + } else { + sp_signal_disconnect_by_data (INKSCAPE, dlg); + } + _color_changed_connection.disconnect(); + + delete color_picker; + + wd.win = dlg = NULL; + wd.stop = 0; + +} + +static gboolean +clonetiler_dialog_delete (GtkObject *object, GdkEvent * /*event*/, gpointer data) +{ + gtk_window_get_position ((GtkWindow *) dlg, &x, &y); + gtk_window_get_size ((GtkWindow *) dlg, &w, &h); + + prefs_set_int_attribute (prefs_path, "x", x); + prefs_set_int_attribute (prefs_path, "y", y); + prefs_set_int_attribute (prefs_path, "w", w); + prefs_set_int_attribute (prefs_path, "h", h); + + return FALSE; // which means, go ahead and destroy it + +} + +static void on_delete() +{ + (void)clonetiler_dialog_delete (0, 0, NULL); +} + +static void +on_picker_color_changed (guint rgba) +{ + static bool is_updating = false; + if (is_updating || !SP_ACTIVE_DESKTOP) + return; + + is_updating = true; + + Inkscape::XML::Node *repr = inkscape_get_repr(INKSCAPE, prefs_path); + gchar c[32]; + sp_svg_write_color(c, 32, rgba); + repr->setAttribute("initial_color", c); + + is_updating = false; +} + +static guint clonetiler_number_of_clones (SPObject *obj); + +static void +clonetiler_change_selection (Inkscape::Application * /*inkscape*/, Inkscape::Selection *selection, GtkWidget *dlg) +{ + GtkWidget *buttons = (GtkWidget *) g_object_get_data (G_OBJECT(dlg), "buttons_on_tiles"); + GtkWidget *status = (GtkWidget *) g_object_get_data (G_OBJECT(dlg), "status"); + + if (selection->isEmpty()) { + gtk_widget_set_sensitive (buttons, FALSE); + gtk_label_set_markup (GTK_LABEL(status), _("Nothing selected.")); + return; + } + + if (g_slist_length ((GSList *) selection->itemList()) > 1) { + gtk_widget_set_sensitive (buttons, FALSE); + gtk_label_set_markup (GTK_LABEL(status), _("More than one object selected.")); + return; + } + + guint n = clonetiler_number_of_clones(selection->singleItem()); + if (n > 0) { + gtk_widget_set_sensitive (buttons, TRUE); + gchar *sta = g_strdup_printf (_("Object has %d tiled clones."), n); + gtk_label_set_markup (GTK_LABEL(status), sta); + g_free (sta); + } else { + gtk_widget_set_sensitive (buttons, FALSE); + gtk_label_set_markup (GTK_LABEL(status), _("Object has no tiled clones.")); + } +} + +static void +clonetiler_external_change (Inkscape::Application * /*inkscape*/, GtkWidget *dlg) +{ + clonetiler_change_selection (NULL, SP_DT_SELECTION(SP_ACTIVE_DESKTOP), dlg); +} + +static void clonetiler_disconnect_gsignal (GObject *widget, gpointer source) { + if (source && G_IS_OBJECT(source)) + sp_signal_disconnect_by_data (source, widget); +} + + +enum { + TILE_P1, + TILE_P2, + TILE_PM, + TILE_PG, + TILE_CM, + TILE_PMM, + TILE_PMG, + TILE_PGG, + TILE_CMM, + TILE_P4, + TILE_P4M, + TILE_P4G, + TILE_P3, + TILE_P31M, + TILE_P3M1, + TILE_P6, + TILE_P6M +}; + + +static NR::Matrix +clonetiler_get_transform ( + // symmetry group + int type, + // row, column + int x, int y, + // center, width, height of the tile + double cx, double cy, + double w, double h, + + // values from the dialog: + double d_x_per_x, double d_y_per_x, double d_x_per_y, double d_y_per_y, + int alternate_x, int alternate_y, double rand_x, double rand_y, + double d_per_x_exp, double d_per_y_exp, + double d_rot_per_x, double d_rot_per_y, int alternate_rotx, int alternate_roty, double rand_rot, + double d_scalex_per_x, double d_scaley_per_x, double d_scalex_per_y, double d_scaley_per_y, + int alternate_scalex, int alternate_scaley, double rand_scalex, double rand_scaley + ) +{ + // in abs units + double eff_x = (alternate_x? (x%2) : pow ((double) x, d_per_x_exp)); + double eff_y = (alternate_y? (y%2) : pow ((double) y, d_per_y_exp)); + double dx = d_x_per_x * w * eff_x + d_x_per_y * w * eff_y + rand_x * w * g_random_double_range (-1, 1); + double dy = d_y_per_x * h * eff_x + d_y_per_y * h * eff_y + rand_y * h * g_random_double_range (-1, 1); + + NR::Matrix rect_translate (NR::translate (w * pow ((double) x, d_per_x_exp) + dx, h * pow ((double) y, d_per_y_exp) + dy)); + + // in deg + double eff_x_rot = (alternate_rotx? (x%2) : (x)); + double eff_y_rot = (alternate_roty? (y%2) : (y)); + double drot = d_rot_per_x * eff_x_rot + d_rot_per_y * eff_y_rot + rand_rot * 180 * g_random_double_range (-1, 1); + + // times the original + double eff_x_s = (alternate_scalex? (x%2) : (x)); + double eff_y_s = (alternate_scaley? (y%2) : (y)); + double rand_scale_x, rand_scale_y; + if (rand_scaley == rand_scalex) { + // if rands are equal, scale proportionally + rand_scale_x = rand_scale_y = rand_scalex * 1 * g_random_double_range (-1, 1); + } else { + rand_scale_x = rand_scalex * 1 * g_random_double_range (-1, 1); + rand_scale_y = rand_scaley * 1 * g_random_double_range (-1, 1); + } + double dscalex = 1 + d_scalex_per_x * eff_x_s + d_scalex_per_y * eff_y_s + rand_scale_x; + if (dscalex < 0) dscalex = 0; + double dscaley = 1 + d_scaley_per_x * eff_x_s + d_scaley_per_y * eff_y_s + rand_scale_y; + if (dscaley < 0) dscaley = 0; + + NR::Matrix drot_c = NR::translate(-cx, -cy) * NR::rotate (M_PI*drot/180) * NR::translate(cx, cy); + + NR::Matrix dscale_c = NR::translate(-cx, -cy) * NR::scale (dscalex, dscaley) * NR::translate(cx, cy); + + NR::Matrix d_s_r = dscale_c * drot_c; + + NR::Matrix rotate_180_c = NR::translate(-cx, -cy) * NR::rotate (M_PI) * NR::translate(cx, cy); + + NR::Matrix rotate_90_c = NR::translate(-cx, -cy) * NR::rotate (-M_PI/2) * NR::translate(cx, cy); + NR::Matrix rotate_m90_c = NR::translate(-cx, -cy) * NR::rotate (M_PI/2) * NR::translate(cx, cy); + + NR::Matrix rotate_120_c = NR::translate(-cx, -cy) * NR::rotate (-2*M_PI/3) * NR::translate(cx, cy); + NR::Matrix rotate_m120_c = NR::translate(-cx, -cy) * NR::rotate (2*M_PI/3) * NR::translate(cx, cy); + + NR::Matrix rotate_60_c = NR::translate(-cx, -cy) * NR::rotate (-M_PI/3) * NR::translate(cx, cy); + NR::Matrix rotate_m60_c = NR::translate(-cx, -cy) * NR::rotate (M_PI/3) * NR::translate(cx, cy); + + double cos60 = cos(M_PI/3); + double sin60 = sin(M_PI/3); + double cos30 = cos(M_PI/6); + double sin30 = sin(M_PI/6); + + NR::Matrix flip_x = NR::translate(-cx, -cy) * NR::scale (-1, 1) * NR::translate(cx, cy); + NR::Matrix flip_y = NR::translate(-cx, -cy) * NR::scale (1, -1) * NR::translate(cx, cy); + + x = (int) pow ((double) x, d_per_x_exp); + y = (int) pow ((double) y, d_per_y_exp); + + switch (type) { + + case TILE_P1: + return d_s_r * rect_translate; + break; + + case TILE_P2: + if (x % 2 == 0) { + return d_s_r * rect_translate; + } else { + return d_s_r * rotate_180_c * rect_translate; + } + break; + + case TILE_PM: + if (x % 2 == 0) { + return d_s_r * rect_translate; + } else { + return d_s_r * flip_x * rect_translate; + } + break; + + case TILE_PG: + if (y % 2 == 0) { + return d_s_r * rect_translate; + } else { + return d_s_r * flip_x * rect_translate; + } + break; + + case TILE_CM: + if ((x + y) % 2 == 0) { + return d_s_r * rect_translate; + } else { + return d_s_r * flip_x * rect_translate; + } + break; + + case TILE_PMM: + if (y % 2 == 0) { + if (x % 2 == 0) { + return d_s_r * rect_translate; + } else { + return d_s_r * flip_x * rect_translate; + } + } else { + if (x % 2 == 0) { + return d_s_r * flip_y * rect_translate; + } else { + return d_s_r * flip_x * flip_y * rect_translate; + } + } + break; + + case TILE_PMG: + if (y % 4 == 0) { + return d_s_r * rect_translate; + } else if (y % 4 == 1) { + return d_s_r * flip_y * rect_translate; + } else if (y % 4 == 2) { + return d_s_r * flip_x * rect_translate; + } else if (y % 4 == 3) { + return d_s_r * flip_x * flip_y * rect_translate; + } + break; + + case TILE_PGG: + if (y % 2 == 0) { + if (x % 2 == 0) { + return d_s_r * rect_translate; + } else { + return d_s_r * flip_y * rect_translate; + } + } else { + if (x % 2 == 0) { + return d_s_r * rotate_180_c * rect_translate; + } else { + return d_s_r * rotate_180_c * flip_y * rect_translate; + } + } + break; + + case TILE_CMM: + if (y % 4 == 0) { + if (x % 2 == 0) { + return d_s_r * rect_translate; + } else { + return d_s_r * flip_x * rect_translate; + } + } else if (y % 4 == 1) { + if (x % 2 == 0) { + return d_s_r * flip_y * rect_translate; + } else { + return d_s_r * flip_x * flip_y * rect_translate; + } + } else if (y % 4 == 2) { + if (x % 2 == 1) { + return d_s_r * rect_translate; + } else { + return d_s_r * flip_x * rect_translate; + } + } else { + if (x % 2 == 1) { + return d_s_r * flip_y * rect_translate; + } else { + return d_s_r * flip_x * flip_y * rect_translate; + } + } + break; + + case TILE_P4: + { + NR::Matrix ori (NR::translate ((w + h) * (x/2) + dx, (h + w) * (y/2) + dy)); + NR::Matrix dia1 (NR::translate (w/2 + h/2, -h/2 + w/2)); + NR::Matrix dia2 (NR::translate (-w/2 + h/2, h/2 + w/2)); + if (y % 2 == 0) { + if (x % 2 == 0) { + return d_s_r * ori; + } else { + return d_s_r * rotate_m90_c * dia1 * ori; + } + } else { + if (x % 2 == 0) { + return d_s_r * rotate_90_c * dia2 * ori; + } else { + return d_s_r * rotate_180_c * dia1 * dia2 * ori; + } + } + } + break; + + case TILE_P4M: + { + double max = MAX(w, h); + NR::Matrix ori (NR::translate ((max + max) * (x/4) + dx, (max + max) * (y/2) + dy)); + NR::Matrix dia1 (NR::translate (w/2 - h/2, h/2 - w/2)); + NR::Matrix dia2 (NR::translate (-h/2 + w/2, w/2 - h/2)); + if (y % 2 == 0) { + if (x % 4 == 0) { + return d_s_r * ori; + } else if (x % 4 == 1) { + return d_s_r * flip_y * rotate_m90_c * dia1 * ori; + } else if (x % 4 == 2) { + return d_s_r * rotate_m90_c * dia1 * NR::translate (h, 0) * ori; + } else if (x % 4 == 3) { + return d_s_r * flip_x * NR::translate (w, 0) * ori; + } + } else { + if (x % 4 == 0) { + return d_s_r * flip_y * NR::translate(0, h) * ori; + } else if (x % 4 == 1) { + return d_s_r * rotate_90_c * dia2 * NR::translate(0, h) * ori; + } else if (x % 4 == 2) { + return d_s_r * flip_y * rotate_90_c * dia2 * NR::translate(h, 0) * NR::translate(0, h) * ori; + } else if (x % 4 == 3) { + return d_s_r * flip_y * flip_x * NR::translate(w, 0) * NR::translate(0, h) * ori; + } + } + } + break; + + case TILE_P4G: + { + double max = MAX(w, h); + NR::Matrix ori (NR::translate ((max + max) * (x/4) + dx, (max + max) * y + dy)); + NR::Matrix dia1 (NR::translate (w/2 + h/2, h/2 - w/2)); + NR::Matrix dia2 (NR::translate (-h/2 + w/2, w/2 + h/2)); + if (((x/4) + y) % 2 == 0) { + if (x % 4 == 0) { + return d_s_r * ori; + } else if (x % 4 == 1) { + return d_s_r * rotate_m90_c * dia1 * ori; + } else if (x % 4 == 2) { + return d_s_r * rotate_90_c * dia2 * ori; + } else if (x % 4 == 3) { + return d_s_r * rotate_180_c * dia1 * dia2 * ori; + } + } else { + if (x % 4 == 0) { + return d_s_r * flip_y * NR::translate (0, h) * ori; + } else if (x % 4 == 1) { + return d_s_r * flip_y * rotate_m90_c * dia1 * NR::translate (-h, 0) * ori; + } else if (x % 4 == 2) { + return d_s_r * flip_y * rotate_90_c * dia2 * NR::translate (h, 0) * ori; + } else if (x % 4 == 3) { + return d_s_r * flip_x * NR::translate (w, 0) * ori; + } + } + } + break; + + case TILE_P3: + { + double width; + double height; + NR::Matrix dia1; + NR::Matrix dia2; + if (w > h) { + width = w + w * cos60; + height = 2 * w * sin60; + dia1 = NR::Matrix (NR::translate (w/2 + w/2 * cos60, -(w/2 * sin60))); + dia2 = dia1 * NR::Matrix (NR::translate (0, 2 * (w/2 * sin60))); + } else { + width = h * cos (M_PI/6); + height = h; + dia1 = NR::Matrix (NR::translate (h/2 * cos30, -(h/2 * sin30))); + dia2 = dia1 * NR::Matrix (NR::translate (0, h/2)); + } + NR::Matrix ori (NR::translate (width * (2*(x/3) + y%2) + dx, (height/2) * y + dy)); + if (x % 3 == 0) { + return d_s_r * ori; + } else if (x % 3 == 1) { + return d_s_r * rotate_m120_c * dia1 * ori; + } else if (x % 3 == 2) { + return d_s_r * rotate_120_c * dia2 * ori; + } + } + break; + + case TILE_P31M: + { + NR::Matrix ori; + NR::Matrix dia1; + NR::Matrix dia2; + NR::Matrix dia3; + NR::Matrix dia4; + if (w > h) { + ori = NR::Matrix(NR::translate (w * (x/6) + w/2 * (y%2) + dx, (w * cos30) * y + dy)); + dia1 = NR::Matrix (NR::translate (0, h/2) * NR::translate (w/2, 0) * NR::translate (w/2 * cos60, -w/2 * sin60) * NR::translate (-h/2 * cos30, -h/2 * sin30) ); + dia2 = dia1 * NR::Matrix (NR::translate (h * cos30, h * sin30)); + dia3 = dia2 * NR::Matrix (NR::translate (0, 2 * (w/2 * sin60 - h/2 * sin30))); + dia4 = dia3 * NR::Matrix (NR::translate (-h * cos30, h * sin30)); + } else { + ori = NR::Matrix (NR::translate (2*h * cos30 * (x/6 + 0.5*(y%2)) + dx, (2*h - h * sin30) * y + dy)); + dia1 = NR::Matrix (NR::translate (0, -h/2) * NR::translate (h/2 * cos30, h/2 * sin30)); + dia2 = dia1 * NR::Matrix (NR::translate (h * cos30, h * sin30)); + dia3 = dia2 * NR::Matrix (NR::translate (0, h/2)); + dia4 = dia3 * NR::Matrix (NR::translate (-h * cos30, h * sin30)); + } + if (x % 6 == 0) { + return d_s_r * ori; + } else if (x % 6 == 1) { + return d_s_r * flip_y * rotate_m120_c * dia1 * ori; + } else if (x % 6 == 2) { + return d_s_r * rotate_m120_c * dia2 * ori; + } else if (x % 6 == 3) { + return d_s_r * flip_y * rotate_120_c * dia3 * ori; + } else if (x % 6 == 4) { + return d_s_r * rotate_120_c * dia4 * ori; + } else if (x % 6 == 5) { + return d_s_r * flip_y * NR::translate(0, h) * ori; + } + } + break; + + case TILE_P3M1: + { + double width; + double height; + NR::Matrix dia1; + NR::Matrix dia2; + NR::Matrix dia3; + NR::Matrix dia4; + if (w > h) { + width = w + w * cos60; + height = 2 * w * sin60; + dia1 = NR::Matrix (NR::translate (0, h/2) * NR::translate (w/2, 0) * NR::translate (w/2 * cos60, -w/2 * sin60) * NR::translate (-h/2 * cos30, -h/2 * sin30) ); + dia2 = dia1 * NR::Matrix (NR::translate (h * cos30, h * sin30)); + dia3 = dia2 * NR::Matrix (NR::translate (0, 2 * (w/2 * sin60 - h/2 * sin30))); + dia4 = dia3 * NR::Matrix (NR::translate (-h * cos30, h * sin30)); + } else { + width = 2 * h * cos (M_PI/6); + height = 2 * h; + dia1 = NR::Matrix (NR::translate (0, -h/2) * NR::translate (h/2 * cos30, h/2 * sin30)); + dia2 = dia1 * NR::Matrix (NR::translate (h * cos30, h * sin30)); + dia3 = dia2 * NR::Matrix (NR::translate (0, h/2)); + dia4 = dia3 * NR::Matrix (NR::translate (-h * cos30, h * sin30)); + } + NR::Matrix ori (NR::translate (width * (2*(x/6) + y%2) + dx, (height/2) * y + dy)); + if (x % 6 == 0) { + return d_s_r * ori; + } else if (x % 6 == 1) { + return d_s_r * flip_y * rotate_m120_c * dia1 * ori; + } else if (x % 6 == 2) { + return d_s_r * rotate_m120_c * dia2 * ori; + } else if (x % 6 == 3) { + return d_s_r * flip_y * rotate_120_c * dia3 * ori; + } else if (x % 6 == 4) { + return d_s_r * rotate_120_c * dia4 * ori; + } else if (x % 6 == 5) { + return d_s_r * flip_y * NR::translate(0, h) * ori; + } + } + break; + + case TILE_P6: + { + NR::Matrix ori; + NR::Matrix dia1; + NR::Matrix dia2; + NR::Matrix dia3; + NR::Matrix dia4; + NR::Matrix dia5; + if (w > h) { + ori = NR::Matrix(NR::translate (2*w * (x/6) + w * (y%2) + dx, (2*w * sin60) * y + dy)); + dia1 = NR::Matrix (NR::translate (w/2 * cos60, -w/2 * sin60)); + dia2 = dia1 * NR::Matrix (NR::translate (w/2, 0)); + dia3 = dia2 * NR::Matrix (NR::translate (w/2 * cos60, w/2 * sin60)); + dia4 = dia3 * NR::Matrix (NR::translate (-w/2 * cos60, w/2 * sin60)); + dia5 = dia4 * NR::Matrix (NR::translate (-w/2, 0)); + } else { + ori = NR::Matrix(NR::translate (2*h * cos30 * (x/6 + 0.5*(y%2)) + dx, (h + h * sin30) * y + dy)); + dia1 = NR::Matrix (NR::translate (-w/2, -h/2) * NR::translate (h/2 * cos30, -h/2 * sin30) * NR::translate (w/2 * cos60, w/2 * sin60)); + dia2 = dia1 * NR::Matrix (NR::translate (-w/2 * cos60, -w/2 * sin60) * NR::translate (h/2 * cos30, -h/2 * sin30) * NR::translate (h/2 * cos30, h/2 * sin30) * NR::translate (-w/2 * cos60, w/2 * sin60)); + dia3 = dia2 * NR::Matrix (NR::translate (w/2 * cos60, -w/2 * sin60) * NR::translate (h/2 * cos30, h/2 * sin30) * NR::translate (-w/2, h/2)); + dia4 = dia3 * dia1.inverse(); + dia5 = dia3 * dia2.inverse(); + } + if (x % 6 == 0) { + return d_s_r * ori; + } else if (x % 6 == 1) { + return d_s_r * rotate_m60_c * dia1 * ori; + } else if (x % 6 == 2) { + return d_s_r * rotate_m120_c * dia2 * ori; + } else if (x % 6 == 3) { + return d_s_r * rotate_180_c * dia3 * ori; + } else if (x % 6 == 4) { + return d_s_r * rotate_120_c * dia4 * ori; + } else if (x % 6 == 5) { + return d_s_r * rotate_60_c * dia5 * ori; + } + } + break; + + case TILE_P6M: + { + + NR::Matrix ori; + NR::Matrix dia1, dia2, dia3, dia4, dia5, dia6, dia7, dia8, dia9, dia10; + if (w > h) { + ori = NR::Matrix(NR::translate (2*w * (x/12) + w * (y%2) + dx, (2*w * sin60) * y + dy)); + dia1 = NR::Matrix (NR::translate (w/2, h/2) * NR::translate (-w/2 * cos60, -w/2 * sin60) * NR::translate (-h/2 * cos30, h/2 * sin30)); + dia2 = dia1 * NR::Matrix (NR::translate (h * cos30, -h * sin30)); + dia3 = dia2 * NR::Matrix (NR::translate (-h/2 * cos30, h/2 * sin30) * NR::translate (w * cos60, 0) * NR::translate (-h/2 * cos30, -h/2 * sin30)); + dia4 = dia3 * NR::Matrix (NR::translate (h * cos30, h * sin30)); + dia5 = dia4 * NR::Matrix (NR::translate (-h/2 * cos30, -h/2 * sin30) * NR::translate (-w/2 * cos60, w/2 * sin60) * NR::translate (w/2, -h/2)); + dia6 = dia5 * NR::Matrix (NR::translate (0, h)); + dia7 = dia6 * dia1.inverse(); + dia8 = dia6 * dia2.inverse(); + dia9 = dia6 * dia3.inverse(); + dia10 = dia6 * dia4.inverse(); + } else { + ori = NR::Matrix(NR::translate (4*h * cos30 * (x/12 + 0.5*(y%2)) + dx, (2*h + 2*h * sin30) * y + dy)); + dia1 = NR::Matrix (NR::translate (-w/2, -h/2) * NR::translate (h/2 * cos30, -h/2 * sin30) * NR::translate (w/2 * cos60, w/2 * sin60)); + dia2 = dia1 * NR::Matrix (NR::translate (h * cos30, -h * sin30)); + dia3 = dia2 * NR::Matrix (NR::translate (-w/2 * cos60, -w/2 * sin60) * NR::translate (h * cos30, 0) * NR::translate (-w/2 * cos60, w/2 * sin60)); + dia4 = dia3 * NR::Matrix (NR::translate (h * cos30, h * sin30)); + dia5 = dia4 * NR::Matrix (NR::translate (w/2 * cos60, -w/2 * sin60) * NR::translate (h/2 * cos30, h/2 * sin30) * NR::translate (-w/2, h/2)); + dia6 = dia5 * NR::Matrix (NR::translate (0, h)); + dia7 = dia6 * dia1.inverse(); + dia8 = dia6 * dia2.inverse(); + dia9 = dia6 * dia3.inverse(); + dia10 = dia6 * dia4.inverse(); + } + if (x % 12 == 0) { + return d_s_r * ori; + } else if (x % 12 == 1) { + return d_s_r * flip_y * rotate_m60_c * dia1 * ori; + } else if (x % 12 == 2) { + return d_s_r * rotate_m60_c * dia2 * ori; + } else if (x % 12 == 3) { + return d_s_r * flip_y * rotate_m120_c * dia3 * ori; + } else if (x % 12 == 4) { + return d_s_r * rotate_m120_c * dia4 * ori; + } else if (x % 12 == 5) { + return d_s_r * flip_x * dia5 * ori; + } else if (x % 12 == 6) { + return d_s_r * flip_x * flip_y * dia6 * ori; + } else if (x % 12 == 7) { + return d_s_r * flip_y * rotate_120_c * dia7 * ori; + } else if (x % 12 == 8) { + return d_s_r * rotate_120_c * dia8 * ori; + } else if (x % 12 == 9) { + return d_s_r * flip_y * rotate_60_c * dia9 * ori; + } else if (x % 12 == 10) { + return d_s_r * rotate_60_c * dia10 * ori; + } else if (x % 12 == 11) { + return d_s_r * flip_y * NR::translate (0, h) * ori; + } + } + break; + + default: + break; + } + + return NR::identity(); +} + +static bool +clonetiler_is_a_clone_of (SPObject *tile, SPObject *obj) +{ + char *id_href = NULL; + + if (obj) { + Inkscape::XML::Node *obj_repr = SP_OBJECT_REPR(obj); + id_href = g_strdup_printf("#%s", obj_repr->attribute("id")); + } + + if (SP_IS_USE(tile) && + SP_OBJECT_REPR(tile)->attribute("xlink:href") && + (!id_href || !strcmp(id_href, SP_OBJECT_REPR(tile)->attribute("xlink:href"))) && + SP_OBJECT_REPR(tile)->attribute("inkscape:tiled-clone-of") && + (!id_href || !strcmp(id_href, SP_OBJECT_REPR(tile)->attribute("inkscape:tiled-clone-of")))) + { + if (id_href) + g_free (id_href); + return true; + } else { + if (id_href) + g_free (id_href); + return false; + } +} + +static NRArena const *trace_arena = NULL; +static unsigned trace_visionkey; +static NRArenaItem *trace_root; +static gdouble trace_zoom; + +static void +clonetiler_trace_hide_tiled_clones_recursively (SPObject *from) +{ + if (!trace_arena) + return; + + for (SPObject *o = sp_object_first_child(from); o != NULL; o = SP_OBJECT_NEXT(o)) { + if (SP_IS_ITEM(o) && clonetiler_is_a_clone_of (o, NULL)) + sp_item_invoke_hide(SP_ITEM(o), trace_visionkey); // FIXME: hide each tiled clone's original too! + clonetiler_trace_hide_tiled_clones_recursively (o); + } +} + +static void +clonetiler_trace_setup (SPDocument *doc, gdouble zoom, SPItem *original) +{ + trace_arena = NRArena::create(); + /* Create ArenaItem and set transform */ + trace_visionkey = sp_item_display_key_new(1); + trace_root = sp_item_invoke_show( SP_ITEM(SP_DOCUMENT_ROOT (doc)), + (NRArena *) trace_arena, trace_visionkey, SP_ITEM_SHOW_DISPLAY); + + // hide the (current) original and any tiled clones, we only want to pick the background + sp_item_invoke_hide(original, trace_visionkey); + clonetiler_trace_hide_tiled_clones_recursively (SP_OBJECT(SP_DOCUMENT_ROOT (doc))); + + sp_document_root (doc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + sp_document_ensure_up_to_date(doc); + + trace_zoom = zoom; +} + +static guint32 +clonetiler_trace_pick (NR::Rect box) +{ + if (!trace_arena) + return 0; + + NRMatrix t; + nr_matrix_set_scale(&t, trace_zoom, trace_zoom); + nr_arena_item_set_transform(trace_root, &t); + NRGC gc(NULL); + nr_matrix_set_identity(&gc.transform); + nr_arena_item_invoke_update( trace_root, NULL, &gc, + NR_ARENA_ITEM_STATE_ALL, + NR_ARENA_ITEM_STATE_NONE ); + + /* Item integer bbox in points */ + NRRectL ibox; + ibox.x0 = (int) floor(trace_zoom * box.min()[NR::X] + 0.5); + ibox.y0 = (int) floor(trace_zoom * box.min()[NR::Y] + 0.5); + ibox.x1 = (int) floor(trace_zoom * box.max()[NR::X] + 0.5); + ibox.y1 = (int) floor(trace_zoom * box.max()[NR::Y] + 0.5); + + /* Find visible area */ + int width = ibox.x1 - ibox.x0; + int height = ibox.y1 - ibox.y0; + + /* Set up pixblock */ + guchar *px = nr_new(guchar, 4 * width * height); + memset(px, 0x00, 4 * width * height); + + /* Render */ + NRPixBlock pb; + nr_pixblock_setup_extern( &pb, NR_PIXBLOCK_MODE_R8G8B8A8N, + ibox.x0, ibox.y0, ibox.x1, ibox.y1, + px, 4 * width, FALSE, FALSE ); + nr_arena_item_invoke_render( trace_root, &ibox, &pb, + NR_ARENA_ITEM_RENDER_NO_CACHE ); + + double R = 0, G = 0, B = 0, A = 0; + double count = 0; + double weight = 0; + + for (int y = ibox.y0; y < ibox.y1; y++) { + const unsigned char *s = NR_PIXBLOCK_PX (&pb) + (y - ibox.y0) * pb.rs; + for (int x = ibox.x0; x < ibox.x1; x++) { + count += 1; + weight += s[3] / 255.0; + R += s[0] / 255.0; + G += s[1] / 255.0; + B += s[2] / 255.0; + A += s[3] / 255.0; + s += 4; + } + } + + nr_pixblock_release(&pb); + + R = R / weight; + G = G / weight; + B = B / weight; + A = A / count; + + R = CLAMP (R, 0.0, 1.0); + G = CLAMP (G, 0.0, 1.0); + B = CLAMP (B, 0.0, 1.0); + A = CLAMP (A, 0.0, 1.0); + + return SP_RGBA32_F_COMPOSE (R, G, B, A); +} + +static void +clonetiler_trace_finish () +{ + if (trace_arena) { + ((NRObject *) trace_arena)->unreference(); + trace_arena = NULL; + } +} + +static void +clonetiler_unclump (GtkWidget *widget, void *) +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop == NULL) + return; + + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + + // check if something is selected + if (selection->isEmpty() || g_slist_length((GSList *) selection->itemList()) > 1) { + SP_DT_MSGSTACK(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select one object whose tiled clones to unclump.")); + return; + } + + SPObject *obj = SP_OBJECT(selection->singleItem()); + SPObject *parent = SP_OBJECT_PARENT (obj); + + GSList *to_unclump = NULL; // not including the original + + for (SPObject *child = sp_object_first_child(parent); child != NULL; child = SP_OBJECT_NEXT(child)) { + if (clonetiler_is_a_clone_of (child, obj)) { + to_unclump = g_slist_prepend (to_unclump, child); + } + } + + sp_document_ensure_up_to_date(SP_DT_DOCUMENT(desktop)); + + unclump (to_unclump); + + g_slist_free (to_unclump); + + sp_document_done (SP_DT_DOCUMENT (desktop)); +} + +static guint +clonetiler_number_of_clones (SPObject *obj) +{ + SPObject *parent = SP_OBJECT_PARENT (obj); + + guint n = 0; + + for (SPObject *child = sp_object_first_child(parent); child != NULL; child = SP_OBJECT_NEXT(child)) { + if (clonetiler_is_a_clone_of (child, obj)) { + n ++; + } + } + + return n; +} + +static void +clonetiler_remove (GtkWidget *widget, void *, bool do_undo = true) +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop == NULL) + return; + + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + + // check if something is selected + if (selection->isEmpty() || g_slist_length((GSList *) selection->itemList()) > 1) { + SP_DT_MSGSTACK(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select one object whose tiled clones to remove.")); + return; + } + + SPObject *obj = SP_OBJECT(selection->singleItem()); + SPObject *parent = SP_OBJECT_PARENT (obj); + +// remove old tiling + GSList *to_delete = NULL; + for (SPObject *child = sp_object_first_child(parent); child != NULL; child = SP_OBJECT_NEXT(child)) { + if (clonetiler_is_a_clone_of (child, obj)) { + to_delete = g_slist_prepend (to_delete, child); + } + } + for (GSList *i = to_delete; i; i = i->next) { + SP_OBJECT(i->data)->deleteObject(); + } + g_slist_free (to_delete); + + clonetiler_change_selection (NULL, selection, dlg); + + if (do_undo) + sp_document_done (SP_DT_DOCUMENT (desktop)); +} + +static NR::Rect +transform_rect(NR::Rect const &r, NR::Matrix const &m) +{ + using NR::X; + using NR::Y; + NR::Point const p1 = r.corner(1) * m; + NR::Point const p2 = r.corner(2) * m; + NR::Point const p3 = r.corner(3) * m; + NR::Point const p4 = r.corner(4) * m; + return NR::Rect( + NR::Point( + std::min(std::min(p1[X], p2[X]), std::min(p3[X], p4[X])), + std::min(std::min(p1[Y], p2[Y]), std::min(p3[Y], p4[Y]))), + NR::Point( + std::max(std::max(p1[X], p2[X]), std::max(p3[X], p4[X])), + std::max(std::max(p1[Y], p2[Y]), std::max(p3[Y], p4[Y])))); +} + +/** +Randomizes \a val by \a rand, with 0 < val < 1 and all values (including 0, 1) having the same +probability of being displaced. + */ +static double +randomize01 (double val, double rand) +{ + double base = MIN (val - rand, 1 - 2*rand); + if (base < 0) base = 0; + val = base + g_random_double_range (0, MIN (2 * rand, 1 - base)); + return CLAMP(val, 0, 1); // this should be unnecessary with the above provisions, but just in case... +} + + +static void +clonetiler_apply (GtkWidget *widget, void *) +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop == NULL) + return; + + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + + // check if something is selected + if (selection->isEmpty()) { + SP_DT_MSGSTACK(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select an object to clone.")); + return; + } + + // Check if more than one object is selected. + if (g_slist_length((GSList *) selection->itemList()) > 1) { + SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, _("If you want to clone several objects, group them and clone the group.")); + return; + } + + SPObject *obj = SP_OBJECT(selection->singleItem()); + Inkscape::XML::Node *obj_repr = SP_OBJECT_REPR(obj); + const char *id_href = g_strdup_printf("#%s", obj_repr->attribute("id")); + SPObject *parent = SP_OBJECT_PARENT (obj); + + clonetiler_remove (NULL, NULL, false); + + double d_x_per_x = 0.01 * prefs_get_double_attribute_limited (prefs_path, "d_x_per_x", 0, -100, 1000); + double d_y_per_x = 0.01 * prefs_get_double_attribute_limited (prefs_path, "d_y_per_x", 0, -100, 1000); + double d_per_x_exp = prefs_get_double_attribute_limited (prefs_path, "d_per_x_exp", 1, 0, 10); + double d_x_per_y = 0.01 * prefs_get_double_attribute_limited (prefs_path, "d_x_per_y", 0, -100, 1000); + double d_y_per_y = 0.01 * prefs_get_double_attribute_limited (prefs_path, "d_y_per_y", 0, -100, 1000); + double d_per_y_exp = prefs_get_double_attribute_limited (prefs_path, "d_per_y_exp", 1, 0, 10); + int alternate_x = prefs_get_int_attribute (prefs_path, "alternate_x", 0); + int alternate_y = prefs_get_int_attribute (prefs_path, "alternate_y", 0); + double rand_x = 0.01 * prefs_get_double_attribute_limited (prefs_path, "rand_x", 0, 0, 1000); + double rand_y = 0.01 * prefs_get_double_attribute_limited (prefs_path, "rand_y", 0, 0, 1000); + + double d_scalex_per_x = 0.01 * prefs_get_double_attribute_limited (prefs_path, "d_scalex_per_x", 0, -100, 1000); + double d_scaley_per_x = 0.01 * prefs_get_double_attribute_limited (prefs_path, "d_scaley_per_x", 0, -100, 1000); + double d_scalex_per_y = 0.01 * prefs_get_double_attribute_limited (prefs_path, "d_scalex_per_y", 0, -100, 1000); + double d_scaley_per_y = 0.01 * prefs_get_double_attribute_limited (prefs_path, "d_scaley_per_y", 0, -100, 1000); + int alternate_scalex = prefs_get_int_attribute (prefs_path, "alternate_scalex", 0); + int alternate_scaley = prefs_get_int_attribute (prefs_path, "alternate_scaley", 0); + double rand_scalex = 0.01 * prefs_get_double_attribute_limited (prefs_path, "rand_scalex", 0, 0, 1000); + double rand_scaley = 0.01 * prefs_get_double_attribute_limited (prefs_path, "rand_scaley", 0, 0, 1000); + + double d_rot_per_x = prefs_get_double_attribute_limited (prefs_path, "d_rot_per_x", 0, -180, 180); + double d_rot_per_y = prefs_get_double_attribute_limited (prefs_path, "d_rot_per_y", 0, -180, 180); + int alternate_rotx = prefs_get_int_attribute (prefs_path, "alternate_rotx", 0); + int alternate_roty = prefs_get_int_attribute (prefs_path, "alternate_roty", 0); + double rand_rot = 0.01 * prefs_get_double_attribute_limited (prefs_path, "rand_rot", 0, 0, 100); + + double d_opacity_per_y = 0.01 * prefs_get_double_attribute_limited (prefs_path, "d_opacity_per_y", 0, 0, 100); + double d_opacity_per_x = 0.01 * prefs_get_double_attribute_limited (prefs_path, "d_opacity_per_x", 0, 0, 100); + int alternate_opacityy = prefs_get_int_attribute (prefs_path, "alternate_opacityy", 0); + int alternate_opacityx = prefs_get_int_attribute (prefs_path, "alternate_opacityx", 0); + double rand_opacity = 0.01 * prefs_get_double_attribute_limited (prefs_path, "rand_opacity", 0, 0, 100); + + const gchar *initial_color = prefs_get_string_attribute (prefs_path, "initial_color"); + double d_hue_per_y = 0.01 * prefs_get_double_attribute_limited (prefs_path, "d_hue_per_y", 0, -100, 100); + double d_hue_per_x = 0.01 * prefs_get_double_attribute_limited (prefs_path, "d_hue_per_x", 0, -100, 100); + double rand_hue = 0.01 * prefs_get_double_attribute_limited (prefs_path, "rand_hue", 0, 0, 100); + double d_saturation_per_y = 0.01 * prefs_get_double_attribute_limited (prefs_path, "d_saturation_per_y", 0, -100, 100); + double d_saturation_per_x = 0.01 * prefs_get_double_attribute_limited (prefs_path, "d_saturation_per_x", 0, -100, 100); + double rand_saturation = 0.01 * prefs_get_double_attribute_limited (prefs_path, "rand_saturation", 0, 0, 100); + double d_lightness_per_y = 0.01 * prefs_get_double_attribute_limited (prefs_path, "d_lightness_per_y", 0, -100, 100); + double d_lightness_per_x = 0.01 * prefs_get_double_attribute_limited (prefs_path, "d_lightness_per_x", 0, -100, 100); + double rand_lightness = 0.01 * prefs_get_double_attribute_limited (prefs_path, "rand_lightness", 0, 0, 100); + int alternate_color_y = prefs_get_int_attribute (prefs_path, "alternate_color_y", 0); + int alternate_color_x = prefs_get_int_attribute (prefs_path, "alternate_color_x", 0); + + int type = prefs_get_int_attribute (prefs_path, "symmetrygroup", 0); + + int keepbbox = prefs_get_int_attribute (prefs_path, "keepbbox", 1); + + int xmax = prefs_get_int_attribute (prefs_path, "xmax", 2); + int ymax = prefs_get_int_attribute (prefs_path, "ymax", 2); + + int fillrect = prefs_get_int_attribute (prefs_path, "fillrect", 0); + double fillwidth = prefs_get_double_attribute_limited (prefs_path, "fillwidth", 50, 0, 6000); + double fillheight = prefs_get_double_attribute_limited (prefs_path, "fillheight", 50, 0, 6000); + + int dotrace = prefs_get_int_attribute (prefs_path, "dotrace", 0); + int pick = prefs_get_int_attribute (prefs_path, "pick", 0); + int pick_to_presence = prefs_get_int_attribute (prefs_path, "pick_to_presence", 0); + int pick_to_size = prefs_get_int_attribute (prefs_path, "pick_to_size", 0); + int pick_to_color = prefs_get_int_attribute (prefs_path, "pick_to_color", 0); + int pick_to_opacity = prefs_get_int_attribute (prefs_path, "pick_to_opacity", 0); + double rand_picked = 0.01 * prefs_get_double_attribute_limited (prefs_path, "rand_picked", 0, 0, 100); + int invert_picked = prefs_get_int_attribute (prefs_path, "invert_picked", 0); + double gamma_picked = prefs_get_double_attribute_limited (prefs_path, "gamma_picked", 0, -10, 10); + + if (dotrace) { + clonetiler_trace_setup (SP_DT_DOCUMENT(desktop), 1.0, SP_ITEM (obj)); + } + + NR::Point c; + double w; + double h; + + if (keepbbox && + obj_repr->attribute("inkscape:tile-w") && + obj_repr->attribute("inkscape:tile-h") && + obj_repr->attribute("inkscape:tile-cx") && + obj_repr->attribute("inkscape:tile-cy")) { + + double cx = sp_repr_get_double_attribute (obj_repr, "inkscape:tile-cx", 0); + double cy = sp_repr_get_double_attribute (obj_repr, "inkscape:tile-cy", 0); + + c = NR::Point (cx, cy); + + w = sp_repr_get_double_attribute (obj_repr, "inkscape:tile-w", 0); + h = sp_repr_get_double_attribute (obj_repr, "inkscape:tile-h", 0); + } else { + NR::Rect const r = SP_ITEM(obj)->invokeBbox(sp_item_i2doc_affine(SP_ITEM(obj))); + c = r.midpoint(); + w = r.dimensions()[NR::X]; + h = r.dimensions()[NR::Y]; + + sp_repr_set_svg_double(obj_repr, "inkscape:tile-w", w); + sp_repr_set_svg_double(obj_repr, "inkscape:tile-h", h); + sp_repr_set_svg_double(obj_repr, "inkscape:tile-cx", c[NR::X]); + sp_repr_set_svg_double(obj_repr, "inkscape:tile-cy", c[NR::Y]); + } + + NR::Point cur = NR::Point (0, 0); + NR::Rect bbox_original = NR::Rect (NR::Point (c[NR::X] - w/2, c[NR::Y] - h/2), NR::Point (c[NR::X] + w/2, c[NR::Y] + h/2)); + + for (int x = 0; + fillrect? + (fabs(cur[NR::X]) < fillwidth && x < 200) // prevent "freezing" with too large fillrect, arbitrarily limit rows + : (x < xmax); + x ++) { + for (int y = 0; + fillrect? + (fabs(cur[NR::Y]) < fillheight && y < 200) // prevent "freezing" with too large fillrect, arbitrarily limit cols + : (y < ymax); + y ++) { + + // Note: We create a clone at 0,0 too, right over the original, in case our clones are colored + + // Get transform from symmetry, shift, scale, rotation + NR::Matrix t = clonetiler_get_transform (type, x, y, c[NR::X], c[NR::Y], w, h, + d_x_per_x, d_y_per_x, d_x_per_y, d_y_per_y, alternate_x, alternate_y, rand_x, rand_y, + d_per_x_exp, d_per_y_exp, + d_rot_per_x, d_rot_per_y, alternate_rotx, alternate_roty, rand_rot, + d_scalex_per_x, d_scaley_per_x, d_scalex_per_y, d_scaley_per_y, + alternate_scalex, alternate_scaley, rand_scalex, rand_scaley); + + cur = c * t - c; + if (fillrect) { + if ((cur[NR::X] > fillwidth) || (cur[NR::Y] > fillheight)) { // off limits + continue; + } + } + + gchar color_string[32]; *color_string = 0; + + // Color tab + if (initial_color) { + guint32 rgba = sp_svg_read_color (initial_color, 0x000000ff); + float hsl[3]; + sp_color_rgb_to_hsl_floatv (hsl, SP_RGBA32_R_F(rgba), SP_RGBA32_G_F(rgba), SP_RGBA32_B_F(rgba)); + + double eff_x = (alternate_color_x? (x%2) : (x)); + double eff_y = (alternate_color_y? (y%2) : (y)); + + hsl[0] += d_hue_per_x * eff_x + d_hue_per_y * eff_y + rand_hue * g_random_double_range (-1, 1); + if (hsl[0] < 0) hsl[0] += 1; + if (hsl[0] > 1) hsl[0] -= 1; + hsl[1] += d_saturation_per_x * eff_x + d_saturation_per_y * eff_y + rand_saturation * g_random_double_range (-1, 1); + hsl[1] = CLAMP (hsl[1], 0, 1); + hsl[2] += d_lightness_per_x * eff_x + d_lightness_per_y * eff_y + rand_lightness * g_random_double_range (-1, 1); + hsl[2] = CLAMP (hsl[2], 0, 1); + + float rgb[3]; + sp_color_hsl_to_rgb_floatv (rgb, hsl[0], hsl[1], hsl[2]); + sp_svg_write_color(color_string, 32, SP_RGBA32_F_COMPOSE(rgb[0], rgb[1], rgb[2], 1.0)); + } + + // Opacity tab + double opacity = 1.0; + int eff_x = (alternate_opacityx? (x%2) : (x)); + int eff_y = (alternate_opacityy? (y%2) : (y)); + opacity = 1 - (d_opacity_per_x * eff_x + d_opacity_per_y * eff_y + rand_opacity * g_random_double_range (-1, 1)); + opacity = CLAMP (opacity, 0, 1); + + // Trace tab + if (dotrace) { + NR::Rect bbox_t = transform_rect (bbox_original, t); + + guint32 rgba = clonetiler_trace_pick (bbox_t); + float r = SP_RGBA32_R_F(rgba); + float g = SP_RGBA32_G_F(rgba); + float b = SP_RGBA32_B_F(rgba); + float a = SP_RGBA32_A_F(rgba); + + float hsl[3]; + sp_color_rgb_to_hsl_floatv (hsl, r, g, b); + + gdouble val = 0; + switch (pick) { + case PICK_COLOR: + val = 1 - hsl[2]; // inverse lightness; to match other picks where black = max + break; + case PICK_OPACITY: + val = a; + break; + case PICK_R: + val = r; + break; + case PICK_G: + val = g; + break; + case PICK_B: + val = b; + break; + case PICK_H: + val = hsl[0]; + break; + case PICK_S: + val = hsl[1]; + break; + case PICK_L: + val = 1 - hsl[2]; + break; + default: + break; + } + + if (rand_picked > 0) { + val = randomize01 (val, rand_picked); + r = randomize01 (r, rand_picked); + g = randomize01 (g, rand_picked); + b = randomize01 (b, rand_picked); + } + + if (gamma_picked != 0) { + double power; + if (gamma_picked < 0) + power = 1/(1 + fabs(gamma_picked)); + else + power = 1 + gamma_picked; + + val = pow (val, power); + r = pow (r, power); + g = pow (g, power); + b = pow (b, power); + } + + if (invert_picked) { + val = 1 - val; + r = 1 - r; + g = 1 - g; + b = 1 - b; + } + + val = CLAMP (val, 0, 1); + r = CLAMP (r, 0, 1); + g = CLAMP (g, 0, 1); + b = CLAMP (b, 0, 1); + + // recompose tweaked color + rgba = SP_RGBA32_F_COMPOSE(r, g, b, a); + + if (pick_to_presence) { + if (g_random_double_range (0, 1) > val) { + continue; // skip! + } + } + if (pick_to_size) { + t = NR::translate(-c[NR::X], -c[NR::Y]) * NR::scale (val, val) * NR::translate(c[NR::X], c[NR::Y]) * t; + } + if (pick_to_opacity) { + opacity *= val; + } + if (pick_to_color) { + sp_svg_write_color(color_string, 32, rgba); + } + } + + if (opacity < 1e-6) { // invisibly transparent, skip + continue; + } + + if (fabs(t[0]) + fabs (t[1]) + fabs(t[2]) + fabs(t[3]) < 1e-6) { // too small, skip + continue; + } + + // Create the clone + Inkscape::XML::Node *clone = sp_repr_new("svg:use"); + clone->setAttribute("x", "0"); + clone->setAttribute("y", "0"); + clone->setAttribute("inkscape:tiled-clone-of", id_href); + clone->setAttribute("xlink:href", id_href); + + gchar affinestr[80]; + if (sp_svg_transform_write(affinestr, 79, t)) { + clone->setAttribute("transform", affinestr); + } else { + clone->setAttribute("transform", NULL); + } + + if (opacity < 1.0) { + sp_repr_set_css_double(clone, "opacity", opacity); + } + + if (*color_string) { + clone->setAttribute("fill", color_string); + clone->setAttribute("stroke", color_string); + } + + // add the new clone to the top of the original's parent + SP_OBJECT_REPR(parent)->appendChild(clone); + Inkscape::GC::release(clone); + } + cur[NR::Y] = 0; + } + + if (dotrace) { + clonetiler_trace_finish (); + } + + clonetiler_change_selection (NULL, selection, dlg); + + sp_document_done(SP_DT_DOCUMENT(desktop)); +} + +static GtkWidget * +clonetiler_new_tab (GtkWidget *nb, const gchar *label) +{ + GtkWidget *l = gtk_label_new_with_mnemonic (label); + GtkWidget *vb = gtk_vbox_new (FALSE, VB_MARGIN); + gtk_container_set_border_width (GTK_CONTAINER (vb), VB_MARGIN); + gtk_notebook_append_page (GTK_NOTEBOOK (nb), vb, l); + return vb; +} + +static void +clonetiler_checkbox_toggled (GtkToggleButton *tb, gpointer *data) +{ + const gchar *attr = (const gchar *) data; + prefs_set_int_attribute (prefs_path, attr, gtk_toggle_button_get_active (tb)); +} + +static GtkWidget * +clonetiler_checkbox (GtkTooltips *tt, const char *tip, const char *attr) +{ + GtkWidget *hb = gtk_hbox_new(FALSE, VB_MARGIN); + + GtkWidget *b = gtk_check_button_new (); + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), b, tip, NULL); + + int value = prefs_get_int_attribute (prefs_path, attr, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(b), value); + + gtk_box_pack_end (GTK_BOX (hb), b, FALSE, TRUE, 0); + gtk_signal_connect ( GTK_OBJECT (b), "clicked", + GTK_SIGNAL_FUNC (clonetiler_checkbox_toggled), (gpointer) attr); + + g_object_set_data (G_OBJECT(b), "uncheckable", GINT_TO_POINTER(TRUE)); + + return hb; +} + + +static void +clonetiler_value_changed (GtkAdjustment *adj, gpointer data) +{ + const gchar *pref = (const gchar *) data; + prefs_set_double_attribute (prefs_path, pref, adj->value); +} + +static GtkWidget * +clonetiler_spinbox (GtkTooltips *tt, const char *tip, const char *attr, double lower, double upper, const gchar *suffix, bool exponent = false) +{ + GtkWidget *hb = gtk_hbox_new(FALSE, 0); + + { + GtkObject *a; + if (exponent) + a = gtk_adjustment_new(1.0, lower, upper, 0.01, 0.05, 0.1); + else + a = gtk_adjustment_new(0.0, lower, upper, 0.1, 0.5, 2); + + GtkWidget *sb; + if (exponent) + sb = gtk_spin_button_new (GTK_ADJUSTMENT (a), 0.01, 2); + else + sb = gtk_spin_button_new (GTK_ADJUSTMENT (a), 0.1, 1); + + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), sb, tip, NULL); + gtk_entry_set_width_chars (GTK_ENTRY (sb), 4); + gtk_box_pack_start (GTK_BOX (hb), sb, FALSE, FALSE, SB_MARGIN); + + double value = prefs_get_double_attribute_limited (prefs_path, attr, exponent? 1 : 0, lower, upper); + gtk_adjustment_set_value (GTK_ADJUSTMENT (a), value); + gtk_signal_connect(GTK_OBJECT(a), "value_changed", + GTK_SIGNAL_FUNC(clonetiler_value_changed), (gpointer) attr); + + if (exponent) + g_object_set_data (G_OBJECT(sb), "oneable", GINT_TO_POINTER(TRUE)); + else + g_object_set_data (G_OBJECT(sb), "zeroable", GINT_TO_POINTER(TRUE)); + } + + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), suffix); + gtk_misc_set_alignment (GTK_MISC (l), 1.0, 0); + gtk_box_pack_start (GTK_BOX (hb), l, FALSE, FALSE, 0); + } + + return hb; +} + +static void +clonetiler_symgroup_changed (GtkMenuItem *item, gpointer data) +{ + gint group_new = GPOINTER_TO_INT (data); + prefs_set_int_attribute ( prefs_path, "symmetrygroup", group_new ); +} + +static void +clonetiler_xy_changed (GtkAdjustment *adj, gpointer data) +{ + const gchar *pref = (const gchar *) data; + prefs_set_int_attribute (prefs_path, pref, (int) floor(adj->value + 0.5)); +} + +static void +clonetiler_keep_bbox_toggled (GtkToggleButton *tb, gpointer data) +{ + prefs_set_int_attribute (prefs_path, "keepbbox", gtk_toggle_button_get_active (tb)); +} + +static void +clonetiler_pick_to (GtkToggleButton *tb, gpointer data) +{ + const gchar *pref = (const gchar *) data; + prefs_set_int_attribute (prefs_path, pref, gtk_toggle_button_get_active (tb)); +} + + +static void +clonetiler_reset_recursive (GtkWidget *w) +{ + if (w && GTK_IS_OBJECT(w)) { + { + int r = GPOINTER_TO_INT (gtk_object_get_data (GTK_OBJECT(w), "zeroable")); + if (r && GTK_IS_SPIN_BUTTON(w)) { // spinbutton + GtkAdjustment *a = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON(w)); + gtk_adjustment_set_value (a, 0); + } + } + { + int r = GPOINTER_TO_INT (gtk_object_get_data (GTK_OBJECT(w), "oneable")); + if (r && GTK_IS_SPIN_BUTTON(w)) { // spinbutton + GtkAdjustment *a = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON(w)); + gtk_adjustment_set_value (a, 1); + } + } + { + int r = GPOINTER_TO_INT (gtk_object_get_data (GTK_OBJECT(w), "uncheckable")); + if (r && GTK_IS_TOGGLE_BUTTON(w)) { // checkbox + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(w), FALSE); + } + } + } + + if (GTK_IS_CONTAINER(w)) { + GList *ch = gtk_container_get_children (GTK_CONTAINER(w)); + for (GList *i = ch; i != NULL; i = i->next) { + clonetiler_reset_recursive (GTK_WIDGET(i->data)); + } + g_list_free (ch); + } +} + +static void +clonetiler_reset (GtkWidget *widget, void *) +{ + clonetiler_reset_recursive (dlg); +} + +static void +clonetiler_table_attach (GtkWidget *table, GtkWidget *widget, float align, int row, int col) +{ + GtkWidget *a = gtk_alignment_new (align, 0, 0, 0); + gtk_container_add(GTK_CONTAINER(a), widget); + gtk_table_attach ( GTK_TABLE (table), a, col, col + 1, row, row + 1, (GtkAttachOptions)4, (GtkAttachOptions)0, 0, 0 ); +} + +static GtkWidget * +clonetiler_table_x_y_rand (int values) +{ + GtkWidget *table = gtk_table_new (values + 2, 5, FALSE); + gtk_container_set_border_width (GTK_CONTAINER (table), VB_MARGIN); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 8); + + { + GtkWidget *hb = gtk_hbox_new (FALSE, 0); + + GtkWidget *i = sp_icon_new (GTK_ICON_SIZE_MENU, "clonetiler_per_row"); + gtk_box_pack_start (GTK_BOX (hb), i, FALSE, FALSE, 2); + + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("Per row:")); + gtk_box_pack_start (GTK_BOX (hb), l, FALSE, FALSE, 2); + + clonetiler_table_attach (table, hb, 0, 1, 2); + } + + { + GtkWidget *hb = gtk_hbox_new (FALSE, 0); + + GtkWidget *i = sp_icon_new (GTK_ICON_SIZE_MENU, "clonetiler_per_column"); + gtk_box_pack_start (GTK_BOX (hb), i, FALSE, FALSE, 2); + + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("Per column:")); + gtk_box_pack_start (GTK_BOX (hb), l, FALSE, FALSE, 2); + + clonetiler_table_attach (table, hb, 0, 1, 3); + } + + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("Randomize:")); + clonetiler_table_attach (table, l, 0, 1, 4); + } + + return table; +} + +static void +clonetiler_pick_switched (GtkToggleButton *tb, gpointer data) +{ + guint v = GPOINTER_TO_INT (data); + prefs_set_int_attribute (prefs_path, "pick", v); +} + + +static void +clonetiler_switch_to_create (GtkToggleButton *tb, GtkWidget *dlg) +{ + GtkWidget *rowscols = (GtkWidget *) g_object_get_data (G_OBJECT(dlg), "rowscols"); + GtkWidget *widthheight = (GtkWidget *) g_object_get_data (G_OBJECT(dlg), "widthheight"); + + if (rowscols) { + gtk_widget_set_sensitive (rowscols, TRUE); + } + if (widthheight) { + gtk_widget_set_sensitive (widthheight, FALSE); + } + + prefs_set_int_attribute (prefs_path, "fillrect", 0); +} + + +static void +clonetiler_switch_to_fill (GtkToggleButton *tb, GtkWidget *dlg) +{ + GtkWidget *rowscols = (GtkWidget *) g_object_get_data (G_OBJECT(dlg), "rowscols"); + GtkWidget *widthheight = (GtkWidget *) g_object_get_data (G_OBJECT(dlg), "widthheight"); + + if (rowscols) { + gtk_widget_set_sensitive (rowscols, FALSE); + } + if (widthheight) { + gtk_widget_set_sensitive (widthheight, TRUE); + } + + prefs_set_int_attribute (prefs_path, "fillrect", 1); +} + + + + +static void +clonetiler_fill_width_changed (GtkAdjustment *adj, GtkWidget *u) +{ + gdouble const raw_dist = adj->value; + SPUnit const &unit = *sp_unit_selector_get_unit(SP_UNIT_SELECTOR(u)); + gdouble const pixels = sp_units_get_pixels (raw_dist, unit); + + prefs_set_double_attribute (prefs_path, "fillwidth", pixels); +} + +static void +clonetiler_fill_height_changed (GtkAdjustment *adj, GtkWidget *u) +{ + gdouble const raw_dist = adj->value; + SPUnit const &unit = *sp_unit_selector_get_unit(SP_UNIT_SELECTOR(u)); + gdouble const pixels = sp_units_get_pixels (raw_dist, unit); + + prefs_set_double_attribute (prefs_path, "fillheight", pixels); +} + + +static void +clonetiler_do_pick_toggled (GtkToggleButton *tb, gpointer data) +{ + GtkWidget *vvb = (GtkWidget *) g_object_get_data (G_OBJECT(dlg), "dotrace"); + + prefs_set_int_attribute (prefs_path, "dotrace", gtk_toggle_button_get_active (tb)); + + if (vvb) + gtk_widget_set_sensitive (vvb, gtk_toggle_button_get_active (tb)); +} + + + + +void +clonetiler_dialog (void) +{ + if (!dlg) + { + gchar title[500]; + sp_ui_dialog_title_string (Inkscape::Verb::get(SP_VERB_DIALOG_CLONETILER), title); + + dlg = sp_window_new (title, TRUE); + if (x == -1000 || y == -1000) { + x = prefs_get_int_attribute (prefs_path, "x", 0); + y = prefs_get_int_attribute (prefs_path, "y", 0); + } + + if (w ==0 || h == 0) { + w = prefs_get_int_attribute (prefs_path, "w", 0); + h = prefs_get_int_attribute (prefs_path, "h", 0); + } + + if (x != 0 || y != 0) { + gtk_window_move ((GtkWindow *) dlg, x, y); + + } else { + gtk_window_set_position(GTK_WINDOW(dlg), GTK_WIN_POS_CENTER); + } + + if (w && h) { + gtk_window_resize ((GtkWindow *) dlg, w, h); + } + + sp_transientize (dlg); + wd.win = dlg; + wd.stop = 0; + + + gtk_signal_connect ( GTK_OBJECT (dlg), "event", GTK_SIGNAL_FUNC (sp_dialog_event_handler), dlg); + + gtk_signal_connect ( GTK_OBJECT (dlg), "destroy", G_CALLBACK (clonetiler_dialog_destroy), dlg); + gtk_signal_connect ( GTK_OBJECT (dlg), "delete_event", G_CALLBACK (clonetiler_dialog_delete), dlg); + + if (Inkscape::NSApplication::Application::getNewGui()) + { + _shutdown_connection = Inkscape::NSApplication::Editor::connectShutdown (&on_delete); + _dialogs_hidden_connection = Inkscape::NSApplication::Editor::connectDialogsHidden (sigc::bind (&on_dialog_hide, dlg)); + _dialogs_unhidden_connection = Inkscape::NSApplication::Editor::connectDialogsUnhidden (sigc::bind (&on_dialog_unhide, dlg)); + _desktop_activated_connection = Inkscape::NSApplication::Editor::connectDesktopActivated (sigc::bind (&on_transientize, &wd)); + } else { + g_signal_connect ( G_OBJECT (INKSCAPE), "shut_down", G_CALLBACK (clonetiler_dialog_delete), dlg); + g_signal_connect ( G_OBJECT (INKSCAPE), "dialogs_hide", G_CALLBACK (sp_dialog_hide), dlg); + g_signal_connect ( G_OBJECT (INKSCAPE), "dialogs_unhide", G_CALLBACK (sp_dialog_unhide), dlg); + g_signal_connect ( G_OBJECT (INKSCAPE), "activate_desktop", G_CALLBACK (sp_transientize_callback), &wd); + } + + GtkTooltips *tt = gtk_tooltips_new(); + + GtkWidget *mainbox = gtk_vbox_new(FALSE, 4); + gtk_container_set_border_width (GTK_CONTAINER (mainbox), 6); + gtk_container_add (GTK_CONTAINER (dlg), mainbox); + + GtkWidget *nb = gtk_notebook_new (); + gtk_box_pack_start (GTK_BOX (mainbox), nb, FALSE, FALSE, 0); + + +// Symmetry + { + GtkWidget *vb = clonetiler_new_tab (nb, _("_Symmetry")); + + GtkWidget *om = gtk_option_menu_new (); + /* TRANSLATORS: For the following 17 symmetry groups, see + * http://www.bib.ulb.ac.be/coursmath/doc/17.htm (visual examples); + * http://www.clarku.edu/~djoyce/wallpaper/seventeen.html (English vocabulary); or + * http://membres.lycos.fr/villemingerard/Geometri/Sym1D.htm (French vocabulary). + */ + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), om, _("Select one of the 17 symmetry groups for the tiling"), NULL); + gtk_box_pack_start (GTK_BOX (vb), om, FALSE, FALSE, SB_MARGIN); + + GtkWidget *m = gtk_menu_new (); + int current = prefs_get_int_attribute (prefs_path, "symmetrygroup", 0); + + struct SymGroups { + int group; + gchar const *label; + } const sym_groups[] = { + // TRANSLATORS: "translation" means "shift" / "displacement" here. + {TILE_P1, _("P1: simple translation")}, + {TILE_P2, _("P2: 180° rotation")}, + {TILE_PM, _("PM: reflection")}, + // TRANSLATORS: "glide reflection" is a reflection and a translation combined. + // For more info, see http://mathforum.org/sum95/suzanne/symsusan.html + {TILE_PG, _("PG: glide reflection")}, + {TILE_CM, _("CM: reflection + glide reflection")}, + {TILE_PMM, _("PMM: reflection + reflection")}, + {TILE_PMG, _("PMG: reflection + 180° rotation")}, + {TILE_PGG, _("PGG: glide reflection + 180° rotation")}, + {TILE_CMM, _("CMM: reflection + reflection + 180° rotation")}, + {TILE_P4, _("P4: 90° rotation")}, + {TILE_P4M, _("P4M: 90° rotation + 45° reflection")}, + {TILE_P4G, _("P4G: 90° rotation + 90° reflection")}, + {TILE_P3, _("P3: 120° rotation")}, + {TILE_P31M, _("P31M: reflection + 120° rotation, dense")}, + {TILE_P3M1, _("P3M1: reflection + 120° rotation, sparse")}, + {TILE_P6, _("P6: 60° rotation")}, + {TILE_P6M, _("P6M: reflection + 60° rotation")}, + }; + + for (unsigned j = 0; j < G_N_ELEMENTS(sym_groups); ++j) { + SymGroups const &sg = sym_groups[j]; + + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), sg.label); + gtk_misc_set_alignment (GTK_MISC(l), 0, 0.5); + + GtkWidget *item = gtk_menu_item_new (); + gtk_container_add (GTK_CONTAINER (item), l); + + gtk_signal_connect ( GTK_OBJECT (item), "activate", + GTK_SIGNAL_FUNC (clonetiler_symgroup_changed), + GINT_TO_POINTER (sg.group) ); + + gtk_menu_append (GTK_MENU (m), item); + } + + gtk_option_menu_set_menu (GTK_OPTION_MENU (om), m); + gtk_option_menu_set_history ( GTK_OPTION_MENU (om), current); + } + + table_row_labels = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); + +// Shift + { + GtkWidget *vb = clonetiler_new_tab (nb, _("S_hift")); + + GtkWidget *table = clonetiler_table_x_y_rand (3); + gtk_box_pack_start (GTK_BOX (vb), table, FALSE, FALSE, 0); + + // X + { + GtkWidget *l = gtk_label_new (""); + // TRANSLATORS: "shift" means: the tiles will be shifted (offset) horizontally by this amount + // xgettext:no-c-format + gtk_label_set_markup (GTK_LABEL(l), _("Shift X:")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 2, 1); + } + + { + GtkWidget *l = clonetiler_spinbox (tt, + // xgettext:no-c-format + _("Horizontal shift per row (in % of tile width)"), "d_x_per_y", + -100, 1000, "%"); + clonetiler_table_attach (table, l, 0, 2, 2); + } + + { + GtkWidget *l = clonetiler_spinbox (tt, + // xgettext:no-c-format + _("Horizontal shift per column (in % of tile width)"), "d_x_per_x", + -100, 1000, "%"); + clonetiler_table_attach (table, l, 0, 2, 3); + } + + { + GtkWidget *l = clonetiler_spinbox (tt, + _("Randomize the horizontal shift by this percentage"), "rand_x", + 0, 1000, "%"); + clonetiler_table_attach (table, l, 0, 2, 4); + } + + // Y + { + GtkWidget *l = gtk_label_new (""); + // TRANSLATORS: "shift" means: the tiles will be shifted (offset) vertically by this amount + // xgettext:no-c-format + gtk_label_set_markup (GTK_LABEL(l), _("Shift Y:")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 3, 1); + } + + { + GtkWidget *l = clonetiler_spinbox (tt, + // xgettext:no-c-format + _("Vertical shift per row (in % of tile height)"), "d_y_per_y", + -100, 1000, "%"); + clonetiler_table_attach (table, l, 0, 3, 2); + } + + { + GtkWidget *l = clonetiler_spinbox (tt, + // xgettext:no-c-format + _("Vertical shift per column (in % of tile height)"), "d_y_per_x", + -100, 1000, "%"); + clonetiler_table_attach (table, l, 0, 3, 3); + } + + { + GtkWidget *l = clonetiler_spinbox (tt, + _("Randomize the vertical shift by this percentage"), "rand_y", + 0, 1000, "%"); + clonetiler_table_attach (table, l, 0, 3, 4); + } + + // Exponent + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("Exponent:")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 4, 1); + } + + { + GtkWidget *l = clonetiler_spinbox (tt, + _("Whether rows are spaced evenly (1), converge (<1) or diverge (>1)"), "d_per_y_exp", + 0, 10, "", true); + clonetiler_table_attach (table, l, 0, 4, 2); + } + + { + GtkWidget *l = clonetiler_spinbox (tt, + _("Whether columns are spaced evenly (1), converge (<1) or diverge (>1)"), "d_per_x_exp", + 0, 10, "", true); + clonetiler_table_attach (table, l, 0, 4, 3); + } + + { // alternates + GtkWidget *l = gtk_label_new (""); + // TRANSLATORS: "Alternate" is a verb here + gtk_label_set_markup (GTK_LABEL(l), _("Alternate:")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 5, 1); + } + + { + GtkWidget *l = clonetiler_checkbox (tt, _("Alternate the sign of shifts for each row"), "alternate_y"); + clonetiler_table_attach (table, l, 0, 5, 2); + } + + { + GtkWidget *l = clonetiler_checkbox (tt, _("Alternate the sign of shifts for each column"), "alternate_x"); + clonetiler_table_attach (table, l, 0, 5, 3); + } + + } + + +// Scale + { + GtkWidget *vb = clonetiler_new_tab (nb, _("Sc_ale")); + + GtkWidget *table = clonetiler_table_x_y_rand (2); + gtk_box_pack_start (GTK_BOX (vb), table, FALSE, FALSE, 0); + + // X + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("Scale X:")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 2, 1); + } + + { + GtkWidget *l = clonetiler_spinbox (tt, + // xgettext:no-c-format + _("Horizontal scale per row (in % of tile width)"), "d_scalex_per_y", + -100, 1000, "%"); + clonetiler_table_attach (table, l, 0, 2, 2); + } + + { + GtkWidget *l = clonetiler_spinbox (tt, + // xgettext:no-c-format + _("Horizontal scale per column (in % of tile width)"), "d_scalex_per_x", + -100, 1000, "%"); + clonetiler_table_attach (table, l, 0, 2, 3); + } + + { + GtkWidget *l = clonetiler_spinbox (tt, + _("Randomize the horizontal scale by this percentage"), "rand_scalex", + 0, 1000, "%"); + clonetiler_table_attach (table, l, 0, 2, 4); + } + + // Y + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("Scale Y:")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 3, 1); + } + + { + GtkWidget *l = clonetiler_spinbox (tt, + // xgettext:no-c-format + _("Vertical scale per row (in % of tile height)"), "d_scaley_per_y", + -100, 1000, "%"); + clonetiler_table_attach (table, l, 0, 3, 2); + } + + { + GtkWidget *l = clonetiler_spinbox (tt, + // xgettext:no-c-format + _("Vertical scale per column (in % of tile height)"), "d_scaley_per_x", + -100, 1000, "%"); + clonetiler_table_attach (table, l, 0, 3, 3); + } + + { + GtkWidget *l = clonetiler_spinbox (tt, + _("Randomize the vertical scale by this percentage"), "rand_scaley", + 0, 1000, "%"); + clonetiler_table_attach (table, l, 0, 3, 4); + } + + { // alternates + GtkWidget *l = gtk_label_new (""); + // TRANSLATORS: "Alternate" is a verb here + gtk_label_set_markup (GTK_LABEL(l), _("Alternate:")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 4, 1); + } + + { + GtkWidget *l = clonetiler_checkbox (tt, _("Alternate the sign of scales for each row"), "alternate_scaley"); + clonetiler_table_attach (table, l, 0, 4, 2); + } + + { + GtkWidget *l = clonetiler_checkbox (tt, _("Alternate the sign of scales for each column"), "alternate_scalex"); + clonetiler_table_attach (table, l, 0, 4, 3); + } + + } + + +// Rotation + { + GtkWidget *vb = clonetiler_new_tab (nb, _("_Rotation")); + + GtkWidget *table = clonetiler_table_x_y_rand (1); + gtk_box_pack_start (GTK_BOX (vb), table, FALSE, FALSE, 0); + + // Angle + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("Angle:")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 2, 1); + } + + { + GtkWidget *l = clonetiler_spinbox (tt, + // xgettext:no-c-format + _("Rotate tiles by this angle for each row"), "d_rot_per_y", + -180, 180, "°"); + clonetiler_table_attach (table, l, 0, 2, 2); + } + + { + GtkWidget *l = clonetiler_spinbox (tt, + // xgettext:no-c-format + _("Rotate tiles by this angle for each column"), "d_rot_per_x", + -180, 180, "°"); + clonetiler_table_attach (table, l, 0, 2, 3); + } + + { + GtkWidget *l = clonetiler_spinbox (tt, + _("Randomize the rotation angle by this percentage"), "rand_rot", + 0, 100, "%"); + clonetiler_table_attach (table, l, 0, 2, 4); + } + + { // alternates + GtkWidget *l = gtk_label_new (""); + // TRANSLATORS: "Alternate" is a verb here + gtk_label_set_markup (GTK_LABEL(l), _("Alternate:")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 3, 1); + } + + { + GtkWidget *l = clonetiler_checkbox (tt, _("Alternate the rotation direction for each row"), "alternate_roty"); + clonetiler_table_attach (table, l, 0, 3, 2); + } + + { + GtkWidget *l = clonetiler_checkbox (tt, _("Alternate the rotation direction for each column"), "alternate_rotx"); + clonetiler_table_attach (table, l, 0, 3, 3); + } + } + + +// Opacity + { + GtkWidget *vb = clonetiler_new_tab (nb, _("_Opacity")); + + GtkWidget *table = clonetiler_table_x_y_rand (1); + gtk_box_pack_start (GTK_BOX (vb), table, FALSE, FALSE, 0); + + // Dissolve + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("Fade out:")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 2, 1); + } + + { + GtkWidget *l = clonetiler_spinbox (tt, + _("Decrease tile opacity by this percentage for each row"), "d_opacity_per_y", + 0, 100, "%"); + clonetiler_table_attach (table, l, 0, 2, 2); + } + + { + GtkWidget *l = clonetiler_spinbox (tt, + _("Decrease tile opacity by this percentage for each column"), "d_opacity_per_x", + 0, 100, "%"); + clonetiler_table_attach (table, l, 0, 2, 3); + } + + { + GtkWidget *l = clonetiler_spinbox (tt, + _("Randomize the tile opacity by this percentage"), "rand_opacity", + 0, 100, "%"); + clonetiler_table_attach (table, l, 0, 2, 4); + } + + { // alternates + GtkWidget *l = gtk_label_new (""); + // TRANSLATORS: "Alternate" is a verb here + gtk_label_set_markup (GTK_LABEL(l), _("Alternate:")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 3, 1); + } + + { + GtkWidget *l = clonetiler_checkbox (tt, _("Alternate the sign of opacity change for each row"), "alternate_opacityy"); + clonetiler_table_attach (table, l, 0, 3, 2); + } + + { + GtkWidget *l = clonetiler_checkbox (tt, _("Alternate the sign of opacity change for each column"), "alternate_opacityx"); + clonetiler_table_attach (table, l, 0, 3, 3); + } + } + + +// Color + { + GtkWidget *vb = clonetiler_new_tab (nb, _("Co_lor")); + + { + GtkWidget *hb = gtk_hbox_new (FALSE, 0); + + GtkWidget *l = gtk_label_new (_("Initial color: ")); + gtk_box_pack_start (GTK_BOX (hb), l, FALSE, FALSE, 0); + + guint32 rgba = 0x000000ff | sp_svg_read_color (prefs_get_string_attribute(prefs_path, "initial_color"), 0x000000ff); + color_picker = new Inkscape::UI::Widget::ColorPicker (*new Glib::ustring(_("Initial color of tiled clones")), *new Glib::ustring(_("Initial color for clones (works only if the original has unset fill or stroke)")), rgba, false); + _color_changed_connection = color_picker->connectChanged (sigc::ptr_fun(on_picker_color_changed)); + + gtk_box_pack_start (GTK_BOX (hb), reinterpret_cast(color_picker->gobj()), FALSE, FALSE, 0); + + gtk_box_pack_start (GTK_BOX (vb), hb, FALSE, FALSE, 0); + } + + + GtkWidget *table = clonetiler_table_x_y_rand (3); + gtk_box_pack_start (GTK_BOX (vb), table, FALSE, FALSE, 0); + + // Hue + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("H:")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 2, 1); + } + + { + GtkWidget *l = clonetiler_spinbox (tt, + _("Change the tile hue by this percentage for each row"), "d_hue_per_y", + -100, 100, "%"); + clonetiler_table_attach (table, l, 0, 2, 2); + } + + { + GtkWidget *l = clonetiler_spinbox (tt, + _("Change the tile hue by this percentage for each column"), "d_hue_per_x", + -100, 100, "%"); + clonetiler_table_attach (table, l, 0, 2, 3); + } + + { + GtkWidget *l = clonetiler_spinbox (tt, + _("Randomize the tile hue by this percentage"), "rand_hue", + 0, 100, "%"); + clonetiler_table_attach (table, l, 0, 2, 4); + } + + + // Saturation + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("S:")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 3, 1); + } + + { + GtkWidget *l = clonetiler_spinbox (tt, + _("Change the color saturation by this percentage for each row"), "d_saturation_per_y", + -100, 100, "%"); + clonetiler_table_attach (table, l, 0, 3, 2); + } + + { + GtkWidget *l = clonetiler_spinbox (tt, + _("Change the color saturation by this percentage for each column"), "d_saturation_per_x", + -100, 100, "%"); + clonetiler_table_attach (table, l, 0, 3, 3); + } + + { + GtkWidget *l = clonetiler_spinbox (tt, + _("Randomize the color saturation by this percentage"), "rand_saturation", + 0, 100, "%"); + clonetiler_table_attach (table, l, 0, 3, 4); + } + + // Lightness + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("L:")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 4, 1); + } + + { + GtkWidget *l = clonetiler_spinbox (tt, + _("Change the color lightness by this percentage for each row"), "d_lightness_per_y", + -100, 100, "%"); + clonetiler_table_attach (table, l, 0, 4, 2); + } + + { + GtkWidget *l = clonetiler_spinbox (tt, + _("Change the color lightness by this percentage for each column"), "d_lightness_per_x", + -100, 100, "%"); + clonetiler_table_attach (table, l, 0, 4, 3); + } + + { + GtkWidget *l = clonetiler_spinbox (tt, + _("Randomize the color lightness by this percentage"), "rand_lightness", + 0, 100, "%"); + clonetiler_table_attach (table, l, 0, 4, 4); + } + + + { // alternates + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("Alternate:")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 5, 1); + } + + { + GtkWidget *l = clonetiler_checkbox (tt, _("Alternate the sign of color changes for each row"), "alternate_color_y"); + clonetiler_table_attach (table, l, 0, 5, 2); + } + + { + GtkWidget *l = clonetiler_checkbox (tt, _("Alternate the sign of color changes for each column"), "alternate_color_x"); + clonetiler_table_attach (table, l, 0, 5, 3); + } + + } + +// Trace + { + GtkWidget *vb = clonetiler_new_tab (nb, _("_Trace")); + + + { + GtkWidget *hb = gtk_hbox_new(FALSE, VB_MARGIN); + gtk_box_pack_start (GTK_BOX (vb), hb, FALSE, FALSE, 0); + + GtkWidget *b = gtk_check_button_new_with_label (_("Trace the drawing under the tiles")); + g_object_set_data (G_OBJECT(b), "uncheckable", GINT_TO_POINTER(TRUE)); + gint old = prefs_get_int_attribute (prefs_path, "dotrace", 0); + gtk_toggle_button_set_active ((GtkToggleButton *) b, old != 0); + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), b, _("For each clone, pick a value from the drawing in that clone's location and apply it to the clone"), NULL); + gtk_box_pack_start (GTK_BOX (hb), b, FALSE, FALSE, 0); + + gtk_signal_connect(GTK_OBJECT(b), "toggled", + GTK_SIGNAL_FUNC(clonetiler_do_pick_toggled), dlg); + } + + { + GtkWidget *vvb = gtk_vbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (vb), vvb, FALSE, FALSE, 0); + g_object_set_data (G_OBJECT(dlg), "dotrace", (gpointer) vvb); + + + { + GtkWidget *frame = gtk_frame_new (_("1. Pick from the drawing:")); + gtk_box_pack_start (GTK_BOX (vvb), frame, FALSE, FALSE, 0); + + GtkWidget *table = gtk_table_new (3, 3, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 4); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_container_add(GTK_CONTAINER(frame), table); + + + GtkWidget* radio; + { + radio = gtk_radio_button_new_with_label (NULL, _("Color")); + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), radio, _("Pick the visible color and opacity"), NULL); + clonetiler_table_attach (table, radio, 0.0, 1, 1); + gtk_signal_connect (GTK_OBJECT (radio), "toggled", + GTK_SIGNAL_FUNC (clonetiler_pick_switched), GINT_TO_POINTER(PICK_COLOR)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), prefs_get_int_attribute(prefs_path, "pick", 0) == PICK_COLOR); + } + { + radio = gtk_radio_button_new_with_label (gtk_radio_button_group (GTK_RADIO_BUTTON (radio)), _("Opacity")); + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), radio, _("Pick the total accumulated opacity"), NULL); + clonetiler_table_attach (table, radio, 0.0, 2, 1); + gtk_signal_connect (GTK_OBJECT (radio), "toggled", + GTK_SIGNAL_FUNC (clonetiler_pick_switched), GINT_TO_POINTER(PICK_OPACITY)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), prefs_get_int_attribute(prefs_path, "pick", 0) == PICK_OPACITY); + } + { + radio = gtk_radio_button_new_with_label (gtk_radio_button_group (GTK_RADIO_BUTTON (radio)), _("R")); + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), radio, _("Pick the Red component of the color"), NULL); + clonetiler_table_attach (table, radio, 0.0, 1, 2); + gtk_signal_connect (GTK_OBJECT (radio), "toggled", + GTK_SIGNAL_FUNC (clonetiler_pick_switched), GINT_TO_POINTER(PICK_R)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), prefs_get_int_attribute(prefs_path, "pick", 0) == PICK_R); + } + { + radio = gtk_radio_button_new_with_label (gtk_radio_button_group (GTK_RADIO_BUTTON (radio)), _("G")); + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), radio, _("Pick the Green component of the color"), NULL); + clonetiler_table_attach (table, radio, 0.0, 2, 2); + gtk_signal_connect (GTK_OBJECT (radio), "toggled", + GTK_SIGNAL_FUNC (clonetiler_pick_switched), GINT_TO_POINTER(PICK_G)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), prefs_get_int_attribute(prefs_path, "pick", 0) == PICK_G); + } + { + radio = gtk_radio_button_new_with_label (gtk_radio_button_group (GTK_RADIO_BUTTON (radio)), _("B")); + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), radio, _("Pick the Blue component of the color"), NULL); + clonetiler_table_attach (table, radio, 0.0, 3, 2); + gtk_signal_connect (GTK_OBJECT (radio), "toggled", + GTK_SIGNAL_FUNC (clonetiler_pick_switched), GINT_TO_POINTER(PICK_B)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), prefs_get_int_attribute(prefs_path, "pick", 0) == PICK_B); + } + { + //TRANSLATORS: only translate "string" in "context|string". + // For more details, see http://developer.gnome.org/doc/API/2.0/glib/glib-I18N.html#Q-:CAPS + radio = gtk_radio_button_new_with_label (gtk_radio_button_group (GTK_RADIO_BUTTON (radio)), Q_("clonetiler|H")); + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), radio, _("Pick the hue of the color"), NULL); + clonetiler_table_attach (table, radio, 0.0, 1, 3); + gtk_signal_connect (GTK_OBJECT (radio), "toggled", + GTK_SIGNAL_FUNC (clonetiler_pick_switched), GINT_TO_POINTER(PICK_H)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), prefs_get_int_attribute(prefs_path, "pick", 0) == PICK_H); + } + { + //TRANSLATORS: only translate "string" in "context|string". + // For more details, see http://developer.gnome.org/doc/API/2.0/glib/glib-I18N.html#Q-:CAPS + radio = gtk_radio_button_new_with_label (gtk_radio_button_group (GTK_RADIO_BUTTON (radio)), Q_("clonetiler|S")); + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), radio, _("Pick the saturation of the color"), NULL); + clonetiler_table_attach (table, radio, 0.0, 2, 3); + gtk_signal_connect (GTK_OBJECT (radio), "toggled", + GTK_SIGNAL_FUNC (clonetiler_pick_switched), GINT_TO_POINTER(PICK_S)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), prefs_get_int_attribute(prefs_path, "pick", 0) == PICK_S); + } + { + //TRANSLATORS: only translate "string" in "context|string". + // For more details, see http://developer.gnome.org/doc/API/2.0/glib/glib-I18N.html#Q-:CAPS + radio = gtk_radio_button_new_with_label (gtk_radio_button_group (GTK_RADIO_BUTTON (radio)), Q_("clonetiler|L")); + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), radio, _("Pick the lightness of the color"), NULL); + clonetiler_table_attach (table, radio, 0.0, 3, 3); + gtk_signal_connect (GTK_OBJECT (radio), "toggled", + GTK_SIGNAL_FUNC (clonetiler_pick_switched), GINT_TO_POINTER(PICK_L)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), prefs_get_int_attribute(prefs_path, "pick", 0) == PICK_L); + } + + } + + { + GtkWidget *frame = gtk_frame_new (_("2. Tweak the picked value:")); + gtk_box_pack_start (GTK_BOX (vvb), frame, FALSE, FALSE, VB_MARGIN); + + GtkWidget *table = gtk_table_new (4, 2, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 4); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_container_add(GTK_CONTAINER(frame), table); + + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("Gamma-correct:")); + clonetiler_table_attach (table, l, 1.0, 1, 1); + } + { + GtkWidget *l = clonetiler_spinbox (tt, + _("Shift the mid-range of the picked value upwards (>0) or downwards (<0)"), "gamma_picked", + -10, 10, ""); + clonetiler_table_attach (table, l, 0.0, 1, 2); + } + + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("Randomize:")); + clonetiler_table_attach (table, l, 1.0, 1, 3); + } + { + GtkWidget *l = clonetiler_spinbox (tt, + _("Randomize the picked value by this percentage"), "rand_picked", + 0, 100, "%"); + clonetiler_table_attach (table, l, 0.0, 1, 4); + } + + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("Invert:")); + clonetiler_table_attach (table, l, 1.0, 2, 1); + } + { + GtkWidget *l = clonetiler_checkbox (tt, _("Invert the picked value"), "invert_picked"); + clonetiler_table_attach (table, l, 0.0, 2, 2); + } + } + + { + GtkWidget *frame = gtk_frame_new (_("3. Apply the value to the clones':")); + gtk_box_pack_start (GTK_BOX (vvb), frame, FALSE, FALSE, 0); + + + GtkWidget *table = gtk_table_new (2, 2, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 4); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_container_add(GTK_CONTAINER(frame), table); + + { + GtkWidget *b = gtk_check_button_new_with_label (_("Presence")); + gint old = prefs_get_int_attribute (prefs_path, "pick_to_presence", 1); + gtk_toggle_button_set_active ((GtkToggleButton *) b, old != 0); + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), b, _("Each clone is created with the probability determined by the picked value in that point"), NULL); + clonetiler_table_attach (table, b, 0.0, 1, 1); + gtk_signal_connect(GTK_OBJECT(b), "toggled", + GTK_SIGNAL_FUNC(clonetiler_pick_to), (gpointer) "pick_to_presence"); + } + + { + GtkWidget *b = gtk_check_button_new_with_label (_("Size")); + gint old = prefs_get_int_attribute (prefs_path, "pick_to_size", 0); + gtk_toggle_button_set_active ((GtkToggleButton *) b, old != 0); + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), b, _("Each clone's size is determined by the picked value in that point"), NULL); + clonetiler_table_attach (table, b, 0.0, 2, 1); + gtk_signal_connect(GTK_OBJECT(b), "toggled", + GTK_SIGNAL_FUNC(clonetiler_pick_to), (gpointer) "pick_to_size"); + } + + { + GtkWidget *b = gtk_check_button_new_with_label (_("Color")); + gint old = prefs_get_int_attribute (prefs_path, "pick_to_color", 0); + gtk_toggle_button_set_active ((GtkToggleButton *) b, old != 0); + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), b, _("Each clone is painted by the picked color (the original must have unset fill or stroke)"), NULL); + clonetiler_table_attach (table, b, 0.0, 1, 2); + gtk_signal_connect(GTK_OBJECT(b), "toggled", + GTK_SIGNAL_FUNC(clonetiler_pick_to), (gpointer) "pick_to_color"); + } + + { + GtkWidget *b = gtk_check_button_new_with_label (_("Opacity")); + gint old = prefs_get_int_attribute (prefs_path, "pick_to_opacity", 0); + gtk_toggle_button_set_active ((GtkToggleButton *) b, old != 0); + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), b, _("Each clone's opacity is determined by the picked value in that point"), NULL); + clonetiler_table_attach (table, b, 0.0, 2, 2); + gtk_signal_connect(GTK_OBJECT(b), "toggled", + GTK_SIGNAL_FUNC(clonetiler_pick_to), (gpointer) "pick_to_opacity"); + } + } + gtk_widget_set_sensitive (vvb, prefs_get_int_attribute (prefs_path, "dotrace", 0)); + } + } + +// Rows/columns, width/height + { + GtkWidget *table = gtk_table_new (2, 2, FALSE); + gtk_container_set_border_width (GTK_CONTAINER (table), VB_MARGIN); + gtk_table_set_row_spacings (GTK_TABLE (table), 4); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (mainbox), table, FALSE, FALSE, 0); + + { + GtkWidget *hb = gtk_hbox_new(FALSE, VB_MARGIN); + g_object_set_data (G_OBJECT(dlg), "rowscols", (gpointer) hb); + + { + GtkObject *a = gtk_adjustment_new(0.0, 1, 500, 1, 10, 10); + int value = prefs_get_int_attribute (prefs_path, "ymax", 2); + gtk_adjustment_set_value (GTK_ADJUSTMENT (a), value); + GtkWidget *sb = gtk_spin_button_new (GTK_ADJUSTMENT (a), 1.0, 0); + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), sb, _("How many rows in the tiling"), NULL); + gtk_entry_set_width_chars (GTK_ENTRY (sb), 5); + gtk_box_pack_start (GTK_BOX (hb), sb, TRUE, TRUE, 0); + + gtk_signal_connect(GTK_OBJECT(a), "value_changed", + GTK_SIGNAL_FUNC(clonetiler_xy_changed), (gpointer) "ymax"); + } + + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), "×"); + gtk_misc_set_alignment (GTK_MISC (l), 1.0, 0.5); + gtk_box_pack_start (GTK_BOX (hb), l, TRUE, TRUE, 0); + } + + { + GtkObject *a = gtk_adjustment_new(0.0, 1, 500, 1, 10, 10); + int value = prefs_get_int_attribute (prefs_path, "xmax", 2); + gtk_adjustment_set_value (GTK_ADJUSTMENT (a), value); + GtkWidget *sb = gtk_spin_button_new (GTK_ADJUSTMENT (a), 1.0, 0); + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), sb, _("How many columns in the tiling"), NULL); + gtk_entry_set_width_chars (GTK_ENTRY (sb), 5); + gtk_box_pack_start (GTK_BOX (hb), sb, TRUE, TRUE, 0); + + gtk_signal_connect(GTK_OBJECT(a), "value_changed", + GTK_SIGNAL_FUNC(clonetiler_xy_changed), (gpointer) "xmax"); + } + + clonetiler_table_attach (table, hb, 0.0, 1, 2); + } + + { + GtkWidget *hb = gtk_hbox_new(FALSE, VB_MARGIN); + g_object_set_data (G_OBJECT(dlg), "widthheight", (gpointer) hb); + + // unitmenu + GtkWidget *u = sp_unit_selector_new (SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE); + sp_unit_selector_set_unit (SP_UNIT_SELECTOR(u), SP_DT_NAMEDVIEW(SP_ACTIVE_DESKTOP)->doc_units); + + { + // Width spinbutton + GtkObject *a = gtk_adjustment_new (0.0, -SP_DESKTOP_SCROLL_LIMIT, SP_DESKTOP_SCROLL_LIMIT, 1.0, 10.0, 10.0); + sp_unit_selector_add_adjustment (SP_UNIT_SELECTOR (u), GTK_ADJUSTMENT (a)); + + double value = prefs_get_double_attribute (prefs_path, "fillwidth", 50); + SPUnit const &unit = *sp_unit_selector_get_unit(SP_UNIT_SELECTOR(u)); + gdouble const units = sp_pixels_get_units (value, unit); + gtk_adjustment_set_value (GTK_ADJUSTMENT (a), units); + + GtkWidget *e = gtk_spin_button_new (GTK_ADJUSTMENT (a), 1.0 , 2); + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), e, _("Width of the rectangle to be filled"), NULL); + gtk_entry_set_width_chars (GTK_ENTRY (e), 5); + gtk_box_pack_start (GTK_BOX (hb), e, TRUE, TRUE, 0); + gtk_signal_connect(GTK_OBJECT(a), "value_changed", + GTK_SIGNAL_FUNC(clonetiler_fill_width_changed), u); + } + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), "×"); + gtk_misc_set_alignment (GTK_MISC (l), 1.0, 0.5); + gtk_box_pack_start (GTK_BOX (hb), l, TRUE, TRUE, 0); + } + + { + // Height spinbutton + GtkObject *a = gtk_adjustment_new (0.0, -SP_DESKTOP_SCROLL_LIMIT, SP_DESKTOP_SCROLL_LIMIT, 1.0, 10.0, 10.0); + sp_unit_selector_add_adjustment (SP_UNIT_SELECTOR (u), GTK_ADJUSTMENT (a)); + + double value = prefs_get_double_attribute (prefs_path, "fillheight", 50); + SPUnit const &unit = *sp_unit_selector_get_unit(SP_UNIT_SELECTOR(u)); + gdouble const units = sp_pixels_get_units (value, unit); + gtk_adjustment_set_value (GTK_ADJUSTMENT (a), units); + + + GtkWidget *e = gtk_spin_button_new (GTK_ADJUSTMENT (a), 1.0 , 2); + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), e, _("Height of the rectangle to be filled"), NULL); + gtk_entry_set_width_chars (GTK_ENTRY (e), 5); + gtk_box_pack_start (GTK_BOX (hb), e, TRUE, TRUE, 0); + gtk_signal_connect(GTK_OBJECT(a), "value_changed", + GTK_SIGNAL_FUNC(clonetiler_fill_height_changed), u); + } + + gtk_box_pack_start (GTK_BOX (hb), u, TRUE, TRUE, 0); + clonetiler_table_attach (table, hb, 0.0, 2, 2); + + } + + // Switch + GtkWidget* radio; + { + radio = gtk_radio_button_new_with_label (NULL, _("Rows, columns: ")); + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), radio, _("Create the specified number of rows and columns"), NULL); + clonetiler_table_attach (table, radio, 0.0, 1, 1); + gtk_signal_connect (GTK_OBJECT (radio), "toggled", GTK_SIGNAL_FUNC (clonetiler_switch_to_create), (gpointer) dlg); + } + if (prefs_get_int_attribute(prefs_path, "fillrect", 0) == 0) { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), TRUE); + gtk_toggle_button_toggled (GTK_TOGGLE_BUTTON (radio)); + } + { + radio = gtk_radio_button_new_with_label (gtk_radio_button_group (GTK_RADIO_BUTTON (radio)), _("Width, height: ")); + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), radio, _("Fill the specified width and height with the tiling"), NULL); + clonetiler_table_attach (table, radio, 0.0, 2, 1); + gtk_signal_connect (GTK_OBJECT (radio), "toggled", GTK_SIGNAL_FUNC (clonetiler_switch_to_fill), (gpointer) dlg); + } + if (prefs_get_int_attribute(prefs_path, "fillrect", 0) == 1) { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), TRUE); + gtk_toggle_button_toggled (GTK_TOGGLE_BUTTON (radio)); + } + } + + +// Use saved pos + { + GtkWidget *hb = gtk_hbox_new(FALSE, VB_MARGIN); + gtk_box_pack_start (GTK_BOX (mainbox), hb, FALSE, FALSE, 0); + + GtkWidget *b = gtk_check_button_new_with_label (_("Use saved size and position of the tile")); + gint keepbbox = prefs_get_int_attribute (prefs_path, "keepbbox", 1); + gtk_toggle_button_set_active ((GtkToggleButton *) b, keepbbox != 0); + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), b, _("Pretend that the size and position of the tile are the same as the last time you tiled it (if any), instead of using the current size"), NULL); + gtk_box_pack_start (GTK_BOX (hb), b, FALSE, FALSE, 0); + + gtk_signal_connect(GTK_OBJECT(b), "toggled", + GTK_SIGNAL_FUNC(clonetiler_keep_bbox_toggled), NULL); + } + +// Statusbar + { + GtkWidget *hb = gtk_hbox_new(FALSE, VB_MARGIN); + gtk_box_pack_end (GTK_BOX (mainbox), hb, FALSE, FALSE, 0); + GtkWidget *l = gtk_label_new(""); + g_object_set_data (G_OBJECT(dlg), "status", (gpointer) l); + gtk_box_pack_start (GTK_BOX (hb), l, FALSE, FALSE, 0); + } + +// Buttons + { + GtkWidget *hb = gtk_hbox_new(FALSE, VB_MARGIN); + gtk_box_pack_start (GTK_BOX (mainbox), hb, FALSE, FALSE, 0); + + { + GtkWidget *b = gtk_button_new (); + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup_with_mnemonic (GTK_LABEL(l), _(" _Create ")); + gtk_container_add (GTK_CONTAINER(b), l); + gtk_tooltips_set_tip (tt, b, _("Create and tile the clones of the selection"), NULL); + gtk_signal_connect (GTK_OBJECT (b), "clicked", GTK_SIGNAL_FUNC (clonetiler_apply), NULL); + gtk_box_pack_end (GTK_BOX (hb), b, FALSE, FALSE, 0); + } + + { // buttons which are enabled only when there are tiled clones + GtkWidget *sb = gtk_hbox_new(FALSE, 0); + gtk_box_pack_end (GTK_BOX (hb), sb, FALSE, FALSE, 0); + g_object_set_data (G_OBJECT(dlg), "buttons_on_tiles", (gpointer) sb); + { + // TRANSLATORS: if a group of objects are "clumped" together, then they + // are unevenly spread in the given amount of space - as shown in the + // diagrams on the left in the following screenshot: + // http://www.inkscape.org/screenshots/gallery/inkscape-0.42-CVS-tiles-unclump.png + // So unclumping is the process of spreading a number of objects out more evenly. + GtkWidget *b = gtk_button_new_with_mnemonic (_(" _Unclump ")); + gtk_tooltips_set_tip (tt, b, _("Spread out clones to reduce clumping; can be applied repeatedly"), NULL); + gtk_signal_connect (GTK_OBJECT (b), "clicked", GTK_SIGNAL_FUNC (clonetiler_unclump), NULL); + gtk_box_pack_end (GTK_BOX (sb), b, FALSE, FALSE, 0); + } + + { + GtkWidget *b = gtk_button_new_with_mnemonic (_(" Re_move ")); + gtk_tooltips_set_tip (tt, b, _("Remove existing tiled clones of the selected object (siblings only)"), NULL); + gtk_signal_connect (GTK_OBJECT (b), "clicked", GTK_SIGNAL_FUNC (clonetiler_remove), NULL); + gtk_box_pack_end (GTK_BOX (sb), b, FALSE, FALSE, 0); + } + + // connect to global selection changed signal (so we can change desktops) and + // external_change (so we're not fooled by undo) + g_signal_connect (G_OBJECT (INKSCAPE), "change_selection", G_CALLBACK (clonetiler_change_selection), dlg); + g_signal_connect (G_OBJECT (INKSCAPE), "external_change", G_CALLBACK (clonetiler_external_change), dlg); + g_signal_connect(G_OBJECT(dlg), "destroy", G_CALLBACK(clonetiler_disconnect_gsignal), G_OBJECT (INKSCAPE)); + + // update now + clonetiler_change_selection (NULL, SP_DT_SELECTION(SP_ACTIVE_DESKTOP), dlg); + } + + { + GtkWidget *b = gtk_button_new_with_mnemonic (_(" R_eset ")); + // TRANSLATORS: "change" is a noun here + gtk_tooltips_set_tip (tt, b, _("Reset all shifts, scales, rotates, opacity and color changes in the dialog to zero"), NULL); + gtk_signal_connect (GTK_OBJECT (b), "clicked", GTK_SIGNAL_FUNC (clonetiler_reset), NULL); + gtk_box_pack_start (GTK_BOX (hb), b, FALSE, FALSE, 0); + } + } + + gtk_widget_show_all (mainbox); + + } // end of if (!dlg) + + gtk_window_present ((GtkWindow *) dlg); +} + + +/* + 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 : diff --git a/src/dialogs/clonetiler.h b/src/dialogs/clonetiler.h new file mode 100644 index 000000000..82206818c --- /dev/null +++ b/src/dialogs/clonetiler.h @@ -0,0 +1,31 @@ +#ifndef __SP_CLONE_TILER_H__ +#define __SP_CLONE_TILER_H__ + +/** + * \brief Clone tiling dialog + * + * Authors: + * bulia byak + * + * Copyright (C) 2004 Authors + * + */ + +#include + +#include + +void clonetiler_dialog ( void ); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/dialogs/debugdialog.cpp b/src/dialogs/debugdialog.cpp new file mode 100644 index 000000000..e90f0a419 --- /dev/null +++ b/src/dialogs/debugdialog.cpp @@ -0,0 +1,348 @@ +/* + * A very simple dialog for displaying Inkscape messages. Messages + * sent to g_log(), g_warning(), g_message(), ets, are routed here, + * in order to avoid messing with the startup console. + * + * Authors: + * Bob Jamison + * Other dudes from The Inkscape Organization + * + * Copyright (C) 2004 The Inkscape Organization + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "debugdialog.h" + +namespace Inkscape { +namespace UI { +namespace Dialogs { + + +//######################################################################### +//## I M P L E M E N T A T I O N +//######################################################################### + +/** + * A dialog that displays log messages + */ +class DebugDialogImpl : public DebugDialog, public Gtk::Dialog +{ + + public: + + + /** + * Constructor + */ + DebugDialogImpl(); + + /** + * Destructor + */ + ~DebugDialogImpl(); + + + /** + * Show the dialog + */ + void show(); + + /** + * Do not show the dialog + */ + void hide(); + + /** + * Clear all information from the dialog + */ + void clear(); + + /** + * Display a message + */ + void message(char const *msg); + + /** + * Redirect g_log() messages to this widget + */ + void captureLogMessages(); + + /** + * Return g_log() messages to normal handling + */ + void releaseLogMessages(); + + + + private: + + + Gtk::MenuBar menuBar; + + Gtk::Menu fileMenu; + + Gtk::ScrolledWindow textScroll; + + Gtk::TextView messageText; + + //Handler ID's + guint handlerDefault; + guint handlerGlibmm; + guint handlerAtkmm; + guint handlerPangomm; + guint handlerGdkmm; + guint handlerGtkmm; + +}; + + + + +//######################################################################### +//## E V E N T S +//######################################################################### + +/** + * Also a public method. Remove all text from the dialog + */ +void DebugDialogImpl::clear() +{ + Glib::RefPtr buffer = messageText.get_buffer(); + buffer->erase(buffer->begin(), buffer->end()); +} + + +//######################################################################### +//## C O N S T R U C T O R / D E S T R U C T O R +//######################################################################### +/** + * Constructor + */ +DebugDialogImpl::DebugDialogImpl() +{ + set_title(_("Messages")); + set_size_request(300, 400); + + Gtk::VBox *mainVBox = get_vbox(); + + //## Add a menu for clear() + menuBar.items().push_back( Gtk::Menu_Helpers::MenuElem(_("_File"), fileMenu) ); + fileMenu.items().push_back( Gtk::Menu_Helpers::MenuElem(_("_Clear"), + sigc::mem_fun(*this, &DebugDialogImpl::clear) ) ); + fileMenu.items().push_back( Gtk::Menu_Helpers::MenuElem(_("Capture log messages"), + sigc::mem_fun(*this, &DebugDialogImpl::captureLogMessages) ) ); + fileMenu.items().push_back( Gtk::Menu_Helpers::MenuElem(_("Release log messages"), + sigc::mem_fun(*this, &DebugDialogImpl::releaseLogMessages) ) ); + mainVBox->pack_start(menuBar, Gtk::PACK_SHRINK); + + + //### Set up the text widget + messageText.set_editable(false); + textScroll.add(messageText); + textScroll.set_policy(Gtk::POLICY_ALWAYS, Gtk::POLICY_ALWAYS); + mainVBox->pack_start(textScroll); + + show_all_children(); + + message("ready."); + message("enable log display by setting "); + message("dialogs.debug 'redirect' attribute to 1 in preferences.xml"); + + handlerDefault = 0; + handlerGlibmm = 0; + handlerAtkmm = 0; + handlerPangomm = 0; + handlerGdkmm = 0; + handlerGtkmm = 0; +} + +/** + * Factory method. Use this to create a new DebugDialog + */ +DebugDialog *DebugDialog::create() +{ + DebugDialog *dialog = new DebugDialogImpl(); + return dialog; +} + + +/** + * Constructor + */ +DebugDialogImpl::~DebugDialogImpl() +{ + + +} + + +//######################################################################### +//## M E T H O D S +//######################################################################### + +void DebugDialogImpl::show() +{ + //call super() + Gtk::Dialog::show(); + //sp_transientize((GtkWidget *)gobj()); //Make transient + raise(); + Gtk::Dialog::present(); +} + + + +void DebugDialogImpl::hide() +{ + //call super() + Gtk::Dialog::hide(); +} + + + +void DebugDialogImpl::message(char const *msg) +{ + Glib::RefPtr buffer = messageText.get_buffer(); + Glib::ustring uMsg = msg; + if (uMsg[uMsg.length()-1] != '\n') + uMsg += '\n'; + buffer->insert (buffer->end(), uMsg); +} + + +/* static instance, to reduce dependencies */ +static DebugDialog *debugDialogInstance = NULL; + +DebugDialog *DebugDialog::getInstance() +{ + if ( !debugDialogInstance ) + { + debugDialogInstance = new DebugDialogImpl(); + } + return debugDialogInstance; +} + + + +void DebugDialog::showInstance() +{ + DebugDialog *debugDialog = getInstance(); + debugDialog->show(); +} + + + + +/*##### THIS IS THE IMPORTANT PART ##### */ +void dialogLoggingFunction(const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *messageText, + gpointer user_data) +{ + DebugDialogImpl *dlg = (DebugDialogImpl *)user_data; + + dlg->message(messageText); + +} + + +void DebugDialogImpl::captureLogMessages() +{ + /* + This might likely need more code, to capture Gtkmm + and Glibmm warnings, or maybe just simply grab stdout/stderr + */ + GLogLevelFlags flags = (GLogLevelFlags) (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | + G_LOG_LEVEL_WARNING | G_LOG_LEVEL_MESSAGE | + G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG); + if ( !handlerDefault ) + { + handlerDefault = g_log_set_handler(NULL, flags, + dialogLoggingFunction, (gpointer)this); + } + if ( !handlerGlibmm ) + { + handlerGlibmm = g_log_set_handler("glibmm", flags, + dialogLoggingFunction, (gpointer)this); + } + if ( !handlerAtkmm ) + { + handlerAtkmm = g_log_set_handler("atkmm", flags, + dialogLoggingFunction, (gpointer)this); + } + if ( !handlerPangomm ) + { + handlerPangomm = g_log_set_handler("pangomm", flags, + dialogLoggingFunction, (gpointer)this); + } + if ( !handlerGdkmm ) + { + handlerGdkmm = g_log_set_handler("gdkmm", flags, + dialogLoggingFunction, (gpointer)this); + } + if ( !handlerGtkmm ) + { + handlerGtkmm = g_log_set_handler("gtkmm", flags, + dialogLoggingFunction, (gpointer)this); + } + message("log capture started"); +} + +void DebugDialogImpl::releaseLogMessages() +{ + if ( handlerDefault ) + { + g_log_remove_handler(NULL, handlerDefault); + handlerDefault = 0; + } + if ( handlerGlibmm ) + { + g_log_remove_handler("glibmm", handlerGlibmm); + handlerGlibmm = 0; + } + if ( handlerAtkmm ) + { + g_log_remove_handler("atkmm", handlerAtkmm); + handlerAtkmm = 0; + } + if ( handlerPangomm ) + { + g_log_remove_handler("pangomm", handlerPangomm); + handlerPangomm = 0; + } + if ( handlerGdkmm ) + { + g_log_remove_handler("gdkmm", handlerGdkmm); + handlerGdkmm = 0; + } + if ( handlerGtkmm ) + { + g_log_remove_handler("gtkmm", handlerGtkmm); + handlerGtkmm = 0; + } + message("log capture discontinued"); +} + + + +} //namespace Dialogs +} //namespace UI +} //namespace Inkscape + +//######################################################################### +//## E N D O F F I L E +//######################################################################### + + + diff --git a/src/dialogs/debugdialog.h b/src/dialogs/debugdialog.h new file mode 100644 index 000000000..1a4a036fd --- /dev/null +++ b/src/dialogs/debugdialog.h @@ -0,0 +1,104 @@ +#ifndef __DEBUGDIALOG_H__ +#define __DEBUGDIALOG_H__ +/* + * A very simple dialog for displaying Inkscape messages. Messages + * sent to g_log(), g_warning(), g_message(), ets, are routed here, + * in order to avoid messing with the startup console. + * + * Authors: + * Bob Jamison + * Other dudes from The Inkscape Organization + * + * Copyright (C) 2004 The Inkscape Organization + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + + +namespace Inkscape { +namespace UI { +namespace Dialogs { + + +/** + * A dialog that displays log messages + */ +class DebugDialog +{ + + public: + + + /** + * Constructor + */ + DebugDialog() {}; + + + /** + * Factory method + */ + static DebugDialog *create(); + + /** + * Destructor + */ + virtual ~DebugDialog() {}; + + + /** + * Show the dialog + */ + virtual void show() = 0; + + /** + * Do not show the dialog + */ + virtual void hide() = 0; + + /** + * Clear all information from the dialog + */ + virtual void clear() = 0; + + /** + * Display a message + */ + virtual void message(char const *msg) = 0; + + /** + * Redirect g_log() messages to this widget + */ + virtual void captureLogMessages() = 0; + + /** + * Return g_log() messages to normal handling + */ + virtual void releaseLogMessages() = 0; + + /** + * Get a shared singleton instance + */ + static DebugDialog *getInstance(); + + /** + * Show the instance above + */ + static void showInstance(); + + + + +}; + + +} //namespace Dialogs +} //namespace UI +} //namespace Inkscape + + + + +#endif /* __DEBUGDIALOG_H__ */ + diff --git a/src/dialogs/dialog-events.cpp b/src/dialogs/dialog-events.cpp new file mode 100644 index 000000000..4e6a93729 --- /dev/null +++ b/src/dialogs/dialog-events.cpp @@ -0,0 +1,247 @@ +#define __DIALOG_EVENTS_C__ + +/** + * \brief Event handler for dialog windows + * + * Authors: + * bulia byak + * + * Copyright (C) 2003 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include "macros.h" +#include +#include "desktop.h" +#include "inkscape-private.h" +#include "prefs-utils.h" +#include "event-context.h" + +#include "dialog-events.h" + + + +/** +* \brief This function is called to zero the transientize semaphore by a +* timeout. +*/ +gboolean +sp_allow_again (gpointer *wd) +{ + ((win_data *) wd)->stop = 0; + return FALSE; // so that it is only called once +} + + + +/** + * \brief Remove focus from window to whoever it is transient for... + * + */ +void +sp_dialog_defocus (GtkWindow *win) +{ + GtkWindow *w; + //find out the document window we're transient for + w = gtk_window_get_transient_for ((GtkWindow *) win); + //switch to it + + if (w) { + gtk_window_present (w); + } +} + + + +/** + * \brief Callback to defocus a widget's parent dialog. + * + */ +void +sp_dialog_defocus_callback (GtkWindow *win, gpointer data) +{ + sp_dialog_defocus ((GtkWindow *) + gtk_widget_get_toplevel ((GtkWidget *) data)); +} + + + +void +sp_dialog_defocus_on_enter (GtkWidget *w) +{ + g_signal_connect ( G_OBJECT (w), "activate", + G_CALLBACK (sp_dialog_defocus_callback), w ); +} + + + +gboolean +sp_dialog_event_handler (GtkWindow *win, GdkEvent *event, gpointer data) +{ + +// if the focus is inside the Text and Font textview, do nothing + GObject *dlg = (GObject *) data; + if (g_object_get_data (dlg, "eatkeys")) { + return FALSE; + } + + gboolean ret = FALSE; + + switch (event->type) { + + case GDK_KEY_PRESS: + + switch (get_group0_keyval (&event->key)) { + case GDK_Escape: + sp_dialog_defocus (win); + ret = TRUE; + break; + case GDK_F4: + case GDK_w: + case GDK_W: + // close dialog + if (MOD__CTRL_ONLY) { + + /* this code sends a delete_event to the dialog, + * instead of just destroying it, so that the + * dialog can do some housekeeping, such as remember + * its position. + */ + GdkEventAny event; + GtkWidget *widget = (GtkWidget *) win; + event.type = GDK_DELETE; + event.window = widget->window; + event.send_event = TRUE; + g_object_ref (G_OBJECT (event.window)); + gtk_main_do_event ((GdkEvent*)&event); + g_object_unref (G_OBJECT (event.window)); + + ret = TRUE; + } + break; + default: // pass keypress to the canvas + break; + } + default: + ; + } + + return ret; + +} + + + +/** + * \brief Make the argument dialog transient to the currently active document + window. + */ +void +sp_transientize (GtkWidget *dialog) +{ + if (prefs_get_int_attribute ( "options.dialogsskiptaskbar", "value", 0)) { + gtk_window_set_skip_taskbar_hint (GTK_WINDOW (dialog), TRUE); + } + + gint transient_policy = prefs_get_int_attribute_limited ( "options.transientpolicy", "value", 1, 0, 2 ); + + if (transient_policy) { + + // if there's an active document window, attach dialog to it as a transient: + + if ( SP_ACTIVE_DESKTOP ) + { + SP_ACTIVE_DESKTOP->setWindowTransient (dialog, transient_policy); + } + } +} // end of sp_transientize() + +void on_transientize (SPDesktop *desktop, win_data *wd ) +{ + sp_transientize_callback (0, desktop, wd); +} + +void +sp_transientize_callback ( Inkscape::Application * /*inkscape*/, + SPDesktop *desktop, win_data *wd ) +{ + gint transient_policy = + prefs_get_int_attribute_limited ( "options.transientpolicy", "value", + 1, 0, 2); + + if (!transient_policy) + return; + + if (wd->stop) { + /* + * if retransientizing of this dialog is still forbidden after + * previous call warning turned off because it was confusingly fired + * when loading many files from command line + */ + // g_warning("Retranzientize aborted! You're switching windows too fast!"); + return; + } + + if (wd->win) + { + wd->stop = 1; // disallow other attempts to retranzientize this dialog + desktop->setWindowTransient (wd->win, transient_policy); + } + + // we're done, allow next retransientizing not sooner than after 6 msec + gtk_timeout_add (6, (GtkFunction) sp_allow_again, (gpointer) wd); +} + +void on_dialog_hide (GtkWidget *w) +{ + if (w) + gtk_widget_hide (w); +} + +void on_dialog_unhide (GtkWidget *w) +{ + if (w) + gtk_widget_show (w); +} + +gboolean +sp_dialog_hide (GtkObject *object, gpointer data) +{ + GtkWidget *dlg = (GtkWidget *) data; + + if (dlg) + gtk_widget_hide (dlg); + + return TRUE; +} + + + +gboolean +sp_dialog_unhide (GtkObject *object, gpointer data) +{ + GtkWidget *dlg = (GtkWidget *) data; + + if (dlg) + gtk_widget_show (dlg); + + return TRUE; +} + + +/* + 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 : diff --git a/src/dialogs/dialog-events.h b/src/dialogs/dialog-events.h new file mode 100644 index 000000000..7cc64c6a2 --- /dev/null +++ b/src/dialogs/dialog-events.h @@ -0,0 +1,66 @@ +#ifndef __DIALOG_EVENTS_H__ +#define __DIALOG_EVENTS_H__ + +/** + * \brief Event handler for dialog windows + * + * Authors: + * bulia byak + * + * Copyright (C) 2003 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#include + +/* + * event callback can only accept one argument, but we need two, + * hence this struct. + * each dialog has a local static copy: + * win is the dialog window + * stop is the transientize semaphore: when 0, retransientizing this dialog + * is allowed + */ + +typedef struct { + GtkWidget *win; + guint stop; +} win_data; + + +gboolean sp_dialog_event_handler ( GtkWindow *win, + GdkEvent *event, + gpointer data ); + +void sp_dialog_defocus ( GtkWindow *win ); +void sp_dialog_defocus_callback ( GtkWindow *win, gpointer data ); +void sp_dialog_defocus_on_enter ( GtkWidget *w ); +void sp_transientize ( GtkWidget *win ); + +void on_transientize ( SPDesktop *desktop, + win_data *wd ); + +void sp_transientize_callback ( Inkscape::Application *inkscape, + SPDesktop *desktop, + win_data *wd ); + +void on_dialog_hide (GtkWidget *w); +void on_dialog_unhide (GtkWidget *w); +gboolean sp_dialog_hide (GtkObject *object, gpointer data); +gboolean sp_dialog_unhide (GtkObject *object, gpointer data); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/dialogs/display-settings.cpp b/src/dialogs/display-settings.cpp new file mode 100644 index 000000000..f083cca06 --- /dev/null +++ b/src/dialogs/display-settings.cpp @@ -0,0 +1,1575 @@ +#define __SP_DISPLAY_SETTINGS_C__ + +/* +* Inkscape Preferences dialog +* +* Authors: +* Lauris Kaplinski +* bulia byak +* +* Copyright (C) 2001 Ximian, Inc. +* Copyright (C) 2001-2004 Authors +* +*/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include + +#include "helper/window.h" +#include "../inkscape.h" +#include "../prefs-utils.h" +#include "dialog-events.h" +#include "../macros.h" +#include "../prefs-utils.h" +#include "../verbs.h" +#include "../interface.h" +#include "../message-stack.h" +#include "../enums.h" +#include "../selcue.h" +#include "../selection.h" +#include "../selection-chemistry.h" +#include "../style.h" +#include "../desktop-handles.h" +#include "../unit-constants.h" +#include "xml/repr.h" +#include "ui/widget/style-swatch.h" + + + + +static GtkWidget *dlg = NULL; +static win_data wd; + +// impossible original values to make sure they are read from prefs +static gint x = -1000, y = -1000; +static gchar *prefs_path = "dialogs.preferences"; + +extern gint nr_arena_image_x_sample; +extern gint nr_arena_image_y_sample; + +#define SB_WIDTH 90 +#define SB_LONG_ADJUSTMENT 20 +#define SB_MARGIN 1 +#define SUFFIX_WIDTH 70 +#define HB_MARGIN 4 +#define VB_MARGIN 4 +#define VB_SKIP 1 + +static void +sp_display_dialog_destroy (GtkObject *object, gpointer data) +{ + + sp_signal_disconnect_by_data (INKSCAPE, dlg); + wd.win = dlg = NULL; + wd.stop = 0; + +} // edn of sp_display_dialog_destroy() + + + +static gboolean +sp_display_dialog_delete (GtkObject *object, GdkEvent *event, gpointer data) +{ + gtk_window_get_position ((GtkWindow *) dlg, &x, &y); + + prefs_set_int_attribute (prefs_path, "x", x); + prefs_set_int_attribute (prefs_path, "y", y); + + return FALSE; // which means, go ahead and destroy it + +} // end of sp_display_dialog_delete() + +static void +prefs_switch_page (GtkNotebook *notebook, + GtkNotebookPage *page, + guint page_num, + gchar *attr) +{ + prefs_set_int_attribute ("dialogs.preferences", attr, page_num); +} + +static gint +get_int_value_data(GtkToggleButton *button) +{ + return GPOINTER_TO_INT((gchar const*)gtk_object_get_data(GTK_OBJECT(button), "value")); +} + +static void +options_selector_show_toggled (GtkToggleButton *button) +{ + if (gtk_toggle_button_get_active (button)) { + gchar const *val = (gchar const*)gtk_object_get_data(GTK_OBJECT(button), "value"); + prefs_set_string_attribute ("tools.select", "show", val); + } +} + +static void +options_store_transform_toggled (GtkToggleButton *button) +{ + if (gtk_toggle_button_get_active (button)) { + guint const val = get_int_value_data(button); + prefs_set_int_attribute ("options.preservetransform", "value", val); + } +} + +static void +options_clone_compensation_toggled (GtkToggleButton *button) +{ + if (gtk_toggle_button_get_active (button)) { + guint const val = get_int_value_data(button); + prefs_set_int_attribute ("options.clonecompensation", "value", val); + } +} + +static void +options_clone_orphans_toggled (GtkToggleButton *button) +{ + if (gtk_toggle_button_get_active (button)) { + guint const val = get_int_value_data(button); + prefs_set_int_attribute ("options.cloneorphans", "value", val); + } +} + +static void +options_selcue_toggled (GtkToggleButton *button) +{ + if (gtk_toggle_button_get_active (button)) { + guint const val = get_int_value_data(button); + prefs_set_int_attribute ("options.selcue", "value", val); + } +} + +static void +options_scale_origin_toggled (GtkToggleButton *button) +{ + if (gtk_toggle_button_get_active (button)) { + gchar const *val = (gchar const *) gtk_object_get_data(GTK_OBJECT(button), "value"); + prefs_set_string_attribute ("tools.select", "scale_origin", val); + } +} + + +/** +* Small helper function to make options_selector a little less +* verbose. +* +* \param b Another radio button in the group, or NULL for the first. +* \param fb Box to add the button to. +* \param n Label for the button. +* \param tip Tooltip. +* \param v_string Key for the button's value, if it is a string. +* \param v_uint Key for the button's value, if it is a uint. +* \param isint Whether this is astring or uint. +* \param s Initial state of the button. +* \param h Toggled handler function. +*/ +static GtkWidget* sp_select_context_add_radio ( + GtkWidget *b, + GtkWidget *fb, + GtkTooltips *tt, + gchar const *n, + gchar const *tip, + char const *v_string, + guint v_uint, + bool isint, + gboolean s, + void (*h)(GtkToggleButton*) + ) +{ + GtkWidget* r = gtk_radio_button_new_with_label ( + b ? gtk_radio_button_group (GTK_RADIO_BUTTON (b)) : NULL, n + ); + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), r, tip, NULL); + gtk_widget_show (r); + + if (isint) + gtk_object_set_data (GTK_OBJECT (r), "value", GUINT_TO_POINTER (v_uint)); + else + gtk_object_set_data (GTK_OBJECT (r), "value", (void*) v_string); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (r), s); + gtk_box_pack_start (GTK_BOX (fb), r, FALSE, FALSE, 0); + gtk_signal_connect (GTK_OBJECT (r), "toggled", GTK_SIGNAL_FUNC (h), NULL); + + return r; +} + +static GtkWidget * +options_selector () +{ + GtkWidget *vb, *f, *fb, *b; + + GtkTooltips *tt = gtk_tooltips_new(); + + vb = gtk_vbox_new (FALSE, VB_MARGIN); + + f = gtk_frame_new (_("When transforming, show:")); + gtk_widget_show (f); + gtk_box_pack_start (GTK_BOX (vb), f, FALSE, FALSE, 0); + + fb = gtk_hbox_new (FALSE, 10); + gtk_widget_show (fb); + gtk_container_add (GTK_CONTAINER (f), fb); + + gchar const *show = prefs_get_string_attribute ("tools.select", "show"); + + b = sp_select_context_add_radio ( + NULL, fb, tt, _("Objects"), + _("Show the actual objects when moving or transforming"), + "content", 0, false, + (show == NULL) || !strcmp (show, "content"), + options_selector_show_toggled + ); + + sp_select_context_add_radio( + b, fb, tt, _("Box outline"), + _("Show only a box outline of the objects when moving or transforming"), + "outline", 0, false, + show && !strcmp (show, "outline"), + options_selector_show_toggled + ); + + f = gtk_frame_new (_("Per-object selection cue:")); + gtk_widget_show (f); + gtk_box_pack_start (GTK_BOX (vb), f, FALSE, FALSE, 0); + + fb = gtk_hbox_new (FALSE, 10); + gtk_widget_show (fb); + gtk_container_add (GTK_CONTAINER (f), fb); + + gint cue = prefs_get_int_attribute ("options.selcue", "value", Inkscape::SelCue::MARK); + + b = sp_select_context_add_radio ( + NULL, fb, tt, _("None"), + _("No per-object selection indication"), NULL, Inkscape::SelCue::NONE, true, + cue == Inkscape::SelCue::NONE, + options_selcue_toggled + ); + + b = sp_select_context_add_radio ( + b, fb, tt, _("Mark"), + _("Each selected object has a diamond mark in the top left corner"), + NULL, Inkscape::SelCue::MARK, true, + cue == Inkscape::SelCue::MARK, + options_selcue_toggled + ); + + sp_select_context_add_radio ( + b, fb, tt, _("Box"), + _("Each selected object displays its bounding box"), NULL, Inkscape::SelCue::BBOX, true, + cue == Inkscape::SelCue::BBOX, + options_selcue_toggled + ); + + f = gtk_frame_new (_("Default scale origin:")); + gtk_widget_show (f); + gtk_box_pack_start (GTK_BOX (vb), f, FALSE, FALSE, 0); + + fb = gtk_hbox_new (FALSE, 10); + gtk_widget_show (fb); + gtk_container_add (GTK_CONTAINER (f), fb); + + gchar const *scale_orig = prefs_get_string_attribute ("tools.select", "scale_origin"); + + b = sp_select_context_add_radio ( + NULL, fb, tt, _("Opposite bounding box edge"), + _("Default scale origin will be on the bounding box of the item"), "bbox", 0, false, + (scale_orig == NULL) || !strcmp (scale_orig, "bbox"), + options_scale_origin_toggled + ); + + sp_select_context_add_radio ( + b, fb, tt, _("Farthest opposite node"), + _("Default scale origin will be on the bounding box of the item's points"), + "points", 0, false, + scale_orig && !strcmp (scale_orig, "points"), + options_scale_origin_toggled + ); + + return vb; +} + + +static void +sp_display_dialog_set_oversample (GtkMenuItem *item, gpointer data) +{ + gint os; + + os = GPOINTER_TO_INT (data); + + g_return_if_fail (os >= 0); + g_return_if_fail (os <= 4); + + nr_arena_image_x_sample = os; + nr_arena_image_y_sample = os; + + inkscape_refresh_display (INKSCAPE); + + prefs_set_int_attribute ( "options.bitmapoversample", "value", os ); + +} + + +static void +options_rotation_steps_changed (GtkMenuItem *item, gpointer data) +{ + gint snaps_new = GPOINTER_TO_INT (data); + prefs_set_int_attribute ( "options.rotationsnapsperpi", "value", snaps_new ); +} + +static void +options_dialogs_ontop_changed (GtkMenuItem *item, gpointer data) +{ + gint policy_new = GPOINTER_TO_INT (data); + prefs_set_int_attribute ( "options.transientpolicy", "value", policy_new ); +} + +void +options_rotation_steps (GtkWidget *vb, GtkTooltips *tt) +{ + GtkWidget *hb = gtk_hbox_new (FALSE, HB_MARGIN); + gtk_widget_show (hb); + gtk_box_pack_start (GTK_BOX (vb), hb, FALSE, FALSE, 0); + + { + GtkWidget *l = gtk_label_new (_("degrees")); + gtk_misc_set_alignment (GTK_MISC (l), 0.0, 0.5); + gtk_widget_set_size_request (l, SUFFIX_WIDTH, -1); + gtk_widget_show (l); + gtk_box_pack_end (GTK_BOX (hb), l, FALSE, FALSE, 0); + } + + { + GtkWidget *om = gtk_option_menu_new (); + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), om, _("Rotating with Ctrl pressed snaps every that much degrees; also, pressing [ or ] rotates by this amount"), NULL); + gtk_widget_set_size_request (om, SB_WIDTH, -1); + gtk_widget_show (om); + gtk_box_pack_end (GTK_BOX (hb), om, FALSE, FALSE, SB_MARGIN); + + GtkWidget *m = gtk_menu_new (); + gtk_widget_show (m); + + int snaps_current = prefs_get_int_attribute ("options.rotationsnapsperpi", "value", 12); + int position_current = 0; + + struct RotSteps { + double degrees; + int snaps; + } const rot_snaps[] = { + {90, 2}, + {60, 3}, + {45, 4}, + {30, 6}, + {15, 12}, + {10, 18}, + {7.5, 24}, + {6, 30}, + {5, 36}, + {3, 60}, + {2, 90}, + {1, 180}, + {1, 0}, + }; + + for (unsigned j = 0; j < G_N_ELEMENTS(rot_snaps); ++j) { + RotSteps const &rs = rot_snaps[j]; + + gchar const *label = NULL; + if (rs.snaps == 0) { + // sorationsnapsperpi == 0 means no snapping + label = _("None"); + } else { + label = g_strdup_printf ("%.2g", rs.degrees); + } + + if (rs.snaps == snaps_current) + position_current = j; + + GtkWidget *item = gtk_menu_item_new_with_label (label); + gtk_signal_connect ( GTK_OBJECT (item), "activate", + GTK_SIGNAL_FUNC (options_rotation_steps_changed), + GINT_TO_POINTER (rs.snaps) ); + gtk_widget_show (item); + gtk_menu_append (GTK_MENU (m), item); + } + + gtk_option_menu_set_menu (GTK_OPTION_MENU (om), m); + gtk_option_menu_set_history ( GTK_OPTION_MENU (om), position_current); + } + + { + GtkWidget *l = gtk_label_new (_("Rotation snaps every:")); + gtk_misc_set_alignment (GTK_MISC (l), 1.0, 0.5); + gtk_widget_show (l); + gtk_box_pack_start (GTK_BOX (hb), l, TRUE, TRUE, 0); + } +} + +void +options_dialogs_ontop (GtkWidget *vb, GtkTooltips *tt) +{ + GtkWidget *hb = gtk_hbox_new (FALSE, HB_MARGIN); + gtk_widget_show (hb); + gtk_box_pack_start (GTK_BOX (vb), hb, FALSE, FALSE, 0); + + { // empty label for alignment + GtkWidget *l = gtk_label_new (""); + gtk_widget_set_size_request (l, SUFFIX_WIDTH - SB_LONG_ADJUSTMENT, -1); + gtk_widget_show (l); + gtk_box_pack_end (GTK_BOX (hb), l, FALSE, FALSE, 0); + } + + { + GtkWidget *om = gtk_option_menu_new (); + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), om, _("None: dialogs are treated as regular windows; Normal: dialogs stay on top of document windows; Aggressive: same as Normal but may work better with some window managers."), NULL); + gtk_widget_set_size_request (om, SB_WIDTH + SB_LONG_ADJUSTMENT, -1); + gtk_widget_show (om); + gtk_box_pack_end (GTK_BOX (hb), om, FALSE, FALSE, SB_MARGIN); + + GtkWidget *m = gtk_menu_new (); + gtk_widget_show (m); + + int current = prefs_get_int_attribute ("options.transientpolicy", "value", 1); + + { + const gchar *label = _("None"); + GtkWidget *item = gtk_menu_item_new_with_label (label); + gtk_signal_connect ( GTK_OBJECT (item), "activate", + GTK_SIGNAL_FUNC (options_dialogs_ontop_changed), + GINT_TO_POINTER (0) ); + gtk_widget_show (item); + gtk_menu_append (GTK_MENU (m), item); + } + + { + const gchar *label = _("Normal"); + GtkWidget *item = gtk_menu_item_new_with_label (label); + gtk_signal_connect ( GTK_OBJECT (item), "activate", + GTK_SIGNAL_FUNC (options_dialogs_ontop_changed), + GINT_TO_POINTER (1) ); + gtk_widget_show (item); + gtk_menu_append (GTK_MENU (m), item); + } + + { + const gchar *label = _("Aggressive"); + GtkWidget *item = gtk_menu_item_new_with_label (label); + gtk_signal_connect ( GTK_OBJECT (item), "activate", + GTK_SIGNAL_FUNC (options_dialogs_ontop_changed), + GINT_TO_POINTER (2) ); + gtk_widget_show (item); + gtk_menu_append (GTK_MENU (m), item); + } + + gtk_option_menu_set_menu (GTK_OPTION_MENU (om), m); + gtk_option_menu_set_history ( GTK_OPTION_MENU (om), current); + } + + { + GtkWidget *l = gtk_label_new (_("Dialogs on top:")); + gtk_misc_set_alignment (GTK_MISC (l), 1.0, 0.5); + gtk_widget_show (l); + gtk_box_pack_start (GTK_BOX (hb), l, TRUE, TRUE, 0); + } +} + + +static void +sp_display_dialog_cursor_tolerance_changed (GtkAdjustment *adj, gpointer data) +{ + prefs_set_double_attribute ( "options.cursortolerance", "value", + adj->value ); +} + +static void +options_freehand_tolerance_changed (GtkAdjustment *adj, gpointer data) +{ + prefs_set_double_attribute ("tools.freehand.pencil", "tolerance", adj->value); +} + +static void +options_changed_double (GtkAdjustment *adj, gpointer data) +{ + const gchar *prefs_path = (const gchar *) data; + prefs_set_double_attribute (prefs_path, "value", adj->value); +} + +static void +options_changed_int (GtkAdjustment *adj, gpointer data) +{ + const gchar *prefs_path = (const gchar *) data; + prefs_set_int_attribute (prefs_path, "value", (int) adj->value); +} + +static void +options_changed_percent (GtkAdjustment *adj, gpointer data) +{ + const gchar *prefs_path = (const gchar *) data; + prefs_set_double_attribute (prefs_path, "value", (adj->value)/100.0); +} + +static void +options_changed_boolean (GtkToggleButton *tb, gpointer data) +{ + const gchar *prefs_path = (const gchar *) data; + const gchar *prefs_attr = (const gchar *) g_object_get_data (G_OBJECT(tb), "attr"); + prefs_set_int_attribute (prefs_path, prefs_attr, gtk_toggle_button_get_active (tb)); +} + +void +options_sb ( + gchar const *label, + gchar const *tooltip, GtkTooltips *tt, + gchar const *suffix, + GtkWidget *box, + gdouble lower, gdouble upper, gdouble step_increment, gdouble page_increment, gdouble page_size, + gchar const *prefs_path, gchar const *attr, gdouble def, + bool isint, bool ispercent, + void (*changed)(GtkAdjustment *, gpointer) +) +{ + GtkWidget *hb = gtk_hbox_new (FALSE, HB_MARGIN); + gtk_widget_show (hb); + gtk_box_pack_start (GTK_BOX (box), hb, FALSE, FALSE, VB_SKIP); + + { + GtkWidget *l = gtk_label_new (suffix); + gtk_misc_set_alignment (GTK_MISC (l), 0.0, 0.5); + gtk_widget_set_size_request (l, SUFFIX_WIDTH, -1); + gtk_widget_show (l); + gtk_box_pack_end (GTK_BOX (hb), l, FALSE, FALSE, 0); + } + + { + GtkObject *a = gtk_adjustment_new(0.0, lower, upper, step_increment, page_increment, page_size); + + gdouble value; + if (isint) + if (ispercent) + value = 100 * (gdouble) prefs_get_double_attribute_limited (prefs_path, attr, def, lower/100.0, upper/100.0); + else + value = (gdouble) prefs_get_int_attribute_limited (prefs_path, attr, (int) def, (int) lower, (int) upper); + else + value = prefs_get_double_attribute_limited (prefs_path, attr, def, lower, upper); + + gtk_adjustment_set_value (GTK_ADJUSTMENT (a), value); + + GtkWidget *sb; + if (isint) { + sb = gtk_spin_button_new (GTK_ADJUSTMENT (a), 1.0, 0); + } else { + if (step_increment < 0.1) + sb = gtk_spin_button_new (GTK_ADJUSTMENT (a), 0.01, 3); + else + sb = gtk_spin_button_new (GTK_ADJUSTMENT (a), 0.01, 2); + } + + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), sb, tooltip, NULL); + gtk_entry_set_width_chars (GTK_ENTRY (sb), 6); + gtk_widget_set_size_request (sb, SB_WIDTH, -1); + gtk_widget_show (sb); + gtk_box_pack_end (GTK_BOX (hb), sb, FALSE, FALSE, SB_MARGIN); + + gtk_signal_connect(GTK_OBJECT(a), "value_changed", + GTK_SIGNAL_FUNC(changed), (gpointer) prefs_path); + } + + { + GtkWidget *l = gtk_label_new (label); + gtk_misc_set_alignment (GTK_MISC (l), 1.0, 0.5); + gtk_widget_show (l); + gtk_box_pack_start (GTK_BOX (hb), l, TRUE, TRUE, 0); + } + +} + + +void +options_checkbox ( + gchar const *label, + gchar const *tooltip, GtkTooltips *tt, + GtkWidget *box, + gchar const *prefs_path, gchar const *attr, gint def, + void (*changed)(GtkToggleButton *, gpointer) +) +{ + GtkWidget *hb = gtk_hbox_new (FALSE, HB_MARGIN); + gtk_widget_show (hb); + gtk_box_pack_start (GTK_BOX (box), hb, FALSE, FALSE, VB_SKIP); + + { // empty label for alignment + GtkWidget *l = gtk_label_new (""); + gtk_widget_set_size_request (l, SUFFIX_WIDTH, -1); + gtk_widget_show (l); + gtk_box_pack_end (GTK_BOX (hb), l, FALSE, FALSE, 0); + } + + { + + GtkWidget *b = gtk_check_button_new (); + + gint value = prefs_get_int_attribute (prefs_path, attr, def); + + gtk_toggle_button_set_active ((GtkToggleButton *) b, value != 0); + + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), b, tooltip, NULL); + + gtk_widget_set_size_request (b, SB_WIDTH, -1); + gtk_widget_show (b); + gtk_box_pack_end (GTK_BOX (hb), b, FALSE, FALSE, SB_MARGIN); + + g_object_set_data (G_OBJECT(b), "attr", (void *) attr); + + gtk_signal_connect(GTK_OBJECT(b), "toggled", + GTK_SIGNAL_FUNC(changed), (gpointer) prefs_path); + } + + { + GtkWidget *l = gtk_label_new (label); + gtk_misc_set_alignment (GTK_MISC (l), 1.0, 0.5); + gtk_widget_show (l); + gtk_box_pack_start (GTK_BOX (hb), l, TRUE, TRUE, 0); + } +} + +void +selcue_checkbox (GtkWidget *vb, GtkTooltips *tt, const gchar *path) +{ + options_checkbox ( + _("Show selection cue"), + _("Whether selected objects display a selection cue (the same as in selector)"), tt, + vb, + path, "selcue", 1, + options_changed_boolean + ); +} + +void +gradientdrag_checkbox (GtkWidget *vb, GtkTooltips *tt, const gchar *path) +{ + options_checkbox ( + _("Enable gradient editing"), + _("Whether selected objects display gradient editing controls"), tt, + vb, + path, "gradientdrag", 1, + options_changed_boolean + ); +} + + +/** +* Helper function for new_objects_style +* +* \param b Another radio button in the group, or NULL for the first. +* \param fb Box to add the button to. +* \param n Label for the button. +* \param tip Tooltip. +* \param v_uint Key for the button's value +* \param s Initial state of the button. +* \param h Toggled handler function. +*/ +static GtkWidget* new_objects_style_add_radio ( + GtkWidget* b, + GtkWidget* fb, + GtkTooltips* tt, + const gchar* n, + const gchar* tip, + guint v_uint, + gboolean s, + void (*h)(GtkToggleButton*, gpointer), + gchar const *prefs_path, + gchar const *attr, + GtkWidget *button + ) +{ + GtkWidget* r = gtk_radio_button_new_with_label ( + b ? gtk_radio_button_group (GTK_RADIO_BUTTON (b)) : NULL, n + ); + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), r, tip, NULL); + gtk_widget_show (r); + + gtk_object_set_data (GTK_OBJECT (r), "value", GUINT_TO_POINTER (v_uint)); + gtk_object_set_data (GTK_OBJECT (r), "attr", (gpointer) attr); + gtk_object_set_data (GTK_OBJECT (r), "button_to_activate", (gpointer) button); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (r), s); + gtk_signal_connect (GTK_OBJECT (r), "toggled", GTK_SIGNAL_FUNC (h), (gpointer) prefs_path); + + return r; +} + +static void +style_from_selection_to_tool(GtkWidget *widget, gchar const *prefs_path) +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop == NULL) + return; + + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + + if (selection->isEmpty()) { + SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, + _("No objects selected to take the style from.")); + return; + } + SPItem *item = selection->singleItem(); + if (!item) { + /* TODO: If each item in the selection has the same style then don't consider it an error. + * Maybe we should try to handle multiple selections anyway, e.g. the intersection of the + * style attributes for the selected items. */ + SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, + _("More than one object selected. Cannot take style from multiple objects.")); + return; + } + + SPCSSAttr *css = take_style_from_item (item); + + if (!css) return; + + // only store text style for the text tool + if (!g_strrstr ((const gchar *) prefs_path, "text")) { + css = sp_css_attr_unset_text (css); + } + + // we cannot store properties with uris - they will be invalid in other documents + css = sp_css_attr_unset_uris (css); + + sp_repr_css_change (inkscape_get_repr (INKSCAPE, prefs_path), css, "style"); + sp_repr_css_attr_unref (css); + + // update the swatch + Inkscape::UI::Widget::StyleSwatch *swatch = + (Inkscape::UI::Widget::StyleSwatch *) gtk_object_get_data (GTK_OBJECT (widget), "swatch"); + if (swatch) { + Inkscape::XML::Node *tool_repr = inkscape_get_repr(INKSCAPE, prefs_path); + if (tool_repr) { + SPCSSAttr *css = sp_repr_css_attr_inherited(tool_repr, "style"); + + swatch->setStyle (css); + + sp_repr_css_attr_unref(css); + } + } +} + +static void +options_changed_radio (GtkToggleButton *tb, gpointer data) +{ + const gchar *prefs_path = (const gchar *) data; + const gchar *prefs_attr = (const gchar *) g_object_get_data (G_OBJECT(tb), "attr"); + const guint val = GPOINTER_TO_INT((const gchar*)gtk_object_get_data(GTK_OBJECT(tb), "value")); + + if (prefs_path && prefs_attr && gtk_toggle_button_get_active (tb)) { + prefs_set_int_attribute (prefs_path, prefs_attr, val); + } + + GtkWidget *button = (GtkWidget *) g_object_get_data (G_OBJECT(tb), "button_to_activate"); + if (button) + gtk_widget_set_sensitive (button, !val); +} + +void +new_objects_style (GtkWidget *vb, GtkTooltips *tt, const gchar *path) +{ + GtkWidget *f = gtk_frame_new (_("Create new objects with:")); + gtk_widget_show (f); + gtk_box_pack_start (GTK_BOX (vb), f, FALSE, FALSE, 0); + + GtkWidget *fb = gtk_vbox_new (FALSE, 0); + gtk_widget_show (fb); + gtk_container_add (GTK_CONTAINER (f), fb); + + guint usecurrent = prefs_get_int_attribute (path, "usecurrent", 0); + + GtkWidget *take = gtk_button_new_with_label (_("Take from selection")); + gtk_tooltips_set_tip (tt, take, _("Remember the style of the (first) selected object as this tool's style"), NULL); + gtk_widget_show (take); + + GtkWidget *b; + { + b = new_objects_style_add_radio ( + NULL, fb, tt, _("Last used style"), _("Apply the style you last set on an object"), + 1, + usecurrent != 0, + options_changed_radio, + path, "usecurrent", take + ); + + GtkWidget *hb = gtk_hbox_new(FALSE, HB_MARGIN); + gtk_box_pack_start (GTK_BOX (hb), b, FALSE, FALSE, 0); + gtk_widget_show_all(hb); + + gtk_box_pack_start (GTK_BOX (fb), hb, FALSE, FALSE, 0); + } + + { + b = new_objects_style_add_radio ( + b, fb, tt, _("This tool's own style:"), _("Each tool may store its own style to apply to the newly created objects. Use the button below to set it."), + 0, + usecurrent == 0, + options_changed_radio, + path, "usecurrent", take + ); + + GtkWidget *hb = gtk_hbox_new(FALSE, HB_MARGIN); + gtk_box_pack_start (GTK_BOX (hb), b, FALSE, FALSE, 0); + + // style swatch + Inkscape::XML::Node *tool_repr = inkscape_get_repr(INKSCAPE, path); + if (tool_repr) { + SPCSSAttr *css = sp_repr_css_attr_inherited(tool_repr, "style"); + Inkscape::UI::Widget::StyleSwatch *swatch = new Inkscape::UI::Widget::StyleSwatch(css); + gtk_box_pack_start (GTK_BOX (hb), (GtkWidget *) swatch->gobj(), FALSE, FALSE, 0); + sp_repr_css_attr_unref(css); + gtk_object_set_data (GTK_OBJECT (take), "swatch", (gpointer) swatch); + } + + // add "take from selection" button + gtk_widget_set_sensitive (take, (usecurrent == 0)); + gtk_box_pack_start (GTK_BOX (hb), take, FALSE, FALSE, 0); + gtk_signal_connect (GTK_OBJECT (take), "clicked", GTK_SIGNAL_FUNC (style_from_selection_to_tool), (void *) path); + + gtk_widget_show_all(hb); + + gtk_box_pack_start (GTK_BOX (fb), hb, FALSE, FALSE, 0); + } +} + + +static GtkWidget * +options_dropper () +{ + GtkTooltips *tt = gtk_tooltips_new(); + + GtkWidget *vb = gtk_vbox_new (FALSE, VB_MARGIN); + + selcue_checkbox (vb, tt, "tools.dropper"); + gradientdrag_checkbox (vb, tt, "tools.dropper"); + + return vb; +} + +GtkWidget * +new_tab (GtkWidget *nb, const gchar *label) +{ + GtkWidget *l = gtk_label_new (label); + gtk_widget_show (l); + GtkWidget *vb = gtk_vbox_new (FALSE, VB_MARGIN); + gtk_widget_show (vb); + gtk_container_set_border_width (GTK_CONTAINER (vb), VB_MARGIN); + gtk_notebook_append_page (GTK_NOTEBOOK (nb), vb, l); + return vb; +} + +void +sp_display_dialog (void) +{ + + GtkWidget *nb, *l, *vb, *vbvb, *hb, *om, *m, *i, *frame; + + if (!dlg) + { + + gchar title[500]; + sp_ui_dialog_title_string (Inkscape::Verb::get(SP_VERB_DIALOG_DISPLAY), title); + + dlg = sp_window_new (title, TRUE); + gtk_window_set_resizable ((GtkWindow *) dlg, FALSE); + if (x == -1000 || y == -1000) { + x = prefs_get_int_attribute (prefs_path, "x", 0); + y = prefs_get_int_attribute (prefs_path, "y", 0); + } + + if (x != 0 || y != 0) { + gtk_window_move ((GtkWindow *) dlg, x, y); + } else { + gtk_window_set_position(GTK_WINDOW(dlg), GTK_WIN_POS_CENTER); + } + + + sp_transientize (dlg); + wd.win = dlg; + wd.stop = 0; + + g_signal_connect ( G_OBJECT (INKSCAPE), "activate_desktop", + G_CALLBACK (sp_transientize_callback), &wd); + + gtk_signal_connect ( GTK_OBJECT (dlg), "event", GTK_SIGNAL_FUNC (sp_dialog_event_handler), dlg); + + gtk_signal_connect ( GTK_OBJECT (dlg), "destroy", G_CALLBACK (sp_display_dialog_destroy), dlg); + gtk_signal_connect ( GTK_OBJECT (dlg), "delete_event", G_CALLBACK (sp_display_dialog_delete), dlg); + g_signal_connect ( G_OBJECT (INKSCAPE), "shut_down", G_CALLBACK (sp_display_dialog_delete), dlg); + + g_signal_connect ( G_OBJECT (INKSCAPE), "dialogs_hide", G_CALLBACK (sp_dialog_hide), dlg); + g_signal_connect ( G_OBJECT (INKSCAPE), "dialogs_unhide", G_CALLBACK (sp_dialog_unhide), dlg); + + GtkTooltips *tt = gtk_tooltips_new(); + + nb = gtk_notebook_new (); + gtk_widget_show (nb); + gtk_container_add (GTK_CONTAINER (dlg), nb); + +// Mouse + vb = new_tab (nb, _("Mouse")); + + options_sb ( + /* TRANSLATORS: "Grab" is a noun here. "Grab sensitivity" is intended to mean how + * close on the screen you need to be to an object to be able to grab it with mouse (in + * pixels). */ + _("Grab sensitivity:"), + _("How close on the screen you need to be to an object to be able to grab it with mouse (in screen pixels)"), tt, + _("pixels"), + /* todo: allow real-world units. */ + vb, + 0.0, 30.0, 1.0, 1.0, 1.0, + "options.cursortolerance", "value", 8.0, + true, false, + sp_display_dialog_cursor_tolerance_changed + ); + + options_sb ( + _("Click/drag threshold:"), + _("Maximum mouse drag (in screen pixels) which is considered a click, not a drag"), tt, + _("pixels"), + vb, + 0.0, 20.0, 1.0, 1.0, 1.0, + "options.dragtolerance", "value", 4.0, + true, false, + options_changed_int + ); + + +// Scrolling + vb = new_tab (nb, _("Scrolling")); + + options_sb ( + _("Mouse wheel scrolls by:"), + _("One mouse wheel notch scrolls by this distance in screen pixels (horizontally with Shift)"), tt, + _("pixels"), + vb, + 0.0, 1000.0, 1.0, 1.0, 1.0, + "options.wheelscroll", "value", 40.0, + true, false, + options_changed_int + ); + + frame = gtk_frame_new (_("Ctrl+arrows")); + gtk_widget_show (frame); + gtk_box_pack_start (GTK_BOX (vb), frame, FALSE, FALSE, 0); + vbvb = gtk_vbox_new (FALSE, VB_MARGIN); + gtk_widget_show (vbvb); + gtk_container_add (GTK_CONTAINER (frame), vbvb); + + options_sb ( + _("Scroll by:"), + _("Pressing Ctrl+arrow key scrolls by this distance (in screen pixels)"), tt, + _("pixels"), + vbvb, + 0.0, 1000.0, 1.0, 1.0, 1.0, + "options.keyscroll", "value", 10.0, + true, false, + options_changed_int + ); + + options_sb ( + _("Acceleration:"), + _("Pressing and holding Ctrl+arrow will gradually speed up scrolling (0 for no acceleration)"), tt, + "", + vbvb, + 0.0, 5.0, 0.01, 1.0, 1.0, + "options.scrollingacceleration", "value", 0.35, + false, false, + options_changed_double + ); + + frame = gtk_frame_new (_("Autoscrolling")); + gtk_widget_show (frame); + gtk_box_pack_start (GTK_BOX (vb), frame, FALSE, FALSE, 0); + vbvb = gtk_vbox_new (FALSE, VB_MARGIN); + gtk_widget_show (vbvb); + gtk_container_add (GTK_CONTAINER (frame), vbvb); + + options_sb ( + _("Speed:"), + _("How fast the canvas autoscrolls when you drag beyond canvas edge (0 to turn autoscroll off)"), tt, + "", + vbvb, + 0.0, 5.0, 0.01, 1.0, 1.0, + "options.autoscrollspeed", "value", 0.7, + false, false, + options_changed_double + ); + + options_sb ( + _("Threshold:"), + _("How far (in screen pixels) you need to be from the canvas edge to trigger autoscroll; positive is outside the canvas, negative is within the canvas"), tt, + _("pixels"), + vbvb, + -600.0, 600.0, 1.0, 1.0, 1.0, + "options.autoscrolldistance", "value", -10.0, + true, false, + options_changed_int + ); + +// Steps + vb = new_tab (nb, _("Steps")); + + options_sb ( + _("Arrow keys move by:"), + _("Pressing an arrow key moves selected object(s) or node(s) by this distance (in px units)"), tt, + _("px"), + vb, + 0.0, 3000.0, 0.01, 1.0, 1.0, + "options.nudgedistance", "value", 2.0, + false, false, + options_changed_double + ); + + options_sb ( + _("> and < scale by:"), + _("Pressing > or < scales selection up or down by this increment (in px units)"), tt, + _("px"), + vb, + 0.0, 3000.0, 0.01, 1.0, 1.0, + "options.defaultscale", "value", 2.0, + false, false, + options_changed_double + ); + + options_sb ( + _("Inset/Outset by:"), + _("Inset and Outset commands displace the path by this distance (in px units)"), tt, + _("px"), + vb, + 0.0, 3000.0, 0.01, 1.0, 1.0, + "options.defaultoffsetwidth", "value", 2.0, + false, false, + options_changed_double + ); + + options_rotation_steps (vb, tt); + +options_checkbox ( + _("Compass-like display of angles"), + // TRANSLATORS: "positive clockwise" means "increasing in clockwise direction" + _("When on, angles are displayed with 0 at north, 0 to 360 range, positive clockwise; otherwise with 0 at east, -180 to 180 range, positive counterclockwise"), tt, + vb, + "options.compassangledisplay", "value", 1, + options_changed_boolean + ); + + options_sb ( + _("Zoom in/out by:"), + _("Zoom tool click, +/- keys, and middle click zoom in and out by this multiplier"), tt, + _("%"), + vb, + 101.0, 500.0, 1.0, 1.0, 1.0, + "options.zoomincrement", "value", 1.414213562, + true, true, + options_changed_percent + ); + +// Tools + vb = new_tab (nb, _("Tools")); + + GtkWidget *nb_tools = gtk_notebook_new (); + gtk_widget_show (nb_tools); + gtk_container_add (GTK_CONTAINER (vb), nb_tools); + + // Selector + { + GtkWidget *vb_tool = new_tab (nb_tools, _("Selector")); + + GtkWidget *selector_page = options_selector (); + gtk_widget_show (selector_page); + gtk_container_add (GTK_CONTAINER (vb_tool), selector_page); + + gradientdrag_checkbox (vb_tool, tt, "tools.select"); + } + + // Node + { + GtkWidget *vb_tool = new_tab (nb_tools, _("Node")); + + selcue_checkbox (vb_tool, tt, "tools.nodes"); + gradientdrag_checkbox (vb_tool, tt, "tools.nodes"); + } + + // Zoom + { + GtkWidget *vb_tool = new_tab (nb_tools, _("Zoom")); + + selcue_checkbox (vb_tool, tt, "tools.zoom"); + gradientdrag_checkbox (vb_tool, tt, "tools.zoom"); + } + + { // The 4 shape tools + GtkWidget *vb_shapes = new_tab (nb_tools, _("Shapes")); + + GtkWidget *nb_shapes = gtk_notebook_new (); + gtk_widget_show (nb_shapes); + gtk_container_add (GTK_CONTAINER (vb_shapes), nb_shapes); + + // Rect + { + GtkWidget *vb_tool = new_tab(nb_shapes, _("Rectangle")); + new_objects_style (vb_tool, tt, "tools.shapes.rect"); + } + + // Ellipse + { + GtkWidget *vb_tool = new_tab(nb_shapes, _("Ellipse")); + new_objects_style (vb_tool, tt, "tools.shapes.arc"); + } + + // Star + { + GtkWidget *vb_tool = new_tab(nb_shapes, _("Star")); + new_objects_style (vb_tool, tt, "tools.shapes.star"); + } + + // Spiral + { + GtkWidget *vb_tool = new_tab(nb_shapes, _("Spiral")); + new_objects_style (vb_tool, tt, "tools.shapes.spiral"); + } + + // common for all shapes + selcue_checkbox (vb_shapes, tt, "tools.shapes"); + gradientdrag_checkbox (vb_shapes, tt, "tools.shapes"); + + g_signal_connect(GTK_OBJECT (nb_shapes), "switch-page", GTK_SIGNAL_FUNC (prefs_switch_page), (void *) "page_shapes"); + gtk_notebook_set_current_page (GTK_NOTEBOOK (nb_shapes), prefs_get_int_attribute ("dialogs.preferences", "page_shapes", 0)); + } + + // Pencil + { + GtkWidget *vb_tool = new_tab (nb_tools, _("Pencil")); + + options_sb ( + _("Tolerance:"), + _("This value affects the amount of smoothing applied to freehand lines; lower values produce more uneven paths with more nodes"), tt, + "", + vb_tool, + 0.0, 100.0, 0.5, 1.0, 1.0, + "tools.freehand.pencil", "tolerance", 10.0, + false, false, + options_freehand_tolerance_changed + ); + + new_objects_style (vb_tool, tt, "tools.freehand.pencil"); + + selcue_checkbox (vb_tool, tt, "tools.freehand.pencil"); + } + + // Pen + { + GtkWidget *vb_tool = new_tab (nb_tools, _("Pen")); + + new_objects_style (vb_tool, tt, "tools.freehand.pen"); + + selcue_checkbox (vb_tool, tt, "tools.freehand.pen"); + } + + // Calligraphy + { + GtkWidget *vb_tool = new_tab (nb_tools, _("Calligraphy")); + + new_objects_style (vb_tool, tt, "tools.calligraphic"); + } + + // Text + { + GtkWidget *vb_tool = new_tab (nb_tools, _("Text")); + + new_objects_style (vb_tool, tt, "tools.text"); + + selcue_checkbox (vb_tool, tt, "tools.text"); + gradientdrag_checkbox (vb_tool, tt, "tools.text"); + } + + // Gradient + { + GtkWidget *vb_tool = new_tab (nb_tools, _("Gradient")); + + selcue_checkbox (vb_tool, tt, "tools.gradient"); + } + + // Connector + { + GtkWidget *vb_tool = new_tab (nb_tools, _("Connector")); + + selcue_checkbox (vb_tool, tt, "tools.connector"); + } + + // Dropper + { + GtkWidget *vb_tool = new_tab (nb_tools, _("Dropper")); + + GtkWidget *dropper_page = options_dropper (); + gtk_widget_show (dropper_page); + gtk_container_add (GTK_CONTAINER (vb_tool), dropper_page); + } + + g_signal_connect(GTK_OBJECT (nb_tools), "switch-page", GTK_SIGNAL_FUNC (prefs_switch_page), (void *) "page_tools"); + gtk_notebook_set_current_page (GTK_NOTEBOOK (nb_tools), prefs_get_int_attribute ("dialogs.preferences", "page_tools", 0)); + + +// Windows + vb = new_tab (nb, _("Windows")); + + options_dialogs_ontop (vb, tt); + +options_checkbox ( + _("Save window geometry"), + _("Save the window size and position with each document (only for Inkscape SVG format)"), tt, + vb, + "options.savewindowgeometry", "value", 1, + options_changed_boolean + ); + +options_checkbox ( + _("Dialogs are hidden in taskbar"), + _("Whether dialog windows are to be hidden in the window manager taskbar"), tt, + vb, + "options.dialogsskiptaskbar", "value", 1, + options_changed_boolean + ); + +options_checkbox ( + _("Zoom when window is resized"), + _("Zoom drawing when document window is resized, to keep the same area visible (this is the default which can be changed in any window using the button above the right scrollbar)"), tt, + vb, + "options.stickyzoom", "value", 0, + options_changed_boolean + ); + + +// Clones + vb = new_tab (nb, _("Clones")); + + // Clone compensation + { + GtkWidget *f = gtk_frame_new (_("When the original moves, its clones and linked offsets:")); + gtk_widget_show (f); + gtk_box_pack_start (GTK_BOX (vb), f, FALSE, FALSE, 0); + + GtkWidget *fb = gtk_vbox_new (FALSE, 0); + gtk_widget_show (fb); + gtk_container_add (GTK_CONTAINER (f), fb); + + gint compense = prefs_get_int_attribute ("options.clonecompensation", "value", SP_CLONE_COMPENSATION_PARALLEL); + + GtkWidget *b = + sp_select_context_add_radio ( + NULL, fb, tt, _("Move in parallel"), + _("Clones are translated by the same vector as their original."), + NULL, SP_CLONE_COMPENSATION_PARALLEL, true, + compense == SP_CLONE_COMPENSATION_PARALLEL, + options_clone_compensation_toggled + ); + + sp_select_context_add_radio ( + b, fb, tt, _("Stay unmoved"), + _("Clones preserve their positions when their original is moved."), + NULL, SP_CLONE_COMPENSATION_UNMOVED, true, + compense == SP_CLONE_COMPENSATION_UNMOVED, + options_clone_compensation_toggled + ); + + sp_select_context_add_radio ( + b, fb, tt, _("Move according to transform"), + _("Each clone moves according to the value of its transform= attribute. For example, a rotated clone will move in a different direction than its original."), + NULL, SP_CLONE_COMPENSATION_NONE, true, + compense == SP_CLONE_COMPENSATION_NONE, + options_clone_compensation_toggled + ); + } + + // Original deletion + { + GtkWidget *f = gtk_frame_new (_("When the original is deleted, its clones:")); + gtk_widget_show (f); + gtk_box_pack_start (GTK_BOX (vb), f, FALSE, FALSE, 0); + + GtkWidget *fb = gtk_vbox_new (FALSE, 0); + gtk_widget_show (fb); + gtk_container_add (GTK_CONTAINER (f), fb); + + gint orphans = prefs_get_int_attribute ("options.cloneorphans", "value", SP_CLONE_ORPHANS_UNLINK); + + GtkWidget *b = + sp_select_context_add_radio ( + NULL, fb, tt, _("Are unlinked"), _("Orphaned clones are converted to regular objects."), NULL, SP_CLONE_ORPHANS_UNLINK, true, + orphans == SP_CLONE_ORPHANS_UNLINK, + options_clone_orphans_toggled + ); + + sp_select_context_add_radio ( + b, fb, tt, _("Are deleted"), _("Orphaned clones are deleted along with their original."), NULL, SP_CLONE_ORPHANS_DELETE, true, + orphans == SP_CLONE_ORPHANS_DELETE, + options_clone_orphans_toggled + ); + +/* + sp_select_context_add_radio ( + b, fb, tt, _("Ask me"), _("Ask me what to do with the clones when their originals are deleted."), NULL, SP_CLONE_ORPHANS_ASKME, true, + orphans == SP_CLONE_ORPHANS_ASKME, + options_clone_orphans_toggled + ); +*/ + } + +// Transforms + /* TRANSLATORS: Noun, i.e. transformations. */ + vb = new_tab (nb, _("Transforms")); + +options_checkbox ( + _("Scale stroke width"), + _("When scaling objects, scale the stroke width by the same proportion"), tt, + vb, + "options.transform", "stroke", 1, + options_changed_boolean + ); + +options_checkbox ( + _("Scale rounded corners in rectangles"), + _("When scaling rectangles, scale the radii of rounded corners"), tt, + vb, + "options.transform", "rectcorners", 0, + options_changed_boolean + ); + +options_checkbox ( + _("Transform gradients"), + _("Transform gradients (in fill or stroke) along with the objects"), tt, + vb, + "options.transform", "gradient", 1, + options_changed_boolean + ); + +options_checkbox ( + _("Transform patterns"), + _("Transform patterns (in fill or stroke) along with the objects"), tt, + vb, + "options.transform", "pattern", 1, + options_changed_boolean + ); + + + // Store transformation (global) + { + /* TRANSLATORS: How to specify the affine transformation in the SVG file. */ + GtkWidget *f = gtk_frame_new (_("Store transformation:")); + gtk_widget_show (f); + gtk_box_pack_start (GTK_BOX (vb), f, FALSE, FALSE, 0); + + GtkWidget *fb = gtk_vbox_new (FALSE, 0); + gtk_widget_show (fb); + gtk_container_add (GTK_CONTAINER (f), fb); + + gint preserve = prefs_get_int_attribute ("options.preservetransform", "value", 0); + + GtkWidget *b = sp_select_context_add_radio ( + NULL, fb, tt, _("Optimized"), + _("If possible, apply transformation to objects without adding a transform= attribute"), + NULL, 0, true, + preserve == 0, + options_store_transform_toggled + ); + + sp_select_context_add_radio ( + b, fb, tt, _("Preserved"), + _("Always store transformation as a transform= attribute on objects"), + NULL, 1, true, + preserve != 0, + options_store_transform_toggled + ); + } + +// Selecting + vb = new_tab (nb, _("Selecting")); + + GtkWidget *f = gtk_frame_new (_("Ctrl+A, Tab, Shift+Tab:")); + gtk_widget_show (f); + gtk_box_pack_start (GTK_BOX (vb), f, FALSE, FALSE, 0); + + GtkWidget *fb = gtk_vbox_new (FALSE, 0); + gtk_widget_show (fb); + gtk_container_add (GTK_CONTAINER (f), fb); + +options_checkbox ( + _("Select only within current layer"), + _("Uncheck this to make keyboard selection commands work on objects in all layers"), tt, + fb, + "options.kbselection", "inlayer", 1, + options_changed_boolean + ); + +options_checkbox ( + _("Ignore hidden objects"), + _("Uncheck this to be able to select objects that are hidden (either by themselves or by being in a hidden group or layer)"), tt, + fb, + "options.kbselection", "onlyvisible", 1, + options_changed_boolean + ); + +options_checkbox ( + _("Ignore locked objects"), + _("Uncheck this to be able to select objects that are locked (either by themselves or by being in a locked group or layer)"), tt, + fb, + "options.kbselection", "onlysensitive", 1, + options_changed_boolean + ); + + +// To be broken into: Display, Save, Export, SVG, Commands + vb = new_tab (nb, _("Misc")); + + options_sb ( + _("Default export resolution:"), + _("Default bitmap resolution (in dots per inch) in the Export dialog"), tt, // FIXME: add "Used for new exports; once exported, documents remember this value on per-object basis" when implemented + _("dpi"), + vb, + 0.0, 6000.0, 1.0, 1.0, 1.0, + "dialogs.export.defaultxdpi", "value", PX_PER_IN, + true, false, + options_changed_int + ); + + options_checkbox ( + /* TRANSLATORS: When on, an imported bitmap creates an element; otherwise it is a + * rectangle with bitmap fill. */ + _("Import bitmap as "), + _("When on, an imported bitmap creates an element; otherwise it is a rectangle with bitmap fill"), tt, + vb, + "options.importbitmapsasimages", "value", 1, + options_changed_boolean + ); + + options_checkbox ( + /* TRANSLATORS: When on, the print out (currently Postscript) will have + * a comment with the each object's label visible, marking the section + * of the printing commands that represent the given object. */ + _("Add label comments to printing output"), + _("When on, a comment will be added to the raw print output, marking the rendered output for an object with its label"), tt, + vb, + "printing.debug", "show-label-comments", 0, + options_changed_boolean + ); + + options_checkbox ( + /* TRANSLATORS: When on, enable the effects menu, default is off */ + _("Enable script effects (requires restart) - EXPERIMENTAL"), + _("When on, the effects menu is enabled, allowing external effect scripts to be called, requires restart before effective - EXPERIMENTAL"), tt, + vb, + "extensions", "show-effects-menu", 0, + options_changed_boolean + ); + + options_sb ( + /* TRANSLATORS: The maximum length of the Open Recent list in the File menu. */ + _("Max recent documents:"), + _("The maximum length of the Open Recent list in the File menu"), tt, + "", + vb, + 0.0, 1000.0, 1.0, 1.0, 1.0, + "options.maxrecentdocuments", "value", 20.0, + true, false, + options_changed_int + ); + + options_sb ( + _("Simplification threshold:"), + _("How strong is the Simplify command by default. If you invoke this command several times in quick succession, it will act more and more aggressively; invoking it again after a pause restores the default threshold."), tt, + "", + vb, + 0.0, 1.0, 0.001, 0.01, 0.01, + "options.simplifythreshold", "value", 0.002, + false, false, + options_changed_double + ); + + + /* Oversample */ + hb = gtk_hbox_new (FALSE, HB_MARGIN); + gtk_widget_show (hb); + gtk_box_pack_start (GTK_BOX (vb), hb, FALSE, FALSE, 0); + + // empty label for alignment + l = gtk_label_new (""); + gtk_widget_set_size_request (l, SUFFIX_WIDTH, -1); + gtk_widget_show (l); + gtk_box_pack_end (GTK_BOX (hb), l, FALSE, FALSE, 0); + + l = gtk_label_new (_("Oversample bitmaps:")); + gtk_misc_set_alignment (GTK_MISC (l), 1.0, 0.5); + gtk_widget_show (l); + gtk_box_pack_start (GTK_BOX (hb), l, TRUE, TRUE, 0); + + om = gtk_option_menu_new (); + gtk_widget_set_size_request (om, SB_WIDTH, -1); + gtk_widget_show (om); + gtk_box_pack_end (GTK_BOX (hb), om, FALSE, FALSE, SB_MARGIN); + + m = gtk_menu_new (); + gtk_widget_show (m); + + i = gtk_menu_item_new_with_label (_("None")); + gtk_signal_connect ( GTK_OBJECT (i), "activate", + GTK_SIGNAL_FUNC (sp_display_dialog_set_oversample), + GINT_TO_POINTER (0) ); + gtk_widget_show (i); + gtk_menu_append (GTK_MENU (m), i); + i = gtk_menu_item_new_with_label (_("2x2")); + gtk_signal_connect ( GTK_OBJECT (i), "activate", + GTK_SIGNAL_FUNC (sp_display_dialog_set_oversample), + GINT_TO_POINTER (1) ); + gtk_widget_show (i); + gtk_menu_append (GTK_MENU (m), i); + i = gtk_menu_item_new_with_label (_("4x4")); + gtk_signal_connect ( GTK_OBJECT (i), "activate", + GTK_SIGNAL_FUNC (sp_display_dialog_set_oversample), + GINT_TO_POINTER (2) ); + gtk_widget_show (i); + gtk_menu_append (GTK_MENU (m), i); + i = gtk_menu_item_new_with_label (_("8x8")); + gtk_signal_connect ( GTK_OBJECT (i), "activate", + GTK_SIGNAL_FUNC (sp_display_dialog_set_oversample), + GINT_TO_POINTER (3)); + gtk_widget_show (i); + gtk_menu_append (GTK_MENU (m), i); + i = gtk_menu_item_new_with_label (_("16x16")); + gtk_signal_connect ( GTK_OBJECT (i), "activate", + GTK_SIGNAL_FUNC (sp_display_dialog_set_oversample), + GINT_TO_POINTER (4) ); + gtk_widget_show (i); + gtk_menu_append (GTK_MENU (m), i); + + gtk_option_menu_set_menu (GTK_OPTION_MENU (om), m); + + gtk_option_menu_set_history ( GTK_OPTION_MENU (om), + nr_arena_image_x_sample); + + g_signal_connect(GTK_OBJECT (nb), "switch-page", GTK_SIGNAL_FUNC (prefs_switch_page), (void *) "page_top"); + gtk_notebook_set_current_page (GTK_NOTEBOOK (nb), prefs_get_int_attribute ("dialogs.preferences", "page_top", 0)); + + } // end of if (!dlg) + + gtk_window_present ((GtkWindow *) dlg); + +} // end of sp_display_dialog() + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/dialogs/display-settings.h b/src/dialogs/display-settings.h new file mode 100644 index 000000000..2a28ea971 --- /dev/null +++ b/src/dialogs/display-settings.h @@ -0,0 +1,69 @@ +#ifndef __SP_DISPLAY_SETTINGS_H__ +#define __SP_DISPLAY_SETTINGS_H__ + +/** + * \brief Display settings dialog + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001 Ximian, Inc. + * + */ + +#include + +#include + +void sp_display_dialog ( void ); +void sp_display_dialog_apply ( GtkWidget * widget ); +void sp_display_dialog_close ( GtkWidget * widget ); + +// UPDATE THIS IF YOU'RE CHANGING OR REARRANGING PREFS PAGES. +// Otherwise the commands that open the dialog with a given page may become out of sync. + +enum { + PREFS_PAGE_MOUSE, + PREFS_PAGE_SCROLLING, + PREFS_PAGE_STEPS, + PREFS_PAGE_TOOLS, + PREFS_PAGE_WINDOWS, + PREFS_PAGE_CLONES, + PREFS_PAGE_TRANSFORMS, + PREFS_PAGE_SELECTING, + PREFS_PAGE_MISC +}; + +enum { + PREFS_PAGE_TOOLS_SELECTOR, + PREFS_PAGE_TOOLS_NODE, + PREFS_PAGE_TOOLS_ZOOM, + PREFS_PAGE_TOOLS_SHAPES, + PREFS_PAGE_TOOLS_PENCIL, + PREFS_PAGE_TOOLS_PEN, + PREFS_PAGE_TOOLS_CALLIGRAPHY, + PREFS_PAGE_TOOLS_TEXT, + PREFS_PAGE_TOOLS_GRADIENT, + PREFS_PAGE_TOOLS_CONNECTOR, + PREFS_PAGE_TOOLS_DROPPER +}; + +enum { + PREFS_PAGE_TOOLS_SHAPES_RECT, + PREFS_PAGE_TOOLS_SHAPES_ELLIPSE, + PREFS_PAGE_TOOLS_SHAPES_STAR, + PREFS_PAGE_TOOLS_SHAPES_SPIRAL +}; + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/dialogs/eek-preview.cpp b/src/dialogs/eek-preview.cpp new file mode 100644 index 000000000..74cf0abf7 --- /dev/null +++ b/src/dialogs/eek-preview.cpp @@ -0,0 +1,522 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Eek Preview Stuffs. + * + * The Initial Developer of the Original Code is + * Jon A. Cruz. + * Portions created by the Initial Developer are Copyright (C) 2005 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include +#include "eek-preview.h" + +#define PRIME_BUTTON_MAGIC_NUMBER 1 + +#define FOCUS_PROP_ID 1 + + + +static void eek_preview_class_init( EekPreviewClass *klass ); +static void eek_preview_init( EekPreview *preview ); + +static GtkWidgetClass* parent_class = 0; + +void eek_preview_set_color( EekPreview* preview, int r, int g, int b ) +{ + preview->_r = r; + preview->_g = g; + preview->_b = b; +} + + +GType eek_preview_get_type(void) +{ + static GType preview_type = 0; + + if (!preview_type) { + static const GTypeInfo preview_info = { + sizeof( EekPreviewClass ), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc)eek_preview_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof( EekPreview ), + 0, /* n_preallocs */ + (GInstanceInitFunc)eek_preview_init, + NULL /* value_table */ + }; + + + preview_type = g_type_register_static( GTK_TYPE_DRAWING_AREA, "EekPreview", &preview_info, (GTypeFlags)0 ); + } + + return preview_type; +} + +GtkWidget* eek_preview_area_new(void) +{ + return NULL; +} + +static void eek_preview_size_request( GtkWidget* widget, GtkRequisition* req ) +{ + gint width = 0; + gint height = 0; + EekPreview* preview = EEK_PREVIEW(widget); + gboolean worked = gtk_icon_size_lookup( preview->_size, &width, &height ); + if ( !worked ) { + width = 16; + height = 16; + } + if ( preview->_view == VIEW_TYPE_LIST ) { + width *= 3; + } + req->width = width; + req->height = height; +} + +enum { + CLICKED_SIGNAL, + ALTCLICKED_SIGNAL, + LAST_SIGNAL +}; + + +static guint eek_preview_signals[LAST_SIGNAL] = { 0 }; + + +gboolean eek_preview_expose_event( GtkWidget* widget, GdkEventExpose* event ) +{ +/* g_message("Exposed!!! %s", GTK_WIDGET_HAS_FOCUS(widget) ? "XXX" : "---" ); */ + gint insetX = 0; + gint insetY = 0; + +/* + gint lower = widget->allocation.width; + lower = (widget->allocation.height < lower) ? widget->allocation.height : lower; + if ( lower > 16 ) { + insetX++; + if ( lower > 18 ) { + insetX++; + if ( lower > 22 ) { + insetX++; + if ( lower > 24 ) { + insetX++; + if ( lower > 32 ) { + insetX++; + } + } + } + } + insetY = insetX; + } +*/ + + if ( GTK_WIDGET_DRAWABLE( widget ) ) { + GtkStyle* style = gtk_widget_get_style( widget ); + + if ( insetX > 0 || insetY > 0 ) { + gtk_paint_flat_box( style, + widget->window, + (GtkStateType)GTK_WIDGET_STATE(widget), + GTK_SHADOW_NONE, + NULL, + widget, + NULL, + 0, 0, + widget->allocation.width, widget->allocation.height); + } + + GdkGC *gc = gdk_gc_new( widget->window ); + EekPreview* preview = EEK_PREVIEW(widget); + GdkColor fg = {0, preview->_r, preview->_g, preview->_b}; + gdk_colormap_alloc_color( gdk_colormap_get_system(), &fg, FALSE, TRUE ); + + gdk_gc_set_foreground( gc, &fg ); + + gdk_draw_rectangle( widget->window, + gc, + TRUE, + insetX, insetY, + widget->allocation.width - (insetX * 2), widget->allocation.height - (insetY * 2) ); + + if ( GTK_WIDGET_HAS_FOCUS(widget) ) { + gtk_paint_focus( style, + widget->window, + GTK_STATE_NORMAL, + NULL, /* GdkRectangle *area, */ + widget, + NULL, + 0 + 1, 0 + 1, + widget->allocation.width - 2, widget->allocation.height - 2 ); + } + } + + + return FALSE; +} + + +static gboolean eek_preview_enter_cb( GtkWidget* widget, GdkEventCrossing* event ) +{ + if ( gtk_get_event_widget( (GdkEvent*)event ) == widget ) { + EekPreview* preview = EEK_PREVIEW(widget); + preview->_within = TRUE; + gtk_widget_set_state( widget, preview->_hot ? GTK_STATE_ACTIVE : GTK_STATE_PRELIGHT ); + } + return FALSE; +} + +static gboolean eek_preview_leave_cb( GtkWidget* widget, GdkEventCrossing* event ) +{ + if ( gtk_get_event_widget( (GdkEvent*)event ) == widget ) { + EekPreview* preview = EEK_PREVIEW(widget); + preview->_within = FALSE; + gtk_widget_set_state( widget, GTK_STATE_NORMAL ); + } + return FALSE; +} + +/* +static gboolean eek_preview_focus_in_event( GtkWidget* widget, GdkEventFocus* event ) +{ + g_message("focus IN"); + gboolean blip = parent_class->focus_in_event ? parent_class->focus_in_event(widget, event) : FALSE; + return blip; +} + +static gboolean eek_preview_focus_out_event( GtkWidget* widget, GdkEventFocus* event ) +{ + g_message("focus OUT"); + gboolean blip = parent_class->focus_out_event ? parent_class->focus_out_event(widget, event) : FALSE; + return blip; +} +*/ + +static gboolean eek_preview_button_press_cb( GtkWidget* widget, GdkEventButton* event ) +{ + if ( gtk_get_event_widget( (GdkEvent*)event ) == widget ) { + EekPreview* preview = EEK_PREVIEW(widget); + + if ( preview->_takesFocus && !GTK_WIDGET_HAS_FOCUS(widget) ) { + gtk_widget_grab_focus(widget); + } + + if ( event->button == PRIME_BUTTON_MAGIC_NUMBER ) { + preview->_hot = TRUE; + if ( preview->_within ) { + gtk_widget_set_state( widget, GTK_STATE_ACTIVE ); + } + } + } + + return FALSE; +} + +static gboolean eek_preview_button_release_cb( GtkWidget* widget, GdkEventButton* event ) +{ + if ( gtk_get_event_widget( (GdkEvent*)event ) == widget ) { + EekPreview* preview = EEK_PREVIEW(widget); + preview->_hot = FALSE; + gtk_widget_set_state( widget, GTK_STATE_NORMAL ); + if ( preview->_within && event->button == PRIME_BUTTON_MAGIC_NUMBER ) { + gboolean isAlt = (event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK; + + if ( isAlt ) { + g_signal_emit( widget, eek_preview_signals[ALTCLICKED_SIGNAL], 0, 2 ); + } else { + g_signal_emit( widget, eek_preview_signals[CLICKED_SIGNAL], 0 ); + } + } + } + return FALSE; +} + +gboolean eek_preview_key_press_event( GtkWidget* widget, GdkEventKey* event) +{ + g_message("TICK"); + return FALSE; +} + +gboolean eek_preview_key_release_event( GtkWidget* widget, GdkEventKey* event) +{ + g_message("tock"); + return FALSE; +} + +static void eek_preview_get_property( GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GObjectClass* gobjClass = G_OBJECT_CLASS(parent_class); + switch ( property_id ) { + case FOCUS_PROP_ID: + { + EekPreview* preview = EEK_PREVIEW( object ); + g_value_set_boolean( value, preview->_takesFocus ); + } + break; + default: + { + if ( gobjClass->get_property ) { + gobjClass->get_property( object, property_id, value, pspec ); + } + } + } +} + +static void eek_preview_set_property( GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GObjectClass* gobjClass = G_OBJECT_CLASS(parent_class); + switch ( property_id ) { + case FOCUS_PROP_ID: + { + EekPreview* preview = EEK_PREVIEW( object ); + gboolean val = g_value_get_boolean( value ); + if ( val != preview->_takesFocus ) { + preview->_takesFocus = val; + } + } + break; + default: + { + if ( gobjClass->set_property ) { + gobjClass->set_property( object, property_id, value, pspec ); + } + } + } +} + + +static gboolean eek_preview_popup_menu( GtkWidget* widget ) +{ +/* g_message("Do the popup!"); */ + gboolean blip = parent_class->popup_menu ? parent_class->popup_menu(widget) : FALSE; + return blip; +} + + +static void eek_preview_class_init( EekPreviewClass *klass ) +{ + GObjectClass* gobjClass = G_OBJECT_CLASS(klass); + /*GtkObjectClass* objectClass = (GtkObjectClass*)klass;*/ + GtkWidgetClass* widgetClass = (GtkWidgetClass*)klass; + + gobjClass->set_property = eek_preview_set_property; + gobjClass->get_property = eek_preview_get_property; + + /*objectClass->destroy = eek_preview_destroy;*/ + + parent_class = (GtkWidgetClass*)g_type_class_peek_parent( klass ); + + /*widgetClass->map = ;*/ + /*widgetClass->unmap = ;*/ + /*widgetClass->realize = ;*/ + /*widgetClass->unrealize = ;*/ + widgetClass->size_request = eek_preview_size_request; + /*widgetClass->size_allocate = ;*/ + /*widgetClass->state_changed = ;*/ + /*widgetClass->style_set = ;*/ + /*widgetClass->grab_notify = ;*/ + + widgetClass->button_press_event = eek_preview_button_press_cb; + widgetClass->button_release_event = eek_preview_button_release_cb; + /*widgetClass->delete_event = ;*/ + /*widgetClass->destroy_event = ;*/ + widgetClass->expose_event = eek_preview_expose_event; +/* widgetClass->key_press_event = eek_preview_key_press_event; */ +/* widgetClass->key_release_event = eek_preview_key_release_event; */ + widgetClass->enter_notify_event = eek_preview_enter_cb; + widgetClass->leave_notify_event = eek_preview_leave_cb; + /*widgetClass->configure_event = ;*/ + /*widgetClass->focus_in_event = eek_preview_focus_in_event;*/ + /*widgetClass->focus_out_event = eek_preview_focus_out_event;*/ + + /* selection */ + /*widgetClass->selection_get = ;*/ + /*widgetClass->selection_received = ;*/ + + + /* drag source: */ + /*widgetClass->drag_begin = ;*/ + /*widgetClass->drag_end = ;*/ + /*widgetClass->drag_data_get = ;*/ + /*widgetClass->drag_data_delete = ;*/ + + /* drag target: */ + /*widgetClass->drag_leave = ;*/ + /*widgetClass->drag_motion = ;*/ + /*widgetClass->drag_drop = ;*/ + /*widgetClass->drag_data_received = ;*/ + + /* For keybindings: */ + widgetClass->popup_menu = eek_preview_popup_menu; + /*widgetClass->show_help = ;*/ + + /* Accessibility support: */ + /*widgetClass->get_accessible = ;*/ + /*widgetClass->screen_changed = ;*/ + /*widgetClass->can_activate_accel = ;*/ + + + eek_preview_signals[CLICKED_SIGNAL] = + g_signal_new( "clicked", + G_TYPE_FROM_CLASS( klass ), + (GSignalFlags)(G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION), + G_STRUCT_OFFSET( EekPreviewClass, clicked ), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0 ); + + eek_preview_signals[ALTCLICKED_SIGNAL] = + g_signal_new( "alt-clicked", + G_TYPE_FROM_CLASS( klass ), + (GSignalFlags)(G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION), + G_STRUCT_OFFSET( EekPreviewClass, clicked ), + NULL, NULL, + g_cclosure_marshal_VOID__INT, G_TYPE_NONE, + 1, G_TYPE_INT ); + + + g_object_class_install_property( gobjClass, + FOCUS_PROP_ID, + g_param_spec_boolean( + "focus-on-click", + NULL, + "flag to grab focus when clicked", + TRUE, + (GParamFlags)(G_PARAM_READWRITE | G_PARAM_CONSTRUCT) + ) + ); +} + +gboolean eek_preview_get_focus_on_click( EekPreview* preview ) +{ + return preview->_takesFocus; +} + +void eek_preview_set_focus_on_click( EekPreview* preview, gboolean focus_on_click ) +{ + if ( focus_on_click != preview->_takesFocus ) { + preview->_takesFocus = focus_on_click; + } +} + +void eek_preview_set_details( EekPreview* preview, PreviewStyle prevstyle, ViewType view, GtkIconSize size ) +{ + preview->_prevstyle = prevstyle; + preview->_view = view; + preview->_size = size; + + gtk_widget_queue_draw(GTK_WIDGET(preview)); +} + +static void eek_preview_init( EekPreview *preview ) +{ + GtkWidget* widg = GTK_WIDGET(preview); + GTK_WIDGET_SET_FLAGS( widg, GTK_CAN_FOCUS ); + GTK_WIDGET_SET_FLAGS( widg, GTK_RECEIVES_DEFAULT ); + + gtk_widget_set_sensitive( widg, TRUE ); + + gtk_widget_add_events(widg, GDK_BUTTON_PRESS_MASK + | GDK_BUTTON_RELEASE_MASK + | GDK_KEY_PRESS_MASK + | GDK_KEY_RELEASE_MASK + | GDK_FOCUS_CHANGE_MASK + | GDK_ENTER_NOTIFY_MASK + | GDK_LEAVE_NOTIFY_MASK ); + +/* gtk_widget_add_events( widg, GDK_ALL_EVENTS_MASK );*/ + + preview->_r = 0x80; + preview->_g = 0x80; + preview->_b = 0xcc; + + preview->_hot = FALSE; + preview->_within = FALSE; + preview->_takesFocus = FALSE; + + preview->_prevstyle = PREVIEW_STYLE_ICON; + preview->_view = VIEW_TYPE_LIST; + preview->_size = GTK_ICON_SIZE_BUTTON; + +/* + GdkColor color = {0}; + color.red = (255 << 8) | 255; + + GdkColor whack = {0}; + whack.green = (255 << 8) | 255; + + gtk_widget_modify_bg( widg, GTK_STATE_NORMAL, &color ); + gtk_widget_modify_bg( widg, GTK_STATE_PRELIGHT, &whack ); +*/ + +/* GTK_STATE_ACTIVE, */ +/* GTK_STATE_PRELIGHT, */ +/* GTK_STATE_SELECTED, */ +/* GTK_STATE_INSENSITIVE */ + + if ( 0 ) { + GdkColor color = {0,0,0,0}; + + color.red = 0xffff; + color.green = 0; + color.blue = 0xffff; + gdk_colormap_alloc_color( gdk_colormap_get_system(), &color, FALSE, TRUE ); + gtk_widget_modify_bg(widg, GTK_STATE_ACTIVE, &color); + + color.red = 0; + color.green = 0xffff; + color.blue = 0; + gdk_colormap_alloc_color( gdk_colormap_get_system(), &color, FALSE, TRUE ); + gtk_widget_modify_bg(widg, GTK_STATE_SELECTED, &color); + + color.red = 0xffff; + color.green = 0; + color.blue = 0; + gdk_colormap_alloc_color( gdk_colormap_get_system(), &color, FALSE, TRUE ); + gtk_widget_modify_bg( widg, GTK_STATE_PRELIGHT, &color ); + } +} + + +GtkWidget* eek_preview_new(void) +{ + return GTK_WIDGET( g_object_new( EEK_PREVIEW_TYPE, NULL ) ); +} + diff --git a/src/dialogs/eek-preview.h b/src/dialogs/eek-preview.h new file mode 100644 index 000000000..1d4e445a5 --- /dev/null +++ b/src/dialogs/eek-preview.h @@ -0,0 +1,112 @@ +#ifndef SEEN_EEK_PREVIEW_H +#define SEEN_EEK_PREVIEW_H +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Eek Preview Stuffs. + * + * The Initial Developer of the Original Code is + * Jon A. Cruz. + * Portions created by the Initial Developer are Copyright (C) 2005 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include + +G_BEGIN_DECLS + + +#define EEK_PREVIEW_TYPE (eek_preview_get_type()) +#define EEK_PREVIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST( (obj), EEK_PREVIEW_TYPE, EekPreview)) +#define EEK_PREVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST( (klass), EEK_PREVIEW_TYPE, EekPreviewClass)) +#define IS_EEK_PREVIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE( (obj), EEK_PREVIEW_TYPE)) +#define IS_EEK_PREVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE( (klass), EEK_PREVIEW_TYPE)) +#define EEK_PREVIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS( (obj), EEK_PREVIEW_TYPE, EekPreviewClass)) + +typedef enum { + PREVIEW_STYLE_ICON = 0, + PREVIEW_STYLE_PREVIEW, + PREVIEW_STYLE_NAME, + PREVIEW_STYLE_BLURB, + PREVIEW_STYLE_ICON_NAME, + PREVIEW_STYLE_ICON_BLURB, + PREVIEW_STYLE_PREVIEW_NAME, + PREVIEW_STYLE_PREVIEW_BLURB +} PreviewStyle; + +typedef enum { + VIEW_TYPE_LIST = 0, + VIEW_TYPE_GRID +} ViewType; + + +typedef struct _EekPreview EekPreview; +typedef struct _EekPreviewClass EekPreviewClass; + + +struct _EekPreview +{ + GtkDrawingArea drawing; + + int _r; + int _g; + int _b; + + gboolean _hot; + gboolean _within; + gboolean _takesFocus; + + PreviewStyle _prevstyle; + ViewType _view; + GtkIconSize _size; +}; + +struct _EekPreviewClass +{ + GtkDrawingAreaClass parent_class; + + void (*clicked) (EekPreview* splat); +}; + + +GType eek_preview_get_type(void) G_GNUC_CONST; +GtkWidget* eek_preview_new(void); + +void eek_preview_set_details( EekPreview* splat, PreviewStyle prevstyle, ViewType view, GtkIconSize size ); +void eek_preview_set_color( EekPreview* splat, int r, int g, int b ); + +gboolean eek_preview_get_focus_on_click( EekPreview* preview ); +void eek_preview_set_focus_on_click( EekPreview* preview, gboolean focus_on_click ); + +G_END_DECLS + + +#endif /* SEEN_EEK_PREVIEW_H */ diff --git a/src/dialogs/export.cpp b/src/dialogs/export.cpp new file mode 100644 index 000000000..9f758bf9e --- /dev/null +++ b/src/dialogs/export.cpp @@ -0,0 +1,1753 @@ +#define __SP_EXPORT_C__ + +/** \file + * \brief PNG export dialog + */ + +/* + * Authors: + * Lauris Kaplinski + * bulia byak + * + * Copyright (C) 1999-2005 Authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include +#include "helper/unit-menu.h" +#include "helper/units.h" +#include "unit-constants.h" +#include "helper/window.h" +#include "inkscape-private.h" +#include "document.h" +#include "desktop-handles.h" +#include "sp-item.h" +#include "selection.h" +#include "file.h" +#include "macros.h" +#include "sp-namedview.h" + +#include "dialog-events.h" +#include "../prefs-utils.h" +#include "../verbs.h" +#include "../interface.h" + +#include "extension/output.h" +#include "extension/db.h" + +#include "io/sys.h" + + +#define SP_EXPORT_MIN_SIZE 1.0 + +#define DPI_BASE PX_PER_IN + +#define EXPORT_COORD_PRECISION 3 + +static void sp_export_area_toggled ( GtkToggleButton *tb, GtkObject *base ); +static void sp_export_export_clicked ( GtkButton *button, GtkObject *base ); +static void sp_export_browse_clicked ( GtkButton *button, gpointer userdata ); +static void sp_export_browse_store ( GtkButton *button, gpointer userdata ); + + +static void sp_export_area_x_value_changed ( GtkAdjustment *adj, + GtkObject *base); + +static void sp_export_area_y_value_changed ( GtkAdjustment *adj, + GtkObject *base); + +static void sp_export_area_width_value_changed ( GtkAdjustment *adj, + GtkObject *base); + +static void sp_export_area_height_value_changed ( GtkAdjustment *adj, + GtkObject *base); + +static void sp_export_bitmap_width_value_changed ( GtkAdjustment *adj, + GtkObject *base); + +static void sp_export_xdpi_value_changed ( GtkAdjustment *adj, + GtkObject *base); + +static void sp_export_selection_changed ( Inkscape::Application *inkscape, + Inkscape::Selection *selection, + GtkObject *base); +static void sp_export_selection_modified ( Inkscape::Application *inkscape, + Inkscape::Selection *selection, + guint flags, + GtkObject *base ); + +static void sp_export_set_area (GtkObject *base, double x0, double y0, double x1, double y1); +static void sp_export_value_set (GtkObject *base, const gchar *key, double val); +static void sp_export_value_set_px (GtkObject *base, const gchar *key, double val); +static float sp_export_value_get ( GtkObject *base, const gchar *key ); +static float sp_export_value_get_px ( GtkObject *base, const gchar *key ); + +static void sp_export_filename_modified (GtkObject * object, gpointer data); +static inline void sp_export_find_default_selection(GtkWidget * dlg); +static void sp_export_detect_size(GtkObject * base); + +static const gchar *prefs_path = "dialogs.export"; + +// these all need to be reinitialized to their defaults during dialog_destroy +static GtkWidget *dlg = NULL; +static win_data wd; +static gint x = -1000, y = -1000, w = 0, h = 0; // impossible original values to make sure they are read from prefs +static gchar * original_name = NULL; +static gchar * doc_export_name = NULL; +static bool was_empty = TRUE; + +/** What type of button is being pressed */ +enum selection_type { + SELECTION_PAGE = 0, /**< Export the whole page */ + SELECTION_DRAWING, /**< Export everything drawn on the page */ + SELECTION_SELECTION, /**< Export everything that is selected */ + SELECTION_CUSTOM, /**< Allows the user to set the region exported */ + SELECTION_NUMBER_OF /**< A counter for the number of these guys */ +}; + +/** A list of strings that is used both in the preferences, and in the + data fields to describe the various values of \c selection_type. */ +static const char * selection_names[SELECTION_NUMBER_OF] = { + "page", "drawing", "selection", "custom"}; + +/** The names on the buttons for the various selection types. */ +static const char * selection_labels[SELECTION_NUMBER_OF] = { + N_("_Page"), N_("_Drawing"), N_("_Selection"), N_("_Custom")}; + +static void +sp_export_dialog_destroy ( GtkObject *object, gpointer data ) +{ + sp_signal_disconnect_by_data (INKSCAPE, dlg); + + wd.win = dlg = NULL; + wd.stop = 0; + x = -1000; y = -1000; w = 0; h = 0; + g_free(original_name); + original_name = NULL; + g_free(doc_export_name); + doc_export_name = NULL; + was_empty = TRUE; + + return; +} // end of sp_export_dialog_destroy() + +/// Called when dialog is closed or inkscape is shut down. +static bool +sp_export_dialog_delete ( GtkObject *object, GdkEvent *event, gpointer data ) +{ + + gtk_window_get_position ((GtkWindow *) dlg, &x, &y); + gtk_window_get_size ((GtkWindow *) dlg, &w, &h); + + prefs_set_int_attribute (prefs_path, "x", x); + prefs_set_int_attribute (prefs_path, "y", y); + prefs_set_int_attribute (prefs_path, "w", w); + prefs_set_int_attribute (prefs_path, "h", h); + + return FALSE; // which means, go ahead and destroy it + +} // end of sp_export_dialog_delete() + +/** + \brief Creates a new spin button for the export dialog + \param key The name of the spin button + \param val A default value for the spin button + \param min Minimum value for the spin button + \param max Maximum value for the spin button + \param step The step size for the spin button + \param page Size of the page increment + \param us Unit selector that effects this spin button + \param t Table to put the spin button in + \param x X location in the table \c t to start with + \param y Y location in the table \c t to start with + \param ll Text to put on the left side of the spin button (optional) + \param lr Text to put on the right side of the spin button (optional) + \param digits Number of digits to display after the decimal + \param sensitive Whether the spin button is sensitive or not + \param cb Callback for when this spin button is changed (optional) + \param dlg Export dialog the spin button is being placed in + +*/ +static void +sp_export_spinbutton_new ( gchar *key, float val, float min, float max, + float step, float page, GtkWidget *us, + GtkWidget *t, int x, int y, + const gchar *ll, const gchar *lr, + int digits, unsigned int sensitive, + GCallback cb, GtkWidget *dlg ) +{ + GtkObject *a = gtk_adjustment_new (val, min, max, step, page, page); + gtk_object_set_data (a, "key", key); + gtk_object_set_data (GTK_OBJECT (dlg), (const gchar *)key, a); + + if (us) { + sp_unit_selector_add_adjustment ( SP_UNIT_SELECTOR (us), + GTK_ADJUSTMENT (a) ); + } + + int pos = 0; + + GtkWidget *l = NULL; + + if (ll) { + + l = gtk_label_new_with_mnemonic ((const gchar *)ll); + gtk_misc_set_alignment (GTK_MISC (l), 1.0, 0.5); + gtk_table_attach ( GTK_TABLE (t), l, x + pos, x + pos + 1, y, y + 1, + (GtkAttachOptions)0, (GtkAttachOptions)0, 0, 0 ); + gtk_widget_set_sensitive (l, sensitive); + pos += 1; + + } + + GtkWidget *sb = gtk_spin_button_new (GTK_ADJUSTMENT (a), 1.0, digits); + gtk_table_attach ( GTK_TABLE (t), sb, x + pos, x + pos + 1, y, y + 1, + (GtkAttachOptions)0, (GtkAttachOptions)0, 0, 0 ); + gtk_widget_set_size_request (sb, 80, -1); + gtk_widget_set_sensitive (sb, sensitive); + pos += 1; + + if (ll) { gtk_label_set_mnemonic_widget (GTK_LABEL(l), sb); } + + if (lr) { + + l = gtk_label_new_with_mnemonic ((const gchar *)lr); + gtk_misc_set_alignment (GTK_MISC (l), 0.0, 0.5); + gtk_table_attach ( GTK_TABLE (t), l, x + pos, x + pos + 1, y, y + 1, + (GtkAttachOptions)0, (GtkAttachOptions)0, 0, 0 ); + gtk_widget_set_sensitive (l, sensitive); + pos += 1; + + gtk_label_set_mnemonic_widget (GTK_LABEL(l), sb); + } + + if (cb) + gtk_signal_connect (a, "value_changed", cb, dlg); + + return; +} // end of sp_export_spinbutton_new() + + +static GtkWidget * +sp_export_dialog_area_frame (GtkWidget * dlg) +{ + GtkWidget * f, * t, * hb, * b, * us, * l, * vb, * unitbox; + + f = gtk_frame_new (_("Export area")); + vb = gtk_vbox_new (FALSE, 2); + gtk_container_add (GTK_CONTAINER (f), vb); + + /* Units box */ + unitbox = gtk_hbox_new (FALSE, 0); + gtk_container_set_border_width (GTK_CONTAINER (unitbox), 4); + /* gets added to the vbox later, but the unit selector is needed + earlier than that */ + + us = sp_unit_selector_new (SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE); + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop) + sp_unit_selector_set_unit (SP_UNIT_SELECTOR(us), SP_DT_NAMEDVIEW(desktop)->doc_units); + gtk_box_pack_end (GTK_BOX (unitbox), us, FALSE, FALSE, 0); + l = gtk_label_new (_("Units:")); + gtk_box_pack_end (GTK_BOX (unitbox), l, FALSE, FALSE, 3); + gtk_object_set_data (GTK_OBJECT (dlg), "units", us); + + hb = gtk_hbox_new (TRUE, 0); + gtk_container_set_border_width (GTK_CONTAINER (hb), 4); + gtk_box_pack_start(GTK_BOX(vb), hb, FALSE, FALSE, 3); + + for (int i = 0; i < SELECTION_NUMBER_OF; i++) { + b = gtk_toggle_button_new_with_mnemonic (_(selection_labels[i])); + gtk_object_set_data (GTK_OBJECT (b), "key", GINT_TO_POINTER(i)); + gtk_object_set_data (GTK_OBJECT (dlg), selection_names[i], b); + gtk_box_pack_start (GTK_BOX (hb), b, FALSE, TRUE, 0); + gtk_signal_connect ( GTK_OBJECT (b), "clicked", + GTK_SIGNAL_FUNC (sp_export_area_toggled), dlg ); + } + + g_signal_connect ( G_OBJECT (INKSCAPE), "change_selection", + G_CALLBACK (sp_export_selection_changed), dlg ); + g_signal_connect ( G_OBJECT (INKSCAPE), "modify_selection", + G_CALLBACK (sp_export_selection_modified), dlg ); + g_signal_connect ( G_OBJECT (INKSCAPE), "activate_desktop", + G_CALLBACK (sp_export_selection_changed), dlg ); + + t = gtk_table_new (2, 6, FALSE); + gtk_box_pack_start(GTK_BOX(vb), t, FALSE, FALSE, 0); + gtk_table_set_row_spacings (GTK_TABLE (t), 4); + gtk_table_set_col_spacings (GTK_TABLE (t), 4); + gtk_container_set_border_width (GTK_CONTAINER (t), 4); + + sp_export_spinbutton_new ( "x0", 0.0, -1000000.0, 1000000.0, 0.1, 1.0, us, + t, 0, 0, _("_x0:"), NULL, EXPORT_COORD_PRECISION, 1, + G_CALLBACK ( sp_export_area_x_value_changed), + dlg ); + + sp_export_spinbutton_new ( "x1", 0.0, -1000000.0, 1000000.0, 0.1, 1.0, us, + t, 2, 0, _("x_1:"), NULL, EXPORT_COORD_PRECISION, 1, + G_CALLBACK (sp_export_area_x_value_changed), + dlg ); + + sp_export_spinbutton_new ( "width", 0.0, -1000000.0, 1000000.0, 0.1, 1.0, + us, t, 4, 0, _("Width:"), NULL, EXPORT_COORD_PRECISION, 1, + G_CALLBACK + (sp_export_area_width_value_changed), + dlg ); + + sp_export_spinbutton_new ( "y0", 0.0, -1000000.0, 1000000.0, 0.1, 1.0, us, + t, 0, 1, _("_y0:"), NULL, EXPORT_COORD_PRECISION, 1, + G_CALLBACK (sp_export_area_y_value_changed), + dlg ); + + sp_export_spinbutton_new ( "y1", 0.0, -1000000.0, 1000000.0, 0.1, 1.0, us, + t, 2, 1, _("y_1:"), NULL, EXPORT_COORD_PRECISION, 1, + G_CALLBACK (sp_export_area_y_value_changed), + dlg ); + + sp_export_spinbutton_new ( "height", 0.0, -1000000.0, 1000000.0, 0.1, 1.0, + us, t, 4, 1, _("Height:"), NULL, EXPORT_COORD_PRECISION, 1, + G_CALLBACK (sp_export_area_height_value_changed), + dlg ); + + /* Adding in the unit box */ + gtk_box_pack_start(GTK_BOX(vb), unitbox, FALSE, FALSE, 0); + + return f; +} // end of sp_export_dialog_area_frame + + +void +sp_export_dialog (void) +{ + if (!dlg) { + GtkWidget *vb, *hb; + + gchar title[500]; + sp_ui_dialog_title_string (Inkscape::Verb::get(SP_VERB_FILE_EXPORT), title); + + dlg = sp_window_new (title, TRUE); + + if (x == -1000 || y == -1000) { + x = prefs_get_int_attribute (prefs_path, "x", 0); + y = prefs_get_int_attribute (prefs_path, "y", 0); + } + + if (w ==0 || h == 0) { + w = prefs_get_int_attribute (prefs_path, "w", 0); + h = prefs_get_int_attribute (prefs_path, "h", 0); + } + + if (x != 0 || y != 0) { + gtk_window_move ((GtkWindow *) dlg, x, y); + } else { + gtk_window_set_position(GTK_WINDOW(dlg), GTK_WIN_POS_CENTER); + } + + if (w && h) + gtk_window_resize ((GtkWindow *) dlg, w, h); + + sp_transientize (dlg); + wd.win = dlg; + wd.stop = 0; + + g_signal_connect ( G_OBJECT (INKSCAPE), "activate_desktop", + G_CALLBACK (sp_transientize_callback), &wd); + + gtk_signal_connect ( GTK_OBJECT (dlg), "event", + GTK_SIGNAL_FUNC (sp_dialog_event_handler), dlg); + + gtk_signal_connect ( GTK_OBJECT (dlg), "destroy", + G_CALLBACK (sp_export_dialog_destroy), dlg); + + gtk_signal_connect ( GTK_OBJECT (dlg), "delete_event", + G_CALLBACK (sp_export_dialog_delete), dlg); + + g_signal_connect ( G_OBJECT (INKSCAPE), "shut_down", + G_CALLBACK (sp_export_dialog_delete), dlg); + + g_signal_connect ( G_OBJECT (INKSCAPE), "dialogs_hide", + G_CALLBACK (sp_dialog_hide), dlg); + + g_signal_connect ( G_OBJECT (INKSCAPE), "dialogs_unhide", + G_CALLBACK (sp_dialog_unhide), dlg); + + GtkTooltips *tt = gtk_tooltips_new(); + + vb = gtk_vbox_new (FALSE, 4); + gtk_container_set_border_width (GTK_CONTAINER (vb), 0); + gtk_container_add (GTK_CONTAINER (dlg), vb); + + /* Export area frame */ + { + GtkWidget *f = sp_export_dialog_area_frame(dlg); + gtk_box_pack_start (GTK_BOX (vb), f, FALSE, FALSE, 0); + } + + /* Bitmap size frame */ + { + GtkWidget *f = gtk_frame_new (_("Bitmap size")); + gtk_box_pack_start (GTK_BOX (vb), f, FALSE, FALSE, 0); + GtkWidget *t = gtk_table_new (2, 5, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (t), 4); + gtk_table_set_col_spacings (GTK_TABLE (t), 4); + gtk_container_set_border_width (GTK_CONTAINER (t), 4); + gtk_container_add (GTK_CONTAINER (f), t); + + sp_export_spinbutton_new ( "bmwidth", 16.0, 1.0, 1000000.0, 1.0, 10.0, + NULL, t, 0, 0, + _("_Width:"), _("pixels at"), 0, 1, + G_CALLBACK + (sp_export_bitmap_width_value_changed), + dlg ); + + sp_export_spinbutton_new ( "xdpi", + prefs_get_double_attribute + ( "dialogs.export.defaultxdpi", + "value", DPI_BASE), + 1.0, 9600.0, 0.1, 1.0, NULL, t, 3, 0, + NULL, _("dp_i"), 2, 1, + G_CALLBACK (sp_export_xdpi_value_changed), + dlg ); + + sp_export_spinbutton_new ( "bmheight", 16.0, 1.0, 1000000.0, 1, 10.0, + NULL, t, 0, 1, _("Height:"), _("pixels at"), + 0, 0, NULL, dlg ); + + /** \todo + * Needs fixing: there's no way to set ydpi currently, so we use + * the defaultxdpi value here, too... + */ + sp_export_spinbutton_new ( "ydpi", prefs_get_double_attribute + ( "dialogs.export.defaultxdpi", + "value", DPI_BASE), + 1.0, 9600.0, 0.1, 1.0, NULL, t, 3, 1, + NULL, _("dpi"), 2, 0, NULL, dlg ); + } + + /* File entry */ + { + GtkWidget *frame = gtk_frame_new (""); + GtkWidget *flabel = gtk_label_new_with_mnemonic (_("_Filename")); + gtk_frame_set_label_widget (GTK_FRAME(frame), flabel); + gtk_box_pack_start (GTK_BOX (vb), frame, FALSE, FALSE, 0); + + GtkWidget *fe = gtk_entry_new (); + + /* + * set the default filename to be that of the current path + document + * with .png extension + * + * One thing to notice here is that this filename may get + * overwritten, but it won't happen here. The filename gets + * written into the text field, but then the button to select + * the area gets set. In that code the filename can be changed + * if there are some with presidence in the document. So, while + * this code sets the name first, it may not be the one users + * really see. + */ + if (SP_ACTIVE_DOCUMENT && SP_DOCUMENT_URI (SP_ACTIVE_DOCUMENT)) + { + gchar *name; + SPDocument * doc = SP_ACTIVE_DOCUMENT; + const gchar *uri = SP_DOCUMENT_URI (doc); + Inkscape::XML::Node * repr = sp_document_repr_root(doc); + const gchar * text_extension = repr->attribute("inkscape:output_extension"); + Inkscape::Extension::Output * oextension = NULL; + + if (text_extension != NULL) { + oextension = dynamic_cast(Inkscape::Extension::db.get(text_extension)); + } + + if (oextension != NULL) { + gchar * old_extension = oextension->get_extension(); + if (g_str_has_suffix(uri, old_extension)) { + gchar * uri_copy; + gchar * extension_point; + gchar * final_name; + + uri_copy = g_strdup(uri); + extension_point = g_strrstr(uri_copy, old_extension); + extension_point[0] = '\0'; + + final_name = g_strconcat(uri_copy, ".png", NULL); + gtk_entry_set_text (GTK_ENTRY (fe), final_name); + + g_free(final_name); + g_free(uri_copy); + } + } else { + name = g_strconcat(uri, ".png", NULL); + gtk_entry_set_text (GTK_ENTRY (fe), name); + g_free(name); + } + + doc_export_name = g_strdup(gtk_entry_get_text(GTK_ENTRY(fe))); + } + g_signal_connect ( G_OBJECT (fe), "changed", + G_CALLBACK (sp_export_filename_modified), dlg); + + hb = gtk_hbox_new (FALSE, 5); + gtk_container_add (GTK_CONTAINER (frame), hb); + gtk_container_set_border_width (GTK_CONTAINER (hb), 4); + + { + GtkWidget *b = gtk_button_new_with_mnemonic (_("_Browse...")); + gtk_box_pack_end (GTK_BOX (hb), b, FALSE, FALSE, 4); + g_signal_connect ( G_OBJECT (b), "clicked", + G_CALLBACK (sp_export_browse_clicked), NULL ); + } + + gtk_box_pack_start (GTK_BOX (hb), fe, TRUE, TRUE, 0); + gtk_object_set_data (GTK_OBJECT (dlg), "filename", fe); + gtk_object_set_data (GTK_OBJECT (dlg), "filename-modified", (gpointer)FALSE); + original_name = g_strdup(gtk_entry_get_text (GTK_ENTRY (fe))); + // pressing enter in the filename field is the same as clicking export: + g_signal_connect ( G_OBJECT (fe), "activate", + G_CALLBACK (sp_export_export_clicked), dlg ); + // focus is in the filename initially: + gtk_widget_grab_focus (GTK_WIDGET (fe)); + + // mnemonic in frame label moves focus to filename: + gtk_label_set_mnemonic_widget (GTK_LABEL(flabel), fe); + } + + /* Buttons */ + hb = gtk_hbox_new (FALSE, 0); + gtk_box_pack_end (GTK_BOX (vb), hb, FALSE, FALSE, 0); + + { + GtkWidget *b = gtk_button_new (); + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup_with_mnemonic (GTK_LABEL(l), _(" _Export ")); + gtk_container_add (GTK_CONTAINER(b), l); + gtk_tooltips_set_tip (tt, b, _("Export the bitmap file with these settings"), NULL); + gtk_signal_connect ( GTK_OBJECT (b), "clicked", + GTK_SIGNAL_FUNC (sp_export_export_clicked), dlg ); + gtk_box_pack_end (GTK_BOX (hb), b, FALSE, FALSE, 0); + } + + gtk_widget_show_all (vb); + + } // end of if (!dlg) + + sp_export_find_default_selection(dlg); + + gtk_window_present ((GtkWindow *) dlg); + + return; +} // end of sp_export_dialog() + +static inline void +sp_export_find_default_selection(GtkWidget * dlg) +{ + selection_type key = SELECTION_NUMBER_OF; + + if ((SP_DT_SELECTION(SP_ACTIVE_DESKTOP))->isEmpty() == false) { + key = SELECTION_SELECTION; + } + + /* Try using the preferences */ + if (key == SELECTION_NUMBER_OF) { + const gchar *what = NULL; + int i = SELECTION_NUMBER_OF; + + what = prefs_get_string_attribute ("dialogs.export.exportarea", "value"); + + if (what != NULL) { + for (i = 0; i < SELECTION_NUMBER_OF; i++) { + if (!strcmp (what, selection_names[i])) { + break; + } + } + } + + key = (selection_type)i; + } + + if (key == SELECTION_NUMBER_OF) { + key = SELECTION_SELECTION; + } + + GtkWidget *button = (GtkWidget *)g_object_get_data(G_OBJECT(dlg), + selection_names[key]); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE); + + return; +} + + +/** + * \brief If selection changed or a different document activated, we must + * recalculate any chosen areas + * + */ +static void +sp_export_selection_changed ( Inkscape::Application *inkscape, + Inkscape::Selection *selection, + GtkObject *base ) +{ + selection_type current_key; + current_key = (selection_type)(GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(base), "selection-type"))); + + if ((current_key == SELECTION_DRAWING || current_key == SELECTION_PAGE) && + (SP_DT_SELECTION(SP_ACTIVE_DESKTOP))->isEmpty() == false && + was_empty) { + gtk_toggle_button_set_active + ( GTK_TOGGLE_BUTTON ( gtk_object_get_data (base, selection_names[SELECTION_SELECTION])), + TRUE ); + } + was_empty = (SP_DT_SELECTION(SP_ACTIVE_DESKTOP))->isEmpty(); + + current_key = (selection_type)(GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(base), "selection-type"))); + + if (inkscape && + SP_IS_INKSCAPE (inkscape) && + selection && + SELECTION_CUSTOM != current_key) { + GtkToggleButton * button; + button = (GtkToggleButton *)gtk_object_get_data(base, selection_names[current_key]); + sp_export_area_toggled(button, base); + } // end of if() + + return; +} // end of sp_export_selection_changed() + +static void +sp_export_selection_modified ( Inkscape::Application *inkscape, + Inkscape::Selection *selection, + guint flags, + GtkObject *base ) +{ + selection_type current_key; + current_key = (selection_type)(GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(base), "selection-type"))); + + switch (current_key) { + case SELECTION_DRAWING: + if ( SP_ACTIVE_DESKTOP ) { + SPDocument *doc; + NRRect bbox; + doc = SP_DT_DOCUMENT (SP_ACTIVE_DESKTOP); + sp_item_bbox_desktop (SP_ITEM (SP_DOCUMENT_ROOT (doc)), &bbox); + + if (!(bbox.x0 > bbox.x1 && bbox.y0 > bbox.y1)) { + sp_export_set_area (base, bbox.x0, bbox.y0, bbox.x1, bbox.y1); + } + } + break; + case SELECTION_SELECTION: + if ((SP_DT_SELECTION(SP_ACTIVE_DESKTOP))->isEmpty() == false) { + NRRect bbox; + (SP_DT_SELECTION (SP_ACTIVE_DESKTOP))->bounds(&bbox); + sp_export_set_area (base, bbox.x0, bbox.y0, bbox.x1, bbox.y1); + } + break; + default: + /* Do nothing for page or for custom */ + break; + } + + return; +} + +/// Called when one of the selection buttons was toggled. +static void +sp_export_area_toggled (GtkToggleButton *tb, GtkObject *base) +{ + if (gtk_object_get_data (base, "update")) + return; + + selection_type key, old_key; + key = (selection_type)(GPOINTER_TO_INT(gtk_object_get_data (GTK_OBJECT (tb), "key"))); + old_key = (selection_type)(GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(base), "selection-type"))); + + /* Ignore all "turned off" events unless we're the only active button */ + if (!gtk_toggle_button_get_active (tb) ) { + + /* Don't let the current selection be deactived - but rerun the + activate to allow the user to renew the values */ + if (key == old_key) { + gtk_toggle_button_set_active ( tb, TRUE ); + } + + return; + } + + /* Turn off the currently active button unless it's us */ + gtk_object_set_data(GTK_OBJECT(base), "selection-type", (gpointer)key); + + if (old_key != key) { + gtk_toggle_button_set_active + ( GTK_TOGGLE_BUTTON ( gtk_object_get_data (base, selection_names[old_key])), + FALSE ); + } + + if ( SP_ACTIVE_DESKTOP ) + { + SPDocument *doc; + NRRect bbox; + doc = SP_DT_DOCUMENT (SP_ACTIVE_DESKTOP); + + /* Notice how the switch is used to 'fall through' here to get + various backups. If you modify this without noticing you'll + probabaly screw something up. */ + switch (key) { + case SELECTION_SELECTION: + if ((SP_DT_SELECTION(SP_ACTIVE_DESKTOP))->isEmpty() == false) + { + (SP_DT_SELECTION (SP_ACTIVE_DESKTOP))->bounds(&bbox); + /* Only if there is a selection that we can set + do we break, otherwise we fall through to the + drawing */ + // std::cout << "Using selection: SELECTION" << std::endl; + key = SELECTION_SELECTION; + break; + } + case SELECTION_DRAWING: + /** \todo + * This returns wrong values if the document has a viewBox. + */ + sp_item_bbox_desktop (SP_ITEM (SP_DOCUMENT_ROOT (doc)), &bbox); + + /* If the drawing is valid, then we'll use it and break + otherwise we drop through to the page settings */ + if (!(bbox.x0 > bbox.x1 && bbox.y0 > bbox.y1)) { + // std::cout << "Using selection: DRAWING" << std::endl; + key = SELECTION_DRAWING; + break; + } + case SELECTION_PAGE: + bbox.x0 = 0.0; + bbox.y0 = 0.0; + bbox.x1 = sp_document_width (doc); + bbox.y1 = sp_document_height (doc); + // std::cout << "Using selection: PAGE" << std::endl; + key = SELECTION_PAGE; + break; + case SELECTION_CUSTOM: + default: + break; + } // switch + + // remember area setting + prefs_set_string_attribute ( "dialogs.export.exportarea", + "value", selection_names[key]); + + if (key != SELECTION_CUSTOM) { + sp_export_set_area (base, bbox.x0, bbox.y0, bbox.x1, bbox.y1); + } + + } // end of if ( SP_ACTIVE_DESKTOP ) + + + if (SP_ACTIVE_DESKTOP && !gtk_object_get_data(GTK_OBJECT(base), "filename-modified")) { + GtkWidget * file_entry; + const gchar * filename = NULL; + float xdpi = 0.0, ydpi = 0.0; + + file_entry = (GtkWidget *)gtk_object_get_data (base, "filename"); + + switch (key) { + case SELECTION_PAGE: + case SELECTION_DRAWING: { + SPDocument * doc = SP_ACTIVE_DOCUMENT; + Inkscape::XML::Node * repr = sp_document_repr_root(doc); + const gchar * dpi_string; + + filename = repr->attribute("inkscape:export-filename"); + + dpi_string = NULL; + dpi_string = repr->attribute("inkscape:export-xdpi"); + if (dpi_string != NULL) { + xdpi = atof(dpi_string); + } + + dpi_string = NULL; + dpi_string = repr->attribute("inkscape:export-ydpi"); + if (dpi_string != NULL) { + ydpi = atof(dpi_string); + } + + if (filename == NULL) { + if (doc_export_name != NULL) { + filename = g_strdup(doc_export_name); + } else { + filename = g_strdup(""); + } + } + + break; + } + case SELECTION_SELECTION: + if ((SP_DT_SELECTION(SP_ACTIVE_DESKTOP))->isEmpty() == false) { + const GSList * reprlst; + bool filename_search = TRUE; + bool xdpi_search = TRUE; + bool ydpi_search = TRUE; + + reprlst = SP_DT_SELECTION(SP_ACTIVE_DESKTOP)->reprList(); + for(; reprlst != NULL && + filename_search && + xdpi_search && + ydpi_search; + reprlst = reprlst->next) { + const gchar * dpi_string; + Inkscape::XML::Node * repr = (Inkscape::XML::Node *)reprlst->data; + + if (filename_search) { + filename = repr->attribute("inkscape:export-filename"); + if (filename != NULL) + filename_search = FALSE; + } + + if (xdpi_search) { + dpi_string = NULL; + dpi_string = repr->attribute("inkscape:export-xdpi"); + if (dpi_string != NULL) { + xdpi = atof(dpi_string); + xdpi_search = FALSE; + } + } + + if (ydpi_search) { + dpi_string = NULL; + dpi_string = repr->attribute("inkscape:export-ydpi"); + if (dpi_string != NULL) { + ydpi = atof(dpi_string); + ydpi_search = FALSE; + } + } + } + + /* If we still don't have a filename -- let's build + one that's nice */ + if (filename == NULL) { + const gchar * id = NULL; + reprlst = SP_DT_SELECTION(SP_ACTIVE_DESKTOP)->reprList(); + for(; reprlst != NULL; reprlst = reprlst->next) { + Inkscape::XML::Node * repr = (Inkscape::XML::Node *)reprlst->data; + if (repr->attribute("id")) { + id = repr->attribute("id"); + break; + } + } + if (id == NULL) /* This should never happen */ + id = "bitmap"; + + gchar * directory = NULL; + const gchar * file_entry_text; + + file_entry_text = gtk_entry_get_text(GTK_ENTRY(file_entry)); + if (directory == NULL && file_entry_text != NULL && file_entry_text[0] != '\0') { + // std::cout << "Directory from dialog" << std::endl; + directory = g_dirname(file_entry_text); + } + + if (directory == NULL) { + /* Grab document directory */ + if (SP_DOCUMENT_URI(SP_ACTIVE_DOCUMENT)) { + // std::cout << "Directory from document" << std::endl; + directory = g_dirname(SP_DOCUMENT_URI(SP_ACTIVE_DOCUMENT)); + } + } + + if (directory == NULL) { + // std::cout << "Home Directory" << std::endl; + directory = homedir_path(NULL); + } + + gchar * id_ext = g_strconcat(id, ".png", NULL); + filename = g_build_filename(directory, id_ext, NULL); + g_free(directory); + g_free(id_ext); + } + } + break; + case SELECTION_CUSTOM: + default: + break; + } + + if (filename != NULL) { + g_free(original_name); + original_name = g_strdup(filename); + gtk_entry_set_text(GTK_ENTRY(file_entry), filename); + } + + if (xdpi != 0.0) { + sp_export_value_set(base, "xdpi", xdpi); + } + + /* These can't be seperate, and setting x sets y, so for + now setting this is disabled. Hopefully it won't be in + the future */ + if (FALSE && ydpi != 0.0) { + sp_export_value_set(base, "ydpi", ydpi); + } + } + + return; +} // end of sp_export_area_toggled() + +/// Called when dialog is deleted +static gint +sp_export_progress_delete ( GtkWidget *widget, GdkEvent *event, GObject *base ) +{ + g_object_set_data (base, "cancel", (gpointer) 1); + return TRUE; +} // end of sp_export_progress_delete() + +/// Called when progress is cancelled +static void +sp_export_progress_cancel ( GtkWidget *widget, GObject *base ) +{ + g_object_set_data (base, "cancel", (gpointer) 1); +} // end of sp_export_progress_cancel() + +/// Called for every progress iteration +static unsigned int +sp_export_progress_callback (float value, void *data) +{ + GtkWidget *prg; + int evtcount; + + if (g_object_get_data ((GObject *) data, "cancel")) + return FALSE; + + prg = (GtkWidget *) g_object_get_data ((GObject *) data, "progress"); + gtk_progress_bar_set_fraction ((GtkProgressBar *) prg, value); + + evtcount = 0; + while ((evtcount < 16) && gdk_events_pending ()) { + gtk_main_iteration_do (FALSE); + evtcount += 1; + } + + gtk_main_iteration_do (FALSE); + + return TRUE; + +} // end of sp_export_progress_callback() + +/// Called when export button is clicked +static void +sp_export_export_clicked (GtkButton *button, GtkObject *base) +{ + if (!SP_ACTIVE_DESKTOP) return; + + GtkWidget *fe = (GtkWidget *)gtk_object_get_data(base, "filename"); + gchar const *filename = gtk_entry_get_text(GTK_ENTRY(fe)); + + float const x0 = sp_export_value_get_px(base, "x0"); + float const y0 = sp_export_value_get_px(base, "y0"); + float const x1 = sp_export_value_get_px(base, "x1"); + float const y1 = sp_export_value_get_px(base, "y1"); + float const xdpi = sp_export_value_get(base, "xdpi"); + float const ydpi = sp_export_value_get(base, "ydpi"); + int const width = int(sp_export_value_get(base, "bmwidth") + 0.5); + int const height = int(sp_export_value_get(base, "bmheight") + 0.5); + + if (filename == NULL || *filename == '\0') { + sp_ui_error_dialog(_("You have to enter a filename")); + return; + } + + if (!((x1 > x0) && (y1 > y0) && (width > 0) && (height > 0))) { + sp_ui_error_dialog (_("The chosen area to be exported is invalid")); + return; + } + + gchar *dirname = g_dirname(filename); + if ( dirname == NULL + || !Inkscape::IO::file_test(dirname, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) ) + { + gchar *safeDir = Inkscape::IO::sanitizeString(dirname); + gchar *error = g_strdup_printf(_("Directory %s does not exist or is not a directory.\n"), + safeDir); + sp_ui_error_dialog(error); + g_free(safeDir); + g_free(error); + g_free(dirname); + return; + } + g_free(dirname); + + SPNamedView *nv = SP_DT_NAMEDVIEW(SP_ACTIVE_DESKTOP); + GtkWidget *dlg, *prg, *btn; /* progressbar-stuff */ + char *fn; + gchar *text; + + dlg = gtk_dialog_new (); + gtk_window_set_title (GTK_WINDOW (dlg), _("Export in progress")); + prg = gtk_progress_bar_new (); + sp_transientize (dlg); + gtk_window_set_resizable (GTK_WINDOW (dlg), FALSE); + g_object_set_data ((GObject *) base, "progress", prg); + fn = g_path_get_basename (filename); + text = g_strdup_printf ( _("Exporting %s (%d x %d)"), + fn, width, height); + g_free (fn); + gtk_progress_bar_set_text ((GtkProgressBar *) prg, text); + g_free (text); + gtk_progress_bar_set_orientation ( (GtkProgressBar *) prg, + GTK_PROGRESS_LEFT_TO_RIGHT); + gtk_box_pack_start ((GtkBox *) ((GtkDialog *) dlg)->vbox, + prg, FALSE, FALSE, 4 ); + btn = gtk_dialog_add_button ( GTK_DIALOG (dlg), + GTK_STOCK_CANCEL, + GTK_RESPONSE_CANCEL ); + + g_signal_connect ( (GObject *) dlg, "delete_event", + (GCallback) sp_export_progress_delete, base); + g_signal_connect ( (GObject *) btn, "clicked", + (GCallback) sp_export_progress_cancel, base); + gtk_window_set_modal ((GtkWindow *) dlg, TRUE); + gtk_widget_show_all (dlg); + + /* Do export */ + if (!sp_export_png_file (SP_DT_DOCUMENT (SP_ACTIVE_DESKTOP), filename, + x0, y0, x1, y1, width, height, + nv->pagecolor, + sp_export_progress_callback, base)) { + gchar * error; + gchar * safeFile = Inkscape::IO::sanitizeString(filename); + error = g_strdup_printf(_("Could not export to filename %s.\n"), safeFile); + sp_ui_error_dialog(error); + g_free(safeFile); + g_free(error); + } + + /* Reset the filename so that it can be changed again by changing + selections and all that */ + g_free(original_name); + original_name = g_strdup(filename); + gtk_object_set_data (GTK_OBJECT (base), "filename-modified", (gpointer)FALSE); + + gtk_widget_destroy (dlg); + g_object_set_data (G_OBJECT (base), "cancel", (gpointer) 0); + + /* Setup the values in the document */ + switch ((selection_type)(GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(base), "selection-type")))) { + case SELECTION_PAGE: + case SELECTION_DRAWING: { + SPDocument * doc = SP_ACTIVE_DOCUMENT; + Inkscape::XML::Node * repr = sp_document_repr_root(doc); + bool modified = FALSE; + const gchar * temp_string; + + bool saved = sp_document_get_undo_sensitive(doc); + sp_document_set_undo_sensitive(doc, FALSE); + + temp_string = repr->attribute("inkscape:export-filename"); + if (temp_string == NULL || strcmp(temp_string, filename)) { + repr->setAttribute("inkscape:export-filename", filename); + modified = TRUE; + } + temp_string = repr->attribute("inkscape:export-xdpi"); + if (temp_string == NULL || xdpi != atof(temp_string)) { + sp_repr_set_svg_double(repr, "inkscape:export-xdpi", xdpi); + modified = TRUE; + } + temp_string = repr->attribute("inkscape:export-ydpi"); + if (temp_string == NULL || xdpi != atof(temp_string)) { + sp_repr_set_svg_double(repr, "inkscape:export-ydpi", ydpi); + modified = TRUE; + } + + if (modified) + repr->setAttribute("sodipodi:modified", "TRUE"); + sp_document_set_undo_sensitive(doc, saved); + break; + } + case SELECTION_SELECTION: { + const GSList * reprlst; + SPDocument * doc = SP_ACTIVE_DOCUMENT; + bool modified = FALSE; + + bool saved = sp_document_get_undo_sensitive(doc); + sp_document_set_undo_sensitive(doc, FALSE); + reprlst = SP_DT_SELECTION(SP_ACTIVE_DESKTOP)->reprList(); + + for(; reprlst != NULL; reprlst = reprlst->next) { + Inkscape::XML::Node * repr = (Inkscape::XML::Node *)reprlst->data; + const gchar * temp_string; + + if (repr->attribute("id") == NULL || + !(g_strrstr(filename, repr->attribute("id")) != NULL && + (!SP_DOCUMENT_URI(SP_ACTIVE_DOCUMENT) || + strcmp(g_dirname(filename), g_dirname(SP_DOCUMENT_URI(SP_ACTIVE_DOCUMENT))) == 0))) { + temp_string = repr->attribute("inkscape:export-filename"); + if (temp_string == NULL || strcmp(temp_string, filename)) { + repr->setAttribute("inkscape:export-filename", filename); + modified = TRUE; + } + } + temp_string = repr->attribute("inkscape:export-xdpi"); + if (temp_string == NULL || xdpi != atof(temp_string)) { + sp_repr_set_svg_double(repr, "inkscape:export-xdpi", xdpi); + modified = TRUE; + } + temp_string = repr->attribute("inkscape:export-ydpi"); + if (temp_string == NULL || xdpi != atof(temp_string)) { + sp_repr_set_svg_double(repr, "inkscape:export-ydpi", ydpi); + modified = TRUE; + } + } + + if (modified) { + Inkscape::XML::Node * repr = sp_document_repr_root(doc); + repr->setAttribute("sodipodi:modified", "TRUE"); + } + + sp_document_set_undo_sensitive(doc, saved); + break; + } + default: + break; + } + + + return; +} // end of sp_export_export_clicked() + +/// Called when Browse button is clicked +static void +sp_export_browse_clicked (GtkButton *button, gpointer userdata) +{ + GtkWidget *fs, *fe; + const gchar *filename; + + fs = gtk_file_selection_new (_("Select a filename for exporting")); + fe = (GtkWidget *)g_object_get_data (G_OBJECT (dlg), "filename"); + + sp_transientize (fs); + + gtk_window_set_modal(GTK_WINDOW (fs), true); + + filename = gtk_entry_get_text (GTK_ENTRY (fe)); + + if (*filename == '\0') { + filename = homedir_path(NULL); + } + + gtk_file_selection_set_filename (GTK_FILE_SELECTION (fs), filename); + + g_signal_connect ( GTK_OBJECT (GTK_FILE_SELECTION (fs)->ok_button), + "clicked", + G_CALLBACK (sp_export_browse_store), + (gpointer) fs ); + + g_signal_connect_swapped ( GTK_OBJECT (GTK_FILE_SELECTION (fs)->ok_button), + "clicked", + G_CALLBACK (gtk_widget_destroy), + (gpointer) fs ); + + g_signal_connect_swapped ( GTK_OBJECT + (GTK_FILE_SELECTION (fs)->cancel_button), + "clicked", + G_CALLBACK (gtk_widget_destroy), + (gpointer) fs ); + + gtk_widget_show (fs); + + return; +} // end of sp_export_browse_clicked() + +/// Called when OK clicked in file dialog +static void +sp_export_browse_store (GtkButton *button, gpointer userdata) +{ + GtkWidget *fs = (GtkWidget *)userdata, *fe; + const gchar *file; + + fe = (GtkWidget *)g_object_get_data (G_OBJECT (dlg), "filename"); + + file = gtk_file_selection_get_filename (GTK_FILE_SELECTION (fs)); + gchar * utf8file = g_filename_to_utf8( file, -1, NULL, NULL, NULL ); + gtk_entry_set_text (GTK_ENTRY (fe), utf8file); + g_free(utf8file); + + g_object_set_data (G_OBJECT (dlg), "filename", fe); + + return; +} // end of sp_export_browse_store() + +// TODO: Move this to nr-rect-fns.h. +static bool +sp_export_bbox_equal(NR::Rect const &one, NR::Rect const &two) +{ + double const epsilon = pow(10.0, -EXPORT_COORD_PRECISION); + return ( + (fabs(one.min()[NR::X] - two.min()[NR::X]) < epsilon) && + (fabs(one.min()[NR::Y] - two.min()[NR::Y]) < epsilon) && + (fabs(one.max()[NR::X] - two.max()[NR::X]) < epsilon) && + (fabs(one.max()[NR::Y] - two.max()[NR::Y]) < epsilon) + ); +} + +/** + \brief This function is used to detect the current selection setting + based on the values in the x0, y0, x1 and y0 fields. + \param base The export dialog itself + + One of the most confusing parts of this function is why the array + is built at the beginning. What needs to happen here is that we + should always check the current selection to see if it is the valid + one. While this is a performance improvement it is also a usability + one during the cases where things like selections and drawings match + size. This way buttons change less 'randomly' (atleast in the eyes + of the user). To do this an array is built where the current selection + type is placed first, and then the others in an order from smallest + to largest (this can be configured by reshuffling \c test_order). + + All of the values in this function are rounded to two decimal places + because that is what is shown to the user. While everything is kept + more accurate than that, the user can't control more acurrate than + that, so for this to work for them - it needs to check on that level + of accuracy. + + \todo finish writing this up +*/ +static void +sp_export_detect_size(GtkObject * base) { + static const selection_type test_order[SELECTION_NUMBER_OF] = {SELECTION_SELECTION, SELECTION_DRAWING, SELECTION_PAGE, SELECTION_CUSTOM}; + selection_type this_test[SELECTION_NUMBER_OF + 1]; + selection_type key = SELECTION_NUMBER_OF; + + NR::Point x(sp_export_value_get_px (base, "x0"), + sp_export_value_get_px (base, "y0")); + NR::Point y(sp_export_value_get_px (base, "x1"), + sp_export_value_get_px (base, "y1")); + NR::Rect current_bbox(x, y); + //std::cout << "Current " << current_bbox; + + this_test[0] = (selection_type)(GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(base), "selection-type"))); + for (int i = 0; i < SELECTION_NUMBER_OF; i++) { + this_test[i + 1] = test_order[i]; + } + + for (int i = 0; + i < SELECTION_NUMBER_OF + 1 && + key == SELECTION_NUMBER_OF && + SP_ACTIVE_DESKTOP != NULL; + i++) { + // std::cout << "Looking at: " << selection_names[this_test[i]] << std::endl; + switch (this_test[i]) { + case SELECTION_SELECTION: + if ((SP_DT_SELECTION(SP_ACTIVE_DESKTOP))->isEmpty() == false) { + NR::Rect bbox = (SP_DT_SELECTION (SP_ACTIVE_DESKTOP))->bounds(); + + //std::cout << "Selection " << bbox; + if (sp_export_bbox_equal(bbox,current_bbox)) { + key = SELECTION_SELECTION; + } + } + break; + case SELECTION_DRAWING: { + SPDocument *doc = SP_DT_DOCUMENT (SP_ACTIVE_DESKTOP); + + NR::Rect bbox = sp_item_bbox_desktop (SP_ITEM (SP_DOCUMENT_ROOT (doc))); + + // std::cout << "Drawing " << bbox2; + if (sp_export_bbox_equal(bbox,current_bbox)) { + key = SELECTION_DRAWING; + } + break; + } + + case SELECTION_PAGE: { + SPDocument *doc; + + doc = SP_DT_DOCUMENT (SP_ACTIVE_DESKTOP); + + NR::Point x(0.0, 0.0); + NR::Point y(sp_document_width(doc), + sp_document_height(doc)); + NR::Rect bbox(x, y); + + // std::cout << "Page " << bbox; + if (sp_export_bbox_equal(bbox,current_bbox)) { + key = SELECTION_PAGE; + } + + break; + } + default: + break; + } + } + // std::cout << std::endl; + + if (key == SELECTION_NUMBER_OF) { + key = SELECTION_CUSTOM; + } + + /* We're now using a custom size, not a fixed one */ + /* printf("Detecting state: %s\n", selection_names[key]); */ + selection_type old = (selection_type)(GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(base), "selection-type"))); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(gtk_object_get_data(base, selection_names[old])), FALSE); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(gtk_object_get_data(base, selection_names[key])), TRUE); + gtk_object_set_data(GTK_OBJECT(base), "selection-type", (gpointer)key); + + return; +} /* sp_export_detect_size */ + +/// Called when area x0 value is changed +static void +sp_export_area_x_value_changed (GtkAdjustment *adj, GtkObject *base) +{ + float x0, x1, xdpi, width; + + if (gtk_object_get_data (base, "update")) + return; + + if (sp_unit_selector_update_test ((SPUnitSelector *)gtk_object_get_data + (base, "units"))) + { + return; + } + + gtk_object_set_data ( base, "update", GUINT_TO_POINTER (TRUE) ); + + x0 = sp_export_value_get_px (base, "x0"); + x1 = sp_export_value_get_px (base, "x1"); + xdpi = sp_export_value_get (base, "xdpi"); + + width = floor ((x1 - x0) * xdpi / DPI_BASE + 0.5); + + if (width < SP_EXPORT_MIN_SIZE) { + const gchar *key; + width = SP_EXPORT_MIN_SIZE; + key = (const gchar *)gtk_object_get_data (GTK_OBJECT (adj), "key"); + + if (!strcmp (key, "x0")) { + x1 = x0 + width * DPI_BASE / xdpi; + sp_export_value_set_px (base, "x1", x1); + } else { + x0 = x1 - width * DPI_BASE / xdpi; + sp_export_value_set_px (base, "x0", x0); + } + } + + sp_export_value_set_px (base, "width", x1 - x0); + sp_export_value_set (base, "bmwidth", width); + + sp_export_detect_size(base); + + gtk_object_set_data (base, "update", GUINT_TO_POINTER (FALSE)); + + return; +} // end of sp_export_area_x_value_changed() + +/// Called when area y0 value is changed. +static void +sp_export_area_y_value_changed (GtkAdjustment *adj, GtkObject *base) +{ + float y0, y1, ydpi, height; + + if (gtk_object_get_data (base, "update")) + return; + + if (sp_unit_selector_update_test ((SPUnitSelector *)gtk_object_get_data + (base, "units"))) + { + return; + } + + gtk_object_set_data (base, "update", GUINT_TO_POINTER (TRUE)); + + y0 = sp_export_value_get_px (base, "y0"); + y1 = sp_export_value_get_px (base, "y1"); + ydpi = sp_export_value_get (base, "ydpi"); + + height = floor ((y1 - y0) * ydpi / DPI_BASE + 0.5); + + if (height < SP_EXPORT_MIN_SIZE) { + const gchar *key; + height = SP_EXPORT_MIN_SIZE; + key = (const gchar *)gtk_object_get_data (GTK_OBJECT (adj), "key"); + if (!strcmp (key, "y0")) { + y1 = y0 + height * DPI_BASE / ydpi; + sp_export_value_set_px (base, "y1", y1); + } else { + y0 = y1 - height * DPI_BASE / ydpi; + sp_export_value_set_px (base, "y0", y0); + } + } + + sp_export_value_set_px (base, "height", y1 - y0); + sp_export_value_set (base, "bmheight", height); + + sp_export_detect_size(base); + + gtk_object_set_data (base, "update", GUINT_TO_POINTER (FALSE)); + + return; +} // end of sp_export_area_y_value_changed() + +/// Called when x1-x0 or area width is changed +static void +sp_export_area_width_value_changed (GtkAdjustment *adj, GtkObject *base) +{ + float x0, x1, xdpi, width, bmwidth; + + if (gtk_object_get_data (base, "update")) + return; + + if (sp_unit_selector_update_test ((SPUnitSelector *)gtk_object_get_data + (base, "units"))) { + return; + } + + gtk_object_set_data (base, "update", GUINT_TO_POINTER (TRUE)); + + x0 = sp_export_value_get_px (base, "x0"); + x1 = sp_export_value_get_px (base, "x1"); + xdpi = sp_export_value_get (base, "xdpi"); + width = sp_export_value_get_px (base, "width"); + bmwidth = floor (width * xdpi / DPI_BASE + 0.5); + + if (bmwidth < SP_EXPORT_MIN_SIZE) { + + bmwidth = SP_EXPORT_MIN_SIZE; + width = bmwidth * DPI_BASE / xdpi; + sp_export_value_set_px (base, "width", width); + } + + sp_export_value_set_px (base, "x1", x0 + width); + sp_export_value_set (base, "bmwidth", bmwidth); + + gtk_object_set_data (base, "update", GUINT_TO_POINTER (FALSE)); + + return; +} // end of sp_export_area_width_value_changed() + +/// Called when y1-y0 or area height is changed. +static void +sp_export_area_height_value_changed (GtkAdjustment *adj, GtkObject *base) +{ + + float y0, y1, ydpi, height, bmheight; + + if (gtk_object_get_data (base, "update")) + return; + + if (sp_unit_selector_update_test ((SPUnitSelector *)gtk_object_get_data + (base, "units"))) { + return; + } + + gtk_object_set_data (base, "update", GUINT_TO_POINTER (TRUE)); + + y0 = sp_export_value_get_px (base, "y0"); + y1 = sp_export_value_get_px (base, "y1"); + ydpi = sp_export_value_get (base, "ydpi"); + height = sp_export_value_get_px (base, "height"); + bmheight = floor (height * ydpi / DPI_BASE + 0.5); + + if (bmheight < SP_EXPORT_MIN_SIZE) { + bmheight = SP_EXPORT_MIN_SIZE; + height = bmheight * DPI_BASE / ydpi; + sp_export_value_set_px (base, "height", height); + } + + sp_export_value_set_px (base, "y1", y0 + height); + sp_export_value_set (base, "bmheight", bmheight); + + gtk_object_set_data (base, "update", GUINT_TO_POINTER (FALSE)); + + return; +} // end of sp_export_area_height_value_changed() + +/** + \brief A function to set the ydpi + \param base The export dialog + + This function grabs all of the y values and then figures out the + new bitmap size based on the changing dpi value. The dpi value is + gotten from the xdpi setting as these can not currently be independent. +*/ +static void +sp_export_set_image_y (GtkObject *base) +{ + float y0, y1, xdpi; + + y0 = sp_export_value_get_px (base, "y0"); + y1 = sp_export_value_get_px (base, "y1"); + xdpi = sp_export_value_get (base, "xdpi"); + + sp_export_value_set (base, "ydpi", xdpi); + sp_export_value_set (base, "bmheight", (y1 - y0) * xdpi / DPI_BASE); + + return; +} // end of sp_export_set_image_y() + +/// Called when pixel width is changed +static void +sp_export_bitmap_width_value_changed (GtkAdjustment *adj, GtkObject *base) +{ + float x0, x1, bmwidth, xdpi; + + if (gtk_object_get_data (base, "update")) + return; + + if (sp_unit_selector_update_test ((SPUnitSelector *)gtk_object_get_data + (base, "units"))) { + return; + } + + gtk_object_set_data (base, "update", GUINT_TO_POINTER (TRUE)); + + x0 = sp_export_value_get_px (base, "x0"); + x1 = sp_export_value_get_px (base, "x1"); + bmwidth = sp_export_value_get (base, "bmwidth"); + + if (bmwidth < SP_EXPORT_MIN_SIZE) { + bmwidth = SP_EXPORT_MIN_SIZE; + sp_export_value_set (base, "bmwidth", bmwidth); + } + + xdpi = bmwidth * DPI_BASE / (x1 - x0); + sp_export_value_set (base, "xdpi", xdpi); + + sp_export_set_image_y (base); + + gtk_object_set_data (base, "update", GUINT_TO_POINTER (FALSE)); + + return; +} // end of sp_export_bitmap_width_value_changed() + +/** + \brief A function to adjust the bitmap width when the xdpi value changes + \param adj The adjustment that was changed + \param base The export dialog itself + + The first thing this function checks is to see if we are doing an + update. If we are, this function just returns because there is another + instance of it that will handle everything for us. If there is a + units change, we also assume that everyone is being updated appropriately + and there is nothing for us to do. + + If we're the highest level function, we set the update flag, and + continue on our way. + + All of the values are grabbed using the \c sp_export_value_get functions + (call to the _pt ones for x0 and x1 but just standard for xdpi). The + xdpi value is saved in the preferences for the next time the dialog + is opened. (does the selection dpi need to be set here?) + + A check is done to to ensure that we aren't outputing an invalid width, + this is set by SP_EXPORT_MIN_SIZE. If that is the case the dpi is + changed to make it valid. + + After all of this the bitmap width is changed. + + We also change the ydpi. This is a temporary hack as these can not + currently be independent. This is likely to change in the future. +*/ +void +sp_export_xdpi_value_changed (GtkAdjustment *adj, GtkObject *base) +{ + float x0, x1, xdpi, bmwidth; + + if (gtk_object_get_data (base, "update")) + return; + + if (sp_unit_selector_update_test ((SPUnitSelector *)gtk_object_get_data + (base, "units"))) { + return; + } + + gtk_object_set_data (base, "update", GUINT_TO_POINTER (TRUE)); + + x0 = sp_export_value_get_px (base, "x0"); + x1 = sp_export_value_get_px (base, "x1"); + xdpi = sp_export_value_get (base, "xdpi"); + + // remember xdpi setting + prefs_set_double_attribute ("dialogs.export.defaultxdpi", "value", xdpi); + + bmwidth = (x1 - x0) * xdpi / DPI_BASE; + + if (bmwidth < SP_EXPORT_MIN_SIZE) { + bmwidth = SP_EXPORT_MIN_SIZE; + if (x1 != x0) + xdpi = bmwidth * DPI_BASE / (x1 - x0); + else + xdpi = DPI_BASE; + sp_export_value_set (base, "xdpi", xdpi); + } + + sp_export_value_set (base, "bmwidth", bmwidth); + + sp_export_set_image_y (base); + + gtk_object_set_data (base, "update", GUINT_TO_POINTER (FALSE)); + + return; +} // end of sp_export_xdpi_value_changed() + + +/** + \brief A function to change the area that is used for the exported + bitmap. + \param base This is the export dialog + \param x0 Horizontal upper left hand corner of the picture in points + \param y0 Vertical upper left hand corner of the picture in points + \param x1 Horizontal lower right hand corner of the picture in points + \param y1 Vertical lower right hand corner of the picture in points + + This function just calls \c sp_export_value_set_px for each of the + parameters that is passed in. This allows for setting them all in + one convient area. + + Update is set to suspend all of the other test running while all the + values are being set up. This allows for a performance increase, but + it also means that the wrong type won't be detected with only some of + the values set. After all the values are set everyone is told that + there has been an update. +*/ +static void +sp_export_set_area ( GtkObject *base, double x0, double y0, double x1, double y1 ) +{ + gtk_object_set_data ( base, "update", GUINT_TO_POINTER (TRUE) ); + sp_export_value_set_px (base, "x1", x1); + sp_export_value_set_px (base, "y1", y1); + sp_export_value_set_px (base, "x0", x0); + sp_export_value_set_px (base, "y0", y0); + gtk_object_set_data ( base, "update", GUINT_TO_POINTER (FALSE) ); + + sp_export_area_x_value_changed ((GtkAdjustment *)gtk_object_get_data (base, "x1"), base); + sp_export_area_y_value_changed ((GtkAdjustment *)gtk_object_get_data (base, "y1"), base); + + return; +} + +/** + \brief Sets the value of an adjustment + \param base The export dialog + \param key Which adjustment to set + \param val What value to set it to + + This function finds the adjustment using the data stored in the + export dialog. After finding the adjustment it then sets + the value of it. +*/ +static void +sp_export_value_set ( GtkObject *base, const gchar *key, double val ) +{ + GtkAdjustment *adj; + + adj = (GtkAdjustment *)gtk_object_get_data (base, key); + + gtk_adjustment_set_value (adj, val); +} + +/** + \brief A function to set a value using the units points + \param base The export dialog + \param key Which value should be set + \param val What the value should be in points + + This function first gets the adjustment for the key that is passed + in. It then figures out what units are currently being used in the + dialog. After doing all of that, it then converts the incoming + value and sets the adjustment. +*/ +static void +sp_export_value_set_px (GtkObject *base, const gchar *key, double val) +{ + const SPUnit *unit = sp_unit_selector_get_unit ((SPUnitSelector *)gtk_object_get_data (base, "units") ); + + sp_export_value_set (base, key, sp_pixels_get_units (val, *unit)); + + return; +} + +/** + \brief Get the value of an adjustment in the export dialog + \param base The export dialog + \param key Which adjustment is being looked for + \return The value in the specified adjustment + + This function gets the adjustment from the data field in the export + dialog. It then grabs the value from the adjustment. +*/ +static float +sp_export_value_get ( GtkObject *base, const gchar *key ) +{ + GtkAdjustment *adj; + + adj = (GtkAdjustment *)gtk_object_get_data (base, key); + + return adj->value; +} // end of sp_export_value_get() + +/** + \brief Grabs a value in the export dialog and converts the unit + to points + \param base The export dialog + \param key Which value should be returned + \return The value in the adjustment in points + + This function, at its most basic, is a call to \c sp_export_value_get + to get the value of the adjustment. It then finds the units that + are being used by looking at the "units" attribute of the export + dialog. Using that it converts the returned value into points. +*/ +static float +sp_export_value_get_px ( GtkObject *base, const gchar *key ) +{ + float value = sp_export_value_get(base, key); + const SPUnit *unit = sp_unit_selector_get_unit ((SPUnitSelector *)gtk_object_get_data (base, "units")); + + return sp_units_get_pixels (value, *unit); +} // end of sp_export_value_get_px() + +/** + \brief This function is called when the filename is changed by + anyone. It resets the virgin bit. + \param object Text entry box + \param data The export dialog + \return None + + This function gets called when the text area is modified. It is + looking for the case where the text area is modified from its + original value. In that case it sets the "filename-modified" bit + to TRUE. If the text dialog returns back to the original text, the + bit gets reset. This should stop simple mistakes. +*/ +static void +sp_export_filename_modified (GtkObject * object, gpointer data) +{ + GtkWidget * text_entry = (GtkWidget *)object; + GtkWidget * export_dialog = (GtkWidget *)data; + + if (!strcmp(original_name, gtk_entry_get_text(GTK_ENTRY(text_entry)))) { + gtk_object_set_data (GTK_OBJECT (export_dialog), "filename-modified", (gpointer)FALSE); +// printf("Modified: FALSE\n"); + } else { + gtk_object_set_data (GTK_OBJECT (export_dialog), "filename-modified", (gpointer)TRUE); +// printf("Modified: TRUE\n"); + } + + return; +} // end sp_export_filename_modified + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/dialogs/export.h b/src/dialogs/export.h new file mode 100644 index 000000000..4fb6ce2a4 --- /dev/null +++ b/src/dialogs/export.h @@ -0,0 +1,24 @@ +#ifndef SP_EXPORT_H +#define SP_EXPORT_H + +/** + * \brief text-edit + * + * Text editing and font changes + * + */ + +void sp_export_dialog (void); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/dialogs/extensions.cpp b/src/dialogs/extensions.cpp new file mode 100644 index 000000000..36ec67744 --- /dev/null +++ b/src/dialogs/extensions.cpp @@ -0,0 +1,128 @@ +/* + * A simple dialog for previewing icon representation. + * + * Authors: + * Jon A. Cruz + * + * Copyright (C) 2005 Jon A. Cruz + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ +#ifdef HAVE_CONFIG_H +# include +#endif + +#include //for GTK_RESPONSE* types +#include + +#include "extension/db.h" +#include "extensions.h" + + +namespace Inkscape { +namespace UI { +namespace Dialogs { + +using Inkscape::Extension::Extension; + +ExtensionsPanel* ExtensionsPanel::instance = 0; + + +ExtensionsPanel& ExtensionsPanel::getInstance() +{ + if ( !instance ) { + instance = new ExtensionsPanel(); + } + + instance->rescan(); + + return *instance; +} + + + +/** + * Constructor + */ +ExtensionsPanel::ExtensionsPanel() : + _showAll(false) +{ + Gtk::ScrolledWindow* scroller = new Gtk::ScrolledWindow(); + + _view.set_editable(false); + + scroller->add(_view); + add(*scroller); + + rescan(); + + show_all_children(); +} + +void ExtensionsPanel::set_full(bool full) +{ + if ( full != _showAll ) { + _showAll = full; + rescan(); + } +} + +void ExtensionsPanel::listCB( Inkscape::Extension::Extension * in_plug, gpointer in_data ) +{ + ExtensionsPanel * self = (ExtensionsPanel*)in_data; + + const char* stateStr; + Extension::state_t state = in_plug->get_state(); + switch ( state ) { + case Extension::STATE_LOADED: + { + stateStr = "loaded"; + } + break; + case Extension::STATE_UNLOADED: + { + stateStr = "unloaded"; + } + break; + case Extension::STATE_DEACTIVATED: + { + stateStr = "deactivated"; + } + break; + default: + stateStr = "unknown"; + } + + if ( self->_showAll || in_plug->deactivated() ) { +// gchar* line = g_strdup_printf( " extension %c %c %s |%s|%s|", +// (in_plug->loaded() ? 'X' : '-'), +// (in_plug->deactivated() ? 'X' : '-'), +// stateStr, in_plug->get_id(), +// in_plug->get_name() ); + gchar* line = g_strdup_printf( "%s %s\n \"%s\"", stateStr, in_plug->get_name(), in_plug->get_id() ); + + self->_view.get_buffer()->insert( self->_view.get_buffer()->end(), line ); + self->_view.get_buffer()->insert( self->_view.get_buffer()->end(), "\n" ); + //g_message( "%s", line ); + } + + + + return; +} + +void ExtensionsPanel::rescan() +{ + _view.get_buffer()->set_text("Extensions:\n"); +// g_message("/------------------"); + + Inkscape::Extension::db.foreach(listCB, (gpointer)this); + +// g_message("\\------------------"); +} + + + +} //namespace Dialogs +} //namespace UI +} //namespace Inkscape diff --git a/src/dialogs/extensions.h b/src/dialogs/extensions.h new file mode 100644 index 000000000..f4dbada9e --- /dev/null +++ b/src/dialogs/extensions.h @@ -0,0 +1,61 @@ + +#ifndef SEEN_EXTENSIONS_H +#define SEEN_EXTENSIONS_H +/* + * A simple dialog for previewing icon representation. + * + * Authors: + * Jon A. Cruz + * + * Copyright (C) 2005 The Inkscape Organization + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include "ui/widget/panel.h" + +namespace Inkscape { + namespace Extension { + class Extension; + } +} + +namespace Inkscape { +namespace UI { +namespace Dialogs { + + +/** + * A panel that displays information about extensions. + */ +class ExtensionsPanel : public Inkscape::UI::Widget::Panel +{ +public: + ExtensionsPanel(); + + static ExtensionsPanel& getInstance(); + + void set_full(bool full); + +private: + ExtensionsPanel(ExtensionsPanel const &); // no copy + ExtensionsPanel &operator=(ExtensionsPanel const &); // no assign + + static ExtensionsPanel* instance; + + static void listCB( Inkscape::Extension::Extension * in_plug, gpointer in_data ); + + void rescan(); + + bool _showAll; + Gtk::TextView _view; +}; + +} //namespace Dialogs +} //namespace UI +} //namespace Inkscape + + + +#endif // SEEN_EXTENSIONS_H diff --git a/src/dialogs/filedialog-win32.cpp b/src/dialogs/filedialog-win32.cpp new file mode 100644 index 000000000..ca9ce5f6c --- /dev/null +++ b/src/dialogs/filedialog-win32.cpp @@ -0,0 +1,381 @@ + +#ifdef HAVE_CONFIG_H +# include +#endif +#include "filedialog.h" + +//#include "extension/internal/win32.h" + +#include + +#include + +#include +#include + +#define UNSAFE_SCRATCH_BUFFER_SIZE 4096 + +namespace Inkscape +{ +namespace UI +{ +namespace Dialogs +{ + +/*################################# +# U T I L I T Y +#################################*/ +static gboolean +win32_is_os_wide() +{ + static gboolean initialized = FALSE; + static gboolean is_wide = FALSE; + static OSVERSIONINFOA osver; + + if ( !initialized ) + { + BOOL result; + + initialized = TRUE; + + memset (&osver, 0, sizeof(OSVERSIONINFOA)); + osver.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA); + result = GetVersionExA (&osver); + if (result) + { + if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT) + is_wide = TRUE; + } + // If we can't even call to get the version, fall back to ANSI API + } + + return is_wide; +} + +/*################################# +# F I L E O P E N +#################################*/ + +struct FileOpenNativeData_def { + char *dir; + FileDialogType fileTypes; + char *title; +}; + +FileOpenDialog::FileOpenDialog( + const char *dir, FileDialogType fileTypes, const char *title) { + + nativeData = (FileOpenNativeData *) + g_malloc(sizeof (FileOpenNativeData)); + if ( !nativeData ) { + // do we want exceptions? + return; + } + + if ( !dir ) + dir = ""; + nativeData->dir = g_strdup(dir); + nativeData->fileTypes = fileTypes; + nativeData->title = g_strdup(title); + + extension = NULL; + filename = NULL; +} + + + +FileOpenDialog::~FileOpenDialog() { + + //do any cleanup here + if ( nativeData ) { + g_free(nativeData->dir); + g_free(nativeData->title); + g_free(nativeData); + } + + if (filename) g_free(filename); + extension = NULL; +} + + + +bool +FileOpenDialog::show() { + + if ( !nativeData ) { + //error + return FALSE; + } + + gint retval = FALSE; + + + //Jon's UNICODE patch + if ( win32_is_os_wide() ) { + gunichar2 fnbufW[UNSAFE_SCRATCH_BUFFER_SIZE * sizeof(gunichar2)] = {0}; + gunichar2* dirW = + g_utf8_to_utf16( nativeData->dir, -1, NULL, NULL, NULL ); + gunichar2 *filterW = (gunichar2 *) L""; + if ( nativeData->fileTypes == SVG_TYPES ) + filterW = (gunichar2 *) L"SVG files\0*.svg;*.svgz\0All files\0*\0"; + else if ( nativeData->fileTypes == IMPORT_TYPES ) + filterW = (gunichar2 *) L"Image files\0*.svg;*.png;*.jpg;*.jpeg;*.bmp;*.gif;*.tiff;*.xpm\0" + L"SVG files\0*.svg\0" + L"All files\0*\0"; + gunichar2* titleW = + g_utf8_to_utf16( nativeData->title, -1, NULL, NULL, NULL ); + OPENFILENAMEW ofn = { + sizeof (OPENFILENAMEW), + NULL, // hwndOwner + NULL, // hInstance + (const WCHAR *)filterW, // lpstrFilter + NULL, // lpstrCustomFilter + 0, // nMaxCustFilter + 1, // nFilterIndex + (WCHAR *)fnbufW, // lpstrFile + sizeof (fnbufW) / sizeof(WCHAR), // nMaxFile + NULL, // lpstrFileTitle + 0, // nMaxFileTitle + (const WCHAR *)dirW, // lpstrInitialDir + (const WCHAR *)titleW, // lpstrTitle + OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_NOCHANGEDIR, // Flags + 0, // nFileOffset + 0, // nFileExtension + NULL, // lpstrDefExt + 0, // lCustData + NULL, // lpfnHook + NULL // lpTemplateName + }; + + retval = GetOpenFileNameW (&ofn); + if (retval) + filename = g_utf16_to_utf8( fnbufW, -1, NULL, NULL, NULL ); + + g_free( dirW ); + g_free( titleW ); + + } else { + gchar *dir = nativeData->dir; + gchar *title = nativeData->title; + gchar fnbuf[UNSAFE_SCRATCH_BUFFER_SIZE] = {0}; + + gchar *filter = ""; + if ( nativeData->fileTypes == SVG_TYPES ) + filter = "SVG files\0*.svg;*.svgz\0All files\0*\0"; + else if ( nativeData->fileTypes == IMPORT_TYPES ) + filter = "Image files\0*.svg;*.png;*.jpg;*.jpeg;*.bmp;*.gif;*.tiff;*.xpm\0" + "SVG files\0*.svg\0" + "All files\0*\0"; + + OPENFILENAMEA ofn = { + sizeof (OPENFILENAMEA), + NULL, // hwndOwner + NULL, // hInstance + (const CHAR *)filter, // lpstrFilter + NULL, // lpstrCustomFilter + 0, // nMaxCustFilter + 1, // nFilterIndex + fnbuf, // lpstrFile + sizeof (fnbuf), // nMaxFile + NULL, // lpstrFileTitle + 0, // nMaxFileTitle + (const CHAR *)dir, // lpstrInitialDir + (const CHAR *)title, // lpstrTitle + OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_NOCHANGEDIR, // Flags + 0, // nFileOffset + 0, // nFileExtension + NULL, // lpstrDefExt + 0, // lCustData + NULL, // lpfnHook + NULL // lpTemplateName + }; + + retval = GetOpenFileNameA (&ofn); + if ( retval ) { + filename = g_strdup( fnbuf ); + /* ### We need to try something like this instead: + GError *err = NULL; + filename = g_filename_to_utf8(fnbuf, -1, NULL, NULL, &err); + if ( !filename && err ) { + g_warning("Charset conversion in show()[%d]%s\n", + err->code, err->message); + } + */ + } + } + + if ( !retval ) { + //int errcode = CommDlgExtendedError(); + return FALSE; + } + + return TRUE; + +} + + + +/*################################# +# F I L E S A V E +#################################*/ + +struct FileSaveNativeData_def { + OPENFILENAME ofn; + gchar filter[UNSAFE_SCRATCH_BUFFER_SIZE]; + gchar fnbuf[4096]; +}; + + + +FileSaveDialog::FileSaveDialog( + const char *dir, FileDialogType fileTypes, const char *title, const char * default_key) { + + nativeData = (FileSaveNativeData *) + g_malloc(sizeof (FileSaveNativeData)); + if ( !nativeData ) { + //do we want exceptions? + return; + } + + extension = NULL; + filename = NULL; + + int default_item = 0; + + GSList* extension_list = Inkscape::Extension::db.get_output_list(); + g_assert (extension_list != NULL); + + /* Make up the filter string for the save dialogue using the list + ** of available output types. + */ + + gchar *p = nativeData->filter; + int N = UNSAFE_SCRATCH_BUFFER_SIZE; + + int n = 1; + for (GSList* i = g_slist_next (extension_list); i != NULL; i = g_slist_next(i)) { + + Inkscape::Extension::DB::IOExtensionDescription* d = + reinterpret_cast(i->data); + + if (!d->sensitive) + continue; + + int w = snprintf (p, N, "%s", d->name); + N -= w + 1; + p += w + 1; + + w = snprintf (p, N, "*"); + N -= w + 1; + p += w + 1; + + g_assert (N >= 0); + + /* Look to see if this extension is the default */ + if (default_key && + d->extension->get_id() && + strcmp (default_key, d->extension->get_id()) == 0) { + default_item = n; + extension = d->extension; + } + + n++; + } + + *p = '\0'; + + nativeData->fnbuf[0] = '\0'; + + if (dir) { + /* We must check that dir is not something like + ** c:\foo\ (ie with a trailing \). If it is, + ** GetSaveFileName will give an error. + */ + int n = strlen(dir); + if (n > 0 && dir[n - 1] != '\\') { + strncpy(nativeData->fnbuf, dir, sizeof(nativeData->fnbuf)); + } + } + + OPENFILENAME ofn = { + sizeof (OPENFILENAME), + NULL, // hwndOwner + NULL, // hInstance + nativeData->filter, // lpstrFilter + NULL, // lpstrCustomFilter + 0, // nMaxCustFilter + default_item, // nFilterIndex + nativeData->fnbuf, // lpstrFile + sizeof (nativeData->fnbuf), // nMaxFile + NULL, // lpstrFileTitle + 0, // nMaxFileTitle + (const CHAR *)dir, // lpstrInitialDir + (const CHAR *)title, // lpstrTitle + OFN_HIDEREADONLY | OFN_NOCHANGEDIR, // Flags + 0, // nFileOffset + 0, // nFileExtension + NULL, // lpstrDefExt + 0, // lCustData + NULL, // lpfnHook + NULL // lpTemplateName + }; + + nativeData->ofn = ofn; +} + +FileSaveDialog::~FileSaveDialog() { + + //do any cleanup here + g_free(nativeData); + if (filename) g_free(filename); + extension = NULL; +} + +bool +FileSaveDialog::show() { + + if (!nativeData) + return FALSE; + int retval = GetSaveFileName (&(nativeData->ofn)); + if (!retval) { + //int errcode = CommDlgExtendedError(); + return FALSE; + } + + GSList* extension_list = Inkscape::Extension::db.get_output_list(); + g_assert (extension_list != NULL); + + /* Work out which extension corresponds to the user's choice of + ** file type. + */ + int n = nativeData->ofn.nFilterIndex - 1; + GSList* i = g_slist_next (extension_list); + + while (n > 0 && i) { + n--; + i = g_slist_next(i); + } + + Inkscape::Extension::DB::IOExtensionDescription* d = + reinterpret_cast(i->data); + + extension = d->extension; + + filename = g_strdup (nativeData->fnbuf); + return TRUE; +} + + + + + + + + +} //namespace Dialogs +} //namespace UI +} //namespace Inkscape + + + + diff --git a/src/dialogs/filedialog.cpp b/src/dialogs/filedialog.cpp new file mode 100644 index 000000000..bf9eaceb5 --- /dev/null +++ b/src/dialogs/filedialog.cpp @@ -0,0 +1,1439 @@ +/* + * Implementation of the file dialog interfaces defined in filedialog.h + * + * Authors: + * Bob Jamison + * Other dudes from The Inkscape Organization + * + * Copyright (C) 2004 The Inkscape Organization + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + + + +//Temporary ugly hack +//Remove these after the get_filter() calls in +//show() on both classes are fixed +#include + +//Another hack +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "prefs-utils.h" +#include +#include +#include +#include +#include "inkscape.h" +#include "svg-view-widget.h" +#include "filedialog.h" + +#undef INK_DUMP_FILENAME_CONV + +#ifdef INK_DUMP_FILENAME_CONV +void dump_str( const gchar* str, const gchar* prefix ); +void dump_ustr( const Glib::ustring& ustr ); +#endif + +namespace Inkscape { +namespace UI { +namespace Dialogs { + +void FileDialogExtensionToPattern (Glib::ustring &pattern, gchar * in_file_extension); + +/*######################################################################### +### SVG Preview Widget +#########################################################################*/ +/** + * Simple class for displaying an SVG file in the "preview widget." + * Currently, this is just a wrapper of the sp_svg_view Gtk widget. + * Hopefully we will eventually replace with a pure Gtkmm widget. + */ +class SVGPreview : public Gtk::VBox +{ +public: + SVGPreview(); + ~SVGPreview(); + + bool setDocument(SPDocument *doc); + + bool setFileName(Glib::ustring &fileName); + + bool setFromMem(char const *xmlBuffer); + + bool set(Glib::ustring &fileName, int dialogType); + + bool setURI(URI &uri); + + /** + * Show image embedded in SVG + */ + void showImage(Glib::ustring &fileName); + + /** + * Show the "No preview" image + */ + void showNoPreview(); + + /** + * Show the "Too large" image + */ + void showTooLarge(long fileLength); + +private: + /** + * The svg document we are currently showing + */ + SPDocument *document; + + /** + * The sp_svg_view widget + */ + GtkWidget *viewerGtk; + + /** + * are we currently showing the "no preview" image? + */ + bool showingNoPreview; + +}; + + +bool SVGPreview::setDocument(SPDocument *doc) +{ + if (document) + sp_document_unref(document); + + sp_document_ref(doc); + document = doc; + + //This should remove it from the box, and free resources + if (viewerGtk) { + gtk_widget_destroy(viewerGtk); + } + + viewerGtk = sp_svg_view_widget_new(doc); + GtkWidget *vbox = (GtkWidget *)gobj(); + gtk_box_pack_start(GTK_BOX(vbox), viewerGtk, TRUE, TRUE, 0); + gtk_widget_show(viewerGtk); + + return true; +} + +bool SVGPreview::setFileName(Glib::ustring &theFileName) +{ + Glib::ustring fileName = theFileName; + + fileName = Glib::filename_to_utf8(fileName); + + SPDocument *doc = sp_document_new (fileName.c_str(), 0); + if (!doc) { + g_warning("SVGView: error loading document '%s'\n", fileName.c_str()); + return false; + } + + setDocument(doc); + + sp_document_unref(doc); + + return true; +} + + + +bool SVGPreview::setFromMem(char const *xmlBuffer) +{ + if (!xmlBuffer) + return false; + + gint len = (gint)strlen(xmlBuffer); + SPDocument *doc = sp_document_new_from_mem(xmlBuffer, len, 0); + if (!doc) { + g_warning("SVGView: error loading buffer '%s'\n",xmlBuffer); + return false; + } + + setDocument(doc); + + sp_document_unref(doc); + + return true; +} + + + +void SVGPreview::showImage(Glib::ustring &theFileName) +{ + Glib::ustring fileName = theFileName; + + + /*##################################### + # LET'S HAVE SOME FUN WITH SVG! + # Instead of just loading an image, why + # don't we make a lovely little svg and + # display it nicely? + #####################################*/ + + //Arbitrary size of svg doc -- rather 'portrait' shaped + gint previewWidth = 400; + gint previewHeight = 600; + + //Get some image info. Smart pointer does not need to be deleted + Glib::RefPtr img = Gdk::Pixbuf::create_from_file(fileName); + gint imgWidth = img->get_width(); + gint imgHeight = img->get_height(); + + //Find the minimum scale to fit the image inside the preview area + double scaleFactorX = (0.9 *(double)previewWidth) / ((double)imgWidth); + double scaleFactorY = (0.9 *(double)previewHeight) / ((double)imgHeight); + double scaleFactor = scaleFactorX; + if (scaleFactorX > scaleFactorY) + scaleFactor = scaleFactorY; + + //Now get the resized values + gint scaledImgWidth = (int) (scaleFactor * (double)imgWidth); + gint scaledImgHeight = (int) (scaleFactor * (double)imgHeight); + + //center the image on the area + gint imgX = (previewWidth - scaledImgWidth) / 2; + gint imgY = (previewHeight - scaledImgHeight) / 2; + + //wrap a rectangle around the image + gint rectX = imgX-1; + gint rectY = imgY-1; + gint rectWidth = scaledImgWidth +2; + gint rectHeight = scaledImgHeight+2; + + //Our template. Modify to taste + gchar const *xformat = + "\n" + "\n" + "\n" + "\n" + "\n" + "%d x %d\n" + "\n\n"; + + //if (!Glib::get_charset()) //If we are not utf8 + fileName = Glib::filename_to_utf8(fileName); + + //Fill in the template + /* FIXME: Do proper XML quoting for fileName. */ + gchar *xmlBuffer = g_strdup_printf(xformat, + previewWidth, previewHeight, + imgX, imgY, scaledImgWidth, scaledImgHeight, + fileName.c_str(), + rectX, rectY, rectWidth, rectHeight, + imgWidth, imgHeight); + + //g_message("%s\n", xmlBuffer); + + //now show it! + setFromMem(xmlBuffer); + g_free(xmlBuffer); +} + + + +void SVGPreview::showNoPreview() +{ + //Are we already showing it? + if (showingNoPreview) + return; + + //Arbitrary size of svg doc -- rather 'portrait' shaped + gint previewWidth = 300; + gint previewHeight = 600; + + //Our template. Modify to taste + gchar const *xformat = + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + " \n" + "%s\n" + "\n\n"; + + //Fill in the template + gchar *xmlBuffer = g_strdup_printf(xformat, + previewWidth, previewHeight, _("No preview")); + + //g_message("%s\n", xmlBuffer); + + //now show it! + setFromMem(xmlBuffer); + g_free(xmlBuffer); + showingNoPreview = true; + +} + +void SVGPreview::showTooLarge(long fileLength) +{ + + //Arbitrary size of svg doc -- rather 'portrait' shaped + gint previewWidth = 300; + gint previewHeight = 600; + + //Our template. Modify to taste + gchar const *xformat = + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "%5.1f MB\n" + "%s\n" + "\n\n"; + + //Fill in the template + double floatFileLength = ((double)fileLength) / 1048576.0; + //printf("%ld %f\n", fileLength, floatFileLength); + gchar *xmlBuffer = g_strdup_printf(xformat, + previewWidth, previewHeight, floatFileLength, + _("too large for preview")); + + //g_message("%s\n", xmlBuffer); + + //now show it! + setFromMem(xmlBuffer); + g_free(xmlBuffer); + +} + +static bool +hasSuffix(Glib::ustring &str, Glib::ustring &ext) +{ + int strLen = str.length(); + int extLen = ext.length(); + if (extLen > strLen) + { + return false; + } + int strpos = strLen-1; + for (int extpos = extLen-1 ; extpos>=0 ; extpos--, strpos--) + { + Glib::ustring::value_type ch = str[strpos]; + if (ch != ext[extpos]) + { + if ( ((ch & 0xff80) != 0) || + static_cast( g_ascii_tolower( static_cast(0x07f & ch) ) ) != ext[extpos] ) + { + return false; + } + } + } + return true; +} + + +/** + * Return true if the image is loadable by Gdk, else false + */ +static bool +isValidImageFile(Glib::ustring &fileName) +{ + std::vectorformats = Gdk::Pixbuf::get_formats(); + for (unsigned int i=0; iextensions = format.get_extensions(); + for (unsigned int j=0; j 0x150000L) + { + showingNoPreview = false; + showTooLarge(fileLen); + return FALSE; + } + } + + Glib::ustring svg = ".svg"; + Glib::ustring svgz = ".svgz"; + + if ((dialogType == SVG_TYPES || dialogType == IMPORT_TYPES) && + (hasSuffix(fileName, svg) || hasSuffix(fileName, svgz) ) + ) + { + bool retval = setFileName(fileName); + showingNoPreview = false; + return retval; + } + else if (isValidImageFile(fileName)) + { + showImage(fileName); + showingNoPreview = false; + return true; + } + else + { + showNoPreview(); + return false; + } +} + + +SVGPreview::SVGPreview() +{ + if (!INKSCAPE) + inkscape_application_init("",false); + document = NULL; + viewerGtk = NULL; + set_size_request(150,150); + showingNoPreview = false; +} + +SVGPreview::~SVGPreview() +{ + +} + + + + + +/*######################################################################### +### F I L E O P E N +#########################################################################*/ + +/** + * Our implementation class for the FileOpenDialog interface.. + */ +class FileOpenDialogImpl : public FileOpenDialog, public Gtk::FileChooserDialog +{ +public: + FileOpenDialogImpl(char const *dir, + FileDialogType fileTypes, + char const *title); + + virtual ~FileOpenDialogImpl(); + + bool show(); + + Inkscape::Extension::Extension *getSelectionType(); + + gchar *getFilename(); + + Glib::SListHandle getFilenames (); +protected: + + + +private: + + + /** + * What type of 'open' are we? (open, import, place, etc) + */ + FileDialogType dialogType; + + /** + * Our svg preview widget + */ + SVGPreview svgPreview; + + /** + * Callback for seeing if the preview needs to be drawn + */ + void updatePreviewCallback(); + + /** + * Fix to allow the user to type the file name + */ + Gtk::Entry fileNameEntry; + + /** + * Create a filter menu for this type of dialog + */ + void createFilterMenu(); + + /** + * Callback for user input into fileNameEntry + */ + void fileNameEntryChangedCallback(); + + /** + * Callback for user changing which item is selected on the list + */ + void fileSelectedCallback(); + + + /** + * Filter name->extension lookup + */ + std::map extensionMap; + + /** + * The extension to use to write this file + */ + Inkscape::Extension::Extension *extension; + + /** + * Filename that was given + */ + Glib::ustring myFilename; + +}; + + + + + +/** + * Callback for checking if the preview needs to be redrawn + */ +void FileOpenDialogImpl::updatePreviewCallback() +{ + Glib::ustring fileName = get_preview_filename(); + + if (fileName.length() < 1) + return; + + svgPreview.set(fileName, dialogType); +} + + + + + +/** + * Callback for fileNameEntry widget + */ +void FileOpenDialogImpl::fileNameEntryChangedCallback() +{ + Glib::ustring fileName = fileNameEntry.get_text(); + + // TODO remove this leak + fileName = Glib::filename_from_utf8(fileName); + + //g_message("User hit return. Text is '%s'\n", fName.c_str()); + + if (!Glib::path_is_absolute(fileName)) { + //try appending to the current path + // not this way: fileName = get_current_folder() + "/" + fName; + std::vector pathSegments; + pathSegments.push_back( get_current_folder() ); + pathSegments.push_back( fileName ); + fileName = Glib::build_filename(pathSegments); + } + + //g_message("path:'%s'\n", fName.c_str()); + + if (Glib::file_test(fileName, Glib::FILE_TEST_IS_DIR)) { + set_current_folder(fileName); + } else if (Glib::file_test(fileName, Glib::FILE_TEST_IS_REGULAR)) { + //dialog with either (1) select a regular file or (2) cd to dir + //simulate an 'OK' + set_filename(fileName); + response(Gtk::RESPONSE_OK); + } +} + + + + + +/** + * Callback for fileNameEntry widget + */ +void FileOpenDialogImpl::fileSelectedCallback() +{ + Glib::ustring fileName = get_filename(); + if (!Glib::get_charset()) //If we are not utf8 + fileName = Glib::filename_to_utf8(fileName); + //g_message("User selected '%s'\n", + // filename().c_str()); + +#ifdef INK_DUMP_FILENAME_CONV + ::dump_ustr( get_filename() ); +#endif + fileNameEntry.set_text(fileName); +} + + + + +void FileOpenDialogImpl::createFilterMenu() +{ + //patterns added dynamically below + Gtk::FileFilter allImageFilter; + allImageFilter.set_name(_("All Images")); + extensionMap[Glib::ustring(_("All Images"))]=NULL; + add_filter(allImageFilter); + + Gtk::FileFilter allFilter; + allFilter.set_name(_("All Files")); + extensionMap[Glib::ustring(_("All Files"))]=NULL; + allFilter.add_pattern("*"); + add_filter(allFilter); + + //patterns added dynamically below + Gtk::FileFilter allInkscapeFilter; + allInkscapeFilter.set_name(_("All Inkscape Files")); + extensionMap[Glib::ustring(_("All Inkscape Files"))]=NULL; + add_filter(allInkscapeFilter); + + Inkscape::Extension::DB::InputList extension_list; + Inkscape::Extension::db.get_input_list(extension_list); + + for (Inkscape::Extension::DB::InputList::iterator current_item = extension_list.begin(); + current_item != extension_list.end(); current_item++) + { + Inkscape::Extension::Input * imod = *current_item; + + // FIXME: would be nice to grey them out instead of not listing them + if (imod->deactivated()) continue; + + Glib::ustring upattern("*"); + FileDialogExtensionToPattern (upattern, imod->get_extension()); + + Gtk::FileFilter filter; + Glib::ustring uname(_(imod->get_filetypename())); + filter.set_name(uname); + filter.add_pattern(upattern); + add_filter(filter); + extensionMap[uname] = imod; + + //g_message("ext %s:%s '%s'\n", ioext->name, ioext->mimetype, upattern.c_str()); + allInkscapeFilter.add_pattern(upattern); + if ( strncmp("image", imod->get_mimetype(), 5)==0 ) + allImageFilter.add_pattern(upattern); + } + + return; +} + + + +/** + * Constructor. Not called directly. Use the factory. + */ +FileOpenDialogImpl::FileOpenDialogImpl(char const *dir, + FileDialogType fileTypes, + char const *title) : + Gtk::FileChooserDialog(Glib::ustring(title)) +{ + + + /* One file at a time */ + /* And also Multiple Files */ + set_select_multiple(true); + + /* Initalize to Autodetect */ + extension = NULL; + /* No filename to start out with */ + myFilename = ""; + + /* Set our dialog type (open, import, etc...)*/ + dialogType = fileTypes; + + + /* Set the pwd and/or the filename */ + if (dir != NULL) + { + Glib::ustring udir(dir); + Glib::ustring::size_type len = udir.length(); + // leaving a trailing backslash on the directory name leads to the infamous + // double-directory bug on win32 + if (len != 0 && udir[len - 1] == '\\') udir.erase(len - 1); + set_current_folder(udir.c_str()); + } + + //###### Add the file types menu + createFilterMenu(); + + //###### Add a preview widget + set_preview_widget(svgPreview); + set_preview_widget_active(true); + set_use_preview_label (false); + + //Catch selection-changed events, so we can adjust the text widget + signal_update_preview().connect( + sigc::mem_fun(*this, &FileOpenDialogImpl::updatePreviewCallback) ); + + + //###### Add a text entry bar, and tie it to file chooser events + fileNameEntry.set_text(get_current_folder()); + set_extra_widget(fileNameEntry); + fileNameEntry.grab_focus(); + + //Catch when user hits [return] on the text field + fileNameEntry.signal_activate().connect( + sigc::mem_fun(*this, &FileOpenDialogImpl::fileNameEntryChangedCallback) ); + + //Catch selection-changed events, so we can adjust the text widget + signal_selection_changed().connect( + sigc::mem_fun(*this, &FileOpenDialogImpl::fileSelectedCallback) ); + + add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); + add_button(Gtk::Stock::OPEN, Gtk::RESPONSE_OK); + +} + + + + + +/** + * Public factory. Called by file.cpp, among others. + */ +FileOpenDialog *FileOpenDialog::create(char const *path, + FileDialogType fileTypes, + char const *title) +{ + FileOpenDialog *dialog = new FileOpenDialogImpl(path, fileTypes, title); + return dialog; +} + + + + +/** + * Destructor + */ +FileOpenDialogImpl::~FileOpenDialogImpl() +{ + +} + + +/** + * Show this dialog modally. Return true if user hits [OK] + */ +bool +FileOpenDialogImpl::show() +{ + set_current_folder(get_current_folder()); //hack to force initial dir listing + set_modal (TRUE); //Window + sp_transientize((GtkWidget *)gobj()); //Make transient + gint b = run(); //Dialog + hide(); + + if (b == Gtk::RESPONSE_OK) + { + //This is a hack, to avoid the warning messages that + //Gtk::FileChooser::get_filter() returns + //should be: Gtk::FileFilter *filter = get_filter(); + GtkFileChooser *gtkFileChooser = Gtk::FileChooser::gobj(); + GtkFileFilter *filter = gtk_file_chooser_get_filter(gtkFileChooser); + if (filter) + { + //Get which extension was chosen, if any + extension = extensionMap[gtk_file_filter_get_name(filter)]; + } + myFilename = get_filename(); + return TRUE; + } + else + { + return FALSE; + } +} + + + + +/** + * Get the file extension type that was selected by the user. Valid after an [OK] + */ +Inkscape::Extension::Extension * +FileOpenDialogImpl::getSelectionType() +{ + return extension; +} + + +/** + * Get the file name chosen by the user. Valid after an [OK] + */ +gchar * +FileOpenDialogImpl::getFilename (void) +{ + return g_strdup(myFilename.c_str()); +} + + +/** + * To Get Multiple filenames selected at-once. + */ +Glib::SListHandleFileOpenDialogImpl::getFilenames() +{ + return get_filenames(); +} + + + + + + +/*######################################################################### +# F I L E S A V E +#########################################################################*/ + +class FileType +{ + public: + FileType() {} + ~FileType() {} + Glib::ustring name; + Glib::ustring pattern; + Inkscape::Extension::Extension *extension; +}; + +/** + * Our implementation of the FileSaveDialog interface. + */ +class FileSaveDialogImpl : public FileSaveDialog, public Gtk::FileChooserDialog +{ + +public: + FileSaveDialogImpl(char const *dir, + FileDialogType fileTypes, + char const *title, + char const *default_key); + + virtual ~FileSaveDialogImpl(); + + bool show(); + + Inkscape::Extension::Extension *getSelectionType(); + + gchar *getFilename(); + + +private: + + /** + * What type of 'open' are we? (save, export, etc) + */ + FileDialogType dialogType; + + /** + * Our svg preview widget + */ + SVGPreview svgPreview; + + /** + * Fix to allow the user to type the file name + */ + Gtk::Entry *fileNameEntry; + + /** + * Callback for seeing if the preview needs to be drawn + */ + void updatePreviewCallback(); + + + + /** + * Allow the specification of the output file type + */ + Gtk::HBox fileTypeBox; + + /** + * Allow the specification of the output file type + */ + Gtk::ComboBoxText fileTypeComboBox; + + + /** + * Data mirror of the combo box + */ + std::vector fileTypes; + + //# Child widgets + Gtk::CheckButton fileTypeCheckbox; + + + /** + * Callback for user input into fileNameEntry + */ + void fileTypeChangedCallback(); + + /** + * Create a filter menu for this type of dialog + */ + void createFileTypeMenu(); + + + bool append_extension; + + /** + * The extension to use to write this file + */ + Inkscape::Extension::Extension *extension; + + /** + * Callback for user input into fileNameEntry + */ + void fileNameEntryChangedCallback(); + + /** + * Filename that was given + */ + Glib::ustring myFilename; +}; + + + + + + +/** + * Callback for checking if the preview needs to be redrawn + */ +void FileSaveDialogImpl::updatePreviewCallback() +{ + Glib::ustring fileName = get_preview_filename(); + if (!fileName.c_str()) + return; + bool retval = svgPreview.set(fileName, dialogType); + set_preview_widget_active(retval); +} + + + +/** + * Callback for fileNameEntry widget + */ +void FileSaveDialogImpl::fileNameEntryChangedCallback() +{ + if (!fileNameEntry) + return; + + Glib::ustring fileName = fileNameEntry->get_text(); + if (!Glib::get_charset()) //If we are not utf8 + fileName = Glib::filename_to_utf8(fileName); + + //g_message("User hit return. Text is '%s'\n", fileName.c_str()); + + if (!Glib::path_is_absolute(fileName)) { + //try appending to the current path + // not this way: fileName = get_current_folder() + "/" + fileName; + std::vector pathSegments; + pathSegments.push_back( get_current_folder() ); + pathSegments.push_back( fileName ); + fileName = Glib::build_filename(pathSegments); + } + + //g_message("path:'%s'\n", fileName.c_str()); + + if (Glib::file_test(fileName, Glib::FILE_TEST_IS_DIR)) { + set_current_folder(fileName); + } else if (/*Glib::file_test(fileName, Glib::FILE_TEST_IS_REGULAR)*/1) { + //dialog with either (1) select a regular file or (2) cd to dir + //simulate an 'OK' + set_filename(fileName); + response(Gtk::RESPONSE_OK); + } +} + + + +/** + * Callback for fileNameEntry widget + */ +void FileSaveDialogImpl::fileTypeChangedCallback() +{ + int sel = fileTypeComboBox.get_active_row_number(); + if (sel<0 || sel >= (int)fileTypes.size()) + return; + FileType type = fileTypes[sel]; + //g_message("selected: %s\n", type.name.c_str()); + Gtk::FileFilter filter; + filter.add_pattern(type.pattern); + set_filter(filter); +} + + + +void FileSaveDialogImpl::createFileTypeMenu() +{ + Inkscape::Extension::DB::OutputList extension_list; + Inkscape::Extension::db.get_output_list(extension_list); + + for (Inkscape::Extension::DB::OutputList::iterator current_item = extension_list.begin(); + current_item != extension_list.end(); current_item++) + { + Inkscape::Extension::Output * omod = *current_item; + + // FIXME: would be nice to grey them out instead of not listing them + if (omod->deactivated()) continue; + + FileType type; + type.name = (_(omod->get_filetypename())); + type.pattern = "*"; + FileDialogExtensionToPattern (type.pattern, omod->get_extension()); + type.extension= omod; + fileTypeComboBox.append_text(type.name); + fileTypes.push_back(type); + } + + //#Let user choose + FileType guessType; + guessType.name = _("Guess from extension"); + guessType.pattern = "*"; + guessType.extension = NULL; + fileTypeComboBox.append_text(guessType.name); + fileTypes.push_back(guessType); + + + fileTypeComboBox.set_active(0); + fileTypeChangedCallback(); //call at least once to set the filter +} + + +void findEntryWidgets(Gtk::Container *parent, std::vector &result) +{ + if (!parent) + return; + std::vector children = parent->get_children(); + for (unsigned int i=0; igobj(); + if (GTK_IS_ENTRY(wid)) + result.push_back((Gtk::Entry *)child); + else if (GTK_IS_CONTAINER(wid)) + findEntryWidgets((Gtk::Container *)child, result); + } + +} + +void findExpanderWidgets(Gtk::Container *parent, std::vector &result) +{ + if (!parent) + return; + std::vector children = parent->get_children(); + for (unsigned int i=0; igobj(); + if (GTK_IS_EXPANDER(wid)) + result.push_back((Gtk::Expander *)child); + else if (GTK_IS_CONTAINER(wid)) + findExpanderWidgets((Gtk::Container *)child, result); + } + +} + + +/** + * Constructor + */ +FileSaveDialogImpl::FileSaveDialogImpl(char const *dir, + FileDialogType fileTypes, + char const *title, + char const *default_key) : + Gtk::FileChooserDialog(Glib::ustring(title), + Gtk::FILE_CHOOSER_ACTION_SAVE) +{ + append_extension = (bool)prefs_get_int_attribute("dialogs.save_as", "append_extension", 1); + + /* One file at a time */ + set_select_multiple(false); + + /* Initalize to Autodetect */ + extension = NULL; + /* No filename to start out with */ + myFilename = ""; + + /* Set our dialog type (save, export, etc...)*/ + dialogType = fileTypes; + + /* Set the pwd and/or the filename */ + if (dir != NULL) + { + Glib::ustring udir(dir); + Glib::ustring::size_type len = udir.length(); + // leaving a trailing backslash on the directory name leads to the infamous + // double-directory bug on win32 + if (len != 0 && udir[len - 1] == '\\') udir.erase(len - 1); + set_current_folder(udir.c_str()); + } + + //###### Add the file types menu + //createFilterMenu(); + + //###### Do we want the .xxx extension automatically added? + fileTypeCheckbox.set_label(Glib::ustring(_("Append filename extension automatically"))); + fileTypeCheckbox.set_active(append_extension); + + fileTypeBox.pack_start(fileTypeCheckbox); + createFileTypeMenu(); + fileTypeComboBox.set_size_request(200,40); + fileTypeComboBox.signal_changed().connect( + sigc::mem_fun(*this, &FileSaveDialogImpl::fileTypeChangedCallback) ); + + fileTypeBox.pack_start(fileTypeComboBox); + + set_extra_widget(fileTypeBox); + //get_vbox()->pack_start(fileTypeBox, false, false, 0); + //get_vbox()->reorder_child(fileTypeBox, 2); + + //###### Add a preview widget + set_preview_widget(svgPreview); + set_preview_widget_active(true); + set_use_preview_label (false); + + //Catch selection-changed events, so we can adjust the text widget + signal_update_preview().connect( + sigc::mem_fun(*this, &FileSaveDialogImpl::updatePreviewCallback) ); + + + //Let's do some customization + fileNameEntry = NULL; + Gtk::Container *cont = get_toplevel(); + std::vector entries; + findEntryWidgets(cont, entries); + //g_message("Found %d entry widgets\n", entries.size()); + if (entries.size() >=1 ) + { + //Catch when user hits [return] on the text field + fileNameEntry = entries[0]; + fileNameEntry->signal_activate().connect( + sigc::mem_fun(*this, &FileSaveDialogImpl::fileNameEntryChangedCallback) ); + } + + //Let's do more customization + std::vector expanders; + findExpanderWidgets(cont, expanders); + //g_message("Found %d expander widgets\n", expanders.size()); + if (expanders.size() >=1 ) + { + //Always show the file list + Gtk::Expander *expander = expanders[0]; + expander->set_expanded(true); + } + + + //if (extension == NULL) + // checkbox.set_sensitive(FALSE); + + add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); + add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK); + + show_all_children(); +} + + + +/** + * Public factory method. Used in file.cpp + */ +FileSaveDialog *FileSaveDialog::create(char const *path, + FileDialogType fileTypes, + char const *title, + char const *default_key) +{ + FileSaveDialog *dialog = new FileSaveDialogImpl(path, fileTypes, title, default_key); + return dialog; +} + + + + + +/** + * Destructor + */ +FileSaveDialogImpl::~FileSaveDialogImpl() +{ +} + + + + +/** + * Show this dialog modally. Return true if user hits [OK] + */ +bool +FileSaveDialogImpl::show() +{ + set_current_folder(get_current_folder()); //hack to force initial dir listing + set_modal (TRUE); //Window + sp_transientize((GtkWidget *)gobj()); //Make transient + gint b = run(); //Dialog + hide(); + + if (b == Gtk::RESPONSE_OK) + { + int sel = fileTypeComboBox.get_active_row_number (); + if (sel>=0 && sel< (int)fileTypes.size()) + { + FileType &type = fileTypes[sel]; + extension = type.extension; + } + myFilename = get_filename(); + + /* + + // FIXME: Why do we have more code + + append_extension = checkbox.get_active(); + prefs_set_int_attribute("dialogs.save_as", "append_extension", append_extension); + prefs_set_string_attribute("dialogs.save_as", "default", + ( extension != NULL ? extension->get_id() : "" )); + */ + return TRUE; + } + else + { + return FALSE; + } +} + + +/** + * Get the file extension type that was selected by the user. Valid after an [OK] + */ +Inkscape::Extension::Extension * +FileSaveDialogImpl::getSelectionType() +{ + return extension; +} + + +/** + * Get the file name chosen by the user. Valid after an [OK] + */ +gchar * +FileSaveDialogImpl::getFilename() +{ + return g_strdup(myFilename.c_str()); +} + +/** + \brief A quick function to turn a standard extension into a searchable + pattern for the file dialogs + \param pattern The patter that the extension should be written to + \param in_file_extension The C string that represents the extension + + This function just goes through the string, and takes all characters + and puts a [] so that both are searched and shown in + the file dialog. This function edits the pattern string to make + this happen. +*/ +void +FileDialogExtensionToPattern (Glib::ustring &pattern, gchar * in_file_extension) +{ + Glib::ustring tmp(in_file_extension); + + for ( guint i = 0; i < tmp.length(); i++ ) { + Glib::ustring::value_type ch = tmp.at(i); + if ( Glib::Unicode::isalpha(ch) ) { + pattern += '['; + pattern += Glib::Unicode::toupper(ch); + pattern += Glib::Unicode::tolower(ch); + pattern += ']'; + } else { + pattern += ch; + } + } +} + +} //namespace Dialogs +} //namespace UI +} //namespace Inkscape + + +/* + 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/dialogs/filedialog.h b/src/dialogs/filedialog.h new file mode 100644 index 000000000..ab3615bf6 --- /dev/null +++ b/src/dialogs/filedialog.h @@ -0,0 +1,172 @@ +#ifndef __FILE_DIALOG_H__ +#define __FILE_DIALOG_H__ + +/** \file Defines classes FileOpenDialog, FileSaveDialog, + * and enums FileDialogType, FileDialogSelectionType. */ + +#include +#include + +namespace Inkscape { + +namespace Extension { +class Extension; +} + +namespace UI { +namespace Dialogs { + + +/** + * Used for setting filters and options, and + * reading them back from user selections. + */ +typedef enum { + SVG_TYPES, + IMPORT_TYPES, + EXPORT_TYPES + } FileDialogType; + +/** + * Used for returning the type selected in a SaveAs + */ +typedef enum { + SVG_NAMESPACE, + SVG_NAMESPACE_WITH_EXTENSIONS + } FileDialogSelectionType; + +/** + * Architecture-specific data + */ +typedef struct FileOpenNativeData_def FileOpenNativeData; + + +/** + * This class provides an implementation-independent API for + * file "Open" dialogs. Using a standard interface obviates the need + * for ugly #ifdefs in file open code + */ +class FileOpenDialog +{ +public: + + + /** + * Constructor .. do not call directly + * @param path the directory where to start searching + * @param fileTypes one of FileDialogTypes + * @param title the title of the dialog + */ + FileOpenDialog() + {}; + + /** + * Factory. + * @param path the directory where to start searching + * @param fileTypes one of FileDialogTypes + * @param title the title of the dialog + */ + static FileOpenDialog *create(const char *path, FileDialogType fileTypes, const char *title); + + + /** + * Destructor. + * Perform any necessary cleanups. + */ + virtual ~FileOpenDialog() {}; + + /** + * Show an OpenFile file selector. + * @return the selected path if user selected one, else NULL + */ + virtual bool show() =0; + + /** + * Return the 'key' (filetype) of the selection, if any + * @return a pointer to a string if successful (which must + * be later freed with g_free(), else NULL. + */ + virtual Inkscape::Extension::Extension * getSelectionType() = 0; + + virtual gchar * getFilename () =0; + + virtual Glib::SListHandle getFilenames () = 0; + +}; //FileOpenDialog + + + + + + +/** + * This class provides an implementation-independent API for + * file "Save" dialogs. + */ +class FileSaveDialog +{ +public: + + /** + * Constructor. Do not call directly . Use the factory. + * @param path the directory where to start searching + * @param fileTypes one of FileDialogTypes + * @param title the title of the dialog + * @param key a list of file types from which the user can select + */ + FileSaveDialog () + {}; + + /** + * Factory. + * @param path the directory where to start searching + * @param fileTypes one of FileDialogTypes + * @param title the title of the dialog + * @param key a list of file types from which the user can select + */ + static FileSaveDialog *create(const char *path, FileDialogType fileTypes, const char *title, const char * default_key); + + + /** + * Destructor. + * Perform any necessary cleanups. + */ + virtual ~FileSaveDialog() {}; + + + /** + * Show an SaveAs file selector. + * @return the selected path if user selected one, else NULL + */ + virtual bool show() =0; + + /** + * Return the 'key' (filetype) of the selection, if any + * @return a pointer to a string if successful (which must + * be later freed with g_free(), else NULL. + */ + virtual Inkscape::Extension::Extension * getSelectionType() = 0; + + virtual gchar * getFilename () =0; + + +}; //FileSaveDialog + + +} //namespace Dialogs +} //namespace UI +} //namespace Inkscape + + +#endif /* __FILE_DIALOG_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:encoding=utf-8:textwidth=99 : diff --git a/src/dialogs/fill-style.cpp b/src/dialogs/fill-style.cpp new file mode 100644 index 000000000..1c151deeb --- /dev/null +++ b/src/dialogs/fill-style.cpp @@ -0,0 +1,546 @@ +#define __SP_FILL_STYLE_C__ + +/** + * \brief Fill style widget + * + * Authors: + * Lauris Kaplinski + * Frank Felfe + * bulia byak + * + * Copyright (C) 1999-2005 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define noSP_FS_VERBOSE + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// These can be deleted once we sort out the libart dependence. + +#define ART_WIND_RULE_NONZERO 0 + +static void sp_fill_style_widget_construct ( SPWidget *spw, + SPPaintSelector *psel ); + +static void sp_fill_style_widget_modify_selection ( SPWidget *spw, + Inkscape::Selection *selection, + guint flags, + SPPaintSelector *psel ); + +static void sp_fill_style_widget_change_subselection ( Inkscape::Application *inkscape, SPDesktop *desktop, SPWidget *spw ); + +static void sp_fill_style_widget_change_selection ( SPWidget *spw, + Inkscape::Selection *selection, + SPPaintSelector *psel ); + +static void sp_fill_style_widget_update (SPWidget *spw); + +static void sp_fill_style_widget_paint_mode_changed ( SPPaintSelector *psel, + SPPaintSelectorMode mode, + SPWidget *spw ); +static void sp_fill_style_widget_fillrule_changed ( SPPaintSelector *psel, + SPPaintSelectorFillRule mode, + SPWidget *spw ); + +static void sp_fill_style_widget_paint_dragged (SPPaintSelector *psel, SPWidget *spw ); +static void sp_fill_style_widget_paint_changed (SPPaintSelector *psel, SPWidget *spw ); + +GtkWidget * +sp_fill_style_widget_new (void) +{ + GtkWidget *spw = sp_widget_new_global (INKSCAPE); + + GtkWidget *vb = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vb); + gtk_container_add (GTK_CONTAINER (spw), vb); + + GtkWidget *psel = sp_paint_selector_new (true); // with fillrule selector + gtk_widget_show (psel); + gtk_box_pack_start (GTK_BOX (vb), psel, TRUE, TRUE, 0); + g_object_set_data (G_OBJECT (spw), "paint-selector", psel); + + g_signal_connect ( G_OBJECT (psel), "mode_changed", + G_CALLBACK (sp_fill_style_widget_paint_mode_changed), + spw ); + + g_signal_connect ( G_OBJECT (psel), "dragged", + G_CALLBACK (sp_fill_style_widget_paint_dragged), + spw ); + + g_signal_connect ( G_OBJECT (psel), "changed", + G_CALLBACK (sp_fill_style_widget_paint_changed), + spw ); + + g_signal_connect ( G_OBJECT (psel), "fillrule_changed", + G_CALLBACK (sp_fill_style_widget_fillrule_changed), + spw ); + + + g_signal_connect ( G_OBJECT (spw), "construct", + G_CALLBACK (sp_fill_style_widget_construct), psel); + +//FIXME: switch these from spw signals to global inkscape object signals; spw just retranslates +//those anyway; then eliminate spw + g_signal_connect ( G_OBJECT (spw), "modify_selection", + G_CALLBACK (sp_fill_style_widget_modify_selection), psel); + + g_signal_connect ( G_OBJECT (spw), "change_selection", + G_CALLBACK (sp_fill_style_widget_change_selection), psel); + + g_signal_connect (INKSCAPE, "change_subselection", G_CALLBACK (sp_fill_style_widget_change_subselection), spw); + + sp_fill_style_widget_update (SP_WIDGET (spw)); + + return spw; + +} // end of sp_fill_style_widget_new() + + + +static void +sp_fill_style_widget_construct ( SPWidget *spw, SPPaintSelector *psel ) +{ + +#ifdef SP_FS_VERBOSE + g_print ( "Fill style widget constructed: inkscape %p repr %p\n", + spw->inkscape, spw->repr ); +#endif + if (spw->inkscape) { + + sp_fill_style_widget_update (spw); + + } + +} // end of sp_fill_style_widget_construct() + +static void +sp_fill_style_widget_modify_selection ( SPWidget *spw, + Inkscape::Selection *selection, + guint flags, + SPPaintSelector *psel ) +{ + if (flags & ( SP_OBJECT_MODIFIED_FLAG | + SP_OBJECT_PARENT_MODIFIED_FLAG | + SP_OBJECT_STYLE_MODIFIED_FLAG) ) + { + sp_fill_style_widget_update (spw); + } +} + +static void +sp_fill_style_widget_change_subselection ( Inkscape::Application *inkscape, + SPDesktop *desktop, + SPWidget *spw ) +{ + sp_fill_style_widget_update (spw); +} + +static void +sp_fill_style_widget_change_selection ( SPWidget *spw, + Inkscape::Selection *selection, + SPPaintSelector *psel ) +{ + sp_fill_style_widget_update (spw); +} + +/** +* \param sel Selection to use, or NULL. +*/ +static void +sp_fill_style_widget_update (SPWidget *spw) +{ + if (g_object_get_data (G_OBJECT (spw), "update")) + return; + + if (g_object_get_data (G_OBJECT (spw), "local")) { + g_object_set_data (G_OBJECT (spw), "local", GINT_TO_POINTER (FALSE)); // local change; do nothing, but reset the flag + return; + } + + g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (TRUE)); + + SPPaintSelector *psel = SP_PAINT_SELECTOR (g_object_get_data (G_OBJECT (spw), "paint-selector")); + + // create temporary style + SPStyle *query = sp_style_new (); + // query style from desktop into it. This returns a result flag and fills query with the style of subselection, if any, or selection + int result = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FILL); + + switch (result) { + case QUERY_STYLE_NOTHING: + { + /* No paint at all */ + sp_paint_selector_set_mode (psel, SP_PAINT_SELECTOR_MODE_EMPTY); + break; + } + + case QUERY_STYLE_SINGLE: + case QUERY_STYLE_MULTIPLE_AVERAGED: // TODO: treat this slightly differently, e.g. display "averaged" somewhere in paint selector + case QUERY_STYLE_MULTIPLE_SAME: + { + SPPaintSelectorMode pselmode = sp_style_determine_paint_selector_mode (query, true); + sp_paint_selector_set_mode (psel, pselmode); + + sp_paint_selector_set_fillrule (psel, query->fill_rule.computed == ART_WIND_RULE_NONZERO? + SP_PAINT_SELECTOR_FILLRULE_NONZERO : SP_PAINT_SELECTOR_FILLRULE_EVENODD); + + if (query->fill.set && query->fill.type == SP_PAINT_TYPE_COLOR) { + gfloat d[3]; + sp_color_get_rgb_floatv (&query->fill.value.color, d); + SPColor color; + sp_color_set_rgb_float (&color, d[0], d[1], d[2]); + sp_paint_selector_set_color_alpha (psel, &color, SP_SCALE24_TO_FLOAT (query->fill_opacity.value)); + + } else if (query->fill.set && query->fill.type == SP_PAINT_TYPE_PAINTSERVER) { + + SPPaintServer *server = SP_STYLE_FILL_SERVER (query); + + if (SP_IS_LINEARGRADIENT (server)) { + SPGradient *vector = sp_gradient_get_vector (SP_GRADIENT (server), FALSE); + sp_paint_selector_set_gradient_linear (psel, vector); + + SPLinearGradient *lg = SP_LINEARGRADIENT (server); + sp_paint_selector_set_gradient_properties (psel, + SP_GRADIENT_UNITS (lg), + SP_GRADIENT_SPREAD (lg)); + } else if (SP_IS_RADIALGRADIENT (server)) { + SPGradient *vector = sp_gradient_get_vector (SP_GRADIENT (server), FALSE); + sp_paint_selector_set_gradient_radial (psel, vector); + + SPRadialGradient *rg = SP_RADIALGRADIENT (server); + sp_paint_selector_set_gradient_properties (psel, + SP_GRADIENT_UNITS (rg), + SP_GRADIENT_SPREAD (rg)); + } else if (SP_IS_PATTERN (server)) { + SPPattern *pat = pattern_getroot (SP_PATTERN (server)); + sp_update_pattern_list (psel, pat); + } + } + break; + } + + case QUERY_STYLE_MULTIPLE_DIFFERENT: + { + sp_paint_selector_set_mode (psel, SP_PAINT_SELECTOR_MODE_MULTIPLE); + break; + } + } + + g_free (query); + + g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (FALSE)); + +} + + +static void +sp_fill_style_widget_paint_mode_changed ( SPPaintSelector *psel, + SPPaintSelectorMode mode, + SPWidget *spw ) +{ + if (g_object_get_data (G_OBJECT (spw), "update")) + return; + + /* TODO: Does this work? */ + /* TODO: Not really, here we have to get old color back from object */ + /* Instead of relying on paint widget having meaningful colors set */ + sp_fill_style_widget_paint_changed (psel, spw); +} + +static void +sp_fill_style_widget_fillrule_changed ( SPPaintSelector *psel, + SPPaintSelectorFillRule mode, + SPWidget *spw ) +{ + if (g_object_get_data (G_OBJECT (spw), "update")) + return; + + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + + SPCSSAttr *css = sp_repr_css_attr_new (); + sp_repr_css_set_property (css, "fill-rule", mode == SP_PAINT_SELECTOR_FILLRULE_EVENODD? "evenodd":"nonzero"); + + sp_desktop_set_style (desktop, css); + + sp_repr_css_attr_unref (css); + + sp_document_done (SP_ACTIVE_DOCUMENT); +} + +static gchar *undo_label_1 = "fill:flatcolor:1"; +static gchar *undo_label_2 = "fill:flatcolor:2"; +static gchar *undo_label = undo_label_1; + +/** +This is called repeatedly while you are dragging a color slider, only for flat color +modes. Previously it set the color in style but did not update the repr for efficiency, however +this was flakey and didn't buy us almost anything. So now it does the same as _changed, except +lumps all its changes for undo. + */ +static void +sp_fill_style_widget_paint_dragged (SPPaintSelector *psel, SPWidget *spw) +{ + if (!spw->inkscape) { + return; + } + + if (g_object_get_data (G_OBJECT (spw), "update")) { + return; + } + + if (g_object_get_data (G_OBJECT (spw), "local")) { + // previous local flag not cleared yet; + // this means dragged events come too fast, so we better skip this one to speed up display + // (it's safe to do this in any case) + return; + } + + g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (TRUE)); + + switch (psel->mode) { + + case SP_PAINT_SELECTOR_MODE_COLOR_RGB: + case SP_PAINT_SELECTOR_MODE_COLOR_CMYK: + { + sp_paint_selector_set_flat_color (psel, SP_ACTIVE_DESKTOP, "fill", "fill-opacity"); + sp_document_maybe_done (SP_DT_DOCUMENT(SP_ACTIVE_DESKTOP), undo_label); + g_object_set_data (G_OBJECT (spw), "local", GINT_TO_POINTER (TRUE)); // local change, do not update from selection + break; + } + + default: + g_warning ( "file %s: line %d: Paint %d should not emit 'dragged'", + __FILE__, __LINE__, psel->mode ); + break; + + } + g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (FALSE)); +} + + +/** +This is called (at least) when: +1 paint selector mode is switched (e.g. flat color -> gradient) +2 you finished dragging a gradient node and released mouse +3 you changed a gradient selector parameter (e.g. spread) +Must update repr. + */ +static void +sp_fill_style_widget_paint_changed ( SPPaintSelector *psel, + SPWidget *spw ) +{ + if (g_object_get_data (G_OBJECT (spw), "update")) { + return; + } + g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (TRUE)); + + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (!desktop) { + return; + } + SPDocument *document = SP_DT_DOCUMENT (desktop); + Inkscape::Selection *selection = SP_DT_SELECTION (desktop); + + GSList const *items = selection->itemList(); + + switch (psel->mode) { + + case SP_PAINT_SELECTOR_MODE_EMPTY: + // This should not happen. + g_warning ( "file %s: line %d: Paint %d should not emit 'changed'", + __FILE__, __LINE__, psel->mode); + break; + case SP_PAINT_SELECTOR_MODE_MULTIPLE: + // This happens when you switch multiple objects with different gradients to flat color; + // nothing to do here. + break; + + case SP_PAINT_SELECTOR_MODE_NONE: + { + SPCSSAttr *css = sp_repr_css_attr_new (); + sp_repr_css_set_property (css, "fill", "none"); + + sp_desktop_set_style (desktop, css); + + sp_repr_css_attr_unref (css); + + sp_document_done (document); + break; + } + + case SP_PAINT_SELECTOR_MODE_COLOR_RGB: + case SP_PAINT_SELECTOR_MODE_COLOR_CMYK: + { + sp_paint_selector_set_flat_color (psel, desktop, "fill", "fill-opacity"); + sp_document_maybe_done (SP_DT_DOCUMENT(desktop), undo_label); + + // on release, toggle undo_label so that the next drag will not be lumped with this one + if (undo_label == undo_label_1) + undo_label = undo_label_2; + else + undo_label = undo_label_1; + + break; + } + + case SP_PAINT_SELECTOR_MODE_GRADIENT_LINEAR: + case SP_PAINT_SELECTOR_MODE_GRADIENT_RADIAL: + if (items) { + SPGradientType const gradient_type = ( psel->mode == SP_PAINT_SELECTOR_MODE_GRADIENT_LINEAR + ? SP_GRADIENT_TYPE_LINEAR + : SP_GRADIENT_TYPE_RADIAL ); + + // HACK: reset fill-opacity - that 0.75 is annoying; BUT remove this when we have an opacity slider for all tabs + SPCSSAttr *css = sp_repr_css_attr_new(); + sp_repr_css_set_property(css, "fill-opacity", "1.0"); + + SPGradient *vector = sp_paint_selector_get_gradient_vector(psel); + if (!vector) { + /* No vector in paint selector should mean that we just changed mode */ + + SPStyle *query = sp_style_new (); + int result = objects_query_fillstroke ((GSList *) items, query, true); + guint32 common_rgb = 0; + if (result == QUERY_STYLE_MULTIPLE_SAME) { + if (query->fill.type != SP_PAINT_TYPE_COLOR) { + common_rgb = sp_desktop_get_color(desktop, true); + } else { + common_rgb = sp_color_get_rgba32_ualpha(&query->fill.value.color, 0xff); + } + vector = sp_document_default_gradient_vector(document, common_rgb); + } + g_free (query); + + for (GSList const *i = items; i != NULL; i = i->next) { + //FIXME: see above + sp_repr_css_change_recursive(SP_OBJECT_REPR(i->data), css, "style"); + + if (!vector) { + sp_item_set_gradient(SP_ITEM(i->data), + sp_gradient_vector_for_object(document, desktop, SP_OBJECT(i->data), true), + gradient_type, true); + } else { + sp_item_set_gradient(SP_ITEM(i->data), vector, gradient_type, true); + } + } + } else { + /* We have changed from another gradient type, or modified spread/units within + * this gradient type. */ + vector = sp_gradient_ensure_vector_normalized (vector); + for (GSList const *i = items; i != NULL; i = i->next) { + //FIXME: see above + sp_repr_css_change_recursive (SP_OBJECT_REPR (i->data), css, "style"); + + SPGradient *gr = sp_item_set_gradient(SP_ITEM(i->data), vector, gradient_type, true); + sp_gradient_selector_attrs_to_gradient (gr, psel); + } + } + + sp_repr_css_attr_unref (css); + + sp_document_done (document); + } + break; + + case SP_PAINT_SELECTOR_MODE_PATTERN: + + if (items) { + + SPPattern *pattern = sp_paint_selector_get_pattern (psel); + if (!pattern) { + + /* No Pattern in paint selector should mean that we just + * changed mode - dont do jack. + */ + + } else { + Inkscape::XML::Node *patrepr = SP_OBJECT_REPR(pattern); + SPCSSAttr *css = sp_repr_css_attr_new (); + gchar *urltext = g_strdup_printf ("url(#%s)", patrepr->attribute("id")); + sp_repr_css_set_property (css, "fill", urltext); + + // HACK: reset fill-opacity - that 0.75 is annoying; BUT remove this when we have an opacity slider for all tabs + sp_repr_css_set_property(css, "fill-opacity", "1.0"); + + // cannot just call sp_desktop_set_style, because we don't want to touch those + // objects who already have the same root pattern but through a different href + // chain. FIXME: move this to a sp_item_set_pattern + for (GSList const *i = items; i != NULL; i = i->next) { + SPObject *selobj = SP_OBJECT (i->data); + + SPStyle *style = SP_OBJECT_STYLE (selobj); + if (style && style->fill.type == SP_PAINT_TYPE_PAINTSERVER) { + SPObject *server = SP_OBJECT_STYLE_FILL_SERVER (selobj); + if (SP_IS_PATTERN (server) && pattern_getroot (SP_PATTERN(server)) == pattern) + // only if this object's pattern is not rooted in our selected pattern, apply + continue; + } + + sp_desktop_apply_css_recursive (selobj, css, true); + } + + sp_repr_css_attr_unref (css); + g_free (urltext); + + } // end if + + sp_document_done (document); + + } // end if + + break; + + case SP_PAINT_SELECTOR_MODE_UNSET: + if (items) { + SPCSSAttr *css = sp_repr_css_attr_new (); + sp_repr_css_unset_property (css, "fill"); + + sp_desktop_set_style (desktop, css); + sp_repr_css_attr_unref (css); + + sp_document_done (document); + } + break; + + default: + g_warning ( "file %s: line %d: Paint selector should not be in " + "mode %d", + __FILE__, __LINE__, psel->mode ); + break; + } + + g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (FALSE)); +} + + +/* + 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 : diff --git a/src/dialogs/fill-style.h b/src/dialogs/fill-style.h new file mode 100644 index 000000000..96cb06987 --- /dev/null +++ b/src/dialogs/fill-style.h @@ -0,0 +1,35 @@ +#ifndef __SP_FILL_STYLE_H__ +#define __SP_FILL_STYLE_H__ + +/** + * \brief Fill style configuration + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +#include + +#include "forward.h" + + +GtkWidget *sp_fill_style_widget_new (void); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/dialogs/find.cpp b/src/dialogs/find.cpp new file mode 100644 index 000000000..85146f8a3 --- /dev/null +++ b/src/dialogs/find.cpp @@ -0,0 +1,763 @@ +#define __SP_TRANSFORMATION_C__ + +/** + * \brief Find dialog + * + * Authors: + * bulia byak + * + * Copyright (C) 2004 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + + +#include "widgets/icon.h" + +#include "message-stack.h" + +//TODO : delete this +GtkWidget * sp_find_dialog_old (void); + +void +//GtkWidget * +sp_find_dialog(){ + // DialogFind::get().present(); + sp_find_dialog_old (); + return; +} + + +#include + +#include +#include "helper/window.h" +#include "macros.h" +#include "inkscape.h" +#include "document.h" +#include "desktop.h" +#include "selection.h" +#include "desktop-handles.h" + +#include "dialog-events.h" +#include "../prefs-utils.h" +#include "../verbs.h" +#include "../interface.h" +#include "../sp-text.h" +#include "../sp-flowtext.h" +#include "../text-editing.h" +#include "../sp-tspan.h" +#include "../selection-chemistry.h" +#include "../sp-defs.h" +#include "../sp-rect.h" +#include "../sp-ellipse.h" +#include "../sp-star.h" +#include "../sp-spiral.h" +#include "../sp-path.h" +#include "../sp-line.h" +#include "../sp-polyline.h" +#include "../sp-item-group.h" +#include "../sp-use.h" +#include "../sp-image.h" +#include "../sp-offset.h" +#include + +using NR::X; +using NR::Y; + +static GtkWidget *dlg = NULL; +static win_data wd; + +// impossible original values to make sure they are read from prefs +static gint x = -1000, y = -1000, w = 0, h = 0; +static gchar *prefs_path = "dialogs.find"; + + + + +static void sp_find_dialog_destroy(GtkObject *object, gpointer) +{ + sp_signal_disconnect_by_data (INKSCAPE, object); + wd.win = dlg = NULL; + wd.stop = 0; +} + + + +static gboolean sp_find_dialog_delete(GtkObject *, GdkEvent *, gpointer data) +{ + gtk_window_get_position (GTK_WINDOW (dlg), &x, &y); + gtk_window_get_size (GTK_WINDOW (dlg), &w, &h); + + prefs_set_int_attribute (prefs_path, "x", x); + prefs_set_int_attribute (prefs_path, "y", y); + prefs_set_int_attribute (prefs_path, "w", w); + prefs_set_int_attribute (prefs_path, "h", h); + + return FALSE; // which means, go ahead and destroy it +} + +void +sp_find_squeeze_window() +{ + GtkRequisition r; + gtk_widget_size_request(dlg, &r); + gtk_window_resize ((GtkWindow *) dlg, r.width, r.height); +} + +bool +item_id_match (SPItem *item, const gchar *id, bool exact) +{ + if (SP_OBJECT_REPR (item) == NULL) + return false; + + if (SP_IS_STRING(item)) // SPStrings have "on demand" ids which are useless for searching + return false; + + const gchar *item_id = (SP_OBJECT_REPR (item))->attribute("id"); + if (item_id == NULL) + return false; + + if (exact) { + return ((bool) !strcmp(item_id, id)); + } else { +// g_print ("strstr: %s %s: %s\n", item_id, id, strstr(item_id, id) != NULL? "yes":"no"); + return ((bool) (strstr(item_id, id) != NULL)); + } +} + +bool +item_text_match (SPItem *item, const gchar *text, bool exact) +{ + if (SP_OBJECT_REPR (item) == NULL) + return false; + + if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) { + const gchar *item_text = sp_te_get_string_multiline (item); + if (item_text == NULL) + return false; + bool ret; + if (exact) { + ret = ((bool) !strcasecmp(item_text, text)); + } else { + //FIXME: strcasestr + ret = ((bool) (strstr(item_text, text) != NULL)); + } + g_free ((void*) item_text); + return ret; + } + return false; +} + +bool +item_style_match (SPItem *item, const gchar *text, bool exact) +{ + if (SP_OBJECT_REPR (item) == NULL) + return false; + + const gchar *item_text = (SP_OBJECT_REPR (item))->attribute("style"); + if (item_text == NULL) + return false; + + if (exact) { + return ((bool) !strcmp(item_text, text)); + } else { + return ((bool) (strstr(item_text, text) != NULL)); + } +} + +bool +item_attr_match (SPItem *item, const gchar *name, bool exact) +{ + if (SP_OBJECT_REPR (item) == NULL) + return false; + + if (exact) { + const gchar *attr_value = (SP_OBJECT_REPR (item))->attribute(name); + return ((bool) (attr_value != NULL)); + } else { + return SP_OBJECT_REPR (item)->matchAttributeName(name); + } +} + + +GSList * +filter_onefield (GSList *l, GObject *dlg, const gchar *field, bool (*match_function)(SPItem *, const gchar *, bool), bool exact) +{ + GtkWidget *widget = GTK_WIDGET (gtk_object_get_data (GTK_OBJECT (dlg), field)); + const gchar *text = gtk_entry_get_text (GTK_ENTRY(widget)); + + if (strlen (text) != 0) { + GSList *n = NULL; + for (GSList *i = l; i != NULL; i = i->next) { + if (match_function (SP_ITEM(i->data), text, exact)) { + n = g_slist_prepend (n, i->data); + } + } + return n; + } else { + return l; + } + + return NULL; +} + + +bool +type_checkbox (GtkWidget *widget, const gchar *data) +{ + return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (gtk_object_get_data (GTK_OBJECT (widget), data))); +} + +bool +item_type_match (SPItem *item, GtkWidget *widget) +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + + if (SP_IS_RECT(item)) { + return (type_checkbox (widget, "shapes") || type_checkbox (widget, "rects")); + + } else if (SP_IS_GENERICELLIPSE(item) || SP_IS_ELLIPSE(item) || SP_IS_ARC(item) || SP_IS_CIRCLE(item)) { + return (type_checkbox (widget, "shapes") || type_checkbox (widget, "ellipses")); + + } else if (SP_IS_STAR(item) || SP_IS_POLYGON(item)) { + return (type_checkbox (widget, "shapes") || type_checkbox (widget, "stars")); + + } else if (SP_IS_SPIRAL(item)) { + return (type_checkbox (widget, "shapes") || type_checkbox (widget, "spirals")); + + } else if (SP_IS_PATH(item) || SP_IS_LINE(item) || SP_IS_POLYLINE(item)) { + return (type_checkbox (widget, "paths")); + + } else if (SP_IS_TEXT(item) || SP_IS_TSPAN(item) || SP_IS_STRING(item)) { + return (type_checkbox (widget, "texts")); + + } else if (SP_IS_GROUP(item) && !desktop->isLayer(item) ) { // never select layers! + return (type_checkbox (widget, "groups")); + + } else if (SP_IS_USE(item)) { + return (type_checkbox (widget, "clones")); + + } else if (SP_IS_IMAGE(item)) { + return (type_checkbox (widget, "images")); + + } else if (SP_IS_OFFSET(item)) { + return (type_checkbox (widget, "offsets")); + } + + return false; +} + +GSList * +filter_types (GSList *l, GObject *dlg, bool (*match_function)(SPItem *, GtkWidget *)) +{ + GtkWidget *widget = GTK_WIDGET (gtk_object_get_data (GTK_OBJECT (dlg), "types")); + + GtkWidget *alltypes = GTK_WIDGET (gtk_object_get_data (GTK_OBJECT (widget), "all")); + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (alltypes))) + return l; + + + GSList *n = NULL; + for (GSList *i = l; i != NULL; i = i->next) { + if (match_function (SP_ITEM(i->data), widget)) { + n = g_slist_prepend (n, i->data); + } + } + return n; +} + + +GSList * +filter_list (GSList *l, GObject *dlg, bool exact) +{ + l = filter_onefield (l, dlg, "text", item_text_match, exact); + l = filter_onefield (l, dlg, "id", item_id_match, exact); + l = filter_onefield (l, dlg, "style", item_style_match, exact); + l = filter_onefield (l, dlg, "attr", item_attr_match, exact); + + l = filter_types (l, dlg, item_type_match); + + return l; +} + +GSList * +all_items (SPObject *r, GSList *l, bool hidden, bool locked) +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + + if (SP_IS_DEFS(r)) + return l; // we're not interested in items in defs + + if (!strcmp (SP_OBJECT_REPR (r)->name(), "svg:metadata")) + return l; // we're not interested in metadata + + for (SPObject *child = sp_object_first_child(r); child; child = SP_OBJECT_NEXT (child)) { + if (SP_IS_ITEM (child) && !SP_OBJECT_IS_CLONED (child) && !desktop->isLayer(SP_ITEM(child))) { + if ((hidden || !desktop->itemIsHidden(SP_ITEM(child))) && (locked || !SP_ITEM(child)->isLocked())) { + l = g_slist_prepend (l, child); + } + } + l = all_items (child, l, hidden, locked); + } + return l; +} + +GSList * +all_selection_items (Inkscape::Selection *s, GSList *l, SPObject *ancestor, bool hidden, bool locked) +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + + for (GSList *i = (GSList *) s->itemList(); i != NULL; i = i->next) { + if (SP_IS_ITEM (i->data) && !SP_OBJECT_IS_CLONED (i->data) && !desktop->isLayer(SP_ITEM(i->data))) { + if (!ancestor || ancestor->isAncestorOf(SP_OBJECT (i->data))) { + if ((hidden || !desktop->itemIsHidden(SP_ITEM(i->data))) && (locked || !SP_ITEM(i->data)->isLocked())) { + l = g_slist_prepend (l, i->data); + } + } + } + if (!ancestor || ancestor->isAncestorOf(SP_OBJECT (i->data))) { + l = all_items (SP_OBJECT (i->data), l, hidden, locked); + } + } + return l; +} + + +void sp_find_dialog_find(GObject *, GObject *dlg) +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + + bool hidden = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (gtk_object_get_data (GTK_OBJECT (dlg), "includehidden"))); + bool locked = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (gtk_object_get_data (GTK_OBJECT (dlg), "includelocked"))); + + GSList *l = NULL; + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (gtk_object_get_data (GTK_OBJECT (dlg), "inselection")))) { + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (gtk_object_get_data (GTK_OBJECT (dlg), "inlayer")))) { + l = all_selection_items (desktop->selection, l, desktop->currentLayer(), hidden, locked); + } else { + l = all_selection_items (desktop->selection, l, NULL, hidden, locked); + } + } else { + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (gtk_object_get_data (GTK_OBJECT (dlg), "inlayer")))) { + l = all_items (desktop->currentLayer(), l, hidden, locked); + } else { + l = all_items (SP_DOCUMENT_ROOT (SP_DT_DOCUMENT (desktop)), l, hidden, locked); + } + } + guint all = g_slist_length (l); + + bool exact = true; + GSList *n = NULL; + n = filter_list (l, dlg, exact); + if (n == NULL) { + exact = false; + n = filter_list (l, dlg, exact); + } + + if (n != NULL) { + int count = g_slist_length (n); + desktop->messageStack()->flashF(Inkscape::NORMAL_MESSAGE, + // TRANSLATORS: "%s" is replaced with "exact" or "partial" when this string is displayed + ngettext("%d object found (out of %d), %s match.", + "%d objects found (out of %d), %s match.", + count), + count, all, exact? _("exact") : _("partial")); + + Inkscape::Selection *selection = SP_DT_SELECTION (desktop); + selection->clear(); + selection->setList(n); + scroll_to_show_item (desktop, SP_ITEM(n->data)); + } else { + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No objects found")); + } +} + +void +sp_find_reset_searchfield (GObject *dlg, const gchar *field) +{ + GtkWidget *widget = GTK_WIDGET (gtk_object_get_data (GTK_OBJECT (dlg), field)); + gtk_entry_set_text (GTK_ENTRY(widget), ""); +} + + +void +sp_find_dialog_reset (GObject *, GObject *dlg) +{ + sp_find_reset_searchfield (dlg, "text"); + sp_find_reset_searchfield (dlg, "id"); + sp_find_reset_searchfield (dlg, "style"); + sp_find_reset_searchfield (dlg, "attr"); + + GtkWidget *types = GTK_WIDGET (gtk_object_get_data (GTK_OBJECT (dlg), "types")); + GtkToggleButton *tb = GTK_TOGGLE_BUTTON (gtk_object_get_data (GTK_OBJECT (types), "all")); + gtk_toggle_button_toggled (tb); + gtk_toggle_button_set_active (tb, TRUE); +} + + +#define FIND_LABELWIDTH 80 + +void +sp_find_new_searchfield (GtkWidget *dlg, GtkWidget *vb, const gchar *label, const gchar *id, GtkTooltips *tt, const gchar *tip) +{ + GtkWidget *hb = gtk_hbox_new (FALSE, 0); + GtkWidget *l = gtk_label_new_with_mnemonic (label); + gtk_widget_set_size_request (l, FIND_LABELWIDTH, -1); + gtk_misc_set_alignment (GTK_MISC (l), 1.0, 0.5); + gtk_box_pack_start (GTK_BOX (hb), l, FALSE, FALSE, 0); + + GtkWidget *tf = gtk_entry_new (); + gtk_entry_set_max_length (GTK_ENTRY (tf), 64); + gtk_box_pack_start (GTK_BOX (hb), tf, TRUE, TRUE, 0); + gtk_object_set_data (GTK_OBJECT (dlg), id, tf); + gtk_tooltips_set_tip (tt, tf, tip, NULL); + g_signal_connect ( G_OBJECT (tf), "activate", G_CALLBACK (sp_find_dialog_find), dlg ); + gtk_label_set_mnemonic_widget (GTK_LABEL(l), tf); + + gtk_box_pack_start (GTK_BOX (vb), hb, FALSE, FALSE, 0); +} + +void +sp_find_new_button (GtkWidget *dlg, GtkWidget *hb, const gchar *label, GtkTooltips *tt, const gchar *tip, void (*function) (GObject *, GObject *)) +{ + GtkWidget *b = gtk_button_new_with_mnemonic (label); + gtk_tooltips_set_tip (tt, b, tip, NULL); + gtk_box_pack_start (GTK_BOX (hb), b, TRUE, TRUE, 0); + g_signal_connect ( G_OBJECT (b), "clicked", G_CALLBACK (function), dlg ); + gtk_widget_show (b); +} + +void +toggle_alltypes (GtkToggleButton *tb, gpointer data) +{ + GtkWidget *alltypes_pane = GTK_WIDGET (gtk_object_get_data (GTK_OBJECT (data), "all-pane")); + if (gtk_toggle_button_get_active (tb)) { + gtk_widget_hide_all (alltypes_pane); + } else { + gtk_widget_show_all (alltypes_pane); + + // excplicit toggle to make sure its handler gets called, no matter what was the original state + gtk_toggle_button_toggled (GTK_TOGGLE_BUTTON (gtk_object_get_data (GTK_OBJECT (data), "shapes"))); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gtk_object_get_data (GTK_OBJECT (data), "shapes")), TRUE); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gtk_object_get_data (GTK_OBJECT (data), "paths")), TRUE); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gtk_object_get_data (GTK_OBJECT (data), "texts")), TRUE); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gtk_object_get_data (GTK_OBJECT (data), "groups")), TRUE); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gtk_object_get_data (GTK_OBJECT (data), "clones")), TRUE); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gtk_object_get_data (GTK_OBJECT (data), "images")), TRUE); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gtk_object_get_data (GTK_OBJECT (data), "offsets")), TRUE); + } + sp_find_squeeze_window(); +} + +void +toggle_shapes (GtkToggleButton *tb, gpointer data) +{ + GtkWidget *shapes_pane = GTK_WIDGET (gtk_object_get_data (GTK_OBJECT (data), "shapes-pane")); + if (gtk_toggle_button_get_active (tb)) { + gtk_widget_hide_all (shapes_pane); + } else { + gtk_widget_show_all (shapes_pane); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gtk_object_get_data (GTK_OBJECT (data), "rects")), FALSE); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gtk_object_get_data (GTK_OBJECT (data), "ellipses")), FALSE); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gtk_object_get_data (GTK_OBJECT (data), "stars")), FALSE); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gtk_object_get_data (GTK_OBJECT (data), "spirals")), FALSE); + } + sp_find_squeeze_window(); +} + + +GtkWidget * +sp_find_types_checkbox (GtkWidget *w, const gchar *data, gboolean active, + GtkTooltips *tt, const gchar *tip, + const gchar *label, + void (*toggled)(GtkToggleButton *, gpointer)) +{ + GtkWidget *hb = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hb); + + { + GtkWidget *b = gtk_check_button_new_with_label (label); + gtk_widget_show (b); + gtk_toggle_button_set_active ((GtkToggleButton *) b, active); + gtk_object_set_data (GTK_OBJECT (w), data, b); + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), b, tip, NULL); + if (toggled) + gtk_signal_connect (GTK_OBJECT (b), "toggled", GTK_SIGNAL_FUNC (toggled), w); + gtk_box_pack_start (GTK_BOX (hb), b, FALSE, FALSE, 0); + } + + return hb; +} + +GtkWidget * +sp_find_types_checkbox_indented (GtkWidget *w, const gchar *data, gboolean active, + GtkTooltips *tt, const gchar *tip, + const gchar *label, + void (*toggled)(GtkToggleButton *, gpointer), guint indent) +{ + GtkWidget *hb = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hb); + + { // empty label for indent + GtkWidget *l = gtk_label_new (""); + gtk_widget_show (l); + gtk_widget_set_size_request (l, FIND_LABELWIDTH + indent, -1); + gtk_box_pack_start (GTK_BOX (hb), l, FALSE, FALSE, 0); + } + + GtkWidget *c = sp_find_types_checkbox (w, data, active, tt, tip, label, toggled); + gtk_box_pack_start (GTK_BOX (hb), c, FALSE, FALSE, 0); + + return hb; +} + + +GtkWidget * +sp_find_types () +{ + GtkTooltips *tt = gtk_tooltips_new (); + + GtkWidget *vb = gtk_vbox_new (FALSE, 4); + gtk_widget_show (vb); + + { + GtkWidget *hb = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hb); + + { + GtkWidget *l = gtk_label_new_with_mnemonic (_("T_ype: ")); + gtk_widget_show (l); + gtk_widget_set_size_request (l, FIND_LABELWIDTH, -1); + gtk_misc_set_alignment (GTK_MISC (l), 1.0, 0.5); + gtk_box_pack_start (GTK_BOX (hb), l, FALSE, FALSE, 0); + } + + GtkWidget *alltypes = sp_find_types_checkbox (vb, "all", TRUE, tt, _("Search in all object types"), _("All types"), toggle_alltypes); + gtk_box_pack_start (GTK_BOX (hb), alltypes, FALSE, FALSE, 0); + + gtk_box_pack_start (GTK_BOX (vb), hb, FALSE, FALSE, 0); + } + + { + GtkWidget *vb_all = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vb_all); + + { + GtkWidget *c = sp_find_types_checkbox_indented (vb, "shapes", FALSE, tt, _("Search all shapes"), _("All shapes"), toggle_shapes, 10); + gtk_box_pack_start (GTK_BOX (vb_all), c, FALSE, FALSE, 0); + } + + + { + GtkWidget *hb = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hb); + + { // empty label for alignment + GtkWidget *l = gtk_label_new (""); + gtk_widget_show (l); + gtk_widget_set_size_request (l, FIND_LABELWIDTH + 20, -1); + gtk_box_pack_start (GTK_BOX (hb), l, FALSE, FALSE, 0); + } + + { + GtkWidget *c = sp_find_types_checkbox (vb, "rects", FALSE, tt, _("Search rectangles"), _("Rectangles"), NULL); + gtk_box_pack_start (GTK_BOX (hb), c, FALSE, FALSE, 0); + } + + { + GtkWidget *c = sp_find_types_checkbox (vb, "ellipses", FALSE, tt, _("Search ellipses, arcs, circles"), _("Ellipses"), NULL); + gtk_box_pack_start (GTK_BOX (hb), c, FALSE, FALSE, 0); + } + + { + GtkWidget *c = sp_find_types_checkbox (vb, "stars", FALSE, tt, _("Search stars and polygons"), _("Stars"), NULL); + gtk_box_pack_start (GTK_BOX (hb), c, FALSE, FALSE, 0); + } + + { + GtkWidget *c = sp_find_types_checkbox (vb, "spirals", FALSE, tt, _("Search spirals"), _("Spirals"), NULL); + gtk_box_pack_start (GTK_BOX (hb), c, FALSE, FALSE, 0); + } + + gtk_object_set_data (GTK_OBJECT (vb), "shapes-pane", hb); + + gtk_box_pack_start (GTK_BOX (vb_all), hb, FALSE, FALSE, 0); + gtk_widget_hide_all (hb); + } + + { + // TRANSLATORS: polyline is a set of connected straight line segments + // http://www.w3.org/TR/SVG11/shapes.html#PolylineElement + GtkWidget *c = sp_find_types_checkbox_indented (vb, "paths", TRUE, tt, _("Search paths, lines, polylines"), _("Paths"), NULL, 10); + gtk_box_pack_start (GTK_BOX (vb_all), c, FALSE, FALSE, 0); + } + + { + GtkWidget *c = sp_find_types_checkbox_indented (vb, "texts", TRUE, tt, _("Search text objects"), _("Texts"), NULL, 10); + gtk_box_pack_start (GTK_BOX (vb_all), c, FALSE, FALSE, 0); + } + + { + GtkWidget *c = sp_find_types_checkbox_indented (vb, "groups", TRUE, tt, _("Search groups"), _("Groups"), NULL, 10); + gtk_box_pack_start (GTK_BOX (vb_all), c, FALSE, FALSE, 0); + } + + { + GtkWidget *c = sp_find_types_checkbox_indented (vb, "clones", TRUE, tt, _("Search clones"), _("Clones"), NULL, 10); + gtk_box_pack_start (GTK_BOX (vb_all), c, FALSE, FALSE, 0); + } + + { + GtkWidget *c = sp_find_types_checkbox_indented (vb, "images", TRUE, tt, _("Search images"), _("Images"), NULL, 10); + gtk_box_pack_start (GTK_BOX (vb_all), c, FALSE, FALSE, 0); + } + + { + GtkWidget *c = sp_find_types_checkbox_indented (vb, "offsets", TRUE, tt, _("Search offset objects"), _("Offsets"), NULL, 10); + gtk_box_pack_start (GTK_BOX (vb_all), c, FALSE, FALSE, 0); + } + + gtk_box_pack_start (GTK_BOX (vb), vb_all, FALSE, FALSE, 0); + gtk_object_set_data (GTK_OBJECT (vb), "all-pane", vb_all); + gtk_widget_hide_all (vb_all); + } + + return vb; +} + + +GtkWidget * +sp_find_dialog_old (void) +{ + if (!dlg) + { + gchar title[500]; + sp_ui_dialog_title_string (Inkscape::Verb::get(SP_VERB_DIALOG_FIND), title); + + dlg = sp_window_new (title, TRUE); + if (x == -1000 || y == -1000) { + x = prefs_get_int_attribute (prefs_path, "x", 0); + y = prefs_get_int_attribute (prefs_path, "y", 0); + } + if (w ==0 || h == 0) { + w = prefs_get_int_attribute (prefs_path, "w", 0); + h = prefs_get_int_attribute (prefs_path, "h", 0); + } + if (x != 0 || y != 0) { + gtk_window_move ((GtkWindow *) dlg, x, y); + } else { + gtk_window_set_position(GTK_WINDOW(dlg), GTK_WIN_POS_CENTER); + } + if (w && h) + gtk_window_resize ((GtkWindow *) dlg, w, h); + + sp_transientize (dlg); + wd.win = dlg; + wd.stop = 0; + g_signal_connect ( G_OBJECT (INKSCAPE), "activate_desktop", G_CALLBACK (sp_transientize_callback), &wd ); + + gtk_signal_connect ( GTK_OBJECT (dlg), "event", GTK_SIGNAL_FUNC (sp_dialog_event_handler), dlg); + + gtk_signal_connect ( GTK_OBJECT (dlg), "destroy", G_CALLBACK (sp_find_dialog_destroy), NULL ); + gtk_signal_connect ( GTK_OBJECT (dlg), "delete_event", G_CALLBACK (sp_find_dialog_delete), dlg); + g_signal_connect ( G_OBJECT (INKSCAPE), "shut_down", G_CALLBACK (sp_find_dialog_delete), dlg); + + g_signal_connect ( G_OBJECT (INKSCAPE), "dialogs_hide", G_CALLBACK (sp_dialog_hide), dlg); + g_signal_connect ( G_OBJECT (INKSCAPE), "dialogs_unhide", G_CALLBACK (sp_dialog_unhide), dlg); + + GtkTooltips *tt = gtk_tooltips_new (); + + gtk_container_set_border_width (GTK_CONTAINER (dlg), 4); + + /* Toplevel vbox */ + GtkWidget *vb = gtk_vbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (dlg), vb); + + sp_find_new_searchfield (dlg, vb, _("_Text: "), "text", tt, _("Find objects by their text content (exact or partial match)")); + sp_find_new_searchfield (dlg, vb, _("_ID: "), "id", tt, _("Find objects by the value of the id attribute (exact or partial match)")); + sp_find_new_searchfield (dlg, vb, _("_Style: "), "style", tt, _("Find objects by the value of the style attribute (exact or partial match)")); + sp_find_new_searchfield (dlg, vb, _("_Attribute: "), "attr", tt ,_("Find objects by the name of an attribute (exact or partial match)")); + + gtk_widget_show_all (vb); + + GtkWidget *types = sp_find_types (); + gtk_object_set_data (GTK_OBJECT (dlg), "types", types); + gtk_box_pack_start (GTK_BOX (vb), types, FALSE, FALSE, 0); + + { + GtkWidget *w = gtk_hseparator_new (); + gtk_widget_show (w); + gtk_box_pack_start (GTK_BOX (vb), w, FALSE, FALSE, 3); + + { + GtkWidget *b = gtk_check_button_new_with_mnemonic (_("Search in s_election")); + gtk_widget_show (b); + gtk_toggle_button_set_active ((GtkToggleButton *) b, FALSE); + gtk_object_set_data (GTK_OBJECT (dlg), "inselection", b); + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), b, _("Limit search to the current selection"), NULL); + gtk_box_pack_start (GTK_BOX (vb), b, FALSE, FALSE, 0); + } + + { + GtkWidget *b = gtk_check_button_new_with_mnemonic (_("Search in current _layer")); + gtk_widget_show (b); + gtk_toggle_button_set_active ((GtkToggleButton *) b, FALSE); + gtk_object_set_data (GTK_OBJECT (dlg), "inlayer", b); + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), b, _("Limit search to the current layer"), NULL); + gtk_box_pack_start (GTK_BOX (vb), b, FALSE, FALSE, 0); + } + + { + GtkWidget *b = gtk_check_button_new_with_mnemonic (_("Include _hidden")); + gtk_widget_show (b); + gtk_toggle_button_set_active ((GtkToggleButton *) b, FALSE); + gtk_object_set_data (GTK_OBJECT (dlg), "includehidden", b); + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), b, _("Include hidden objects in search"), NULL); + gtk_box_pack_start (GTK_BOX (vb), b, FALSE, FALSE, 0); + } + + { + GtkWidget *b = gtk_check_button_new_with_mnemonic (_("Include l_ocked")); + gtk_widget_show (b); + gtk_toggle_button_set_active ((GtkToggleButton *) b, FALSE); + gtk_object_set_data (GTK_OBJECT (dlg), "includelocked", b); + gtk_tooltips_set_tip (GTK_TOOLTIPS (tt), b, _("Include locked objects in search"), NULL); + gtk_box_pack_start (GTK_BOX (vb), b, FALSE, FALSE, 0); + } + } + + { + GtkWidget *hb = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hb); + gtk_box_pack_start (GTK_BOX (vb), hb, FALSE, FALSE, 0); + + // TRANSLATORS: "Clear" is a verb here + sp_find_new_button (dlg, hb, _("_Clear"), tt, _("Clear values"), sp_find_dialog_reset); + sp_find_new_button (dlg, hb, _("_Find"), tt, _("Select objects matching all of the fields you filled in"), sp_find_dialog_find); + } + } + + gtk_widget_show((GtkWidget *) dlg); + gtk_window_present ((GtkWindow *) dlg); + sp_find_dialog_reset (NULL, G_OBJECT (dlg)); + + return dlg; +} + + +/* + 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 : diff --git a/src/dialogs/find.h b/src/dialogs/find.h new file mode 100644 index 000000000..38200d532 --- /dev/null +++ b/src/dialogs/find.h @@ -0,0 +1,31 @@ +#ifndef SEEN_FIND_H +#define SEEN_FIND_H + +/** + * \brief Find dialog + * + * Authors: + * bulia byak + * + * Copyright (C) 2004 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +void sp_find_dialog(); + + +#endif /* !SEEN_FIND_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 : diff --git a/src/dialogs/iconpreview.cpp b/src/dialogs/iconpreview.cpp new file mode 100644 index 000000000..b3ea55df3 --- /dev/null +++ b/src/dialogs/iconpreview.cpp @@ -0,0 +1,320 @@ +/* + * A simple dialog for previewing icon representation. + * + * Authors: + * Jon A. Cruz + * Bob Jamison + * Other dudes from The Inkscape Organization + * + * Copyright (C) 2004 Bob Jamison + * Copyright (C) 2005 Jon A. Cruz + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "iconpreview.h" + +#include + +#include //for GTK_RESPONSE* types +#include +#include +#include + +#include "prefs-utils.h" +#include "inkscape.h" +#include "document.h" +#include "desktop-handles.h" +#include "selection.h" +#include "desktop.h" +#include "display/nr-arena.h" +#include "sp-root.h" +#include "xml/repr.h" + +extern "C" { +// takes doc, root, icon, and icon name to produce pixels +guchar * +sp_icon_doc_icon( SPDocument *doc, NRArenaItem *root, + const gchar *name, unsigned int psize ); +} + +namespace Inkscape { +namespace UI { +namespace Dialogs { + + +IconPreviewPanel* IconPreviewPanel::instance = 0; + + +IconPreviewPanel& IconPreviewPanel::getInstance() +{ + if ( !instance ) { + instance = new IconPreviewPanel(); + } + + instance->refreshPreview(); + + return *instance; +} + +//######################################################################### +//## E V E N T S +//######################################################################### + +void IconPreviewPanel::on_button_clicked(int which) +{ + if ( hot != which ) { + buttons[hot]->set_active( false ); + + hot = which; + updateMagnify(); + queue_draw(); + } +} + + + + +//######################################################################### +//## C O N S T R U C T O R / D E S T R U C T O R +//######################################################################### +/** + * Constructor + */ +IconPreviewPanel::IconPreviewPanel() : + Panel(), + hot(1), + refreshButton(0), + selectionButton(0) +{ + numEntries = 0; + Inkscape::XML::Node *things = inkscape_get_repr(INKSCAPE, "iconpreview.sizes.default"); + if (things) { + std::vector rawSizes; + for ( Inkscape::XML::Node *child = things->firstChild(); child; child = child->next() ) + { + gchar const *id = child->attribute("id"); + if ( id ) + { + std::string path("iconpreview.sizes.default."); + path += id; + gint show = prefs_get_int_attribute_limited( path.c_str(), "show", 1, 0, 1 ); + gint sizeVal = prefs_get_int_attribute( path.c_str(), "value", -1 ); + if ( show && (sizeVal > 0) ) + { + rawSizes.push_back( sizeVal ); + } + } + } + + if ( !rawSizes.empty() ) + { + numEntries = rawSizes.size(); + sizes = new int[numEntries]; + int i = 0; + for ( std::vector::iterator it = rawSizes.begin(); it != rawSizes.end(); ++it, ++i ) { + sizes[i] = *it; + } + } + } + + if ( numEntries < 1 ) + { + numEntries = 5; + sizes = new int[numEntries]; + sizes[0] = 16; + sizes[1] = 24; + sizes[2] = 32; + sizes[3] = 48; + sizes[5] = 128; + } + + pixMem = new guchar*[numEntries]; + images = new Gtk::Image*[numEntries]; + labels = new Glib::ustring*[numEntries]; + buttons = new Gtk::ToggleToolButton*[numEntries]; + + + for ( int i = 0; i < numEntries; i++ ) { + char *label = g_strdup_printf(_("%d x %d"), sizes[i], sizes[i]); + labels[i] = new Glib::ustring(label); + g_free(label); + pixMem[i] = 0; + images[i] = 0; + } + + + magLabel.set_label( *labels[hot] ); + + Gtk::VBox* magBox = new Gtk::VBox(); + + magBox->pack_start( magnified ); + magBox->pack_start( magLabel, Gtk::PACK_SHRINK ); + + + Gtk::VBox * verts = new Gtk::VBox(); + for ( int i = 0; i < numEntries; i++ ) { + pixMem[i] = new guchar[4 * sizes[i] * sizes[i]]; + memset( pixMem[i], 0x00, 4 * sizes[i] * sizes[i] ); + + GdkPixbuf *pb = gdk_pixbuf_new_from_data( pixMem[i], GDK_COLORSPACE_RGB, TRUE, 8, sizes[i], sizes[i], sizes[i] * 4, /*(GdkPixbufDestroyNotify)nr_free*/NULL, NULL ); + GtkImage* img = GTK_IMAGE( gtk_image_new_from_pixbuf( pb ) ); + images[i] = Glib::wrap(img); + Glib::ustring label(*labels[i]); + buttons[i] = new Gtk::ToggleToolButton(label); + buttons[i]->set_active( i == hot ); + buttons[i]->set_icon_widget(*images[i]); + + tips.set_tip((*buttons[i]), label); + + buttons[i]->signal_clicked().connect( sigc::bind( sigc::mem_fun(*this, &IconPreviewPanel::on_button_clicked), i) ); + + + verts->add(*buttons[i]); + } + + iconBox.pack_start(splitter); + splitter.pack1( *magBox, true, true ); + splitter.pack2( *verts, false, false ); + + + //## The Refresh button + + + Gtk::HButtonBox* holder = new Gtk::HButtonBox( Gtk::BUTTONBOX_END ); + pack_end( *holder, false, false ); + + selectionButton = new Gtk::ToggleButton(_("Selection")); // , GTK_RESPONSE_APPLY + holder->pack_start( *selectionButton, false, false ); + tips.set_tip((*selectionButton), _("Selection only or whole document")); + selectionButton->signal_clicked().connect( sigc::mem_fun(*this, &IconPreviewPanel::modeToggled) ); + + gint val = prefs_get_int_attribute_limited( "iconpreview", "selectionOnly", 0, 0, 1 ); + selectionButton->set_active( val != 0 ); + + refreshButton = new Gtk::Button(Gtk::Stock::REFRESH); // , GTK_RESPONSE_APPLY + holder->pack_end( *refreshButton, false, false ); + tips.set_tip((*refreshButton), _("Refresh the icons")); + refreshButton->signal_clicked().connect( sigc::mem_fun(*this, &IconPreviewPanel::refreshPreview) ); + + + pack_start(iconBox, Gtk::PACK_EXPAND_WIDGET); + + show_all_children(); +} + +//######################################################################### +//## M E T H O D S +//######################################################################### + + +void IconPreviewPanel::refreshPreview() +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if ( desktop ) { + + if ( selectionButton && selectionButton->get_active() ) + { + Inkscape::Selection * sel = SP_DT_SELECTION(desktop); + if ( sel ) { + //g_message("found a selection to play with"); + + GSList const *items = sel->itemList(); + SPObject *target = 0; + while ( items && !target ) { + SPItem* item = SP_ITEM( items->data ); + SPObject * obj = SP_OBJECT(item); + gchar const *id = SP_OBJECT_ID( obj ); + if ( id ) { + target = obj; + } + + items = g_slist_next(items); + } + if ( target ) { + renderPreview(target); + } + } + } + else + { + SPObject *target = desktop->currentRoot(); + if ( target ) { + renderPreview(target); + } + } + } +} + +void IconPreviewPanel::modeToggled() +{ + prefs_set_int_attribute( "iconpreview", "selectionOnly", (selectionButton && selectionButton->get_active()) ? 1 : 0 ); + + refreshPreview(); +} + +void IconPreviewPanel::renderPreview( SPObject* obj ) +{ + SPDocument * doc = SP_OBJECT_DOCUMENT(obj); + gchar * id = SP_OBJECT_ID(obj); + +// g_message(" setting up to render '%s' as the icon", id ); + + NRArenaItem *root = NULL; + + /* Create new arena */ + NRArena *arena = NRArena::create(); + + /* Create ArenaItem and set transform */ + unsigned int visionkey = sp_item_display_key_new(1); + + /* fixme: Memory manage root if needed (Lauris) */ + root = sp_item_invoke_show ( SP_ITEM( SP_DOCUMENT_ROOT(doc) ), + arena, visionkey, SP_ITEM_SHOW_DISPLAY ); + + for ( int i = 0; i < numEntries; i++ ) { + guchar * px = sp_icon_doc_icon( doc, root, id, sizes[i] ); +// g_message( " size %d %s", sizes[i], (px ? "worked" : "failed") ); + if ( px ) { + memcpy( pixMem[i], px, sizes[i] * sizes[i] * 4 ); + g_free( px ); + px = 0; + } else { + memset( pixMem[i], 0, sizes[i] * sizes[i] * 4 ); + } + images[i]->queue_draw(); + } + updateMagnify(); +} + +void IconPreviewPanel::updateMagnify() +{ + Glib::RefPtr buf = images[hot]->get_pixbuf()->scale_simple( 128, 128, Gdk::INTERP_NEAREST ); + magLabel.set_label( *labels[hot] ); + magnified.set( buf ); + magnified.queue_draw(); + magnified.get_parent()->queue_draw(); +} + + +} //namespace Dialogs +} //namespace UI +} //namespace Inkscape + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : + +//######################################################################### +//## E N D O F F I L E +//######################################################################### diff --git a/src/dialogs/iconpreview.h b/src/dialogs/iconpreview.h new file mode 100644 index 000000000..e4be831d3 --- /dev/null +++ b/src/dialogs/iconpreview.h @@ -0,0 +1,84 @@ +#ifndef SEEN_ICON_PREVIEW_H +#define SEEN_ICON_PREVIEW_H +/* + * A simple dialog for previewing icon representation. + * + * Authors: + * Jon A. Cruz + * Bob Jamison + * Other dudes from The Inkscape Organization + * + * Copyright (C) 2004,2005 The Inkscape Organization + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#include +#include +#include +#include + +#include "ui/widget/panel.h" + +struct SPObject; + +namespace Inkscape { +namespace UI { +namespace Dialogs { + + +/** + * A panel that displays an icon preview + */ +class IconPreviewPanel : public Inkscape::UI::Widget::Panel +{ +public: + IconPreviewPanel(); + //IconPreviewPanel(Glib::ustring const &label); + + static IconPreviewPanel& getInstance(); + + void refreshPreview(); + void modeToggled(); + +private: + IconPreviewPanel(IconPreviewPanel const &); // no copy + IconPreviewPanel &operator=(IconPreviewPanel const &); // no assign + + + void on_button_clicked(int which); + void renderPreview( SPObject* obj ); + void updateMagnify(); + + static IconPreviewPanel* instance; + + Gtk::Tooltips tips; + + Gtk::VBox iconBox; + Gtk::HPaned splitter; + + int hot; + int numEntries; + int* sizes; + + Gtk::Image magnified; + Gtk::Label magLabel; + + Gtk::Button *refreshButton; + Gtk::ToggleButton *selectionButton; + + guchar** pixMem; + Gtk::Image** images; + Glib::ustring** labels; + Gtk::ToggleToolButton** buttons; +}; + +} //namespace Dialogs +} //namespace UI +} //namespace Inkscape + + + +#endif // SEEN_ICON_PREVIEW_H diff --git a/src/dialogs/in-dt-coordsys.cpp b/src/dialogs/in-dt-coordsys.cpp new file mode 100644 index 000000000..54d9ac326 --- /dev/null +++ b/src/dialogs/in-dt-coordsys.cpp @@ -0,0 +1,37 @@ +#include "sp-root.h" + +/** Returns true iff \a item is suitable to be included in the selection, in particular + whether it has a bounding box in the desktop coordinate system for rendering resize handles. + + Descendents of nodes (markers etc.) return false, for example. +*/ +bool in_dt_coordsys(SPObject const &item) +{ + /* Definition based on sp_item_i2doc_affine. */ + SPObject const *child = &item; + g_return_val_if_fail(child != NULL, false); + for(;;) { + if (!SP_IS_ITEM(child)) { + return false; + } + SPObject const * const parent = SP_OBJECT_PARENT(child); + if (parent == NULL) { + break; + } + child = parent; + } + g_assert(SP_IS_ROOT(child)); + /* Relevance: Otherwise, I'm not sure whether to return true or false. */ + return true; +} + +/* + 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 : diff --git a/src/dialogs/in-dt-coordsys.h b/src/dialogs/in-dt-coordsys.h new file mode 100644 index 000000000..6078f9b41 --- /dev/null +++ b/src/dialogs/in-dt-coordsys.h @@ -0,0 +1,19 @@ +#ifndef SEEN_IN_DT_COORDSYS +#define SEEN_IN_DT_COORDSYS +#include "forward.h" + +bool in_dt_coordsys(SPObject const &item); + + +#endif /* !SEEN_IN_DT_COORDSYS */ + +/* + 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 : diff --git a/src/dialogs/input.cpp b/src/dialogs/input.cpp new file mode 100644 index 000000000..da8eba196 --- /dev/null +++ b/src/dialogs/input.cpp @@ -0,0 +1,273 @@ +#define __SP_INPUT_C__ + +/* + * Extended input devices dialog + * + * Authors: + * Nicklas Lindgren + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include + +#include "../inkscape.h" +#include "../macros.h" +#include "../verbs.h" +#include "../interface.h" +#include "../xml/repr.h" + +#include "dialog-events.h" +#include "../prefs-utils.h" + + +static GtkWidget *dlg = NULL; +static win_data wd; + +// impossible original values to make sure they are read from prefs +static gint x = -1000, y = -1000, w = 0, h = 0; +static gchar *prefs_path = "dialogs.input"; + +static void +sp_input_dialog_destroy (GtkObject *object, gpointer data) +{ + sp_signal_disconnect_by_data (INKSCAPE, dlg); + wd.win = dlg = NULL; + wd.stop = 0; +} + +static gboolean +sp_input_dialog_delete (GtkObject *object, GdkEvent *event, gpointer data) +{ + gtk_window_get_position ((GtkWindow *) dlg, &x, &y); + gtk_window_get_size ((GtkWindow *) dlg, &w, &h); + + prefs_set_int_attribute (prefs_path, "x", x); + prefs_set_int_attribute (prefs_path, "y", y); + prefs_set_int_attribute (prefs_path, "w", w); + prefs_set_int_attribute (prefs_path, "h", h); + + return FALSE; // which means, go ahead and destroy it + +} + +static gchar *axis_use_strings[GDK_AXIS_LAST] = { + "ignore", "x", "y", "pressure", "xtilt", "ytilt", "wheel" +}; + +void +sp_input_load_from_preferences (void) +{ + Inkscape::XML::Node *devices = inkscape_get_repr(INKSCAPE, "devices"); + if (devices == NULL) + return; + + Inkscape::XML::Node *repr; + GList *list_ptr; + + for (list_ptr = gdk_devices_list(); list_ptr != NULL; list_ptr = list_ptr->next) { + GdkDevice *device = static_cast(list_ptr->data); + repr = sp_repr_lookup_child(devices, "id", device->name); + if (repr != NULL) { + GdkInputMode mode; + const gchar *attribute = repr->attribute("mode"); + + if (attribute == NULL) + mode = GDK_MODE_DISABLED; + else if (!strcmp(attribute, "screen")) + mode = GDK_MODE_SCREEN; + else if (!strcmp(attribute, "window")) + mode = GDK_MODE_WINDOW; + else + mode = GDK_MODE_DISABLED; + + if (device->mode != mode) { + gdk_device_set_mode(device, mode); + } + + const gchar *temp_ptr; + std::string::size_type pos0; + std::string::size_type pos1; + gint i; + gint j; + + GdkAxisUse axis_use; + + temp_ptr = repr->attribute("axes"); + if (temp_ptr != NULL) { + const std::string temp_str = temp_ptr; + pos0 = pos1 = 0; + for (i=0; i < device->num_axes; i++) { + pos1 = temp_str.find(";", pos0); + if (pos1 == std::string::npos) + break; // Too few axis specifications + + axis_use = GDK_AXIS_IGNORE; + for (j=0; j < GDK_AXIS_LAST; j++) + if (!strcmp(temp_str.substr(pos0, pos1-pos0).c_str(), axis_use_strings[j])) { + axis_use = static_cast(j); + break; + } + gdk_device_set_axis_use(device, i, axis_use); + pos0 = pos1 + 1; + } + } + + guint keyval; + GdkModifierType modifier; + + temp_ptr = repr->attribute("keys"); + if (temp_ptr != NULL) { + const std::string temp_str = temp_ptr; + pos0 = pos1 = 0; + for (i=0; i < device->num_keys; i++) { + pos1 = temp_str.find(";", pos0); + if (pos1 == std::string::npos) + break; // Too few key specifications + + gtk_accelerator_parse(temp_str.substr(pos0, pos1-pos0).c_str(), &keyval, &modifier); + gdk_device_set_key(device, i, keyval, modifier); + pos0 = pos1 + 1; + } + } + } + } +} + +void +sp_input_save_to_preferences (void) +{ + Inkscape::XML::Node *devices = inkscape_get_repr(INKSCAPE, "devices"); + if (devices == NULL) + // TODO: find a clean way to add a node to the preferences root, or + // give an error message + return; + + Inkscape::XML::Node *repr; + GList *list_ptr; + + for (list_ptr = gdk_devices_list(); list_ptr != NULL; list_ptr = list_ptr->next) { + gint i; + std::string temp_attribute; + GdkDevice *device = static_cast(list_ptr->data); + + repr = sp_repr_lookup_child(devices, "id", device->name); + if (repr == NULL) { + repr = sp_repr_new("group"); + repr->setAttribute("id", device->name); + devices->appendChild(repr); + Inkscape::GC::release(repr); + } + switch (device->mode) { + default: + case GDK_MODE_DISABLED: { + repr->setAttribute("mode", "disabled"); + break; + } + case GDK_MODE_SCREEN: { + repr->setAttribute("mode", "screen"); + break; + } + case GDK_MODE_WINDOW: { + repr->setAttribute("mode", "window"); + break; + } + } + + temp_attribute = ""; + for (i=0; i < device->num_axes; i++) { + temp_attribute += axis_use_strings[device->axes[i].use]; + temp_attribute += ";"; + } + repr->setAttribute("axes", temp_attribute.c_str()); + + temp_attribute = ""; + for (i=0; i < device->num_keys; i++) { + temp_attribute += gtk_accelerator_name(device->keys[i].keyval, device->keys[i].modifiers); + temp_attribute += ";"; + } + repr->setAttribute("keys", temp_attribute.c_str()); + } +} + +static void +sp_input_save_button (GtkObject *object, gpointer data) +{ + sp_input_save_to_preferences(); +} + +/** + * \brief Dialog + * + */ +void +sp_input_dialog (void) +{ + if (dlg == NULL) { + + gchar title[500]; + sp_ui_dialog_title_string (Inkscape::Verb::get(SP_VERB_DIALOG_INPUT), title); + + dlg = gtk_input_dialog_new(); + + if (x == -1000 || y == -1000) { + x = prefs_get_int_attribute (prefs_path, "x", 0); + y = prefs_get_int_attribute (prefs_path, "y", 0); + } + + if (w ==0 || h == 0) { + w = prefs_get_int_attribute (prefs_path, "w", 0); + h = prefs_get_int_attribute (prefs_path, "h", 0); + } + + if (x != 0 || y != 0) { + gtk_window_move ((GtkWindow *) dlg, x, y); + } else { + gtk_window_set_position(GTK_WINDOW(dlg), GTK_WIN_POS_CENTER); + } + + if (w && h) { + gtk_window_resize ((GtkWindow *) dlg, w, h); + } + + sp_transientize (dlg); + wd.win = dlg; + wd.stop = 0; + + g_signal_connect ( G_OBJECT (INKSCAPE), "activate_desktop", G_CALLBACK (sp_transientize_callback), &wd); + gtk_signal_connect ( GTK_OBJECT (dlg), "event", GTK_SIGNAL_FUNC (sp_dialog_event_handler), dlg); + gtk_signal_connect ( GTK_OBJECT (dlg), "destroy", G_CALLBACK (sp_input_dialog_destroy), dlg); + gtk_signal_connect ( GTK_OBJECT (dlg), "delete_event", G_CALLBACK (sp_input_dialog_delete), dlg); + g_signal_connect ( G_OBJECT (INKSCAPE), "shut_down", G_CALLBACK (sp_input_dialog_delete), dlg); + g_signal_connect ( G_OBJECT (INKSCAPE), "dialogs_hide", G_CALLBACK (sp_dialog_hide), dlg); + g_signal_connect ( G_OBJECT (INKSCAPE), "dialogs_unhide", G_CALLBACK (sp_dialog_unhide), dlg); + + // Dialog-specific stuff + gtk_signal_connect_object (GTK_OBJECT(GTK_INPUT_DIALOG(dlg)->close_button), + "clicked", + (GtkSignalFunc)gtk_widget_destroy, + GTK_OBJECT(dlg)); + gtk_signal_connect (GTK_OBJECT(GTK_INPUT_DIALOG(dlg)->save_button), + "clicked", + (GtkSignalFunc)sp_input_save_button, NULL); + } + + gtk_window_present ((GtkWindow *) dlg); +} + + +/* + 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 : diff --git a/src/dialogs/input.h b/src/dialogs/input.h new file mode 100644 index 000000000..70e68774e --- /dev/null +++ b/src/dialogs/input.h @@ -0,0 +1,32 @@ +#ifndef __SP_INPUT_H__ +#define __SP_INPUT_H__ + +/** + * \brief Extended input device dialog + * + * Author: + * Nicklas Lindgren + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + +void sp_input_load_from_preferences (void); +void sp_input_save_to_preferences (void); +void sp_input_dialog (void); + + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/dialogs/item-properties.cpp b/src/dialogs/item-properties.cpp new file mode 100644 index 000000000..a8031b13e --- /dev/null +++ b/src/dialogs/item-properties.cpp @@ -0,0 +1,497 @@ +#define __SP_ITEM_PROPERTIES_C__ + +/* + * Object properties dialog + * + * Authors: + * Lauris Kaplinski + * bulia byak + * + * Copyright (C) 1999-2005 Authors + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "helper/window.h" +#include "../widgets/sp-widget.h" +#include "../inkscape.h" +#include "../document.h" +#include "../desktop-handles.h" +#include "../selection.h" +#include "../sp-item.h" +#include "../macros.h" +#include "../verbs.h" +#include "../interface.h" + +#include "dialog-events.h" +#include "../prefs-utils.h" + + +static GtkWidget *dlg = NULL; +static win_data wd; + +// impossible original values to make sure they are read from prefs +static gint x = -1000, y = -1000, w = 0, h = 0; +static gchar *prefs_path = "dialogs.object"; + +static void sp_item_widget_modify_selection (SPWidget *spw, Inkscape::Selection *selection, guint flags, GtkWidget *itemw); +static void sp_item_widget_change_selection (SPWidget *spw, Inkscape::Selection *selection, GtkWidget *itemw); +static void sp_item_widget_setup (SPWidget *spw, Inkscape::Selection *selection); +static void sp_item_widget_sensitivity_toggled (GtkWidget *widget, SPWidget *spw); +static void sp_item_widget_hidden_toggled (GtkWidget *widget, SPWidget *spw); +static void sp_item_widget_label_changed (GtkWidget *widget, SPWidget *spw); + +static void +sp_item_dialog_destroy (GtkObject *object, gpointer data) +{ + sp_signal_disconnect_by_data (INKSCAPE, dlg); + wd.win = dlg = NULL; + wd.stop = 0; +} + +static gboolean +sp_item_dialog_delete (GtkObject *object, GdkEvent *event, gpointer data) +{ + gtk_window_get_position ((GtkWindow *) dlg, &x, &y); + gtk_window_get_size ((GtkWindow *) dlg, &w, &h); + + prefs_set_int_attribute (prefs_path, "x", x); + prefs_set_int_attribute (prefs_path, "y", y); + prefs_set_int_attribute (prefs_path, "w", w); + prefs_set_int_attribute (prefs_path, "h", h); + + return FALSE; // which means, go ahead and destroy it + +} + +/** + * \brief Creates new instance of item properties widget + * + */ +GtkWidget * +sp_item_widget_new (void) +{ + + GtkWidget *spw, *vb, *t, *cb, *l, *f, *tf, *pb; + GtkTextBuffer *desc_buffer; + + GtkTooltips *tt = gtk_tooltips_new(); + + /* Create container widget */ + spw = sp_widget_new_global (INKSCAPE); + gtk_signal_connect ( GTK_OBJECT (spw), "modify_selection", + GTK_SIGNAL_FUNC (sp_item_widget_modify_selection), + spw ); + gtk_signal_connect ( GTK_OBJECT (spw), "change_selection", + GTK_SIGNAL_FUNC (sp_item_widget_change_selection), + spw ); + + vb = gtk_vbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (spw), vb); + + t = gtk_table_new (3, 4, FALSE); + gtk_container_set_border_width(GTK_CONTAINER(t), 4); + gtk_table_set_row_spacings (GTK_TABLE (t), 4); + gtk_table_set_col_spacings (GTK_TABLE (t), 4); + gtk_box_pack_start (GTK_BOX (vb), t, TRUE, TRUE, 0); + + + /* Create the label for the object id */ + l = gtk_label_new_with_mnemonic (_("_Id")); + gtk_misc_set_alignment (GTK_MISC (l), 1, 0.5); + gtk_table_attach ( GTK_TABLE (t), l, 0, 1, 0, 1, + (GtkAttachOptions)( GTK_SHRINK | GTK_FILL ), + (GtkAttachOptions)0, 0, 0 ); + gtk_object_set_data (GTK_OBJECT (spw), "id_label", l); + + /* Create the entry box for the object id */ + tf = gtk_entry_new (); + gtk_tooltips_set_tip (tt, tf, _("The id= attribute (only letters, digits, and the characters .-_: allowed)"), NULL); + gtk_entry_set_max_length (GTK_ENTRY (tf), 64); + gtk_table_attach ( GTK_TABLE (t), tf, 1, 2, 0, 1, + (GtkAttachOptions)( GTK_EXPAND | GTK_FILL ), + (GtkAttachOptions)0, 0, 0 ); + gtk_object_set_data (GTK_OBJECT (spw), "id", tf); + gtk_label_set_mnemonic_widget (GTK_LABEL(l), tf); + + // pressing enter in the id field is the same as clicking Set: + g_signal_connect ( G_OBJECT (tf), "activate", G_CALLBACK (sp_item_widget_label_changed), spw); + // focus is in the id field initially: + gtk_widget_grab_focus (GTK_WIDGET (tf)); + + /* Button for setting the object's id, label, title and description. */ + pb = gtk_button_new_with_mnemonic (_("_Set")); + gtk_table_attach ( GTK_TABLE (t), pb, 2, 3, 0, 1, + (GtkAttachOptions)( GTK_SHRINK | GTK_FILL ), + (GtkAttachOptions)0, 0, 0 ); + gtk_signal_connect ( GTK_OBJECT (pb), "clicked", + GTK_SIGNAL_FUNC (sp_item_widget_label_changed), + spw ); + + /* Create the label for the object label */ + l = gtk_label_new_with_mnemonic (_("_Label")); + gtk_misc_set_alignment (GTK_MISC (l), 1, 0.5); + gtk_table_attach ( GTK_TABLE (t), l, 0, 1, 1, 2, + (GtkAttachOptions)( GTK_SHRINK | GTK_FILL ), + (GtkAttachOptions)0, 0, 0 ); + gtk_object_set_data (GTK_OBJECT (spw), "label_label", l); + + /* Create the entry box for the object label */ + tf = gtk_entry_new (); + gtk_tooltips_set_tip (tt, tf, _("A freeform label for the object"), NULL); + gtk_entry_set_max_length (GTK_ENTRY (tf), 256); + gtk_table_attach ( GTK_TABLE (t), tf, 1, 2, 1, 2, + (GtkAttachOptions)( GTK_EXPAND | GTK_FILL ), + (GtkAttachOptions)0, 0, 0 ); + gtk_object_set_data (GTK_OBJECT (spw), "label", tf); + gtk_label_set_mnemonic_widget (GTK_LABEL(l), tf); + + // pressing enter in the label field is the same as clicking Set: + g_signal_connect ( G_OBJECT (tf), "activate", G_CALLBACK (sp_item_widget_label_changed), spw); + + /* Create the label for the object title */ + l = gtk_label_new (_("Title")); + gtk_misc_set_alignment (GTK_MISC (l), 1, 0.5); + gtk_table_attach ( GTK_TABLE (t), l, 0, 1, 2, 3, + (GtkAttachOptions)( GTK_SHRINK | GTK_FILL ), + (GtkAttachOptions)0, 0, 0 ); + gtk_object_set_data (GTK_OBJECT (spw), "title_label", l); + + /* Create the entry box for the object title */ + tf = gtk_entry_new (); + gtk_widget_set_sensitive (GTK_WIDGET (tf), FALSE); + gtk_entry_set_max_length (GTK_ENTRY (tf), 256); + gtk_table_attach ( GTK_TABLE (t), tf, 1, 3, 2, 3, + (GtkAttachOptions)( GTK_EXPAND | GTK_FILL ), + (GtkAttachOptions)0, 0, 0 ); + gtk_object_set_data (GTK_OBJECT (spw), "title", tf); + + /* Create the frame for the object description */ + f = gtk_frame_new (_("Description")); + gtk_table_attach ( GTK_TABLE (t), f, 0, 3, 3, 4, + (GtkAttachOptions)( GTK_EXPAND | GTK_FILL ), + (GtkAttachOptions)( GTK_EXPAND | GTK_FILL ), 0, 0 ); + gtk_object_set_data (GTK_OBJECT (spw), "desc_frame", l); + + /* Create the text view box for the object description */ + GtkWidget *textframe = gtk_frame_new(NULL); + gtk_container_set_border_width(GTK_CONTAINER(textframe), 4); + gtk_widget_set_sensitive (GTK_WIDGET (textframe), FALSE); + gtk_container_add (GTK_CONTAINER (f), textframe); + gtk_frame_set_shadow_type (GTK_FRAME (textframe), GTK_SHADOW_IN); + + tf = gtk_text_view_new(); + desc_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(tf)); + gtk_text_buffer_set_text(desc_buffer, "", -1); + gtk_container_add (GTK_CONTAINER (textframe), tf); + gtk_object_set_data (GTK_OBJECT (spw), "desc", tf); + + /* Check boxes */ + GtkWidget *hb_cb = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (vb), hb_cb, FALSE, FALSE, 0); + t = gtk_table_new (1, 2, TRUE); + gtk_container_set_border_width(GTK_CONTAINER(t), 0); + gtk_box_pack_start (GTK_BOX (hb_cb), t, TRUE, TRUE, 10); + + /* Hide */ + cb = gtk_check_button_new_with_mnemonic (_("_Hide")); + gtk_tooltips_set_tip (tt, cb, _("Check to make the object invisible"), NULL); + gtk_table_attach ( GTK_TABLE (t), cb, 0, 1, 0, 1, + (GtkAttachOptions)( GTK_EXPAND | GTK_FILL ), + (GtkAttachOptions)0, 0, 0 ); + g_signal_connect (G_OBJECT(cb), "toggled", G_CALLBACK(sp_item_widget_hidden_toggled), spw); + gtk_object_set_data(GTK_OBJECT(spw), "hidden", cb); + + /* Lock */ + // TRANSLATORS: "Lock" is a verb here + cb = gtk_check_button_new_with_mnemonic (_("L_ock")); + gtk_tooltips_set_tip (tt, cb, _("Check to make the object insensitive (not selectable by mouse)"), NULL); + gtk_table_attach ( GTK_TABLE (t), cb, 1, 2, 0, 1, + (GtkAttachOptions)( GTK_EXPAND | GTK_FILL ), + (GtkAttachOptions)0, 0, 0 ); + gtk_signal_connect ( GTK_OBJECT (cb), "toggled", + GTK_SIGNAL_FUNC (sp_item_widget_sensitivity_toggled), + spw ); + gtk_object_set_data (GTK_OBJECT (spw), "sensitive", cb); + + gtk_widget_show_all (spw); + + sp_item_widget_setup (SP_WIDGET (spw), SP_DT_SELECTION (SP_ACTIVE_DESKTOP)); + + return (GtkWidget *) spw; + +} //end of sp_item_widget_new() + + + +static void +sp_item_widget_modify_selection ( SPWidget *spw, + Inkscape::Selection *selection, + guint flags, + GtkWidget *itemw ) +{ + sp_item_widget_setup (spw, selection); +} + + + +static void +sp_item_widget_change_selection ( SPWidget *spw, + Inkscape::Selection *selection, + GtkWidget *itemw ) +{ + sp_item_widget_setup (spw, selection); +} + + +/** +* \param selection Selection to use; should not be NULL. +*/ +static void +sp_item_widget_setup ( SPWidget *spw, Inkscape::Selection *selection ) +{ + g_assert (selection != NULL); + + if (gtk_object_get_data (GTK_OBJECT (spw), "blocked")) + return; + + if (!selection->singleItem()) { + gtk_widget_set_sensitive (GTK_WIDGET (spw), FALSE); + return; + } else { + gtk_widget_set_sensitive (GTK_WIDGET (spw), TRUE); + } + + gtk_object_set_data (GTK_OBJECT (spw), "blocked", GUINT_TO_POINTER (TRUE)); + + SPItem *item = selection->singleItem(); + + /* Sensitive */ + GtkWidget *w = GTK_WIDGET(gtk_object_get_data (GTK_OBJECT (spw), "sensitive")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w), item->isLocked()); + + /* Hidden */ + w = GTK_WIDGET(gtk_object_get_data (GTK_OBJECT (spw), "hidden")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), item->isExplicitlyHidden()); + + if (SP_OBJECT_IS_CLONED (item)) { + + /* ID */ + w = GTK_WIDGET(gtk_object_get_data (GTK_OBJECT (spw), "id")); + gtk_entry_set_text (GTK_ENTRY (w), ""); + gtk_widget_set_sensitive (w, FALSE); + w = GTK_WIDGET(gtk_object_get_data (GTK_OBJECT (spw), "id_label")); + gtk_label_set_text (GTK_LABEL (w), _("Ref")); + + /* Label */ + w = GTK_WIDGET(gtk_object_get_data (GTK_OBJECT (spw), "label")); + gtk_entry_set_text (GTK_ENTRY (w), ""); + gtk_widget_set_sensitive (w, FALSE); + w = GTK_WIDGET(gtk_object_get_data (GTK_OBJECT (spw), "label_label")); + gtk_label_set_text (GTK_LABEL (w), _("Ref")); + + } else { + SPObject *obj = (SPObject*)item; + + /* ID */ + w = GTK_WIDGET(gtk_object_get_data (GTK_OBJECT (spw), "id")); + gtk_entry_set_text (GTK_ENTRY (w), obj->id); + gtk_widget_set_sensitive (w, TRUE); + w = GTK_WIDGET(gtk_object_get_data (GTK_OBJECT (spw), "id_label")); + gtk_label_set_markup_with_mnemonic (GTK_LABEL (w), _("_Id")); + + /* Label */ + w = GTK_WIDGET(gtk_object_get_data (GTK_OBJECT (spw), "label")); + gtk_entry_set_text (GTK_ENTRY (w), obj->defaultLabel()); + gtk_widget_set_sensitive (w, TRUE); + w = GTK_WIDGET(gtk_object_get_data (GTK_OBJECT (spw), "label_label")); + } + + gtk_object_set_data (GTK_OBJECT (spw), "blocked", GUINT_TO_POINTER (FALSE)); + + +} // end of sp_item_widget_setup() + + + +static void +sp_item_widget_sensitivity_toggled (GtkWidget *widget, SPWidget *spw) +{ + if (gtk_object_get_data (GTK_OBJECT (spw), "blocked")) + return; + + SPItem *item = SP_DT_SELECTION(SP_ACTIVE_DESKTOP)->singleItem(); + g_return_if_fail (item != NULL); + + gtk_object_set_data (GTK_OBJECT (spw), "blocked", GUINT_TO_POINTER (TRUE)); + + item->setLocked(gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))); + + sp_document_maybe_done (SP_ACTIVE_DOCUMENT, "ItemDialog:insensitive"); + + gtk_object_set_data (GTK_OBJECT (spw), "blocked", GUINT_TO_POINTER (FALSE)); +} + +void +sp_item_widget_hidden_toggled(GtkWidget *widget, SPWidget *spw) +{ + if (gtk_object_get_data (GTK_OBJECT (spw), "blocked")) + return; + + SPItem *item = SP_DT_SELECTION(SP_ACTIVE_DESKTOP)->singleItem(); + g_return_if_fail (item != NULL); + + gtk_object_set_data (GTK_OBJECT (spw), "blocked", GUINT_TO_POINTER (TRUE)); + + item->setExplicitlyHidden(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))); + + sp_document_maybe_done (SP_ACTIVE_DOCUMENT, "ItemDialog:visiblity"); + + gtk_object_set_data (GTK_OBJECT (spw), "blocked", GUINT_TO_POINTER (FALSE)); +} + +static void +sp_item_widget_label_changed (GtkWidget *widget, SPWidget *spw) +{ + if (gtk_object_get_data (GTK_OBJECT (spw), "blocked")) + return; + + SPItem *item = SP_DT_SELECTION(SP_ACTIVE_DESKTOP)->singleItem(); + g_return_if_fail (item != NULL); + + gtk_object_set_data (GTK_OBJECT (spw), "blocked", GUINT_TO_POINTER (TRUE)); + + /* Retrieve the label widget for the object's id */ + GtkWidget *id_entry = GTK_WIDGET(gtk_object_get_data (GTK_OBJECT (spw), "id")); + gchar *id = (gchar *) gtk_entry_get_text (GTK_ENTRY (id_entry)); + g_strcanon (id, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.:", '_'); + GtkWidget *id_label = GTK_WIDGET(gtk_object_get_data (GTK_OBJECT (spw), "id_label")); + if (!strcmp (id, SP_OBJECT_ID(item))) { + gtk_label_set_markup_with_mnemonic (GTK_LABEL (id_label), _("_Id")); + } else if (!*id || !isalnum (*id)) { + gtk_label_set_text (GTK_LABEL (id_label), _("Id invalid! ")); + } else if (SP_ACTIVE_DOCUMENT->getObjectById(id) != NULL) { + gtk_label_set_text (GTK_LABEL (id_label), _("Id exists! ")); + } else { + SPException ex; + gtk_label_set_markup_with_mnemonic (GTK_LABEL (id_label), _("_Id")); + SP_EXCEPTION_INIT (&ex); + sp_object_setAttribute (SP_OBJECT (item), "id", id, &ex); + sp_document_maybe_done (SP_ACTIVE_DOCUMENT, "ItemDialog:id"); + } + + /* Retrieve the label widget for the object's label */ + GtkWidget *label_entry = GTK_WIDGET(gtk_object_get_data (GTK_OBJECT (spw), "label")); + gchar *label = (gchar *)gtk_entry_get_text (GTK_ENTRY (label_entry)); + g_assert(label != NULL); + + /* Give feedback on success of setting the drawing object's label + * using the widget's label text + */ + SPObject *obj = (SPObject*)item; + if (strcmp (label, obj->defaultLabel())) { + obj->setLabel(label); + sp_document_maybe_done (SP_ACTIVE_DOCUMENT, "inkscape:label"); + } + + /* Retrieve the title */ + GtkWidget *w = GTK_WIDGET(gtk_object_get_data (GTK_OBJECT (spw), "title")); + gchar *title = (gchar *)gtk_entry_get_text (GTK_ENTRY (w)); + if (title != NULL) { + obj->setTitle(title); + sp_document_maybe_done (SP_ACTIVE_DOCUMENT, "title"); + } + + /* Retrieve the description */ + gchar *desc = NULL; /* TODO: get text from text buffer */ + if (desc != NULL) { + obj->setDesc(desc); + sp_document_maybe_done (SP_ACTIVE_DOCUMENT, "desc"); + } + + gtk_object_set_data (GTK_OBJECT (spw), "blocked", GUINT_TO_POINTER (FALSE)); + +} // end of sp_item_widget_label_changed() + + +/** + * \brief Dialog + * + */ +void +sp_item_dialog (void) +{ + if (dlg == NULL) { + + gchar title[500]; + sp_ui_dialog_title_string (Inkscape::Verb::get(SP_VERB_DIALOG_ITEM), title); + + dlg = sp_window_new (title, TRUE); + if (x == -1000 || y == -1000) { + x = prefs_get_int_attribute (prefs_path, "x", 0); + y = prefs_get_int_attribute (prefs_path, "y", 0); + } + + if (w ==0 || h == 0) { + w = prefs_get_int_attribute (prefs_path, "w", 0); + h = prefs_get_int_attribute (prefs_path, "h", 0); + } + + if (x != 0 || y != 0) { + gtk_window_move ((GtkWindow *) dlg, x, y); + } else { + gtk_window_set_position(GTK_WINDOW(dlg), GTK_WIN_POS_CENTER); + } + + if (w && h) { + gtk_window_resize ((GtkWindow *) dlg, w, h); + } + + sp_transientize (dlg); + wd.win = dlg; + wd.stop = 0; + + g_signal_connect ( G_OBJECT (INKSCAPE), "activate_desktop", G_CALLBACK (sp_transientize_callback), &wd); + gtk_signal_connect ( GTK_OBJECT (dlg), "event", GTK_SIGNAL_FUNC (sp_dialog_event_handler), dlg); + gtk_signal_connect ( GTK_OBJECT (dlg), "destroy", G_CALLBACK (sp_item_dialog_destroy), dlg); + gtk_signal_connect ( GTK_OBJECT (dlg), "delete_event", G_CALLBACK (sp_item_dialog_delete), dlg); + g_signal_connect ( G_OBJECT (INKSCAPE), "shut_down", G_CALLBACK (sp_item_dialog_delete), dlg); + g_signal_connect ( G_OBJECT (INKSCAPE), "dialogs_hide", G_CALLBACK (sp_dialog_hide), dlg); + g_signal_connect ( G_OBJECT (INKSCAPE), "dialogs_unhide", G_CALLBACK (sp_dialog_unhide), dlg); + + // Dialog-specific stuff + GtkWidget *itemw = sp_item_widget_new (); + gtk_widget_show (itemw); + gtk_container_add (GTK_CONTAINER (dlg), itemw); + + } + + gtk_window_present ((GtkWindow *) dlg); +} + + +/* + 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 : diff --git a/src/dialogs/item-properties.h b/src/dialogs/item-properties.h new file mode 100644 index 000000000..a81034aae --- /dev/null +++ b/src/dialogs/item-properties.h @@ -0,0 +1,38 @@ +#ifndef __SP_ITEM_PROPERTIES_H__ +#define __SP_ITEM_PROPERTIES_H__ + +/** + * \brief Display settings dialog + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001 Ximian, Inc. + * + */ + +#include + + + +#include +#include "../forward.h" + +GtkWidget *sp_item_widget_new (void); + +void sp_item_dialog (void); + + + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/dialogs/layer-properties.cpp b/src/dialogs/layer-properties.cpp new file mode 100644 index 000000000..8806e97f0 --- /dev/null +++ b/src/dialogs/layer-properties.cpp @@ -0,0 +1,199 @@ +/** + * + * \brief Dialog for renaming layers + * + * Author: + * Bryce W. Harrington + * + * Copyright (C) 2004 Bryce Harrington + * + * Released under GNU GPL. Read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +#include +#include "inkscape.h" +#include "desktop.h" +#include "document.h" +#include "message-stack.h" +#include "desktop-handles.h" +#include "layer-fns.h" +#include "sp-object.h" + +#include "layer-properties.h" + +namespace Inkscape { +namespace UI { +namespace Dialogs { + +LayerPropertiesDialog::LayerPropertiesDialog() +: _strategy(NULL), _desktop(NULL), _layer(NULL) +{ + GtkWidget *dlg = GTK_WIDGET(gobj()); + g_assert(dlg); + + Gtk::VBox *mainVBox = get_vbox(); + + // Layer name widgets + _layer_name_entry.set_activates_default(true); + _layer_name_hbox.pack_end(_layer_name_entry, false, false, 4); + _layer_name_label.set_label(_("Layer name:")); + _layer_name_hbox.pack_end(_layer_name_label, false, false, 4); + mainVBox->pack_start(_layer_name_hbox, false, false, 4); + + // Buttons + _close_button.set_use_stock(true); + _close_button.set_label(Gtk::Stock::CANCEL.id); + _close_button.set_flags(Gtk::CAN_DEFAULT); + + _apply_button.set_use_underline(true); + _apply_button.set_flags(Gtk::CAN_DEFAULT); + + _close_button.signal_clicked() + .connect(sigc::mem_fun(*this, &LayerPropertiesDialog::_close)); + _apply_button.signal_clicked() + .connect(sigc::mem_fun(*this, &LayerPropertiesDialog::_apply)); + + signal_delete_event().connect( + sigc::bind_return( + sigc::hide(sigc::mem_fun(*this, &LayerPropertiesDialog::_close)), + true + ) + ); + + add_action_widget(_close_button, Gtk::RESPONSE_CLOSE); + add_action_widget(_apply_button, Gtk::RESPONSE_APPLY); + + _apply_button.grab_default(); + + show_all_children(); +} + +LayerPropertiesDialog::~LayerPropertiesDialog() { + _setDesktop(NULL); + _setLayer(NULL); +} + +void LayerPropertiesDialog::_showDialog(LayerPropertiesDialog::Strategy &strategy, + SPDesktop *desktop, SPObject *layer) +{ + LayerPropertiesDialog *dialog = new LayerPropertiesDialog(); + + dialog->_strategy = &strategy; + dialog->_setDesktop(desktop); + dialog->_setLayer(layer); + + dialog->_strategy->setup(*dialog); + + dialog->set_modal(true); + desktop->setWindowTransient (dialog->gobj()); + dialog->property_destroy_with_parent() = true; + + dialog->show(); + dialog->present(); +} + +void +LayerPropertiesDialog::_apply() +{ + g_assert(_strategy != NULL); + + _strategy->perform(*this); + sp_document_done(SP_DT_DOCUMENT(SP_ACTIVE_DESKTOP)); + + _close(); +} + +void +LayerPropertiesDialog::_close() +{ + _setLayer(NULL); + _setDesktop(NULL); + destroy_(); + Glib::signal_idle().connect( + sigc::bind_return( + sigc::bind(sigc::ptr_fun(&::operator delete), this), + false + ) + ); +} + +void LayerPropertiesDialog::Rename::setup(LayerPropertiesDialog &dialog) { + SPDesktop *desktop=dialog._desktop; + dialog.set_title(_("Rename Layer")); + gchar const *name = desktop->currentLayer()->label(); + dialog._layer_name_entry.set_text(( name ? name : "" )); + dialog._apply_button.set_label(_("_Rename")); +} + +void LayerPropertiesDialog::Rename::perform(LayerPropertiesDialog &dialog) { + SPDesktop *desktop=dialog._desktop; + Glib::ustring name(dialog._layer_name_entry.get_text()); + desktop->currentLayer()->setLabel( + ( name.empty() ? NULL : (gchar *)name.c_str() ) + ); + sp_document_done(SP_DT_DOCUMENT(desktop)); + // TRANSLATORS: This means "The layer has been renamed" + desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Renamed layer")); +} + +void LayerPropertiesDialog::Create::setup(LayerPropertiesDialog &dialog) { + dialog.set_title(_("Add Layer")); + dialog._layer_name_entry.set_text(""); + dialog._apply_button.set_label(_("_Add")); +} + +void LayerPropertiesDialog::Create::perform(LayerPropertiesDialog &dialog) { + SPDesktop *desktop=dialog._desktop; + SPObject *new_layer=Inkscape::create_layer( + desktop->currentRoot(), dialog._layer + ); + Glib::ustring name(dialog._layer_name_entry.get_text()); + if (!name.empty()) { + new_layer->setLabel((gchar *)name.c_str()); + } + SP_DT_SELECTION(desktop)->clear(); + desktop->setCurrentLayer(new_layer); + desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("New layer created.")); +} + +void LayerPropertiesDialog::_setDesktop(SPDesktop *desktop) { + if (desktop) { + Inkscape::GC::anchor (desktop); + } + if (_desktop) { + Inkscape::GC::release (_desktop); + } + _desktop = desktop; +} + +void LayerPropertiesDialog::_setLayer(SPObject *layer) { + if (layer) { + sp_object_ref(layer, NULL); + } + if (_layer) { + sp_object_unref(_layer, NULL); + } + _layer = layer; +} + +} // namespace +} // namespace +} // namespace + + +/* + 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 : diff --git a/src/dialogs/layer-properties.h b/src/dialogs/layer-properties.h new file mode 100644 index 000000000..15404b2e5 --- /dev/null +++ b/src/dialogs/layer-properties.h @@ -0,0 +1,110 @@ +/** + * + * \brief Dialog for renaming layers + * + * Author: + * Bryce W. Harrington + * + * Copyright (C) 2004 Bryce Harrington + * + * Released under GNU GPL. Read the file 'COPYING' for more information + */ + +#ifndef INKSCAPE_DIALOG_LAYER_PROPERTIES_H +#define INKSCAPE_DIALOG_LAYER_PROPERTIES_H + +#include +#include +#include +#include +#include +#include +#include + +#include "selection.h" + +namespace Inkscape { +namespace UI { +namespace Dialogs { + +class LayerPropertiesDialog : public Gtk::Dialog { + public: + LayerPropertiesDialog(); + virtual ~LayerPropertiesDialog(); + + Glib::ustring getName() const { return "LayerPropertiesDialog"; } + + static void showRename(SPDesktop *desktop, SPObject *layer) { + _showDialog(Rename::instance(), desktop, layer); + } + static void showCreate(SPDesktop *desktop, SPObject *layer) { + _showDialog(Create::instance(), desktop, layer); + } + +protected: + struct Strategy { + virtual ~Strategy() {} + virtual void setup(LayerPropertiesDialog &)=0; + virtual void perform(LayerPropertiesDialog &)=0; + }; + struct Rename : public Strategy { + static Rename &instance() { static Rename instance; return instance; } + void setup(LayerPropertiesDialog &dialog); + void perform(LayerPropertiesDialog &dialog); + }; + struct Create : public Strategy { + static Create &instance() { static Create instance; return instance; } + void setup(LayerPropertiesDialog &dialog); + void perform(LayerPropertiesDialog &dialog); + }; + + friend class Rename; + friend class Create; + + Strategy *_strategy; + SPDesktop *_desktop; + SPObject *_layer; + + Gtk::HBox _layer_name_hbox; + Gtk::Label _layer_name_label; + Gtk::Entry _layer_name_entry; + + Gtk::Button _close_button; + Gtk::Button _apply_button; + + sigc::connection _destroy_connection; + + static LayerPropertiesDialog &_instance() { + static LayerPropertiesDialog instance; + return instance; + } + + void _setDesktop(SPDesktop *desktop); + void _setLayer(SPObject *layer); + + static void _showDialog(Strategy &strategy, SPDesktop *desktop, SPObject *layer); + void _apply(); + void _close(); + +private: + LayerPropertiesDialog(LayerPropertiesDialog const &); // no copy + LayerPropertiesDialog &operator=(LayerPropertiesDialog const &); // no assign +}; + +} // namespace +} // namespace +} // namespace + + +#endif //INKSCAPE_DIALOG_LAYER_PROPERTIES_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:encoding=utf-8:textwidth=99 : diff --git a/src/dialogs/makefile.in b/src/dialogs/makefile.in new file mode 100644 index 000000000..5f39e1899 --- /dev/null +++ b/src/dialogs/makefile.in @@ -0,0 +1,17 @@ +# Convenience stub makefile to call the real Makefile. + +@SET_MAKE@ + +# Explicit so that it's the default rule. +all: + cd .. && $(MAKE) dialogs/all + +clean %.a %.o: + cd .. && $(MAKE) dialogs/$@ + +.PHONY: all clean + +OBJEXT = @OBJEXT@ + +.SUFFIXES: +.SUFFIXES: .a .$(OBJEXT) diff --git a/src/dialogs/object-attributes.cpp b/src/dialogs/object-attributes.cpp new file mode 100644 index 000000000..05370d1d0 --- /dev/null +++ b/src/dialogs/object-attributes.cpp @@ -0,0 +1,140 @@ +#define __SP_OBJECT_ATTRIBUTES_C__ + +/** + * \brief Generic properties editor + * + * Authors: + * Lauris Kaplinski + * bulia byak + * + * Copyright (C) 1999-2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include +#include "helper/window.h" +#include "macros.h" +#include "sp-anchor.h" +#include "sp-attribute-widget.h" + +struct SPAttrDesc { + gchar const *label; + gchar const *attribute; +}; + +static const SPAttrDesc anchor_desc[] = { + { N_("Href:"), "xlink:href"}, + { N_("Target:"), "target"}, + { N_("Type:"), "xlink:type"}, + // TRANSLATORS: for info, see http://www.w3.org/TR/2000/CR-SVG-20000802/linking.html#AElementXLinkRoleAttribute + // Identifies the type of the related resource with an absolute URI + { N_("Role:"), "xlink:role"}, + // TRANSLATORS: for info, see http://www.w3.org/TR/2000/CR-SVG-20000802/linking.html#AElementXLinkArcRoleAttribute + // For situations where the nature/role alone isn't enough, this offers an additional URI defining the purpose of the link. + { N_("Arcrole:"), "xlink:arcrole"}, + // TRANSLATORS: for info, see http://www.w3.org/TR/2000/CR-SVG-20000802/linking.html#AElementXLinkTitleAttribute + { N_("Title:"), "xlink:title"}, + { N_("Show:"), "xlink:show"}, + // TRANSLATORS: for info, see http://www.w3.org/TR/2000/CR-SVG-20000802/linking.html#AElementXLinkActuateAttribute + { N_("Actuate:"), "xlink:actuate"}, + { NULL, NULL} +}; + +static const SPAttrDesc image_desc[] = { + { N_("URL:"), "xlink:href"}, + { N_("X:"), "x"}, + { N_("Y:"), "y"}, + { N_("Width:"), "width"}, + { N_("Height:"), "height"}, + { NULL, NULL} +}; + + +static void +object_released (GtkObject *object, GtkWidget *widget) +{ + gtk_widget_destroy (widget); +} + + + +static void +window_destroyed (GtkObject *window, GtkObject *object) +{ + sp_signal_disconnect_by_data (object, window); +} + + + +static void +sp_object_attr_show_dialog ( SPObject *object, + const SPAttrDesc *desc, + const gchar *tag ) +{ + const gchar **labels, **attrs; + gint len, i; + gchar *title; + GtkWidget *w, *t; + + len = 0; + while (desc[len].label) len += 1; + + labels = (const gchar **)alloca (len * sizeof (char *)); + attrs = (const gchar **)alloca (len * sizeof (char *)); + + for (i = 0; i < len; i++) { + labels[i] = desc[i].label; + attrs[i] = desc[i].attribute; + } + + title = g_strdup_printf (_("%s attributes"), tag); + w = sp_window_new (title, TRUE); + g_free (title); + + t = sp_attribute_table_new (object, len, labels, attrs); + gtk_widget_show (t); + gtk_container_add (GTK_CONTAINER (w), t); + + g_signal_connect ( G_OBJECT (w), "destroy", + G_CALLBACK (window_destroyed), object ); + + g_signal_connect ( G_OBJECT (object), "release", + G_CALLBACK (object_released), w ); + + gtk_widget_show (w); + +} // end of sp_object_attr_show_dialog() + + + +void +sp_object_attributes_dialog (SPObject *object, const gchar *tag) +{ + g_return_if_fail (object != NULL); + g_return_if_fail (SP_IS_OBJECT (object)); + g_return_if_fail (tag != NULL); + + if (!strcmp (tag, "Link")) { + sp_object_attr_show_dialog (object, anchor_desc, tag); + } else if (!strcmp (tag, "Image")) { + sp_object_attr_show_dialog (object, image_desc, tag); + } + +} // end of sp_object_attributes_dialog() + + + +/* + 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 : diff --git a/src/dialogs/object-attributes.h b/src/dialogs/object-attributes.h new file mode 100644 index 000000000..2fc562e16 --- /dev/null +++ b/src/dialogs/object-attributes.h @@ -0,0 +1,37 @@ +#ifndef __SP_OBJECT_ATTRIBUTES_H__ +#define __SP_OBJECT_ATTRIBUTES_H__ + +/** + * \brief Generic object attribute editor + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001 Ximian, Inc. + * + * Licensed under GNU GPL + */ + +#include + + + +#include +#include "../forward.h" + +void sp_object_attributes_dialog (SPObject *object, const gchar *tag); + + + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/dialogs/object-properties.cpp b/src/dialogs/object-properties.cpp new file mode 100644 index 000000000..9cbf1c3ea --- /dev/null +++ b/src/dialogs/object-properties.cpp @@ -0,0 +1,318 @@ +#define __OBJECT_PROPERTIES_C__ + +/** + * \brief Fill, stroke, and stroke style dialog + * + * Authors: + * Lauris Kaplinski + * Frank Felfe + * bulia byak + * + * Copyright (C) 1999-2005 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include +#include "helper/window.h" +#include "widgets/sp-widget.h" +#include "widgets/icon.h" +#include "macros.h" +#include "inkscape.h" +#include "fill-style.h" +#include "stroke-style.h" +#include "dialog-events.h" +#include "verbs.h" +#include "interface.h" +#include "style.h" +#include "inkscape-stock.h" +#include "prefs-utils.h" +#include "svg/css-ostringstream.h" +#include "desktop-handles.h" +#include "desktop-style.h" +#include "document.h" +#include "xml/repr.h" + +static GtkWidget *dlg = NULL; +static win_data wd; + +// impossible original values to make sure they are read from prefs +static gint x = -1000, y = -1000, w = 0, h = 0; +static gchar *prefs_path = "dialogs.fillstroke"; + +static void sp_fillstroke_selection_modified ( Inkscape::Application *inkscape, Inkscape::Selection *selection, guint flags, GtkObject *base ); +static void sp_fillstroke_selection_changed ( Inkscape::Application *inkscape, Inkscape::Selection *selection, GtkObject *base ); +static void sp_fillstroke_opacity_changed (GtkAdjustment *a, SPWidget *dlg); + +static void +sp_object_properties_dialog_destroy (GtkObject *object, gpointer data) +{ + sp_signal_disconnect_by_data (INKSCAPE, dlg); + wd.win = dlg = NULL; + wd.stop = 0; +} + +static gboolean +sp_object_properties_dialog_delete ( GtkObject *object, + GdkEvent *event, + gpointer data ) +{ + + gtk_window_get_position ((GtkWindow *) dlg, &x, &y); + gtk_window_get_size ((GtkWindow *) dlg, &w, &h); + + prefs_set_int_attribute (prefs_path, "x", x); + prefs_set_int_attribute (prefs_path, "y", y); + prefs_set_int_attribute (prefs_path, "w", w); + prefs_set_int_attribute (prefs_path, "h", h); + + return FALSE; // which means, go ahead and destroy it + +} + + +void +sp_object_properties_page( GtkWidget *nb, + GtkWidget *page, + char *label, + char *dlg_name, + char *label_image ) +{ + GtkWidget *hb, *l, *px; + + hb = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hb); + + px = sp_icon_new( GTK_ICON_SIZE_MENU, label_image ); + gtk_widget_show (px); + gtk_box_pack_start (GTK_BOX (hb), px, FALSE, FALSE, 2); + + l = gtk_label_new_with_mnemonic (label); + gtk_widget_show (l); + gtk_box_pack_start (GTK_BOX (hb), l, FALSE, FALSE, 0); + + gtk_widget_show (page); + gtk_notebook_append_page (GTK_NOTEBOOK (nb), page, hb); + gtk_object_set_data (GTK_OBJECT (dlg), dlg_name, page); +} + +void +sp_object_properties_dialog (void) +{ + if (!dlg) { + gchar title[500]; + sp_ui_dialog_title_string (Inkscape::Verb::get(SP_VERB_DIALOG_FILL_STROKE), title); + + dlg = sp_window_new (title, TRUE); + if (x == -1000 || y == -1000) { + x = prefs_get_int_attribute (prefs_path, "x", 0); + y = prefs_get_int_attribute (prefs_path, "y", 0); + } + if (w ==0 || h == 0) { + w = prefs_get_int_attribute (prefs_path, "w", 0); + h = prefs_get_int_attribute (prefs_path, "h", 0); + } + if (x != 0 || y != 0) + gtk_window_move ((GtkWindow *) dlg, x, y); + else + gtk_window_set_position(GTK_WINDOW(dlg), GTK_WIN_POS_CENTER); + if (w && h) gtk_window_resize ((GtkWindow *) dlg, w, h); + sp_transientize (dlg); + wd.win = dlg; + wd.stop = 0; + + g_signal_connect ( G_OBJECT (INKSCAPE), "activate_desktop", G_CALLBACK (sp_transientize_callback), &wd ); + + gtk_signal_connect ( GTK_OBJECT (dlg), "event", GTK_SIGNAL_FUNC (sp_dialog_event_handler), dlg ); + + gtk_signal_connect ( GTK_OBJECT (dlg), "destroy", G_CALLBACK (sp_object_properties_dialog_destroy), dlg ); + gtk_signal_connect ( GTK_OBJECT (dlg), "delete_event", G_CALLBACK (sp_object_properties_dialog_delete), dlg ); + g_signal_connect ( G_OBJECT (INKSCAPE), "shut_down", G_CALLBACK (sp_object_properties_dialog_delete), dlg ); + + g_signal_connect ( G_OBJECT (INKSCAPE), "dialogs_hide", G_CALLBACK (sp_dialog_hide), dlg ); + g_signal_connect ( G_OBJECT (INKSCAPE), "dialogs_unhide", G_CALLBACK (sp_dialog_unhide), dlg ); + + GtkWidget *vb = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vb); + gtk_container_add (GTK_CONTAINER (dlg), vb); + + GtkWidget *nb = gtk_notebook_new (); + gtk_widget_show (nb); + gtk_box_pack_start (GTK_BOX (vb), nb, TRUE, TRUE, 0); + gtk_object_set_data (GTK_OBJECT (dlg), "notebook", nb); + + /* Fill page */ + { + GtkWidget *page = sp_fill_style_widget_new (); + sp_object_properties_page(nb, page, _("_Fill"), "fill", + INKSCAPE_STOCK_PROPERTIES_FILL_PAGE); + } + + /* Stroke paint page */ + { + GtkWidget *page = sp_stroke_style_paint_widget_new (); + sp_object_properties_page(nb, page, _("Stroke _paint"), "stroke-paint", + INKSCAPE_STOCK_PROPERTIES_STROKE_PAINT_PAGE); + } + + /* Stroke style page */ + { + GtkWidget *page = sp_stroke_style_line_widget_new (); + sp_object_properties_page(nb, page, _("Stroke st_yle"), "stroke-line", + INKSCAPE_STOCK_PROPERTIES_STROKE_PAGE); + } + + /* Opacity */ + + GtkWidget *o_vb = gtk_vbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (vb), o_vb, FALSE, FALSE, 2); + gtk_object_set_data (GTK_OBJECT (dlg), "master_opacity", o_vb); + + GtkWidget *l_hb = gtk_hbox_new (FALSE, 4); + GtkWidget *l = gtk_label_new_with_mnemonic (_("Master _opacity")); + gtk_misc_set_alignment (GTK_MISC (l), 0.0, 1.0); + gtk_box_pack_start (GTK_BOX (l_hb), l, FALSE, FALSE, 4); + gtk_box_pack_start (GTK_BOX (o_vb), l_hb, FALSE, FALSE, 0); + + GtkWidget *hb = gtk_hbox_new (FALSE, 4); + gtk_box_pack_start (GTK_BOX (o_vb), hb, FALSE, FALSE, 0); + + GtkObject *a = gtk_adjustment_new (1.0, 0.0, 1.0, 0.01, 0.1, 0.0); + gtk_object_set_data(GTK_OBJECT(dlg), "master_opacity_adjustment", a); + + GtkWidget *s = gtk_hscale_new (GTK_ADJUSTMENT (a)); + gtk_scale_set_draw_value (GTK_SCALE (s), FALSE); + gtk_box_pack_start (GTK_BOX (hb), s, TRUE, TRUE, 4); + gtk_label_set_mnemonic_widget (GTK_LABEL(l), s); + + GtkWidget *sb = gtk_spin_button_new (GTK_ADJUSTMENT (a), 0.01, 3); + gtk_box_pack_start (GTK_BOX (hb), sb, FALSE, FALSE, 0); + + gtk_signal_connect ( a, "value_changed", + GTK_SIGNAL_FUNC (sp_fillstroke_opacity_changed), + dlg ); + + gtk_widget_show_all (o_vb); + + // these callbacks are only for the master opacity update; the tabs above take care of themselves + g_signal_connect ( G_OBJECT (INKSCAPE), "change_selection", G_CALLBACK (sp_fillstroke_selection_changed), dlg ); + g_signal_connect ( G_OBJECT (INKSCAPE), "modify_selection", G_CALLBACK (sp_fillstroke_selection_modified), dlg ); + g_signal_connect ( G_OBJECT (INKSCAPE), "activate_desktop", G_CALLBACK (sp_fillstroke_selection_changed), dlg ); + + sp_fillstroke_selection_changed(NULL, NULL, NULL); + + gtk_widget_show (dlg); + + } else { + gtk_window_present (GTK_WINDOW (dlg)); + } + +} // end of sp_object_properties_dialog() + +void sp_object_properties_fill (void) +{ + sp_object_properties_dialog (); + GtkWidget *nb = (GtkWidget *)gtk_object_get_data (GTK_OBJECT (dlg), "notebook"); + gtk_notebook_set_page (GTK_NOTEBOOK (nb), 0); +} + +void sp_object_properties_stroke (void) +{ + sp_object_properties_dialog (); + GtkWidget *nb = (GtkWidget *)gtk_object_get_data (GTK_OBJECT (dlg), "notebook"); + gtk_notebook_set_page (GTK_NOTEBOOK (nb), 1); +} + +void sp_object_properties_stroke_style (void) +{ + sp_object_properties_dialog (); + GtkWidget *nb = (GtkWidget *)gtk_object_get_data (GTK_OBJECT (dlg), "notebook"); + gtk_notebook_set_page (GTK_NOTEBOOK (nb), 2); +} + + + +static void +sp_fillstroke_selection_modified ( Inkscape::Application *inkscape, + Inkscape::Selection *selection, + guint flags, + GtkObject *base ) +{ + sp_fillstroke_selection_changed ( inkscape, selection, base ); +} + + +static void +sp_fillstroke_selection_changed ( Inkscape::Application *inkscape, + Inkscape::Selection *selection, + GtkObject *base ) +{ + if (gtk_object_get_data (GTK_OBJECT (dlg), "blocked")) + return; + gtk_object_set_data (GTK_OBJECT (dlg), "blocked", GUINT_TO_POINTER (TRUE)); + + GtkWidget *opa = GTK_WIDGET (gtk_object_get_data (GTK_OBJECT (dlg), "master_opacity")); + GtkAdjustment *a = GTK_ADJUSTMENT(gtk_object_get_data(GTK_OBJECT(dlg), "master_opacity_adjustment")); + + // create temporary style + SPStyle *query = sp_style_new (); + // query style from desktop into it. This returns a result flag and fills query with the style of subselection, if any, or selection + int result = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_MASTEROPACITY); + + switch (result) { + case QUERY_STYLE_NOTHING: + gtk_widget_set_sensitive (opa, FALSE); + break; + case QUERY_STYLE_SINGLE: + case QUERY_STYLE_MULTIPLE_AVERAGED: // TODO: treat this slightly differently + case QUERY_STYLE_MULTIPLE_SAME: + gtk_widget_set_sensitive (opa, TRUE); + gtk_adjustment_set_value(a, SP_SCALE24_TO_FLOAT(query->opacity.value)); + break; + } + + g_free (query); + gtk_object_set_data (GTK_OBJECT (dlg), "blocked", GUINT_TO_POINTER (FALSE)); +} + +static void +sp_fillstroke_opacity_changed (GtkAdjustment *a, SPWidget *dlg) +{ + if (gtk_object_get_data (GTK_OBJECT (dlg), "blocked")) + return; + + gtk_object_set_data (GTK_OBJECT (dlg), "blocked", GUINT_TO_POINTER (TRUE)); + + SPCSSAttr *css = sp_repr_css_attr_new (); + + Inkscape::CSSOStringStream os; + os << CLAMP (a->value, 0.0, 1.0); + sp_repr_css_set_property (css, "opacity", os.str().c_str()); + + sp_desktop_set_style (SP_ACTIVE_DESKTOP, css); + + sp_repr_css_attr_unref (css); + + sp_document_maybe_done (SP_DT_DOCUMENT (SP_ACTIVE_DESKTOP), "fillstroke:opacity"); + + gtk_object_set_data (GTK_OBJECT (dlg), "blocked", GUINT_TO_POINTER (FALSE)); +} + + +/* + 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 : diff --git a/src/dialogs/object-properties.h b/src/dialogs/object-properties.h new file mode 100644 index 000000000..c78142095 --- /dev/null +++ b/src/dialogs/object-properties.h @@ -0,0 +1,34 @@ +#ifndef __OBJECT_PROPERTIES_H__ +#define __OBJECT_PROPERTIES_H__ + +/** + * \brief Basic object style dialog + * + * Authors: + * Lauris Kaplinski + * Frank Felfe + * + * Copyright (C) 2000-2001 Lauris Kaplinski and Frank Felfe + * Copyright (C) 2001-2002 Ximian, Inc. and Lauris Kaplinski + * + * Released under GNU GPL + */ + +void sp_object_properties_dialog (void); + +void sp_object_properties_fill (void); +void sp_object_properties_stroke (void); +void sp_object_properties_stroke_style (void); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/dialogs/rdf.cpp b/src/dialogs/rdf.cpp new file mode 100644 index 000000000..d9949c05b --- /dev/null +++ b/src/dialogs/rdf.cpp @@ -0,0 +1,1002 @@ +/** + * \brief RDF manipulation functions + * + * FIXME: move these to xml/ instead of dialogs/ + * + * Authors: + * Kees Cook + * + * Copyright (C) 2004 Kees Cook + * + * Released under GNU GPL, read the file 'COPYING' for more information + * + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + + + +#include "xml/repr.h" +#include "rdf.h" +#include "sp-item-group.h" + +/* + + Example RDF XML from various places... + + + + title of work + year + description of work + + creator + + + holder + + + + + + + + + + SVG Road Signs + + John Cliff + + + + + + + + + + + + + + +Bag example: + + + +open clip art logo +images +logo +clip art +ocal +logotype +filetype + + + + +*/ + +struct rdf_double_t rdf_license_empty [] = { + { NULL, NULL } +}; + +struct rdf_double_t rdf_license_cc_a [] = { + { "cc:permits", "http://web.resource.org/cc/Reproduction", }, + { "cc:permits", "http://web.resource.org/cc/Distribution", }, + { "cc:requires", "http://web.resource.org/cc/Notice", }, + { "cc:requires", "http://web.resource.org/cc/Attribution", }, + { "cc:permits", "http://web.resource.org/cc/DerivativeWorks", }, + { NULL, NULL } +}; + +struct rdf_double_t rdf_license_cc_a_sa [] = { + { "cc:permits", "http://web.resource.org/cc/Reproduction", }, + { "cc:permits", "http://web.resource.org/cc/Distribution", }, + { "cc:requires", "http://web.resource.org/cc/Notice", }, + { "cc:requires", "http://web.resource.org/cc/Attribution", }, + { "cc:permits", "http://web.resource.org/cc/DerivativeWorks", }, + { "cc:requires", "http://web.resource.org/cc/ShareAlike", }, + { NULL, NULL } +}; + +struct rdf_double_t rdf_license_cc_a_nd [] = { + { "cc:permits", "http://web.resource.org/cc/Reproduction", }, + { "cc:permits", "http://web.resource.org/cc/Distribution", }, + { "cc:requires", "http://web.resource.org/cc/Notice", }, + { "cc:requires", "http://web.resource.org/cc/Attribution", }, + { NULL, NULL } +}; + +struct rdf_double_t rdf_license_cc_a_nc [] = { + { "cc:permits", "http://web.resource.org/cc/Reproduction", }, + { "cc:permits", "http://web.resource.org/cc/Distribution", }, + { "cc:requires", "http://web.resource.org/cc/Notice", }, + { "cc:requires", "http://web.resource.org/cc/Attribution", }, + { "cc:prohibits", "http://web.resource.org/cc/CommercialUse", }, + { "cc:permits", "http://web.resource.org/cc/DerivativeWorks", }, + { NULL, NULL } +}; + +struct rdf_double_t rdf_license_cc_a_nc_sa [] = { + { "cc:permits", "http://web.resource.org/cc/Reproduction", }, + { "cc:permits", "http://web.resource.org/cc/Distribution", }, + { "cc:requires", "http://web.resource.org/cc/Notice", }, + { "cc:requires", "http://web.resource.org/cc/Attribution", }, + { "cc:prohibits", "http://web.resource.org/cc/CommercialUse", }, + { "cc:permits", "http://web.resource.org/cc/DerivativeWorks", }, + { "cc:requires", "http://web.resource.org/cc/ShareAlike", }, + { NULL, NULL } +}; + +struct rdf_double_t rdf_license_cc_a_nc_nd [] = { + { "cc:permits", "http://web.resource.org/cc/Reproduction", }, + { "cc:permits", "http://web.resource.org/cc/Distribution", }, + { "cc:requires", "http://web.resource.org/cc/Notice", }, + { "cc:requires", "http://web.resource.org/cc/Attribution", }, + { "cc:prohibits", "http://web.resource.org/cc/CommercialUse", }, + { NULL, NULL } +}; + +struct rdf_double_t rdf_license_gpl [] = { + { "cc:permits", "http://web.resource.org/cc/Reproduction", }, + { "cc:permits", "http://web.resource.org/cc/Distribution", }, + { "cc:requires", "http://web.resource.org/cc/Notice", }, + { "cc:permits", "http://web.resource.org/cc/DerivativeWorks", }, + { "cc:requires", "http://web.resource.org/cc/ShareAlike", }, + { "cc:requires", "http://web.resource.org/cc/SourceCode", }, + { NULL, NULL } +}; + +struct rdf_double_t rdf_license_pd [] = { + { "cc:permits", "http://web.resource.org/cc/Reproduction", }, + { "cc:permits", "http://web.resource.org/cc/Distribution", }, + { "cc:permits", "http://web.resource.org/cc/DerivativeWorks", }, + { NULL, NULL } +}; + +struct rdf_double_t rdf_license_freeart [] = { + { "cc:permits", "http://web.resource.org/cc/Reproduction", }, + { "cc:permits", "http://web.resource.org/cc/Distribution", }, + { "cc:permits", "http://web.resource.org/cc/DerivativeWorks", }, + { "cc:requires", "http://web.resource.org/cc/ShareAlike", }, + { "cc:requires", "http://web.resource.org/cc/Notice", }, + { "cc:requires", "http://web.resource.org/cc/Attribution", }, + { NULL, NULL } +}; + +struct rdf_license_t rdf_licenses [] = { + { "CC Attribution", + "http://creativecommons.org/licenses/by/2.0/", + rdf_license_cc_a, + }, + + { "CC Attribution-ShareAlike", + "http://creativecommons.org/licenses/by-sa/2.0/", + rdf_license_cc_a_sa, + }, + + { "CC Attribution-NoDerivs", + "http://creativecommons.org/licenses/by-nd/2.0/", + rdf_license_cc_a_nd, + }, + + { "CC Attribution-NonCommercial", + "http://creativecommons.org/licenses/by-nc/2.0/", + rdf_license_cc_a_nc, + }, + + { "CC Attribution-NonCommercial-ShareAlike", + "http://creativecommons.org/licenses/by-nc-sa/2.0/", + rdf_license_cc_a_nc_sa, + }, + + { "CC Attribution-NonCommercial-NoDerivs", + "http://creativecommons.org/licenses/by-nc-nd/2.0/", + rdf_license_cc_a_nc_nd, + }, + + { "GNU General Public License", + "http://creativecommons.org/licenses/GPL/2.0/", + rdf_license_gpl, + }, + + { "GNU Lesser General Public License", + "http://creativecommons.org/licenses/LGPL/2.1/", + rdf_license_gpl, + }, + + { "Public Domain", + "http://web.resource.org/cc/PublicDomain", + rdf_license_pd, + }, + + { "FreeArt", + "http://artlibre.org/licence.php/lalgb.html", + rdf_license_freeart, + }, + + { NULL, NULL, rdf_license_empty, } +}; + +#define XML_TAG_NAME_SVG "svg:svg" +#define XML_TAG_NAME_METADATA "svg:metadata" +#define XML_TAG_NAME_RDF "rdf:RDF" +#define XML_TAG_NAME_WORK "cc:Work" +#define XML_TAG_NAME_LICENSE "cc:License" + +// Remember when using the "title" and "tip" elements to pass them through +// the localization functions when you use them! +struct rdf_work_entity_t rdf_work_entities [] = { + { "title", N_("Title"), "dc:title", RDF_CONTENT, + N_("Name by which this document is formally known."), RDF_FORMAT_LINE, RDF_EDIT_GENERIC, + }, + { "date", N_("Date"), "dc:date", RDF_CONTENT, + N_("Date associated with the creation of this document (YYYY-MM-DD)."), RDF_FORMAT_LINE, RDF_EDIT_GENERIC, + }, + { "format", N_("Format"), "dc:format", RDF_CONTENT, + N_("The physical or digital manifestation of this document (MIME type)."), RDF_FORMAT_LINE, RDF_EDIT_HARDCODED, + }, + { "type", N_("Type"), "dc:type", RDF_RESOURCE, + N_("Type of document (DCMI Type)."), RDF_FORMAT_LINE, RDF_EDIT_HARDCODED, + }, + + { "creator", N_("Creator"), "dc:creator", RDF_AGENT, + N_("Name of entity primarily responsible for making the content of this document."), RDF_FORMAT_LINE, RDF_EDIT_GENERIC, + }, + { "rights", N_("Rights"), "dc:rights", RDF_AGENT, + N_("Name of entity with rights to the Intellectual Property of this document."), RDF_FORMAT_LINE, RDF_EDIT_GENERIC, + }, + { "publisher", N_("Publisher"), "dc:publisher", RDF_AGENT, + N_("Name of entity responsible for making this document available."), RDF_FORMAT_LINE, RDF_EDIT_GENERIC, + }, + + { "identifier", N_("Identifier"), "dc:identifier", RDF_CONTENT, + N_("Unique URI to reference this document."), RDF_FORMAT_LINE, RDF_EDIT_GENERIC, + }, + { "source", N_("Source"), "dc:source", RDF_CONTENT, + N_("Unique URI to reference the source of this document."), RDF_FORMAT_LINE, RDF_EDIT_GENERIC, + }, + { "relation", N_("Relation"), "dc:relation", RDF_CONTENT, + N_("Unique URI to a related document."), RDF_FORMAT_LINE, RDF_EDIT_GENERIC, + }, + { "language", N_("Language"), "dc:language", RDF_CONTENT, + N_("Two-letter language tag with optional subtags for the language of this document. (e.g. 'en-GB')"), RDF_FORMAT_LINE, RDF_EDIT_GENERIC, + }, + { "subject", N_("Keywords"), "dc:subject", RDF_BAG, + N_("The topic of this document as comma-separated key words, phrases, or classifications."), RDF_FORMAT_LINE, RDF_EDIT_GENERIC, + }, + // TRANSLATORS: "Coverage": the spatial or temporal characteristics of the content. + // For info, see Appendix D of http://www.w3.org/TR/1998/WD-rdf-schema-19980409/ + { "coverage", N_("Coverage"), "dc:coverage", RDF_CONTENT, + N_("Extent or scope of this document."), RDF_FORMAT_LINE, RDF_EDIT_GENERIC, + }, + + { "description", N_("Description"), "dc:description", RDF_CONTENT, + N_("A short account of the content of this document."), RDF_FORMAT_MULTILINE, RDF_EDIT_GENERIC, + }, + + // FIXME: need to handle 1 agent per line of input + { "contributor", N_("Contributors"), "dc:contributor", RDF_AGENT, + N_("Names of entities responsible for making contributions to the content of this document."), RDF_FORMAT_MULTILINE, RDF_EDIT_GENERIC, + }, + + // TRANSLATORS: URL to a page that defines the license for the document + { "license_uri", N_("URI"), "cc:license", RDF_RESOURCE, + // TRANSLATORS: this is where you put a URL to a page that defines the license + N_("URI to this document's license's namespace definition."), RDF_FORMAT_LINE, RDF_EDIT_SPECIAL, + }, + + // TRANSLATORS: fragment of XML representing the license of the document + { "license_fragment", N_("Fragment"), "License", RDF_XML, + N_("XML fragment for the RDF 'License' section."), RDF_FORMAT_MULTILINE, RDF_EDIT_SPECIAL, + }, + + { NULL, NULL, NULL, RDF_CONTENT, + NULL, RDF_FORMAT_LINE, RDF_EDIT_HARDCODED, + } +}; + +/** + * \brief Retrieves a known RDF/Work entity by name + * \return A pointer to an RDF/Work entity + * \param name The desired RDF/Work entity + * + */ +struct rdf_work_entity_t * +rdf_find_entity(gchar const * name) +{ + struct rdf_work_entity_t *entity; + for (entity=rdf_work_entities; entity->name; entity++) { + if (strcmp(entity->name,name)==0) break; + } + if (entity->name) return entity; + return NULL; +} + +/* + * Takes the inkscape rdf struct and spits out a static RDF, which is only + * useful for testing. We must merge the rdf struct into the XML DOM for + * changes to be saved. + */ +/* + + Since g_markup_printf_escaped doesn't exist for most people's glib + right now, this function will remain commented out since it's only + for generic debug anyway. --Kees + +gchar * +rdf_string(struct rdf_t * rdf) +{ + gulong overall=0; + gchar *string=NULL; + + gchar *rdf_head="\ +\ +"; + gchar *work_head="\ +\ + \ +"; + gchar *work_title=NULL; + gchar *work_date=NULL; + gchar *work_description=NULL; + gchar *work_creator=NULL; + gchar *work_owner=NULL; + gchar *work_source=NULL; + gchar *work_license=NULL; + gchar *license_head=NULL; + gchar *license=NULL; + gchar *license_end="\n"; + gchar *work_end="\n"; + gchar *rdf_end="\n"; + + if (rdf && rdf->work_title && rdf->work_title[0]) { + work_title=g_markup_printf_escaped(" %s\n", + rdf->work_title); + overall+=strlen(work_title); + } + if (rdf && rdf->work_date && rdf->work_date[0]) { + work_date=g_markup_printf_escaped(" %s\n", + rdf->work_date); + overall+=strlen(work_date); + } + if (rdf && rdf->work_description && rdf->work_description[0]) { + work_description=g_markup_printf_escaped(" %s\n", + rdf->work_description); + overall+=strlen(work_description); + } + if (rdf && rdf->work_creator && rdf->work_creator[0]) { + work_creator=g_markup_printf_escaped(" \ + %s\ + \n", + rdf->work_creator); + overall+=strlen(work_creator); + } + if (rdf && rdf->work_owner && rdf->work_owner[0]) { + work_owner=g_markup_printf_escaped(" \ + %s\ + \n", + rdf->work_owner); + overall+=strlen(work_owner); + } + if (rdf && rdf->work_source && rdf->work_source[0]) { + work_source=g_markup_printf_escaped(" \n", + rdf->work_source); + overall+=strlen(work_source); + } + if (rdf && rdf->license && rdf->license->work_rdf && rdf->license->work_rdf[0]) { + work_license=g_markup_printf_escaped(" \n", + rdf->license->work_rdf); + overall+=strlen(work_license); + + license_head=g_markup_printf_escaped("\n", + rdf->license->work_rdf); + overall+=strlen(license_head); + overall+=strlen(rdf->license->license_rdf); + overall+=strlen(license_end); + } + + overall+=strlen(rdf_head)+strlen(rdf_end); + overall+=strlen(work_head)+strlen(work_end); + + overall++; // NULL term + + if (!(string=(gchar*)g_malloc(overall))) { + return NULL; + } + + string[0]='\0'; + strcat(string,rdf_head); + strcat(string,work_head); + + if (work_title) strcat(string,work_title); + if (work_date) strcat(string,work_date); + if (work_description) strcat(string,work_description); + if (work_creator) strcat(string,work_creator); + if (work_owner) strcat(string,work_owner); + if (work_source) strcat(string,work_source); + if (work_license) strcat(string,work_license); + + strcat(string,work_end); + if (license_head) { + strcat(string,license_head); + strcat(string,rdf->license->license_rdf); + strcat(string,license_end); + } + strcat(string,rdf_end); + + return string; +} +*/ + + +/** + * \brief Pull the text out of an RDF entity, depends on how it's stored + * \return A pointer to the entity's static contents as a string + * \param repr The XML element to extract from + * \param entity The desired RDF/Work entity + * + */ +const gchar * +rdf_get_repr_text ( Inkscape::XML::Node * repr, struct rdf_work_entity_t * entity ) +{ + g_return_val_if_fail (repr != NULL, NULL); + g_return_val_if_fail (entity != NULL, NULL); + static gchar * bag = NULL; + gchar * holder = NULL; + + Inkscape::XML::Node * temp=NULL; + switch (entity->datatype) { + case RDF_CONTENT: + temp = sp_repr_children(repr); + if ( temp == NULL ) return NULL; + + return temp->content(); + + case RDF_AGENT: + temp = sp_repr_lookup_name ( repr, "cc:Agent", 1 ); + if ( temp == NULL ) return NULL; + + temp = sp_repr_lookup_name ( temp, "dc:title", 1 ); + if ( temp == NULL ) return NULL; + + temp = sp_repr_children(temp); + if ( temp == NULL ) return NULL; + + return temp->content(); + + case RDF_RESOURCE: + return repr->attribute("rdf:resource"); + + case RDF_XML: + return "xml goes here"; + + case RDF_BAG: + /* clear the static string. yucky. */ + if (bag) g_free(bag); + bag = NULL; + + temp = sp_repr_lookup_name ( repr, "rdf:Bag", 1 ); + if ( temp == NULL ) { + /* backwards compatible: read contents */ + temp = sp_repr_children(repr); + if ( temp == NULL ) return NULL; + + return temp->content(); + } + + for ( temp = sp_repr_children(temp) ; + temp ; + temp = sp_repr_next(temp) ) { + if (!strcmp(temp->name(),"rdf:li") && + temp->firstChild()) { + const gchar * str = temp->firstChild()->content(); + if (bag) { + holder = bag; + bag = g_strconcat(holder, ", ", str, NULL); + g_free(holder); + } + else { + bag = g_strdup(str); + } + } + } + return bag; + + default: + break; + } + return NULL; +} + +unsigned int +rdf_set_repr_text ( Inkscape::XML::Node * repr, + struct rdf_work_entity_t * entity, + gchar const * text ) +{ + g_return_val_if_fail ( repr != NULL, 0); + g_return_val_if_fail ( entity != NULL, 0); + g_return_val_if_fail ( text != NULL, 0); + gchar * str = NULL; + gchar** strlist = NULL; + int i; + + Inkscape::XML::Node * temp=NULL; + Inkscape::XML::Node * child=NULL; + Inkscape::XML::Node * parent=repr; + switch (entity->datatype) { + case RDF_CONTENT: + temp = sp_repr_children(parent); + if ( temp == NULL ) { + temp = sp_repr_new_text( text ); + g_return_val_if_fail (temp != NULL, 0); + + parent->appendChild(temp); + Inkscape::GC::release(temp); + + return TRUE; + } + else { + temp->setContent(text); + return TRUE; + } + + case RDF_AGENT: + temp = sp_repr_lookup_name ( parent, "cc:Agent", 1 ); + if ( temp == NULL ) { + temp = sp_repr_new ( "cc:Agent" ); + g_return_val_if_fail (temp != NULL, 0); + + parent->appendChild(temp); + Inkscape::GC::release(temp); + } + parent = temp; + + temp = sp_repr_lookup_name ( parent, "dc:title", 1 ); + if ( temp == NULL ) { + temp = sp_repr_new ( "dc:title" ); + g_return_val_if_fail (temp != NULL, 0); + + parent->appendChild(temp); + Inkscape::GC::release(temp); + } + parent = temp; + + temp = sp_repr_children(parent); + if ( temp == NULL ) { + temp = sp_repr_new_text( text ); + g_return_val_if_fail (temp != NULL, 0); + + parent->appendChild(temp); + Inkscape::GC::release(temp); + + return TRUE; + } + else { + temp->setContent(text); + return TRUE; + } + + case RDF_RESOURCE: + parent->setAttribute("rdf:resource", text ); + return true; + + case RDF_XML: + return 1; + + case RDF_BAG: + /* find/create the rdf:Bag item */ + temp = sp_repr_lookup_name ( parent, "rdf:Bag", 1 ); + if ( temp == NULL ) { + /* backward compatibility: drop the dc:subject contents */ + while ( (temp = sp_repr_children( parent )) ) { + parent->removeChild(temp); + } + + temp = sp_repr_new ( "rdf:Bag" ); + g_return_val_if_fail (temp != NULL, 0); + + parent->appendChild(temp); + Inkscape::GC::release(temp); + } + parent = temp; + + /* toss all the old list items */ + while ( (temp = sp_repr_children( parent )) ) { + parent->removeChild(temp); + } + + /* chop our list up on commas */ + strlist = g_strsplit( text, ",", 0); + + for (i = 0; (str = strlist[i]); i++) { + temp = sp_repr_new ( "rdf:li" ); + g_return_val_if_fail (temp != NULL, 0); + + parent->appendChild(temp); + Inkscape::GC::release(temp); + + child = sp_repr_new_text( g_strstrip(str) ); + g_return_val_if_fail (child != NULL, 0); + + temp->appendChild(child); + Inkscape::GC::release(child); + } + g_strfreev( strlist ); + + return 1; + + default: + break; + } + return 0; +} + +Inkscape::XML::Node * +rdf_get_rdf_root_repr ( SPDocument * doc, bool build ) +{ + g_return_val_if_fail (doc != NULL, NULL); + g_return_val_if_fail (doc->rroot != NULL, NULL); + + Inkscape::XML::Node * rdf = sp_repr_lookup_name ( doc->rroot, XML_TAG_NAME_RDF ); + + if (rdf == NULL) { + //printf("missing XML '%s'\n",XML_TAG_NAME_RDF); + if (!build) return NULL; + + Inkscape::XML::Node * svg = sp_repr_lookup_name ( doc->rroot, XML_TAG_NAME_SVG ); + g_return_val_if_fail ( svg != NULL, NULL ); + + Inkscape::XML::Node * parent = sp_repr_lookup_name ( svg, XML_TAG_NAME_METADATA ); + if ( parent == NULL ) { + parent = sp_repr_new( XML_TAG_NAME_METADATA ); + g_return_val_if_fail ( parent != NULL, NULL); + + svg->appendChild(parent); + Inkscape::GC::release(parent); + } + + rdf = sp_repr_new( XML_TAG_NAME_RDF ); + g_return_val_if_fail (rdf != NULL, NULL); + + parent->appendChild(rdf); + Inkscape::GC::release(rdf); + } + + /* + * some implementations do not put RDF stuff inside , + * so we need to check for it and add it if we don't see it + */ + Inkscape::XML::Node * want_metadata = sp_repr_parent ( rdf ); + g_return_val_if_fail (want_metadata != NULL, NULL); + if (strcmp( want_metadata->name(), XML_TAG_NAME_METADATA )) { + Inkscape::XML::Node * metadata = sp_repr_new( XML_TAG_NAME_METADATA ); + g_return_val_if_fail (metadata != NULL, NULL); + + /* attach the metadata node */ + want_metadata->appendChild(metadata); + Inkscape::GC::release(metadata); + + /* move the RDF into it */ + Inkscape::GC::anchor(rdf); + sp_repr_unparent ( rdf ); + metadata->appendChild(rdf); + Inkscape::GC::release(rdf); + } + + return rdf; +} + +Inkscape::XML::Node * +rdf_get_xml_repr( SPDocument * doc, gchar const * name, bool build ) +{ + g_return_val_if_fail (name != NULL, NULL); + g_return_val_if_fail (doc != NULL, NULL); + g_return_val_if_fail (doc->rroot != NULL, NULL); + + Inkscape::XML::Node * rdf = rdf_get_rdf_root_repr ( doc, build ); + if (!rdf) return NULL; + + Inkscape::XML::Node * xml = sp_repr_lookup_name ( rdf, name ); + if (xml == NULL) { + //printf("missing XML '%s'\n",name); + if (!build) return NULL; + + xml = sp_repr_new( name ); + g_return_val_if_fail (xml != NULL, NULL); + + xml->setAttribute("rdf:about", "" ); + + rdf->appendChild(xml); + Inkscape::GC::release(xml); + } + + return xml; +} + +Inkscape::XML::Node * +rdf_get_work_repr( SPDocument * doc, gchar const * name, bool build ) +{ + g_return_val_if_fail (name != NULL, NULL); + g_return_val_if_fail (doc != NULL, NULL); + g_return_val_if_fail (doc->rroot != NULL, NULL); + + Inkscape::XML::Node * work = rdf_get_xml_repr ( doc, XML_TAG_NAME_WORK, build ); + if (!work) return NULL; + + Inkscape::XML::Node * item = sp_repr_lookup_name ( work, name, 1 ); + if (item == NULL) { + //printf("missing XML '%s'\n",name); + if (!build) return NULL; + + item = sp_repr_new( name ); + g_return_val_if_fail (item != NULL, NULL); + + work->appendChild(item); + Inkscape::GC::release(item); + } + + return item; +} + + + +/** + * \brief Retrieves a known RDF/Work entity's contents from the document XML by name + * \return A pointer to the entity's static contents as a string, or NULL if no entity exists + * \param entity The desired RDF/Work entity + * + */ +const gchar * +rdf_get_work_entity(SPDocument * doc, struct rdf_work_entity_t * entity) +{ + g_return_val_if_fail (doc != NULL, NULL); + if ( entity == NULL ) return NULL; + //printf("want '%s'\n",entity->title); + + Inkscape::XML::Node * item; + if ( entity->datatype == RDF_XML ) { + item = rdf_get_xml_repr ( doc, entity->tag, FALSE ); + } + else { + item = rdf_get_work_repr( doc, entity->tag, FALSE ); + } + if ( item == NULL ) return NULL; + + const gchar * result = rdf_get_repr_text ( item, entity ); + //printf("found '%s' == '%s'\n", entity->title, result ); + return result; +} + +/** + * \brief Stores a string into a named RDF/Work entity in the document XML + * \param entity The desired RDF/Work entity to replace + * \param string The string to replace the entity contents with + * + */ +unsigned int +rdf_set_work_entity(SPDocument * doc, struct rdf_work_entity_t * entity, + const gchar * text) +{ + g_return_val_if_fail ( entity != NULL, 0 ); + if (text == NULL) { + // FIXME: on a "NULL" text, delete the entity. For now, blank it. + text=""; + } + + /* + printf("changing '%s' (%s) to '%s'\n", + entity->title, + entity->tag, + text); + */ + + Inkscape::XML::Node * item = rdf_get_work_repr( doc, entity->tag, TRUE ); + g_return_val_if_fail ( item != NULL, 0 ); + + return rdf_set_repr_text ( item, entity, text ); +} + +#undef DEBUG_MATCH + +bool +rdf_match_license ( Inkscape::XML::Node * repr, struct rdf_license_t * license ) +{ + g_assert ( repr != NULL ); + g_assert ( license != NULL ); + + bool result=TRUE; +#ifdef DEBUG_MATCH + printf("checking against '%s'\n",license->name); +#endif + + int count = 0; + for (struct rdf_double_t * details = license->details; + details->name; details++ ) { + count++; + } + bool * matched = (bool*)calloc(count,sizeof(bool)); + + for (Inkscape::XML::Node * current = sp_repr_children ( repr ); + current; + current = sp_repr_next ( current ) ) { + + gchar const * attr = current->attribute("rdf:resource"); + if ( attr == NULL ) continue; + +#ifdef DEBUG_MATCH + printf("\texamining '%s' => '%s'\n", current->name(), attr); +#endif + + bool found_match=FALSE; + for (int i=0; iname(), license->details[i].name); + printf("\t\t'%s' vs '%s'\n", attr, license->details[i].resource); +#endif + + if (!strcmp( current->name(), + license->details[i].name ) && + !strcmp( attr, + license->details[i].resource )) { + matched[i]=TRUE; + found_match=TRUE; +#ifdef DEBUG_MATCH + printf("\t\tgood!\n"); +#endif + break; + } + } + if (!found_match) { + // if we checked each known item of the license + // and didn't find it, we must abort + result=FALSE; +#ifdef DEBUG_MATCH + printf("\t\tno '%s' element matched XML (bong)!\n",license->name); +#endif + break; + } + } +#ifdef DEBUG_MATCH + if (result) printf("\t\tall XML found matching elements!\n"); +#endif + for (int i=0; result && iname); +#endif + } + } + +#ifdef DEBUG_MATCH + printf("\t\tall '%s' elements used to match!\n",license->name); +#endif + + free(matched); + +#ifdef DEBUG_MATCH + if (result) printf("matched '%s'\n",license->name); +#endif + return result; +} + +/** + * \brief Attempts to match and retrieve a known RDF/License from the document XML + * \return A pointer to the static RDF license structure + * + */ +struct rdf_license_t * +rdf_get_license(SPDocument * document) +{ + Inkscape::XML::Node * repr = rdf_get_xml_repr ( document, XML_TAG_NAME_LICENSE, FALSE ); + if (repr) { + for (struct rdf_license_t * license = rdf_licenses; + license->name; license++ ) { + if ( rdf_match_license ( repr, license ) ) return license; + } + } +#ifdef DEBUG_MATCH + else { + printf("no license XML\n"); + } +#endif + return NULL; +} + +/** + * \brief Stores an RDF/License XML in the document XML + * \param document Which document to update + * \param license The desired RDF/License structure to store; NULL drops old license, so can be used for proprietary license. + * + */ +void +rdf_set_license(SPDocument * document, struct rdf_license_t const * license) +{ + // drop old license section + Inkscape::XML::Node * repr = rdf_get_xml_repr ( document, XML_TAG_NAME_LICENSE, FALSE ); + if (repr) sp_repr_unparent(repr); + + if (!license) return; + + // build new license section + repr = rdf_get_xml_repr ( document, XML_TAG_NAME_LICENSE, TRUE ); + g_assert ( repr ); + + repr->setAttribute("rdf:about", license->uri ); + + for (struct rdf_double_t * detail = license->details; + detail->name; detail++) { + Inkscape::XML::Node * child = sp_repr_new( detail->name ); + g_assert ( child != NULL ); + + child->setAttribute("rdf:resource", detail->resource ); + repr->appendChild(child); + Inkscape::GC::release(child); + } +} + +struct rdf_entity_default_t { + gchar const * name; + gchar const * text; +}; +struct rdf_entity_default_t rdf_defaults[] = { + { "format", "image/svg+xml", }, + { "type", "http://purl.org/dc/dcmitype/StillImage", }, + { NULL, NULL, } +}; + +void +rdf_set_defaults ( SPDocument * document ) +{ + g_assert ( document != NULL ); + + // Create metadata node if it doesn't already exist + if (!sp_item_group_get_child_by_name ((SPGroup *) document->root, NULL, + XML_TAG_NAME_METADATA)) { + // create repr + Inkscape::XML::Node * rnew = sp_repr_new (XML_TAG_NAME_METADATA); + // insert into the document + document->rroot->addChild(rnew, NULL); + // clean up + Inkscape::GC::release(rnew); + } + + /* install defaults */ + for ( struct rdf_entity_default_t * rdf_default = rdf_defaults; + rdf_default->name; + rdf_default++) { + struct rdf_work_entity_t * entity = rdf_find_entity ( rdf_default->name ); + g_assert ( entity != NULL ); + + if ( rdf_get_work_entity ( document, entity ) == NULL ) { + rdf_set_work_entity ( document, entity, rdf_default->text ); + } + } +} + + +/* + 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/dialogs/rdf.h b/src/dialogs/rdf.h new file mode 100644 index 000000000..29da859c2 --- /dev/null +++ b/src/dialogs/rdf.h @@ -0,0 +1,124 @@ +/** + * + * \brief headers for RDF types + * + * Authors: + * Kees Cook + * + * Copyright (C) 2004 Kees Cook + * + * Released under GNU GPL, read the file 'COPYING' for more information + * + */ +#ifndef _RDF_H_ +#define _RDF_H_ + +#include + +#include +#include "document.h" + +// yeah, it's not a triple yet... +/** + * \brief Holds license name/resource doubles for rdf_license_t entries + */ +struct rdf_double_t { + gchar *name; + gchar *resource; +}; + +/** + * \brief Holds license name and RDF information + */ +struct rdf_license_t { + gchar *name; /* localized name of this license */ + gchar *uri; /* URL for the RDF/Work/license element */ + struct rdf_double_t *details; /* the license details */ +// gchar *fragment; /* XML contents for the RDF/License tag */ +}; + +extern rdf_license_t rdf_licenses []; + +/** + * \brief Describes how a given RDF entity is stored in XML + */ +enum RDFType { + RDF_CONTENT, // direct between-XML-tags content + RDF_AGENT, // requires the "Agent" hierarchy before doing content + RDF_RESOURCE, // stored in "rdf:resource" element + RDF_XML, // literal XML + RDF_BAG // rdf:Bag resources +}; + +/** + * \brief Describes how a given RDF entity should be edited + */ +enum RDF_Format { + RDF_FORMAT_LINE, // uses single line data (GtkEntry) + RDF_FORMAT_MULTILINE, // uses multiline data (GtkTextView) + RDF_FORMAT_SPECIAL // uses some other edit methods +}; + +enum RDF_Editable { + RDF_EDIT_GENERIC, // editable via generic widgets + RDF_EDIT_SPECIAL, // special widgets are needed + RDF_EDIT_HARDCODED // isn't editable +}; + +/** + * \brief Holds known RDF/Work tags + */ +struct rdf_work_entity_t { + char *name; /* unique name of this entity for internal reference */ + gchar *title; /* localized title of this entity for data entry */ + gchar *tag; /* namespace tag for the RDF/Work element */ + RDFType datatype; /* how to extract/inject the RDF information */ + gchar *tip; /* tool tip to explain the meaning of the entity */ + RDF_Format format; /* in what format is this data edited? */ + RDF_Editable editable;/* in what way is the data editable? */ +}; + +extern rdf_work_entity_t rdf_work_entities []; + +/** + * \brief Generic collection of RDF information for the RDF debug function + */ +struct rdf_t { + gchar* work_title; + gchar* work_date; + gchar* work_creator; + gchar* work_owner; + gchar* work_publisher; + gchar* work_type; + gchar* work_source; + gchar* work_subject; + gchar* work_description; + struct rdf_license_t* license; +}; + +struct rdf_work_entity_t * rdf_find_entity(gchar const * name); + +const gchar * rdf_get_work_entity(SPDocument * doc, + struct rdf_work_entity_t * entity); +unsigned int rdf_set_work_entity(SPDocument * doc, + struct rdf_work_entity_t * entity, + const gchar * text); + +struct rdf_license_t * rdf_get_license(SPDocument * doc); +void rdf_set_license(SPDocument * doc, + struct rdf_license_t const * license); + +void rdf_set_defaults ( SPDocument * document ); + +#endif // _RDF_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 : diff --git a/src/dialogs/sp-attribute-widget.cpp b/src/dialogs/sp-attribute-widget.cpp new file mode 100644 index 000000000..d7c4316e1 --- /dev/null +++ b/src/dialogs/sp-attribute-widget.cpp @@ -0,0 +1,802 @@ +#define __SP_ATTRIBUTE_WIDGET_C__ + +/** + * \brief SPAttributeWidget + * + * Widget, that listens and modifies repr attributes + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 2001 Ximian, Inc. + * + * Licensed under GNU GPL + */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include "xml/repr.h" +#include "macros.h" +#include "document.h" +#include "sp-object.h" +#include + +#include "sp-attribute-widget.h" + +static void sp_attribute_widget_class_init (SPAttributeWidgetClass *klass); +static void sp_attribute_widget_init (SPAttributeWidget *widget); +static void sp_attribute_widget_destroy (GtkObject *object); + +static void sp_attribute_widget_changed (GtkEditable *editable); + +static void sp_attribute_widget_object_modified ( SPObject *object, + guint flags, + SPAttributeWidget *spaw ); +static void sp_attribute_widget_object_release ( SPObject *object, + SPAttributeWidget *spaw ); + +static GtkEntryClass *parent_class; + + + + +GtkType +sp_attribute_widget_get_type (void) +{ + static GtkType type = 0; + if (!type) { + static const GtkTypeInfo info = { + "SPAttributeWidget", + sizeof (SPAttributeWidget), + sizeof (SPAttributeWidgetClass), + (GtkClassInitFunc) sp_attribute_widget_class_init, + (GtkObjectInitFunc) sp_attribute_widget_init, + NULL, NULL, NULL + }; + type = gtk_type_unique (GTK_TYPE_ENTRY, &info); + } + return type; + +} // end of sp_attribute_widget_get_type() + + + +static void +sp_attribute_widget_class_init (SPAttributeWidgetClass *klass) +{ + GtkObjectClass *object_class; + GtkWidgetClass *widget_class; + GtkEditableClass *editable_class; + + object_class = GTK_OBJECT_CLASS (klass); + widget_class = GTK_WIDGET_CLASS (klass); + editable_class = GTK_EDITABLE_CLASS (klass); + + parent_class = (GtkEntryClass*)gtk_type_class (GTK_TYPE_ENTRY); + + object_class->destroy = sp_attribute_widget_destroy; + + editable_class->changed = sp_attribute_widget_changed; + +} // end of sp_attribute_widget_class_init() + + + +static void +sp_attribute_widget_init (SPAttributeWidget *spaw) +{ + spaw->blocked = FALSE; + spaw->hasobj = FALSE; + + spaw->src.object = NULL; + + spaw->attribute = NULL; +} + + + +static void +sp_attribute_widget_destroy (GtkObject *object) +{ + + SPAttributeWidget *spaw; + + spaw = SP_ATTRIBUTE_WIDGET (object); + + if (spaw->attribute) { + g_free (spaw->attribute); + spaw->attribute = NULL; + } + + + if (spaw->hasobj) { + + if (spaw->src.object) { + sp_signal_disconnect_by_data (spaw->src.object, spaw); + spaw->src.object = NULL; + } + } else { + + if (spaw->src.repr) { + spaw->src.repr = Inkscape::GC::release(spaw->src.repr); + } + } // end of if() + + ((GtkObjectClass *) parent_class)->destroy (object); + +} + + + +static void +sp_attribute_widget_changed (GtkEditable *editable) +{ + + SPAttributeWidget *spaw; + + spaw = SP_ATTRIBUTE_WIDGET (editable); + + if (!spaw->blocked) { + + const gchar *text; + spaw->blocked = TRUE; + text = gtk_entry_get_text (GTK_ENTRY (spaw)); + if (!*text) + text = NULL; + + if (spaw->hasobj && spaw->src.object) { + + if (!sp_repr_set_attr ( SP_OBJECT_REPR (spaw->src.object), + spaw->attribute, text) ) + { + /* Cannot set attribute */ + text = SP_OBJECT_REPR (spaw->src.object)->attribute(spaw->attribute); + gtk_entry_set_text (GTK_ENTRY (spaw), text ? text : ""); + } + sp_document_done (SP_OBJECT_DOCUMENT (spaw->src.object)); + + } else if (spaw->src.repr) { + + if (!sp_repr_set_attr (spaw->src.repr, spaw->attribute, text)) + { + /* Cannot set attribute */ + text = spaw->src.repr->attribute(spaw->attribute); + gtk_entry_set_text (GTK_ENTRY (spaw), text ? text : ""); + } + /* TODO: Warning! Undo will not be flushed in given case */ + } + spaw->blocked = FALSE; + } + +} // end of sp_attribute_widget_changed() + + + +GtkWidget * +sp_attribute_widget_new ( SPObject *object, const gchar *attribute ) +{ + SPAttributeWidget *spaw; + + g_return_val_if_fail (!object || SP_IS_OBJECT (object), NULL); + g_return_val_if_fail (!object || attribute, NULL); + + spaw = (SPAttributeWidget*)gtk_type_new (SP_TYPE_ATTRIBUTE_WIDGET); + + sp_attribute_widget_set_object (spaw, object, attribute); + + return GTK_WIDGET (spaw); + +} // end of sp_attribute_widget_new() + + + +GtkWidget * +sp_attribute_widget_new_repr ( Inkscape::XML::Node *repr, const gchar *attribute ) +{ + SPAttributeWidget *spaw; + + spaw = (SPAttributeWidget*)gtk_type_new (SP_TYPE_ATTRIBUTE_WIDGET); + + sp_attribute_widget_set_repr (spaw, repr, attribute); + + return GTK_WIDGET (spaw); +} + + + +void +sp_attribute_widget_set_object ( SPAttributeWidget *spaw, + SPObject *object, + const gchar *attribute ) +{ + + g_return_if_fail (spaw != NULL); + g_return_if_fail (SP_IS_ATTRIBUTE_WIDGET (spaw)); + g_return_if_fail (!object || SP_IS_OBJECT (object)); + g_return_if_fail (!object || attribute); + g_return_if_fail (attribute != NULL); + + if (spaw->attribute) { + g_free (spaw->attribute); + spaw->attribute = NULL; + } + + if (spaw->hasobj) { + + if (spaw->src.object) { + sp_signal_disconnect_by_data (spaw->src.object, spaw); + spaw->src.object = NULL; + } + } else { + + if (spaw->src.repr) { + spaw->src.repr = Inkscape::GC::release(spaw->src.repr); + } + } + + spaw->hasobj = TRUE; + + if (object) { + const gchar *val; + + spaw->blocked = TRUE; + spaw->src.object = object; + g_signal_connect ( G_OBJECT (object), "modified", + G_CALLBACK (sp_attribute_widget_object_modified), + spaw ); + g_signal_connect ( G_OBJECT (object), "release", + G_CALLBACK (sp_attribute_widget_object_release), + spaw ); + + spaw->attribute = g_strdup (attribute); + + val = SP_OBJECT_REPR (object)->attribute(attribute); + gtk_entry_set_text (GTK_ENTRY (spaw), val ? val : (const gchar *) ""); + spaw->blocked = FALSE; + } + + gtk_widget_set_sensitive (GTK_WIDGET (spaw), (spaw->src.object != NULL)); + +} // end of sp_attribute_widget_set_object() + + + +void +sp_attribute_widget_set_repr ( SPAttributeWidget *spaw, + Inkscape::XML::Node *repr, + const gchar *attribute ) +{ + + g_return_if_fail (spaw != NULL); + g_return_if_fail (SP_IS_ATTRIBUTE_WIDGET (spaw)); + g_return_if_fail (attribute != NULL); + + if (spaw->attribute) { + g_free (spaw->attribute); + spaw->attribute = NULL; + } + + if (spaw->hasobj) { + + if (spaw->src.object) { + sp_signal_disconnect_by_data (spaw->src.object, spaw); + spaw->src.object = NULL; + } + } else { + + if (spaw->src.repr) { + spaw->src.repr = Inkscape::GC::release(spaw->src.repr); + } + } + + spaw->hasobj = FALSE; + + if (repr) { + const gchar *val; + + spaw->blocked = TRUE; + spaw->src.repr = Inkscape::GC::anchor(repr); + spaw->attribute = g_strdup (attribute); + + val = repr->attribute(attribute); + gtk_entry_set_text (GTK_ENTRY (spaw), val ? val : (const gchar *) ""); + spaw->blocked = FALSE; + } + + gtk_widget_set_sensitive (GTK_WIDGET (spaw), (spaw->src.repr != NULL)); + +} // end of sp_attribute_widget_set_repr() + + + +static void +sp_attribute_widget_object_modified ( SPObject *object, + guint flags, + SPAttributeWidget *spaw ) +{ + + if (flags && SP_OBJECT_MODIFIED_FLAG) { + + const gchar *val, *text; + val = SP_OBJECT_REPR (spaw->src.object)->attribute(spaw->attribute); + text = gtk_entry_get_text (GTK_ENTRY (spaw)); + + if (val || text) { + + if (!val || !text || strcmp (val, text)) { + /* We are different */ + spaw->blocked = TRUE; + gtk_entry_set_text ( GTK_ENTRY (spaw), + val ? val : (const gchar *) ""); + spaw->blocked = FALSE; + } // end of if() + + } // end of if() + + } //end of if() + +} // end of sp_attribute_widget_object_modified() + + + +static void +sp_attribute_widget_object_release ( SPObject *object, + SPAttributeWidget *spaw ) +{ + sp_attribute_widget_set_object (spaw, NULL, NULL); +} + + + +/* SPAttributeTable */ + +static void sp_attribute_table_class_init (SPAttributeTableClass *klass); +static void sp_attribute_table_init (SPAttributeTable *widget); +static void sp_attribute_table_destroy (GtkObject *object); + +static void sp_attribute_table_object_modified (SPObject *object, guint flags, SPAttributeTable *spaw); +static void sp_attribute_table_object_release (SPObject *object, SPAttributeTable *spaw); +static void sp_attribute_table_entry_changed (GtkEditable *editable, SPAttributeTable *spat); + +static GtkVBoxClass *table_parent_class; + + + + +GtkType +sp_attribute_table_get_type (void) +{ + static GtkType type = 0; + if (!type) { + static const GtkTypeInfo info = { + "SPAttributeTable", + sizeof (SPAttributeTable), + sizeof (SPAttributeTableClass), + (GtkClassInitFunc) sp_attribute_table_class_init, + (GtkObjectInitFunc) sp_attribute_table_init, + NULL, NULL, NULL + }; + type = gtk_type_unique (GTK_TYPE_VBOX, &info); + } + return type; + +} // end of sp_attribute_table_get_type() + + + +static void +sp_attribute_table_class_init (SPAttributeTableClass *klass) +{ + GtkObjectClass *object_class; + GtkWidgetClass *widget_class; + + object_class = GTK_OBJECT_CLASS (klass); + widget_class = GTK_WIDGET_CLASS (klass); + + table_parent_class = (GtkVBoxClass*)gtk_type_class (GTK_TYPE_VBOX); + + object_class->destroy = sp_attribute_table_destroy; + +} // end of sp_attribute_table_class_init() + + + +static void +sp_attribute_table_init ( SPAttributeTable *spat ) +{ + spat->blocked = FALSE; + spat->hasobj = FALSE; + spat->table = NULL; + spat->src.object = NULL; + spat->num_attr = 0; + spat->attributes = NULL; + spat->entries = NULL; +} + +static void +sp_attribute_table_destroy ( GtkObject *object ) +{ + SPAttributeTable *spat; + + spat = SP_ATTRIBUTE_TABLE (object); + + if (spat->attributes) { + gint i; + for (i = 0; i < spat->num_attr; i++) { + g_free (spat->attributes[i]); + } + g_free (spat->attributes); + spat->attributes = NULL; + } + + if (spat->hasobj) { + + if (spat->src.object) { + sp_signal_disconnect_by_data (spat->src.object, spat); + spat->src.object = NULL; + } + } else { + if (spat->src.repr) { + spat->src.repr = Inkscape::GC::release(spat->src.repr); + } + } // end of if() + + + if (spat->entries) { + g_free (spat->entries); + spat->entries = NULL; + } + + spat->table = NULL; + + if (((GtkObjectClass *) table_parent_class)->destroy) { + (* ((GtkObjectClass *) table_parent_class)->destroy) (object); + } + +} // end of sp_attribute_table_destroy() + + +GtkWidget * +sp_attribute_table_new ( SPObject *object, + gint num_attr, + const gchar **labels, + const gchar **attributes ) +{ + SPAttributeTable *spat; + + g_return_val_if_fail (!object || SP_IS_OBJECT (object), NULL); + g_return_val_if_fail (!object || (num_attr > 0), NULL); + g_return_val_if_fail (!num_attr || (labels && attributes), NULL); + + spat = (SPAttributeTable*)gtk_type_new (SP_TYPE_ATTRIBUTE_TABLE); + + sp_attribute_table_set_object (spat, object, num_attr, labels, attributes); + + return GTK_WIDGET (spat); + +} // end of sp_attribute_table_new() + + + +GtkWidget * +sp_attribute_table_new_repr ( Inkscape::XML::Node *repr, + gint num_attr, + const gchar **labels, + const gchar **attributes ) +{ + SPAttributeTable *spat; + + g_return_val_if_fail (!num_attr || (labels && attributes), NULL); + + spat = (SPAttributeTable*)gtk_type_new (SP_TYPE_ATTRIBUTE_TABLE); + + sp_attribute_table_set_repr (spat, repr, num_attr, labels, attributes); + + return GTK_WIDGET (spat); + +} // end of sp_attribute_table_new_repr() + + + +#define XPAD 4 +#define YPAD 0 + +void +sp_attribute_table_set_object ( SPAttributeTable *spat, + SPObject *object, + gint num_attr, + const gchar **labels, + const gchar **attributes ) +{ + + g_return_if_fail (spat != NULL); + g_return_if_fail (SP_IS_ATTRIBUTE_TABLE (spat)); + g_return_if_fail (!object || SP_IS_OBJECT (object)); + g_return_if_fail (!object || (num_attr > 0)); + g_return_if_fail (!num_attr || (labels && attributes)); + + if (spat->table) { + gtk_widget_destroy (spat->table); + spat->table = NULL; + } + + if (spat->attributes) { + gint i; + for (i = 0; i < spat->num_attr; i++) { + g_free (spat->attributes[i]); + } + g_free (spat->attributes); + spat->attributes = NULL; + } + + if (spat->entries) { + g_free (spat->entries); + spat->entries = NULL; + } + + if (spat->hasobj) { + if (spat->src.object) { + sp_signal_disconnect_by_data (spat->src.object, spat); + spat->src.object = NULL; + } + } else { + if (spat->src.repr) { + spat->src.repr = Inkscape::GC::release(spat->src.repr); + } + } + + spat->hasobj = TRUE; + + if (object) { + gint i; + + spat->blocked = TRUE; + + /* Set up object */ + spat->src.object = object; + spat->num_attr = num_attr; + g_signal_connect ( G_OBJECT (object), "modified", + G_CALLBACK (sp_attribute_table_object_modified), + spat ); + g_signal_connect ( G_OBJECT (object), "release", + G_CALLBACK (sp_attribute_table_object_release), + spat ); + /* Create table */ + spat->table = gtk_table_new (num_attr, 2, FALSE); + gtk_container_add (GTK_CONTAINER (spat), spat->table); + /* Arrays */ + spat->attributes = g_new0 (gchar *, num_attr); + spat->entries = g_new0 (GtkWidget *, num_attr); + /* Fill rows */ + for (i = 0; i < num_attr; i++) { + GtkWidget *w; + const gchar *val; + + spat->attributes[i] = g_strdup (attributes[i]); + w = gtk_label_new (_(labels[i])); + gtk_widget_show (w); + gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5); + gtk_table_attach ( GTK_TABLE (spat->table), w, 0, 1, i, i + 1, + GTK_FILL, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + XPAD, YPAD ); + w = gtk_entry_new (); + gtk_widget_show (w); + val = SP_OBJECT_REPR (object)->attribute(attributes[i]); + gtk_entry_set_text (GTK_ENTRY (w), val ? val : (const gchar *) ""); + gtk_table_attach ( GTK_TABLE (spat->table), w, 1, 2, i, i + 1, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + XPAD, YPAD ); + spat->entries[i] = w; + g_signal_connect ( G_OBJECT (w), "changed", + G_CALLBACK (sp_attribute_table_entry_changed), + spat ); + } + /* Show table */ + gtk_widget_show (spat->table); + + spat->blocked = FALSE; + } + + gtk_widget_set_sensitive ( GTK_WIDGET (spat), + (spat->src.object != NULL) ); + +} // end of sp_attribute_table_set_object() + + + +void +sp_attribute_table_set_repr ( SPAttributeTable *spat, + Inkscape::XML::Node *repr, + gint num_attr, + const gchar **labels, + const gchar **attributes ) +{ + g_return_if_fail (spat != NULL); + g_return_if_fail (SP_IS_ATTRIBUTE_TABLE (spat)); + g_return_if_fail (!num_attr || (labels && attributes)); + + if (spat->table) { + gtk_widget_destroy (spat->table); + spat->table = NULL; + } + + if (spat->attributes) { + gint i; + for (i = 0; i < spat->num_attr; i++) { + g_free (spat->attributes[i]); + } + g_free (spat->attributes); + spat->attributes = NULL; + } + + if (spat->entries) { + g_free (spat->entries); + spat->entries = NULL; + } + + if (spat->hasobj) { + if (spat->src.object) { + sp_signal_disconnect_by_data (spat->src.object, spat); + spat->src.object = NULL; + } + } else { + if (spat->src.repr) { + spat->src.repr = Inkscape::GC::release(spat->src.repr); + } + } + + spat->hasobj = FALSE; + + if (repr) { + gint i; + + spat->blocked = TRUE; + + /* Set up repr */ + spat->src.repr = Inkscape::GC::anchor(repr); + spat->num_attr = num_attr; + /* Create table */ + spat->table = gtk_table_new (num_attr, 2, FALSE); + gtk_container_add (GTK_CONTAINER (spat), spat->table); + /* Arrays */ + spat->attributes = g_new0 (gchar *, num_attr); + spat->entries = g_new0 (GtkWidget *, num_attr); + + /* Fill rows */ + for (i = 0; i < num_attr; i++) { + GtkWidget *w; + const gchar *val; + + spat->attributes[i] = g_strdup (attributes[i]); + w = gtk_label_new (labels[i]); + gtk_widget_show (w); + gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5); + gtk_table_attach ( GTK_TABLE (spat->table), w, 0, 1, i, i + 1, + GTK_FILL, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + XPAD, YPAD ); + w = gtk_entry_new (); + gtk_widget_show (w); + val = repr->attribute(attributes[i]); + gtk_entry_set_text (GTK_ENTRY (w), val ? val : (const gchar *) ""); + gtk_table_attach ( GTK_TABLE (spat->table), w, 1, 2, i, i + 1, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + XPAD, YPAD ); + spat->entries[i] = w; + g_signal_connect ( G_OBJECT (w), "changed", + G_CALLBACK (sp_attribute_table_entry_changed), + spat ); + } + /* Show table */ + gtk_widget_show (spat->table); + + spat->blocked = FALSE; + } + + gtk_widget_set_sensitive (GTK_WIDGET (spat), (spat->src.repr != NULL)); + +} // end of sp_attribute_table_set_repr() + + + +static void +sp_attribute_table_object_modified ( SPObject *object, + guint flags, + SPAttributeTable *spat ) +{ + if (flags && SP_OBJECT_MODIFIED_FLAG) + { + gint i; + for (i = 0; i < spat->num_attr; i++) { + const gchar *val, *text; + val = SP_OBJECT_REPR (spat->src.object)->attribute(spat->attributes[i]); + text = gtk_entry_get_text (GTK_ENTRY (spat->entries[i])); + if (val || text) { + if (!val || !text || strcmp (val, text)) { + /* We are different */ + spat->blocked = TRUE; + gtk_entry_set_text ( GTK_ENTRY (spat->entries[i]), + val ? val : (const gchar *) ""); + spat->blocked = FALSE; + } + } + } + } // end of if() + +} // end of sp_attribute_table_object_modified() + + + +static void +sp_attribute_table_object_release (SPObject *object, SPAttributeTable *spat) +{ + sp_attribute_table_set_object (spat, NULL, 0, NULL, NULL); +} + + + +static void +sp_attribute_table_entry_changed ( GtkEditable *editable, + SPAttributeTable *spat ) +{ + if (!spat->blocked) + { + gint i; + for (i = 0; i < spat->num_attr; i++) { + + if (GTK_WIDGET (editable) == spat->entries[i]) { + const gchar *text; + spat->blocked = TRUE; + text = gtk_entry_get_text (GTK_ENTRY (spat->entries[i])); + + if (!*text) + text = NULL; + + if (spat->hasobj && spat->src.object) { + if (!sp_repr_set_attr ( SP_OBJECT_REPR (spat->src.object), + spat->attributes[i], text)) + { + /* Cannot set attribute */ + text = SP_OBJECT_REPR (spat->src.object)->attribute(spat->attributes[i]); + gtk_entry_set_text ( GTK_ENTRY (spat->entries[i]), + text ? text : (const gchar *) ""); + } + sp_document_done (SP_OBJECT_DOCUMENT (spat->src.object)); + + } else if (spat->src.repr) { + + if (!sp_repr_set_attr (spat->src.repr, + spat->attributes[i], text)) + { + /* Cannot set attribute */ + text = spat->src.repr->attribute(spat->attributes[i]); + gtk_entry_set_text ( GTK_ENTRY (spat->entries[i]), + text ? text : (const gchar *) "" ); + } + /* TODO: Warning! Undo will not be flushed in given case */ + } + spat->blocked = FALSE; + return; + } + } + g_warning ("file %s: line %d: Entry signalled change, but there is no such entry", __FILE__, __LINE__); + } // end of if() + +} // end of sp_attribute_table_entry_changed() + +/* + 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 : diff --git a/src/dialogs/sp-attribute-widget.h b/src/dialogs/sp-attribute-widget.h new file mode 100644 index 000000000..d5b2075ce --- /dev/null +++ b/src/dialogs/sp-attribute-widget.h @@ -0,0 +1,128 @@ +#ifndef __SP_ATTRIBUTE_WIDGET_H__ +#define __SP_ATTRIBUTE_WIDGET_H__ + +/** + * \brief SPAttributeWidget + * + * Widget, that listens and modifies repr attributes + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 2002 authors + * Copyright (C) 2001 Ximian, Inc. + * + * Licensed under GNU GPL, read the file 'COPYING' for more information + */ + +#include + + + +#define SP_TYPE_ATTRIBUTE_WIDGET (sp_attribute_widget_get_type ()) +#define SP_ATTRIBUTE_WIDGET(obj) (GTK_CHECK_CAST ((obj), SP_TYPE_ATTRIBUTE_WIDGET, SPAttributeWidget)) +#define SP_ATTRIBUTE_WIDGET_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), SP_TYPE_ATTRIBUTE_WIDGET, SPAttributeWidgetClass)) +#define SP_IS_ATTRIBUTE_WIDGET(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_ATTRIBUTE_WIDGET)) +#define SP_IS_ATTRIBUTE_WIDGET_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), SP_TYPE_ATTRIBUTE_WIDGET)) + +#define SP_TYPE_ATTRIBUTE_TABLE (sp_attribute_table_get_type ()) +#define SP_ATTRIBUTE_TABLE(obj) (GTK_CHECK_CAST ((obj), SP_TYPE_ATTRIBUTE_TABLE, SPAttributeTable)) +#define SP_ATTRIBUTE_TABLE_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), SP_TYPE_ATTRIBUTE_TABLE, SPAttributeTableClass)) +#define SP_IS_ATTRIBUTE_TABLE(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_ATTRIBUTE_TABLE)) +#define SP_IS_ATTRIBUTE_TABLE_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), SP_TYPE_ATTRIBUTE_TABLE)) + +namespace Inkscape { +namespace XML { +class Node; +} +} + + +struct SPAttributeWidget; +struct SPAttributeWidgetClass; + +struct SPAttributeTable; +struct SPAttributeTableClass; + +#include +#include + +#include + +struct SPAttributeWidget { + GtkEntry entry; + guint blocked : 1; + guint hasobj : 1; + union { + SPObject *object; + Inkscape::XML::Node *repr; + } src; + gchar *attribute; +}; + +struct SPAttributeWidgetClass { + GtkEntryClass entry_class; +}; + +GtkType sp_attribute_widget_get_type (void); + +GtkWidget *sp_attribute_widget_new (SPObject *object, const gchar *attribute); +GtkWidget *sp_attribute_widget_new_repr (Inkscape::XML::Node *repr, const gchar *attribute); + +void sp_attribute_widget_set_object ( SPAttributeWidget *spw, + SPObject *object, + const gchar *attribute ); +void sp_attribute_widget_set_repr ( SPAttributeWidget *spw, + Inkscape::XML::Node *repr, + const gchar *attribute ); + +/* SPAttributeTable */ + +struct SPAttributeTable { + GtkVBox vbox; + guint blocked : 1; + guint hasobj : 1; + GtkWidget *table; + union { + SPObject *object; + Inkscape::XML::Node *repr; + } src; + gint num_attr; + gchar **attributes; + GtkWidget **entries; +}; + +struct SPAttributeTableClass { + GtkEntryClass entry_class; +}; + +GtkType sp_attribute_table_get_type (void); + +GtkWidget *sp_attribute_table_new ( SPObject *object, gint num_attr, + const gchar **labels, + const gchar **attributes ); +GtkWidget *sp_attribute_table_new_repr ( Inkscape::XML::Node *repr, gint num_attr, + const gchar **labels, + const gchar **attributes ); +void sp_attribute_table_set_object ( SPAttributeTable *spw, + SPObject *object, gint num_attr, + const gchar **labels, + const gchar **attrs ); +void sp_attribute_table_set_repr ( SPAttributeTable *spw, + Inkscape::XML::Node *repr, gint num_attr, + const gchar **labels, + const gchar **attrs ); + + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/dialogs/stroke-style.cpp b/src/dialogs/stroke-style.cpp new file mode 100644 index 000000000..b252cd125 --- /dev/null +++ b/src/dialogs/stroke-style.cpp @@ -0,0 +1,1726 @@ +#define __SP_STROKE_STYLE_C__ + +/** + * \brief Stroke style dialog + * + * Authors: + * Lauris Kaplinski + * Bryce Harrington + * bulia byak + * + * Copyright (C) 2001-2005 authors + * Copyright (C) 2001 Ximian, Inc. + * Copyright (C) 2004 John Cliff + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define noSP_SS_VERBOSE + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + + + +#include + +#include +#include "helper/unit-menu.h" +#include "helper/units.h" +#include "svg/css-ostringstream.h" +#include "widgets/sp-widget.h" +#include "widgets/spw-utilities.h" +#include "sp-linear-gradient.h" +#include "sp-radial-gradient.h" +#include "sp-marker.h" +#include +#include +#include +#include "style.h" +#include "gradient-chemistry.h" +#include "sp-namedview.h" +#include "desktop-handles.h" +#include "desktop-style.h" +#include "selection.h" +#include "inkscape.h" +#include "inkscape-stock.h" +#include "dialogs/dialog-events.h" +#include "sp-text.h" +#include "sp-rect.h" +#include "document-private.h" +#include "display/nr-arena.h" +#include "display/nr-arena-item.h" +#include "path-prefix.h" +#include "widgets/icon.h" +#include "helper/stock-items.h" +#include "io/sys.h" + +#include "dialogs/stroke-style.h" + +/* Paint */ + +static void sp_stroke_style_paint_construct(SPWidget *spw, SPPaintSelector *psel); +static void sp_stroke_style_paint_selection_modified (SPWidget *spw, Inkscape::Selection *selection, guint flags, SPPaintSelector *psel); +static void sp_stroke_style_paint_selection_changed (SPWidget *spw, Inkscape::Selection *selection, SPPaintSelector *psel); +static void sp_stroke_style_paint_update(SPWidget *spw); + +static void sp_stroke_style_paint_mode_changed(SPPaintSelector *psel, SPPaintSelectorMode mode, SPWidget *spw); +static void sp_stroke_style_paint_dragged(SPPaintSelector *psel, SPWidget *spw); +static void sp_stroke_style_paint_changed(SPPaintSelector *psel, SPWidget *spw); + +static void sp_stroke_style_widget_change_subselection ( Inkscape::Application *inkscape, SPDesktop *desktop, SPWidget *spw ); + +GtkWidget * +sp_stroke_style_paint_widget_new(void) +{ + GtkWidget *spw, *psel; + + spw = sp_widget_new_global(INKSCAPE); + + psel = sp_paint_selector_new(false); // without fillrule selector + gtk_widget_show(psel); + gtk_container_add(GTK_CONTAINER(spw), psel); + gtk_object_set_data(GTK_OBJECT(spw), "paint-selector", psel); + + gtk_signal_connect(GTK_OBJECT(spw), "construct", + GTK_SIGNAL_FUNC(sp_stroke_style_paint_construct), + psel); + gtk_signal_connect(GTK_OBJECT(spw), "modify_selection", + GTK_SIGNAL_FUNC(sp_stroke_style_paint_selection_modified), + psel); + gtk_signal_connect(GTK_OBJECT(spw), "change_selection", + GTK_SIGNAL_FUNC(sp_stroke_style_paint_selection_changed), + psel); + + g_signal_connect (INKSCAPE, "change_subselection", G_CALLBACK (sp_stroke_style_widget_change_subselection), spw); + + gtk_signal_connect(GTK_OBJECT(psel), "mode_changed", + GTK_SIGNAL_FUNC(sp_stroke_style_paint_mode_changed), + spw); + gtk_signal_connect(GTK_OBJECT(psel), "dragged", + GTK_SIGNAL_FUNC(sp_stroke_style_paint_dragged), + spw); + gtk_signal_connect(GTK_OBJECT(psel), "changed", + GTK_SIGNAL_FUNC(sp_stroke_style_paint_changed), + spw); + + sp_stroke_style_paint_update (SP_WIDGET(spw)); + return spw; +} + +static void +sp_stroke_style_paint_construct(SPWidget *spw, SPPaintSelector *psel) +{ +#ifdef SP_SS_VERBOSE + g_print( "Stroke style widget constructed: inkscape %p repr %p\n", + spw->inkscape, spw->repr ); +#endif + if (spw->inkscape) { + sp_stroke_style_paint_update (spw); + } +} + +static void +sp_stroke_style_paint_selection_modified ( SPWidget *spw, + Inkscape::Selection *selection, + guint flags, + SPPaintSelector *psel) +{ + if (flags & ( SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG | + SP_OBJECT_STYLE_MODIFIED_FLAG) ) { + sp_stroke_style_paint_update(spw); + } +} + + +static void +sp_stroke_style_paint_selection_changed ( SPWidget *spw, + Inkscape::Selection *selection, + SPPaintSelector *psel ) +{ + sp_stroke_style_paint_update (spw); +} + +static void +sp_stroke_style_widget_change_subselection ( Inkscape::Application *inkscape, + SPDesktop *desktop, + SPWidget *spw ) +{ + sp_stroke_style_paint_update (spw); +} + +static void +sp_stroke_style_paint_update (SPWidget *spw) +{ + if (gtk_object_get_data(GTK_OBJECT(spw), "update")) { + return; + } + + gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(TRUE)); + + SPPaintSelector *psel = SP_PAINT_SELECTOR(gtk_object_get_data(GTK_OBJECT(spw), "paint-selector")); + + // create temporary style + SPStyle *query = sp_style_new (); + // query into it + int result = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKE); + + switch (result) { + case QUERY_STYLE_NOTHING: + { + /* No paint at all */ + sp_paint_selector_set_mode (psel, SP_PAINT_SELECTOR_MODE_EMPTY); + break; + } + + case QUERY_STYLE_SINGLE: + case QUERY_STYLE_MULTIPLE_AVERAGED: // TODO: treat this slightly differently, e.g. display "averaged" somewhere in paint selector + case QUERY_STYLE_MULTIPLE_SAME: + { + SPPaintSelectorMode pselmode = sp_style_determine_paint_selector_mode (query, false); + sp_paint_selector_set_mode (psel, pselmode); + + if (query->stroke.set && query->stroke.type == SP_PAINT_TYPE_COLOR) { + gfloat d[3]; + sp_color_get_rgb_floatv (&query->stroke.value.color, d); + SPColor color; + sp_color_set_rgb_float (&color, d[0], d[1], d[2]); + sp_paint_selector_set_color_alpha (psel, &color, SP_SCALE24_TO_FLOAT (query->stroke_opacity.value)); + + } else if (query->stroke.set && query->stroke.type == SP_PAINT_TYPE_PAINTSERVER) { + + SPPaintServer *server = SP_STYLE_STROKE_SERVER (query); + + if (SP_IS_LINEARGRADIENT (server)) { + SPGradient *vector = sp_gradient_get_vector (SP_GRADIENT (server), FALSE); + sp_paint_selector_set_gradient_linear (psel, vector); + + SPLinearGradient *lg = SP_LINEARGRADIENT (server); + sp_paint_selector_set_gradient_properties (psel, + SP_GRADIENT_UNITS (lg), + SP_GRADIENT_SPREAD (lg)); + } else if (SP_IS_RADIALGRADIENT (server)) { + SPGradient *vector = sp_gradient_get_vector (SP_GRADIENT (server), FALSE); + sp_paint_selector_set_gradient_radial (psel, vector); + + SPRadialGradient *rg = SP_RADIALGRADIENT (server); + sp_paint_selector_set_gradient_properties (psel, + SP_GRADIENT_UNITS (rg), + SP_GRADIENT_SPREAD (rg)); + } else if (SP_IS_PATTERN (server)) { + SPPattern *pat = pattern_getroot (SP_PATTERN (server)); + sp_update_pattern_list (psel, pat); + } + } + break; + } + + case QUERY_STYLE_MULTIPLE_DIFFERENT: + { + sp_paint_selector_set_mode (psel, SP_PAINT_SELECTOR_MODE_MULTIPLE); + break; + } + } + + g_free (query); + + gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(FALSE)); +} + +static void +sp_stroke_style_paint_mode_changed( SPPaintSelector *psel, + SPPaintSelectorMode mode, + SPWidget *spw ) +{ + if (gtk_object_get_data(GTK_OBJECT(spw), "update")) { + return; + } + + /* TODO: Does this work? + * Not really, here we have to get old color back from object + * Instead of relying on paint widget having meaningful colors set + */ + sp_stroke_style_paint_changed(psel, spw); +} + +static gchar *undo_label_1 = "stroke:flatcolor:1"; +static gchar *undo_label_2 = "stroke:flatcolor:2"; +static gchar *undo_label = undo_label_1; + +static void +sp_stroke_style_paint_dragged(SPPaintSelector *psel, SPWidget *spw) +{ + if (gtk_object_get_data(GTK_OBJECT(spw), "update")) { + return; + } + + switch (psel->mode) { + case SP_PAINT_SELECTOR_MODE_COLOR_RGB: + case SP_PAINT_SELECTOR_MODE_COLOR_CMYK: + { + sp_paint_selector_set_flat_color (psel, SP_ACTIVE_DESKTOP, "stroke", "stroke-opacity"); + sp_document_maybe_done (SP_DT_DOCUMENT(SP_ACTIVE_DESKTOP), undo_label); + break; + } + + default: + g_warning( "file %s: line %d: Paint %d should not emit 'dragged'", + __FILE__, __LINE__, psel->mode); + break; + } +} + +static void +sp_stroke_style_paint_changed(SPPaintSelector *psel, SPWidget *spw) +{ + if (gtk_object_get_data(GTK_OBJECT(spw), "update")) { + return; + } + g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (TRUE)); + + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + SPDocument *document = SP_DT_DOCUMENT (desktop); + Inkscape::Selection *selection = SP_DT_SELECTION (desktop); + + GSList const *items = selection->itemList(); + + switch (psel->mode) { + case SP_PAINT_SELECTOR_MODE_EMPTY: + // This should not happen. + g_warning ( "file %s: line %d: Paint %d should not emit 'changed'", + __FILE__, __LINE__, psel->mode); + break; + case SP_PAINT_SELECTOR_MODE_MULTIPLE: + // This happens when you switch multiple objects with different gradients to flat color; + // nothing to do here. + break; + + case SP_PAINT_SELECTOR_MODE_NONE: + { + SPCSSAttr *css = sp_repr_css_attr_new(); + sp_repr_css_set_property(css, "stroke", "none"); + + sp_desktop_set_style (desktop, css); + + sp_repr_css_attr_unref(css); + + sp_document_done(document); + break; + } + + case SP_PAINT_SELECTOR_MODE_COLOR_RGB: + case SP_PAINT_SELECTOR_MODE_COLOR_CMYK: + { + sp_paint_selector_set_flat_color (psel, desktop, "stroke", "stroke-opacity"); + sp_document_maybe_done (SP_DT_DOCUMENT(desktop), undo_label); + + // on release, toggle undo_label so that the next drag will not be lumped with this one + if (undo_label == undo_label_1) + undo_label = undo_label_2; + else + undo_label = undo_label_1; + + break; + } + + case SP_PAINT_SELECTOR_MODE_GRADIENT_LINEAR: + case SP_PAINT_SELECTOR_MODE_GRADIENT_RADIAL: + if (items) { + SPGradientType const gradient_type = ( psel->mode == SP_PAINT_SELECTOR_MODE_GRADIENT_LINEAR + ? SP_GRADIENT_TYPE_LINEAR + : SP_GRADIENT_TYPE_RADIAL ); + SPGradient *vector = sp_paint_selector_get_gradient_vector(psel); + if (!vector) { + /* No vector in paint selector should mean that we just changed mode */ + + SPStyle *query = sp_style_new (); + int result = objects_query_fillstroke ((GSList *) items, query, false); + guint32 common_rgb = 0; + if (result == QUERY_STYLE_MULTIPLE_SAME) { + if (query->fill.type != SP_PAINT_TYPE_COLOR) { + common_rgb = sp_desktop_get_color(desktop, false); + } else { + common_rgb = sp_color_get_rgba32_ualpha(&query->stroke.value.color, 0xff); + } + vector = sp_document_default_gradient_vector(document, common_rgb); + } + g_free (query); + + for (GSList const *i = items; i != NULL; i = i->next) { + if (!vector) { + sp_item_set_gradient(SP_ITEM(i->data), + sp_gradient_vector_for_object(document, desktop, SP_OBJECT(i->data), false), + gradient_type, false); + } else { + sp_item_set_gradient(SP_ITEM(i->data), vector, gradient_type, false); + } + } + } else { + vector = sp_gradient_ensure_vector_normalized(vector); + for (GSList const *i = items; i != NULL; i = i->next) { + SPGradient *gr = sp_item_set_gradient(SP_ITEM(i->data), vector, gradient_type, false); + sp_gradient_selector_attrs_to_gradient(gr, psel); + } + } + + sp_document_done(document); + } + break; + + case SP_PAINT_SELECTOR_MODE_PATTERN: + + if (items) { + + SPPattern *pattern = sp_paint_selector_get_pattern (psel); + if (!pattern) { + + /* No Pattern in paint selector should mean that we just + * changed mode - dont do jack. + */ + + } else { + Inkscape::XML::Node *patrepr = SP_OBJECT_REPR(pattern); + SPCSSAttr *css = sp_repr_css_attr_new (); + gchar *urltext = g_strdup_printf ("url(#%s)", patrepr->attribute("id")); + sp_repr_css_set_property (css, "stroke", urltext); + + for (GSList const *i = items; i != NULL; i = i->next) { + Inkscape::XML::Node *selrepr = SP_OBJECT_REPR (i->data); + SPObject *selobj = SP_OBJECT (i->data); + if (!selrepr) + continue; + + SPStyle *style = SP_OBJECT_STYLE (selobj); + if (style && style->stroke.type == SP_PAINT_TYPE_PAINTSERVER) { + SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER (selobj); + if (SP_IS_PATTERN (server) && pattern_getroot (SP_PATTERN(server)) == pattern) + // only if this object's pattern is not rooted in our selected pattern, apply + continue; + } + + sp_repr_css_change_recursive (selrepr, css, "style"); + } + + sp_repr_css_attr_unref (css); + g_free (urltext); + + } // end if + + sp_document_done (document); + } // end if + + break; + + case SP_PAINT_SELECTOR_MODE_UNSET: + if (items) { + SPCSSAttr *css = sp_repr_css_attr_new (); + sp_repr_css_unset_property (css, "stroke"); + + sp_desktop_set_style (desktop, css); + sp_repr_css_attr_unref (css); + + sp_document_done (document); + } + break; + + default: + g_warning( "file %s: line %d: Paint selector should not be in " + "mode %d", + __FILE__, __LINE__, + psel->mode ); + break; + } + + g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (FALSE)); +} + + + + + +/* Line */ + +static void sp_stroke_style_line_construct(SPWidget *spw, gpointer data); +static void sp_stroke_style_line_selection_modified (SPWidget *spw, + Inkscape::Selection *selection, + guint flags, + gpointer data); + +static void sp_stroke_style_line_selection_changed (SPWidget *spw, + Inkscape::Selection *selection, + gpointer data ); + +static void sp_stroke_style_line_update(SPWidget *spw, Inkscape::Selection *sel); + +static void sp_stroke_style_set_join_buttons(SPWidget *spw, + GtkWidget *active); + +static void sp_stroke_style_set_cap_buttons(SPWidget *spw, + GtkWidget *active); + +static void sp_stroke_style_width_changed(GtkAdjustment *adj, SPWidget *spw); +static void sp_stroke_style_miterlimit_changed(GtkAdjustment *adj, SPWidget *spw); +static void sp_stroke_style_any_toggled(GtkToggleButton *tb, SPWidget *spw); +static void sp_stroke_style_line_dash_changed(SPDashSelector *dsel, + SPWidget *spw); + +static void sp_stroke_style_update_marker_menus(SPWidget *spw, GSList const *objects); + +static SPObject *ink_extract_marker_name(gchar const *n); + + +/** + * Helper function for creating radio buttons. This should probably be re-thought out + * when reimplementing this with Gtkmm. + */ +static GtkWidget * +sp_stroke_radio_button(GtkWidget *tb, char const *icon, + GtkWidget *hb, GtkWidget *spw, + gchar const *key, gchar const *data) +{ + g_assert(icon != NULL); + g_assert(hb != NULL); + g_assert(spw != NULL); + + if (tb == NULL) { + tb = gtk_radio_button_new(NULL); + } else { + tb = gtk_radio_button_new(gtk_radio_button_group(GTK_RADIO_BUTTON(tb)) ); + } + + gtk_widget_show(tb); + gtk_toggle_button_set_mode(GTK_TOGGLE_BUTTON(tb), FALSE); + gtk_box_pack_start(GTK_BOX(hb), tb, FALSE, FALSE, 0); + gtk_object_set_data(GTK_OBJECT(spw), icon, tb); + gtk_object_set_data(GTK_OBJECT(tb), key, (gpointer*)data); + gtk_signal_connect(GTK_OBJECT(tb), "toggled", + GTK_SIGNAL_FUNC(sp_stroke_style_any_toggled), + spw); + GtkWidget *px = sp_icon_new(GTK_ICON_SIZE_LARGE_TOOLBAR, icon); + g_assert(px != NULL); + gtk_widget_show(px); + gtk_container_add(GTK_CONTAINER(tb), px); + + return tb; + +} + +static GtkWidget * +sp_marker_prev_new(unsigned size, gchar const *mname, + SPDocument *source, SPDocument *sandbox, + gchar *menu_id, NRArena const *arena, unsigned visionkey, NRArenaItem *root) +{ + // the object of the marker + SPObject const *marker = source->getObjectById(mname); + if (marker == NULL) + return NULL; + + // the repr of the marker; make a copy with id="sample" + Inkscape::XML::Node *mrepr = SP_OBJECT_REPR (marker)->duplicate(); + mrepr->setAttribute("id", "sample"); + + // replace the old sample in the sandbox by the new one + Inkscape::XML::Node *defsrepr = SP_OBJECT_REPR (sandbox->getObjectById("defs")); + SPObject *oldmarker = sandbox->getObjectById("sample"); + if (oldmarker) + oldmarker->deleteObject(false); + defsrepr->appendChild(mrepr); + Inkscape::GC::release(mrepr); + +// Uncomment this to get the sandbox documents saved (useful for debugging) + //FILE *fp = fopen (g_strconcat(mname, ".svg", NULL), "w"); + //sp_repr_save_stream (sp_document_repr_doc (sandbox), fp); + //fclose (fp); + + // object to render; note that the id is the same as that of the menu we're building + SPObject *object = sandbox->getObjectById(menu_id); + sp_document_root (sandbox)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + sp_document_ensure_up_to_date(sandbox); + + if (object == NULL || !SP_IS_ITEM(object)) + return NULL; // sandbox broken? + + // Find object's bbox in document + NR::Matrix const i2doc(sp_item_i2doc_affine(SP_ITEM(object))); + + NR::Rect const dbox = SP_ITEM(object)->invokeBbox(i2doc); + + if (dbox.isEmpty()) { + return NULL; + } + + /* Update to renderable state */ + NRMatrix t; + double sf = 0.8; + nr_matrix_set_scale(&t, sf, sf); + nr_arena_item_set_transform(root, &t); + NRGC gc(NULL); + nr_matrix_set_identity(&gc.transform); + nr_arena_item_invoke_update( root, NULL, &gc, + NR_ARENA_ITEM_STATE_ALL, + NR_ARENA_ITEM_STATE_NONE ); + + /* Item integer bbox in points */ + NRRectL ibox; + ibox.x0 = (int) floor(sf * dbox.min()[NR::X] + 0.5); + ibox.y0 = (int) floor(sf * dbox.min()[NR::Y] + 0.5); + ibox.x1 = (int) floor(sf * dbox.max()[NR::X] + 0.5); + ibox.y1 = (int) floor(sf * dbox.max()[NR::Y] + 0.5); + + /* Find visible area */ + int width = ibox.x1 - ibox.x0; + int height = ibox.y1 - ibox.y0; + int dx = size; + int dy = size; + dx=(dx - width)/2; // watch out for size, since 'unsigned'-'signed' can cause problems if the result is negative + dy=(dy - height)/2; + + NRRectL area; + area.x0 = ibox.x0 - dx; + area.y0 = ibox.y0 - dy; + area.x1 = area.x0 + size; + area.y1 = area.y0 + size; + + /* Actual renderable area */ + NRRectL ua; + ua.x0 = MAX(ibox.x0, area.x0); + ua.y0 = MAX(ibox.y0, area.y0); + ua.x1 = MIN(ibox.x1, area.x1); + ua.y1 = MIN(ibox.y1, area.y1); + + /* Set up pixblock */ + guchar *px = nr_new(guchar, 4 * size * size); + memset(px, 0x00, 4 * size * size); + + /* Render */ + NRPixBlock B; + nr_pixblock_setup_extern( &B, NR_PIXBLOCK_MODE_R8G8B8A8N, + ua.x0, ua.y0, ua.x1, ua.y1, + px + 4 * size * (ua.y0 - area.y0) + + 4 * (ua.x0 - area.x0), + 4 * size, FALSE, FALSE ); + nr_arena_item_invoke_render( root, &ua, &B, + NR_ARENA_ITEM_RENDER_NO_CACHE ); + nr_pixblock_release(&B); + + // Create widget + GtkWidget *pb = gtk_image_new_from_pixbuf(gdk_pixbuf_new_from_data(px, + GDK_COLORSPACE_RGB, + TRUE, + 8, size, size, size * 4, + (GdkPixbufDestroyNotify)nr_free, + NULL)); + return pb; +} + + +#define MARKER_ITEM_MARGIN 0 + + +/** + * sp_marker_list_from_doc() + * + * \brief Pick up all markers from source, except those that are in + * current_doc (if non-NULL), and add items to the m menu + * + */ +static void +sp_marker_list_from_doc (GtkWidget *m, SPDocument *current_doc, SPDocument *source, SPDocument *markers_doc, SPDocument *sandbox, gchar *menu_id) +{ + + // search through defs + GSList *ml = NULL; + SPDefs *defs= (SPDefs *) SP_DOCUMENT_DEFS (source); + for ( SPObject *ochild = sp_object_first_child(SP_OBJECT(defs)) ; ochild != NULL ; ochild = SP_OBJECT_NEXT (ochild) ) { + if (SP_IS_MARKER(ochild)) { + ml = g_slist_prepend (ml, ochild); + } + } + + // Do this here, outside of loop, to speed up preview generation: + /* Create new arena */ + NRArena const *arena = NRArena::create(); + /* Create ArenaItem and set transform */ + unsigned const visionkey = sp_item_display_key_new(1); + NRArenaItem *root = sp_item_invoke_show( SP_ITEM(SP_DOCUMENT_ROOT (sandbox)), (NRArena *) arena, visionkey, SP_ITEM_SHOW_DISPLAY ); + + for (; ml != NULL; ml = ml->next) { + + if (!SP_IS_MARKER(ml->data)) + continue; + + Inkscape::XML::Node *repr = SP_OBJECT_REPR((SPItem *) ml->data); + + bool stock_dupe = false; + + if (markers_doc && repr->attribute("inkscape:stockid")) { + // find out if markers_doc has a marker with the same stockid, and if so, skip this + for (SPObject *child = sp_object_first_child(SP_OBJECT(SP_DOCUMENT_DEFS(markers_doc))) ; + child != NULL; + child = SP_OBJECT_NEXT(child) ) + { + if (SP_IS_MARKER(child) && + SP_OBJECT_REPR(child)->attribute("inkscape:stockid") && + !strcmp(repr->attribute("inkscape:stockid"), SP_OBJECT_REPR(child)->attribute("inkscape:stockid"))) { + stock_dupe = true; + } + } + } + + if (stock_dupe) // stock item, dont add to list from current doc + continue; + + GtkWidget *i = gtk_menu_item_new(); + gtk_widget_show(i); + + if (repr->attribute("inkscape:stockid")) + g_object_set_data (G_OBJECT(i), "stockid", (void *) "true"); + else + g_object_set_data (G_OBJECT(i), "stockid", (void *) "false"); + + gchar const *markid = repr->attribute("id"); + g_object_set_data (G_OBJECT(i), "marker", (void *) markid); + + GtkWidget *hb = gtk_hbox_new(FALSE, MARKER_ITEM_MARGIN); + gtk_widget_show(hb); + + // generate preview + GtkWidget *prv = sp_marker_prev_new (22, markid, source, sandbox, menu_id, arena, visionkey, root); + gtk_widget_show(prv); + gtk_box_pack_start(GTK_BOX(hb), prv, FALSE, FALSE, 6); + + // create label + GtkWidget *l = gtk_label_new(repr->attribute("id")); + gtk_widget_show(l); + gtk_misc_set_alignment(GTK_MISC(l), 0.0, 0.5); + + gtk_box_pack_start(GTK_BOX(hb), l, TRUE, TRUE, 0); + + gtk_widget_show(hb); + gtk_container_add(GTK_CONTAINER(i), hb); + + gtk_menu_append(GTK_MENU(m), i); + } + + g_slist_free (ml); +} + + +SPDocument * +ink_markers_preview_doc () +{ +gchar const *buffer = "" +" " + +" " +" " +" " +" " + +" " +" " +" " +" " + +" " +" " +" " +" " + +""; + + return sp_document_new_from_mem (buffer, strlen(buffer), FALSE); +} + + + +static GtkWidget * +ink_marker_menu( GtkWidget *tbl, gchar *menu_id, SPDocument *sandbox) +{ + SPDesktop *desktop = inkscape_active_desktop(); + SPDocument *doc = SP_DT_DOCUMENT(desktop); + GtkWidget *mnu = gtk_option_menu_new(); + + /* Create new menu widget */ + GtkWidget *m = gtk_menu_new(); + gtk_widget_show(m); + + g_object_set_data(G_OBJECT(mnu), "updating", (gpointer) FALSE); + + if (!doc) { + GtkWidget *i = gtk_menu_item_new_with_label(_("No document selected")); + gtk_widget_show(i); + gtk_menu_append(GTK_MENU(m), i); + gtk_widget_set_sensitive(mnu, FALSE); + + } else { + + // add "None" + { + GtkWidget *i = gtk_menu_item_new(); + gtk_widget_show(i); + + g_object_set_data(G_OBJECT(i), "marker", (void *) "none"); + + GtkWidget *hb = gtk_hbox_new(FALSE, MARKER_ITEM_MARGIN); + gtk_widget_show(hb); + + GtkWidget *l = gtk_label_new( _("None") ); + gtk_widget_show(l); + gtk_misc_set_alignment(GTK_MISC(l), 0.0, 0.5); + + gtk_box_pack_start(GTK_BOX(hb), l, TRUE, TRUE, 0); + + gtk_widget_show(hb); + gtk_container_add(GTK_CONTAINER(i), hb); + gtk_menu_append(GTK_MENU(m), i); + } + + // find and load markers.svg + static SPDocument *markers_doc = NULL; + char *markers_source = g_build_filename(INKSCAPE_MARKERSDIR, "markers.svg", NULL); + if (Inkscape::IO::file_test(markers_source, G_FILE_TEST_IS_REGULAR)) { + markers_doc = sp_document_new(markers_source, FALSE); + } + g_free(markers_source); + + // suck in from current doc + sp_marker_list_from_doc ( m, NULL, doc, markers_doc, sandbox, menu_id ); + + // add separator + { + GtkWidget *i = gtk_separator_menu_item_new(); + gtk_widget_show(i); + gtk_menu_append(GTK_MENU(m), i); + } + + // suck in from markers.svg + if (markers_doc) { + sp_document_ensure_up_to_date(doc); + sp_marker_list_from_doc ( m, doc, markers_doc, NULL, sandbox, menu_id ); + } + + gtk_widget_set_sensitive(mnu, TRUE); + } + + gtk_object_set_data(GTK_OBJECT(mnu), "menu_id", menu_id); + gtk_option_menu_set_menu(GTK_OPTION_MENU(mnu), m); + + /* Set history */ + gtk_option_menu_set_history(GTK_OPTION_MENU(mnu), 0); + + return mnu; +} + + +/*user selected existing marker from list*/ +static void +sp_marker_select(GtkOptionMenu *mnu, GtkWidget *spw) +{ + if (gtk_object_get_data(GTK_OBJECT(spw), "update")) { + return; + } + + SPDesktop *desktop = inkscape_active_desktop(); + SPDocument *document = SP_DT_DOCUMENT(desktop); + if (!document) { + return; + } + + /* Get Marker */ + if (!g_object_get_data(G_OBJECT(gtk_menu_get_active(GTK_MENU(gtk_option_menu_get_menu(mnu)))), + "marker")) + { + return; + } + gchar *markid = (gchar *) g_object_get_data(G_OBJECT(gtk_menu_get_active(GTK_MENU(gtk_option_menu_get_menu(mnu)))), + "marker"); + gchar *marker = ""; + if (strcmp(markid, "none")){ + gchar *stockid = (gchar *) g_object_get_data(G_OBJECT(gtk_menu_get_active(GTK_MENU(gtk_option_menu_get_menu(mnu)))), + "stockid"); + + gchar *markurn = markid; + if (!strcmp(stockid,"true")) markurn = g_strconcat("urn:inkscape:marker:",markid,NULL); + SPObject *mark = get_stock_item(markurn); + if (mark) { + Inkscape::XML::Node *repr = SP_OBJECT_REPR(mark); + marker = g_strconcat("url(#", repr->attribute("id"), ")", NULL); + } + } else { + marker = markid; + } + + SPCSSAttr *css = sp_repr_css_attr_new(); + gchar *menu_id = (gchar *) g_object_get_data(G_OBJECT(mnu), "menu_id"); + sp_repr_css_set_property(css, menu_id, marker); + + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + GSList const *items = selection->itemList(); + for (; items != NULL; items = items->next) { + SPItem *item = (SPItem *) items->data; + if (!SP_IS_SHAPE(item) || SP_IS_RECT(item)) // can't set marker to rect, until it's converted to using + continue; + Inkscape::XML::Node *selrepr = SP_OBJECT_REPR((SPItem *) items->data); + if (selrepr) { + sp_repr_css_change_recursive(selrepr, css, "style"); + } + SP_OBJECT(items->data)->requestModified(SP_OBJECT_MODIFIED_FLAG); + SP_OBJECT(items->data)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + } + + sp_repr_css_attr_unref(css); + + sp_document_done(document); +} + +static gboolean stroke_width_set_unit(SPUnitSelector *, + SPUnit const *old, + SPUnit const *new_units, + GObject *spw) +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + + if (!desktop) { + return FALSE; + } + + Inkscape::Selection *selection = SP_DT_SELECTION (desktop); + + if (selection->isEmpty()) + return FALSE; + + GSList const *objects = selection->itemList(); + + if ((old->base == SP_UNIT_ABSOLUTE || old->base == SP_UNIT_DEVICE) && + (new_units->base == SP_UNIT_DIMENSIONLESS)) { + + /* Absolute to percentage */ + g_object_set_data (spw, "update", GUINT_TO_POINTER (TRUE)); + + GtkAdjustment *a = GTK_ADJUSTMENT(g_object_get_data (spw, "width")); + float w = sp_units_get_pixels (a->value, *old); + + gdouble average = stroke_average_width (objects); + + if (average == NR_HUGE || average == 0) + return FALSE; + + gtk_adjustment_set_value (a, 100.0 * w / average); + + g_object_set_data (spw, "update", GUINT_TO_POINTER (FALSE)); + return TRUE; + + } else if ((old->base == SP_UNIT_DIMENSIONLESS) && + (new_units->base == SP_UNIT_ABSOLUTE || new_units->base == SP_UNIT_DEVICE)) { + + /* Percentage to absolute */ + g_object_set_data (spw, "update", GUINT_TO_POINTER (TRUE)); + + GtkAdjustment *a = GTK_ADJUSTMENT(g_object_get_data (spw, "width")); + + gdouble average = stroke_average_width (objects); + + gtk_adjustment_set_value (a, sp_pixels_get_units (0.01 * a->value * average, *new_units)); + + g_object_set_data (spw, "update", GUINT_TO_POINTER (FALSE)); + return TRUE; + } + + return FALSE; +} + + +/** + * \brief Creates a new widget for the line stroke style. + * + */ +GtkWidget * +sp_stroke_style_line_widget_new(void) +{ + GtkWidget *spw, *f, *t, *hb, *sb, *us, *tb, *ds; + GtkObject *a; + + GtkTooltips *tt = gtk_tooltips_new(); + + spw = sp_widget_new_global(INKSCAPE); + + f = gtk_hbox_new (FALSE, 0); + gtk_widget_show(f); + gtk_container_add(GTK_CONTAINER(spw), f); + + t = gtk_table_new(3, 6, FALSE); + gtk_widget_show(t); + gtk_container_set_border_width(GTK_CONTAINER(t), 4); + gtk_table_set_row_spacings(GTK_TABLE(t), 4); + gtk_container_add(GTK_CONTAINER(f), t); + gtk_object_set_data(GTK_OBJECT(spw), "stroke", t); + + gint i = 0; + + /* Stroke width */ + spw_label(t, _("Width:"), 0, i); + + hb = spw_hbox(t, 3, 1, i); + +// TODO: when this is gtkmmified, use an Inkscape::UI::Widget::ScalarUnit instead of the separate +// spinbutton and unit selector for stroke width. In sp_stroke_style_line_update, use +// setHundredPercent to remember the aeraged width corresponding to 100%. Then the +// stroke_width_set_unit will be removed (because ScalarUnit takes care of conversions itself), and +// with it, the two remaining calls of stroke_average_width, allowing us to get rid of that +// function in desktop-style. + + a = gtk_adjustment_new(1.0, 0.0, 1000.0, 0.1, 10.0, 10.0); + gtk_object_set_data(GTK_OBJECT(spw), "width", a); + sb = gtk_spin_button_new(GTK_ADJUSTMENT(a), 0.1, 3); + gtk_tooltips_set_tip(tt, sb, _("Stroke width"), NULL); + gtk_widget_show(sb); + + sp_dialog_defocus_on_enter(sb); + + gtk_box_pack_start(GTK_BOX(hb), sb, FALSE, FALSE, 0); + us = sp_unit_selector_new(SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE); + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop) + sp_unit_selector_set_unit (SP_UNIT_SELECTOR(us), SP_DT_NAMEDVIEW(desktop)->doc_units); + sp_unit_selector_add_unit(SP_UNIT_SELECTOR(us), &sp_unit_get_by_id(SP_UNIT_PERCENT), 0); + g_signal_connect ( G_OBJECT (us), "set_unit", G_CALLBACK (stroke_width_set_unit), spw ); + gtk_widget_show(us); + sp_unit_selector_add_adjustment( SP_UNIT_SELECTOR(us), GTK_ADJUSTMENT(a) ); + gtk_box_pack_start(GTK_BOX(hb), us, FALSE, FALSE, 0); + gtk_object_set_data(GTK_OBJECT(spw), "units", us); + + gtk_signal_connect( GTK_OBJECT(a), "value_changed", GTK_SIGNAL_FUNC(sp_stroke_style_width_changed), spw ); + i++; + + /* Join type */ + // TRANSLATORS: The line join style specifies the shape to be used at the + // corners of paths. It can be "miter", "round" or "bevel". + spw_label(t, _("Join:"), 0, i); + + hb = spw_hbox(t, 3, 1, i); + + tb = NULL; + + tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_JOIN_MITER, + hb, spw, "join", "miter"); + + // TRANSLATORS: Miter join: joining lines with a sharp (pointed) corner. + // For an example, draw a triangle with a large stroke width and modify the + // "Join" option (in the Fill and Stroke dialog). + gtk_tooltips_set_tip(tt, tb, _("Miter join"), NULL); + + tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_JOIN_ROUND, + hb, spw, "join", "round"); + + // TRANSLATORS: Round join: joining lines with a rounded corner. + // For an example, draw a triangle with a large stroke width and modify the + // "Join" option (in the Fill and Stroke dialog). + gtk_tooltips_set_tip(tt, tb, _("Round join"), NULL); + + tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_JOIN_BEVEL, + hb, spw, "join", "bevel"); + + // TRANSLATORS: Bevel join: joining lines with a blunted (flattened) corner. + // For an example, draw a triangle with a large stroke width and modify the + // "Join" option (in the Fill and Stroke dialog). + gtk_tooltips_set_tip(tt, tb, _("Bevel join"), NULL); + + i++; + + /* Miterlimit */ + // TRANSLATORS: Miter limit: only for "miter join", this limits the length + // of the sharp "spike" when the lines connect at too sharp an angle. + // When two line segments meet at a sharp angle, a miter join results in a + // spike that extends well beyond the connection point. The purpose of the + // miter limit is to cut off such spikes (i.e. convert them into bevels) + // when they become too long. + spw_label(t, _("Miter limit:"), 0, i); + + hb = spw_hbox(t, 3, 1, i); + + a = gtk_adjustment_new(4.0, 0.0, 100.0, 0.1, 10.0, 10.0); + gtk_object_set_data(GTK_OBJECT(spw), "miterlimit", a); + + sb = gtk_spin_button_new(GTK_ADJUSTMENT(a), 0.1, 2); + gtk_tooltips_set_tip(tt, sb, _("Maximum length of the miter (in units of stroke width)"), NULL); + gtk_widget_show(sb); + gtk_object_set_data(GTK_OBJECT(spw), "miterlimit_sb", sb); + sp_dialog_defocus_on_enter(sb); + + gtk_box_pack_start(GTK_BOX(hb), sb, FALSE, FALSE, 0); + + gtk_signal_connect( GTK_OBJECT(a), "value_changed", + GTK_SIGNAL_FUNC(sp_stroke_style_miterlimit_changed), spw ); + i++; + + /* Cap type */ + // TRANSLATORS: cap type specifies the shape for the ends of lines + spw_label(t, _("Cap:"), 0, i); + + hb = spw_hbox(t, 3, 1, i); + + tb = NULL; + + tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_CAP_BUTT, + hb, spw, "cap", "butt"); + + // TRANSLATORS: Butt cap: the line shape does not extend beyond the end point + // of the line; the ends of the line are square + gtk_tooltips_set_tip(tt, tb, _("Butt cap"), NULL); + + tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_CAP_ROUND, + hb, spw, "cap", "round"); + + // TRANSLATORS: Round cap: the line shape extends beyond the end point of the + // line; the ends of the line are rounded + gtk_tooltips_set_tip(tt, tb, _("Round cap"), NULL); + + tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_CAP_SQUARE, + hb, spw, "cap", "square"); + + // TRANSLATORS: Square cap: the line shape extends beyond the end point of the + // line; the ends of the line are square + gtk_tooltips_set_tip(tt, tb, _("Square cap"), NULL); + + i++; + + + /* Dash */ + spw_label(t, _("Dashes:"), 0, i); + ds = sp_dash_selector_new( inkscape_get_repr( INKSCAPE, + "palette.dashes") ); + + gtk_widget_show(ds); + gtk_table_attach( GTK_TABLE(t), ds, 1, 4, i, i+1, + (GtkAttachOptions)( GTK_EXPAND | GTK_FILL ), + (GtkAttachOptions)0, 0, 0 ); + gtk_object_set_data(GTK_OBJECT(spw), "dash", ds); + gtk_signal_connect( GTK_OBJECT(ds), "changed", + GTK_SIGNAL_FUNC(sp_stroke_style_line_dash_changed), + spw ); + i++; + + /* Drop down marker selectors*/ + + // doing this here once, instead of for each preview, to speed things up + SPDocument *sandbox = ink_markers_preview_doc (); + + // TRANSLATORS: Path markers are an SVG feature that allows you to attach arbitrary shapes + // (arrowheads, bullets, faces, whatever) to the start, end, or middle nodes of a path. + spw_label(t, _("Start Markers:"), 0, i); + GtkWidget *mnu = ink_marker_menu( spw ,"marker-start", sandbox); + gtk_signal_connect( GTK_OBJECT(mnu), "changed", GTK_SIGNAL_FUNC(sp_marker_select), spw ); + gtk_widget_show(mnu); + gtk_table_attach( GTK_TABLE(t), mnu, 1, 4, i, i+1, + (GtkAttachOptions)( GTK_EXPAND | GTK_FILL ), + (GtkAttachOptions)0, 0, 0 ); + gtk_object_set_data(GTK_OBJECT(spw), "start_mark_menu", mnu); + + i++; + spw_label(t, _("Mid Markers:"), 0, i); + mnu = NULL; + mnu = ink_marker_menu( spw ,"marker-mid", sandbox); + gtk_signal_connect( GTK_OBJECT(mnu), "changed", GTK_SIGNAL_FUNC(sp_marker_select), spw ); + gtk_widget_show(mnu); + gtk_table_attach( GTK_TABLE(t), mnu, 1, 4, i, i+1, + (GtkAttachOptions)( GTK_EXPAND | GTK_FILL ), + (GtkAttachOptions)0, 0, 0 ); + gtk_object_set_data(GTK_OBJECT(spw), "mid_mark_menu", mnu); + + i++; + spw_label(t, _("End Markers:"), 0, i); + mnu = NULL; + mnu = ink_marker_menu( spw ,"marker-end", sandbox); + gtk_signal_connect( GTK_OBJECT(mnu), "changed", GTK_SIGNAL_FUNC(sp_marker_select), spw ); + gtk_widget_show(mnu); + gtk_table_attach( GTK_TABLE(t), mnu, 1, 4, i, i+1, + (GtkAttachOptions)( GTK_EXPAND | GTK_FILL ), + (GtkAttachOptions)0, 0, 0 ); + gtk_object_set_data(GTK_OBJECT(spw), "end_mark_menu", mnu); + + i++; + + gtk_signal_connect( GTK_OBJECT(spw), "construct", + GTK_SIGNAL_FUNC(sp_stroke_style_line_construct), + NULL ); + gtk_signal_connect( GTK_OBJECT(spw), "modify_selection", + GTK_SIGNAL_FUNC(sp_stroke_style_line_selection_modified), + NULL ); + gtk_signal_connect( GTK_OBJECT(spw), "change_selection", + GTK_SIGNAL_FUNC(sp_stroke_style_line_selection_changed), + NULL ); + + sp_stroke_style_line_update( SP_WIDGET(spw), desktop ? SP_DT_SELECTION(desktop) : NULL); + + return spw; +} + + + +static void +sp_stroke_style_line_construct(SPWidget *spw, gpointer data) +{ + +#ifdef SP_SS_VERBOSE + g_print( "Stroke style widget constructed: inkscape %p repr %p\n", + spw->inkscape, spw->repr ); +#endif + if (spw->inkscape) { + sp_stroke_style_line_update(spw, + ( SP_ACTIVE_DESKTOP + ? SP_DT_SELECTION(SP_ACTIVE_DESKTOP) + : NULL )); + } +} + +static void +sp_stroke_style_line_selection_modified ( SPWidget *spw, + Inkscape::Selection *selection, + guint flags, + gpointer data ) +{ + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG)) { + sp_stroke_style_line_update (spw, selection); + } + +} + +static void +sp_stroke_style_line_selection_changed ( SPWidget *spw, + Inkscape::Selection *selection, + gpointer data ) +{ + sp_stroke_style_line_update (spw, selection); +} + + +static void +sp_dash_selector_set_from_style (GtkWidget *dsel, SPStyle *style) +{ + if (style->stroke_dash.n_dash > 0) { + double d[64]; + int len = MIN(style->stroke_dash.n_dash, 64); + for (int i = 0; i < len; i++) { + if (style->stroke_width.computed != 0) + d[i] = style->stroke_dash.dash[i] / style->stroke_width.computed; + else + d[i] = style->stroke_dash.dash[i]; // is there a better thing to do for stroke_width==0? + } + sp_dash_selector_set_dash(SP_DASH_SELECTOR(dsel), len, d, + style->stroke_width.computed != 0? + style->stroke_dash.offset / style->stroke_width.computed : + style->stroke_dash.offset); + } else { + sp_dash_selector_set_dash(SP_DASH_SELECTOR(dsel), 0, NULL, 0.0); + } +} + +static void +sp_jointype_set (SPWidget *spw, unsigned const jointype) +{ + GtkWidget *tb = NULL; + switch (jointype) { + case SP_STROKE_LINEJOIN_MITER: + tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_JOIN_MITER)); + break; + case SP_STROKE_LINEJOIN_ROUND: + tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_JOIN_ROUND)); + break; + case SP_STROKE_LINEJOIN_BEVEL: + tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_JOIN_BEVEL)); + break; + default: + break; + } + sp_stroke_style_set_join_buttons (spw, tb); +} + +static void +sp_captype_set (SPWidget *spw, unsigned const captype) +{ + GtkWidget *tb = NULL; + switch (captype) { + case SP_STROKE_LINECAP_BUTT: + tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_CAP_BUTT)); + break; + case SP_STROKE_LINECAP_ROUND: + tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_CAP_ROUND)); + break; + case SP_STROKE_LINECAP_SQUARE: + tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_CAP_SQUARE)); + break; + default: + break; + } + sp_stroke_style_set_cap_buttons (spw, tb); +} + +static void +sp_stroke_style_line_update(SPWidget *spw, Inkscape::Selection *sel) +{ + if (gtk_object_get_data(GTK_OBJECT(spw), "update")) { + return; + } + + gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(TRUE)); + + GtkWidget *sset = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), "stroke")); + GtkObject *width = GTK_OBJECT(gtk_object_get_data(GTK_OBJECT(spw), "width")); + GtkObject *ml = GTK_OBJECT(gtk_object_get_data(GTK_OBJECT(spw), "miterlimit")); + GtkWidget *us = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), "units")); + GtkWidget *dsel = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), "dash")); + + // create temporary style + SPStyle *query = sp_style_new (); + // query into it + int result_sw = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKEWIDTH); + int result_ml = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKEMITERLIMIT); + int result_cap = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKECAP); + int result_join = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKEJOIN); + + if (result_sw == QUERY_STYLE_NOTHING) { + /* No objects stroked, set insensitive */ + gtk_widget_set_sensitive(sset, FALSE); + + gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(FALSE)); + return; + } else { + gtk_widget_set_sensitive(sset, TRUE); + + SPUnit const *unit = sp_unit_selector_get_unit(SP_UNIT_SELECTOR(us)); + + if (result_sw == QUERY_STYLE_MULTIPLE_AVERAGED) { + sp_unit_selector_set_unit(SP_UNIT_SELECTOR(us), &sp_unit_get_by_id(SP_UNIT_PERCENT)); + } else { + // same width, or only one object; no sense to keep percent, switch to absolute + if (unit->base != SP_UNIT_ABSOLUTE && unit->base != SP_UNIT_DEVICE) { + sp_unit_selector_set_unit(SP_UNIT_SELECTOR(us), SP_DT_NAMEDVIEW(SP_ACTIVE_DESKTOP)->doc_units); + } + } + + unit = sp_unit_selector_get_unit (SP_UNIT_SELECTOR (us)); + + if (unit->base == SP_UNIT_ABSOLUTE || unit->base == SP_UNIT_DEVICE) { + double avgwidth = sp_pixels_get_units (query->stroke_width.computed, *unit); + gtk_adjustment_set_value(GTK_ADJUSTMENT(width), avgwidth); + } else { + gtk_adjustment_set_value(GTK_ADJUSTMENT(width), 100); + } + } + + if (result_ml != QUERY_STYLE_NOTHING) + gtk_adjustment_set_value(GTK_ADJUSTMENT(ml), query->stroke_miterlimit.value); // TODO: reflect averagedness? + + if (result_join != QUERY_STYLE_MULTIPLE_DIFFERENT) { + sp_jointype_set (spw, query->stroke_linejoin.value); + } else { + sp_stroke_style_set_join_buttons(spw, NULL); + } + + if (result_cap != QUERY_STYLE_MULTIPLE_DIFFERENT) { + sp_captype_set (spw, query->stroke_linecap.value); + } else { + sp_stroke_style_set_cap_buttons(spw, NULL); + } + + g_free (query); + + GSList const *objects = sel->itemList(); + SPObject * const object = SP_OBJECT(objects->data); + SPStyle * const style = SP_OBJECT_STYLE(object); + + /* Markers */ + sp_stroke_style_update_marker_menus(spw, objects); // FIXME: make this desktop query too + + /* Dash */ + sp_dash_selector_set_from_style (dsel, style); // FIXME: make this desktop query too + + gtk_widget_set_sensitive(sset, TRUE); + + gtk_object_set_data(GTK_OBJECT(spw), "update", + GINT_TO_POINTER(FALSE)); +} + +static void +sp_stroke_style_set_scaled_dash(SPCSSAttr *css, + int ndash, double *dash, double offset, + double scale) +{ + if (ndash > 0) { + Inkscape::CSSOStringStream osarray; + for (int i = 0; i < ndash; i++) { + osarray << dash[i] * scale; + if (i < (ndash - 1)) { + osarray << ","; + } + } + sp_repr_css_set_property(css, "stroke-dasharray", osarray.str().c_str()); + + Inkscape::CSSOStringStream osoffset; + osoffset << offset * scale; + sp_repr_css_set_property(css, "stroke-dashoffset", osoffset.str().c_str()); + } else { + sp_repr_css_set_property(css, "stroke-dasharray", "none"); + sp_repr_css_set_property(css, "stroke-dashoffset", NULL); + } +} + +static void +sp_stroke_style_scale_line(SPWidget *spw) +{ + if (gtk_object_get_data(GTK_OBJECT(spw), "update")) { + return; + } + + gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(TRUE)); + + GtkAdjustment *wadj = GTK_ADJUSTMENT(gtk_object_get_data(GTK_OBJECT(spw), "width")); + SPUnitSelector *us = SP_UNIT_SELECTOR(gtk_object_get_data(GTK_OBJECT(spw), "units")); + SPDashSelector *dsel = SP_DASH_SELECTOR(gtk_object_get_data(GTK_OBJECT(spw), "dash")); + GtkAdjustment *ml = GTK_ADJUSTMENT(gtk_object_get_data(GTK_OBJECT(spw), "miterlimit")); + + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + SPDocument *document = SP_DT_DOCUMENT (desktop); + Inkscape::Selection *selection = SP_DT_SELECTION (desktop); + + GSList const *items = selection->itemList(); + + /* TODO: Create some standardized method */ + SPCSSAttr *css = sp_repr_css_attr_new(); + + if (items) { + + double width_typed = wadj->value; + double const miterlimit = ml->value; + + SPUnit const *const unit = sp_unit_selector_get_unit(SP_UNIT_SELECTOR(us)); + + double *dash, offset; + int ndash; + sp_dash_selector_get_dash(dsel, &ndash, &dash, &offset); + + for (GSList const *i = items; i != NULL; i = i->next) { + /* Set stroke width */ + double width; + if (unit->base == SP_UNIT_ABSOLUTE || unit->base == SP_UNIT_DEVICE) { + width = sp_units_get_pixels (width_typed, *unit); + } else { // percentage + gdouble old_w = SP_OBJECT_STYLE (i->data)->stroke_width.computed; + width = old_w * width_typed / 100; + } + + { + Inkscape::CSSOStringStream os_width; + os_width << width; + sp_repr_css_set_property(css, "stroke-width", os_width.str().c_str()); + } + + { + Inkscape::CSSOStringStream os_ml; + os_ml << miterlimit; + sp_repr_css_set_property(css, "stroke-miterlimit", os_ml.str().c_str()); + } + + /* Set dash */ + sp_stroke_style_set_scaled_dash(css, ndash, dash, offset, width); + + sp_desktop_apply_css_recursive (SP_OBJECT(i->data), css, true); + } + + g_free(dash); + + if (unit->base != SP_UNIT_ABSOLUTE && unit->base != SP_UNIT_DEVICE) { + // reset to 100 percent + gtk_adjustment_set_value (wadj, 100.0); + } + + } + + // we have already changed the items, so set style without changing selection + // FIXME: move the above stroke-setting stuff, including percentages, to desktop-style + sp_desktop_set_style (desktop, css, false); + + sp_repr_css_attr_unref(css); + + sp_document_done(document); + + gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(FALSE)); +} + + + +static void +sp_stroke_style_width_changed(GtkAdjustment *adj, SPWidget *spw) +{ + if (gtk_object_get_data(GTK_OBJECT(spw), "update")) { + return; + } + + sp_stroke_style_scale_line(spw); +} + +static void +sp_stroke_style_miterlimit_changed(GtkAdjustment *adj, SPWidget *spw) +{ + if (gtk_object_get_data(GTK_OBJECT(spw), "update")) { + return; + } + + sp_stroke_style_scale_line(spw); +} + +static void +sp_stroke_style_line_dash_changed(SPDashSelector *dsel, SPWidget *spw) +{ + if (gtk_object_get_data(GTK_OBJECT(spw), "update")) { + return; + } + + sp_stroke_style_scale_line(spw); +} + + + +/** + * \brief This routine handles toggle events for buttons in the stroke style + * dialog. + * When activated, this routine gets the data for the various widgets, and then + * calls the respective routines to update css properties, etc. + * + */ +static void +sp_stroke_style_any_toggled(GtkToggleButton *tb, SPWidget *spw) +{ + if (gtk_object_get_data(GTK_OBJECT(spw), "update")) { + return; + } + + if (gtk_toggle_button_get_active(tb)) { + + gchar const *join + = static_cast(gtk_object_get_data(GTK_OBJECT(tb), "join")); + gchar const *cap + = static_cast(gtk_object_get_data(GTK_OBJECT(tb), "cap")); + + if (join) { + GtkWidget *ml = GTK_WIDGET(g_object_get_data(G_OBJECT(spw), "miterlimit_sb")); + gtk_widget_set_sensitive (ml, !strcmp(join, "miter")); + } + + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + + /* TODO: Create some standardized method */ + SPCSSAttr *css = sp_repr_css_attr_new(); + + if (join) { + sp_repr_css_set_property(css, "stroke-linejoin", join); + + sp_desktop_set_style (desktop, css); + + sp_stroke_style_set_join_buttons(spw, GTK_WIDGET(tb)); + } else if (cap) { + sp_repr_css_set_property(css, "stroke-linecap", cap); + + sp_desktop_set_style (desktop, css); + + sp_stroke_style_set_cap_buttons(spw, GTK_WIDGET(tb)); + } + + sp_repr_css_attr_unref(css); + + sp_document_done(SP_DT_DOCUMENT(desktop)); + } +} + + +static void +sp_stroke_style_set_join_buttons(SPWidget *spw, GtkWidget *active) +{ + GtkWidget *tb; + + tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw), + INKSCAPE_STOCK_JOIN_MITER) ); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb)); + + GtkWidget *ml = GTK_WIDGET(g_object_get_data(G_OBJECT(spw), "miterlimit_sb")); + gtk_widget_set_sensitive(ml, (active == tb)); + + tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw), + INKSCAPE_STOCK_JOIN_ROUND) ); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb)); + tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw), + INKSCAPE_STOCK_JOIN_BEVEL) ); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb)); +} + + + +static void +sp_stroke_style_set_cap_buttons(SPWidget *spw, GtkWidget *active) +{ + GtkWidget *tb; + + tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw), + INKSCAPE_STOCK_CAP_BUTT)); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb)); + tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw), + INKSCAPE_STOCK_CAP_ROUND) ); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb)); + tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw), + INKSCAPE_STOCK_CAP_SQUARE) ); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb)); +} + +static void +ink_marker_menu_set_current(SPObject *marker, GtkOptionMenu *mnu) +{ + gtk_object_set_data(GTK_OBJECT(mnu), "update", GINT_TO_POINTER(TRUE)); + + GtkMenu *m = GTK_MENU(gtk_option_menu_get_menu(mnu)); + if (marker != NULL) { + bool mark_is_stock = false; + if (SP_OBJECT_REPR(marker)->attribute("inkscape:stockid")) + mark_is_stock = true; + + gchar *markname; + if (mark_is_stock) + markname = g_strdup(SP_OBJECT_REPR(marker)->attribute("inkscape:stockid")); + else + markname = g_strdup(SP_OBJECT_REPR(marker)->attribute("id")); + + int markpos = 0; + GList *kids = GTK_MENU_SHELL(m)->children; + int i = 0; + for (; kids != NULL; kids = kids->next) { + gchar *mark = (gchar *) g_object_get_data(G_OBJECT(kids->data), "marker"); + if ( mark && strcmp(mark, markname) == 0 ) { + if ( mark_is_stock && !strcmp((gchar *) g_object_get_data(G_OBJECT(kids->data), "stockid"), "true")) + markpos = i; + if ( !mark_is_stock && !strcmp((gchar *) g_object_get_data(G_OBJECT(kids->data), "stockid"), "false")) + markpos = i; + } + i++; + } + gtk_option_menu_set_history(GTK_OPTION_MENU(mnu), markpos); + + g_free (markname); + } + else { + gtk_option_menu_set_history(GTK_OPTION_MENU(mnu), 0); + } + gtk_object_set_data(GTK_OBJECT(mnu), "update", GINT_TO_POINTER(FALSE)); +} + +static void +sp_stroke_style_update_marker_menus( SPWidget *spw, + GSList const *objects) +{ + struct { char const *key; int loc; } const keyloc[] = { + { "start_mark_menu", SP_MARKER_LOC_START }, + { "mid_mark_menu", SP_MARKER_LOC_MID }, + { "end_mark_menu", SP_MARKER_LOC_END } + }; + + bool all_texts = true; + for (GSList *i = (GSList *) objects; i != NULL; i = i->next) { + if (!SP_IS_TEXT (i->data)) { + all_texts = false; + } + } + + for (unsigned i = 0; i < G_N_ELEMENTS(keyloc); ++i) { + GtkOptionMenu *mnu = (GtkOptionMenu *) g_object_get_data(G_OBJECT(spw), keyloc[i].key); + if (all_texts) { + // Per SVG spec, text objects cannot have markers; disable menus if only texts are selected + gtk_widget_set_sensitive (GTK_WIDGET(mnu), FALSE); + } else { + gtk_widget_set_sensitive (GTK_WIDGET(mnu), TRUE); + } + } + + // We show markers of the first object in the list only + // FIXME: use the first in the list that has the marker of each type, if any + SPObject *object = SP_OBJECT(objects->data); + + for (unsigned i = 0; i < G_N_ELEMENTS(keyloc); ++i) { + // For all three marker types, + + // find the corresponding menu + GtkOptionMenu *mnu = (GtkOptionMenu *) g_object_get_data(G_OBJECT(spw), keyloc[i].key); + + // Quit if we're in update state + if (gtk_object_get_data(GTK_OBJECT(mnu), "update")) { + return; + } + + if (object->style->marker[keyloc[i].loc].value != NULL && !all_texts) { + // If the object has this type of markers, + + // Extract the name of the marker that the object uses + SPObject *marker = ink_extract_marker_name(object->style->marker[keyloc[i].loc].value); + // Scroll the menu to that marker + ink_marker_menu_set_current (marker, mnu); + + } else { + gtk_option_menu_set_history(GTK_OPTION_MENU(mnu), 0); + } + } +} + + +/** Extract the actual name of the link + * e.g. get mTriangle from url(#mTriangle). + * \return Buffer containing the actual name, allocated from GLib; + * the caller should free the buffer when they no longer need it. + */ +static SPObject* +ink_extract_marker_name(gchar const *n) +{ + gchar const *p = n; + while (*p != '\0' && *p != '#') { + p++; + } + + if (*p == '\0' || p[1] == '\0') { + return NULL; + } + + p++; + int c = 0; + while (p[c] != '\0' && p[c] != ')') { + c++; + } + + if (p[c] == '\0') { + return NULL; + } + + gchar* b = g_strdup(p); + b[c] = '\0'; + + + SPDesktop *desktop = inkscape_active_desktop(); + SPDocument *doc = SP_DT_DOCUMENT(desktop); + SPObject *marker = doc->getObjectById(b); + return marker; +} + + +/* + 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=c++:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/dialogs/stroke-style.h b/src/dialogs/stroke-style.h new file mode 100644 index 000000000..209407b3f --- /dev/null +++ b/src/dialogs/stroke-style.h @@ -0,0 +1,35 @@ +#ifndef __SP_STROKE_STYLE_H__ +#define __SP_STROKE_STYLE_H__ + +/** + * \brief Stroke style dialog + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001 Ximian, Inc. + * + */ + +#include + +#include + +#include "forward.h" +#include "display/canvas-bpath.h" + +GtkWidget *sp_stroke_style_paint_widget_new (void); +GtkWidget *sp_stroke_style_line_widget_new (void); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/dialogs/swatches.cpp b/src/dialogs/swatches.cpp new file mode 100644 index 000000000..739cda3b2 --- /dev/null +++ b/src/dialogs/swatches.cpp @@ -0,0 +1,516 @@ +/* + * A simple panel for color swatches + * + * Authors: + * Jon A. Cruz + * + * Copyright (C) 2005 Jon A. Cruz + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ +#ifdef HAVE_CONFIG_H +# include +#endif + +#include //for GTK_RESPONSE* types +#include + +#include +#include "inkscape.h" +#include "document.h" +#include "desktop-handles.h" +#include "extension/db.h" +#include "inkscape.h" +#include "svg/svg.h" +#include "desktop-style.h" +#include "io/sys.h" +#include "path-prefix.h" +#include "swatches.h" + +#include "eek-preview.h" + +namespace Inkscape { +namespace UI { +namespace Dialogs { + +SwatchesPanel* SwatchesPanel::instance = 0; + + +ColorItem::ColorItem( unsigned int r, unsigned int g, unsigned int b, Glib::ustring& name ) : + _r(r), + _g(g), + _b(b), + _name(name) +{ +} + +ColorItem::~ColorItem() +{ +} + +ColorItem::ColorItem(ColorItem const &other) : + Inkscape::UI::Previewable() +{ + if ( this != &other ) { + *this = other; + } +} + +ColorItem &ColorItem::operator=(ColorItem const &other) +{ + if ( this != &other ) { + _r = other._r; + _g = other._g; + _b = other._b; + _name = other._name; + } + return *this; +} + +typedef enum { + XCOLOR_DATA = 0, + TEXT_DATA +} colorFlavorType; + +static const GtkTargetEntry color_entries[] = { + {"application/x-color", 0, XCOLOR_DATA}, + {"text/plain", 0, TEXT_DATA}, +}; + +static void dragGetColorData( GtkWidget *widget, + GdkDragContext *drag_context, + GtkSelectionData *data, + guint info, + guint time, + gpointer user_data) +{ + static GdkAtom typeXColor = gdk_atom_intern("application/x-color", FALSE); + static GdkAtom typeText = gdk_atom_intern("text/plain", FALSE); + + ColorItem* item = reinterpret_cast(user_data); + if ( info == 1 ) { + gchar* tmp = g_strdup_printf("#%02x%02x%02x", item->_r, item->_g, item->_b); + + gtk_selection_data_set( data, + typeText, + 8, // format + (guchar*)tmp, + strlen((const char*)tmp) + 1); + g_free(tmp); + tmp = 0; + } else { + guchar tmp[8]; + tmp[0] = item->_r; + tmp[1] = item->_r; + tmp[2] = item->_g; + tmp[3] = item->_g; + tmp[4] = item->_b; + tmp[5] = item->_b; + tmp[6] = 0x0ff; + tmp[7] = 0x0ff; + gtk_selection_data_set( data, + typeXColor, + 8, // format + tmp, + (3+1) * 2); + } +} + +//"drag-drop" +gboolean dragDropColorData( GtkWidget *widget, + GdkDragContext *drag_context, + gint x, + gint y, + guint time, + gpointer user_data) +{ +// TODO finish + return TRUE; +} + +static void bouncy( GtkWidget* widget, gpointer callback_data ) { + ColorItem* item = reinterpret_cast(callback_data); + if ( item ) { + item->buttonClicked(false); + } +} + +static void bouncy2( GtkWidget* widget, gint arg1, gpointer callback_data ) { + ColorItem* item = reinterpret_cast(callback_data); + if ( item ) { + item->buttonClicked(true); + } +} + +Gtk::Widget* ColorItem::getPreview(PreviewStyle style, ViewType view, Gtk::BuiltinIconSize size) +{ + Gtk::Widget* widget = 0; + if ( style == PREVIEW_STYLE_BLURB ) { + Gtk::Label *lbl = new Gtk::Label(_name); + lbl->set_alignment(Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER); + widget = lbl; + } else { + Glib::ustring blank(" "); + if ( size == Gtk::ICON_SIZE_MENU ) { + blank = " "; + } + + GtkWidget* eekWidget = eek_preview_new(); + EekPreview * preview = EEK_PREVIEW(eekWidget); + Gtk::Widget* newBlot = Glib::wrap(eekWidget); + + eek_preview_set_color( preview, (_r << 8)|_r, (_g << 8)|_g, (_b << 8)|_b); + + eek_preview_set_details( preview, (::PreviewStyle)style, (::ViewType)view, (::GtkIconSize)size ); + + GValue val = {0, {{0}, {0}}}; + g_value_init( &val, G_TYPE_BOOLEAN ); + g_value_set_boolean( &val, FALSE ); + g_object_set_property( G_OBJECT(preview), "focus-on-click", &val ); + +/* + Gtk::Button *btn = new Gtk::Button(blank); + Gdk::Color color; + color.set_rgb((_r << 8)|_r, (_g << 8)|_g, (_b << 8)|_b); + btn->modify_bg(Gtk::STATE_NORMAL, color); + btn->modify_bg(Gtk::STATE_ACTIVE, color); + btn->modify_bg(Gtk::STATE_PRELIGHT, color); + btn->modify_bg(Gtk::STATE_SELECTED, color); + + Gtk::Widget* newBlot = btn; +*/ + + tips.set_tip((*newBlot), _name); + +/* + newBlot->signal_clicked().connect( sigc::mem_fun(*this, &ColorItem::buttonClicked) ); + + sigc::signal type_signal_something; +*/ + g_signal_connect( G_OBJECT(newBlot->gobj()), + "clicked", + G_CALLBACK(bouncy), + this); + + g_signal_connect( G_OBJECT(newBlot->gobj()), + "alt-clicked", + G_CALLBACK(bouncy2), + this); + + gtk_drag_source_set( GTK_WIDGET(newBlot->gobj()), + GDK_BUTTON1_MASK, + color_entries, + G_N_ELEMENTS(color_entries), + GdkDragAction(GDK_ACTION_MOVE | GDK_ACTION_COPY) ); + + g_signal_connect( G_OBJECT(newBlot->gobj()), + "drag-data-get", + G_CALLBACK(dragGetColorData), + this); + + g_signal_connect( G_OBJECT(newBlot->gobj()), + "drag-drop", + G_CALLBACK(dragDropColorData), + this); + + widget = newBlot; + } + + return widget; +} + +void ColorItem::buttonClicked(bool secondary) +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop) { + char const * attrName = secondary ? "stroke" : "fill"; + guint32 rgba = (_r << 24) | (_g << 16) | (_b << 8) | 0xff; + gchar c[64]; + sp_svg_write_color(c, 64, rgba); + + SPCSSAttr *css = sp_repr_css_attr_new(); + sp_repr_css_set_property( css, attrName, c ); + sp_desktop_set_style(desktop, css); + + sp_repr_css_attr_unref(css); + sp_document_done (SP_DT_DOCUMENT (desktop)); + } +} + + + + +static char* trim( char* str ) { + char* ret = str; + while ( *str && (*str == ' ' || *str == '\t') ) { + str++; + } + ret = str; + while ( *str ) { + str++; + } + str--; + while ( str > ret && ( *str == ' ' || *str == '\t' ) || *str == '\r' || *str == '\n' ) { + *str-- = 0; + } + return ret; +} + +void skipWhitespace( char*& str ) { + while ( *str == ' ' || *str == '\t' ) { + str++; + } +} + +bool parseNum( char*& str, int& val ) { + val = 0; + while ( '0' <= *str && *str <= '9' ) { + val = val * 10 + (*str - '0'); + str++; + } + bool retval = !(*str == 0 || *str == ' ' || *str == '\t' || *str == '\r' || *str == '\n'); + return retval; +} + + +class JustForNow +{ +public: + Glib::ustring _name; + std::vector _colors; +}; + +static std::vector possible; + +static void loadPaletteFile( gchar const *filename ) +{ + char block[1024]; + FILE *f = Inkscape::IO::fopen_utf8name( filename, "r" ); + if ( f ) { + char* result = fgets( block, sizeof(block), f ); + if ( result ) { + if ( strncmp( "GIMP Palette", block, 12 ) == 0 ) { + bool inHeader = true; + bool hasErr = false; + + JustForNow *onceMore = new JustForNow(); + + do { + result = fgets( block, sizeof(block), f ); + block[sizeof(block) - 1] = 0; + if ( result ) { + if ( block[0] == '#' ) { + // ignore comment + } else { + char *ptr = block; + // very simple check for header versus entry + while ( *ptr == ' ' || *ptr == '\t' ) { + ptr++; + } + if ( *ptr == 0 ) { + // blank line. skip it. + } else if ( '0' <= *ptr && *ptr <= '9' ) { + // should be an entry link + inHeader = false; + ptr = block; + Glib::ustring name(""); + int r = 0; + int g = 0; + int b = 0; + skipWhitespace(ptr); + if ( *ptr ) { + hasErr = parseNum(ptr, r); + if ( !hasErr ) { + skipWhitespace(ptr); + hasErr = parseNum(ptr, g); + } + if ( !hasErr ) { + skipWhitespace(ptr); + hasErr = parseNum(ptr, b); + } + if ( !hasErr && *ptr ) { + char* n = trim(ptr); + if (n != NULL) { + name = n; + } + } + if ( !hasErr ) { + // Add the entry now + Glib::ustring nameStr(name); + ColorItem* item = new ColorItem( r, g, b, nameStr ); + onceMore->_colors.push_back(item); + } + } else { + hasErr = true; + } + } else { + if ( !inHeader ) { + // Hmmm... probably bad. Not quite the format we want? + hasErr = true; + } else { + char* sep = strchr(result, ':'); + if ( sep ) { + *sep = 0; + char* val = trim(sep + 1); + char* name = trim(result); + if ( *name ) { + if ( strcmp( "Name", name ) == 0 ) { + onceMore->_name = val; + } + } else { + // error + hasErr = true; + } + } else { + // error + hasErr = true; + } + } + } + } + } + } while ( result && !hasErr ); + if ( !hasErr ) { + possible.push_back(onceMore); + } else { + delete onceMore; + } + } + } + + fclose(f); + } +} + +static void loadEmUp() +{ + static bool beenHere = false; + if ( !beenHere ) { + beenHere = true; + + std::list sources; + sources.push_back( profile_path("palettes") ); + sources.push_back( g_strdup(INKSCAPE_PALETTESDIR) ); + + // Use this loop to iterate through a list of possible document locations. + while (!sources.empty()) { + gchar *dirname = sources.front(); + + if ( Inkscape::IO::file_test( dirname, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) ) ) { + GError *err = 0; + GDir *directory = g_dir_open(dirname, 0, &err); + if (!directory) { + gchar *safeDir = Inkscape::IO::sanitizeString(dirname); + g_warning(_("Palettes directory (%s) is unavailable."), safeDir); + g_free(safeDir); + } else { + gchar *filename = 0; + while ((filename = (gchar *)g_dir_read_name(directory)) != NULL) { + gchar* full = g_build_filename(dirname, filename, NULL); + if ( !Inkscape::IO::file_test( full, (GFileTest)(G_FILE_TEST_IS_DIR ) ) ) { + loadPaletteFile(full); + } + g_free(full); + } + g_dir_close(directory); + } + } + + // toss the dirname + g_free(dirname); + sources.pop_front(); + } + } +} + + + + + + + + + +SwatchesPanel& SwatchesPanel::getInstance() +{ + if ( !instance ) { + instance = new SwatchesPanel(); + } + + return *instance; +} + + + +/** + * Constructor + */ +SwatchesPanel::SwatchesPanel() : + Inkscape::UI::Widget::Panel ("dialogs.swatches"), + _holder(0) +{ + _holder = new PreviewHolder(); + loadEmUp(); + + if ( !possible.empty() ) { + JustForNow* first = possible.front(); + for ( std::vector::iterator it = first->_colors.begin(); it != first->_colors.end(); it++ ) { + _holder->addPreview(*it); + } + + Gtk::RadioMenuItem::Group groupOne; + int i = 0; + for ( std::vector::iterator it = possible.begin(); it != possible.end(); it++ ) { + JustForNow* curr = *it; + Gtk::RadioMenuItem* single = manage(new Gtk::RadioMenuItem(groupOne, curr->_name)); + _regItem( single, 3, i ); + i++; + } + + } + + + pack_start(*_holder, Gtk::PACK_EXPAND_WIDGET); + _setTargetFillable(_holder); + + show_all_children(); + + restorePanelPrefs(); +} + +SwatchesPanel::~SwatchesPanel() +{ +} + +void SwatchesPanel::_handleAction( int setId, int itemId ) +{ + switch( setId ) { + case 3: + { + if ( itemId >= 0 && itemId < static_cast(possible.size()) ) { + _holder->clear(); + JustForNow* curr = possible[itemId]; + for ( std::vector::iterator it = curr->_colors.begin(); it != curr->_colors.end(); it++ ) { + _holder->addPreview(*it); + } + } + } + break; + } +} + +} //namespace Dialogs +} //namespace UI +} //namespace Inkscape + + +/* + 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 : diff --git a/src/dialogs/swatches.h b/src/dialogs/swatches.h new file mode 100644 index 000000000..ca17cd066 --- /dev/null +++ b/src/dialogs/swatches.h @@ -0,0 +1,93 @@ + +#ifndef SEEN_SWATCHES_H +#define SEEN_SWATCHES_H +/* + * A simple dialog for previewing icon representation. + * + * Authors: + * Jon A. Cruz + * + * Copyright (C) 2005 Jon A. Cruz + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include + +#include "ui/widget/panel.h" +#include "ui/previewholder.h" + +namespace Inkscape { +namespace UI { +namespace Dialogs { + + + +/** + * The color swatch you see on screen as a clickable box. + */ +class ColorItem : public Inkscape::UI::Previewable +{ +public: + ColorItem( unsigned int r, unsigned int g, unsigned int b, + Glib::ustring& name ); + virtual ~ColorItem(); + ColorItem(ColorItem const &other); + virtual ColorItem &operator=(ColorItem const &other); + virtual Gtk::Widget* getPreview(PreviewStyle style, + ViewType view, + Gtk::BuiltinIconSize size); + void buttonClicked(bool secondary = false); + unsigned int _r; + unsigned int _g; + unsigned int _b; + Glib::ustring _name; + +private: + Gtk::Tooltips tips; +}; + + + +/** + * A panel that displays color swatches. + */ +class SwatchesPanel : public Inkscape::UI::Widget::Panel +{ +public: + SwatchesPanel(); + virtual ~SwatchesPanel(); + + static SwatchesPanel& getInstance(); + +protected: + virtual void _handleAction( int setId, int itemId ); + +private: + SwatchesPanel(SwatchesPanel const &); // no copy + SwatchesPanel &operator=(SwatchesPanel const &); // no assign + + static SwatchesPanel* instance; + + PreviewHolder* _holder; +}; + +} //namespace Dialogs +} //namespace UI +} //namespace Inkscape + + + +#endif // SEEN_SWATCHES_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:encoding=utf-8:textwidth=99 : diff --git a/src/dialogs/text-edit.cpp b/src/dialogs/text-edit.cpp new file mode 100644 index 000000000..b4dcd4bce --- /dev/null +++ b/src/dialogs/text-edit.cpp @@ -0,0 +1,915 @@ +#define __SP_TEXT_EDIT_C__ + +/** + * \brief Text editing dialog + * + * Author: + * Lauris Kaplinski + * bulia byak + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include + +#ifdef WITH_GTKSPELL +extern "C" { +# include +} +#endif + +#include "macros.h" +#include +#include "helper/window.h" +#include "../widgets/font-selector.h" +#include "../inkscape.h" +#include "../document.h" +#include "../desktop-style.h" +#include "../desktop-handles.h" +#include "../selection.h" +#include "../style.h" +#include "../sp-text.h" +#include "../sp-flowtext.h" +#include "../text-editing.h" +#include "../inkscape-stock.h" +#include + +#include "dialog-events.h" +#include "../prefs-utils.h" +#include "../verbs.h" +#include "../interface.h" +#include "svg/css-ostringstream.h" +#include "widgets/icon.h" +#include + + +#define VB_MARGIN 4 + +static void sp_text_edit_dialog_selection_modified (Inkscape::Application *inkscape, Inkscape::Selection *sel, guint flags, GtkWidget *dlg); +static void sp_text_edit_dialog_selection_changed (Inkscape::Application *inkscape, Inkscape::Selection *sel, GtkWidget *dlg); +static void sp_text_edit_dialog_subselection_changed ( Inkscape::Application *inkscape, SPDesktop *desktop, GtkWidget *dlg); + +static void sp_text_edit_dialog_set_default (GtkButton *button, GtkWidget *dlg); +static void sp_text_edit_dialog_apply (GtkButton *button, GtkWidget *dlg); +static void sp_text_edit_dialog_close (GtkButton *button, GtkWidget *dlg); + +static void sp_text_edit_dialog_read_selection (GtkWidget *dlg, gboolean style, gboolean content); + +static void sp_text_edit_dialog_text_changed (GtkTextBuffer *tb, GtkWidget *dlg); +static void sp_text_edit_dialog_font_changed (SPFontSelector *fontsel, font_instance *font, GtkWidget *dlg); +static void sp_text_edit_dialog_any_toggled (GtkToggleButton *tb, GtkWidget *dlg); +static void sp_text_edit_dialog_line_spacing_changed (GtkEditable *editable, GtkWidget *dlg); + +static SPItem *sp_ted_get_selected_text_item (void); +static unsigned sp_ted_get_selected_text_count (void); + + +static const gchar *spacings[] = {"50%", "80%", "90%", "100%", "110%", "120%", "130%", "140%", "150%", "200%", "300%", NULL}; + +static GtkWidget *dlg = NULL; +static win_data wd; +// impossible original values to make sure they are read from prefs +static gint x = -1000, y = -1000, w = 0, h = 0; +static gchar const *prefs_path = "dialogs.textandfont"; + + + + +static void +sp_text_edit_dialog_destroy (GtkObject *object, gpointer data) +{ + sp_signal_disconnect_by_data (INKSCAPE, dlg); + wd.win = dlg = NULL; + wd.stop = 0; +} + + + +static gboolean +sp_text_edit_dialog_delete (GtkObject *object, GdkEvent *event, gpointer data) +{ + gtk_window_get_position ((GtkWindow *) dlg, &x, &y); + gtk_window_get_size ((GtkWindow *) dlg, &w, &h); + + prefs_set_int_attribute (prefs_path, "x", x); + prefs_set_int_attribute (prefs_path, "y", y); + prefs_set_int_attribute (prefs_path, "w", w); + prefs_set_int_attribute (prefs_path, "h", h); + + return FALSE; // which means, go ahead and destroy it +} + + +/** + These callbacks set the eatkeys flag when the text editor is entered and cancel it when it's left. + This flag is used to prevent passing keys from the dialog to canvas, so that the text editor + can handle keys like Esc and Ctrl+Z itself. + */ +gboolean +text_view_focus_in (GtkWidget *w, GdkEventKey *event, gpointer data) +{ + GObject *dlg = (GObject *) data; + g_object_set_data (dlg, "eatkeys", GINT_TO_POINTER (TRUE)); + return FALSE; +} + +gboolean +text_view_focus_out (GtkWidget *w, GdkEventKey *event, gpointer data) +{ + GObject *dlg = (GObject *) data; + g_object_set_data (dlg, "eatkeys", GINT_TO_POINTER (FALSE)); + return FALSE; +} + + +void +sp_text_edit_dialog (void) +{ + + if (!dlg) { + + gchar title[500]; + sp_ui_dialog_title_string (Inkscape::Verb::get(SP_VERB_DIALOG_TEXT), title); + + dlg = sp_window_new (title, TRUE); + if (x == -1000 || y == -1000) { + x = prefs_get_int_attribute (prefs_path, "x", 0); + y = prefs_get_int_attribute (prefs_path, "y", 0); + } + + if (w ==0 || h == 0) { + w = prefs_get_int_attribute (prefs_path, "w", 0); + h = prefs_get_int_attribute (prefs_path, "h", 0); + } + + if (x != 0 || y != 0) { + gtk_window_move ((GtkWindow *) dlg, x, y); + } else { + gtk_window_set_position(GTK_WINDOW(dlg), GTK_WIN_POS_CENTER); + } + + if (w && h) + gtk_window_resize ((GtkWindow *) dlg, w, h); + + sp_transientize (dlg); + wd.win = dlg; + wd.stop = 0; + g_signal_connect ( G_OBJECT (INKSCAPE), "activate_desktop", G_CALLBACK (sp_transientize_callback), &wd ); + + gtk_signal_connect ( GTK_OBJECT (dlg), "event", GTK_SIGNAL_FUNC (sp_dialog_event_handler), dlg ); + + gtk_signal_connect ( GTK_OBJECT (dlg), "destroy", G_CALLBACK (sp_text_edit_dialog_destroy), dlg ); + gtk_signal_connect ( GTK_OBJECT (dlg), "delete_event", G_CALLBACK (sp_text_edit_dialog_delete), dlg ); + g_signal_connect ( G_OBJECT (INKSCAPE), "shut_down", G_CALLBACK (sp_text_edit_dialog_delete), dlg ); + + g_signal_connect ( G_OBJECT (INKSCAPE), "dialogs_hide", G_CALLBACK (sp_dialog_hide), dlg ); + g_signal_connect ( G_OBJECT (INKSCAPE), "dialogs_unhide", G_CALLBACK (sp_dialog_unhide), dlg ); + + gtk_window_set_policy (GTK_WINDOW (dlg), TRUE, TRUE, FALSE); + + GtkTooltips *tt = gtk_tooltips_new(); + + // box containing the notebook and the bottom buttons + GtkWidget *mainvb = gtk_vbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (dlg), mainvb); + + // notebook + GtkWidget *nb = gtk_notebook_new (); + gtk_box_pack_start (GTK_BOX (mainvb), nb, TRUE, TRUE, 0); + g_object_set_data (G_OBJECT (dlg), "notebook", nb); + + + + // Font tab + { + GtkWidget *l = gtk_label_new (_("Font")); + GtkWidget *vb = gtk_vbox_new (FALSE, VB_MARGIN); + gtk_container_set_border_width (GTK_CONTAINER (vb), VB_MARGIN); + gtk_notebook_append_page (GTK_NOTEBOOK (nb), vb, l); + + /* HBox containing font selection and layout */ + GtkWidget *hb = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (vb), hb, TRUE, TRUE, 0); + + // font and style selector + GtkWidget *fontsel = sp_font_selector_new (); + g_signal_connect ( G_OBJECT (fontsel), "font_set", G_CALLBACK (sp_text_edit_dialog_font_changed), dlg ); + gtk_box_pack_start (GTK_BOX (hb), fontsel, TRUE, TRUE, 0); + g_object_set_data (G_OBJECT (dlg), "fontsel", fontsel); + + // Layout + { + GtkWidget *f = gtk_frame_new (_("Layout")); + gtk_box_pack_start (GTK_BOX (hb), f, FALSE, FALSE, 4); + GtkWidget *l_vb = gtk_vbox_new (FALSE, VB_MARGIN); + gtk_container_add (GTK_CONTAINER (f), l_vb); + + { + GtkWidget *row = gtk_hbox_new (FALSE, VB_MARGIN); + GtkWidget *group; + + // align left + { + GtkWidget *px = gtk_image_new_from_stock ( GTK_STOCK_JUSTIFY_LEFT, GTK_ICON_SIZE_LARGE_TOOLBAR ); + GtkWidget *b = group = gtk_radio_button_new (NULL); + gtk_tooltips_set_tip (tt, b, _("Align lines left"), NULL); + gtk_button_set_relief (GTK_BUTTON (b), GTK_RELIEF_NONE); + g_signal_connect ( G_OBJECT (b), "toggled", G_CALLBACK (sp_text_edit_dialog_any_toggled), dlg); + gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (b), FALSE ); + gtk_container_add (GTK_CONTAINER (b), px); + gtk_box_pack_start (GTK_BOX (row), b, FALSE, FALSE, 0); + g_object_set_data (G_OBJECT (dlg), "text_anchor_start", b); + } + + // align center + { + GtkWidget *px = gtk_image_new_from_stock ( GTK_STOCK_JUSTIFY_CENTER, GTK_ICON_SIZE_LARGE_TOOLBAR ); + GtkWidget *b = gtk_radio_button_new (gtk_radio_button_group (GTK_RADIO_BUTTON (group))); + /* TRANSLATORS: `Center' here is a verb. */ + gtk_tooltips_set_tip (tt, b, _("Center lines"), NULL); + gtk_button_set_relief (GTK_BUTTON (b), GTK_RELIEF_NONE); + g_signal_connect ( G_OBJECT (b), "toggled", G_CALLBACK (sp_text_edit_dialog_any_toggled), dlg ); + gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (b), FALSE); + gtk_container_add (GTK_CONTAINER (b), px); + gtk_box_pack_start (GTK_BOX (row), b, FALSE, FALSE, 0); + g_object_set_data (G_OBJECT (dlg), "text_anchor_middle", b); + } + + // align right + { + GtkWidget *px = gtk_image_new_from_stock ( GTK_STOCK_JUSTIFY_RIGHT, GTK_ICON_SIZE_LARGE_TOOLBAR ); + GtkWidget *b = gtk_radio_button_new (gtk_radio_button_group (GTK_RADIO_BUTTON (group))); + gtk_tooltips_set_tip (tt, b, _("Align lines right"), NULL); + gtk_button_set_relief (GTK_BUTTON (b), GTK_RELIEF_NONE); + g_signal_connect ( G_OBJECT (b), "toggled", G_CALLBACK (sp_text_edit_dialog_any_toggled), dlg ); + gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (b), FALSE); + gtk_container_add (GTK_CONTAINER (b), px); + gtk_box_pack_start (GTK_BOX (row), b, FALSE, FALSE, 0); + g_object_set_data (G_OBJECT (dlg), "text_anchor_end", b); + } + + gtk_box_pack_start (GTK_BOX (l_vb), row, FALSE, FALSE, 0); + } + + + { + GtkWidget *row = gtk_hbox_new (FALSE, VB_MARGIN); + GtkWidget *group; + + // horizontal + { + GtkWidget *px = sp_icon_new( GTK_ICON_SIZE_LARGE_TOOLBAR, + INKSCAPE_STOCK_WRITING_MODE_LR ); + GtkWidget *b = group = gtk_radio_button_new (NULL); + gtk_tooltips_set_tip (tt, b, _("Horizontal text"), NULL); + gtk_button_set_relief (GTK_BUTTON (b), GTK_RELIEF_NONE); + g_signal_connect ( G_OBJECT (b), "toggled", G_CALLBACK (sp_text_edit_dialog_any_toggled), dlg ); + gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (b), FALSE); + gtk_container_add (GTK_CONTAINER (b), px); + gtk_box_pack_start (GTK_BOX (row), b, FALSE, FALSE, 0); + g_object_set_data (G_OBJECT (dlg), INKSCAPE_STOCK_WRITING_MODE_LR, b); + } + + // vertical + { + GtkWidget *px = sp_icon_new( GTK_ICON_SIZE_LARGE_TOOLBAR, + INKSCAPE_STOCK_WRITING_MODE_TB ); + GtkWidget *b = gtk_radio_button_new (gtk_radio_button_group (GTK_RADIO_BUTTON (group))); + gtk_tooltips_set_tip (tt, b, _("Vertical text"), NULL); + gtk_button_set_relief (GTK_BUTTON (b), GTK_RELIEF_NONE); + g_signal_connect ( G_OBJECT (b), "toggled", G_CALLBACK (sp_text_edit_dialog_any_toggled), dlg ); + gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (b), FALSE); + gtk_container_add (GTK_CONTAINER (b), px); + gtk_box_pack_start (GTK_BOX (row), b, FALSE, FALSE, 0); + g_object_set_data (G_OBJECT (dlg), INKSCAPE_STOCK_WRITING_MODE_TB, b); + } + + gtk_box_pack_start (GTK_BOX (l_vb), row, FALSE, FALSE, 0); + } + + { + GtkWidget *row = gtk_hbox_new (FALSE, VB_MARGIN); + + l = gtk_label_new (_("Line spacing:")); + gtk_misc_set_alignment (GTK_MISC (l), 1.0, 0.5); + gtk_box_pack_start (GTK_BOX (row), l, FALSE, FALSE, VB_MARGIN); + + gtk_box_pack_start (GTK_BOX (l_vb), row, FALSE, FALSE, 0); + } + + { + GtkWidget *row = gtk_hbox_new (FALSE, VB_MARGIN); + + GtkWidget *c = gtk_combo_new (); + gtk_combo_set_value_in_list ((GtkCombo *) c, FALSE, FALSE); + gtk_combo_set_use_arrows ((GtkCombo *) c, TRUE); + gtk_combo_set_use_arrows_always ((GtkCombo *) c, TRUE); + gtk_widget_set_size_request (c, 90, -1); + + { /* Setup strings */ + GList *sl = NULL; + for (int i = 0; spacings[i]; i++) { + sl = g_list_prepend (sl, (void *) spacings[i]); + } + sl = g_list_reverse (sl); + gtk_combo_set_popdown_strings ((GtkCombo *) c, sl); + g_list_free (sl); + } + + g_signal_connect ( (GObject *) ((GtkCombo *) c)->entry, + "changed", + (GCallback) sp_text_edit_dialog_line_spacing_changed, + dlg ); + gtk_box_pack_start (GTK_BOX (row), c, FALSE, FALSE, VB_MARGIN); + g_object_set_data (G_OBJECT (dlg), "line_spacing", c); + + gtk_box_pack_start (GTK_BOX (l_vb), row, FALSE, FALSE, VB_MARGIN); + } + } + + /* Font preview */ + GtkWidget *preview = sp_font_preview_new (); + gtk_box_pack_start (GTK_BOX (vb), preview, TRUE, TRUE, 4); + g_object_set_data (G_OBJECT (dlg), "preview", preview); + } + + + // Text tab + { + GtkWidget *l = gtk_label_new (_("Text")); + GtkWidget *vb = gtk_vbox_new (FALSE, VB_MARGIN); + gtk_container_set_border_width (GTK_CONTAINER (vb), VB_MARGIN); + gtk_notebook_append_page (GTK_NOTEBOOK (nb), vb, l); + + GtkWidget *scroller = gtk_scrolled_window_new ( NULL, NULL ); + gtk_scrolled_window_set_policy ( GTK_SCROLLED_WINDOW (scroller), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC ); + gtk_scrolled_window_set_shadow_type ( GTK_SCROLLED_WINDOW(scroller), GTK_SHADOW_IN ); + gtk_widget_show (scroller); + + GtkTextBuffer *tb = gtk_text_buffer_new (NULL); + GtkWidget *txt = gtk_text_view_new_with_buffer (tb); + gtk_text_view_set_wrap_mode ((GtkTextView *) txt, GTK_WRAP_WORD); +#ifdef WITH_GTKSPELL + GError *error = NULL; + char *errortext = NULL; + /* todo: Use computed xml:lang attribute of relevant element, if present, to specify the + language (either as 2nd arg of gtkspell_new_attach, or with explicit + gtkspell_set_language call in; see advanced.c example in gtkspell docs). + sp_text_edit_dialog_read_selection looks like a suitable place. */ + if (gtkspell_new_attach(GTK_TEXT_VIEW(txt), NULL, &error) == NULL) { + g_print("gtkspell error: %s\n", error->message); + errortext = g_strdup_printf("GtkSpell was unable to initialize.\n" + "%s", error->message); + g_error_free(error); + } +#endif + gtk_widget_set_size_request (txt, -1, 64); + gtk_text_view_set_editable (GTK_TEXT_VIEW (txt), TRUE); + gtk_container_add (GTK_CONTAINER (scroller), txt); + gtk_box_pack_start (GTK_BOX (vb), scroller, TRUE, TRUE, 0); + g_signal_connect ( G_OBJECT (tb), "changed", + G_CALLBACK (sp_text_edit_dialog_text_changed), dlg ); + g_signal_connect (G_OBJECT (txt), "focus-in-event", G_CALLBACK (text_view_focus_in), dlg); + g_signal_connect (G_OBJECT (txt), "focus-out-event", G_CALLBACK (text_view_focus_out), dlg); + g_object_set_data (G_OBJECT (dlg), "text", tb); + g_object_set_data (G_OBJECT (dlg), "textw", txt); + } + + /* Buttons */ + GtkWidget *hb = gtk_hbox_new (FALSE, VB_MARGIN); + gtk_container_set_border_width (GTK_CONTAINER (hb), 4); + gtk_box_pack_start (GTK_BOX (mainvb), hb, FALSE, FALSE, 0); + + { + GtkWidget *b = gtk_button_new_with_label (_("Set as default")); + g_signal_connect ( G_OBJECT (b), "clicked", + G_CALLBACK (sp_text_edit_dialog_set_default), + dlg ); + gtk_box_pack_start (GTK_BOX (hb), b, FALSE, FALSE, 0); + g_object_set_data (G_OBJECT (dlg), "default", b); + } + + { + GtkWidget *b = gtk_button_new_from_stock (GTK_STOCK_CLOSE); + g_signal_connect ( G_OBJECT (b), "clicked", + G_CALLBACK (sp_text_edit_dialog_close), dlg ); + gtk_box_pack_end (GTK_BOX (hb), b, FALSE, FALSE, 0); + } + + { + GtkWidget *b = gtk_button_new_from_stock (GTK_STOCK_APPLY); + g_signal_connect ( G_OBJECT (b), "clicked", + G_CALLBACK (sp_text_edit_dialog_apply), dlg ); + gtk_box_pack_end ( GTK_BOX (hb), b, FALSE, FALSE, 0 ); + g_object_set_data (G_OBJECT (dlg), "apply", b); + } + + g_signal_connect ( G_OBJECT (INKSCAPE), "modify_selection", + G_CALLBACK (sp_text_edit_dialog_selection_modified), dlg); + g_signal_connect ( G_OBJECT (INKSCAPE), "change_selection", + G_CALLBACK (sp_text_edit_dialog_selection_changed), dlg); + g_signal_connect (INKSCAPE, "change_subselection", G_CALLBACK (sp_text_edit_dialog_subselection_changed), dlg); + + gtk_widget_show_all (dlg); + + sp_text_edit_dialog_read_selection (dlg, TRUE, TRUE); + } + + gtk_window_present ((GtkWindow *) dlg); + +} // end of sp_text_edit_dialog() + + + +static void +sp_text_edit_dialog_selection_modified ( Inkscape::Application *inkscape, + Inkscape::Selection *sel, + guint flags, + GtkWidget *dlg ) +{ + gboolean style, content; + + style = + ((flags & ( SP_OBJECT_CHILD_MODIFIED_FLAG | + SP_OBJECT_STYLE_MODIFIED_FLAG )) != 0 ); + + content = + ((flags & ( SP_OBJECT_CHILD_MODIFIED_FLAG | + SP_TEXT_CONTENT_MODIFIED_FLAG )) != 0 ); + + sp_text_edit_dialog_read_selection (dlg, style, content); + +} + + + +static void +sp_text_edit_dialog_selection_changed ( Inkscape::Application *inkscape, + Inkscape::Selection *sel, + GtkWidget *dlg ) +{ + sp_text_edit_dialog_read_selection (dlg, TRUE, TRUE); +} + +static void sp_text_edit_dialog_subselection_changed ( Inkscape::Application *inkscape, SPDesktop *desktop, GtkWidget *dlg ) +{ + sp_text_edit_dialog_read_selection (dlg, TRUE, FALSE); +} + +static void +sp_text_edit_dialog_update_object_text ( SPItem *text ) +{ + GtkTextBuffer *tb; + GtkTextIter start, end; + gchar *str; + + tb = (GtkTextBuffer*)g_object_get_data (G_OBJECT (dlg), "text"); + + /* write text */ + if (gtk_text_buffer_get_modified (tb)) { + gtk_text_buffer_get_bounds (tb, &start, &end); + str = gtk_text_buffer_get_text (tb, &start, &end, TRUE); + sp_te_set_repr_text_multiline (text, str); + g_free (str); + gtk_text_buffer_set_modified (tb, FALSE); + } +} + +SPCSSAttr * +sp_get_text_dialog_style () +{ + GtkWidget *fontsel = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "fontsel"); + + SPCSSAttr *css = sp_repr_css_attr_new (); + + /* font */ + font_instance *font = sp_font_selector_get_font (SP_FONT_SELECTOR (fontsel)); + + if ( font ) { + gchar c[256]; + font->Family(c, 256); + sp_repr_css_set_property (css, "font-family", c); + + font->Attribute( "weight", c, 256); + sp_repr_css_set_property (css, "font-weight", c); + + font->Attribute("style", c, 256); + sp_repr_css_set_property (css, "font-style", c); + + font->Attribute("stretch", c, 256); + sp_repr_css_set_property (css, "font-stretch", c); + + font->Attribute("variant", c, 256); + sp_repr_css_set_property (css, "font-variant", c); + + Inkscape::CSSOStringStream os; + os << sp_font_selector_get_size (SP_FONT_SELECTOR (fontsel)); + sp_repr_css_set_property (css, "font-size", os.str().c_str()); + + font->Unref(); + font=NULL; + } + + /* Layout */ + GtkWidget *b = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "text_anchor_start"); + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (b))) { + sp_repr_css_set_property (css, "text-anchor", "start"); + sp_repr_css_set_property (css, "text-align", "start"); + } else { + b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), + "text_anchor_middle"); + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (b))) { + sp_repr_css_set_property (css, "text-anchor", "middle"); + sp_repr_css_set_property (css, "text-align", "center"); + } else { + sp_repr_css_set_property (css, "text-anchor", "end"); + sp_repr_css_set_property (css, "text-align", "end"); + } + } + + b = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), INKSCAPE_STOCK_WRITING_MODE_LR ); + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (b))) { + sp_repr_css_set_property (css, "writing-mode", "lr"); + } else { + sp_repr_css_set_property (css, "writing-mode", "tb"); + } + + // Note that CSS 1.1 does not support line-height; we set it for consistency, but also set + // sodipodi:linespacing for backwards compatibility; in 1.2 we use line-height for flowtext + GtkWidget *combo = (GtkWidget*)g_object_get_data ((GObject *) dlg, "line_spacing"); + const char *sstr = gtk_entry_get_text ((GtkEntry *) ((GtkCombo *) (combo))->entry); + sp_repr_css_set_property (css, "line-height", sstr); + + return css; +} + + +static void +sp_text_edit_dialog_set_default (GtkButton *button, GtkWidget *dlg) +{ + GtkWidget *def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default"); + + SPCSSAttr *css = sp_get_text_dialog_style (); + + g_object_set_data (G_OBJECT (dlg), "blocked", GINT_TO_POINTER (TRUE)); + sp_repr_css_change (inkscape_get_repr (INKSCAPE, "tools.text"), css, "style"); + g_object_set_data (G_OBJECT (dlg), "blocked", GINT_TO_POINTER (FALSE)); + + sp_repr_css_attr_unref (css); + + gtk_widget_set_sensitive (def, FALSE); +} + + + +static void +sp_text_edit_dialog_apply (GtkButton *button, GtkWidget *dlg) +{ + g_object_set_data (G_OBJECT (dlg), "blocked", GINT_TO_POINTER (TRUE)); + + GtkWidget *apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply"); + GtkWidget *def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default"); + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + + unsigned items = 0; + const GSList *item_list = SP_DT_SELECTION(desktop)->itemList(); + + SPCSSAttr *css = sp_get_text_dialog_style (); + + sp_desktop_set_style(desktop, css, true); + + for (; item_list != NULL; item_list = item_list->next) { + // apply style to the reprs of all text objects in the selection + if (SP_IS_TEXT (item_list->data)) { + + // backwards compatibility: + SP_OBJECT_REPR(item_list->data)->setAttribute("sodipodi:linespacing", sp_repr_css_property (css, "line-height", NULL)); + + ++items; + } + else if (SP_IS_FLOWTEXT (item_list->data)) + // no need to set sodipodi:linespacing, because Inkscape never supported it on flowtext + ++items; + } + + if (items == 0) { + // no text objects; apply style to prefs for new objects + sp_repr_css_change (inkscape_get_repr (INKSCAPE, "tools.text"), css, "style"); + gtk_widget_set_sensitive (def, FALSE); + } else if (items == 1) { + /* exactly one text object; now set its text, too */ + SPItem *item = SP_DT_SELECTION(SP_ACTIVE_DESKTOP)->singleItem(); + if (SP_IS_TEXT (item) || SP_IS_FLOWTEXT(item)) { + sp_text_edit_dialog_update_object_text (item); + } + } + + // complete the transaction + sp_document_done (SP_DT_DOCUMENT (SP_ACTIVE_DESKTOP)); + + gtk_widget_set_sensitive (apply, FALSE); + + sp_repr_css_attr_unref (css); + + g_object_set_data (G_OBJECT (dlg), "blocked", GINT_TO_POINTER (FALSE)); +} + + + +static void +sp_text_edit_dialog_close (GtkButton *button, GtkWidget *dlg) +{ + gtk_widget_destroy (GTK_WIDGET (dlg)); +} + +static void +sp_text_edit_dialog_read_selection ( GtkWidget *dlg, + gboolean dostyle, + gboolean docontent ) +{ + if (g_object_get_data (G_OBJECT (dlg), "blocked")) + return; + + g_object_set_data (G_OBJECT (dlg), "blocked", GINT_TO_POINTER (TRUE)); + + GtkWidget *notebook = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "notebook"); + GtkWidget *textw = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "textw"); + GtkWidget *fontsel = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "fontsel"); + GtkWidget *preview = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "preview"); + GtkWidget *apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply"); + GtkWidget *def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default"); + + GtkTextBuffer *tb = (GtkTextBuffer*)g_object_get_data (G_OBJECT (dlg), "text"); + + SPItem *text = sp_ted_get_selected_text_item (); + + Inkscape::XML::Node *repr; + if (text) + { + guint items = sp_ted_get_selected_text_count (); + if (items == 1) { + gtk_widget_set_sensitive (textw, TRUE); + } else { + gtk_widget_set_sensitive (textw, FALSE); + } + gtk_widget_set_sensitive (apply, FALSE); + gtk_widget_set_sensitive (def, TRUE); + + if (docontent) { + gchar *str; + str = sp_te_get_string_multiline (text); + + if (str) { + int pos; + pos = 0; + + if (items == 1) { + gtk_text_buffer_set_text (tb, str, strlen (str)); + gtk_text_buffer_set_modified (tb, FALSE); + } + sp_font_preview_set_phrase (SP_FONT_PREVIEW (preview), str); + g_free (str); + + } else { + gtk_text_buffer_set_text (tb, "", 0); + sp_font_preview_set_phrase (SP_FONT_PREVIEW (preview), NULL); + } + } // end of if (docontent) + repr = SP_OBJECT_REPR (text); + + } else { + gtk_widget_set_sensitive (textw, FALSE); + gtk_widget_set_sensitive (apply, FALSE); + gtk_widget_set_sensitive (def, FALSE); + } + + if (dostyle) { + + // create temporary style + SPStyle *query = sp_style_new (); + // query style from desktop into it. This returns a result flag and fills query with the style of subselection, if any, or selection + int result_family = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FONTFAMILY); + int result_style = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FONTSTYLE); + int result_numbers = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FONTNUMBERS); + + // If querying returned nothing, read the style from the text tool prefs (default style for new texts) + if (result_family == QUERY_STYLE_NOTHING || result_style == QUERY_STYLE_NOTHING || result_numbers == QUERY_STYLE_NOTHING) { + repr = inkscape_get_repr (INKSCAPE, "tools.text"); + if (repr) { + gtk_widget_set_sensitive (notebook, TRUE); + sp_style_read_from_repr (query, repr); + } else { + gtk_widget_set_sensitive (notebook, FALSE); + } + } + + // FIXME: process result_family/style == QUERY_STYLE_MULTIPLE_DIFFERENT by showing "Many" in the lists + font_instance *font = (font_factory::Default())->Face ( query->text->font_family.value, font_style_to_pos(*query) ); + if (font) { + // the font is oversized, so we need to pass the true size separately + sp_font_selector_set_font (SP_FONT_SELECTOR (fontsel), font, query->font_size.computed); + sp_font_preview_set_font (SP_FONT_PREVIEW (preview), font, SP_FONT_SELECTOR(fontsel)); + font->Unref(); + font=NULL; + } + + GtkWidget *b; + if (query->text_anchor.computed == SP_CSS_TEXT_ANCHOR_START) { + b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), "text_anchor_start" ); + } else if (query->text_anchor.computed == SP_CSS_TEXT_ANCHOR_MIDDLE) { + b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), "text_anchor_middle" ); + } else { + b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), "text_anchor_end" ); + } + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (b), TRUE); + + if (query->writing_mode.computed == SP_CSS_WRITING_MODE_LR_TB) { + b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), INKSCAPE_STOCK_WRITING_MODE_LR ); + } else { + b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), INKSCAPE_STOCK_WRITING_MODE_TB ); + } + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (b), TRUE); + + GtkWidget *combo = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), "line_spacing" ); + double height; + if (query->line_height.normal) height = Inkscape::Text::Layout::LINE_HEIGHT_NORMAL; + else if (query->line_height.unit == SP_CSS_UNIT_PERCENT) + height = query->line_height.value; + else height = query->line_height.computed; + gchar *sstr = g_strdup_printf ("%d%%", (int) floor(height * 100 + 0.5)); + gtk_entry_set_text ((GtkEntry *) ((GtkCombo *) (combo))->entry, sstr); + g_free(sstr); + + g_free (query); + } + + g_object_set_data (G_OBJECT (dlg), "blocked", NULL); +} + + +static void +sp_text_edit_dialog_text_changed (GtkTextBuffer *tb, GtkWidget *dlg) +{ + GtkWidget *textw, *preview, *apply, *def; + GtkTextIter start, end; + gchar *str; + + if (g_object_get_data (G_OBJECT (dlg), "blocked")) + return; + + SPItem *text = sp_ted_get_selected_text_item (); + + textw = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "textw"); + preview = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "preview"); + apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply"); + def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default"); + + gtk_text_buffer_get_bounds (tb, &start, &end); + str = gtk_text_buffer_get_text (tb, &start, &end, TRUE); + + if (str && *str) { + sp_font_preview_set_phrase (SP_FONT_PREVIEW (preview), str); + } else { + sp_font_preview_set_phrase (SP_FONT_PREVIEW (preview), NULL); + } + g_free (str); + + if (text) { + gtk_widget_set_sensitive (apply, TRUE); + } + gtk_widget_set_sensitive (def, TRUE); + +} // end of sp_text_edit_dialog_text_changed() + + + +static void +sp_text_edit_dialog_font_changed ( SPFontSelector *fsel, + font_instance *font, + GtkWidget *dlg ) +{ + GtkWidget *preview, *apply, *def; + + if (g_object_get_data (G_OBJECT (dlg), "blocked")) + return; + + SPItem *text = sp_ted_get_selected_text_item (); + + preview = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "preview"); + apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply"); + def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default"); + + sp_font_preview_set_font (SP_FONT_PREVIEW (preview), font, SP_FONT_SELECTOR(fsel)); + + if (text) { + gtk_widget_set_sensitive (apply, TRUE); + } + gtk_widget_set_sensitive (def, TRUE); + +} // end of sp_text_edit_dialog_font_changed() + + + +static void +sp_text_edit_dialog_any_toggled (GtkToggleButton *tb, GtkWidget *dlg) +{ + GtkWidget *apply, *def; + + if (g_object_get_data (G_OBJECT (dlg), "blocked")) + return; + + SPItem *text = sp_ted_get_selected_text_item (); + + apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply"); + def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default"); + + if (text) { + gtk_widget_set_sensitive (apply, TRUE); + } + gtk_widget_set_sensitive (def, TRUE); +} + + + +static void +sp_text_edit_dialog_line_spacing_changed (GtkEditable *editable, GtkWidget *dlg) +{ + GtkWidget *apply, *def; + + if (g_object_get_data ((GObject *) dlg, "blocked")) + return; + + SPItem *text = sp_ted_get_selected_text_item (); + + apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply"); + def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default"); + + if (text) { + gtk_widget_set_sensitive (apply, TRUE); + } + gtk_widget_set_sensitive (def, TRUE); +} + + + +static SPItem * +sp_ted_get_selected_text_item (void) +{ + if (!SP_ACTIVE_DESKTOP) + return NULL; + + for (const GSList *item = SP_DT_SELECTION(SP_ACTIVE_DESKTOP)->itemList(); + item != NULL; + item = item->next) + { + if (SP_IS_TEXT(item->data) || SP_IS_FLOWTEXT(item->data)) + return SP_ITEM (item->data); + } + + return NULL; +} + + + +static unsigned +sp_ted_get_selected_text_count (void) +{ + if (!SP_ACTIVE_DESKTOP) + return 0; + + unsigned int items = 0; + + for (const GSList *item = SP_DT_SELECTION(SP_ACTIVE_DESKTOP)->itemList(); + item != NULL; + item = item->next) + { + if (SP_IS_TEXT(item->data) || SP_IS_FLOWTEXT(item->data)) + ++items; + } + + return items; +} + +/* + 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 : diff --git a/src/dialogs/text-edit.h b/src/dialogs/text-edit.h new file mode 100644 index 000000000..9350de219 --- /dev/null +++ b/src/dialogs/text-edit.h @@ -0,0 +1,24 @@ +#ifndef SP_TEXT_EDIT_H +#define SP_TEXT_EDIT_H + +/** + * \brief text-edit + * + * Text editing and font changes + * + */ + +void sp_text_edit_dialog (void); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/dialogs/tiledialog.cpp b/src/dialogs/tiledialog.cpp new file mode 100644 index 000000000..12c94ed81 --- /dev/null +++ b/src/dialogs/tiledialog.cpp @@ -0,0 +1,872 @@ +/* + * A simple dialog for creating grid type arrangements of selected objects + * + * Authors: + * Bob Jamison ( based off trace dialog) + * John Cliff + * Other dudes from The Inkscape Organization + * + * Copyright (C) 2004 Bob Jamison + * Copyright (C) 2004 John Cliff + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ +//#define DEBUG_GRID_ARRANGE 1 + +#ifdef HAVE_CONFIG_H +# include +#endif + + +#include //for GTK_RESPONSE* types +#include +#include +#include + +#include "libnr/nr-matrix-ops.h" +#include "verbs.h" +#include "prefs-utils.h" +#include "inkscape.h" +#include "desktop-handles.h" +#include "selection.h" +#include "document.h" +#include "sp-item.h" +#include "widgets/icon.h" +#include "tiledialog.h" + + + +/* + * Sort items by their x co-ordinates, taking account of y (keeps rows intact) + * + * <0 *elem1 goes before *elem2 + * 0 *elem1 == *elem2 + * >0 *elem1 goes after *elem2 + */ +int +sp_compare_x_position(SPItem *first, SPItem *second) +{ + using NR::X; + using NR::Y; + + NR::Rect const a = first->invokeBbox(sp_item_i2doc_affine(first)); + double const a_height = a.dimensions()[Y]; + + NR::Rect const b = second->invokeBbox(sp_item_i2doc_affine(second)); + double const b_height = b.dimensions()[Y]; + + bool a_in_b_vert = false; + if ((a.min()[Y] < b.min()[Y] + 0.1) && (a.min()[Y] > b.min()[Y] - b_height)) { + a_in_b_vert = true; + } else if ((b.min()[Y] < a.min()[Y] + 0.1) && (b.min()[Y] > a.min()[Y] - a_height)) { + a_in_b_vert = true; + } else if (b.min()[Y] == a.min()[Y]) { + a_in_b_vert = true; + } else { + a_in_b_vert = false; + } + + if (!a_in_b_vert) { + return -1; + } + if (a_in_b_vert && a.min()[X] > b.min()[X]) { + return 1; + } + if (a_in_b_vert && a.min()[X] < b.min()[X]) { + return -1; + } + return 0; +} + +/* + * Sort items by their y co-ordinates. + */ +int +sp_compare_y_position(SPItem *first, SPItem *second) +{ + NR::Rect const a = first->invokeBbox(sp_item_i2doc_affine(first)); + NR::Rect const b = second->invokeBbox(sp_item_i2doc_affine(second)); + + if (a.min()[NR::Y] > b.min()[NR::Y]) { + return 1; + } + if (a.min()[NR::Y] < b.min()[NR::Y]) { + return -1; + } + + return 0; +} + +namespace Inkscape { +namespace UI { +namespace Dialog { + + +//######################################################################### +//## E V E N T S +//######################################################################### + +/* + * + * This arranges the selection in a grid pattern. + * + */ + +void TileDialog::Grid_Arrange () +{ + + int cnt,row_cnt,col_cnt,a,row,col; + double grid_left,grid_top,col_width,row_height,paddingx,paddingy,width, height, new_x, new_y,cx,cy; + double total_col_width,total_row_height; + col_width = 0; + row_height = 0; + total_col_width=0; + total_row_height=0; + + + // set padding to manual values + paddingx = XPadSpinner.get_value(); + paddingy = YPadSpinner.get_value(); + + std::vector row_heights; + std::vector col_widths; + std::vector row_ys; + std::vector col_xs; + + int NoOfCols = NoOfColsSpinner.get_value_as_int(); + int NoOfRows = NoOfRowsSpinner.get_value_as_int(); + + width = 0; + for (a=0;aitemList(); + cnt=0; + for (; items != NULL; items = items->next) { + SPItem *item = SP_ITEM(items->data); + NR::Rect const b = item->invokeBbox(sp_item_i2doc_affine(item)); + width = b.dimensions()[NR::X]; + height = b.dimensions()[NR::Y]; + cx = b.midpoint()[NR::X]; + cy = b.midpoint()[NR::Y]; + + if (b.min()[NR::X] < grid_left) { + grid_left = b.min()[NR::X]; + } + if (b.min()[NR::Y] < grid_top) { + grid_top = b.min()[NR::Y]; + } + if (width > col_width) { + col_width = width; + } + if (height > row_height) { + row_height = height; + } + } + + + // require the sorting done before we can calculate row heights etc. + + const GSList *items2 = selection->itemList(); + GSList *rev = g_slist_copy((GSList *) items2); + GSList *sorted = NULL; + rev = g_slist_sort(rev, (GCompareFunc) sp_compare_y_position); + sorted = g_slist_sort(rev, (GCompareFunc) sp_compare_x_position); + + + // Calculate individual Row and Column sizes if necessary + + + cnt=0; + const GSList *sizes = sorted; + for (; sizes != NULL; sizes = sizes->next) { + SPItem *item = SP_ITEM(sizes->data); + NR::Rect const b = item->invokeBbox(sp_item_i2doc_affine(item)); + width = b.dimensions()[NR::X]; + height = b.dimensions()[NR::Y]; + if (width > col_widths[(cnt % NoOfCols)]) { + col_widths[(cnt % NoOfCols)] = width; + } + if (height > row_heights[(cnt / NoOfCols)]) { + row_heights[(cnt / NoOfCols)] = height; + } + cnt++; + } + + + /// Make sure the top and left of the grid dont move by compensating for align values. + if (RowHeightButton.get_active()){ + grid_top = grid_top - (((row_height - row_heights[0]) / 2)*(VertAlign)); + } + if (ColumnWidthButton.get_active()){ + grid_left = grid_left - (((col_width - col_widths[0]) /2)*(HorizAlign)); + } + + #ifdef DEBUG_GRID_ARRANGE + g_print("\n cx = %f cy= %f gridleft=%f",cx,cy,grid_left); + #endif + + // Calculate total widths and heights, allowing for columns and rows non uniformly sized. + + if (ColumnWidthButton.get_active()){ + total_col_width = col_width * NoOfCols; + col_widths.clear(); + for (a=0;abounds(); +#ifdef DEBUG_GRID_ARRANGE +g_print("\n row = %f col = %f selection x= %f selection y = %f", total_row_height,total_col_width, b.extent(NR::X), b.extent(NR::Y)); +#endif + paddingx = (b.extent(NR::X) - total_col_width) / (NoOfCols -1); + paddingy = (b.extent(NR::Y) - total_row_height) / (NoOfRows -1); + } + +/* + Horizontal align - Left = 0 + Centre = 1 + Right = 2 + + Vertical align - Top = 0 + Middle = 1 + Bottom = 2 + + X position is calculated by taking the grids left co-ord, adding the distance to the column, + then adding 1/2 the spacing multiplied by the align variable above, + Y position likewise, takes the top of the grid, adds the y to the current row then adds the padding in to align it. + +*/ + + // Calculate row and column x and y coords required to allow for columns and rows which are non uniformly sized. + + for (a=0;adata); + sorted = sorted->next; + } + + for (; current_row != NULL; current_row = current_row->next) { + SPItem *item=SP_ITEM(current_row->data); + Inkscape::XML::Node *repr = SP_OBJECT_REPR(item); + NR::Rect const b = item->invokeBbox(sp_item_i2doc_affine(item)); + width = b.dimensions()[NR::X]; + height = b.dimensions()[NR::Y]; + row = cnt / NoOfCols; + col = cnt % NoOfCols; + + // original before I started fecking about with it. + // new_x = grid_left + (((col_width - width)/2)*HorizAlign) + (( col_width + paddingx ) * (cnt % NoOfCols)); + // new_y = grid_top + (((row_height - height)/2)*VertAlign) +(( row_height + paddingy ) * (cnt / NoOfCols)); + + new_x = grid_left + (((col_widths[col] - width)/2)*HorizAlign) + col_xs[col]; + new_y = grid_top + (((row_heights[row] - height)/2)*VertAlign) + row_ys[row]; + + NR::Point move = NR::Point(new_x - b.min()[NR::X], b.min()[NR::Y] - new_y); // why are the two args the opposite ways round??? + NR::Matrix const &affine = NR::Matrix(NR::translate(move)); + sp_item_set_i2d_affine(item, sp_item_i2d_affine(item) * affine); + sp_item_write_transform(item, repr, item->transform, NULL); + SP_OBJECT (current_row->data)->updateRepr(repr, SP_OBJECT_WRITE_EXT); + cnt +=1; + } + g_slist_free (current_row); + } + + NRRect b; + selection->bounds(&b); + + sp_document_done (SP_DT_DOCUMENT (desktop)); + +} + + +//######################################################################### +//## E V E N T S +//######################################################################### + + +void TileDialog::_apply() +{ + Grid_Arrange(); +} + + +/** + * changed value in # of columns spinbox. + */ +void TileDialog::on_row_spinbutton_changed() +{ + // quit if run by the attr_changed listener + if (updating) { + return; + } + + // in turn, prevent listener from responding + updating = true; + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + + Inkscape::Selection *selection = SP_DT_SELECTION (desktop); + + GSList const *items = selection->itemList(); + int selcount = g_slist_length((GSList *)items); + + double PerCol = ceil(selcount / NoOfColsSpinner.get_value()); + NoOfRowsSpinner.set_value(PerCol); + prefs_set_double_attribute ("dialogs.gridtiler", "NoOfCols", NoOfColsSpinner.get_value()); + updating=false; +} + +/** + * changed value in # of rows spinbox. + */ +void TileDialog::on_col_spinbutton_changed() +{ + // quit if run by the attr_changed listener + if (updating) { + return; + } + + // in turn, prevent listener from responding + updating = true; + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + Inkscape::Selection *selection = SP_DT_SELECTION (desktop); + + GSList const *items = selection->itemList(); + int selcount = g_slist_length((GSList *)items); + + double PerRow = ceil(selcount / NoOfRowsSpinner.get_value()); + NoOfColsSpinner.set_value(PerRow); + prefs_set_double_attribute ("dialogs.gridtiler", "NoOfCols", PerRow); + + updating=false; +} + +/** + * changed value in x padding spinbox. + */ +void TileDialog::on_xpad_spinbutton_changed() +{ + + prefs_set_double_attribute ("dialogs.gridtiler", "XPad", XPadSpinner.get_value()); + +} + +/** + * changed value in y padding spinbox. + */ +void TileDialog::on_ypad_spinbutton_changed() +{ + + prefs_set_double_attribute ("dialogs.gridtiler", "YPad", YPadSpinner.get_value()); + +} + + +/** + * checked/unchecked autosize Rows button. + */ +void TileDialog::on_RowSize_checkbutton_changed() +{ + + if (RowHeightButton.get_active()) { + prefs_set_double_attribute ("dialogs.gridtiler", "AutoRowSize", 20); + } else { + prefs_set_double_attribute ("dialogs.gridtiler", "AutoRowSize", -20); + } + RowHeightBox.set_sensitive ( !RowHeightButton.get_active()); +} + +/** + * checked/unchecked autosize Rows button. + */ +void TileDialog::on_ColSize_checkbutton_changed() +{ + + if (ColumnWidthButton.get_active()) { + prefs_set_double_attribute ("dialogs.gridtiler", "AutoColSize", 20); + } else { + prefs_set_double_attribute ("dialogs.gridtiler", "AutoColSize", -20); + } + ColumnWidthBox.set_sensitive ( !ColumnWidthButton.get_active()); + +} + +/** + * changed value in columns spinbox. + */ +void TileDialog::on_rowSize_spinbutton_changed() +{ + // quit if run by the attr_changed listener + if (updating) { + return; + } + + // in turn, prevent listener from responding + updating = true; + prefs_set_double_attribute ("dialogs.gridtiler", "RowHeight", RowHeightSpinner.get_value()); + updating=false; + +} + +/** + * changed value in rows spinbox. + */ +void TileDialog::on_colSize_spinbutton_changed() +{ + // quit if run by the attr_changed listener + if (updating) { + return; + } + + // in turn, prevent listener from responding + updating = true; + prefs_set_double_attribute ("dialogs.gridtiler", "ColWidth", ColumnWidthSpinner.get_value()); + updating=false; + +} + +/** + * changed Radio button in Spacing group. + */ +void TileDialog::Spacing_button_changed() +{ + if (SpaceManualRadioButton.get_active()) { + prefs_set_double_attribute ("dialogs.gridtiler", "SpacingType", 20); + } else { + prefs_set_double_attribute ("dialogs.gridtiler", "SpacingType", -20); + } + + SizesHBox.set_sensitive ( SpaceManualRadioButton.get_active()); +} + +/** + * changed Radio button in Vertical Align group. + */ +void TileDialog::VertAlign_changed() +{ + if (VertTopRadioButton.get_active()) { + VertAlign = 0; + prefs_set_double_attribute ("dialogs.gridtiler", "VertAlign", 0); + } else if (VertCentreRadioButton.get_active()){ + VertAlign = 1; + prefs_set_double_attribute ("dialogs.gridtiler", "VertAlign", 1); + } else if (VertBotRadioButton.get_active()){ + VertAlign = 2; + prefs_set_double_attribute ("dialogs.gridtiler", "VertAlign", 2); + } + +} + +/** + * changed Radio button in Vertical Align group. + */ +void TileDialog::HorizAlign_changed() +{ + if (HorizLeftRadioButton.get_active()) { + HorizAlign = 0; + prefs_set_double_attribute ("dialogs.gridtiler", "HorizAlign", 0); + } else if (HorizCentreRadioButton.get_active()){ + HorizAlign = 1; + prefs_set_double_attribute ("dialogs.gridtiler", "HorizAlign", 1); + } else if (HorizRightRadioButton.get_active()){ + HorizAlign = 2; + prefs_set_double_attribute ("dialogs.gridtiler", "HorizAlign", 2); + } + +} + +/** + * Desktop selection changed + */ +void TileDialog::updateSelection() +{ + double col_width, row_height; + // quit if run by the attr_changed listener + if (updating) { + return; + } + + col_width=0; + row_height=0; + // in turn, prevent listener from responding + updating = true; + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + Inkscape::Selection *selection = SP_DT_SELECTION (desktop); + const GSList *items = selection->itemList(); + int selcount = g_slist_length((GSList *)items); + + if (NoOfColsSpinner.get_value()>1){ + // Update the number of rows assuming number of columns wanted remains same. + double NoOfRows = ceil(selcount / NoOfColsSpinner.get_value()); + NoOfRowsSpinner.set_value(NoOfRows); + + // if the selection has less than the number set for one row, reduce it appropriately + if (selcountupdateSelection(); +} + + +//######################################################################### +//## C O N S T R U C T O R / D E S T R U C T O R +//######################################################################### +/** + * Constructor + */ +TileDialog::TileDialog() + : Dialog ("dialogs.gridtiler", SP_VERB_SELECTION_GRIDTILE) +{ + // bool used by spin button callbacks to stop loops where they change each other. + updating = false; + + // could not do this in gtkmm - there's no Gtk::SizeGroup public constructor (!) + GtkSizeGroup *_col1 = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); + GtkSizeGroup *_col2 = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); + GtkSizeGroup *_col3 = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); + + { + // Selection Change signal + g_signal_connect ( G_OBJECT (INKSCAPE), "change_selection", G_CALLBACK (updateSelectionCallback), this); + } + + Gtk::VBox *mainVBox = get_vbox(); + +#define MARGIN 2 + + //##Set up the panel + + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + + Inkscape::Selection *selection = SP_DT_SELECTION (desktop); + int selcount = 1; + if (!selection->isEmpty()) { + GSList const *items = selection->itemList(); + selcount = g_slist_length((GSList *)items); + } + + + /*#### Number of Rows ####*/ + + double PerRow = selcount; + double PerCol = 1; + + #ifdef DEBUG_GRID_ARRANGE + g_print("/n PerRox = %f PerCol = %f selcount = %d",PerRow,PerCol,selcount); + #endif + + NoOfRowsLabel.set_label(_("Rows:")); + NoOfRowsBox.pack_start(NoOfRowsLabel, false, false, MARGIN); + + NoOfRowsSpinner.set_digits(0); + NoOfRowsSpinner.set_increments(1, 5); + NoOfRowsSpinner.set_range(1.0, 100.0); + NoOfRowsSpinner.set_value(PerCol); + NoOfRowsSpinner.signal_changed().connect(sigc::mem_fun(*this, &TileDialog::on_col_spinbutton_changed)); + tips.set_tip(NoOfRowsSpinner, _("Number of rows")); + NoOfRowsBox.pack_start(NoOfRowsSpinner, false, false, MARGIN); + gtk_size_group_add_widget(_col1, (GtkWidget *) NoOfRowsBox.gobj()); + + RowHeightButton.set_label(_("Equal height")); + double AutoRow = prefs_get_double_attribute ("dialogs.gridtiler", "AutoRowSize", 15); + if (AutoRow>0) + AutoRowSize=true; + else + AutoRowSize=false; + RowHeightButton.set_active(AutoRowSize); + + NoOfRowsBox.pack_start(RowHeightButton, false, false, MARGIN); + + tips.set_tip(RowHeightButton, _("If not set, each row has the height of the tallest object in it")); + RowHeightButton.signal_toggled().connect(sigc::mem_fun(*this, &TileDialog::on_RowSize_checkbutton_changed)); + + { + /*#### Radio buttons to control vertical alignment ####*/ + + VertAlignLabel.set_label(_("Align:")); + VertAlignHBox.pack_start(VertAlignLabel, false, false, MARGIN); + + VertTopRadioButton.signal_toggled().connect(sigc::mem_fun(*this, &TileDialog::VertAlign_changed)); + VertAlignGroup = VertTopRadioButton.get_group(); + VertAlignVBox.pack_start(VertTopRadioButton, false, false, 0); + + VertCentreRadioButton.set_group(VertAlignGroup); + VertCentreRadioButton.signal_toggled().connect(sigc::mem_fun(*this, &TileDialog::VertAlign_changed)); + VertAlignVBox.pack_start(VertCentreRadioButton, false, false, 0); + + VertBotRadioButton.set_group(VertAlignGroup); + VertBotRadioButton.signal_toggled().connect(sigc::mem_fun(*this, &TileDialog::VertAlign_changed)); + VertAlignVBox.pack_start(VertBotRadioButton, false, false, 0); + + VertAlign = prefs_get_double_attribute ("dialogs.gridtiler", "VertAlign", 1); + if (VertAlign == 0) { + VertTopRadioButton.set_active(TRUE); + } + else if (VertAlign == 1) { + VertCentreRadioButton.set_active(TRUE); + } + else if (VertAlign == 2){ + VertBotRadioButton.set_active(TRUE); + } + VertAlignHBox.pack_start(VertAlignVBox, false, false, MARGIN); + NoOfRowsBox.pack_start(VertAlignHBox, false, false, MARGIN); + } + + SpinsHBox.pack_start(NoOfRowsBox, false, false, MARGIN); + + + /*#### Label for X ####*/ + padXByYLabel.set_label(" "); + XByYLabelVBox.pack_start(padXByYLabel, false, false, MARGIN); + XByYLabel.set_markup(" × "); + XByYLabelVBox.pack_start(XByYLabel, false, false, MARGIN); + SpinsHBox.pack_start(XByYLabelVBox, false, false, MARGIN); + gtk_size_group_add_widget(_col2, (GtkWidget *) XByYLabelVBox.gobj()); + + /*#### Number of columns ####*/ + + NoOfColsLabel.set_label(_("Columns:")); + NoOfColsBox.pack_start(NoOfColsLabel, false, false, MARGIN); + + NoOfColsSpinner.set_digits(0); + NoOfColsSpinner.set_increments(1, 5); + NoOfColsSpinner.set_range(1.0, 100.0); + NoOfColsSpinner.set_value(PerRow); + NoOfColsSpinner.signal_changed().connect(sigc::mem_fun(*this, &TileDialog::on_row_spinbutton_changed)); + tips.set_tip(NoOfColsSpinner, _("Number of columns")); + NoOfColsBox.pack_start(NoOfColsSpinner, false, false, MARGIN); + gtk_size_group_add_widget(_col3, (GtkWidget *) NoOfColsBox.gobj()); + + ColumnWidthButton.set_label(_("Equal width")); + double AutoCol = prefs_get_double_attribute ("dialogs.gridtiler", "AutoColSize", 15); + if (AutoCol>0) + AutoColSize=true; + else + AutoColSize=false; + ColumnWidthButton.set_active(AutoColSize); + NoOfColsBox.pack_start(ColumnWidthButton, false, false, MARGIN); + + tips.set_tip(ColumnWidthButton, _("If not set, each column has the width of the widest object in it")); + ColumnWidthButton.signal_toggled().connect(sigc::mem_fun(*this, &TileDialog::on_ColSize_checkbutton_changed)); + + + { + /*#### Radio buttons to control horizontal alignment ####*/ + + HorizAlignLabel.set_label(_("Align:")); + HorizAlignVBox.pack_start(HorizAlignLabel, false, false, MARGIN); + + HorizAlignHBox.pack_start(*(new Gtk::HBox()), true, true, 0); // centering strut + + HorizLeftRadioButton.signal_toggled().connect(sigc::mem_fun(*this, &TileDialog::HorizAlign_changed)); + HorizAlignGroup = HorizLeftRadioButton.get_group(); + HorizAlignHBox.pack_start(HorizLeftRadioButton, false, false, 0); + + HorizCentreRadioButton.set_group(HorizAlignGroup); + HorizCentreRadioButton.signal_toggled().connect(sigc::mem_fun(*this, &TileDialog::HorizAlign_changed)); + HorizAlignHBox.pack_start(HorizCentreRadioButton, false, false, 0); + + HorizRightRadioButton.set_group(HorizAlignGroup); + HorizRightRadioButton.signal_toggled().connect(sigc::mem_fun(*this, &TileDialog::HorizAlign_changed)); + HorizAlignHBox.pack_start(HorizRightRadioButton, false, false, 0); + + HorizAlignHBox.pack_start(*(new Gtk::HBox()), true, true, 0); // centering strut + + HorizAlign = prefs_get_double_attribute ("dialogs.gridtiler", "HorizAlign", 1); + if (HorizAlign == 0) { + HorizLeftRadioButton.set_active(TRUE); + } + else if (HorizAlign == 1) { + HorizCentreRadioButton.set_active(TRUE); + } + else if (HorizAlign == 2) { + HorizRightRadioButton.set_active(TRUE); + } + HorizAlignVBox.pack_start(HorizAlignHBox, false, false, MARGIN); + NoOfColsBox.pack_start(HorizAlignVBox, false, false, MARGIN); + } + + SpinsHBox.pack_start(NoOfColsBox, false, false, MARGIN); + + TileBox.pack_start(SpinsHBox, false, false, MARGIN); + + { + /*#### Radio buttons to control spacing manually or to fit selection bbox ####*/ + SpaceByBBoxRadioButton.set_label(_("Fit into selection box")); + SpaceByBBoxRadioButton.signal_toggled().connect(sigc::mem_fun(*this, &TileDialog::Spacing_button_changed)); + SpacingGroup = SpaceByBBoxRadioButton.get_group(); + + SpacingVBox.pack_start(SpaceByBBoxRadioButton, false, false, MARGIN); + + SpaceManualRadioButton.set_label(_("Set spacing:")); + SpaceManualRadioButton.set_group(SpacingGroup); + SpaceManualRadioButton.signal_toggled().connect(sigc::mem_fun(*this, &TileDialog::Spacing_button_changed)); + SpacingVBox.pack_start(SpaceManualRadioButton, false, false, MARGIN); + + TileBox.pack_start(SpacingVBox, false, false, MARGIN); + } + + { + /*#### Y Padding ####*/ + + GtkWidget *i = sp_icon_new (GTK_ICON_SIZE_MENU, "clonetiler_per_row"); + YPadBox.pack_start (*(Glib::wrap(i)), false, false, MARGIN); + + YPadSpinner.set_digits(1); + YPadSpinner.set_increments(0.2, 2); + YPadSpinner.set_range(-10000, 10000); + double YPad = prefs_get_double_attribute ("dialogs.gridtiler", "YPad", 15); + YPadSpinner.set_value(YPad); + YPadBox.pack_start(YPadSpinner, true, true, MARGIN); + tips.set_tip(YPadSpinner, _("Vertical spacing between rows (px units)")); + YPadSpinner.signal_changed().connect(sigc::mem_fun(*this, &TileDialog::on_ypad_spinbutton_changed)); + gtk_size_group_add_widget(_col1, (GtkWidget *) YPadBox.gobj()); + + SizesHBox.pack_start(YPadBox, false, false, MARGIN); + } + + { + Gtk::HBox *spacer = new Gtk::HBox; + SizesHBox.pack_start(*spacer, false, false, 0); + gtk_size_group_add_widget(_col2, (GtkWidget *) spacer->gobj()); + } + + { + /*#### X padding ####*/ + + GtkWidget *i = sp_icon_new (GTK_ICON_SIZE_MENU, "clonetiler_per_column"); + XPadBox.pack_start (*(Glib::wrap(i)), false, false, MARGIN); + + XPadSpinner.set_digits(1); + XPadSpinner.set_increments(0.2, 2); + XPadSpinner.set_range(-10000, 10000); + double XPad = prefs_get_double_attribute ("dialogs.gridtiler", "XPad", 15); + XPadSpinner.set_value(XPad); + XPadBox.pack_start(XPadSpinner, true, true, MARGIN); + tips.set_tip(XPadSpinner, _("Horizontal spacing between columns (px units)")); + XPadSpinner.signal_changed().connect(sigc::mem_fun(*this, &TileDialog::on_xpad_spinbutton_changed)); + gtk_size_group_add_widget(_col3, (GtkWidget *) XPadBox.gobj()); + + SizesHBox.pack_start(XPadBox, false, false, MARGIN); + } + + + TileBox.pack_start(SizesHBox, false, false, MARGIN); + + mainVBox->pack_start(TileBox); + + double SpacingType = prefs_get_double_attribute ("dialogs.gridtiler", "SpacingType", 15); + if (SpacingType>0) { + ManualSpacing=true; + } else { + ManualSpacing=false; + } + SpaceManualRadioButton.set_active(ManualSpacing); + SpaceByBBoxRadioButton.set_active(!ManualSpacing); + SizesHBox.set_sensitive (ManualSpacing); + + //## The OK button + TileOkButton = add_button(Gtk::Stock::APPLY, GTK_RESPONSE_APPLY); + tips.set_tip((*TileOkButton), _("Arrange selected objects")); + + show_all_children(); +} + + + + + + +} //namespace Dialog +} //namespace UI +} //namespace Inkscape + +//######################################################################### +//## E N D O F F I L E +//######################################################################### + + +/* + 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/dialogs/tiledialog.h b/src/dialogs/tiledialog.h new file mode 100644 index 000000000..69a5ea3e2 --- /dev/null +++ b/src/dialogs/tiledialog.h @@ -0,0 +1,190 @@ +#ifndef __TILEDIALOG_H__ +#define __TILEDIALOG_H__ +/* + * A simple dialog for creating grid type arrangements of selected objects + * + * Authors: + * Bob Jamison ( based off trace dialog) + * John Cliff + * Other dudes from The Inkscape Organization + * + * Copyright (C) 2004 Bob Jamison + * Copyright (C) 2004 John Cliff + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + +#include +#include +#include +#include +#include +#include +#include + +#include "ui/dialog/dialog.h" + +namespace Inkscape { +namespace UI { +namespace Dialog { + + +/** + * A dialog that displays log messages + */ +class TileDialog : public Dialog { + +public: + + /** + * Constructor + */ + TileDialog() ; + + + /** + * Factory method + */ + static TileDialog *create() { return new TileDialog(); } + + /** + * Destructor + */ + virtual ~TileDialog() {}; + + /** + * Do the actual work + */ + void Grid_Arrange(); + + /** + * Respond to selection change + */ + void updateSelection(); + + + /** + * Callback from Apply + */ + virtual void _apply(); + + /** + * Callback from spinbuttons + */ + void on_row_spinbutton_changed(); + void on_col_spinbutton_changed(); + void on_xpad_spinbutton_changed(); + void on_ypad_spinbutton_changed(); + void on_RowSize_checkbutton_changed(); + void on_ColSize_checkbutton_changed(); + void on_rowSize_spinbutton_changed(); + void on_colSize_spinbutton_changed(); + void Spacing_button_changed(); + void VertAlign_changed(); + void HorizAlign_changed(); + + +private: + TileDialog(TileDialog const &d); // no copy + void operator=(TileDialog const &d); // no assign + + bool userHidden; + bool updating; + + + + Gtk::Notebook notebook; + Gtk::Tooltips tips; + + Gtk::VBox TileBox; + Gtk::Button *TileOkButton; + Gtk::Button *TileCancelButton; + + // Number selected label + Gtk::Label SelectionContentsLabel; + + + Gtk::HBox AlignHBox; + Gtk::HBox SpinsHBox; + Gtk::HBox SizesHBox; + + // Number per Row + Gtk::VBox NoOfColsBox; + Gtk::Label NoOfColsLabel; + Gtk::SpinButton NoOfColsSpinner; + bool AutoRowSize; + Gtk::CheckButton RowHeightButton; + + Gtk::VBox XByYLabelVBox; + Gtk::Label padXByYLabel; + Gtk::Label XByYLabel; + + // Number per Column + Gtk::VBox NoOfRowsBox; + Gtk::Label NoOfRowsLabel; + Gtk::SpinButton NoOfRowsSpinner; + bool AutoColSize; + Gtk::CheckButton ColumnWidthButton; + + // Vertical align + Gtk::Label VertAlignLabel; + Gtk::HBox VertAlignHBox; + Gtk::VBox VertAlignVBox; + Gtk::RadioButtonGroup VertAlignGroup; + Gtk::RadioButton VertCentreRadioButton; + Gtk::RadioButton VertTopRadioButton; + Gtk::RadioButton VertBotRadioButton; + double VertAlign; + + // Horizontal align + Gtk::Label HorizAlignLabel; + Gtk::VBox HorizAlignVBox; + Gtk::HBox HorizAlignHBox; + Gtk::RadioButtonGroup HorizAlignGroup; + Gtk::RadioButton HorizCentreRadioButton; + Gtk::RadioButton HorizLeftRadioButton; + Gtk::RadioButton HorizRightRadioButton; + double HorizAlign; + + // padding in x + Gtk::VBox XPadBox; + Gtk::Label XPadLabel; + Gtk::SpinButton XPadSpinner; + + // padding in y + Gtk::VBox YPadBox; + Gtk::Label YPadLabel; + Gtk::SpinButton YPadSpinner; + + // BBox or manual spacing + Gtk::VBox SpacingVBox; + Gtk::RadioButtonGroup SpacingGroup; + Gtk::RadioButton SpaceByBBoxRadioButton; + Gtk::RadioButton SpaceManualRadioButton; + bool ManualSpacing; + + + + // Row height + Gtk::VBox RowHeightVBox; + Gtk::HBox RowHeightBox; + Gtk::Label RowHeightLabel; + Gtk::SpinButton RowHeightSpinner; + + // Column width + Gtk::VBox ColumnWidthVBox; + Gtk::HBox ColumnWidthBox; + Gtk::Label ColumnWidthLabel; + Gtk::SpinButton ColumnWidthSpinner; + +}; + + +} //namespace Dialog +} //namespace UI +} //namespace Inkscape + + +#endif /* __TILEDIALOG_H__ */ + diff --git a/src/dialogs/unclump.cpp b/src/dialogs/unclump.cpp new file mode 100644 index 000000000..64c348be5 --- /dev/null +++ b/src/dialogs/unclump.cpp @@ -0,0 +1,371 @@ +#define __UNCLUMP_C__ + +/* + * Unclumping objects + * + * Authors: + * bulia byak + * + * Copyright (C) 2005 Authors + * Released under GNU GPL + */ + + +#include +#include "libnr/nr-matrix-ops.h" +#include "sp-item.h" + + +// Taking bbox of an item is an expensive operation, and we need to do it many times, so here we +// cache the centers, widths, and heights of items + +//FIXME: make a class with these cashes as members instead of globals +std::map c_cache; +std::map wh_cache; + +/** +Center of bbox of item +*/ +NR::Point +unclump_center (SPItem *item) +{ + std::map::iterator i = c_cache.find(SP_OBJECT_ID(item)); + if ( i != c_cache.end() ) { + return i->second; + } + + NR::Rect const r = item->invokeBbox(sp_item_i2d_affine(item)); + NR::Point const c = r.midpoint(); + c_cache[SP_OBJECT_ID(item)] = c; + return c; +} + +NR::Point +unclump_wh (SPItem *item) +{ + NR::Point wh; + std::map::iterator i = wh_cache.find(SP_OBJECT_ID(item)); + if ( i != wh_cache.end() ) { + wh = i->second; + } else { + NR::Rect const r = item->invokeBbox(sp_item_i2d_affine(item)); + wh = r.dimensions(); + wh_cache[SP_OBJECT_ID(item)] = wh; + } + + return wh; +} + +/** +Distance between "edges" of item1 and item2. An item is considered to be an ellipse inscribed into its w/h, +so its radius (distance from center to edge) depends on the w/h and the angle towards the other item. +May be negative if the edge of item1 is between the center and the edge of item2. +*/ +double +unclump_dist (SPItem *item1, SPItem *item2) +{ + NR::Point c1 = unclump_center (item1); + NR::Point c2 = unclump_center (item2); + + NR::Point wh1 = unclump_wh (item1); + NR::Point wh2 = unclump_wh (item2); + + // angle from each item's center to the other's, unsqueezed by its w/h, normalized to 0..pi/2 + double a1 = atan2 ((c2 - c1)[NR::Y], (c2 - c1)[NR::X] * wh1[NR::Y]/wh1[NR::X]); + a1 = fabs (a1); + if (a1 > M_PI/2) a1 = M_PI - a1; + + double a2 = atan2 ((c1 - c2)[NR::Y], (c1 - c2)[NR::X] * wh2[NR::Y]/wh2[NR::X]); + a2 = fabs (a2); + if (a2 > M_PI/2) a2 = M_PI - a2; + + // get the radius of each item for the given angle + double r1 = 0.5 * (wh1[NR::X] + (wh1[NR::Y] - wh1[NR::X]) * (a1/(M_PI/2))); + double r2 = 0.5 * (wh2[NR::X] + (wh2[NR::Y] - wh2[NR::X]) * (a2/(M_PI/2))); + + // dist between centers minus angle-adjusted radii + double dist_r = (NR::L2 (c2 - c1) - r1 - r2); + + double stretch1 = wh1[NR::Y]/wh1[NR::X]; + double stretch2 = wh2[NR::Y]/wh2[NR::X]; + + if ((stretch1 > 1.5 || stretch1 < 0.66) && (stretch2 > 1.5 || stretch2 < 0.66)) { + + std::vector dists; + dists.push_back (dist_r); + + // If both objects are not circle-like, find dists between four corners + std::vector c1_points(2); + { + double y_closest; + if (c2[NR::Y] > c1[NR::Y] + wh1[NR::Y]/2) { + y_closest = c1[NR::Y] + wh1[NR::Y]/2; + } else if (c2[NR::Y] < c1[NR::Y] - wh1[NR::Y]/2) { + y_closest = c1[NR::Y] - wh1[NR::Y]/2; + } else { + y_closest = c2[NR::Y]; + } + c1_points[0] = NR::Point (c1[NR::X], y_closest); + double x_closest; + if (c2[NR::X] > c1[NR::X] + wh1[NR::X]/2) { + x_closest = c1[NR::X] + wh1[NR::X]/2; + } else if (c2[NR::X] < c1[NR::X] - wh1[NR::X]/2) { + x_closest = c1[NR::X] - wh1[NR::X]/2; + } else { + x_closest = c2[NR::X]; + } + c1_points[1] = NR::Point (x_closest, c1[NR::Y]); + } + + + std::vector c2_points(2); + { + double y_closest; + if (c1[NR::Y] > c2[NR::Y] + wh2[NR::Y]/2) { + y_closest = c2[NR::Y] + wh2[NR::Y]/2; + } else if (c1[NR::Y] < c2[NR::Y] - wh2[NR::Y]/2) { + y_closest = c2[NR::Y] - wh2[NR::Y]/2; + } else { + y_closest = c1[NR::Y]; + } + c2_points[0] = NR::Point (c2[NR::X], y_closest); + double x_closest; + if (c1[NR::X] > c2[NR::X] + wh2[NR::X]/2) { + x_closest = c2[NR::X] + wh2[NR::X]/2; + } else if (c1[NR::X] < c2[NR::X] - wh2[NR::X]/2) { + x_closest = c2[NR::X] - wh2[NR::X]/2; + } else { + x_closest = c1[NR::X]; + } + c2_points[1] = NR::Point (x_closest, c2[NR::Y]); + } + + for (int i = 0; i < 2; i ++) { + for (int j = 0; j < 2; j ++) { + dists.push_back (NR::L2 (c1_points[i] - c2_points[j])); + } + } + + // return the minimum of all dists + return *std::min_element(dists.begin(), dists.end()); + } else { + return dist_r; + } +} + +/** +Average unclump_dist from item to others +*/ +double unclump_average (SPItem *item, GSList *others) +{ + int n = 0; + double sum = 0; + + for (GSList *i = others; i != NULL; i = i->next) { + SPItem *other = SP_ITEM (i->data); + + if (other == item) + continue; + + n++; + sum += unclump_dist (item, other); + } + + if (n != 0) + return sum/n; + else + return 0; +} + +/** +Closest to item among others + */ +SPItem *unclump_closest (SPItem *item, GSList *others) +{ + double min = HUGE_VAL; + SPItem *closest = NULL; + + for (GSList *i = others; i != NULL; i = i->next) { + SPItem *other = SP_ITEM (i->data); + + if (other == item) + continue; + + double dist = unclump_dist (item, other); + if (dist < min && fabs (dist) < 1e6) { + min = dist; + closest = other; + } + } + + return closest; +} + +/** +Most distant from item among others + */ +SPItem *unclump_farest (SPItem *item, GSList *others) +{ + double max = -HUGE_VAL; + SPItem *farest = NULL; + + for (GSList *i = others; i != NULL; i = i->next) { + SPItem *other = SP_ITEM (i->data); + + if (other == item) + continue; + + double dist = unclump_dist (item, other); + if (dist > max && fabs (dist) < 1e6) { + max = dist; + farest = other; + } + } + + return farest; +} + +/** +Removes from the \a rest list those items that are "behind" \a closest as seen from \a item, +i.e. those on the other side of the line through \a closest perpendicular to the direction from \a +item to \a closest. Returns a newly created list which must be freed. + */ +GSList * +unclump_remove_behind (SPItem *item, SPItem *closest, GSList *rest) +{ + NR::Point it = unclump_center (item); + NR::Point p1 = unclump_center (closest); + + // perpendicular through closest to the direction to item: + NR::Point perp = NR::rot90(it - p1); + NR::Point p2 = p1 + perp; + + // get the standard Ax + By + C = 0 form for p1-p2: + double A = p1[NR::Y] - p2[NR::Y]; + double B = p2[NR::X] - p1[NR::X]; + double C = p2[NR::Y] * p1[NR::X] - p1[NR::Y] * p2[NR::X]; + + // substitute the item into it: + double val_item = A * it[NR::X] + B * it[NR::Y] + C; + + GSList *out = NULL; + + for (GSList *i = rest; i != NULL; i = i->next) { + SPItem *other = SP_ITEM (i->data); + + if (other == item) + continue; + + NR::Point o = unclump_center (other); + double val_other = A * o[NR::X] + B * o[NR::Y] + C; + + if (val_item * val_other <= 1e-6) { + // different signs, which means item and other are on the different sides of p1-p2 line; skip + } else { + out = g_slist_prepend (out, other); + } + } + + return out; +} + +/** +Moves \a what away from \a from by \a dist + */ +void +unclump_push (SPItem *from, SPItem *what, double dist) +{ + NR::Point it = unclump_center (what); + NR::Point p = unclump_center (from); + NR::Point by = dist * NR::unit_vector (- (p - it)); + + NR::Matrix move = NR::Matrix (NR::translate (by)); + + std::map::iterator i = c_cache.find(SP_OBJECT_ID(what)); + if ( i != c_cache.end() ) { + i->second *= move; + } + + //g_print ("push %s at %g,%g from %g,%g by %g,%g, dist %g\n", SP_OBJECT_ID(what), it[NR::X],it[NR::Y], p[NR::X],p[NR::Y], by[NR::X],by[NR::Y], dist); + + sp_item_set_i2d_affine(what, sp_item_i2d_affine(what) * move); + sp_item_write_transform(what, SP_OBJECT_REPR(what), what->transform, NULL); +} + +/** +Moves \a what towards \a to by \a dist + */ +void +unclump_pull (SPItem *to, SPItem *what, double dist) +{ + NR::Point it = unclump_center (what); + NR::Point p = unclump_center (to); + NR::Point by = dist * NR::unit_vector (p - it); + + NR::Matrix move = NR::Matrix (NR::translate (by)); + + std::map::iterator i = c_cache.find(SP_OBJECT_ID(what)); + if ( i != c_cache.end() ) { + i->second *= move; + } + + //g_print ("pull %s at %g,%g to %g,%g by %g,%g, dist %g\n", SP_OBJECT_ID(what), it[NR::X],it[NR::Y], p[NR::X],p[NR::Y], by[NR::X],by[NR::Y], dist); + + sp_item_set_i2d_affine(what, sp_item_i2d_affine(what) * move); + sp_item_write_transform(what, SP_OBJECT_REPR(what), what->transform, NULL); +} + + +/** +Unclumps the items in \a items, reducing local unevenness in their distribution. Produces an effect +similar to "engraver dots". The only distribution which is unchanged by unclumping is a hexagonal +grid. May be called repeatedly for stronger effect. + */ +void +unclump (GSList *items) +{ + c_cache.clear(); + wh_cache.clear(); + + for (GSList *i = items; i != NULL; i = i->next) { // for each original/clone x: + SPItem *item = SP_ITEM (i->data); + + GSList *nei = NULL; + + GSList *rest = g_slist_copy (items); + rest = g_slist_remove (rest, item); + + while (rest != NULL) { + SPItem *closest = unclump_closest (item, rest); + if (closest) { + nei = g_slist_prepend (nei, closest); + rest = g_slist_remove (rest, closest); + GSList *new_rest = unclump_remove_behind (item, closest, rest); + g_slist_free (rest); + rest = new_rest; + } else { + g_slist_free (rest); + break; + } + } + + if (g_slist_length (nei) >= 2) { + double ave = unclump_average (item, nei); + + SPItem *closest = unclump_closest (item, nei); + SPItem *farest = unclump_farest (item, nei); + + double dist_closest = unclump_dist (closest, item); + double dist_farest = unclump_dist (farest, item); + + //g_print ("NEI %d for item %s closest %s at %g farest %s at %g ave %g\n", g_slist_length(nei), SP_OBJECT_ID(item), SP_OBJECT_ID(closest), dist_closest, SP_OBJECT_ID(farest), dist_farest, ave); + + if (fabs (ave) < 1e6 && fabs (dist_closest) < 1e6 && fabs (dist_farest) < 1e6) { // otherwise the items are bogus + // increase these coefficients to make unclumping more aggressive and less stable + // the pull coefficient is a bit bigger to counteract the long-term expansion trend + unclump_push (closest, item, 0.3 * (ave - dist_closest)); + unclump_pull (farest, item, 0.35 * (dist_farest - ave)); + } + } + } +} diff --git a/src/dialogs/unclump.h b/src/dialogs/unclump.h new file mode 100644 index 000000000..6cebc0caf --- /dev/null +++ b/src/dialogs/unclump.h @@ -0,0 +1,20 @@ +#ifndef UNCLUMP_H_SEEN +#define UNCLUMP_H_SEEN + +/** \file + * Unclumping objects + */ +/* + * Authors: + * bulia byak + * + * Copyright (C) 2005 Authors + * Released under GNU GPL + */ + +#include + +void unclump(GSList *items); + + +#endif /* !UNCLUMP_H_SEEN */ diff --git a/src/dialogs/xml-tree.cpp b/src/dialogs/xml-tree.cpp new file mode 100644 index 000000000..079a09b5a --- /dev/null +++ b/src/dialogs/xml-tree.cpp @@ -0,0 +1,1575 @@ +#define __SP_XMLVIEW_TREE_C__ + +/** + * \brief XML View + * + * Authors: + * Lauris Kaplinski + * MenTaLguY + * bulia byak + * + * Copyright (C) 1999-2005 Authors + * Copyright (C) 2004 David Turner + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "helper/window.h" +#include "macros.h" +#include "../inkscape.h" +#include "../document.h" +#include "../desktop-handles.h" +#include "desktop.h" +#include "../selection.h" +#include "../sp-string.h" +#include "../sp-tspan.h" +#include "../sp-root.h" +#include "../event-context.h" +#include "in-dt-coordsys.h" + + +#include "../widgets/sp-xmlview-tree.h" +#include "../widgets/sp-xmlview-content.h" +#include "../widgets/sp-xmlview-attr-list.h" + +#include "../inkscape-stock.h" +#include "widgets/icon.h" + +#include "dialog-events.h" +#include "../prefs-utils.h" +#include "../verbs.h" +#include "../interface.h" + +#include "shortcuts.h" +#include + +#include "message-stack.h" +#include "message-context.h" + +struct EditableDest { + GtkEditable *editable; + gchar *text; +}; + +static GtkWidget *dlg = NULL; +static sigc::connection sel_changed_connection; +static sigc::connection document_uri_set_connection; +static sigc::connection document_replaced_connection; +static win_data wd; +// impossible original values to make sure they are read from prefs +static gint x = -1000, y = -1000, w = 0, h = 0; +static gchar *prefs_path = "dialogs.xml"; +static GtkWidget *status = NULL; +static Inkscape::MessageStack *_message_stack = NULL; +static Inkscape::MessageContext *_message_context = NULL; +static sigc::connection _message_changed_connection; + +static GtkTooltips *tooltips = NULL; +static GtkEditable *attr_name = NULL; +static GtkTextView *attr_value = NULL; +static SPXMLViewTree *tree = NULL; +static SPXMLViewAttrList *attributes = NULL; +static SPXMLViewContent *content = NULL; + +static gint blocked = 0; +static SPDesktop *current_desktop = NULL; +static SPDocument *current_document = NULL; +static gint selected_attr = 0; +static Inkscape::XML::Node *selected_repr = NULL; + +static void sp_xmltree_desktop_change( Inkscape::Application *inkscape, SPDesktop *desktop, GtkWidget *dialog ); + +static void set_tree_desktop(SPDesktop *desktop); +static void set_tree_document(SPDocument *document); +static void set_tree_repr(Inkscape::XML::Node *repr); + +static void set_tree_select(Inkscape::XML::Node *repr); +static void propagate_tree_select(Inkscape::XML::Node *repr); + +static Inkscape::XML::Node *get_dt_select(); +static void set_dt_select(Inkscape::XML::Node *repr); + +static void on_tree_select_row(GtkCTree *tree, GtkCTreeNode *node, gint column, gpointer data); +static void on_tree_unselect_row(GtkCTree *tree, GtkCTreeNode *node, gint column, gpointer data); +static void after_tree_move(GtkCTree *tree, GtkCTreeNode *node, GtkCTreeNode *new_parent, GtkCTreeNode *new_sibling, gpointer data); +static void on_destroy(GtkObject *object, gpointer data); +static gboolean on_delete(GtkObject *object, GdkEvent *event, gpointer data); + +static void on_tree_select_row_enable_if_element(GtkCTree *tree, GtkCTreeNode *node, gint column, gpointer data); +static void on_tree_select_row_enable_if_mutable(GtkCTree *tree, GtkCTreeNode *node, gint column, gpointer data); +static void on_tree_select_row_show_if_element(GtkCTree *tree, GtkCTreeNode *node, gint column, gpointer data); +static void on_tree_select_row_show_if_text(GtkCTree *tree, GtkCTreeNode *node, gint column, gpointer data); +static void on_tree_select_row_enable_if_indentable(GtkCTree *tree, GtkCTreeNode *node, gint column, gpointer data); +static void on_tree_select_row_enable_if_not_first_child(GtkCTree *tree, GtkCTreeNode *node, gint column, gpointer data); +static void on_tree_select_row_enable_if_not_last_child(GtkCTree *tree, GtkCTreeNode *node, gint column, gpointer data); +static void on_tree_select_row_enable_if_has_grandparent(GtkCTree *tree, GtkCTreeNode *node, gint column, gpointer data); + +static void on_tree_unselect_row_clear_text(GtkCTree *tree, GtkCTreeNode *node, gint column, gpointer data); +static void on_tree_unselect_row_disable(GtkCTree *tree, GtkCTreeNode *node, gint column, gpointer data); +static void on_tree_unselect_row_hide(GtkCTree *tree, GtkCTreeNode *node, gint column, gpointer data); + +static void on_attr_select_row(GtkCList *list, gint row, gint column, GdkEventButton *event, gpointer data); +static void on_attr_unselect_row(GtkCList *list, gint row, gint column, GdkEventButton *event, gpointer data); +static void on_attr_row_changed( GtkCList *list, gint row, gpointer data ); + +static void on_attr_select_row_enable(GtkCList *list, gint row, gint column, GdkEventButton *event, gpointer data); +static void on_attr_unselect_row_disable(GtkCList *list, gint row, gint column, GdkEventButton *event, gpointer data); + +static void on_attr_select_row_set_name_content(GtkCList *list, gint row, gint column, GdkEventButton *event, gpointer data); +static void on_attr_select_row_set_value_content(GtkCList *list, gint row, gint column, GdkEventButton *event, gpointer data); +static void on_attr_unselect_row_clear_text(GtkCList *list, gint row, gint column, GdkEventButton *event, gpointer data); + +static void on_editable_changed_enable_if_valid_xml_name(GtkEditable *editable, gpointer data); + +static void on_desktop_selection_changed(Inkscape::Selection *selection); +static void on_document_replaced(SPDesktop *dt, SPDocument *document); +static void on_document_uri_set(gchar const *uri, SPDocument *document); + +static void on_clicked_get_editable_text(GtkWidget *widget, gpointer data); + +static void _set_status_message(Inkscape::MessageType type, const gchar *message, GtkWidget *dialog); + +static void cmd_new_element_node(GtkObject *object, gpointer data); +static void cmd_new_text_node(GtkObject *object, gpointer data); +static void cmd_duplicate_node(GtkObject *object, gpointer data); +static void cmd_delete_node(GtkObject *object, gpointer data); + +static void cmd_raise_node(GtkObject *object, gpointer data); +static void cmd_lower_node(GtkObject *object, gpointer data); +static void cmd_indent_node(GtkObject *object, gpointer data); +static void cmd_unindent_node(GtkObject *object, gpointer data); + +static void cmd_delete_attr(GtkObject *object, gpointer data); +static void cmd_set_attr(GtkObject *object, gpointer data); + +static gboolean sp_xml_tree_key_press(GtkWidget *widget, GdkEventKey *event); + + +/* + * \brief Sets the XML status bar when the tree is selected. + */ +void tree_reset_context() +{ + _message_context->set(Inkscape::NORMAL_MESSAGE, + _("Click to select nodes, drag to rearrange.")); +} + + +/* + * \brief Sets the XML status bar, depending on which attr is selected. + */ +void attr_reset_context(gint attr) +{ + if (attr == 0) { + _message_context->set(Inkscape::NORMAL_MESSAGE, + _("Click attribute to edit.")); + } + else { + const gchar *name = g_quark_to_string(attr); + gchar *message = g_strdup_printf(_("Attribute %s selected. Press Ctrl+Enter when done editing to commit changes."), name); + _message_context->set(Inkscape::NORMAL_MESSAGE, message); + g_free(message); + } +} + + +void sp_xml_tree_dialog() +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + + if (!desktop) { + return; + } + + if (dlg == NULL) + { // very long block + + GtkWidget *box, *sw, *paned, *toolbar, *button; + GtkWidget *text_container, *attr_container, *attr_subpaned_container, *box2; + GtkWidget *set_attr; + + tooltips = gtk_tooltips_new(); + gtk_tooltips_enable(tooltips); + + dlg = sp_window_new("", TRUE); + if (x == -1000 || y == -1000) { + x = prefs_get_int_attribute(prefs_path, "x", 0); + y = prefs_get_int_attribute(prefs_path, "y", 0); + } + if (w ==0 || h == 0) { + w = prefs_get_int_attribute(prefs_path, "w", 0); + h = prefs_get_int_attribute(prefs_path, "h", 0); + } + if (x != 0 || y != 0) { + gtk_window_move((GtkWindow *) dlg, x, y); + } else { + gtk_window_set_position(GTK_WINDOW(dlg), GTK_WIN_POS_CENTER); + } + + if (w && h) { + gtk_window_resize((GtkWindow *) dlg, w, h); + } + sp_transientize(dlg); + wd.win = dlg; + wd.stop = 0; + g_signal_connect ( G_OBJECT(INKSCAPE), "activate_desktop", G_CALLBACK(sp_transientize_callback), &wd ); + + gtk_signal_connect( GTK_OBJECT(dlg), "event", GTK_SIGNAL_FUNC(sp_dialog_event_handler), dlg ); + + gtk_signal_connect( GTK_OBJECT(dlg), "destroy", G_CALLBACK(on_destroy), dlg); + gtk_signal_connect( GTK_OBJECT(dlg), "delete_event", G_CALLBACK(on_delete), dlg); + g_signal_connect ( G_OBJECT(INKSCAPE), "shut_down", G_CALLBACK(on_delete), dlg); + + g_signal_connect ( G_OBJECT(INKSCAPE), "dialogs_hide", G_CALLBACK(sp_dialog_hide), dlg); + g_signal_connect ( G_OBJECT(INKSCAPE), "dialogs_unhide", G_CALLBACK(sp_dialog_unhide), dlg); + + + gtk_container_set_border_width(GTK_CONTAINER(dlg), 0); + gtk_window_set_default_size(GTK_WINDOW(dlg), 640, 384); + + GtkWidget *vbox = gtk_vbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(dlg), vbox); + + GtkWidget *hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_end(GTK_BOX(vbox), hbox, FALSE, FALSE, 2); + + status = gtk_label_new(NULL); + gtk_misc_set_alignment(GTK_MISC(status), 0.0, 0.5); + gtk_widget_set_size_request(status, 1, -1); + gtk_label_set_markup(GTK_LABEL(status), ""); + gtk_box_pack_start(GTK_BOX(hbox), gtk_hbox_new(FALSE, 0), FALSE, FALSE, 4); + gtk_box_pack_start(GTK_BOX(hbox), status, TRUE, TRUE, 0); + + paned = gtk_hpaned_new(); + gtk_paned_set_position(GTK_PANED(paned), 256); + gtk_box_pack_start(GTK_BOX(vbox), paned, TRUE, TRUE, 0); + + _message_stack = new Inkscape::MessageStack(); + _message_context = new Inkscape::MessageContext(_message_stack); + _message_changed_connection = _message_stack->connectChanged( + sigc::bind(sigc::ptr_fun(_set_status_message), dlg) + ); + + /* tree view */ + + box = gtk_vbox_new(FALSE, 0); + gtk_paned_pack1(GTK_PANED(paned), box, FALSE, FALSE); + + tree = SP_XMLVIEW_TREE(sp_xmlview_tree_new(NULL, NULL, NULL)); + gtk_tooltips_set_tip( tooltips, GTK_WIDGET(tree), + _("Drag to reorder nodes"), NULL ); + + g_signal_connect( G_OBJECT(tree), "tree_select_row", + G_CALLBACK(on_tree_select_row), NULL ); + + g_signal_connect( G_OBJECT(tree), "tree_unselect_row", + G_CALLBACK(on_tree_unselect_row), NULL ); + + g_signal_connect_after( G_OBJECT(tree), "tree_move", + G_CALLBACK(after_tree_move), NULL); + + /* TODO: replace gtk_signal_connect_while_alive() with something + * else... + */ + toolbar = gtk_toolbar_new(); + gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS); + gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0); + + button = gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), + NULL, + _("New element node"), + NULL, + sp_icon_new( GTK_ICON_SIZE_LARGE_TOOLBAR, + INKSCAPE_STOCK_ADD_XML_ELEMENT_NODE ), + G_CALLBACK(cmd_new_element_node), + NULL); + + gtk_signal_connect_while_alive( GTK_OBJECT(tree), + "tree_select_row", + G_CALLBACK(on_tree_select_row_enable_if_element), + button, + GTK_OBJECT(button)); + + gtk_signal_connect_while_alive(GTK_OBJECT(tree), + "tree_unselect_row", + G_CALLBACK(on_tree_unselect_row_disable), + button, + GTK_OBJECT(button)); + + gtk_widget_set_sensitive(GTK_WIDGET(button), FALSE); + + button = gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), + NULL, _("New text node"), NULL, + sp_icon_new( GTK_ICON_SIZE_LARGE_TOOLBAR, + INKSCAPE_STOCK_ADD_XML_TEXT_NODE ), + G_CALLBACK(cmd_new_text_node), + NULL); + + gtk_signal_connect_while_alive(GTK_OBJECT(tree), + "tree_select_row", + G_CALLBACK(on_tree_select_row_enable_if_element), + button, + GTK_OBJECT(button)); + + gtk_signal_connect_while_alive(GTK_OBJECT(tree), + "tree_unselect_row", + G_CALLBACK(on_tree_unselect_row_disable), + button, + GTK_OBJECT(button)); + + gtk_widget_set_sensitive(GTK_WIDGET(button), FALSE); + + button = gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), + NULL, _("Duplicate node"), NULL, + sp_icon_new( GTK_ICON_SIZE_LARGE_TOOLBAR, + INKSCAPE_STOCK_DUPLICATE_XML_NODE ), + G_CALLBACK(cmd_duplicate_node), + NULL); + + gtk_signal_connect_while_alive(GTK_OBJECT(tree), + "tree_select_row", + G_CALLBACK(on_tree_select_row_enable_if_mutable), + button, + GTK_OBJECT(button)); + + gtk_signal_connect_while_alive(GTK_OBJECT(tree), "tree_unselect_row", + G_CALLBACK(on_tree_unselect_row_disable), + button, GTK_OBJECT(button)); + + gtk_widget_set_sensitive(GTK_WIDGET(button), FALSE); + + gtk_toolbar_append_space(GTK_TOOLBAR(toolbar)); + + button = gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), + NULL, _("Delete node"), NULL, + sp_icon_new( GTK_ICON_SIZE_LARGE_TOOLBAR, + INKSCAPE_STOCK_DELETE_XML_NODE ), + G_CALLBACK(cmd_delete_node), NULL ); + + gtk_signal_connect_while_alive(GTK_OBJECT(tree), "tree_select_row", + G_CALLBACK(on_tree_select_row_enable_if_mutable), + button, GTK_OBJECT(button)); + gtk_signal_connect_while_alive(GTK_OBJECT(tree), "tree_unselect_row", + G_CALLBACK(on_tree_unselect_row_disable), + button, GTK_OBJECT(button)); + gtk_widget_set_sensitive(GTK_WIDGET(button), FALSE); + + gtk_toolbar_append_space(GTK_TOOLBAR(toolbar)); + + button = gtk_toolbar_append_item( GTK_TOOLBAR(toolbar), "<", + _("Unindent node"), NULL, + gtk_arrow_new(GTK_ARROW_LEFT, GTK_SHADOW_IN), + G_CALLBACK(cmd_unindent_node), NULL); + + gtk_signal_connect_while_alive(GTK_OBJECT(tree), "tree_select_row", + G_CALLBACK(on_tree_select_row_enable_if_has_grandparent), + button, GTK_OBJECT(button)); + + gtk_signal_connect_while_alive(GTK_OBJECT(tree), "tree_unselect_row", + G_CALLBACK(on_tree_unselect_row_disable), + button, GTK_OBJECT(button)); + + gtk_widget_set_sensitive(GTK_WIDGET(button), FALSE); + + button = gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), ">", + _("Indent node"), NULL, + gtk_arrow_new(GTK_ARROW_RIGHT, GTK_SHADOW_IN), + G_CALLBACK(cmd_indent_node), NULL); + gtk_signal_connect_while_alive(GTK_OBJECT(tree), "tree_select_row", + G_CALLBACK(on_tree_select_row_enable_if_indentable), + button, GTK_OBJECT(button)); + gtk_signal_connect_while_alive(GTK_OBJECT(tree), "tree_unselect_row", + (GCallback) on_tree_unselect_row_disable, + button, GTK_OBJECT(button)); + gtk_widget_set_sensitive(GTK_WIDGET(button), FALSE); + + button = gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), "^", + _("Raise node"), NULL, + gtk_arrow_new(GTK_ARROW_UP, GTK_SHADOW_IN), + G_CALLBACK(cmd_raise_node), NULL); + gtk_signal_connect_while_alive(GTK_OBJECT(tree), "tree_select_row", + G_CALLBACK(on_tree_select_row_enable_if_not_first_child), + button, GTK_OBJECT(button)); + gtk_signal_connect_while_alive(GTK_OBJECT(tree), "tree_unselect_row", + G_CALLBACK(on_tree_unselect_row_disable), + button, GTK_OBJECT(button)); + gtk_widget_set_sensitive(GTK_WIDGET(button), FALSE); + + button = gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), "v", + _("Lower node"), NULL, + gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_IN), + G_CALLBACK(cmd_lower_node), NULL); + gtk_signal_connect_while_alive(GTK_OBJECT(tree), "tree_select_row", + G_CALLBACK(on_tree_select_row_enable_if_not_last_child), + button, GTK_OBJECT(button)); + gtk_signal_connect_while_alive(GTK_OBJECT(tree), "tree_unselect_row", + G_CALLBACK(on_tree_unselect_row_disable), + button, GTK_OBJECT(button)); + gtk_widget_set_sensitive(GTK_WIDGET(button), FALSE); + + gtk_box_pack_start(GTK_BOX(box), toolbar, FALSE, TRUE, 0); + + sw = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(sw), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC ); + gtk_box_pack_start(GTK_BOX(box), sw, TRUE, TRUE, 0); + + gtk_container_add(GTK_CONTAINER(sw), GTK_WIDGET(tree)); + + /* node view */ + + box = gtk_vbox_new(FALSE, 0); + gtk_paned_pack2(GTK_PANED(paned), box, TRUE, TRUE); + + /* attributes */ + + attr_container = gtk_vbox_new(FALSE, 0); + gtk_box_pack_start( GTK_BOX(box), GTK_WIDGET(attr_container), + TRUE, TRUE, 0 ); + + attributes = SP_XMLVIEW_ATTR_LIST(sp_xmlview_attr_list_new(NULL)); + g_signal_connect( G_OBJECT(attributes), "select_row", + G_CALLBACK(on_attr_select_row), NULL); + g_signal_connect( G_OBJECT(attributes), "unselect_row", + G_CALLBACK(on_attr_unselect_row), NULL); + g_signal_connect( G_OBJECT(attributes), "row-value-changed", + G_CALLBACK(on_attr_row_changed), NULL); + + toolbar = gtk_toolbar_new(); + gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS); + gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0); + + button = gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), + NULL, _("Delete attribute"), NULL, + sp_icon_new( GTK_ICON_SIZE_LARGE_TOOLBAR, + INKSCAPE_STOCK_DELETE_XML_ATTRIBUTE ), + (GCallback) cmd_delete_attr, NULL); + + gtk_signal_connect_while_alive(GTK_OBJECT(attributes), "select_row", + (GCallback) on_attr_select_row_enable, button, + GTK_OBJECT(button)); + + gtk_signal_connect_while_alive(GTK_OBJECT(attributes), "unselect_row", + (GCallback) on_attr_unselect_row_disable, button, + GTK_OBJECT(button)); + + gtk_signal_connect_while_alive(GTK_OBJECT(tree), "tree_unselect_row", + (GCallback) on_tree_unselect_row_disable, button, + GTK_OBJECT(button)); + + gtk_widget_set_sensitive(GTK_WIDGET(button), FALSE); + + gtk_box_pack_start( GTK_BOX(attr_container), + GTK_WIDGET(toolbar), FALSE, TRUE, 0 ); + + attr_subpaned_container = gtk_vpaned_new(); + gtk_box_pack_start( GTK_BOX(attr_container), + GTK_WIDGET(attr_subpaned_container), + TRUE, TRUE, 0 ); + gtk_widget_show(attr_subpaned_container); + + sw = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_paned_pack1( GTK_PANED(attr_subpaned_container), + GTK_WIDGET(sw), TRUE, TRUE ); + gtk_container_add(GTK_CONTAINER(sw), GTK_WIDGET(attributes)); + + toolbar = gtk_vbox_new(FALSE, 4); + gtk_container_set_border_width(GTK_CONTAINER(toolbar), 4); + + box2 = gtk_hbox_new(FALSE, 4); + gtk_box_pack_start( GTK_BOX(toolbar), GTK_WIDGET(box2), + FALSE, TRUE, 0); + + attr_name = GTK_EDITABLE(gtk_entry_new()); + gtk_tooltips_set_tip( tooltips, GTK_WIDGET(attr_name), + // TRANSLATORS: "Attribute" is a noun here + _("Attribute name"), NULL ); + + gtk_signal_connect( GTK_OBJECT(attributes), "select_row", + (GCallback) on_attr_select_row_set_name_content, + attr_name); + + gtk_signal_connect( GTK_OBJECT(attributes), "unselect_row", + (GCallback) on_attr_unselect_row_clear_text, + attr_name); + + gtk_signal_connect( GTK_OBJECT(tree), "tree_unselect_row", + (GCallback) on_tree_unselect_row_clear_text, + attr_name); + + gtk_box_pack_start( GTK_BOX(box2), GTK_WIDGET(attr_name), + TRUE, TRUE, 0); + + set_attr = gtk_button_new(); + gtk_tooltips_set_tip( tooltips, GTK_WIDGET(set_attr), + // TRANSLATORS: "Set" is a verb here + _("Set attribute"), NULL ); + // TRANSLATORS: "Set" is a verb here + GtkWidget *set_label = gtk_label_new(_("Set")); + gtk_container_add(GTK_CONTAINER(set_attr), set_label); + + gtk_signal_connect( GTK_OBJECT(set_attr), "clicked", + (GCallback) cmd_set_attr, NULL); + gtk_signal_connect( GTK_OBJECT(attr_name), "changed", + (GCallback) on_editable_changed_enable_if_valid_xml_name, + set_attr ); + gtk_widget_set_sensitive(GTK_WIDGET(set_attr), FALSE); + + gtk_box_pack_start(GTK_BOX(box2), set_attr, FALSE, FALSE, 0); + + sw = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(sw), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC ); + gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_IN ); + gtk_box_pack_start(GTK_BOX(toolbar), sw, TRUE, TRUE, 0); + + attr_value =(GtkTextView *) gtk_text_view_new(); + gtk_text_view_set_wrap_mode((GtkTextView *) attr_value, GTK_WRAP_CHAR); + gtk_tooltips_set_tip( tooltips, GTK_WIDGET(attr_value), + // TRANSLATORS: "Attribute" is a noun here + _("Attribute value"), NULL ); + gtk_signal_connect( GTK_OBJECT(attributes), "select_row", + (GCallback) on_attr_select_row_set_value_content, + attr_value ); + gtk_signal_connect( GTK_OBJECT(attributes), "unselect_row", + (GCallback) on_attr_unselect_row_clear_text, + attr_value ); + gtk_signal_connect( GTK_OBJECT(tree), "tree_unselect_row", + (GCallback) on_tree_unselect_row_clear_text, + attr_value ); + gtk_text_view_set_editable(attr_value, TRUE); + gtk_container_add( GTK_CONTAINER(sw), + GTK_WIDGET(attr_value) ); + + gtk_paned_pack2( GTK_PANED(attr_subpaned_container), + GTK_WIDGET(toolbar), FALSE, TRUE ); + + /* text */ + + sw = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(sw), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC ); + gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_IN ); + gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(sw), TRUE, TRUE, 0); + + content = SP_XMLVIEW_CONTENT(sp_xmlview_content_new(NULL)); + gtk_container_add(GTK_CONTAINER(sw), GTK_WIDGET(content)); + + text_container = sw; + + /* initial show/hide */ + + gtk_widget_show_all(GTK_WIDGET(dlg)); + + gtk_signal_connect_while_alive(GTK_OBJECT(tree), "tree_select_row", + (GCallback) on_tree_select_row_show_if_element, + attr_container, GTK_OBJECT(attr_container)); + + gtk_signal_connect_while_alive(GTK_OBJECT(tree), "tree_unselect_row", + (GCallback) on_tree_unselect_row_hide, + attr_container, GTK_OBJECT(attr_container)); + + gtk_widget_hide(attr_container); + + gtk_signal_connect_while_alive(GTK_OBJECT(tree), "tree_select_row", + (GCallback) on_tree_select_row_show_if_text, + text_container, GTK_OBJECT(text_container)); + + gtk_signal_connect_while_alive(GTK_OBJECT(tree), "tree_unselect_row", + (GCallback) on_tree_unselect_row_hide, + text_container, GTK_OBJECT(text_container)); + + gtk_widget_hide(text_container); + + g_signal_connect( G_OBJECT(INKSCAPE), "activate_desktop", + G_CALLBACK(sp_xmltree_desktop_change), dlg); + + g_signal_connect( G_OBJECT(INKSCAPE), "deactivate_desktop", + G_CALLBACK(sp_xmltree_desktop_change), dlg); + + g_signal_connect((GObject *) dlg, "key_press_event", (GCallback) sp_xml_tree_key_press, NULL); + + tree_reset_context(); + } // end of if (dlg == NULL) + + gtk_window_present((GtkWindow *) dlg); + + g_assert(desktop != NULL); + set_tree_desktop(desktop); + +} // end of sp_xml_tree_dialog() + +static gboolean sp_xml_tree_key_press(GtkWidget *widget, GdkEventKey *event) +{ + + unsigned int shortcut = get_group0_keyval(event) | + ( event->state & GDK_SHIFT_MASK ? + SP_SHORTCUT_SHIFT_MASK : 0 ) | + ( event->state & GDK_CONTROL_MASK ? + SP_SHORTCUT_CONTROL_MASK : 0 ) | + ( event->state & GDK_MOD1_MASK ? + SP_SHORTCUT_ALT_MASK : 0 ); + + /* fixme: if you need to add more xml-tree-specific callbacks, you should probably upgrade + * the sp_shortcut mechanism to take into account windows. */ + if (shortcut == (SP_SHORTCUT_CONTROL_MASK | GDK_Return)) { + cmd_set_attr(NULL, NULL); + return true; + } + return false; +} + + +static void sp_xmltree_desktop_change(Inkscape::Application *inkscape, + SPDesktop *desktop, + GtkWidget *dialog ) +{ + if (!desktop) { + return; + } + set_tree_desktop(desktop); +} + + +void set_tree_desktop(SPDesktop *desktop) +{ + if ( desktop == current_desktop ) { + return; + } + + if (current_desktop) { + sel_changed_connection.disconnect(); + document_replaced_connection.disconnect(); + } + current_desktop = desktop; + if (desktop) { + sel_changed_connection = SP_DT_SELECTION(desktop)->connectChanged(&on_desktop_selection_changed); + document_replaced_connection = desktop->connectDocumentReplaced(&on_document_replaced); + set_tree_document(SP_DT_DOCUMENT(desktop)); + } else { + set_tree_document(NULL); + } + +} // end of set_tree_desktop() + + + +void set_tree_document(SPDocument *document) +{ + if (document == current_document) { + return; + } + + if (current_document) { + document_uri_set_connection.disconnect(); + } + current_document = document; + if (current_document) { + + document_uri_set_connection = current_document->connectURISet(sigc::bind(sigc::ptr_fun(&on_document_uri_set), current_document)); + on_document_uri_set(SP_DOCUMENT_URI(current_document), current_document); + set_tree_repr(sp_document_repr_root(current_document)); + + } else { + set_tree_repr(NULL); + } +} + + + +void set_tree_repr(Inkscape::XML::Node *repr) +{ + if (repr == selected_repr) { + return; + } + + gtk_clist_freeze(GTK_CLIST(tree)); + + sp_xmlview_tree_set_repr(tree, repr); + + if (repr) { + set_tree_select(get_dt_select()); + } else { + set_tree_select(NULL); + } + + gtk_clist_thaw(GTK_CLIST(tree)); + + propagate_tree_select(selected_repr); + +} + + + +void set_tree_select(Inkscape::XML::Node *repr) +{ + if (selected_repr) { + Inkscape::GC::release(selected_repr); + } + + selected_repr = repr; + if (repr) { + GtkCTreeNode *node; + + Inkscape::GC::anchor(selected_repr); + + node = sp_xmlview_tree_get_repr_node(SP_XMLVIEW_TREE(tree), repr); + if (node) { + GtkCTreeNode *parent; + + gtk_ctree_select(GTK_CTREE(tree), node); + + parent = GTK_CTREE_ROW(node)->parent; + while (parent) { + gtk_ctree_expand(GTK_CTREE(tree), parent); + parent = GTK_CTREE_ROW(parent)->parent; + } + + gtk_ctree_node_moveto(GTK_CTREE(tree), node, 0, 0.66, 0.0); + } + } else { + gtk_clist_unselect_all(GTK_CLIST(tree)); + } + propagate_tree_select(repr); +} + + + +void propagate_tree_select(Inkscape::XML::Node *repr) +{ + if (repr && repr->type() == Inkscape::XML::ELEMENT_NODE) { + sp_xmlview_attr_list_set_repr(attributes, repr); + } else { + sp_xmlview_attr_list_set_repr(attributes, NULL); + } + + if (repr && ( repr->type() == Inkscape::XML::TEXT_NODE || repr->type() == Inkscape::XML::COMMENT_NODE ) ) { + sp_xmlview_content_set_repr(content, repr); + } else { + sp_xmlview_content_set_repr(content, NULL); + } +} + + +Inkscape::XML::Node *get_dt_select() +{ + if (!current_desktop) { + return NULL; + } + + return SP_DT_SELECTION(current_desktop)->singleRepr(); +} + + + +void set_dt_select(Inkscape::XML::Node *repr) +{ + if (!current_desktop) { + return; + } + + Inkscape::Selection *selection = SP_DT_SELECTION(current_desktop); + + SPObject *object; + if (repr) { + while ( ( repr->type() != Inkscape::XML::ELEMENT_NODE ) + && sp_repr_parent(repr) ) + { + repr = sp_repr_parent(repr); + } // end of while loop + + object = SP_DT_DOCUMENT(current_desktop)->getObjectByRepr(repr); + } else { + object = NULL; + } + + blocked++; + if ( object && in_dt_coordsys(*object) + && !( SP_IS_TSPAN(object) || + SP_IS_STRING(object) || + SP_IS_ROOT(object) ) ) + { + /* We cannot set selection to tspan, string, or root; failures and + * crashes will occur. */ + /* TODO: when a tspan is highlighted, set selection to its parent + * text + */ + selection->set(SP_ITEM(object)); + } + blocked--; + +} // end of set_dt_select() + + +void on_tree_select_row(GtkCTree *tree, + GtkCTreeNode *node, + gint column, + gpointer data) +{ + if (blocked) { + return; + } + + Inkscape::XML::Node *repr = sp_xmlview_tree_node_get_repr(SP_XMLVIEW_TREE(tree), node); + g_assert(repr != NULL); + + if (selected_repr == repr) { + return; + } + + if (selected_repr) { + Inkscape::GC::release(selected_repr); + selected_repr = NULL; + } + selected_repr = repr; + Inkscape::GC::anchor(selected_repr); + + propagate_tree_select(selected_repr); + + set_dt_select(selected_repr); + + tree_reset_context(); +} + +void on_tree_unselect_row(GtkCTree *tree, + GtkCTreeNode *node, + gint column, + gpointer data) +{ + if (blocked) { + return; + } + + Inkscape::XML::Node *repr = sp_xmlview_tree_node_get_repr(SP_XMLVIEW_TREE(tree), node); + propagate_tree_select(NULL); + set_dt_select(NULL); + + if (selected_repr && (selected_repr == repr)) { + Inkscape::GC::release(selected_repr); + selected_repr = NULL; + selected_attr = 0; + } +} + + + +void after_tree_move(GtkCTree *tree, + GtkCTreeNode *node, + GtkCTreeNode *new_parent, + GtkCTreeNode *new_sibling, + gpointer data) +{ + if (GTK_CTREE_ROW(node)->parent == new_parent && + GTK_CTREE_ROW(node)->sibling == new_sibling) + { + sp_document_done(current_document); + } else { + sp_document_cancel(current_document); + } +} + + +static void on_destroy(GtkObject *object, gpointer data) +{ + set_tree_desktop(NULL); + gtk_object_destroy(GTK_OBJECT(tooltips)); + tooltips = NULL; + sp_signal_disconnect_by_data(INKSCAPE, dlg); + wd.win = dlg = NULL; + wd.stop = 0; + + _message_changed_connection.disconnect(); + delete _message_context; + _message_context = NULL; + Inkscape::GC::release(_message_stack); + _message_stack = NULL; + _message_changed_connection.~connection(); + + status = NULL; +} + + + +static gboolean on_delete(GtkObject *object, GdkEvent *event, gpointer data) +{ + gtk_window_get_position((GtkWindow *) dlg, &x, &y); + gtk_window_get_size((GtkWindow *) dlg, &w, &h); + + prefs_set_int_attribute(prefs_path, "x", x); + prefs_set_int_attribute(prefs_path, "y", y); + prefs_set_int_attribute(prefs_path, "w", w); + prefs_set_int_attribute(prefs_path, "h", h); + + return FALSE; // which means, go ahead and destroy it +} + + +static void _set_status_message(Inkscape::MessageType type, const gchar *message, GtkWidget *dialog) +{ + if (status) { + gtk_label_set_markup(GTK_LABEL(status), message ? message : ""); + } +} + + +void on_tree_select_row_enable(GtkCTree *tree, + GtkCTreeNode *node, + gint column, + gpointer data) +{ + gtk_widget_set_sensitive(GTK_WIDGET(data), TRUE); +} + + + +void on_tree_select_row_enable_if_element(GtkCTree *tree, + GtkCTreeNode *node, + gint column, + gpointer data ) +{ + Inkscape::XML::Node *repr = sp_xmlview_tree_node_get_repr(SP_XMLVIEW_TREE(tree), node); + + if (repr->type() == Inkscape::XML::ELEMENT_NODE) { + gtk_widget_set_sensitive(GTK_WIDGET(data), TRUE); + } else { + gtk_widget_set_sensitive(GTK_WIDGET(data), FALSE); + } +} + + + +void on_tree_select_row_show_if_element(GtkCTree *tree, GtkCTreeNode *node, + gint column, gpointer data) +{ + Inkscape::XML::Node *repr = sp_xmlview_tree_node_get_repr(SP_XMLVIEW_TREE(tree), node); + + if (repr->type() == Inkscape::XML::ELEMENT_NODE) { + gtk_widget_show(GTK_WIDGET(data)); + } else { + gtk_widget_hide(GTK_WIDGET(data)); + } +} + + + +void on_tree_select_row_show_if_text(GtkCTree *tree, GtkCTreeNode *node, + gint column, gpointer data) +{ + Inkscape::XML::Node *repr = sp_xmlview_tree_node_get_repr(SP_XMLVIEW_TREE(tree), node); + + if ( repr->type() == Inkscape::XML::TEXT_NODE || repr->type() == Inkscape::XML::COMMENT_NODE ) { + gtk_widget_show(GTK_WIDGET(data)); + } else { + gtk_widget_hide(GTK_WIDGET(data)); + } +} + + +gboolean xml_tree_node_mutable(GtkCTreeNode *node) +{ + // top-level is immutable, obviously + if (!GTK_CTREE_ROW(node)->parent) { + return false; + } + + // if not in base level (where namedview, defs, etc go), we're mutable + if (GTK_CTREE_ROW(GTK_CTREE_ROW(node)->parent)->parent) { + return true; + } + + Inkscape::XML::Node *repr; + repr = sp_xmlview_tree_node_get_repr(SP_XMLVIEW_TREE(tree), node); + g_assert(repr); + + // don't let "defs" or "namedview" disappear + if ( !strcmp(repr->name(),"svg:defs") || + !strcmp(repr->name(),"sodipodi:namedview") ) { + return false; + } + + // everyone else is okay, I guess. :) + return true; +} + + +void on_tree_select_row_enable_if_mutable(GtkCTree *tree, GtkCTreeNode *node, + gint column, gpointer data) +{ + gtk_widget_set_sensitive(GTK_WIDGET(data), xml_tree_node_mutable(node)); +} + + + +void on_tree_unselect_row_disable(GtkCTree *tree, GtkCTreeNode *node, + gint column, gpointer data) +{ + gtk_widget_set_sensitive(GTK_WIDGET(data), FALSE); +} + + + +void on_tree_unselect_row_hide(GtkCTree *tree, GtkCTreeNode *node, + gint column, gpointer data) +{ + gtk_widget_hide(GTK_WIDGET(data)); +} + + + +void on_tree_unselect_row_clear_text(GtkCTree *tree, GtkCTreeNode *node, + gint column, gpointer data) +{ + if (GTK_IS_EDITABLE(data)) { + gtk_editable_delete_text(GTK_EDITABLE(data), 0, -1); + } else if (GTK_IS_TEXT_VIEW(data)) { + GtkTextBuffer *tb; + tb = gtk_text_view_get_buffer(GTK_TEXT_VIEW(data)); + gtk_text_buffer_set_text(tb, "", 0); + } +} + + +void on_attr_select_row(GtkCList *list, gint row, gint column, + GdkEventButton *event, gpointer data) +{ + selected_attr = sp_xmlview_attr_list_get_row_key(list, row); + gtk_window_set_focus(GTK_WINDOW(dlg), GTK_WIDGET(attr_value)); + + attr_reset_context(selected_attr); +} + + +void on_attr_unselect_row(GtkCList *list, gint row, gint column, + GdkEventButton *event, gpointer data) +{ + selected_attr = 0; + attr_reset_context(selected_attr); +} + + +void on_attr_row_changed(GtkCList *list, gint row, gpointer data) +{ + gint attr = sp_xmlview_attr_list_get_row_key(list, row); + + if (attr == selected_attr) { + /* if the attr changed, reselect the row in the list to sync + the edit box */ + + /* + // get current attr values + const gchar * name = g_quark_to_string (sp_xmlview_attr_list_get_row_key (list, row)); + const gchar * value = selected_repr->attribute(name); + + g_warning("value: '%s'",value); + + // get the edit box value + GtkTextIter start, end; + gtk_text_buffer_get_bounds ( gtk_text_view_get_buffer (attr_value), + &start, &end ); + gchar * text = gtk_text_buffer_get_text ( gtk_text_view_get_buffer (attr_value), + &start, &end, TRUE ); + g_warning("text: '%s'",text); + + // compare to edit box + if (strcmp(text,value)) { + // issue warning if they're different + _message_stack->flash(Inkscape::WARNING_MESSAGE, + _("Attribute changed in GUI while editing values!")); + } + g_free (text); + + */ + gtk_clist_unselect_row( GTK_CLIST(list), row, 0 ); + gtk_clist_select_row( GTK_CLIST(list), row, 0 ); + } +} + + +void on_attr_select_row_set_name_content(GtkCList *list, gint row, + gint column, GdkEventButton *event, + gpointer data) +{ + GtkEditable *editable = GTK_EDITABLE(data); + const gchar *name = g_quark_to_string(sp_xmlview_attr_list_get_row_key(list, row)); + gtk_editable_delete_text(editable, 0, -1); + gint pos = 0; + gtk_editable_insert_text(editable, name, strlen(name), &pos); +} + + + +void on_attr_select_row_set_value_content(GtkCList *list, gint row, gint column, + GdkEventButton *event, + gpointer data) +{ + GtkTextBuffer *tb = gtk_text_view_get_buffer(GTK_TEXT_VIEW(data)); + const gchar *name = g_quark_to_string(sp_xmlview_attr_list_get_row_key(list, row)); + const gchar *value = selected_repr->attribute(name); + if (!value) { + value = ""; + } + gtk_text_buffer_set_text(tb, value, strlen(value)); +} + + +void on_tree_select_row_enable_if_indentable(GtkCTree *tree, GtkCTreeNode *node, + gint column, gpointer data) +{ + gboolean indentable = FALSE; + + if (xml_tree_node_mutable(node)) { + Inkscape::XML::Node *repr, *prev; + repr = sp_xmlview_tree_node_get_repr(SP_XMLVIEW_TREE(tree), node); + + Inkscape::XML::Node *parent=repr->parent(); + if ( parent && repr != parent->firstChild() ) { + g_assert(parent->firstChild()); + + // skip to the child just before the current repr + for ( prev = parent->firstChild() ; + prev && prev->next() != repr ; + prev = prev->next() ); + + if (prev && prev->type() == Inkscape::XML::ELEMENT_NODE) { + indentable = TRUE; + } + } + } + + gtk_widget_set_sensitive(GTK_WIDGET(data), indentable); +} + + + +void on_tree_select_row_enable_if_not_first_child(GtkCTree *tree, + GtkCTreeNode *node, + gint column, + gpointer data) +{ + Inkscape::XML::Node *repr = sp_xmlview_tree_node_get_repr(SP_XMLVIEW_TREE(tree), node); + + Inkscape::XML::Node *parent=repr->parent(); + if ( parent && repr != parent->firstChild() ) { + gtk_widget_set_sensitive(GTK_WIDGET(data), TRUE); + } else { + gtk_widget_set_sensitive(GTK_WIDGET(data), FALSE); + } +} + + + +void on_tree_select_row_enable_if_not_last_child(GtkCTree *tree, + GtkCTreeNode *node, + gint column, gpointer data) +{ + Inkscape::XML::Node *repr = sp_xmlview_tree_node_get_repr(SP_XMLVIEW_TREE(tree), node); + + Inkscape::XML::Node *parent=repr->parent(); + if ( parent && parent->parent() && repr->next() ) { + gtk_widget_set_sensitive(GTK_WIDGET(data), TRUE); + } else { + gtk_widget_set_sensitive(GTK_WIDGET(data), FALSE); + } +} + + + +void on_tree_select_row_enable_if_has_grandparent(GtkCTree *tree, + GtkCTreeNode *node, + gint column, gpointer data) +{ + GtkCTreeNode *parent = GTK_CTREE_ROW(node)->parent; + + if (parent) { + GtkCTreeNode *grandparent = GTK_CTREE_ROW(parent)->parent; + if (grandparent) { + gtk_widget_set_sensitive(GTK_WIDGET(data), TRUE); + } else { + gtk_widget_set_sensitive(GTK_WIDGET(data), FALSE); + } + } else { + gtk_widget_set_sensitive(GTK_WIDGET(data), FALSE); + } +} + + + +void on_attr_select_row_enable(GtkCList *list, gint row, gint column, + GdkEventButton *event, gpointer data) +{ + gtk_widget_set_sensitive(GTK_WIDGET(data), TRUE); +} + + + +void on_attr_unselect_row_disable(GtkCList *list, gint row, gint column, + GdkEventButton *event, gpointer data) +{ + gtk_widget_set_sensitive(GTK_WIDGET(data), FALSE); +} + + + +void on_attr_unselect_row_clear_text(GtkCList *list, gint row, gint column, + GdkEventButton *event, gpointer data) +{ + if (GTK_IS_EDITABLE(data)) { + gtk_editable_delete_text(GTK_EDITABLE(data), 0, -1); + } else if (GTK_IS_TEXT_VIEW(data)) { + GtkTextBuffer *tb; + tb = gtk_text_view_get_buffer(GTK_TEXT_VIEW(data)); + gtk_text_buffer_set_text(tb, "", 0); + } +} + + + +void on_editable_changed_enable_if_valid_xml_name(GtkEditable *editable, + gpointer data) +{ + gchar *text = gtk_editable_get_chars(editable, 0, -1); + + /* TODO: need to do checking a little more rigorous than this */ + + if (strlen(text)) { + gtk_widget_set_sensitive(GTK_WIDGET(data), TRUE); + } else { + gtk_widget_set_sensitive(GTK_WIDGET(data), FALSE); + } + g_free(text); +} + + + +void on_desktop_selection_changed(Inkscape::Selection *selection) +{ + if (!blocked++) { + set_tree_select(get_dt_select()); + } + blocked--; +} + +static void on_document_replaced(SPDesktop *dt, SPDocument *doc) +{ + if (current_desktop) + sel_changed_connection.disconnect(); + + sel_changed_connection = SP_DT_SELECTION(dt)->connectChanged(&on_desktop_selection_changed); + set_tree_document(doc); +} + +void on_document_uri_set(gchar const *uri, SPDocument *document) +{ + gchar title[500]; + sp_ui_dialog_title_string(Inkscape::Verb::get(SP_VERB_DIALOG_XML_EDITOR), title); + gchar *t = g_strdup_printf("%s: %s", SP_DOCUMENT_NAME(document), title); + gtk_window_set_title(GTK_WINDOW(dlg), t); + g_free(t); +} + + + +void on_clicked_get_editable_text(GtkWidget *widget, gpointer data) +{ + EditableDest *dest = (EditableDest *) data; + dest->text = gtk_editable_get_chars(dest->editable, 0, -1); +} + + + +void cmd_new_element_node(GtkObject *object, gpointer data) +{ + EditableDest name; + GtkWidget *window, *create, *cancel, *vbox, *entry, *bbox, *sep; + + g_assert(selected_repr != NULL); + + window = sp_window_new(NULL, TRUE); + gtk_container_set_border_width(GTK_CONTAINER(window), 4); + gtk_window_set_title(GTK_WINDOW(window), _("New element node...")); + gtk_window_set_policy(GTK_WINDOW(window), FALSE, FALSE, TRUE); + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_transient_for(GTK_WINDOW(window), GTK_WINDOW(dlg)); + gtk_window_set_modal(GTK_WINDOW(window), TRUE); + gtk_signal_connect(GTK_OBJECT(window), "destroy", gtk_main_quit, NULL); + + vbox = gtk_vbox_new(FALSE, 4); + gtk_container_add(GTK_CONTAINER(window), vbox); + + entry = gtk_entry_new(); + gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, TRUE, 0); + + sep = gtk_hseparator_new(); + gtk_box_pack_start(GTK_BOX(vbox), sep, FALSE, TRUE, 0); + + bbox = gtk_hbutton_box_new(); + gtk_container_set_border_width(GTK_CONTAINER(bbox), 4); + gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); + gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, TRUE, 0); + + cancel = gtk_button_new_with_label(_("Cancel")); + gtk_signal_connect_object( GTK_OBJECT(cancel), "clicked", + G_CALLBACK(gtk_widget_destroy), + GTK_OBJECT(window) ); + gtk_container_add(GTK_CONTAINER(bbox), cancel); + + create = gtk_button_new_with_label(_("Create")); + gtk_widget_set_sensitive(GTK_WIDGET(create), FALSE); + gtk_signal_connect( GTK_OBJECT(entry), "changed", + G_CALLBACK(on_editable_changed_enable_if_valid_xml_name), + create ); + gtk_signal_connect( GTK_OBJECT(create), "clicked", + G_CALLBACK(on_clicked_get_editable_text), &name ); + gtk_signal_connect_object( GTK_OBJECT(create), "clicked", + G_CALLBACK(gtk_widget_destroy), + GTK_OBJECT(window) ); + GTK_WIDGET_SET_FLAGS( GTK_WIDGET(create), + GTK_CAN_DEFAULT | GTK_RECEIVES_DEFAULT ); + gtk_container_add(GTK_CONTAINER(bbox), create); + + gtk_widget_show_all(GTK_WIDGET(window)); + gtk_window_set_default(GTK_WINDOW(window), GTK_WIDGET(create)); + gtk_window_set_focus(GTK_WINDOW(window), GTK_WIDGET(entry)); + + name.editable = GTK_EDITABLE(entry); + name.text = NULL; + + gtk_main(); + + g_assert(selected_repr != NULL); + + if (name.text) { + Inkscape::XML::Node *new_repr; + new_repr = sp_repr_new(name.text); + g_free(name.text); + selected_repr->appendChild(new_repr); + set_tree_select(new_repr); + set_dt_select(new_repr); + } + +} // end of cmd_new_element_node() + + + +void cmd_new_text_node(GtkObject *object, gpointer data) +{ + g_assert(selected_repr != NULL); + + Inkscape::XML::Node *text = sp_repr_new_text(""); + selected_repr->appendChild(text); + + sp_document_done(current_document); + + set_tree_select(text); + set_dt_select(text); + + gtk_window_set_focus(GTK_WINDOW(dlg), GTK_WIDGET(content)); + +} + +void cmd_duplicate_node(GtkObject *object, gpointer data) +{ + g_assert(selected_repr != NULL); + + Inkscape::XML::Node *parent = sp_repr_parent(selected_repr); + Inkscape::XML::Node *dup = selected_repr->duplicate(); + parent->addChild(dup, selected_repr); + + sp_document_done(current_document); + + GtkCTreeNode *node = sp_xmlview_tree_get_repr_node(SP_XMLVIEW_TREE(tree), dup); + + if (node) { + gtk_ctree_select(GTK_CTREE(tree), node); + } +} + + + +void cmd_delete_node(GtkObject *object, gpointer data) +{ + g_assert(selected_repr != NULL); + sp_repr_unparent(selected_repr); + + sp_document_done(current_document); +} + + + +void cmd_delete_attr(GtkObject *object, gpointer data) +{ + g_assert(selected_repr != NULL); + g_assert(selected_attr != 0); + selected_repr->setAttribute(g_quark_to_string(selected_attr), NULL); + + SPObject *updated=current_document->getObjectByRepr(selected_repr); + if (updated) { + // force immediate update of dependant attributes + updated->updateRepr(); + } + + sp_document_done(current_document); +} + + + +void cmd_set_attr(GtkObject *object, gpointer data) +{ + g_assert(selected_repr != NULL); + + gchar *name = gtk_editable_get_chars(attr_name, 0, -1); + GtkTextIter start; + GtkTextIter end; + gtk_text_buffer_get_bounds( gtk_text_view_get_buffer(attr_value), + &start, &end ); + gchar *value = gtk_text_buffer_get_text( gtk_text_view_get_buffer(attr_value), + &start, &end, TRUE ); + + if (!sp_repr_set_attr(selected_repr, name, value)) { + gchar *message = g_strdup_printf(_("Cannot set %s: Another element with value %s already exists!"), name, value); + _message_stack->flash(Inkscape::WARNING_MESSAGE, message); + g_free(message); + } + + g_free(name); + g_free(value); + + SPObject *updated = current_document->getObjectByRepr(selected_repr); + if (updated) { + // force immediate update of dependant attributes + updated->updateRepr(); + } + + sp_document_done(current_document); + + /* TODO: actually, the row won't have been created yet. why? */ + gint row = sp_xmlview_attr_list_find_row_from_key(GTK_CLIST(attributes), + g_quark_from_string(name)); + if (row != -1) { + gtk_clist_select_row(GTK_CLIST(attributes), row, 0); + } +} + + + +void cmd_raise_node(GtkObject *object, gpointer data) +{ + g_assert(selected_repr != NULL); + + Inkscape::XML::Node *parent = sp_repr_parent(selected_repr); + g_return_if_fail(parent != NULL); + g_return_if_fail(parent->firstChild() != selected_repr); + + Inkscape::XML::Node *ref = NULL; + Inkscape::XML::Node *before = parent->firstChild(); + while (before && before->next() != selected_repr) { + ref = before; + before = before->next(); + } + + parent->changeOrder(selected_repr, ref); + + sp_document_done(current_document); + + set_tree_select(selected_repr); + set_dt_select(selected_repr); +} + + + +void cmd_lower_node(GtkObject *object, gpointer data) +{ + g_assert(selected_repr != NULL); + g_return_if_fail(selected_repr->next() != NULL); + Inkscape::XML::Node *parent = sp_repr_parent(selected_repr); + + parent->changeOrder(selected_repr, selected_repr->next()); + + sp_document_done(current_document); + + set_tree_select(selected_repr); + set_dt_select(selected_repr); +} + +void cmd_indent_node(GtkObject *object, gpointer data) +{ + Inkscape::XML::Node *repr = selected_repr; + g_assert(repr != NULL); + Inkscape::XML::Node *parent = sp_repr_parent(repr); + g_return_if_fail(parent != NULL); + g_return_if_fail(parent->firstChild() != repr); + + Inkscape::XML::Node* prev = parent->firstChild(); + while (prev && prev->next() != repr) { + prev = prev->next(); + } + g_return_if_fail(prev != NULL); + g_return_if_fail(prev->type() == Inkscape::XML::ELEMENT_NODE); + + Inkscape::XML::Node* ref = NULL; + if (prev->firstChild()) { + for( ref = prev->firstChild() ; ref->next() ; ref = ref->next() ); + } + + parent->removeChild(repr); + prev->addChild(repr, ref); + + sp_document_done(current_document); + set_tree_select(repr); + set_dt_select(repr); + +} // end of cmd_indent_node() + + + +void cmd_unindent_node(GtkObject *object, gpointer data) +{ + Inkscape::XML::Node *repr = selected_repr; + g_assert(repr != NULL); + Inkscape::XML::Node *parent = sp_repr_parent(repr); + g_return_if_fail(parent); + Inkscape::XML::Node *grandparent = sp_repr_parent(parent); + g_return_if_fail(grandparent); + + parent->removeChild(repr); + grandparent->addChild(repr, parent); + + sp_document_done(current_document); + set_tree_select(repr); + set_dt_select(repr); + +} // end of cmd_unindent_node() + + +/* + 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 : diff --git a/src/dialogs/xml-tree.h b/src/dialogs/xml-tree.h new file mode 100644 index 000000000..6d0d44a7d --- /dev/null +++ b/src/dialogs/xml-tree.h @@ -0,0 +1,29 @@ +#ifndef SP_XML_TREE_H +#define SP_XML_TREE_H + +/** + * \brief XML tree editing dialog for Inkscape + * + * Copyright Lauris Kaplinski, 2000 + * + * Released under GNU General Public License. + * + * This is XML tree editor, which allows direct modifying of all elements + * of Inkscape document, including foreign ones. + * + */ + +void sp_xml_tree_dialog (void); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/dir-util-test.cpp b/src/dir-util-test.cpp new file mode 100644 index 000000000..691f6fee1 --- /dev/null +++ b/src/dir-util-test.cpp @@ -0,0 +1,48 @@ +#include "utest/test-2ary-cases.h" +#include "dir-util.h" +#include "streq.h" + +struct streq_functor { + bool operator()(char const *a, char const *b) + { + return streq(a, b); + } +}; + +static bool +test_sp_relative_path_from_path() +{ + utest_start("sp_relative_path_from_path"); + struct Case2 cases[] = { + {"/foo/bar", "/foo", "bar"}, + {"/foo/barney", "/foo/bar", "/foo/barney"}, + {"/foo/bar/baz", "/foo/", "bar/baz"}, + {"/foo/bar/baz", "/", "foo/bar/baz"}, + {"/foo/bar/baz", "/foo/qux", "/foo/bar/baz"}, + {"/foo", NULL, "/foo"} + }; + test_2ary_cases + ("sp_relative_path_from_path", + sp_relative_path_from_path, + G_N_ELEMENTS(cases), cases); + return utest_end(); +} + +int main() +{ + return ( test_sp_relative_path_from_path() + ? EXIT_SUCCESS + : EXIT_FAILURE ); +} + + +/* + 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 : diff --git a/src/dir-util.cpp b/src/dir-util.cpp new file mode 100644 index 000000000..b83eec002 --- /dev/null +++ b/src/dir-util.cpp @@ -0,0 +1,278 @@ +/** \file Some utility functions for filenames. */ + +#define DIR_UTIL_C + +#include +#include +#include +#include +#include +#include +#include + +/** Returns a form of \a path relative to \a base if that is easy to construct (e.g. if \a path + appears to be in the directory specified by \a base), otherwise returns \a path. + + N.B. The return value is a pointer into the \a path string. + + \a base is expected to be either NULL or the absolute path of a directory. + + \a path is expected to be an absolute path. + + \see inkscape_abs2rel for a more sophisticated version. + \see prepend_current_dir_if_relative. +*/ +char const * +sp_relative_path_from_path(char const *const path, char const *const base) +{ + if (base == NULL || path == NULL) { + return path; + } + + size_t base_len = strlen(base); + while (base_len != 0 + && (base[base_len - 1] == G_DIR_SEPARATOR)) + { + --base_len; + } + + if ((memcmp(path, base, base_len) == 0) + && (path[base_len] == G_DIR_SEPARATOR)) + { + char const *ret = path + base_len + 1; + while (*ret == G_DIR_SEPARATOR) { + ++ret; + } + if (*ret != '\0') { + return ret; + } + } + + return path; +} + +char const * +sp_extension_from_path(char const *const path) +{ + if (path == NULL) { + return NULL; + } + + char const *p = path; + while (*p != '\0') p++; + + while ((p >= path) && (*p != G_DIR_SEPARATOR) && (*p != '.')) p--; + if (* p != '.') return NULL; + p++; + + return p; +} + + +/* current == "./", parent == "../" */ +static char const dots[] = {'.', '.', G_DIR_SEPARATOR, '\0'}; +static char const *const parent = dots; +static char const *const current = dots + 1; + +/** + * \brief Convert a relative path name into absolute. If path is already absolute, does nothing except copying path to result. + * + * \param path relative path + * \param base base directory (must be absolute path) + * \param result result buffer + * \param size size of result buffer + * \return != NULL: absolute path + * == NULL: error + +\comment + based on functions by Shigio Yamaguchi. + FIXME:TODO: force it to also do path normalization of the entire resulting path, + i.e. get rid of any .. and . in any place, even if 'path' is already absolute + (now it returns it unchanged in this case) + + */ +char * +inkscape_rel2abs (const char *path, const char *base, char *result, const size_t size) +{ + const char *pp, *bp; + /* endp points the last position which is safe in the result buffer. */ + const char *endp = result + size - 1; + char *rp; + int length; + if (*path == G_DIR_SEPARATOR) + { + if (strlen (path) >= size) + goto erange; + strcpy (result, path); + goto finish; + } + else if (*base != G_DIR_SEPARATOR || !size) + { + errno = EINVAL; + return (NULL); + } + else if (size == 1) + goto erange; + if (!strcmp (path, ".") || !strcmp (path, current)) + { + if (strlen (base) >= size) + goto erange; + strcpy (result, base); + /* rp points the last char. */ + rp = result + strlen (base) - 1; + if (*rp == G_DIR_SEPARATOR) + *rp = 0; + else + rp++; + /* rp point NULL char */ + if (*++path == G_DIR_SEPARATOR) + { + /* Append G_DIR_SEPARATOR to the tail of path name. */ + *rp++ = G_DIR_SEPARATOR; + if (rp > endp) + goto erange; + *rp = 0; + } + goto finish; + } + bp = base + strlen (base); + if (*(bp - 1) == G_DIR_SEPARATOR) + --bp; + /* up to root. */ + for (pp = path; *pp && *pp == '.';) + { + if (!strncmp (pp, parent, 3)) + { + pp += 3; + while (bp > base && *--bp != G_DIR_SEPARATOR) + ; + } + else if (!strncmp (pp, current, 2)) + { + pp += 2; + } + else if (!strncmp (pp, "..\0", 3)) + { + pp += 2; + while (bp > base && *--bp != G_DIR_SEPARATOR) + ; + } + else + break; + } + /* down to leaf. */ + length = bp - base; + if (length >= static_cast(size)) + goto erange; + strncpy (result, base, length); + rp = result + length; + if (*pp || *(pp - 1) == G_DIR_SEPARATOR || length == 0) + *rp++ = G_DIR_SEPARATOR; + if (rp + strlen (pp) > endp) + goto erange; + strcpy (rp, pp); +finish: + return result; +erange: + errno = ERANGE; + return (NULL); +} + +char * +inkscape_abs2rel (const char *path, const char *base, char *result, const size_t size) +{ + const char *pp, *bp, *branch; + /* endp points the last position which is safe in the result buffer. */ + const char *endp = result + size - 1; + char *rp; + + if (*path != G_DIR_SEPARATOR) + { + if (strlen (path) >= size) + goto erange; + strcpy (result, path); + goto finish; + } + else if (*base != G_DIR_SEPARATOR || !size) + { + errno = EINVAL; + return (NULL); + } + else if (size == 1) + goto erange; + /* seek to branched point. */ + branch = path; + for (pp = path, bp = base; *pp && *bp && *pp == *bp; pp++, bp++) + if (*pp == G_DIR_SEPARATOR) + branch = pp; + if ((*pp == 0 || *pp == G_DIR_SEPARATOR && *(pp + 1) == 0) && + (*bp == 0 || *bp == G_DIR_SEPARATOR && *(bp + 1) == 0)) + { + rp = result; + *rp++ = '.'; + if (*pp == G_DIR_SEPARATOR || *(pp - 1) == G_DIR_SEPARATOR) + *rp++ = G_DIR_SEPARATOR; + if (rp > endp) + goto erange; + *rp = 0; + goto finish; + } + if (*pp == 0 && *bp == G_DIR_SEPARATOR || *pp == G_DIR_SEPARATOR && *bp == 0) + branch = pp; + /* up to root. */ + rp = result; + for (bp = base + (branch - path); *bp; bp++) + if (*bp == G_DIR_SEPARATOR && *(bp + 1) != 0) + { + if (rp + 3 > endp) + goto erange; + *rp++ = '.'; + *rp++ = '.'; + *rp++ = G_DIR_SEPARATOR; + } + if (rp > endp) + goto erange; + *rp = 0; + /* down to leaf. */ + if (*branch) + { + if (rp + strlen (branch + 1) > endp) + goto erange; + strcpy (rp, branch + 1); + } + else + *--rp = 0; +finish: + return result; +erange: + errno = ERANGE; + return (NULL); +} + +void +prepend_current_dir_if_relative (char **result, const gchar *uri) +{ + if (!uri) { + *(result) = NULL; + return; + } + + char *full_path = (char *) g_malloc (1001); + char *cwd = g_get_current_dir(); + + gsize bytesRead = 0; + gsize bytesWritten = 0; + GError* error = NULL; + gchar* cwd_utf8 = g_filename_to_utf8 ( cwd, + -1, + &bytesRead, + &bytesWritten, + &error); + + inkscape_rel2abs (uri, cwd_utf8, full_path, 1000); + *(result) = g_strdup (full_path); + g_free (full_path); + g_free (cwd); +} + + diff --git a/src/dir-util.h b/src/dir-util.h new file mode 100644 index 000000000..f7d75e8e4 --- /dev/null +++ b/src/dir-util.h @@ -0,0 +1,33 @@ +#ifndef SEEN_DIR_UTIL_H +#define SEEN_DIR_UTIL_H + +/* + * path-util.h + * + * here are functions sp_relative_path & cousins + * maybe they are already implemented in standard libs + * + */ + +#include +#include + +char const *sp_relative_path_from_path(char const *path, char const *base); +char const *sp_extension_from_path(char const *path); +char *inkscape_rel2abs(char const *path, char const *base, char *result, size_t const size); +char *inkscape_abs2rel(char const *path, char const *base, char *result, size_t const size); +void prepend_current_dir_if_relative(char **result, gchar const *uri); + + +#endif /* !SEEN_DIR_UTIL_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:encoding=utf-8:textwidth=99 : diff --git a/src/display/.cvsignore b/src/display/.cvsignore new file mode 100644 index 000000000..49cf2d7a9 --- /dev/null +++ b/src/display/.cvsignore @@ -0,0 +1,7 @@ +.deps +.dirstamp +.libs +Makefile +Makefile.in +makefile +*-test diff --git a/src/display/Makefile_insert b/src/display/Makefile_insert new file mode 100644 index 000000000..de6ec85c2 --- /dev/null +++ b/src/display/Makefile_insert @@ -0,0 +1,66 @@ +## Makefile.am fragment sourced by src/Makefile.am. +# +# Sodipodi GnomeCanvas objects +# Author: Lauris Kaplinski +# +# Here are major objects, used for displaying things +# + +display/all: display/libspdisplay.a + +display/clean: + rm -f display/libspdisplay.a $(display_libspdisplay_a_OBJECTS) + +display/canvas-arena.$(OBJEXT): helper/sp-marshal.h +display/sp-canvas.$(OBJEXT): helper/sp-marshal.h + +display_libspdisplay_a_SOURCES = \ + display/nr-arena-forward.h \ + display/nr-arena.cpp \ + display/nr-arena.h \ + display/nr-arena-item.cpp \ + display/nr-arena-item.h \ + display/nr-arena-group.cpp \ + display/nr-arena-group.h \ + display/nr-arena-image.cpp \ + display/nr-arena-image.h \ + display/nr-arena-shape.cpp \ + display/nr-arena-shape.h \ + display/nr-arena-glyphs.cpp \ + display/nr-arena-glyphs.h \ + display/canvas-arena.cpp \ + display/canvas-arena.h \ + display/bezier-utils.cpp \ + display/bezier-utils.h \ + display/canvas-bpath.cpp \ + display/canvas-bpath.h \ + display/canvas-grid.cpp \ + display/canvas-grid.h \ + display/curve.cpp \ + display/curve.h \ + display/display-forward.h \ + display/gnome-canvas-acetate.cpp \ + display/gnome-canvas-acetate.h \ + display/guideline.cpp \ + display/guideline.h \ + display/nr-gradient-gpl.cpp \ + display/nr-gradient-gpl.h \ + display/nr-plain-stuff-gdk.cpp \ + display/nr-plain-stuff-gdk.h \ + display/nr-plain-stuff.cpp \ + display/nr-plain-stuff.h \ + display/sodipodi-ctrl.cpp \ + display/sodipodi-ctrl.h \ + display/sodipodi-ctrlrect.cpp \ + display/sodipodi-ctrlrect.h \ + display/sp-canvas-util.cpp \ + display/sp-canvas-util.h \ + display/sp-canvas.cpp \ + display/sp-canvas.h \ + display/sp-ctrlline.cpp \ + display/sp-ctrlline.h \ + display/sp-ctrlquadr.cpp \ + display/sp-ctrlquadr.h + +display_bezier_utils_test_SOURCES = display/bezier-utils-test.cpp +display_bezier_utils_test_LDADD = libnr/libnr.a -lglib-2.0 diff --git a/src/display/bezier-utils-test.cpp b/src/display/bezier-utils-test.cpp new file mode 100644 index 000000000..d42672113 --- /dev/null +++ b/src/display/bezier-utils-test.cpp @@ -0,0 +1,334 @@ +#include "../utest/utest.h" +#include +#include /* NR_DF_TEST_CLOSE */ + +/* mental disclaims all responsibility for this evil idea for testing + static functions. The main disadvantages are that we retain the + #define's and `using' directives of the included file. */ +#include "bezier-utils.cpp" + +using NR::Point; + +static bool range_approx_equal(double const a[], double const b[], unsigned len); + +/* (Returns false if NaN encountered.) */ +template +static bool range_equal(T const a[], T const b[], unsigned len) { + for (unsigned i = 0; i < len; ++i) { + if ( a[i] != b[i] ) { + return false; + } + } + return true; +} + +inline bool point_approx_equal(NR::Point const &a, NR::Point const &b, double const eps) +{ + using NR::X; using NR::Y; + return ( NR_DF_TEST_CLOSE(a[X], b[X], eps) && + NR_DF_TEST_CLOSE(a[Y], b[Y], eps) ); +} + +static inline double square(double const x) { + return x * x; +} + +/** Determine whether the found control points are the same as previously found on some developer's + machine. Doesn't call utest__fail, just writes a message to stdout for diagnostic purposes: + the most important test is that the root-mean-square of errors in the estimation are low rather + than that the control points found are the same. +**/ +static void compare_ctlpts(Point const est_b[], Point const exp_est_b[]) +{ + unsigned diff_mask = 0; + for (unsigned i = 0; i < 4; ++i) { + for (unsigned d = 0; d < 2; ++d) { + if ( fabs( est_b[i][d] - exp_est_b[i][d] ) > 1.1e-5 ) { + diff_mask |= 1 << ( i * 2 + d ); + } + } + } + if ( diff_mask != 0 ) { + printf("Warning: got different control points from previously-coded (diffs=0x%x).\n", + diff_mask); + printf(" Previous:"); + for (unsigned i = 0; i < 4; ++i) { + printf(" (%g, %g)", exp_est_b[i][0], exp_est_b[i][1]); // localizing ok + } + putchar('\n'); + printf(" Found: "); + for (unsigned i = 0; i < 4; ++i) { + printf(" (%g, %g)", est_b[i][0], est_b[i][1]); // localizing ok + } + putchar('\n'); + } +} + +static void compare_rms(Point const est_b[], double const t[], Point const d[], unsigned const n, + double const exp_rms_error) +{ + double sum_errsq = 0.0; + for (unsigned i = 0; i < n; ++i) { + Point const fit_pt = bezier_pt(3, est_b, t[i]); + Point const diff = fit_pt - d[i]; + sum_errsq += dot(diff, diff); + } + double const rms_error = sqrt( sum_errsq / n ); + UTEST_ASSERT( rms_error <= exp_rms_error + 1.1e-6 ); + if ( rms_error < exp_rms_error - 1.1e-6 ) { + /* The fitter code appears to have improved [or the floating point calculations differ + on this machine from the machine where exp_rms_error was calculated]. */ + printf("N.B. rms_error regression requirement can be decreased: have rms_error=%g.\n", rms_error); // localizing ok + } +} + +int main(int argc, char *argv[]) { + utest_start("bezier-utils.cpp"); + + UTEST_TEST("copy_without_nans_or_adjacent_duplicates") { + NR::Point const src[] = { + Point(2., 3.), + Point(2., 3.), + Point(0., 0.), + Point(2., 3.), + Point(2., 3.), + Point(1., 9.), + Point(1., 9.) + }; + Point const exp_dest[] = { + Point(2., 3.), + Point(0., 0.), + Point(2., 3.), + Point(1., 9.) + }; + g_assert( G_N_ELEMENTS(src) == 7 ); + Point dest[7]; + struct tst { + unsigned src_ix0; + unsigned src_len; + unsigned exp_dest_ix0; + unsigned exp_dest_len; + } const test_data[] = { + /* src start ix, src len, exp_dest start ix, exp dest len */ + {0, 0, 0, 0}, + {2, 1, 1, 1}, + {0, 1, 0, 1}, + {0, 2, 0, 1}, + {0, 3, 0, 2}, + {1, 3, 0, 3}, + {0, 5, 0, 3}, + {0, 6, 0, 4}, + {0, 7, 0, 4} + }; + for (unsigned i = 0 ; i < G_N_ELEMENTS(test_data) ; ++i) { + tst const &t = test_data[i]; + UTEST_ASSERT( t.exp_dest_len + == copy_without_nans_or_adjacent_duplicates(src + t.src_ix0, + t.src_len, + dest) ); + UTEST_ASSERT(range_equal(dest, + exp_dest + t.exp_dest_ix0, + t.exp_dest_len)); + } + } + + UTEST_TEST("bezier_pt(1)") { + Point const a[] = {Point(2.0, 4.0), + Point(1.0, 8.0)}; + UTEST_ASSERT( bezier_pt(1, a, 0.0) == a[0] ); + UTEST_ASSERT( bezier_pt(1, a, 1.0) == a[1] ); + UTEST_ASSERT( bezier_pt(1, a, 0.5) == Point(1.5, 6.0) ); + double const t[] = {0.5, 0.25, 0.3, 0.6}; + for (unsigned i = 0; i < G_N_ELEMENTS(t); ++i) { + double const ti = t[i], si = 1.0 - ti; + UTEST_ASSERT( bezier_pt(1, a, ti) == si * a[0] + ti * a[1] ); + } + } + + UTEST_TEST("bezier_pt(2)") { + Point const b[] = {Point(1.0, 2.0), + Point(8.0, 4.0), + Point(3.0, 1.0)}; + UTEST_ASSERT( bezier_pt(2, b, 0.0) == b[0] ); + UTEST_ASSERT( bezier_pt(2, b, 1.0) == b[2] ); + UTEST_ASSERT( bezier_pt(2, b, 0.5) == Point(5.0, 2.75) ); + double const t[] = {0.5, 0.25, 0.3, 0.6}; + for (unsigned i = 0; i < G_N_ELEMENTS(t); ++i) { + double const ti = t[i], si = 1.0 - ti; + Point const exp_pt( si*si * b[0] + 2*si*ti * b[1] + ti*ti * b[2] ); + Point const pt(bezier_pt(2, b, ti)); + UTEST_ASSERT(point_approx_equal(pt, exp_pt, 1e-11)); + } + } + + Point const c[] = {Point(1.0, 2.0), + Point(8.0, 4.0), + Point(3.0, 1.0), + Point(-2.0, -4.0)}; + UTEST_TEST("bezier_pt(3)") { + UTEST_ASSERT( bezier_pt(3, c, 0.0) == c[0] ); + UTEST_ASSERT( bezier_pt(3, c, 1.0) == c[3] ); + UTEST_ASSERT( bezier_pt(3, c, 0.5) == Point(4.0, 13.0/8.0) ); + double const t[] = {0.5, 0.25, 0.3, 0.6}; + for (unsigned i = 0; i < G_N_ELEMENTS(t); ++i) { + double const ti = t[i], si = 1.0 - ti; + UTEST_ASSERT( LInfty( bezier_pt(3, c, ti) + - ( si*si*si * c[0] + + 3*si*si*ti * c[1] + + 3*si*ti*ti * c[2] + + ti*ti*ti * c[3] ) ) + < 1e-4 ); + } + } + + struct Err_tst { + Point pt; + double u; + double err; + } const err_tst[] = { + {c[0], 0.0, 0.0}, + {Point(4.0, 13.0/8.0), 0.5, 0.0}, + {Point(4.0, 2.0), 0.5, 9.0/64.0}, + {Point(3.0, 2.0), 0.5, 1.0 + 9.0/64.0}, + {Point(6.0, 2.0), 0.5, 4.0 + 9.0/64.0}, + {c[3], 1.0, 0.0}, + }; + + UTEST_TEST("compute_max_error_ratio") { + Point d[G_N_ELEMENTS(err_tst)]; + double u[G_N_ELEMENTS(err_tst)]; + for (unsigned i = 0; i < G_N_ELEMENTS(err_tst); ++i) { + Err_tst const &t = err_tst[i]; + d[i] = t.pt; + u[i] = t.u; + } + g_assert( G_N_ELEMENTS(u) == G_N_ELEMENTS(d) ); + unsigned max_ix = ~0u; + double const err_ratio = compute_max_error_ratio(d, u, G_N_ELEMENTS(d), c, 1.0, &max_ix); + UTEST_ASSERT( fabs( sqrt(err_tst[4].err) - err_ratio ) < 1e-12 ); + UTEST_ASSERT( max_ix == 4 ); + } + + UTEST_TEST("chord_length_parameterize") { + /* n == 2 */ + { + Point const d[] = {Point(2.9415, -5.8149), + Point(23.021, 4.9814)}; + double u[G_N_ELEMENTS(d)]; + double const exp_u[] = {0.0, 1.0}; + g_assert( G_N_ELEMENTS(u) == G_N_ELEMENTS(exp_u) ); + chord_length_parameterize(d, u, G_N_ELEMENTS(d)); + UTEST_ASSERT(range_equal(u, exp_u, G_N_ELEMENTS(exp_u))); + } + + /* Straight line. */ + { + double const exp_u[] = {0.0, 0.1829, 0.2105, 0.2105, 0.619, 0.815, 0.999, 1.0}; + unsigned const n = G_N_ELEMENTS(exp_u); + Point d[n]; + double u[n]; + Point const a(-23.985, 4.915), b(4.9127, 5.203); + for (unsigned i = 0; i < n; ++i) { + double bi = exp_u[i], ai = 1.0 - bi; + d[i] = ai * a + bi * b; + } + chord_length_parameterize(d, u, n); + UTEST_ASSERT(range_approx_equal(u, exp_u, n)); + } + } + + /* Feed it some points that can be fit exactly with a single bezier segment, and see how + well it manages. */ + Point const src_b[4] = {Point(5., -3.), + Point(8., 0.), + Point(4., 2.), + Point(3., 3.)}; + double const t[] = {0.0, .001, .03, .05, .09, .13, .18, .25, .29, .33, .39, .44, + .51, .57, .62, .69, .75, .81, .91, .93, .97, .98, .999, 1.0}; + unsigned const n = G_N_ELEMENTS(t); + Point d[n]; + for (unsigned i = 0; i < n; ++i) { + d[i] = bezier_pt(3, src_b, t[i]); + } + Point const tHat1(unit_vector( src_b[1] - src_b[0] )); + Point const tHat2(unit_vector( src_b[2] - src_b[3] )); + + UTEST_TEST("generate_bezier") { + Point est_b[4]; + generate_bezier(est_b, d, t, n, tHat1, tHat2, 1.0); + + compare_ctlpts(est_b, src_b); + + /* We're being unfair here in using our t[] rather than best t[] for est_b: we + may over-estimate RMS of errors. */ + compare_rms(est_b, t, d, n, 1e-8); + } + + UTEST_TEST("sp_bezier_fit_cubic_full") { + Point est_b[4]; + int splitpoints[2]; + gint const succ = sp_bezier_fit_cubic_full(est_b, splitpoints, d, n, tHat1, tHat2, square(1.2), 1); + UTEST_ASSERT( succ == 1 ); + + Point const exp_est_b[4] = { + Point(5.000000, -3.000000), + Point(7.5753, -0.4247), + Point(4.77533, 1.22467), + Point(3, 3) + }; + compare_ctlpts(est_b, exp_est_b); + + /* We're being unfair here in using our t[] rather than best t[] for est_b: we + may over-estimate RMS of errors. */ + compare_rms(est_b, t, d, n, .307911); + } + + UTEST_TEST("sp_bezier_fit_cubic") { + Point est_b[4]; + gint const succ = sp_bezier_fit_cubic(est_b, d, n, square(1.2)); + UTEST_ASSERT( succ == 1 ); + + Point const exp_est_b[4] = { + Point(5.000000, -3.000000), + Point(7.57134, -0.423509), + Point(4.77929, 1.22426), + Point(3, 3) + }; + compare_ctlpts(est_b, exp_est_b); + +#if 1 /* A change has been made to right_tangent. I believe that usually this change + will result in better fitting, but it won't do as well for this example where + we happen to be feeding a t=0.999 point to the fitter. */ + printf("TODO: Update this test case for revised right_tangent implementation.\n"); + /* In particular, have a test case to show whether the new implementation + really is likely to be better on average. */ +#else + /* We're being unfair here in using our t[] rather than best t[] for est_b: we + may over-estimate RMS of errors. */ + compare_rms(est_b, t, d, n, .307983); +#endif + } + + return !utest_end(); +} + +/* (Returns false if NaN encountered.) */ +static bool range_approx_equal(double const a[], double const b[], unsigned const len) { + for (unsigned i = 0; i < len; ++i) { + if (!( fabs( a[i] - b[i] ) < 1e-4 )) { + return false; + } + } + return true; +} + +/* + 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 : diff --git a/src/display/bezier-utils-work.txt b/src/display/bezier-utils-work.txt new file mode 100644 index 000000000..8604c1207 --- /dev/null +++ b/src/display/bezier-utils-work.txt @@ -0,0 +1,34 @@ +min .5 * sum_i lensq(bez_pt(b, u[i]) - d[i]) + +lensq(d)=dot(d, d) = d.x * d.x + d.y * d.y + +sum_i (f(i) + g(i)) = sum_i f(i) + sum_i g(i), so +we can separate into x,y parts. Since they are the same, we write `z' in the below +to mean either x or y. + +.5 * sum_i (bez_pt(b, u[i]) - d[i]).z^2 + += .5 * sum_i (B0(u[i]) * b[0] + + B1(u[i]) * b[1] + + B2(u[i]) * b[2] + + B3(u[i]) * b[3] + - d[i] ).z^2 + += H. + +Suppose that b[0,1,3] are fixed (with b[1] perhaps being calculated +from a prior call to existing generate_bezier). + +d H / d b[2].z = sum_i B2(u[i]) * (bez_pt(b, u[i]) - d[i]).z + +Solve for dH/db[2].z==0: + +-sum_i B2(u[i]) B2(u[i]) * b[2].z = sum_i B2(u[i]) * (B0(u[i]) * b[0] + + B1(u[i]) * b[1] + + B3(u[i]) * b[3] + - d[i] ).z +b[2].z = ((sum_i B2(u[i]) * (B0(u[i]) * b[0] + + B1(u[i]) * b[1] + + B3(u[i]) * b[3] + - d[i] ).z) + / -sum_i (B2(u[i]))^2) diff --git a/src/display/bezier-utils.cpp b/src/display/bezier-utils.cpp new file mode 100644 index 000000000..8316c86cc --- /dev/null +++ b/src/display/bezier-utils.cpp @@ -0,0 +1,983 @@ +#define __SP_BEZIER_UTILS_C__ + +/** \file + * Bezier interpolation for inkscape drawing code. + */ +/* + * Original code published in: + * An Algorithm for Automatically Fitting Digitized Curves + * by Philip J. Schneider + * "Graphics Gems", Academic Press, 1990 + * + * Authors: + * Philip J. Schneider + * Lauris Kaplinski + * Peter Moulder + * + * Copyright (C) 1990 Philip J. Schneider + * Copyright (C) 2001 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * Copyright (C) 2003,2004 Monash University + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define SP_HUGE 1e5 +#define noBEZIER_DEBUG + +#ifdef HAVE_CONFIG_H +# include +#endif + +#ifdef HAVE_IEEEFP_H +# include +#endif + +#include +#include +#include "bezier-utils.h" +#include + +#include "isnan.h" + + +typedef NR::Point BezierCurve[]; + +/* Forward declarations */ +static void generate_bezier(NR::Point b[], NR::Point const d[], gdouble const u[], unsigned len, + NR::Point const &tHat1, NR::Point const &tHat2, double tolerance_sq); +static void estimate_lengths(NR::Point bezier[], + NR::Point const data[], gdouble const u[], unsigned len, + NR::Point const &tHat1, NR::Point const &tHat2); +static void estimate_bi(NR::Point b[4], unsigned ei, + NR::Point const data[], double const u[], unsigned len); +static void reparameterize(NR::Point const d[], unsigned len, double u[], BezierCurve const bezCurve); +static gdouble NewtonRaphsonRootFind(BezierCurve const Q, NR::Point const &P, gdouble u); +static NR::Point bezier_pt(unsigned degree, NR::Point const V[], gdouble t); +static NR::Point sp_darray_center_tangent(NR::Point const d[], unsigned center, unsigned length); +static NR::Point sp_darray_right_tangent(NR::Point const d[], unsigned const len); +static unsigned copy_without_nans_or_adjacent_duplicates(NR::Point const src[], unsigned src_len, NR::Point dest[]); +static void chord_length_parameterize(NR::Point const d[], gdouble u[], unsigned len); +static double compute_max_error_ratio(NR::Point const d[], double const u[], unsigned len, + BezierCurve const bezCurve, double tolerance, + unsigned *splitPoint); +static double compute_hook(NR::Point const &a, NR::Point const &b, double const u, BezierCurve const bezCurve, + double const tolerance); + + +static NR::Point const unconstrained_tangent(0, 0); + + +/* + * B0, B1, B2, B3 : Bezier multipliers + */ + +#define B0(u) ( ( 1.0 - u ) * ( 1.0 - u ) * ( 1.0 - u ) ) +#define B1(u) ( 3 * u * ( 1.0 - u ) * ( 1.0 - u ) ) +#define B2(u) ( 3 * u * u * ( 1.0 - u ) ) +#define B3(u) ( u * u * u ) + +#ifdef BEZIER_DEBUG +# define DOUBLE_ASSERT(x) g_assert( ( (x) > -SP_HUGE ) && ( (x) < SP_HUGE ) ) +# define BEZIER_ASSERT(b) do { \ + DOUBLE_ASSERT((b)[0][NR::X]); DOUBLE_ASSERT((b)[0][NR::Y]); \ + DOUBLE_ASSERT((b)[1][NR::X]); DOUBLE_ASSERT((b)[1][NR::Y]); \ + DOUBLE_ASSERT((b)[2][NR::X]); DOUBLE_ASSERT((b)[2][NR::Y]); \ + DOUBLE_ASSERT((b)[3][NR::X]); DOUBLE_ASSERT((b)[3][NR::Y]); \ + } while(0) +#else +# define DOUBLE_ASSERT(x) do { } while(0) +# define BEZIER_ASSERT(b) do { } while(0) +#endif + + +/** + * Fit a single-segment Bezier curve to a set of digitized points. + * + * \return Number of segments generated, or -1 on error. + */ +gint +sp_bezier_fit_cubic(NR::Point *bezier, NR::Point const *data, gint len, gdouble error) +{ + return sp_bezier_fit_cubic_r(bezier, data, len, error, 1); +} + +/** + * Fit a multi-segment Bezier curve to a set of digitized points, with + * possible weedout of identical points and NaNs. + * + * \param max_beziers Maximum number of generated segments + * \param Result array, must be large enough for n. segments * 4 elements. + * + * \return Number of segments generated, or -1 on error. + */ +gint +sp_bezier_fit_cubic_r(NR::Point bezier[], NR::Point const data[], gint const len, gdouble const error, unsigned const max_beziers) +{ + g_return_val_if_fail(bezier != NULL, -1); + g_return_val_if_fail(data != NULL, -1); + g_return_val_if_fail(len > 0, -1); + g_return_val_if_fail(max_beziers < (1ul << (31 - 2 - 1 - 3)), -1); + + NR::Point *uniqued_data = g_new(NR::Point, len); + unsigned uniqued_len = copy_without_nans_or_adjacent_duplicates(data, len, uniqued_data); + + if ( uniqued_len < 2 ) { + g_free(uniqued_data); + return 0; + } + + /* Call fit-cubic function with recursion. */ + gint const ret = sp_bezier_fit_cubic_full(bezier, NULL, uniqued_data, uniqued_len, + unconstrained_tangent, unconstrained_tangent, + error, max_beziers); + g_free(uniqued_data); + return ret; +} + +/** + * Copy points from src to dest, filter out points containing NaN and + * adjacent points with equal x and y. + * \return length of dest + */ +static unsigned +copy_without_nans_or_adjacent_duplicates(NR::Point const src[], unsigned src_len, NR::Point dest[]) +{ + unsigned si = 0; + for (;;) { + if ( si == src_len ) { + return 0; + } + if (!isNaN(src[si][NR::X]) && + !isNaN(src[si][NR::Y])) { + dest[0] = NR::Point(src[si]); + ++si; + break; + } + } + unsigned di = 0; + for (; si < src_len; ++si) { + NR::Point const src_pt = NR::Point(src[si]); + if ( src_pt != dest[di] + && !isNaN(src_pt[NR::X]) + && !isNaN(src_pt[NR::Y])) { + dest[++di] = src_pt; + } + } + unsigned dest_len = di + 1; + g_assert( dest_len <= src_len ); + return dest_len; +} + +/** + * Fit a multi-segment Bezier curve to a set of digitized points, without + * possible weedout of identical points and NaNs. + * + * \pre data is uniqued, i.e. not exist i: data[i] == data[i + 1]. + * \param max_beziers Maximum number of generated segments + * \param Result array, must be large enough for n. segments * 4 elements. + */ +gint +sp_bezier_fit_cubic_full(NR::Point bezier[], int split_points[], + NR::Point const data[], gint const len, + NR::Point const &tHat1, NR::Point const &tHat2, + double const error, unsigned const max_beziers) +{ + int const maxIterations = 4; /* Max times to try iterating */ + + g_return_val_if_fail(bezier != NULL, -1); + g_return_val_if_fail(data != NULL, -1); + g_return_val_if_fail(len > 0, -1); + g_return_val_if_fail(max_beziers >= 1, -1); + g_return_val_if_fail(error >= 0.0, -1); + + if ( len < 2 ) return 0; + + if ( len == 2 ) { + /* We have 2 points, which can be fitted trivially. */ + bezier[0] = data[0]; + bezier[3] = data[len - 1]; + double const dist = ( L2( data[len - 1] + - data[0] ) + / 3.0 ); + if (isNaN(dist)) { + /* Numerical problem, fall back to straight line segment. */ + bezier[1] = bezier[0]; + bezier[2] = bezier[3]; + } else { + bezier[1] = ( is_zero(tHat1) + ? ( 2 * bezier[0] + bezier[3] ) / 3. + : bezier[0] + dist * tHat1 ); + bezier[2] = ( is_zero(tHat2) + ? ( bezier[0] + 2 * bezier[3] ) / 3. + : bezier[3] + dist * tHat2 ); + } + BEZIER_ASSERT(bezier); + return 1; + } + + /* Parameterize points, and attempt to fit curve */ + unsigned splitPoint; /* Point to split point set at. */ + bool is_corner; + { + double *u = g_new(double, len); + chord_length_parameterize(data, u, len); + if ( u[len - 1] == 0.0 ) { + /* Zero-length path: every point in data[] is the same. + * + * (Clients aren't allowed to pass such data; handling the case is defensive + * programming.) + */ + g_free(u); + return 0; + } + + generate_bezier(bezier, data, u, len, tHat1, tHat2, error); + reparameterize(data, len, u, bezier); + + /* Find max deviation of points to fitted curve. */ + double const tolerance = sqrt(error + 1e-9); + double maxErrorRatio = compute_max_error_ratio(data, u, len, bezier, tolerance, &splitPoint); + + if ( fabs(maxErrorRatio) <= 1.0 ) { + BEZIER_ASSERT(bezier); + g_free(u); + return 1; + } + + /* If error not too large, then try some reparameterization and iteration. */ + if ( 0.0 <= maxErrorRatio && maxErrorRatio <= 3.0 ) { + for (int i = 0; i < maxIterations; i++) { + generate_bezier(bezier, data, u, len, tHat1, tHat2, error); + reparameterize(data, len, u, bezier); + maxErrorRatio = compute_max_error_ratio(data, u, len, bezier, tolerance, &splitPoint); + if ( fabs(maxErrorRatio) <= 1.0 ) { + BEZIER_ASSERT(bezier); + g_free(u); + return 1; + } + } + } + g_free(u); + is_corner = (maxErrorRatio < 0); + } + + if (is_corner) { + g_assert(splitPoint < unsigned(len)); + if (splitPoint == 0) { + if (is_zero(tHat1)) { + /* Got spike even with unconstrained initial tangent. */ + ++splitPoint; + } else { + return sp_bezier_fit_cubic_full(bezier, split_points, data, len, unconstrained_tangent, tHat2, + error, max_beziers); + } + } else if (splitPoint == unsigned(len - 1)) { + if (is_zero(tHat2)) { + /* Got spike even with unconstrained final tangent. */ + --splitPoint; + } else { + return sp_bezier_fit_cubic_full(bezier, split_points, data, len, tHat1, unconstrained_tangent, + error, max_beziers); + } + } + } + + if ( 1 < max_beziers ) { + /* + * Fitting failed -- split at max error point and fit recursively + */ + unsigned const rec_max_beziers1 = max_beziers - 1; + + NR::Point recTHat2, recTHat1; + if (is_corner) { + g_return_val_if_fail(0 < splitPoint && splitPoint < unsigned(len - 1), -1); + recTHat1 = recTHat2 = unconstrained_tangent; + } else { + /* Unit tangent vector at splitPoint. */ + recTHat2 = sp_darray_center_tangent(data, splitPoint, len); + recTHat1 = -recTHat2; + } + gint const nsegs1 = sp_bezier_fit_cubic_full(bezier, split_points, data, splitPoint + 1, + tHat1, recTHat2, error, rec_max_beziers1); + if ( nsegs1 < 0 ) { +#ifdef BEZIER_DEBUG + g_print("fit_cubic[1]: recursive call failed\n"); +#endif + return -1; + } + g_assert( nsegs1 != 0 ); + if (split_points != NULL) { + split_points[nsegs1 - 1] = splitPoint; + } + unsigned const rec_max_beziers2 = max_beziers - nsegs1; + gint const nsegs2 = sp_bezier_fit_cubic_full(bezier + nsegs1*4, + ( split_points == NULL + ? NULL + : split_points + nsegs1 ), + data + splitPoint, len - splitPoint, + recTHat1, tHat2, error, rec_max_beziers2); + if ( nsegs2 < 0 ) { +#ifdef BEZIER_DEBUG + g_print("fit_cubic[2]: recursive call failed\n"); +#endif + return -1; + } + +#ifdef BEZIER_DEBUG + g_print("fit_cubic: success[nsegs: %d+%d=%d] on max_beziers:%u\n", + nsegs1, nsegs2, nsegs1 + nsegs2, max_beziers); +#endif + return nsegs1 + nsegs2; + } else { + return -1; + } +} + + +/** + * Fill in \a bezier[] based on the given data and tangent requirements, using + * a least-squares fit. + * + * Each of tHat1 and tHat2 should be either a zero vector or a unit vector. + * If it is zero, then bezier[1 or 2] is estimated without constraint; otherwise, + * it bezier[1 or 2] is placed in the specified direction from bezier[0 or 3]. + * + * \param tolerance_sq Used only for an initial guess as to tangent directions + * when \a tHat1 or \a tHat2 is zero. + */ +static void +generate_bezier(NR::Point bezier[], + NR::Point const data[], gdouble const u[], unsigned const len, + NR::Point const &tHat1, NR::Point const &tHat2, + double const tolerance_sq) +{ + bool const est1 = is_zero(tHat1); + bool const est2 = is_zero(tHat2); + NR::Point est_tHat1( est1 + ? sp_darray_left_tangent(data, len, tolerance_sq) + : tHat1 ); + NR::Point est_tHat2( est2 + ? sp_darray_right_tangent(data, len, tolerance_sq) + : tHat2 ); + estimate_lengths(bezier, data, u, len, est_tHat1, est_tHat2); + /* We find that sp_darray_right_tangent tends to produce better results + for our current freehand tool than full estimation. */ + if (est1) { + estimate_bi(bezier, 1, data, u, len); + if (bezier[1] != bezier[0]) { + est_tHat1 = unit_vector(bezier[1] - bezier[0]); + } + estimate_lengths(bezier, data, u, len, est_tHat1, est_tHat2); + } +} + + +static void +estimate_lengths(NR::Point bezier[], + NR::Point const data[], gdouble const uPrime[], unsigned const len, + NR::Point const &tHat1, NR::Point const &tHat2) +{ + double C[2][2]; /* Matrix C. */ + double X[2]; /* Matrix X. */ + + /* Create the C and X matrices. */ + C[0][0] = 0.0; + C[0][1] = 0.0; + C[1][0] = 0.0; + C[1][1] = 0.0; + X[0] = 0.0; + X[1] = 0.0; + + /* First and last control points of the Bezier curve are positioned exactly at the first and + last data points. */ + bezier[0] = data[0]; + bezier[3] = data[len - 1]; + + for (unsigned i = 0; i < len; i++) { + /* Bezier control point coefficients. */ + double const b0 = B0(uPrime[i]); + double const b1 = B1(uPrime[i]); + double const b2 = B2(uPrime[i]); + double const b3 = B3(uPrime[i]); + + /* rhs for eqn */ + NR::Point const a1 = b1 * tHat1; + NR::Point const a2 = b2 * tHat2; + + C[0][0] += dot(a1, a1); + C[0][1] += dot(a1, a2); + C[1][0] = C[0][1]; + C[1][1] += dot(a2, a2); + + /* Additional offset to the data point from the predicted point if we were to set bezier[1] + to bezier[0] and bezier[2] to bezier[3]. */ + NR::Point const shortfall + = ( data[i] + - ( ( b0 + b1 ) * bezier[0] ) + - ( ( b2 + b3 ) * bezier[3] ) ); + X[0] += dot(a1, shortfall); + X[1] += dot(a2, shortfall); + } + + /* We've constructed a pair of equations in the form of a matrix product C * alpha = X. + Now solve for alpha. */ + double alpha_l, alpha_r; + + /* Compute the determinants of C and X. */ + double const det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1]; + if ( det_C0_C1 != 0 ) { + /* Apparently Kramer's rule. */ + double const det_C0_X = C[0][0] * X[1] - C[0][1] * X[0]; + double const det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1]; + alpha_l = det_X_C1 / det_C0_C1; + alpha_r = det_C0_X / det_C0_C1; + } else { + /* The matrix is under-determined. Try requiring alpha_l == alpha_r. + * + * One way of implementing the constraint alpha_l == alpha_r is to treat them as the same + * variable in the equations. We can do this by adding the columns of C to form a single + * column, to be multiplied by alpha to give the column vector X. + * + * We try each row in turn. + */ + double const c0 = C[0][0] + C[0][1]; + if (c0 != 0) { + alpha_l = alpha_r = X[0] / c0; + } else { + double const c1 = C[1][0] + C[1][1]; + if (c1 != 0) { + alpha_l = alpha_r = X[1] / c1; + } else { + /* Let the below code handle this. */ + alpha_l = alpha_r = 0.; + } + } + } + + /* If alpha negative, use the Wu/Barsky heuristic (see text). (If alpha is 0, you get + coincident control points that lead to divide by zero in any subsequent + NewtonRaphsonRootFind() call.) */ + /// \todo Check whether this special-casing is necessary now that + /// NewtonRaphsonRootFind handles non-positive denominator. + if ( alpha_l < 1.0e-6 || + alpha_r < 1.0e-6 ) + { + alpha_l = alpha_r = ( L2( data[len - 1] + - data[0] ) + / 3.0 ); + } + + /* Control points 1 and 2 are positioned an alpha distance out on the tangent vectors, left and + right, respectively. */ + bezier[1] = alpha_l * tHat1 + bezier[0]; + bezier[2] = alpha_r * tHat2 + bezier[3]; + + return; +} + +static double lensq(NR::Point const p) { + return dot(p, p); +} + +static void +estimate_bi(NR::Point bezier[4], unsigned const ei, + NR::Point const data[], double const u[], unsigned const len) +{ + g_return_if_fail(1 <= ei && ei <= 2); + unsigned const oi = 3 - ei; + double num[2] = {0., 0.}; + double den = 0.; + for (unsigned i = 0; i < len; ++i) { + double const ui = u[i]; + double const b[4] = { + B0(ui), + B1(ui), + B2(ui), + B3(ui) + }; + + for (unsigned d = 0; d < 2; ++d) { + num[d] += b[ei] * (b[0] * bezier[0][d] + + b[oi] * bezier[oi][d] + + b[3] * bezier[3][d] + + - data[i][d]); + } + den -= b[ei] * b[ei]; + } + + if (den != 0.) { + for (unsigned d = 0; d < 2; ++d) { + bezier[ei][d] = num[d] / den; + } + } else { + bezier[ei] = ( oi * bezier[0] + ei * bezier[3] ) / 3.; + } +} + +/** + * Given set of points and their parameterization, try to find a better assignment of parameter + * values for the points. + * + * \param d Array of digitized points. + * \param u Current parameter values. + * \param bezCurve Current fitted curve. + * \param len Number of values in both d and u arrays. + * Also the size of the array that is allocated for return. + */ +static void +reparameterize(NR::Point const d[], + unsigned const len, + double u[], + BezierCurve const bezCurve) +{ + g_assert( 2 <= len ); + + unsigned const last = len - 1; + g_assert( bezCurve[0] == d[0] ); + g_assert( bezCurve[3] == d[last] ); + g_assert( u[0] == 0.0 ); + g_assert( u[last] == 1.0 ); + /* Otherwise, consider including 0 and last in the below loop. */ + + for (unsigned i = 1; i < last; i++) { + u[i] = NewtonRaphsonRootFind(bezCurve, d[i], u[i]); + } +} + +/** + * Use Newton-Raphson iteration to find better root. + * + * \param Q Current fitted curve + * \param P Digitized point + * \param u Parameter value for "P" + * + * \return Improved u + */ +static gdouble +NewtonRaphsonRootFind(BezierCurve const Q, NR::Point const &P, gdouble const u) +{ + g_assert( 0.0 <= u ); + g_assert( u <= 1.0 ); + + /* Generate control vertices for Q'. */ + NR::Point Q1[3]; + for (unsigned i = 0; i < 3; i++) { + Q1[i] = 3.0 * ( Q[i+1] - Q[i] ); + } + + /* Generate control vertices for Q''. */ + NR::Point Q2[2]; + for (unsigned i = 0; i < 2; i++) { + Q2[i] = 2.0 * ( Q1[i+1] - Q1[i] ); + } + + /* Compute Q(u), Q'(u) and Q''(u). */ + NR::Point const Q_u = bezier_pt(3, Q, u); + NR::Point const Q1_u = bezier_pt(2, Q1, u); + NR::Point const Q2_u = bezier_pt(1, Q2, u); + + /* Compute f(u)/f'(u), where f is the derivative wrt u of distsq(u) = 0.5 * the square of the + distance from P to Q(u). Here we're using Newton-Raphson to find a stationary point in the + distsq(u), hopefully corresponding to a local minimum in distsq (and hence a local minimum + distance from P to Q(u)). */ + NR::Point const diff = Q_u - P; + double numerator = dot(diff, Q1_u); + double denominator = dot(Q1_u, Q1_u) + dot(diff, Q2_u); + + double improved_u; + if ( denominator > 0. ) { + /* One iteration of Newton-Raphson: + improved_u = u - f(u)/f'(u) */ + improved_u = u - ( numerator / denominator ); + } else { + /* Using Newton-Raphson would move in the wrong direction (towards a local maximum rather + than local minimum), so we move an arbitrary amount in the right direction. */ + if ( numerator > 0. ) { + improved_u = u * .98 - .01; + } else if ( numerator < 0. ) { + /* Deliberately asymmetrical, to reduce the chance of cycling. */ + improved_u = .031 + u * .98; + } else { + improved_u = u; + } + } + + if (!isFinite(improved_u)) { + improved_u = u; + } else if ( improved_u < 0.0 ) { + improved_u = 0.0; + } else if ( improved_u > 1.0 ) { + improved_u = 1.0; + } + + /* Ensure that improved_u isn't actually worse. */ + { + double const diff_lensq = lensq(diff); + for (double proportion = .125; ; proportion += .125) { + if ( lensq( bezier_pt(3, Q, improved_u) - P ) > diff_lensq ) { + if ( proportion > 1.0 ) { + //g_warning("found proportion %g", proportion); + improved_u = u; + break; + } + improved_u = ( ( 1 - proportion ) * improved_u + + proportion * u ); + } else { + break; + } + } + } + + DOUBLE_ASSERT(improved_u); + return improved_u; +} + +/** + * Evaluate a Bezier curve at parameter value \a t. + * + * \param degree The degree of the Bezier curve: 3 for cubic, 2 for quadratic etc. + * \param V The control points for the Bezier curve. Must have (\a degree+1) + * elements. + * \param t The "parameter" value, specifying whereabouts along the curve to + * evaluate. Typically in the range [0.0, 1.0]. + * + * Let s = 1 - t. + * BezierII(1, V) gives (s, t) * V, i.e. t of the way + * from V[0] to V[1]. + * BezierII(2, V) gives (s**2, 2*s*t, t**2) * V. + * BezierII(3, V) gives (s**3, 3 s**2 t, 3s t**2, t**3) * V. + * + * The derivative of BezierII(i, V) with respect to t + * is i * BezierII(i-1, V'), where for all j, V'[j] = + * V[j + 1] - V[j]. + */ +static NR::Point +bezier_pt(unsigned const degree, NR::Point const V[], gdouble const t) +{ + /** Pascal's triangle. */ + static int const pascal[4][4] = {{1}, + {1, 1}, + {1, 2, 1}, + {1, 3, 3, 1}}; + g_assert( degree < G_N_ELEMENTS(pascal) ); + gdouble const s = 1.0 - t; + + /* Calculate powers of t and s. */ + double spow[4]; + double tpow[4]; + spow[0] = 1.0; spow[1] = s; + tpow[0] = 1.0; tpow[1] = t; + for (unsigned i = 1; i < degree; ++i) { + spow[i + 1] = spow[i] * s; + tpow[i + 1] = tpow[i] * t; + } + + NR::Point ret = spow[degree] * V[0]; + for (unsigned i = 1; i <= degree; ++i) { + ret += pascal[degree][i] * spow[degree - i] * tpow[i] * V[i]; + } + return ret; +} + +/* + * ComputeLeftTangent, ComputeRightTangent, ComputeCenterTangent : + * Approximate unit tangents at endpoints and "center" of digitized curve + */ + +/** + * Estimate the (forward) tangent at point d[first + 0.5]. + * + * Unlike the center and right versions, this calculates the tangent in + * the way one might expect, i.e., wrt increasing index into d. + * \pre (2 \<= len) and (d[0] != d[1]). + **/ +NR::Point +sp_darray_left_tangent(NR::Point const d[], unsigned const len) +{ + g_assert( len >= 2 ); + g_assert( d[0] != d[1] ); + return unit_vector( d[1] - d[0] ); +} + +/** + * Estimates the (backward) tangent at d[last - 0.5]. + * + * \note The tangent is "backwards", i.e. it is with respect to + * decreasing index rather than increasing index. + * + * \pre 2 \<= len. + * \pre d[len - 1] != d[len - 2]. + * \pre all[p in d] in_svg_plane(p). + */ +static NR::Point +sp_darray_right_tangent(NR::Point const d[], unsigned const len) +{ + g_assert( 2 <= len ); + unsigned const last = len - 1; + unsigned const prev = last - 1; + g_assert( d[last] != d[prev] ); + return unit_vector( d[prev] - d[last] ); +} + +/** + * Estimate the (forward) tangent at point d[0]. + * + * Unlike the center and right versions, this calculates the tangent in + * the way one might expect, i.e., wrt increasing index into d. + * + * \pre 2 \<= len. + * \pre d[0] != d[1]. + * \pre all[p in d] in_svg_plane(p). + * \post is_unit_vector(ret). + **/ +NR::Point +sp_darray_left_tangent(NR::Point const d[], unsigned const len, double const tolerance_sq) +{ + g_assert( 2 <= len ); + g_assert( 0 <= tolerance_sq ); + for (unsigned i = 1;;) { + NR::Point const pi(d[i]); + NR::Point const t(pi - d[0]); + double const distsq = dot(t, t); + if ( tolerance_sq < distsq ) { + return unit_vector(t); + } + ++i; + if (i == len) { + return ( distsq == 0 + ? sp_darray_left_tangent(d, len) + : unit_vector(t) ); + } + } +} + +/** + * Estimates the (backward) tangent at d[last]. + * + * \note The tangent is "backwards", i.e. it is with respect to + * decreasing index rather than increasing index. + * + * \pre 2 \<= len. + * \pre d[len - 1] != d[len - 2]. + * \pre all[p in d] in_svg_plane(p). + */ +NR::Point +sp_darray_right_tangent(NR::Point const d[], unsigned const len, double const tolerance_sq) +{ + g_assert( 2 <= len ); + g_assert( 0 <= tolerance_sq ); + unsigned const last = len - 1; + for (unsigned i = last - 1;; i--) { + NR::Point const pi(d[i]); + NR::Point const t(pi - d[last]); + double const distsq = dot(t, t); + if ( tolerance_sq < distsq ) { + return unit_vector(t); + } + if (i == 0) { + return ( distsq == 0 + ? sp_darray_right_tangent(d, len) + : unit_vector(t) ); + } + } +} + +/** + * Estimates the (backward) tangent at d[center], by averaging the two + * segments connected to d[center] (and then normalizing the result). + * + * \note The tangent is "backwards", i.e. it is with respect to + * decreasing index rather than increasing index. + * + * \pre (0 \< center \< len - 1) and d is uniqued (at least in + * the immediate vicinity of \a center). + */ +static NR::Point +sp_darray_center_tangent(NR::Point const d[], + unsigned const center, + unsigned const len) +{ + g_assert( center != 0 ); + g_assert( center < len - 1 ); + + NR::Point ret; + if ( d[center + 1] == d[center - 1] ) { + /* Rotate 90 degrees in an arbitrary direction. */ + NR::Point const diff = d[center] - d[center - 1]; + ret = NR::rot90(diff); + } else { + ret = d[center - 1] - d[center + 1]; + } + ret.normalize(); + return ret; +} + + +/** + * Assign parameter values to digitized points using relative distances between points. + * + * \pre Parameter array u must have space for \a len items. + */ +static void +chord_length_parameterize(NR::Point const d[], gdouble u[], unsigned const len) +{ + g_return_if_fail( 2 <= len ); + + /* First let u[i] equal the distance travelled along the path from d[0] to d[i]. */ + u[0] = 0.0; + for (unsigned i = 1; i < len; i++) { + double const dist = L2( d[i] - d[i-1] ); + u[i] = u[i-1] + dist; + } + + /* Then scale to [0.0 .. 1.0]. */ + gdouble tot_len = u[len - 1]; + g_return_if_fail( tot_len != 0 ); + if (isFinite(tot_len)) { + for (unsigned i = 1; i < len; ++i) { + u[i] /= tot_len; + } + } else { + /* We could do better, but this probably never happens anyway. */ + for (unsigned i = 1; i < len; ++i) { + u[i] = i / (gdouble) ( len - 1 ); + } + } + + /** \todo + * It's been reported that u[len - 1] can differ from 1.0 on some + * systems (amd64), despite it having been calculated as x / x where x + * is isFinite and non-zero. + */ + if (u[len - 1] != 1) { + double const diff = u[len - 1] - 1; + if (fabs(diff) > 1e-13) { + g_warning("u[len - 1] = %19g (= 1 + %19g), expecting exactly 1", + u[len - 1], diff); + } + u[len - 1] = 1; + } + +#ifdef BEZIER_DEBUG + g_assert( u[0] == 0.0 && u[len - 1] == 1.0 ); + for (unsigned i = 1; i < len; i++) { + g_assert( u[i] >= u[i-1] ); + } +#endif +} + + + + +/** + * Find the maximum squared distance of digitized points to fitted curve, and (if this maximum + * error is non-zero) set \a *splitPoint to the corresponding index. + * + * \pre 2 \<= len. + * \pre u[0] == 0. + * \pre u[len - 1] == 1.0. + * \post ((ret == 0.0) + * || ((*splitPoint \< len - 1) + * \&\& (*splitPoint != 0 || ret \< 0.0))). + */ +static gdouble +compute_max_error_ratio(NR::Point const d[], double const u[], unsigned const len, + BezierCurve const bezCurve, double const tolerance, + unsigned *const splitPoint) +{ + g_assert( 2 <= len ); + unsigned const last = len - 1; + g_assert( bezCurve[0] == d[0] ); + g_assert( bezCurve[3] == d[last] ); + g_assert( u[0] == 0.0 ); + g_assert( u[last] == 1.0 ); + /* I.e. assert that the error for the first & last points is zero. + * Otherwise we should include those points in the below loop. + * The assertion is also necessary to ensure 0 < splitPoint < last. + */ + + double maxDistsq = 0.0; /* Maximum error */ + double max_hook_ratio = 0.0; + unsigned snap_end = 0; + NR::Point prev = bezCurve[0]; + for (unsigned i = 1; i <= last; i++) { + NR::Point const curr = bezier_pt(3, bezCurve, u[i]); + double const distsq = lensq( curr - d[i] ); + if ( distsq > maxDistsq ) { + maxDistsq = distsq; + *splitPoint = i; + } + double const hook_ratio = compute_hook(prev, curr, .5 * (u[i - 1] + u[i]), bezCurve, tolerance); + if (max_hook_ratio < hook_ratio) { + max_hook_ratio = hook_ratio; + snap_end = i; + } + prev = curr; + } + + double const dist_ratio = sqrt(maxDistsq) / tolerance; + double ret; + if (max_hook_ratio <= dist_ratio) { + ret = dist_ratio; + } else { + g_assert(0 < snap_end); + ret = -max_hook_ratio; + *splitPoint = snap_end - 1; + } + g_assert( ret == 0.0 + || ( ( *splitPoint < last ) + && ( *splitPoint != 0 || ret < 0. ) ) ); + return ret; +} + +/** + * Whereas compute_max_error_ratio() checks for itself that each data point + * is near some point on the curve, this function checks that each point on + * the curve is near some data point (or near some point on the polyline + * defined by the data points, or something like that: we allow for a + * "reasonable curviness" from such a polyline). "Reasonable curviness" + * means we draw a circle centred at the midpoint of a..b, of radius + * proportional to the length |a - b|, and require that each point on the + * segment of bezCurve between the parameters of a and b be within that circle. + * If any point P on the bezCurve segment is outside of that allowable + * region (circle), then we return some metric that increases with the + * distance from P to the circle. + * + * Given that this is a fairly arbitrary criterion for finding appropriate + * places for sharp corners, we test only one point on bezCurve, namely + * the point on bezCurve with parameter halfway between our estimated + * parameters for a and b. (Alternatives are taking the farthest of a + * few parameters between those of a and b, or even using a variant of + * NewtonRaphsonFindRoot() for finding the maximum rather than minimum + * distance.) + */ +static double +compute_hook(NR::Point const &a, NR::Point const &b, double const u, BezierCurve const bezCurve, + double const tolerance) +{ + NR::Point const P = bezier_pt(3, bezCurve, u); + NR::Point const diff = .5 * (a + b) - P; + double const dist = NR::L2(diff); + if (dist < tolerance) { + return 0; + } + double const allowed = NR::L2(b - a) + tolerance; + return dist / allowed; + /** \todo + * effic: Hooks are very rare. We could start by comparing + * distsq, only resorting to the more expensive L2 in cases of + * uncertainty. + */ +} + +/* + 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 : diff --git a/src/display/bezier-utils.h b/src/display/bezier-utils.h new file mode 100644 index 000000000..f281ab220 --- /dev/null +++ b/src/display/bezier-utils.h @@ -0,0 +1,49 @@ +#ifndef __SP_BEZIER_UTILS_H__ +#define __SP_BEZIER_UTILS_H__ + +/* + * An Algorithm for Automatically Fitting Digitized Curves + * by Philip J. Schneider + * from "Graphics Gems", Academic Press, 1990 + * + * Authors: + * Philip J. Schneider + * Lauris Kaplinski + * + * Copyright (C) 1990 Philip J. Schneider + * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc. + * + * Released under GNU GPL + */ + +#include +#include + +/* Bezier approximation utils */ + +gint sp_bezier_fit_cubic(NR::Point bezier[], NR::Point const data[], gint len, gdouble error); + +gint sp_bezier_fit_cubic_r(NR::Point bezier[], NR::Point const data[], gint len, gdouble error, + unsigned max_beziers); + +gint sp_bezier_fit_cubic_full(NR::Point bezier[], int split_points[], NR::Point const data[], gint len, + NR::Point const &tHat1, NR::Point const &tHat2, + gdouble error, unsigned max_beziers); + +NR::Point sp_darray_left_tangent(NR::Point const d[], unsigned const len); +NR::Point sp_darray_left_tangent(NR::Point const d[], unsigned const len, double const tolerance_sq); +NR::Point sp_darray_right_tangent(NR::Point const d[], unsigned const length, double const tolerance_sq); + + +#endif /* __SP_BEZIER_UTILS_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:encoding=utf-8:textwidth=99 : diff --git a/src/display/canvas-arena.cpp b/src/display/canvas-arena.cpp new file mode 100644 index 000000000..1d77de158 --- /dev/null +++ b/src/display/canvas-arena.cpp @@ -0,0 +1,451 @@ +#define __SP_CANVAS_ARENA_C__ + +/* + * RGBA display list system for inkscape + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +enum { + ARENA_EVENT, + LAST_SIGNAL +}; + +static void sp_canvas_arena_class_init(SPCanvasArenaClass *klass); +static void sp_canvas_arena_init(SPCanvasArena *group); +static void sp_canvas_arena_destroy(GtkObject *object); + +static void sp_canvas_arena_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags); +static void sp_canvas_arena_render (SPCanvasItem *item, SPCanvasBuf *buf); +static double sp_canvas_arena_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item); +static gint sp_canvas_arena_event (SPCanvasItem *item, GdkEvent *event); + +static gint sp_canvas_arena_send_event (SPCanvasArena *arena, GdkEvent *event); + +static void sp_canvas_arena_request_update (NRArena *arena, NRArenaItem *item, void *data); +static void sp_canvas_arena_request_render (NRArena *arena, NRRectL *area, void *data); + +NRArenaEventVector carenaev = { + {NULL}, + sp_canvas_arena_request_update, + sp_canvas_arena_request_render +}; + +static SPCanvasItemClass *parent_class; +static guint signals[LAST_SIGNAL] = {0}; + +GtkType +sp_canvas_arena_get_type (void) +{ + static GtkType type = 0; + if (!type) { + GtkTypeInfo info = { + "SPCanvasArena", + sizeof (SPCanvasArena), + sizeof (SPCanvasArenaClass), + (GtkClassInitFunc) sp_canvas_arena_class_init, + (GtkObjectInitFunc) sp_canvas_arena_init, + NULL, NULL, NULL + }; + type = gtk_type_unique (SP_TYPE_CANVAS_ITEM, &info); + } + return type; +} + +static void +sp_canvas_arena_class_init (SPCanvasArenaClass *klass) +{ + GtkObjectClass *object_class; + SPCanvasItemClass *item_class; + + object_class = (GtkObjectClass *) klass; + item_class = (SPCanvasItemClass *) klass; + + parent_class = (SPCanvasItemClass*)gtk_type_class (SP_TYPE_CANVAS_ITEM); + + signals[ARENA_EVENT] = gtk_signal_new ("arena_event", + GTK_RUN_LAST, + GTK_CLASS_TYPE(object_class), + GTK_SIGNAL_OFFSET (SPCanvasArenaClass, arena_event), + sp_marshal_INT__POINTER_POINTER, + GTK_TYPE_INT, 2, GTK_TYPE_POINTER, GTK_TYPE_POINTER); + + object_class->destroy = sp_canvas_arena_destroy; + + item_class->update = sp_canvas_arena_update; + item_class->render = sp_canvas_arena_render; + item_class->point = sp_canvas_arena_point; + item_class->event = sp_canvas_arena_event; +} + +static void +sp_canvas_arena_init (SPCanvasArena *arena) +{ + arena->sticky = FALSE; + + arena->arena = NRArena::create(); + arena->root = NRArenaGroup::create(arena->arena); + nr_arena_group_set_transparent (NR_ARENA_GROUP (arena->root), TRUE); + +#ifdef arena_item_tile_cache + arena->root->skipCaching=true; +#endif + + arena->active = NULL; + + nr_active_object_add_listener ((NRActiveObject *) arena->arena, (NRObjectEventVector *) &carenaev, sizeof (carenaev), arena); +} + +static void +sp_canvas_arena_destroy (GtkObject *object) +{ + SPCanvasArena *arena = SP_CANVAS_ARENA (object); + + if (arena->active) { + nr_object_unref ((NRObject *) arena->active); + arena->active = NULL; + } + + if (arena->root) { + nr_arena_item_unref (arena->root); + arena->root = NULL; + } + + if (arena->arena) { + nr_active_object_remove_listener_by_data ((NRActiveObject *) arena->arena, arena); + + nr_object_unref ((NRObject *) arena->arena); + arena->arena = NULL; + } + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} + +static void +sp_canvas_arena_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags) +{ + SPCanvasArena *arena = SP_CANVAS_ARENA (item); + + if (((SPCanvasItemClass *) parent_class)->update) + (* ((SPCanvasItemClass *) parent_class)->update) (item, affine, flags); + + arena->gc.transform = affine; + + guint reset; + reset = (flags & SP_CANVAS_UPDATE_AFFINE)? NR_ARENA_ITEM_STATE_ALL : NR_ARENA_ITEM_STATE_NONE; + + nr_arena_item_invoke_update (arena->root, NULL, &arena->gc, NR_ARENA_ITEM_STATE_ALL, reset); + + item->x1 = arena->root->bbox.x0 - 1; + item->y1 = arena->root->bbox.y0 - 1; + item->x2 = arena->root->bbox.x1 + 1; + item->y2 = arena->root->bbox.y1 + 1; + + if (arena->cursor) { + /* Mess with enter/leave notifiers */ + NRArenaItem *new_arena = nr_arena_item_invoke_pick (arena->root, arena->c, arena->arena->delta, arena->sticky); + if (new_arena != arena->active) { + GdkEventCrossing ec; + ec.window = GTK_WIDGET (item->canvas)->window; + ec.send_event = TRUE; + ec.subwindow = ec.window; + ec.time = GDK_CURRENT_TIME; + ec.x = arena->c[NR::X]; + ec.y = arena->c[NR::Y]; + /* fixme: */ + if (arena->active) { + ec.type = GDK_LEAVE_NOTIFY; + sp_canvas_arena_send_event (arena, (GdkEvent *) &ec); + } + /* fixme: This is not optimal - better track ::destroy (Lauris) */ + if (arena->active) nr_object_unref ((NRObject *) arena->active); + arena->active = new_arena; + if (arena->active) nr_object_ref ((NRObject *) arena->active); + if (arena->active) { + ec.type = GDK_ENTER_NOTIFY; + sp_canvas_arena_send_event (arena, (GdkEvent *) &ec); + } + } + } +} + +#ifdef arena_item_tile_cache +extern void age_cache(void); +#endif + +static void +sp_canvas_arena_render (SPCanvasItem *item, SPCanvasBuf *buf) +{ + gint bw, bh, sw, sh; + gint x, y; + + SPCanvasArena *arena = SP_CANVAS_ARENA (item); + + nr_arena_item_invoke_update (arena->root, NULL, &arena->gc, + NR_ARENA_ITEM_STATE_BBOX | NR_ARENA_ITEM_STATE_RENDER, + NR_ARENA_ITEM_STATE_NONE); + + sp_canvas_prepare_buffer(buf); + +#ifdef arena_item_tile_cache + age_cache(); +#endif + bw = buf->rect.x1 - buf->rect.x0; + bh = buf->rect.y1 - buf->rect.y0; + if ((bw < 1) || (bh < 1)) return; + + if (arena->arena->rendermode != RENDERMODE_OUTLINE) { // use 256K as a compromise to not slow down gradients + /* 256K is the cached buffer and we need 4 channels */ + if (bw * bh < 65536) { // 256K/4 + /* We can go with single buffer */ + sw = bw; + sh = bh; + } else if (bw <= 4096) { + /* Go with row buffer */ + sw = bw; + sh = 65536 / bw; + } else if (bh <= 4096) { + /* Go with column buffer */ + sw = 65536 / bh; + sh = bh; + } else { + sw = 256; + sh = 256; + } + } else { // paths only, so 1M works faster + /* 1M is the cached buffer and we need 4 channels */ + if (bw * bh < 262144) { // 1M/4 + /* We can go with single buffer */ + sw = bw; + sh = bh; + } else if (bw <= 8192) { + /* Go with row buffer */ + sw = bw; + sh = 262144 / bw; + } else if (bh <= 8192) { + /* Go with column buffer */ + sw = 262144 / bh; + sh = bh; + } else { + sw = 512; + sh = 512; + } + } + +/* fixme: RGB transformed bitmap blit is not implemented (Lauris) */ +/* And even if it would be, unless it uses MMX there is little reason to go RGB */ +#define STRICT_RGBA + + for (y = buf->rect.y0; y < buf->rect.y1; y += sh) { + for (x = buf->rect.x0; x < buf->rect.x1; x += sw) { + NRRectL area; +#ifdef STRICT_RGBA + NRPixBlock pb; +#endif + NRPixBlock cb; + + area.x0 = x; + area.y0 = y; + area.x1 = MIN (x + sw, buf->rect.x1); + area.y1 = MIN (y + sh, buf->rect.y1); + +#ifdef STRICT_RGBA + nr_pixblock_setup_fast (&pb, NR_PIXBLOCK_MODE_R8G8B8A8P, area.x0, area.y0, area.x1, area.y1, TRUE); + /* fixme: */ + pb.empty = FALSE; +#endif + + nr_pixblock_setup_extern (&cb, NR_PIXBLOCK_MODE_R8G8B8, area.x0, area.y0, area.x1, area.y1, + buf->buf + (y - buf->rect.y0) * buf->buf_rowstride + 3 * (x - buf->rect.x0), + buf->buf_rowstride, + FALSE, FALSE); + +#ifdef STRICT_RGBA + nr_arena_item_invoke_render (arena->root, &area, &pb, 0); + nr_blit_pixblock_pixblock (&cb, &pb); + nr_pixblock_release (&pb); +#else + nr_arena_item_invoke_render (arena->root, &area, &cb, 0); +#endif + + nr_pixblock_release (&cb); + } + } +} + +static double +sp_canvas_arena_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item) +{ + SPCanvasArena *arena = SP_CANVAS_ARENA (item); + + nr_arena_item_invoke_update (arena->root, NULL, &arena->gc, + NR_ARENA_ITEM_STATE_BBOX | NR_ARENA_ITEM_STATE_PICK, + NR_ARENA_ITEM_STATE_NONE); + + NRArenaItem *picked = nr_arena_item_invoke_pick (arena->root, p, arena->arena->delta, arena->sticky); + + arena->picked = picked; + + if (picked) { + *actual_item = item; + return 0.0; + } + + return 1e18; +} + +static gint +sp_canvas_arena_event (SPCanvasItem *item, GdkEvent *event) +{ + NRArenaItem *new_arena; + /* fixme: This sucks, we have to handle enter/leave notifiers */ + + SPCanvasArena *arena = SP_CANVAS_ARENA (item); + + gint ret = FALSE; + + switch (event->type) { + case GDK_ENTER_NOTIFY: + if (!arena->cursor) { + if (arena->active) { + //g_warning ("Cursor entered to arena with already active item"); + nr_object_unref ((NRObject *) arena->active); + } + arena->cursor = TRUE; + + /* TODO ... event -> arena transform? */ + arena->c = NR::Point(event->crossing.x, event->crossing.y); + + /* fixme: Not sure abut this, but seems the right thing (Lauris) */ + nr_arena_item_invoke_update (arena->root, NULL, &arena->gc, NR_ARENA_ITEM_STATE_PICK, NR_ARENA_ITEM_STATE_NONE); + arena->active = nr_arena_item_invoke_pick (arena->root, arena->c, arena->arena->delta, arena->sticky); + if (arena->active) nr_object_ref ((NRObject *) arena->active); + ret = sp_canvas_arena_send_event (arena, event); + } + break; + case GDK_LEAVE_NOTIFY: + if (arena->cursor) { + ret = sp_canvas_arena_send_event (arena, event); + if (arena->active) nr_object_unref ((NRObject *) arena->active); + arena->active = NULL; + arena->cursor = FALSE; + } + break; + case GDK_MOTION_NOTIFY: + /* TODO ... event -> arena transform? */ + arena->c = NR::Point(event->motion.x, event->motion.y); + + /* fixme: Not sure abut this, but seems the right thing (Lauris) */ + nr_arena_item_invoke_update (arena->root, NULL, &arena->gc, NR_ARENA_ITEM_STATE_PICK, NR_ARENA_ITEM_STATE_NONE); + new_arena = nr_arena_item_invoke_pick (arena->root, arena->c, arena->arena->delta, arena->sticky); + if (new_arena != arena->active) { + GdkEventCrossing ec; + ec.window = event->motion.window; + ec.send_event = event->motion.send_event; + ec.subwindow = event->motion.window; + ec.time = event->motion.time; + ec.x = event->motion.x; + ec.y = event->motion.y; + /* fixme: */ + if (arena->active) { + ec.type = GDK_LEAVE_NOTIFY; + ret = sp_canvas_arena_send_event (arena, (GdkEvent *) &ec); + } + if (arena->active) nr_object_unref ((NRObject *) arena->active); + arena->active = new_arena; + if (arena->active) nr_object_ref ((NRObject *) arena->active); + if (arena->active) { + ec.type = GDK_ENTER_NOTIFY; + ret = sp_canvas_arena_send_event (arena, (GdkEvent *) &ec); + } + } + ret = sp_canvas_arena_send_event (arena, event); + break; + default: + /* Just send event */ + ret = sp_canvas_arena_send_event (arena, event); + break; + } + + return ret; +} + +static gint +sp_canvas_arena_send_event (SPCanvasArena *arena, GdkEvent *event) +{ + gint ret = FALSE; + + /* Send event to arena */ + gtk_signal_emit (GTK_OBJECT (arena), signals[ARENA_EVENT], arena->active, event, &ret); + + return ret; +} + +static void +sp_canvas_arena_request_update (NRArena *arena, NRArenaItem *item, void *data) +{ + sp_canvas_item_request_update (SP_CANVAS_ITEM (data)); +} + +static void +sp_canvas_arena_request_render (NRArena *arena, NRRectL *area, void *data) +{ + sp_canvas_request_redraw (SP_CANVAS_ITEM (data)->canvas, area->x0, area->y0, area->x1, area->y1); +} + +void +sp_canvas_arena_set_pick_delta (SPCanvasArena *ca, gdouble delta) +{ + g_return_if_fail (ca != NULL); + g_return_if_fail (SP_IS_CANVAS_ARENA (ca)); + + /* fixme: repick? */ + ca->delta = delta; +} + +void +sp_canvas_arena_set_sticky (SPCanvasArena *ca, gboolean sticky) +{ + g_return_if_fail (ca != NULL); + g_return_if_fail (SP_IS_CANVAS_ARENA (ca)); + + /* fixme: repick? */ + ca->sticky = sticky; +} + +void +sp_canvas_arena_render_pixblock (SPCanvasArena *ca, NRPixBlock *pb) +{ + NRRectL area; + + g_return_if_fail (ca != NULL); + g_return_if_fail (SP_IS_CANVAS_ARENA (ca)); + + /* fixme: */ + pb->empty = FALSE; + + area.x0 = pb->area.x0; + area.y0 = pb->area.y0; + area.x1 = pb->area.x1; + area.y1 = pb->area.y1; + + nr_arena_item_invoke_render (ca->root, &area, pb, 0); +} + diff --git a/src/display/canvas-arena.h b/src/display/canvas-arena.h new file mode 100644 index 000000000..805bee60a --- /dev/null +++ b/src/display/canvas-arena.h @@ -0,0 +1,58 @@ +#ifndef __SP_CANVAS_ARENA_H__ +#define __SP_CANVAS_ARENA_H__ + +/* + * RGBA display list system for inkscape + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +struct SPCanvasArena; +struct SPCanvasArenaClass; + +#define SP_TYPE_CANVAS_ARENA (sp_canvas_arena_get_type ()) +#define SP_CANVAS_ARENA(obj) (GTK_CHECK_CAST ((obj), SP_TYPE_CANVAS_ARENA, SPCanvasArena)) +#define SP_CANVAS_ARENA_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), SP_TYPE_CANVAS_ARENA, SPCanvasArenaClass)) +#define SP_IS_CANVAS_ARENA(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_CANVAS_ARENA)) +#define SP_IS_CANVAS_ARENA_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), SP_TYPE_CANVAS_ARENA)) + +#include "../display/sp-canvas.h" +#include "nr-arena-item.h" + +struct SPCanvasArena { + SPCanvasItem item; + + guint cursor : 1; + guint sticky : 1; + NR::Point c; // what is this? + + NRArena *arena; + NRArenaItem *root; + NRGC gc; + + NRArenaItem *active; + /* fixme: */ + NRArenaItem *picked; + gdouble delta; +}; + +struct SPCanvasArenaClass { + SPCanvasItemClass parent_class; + + gint (* arena_event) (SPCanvasArena *carena, NRArenaItem *item, GdkEvent *event); +}; + +GtkType sp_canvas_arena_get_type (void); + +void sp_canvas_arena_set_pick_delta (SPCanvasArena *ca, gdouble delta); +void sp_canvas_arena_set_sticky (SPCanvasArena *ca, gboolean sticky); + +void sp_canvas_arena_render_pixblock (SPCanvasArena *ca, NRPixBlock *pb); + +#endif diff --git a/src/display/canvas-bpath.cpp b/src/display/canvas-bpath.cpp new file mode 100644 index 000000000..a6434ae3d --- /dev/null +++ b/src/display/canvas-bpath.cpp @@ -0,0 +1,485 @@ +#define __SP_CANVAS_BPATH_C__ + +/* + * Simple bezier bpath CanvasItem for inkscape + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc. + * + * Released under GNU GPL + * + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include "sp-canvas-util.h" +#include "canvas-bpath.h" +#include "display/display-forward.h" +#include "display/curve.h" +#include +#include +#include +#include +#include + +void nr_pixblock_render_bpath_rgba (Shape* theS,uint32_t color,NRRectL &area,char* destBuf,int stride); + +static void sp_canvas_bpath_class_init (SPCanvasBPathClass *klass); +static void sp_canvas_bpath_init (SPCanvasBPath *path); +static void sp_canvas_bpath_destroy (GtkObject *object); + +static void sp_canvas_bpath_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags); +static void sp_canvas_bpath_render (SPCanvasItem *item, SPCanvasBuf *buf); +static double sp_canvas_bpath_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item); + +static SPCanvasItemClass *parent_class; + +GtkType +sp_canvas_bpath_get_type (void) +{ + static GtkType type = 0; + if (!type) { + GtkTypeInfo info = { + "SPCanvasBPath", + sizeof (SPCanvasBPath), + sizeof (SPCanvasBPathClass), + (GtkClassInitFunc) sp_canvas_bpath_class_init, + (GtkObjectInitFunc) sp_canvas_bpath_init, + NULL, NULL, NULL + }; + type = gtk_type_unique (SP_TYPE_CANVAS_ITEM, &info); + } + return type; +} + +static void +sp_canvas_bpath_class_init (SPCanvasBPathClass *klass) +{ + GtkObjectClass *object_class; + SPCanvasItemClass *item_class; + + object_class = GTK_OBJECT_CLASS (klass); + item_class = (SPCanvasItemClass *) klass; + + parent_class = (SPCanvasItemClass*)gtk_type_class (SP_TYPE_CANVAS_ITEM); + + object_class->destroy = sp_canvas_bpath_destroy; + + item_class->update = sp_canvas_bpath_update; + item_class->render = sp_canvas_bpath_render; + item_class->point = sp_canvas_bpath_point; +} + +static void +sp_canvas_bpath_init (SPCanvasBPath * bpath) +{ + bpath->fill_rgba = 0x000000ff; + bpath->fill_rule = SP_WIND_RULE_EVENODD; + + bpath->stroke_rgba = 0x00000000; + bpath->stroke_width = 1.0; + bpath->stroke_linejoin = SP_STROKE_LINEJOIN_MITER; + bpath->stroke_linecap = SP_STROKE_LINECAP_BUTT; + bpath->stroke_miterlimit = 11.0; + + bpath->fill_shp=NULL; + bpath->stroke_shp=NULL; +} + +static void +sp_canvas_bpath_destroy (GtkObject *object) +{ + SPCanvasBPath *cbp = SP_CANVAS_BPATH (object); + if (cbp->fill_shp) { + delete cbp->fill_shp; + cbp->fill_shp = NULL; + } + + if (cbp->stroke_shp) { + delete cbp->stroke_shp; + cbp->stroke_shp = NULL; + } + if (cbp->curve) { + cbp->curve = sp_curve_unref (cbp->curve); + } + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} + +static void +sp_canvas_bpath_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags) +{ + SPCanvasBPath *cbp = SP_CANVAS_BPATH (item); + + sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2); + + if (((SPCanvasItemClass *) parent_class)->update) + ((SPCanvasItemClass *) parent_class)->update (item, affine, flags); + + sp_canvas_item_reset_bounds (item); + + if (cbp->fill_shp) { + delete cbp->fill_shp; + cbp->fill_shp = NULL; + } + + if (cbp->stroke_shp) { + delete cbp->stroke_shp; + cbp->stroke_shp = NULL; + } + if (!cbp->curve) return; + + NRRect dbox; + + dbox.x0 = dbox.y0 = 0.0; + dbox.x1 = dbox.y1 = -1.0; + + if ((cbp->fill_rgba & 0xff) || (cbp->stroke_rgba & 0xff)) { + Path* thePath=new Path; + thePath->LoadArtBPath(cbp->curve->bpath, affine, true); + thePath->Convert(0.25); + if ((cbp->fill_rgba & 0xff) && (cbp->curve->end > 2)) { + Shape* theShape=new Shape; + thePath->Fill(theShape,0); + if ( cbp->fill_shp == NULL ) cbp->fill_shp=new Shape; + if ( cbp->fill_rule == SP_WIND_RULE_EVENODD ) { + cbp->fill_shp->ConvertToShape(theShape,fill_oddEven); + } else { + cbp->fill_shp->ConvertToShape(theShape,fill_nonZero); + } + delete theShape; + cbp->fill_shp->CalcBBox(); + if ( cbp->fill_shp->leftX < cbp->fill_shp->rightX ) { + if ( dbox.x0 >= dbox.x1 ) { + dbox.x0 = cbp->fill_shp->leftX; dbox.x1 = cbp->fill_shp->rightX; + dbox.y0 = cbp->fill_shp->topY; dbox.y1 = cbp->fill_shp->bottomY; + } else { + if ( cbp->fill_shp->leftX < dbox.x0 ) dbox.x0=cbp->fill_shp->leftX; + if ( cbp->fill_shp->rightX > dbox.x1 ) dbox.x1=cbp->fill_shp->rightX; + if ( cbp->fill_shp->topY < dbox.y0 ) dbox.y0=cbp->fill_shp->topY; + if ( cbp->fill_shp->bottomY > dbox.y1 ) dbox.y1=cbp->fill_shp->bottomY; + } + } + } + if ((cbp->stroke_rgba & 0xff) && (cbp->curve->end > 1)) { + JoinType join=join_straight; +// Shape* theShape=new Shape; + ButtType butt=butt_straight; + if ( cbp->stroke_shp == NULL ) cbp->stroke_shp=new Shape; + if ( cbp->stroke_linecap == SP_STROKE_LINECAP_BUTT ) butt=butt_straight; + if ( cbp->stroke_linecap == SP_STROKE_LINECAP_ROUND ) butt=butt_round; + if ( cbp->stroke_linecap == SP_STROKE_LINECAP_SQUARE ) butt=butt_square; + if ( cbp->stroke_linejoin == SP_STROKE_LINEJOIN_MITER ) join=join_pointy; + if ( cbp->stroke_linejoin == SP_STROKE_LINEJOIN_ROUND ) join=join_round; + if ( cbp->stroke_linejoin == SP_STROKE_LINEJOIN_BEVEL ) join=join_straight; + thePath->Stroke(cbp->stroke_shp,false,0.5*cbp->stroke_width, join,butt,cbp->stroke_width*cbp->stroke_miterlimit ); + // thePath->Stroke(theShape,false,0.5*cbp->stroke_width, join,butt,cbp->stroke_width*cbp->stroke_miterlimit ); + // cbp->stroke_shp->ConvertToShape(theShape,fill_nonZero); + + cbp->stroke_shp->CalcBBox(); + if ( cbp->stroke_shp->leftX < cbp->stroke_shp->rightX ) { + if ( dbox.x0 >= dbox.x1 ) { + dbox.x0 = cbp->stroke_shp->leftX;dbox.x1 = cbp->stroke_shp->rightX; + dbox.y0 = cbp->stroke_shp->topY;dbox.y1 = cbp->stroke_shp->bottomY; + } else { + if ( cbp->stroke_shp->leftX < dbox.x0 ) dbox.x0 = cbp->stroke_shp->leftX; + if ( cbp->stroke_shp->rightX > dbox.x1 ) dbox.x1 = cbp->stroke_shp->rightX; + if ( cbp->stroke_shp->topY < dbox.y0 ) dbox.y0 = cbp->stroke_shp->topY; + if ( cbp->stroke_shp->bottomY > dbox.y1 ) dbox.y1 = cbp->stroke_shp->bottomY; + } + } +// delete theShape; + } + delete thePath; + } + + + item->x1 = (int)dbox.x0; + item->y1 = (int)dbox.y0; + item->x2 = (int)dbox.x1; + item->y2 = (int)dbox.y1; + + sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2); +} + +static void +sp_canvas_bpath_render (SPCanvasItem *item, SPCanvasBuf *buf) +{ + SPCanvasBPath *cbp = SP_CANVAS_BPATH (item); + + sp_canvas_prepare_buffer(buf); + + NRRectL area; + area.x0=buf->rect.x0; + area.x1=buf->rect.x1; + area.y0=buf->rect.y0; + area.y1=buf->rect.y1; + if ( cbp->fill_shp ) { + nr_pixblock_render_bpath_rgba (cbp->fill_shp,cbp->fill_rgba,area,(char*)buf->buf, buf->buf_rowstride); + } + if ( cbp->stroke_shp ) { + nr_pixblock_render_bpath_rgba (cbp->stroke_shp,cbp->stroke_rgba,area,(char*)buf->buf, buf->buf_rowstride); + } + +} + +#define BIGVAL 1e18 + +static double +sp_canvas_bpath_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item) +{ + SPCanvasBPath *cbp = SP_CANVAS_BPATH (item); + + if (cbp->fill_shp && (cbp->fill_shp->PtWinding(p) > 0 )) { + *actual_item = item; + return 0.0; + } + if (cbp->stroke_shp ) { + if (cbp->stroke_shp->PtWinding(p) > 0 ) { + *actual_item = item; + return 0.0; + } + return distance(cbp->stroke_shp, p); + } + if (cbp->fill_shp) { + return distance(cbp->fill_shp, p); + } + + return BIGVAL; +} + +SPCanvasItem * +sp_canvas_bpath_new (SPCanvasGroup *parent, SPCurve *curve) +{ + g_return_val_if_fail (parent != NULL, NULL); + g_return_val_if_fail (SP_IS_CANVAS_GROUP (parent), NULL); + + SPCanvasItem *item = sp_canvas_item_new (parent, SP_TYPE_CANVAS_BPATH, NULL); + + sp_canvas_bpath_set_bpath (SP_CANVAS_BPATH (item), curve); + + return item; +} + +void +sp_canvas_bpath_set_bpath (SPCanvasBPath *cbp, SPCurve *curve) +{ + g_return_if_fail (cbp != NULL); + g_return_if_fail (SP_IS_CANVAS_BPATH (cbp)); + + if (cbp->curve) { + cbp->curve = sp_curve_unref (cbp->curve); + } + + if (curve) { + cbp->curve = sp_curve_ref (curve); + } + + sp_canvas_item_request_update (SP_CANVAS_ITEM (cbp)); +} + +void +sp_canvas_bpath_set_fill (SPCanvasBPath *cbp, guint32 rgba, SPWindRule rule) +{ + g_return_if_fail (cbp != NULL); + g_return_if_fail (SP_IS_CANVAS_BPATH (cbp)); + + cbp->fill_rgba = rgba; + cbp->fill_rule = rule; + + sp_canvas_item_request_update (SP_CANVAS_ITEM (cbp)); +} + +void +sp_canvas_bpath_set_stroke (SPCanvasBPath *cbp, guint32 rgba, gdouble width, SPStrokeJoinType join, SPStrokeCapType cap) +{ + g_return_if_fail (cbp != NULL); + g_return_if_fail (SP_IS_CANVAS_BPATH (cbp)); + + cbp->stroke_rgba = rgba; + cbp->stroke_width = MAX (width, 0.1); + cbp->stroke_linejoin = join; + cbp->stroke_linecap = cap; + + sp_canvas_item_request_update (SP_CANVAS_ITEM (cbp)); +} + + + +static void +bpath_run_A8_OR (raster_info &dest,void *data,int st,float vst,int en,float ven) +{ + union { + uint8_t comp[4]; + uint32_t col; + } tempCol; + if ( st >= en ) return; + tempCol.col=*(uint32_t*)data; + + unsigned int r, g, b, a; + r = NR_RGBA32_R (tempCol.col); + g = NR_RGBA32_G (tempCol.col); + b = NR_RGBA32_B (tempCol.col); + a = NR_RGBA32_A (tempCol.col); + if (a == 0) return; + + vst*=a; + ven*=a; + + if ( vst < 0 ) vst=0; + if ( vst > 255 ) vst=255; + if ( ven < 0 ) ven=0; + if ( ven > 255 ) ven=255; + float sv=vst; + float dv=ven-vst; + int len=en-st; + uint8_t* d=(uint8_t*)dest.buffer; + + d+=3*(st-dest.startPix); + if ( fabs(dv) < 0.001 ) { + if ( sv > 249.999 ) { + /* Simple copy */ + while (len > 0) { + d[0] = NR_COMPOSEN11 (r, 255, d[0]); + d[1] = NR_COMPOSEN11 (g, 255, d[1]); + d[2] = NR_COMPOSEN11 (b, 255, d[2]); + d += 3; + len -= 1; + } + } else { + unsigned int c0_24=(int)sv; + c0_24&=0xFF; + while (len > 0) { + d[0] = NR_COMPOSEN11 (r, c0_24, d[0]); + d[1] = NR_COMPOSEN11 (g, c0_24, d[1]); + d[2] = NR_COMPOSEN11 (b, c0_24, d[2]); + d += 3; + len -= 1; + } + } + } else { + if ( en <= st+1 ) { + sv=0.5*(vst+ven); + unsigned int c0_24=(int)sv; + c0_24&=0xFF; + d[0] = NR_COMPOSEN11 (r, c0_24, d[0]); + d[1] = NR_COMPOSEN11 (g, c0_24, d[1]); + d[2] = NR_COMPOSEN11 (b, c0_24, d[2]); + } else { + dv/=len; + sv+=0.5*dv; // correction trapezoidale + sv*=65536; + dv*=65536; + int c0_24 = static_cast(CLAMP(sv, 0, 16777216)); + int s0_24 = static_cast(dv); + while (len > 0) { + unsigned int ca; + /* Draw */ + ca = c0_24 >> 16; + if ( ca > 255 ) ca=255; + d[0] = NR_COMPOSEN11 (r, ca, d[0]); + d[1] = NR_COMPOSEN11 (g, ca, d[1]); + d[2] = NR_COMPOSEN11 (b, ca, d[2]); + d += 3; + c0_24 += s0_24; + c0_24 = CLAMP (c0_24, 0, 16777216); + len -= 1; + } + } + } +} + +void nr_pixblock_render_bpath_rgba (Shape* theS,uint32_t color,NRRectL &area,char* destBuf,int stride) +{ + + theS->CalcBBox(); + float l=theS->leftX,r=theS->rightX,t=theS->topY,b=theS->bottomY; + int il,ir,it,ib; + il=(int)floor(l); + ir=(int)ceil(r); + it=(int)floor(t); + ib=(int)ceil(b); + + if ( il >= area.x1 || ir <= area.x0 || it >= area.y1 || ib <= area.y0 ) return; + if ( il < area.x0 ) il=area.x0; + if ( it < area.y0 ) it=area.y0; + if ( ir > area.x1 ) ir=area.x1; + if ( ib > area.y1 ) ib=area.y1; + +/* // version par FloatLigne + int curPt; + float curY; + theS->BeginRaster(curY,curPt,1.0); + + FloatLigne* theI=new FloatLigne(); + IntLigne* theIL=new IntLigne(); + + theS->Scan(curY,curPt,(float)(it),1.0); + + char* mdata=(char*)destBuf; + uint32_t* ligStart=((uint32_t*)(mdata+(3*(il-area.x0)+stride*(it-area.y0)))); + for (int y=it;yReset(); + if ( y&0x00000003 ) { + theS->Scan(curY,curPt,((float)(y+1)),theI,false,1.0); + } else { + theS->Scan(curY,curPt,((float)(y+1)),theI,true,1.0); + } + theI->Flatten(); + theIL->Copy(theI); + + raster_info dest; + dest.startPix=il; + dest.endPix=ir; + dest.sth=il; + dest.stv=y; + dest.buffer=ligStart; + theIL->Raster(dest,&color,bpath_run_A8_OR); + ligStart=((uint32_t*)(((char*)ligStart)+stride)); + } + theS->EndRaster(); + delete theI; + delete theIL; */ + + // version par BitLigne directe + int curPt; + float curY; + theS->BeginQuickRaster(curY, curPt); + + BitLigne* theI[4]; + for (int i=0;i<4;i++) theI[i]=new BitLigne(il,ir); + IntLigne* theIL=new IntLigne(); + + theS->DirectQuickScan(curY,curPt,(float)(it),true,0.25); + + char* mdata=(char*)destBuf; + uint32_t* ligStart=((uint32_t*)(mdata+(3*(il-area.x0)+stride*(it-area.y0)))); + for (int y=it;yReset(); + + for (int i = 0; i < 4; i++) + theS->QuickScan(curY, curPt, ((float)(y+0.25*(i+1))), + fill_oddEven, theI[i], 0.25); + + theIL->Copy(4,theI); + // theI[0]->Affiche(); + // theIL->Affiche(); + + raster_info dest; + dest.startPix=il; + dest.endPix=ir; + dest.sth=il; + dest.stv=y; + dest.buffer=ligStart; + theIL->Raster(dest,&color,bpath_run_A8_OR); + ligStart=((uint32_t*)(((char*)ligStart)+stride)); + } + theS->EndQuickRaster(); + for (int i=0;i<4;i++) delete theI[i]; + delete theIL; +} diff --git a/src/display/canvas-bpath.h b/src/display/canvas-bpath.h new file mode 100644 index 000000000..072fe1087 --- /dev/null +++ b/src/display/canvas-bpath.h @@ -0,0 +1,99 @@ +#ifndef __SP_CANVAS_BPATH_H__ +#define __SP_CANVAS_BPATH_H__ + +/* + * Simple bezier bpath CanvasItem for inkscape + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc. + * + * Released under GNU GPL + * + */ + +#include + +#include + +struct SPCanvasBPath; +struct SPCanvasBPathClass; +struct SPCurve; + +#define SP_TYPE_CANVAS_BPATH (sp_canvas_bpath_get_type ()) +#define SP_CANVAS_BPATH(obj) (GTK_CHECK_CAST ((obj), SP_TYPE_CANVAS_BPATH, SPCanvasBPath)) +#define SP_CANVAS_BPATH_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), SP_TYPE_CANVAS_BPATH, SPCanvasBPathClass)) +#define SP_IS_CANVAS_BPATH(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_CANVAS_BPATH)) +#define SP_IS_CANVAS_BPATH_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), SP_TYPE_CANVAS_BPATH)) + +#define bpath_liv + +class Shape; + +/* stroke-linejoin */ + +typedef enum { + SP_STROKE_LINEJOIN_MITER, + SP_STROKE_LINEJOIN_ROUND, + SP_STROKE_LINEJOIN_BEVEL +} SPStrokeJoinType; + +/* stroke-linecap */ + +typedef enum { + SP_STROKE_LINECAP_BUTT, + SP_STROKE_LINECAP_ROUND, + SP_STROKE_LINECAP_SQUARE +} SPStrokeCapType; + + +/* fill-rule */ +/* clip-rule */ + +typedef enum { + SP_WIND_RULE_NONZERO, + SP_WIND_RULE_INTERSECT, + SP_WIND_RULE_EVENODD, + SP_WIND_RULE_POSITIVE +} SPWindRule; + + +struct SPCanvasBPath { + SPCanvasItem item; + + /* Line def */ + SPCurve *curve; + + /* Fill attributes */ + guint32 fill_rgba; + SPWindRule fill_rule; + + /* Line attributes */ + guint32 stroke_rgba; + gdouble stroke_width; + SPStrokeJoinType stroke_linejoin; + SPStrokeCapType stroke_linecap; + gdouble stroke_miterlimit; + + /* State */ + Shape *fill_shp; + Shape *stroke_shp; +}; + +struct SPCanvasBPathClass { + SPCanvasItemClass parent_class; +}; + +GtkType sp_canvas_bpath_get_type (void); + +SPCanvasItem *sp_canvas_bpath_new (SPCanvasGroup *parent, SPCurve *curve); + +void sp_canvas_bpath_set_bpath (SPCanvasBPath *cbp, SPCurve *curve); +void sp_canvas_bpath_set_fill (SPCanvasBPath *cbp, guint32 rgba, SPWindRule rule); +void sp_canvas_bpath_set_stroke (SPCanvasBPath *cbp, guint32 rgba, gdouble width, SPStrokeJoinType join, SPStrokeCapType cap); + + + +#endif + diff --git a/src/display/canvas-grid.cpp b/src/display/canvas-grid.cpp new file mode 100644 index 000000000..6618c2358 --- /dev/null +++ b/src/display/canvas-grid.cpp @@ -0,0 +1,308 @@ +#define SP_CANVAS_GRID_C + +/* + * SPCGrid + * + * Copyright (C) Lauris Kaplinski 2000 + * + */ + + +#include "sp-canvas-util.h" +#include "canvas-grid.h" +#include "display-forward.h" +#include + +enum { + ARG_0, + ARG_ORIGINX, + ARG_ORIGINY, + ARG_SPACINGX, + ARG_SPACINGY, + ARG_COLOR, + ARG_EMPCOLOR, + ARG_EMPSPACING +}; + + +static void sp_cgrid_class_init (SPCGridClass *klass); +static void sp_cgrid_init (SPCGrid *grid); +static void sp_cgrid_destroy (GtkObject *object); +static void sp_cgrid_set_arg (GtkObject *object, GtkArg *arg, guint arg_id); + +static void sp_cgrid_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags); +static void sp_cgrid_render (SPCanvasItem *item, SPCanvasBuf *buf); + +static SPCanvasItemClass * parent_class; + +GtkType +sp_cgrid_get_type (void) +{ + static GtkType cgrid_type = 0; + + if (!cgrid_type) { + GtkTypeInfo cgrid_info = { + "SPCGrid", + sizeof (SPCGrid), + sizeof (SPCGridClass), + (GtkClassInitFunc) sp_cgrid_class_init, + (GtkObjectInitFunc) sp_cgrid_init, + NULL, NULL, + (GtkClassInitFunc) NULL + }; + cgrid_type = gtk_type_unique (sp_canvas_item_get_type (), &cgrid_info); + } + return cgrid_type; +} + +static void +sp_cgrid_class_init (SPCGridClass *klass) +{ + GtkObjectClass *object_class; + SPCanvasItemClass *item_class; + + object_class = (GtkObjectClass *) klass; + item_class = (SPCanvasItemClass *) klass; + + parent_class = (SPCanvasItemClass*)gtk_type_class (sp_canvas_item_get_type ()); + + gtk_object_add_arg_type ("SPCGrid::originx", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_ORIGINX); + gtk_object_add_arg_type ("SPCGrid::originy", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_ORIGINY); + gtk_object_add_arg_type ("SPCGrid::spacingx", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_SPACINGX); + gtk_object_add_arg_type ("SPCGrid::spacingy", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_SPACINGY); + gtk_object_add_arg_type ("SPCGrid::color", GTK_TYPE_INT, GTK_ARG_WRITABLE, ARG_COLOR); + gtk_object_add_arg_type ("SPCGrid::empcolor", GTK_TYPE_INT, GTK_ARG_WRITABLE, ARG_EMPCOLOR); + gtk_object_add_arg_type ("SPCGrid::empspacing", GTK_TYPE_INT, GTK_ARG_WRITABLE, ARG_EMPSPACING); + + object_class->destroy = sp_cgrid_destroy; + object_class->set_arg = sp_cgrid_set_arg; + + item_class->update = sp_cgrid_update; + item_class->render = sp_cgrid_render; +} + +static void +sp_cgrid_init (SPCGrid *grid) +{ + grid->origin[NR::X] = grid->origin[NR::Y] = 0.0; + grid->spacing[NR::X] = grid->spacing[NR::Y] = 8.0; + grid->color = 0x0000ff7f; + grid->empcolor = 0x3F3FFF40; + grid->empspacing = 5; +} + +static void +sp_cgrid_destroy (GtkObject *object) +{ + g_return_if_fail (object != NULL); + g_return_if_fail (SP_IS_CGRID (object)); + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} + +static void +sp_cgrid_set_arg (GtkObject *object, GtkArg *arg, guint arg_id) +{ + SPCanvasItem *item = SP_CANVAS_ITEM (object); + SPCGrid *grid = SP_CGRID (object); + + switch (arg_id) { + case ARG_ORIGINX: + grid->origin[NR::X] = GTK_VALUE_DOUBLE (* arg); + sp_canvas_item_request_update (item); + break; + case ARG_ORIGINY: + grid->origin[NR::Y] = GTK_VALUE_DOUBLE (* arg); + sp_canvas_item_request_update (item); + break; + case ARG_SPACINGX: + grid->spacing[NR::X] = GTK_VALUE_DOUBLE (* arg); + if (grid->spacing[NR::X] < 0.01) grid->spacing[NR::X] = 0.01; + sp_canvas_item_request_update (item); + break; + case ARG_SPACINGY: + grid->spacing[NR::Y] = GTK_VALUE_DOUBLE (* arg); + if (grid->spacing[NR::Y] < 0.01) grid->spacing[NR::Y] = 0.01; + sp_canvas_item_request_update (item); + break; + case ARG_COLOR: + grid->color = GTK_VALUE_INT (* arg); + sp_canvas_item_request_update (item); + break; + case ARG_EMPCOLOR: + grid->empcolor = GTK_VALUE_INT (* arg); + sp_canvas_item_request_update (item); + break; + case ARG_EMPSPACING: + grid->empspacing = GTK_VALUE_INT (* arg); + // std::cout << "Emphasis Spacing: " << grid->empspacing << std::endl; + sp_canvas_item_request_update (item); + break; + default: + break; + } +} + +static void +sp_grid_hline (SPCanvasBuf *buf, gint y, gint xs, gint xe, guint32 rgba) +{ + if ((y >= buf->rect.y0) && (y < buf->rect.y1)) { + guint r, g, b, a; + gint x0, x1, x; + guchar *p; + r = NR_RGBA32_R (rgba); + g = NR_RGBA32_G (rgba); + b = NR_RGBA32_B (rgba); + a = NR_RGBA32_A (rgba); + x0 = MAX (buf->rect.x0, xs); + x1 = MIN (buf->rect.x1, xe + 1); + p = buf->buf + (y - buf->rect.y0) * buf->buf_rowstride + (x0 - buf->rect.x0) * 3; + for (x = x0; x < x1; x++) { + p[0] = NR_COMPOSEN11 (r, a, p[0]); + p[1] = NR_COMPOSEN11 (g, a, p[1]); + p[2] = NR_COMPOSEN11 (b, a, p[2]); + p += 3; + } + } +} + +static void +sp_grid_vline (SPCanvasBuf *buf, gint x, gint ys, gint ye, guint32 rgba) +{ + if ((x >= buf->rect.x0) && (x < buf->rect.x1)) { + guint r, g, b, a; + gint y0, y1, y; + guchar *p; + r = NR_RGBA32_R(rgba); + g = NR_RGBA32_G (rgba); + b = NR_RGBA32_B (rgba); + a = NR_RGBA32_A (rgba); + y0 = MAX (buf->rect.y0, ys); + y1 = MIN (buf->rect.y1, ye + 1); + p = buf->buf + (y0 - buf->rect.y0) * buf->buf_rowstride + (x - buf->rect.x0) * 3; + for (y = y0; y < y1; y++) { + p[0] = NR_COMPOSEN11 (r, a, p[0]); + p[1] = NR_COMPOSEN11 (g, a, p[1]); + p[2] = NR_COMPOSEN11 (b, a, p[2]); + p += buf->buf_rowstride; + } + } +} + +/** + \brief This function renders the grid on a particular canvas buffer + \param item The grid to render on the buffer + \param buf The buffer to render the grid on + + This function gets called a touch more than you might believe, + about once per tile. This means that it could probably be optimized + and help things out. + + Basically this function has to determine where in the canvas it is, + and how that associates with the grid. It does this first by looking + at the bounding box of the buffer, and then calculates where the grid + starts in that buffer. It will then step through grid lines until + it is outside of the buffer. + + For each grid line it is drawn using the function \c sp_grid_hline + or \c sp_grid_vline. These are convience functions for the sake + of making the function easier to read. + + Also, there are emphisized lines on the grid. While the \c syg and + \c sxg variable track grid positioning, the \c xlinestart and \c + ylinestart variables track the 'count' of what lines they are. If + that count is a multiple of the line seperation between emphisis + lines, then that line is drawn in the emphisis color. +*/ +static void +sp_cgrid_render (SPCanvasItem * item, SPCanvasBuf * buf) +{ + SPCGrid *grid = SP_CGRID (item); + + sp_canvas_prepare_buffer (buf); + + const gdouble sxg = floor ((buf->rect.x0 - grid->ow[NR::X]) / grid->sw[NR::X]) * grid->sw[NR::X] + grid->ow[NR::X]; + const gint xlinestart = (gint) Inkscape::round((sxg - grid->ow[NR::X]) / grid->sw[NR::X]); + const gdouble syg = floor ((buf->rect.y0 - grid->ow[NR::Y]) / grid->sw[NR::Y]) * grid->sw[NR::Y] + grid->ow[NR::Y]; + const gint ylinestart = (gint) Inkscape::round((syg - grid->ow[NR::Y]) / grid->sw[NR::Y]); + + gint ylinenum; + gdouble y; + for (y = syg, ylinenum = ylinestart; y < buf->rect.y1; y += grid->sw[NR::Y], ylinenum++) { + const gint y0 = (gint) Inkscape::round(y); + const gint y1 = (gint) Inkscape::round(y + grid->sw[NR::Y]); + + if (!grid->scaled[NR::Y] && + (ylinenum % grid->empspacing) == 0) { + sp_grid_hline (buf, y0, buf->rect.x0, buf->rect.x1 - 1, grid->empcolor); + } else { + sp_grid_hline (buf, y0, buf->rect.x0, buf->rect.x1 - 1, grid->color); + } + + gint xlinenum; + gdouble x; + for (x = sxg, xlinenum = xlinestart; x < buf->rect.x1; x += grid->sw[NR::X], xlinenum++) { + const gint ix = (gint) Inkscape::round(x); + if (!grid->scaled[NR::X] && + (xlinenum % grid->empspacing) == 0) { + sp_grid_vline (buf, ix, y0 + 1, y1 - 1, grid->empcolor); + } else { + sp_grid_vline (buf, ix, y0 + 1, y1 - 1, grid->color); + } + } + } +} + +static void +sp_cgrid_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags) +{ + SPCGrid *grid = SP_CGRID (item); + + if (parent_class->update) + (* parent_class->update) (item, affine, flags); + + grid->ow = grid->origin * affine; + grid->sw = grid->spacing * affine; + grid->sw -= NR::Point(affine[4], affine[5]); + + for(int dim = 0; dim < 2; dim++) { + gint scaling_factor = grid->empspacing; + + if (scaling_factor <= 1) + scaling_factor = 5; + + grid->scaled[dim] = FALSE; + grid->sw[dim] = fabs (grid->sw[dim]); + while (grid->sw[dim] < 8.0) { + grid->scaled[dim] = TRUE; + grid->sw[dim] *= scaling_factor; + /* First pass, go up to the major line spacing, then + keep increasing by two. */ + scaling_factor = 2; + } + } + + if (grid->empspacing == 0) { + grid->scaled[NR::Y] = TRUE; + grid->scaled[NR::X] = TRUE; + } + + sp_canvas_request_redraw (item->canvas, + -1000000, -1000000, + 1000000, 1000000); + + item->x1 = item->y1 = -1000000; + item->x2 = item->y2 = 1000000; +} + +/* + 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/display/canvas-grid.h b/src/display/canvas-grid.h new file mode 100644 index 000000000..0f3791773 --- /dev/null +++ b/src/display/canvas-grid.h @@ -0,0 +1,49 @@ +#ifndef SP_CANVAS_GRID_H +#define SP_CANVAS_GRID_H + +/* + * SPCGrid + * + * Generic (and quite unintelligent) grid item for gnome canvas + * + * Copyright (C) Lauris Kaplinski 2000 + * + */ + +#include + + + +#define SP_TYPE_CGRID (sp_cgrid_get_type ()) +#define SP_CGRID(obj) (GTK_CHECK_CAST ((obj), SP_TYPE_CGRID, SPCGrid)) +#define SP_CGRID_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), SP_TYPE_CGRID, SPCGridClass)) +#define SP_IS_CGRID(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_CGRID)) +#define SP_IS_CGRID_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), SP_TYPE_CGRID)) + + +/** \brief All the variables that are tracked for a grid specific + canvas item. */ +struct SPCGrid : public SPCanvasItem{ + NR::Point origin; /**< Origin of the grid */ + NR::Point spacing; /**< Spacing between elements of the grid */ + guint32 color; /**< Color for normal lines */ + guint32 empcolor; /**< Color for emphisis lines */ + gint empspacing; /**< Spacing between emphisis lines */ + bool scaled[2]; /**< Whether the grid is in scaled mode, which can + be different in the X or Y direction, hense two + variables */ + NR::Point ow; /**< Transformed origin by the affine for the zoom */ + NR::Point sw; /**< Transformed spacing by the affine for the zoom */ +}; + +struct SPCGridClass { + SPCanvasItemClass parent_class; +}; + + +/* Standard Gtk function */ +GtkType sp_cgrid_get_type (void); + + + +#endif diff --git a/src/display/curve.cpp b/src/display/curve.cpp new file mode 100644 index 000000000..a8a6e354e --- /dev/null +++ b/src/display/curve.cpp @@ -0,0 +1,1289 @@ +#define __CURVE_C__ + +/** \file + * Routines for SPCurve and for NArtBpath arrays in general. + */ + +/* + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2000 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL + */ + + +#include +#include +#include +#include + +#define SP_CURVE_LENSTEP 32 + +static bool sp_bpath_good(NArtBpath const bpath[]); +static NArtBpath *sp_bpath_clean(NArtBpath const bpath[]); +static NArtBpath const *sp_bpath_check_subpath(NArtBpath const bpath[]); +static unsigned sp_bpath_length(NArtBpath const bpath[]); +static bool sp_bpath_closed(NArtBpath const bpath[]); + +/* Constructors */ + +/** + * The returned curve's state is as if sp_curve_reset has just been called on it. + */ +SPCurve * +sp_curve_new() +{ + return sp_curve_new_sized(SP_CURVE_LENSTEP); +} + +/** + * Like sp_curve_new, but overriding the default initial capacity. + * + * The returned curve's state is as if sp_curve_reset has just been called on it. + * + * \param length Initial number of NArtBpath elements allocated for bpath (including NR_END + * element). + */ +SPCurve * +sp_curve_new_sized(gint length) +{ + g_return_val_if_fail(length > 0, NULL); + + SPCurve *curve = g_new(SPCurve, 1); + + curve->refcount = 1; + curve->bpath = nr_new(NArtBpath, length); + curve->bpath->code = NR_END; + curve->end = 0; + curve->length = length; + curve->substart = 0; + curve->sbpath = false; + curve->hascpt = false; + curve->posSet = false; + curve->moving = false; + curve->closed = false; + + return curve; +} + +/** + * Convert NArtBpath object to SPCurve object. + * + * \return new SPCurve, or NULL if the curve was not created for some reason. + */ +SPCurve * +sp_curve_new_from_bpath(NArtBpath *bpath) +{ + g_return_val_if_fail(bpath != NULL, NULL); + + if (!sp_bpath_good(bpath)) { + NArtBpath *new_bpath = sp_bpath_clean(bpath); + if (new_bpath == NULL) { + return NULL; + } + nr_free(bpath); + bpath = new_bpath; + } + + SPCurve *curve = g_new(SPCurve, 1); + + curve->refcount = 1; + curve->bpath = bpath; + curve->length = sp_bpath_length(bpath); + curve->end = curve->length - 1; + gint i = curve->end; + for (; i > 0; i--) + if ((curve->bpath[i].code == NR_MOVETO) || + (curve->bpath[i].code == NR_MOVETO_OPEN)) + break; + curve->substart = i; + curve->sbpath = false; + curve->hascpt = false; + curve->posSet = false; + curve->moving = false; + curve->closed = sp_bpath_closed(bpath); + + return curve; +} + +/** + * Construct an SPCurve from read-only, static storage. + * + * We could treat read-onliness and staticness (i.e. can't call free on bpath) as orthogonal + * attributes, but at the time of writing we have only one caller. + */ +SPCurve * +sp_curve_new_from_static_bpath(NArtBpath const *bpath) +{ + g_return_val_if_fail(bpath != NULL, NULL); + + bool sbpath; + if (!sp_bpath_good(bpath)) { + NArtBpath *new_bpath = sp_bpath_clean(bpath); + g_return_val_if_fail(new_bpath != NULL, NULL); + sbpath = false; + bpath = new_bpath; + } else { + sbpath = true; + } + + SPCurve *curve = g_new(SPCurve, 1); + + curve->refcount = 1; + curve->bpath = const_cast(bpath); + curve->length = sp_bpath_length(bpath); + curve->end = curve->length - 1; + gint i = curve->end; + for (; i > 0; i--) + if ((curve->bpath[i].code == NR_MOVETO) || + (curve->bpath[i].code == NR_MOVETO_OPEN)) + break; + curve->substart = i; + curve->sbpath = sbpath; + curve->hascpt = false; + curve->posSet = false; + curve->moving = false; + curve->closed = sp_bpath_closed(bpath); + + return curve; +} + +/** + * Convert const NArtBpath array to SPCurve. + * + * \return new SPCurve, or NULL if the curve was not created for some reason. + */ +SPCurve *sp_curve_new_from_foreign_bpath(NArtBpath const bpath[]) +{ + g_return_val_if_fail(bpath != NULL, NULL); + + NArtBpath *new_bpath; + if (!sp_bpath_good(bpath)) { + new_bpath = sp_bpath_clean(bpath); + g_return_val_if_fail(new_bpath != NULL, NULL); + } else { + unsigned const len = sp_bpath_length(bpath); + new_bpath = nr_new(NArtBpath, len); + memcpy(new_bpath, bpath, len * sizeof(NArtBpath)); + } + + SPCurve *curve = sp_curve_new_from_bpath(new_bpath); + + if (!curve) + nr_free(new_bpath); + + return curve; +} + +/** + * Increase refcount of curve. + * + * \todo should this be shared with other refcounting code? + */ +SPCurve * +sp_curve_ref(SPCurve *curve) +{ + g_return_val_if_fail(curve != NULL, NULL); + + curve->refcount += 1; + + return curve; +} + +/** + * Decrease refcount of curve, with possible destruction. + * + * \todo should this be shared with other refcounting code? + */ +SPCurve * +sp_curve_unref(SPCurve *curve) +{ + g_return_val_if_fail(curve != NULL, NULL); + + curve->refcount -= 1; + + if (curve->refcount < 1) { + if ((!curve->sbpath) && (curve->bpath)) { + nr_free(curve->bpath); + } + g_free(curve); + } + + return NULL; +} + +/** + * Add space for more paths in curve. + */ +static void +sp_curve_ensure_space(SPCurve *curve, gint space) +{ + g_return_if_fail(curve != NULL); + g_return_if_fail(space > 0); + + if (curve->end + space < curve->length) + return; + + if (space < SP_CURVE_LENSTEP) + space = SP_CURVE_LENSTEP; + + curve->bpath = nr_renew(curve->bpath, NArtBpath, curve->length + space); + + curve->length += space; +} + +/** + * Create new curve from its own bpath array. + */ +SPCurve * +sp_curve_copy(SPCurve *curve) +{ + g_return_val_if_fail(curve != NULL, NULL); + + return sp_curve_new_from_foreign_bpath(curve->bpath); +} + +/** + * Return new curve that is the concatenation of all curves in list. + */ +SPCurve * +sp_curve_concat(GSList const *list) +{ + g_return_val_if_fail(list != NULL, NULL); + + gint length = 0; + + for (GSList const *l = list; l != NULL; l = l->next) { + SPCurve *c = (SPCurve *) l->data; + length += c->end; + } + + SPCurve *new_curve = sp_curve_new_sized(length + 1); + + NArtBpath *bp = new_curve->bpath; + + for (GSList const *l = list; l != NULL; l = l->next) { + SPCurve *c = (SPCurve *) l->data; + memcpy(bp, c->bpath, c->end * sizeof(NArtBpath)); + bp += c->end; + } + + bp->code = NR_END; + + new_curve->end = length; + gint i; + for (i = new_curve->end; i > 0; i--) { + if ((new_curve->bpath[i].code == NR_MOVETO) || + (new_curve->bpath[i].code == NR_MOVETO_OPEN) ) + break; + } + + new_curve->substart = i; + + return new_curve; +} + +/** + * Returns a list of new curves corresponding to the subpaths in \a curve. + */ +GSList * +sp_curve_split(SPCurve const *curve) +{ + g_return_val_if_fail(curve != NULL, NULL); + + gint p = 0; + GSList *l = NULL; + + while (p < curve->end) { + gint i = 1; + while ((curve->bpath[p + i].code == NR_LINETO) || + (curve->bpath[p + i].code == NR_CURVETO)) + i++; + SPCurve *new_curve = sp_curve_new_sized(i + 1); + memcpy(new_curve->bpath, curve->bpath + p, i * sizeof(NArtBpath)); + new_curve->end = i; + new_curve->bpath[i].code = NR_END; + new_curve->substart = 0; + new_curve->closed = (new_curve->bpath->code == NR_MOVETO); + new_curve->hascpt = (new_curve->bpath->code == NR_MOVETO_OPEN); + l = g_slist_append(l, new_curve); + /** \todo + * effic: Use g_slist_prepend instead. Either work backwards from + * the end of curve, or work forwards as at present but do + * g_slist_reverse before returning. + */ + p += i; + } + + return l; +} + +/** + * Transform all paths in curve, template helper. + */ +template +static void +tmpl_curve_transform(SPCurve *const curve, M const &m) +{ + g_return_if_fail(curve != NULL); + g_return_if_fail(!curve->sbpath); + + for (gint i = 0; i < curve->end; i++) { + NArtBpath *p = curve->bpath + i; + switch (p->code) { + case NR_MOVETO: + case NR_MOVETO_OPEN: + case NR_LINETO: { + p->setC(3, p->c(3) * m); + break; + } + case NR_CURVETO: + for (unsigned i = 1; i <= 3; ++i) { + p->setC(i, p->c(i) * m); + } + break; + default: + g_warning("Illegal pathcode %d", p->code); + break; + } + } +} + +/** + * Transform all paths in curve using matrix. + */ +void +sp_curve_transform(SPCurve *const curve, NR::Matrix const &m) +{ + tmpl_curve_transform(curve, m); +} + +/** + * Transform all paths in curve using NR::translate. + */ +void +sp_curve_transform(SPCurve *const curve, NR::translate const &m) +{ + tmpl_curve_transform(curve, m); +} + + +/* Methods */ + +/** + * Set curve to empty curve. + */ +void +sp_curve_reset(SPCurve *curve) +{ + g_return_if_fail(curve != NULL); + g_return_if_fail(!curve->sbpath); + + curve->bpath->code = NR_END; + curve->end = 0; + curve->substart = 0; + curve->hascpt = false; + curve->posSet = false; + curve->moving = false; + curve->closed = false; +} + +/* Several consecutive movetos are ALLOWED */ + +/** + * Calls sp_curve_moveto() with point made of given coordinates. + */ +void +sp_curve_moveto(SPCurve *curve, gdouble x, gdouble y) +{ + sp_curve_moveto(curve, NR::Point(x, y)); +} + +/** + * Perform a moveto to a point, thus starting a new subpath. + */ +void +sp_curve_moveto(SPCurve *curve, NR::Point const &p) +{ + g_return_if_fail(curve != NULL); + g_return_if_fail(!curve->sbpath); + g_return_if_fail(!curve->moving); + + curve->substart = curve->end; + curve->hascpt = true; + curve->posSet = true; + curve->movePos = p; +} + +/** + * Calls sp_curve_lineto() with a point's coordinates. + */ +void +sp_curve_lineto(SPCurve *curve, NR::Point const &p) +{ + sp_curve_lineto(curve, p[NR::X], p[NR::Y]); +} + +/** + * Adds a line to the current subpath. + */ +void +sp_curve_lineto(SPCurve *curve, gdouble x, gdouble y) +{ + g_return_if_fail(curve != NULL); + g_return_if_fail(!curve->sbpath); + g_return_if_fail(curve->hascpt); + + if (curve->moving) { + /* fix endpoint */ + g_return_if_fail(!curve->posSet); + g_return_if_fail(curve->end > 1); + NArtBpath *bp = curve->bpath + curve->end - 1; + g_return_if_fail(bp->code == NR_LINETO); + bp->x3 = x; + bp->y3 = y; + curve->moving = false; + return; + } + + if (curve->posSet) { + /* start a new segment */ + sp_curve_ensure_space(curve, 2); + NArtBpath *bp = curve->bpath + curve->end; + bp->code = NR_MOVETO_OPEN; + bp->setC(3, curve->movePos); + bp++; + bp->code = NR_LINETO; + bp->x3 = x; + bp->y3 = y; + bp++; + bp->code = NR_END; + curve->end += 2; + curve->posSet = false; + curve->closed = false; + return; + } + + /* add line */ + + g_return_if_fail(curve->end > 1); + sp_curve_ensure_space(curve, 1); + NArtBpath *bp = curve->bpath + curve->end; + bp->code = NR_LINETO; + bp->x3 = x; + bp->y3 = y; + bp++; + bp->code = NR_END; + curve->end++; +} + +/// Unused +void +sp_curve_lineto_moving(SPCurve *curve, gdouble x, gdouble y) +{ + g_return_if_fail(curve != NULL); + g_return_if_fail(!curve->sbpath); + g_return_if_fail(curve->hascpt); + + if (curve->moving) { + /* change endpoint */ + g_return_if_fail(!curve->posSet); + g_return_if_fail(curve->end > 1); + NArtBpath *bp = curve->bpath + curve->end - 1; + g_return_if_fail(bp->code == NR_LINETO); + bp->x3 = x; + bp->y3 = y; + return; + } + + if (curve->posSet) { + /* start a new segment */ + sp_curve_ensure_space(curve, 2); + NArtBpath *bp = curve->bpath + curve->end; + bp->code = NR_MOVETO_OPEN; + bp->setC(3, curve->movePos); + bp++; + bp->code = NR_LINETO; + bp->x3 = x; + bp->y3 = y; + bp++; + bp->code = NR_END; + curve->end += 2; + curve->posSet = false; + curve->moving = true; + curve->closed = false; + return; + } + + /* add line */ + + g_return_if_fail(curve->end > 1); + sp_curve_ensure_space(curve, 1); + NArtBpath *bp = curve->bpath + curve->end; + bp->code = NR_LINETO; + bp->x3 = x; + bp->y3 = y; + bp++; + bp->code = NR_END; + curve->end++; + curve->moving = true; +} + +/** + * Calls sp_curve_curveto() with coordinates of three points. + */ +void +sp_curve_curveto(SPCurve *curve, NR::Point const &p0, NR::Point const &p1, NR::Point const &p2) +{ + using NR::X; + using NR::Y; + sp_curve_curveto(curve, + p0[X], p0[Y], + p1[X], p1[Y], + p2[X], p2[Y]); +} + +/** + * Adds a bezier segment to the current subpath. + */ +void +sp_curve_curveto(SPCurve *curve, gdouble x0, gdouble y0, gdouble x1, gdouble y1, gdouble x2, gdouble y2) +{ + g_return_if_fail(curve != NULL); + g_return_if_fail(!curve->sbpath); + g_return_if_fail(curve->hascpt); + g_return_if_fail(!curve->moving); + + if (curve->posSet) { + /* start a new segment */ + sp_curve_ensure_space(curve, 2); + NArtBpath *bp = curve->bpath + curve->end; + bp->code = NR_MOVETO_OPEN; + bp->setC(3, curve->movePos); + bp++; + bp->code = NR_CURVETO; + bp->x1 = x0; + bp->y1 = y0; + bp->x2 = x1; + bp->y2 = y1; + bp->x3 = x2; + bp->y3 = y2; + bp++; + bp->code = NR_END; + curve->end += 2; + curve->posSet = false; + curve->closed = false; + return; + } + + /* add curve */ + + g_return_if_fail(curve->end > 1); + sp_curve_ensure_space(curve, 1); + NArtBpath *bp = curve->bpath + curve->end; + bp->code = NR_CURVETO; + bp->x1 = x0; + bp->y1 = y0; + bp->x2 = x1; + bp->y2 = y1; + bp->x3 = x2; + bp->y3 = y2; + bp++; + bp->code = NR_END; + curve->end++; +} + +/** + * Close current subpath by possibly adding a line between start and end. + */ +void +sp_curve_closepath(SPCurve *curve) +{ + g_return_if_fail(curve != NULL); + g_return_if_fail(!curve->sbpath); + g_return_if_fail(curve->hascpt); + g_return_if_fail(!curve->posSet); + g_return_if_fail(!curve->moving); + g_return_if_fail(!curve->closed); + /* We need at least moveto, curveto, end. */ + g_return_if_fail(curve->end - curve->substart > 1); + + { + NArtBpath *bs = curve->bpath + curve->substart; + NArtBpath *be = curve->bpath + curve->end - 1; + + if (bs->c(3) != be->c(3)) { + sp_curve_lineto(curve, bs->c(3)); + bs = curve->bpath + curve->substart; + } + + bs->code = NR_MOVETO; + } + curve->closed = true; + + for (NArtBpath const *bp = curve->bpath; bp->code != NR_END; bp++) { + /** \todo + * effic: Maintain a count of NR_MOVETO_OPEN's (e.g. instead of + * the closed boolean). + */ + if (bp->code == NR_MOVETO_OPEN) { + curve->closed = false; + break; + } + } + + curve->hascpt = false; +} + +/** Like sp_curve_closepath() but sets the end point of the current + command to the subpath start point instead of adding a new lineto. + + Used for freehand drawing when the user draws back to the start point. +**/ +void +sp_curve_closepath_current(SPCurve *curve) +{ + g_return_if_fail(curve != NULL); + g_return_if_fail(!curve->sbpath); + g_return_if_fail(curve->hascpt); + g_return_if_fail(!curve->posSet); + g_return_if_fail(!curve->closed); + /* We need at least moveto, curveto, end. */ + g_return_if_fail(curve->end - curve->substart > 1); + + { + NArtBpath *bs = curve->bpath + curve->substart; + NArtBpath *be = curve->bpath + curve->end - 1; + + be->x3 = bs->x3; + be->y3 = bs->y3; + + bs->code = NR_MOVETO; + } + curve->closed = true; + + for (NArtBpath const *bp = curve->bpath; bp->code != NR_END; bp++) { + /** \todo + * effic: Maintain a count of NR_MOVETO_OPEN's (e.g. instead of + * the closed boolean). + */ + if (bp->code == NR_MOVETO_OPEN) { + curve->closed = false; + break; + } + } + + curve->hascpt = false; + curve->moving = false; +} + +/** + * True if no paths are in curve. + */ +bool +sp_curve_empty(SPCurve *curve) +{ + g_return_val_if_fail(curve != NULL, TRUE); + + return (curve->bpath->code == NR_END); +} + +/** + * Return last subpath or NULL. + */ +NArtBpath * +sp_curve_last_bpath(SPCurve const *curve) +{ + g_return_val_if_fail(curve != NULL, NULL); + + if (curve->end == 0) { + return NULL; + } + + return curve->bpath + curve->end - 1; +} + +/** + * Return first subpath or NULL. + */ +NArtBpath * +sp_curve_first_bpath(SPCurve const *curve) +{ + g_return_val_if_fail(curve != NULL, NULL); + + if (curve->end == 0) { + return NULL; + } + + return curve->bpath; +} + +/** + * Return first point of first subpath or (0,0). + */ +NR::Point +sp_curve_first_point(SPCurve const *const curve) +{ + NArtBpath *const bpath = sp_curve_first_bpath(curve); + g_return_val_if_fail(bpath != NULL, NR::Point(0, 0)); + return bpath->c(3); +} + +/** + * Return the second point of first subpath or curve->movePos if curve too short. + */ +NR::Point +sp_curve_second_point(SPCurve const *const curve) +{ + g_return_val_if_fail(curve != NULL, NR::Point(0, 0)); + + if (curve->end < 1) { + return curve->movePos; + } + + NArtBpath *bpath = NULL; + if (curve->end < 2) { + bpath = curve->bpath; + } else { + bpath = curve->bpath + 1; + } + g_return_val_if_fail(bpath != NULL, NR::Point(0, 0)); + return bpath->c(3); +} + +/** + * Return the second-last point of last subpath or curve->movePos if curve too short. + */ +NR::Point +sp_curve_penultimate_point(SPCurve const *const curve) +{ + g_return_val_if_fail(curve != NULL, NR::Point(0, 0)); + + if (curve->end < 2) { + return curve->movePos; + } + + NArtBpath *const bpath = curve->bpath + curve->end - 2; + g_return_val_if_fail(bpath != NULL, NR::Point(0, 0)); + return bpath->c(3); +} + +/** + * Return last point of last subpath or (0,0). + */ +NR::Point +sp_curve_last_point(SPCurve const *const curve) +{ + NArtBpath *const bpath = sp_curve_last_bpath(curve); + g_return_val_if_fail(bpath != NULL, NR::Point(0, 0)); + return bpath->c(3); +} + +inline static bool +is_moveto(NRPathcode const c) +{ + return c == NR_MOVETO || c == NR_MOVETO_OPEN; +} + +/** + * Returns \a curve but drawn in the opposite direction. + * Should result in the same shape, but + * with all its markers drawn facing the other direction. + **/ +SPCurve * +sp_curve_reverse(SPCurve const *curve) +{ + /* We need at least moveto, curveto, end. */ + g_return_val_if_fail(curve->end - curve->substart > 1, NULL); + + NArtBpath const *be = curve->bpath + curve->end - 1; + + g_assert(is_moveto(curve->bpath[curve->substart].code)); + g_assert(is_moveto(curve->bpath[0].code)); + g_assert((be+1)->code == NR_END); + + SPCurve *new_curve = sp_curve_new_sized(curve->length); + sp_curve_moveto(new_curve, be->c(3)); + + for (NArtBpath const *bp = be; ; --bp) { + switch (bp->code) { + case NR_MOVETO: + g_assert(new_curve->bpath[new_curve->substart].code == NR_MOVETO_OPEN); + new_curve->bpath[new_curve->substart].code = NR_MOVETO; + /* FALL-THROUGH */ + case NR_MOVETO_OPEN: + if (bp == curve->bpath) { + return new_curve; + } + sp_curve_moveto(new_curve, (bp-1)->c(3)); + break; + + case NR_LINETO: + sp_curve_lineto(new_curve, (bp-1)->c(3)); + break; + + case NR_CURVETO: + sp_curve_curveto(new_curve, bp->c(2), bp->c(1), (bp-1)->c(3)); + break; + + default: + g_assert_not_reached(); + } + } +} + +/** + * Append \a curve2 to \a curve. + */ +void +sp_curve_append(SPCurve *curve, + SPCurve const *curve2, + bool use_lineto) +{ + g_return_if_fail(curve != NULL); + g_return_if_fail(curve2 != NULL); + + if (curve2->end < 1) + return; + + NArtBpath const *bs = curve2->bpath; + + bool closed = curve->closed; + + for (NArtBpath const *bp = bs; bp->code != NR_END; bp++) { + switch (bp->code) { + case NR_MOVETO_OPEN: + if (use_lineto && curve->hascpt) { + sp_curve_lineto(curve, bp->x3, bp->y3); + use_lineto = FALSE; + } else { + if (closed) sp_curve_closepath(curve); + sp_curve_moveto(curve, bp->x3, bp->y3); + } + closed = false; + break; + + case NR_MOVETO: + if (use_lineto && curve->hascpt) { + sp_curve_lineto(curve, bp->x3, bp->y3); + use_lineto = FALSE; + } else { + if (closed) sp_curve_closepath(curve); + sp_curve_moveto(curve, bp->x3, bp->y3); + } + closed = true; + break; + + case NR_LINETO: + sp_curve_lineto(curve, bp->x3, bp->y3); + break; + + case NR_CURVETO: + sp_curve_curveto(curve, bp->x1, bp->y1, bp->x2, bp->y2, bp->x3, bp->y3); + break; + + case NR_END: + g_assert_not_reached(); + } + } + + if (closed) { + sp_curve_closepath(curve); + } +} + +/** + * Append \a c1 to \a c0 with possible fusing of close endpoints. + */ +SPCurve * +sp_curve_append_continuous(SPCurve *c0, SPCurve const *c1, gdouble tolerance) +{ + g_return_val_if_fail(c0 != NULL, NULL); + g_return_val_if_fail(c1 != NULL, NULL); + g_return_val_if_fail(!c0->closed, NULL); + g_return_val_if_fail(!c1->closed, NULL); + + if (c1->end < 1) { + return c0; + } + + NArtBpath *be = sp_curve_last_bpath(c0); + if (be) { + NArtBpath const *bs = sp_curve_first_bpath(c1); + if ( bs + && ( fabs( bs->x3 - be->x3 ) <= tolerance ) + && ( fabs( bs->y3 - be->y3 ) <= tolerance ) ) + { + /** \todo + * fixme: Strictly we mess in case of multisegment mixed + * open/close curves + */ + bool closed = false; + for (bs = bs + 1; bs->code != NR_END; bs++) { + switch (bs->code) { + case NR_MOVETO_OPEN: + if (closed) sp_curve_closepath(c0); + sp_curve_moveto(c0, bs->x3, bs->y3); + closed = false; + break; + case NR_MOVETO: + if (closed) sp_curve_closepath(c0); + sp_curve_moveto(c0, bs->x3, bs->y3); + closed = true; + break; + case NR_LINETO: + sp_curve_lineto(c0, bs->x3, bs->y3); + break; + case NR_CURVETO: + sp_curve_curveto(c0, bs->x1, bs->y1, bs->x2, bs->y2, bs->x3, bs->y3); + break; + case NR_END: + g_assert_not_reached(); + } + } + } else { + sp_curve_append(c0, c1, TRUE); + } + } else { + sp_curve_append(c0, c1, TRUE); + } + + return c0; +} + +/** + * Remove last segment of curve. + */ +void +sp_curve_backspace(SPCurve *curve) +{ + g_return_if_fail(curve != NULL); + + if (curve->end > 0) { + curve->end -= 1; + if (curve->end > 0) { + NArtBpath *bp = curve->bpath + curve->end - 1; + if ((bp->code == NR_MOVETO) || + (bp->code == NR_MOVETO_OPEN) ) + { + curve->hascpt = true; + curve->posSet = true; + curve->closed = false; + curve->movePos = bp->c(3); + curve->end -= 1; + } + } + curve->bpath[curve->end].code = NR_END; + } +} + +/* Private methods */ + +/** + * True if all subpaths in bpath array pass consistency check. + */ +static bool sp_bpath_good(NArtBpath const bpath[]) +{ + g_return_val_if_fail(bpath != NULL, FALSE); + + NArtBpath const *bp = bpath; + while (bp->code != NR_END) { + bp = sp_bpath_check_subpath(bp); + if (bp == NULL) + return false; + } + + return true; +} + +/** + * Return copy of a bpath array, discarding any inconsistencies. + */ +static NArtBpath *sp_bpath_clean(NArtBpath const bpath[]) +{ + NArtBpath *new_bpath = nr_new(NArtBpath, sp_bpath_length(bpath)); + + NArtBpath const *bp = bpath; + NArtBpath *np = new_bpath; + + while (bp->code != NR_END) { + if (sp_bpath_check_subpath(bp)) { + *np++ = *bp++; + while ((bp->code == NR_LINETO) || + (bp->code == NR_CURVETO)) + *np++ = *bp++; + } else { + bp++; + while ((bp->code == NR_LINETO) || + (bp->code == NR_CURVETO)) + bp++; + } + } + + if (np == new_bpath) { + nr_free(new_bpath); + return NULL; + } + + np->code = NR_END; + np += 1; + + new_bpath = nr_renew(new_bpath, NArtBpath, np - new_bpath); + + return new_bpath; +} + +/** + * Perform consistency check of bpath array. + * \return Address of NR_END node or NULL. + */ +static NArtBpath const *sp_bpath_check_subpath(NArtBpath const bpath[]) +{ + g_return_val_if_fail(bpath != NULL, NULL); + + bool closed; + if (bpath->code == NR_MOVETO) { + closed = true; + } else if (bpath->code == NR_MOVETO_OPEN) { + closed = false; + } else { + return NULL; + } + + gint len = 0; + gint i; + /** \todo + * effic: consider checking for END/MOVE/MOVETO inside switch block + */ + for (i = 1; (bpath[i].code != NR_END) && (bpath[i].code != NR_MOVETO) && (bpath[i].code != NR_MOVETO_OPEN); i++) { + switch (bpath[i].code) { + case NR_LINETO: + case NR_CURVETO: + len++; + break; + default: + return NULL; + } + } + + if (closed) { + if (len < 1) + return NULL; + + if ((bpath->x3 != bpath[i-1].x3) || (bpath->y3 != bpath[i-1].y3)) + return NULL; + } else { + if (len < 1) + return NULL; + } + + return bpath + i; +} + +/** + * Returns index of first NR_END bpath in array. + */ +static unsigned sp_bpath_length(NArtBpath const bpath[]) +{ + g_return_val_if_fail(bpath != NULL, FALSE); + + unsigned ret = 0; + while ( bpath[ret].code != NR_END ) { + ++ret; + } + ++ret; + + return ret; +} + +/** + * \brief + * + * \todo + * fixme: this is bogus -- it doesn't check for nr_moveto, which will indicate + * a closing of the subpath it's nonsense to talk about a path as a whole + * being closed, although maybe someone would want that for some other reason? + * Oh, also, if the bpath just ends, then it's *open*. I hope nobody is using + * this code for anything. + */ +static bool sp_bpath_closed(NArtBpath const bpath[]) +{ + g_return_val_if_fail(bpath != NULL, FALSE); + + for (NArtBpath const *bp = bpath; bp->code != NR_END; bp++) { + if (bp->code == NR_MOVETO_OPEN) { + return false; + } + } + + return true; +} + +/** + * Returns length of bezier segment. + */ +static double +bezier_len(NR::Point const &c0, + NR::Point const &c1, + NR::Point const &c2, + NR::Point const &c3, + double const threshold) +{ + /** \todo + * The SVG spec claims that a closed form exists, but for the moment I'll + * use a stupid algorithm. + */ + double const lbound = L2( c3 - c0 ); + double const ubound = L2( c1 - c0 ) + L2( c2 - c1 ) + L2( c3 - c2 ); + double ret; + if ( ubound - lbound <= threshold ) { + ret = .5 * ( lbound + ubound ); + } else { + NR::Point const a1( .5 * ( c0 + c1 ) ); + NR::Point const b2( .5 * ( c2 + c3 ) ); + NR::Point const c12( .5 * ( c1 + c2 ) ); + NR::Point const a2( .5 * ( a1 + c12 ) ); + NR::Point const b1( .5 * ( c12 + b2 ) ); + NR::Point const midpoint( .5 * ( a2 + b1 ) ); + double const rec_threshold = .625 * threshold; + ret = bezier_len(c0, a1, a2, midpoint, rec_threshold) + bezier_len(midpoint, b1, b2, c3, rec_threshold); + if (!(lbound - 1e-2 <= ret && ret <= ubound + 1e-2)) { + using NR::X; using NR::Y; + g_warning("ret=%f outside of expected bounds [%f, %f] for {(%.0f %.0f) (%.0f %.0f) (%.0f %.0f) (%.0f %.0f)}", + ret, lbound, ubound, c0[X], c0[Y], c1[X], c1[Y], c2[X], c2[Y], c3[X], c3[Y]); + } + } + return ret; +} + +/** + * Returns total length of curve, excluding length of closepath segments. + */ +static double +sp_curve_distance_including_space(SPCurve const *const curve, double seg2len[]) +{ + g_return_val_if_fail(curve != NULL, 0.); + + double ret = 0.0; + + if ( curve->bpath->code == NR_END ) { + return ret; + } + + NR::Point prev(curve->bpath->c(3)); + for (gint i = 1; i < curve->end; ++i) { + NArtBpath &p = curve->bpath[i]; + double seg_len = 0; + switch (p.code) { + case NR_MOVETO_OPEN: + case NR_MOVETO: + case NR_LINETO: + seg_len = L2(p.c(3) - prev); + break; + + case NR_CURVETO: + seg_len = bezier_len(prev, p.c(1), p.c(2), p.c(3), 1.); + break; + + case NR_END: + return ret; + } + seg2len[i - 1] = seg_len; + ret += seg_len; + prev = p.c(3); + } + g_assert(!(ret < 0)); + return ret; +} + +/** + * Like sp_curve_distance_including_space(), but ensures that the + * result >= 1e-18: uses 1 per segment if necessary. + */ +static double +sp_curve_nonzero_distance_including_space(SPCurve const *const curve, double seg2len[]) +{ + double const real_dist(sp_curve_distance_including_space(curve, seg2len)); + if (real_dist >= 1e-18) { + return real_dist; + } else { + unsigned const nSegs = SP_CURVE_LENGTH(curve) - 1; + for (unsigned i = 0; i < nSegs; ++i) { + seg2len[i] = 1.; + } + return (double) nSegs; + } +} + +void +sp_curve_stretch_endpoints(SPCurve *curve, NR::Point const &new_p0, NR::Point const &new_p1) +{ + if (sp_curve_empty(curve)) { + return; + } + g_assert(unsigned(SP_CURVE_LENGTH(curve)) + 1 == sp_bpath_length(curve->bpath)); + unsigned const nSegs = SP_CURVE_LENGTH(curve) - 1; + g_assert(nSegs != 0); + double *const seg2len = new double[nSegs]; + double const tot_len = sp_curve_nonzero_distance_including_space(curve, seg2len); + NR::Point const offset0( new_p0 - sp_curve_first_point(curve) ); + NR::Point const offset1( new_p1 - sp_curve_last_point(curve) ); + curve->bpath->setC(3, new_p0); + double begin_dist = 0.; + for (unsigned si = 0; si < nSegs; ++si) { + double const end_dist = begin_dist + seg2len[si]; + NArtBpath &p = curve->bpath[1 + si]; + switch (p.code) { + case NR_LINETO: + case NR_MOVETO: + case NR_MOVETO_OPEN: + p.setC(3, p.c(3) + NR::Lerp(end_dist / tot_len, offset0, offset1)); + break; + + case NR_CURVETO: + for (unsigned ci = 1; ci <= 3; ++ci) { + p.setC(ci, p.c(ci) + Lerp((begin_dist + ci * seg2len[si] / 3.) / tot_len, offset0, offset1)); + } + break; + + default: + g_assert_not_reached(); + } + + begin_dist = end_dist; + } + g_assert(L1(curve->bpath[nSegs].c(3) - new_p1) < 1.); + /* Explicit set for better numerical properties. */ + curve->bpath[nSegs].setC(3, new_p1); + delete [] seg2len; +} + +void +sp_curve_move_endpoints(SPCurve *curve, NR::Point const &new_p0, + NR::Point const &new_p1) +{ + if (sp_curve_empty(curve)) { + return; + } + unsigned const nSegs = SP_CURVE_LENGTH(curve) - 1; + g_assert(nSegs != 0); + + curve->bpath->setC(3, new_p0); + curve->bpath[nSegs].setC(3, new_p1); +} + + +/* + 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/display/curve.h b/src/display/curve.h new file mode 100644 index 000000000..d63796140 --- /dev/null +++ b/src/display/curve.h @@ -0,0 +1,133 @@ +#ifndef SEEN_DISPLAY_CURVE_H +#define SEEN_DISPLAY_CURVE_H + +/** \file + * Wrapper around an array of NArtBpath objects. + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2000 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL + */ + +#include +#include + +#include "libnr/nr-forward.h" +#include "libnr/nr-point.h" + +/// Wrapper around NArtBpath. +struct SPCurve { + gint refcount; + NArtBpath *bpath; + + /// Index in bpath[] of NR_END element. + gint end; + + /// Allocated size (i.e., capacity) of bpath[] array. Not to be confused + /// with the SP_CURVE_LENGTH macro, which returns the logical length of + /// the path (i.e., index of NR_END). + gint length; + + /// Index in bpath[] of the start (i.e., moveto element) of the last + /// subpath in this path. + gint substart; + + /// Previous moveto position. + /// \note This is used for coalescing moveto's, whereas if we're to + /// conform to the SVG spec then we mustn't coalesce movetos if we have + /// midpoint markers. Ref: + /// http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes + /// (first subitem of the item about zero-length path segments) + NR::Point movePos; + + /// True iff bpath points to read-only, static storage (see callers of + /// sp_curve_new_from_static_bpath), in which case we shouldn't free + /// bpath and shouldn't write through it. + bool sbpath : 1; + + /// True iff current point is defined. Initially false for a new curve; + /// becomes true after moveto; becomes false on closepath. Curveto, + /// lineto etc. require hascpt; hascpt remains true after lineto/curveto. + bool hascpt : 1; + + /// True iff previous was moveto. + bool posSet : 1; + + /// True iff bpath end is moving. + bool moving : 1; + + /// True iff all subpaths are closed. + bool closed : 1; +}; + +#define SP_CURVE_LENGTH(c) (((SPCurve const *)(c))->end) +#define SP_CURVE_BPATH(c) (((SPCurve const *)(c))->bpath) +#define SP_CURVE_SEGMENT(c,i) (((SPCurve const *)(c))->bpath + (i)) + +/* Constructors */ + +SPCurve *sp_curve_new(); +SPCurve *sp_curve_new_sized(gint length); +SPCurve *sp_curve_new_from_bpath(NArtBpath *bpath); +SPCurve *sp_curve_new_from_static_bpath(NArtBpath const *bpath); +SPCurve *sp_curve_new_from_foreign_bpath(NArtBpath const bpath[]); + +SPCurve *sp_curve_ref(SPCurve *curve); +SPCurve *sp_curve_unref(SPCurve *curve); + +SPCurve *sp_curve_copy(SPCurve *curve); +SPCurve *sp_curve_concat(GSList const *list); +GSList *sp_curve_split(SPCurve const *curve); +void sp_curve_transform(SPCurve *curve, NR::Matrix const &); +void sp_curve_transform(SPCurve *curve, NR::translate const &); +void sp_curve_stretch_endpoints(SPCurve *curve, NR::Point const &, NR::Point const &); +void sp_curve_move_endpoints(SPCurve *curve, NR::Point const &, + NR::Point const &); + +/* Methods */ + +void sp_curve_reset(SPCurve *curve); + +void sp_curve_moveto(SPCurve *curve, NR::Point const &p); +void sp_curve_moveto(SPCurve *curve, gdouble x, gdouble y); +void sp_curve_lineto(SPCurve *curve, NR::Point const &p); +void sp_curve_lineto(SPCurve *curve, gdouble x, gdouble y); +void sp_curve_lineto_moving(SPCurve *curve, gdouble x, gdouble y); +void sp_curve_curveto(SPCurve *curve, NR::Point const &p0, NR::Point const &p1, NR::Point const &p2); +void sp_curve_curveto(SPCurve *curve, gdouble x0, gdouble y0, gdouble x1, gdouble y1, gdouble x2, gdouble y2); +void sp_curve_closepath(SPCurve *curve); +void sp_curve_closepath_current(SPCurve *curve); + +SPCurve *sp_curve_append_continuous(SPCurve *c0, SPCurve const *c1, gdouble tolerance); + +#define sp_curve_is_empty sp_curve_empty +bool sp_curve_empty(SPCurve *curve); +NArtBpath *sp_curve_last_bpath(SPCurve const *curve); +NArtBpath *sp_curve_first_bpath(SPCurve const *curve); +NR::Point sp_curve_first_point(SPCurve const *curve); +NR::Point sp_curve_last_point(SPCurve const *curve); +NR::Point sp_curve_second_point(SPCurve const *curve); +NR::Point sp_curve_penultimate_point(SPCurve const *curve); + +void sp_curve_append(SPCurve *curve, SPCurve const *curve2, bool use_lineto); +SPCurve *sp_curve_reverse(SPCurve const *curve); +void sp_curve_backspace(SPCurve *curve); + + +#endif /* !SEEN_DISPLAY_CURVE_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 : diff --git a/src/display/display-forward.h b/src/display/display-forward.h new file mode 100644 index 000000000..2af6f2c44 --- /dev/null +++ b/src/display/display-forward.h @@ -0,0 +1,46 @@ +#ifndef SEEN_DISPLAY_DISPLAY_FORWARD_H +#define SEEN_DISPLAY_DISPLAY_FORWARD_H + +#include + +struct SPCanvas; +struct SPCanvasClass; +struct SPCanvasItem; +struct SPCanvasItemClass; +struct SPCanvasGroup; +struct SPCanvasGroupClass; +struct SPCurve; + + +#define SP_TYPE_CANVAS_ITEM (sp_canvas_item_get_type()) +#define SP_CANVAS_ITEM(obj) (GTK_CHECK_CAST((obj), SP_TYPE_CANVAS_ITEM, SPCanvasItem)) +#define SP_IS_CANVAS_ITEM(obj) (GTK_CHECK_TYPE((obj), SP_TYPE_CANVAS_ITEM)) +#define SP_CANVAS_ITEM_GET_CLASS(o) (GTK_CHECK_GET_CLASS((o), SP_TYPE_CANVAS_ITEM, SPCanvasItemClass)) + +GType sp_canvas_item_get_type(); + +#define SP_TYPE_CANVAS_GROUP (sp_canvas_group_get_type()) +#define SP_CANVAS_GROUP(obj) (GTK_CHECK_CAST((obj), SP_TYPE_CANVAS_GROUP, SPCanvasGroup)) +#define SP_IS_CANVAS_GROUP(obj) (GTK_CHECK_TYPE((obj), SP_TYPE_CANVAS_GROUP)) + +GType sp_canvas_group_get_type(); + +#define SP_TYPE_CANVAS (sp_canvas_get_type()) +#define SP_CANVAS(obj) (GTK_CHECK_CAST((obj), SP_TYPE_CANVAS, SPCanvas)) +#define SP_IS_CANVAS(obj) (GTK_CHECK_TYPE((obj), SP_TYPE_CANVAS)) + +GType sp_canvas_get_type(); + + +#endif /* !SEEN_DISPLAY_DISPLAY_FORWARD_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 : diff --git a/src/display/gnome-canvas-acetate.cpp b/src/display/gnome-canvas-acetate.cpp new file mode 100644 index 000000000..1a58c4b19 --- /dev/null +++ b/src/display/gnome-canvas-acetate.cpp @@ -0,0 +1,100 @@ +#define __SP_CANVAS_ACETATE_C__ + +/* + * Infinite invisible canvas item + * + * Author: + * Federico Mena + * Raph Levien + * Lauris Kaplinski + * + * Copyright (C) 1998-1999 The Free Software Foundation + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "display-forward.h" +#include "gnome-canvas-acetate.h" + +static void sp_canvas_acetate_class_init (SPCanvasAcetateClass *klass); +static void sp_canvas_acetate_init (SPCanvasAcetate *acetate); +static void sp_canvas_acetate_destroy (GtkObject *object); + +static void sp_canvas_acetate_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags); +static double sp_canvas_acetate_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item); + +static SPCanvasItemClass *parent_class; + +GtkType +sp_canvas_acetate_get_type (void) +{ + static GtkType acetate_type = 0; + if (!acetate_type) { + GtkTypeInfo acetate_info = { + "SPCanvasAcetate", + sizeof (SPCanvasAcetate), + sizeof (SPCanvasAcetateClass), + (GtkClassInitFunc) sp_canvas_acetate_class_init, + (GtkObjectInitFunc) sp_canvas_acetate_init, + NULL, NULL, NULL + }; + acetate_type = gtk_type_unique (sp_canvas_item_get_type (), &acetate_info); + } + return acetate_type; +} + +static void +sp_canvas_acetate_class_init (SPCanvasAcetateClass *klass) +{ + GtkObjectClass *object_class; + SPCanvasItemClass *item_class; + + object_class = (GtkObjectClass *) klass; + item_class = (SPCanvasItemClass *) klass; + + parent_class = (SPCanvasItemClass*)gtk_type_class (sp_canvas_item_get_type ()); + + object_class->destroy = sp_canvas_acetate_destroy; + + item_class->update = sp_canvas_acetate_update; + item_class->point = sp_canvas_acetate_point; +} + +static void +sp_canvas_acetate_init (SPCanvasAcetate *acetate) +{ + /* Nothing here */ +} + +static void +sp_canvas_acetate_destroy (GtkObject *object) +{ + SPCanvasAcetate *acetate; + + g_return_if_fail (object != NULL); + g_return_if_fail (GNOME_IS_CANVAS_ACETATE (object)); + + acetate = SP_CANVAS_ACETATE (object); + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} + +static void +sp_canvas_acetate_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags) +{ + item->x1 = -G_MAXINT; + item->y1 = -G_MAXINT; + item->x2 = G_MAXINT; + item->y2 = G_MAXINT; +} + +static double +sp_canvas_acetate_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item) +{ + *actual_item = item; + + return 0.0; +} + diff --git a/src/display/gnome-canvas-acetate.h b/src/display/gnome-canvas-acetate.h new file mode 100644 index 000000000..40574e1bf --- /dev/null +++ b/src/display/gnome-canvas-acetate.h @@ -0,0 +1,41 @@ +#ifndef __SP_CANVAS_ACETATE_H__ +#define __SP_CANVAS_ACETATE_H__ + +/* + * Infinite invisible canvas item + * + * Author: + * Federico Mena + * Raph Levien + * Lauris Kaplinski + * + * Copyright (C) 1998-1999 The Free Software Foundation + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include "display/sp-canvas.h" + + +#define GNOME_TYPE_CANVAS_ACETATE (sp_canvas_acetate_get_type ()) +#define SP_CANVAS_ACETATE(obj) (GTK_CHECK_CAST ((obj), GNOME_TYPE_CANVAS_ACETATE, SPCanvasAcetate)) +#define SP_CANVAS_ACETATE_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), GNOME_TYPE_CANVAS_ACETATE, SPCanvasAcetateClass)) +#define GNOME_IS_CANVAS_ACETATE(obj) (GTK_CHECK_TYPE ((obj), GNOME_TYPE_CANVAS_ACETATE)) +#define GNOME_IS_CANVAS_ACETATE_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), GNOME_TYPE_CANVAS_ACETATE)) + + +struct SPCanvasAcetate { + SPCanvasItem item; +}; + +struct SPCanvasAcetateClass { + SPCanvasItemClass parent_class; +}; + +GtkType sp_canvas_acetate_get_type (void); + + + +#endif diff --git a/src/display/guideline.cpp b/src/display/guideline.cpp new file mode 100644 index 000000000..d44ac8ab8 --- /dev/null +++ b/src/display/guideline.cpp @@ -0,0 +1,198 @@ +#define __SP_GUIDELINE_C__ + +/* + * Infinite horizontal/vertical line + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2000-2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + +#include +#include "display-forward.h" +#include "sp-canvas-util.h" +#include "guideline.h" + +static void sp_guideline_class_init(SPGuideLineClass *c); +static void sp_guideline_init(SPGuideLine *guideline); +static void sp_guideline_destroy(GtkObject *object); + +static void sp_guideline_update(SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags); +static void sp_guideline_render(SPCanvasItem *item, SPCanvasBuf *buf); + +static double sp_guideline_point(SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item); + +static SPCanvasItemClass *parent_class; + +GType sp_guideline_get_type() +{ + static GType guideline_type = 0; + + if (!guideline_type) { + static const GTypeInfo guideline_info = + { + sizeof (SPGuideLineClass), + NULL, NULL, + (GClassInitFunc) sp_guideline_class_init, + NULL, NULL, + sizeof (SPGuideLine), + 16, + (GInstanceInitFunc) sp_guideline_init, + NULL, + }; + + guideline_type = g_type_register_static(SP_TYPE_CANVAS_ITEM, "SPGuideLine", &guideline_info, (GTypeFlags) 0); + } + + return guideline_type; +} + +static void sp_guideline_class_init(SPGuideLineClass *c) +{ + parent_class = (SPCanvasItemClass*) g_type_class_peek_parent(c); + + GtkObjectClass *object_class = (GtkObjectClass *) c; + object_class->destroy = sp_guideline_destroy; + + SPCanvasItemClass *item_class = (SPCanvasItemClass *) c; + item_class->update = sp_guideline_update; + item_class->render = sp_guideline_render; + item_class->point = sp_guideline_point; +} + +static void sp_guideline_init(SPGuideLine *gl) +{ + gl->rgba = 0x0000ff7f; + + gl->vertical = 0; + gl->sensitive = 0; +} + +static void sp_guideline_destroy(GtkObject *object) +{ + GTK_OBJECT_CLASS(parent_class)->destroy(object); +} + +static void sp_guideline_render(SPCanvasItem *item, SPCanvasBuf *buf) +{ + SPGuideLine const *gl = SP_GUIDELINE (item); + + sp_canvas_prepare_buffer(buf); + + unsigned int const r = NR_RGBA32_R (gl->rgba); + unsigned int const g = NR_RGBA32_G (gl->rgba); + unsigned int const b = NR_RGBA32_B (gl->rgba); + unsigned int const a = NR_RGBA32_A (gl->rgba); + + int p0, p1, step; + unsigned char *d; + + if (gl->vertical) { + + if (gl->position < buf->rect.x0 || gl->position >= buf->rect.x1) { + return; + } + + p0 = buf->rect.y0; + p1 = buf->rect.y1; + step = buf->buf_rowstride; + d = buf->buf + 3 * (gl->position - buf->rect.x0); + + } else { + + if (gl->position < buf->rect.y0 || gl->position >= buf->rect.y1) { + return; + } + + p0 = buf->rect.x0; + p1 = buf->rect.x1; + step = 3; + d = buf->buf + (gl->position - buf->rect.y0) * buf->buf_rowstride; + } + + for (int p = p0; p < p1; p++) { + d[0] = NR_COMPOSEN11(r, a, d[0]); + d[1] = NR_COMPOSEN11(g, a, d[1]); + d[2] = NR_COMPOSEN11(b, a, d[2]); + d += step; + } +} + +static void sp_guideline_update(SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags) +{ + SPGuideLine *gl = SP_GUIDELINE(item); + + if (((SPCanvasItemClass *) parent_class)->update) { + ((SPCanvasItemClass *) parent_class)->update(item, affine, flags); + } + + if (gl->vertical) { + gl->position = (int) (affine[4] + 0.5); + sp_canvas_update_bbox (item, gl->position, -1000000, gl->position + 1, 1000000); + } else { + gl->position = (int) (affine[5] - 0.5); + sp_canvas_update_bbox (item, -1000000, gl->position, 1000000, gl->position + 1); + } +} + +static double sp_guideline_point(SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item) +{ + SPGuideLine *gl = SP_GUIDELINE (item); + + if (!gl->sensitive) { + return NR_HUGE; + } + + *actual_item = item; + + if (gl->vertical) { + return MAX(fabs(gl->position - p[NR::X])-1, 0); + } else { + return MAX(fabs(gl->position - p[NR::Y])-1, 0); + } +} + +SPCanvasItem *sp_guideline_new(SPCanvasGroup *parent, double position, unsigned int vertical) +{ + SPCanvasItem *item = sp_canvas_item_new(parent, SP_TYPE_GUIDELINE, NULL); + + SPGuideLine *gl = SP_GUIDELINE(item); + + gl->vertical = vertical; + sp_guideline_set_position(gl, position); + + return item; +} + +void sp_guideline_set_position(SPGuideLine *gl, double position) +{ + sp_canvas_item_affine_absolute(SP_CANVAS_ITEM (gl), + NR::Matrix(NR::translate(position, position))); +} + +void sp_guideline_set_color(SPGuideLine *gl, unsigned int rgba) +{ + gl->rgba = rgba; + + sp_canvas_item_request_update(SP_CANVAS_ITEM(gl)); +} + +void sp_guideline_set_sensitive(SPGuideLine *gl, int sensitive) +{ + gl->sensitive = sensitive; +} + +/* + 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/display/guideline.h b/src/display/guideline.h new file mode 100644 index 000000000..22f0af69a --- /dev/null +++ b/src/display/guideline.h @@ -0,0 +1,55 @@ +#ifndef __SP_GUIDELINE_H__ +#define __SP_GUIDELINE_H__ + +/* + * Infinite horizontal/vertical line; the visual representation of SPGuide. + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2000-2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-canvas.h" + +#define SP_TYPE_GUIDELINE (sp_guideline_get_type()) +#define SP_GUIDELINE(o) (GTK_CHECK_CAST((o), SP_TYPE_GUIDELINE, SPGuideLine)) +#define SP_IS_GUIDELINE(o) (GTK_CHECK_TYPE((o), SP_TYPE_GUIDELINE)) + +struct SPGuideLine { + SPCanvasItem item; + + guint32 rgba; + + int position; + + unsigned int vertical : 1; + unsigned int sensitive : 1; +}; + +struct SPGuideLineClass { + SPCanvasItemClass parent_class; +}; + +GType sp_guideline_get_type(); + +SPCanvasItem *sp_guideline_new(SPCanvasGroup *parent, double position, unsigned int vertical); + +void sp_guideline_set_position(SPGuideLine *gl, double position); +void sp_guideline_set_color(SPGuideLine *gl, unsigned int rgba); +void sp_guideline_set_sensitive(SPGuideLine *gl, int sensitive); + +#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 : diff --git a/src/display/makefile.in b/src/display/makefile.in new file mode 100644 index 000000000..9d9426809 --- /dev/null +++ b/src/display/makefile.in @@ -0,0 +1,17 @@ +# Convenience stub makefile to call the real Makefile. + +@SET_MAKE@ + +# Explicit so that it's the default rule. +all: + cd .. && $(MAKE) display/all + +clean %.a %.o: + cd .. && $(MAKE) display/$@ + +.PHONY: all clean + +OBJEXT = @OBJEXT@ + +.SUFFIXES: +.SUFFIXES: .a .$(OBJEXT) diff --git a/src/display/nr-arena-forward.h b/src/display/nr-arena-forward.h new file mode 100644 index 000000000..67f62a78b --- /dev/null +++ b/src/display/nr-arena-forward.h @@ -0,0 +1,51 @@ +#ifndef __NR_ARENA_FORWARD_H__ +#define __NR_ARENA_FORWARD_H__ + +/* + * RGBA display list system for inkscape + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +struct NRArena; +struct NRArenaClass; + +struct NRArenaItem; +struct NRArenaItemClass; + +struct NRArenaGroup; +struct NRArenaGroupClass; + +struct NRArenaShape; +struct NRArenaShapeClass; + +struct NRArenaShapeGroup; +struct NRArenaShapeGroupClass; + +struct NRArenaImage; +struct NRArenaImageClass; + +struct NRArenaGlyphs; +struct NRArenaGlyphsClass; + +struct NRArenaGlyphsGroup; +struct NRArenaGlyphsGroupClass; + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/display/nr-arena-glyphs.cpp b/src/display/nr-arena-glyphs.cpp new file mode 100644 index 000000000..861b8baf8 --- /dev/null +++ b/src/display/nr-arena-glyphs.cpp @@ -0,0 +1,679 @@ +#define __NR_ARENA_GLYPHS_C__ + +/* + * RGBA display list system for inkscape + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL + * + */ + + +#ifdef HAVE_CONFIG_H +# include +#endif +#include +#include +#include "../style.h" +#include "nr-arena.h" +#include "nr-arena-glyphs.h" + +#ifdef test_glyph_liv +#include "../display/canvas-bpath.h" +#include +#include +#include + +// defined in nr-arena-shape.cpp +void nr_pixblock_render_shape_mask_or (NRPixBlock &m,Shape* theS); +#endif + +static void nr_arena_glyphs_class_init (NRArenaGlyphsClass *klass); +static void nr_arena_glyphs_init (NRArenaGlyphs *glyphs); +static void nr_arena_glyphs_finalize (NRObject *object); + +static guint nr_arena_glyphs_update (NRArenaItem *item, NRRectL *area, NRGC *gc, guint state, guint reset); +static guint nr_arena_glyphs_clip (NRArenaItem *item, NRRectL *area, NRPixBlock *pb); +static NRArenaItem *nr_arena_glyphs_pick (NRArenaItem *item, NR::Point p, double delta, unsigned int sticky); + +static NRArenaItemClass *glyphs_parent_class; + +NRType +nr_arena_glyphs_get_type (void) +{ + static NRType type = 0; + if (!type) { + type = nr_object_register_type (NR_TYPE_ARENA_ITEM, + "NRArenaGlyphs", + sizeof (NRArenaGlyphsClass), + sizeof (NRArenaGlyphs), + (void (*) (NRObjectClass *)) nr_arena_glyphs_class_init, + (void (*) (NRObject *)) nr_arena_glyphs_init); + } + return type; +} + +static void +nr_arena_glyphs_class_init (NRArenaGlyphsClass *klass) +{ + NRObjectClass *object_class; + NRArenaItemClass *item_class; + + object_class = (NRObjectClass *) klass; + item_class = (NRArenaItemClass *) klass; + + glyphs_parent_class = (NRArenaItemClass *) ((NRObjectClass *) klass)->parent; + + object_class->finalize = nr_arena_glyphs_finalize; + object_class->cpp_ctor = NRObject::invoke_ctor; + + item_class->update = nr_arena_glyphs_update; + item_class->clip = nr_arena_glyphs_clip; + item_class->pick = nr_arena_glyphs_pick; +} + +static void +nr_arena_glyphs_init (NRArenaGlyphs *glyphs) +{ + glyphs->style = NULL; + nr_matrix_set_identity(&glyphs->g_transform); + glyphs->font = NULL; + glyphs->glyph = 0; + + glyphs->rfont = NULL; + glyphs->sfont = NULL; + glyphs->x = glyphs->y = 0.0; + +// nr_matrix_set_identity(&glyphs->cached_tr); +// glyphs->cached_shp=NULL; +// glyphs->cached_shp_dirty=false; +// glyphs->cached_style_dirty=false; + +// glyphs->stroke_shp=NULL; +} + +static void +nr_arena_glyphs_finalize (NRObject *object) +{ + NRArenaGlyphs *glyphs=static_cast(object); + +// if (glyphs->cached_shp) { +// delete glyphs->cached_shp; +// glyphs->cached_shp = NULL; +// } +// if (glyphs->stroke_shp) { +// delete glyphs->stroke_shp; +// glyphs->stroke_shp = NULL; +// } + + if (glyphs->rfont) { + glyphs->rfont->Unref(); + glyphs->rfont=NULL; + } + if (glyphs->sfont) { + glyphs->sfont->Unref(); + glyphs->sfont=NULL; + } + + if (glyphs->font) { + glyphs->font->Unref(); + glyphs->font=NULL; + } + + if (glyphs->style) { + sp_style_unref (glyphs->style); + glyphs->style = NULL; + } + + ((NRObjectClass *) glyphs_parent_class)->finalize (object); +} + +static guint +nr_arena_glyphs_update (NRArenaItem *item, NRRectL *area, NRGC *gc, guint state, guint reset) +{ + NRArenaGlyphs *glyphs; + raster_font *rfont; + + glyphs = NR_ARENA_GLYPHS (item); + + if (!glyphs->font || !glyphs->style) return NR_ARENA_ITEM_STATE_ALL; + if ((glyphs->style->fill.type == SP_PAINT_TYPE_NONE) && (glyphs->style->stroke.type == SP_PAINT_TYPE_NONE)) return NR_ARENA_ITEM_STATE_ALL; + + NRRect bbox; + bbox.x0 = bbox.y0 = NR_HUGE; + bbox.x1 = bbox.y1 = -NR_HUGE; + + if (glyphs->style->fill.type != SP_PAINT_TYPE_NONE) { + NRMatrix t; + nr_matrix_multiply (&t, &glyphs->g_transform, &gc->transform); + glyphs->x = t.c[4]; + glyphs->y = t.c[5]; + t.c[4]=0; + t.c[5]=0; + rfont = glyphs->font->RasterFont(t, 0); + if (glyphs->rfont) glyphs->rfont->Unref(); + glyphs->rfont = rfont; + + if (glyphs->style->stroke.type == SP_PAINT_TYPE_NONE) { // Optimization: do fill bbox only if there's no stroke + NRRect narea; + if ( glyphs->rfont ) glyphs->rfont->BBox(glyphs->glyph, &narea); + bbox.x0 = narea.x0 + glyphs->x; + bbox.y0 = narea.y0 + glyphs->y; + bbox.x1 = narea.x1 + glyphs->x; + bbox.y1 = narea.y1 + glyphs->y; + } + } + + if (glyphs->style->stroke.type != SP_PAINT_TYPE_NONE) { + /* Build state data */ + NRMatrix t; + nr_matrix_multiply (&t, &glyphs->g_transform, &gc->transform); + glyphs->x = t.c[4]; + glyphs->y = t.c[5]; + t.c[4]=0; + t.c[5]=0; + + const float scale = NR_MATRIX_DF_EXPANSION (&gc->transform); + if ( fabs(glyphs->style->stroke_width.computed * scale) > 0.01 ) { // sinon c'est 0=oon veut pas de bord + font_style nstyl; + nstyl.transform = t; + nstyl.stroke_width=MAX (0.125, glyphs->style->stroke_width.computed * scale); + if ( glyphs->style->stroke_linecap.computed == SP_STROKE_LINECAP_BUTT ) nstyl.stroke_cap=butt_straight; + if ( glyphs->style->stroke_linecap.computed == SP_STROKE_LINECAP_ROUND ) nstyl.stroke_cap=butt_round; + if ( glyphs->style->stroke_linecap.computed == SP_STROKE_LINECAP_SQUARE ) nstyl.stroke_cap=butt_square; + if ( glyphs->style->stroke_linejoin.computed == SP_STROKE_LINEJOIN_MITER ) nstyl.stroke_join=join_pointy; + if ( glyphs->style->stroke_linejoin.computed == SP_STROKE_LINEJOIN_ROUND ) nstyl.stroke_join=join_round; + if ( glyphs->style->stroke_linejoin.computed == SP_STROKE_LINEJOIN_BEVEL ) nstyl.stroke_join=join_straight; + nstyl.stroke_miter_limit = glyphs->style->stroke_miterlimit.value; + nstyl.nbDash=0; + nstyl.dashes=NULL; + if ( glyphs->style->stroke_dash.n_dash > 0 ) { + nstyl.nbDash=glyphs->style->stroke_dash.n_dash; + nstyl.dashes=(double*)malloc(nstyl.nbDash*sizeof(double)); + for (int i = 0; i < nstyl.nbDash; i++) nstyl.dashes[i]= glyphs->style->stroke_dash.dash[i] * scale; + } + rfont = glyphs->font->RasterFont( nstyl); + if ( nstyl.dashes ) free(nstyl.dashes); + if (glyphs->sfont) glyphs->sfont->Unref(); + glyphs->sfont = rfont; + + NRRect narea; + if ( glyphs->sfont ) glyphs->sfont->BBox(glyphs->glyph, &narea); + narea.x0-=nstyl.stroke_width; + narea.y0-=nstyl.stroke_width; + narea.x1+=nstyl.stroke_width; + narea.y1+=nstyl.stroke_width; + bbox.x0 = narea.x0 + glyphs->x; + bbox.y0 = narea.y0 + glyphs->y; + bbox.x1 = narea.x1 + glyphs->x; + bbox.y1 = narea.y1 + glyphs->y; + } + } + if (nr_rect_d_test_empty(&bbox)) return NR_ARENA_ITEM_STATE_ALL; + + item->bbox.x0 = (gint32)(bbox.x0 - 1.0); + item->bbox.y0 = (gint32)(bbox.y0 - 1.0); + item->bbox.x1 = (gint32)(bbox.x1 + 1.0); + item->bbox.y1 = (gint32)(bbox.y1 + 1.0); + nr_arena_request_render_rect (item->arena, &item->bbox); + + return NR_ARENA_ITEM_STATE_ALL; +} + +static guint +nr_arena_glyphs_clip (NRArenaItem *item, NRRectL *area, NRPixBlock *pb) +{ + NRArenaGlyphs *glyphs; + + glyphs = NR_ARENA_GLYPHS (item); + + if (!glyphs->font ) return item->state; + + /* TODO : render to greyscale pixblock provided for clipping */ + + return item->state; +} + +static NRArenaItem * +nr_arena_glyphs_pick (NRArenaItem *item, NR::Point p, gdouble delta, unsigned int sticky) +{ + NRArenaGlyphs *glyphs; + + glyphs = NR_ARENA_GLYPHS (item); + + if (!glyphs->font ) return NULL; + if (!glyphs->style) return NULL; + + const double x = p[NR::X]; + const double y = p[NR::Y]; + /* With text we take a simple approach: pick if the point is in a characher bbox */ + if ((x >= item->bbox.x0) && (y >= item->bbox.y0) && (x <= item->bbox.x1) && (y <= item->bbox.y1)) return item; + +/* NR::Point const thePt = p; + if (glyphs->stroke_shp && (glyphs->style->stroke.type != SP_PAINT_TYPE_NONE)) { + if (glyphs->stroke_shp->PtWinding(thePt) > 0 ) return item; + } + if (delta > 1e-3) { + if (glyphs->stroke_shp && (glyphs->style->stroke.type != SP_PAINT_TYPE_NONE)) { + if ( glyphs->stroke_shp->DistanceLE(thePt, delta)) return item; + } + }*/ + + return NULL; +} + +void +nr_arena_glyphs_set_path (NRArenaGlyphs *glyphs, SPCurve *curve, unsigned int lieutenant, font_instance *font, gint glyph, const NRMatrix *transform) +{ + nr_return_if_fail (glyphs != NULL); + nr_return_if_fail (NR_IS_ARENA_GLYPHS (glyphs)); + + nr_arena_item_request_render (NR_ARENA_ITEM (glyphs)); + + // glyphs->cached_shp_dirty=true; + + if (transform) { + glyphs->g_transform = *transform; + } else { + nr_matrix_set_identity (&glyphs->g_transform); + } + + //printf("glyph_setpath "); + if ( font ) font->Ref(); + if ( glyphs->font ) glyphs->font->Unref(); + glyphs->font=font; + glyphs->glyph = glyph; + + nr_arena_item_request_update (NR_ARENA_ITEM (glyphs), NR_ARENA_ITEM_STATE_ALL, FALSE); +} + +void +nr_arena_glyphs_set_style (NRArenaGlyphs *glyphs, SPStyle *style) +{ + nr_return_if_fail (glyphs != NULL); + nr_return_if_fail (NR_IS_ARENA_GLYPHS (glyphs)); + +// glyphs->cached_style_dirty=true; + + if (style) sp_style_ref (style); + if (glyphs->style) sp_style_unref (glyphs->style); + glyphs->style = style; + + nr_arena_item_request_update (NR_ARENA_ITEM (glyphs), NR_ARENA_ITEM_STATE_ALL, FALSE); +} + +static guint +nr_arena_glyphs_fill_mask (NRArenaGlyphs *glyphs, NRRectL *area, NRPixBlock *m) +{ + NRArenaItem *item; + + /* fixme: area == m->area, so merge these */ + + item = NR_ARENA_ITEM (glyphs); + + if (glyphs->rfont && nr_rect_l_test_intersect (area, &item->bbox)) { + raster_glyph* g=glyphs->rfont->GetGlyph(glyphs->glyph); + if ( g ) g->Blit(NR::Point(glyphs->x, glyphs->y),*m); + } + + return item->state; +} + +static guint +nr_arena_glyphs_stroke_mask (NRArenaGlyphs *glyphs, NRRectL *area, NRPixBlock *m) +{ + NRArenaItem *item; + + item = NR_ARENA_ITEM (glyphs); + if (glyphs->sfont && nr_rect_l_test_intersect (area, &item->bbox)) { + raster_glyph* g=glyphs->sfont->GetGlyph(glyphs->glyph); + if ( g ) g->Blit(NR::Point(glyphs->x, glyphs->y),*m); + } +/* if (glyphs->stroke_shp && nr_rect_l_test_intersect (area, &item->bbox)) { + NRPixBlock gb; + gint x, y; + nr_pixblock_setup_fast (&gb, NR_PIXBLOCK_MODE_A8, area->x0, area->y0, area->x1, area->y1, TRUE); + // art_gray_svp_aa is just fillung apparently + // dunno why it's used here instead of its libnr counterpart + nr_pixblock_render_shape_mask_or (gb,glyphs->stroke_shp); + for (y = area->y0; y < area->y1; y++) { + guchar *d, *s; + d = NR_PIXBLOCK_PX (m) + (y - area->y0) * m->rs; + s = NR_PIXBLOCK_PX (&gb) + (y - area->y0) * gb.rs; + for (x = area->x0; x < area->x1; x++) { + *d = (*d) + ((255 - *d) * (*s) / 255); + d += 1; + s += 1; + } + } + nr_pixblock_release (&gb); + m->empty = FALSE; + }*/ + + return item->state; +} + +static void nr_arena_glyphs_group_class_init (NRArenaGlyphsGroupClass *klass); +static void nr_arena_glyphs_group_init (NRArenaGlyphsGroup *group); +static void nr_arena_glyphs_group_finalize (NRObject *object); + +static guint nr_arena_glyphs_group_update (NRArenaItem *item, NRRectL *area, NRGC *gc, guint state, guint reset); +static unsigned int nr_arena_glyphs_group_render (NRArenaItem *item, NRRectL *area, NRPixBlock *pb, unsigned int flags); +static unsigned int nr_arena_glyphs_group_clip (NRArenaItem *item, NRRectL *area, NRPixBlock *pb); +static NRArenaItem *nr_arena_glyphs_group_pick (NRArenaItem *item, NR::Point p, gdouble delta, unsigned int sticky); + +static NRArenaGroupClass *group_parent_class; + +NRType +nr_arena_glyphs_group_get_type (void) +{ + static NRType type = 0; + if (!type) { + type = nr_object_register_type (NR_TYPE_ARENA_GROUP, + "NRArenaGlyphsGroup", + sizeof (NRArenaGlyphsGroupClass), + sizeof (NRArenaGlyphsGroup), + (void (*) (NRObjectClass *)) nr_arena_glyphs_group_class_init, + (void (*) (NRObject *)) nr_arena_glyphs_group_init); + } + return type; +} + +static void +nr_arena_glyphs_group_class_init (NRArenaGlyphsGroupClass *klass) +{ + NRObjectClass *object_class; + NRArenaItemClass *item_class; + + object_class = (NRObjectClass *) klass; + item_class = (NRArenaItemClass *) klass; + + group_parent_class = (NRArenaGroupClass *) ((NRObjectClass *) klass)->parent; + + object_class->finalize = nr_arena_glyphs_group_finalize; + object_class->cpp_ctor = NRObject::invoke_ctor; + + item_class->update = nr_arena_glyphs_group_update; + item_class->render = nr_arena_glyphs_group_render; + item_class->clip = nr_arena_glyphs_group_clip; + item_class->pick = nr_arena_glyphs_group_pick; +} + +static void +nr_arena_glyphs_group_init (NRArenaGlyphsGroup *group) +{ + group->style = NULL; + group->paintbox.x0 = group->paintbox.y0 = 0.0F; + group->paintbox.x1 = group->paintbox.y1 = 1.0F; + + group->fill_painter = NULL; + group->stroke_painter = NULL; +} + +static void +nr_arena_glyphs_group_finalize (NRObject *object) +{ + NRArenaGlyphsGroup *group=static_cast(object); + + if (group->fill_painter) { + sp_painter_free (group->fill_painter); + group->fill_painter = NULL; + } + + if (group->stroke_painter) { + sp_painter_free (group->stroke_painter); + group->stroke_painter = NULL; + } + + if (group->style) { + sp_style_unref (group->style); + group->style = NULL; + } + + ((NRObjectClass *) group_parent_class)->finalize (object); +} + +static guint +nr_arena_glyphs_group_update (NRArenaItem *item, NRRectL *area, NRGC *gc, guint state, guint reset) +{ + NRArenaGlyphsGroup *group = NR_ARENA_GLYPHS_GROUP (item); + + if (group->fill_painter) { + sp_painter_free (group->fill_painter); + group->fill_painter = NULL; + } + + if (group->stroke_painter) { + sp_painter_free (group->stroke_painter); + group->stroke_painter = NULL; + } + + item->render_opacity = TRUE; + if (group->style->fill.type == SP_PAINT_TYPE_PAINTSERVER) { + group->fill_painter = sp_paint_server_painter_new (SP_STYLE_FILL_SERVER (group->style), + NR::Matrix (&gc->transform), NR::Matrix (&gc->parent->transform), + &group->paintbox); + item->render_opacity = FALSE; + } + + if (group->style->stroke.type == SP_PAINT_TYPE_PAINTSERVER) { + group->stroke_painter = sp_paint_server_painter_new (SP_STYLE_STROKE_SERVER (group->style), + NR::Matrix (&gc->transform), NR::Matrix (&gc->parent->transform), + &group->paintbox); + item->render_opacity = FALSE; + } + + if ( item->render_opacity == TRUE && group->style->stroke.type != SP_PAINT_TYPE_NONE && group->style->fill.type != SP_PAINT_TYPE_NONE ) { + item->render_opacity=FALSE; + } + + if (((NRArenaItemClass *) group_parent_class)->update) + return ((NRArenaItemClass *) group_parent_class)->update (item, area, gc, state, reset); + + return NR_ARENA_ITEM_STATE_ALL; +} + +/* This sucks - as soon, as we have inheritable renderprops, do something with that opacity */ + +static unsigned int +nr_arena_glyphs_group_render (NRArenaItem *item, NRRectL *area, NRPixBlock *pb, unsigned int flags) +{ + NRArenaItem *child; + + NRArenaGroup *group = NR_ARENA_GROUP (item); + NRArenaGlyphsGroup *ggroup = NR_ARENA_GLYPHS_GROUP (item); + SPStyle const *style = ggroup->style; + + guint ret = item->state; + + /* Fill */ + if (style->fill.type != SP_PAINT_TYPE_NONE || item->arena->rendermode == RENDERMODE_OUTLINE) { + NRPixBlock m; + nr_pixblock_setup_fast (&m, NR_PIXBLOCK_MODE_A8, area->x0, area->y0, area->x1, area->y1, TRUE); + + /* Render children fill mask */ + for (child = group->children; child != NULL; child = child->next) { + ret = nr_arena_glyphs_fill_mask (NR_ARENA_GLYPHS (child), area, &m); + if (!(ret & NR_ARENA_ITEM_STATE_RENDER)) { + nr_pixblock_release (&m); + return ret; + } + } + + /* Composite into buffer */ + if (style->fill.type == SP_PAINT_TYPE_COLOR || item->arena->rendermode == RENDERMODE_OUTLINE) { + guint32 rgba; + if (item->arena->rendermode == RENDERMODE_OUTLINE) { + // In outline mode, render fill only, using outlinecolor + rgba = item->arena->outlinecolor; + } else if ( item->render_opacity ) { + rgba = sp_color_get_rgba32_falpha (&style->fill.value.color, + SP_SCALE24_TO_FLOAT (style->fill_opacity.value) * + SP_SCALE24_TO_FLOAT (style->opacity.value)); + } else { + rgba = sp_color_get_rgba32_falpha (&style->fill.value.color, SP_SCALE24_TO_FLOAT (style->fill_opacity.value)); + } + nr_blit_pixblock_mask_rgba32 (pb, &m, rgba); + pb->empty = FALSE; + } else if (style->fill.type == SP_PAINT_TYPE_PAINTSERVER) { + if (ggroup->fill_painter) { + nr_arena_render_paintserver_fill (pb, area, ggroup->fill_painter, SP_SCALE24_TO_FLOAT (style->fill_opacity.value), &m); + } + } + + nr_pixblock_release (&m); + } + + /* Stroke */ + if (style->stroke.type != SP_PAINT_TYPE_NONE && !(item->arena->rendermode == RENDERMODE_OUTLINE)) { + NRPixBlock m; + guint32 rgba; + nr_pixblock_setup_fast (&m, NR_PIXBLOCK_MODE_A8, area->x0, area->y0, area->x1, area->y1, TRUE); + /* Render children stroke mask */ + for (child = group->children; child != NULL; child = child->next) { + ret = nr_arena_glyphs_stroke_mask (NR_ARENA_GLYPHS (child), area, &m); + if (!(ret & NR_ARENA_ITEM_STATE_RENDER)) { + nr_pixblock_release (&m); + return ret; + } + } + /* Composite into buffer */ + switch (style->stroke.type) { + case SP_PAINT_TYPE_COLOR: + if ( item->render_opacity ) { + rgba = sp_color_get_rgba32_falpha (&style->stroke.value.color, + SP_SCALE24_TO_FLOAT (style->stroke_opacity.value) * + SP_SCALE24_TO_FLOAT (style->opacity.value)); + } else { + rgba = sp_color_get_rgba32_falpha (&style->stroke.value.color, + SP_SCALE24_TO_FLOAT (style->stroke_opacity.value)); + } + nr_blit_pixblock_mask_rgba32 (pb, &m, rgba); + pb->empty = FALSE; + break; + case SP_PAINT_TYPE_PAINTSERVER: + if (ggroup->stroke_painter) { + nr_arena_render_paintserver_fill (pb, area, ggroup->stroke_painter, SP_SCALE24_TO_FLOAT (style->stroke_opacity.value), &m); + } + break; + default: + break; + } + nr_pixblock_release (&m); + } + + return ret; +} + +static unsigned int +nr_arena_glyphs_group_clip (NRArenaItem *item, NRRectL *area, NRPixBlock *pb) +{ + NRArenaGroup *group = NR_ARENA_GROUP (item); + + guint ret = item->state; + + /* Render children fill mask */ + for (NRArenaItem *child = group->children; child != NULL; child = child->next) { + ret = nr_arena_glyphs_fill_mask (NR_ARENA_GLYPHS (child), area, pb); + if (!(ret & NR_ARENA_ITEM_STATE_RENDER)) return ret; + } + + return ret; +} + +static NRArenaItem * +nr_arena_glyphs_group_pick (NRArenaItem *item, NR::Point p, gdouble delta, unsigned int sticky) +{ + NRArenaItem *picked = NULL; + + if (((NRArenaItemClass *) group_parent_class)->pick) + picked = ((NRArenaItemClass *) group_parent_class)->pick (item, p, delta, sticky); + + if (picked) picked = item; + + return picked; +} + +void +nr_arena_glyphs_group_clear (NRArenaGlyphsGroup *sg) +{ + NRArenaGroup *group = NR_ARENA_GROUP (sg); + + nr_arena_item_request_render (NR_ARENA_ITEM (group)); + + while (group->children) { + nr_arena_item_remove_child (NR_ARENA_ITEM (group), group->children); + } +} + +void +nr_arena_glyphs_group_add_component (NRArenaGlyphsGroup *sg, font_instance *font, int glyph, const NRMatrix *transform) +{ + NRArenaGroup *group; + NRBPath bpath; + + group = NR_ARENA_GROUP (sg); + + if ( font ) bpath.path=(NArtBpath*)font->ArtBPath(glyph); else bpath.path=NULL; + if ( bpath.path ) { + + nr_arena_item_request_render (NR_ARENA_ITEM (group)); + + NRArenaItem *new_arena; + new_arena = NRArenaGlyphs::create(group->arena); + nr_arena_item_append_child (NR_ARENA_ITEM (group), new_arena); + nr_arena_item_unref (new_arena); + nr_arena_glyphs_set_path (NR_ARENA_GLYPHS (new_arena), NULL, FALSE, font, glyph, transform); + nr_arena_glyphs_set_style (NR_ARENA_GLYPHS (new_arena), sg->style); + } + +} + +void +nr_arena_glyphs_group_set_style (NRArenaGlyphsGroup *sg, SPStyle *style) +{ + NRArenaGroup *group; + NRArenaItem *child; + + nr_return_if_fail (sg != NULL); + nr_return_if_fail (NR_IS_ARENA_GLYPHS_GROUP (sg)); + + group = NR_ARENA_GROUP (sg); + + if (style) sp_style_ref (style); + if (sg->style) sp_style_unref (sg->style); + sg->style = style; + + for (child = group->children; child != NULL; child = child->next) { + nr_return_if_fail (NR_IS_ARENA_GLYPHS (child)); + nr_arena_glyphs_set_style (NR_ARENA_GLYPHS (child), sg->style); + } + + nr_arena_item_request_update (NR_ARENA_ITEM (sg), NR_ARENA_ITEM_STATE_ALL, FALSE); +} + +void +nr_arena_glyphs_group_set_paintbox (NRArenaGlyphsGroup *gg, const NRRect *pbox) +{ + nr_return_if_fail (gg != NULL); + nr_return_if_fail (NR_IS_ARENA_GLYPHS_GROUP (gg)); + nr_return_if_fail (pbox != NULL); + + if ((pbox->x0 < pbox->x1) && (pbox->y0 < pbox->y1)) { + gg->paintbox.x0 = pbox->x0; + gg->paintbox.y0 = pbox->y0; + gg->paintbox.x1 = pbox->x1; + gg->paintbox.y1 = pbox->y1; + } else { + /* fixme: We kill warning, although not sure what to do here (Lauris) */ + gg->paintbox.x0 = gg->paintbox.y0 = 0.0F; + gg->paintbox.x1 = gg->paintbox.y1 = 256.0F; + } + + nr_arena_item_request_update (NR_ARENA_ITEM (gg), NR_ARENA_ITEM_STATE_ALL, FALSE); +} + diff --git a/src/display/nr-arena-glyphs.h b/src/display/nr-arena-glyphs.h new file mode 100644 index 000000000..23b74a378 --- /dev/null +++ b/src/display/nr-arena-glyphs.h @@ -0,0 +1,109 @@ +#ifndef __NR_ARENA_GLYPHS_H__ +#define __NR_ARENA_GLYPHS_H__ + +/* + * RGBA display list system for inkscape + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL + * + */ + +#define NR_TYPE_ARENA_GLYPHS (nr_arena_glyphs_get_type ()) +#define NR_ARENA_GLYPHS(obj) (NR_CHECK_INSTANCE_CAST ((obj), NR_TYPE_ARENA_GLYPHS, NRArenaGlyphs)) +#define NR_IS_ARENA_GLYPHS(obj) (NR_CHECK_INSTANCE_TYPE ((obj), NR_TYPE_ARENA_GLYPHS)) + +#include + +#include +#include +#include +#include + +#define test_glyph_liv + +class Shape; + +NRType nr_arena_glyphs_get_type (void); + +struct NRArenaGlyphs : public NRArenaItem { + /* Glyphs data */ + SPStyle *style; + NRMatrix g_transform; + font_instance *font; + gint glyph; + + raster_font *rfont; + raster_font *sfont; + float x, y; + +// NRMatrix cached_tr; +// Shape *cached_shp; +// bool cached_shp_dirty; +// bool cached_style_dirty; + +// Shape *stroke_shp; + + static NRArenaGlyphs *create(NRArena *arena) { + NRArenaGlyphs *obj=reinterpret_cast(nr_object_new(NR_TYPE_ARENA_GLYPHS)); + obj->init(arena); + return obj; + } +}; + +struct NRArenaGlyphsClass { + NRArenaItemClass parent_class; +}; + +void nr_arena_glyphs_set_path (NRArenaGlyphs *glyphs, + SPCurve *curve, unsigned int lieutenant, + font_instance *font, int glyph, + const NRMatrix *transform); +void nr_arena_glyphs_set_style (NRArenaGlyphs *glyphs, SPStyle *style); + +/* Integrated group of component glyphss */ + +typedef struct NRArenaGlyphsGroup NRArenaGlyphsGroup; +typedef struct NRArenaGlyphsGroupClass NRArenaGlyphsGroupClass; + +#include "nr-arena-group.h" + +#define NR_TYPE_ARENA_GLYPHS_GROUP (nr_arena_glyphs_group_get_type ()) +#define NR_ARENA_GLYPHS_GROUP(obj) (NR_CHECK_INSTANCE_CAST ((obj), NR_TYPE_ARENA_GLYPHS_GROUP, NRArenaGlyphsGroup)) +#define NR_IS_ARENA_GLYPHS_GROUP(obj) (NR_CHECK_INSTANCE_TYPE ((obj), NR_TYPE_ARENA_GLYPHS_GROUP)) + +NRType nr_arena_glyphs_group_get_type (void); + +struct NRArenaGlyphsGroup : public NRArenaGroup { + SPStyle *style; + NRRect paintbox; + /* State data */ + SPPainter *fill_painter; + SPPainter *stroke_painter; + + static NRArenaGlyphsGroup *create(NRArena *arena) { + NRArenaGlyphsGroup *obj=reinterpret_cast(nr_object_new(NR_TYPE_ARENA_GLYPHS_GROUP)); + obj->init(arena); + return obj; + } +}; + +struct NRArenaGlyphsGroupClass { + NRArenaGroupClass parent_class; +}; + +/* Utility functions */ + +void nr_arena_glyphs_group_clear (NRArenaGlyphsGroup *group); + +void nr_arena_glyphs_group_add_component (NRArenaGlyphsGroup *group, font_instance *font, int glyph, const NRMatrix *transform); + +void nr_arena_glyphs_group_set_style (NRArenaGlyphsGroup *group, SPStyle *style); + +void nr_arena_glyphs_group_set_paintbox (NRArenaGlyphsGroup *group, const NRRect *pbox); + +#endif diff --git a/src/display/nr-arena-group.cpp b/src/display/nr-arena-group.cpp new file mode 100644 index 000000000..64274202f --- /dev/null +++ b/src/display/nr-arena-group.cpp @@ -0,0 +1,256 @@ +#define __NR_ARENA_GROUP_C__ + +/* + * RGBA display list system for inkscape + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "nr-arena-group.h" + +static void nr_arena_group_class_init (NRArenaGroupClass *klass); +static void nr_arena_group_init (NRArenaGroup *group); + +static NRArenaItem *nr_arena_group_children (NRArenaItem *item); +static NRArenaItem *nr_arena_group_last_child (NRArenaItem *item); +static void nr_arena_group_add_child (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref); +static void nr_arena_group_remove_child (NRArenaItem *item, NRArenaItem *child); +static void nr_arena_group_set_child_position (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref); + +static unsigned int nr_arena_group_update (NRArenaItem *item, NRRectL *area, NRGC *gc, unsigned int state, unsigned int reset); +static unsigned int nr_arena_group_render (NRArenaItem *item, NRRectL *area, NRPixBlock *pb, unsigned int flags); +static unsigned int nr_arena_group_clip (NRArenaItem *item, NRRectL *area, NRPixBlock *pb); +static NRArenaItem *nr_arena_group_pick (NRArenaItem *item, NR::Point p, double delta, unsigned int sticky); + +static NRArenaItemClass *parent_class; + +NRType +nr_arena_group_get_type (void) +{ + static NRType type = 0; + if (!type) { + type = nr_object_register_type (NR_TYPE_ARENA_ITEM, + "NRArenaGroup", + sizeof (NRArenaGroupClass), + sizeof (NRArenaGroup), + (void (*) (NRObjectClass *)) nr_arena_group_class_init, + (void (*) (NRObject *)) nr_arena_group_init); + } + return type; +} + +static void +nr_arena_group_class_init (NRArenaGroupClass *klass) +{ + NRObjectClass *object_class; + NRArenaItemClass *item_class; + + object_class = (NRObjectClass *) klass; + item_class = (NRArenaItemClass *) klass; + + parent_class = (NRArenaItemClass *) ((NRObjectClass *) klass)->parent; + + object_class->cpp_ctor = NRObject::invoke_ctor; + + item_class->children = nr_arena_group_children; + item_class->last_child = nr_arena_group_last_child; + item_class->add_child = nr_arena_group_add_child; + item_class->set_child_position = nr_arena_group_set_child_position; + item_class->remove_child = nr_arena_group_remove_child; + item_class->update = nr_arena_group_update; + item_class->render = nr_arena_group_render; + item_class->clip = nr_arena_group_clip; + item_class->pick = nr_arena_group_pick; +} + +static void +nr_arena_group_init (NRArenaGroup *group) +{ + group->transparent = FALSE; + group->children = NULL; + group->last = NULL; + nr_matrix_set_identity (&group->child_transform); + +#ifdef arena_item_tile_cache + group->skipCaching=true; +#endif + +} + +static NRArenaItem * +nr_arena_group_children (NRArenaItem *item) +{ + NRArenaGroup *group = NR_ARENA_GROUP (item); + + return group->children; +} + +static NRArenaItem * +nr_arena_group_last_child (NRArenaItem *item) +{ + NRArenaGroup *group = NR_ARENA_GROUP (item); + + return group->last; +} + +static void +nr_arena_group_add_child (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref) +{ + NRArenaGroup *group = NR_ARENA_GROUP (item); + + if (!ref) { + group->children = nr_arena_item_attach_ref (item, child, NULL, group->children); + } else { + ref->next = nr_arena_item_attach_ref (item, child, ref, ref->next); + } + + if (ref == group->last) group->last = child; + + nr_arena_item_request_update (item, NR_ARENA_ITEM_STATE_ALL, FALSE); +} + +static void +nr_arena_group_remove_child (NRArenaItem *item, NRArenaItem *child) +{ + NRArenaGroup *group = NR_ARENA_GROUP (item); + + if (child == group->last) group->last = child->prev; + + if (child->prev) { + nr_arena_item_detach_unref (item, child); + } else { + group->children = nr_arena_item_detach_unref (item, child); + } + + nr_arena_item_request_update (item, NR_ARENA_ITEM_STATE_ALL, FALSE); +} + +static void +nr_arena_group_set_child_position (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref) +{ + NRArenaGroup *group = NR_ARENA_GROUP (item); + + if (child == group->last) group->last = child->prev; + + if (child->prev) { + nr_arena_item_detach_unref (item, child); + } else { + group->children = nr_arena_item_detach_unref (item, child); + } + + if (!ref) { + group->children = nr_arena_item_attach_ref (item, child, NULL, group->children); + } else { + ref->next = nr_arena_item_attach_ref (item, child, ref, ref->next); + } + + if (ref == group->last) group->last = child; + + nr_arena_item_request_render (child); +} + +static unsigned int +nr_arena_group_update (NRArenaItem *item, NRRectL *area, NRGC *gc, unsigned int state, unsigned int reset) +{ + unsigned int newstate; + + NRArenaGroup *group = NR_ARENA_GROUP (item); + + unsigned int beststate = NR_ARENA_ITEM_STATE_ALL; + + for (NRArenaItem *child = group->children; child != NULL; child = child->next) { + NRGC cgc(gc); + nr_matrix_multiply (&cgc.transform, &group->child_transform, &gc->transform); + newstate = nr_arena_item_invoke_update (child, area, &cgc, state, reset); + beststate = beststate & newstate; + } + + if (beststate & NR_ARENA_ITEM_STATE_BBOX) { + nr_rect_l_set_empty (&item->bbox); + for (NRArenaItem *child = group->children; child != NULL; child = child->next) { + nr_rect_l_union (&item->bbox, &item->bbox, &child->bbox); + } + } + + return beststate; +} + +static unsigned int +nr_arena_group_render (NRArenaItem *item, NRRectL *area, NRPixBlock *pb, unsigned int flags) +{ + NRArenaGroup *group = NR_ARENA_GROUP (item); + + unsigned int ret = item->state; + + /* Just compose children into parent buffer */ + for (NRArenaItem *child = group->children; child != NULL; child = child->next) { + ret = nr_arena_item_invoke_render (child, area, pb, flags); + if (ret & NR_ARENA_ITEM_STATE_INVALID) break; + } + + return ret; +} + +static unsigned int +nr_arena_group_clip (NRArenaItem *item, NRRectL *area, NRPixBlock *pb) +{ + NRArenaGroup *group = NR_ARENA_GROUP (item); + + unsigned int ret = item->state; + + /* Just compose children into parent buffer */ + for (NRArenaItem *child = group->children; child != NULL; child = child->next) { + ret = nr_arena_item_invoke_clip (child, area, pb); + if (ret & NR_ARENA_ITEM_STATE_INVALID) break; + } + + return ret; +} + +static NRArenaItem * +nr_arena_group_pick (NRArenaItem *item, NR::Point p, double delta, unsigned int sticky) +{ + NRArenaGroup *group = NR_ARENA_GROUP (item); + + for (NRArenaItem *child = group->last; child != NULL; child = child->prev) { + NRArenaItem *picked = nr_arena_item_invoke_pick (child, p, delta, sticky); + if (picked) + return (group->transparent) ? picked : item; + } + + return NULL; +} + +void +nr_arena_group_set_transparent (NRArenaGroup *group, unsigned int transparent) +{ + nr_return_if_fail (group != NULL); + nr_return_if_fail (NR_IS_ARENA_GROUP (group)); + + group->transparent = transparent; +} + +void nr_arena_group_set_child_transform(NRArenaGroup *group, NR::Matrix const &t) +{ + NRMatrix nt(t); + nr_arena_group_set_child_transform(group, &nt); +} + +void nr_arena_group_set_child_transform(NRArenaGroup *group, NRMatrix const *t) +{ + if (!t) t = &NR_MATRIX_IDENTITY; + + if (!NR_MATRIX_DF_TEST_CLOSE (t, &group->child_transform, NR_EPSILON)) { + nr_arena_item_request_render (NR_ARENA_ITEM (group)); + group->child_transform = *t; + nr_arena_item_request_update (NR_ARENA_ITEM (group), NR_ARENA_ITEM_STATE_ALL, TRUE); + } +} + + diff --git a/src/display/nr-arena-group.h b/src/display/nr-arena-group.h new file mode 100644 index 000000000..b33495362 --- /dev/null +++ b/src/display/nr-arena-group.h @@ -0,0 +1,46 @@ +#ifndef __NR_ARENA_GROUP_H__ +#define __NR_ARENA_GROUP_H__ + +/* + * RGBA display list system for inkscape + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc. + * + * Released under GNU GPL + * + */ + +#define NR_TYPE_ARENA_GROUP (nr_arena_group_get_type ()) +#define NR_ARENA_GROUP(o) (NR_CHECK_INSTANCE_CAST ((o), NR_TYPE_ARENA_GROUP, NRArenaGroup)) +#define NR_IS_ARENA_GROUP(o) (NR_CHECK_INSTANCE_TYPE ((o), NR_TYPE_ARENA_GROUP)) + +#include "nr-arena-item.h" + +NRType nr_arena_group_get_type (void); + +struct NRArenaGroup : public NRArenaItem{ + unsigned int transparent : 1; + NRArenaItem *children; + NRArenaItem *last; + NRMatrix child_transform; + + static NRArenaGroup *create(NRArena *arena) { + NRArenaGroup *obj=reinterpret_cast(nr_object_new(NR_TYPE_ARENA_GROUP)); + obj->init(arena); + return obj; + } +}; + +struct NRArenaGroupClass { + NRArenaItemClass parent_class; +}; + +void nr_arena_group_set_transparent (NRArenaGroup *group, unsigned int transparent); + +void nr_arena_group_set_child_transform(NRArenaGroup *group, NR::Matrix const &t); +void nr_arena_group_set_child_transform(NRArenaGroup *group, NRMatrix const *t); + +#endif diff --git a/src/display/nr-arena-image.cpp b/src/display/nr-arena-image.cpp new file mode 100644 index 000000000..ee566d2dc --- /dev/null +++ b/src/display/nr-arena-image.cpp @@ -0,0 +1,258 @@ +#define __NR_ARENA_IMAGE_C__ + +/* + * RGBA display list system for inkscape + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include "../prefs-utils.h" +#include "nr-arena-image.h" + +int nr_arena_image_x_sample = 1; +int nr_arena_image_y_sample = 1; + +/* + * NRArenaCanvasImage + * + */ + +static void nr_arena_image_class_init (NRArenaImageClass *klass); +static void nr_arena_image_init (NRArenaImage *image); +static void nr_arena_image_finalize (NRObject *object); + +static unsigned int nr_arena_image_update (NRArenaItem *item, NRRectL *area, NRGC *gc, unsigned int state, unsigned int reset); +static unsigned int nr_arena_image_render (NRArenaItem *item, NRRectL *area, NRPixBlock *pb, unsigned int flags); +static NRArenaItem *nr_arena_image_pick (NRArenaItem *item, NR::Point p, double delta, unsigned int sticky); + +static NRArenaItemClass *parent_class; + +NRType +nr_arena_image_get_type (void) +{ + static NRType type = 0; + if (!type) { + type = nr_object_register_type (NR_TYPE_ARENA_ITEM, + "NRArenaImage", + sizeof (NRArenaImageClass), + sizeof (NRArenaImage), + (void (*) (NRObjectClass *)) nr_arena_image_class_init, + (void (*) (NRObject *)) nr_arena_image_init); + } + return type; +} + +static void +nr_arena_image_class_init (NRArenaImageClass *klass) +{ + NRObjectClass *object_class; + NRArenaItemClass *item_class; + + object_class = (NRObjectClass *) klass; + item_class = (NRArenaItemClass *) klass; + + parent_class = (NRArenaItemClass *) ((NRObjectClass *) klass)->parent; + + object_class->finalize = nr_arena_image_finalize; + object_class->cpp_ctor = NRObject::invoke_ctor; + + item_class->update = nr_arena_image_update; + item_class->render = nr_arena_image_render; + item_class->pick = nr_arena_image_pick; +} + +static void +nr_arena_image_init (NRArenaImage *image) +{ + image->px = NULL; + + image->pxw = image->pxh = image->pxrs = 0; + image->x = image->y = 0.0; + image->width = 256.0; + image->height = 256.0; + + nr_matrix_set_identity (&image->grid2px); +} + +static void +nr_arena_image_finalize (NRObject *object) +{ + NRArenaImage *image = NR_ARENA_IMAGE (object); + + image->px = NULL; + + ((NRObjectClass *) parent_class)->finalize (object); +} + +static unsigned int +nr_arena_image_update (NRArenaItem *item, NRRectL *area, NRGC *gc, unsigned int state, unsigned int reset) +{ + NRMatrix grid2px; + + NRArenaImage *image = NR_ARENA_IMAGE (item); + + /* Request render old */ + nr_arena_item_request_render (item); + + /* Copy affine */ + nr_matrix_invert (&grid2px, &gc->transform); + double hscale, vscale; // todo: replace with NR::scale + if (image->px) { + hscale = image->pxw / image->width; + vscale = image->pxh / image->height; + } else { + hscale = 1.0; + vscale = 1.0; + } + + image->grid2px[0] = grid2px.c[0] * hscale; + image->grid2px[2] = grid2px.c[2] * hscale; + image->grid2px[4] = grid2px.c[4] * hscale; + image->grid2px[1] = grid2px.c[1] * vscale; + image->grid2px[3] = grid2px.c[3] * vscale; + image->grid2px[5] = grid2px.c[5] * vscale; + + image->grid2px[4] -= image->x * hscale; + image->grid2px[5] -= image->y * vscale; + + /* Calculate bbox */ + if (image->px) { + NRRect bbox; + + bbox.x0 = image->x; + bbox.y0 = image->y; + bbox.x1 = image->x + image->width; + bbox.y1 = image->y + image->height; + nr_rect_d_matrix_transform (&bbox, &bbox, &gc->transform); + + item->bbox.x0 = (int) floor (bbox.x0); + item->bbox.y0 = (int) floor (bbox.y0); + item->bbox.x1 = (int) ceil (bbox.x1); + item->bbox.y1 = (int) ceil (bbox.y1); + } else { + item->bbox.x0 = (int) gc->transform[4]; + item->bbox.y0 = (int) gc->transform[5]; + item->bbox.x1 = item->bbox.x0 - 1; + item->bbox.y1 = item->bbox.y0 - 1; + } + + nr_arena_item_request_render (item); + + return NR_ARENA_ITEM_STATE_ALL; +} + +#define FBITS 12 +#define b2i (image->grid2px) + +static unsigned int +nr_arena_image_render (NRArenaItem *item, NRRectL *area, NRPixBlock *pb, unsigned int flags) +{ + nr_arena_image_x_sample = prefs_get_int_attribute ("options.bitmapoversample", "value", 1); + nr_arena_image_y_sample = nr_arena_image_x_sample; + + NRArenaImage *image = NR_ARENA_IMAGE (item); + + if (!image->px) return item->state; + + guint32 Falpha = item->opacity; + if (Falpha < 1) return item->state; + + unsigned char * dpx = NR_PIXBLOCK_PX (pb); + const int drs = pb->rs; + const int dw = pb->area.x1 - pb->area.x0; + const int dh = pb->area.y1 - pb->area.y0; + + unsigned char * spx = image->px; + const int srs = image->pxrs; + const int sw = image->pxw; + const int sh = image->pxh; + + NR::Matrix d2s; + + d2s[0] = b2i[0]; + d2s[1] = b2i[1]; + d2s[2] = b2i[2]; + d2s[3] = b2i[3]; + d2s[4] = b2i[0] * pb->area.x0 + b2i[2] * pb->area.y0 + b2i[4]; + d2s[5] = b2i[1] * pb->area.x0 + b2i[3] * pb->area.y0 + b2i[5]; + + if (pb->mode == NR_PIXBLOCK_MODE_R8G8B8) { + /* fixme: This is not implemented yet (Lauris) */ + /* nr_R8G8B8_R8G8B8_R8G8B8A8_N_TRANSFORM (dpx, dw, dh, drs, spx, sw, sh, srs, d2s, Falpha, nr_arena_image_x_sample, nr_arena_image_y_sample); */ + } else if (pb->mode == NR_PIXBLOCK_MODE_R8G8B8A8P) { + nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM (dpx, dw, dh, drs, spx, sw, sh, srs, d2s, Falpha, nr_arena_image_x_sample, nr_arena_image_y_sample); + } else if (pb->mode == NR_PIXBLOCK_MODE_R8G8B8A8N) { + + //FIXME: The _N_N_N_ version gives a gray border around images, see bug 906376 + // This mode is only used when exporting, screen rendering always has _P_P_P_, so I decided to simply replace it for now + // Feel free to propose a better fix + + //nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_N_TRANSFORM (dpx, dw, dh, drs, spx, sw, sh, srs, d2s, Falpha, nr_arena_image_x_sample, nr_arena_image_y_sample); + nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM (dpx, dw, dh, drs, spx, sw, sh, srs, d2s, Falpha, nr_arena_image_x_sample, nr_arena_image_y_sample); + } + + pb->empty = FALSE; + + return item->state; +} + +static NRArenaItem * +nr_arena_image_pick (NRArenaItem *item, NR::Point p, double delta, unsigned int sticky) +{ + NRArenaImage *image = NR_ARENA_IMAGE (item); + + if (!image->px) return NULL; + + unsigned char * const pixels = image->px; + const int width = image->pxw; + const int height = image->pxh; + const int rowstride = image->pxrs; + NR::Point tp = p * image->grid2px; + const int ix = (int)(tp[NR::X]); + const int iy = (int)(tp[NR::Y]); + + if ((ix < 0) || (iy < 0) || (ix >= width) || (iy >= height)) + return NULL; + + unsigned char *pix_ptr = pixels + iy * rowstride + ix * 4; + // is the alpha not transparent? + return (pix_ptr[3] > 0) ? item : NULL; +} + +/* Utility */ + +void +nr_arena_image_set_pixels (NRArenaImage *image, const unsigned char *px, unsigned int pxw, unsigned int pxh, unsigned int pxrs) +{ + nr_return_if_fail (image != NULL); + nr_return_if_fail (NR_IS_ARENA_IMAGE (image)); + + image->px = (unsigned char *) px; + image->pxw = pxw; + image->pxh = pxh; + image->pxrs = pxrs; + + nr_arena_item_request_update (NR_ARENA_ITEM (image), NR_ARENA_ITEM_STATE_ALL, FALSE); +} + +void +nr_arena_image_set_geometry (NRArenaImage *image, double x, double y, double width, double height) +{ + nr_return_if_fail (image != NULL); + nr_return_if_fail (NR_IS_ARENA_IMAGE (image)); + + image->x = x; + image->y = y; + image->width = width; + image->height = height; + + nr_arena_item_request_update (NR_ARENA_ITEM (image), NR_ARENA_ITEM_STATE_ALL, FALSE); +} + diff --git a/src/display/nr-arena-image.h b/src/display/nr-arena-image.h new file mode 100644 index 000000000..8c5afc55f --- /dev/null +++ b/src/display/nr-arena-image.h @@ -0,0 +1,51 @@ +#ifndef __NR_ARENA_IMAGE_H__ +#define __NR_ARENA_IMAGE_H__ + +/* + * RGBA display list system for inkscape + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define NR_TYPE_ARENA_IMAGE (nr_arena_image_get_type ()) +#define NR_ARENA_IMAGE(o) (NR_CHECK_INSTANCE_CAST ((o), NR_TYPE_ARENA_IMAGE, NRArenaImage)) +#define NR_IS_ARENA_IMAGE(o) (NR_CHECK_INSTANCE_TYPE ((o), NR_TYPE_ARENA_IMAGE)) + +#include +#include "nr-arena-item.h" + +NRType nr_arena_image_get_type (void); + +struct NRArenaImage : public NRArenaItem { + unsigned char *px; + unsigned int pxw; + unsigned int pxh; + unsigned int pxrs; + + double x, y; + double width, height; + + /* From GRID to PIXELS */ + NR::Matrix grid2px; + + static NRArenaImage *create(NRArena *arena) { + NRArenaImage *obj=reinterpret_cast(nr_object_new(NR_TYPE_ARENA_IMAGE)); + obj->init(arena); + return obj; + } +}; + +struct NRArenaImageClass { + NRArenaItemClass parent_class; +}; + +void nr_arena_image_set_pixels (NRArenaImage *image, const unsigned char *px, unsigned int pxw, unsigned int pxh, unsigned int pxrs); +void nr_arena_image_set_geometry (NRArenaImage *image, double x, double y, double width, double height); + +#endif diff --git a/src/display/nr-arena-item.cpp b/src/display/nr-arena-item.cpp new file mode 100644 index 000000000..ccabe7b28 --- /dev/null +++ b/src/display/nr-arena-item.cpp @@ -0,0 +1,1155 @@ +#define __NR_ARENA_ITEM_C__ + +/* + * RGBA display list system for inkscape + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define noNR_ARENA_ITEM_VERBOSE +#define noNR_ARENA_ITEM_DEBUG_CASCADE + + +#include +#include +#include "nr-arena.h" +#include "nr-arena-item.h" +//#include "nr-arena-group.h" + + +static void nr_arena_item_class_init (NRArenaItemClass *klass); +static void nr_arena_item_init (NRArenaItem *item); +static void nr_arena_item_private_finalize (NRObject *object); + +#ifdef arena_item_tile_cache +bool insert_cache(NRArenaItem* owner,int th,int tv,NRPixBlock *ipb,NRPixBlock *mpb,double activity,double duration); +void remove_caches(NRArenaItem* owner); +bool test_cache(NRArenaItem* owner,int th,int tv,NRPixBlock &ipb,NRPixBlock &mpb,bool &hasMask); +#endif + +static NRObjectClass *parent_class; + +NRType +nr_arena_item_get_type (void) +{ + static NRType type = 0; + if (!type) { + type = nr_object_register_type (NR_TYPE_OBJECT, + "NRArenaItem", + sizeof (NRArenaItemClass), + sizeof (NRArenaItem), + (void (*) (NRObjectClass *)) nr_arena_item_class_init, + (void (*) (NRObject *)) nr_arena_item_init); + } + return type; +} + +static void +nr_arena_item_class_init (NRArenaItemClass *klass) +{ + NRObjectClass *object_class; + + object_class = (NRObjectClass *) klass; + + parent_class = ((NRObjectClass *) klass)->parent; + + object_class->finalize = nr_arena_item_private_finalize; + object_class->cpp_ctor = NRObject::invoke_ctor; +} + +NRArenaItem::NRArenaItem() { + // clear all reverse-pointing pointers before finalization + clearOnceInaccessible(&arena); + clearOnceInaccessible(&parent); + clearOnceInaccessible(&prev); +} + +static void +nr_arena_item_init (NRArenaItem *item) +{ + item->arena = NULL; + item->parent = NULL; + item->next = item->prev = NULL; + + item->key = 0; + + item->state = 0; + item->sensitive = TRUE; + item->visible = TRUE; + + memset(&item->bbox, 0, sizeof(item->bbox)); + item->transform = NULL; + item->opacity = 255; + item->render_opacity = FALSE; + +#ifdef arena_item_tile_cache + item->activity=0.0; + item->skipCaching=false; +#endif + + item->transform = NULL; + item->clip = NULL; + item->mask = NULL; + item->px = NULL; + item->data = NULL; +} + +static void +nr_arena_item_private_finalize (NRObject *object) +{ + NRArenaItem *item=static_cast(object); + +#ifdef arena_item_tile_cache + remove_caches(item); +#endif + + if (item->px) { + nr_free (item->px); + } + + if (item->transform) { + nr_free (item->transform); + } + + ((NRObjectClass *) (parent_class))->finalize (object); +} + +NRArenaItem * +nr_arena_item_children (NRArenaItem *item) +{ + nr_return_val_if_fail (item != NULL, NULL); + nr_return_val_if_fail (NR_IS_ARENA_ITEM (item), NULL); + + if (NR_ARENA_ITEM_VIRTUAL (item, children)) + return NR_ARENA_ITEM_VIRTUAL (item, children) (item); + + return NULL; +} + +NRArenaItem * +nr_arena_item_last_child (NRArenaItem *item) +{ + nr_return_val_if_fail (item != NULL, NULL); + nr_return_val_if_fail (NR_IS_ARENA_ITEM (item), NULL); + + if (NR_ARENA_ITEM_VIRTUAL (item, last_child)) { + return NR_ARENA_ITEM_VIRTUAL (item, last_child) (item); + } else { + NRArenaItem *ref; + ref = nr_arena_item_children (item); + if (ref) while (ref->next) ref = ref->next; + return ref; + } +} + +void +nr_arena_item_add_child (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref) +{ + nr_return_if_fail (item != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (item)); + nr_return_if_fail (child != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (child)); + nr_return_if_fail (child->parent == NULL); + nr_return_if_fail (child->prev == NULL); + nr_return_if_fail (child->next == NULL); + nr_return_if_fail (child->arena == item->arena); + nr_return_if_fail (child != ref); + nr_return_if_fail (!ref || NR_IS_ARENA_ITEM (ref)); + nr_return_if_fail (!ref || (ref->parent == item)); + + if (NR_ARENA_ITEM_VIRTUAL (item, add_child)) + NR_ARENA_ITEM_VIRTUAL (item, add_child) (item, child, ref); +} + +void +nr_arena_item_remove_child (NRArenaItem *item, NRArenaItem *child) +{ + nr_return_if_fail (item != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (item)); + nr_return_if_fail (child != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (child)); + nr_return_if_fail (child->parent == item); + + if (NR_ARENA_ITEM_VIRTUAL (item, remove_child)) + NR_ARENA_ITEM_VIRTUAL (item, remove_child) (item, child); +} + +void +nr_arena_item_set_child_position (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref) +{ + nr_return_if_fail (item != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (item)); + nr_return_if_fail (child != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (child)); + nr_return_if_fail (child->parent == item); + nr_return_if_fail (!ref || NR_IS_ARENA_ITEM (ref)); + nr_return_if_fail (!ref || (ref->parent == item)); + + if (NR_ARENA_ITEM_VIRTUAL (item, set_child_position)) + NR_ARENA_ITEM_VIRTUAL (item, set_child_position) (item, child, ref); +} + +NRArenaItem * +nr_arena_item_ref (NRArenaItem *item) +{ + nr_object_ref ((NRObject *) item); + + return item; +} + +NRArenaItem * +nr_arena_item_unref (NRArenaItem *item) +{ + nr_object_unref ((NRObject *) item); + + return NULL; +} + +unsigned int +nr_arena_item_invoke_update (NRArenaItem *item, NRRectL *area, NRGC *gc, unsigned int state, unsigned int reset) +{ + NRGC childgc(gc); + + nr_return_val_if_fail (item != NULL, NR_ARENA_ITEM_STATE_INVALID); + nr_return_val_if_fail (NR_IS_ARENA_ITEM (item), NR_ARENA_ITEM_STATE_INVALID); + nr_return_val_if_fail (!(state & NR_ARENA_ITEM_STATE_INVALID), NR_ARENA_ITEM_STATE_INVALID); + +#ifdef NR_ARENA_ITEM_DEBUG_CASCADE + printf ("Update %s:%p %x %x %x\n", nr_type_name_from_instance ((GTypeInstance *) item), item, state, item->state, reset); +#endif + + /* return if in error */ + if (item->state & NR_ARENA_ITEM_STATE_INVALID) return item->state; + /* Set reset flags according to propagation status */ + if (item->propagate) { + reset |= ~item->state; + item->propagate = FALSE; + } + /* Reset our state */ + item->state &= ~reset; + /* Return if NOP */ + if (!(~item->state & state)) return item->state; + /* Test whether to return immediately */ + if (area && (item->state & NR_ARENA_ITEM_STATE_BBOX)) { + if (!nr_rect_l_test_intersect (area, &item->bbox)) return item->state; + } + + /* Reset image cache, if not to be kept */ + if (!(item->state & NR_ARENA_ITEM_STATE_IMAGE) && (item->px)) { + nr_free (item->px); + item->px = NULL; + } +#ifdef arena_item_tile_cache + remove_caches(item); +#endif + + /* Set up local gc */ + childgc = *gc; + if (item->transform) { + nr_matrix_multiply (&childgc.transform, item->transform, &childgc.transform); + } + + /* Invoke the real method */ + item->state = NR_ARENA_ITEM_VIRTUAL (item, update) (item, area, &childgc, state, reset); + if (item->state & NR_ARENA_ITEM_STATE_INVALID) return item->state; + /* Clipping */ + if (item->clip) { + unsigned int newstate; + newstate = nr_arena_item_invoke_update (item->clip, area, &childgc, state, reset); + if (newstate & NR_ARENA_ITEM_STATE_INVALID) { + item->state |= NR_ARENA_ITEM_STATE_INVALID; + return item->state; + } + nr_rect_l_intersect (&item->bbox, &item->bbox, &item->clip->bbox); + } + /* Masking */ + if (item->mask) { + unsigned int newstate; + newstate = nr_arena_item_invoke_update (item->mask, area, &childgc, state, reset); + if (newstate & NR_ARENA_ITEM_STATE_INVALID) { + item->state |= NR_ARENA_ITEM_STATE_INVALID; + return item->state; + } + nr_rect_l_intersect (&item->bbox, &item->bbox, &item->mask->bbox); + } + + return item->state; +} + +/** + * Render item to pixblock. + * + * \return Has NR_ARENA_ITEM_STATE_RENDER set on success. + */ + +unsigned int nr_arena_item_invoke_render(NRArenaItem *item, NRRectL const *area, NRPixBlock *pb, unsigned int flags) +{ + NRRectL carea; + NRPixBlock *dpb; + NRPixBlock cpb; + unsigned int state; + + nr_return_val_if_fail (item != NULL, NR_ARENA_ITEM_STATE_INVALID); + nr_return_val_if_fail (NR_IS_ARENA_ITEM (item), NR_ARENA_ITEM_STATE_INVALID); + nr_return_val_if_fail (item->state & NR_ARENA_ITEM_STATE_BBOX, item->state); + +#ifdef NR_ARENA_ITEM_VERBOSE + printf ("Invoke render %p: %d %d - %d %d\n", item, area->x0, area->y0, area->x1, area->y1); +#endif + +#ifdef arena_item_tile_cache + item->activity*=0.5; +#endif + + /* If we are outside bbox just return successfully */ + if (!item->visible) return item->state | NR_ARENA_ITEM_STATE_RENDER; + nr_rect_l_intersect (&carea, area, &item->bbox); + if (nr_rect_l_test_empty (&carea)) return item->state | NR_ARENA_ITEM_STATE_RENDER; + + if (item->px) { + /* Has cache pixblock, render this and return */ + nr_pixblock_setup_extern (&cpb, NR_PIXBLOCK_MODE_R8G8B8A8P, + /* fixme: This probably cannot overflow, because we render only if visible */ + /* fixme: and pixel cache is there only for small items */ + /* fixme: But this still needs extra check (Lauris) */ + item->bbox.x0, item->bbox.y0, + item->bbox.x1, item->bbox.y1, + item->px, 4 * (item->bbox.x1 - item->bbox.x0), FALSE, FALSE); + nr_blit_pixblock_pixblock (pb, &cpb); + nr_pixblock_release (&cpb); + pb->empty = FALSE; + return item->state | NR_ARENA_ITEM_STATE_RENDER; + } + + dpb = pb; + bool canCache=false; +#ifdef arena_item_tile_cache + bool checkCache=false; + int tile_h=0,tile_v=0; +#endif + /* Setup cache if we can */ + if ((!(flags & NR_ARENA_ITEM_RENDER_NO_CACHE)) && + (carea.x0 <= item->bbox.x0) && (carea.y0 <= item->bbox.y0) && + (carea.x1 >= item->bbox.x1) && (carea.y1 >= item->bbox.y1) && + (((item->bbox.x1 - item->bbox.x0) * (item->bbox.y1 - item->bbox.y0)) <= 4096)) { + // Item bbox is fully in renderable area and size is acceptable + carea.x0 = item->bbox.x0; + carea.y0 = item->bbox.y0; + carea.x1 = item->bbox.x1; + carea.y1 = item->bbox.y1; + item->px = nr_new (unsigned char, 4 * (carea.x1 - carea.x0) * (carea.y1 - carea.y0)); + nr_pixblock_setup_extern (&cpb, NR_PIXBLOCK_MODE_R8G8B8A8P, + carea.x0, carea.y0, carea.x1, carea.y1, + item->px, 4 * (carea.x1 - carea.x0), TRUE, TRUE); + dpb = &cpb; + // Set nocache flag for downstream rendering + flags |= NR_ARENA_ITEM_RENDER_NO_CACHE; + } else { +#ifdef arena_item_tile_cache + if ( item->skipCaching ) { + } else { + int tl=area->x0&(~127); + int tt=area->y0&(~127); + if ( area->x1 <= tl+128 && area->y1 <= tt+128 ) { + checkCache=true; + tile_h=tl/128; + tile_v=tt/128; + int surf=(area->x1-area->x0)*(area->y1-area->y0); + if ( surf >= 4096 ) { + canCache=true; + carea.x0=tl; + carea.y0=tt; + carea.x1=tl+128; + carea.y1=tt+128; + } + } + } +#endif + } + +#ifdef arena_item_tile_cache + item->activity+=1.0; +#endif + +#ifdef arena_item_tile_cache + if ( checkCache ) { + NRPixBlock ipb, mpb; + bool hasMask; + if ( test_cache(item,tile_h,tile_v,ipb,mpb,hasMask) ) { + // youpi! c'etait deja cache + if ( hasMask ) { + nr_blit_pixblock_pixblock_mask (dpb, &ipb, &mpb); + } else if ( ((item->opacity != 255) && !item->render_opacity) ) { + nr_blit_pixblock_pixblock_alpha (dpb, &ipb, item->opacity); + } else { + nr_blit_pixblock_pixblock (pb, &ipb); + } + pb->empty = FALSE; + return item->state | NR_ARENA_ITEM_STATE_RENDER; + } + } +#endif + if ( canCache ) { +#ifdef arena_item_tile_cache + // nota: exclusif de dpb != pb, donc pas de cas particulier a la fin + NRPixBlock ipb, mpb; + + // struct timeval start_time,end_time; + // gettimeofday(&start_time,NULL); + GTimeVal start_time,end_time; + g_get_current_time (&start_time); + int duration=0; + + /* Setup and render item buffer */ + nr_pixblock_setup_fast (&ipb, NR_PIXBLOCK_MODE_R8G8B8A8P, carea.x0, carea.y0, carea.x1, carea.y1, TRUE); + state = NR_ARENA_ITEM_VIRTUAL (item, render) (item, &carea, &ipb, flags); + if (state & NR_ARENA_ITEM_STATE_INVALID) { + /* Clean up and return error */ + nr_pixblock_release (&ipb); + if (dpb != pb) nr_pixblock_release (dpb); + item->state |= NR_ARENA_ITEM_STATE_INVALID; + return item->state; + } + ipb.empty = FALSE; + + if (item->clip || item->mask) { + /* Setup mask pixblock */ + nr_pixblock_setup_fast (&mpb, NR_PIXBLOCK_MODE_A8, carea.x0, carea.y0, carea.x1, carea.y1, TRUE); + /* Do clip if needed */ + if (item->clip) { + state = nr_arena_item_invoke_clip (item->clip, &carea, &mpb); + if (state & NR_ARENA_ITEM_STATE_INVALID) { + /* Clean up and return error */ + nr_pixblock_release (&mpb); + nr_pixblock_release (&ipb); + if (dpb != pb) nr_pixblock_release (dpb); + item->state |= NR_ARENA_ITEM_STATE_INVALID; + return item->state; + } + mpb.empty = FALSE; + } + /* Do mask if needed */ + if (item->mask) { + NRPixBlock tpb; + /* Set up yet another temporary pixblock */ + nr_pixblock_setup_fast (&tpb, NR_PIXBLOCK_MODE_R8G8B8A8N, carea.x0, carea.y0, carea.x1, carea.y1, TRUE); + state = NR_ARENA_ITEM_VIRTUAL (item->mask, render) (item->mask, &carea, &tpb, flags); + if (state & NR_ARENA_ITEM_STATE_INVALID) { + /* Clean up and return error */ + nr_pixblock_release (&tpb); + nr_pixblock_release (&mpb); + nr_pixblock_release (&ipb); + if (dpb != pb) nr_pixblock_release (dpb); + item->state |= NR_ARENA_ITEM_STATE_INVALID; + return item->state; + } + /* Composite with clip */ + if (item->clip) { + int x, y; + for (y = carea.y0; y < carea.y1; y++) { + unsigned char *s, *d; + s = NR_PIXBLOCK_PX (&tpb) + (y - carea.y0) * tpb.rs; + d = NR_PIXBLOCK_PX (&mpb) + (y - carea.y0) * mpb.rs; + for (x = carea.x0; x < carea.x1; x++) { + unsigned int m; + m = ((s[0] + s[1] + s[2]) * s[3] + 127) / (3 * 255); + d[0] = NR_PREMUL (d[0], m); + s += 4; + d += 1; + } + } + } else { + int x, y; + for (y = carea.y0; y < carea.y1; y++) { + unsigned char *s, *d; + s = NR_PIXBLOCK_PX (&tpb) + (y - carea.y0) * tpb.rs; + d = NR_PIXBLOCK_PX (&mpb) + (y - carea.y0) * mpb.rs; + for (x = carea.x0; x < carea.x1; x++) { + unsigned int m; + m = ((s[0] + s[1] + s[2]) * s[3] + 127) / (3 * 255); + d[0] = m; + s += 4; + d += 1; + } + } + mpb.empty = FALSE; + } + nr_pixblock_release (&tpb); + } + /* Multiply with opacity if needed */ + if ((item->opacity != 255) && !item->render_opacity && item->arena->rendermode != RENDERMODE_OUTLINE) { + int x, y; + unsigned int a; + a = item->opacity; + for (y = carea.y0; y < carea.y1; y++) { + unsigned char *d; + d = NR_PIXBLOCK_PX (&mpb) + (y - carea.y0) * mpb.rs; + for (x = carea.x0; x < carea.x1; x++) { + d[0] = NR_PREMUL (d[0], a); + d += 1; + } + } + } + /* Compose rendering pixblock int destination */ + // gettimeofday(&end_time,NULL); + g_get_current_time (&end_time); + duration=(end_time.tv_sec-start_time.tv_sec)*1000+(end_time.tv_usec-start_time.tv_usec)/1000; + if ( !(ipb.empty) ) { + nr_blit_pixblock_pixblock_mask (dpb, &ipb, &mpb); + if ( insert_cache(item,tile_h,tile_v,&ipb,&mpb,item->activity,(double)duration) ) { + } else { + nr_pixblock_release (&mpb); + nr_pixblock_release (&ipb); + } + dpb->empty = FALSE; + } else { + nr_pixblock_release (&ipb); + } + } else if ( ((item->opacity != 255) && !item->render_opacity && item->arena->rendermode != RENDERMODE_OUTLINE) ) { + /* Opacity only */ + // gettimeofday(&end_time,NULL); + g_get_current_time (&end_time); + duration=(end_time.tv_sec-start_time.tv_sec)*1000+(end_time.tv_usec-start_time.tv_usec)/1000; + if ( !(ipb.empty) ) { + nr_blit_pixblock_pixblock_alpha (dpb, &ipb, item->opacity); + if ( insert_cache(item,tile_h,tile_v,&ipb,NULL,item->activity,(double)duration) ) { + } else { + nr_pixblock_release (&ipb); + } + dpb->empty = FALSE; + } else { + nr_pixblock_release (&ipb); + } + } else { + // gettimeofday(&end_time,NULL); + g_get_current_time (&end_time); + duration=(end_time.tv_sec-start_time.tv_sec)*1000+(end_time.tv_usec-start_time.tv_usec)/1000; + if ( !(ipb.empty) ) { + nr_blit_pixblock_pixblock (dpb, &ipb); + if ( insert_cache(item,tile_h,tile_v,&ipb,NULL,item->activity,(double)duration) ) { + } else { + nr_pixblock_release (&ipb); + } + dpb->empty = FALSE; + } else { + nr_pixblock_release (&ipb); + } + } +#endif + } else { + /* Determine, whether we need temporary buffer */ + if (item->clip || item->mask || ((item->opacity != 255) && !item->render_opacity && item->arena->rendermode != RENDERMODE_OUTLINE)) { + NRPixBlock ipb, mpb; + + /* Setup and render item buffer */ + nr_pixblock_setup_fast (&ipb, NR_PIXBLOCK_MODE_R8G8B8A8P, carea.x0, carea.y0, carea.x1, carea.y1, TRUE); + state = NR_ARENA_ITEM_VIRTUAL (item, render) (item, &carea, &ipb, flags); + if (state & NR_ARENA_ITEM_STATE_INVALID) { + /* Clean up and return error */ + nr_pixblock_release (&ipb); + if (dpb != pb) nr_pixblock_release (dpb); + item->state |= NR_ARENA_ITEM_STATE_INVALID; + return item->state; + } + ipb.empty = FALSE; + + if (item->clip || item->mask) { + /* Setup mask pixblock */ + nr_pixblock_setup_fast (&mpb, NR_PIXBLOCK_MODE_A8, carea.x0, carea.y0, carea.x1, carea.y1, TRUE); + /* Do clip if needed */ + if (item->clip) { + state = nr_arena_item_invoke_clip (item->clip, &carea, &mpb); + if (state & NR_ARENA_ITEM_STATE_INVALID) { + /* Clean up and return error */ + nr_pixblock_release (&mpb); + nr_pixblock_release (&ipb); + if (dpb != pb) nr_pixblock_release (dpb); + item->state |= NR_ARENA_ITEM_STATE_INVALID; + return item->state; + } + mpb.empty = FALSE; + } + /* Do mask if needed */ + if (item->mask) { + NRPixBlock tpb; + /* Set up yet another temporary pixblock */ + nr_pixblock_setup_fast (&tpb, NR_PIXBLOCK_MODE_R8G8B8A8N, carea.x0, carea.y0, carea.x1, carea.y1, TRUE); + state = NR_ARENA_ITEM_VIRTUAL (item->mask, render) (item->mask, &carea, &tpb, flags); + if (state & NR_ARENA_ITEM_STATE_INVALID) { + /* Clean up and return error */ + nr_pixblock_release (&tpb); + nr_pixblock_release (&mpb); + nr_pixblock_release (&ipb); + if (dpb != pb) nr_pixblock_release (dpb); + item->state |= NR_ARENA_ITEM_STATE_INVALID; + return item->state; + } + /* Composite with clip */ + if (item->clip) { + int x, y; + for (y = carea.y0; y < carea.y1; y++) { + unsigned char *s, *d; + s = NR_PIXBLOCK_PX (&tpb) + (y - carea.y0) * tpb.rs; + d = NR_PIXBLOCK_PX (&mpb) + (y - carea.y0) * mpb.rs; + for (x = carea.x0; x < carea.x1; x++) { + unsigned int m; + m = ((s[0] + s[1] + s[2]) * s[3] + 127) / (3 * 255); + d[0] = NR_PREMUL (d[0], m); + s += 4; + d += 1; + } + } + } else { + int x, y; + for (y = carea.y0; y < carea.y1; y++) { + unsigned char *s, *d; + s = NR_PIXBLOCK_PX (&tpb) + (y - carea.y0) * tpb.rs; + d = NR_PIXBLOCK_PX (&mpb) + (y - carea.y0) * mpb.rs; + for (x = carea.x0; x < carea.x1; x++) { + unsigned int m; + m = ((s[0] + s[1] + s[2]) * s[3] + 127) / (3 * 255); + d[0] = m; + s += 4; + d += 1; + } + } + mpb.empty = FALSE; + } + nr_pixblock_release (&tpb); + } + /* Multiply with opacity if needed */ + if ((item->opacity != 255) && !item->render_opacity && item->arena->rendermode != RENDERMODE_OUTLINE) { + int x, y; + unsigned int a; + a = item->opacity; + for (y = carea.y0; y < carea.y1; y++) { + unsigned char *d; + d = NR_PIXBLOCK_PX (&mpb) + (y - carea.y0) * mpb.rs; + for (x = carea.x0; x < carea.x1; x++) { + d[0] = NR_PREMUL (d[0], a); + d += 1; + } + } + } + /* Compose rendering pixblock int destination */ + nr_blit_pixblock_pixblock_mask (dpb, &ipb, &mpb); + nr_pixblock_release (&mpb); + } else { + /* Opacity only */ + nr_blit_pixblock_pixblock_alpha (dpb, &ipb, item->opacity); + } + nr_pixblock_release (&ipb); + dpb->empty = FALSE; + } else { + /* Just render */ + state = NR_ARENA_ITEM_VIRTUAL (item, render) (item, &carea, dpb, flags); + if (state & NR_ARENA_ITEM_STATE_INVALID) { + /* Clean up and return error */ + if (dpb != pb) nr_pixblock_release (dpb); + item->state |= NR_ARENA_ITEM_STATE_INVALID; + return item->state; + } + dpb->empty = FALSE; + } + + if (dpb != pb) { + /* Have to blit from cache */ + nr_blit_pixblock_pixblock (pb, dpb); + nr_pixblock_release (dpb); + pb->empty = FALSE; + item->state |= NR_ARENA_ITEM_STATE_IMAGE; + } + } + return item->state | NR_ARENA_ITEM_STATE_RENDER; +} + +unsigned int +nr_arena_item_invoke_clip (NRArenaItem *item, NRRectL *area, NRPixBlock *pb) +{ + nr_return_val_if_fail (item != NULL, NR_ARENA_ITEM_STATE_INVALID); + nr_return_val_if_fail (NR_IS_ARENA_ITEM (item), NR_ARENA_ITEM_STATE_INVALID); + /* we originally short-circuited if the object state included + * NR_ARENA_ITEM_STATE_CLIP (and showed a warning on the console); + * anyone know why we stopped doing so? + */ + nr_return_val_if_fail ((pb->area.x1 - pb->area.x0) >= (area->x1 - area->x0), NR_ARENA_ITEM_STATE_INVALID); + nr_return_val_if_fail ((pb->area.y1 - pb->area.y0) >= (area->y1 - area->y0), NR_ARENA_ITEM_STATE_INVALID); + +#ifdef NR_ARENA_ITEM_VERBOSE + printf ("Invoke render %p: %d %d - %d %d\n", item, area->x0, area->y0, area->x1, area->y1); +#endif + + if (item->visible && nr_rect_l_test_intersect (area, &item->bbox)) { + /* Need render that item */ + if (((NRArenaItemClass *) NR_OBJECT_GET_CLASS (item))->clip) + return ((NRArenaItemClass *) NR_OBJECT_GET_CLASS(item))->clip (item, area, pb); + } + + return item->state; +} + +NRArenaItem * +nr_arena_item_invoke_pick (NRArenaItem *item, NR::Point p, double delta, unsigned int sticky) +{ + nr_return_val_if_fail (item != NULL, NULL); + nr_return_val_if_fail (NR_IS_ARENA_ITEM (item), NULL); + + // Sometimes there's no BBOX in item->state, reason unknown (bug 992817); I made this not an assert to remove the warning + if (!(item->state & NR_ARENA_ITEM_STATE_BBOX) || !(item->state & NR_ARENA_ITEM_STATE_PICK)) + return NULL; + + if (!sticky && !(item->visible && item->sensitive)) return NULL; + + // TODO: rewrite using NR::Rect + const double x = p[NR::X]; + const double y = p[NR::Y]; + + if (((x + delta) >= item->bbox.x0) && + ((x - delta) < item->bbox.x1) && + ((y + delta) >= item->bbox.y0) && + ((y - delta) < item->bbox.y1)) { + if (((NRArenaItemClass *) NR_OBJECT_GET_CLASS (item))->pick) + return ((NRArenaItemClass *) NR_OBJECT_GET_CLASS (item))->pick (item, p, delta, sticky); + } + + return NULL; +} + +void +nr_arena_item_request_update (NRArenaItem *item, unsigned int reset, unsigned int propagate) +{ + nr_return_if_fail (item != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (item)); + nr_return_if_fail (!(reset & NR_ARENA_ITEM_STATE_INVALID)); + + if (propagate && !item->propagate) item->propagate = TRUE; + + if (item->state & reset) { + item->state &= ~reset; + if (item->parent) { + nr_arena_item_request_update (item->parent, reset, FALSE); + } else { + nr_arena_request_update (item->arena, item); + } + } +} + +void +nr_arena_item_request_render (NRArenaItem *item) +{ + nr_return_if_fail (item != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (item)); + + nr_arena_request_render_rect (item->arena, &item->bbox); +} + +/* Public */ + +NRArenaItem * +nr_arena_item_unparent (NRArenaItem *item) +{ + nr_return_val_if_fail (item != NULL, NULL); + nr_return_val_if_fail (NR_IS_ARENA_ITEM (item), NULL); + + nr_arena_item_request_render (item); + + if (item->parent) { + nr_arena_item_remove_child (item->parent, item); + } + + return NULL; +} + +void +nr_arena_item_append_child (NRArenaItem *parent, NRArenaItem *child) +{ + nr_return_if_fail (parent != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (parent)); + nr_return_if_fail (child != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (child)); + nr_return_if_fail (parent->arena == child->arena); + nr_return_if_fail (child->parent == NULL); + nr_return_if_fail (child->prev == NULL); + nr_return_if_fail (child->next == NULL); + + nr_arena_item_add_child (parent, child, nr_arena_item_last_child (parent)); +} + +void +nr_arena_item_set_transform(NRArenaItem *item, NR::Matrix const &transform) +{ + NRMatrix const t(transform); + nr_arena_item_set_transform(item, &t); +} + +void +nr_arena_item_set_transform(NRArenaItem *item, NRMatrix const *transform) +{ + const NRMatrix *ms, *md; + + nr_return_if_fail (item != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (item)); + + if (!transform && !item->transform) return; + + md = (item->transform) ? item->transform : &NR_MATRIX_IDENTITY; + ms = (transform) ? transform : &NR_MATRIX_IDENTITY; + + if (!NR_MATRIX_DF_TEST_CLOSE (md, ms, NR_EPSILON)) { + nr_arena_item_request_render (item); + if (!transform || nr_matrix_test_identity (transform, NR_EPSILON)) { + /* Set to identity affine */ + if (item->transform) nr_free (item->transform); + item->transform = NULL; + } else { + if (!item->transform) item->transform = nr_new (NRMatrix, 1); + *item->transform = *transform; + } + nr_arena_item_request_update (item, NR_ARENA_ITEM_STATE_ALL, TRUE); + } +} + +void +nr_arena_item_set_opacity (NRArenaItem *item, double opacity) +{ + nr_return_if_fail (item != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (item)); + + nr_arena_item_request_render (item); + + item->opacity = (unsigned int) (opacity * 255.9999); +} + +void +nr_arena_item_set_sensitive (NRArenaItem *item, unsigned int sensitive) +{ + nr_return_if_fail (item != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (item)); + + /* fixme: mess with pick/repick... */ + + item->sensitive = sensitive; +} + +void +nr_arena_item_set_visible (NRArenaItem *item, unsigned int visible) +{ + nr_return_if_fail (item != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (item)); + + item->visible = visible; + + nr_arena_item_request_render (item); +} + +void +nr_arena_item_set_clip (NRArenaItem *item, NRArenaItem *clip) +{ + nr_return_if_fail (item != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (item)); + nr_return_if_fail (!clip || NR_IS_ARENA_ITEM (clip)); + + if (clip != item->clip) { + nr_arena_item_request_render (item); + if (item->clip) item->clip = nr_arena_item_detach_unref (item, item->clip); + if (clip) item->clip = nr_arena_item_attach_ref (item, clip, NULL, NULL); + nr_arena_item_request_update (item, NR_ARENA_ITEM_STATE_ALL, TRUE); + } +} + +void +nr_arena_item_set_mask (NRArenaItem *item, NRArenaItem *mask) +{ + nr_return_if_fail (item != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (item)); + nr_return_if_fail (!mask || NR_IS_ARENA_ITEM (mask)); + + if (mask != item->mask) { + nr_arena_item_request_render (item); + if (item->mask) item->mask = nr_arena_item_detach_unref (item, item->mask); + if (mask) item->mask = nr_arena_item_attach_ref (item, mask, NULL, NULL); + nr_arena_item_request_update (item, NR_ARENA_ITEM_STATE_ALL, TRUE); + } +} + +void +nr_arena_item_set_order (NRArenaItem *item, int order) +{ + NRArenaItem *children, *child, *ref; + int pos; + + nr_return_if_fail (item != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (item)); + + if (!item->parent) return; + + children = nr_arena_item_children (item->parent); + + ref = NULL; + pos = 0; + for (child = children; child != NULL; child = child->next) { + if (pos >= order) break; + if (child != item) { + ref = child; + pos += 1; + } + } + + nr_arena_item_set_child_position (item->parent, item, ref); +} + +/* Helpers */ + +NRArenaItem * +nr_arena_item_attach_ref (NRArenaItem *parent, NRArenaItem *child, NRArenaItem *prev, NRArenaItem *next) +{ + nr_return_val_if_fail (parent != NULL, NULL); + nr_return_val_if_fail (NR_IS_ARENA_ITEM (parent), NULL); + nr_return_val_if_fail (child != NULL, NULL); + nr_return_val_if_fail (NR_IS_ARENA_ITEM (child), NULL); + nr_return_val_if_fail (child->parent == NULL, NULL); + nr_return_val_if_fail (child->prev == NULL, NULL); + nr_return_val_if_fail (child->next == NULL, NULL); + nr_return_val_if_fail (!prev || NR_IS_ARENA_ITEM (prev), NULL); + nr_return_val_if_fail (!prev || (prev->parent == parent), NULL); + nr_return_val_if_fail (!prev || (prev->next == next), NULL); + nr_return_val_if_fail (!next || NR_IS_ARENA_ITEM (next), NULL); + nr_return_val_if_fail (!next || (next->parent == parent), NULL); + nr_return_val_if_fail (!next || (next->prev == prev), NULL); + + child->parent = parent; + child->prev = prev; + child->next = next; + + if (prev) prev->next = child; + if (next) next->prev = child; + + return child; +} + +NRArenaItem * +nr_arena_item_detach_unref (NRArenaItem *parent, NRArenaItem *child) +{ + NRArenaItem *prev, *next; + + nr_return_val_if_fail (parent != NULL, NULL); + nr_return_val_if_fail (NR_IS_ARENA_ITEM (parent), NULL); + nr_return_val_if_fail (child != NULL, NULL); + nr_return_val_if_fail (NR_IS_ARENA_ITEM (child), NULL); + nr_return_val_if_fail (child->parent == parent, NULL); + + prev = child->prev; + next = child->next; + + child->parent = NULL; + child->prev = NULL; + child->next = NULL; + + if (prev) prev->next = next; + if (next) next->prev = prev; + + return next; +} + +/* + * + * caches + * + */ + +#ifdef arena_item_tile_cache +typedef struct cache_entry { + int key; + double score; + NRArenaItem* owner; + int th,tv; + int prev,next; + NRPixBlock ipb; + bool hasMask; + NRPixBlock mpb; +} cache_entry; + +int hash_max=2048,hash_fill=1024; + +int *keys=NULL; +int nbCch=0; + +int nbEnt=0,maxEnt=0; +cache_entry* entries=NULL; + +//#define tile_cache_stats +#ifdef tile_cache_stats +double hits=0,misses=0; +int hitMissCount=0; +#endif + +int hash_that(NRArenaItem* owner,int th,int tv) +{ + int res=GPOINTER_TO_INT(owner); + res*=17; + res+=th; + res*=59; + res+=tv; + res*=217; + if ( res < 0 ) res=-res; + res%=hash_max; + return res; +} + +bool test_cache(NRArenaItem* owner,int th,int tv,NRPixBlock &ipb,NRPixBlock &mpb,bool &hasMask) +{ + if ( keys == NULL ) { + hash_max = prefs_get_int_attribute ("options.arenatilescachesize", "value", 2048); + hash_fill=(hash_max*3)/4; + keys=(int*)malloc(hash_max*sizeof(int)); + for (int i=0;i= 0 && cur < nbEnt ) { + if ( entries[cur].owner == owner && entries[cur].th == th && entries[cur].tv == tv ) { + hasMask=entries[cur].hasMask; + ipb=entries[cur].ipb; + mpb=entries[cur].mpb; +#ifdef tile_cache_stats + hits+=1.0; +#endif + return true; + } + cur=entries[cur].next; + } +#ifdef tile_cache_stats + misses+=1.0; +#endif + return false; +} +void remove_one_cache(int no) +{ + if ( no < 0 || no >= nbEnt ) return; + + nr_pixblock_release(&entries[no].ipb); + if ( entries[no].hasMask ) nr_pixblock_release(&entries[no].mpb); + + if ( entries[no].prev >= 0 ) entries[entries[no].prev].next=entries[no].next; + if ( entries[no].next >= 0 ) entries[entries[no].next].prev=entries[no].prev; + if ( entries[no].prev < 0 ) keys[entries[no].key]=entries[no].next; + entries[no].prev=entries[no].next=entries[no].key=-1; + + if ( no == nbEnt-1 ) { + nbEnt--; + return; + } + entries[no]=entries[--nbEnt]; + if ( entries[no].prev >= 0 ) entries[entries[no].prev].next=no; + if ( entries[no].next >= 0 ) entries[entries[no].next].prev=no; + if ( entries[no].prev < 0 ) keys[entries[no].key]=no; +} +void remove_caches(NRArenaItem* owner) +{ + if ( keys == NULL ) { + hash_max = prefs_get_int_attribute ("options.arenatilescachesize", "value", 2048); + hash_fill=(hash_max*3)/4; + keys=(int*)malloc(hash_max*sizeof(int)); + for (int i=0;i=0;i--) { + if ( entries[i].owner == owner ) { + remove_one_cache(i); + } + } +} +void age_cache(void) +{ + for (int i=0;i 100 ) { + hitMissCount=0; + printf("hit/miss = %f used/total=%i/%i\n",(misses>0.001)?hits/misses:100000.0,nbEnt,hash_max); // localizing ok + } +#endif + int key=hash_that(owner,th,tv); + double nScore=/*activity**/duration; + + if ( keys[key] >= 0 ) { + int cur=keys[key]; + while ( cur >= 0 && cur < nbEnt ) { + if ( entries[cur].owner == owner && entries[cur].th == th && entries[cur].tv == tv ) { + remove_one_cache(cur); + break; + } + cur=entries[cur].next; + } + } + + bool doAdd=false; + if ( nbEnt < hash_fill ) { + doAdd=true; + } else { + double worstS=entries[0].score; + int worstE=0; + for (int i=1;i= maxEnt ) { + maxEnt=2*nbEnt+1; + entries=(cache_entry*)realloc(entries,maxEnt*sizeof(cache_entry)); + } + entries[nbEnt].key=key; + entries[nbEnt].score=nScore; + entries[nbEnt].owner=owner; + entries[nbEnt].th=th; + entries[nbEnt].tv=tv; + entries[nbEnt].prev=entries[nbEnt].next=-1; + entries[nbEnt].ipb=*ipb; + if ( mpb ) { + entries[nbEnt].hasMask=true; + entries[nbEnt].mpb=*mpb; + } else { + entries[nbEnt].hasMask=false; + } + entries[nbEnt].next=keys[key]; + if ( entries[nbEnt].next >= 0 ) entries[entries[nbEnt].next].prev=nbEnt; + keys[key]=nbEnt; + + nbEnt++; + return true; +} +#endif + + + + diff --git a/src/display/nr-arena-item.h b/src/display/nr-arena-item.h new file mode 100644 index 000000000..f43152ff9 --- /dev/null +++ b/src/display/nr-arena-item.h @@ -0,0 +1,188 @@ +#ifndef __NR_ARENA_ITEM_H__ +#define __NR_ARENA_ITEM_H__ + +/* + * RGBA display list system for inkscape + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define NR_TYPE_ARENA_ITEM (nr_arena_item_get_type ()) +#define NR_ARENA_ITEM(o) (NR_CHECK_INSTANCE_CAST ((o), NR_TYPE_ARENA_ITEM, NRArenaItem)) +#define NR_IS_ARENA_ITEM(o) (NR_CHECK_INSTANCE_TYPE ((o), NR_TYPE_ARENA_ITEM)) + +#define NR_ARENA_ITEM_VIRTUAL(i,m) (((NRArenaItemClass *) NR_OBJECT_GET_CLASS (i))->m) + +/* + * NRArenaItem state flags + */ + +/* + * NR_ARENA_ITEM_STATE_INVALID + * + * If set or retuned indicates, that given object is in error. + * Calling method has to return immediately, with appropriate + * error flag. + */ + +#define NR_ARENA_ITEM_STATE_INVALID (1 << 0) + + +#define NR_ARENA_ITEM_STATE_BBOX (1 << 1) +#define NR_ARENA_ITEM_STATE_COVERAGE (1 << 2) +#define NR_ARENA_ITEM_STATE_DRAFT (1 << 3) +#define NR_ARENA_ITEM_STATE_RENDER (1 << 4) +#define NR_ARENA_ITEM_STATE_CLIP (1 << 5) +#define NR_ARENA_ITEM_STATE_MASK (1 << 6) +#define NR_ARENA_ITEM_STATE_PICK (1 << 7) +#define NR_ARENA_ITEM_STATE_IMAGE (1 << 8) + +#define NR_ARENA_ITEM_STATE_NONE 0x0000 +#define NR_ARENA_ITEM_STATE_ALL 0x01fe + +#define NR_ARENA_ITEM_STATE(i,s) (NR_ARENA_ITEM (i)->state & (s)) +#define NR_ARENA_ITEM_SET_STATE(i,s) (NR_ARENA_ITEM (i)->state |= (s)) +#define NR_ARENA_ITEM_UNSET_STATE(i,s) (NR_ARENA_ITEM (i)->state &= ~(s)) + +#define NR_ARENA_ITEM_RENDER_NO_CACHE (1 << 0) + +#include +#include +#include +#include +#include "nr-arena-forward.h" + +// My testing shows that disabling cache reduces the amount +// of leaked memory when many documents are loaded one from the other, +// while there's no noticeable change in speed +//#define arena_item_tile_cache + +struct NRGC { + NRGC(NRGC const *p) : parent(p) {} + NRGC const *parent; + NRMatrix transform; +}; + +struct NRArenaItem : public NRObject { + NRArenaItem(); + + NRArena *arena; + NRArenaItem *parent; + NRArenaItem *next; + NRArenaItem *prev; + + /* Item state */ + unsigned int state : 16; + unsigned int propagate : 1; + unsigned int sensitive : 1; + unsigned int visible : 1; + /* Whether items renders opacity itself */ + unsigned int render_opacity : 1; + /* Opacity itself */ + unsigned int opacity : 8; + + /* Key for secondary rendering */ + unsigned int key; + +#ifdef arena_item_tile_cache + bool skipCaching; + double activity; +#endif + + /* BBox in grid coordinates */ + NRRectL bbox; + /* Our affine */ + NRMatrix *transform; + /* Clip item */ + NRArenaItem *clip; + /* Mask item */ + NRArenaItem *mask; + /* Rendered buffer */ + unsigned char *px; + + /* Single data member */ + void *data; + + void init(NRArena *arena) { + this->arena = arena; + } +}; + +struct NRArenaItemClass : public NRObjectClass { + NRArenaItem * (* children) (NRArenaItem *item); + NRArenaItem * (* last_child) (NRArenaItem *item); + void (* add_child) (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref); + void (* remove_child) (NRArenaItem *item, NRArenaItem *child); + void (* set_child_position) (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref); + + unsigned int (* update) (NRArenaItem *item, NRRectL *area, NRGC *gc, unsigned int state, unsigned int reset); + unsigned int (* render) (NRArenaItem *item, NRRectL *area, NRPixBlock *pb, unsigned int flags); + unsigned int (* clip) (NRArenaItem *item, NRRectL *area, NRPixBlock *pb); + NRArenaItem * (* pick) (NRArenaItem *item, NR::Point p, double delta, unsigned int sticky); +}; + +#define NR_ARENA_ITEM_ARENA(ai) (((NRArenaItem *) (ai))->arena) + +NRType nr_arena_item_get_type (void); + +NRArenaItem *nr_arena_item_ref (NRArenaItem *item); +NRArenaItem *nr_arena_item_unref (NRArenaItem *item); + +NRArenaItem *nr_arena_item_children (NRArenaItem *item); +NRArenaItem *nr_arena_item_last_child (NRArenaItem *item); +void nr_arena_item_add_child (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref); +void nr_arena_item_remove_child (NRArenaItem *item, NRArenaItem *child); +void nr_arena_item_set_child_position (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref); + +/* + * Invoke update to given state, if item is inside area + * + * area == NULL is infinite + * gc is PARENT gc for invoke, CHILD gc in corresponding virtual method + * state - requested to state (bitwise or of requested flags) + * reset - reset to state (bitwise or of flags to reset) + */ + +unsigned int nr_arena_item_invoke_update (NRArenaItem *item, NRRectL *area, NRGC *gc, unsigned int state, unsigned int reset); + +unsigned int nr_arena_item_invoke_render(NRArenaItem *item, NRRectL const *area, NRPixBlock *pb, unsigned int flags); + +unsigned int nr_arena_item_invoke_clip (NRArenaItem *item, NRRectL *area, NRPixBlock *pb); +NRArenaItem *nr_arena_item_invoke_pick (NRArenaItem *item, NR::Point p, double delta, unsigned int sticky); + +void nr_arena_item_request_update (NRArenaItem *item, unsigned int reset, unsigned int propagate); +void nr_arena_item_request_render (NRArenaItem *item); + +/* Public */ + +NRArenaItem *nr_arena_item_unparent (NRArenaItem *item); + +void nr_arena_item_append_child (NRArenaItem *parent, NRArenaItem *child); + +void nr_arena_item_set_transform(NRArenaItem *item, NR::Matrix const &transform); +void nr_arena_item_set_transform(NRArenaItem *item, NRMatrix const *transform); +void nr_arena_item_set_opacity (NRArenaItem *item, double opacity); +void nr_arena_item_set_sensitive (NRArenaItem *item, unsigned int sensitive); +void nr_arena_item_set_visible (NRArenaItem *item, unsigned int visible); +void nr_arena_item_set_clip (NRArenaItem *item, NRArenaItem *clip); +void nr_arena_item_set_mask (NRArenaItem *item, NRArenaItem *mask); +void nr_arena_item_set_order (NRArenaItem *item, int order); + +/* Helpers */ + +NRArenaItem *nr_arena_item_attach_ref (NRArenaItem *parent, NRArenaItem *child, NRArenaItem *prev, NRArenaItem *next); +NRArenaItem *nr_arena_item_detach_unref (NRArenaItem *parent, NRArenaItem *child); + +#define NR_ARENA_ITEM_SET_DATA(i,v) (((NRArenaItem *) (i))->data = (v)) +#define NR_ARENA_ITEM_GET_DATA(i) (((NRArenaItem *) (i))->data) + +#define NR_ARENA_ITEM_SET_KEY(i,k) (((NRArenaItem *) (i))->key = (k)) +#define NR_ARENA_ITEM_GET_KEY(i) (((NRArenaItem *) (i))->key) + +#endif diff --git a/src/display/nr-arena-shape.cpp b/src/display/nr-arena-shape.cpp new file mode 100644 index 000000000..4fd381ad4 --- /dev/null +++ b/src/display/nr-arena-shape.cpp @@ -0,0 +1,1298 @@ +#define __NR_ARENA_SHAPE_C__ + +/* + * RGBA display list system for inkscape + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//int showRuns=0; +void nr_pixblock_render_shape_mask_or (NRPixBlock &m,Shape* theS); + +static void nr_arena_shape_class_init (NRArenaShapeClass *klass); +static void nr_arena_shape_init (NRArenaShape *shape); +static void nr_arena_shape_finalize (NRObject *object); + +static NRArenaItem *nr_arena_shape_children (NRArenaItem *item); +static void nr_arena_shape_add_child (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref); +static void nr_arena_shape_remove_child (NRArenaItem *item, NRArenaItem *child); +static void nr_arena_shape_set_child_position (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref); + +static guint nr_arena_shape_update (NRArenaItem *item, NRRectL *area, NRGC *gc, guint state, guint reset); +static unsigned int nr_arena_shape_render (NRArenaItem *item, NRRectL *area, NRPixBlock *pb, unsigned int flags); +static guint nr_arena_shape_clip (NRArenaItem *item, NRRectL *area, NRPixBlock *pb); +static NRArenaItem *nr_arena_shape_pick (NRArenaItem *item, NR::Point p, double delta, unsigned int sticky); + +static NRArenaItemClass *shape_parent_class; + +NRType +nr_arena_shape_get_type (void) +{ + static NRType type = 0; + if (!type) { + type = nr_object_register_type (NR_TYPE_ARENA_ITEM, + "NRArenaShape", + sizeof (NRArenaShapeClass), + sizeof (NRArenaShape), + (void (*) (NRObjectClass *)) nr_arena_shape_class_init, + (void (*) (NRObject *)) nr_arena_shape_init); + } + return type; +} + +static void +nr_arena_shape_class_init (NRArenaShapeClass *klass) +{ + NRObjectClass *object_class; + NRArenaItemClass *item_class; + + object_class = (NRObjectClass *) klass; + item_class = (NRArenaItemClass *) klass; + + shape_parent_class = (NRArenaItemClass *) ((NRObjectClass *) klass)->parent; + + object_class->finalize = nr_arena_shape_finalize; + object_class->cpp_ctor = NRObject::invoke_ctor; + + item_class->children = nr_arena_shape_children; + item_class->add_child = nr_arena_shape_add_child; + item_class->set_child_position = nr_arena_shape_set_child_position; + item_class->remove_child = nr_arena_shape_remove_child; + item_class->update = nr_arena_shape_update; + item_class->render = nr_arena_shape_render; + item_class->clip = nr_arena_shape_clip; + item_class->pick = nr_arena_shape_pick; +} + +static void +nr_arena_shape_init (NRArenaShape *shape) +{ + shape->curve = NULL; + shape->style = NULL; + shape->paintbox.x0 = shape->paintbox.y0 = 0.0F; + shape->paintbox.x1 = shape->paintbox.y1 = 256.0F; + + nr_matrix_set_identity (&shape->ctm); + shape->fill_painter = NULL; + shape->stroke_painter = NULL; + shape->cached_fill = NULL; + shape->cached_stroke = NULL; + shape->fill_shp = NULL; + shape->stroke_shp = NULL; + + shape->delayed_shp = false; + + shape->approx_bbox.x0 = shape->approx_bbox.y0 = 0; + shape->approx_bbox.x1 = shape->approx_bbox.y1 = 0; + nr_matrix_set_identity(&shape->cached_fctm); + nr_matrix_set_identity(&shape->cached_sctm); + + shape->markers = NULL; +} + +static void +nr_arena_shape_finalize (NRObject *object) +{ + NRArenaShape *shape = (NRArenaShape *) (object); + + if (shape->fill_shp) delete shape->fill_shp; + if (shape->stroke_shp) delete shape->stroke_shp; + if (shape->cached_fill) delete shape->cached_fill; + if (shape->cached_stroke) delete shape->cached_stroke; + if (shape->fill_painter) sp_painter_free (shape->fill_painter); + if (shape->stroke_painter) sp_painter_free (shape->stroke_painter); + + if (shape->style) sp_style_unref (shape->style); + if (shape->curve) sp_curve_unref (shape->curve); + + ((NRObjectClass *) shape_parent_class)->finalize (object); +} + +static NRArenaItem * +nr_arena_shape_children (NRArenaItem *item) +{ + NRArenaShape *shape = (NRArenaShape *) item; + + return shape->markers; +} + +static void +nr_arena_shape_add_child (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref) +{ + NRArenaShape *shape = (NRArenaShape *) item; + + if (!ref) { + shape->markers = nr_arena_item_attach_ref (item, child, NULL, shape->markers); + } else { + ref->next = nr_arena_item_attach_ref (item, child, ref, ref->next); + } + + nr_arena_item_request_update (item, NR_ARENA_ITEM_STATE_ALL, FALSE); +} + +static void +nr_arena_shape_remove_child (NRArenaItem *item, NRArenaItem *child) +{ + NRArenaShape *shape = (NRArenaShape *) item; + + if (child->prev) { + nr_arena_item_detach_unref (item, child); + } else { + shape->markers = nr_arena_item_detach_unref (item, child); + } + + nr_arena_item_request_update (item, NR_ARENA_ITEM_STATE_ALL, FALSE); +} + +static void +nr_arena_shape_set_child_position (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref) +{ + NRArenaShape *shape = (NRArenaShape *) item; + + if (child->prev) { + nr_arena_item_detach_unref (item, child); + } else { + shape->markers = nr_arena_item_detach_unref (item, child); + } + + if (!ref) { + shape->markers = nr_arena_item_attach_ref (item, child, NULL, shape->markers); + } else { + ref->next = nr_arena_item_attach_ref (item, child, ref, ref->next); + } + + nr_arena_item_request_render (child); +} + +void nr_arena_shape_update_stroke(NRArenaShape *shape,NRGC* gc); +void nr_arena_shape_update_fill(NRArenaShape *shape,NRGC *gc); +void nr_arena_shape_add_bboxes(NRArenaShape* shape,NRRect &bbox); + +static guint +nr_arena_shape_update (NRArenaItem *item, NRRectL *area, NRGC *gc, guint state, guint reset) +{ + NRRect bbox; + + NRArenaShape *shape = NR_ARENA_SHAPE (item); + + unsigned int beststate = NR_ARENA_ITEM_STATE_ALL; + + unsigned int newstate; + for (NRArenaItem *child = shape->markers; child != NULL; child = child->next) { + newstate = nr_arena_item_invoke_update (child, area, gc, state, reset); + beststate = beststate & newstate; + } + + if (!(state & NR_ARENA_ITEM_STATE_RENDER)) { + /* We do not have to create rendering structures */ + shape->ctm = gc->transform; + if (state & NR_ARENA_ITEM_STATE_BBOX) { + if (shape->curve) { + NRBPath bp; + /* fixme: */ + bbox.x0 = bbox.y0 = NR_HUGE; + bbox.x1 = bbox.y1 = -NR_HUGE; + bp.path = shape->curve->bpath; + nr_path_matrix_bbox_union(&bp, gc->transform, &bbox); + item->bbox.x0 = (gint32)(bbox.x0 - 1.0F); + item->bbox.y0 = (gint32)(bbox.y0 - 1.0F); + item->bbox.x1 = (gint32)(bbox.x1 + 1.9999F); + item->bbox.y1 = (gint32)(bbox.y1 + 1.9999F); + } + if (beststate & NR_ARENA_ITEM_STATE_BBOX) { + for (NRArenaItem *child = shape->markers; child != NULL; child = child->next) { + nr_rect_l_union (&item->bbox, &item->bbox, &child->bbox); + } + } + } + return (state | item->state); + } + + shape->delayed_shp=true; + shape->ctm = gc->transform; + bbox.x0 = bbox.y0 = NR_HUGE; + bbox.x1 = bbox.y1 = -NR_HUGE; + + if (shape->curve) { + NRBPath bp; + /* fixme: */ + bbox.x0 = bbox.y0 = NR_HUGE; + bbox.x1 = bbox.y1 = -NR_HUGE; + bp.path = shape->curve->bpath; + nr_path_matrix_bbox_union(&bp, gc->transform, &bbox); + if (shape->_stroke.paint.type() != NRArenaShape::Paint::NONE) { + float width, scale; + scale = NR_MATRIX_DF_EXPANSION (&gc->transform); + width = MAX (0.125, shape->_stroke.width * scale); + if ( fabs(shape->_stroke.width * scale) > 0.01 ) { // sinon c'est 0=oon veut pas de bord + bbox.x0-=width; + bbox.x1+=width; + bbox.y0-=width; + bbox.y1+=width; + } + // those pesky miters, now + float miterMax=width*shape->_stroke.mitre_limit; + if ( miterMax > 0.01 ) { + // grunt mode. we should compute the various miters instead (one for each point on the curve) + bbox.x0-=miterMax; + bbox.x1+=miterMax; + bbox.y0-=miterMax; + bbox.y1+=miterMax; + } + } + } else { + } + shape->approx_bbox.x0 = (gint32)(bbox.x0 - 1.0F); + shape->approx_bbox.y0 = (gint32)(bbox.y0 - 1.0F); + shape->approx_bbox.x1 = (gint32)(bbox.x1 + 1.9999F); + shape->approx_bbox.y1 = (gint32)(bbox.y1 + 1.9999F); + if ( area && nr_rect_l_test_intersect (area, &shape->approx_bbox) ) shape->delayed_shp=false; + + /* Release state data */ + if (TRUE || !nr_matrix_test_transform_equal (&gc->transform, &shape->ctm, NR_EPSILON)) { + /* Concept test */ + if (shape->fill_shp) { + delete shape->fill_shp; + shape->fill_shp = NULL; + } + } + if (shape->stroke_shp) { + delete shape->stroke_shp; + shape->stroke_shp = NULL; + } + if (shape->fill_painter) { + sp_painter_free (shape->fill_painter); + shape->fill_painter = NULL; + } + if (shape->stroke_painter) { + sp_painter_free (shape->stroke_painter); + shape->stroke_painter = NULL; + } + + if (!shape->curve || !shape->style) return NR_ARENA_ITEM_STATE_ALL; + if (sp_curve_is_empty (shape->curve)) return NR_ARENA_ITEM_STATE_ALL; + if ( ( shape->_fill.paint.type() == NRArenaShape::Paint::NONE ) && + ( shape->_stroke.paint.type() == NRArenaShape::Paint::NONE ) ) + { + return NR_ARENA_ITEM_STATE_ALL; + } + + /* Build state data */ + if ( shape->delayed_shp ) { + item->bbox=shape->approx_bbox; + } else { + nr_arena_shape_update_stroke(shape,gc); + nr_arena_shape_update_fill(shape,gc); + + bbox.x0 = bbox.y0 = bbox.x1 = bbox.y1 = 0.0; + nr_arena_shape_add_bboxes(shape,bbox); + + shape->approx_bbox.x0 = (gint32)(bbox.x0 - 1.0F); + shape->approx_bbox.y0 = (gint32)(bbox.y0 - 1.0F); + shape->approx_bbox.x1 = (gint32)(bbox.x1 + 1.9999F); + shape->approx_bbox.y1 = (gint32)(bbox.y1 + 1.9999F); + } + + if (nr_rect_d_test_empty (&bbox)) return NR_ARENA_ITEM_STATE_ALL; + + item->bbox.x0 = (gint32)(bbox.x0 - 1.0F); + item->bbox.y0 = (gint32)(bbox.y0 - 1.0F); + item->bbox.x1 = (gint32)(bbox.x1 + 1.0F); + item->bbox.y1 = (gint32)(bbox.y1 + 1.0F); + nr_arena_request_render_rect (item->arena, &item->bbox); + + item->render_opacity = TRUE; + if ( shape->_fill.paint.type() == NRArenaShape::Paint::SERVER ) { + if (gc && gc->parent) { + shape->fill_painter = sp_paint_server_painter_new (shape->_fill.paint.server(), + NR::Matrix (&gc->transform), NR::Matrix (&gc->parent->transform), + &shape->paintbox); + } + item->render_opacity = FALSE; + } + if ( shape->_stroke.paint.type() == NRArenaShape::Paint::SERVER ) { + if (gc && gc->parent) { + shape->stroke_painter = sp_paint_server_painter_new (shape->_stroke.paint.server(), + NR::Matrix (&gc->transform), NR::Matrix (&gc->parent->transform), + &shape->paintbox); + } + item->render_opacity = FALSE; + } + if ( item->render_opacity == TRUE && + shape->_fill.paint.type() != NRArenaShape::Paint::NONE && + shape->_stroke.paint.type() != NRArenaShape::Paint::NONE ) + { + // don't merge item opacity with paint opacity if there is a stroke on the fill + item->render_opacity = FALSE; + } + + if (beststate & NR_ARENA_ITEM_STATE_BBOX) { + for (NRArenaItem *child = shape->markers; child != NULL; child = child->next) { + nr_rect_l_union (&item->bbox, &item->bbox, &child->bbox); + } + } + + return NR_ARENA_ITEM_STATE_ALL; +} + +int matrix_is_isometry(NR::Matrix p) { + NR::Matrix tp; + // transposition + tp[0]=p[0]; + tp[1]=p[2]; + tp[2]=p[1]; + tp[3]=p[3]; + for(int i = 4; i < 6; i++) // shut valgrind up :) + tp[i] = p[i] = 0; + NR::Matrix isom = tp*p; // A^T * A = adjunct? + // Is the adjunct nearly an identity function? + if (isom.is_translation(0.01)) { + // the transformation is an isometry -> no need to recompute + // the uncrossed polygon + if ( p.det() < 0 ) + return -1; + else + return 1; + } + return 0; +} + +void +nr_arena_shape_update_fill(NRArenaShape *shape,NRGC *gc) +{ + shape->delayed_shp = false; + if ((shape->_fill.paint.type() != NRArenaShape::Paint::NONE) && + ((shape->curve->end > 2) || (shape->curve->bpath[1].code == NR_CURVETO)) ) { + if (TRUE || !shape->fill_shp) { + NR::Matrix cached_to_new; + int isometry = 0; + if ( shape->cached_fill ) { + cached_to_new = shape->cached_fctm.inverse()*gc->transform; + isometry = matrix_is_isometry(cached_to_new); + } + if ( isometry == 0 ) { + if ( shape->cached_fill == NULL ) shape->cached_fill=new Shape; + shape->cached_fill->Reset(); + + Path* thePath=new Path; + Shape* theShape=new Shape; + { + NR::Matrix tempMat(gc->transform); + thePath->LoadArtBPath(shape->curve->bpath,tempMat,true); + } + + thePath->Convert(1.0); + + thePath->Fill(theShape, 0); + + if ( shape->_fill.rule == NRArenaShape::EVEN_ODD ) { + shape->cached_fill->ConvertToShape(theShape, fill_oddEven); + // alternatively, this speeds up rendering of oddeven shapes but disables AA :( + //shape->cached_fill->Copy(theShape); + } else { + shape->cached_fill->ConvertToShape(theShape, fill_nonZero); + } + shape->cached_fctm=gc->transform; + delete theShape; + delete thePath; + if ( shape->fill_shp == NULL ) + shape->fill_shp = new Shape; + + shape->fill_shp->Copy(shape->cached_fill); + + } else { + + if ( shape->fill_shp == NULL ) + shape->fill_shp=new Shape; + shape->fill_shp->Reset(shape->cached_fill->numberOfPoints(), + shape->cached_fill->numberOfEdges()); + for (int i = 0; i < shape->cached_fill->numberOfPoints(); i++) + shape->fill_shp->AddPoint(shape->cached_fill->getPoint(i).x * cached_to_new); + if ( isometry == 1 ) { + for (int i = 0; i < shape->cached_fill->numberOfEdges(); i++) + shape->fill_shp->AddEdge(shape->cached_fill->getEdge(i).st, + shape->cached_fill->getEdge(i).en); + } else if ( isometry == -1 ) { // need to flip poly. + for (int i = 0; i < shape->cached_fill->numberOfEdges(); i++) + shape->fill_shp->AddEdge(shape->cached_fill->getEdge(i).en, + shape->cached_fill->getEdge(i).st); + } + shape->fill_shp->ForceToPolygon(); + shape->fill_shp->needPointsSorting(); + shape->fill_shp->needEdgesSorting(); + } + } + } +} + +void +nr_arena_shape_update_stroke(NRArenaShape *shape,NRGC* gc) +{ + SPStyle* style = shape->style; + + shape->delayed_shp = false; + + const float scale = NR_MATRIX_DF_EXPANSION (&gc->transform); + + if (NR_ARENA_ITEM(shape)->arena->rendermode == RENDERMODE_OUTLINE || + ((shape->_stroke.paint.type() != NRArenaShape::Paint::NONE) && + ( fabs(shape->_stroke.width * scale) > 0.01 ))) { // sinon c'est 0=oon veut pas de bord + + float width = MAX (0.125, shape->_stroke.width * scale); + if (NR_ARENA_ITEM(shape)->arena->rendermode == RENDERMODE_OUTLINE) + width = 0.5; // 1 pixel wide, independent of zoom + + NR::Matrix cached_to_new; + + int isometry = 0; + if ( shape->cached_stroke ) { + cached_to_new = shape->cached_sctm.inverse() * gc->transform; + isometry = matrix_is_isometry(cached_to_new); + } + + if ( isometry == 0 ) { + if ( shape->cached_stroke == NULL ) shape->cached_stroke=new Shape; + shape->cached_stroke->Reset(); + Path* thePath = new Path; + Shape* theShape = new Shape; + { + NR::Matrix tempMat(gc->transform); + thePath->LoadArtBPath(shape->curve->bpath, tempMat, true); + } + + if (NR_ARENA_ITEM(shape)->arena->rendermode != RENDERMODE_OUTLINE) + thePath->Convert(1.0); + else + thePath->Convert(4.0); // slightly rougher & faster + + if (style->stroke_dash.n_dash && NR_ARENA_ITEM(shape)->arena->rendermode != RENDERMODE_OUTLINE) { + double dlen = 0.0; + for (int i = 0; i < style->stroke_dash.n_dash; i++) { + dlen += style->stroke_dash.dash[i] * scale; + } + if (dlen >= 1.0) { + NRVpathDash dash; + dash.offset = style->stroke_dash.offset * scale; + dash.n_dash = style->stroke_dash.n_dash; + dash.dash = g_new (double, dash.n_dash); + for (int i = 0; i < dash.n_dash; i++) { + dash.dash[i] = style->stroke_dash.dash[i] * scale; + } + int nbD=dash.n_dash; + float *dashs=(float*)malloc((nbD+1)*sizeof(float)); + while ( dash.offset >= dlen ) dash.offset-=dlen; + dashs[0]=dash.dash[0]; + for (int i=1; iDashPolyline(0.0,0.0,dlen,nbD,dashs,true,dash.offset); + free(dashs); + g_free (dash.dash); + } + } + ButtType butt=butt_straight; + switch(shape->_stroke.cap) { + case NRArenaShape::BUTT_CAP: + butt = butt_straight; + break; + case NRArenaShape::ROUND_CAP: + butt = butt_round; + break; + case NRArenaShape::SQUARE_CAP: + butt = butt_square; + break; + } + JoinType join=join_straight; + switch(shape->_stroke.join) { + case NRArenaShape::MITRE_JOIN: + join = join_pointy; + break; + case NRArenaShape::ROUND_JOIN: + join = join_round; + break; + case NRArenaShape::BEVEL_JOIN: + join = join_straight; + break; + } + + if (NR_ARENA_ITEM(shape)->arena->rendermode == RENDERMODE_OUTLINE) { + butt = butt_straight; + join = join_straight; + } + + thePath->Stroke(theShape, false, 0.5*width, join, butt, + 0.5*width*shape->_stroke.mitre_limit); + + + if (NR_ARENA_ITEM(shape)->arena->rendermode == RENDERMODE_OUTLINE) { + // speeds it up, but uses evenodd for the stroke shape (which does not matter for 1-pixel wide outline) + shape->cached_stroke->Copy(theShape); + } else { + shape->cached_stroke->ConvertToShape(theShape, fill_nonZero); + } + + shape->cached_sctm=gc->transform; + delete thePath; + delete theShape; + if ( shape->stroke_shp == NULL ) shape->stroke_shp=new Shape; + + shape->stroke_shp->Copy(shape->cached_stroke); + + } else { + + if ( shape->stroke_shp == NULL ) + shape->stroke_shp=new Shape; + shape->stroke_shp->Reset(shape->cached_stroke->numberOfPoints(), shape->cached_stroke->numberOfEdges()); + for (int i = 0; i < shape->cached_stroke->numberOfPoints(); i++) + shape->stroke_shp->AddPoint(shape->cached_stroke->getPoint(i).x * cached_to_new); + if ( isometry == 1 ) { + for (int i = 0; i < shape->cached_stroke->numberOfEdges(); i++) + shape->stroke_shp->AddEdge(shape->cached_stroke->getEdge(i).st, + shape->cached_stroke->getEdge(i).en); + } else if ( isometry == -1 ) { + for (int i = 0; i < shape->cached_stroke->numberOfEdges(); i++) + shape->stroke_shp->AddEdge(shape->cached_stroke->getEdge(i).en, + shape->cached_stroke->getEdge(i).st); + } + shape->stroke_shp->ForceToPolygon(); + shape->stroke_shp->needPointsSorting(); + shape->stroke_shp->needEdgesSorting(); + } + } +} + + +void +nr_arena_shape_add_bboxes(NRArenaShape* shape, NRRect &bbox) +{ + if ( shape->stroke_shp ) { + shape->stroke_shp->CalcBBox(); + shape->stroke_shp->leftX=floor(shape->stroke_shp->leftX); + shape->stroke_shp->rightX=ceil(shape->stroke_shp->rightX); + shape->stroke_shp->topY=floor(shape->stroke_shp->topY); + shape->stroke_shp->bottomY=ceil(shape->stroke_shp->bottomY); + if ( bbox.x0 >= bbox.x1 ) { + if ( shape->stroke_shp->leftX < shape->stroke_shp->rightX ) { + bbox.x0=shape->stroke_shp->leftX; + bbox.x1=shape->stroke_shp->rightX; + } + } else { + if ( shape->stroke_shp->leftX < bbox.x0 ) + bbox.x0=shape->stroke_shp->leftX; + if ( shape->stroke_shp->rightX > bbox.x1 ) + bbox.x1=shape->stroke_shp->rightX; + } + if ( bbox.y0 >= bbox.y1 ) { + if ( shape->stroke_shp->topY < shape->stroke_shp->bottomY ) { + bbox.y0=shape->stroke_shp->topY; + bbox.y1=shape->stroke_shp->bottomY; + } + } else { + if ( shape->stroke_shp->topY < bbox.y0 ) + bbox.y0=shape->stroke_shp->topY; + if ( shape->stroke_shp->bottomY > bbox.y1 ) + bbox.y1=shape->stroke_shp->bottomY; + } + } + if ( shape->fill_shp ) { + shape->fill_shp->CalcBBox(); + shape->fill_shp->leftX=floor(shape->fill_shp->leftX); + shape->fill_shp->rightX=ceil(shape->fill_shp->rightX); + shape->fill_shp->topY=floor(shape->fill_shp->topY); + shape->fill_shp->bottomY=ceil(shape->fill_shp->bottomY); + if ( bbox.x0 >= bbox.x1 ) { + if ( shape->fill_shp->leftX < shape->fill_shp->rightX ) { + bbox.x0=shape->fill_shp->leftX; + bbox.x1=shape->fill_shp->rightX; + } + } else { + if ( shape->fill_shp->leftX < bbox.x0 ) bbox.x0=shape->fill_shp->leftX; + if ( shape->fill_shp->rightX > bbox.x1 ) bbox.x1=shape->fill_shp->rightX; + } + if ( bbox.y0 >= bbox.y1 ) { + if ( shape->fill_shp->topY < shape->fill_shp->bottomY ) { + bbox.y0=shape->fill_shp->topY; + bbox.y1=shape->fill_shp->bottomY; + } + } else { + if ( shape->fill_shp->topY < bbox.y0 ) bbox.y0=shape->fill_shp->topY; + if ( shape->fill_shp->bottomY > bbox.y1 ) bbox.y1=shape->fill_shp->bottomY; + } + } +} +static unsigned int +nr_arena_shape_render (NRArenaItem *item, NRRectL *area, NRPixBlock *pb, unsigned int flags) +{ + NRArenaShape *shape = NR_ARENA_SHAPE (item); + + if (!shape->curve) return item->state; + if (!shape->style) return item->state; + + if ( shape->delayed_shp ) { + if ( nr_rect_l_test_intersect (area, &item->bbox) ) { + NRGC tempGC(NULL); + tempGC.transform=shape->ctm; + nr_arena_shape_update_stroke(shape,&tempGC); + nr_arena_shape_update_fill(shape,&tempGC); +/* NRRect bbox; + bbox.x0 = bbox.y0 = bbox.x1 = bbox.y1 = 0.0; + nr_arena_shape_add_bboxes(shape,bbox); + item->bbox.x0 = (gint32)(bbox.x0 - 1.0F); + item->bbox.y0 = (gint32)(bbox.y0 - 1.0F); + item->bbox.x1 = (gint32)(bbox.x1 + 1.0F); + item->bbox.y1 = (gint32)(bbox.y1 + 1.0F); + shape->approx_bbox=item->bbox;*/ + } + } + + SPStyle const *style = shape->style; + if ( shape->fill_shp && NR_ARENA_ITEM(shape)->arena->rendermode != RENDERMODE_OUTLINE) { + NRPixBlock m; + guint32 rgba; + + nr_pixblock_setup_fast (&m, NR_PIXBLOCK_MODE_A8, area->x0, area->y0, area->x1, area->y1, TRUE); + nr_pixblock_render_shape_mask_or (m,shape->fill_shp); + m.empty = FALSE; + + if (shape->_fill.paint.type() == NRArenaShape::Paint::NONE) { + // do not render fill in any way + } else if (shape->_fill.paint.type() == NRArenaShape::Paint::COLOR) { + if ( item->render_opacity ) { + rgba = sp_color_get_rgba32_falpha (&shape->_fill.paint.color(), + shape->_fill.opacity * + SP_SCALE24_TO_FLOAT (style->opacity.value)); + } else { + rgba = sp_color_get_rgba32_falpha (&shape->_fill.paint.color(), + shape->_fill.opacity); + } + nr_blit_pixblock_mask_rgba32 (pb, &m, rgba); + pb->empty = FALSE; + } else if (shape->_fill.paint.type() == NRArenaShape::Paint::SERVER) { + if (shape->fill_painter) { + nr_arena_render_paintserver_fill (pb, area, shape->fill_painter, shape->_fill.opacity, &m); + } + } + + nr_pixblock_release (&m); + } + + if ( shape->stroke_shp ) { + NRPixBlock m; + guint32 rgba; + + nr_pixblock_setup_fast (&m, NR_PIXBLOCK_MODE_A8, area->x0, area->y0, area->x1, area->y1, TRUE); + nr_pixblock_render_shape_mask_or (m, shape->stroke_shp); + m.empty = FALSE; + + if (shape->_stroke.paint.type() == NRArenaShape::Paint::COLOR || + NR_ARENA_ITEM(shape)->arena->rendermode == RENDERMODE_OUTLINE) { + if ( NR_ARENA_ITEM(shape)->arena->rendermode == RENDERMODE_OUTLINE) { + rgba = NR_ARENA_ITEM(shape)->arena->outlinecolor; + } else if ( item->render_opacity ) { + rgba = sp_color_get_rgba32_falpha (&shape->_stroke.paint.color(), + shape->_stroke.opacity * + SP_SCALE24_TO_FLOAT (style->opacity.value)); + } else { + rgba = sp_color_get_rgba32_falpha (&shape->_stroke.paint.color(), + shape->_stroke.opacity); + } + nr_blit_pixblock_mask_rgba32 (pb, &m, rgba); + pb->empty = FALSE; + } else if (shape->_stroke.paint.type() == NRArenaShape::Paint::SERVER) { + if (shape->stroke_painter) { + nr_arena_render_paintserver_fill (pb, area, shape->stroke_painter, shape->_stroke.opacity, &m); + } + } + + nr_pixblock_release (&m); + } + + /* Just compose children into parent buffer */ + for (NRArenaItem *child = shape->markers; child != NULL; child = child->next) { + unsigned int ret; + ret = nr_arena_item_invoke_render (child, area, pb, flags); + if (ret & NR_ARENA_ITEM_STATE_INVALID) return ret; + } + + return item->state; +} + +static guint +nr_arena_shape_clip (NRArenaItem *item, NRRectL *area, NRPixBlock *pb) +{ + NRArenaShape *shape = NR_ARENA_SHAPE (item); + + if (!shape->curve) return item->state; + + if ( shape->delayed_shp ) { + if ( nr_rect_l_test_intersect (area, &item->bbox) ) { + NRGC tempGC(NULL); + tempGC.transform=shape->ctm; + nr_arena_shape_update_stroke(shape,&tempGC); + nr_arena_shape_update_fill(shape,&tempGC); + /* NRRect bbox; + bbox.x0 = bbox.y0 = bbox.x1 = bbox.y1 = 0.0; + nr_arena_shape_add_bboxes(shape,bbox); + item->bbox.x0 = (gint32)(bbox.x0 - 1.0F); + item->bbox.y0 = (gint32)(bbox.y0 - 1.0F); + item->bbox.x1 = (gint32)(bbox.x1 + 1.0F); + item->bbox.y1 = (gint32)(bbox.y1 + 1.0F); + shape->approx_bbox=item->bbox;*/ + } + } + + if ( shape->fill_shp ) { + NRPixBlock m; + + /* fixme: We can OR in one step (Lauris) */ + nr_pixblock_setup_fast (&m, NR_PIXBLOCK_MODE_A8, area->x0, area->y0, area->x1, area->y1, TRUE); + nr_pixblock_render_shape_mask_or (m,shape->fill_shp); + + for (int y = area->y0; y < area->y1; y++) { + unsigned char *s, *d; + s = NR_PIXBLOCK_PX (&m) + (y - area->y0) * m.rs; + d = NR_PIXBLOCK_PX (pb) + (y - area->y0) * pb->rs; + for (int x = area->x0; x < area->x1; x++) { + *d = NR_A7_NORMALIZED(*s,*d); + d ++; + s ++; + } + } + nr_pixblock_release (&m); + pb->empty = FALSE; + } + + return item->state; +} + +static NRArenaItem * +nr_arena_shape_pick (NRArenaItem *item, NR::Point p, double delta, unsigned int /*sticky*/) +{ + NRArenaShape *shape = NR_ARENA_SHAPE (item); + + if (!shape->curve) return NULL; + if (!shape->style) return NULL; + if ( shape->delayed_shp ) { + NRRectL area; + area.x0=(int)floor(p[NR::X]); + area.x1=(int)ceil(p[NR::X]); + area.y0=(int)floor(p[NR::Y]); + area.y1=(int)ceil(p[NR::Y]); + int idelta = (int)ceil(delta) + 1; + // njh: inset rect + area.x0-=idelta; + area.x1+=idelta; + area.y0-=idelta; + area.y1+=idelta; + if ( nr_rect_l_test_intersect (&area, &item->bbox) ) { + NRGC tempGC(NULL); + tempGC.transform=shape->ctm; + nr_arena_shape_update_stroke(shape,&tempGC); + nr_arena_shape_update_fill(shape,&tempGC); + /* NRRect bbox; + bbox.x0 = bbox.y0 = bbox.x1 = bbox.y1 = 0.0; + nr_arena_shape_add_bboxes(shape,bbox); + item->bbox.x0 = (gint32)(bbox.x0 - 1.0F); + item->bbox.y0 = (gint32)(bbox.y0 - 1.0F); + item->bbox.x1 = (gint32)(bbox.x1 + 1.0F); + item->bbox.y1 = (gint32)(bbox.y1 + 1.0F); + shape->approx_bbox=item->bbox;*/ + } + } + + if (item->state & NR_ARENA_ITEM_STATE_RENDER) { + if (shape->fill_shp && (shape->_fill.paint.type() != NRArenaShape::Paint::NONE)) { + if (shape->fill_shp->PtWinding(p) > 0 ) return item; + } + if (shape->stroke_shp && (shape->_stroke.paint.type() != NRArenaShape::Paint::NONE)) { + if (shape->stroke_shp->PtWinding(p) > 0 ) return item; + } + if (delta > 1e-3) { + if (shape->fill_shp && (shape->_fill.paint.type() != NRArenaShape::Paint::NONE)) { + if (distanceLessThanOrEqual(shape->fill_shp, p, delta)) return item; + } + if (shape->stroke_shp && (shape->_stroke.paint.type() != NRArenaShape::Paint::NONE)) { + if (distanceLessThanOrEqual(shape->stroke_shp, p, delta)) return item; + } + } + } else { + NRBPath bp; + bp.path = shape->curve->bpath; + double dist = NR_HUGE; + int wind = 0; + nr_path_matrix_point_bbox_wind_distance(&bp, shape->ctm, p, NULL, &wind, &dist, NR_EPSILON); + if (shape->_fill.paint.type() != NRArenaShape::Paint::NONE) { + if (!shape->style->fill_rule.computed) { + if (wind != 0) return item; + } else { + if (wind & 0x1) return item; + } + } + if (shape->_stroke.paint.type() != NRArenaShape::Paint::NONE) { + /* fixme: We do not take stroke width into account here (Lauris) */ + if (dist < delta) return item; + } + } + + return NULL; +} + +/** + * + * Requests a render of the shape, then if the shape is already a curve it + * unrefs the old curve; if the new curve is valid it creates a copy of the + * curve and adds it to the shape. Finally, it requests an update of the + * arena for the shape. + */ +void nr_arena_shape_set_path(NRArenaShape *shape, SPCurve *curve,bool justTrans) +{ + g_return_if_fail (shape != NULL); + g_return_if_fail (NR_IS_ARENA_SHAPE (shape)); + + if ( justTrans == false ) { + // dirty cached versions + if ( shape->cached_fill ) { + delete shape->cached_fill; + shape->cached_fill=NULL; + } + if ( shape->cached_stroke ) { + delete shape->cached_stroke; + shape->cached_stroke=NULL; + } + } + + nr_arena_item_request_render (NR_ARENA_ITEM (shape)); + + if (shape->curve) { + sp_curve_unref (shape->curve); + shape->curve = NULL; + } + + if (curve) { + shape->curve = curve; + sp_curve_ref (curve); + } + + nr_arena_item_request_update (NR_ARENA_ITEM (shape), NR_ARENA_ITEM_STATE_ALL, FALSE); +} + +void NRArenaShape::setFill(SPPaintServer *server) { + _fill.paint.set(server); + _invalidateCachedFill(); +} + +void NRArenaShape::setFill(SPColor const &color) { + _fill.paint.set(color); + _invalidateCachedFill(); +} + +void NRArenaShape::setFillOpacity(double opacity) { + _fill.opacity = opacity; + _invalidateCachedFill(); +} + +void NRArenaShape::setFillRule(NRArenaShape::FillRule rule) { + _fill.rule = rule; + _invalidateCachedFill(); +} + +void NRArenaShape::setStroke(SPPaintServer *server) { + _stroke.paint.set(server); + _invalidateCachedStroke(); +} + +void NRArenaShape::setStroke(SPColor const &color) { + _stroke.paint.set(color); + _invalidateCachedStroke(); +} + +void NRArenaShape::setStrokeOpacity(double opacity) { + _stroke.opacity = opacity; + _invalidateCachedStroke(); +} + +void NRArenaShape::setStrokeWidth(double width) { + _stroke.width = width; + _invalidateCachedStroke(); +} + +void NRArenaShape::setMitreLimit(double limit) { + _stroke.mitre_limit = limit; + _invalidateCachedStroke(); +} + +void NRArenaShape::setLineCap(NRArenaShape::CapType cap) { + _stroke.cap = cap; + _invalidateCachedStroke(); +} + +void NRArenaShape::setLineJoin(NRArenaShape::JoinType join) { + _stroke.join = join; + _invalidateCachedStroke(); +} + +/** nr_arena_shape_set_style + * + * Unrefs any existing style and ref's to the given one, then requests an update of the arena + */ +void +nr_arena_shape_set_style (NRArenaShape *shape, SPStyle *style) +{ + g_return_if_fail (shape != NULL); + g_return_if_fail (NR_IS_ARENA_SHAPE (shape)); + + if (style) sp_style_ref (style); + if (shape->style) sp_style_unref (shape->style); + shape->style = style; + + switch (style->fill.type) { + case SP_PAINT_TYPE_NONE: { + shape->setFill(NULL); + break; + } + case SP_PAINT_TYPE_COLOR: { + shape->setFill(style->fill.value.color); + break; + } + case SP_PAINT_TYPE_PAINTSERVER: { + shape->setFill(style->fill.value.paint.server); + break; + } + default: { + g_assert_not_reached(); + } + } + shape->setFillOpacity(SP_SCALE24_TO_FLOAT(style->fill_opacity.value)); + switch (style->fill_rule.computed) { + case SP_WIND_RULE_EVENODD: { + shape->setFillRule(NRArenaShape::EVEN_ODD); + break; + } + case SP_WIND_RULE_NONZERO: { + shape->setFillRule(NRArenaShape::NONZERO); + break; + } + default: { + g_assert_not_reached(); + } + } + + switch (style->stroke.type) { + case SP_PAINT_TYPE_NONE: { + shape->setStroke(NULL); + break; + } + case SP_PAINT_TYPE_COLOR: { + shape->setStroke(style->stroke.value.color); + break; + } + case SP_PAINT_TYPE_PAINTSERVER: { + shape->setStroke(style->stroke.value.paint.server); + break; + } + default: { + g_assert_not_reached(); + } + } + shape->setStrokeWidth(style->stroke_width.computed); + shape->setStrokeOpacity(SP_SCALE24_TO_FLOAT(style->stroke_opacity.value)); + switch (style->stroke_linecap.computed) { + case SP_STROKE_LINECAP_ROUND: { + shape->setLineCap(NRArenaShape::ROUND_CAP); + break; + } + case SP_STROKE_LINECAP_SQUARE: { + shape->setLineCap(NRArenaShape::SQUARE_CAP); + break; + } + case SP_STROKE_LINECAP_BUTT: { + shape->setLineCap(NRArenaShape::BUTT_CAP); + break; + } + default: { + g_assert_not_reached(); + } + } + switch (style->stroke_linejoin.computed) { + case SP_STROKE_LINEJOIN_ROUND: { + shape->setLineJoin(NRArenaShape::ROUND_JOIN); + break; + } + case SP_STROKE_LINEJOIN_BEVEL: { + shape->setLineJoin(NRArenaShape::BEVEL_JOIN); + break; + } + case SP_STROKE_LINEJOIN_MITER: { + shape->setLineJoin(NRArenaShape::MITRE_JOIN); + break; + } + default: { + g_assert_not_reached(); + } + } + shape->setMitreLimit(style->stroke_miterlimit.value); + + nr_arena_item_request_update(shape, NR_ARENA_ITEM_STATE_ALL, FALSE); +} + +void +nr_arena_shape_set_paintbox (NRArenaShape *shape, const NRRect *pbox) +{ + g_return_if_fail (shape != NULL); + g_return_if_fail (NR_IS_ARENA_SHAPE (shape)); + g_return_if_fail (pbox != NULL); + + if ((pbox->x0 < pbox->x1) && (pbox->y0 < pbox->y1)) { + shape->paintbox = *pbox; + } else { + /* fixme: We kill warning, although not sure what to do here (Lauris) */ + shape->paintbox.x0 = shape->paintbox.y0 = 0.0F; + shape->paintbox.x1 = shape->paintbox.y1 = 256.0F; + } + + nr_arena_item_request_update(shape, NR_ARENA_ITEM_STATE_ALL, FALSE); +} + +void NRArenaShape::setPaintBox(NR::Rect const &pbox) +{ + paintbox.x0 = pbox.min()[NR::X]; + paintbox.y0 = pbox.min()[NR::Y]; + paintbox.x1 = pbox.max()[NR::X]; + paintbox.y1 = pbox.max()[NR::Y]; + + nr_arena_item_request_update(this, NR_ARENA_ITEM_STATE_ALL, FALSE); +} + +static void +shape_run_A8_OR (raster_info &dest,void */*data*/,int st,float vst,int en,float ven) +{ + if ( st >= en ) return; + if ( vst < 0 ) vst=0; + if ( vst > 1 ) vst=1; + if ( ven < 0 ) ven=0; + if ( ven > 1 ) ven=1; + float sv=vst; + float dv=ven-vst; + int len=en-st; + unsigned char* d=(unsigned char*)dest.buffer; + d+=(st-dest.startPix); + if ( fabs(dv) < 0.001 ) { + if ( vst > 0.999 ) { + /* Simple copy */ + while (len > 0) { + d[0] = 255; + d += 1; + len -= 1; + } + } else { + sv*=256; + unsigned int c0_24=(int)sv; + c0_24&=0xFF; + while (len > 0) { + unsigned int da; + /* Draw */ + da = NR_A7(c0_24,d[0]); + d[0] = NR_PREMUL_SINGLE(da); + d += 1; + len -= 1; + } + } + } else { + if ( en <= st+1 ) { + sv=0.5*(vst+ven); + sv*=256; + unsigned int c0_24=(int)sv; + c0_24&=0xFF; + unsigned int da; + /* Draw */ + da = NR_A7(c0_24,d[0]); + d[0] = NR_PREMUL_SINGLE(da); + } else { + dv/=len; + sv+=0.5*dv; // correction trapezoidale + sv*=16777216; + dv*=16777216; + int c0_24 = static_cast(CLAMP(sv, 0, 16777216)); + int s0_24 = static_cast(dv); + while (len > 0) { + unsigned int ca, da; + /* Draw */ + ca = c0_24 >> 16; + if ( ca > 255 ) ca=255; + da = NR_A7(ca,d[0]); + d[0] = NR_PREMUL_SINGLE(da); + d += 1; + c0_24 += s0_24; + c0_24 = CLAMP (c0_24, 0, 16777216); + len -= 1; + } + } + } +} + +void nr_pixblock_render_shape_mask_or (NRPixBlock &m,Shape* theS) +{ + + theS->CalcBBox(); + float l=theS->leftX,r=theS->rightX,t=theS->topY,b=theS->bottomY; + int il,ir,it,ib; + il=(int)floor(l); + ir=(int)ceil(r); + it=(int)floor(t); + ib=(int)ceil(b); + + if ( il >= m.area.x1 || ir <= m.area.x0 || it >= m.area.y1 || ib <= m.area.y0 ) return; + if ( il < m.area.x0 ) il=m.area.x0; + if ( it < m.area.y0 ) it=m.area.y0; + if ( ir > m.area.x1 ) ir=m.area.x1; + if ( ib > m.area.y1 ) ib=m.area.y1; + + // version par FloatLigne + int curPt; + float curY; + theS->BeginQuickRaster(curY, curPt); + + FloatLigne* theI=new FloatLigne(); + IntLigne* theIL=new IntLigne(); + + theS->DirectQuickScan(curY,curPt,(float)(it),true,1.0); + + char* mdata=(char*)m.data.px; + if ( m.size == NR_PIXBLOCK_SIZE_TINY ) mdata=(char*)m.data.p; + uint32_t* ligStart=((uint32_t*)(mdata+((il-m.area.x0)+m.rs*(it-m.area.y0)))); + for (int y=it;yReset(); + theS->QuickScan(curY,curPt,((float)(y+1)),theI,1.0); + theI->Flatten(); + theIL->Copy(theI); + + raster_info dest; + dest.startPix=il; + dest.endPix=ir; + dest.sth=il; + dest.stv=y; + dest.buffer=ligStart; + theIL->Raster(dest,NULL,shape_run_A8_OR); + ligStart=((uint32_t*)(((char*)ligStart)+m.rs)); + } + theS->EndQuickRaster(); + delete theI; + delete theIL; + + /* // version par BitLigne + int curPt; + float curY; + theS->BeginRaster(curY,curPt,1.0); + + BitLigne* theI[4]; + for (int i=0;i<4;i++) theI[i]=new BitLigne(il,ir); + IntLigne* theIL=new IntLigne(); + + theS->Scan(curY,curPt,(float)(it),0.25); + + char* mdata=(char*)m.data.px; + if ( m.size == NR_PIXBLOCK_SIZE_TINY ) mdata=(char*)m.data.p; + uint32_t* ligStart=((uint32_t*)(mdata+((il-m.area.x0)+m.rs*(it-m.area.y0)))); + for (int y=it;yReset(); + if ( y&0x00000003 ) { + theS->Scan(curY,curPt,((float)(y+0.25)),fill_oddEven,theI[0],false,0.25); + theS->Scan(curY,curPt,((float)(y+0.5)),fill_oddEven,theI[1],false,0.25); + theS->Scan(curY,curPt,((float)(y+0.75)),fill_oddEven,theI[2],false,0.25); + theS->Scan(curY,curPt,((float)(y+1.0)),fill_oddEven,theI[3],false,0.25); + } else { + theS->Scan(curY,curPt,((float)(y+0.25)),fill_oddEven,theI[0],true,0.25); + theS->Scan(curY,curPt,((float)(y+0.5)),fill_oddEven,theI[1],true,0.25); + theS->Scan(curY,curPt,((float)(y+0.75)),fill_oddEven,theI[2],true,0.25); + theS->Scan(curY,curPt,((float)(y+1.0)),fill_oddEven,theI[3],true,0.25); + } + theIL->Copy(4,theI); + + raster_info dest; + dest.startPix=il; + dest.endPix=ir; + dest.sth=il; + dest.stv=y; + dest.buffer=ligStart; + theIL->Raster(dest,NULL,shape_run_A8_OR); + ligStart=((uint32_t*)(((char*)ligStart)+m.rs)); + } + theS->EndRaster(); + for (int i=0;i<4;i++) delete theI[i]; + delete theIL;*/ + +/* // version par BitLigne directe + int curPt; + float curY; + theS->BeginQuickRaster(curY,curPt,1.0); + + BitLigne* theI[4]; + for (int i=0;i<4;i++) theI[i]=new BitLigne(il,ir); + IntLigne* theIL=new IntLigne(); + + theS->DirectQuickScan(curY,curPt,(float)(it),true,0.25); + + char* mdata=(char*)m.data.px; + if ( m.size == NR_PIXBLOCK_SIZE_TINY ) mdata=(char*)m.data.p; + uint32_t* ligStart=((uint32_t*)(mdata+((il-m.area.x0)+m.rs*(it-m.area.y0)))); + for (int y=it;yReset(); + if ( y&0x00000003 ) { + theS->QuickScan(curY,curPt,((float)(y+0.25)),fill_oddEven,theI[0],false,0.25); + theS->QuickScan(curY,curPt,((float)(y+0.5)),fill_oddEven,theI[1],false,0.25); + theS->QuickScan(curY,curPt,((float)(y+0.75)),fill_oddEven,theI[2],false,0.25); + theS->QuickScan(curY,curPt,((float)(y+1.0)),fill_oddEven,theI[3],false,0.25); + } else { + theS->QuickScan(curY,curPt,((float)(y+0.25)),fill_oddEven,theI[0],true,0.25); + theS->QuickScan(curY,curPt,((float)(y+0.5)),fill_oddEven,theI[1],true,0.25); + theS->QuickScan(curY,curPt,((float)(y+0.75)),fill_oddEven,theI[2],true,0.25); + theS->QuickScan(curY,curPt,((float)(y+1.0)),fill_oddEven,theI[3],true,0.25); + } + theIL->Copy(4,theI); + + raster_info dest; + dest.startPix=il; + dest.endPix=ir; + dest.sth=il; + dest.stv=y; + dest.buffer=ligStart; + theIL->Raster(dest,NULL,shape_run_A8_OR); + ligStart=((uint32_t*)(((char*)ligStart)+m.rs)); + } + theS->EndQuickRaster(); + for (int i=0;i<4;i++) delete theI[i]; + delete theIL;*/ +} + + +/* + 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 : diff --git a/src/display/nr-arena-shape.h b/src/display/nr-arena-shape.h new file mode 100644 index 000000000..f7991bf4d --- /dev/null +++ b/src/display/nr-arena-shape.h @@ -0,0 +1,213 @@ +#ifndef __NR_ARENA_SHAPE_H__ +#define __NR_ARENA_SHAPE_H__ + +/* + * RGBA display list system for inkscape + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define NR_TYPE_ARENA_SHAPE (nr_arena_shape_get_type ()) +#define NR_ARENA_SHAPE(obj) (NR_CHECK_INSTANCE_CAST ((obj), NR_TYPE_ARENA_SHAPE, NRArenaShape)) +#define NR_IS_ARENA_SHAPE(obj) (NR_CHECK_INSTANCE_TYPE ((obj), NR_TYPE_ARENA_SHAPE)) + +//#include + +#include "display/curve.h" +#include "display/canvas-bpath.h" +#include "forward.h" +#include "sp-paint-server.h" +#include "nr-arena-item.h" + +#include "../color.h" + +#include "../livarot/Shape.h" + +NRType nr_arena_shape_get_type (void); + +struct NRArenaShape : public NRArenaItem { + class Paint { + public: + enum Type { + NONE, + COLOR, + SERVER + }; + + Paint() : _type(NONE), _server(NULL) { + sp_color_set_rgb_rgba32(&_color, 0); + } + Paint(Paint const &p) { _assign(p); } + ~Paint() { clear(); } + + Type type() const { return _type; } + SPPaintServer *server() const { return _server; } + SPColor const &color() const { return _color; } + + Paint &operator=(Paint const &p) { + set(p); + return *this; + } + + void set(Paint const &p) { + clear(); + _assign(p); + } + void set(SPColor const &color) { + clear(); + _type = COLOR; + sp_color_copy(&_color, &color); + } + void set(SPPaintServer *server) { + clear(); + if (server) { + _type = SERVER; + _server = server; + sp_object_ref(_server, NULL); + } + } + void clear() { + if ( _type == SERVER ) { + sp_object_unref(_server, NULL); + _server = NULL; + } + _type = NONE; + } + + private: + Type _type; + SPColor _color; + SPPaintServer *_server; + + void _assign(Paint const &p) { + _type = p._type; + _server = p._server; + sp_color_copy(&_color, &p._color); + if (_server) { + sp_object_ref(_server, NULL); + } + } + }; + + enum FillRule { + EVEN_ODD, + NONZERO + }; + + enum CapType { + ROUND_CAP, + SQUARE_CAP, + BUTT_CAP + }; + + enum JoinType { + ROUND_JOIN, + BEVEL_JOIN, + MITRE_JOIN + }; + + /* Shape data */ + SPCurve *curve; + SPStyle *style; + NRRect paintbox; + /* State data */ + NR::Matrix ctm; + + SPPainter *fill_painter; + SPPainter *stroke_painter; + // the 2 cached polygons, for rasterizations uses + Shape *fill_shp; + Shape *stroke_shp; + // delayed_shp=true means the *_shp polygons are not computed yet + // they'll be computed on demand in *_render(), *_pick() or *_clip() + // the goal is to not uncross polygons that are outside the viewing region + bool delayed_shp; + // approximate bounding box, for the case when the polygons have been delayed + NRRectL approx_bbox; + // cache for transformations: cached_fill and cached_stroke are + // polygons computed for the cached_fctm and cache_sctm respectively + // when the transformation changes interactively (tracked by the + // SP_OBJECT_USER_MODIFIED_FLAG_B), we check if it's an isometry wrt + // the cached ctm. if it's an isometry, just apply it to the cached + // polygon to get the *_shp polygon. Otherwise, recompute so this + // works fine for translation and rotation, but not scaling and + // skewing + NR::Matrix cached_fctm; + NR::Matrix cached_sctm; + + Shape *cached_fill; + Shape *cached_stroke; + /* Markers */ + NRArenaItem *markers; + + static NRArenaShape *create(NRArena *arena) { + NRArenaShape *obj=reinterpret_cast(nr_object_new(NR_TYPE_ARENA_SHAPE)); + obj->init(arena); + return obj; + } + + void setFill(SPPaintServer *server); + void setFill(SPColor const &color); + void setFillOpacity(double opacity); + void setFillRule(FillRule rule); + + void setStroke(SPPaintServer *server); + void setStroke(SPColor const &color); + void setStrokeOpacity(double opacity); + void setStrokeWidth(double width); + void setLineCap(CapType cap); + void setLineJoin(JoinType join); + void setMitreLimit(double limit); + + void setPaintBox(NR::Rect const &pbox); + + void _invalidateCachedFill() { + if (cached_fill) { + delete cached_fill; + cached_fill = NULL; + } + } + void _invalidateCachedStroke() { + if (cached_stroke) { + delete cached_stroke; + cached_stroke = NULL; + } + } + + struct Style { + Style() : opacity(0.0) {} + Paint paint; + double opacity; + }; + struct FillStyle : public Style { + FillStyle() : rule(EVEN_ODD) {} + FillRule rule; + } _fill; + struct StrokeStyle : public Style { + StrokeStyle() + : cap(ROUND_CAP), join(ROUND_JOIN), + width(0.0), mitre_limit(0.0) + {} + + CapType cap; + JoinType join; + double width; + double mitre_limit; + } _stroke; +}; + +struct NRArenaShapeClass { + NRArenaItemClass parent_class; +}; + +void nr_arena_shape_set_path(NRArenaShape *shape, SPCurve *curve, bool justTrans); +void nr_arena_shape_set_style (NRArenaShape *shape, SPStyle *style); +void nr_arena_shape_set_paintbox (NRArenaShape *shape, const NRRect *pbox); + +#endif diff --git a/src/display/nr-arena.cpp b/src/display/nr-arena.cpp new file mode 100644 index 000000000..f54bfbb90 --- /dev/null +++ b/src/display/nr-arena.cpp @@ -0,0 +1,131 @@ +#define __NR_ARENA_C__ + +/* + * RGBA display list system for inkscape + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "nr-arena-item.h" +#include "nr-arena.h" +#include + +static void nr_arena_class_init (NRArenaClass *klass); +static void nr_arena_init (NRArena *arena); +static void nr_arena_finalize (NRObject *object); + +static NRActiveObjectClass *parent_class; + +NRType +nr_arena_get_type (void) +{ + static NRType type = 0; + if (!type) { + type = nr_object_register_type (NR_TYPE_ACTIVE_OBJECT, + "NRArena", + sizeof (NRArenaClass), + sizeof (NRArena), + (void (*) (NRObjectClass *)) nr_arena_class_init, + (void (*) (NRObject *)) nr_arena_init); + } + return type; +} + +static void +nr_arena_class_init (NRArenaClass *klass) +{ + NRObjectClass *object_class = (NRObjectClass *) klass; + + parent_class = (NRActiveObjectClass *) (((NRObjectClass *) klass)->parent); + + object_class->finalize = nr_arena_finalize; + object_class->cpp_ctor = NRObject::invoke_ctor; +} + +static void +nr_arena_init (NRArena *arena) +{ + arena->delta = 0; // to be set by desktop from prefs + arena->rendermode = RENDERMODE_NORMAL; // default is normal render + arena->outlinecolor = 0xff; // black; to be set by desktop from bg color +} + +static void +nr_arena_finalize (NRObject *object) +{ + ((NRObjectClass *) (parent_class))->finalize (object); +} + +void +nr_arena_request_update (NRArena *arena, NRArenaItem *item) +{ + NRActiveObject *aobject = (NRActiveObject *) arena; + + nr_return_if_fail (arena != NULL); + nr_return_if_fail (NR_IS_ARENA (arena)); + nr_return_if_fail (item != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (item)); + + if (aobject->callbacks) { + for (unsigned int i = 0; i < aobject->callbacks->length; i++) { + NRObjectListener *listener = aobject->callbacks->listeners + i; + NRArenaEventVector *avector = (NRArenaEventVector *) listener->vector; + if ((listener->size >= sizeof (NRArenaEventVector)) && avector->request_update) { + avector->request_update (arena, item, listener->data); + } + } + } +} + +void +nr_arena_request_render_rect (NRArena *arena, NRRectL *area) +{ + NRActiveObject *aobject = (NRActiveObject *) arena; + + nr_return_if_fail (arena != NULL); + nr_return_if_fail (NR_IS_ARENA (arena)); + nr_return_if_fail (area != NULL); + + if (aobject->callbacks && area && !nr_rect_l_test_empty (area)) { + for (unsigned int i = 0; i < aobject->callbacks->length; i++) { + NRObjectListener *listener = aobject->callbacks->listeners + i; + NRArenaEventVector *avector = (NRArenaEventVector *) listener->vector; + if ((listener->size >= sizeof (NRArenaEventVector)) && avector->request_render) { + avector->request_render (arena, area, listener->data); + } + } + } +} + +void +nr_arena_render_paintserver_fill (NRPixBlock *pb, NRRectL *area, SPPainter *painter, float opacity, NRPixBlock *mask) +{ + NRPixBlock cb, cb_opa; + nr_pixblock_setup_fast (&cb, NR_PIXBLOCK_MODE_R8G8B8A8N, area->x0, area->y0, area->x1, area->y1, TRUE); + nr_pixblock_setup_fast (&cb_opa, NR_PIXBLOCK_MODE_R8G8B8A8N, area->x0, area->y0, area->x1, area->y1, TRUE); + + /* Need separate gradient buffer (lauris)*/ + // do the filling + painter->fill (painter, &cb); + cb.empty = FALSE; + + // do the fill-opacity and mask composite + if (opacity < 1.0) { + nr_blit_pixblock_pixblock_alpha (&cb_opa, &cb, (int) floor (255 * opacity)); + cb_opa.empty = FALSE; + nr_blit_pixblock_pixblock_mask (pb, &cb_opa, mask); + } else { + nr_blit_pixblock_pixblock_mask (pb, &cb, mask); + } + + pb->empty = FALSE; + + nr_pixblock_release (&cb); + nr_pixblock_release (&cb_opa); +} diff --git a/src/display/nr-arena.h b/src/display/nr-arena.h new file mode 100644 index 000000000..245ce14db --- /dev/null +++ b/src/display/nr-arena.h @@ -0,0 +1,57 @@ +#ifndef __NR_ARENA_H__ +#define __NR_ARENA_H__ + +/* + * RGBA display list system for inkscape + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define NR_TYPE_ARENA (nr_arena_get_type ()) +#define NR_ARENA(o) (NR_CHECK_INSTANCE_CAST ((o), NR_TYPE_ARENA, NRArena)) +#define NR_IS_ARENA(o) (NR_CHECK_INSTANCE_TYPE ((o), NR_TYPE_ARENA)) + +#include +#include +#include "nr-arena-forward.h" +#include "sp-paint-server.h" + +NRType nr_arena_get_type (void); + +struct NRArenaEventVector { + NRObjectEventVector parent; + void (* request_update) (NRArena *arena, NRArenaItem *item, void *data); + void (* request_render) (NRArena *arena, NRRectL *area, void *data); +}; + +enum { + RENDERMODE_NORMAL, + RENDERMODE_NOAA, + RENDERMODE_OUTLINE +}; + +struct NRArena : public NRActiveObject { + static NRArena *create() { + return reinterpret_cast(nr_object_new(NR_TYPE_ARENA)); + } + + double delta; + int rendermode; + guint32 outlinecolor; +}; + +struct NRArenaClass : public NRActiveObjectClass { +}; + +void nr_arena_request_update (NRArena *arena, NRArenaItem *item); +void nr_arena_request_render_rect (NRArena *arena, NRRectL *area); + +void nr_arena_render_paintserver_fill (NRPixBlock *pb, NRRectL *area, SPPainter *painter, float opacity, NRPixBlock *mask); + +#endif diff --git a/src/display/nr-gradient-gpl.cpp b/src/display/nr-gradient-gpl.cpp new file mode 100644 index 000000000..54a33cfdb --- /dev/null +++ b/src/display/nr-gradient-gpl.cpp @@ -0,0 +1,306 @@ +#define __NR_GRADIENT_C__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include + +#include "nr-gradient-gpl.h" + +#define noNR_USE_GENERIC_RENDERER + +#define NRG_MASK (NR_GRADIENT_VECTOR_LENGTH - 1) +#define NRG_2MASK ((NR_GRADIENT_VECTOR_LENGTH << 1) - 1) + +static void nr_lgradient_render_block (NRRenderer *r, NRPixBlock *pb, NRPixBlock *m); +static void nr_lgradient_render_R8G8B8A8N_EMPTY (NRLGradientRenderer *lgr, unsigned char *px, int x0, int y0, int width, int height, int rs); +static void nr_lgradient_render_R8G8B8A8N (NRLGradientRenderer *lgr, unsigned char *px, int x0, int y0, int width, int height, int rs); +static void nr_lgradient_render_R8G8B8 (NRLGradientRenderer *lgr, unsigned char *px, int x0, int y0, int width, int height, int rs); +static void nr_lgradient_render_generic (NRLGradientRenderer *lgr, NRPixBlock *pb); + +NRRenderer * +nr_lgradient_renderer_setup (NRLGradientRenderer *lgr, + const unsigned char *cv, + unsigned int spread, + const NRMatrix *gs2px, + float x0, float y0, + float x1, float y1) +{ + NRMatrix n2gs, n2px, px2n; + + lgr->renderer.render = nr_lgradient_render_block; + + lgr->vector = cv; + lgr->spread = spread; + + n2gs.c[0] = x1 - x0; + n2gs.c[1] = y1 - y0; + n2gs.c[2] = y1 - y0; + n2gs.c[3] = x0 - x1; + n2gs.c[4] = x0; + n2gs.c[5] = y0; + + nr_matrix_multiply (&n2px, &n2gs, gs2px); + nr_matrix_invert (&px2n, &n2px); + + lgr->x0 = n2px.c[4] - 0.5; + lgr->y0 = n2px.c[5] - 0.5; + lgr->dx = px2n.c[0] * NR_GRADIENT_VECTOR_LENGTH; + lgr->dy = px2n.c[2] * NR_GRADIENT_VECTOR_LENGTH; + + return (NRRenderer *) lgr; +} + +static void +nr_lgradient_render_block (NRRenderer *r, NRPixBlock *pb, NRPixBlock *m) +{ + NRLGradientRenderer *lgr; + int width, height; + + lgr = (NRLGradientRenderer *) r; + + width = pb->area.x1 - pb->area.x0; + height = pb->area.y1 - pb->area.y0; + +#ifdef NR_USE_GENERIC_RENDERER + nr_lgradient_render_generic (lgr, pb); +#else + if (pb->empty) { + switch (pb->mode) { + case NR_PIXBLOCK_MODE_A8: + nr_lgradient_render_generic (lgr, pb); + break; + case NR_PIXBLOCK_MODE_R8G8B8: + nr_lgradient_render_generic (lgr, pb); + break; + case NR_PIXBLOCK_MODE_R8G8B8A8N: + nr_lgradient_render_R8G8B8A8N_EMPTY (lgr, NR_PIXBLOCK_PX (pb), pb->area.x0, pb->area.y0, width, height, pb->rs); + break; + case NR_PIXBLOCK_MODE_R8G8B8A8P: + nr_lgradient_render_generic (lgr, pb); + break; + default: + break; + } + } else { + switch (pb->mode) { + case NR_PIXBLOCK_MODE_A8: + nr_lgradient_render_generic (lgr, pb); + break; + case NR_PIXBLOCK_MODE_R8G8B8: + nr_lgradient_render_R8G8B8 (lgr, NR_PIXBLOCK_PX (pb), pb->area.x0, pb->area.y0, width, height, pb->rs); + break; + case NR_PIXBLOCK_MODE_R8G8B8A8N: + nr_lgradient_render_R8G8B8A8N (lgr, NR_PIXBLOCK_PX (pb), pb->area.x0, pb->area.y0, width, height, pb->rs); + break; + case NR_PIXBLOCK_MODE_R8G8B8A8P: + nr_lgradient_render_generic (lgr, pb); + break; + default: + break; + } + } +#endif +} + +static void +nr_lgradient_render_R8G8B8A8N_EMPTY (NRLGradientRenderer *lgr, unsigned char *px, int x0, int y0, int width, int height, int rs) +{ + int x, y; + double pos; + + for (y = 0; y < height; y++) { + const unsigned char *s; + unsigned char *d; + int idx; + d = px + y * rs; + pos = (y + y0 - lgr->y0) * lgr->dy + (0 + x0 - lgr->x0) * lgr->dx; + if (lgr->spread == NR_GRADIENT_SPREAD_PAD) { + for (x = 0; x < width; x++) { + idx = (int) CLAMP (pos, 0, (double) NRG_MASK); + s = lgr->vector + 4 * idx; + d[0] = s[0]; + d[1] = s[1]; + d[2] = s[2]; + d[3] = s[3]; + d += 4; + pos += lgr->dx; + } + } else if (lgr->spread == NR_GRADIENT_SPREAD_REFLECT) { + for (x = 0; x < width; x++) { + idx = (int) ((long long) pos & NRG_2MASK); + if (idx > NRG_MASK) idx = NRG_2MASK - idx; + s = lgr->vector + 4 * idx; + d[0] = s[0]; + d[1] = s[1]; + d[2] = s[2]; + d[3] = s[3]; + d += 4; + pos += lgr->dx; + } + } else { + for (x = 0; x < width; x++) { + idx = (int) ((long long) pos & NRG_MASK); + s = lgr->vector + 4 * idx; + d[0] = s[0]; + d[1] = s[1]; + d[2] = s[2]; + d[3] = s[3]; + d += 4; + pos += lgr->dx; + } + } + } +} + +static void +nr_lgradient_render_R8G8B8A8N (NRLGradientRenderer *lgr, unsigned char *px, int x0, int y0, int width, int height, int rs) +{ + int x, y; + unsigned char *d; + double pos; + + for (y = 0; y < height; y++) { + d = px + y * rs; + pos = (y + y0 - lgr->y0) * lgr->dy + (0 + x0 - lgr->x0) * lgr->dx; + for (x = 0; x < width; x++) { + int idx; + unsigned int ca; + const unsigned char *s; + switch (lgr->spread) { + case NR_GRADIENT_SPREAD_PAD: + idx = (int) CLAMP (pos, 0, (double) NRG_MASK); + break; + case NR_GRADIENT_SPREAD_REFLECT: + idx = (int) ((long long) pos & NRG_2MASK); + if (idx > NRG_MASK) idx = NRG_2MASK - idx; + break; + case NR_GRADIENT_SPREAD_REPEAT: + idx = (int) ((long long) pos & NRG_MASK); + break; + default: + idx = 0; + break; + } + /* Full composition */ + s = lgr->vector + 4 * idx; + if (s[3] == 255) { + d[0] = s[0]; + d[1] = s[1]; + d[2] = s[2]; + d[3] = 255; + } else if (s[3] != 0) { + ca = NR_A7(s[3],d[3]); + d[0] = NR_COMPOSENNN_A7 (s[0], s[3], d[0], d[3], ca); + d[1] = NR_COMPOSENNN_A7 (s[1], s[3], d[1], d[3], ca); + d[2] = NR_COMPOSENNN_A7 (s[2], s[3], d[2], d[3], ca); + d[3] = NR_PREMUL_SINGLE(ca); + } + d += 4; + pos += lgr->dx; + } + } +} + +static void +nr_lgradient_render_R8G8B8 (NRLGradientRenderer *lgr, unsigned char *px, int x0, int y0, int width, int height, int rs) +{ + int x, y; + unsigned char *d; + double pos; + + for (y = 0; y < height; y++) { + d = px + y * rs; + pos = (y + y0 - lgr->y0) * lgr->dy + (0 + x0 - lgr->x0) * lgr->dx; + for (x = 0; x < width; x++) { + int idx; + const unsigned char *s; + switch (lgr->spread) { + case NR_GRADIENT_SPREAD_PAD: + idx = (int) CLAMP (pos, 0, (double) NRG_MASK); + break; + case NR_GRADIENT_SPREAD_REFLECT: + idx = (int) ((long long) pos & NRG_2MASK); + if (idx > NRG_MASK) idx = NRG_2MASK - idx; + break; + case NR_GRADIENT_SPREAD_REPEAT: + idx = (int) ((long long) pos & NRG_MASK); + break; + default: + idx = 0; + break; + } + /* Full composition */ + s = lgr->vector + 4 * idx; + d[0] = NR_COMPOSEN11 (s[0], s[3], d[0]); + d[1] = NR_COMPOSEN11 (s[1], s[3], d[1]); + d[2] = NR_COMPOSEN11 (s[2], s[3], d[2]); + d += 3; + pos += lgr->dx; + } + } +} + +static void +nr_lgradient_render_generic (NRLGradientRenderer *lgr, NRPixBlock *pb) +{ + int x, y; + unsigned char *d; + double pos; + int bpp; + NRPixBlock spb; + int x0, y0, width, height, rs; + + x0 = pb->area.x0; + y0 = pb->area.y0; + width = pb->area.x1 - pb->area.x0; + height = pb->area.y1 - pb->area.y0; + rs = pb->rs; + + nr_pixblock_setup_extern (&spb, NR_PIXBLOCK_MODE_R8G8B8A8N, 0, 0, NR_GRADIENT_VECTOR_LENGTH, 1, + (unsigned char *) lgr->vector, + 4 * NR_GRADIENT_VECTOR_LENGTH, + 0, 0); + bpp = (pb->mode == NR_PIXBLOCK_MODE_A8) ? 1 : (pb->mode == NR_PIXBLOCK_MODE_R8G8B8) ? 3 : 4; + + for (y = 0; y < height; y++) { + d = NR_PIXBLOCK_PX (pb) + y * rs; + pos = (y + y0 - lgr->y0) * lgr->dy + (0 + x0 - lgr->x0) * lgr->dx; + for (x = 0; x < width; x++) { + int idx; + const unsigned char *s; + switch (lgr->spread) { + case NR_GRADIENT_SPREAD_PAD: + idx = (int) CLAMP (pos, 0, (double) NRG_MASK); + break; + case NR_GRADIENT_SPREAD_REFLECT: + idx = (int) ((long long) pos & NRG_2MASK); + if (idx > NRG_MASK) idx = NRG_2MASK - idx; + break; + case NR_GRADIENT_SPREAD_REPEAT: + idx = (int) ((long long) pos & NRG_MASK); + break; + default: + idx = 0; + break; + } + s = lgr->vector + 4 * idx; + nr_compose_pixblock_pixblock_pixel (pb, d, &spb, s); + d += bpp; + pos += lgr->dx; + } + } + + nr_pixblock_release (&spb); +} + diff --git a/src/display/nr-gradient-gpl.h b/src/display/nr-gradient-gpl.h new file mode 100644 index 000000000..db7d9bb54 --- /dev/null +++ b/src/display/nr-gradient-gpl.h @@ -0,0 +1,41 @@ +#ifndef __NR_GRADIENT_GPL_H__ +#define __NR_GRADIENT_GPL_H__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +/* + * Here is GPL code, unlike other libnr wihich is public domain + */ + +#include + +/* Linear */ + +struct NRLGradientRenderer { + NRRenderer renderer; + const unsigned char *vector; + unsigned int spread; + double x0, y0; + double dx, dy; +}; + +NRRenderer *nr_lgradient_renderer_setup (NRLGradientRenderer *lgr, + const unsigned char *cv, + unsigned int spread, + const NRMatrix *gs2px, + float x0, float y0, + float x1, float y1); + + + +#endif diff --git a/src/display/nr-plain-stuff-gdk.cpp b/src/display/nr-plain-stuff-gdk.cpp new file mode 100644 index 000000000..d5b43f4ea --- /dev/null +++ b/src/display/nr-plain-stuff-gdk.cpp @@ -0,0 +1,46 @@ +#define __NR_PLAIN_STUFF_GDK_C__ + +/* + * Miscellaneous simple rendering utilities + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc. + * + * Released under GNU GPL + */ + +#include +#include "nr-plain-stuff.h" +#include "nr-plain-stuff-gdk.h" + +void +nr_gdk_draw_rgba32_solid (GdkDrawable *drawable, GdkGC *gc, gint x, gint y, gint w, gint h, guint32 rgba) +{ + NRPixBlock pb; + + nr_pixblock_setup_fast (&pb, NR_PIXBLOCK_MODE_R8G8B8A8N, 0, 0, w, h, FALSE); + + nr_render_rgba32_rgb (NR_PIXBLOCK_PX (&pb), w, h, pb.rs, x, y, rgba); + gdk_draw_rgb_image (drawable, gc, x, y, w, h, GDK_RGB_DITHER_MAX, NR_PIXBLOCK_PX (&pb), pb.rs); + + nr_pixblock_release (&pb); +} + +void +nr_gdk_draw_gray_garbage (GdkDrawable *drawable, GdkGC *gc, gint x, gint y, gint w, gint h) +{ + for (gint yy = y; yy < y + h; yy += 64) { + for (gint xx = x; xx < x + w; xx += 64) { + NRPixBlock pb; + gint ex = MIN (xx + 64, x + w); + gint ey = MIN (yy + 64, y + h); + nr_pixblock_setup_fast (&pb, NR_PIXBLOCK_MODE_R8G8B8, xx, yy, ex, ey, FALSE); + nr_pixblock_render_gray_noise (&pb, NULL); + gdk_draw_rgb_image (drawable, gc, xx, yy, ex - xx, ey - yy, GDK_RGB_DITHER_NONE, NR_PIXBLOCK_PX (&pb), pb.rs); + nr_pixblock_release (&pb); + } + } +} + diff --git a/src/display/nr-plain-stuff-gdk.h b/src/display/nr-plain-stuff-gdk.h new file mode 100644 index 000000000..7c83792a8 --- /dev/null +++ b/src/display/nr-plain-stuff-gdk.h @@ -0,0 +1,32 @@ +#ifndef __NR_PLAIN_STUFF_GDK_H__ +#define __NR_PLAIN_STUFF_GDK_H__ + +/* + * Miscellaneous simple rendering utilities + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc. + * + * Released under GNU GPL + */ + +#include + +void nr_gdk_draw_rgba32_solid (GdkDrawable *drawable, GdkGC *gc, gint x, gint y, gint w, gint h, guint32 rgba); + +void nr_gdk_draw_gray_garbage (GdkDrawable *drawable, GdkGC *gc, gint x, gint y, gint w, gint h); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/display/nr-plain-stuff.cpp b/src/display/nr-plain-stuff.cpp new file mode 100644 index 000000000..af6e002ec --- /dev/null +++ b/src/display/nr-plain-stuff.cpp @@ -0,0 +1,94 @@ +#define __NR_PLAIN_STUFF_C__ + +/* + * Miscellaneous simple rendering utilities + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc. + * + * Released under GNU GPL + */ + +#include +#include +#include "nr-plain-stuff.h" + +#define NR_DEFAULT_CHECKERSIZEP2 2 +#define NR_DEFAULT_CHECKERCOLOR0 0xbfbfbfff +#define NR_DEFAULT_CHECKERCOLOR1 0x808080ff + +void +nr_render_checkerboard_rgb (guchar *px, gint w, gint h, gint rs, gint xoff, gint yoff) +{ + g_return_if_fail (px != NULL); + + nr_render_checkerboard_rgb_custom (px, w, h, rs, xoff, yoff, NR_DEFAULT_CHECKERCOLOR0, NR_DEFAULT_CHECKERCOLOR1, NR_DEFAULT_CHECKERSIZEP2); +} + +void +nr_render_checkerboard_rgb_custom (guchar *px, gint w, gint h, gint rs, gint xoff, gint yoff, guint32 c0, guint32 c1, gint sizep2) +{ + gint x, y, m; + guint r0, g0, b0; + guint r1, g1, b1; + + g_return_if_fail (px != NULL); + g_return_if_fail (sizep2 >= 0); + g_return_if_fail (sizep2 <= 8); + + xoff &= 0x1ff; + yoff &= 0x1ff; + m = 0x1 << sizep2; + r0 = NR_RGBA32_R (c0); + g0 = NR_RGBA32_G (c0); + b0 = NR_RGBA32_B (c0); + r1 = NR_RGBA32_R (c1); + g1 = NR_RGBA32_G (c1); + b1 = NR_RGBA32_B (c1); + + for (y = 0; y < h; y++) { + guchar *p; + p = px; + for (x = 0; x < w; x++) { + if (((x + xoff) ^ (y + yoff)) & m) { + *p++ = r0; + *p++ = g0; + *p++ = b0; + } else { + *p++ = r1; + *p++ = g1; + *p++ = b1; + } + } + px += rs; + } +} + +void +nr_render_rgba32_rgb (guchar *px, gint w, gint h, gint rs, gint xoff, gint yoff, guint32 c) +{ + guint32 c0, c1; + gint a, r, g, b, cr, cg, cb; + + g_return_if_fail (px != NULL); + + r = NR_RGBA32_R (c); + g = NR_RGBA32_G (c); + b = NR_RGBA32_B (c); + a = NR_RGBA32_A (c); + + cr = NR_COMPOSEN11 (r, a, NR_RGBA32_R (NR_DEFAULT_CHECKERCOLOR0)); + cg = NR_COMPOSEN11 (g, a, NR_RGBA32_G (NR_DEFAULT_CHECKERCOLOR0)); + cb = NR_COMPOSEN11 (b, a, NR_RGBA32_B (NR_DEFAULT_CHECKERCOLOR0)); + c0 = (cr << 24) | (cg << 16) | (cb << 8) | 0xff; + + cr = NR_COMPOSEN11 (r, a, NR_RGBA32_R (NR_DEFAULT_CHECKERCOLOR1)); + cg = NR_COMPOSEN11 (g, a, NR_RGBA32_G (NR_DEFAULT_CHECKERCOLOR1)); + cb = NR_COMPOSEN11 (b, a, NR_RGBA32_B (NR_DEFAULT_CHECKERCOLOR1)); + c1 = (cr << 24) | (cg << 16) | (cb << 8) | 0xff; + + nr_render_checkerboard_rgb_custom (px, w, h, rs, xoff, yoff, c0, c1, NR_DEFAULT_CHECKERSIZEP2); +} + diff --git a/src/display/nr-plain-stuff.h b/src/display/nr-plain-stuff.h new file mode 100644 index 000000000..c568f38a6 --- /dev/null +++ b/src/display/nr-plain-stuff.h @@ -0,0 +1,33 @@ +#ifndef __NR_PLAIN_STUFF_H__ +#define __NR_PLAIN_STUFF_H__ + +/* + * Miscellaneous simple rendering utilities + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc. + * + * Released under GNU GPL + */ + +#include + +void nr_render_checkerboard_rgb (guchar *px, gint w, gint h, gint rs, gint xoff, gint yoff); +void nr_render_checkerboard_rgb_custom (guchar *px, gint w, gint h, gint rs, gint xoff, gint yoff, guint32 c0, guint32 c1, gint sizep2); + +void nr_render_rgba32_rgb (guchar *px, gint w, gint h, gint rs, gint xoff, gint yoff, guint32 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:encoding=utf-8:textwidth=99 : diff --git a/src/display/sodipodi-ctrl.cpp b/src/display/sodipodi-ctrl.cpp new file mode 100644 index 000000000..71786fd96 --- /dev/null +++ b/src/display/sodipodi-ctrl.cpp @@ -0,0 +1,494 @@ +#define INKSCAPE_CTRL_C + +/* + * SPCtrl + * + * We render it by hand to reduce allocing/freeing svps & to get clean + * (non-aa) images + * + */ + +#include "sp-canvas-util.h" +#include "display-forward.h" +#include "sodipodi-ctrl.h" + +enum { + ARG_0, + ARG_SHAPE, + ARG_MODE, + ARG_ANCHOR, + ARG_SIZE, + ARG_FILLED, + ARG_FILL_COLOR, + ARG_STROKED, + ARG_STROKE_COLOR, + ARG_PIXBUF +}; + + +static void sp_ctrl_class_init (SPCtrlClass *klass); +static void sp_ctrl_init (SPCtrl *ctrl); +static void sp_ctrl_destroy (GtkObject *object); +static void sp_ctrl_set_arg (GtkObject *object, GtkArg *arg, guint arg_id); + +static void sp_ctrl_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags); +static void sp_ctrl_render (SPCanvasItem *item, SPCanvasBuf *buf); + +static double sp_ctrl_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item); + + +static SPCanvasItemClass *parent_class; + +GtkType +sp_ctrl_get_type (void) +{ + static GtkType ctrl_type = 0; + if (!ctrl_type) { + static const GTypeInfo ctrl_info = { + sizeof (SPCtrlClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_ctrl_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (SPCtrl), + 0, /* n_preallocs */ + (GInstanceInitFunc) sp_ctrl_init, + NULL + }; + ctrl_type = g_type_register_static (SP_TYPE_CANVAS_ITEM, "SPCtrl", &ctrl_info, (GTypeFlags)0); + } + return ctrl_type; +} + +static void +sp_ctrl_class_init (SPCtrlClass *klass) +{ + GtkObjectClass *object_class; + SPCanvasItemClass *item_class; + + object_class = (GtkObjectClass *) klass; + item_class = (SPCanvasItemClass *) klass; + + parent_class = (SPCanvasItemClass *)gtk_type_class (sp_canvas_item_get_type ()); + + gtk_object_add_arg_type ("SPCtrl::shape", GTK_TYPE_INT, GTK_ARG_READWRITE, ARG_SHAPE); + gtk_object_add_arg_type ("SPCtrl::mode", GTK_TYPE_INT, GTK_ARG_READWRITE, ARG_MODE); + gtk_object_add_arg_type ("SPCtrl::anchor", GTK_TYPE_ANCHOR_TYPE, GTK_ARG_READWRITE, ARG_ANCHOR); + gtk_object_add_arg_type ("SPCtrl::size", GTK_TYPE_DOUBLE, GTK_ARG_READWRITE, ARG_SIZE); + gtk_object_add_arg_type ("SPCtrl::pixbuf", GTK_TYPE_POINTER, GTK_ARG_READWRITE, ARG_PIXBUF); + gtk_object_add_arg_type ("SPCtrl::filled", GTK_TYPE_BOOL, GTK_ARG_READWRITE, ARG_FILLED); + gtk_object_add_arg_type ("SPCtrl::fill_color", GTK_TYPE_INT, GTK_ARG_READWRITE, ARG_FILL_COLOR); + gtk_object_add_arg_type ("SPCtrl::stroked", GTK_TYPE_BOOL, GTK_ARG_READWRITE, ARG_STROKED); + gtk_object_add_arg_type ("SPCtrl::stroke_color", GTK_TYPE_INT, GTK_ARG_READWRITE, ARG_STROKE_COLOR); + + object_class->destroy = sp_ctrl_destroy; + object_class->set_arg = sp_ctrl_set_arg; + + item_class->update = sp_ctrl_update; + item_class->render = sp_ctrl_render; + item_class->point = sp_ctrl_point; +} + +static void +sp_ctrl_init (SPCtrl *ctrl) +{ + ctrl->shape = SP_CTRL_SHAPE_SQUARE; + ctrl->mode = SP_CTRL_MODE_COLOR; + ctrl->anchor = GTK_ANCHOR_CENTER; + ctrl->span = 3; + ctrl->defined = TRUE; + ctrl->shown = FALSE; + ctrl->build = FALSE; + ctrl->filled = 1; + ctrl->stroked = 0; + ctrl->fill_color = 0x000000ff; + ctrl->stroke_color = 0x000000ff; + + ctrl->box.x0 = ctrl->box.y0 = ctrl->box.x1 = ctrl->box.y1 = 0; + ctrl->cache = NULL; + ctrl->pixbuf = NULL; +} + +static void +sp_ctrl_destroy (GtkObject *object) +{ + SPCtrl *ctrl; + + g_return_if_fail (object != NULL); + g_return_if_fail (SP_IS_CTRL (object)); + + ctrl = SP_CTRL (object); + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} + +static void +sp_ctrl_set_arg (GtkObject *object, GtkArg *arg, guint arg_id) +{ + SPCanvasItem *item; + SPCtrl *ctrl; + GdkPixbuf * pixbuf = NULL; + + item = SP_CANVAS_ITEM (object); + ctrl = SP_CTRL (object); + + switch (arg_id) { + case ARG_SHAPE: + ctrl->shape = (SPCtrlShapeType)(GTK_VALUE_INT (*arg)); + ctrl->build = FALSE; + sp_canvas_item_request_update (item); + break; + case ARG_MODE: + ctrl->mode = (SPCtrlModeType)(GTK_VALUE_INT (*arg)); + ctrl->build = FALSE; + sp_canvas_item_request_update (item); + break; + case ARG_ANCHOR: + ctrl->anchor = (GtkAnchorType)(GTK_VALUE_INT (*arg)); + ctrl->build = FALSE; + sp_canvas_item_request_update (item); + break; + case ARG_SIZE: + ctrl->span = (gint) ((GTK_VALUE_DOUBLE (*arg) - 1.0) / 2.0 + 0.5); + ctrl->defined = (ctrl->span > 0); + ctrl->build = FALSE; + sp_canvas_item_request_update (item); + break; + case ARG_FILLED: + ctrl->filled = GTK_VALUE_BOOL (*arg); + ctrl->build = FALSE; + sp_canvas_item_request_update (item); + break; + case ARG_FILL_COLOR: + ctrl->fill_color = GTK_VALUE_INT (*arg); + ctrl->build = FALSE; + sp_canvas_item_request_update (item); + break; + case ARG_STROKED: + ctrl->stroked = GTK_VALUE_BOOL (*arg); + ctrl->build = FALSE; + sp_canvas_item_request_update (item); + break; + case ARG_STROKE_COLOR: + ctrl->stroke_color = GTK_VALUE_INT (*arg); + ctrl->build = FALSE; + sp_canvas_item_request_update (item); + break; + case ARG_PIXBUF: + pixbuf = (GdkPixbuf*)(GTK_VALUE_POINTER (*arg)); + if (gdk_pixbuf_get_has_alpha (pixbuf)) { + ctrl->pixbuf = pixbuf; + } else { + ctrl->pixbuf = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0); + gdk_pixbuf_unref (pixbuf); + } + ctrl->build = FALSE; + break; + default: + break; + } +} + +static void +sp_ctrl_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags) +{ + SPCtrl *ctrl; + gint x, y; + + ctrl = SP_CTRL (item); + + if (((SPCanvasItemClass *) parent_class)->update) + (* ((SPCanvasItemClass *) parent_class)->update) (item, affine, flags); + + sp_canvas_item_reset_bounds (item); + + if (ctrl->shown) { + sp_canvas_request_redraw (item->canvas, ctrl->box.x0, ctrl->box.y0, ctrl->box.x1 + 1, ctrl->box.y1 + 1); + } + + if (!ctrl->defined) return; + + x = (gint) ((affine[4] > 0) ? (affine[4] + 0.5) : (affine[4] - 0.5)) - ctrl->span; + y = (gint) ((affine[5] > 0) ? (affine[5] + 0.5) : (affine[5] - 0.5)) - ctrl->span; + + switch (ctrl->anchor) { + case GTK_ANCHOR_N: + case GTK_ANCHOR_CENTER: + case GTK_ANCHOR_S: + break; + case GTK_ANCHOR_NW: + case GTK_ANCHOR_W: + case GTK_ANCHOR_SW: + x += ctrl->span; + break; + case GTK_ANCHOR_NE: + case GTK_ANCHOR_E: + case GTK_ANCHOR_SE: + x -= (ctrl->span + 1); + break; + } + + switch (ctrl->anchor) { + case GTK_ANCHOR_W: + case GTK_ANCHOR_CENTER: + case GTK_ANCHOR_E: + break; + case GTK_ANCHOR_NW: + case GTK_ANCHOR_N: + case GTK_ANCHOR_NE: + y += ctrl->span; + break; + case GTK_ANCHOR_SW: + case GTK_ANCHOR_S: + case GTK_ANCHOR_SE: + y -= (ctrl->span + 1); + break; + } + + ctrl->box.x0 = x; + ctrl->box.y0 = y; + ctrl->box.x1 = ctrl->box.x0 + 2 * ctrl->span; + ctrl->box.y1 = ctrl->box.y0 + 2 * ctrl->span; + + sp_canvas_update_bbox (item, ctrl->box.x0, ctrl->box.y0, ctrl->box.x1 + 1, ctrl->box.y1 + 1); + +} + +static double +sp_ctrl_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item) +{ + SPCtrl *ctrl = SP_CTRL (item); + + *actual_item = item; + + const double x = p[NR::X]; + const double y = p[NR::Y]; + + if ((x >= ctrl->box.x0) && (x <= ctrl->box.x1) && (y >= ctrl->box.y0) && (y <= ctrl->box.y1)) return 0.0; + + return 1e18; +} + +static void +sp_ctrl_build_cache (SPCtrl *ctrl) +{ + guchar * p, *q; + gint size, x, y, z, s, a, side, c; + guint8 fr, fg, fb, fa, sr, sg, sb, sa; + + if (ctrl->filled) { + fr = (ctrl->fill_color >> 24) & 0xff; + fg = (ctrl->fill_color >> 16) & 0xff; + fb = (ctrl->fill_color >> 8) & 0xff; + fa = (ctrl->fill_color) & 0xff; + } else { fr = 0x00; fg = 0x00; fb = 0x00; fa = 0x00; } + if (ctrl->stroked) { + sr = (ctrl->stroke_color >> 24) & 0xff; + sg = (ctrl->stroke_color >> 16) & 0xff; + sb = (ctrl->stroke_color >> 8) & 0xff; + sa = (ctrl->stroke_color) & 0xff; + } else { sr = fr; sg = fg; sb = fb; sa = fa; } + + + side = (ctrl->span * 2 +1); + c = ctrl->span ; + g_free (ctrl->cache); + size = (side) * (side) * 4; + ctrl->cache = (guchar*)g_malloc (size); + if (side < 2) return; + + switch (ctrl->shape) { + case SP_CTRL_SHAPE_SQUARE: + p = ctrl->cache; + for (x=0; x < side; x++) { *p++ = sr; *p++ = sg; *p++ = sb; *p++ = sa; } + for (y = 2; y < side; y++) { + *p++ = sr; *p++ = sg; *p++ = sb; *p++ = sa; + for (x=2; x < side; x++) { *p++ = fr; *p++ = fg; *p++ = fb; *p++ = fa; } + *p++ = sr; *p++ = sg; *p++ = sb; *p++ = sa; + } + for (x=0; x < side; x++) { *p++ = sr; *p++ = sg; *p++ = sb; *p++ = sa; } + ctrl->build = TRUE; + break; + case SP_CTRL_SHAPE_DIAMOND: + p = ctrl->cache; + for (y = 0; y < side; y++) { + z = abs (c - y); + for (x = 0; x < z; x++) {*p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00;} + *p++ = sr; *p++ = sg; *p++ = sb; *p++ = sa; x++; + for (; x < side - z -1; x++) { *p++ = fr; *p++ = fg; *p++ = fb; *p++ = fa; } + if (z != c) {*p++ = sr; *p++ = sg; *p++ = sb; *p++ = sa; x++;} + for (; x < side; x++) {*p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00;} + } + break; + case SP_CTRL_SHAPE_CIRCLE: + p = ctrl->cache; + q = p + size -1; + s = -1; + for (y = 0; y <= c ; y++) { + a = abs (c - y); + z = (gint)(0.0 + sqrt ((c+.4)*(c+.4) - a*a)); + x = 0; + while (x < c-z) { + *p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00; + *q-- = 0x00; *q-- = 0x00; *q-- = 0x00; *q-- = 0x00; + x++; + } + do { + *p++ = sr; *p++ = sg; *p++ = sb; *p++ = sa; + *q-- = sa; *q-- = sb; *q-- = sg; *q-- = sr; + x++; + } while (x < c-s); + while (x < MIN(c+s+1, c+z)) { + *p++ = fr; *p++ = fg; *p++ = fb; *p++ = fa; + *q-- = fa; *q-- = fb; *q-- = fg; *q-- = fr; + x++; + } + do { + *p++ = sr; *p++ = sg; *p++ = sb; *p++ = sa; + *q-- = sa; *q-- = sb; *q-- = sg; *q-- = sr; + x++; + } while (x <= c+z); + while (x < side) { + *p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00; + *q-- = 0x00; *q-- = 0x00; *q-- = 0x00; *q-- = 0x00; + x++; + } + s = z; + } + ctrl->build = TRUE; + break; + case SP_CTRL_SHAPE_CROSS: + p = ctrl->cache; + for (y = 0; y < side; y++) { + z = abs (c - y); + for (x = 0; x < c-z; x++) {*p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00;} + *p++ = sr; *p++ = sg; *p++ = sb; *p++ = sa; x++; + for (; x < c + z; x++) {*p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00;} + if (z != 0) {*p++ = sr; *p++ = sg; *p++ = sb; *p++ = sa; x++;} + for (; x < side; x++) {*p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00;} + } + ctrl->build = TRUE; + break; + case SP_CTRL_SHAPE_BITMAP: + if (ctrl->pixbuf) { + unsigned char *px; + unsigned int rs; + px = gdk_pixbuf_get_pixels (ctrl->pixbuf); + rs = gdk_pixbuf_get_rowstride (ctrl->pixbuf); + for (y = 0; y < side; y++){ + unsigned char *s, *d; + s = px + y * rs; + d = ctrl->cache + 4 * side * y; + for (x = 0; x < side; x++) { + if (s[3] < 0x80) { + d[0] = 0x00; + d[1] = 0x00; + d[2] = 0x00; + d[3] = 0x00; + } else if (s[0] < 0x80) { + d[0] = sr; + d[1] = sg; + d[2] = sb; + d[3] = sa; + } else { + d[0] = fr; + d[1] = fg; + d[2] = fb; + d[3] = fa; + } + s += 4; + d += 4; + } + } + } else { + g_print ("control has no pixmap\n"); + } + ctrl->build = TRUE; + break; + case SP_CTRL_SHAPE_IMAGE: + if (ctrl->pixbuf) { + guint r = gdk_pixbuf_get_rowstride (ctrl->pixbuf); + guchar * pix; + q = gdk_pixbuf_get_pixels (ctrl->pixbuf); + p = ctrl->cache; + for (y = 0; y < side; y++){ + pix = q + (y * r); + for (x = 0; x < side; x++) { + *p++ = *pix++; + *p++ = *pix++; + *p++ = *pix++; + *p++ = *pix++; + } + } + } else { g_print ("control has no pixmap\n"); } + ctrl->build = TRUE; + break; + default: + break; + } + +} + +// composite background, foreground, alpha for xor mode +#define COMPOSE_X(b,f,a) ( ( ((guchar) b) * ((guchar) (0xff - a)) + ((guchar) ((b ^ ~f) + b/4 - (b>127? 63 : 0))) * ((guchar) a) ) / 0xff ) +// composite background, foreground, alpha for color mode +#define COMPOSE_N(b,f,a) ( ( ((guchar) b) * ((guchar) (0xff - a)) + ((guchar) f) * ((guchar) a) ) / 0xff ) + +static void +sp_ctrl_render (SPCanvasItem *item, SPCanvasBuf *buf) +{ + gint y0, y1, y, x0,x1,x; + guchar * p, * q, a; + + SPCtrl *ctrl = SP_CTRL (item); + + if (!ctrl->defined) return; + if ((!ctrl->filled) && (!ctrl->stroked)) return; + + sp_canvas_prepare_buffer (buf); + + // the control-image is rendered into ctrl->cache + if (!ctrl->build) sp_ctrl_build_cache (ctrl); + + // then we render from ctrl->cache + y0 = MAX (ctrl->box.y0, buf->rect.y0); + y1 = MIN (ctrl->box.y1, buf->rect.y1 - 1); + x0 = MAX (ctrl->box.x0, buf->rect.x0); + x1 = MIN (ctrl->box.x1, buf->rect.x1 - 1); + + bool colormode; + + for (y = y0; y <= y1; y++) { + p = buf->buf + (y - buf->rect.y0) * buf->buf_rowstride + (x0 - buf->rect.x0) * 3; + q = ctrl->cache + ((y - ctrl->box.y0) * (ctrl->span*2+1) + (x0 - ctrl->box.x0)) * 4; + for (x = x0; x <= x1; x++) { + a = *(q + 3); + // 00000000 is the only way to get invisible; all other colors with alpha 00 are treated as mode_color with alpha ff + colormode = false; + if (a == 0x00 && !(q[0] == 0x00 && q[1] == 0x00 && q[2] == 0x00)) { + a = 0xff; + colormode = true; + } + if (ctrl->mode == SP_CTRL_MODE_COLOR || colormode) { + p[0] = COMPOSE_N (p[0], q[0], a); + p[1] = COMPOSE_N (p[1], q[1], a); + p[2] = COMPOSE_N (p[2], q[2], a); + q += 4; + p += 3; + } else if (ctrl->mode == SP_CTRL_MODE_XOR) { + p[0] = COMPOSE_X (p[0], q[0], a); + p[1] = COMPOSE_X (p[1], q[1], a); + p[2] = COMPOSE_X (p[2], q[2], a); + q += 4; + p += 3; + } + } + } + ctrl->shown = TRUE; +} + +void SPCtrl::moveto (NR::Point const p) { + sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (this), NR::Matrix(NR::translate (p))); +} diff --git a/src/display/sodipodi-ctrl.h b/src/display/sodipodi-ctrl.h new file mode 100644 index 000000000..c0e584ce2 --- /dev/null +++ b/src/display/sodipodi-ctrl.h @@ -0,0 +1,65 @@ +#ifndef INKSCAPE_CTRL_H +#define INKSCAPE_CTRL_H + +/* sodipodi-ctrl + * + * It is simply small square, which does not scale nor rotate + * + */ + +#include +#include "sp-canvas.h" +#include +#include + + + +#define SP_TYPE_CTRL (sp_ctrl_get_type ()) +#define SP_CTRL(obj) (GTK_CHECK_CAST ((obj), SP_TYPE_CTRL, SPCtrl)) +#define SP_CTRL_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), SP_TYPE_CTRL, SPCtrlClass)) +#define SP_IS_CTRL(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_CTRL)) +#define SP_IS_CTRL_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), SP_TYPE_CTRL)) + +typedef enum { + SP_CTRL_SHAPE_SQUARE, + SP_CTRL_SHAPE_DIAMOND, + SP_CTRL_SHAPE_CIRCLE, + SP_CTRL_SHAPE_CROSS, + SP_CTRL_SHAPE_BITMAP, + SP_CTRL_SHAPE_IMAGE +} SPCtrlShapeType; + + +typedef enum { + SP_CTRL_MODE_COLOR, + SP_CTRL_MODE_XOR +} SPCtrlModeType; + +struct SPCtrl : public SPCanvasItem{ + SPCtrlShapeType shape; + SPCtrlModeType mode; + GtkAnchorType anchor; + gint span; + guint defined : 1; + guint shown : 1; + guint build : 1; + guint filled : 1; + guint stroked : 1; + guint32 fill_color; + guint32 stroke_color; + + NRRectL box; /* NB! x1 & y1 are included */ + guchar *cache; + GdkPixbuf * pixbuf; + + void moveto(NR::Point const p); +}; + +struct SPCtrlClass : public SPCanvasItemClass{ +}; + + + +/* Standard Gtk function */ +GtkType sp_ctrl_get_type (void); +#endif diff --git a/src/display/sodipodi-ctrlrect.cpp b/src/display/sodipodi-ctrlrect.cpp new file mode 100644 index 000000000..05449f2bb --- /dev/null +++ b/src/display/sodipodi-ctrlrect.cpp @@ -0,0 +1,330 @@ +#define __INKSCAPE_CTRLRECT_C__ + +/* + * Simple non-transformed rectangle, usable for rubberband + * + * Author: + * Lauris Kaplinski + * bulia byak + * Carl Hetherington + * + * Copyright (C) 1999-2001 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL + * + */ + +#include "display-forward.h" +#include "sp-canvas-util.h" +#include "sodipodi-ctrlrect.h" + +/* + * Currently we do not have point method, as it should always be painted + * during some transformation, which takes care of events... + * + * Corner coords can be in any order - i.e. x1 < x0 is allowed + */ + +static void sp_ctrlrect_class_init(SPCtrlRectClass *c); +static void sp_ctrlrect_init(CtrlRect *ctrlrect); +static void sp_ctrlrect_destroy(GtkObject *object); + +static void sp_ctrlrect_update(SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags); +static void sp_ctrlrect_render(SPCanvasItem *item, SPCanvasBuf *buf); + +static SPCanvasItemClass *parent_class; + +static const guint DASH_LENGTH = 4; + +GtkType sp_ctrlrect_get_type() +{ + static GtkType ctrlrect_type = 0; + + if (!ctrlrect_type) { + GtkTypeInfo ctrlrect_info = { + "SPCtrlRect", + sizeof(CtrlRect), + sizeof(SPCtrlRectClass), + (GtkClassInitFunc) sp_ctrlrect_class_init, + (GtkObjectInitFunc) sp_ctrlrect_init, + NULL, NULL, NULL + }; + ctrlrect_type = gtk_type_unique(SP_TYPE_CANVAS_ITEM, &ctrlrect_info); + } + return ctrlrect_type; +} + +static void sp_ctrlrect_class_init(SPCtrlRectClass *c) +{ + GtkObjectClass *object_class = (GtkObjectClass *) c; + SPCanvasItemClass *item_class = (SPCanvasItemClass *) c; + + parent_class = (SPCanvasItemClass*) gtk_type_class(sp_canvas_item_get_type()); + + object_class->destroy = sp_ctrlrect_destroy; + + item_class->update = sp_ctrlrect_update; + item_class->render = sp_ctrlrect_render; +} + +static void sp_ctrlrect_init(CtrlRect *cr) +{ + cr->init(); +} + +static void sp_ctrlrect_destroy(GtkObject *object) +{ + if (GTK_OBJECT_CLASS(parent_class)->destroy) { + (* GTK_OBJECT_CLASS(parent_class)->destroy)(object); + } +} + +/* FIXME: use definitions from somewhere else */ +#define RGBA_R(v) ((v) >> 24) +#define RGBA_G(v) (((v) >> 16) & 0xff) +#define RGBA_B(v) (((v) >> 8) & 0xff) +#define RGBA_A(v) ((v) & 0xff) +#define COMPOSE(b,f,a) ( ( ((guchar) b) * ((guchar) (0xff - a)) + ((guchar) ((b ^ ~f) + b/4 - (b>127? 63 : 0))) * ((guchar) a) ) / 0xff ) + +static void sp_ctrlrect_hline(SPCanvasBuf *buf, gint y, gint xs, gint xe, guint32 rgba, guint dashed) +{ + if (y >= buf->rect.y0 && y < buf->rect.y1) { + guint const r = RGBA_R(rgba); + guint const g = RGBA_G(rgba); + guint const b = RGBA_B(rgba); + guint const a = RGBA_A(rgba); + gint const x0 = MAX(buf->rect.x0, xs); + gint const x1 = MIN(buf->rect.x1, xe + 1); + guchar *p = buf->buf + (y - buf->rect.y0) * buf->buf_rowstride + (x0 - buf->rect.x0) * 3; + for (gint x = x0; x < x1; x++) { + if (!dashed || ((x / DASH_LENGTH) % 2)) { + p[0] = COMPOSE(p[0], r, a); + p[1] = COMPOSE(p[1], g, a); + p[2] = COMPOSE(p[2], b, a); + } + p += 3; + } + } +} + +static void sp_ctrlrect_vline(SPCanvasBuf *buf, gint x, gint ys, gint ye, guint32 rgba, guint dashed) +{ + if (x >= buf->rect.x0 && x < buf->rect.x1) { + guint const r = RGBA_R(rgba); + guint const g = RGBA_G(rgba); + guint const b = RGBA_B(rgba); + guint const a = RGBA_A(rgba); + gint const y0 = MAX(buf->rect.y0, ys); + gint const y1 = MIN(buf->rect.y1, ye + 1); + guchar *p = buf->buf + (y0 - buf->rect.y0) * buf->buf_rowstride + (x - buf->rect.x0) * 3; + for (gint y = y0; y < y1; y++) { + if (!dashed || ((y / DASH_LENGTH) % 2)) { + p[0] = COMPOSE(p[0], r, a); + p[1] = COMPOSE(p[1], g, a); + p[2] = COMPOSE(p[2], b, a); + } + p += buf->buf_rowstride; + } + } +} + +/** Fills the pixels in [xs, xe)*[ys,ye) clipped to the tile with rgb * a. */ +static void sp_ctrlrect_area(SPCanvasBuf *buf, gint xs, gint ys, gint xe, gint ye, guint32 rgba) +{ + guint const r = RGBA_R(rgba); + guint const g = RGBA_G(rgba); + guint const b = RGBA_B(rgba); + guint const a = RGBA_A(rgba); + gint const x0 = MAX(buf->rect.x0, xs); + gint const x1 = MIN(buf->rect.x1, xe + 1); + gint const y0 = MAX(buf->rect.y0, ys); + gint const y1 = MIN(buf->rect.y1, ye + 1); + for (gint y = y0; y < y1; y++) { + guchar *p = buf->buf + (y - buf->rect.y0) * buf->buf_rowstride + (x0 - buf->rect.x0) * 3; + for (gint x = x0; x < x1; x++) { + p[0] = COMPOSE(p[0], r, a); + p[1] = COMPOSE(p[1], g, a); + p[2] = COMPOSE(p[2], b, a); + p += 3; + } + } +} + +static void sp_ctrlrect_render(SPCanvasItem *item, SPCanvasBuf *buf) +{ + SP_CTRLRECT(item)->render(buf); +} + + +static void sp_ctrlrect_update(SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags) +{ + SP_CTRLRECT(item)->update(affine, flags); +} + + + +void CtrlRect::init() +{ + _has_fill = false; + _dashed = false; + _shadow = 0; + + _area.x0 = _area.y0 = 0; + _area.x1 = _area.y1 = -1; + + _shadow_size = 0; + + _border_color = 0x000000ff; + _fill_color = 0xffffffff; + _shadow_color = 0x000000ff; +} + + +void CtrlRect::render(SPCanvasBuf *buf) +{ + if ((_area.x0 < buf->rect.x1) && + (_area.y0 < buf->rect.y1) && + ((_area.x1 + _shadow_size) >= buf->rect.x0) && + ((_area.y1 + _shadow_size) >= buf->rect.y0)) { + sp_canvas_prepare_buffer(buf); + + /* Top */ + sp_ctrlrect_hline(buf, _area.y0, _area.x0, _area.x1, _border_color, _dashed); + /* Bottom */ + sp_ctrlrect_hline(buf, _area.y1, _area.x0, _area.x1, _border_color, _dashed); + /* Left */ + sp_ctrlrect_vline(buf, _area.x0, _area.y0 + 1, _area.y1 - 1, _border_color, _dashed); + /* Right */ + sp_ctrlrect_vline(buf, _area.x1, _area.y0 + 1, _area.y1 - 1, _border_color, _dashed); + if (_shadow_size > 0) { + /* Right shadow */ + sp_ctrlrect_area(buf, _area.x1 + 1, _area.y0 + _shadow_size, + _area.x1 + _shadow_size, _area.y1 + _shadow_size, _shadow_color); + /* Bottom shadow */ + sp_ctrlrect_area(buf, _area.x0 + _shadow_size, _area.y1 + 1, + _area.x1, _area.y1 + _shadow_size, _shadow_color); + } + if (_has_fill) { + /* Fill */ + sp_ctrlrect_area(buf, _area.x0 + 1, _area.y0 + 1, + _area.x1 - 1, _area.y1 - 1, _fill_color); + } + } +} + + +void CtrlRect::update(NR::Matrix const &affine, unsigned int flags) +{ + if (((SPCanvasItemClass *) parent_class)->update) { + ((SPCanvasItemClass *) parent_class)->update(this, affine, flags); + } + + sp_canvas_item_reset_bounds(this); + + /* Request redraw old */ + if (!_has_fill) { + /* Top */ + sp_canvas_request_redraw(canvas, + _area.x0 - 1, _area.y0 - 1, + _area.x1 + 1, _area.y0 + 1); + /* Left */ + sp_canvas_request_redraw(canvas, + _area.x0 - 1, _area.y0 - 1, + _area.x0 + 1, _area.y1 + 1); + /* Right */ + sp_canvas_request_redraw(canvas, + _area.x1 - 1, _area.y0 - 1, + _area.x1 + _shadow_size + 1, _area.y1 + _shadow_size + 1); + /* Bottom */ + sp_canvas_request_redraw(canvas, + _area.x0 - 1, _area.y1 - 1, + _area.x1 + _shadow_size + 1, _area.y1 + _shadow_size + 1); + } else { + sp_canvas_request_redraw(canvas, + _area.x0 - 1, _area.y0 - 1, + _area.x1 + _shadow_size + 1, _area.y1 + _shadow_size + 1); + } + + NR::Rect bbox(_rect.min() * affine, _rect.max() * affine); + + _area.x0 = (int) floor(bbox.min()[NR::X] + 0.5); + _area.y0 = (int) floor(bbox.min()[NR::Y] + 0.5); + _area.x1 = (int) floor(bbox.max()[NR::X] + 0.5); + _area.y1 = (int) floor(bbox.max()[NR::Y] + 0.5); + + _shadow_size = _shadow; + + /* Request redraw new */ + if (!_has_fill) { + /* Top */ + sp_canvas_request_redraw(canvas, + _area.x0 - 1, _area.y0 - 1, + _area.x1 + 1, _area.y0 + 1); + /* Left */ + sp_canvas_request_redraw(canvas, + _area.x0 - 1, _area.y0 - 1, + _area.x0 + 1, _area.y1 + 1); + /* Right */ + sp_canvas_request_redraw(canvas, + _area.x1 - 1, _area.y0 - 1, + _area.x1 + _shadow_size + 1, _area.y1 + _shadow_size + 1); + /* Bottom */ + sp_canvas_request_redraw(canvas, + _area.x0 - 1, _area.y1 - 1, + _area.x1 + _shadow_size + 1, _area.y1 + _shadow_size + 1); + } else { + sp_canvas_request_redraw(canvas, + _area.x0 - 1, _area.y0 - 1, + _area.x1 + _shadow_size + 1, _area.y1 + _shadow_size + 1); + } + + x1 = _area.x0 - 1; + y1 = _area.y0 - 1; + x2 = _area.x1 + _shadow_size + 1; + y2 = _area.y1 + _shadow_size + 1; +} + + +void CtrlRect::setColor(guint32 b, bool h, guint f) +{ + _border_color = b; + _has_fill = h; + _fill_color = f; + _requestUpdate(); +} + +void CtrlRect::setShadow(int s, guint c) +{ + _shadow = s; + _shadow_color = c; + _requestUpdate(); +} + +void CtrlRect::setRectangle(NR::Rect const &r) +{ + _rect = r; + _requestUpdate(); +} + +void CtrlRect::setDashed(bool d) +{ + _dashed = d; + _requestUpdate(); +} + +void CtrlRect::_requestUpdate() +{ + sp_canvas_item_request_update(SP_CANVAS_ITEM(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:encoding=utf-8:textwidth=99 : diff --git a/src/display/sodipodi-ctrlrect.h b/src/display/sodipodi-ctrlrect.h new file mode 100644 index 000000000..dc931b7dc --- /dev/null +++ b/src/display/sodipodi-ctrlrect.h @@ -0,0 +1,70 @@ +#ifndef __INKSCAPE_CTRLRECT_H__ +#define __INKSCAPE_CTRLRECT_H__ + +/** + * \file sodipodi-ctrlrect.h + * \brief Simple non-transformed rectangle, usable for rubberband + * + * Authors: + * Lauris Kaplinski + * Carl Hetherington + * + * Copyright (C) 1999-2001 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL + * + */ + +#include +#include "sp-canvas.h" + +#define SP_TYPE_CTRLRECT (sp_ctrlrect_get_type ()) +#define SP_CTRLRECT(obj) (GTK_CHECK_CAST((obj), SP_TYPE_CTRLRECT, CtrlRect)) +#define SP_CTRLRECT_CLASS(c) (GTK_CHECK_CLASS_CAST((c), SP_TYPE_CTRLRECT, SPCtrlRectClass)) +#define SP_IS_CTRLRECT(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_CTRLRECT)) +#define SP_IS_CTRLRECT_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), SP_TYPE_CTRLRECT)) + +class CtrlRect : public SPCanvasItem +{ +public: + + void init(); + void setColor(guint32 b, bool h, guint f); + void setShadow(int s, guint c); + void setRectangle(NR::Rect const &r); + void setDashed(bool d); + + void render(SPCanvasBuf *buf); + void update(NR::Matrix const &affine, unsigned int flags); + +private: + void _requestUpdate(); + + NR::Rect _rect; + bool _has_fill; + bool _dashed; + NRRectL _area; + gint _shadow_size; + guint32 _border_color; + guint32 _fill_color; + guint32 _shadow_color; + int _shadow; +}; + +struct SPCtrlRectClass : public SPCanvasItemClass {}; + +GtkType sp_ctrlrect_get_type(); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/display/sp-canvas-util.cpp b/src/display/sp-canvas-util.cpp new file mode 100644 index 000000000..e4f2c7b75 --- /dev/null +++ b/src/display/sp-canvas-util.cpp @@ -0,0 +1,307 @@ +#define __SP_CANVAS_UTILS_C__ + +/* + * Helper stuff for SPCanvas + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + +#include "libnr/nr-matrix-div.h" +#include "libnr/nr-pixops.h" +#include "sp-canvas-util.h" + +#include +#include +#include + +void +sp_canvas_update_bbox (SPCanvasItem *item, int x1, int y1, int x2, int y2) +{ + sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2); + item->x1 = x1; + item->y1 = y1; + item->x2 = x2; + item->y2 = y2; + sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2); +} + +void +sp_canvas_item_reset_bounds (SPCanvasItem *item) +{ + item->x1 = 0.0; + item->y1 = 0.0; + item->x2 = 0.0; + item->y2 = 0.0; +} + +void +sp_canvas_prepare_buffer (SPCanvasBuf *buf) +{ + if (buf->is_empty) { + sp_canvas_clear_buffer(buf); + buf->is_empty = false; + } +} + +void +sp_canvas_clear_buffer (SPCanvasBuf *buf) +{ + unsigned char r, g, b; + + r = (buf->bg_color >> 16) & 0xff; + g = (buf->bg_color >> 8) & 0xff; + b = buf->bg_color & 0xff; + + if ((r != g) || (r != b)) { + int x, y; + for (y = buf->rect.y0; y < buf->rect.y1; y++) { + unsigned char *p; + p = buf->buf + (y - buf->rect.y0) * buf->buf_rowstride; + for (x = buf->rect.x0; x < buf->rect.x1; x++) { + *p++ = r; + *p++ = g; + *p++ = b; + } + } + } else { + int y; + for (y = buf->rect.y0; y < buf->rect.y1; y++) { + memset (buf->buf + (y - buf->rect.y0) * buf->buf_rowstride, r, 3 * (buf->rect.x1 - buf->rect.x0)); + } + } +} + +NR::Matrix sp_canvas_item_i2p_affine (SPCanvasItem * item) +{ + g_assert (item != NULL); // this may be overly zealous - it is + // plausible that this gets called + // with item == 0 + + return item->xform; +} + +NR::Matrix sp_canvas_item_i2i_affine (SPCanvasItem * from, SPCanvasItem * to) +{ + g_assert (from != NULL); + g_assert (to != NULL); + + return sp_canvas_item_i2w_affine(from) / sp_canvas_item_i2w_affine(to); +} + +void sp_canvas_item_set_i2w_affine (SPCanvasItem * item, NR::Matrix const &i2w) +{ + g_assert (item != NULL); + + sp_canvas_item_affine_absolute(item, i2w / sp_canvas_item_i2w_affine(item->parent)); +} + +void sp_canvas_item_move_to_z (SPCanvasItem * item, gint z) +{ + g_assert (item != NULL); + + gint current_z = sp_canvas_item_order (item); + + if (current_z == -1) // not found in its parent + return; + + if (z == current_z) + return; + + if (z > current_z) + sp_canvas_item_raise (item, z - current_z); + + sp_canvas_item_lower (item, current_z - z); +} + +gint +sp_canvas_item_compare_z (SPCanvasItem * a, SPCanvasItem * b) +{ + const gint o_a = sp_canvas_item_order (a); + const gint o_b = sp_canvas_item_order (b); + + if (o_a > o_b) return -1; + if (o_a < o_b) return 1; + + return 0; +} + +// These two functions are used by canvasitems that use livarot (currently ctrlline and ctrlquadr) + +void +ctrl_run_A8_OR (raster_info &dest,void *data,int st,float vst,int en,float ven) +{ + union { + uint8_t comp[4]; + uint32_t col; + } tempCol; + if ( st >= en ) return; + tempCol.col=*(uint32_t*)data; + + unsigned int r, g, b, a; + r = NR_RGBA32_R (tempCol.col); + g = NR_RGBA32_G (tempCol.col); + b = NR_RGBA32_B (tempCol.col); + a = NR_RGBA32_A (tempCol.col); + if (a == 0) return; + + vst*=a; + ven*=a; + + if ( vst < 0 ) vst=0; + if ( vst > 255 ) vst=255; + if ( ven < 0 ) ven=0; + if ( ven > 255 ) ven=255; + float sv=vst; + float dv=ven-vst; + int len=en-st; + uint8_t* d=(uint8_t*)dest.buffer; + + d+=3*(st-dest.startPix); + if ( fabs(dv) < 0.001 ) { + if ( sv > 249.999 ) { + /* Simple copy */ + while (len > 0) { + d[0] = INK_COMPOSE (r, 255, d[0]); + d[1] = INK_COMPOSE (g, 255, d[1]); + d[2] = INK_COMPOSE (b, 255, d[2]); + d += 3; + len -= 1; + } + } else { + unsigned int c0_24=(int)sv; + c0_24&=0xFF; + while (len > 0) { + d[0] = INK_COMPOSE (r, c0_24, d[0]); + d[1] = INK_COMPOSE (g, c0_24, d[1]); + d[2] = INK_COMPOSE (b, c0_24, d[2]); + d += 3; + len -= 1; + } + } + } else { + if ( en <= st+1 ) { + sv=0.5*(vst+ven); + unsigned int c0_24=(int)sv; + c0_24&=0xFF; + d[0] = INK_COMPOSE (r, c0_24, d[0]); + d[1] = INK_COMPOSE (g, c0_24, d[1]); + d[2] = INK_COMPOSE (b, c0_24, d[2]); + } else { + dv/=len; + sv+=0.5*dv; // correction trapezoidale + sv*=65536; + dv*=65536; + int c0_24 = static_cast(CLAMP(sv, 0, 16777216)); + int s0_24 = static_cast(dv); + while (len > 0) { + unsigned int ca; + /* Draw */ + ca = c0_24 >> 16; + if ( ca > 255 ) ca=255; + d[0] = INK_COMPOSE (r, ca, d[0]); + d[1] = INK_COMPOSE (g, ca, d[1]); + d[2] = INK_COMPOSE (b, ca, d[2]); + d += 3; + c0_24 += s0_24; + c0_24 = CLAMP (c0_24, 0, 16777216); + len -= 1; + } + } + } +} + +void nr_pixblock_render_ctrl_rgba (Shape* theS,uint32_t color,NRRectL &area,char* destBuf,int stride) +{ + + theS->CalcBBox(); + float l=theS->leftX,r=theS->rightX,t=theS->topY,b=theS->bottomY; + int il,ir,it,ib; + il=(int)floor(l); + ir=(int)ceil(r); + it=(int)floor(t); + ib=(int)ceil(b); + +// printf("bbox %i %i %i %i render %i %i %i %i\n",il,it,ir,ib,area.x0,area.y0,area.x1,area.y1); + + if ( il >= area.x1 || ir <= area.x0 || it >= area.y1 || ib <= area.y0 ) return; + if ( il < area.x0 ) il=area.x0; + if ( it < area.y0 ) it=area.y0; + if ( ir > area.x1 ) ir=area.x1; + if ( ib > area.y1 ) ib=area.y1; + +/* // version par FloatLigne + int curPt; + float curY; + theS->BeginRaster(curY,curPt,1.0); + + FloatLigne* theI=new FloatLigne(); + IntLigne* theIL=new IntLigne(); + + theS->Scan(curY,curPt,(float)(it),1.0); + + char* mdata=(char*)destBuf; + uint32_t* ligStart=((uint32_t*)(mdata+(3*(il-area.x0)+stride*(it-area.y0)))); + for (int y=it;yReset(); + if ( y&0x00000003 ) { + theS->Scan(curY,curPt,((float)(y+1)),theI,false,1.0); + } else { + theS->Scan(curY,curPt,((float)(y+1)),theI,true,1.0); + } + theI->Flatten(); + theIL->Copy(theI); + + raster_info dest; + dest.startPix=il; + dest.endPix=ir; + dest.sth=il; + dest.stv=y; + dest.buffer=ligStart; + theIL->Raster(dest,&color,bpath_run_A8_OR); + ligStart=((uint32_t*)(((char*)ligStart)+stride)); + } + theS->EndRaster(); + delete theI; + delete theIL; */ + + // version par BitLigne directe + int curPt; + float curY; + theS->BeginQuickRaster(curY, curPt); + + BitLigne* theI[4]; + for (int i=0;i<4;i++) theI[i]=new BitLigne(il,ir); + IntLigne* theIL=new IntLigne(); + + theS->QuickScan(curY,curPt,(float)(it),true,0.25); + + char* mdata=(char*)destBuf; + uint32_t* ligStart=((uint32_t*)(mdata+(3*(il-area.x0)+stride*(it-area.y0)))); + for (int y=it;yReset(); + theS->QuickScan(curY,curPt,((float)(y+0.25)),fill_oddEven,theI[0],0.25); + theS->QuickScan(curY,curPt,((float)(y+0.5)),fill_oddEven,theI[1],0.25); + theS->QuickScan(curY,curPt,((float)(y+0.75)),fill_oddEven,theI[2],0.25); + theS->QuickScan(curY,curPt,((float)(y+1.0)),fill_oddEven,theI[3],0.25); + theIL->Copy(4,theI); + + raster_info dest; + dest.startPix=il; + dest.endPix=ir; + dest.sth=il; + dest.stv=y; + dest.buffer=ligStart; + theIL->Raster(dest,&color,ctrl_run_A8_OR); + ligStart=((uint32_t*)(((char*)ligStart)+stride)); + } + theS->EndQuickRaster(); + for (int i=0;i<4;i++) delete theI[i]; + delete theIL; +} diff --git a/src/display/sp-canvas-util.h b/src/display/sp-canvas-util.h new file mode 100644 index 000000000..b592ba1d0 --- /dev/null +++ b/src/display/sp-canvas-util.h @@ -0,0 +1,61 @@ +#ifndef __SP_CANVAS_UTILS_H__ +#define __SP_CANVAS_UTILS_H__ + +/* + * Helper stuff for SPCanvas + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-canvas.h" + +/* Miscellaneous utility & convenience functions for general canvas objects */ + +void sp_canvas_update_bbox (SPCanvasItem *item, int x1, int y1, int x2, int y2); +void sp_canvas_item_reset_bounds (SPCanvasItem *item); +void sp_canvas_prepare_buffer (SPCanvasBuf *buf); + +/* fill buffer with background color */ + +void +sp_canvas_clear_buffer (SPCanvasBuf * buf); + +/* get i2p (item to parent) affine transformation as general 6-element array */ + +NR::Matrix sp_canvas_item_i2p_affine (SPCanvasItem * item); + +/* get i2i (item to item) affine transformation as general 6-element array */ + +NR::Matrix sp_canvas_item_i2i_affine (SPCanvasItem * from, SPCanvasItem * to); + +/* set item affine matrix to achieve given i2w matrix */ + +void sp_canvas_item_set_i2w_affine (SPCanvasItem * item, NR::Matrix const & aff); + +void sp_canvas_item_move_to_z (SPCanvasItem * item, gint z); + +gint sp_canvas_item_compare_z (SPCanvasItem * a, SPCanvasItem * b); + +class Shape; +class raster_info; +void ctrl_run_A8_OR (raster_info &dest, void *data, int st, float vst, int en, float ven); +void nr_pixblock_render_ctrl_rgba (Shape* theS, uint32_t color, NRRectL &area, char* destBuf, int stride); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/display/sp-canvas.cpp b/src/display/sp-canvas.cpp new file mode 100644 index 000000000..d1d7221f0 --- /dev/null +++ b/src/display/sp-canvas.cpp @@ -0,0 +1,2074 @@ +#define __SP_CANVAS_C__ + +/** \file + * Port of GnomeCanvas for Inkscape needs + * + * Authors: + * Federico Mena + * Raph Levien + * Lauris Kaplinski + * fred + * + * Copyright (C) 1998 The Free Software Foundation + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include +#include + +#include +#include +#include "display-forward.h" +#include +#include +#include + +enum { + RENDERMODE_NORMAL, + RENDERMODE_NOAA, + RENDERMODE_OUTLINE +}; + +const gint sp_canvas_update_priority = G_PRIORITY_HIGH_IDLE; + +#define SP_CANVAS_WINDOW(c) (((GtkWidget *) (c))->window) + +enum { + SP_CANVAS_ITEM_VISIBLE = 1 << 7, + SP_CANVAS_ITEM_NEED_UPDATE = 1 << 8, + SP_CANVAS_ITEM_NEED_AFFINE = 1 << 9 +}; + +/** + * A group of Items. + */ +struct SPCanvasGroup { + SPCanvasItem item; + + GList *items, *last; +}; + +/** + * The SPCanvasGroup vtable. + */ +struct SPCanvasGroupClass { + SPCanvasItemClass parent_class; +}; + +/** + * The SPCanvas vtable. + */ +struct SPCanvasClass { + GtkWidgetClass parent_class; +}; + +static void group_add (SPCanvasGroup *group, SPCanvasItem *item); +static void group_remove (SPCanvasGroup *group, SPCanvasItem *item); + +/* SPCanvasItem */ + +enum {ITEM_EVENT, ITEM_LAST_SIGNAL}; + + +static void sp_canvas_request_update (SPCanvas *canvas); + +static void sp_canvas_item_class_init (SPCanvasItemClass *klass); +static void sp_canvas_item_init (SPCanvasItem *item); +static void sp_canvas_item_dispose (GObject *object); +static void sp_canvas_item_construct (SPCanvasItem *item, SPCanvasGroup *parent, const gchar *first_arg_name, va_list args); + +static int emit_event (SPCanvas *canvas, GdkEvent *event); + +static guint item_signals[ITEM_LAST_SIGNAL] = { 0 }; + +static GtkObjectClass *item_parent_class; + +/** + * Registers the SPCanvasItem class with Glib and returns its type number. + */ +GType +sp_canvas_item_get_type (void) +{ + static GType type = 0; + if (!type) { + static const GTypeInfo info = { + sizeof (SPCanvasItemClass), + NULL, NULL, + (GClassInitFunc) sp_canvas_item_class_init, + NULL, NULL, + sizeof (SPCanvasItem), + 0, + (GInstanceInitFunc) sp_canvas_item_init, + NULL + }; + type = g_type_register_static (GTK_TYPE_OBJECT, "SPCanvasItem", &info, (GTypeFlags)0); + } + + return type; +} + +/** + * Initializes the SPCanvasItem vtable and the "event" signal. + */ +static void +sp_canvas_item_class_init (SPCanvasItemClass *klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + + /* fixme: Derive from GObject */ + item_parent_class = (GtkObjectClass*)gtk_type_class (GTK_TYPE_OBJECT); + + item_signals[ITEM_EVENT] = g_signal_new ("event", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (SPCanvasItemClass, event), + NULL, NULL, + sp_marshal_BOOLEAN__POINTER, + G_TYPE_BOOLEAN, 1, + GDK_TYPE_EVENT); + + object_class->dispose = sp_canvas_item_dispose; +} + +/** + * Callback for initialization of SPCanvasItem. + */ +static void +sp_canvas_item_init (SPCanvasItem *item) +{ + item->flags |= SP_CANVAS_ITEM_VISIBLE; + item->xform = NR::Matrix(NR::identity()); +} + +/** + * Constructs new SPCanvasItem on SPCanvasGroup. + */ +SPCanvasItem * +sp_canvas_item_new (SPCanvasGroup *parent, GtkType type, const gchar *first_arg_name, ...) +{ + va_list args; + + g_return_val_if_fail (parent != NULL, NULL); + g_return_val_if_fail (SP_IS_CANVAS_GROUP (parent), NULL); + g_return_val_if_fail (gtk_type_is_a (type, sp_canvas_item_get_type ()), NULL); + + SPCanvasItem *item = SP_CANVAS_ITEM (gtk_type_new (type)); + + va_start (args, first_arg_name); + sp_canvas_item_construct (item, parent, first_arg_name, args); + va_end (args); + + return item; +} + +/** + * Sets up the newly created SPCanvasItem. + * + * We make it static for encapsulation reasons since it was nowhere used. + */ +static void +sp_canvas_item_construct (SPCanvasItem *item, SPCanvasGroup *parent, const gchar *first_arg_name, va_list args) +{ + g_return_if_fail (SP_IS_CANVAS_GROUP (parent)); + g_return_if_fail (SP_IS_CANVAS_ITEM (item)); + + item->parent = SP_CANVAS_ITEM (parent); + item->canvas = item->parent->canvas; + + g_object_set_valist (G_OBJECT (item), first_arg_name, args); + + group_add (SP_CANVAS_GROUP (item->parent), item); + + sp_canvas_item_request_update (item); + sp_canvas_request_redraw (item->canvas, (int)(item->x1), (int)(item->y1), (int)(item->x2 + 1), (int)(item->y2 + 1)); + item->canvas->need_repick = TRUE; +} + +/** + * Helper function that requests redraw only if item's visible flag is set. + */ +static void +redraw_if_visible (SPCanvasItem *item) +{ + if (item->flags & SP_CANVAS_ITEM_VISIBLE) { + sp_canvas_request_redraw (item->canvas, (int)(item->x1), (int)(item->y1), (int)(item->x2 + 1), (int)(item->y2 + 1)); + } +} + +/** + * Callback that removes item from all referers and destroys it. + */ +static void +sp_canvas_item_dispose (GObject *object) +{ + SPCanvasItem *item = SP_CANVAS_ITEM (object); + + redraw_if_visible (item); + item->flags &= ~SP_CANVAS_ITEM_VISIBLE; + + if (item == item->canvas->current_item) { + item->canvas->current_item = NULL; + item->canvas->need_repick = TRUE; + } + + if (item == item->canvas->new_current_item) { + item->canvas->new_current_item = NULL; + item->canvas->need_repick = TRUE; + } + + if (item == item->canvas->grabbed_item) { + item->canvas->grabbed_item = NULL; + gdk_pointer_ungrab (GDK_CURRENT_TIME); + } + + if (item == item->canvas->focused_item) + item->canvas->focused_item = NULL; + + if (item->parent) { + group_remove (SP_CANVAS_GROUP (item->parent), item); + } + + G_OBJECT_CLASS (item_parent_class)->dispose (object); +} + +/** + * Helper function to update item and its children. + * + * NB! affine is parent2canvas. + */ +static void +sp_canvas_item_invoke_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags) +{ + /* Apply the child item's transform */ + NR::Matrix child_affine = item->xform * affine; + + /* apply object flags to child flags */ + int child_flags = flags & ~SP_CANVAS_UPDATE_REQUESTED; + + if (item->flags & SP_CANVAS_ITEM_NEED_UPDATE) + child_flags |= SP_CANVAS_UPDATE_REQUESTED; + + if (item->flags & SP_CANVAS_ITEM_NEED_AFFINE) + child_flags |= SP_CANVAS_UPDATE_AFFINE; + + if (child_flags & (SP_CANVAS_UPDATE_REQUESTED | SP_CANVAS_UPDATE_AFFINE)) { + if (SP_CANVAS_ITEM_GET_CLASS (item)->update) + SP_CANVAS_ITEM_GET_CLASS (item)->update (item, child_affine, child_flags); + } + + GTK_OBJECT_UNSET_FLAGS (item, SP_CANVAS_ITEM_NEED_UPDATE); + GTK_OBJECT_UNSET_FLAGS (item, SP_CANVAS_ITEM_NEED_AFFINE); +} + +/** + * Helper function to invoke the point method of the item. + * + * The argument x, y should be in the parent's item-relative coordinate + * system. This routine applies the inverse of the item's transform, + * maintaining the affine invariant. + */ +static double +sp_canvas_item_invoke_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item) +{ + if (SP_CANVAS_ITEM_GET_CLASS (item)->point) + return SP_CANVAS_ITEM_GET_CLASS (item)->point (item, p, actual_item); + + return NR_HUGE; +} + +/** + * Makes the item's affine transformation matrix be equal to the specified + * matrix. + * + * @item: A canvas item. + * @affine: An affine transformation matrix. + */ +void +sp_canvas_item_affine_absolute (SPCanvasItem *item, NR::Matrix const& affine) +{ + item->xform = affine; + + if (!(item->flags & SP_CANVAS_ITEM_NEED_AFFINE)) { + item->flags |= SP_CANVAS_ITEM_NEED_AFFINE; + if (item->parent != NULL) { + sp_canvas_item_request_update (item->parent); + } else { + sp_canvas_request_update (item->canvas); + } + } + + item->canvas->need_repick = TRUE; +} + +/** + * Convenience function to reorder items in a group's child list. + * + * This puts the specified link after the "before" link. + */ +static void +put_item_after (GList *link, GList *before) +{ + if (link == before) + return; + + SPCanvasGroup *parent = SP_CANVAS_GROUP (SP_CANVAS_ITEM (link->data)->parent); + + if (before == NULL) { + if (link == parent->items) return; + + link->prev->next = link->next; + + if (link->next) { + link->next->prev = link->prev; + } else { + parent->last = link->prev; + } + + link->prev = before; + link->next = parent->items; + link->next->prev = link; + parent->items = link; + } else { + if ((link == parent->last) && (before == parent->last->prev)) + return; + + if (link->next) + link->next->prev = link->prev; + + if (link->prev) + link->prev->next = link->next; + else { + parent->items = link->next; + parent->items->prev = NULL; + } + + link->prev = before; + link->next = before->next; + + link->prev->next = link; + + if (link->next) + link->next->prev = link; + else + parent->last = link; + } +} + + +/** + * Raises the item in its parent's stack by the specified number of positions. + * + * \param item A canvas item. + * \param positions Number of steps to raise the item. + * + * If the number of positions is greater than the distance to the top of the + * stack, then the item is put at the top. + */ +void +sp_canvas_item_raise (SPCanvasItem *item, int positions) +{ + g_return_if_fail (item != NULL); + g_return_if_fail (SP_IS_CANVAS_ITEM (item)); + g_return_if_fail (positions >= 0); + + if (!item->parent || positions == 0) + return; + + SPCanvasGroup *parent = SP_CANVAS_GROUP (item->parent); + GList *link = g_list_find (parent->items, item); + g_assert (link != NULL); + + GList *before; + for (before = link; positions && before; positions--) + before = before->next; + + if (!before) + before = parent->last; + + put_item_after (link, before); + + redraw_if_visible (item); + item->canvas->need_repick = TRUE; +} + + +/** + * Lowers the item in its parent's stack by the specified number of positions. + * + * \param item A canvas item. + * \param positions Number of steps to lower the item. + * + * If the number of positions is greater than the distance to the bottom of the + * stack, then the item is put at the bottom. + **/ +void +sp_canvas_item_lower (SPCanvasItem *item, int positions) +{ + g_return_if_fail (item != NULL); + g_return_if_fail (SP_IS_CANVAS_ITEM (item)); + g_return_if_fail (positions >= 1); + + if (!item->parent || positions == 0) + return; + + SPCanvasGroup *parent = SP_CANVAS_GROUP (item->parent); + GList *link = g_list_find (parent->items, item); + g_assert (link != NULL); + + GList *before; + if (link->prev) + for (before = link->prev; positions && before; positions--) + before = before->prev; + else + before = NULL; + + put_item_after (link, before); + + redraw_if_visible (item); + item->canvas->need_repick = TRUE; +} + +/** + * Sets visible flag on item and requests a redraw. + */ +void +sp_canvas_item_show (SPCanvasItem *item) +{ + g_return_if_fail (item != NULL); + g_return_if_fail (SP_IS_CANVAS_ITEM (item)); + + if (item->flags & SP_CANVAS_ITEM_VISIBLE) + return; + + item->flags |= SP_CANVAS_ITEM_VISIBLE; + + sp_canvas_request_redraw (item->canvas, (int)(item->x1), (int)(item->y1), (int)(item->x2 + 1), (int)(item->y2 + 1)); + item->canvas->need_repick = TRUE; +} + +/** + * Clears visible flag on item and requests a redraw. + */ +void +sp_canvas_item_hide (SPCanvasItem *item) +{ + g_return_if_fail (item != NULL); + g_return_if_fail (SP_IS_CANVAS_ITEM (item)); + + if (!(item->flags & SP_CANVAS_ITEM_VISIBLE)) + return; + + item->flags &= ~SP_CANVAS_ITEM_VISIBLE; + + sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)(item->x2 + 1), (int)(item->y2 + 1)); + item->canvas->need_repick = TRUE; +} + +/** + * Grab item under cursor. + * + * \pre !canvas->grabbed_item && item->flags & SP_CANVAS_ITEM_VISIBLE + */ +int +sp_canvas_item_grab (SPCanvasItem *item, guint event_mask, GdkCursor *cursor, guint32 etime) +{ + g_return_val_if_fail (item != NULL, -1); + g_return_val_if_fail (SP_IS_CANVAS_ITEM (item), -1); + g_return_val_if_fail (GTK_WIDGET_MAPPED (item->canvas), -1); + + if (item->canvas->grabbed_item) + return -1; + + if (!(item->flags & SP_CANVAS_ITEM_VISIBLE)) + return -1; + + /* fixme: Top hack (Lauris) */ + /* fixme: If we add key masks to event mask, Gdk will abort (Lauris) */ + /* fixme: But Canvas actualle does get key events, so all we need is routing these here */ + gdk_pointer_grab (SP_CANVAS_WINDOW (item->canvas), FALSE, + (GdkEventMask)(event_mask & (~(GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK))), + NULL, cursor, etime); + + item->canvas->grabbed_item = item; + item->canvas->grabbed_event_mask = event_mask; + item->canvas->current_item = item; /* So that events go to the grabbed item */ + + return 0; +} + +/** + * Ungrabs the item, which must have been grabbed in the canvas, and ungrabs the + * mouse. + * + * \param item A canvas item that holds a grab. + * \param etime The timestamp for ungrabbing the mouse. + */ +void +sp_canvas_item_ungrab (SPCanvasItem *item, guint32 etime) +{ + g_return_if_fail (item != NULL); + g_return_if_fail (SP_IS_CANVAS_ITEM (item)); + + if (item->canvas->grabbed_item != item) + return; + + item->canvas->grabbed_item = NULL; + + gdk_pointer_ungrab (etime); +} + +/** + * Returns the product of all transformation matrices from the root item down + * to the item. + */ +NR::Matrix sp_canvas_item_i2w_affine(SPCanvasItem const *item) +{ + g_assert (SP_IS_CANVAS_ITEM (item)); // should we get this? + + NR::Matrix affine = NR::identity(); + + while (item) { + affine *= item->xform; + item = item->parent; + } + return affine; +} + +/** + * Helper that returns true iff item is descendant of parent. + */ +static bool is_descendant(SPCanvasItem const *item, SPCanvasItem const *parent) +{ + while (item) { + if (item == parent) + return true; + item = item->parent; + } + + return false; +} + +/** + * Focus canvas, and item under cursor if it is not already focussed. + */ +void +sp_canvas_item_grab_focus (SPCanvasItem *item) +{ + g_return_if_fail (item != NULL); + g_return_if_fail (SP_IS_CANVAS_ITEM (item)); + g_return_if_fail (GTK_WIDGET_CAN_FOCUS (GTK_WIDGET (item->canvas))); + + SPCanvasItem *focused_item = item->canvas->focused_item; + + if (focused_item) { + GdkEvent ev; + ev.focus_change.type = GDK_FOCUS_CHANGE; + ev.focus_change.window = SP_CANVAS_WINDOW (item->canvas); + ev.focus_change.send_event = FALSE; + ev.focus_change.in = FALSE; + + emit_event (item->canvas, &ev); + } + + item->canvas->focused_item = item; + gtk_widget_grab_focus (GTK_WIDGET (item->canvas)); + + if (focused_item) { + GdkEvent ev; + ev.focus_change.type = GDK_FOCUS_CHANGE; + ev.focus_change.window = SP_CANVAS_WINDOW (item->canvas); + ev.focus_change.send_event = FALSE; + ev.focus_change.in = TRUE; + + emit_event (item->canvas, &ev); + } +} + +/** + * Requests that the canvas queue an update for the specified item. + * + * To be used only by item implementations. + */ +void +sp_canvas_item_request_update (SPCanvasItem *item) +{ + if (item->flags & SP_CANVAS_ITEM_NEED_UPDATE) + return; + + item->flags |= SP_CANVAS_ITEM_NEED_UPDATE; + + if (item->parent != NULL) { + /* Recurse up the tree */ + sp_canvas_item_request_update (item->parent); + } else { + /* Have reached the top of the tree, make sure the update call gets scheduled. */ + sp_canvas_request_update (item->canvas); + } +} + +/** + * Returns position of item in group. + */ +gint sp_canvas_item_order (SPCanvasItem * item) +{ + return g_list_index (SP_CANVAS_GROUP (item->parent)->items, item); +} + +/* SPCanvasGroup */ + +static void sp_canvas_group_class_init (SPCanvasGroupClass *klass); +static void sp_canvas_group_init (SPCanvasGroup *group); +static void sp_canvas_group_destroy (GtkObject *object); + +static void sp_canvas_group_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags); +static double sp_canvas_group_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item); +static void sp_canvas_group_render (SPCanvasItem *item, SPCanvasBuf *buf); + +static SPCanvasItemClass *group_parent_class; + +/** + * Registers SPCanvasGroup class with Gtk and returns its type number. + */ +GtkType +sp_canvas_group_get_type (void) +{ + static GtkType group_type = 0; + + if (!group_type) { + static const GtkTypeInfo group_info = { + "SPCanvasGroup", + sizeof (SPCanvasGroup), + sizeof (SPCanvasGroupClass), + (GtkClassInitFunc) sp_canvas_group_class_init, + (GtkObjectInitFunc) sp_canvas_group_init, + NULL, NULL, NULL + }; + + group_type = gtk_type_unique (sp_canvas_item_get_type (), &group_info); + } + + return group_type; +} + +/** + * Class initialization function for SPCanvasGroupClass + */ +static void +sp_canvas_group_class_init (SPCanvasGroupClass *klass) +{ + GtkObjectClass *object_class = (GtkObjectClass *) klass; + SPCanvasItemClass *item_class = (SPCanvasItemClass *) klass; + + group_parent_class = (SPCanvasItemClass*)gtk_type_class (sp_canvas_item_get_type ()); + + object_class->destroy = sp_canvas_group_destroy; + + item_class->update = sp_canvas_group_update; + item_class->render = sp_canvas_group_render; + item_class->point = sp_canvas_group_point; +} + +/** + * Callback. Empty. + */ +static void +sp_canvas_group_init (SPCanvasGroup */*group*/) +{ + /* Nothing here */ +} + +/** + * Callback that destroys all items in group and calls group's virtual + * destroy() function. + */ +static void +sp_canvas_group_destroy (GtkObject *object) +{ + g_return_if_fail (object != NULL); + g_return_if_fail (SP_IS_CANVAS_GROUP (object)); + + const SPCanvasGroup *group = SP_CANVAS_GROUP (object); + + GList *list = group->items; + while (list) { + SPCanvasItem *child = (SPCanvasItem *)list->data; + list = list->next; + + gtk_object_destroy (GTK_OBJECT (child)); + } + + if (GTK_OBJECT_CLASS (group_parent_class)->destroy) + (* GTK_OBJECT_CLASS (group_parent_class)->destroy) (object); +} + +/** + * Update handler for canvas groups + */ +static void +sp_canvas_group_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags) +{ + const SPCanvasGroup *group = SP_CANVAS_GROUP (item); + NR::ConvexHull corners(NR::Point(0, 0)); + bool empty=true; + + for (GList *list = group->items; list; list = list->next) { + SPCanvasItem *i = (SPCanvasItem *)list->data; + + sp_canvas_item_invoke_update (i, affine, flags); + + if ( i->x2 > i->x1 && i->y2 > i->y1 ) { + if (empty) { + corners = NR::ConvexHull(NR::Point(i->x1, i->y1)); + empty = false; + } else { + corners.add(NR::Point(i->x1, i->y1)); + } + corners.add(NR::Point(i->x2, i->y2)); + } + } + + NR::Rect const &bounds = corners.bounds(); + item->x1 = bounds.min()[NR::X]; + item->y1 = bounds.min()[NR::Y]; + item->x2 = bounds.max()[NR::X]; + item->y2 = bounds.max()[NR::Y]; +} + +/** + * Point handler for canvas groups. + */ +static double +sp_canvas_group_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item) +{ + const SPCanvasGroup *group = SP_CANVAS_GROUP (item); + const double x = p[NR::X]; + const double y = p[NR::Y]; + int x1 = (int)(x - item->canvas->close_enough); + int y1 = (int)(y - item->canvas->close_enough); + int x2 = (int)(x + item->canvas->close_enough); + int y2 = (int)(y + item->canvas->close_enough); + + double best = 0.0; + *actual_item = NULL; + + double dist = 0.0; + + for (GList *list = group->items; list; list = list->next) { + SPCanvasItem *child = (SPCanvasItem *)list->data; + + if ((child->x1 <= x2) && (child->y1 <= y2) && (child->x2 >= x1) && (child->y2 >= y1)) { + SPCanvasItem *point_item = NULL; /* cater for incomplete item implementations */ + + int has_point; + if ((child->flags & SP_CANVAS_ITEM_VISIBLE) && SP_CANVAS_ITEM_GET_CLASS (child)->point) { + dist = sp_canvas_item_invoke_point (child, p, &point_item); + has_point = TRUE; + } else + has_point = FALSE; + + if (has_point && point_item && ((int) (dist + 0.5) <= item->canvas->close_enough)) { + best = dist; + *actual_item = point_item; + } + } + } + + return best; +} + +/** + * Renders all visible canvas group items in buf rectangle. + */ +static void +sp_canvas_group_render (SPCanvasItem *item, SPCanvasBuf *buf) +{ + const SPCanvasGroup *group = SP_CANVAS_GROUP (item); + + for (GList *list = group->items; list; list = list->next) { + SPCanvasItem *child = (SPCanvasItem *)list->data; + if (child->flags & SP_CANVAS_ITEM_VISIBLE) { + if ((child->x1 < buf->rect.x1) && + (child->y1 < buf->rect.y1) && + (child->x2 > buf->rect.x0) && + (child->y2 > buf->rect.y0)) { + if (SP_CANVAS_ITEM_GET_CLASS (child)->render) + SP_CANVAS_ITEM_GET_CLASS (child)->render (child, buf); + } + } + } +} + +/** + * Adds an item to a canvas group. + */ +static void +group_add (SPCanvasGroup *group, SPCanvasItem *item) +{ + gtk_object_ref (GTK_OBJECT (item)); + gtk_object_sink (GTK_OBJECT (item)); + + if (!group->items) { + group->items = g_list_append (group->items, item); + group->last = group->items; + } else { + group->last = g_list_append (group->last, item)->next; + } + + sp_canvas_item_request_update (item); +} + +/** + * Removes an item from a canvas group + */ +static void +group_remove (SPCanvasGroup *group, SPCanvasItem *item) +{ + g_return_if_fail (group != NULL); + g_return_if_fail (SP_IS_CANVAS_GROUP (group)); + g_return_if_fail (item != NULL); + + for (GList *children = group->items; children; children = children->next) { + if (children->data == item) { + + /* Unparent the child */ + + item->parent = NULL; + gtk_object_unref (GTK_OBJECT (item)); + + /* Remove it from the list */ + + if (children == group->last) group->last = children->prev; + + group->items = g_list_remove_link (group->items, children); + g_list_free (children); + break; + } + } +} + +/* SPCanvas */ + +static void sp_canvas_class_init (SPCanvasClass *klass); +static void sp_canvas_init (SPCanvas *canvas); +static void sp_canvas_destroy (GtkObject *object); + +static void sp_canvas_realize (GtkWidget *widget); +static void sp_canvas_unrealize (GtkWidget *widget); + +static void sp_canvas_size_request (GtkWidget *widget, GtkRequisition *req); +static void sp_canvas_size_allocate (GtkWidget *widget, GtkAllocation *allocation); + +static gint sp_canvas_button (GtkWidget *widget, GdkEventButton *event); +static gint sp_canvas_scroll (GtkWidget *widget, GdkEventScroll *event); +static gint sp_canvas_motion (GtkWidget *widget, GdkEventMotion *event); +static gint sp_canvas_expose (GtkWidget *widget, GdkEventExpose *event); +static gint sp_canvas_key (GtkWidget *widget, GdkEventKey *event); +static gint sp_canvas_crossing (GtkWidget *widget, GdkEventCrossing *event); +static gint sp_canvas_focus_in (GtkWidget *widget, GdkEventFocus *event); +static gint sp_canvas_focus_out (GtkWidget *widget, GdkEventFocus *event); + +static GtkWidgetClass *canvas_parent_class; + +void sp_canvas_resize_tiles(SPCanvas* canvas,int nl,int nt,int nr,int nb); +void sp_canvas_dirty_rect(SPCanvas* canvas,int nl,int nt,int nr,int nb); + +/** + * Registers the SPCanvas class if necessary, and returns the type ID + * associated to it. + * + * \return The type ID of the SPCanvas class. + **/ +GtkType +sp_canvas_get_type (void) +{ + static GtkType canvas_type = 0; + + if (!canvas_type) { + static const GtkTypeInfo canvas_info = { + "SPCanvas", + sizeof (SPCanvas), + sizeof (SPCanvasClass), + (GtkClassInitFunc) sp_canvas_class_init, + (GtkObjectInitFunc) sp_canvas_init, + NULL, NULL, NULL + }; + + canvas_type = gtk_type_unique (GTK_TYPE_WIDGET, &canvas_info); + } + + return canvas_type; +} + +/** + * Class initialization function for SPCanvasClass. + */ +static void +sp_canvas_class_init (SPCanvasClass *klass) +{ + GtkObjectClass *object_class = (GtkObjectClass *) klass; + GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; + + canvas_parent_class = (GtkWidgetClass *)gtk_type_class (GTK_TYPE_WIDGET); + + object_class->destroy = sp_canvas_destroy; + + widget_class->realize = sp_canvas_realize; + widget_class->unrealize = sp_canvas_unrealize; + widget_class->size_request = sp_canvas_size_request; + widget_class->size_allocate = sp_canvas_size_allocate; + widget_class->button_press_event = sp_canvas_button; + widget_class->button_release_event = sp_canvas_button; + widget_class->motion_notify_event = sp_canvas_motion; + widget_class->scroll_event = sp_canvas_scroll; + widget_class->expose_event = sp_canvas_expose; + widget_class->key_press_event = sp_canvas_key; + widget_class->key_release_event = sp_canvas_key; + widget_class->enter_notify_event = sp_canvas_crossing; + widget_class->leave_notify_event = sp_canvas_crossing; + widget_class->focus_in_event = sp_canvas_focus_in; + widget_class->focus_out_event = sp_canvas_focus_out; +} + +/** + * Callback: object initialization for SPCanvas. + */ +static void +sp_canvas_init (SPCanvas *canvas) +{ + GTK_WIDGET_UNSET_FLAGS (canvas, GTK_NO_WINDOW); + GTK_WIDGET_UNSET_FLAGS (canvas, GTK_DOUBLE_BUFFERED); + GTK_WIDGET_SET_FLAGS (canvas, GTK_CAN_FOCUS); + + canvas->pick_event.type = GDK_LEAVE_NOTIFY; + canvas->pick_event.crossing.x = 0; + canvas->pick_event.crossing.y = 0; + + /* Create the root item as a special case */ + canvas->root = SP_CANVAS_ITEM (gtk_type_new (sp_canvas_group_get_type ())); + canvas->root->canvas = canvas; + + gtk_object_ref (GTK_OBJECT (canvas->root)); + gtk_object_sink (GTK_OBJECT (canvas->root)); + + canvas->need_repick = TRUE; + + canvas->tiles=NULL; + canvas->tLeft=canvas->tTop=canvas->tRight=canvas->tBottom=0; + canvas->tileH=canvas->tileV=0; +} + +/** + * Convenience function to remove the idle handler of a canvas. + */ +static void +remove_idle (SPCanvas *canvas) +{ + if (canvas->idle_id) { + gtk_idle_remove (canvas->idle_id); + canvas->idle_id = 0; + } +} + +/* + * Removes the transient state of the canvas (idle handler, grabs). + */ +static void +shutdown_transients (SPCanvas *canvas) +{ + /* We turn off the need_redraw flag, since if the canvas is mapped again + * it will request a redraw anyways. We do not turn off the need_update + * flag, though, because updates are not queued when the canvas remaps + * itself. + */ + if (canvas->need_redraw) { + canvas->need_redraw = FALSE; + } + if ( canvas->tiles ) free(canvas->tiles); + canvas->tiles=NULL; + canvas->tLeft=canvas->tTop=canvas->tRight=canvas->tBottom=0; + canvas->tileH=canvas->tileV=0; + + if (canvas->grabbed_item) { + canvas->grabbed_item = NULL; + gdk_pointer_ungrab (GDK_CURRENT_TIME); + } + + remove_idle (canvas); +} + +/** + * Destroy handler for SPCanvas. + */ +static void +sp_canvas_destroy (GtkObject *object) +{ + SPCanvas *canvas = SP_CANVAS (object); + + if (canvas->root) { + gtk_object_unref (GTK_OBJECT (canvas->root)); + canvas->root = NULL; + } + + shutdown_transients (canvas); + + if (GTK_OBJECT_CLASS (canvas_parent_class)->destroy) + (* GTK_OBJECT_CLASS (canvas_parent_class)->destroy) (object); +} + +/** + * Returns new canvas as widget. + */ +GtkWidget * +sp_canvas_new_aa (void) +{ + SPCanvas *canvas = (SPCanvas *)gtk_type_new (sp_canvas_get_type ()); + + return (GtkWidget *) canvas; +} + +/** + * The canvas widget's realize callback. + */ +static void +sp_canvas_realize (GtkWidget *widget) +{ + SPCanvas *canvas = SP_CANVAS (widget); + + GdkWindowAttr attributes; + attributes.window_type = GDK_WINDOW_CHILD; + attributes.x = widget->allocation.x; + attributes.y = widget->allocation.y; + attributes.width = widget->allocation.width; + attributes.height = widget->allocation.height; + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.visual = gdk_rgb_get_visual (); + attributes.colormap = gdk_rgb_get_cmap (); + attributes.event_mask = (gtk_widget_get_events (widget) | + GDK_EXPOSURE_MASK | + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK | + GDK_PROXIMITY_IN_MASK | + GDK_PROXIMITY_OUT_MASK | + GDK_KEY_PRESS_MASK | + GDK_KEY_RELEASE_MASK | + GDK_ENTER_NOTIFY_MASK | + GDK_LEAVE_NOTIFY_MASK | + GDK_FOCUS_CHANGE_MASK); + gint attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; + + widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask); + gdk_window_set_user_data (widget->window, widget); + gtk_widget_set_events(widget, attributes.event_mask); + + GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); + + canvas->pixmap_gc = gdk_gc_new (SP_CANVAS_WINDOW (canvas)); +} + +/** + * The canvas widget's unrealize callback. + */ +static void +sp_canvas_unrealize (GtkWidget *widget) +{ + SPCanvas *canvas = SP_CANVAS (widget); + + shutdown_transients (canvas); + + gdk_gc_destroy (canvas->pixmap_gc); + canvas->pixmap_gc = NULL; + + if (GTK_WIDGET_CLASS (canvas_parent_class)->unrealize) + (* GTK_WIDGET_CLASS (canvas_parent_class)->unrealize) (widget); +} + +/** + * The canvas widget's size_request callback. + */ +static void +sp_canvas_size_request (GtkWidget *widget, GtkRequisition *req) +{ + static_cast(SP_CANVAS (widget)); + + req->width = 256; + req->height = 256; +} + +/** + * The canvas widget's size_allocate callback. + */ +static void +sp_canvas_size_allocate (GtkWidget *widget, GtkAllocation *allocation) +{ + SPCanvas *canvas = SP_CANVAS (widget); + + /* Schedule redraw of new region */ + sp_canvas_resize_tiles(canvas,canvas->x0,canvas->y0,canvas->x0+allocation->width,canvas->y0+allocation->height); + if (allocation->width > widget->allocation.width) { + sp_canvas_request_redraw (canvas, + canvas->x0 + widget->allocation.width, + 0, + canvas->x0 + allocation->width, + canvas->y0 + allocation->height); + } + if (allocation->height > widget->allocation.height) { + sp_canvas_request_redraw (canvas, + 0, + canvas->y0 + widget->allocation.height, + canvas->x0 + allocation->width, + canvas->y0 + allocation->height); + } + + widget->allocation = *allocation; + + if (GTK_WIDGET_REALIZED (widget)) { + gdk_window_move_resize (widget->window, + widget->allocation.x, widget->allocation.y, + widget->allocation.width, widget->allocation.height); + } +} + +/** + * Helper that emits an event for an item in the canvas, be it the current + * item, grabbed item, or focused item, as appropriate. + */ +static int +emit_event (SPCanvas *canvas, GdkEvent *event) +{ + guint mask; + + if (canvas->grabbed_item) { + switch (event->type) { + case GDK_ENTER_NOTIFY: + mask = GDK_ENTER_NOTIFY_MASK; + break; + case GDK_LEAVE_NOTIFY: + mask = GDK_LEAVE_NOTIFY_MASK; + break; + case GDK_MOTION_NOTIFY: + mask = GDK_POINTER_MOTION_MASK; + break; + case GDK_BUTTON_PRESS: + case GDK_2BUTTON_PRESS: + case GDK_3BUTTON_PRESS: + mask = GDK_BUTTON_PRESS_MASK; + break; + case GDK_BUTTON_RELEASE: + mask = GDK_BUTTON_RELEASE_MASK; + break; + case GDK_KEY_PRESS: + mask = GDK_KEY_PRESS_MASK; + break; + case GDK_KEY_RELEASE: + mask = GDK_KEY_RELEASE_MASK; + break; + case GDK_SCROLL: + mask = GDK_SCROLL; + break; + default: + mask = 0; + break; + } + + if (!(mask & canvas->grabbed_event_mask)) return FALSE; + } + + /* Convert to world coordinates -- we have two cases because of diferent + * offsets of the fields in the event structures. + */ + + GdkEvent ev = *event; + + switch (ev.type) { + case GDK_ENTER_NOTIFY: + case GDK_LEAVE_NOTIFY: + ev.crossing.x += canvas->x0; + ev.crossing.y += canvas->y0; + break; + case GDK_MOTION_NOTIFY: + case GDK_BUTTON_PRESS: + case GDK_2BUTTON_PRESS: + case GDK_3BUTTON_PRESS: + case GDK_BUTTON_RELEASE: + ev.motion.x += canvas->x0; + ev.motion.y += canvas->y0; + break; + default: + break; + } + + /* Choose where we send the event */ + + /* canvas->current_item becomes NULL in some cases under Win32 + ** (e.g. if the pointer leaves the window). So this is a hack that + ** Lauris applied to SP to get around the problem. + */ + SPCanvasItem* item = NULL; + if (canvas->grabbed_item && !is_descendant (canvas->current_item, canvas->grabbed_item)) { + item = canvas->grabbed_item; + } else { + item = canvas->current_item; + } + + if (canvas->focused_item && + ((event->type == GDK_KEY_PRESS) || + (event->type == GDK_KEY_RELEASE) || + (event->type == GDK_FOCUS_CHANGE))) { + item = canvas->focused_item; + } + + /* The event is propagated up the hierarchy (for if someone connected to + * a group instead of a leaf event), and emission is stopped if a + * handler returns TRUE, just like for GtkWidget events. + */ + + gint finished = FALSE; + + while (item && !finished) { + gtk_object_ref (GTK_OBJECT (item)); + gtk_signal_emit (GTK_OBJECT (item), item_signals[ITEM_EVENT], &ev, &finished); + SPCanvasItem *parent = item->parent; + gtk_object_unref (GTK_OBJECT (item)); + item = parent; + } + + return finished; +} + +/** + * Helper that re-picks the current item in the canvas, based on the event's + * coordinates and emits enter/leave events for items as appropriate. + */ +static int +pick_current_item (SPCanvas *canvas, GdkEvent *event) +{ + double x, y; + + int retval = FALSE; + + /* Save the event in the canvas. This is used to synthesize enter and + * leave events in case the current item changes. It is also used to + * re-pick the current item if the current one gets deleted. Also, + * synthesize an enter event. + */ + if (event != &canvas->pick_event) { + if ((event->type == GDK_MOTION_NOTIFY) || (event->type == GDK_BUTTON_RELEASE)) { + /* these fields have the same offsets in both types of events */ + + canvas->pick_event.crossing.type = GDK_ENTER_NOTIFY; + canvas->pick_event.crossing.window = event->motion.window; + canvas->pick_event.crossing.send_event = event->motion.send_event; + canvas->pick_event.crossing.subwindow = NULL; + canvas->pick_event.crossing.x = event->motion.x; + canvas->pick_event.crossing.y = event->motion.y; + canvas->pick_event.crossing.mode = GDK_CROSSING_NORMAL; + canvas->pick_event.crossing.detail = GDK_NOTIFY_NONLINEAR; + canvas->pick_event.crossing.focus = FALSE; + canvas->pick_event.crossing.state = event->motion.state; + + /* these fields don't have the same offsets in both types of events */ + + if (event->type == GDK_MOTION_NOTIFY) { + canvas->pick_event.crossing.x_root = event->motion.x_root; + canvas->pick_event.crossing.y_root = event->motion.y_root; + } else { + canvas->pick_event.crossing.x_root = event->button.x_root; + canvas->pick_event.crossing.y_root = event->button.y_root; + } + } else { + canvas->pick_event = *event; + } + } + + /* Don't do anything else if this is a recursive call */ + if (canvas->in_repick) return retval; + + /* LeaveNotify means that there is no current item, so we don't look for one */ + if (canvas->pick_event.type != GDK_LEAVE_NOTIFY) { + /* these fields don't have the same offsets in both types of events */ + + if (canvas->pick_event.type == GDK_ENTER_NOTIFY) { + x = canvas->pick_event.crossing.x; + y = canvas->pick_event.crossing.y; + } else { + x = canvas->pick_event.motion.x; + y = canvas->pick_event.motion.y; + } + + /* world coords */ + x += canvas->x0; + y += canvas->y0; + + /* find the closest item */ + if (canvas->root->flags & SP_CANVAS_ITEM_VISIBLE) { + sp_canvas_item_invoke_point (canvas->root, NR::Point(x, y), &canvas->new_current_item); + } else { + canvas->new_current_item = NULL; + } + } else { + canvas->new_current_item = NULL; + } + + if ((canvas->new_current_item == canvas->current_item) && !canvas->left_grabbed_item) { + return retval; /* current item did not change */ + } + + /* Synthesize events for old and new current items */ + + if ((canvas->new_current_item != canvas->current_item) + && (canvas->current_item != NULL) + && !canvas->left_grabbed_item) { + GdkEvent new_event; + SPCanvasItem *item; + + item = canvas->current_item; + + new_event = canvas->pick_event; + new_event.type = GDK_LEAVE_NOTIFY; + + new_event.crossing.detail = GDK_NOTIFY_ANCESTOR; + new_event.crossing.subwindow = NULL; + canvas->in_repick = TRUE; + retval = emit_event (canvas, &new_event); + canvas->in_repick = FALSE; + } + + /* Handle the rest of cases */ + + canvas->left_grabbed_item = FALSE; + canvas->current_item = canvas->new_current_item; + + if (canvas->current_item != NULL) { + GdkEvent new_event; + + new_event = canvas->pick_event; + new_event.type = GDK_ENTER_NOTIFY; + new_event.crossing.detail = GDK_NOTIFY_ANCESTOR; + new_event.crossing.subwindow = NULL; + retval = emit_event (canvas, &new_event); + } + + return retval; +} + +/** + * Button event handler for the canvas. + */ +static gint +sp_canvas_button (GtkWidget *widget, GdkEventButton *event) +{ + SPCanvas *canvas = SP_CANVAS (widget); + + int retval = FALSE; + + /* dispatch normally regardless of the event's window if an item has + has a pointer grab in effect */ + if (!canvas->grabbed_item && + event->window != SP_CANVAS_WINDOW (canvas)) + return retval; + + int mask; + switch (event->button) { + case 1: + mask = GDK_BUTTON1_MASK; + break; + case 2: + mask = GDK_BUTTON2_MASK; + break; + case 3: + mask = GDK_BUTTON3_MASK; + break; + case 4: + mask = GDK_BUTTON4_MASK; + break; + case 5: + mask = GDK_BUTTON5_MASK; + break; + default: + mask = 0; + } + + switch (event->type) { + case GDK_BUTTON_PRESS: + case GDK_2BUTTON_PRESS: + case GDK_3BUTTON_PRESS: + /* Pick the current item as if the button were not pressed, and + * then process the event. + */ + canvas->state = event->state; + pick_current_item (canvas, (GdkEvent *) event); + canvas->state ^= mask; + retval = emit_event (canvas, (GdkEvent *) event); + break; + + case GDK_BUTTON_RELEASE: + /* Process the event as if the button were pressed, then repick + * after the button has been released + */ + canvas->state = event->state; + retval = emit_event (canvas, (GdkEvent *) event); + event->state ^= mask; + canvas->state = event->state; + pick_current_item (canvas, (GdkEvent *) event); + event->state ^= mask; + break; + + default: + g_assert_not_reached (); + } + + return retval; +} + +/** + * Scroll event handler for the canvas. + * + * \todo FIXME: generate motion events to re-select items. + */ +static gint +sp_canvas_scroll (GtkWidget *widget, GdkEventScroll *event) +{ + return emit_event (SP_CANVAS (widget), (GdkEvent *) event); +} + +/** + * Motion event handler for the canvas. + */ +static int +sp_canvas_motion (GtkWidget *widget, GdkEventMotion *event) +{ + SPCanvas *canvas = SP_CANVAS (widget); + + if (event->window != SP_CANVAS_WINDOW (canvas)) + return FALSE; + + if (canvas->grabbed_event_mask & GDK_POINTER_MOTION_HINT_MASK) { + gint x, y; + gdk_window_get_pointer (widget->window, &x, &y, NULL); + event->x = x; + event->y = y; + } + + canvas->state = event->state; + pick_current_item (canvas, (GdkEvent *) event); + + return emit_event (canvas, (GdkEvent *) event); +} + +/** + * Helper that draws a specific rectangular part of the canvas. + */ +static void +sp_canvas_paint_rect (SPCanvas *canvas, int xx0, int yy0, int xx1, int yy1) +{ + g_return_if_fail (!canvas->need_update); + + GtkWidget *widget = GTK_WIDGET (canvas); + + int draw_x1 = MAX (xx0, canvas->x0); + int draw_y1 = MAX (yy0, canvas->y0); + int draw_x2 = MIN (xx1, canvas->x0/*draw_x1*/ + GTK_WIDGET (canvas)->allocation.width); + int draw_y2 = MIN (yy1, canvas->y0/*draw_y1*/ + GTK_WIDGET (canvas)->allocation.height); + + int bw = draw_x2 - draw_x1; + int bh = draw_y2 - draw_y1; + if ((bw < 1) || (bh < 1)) + return; + + int sw, sh; + if (canvas->rendermode != RENDERMODE_OUTLINE) { // use 256K as a compromise to not slow down gradients + /* 256K is the cached buffer and we need 3 channels */ + if (bw * bh < 87381) { // 256K/3 + // We can go with single buffer + sw = bw; + sh = bh; + } else if (bw <= (16 * 341)) { + // Go with row buffer + sw = bw; + sh = 87381 / bw; + } else if (bh <= (16 * 256)) { + // Go with column buffer + sw = 87381 / bh; + sh = bh; + } else { + sw = 341; + sh = 256; + } + } else { // paths only, so 1M works faster + /* 1M is the cached buffer and we need 3 channels */ + if (bw * bh < 349525) { // 1M/3 + // We can go with single buffer + sw = bw; + sh = bh; + } else if (bw <= (16 * 682)) { + // Go with row buffer + sw = bw; + sh = 349525 / bw; + } else if (bh <= (16 * 512)) { + // Go with column buffer + sw = 349525 / bh; + sh = bh; + } else { + sw = 682; + sh = 512; + } + } + + // As we can come from expose, we have to tile here + for (int y0 = draw_y1; y0 < draw_y2; y0 += sh) { + int y1 = MIN (y0 + sh, draw_y2); + for (int x0 = draw_x1; x0 < draw_x2; x0 += sw) { + int x1 = MIN (x0 + sw, draw_x2); + + SPCanvasBuf buf; + if (canvas->rendermode != RENDERMODE_OUTLINE) { + buf.buf = nr_pixelstore_256K_new (FALSE, 0); + } else { + buf.buf = nr_pixelstore_1M_new (FALSE, 0); + } + + buf.buf_rowstride = sw * 3; + buf.rect.x0 = x0; + buf.rect.y0 = y0; + buf.rect.x1 = x1; + buf.rect.y1 = y1; + GdkColor *color = &widget->style->bg[GTK_STATE_NORMAL]; + buf.bg_color = (((color->red & 0xff00) << 8) + | (color->green & 0xff00) + | (color->blue >> 8)); + buf.is_empty = true; + + if (canvas->root->flags & SP_CANVAS_ITEM_VISIBLE) { + SP_CANVAS_ITEM_GET_CLASS (canvas->root)->render (canvas->root, &buf); + } + + if (buf.is_empty) { + gdk_rgb_gc_set_foreground (canvas->pixmap_gc, buf.bg_color); + gdk_draw_rectangle (SP_CANVAS_WINDOW (canvas), + canvas->pixmap_gc, + TRUE, + x0 - canvas->x0, y0 - canvas->y0, + x1 - x0, y1 - y0); + } else { + gdk_draw_rgb_image_dithalign (SP_CANVAS_WINDOW (canvas), + canvas->pixmap_gc, + x0 - canvas->x0, y0 - canvas->y0, + x1 - x0, y1 - y0, + GDK_RGB_DITHER_MAX, + buf.buf, + sw * 3, + x0 - canvas->x0, y0 - canvas->y0); + } + + if (canvas->rendermode != RENDERMODE_OUTLINE) { + nr_pixelstore_256K_free (buf.buf); + } else { + nr_pixelstore_1M_free (buf.buf); + } + + } + } +} + +/** + * The canvas widget's expose callback. + */ +static gint +sp_canvas_expose (GtkWidget *widget, GdkEventExpose *event) +{ + SPCanvas *canvas = SP_CANVAS (widget); + + if (!GTK_WIDGET_DRAWABLE (widget) || + (event->window != SP_CANVAS_WINDOW (canvas))) + return FALSE; + + int n_rects; + GdkRectangle *rects; + gdk_region_get_rectangles (event->region, &rects, &n_rects); + + for (int i = 0; i < n_rects; i++) { + NRRectL rect; + + rect.x0 = rects[i].x + canvas->x0; + rect.y0 = rects[i].y + canvas->y0; + rect.x1 = rect.x0 + rects[i].width; + rect.y1 = rect.y0 + rects[i].height; + + if (canvas->need_update || canvas->need_redraw) { + sp_canvas_request_redraw (canvas, rect.x0, rect.y0, rect.x1, rect.y1); + } else { + /* No pending updates, draw exposed area immediately */ + sp_canvas_paint_rect (canvas, rect.x0, rect.y0, rect.x1, rect.y1); + } + } + + if (n_rects > 0) + g_free (rects); + + return FALSE; +} + +/** + * The canvas widget's keypress callback. + */ +static gint +sp_canvas_key (GtkWidget *widget, GdkEventKey *event) +{ + return emit_event (SP_CANVAS (widget), (GdkEvent *) event); +} + +/** + * Crossing event handler for the canvas. + */ +static gint +sp_canvas_crossing (GtkWidget *widget, GdkEventCrossing *event) +{ + SPCanvas *canvas = SP_CANVAS (widget); + + if (event->window != SP_CANVAS_WINDOW (canvas)) + return FALSE; + + canvas->state = event->state; + return pick_current_item (canvas, (GdkEvent *) event); +} + +/** + * Focus in handler for the canvas. + */ +static gint +sp_canvas_focus_in (GtkWidget *widget, GdkEventFocus *event) +{ + GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS); + + SPCanvas *canvas = SP_CANVAS (widget); + + if (canvas->focused_item) { + return emit_event (canvas, (GdkEvent *) event); + } else { + return FALSE; + } +} + +/** + * Focus out handler for the canvas. + */ +static gint +sp_canvas_focus_out (GtkWidget *widget, GdkEventFocus *event) +{ + GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_FOCUS); + + SPCanvas *canvas = SP_CANVAS (widget); + + if (canvas->focused_item) + return emit_event (canvas, (GdkEvent *) event); + else + return FALSE; +} + +/** + * Helper that repaints the areas in the canvas that need it. + */ +static int +paint (SPCanvas *canvas) +{ + if (canvas->need_update) { + sp_canvas_item_invoke_update (canvas->root, NR::identity(), 0); + canvas->need_update = FALSE; + } + + if (!canvas->need_redraw) + return TRUE; + + GtkWidget const *widget = GTK_WIDGET(canvas); + int const canvas_x1 = canvas->x0 + widget->allocation.width; + int const canvas_y1 = canvas->y0 + widget->allocation.height; + + NRRectL topaint; + topaint.x0 = topaint.y0 = topaint.x1 = topaint.y1 = 0; + + for (int j=canvas->tTop&(~3);jtBottom;j+=4) { + for (int i=canvas->tLeft&(~3);itRight;i+=4) { + int mode=0; + + int pl=i+1,pr=i,pt=j+4,pb=j; + for (int l=MAX(j,canvas->tTop);ltBottom);l++) { + for (int k=MAX(i,canvas->tLeft);ktRight);k++) { + if ( canvas->tiles[(k-canvas->tLeft)+(l-canvas->tTop)*canvas->tileH] ) { + mode|=1<<((k-i)+(l-j)*4); + if ( k < pl ) pl=k; + if ( k+1 > pr ) pr=k+1; + if ( l < pt ) pt=l; + if ( l+1 > pb ) pb=l+1; + } + canvas->tiles[(k-canvas->tLeft)+(l-canvas->tTop)*canvas->tileH]=0; + } + } + + if ( mode ) { + NRRectL tile; + tile.x0 = MAX (pl*32, canvas->x0); + tile.y0 = MAX (pt*32, canvas->y0); + tile.x1 = MIN (pr*32, canvas_x1); + tile.y1 = MIN (pb*32, canvas_y1); + if ((tile.x0 < tile.x1) && (tile.y0 < tile.y1)) { + nr_rect_l_union (&topaint, &topaint, &tile); + } + + } + } + } + + sp_canvas_paint_rect (canvas, topaint.x0, topaint.y0, topaint.x1, topaint.y1); + + canvas->need_redraw = FALSE; + return TRUE; +} + +/** + * Helper that invokes update, paint, and repick on canvas. + */ +static int +do_update (SPCanvas *canvas) +{ + /* Cause the update if necessary */ + if (canvas->need_update) { + sp_canvas_item_invoke_update (canvas->root, NR::identity(), 0); + canvas->need_update = FALSE; + } + + /* Paint if able to */ + if (GTK_WIDGET_DRAWABLE (canvas)) { + return paint (canvas); + } + + /* Pick new current item */ + while (canvas->need_repick) { + canvas->need_repick = FALSE; + pick_current_item (canvas, &canvas->pick_event); + } + + return TRUE; +} + +/** + * Idle handler for the canvas that deals with pending updates and redraws. + */ +static gint +idle_handler (gpointer data) +{ + GDK_THREADS_ENTER (); + + SPCanvas *canvas = SP_CANVAS (data); + + const int ret = do_update (canvas); + + if (ret) { + /* Reset idle id */ + canvas->idle_id = 0; + } + + GDK_THREADS_LEAVE (); + + return !ret; +} + +/** + * Convenience function to add an idle handler to a canvas. + */ +static void +add_idle (SPCanvas *canvas) +{ + if (canvas->idle_id != 0) + return; + + canvas->idle_id = gtk_idle_add_priority (sp_canvas_update_priority, idle_handler, canvas); +} + +/** + * Returns the root group of the specified canvas. + */ +SPCanvasGroup * +sp_canvas_root (SPCanvas *canvas) +{ + g_return_val_if_fail (canvas != NULL, NULL); + g_return_val_if_fail (SP_IS_CANVAS (canvas), NULL); + + return SP_CANVAS_GROUP (canvas->root); +} + +/** + * Scrolls canvas to specific position. + */ +void +sp_canvas_scroll_to (SPCanvas *canvas, double cx, double cy, unsigned int clear) +{ + g_return_if_fail (canvas != NULL); + g_return_if_fail (SP_IS_CANVAS (canvas)); + + int ix = (int) (cx + 0.5); + int iy = (int) (cy + 0.5); + int dx = ix - canvas->x0; + int dy = iy - canvas->y0; + + canvas->dx0 = cx; + canvas->dy0 = cy; + canvas->x0 = ix; + canvas->y0 = iy; + + sp_canvas_resize_tiles(canvas,canvas->x0,canvas->y0,canvas->x0+canvas->widget.allocation.width,canvas->y0+canvas->widget.allocation.height); + + if (!clear) { + // scrolling without zoom; redraw only the newly exposed areas + if ((dx != 0) || (dy != 0)) { + int width, height; + width = canvas->widget.allocation.width; + height = canvas->widget.allocation.height; + if (GTK_WIDGET_REALIZED (canvas)) { + gdk_window_scroll (SP_CANVAS_WINDOW (canvas), -dx, -dy); + gdk_window_process_updates (SP_CANVAS_WINDOW (canvas), TRUE); + } + if (dx < 0) { + sp_canvas_request_redraw (canvas, ix + 0, iy + 0, ix - dx, iy + height); + } else if (dx > 0) { + sp_canvas_request_redraw (canvas, ix + width - dx, iy + 0, ix + width, iy + height); + } + if (dy < 0) { + sp_canvas_request_redraw (canvas, ix + 0, iy + 0, ix + width, iy - dy); + } else if (dy > 0) { + sp_canvas_request_redraw (canvas, ix + 0, iy + height - dy, ix + width, iy + height); + } + } + } else { + // scrolling as part of zoom; do nothing here - the next do_update will perform full redraw + } +} + +/** + * Updates canvas if necessary. + */ +void +sp_canvas_update_now (SPCanvas *canvas) +{ + g_return_if_fail (canvas != NULL); + g_return_if_fail (SP_IS_CANVAS (canvas)); + + if (!(canvas->need_update || + canvas->need_redraw)) + return; + + remove_idle (canvas); + do_update (canvas); +} + +/** + * Update callback for canvas widget. + */ +static void +sp_canvas_request_update (SPCanvas *canvas) +{ + canvas->need_update = TRUE; + add_idle (canvas); +} + +/** + * Forces redraw of rectangular canvas area. + */ +void +sp_canvas_request_redraw (SPCanvas *canvas, int x0, int y0, int x1, int y1) +{ + NRRectL bbox; + NRRectL visible; + NRRectL clip; + + g_return_if_fail (canvas != NULL); + g_return_if_fail (SP_IS_CANVAS (canvas)); + + if (!GTK_WIDGET_DRAWABLE (canvas)) return; + if ((x0 >= x1) || (y0 >= y1)) return; + + bbox.x0 = x0; + bbox.y0 = y0; + bbox.x1 = x1; + bbox.y1 = y1; + + visible.x0 = canvas->x0; + visible.y0 = canvas->y0; + visible.x1 = visible.x0 + GTK_WIDGET (canvas)->allocation.width; + visible.y1 = visible.y0 + GTK_WIDGET (canvas)->allocation.height; + + nr_rect_l_intersect (&clip, &bbox, &visible); + + sp_canvas_dirty_rect(canvas,x0,y0,x1,y1); + add_idle (canvas); +} + +/** + * Sets world coordinates from win and canvas. + */ +void sp_canvas_window_to_world(SPCanvas const *canvas, double winx, double winy, double *worldx, double *worldy) +{ + g_return_if_fail (canvas != NULL); + g_return_if_fail (SP_IS_CANVAS (canvas)); + + if (worldx) *worldx = canvas->x0 + winx; + if (worldy) *worldy = canvas->y0 + winy; +} + +/** + * Sets win coordinates from world and canvas. + */ +void sp_canvas_world_to_window(SPCanvas const *canvas, double worldx, double worldy, double *winx, double *winy) +{ + g_return_if_fail (canvas != NULL); + g_return_if_fail (SP_IS_CANVAS (canvas)); + + if (winx) *winx = worldx - canvas->x0; + if (winy) *winy = worldy - canvas->y0; +} + +/** + * Converts point from win to world coordinates. + */ +NR::Point sp_canvas_window_to_world(SPCanvas const *canvas, NR::Point const win) +{ + g_assert (canvas != NULL); + g_assert (SP_IS_CANVAS (canvas)); + + return NR::Point(canvas->x0 + win[0], canvas->y0 + win[1]); +} + +/** + * Converts point from world to win coordinates. + */ +NR::Point sp_canvas_world_to_window(SPCanvas const *canvas, NR::Point const world) +{ + g_assert (canvas != NULL); + g_assert (SP_IS_CANVAS (canvas)); + + return NR::Point(world[0] - canvas->x0, world[1] - canvas->y0); +} + +/** + * Returns true if point given in world coordinates is inside window. + */ +bool sp_canvas_world_pt_inside_window(SPCanvas const *canvas, NR::Point const &world) +{ + g_assert( canvas != NULL ); + g_assert(SP_IS_CANVAS(canvas)); + + using NR::X; + using NR::Y; + GtkWidget const &w = *GTK_WIDGET(canvas); + return ( ( canvas->x0 <= world[X] ) && + ( canvas->y0 <= world[Y] ) && + ( world[X] < canvas->x0 + w.allocation.width ) && + ( world[Y] < canvas->y0 + w.allocation.height ) ); +} + +/** + * Return canvas window coordinates as NRRect. + */ +NR::Rect SPCanvas::getViewbox() const +{ + GtkWidget const *w = GTK_WIDGET(this); + + return NR::Rect(NR::Point(dx0, dy0), + NR::Point(dx0 + w->allocation.width, dy0 + w->allocation.height)); +} + +inline int sp_canvas_tile_floor(int x) +{ + return (x&(~31))/32; +} + +inline int sp_canvas_tile_ceil(int x) +{ + return ((x+31)&(~31))/32; +} + +/** + * Helper that changes tile size for canvas redraw. + */ +void sp_canvas_resize_tiles(SPCanvas* canvas,int nl,int nt,int nr,int nb) +{ + if ( nl >= nr || nt >= nb ) { + if ( canvas->tiles ) free(canvas->tiles); + canvas->tLeft=canvas->tTop=canvas->tRight=canvas->tBottom=0; + canvas->tileH=canvas->tileV=0; + canvas->tiles=NULL; + return; + } + int tl=sp_canvas_tile_floor(nl); + int tt=sp_canvas_tile_floor(nt); + int tr=sp_canvas_tile_ceil(nr); + int tb=sp_canvas_tile_ceil(nb); + + int nh=tr-tl,nv=tb-tt; + uint8_t* ntiles=(uint8_t*)malloc(nh*nv*sizeof(uint8_t)); + for (int i=tl;i= canvas->tLeft && i < canvas->tRight && j >= canvas->tTop && j < canvas->tBottom ) { + ntiles[ind]=canvas->tiles[(i-canvas->tLeft)+(j-canvas->tTop)*canvas->tileH]; + } else { + ntiles[ind]=0; + } + } + } + if ( canvas->tiles ) free(canvas->tiles); + canvas->tiles=ntiles; + canvas->tLeft=tl; + canvas->tTop=tt; + canvas->tRight=tr; + canvas->tBottom=tb; + canvas->tileH=nh; + canvas->tileV=nv; +} + +/** + * Helper that marks specific canvas rectangle for redraw. + */ +void sp_canvas_dirty_rect(SPCanvas* canvas,int nl,int nt,int nr,int nb) +{ + if ( nl >= nr || nt >= nb ) { + return; + } + int tl=sp_canvas_tile_floor(nl); + int tt=sp_canvas_tile_floor(nt); + int tr=sp_canvas_tile_ceil(nr); + int tb=sp_canvas_tile_ceil(nb); + if ( tl >= canvas->tRight || tr <= canvas->tLeft || tt >= canvas->tBottom || tb <= canvas->tTop ) return; + if ( tl < canvas->tLeft ) tl=canvas->tLeft; + if ( tr > canvas->tRight ) tr=canvas->tRight; + if ( tt < canvas->tTop ) tt=canvas->tTop; + if ( tb > canvas->tBottom ) tb=canvas->tBottom; + + canvas->need_redraw = TRUE; + + for (int i=tl;itiles[(i-canvas->tLeft)+(j-canvas->tTop)*canvas->tileH]=1; + } + } +} + + +/* + 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/display/sp-canvas.h b/src/display/sp-canvas.h new file mode 100644 index 000000000..905c9272f --- /dev/null +++ b/src/display/sp-canvas.h @@ -0,0 +1,186 @@ +#ifndef __SP_CANVAS_H__ +#define __SP_CANVAS_H__ + +/** \file + * SPCanvas, SPCanvasBuf, and SPCanvasItem. + * + * Authors: + * Federico Mena + * Raph Levien + * Lauris Kaplinski + * + * Copyright (C) 1998 The Free Software Foundation + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +struct SPCanvas; +struct SPCanvasGroup; + +enum { + SP_CANVAS_UPDATE_REQUESTED = 1 << 0, + SP_CANVAS_UPDATE_AFFINE = 1 << 1 +}; + +/** + * The canvas buf contains the actual pixels. + */ +struct SPCanvasBuf{ + guchar *buf; + int buf_rowstride; + NRRectL rect; + /// Background color, given as 0xrrggbb + guint32 bg_color; + // If empty, ignore contents of buffer and use a solid area of bg_color + bool is_empty; +}; + +/** + * An SPCanvasItem refers to a SPCanvas and to its parent item; it has + * four coordinates, a bounding rectangle, and a transformation matrix. + */ +struct SPCanvasItem : public GtkObject { + SPCanvas *canvas; + SPCanvasItem *parent; + + double x1, y1, x2, y2; + NR::Rect bounds; + NR::Matrix xform; +}; + +/** + * The vtable of an SPCanvasItem. + */ +struct SPCanvasItemClass : public GtkObjectClass { + void (* update) (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags); + + void (* render) (SPCanvasItem *item, SPCanvasBuf *buf); + double (* point) (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item); + + int (* event) (SPCanvasItem *item, GdkEvent *event); +}; + +SPCanvasItem *sp_canvas_item_new(SPCanvasGroup *parent, GtkType type, const gchar *first_arg_name, ...); + +#define sp_canvas_item_set gtk_object_set + +void sp_canvas_item_affine_absolute(SPCanvasItem *item, NR::Matrix const &aff); + +void sp_canvas_item_raise(SPCanvasItem *item, int positions); +void sp_canvas_item_lower(SPCanvasItem *item, int positions); +void sp_canvas_item_show(SPCanvasItem *item); +void sp_canvas_item_hide(SPCanvasItem *item); +int sp_canvas_item_grab(SPCanvasItem *item, unsigned int event_mask, GdkCursor *cursor, guint32 etime); +void sp_canvas_item_ungrab(SPCanvasItem *item, guint32 etime); + +NR::Matrix sp_canvas_item_i2w_affine(SPCanvasItem const *item); + +void sp_canvas_item_grab_focus(SPCanvasItem *item); + +void sp_canvas_item_request_update(SPCanvasItem *item); + +/* get item z-order in parent group */ + +gint sp_canvas_item_order(SPCanvasItem * item); + + +// SPCanvas ------------------------------------------------- +/** + * Port of GnomeCanvas for inkscape needs. + */ +struct SPCanvas { + GtkWidget widget; + + guint idle_id; + + SPCanvasItem *root; + + double dx0, dy0; + int x0, y0; + + /* Area that needs redrawing, stored as a microtile array */ + int tLeft,tTop,tRight,tBottom; + int tileH,tileV; + uint8_t *tiles; + + /* Last known modifier state, for deferred repick when a button is down */ + int state; + + /* The item containing the mouse pointer, or NULL if none */ + SPCanvasItem *current_item; + + /* Item that is about to become current (used to track deletions and such) */ + SPCanvasItem *new_current_item; + + /* Item that holds a pointer grab, or NULL if none */ + SPCanvasItem *grabbed_item; + + /* Event mask specified when grabbing an item */ + guint grabbed_event_mask; + + /* If non-NULL, the currently focused item */ + SPCanvasItem *focused_item; + + /* Event on which selection of current item is based */ + GdkEvent pick_event; + + int close_enough; + + /* GC for temporary draw pixmap */ + GdkGC *pixmap_gc; + + unsigned int need_update : 1; + unsigned int need_redraw : 1; + unsigned int need_repick : 1; + + /* For use by internal pick_current_item() function */ + unsigned int left_grabbed_item : 1; + /* For use by internal pick_current_item() function */ + unsigned int in_repick : 1; + + int rendermode; + + NR::Rect getViewbox() const; +}; + +GtkWidget *sp_canvas_new_aa(); + +SPCanvasGroup *sp_canvas_root(SPCanvas *canvas); + +void sp_canvas_scroll_to(SPCanvas *canvas, double cx, double cy, unsigned int clear); +void sp_canvas_update_now(SPCanvas *canvas); + +void sp_canvas_request_redraw(SPCanvas *canvas, int x1, int y1, int x2, int y2); + +void sp_canvas_window_to_world(SPCanvas const *canvas, double winx, double winy, double *worldx, double *worldy); +void sp_canvas_world_to_window(SPCanvas const *canvas, double worldx, double worldy, double *winx, double *winy); + +NR::Point sp_canvas_window_to_world(SPCanvas const *canvas, NR::Point const win); +NR::Point sp_canvas_world_to_window(SPCanvas const *canvas, NR::Point const world); + +bool sp_canvas_world_pt_inside_window(SPCanvas const *canvas, NR::Point const &world); + +#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 : diff --git a/src/display/sp-ctrlline.cpp b/src/display/sp-ctrlline.cpp new file mode 100644 index 000000000..0a278bb69 --- /dev/null +++ b/src/display/sp-ctrlline.cpp @@ -0,0 +1,217 @@ +#define __INKSCAPE_CTRLLINE_C__ + +/* + * Simple straight line + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * + * Released under GNU GPL + */ + +/* + * TODO: + * Draw it by hand - we really do not need aa stuff for it + * + */ + +#include "display-forward.h" +#include "sp-canvas-util.h" +#include "sp-ctrlline.h" + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include +#include + +struct SPCtrlLine : public SPCanvasItem{ + guint32 rgba; + NRPoint s, e; + Shape* shp; +}; + +struct SPCtrlLineClass : public SPCanvasItemClass{}; + +static void sp_ctrlline_class_init (SPCtrlLineClass *klass); +static void sp_ctrlline_init (SPCtrlLine *ctrlline); +static void sp_ctrlline_destroy (GtkObject *object); + +static void sp_ctrlline_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags); +static void sp_ctrlline_render (SPCanvasItem *item, SPCanvasBuf *buf); + +static SPCanvasItemClass *parent_class; + +GtkType +sp_ctrlline_get_type (void) +{ + static GtkType type = 0; + + if (!type) { + GtkTypeInfo info = { + "SPCtrlLine", + sizeof (SPCtrlLine), + sizeof (SPCtrlLineClass), + (GtkClassInitFunc) sp_ctrlline_class_init, + (GtkObjectInitFunc) sp_ctrlline_init, + NULL, NULL, NULL + }; + type = gtk_type_unique (SP_TYPE_CANVAS_ITEM, &info); + } + return type; +} + +static void +sp_ctrlline_class_init (SPCtrlLineClass *klass) +{ + GtkObjectClass *object_class = (GtkObjectClass *) klass; + SPCanvasItemClass *item_class = (SPCanvasItemClass *) klass; + + parent_class = (SPCanvasItemClass*)gtk_type_class (SP_TYPE_CANVAS_ITEM); + + object_class->destroy = sp_ctrlline_destroy; + + item_class->update = sp_ctrlline_update; + item_class->render = sp_ctrlline_render; +} + +static void +sp_ctrlline_init (SPCtrlLine *ctrlline) +{ + ctrlline->rgba = 0x0000ff7f; + ctrlline->s.x = ctrlline->s.y = ctrlline->e.x = ctrlline->e.y = 0.0; + ctrlline->shp=NULL; +} + +static void +sp_ctrlline_destroy (GtkObject *object) +{ + g_return_if_fail (object != NULL); + g_return_if_fail (SP_IS_CTRLLINE (object)); + + SPCtrlLine *ctrlline = SP_CTRLLINE (object); + + if (ctrlline->shp) { + delete ctrlline->shp; + ctrlline->shp = NULL; + } + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} + +static void +sp_ctrlline_render (SPCanvasItem *item, SPCanvasBuf *buf) +{ + SPCtrlLine *ctrlline = SP_CTRLLINE (item); + + NRRectL area; + area.x0=buf->rect.x0; + area.x1=buf->rect.x1; + area.y0=buf->rect.y0; + area.y1=buf->rect.y1; + + if (ctrlline->shp) { + sp_canvas_prepare_buffer (buf); + nr_pixblock_render_ctrl_rgba (ctrlline->shp,ctrlline->rgba,area,(char*)buf->buf, buf->buf_rowstride); + } +} + +static void +sp_ctrlline_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags) +{ + NRRect dbox; + + SPCtrlLine *cl = SP_CTRLLINE (item); + + sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2); + + if (parent_class->update) + (* parent_class->update) (item, affine, flags); + + sp_canvas_item_reset_bounds (item); + + dbox.x0=dbox.x1=dbox.y0=dbox.y1=0; + if (cl->shp) { + delete cl->shp; + cl->shp = NULL; + } + Path* thePath = new Path; + thePath->MoveTo(NR::Point(cl->s.x, cl->s.y) * affine); + thePath->LineTo(NR::Point(cl->e.x, cl->e.y) * affine); + + thePath->Convert(1.0); + if ( cl->shp == NULL ) cl->shp=new Shape; + thePath->Stroke(cl->shp,false,0.5,join_straight,butt_straight,20.0,false); + cl->shp->CalcBBox(); + if ( cl->shp->leftX < cl->shp->rightX ) { + if ( dbox.x0 >= dbox.x1 ) { + dbox.x0=cl->shp->leftX;dbox.x1=cl->shp->rightX; + dbox.y0=cl->shp->topY;dbox.y1=cl->shp->bottomY; + } else { + if ( cl->shp->leftX < dbox.x0 ) dbox.x0=cl->shp->leftX; + if ( cl->shp->rightX > dbox.x1 ) dbox.x1=cl->shp->rightX; + if ( cl->shp->topY < dbox.y0 ) dbox.y0=cl->shp->topY; + if ( cl->shp->bottomY > dbox.y1 ) dbox.y1=cl->shp->bottomY; + } + } + delete thePath; + + item->x1 = (int)dbox.x0; + item->y1 = (int)dbox.y0; + item->x2 = (int)dbox.x1; + item->y2 = (int)dbox.y1; + + sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2); +} + +void +sp_ctrlline_set_rgba32 (SPCtrlLine *cl, guint32 rgba) +{ + g_return_if_fail (cl != NULL); + g_return_if_fail (SP_IS_CTRLLINE (cl)); + + if (rgba != cl->rgba) { + SPCanvasItem *item; + cl->rgba = rgba; + item = SP_CANVAS_ITEM (cl); + sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2); + } +} + +#define EPSILON 1e-6 +#define DIFFER(a,b) (fabs ((a) - (b)) > EPSILON) + +void +sp_ctrlline_set_coords (SPCtrlLine *cl, gdouble x0, gdouble y0, gdouble x1, gdouble y1) +{ + g_return_if_fail (cl != NULL); + g_return_if_fail (SP_IS_CTRLLINE (cl)); + + if (DIFFER (x0, cl->s.x) || DIFFER (y0, cl->s.y) || DIFFER (x1, cl->e.x) || DIFFER (y1, cl->e.y)) { + cl->s.x = x0; + cl->s.y = y0; + cl->e.x = x1; + cl->e.y = y1; + sp_canvas_item_request_update (SP_CANVAS_ITEM (cl)); + } +} + +void +sp_ctrlline_set_coords (SPCtrlLine *cl, const NR::Point start, const NR::Point end) +{ + sp_ctrlline_set_coords(cl, start[0], start[1], end[0], end[1]); +} + +/* + 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 : diff --git a/src/display/sp-ctrlline.h b/src/display/sp-ctrlline.h new file mode 100644 index 000000000..500fbf08f --- /dev/null +++ b/src/display/sp-ctrlline.h @@ -0,0 +1,45 @@ +#ifndef __INKSCAPE_CTRLLINE_H__ +#define __INKSCAPE_CTRLLINE_H__ + +/* + * Simple straight line + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * + * Released under GNU GPL + */ + +#include "sp-canvas.h" + + + +#define SP_TYPE_CTRLLINE (sp_ctrlline_get_type ()) +#define SP_CTRLLINE(obj) (GTK_CHECK_CAST ((obj), SP_TYPE_CTRLLINE, SPCtrlLine)) +#define SP_IS_CTRLLINE(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_CTRLLINE)) + +struct SPCtrlLine; +struct SPCtrlLineClass; + +GtkType sp_ctrlline_get_type (void); + +void sp_ctrlline_set_rgba32 (SPCtrlLine *cl, guint32 rgba); +void sp_ctrlline_set_coords (SPCtrlLine *cl, gdouble x0, gdouble y0, gdouble x1, gdouble y1); +void sp_ctrlline_set_coords (SPCtrlLine *cl, const NR::Point start, const NR::Point end); + + + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/display/sp-ctrlquadr.cpp b/src/display/sp-ctrlquadr.cpp new file mode 100644 index 000000000..e9488cdb5 --- /dev/null +++ b/src/display/sp-ctrlquadr.cpp @@ -0,0 +1,210 @@ +#define __INKSCAPE_CTRLQUADR_C__ + +/* + * Quadrilateral + * + * Authors: + * bulia byak + * + * Copyright (C) 2005 authors + * + * Released under GNU GPL + */ + +#include "display-forward.h" +#include "sp-canvas-util.h" +#include "sp-ctrlquadr.h" + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include +#include + +struct SPCtrlQuadr : public SPCanvasItem{ + guint32 rgba; + NR::Point p1, p2, p3, p4; + Shape* shp; +}; + +struct SPCtrlQuadrClass : public SPCanvasItemClass{}; + +static void sp_ctrlquadr_class_init (SPCtrlQuadrClass *klass); +static void sp_ctrlquadr_init (SPCtrlQuadr *ctrlquadr); +static void sp_ctrlquadr_destroy (GtkObject *object); + +static void sp_ctrlquadr_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags); +static void sp_ctrlquadr_render (SPCanvasItem *item, SPCanvasBuf *buf); + +static SPCanvasItemClass *parent_class; + +GtkType +sp_ctrlquadr_get_type (void) +{ + static GtkType type = 0; + + if (!type) { + GtkTypeInfo info = { + "SPCtrlQuadr", + sizeof (SPCtrlQuadr), + sizeof (SPCtrlQuadrClass), + (GtkClassInitFunc) sp_ctrlquadr_class_init, + (GtkObjectInitFunc) sp_ctrlquadr_init, + NULL, NULL, NULL + }; + type = gtk_type_unique (SP_TYPE_CANVAS_ITEM, &info); + } + return type; +} + +static void +sp_ctrlquadr_class_init (SPCtrlQuadrClass *klass) +{ + GtkObjectClass *object_class = (GtkObjectClass *) klass; + SPCanvasItemClass *item_class = (SPCanvasItemClass *) klass; + + parent_class = (SPCanvasItemClass*)gtk_type_class (SP_TYPE_CANVAS_ITEM); + + object_class->destroy = sp_ctrlquadr_destroy; + + item_class->update = sp_ctrlquadr_update; + item_class->render = sp_ctrlquadr_render; +} + +static void +sp_ctrlquadr_init (SPCtrlQuadr *ctrlquadr) +{ + ctrlquadr->rgba = 0x000000ff; + ctrlquadr->p1 = NR::Point(0, 0); + ctrlquadr->p2 = NR::Point(0, 0); + ctrlquadr->p3 = NR::Point(0, 0); + ctrlquadr->p4 = NR::Point(0, 0); + ctrlquadr->shp=NULL; +} + +static void +sp_ctrlquadr_destroy (GtkObject *object) +{ + g_return_if_fail (object != NULL); + g_return_if_fail (SP_IS_CTRLQUADR (object)); + + SPCtrlQuadr *ctrlquadr = SP_CTRLQUADR (object); + + if (ctrlquadr->shp) { + delete ctrlquadr->shp; + ctrlquadr->shp = NULL; + } + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} + +static void +sp_ctrlquadr_render (SPCanvasItem *item, SPCanvasBuf *buf) +{ + SPCtrlQuadr *ctrlquadr = SP_CTRLQUADR (item); + + NRRectL area; + area.x0=buf->rect.x0; + area.x1=buf->rect.x1; + area.y0=buf->rect.y0; + area.y1=buf->rect.y1; + + if (ctrlquadr->shp) { + sp_canvas_prepare_buffer (buf); + nr_pixblock_render_ctrl_rgba (ctrlquadr->shp,ctrlquadr->rgba,area,(char*)buf->buf, buf->buf_rowstride); + } +} + +static void +sp_ctrlquadr_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags) +{ + NRRect dbox; + + SPCtrlQuadr *cl = SP_CTRLQUADR (item); + + sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2); + + if (parent_class->update) + (* parent_class->update) (item, affine, flags); + + sp_canvas_item_reset_bounds (item); + + dbox.x0=dbox.x1=dbox.y0=dbox.y1=0; + if (cl->shp) { + delete cl->shp; + cl->shp = NULL; + } + Path* thePath = new Path; + thePath->MoveTo(cl->p1 * affine); + thePath->LineTo(cl->p2 * affine); + thePath->LineTo(cl->p3 * affine); + thePath->LineTo(cl->p4 * affine); + thePath->LineTo(cl->p1 * affine); + + thePath->Convert(1.0); + + if ( cl->shp == NULL ) cl->shp=new Shape; + thePath->Fill(cl->shp, 0); + + cl->shp->CalcBBox(); + if ( cl->shp->leftX < cl->shp->rightX ) { + if ( dbox.x0 >= dbox.x1 ) { + dbox.x0=cl->shp->leftX;dbox.x1=cl->shp->rightX; + dbox.y0=cl->shp->topY;dbox.y1=cl->shp->bottomY; + } else { + if ( cl->shp->leftX < dbox.x0 ) dbox.x0=cl->shp->leftX; + if ( cl->shp->rightX > dbox.x1 ) dbox.x1=cl->shp->rightX; + if ( cl->shp->topY < dbox.y0 ) dbox.y0=cl->shp->topY; + if ( cl->shp->bottomY > dbox.y1 ) dbox.y1=cl->shp->bottomY; + } + } + delete thePath; + + item->x1 = (int)dbox.x0; + item->y1 = (int)dbox.y0; + item->x2 = (int)dbox.x1; + item->y2 = (int)dbox.y1; + + sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2); +} + +void +sp_ctrlquadr_set_rgba32 (SPCtrlQuadr *cl, guint32 rgba) +{ + g_return_if_fail (cl != NULL); + g_return_if_fail (SP_IS_CTRLQUADR (cl)); + + if (rgba != cl->rgba) { + SPCanvasItem *item; + cl->rgba = rgba; + item = SP_CANVAS_ITEM (cl); + sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2); + } +} + +void +sp_ctrlquadr_set_coords (SPCtrlQuadr *cl, NR::Point p1, NR::Point p2, NR::Point p3, NR::Point p4) +{ + g_return_if_fail (cl != NULL); + g_return_if_fail (SP_IS_CTRLQUADR (cl)); + + if (p1 != cl->p1 || p2 != cl->p2 || p3 != cl->p3 || p4 != cl->p4) { + cl->p1 = p1; + cl->p2 = p2; + cl->p3 = p3; + cl->p4 = p4; + sp_canvas_item_request_update (SP_CANVAS_ITEM (cl)); + } +} + +/* + 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 : diff --git a/src/display/sp-ctrlquadr.h b/src/display/sp-ctrlquadr.h new file mode 100644 index 000000000..38951448c --- /dev/null +++ b/src/display/sp-ctrlquadr.h @@ -0,0 +1,43 @@ +#ifndef __INKSCAPE_CTRLQUADR_H__ +#define __INKSCAPE_CTRLQUADR_H__ + +/* + * Quadrilateral + * + * Authors: + * bulia byak + * + * Copyright (C) 2005 authors + * + * Released under GNU GPL + */ + +#include "sp-canvas.h" + + + +#define SP_TYPE_CTRLQUADR (sp_ctrlquadr_get_type ()) +#define SP_CTRLQUADR(obj) (GTK_CHECK_CAST ((obj), SP_TYPE_CTRLQUADR, SPCtrlQuadr)) +#define SP_IS_CTRLQUADR(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_CTRLQUADR)) + +struct SPCtrlQuadr; +struct SPCtrlQuadrClass; + +GtkType sp_ctrlquadr_get_type (void); + +void sp_ctrlquadr_set_rgba32 (SPCtrlQuadr *cl, guint32 rgba); +void sp_ctrlquadr_set_coords (SPCtrlQuadr *cl, const NR::Point p1, const NR::Point p2, const NR::Point p3, const NR::Point p4); + + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/display/testnr.cpp b/src/display/testnr.cpp new file mode 100644 index 000000000..3a3478d28 --- /dev/null +++ b/src/display/testnr.cpp @@ -0,0 +1,24 @@ +#include +#include "sp-arena.h" + +int +main (int argc, char ** argv) +{ + GtkWidget * w, * c; + + gtk_init (&argc, &argv); + + w = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + c = sp_arena_new (); + gtk_widget_show (c); + + gtk_container_add (GTK_CONTAINER (w), c); + + gtk_widget_show (w); + + gtk_main (); + + return 0; +} + diff --git a/src/document-private.h b/src/document-private.h new file mode 100644 index 000000000..34e61a9a0 --- /dev/null +++ b/src/document-private.h @@ -0,0 +1,67 @@ +#ifndef __SP_DOCUMENT_PRIVATE_H__ +#define __SP_DOCUMENT_PRIVATE_H__ + +/* + * Seldom needed document data + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#include "xml/event-fns.h" +#include "sp-defs.h" +#include "sp-root.h" +#include "document.h" + +#include "composite-undo-stack-observer.h" + +#define SP_DOCUMENT_DEFS(d) ((SPObject *) SP_ROOT (SP_DOCUMENT_ROOT (d))->defs) + +namespace Inkscape { +namespace XML { +class Event; +} +} + + +struct SPDocumentPrivate { + typedef std::map IDChangedSignalMap; + typedef std::map ResourcesChangedSignalMap; + + GHashTable *iddef; /**< Dictionary of id -> SPObject mappings */ + GHashTable *reprdef; /**< Dictionary of Inkscape::XML::Node -> SPObject mappings */ + + /** Dictionary of signals for id changes */ + IDChangedSignalMap id_changed_signals; + + /* Resources */ + /* It is GHashTable of GSLists */ + GHashTable *resources; + ResourcesChangedSignalMap resources_changed_signals; + + SPDocument::ModifiedSignal modified_signal; + SPDocument::URISetSignal uri_set_signal; + SPDocument::ResizedSignal resized_signal; + SPDocument::ReconstructionStart _reconstruction_start_signal; + SPDocument::ReconstructionFinish _reconstruction_finish_signal; + + /* Undo/Redo state */ + guint sensitive: 1; /* If we save actions to undo stack */ + Inkscape::XML::Event * partial; /* partial undo log when interrupted */ + int history_size; + GSList * undo; /* Undo stack of reprs */ + GSList * redo; /* Redo stack of reprs */ + + /* Undo listener */ + Inkscape::CompositeUndoStackObserver undoStackObservers; + +}; + +#endif diff --git a/src/document-undo.cpp b/src/document-undo.cpp new file mode 100644 index 000000000..226d38127 --- /dev/null +++ b/src/document-undo.cpp @@ -0,0 +1,322 @@ +#define __SP_DOCUMENT_UNDO_C__ + +/** \file + * Undo/Redo stack implementation + * + * Authors: + * Lauris Kaplinski + * MenTaLguY + * + * Copyright (C) 1999-2003 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + * + * Using the split document model gives sodipodi a very simple and clean + * undo implementation. Whenever mutation occurs in the XML tree, + * SPObject invokes one of the five corresponding handlers of its + * container document. This writes down a generic description of the + * given action, and appends it to the recent action list, kept by the + * document. There will be as many action records as there are mutation + * events, which are all kept and processed together in the undo + * stack. Two methods exist to indicate that the given action is completed: + * + * \verbatim + void sp_document_done (SPDocument *document) + void sp_document_maybe_done (SPDocument *document, const unsigned char *key) \endverbatim + * + * Both move the recent action list into the undo stack and clear the + * list afterwards. While the first method does an unconditional push, + * the second one first checks the key of the most recent stack entry. If + * the keys are identical, the current action list is appended to the + * existing stack entry, instead of pushing it onto its own. This + * behaviour can be used to collect multi-step actions (like winding the + * Gtk spinbutton) from the UI into a single undoable step. + * + * For controls implemented by Sodipodi itself, implementing undo as a + * single step is usually done in a more efficent way. Most controls have + * the abstract model of grab, drag, release, and change user + * action. During the grab phase, all modifications are done to the + * SPObject directly - i.e. they do not change XML tree, and thus do not + * generate undo actions either. Only at the release phase (normally + * associated with releasing the mousebutton), changes are written back + * to the XML tree, thus generating only a single set of undo actions. + * (Lauris Kaplinski) + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + + + +#if HAVE_STRING_H +#endif + + +#if HAVE_STDLIB_H +#endif + +#include "xml/repr.h" +#include "document-private.h" +#include "inkscape.h" +#include "debug/event-tracker.h" +#include "debug/simple-event.h" + + +/* + * Undo & redo + */ +/** + * Set undo sensitivity. + * + * \note + * Since undo sensitivity needs to be nested, setting undo sensitivity + * should be done like this: + *\verbatim + gboolean saved = sp_document_get_undo_sensitive(document); + sp_document_set_undo_sensitive(document, FALSE); + ... do stuff ... + sp_document_set_undo_sensitive(document, saved); \endverbatim + */ +void +sp_document_set_undo_sensitive (SPDocument *doc, gboolean sensitive) +{ + g_assert (doc != NULL); + g_assert (doc->priv != NULL); + + if ( !(sensitive) == !(doc->priv->sensitive) ) + return; + + if (sensitive) { + sp_repr_begin_transaction (doc->rdoc); + } else { + doc->priv->partial = sp_repr_coalesce_log ( + doc->priv->partial, + sp_repr_commit_undoable (doc->rdoc) + ); + } + + doc->priv->sensitive = !!sensitive; +} + +gboolean sp_document_get_undo_sensitive(SPDocument const *document) { + g_assert(document != NULL); + g_assert(document->priv != NULL); + + return document->priv->sensitive; +} + +void +sp_document_done (SPDocument *doc) +{ + sp_document_maybe_done (doc, NULL); +} + +void +sp_document_reset_key (Inkscape::Application *inkscape, SPDesktop *desktop, GtkObject *base) +{ + SPDocument *doc = (SPDocument *) base; + doc->actionkey = NULL; +} + +void +sp_document_maybe_done (SPDocument *doc, const gchar *key) +{ + g_assert (doc != NULL); + g_assert (doc->priv != NULL); + g_assert (doc->priv->sensitive); + + doc->collectOrphans(); + + sp_document_ensure_up_to_date (doc); + + sp_document_clear_redo (doc); + + Inkscape::XML::Event *log = sp_repr_coalesce_log (doc->priv->partial, sp_repr_commit_undoable (doc->rdoc)); + doc->priv->partial = NULL; + + if (!log) { + sp_repr_begin_transaction (doc->rdoc); + return; + } + + if (key && doc->actionkey && !strcmp (key, doc->actionkey) && doc->priv->undo) { + doc->priv->undo->data = sp_repr_coalesce_log ((Inkscape::XML::Event *)doc->priv->undo->data, log); + } else { + doc->priv->undo = g_slist_prepend (doc->priv->undo, log); + doc->priv->history_size++; + doc->priv->undoStackObservers.notifyUndoCommitEvent(log); + } + + doc->actionkey = key; + + doc->virgin = FALSE; + if (!doc->rroot->attribute("sodipodi:modified")) { + doc->rroot->setAttribute("sodipodi:modified", "true"); + } + + sp_repr_begin_transaction (doc->rdoc); +} + +void +sp_document_cancel (SPDocument *doc) +{ + g_assert (doc != NULL); + g_assert (doc->priv != NULL); + g_assert (doc->priv->sensitive); + + sp_repr_rollback (doc->rdoc); + + if (doc->priv->partial) { + sp_repr_undo_log (doc->priv->partial); + sp_repr_free_log (doc->priv->partial); + doc->priv->partial = NULL; + } + + sp_repr_begin_transaction (doc->rdoc); +} + +namespace { + +void finish_incomplete_transaction(SPDocument &doc) { + SPDocumentPrivate &priv=*doc.priv; + Inkscape::XML::Event *log=sp_repr_commit_undoable(doc.rdoc); + if (log || priv.partial) { + g_warning ("Incomplete undo transaction:"); + priv.partial = sp_repr_coalesce_log(priv.partial, log); + sp_repr_debug_print_log(priv.partial); + priv.undo = g_slist_prepend(priv.undo, priv.partial); + priv.partial = NULL; + } +} + +} + +gboolean +sp_document_undo (SPDocument *doc) +{ + using Inkscape::Debug::EventTracker; + using Inkscape::Debug::SimpleEvent; + + gboolean ret; + + EventTracker > tracker("undo"); + + g_assert (doc != NULL); + g_assert (doc->priv != NULL); + g_assert (doc->priv->sensitive); + + doc->priv->sensitive = FALSE; + + doc->actionkey = NULL; + + finish_incomplete_transaction(*doc); + + if (doc->priv->undo) { + Inkscape::XML::Event *log=(Inkscape::XML::Event *)doc->priv->undo->data; + doc->priv->undo = g_slist_remove (doc->priv->undo, log); + sp_repr_undo_log (log); + doc->priv->redo = g_slist_prepend (doc->priv->redo, log); + + doc->rroot->setAttribute("sodipodi:modified", "true"); + doc->priv->undoStackObservers.notifyUndoEvent(log); + + ret = TRUE; + } else { + ret = FALSE; + } + + sp_repr_begin_transaction (doc->rdoc); + + doc->priv->sensitive = TRUE; + + if (ret) + inkscape_external_change(); + + return ret; +} + +gboolean +sp_document_redo (SPDocument *doc) +{ + using Inkscape::Debug::EventTracker; + using Inkscape::Debug::SimpleEvent; + + gboolean ret; + + EventTracker > tracker("redo"); + + g_assert (doc != NULL); + g_assert (doc->priv != NULL); + g_assert (doc->priv->sensitive); + + doc->priv->sensitive = FALSE; + + doc->actionkey = NULL; + + finish_incomplete_transaction(*doc); + + if (doc->priv->redo) { + Inkscape::XML::Event *log=(Inkscape::XML::Event *)doc->priv->redo->data; + doc->priv->redo = g_slist_remove (doc->priv->redo, log); + sp_repr_replay_log (log); + doc->priv->undo = g_slist_prepend (doc->priv->undo, log); + + doc->rroot->setAttribute("sodipodi:modified", "true"); + doc->priv->undoStackObservers.notifyRedoEvent(log); + + ret = TRUE; + } else { + ret = FALSE; + } + + sp_repr_begin_transaction (doc->rdoc); + + doc->priv->sensitive = TRUE; + + if (ret) + inkscape_external_change(); + + return ret; +} + +void +sp_document_clear_undo (SPDocument *doc) +{ + while (doc->priv->undo) { + GSList *current; + + current = doc->priv->undo; + doc->priv->undo = current->next; + doc->priv->history_size--; + + sp_repr_free_log ((Inkscape::XML::Event *)current->data); + g_slist_free_1 (current); + } +} + +void +sp_document_clear_redo (SPDocument *doc) +{ + while (doc->priv->redo) { + GSList *current; + + current = doc->priv->redo; + doc->priv->redo = current->next; + doc->priv->history_size--; + + sp_repr_free_log ((Inkscape::XML::Event *)current->data); + g_slist_free_1 (current); + } +} +/* + 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/document.cpp b/src/document.cpp new file mode 100644 index 000000000..f69c480f7 --- /dev/null +++ b/src/document.cpp @@ -0,0 +1,1093 @@ +#define __SP_DOCUMENT_C__ + +/** \file + * SPDocument manipulation + * + * Authors: + * Lauris Kaplinski + * MenTaLguY + * bulia byak + * + * Copyright (C) 2004-2005 MenTaLguY + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +/** \class SPDocument + * SPDocument serves as the container of both model trees (agnostic XML + * and typed object tree), and implements all of the document-level + * functionality used by the program. Many document level operations, like + * load, save, print, export and so on, use SPDocument as their basic datatype. + * + * SPDocument implements undo and redo stacks and an id-based object + * dictionary. Thanks to unique id attributes, the latter can be used to + * map from the XML tree back to the object tree. + * + * SPDocument performs the basic operations needed for asynchronous + * update notification (SPObject ::modified virtual method), and implements + * the 'modified' signal, as well. + */ + + +#define noSP_DOCUMENT_DEBUG_IDLE +#define noSP_DOCUMENT_DEBUG_UNDO + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include +#include "application/application.h" +#include "application/editor.h" +#include "libnr/nr-matrix-fns.h" +#include "xml/repr.h" +#include "helper/units.h" +#include "inkscape-private.h" +#include "inkscape_version.h" +#include "sp-object-repr.h" +#include "document-private.h" +#include "dir-util.h" +#include "unit-constants.h" +#include "prefs-utils.h" + +#include "display/nr-arena-item.h" + +#include "dialogs/rdf.h" + +#define A4_WIDTH_STR "210mm" +#define A4_HEIGHT_STR "297mm" + +#define SP_DOCUMENT_UPDATE_PRIORITY (G_PRIORITY_HIGH_IDLE - 1) + + +static gint sp_document_idle_handler(gpointer data); + +gboolean sp_document_resource_list_free(gpointer key, gpointer value, gpointer data); + +static gint doc_count = 0; + +SPDocument::SPDocument() { + SPDocumentPrivate *p; + + keepalive = FALSE; + virgin = TRUE; + + modified_id = 0; + + rdoc = NULL; + rroot = NULL; + root = NULL; + style_cascade = cr_cascade_new(NULL, NULL, NULL); + + uri = NULL; + base = NULL; + name = NULL; + + _collection_queue = NULL; + + p = new SPDocumentPrivate(); + + p->iddef = g_hash_table_new(g_direct_hash, g_direct_equal); + p->reprdef = g_hash_table_new(g_direct_hash, g_direct_equal); + + p->resources = g_hash_table_new(g_str_hash, g_str_equal); + + p->sensitive = FALSE; + p->partial = NULL; + p->history_size = 0; + p->undo = NULL; + p->redo = NULL; + + priv = p; +} + +SPDocument::~SPDocument() { + collectOrphans(); + + if (priv) { + inkscape_remove_document(this); + + if (priv->partial) { + sp_repr_free_log(priv->partial); + priv->partial = NULL; + } + + sp_document_clear_redo(this); + sp_document_clear_undo(this); + + if (root) { + sp_object_invoke_release(root); + g_object_unref(G_OBJECT(root)); + root = NULL; + } + + if (priv->iddef) g_hash_table_destroy(priv->iddef); + if (priv->reprdef) g_hash_table_destroy(priv->reprdef); + + if (rdoc) Inkscape::GC::release(rdoc); + + /* Free resources */ + g_hash_table_foreach_remove(priv->resources, sp_document_resource_list_free, this); + g_hash_table_destroy(priv->resources); + + delete priv; + priv = NULL; + } + + cr_cascade_unref(style_cascade); + style_cascade = NULL; + + if (name) { + g_free(name); + name = NULL; + } + if (base) { + g_free(base); + base = NULL; + } + if (uri) { + g_free(uri); + uri = NULL; + } + + if (modified_id) { + gtk_idle_remove(modified_id); + modified_id = 0; + } + + _selection_changed_connection.disconnect(); + _desktop_activated_connection.disconnect(); + + if (keepalive) { + inkscape_unref(); + keepalive = FALSE; + } + + //delete this->_whiteboard_session_manager; +} + +void SPDocument::queueForOrphanCollection(SPObject *object) { + g_return_if_fail(object != NULL); + g_return_if_fail(SP_OBJECT_DOCUMENT(object) == this); + + sp_object_ref(object, NULL); + _collection_queue = g_slist_prepend(_collection_queue, object); +} + +void SPDocument::collectOrphans() { + while (_collection_queue) { + GSList *objects=_collection_queue; + _collection_queue = NULL; + for ( GSList *iter=objects ; iter ; iter = iter->next ) { + SPObject *object=reinterpret_cast(iter->data); + object->collectOrphan(); + sp_object_unref(object, NULL); + } + g_slist_free(objects); + } +} + +void SPDocument::reset_key (void *dummy) +{ + actionkey = NULL; +} + +static SPDocument * +sp_document_create(Inkscape::XML::Document *rdoc, + gchar const *uri, + gchar const *base, + gchar const *name, + unsigned int keepalive) +{ + SPDocument *document; + Inkscape::XML::Node *rroot; + Inkscape::Version sodipodi_version; + + rroot = sp_repr_document_root(rdoc); + + document = new SPDocument(); + + document->keepalive = keepalive; + + document->rdoc = rdoc; + document->rroot = rroot; + +#ifndef WIN32 + prepend_current_dir_if_relative(&(document->uri), uri); +#else + // FIXME: it may be that prepend_current_dir_if_relative works OK on windows too, test! + document->uri = uri? g_strdup(uri) : NULL; +#endif + + // base is simply the part of the path before filename; e.g. when running "inkscape ../file.svg" the base is "../" + // which is why we use g_get_current_dir() in calculating the abs path above + //This is NULL for a new document + if (base) + document->base = g_strdup(base); + else + document->base = NULL; + document->name = g_strdup(name); + + document->root = sp_object_repr_build_tree(document, rroot); + + sodipodi_version = SP_ROOT(document->root)->version.sodipodi; + + /* fixme: Not sure about this, but lets assume ::build updates */ + rroot->setAttribute("sodipodi:version", SODIPODI_VERSION); + rroot->setAttribute("inkscape:version", INKSCAPE_VERSION); + /* fixme: Again, I moved these here to allow version determining in ::build (Lauris) */ + + /* Quick hack 2 - get default image size into document */ + if (!rroot->attribute("width")) rroot->setAttribute("width", A4_WIDTH_STR); + if (!rroot->attribute("height")) rroot->setAttribute("height", A4_HEIGHT_STR); + /* End of quick hack 2 */ + + /* Quick hack 3 - Set uri attributes */ + if (uri) { + /* fixme: Think, what this means for images (Lauris) */ + rroot->setAttribute("sodipodi:docname", uri); + if (document->base) + rroot->setAttribute("sodipodi:docbase", document->base); + } + /* End of quick hack 3 */ + + // creating namedview + if (!sp_item_group_get_child_by_name((SPGroup *) document->root, NULL, "sodipodi:namedview")) { + // if there's none in the document already, + Inkscape::XML::Node *r = NULL; + Inkscape::XML::Node *rnew = NULL; + r = inkscape_get_repr(INKSCAPE, "template.base"); + // see if there's a template with id="base" in the preferences + if (!r) { + // if there's none, create an empty element + rnew = sp_repr_new("sodipodi:namedview"); + rnew->setAttribute("id", "base"); + } else { + // otherwise, take from preferences + rnew = r->duplicate(); + } + // insert into the document + rroot->addChild(rnew, NULL); + // clean up + Inkscape::GC::release(rnew); + } + + /* Defs */ + if (!SP_ROOT(document->root)->defs) { + Inkscape::XML::Node *r; + r = sp_repr_new("svg:defs"); + rroot->addChild(r, NULL); + Inkscape::GC::release(r); + g_assert(SP_ROOT(document->root)->defs); + } + + /* Default RDF */ + rdf_set_defaults( document ); + + if (keepalive) { + inkscape_ref(); + } + + sp_document_set_undo_sensitive(document, TRUE); + + // reset undo key when selection changes, so that same-key actions on different objects are not coalesced + if (!Inkscape::NSApplication::Application::getNewGui()) { + g_signal_connect(G_OBJECT(INKSCAPE), "change_selection", + G_CALLBACK(sp_document_reset_key), document); + g_signal_connect(G_OBJECT(INKSCAPE), "activate_desktop", + G_CALLBACK(sp_document_reset_key), document); + } else { + document->_selection_changed_connection = Inkscape::NSApplication::Editor::connectSelectionChanged (sigc::mem_fun (*document, &SPDocument::reset_key)); + document->_desktop_activated_connection = Inkscape::NSApplication::Editor::connectDesktopActivated (sigc::mem_fun (*document, &SPDocument::reset_key)); + } + inkscape_add_document(document); + + return document; +} + +/** + * Fetches document from URI, or creates new, if NULL; public document + * appears in document list. + */ +SPDocument * +sp_document_new(gchar const *uri, unsigned int keepalive, bool make_new) +{ + SPDocument *doc; + Inkscape::XML::Document *rdoc; + gchar *base = NULL; + gchar *name = NULL; + + if (uri) { + Inkscape::XML::Node *rroot; + gchar *s, *p; + /* Try to fetch repr from file */ + rdoc = sp_repr_read_file(uri, SP_SVG_NS_URI); + /* If file cannot be loaded, return NULL without warning */ + if (rdoc == NULL) return NULL; + rroot = sp_repr_document_root(rdoc); + /* If xml file is not svg, return NULL without warning */ + /* fixme: destroy document */ + if (strcmp(rroot->name(), "svg:svg") != 0) return NULL; + s = g_strdup(uri); + p = strrchr(s, '/'); + if (p) { + name = g_strdup(p + 1); + p[1] = '\0'; + base = g_strdup(s); + } else { + base = NULL; + name = g_strdup(uri); + } + g_free(s); + } else { + rdoc = sp_repr_document_new("svg:svg"); + } + + if (make_new) { + base = NULL; + uri = NULL; + name = g_strdup_printf(_("New document %d"), ++doc_count); + } + + //# These should be set by now + g_assert(name); + + doc = sp_document_create(rdoc, uri, base, name, keepalive); + + g_free(base); + g_free(name); + + return doc; +} + +SPDocument * +sp_document_new_from_mem(gchar const *buffer, gint length, unsigned int keepalive) +{ + SPDocument *doc; + Inkscape::XML::Document *rdoc; + Inkscape::XML::Node *rroot; + gchar *name; + + rdoc = sp_repr_read_mem(buffer, length, SP_SVG_NS_URI); + + /* If it cannot be loaded, return NULL without warning */ + if (rdoc == NULL) return NULL; + + rroot = sp_repr_document_root(rdoc); + /* If xml file is not svg, return NULL without warning */ + /* fixme: destroy document */ + if (strcmp(rroot->name(), "svg:svg") != 0) return NULL; + + name = g_strdup_printf(_("Memory document %d"), ++doc_count); + + doc = sp_document_create(rdoc, NULL, NULL, name, keepalive); + + return doc; +} + +SPDocument *sp_document_new_dummy() { + SPDocument *document = new SPDocument(); + inkscape_add_document(document); + return document; +} + +SPDocument * +sp_document_ref(SPDocument *doc) +{ + g_return_val_if_fail(doc != NULL, NULL); + Inkscape::GC::anchor(doc); + return doc; +} + +SPDocument * +sp_document_unref(SPDocument *doc) +{ + g_return_val_if_fail(doc != NULL, NULL); + Inkscape::GC::release(doc); + return NULL; +} + +gdouble sp_document_width(SPDocument *document) +{ + g_return_val_if_fail(document != NULL, 0.0); + g_return_val_if_fail(document->priv != NULL, 0.0); + g_return_val_if_fail(document->root != NULL, 0.0); + + return SP_ROOT(document->root)->width.computed; +} + +void +sp_document_set_width (SPDocument *document, gdouble width, const SPUnit *unit) +{ + SPRoot *root = SP_ROOT(document->root); + + if (root->width.unit == SVGLength::PERCENT && root->viewBox_set) { // set to viewBox= + root->viewBox.x1 = root->viewBox.x0 + sp_units_get_pixels (width, *unit); + } else { // set to width= + root->width.computed = sp_units_get_pixels (width, *unit); + /* SVG does not support meters as a unit, so we must translate meters to + * cm when writing */ + if (!strcmp(unit->abbr, "m")) { + root->width.value = 100*width; + root->width.unit = SVGLength::CM; + } else { + root->width.value = width; + root->width.unit = (SVGLength::Unit) sp_unit_get_svg_unit(unit); + } + } + + SP_OBJECT (root)->updateRepr(); +} + +void sp_document_set_height (SPDocument * document, gdouble height, const SPUnit *unit) +{ + SPRoot *root = SP_ROOT(document->root); + + if (root->height.unit == SVGLength::PERCENT && root->viewBox_set) { // set to viewBox= + root->viewBox.y1 = root->viewBox.y0 + sp_units_get_pixels (height, *unit); + } else { // set to height= + root->height.computed = sp_units_get_pixels (height, *unit); + /* SVG does not support meters as a unit, so we must translate meters to + * cm when writing */ + if (!strcmp(unit->abbr, "m")) { + root->height.value = 100*height; + root->height.unit = SVGLength::CM; + } else { + root->height.value = height; + root->height.unit = (SVGLength::Unit) sp_unit_get_svg_unit(unit); + } + } + + SP_OBJECT (root)->updateRepr(); +} + +gdouble sp_document_height(SPDocument *document) +{ + g_return_val_if_fail(document != NULL, 0.0); + g_return_val_if_fail(document->priv != NULL, 0.0); + g_return_val_if_fail(document->root != NULL, 0.0); + + return SP_ROOT(document->root)->height.computed; +} + +void sp_document_set_uri(SPDocument *document, gchar const *uri) +{ + g_return_if_fail(document != NULL); + + if (document->name) { + g_free(document->name); + document->name = NULL; + } + if (document->base) { + g_free(document->base); + document->base = NULL; + } + if (document->uri) { + g_free(document->uri); + document->uri = NULL; + } + + if (uri) { + +#ifndef WIN32 + prepend_current_dir_if_relative(&(document->uri), uri); +#else + // FIXME: it may be that prepend_current_dir_if_relative works OK on windows too, test! + document->uri = g_strdup(uri); +#endif + + /* fixme: Think, what this means for images (Lauris) */ + document->base = g_path_get_dirname(document->uri); + document->name = g_path_get_basename(document->uri); + + } else { + document->uri = g_strdup_printf(_("Unnamed document %d"), ++doc_count); + document->base = NULL; + document->name = g_strdup(document->uri); + } + + // Update saveable repr attributes. + Inkscape::XML::Node *repr = sp_document_repr_root(document); + // changing uri in the document repr must not be not undoable + gboolean saved = sp_document_get_undo_sensitive(document); + sp_document_set_undo_sensitive(document, FALSE); + if (document->base) + repr->setAttribute("sodipodi:docbase", document->base); + + repr->setAttribute("sodipodi:docname", document->name); + sp_document_set_undo_sensitive(document, saved); + + document->priv->uri_set_signal.emit(document->uri); +} + +void +sp_document_resized_signal_emit(SPDocument *doc, gdouble width, gdouble height) +{ + g_return_if_fail(doc != NULL); + + doc->priv->resized_signal.emit(width, height); +} + +sigc::connection SPDocument::connectModified(SPDocument::ModifiedSignal::slot_type slot) +{ + return priv->modified_signal.connect(slot); +} + +sigc::connection SPDocument::connectURISet(SPDocument::URISetSignal::slot_type slot) +{ + return priv->uri_set_signal.connect(slot); +} + +sigc::connection SPDocument::connectResized(SPDocument::ResizedSignal::slot_type slot) +{ + return priv->resized_signal.connect(slot); +} + +sigc::connection +SPDocument::connectReconstructionStart(SPDocument::ReconstructionStart::slot_type slot) +{ + return priv->_reconstruction_start_signal.connect(slot); +} + +void +SPDocument::emitReconstructionStart(void) +{ + // printf("Starting Reconstruction\n"); + priv->_reconstruction_start_signal.emit(); + return; +} + +sigc::connection +SPDocument::connectReconstructionFinish(SPDocument::ReconstructionFinish::slot_type slot) +{ + return priv->_reconstruction_finish_signal.connect(slot); +} + +void +SPDocument::emitReconstructionFinish(void) +{ + // printf("Finishing Reconstruction\n"); + priv->_reconstruction_finish_signal.emit(); + return; +} + + +void SPDocument::_emitModified() { + static guint const flags = SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG; + root->emitModified(0); + priv->modified_signal.emit(flags); +} + +void SPDocument::bindObjectToId(gchar const *id, SPObject *object) { + GQuark idq = g_quark_from_string(id); + + if (object) { + g_assert(g_hash_table_lookup(priv->iddef, GINT_TO_POINTER(idq)) == NULL); + g_hash_table_insert(priv->iddef, GINT_TO_POINTER(idq), object); + } else { + g_assert(g_hash_table_lookup(priv->iddef, GINT_TO_POINTER(idq)) != NULL); + g_hash_table_remove(priv->iddef, GINT_TO_POINTER(idq)); + } + + SPDocumentPrivate::IDChangedSignalMap::iterator pos; + + pos = priv->id_changed_signals.find(idq); + if ( pos != priv->id_changed_signals.end() ) { + if (!(*pos).second.empty()) { + (*pos).second.emit(object); + } else { // discard unused signal + priv->id_changed_signals.erase(pos); + } + } +} + +void +SPDocument::addUndoObserver(Inkscape::UndoStackObserver& observer) +{ + this->priv->undoStackObservers.add(observer); +} + +void +SPDocument::removeUndoObserver(Inkscape::UndoStackObserver& observer) +{ + this->priv->undoStackObservers.remove(observer); +} + +SPObject *SPDocument::getObjectById(gchar const *id) { + g_return_val_if_fail(id != NULL, NULL); + + GQuark idq = g_quark_from_string(id); + return (SPObject*)g_hash_table_lookup(priv->iddef, GINT_TO_POINTER(idq)); +} + +sigc::connection SPDocument::connectIdChanged(gchar const *id, + SPDocument::IDChangedSignal::slot_type slot) +{ + return priv->id_changed_signals[g_quark_from_string(id)].connect(slot); +} + +void SPDocument::bindObjectToRepr(Inkscape::XML::Node *repr, SPObject *object) { + if (object) { + g_assert(g_hash_table_lookup(priv->reprdef, repr) == NULL); + g_hash_table_insert(priv->reprdef, repr, object); + } else { + g_assert(g_hash_table_lookup(priv->reprdef, repr) != NULL); + g_hash_table_remove(priv->reprdef, repr); + } +} + +SPObject *SPDocument::getObjectByRepr(Inkscape::XML::Node *repr) { + g_return_val_if_fail(repr != NULL, NULL); + return (SPObject*)g_hash_table_lookup(priv->reprdef, repr); +} + +/* Object modification root handler */ + +void +sp_document_request_modified(SPDocument *doc) +{ + if (!doc->modified_id) { + doc->modified_id = gtk_idle_add_priority(SP_DOCUMENT_UPDATE_PRIORITY, sp_document_idle_handler, doc); + } +} + +void +sp_document_setup_viewport (SPDocument *doc, SPItemCtx *ctx) +{ + ctx->ctx.flags = 0; + ctx->i2doc = NR::identity(); + /* Set up viewport in case svg has it defined as percentages */ + if (SP_ROOT(doc->root)->viewBox_set) { // if set, take from viewBox + ctx->vp.x0 = SP_ROOT(doc->root)->viewBox.x0; + ctx->vp.y0 = SP_ROOT(doc->root)->viewBox.y0; + ctx->vp.x1 = SP_ROOT(doc->root)->viewBox.x1; + ctx->vp.y1 = SP_ROOT(doc->root)->viewBox.y1; + } else { // as a last resort, set size to A4 + ctx->vp.x0 = 0.0; + ctx->vp.y0 = 0.0; + ctx->vp.x1 = 210 * PX_PER_MM; + ctx->vp.y1 = 297 * PX_PER_MM; + } + ctx->i2vp = NR::identity(); +} + +gint +sp_document_ensure_up_to_date(SPDocument *doc) +{ + int lc; + lc = 32; + while (doc->root->uflags || doc->root->mflags) { + lc -= 1; + if (lc < 0) { + g_warning("More than 32 iterations while updating document '%s'", doc->uri); + if (doc->modified_id) { + /* Remove handler */ + gtk_idle_remove(doc->modified_id); + doc->modified_id = 0; + } + return FALSE; + } + /* Process updates */ + if (doc->root->uflags) { + SPItemCtx ctx; + sp_document_setup_viewport (doc, &ctx); + doc->root->updateDisplay((SPCtx *)&ctx, 0); + } + doc->_emitModified(); + } + if (doc->modified_id) { + /* Remove handler */ + gtk_idle_remove(doc->modified_id); + doc->modified_id = 0; + } + return TRUE; +} + +static gint +sp_document_idle_handler(gpointer data) +{ + SPDocument *doc; + int repeat; + + doc = static_cast(data); + +#ifdef SP_DOCUMENT_DEBUG_IDLE + g_print("->\n"); +#endif + + /* Process updates */ + if (doc->root->uflags) { + SPItemCtx ctx; + sp_document_setup_viewport (doc, &ctx); + + gboolean saved = sp_document_get_undo_sensitive(doc); + sp_document_set_undo_sensitive(doc, FALSE); + + doc->root->updateDisplay((SPCtx *)&ctx, 0); + + sp_document_set_undo_sensitive(doc, saved); + /* if (doc->root->uflags & SP_OBJECT_MODIFIED_FLAG) return TRUE; */ + } + + doc->_emitModified(); + + repeat = (doc->root->uflags || doc->root->mflags); + if (!repeat) doc->modified_id = 0; + return repeat; +} + +static bool is_within(NR::Rect const &area, NR::Rect const &box) +{ + return area.contains(box); +} + +static bool overlaps(NR::Rect const &area, NR::Rect const &box) +{ + return area.intersects(box); +} + +static GSList *find_items_in_area(GSList *s, SPGroup *group, unsigned int dkey, NR::Rect const &area, + bool (*test)(NR::Rect const &, NR::Rect const &), bool take_insensitive = false) +{ + g_return_val_if_fail(SP_IS_GROUP(group), s); + + for (SPObject *o = sp_object_first_child(SP_OBJECT(group)) ; o != NULL ; o = SP_OBJECT_NEXT(o) ) { + if (!SP_IS_ITEM(o)) { + continue; + } + if (SP_IS_GROUP(o) && SP_GROUP(o)->effectiveLayerMode(dkey) == SPGroup::LAYER ) { + s = find_items_in_area(s, SP_GROUP(o), dkey, area, test); + } else { + SPItem *child = SP_ITEM(o); + NR::Rect box = sp_item_bbox_desktop(child); + if (test(area, box) && (take_insensitive || child->isVisibleAndUnlocked(dkey))) { + s = g_slist_append(s, child); + } + } + } + + return s; +} + +/** +Returns true if an item is among the descendants of group (recursively). + */ +bool item_is_in_group(SPItem *item, SPGroup *group) +{ + for (SPObject *o = sp_object_first_child(SP_OBJECT(group)) ; o != NULL ; o = SP_OBJECT_NEXT(o) ) { + if (!SP_IS_ITEM(o)) continue; + if (SP_ITEM(o) == item) + return true; + if (SP_IS_GROUP(o)) + if (item_is_in_group(item, SP_GROUP(o))) + return true; + } + return false; +} + +/** +Returns the bottommost item from the list which is at the point, or NULL if none. +*/ +SPItem* +sp_document_item_from_list_at_point_bottom(unsigned int dkey, SPGroup *group, GSList const *list, + NR::Point const p, bool take_insensitive) +{ + g_return_val_if_fail(group, NULL); + + gdouble delta = prefs_get_double_attribute ("options.cursortolerance", "value", 1.0); + + for (SPObject *o = sp_object_first_child(SP_OBJECT(group)) ; o != NULL ; o = SP_OBJECT_NEXT(o) ) { + + if (!SP_IS_ITEM(o)) continue; + + SPItem *item = SP_ITEM(o); + NRArenaItem *arenaitem = sp_item_get_arenaitem(item, dkey); + if (arenaitem && nr_arena_item_invoke_pick(arenaitem, p, delta, 1) != NULL + && (take_insensitive || item->isVisibleAndUnlocked(dkey))) { + if (g_slist_find((GSList *) list, item) != NULL) + return item; + } + + if (SP_IS_GROUP(o)) { + SPItem *found = sp_document_item_from_list_at_point_bottom(dkey, SP_GROUP(o), list, p, take_insensitive); + if (found) + return found; + } + + } + return NULL; +} + +/** +Returns the topmost (in z-order) item from the descendants of group (recursively) which +is at the point p, or NULL if none. Honors into_groups on whether to recurse into +non-layer groups or not. Honors take_insensitive on whether to return insensitive +items. If upto != NULL, then if item upto is encountered (at any level), stops searching +upwards in z-order and returns what it has found so far (i.e. the found item is +guaranteed to be lower than upto). + */ +SPItem* +find_item_at_point(unsigned int dkey, SPGroup *group, NR::Point const p, gboolean into_groups, bool take_insensitive = false, SPItem *upto = NULL) +{ + SPItem *seen = NULL, *newseen = NULL; + + gdouble delta = prefs_get_double_attribute ("options.cursortolerance", "value", 1.0); + + for (SPObject *o = sp_object_first_child(SP_OBJECT(group)) ; o != NULL ; o = SP_OBJECT_NEXT(o) ) { + if (!SP_IS_ITEM(o)) continue; + + if (upto && SP_ITEM(o) == upto) + break; + + if (SP_IS_GROUP(o) && (SP_GROUP(o)->effectiveLayerMode(dkey) == SPGroup::LAYER || into_groups)) { + // if nothing found yet, recurse into the group + newseen = find_item_at_point(dkey, SP_GROUP(o), p, into_groups, take_insensitive, upto); + if (newseen) { + seen = newseen; + newseen = NULL; + } + + if (item_is_in_group(upto, SP_GROUP(o))) + break; + + } else { + SPItem *child = SP_ITEM(o); + NRArenaItem *arenaitem = sp_item_get_arenaitem(child, dkey); + + // seen remembers the last (topmost) of items pickable at this point + if (arenaitem && nr_arena_item_invoke_pick(arenaitem, p, delta, 1) != NULL + && (take_insensitive || child->isVisibleAndUnlocked(dkey))) { + seen = child; + } + } + } + return seen; +} + +/** +Returns the topmost non-layer group from the descendants of group which is at point +p, or NULL if none. Recurses into layers but not into groups. + */ +SPItem* +find_group_at_point(unsigned int dkey, SPGroup *group, NR::Point const p) +{ + SPItem *seen = NULL; + + gdouble delta = prefs_get_double_attribute ("options.cursortolerance", "value", 1.0); + + for (SPObject *o = sp_object_first_child(SP_OBJECT(group)) ; o != NULL ; o = SP_OBJECT_NEXT(o) ) { + if (!SP_IS_ITEM(o)) continue; + if (SP_IS_GROUP(o) && SP_GROUP(o)->effectiveLayerMode(dkey) == SPGroup::LAYER) { + SPItem *newseen = find_group_at_point(dkey, SP_GROUP(o), p); + if (newseen) { + seen = newseen; + } + } + if (SP_IS_GROUP(o) && SP_GROUP(o)->effectiveLayerMode(dkey) != SPGroup::LAYER ) { + SPItem *child = SP_ITEM(o); + NRArenaItem *arenaitem = sp_item_get_arenaitem(child, dkey); + + // seen remembers the last (topmost) of groups pickable at this point + if (arenaitem && nr_arena_item_invoke_pick(arenaitem, p, delta, 1) != NULL) { + seen = child; + } + } + } + return seen; +} + +/* + * Return list of items, contained in box + * + * Assumes box is normalized (and g_asserts it!) + * + */ + +GSList *sp_document_items_in_box(SPDocument *document, unsigned int dkey, NR::Rect const &box) +{ + g_return_val_if_fail(document != NULL, NULL); + g_return_val_if_fail(document->priv != NULL, NULL); + + return find_items_in_area(NULL, SP_GROUP(document->root), dkey, box, is_within); +} + +/* + * Return list of items, that the parts of the item contained in box + * + * Assumes box is normalized (and g_asserts it!) + * + */ + +GSList *sp_document_partial_items_in_box(SPDocument *document, unsigned int dkey, NR::Rect const &box) +{ + g_return_val_if_fail(document != NULL, NULL); + g_return_val_if_fail(document->priv != NULL, NULL); + + return find_items_in_area(NULL, SP_GROUP(document->root), dkey, box, overlaps); +} + +SPItem * +sp_document_item_at_point(SPDocument *document, unsigned const key, NR::Point const p, + gboolean const into_groups, SPItem *upto) +{ + g_return_val_if_fail(document != NULL, NULL); + g_return_val_if_fail(document->priv != NULL, NULL); + + return find_item_at_point(key, SP_GROUP(document->root), p, into_groups, false, upto); +} + +SPItem* +sp_document_group_at_point(SPDocument *document, unsigned int key, NR::Point const p) +{ + g_return_val_if_fail(document != NULL, NULL); + g_return_val_if_fail(document->priv != NULL, NULL); + + return find_group_at_point(key, SP_GROUP(document->root), p); +} + + +/* Resource management */ + +gboolean +sp_document_add_resource(SPDocument *document, gchar const *key, SPObject *object) +{ + GSList *rlist; + GQuark q = g_quark_from_string(key); + + g_return_val_if_fail(document != NULL, FALSE); + g_return_val_if_fail(key != NULL, FALSE); + g_return_val_if_fail(*key != '\0', FALSE); + g_return_val_if_fail(object != NULL, FALSE); + g_return_val_if_fail(SP_IS_OBJECT(object), FALSE); + + if (SP_OBJECT_IS_CLONED(object)) + return FALSE; + + rlist = (GSList*)g_hash_table_lookup(document->priv->resources, key); + g_return_val_if_fail(!g_slist_find(rlist, object), FALSE); + rlist = g_slist_prepend(rlist, object); + g_hash_table_insert(document->priv->resources, (gpointer) key, rlist); + + document->priv->resources_changed_signals[q].emit(); + + return TRUE; +} + +gboolean +sp_document_remove_resource(SPDocument *document, gchar const *key, SPObject *object) +{ + GSList *rlist; + GQuark q = g_quark_from_string(key); + + g_return_val_if_fail(document != NULL, FALSE); + g_return_val_if_fail(key != NULL, FALSE); + g_return_val_if_fail(*key != '\0', FALSE); + g_return_val_if_fail(object != NULL, FALSE); + g_return_val_if_fail(SP_IS_OBJECT(object), FALSE); + + rlist = (GSList*)g_hash_table_lookup(document->priv->resources, key); + g_return_val_if_fail(rlist != NULL, FALSE); + g_return_val_if_fail(g_slist_find(rlist, object), FALSE); + rlist = g_slist_remove(rlist, object); + g_hash_table_insert(document->priv->resources, (gpointer) key, rlist); + + document->priv->resources_changed_signals[q].emit(); + + return TRUE; +} + +GSList const * +sp_document_get_resource_list(SPDocument *document, gchar const *key) +{ + g_return_val_if_fail(document != NULL, NULL); + g_return_val_if_fail(key != NULL, NULL); + g_return_val_if_fail(*key != '\0', NULL); + + return (GSList*)g_hash_table_lookup(document->priv->resources, key); +} + +sigc::connection sp_document_resources_changed_connect(SPDocument *document, + gchar const *key, + SPDocument::ResourcesChangedSignal::slot_type slot) +{ + GQuark q = g_quark_from_string(key); + return document->priv->resources_changed_signals[q].connect(slot); +} + +/* Helpers */ + +gboolean +sp_document_resource_list_free(gpointer key, gpointer value, gpointer data) +{ + g_slist_free((GSList *) value); + return TRUE; +} + +unsigned int +count_objects_recursive(SPObject *obj, unsigned int count) +{ + count++; // obj itself + + for (SPObject *i = sp_object_first_child(obj); i != NULL; i = SP_OBJECT_NEXT(i)) { + count = count_objects_recursive(i, count); + } + + return count; +} + +unsigned int +objects_in_document(SPDocument *document) +{ + return count_objects_recursive(SP_DOCUMENT_ROOT(document), 0); +} + +void +vacuum_document_recursive(SPObject *obj) +{ + if (SP_IS_DEFS(obj)) { + for (SPObject *def = obj->firstChild(); def; def = SP_OBJECT_NEXT(def)) { + /* fixme: some inkscape-internal nodes in the future might not be collectable */ + def->requestOrphanCollection(); + } + } else { + for (SPObject *i = sp_object_first_child(obj); i != NULL; i = SP_OBJECT_NEXT(i)) { + vacuum_document_recursive(i); + } + } +} + +unsigned int +vacuum_document(SPDocument *document) +{ + unsigned int start = objects_in_document(document); + unsigned int end; + unsigned int newend = start; + + unsigned int iterations = 0; + + do { + end = newend; + + vacuum_document_recursive(SP_DOCUMENT_ROOT(document)); + document->collectOrphans(); + iterations++; + + newend = objects_in_document(document); + + } while (iterations < 100 && newend < end); + + return start - newend; +} + + +/* + 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 : diff --git a/src/document.h b/src/document.h new file mode 100644 index 000000000..da8b656ef --- /dev/null +++ b/src/document.h @@ -0,0 +1,218 @@ +#ifndef __SP_DOCUMENT_H__ +#define __SP_DOCUMENT_H__ + +/** \file + * SPDocument: Typed SVG document implementation + * + * Authors: + * Lauris Kaplinski + * MenTaLguY + * + * Copyright (C) 2004-2005 MenTaLguY + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#include +#include + +#include "libcroco/cr-cascade.h" +#include "libnr/nr-forward.h" + +#include "gc-managed.h" +#include "gc-finalized.h" +#include "gc-anchored.h" + +struct SPDesktop; +struct SPItem; +struct SPObject; +struct SPGroup; + +namespace Inkscape { + struct Application; + class Selection; + class UndoStackObserver; + namespace XML { + class Document; + class Node; + } +} + +class SPDocumentPrivate; + +/// Typed SVG document implementation. +struct SPDocument : public Inkscape::GC::Managed<>, + public Inkscape::GC::Finalized, + public Inkscape::GC::Anchored +{ + typedef sigc::signal IDChangedSignal; + typedef sigc::signal ResourcesChangedSignal; + typedef sigc::signal ModifiedSignal; + typedef sigc::signal URISetSignal; + typedef sigc::signal ResizedSignal; + typedef sigc::signal ReconstructionStart; + typedef sigc::signal ReconstructionFinish; + + SPDocument(); + ~SPDocument(); + + unsigned int keepalive : 1; + unsigned int virgin : 1; ///< Has the document never been touched? + + Inkscape::XML::Document *rdoc; ///< Our Inkscape::XML::Document + Inkscape::XML::Node *rroot; ///< Root element of Inkscape::XML::Document + SPObject *root; ///< Our SPRoot + CRCascade *style_cascade; + + gchar *uri; ///< URI string or NULL + gchar *base; + gchar *name; + + SPDocumentPrivate *priv; + + /// Last action key + const gchar *actionkey; + /// Handler ID + guint modified_id; + + sigc::connection connectModified(ModifiedSignal::slot_type slot); + sigc::connection connectURISet(URISetSignal::slot_type slot); + sigc::connection connectResized(ResizedSignal::slot_type slot); + + void bindObjectToId(gchar const *id, SPObject *object); + SPObject *getObjectById(gchar const *id); + sigc::connection connectIdChanged(const gchar *id, IDChangedSignal::slot_type slot); + + void bindObjectToRepr(Inkscape::XML::Node *repr, SPObject *object); + SPObject *getObjectByRepr(Inkscape::XML::Node *repr); + + void queueForOrphanCollection(SPObject *object); + void collectOrphans(); + + void _emitModified(); + + GSList *_collection_queue; + + void addUndoObserver(Inkscape::UndoStackObserver& observer); + void removeUndoObserver(Inkscape::UndoStackObserver& observer); + +private: + SPDocument(SPDocument const &); // no copy + void operator=(SPDocument const &); // no assign + +public: + sigc::connection connectReconstructionStart(ReconstructionStart::slot_type slot); + sigc::connection connectReconstructionFinish (ReconstructionFinish::slot_type slot); + void emitReconstructionStart (void); + void emitReconstructionFinish (void); + + void reset_key (void *dummy); + sigc::connection _selection_changed_connection; + sigc::connection _desktop_activated_connection; +}; + +SPDocument *sp_document_new (const gchar *uri, unsigned int keepalive, bool make_new = false); +SPDocument *sp_document_new_from_mem (const gchar *buffer, gint length, unsigned int keepalive); +SPDocument *sp_document_new_dummy(); + +SPDocument *sp_document_ref (SPDocument *doc); +SPDocument *sp_document_unref (SPDocument *doc); + +/* + * Access methods + */ + +#define sp_document_repr_doc(d) (d->rdoc) +#define sp_document_repr_root(d) (d->rroot) +#define sp_document_root(d) (d->root) +#define SP_DOCUMENT_ROOT(d) (d->root) + +gdouble sp_document_width (SPDocument * document); +gdouble sp_document_height (SPDocument * document); + +struct SPUnit; + +void sp_document_set_width (SPDocument * document, gdouble width, const SPUnit *unit); +void sp_document_set_height (SPDocument * document, gdouble height, const SPUnit *unit); + +#define SP_DOCUMENT_URI(d) (d->uri) +#define SP_DOCUMENT_NAME(d) (d->name) +#define SP_DOCUMENT_BASE(d) (d->base) + +/* + * Dictionary + */ + +/* + * Undo & redo + */ + +void sp_document_set_undo_sensitive (SPDocument * document, gboolean sensitive); +gboolean sp_document_get_undo_sensitive (SPDocument const * document); + +void sp_document_clear_undo (SPDocument * document); +void sp_document_clear_redo (SPDocument * document); + +void sp_document_child_added (SPDocument *doc, SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref); +void sp_document_child_removed (SPDocument *doc, SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref); +void sp_document_attr_changed (SPDocument *doc, SPObject *object, const gchar *key, const gchar *oldval, const gchar *newval); +void sp_document_content_changed (SPDocument *doc, SPObject *object, const gchar *oldcontent, const gchar *newcontent); +void sp_document_order_changed (SPDocument *doc, SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *oldref, Inkscape::XML::Node *newref); + +/* Object modification root handler */ +void sp_document_request_modified (SPDocument *doc); +gint sp_document_ensure_up_to_date (SPDocument *doc); + +/* Save all previous actions to stack, as one undo step */ +void sp_document_done (SPDocument *document); +void sp_document_maybe_done (SPDocument *document, const gchar *key); +void sp_document_reset_key (Inkscape::Application *inkscape, SPDesktop *desktop, GtkObject *base); + +/* Cancel (and revert) current unsaved actions */ +void sp_document_cancel (SPDocument *document); + +/* Undo and redo */ +gboolean sp_document_undo (SPDocument * document); +gboolean sp_document_redo (SPDocument * document); + +/* Resource management */ +gboolean sp_document_add_resource (SPDocument *document, const gchar *key, SPObject *object); +gboolean sp_document_remove_resource (SPDocument *document, const gchar *key, SPObject *object); +const GSList *sp_document_get_resource_list (SPDocument *document, const gchar *key); +sigc::connection sp_document_resources_changed_connect(SPDocument *document, const gchar *key, SPDocument::ResourcesChangedSignal::slot_type slot); + + +/* + * Ideas: How to overcome style invalidation nightmare + * + * 1. There is reference request dictionary, that contains + * objects (styles) needing certain id. Object::build checks + * final id against it, and invokes necesary methods + * + * 2. Removing referenced object is simply prohibited - + * needs analyse, how we can deal with situations, where + * we simply want to ungroup etc. - probably we need + * Repr::reparent method :( [Or was it ;)] + * + */ + +/* + * Misc + */ + +GSList * sp_document_items_in_box(SPDocument *document, unsigned int dkey, NR::Rect const &box); +GSList * sp_document_partial_items_in_box(SPDocument *document, unsigned int dkey, NR::Rect const &box); +SPItem* sp_document_item_from_list_at_point_bottom (unsigned int dkey, SPGroup *group, const GSList *list, NR::Point const p, bool take_insensitive = false); +SPItem * sp_document_item_at_point (SPDocument *document, unsigned int key, NR::Point const p, gboolean into_groups, SPItem *upto = NULL); +SPItem * sp_document_group_at_point (SPDocument *document, unsigned int key, NR::Point const p); + +void sp_document_set_uri (SPDocument *document, const gchar *uri); +void sp_document_resized_signal_emit (SPDocument *doc, gdouble width, gdouble height); + +unsigned int vacuum_document (SPDocument *document); + +#endif diff --git a/src/dom/.cvsignore b/src/dom/.cvsignore new file mode 100644 index 000000000..38efca7bc --- /dev/null +++ b/src/dom/.cvsignore @@ -0,0 +1,3 @@ +.deps +.dirstamp +makefile diff --git a/src/dom/001.css b/src/dom/001.css new file mode 100755 index 000000000..db0207e05 --- /dev/null +++ b/src/dom/001.css @@ -0,0 +1,214 @@ +/* css Zen Garden default style - 'Tranquille' by Dave Shea - http://www.mezzoblue.com/ */ +/* css released under Creative Commons License - http://creativecommons.org/licenses/by-nc-sa/1.0/ */ +/* All associated graphics copyright 2003, Dave Shea */ +/* Added: May 7th, 2003 */ + + +/* IMPORTANT */ +/* This design is not a template. You may not reproduce it elsewhere without the + designer's written permission. However, feel free to study the CSS and use + techniques you learn from it elsewhere. */ + + +/* The Zen Garden default was the first I put together, and almost didn't make the cut. I briefly flirted with using + 'Salmon Cream Cheese' as the main style for the Garden, but switched back to this one before launch. + + All graphics in this design were illustrated by me in Photoshop. Google Image Search provided inspiration for + some of the elements. I did a bit of research on Kanji to come up with the characters on the top left. Anyone who + can read that will most likely tell you it makes no sense, but the best I could do was putting together the + characters for 'beginning' 'complete' and 'skill' to roughly say something like 'we're breaking fresh ground.' + + It's a stretch. */ + + +/* basic elements */ +html { + margin: 0px; + padding: 0px; + } +body { + font: 9pt/17pt georgia; + color: #555753; + background: #fff url(/001/blossoms.jpg) no-repeat bottom right; + margin: 0px; + padding: 0px; + } +p { + font: 9pt/17pt georgia; + margin-top: 0px; + text-align: justify; + } +h3 { + font: italic normal 12pt georgia; + letter-spacing: 1px; + margin-bottom: 0px; + color: #7D775C; + } +a:link { + font-weight: bold; + text-decoration: none; + color: #B7A5DF; + } +a:visited { + font-weight: bold; + text-decoration: none; + color: #D4CDDC; + } +a:hover, a:active { + text-decoration: underline; + color: #9685BA; + } +acronym { + border-bottom: none; + } + + +/* specific divs */ +#container { + background: url(/001/zen-bg.jpg) no-repeat top left; + padding: 0px 175px 0px 110px; + margin: 0px; + position: absolute; + top: 0px; + left: 0px; + } + +#intro { + min-width: 470px; + } +#pageHeader { + margin-bottom: 20px; + } + +/* using an image to replace text in an h1. This trick courtesy Douglas Bowman, http://www.stopdesign.com/articles/css/replace-text/ */ +#pageHeader h1 { + background: transparent url(/001/h1.gif) no-repeat top left; + margin-top: 10px; + width: 219px; + height: 87px; + float: left; + } +#pageHeader h1 span { + display:none + } +#pageHeader h2 { + background: transparent url(/001/h2.gif) no-repeat top left; + margin-top: 58px; + margin-bottom: 40px; + width: 200px; + height: 18px; + float: right; + } +#pageHeader h2 span { + display:none + } + +#quickSummary { + clear:both; + margin: 20px 20px 20px 10px; + width: 160px; + float: left; + } +#quickSummary p { + font: italic 10pt/22pt georgia; + text-align:center; + } + +#preamble { + clear: right; + padding: 0px 10px 0px 10px; + } +#supportingText { + padding-left: 10px; + margin-bottom: 40px; + } + +#footer { + text-align: center; + } +#footer a:link, #footer a:visited { + margin-right: 20px; + } + +#linkList { + margin-left: 600px; + position: absolute; + top: 0px; + right: 0px; + } +#linkList2 { + font: 10px verdana, sans-serif; + background: transparent url(/001/paper-bg.jpg) top left repeat-y; + padding: 10px; + margin-top: 150px; + width: 130px; + } +#linkList h3.select { + background: transparent url(/001/h3.gif) no-repeat top left; + margin: 10px 0px 5px 0px; + width: 97px; + height: 16px; + } +#linkList h3.select span { + display:none + } +#linkList h3.favorites { + background: transparent url(/001/h4.gif) no-repeat top left; + margin: 25px 0px 5px 0px; + width: 60px; + height: 18px; + } +#linkList h3.favorites span { + display:none + } +#linkList h3.archives { + background: transparent url(/001/h5.gif) no-repeat top left; + margin: 25px 0px 5px 0px; + width:57px; + height: 14px; + } +#linkList h3.archives span { + display:none + } +#linkList h3.resources { + background: transparent url(/001/h6.gif) no-repeat top left; + margin: 25px 0px 5px 0px; + width:63px; + height: 10px; + } +#linkList h3.resources span { + display:none + } + + +#linkList ul { + margin: 0px; + padding: 0px; + } +#linkList li { + line-height: 2.5ex; + background: transparent url(/001/cr1.gif) no-repeat top center; + display: block; + padding-top: 5px; + margin-bottom: 5px; + list-style-type: none; + } +#linkList li a:link { + color: #988F5E; + } +#linkList li a:visited { + color: #B3AE94; + } + + +#extraDiv1 { + background: transparent url(/001/cr2.gif) top left no-repeat; + position: absolute; + top: 40px; + right: 0px; + width: 148px; + height: 110px; + } +.accesskey { + text-decoration: underline; + } \ No newline at end of file diff --git a/src/dom/Filt.java b/src/dom/Filt.java new file mode 100755 index 000000000..a2c10550f --- /dev/null +++ b/src/dom/Filt.java @@ -0,0 +1,77 @@ + + +import java.io.*; + +public class Filt +{ + +void p(String s) +{ + System.out.println(s); +} + +void output(String s) +{ + String name = s.trim(); + if (name == null || name.length()<2) + return; + String ucName = name.substring(0,1).toUpperCase() + + name.substring(1); + + p("/**"); + p(" * return the '" + name + "' property" ); + p(" */"); + p("DOMString CSS2PropertiesImpl::get" + ucName + "()"); + p("{"); + p(" return " + name + ";"); + p("}"); + p(""); + p("/**"); + p(" * set the '" + name + "' property"); + p(" */"); + p("void CSS2PropertiesImpl::set" + ucName + "(const DOMString &val)"); + p(" throw (dom::DOMException)"); + p("{"); + p(" " + name + " = val;"); + p("}"); + p(""); + + +} + + +void doIt() +{ + try + { + BufferedReader in = new BufferedReader(new FileReader("cssprop.txt")); + + while (true) + { + String s = in.readLine(); + if (s == null) + break; + output(s); + } + + in.close(); + } + catch (Exception e) + { + } + +} + + + +public static void main(String argv[]) +{ + Filt f = new Filt(); + f.doIt(); + +} + + + +} + diff --git a/src/dom/ImplGen.java b/src/dom/ImplGen.java new file mode 100755 index 000000000..b79f70725 --- /dev/null +++ b/src/dom/ImplGen.java @@ -0,0 +1,180 @@ + + +import java.io.*; +import java.util.StringTokenizer; + + + +public class ImplGen +{ +BufferedWriter out; +String className; +String defaultReturn; + +void trace(String msg) +{ + System.out.println(msg); +} + +void err(String msg) +{ + System.out.print("error:"); + System.out.println(msg); +} + +void p(String s) +{ + try + { + out.write(s); + } + catch (IOException e) + { + } +} + +void sp(int count) +{ + for (int i=0 ; i0 && Character.isLetter(s.charAt(0))) + { + defaultReturn = "NULL"; + } + else if (s.startsWith("~")) + { + defaultReturn = ""; + } + + int pos = s.indexOf("("); + if (!s.startsWith("~") && pos > 0 && + Character.isLetterOrDigit(s.charAt(pos-1))) + { + while (Character.isLetterOrDigit(s.charAt(pos-1))) + pos--; + String news = s.substring(0, pos) + + className + "::" + + s.substring(pos); + s = news; + } + + if (s.startsWith("~")) + { + p(className); p("::"); + p(s); p("\n"); + } + else if (s.startsWith("}") && defaultReturn.length()>0) + { + p(" return "); p(defaultReturn); p(";"); p("\n"); + p(s); p("\n"); + } + else + { + p(s); p("\n"); + } + + + +} + + + + + +void doIt(String inName) +{ + String cppName = inName + ".cpp"; + try + { + BufferedReader in = new BufferedReader(new FileReader(inName)); + out = new BufferedWriter(new FileWriter(cppName)); + while (true) + { + String s = in.readLine(); + if (s == null) + break; + process(s); + } + + in.close(); + out.close(); + } + catch (Exception e) + { + } + +} + + +public ImplGen() +{ +} + + +public static void main(String argv[]) +{ + if (argv.length != 1) + { + System.out.println("usage: ImplGen "); + return; + } + ImplGen ig = new ImplGen(); + ig.doIt(argv[0]); + +} + + + + + + + + + + + + + + + + + + + +} diff --git a/src/dom/Makefile.static b/src/dom/Makefile.static new file mode 100755 index 000000000..907fe9cce --- /dev/null +++ b/src/dom/Makefile.static @@ -0,0 +1,71 @@ +########################################################################### +# +# Makefile for testing DOM code +# +########################################################################### + + +CC = gcc +CXX = g++ + +INC = -I. +CFLAGS = -g -Wall +LIBS = + +OBJ = \ +charclass.o \ +cssparser.o \ +domimpl.o \ +domstream.o \ +domstring.o \ +lsimpl.o \ +smilimpl.o \ +stringstream.o \ +svgimpl.o \ +svglsimpl.o \ +svgparser.o \ +uri.o \ +uristream.o \ +xmlreader.o \ +xpathimpl.o \ +xpathparser.o + +all: testsvg uritest xpathtest + +testdom: libdom.a testdom.o + $(CXX) -o testdom testdom.o libdom.a $(LIBS) + +testsvg: libdom.a testsvg.o + $(CXX) -o testsvg testsvg.o libdom.a $(LIBS) + +uritest: libdom.a uritest.o + $(CXX) -o uritest uritest.o libdom.a $(LIBS) + +xpathtest: libdom.a xpathtest.o + $(CXX) -o xpathtest xpathtest.o libdom.a $(LIBS) + +libdom.a: $(OBJ) + ar crv libdom.a $(OBJ) + +.cpp.o: + $(CXX) $(CFLAGS) $(INC) -c -o $@ $< + +clean: + -$(RM) *.o + -$(RM) *.a + -$(RM) *.gch + -$(RM) *.class + -$(RM) testdom + -$(RM) testdom.exe + -$(RM) testsvg + -$(RM) testsvg.exe + -$(RM) uritest + -$(RM) uritest.exe + -$(RM) xpathtest + -$(RM) xpathtest.exe + -$(RM) core.* + +########################################################################### +# E N D O F F I L E +########################################################################### + diff --git a/src/dom/Makefile_insert b/src/dom/Makefile_insert new file mode 100644 index 000000000..19be5c9f9 --- /dev/null +++ b/src/dom/Makefile_insert @@ -0,0 +1,57 @@ +## Makefile.am fragment sourced by src/Makefile.am. + +dom/all: dom/libdom.a + +dom/clean: + rm -f dom/libdom.a $(dom_libdom_a_OBJECTS) + +dom_libdom_a_SOURCES = \ + dom/charclass.cpp \ + dom/charclass.h \ + dom/css.h \ + dom/cssparser.cpp \ + dom/cssparser.h \ + dom/dom.h \ + dom/domimpl.cpp \ + dom/domimpl.h \ + dom/domstream.cpp \ + dom/domstream.h \ + dom/domstring.cpp \ + dom/domstring.h \ + dom/domstringimpl.h \ + dom/events.h \ + dom/ls.h \ + dom/lsimpl.cpp \ + dom/lsimpl.h \ + dom/phoebedom.h \ + dom/prop-css2.cpp \ + dom/prop-css.cpp \ + dom/prop-svg.cpp \ + dom/smil.h \ + dom/smilimpl.cpp \ + dom/smilimpl.h \ + dom/stringstream.cpp \ + dom/stringstream.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgimpl.cpp \ + dom/svgimpl.h \ + dom/svglsimpl.cpp \ + dom/svglsimpl.h \ + dom/svgparser.cpp \ + dom/svgparser.h \ + dom/svgtypes.h \ + dom/traversal.h \ + dom/uri.cpp \ + dom/uri.h \ + dom/uristream.cpp \ + dom/uristream.h \ + dom/views.h \ + dom/xmlreader.cpp \ + dom/xmlreader.h \ + dom/xpath.h \ + dom/xpathimpl.cpp \ + dom/xpathimpl.h \ + dom/xpathparser.cpp \ + dom/xpathparser.h + diff --git a/src/dom/README b/src/dom/README new file mode 100644 index 000000000..142d67ef3 --- /dev/null +++ b/src/dom/README @@ -0,0 +1,25 @@ +Phoebe DOM +========== + +This is the DOM library that is going to provide +Inkscape with a w3c DOM-idl interface, hopefully +aiding in scripting and animation. + +The name 'Phoebe' in inherited from an older project +which had its own DOM implementation. The name itself +is of a moon of Saturn. + +The library is intended to be 'vanilla' and generic. The +library has its own parser, even if it is not used. +The base code should never be modified to be Inkscape-specific. +It should not have any dependencies beyond STL, nor +should it veer away from adherence to the W3C DOM APIs. + +The most common usage of this library would probably be +limited to the classes defined in dom.h. A scriptable +item would inherit and extend org::w3c::dom::Node, and modify +the behaviour of the getters and setters for its attributes. + + + +Bob Jamison diff --git a/src/dom/acid.css b/src/dom/acid.css new file mode 100755 index 000000000..7ea8750f5 --- /dev/null +++ b/src/dom/acid.css @@ -0,0 +1,110 @@ + /* section numbers refer to CSS2.1 */ + + /* page setup */ + html { font: 12px sans-serif; margin: 0; padding: 0; overflow: hidden; /* hides scrollbars on viewport, see 11.1.1:3 */ background: white; color: red; } + body { margin: 0; padding: 0; } + + /* introduction message */ + .intro { font: 2em sans-serif; margin: 3.5em 2em; padding: 0.5em; border: solid thin; background: white; color: black; position: relative; z-index: 2; /* should cover the black and red bars that are fixed-positioned */ } + .intro * { font: inherit; margin: 0; padding: 0; } + .intro h1 { font-size: 1em; font-weight: bolder; margin: 0; padding: 0; } + .intro :link { color: blue; } + .intro :visited { color: purple; } + + /* picture setup */ + #top { margin: 100em 3em 0; padding: 2em 0 0 .5em; text-align: left; font: 2em/24px sans-serif; color: navy; white-space: pre; } /* "Hello World!" text */ + .picture { position: relative; border: 1em solid transparent; margin: 0 0 100em 3em; } /* containing block for face */ + .picture { background: red; } /* overriden by preferred stylesheet below */ + + /* top line of face (scalp): fixed positioning and min/max height/width */ + .picture p { position: fixed; margin: 0; padding: 0; border: 0; top: 9em; left: 11em; width: 140%; max-width: 4em; height: 8px; min-height: 1em; max-height: 2mm; /* min-height overrides max-height, see 10.7 */ background: black; border-bottom: 0.5em yellow solid; } + + /* bits that shouldn't be part of the top line (and shouldn't be visible at all): HTML parsing, "+" combinator, stacking order */ + .picture p.bad { border-bottom: red solid; /* shouldn't matter, because the "p + table + p" rule below should match it too, thus hiding it */ } + .picture p + p { background: maroon; z-index: 1; } /* shouldn't match anything */ + .picture p + table + p { margin-top: 3em; /* should end up under the absolutely positioned table below, and thus not be visible */ } + + /* second line of face: attribute selectors, float positioning */ + [class~=one].first.one { position: absolute; top: 0; margin: 36px 0 0 60px; padding: 0; border: black 2em; border-style: none solid; /* shrink wraps around float */ } + [class~=one][class~=first] [class=second\ two][class="second two"] { float: right; width: 48px; height: 12px; background: yellow; margin: 0; padding: 0; } /* only content of abs pos block */ + + /* third line of face: width and overflow */ + .forehead { margin: 4em; width: 8em; border-left: solid black 1em; border-right: solid black 1em; background: red url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR42mP4%2F58BAAT%2FAf9jgNErAAAAAElFTkSuQmCC); /* that's a 1x1 yellow pixel PNG */ } + .forehead * { width: 12em; line-height: 1em; } + + /* class selectors headache */ + .two.error.two { background: maroon; } /* shouldn't match */ + .forehead.error.forehead { background: red; } /* shouldn't match */ + [class=second two] { background: red; } /* this should be ignored (invalid selector -- grammar says it only accepts IDENTs or STRINGs) */ + + /* fourth and fifth lines of face, with eyes: paint order test (see appendix E) and fixed backgrounds */ + /* the two images are identical: 2-by-2 squares with the top left + and bottom right pixels set to yellow and the other two set to + transparent. Since they are offset by one pixel from each other, + the second one paints exactly over the transparent parts of the + first one, thus creating a solid yellow block. */ + .eyes { position: absolute; top: 5em; left: 3em; margin: 0; padding: 0; background: red; } + #eyes-a { height: 0; line-height: 2em; text-align: right; } /* contents should paint top-most because they're inline */ + #eyes-a object { display: inline; vertical-align: bottom; } + #eyes-a object[type] { width: 7.5em; height: 2.5em; } /* should have no effect since that object should fallback to being inline (height/width don't apply to inlines) */ + #eyes-a object object object { border-right: solid 1em black; padding: 0 12px 0 11px; background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAABnRSTlMAAAAAAABupgeRAAAABmJLR0QA%2FwD%2FAP%2BgvaeTAAAAEUlEQVR42mP4%2F58BCv7%2FZwAAHfAD%2FabwPj4AAAAASUVORK5CYII%3D) fixed 1px 0; } + #eyes-b { float: left; width: 10em; height: 2em; background: fixed url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAABnRSTlMAAAAAAABupgeRAAAABmJLR0QA%2FwD%2FAP%2BgvaeTAAAAEUlEQVR42mP4%2F58BCv7%2FZwAAHfAD%2FabwPj4AAAAASUVORK5CYII%3D); border-left: solid 1em black; border-right: solid 1em red; } /* should paint in the middle layer because it is a float */ + #eyes-c { display: block; background: red; border-left: 2em solid yellow; width: 10em; height: 2em; } /* should paint bottom most because it is a block */ + + /* lines six to nine, with nose: auto margins */ + .nose { float: left; margin: -2em 2em -1em; border: solid 1em black; border-top: 0; min-height: 80%; height: 60%; max-height: 3em; /* percentages become auto (see 10.5 and 10.7) and intrinsic height is more than 3em, so 3em wins */ padding: 0; width: 12em; } + .nose > div { padding: 1em 1em 3em; height: 0; background: yellow; } + .nose div div { width: 2em; height: 2em; background: red; margin: auto; } + .nose :hover div { border-color: blue; } + .nose div:hover :before { border-bottom-color: inherit; } + .nose div:hover :after { border-top-color: inherit; } + .nose div div:before { display: block; border-style: none solid solid; border-color: red yellow black yellow; border-width: 1em; content: ''; height: 0; } + .nose div :after { display: block; border-style: solid solid none; border-color: black yellow red yellow; border-width: 1em; content: ''; height: 0; } + + /* between lines nine and ten: margin collapsing with 'float' and 'clear' */ + .empty { margin: 6.25em; height: 10%; /* computes to auto which makes it empty per 8.3.1:7 (own margins) */ } + .empty div { margin: 0 2em -6em 4em; } + .smile { margin: 5em 3em; clear: both; /* clearance is negative (see 8.3.1 and 9.5.1) */ } + + /* line ten and eleven: containing block for abs pos */ + .smile div { margin-top: 0.25em; background: black; width: 12em; height: 2em; position: relative; bottom: -1em; } + .smile div div { position: absolute; top: 0; right: 1em; width: auto; height: 0; margin: 0; border: yellow solid 1em; } + + /* smile (over lines ten and eleven): backgrounds behind borders, inheritance of 'float', nested floats, negative heights */ + .smile div div span { display: inline; margin: -1em 0 0 0; border: solid 1em transparent; border-style: none solid; float: right; background: black; height: 1em; } + .smile div div span em { float: inherit; border-top: solid yellow 1em; border-bottom: solid black 1em; } /* zero-height block; width comes from (zero-height) child. */ + .smile div div span em strong { width: 6em; display: block; margin-bottom: -1em; /* should have no effect, since parent has top&bottom borders, so this margin doesn't collapse */ } + + /* line twelve: line-height */ + .chin { margin: -4em 4em 0; width: 8em; line-height: 1em; border-left: solid 1em black; border-right: solid 1em black; background: yellow url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAIAAAFSDNYfAAAAaklEQVR42u3XQQrAIAwAQeP%2F%2F6wf8CJBJTK9lnQ7FpHGaOurt1I34nfH9pMMZAZ8BwMGEvvh%2BBsJCAgICLwIOA8EBAQEBAQEBAQEBK79H5RfIQAAAAAAAAAAAAAAAAAAAAAAAAAAAID%2FABMSqAfj%2FsLmvAAAAABJRU5ErkJggg%3D%3D) /* 64x64 red square */ no-repeat fixed /* shouldn't be visible unless the smiley is moved to the top left of the viewport */; } + .chin div { display: inline; font: 2px/4px serif; } + + /* line thirteen: cascade and selector tests */ + .parser-container div { color: maroon; border: solid; color: orange; } /* setup */ + div.parser-container * { border-color: black; /* overrides (implied) border-color on previous line */ } /* setup */ + * div.parser { border-width: 0 2em; /* overrides (implied) declarations on earlier line */ } /* setup */ + + /* line thirteen continued: parser tests */ + .parser { /* comment parsing test -- comment ends before the end of this line, the backslash should have no effect: \*/ } + .parser { margin: 0 5em 1em; padding: 0 1em; width: 2em; height: 1em; error: \}; background: yellow; } /* setup with parsing test */ + * html .parser { background: gray; } + \.parser { padding: 2em; } + .parser { m\argin: 2em; }; + .parser { height: 3em; } + .parser { width: 200; } + .parser { border: 5em solid red ! error; } + .parser { background: red pink; } + + /* line fourteen (last line of face): table */ + ul { display: table; padding: 0; margin: -1em 7em 0; background: red; } + ul li { padding: 0; margin: 0; } + ul li.first-part { display: table-cell; height: 1em; width: 1em; background: black; } + ul li.second-part { display: table; height: 1em; width: 1em; background: black; } /* anonymous table cell wraps around this */ + ul li.third-part { display: table-cell; height: 0.5em; /* gets stretched to fit row */ width: 1em; background: black; } + ul li.fourth-part { list-style: none; height: 1em; width: 1em; background: black; } /* anonymous table cell wraps around this */ + + /* bits that shouldn't appear: inline alignment in cells */ + .image-height-test { height: 10px; overflow: hidden; font: 20em serif; } /* only the area between the top of the line box and the top of the image should be visible */ + table { margin: 0; border-spacing: 0; } + td { padding: 0; } + diff --git a/src/dom/base.css b/src/dom/base.css new file mode 100755 index 000000000..7176bbf94 --- /dev/null +++ b/src/dom/base.css @@ -0,0 +1,32 @@ +/* recover from old-browser styling */ + +*.oldbl {display: block !important;} +*.oldin {display: inline !important;} +*.ahem {display: none !important;} +img.pic {display: block !important;} + +/* NS6.x-specific fix(es) */ + +/*|*:-moz-list-bullet, *|*:-moz-list-number {font-size: 1em;}/ + +/* misc */ + +.skipper {display: none !important;} + +* {font-size: 100%;} +h1 {font-size: 2em;} +h2 {font-size: 1.5em;} +h3 {font-size: 1.33em;} +h4 {font-size: 1.1em;} +h5 {font-size: 0.9em;} +h6 {font-size: 0.75em;} +pre, code, tt {font: 95% "Andale Mono", Courier, "Courier New", monospace;} + +img.pic {float: right; margin: 0.25em 0 0.66em 1.5em;} +img.border {border: 3px double;} + +p.contact {margin: 0 1em !important; text-align: right; font-size: 90%;} + +#main {min-height: 30em;} +#header h1 a, #nav a {text-decoration: none;} +#nav {padding-top: 0.75em;} diff --git a/src/dom/charclass.cpp b/src/dom/charclass.cpp new file mode 100755 index 000000000..81c3fb18e --- /dev/null +++ b/src/dom/charclass.cpp @@ -0,0 +1,451 @@ +/** + * Phoebe DOM Implementation. + * + * This is a C++ approximation of the W3C DOM model, which follows + * fairly closely the specifications in the various .idl files, copies of + * which are provided for reference. Most important is this one: + * + * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/idl-definitions.html + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2005 Bob Jamison + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "charclass.h" + + +/** + * (impl) LetterOrDigit ::= + * Letter | Digit + */ +bool isLetterOrDigit(int ch) +{ + if (isLetter(ch)) + return true; + if (isDigit(ch)) + return true; + return false; +} + +/** + * (84) Letter ::= + * BaseChar | Ideographic + */ +bool isLetter(int ch) +{ + if (isBaseChar(ch)) + return true; + if (isIdeographic(ch)) + return true; + return false; +} + + +/** + * (85) BaseChar ::= + */ +bool isBaseChar(int ch) +{ + + if ( (0x0041 <= ch && ch <= 0x005A) | + (0x0061 <= ch && ch <= 0x007A) | + (0x00C0 <= ch && ch <= 0x00D6) | + (0x00D8 <= ch && ch <= 0x00F6) | + (0x00F8 <= ch && ch <= 0x00FF) | + (0x0100 <= ch && ch <= 0x0131) | + (0x0134 <= ch && ch <= 0x013E) | + (0x0141 <= ch && ch <= 0x0148) | + (0x014A <= ch && ch <= 0x017E) | + (0x0180 <= ch && ch <= 0x01C3) | + (0x01CD <= ch && ch <= 0x01F0) | + (0x01F4 <= ch && ch <= 0x01F5) | + (0x01FA <= ch && ch <= 0x0217) | + (0x0250 <= ch && ch <= 0x02A8) | + (0x02BB <= ch && ch <= 0x02C1) | + ch == 0x0386 | + (0x0388 <= ch && ch <= 0x038A) | + ch == 0x038C | + (0x038E <= ch && ch <= 0x03A1) | + (0x03A3 <= ch && ch <= 0x03CE) | + (0x03D0 <= ch && ch <= 0x03D6) | + ch == 0x03DA | + ch == 0x03DC | + ch == 0x03DE | + ch == 0x03E0 | + (0x03E2 <= ch && ch <= 0x03F3) | + (0x0401 <= ch && ch <= 0x040C) | + (0x040E <= ch && ch <= 0x044F) | + (0x0451 <= ch && ch <= 0x045C) | + (0x045E <= ch && ch <= 0x0481) | + (0x0490 <= ch && ch <= 0x04C4) | + (0x04C7 <= ch && ch <= 0x04C8) | + (0x04CB <= ch && ch <= 0x04CC) | + (0x04D0 <= ch && ch <= 0x04EB) | + (0x04EE <= ch && ch <= 0x04F5) | + (0x04F8 <= ch && ch <= 0x04F9) | + (0x0531 <= ch && ch <= 0x0556) | + ch == 0x0559 | + (0x0561 <= ch && ch <= 0x0586) | + (0x05D0 <= ch && ch <= 0x05EA) | + (0x05F0 <= ch && ch <= 0x05F2) | + (0x0621 <= ch && ch <= 0x063A) | + (0x0641 <= ch && ch <= 0x064A) | + (0x0671 <= ch && ch <= 0x06B7) | + (0x06BA <= ch && ch <= 0x06BE) | + (0x06C0 <= ch && ch <= 0x06CE) | + (0x06D0 <= ch && ch <= 0x06D3) | + ch == 0x06D5 | + (0x06E5 <= ch && ch <= 0x06E6) | + (0x0905 <= ch && ch <= 0x0939) | + ch == 0x093D | + (0x0958 <= ch && ch <= 0x0961) | + (0x0985 <= ch && ch <= 0x098C) | + (0x098F <= ch && ch <= 0x0990) | + (0x0993 <= ch && ch <= 0x09A8) | + (0x09AA <= ch && ch <= 0x09B0) | + ch == 0x09B2 | + (0x09B6 <= ch && ch <= 0x09B9) | + (0x09DC <= ch && ch <= 0x09DD) | + (0x09DF <= ch && ch <= 0x09E1) | + (0x09F0 <= ch && ch <= 0x09F1) | + (0x0A05 <= ch && ch <= 0x0A0A) | + (0x0A0F <= ch && ch <= 0x0A10) | + (0x0A13 <= ch && ch <= 0x0A28) | + (0x0A2A <= ch && ch <= 0x0A30) | + (0x0A32 <= ch && ch <= 0x0A33) | + (0x0A35 <= ch && ch <= 0x0A36) | + (0x0A38 <= ch && ch <= 0x0A39) | + (0x0A59 <= ch && ch <= 0x0A5C) | + ch == 0x0A5E | + (0x0A72 <= ch && ch <= 0x0A74) | + (0x0A85 <= ch && ch <= 0x0A8B) | + ch == 0x0A8D | + (0x0A8F <= ch && ch <= 0x0A91) | + (0x0A93 <= ch && ch <= 0x0AA8) | + (0x0AAA <= ch && ch <= 0x0AB0) | + (0x0AB2 <= ch && ch <= 0x0AB3) | + (0x0AB5 <= ch && ch <= 0x0AB9) | + ch == 0x0ABD | + ch == 0x0AE0 | + (0x0B05 <= ch && ch <= 0x0B0C) | + (0x0B0F <= ch && ch <= 0x0B10) | + (0x0B13 <= ch && ch <= 0x0B28) | + (0x0B2A <= ch && ch <= 0x0B30) | + (0x0B32 <= ch && ch <= 0x0B33) | + (0x0B36 <= ch && ch <= 0x0B39) | + ch == 0x0B3D | + (0x0B5C <= ch && ch <= 0x0B5D) | + (0x0B5F <= ch && ch <= 0x0B61) | + (0x0B85 <= ch && ch <= 0x0B8A) | + (0x0B8E <= ch && ch <= 0x0B90) | + (0x0B92 <= ch && ch <= 0x0B95) | + (0x0B99 <= ch && ch <= 0x0B9A) | + ch == 0x0B9C | + (0x0B9E <= ch && ch <= 0x0B9F) | + (0x0BA3 <= ch && ch <= 0x0BA4) | + (0x0BA8 <= ch && ch <= 0x0BAA) | + (0x0BAE <= ch && ch <= 0x0BB5) | + (0x0BB7 <= ch && ch <= 0x0BB9) | + (0x0C05 <= ch && ch <= 0x0C0C) | + (0x0C0E <= ch && ch <= 0x0C10) | + (0x0C12 <= ch && ch <= 0x0C28) | + (0x0C2A <= ch && ch <= 0x0C33) | + (0x0C35 <= ch && ch <= 0x0C39) | + (0x0C60 <= ch && ch <= 0x0C61) | + (0x0C85 <= ch && ch <= 0x0C8C) | + (0x0C8E <= ch && ch <= 0x0C90) | + (0x0C92 <= ch && ch <= 0x0CA8) | + (0x0CAA <= ch && ch <= 0x0CB3) | + (0x0CB5 <= ch && ch <= 0x0CB9) | + ch == 0x0CDE | + (0x0CE0 <= ch && ch <= 0x0CE1) | + (0x0D05 <= ch && ch <= 0x0D0C) | + (0x0D0E <= ch && ch <= 0x0D10) | + (0x0D12 <= ch && ch <= 0x0D28) | + (0x0D2A <= ch && ch <= 0x0D39) | + (0x0D60 <= ch && ch <= 0x0D61) | + (0x0E01 <= ch && ch <= 0x0E2E) | + ch == 0x0E30 | + (0x0E32 <= ch && ch <= 0x0E33) | + (0x0E40 <= ch && ch <= 0x0E45) | + (0x0E81 <= ch && ch <= 0x0E82) | + ch == 0x0E84 | + (0x0E87 <= ch && ch <= 0x0E88) | + ch == 0x0E8A | + ch == 0x0E8D | + (0x0E94 <= ch && ch <= 0x0E97) | + (0x0E99 <= ch && ch <= 0x0E9F) | + (0x0EA1 <= ch && ch <= 0x0EA3) | + ch == 0x0EA5 | + ch == 0x0EA7 | + (0x0EAA <= ch && ch <= 0x0EAB) | + (0x0EAD <= ch && ch <= 0x0EAE) | + ch == 0x0EB0 | + (0x0EB2 <= ch && ch <= 0x0EB3) | + ch == 0x0EBD | + (0x0EC0 <= ch && ch <= 0x0EC4) | + (0x0F40 <= ch && ch <= 0x0F47) | + (0x0F49 <= ch && ch <= 0x0F69) | + (0x10A0 <= ch && ch <= 0x10C5) | + (0x10D0 <= ch && ch <= 0x10F6) | + ch == 0x1100 | + (0x1102 <= ch && ch <= 0x1103) | + (0x1105 <= ch && ch <= 0x1107) | + ch == 0x1109 | + (0x110B <= ch && ch <= 0x110C) | + (0x110E <= ch && ch <= 0x1112) | + ch == 0x113C | + ch == 0x113E | + ch == 0x1140 | + ch == 0x114C | + ch == 0x114E | + ch == 0x1150 | + (0x1154 <= ch && ch <= 0x1155) | + ch == 0x1159 | + (0x115F <= ch && ch <= 0x1161) | + ch == 0x1163 | + ch == 0x1165 | + ch == 0x1167 | + ch == 0x1169 | + (0x116D <= ch && ch <= 0x116E) | + (0x1172 <= ch && ch <= 0x1173) | + ch == 0x1175 | + ch == 0x119E | + ch == 0x11A8 | + ch == 0x11AB | + (0x11AE <= ch && ch <= 0x11AF) | + (0x11B7 <= ch && ch <= 0x11B8) | + ch == 0x11BA | + (0x11BC <= ch && ch <= 0x11C2) | + ch == 0x11EB | + ch == 0x11F0 | + ch == 0x11F9 | + (0x1E00 <= ch && ch <= 0x1E9B) | + (0x1EA0 <= ch && ch <= 0x1EF9) | + (0x1F00 <= ch && ch <= 0x1F15) | + (0x1F18 <= ch && ch <= 0x1F1D) | + (0x1F20 <= ch && ch <= 0x1F45) | + (0x1F48 <= ch && ch <= 0x1F4D) | + (0x1F50 <= ch && ch <= 0x1F57) | + ch == 0x1F59 | + ch == 0x1F5B | + ch == 0x1F5D | + (0x1F5F <= ch && ch <= 0x1F7D) | + (0x1F80 <= ch && ch <= 0x1FB4) | + (0x1FB6 <= ch && ch <= 0x1FBC) | + ch == 0x1FBE | + (0x1FC2 <= ch && ch <= 0x1FC4) | + (0x1FC6 <= ch && ch <= 0x1FCC) | + (0x1FD0 <= ch && ch <= 0x1FD3) | + (0x1FD6 <= ch && ch <= 0x1FDB) | + (0x1FE0 <= ch && ch <= 0x1FEC) | + (0x1FF2 <= ch && ch <= 0x1FF4) | + (0x1FF6 <= ch && ch <= 0x1FFC) | + ch == 0x2126 | + (0x212A <= ch && ch <= 0x212B) | + ch == 0x212E | + (0x2180 <= ch && ch <= 0x2182) | + (0x3041 <= ch && ch <= 0x3094) | + (0x30A1 <= ch && ch <= 0x30FA) | + (0x3105 <= ch && ch <= 0x312C) | + (0xAC00 <= ch && ch <= 0xD7A3) ) + return true; + return false; +} + + + +/** + * (86) Ideographic ::= + */ +bool isIdeographic(int ch) +{ + if ( (0x4E00 <= ch && ch <=0x9FA5) | + ch == 0x3007 | + (0x3021 <= ch && ch <=0x3029) ) + return true; + return false; +} + +/** + * (87) CombiningChar ::= + */ +bool isCombiningChar(int ch) +{ + if ( (0x0300 <= ch && ch <= 0x0345) | + (0x0360 <= ch && ch <= 0x0361) | + (0x0483 <= ch && ch <= 0x0486) | + (0x0591 <= ch && ch <= 0x05A1) | + (0x05A3 <= ch && ch <= 0x05B9) | + (0x05BB <= ch && ch <= 0x05BD) | + ch == 0x05BF | + (0x05C1 <= ch && ch <= 0x05C2) | + ch == 0x05C4 | + (0x064B <= ch && ch <= 0x0652) | + ch == 0x0670 | + (0x06D6 <= ch && ch <= 0x06DC) | + (0x06DD <= ch && ch <= 0x06DF) | + (0x06E0 <= ch && ch <= 0x06E4) | + (0x06E7 <= ch && ch <= 0x06E8) | + (0x06EA <= ch && ch <= 0x06ED) | + (0x0901 <= ch && ch <= 0x0903) | + ch == 0x093C | + (0x093E <= ch && ch <= 0x094C) | + ch == 0x094D | + (0x0951 <= ch && ch <= 0x0954) | + (0x0962 <= ch && ch <= 0x0963) | + (0x0981 <= ch && ch <= 0x0983) | + ch == 0x09BC | + ch == 0x09BE | + ch == 0x09BF | + (0x09C0 <= ch && ch <= 0x09C4) | + (0x09C7 <= ch && ch <= 0x09C8) | + (0x09CB <= ch && ch <= 0x09CD) | + ch == 0x09D7 | + (0x09E2 <= ch && ch <= 0x09E3) | + ch == 0x0A02 | + ch == 0x0A3C | + ch == 0x0A3E | + ch == 0x0A3F | + (0x0A40 <= ch && ch <= 0x0A42) | + (0x0A47 <= ch && ch <= 0x0A48) | + (0x0A4B <= ch && ch <= 0x0A4D) | + (0x0A70 <= ch && ch <= 0x0A71) | + (0x0A81 <= ch && ch <= 0x0A83) | + ch == 0x0ABC | + (0x0ABE <= ch && ch <= 0x0AC5) | + (0x0AC7 <= ch && ch <= 0x0AC9) | + (0x0ACB <= ch && ch <= 0x0ACD) | + (0x0B01 <= ch && ch <= 0x0B03) | + ch == 0x0B3C | + (0x0B3E <= ch && ch <= 0x0B43) | + (0x0B47 <= ch && ch <= 0x0B48) | + (0x0B4B <= ch && ch <= 0x0B4D) | + (0x0B56 <= ch && ch <= 0x0B57) | + (0x0B82 <= ch && ch <= 0x0B83) | + (0x0BBE <= ch && ch <= 0x0BC2) | + (0x0BC6 <= ch && ch <= 0x0BC8) | + (0x0BCA <= ch && ch <= 0x0BCD) | + ch == 0x0BD7 | + (0x0C01 <= ch && ch <= 0x0C03) | + (0x0C3E <= ch && ch <= 0x0C44) | + (0x0C46 <= ch && ch <= 0x0C48) | + (0x0C4A <= ch && ch <= 0x0C4D) | + (0x0C55 <= ch && ch <= 0x0C56) | + (0x0C82 <= ch && ch <= 0x0C83) | + (0x0CBE <= ch && ch <= 0x0CC4) | + (0x0CC6 <= ch && ch <= 0x0CC8) | + (0x0CCA <= ch && ch <= 0x0CCD) | + (0x0CD5 <= ch && ch <= 0x0CD6) | + (0x0D02 <= ch && ch <= 0x0D03) | + (0x0D3E <= ch && ch <= 0x0D43) | + (0x0D46 <= ch && ch <= 0x0D48) | + (0x0D4A <= ch && ch <= 0x0D4D) | + ch == 0x0D57 | + ch == 0x0E31 | + (0x0E34 <= ch && ch <= 0x0E3A) | + (0x0E47 <= ch && ch <= 0x0E4E) | + ch == 0x0EB1 | + (0x0EB4 <= ch && ch <= 0x0EB9) | + (0x0EBB <= ch && ch <= 0x0EBC) | + (0x0EC8 <= ch && ch <= 0x0ECD) | + (0x0F18 <= ch && ch <= 0x0F19) | + ch == 0x0F35 | + ch == 0x0F37 | + ch == 0x0F39 | + ch == 0x0F3E | + ch == 0x0F3F | + (0x0F71 <= ch && ch <= 0x0F84) | + (0x0F86 <= ch && ch <= 0x0F8B) | + (0x0F90 <= ch && ch <= 0x0F95) | + ch == 0x0F97 | + (0x0F99 <= ch && ch <= 0x0FAD) | + (0x0FB1 <= ch && ch <= 0x0FB7) | + ch == 0x0FB9 | + (0x20D0 <= ch && ch <= 0x20DC) | + ch == 0x20E1 | + (0x302A <= ch && ch <= 0x302F) | + ch == 0x3099 | + ch == 0x309A ) + return true; + return false; +} + + +/** + * (88) Digit ::= + */ +bool isDigit(int ch) +{ + if ( (0x0030 <= ch && ch <= 0x0039) | + (0x0660 <= ch && ch <= 0x0669) | + (0x06F0 <= ch && ch <= 0x06F9) | + (0x0966 <= ch && ch <= 0x096F) | + (0x09E6 <= ch && ch <= 0x09EF) | + (0x0A66 <= ch && ch <= 0x0A6F) | + (0x0AE6 <= ch && ch <= 0x0AEF) | + (0x0B66 <= ch && ch <= 0x0B6F) | + (0x0BE7 <= ch && ch <= 0x0BEF) | + (0x0C66 <= ch && ch <= 0x0C6F) | + (0x0CE6 <= ch && ch <= 0x0CEF) | + (0x0D66 <= ch && ch <= 0x0D6F) | + (0x0E50 <= ch && ch <= 0x0E59) | + (0x0ED0 <= ch && ch <= 0x0ED9) | + (0x0F20 <= ch && ch <= 0x0F29) ) + return true; + return false; +} + + +/** + * (89) Extender ::= + */ +bool isExtender(int ch) +{ + if ( ch == 0x00B7 | + ch == 0x02D0 | + ch == 0x02D1 | + ch == 0x0387 | + ch == 0x0640 | + ch == 0x0E46 | + ch == 0x0EC6 | + ch == 0x3005 | + (0x3031 <= ch && ch <= 0x3035) | + (0x309D <= ch && ch <= 0x309E) | + (0x30FC <= ch && ch <= 0x30FE) ) + return true; + return false; +} + + + + + + + + + + + + + + + + + diff --git a/src/dom/charclass.h b/src/dom/charclass.h new file mode 100755 index 000000000..5de08c0d4 --- /dev/null +++ b/src/dom/charclass.h @@ -0,0 +1,185 @@ +#ifndef __CHARCLASS_H__ +#define __CHARCLASS_H__ +/** + * Phoebe DOM Implementation. + * + * This is a C++ approximation of the W3C DOM model, which follows + * fairly closely the specifications in the various .idl files, copies of + * which are provided for reference. Most important is this one: + * + * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/idl-definitions.html + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2005 Bob Jamison + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + + +/** + * Utility classes to test characters for their class, as specified in + * http://www.w3.org/TR/REC-xml/#CharClasses + */ + +/** + * Convenience method. Not in spec + * [impl] LetterOrDigit ::= + * Letter | Digit + */ +bool isLetterOrDigit(int ch); + + +/** + * [84] Letter ::= + * BaseChar | Ideographic + */ +bool isLetter(int ch); + + +/** + * [85] BaseChar ::= + * [#x0041-#x005A] | [#x0061-#x007A] | [#x00C0-#x00D6] | [#x00D8-#x00F6] | + * [#x00F8-#x00FF] | [#x0100-#x0131] | [#x0134-#x013E] | [#x0141-#x0148] | + * [#x014A-#x017E] | [#x0180-#x01C3] | [#x01CD-#x01F0] | [#x01F4-#x01F5] | + * [#x01FA-#x0217] | [#x0250-#x02A8] | [#x02BB-#x02C1] | #x0386 | + * [#x0388-#x038A] | #x038C | [#x038E-#x03A1] | [#x03A3-#x03CE] | + * [#x03D0-#x03D6] | #x03DA | #x03DC | #x03DE | #x03E0 | [#x03E2-#x03F3] | + * [#x0401-#x040C] | [#x040E-#x044F] | [#x0451-#x045C] | [#x045E-#x0481] | + * [#x0490-#x04C4] | [#x04C7-#x04C8] | [#x04CB-#x04CC] | [#x04D0-#x04EB] | + * [#x04EE-#x04F5] | [#x04F8-#x04F9] | [#x0531-#x0556] | #x0559 | + * [#x0561-#x0586] | [#x05D0-#x05EA] | [#x05F0-#x05F2] | [#x0621-#x063A] | + * [#x0641-#x064A] | [#x0671-#x06B7] | [#x06BA-#x06BE] | [#x06C0-#x06CE] | + * [#x06D0-#x06D3] | #x06D5 | [#x06E5-#x06E6] | [#x0905-#x0939] | #x093D | + * [#x0958-#x0961] | [#x0985-#x098C] | [#x098F-#x0990] | [#x0993-#x09A8] | + * [#x09AA-#x09B0] | #x09B2 | [#x09B6-#x09B9] | [#x09DC-#x09DD] | + * [#x09DF-#x09E1] | [#x09F0-#x09F1] | [#x0A05-#x0A0A] | [#x0A0F-#x0A10] | + * [#x0A13-#x0A28] | [#x0A2A-#x0A30] | [#x0A32-#x0A33] | [#x0A35-#x0A36] | + * [#x0A38-#x0A39] | [#x0A59-#x0A5C] | #x0A5E | [#x0A72-#x0A74] | + * [#x0A85-#x0A8B] | #x0A8D | [#x0A8F-#x0A91] | [#x0A93-#x0AA8] | + * [#x0AAA-#x0AB0] | [#x0AB2-#x0AB3] | [#x0AB5-#x0AB9] | #x0ABD | #x0AE0 | + * [#x0B05-#x0B0C] | [#x0B0F-#x0B10] | [#x0B13-#x0B28] | [#x0B2A-#x0B30] | + * [#x0B32-#x0B33] | [#x0B36-#x0B39] | #x0B3D | [#x0B5C-#x0B5D] | + * [#x0B5F-#x0B61] | [#x0B85-#x0B8A] | [#x0B8E-#x0B90] | [#x0B92-#x0B95] | + * [#x0B99-#x0B9A] | #x0B9C | [#x0B9E-#x0B9F] | [#x0BA3-#x0BA4] | + * [#x0BA8-#x0BAA] | [#x0BAE-#x0BB5] | [#x0BB7-#x0BB9] | [#x0C05-#x0C0C] | + * [#x0C0E-#x0C10] | [#x0C12-#x0C28] | [#x0C2A-#x0C33] | + * [#x0C35-#x0C39] | [#x0C60-#x0C61] | [#x0C85-#x0C8C] | [#x0C8E-#x0C90] | + * [#x0C92-#x0CA8] | [#x0CAA-#x0CB3] | [#x0CB5-#x0CB9] | #x0CDE | + * [#x0CE0-#x0CE1] | [#x0D05-#x0D0C] | [#x0D0E-#x0D10] | [#x0D12-#x0D28] | + * [#x0D2A-#x0D39] | [#x0D60-#x0D61] | [#x0E01-#x0E2E] | #x0E30 | + * [#x0E32-#x0E33] | [#x0E40-#x0E45] | [#x0E81-#x0E82] | #x0E84 | + * [#x0E87-#x0E88] | #x0E8A | #x0E8D | [#x0E94-#x0E97] | [#x0E99-#x0E9F] | + * [#x0EA1-#x0EA3] | #x0EA5 | #x0EA7 | [#x0EAA-#x0EAB] | [#x0EAD-#x0EAE] | + * #x0EB0 | [#x0EB2-#x0EB3] | #x0EBD | [#x0EC0-#x0EC4] | [#x0F40-#x0F47] | + * [#x0F49-#x0F69] | [#x10A0-#x10C5] | [#x10D0-#x10F6] | #x1100 | + * [#x1102-#x1103] | [#x1105-#x1107] | #x1109 | [#x110B-#x110C] | + * [#x110E-#x1112] | #x113C | #x113E | #x1140 | #x114C | #x114E | + * #x1150 | [#x1154-#x1155] | #x1159 | [#x115F-#x1161] | #x1163 | + * #x1165 | #x1167 | #x1169 | [#x116D-#x116E] | [#x1172-#x1173] | #x1175 | + * #x119E | #x11A8 | #x11AB | [#x11AE-#x11AF] | [#x11B7-#x11B8] | #x11BA | + * [#x11BC-#x11C2] | #x11EB | #x11F0 | #x11F9 | [#x1E00-#x1E9B] | + * [#x1EA0-#x1EF9] | [#x1F00-#x1F15] | [#x1F18-#x1F1D] | [#x1F20-#x1F45] | + * [#x1F48-#x1F4D] | [#x1F50-#x1F57] | #x1F59 | #x1F5B | #x1F5D | + * [#x1F5F-#x1F7D] | [#x1F80-#x1FB4] | [#x1FB6-#x1FBC] | #x1FBE | + * [#x1FC2-#x1FC4] | [#x1FC6-#x1FCC] | [#x1FD0-#x1FD3] | [#x1FD6-#x1FDB] | + * [#x1FE0-#x1FEC] | [#x1FF2-#x1FF4] | [#x1FF6-#x1FFC] | #x2126 | + * [#x212A-#x212B] | #x212E | [#x2180-#x2182] | [#x3041-#x3094] | + * [#x30A1-#x30FA] | [#x3105-#x312C] | [#xAC00-#xD7A3] + */ +bool isBaseChar(int ch); + + +/** + * [86] Ideographic ::= + * [#x4E00-#x9FA5] | #x3007 | [#x3021-#x3029] + */ +bool isIdeographic(int ch); + + + + + +/** + * [87] CombiningChar ::= + * [#x0300-#x0345] | [#x0360-#x0361] | [#x0483-#x0486] | [#x0591-#x05A1] | + * [#x05A3-#x05B9] | [#x05BB-#x05BD] | #x05BF | [#x05C1-#x05C2] | #x05C4 | + * [#x064B-#x0652] | #x0670 | [#x06D6-#x06DC] | [#x06DD-#x06DF] | + * [#x06E0-#x06E4] | [#x06E7-#x06E8] | [#x06EA-#x06ED] | [#x0901-#x0903] | + * #x093C | [#x093E-#x094C] | #x094D | [#x0951-#x0954] |[#x0962-#x0963] | + * [#x0981-#x0983] | #x09BC | #x09BE | #x09BF | [#x09C0-#x09C4] | + * [#x09C7-#x09C8] | [#x09CB-#x09CD] | #x09D7 | [#x09E2-#x09E3] | + * #x0A02 | #x0A3C | #x0A3E | #x0A3F | [#x0A40-#x0A42] | [#x0A47-#x0A48] | + * [#x0A4B-#x0A4D] | [#x0A70-#x0A71] | [#x0A81-#x0A83] | #x0ABC | + * [#x0ABE-#x0AC5] | [#x0AC7-#x0AC9] | [#x0ACB-#x0ACD] | [#x0B01-#x0B03] | + * #x0B3C | [#x0B3E-#x0B43] | [#x0B47-#x0B48] | [#x0B4B-#x0B4D] | + * [#x0B56-#x0B57] | [#x0B82-#x0B83] | [#x0BBE-#x0BC2] | [#x0BC6-#x0BC8] | + * [#x0BCA-#x0BCD] | #x0BD7 | [#x0C01-#x0C03] | [#x0C3E-#x0C44] | + * [#x0C46-#x0C48] | [#x0C4A-#x0C4D] | [#x0C55-#x0C56] | [#x0C82-#x0C83] | + * [#x0CBE-#x0CC4] | [#x0CC6-#x0CC8] | [#x0CCA-#x0CCD] | [#x0CD5-#x0CD6] | + * [#x0D02-#x0D03] | [#x0D3E-#x0D43] | [#x0D46-#x0D48] | [#x0D4A-#x0D4D] | + * #x0D57 | #x0E31 | [#x0E34-#x0E3A] | [#x0E47-#x0E4E] | #x0EB1 | + * [#x0EB4-#x0EB9] | [#x0EBB-#x0EBC] | [#x0EC8-#x0ECD] | [#x0F18-#x0F19] | + * #x0F35 | #x0F37 | #x0F39 | #x0F3E | #x0F3F | [#x0F71-#x0F84] | + * [#x0F86-#x0F8B] | [#x0F90-#x0F95] | #x0F97 | [#x0F99-#x0FAD] | + * [#x0FB1-#x0FB7] | #x0FB9 | [#x20D0-#x20DC] | #x20E1 | [#x302A-#x302F] | + * #x3099 | #x309A + */ +bool isCombiningChar(int ch); + + + +/** + * [88] Digit ::= + * [#x0030-#x0039] | [#x0660-#x0669] | [#x06F0-#x06F9] | + * [#x0966-#x096F] | [#x09E6-#x09EF] | [#x0A66-#x0A6F] | [#x0AE6-#x0AEF] | + * [#x0B66-#x0B6F] | [#x0BE7-#x0BEF] | [#x0C66-#x0C6F] | [#x0CE6-#x0CEF] | + * [#x0D66-#x0D6F] | [#x0E50-#x0E59] | [#x0ED0-#x0ED9] | [#x0F20-#x0F29] + */ +bool isDigit(int ch); + + + + +/** + * [89] Extender ::= + * #x00B7 | #x02D0 | #x02D1 | #x0387 | #x0640 | #x0E46 | #x0EC6 | + * #x3005 | [#x3031-#x3035] | [#x309D-#x309E] | [#x30FC-#x30FE] + */ +bool isExtender(int ch); + + + + +#endif /* __CHARCLASS_H__ */ + + + + + + + + + + + + + + + + + diff --git a/src/dom/css.h b/src/dom/css.h new file mode 100755 index 000000000..6ab92389e --- /dev/null +++ b/src/dom/css.h @@ -0,0 +1,4121 @@ +/* + * Copyright (c) 2000 World Wide Web Consortium, + * (Massachusetts Institute of Technology, Institut National de + * Recherche en Informatique et en Automatique, Keio University). All + * Rights Reserved. This program is distributed under the W3C's Software + * Intellectual Property License. This program is distributed the + * hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. + * See W3C License http://www.w3.org/Consortium/Legal/ for more details. + */ + +// File: http://www.w3.org/TR/2000/REC-DOM-Level-2-Style-20001113/css.idl + +#ifndef __CSS_H__ +#define __CSS_H__ + +#include "dom.h" +#include "stylesheets.h" +#include "views.h" + +#include +#include + + +namespace org { +namespace w3c { +namespace dom { +namespace css { + + + + +//Make local definitions +typedef dom::DOMString DOMString; +typedef dom::Element Element; +typedef dom::DOMImplementation DOMImplementation; + +//forward declarations +class CSSRule; +class CSSStyleSheet; +class CSSStyleDeclaration; +class CSSValue; +class Counter; +class Rect; +class RGBColor; + + + + + +/*######################################################################### +## CSSRule +#########################################################################*/ + +/** + * + */ +class CSSRule +{ +public: + + typedef enum + { + UNKNOWN_RULE = 0, + STYLE_RULE = 1, + CHARSET_RULE = 2, + IMPORT_RULE = 3, + MEDIA_RULE = 4, + FONT_FACE_RULE = 5, + PAGE_RULE = 6 + } RuleType; + + + /** + * + */ + virtual unsigned short getType() + { + return type; + } + + /** + * + */ + virtual DOMString getCssText() + { + return cssText; + } + + /** + * + */ + virtual void setCssText(const DOMString &val) throw (dom::DOMException) + { + cssText = val; + } + + /** + * + */ + virtual CSSStyleSheet *getParentStyleSheet() + { + return parentStyleSheet; + } + + /** + * + */ + virtual CSSRule *getParentRule() + { + return parentRule; + } + + + + //################## + //# Non-API methods + //################## + + /** + * + */ + CSSRule() + { + type = UNKNOWN_RULE; + cssText = ""; + parentStyleSheet = NULL; + parentRule = NULL; + } + + /** + * + */ + CSSRule(const CSSRule &other) + { + type = other.type; + cssText = other.cssText; + parentStyleSheet = other.parentStyleSheet; + parentRule = other.parentRule; + } + + /** + * + */ + virtual ~CSSRule() {} + +protected: + + int type; + + DOMString cssText; + + CSSStyleSheet *parentStyleSheet; + + CSSRule *parentRule; +}; + + + +/*######################################################################### +## CSSRuleList +#########################################################################*/ + +/** + * + */ +class CSSRuleList +{ +public: + + /** + * + */ + virtual unsigned long getLength() + { + return rules.size(); + } + + /** + * + */ + virtual CSSRule item(unsigned long index) + { + if (index>=rules.size()) + { + CSSRule rule; + return rule; + } + return rules[index]; + } + + + //################## + //# Non-API methods + //################## + + /** + * + */ + CSSRuleList() {} + + + /** + * + */ + CSSRuleList(const CSSRuleList &other) + { + rules = other.rules; + } + + /** + * + */ + virtual ~CSSRuleList() {} + +protected: + +friend class CSSMediaRule; +friend class CSSStyleSheet; + + /** + * + */ + virtual void addRule(const CSSRule &rule) + { + rules.push_back(rule); + } + + + /** + * + */ + virtual void deleteRule(unsigned long index) + { + if (index>=rules.size()) + return; + std::vector::iterator iter = rules.begin() + index; + rules.erase(iter); + } + + + /** + * + */ + virtual long insertRule(const CSSRule &rule, unsigned long index) + { + if (index>=rules.size()) + return -1; + std::vector::iterator iter = rules.begin() + index; + rules.insert(iter, rule); + return index; + } + + std::vectorrules; +}; + + +/*######################################################################### +## CSSStyleSheet +#########################################################################*/ + +/** + * + */ +class CSSStyleSheet : virtual public stylesheets::StyleSheet +{ +public: + + /** + * + */ + virtual CSSRule *getOwnerRule() + { + return ownerRule; + } + + /** + * + */ + virtual CSSRuleList getCssRules() + { + return rules; + } + + /** + * + */ + virtual unsigned long insertRule(const DOMString &ruleStr, + unsigned long index) + throw (dom::DOMException) + { + CSSRule rule; + return rules.insertRule(rule, index); + } + + /** + * + */ + virtual void deleteRule(unsigned long index) + throw (dom::DOMException) + { + rules.deleteRule(index); + } + + //################## + //# Non-API methods + //################## + + /** + * + */ + CSSStyleSheet() : stylesheets::StyleSheet() + { + } + + /** + * + */ + CSSStyleSheet(const CSSStyleSheet &other) : + stylesheets::StyleSheet(other) + { + ownerRule = other.ownerRule; + rules = other.rules; + } + + /** + * + */ + virtual ~CSSStyleSheet() {} + +protected: + + CSSRule *ownerRule; + + CSSRuleList rules; +}; + + +/*######################################################################### +## CSSValue +#########################################################################*/ + +/** + * + */ +class CSSValue +{ +public: + + /** + * UnitTypes + */ + enum + { + CSS_INHERIT = 0, + CSS_PRIMITIVE_VALUE = 1, + CSS_VALUE_LIST = 2, + CSS_CUSTOM = 3 + }; + + /** + * + */ + virtual DOMString getCssText() + { + return cssText; + } + + /** + * + */ + virtual void setCssText(const DOMString &val) + throw (dom::DOMException) + { + cssText = val; + } + + /** + * + */ + virtual unsigned short getCssValueType() + { + return valueType; + } + + //################## + //# Non-API methods + //################## + + /** + * + */ + CSSValue() + { + valueType = CSS_INHERIT; + } + + /** + * + */ + CSSValue(const CSSValue &other) + { + cssText = other.cssText; + valueType = other.valueType; + } + + /** + * + */ + virtual ~CSSValue() {} + +protected: + + DOMString cssText; + int valueType; +}; + + + + + +/*######################################################################### +## CSSStyleDeclaration +#########################################################################*/ + +class CSSStyleDeclarationEntry +{ +public: + CSSStyleDeclarationEntry(const DOMString &nameArg, + const DOMString &valueArg, + const DOMString &prioArg) + { + name = nameArg; + value = valueArg; + prio = prioArg; + } + ~CSSStyleDeclarationEntry(){} + DOMString name; + DOMString value; + DOMString prio; +}; + + + +/** + * + */ +class CSSStyleDeclaration +{ +public: + + /** + * + */ + virtual DOMString getCssText() + { + return cssText; + } + + /** + * + */ + virtual void setCssText(const DOMString &val) + throw (dom::DOMException) + { + cssText = val; + } + + /** + * + */ + virtual DOMString getPropertyValue(const DOMString &propertyName) + { + std::vector::iterator iter; + for (iter=items.begin() ; iter!=items.end() ; iter++) + { + if (iter->name == propertyName) + return iter->value; + } + return ""; + } + + /** + * + */ + virtual CSSValue getPropertyCSSValue(const DOMString &propertyName) + { + CSSValue value; + return value; + } + + /** + * + */ + virtual DOMString removeProperty(const DOMString &propertyName) + throw (dom::DOMException) + { + std::vector::iterator iter; + for (iter=items.begin() ; iter!=items.end() ; iter++) + { + if (iter->name == propertyName) + items.erase(iter); + } + return propertyName; + } + + /** + * + */ + virtual DOMString getPropertyPriority(const DOMString &propertyName) + { + std::vector::iterator iter; + for (iter=items.begin() ; iter!=items.end() ; iter++) + { + if (iter->name == propertyName) + return iter->prio; + } + return ""; + } + + /** + * + */ + virtual void setProperty(const DOMString &propertyName, + const DOMString &value, + const DOMString &priority) + throw (dom::DOMException) + { + std::vector::iterator iter; + for (iter=items.begin() ; iter!=items.end() ; iter++) + { + if (iter->name == propertyName) + { + iter->name = propertyName; + iter->value = value; + iter->prio = priority; + return; + } + } + CSSStyleDeclarationEntry entry(propertyName, value, priority); + items.push_back(entry); + } + + /** + * + */ + virtual unsigned long getLength() + { + return items.size(); + } + + /** + * + */ + virtual DOMString item(unsigned long index) + { + if (index>=items.size()) + return ""; + DOMString ret = items[index].name; + ret.append(":"); + ret.append(items[index].value); + return ret; + } + + /** + * + */ + virtual CSSRule *getParentRule() + { + return parentRule; + } + + //################## + //# Non-API methods + //################## + + /** + * + */ + CSSStyleDeclaration() + { + parentRule = NULL; + } + + /** + * + */ + CSSStyleDeclaration(const CSSStyleDeclaration &other) + { + } + + /** + * + */ + virtual ~CSSStyleDeclaration() {} + +protected: + + DOMString cssText; + + CSSRule *parentRule; + + std::vector items; +}; + + + + +/*######################################################################### +## CSSStyleRule +#########################################################################*/ + +/** + * + */ +class CSSStyleRule : virtual public CSSRule +{ +public: + + /** + * + */ + virtual DOMString getSelectorText() + { + return selectorText; + } + + /** + * + */ + virtual void setSelectorText(const DOMString &val) + throw (dom::DOMException) + { + selectorText = val; + } + + + /** + * + */ + virtual CSSStyleDeclaration &getStyle() + { + return style; + } + + //################## + //# Non-API methods + //################## + + /** + * + */ + CSSStyleRule() : CSSRule() + { + type = STYLE_RULE; + selectorText = ""; + } + + + /** + * + */ + CSSStyleRule(const CSSStyleRule &other) : CSSRule(other) + { + selectorText = other.selectorText; + style = other.style; + } + + /** + * + */ + virtual ~CSSStyleRule() {} + +protected: + + DOMString selectorText; + + CSSStyleDeclaration style; + +}; + +/*######################################################################### +## CSSMediaRule +#########################################################################*/ + +/** + * + */ +class CSSMediaRule : virtual public CSSRule +{ +public: + + /** + * + */ + virtual stylesheets::MediaList getMedia() + { + return mediaList; + } + + /** + * + */ + virtual CSSRuleList getCssRules() + { + return cssRules; + } + + /** + * + */ + virtual unsigned long insertRule(const DOMString &ruleStr, + unsigned long index) + throw (dom::DOMException) + { + if (index>cssRules.getLength()) + return 0; + CSSRule rule; + cssRules.insertRule(rule, index); + return index; + } + + /** + * + */ + virtual void deleteRule(unsigned long index) + throw(dom::DOMException) + { + cssRules.deleteRule(index); + } + + //################## + //# Non-API methods + //################## + + /** + * + */ + CSSMediaRule() : CSSRule() + { + type = MEDIA_RULE; + } + + /** + * + */ + CSSMediaRule(const CSSMediaRule &other) : CSSRule(other) + { + mediaList = other.mediaList; + cssRules = other.cssRules; + } + + /** + * + */ + virtual ~CSSMediaRule() {} + +protected: + + stylesheets::MediaList mediaList; + + CSSRuleList cssRules; +}; + + + + +/*######################################################################### +## CSSFontFaceRule +#########################################################################*/ + +/** + * + */ +class CSSFontFaceRule : virtual public CSSRule +{ +public: + + /** + * + */ + virtual CSSStyleDeclaration getStyle() + { + return style; + } + + + //################## + //# Non-API methods + //################## + + /** + * + */ + CSSFontFaceRule() : CSSRule() + { + type = FONT_FACE_RULE; + } + + /** + * + */ + CSSFontFaceRule(const CSSFontFaceRule &other) : CSSRule(other) + { + style = other.style; + } + + /** + * + */ + virtual ~CSSFontFaceRule() {} + +protected: + + CSSStyleDeclaration style; +}; + + + + +/*######################################################################### +## CSSPageRule +#########################################################################*/ + +/** + * + */ +class CSSPageRule : virtual public CSSRule +{ +public: + + /** + * + */ + virtual DOMString getSelectorText() + { + return selectorText; + } + + /** + * + */ + virtual void setSelectorText(const DOMString &val) + throw(dom::DOMException) + { + selectorText = val; + } + + + /** + * + */ + virtual CSSStyleDeclaration getStyle() + { + return style; + } + + + //################## + //# Non-API methods + //################## + + /** + * + */ + CSSPageRule() : CSSRule() + { + type = PAGE_RULE; + } + + /** + * + */ + CSSPageRule(const CSSPageRule &other) : CSSRule(other) + { + selectorText = other.selectorText; + style = other.style; + } + + /** + * + */ + virtual ~CSSPageRule() {} + +protected: + + DOMString selectorText; + + CSSStyleDeclaration style; +}; + + + + + +/*######################################################################### +## CSSImportRule +#########################################################################*/ + +/** + * + */ +class CSSImportRule : virtual public CSSRule +{ +public: + + /** + * + */ + virtual DOMString getHref() + { + return href; + } + + /** + * + */ + virtual stylesheets::MediaList getMedia() + { + return mediaList; + } + + /** + * + */ + virtual CSSStyleSheet getStyleSheet() + { + return styleSheet; + } + + + //################## + //# Non-API methods + //################## + + /** + * + */ + CSSImportRule() : CSSRule() + { + type = IMPORT_RULE; + } + + /** + * + */ + CSSImportRule(const CSSImportRule &other) : CSSRule(other) + { + mediaList = other.mediaList; + styleSheet = other.styleSheet; + } + + /** + * + */ + virtual ~CSSImportRule() {} + +protected: + + DOMString href; + + stylesheets::MediaList mediaList; + + CSSStyleSheet styleSheet; +}; + + + + + + +/*######################################################################### +## CSSCharsetRule +#########################################################################*/ + +/** + * + */ +class CSSCharsetRule : virtual public CSSRule +{ +public: + + /** + * + */ + virtual DOMString getEncoding() + { + return encoding; + } + + /** + * + */ + virtual void setEncoding(const DOMString &val) throw (dom::DOMException) + { + encoding = val; + } + + //################## + //# Non-API methods + //################## + + /** + * + */ + CSSCharsetRule() : CSSRule() + { + type = CHARSET_RULE; + } + + /** + * + */ + CSSCharsetRule(const CSSCharsetRule &other) : CSSRule(other) + { + encoding = other.encoding; + } + + /** + * + */ + virtual ~CSSCharsetRule() {} + +protected: + + DOMString encoding; + +}; + + + + + +/*######################################################################### +## CSSUnknownRule +#########################################################################*/ + +/** + * + */ +class CSSUnknownRule : virtual public CSSRule +{ +public: + + //################## + //# Non-API methods + //################## + + /** + * + */ + CSSUnknownRule() : CSSRule() + { + type = UNKNOWN_RULE; + } + + /** + * + */ + CSSUnknownRule(const CSSUnknownRule &other) : CSSRule(other) + { + } + + /** + * + */ + virtual ~CSSUnknownRule() {} +}; + + + + + + + +/*######################################################################### +## CSSValueList +#########################################################################*/ + +/** + * + */ +class CSSValueList : virtual public CSSValue +{ +public: + + /** + * + */ + virtual unsigned long getLength() + { + return items.size(); + } + + /** + * + */ + virtual CSSValue item(unsigned long index) + { + if (index>=items.size()) + { + CSSValue dummy; + return dummy; + } + return items[index]; + } + + //################## + //# Non-API methods + //################## + + /** + * + */ + CSSValueList() + { + } + + /** + * + */ + CSSValueList(const CSSValueList &other) : CSSValue(other) + { + items = other.items; + } + + /** + * + */ + virtual ~CSSValueList() {} + +protected: + + std::vector items; +}; + + + + +/*######################################################################### +## CSSPrimitiveValue +#########################################################################*/ + +/** + * + */ +class CSSPrimitiveValue : virtual public CSSValue +{ +public: + + /** + * UnitTypes + */ + enum + { + CSS_UNKNOWN = 0, + CSS_NUMBER = 1, + CSS_PERCENTAGE = 2, + CSS_EMS = 3, + CSS_EXS = 4, + CSS_PX = 5, + CSS_CM = 6, + CSS_MM = 7, + CSS_IN = 8, + CSS_PT = 9, + CSS_PC = 10, + CSS_DEG = 11, + CSS_RAD = 12, + CSS_GRAD = 13, + CSS_MS = 14, + CSS_S = 15, + CSS_HZ = 16, + CSS_KHZ = 17, + CSS_DIMENSION = 18, + CSS_STRING = 19, + CSS_URI = 20, + CSS_IDENT = 21, + CSS_ATTR = 22, + CSS_COUNTER = 23, + CSS_RECT = 24, + CSS_RGBCOLOR = 25 + }; + + + /** + * + */ + virtual unsigned short getPrimitiveType() + { + return primitiveType; + } + + /** + * + */ + virtual void setFloatValue(unsigned short unitType, + double doubleValueArg) + throw (dom::DOMException) + { + primitiveType = unitType; + doubleValue = doubleValueArg; + } + /** + * + */ + virtual double getFloatValue(unsigned short unitType) + throw (dom::DOMException) + { + return doubleValue; + } + + /** + * + */ + virtual void setStringValue(unsigned short stringType, + const DOMString &stringValueArg) + throw (dom::DOMException) + { + stringValue = stringValueArg; + } + + /** + * + */ + virtual DOMString getStringValue() throw (dom::DOMException) + { + return stringValue; + } + + /** + * + */ + virtual Counter *getCounterValue() throw (dom::DOMException) + { + return NULL; + } + + /** + * + */ + virtual Rect *getRectValue() throw (dom::DOMException) + { + return NULL; + } + + /** + * + */ + virtual RGBColor *getRGBColorValue() throw (dom::DOMException) + { + return NULL; + } + + //################## + //# Non-API methods + //################## + + /** + * + */ + CSSPrimitiveValue() : CSSValue() + { + } + + /** + * + */ + CSSPrimitiveValue(const CSSPrimitiveValue &other) : CSSValue(other) + { + } + + /** + * + */ + virtual ~CSSPrimitiveValue() {} + +protected: + + int primitiveType; + + double doubleValue; + + DOMString stringValue; + + +}; + + + +/*######################################################################### +## RGBColor +#########################################################################*/ + +/** + * + */ +class RGBColor +{ +public: + + /** + * + */ + virtual CSSPrimitiveValue getRed() + { + return red; + } + + /** + * + */ + virtual CSSPrimitiveValue getGreen() + { + return green; + } + + /** + * + */ + virtual CSSPrimitiveValue getBlue() + { + return blue; + } + + /** + * REPLACES: RGBColor CSSPrimitiveValue::getRGBColorValue() throw (dom::DOMException) + */ + static RGBColor getRGBColorValue(const CSSPrimitiveValue &val) + { + RGBColor col; + return col; + } + + //################## + //# Non-API methods + //################## + + /** + * + */ + RGBColor() {} + + /** + * + */ + RGBColor(const RGBColor &other) + { + red = other.red; + green = other.green; + blue = other.blue; + } + + /** + * + */ + virtual ~RGBColor() {} + +protected: + + CSSPrimitiveValue red; + CSSPrimitiveValue green; + CSSPrimitiveValue blue; +}; + + + + +/*######################################################################### +## Rect +#########################################################################*/ + +/** + * + */ +class Rect +{ +public: + + /** + * + */ + virtual CSSPrimitiveValue getTop() + { + return top; + } + + /** + * + */ + virtual CSSPrimitiveValue getRight() + { + return right; + } + + /** + * + */ + virtual CSSPrimitiveValue getBottom() + { + return bottom; + } + + /** + * + */ + virtual CSSPrimitiveValue getLeft() + { + return left; + } + + /** + * REPLACES: Rect CSSPrimitiveValue::getRectValue() throw (dom::DOMException) + */ + static Rect getRectValue(const CSSPrimitiveValue &val) + { + Rect rect; + return rect; + } + + //################## + //# Non-API methods + //################## + + /** + * + */ + Rect() {} + + /** + * + */ + Rect(const Rect &other) + { + top = other.top; + right = other.right; + bottom = other.bottom; + left = other.left; + } + + /** + * + */ + virtual ~Rect() {} + +protected: + + CSSPrimitiveValue top; + CSSPrimitiveValue right; + CSSPrimitiveValue bottom; + CSSPrimitiveValue left; +}; + + + + + + +/*######################################################################### +## Counter +#########################################################################*/ + +/** + * + */ +class Counter +{ +public: + + /** + * + */ + virtual DOMString getIdentifier() + { + return identifier; + } + + /** + * + */ + virtual DOMString getListStyle() + { + return listStyle; + } + + /** + * + */ + virtual DOMString getSeparator() + { + return separator; + } + + /** + * REPLACES: Counter CSSPrimitiveValue::getCounterValue() throw (dom::DOMException) + */ + static Counter getCounterValue(const CSSPrimitiveValue &val) + { + Counter counter; + return counter; + } + + //################## + //# Non-API methods + //################## + + /** + * + */ + Counter() {} + + /** + * + */ + Counter(const Counter &other) + { + identifier = other.identifier; + listStyle = other.listStyle; + separator = other.separator; + } + + /** + * + */ + virtual ~Counter() {} + +protected: + + DOMString identifier; + DOMString listStyle; + DOMString separator; + +}; + + + + +/*######################################################################### +## ElementCSSInlineStyle +#########################################################################*/ + +/** + * + */ +class ElementCSSInlineStyle +{ +public: + + /** + * + */ + virtual CSSStyleDeclaration getStyle() + { + return style; + } + + //################## + //# Non-API methods + //################## + + /** + * + */ + ElementCSSInlineStyle() {} + + /** + * + */ + ElementCSSInlineStyle(const ElementCSSInlineStyle &other) + { + style = other.style; + } + + /** + * + */ + virtual ~ElementCSSInlineStyle() {} + +protected: + + CSSStyleDeclaration style; +}; + + + + + + +/*######################################################################### +## CSS2Properties +#########################################################################*/ + +/** + * + */ +class CSS2Properties +{ +public: + + + /** + * return the 'azimuth' property + */ + virtual DOMString getAzimuth() + { + return azimuth; + } + + /** + * set the 'azimuth' property + */ + virtual void setAzimuth(const DOMString &val) + throw (dom::DOMException) + { + azimuth = val; + } + + /** + * return the 'background' property + */ + virtual DOMString getBackground() + { + return background; + } + + /** + * set the 'background' property + */ + virtual void setBackground(const DOMString &val) + throw (dom::DOMException) + { + background = val; + } + + /** + * return the 'backgroundAttachment' property + */ + virtual DOMString getBackgroundAttachment() + { + return backgroundAttachment; + } + + /** + * set the 'backgroundAttachment' property + */ + virtual void setBackgroundAttachment(const DOMString &val) + throw (dom::DOMException) + { + backgroundAttachment = val; + } + + /** + * return the 'backgroundColor' property + */ + virtual DOMString getBackgroundColor() + { + return backgroundColor; + } + + /** + * set the 'backgroundColor' property + */ + virtual void setBackgroundColor(const DOMString &val) + throw (dom::DOMException) + { + backgroundColor = val; + } + + /** + * return the 'backgroundImage' property + */ + virtual DOMString getBackgroundImage() + { + return backgroundImage; + } + + /** + * set the 'backgroundImage' property + */ + virtual void setBackgroundImage(const DOMString &val) + throw (dom::DOMException) + { + backgroundImage = val; + } + + /** + * return the 'backgroundPosition' property + */ + virtual DOMString getBackgroundPosition() + { + return backgroundPosition; + } + + /** + * set the 'backgroundPosition' property + */ + virtual void setBackgroundPosition(const DOMString &val) + throw (dom::DOMException) + { + backgroundPosition = val; + } + + /** + * return the 'backgroundRepeat' property + */ + virtual DOMString getBackgroundRepeat() + { + return backgroundRepeat; + } + + /** + * set the 'backgroundRepeat' property + */ + virtual void setBackgroundRepeat(const DOMString &val) + throw (dom::DOMException) + { + backgroundRepeat = val; + } + + /** + * return the 'border' property + */ + virtual DOMString getBorder() + { + return border; + } + + /** + * set the 'border' property + */ + virtual void setBorder(const DOMString &val) + throw (dom::DOMException) + { + border = val; + } + + /** + * return the 'borderCollapse' property + */ + virtual DOMString getBorderCollapse() + { + return borderCollapse; + } + + /** + * set the 'borderCollapse' property + */ + virtual void setBorderCollapse(const DOMString &val) + throw (dom::DOMException) + { + borderCollapse = val; + } + + /** + * return the 'borderColor' property + */ + virtual DOMString getBorderColor() + { + return borderColor; + } + + /** + * set the 'borderColor' property + */ + virtual void setBorderColor(const DOMString &val) + throw (dom::DOMException) + { + borderColor = val; + } + + /** + * return the 'borderSpacing' property + */ + virtual DOMString getBorderSpacing() + { + return borderSpacing; + } + + /** + * set the 'borderSpacing' property + */ + virtual void setBorderSpacing(const DOMString &val) + throw (dom::DOMException) + { + borderSpacing = val; + } + + /** + * return the 'borderStyle' property + */ + virtual DOMString getBorderStyle() + { + return borderStyle; + } + + /** + * set the 'borderStyle' property + */ + virtual void setBorderStyle(const DOMString &val) + throw (dom::DOMException) + { + borderStyle = val; + } + + /** + * return the 'borderTop' property + */ + virtual DOMString getBorderTop() + { + return borderTop; + } + + /** + * set the 'borderTop' property + */ + virtual void setBorderTop(const DOMString &val) + throw (dom::DOMException) + { + borderTop = val; + } + + /** + * return the 'borderRight' property + */ + virtual DOMString getBorderRight() + { + return borderRight; + } + + /** + * set the 'borderRight' property + */ + virtual void setBorderRight(const DOMString &val) + throw (dom::DOMException) + { + borderRight = val; + } + + /** + * return the 'borderBottom' property + */ + virtual DOMString getBorderBottom() + { + return borderBottom; + } + + /** + * set the 'borderBottom' property + */ + virtual void setBorderBottom(const DOMString &val) + throw (dom::DOMException) + { + borderBottom = val; + } + + /** + * return the 'borderLeft' property + */ + virtual DOMString getBorderLeft() + { + return borderLeft; + } + + /** + * set the 'borderLeft' property + */ + virtual void setBorderLeft(const DOMString &val) + throw (dom::DOMException) + { + borderLeft = val; + } + + /** + * return the 'borderTopColor' property + */ + virtual DOMString getBorderTopColor() + { + return borderTopColor; + } + + /** + * set the 'borderTopColor' property + */ + virtual void setBorderTopColor(const DOMString &val) + throw (dom::DOMException) + { + borderTopColor = val; + } + + /** + * return the 'borderRightColor' property + */ + virtual DOMString getBorderRightColor() + { + return borderRightColor; + } + + /** + * set the 'borderRightColor' property + */ + virtual void setBorderRightColor(const DOMString &val) + throw (dom::DOMException) + { + borderRightColor = val; + } + + /** + * return the 'borderBottomColor' property + */ + virtual DOMString getBorderBottomColor() + { + return borderBottomColor; + } + + /** + * set the 'borderBottomColor' property + */ + virtual void setBorderBottomColor(const DOMString &val) + throw (dom::DOMException) + { + borderBottomColor = val; + } + + /** + * return the 'borderLeftColor' property + */ + virtual DOMString getBorderLeftColor() + { + return borderLeftColor; + } + + /** + * set the 'borderLeftColor' property + */ + virtual void setBorderLeftColor(const DOMString &val) + throw (dom::DOMException) + { + borderLeftColor = val; + } + + /** + * return the 'borderTopStyle' property + */ + virtual DOMString getBorderTopStyle() + { + return borderTopStyle; + } + + /** + * set the 'borderTopStyle' property + */ + virtual void setBorderTopStyle(const DOMString &val) + throw (dom::DOMException) + { + borderTopStyle = val; + } + + /** + * return the 'borderRightStyle' property + */ + virtual DOMString getBorderRightStyle() + { + return borderRightStyle; + } + + /** + * set the 'borderRightStyle' property + */ + virtual void setBorderRightStyle(const DOMString &val) + throw (dom::DOMException) + { + borderRightStyle = val; + } + + /** + * return the 'borderBottomStyle' property + */ + virtual DOMString getBorderBottomStyle() + { + return borderBottomStyle; + } + + /** + * set the 'borderBottomStyle' property + */ + virtual void setBorderBottomStyle(const DOMString &val) + throw (dom::DOMException) + { + borderBottomStyle = val; + } + + /** + * return the 'borderLeftStyle' property + */ + virtual DOMString getBorderLeftStyle() + { + return borderLeftStyle; + } + + /** + * set the 'borderLeftStyle' property + */ + virtual void setBorderLeftStyle(const DOMString &val) + throw (dom::DOMException) + { + borderLeftStyle = val; + } + + /** + * return the 'borderTopWidth' property + */ + virtual DOMString getBorderTopWidth() + { + return borderTopWidth; + } + + /** + * set the 'borderTopWidth' property + */ + virtual void setBorderTopWidth(const DOMString &val) + throw (dom::DOMException) + { + borderTopWidth = val; + } + + /** + * return the 'borderRightWidth' property + */ + virtual DOMString getBorderRightWidth() + { + return borderRightWidth; + } + + /** + * set the 'borderRightWidth' property + */ + virtual void setBorderRightWidth(const DOMString &val) + throw (dom::DOMException) + { + borderRightWidth = val; + } + + /** + * return the 'borderBottomWidth' property + */ + virtual DOMString getBorderBottomWidth() + { + return borderBottomWidth; + } + + /** + * set the 'borderBottomWidth' property + */ + virtual void setBorderBottomWidth(const DOMString &val) + throw (dom::DOMException) + { + borderBottomWidth = val; + } + + /** + * return the 'borderLeftWidth' property + */ + virtual DOMString getBorderLeftWidth() + { + return borderLeftWidth; + } + + /** + * set the 'borderLeftWidth' property + */ + virtual void setBorderLeftWidth(const DOMString &val) + throw (dom::DOMException) + { + borderLeftWidth = val; + } + + /** + * return the 'borderWidth' property + */ + virtual DOMString getBorderWidth() + { + return borderWidth; + } + + /** + * set the 'borderWidth' property + */ + virtual void setBorderWidth(const DOMString &val) + throw (dom::DOMException) + { + borderWidth = val; + } + + /** + * return the 'bottom' property + */ + virtual DOMString getBottom() + { + return bottom; + } + + /** + * set the 'bottom' property + */ + virtual void setBottom(const DOMString &val) + throw (dom::DOMException) + { + bottom = val; + } + + /** + * return the 'captionSide' property + */ + virtual DOMString getCaptionSide() + { + return captionSide; + } + + /** + * set the 'captionSide' property + */ + virtual void setCaptionSide(const DOMString &val) + throw (dom::DOMException) + { + captionSide = val; + } + + /** + * return the 'clear' property + */ + virtual DOMString getClear() + { + return clear; + } + + /** + * set the 'clear' property + */ + virtual void setClear(const DOMString &val) + throw (dom::DOMException) + { + clear = val; + } + + /** + * return the 'clip' property + */ + virtual DOMString getClip() + { + return clip; + } + + /** + * set the 'clip' property + */ + virtual void setClip(const DOMString &val) + throw (dom::DOMException) + { + clip = val; + } + + /** + * return the 'color' property + */ + virtual DOMString getColor() + { + return color; + } + + /** + * set the 'color' property + */ + virtual void setColor(const DOMString &val) + throw (dom::DOMException) + { + color = val; + } + + /** + * return the 'content' property + */ + virtual DOMString getContent() + { + return content; + } + + /** + * set the 'content' property + */ + virtual void setContent(const DOMString &val) + throw (dom::DOMException) + { + content = val; + } + + /** + * return the 'counterIncrement' property + */ + virtual DOMString getCounterIncrement() + { + return counterIncrement; + } + + /** + * set the 'counterIncrement' property + */ + virtual void setCounterIncrement(const DOMString &val) + throw (dom::DOMException) + { + counterIncrement = val; + } + + /** + * return the 'counterReset' property + */ + virtual DOMString getCounterReset() + { + return counterReset; + } + + /** + * set the 'counterReset' property + */ + virtual void setCounterReset(const DOMString &val) + throw (dom::DOMException) + { + counterReset = val; + } + + /** + * return the 'cue' property + */ + virtual DOMString getCue() + { + return cue; + } + + /** + * set the 'cue' property + */ + virtual void setCue(const DOMString &val) + throw (dom::DOMException) + { + cue = val; + } + + /** + * return the 'cueAfter' property + */ + virtual DOMString getCueAfter() + { + return cueAfter; + } + + /** + * set the 'cueAfter' property + */ + virtual void setCueAfter(const DOMString &val) + throw (dom::DOMException) + { + cueAfter = val; + } + + /** + * return the 'cueBefore' property + */ + virtual DOMString getCueBefore() + { + return cueBefore; + } + + /** + * set the 'cueBefore' property + */ + virtual void setCueBefore(const DOMString &val) + throw (dom::DOMException) + { + cueBefore = val; + } + + /** + * return the 'cursor' property + */ + virtual DOMString getCursor() + { + return cursor; + } + + /** + * set the 'cursor' property + */ + virtual void setCursor(const DOMString &val) + throw (dom::DOMException) + { + cursor = val; + } + + /** + * return the 'direction' property + */ + virtual DOMString getDirection() + { + return direction; + } + + /** + * set the 'direction' property + */ + virtual void setDirection(const DOMString &val) + throw (dom::DOMException) + { + direction = val; + } + + /** + * return the 'display' property + */ + virtual DOMString getDisplay() + { + return display; + } + + /** + * set the 'display' property + */ + virtual void setDisplay(const DOMString &val) + throw (dom::DOMException) + { + display = val; + } + + /** + * return the 'elevation' property + */ + virtual DOMString getElevation() + { + return elevation; + } + + /** + * set the 'elevation' property + */ + virtual void setElevation(const DOMString &val) + throw (dom::DOMException) + { + elevation = val; + } + + /** + * return the 'emptyCells' property + */ + virtual DOMString getEmptyCells() + { + return emptyCells; + } + + /** + * set the 'emptyCells' property + */ + virtual void setEmptyCells(const DOMString &val) + throw (dom::DOMException) + { + emptyCells = val; + } + + /** + * return the 'cssFloat' property + */ + virtual DOMString getCssFloat() + { + return cssFloat; + } + + /** + * set the 'cssFloat' property + */ + virtual void setCssFloat(const DOMString &val) + throw (dom::DOMException) + { + cssFloat = val; + } + + /** + * return the 'font' property + */ + virtual DOMString getFont() + { + return font; + } + + /** + * set the 'font' property + */ + virtual void setFont(const DOMString &val) + throw (dom::DOMException) + { + font = val; + } + + /** + * return the 'fontFamily' property + */ + virtual DOMString getFontFamily() + { + return fontFamily; + } + + /** + * set the 'fontFamily' property + */ + virtual void setFontFamily(const DOMString &val) + throw (dom::DOMException) + { + fontFamily = val; + } + + /** + * return the 'fontSize' property + */ + virtual DOMString getFontSize() + { + return fontSize; + } + + /** + * set the 'fontSize' property + */ + virtual void setFontSize(const DOMString &val) + throw (dom::DOMException) + { + fontSize = val; + } + + /** + * return the 'fontSizeAdjust' property + */ + virtual DOMString getFontSizeAdjust() + { + return fontSizeAdjust; + } + + /** + * set the 'fontSizeAdjust' property + */ + virtual void setFontSizeAdjust(const DOMString &val) + throw (dom::DOMException) + { + fontSizeAdjust = val; + } + + /** + * return the 'fontStretch' property + */ + virtual DOMString getFontStretch() + { + return fontStretch; + } + + /** + * set the 'fontStretch' property + */ + virtual void setFontStretch(const DOMString &val) + throw (dom::DOMException) + { + fontStretch = val; + } + + /** + * return the 'fontStyle' property + */ + virtual DOMString getFontStyle() + { + return fontStyle; + } + + /** + * set the 'fontStyle' property + */ + virtual void setFontStyle(const DOMString &val) + throw (dom::DOMException) + { + fontStyle = val; + } + + /** + * return the 'fontVariant' property + */ + virtual DOMString getFontVariant() + { + return fontVariant; + } + + /** + * set the 'fontVariant' property + */ + virtual void setFontVariant(const DOMString &val) + throw (dom::DOMException) + { + fontVariant = val; + } + + /** + * return the 'fontWeight' property + */ + virtual DOMString getFontWeight() + { + return fontWeight; + } + + /** + * set the 'fontWeight' property + */ + virtual void setFontWeight(const DOMString &val) + throw (dom::DOMException) + { + fontWeight = val; + } + + /** + * return the 'height' property + */ + virtual DOMString getHeight() + { + return height; + } + + /** + * set the 'height' property + */ + virtual void setHeight(const DOMString &val) + throw (dom::DOMException) + { + height = val; + } + + /** + * return the 'left' property + */ + virtual DOMString getLeft() + { + return left; + } + + /** + * set the 'left' property + */ + virtual void setLeft(const DOMString &val) + throw (dom::DOMException) + { + left = val; + } + + /** + * return the 'letterSpacing' property + */ + virtual DOMString getLetterSpacing() + { + return letterSpacing; + } + + /** + * set the 'letterSpacing' property + */ + virtual void setLetterSpacing(const DOMString &val) + throw (dom::DOMException) + { + letterSpacing = val; + } + + /** + * return the 'lineHeight' property + */ + virtual DOMString getLineHeight() + { + return lineHeight; + } + + /** + * set the 'lineHeight' property + */ + virtual void setLineHeight(const DOMString &val) + throw (dom::DOMException) + { + lineHeight = val; + } + + /** + * return the 'listStyle' property + */ + virtual DOMString getListStyle() + { + return listStyle; + } + + /** + * set the 'listStyle' property + */ + virtual void setListStyle(const DOMString &val) + throw (dom::DOMException) + { + listStyle = val; + } + + /** + * return the 'listStyleImage' property + */ + virtual DOMString getListStyleImage() + { + return listStyleImage; + } + + /** + * set the 'listStyleImage' property + */ + virtual void setListStyleImage(const DOMString &val) + throw (dom::DOMException) + { + listStyleImage = val; + } + + /** + * return the 'listStylePosition' property + */ + virtual DOMString getListStylePosition() + { + return listStylePosition; + } + + /** + * set the 'listStylePosition' property + */ + virtual void setListStylePosition(const DOMString &val) + throw (dom::DOMException) + { + listStylePosition = val; + } + + /** + * return the 'listStyleType' property + */ + virtual DOMString getListStyleType() + { + return listStyleType; + } + + /** + * set the 'listStyleType' property + */ + virtual void setListStyleType(const DOMString &val) + throw (dom::DOMException) + { + listStyleType = val; + } + + /** + * return the 'margin' property + */ + virtual DOMString getMargin() + { + return margin; + } + + /** + * set the 'margin' property + */ + virtual void setMargin(const DOMString &val) + throw (dom::DOMException) + { + margin = val; + } + + /** + * return the 'marginTop' property + */ + virtual DOMString getMarginTop() + { + return marginTop; + } + + /** + * set the 'marginTop' property + */ + virtual void setMarginTop(const DOMString &val) + throw (dom::DOMException) + { + marginTop = val; + } + + /** + * return the 'marginRight' property + */ + virtual DOMString getMarginRight() + { + return marginRight; + } + + /** + * set the 'marginRight' property + */ + virtual void setMarginRight(const DOMString &val) + throw (dom::DOMException) + { + marginRight = val; + } + + /** + * return the 'marginBottom' property + */ + virtual DOMString getMarginBottom() + { + return marginBottom; + } + + /** + * set the 'marginBottom' property + */ + virtual void setMarginBottom(const DOMString &val) + throw (dom::DOMException) + { + marginBottom = val; + } + + /** + * return the 'marginLeft' property + */ + virtual DOMString getMarginLeft() + { + return marginLeft; + } + + /** + * set the 'marginLeft' property + */ + virtual void setMarginLeft(const DOMString &val) + throw (dom::DOMException) + { + marginLeft = val; + } + + /** + * return the 'markerOffset' property + */ + virtual DOMString getMarkerOffset() + { + return markerOffset; + } + + /** + * set the 'markerOffset' property + */ + virtual void setMarkerOffset(const DOMString &val) + throw (dom::DOMException) + { + markerOffset = val; + } + + /** + * return the 'marks' property + */ + virtual DOMString getMarks() + { + return marks; + } + + /** + * set the 'marks' property + */ + virtual void setMarks(const DOMString &val) + throw (dom::DOMException) + { + marks = val; + } + + /** + * return the 'maxHeight' property + */ + virtual DOMString getMaxHeight() + { + return maxHeight; + } + + /** + * set the 'maxHeight' property + */ + virtual void setMaxHeight(const DOMString &val) + throw (dom::DOMException) + { + maxHeight = val; + } + + /** + * return the 'maxWidth' property + */ + virtual DOMString getMaxWidth() + { + return maxWidth; + } + + /** + * set the 'maxWidth' property + */ + virtual void setMaxWidth(const DOMString &val) + throw (dom::DOMException) + { + maxWidth = val; + } + + /** + * return the 'minHeight' property + */ + virtual DOMString getMinHeight() + { + return minHeight; + } + + /** + * set the 'minHeight' property + */ + virtual void setMinHeight(const DOMString &val) + throw (dom::DOMException) + { + minHeight = val; + } + + /** + * return the 'minWidth' property + */ + virtual DOMString getMinWidth() + { + return minWidth; + } + + /** + * set the 'minWidth' property + */ + virtual void setMinWidth(const DOMString &val) + throw (dom::DOMException) + { + minWidth = val; + } + + /** + * return the 'orphans' property + */ + virtual DOMString getOrphans() + { + return orphans; + } + + /** + * set the 'orphans' property + */ + virtual void setOrphans(const DOMString &val) + throw (dom::DOMException) + { + orphans = val; + } + + /** + * return the 'outline' property + */ + virtual DOMString getOutline() + { + return outline; + } + + /** + * set the 'outline' property + */ + virtual void setOutline(const DOMString &val) + throw (dom::DOMException) + { + outline = val; + } + + /** + * return the 'outlineColor' property + */ + virtual DOMString getOutlineColor() + { + return outlineColor; + } + + /** + * set the 'outlineColor' property + */ + virtual void setOutlineColor(const DOMString &val) + throw (dom::DOMException) + { + outlineColor = val; + } + + /** + * return the 'outlineStyle' property + */ + virtual DOMString getOutlineStyle() + { + return outlineStyle; + } + + /** + * set the 'outlineStyle' property + */ + virtual void setOutlineStyle(const DOMString &val) + throw (dom::DOMException) + { + outlineStyle = val; + } + + /** + * return the 'outlineWidth' property + */ + virtual DOMString getOutlineWidth() + { + return outlineWidth; + } + + /** + * set the 'outlineWidth' property + */ + virtual void setOutlineWidth(const DOMString &val) + throw (dom::DOMException) + { + outlineWidth = val; + } + + /** + * return the 'overflow' property + */ + virtual DOMString getOverflow() + { + return overflow; + } + + /** + * set the 'overflow' property + */ + virtual void setOverflow(const DOMString &val) + throw (dom::DOMException) + { + overflow = val; + } + + /** + * return the 'padding' property + */ + virtual DOMString getPadding() + { + return padding; + } + + /** + * set the 'padding' property + */ + virtual void setPadding(const DOMString &val) + throw (dom::DOMException) + { + padding = val; + } + + /** + * return the 'paddingTop' property + */ + virtual DOMString getPaddingTop() + { + return paddingTop; + } + + /** + * set the 'paddingTop' property + */ + virtual void setPaddingTop(const DOMString &val) + throw (dom::DOMException) + { + paddingTop = val; + } + + /** + * return the 'paddingRight' property + */ + virtual DOMString getPaddingRight() + { + return paddingRight; + } + + /** + * set the 'paddingRight' property + */ + virtual void setPaddingRight(const DOMString &val) + throw (dom::DOMException) + { + paddingRight = val; + } + + /** + * return the 'paddingBottom' property + */ + virtual DOMString getPaddingBottom() + { + return paddingBottom; + } + + /** + * set the 'paddingBottom' property + */ + virtual void setPaddingBottom(const DOMString &val) + throw (dom::DOMException) + { + paddingBottom = val; + } + + /** + * return the 'paddingLeft' property + */ + virtual DOMString getPaddingLeft() + { + return paddingLeft; + } + + /** + * set the 'paddingLeft' property + */ + virtual void setPaddingLeft(const DOMString &val) + throw (dom::DOMException) + { + paddingLeft = val; + } + + /** + * return the 'page' property + */ + virtual DOMString getPage() + { + return page; + } + + /** + * set the 'page' property + */ + virtual void setPage(const DOMString &val) + throw (dom::DOMException) + { + page = val; + } + + /** + * return the 'pageBreakAfter' property + */ + virtual DOMString getPageBreakAfter() + { + return pageBreakAfter; + } + + /** + * set the 'pageBreakAfter' property + */ + virtual void setPageBreakAfter(const DOMString &val) + throw (dom::DOMException) + { + pageBreakAfter = val; + } + + /** + * return the 'pageBreakBefore' property + */ + virtual DOMString getPageBreakBefore() + { + return pageBreakBefore; + } + + /** + * set the 'pageBreakBefore' property + */ + virtual void setPageBreakBefore(const DOMString &val) + throw (dom::DOMException) + { + pageBreakBefore = val; + } + + /** + * return the 'pageBreakInside' property + */ + virtual DOMString getPageBreakInside() + { + return pageBreakInside; + } + + /** + * set the 'pageBreakInside' property + */ + virtual void setPageBreakInside(const DOMString &val) + throw (dom::DOMException) + { + pageBreakInside = val; + } + + /** + * return the 'pause' property + */ + virtual DOMString getPause() + { + return pause; + } + + /** + * set the 'pause' property + */ + virtual void setPause(const DOMString &val) + throw (dom::DOMException) + { + pause = val; + } + + /** + * return the 'pauseAfter' property + */ + virtual DOMString getPauseAfter() + { + return pauseAfter; + } + + /** + * set the 'pauseAfter' property + */ + virtual void setPauseAfter(const DOMString &val) + throw (dom::DOMException) + { + pauseAfter = val; + } + + /** + * return the 'pauseBefore' property + */ + virtual DOMString getPauseBefore() + { + return pauseBefore; + } + + /** + * set the 'pauseBefore' property + */ + virtual void setPauseBefore(const DOMString &val) + throw (dom::DOMException) + { + pauseBefore = val; + } + + /** + * return the 'pitch' property + */ + virtual DOMString getPitch() + { + return pitch; + } + + /** + * set the 'pitch' property + */ + virtual void setPitch(const DOMString &val) + throw (dom::DOMException) + { + pitch = val; + } + + /** + * return the 'pitchRange' property + */ + virtual DOMString getPitchRange() + { + return pitchRange; + } + + /** + * set the 'pitchRange' property + */ + virtual void setPitchRange(const DOMString &val) + throw (dom::DOMException) + { + pitchRange = val; + } + + /** + * return the 'playDuring' property + */ + virtual DOMString getPlayDuring() + { + return playDuring; + } + + /** + * set the 'playDuring' property + */ + virtual void setPlayDuring(const DOMString &val) + throw (dom::DOMException) + { + playDuring = val; + } + + /** + * return the 'position' property + */ + virtual DOMString getPosition() + { + return position; + } + + /** + * set the 'position' property + */ + virtual void setPosition(const DOMString &val) + throw (dom::DOMException) + { + position = val; + } + + /** + * return the 'quotes' property + */ + virtual DOMString getQuotes() + { + return quotes; + } + + /** + * set the 'quotes' property + */ + virtual void setQuotes(const DOMString &val) + throw (dom::DOMException) + { + quotes = val; + } + + /** + * return the 'richness' property + */ + virtual DOMString getRichness() + { + return richness; + } + + /** + * set the 'richness' property + */ + virtual void setRichness(const DOMString &val) + throw (dom::DOMException) + { + richness = val; + } + + /** + * return the 'right' property + */ + virtual DOMString getRight() + { + return right; + } + + /** + * set the 'right' property + */ + virtual void setRight(const DOMString &val) + throw (dom::DOMException) + { + right = val; + } + + /** + * return the 'size' property + */ + virtual DOMString getSize() + { + return size; + } + + /** + * set the 'size' property + */ + virtual void setSize(const DOMString &val) + throw (dom::DOMException) + { + size = val; + } + + /** + * return the 'speak' property + */ + virtual DOMString getSpeak() + { + return speak; + } + + /** + * set the 'speak' property + */ + virtual void setSpeak(const DOMString &val) + throw (dom::DOMException) + { + speak = val; + } + + /** + * return the 'speakHeader' property + */ + virtual DOMString getSpeakHeader() + { + return speakHeader; + } + + /** + * set the 'speakHeader' property + */ + virtual void setSpeakHeader(const DOMString &val) + throw (dom::DOMException) + { + speakHeader = val; + } + + /** + * return the 'speakNumeral' property + */ + virtual DOMString getSpeakNumeral() + { + return speakNumeral; + } + + /** + * set the 'speakNumeral' property + */ + virtual void setSpeakNumeral(const DOMString &val) + throw (dom::DOMException) + { + speakNumeral = val; + } + + /** + * return the 'speakPunctuation' property + */ + virtual DOMString getSpeakPunctuation() + { + return speakPunctuation; + } + + /** + * set the 'speakPunctuation' property + */ + virtual void setSpeakPunctuation(const DOMString &val) + throw (dom::DOMException) + { + speakPunctuation = val; + } + + /** + * return the 'speechRate' property + */ + virtual DOMString getSpeechRate() + { + return speechRate; + } + + /** + * set the 'speechRate' property + */ + virtual void setSpeechRate(const DOMString &val) + throw (dom::DOMException) + { + speechRate = val; + } + + /** + * return the 'stress' property + */ + virtual DOMString getStress() + { + return stress; + } + + /** + * set the 'stress' property + */ + virtual void setStress(const DOMString &val) + throw (dom::DOMException) + { + stress = val; + } + + /** + * return the 'tableLayout' property + */ + virtual DOMString getTableLayout() + { + return tableLayout; + } + + /** + * set the 'tableLayout' property + */ + virtual void setTableLayout(const DOMString &val) + throw (dom::DOMException) + { + tableLayout = val; + } + + /** + * return the 'textAlign' property + */ + virtual DOMString getTextAlign() + { + return textAlign; + } + + /** + * set the 'textAlign' property + */ + virtual void setTextAlign(const DOMString &val) + throw (dom::DOMException) + { + textAlign = val; + } + + /** + * return the 'textDecoration' property + */ + virtual DOMString getTextDecoration() + { + return textDecoration; + } + + /** + * set the 'textDecoration' property + */ + virtual void setTextDecoration(const DOMString &val) + throw (dom::DOMException) + { + textDecoration = val; + } + + /** + * return the 'textIndent' property + */ + virtual DOMString getTextIndent() + { + return textIndent; + } + + /** + * set the 'textIndent' property + */ + virtual void setTextIndent(const DOMString &val) + throw (dom::DOMException) + { + textIndent = val; + } + + /** + * return the 'textShadow' property + */ + virtual DOMString getTextShadow() + { + return textShadow; + } + + /** + * set the 'textShadow' property + */ + virtual void setTextShadow(const DOMString &val) + throw (dom::DOMException) + { + textShadow = val; + } + + /** + * return the 'textTransform' property + */ + virtual DOMString getTextTransform() + { + return textTransform; + } + + /** + * set the 'textTransform' property + */ + virtual void setTextTransform(const DOMString &val) + throw (dom::DOMException) + { + textTransform = val; + } + + /** + * return the 'top' property + */ + virtual DOMString getTop() + { + return top; + } + + /** + * set the 'top' property + */ + virtual void setTop(const DOMString &val) + throw (dom::DOMException) + { + top = val; + } + + /** + * return the 'unicodeBidi' property + */ + virtual DOMString getUnicodeBidi() + { + return unicodeBidi; + } + + /** + * set the 'unicodeBidi' property + */ + virtual void setUnicodeBidi(const DOMString &val) + throw (dom::DOMException) + { + unicodeBidi = val; + } + + /** + * return the 'verticalAlign' property + */ + virtual DOMString getVerticalAlign() + { + return verticalAlign; + } + + /** + * set the 'verticalAlign' property + */ + virtual void setVerticalAlign(const DOMString &val) + throw (dom::DOMException) + { + verticalAlign = val; + } + + /** + * return the 'visibility' property + */ + virtual DOMString getVisibility() + { + return visibility; + } + + /** + * set the 'visibility' property + */ + virtual void setVisibility(const DOMString &val) + throw (dom::DOMException) + { + visibility = val; + } + + /** + * return the 'voiceFamily' property + */ + virtual DOMString getVoiceFamily() + { + return voiceFamily; + } + + /** + * set the 'voiceFamily' property + */ + virtual void setVoiceFamily(const DOMString &val) + throw (dom::DOMException) + { + voiceFamily = val; + } + + /** + * return the 'volume' property + */ + virtual DOMString getVolume() + { + return volume; + } + + /** + * set the 'volume' property + */ + virtual void setVolume(const DOMString &val) + throw (dom::DOMException) + { + volume = val; + } + + /** + * return the 'whiteSpace' property + */ + virtual DOMString getWhiteSpace() + { + return whiteSpace; + } + + /** + * set the 'whiteSpace' property + */ + virtual void setWhiteSpace(const DOMString &val) + throw (dom::DOMException) + { + whiteSpace = val; + } + + /** + * return the 'widows' property + */ + virtual DOMString getWidows() + { + return widows; + } + + /** + * set the 'widows' property + */ + virtual void setWidows(const DOMString &val) + throw (dom::DOMException) + { + widows = val; + } + + /** + * return the 'width' property + */ + virtual DOMString getWidth() + { + return width; + } + + /** + * set the 'width' property + */ + virtual void setWidth(const DOMString &val) + throw (dom::DOMException) + { + width = val; + } + + /** + * return the 'wordSpacing' property + */ + virtual DOMString getWordSpacing() + { + return wordSpacing; + } + + /** + * set the 'wordSpacing' property + */ + virtual void setWordSpacing(const DOMString &val) + throw (dom::DOMException) + { + wordSpacing = val; + } + + /** + * return the 'zIndex' property + */ + virtual DOMString getZIndex() + { + return zIndex; + } + + /** + * set the 'zIndex' property + */ + virtual void setZIndex(const DOMString &val) + throw (dom::DOMException) + { + zIndex = val; + } + + + + //################## + //# Non-API methods + //################## + + /** + * + */ + CSS2Properties() + { + } + + /** + * + */ + CSS2Properties(const CSS2Properties &other) + { + azimuth = other.azimuth; + background = other.background; + backgroundAttachment = other.backgroundAttachment; + backgroundColor = other.backgroundColor; + backgroundImage = other.backgroundImage; + backgroundPosition = other.backgroundPosition; + backgroundRepeat = other.backgroundRepeat; + border = other.border; + borderCollapse = other.borderCollapse; + borderColor = other.borderColor; + borderSpacing = other.borderSpacing; + borderStyle = other.borderStyle; + borderTop = other.borderTop; + borderRight = other.borderRight; + borderBottom = other.borderBottom; + borderLeft = other.borderLeft; + borderTopColor = other.borderTopColor; + borderRightColor = other.borderRightColor; + borderBottomColor = other.borderBottomColor; + borderLeftColor = other.borderLeftColor; + borderTopStyle = other.borderTopStyle; + borderRightStyle = other.borderRightStyle; + borderBottomStyle = other.borderBottomStyle; + borderLeftStyle = other.borderLeftStyle; + borderTopWidth = other.borderTopWidth; + borderRightWidth = other.borderRightWidth; + borderBottomWidth = other.borderBottomWidth; + borderLeftWidth = other.borderLeftWidth; + borderWidth = other.borderWidth; + bottom = other.bottom; + captionSide = other.captionSide; + clear = other.clear; + clip = other.clip; + color = other.color; + content = other.content; + counterIncrement = other.counterIncrement; + counterReset = other.counterReset; + cue = other.cue; + cueAfter = other.cueAfter; + cueBefore = other.cueBefore; + cursor = other.cursor; + direction = other.direction; + display = other.display; + elevation = other.elevation; + emptyCells = other.emptyCells; + cssFloat = other.cssFloat; + font = other.font; + fontFamily = other.fontFamily; + fontSize = other.fontSize; + fontSizeAdjust = other.fontSizeAdjust; + fontStretch = other.fontStretch; + fontStyle = other.fontStyle; + fontVariant = other.fontVariant; + fontWeight = other.fontWeight; + height = other.height; + left = other.left; + letterSpacing = other.letterSpacing; + lineHeight = other.lineHeight; + listStyle = other.listStyle; + listStyleImage = other.listStyleImage; + listStylePosition = other.listStylePosition; + listStyleType = other.listStyleType; + margin = other.margin; + marginTop = other.marginTop; + marginRight = other.marginRight; + marginBottom = other.marginBottom; + marginLeft = other.marginLeft; + markerOffset = other.markerOffset; + marks = other.marks; + maxHeight = other.maxHeight; + maxWidth = other.maxWidth; + minHeight = other.minHeight; + minWidth = other.minWidth; + orphans = other.orphans; + outline = other.outline; + outlineColor = other.outlineColor; + outlineStyle = other.outlineStyle; + outlineWidth = other.outlineWidth; + overflow = other.overflow; + padding = other.padding; + paddingTop = other.paddingTop; + paddingRight = other.paddingRight; + paddingBottom = other.paddingBottom; + paddingLeft = other.paddingLeft; + page = other.page; + pageBreakAfter = other.pageBreakAfter; + pageBreakBefore = other.pageBreakBefore; + pageBreakInside = other.pageBreakInside; + pause = other.pause; + pauseAfter = other.pauseAfter; + pauseBefore = other.pauseBefore; + pitch = other.pitch; + pitchRange = other.pitchRange; + playDuring = other.playDuring; + position = other.position; + quotes = other.quotes; + richness = other.richness; + right = other.right; + size = other.size; + speak = other.speak; + speakHeader = other.speakHeader; + speakNumeral = other.speakNumeral; + speakPunctuation = other.speakPunctuation; + speechRate = other.speechRate; + stress = other.stress; + tableLayout = other.tableLayout; + textAlign = other.textAlign; + textDecoration = other.textDecoration; + textIndent = other.textIndent; + textShadow = other.textShadow; + textTransform = other.textTransform; + top = other.top; + unicodeBidi = other.unicodeBidi; + verticalAlign = other.verticalAlign; + visibility = other.visibility; + voiceFamily = other.voiceFamily; + volume = other.volume; + whiteSpace = other.whiteSpace; + widows = other.widows; + width = other.width; + wordSpacing = other.wordSpacing; + zIndex = other.zIndex; + } + + /** + * + */ + virtual ~CSS2Properties() {} + +protected: + + //###################### + //# P R O P E R T I E S + //###################### + DOMString azimuth; + DOMString background; + DOMString backgroundAttachment; + DOMString backgroundColor; + DOMString backgroundImage; + DOMString backgroundPosition; + DOMString backgroundRepeat; + DOMString border; + DOMString borderCollapse; + DOMString borderColor; + DOMString borderSpacing; + DOMString borderStyle; + DOMString borderTop; + DOMString borderRight; + DOMString borderBottom; + DOMString borderLeft; + DOMString borderTopColor; + DOMString borderRightColor; + DOMString borderBottomColor; + DOMString borderLeftColor; + DOMString borderTopStyle; + DOMString borderRightStyle; + DOMString borderBottomStyle; + DOMString borderLeftStyle; + DOMString borderTopWidth; + DOMString borderRightWidth; + DOMString borderBottomWidth; + DOMString borderLeftWidth; + DOMString borderWidth; + DOMString bottom; + DOMString captionSide; + DOMString clear; + DOMString clip; + DOMString color; + DOMString content; + DOMString counterIncrement; + DOMString counterReset; + DOMString cue; + DOMString cueAfter; + DOMString cueBefore; + DOMString cursor; + DOMString direction; + DOMString display; + DOMString elevation; + DOMString emptyCells; + DOMString cssFloat; + DOMString font; + DOMString fontFamily; + DOMString fontSize; + DOMString fontSizeAdjust; + DOMString fontStretch; + DOMString fontStyle; + DOMString fontVariant; + DOMString fontWeight; + DOMString height; + DOMString left; + DOMString letterSpacing; + DOMString lineHeight; + DOMString listStyle; + DOMString listStyleImage; + DOMString listStylePosition; + DOMString listStyleType; + DOMString margin; + DOMString marginTop; + DOMString marginRight; + DOMString marginBottom; + DOMString marginLeft; + DOMString markerOffset; + DOMString marks; + DOMString maxHeight; + DOMString maxWidth; + DOMString minHeight; + DOMString minWidth; + DOMString orphans; + DOMString outline; + DOMString outlineColor; + DOMString outlineStyle; + DOMString outlineWidth; + DOMString overflow; + DOMString padding; + DOMString paddingTop; + DOMString paddingRight; + DOMString paddingBottom; + DOMString paddingLeft; + DOMString page; + DOMString pageBreakAfter; + DOMString pageBreakBefore; + DOMString pageBreakInside; + DOMString pause; + DOMString pauseAfter; + DOMString pauseBefore; + DOMString pitch; + DOMString pitchRange; + DOMString playDuring; + DOMString position; + DOMString quotes; + DOMString richness; + DOMString right; + DOMString size; + DOMString speak; + DOMString speakHeader; + DOMString speakNumeral; + DOMString speakPunctuation; + DOMString speechRate; + DOMString stress; + DOMString tableLayout; + DOMString textAlign; + DOMString textDecoration; + DOMString textIndent; + DOMString textShadow; + DOMString textTransform; + DOMString top; + DOMString unicodeBidi; + DOMString verticalAlign; + DOMString visibility; + DOMString voiceFamily; + DOMString volume; + DOMString whiteSpace; + DOMString widows; + DOMString width; + DOMString wordSpacing; + DOMString zIndex; + + +}; + + + + + + + + +/*######################################################################### +## ViewCSS +#########################################################################*/ + +/** + * again, a mismatch with views::View and views::AbstractView + */ +class ViewCSS : virtual public views::View +{ +public: + + /** + * + */ + virtual CSSStyleDeclaration getComputedStyle(const Element &elt, + const DOMString &pseudoElt) + { + CSSStyleDeclaration style; + return style; + } + + //################## + //# Non-API methods + //################## + + /** + * + */ + ViewCSS() : views::View() + { + } + + /** + * + */ + ViewCSS(const ViewCSS &other) : views::View(other) + { + } + + /** + * + */ + virtual ~ViewCSS() {} +}; + + + + + +/*######################################################################### +## DocumentCSS +#########################################################################*/ + +/** + * + */ +class DocumentCSS : virtual public stylesheets::DocumentStyle +{ +public: + + /** + * + */ + virtual CSSStyleDeclaration getOverrideStyle(const Element *elt, + const DOMString &pseudoElt) + { + CSSStyleDeclaration style; + return style; + } + + //################## + //# Non-API methods + //################## + + /** + * + */ + DocumentCSS() : stylesheets::DocumentStyle() + { + } + + /** + * + */ + DocumentCSS(const DocumentCSS &other) : stylesheets::DocumentStyle(other) + { + } + + /** + * + */ + virtual ~DocumentCSS() {} +}; + + + + + + +/*######################################################################### +## DOMImplementationCSS +#########################################################################*/ + +/** + * + */ +class DOMImplementationCSS : virtual public DOMImplementation +{ +public: + + /** + * + */ + virtual CSSStyleSheet createCSSStyleSheet(const DOMString &title, + const DOMString &media) + throw (dom::DOMException) + { + CSSStyleSheet sheet; + return sheet; + } + + //################## + //# Non-API methods + //################## + + /** + * + */ + DOMImplementationCSS() {} + + /** + * + */ + DOMImplementationCSS(const DOMImplementationCSS &other) + : DOMImplementation(other) + { + } + + /** + * + */ + virtual ~DOMImplementationCSS() {} +}; + + + + + + + + +} //namespace css +} //namespace dom +} //namespace org +} //namespace w3c + + +#endif // __CSS_H__ + +/*######################################################################### +## E N D O F F I L E +#########################################################################*/ diff --git a/src/dom/css.idl b/src/dom/css.idl new file mode 100755 index 000000000..5033f901c --- /dev/null +++ b/src/dom/css.idl @@ -0,0 +1,633 @@ +/* + * Copyright (c) 2000 World Wide Web Consortium, + * (Massachusetts Institute of Technology, Institut National de + * Recherche en Informatique et en Automatique, Keio University). All + * Rights Reserved. This program is distributed under the W3C's Software + * Intellectual Property License. This program is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. + * See W3C License http://www.w3.org/Consortium/Legal/ for more details. + */ + +// File: http://www.w3.org/TR/2000/REC-DOM-Level-2-Style-20001113/css.idl + +#ifndef _CSS_IDL_ +#define _CSS_IDL_ + +#include "dom.idl" +#include "stylesheets.idl" +#include "views.idl" + +#pragma prefix "dom.w3c.org" +module css +{ + + typedef dom::DOMString DOMString; + typedef dom::Element Element; + typedef dom::DOMImplementation DOMImplementation; + + interface CSSRule; + interface CSSStyleSheet; + interface CSSStyleDeclaration; + interface CSSValue; + interface Counter; + interface Rect; + interface RGBColor; + + // Introduced in DOM Level 2: + interface CSSRuleList { + readonly attribute unsigned long length; + CSSRule item(in unsigned long index); + }; + + // Introduced in DOM Level 2: + interface CSSRule { + + // RuleType + const unsigned short UNKNOWN_RULE = 0; + const unsigned short STYLE_RULE = 1; + const unsigned short CHARSET_RULE = 2; + const unsigned short IMPORT_RULE = 3; + const unsigned short MEDIA_RULE = 4; + const unsigned short FONT_FACE_RULE = 5; + const unsigned short PAGE_RULE = 6; + + readonly attribute unsigned short type; + attribute DOMString cssText; + // raises(dom::DOMException) on setting + + readonly attribute CSSStyleSheet parentStyleSheet; + readonly attribute CSSRule parentRule; + }; + + // Introduced in DOM Level 2: + interface CSSStyleRule : CSSRule { + attribute DOMString selectorText; + // raises(dom::DOMException) on setting + + readonly attribute CSSStyleDeclaration style; + }; + + // Introduced in DOM Level 2: + interface CSSMediaRule : CSSRule { + readonly attribute stylesheets::MediaList media; + readonly attribute CSSRuleList cssRules; + unsigned long insertRule(in DOMString rule, + in unsigned long index) + raises(dom::DOMException); + void deleteRule(in unsigned long index) + raises(dom::DOMException); + }; + + // Introduced in DOM Level 2: + interface CSSFontFaceRule : CSSRule { + readonly attribute CSSStyleDeclaration style; + }; + + // Introduced in DOM Level 2: + interface CSSPageRule : CSSRule { + attribute DOMString selectorText; + // raises(dom::DOMException) on setting + + readonly attribute CSSStyleDeclaration style; + }; + + // Introduced in DOM Level 2: + interface CSSImportRule : CSSRule { + readonly attribute DOMString href; + readonly attribute stylesheets::MediaList media; + readonly attribute CSSStyleSheet styleSheet; + }; + + // Introduced in DOM Level 2: + interface CSSCharsetRule : CSSRule { + attribute DOMString encoding; + // raises(dom::DOMException) on setting + + }; + + // Introduced in DOM Level 2: + interface CSSUnknownRule : CSSRule { + }; + + // Introduced in DOM Level 2: + interface CSSStyleDeclaration { + attribute DOMString cssText; + // raises(dom::DOMException) on setting + + DOMString getPropertyValue(in DOMString propertyName); + CSSValue getPropertyCSSValue(in DOMString propertyName); + DOMString removeProperty(in DOMString propertyName) + raises(dom::DOMException); + DOMString getPropertyPriority(in DOMString propertyName); + void setProperty(in DOMString propertyName, + in DOMString value, + in DOMString priority) + raises(dom::DOMException); + readonly attribute unsigned long length; + DOMString item(in unsigned long index); + readonly attribute CSSRule parentRule; + }; + + // Introduced in DOM Level 2: + interface CSSValue { + + // UnitTypes + const unsigned short CSS_INHERIT = 0; + const unsigned short CSS_PRIMITIVE_VALUE = 1; + const unsigned short CSS_VALUE_LIST = 2; + const unsigned short CSS_CUSTOM = 3; + + attribute DOMString cssText; + // raises(dom::DOMException) on setting + + readonly attribute unsigned short cssValueType; + }; + + // Introduced in DOM Level 2: + interface CSSPrimitiveValue : CSSValue { + + // UnitTypes + const unsigned short CSS_UNKNOWN = 0; + const unsigned short CSS_NUMBER = 1; + const unsigned short CSS_PERCENTAGE = 2; + const unsigned short CSS_EMS = 3; + const unsigned short CSS_EXS = 4; + const unsigned short CSS_PX = 5; + const unsigned short CSS_CM = 6; + const unsigned short CSS_MM = 7; + const unsigned short CSS_IN = 8; + const unsigned short CSS_PT = 9; + const unsigned short CSS_PC = 10; + const unsigned short CSS_DEG = 11; + const unsigned short CSS_RAD = 12; + const unsigned short CSS_GRAD = 13; + const unsigned short CSS_MS = 14; + const unsigned short CSS_S = 15; + const unsigned short CSS_HZ = 16; + const unsigned short CSS_KHZ = 17; + const unsigned short CSS_DIMENSION = 18; + const unsigned short CSS_STRING = 19; + const unsigned short CSS_URI = 20; + const unsigned short CSS_IDENT = 21; + const unsigned short CSS_ATTR = 22; + const unsigned short CSS_COUNTER = 23; + const unsigned short CSS_RECT = 24; + const unsigned short CSS_RGBCOLOR = 25; + + readonly attribute unsigned short primitiveType; + void setFloatValue(in unsigned short unitType, + in float floatValue) + raises(dom::DOMException); + float getFloatValue(in unsigned short unitType) + raises(dom::DOMException); + void setStringValue(in unsigned short stringType, + in DOMString stringValue) + raises(dom::DOMException); + DOMString getStringValue() + raises(dom::DOMException); + Counter getCounterValue() + raises(dom::DOMException); + Rect getRectValue() + raises(dom::DOMException); + RGBColor getRGBColorValue() + raises(dom::DOMException); + }; + + // Introduced in DOM Level 2: + interface CSSValueList : CSSValue { + readonly attribute unsigned long length; + CSSValue item(in unsigned long index); + }; + + // Introduced in DOM Level 2: + interface RGBColor { + readonly attribute CSSPrimitiveValue red; + readonly attribute CSSPrimitiveValue green; + readonly attribute CSSPrimitiveValue blue; + }; + + // Introduced in DOM Level 2: + interface Rect { + readonly attribute CSSPrimitiveValue top; + readonly attribute CSSPrimitiveValue right; + readonly attribute CSSPrimitiveValue bottom; + readonly attribute CSSPrimitiveValue left; + }; + + // Introduced in DOM Level 2: + interface Counter { + readonly attribute DOMString identifier; + readonly attribute DOMString listStyle; + readonly attribute DOMString separator; + }; + + // Introduced in DOM Level 2: + interface ElementCSSInlineStyle { + readonly attribute CSSStyleDeclaration style; + }; + + // Introduced in DOM Level 2: + interface CSS2Properties { + attribute DOMString azimuth; + // raises(dom::DOMException) on setting + + attribute DOMString background; + // raises(dom::DOMException) on setting + + attribute DOMString backgroundAttachment; + // raises(dom::DOMException) on setting + + attribute DOMString backgroundColor; + // raises(dom::DOMException) on setting + + attribute DOMString backgroundImage; + // raises(dom::DOMException) on setting + + attribute DOMString backgroundPosition; + // raises(dom::DOMException) on setting + + attribute DOMString backgroundRepeat; + // raises(dom::DOMException) on setting + + attribute DOMString border; + // raises(dom::DOMException) on setting + + attribute DOMString borderCollapse; + // raises(dom::DOMException) on setting + + attribute DOMString borderColor; + // raises(dom::DOMException) on setting + + attribute DOMString borderSpacing; + // raises(dom::DOMException) on setting + + attribute DOMString borderStyle; + // raises(dom::DOMException) on setting + + attribute DOMString borderTop; + // raises(dom::DOMException) on setting + + attribute DOMString borderRight; + // raises(dom::DOMException) on setting + + attribute DOMString borderBottom; + // raises(dom::DOMException) on setting + + attribute DOMString borderLeft; + // raises(dom::DOMException) on setting + + attribute DOMString borderTopColor; + // raises(dom::DOMException) on setting + + attribute DOMString borderRightColor; + // raises(dom::DOMException) on setting + + attribute DOMString borderBottomColor; + // raises(dom::DOMException) on setting + + attribute DOMString borderLeftColor; + // raises(dom::DOMException) on setting + + attribute DOMString borderTopStyle; + // raises(dom::DOMException) on setting + + attribute DOMString borderRightStyle; + // raises(dom::DOMException) on setting + + attribute DOMString borderBottomStyle; + // raises(dom::DOMException) on setting + + attribute DOMString borderLeftStyle; + // raises(dom::DOMException) on setting + + attribute DOMString borderTopWidth; + // raises(dom::DOMException) on setting + + attribute DOMString borderRightWidth; + // raises(dom::DOMException) on setting + + attribute DOMString borderBottomWidth; + // raises(dom::DOMException) on setting + + attribute DOMString borderLeftWidth; + // raises(dom::DOMException) on setting + + attribute DOMString borderWidth; + // raises(dom::DOMException) on setting + + attribute DOMString bottom; + // raises(dom::DOMException) on setting + + attribute DOMString captionSide; + // raises(dom::DOMException) on setting + + attribute DOMString clear; + // raises(dom::DOMException) on setting + + attribute DOMString clip; + // raises(dom::DOMException) on setting + + attribute DOMString color; + // raises(dom::DOMException) on setting + + attribute DOMString content; + // raises(dom::DOMException) on setting + + attribute DOMString counterIncrement; + // raises(dom::DOMException) on setting + + attribute DOMString counterReset; + // raises(dom::DOMException) on setting + + attribute DOMString cue; + // raises(dom::DOMException) on setting + + attribute DOMString cueAfter; + // raises(dom::DOMException) on setting + + attribute DOMString cueBefore; + // raises(dom::DOMException) on setting + + attribute DOMString cursor; + // raises(dom::DOMException) on setting + + attribute DOMString direction; + // raises(dom::DOMException) on setting + + attribute DOMString display; + // raises(dom::DOMException) on setting + + attribute DOMString elevation; + // raises(dom::DOMException) on setting + + attribute DOMString emptyCells; + // raises(dom::DOMException) on setting + + attribute DOMString cssFloat; + // raises(dom::DOMException) on setting + + attribute DOMString font; + // raises(dom::DOMException) on setting + + attribute DOMString fontFamily; + // raises(dom::DOMException) on setting + + attribute DOMString fontSize; + // raises(dom::DOMException) on setting + + attribute DOMString fontSizeAdjust; + // raises(dom::DOMException) on setting + + attribute DOMString fontStretch; + // raises(dom::DOMException) on setting + + attribute DOMString fontStyle; + // raises(dom::DOMException) on setting + + attribute DOMString fontVariant; + // raises(dom::DOMException) on setting + + attribute DOMString fontWeight; + // raises(dom::DOMException) on setting + + attribute DOMString height; + // raises(dom::DOMException) on setting + + attribute DOMString left; + // raises(dom::DOMException) on setting + + attribute DOMString letterSpacing; + // raises(dom::DOMException) on setting + + attribute DOMString lineHeight; + // raises(dom::DOMException) on setting + + attribute DOMString listStyle; + // raises(dom::DOMException) on setting + + attribute DOMString listStyleImage; + // raises(dom::DOMException) on setting + + attribute DOMString listStylePosition; + // raises(dom::DOMException) on setting + + attribute DOMString listStyleType; + // raises(dom::DOMException) on setting + + attribute DOMString margin; + // raises(dom::DOMException) on setting + + attribute DOMString marginTop; + // raises(dom::DOMException) on setting + + attribute DOMString marginRight; + // raises(dom::DOMException) on setting + + attribute DOMString marginBottom; + // raises(dom::DOMException) on setting + + attribute DOMString marginLeft; + // raises(dom::DOMException) on setting + + attribute DOMString markerOffset; + // raises(dom::DOMException) on setting + + attribute DOMString marks; + // raises(dom::DOMException) on setting + + attribute DOMString maxHeight; + // raises(dom::DOMException) on setting + + attribute DOMString maxWidth; + // raises(dom::DOMException) on setting + + attribute DOMString minHeight; + // raises(dom::DOMException) on setting + + attribute DOMString minWidth; + // raises(dom::DOMException) on setting + + attribute DOMString orphans; + // raises(dom::DOMException) on setting + + attribute DOMString outline; + // raises(dom::DOMException) on setting + + attribute DOMString outlineColor; + // raises(dom::DOMException) on setting + + attribute DOMString outlineStyle; + // raises(dom::DOMException) on setting + + attribute DOMString outlineWidth; + // raises(dom::DOMException) on setting + + attribute DOMString overflow; + // raises(dom::DOMException) on setting + + attribute DOMString padding; + // raises(dom::DOMException) on setting + + attribute DOMString paddingTop; + // raises(dom::DOMException) on setting + + attribute DOMString paddingRight; + // raises(dom::DOMException) on setting + + attribute DOMString paddingBottom; + // raises(dom::DOMException) on setting + + attribute DOMString paddingLeft; + // raises(dom::DOMException) on setting + + attribute DOMString page; + // raises(dom::DOMException) on setting + + attribute DOMString pageBreakAfter; + // raises(dom::DOMException) on setting + + attribute DOMString pageBreakBefore; + // raises(dom::DOMException) on setting + + attribute DOMString pageBreakInside; + // raises(dom::DOMException) on setting + + attribute DOMString pause; + // raises(dom::DOMException) on setting + + attribute DOMString pauseAfter; + // raises(dom::DOMException) on setting + + attribute DOMString pauseBefore; + // raises(dom::DOMException) on setting + + attribute DOMString pitch; + // raises(dom::DOMException) on setting + + attribute DOMString pitchRange; + // raises(dom::DOMException) on setting + + attribute DOMString playDuring; + // raises(dom::DOMException) on setting + + attribute DOMString position; + // raises(dom::DOMException) on setting + + attribute DOMString quotes; + // raises(dom::DOMException) on setting + + attribute DOMString richness; + // raises(dom::DOMException) on setting + + attribute DOMString right; + // raises(dom::DOMException) on setting + + attribute DOMString size; + // raises(dom::DOMException) on setting + + attribute DOMString speak; + // raises(dom::DOMException) on setting + + attribute DOMString speakHeader; + // raises(dom::DOMException) on setting + + attribute DOMString speakNumeral; + // raises(dom::DOMException) on setting + + attribute DOMString speakPunctuation; + // raises(dom::DOMException) on setting + + attribute DOMString speechRate; + // raises(dom::DOMException) on setting + + attribute DOMString stress; + // raises(dom::DOMException) on setting + + attribute DOMString tableLayout; + // raises(dom::DOMException) on setting + + attribute DOMString textAlign; + // raises(dom::DOMException) on setting + + attribute DOMString textDecoration; + // raises(dom::DOMException) on setting + + attribute DOMString textIndent; + // raises(dom::DOMException) on setting + + attribute DOMString textShadow; + // raises(dom::DOMException) on setting + + attribute DOMString textTransform; + // raises(dom::DOMException) on setting + + attribute DOMString top; + // raises(dom::DOMException) on setting + + attribute DOMString unicodeBidi; + // raises(dom::DOMException) on setting + + attribute DOMString verticalAlign; + // raises(dom::DOMException) on setting + + attribute DOMString visibility; + // raises(dom::DOMException) on setting + + attribute DOMString voiceFamily; + // raises(dom::DOMException) on setting + + attribute DOMString volume; + // raises(dom::DOMException) on setting + + attribute DOMString whiteSpace; + // raises(dom::DOMException) on setting + + attribute DOMString widows; + // raises(dom::DOMException) on setting + + attribute DOMString width; + // raises(dom::DOMException) on setting + + attribute DOMString wordSpacing; + // raises(dom::DOMException) on setting + + attribute DOMString zIndex; + // raises(dom::DOMException) on setting + + }; + + // Introduced in DOM Level 2: + interface CSSStyleSheet : stylesheets::StyleSheet { + readonly attribute CSSRule ownerRule; + readonly attribute CSSRuleList cssRules; + unsigned long insertRule(in DOMString rule, + in unsigned long index) + raises(dom::DOMException); + void deleteRule(in unsigned long index) + raises(dom::DOMException); + }; + + // Introduced in DOM Level 2: + interface ViewCSS : views::AbstractView { + CSSStyleDeclaration getComputedStyle(in Element elt, + in DOMString pseudoElt); + }; + + // Introduced in DOM Level 2: + interface DocumentCSS : stylesheets::DocumentStyle { + CSSStyleDeclaration getOverrideStyle(in Element elt, + in DOMString pseudoElt); + }; + + // Introduced in DOM Level 2: + interface DOMImplementationCSS : DOMImplementation { + CSSStyleSheet createCSSStyleSheet(in DOMString title, + in DOMString media) + raises(dom::DOMException); + }; +}; + +#endif // _CSS_IDL_ + diff --git a/src/dom/cssparser.cpp b/src/dom/cssparser.cpp new file mode 100755 index 000000000..8f3b201ee --- /dev/null +++ b/src/dom/cssparser.cpp @@ -0,0 +1,1669 @@ +/** + * Phoebe DOM Implementation. + * + * This is a C++ approximation of the W3C DOM model, which follows + * fairly closely the specifications in the various .idl files, copies of + * which are provided for reference. Most important is this one: + * + * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/idl-definitions.html + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2005 Bob Jamison + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cssparser.h" +#include "charclass.h" + +#include + +namespace org +{ +namespace w3c +{ +namespace dom +{ +namespace css +{ + +//######################################################################### +//# M E S S A G E S +//######################################################################### + +/** + * Get the column and row number of the given character position + */ +void CssParser::getColumnAndRow(int p, int &colResult, int &rowResult, int &lastNL) +{ + int col = 1; + int row = 1; + int lastnl = 0; + + for (int i=0 ; i

= parselen) + return 0; + XMLCh ch = parsebuf[p]; + //printf("%c", ch); + lastPosition = p; + return ch; +} + + + +/** + * Test if the given substring exists at the given position + * in parsebuf. Use get() in case of out-of-bounds + */ +bool CssParser::match(int pos, char *str) +{ + while (*str) + { + if (get(pos++) != *str++) + return false; + } + return true; +} + +/** + * + */ +int CssParser::skipwhite(int p) +{ + while (p < parselen) + { + //# XML COMMENT + if (match(p, "")) + { + p+=3; + done=true; + break; + } + p++; + } + lastPosition = p; + if (!done) + { + error("unterminated comment"); + return -1; + } + } + //# C comment + else if (match(p, "/*")) + { + p+=2; + bool done=false; + while (p'9') + break; + str.push_back(ch); + p++; + } + if (get(p) == '.' && get(p+1)>='0' && get(p+1)<='9') + { + p++; + str.push_back('.'); + while (p < parselen) + { + XMLCh ch = get(p); + if (ch<'0' || ch>'9') + break; + str.push_back(ch); + p++; + } + } + if (p>p0) + { + char *start = (char *)str.c_str(); + char *end = NULL; + double val = strtod(start, &end); + if (end > start) + { + result = val; + return p; + } + } + + //not a number + return p0; +} + + + +/** + * Assume that we are starting on a quote. Ends on the char + * after the final '"' + */ +int CssParser::getQuoted(int p0, DOMString &result) +{ + + int p = p0; + + XMLCh quoteChar = get(p); + if (quoteChar != '"' && quoteChar != '\'') + return p0; + + p++; + + DOMString buf; + + bool done = false; + while (pp) + { + p = p2; + continue; + } + + //Media + p2 = getMedia(p); + if (p2<0) + { + return -1; + } + if (p2>p) + { + p = p2; + continue; + } + + //Page + p2 = getPage(p); + if (p2<0) + { + return -1; + } + if (p2>p) + { + p = p2; + continue; + } + + //none of the above + break; + } + + return p; +} + +/** + * import + * : IMPORT_SYM S* + * [STRING|URI] S* [ medium [ COMMA S* medium]* ]? ';' S* + * ; + */ +int CssParser::getImport(int p0) +{ + int p = p0; + if (!match(p, "@import")) + return p0; + p+=7; + p = skipwhite(p); + + //# STRING | URI + DOMString str; + int p2 = getQuoted(p, str); + if (p2<0) + { + return -1; + } + if (p2<=p) + { + p2 = getUri(p, str); + if (p2<0) + { + return -1; + } + if (p2<=p) + { + error("quoted string or URI required after @import"); + return -1; + } + } + p = p2; + p2 = getMedium(p); + if (p2<0) + return -1; + + p = p2; + p = skipwhite(p); + XMLCh ch = get(p); + if (ch != ';') + { + error("@import must be terminated with ';'"); + return -1; + } + p++; + return p; +} + +/** + * media + * : MEDIA_SYM S* medium [ COMMA S* medium ]* LBRACE S* ruleset* '}' S* + * ; + */ +int CssParser::getMedia(int p0) +{ + int p = p0; + XMLCh ch; + if (!match(p, "@media")) + return p0; + p+=6; + p = skipwhite(p); + + //# MEDIUM LIST + int p2 = getMedium(p); + if (p2<0) + return -1; + if (p2<=p) + { + error("@media must be followed by medium"); + return -1; + } + p = p2; + while (true) + { + ch = get(p); + if (ch != ',') + break; + p2 = getMedium(p); + if (p2<0) + return -1; + if (p2<=p) + { + error("',' in medium list must be followed by medium"); + return -1; + } + p = p2; + } + + p = skipwhite(p); + ch = get(p); + if (ch!='{') + { + error("@media requires '{' for ruleset"); + return -1; + } + p++; + p2 = getRuleSet(p); + if (p2<0) + return -1; + if (p2<=p) + { + error("@media requires ruleset after '{'"); + return -1; + } + p = p2; + ch = get(p); + if (ch != '}') + { + error("@media requires '}' after ruleset"); + return -1; + } + p++; + return p0; +} + +/** + * medium + * : IDENT S* + * ; + */ +int CssParser::getMedium(int p0) +{ + int p = p0; + p = skipwhite(p); + + DOMString ident; + int p2 = getWord(p, ident); + if (p2<0) + return -1; + if (p2<=p) + return p0; + p = p2; + + return p; +} + +/** + * page + * : PAGE_SYM S* pseudo_page? S* + * LBRACE S* declaration [ ';' S* declaration ]* '}' S* + * ; + */ +int CssParser::getPage(int p0) +{ + int p = p0; + + //# @PAGE + p = skipwhite(p); + if (!match(p, "@page")) + return p0; + p+= 5; + + //#PSEUDO PAGE 0 or 1 + p = skipwhite(p); + int p2 = getPseudoPage(p); + if (p2<0) + return -1; + if (p2>p) + { + p = p2; + } + + //# { + p=skipwhite(p); + XMLCh ch = get(p); + if (p != '{') + { + error("@page requires '{' before declarations"); + } + p++; + + //# DECLARATION LIST + p = skipwhite(p); + CSSStyleDeclaration declarationList; + p2 = getDeclaration(p, declarationList); + if (p2<0) + return -1; + if (p2<=p) + { + error("@page requires declaration(s) after '{'"); + return -1; + } + while (true) + { + p = skipwhite(p2); + ch = get(p); + if (ch != ';') + break; + p++; + p = skipwhite(p); + p2 = getDeclaration(p, declarationList); + if (p2<0) + return -1; + if (p2<= p) + { + error("@page requires declaration after ';'"); + return -1; + } + } + + //# } + p=skipwhite(p); + ch = get(p); + if (p != '}') + { + error("@page requires '}' after declarations"); + } + p++; + + return p; +} + +/** + * pseudo_page + * : ':' IDENT + * ; + */ +int CssParser::getPseudoPage(int p0) +{ + int p = p0; + if (!match(p, ":")) + return p0; + p++; + DOMString str; + int p2 = getWord(p, str); + if (p2<0) + return -1; + if (p2<=p) + { + error("pseudo-page requires identifier after ':'"); + return -1; + } + p = p2; + return p; +} + +/** + * ruleset + * : selector [ COMMA S* selector ]* + * LBRACE S* declaration [ ';' S* declaration ]* '}' S* + * ; + */ +int CssParser::getRuleSet(int p0) +{ + int p = p0; + XMLCh ch; + + //## SELECTOR + p = skipwhite(p); + int p2 = getSelector(p); + if (p2<0) + return -1; + if (p2<=p) //no selector + { + if (get(p) != '{')//check for selector-less rule + return p0;//not me + } + p = p2; + while (true) + { + p = skipwhite(p); + ch = get(p); + if (ch != ',') + break; + p++; + p = skipwhite(p); + int p2 = getSelector(p); + if (p2<0) + return -1; + if (p2<=p) + { + error("selector required after ',' in list"); + return -1; + } + p = p2; + } + + //## { + ch = get(p); + if (ch != '{') + { + error("'{' required before declarations of ruleset"); + return -1; + } + p++; + + //## DECLARATIONS ( 0 to many ) + CSSStyleDeclaration declarationList; + + p = skipwhite(p); + p2 = getDeclaration(p, declarationList); + if (p2<0) + return -1; + if (p2>p) + { + p = p2; + while (true) + { + p = skipwhite(p); + ch = get(p); + if (ch != ';') + break; + p++; + p = skipwhite(p); + p2 = getDeclaration(p, declarationList); + if (p2<0) + return -1; + if (p2<=p) + { + //apparently this is ok + //error("declaration required after ';' in ruleset"); + //return -1; + break; + } + p = p2; + } + } + //## } + ch = get(p); + if (ch != '}') + { + error("ruleset requires closing '}'"); + return -1; + } + p++; + p = skipwhite(p); + + return p; +} + +/** + * selector + * : simple_selector [ combinator simple_selector ]* + * ; + */ +int CssParser::getSelector(int p0) +{ + int p = p0; + + //## SIMPLE SELECTOR + p = skipwhite(p); + int p2 = getSimpleSelector(p); + if (p2<0) + return -1; + if (p2<=p) + return p0; //not me + p = p2; + + //## COMBINATORS + MORE SELECTORS + while (true) + { + XMLCh ch = get(p); + bool wasSpace = isspace(ch); + p = skipwhite(p); + ch = get(p); + //# Combinators + //easier to do here than have a getCombinator() + int visibleCombinator = false; + if (ch == '+') + { + visibleCombinator = true; + p++; + } + else if (ch == '>') + { + visibleCombinator = true; + p++; + } + else if (wasSpace) + { + } + else + { + break; + } + p = skipwhite(p); + p2 = getSimpleSelector(p); + if (p2<0) + return -1; + if (p2<=p) + { + if (visibleCombinator) + { + error("need simple selector after combinator"); + return -1; + } + else + { + break; + } + } + p = p2; + } + return p; +} + +/** + * simple_selector + * : element_name [ HASH | class | attrib | pseudo ]* + * | [ HASH | class | attrib | pseudo ]+ + * ; + */ +int CssParser::getSimpleSelector(int p0) +{ + int p = p0; + int p2; + + DOMString str; + + p = skipwhite(p); + + int selectorItems = 0; + + XMLCh ch = get(p); + + //###################### + //# Note: do NOT skipwhite between items. Only within the + //# pseudo function and attrib below + //###################### + + //#Element name 0 or 1 + if (isLetter(ch)) + { + p2 = getWord(p, str); + if (p2<0) + return -1; + if (p2<=p) + { + error("null element name"); + return -1; + } + selectorItems++; + p = p2; + } + else if (ch == '*') + { + str = "*"; + p++; + selectorItems++; + } + + + + //## HASH, CLASS, ATTRIB, PSEUDO (0 to many with elem name, 1 to many without) + while (true) + { + XMLCh ch = get(p); + + //# HASH + if (ch == '#') + { + p++; + p2 = getWord(p, str); + if (p2<0) + return -1; + if (p2<=p) + { + error("no name for hash"); + return -1; + } + p = p2; + selectorItems++; + } + + //# CLASS + else if (ch == '.') + { + p++; + p2 = getWord(p, str); + if (p2<0) + return -1; + if (p2<=p) + { + error("no name for class"); + return -1; + } + p = p2; + selectorItems++; + } + + //# ATTRIB + else if (ch == '[') + { + p++; + p = skipwhite(p); + p2 = getWord(p, str); + if (p2<0) + return -1; + if (p2<=p) + { + error("no name for class"); + return -1; + } + p = skipwhite(p2); + bool getRHS=false; + if (match(p, "=")) + { + p++; + getRHS=true; + } + else if (match(p, "~=")) + { + p+=2; + getRHS=true; + } + else if (match(p, "|=")) + { + p+=2; + getRHS=true; + } + if (getRHS) + { + p = skipwhite(p); + ch = get(p); + if (isLetter(ch)) + { + p2 = getWord(p, str); + if (p2<0) + return -1; + if (p2<=p) + { + error("null ident on rhs of attrib"); + return -1; + } + p = p2; + } + else if (ch == '\'' || ch =='"') + { + p2 = getQuoted(p, str); + if (p2<0) + return -1; + if (p2<=p) + { + error("null literal string on rhs of attrib"); + return -1; + } + p = p2; + } + }//getRHS + p = skipwhite(p); + ch = get(p); + if (ch != ']') + { + error("attrib needs closing ']'"); + //return -1; + p = skipBlock(p); + return p; + } + p++; + selectorItems++; + } + + //# PSEUDO + else if (ch == ':') + { + p++; + p2 = getWord(p, str); + if (p2<0) + return -1; + if (p2<=p) + { + error("no name for pseudo"); + return -1; + } + p = p2; + selectorItems++; + ch = get(p); + if (ch == '(') + { + p++; + p = skipwhite(p); + ch = get(p); + if (isLetter(ch)) + { + p2 = getWord(p, str); + if (p2<0) + return -1; + if (p2<=p) + { + error("null function parameter in pseudo"); + return -1; + } + p = skipwhite(p2); + ch = get(p); + } + if (ch != ')') + { + error("function in pseudo needs ')'"); + return -1; + } + p++; + }// ch==( -function- + }//pseudo + + //# none of the above + else + { + break; + } + + }//while + + + if (selectorItems > 0) + return p; + return p0; +} + +/** + * declaration + * : property ':' S* expr prio? + * | {empty} + * ; + */ +int CssParser::getDeclaration(int p0, CSSStyleDeclaration &declarationList) +{ + int p = p0; + + //## PROPERTY + p = skipwhite(p); + XMLCh ch = get(p); + if (!isLetter(ch)) + return p0; //not me + DOMString propName; + int p2 = getWord(p, propName); + if (p2<0) + return -1; + + //## ':' + p = skipwhite(p2); + ch = get(p); + if (ch != ':') + { + error("declaration requires ':' between name and value"); + return -1; + } + p++; + + //## EXPR + p = skipwhite(p); + p2 = getExpr(p); + if (p2<0) + return -1; + if (p2<=p) + { + error("declaration requires value after ':'"); + return -1; + } + DOMString propVal; + for (int i=p ; ip) + { + //do something + p = p2; + } + + return p; +} + +/** + * prio + * : IMPORTANT_SYM S* + * ; + */ +int CssParser::getPrio(int p0, DOMString &val) +{ + int p = p0; + + //## '!" + p = skipwhite(p); + XMLCh ch = get(p); + if (ch != '!') + return p0; + p++; + + //## "important" + p = skipwhite(p); + if (!match(p, "important")) + { + error("priority symbol is 'important'"); + return -1; + } + p += 9; + val = "important"; + return p; +} + +/** + * expr + * : term [ operator term ]* + * ; + */ +int CssParser::getExpr(int p0) +{ + int p = p0; + + //## TERM + p = skipwhite(p); + int p2 = getTerm(p); + if (p2<0) + return -1; + if (p2<=p) + return p0; //not me + p = p2; + while (p < parselen) + { + p = skipwhite(p); + //#Operator. do this instead of getOperator() + XMLCh ch = get(p); + int visibleTerm = false; + if (ch == '/') + { + visibleTerm = true; + p++; + } + else if (ch == ',') + { + visibleTerm = true; + p++; + } + else + { + //just space. this is allowable between terms, + // so we still need to check for another term + } + p = skipwhite(p); + p2 = getTerm(p); + if (p2<0) + return -1; + if (p2<=p) + { + if (visibleTerm) + { + error("expression requires term after operator"); + return -1; + } + else + { + break; + } + } + p = p2; + } + + return p; +} + +/** + * term + * : unary_operator? + * [ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* | ANGLE S* | + * TIME S* | FREQ S* | function ] + * | STRING S* | IDENT S* | URI S* | hexcolor + * ; + */ +int CssParser::getTerm(int p0) +{ + int p = p0; + p = skipwhite(p); + int unitType = CSSPrimitiveValue::CSS_UNKNOWN; + //# Unary operator + XMLCh ch = get(p); + bool hasUnary = false; + if (ch == '-') + { + p++; + hasUnary = true; + } + else if (ch == '+') + { + p++; + hasUnary = true; + } + //# NUMERIC + double numVal; + int p2 = getNumber(p, numVal); + if (p2<0) + return -1; + if (p2>p) + { + p = p2; + if (match(p, "%")) + { + unitType = CSSPrimitiveValue::CSS_PERCENTAGE; + p++; + } + else if (match(p, "em")) + { + unitType = CSSPrimitiveValue::CSS_EMS; + p+=2; + } + else if (match(p, "ex")) + { + unitType = CSSPrimitiveValue::CSS_EXS; + p+=2; + } + else if (match(p, "px")) + { + unitType = CSSPrimitiveValue::CSS_PX; + p+=2; + } + else if (match(p, "cm")) + { + unitType = CSSPrimitiveValue::CSS_CM; + p+=2; + } + else if (match(p, "mm")) + { + unitType = CSSPrimitiveValue::CSS_MM; + p+=2; + } + else if (match(p, "in")) + { + unitType = CSSPrimitiveValue::CSS_IN; + p+=2; + } + else if (match(p, "pt")) + { + unitType = CSSPrimitiveValue::CSS_PT; + p+=2; + } + else if (match(p, "pc")) + { + unitType = CSSPrimitiveValue::CSS_PC; + p+=2; + } + else if (match(p, "deg")) + { + unitType = CSSPrimitiveValue::CSS_DEG; + p+=3; + } + else if (match(p, "rad")) + { + unitType = CSSPrimitiveValue::CSS_RAD; + p+=3; + } + else if (match(p, "grad")) + { + unitType = CSSPrimitiveValue::CSS_GRAD; + p+=4; + } + else if (match(p, "ms")) + { + unitType = CSSPrimitiveValue::CSS_MS; + p+=2; + } + else if (match(p, "s")) + { + unitType = CSSPrimitiveValue::CSS_S; + p+=1; + } + else if (match(p, "Hz")) + { + unitType = CSSPrimitiveValue::CSS_HZ; + p+=2; + } + else if (match(p, "kHz")) + { + unitType = CSSPrimitiveValue::CSS_KHZ; + p+=2; + } + else if (isLetter(get(p)))//some other string + { + DOMString suffix; + p2 = getWord(p, suffix); + if (p2<0) + return -1; + unitType = CSSPrimitiveValue::CSS_DIMENSION; + p = p2; + } + else //plain number + { + unitType = CSSPrimitiveValue::CSS_NUMBER; + } + return p; + } + + DOMString str; + + //## URI --do before function, as syntax is similar + p2 = getUri(p, str); + if (p2<0) + return -1; + if (p2>p) + { + if (hasUnary) + { + error("+ or - not allowed on URI"); + return -1; + } + p = p2; + unitType = CSSPrimitiveValue::CSS_URI; + return p; + } + + //## FUNCTION + p2 = getFunction(p); + if (p2<0) + return -1; + if (p2>p) + { + p = p2; + return p; + } + + //## STRING + ch = get(p); + if (ch == '"' || ch == '\'') + { + p2 = getQuoted(p, str); + if (p2<0) + return -1; + if (p2>p) + { + if (hasUnary) + { + error("+ or - not allowed on a string"); + return -1; + } + p = p2; + unitType = CSSPrimitiveValue::CSS_STRING; + return p; + } + } + + //## IDENT + ch = get(p); + if (isLetter(ch)) + { + p2 = getWord(p, str); + if (p2<0) + return -1; + if (p2>p) + { + if (hasUnary) + { + error("+ or - not allowed on an identifier"); + return -1; + } + p = p2; + unitType = CSSPrimitiveValue::CSS_IDENT; + return p; + } + } + + + //## HEXCOLOR + p2 = getHexColor(p); + if (p2<0) + return -1; + if (p2>p) + { + if (hasUnary) + { + error("+ or - not allowed on hex color"); + return -1; + } + p = p2; + unitType = CSSPrimitiveValue::CSS_RGBCOLOR; + return p; + } + + + return p0; +} + +/** + * function + * : FUNCTION S* expr ')' S* + * ; + */ +int CssParser::getFunction(int p0) +{ + int p = p0; + + //## IDENT + ( (both) + DOMString name; + p = skipwhite(p); + int p2 = getWord(p, name); + if (p2<0) + return -1; + if (p2<=p) + return p0; //not me + if (name == "uri" || name=="url") + return p0; //not me + p = skipwhite(p2); + XMLCh ch = get(p); + if (ch != '(') + return p0; //still not me + p++; + + //## EXPR + p = skipwhite(p); + p2 = getExpr(p); + if (p2<0) + return -1; + if (p2<=p) + { + error("function requires expression"); + return -1; + } + p = p2; + + //## ')' + p = skipwhite(p); + ch = get(p); + if (ch != ')') + { + error("function requires closing ')'"); + return -1; + } + p++; + p = skipwhite(p); + + return p; +} + +/** + * There is a constraint on the color that it must + * have either 3 or 6 hex-digits (i.e., [0-9a-fA-F]) + * after the "#"; e.g., "#000" is OK, but "#abcd" is not. + * hexcolor + * : HASH S* + * ; + */ +int CssParser::getHexColor(int p0) +{ + int p = p0; + + //## '#' + p = skipwhite(p); + if (!match(p, "#")) + return p0; + p++; + + //## HEX + DOMString hex; + long hexVal = 0; + while (p < parselen) + { + XMLCh b = get(p); + if (b>='0' && b<='9') + { + hexVal = (hexVal << 4) + (b - '0'); + hex.push_back(b); + p++; + } + else if (b>='a' && b<='f') + { + hexVal = (hexVal << 4) + (b - 'a' + 10); + hex.push_back(b); + p++; + } + else if (b>='A' && b<='F') + { + hexVal = (hexVal << 4) + (b - 'A' + 10); + hex.push_back(b); + p++; + } + else + { + break; + } + } + + if (hex.size() != 3 && hex.size() != 6) + { + error("exactly 3 or 6 hex digits are required after '#'"); + return -1; + } + + return p; +} + + + +/** + * + */ +bool CssParser::parse(const DOMString &str) +{ + /* + int len = str.size(); + for (int i=0 ; i +#include + +#define OWN_STRING + +#ifdef OWN_STRING +#include "domstring.h" +#endif + +#define XMLNSNAME "http://www.w3.org/2000/xmlns/" + +namespace org +{ +namespace w3c +{ +namespace dom +{ + + + +#ifndef OWN_STRING +typedef unsigned short XMLCh; +typedef std::string DOMString; +#endif + +/** + * + */ +typedef unsigned long long DOMTimeStamp; + +/** + * + */ +typedef void DOMUserData; + +/** + * + */ +typedef void DOMObject; + + +class DOMException; +class DOMStringList; +class NameList; +class DOMImplementationList; +class DOMImplementationSource; +class DOMImplementation; +class Node; +class NodeList; +class NamedNodeMap; +class CharacterData; +class Attr; +class Element; +class Text; +class Comment; +class TypeInfo; +class UserDataHandler; +class DOMError; +class DOMErrorHandler; +class DOMLocator; +class DOMConfiguration; +class CDATASection; +class DocumentType; +class Notation; +class Entity; +class EntityReference; +class ProcessingInstruction; +class DocumentFragment; +class Document; + + +/** + * NOTE: We were originally intending to split ALL specifications into + * interface and implementation. After consideration, though, it behooves + * us to simplify things by implementing the base exception and + * container classes directly: + * + * DOMException + * DOMStringList + * NameList + * DOMImplementationList + * DOMImplementationSource + * DOMImplementation + * NodeList + * NamedNodeMap + */ + + +/*######################################################################### +## DOMException +#########################################################################*/ +/** + * This is the only non-interface class + */ +class DOMException +{ + +public: + + DOMException(const DOMString &reasonMsg) + { msg = reasonMsg; } + + DOMException(short theCode) + { + code = theCode; + } + + virtual ~DOMException() throw() + {} + + /** + * + */ + unsigned short code; + + /** + * + */ + DOMString msg; + + /** + * Get a string, translated from the code. + * Like std::exception. Not in spec. + */ + const char *what() + { return (const char *)msg.c_str(); } + + + +}; + + + + +/** + * ExceptionCode + */ +typedef enum +{ + INDEX_SIZE_ERR = 1, + DOMSTRING_SIZE_ERR = 2, + HIERARCHY_REQUEST_ERR = 3, + WRONG_DOCUMENT_ERR = 4, + INVALID_CHARACTER_ERR = 5, + NO_DATA_ALLOWED_ERR = 6, + NO_MODIFICATION_ALLOWED_ERR = 7, + NOT_FOUND_ERR = 8, + NOT_SUPPORTED_ERR = 9, + INUSE_ATTRIBUTE_ERR = 10, + INVALID_STATE_ERR = 11, + SYNTAX_ERR = 12, + INVALID_MODIFICATION_ERR = 13, + NAMESPACE_ERR = 14, + INVALID_ACCESS_ERR = 15, + VALIDATION_ERR = 16, + TYPE_MISMATCH_ERR = 17 +} ExceptionCode; + + +/*######################################################################### +## DOMStringList +#########################################################################*/ + +class DOMStringList +{ +public: + + /** + * + */ + virtual DOMString item(unsigned long index) + { + if (index>=strings.size()) + return ""; + return strings[index]; + } + + /** + * + */ + virtual unsigned long getLength() + { + return (unsigned long) strings.size(); + } + + /** + * + */ + virtual bool contains(const DOMString &str) + { + for (unsigned int i=0; istrings; + +}; + + + +/*######################################################################### +## NameList +#########################################################################*/ +class NamePair +{ +public: + NamePair(const DOMString &theNamespaceURI, const DOMString &theName) + { + namespaceURI = theNamespaceURI; + name = theName; + } + NamePair(const NamePair &other) + { + namespaceURI = other.namespaceURI; + name = other.name; + } + virtual ~NamePair() {} + + DOMString namespaceURI; + DOMString name; +}; + + + +class NameList +{ +public: + + /** + * + */ + virtual DOMString getName(unsigned long index) + { + if (index>=namePairs.size()) + return ""; + return namePairs[index].name; + } + + /** + * + */ + virtual DOMString getNamespaceURI(unsigned long index) + { + if (index>=namePairs.size()) + return ""; + return namePairs[index].namespaceURI; + } + + /** + * + */ + virtual unsigned long getLength() + { + return (unsigned long)namePairs.size(); + } + + /** + * + */ + virtual bool contains(const DOMString &name) + { + for (unsigned int i=0; i namePairs; + +}; + +/*######################################################################### +## DOMImplementationList +#########################################################################*/ + +class DOMImplementationList +{ +public: + + /** + * + */ + virtual DOMImplementation *getDOMImplementation(unsigned long index) + { + if (index >implementations.size()) + return NULL; + return implementations[index]; + } + + /** + * + */ + virtual unsigned long getLength() + { + return (unsigned long) implementations.size(); + } + + + + + //################## + //# Non-API methods + //################## + + /** + * + */ + DOMImplementationList() {} + + + /** + * + */ + DOMImplementationList(const DOMImplementationList &other) + { + implementations = other.implementations; + } + + /** + * + */ + virtual ~DOMImplementationList() {} + +protected: + + std::vectorimplementations; + +}; + + +/*######################################################################### +## DOMImplementationSource +#########################################################################*/ + +class DOMImplementationSource +{ +public: + + /** + * + */ + virtual DOMImplementation *getDOMImplementation(const DOMString &features) = 0; + + /** + * + */ + virtual DOMImplementationList getDOMImplementationList(const DOMString &features) = 0; + + //################## + //# Non-API methods + //################## + + /** + * + */ + virtual ~DOMImplementationSource() {} + +}; + + + + + +/*######################################################################### +## DOMImplementation +#########################################################################*/ +/** + * + */ +class DOMImplementation +{ +public: + /** + * + */ + virtual bool hasFeature(const DOMString& feature, const DOMString& version) = 0; + + + /** + * + */ + virtual DocumentType *createDocumentType(const DOMString& qualifiedName, + const DOMString& publicId, + const DOMString& systemId) + throw(DOMException) = 0; + + /** + * + */ + virtual Document *createDocument(const DOMString& namespaceURI, + const DOMString& qualifiedName, + DocumentType *doctype) + throw(DOMException) = 0; + /** + * + */ + virtual DOMObject *getFeature(const DOMString& feature, + const DOMString& version) = 0; + + + //################## + //# Non-API methods + //################## + + /** + * + */ + virtual ~DOMImplementation() {} + +}; + + + + +/*######################################################################### +## Node +#########################################################################*/ + +/** + * + */ +class Node +{ +public: + + typedef enum + { + ELEMENT_NODE = 1, + ATTRIBUTE_NODE = 2, + TEXT_NODE = 3, + CDATA_SECTION_NODE = 4, + ENTITY_REFERENCE_NODE = 5, + ENTITY_NODE = 6, + PROCESSING_INSTRUCTION_NODE = 7, + COMMENT_NODE = 8, + DOCUMENT_NODE = 9, + DOCUMENT_TYPE_NODE = 10, + DOCUMENT_FRAGMENT_NODE = 11, + NOTATION_NODE = 12 + } NodeType; + + /** + * + */ + virtual DOMString getNodeName() = 0; + + /** + * + */ + virtual DOMString getNodeValue() throw (DOMException) = 0; + + /** + * + */ + virtual void setNodeValue(const DOMString& val) throw (DOMException) = 0; + + /** + * + */ + virtual unsigned short getNodeType() = 0; + + /** + * + */ + virtual Node *getParentNode() = 0; + + /** + * + */ + virtual NodeList getChildNodes() = 0; + + /** + * + */ + virtual Node *getFirstChild() = 0; + + /** + * + */ + virtual Node *getLastChild() = 0; + + /** + * + */ + virtual Node *getPreviousSibling() = 0; + + /** + * + */ + virtual Node *getNextSibling() = 0; + + /** + * + */ + virtual NamedNodeMap &getAttributes() = 0; + + + /** + * + */ + virtual Document *getOwnerDocument() = 0; + + /** + * + */ + virtual Node *insertBefore(const Node *newChild, + const Node *refChild) + throw(DOMException) = 0; + + /** + * + */ + virtual Node *replaceChild(const Node *newChild, + const Node *oldChild) + throw(DOMException) = 0; + + /** + * + */ + virtual Node *removeChild(const Node *oldChild) + throw(DOMException) = 0; + + /** + * + */ + virtual Node *appendChild(const Node *newChild) + throw(DOMException) = 0; + + /** + * + */ + virtual bool hasChildNodes() = 0; + + /** + * + */ + virtual Node *cloneNode(bool deep) = 0; + + /** + * + */ + virtual void normalize() = 0; + + /** + * + */ + virtual bool isSupported(const DOMString& feature, + const DOMString& version) = 0; + + /** + * + */ + virtual DOMString getNamespaceURI() = 0; + + /** + * + */ + virtual DOMString getPrefix() = 0; + + /** + * + */ + virtual void setPrefix(const DOMString& val) throw(DOMException) = 0; + + /** + * + */ + virtual DOMString getLocalName() = 0; + + /** + * + */ + virtual bool hasAttributes() = 0; + + /** + * + */ + virtual DOMString getBaseURI() = 0; + + typedef enum + { + DOCUMENT_POSITION_DISCONNECTED = 0x01, + DOCUMENT_POSITION_PRECEDING = 0x02, + DOCUMENT_POSITION_FOLLOWING = 0x04, + DOCUMENT_POSITION_CONTAINS = 0x08, + DOCUMENT_POSITION_CONTAINED_BY = 0x10, + DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 0x20 + } DocumentPosition; + + + /** + * + */ + virtual unsigned short compareDocumentPosition(const Node *other) = 0; + + /** + * + */ + virtual DOMString getTextContext() throw(DOMException) = 0; + + + /** + * + */ + virtual void setTextContext(const DOMString &val) throw(DOMException) = 0; + + + /** + * + */ + virtual DOMString lookupPrefix(const DOMString &namespaceURI) =0; + + + /** + * + */ + virtual bool isDefaultNamespace(const DOMString &namespaceURI) =0; + + + /** + * + */ + virtual DOMString lookupNamespaceURI(const DOMString &prefix) =0; + + + /** + * + */ + virtual bool isEqualNode(const Node *node) =0; + + + + /** + * + */ + virtual DOMObject *getFeature(const DOMString &feature, + const DOMString &version) =0; + + /** + * + */ + virtual DOMUserData *setUserData(const DOMString &key, + const DOMUserData *data, + const UserDataHandler *handler) =0; + + + /** + * + */ + virtual DOMUserData *getUserData(const DOMString &namespaceURI) =0; + + //################## + //# Non-API methods + //################## + + + /** + * + */ + virtual ~Node() {} + + + + +}; + + + +/*######################################################################### +## NodeList +#########################################################################*/ + +/** + * + */ +class NodeList +{ +public: + /** + * + */ + virtual Node *item(unsigned long index) + { + if (index>=nodes.size()) + return NULL; + return nodes[index]; + } + + /** + * + */ + virtual unsigned long getLength() + { + return (unsigned long) nodes.size(); + } + + //################## + //# Non-API methods + //################## + + + /** + * + */ + NodeList() {} + + /** + * + */ + NodeList(const NodeList &other) + { + nodes = other.nodes; + } + + /** + * + */ + virtual ~NodeList() {} + +protected: + +friend class NodeImpl; +friend class ElementImpl; + + /* + * + */ + virtual void add(const Node *node) + { + nodes.push_back((Node *)node); + } + +protected: + + std::vector nodes; + +}; + + + + +/*######################################################################### +## NamedNodeMap +#########################################################################*/ + +class NamedNodeMapEntry +{ +public: + NamedNodeMapEntry(const DOMString &theNamespaceURI, + const DOMString &theName, + const Node *theNode) + { + namespaceURI = theNamespaceURI; + name = theName; + node = (Node *)theNode; + } + NamedNodeMapEntry(const NamedNodeMapEntry &other) + { + namespaceURI = other.namespaceURI; + name = other.name; + node = other.node; + } + virtual ~NamedNodeMapEntry() + { + } + DOMString namespaceURI; + DOMString name; + Node *node; +}; + +/** + * + */ +class NamedNodeMap +{ +public: + + /** + * + */ + virtual Node *getNamedItem(const DOMString& name) + { + std::vector::iterator iter; + for (iter = entries.begin() ; iter!=entries.end() ; iter++) + { + if (iter->name == name) + { + Node *node = iter->node; + return node; + } + } + return NULL; + } + + /** + * + */ + virtual Node *setNamedItem(Node *arg) throw(DOMException) + { + if (!arg) + return NULL; + DOMString namespaceURI = arg->getNamespaceURI(); + DOMString name = arg->getNodeName(); + std::vector::iterator iter; + for (iter = entries.begin() ; iter!=entries.end() ; iter++) + { + if (iter->name == name) + { + Node *node = iter->node; + iter->node = arg; + return node; + } + } + NamedNodeMapEntry entry(namespaceURI, name, arg); + entries.push_back(entry); + return arg; + } + + + /** + * + */ + virtual Node *removeNamedItem(const DOMString& name) throw(DOMException) + { + std::vector::iterator iter; + for (iter = entries.begin() ; iter!=entries.end() ; iter++) + { + if (iter->name == name) + { + Node *node = iter->node; + entries.erase(iter); + return node; + } + } + return NULL; + } + + /** + * + */ + virtual Node *item(unsigned long index) + { + if (index>=entries.size()) + return NULL; + return entries[index].node; + } + + /** + * + */ + virtual unsigned long getLength() + { + return (unsigned long)entries.size(); + } + + /** + * + */ + virtual Node *getNamedItemNS(const DOMString& namespaceURI, + const DOMString& localName) + { + std::vector::iterator iter; + for (iter = entries.begin() ; iter!=entries.end() ; iter++) + { + if (iter->namespaceURI == namespaceURI && iter->name == localName) + { + Node *node = iter->node; + return node; + } + } + return NULL; + } + + /** + * + */ + virtual Node *setNamedItemNS(Node *arg) throw(DOMException) + { + if (!arg) + return NULL; + DOMString namespaceURI = arg->getNamespaceURI(); + DOMString name = arg->getNodeName(); + std::vector::iterator iter; + for (iter = entries.begin() ; iter!=entries.end() ; iter++) + { + if (iter->namespaceURI == namespaceURI && iter->name == name) + { + Node *node = iter->node; + iter->node = arg; + return node; + } + } + NamedNodeMapEntry entry(namespaceURI, name, arg); + entries.push_back(entry); + return arg; + } + + /** + * + */ + virtual Node *removeNamedItemNS(const DOMString& namespaceURI, + const DOMString& localName) + throw(DOMException) + { + std::vector::iterator iter; + for (iter = entries.begin() ; iter!=entries.end() ; iter++) + { + if (iter->namespaceURI == namespaceURI && iter->name == localName) + { + Node *node = iter->node; + entries.erase(iter); + return node; + } + } + return NULL; + } + + //################## + //# Non-API methods + //################## + + /** + * + */ + NamedNodeMap() {} + + + /** + * + */ + NamedNodeMap(const NamedNodeMap &other) + { + entries = other.entries; + } + + + /** + * + */ + virtual ~NamedNodeMap() {} + +protected: + + std::vector entries; + +}; + + + + +/*######################################################################### +## CharacterData +#########################################################################*/ + +/** + * + */ +class CharacterData : virtual public Node +{ +public: + + /** + * + */ + virtual DOMString getData() throw(DOMException) = 0; + + /** + * + */ + virtual void setData(const DOMString& val) throw(DOMException) = 0; + + /** + * + */ + virtual unsigned long getLength() = 0; + + /** + * + */ + virtual DOMString substringData(unsigned long offset, + unsigned long count) + throw(DOMException) = 0; + + /** + * + */ + virtual void appendData(const DOMString& arg) throw(DOMException) = 0; + + /** + * + */ + virtual void insertData(unsigned long offset, + const DOMString& arg) + throw(DOMException) = 0; + + /** + * + */ + virtual void deleteData(unsigned long offset, + unsigned long count) + throw(DOMException) = 0; + + /** + * + */ + virtual void replaceData(unsigned long offset, + unsigned long count, + const DOMString& arg) + throw(DOMException) = 0; + + + //################## + //# Non-API methods + //################## + + + /** + * + */ + virtual ~CharacterData() {} + +}; + + + + + +/*######################################################################### +## Attr +#########################################################################*/ + +/** + * + */ +class Attr : virtual public Node +{ +public: + + /** + * + */ + virtual DOMString getName() = 0; + + /** + * + */ + virtual bool getSpecified() = 0; + + /** + * + */ + virtual DOMString getValue() = 0; + + /** + * + */ + virtual void setValue(const DOMString& val) throw(DOMException) = 0; + + /** + * + */ + virtual Element *getOwnerElement() = 0; + + + /** + * + */ + virtual TypeInfo *getSchemaTypeInfo() = 0; + + + /** + * + */ + virtual bool getIsId() = 0; + + //################## + //# Non-API methods + //################## + + + /** + * + */ + virtual ~Attr() {} + +}; + + + + + +/*######################################################################### +## Element +#########################################################################*/ + +/** + * + */ +class Element : virtual public Node +{ +public: + + + /** + * + */ + virtual DOMString getTagName() = 0; + + /** + * + */ + virtual DOMString getAttribute(const DOMString& name) = 0; + + /** + * + */ + virtual void setAttribute(const DOMString& name, + const DOMString& value) + throw(DOMException) = 0; + + /** + * + */ + virtual void removeAttribute(const DOMString& name) + throw(DOMException) = 0; + + /** + * + */ + virtual Attr *getAttributeNode(const DOMString& name) = 0; + + /** + * + */ + virtual Attr *setAttributeNode(Attr *newAttr) + throw(DOMException) = 0; + + /** + * + */ + virtual Attr *removeAttributeNode(Attr *oldAttr) + throw(DOMException) = 0; + + /** + * + */ + virtual NodeList getElementsByTagName(const DOMString& name) = 0; + + /** + * + */ + virtual DOMString getAttributeNS(const DOMString& namespaceURI, + const DOMString& localName) = 0; + + /** + * + */ + virtual void setAttributeNS(const DOMString& namespaceURI, + const DOMString& qualifiedName, + const DOMString& value) + throw(DOMException) = 0; + + /** + * + */ + virtual void removeAttributeNS(const DOMString& namespaceURI, + const DOMString& localName) + throw(DOMException) = 0; + + /** + * + */ + virtual Attr *getAttributeNodeNS(const DOMString& namespaceURI, + const DOMString& localName) = 0; + + /** + * + */ + virtual Attr *setAttributeNodeNS(Attr *newAttr) + throw(DOMException) = 0; + + /** + * + */ + virtual NodeList getElementsByTagNameNS(const DOMString& namespaceURI, + const DOMString& localName) = 0; + + /** + * + */ + virtual bool hasAttribute(const DOMString& name) = 0; + + /** + * + */ + virtual bool hasAttributeNS(const DOMString& namespaceURI, + const DOMString& localName) = 0; + + /** + * + */ + virtual TypeInfo *getSchemaTypeInto() = 0; + + + /** + * + */ + virtual void setIdAttribute(const DOMString &name, + bool isId) throw (DOMException) = 0; + + /** + * + */ + virtual void setIdAttributeNS(const DOMString &namespaceURI, + const DOMString &localName, + bool isId) throw (DOMException) = 0; + + /** + * + */ + virtual void setIdAttributeNode(const Attr *idAttr, + bool isId) throw (DOMException) = 0; + + //################## + //# Non-API methods + //################## + + /** + * + */ + virtual ~Element() {} + +}; + + + + + +/*######################################################################### +## Text +#########################################################################*/ + +/** + * + */ +class Text : virtual public CharacterData +{ +public: + + /** + * + */ + virtual Text *splitText(unsigned long offset) + throw(DOMException) = 0; + + /** + * + */ + virtual bool getIsElementContentWhitespace()= 0; + + /** + * + */ + virtual DOMString getWholeText() = 0; + + + /** + * + */ + virtual Text *replaceWholeText(const DOMString &content) + throw(DOMException) = 0; + + //################## + //# Non-API methods + //################## + + + /** + * + */ + virtual ~Text() {} + +}; + + + +/*######################################################################### +## Comment +#########################################################################*/ + +/** + * + */ +class Comment : virtual public CharacterData +{ +public: + + //################## + //# Non-API methods + //################## + + + /** + * + */ + virtual ~Comment() {} + + +}; + + + +/*######################################################################### +## TypeInfo +#########################################################################*/ + +/** + * + */ +class TypeInfo +{ +public: + + /** + * + */ + virtual DOMString getTypeName() =0; + + /** + * + */ + virtual DOMString getTypeNamespace() =0; + + typedef enum + { + DERIVATION_RESTRICTION = 0x00000001, + DERIVATION_EXTENSION = 0x00000002, + DERIVATION_UNION = 0x00000004, + DERIVATION_LIST = 0x00000008 + } DerivationMethod; + + + /** + * + */ + virtual bool isDerivedFrom(const DOMString &typeNamespaceArg, + const DOMString &typeNameArg, + DerivationMethod derivationMethod) =0; + + //################## + //# Non-API methods + //################## + + + /** + * + */ + virtual ~TypeInfo() {} + +}; + + + + +/*######################################################################### +## UserDataHandler +#########################################################################*/ + +/** + * + */ +class UserDataHandler +{ +public: + + typedef enum + { + NODE_CLONED = 1, + NODE_IMPORTED = 2, + NODE_DELETED = 3, + NODE_RENAMED = 4, + NODE_ADOPTED = 5 + } OperationType; + + + /** + * + */ + virtual void handle(unsigned short operation, + const DOMString &key, + const DOMUserData *data, + const Node *src, + const Node *dst) =0; + + //################## + //# Non-API methods + //################## + + + /** + * + */ + virtual ~UserDataHandler() {} + +}; + + +/*######################################################################### +## DOMError +#########################################################################*/ + +/** + * + */ +class DOMError +{ +public: + + typedef enum + { + SEVERITY_WARNING = 1, + SEVERITY_ERROR = 2, + SEVERITY_FATAL_ERROR = 3 + } ErrorSeverity; + + + /** + * + */ + virtual unsigned short getSeverity() =0; + + /** + * + */ + virtual DOMString getMessage() =0; + + /** + * + */ + virtual DOMString getType() =0; + + /** + * + */ + virtual DOMObject *getRelatedException() =0; + + /** + * + */ + virtual DOMObject *getRelatedData() =0; + + /** + * + */ + virtual DOMLocator *getLocation() =0; + + + //################## + //# Non-API methods + //################## + + + /** + * + */ + virtual ~DOMError() {} + +}; + + +/*######################################################################### +## DOMErrorHandler +#########################################################################*/ + +/** + * + */ +class DOMErrorHandler +{ +public: + /** + * + */ + virtual bool handleError(const DOMError *error) =0; + + //################## + //# Non-API methods + //################## + + + /** + * + */ + virtual ~DOMErrorHandler() {} + +}; + + + +/*######################################################################### +## DOMLocator +#########################################################################*/ + +/** + * + */ +class DOMLocator +{ +public: + + /** + * + */ + virtual long getLineNumber() =0; + + /** + * + */ + virtual long getColumnNumber() =0; + + /** + * + */ + virtual long getByteOffset() =0; + + /** + * + */ + virtual long getUtf16Offset() =0; + + + /** + * + */ + virtual Node *getRelatedNode() =0; + + + /** + * + */ + virtual DOMString getUri() =0; + + //################## + //# Non-API methods + //################## + + /** + * + */ + virtual ~DOMLocator() {} +}; + + +/*######################################################################### +## DOMConfiguration +#########################################################################*/ + +/** + * + */ +class DOMConfiguration +{ +public: + + /** + * + */ + virtual void setParameter(const DOMString &name, + const DOMUserData *value) throw (DOMException) =0; + + /** + * + */ + virtual DOMUserData *getParameter(const DOMString &name) + throw (DOMException) =0; + + /** + * + */ + virtual bool canSetParameter(const DOMString &name, + const DOMUserData *data) =0; + + /** + * + */ + virtual DOMStringList *getParameterNames() =0; + + //################## + //# Non-API methods + //################## + + + /** + * + */ + virtual ~DOMConfiguration() {} + +}; + + + + + + +/*######################################################################### +## CDATASection +#########################################################################*/ +/** + * + */ +class CDATASection : virtual public Text +{ +public: + + //################## + //# Non-API methods + //################## + + + /** + * + */ + virtual ~CDATASection() {} + +}; + + + + +/*######################################################################### +## DocumentType +#########################################################################*/ + +/** + * + */ +class DocumentType : virtual public Node +{ +public: + + /** + * + */ + virtual DOMString getName() = 0; + + /** + * + */ + virtual NamedNodeMap getEntities() = 0; + + /** + * + */ + virtual NamedNodeMap getNotations() = 0; + + /** + * + */ + virtual DOMString getPublicId() = 0; + + /** + * + */ + virtual DOMString getSystemId() = 0; + + /** + * + */ + virtual DOMString getInternalSubset() = 0; + + //################## + //# Non-API methods + //################## + + /** + * + */ + virtual ~DocumentType() {} + +}; + + + + + +/*######################################################################### +## Notation +#########################################################################*/ + +/** + * + */ +class Notation : virtual public Node +{ +public: + + /** + * + */ + virtual DOMString getPublicId() = 0; + + /** + * + */ + virtual DOMString getSystemId() = 0; + + //################## + //# Non-API methods + //################## + + + /** + * + */ + virtual ~Notation() {} +}; + + + + + + +/*######################################################################### +## Entity +#########################################################################*/ + +/** + * + */ +class Entity : virtual public Node +{ +public: + + /** + * + */ + virtual DOMString getPublicId() = 0; + + /** + * + */ + virtual DOMString getSystemId() = 0; + + /** + * + */ + virtual DOMString getNotationName() = 0; + + /** + * + */ + virtual DOMString getInputEncoding() = 0; + + /** + * + */ + virtual DOMString getXmlEncoding() = 0; + + /** + * + */ + virtual DOMString getXmlVersion() = 0; + + //################## + //# Non-API methods + //################## + + + /** + * + */ + virtual ~Entity() {} +}; + + + + + +/*######################################################################### +## EntityReference +#########################################################################*/ +/** + * + */ +class EntityReference : virtual public Node +{ +public: + + + //################## + //# Non-API methods + //################## + + /** + * + */ + virtual ~EntityReference() {} +}; + + + + + +/*######################################################################### +## ProcessingInstruction +#########################################################################*/ + +/** + * + */ +class ProcessingInstruction : virtual public Node +{ +public: + + /** + * + */ + virtual DOMString getTarget() = 0; + + /** + * + */ + virtual DOMString getData() = 0; + + /** + * + */ + virtual void setData(const DOMString& val) throw(DOMException) = 0; + + //################## + //# Non-API methods + //################## + + + /** + * + */ + virtual ~ProcessingInstruction() {} + +}; + + + + + +/*######################################################################### +## DocumentFragment +#########################################################################*/ +/** + * + */ +class DocumentFragment : virtual public Node +{ +public: + + //################## + //# Non-API methods + //################## + + + /** + * + */ + virtual ~DocumentFragment() {} +}; + + + + + + +/*######################################################################### +## Document +#########################################################################*/ + +/** + * + */ +class Document : virtual public Node +{ +public: + + /** + * + */ + virtual DocumentType *getDoctype() = 0; + + /** + * + */ + virtual DOMImplementation *getImplementation() = 0; + + /** + * + */ + virtual Element *getDocumentElement() = 0; + + /** + * + */ + virtual Element *createElement(const DOMString& tagName) + throw(DOMException) = 0; + + /** + * + */ + virtual DocumentFragment *createDocumentFragment() = 0; + + /** + * + */ + virtual Text *createTextNode(const DOMString& data) = 0; + + /** + * + */ + virtual Comment *createComment(const DOMString& data) = 0; + + /** + * + */ + virtual CDATASection *createCDATASection(const DOMString& data) + throw(DOMException) = 0; + + /** + * + */ + virtual ProcessingInstruction *createProcessingInstruction(const DOMString& target, + const DOMString& data) + throw(DOMException) = 0; + + /** + * + */ + virtual Attr *createAttribute(const DOMString& name) + throw(DOMException) = 0; + + /** + * + */ + virtual EntityReference *createEntityReference(const DOMString& name) + throw(DOMException) = 0; + + /** + * + */ + virtual NodeList getElementsByTagName(const DOMString& tagname) = 0; + + + /** + * + */ + virtual Node *importNode(const Node *importedNode, + bool deep) + throw(DOMException) = 0; + + /** + * + */ + virtual Element *createElementNS(const DOMString& namespaceURI, + const DOMString& qualifiedName) + throw(DOMException) = 0; + + /** + * + */ + virtual Attr *createAttributeNS(const DOMString& namespaceURI, + const DOMString& qualifiedName) + throw(DOMException) = 0; + + /** + * + */ + virtual NodeList getElementsByTagNameNS(const DOMString& namespaceURI, + const DOMString& localName) = 0; + + /** + * + */ + virtual Element *getElementById(const DOMString& elementId) = 0; + + + /** + * + */ + virtual DOMString getInputEncoding() = 0; + + + /** + * + */ + virtual DOMString getXmlEncoding() = 0; + + /** + * + */ + virtual bool getXmlStandalone() = 0; + + /** + * + */ + virtual void setXmlStandalone(bool val) throw (DOMException) = 0; + + /** + * + */ + virtual DOMString getXmlVersion() = 0; + + /** + * + */ + virtual void setXmlVersion(const DOMString &version) throw (DOMException) = 0; + + /** + * + */ + virtual bool getStrictErrorChecking() = 0; + + /** + * + */ + virtual void setStrictErrorChecking(bool val) = 0; + + + /** + * + */ + virtual DOMString getDocumentURI() = 0; + + /** + * + */ + virtual void setDocumentURI(const DOMString &uri) = 0; + + /** + * + */ + virtual Node *adoptNode(const Node *source) throw (DOMException) = 0; + + /** + * + */ + virtual DOMConfiguration *getDomConfig() = 0; + + /** + * + */ + virtual void normalizeDocument() = 0; + + /** + * + */ + virtual Node *renameNode(const Node *n, + const DOMString &namespaceURI, + const DOMString &qualifiedName) + throw (DOMException) = 0; + + + //################## + //# Non-API methods + //################## + + /** + * + */ + virtual ~Document() {} + +}; + + + + + + + + + + + +} //namespace dom +} //namespace w3c +} //namespace org + + +#endif // __DOM_H__ + + +/*######################################################################### +## E N D O F F I L E +#########################################################################*/ + + + + diff --git a/src/dom/dom.idl b/src/dom/dom.idl new file mode 100755 index 000000000..4c9dcbfe2 --- /dev/null +++ b/src/dom/dom.idl @@ -0,0 +1,548 @@ +/* + * Copyright (c) 2004 World Wide Web Consortium, + * + * (Massachusetts Institute of Technology, European Research Consortium for + * Informatics and Mathematics, Keio University). All Rights Reserved. This + * work is distributed under the W3C(r) Software License [1] in the hope that + * it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * [1] http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 + */ + +// File: http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/dom.idl + +#ifndef _DOM_IDL_ +#define _DOM_IDL_ + +#pragma prefix "w3c.org" +module dom +{ + + valuetype DOMString sequence; + + typedef unsigned long long DOMTimeStamp; + + typedef any DOMUserData; + + typedef Object DOMObject; + + interface DOMImplementation; + interface DocumentType; + interface Document; + interface NodeList; + interface NamedNodeMap; + interface UserDataHandler; + interface Element; + interface TypeInfo; + interface DOMLocator; + + exception DOMException { + unsigned short code; + }; + // ExceptionCode + const unsigned short INDEX_SIZE_ERR = 1; + const unsigned short DOMSTRING_SIZE_ERR = 2; + const unsigned short HIERARCHY_REQUEST_ERR = 3; + const unsigned short WRONG_DOCUMENT_ERR = 4; + const unsigned short INVALID_CHARACTER_ERR = 5; + const unsigned short NO_DATA_ALLOWED_ERR = 6; + const unsigned short NO_MODIFICATION_ALLOWED_ERR = 7; + const unsigned short NOT_FOUND_ERR = 8; + const unsigned short NOT_SUPPORTED_ERR = 9; + const unsigned short INUSE_ATTRIBUTE_ERR = 10; + // Introduced in DOM Level 2: + const unsigned short INVALID_STATE_ERR = 11; + // Introduced in DOM Level 2: + const unsigned short SYNTAX_ERR = 12; + // Introduced in DOM Level 2: + const unsigned short INVALID_MODIFICATION_ERR = 13; + // Introduced in DOM Level 2: + const unsigned short NAMESPACE_ERR = 14; + // Introduced in DOM Level 2: + const unsigned short INVALID_ACCESS_ERR = 15; + // Introduced in DOM Level 3: + const unsigned short VALIDATION_ERR = 16; + // Introduced in DOM Level 3: + const unsigned short TYPE_MISMATCH_ERR = 17; + + + // Introduced in DOM Level 3: + interface DOMStringList { + DOMString item(in unsigned long index); + readonly attribute unsigned long length; + boolean contains(in DOMString str); + }; + + // Introduced in DOM Level 3: + interface NameList { + DOMString getName(in unsigned long index); + DOMString getNamespaceURI(in unsigned long index); + readonly attribute unsigned long length; + boolean contains(in DOMString str); + boolean containsNS(in DOMString namespaceURI, + in DOMString name); + }; + + // Introduced in DOM Level 3: + interface DOMImplementationList { + DOMImplementation item(in unsigned long index); + readonly attribute unsigned long length; + }; + + // Introduced in DOM Level 3: + interface DOMImplementationSource { + DOMImplementation getDOMImplementation(in DOMString features); + DOMImplementationList getDOMImplementationList(in DOMString features); + }; + + interface DOMImplementation { + boolean hasFeature(in DOMString feature, + in DOMString version); + // Introduced in DOM Level 2: + DocumentType createDocumentType(in DOMString qualifiedName, + in DOMString publicId, + in DOMString systemId) + raises(DOMException); + // Introduced in DOM Level 2: + Document createDocument(in DOMString namespaceURI, + in DOMString qualifiedName, + in DocumentType doctype) + raises(DOMException); + // Introduced in DOM Level 3: + DOMObject getFeature(in DOMString feature, + in DOMString version); + }; + + interface Node { + + // NodeType + const unsigned short ELEMENT_NODE = 1; + const unsigned short ATTRIBUTE_NODE = 2; + const unsigned short TEXT_NODE = 3; + const unsigned short CDATA_SECTION_NODE = 4; + const unsigned short ENTITY_REFERENCE_NODE = 5; + const unsigned short ENTITY_NODE = 6; + const unsigned short PROCESSING_INSTRUCTION_NODE = 7; + const unsigned short COMMENT_NODE = 8; + const unsigned short DOCUMENT_NODE = 9; + const unsigned short DOCUMENT_TYPE_NODE = 10; + const unsigned short DOCUMENT_FRAGMENT_NODE = 11; + const unsigned short NOTATION_NODE = 12; + + readonly attribute DOMString nodeName; + attribute DOMString nodeValue; + // raises(DOMException) on setting + // raises(DOMException) on retrieval + + readonly attribute unsigned short nodeType; + readonly attribute Node parentNode; + readonly attribute NodeList childNodes; + readonly attribute Node firstChild; + readonly attribute Node lastChild; + readonly attribute Node previousSibling; + readonly attribute Node nextSibling; + readonly attribute NamedNodeMap attributes; + // Modified in DOM Level 2: + readonly attribute Document ownerDocument; + // Modified in DOM Level 3: + Node insertBefore(in Node newChild, + in Node refChild) + raises(DOMException); + // Modified in DOM Level 3: + Node replaceChild(in Node newChild, + in Node oldChild) + raises(DOMException); + // Modified in DOM Level 3: + Node removeChild(in Node oldChild) + raises(DOMException); + // Modified in DOM Level 3: + Node appendChild(in Node newChild) + raises(DOMException); + boolean hasChildNodes(); + Node cloneNode(in boolean deep); + // Modified in DOM Level 3: + void normalize(); + // Introduced in DOM Level 2: + boolean isSupported(in DOMString feature, + in DOMString version); + // Introduced in DOM Level 2: + readonly attribute DOMString namespaceURI; + // Introduced in DOM Level 2: + attribute DOMString prefix; + // raises(DOMException) on setting + + // Introduced in DOM Level 2: + readonly attribute DOMString localName; + // Introduced in DOM Level 2: + boolean hasAttributes(); + // Introduced in DOM Level 3: + readonly attribute DOMString baseURI; + + // DocumentPosition + const unsigned short DOCUMENT_POSITION_DISCONNECTED = 0x01; + const unsigned short DOCUMENT_POSITION_PRECEDING = 0x02; + const unsigned short DOCUMENT_POSITION_FOLLOWING = 0x04; + const unsigned short DOCUMENT_POSITION_CONTAINS = 0x08; + const unsigned short DOCUMENT_POSITION_CONTAINED_BY = 0x10; + const unsigned short DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 0x20; + + // Introduced in DOM Level 3: + unsigned short compareDocumentPosition(in Node other) + raises(DOMException); + // Introduced in DOM Level 3: + attribute DOMString textContent; + // raises(DOMException) on setting + // raises(DOMException) on retrieval + + // Introduced in DOM Level 3: + boolean isSameNode(in Node other); + // Introduced in DOM Level 3: + DOMString lookupPrefix(in DOMString namespaceURI); + // Introduced in DOM Level 3: + boolean isDefaultNamespace(in DOMString namespaceURI); + // Introduced in DOM Level 3: + DOMString lookupNamespaceURI(in DOMString prefix); + // Introduced in DOM Level 3: + boolean isEqualNode(in Node arg); + // Introduced in DOM Level 3: + DOMObject getFeature(in DOMString feature, + in DOMString version); + // Introduced in DOM Level 3: + DOMUserData setUserData(in DOMString key, + in DOMUserData data, + in UserDataHandler handler); + // Introduced in DOM Level 3: + DOMUserData getUserData(in DOMString key); + }; + + interface NodeList { + Node item(in unsigned long index); + readonly attribute unsigned long length; + }; + + interface NamedNodeMap { + Node getNamedItem(in DOMString name); + Node setNamedItem(in Node arg) + raises(DOMException); + Node removeNamedItem(in DOMString name) + raises(DOMException); + Node item(in unsigned long index); + readonly attribute unsigned long length; + // Introduced in DOM Level 2: + Node getNamedItemNS(in DOMString namespaceURI, + in DOMString localName) + raises(DOMException); + // Introduced in DOM Level 2: + Node setNamedItemNS(in Node arg) + raises(DOMException); + // Introduced in DOM Level 2: + Node removeNamedItemNS(in DOMString namespaceURI, + in DOMString localName) + raises(DOMException); + }; + + interface CharacterData : Node { + attribute DOMString data; + // raises(DOMException) on setting + // raises(DOMException) on retrieval + + readonly attribute unsigned long length; + DOMString substringData(in unsigned long offset, + in unsigned long count) + raises(DOMException); + void appendData(in DOMString arg) + raises(DOMException); + void insertData(in unsigned long offset, + in DOMString arg) + raises(DOMException); + void deleteData(in unsigned long offset, + in unsigned long count) + raises(DOMException); + void replaceData(in unsigned long offset, + in unsigned long count, + in DOMString arg) + raises(DOMException); + }; + + interface Attr : Node { + readonly attribute DOMString name; + readonly attribute boolean specified; + attribute DOMString value; + // raises(DOMException) on setting + + // Introduced in DOM Level 2: + readonly attribute Element ownerElement; + // Introduced in DOM Level 3: + readonly attribute TypeInfo schemaTypeInfo; + // Introduced in DOM Level 3: + readonly attribute boolean isId; + }; + + interface Element : Node { + readonly attribute DOMString tagName; + DOMString getAttribute(in DOMString name); + void setAttribute(in DOMString name, + in DOMString value) + raises(DOMException); + void removeAttribute(in DOMString name) + raises(DOMException); + Attr getAttributeNode(in DOMString name); + Attr setAttributeNode(in Attr newAttr) + raises(DOMException); + Attr removeAttributeNode(in Attr oldAttr) + raises(DOMException); + NodeList getElementsByTagName(in DOMString name); + // Introduced in DOM Level 2: + DOMString getAttributeNS(in DOMString namespaceURI, + in DOMString localName) + raises(DOMException); + // Introduced in DOM Level 2: + void setAttributeNS(in DOMString namespaceURI, + in DOMString qualifiedName, + in DOMString value) + raises(DOMException); + // Introduced in DOM Level 2: + void removeAttributeNS(in DOMString namespaceURI, + in DOMString localName) + raises(DOMException); + // Introduced in DOM Level 2: + Attr getAttributeNodeNS(in DOMString namespaceURI, + in DOMString localName) + raises(DOMException); + // Introduced in DOM Level 2: + Attr setAttributeNodeNS(in Attr newAttr) + raises(DOMException); + // Introduced in DOM Level 2: + NodeList getElementsByTagNameNS(in DOMString namespaceURI, + in DOMString localName) + raises(DOMException); + // Introduced in DOM Level 2: + boolean hasAttribute(in DOMString name); + // Introduced in DOM Level 2: + boolean hasAttributeNS(in DOMString namespaceURI, + in DOMString localName) + raises(DOMException); + // Introduced in DOM Level 3: + readonly attribute TypeInfo schemaTypeInfo; + // Introduced in DOM Level 3: + void setIdAttribute(in DOMString name, + in boolean isId) + raises(DOMException); + // Introduced in DOM Level 3: + void setIdAttributeNS(in DOMString namespaceURI, + in DOMString localName, + in boolean isId) + raises(DOMException); + // Introduced in DOM Level 3: + void setIdAttributeNode(in Attr idAttr, + in boolean isId) + raises(DOMException); + }; + + interface Text : CharacterData { + Text splitText(in unsigned long offset) + raises(DOMException); + // Introduced in DOM Level 3: + readonly attribute boolean isElementContentWhitespace; + // Introduced in DOM Level 3: + readonly attribute DOMString wholeText; + // Introduced in DOM Level 3: + Text replaceWholeText(in DOMString content) + raises(DOMException); + }; + + interface Comment : CharacterData { + }; + + // Introduced in DOM Level 3: + interface TypeInfo { + readonly attribute DOMString typeName; + readonly attribute DOMString typeNamespace; + + // DerivationMethods + const unsigned long DERIVATION_RESTRICTION = 0x00000001; + const unsigned long DERIVATION_EXTENSION = 0x00000002; + const unsigned long DERIVATION_UNION = 0x00000004; + const unsigned long DERIVATION_LIST = 0x00000008; + + boolean isDerivedFrom(in DOMString typeNamespaceArg, + in DOMString typeNameArg, + in unsigned long derivationMethod); + }; + + // Introduced in DOM Level 3: + interface UserDataHandler { + + // OperationType + const unsigned short NODE_CLONED = 1; + const unsigned short NODE_IMPORTED = 2; + const unsigned short NODE_DELETED = 3; + const unsigned short NODE_RENAMED = 4; + const unsigned short NODE_ADOPTED = 5; + + void handle(in unsigned short operation, + in DOMString key, + in DOMUserData data, + in Node src, + in Node dst); + }; + + // Introduced in DOM Level 3: + interface DOMError { + + // ErrorSeverity + const unsigned short SEVERITY_WARNING = 1; + const unsigned short SEVERITY_ERROR = 2; + const unsigned short SEVERITY_FATAL_ERROR = 3; + + readonly attribute unsigned short severity; + readonly attribute DOMString message; + readonly attribute DOMString type; + readonly attribute DOMObject relatedException; + readonly attribute DOMObject relatedData; + readonly attribute DOMLocator location; + }; + + // Introduced in DOM Level 3: + interface DOMErrorHandler { + boolean handleError(in DOMError error); + }; + + // Introduced in DOM Level 3: + interface DOMLocator { + readonly attribute long lineNumber; + readonly attribute long columnNumber; + readonly attribute long byteOffset; + readonly attribute long utf16Offset; + readonly attribute Node relatedNode; + readonly attribute DOMString uri; + }; + + // Introduced in DOM Level 3: + interface DOMConfiguration { + void setParameter(in DOMString name, + in DOMUserData value) + raises(DOMException); + DOMUserData getParameter(in DOMString name) + raises(DOMException); + boolean canSetParameter(in DOMString name, + in DOMUserData value); + readonly attribute DOMStringList parameterNames; + }; + + interface CDATASection : Text { + }; + + interface DocumentType : Node { + readonly attribute DOMString name; + readonly attribute NamedNodeMap entities; + readonly attribute NamedNodeMap notations; + // Introduced in DOM Level 2: + readonly attribute DOMString publicId; + // Introduced in DOM Level 2: + readonly attribute DOMString systemId; + // Introduced in DOM Level 2: + readonly attribute DOMString internalSubset; + }; + + interface Notation : Node { + readonly attribute DOMString publicId; + readonly attribute DOMString systemId; + }; + + interface Entity : Node { + readonly attribute DOMString publicId; + readonly attribute DOMString systemId; + readonly attribute DOMString notationName; + // Introduced in DOM Level 3: + readonly attribute DOMString inputEncoding; + // Introduced in DOM Level 3: + readonly attribute DOMString xmlEncoding; + // Introduced in DOM Level 3: + readonly attribute DOMString xmlVersion; + }; + + interface EntityReference : Node { + }; + + interface ProcessingInstruction : Node { + readonly attribute DOMString target; + attribute DOMString data; + // raises(DOMException) on setting + + }; + + interface DocumentFragment : Node { + }; + + interface Document : Node { + // Modified in DOM Level 3: + readonly attribute DocumentType doctype; + readonly attribute DOMImplementation implementation; + readonly attribute Element documentElement; + Element createElement(in DOMString tagName) + raises(DOMException); + DocumentFragment createDocumentFragment(); + Text createTextNode(in DOMString data); + Comment createComment(in DOMString data); + CDATASection createCDATASection(in DOMString data) + raises(DOMException); + ProcessingInstruction createProcessingInstruction(in DOMString target, + in DOMString data) + raises(DOMException); + Attr createAttribute(in DOMString name) + raises(DOMException); + EntityReference createEntityReference(in DOMString name) + raises(DOMException); + NodeList getElementsByTagName(in DOMString tagname); + // Introduced in DOM Level 2: + Node importNode(in Node importedNode, + in boolean deep) + raises(DOMException); + // Introduced in DOM Level 2: + Element createElementNS(in DOMString namespaceURI, + in DOMString qualifiedName) + raises(DOMException); + // Introduced in DOM Level 2: + Attr createAttributeNS(in DOMString namespaceURI, + in DOMString qualifiedName) + raises(DOMException); + // Introduced in DOM Level 2: + NodeList getElementsByTagNameNS(in DOMString namespaceURI, + in DOMString localName); + // Introduced in DOM Level 2: + Element getElementById(in DOMString elementId); + // Introduced in DOM Level 3: + readonly attribute DOMString inputEncoding; + // Introduced in DOM Level 3: + readonly attribute DOMString xmlEncoding; + // Introduced in DOM Level 3: + attribute boolean xmlStandalone; + // raises(DOMException) on setting + + // Introduced in DOM Level 3: + attribute DOMString xmlVersion; + // raises(DOMException) on setting + + // Introduced in DOM Level 3: + attribute boolean strictErrorChecking; + // Introduced in DOM Level 3: + attribute DOMString documentURI; + // Introduced in DOM Level 3: + Node adoptNode(in Node source) + raises(DOMException); + // Introduced in DOM Level 3: + readonly attribute DOMConfiguration domConfig; + // Introduced in DOM Level 3: + void normalizeDocument(); + // Introduced in DOM Level 3: + Node renameNode(in Node n, + in DOMString namespaceURI, + in DOMString qualifiedName) + raises(DOMException); + }; +}; + +#endif // _DOM_IDL_ + diff --git a/src/dom/domimpl.cpp b/src/dom/domimpl.cpp new file mode 100755 index 000000000..b3197c948 --- /dev/null +++ b/src/dom/domimpl.cpp @@ -0,0 +1,2984 @@ +/** + * Phoebe DOM Implementation. + * + * This is a C++ approximation of the W3C DOM model, which follows + * fairly closely the specifications in the various .idl files, copies of + * which are provided for reference. Most important is this one: + * + * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/idl-definitions.html + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2005 Bob Jamison + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include "domimpl.h" + +namespace org +{ +namespace w3c +{ +namespace dom +{ + + +/** + * Test if the given substring exists for the length of the string + * in a given buffer + */ +/* +static bool match(const DOMString &buf, char *str) +{ + int pos = 0; + while (*str) + { + if (buf[pos++] != *str++) + return false; + } + return true; +} +*/ + + + +/*######################################################################### +## DOMImplementationSourceImpl +#########################################################################*/ + + +/** + * + */ +DOMImplementationSourceImpl::DOMImplementationSourceImpl() +{ + domImpl = new DOMImplementationImpl(); +} + +/** + * + */ +DOMImplementationSourceImpl::~DOMImplementationSourceImpl() +{ + delete domImpl; +} + +/** + * + */ +DOMImplementation *DOMImplementationSourceImpl::getDOMImplementation( + const DOMString &features) +{ + return domImpl; +} + +/** + * + */ +DOMImplementationList DOMImplementationSourceImpl::getDOMImplementationList( + const DOMString &features) +{ + return domImplList; +} + + + + + + + +/*######################################################################### +## DOMImplementationImpl +#########################################################################*/ + + +/** + * + */ +DOMImplementationImpl::DOMImplementationImpl() +{ +} + +/** + * + */ +DOMImplementationImpl::~DOMImplementationImpl() +{ +} + +/** + * + */ +bool DOMImplementationImpl::hasFeature(const DOMString& feature, + const DOMString& version) +{ + return false; +} + + +/** + * + */ +DocumentType *DOMImplementationImpl::createDocumentType(const DOMString& qualifiedName, + const DOMString& publicId, + const DOMString& systemId) + throw(DOMException) +{ + DocumentTypeImpl *typeImpl = new DocumentTypeImpl(qualifiedName, + publicId, systemId); + return typeImpl; +} + +/** + * + */ +Document *DOMImplementationImpl::createDocument( + const DOMString& namespaceURI, + const DOMString& qualifiedName, + DocumentType *doctype) + throw(DOMException) +{ + DocumentImpl *doc = new DocumentImpl(this, + namespaceURI, + qualifiedName, + doctype); + return doc; +} + +/** + * + */ +DOMObject *DOMImplementationImpl::getFeature(const DOMString& feature, + const DOMString& version) + +{ + return NULL; +} + + + + + +/*######################################################################### +## NodeImpl +#########################################################################*/ + +/** + * Utility for finding the first Element above + * a given node. Used by several methods below + */ +static Node *getAncestorElement(Node *node) +{ + if (!node) + return NULL; + node = node->getParentNode(); + //Either quit because I am an element, or because I am null + while (node) + { + if (node->getNodeType() == Node::ELEMENT_NODE) + return node; + node = node->getParentNode(); + } + return node; +} + +/** + * + */ +DOMString NodeImpl::getNodeName() +{ + return nodeName; +} + +/** + * + */ +DOMString NodeImpl::getNodeValue() throw (DOMException) +{ + return nodeValue; +} + +/** + * + */ +void NodeImpl::setNodeValue(const DOMString& val) throw (DOMException) +{ + nodeValue = val; +} + +/** + * + */ +unsigned short NodeImpl::getNodeType() +{ + return nodeType; +} + +/** + * + */ +Node *NodeImpl::getParentNode() +{ + return parent; +} + +/** + * + */ +NodeList NodeImpl::getChildNodes() +{ + NodeList list; + for (NodeImpl *node = firstChild ; node ; node=node->next) + list.add(node); + return list; +} + +/** + * + */ +Node *NodeImpl::getFirstChild() +{ + return firstChild; +} + +/** + * + */ +Node *NodeImpl::getLastChild() +{ + return lastChild; +} + +/** + * + */ +Node *NodeImpl::getPreviousSibling() +{ + return prev; +} + +/** + * + */ +Node *NodeImpl::getNextSibling() +{ + return next; +} + +/** + * + */ +NamedNodeMap &NodeImpl::getAttributes() +{ + NamedNodeMap &attrs = attributes; + return attrs; +} + + +/** + * + */ +Document *NodeImpl::getOwnerDocument() +{ + return ownerDocument; +} + +/** + * + */ +Node *NodeImpl::insertBefore(const Node *newChild, + const Node *refChild) + throw(DOMException) +{ + Node *node = NULL; + return node; +} + +/** + * + */ +Node *NodeImpl::replaceChild(const Node *newChild, + const Node *oldChild) + throw(DOMException) +{ + Node *node = NULL; + return node; +} + +/** + * + */ +Node *NodeImpl::removeChild(const Node *oldChild) + throw(DOMException) +{ + Node *node = NULL; + return node; +} + +/** + * + */ +Node *NodeImpl::appendChild(const Node *newChild) + throw(DOMException) +{ + if (!newChild) + return NULL; + + Node *child = (Node *)newChild; + NodeImpl *childImpl = dynamic_cast (child); + + childImpl->parent = this; + childImpl->ownerDocument = ownerDocument; + + if (!firstChild || !lastChild) + { + //Set up our first member + firstChild = childImpl; + lastChild = childImpl; + } + else + { + //link at the last position + lastChild->next = childImpl; + childImpl->prev = lastChild; + lastChild = childImpl; + } + + return child; +} + +/** + * + */ +bool NodeImpl::hasChildNodes() +{ + return (firstChild != NULL); +} + +/** + * + */ +Node *NodeImpl::cloneNode(bool deep) +{ + NodeImpl *node = new NodeImpl(ownerDocument, nodeName); + node->parent = parent; + node->prev = prev; + node->next = next; + node->userData = userData; + node->nodeValue = nodeValue; + + if (deep) + { + node->firstChild = node->lastChild = NULL; + for (NodeImpl *child = firstChild ; child ; child=child->next) + { + node->appendChild(child->cloneNode(deep)); + } + } + else + { + node->firstChild = firstChild; + node->lastChild = lastChild; + } + + return node; +} + +/** + * Concatenate adjoining text subnodes, remove null-length nodes + */ +void NodeImpl::normalize() +{ + //First, concatenate adjoining text nodes + NodeImpl *next = NULL; + for (NodeImpl *child = firstChild ; child ; child=next) + { + if (child->getNodeType() != Node::TEXT_NODE) + continue; + next = NULL; + DOMString sval = child->getNodeValue(); + for (NodeImpl *sibling = child->next ; sibling ; sibling=next) + { + next = sibling->next; + if (sibling->getNodeType() != Node::TEXT_NODE) + break; + sval.append(sibling->getNodeValue()); + //unlink and delete + child->next = sibling->next; + if (sibling->next) + sibling->next->prev = child; + delete sibling; + } + child->setNodeValue(sval); + } + + //Next, we remove zero-length text subnodes + next = NULL; + for (NodeImpl *child = firstChild ; child ; child=next) + { + next = child->next; + if (child->getNodeType() != Node::TEXT_NODE) + continue; + if (child->getNodeValue().size() == 0) + { + //unlink and delete + if (child->prev) + child->prev->next = child->next; + if (child->next) + child->next->prev = child->prev; + delete child; + } + } + +} + +/** + * + */ +bool NodeImpl::isSupported(const DOMString& feature, + const DOMString& version) +{ + //again, no idea + return false; +} + +/** + * + */ +DOMString NodeImpl::getNamespaceURI() +{ + return namespaceURI; +} + +/** + * + */ +DOMString NodeImpl::getPrefix() +{ + return prefix; +} + +/** + * + */ +void NodeImpl::setPrefix(const DOMString& val) throw(DOMException) +{ + prefix = val; + if (prefix.size()>0) + nodeName = prefix + ":" + localName; + else + nodeName = localName; +} + +/** + * + */ +DOMString NodeImpl::getLocalName() +{ + return localName; +} + +/** + * + */ +bool NodeImpl::hasAttributes() +{ + return (attributes.getLength() > 0); +} + +/** + * + */ +DOMString NodeImpl::getBaseURI() +{ + return baseURI; +} + +/** + * + */ +unsigned short NodeImpl::compareDocumentPosition(const Node *otherArg) +{ + if (!otherArg || this == otherArg) + return 0;//no flags + + Node *node; + Node *other = (Node *)otherArg; + + //Look above me + for (node=getParentNode() ; node ; node=node->getParentNode()) + if (node == other) + return DOCUMENT_POSITION_CONTAINED_BY; + + //Look above the other guy. See me? + for (node=other->getParentNode() ; node ; node=node->getParentNode()) + if (node == this) + return DOCUMENT_POSITION_CONTAINS; + + + return DOCUMENT_POSITION_DISCONNECTED | + DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC; +} + +/** + * + */ +DOMString NodeImpl::getTextContext() throw(DOMException) +{ + //no idea + return DOMString(""); +} + + +/** + * + */ +void NodeImpl::setTextContext(const DOMString &val) throw(DOMException) +{ + //no idea +} + + +/** + * From DOM3 Namespace algorithms + */ +DOMString NodeImpl::lookupPrefix(const DOMString &theNamespaceURI) +{ + + if (theNamespaceURI.size()==0) + { + return DOMString(""); + } + + switch (nodeType) + { + case Node::ELEMENT_NODE: + { + ElementImpl *elem = (ElementImpl *)this; + return lookupNamespacePrefix(theNamespaceURI, elem); + } + case Node::DOCUMENT_NODE: + { + Document *doc = (Document *)this; + Element *elem = doc->getDocumentElement(); + return elem->lookupPrefix(theNamespaceURI); + } + case Node::ENTITY_NODE : + case Node::NOTATION_NODE: + case Node::DOCUMENT_FRAGMENT_NODE: + case Node::DOCUMENT_TYPE_NODE: + return DOMString(""); // type is unknown + case Node::ATTRIBUTE_NODE: + { + Attr *attr = (Attr *)this; + Element *elem = (Element *)attr->getOwnerElement(); + if ( elem ) + { + return elem->lookupPrefix(theNamespaceURI); + } + return DOMString(""); + } + default: + { + //Get ancestor element, if any + if ( Node *ancestor = getAncestorElement(this) ) + { + return ancestor->lookupPrefix(theNamespaceURI); + } + return DOMString(""); + } + }//switch + return DOMString(""); +} + + +/** + * + */ +bool NodeImpl::isDefaultNamespace(const DOMString &theNamespaceURI) +{ + switch (nodeType) + { + case ELEMENT_NODE: + if ( namespaceURI.size()>0 && prefix.size()==0 ) + { + return (namespaceURI == theNamespaceURI); + } + if ( Node *attr = attributes.getNamedItem("xmlns")) + { + return (attr->getNodeValue() == theNamespaceURI); + } + + + if ( Node *ancestor = getAncestorElement(this) ) + { + return ancestor->isDefaultNamespace(theNamespaceURI); + } + else + { + return false; + } + case DOCUMENT_NODE: + { //just use braces for local declaration + Document *doc = (Document *)this; + Element *elem = doc->getDocumentElement(); + return elem->isDefaultNamespace(theNamespaceURI); + } + case ENTITY_NODE: + case NOTATION_NODE: + case DOCUMENT_TYPE_NODE: + case DOCUMENT_FRAGMENT_NODE: + return false; + case ATTRIBUTE_NODE: + {//braces only for scope + Attr *attr = (Attr *)this; + Element *ownerElement = attr->getOwnerElement(); + if ( ownerElement ) + { + return ownerElement->isDefaultNamespace(theNamespaceURI); + } + else + { + return false; + } + } + default: + if ( Node *ancestor = getAncestorElement(this) ) + { + return ancestor->isDefaultNamespace(theNamespaceURI); + } + else + { + return false; + } + }//switch + + return false; +} + + +/** + * + */ +DOMString NodeImpl::lookupNamespaceURI(const DOMString &thePrefix) +{ + switch (nodeType) + { + case ELEMENT_NODE: + { + if ( namespaceURI.size()>0 && prefix == thePrefix ) + { + DOMString nsURI = namespaceURI; + return (nsURI); + } + if ( hasAttributes() ) + { + NamedNodeMap attributes = getAttributes(); + int nrAttrs = attributes.getLength(); + for (int i=0 ; igetPrefix() == "xmlns" && attr->getLocalName() == thePrefix ) + { // non default namespace + if (attr->getNodeValue().size()>0) + { + return (attr->getNodeValue()); + } + return DOMString(""); + } + else if (attr->getLocalName() == "xmlns" && thePrefix.size()==0) + { // default namespace + if (attr->getNodeValue().size()>0) + { + return (attr->getNodeValue()); + } + return DOMString(""); + } + } + } + + if ( Node *ancestor = getAncestorElement(this) ) + { + return ancestor->lookupNamespaceURI(thePrefix); + } + return DOMString(""); + } + case DOCUMENT_NODE: + { + Document *doc = (Document *)this; + Element *elem = doc->getDocumentElement(); + return elem->lookupNamespaceURI(thePrefix); + } + case ENTITY_NODE: + case NOTATION_NODE: + case DOCUMENT_TYPE_NODE: + case DOCUMENT_FRAGMENT_NODE: + return DOMString(""); + + case ATTRIBUTE_NODE: + { + if (Element *ownerElement = ((Attr *)this)->getOwnerElement()) + { + return ownerElement->lookupNamespaceURI(thePrefix); + } + else + { + return DOMString(""); + } + } + default: + if ( Node *ancestor = getAncestorElement(this) ) + { + return ancestor->lookupNamespaceURI(thePrefix); + } + else + { + return DOMString(""); + } + }//switch +} + + +/** + * + */ +bool NodeImpl::isEqualNode(const Node *nodeArg) +{ + if (!nodeArg) + return false; + + if (this == nodeArg) + return true; + + Node *node = (Node *)nodeArg; + + if (getNodeType() != node->getNodeType() || + getNodeName() != node->getNodeName() || + getLocalName() != node->getLocalName() || + getNamespaceURI() != node->getNamespaceURI() || + getPrefix() != node->getPrefix() || + getNodeValue() != node->getNodeValue() || + getBaseURI() != node->getBaseURI() ) + return false; + + return true; +} + + + +/** + * + */ +DOMObject *NodeImpl::getFeature(const DOMString &feature, + const DOMString &version) +{ + //dont know + return NULL; +} + +/** + * + */ +DOMUserData *NodeImpl::setUserData(const DOMString &key, + const DOMUserData *data, + const UserDataHandler *handler) +{ + UserDataEntry *entry = userDataEntries; + UserDataEntry *prev = NULL; + while (entry) + { + if (entry->key == key) + { + DOMUserData *oldData = entry->data; + entry->data = (DOMUserData *)data; + entry->handler = (UserDataHandler *)handler; + return oldData; + } + prev = entry; + entry = entry->next; + } + + //Make a new one + UserDataEntry *newEntry = new UserDataEntry(key, data, handler); + if (!prev) + userDataEntries = newEntry; + else + prev->next = newEntry; + + return NULL; +} + +/** + * + */ +DOMUserData *NodeImpl::getUserData(const DOMString &key) +{ + UserDataEntry *entry = userDataEntries; + while (entry) + { + if (entry->key == key) + return entry->data; + entry = entry->next; + } + return NULL; +} + + + +//################## +//# Non-API methods +//################## + +/** + * + */ +void NodeImpl::setNodeName(const DOMString &qualifiedName) +{ + nodeName = qualifiedName; + prefix = ""; + localName = ""; + for (unsigned int i=0 ; i0 && namespaceURI==theNamespaceURI && + prefix.size()>0 && + originalElement->lookupNamespaceURI(prefix) == theNamespaceURI) + { + return (prefix); + } + + if ( hasAttributes() ) + { + NamedNodeMap attributes = getAttributes(); + int nrAttrs = attributes.getLength(); + for (int i=0 ; igetLocalName(); + if (attr->getPrefix() == "xmlns" && + attr->getNodeValue() == theNamespaceURI && + originalElement->lookupNamespaceURI(attrLocalName) + == theNamespaceURI) + { + return (attrLocalName); + } + } + } + + //Get ancestor element, if any + NodeImpl *ancestor = parent; + while (ancestor && ancestor->getNodeType()!= Node::ELEMENT_NODE) + ancestor = ancestor->parent; + + if ( ancestor ) + { + return ancestor->lookupNamespacePrefix(theNamespaceURI, originalElement); + } + + return DOMString(""); +} + + +/** + * + */ +NodeImpl::NodeImpl() +{ + init(); +} + + + + +/** + * + */ +NodeImpl::NodeImpl(DocumentImpl *owner) +{ + init(); + ownerDocument = owner; +} + +/** + * + */ +NodeImpl::NodeImpl(DocumentImpl *owner, const DOMString &nodeName) +{ + init(); + ownerDocument = owner; + setNodeName(nodeName); +} + +/** + * + */ +NodeImpl::NodeImpl(DocumentImpl *owner, const DOMString &theNamespaceURI, + const DOMString &qualifiedName) +{ + init(); + ownerDocument = owner; + //if (owner) + // namespaceURI = owner->stringCache(theNamespaceURI); + setNodeName(qualifiedName); +} + + + +/** + * + */ +void NodeImpl::init() +{ + nodeType = 0; //none yet + nodeValue = ""; + setNodeName(""); + namespaceURI = ""; + parent = NULL; + prev = NULL; + next = NULL; + userData = NULL; + firstChild = NULL; + lastChild = NULL; + ownerDocument = NULL; + userDataEntries = NULL; +} + +/** + * + */ +void NodeImpl::assign(const NodeImpl &other) +{ + ownerDocument = other.ownerDocument; + prefix = other.prefix; + localName = other.localName; + nodeName = other.nodeName; + nodeValue = other.nodeValue; + namespaceURI = other.namespaceURI; + attributes = other.attributes; +} + + +/** + * + */ +NodeImpl::~NodeImpl() +{ + if (userDataEntries) + delete userDataEntries; +} + + + +/*######################################################################### +## CharacterDataImpl +#########################################################################*/ + + +/** + * + */ +CharacterDataImpl::CharacterDataImpl() : NodeImpl() +{ +} + +/** + * + */ +CharacterDataImpl::CharacterDataImpl(DocumentImpl *owner, + const DOMString &theValue) : NodeImpl() +{ + ownerDocument = owner; + nodeValue = theValue; +} + +/** + * + */ +CharacterDataImpl::~CharacterDataImpl() +{ +} + +/** + * + */ +DOMString CharacterDataImpl::getData() throw(DOMException) +{ + return nodeValue; +} + +/** + * + */ +void CharacterDataImpl::setData(const DOMString& val) throw(DOMException) +{ + nodeValue = val; +} + +/** + * + */ +unsigned long CharacterDataImpl::getLength() +{ + return nodeValue.size(); +} + +/** + * + */ +DOMString CharacterDataImpl::substringData(unsigned long offset, + unsigned long count) + throw(DOMException) +{ + return nodeValue.substr(offset, count); +} + +/** + * + */ +void CharacterDataImpl::appendData(const DOMString& arg) throw(DOMException) +{ + nodeValue += arg; +} + +/** + * + */ +void CharacterDataImpl::insertData(unsigned long offset, + const DOMString& arg) + throw(DOMException) +{ + nodeValue.insert(offset, arg); +} + +/** + * + */ +void CharacterDataImpl::deleteData(unsigned long offset, + unsigned long count) + throw(DOMException) +{ + nodeValue.erase(offset, count); +} + +/** + * + */ +void CharacterDataImpl::replaceData(unsigned long offset, + unsigned long count, + const DOMString& arg) + throw(DOMException) +{ + nodeValue.replace(offset, count, arg); +} + + + + + + +/*######################################################################### +## AttrImpl +#########################################################################*/ + +/** + * + */ +DOMString AttrImpl::getName() +{ + return nodeName; +} + +/** + * + */ +bool AttrImpl::getSpecified() +{ + return (nodeValue.size() > 0); +} + +/** + * + */ +DOMString AttrImpl::getValue() +{ + return nodeValue; +} + +/** + * + */ +void AttrImpl::setValue(const DOMString& val) throw(DOMException) +{ + nodeValue = val; +} + +/** + * + */ +Element *AttrImpl::getOwnerElement() +{ + return ownerElement; +} + + +/** + * + */ +TypeInfo *AttrImpl::getSchemaTypeInfo() +{ + return NULL; +} + + +/** + * + */ +bool AttrImpl::getIsId() +{ + return (nodeName == "id"); +} + + + +//################## +//# Non-API methods +//################## + + +void AttrImpl::setOwnerElement(const Element *elem) +{ + ownerElement = (Element *)elem; +} + +/** + * + */ +AttrImpl::AttrImpl(DocumentImpl *owner, const DOMString &theName) + : NodeImpl() +{ + nodeType = ATTRIBUTE_NODE; + ownerDocument = owner; + setNodeName(theName); +} + +/** + * + */ +AttrImpl::AttrImpl(DocumentImpl *owner, + const DOMString &theNamespaceURI, + const DOMString &theQualifiedName) + : NodeImpl() +{ + nodeType = ATTRIBUTE_NODE; + ownerDocument = owner; + //if (owner) + // namespaceURI = owner->stringCache(theNamespaceURI); + setNodeName(theQualifiedName); +} + +/** + * + */ +AttrImpl::~AttrImpl() +{ +} + + + + + +/*######################################################################### +## ElementImpl +#########################################################################*/ + + +/** + * + */ +DOMString ElementImpl::getTagName() +{ + if (prefix.size() > 0) + return prefix + ":" + nodeName; + else + return nodeName; +} + +/** + * + */ +DOMString ElementImpl::getAttribute(const DOMString& name) +{ + Node *node = attributes.getNamedItem(name); + if (!node || node->getNodeType() != ATTRIBUTE_NODE) + return DOMString(""); + Attr *attr = dynamic_cast(node); + return attr->getValue(); +} + +/** + * + */ +void ElementImpl::setAttribute(const DOMString& name, + const DOMString& value) + throw(DOMException) +{ + AttrImpl *attr = new AttrImpl(ownerDocument, name); + attr->setValue(value); + attr->setOwnerElement(this); + attributes.setNamedItem(attr); +} + +/** + * + */ +void ElementImpl::removeAttribute(const DOMString& name) + throw(DOMException) +{ + attributes.removeNamedItem(name); +} + +/** + * + */ +Attr *ElementImpl::getAttributeNode(const DOMString& name) +{ + Node *node = attributes.getNamedItem(name); + if (!node || node->getNodeType() != ATTRIBUTE_NODE) + return NULL; + Attr *attr = dynamic_cast(node); + return attr; +} + +/** + * + */ +Attr *ElementImpl::setAttributeNode(Attr *attr) + throw(DOMException) +{ + attributes.setNamedItem(attr); + return attr; +} + +/** + * + */ +Attr *ElementImpl::removeAttributeNode(Attr *attr) + throw(DOMException) +{ + if (!attr) + return NULL; + attributes.removeNamedItem(attr->getName()); + return attr; +} + + +/** + * + */ +void ElementImpl::getElementsByTagNameRecursive(NodeList &list, + const DOMString& name, Element *elem) +{ + if (!elem) + return; + + if (name == elem->getTagName()) + list.add(elem); + for (Node *node = elem->getFirstChild() ; node ; node=node->getNextSibling()) + { + if (node->getNodeType() != Node::ELEMENT_NODE) + continue; + Element *childElem = dynamic_cast(node); + getElementsByTagNameRecursive(list, name, childElem); + } +} + + +/** + * + */ +NodeList ElementImpl::getElementsByTagName(const DOMString& tagName) +{ + NodeList list; + getElementsByTagNameRecursive(list, tagName, this); + return list; +} + +/** + * + */ +DOMString ElementImpl::getAttributeNS(const DOMString& namespaceURI, + const DOMString& localName) +{ + Node *node = attributes.getNamedItemNS(namespaceURI, localName); + if (!node || node->getNodeType()!=ATTRIBUTE_NODE) + return DOMString(""); + Attr *attr = dynamic_cast(node); + return attr->getValue(); +} + +/** + * + */ +void ElementImpl::setAttributeNS(const DOMString& namespaceURI, + const DOMString& qualifiedName, + const DOMString& value) + throw(DOMException) +{ + AttrImpl *attr = new AttrImpl(ownerDocument, namespaceURI, qualifiedName); + attr->setValue(value); + attr->setOwnerElement(this); + attributes.setNamedItemNS(attr); +} + +/** + * + */ +void ElementImpl::removeAttributeNS(const DOMString& namespaceURI, + const DOMString& localName) + throw(DOMException) +{ + attributes.removeNamedItemNS(namespaceURI, localName); +} + +/** + * + */ + Attr *ElementImpl::getAttributeNodeNS(const DOMString& namespaceURI, + const DOMString& localName) +{ + Node *node = attributes.getNamedItemNS(namespaceURI, localName); + if (!node || node->getNodeType() != ATTRIBUTE_NODE) + return NULL; + Attr *attr = dynamic_cast(node); + return attr; +} + +/** + * + */ +Attr *ElementImpl::setAttributeNodeNS(Attr *attr) + throw(DOMException) +{ + attributes.setNamedItemNS(attr); + return attr; +} + + +/** + * + */ +void ElementImpl::getElementsByTagNameNSRecursive(NodeList &list, + const DOMString& namespaceURI, const DOMString& tagName, Element *elem) +{ + if (!elem) + return; + + if (namespaceURI == elem->getNamespaceURI() && tagName == elem->getTagName()) + list.add(elem); + for (Node *node = elem->getFirstChild() ; node ; node=node->getNextSibling()) + { + if (node->getNodeType() != Node::ELEMENT_NODE) + continue; + Element *childElem = dynamic_cast(node); + getElementsByTagNameNSRecursive(list, namespaceURI, tagName, childElem); + } +} + +/** + * + */ +NodeList ElementImpl::getElementsByTagNameNS(const DOMString& namespaceURI, + const DOMString& localName) +{ + NodeList list; + getElementsByTagNameNSRecursive(list, namespaceURI, localName, this); + return list; +} + +/** + * + */ +bool ElementImpl::hasAttribute(const DOMString& attrName) +{ + Node *node = attributes.getNamedItem(attrName); + if (!node || node->getNodeType() != ATTRIBUTE_NODE) + return false; + return true; +} + +/** + * + */ +bool ElementImpl::hasAttributeNS(const DOMString& namespaceURI, + const DOMString& localName) +{ + Node *node = attributes.getNamedItemNS(namespaceURI, localName); + if (!node || node->getNodeType() != ATTRIBUTE_NODE) + return false; + return true; +} + +/** + * + */ +TypeInfo *ElementImpl::getSchemaTypeInto() +{ + //fixme + return NULL; +} + + +/** + * + */ +void ElementImpl::setIdAttribute(const DOMString &name, + bool isId) throw (DOMException) +{ + //fixme +} + +/** + * + */ +void ElementImpl::setIdAttributeNS(const DOMString &namespaceURI, + const DOMString &localName, + bool isId) throw (DOMException) +{ + //fixme +} + +/** + * + */ +void ElementImpl::setIdAttributeNode(const Attr *idAttr, + bool isId) throw (DOMException) +{ + //fixme +} + + +//################## +//# Non-API methods +//################## + + +/** + * + */ +ElementImpl::ElementImpl() : NodeImpl() +{ + nodeType = ELEMENT_NODE; +} + +/** + * + */ +ElementImpl::ElementImpl(DocumentImpl *owner, const DOMString &tagName) + : NodeImpl() +{ + nodeType = ELEMENT_NODE; + ownerDocument = owner; + setNodeName(tagName); +} + +/** + * + */ +ElementImpl::ElementImpl(DocumentImpl *owner, + const DOMString &theNamespaceURI, + const DOMString &qualifiedName) : + NodeImpl() +{ + nodeType = ELEMENT_NODE; + ownerDocument = owner; + setNodeName(qualifiedName); +} + +/** + * + */ +ElementImpl::~ElementImpl() +{ +} + + +/** + * + */ +void ElementImpl::normalizeNamespaces() +{ + //printf("### NORMALIZE\n"); + + NamedNodeMap attrs = getAttributes(); + + //####################################### + //# Pick up local namespace declarations + //####################################### + bindingsClear(); //Reset bindings on this node + + int nrAttrs = attrs.getLength(); + for (int i=0; igetNodeType() != Node::ATTRIBUTE_NODE) + continue; + AttrImpl *attr = dynamic_cast(attrNode); + DOMString attrNS = attr->getNamespaceURI(); + DOMString attrName = attr->getLocalName(); + DOMString attrPrefix = attr->getPrefix(); + DOMString attrValue = attr->getNodeValue(); + if (attrName != "xmlns" && attrPrefix != "xmlns") + continue; + + //is the namespace declaration is invalid? + if (attrValue == XMLNSNAME || attrName == attrPrefix) + { + // Note: The prefix xmlns is used only to declare namespace bindings and + // is by definition bound to the namespace name http://www.w3.org/2000/xmlns/. + // It must not be declared. No other prefix may be bound to this namespace name. + + //==> Report an error. + printf("normalizeNamespaces() error: Namespace %s cannot be reassigned\n", + XMLNSNAME); + + } + else + { + //==> Record the namespace declaration + attr->setNamespaceURI(XMLNSNAME); + if (attrPrefix.size() > 0) + bindingsAdd(attrPrefix, attrValue); + else + bindingsAdd("*", attrValue);//default + + } + } + + + //####################################### + //# Fixup element's namespace + //####################################### + if ( namespaceURI.size() > 0 ) + { + DOMString key = prefix; + if (key.size() == 0) + key = "*"; + DOMString binding = bindingsFind(key); + //Element's prefix/namespace pair (or default namespace, if no prefix) + // are within the scope of a binding + if ( binding == namespaceURI ) + { + //==> do nothing, declaration in scope is inherited + + // See section "B.1.1: Scope of a binding" for an example + + } + else + { + + /* + ==> Create a local namespace declaration attr for this namespace, + with Element's current prefix (or a default namespace, if + no prefix). If there's a conflicting local declaration + already present, change its value to use this namespace. + + See section "B.1.2: Conflicting namespace declaration" for an example + */ + DOMString attrName = "xmlns"; + if (prefix.size() > 0) + { + attrName.append(":"); + attrName.append(prefix); + } + setAttribute(attrName, namespaceURI); + // NOTE that this may break other nodes within this Element's + // subtree, if they're already using this prefix. + // They will be repaired when we reach them. + } + } + else // Element has no namespace URI: + { + //############################################### + //# Bob -- alter this from the specs a bit. + //# Since the XmlReader does not set namespaces, + //# do it here + //############################################### + DOMString localName = getLocalName(); + if ( localName.size()==0 ) + { + // DOM Level 1 node + /* + ==> if in process of validation against a namespace aware schema + (i.e XML Schema) report a fatal error: the processor can not recover + in this situation. + Otherwise, report an error: no namespace fixup will be performed on this node. + */ + printf("normalizeNamespaces() error: no localName\n"); + } + else + { + // Element has no pseudo-prefix + //there's a conflicting local default namespace declaration already present + if ( prefix.size()==0 ) + { + //==> change its value to use this empty namespace. + namespaceURI = bindingsFind("*"); + //setAttribute("xmlns", ""); + } + else //#BOB . I added this. + { + namespaceURI = bindingsFind(prefix); + } + // NOTE that this may break other nodes within this Element's + // subtree, if they're already using the default namespaces. + // They will be repaired when we reach them. + } + } + + + //####################################### + //# Examine and polish the attributes + //####################################### + nrAttrs = attrs.getLength(); + for (int i=0; igetNodeType() != Node::ATTRIBUTE_NODE) + continue; + Attr *attr = dynamic_cast(attrNode); + DOMString attrNS = attr->getNamespaceURI(); + DOMString attrPrefix = attr->getPrefix(); + DOMString attrValue = attr->getNodeValue(); + if (attrNS == XMLNSNAME) + continue; + + if ( attrNS.size()>0 ) //Attr[i] has a namespace URI + { + DOMString attrBinding = bindingsFind(attrPrefix); + /* + if attribute has no prefix (default namespace decl does not apply to attributes) + OR + attribute prefix is not declared + OR + conflict: attribute has a prefix that conflicts with a binding + already active in scope + */ + if ( attrPrefix.size() == 0 || attrBinding.size() == 0) + { + //namespaceURI matches an in scope declaration of one or more prefixes) + DOMString prefixForNS = lookupNamespacePrefix(attrNS, this); + if ( prefixForNS.size() > 0 ) + { + // pick the most local binding available; + // if there is more than one pick one arbitrarily + + //==> change attribute's prefix. + attr->setPrefix(prefixForNS); + } + else + { + // the current prefix is not null and it has no in scope declaration) + if ( attrPrefix.size() > 0 || attrBinding.size() == 0 ) + { + //==> declare this prefix + DOMString newAttrName = "xmlns:"; + newAttrName.append(attrPrefix); + setAttribute(newAttrName, attrNS); + bindingsAdd(attrPrefix, attrNS); + } + else + { + // find a prefix following the pattern "NS" +index (starting at 1) + // make sure this prefix is not declared in the current scope. + // create a local namespace declaration attribute + + //==> declare this prefix + char buf[16]; + sprintf(buf, "%d" , ownerDocument->namespaceIndex++); + DOMString newPrefix = "NS"; + newPrefix.append(buf); + DOMString newAttrName = "xmlns:"; + newAttrName.append(newPrefix); + setAttribute(newAttrName, attrNS); + bindingsAdd(newPrefix, attrNS); + //==> change attribute's prefix. + } + } + } + } + else // Attr has no namespace URI + { + // Attr has no localName + if ( attr->getLocalName().size() == 0 ) + { + // DOM Level 1 node + /* + ==> if in process of validation against a namespace aware schema + (i.e XML Schema) report a fatal error: the processor can not recover + in this situation. + Otherwise, report an error: no namespace fixup will be performed on this node. + */ + printf("normalizeNamespaces: no local name for attribute\n"); + } + else + { + // attr has no namespace URI and no prefix + // no action is required, since attrs don't use default + //==> do nothing + } + } + } // end for-all-Attrs + + + //####################################### + //# Recursively normalize children + //####################################### + for (Node *child=getFirstChild() ; child ; child=child->getNextSibling()) + { + if (child->getNodeType() != Node::ELEMENT_NODE) + continue; + ElementImpl *childElement = dynamic_cast(child); + childElement->normalizeNamespaces(); + } + +} + + +/*######################################################################### +## TextImpl +#########################################################################*/ + + +/** + * + */ +TextImpl::TextImpl() : CharacterDataImpl() +{ + nodeType = TEXT_NODE; + nodeName = "#text"; +} + + +/** + * + */ +TextImpl::TextImpl(DocumentImpl *owner, const DOMString &value) + : CharacterDataImpl() +{ + nodeType = TEXT_NODE; + nodeName = "#text"; + ownerDocument = owner; + nodeValue = value; +} + + +/** + * + */ +TextImpl::~TextImpl() +{ +} + +/** + * + */ +Text *TextImpl::splitText(unsigned long offset) + throw(DOMException) +{ + return NULL; +} + +/** + * + */ +bool TextImpl::getIsElementContentWhitespace() +{ + return false; +} + +/** + * + */ +DOMString TextImpl::getWholeText() +{ + return nodeValue; +} + + +/** + * + */ +Text *TextImpl::replaceWholeText(const DOMString &content) + throw(DOMException) +{ + return NULL; +} + + +/*######################################################################### +## CommentImpl +#########################################################################*/ + +/** + * + */ +CommentImpl::CommentImpl() : CharacterDataImpl() +{ + nodeType = COMMENT_NODE; + nodeName = "#comment"; +} + + +/** + * + */ +CommentImpl::CommentImpl(DocumentImpl *owner, const DOMString &value) + : CharacterDataImpl() +{ + nodeType = COMMENT_NODE; + nodeName = "#comment"; + ownerDocument = owner; + nodeValue = value; +} + + +/** + * + */ +CommentImpl::~CommentImpl() +{ +} + + + + +/*######################################################################### +## TypeInfoImpl +#########################################################################*/ + + +/** + * + */ +TypeInfoImpl::TypeInfoImpl(const DOMString &typeNamespaceArg, + const DOMString &typeNameArg, + const DerivationMethod derivationMethodArg) +{ + typeNamespace = typeNamespaceArg; + typeName = typeNameArg; + derivationMethod = derivationMethodArg; +} + + +/** + * + */ +TypeInfoImpl::~TypeInfoImpl() +{ +} + + +/** + * + */ +DOMString TypeInfoImpl::getTypeName() +{ + return typeName; +} + +/** + * + */ +DOMString TypeInfoImpl::getTypeNamespace() +{ + return typeNamespace; +} + +/** + * + */ +bool TypeInfoImpl::isDerivedFrom(const DOMString &typeNamespaceArg, + const DOMString &typeNameArg, + const DerivationMethod derivationMethodArg) +{ + if (typeNamespaceArg == typeNamespace && + typeName == typeNameArg && + derivationMethod == derivationMethodArg) + return true; + return false; +} + + + +/*######################################################################### +## UserDataHandlerImpl +#########################################################################*/ + + + +/** + * + */ +UserDataHandlerImpl::UserDataHandlerImpl() +{ +} + + +/** + * + */ +UserDataHandlerImpl::~UserDataHandlerImpl() +{ +} + +/** + * + */ +void UserDataHandlerImpl::handle(unsigned short operation, + const DOMString &key, + const DOMUserData *data, + const Node *src, + const Node *dst) +{ + //do nothing. do we need anything here? +} + + + +/*######################################################################### +## DOMErrorImpl +#########################################################################*/ + + + +/** + * + */ +DOMErrorImpl::DOMErrorImpl() +{ +} + + +/** + * + */ +DOMErrorImpl::~DOMErrorImpl() +{ +} + +/** + * + */ +unsigned short DOMErrorImpl::getSeverity() +{ + return severity; +} + +/** + * + */ +DOMString DOMErrorImpl::getMessage() +{ + return message; +} + +/** + * + */ +DOMString DOMErrorImpl::getType() +{ + return type; +} + +/** + * + */ +DOMObject *DOMErrorImpl::getRelatedException() +{ + return NULL; +} + +/** + * + */ +DOMObject *DOMErrorImpl::getRelatedData() +{ + return NULL; +} + +/** + * + */ +DOMLocator *DOMErrorImpl::getLocation() +{ + //really should fill this in + return NULL; +} + + + + +/*######################################################################### +## DOMErrorHandlerImpl +#########################################################################*/ + + + +/** + * + */ +DOMErrorHandlerImpl::DOMErrorHandlerImpl() +{ +} + + +/** + * + */ +DOMErrorHandlerImpl::~DOMErrorHandlerImpl() +{ +} + +/** + * + */ +bool DOMErrorHandlerImpl::handleError(const DOMError *error) +{ + if (!error) + return false; + return true; +} + + + + +/*######################################################################### +## DOMLocatorImpl +#########################################################################*/ + + +/** + * + */ +DOMLocatorImpl::DOMLocatorImpl() +{ +} + + +/** + * + */ +DOMLocatorImpl::~DOMLocatorImpl() +{ +} + + +/** + * + */ +long DOMLocatorImpl::getLineNumber() +{ + return lineNumber; +} + +/** + * + */ +long DOMLocatorImpl::getColumnNumber() +{ + return columnNumber; +} + +/** + * + */ +long DOMLocatorImpl::getByteOffset() +{ + return byteOffset; +} + +/** + * + */ +long DOMLocatorImpl::getUtf16Offset() +{ + return utf16Offset; +} + + +/** + * + */ +Node *DOMLocatorImpl::getRelatedNode() +{ + return relatedNode; +} + + +/** + * + */ +DOMString DOMLocatorImpl::getUri() +{ + return uri; +} + + + +/*######################################################################### +## DOMConfigurationImpl +#########################################################################*/ + + +/** + * + */ +DOMConfigurationImpl::DOMConfigurationImpl() +{ +} + + +/** + * + */ +DOMConfigurationImpl::~DOMConfigurationImpl() +{ +} + +/** + * + */ +void DOMConfigurationImpl::setParameter(const DOMString &name, + const DOMUserData *value) throw (DOMException) +{ +} + +/** + * + */ +DOMUserData *DOMConfigurationImpl::getParameter(const DOMString &name) + throw (DOMException) +{ + return NULL; +} + +/** + * + */ +bool DOMConfigurationImpl::canSetParameter(const DOMString &name, + const DOMUserData *data) +{ + return false; +} + +/** + * + */ +DOMStringList *DOMConfigurationImpl::getParameterNames() +{ + return NULL; +} + + + +/*######################################################################### +## CDATASectionImpl +#########################################################################*/ + +/** + * + */ +CDATASectionImpl::CDATASectionImpl() : TextImpl() +{ + nodeType = CDATA_SECTION_NODE; + nodeName = "#cdata-section"; +} + +/** + * + */ +CDATASectionImpl::CDATASectionImpl(DocumentImpl *owner, const DOMString &theValue) + : TextImpl() +{ + nodeType = CDATA_SECTION_NODE; + nodeName = "#cdata-section"; + ownerDocument = owner; + nodeValue = theValue; +} + + +/** + * + */ +CDATASectionImpl::~CDATASectionImpl() +{ +} + + + + + +/*######################################################################### +## DocumentTypeImpl +#########################################################################*/ + +/** + * + */ +DocumentTypeImpl::DocumentTypeImpl(const DOMString& theName, + const DOMString& thePublicId, + const DOMString& theSystemId) + : NodeImpl() +{ + nodeType = DOCUMENT_TYPE_NODE; + nodeName = theName; + publicId = thePublicId; + systemId = theSystemId; +} + +/** + * + */ +DocumentTypeImpl::~DocumentTypeImpl() +{ +} + +/** + * + */ +DOMString DocumentTypeImpl::getName() +{ + return nodeName; +} + +/** + * + */ +NamedNodeMap DocumentTypeImpl::getEntities() +{ + return entities; +} + +/** + * + */ +NamedNodeMap DocumentTypeImpl::getNotations() +{ + return notations; +} + +/** + * + */ +DOMString DocumentTypeImpl::getPublicId() +{ + return publicId; +} + +/** + * + */ +DOMString DocumentTypeImpl::getSystemId() +{ + return systemId; +} + +/** + * + */ +DOMString DocumentTypeImpl::getInternalSubset() +{ + return DOMString(""); +} + + + + + + +/*######################################################################### +## NotationImpl +#########################################################################*/ + + + +/** + * + */ +NotationImpl::NotationImpl(DocumentImpl *owner) : NodeImpl() +{ + nodeType = NOTATION_NODE; + ownerDocument = owner; +} + + +/** + * + */ +NotationImpl::~NotationImpl() +{ +} + +/** + * + */ +DOMString NotationImpl::getPublicId() +{ + return publicId; +} + +/** + * + */ +DOMString NotationImpl::getSystemId() +{ + return systemId; +} + + + + + + + + +/*######################################################################### +## EntityImpl +#########################################################################*/ + + +/** + * + */ +EntityImpl::EntityImpl() : NodeImpl() +{ + nodeType = ENTITY_NODE; +} + + +/** + * + */ +EntityImpl::EntityImpl(DocumentImpl *owner) : NodeImpl() +{ + nodeType = ENTITY_NODE; + ownerDocument = owner; +} + + +/** + * + */ +EntityImpl::~EntityImpl() +{ +} + +/** + * + */ +DOMString EntityImpl::getPublicId() +{ + return publicId; +} + +/** + * + */ +DOMString EntityImpl::getSystemId() +{ + return systemId; +} + +/** + * + */ +DOMString EntityImpl::getNotationName() +{ + return notationName; +} + +/** + * + */ +DOMString EntityImpl::getInputEncoding() +{ + return inputEncoding; +} + +/** + * + */ +DOMString EntityImpl::getXmlEncoding() +{ + return xmlEncoding; +} + +/** + * + */ +DOMString EntityImpl::getXmlVersion() +{ + return xmlVersion; +} + + + + + + +/*######################################################################### +## EntityReferenceImpl +#########################################################################*/ + + + +/** + * + */ +EntityReferenceImpl::EntityReferenceImpl() : NodeImpl() +{ + nodeType = ENTITY_REFERENCE_NODE; +} + + +/** + * + */ +EntityReferenceImpl::EntityReferenceImpl(DocumentImpl *owner, + const DOMString &theName) + : NodeImpl() +{ + nodeType = ENTITY_REFERENCE_NODE; + nodeName = theName; + ownerDocument = owner; +} + + +/** + * + */ +EntityReferenceImpl::~EntityReferenceImpl() +{ +} + + + +/*######################################################################### +## ProcessingInstructionImpl +#########################################################################*/ + + + + +/** + * + */ +ProcessingInstructionImpl::ProcessingInstructionImpl(): NodeImpl() +{ + nodeType = PROCESSING_INSTRUCTION_NODE; +} + + + +/** + * + */ +ProcessingInstructionImpl::ProcessingInstructionImpl(DocumentImpl *owner, + const DOMString &target, + const DOMString &data) + : NodeImpl() +{ + nodeType = PROCESSING_INSTRUCTION_NODE; + ownerDocument = owner; + nodeName = target; + nodeValue = data; +} + + +/** + * + */ +ProcessingInstructionImpl::~ProcessingInstructionImpl() +{ +} + + + + +/** + * + */ +DOMString ProcessingInstructionImpl::getTarget() +{ + return nodeName; +} + +/** + * + */ +DOMString ProcessingInstructionImpl::getData() +{ + return nodeValue; +} + +/** + * + */ +void ProcessingInstructionImpl::setData(const DOMString& val) throw(DOMException) +{ + //do something here +} + + + + + + + +/*######################################################################### +## DocumentFragmentImpl +#########################################################################*/ + +/** + * + */ +DocumentFragmentImpl::DocumentFragmentImpl() : NodeImpl() +{ + nodeType = DOCUMENT_FRAGMENT_NODE; + nodeName = "#document-fragment"; +} + + +/** + * + */ +DocumentFragmentImpl::DocumentFragmentImpl(DocumentImpl *owner) : NodeImpl() +{ + nodeType = DOCUMENT_FRAGMENT_NODE; + nodeName = "#document-fragment"; + ownerDocument = owner; +} + + +/** + * + */ +DocumentFragmentImpl::~DocumentFragmentImpl() +{ +} + + + + + + +/*######################################################################### +## DocumentImpl +#########################################################################*/ + + + +/** + * + */ +DocumentType *DocumentImpl::getDoctype() +{ + return doctype; +} + +/** + * + */ +DOMImplementation *DocumentImpl::getImplementation() +{ + return parent; +} + +/** + * + */ +Element *DocumentImpl::getDocumentElement() +{ + return documentElement; +} + +/** + * + */ +Element *DocumentImpl::createElement(const DOMString& tagName) + throw(DOMException) +{ + ElementImpl *impl = new ElementImpl(this, tagName); + return impl; +} + +/** + * + */ +DocumentFragment *DocumentImpl::createDocumentFragment() +{ + DocumentFragmentImpl *frag = new DocumentFragmentImpl(this); + return frag; +} + +/** + * + */ +Text *DocumentImpl::createTextNode(const DOMString& data) +{ + TextImpl *text = new TextImpl(this, data); + return text; +} + +/** + * + */ +Comment *DocumentImpl::createComment(const DOMString& data) +{ + CommentImpl *comment = new CommentImpl(this, data); + return comment; +} + +/** + * + */ +CDATASection *DocumentImpl::createCDATASection(const DOMString& data) + throw(DOMException) +{ + CDATASectionImpl *cdata = new CDATASectionImpl(this, data); + return cdata; +} + +/** + * + */ +ProcessingInstruction *DocumentImpl::createProcessingInstruction(const DOMString& target, + const DOMString& data) + throw(DOMException) +{ + ProcessingInstructionImpl *cdata = + new ProcessingInstructionImpl(this, target, data); + return cdata; +} + +/** + * + */ +Attr *DocumentImpl::createAttribute(const DOMString& attrName) + throw(DOMException) +{ + AttrImpl *attr = new AttrImpl(this, attrName); + return attr; +} + +/** + * + */ +EntityReference *DocumentImpl::createEntityReference(const DOMString& erName) + throw(DOMException) +{ + EntityReferenceImpl *ref = new EntityReferenceImpl(this, erName); + return ref; +} + + +/** + * + */ +NodeList DocumentImpl::getElementsByTagName(const DOMString& tagname) +{ + NodeList list; + ElementImpl::getElementsByTagNameRecursive(list, + tagname, documentElement); + return list; +} + + +/** + * + */ +Node *DocumentImpl::importNode(const Node *importedNode, + bool deep) + throw(DOMException) +{ + return NULL; +} + +/** + * + */ +Element *DocumentImpl::createElementNS(const DOMString& namespaceURI, + const DOMString& qualifiedName) + throw(DOMException) +{ + ElementImpl *elem = new ElementImpl(this, namespaceURI, qualifiedName); + return elem; +} + +/** + * + */ +Attr *DocumentImpl::createAttributeNS(const DOMString& namespaceURI, + const DOMString& qualifiedName) + throw(DOMException) +{ + AttrImpl *attr = new AttrImpl(this, namespaceURI, qualifiedName); + return attr; +} + + +/** + * + */ +NodeList DocumentImpl::getElementsByTagNameNS(const DOMString& namespaceURI, + const DOMString& localName) +{ + NodeList list; + ElementImpl::getElementsByTagNameNSRecursive(list, namespaceURI, + localName, documentElement); + return list; +} + +/** + * + */ +Element *DocumentImpl::getElementById(const DOMString& elementId) +{ + for (NamedElementItem *entry = elementsById.next; entry ; entry=entry->next) + if (entry->name == elementId) + return entry->elem; + return NULL; +} + + +/** + * + */ +DOMString DocumentImpl::getInputEncoding() +{ + return inputEncoding; +} + + +/** + * + */ +DOMString DocumentImpl::getXmlEncoding() +{ + return xmlEncoding; +} + +/** + * + */ +bool DocumentImpl::getXmlStandalone() +{ + return xmlStandalone; +} + +/** + * + */ +void DocumentImpl::setXmlStandalone(bool val) throw (DOMException) +{ + xmlStandalone = val; +} + +/** + * + */ +DOMString DocumentImpl::getXmlVersion() +{ + return xmlVersion; +} + +/** + * + */ +void DocumentImpl::setXmlVersion(const DOMString &version) throw (DOMException) +{ + xmlVersion = version; +} + +/** + * + */ +bool DocumentImpl::getStrictErrorChecking() +{ + return strictErrorChecking; +} + +/** + * + */ +void DocumentImpl::setStrictErrorChecking(bool val) +{ + strictErrorChecking = val; +} + + +/** + * + */ +DOMString DocumentImpl::getDocumentURI() +{ + if (!documentURI) + return DOMString(""); + DOMString docURI = *documentURI; + return docURI; +} + +/** + * + */ +void DocumentImpl::setDocumentURI(const DOMString &uri) +{ + //documentURI = stringCache(uri); +} + +/** + * + */ +Node *DocumentImpl::adoptNode(const Node *source) throw (DOMException) +{ + return (Node *)source; +} + +/** + * + */ +DOMConfiguration *DocumentImpl::getDomConfig() +{ + return domConfig; +} + +/** + * + */ +void DocumentImpl::normalizeDocument() +{ + //i assume that this means adjusting namespaces & prefixes + if (documentElement) + documentElement->normalizeNamespaces(); +} + +/** + * + */ +Node *DocumentImpl::renameNode(const Node *n, + const DOMString &namespaceURI, + const DOMString &qualifiedName) + throw (DOMException) +{ + Node *node = (Node *) n; + NodeImpl *nodeImpl = dynamic_cast (node); + //nodeImpl->namespaceURI = stringCache(namespaceURI); + nodeImpl->setNodeName(qualifiedName); + return node; +} + + + +//################## +//# Non-API methods +//################## + +/** + * + */ +DocumentImpl::DocumentImpl(const DOMImplementation *domImpl, + const DOMString &theNamespaceURI, + const DOMString &theQualifiedName, + const DocumentType *theDoctype) : NodeImpl() +{ + nodeType = DOCUMENT_NODE; + nodeName = "#document"; + parent = (DOMImplementation *)domImpl; + //documentURI = stringCache(theNamespaceURI); + qualifiedName = theQualifiedName; + if (theDoctype) //only assign if not null. + doctype = (DocumentType *)theDoctype; + else + doctype = new DocumentTypeImpl("", "", ""); + documentElement = new ElementImpl(this, "root"); + namespaceIndex = 0; +} + + +/** + * + */ +DocumentImpl::~DocumentImpl() +{ + delete documentElement; +} + + + + + + + + + + + + +} //namespace dom +} //namespace w3c +} //namespace org + + + +/*######################################################################### +## E N D O F F I L E +#########################################################################*/ + + + + diff --git a/src/dom/domimpl.h b/src/dom/domimpl.h new file mode 100755 index 000000000..677f5eb8c --- /dev/null +++ b/src/dom/domimpl.h @@ -0,0 +1,2002 @@ +#ifndef __DOMIMPL_H__ +#define __DOMIMPL_H__ +/** + * Phoebe DOM Implementation. + * + * This is a C++ approximation of the W3C DOM model, which follows + * fairly closely the specifications in the various .idl files, copies of + * which are provided for reference. Most important is this one: + * + * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/idl-definitions.html + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2005 Bob Jamison + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include "dom.h" + +#include + +namespace org +{ +namespace w3c +{ +namespace dom +{ + + + +class DOMImplementationSourceImpl; +class DOMImplementationImpl; +class NodeImpl; +class CharacterDataImpl; +class AttrImpl; +class ElementImpl; +class TextImpl; +class CommentImpl; +class TypeInfoImpl; +class UserDataHandlerImpl; +class DOMErrorImpl; +class DOMErrorHandlerImpl; +class DOMLocatorImpl; +class DOMConfigurationImpl; +class CDATASectionImpl; +class DocumentTypeImpl; +class NotationImpl; +class EntityImpl; +class EntityReferenceImpl; +class ProcessingInstructionImpl; +class DocumentFragmentImpl; +class DocumentImpl; + + + +/*######################################################################### +## DOMImplementationSourceImpl +#########################################################################*/ + +class DOMImplementationSourceImpl : public DOMImplementationSource +{ +public: + + /** + * + */ + virtual DOMImplementation *getDOMImplementation(const DOMString &features); + + /** + * + */ + virtual DOMImplementationList getDOMImplementationList(const DOMString &features); + + + //################## + //# Non-API methods + //################## + + /** + * + */ + DOMImplementationSourceImpl(); + + /** + * + */ + virtual ~DOMImplementationSourceImpl(); + +protected: + + + DOMImplementationImpl *domImpl; + DOMImplementationList domImplList; +}; + + + + + +/*######################################################################### +## DOMImplementationImpl +#########################################################################*/ +/** + * + */ +class DOMImplementationImpl : public DOMImplementation +{ +public: + + + /** + * + */ + DOMImplementationImpl(); + + /** + * + */ + virtual ~DOMImplementationImpl(); + + /** + * + */ + virtual bool hasFeature(const DOMString& feature, const DOMString& version); + + + /** + * + */ + virtual DocumentType *createDocumentType(const DOMString& qualifiedName, + const DOMString& publicId, + const DOMString& systemId) + throw(DOMException); + + /** + * + */ + virtual Document *createDocument(const DOMString& namespaceURI, + const DOMString& qualifiedName, + DocumentType *doctype) + throw(DOMException); + /** + * + */ + virtual DOMObject *getFeature(const DOMString& feature, + const DOMString& version); + + +protected: + +}; + + + + +/*######################################################################### +## NodeImpl +#########################################################################*/ + +/** + * + */ +class NodeImpl : virtual public Node +{ + + friend class DocumentImpl; + +public: + + /** + * + */ + virtual DOMString getNodeName(); + + /** + * + */ + virtual DOMString getNodeValue() throw (DOMException); + + /** + * + */ + virtual void setNodeValue(const DOMString& val) throw (DOMException); + + /** + * + */ + virtual unsigned short getNodeType(); + + /** + * + */ + virtual Node *getParentNode(); + + /** + * + */ + virtual NodeList getChildNodes(); + + /** + * + */ + virtual Node *getFirstChild(); + + /** + * + */ + virtual Node *getLastChild(); + + /** + * + */ + virtual Node *getPreviousSibling(); + + /** + * + */ + virtual Node *getNextSibling(); + + /** + * + */ + virtual NamedNodeMap &getAttributes(); + + + /** + * + */ + virtual Document *getOwnerDocument(); + + /** + * + */ + virtual Node *insertBefore(const Node *newChild, + const Node *refChild) + throw(DOMException); + + /** + * + */ + virtual Node *replaceChild(const Node *newChild, + const Node *oldChild) + throw(DOMException); + + /** + * + */ + virtual Node *removeChild(const Node *oldChild) + throw(DOMException); + + /** + * + */ + virtual Node *appendChild(const Node *newChild) + throw(DOMException); + + /** + * + */ + virtual bool hasChildNodes(); + + /** + * + */ + virtual Node *cloneNode(bool deep); + + /** + * + */ + virtual void normalize(); + + /** + * + */ + virtual bool isSupported(const DOMString& feature, + const DOMString& version); + + /** + * + */ + virtual DOMString getNamespaceURI(); + + /** + * + */ + virtual DOMString getPrefix(); + + /** + * + */ + virtual void setPrefix(const DOMString& val) throw(DOMException); + + /** + * + */ + virtual DOMString getLocalName(); + + /** + * + */ + virtual bool hasAttributes(); + + /** + * + */ + virtual DOMString getBaseURI(); + + /** + * + */ + virtual unsigned short compareDocumentPosition(const Node *other); + + /** + * + */ + virtual DOMString getTextContext() throw(DOMException); + + + /** + * + */ + virtual void setTextContext(const DOMString &val) throw(DOMException); + + + /** + * + */ + virtual DOMString lookupPrefix(const DOMString &namespaceURI); + + + /** + * + */ + virtual bool isDefaultNamespace(const DOMString &namespaceURI); + + + /** + * + */ + virtual DOMString lookupNamespaceURI(const DOMString &prefix); + + + /** + * + */ + virtual bool isEqualNode(const Node *node); + + + + /** + * + */ + virtual DOMObject *getFeature(const DOMString &feature, + const DOMString &version); + + /** + * + */ + virtual DOMUserData *setUserData(const DOMString &key, + const DOMUserData *data, + const UserDataHandler *handler); + + + /** + * + */ + virtual DOMUserData *getUserData(const DOMString &namespaceURI); + + + //################## + //# Non-API methods + //################## + + /** + * + */ + virtual void bindingsAdd(const DOMString &prefix, const DOMString &namespaceURI) + { + bindings[prefix] = namespaceURI; + } + + /** + * + */ + virtual void bindingsClear() + { + bindings.clear(); + } + + DOMString bindingsFind(const DOMString &prefix) + { + std::map::iterator iter = + bindings.find(prefix); + if (iter != bindings.end()) + { + DOMString ret = iter->second; + return ret; + } + if (parent) + { + DOMString ret = parent->bindingsFind(prefix); + if (ret.size() > 0) + return ret; + } + return ""; + } + + /** + * + */ + virtual void setNodeName(const DOMString &qualifiedName); + + /** + * + */ + virtual void setNamespaceURI(const DOMString &theNamespaceURI); + + /** + * + */ + DOMString lookupNamespacePrefix(const DOMString &namespaceURI, + Node *originalElement); + /** + * + */ + NodeImpl(); + + /** + * + */ + NodeImpl(DocumentImpl *owner); + + /** + * + */ + NodeImpl(DocumentImpl *owner, const DOMString &nodeName); + + /** + * + */ + NodeImpl(DocumentImpl *owner, const DOMString &namespaceURI, const DOMString &nodeName); + + /** + * + */ + virtual ~NodeImpl(); + + + /** + * + */ + void assign(const NodeImpl &other); + +protected: + + /** + * Set up the internal values + */ + void init(); + + unsigned short nodeType; + + NodeImpl *parent; + + NodeImpl *prev; + + NodeImpl *next; + + DOMUserData *userData; + + DOMString prefix; + + DOMString localName; + + DOMString nodeName; + + DOMString namespaceURI; + + DOMString baseURI; + + DOMString nodeValue; + + NodeImpl *firstChild; + NodeImpl *lastChild; + + DocumentImpl *ownerDocument; + + NamedNodeMap attributes; + + class UserDataEntry + { + public: + UserDataEntry(const DOMString &theKey, + const DOMUserData *theData, + const UserDataHandler *theHandler) + { + next = NULL; + key = theKey; + data = (DOMUserData *)theData; + handler = (UserDataHandler *)theHandler; + } + ~UserDataEntry() + { + //delete anything after me, too + if (next) + delete next; + } + + UserDataEntry *next; + DOMString key; + DOMUserData *data; + UserDataHandler *handler; + }; + + UserDataEntry *userDataEntries; + + //### Our prefix->namespaceURI bindings + + std::map bindings; + + +}; + + + +/*######################################################################### +## CharacterDataImpl +#########################################################################*/ + +/** + * + */ +class CharacterDataImpl : virtual public CharacterData, protected NodeImpl +{ +public: + + /** + * + */ + virtual DOMString getData() throw(DOMException); + + /** + * + */ + virtual void setData(const DOMString& val) throw(DOMException); + + /** + * + */ + virtual unsigned long getLength(); + + /** + * + */ + virtual DOMString substringData(unsigned long offset, + unsigned long count) + throw(DOMException); + + /** + * + */ + virtual void appendData(const DOMString& arg) throw(DOMException); + + /** + * + */ + virtual void insertData(unsigned long offset, + const DOMString& arg) + throw(DOMException); + + /** + * + */ + virtual void deleteData(unsigned long offset, + unsigned long count) + throw(DOMException); + + /** + * + */ + virtual void replaceData(unsigned long offset, + unsigned long count, + const DOMString& arg) + throw(DOMException); + + + //################## + //# Non-API methods + //################## + + /** + * + */ + CharacterDataImpl(); + + + /** + * + */ + CharacterDataImpl(DocumentImpl *owner, const DOMString &value); + + /** + * + */ + virtual ~CharacterDataImpl(); + +protected: + + //'data' is the nodeValue + +}; + + + + + +/*######################################################################### +## AttrImpl +#########################################################################*/ + +/** + * + */ +class AttrImpl : virtual public Attr, public NodeImpl +{ +public: + + /** + * + */ + virtual DOMString getName(); + + /** + * + */ + virtual bool getSpecified(); + + /** + * + */ + virtual DOMString getValue(); + + /** + * + */ + virtual void setValue(const DOMString& val) throw(DOMException); + + /** + * + */ + virtual Element *getOwnerElement(); + + + /** + * + */ + virtual TypeInfo *getSchemaTypeInfo(); + + + /** + * + */ + virtual bool getIsId(); + + + + //################## + //# Non-API methods + //################## + + /** + * + */ + virtual void setOwnerElement(const Element *elem); + + /** + * + */ + AttrImpl(DocumentImpl *owner, const DOMString &name); + + /** + * + */ + AttrImpl(DocumentImpl *owner, const DOMString &namespaceURI, const DOMString &name); + + /** + * + */ + virtual ~AttrImpl(); + +protected: + + + Element *ownerElement; + + +}; + + + + + +/*######################################################################### +## ElementImpl +#########################################################################*/ + +/** + * + */ +class ElementImpl : virtual public Element, public NodeImpl +{ +public: + + /** + * + */ + virtual DOMString getTagName(); + + /** + * + */ + virtual DOMString getAttribute(const DOMString& name); + + /** + * + */ + virtual void setAttribute(const DOMString& name, + const DOMString& value) + throw(DOMException); + + /** + * + */ + virtual void removeAttribute(const DOMString& name) + throw(DOMException); + + /** + * + */ + virtual Attr *getAttributeNode(const DOMString& name); + + /** + * + */ + virtual Attr *setAttributeNode(Attr *newAttr) + throw(DOMException); + + /** + * + */ + virtual Attr *removeAttributeNode(Attr *oldAttr) + throw(DOMException); + + /** + * + */ + virtual NodeList getElementsByTagName(const DOMString& name); + + /** + * + */ + virtual DOMString getAttributeNS(const DOMString& namespaceURI, + const DOMString& localName); + + /** + * + */ + virtual void setAttributeNS(const DOMString& namespaceURI, + const DOMString& qualifiedName, + const DOMString& value) + throw(DOMException); + + /** + * + */ + virtual void removeAttributeNS(const DOMString& namespaceURI, + const DOMString& localName) + throw(DOMException); + + /** + * + */ + virtual Attr *getAttributeNodeNS(const DOMString& namespaceURI, + const DOMString& localName); + + /** + * + */ + virtual Attr *setAttributeNodeNS(Attr *newAttr) + throw(DOMException); + + /** + * + */ + virtual NodeList getElementsByTagNameNS(const DOMString& namespaceURI, + const DOMString& localName); + + /** + * + */ + virtual bool hasAttribute(const DOMString& name); + + /** + * + */ + virtual bool hasAttributeNS(const DOMString& namespaceURI, + const DOMString& localName); + + /** + * + */ + virtual TypeInfo *getSchemaTypeInto(); + + + /** + * + */ + virtual void setIdAttribute(const DOMString &name, + bool isId) throw (DOMException); + + /** + * + */ + virtual void setIdAttributeNS(const DOMString &namespaceURI, + const DOMString &localName, + bool isId) throw (DOMException); + + /** + * + */ + virtual void setIdAttributeNode(const Attr *idAttr, + bool isId) throw (DOMException); + + + + //################## + //# Non-API methods + //################## + + + /** + * + */ + ElementImpl(); + + /** + * + */ + ElementImpl(DocumentImpl *owner, const DOMString &tagName); + + /** + * + */ + ElementImpl(DocumentImpl *owner, const DOMString &namespaceURI, const DOMString &tagName); + + /** + * + */ + virtual ~ElementImpl(); + + /** + * + */ + void normalizeNamespaces(); + +protected: + +friend class DocumentImpl; + + static void getElementsByTagNameRecursive(NodeList &list, + const DOMString& name, Element *elem); + static void getElementsByTagNameNSRecursive(NodeList &list, + const DOMString& namespaceURI, const DOMString& tagName, Element *elem); +}; + + + + + +/*######################################################################### +## TextImpl +#########################################################################*/ + +/** + * + */ +class TextImpl : virtual public Text, protected CharacterDataImpl +{ +public: + + /** + * + */ + virtual Text *splitText(unsigned long offset) + throw(DOMException); + + /** + * + */ + virtual bool getIsElementContentWhitespace(); + + /** + * + */ + virtual DOMString getWholeText(); + + + /** + * + */ + virtual Text *replaceWholeText(const DOMString &content) + throw(DOMException); + + //################## + //# Non-API methods + //################## + + /** + * + */ + TextImpl(); + + + /** + * + */ + TextImpl(DocumentImpl *owner, const DOMString &val); + + /** + * + */ + virtual ~TextImpl(); + +protected: + +}; + + + +/*######################################################################### +## CommentImpl +#########################################################################*/ + +/** + * + */ +class CommentImpl : virtual public Comment, protected CharacterDataImpl +{ +public: + + //################## + //# Non-API methods + //################## + + /** + * + */ + CommentImpl(); + + /** + * + */ + CommentImpl(DocumentImpl *owner, const DOMString &theValue); + + /** + * + */ + virtual ~CommentImpl(); +}; + + + +/*######################################################################### +## TypeInfoImpl +#########################################################################*/ + +/** + * + */ +class TypeInfoImpl : public TypeInfo +{ +public: + + /** + * + */ + virtual DOMString getTypeName(); + + /** + * + */ + virtual DOMString getTypeNamespace(); + + /** + * + */ + virtual bool isDerivedFrom(const DOMString &typeNamespaceArg, + const DOMString &typeNameArg, + const DerivationMethod derivationMethod); + + + //################## + //# Non-API methods + //################## + + + /** + * + */ + TypeInfoImpl(const DOMString &typeNamespaceArg, + const DOMString &typeNameArg, + const DerivationMethod derivationMethod); + + /** + * + */ + virtual ~TypeInfoImpl(); + +protected: + + DOMString typeName; + + DOMString typeNamespace; + + unsigned short derivationMethod; + +}; + + + + +/*######################################################################### +## UserDataHandlerImpl +#########################################################################*/ + +/** + * + */ +class UserDataHandlerImpl : public UserDataHandler +{ +public: + + /** + * + */ + virtual void handle(unsigned short operation, + const DOMString &key, + const DOMUserData *data, + const Node *src, + const Node *dst); + + //################## + //# Non-API methods + //################## + + +protected: + + /** + * + */ + UserDataHandlerImpl(); + + /** + * + */ + virtual ~UserDataHandlerImpl(); +}; + + +/*######################################################################### +## DOMErrorImpl +#########################################################################*/ + +/** + * + */ +class DOMErrorImpl : public DOMError +{ +public: + + /** + * + */ + virtual unsigned short getSeverity(); + + /** + * + */ + virtual DOMString getMessage(); + + /** + * + */ + virtual DOMString getType(); + + /** + * + */ + virtual DOMObject *getRelatedException(); + + /** + * + */ + virtual DOMObject *getRelatedData(); + + /** + * + */ + virtual DOMLocator *getLocation(); + + + //################## + //# Non-API methods + //################## + + +protected: + + /** + * + */ + DOMErrorImpl(); + + /** + * + */ + virtual ~DOMErrorImpl(); + + unsigned short severity; + + DOMString message; + + DOMString type; + + +}; + + +/*######################################################################### +## DOMErrorHandlerImpl +#########################################################################*/ + +/** + * + */ +class DOMErrorHandlerImpl : public DOMErrorHandler +{ +public: + + /** + * + */ + virtual bool handleError(const DOMError *error); + + + + //################## + //# Non-API methods + //################## + + + +protected: + + /** + * + */ + DOMErrorHandlerImpl(); + + /** + * + */ + virtual ~DOMErrorHandlerImpl(); + + +}; + + + +/*######################################################################### +## DOMLocatorImpl +#########################################################################*/ + +/** + * + */ +class DOMLocatorImpl : public DOMLocator +{ +public: + + /** + * + */ + virtual long getLineNumber(); + + /** + * + */ + virtual long getColumnNumber(); + + /** + * + */ + virtual long getByteOffset(); + + /** + * + */ + virtual long getUtf16Offset(); + + + /** + * + */ + virtual Node *getRelatedNode(); + + + /** + * + */ + virtual DOMString getUri(); + + + + //################## + //# Non-API methods + //################## + + + /** + * + */ + DOMLocatorImpl(); + + /** + * + */ + virtual ~DOMLocatorImpl(); + +protected: + + + long lineNumber; + + long columnNumber; + + long byteOffset; + + long utf16Offset; + + Node *relatedNode; + + DOMString uri; +}; + + +/*######################################################################### +## DOMConfigurationImpl +#########################################################################*/ + +/** + * + */ +class DOMConfigurationImpl : public DOMConfiguration +{ +public: + + /** + * + */ + virtual void setParameter(const DOMString &name, + const DOMUserData *value) throw (DOMException); + + /** + * + */ + virtual DOMUserData *getParameter(const DOMString &name) + throw (DOMException); + + /** + * + */ + virtual bool canSetParameter(const DOMString &name, + const DOMUserData *data); + + /** + * + */ + virtual DOMStringList *getParameterNames(); + + + //################## + //# Non-API methods + //################## + + /** + * + */ + DOMConfigurationImpl(); + + /** + * + */ + virtual ~DOMConfigurationImpl(); + +protected: + +}; + + + + + + +/*######################################################################### +## CDATASectionImpl +#########################################################################*/ +/** + * + */ +class CDATASectionImpl : public CDATASection, public TextImpl +{ +public: + + //################## + //# Non-API methods + //################## + + /** + * + */ + CDATASectionImpl(); + + + /** + * + */ + CDATASectionImpl(DocumentImpl *owner, const DOMString &value); + + /** + * + */ + virtual ~CDATASectionImpl(); + +}; + + + + +/*######################################################################### +## DocumentTypeImpl +#########################################################################*/ + +/** + * + */ +class DocumentTypeImpl : public DocumentType, public NodeImpl +{ +public: + + /** + * + */ + virtual DOMString getName(); + + /** + * + */ + virtual NamedNodeMap getEntities(); + + /** + * + */ + virtual NamedNodeMap getNotations(); + + /** + * + */ + virtual DOMString getPublicId(); + + /** + * + */ + virtual DOMString getSystemId(); + + /** + * + */ + virtual DOMString getInternalSubset(); + + //################## + //# Non-API methods + //################## + + /** + * + */ + DocumentTypeImpl(); + + /** + * + */ + DocumentTypeImpl(const DOMString& name, + const DOMString& publicId, + const DOMString& systemId); + /** + * + */ + virtual ~DocumentTypeImpl(); + + +protected: + DOMString name; + DOMString publicId; + DOMString systemId; + + NamedNodeMap entities; + NamedNodeMap notations; + +}; + + + + + +/*######################################################################### +## NotationImpl +#########################################################################*/ + +/** + * + */ +class NotationImpl : public Notation, public NodeImpl +{ +public: + + /** + * + */ + virtual DOMString getPublicId(); + + /** + * + */ + virtual DOMString getSystemId(); + + + //################## + //# Non-API methods + //################## + + /** + * + */ + NotationImpl(); + + /** + * + */ + NotationImpl(DocumentImpl *owner); + + /** + * + */ + virtual ~NotationImpl(); + + +protected: + + + + DOMString publicId; + + DOMString systemId; +}; + + + + + + +/*######################################################################### +## EntityImpl +#########################################################################*/ + +/** + * + */ +class EntityImpl : public Entity, public NodeImpl +{ +public: + + /** + * + */ + virtual DOMString getPublicId(); + + /** + * + */ + virtual DOMString getSystemId(); + + /** + * + */ + virtual DOMString getNotationName(); + + /** + * + */ + virtual DOMString getInputEncoding(); + + /** + * + */ + virtual DOMString getXmlEncoding(); + + /** + * + */ + virtual DOMString getXmlVersion(); + + + //################## + //# Non-API methods + //################## + + /** + * + */ + EntityImpl(); + + + /** + * + */ + EntityImpl(DocumentImpl *owner); + + /** + * + */ + virtual ~EntityImpl(); + +protected: + + + + DOMString publicId; + + DOMString systemId; + + DOMString notationName; + + DOMString inputEncoding; + + DOMString xmlEncoding; + + DOMString xmlVersion; + +}; + + + + + +/*######################################################################### +## EntityReferenceImpl +#########################################################################*/ +/** + * + */ +class EntityReferenceImpl : public EntityReference, public NodeImpl +{ +public: + + //################## + //# Non-API methods + //################## + + /** + * + */ + EntityReferenceImpl(); + + + /** + * + */ + EntityReferenceImpl(DocumentImpl *owner, const DOMString &theName); + + /** + * + */ + virtual ~EntityReferenceImpl(); + +}; + + + + + +/*######################################################################### +## ProcessingInstructionImpl +#########################################################################*/ + +/** + * + */ +class ProcessingInstructionImpl : public ProcessingInstruction, public NodeImpl +{ +public: + + /** + * + */ + virtual DOMString getTarget(); + + /** + * + */ + virtual DOMString getData(); + + /** + * + */ + virtual void setData(const DOMString& val) throw(DOMException); + + + //################## + //# Non-API methods + //################## + + + /** + * + */ + ProcessingInstructionImpl(); + + + /** + * + */ + ProcessingInstructionImpl(DocumentImpl *owner, + const DOMString &target, + const DOMString &data); + + /** + * + */ + virtual ~ProcessingInstructionImpl(); + + +protected: + + + //'target' is nodeName + + //'data' is nodeValue + + +}; + + + + + +/*######################################################################### +## DocumentFragmentImpl +#########################################################################*/ +/** + * + */ +class DocumentFragmentImpl : public DocumentFragment, public NodeImpl +{ + +public: + + //################## + //# Non-API methods + //################## + + /** + * + */ + DocumentFragmentImpl(); + + /** + * + */ + DocumentFragmentImpl(DocumentImpl *owner); + + /** + * + */ + virtual ~DocumentFragmentImpl(); + +}; + + + + + + +/*######################################################################### +## DocumentImpl +#########################################################################*/ + +/** + * + */ +class DocumentImpl : virtual public Document, public NodeImpl +{ +public: + + /** + * + */ + virtual DocumentType *getDoctype(); + + /** + * + */ + virtual DOMImplementation *getImplementation(); + + /** + * + */ + virtual Element *getDocumentElement(); + + /** + * + */ + virtual Element *createElement(const DOMString& tagName) + throw(DOMException); + + /** + * + */ + virtual DocumentFragment *createDocumentFragment(); + + /** + * + */ + virtual Text *createTextNode(const DOMString& data); + + /** + * + */ + virtual Comment *createComment(const DOMString& data); + + /** + * + */ + virtual CDATASection *createCDATASection(const DOMString& data) + throw(DOMException); + + /** + * + */ + virtual ProcessingInstruction *createProcessingInstruction(const DOMString& target, + const DOMString& data) + throw(DOMException); + + /** + * + */ + virtual Attr *createAttribute(const DOMString& name) + throw(DOMException); + + /** + * + */ + virtual EntityReference *createEntityReference(const DOMString& name) + throw(DOMException); + + /** + * + */ + virtual NodeList getElementsByTagName(const DOMString& tagname); + + + /** + * + */ + virtual Node *importNode(const Node *importedNode, + bool deep) + throw(DOMException); + + /** + * + */ + virtual Element *createElementNS(const DOMString& namespaceURI, + const DOMString& qualifiedName) + throw(DOMException); + + /** + * + */ + virtual Attr *createAttributeNS(const DOMString& namespaceURI, + const DOMString& qualifiedName) + throw(DOMException); + + /** + * + */ + virtual NodeList getElementsByTagNameNS(const DOMString& namespaceURI, + const DOMString& localName); + + /** + * + */ + virtual Element *getElementById(const DOMString& elementId); + + + /** + * + */ + virtual DOMString getInputEncoding(); + + + /** + * + */ + virtual DOMString getXmlEncoding(); + + /** + * + */ + virtual bool getXmlStandalone(); + + /** + * + */ + virtual void setXmlStandalone(bool val) throw (DOMException); + + /** + * + */ + virtual DOMString getXmlVersion(); + + /** + * + */ + virtual void setXmlVersion(const DOMString &version) throw (DOMException); + + /** + * + */ + virtual bool getStrictErrorChecking(); + + /** + * + */ + virtual void setStrictErrorChecking(bool val); + + + /** + * + */ + virtual DOMString getDocumentURI(); + + /** + * + */ + virtual void setDocumentURI(const DOMString &uri); + + /** + * + */ + virtual Node *adoptNode(const Node *source) throw (DOMException); + + /** + * + */ + virtual DOMConfiguration *getDomConfig(); + + /** + * + */ + virtual void normalizeDocument(); + + /** + * + */ + virtual Node *renameNode(const Node *n, + const DOMString &name, + const DOMString &qualifiedName) + throw (DOMException); + + + //################## + //# Non-API methods + //################## + + DocumentImpl(const DOMImplementation *domImpl, + const DOMString &namespaceURI, + const DOMString &qualifiedName, + const DocumentType *doctype); + + virtual ~DocumentImpl(); + + + DOMString *stringCache(const DOMString &val); + + int namespaceIndex; + +protected: + + DOMImplementation *parent; + + DOMString *documentURI; + + DOMString qualifiedName; + + DocumentType *doctype; + + ElementImpl *documentElement; + + class NamedElementItem + { + public: + NamedElementItem() + { + next = NULL; + } + NamedElementItem(const DOMString &nameArg, Element *elemArg) + { + next = NULL; + name = nameArg; + elem = elemArg; + } + ~NamedElementItem() + { + if (next) + delete next; + } + NamedElementItem *next; + DOMString name; + Element *elem; + }; + + NamedElementItem elementsById; + + + DOMString xmlEncoding; + + DOMString inputEncoding; + + DOMString xmlVersion; + + bool xmlStandalone; + + bool strictErrorChecking; + + DOMConfiguration *domConfig; + + NamedNodeMap namespaceURIs; + + +}; + + + + + + + + + + + +} //namespace dom +} //namespace w3c +} //namespace org + + +#endif // __DOMIMPL_H__ + + +/*######################################################################### +## E N D O F F I L E +#########################################################################*/ + + + + diff --git a/src/dom/domstream.cpp b/src/dom/domstream.cpp new file mode 100755 index 000000000..1129a7f46 --- /dev/null +++ b/src/dom/domstream.cpp @@ -0,0 +1,874 @@ +/** + * Phoebe DOM Implementation. + * + * This is a C++ approximation of the W3C DOM model, which follows + * fairly closely the specifications in the various .idl files, copies of + * which are provided for reference. Most important is this one: + * + * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/idl-definitions.html + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2005 Bob Jamison + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * Our base input/output stream classes. These are is directly + * inherited from iostreams, and includes any extra + * functionality that we might need. + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2004 Inkscape.org + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +#include "domstream.h" + +namespace org +{ +namespace w3c +{ +namespace dom +{ + + +//######################################################################### +//# U T I L I T Y +//######################################################################### + +void pipeStream(InputStream &source, OutputStream &dest) +{ + for (;;) + { + int ch = source.get(); + if (ch<0) + break; + dest.put(ch); + } + dest.flush(); +} + +//######################################################################### +//# B A S I C I N P U T S T R E A M +//######################################################################### + + +/** + * + */ +BasicInputStream::BasicInputStream(const InputStream &sourceStream) + : source((InputStream &)sourceStream) +{ + closed = false; +} + +/** + * Returns the number of bytes that can be read (or skipped over) from + * this input stream without blocking by the next caller of a method for + * this input stream. + */ +int BasicInputStream::available() +{ + if (closed) + return 0; + return source.available(); +} + + +/** + * Closes this input stream and releases any system resources + * associated with the stream. + */ +void BasicInputStream::close() +{ + if (closed) + return; + source.close(); + closed = true; +} + +/** + * Reads the next byte of data from the input stream. -1 if EOF + */ +int BasicInputStream::get() +{ + if (closed) + return -1; + return source.get(); +} + + + +//######################################################################### +//# B A S I C O U T P U T S T R E A M +//######################################################################### + +/** + * + */ +BasicOutputStream::BasicOutputStream(const OutputStream &destinationStream) + : destination((OutputStream &)destinationStream) +{ + closed = false; +} + +/** + * Closes this output stream and releases any system resources + * associated with this stream. + */ +void BasicOutputStream::close() +{ + if (closed) + return; + destination.close(); + closed = true; +} + +/** + * Flushes this output stream and forces any buffered output + * bytes to be written out. + */ +void BasicOutputStream::flush() +{ + if (closed) + return; + destination.flush(); +} + +/** + * Writes the specified byte to this output stream. + */ +void BasicOutputStream::put(XMLCh ch) +{ + if (closed) + return; + destination.put(ch); +} + + + +//######################################################################### +//# B A S I C R E A D E R +//######################################################################### + + +/** + * + */ +BasicReader::BasicReader(Reader &sourceReader) +{ + source = &sourceReader; +} + +/** + * Returns the number of bytes that can be read (or skipped over) from + * this reader without blocking by the next caller of a method for + * this reader. + */ +int BasicReader::available() +{ + if (source) + return source->available(); + else + return 0; +} + + +/** + * Closes this reader and releases any system resources + * associated with the reader. + */ +void BasicReader::close() +{ + if (source) + source->close(); +} + +/** + * Reads the next byte of data from the reader. + */ +int BasicReader::get() +{ + if (source) + return source->get(); + else + return -1; +} + + + + + + +/** + * Reads a line of data from the reader. + */ +DOMString BasicReader::readLine() +{ + DOMString str; + while (available() > 0) + { + XMLCh ch = get(); + if (ch == '\n') + break; + str.push_back(ch); + } + return str; +} + +/** + * Reads a line of data from the reader. + */ +DOMString BasicReader::readWord() +{ + DOMString str; + while (available() > 0) + { + XMLCh ch = get(); + if (!isprint(ch)) + break; + str.push_back(ch); + } + return str; +} + + +static bool getLong(DOMString &str, long *val) +{ + const char *begin = str.c_str(); + char *end; + long ival = strtol(begin, &end, 10); + if (str == end) + return false; + *val = ival; + return true; +} + +static bool getULong(const DOMString &str, unsigned long *val) +{ + DOMString tmp = str; + char *begin = (char *)tmp.c_str(); + char *end; + unsigned long ival = strtoul(begin, &end, 10); + if (begin == end) + return false; + *val = ival; + return true; +} + +static bool getDouble(const DOMString &str, double *val) +{ + DOMString tmp = str; + const char *begin = tmp.c_str(); + char *end; + double ival = strtod(begin, &end); + if (begin == end) + return false; + *val = ival; + return true; +} + + + + +/** + * + */ +Reader &BasicReader::readBool (bool& val ) +{ + DOMString buf = readWord(); + if (buf == "true") + val = true; + else + val = false; + return *this; +} + +/** + * + */ +Reader &BasicReader::readShort (short& val ) +{ + DOMString buf = readWord(); + long ival; + if (getLong(buf, &ival)) + val = (short) ival; + return *this; +} + +/** + * + */ +Reader &BasicReader::readUnsignedShort (unsigned short& val ) +{ + DOMString buf = readWord(); + unsigned long ival; + if (getULong(buf, &ival)) + val = (unsigned short) ival; + return *this; +} + +/** + * + */ +Reader &BasicReader::readInt (int& val ) +{ + DOMString buf = readWord(); + long ival; + if (getLong(buf, &ival)) + val = (int) ival; + return *this; +} + +/** + * + */ +Reader &BasicReader::readUnsignedInt (unsigned int& val ) +{ + DOMString buf = readWord(); + unsigned long ival; + if (getULong(buf, &ival)) + val = (unsigned int) ival; + return *this; +} + +/** + * + */ +Reader &BasicReader::readLong (long& val ) +{ + DOMString buf = readWord(); + long ival; + if (getLong(buf, &ival)) + val = ival; + return *this; +} + +/** + * + */ +Reader &BasicReader::readUnsignedLong (unsigned long& val ) +{ + DOMString buf = readWord(); + unsigned long ival; + if (getULong(buf, &ival)) + val = ival; + return *this; +} + +/** + * + */ +Reader &BasicReader::readFloat (float& val ) +{ + DOMString buf = readWord(); + double ival; + if (getDouble(buf, &ival)) + val = (float)ival; + return *this; +} + +/** + * + */ +Reader &BasicReader::readDouble (double& val ) +{ + DOMString buf = readWord(); + double ival; + if (getDouble(buf, &ival)) + val = ival; + return *this; +} + + + +//######################################################################### +//# I N P U T S T R E A M R E A D E R +//######################################################################### + + +InputStreamReader::InputStreamReader(const InputStream &inputStreamSource) + : inputStream((InputStream &)inputStreamSource) +{ +} + + + +/** + * Close the underlying OutputStream + */ +void InputStreamReader::close() +{ + inputStream.close(); +} + +/** + * Flush the underlying OutputStream + */ +int InputStreamReader::available() +{ + return inputStream.available(); +} + +/** + * Overloaded to receive its bytes from an InputStream + * rather than a Reader + */ +int InputStreamReader::get() +{ + //Do we need conversions here? + int ch = (XMLCh)inputStream.get(); + return ch; +} + + + +//######################################################################### +//# S T D R E A D E R +//######################################################################### + + +/** + * + */ +StdReader::StdReader() +{ + inputStream = new StdInputStream(); +} + +/** + * + */ +StdReader::~StdReader() +{ + delete inputStream; +} + + + +/** + * Close the underlying OutputStream + */ +void StdReader::close() +{ + inputStream->close(); +} + +/** + * Flush the underlying OutputStream + */ +int StdReader::available() +{ + return inputStream->available(); +} + +/** + * Overloaded to receive its bytes from an InputStream + * rather than a Reader + */ +int StdReader::get() +{ + //Do we need conversions here? + XMLCh ch = (XMLCh)inputStream->get(); + return ch; +} + + + + + +//######################################################################### +//# B A S I C W R I T E R +//######################################################################### + +/** + * + */ +BasicWriter::BasicWriter(const Writer &destinationWriter) +{ + destination = (Writer *)&destinationWriter; +} + +/** + * Closes this writer and releases any system resources + * associated with this writer. + */ +void BasicWriter::close() +{ + if (destination) + destination->close(); +} + +/** + * Flushes this output stream and forces any buffered output + * bytes to be written out. + */ +void BasicWriter::flush() +{ + if (destination) + destination->flush(); +} + +/** + * Writes the specified byte to this output writer. + */ +void BasicWriter::put(XMLCh ch) +{ + if (destination) + destination->put(ch); +} + +/** + * Provide printf()-like formatting + */ +Writer &BasicWriter::printf(char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + //replace this wish vsnprintf() + char buf[256]; + vsnprintf(buf, 255, fmt, args); + va_end(args); + if (buf) { + writeString(buf); + //free(buf); + } + return *this; +} +/** + * Writes the specified character to this output writer. + */ +Writer &BasicWriter::writeChar(char ch) +{ + XMLCh uch = ch; + put(uch); + return *this; +} + + +/** + * Writes the specified standard string to this output writer. + */ +Writer &BasicWriter::writeString(const DOMString &str) +{ + for (int i=0; i< (int)str.size(); i++) + put(str[i]); + return *this; +} + + +/** + * + */ +Writer &BasicWriter::writeBool (bool val ) +{ + if (val) + writeString("true"); + else + writeString("false"); + return *this; +} + + +/** + * + */ +Writer &BasicWriter::writeShort (short val ) +{ + char buf[32]; + snprintf(buf, 31, "%d", val); + writeString(buf); + return *this; +} + + + +/** + * + */ +Writer &BasicWriter::writeUnsignedShort (unsigned short val ) +{ + char buf[32]; + snprintf(buf, 31, "%u", val); + writeString(buf); + return *this; +} + +/** + * + */ +Writer &BasicWriter::writeInt (int val) +{ + char buf[32]; + snprintf(buf, 31, "%d", val); + writeString(buf); + return *this; +} + +/** + * + */ +Writer &BasicWriter::writeUnsignedInt (unsigned int val) +{ + char buf[32]; + snprintf(buf, 31, "%u", val); + writeString(buf); + return *this; +} + +/** + * + */ +Writer &BasicWriter::writeLong (long val) +{ + char buf[32]; + snprintf(buf, 31, "%ld", val); + writeString(buf); + return *this; +} + +/** + * + */ +Writer &BasicWriter::writeUnsignedLong(unsigned long val) +{ + char buf[32]; + snprintf(buf, 31, "%lu", val); + writeString(buf); + return *this; +} + +/** + * + */ +Writer &BasicWriter::writeFloat(float val) +{ + char buf[32]; + snprintf(buf, 31, "%8.3f", val); + writeString(buf); + return *this; +} + +/** + * + */ +Writer &BasicWriter::writeDouble(double val) +{ + char buf[32]; + snprintf(buf, 31, "%8.3f", val); + writeString(buf); + return *this; +} + + + + +//######################################################################### +//# O U T P U T S T R E A M W R I T E R +//######################################################################### + + +OutputStreamWriter::OutputStreamWriter(OutputStream &outputStreamDest) + : outputStream(outputStreamDest) +{ +} + + + +/** + * Close the underlying OutputStream + */ +void OutputStreamWriter::close() +{ + flush(); + outputStream.close(); +} + +/** + * Flush the underlying OutputStream + */ +void OutputStreamWriter::flush() +{ + outputStream.flush(); +} + +/** + * Overloaded to redirect the output chars from the next Writer + * in the chain to an OutputStream instead. + */ +void OutputStreamWriter::put(XMLCh ch) +{ + //Do we need conversions here? + int intCh = (int) ch; + outputStream.put(intCh); +} + +//######################################################################### +//# S T D W R I T E R +//######################################################################### + + +/** + * + */ +StdWriter::StdWriter() +{ + outputStream = new StdOutputStream(); +} + + +/** + * + */ +StdWriter::~StdWriter() +{ + delete outputStream; +} + + + +/** + * Close the underlying OutputStream + */ +void StdWriter::close() +{ + flush(); + outputStream->close(); +} + +/** + * Flush the underlying OutputStream + */ +void StdWriter::flush() +{ + outputStream->flush(); +} + +/** + * Overloaded to redirect the output chars from the next Writer + * in the chain to an OutputStream instead. + */ +void StdWriter::put(XMLCh ch) +{ + //Do we need conversions here? + int intCh = (int) ch; + outputStream->put(intCh); +} + + + + + + + + + + + + +//############################################### +//# O P E R A T O R S +//############################################### +//# Normally these would be in the .h, but we +//# just want to be absolutely certain that these +//# are never multiply defined. Easy to maintain, +//# though. Just occasionally copy/paste these +//# into the .h , and replace the {} with a ; +//############################################### + + + + +Reader& operator>> (Reader &reader, bool& val ) + { return reader.readBool(val); } + +Reader& operator>> (Reader &reader, short &val) + { return reader.readShort(val); } + +Reader& operator>> (Reader &reader, unsigned short &val) + { return reader.readUnsignedShort(val); } + +Reader& operator>> (Reader &reader, int &val) + { return reader.readInt(val); } + +Reader& operator>> (Reader &reader, unsigned int &val) + { return reader.readUnsignedInt(val); } + +Reader& operator>> (Reader &reader, long &val) + { return reader.readLong(val); } + +Reader& operator>> (Reader &reader, unsigned long &val) + { return reader.readUnsignedLong(val); } + +Reader& operator>> (Reader &reader, float &val) + { return reader.readFloat(val); } + +Reader& operator>> (Reader &reader, double &val) + { return reader.readDouble(val); } + + + + +Writer& operator<< (Writer &writer, char val) + { return writer.writeChar(val); } + +Writer& operator<< (Writer &writer, const DOMString &val) + { return writer.writeString(val); } + +Writer& operator<< (Writer &writer, bool val) + { return writer.writeBool(val); } + +Writer& operator<< (Writer &writer, short val) + { return writer.writeShort(val); } + +Writer& operator<< (Writer &writer, unsigned short val) + { return writer.writeUnsignedShort(val); } + +Writer& operator<< (Writer &writer, int val) + { return writer.writeInt(val); } + +Writer& operator<< (Writer &writer, unsigned int val) + { return writer.writeUnsignedInt(val); } + +Writer& operator<< (Writer &writer, long val) + { return writer.writeLong(val); } + +Writer& operator<< (Writer &writer, unsigned long val) + { return writer.writeUnsignedLong(val); } + +Writer& operator<< (Writer &writer, float val) + { return writer.writeFloat(val); } + +Writer& operator<< (Writer &writer, double val) + { return writer.writeDouble(val); } + + + +} //namespace dom +} //namespace w3c +} //namespace org + + +//######################################################################### +//# E N D O F F I L E +//######################################################################### diff --git a/src/dom/domstream.h b/src/dom/domstream.h new file mode 100755 index 000000000..64c3d0eb4 --- /dev/null +++ b/src/dom/domstream.h @@ -0,0 +1,674 @@ +#ifndef __DOMSTREAM_H__ +#define __DOMSTREAM_H__ +/** + * Phoebe DOM Implementation. + * + * This is a C++ approximation of the W3C DOM model, which follows + * fairly closely the specifications in the various .idl files, copies of + * which are provided for reference. Most important is this one: + * + * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/idl-definitions.html + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2005 Bob Jamison + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + + +#include "dom.h" + +namespace org +{ +namespace w3c +{ +namespace dom +{ + + + +class StreamException +{ +public: + + StreamException(const DOMString &theReason) throw() + { reason = theReason; } + ~StreamException() throw() + { } + char const *what() + { return reason.c_str(); } + +private: + + DOMString reason; + +}; + +//######################################################################### +//# I N P U T S T R E A M +//######################################################################### + +/** + * This interface is the base of all input stream classes. Users who wish + * to make an InputStream that is part of a chain should inherit from + * BasicInputStream. Inherit from this class to make a source endpoint, + * such as a URI or buffer. + * + */ +class InputStream +{ + +public: + + /** + * Constructor. + */ + InputStream() {} + + /** + * Destructor + */ + virtual ~InputStream() {} + + /** + * Return the number of bytes that are currently available + * to be read + */ + virtual int available() = 0; + + /** + * Do whatever it takes to 'close' this input stream + * The most likely implementation of this method will be + * for endpoints that use a resource for their data. + */ + virtual void close() = 0; + + /** + * Read one byte from this input stream. This is a blocking + * call. If no data is currently available, this call will + * not return until it exists. If the user does not want + * their code to block, then the usual solution is: + * if (available() > 0) + * myChar = get(); + * This call returns -1 on end-of-file. + */ + virtual int get() = 0; + +}; // class InputStream + + + + +/** + * This is the class that most users should inherit, to provide + * their own streams. + * + */ +class BasicInputStream : public InputStream +{ + +public: + + BasicInputStream(const InputStream &sourceStream); + + virtual ~BasicInputStream() {} + + virtual int available(); + + virtual void close(); + + virtual int get(); + +protected: + + bool closed; + + InputStream &source; + +private: + + +}; // class BasicInputStream + + + +/** + * Convenience class for reading from standard input + */ +class StdInputStream : public InputStream +{ +public: + + int available() + { return 0; } + + void close() + { /* do nothing */ } + + int get() + { return getchar(); } + +}; + + + + + + +//######################################################################### +//# O U T P U T S T R E A M +//######################################################################### + +/** + * This interface is the base of all input stream classes. Users who wish + * to make an OutputStream that is part of a chain should inherit from + * BasicOutputStream. Inherit from this class to make a destination endpoint, + * such as a URI or buffer. + */ +class OutputStream +{ + +public: + + /** + * Constructor. + */ + OutputStream() {} + + /** + * Destructor + */ + virtual ~OutputStream() {} + + /** + * This call should + * 1. flush itself + * 2. close itself + * 3. close the destination stream + */ + virtual void close() = 0; + + /** + * This call should push any pending data it might have to + * the destination stream. It should NOT call flush() on + * the destination stream. + */ + virtual void flush() = 0; + + /** + * Send one byte to the destination stream. + */ + virtual void put(XMLCh ch) = 0; + + +}; // class OutputStream + + +/** + * This is the class that most users should inherit, to provide + * their own output streams. + */ +class BasicOutputStream : public OutputStream +{ + +public: + + BasicOutputStream(const OutputStream &destinationStream); + + virtual ~BasicOutputStream() {} + + virtual void close(); + + virtual void flush(); + + virtual void put(XMLCh ch); + +protected: + + bool closed; + + OutputStream &destination; + + +}; // class BasicOutputStream + + + +/** + * Convenience class for writing to standard output + */ +class StdOutputStream : public OutputStream +{ +public: + + void close() + { } + + void flush() + { } + + void put(XMLCh ch) + { putchar(ch); } + +}; + + + + +//######################################################################### +//# R E A D E R +//######################################################################### + + +/** + * This interface and its descendants are for unicode character-oriented input + * + */ +class Reader +{ + +public: + + /** + * Constructor. + */ + Reader() {} + + /** + * Destructor + */ + virtual ~Reader() {} + + + virtual int available() = 0; + + virtual void close() = 0; + + virtual int get() = 0; + + virtual DOMString readLine() = 0; + + virtual DOMString readWord() = 0; + + /* Input formatting */ + virtual Reader& readBool (bool& val ) = 0; + + virtual Reader& readShort (short &val) = 0; + + virtual Reader& readUnsignedShort (unsigned short &val) = 0; + + virtual Reader& readInt (int &val) = 0; + + virtual Reader& readUnsignedInt (unsigned int &val) = 0; + + virtual Reader& readLong (long &val) = 0; + + virtual Reader& readUnsignedLong (unsigned long &val) = 0; + + virtual Reader& readFloat (float &val) = 0; + + virtual Reader& readDouble (double &val) = 0; + +}; // interface Reader + + + +/** + * This class and its descendants are for unicode character-oriented input + * + */ +class BasicReader : public Reader +{ + +public: + + BasicReader(Reader &sourceStream); + + virtual ~BasicReader() {} + + virtual int available(); + + virtual void close(); + + virtual int get(); + + virtual DOMString readLine(); + + virtual DOMString readWord(); + + /* Input formatting */ + virtual Reader& readBool (bool& val ); + + virtual Reader& readShort (short &val) ; + + virtual Reader& readUnsignedShort (unsigned short &val) ; + + virtual Reader& readInt (int &val) ; + + virtual Reader& readUnsignedInt (unsigned int &val) ; + + virtual Reader& readLong (long &val) ; + + virtual Reader& readUnsignedLong (unsigned long &val) ; + + virtual Reader& readFloat (float &val) ; + + virtual Reader& readDouble (double &val) ; + +protected: + + Reader *source; + + BasicReader() + { source = NULL; } + +private: + +}; // class BasicReader + + + +Reader& operator>> (Reader &reader, bool& val ); + +Reader& operator>> (Reader &reader, short &val); + +Reader& operator>> (Reader &reader, unsigned short &val); + +Reader& operator>> (Reader &reader, int &val); + +Reader& operator>> (Reader &reader, unsigned int &val); + +Reader& operator>> (Reader &reader, long &val); + +Reader& operator>> (Reader &reader, unsigned long &val); + +Reader& operator>> (Reader &reader, float &val); + +Reader& operator>> (Reader &reader, double &val); + + + + +/** + * Class for placing a Reader on an open InputStream + * + */ +class InputStreamReader : public BasicReader +{ +public: + + InputStreamReader(const InputStream &inputStreamSource); + + /*Overload these 3 for your implementation*/ + virtual int available(); + + virtual void close(); + + virtual int get(); + + +private: + + InputStream &inputStream; + + +}; + +/** + * Convenience class for reading formatted from standard input + * + */ +class StdReader : public BasicReader +{ +public: + + StdReader(); + + ~StdReader(); + + /*Overload these 3 for your implementation*/ + virtual int available(); + + virtual void close(); + + virtual int get(); + + +private: + + InputStream *inputStream; + + +}; + + + + + +//######################################################################### +//# W R I T E R +//######################################################################### + +/** + * This interface and its descendants are for unicode character-oriented output + * + */ +class Writer +{ + +public: + + /** + * Constructor. + */ + Writer() {} + + /** + * Destructor + */ + virtual ~Writer() {} + + virtual void close() = 0; + + virtual void flush() = 0; + + virtual void put(XMLCh ch) = 0; + + /* Formatted output */ + virtual Writer& printf(char *fmt, ...) = 0; + + virtual Writer& writeChar(char val) = 0; + + virtual Writer& writeString(const DOMString &val) = 0; + + virtual Writer& writeBool (bool val ) = 0; + + virtual Writer& writeShort (short val ) = 0; + + virtual Writer& writeUnsignedShort (unsigned short val ) = 0; + + virtual Writer& writeInt (int val ) = 0; + + virtual Writer& writeUnsignedInt (unsigned int val ) = 0; + + virtual Writer& writeLong (long val ) = 0; + + virtual Writer& writeUnsignedLong (unsigned long val ) = 0; + + virtual Writer& writeFloat (float val ) = 0; + + virtual Writer& writeDouble (double val ) = 0; + + + +}; // interface Writer + + +/** + * This class and its descendants are for unicode character-oriented output + * + */ +class BasicWriter : public Writer +{ + +public: + + BasicWriter(const Writer &destinationWriter); + + virtual ~BasicWriter() {} + + /*Overload these 3 for your implementation*/ + virtual void close(); + + virtual void flush(); + + virtual void put(XMLCh ch); + + + + /* Formatted output */ + virtual Writer &printf(char *fmt, ...); + + virtual Writer& writeChar(char val); + + virtual Writer& writeString(const DOMString &val); + + virtual Writer& writeBool (bool val ); + + virtual Writer& writeShort (short val ); + + virtual Writer& writeUnsignedShort (unsigned short val ); + + virtual Writer& writeInt (int val ); + + virtual Writer& writeUnsignedInt (unsigned int val ); + + virtual Writer& writeLong (long val ); + + virtual Writer& writeUnsignedLong (unsigned long val ); + + virtual Writer& writeFloat (float val ); + + virtual Writer& writeDouble (double val ); + + +protected: + + Writer *destination; + + BasicWriter() + { destination = NULL; } + +private: + +}; // class BasicWriter + + + +Writer& operator<< (Writer &writer, char val); + +Writer& operator<< (Writer &writer, const DOMString &val); + +Writer& operator<< (Writer &writer, bool val); + +Writer& operator<< (Writer &writer, short val); + +Writer& operator<< (Writer &writer, unsigned short val); + +Writer& operator<< (Writer &writer, int val); + +Writer& operator<< (Writer &writer, unsigned int val); + +Writer& operator<< (Writer &writer, long val); + +Writer& operator<< (Writer &writer, unsigned long val); + +Writer& operator<< (Writer &writer, float val); + +Writer& operator<< (Writer &writer, double val); + + + + +/** + * Class for placing a Writer on an open OutputStream + * + */ +class OutputStreamWriter : public BasicWriter +{ +public: + + OutputStreamWriter(OutputStream &outputStreamDest); + + /*Overload these 3 for your implementation*/ + virtual void close(); + + virtual void flush(); + + virtual void put(XMLCh ch); + + +private: + + OutputStream &outputStream; + + +}; + + +/** + * Convenience class for writing to standard output + */ +class StdWriter : public BasicWriter +{ +public: + StdWriter(); + + ~StdWriter(); + + + virtual void close(); + + + virtual void flush(); + + + virtual void put(XMLCh ch); + + +private: + + OutputStream *outputStream; + +}; + +//######################################################################### +//# U T I L I T Y +//######################################################################### + +void pipeStream(InputStream &source, OutputStream &dest); + + + + +} //namespace dom +} //namespace w3c +} //namespace org + + +#endif /* __DOMSTREAM_H__ */ diff --git a/src/dom/domstring.cpp b/src/dom/domstring.cpp new file mode 100755 index 000000000..11b301066 --- /dev/null +++ b/src/dom/domstring.cpp @@ -0,0 +1,414 @@ +/** + * Phoebe DOM Implementation. + * + * This is a C++ approximation of the W3C DOM model, which follows + * fairly closely the specifications in the various .idl files, copies of + * which are provided for reference. Most important is this one: + * + * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/idl-definitions.html + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2005 Bob Jamison + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include "domstring.h" + +namespace org +{ +namespace w3c +{ +namespace dom +{ + + +//######################################################################### +//# C O N S T R U C T O R S +//######################################################################### + +DOMString::DOMString() +{ + init(); +} + +DOMString::DOMString(const DOMString &other) +{ + init(); + chars = other.chars; +} + +DOMString::DOMString(const char *str) +{ + init(); + append(str); +} + + +DOMString::~DOMString() +{ + if (cstring) + free(cstring); +} + + +/** + * Called only by Constructors + */ +void DOMString::init() +{ + cstring = NULL; + chars.clear(); +} + +//######################################################################### +//# M O D I F Y +//######################################################################### + +DOMString &DOMString::append(const DOMString &str) +{ + unsigned int len = str.size(); + for (unsigned int i=0 ; i::iterator iter = chars.begin(); + chars.erase(iter, iter + count); +} + +DOMString &DOMString::insert(unsigned long pos, const DOMString &str) +{ + DOMString a = substr(0, pos); + DOMString b = substr(pos, size()); + clear(); + append(a); + append(str); + append(b); + return *this; +} + +DOMString &DOMString::insert(unsigned long pos, const char *str) +{ + DOMString a = substr(0, pos); + DOMString b = substr(pos, size()); + clear(); + append(a); + append(str); + append(b); + return *this; +} + +DOMString &DOMString::insert(unsigned long pos, const std::string &str) +{ + DOMString a = substr(0, pos); + DOMString b = substr(pos, size()); + clear(); + append(a); + append(str); + append(b); + return *this; +} + + +DOMString &DOMString::prepend(const DOMString &str) +{ + DOMString tmp = *this; + clear(); + append(str); + append(tmp); + return *this; +} + +DOMString &DOMString::prepend(const char *str) +{ + DOMString tmp = *this; + clear(); + append(str); + append(tmp); + return *this; +} + +DOMString &DOMString::prepend(const std::string &str) +{ + DOMString tmp = *this; + clear(); + append(str); + append(tmp); + return *this; +} + +DOMString &DOMString::replace(unsigned long pos, unsigned long count, + const DOMString &str) +{ + DOMString a = substr(0, pos); + DOMString b = substr(pos+count, size()); + clear(); + append(a); + append(str); + append(b); + return *this; +} + +DOMString &DOMString::replace(unsigned long pos, unsigned long count, + const char *str) +{ + DOMString a = substr(0, pos); + DOMString b = substr(pos+count, size()); + clear(); + append(a); + append(str); + append(b); + return *this; +} + +DOMString &DOMString::replace(unsigned long pos, unsigned long count, + const std::string &str) +{ + DOMString a = substr(0, pos); + DOMString b = substr(pos+count, size()); + clear(); + append(a); + append(str); + append(b); + return *this; +} + + +DOMString &DOMString::push_back(XMLCh ch) +{ + chars.push_back(ch); + return *this; +} + + + +void DOMString::clear() +{ + chars.clear(); + if (cstring) + { + free(cstring); + cstring = NULL; + } +} + +//######################################################################### +//# Q U E R Y +//######################################################################### + +XMLCh DOMString::charAt(unsigned long index) const +{ + return chars[index]; +} + +XMLCh DOMString::operator[](unsigned long index) const +{ + return chars[index]; +} + +DOMString DOMString::substr(unsigned long start, unsigned long end) const +{ + DOMString ret; + for (unsigned long i = start; i +#include + +namespace org +{ +namespace w3c +{ +namespace dom +{ + +/** + * + */ +typedef unsigned short XMLCh; + +class DOMString +{ +public: + + //############################### + //# C O N S T R U C T O R S + //############################### + + /** + * + */ + DOMString(); + + /** + * + */ + DOMString(const char *str); + + + /** + * + */ + DOMString(const DOMString &str); + + + /** + * + */ + DOMString(const std::string &str); + + /** + * + */ + virtual ~DOMString(); + + + //############################### + //# M O D I F Y + //############################### + + + + /** + * + */ + virtual DOMString &append(const DOMString &str); + virtual DOMString &operator +(const DOMString &str) + { return append(str); } + virtual DOMString &operator +=(const DOMString &str) + { return append(str); } + + /** + * + */ + virtual DOMString &append(const char *str); + + /** + * + */ + virtual DOMString &append(const std::string &str); + + + /** + * + */ + virtual DOMString &assign(const DOMString &str); + + /** + * + */ + virtual DOMString &assign(const char *str); + + /** + * + */ + virtual DOMString &assign(const std::string &str); + + /** + * + */ + virtual void erase(unsigned long pos, unsigned long count); + + /** + * + */ + virtual DOMString &insert(unsigned long pos, const DOMString &str); + + /** + * + */ + virtual DOMString &insert(unsigned long pos, const char *str); + + /** + * + */ + virtual DOMString &insert(unsigned long pos, const std::string &str); + + + /** + * + */ + virtual DOMString &prepend(const DOMString &str); + + /** + * + */ + virtual DOMString &prepend(const char *str); + + /** + * + */ + virtual DOMString &prepend(const std::string &str); + + + /** + * + */ + virtual DOMString &replace(unsigned long pos, unsigned long count, + const DOMString &str); + + /** + * + */ + virtual DOMString &replace(unsigned long pos, unsigned long count, + const char *str); + + /** + * + */ + virtual DOMString &replace(unsigned long pos, unsigned long count, + const std::string &str); + + /** + * + */ + virtual DOMString &push_back(XMLCh ch); + + /** + * + */ + virtual void clear(); + + //############################### + //# Q U E R Y + //############################### + + /** + * + */ + virtual DOMString substr(unsigned long start, unsigned long end) const; + + /** + * + */ + virtual XMLCh charAt(unsigned long index) const; + + /** + * + */ + virtual XMLCh operator[](unsigned long index) const; + + /** + * + */ + virtual unsigned long size() const; + + /** + * + */ + virtual const char *c_str(); + + //############################### + //# C O M P A R I S O N + //############################### + + /** + * + */ + virtual int compare(const DOMString &str) const; + virtual bool operator <(const DOMString &str) const + { return (compare(str)<0) ; } + virtual bool operator <=(const DOMString &str) const + { return (compare(str)<=0) ; } + virtual bool operator >(const DOMString &str) const + { return (compare(str)>0) ; } + virtual bool operator >=(const DOMString &str) const + { return (compare(str)>=0) ; } + virtual bool operator !=(const DOMString &str) const + { return (compare(str)!=0) ; } + virtual bool operator ==(const DOMString &str) const + { return (compare(str)==0) ; } + + /** + * + */ + virtual int compare(const char *str) const; + virtual bool operator <(const char *str) const + { return (compare(str)<0) ; } + virtual bool operator <=(const char *str) const + { return (compare(str)<=0) ; } + virtual bool operator >(const char *str) const + { return (compare(str)>0) ; } + virtual bool operator >=(const char *str) const + { return (compare(str)>=0) ; } + virtual bool operator !=(const char *str) const + { return (compare(str)!=0) ; } + virtual bool operator ==(const char *str) const + { return (compare(str)==0) ; } + + /** + * + */ + virtual int compare(const std::string &str) const; + virtual bool operator <(const std::string &str) const + { return (compare(str)<0) ; } + virtual bool operator <=(const std::string &str) const + { return (compare(str)<=0) ; } + virtual bool operator >(const std::string &str) const + { return (compare(str)>0) ; } + virtual bool operator >=(const std::string &str) const + { return (compare(str)>=0) ; } + virtual bool operator !=(const std::string &str) const + { return (compare(str)!=0) ; } + virtual bool operator ==(const std::string &str) const + { return (compare(str)==0) ; } + + + + +protected: + + void init(); + + char *cstring; + + std::vector chars; + +}; // class DOMString + + + + +//############################### +//# O P E R A T O R S +//############################### + +DOMString &operator +(DOMString &a, const char *b); + +DOMString &operator +(const char *b, DOMString &a); + + + +} //namespace dom +} //namespace w3c +} //namespace org + +#endif // __DOMSTRING_H__ +//######################################################################### +//## E N D O F F I L E +//######################################################################### + + diff --git a/src/dom/domstringimpl.h b/src/dom/domstringimpl.h new file mode 100755 index 000000000..965f9702d --- /dev/null +++ b/src/dom/domstringimpl.h @@ -0,0 +1,107 @@ +#ifndef __DOMSTRINGIMPL_H__ +#define __DOMSTRINGIMPL_H__ +/** + * Phoebe DOM Implementation. + * + * This is a C++ approximation of the W3C DOM model, which follows + * fairly closely the specifications in the various .idl files, copies of + * which are provided for reference. Most important is this one: + * + * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/idl-definitions.html + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2005 Bob Jamison + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +class DOMStringImpl +{ +public: + + /** + * + */ + DOMString(); + + /** + * + */ + DOMString(const char *str); + + /** + * + */ + DOMString(const DOMString &str); + + /** + * + */ + virtual ~DOMString(); + + /** + * + */ + virtual void append(const DOMString &str); + + /** + * + */ + virtual void append(const char *str); + + /** + * + */ + virtual void push_back(int ch); + + /** + * + */ + virtual DOMString &substring(int start, int end); + + /** + * + */ + virtual int charAt(unsigned long index); + + /** + * + */ + virtual unsigned long size(); + + /** + * + */ + virtual const char *c_str(); + + +protected: + + void init(); + + char *cstring; + + unsigned long length; + +}; + + + + + + +#endif /* __DOMSTRINGIMPL_H__ */ diff --git a/src/dom/events.h b/src/dom/events.h new file mode 100755 index 000000000..5a8fe6ccd --- /dev/null +++ b/src/dom/events.h @@ -0,0 +1,1351 @@ +#ifndef __EVENTS_H__ +#define __EVENTS_H__ + +/** + * Phoebe DOM Implementation. + * + * This is a C++ approximation of the W3C DOM model, which follows + * fairly closely the specifications in the various .idl files, copies of + * which are provided for reference. Most important is this one: + * + * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/idl-definitions.html + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2005 Bob Jamison + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include "dom.h" +#include "views.h" + + + + +namespace org { +namespace w3c { +namespace dom { +namespace events { + + + + +//Local definitions +typedef dom::DOMString DOMString; +typedef dom::DOMTimeStamp DOMTimeStamp; +typedef dom::Node Node; + + + + +//forward declarations +class Event; +class EventTarget; +class EventListener; +class DocumentEvent; +class CustomEvent; +class UIEvent; +class TextEvent; +class MouseEvent; +class KeyboardEvent; +class MutationEvent; +class MutationNameEvent; + + + +/*######################################################################### +## EventException +#########################################################################*/ + +/** + * + */ +class EventException +{ +public: + + EventException(short theCode) + { + code = theCode; + } + + ~EventException() throw() + {} + + unsigned short code; +}; + + /** + * EventExceptionCode + */ + enum + { + UNSPECIFIED_EVENT_TYPE_ERR = 0, + DISPATCH_REQUEST_ERR = 1 + }; + + + +/*######################################################################### +## Event +#########################################################################*/ + +/** + * + */ +class Event +{ +public: + + /** + * PhaseType + */ + typedef enum + { + CAPTURING_PHASE = 1, + AT_TARGET = 2, + BUBBLING_PHASE = 3 + } PhaseType; + + /** + * + */ + virtual DOMString getType() const + { return eventType; } + + /** + * + */ + virtual EventTarget *getTarget() + { return target; } + + /** + * + */ + virtual EventTarget *getCurrentTarget() + { return currentTarget; } + + /** + * + */ + virtual unsigned short getEventPhase() + { return eventPhase; } + + /** + * + */ + virtual bool getBubbles() + { return canBubble; } + + /** + * + */ + virtual bool getCancelable() + { return cancelable; } + + /** + * + */ + virtual DOMTimeStamp getTimeStamp() + { return timeStamp; } + + /** + * + */ + virtual void stopPropagation() + { + } + + /** + * + */ + virtual void preventDefault() + { + } + + /** + * + */ + virtual void initEvent(const DOMString &eventTypeArg, + bool canBubbleArg, + bool cancelableArg) + { + namespaceURI = ""; + eventType = eventTypeArg; + canBubble = canBubbleArg; + cancelable = cancelableArg; + } + + + /** + * + */ + virtual DOMString getNamespaceURI() const + { return namespaceURI; } + + /** + * + */ + virtual bool isCustom() + { return custom; } + + /** + * + */ + virtual void stopImmediatePropagation() + { + } + + /** + * + */ + virtual bool isDefaultPrevented() + { return defaultPrevented; } + + /** + * + */ + virtual void initEventNS(const DOMString &namespaceURIArg, + const DOMString &eventTypeArg, + bool canBubbleArg, + bool cancelableArg) + { + namespaceURI = namespaceURIArg; + eventType = eventTypeArg; + canBubble = canBubbleArg; + cancelable = cancelableArg; + } + + //################## + //# Non-API methods + //################## + + /** + * + */ + Event() + { + init(); + } + + /** + * + */ + Event(const DOMString &eventTypeArg) + { + init(); + eventType = eventTypeArg; + } + + + /** + * + */ + Event(const Event &other) + { + eventType = other.eventType; + target = other.target; + currentTarget = other.currentTarget; + eventPhase = other.eventPhase; + canBubble = other.canBubble; + cancelable = other.cancelable; + timeStamp = other.timeStamp; + namespaceURI = other.namespaceURI; + custom = other.custom; + defaultPrevented = other.defaultPrevented; + } + + /** + * + */ + virtual ~Event() {} + +protected: + + /** + * + */ + void init() + { + eventType = ""; + target = NULL; + currentTarget = NULL; + eventPhase = 0; + canBubble = false; + cancelable = false; + //timeStamp = other.timeStamp; + namespaceURI = ""; + custom = false; + defaultPrevented = false; + } + + DOMString eventType; + EventTarget *target; + EventTarget *currentTarget; + unsigned short eventPhase; + bool canBubble; + bool cancelable; + DOMTimeStamp timeStamp; + DOMString namespaceURI; + bool custom; + bool defaultPrevented; + +}; + + + + +/*######################################################################### +## EventListener +#########################################################################*/ + +/** + * + */ +class EventListener +{ +public: + + /** + * + */ + virtual void handleEvent(const Event &evt) + {} + + //################## + //# Non-API methods + //################## + + /** + * + */ + virtual ~EventListener() {} +}; + + + + + + +/*######################################################################### +## EventTarget +#########################################################################*/ + +class EventListenerEntry +{ +public: + EventListenerEntry(const DOMString &namespaceURIArg, + const DOMString &eventTypeArg, + const EventListener *listenerArg, + bool useCaptureArg) + { + namespaceURI = namespaceURIArg; + eventType = eventTypeArg; + listener = (EventListener *)listenerArg; + useCapture = useCaptureArg; + } + + EventListenerEntry(const EventListenerEntry &other) + { + namespaceURI = other.namespaceURI; + eventType = other.eventType; + listener = other.listener; + useCapture = other.useCapture; + } + + virtual ~EventListenerEntry() {} + + DOMString namespaceURI; + DOMString eventType; + EventListener *listener; + bool useCapture; +}; + + +/** + * + */ +class EventTarget +{ +public: + + /** + * + */ + virtual void addEventListener(const DOMString &type, + const EventListener *listener, + bool useCapture) + { + EventListenerEntry entry("", type, listener, useCapture); + listeners.push_back(entry); + } + + /** + * + */ + virtual void removeEventListener(const DOMString &type, + const EventListener *listener, + bool useCapture) + { + std::vector::iterator iter; + for (iter = listeners.begin() ; iter != listeners.end() ; iter++) + { + EventListenerEntry entry = *iter; + if (entry.eventType == type && + entry.listener == listener && + useCapture && entry.useCapture) + listeners.erase(iter); + } + } + + /** + * + */ + virtual bool dispatchEvent(const Event &evt) throw(EventException) + { + + for (unsigned int i=0 ; ihandleEvent(evt); + } + } + return true; + } + + + /** + * + */ + virtual void addEventListenerNS(const DOMString &namespaceURI, + const DOMString &type, + const EventListener *listener, + bool useCapture) + { + EventListenerEntry entry(namespaceURI, type, listener, useCapture); + listeners.push_back(entry); + } + + /** + * + */ + virtual void removeEventListenerNS(const DOMString &namespaceURI, + const DOMString &type, + const EventListener *listener, + bool useCapture) + { + std::vector::iterator iter; + for (iter = listeners.begin() ; iter != listeners.end() ; iter++) + { + EventListenerEntry entry = *iter; + if (entry.namespaceURI == namespaceURI && + entry.eventType == type && + entry.listener == listener && + useCapture && entry.useCapture) + listeners.erase(iter); + } + } + + /** + * + */ + virtual bool willTriggerNS(const DOMString &namespaceURI, + const DOMString &type) + { + std::vector::iterator iter; + for (iter = listeners.begin() ; iter != listeners.end() ; iter++) + { + EventListenerEntry entry = *iter; + if (entry.namespaceURI == namespaceURI && + entry.eventType == type) + return true; + } + return false; + } + + /** + * + */ + virtual bool hasEventListenerNS(const DOMString &namespaceURI, + const DOMString &type) + { + std::vector::iterator iter; + for (iter = listeners.begin() ; iter != listeners.end() ; iter++) + { + EventListenerEntry entry = *iter; + if (entry.namespaceURI == namespaceURI && + entry.eventType == type) + return true; + } + return false; + } + + + //################## + //# Non-API methods + //################## + + /** + * + */ + EventTarget() {} + + /** + * + */ + EventTarget(const EventTarget &other) + { + listeners = other.listeners; + } + + /** + * + */ + virtual ~EventTarget() {} + +protected: + + std::vector listeners; + +}; + + + + +/*######################################################################### +## DocumentEvent +#########################################################################*/ + +/* + * + */ +class DocumentEvent : virtual public Event +{ +public: + + /** + * + */ + virtual Event createEvent(const DOMString &eventType) + throw (dom::DOMException) + { + Event event; + return event; + } + + /** + * + */ + virtual bool canDispatch(const DOMString &namespaceURI, + const DOMString &type) + { + return dispatchable; + } + + //################## + //# Non-API methods + //################## + + /** + * + */ + DocumentEvent() {} + + /** + * + */ + DocumentEvent(const DocumentEvent &other) : Event(other) + { + dispatchable = other.dispatchable; + } + + /** + * + */ + virtual ~DocumentEvent() {} + +protected: + + bool dispatchable; + + +}; + + +/*######################################################################### +## CustomEvent +#########################################################################*/ + +/* + * + */ +class CustomEvent : virtual public Event +{ +public: + + /** + * + */ + virtual void setDispatchState(const EventTarget *target, + unsigned short phase) + { + } + + /** + * + */ + virtual bool isPropagationStopped() + { + return propagationStopped; + } + + /** + * + */ + virtual bool isImmediatePropagationStopped() + { + return immediatePropagationStopped; + } + + //################## + //# Non-API methods + //################## + + /** + * + */ + CustomEvent() {} + + /** + * + */ + CustomEvent(const CustomEvent &other) : Event(other) + { + propagationStopped = other.propagationStopped; + immediatePropagationStopped = other.immediatePropagationStopped; + } + + /** + * + */ + virtual ~CustomEvent() {} + +protected: + + bool propagationStopped; + bool immediatePropagationStopped; + + + +}; + + + + +/*######################################################################### +## UIEvent +#########################################################################*/ + +/** + * + */ +class UIEvent : virtual public Event +{ +public: + + /** + * Note that the return type as listed in level3 events.idl is + * AbstractView, while in level3 views.idl it is called View + */ + virtual views::View getView() + { return view; } + + /** + * + */ + virtual long getDetail() + { return detail; } + + /** + * Note that views.idl and events.idl disagree on the name of Views + */ + virtual void initUIEvent(const DOMString &typeArg, + bool canBubbleArg, + bool cancelableArg, + const views::View *viewArg, + long detailArg) + { + } + + /** + * Note that views.idl and events.idl disagree on the name of Views + */ + virtual void initUIEventNS(const DOMString &namespaceURI, + const DOMString &typeArg, + bool canBubbleArg, + bool cancelableArg, + const views::View *viewArg, + long detailArg) + { + } + + //################## + //# Non-API methods + //################## + + /** + * + */ + UIEvent() {} + + /** + * + */ + UIEvent(const UIEvent &other) : Event(other) + { + view = other.view; + detail = other.detail; + } + + /** + * + */ + virtual ~UIEvent() {} + +protected: + + views::View view; + long detail; +}; + + + + +/*######################################################################### +## TextEvent +#########################################################################*/ + +/** + * + */ +class TextEvent : virtual public UIEvent +{ +public: + + /** + * + */ + virtual DOMString getData() + { return data; } + + /** + * Note that views.idl and events.idl disagree on the name of Views + */ + virtual void initTextEvent(const DOMString &typeArg, + bool canBubbleArg, + bool cancelableArg, + const views::View *viewArg, + long detailArg) + { + } + + /** + * + */ + virtual void initTextEventNS(const DOMString &namespaceURI, + const DOMString &typeArg, + bool canBubbleArg, + bool cancelableArg, + const views::View *viewArg, + long detailArg) + { + } + + //################## + //# Non-API methods + //################## + + /** + * + */ + TextEvent() {} + + /** + * + */ + TextEvent(const TextEvent &other) : Event(other), UIEvent(other) + { + data = other.data; + } + + /** + * + */ + virtual ~TextEvent() {} + +protected: + + DOMString data; + +}; + + + + + + + + +/*######################################################################### +## MouseEvent +#########################################################################*/ + +/** + * + */ +class MouseEvent : virtual public UIEvent +{ +public: + + /** + * + */ + virtual long getScreenX() + { return screenX; } + + /** + * + */ + virtual long getScreenY() + { return screenY; } + + /** + * + */ + virtual long getClientX() + { return clientX; } + + /** + * + */ + virtual long getClientY() + { return clientY; } + + /** + * + */ + virtual bool getCtrlKey() + { return ctrlKey; } + + /** + * + */ + virtual bool getShiftKey() + { return shiftKey; } + + /** + * + */ + virtual bool getAltKey() + { return altKey; } + + /** + * + */ + virtual bool getMetaKey() + { return metaKey; } + + /** + * + */ + virtual unsigned short getButton() + { return button; } + + /** + * + */ + virtual EventTarget *getRelatedTarget() + { return relatedTarget; } + + + /** + * + */ + virtual bool getModifierState() + { return modifierState; } + + /** + * + */ + virtual void initMouseEvent(const DOMString &typeArg, + bool canBubbleArg, + bool cancelableArg, + const views::View *viewArg, + long detailArg, + long screenXArg, + long screenYArg, + long clientXArg, + long clientYArg, + bool ctrlKeyArg, + bool altKeyArg, + bool shiftKeyArg, + bool metaKeyArg, + unsigned short buttonArg, + const EventTarget *relatedTargetArg) + { + } + + + /** + * + */ + virtual void initMouseEventNS(const DOMString &namespaceURI, + const DOMString &typeArg, + bool canBubbleArg, + bool cancelableArg, + const views::View *viewArg, + long detailArg, + long screenXArg, + long screenYArg, + long clientXArg, + long clientYArg, + unsigned short buttonArg, + const EventTarget *relatedTargetArg, + const DOMString &modifiersList) + { + } + + //################## + //# Non-API methods + //################## + + /** + * + */ + MouseEvent() {} + + /** + * + */ + MouseEvent(const MouseEvent &other) : Event(other), UIEvent(other) + { + screenX = other.screenX; + screenY = other.screenY; + clientX = other.clientX; + clientY = other.clientY; + ctrlKey = other.ctrlKey; + shiftKey = other.shiftKey; + altKey = other.altKey; + metaKey = other.metaKey; + button = other.button; + relatedTarget = other.relatedTarget; + modifierState = other.modifierState; + } + + /** + * + */ + virtual ~MouseEvent() {} + +protected: + + long screenX; + long screenY; + long clientX; + long clientY; + bool ctrlKey; + bool shiftKey; + bool altKey; + bool metaKey; + unsigned short button; + EventTarget *relatedTarget; + bool modifierState; + + +}; + + + + +/*######################################################################### +## KeyboardEvent +#########################################################################*/ + +/** + * + */ +class KeyboardEvent : virtual public UIEvent +{ +public: + + typedef enum + { + DOM_KEY_LOCATION_STANDARD = 0x00, + DOM_KEY_LOCATION_LEFT = 0x01, + DOM_KEY_LOCATION_RIGHT = 0x02, + DOM_KEY_LOCATION_NUMPAD = 0x03 + } KeyLocationCode; + + /** + * + */ + virtual DOMString getKeyIdentifier() + { return keyIdentifier; } + + /** + * + */ + virtual unsigned long getKeyLocation() + { return keyLocation; } + + /** + * + */ + virtual bool getCtrlKey() + { return ctrlKey; } + + /** + * + */ + virtual bool getShiftKey() + { return shiftKey; } + + /** + * + */ + virtual bool getAltKey() + { return altKey; } + + /** + * + */ + virtual bool getMetaKey() + { return metaKey; } + + /** + * + */ + virtual bool getModifierState() + { return modifierState; } + + /** + * + */ + virtual void initKeyboardEvent(const DOMString &typeArg, + bool canBubbleArg, + bool cancelableArg, + const views::View *viewArg, + const DOMString &keyIdentifier, + unsigned long keyLocation, + const DOMString modifiersList) + { + } + + + + /** + * + */ + virtual void initKeyboardEventNS(const DOMString &namespaceURI, + const DOMString &typeArg, + bool canBubbleArg, + bool cancelableArg, + const views::View *viewArg, + const DOMString &keyIdentifier, + unsigned long keyLocation, + const DOMString modifiersList) + { + } + + //################## + //# Non-API methods + //################## + + /** + * + */ + KeyboardEvent() {} + + /** + * + */ + KeyboardEvent(const KeyboardEvent &other) : Event(other), UIEvent(other) + { + keyIdentifier = other.keyIdentifier; + keyLocation = other.keyLocation; + ctrlKey = other.ctrlKey; + shiftKey = other.shiftKey; + altKey = other.altKey; + metaKey = other.metaKey; + modifierState = other.modifierState; + } + + /** + * + */ + virtual ~KeyboardEvent() {} + +protected: + + DOMString keyIdentifier; + unsigned long keyLocation; + bool ctrlKey; + bool shiftKey; + bool altKey; + bool metaKey; + bool modifierState; +}; + + + + + + + + + +/*######################################################################### +## MutationEvent +#########################################################################*/ + +/** + * + */ +class MutationEvent : virtual public Event +{ +public: + + /** + * attrChangeType + */ + typedef enum + { + MODIFICATION = 1, + ADDITION = 2, + REMOVAL = 3 + } AttrChangeType; + + /** + * + */ + virtual Node *getRelatedNode() + { return relatedNode; } + + /** + * + */ + virtual DOMString getPrevValue() + { return prevValue; } + + /** + * + */ + virtual DOMString getNewValue() + { return newValue; } + + /** + * + */ + virtual DOMString getAttrName() + { return attrName; } + + /** + * + */ + virtual unsigned short getAttrChange() + { + return attrChange; + } + + /** + * + */ + virtual void initMutationEvent(const DOMString &typeArg, + bool canBubbleArg, + bool cancelableArg, + const Node *relatedNodeArg, + const DOMString &prevValueArg, + const DOMString &newValueArg, + const DOMString &attrNameArg, + unsigned short attrChangeArg) + { + } + + /** + * + */ + virtual void initMutationEventNS(const DOMString &namespaceURI, + const DOMString &typeArg, + bool canBubbleArg, + bool cancelableArg, + const Node *relatedNodeArg, + const DOMString &prevValueArg, + const DOMString &newValueArg, + const DOMString &attrNameArg, + unsigned short attrChangeArg) + { + } + + + //################## + //# Non-API methods + //################## + + /** + * + */ + MutationEvent() + { + relatedNode = NULL; + } + + /** + * + */ + MutationEvent(const MutationEvent &other) : Event(other) + { + relatedNode = other.relatedNode; + prevValue = other.prevValue; + newValue = other.newValue; + attrName = other.attrName; + attrChange = other.attrChange; + } + + /** + * + */ + virtual ~MutationEvent() {} + +protected: + + Node *relatedNode; + DOMString prevValue; + DOMString newValue; + DOMString attrName; + unsigned short attrChange; + +}; + + + + +/*######################################################################### +## MutationNameEvent +#########################################################################*/ + +/** + * + */ +class MutationNameEvent : virtual public MutationEvent +{ +public: + + /** + * + */ + virtual DOMString getPrevNamespaceURI() + { return prevNamespaceURI; } + + /** + * + */ + virtual DOMString getPrevNodeName() + { return prevNodeName; } + + /** + * + */ + virtual void initMutationNameEvent(const DOMString &typeArg, + bool canBubbleArg, + bool cancelableArg, + const Node *relatedNodeArg, + const DOMString &prevNamespaceURIArg, + const DOMString &prevNodeNameArg) + { + } + + + /** + * + */ + virtual void initMutationNameEventNS(const DOMString &namespaceURI, + const DOMString &typeArg, + bool canBubbleArg, + bool cancelableArg, + const Node *relatedNodeArg, + const DOMString &prevNamespaceURIArg, + const DOMString &prevNodeNameArg) + { + } + + + + //################## + //# Non-API methods + //################## + + /** + * + */ + MutationNameEvent() {} + + + /** + * + */ + MutationNameEvent(const MutationNameEvent &other) + : Event(other), MutationEvent(other) + { + prevNamespaceURI = other.prevNamespaceURI; + prevNodeName = other.prevNodeName; + } + + + /** + * + */ + virtual ~MutationNameEvent() {} + +protected: + + DOMString prevNamespaceURI; + DOMString prevNodeName; + + +}; + + + + + + +} //namespace events +} //namespace dom +} //namespace w3c +} //namespace org + +#endif /* __EVENTS_H__ */ + +/*######################################################################### +## E N D O F F I L E +#########################################################################*/ + diff --git a/src/dom/events.idl b/src/dom/events.idl new file mode 100755 index 000000000..5773dcaf6 --- /dev/null +++ b/src/dom/events.idl @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2003 World Wide Web Consortium, + * + * (Massachusetts Institute of Technology, European Research Consortium for + * Informatics and Mathematics, Keio University). All Rights Reserved. This + * work is distributed under the W3C(r) Software License [1] in the hope that + * it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * [1] http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 + */ + +// File: http://www.w3.org/TR/2003/NOTE-DOM-Level-3-Events-20031107/events.idl + +#ifndef _EVENTS_IDL_ +#define _EVENTS_IDL_ + +#include "dom.idl" +#include "views.idl" + +#pragma prefix "dom.w3c.org" +module events +{ + + typedef dom::DOMString DOMString; + typedef dom::DOMTimeStamp DOMTimeStamp; + typedef dom::DOMObject DOMObject; + typedef dom::Node Node; + + interface EventTarget; + interface EventListener; + + // Introduced in DOM Level 2: + exception EventException { + unsigned short code; + }; + // EventExceptionCode + const unsigned short UNSPECIFIED_EVENT_TYPE_ERR = 0; + // Introduced in DOM Level 3: + const unsigned short DISPATCH_REQUEST_ERR = 1; + + + // Introduced in DOM Level 2: + interface Event { + + // PhaseType + const unsigned short CAPTURING_PHASE = 1; + const unsigned short AT_TARGET = 2; + const unsigned short BUBBLING_PHASE = 3; + + readonly attribute DOMString type; + readonly attribute EventTarget target; + readonly attribute EventTarget currentTarget; + readonly attribute unsigned short eventPhase; + readonly attribute boolean bubbles; + readonly attribute boolean cancelable; + readonly attribute DOMTimeStamp timeStamp; + void stopPropagation(); + void preventDefault(); + void initEvent(in DOMString eventTypeArg, + in boolean canBubbleArg, + in boolean cancelableArg); + // Introduced in DOM Level 3: + readonly attribute DOMString namespaceURI; + // Introduced in DOM Level 3: + boolean isCustom(); + // Introduced in DOM Level 3: + void stopImmediatePropagation(); + // Introduced in DOM Level 3: + boolean isDefaultPrevented(); + // Introduced in DOM Level 3: + void initEventNS(in DOMString namespaceURIArg, + in DOMString eventTypeArg, + in boolean canBubbleArg, + in boolean cancelableArg); + }; + + // Introduced in DOM Level 2: + interface EventTarget { + void addEventListener(in DOMString type, + in EventListener listener, + in boolean useCapture); + void removeEventListener(in DOMString type, + in EventListener listener, + in boolean useCapture); + // Modified in DOM Level 3: + boolean dispatchEvent(in Event evt) + raises(EventException); + // Introduced in DOM Level 3: + void addEventListenerNS(in DOMString namespaceURI, + in DOMString type, + in EventListener listener, + in boolean useCapture, + in DOMObject evtGroup); + // Introduced in DOM Level 3: + void removeEventListenerNS(in DOMString namespaceURI, + in DOMString type, + in EventListener listener, + in boolean useCapture); + // Introduced in DOM Level 3: + boolean willTriggerNS(in DOMString namespaceURI, + in DOMString type); + // Introduced in DOM Level 3: + boolean hasEventListenerNS(in DOMString namespaceURI, + in DOMString type); + }; + + // Introduced in DOM Level 2: + interface EventListener { + void handleEvent(in Event evt); + }; + + // Introduced in DOM Level 2: + interface DocumentEvent { + Event createEvent(in DOMString eventType) + raises(dom::DOMException); + // Introduced in DOM Level 3: + boolean canDispatch(in DOMString namespaceURI, + in DOMString type); + }; + + // Introduced in DOM Level 3: + interface CustomEvent : Event { + void setDispatchState(in EventTarget target, + in unsigned short phase); + boolean isPropagationStopped(); + boolean isImmediatePropagationStopped(); + }; + + // Introduced in DOM Level 2: + interface UIEvent : Event { + readonly attribute views::AbstractView view; + readonly attribute long detail; + void initUIEvent(in DOMString typeArg, + in boolean canBubbleArg, + in boolean cancelableArg, + in views::AbstractView viewArg, + in long detailArg); + // Introduced in DOM Level 3: + void initUIEventNS(in DOMString namespaceURI, + in DOMString typeArg, + in boolean canBubbleArg, + in boolean cancelableArg, + in views::AbstractView viewArg, + in long detailArg); + }; + + // Introduced in DOM Level 3: + interface TextEvent : UIEvent { + readonly attribute DOMString data; + void initTextEvent(in DOMString typeArg, + in boolean canBubbleArg, + in boolean cancelableArg, + in views::AbstractView viewArg, + in DOMString dataArg); + void initTextEventNS(in DOMString namespaceURI, + in DOMString type, + in boolean canBubbleArg, + in boolean cancelableArg, + in views::AbstractView viewArg, + in DOMString dataArg); + }; + + // Introduced in DOM Level 2: + interface MouseEvent : UIEvent { + readonly attribute long screenX; + readonly attribute long screenY; + readonly attribute long clientX; + readonly attribute long clientY; + readonly attribute boolean ctrlKey; + readonly attribute boolean shiftKey; + readonly attribute boolean altKey; + readonly attribute boolean metaKey; + readonly attribute unsigned short button; + readonly attribute EventTarget relatedTarget; + void initMouseEvent(in DOMString typeArg, + in boolean canBubbleArg, + in boolean cancelableArg, + in views::AbstractView viewArg, + in long detailArg, + in long screenXArg, + in long screenYArg, + in long clientXArg, + in long clientYArg, + in boolean ctrlKeyArg, + in boolean altKeyArg, + in boolean shiftKeyArg, + in boolean metaKeyArg, + in unsigned short buttonArg, + in EventTarget relatedTargetArg); + // Introduced in DOM Level 3: + boolean getModifierState(in DOMString keyIdentifierArg); + // Introduced in DOM Level 3: + void initMouseEventNS(in DOMString namespaceURI, + in DOMString typeArg, + in boolean canBubbleArg, + in boolean cancelableArg, + in views::AbstractView viewArg, + in long detailArg, + in long screenXArg, + in long screenYArg, + in long clientXArg, + in long clientYArg, + in unsigned short buttonArg, + in EventTarget relatedTargetArg, + in DOMString modifiersList); + }; + + // Introduced in DOM Level 3: + interface KeyboardEvent : UIEvent { + + // KeyLocationCode + const unsigned long DOM_KEY_LOCATION_STANDARD = 0x00; + const unsigned long DOM_KEY_LOCATION_LEFT = 0x01; + const unsigned long DOM_KEY_LOCATION_RIGHT = 0x02; + const unsigned long DOM_KEY_LOCATION_NUMPAD = 0x03; + + readonly attribute DOMString keyIdentifier; + readonly attribute unsigned long keyLocation; + readonly attribute boolean ctrlKey; + readonly attribute boolean shiftKey; + readonly attribute boolean altKey; + readonly attribute boolean metaKey; + boolean getModifierState(in DOMString keyIdentifierArg); + void initKeyboardEvent(in DOMString typeArg, + in boolean canBubbleArg, + in boolean cancelableArg, + in views::AbstractView viewArg, + in DOMString keyIdentifierArg, + in unsigned long keyLocationArg, + in DOMString modifiersList); + void initKeyboardEventNS(in DOMString namespaceURI, + in DOMString typeArg, + in boolean canBubbleArg, + in boolean cancelableArg, + in views::AbstractView viewArg, + in DOMString keyIdentifierArg, + in unsigned long keyLocationArg, + in DOMString modifiersList); + }; + + // Introduced in DOM Level 2: + interface MutationEvent : Event { + + // attrChangeType + const unsigned short MODIFICATION = 1; + const unsigned short ADDITION = 2; + const unsigned short REMOVAL = 3; + + readonly attribute Node relatedNode; + readonly attribute DOMString prevValue; + readonly attribute DOMString newValue; + readonly attribute DOMString attrName; + readonly attribute unsigned short attrChange; + void initMutationEvent(in DOMString typeArg, + in boolean canBubbleArg, + in boolean cancelableArg, + in Node relatedNodeArg, + in DOMString prevValueArg, + in DOMString newValueArg, + in DOMString attrNameArg, + in unsigned short attrChangeArg); + // Introduced in DOM Level 3: + void initMutationEventNS(in DOMString namespaceURI, + in DOMString typeArg, + in boolean canBubbleArg, + in boolean cancelableArg, + in Node relatedNodeArg, + in DOMString prevValueArg, + in DOMString newValueArg, + in DOMString attrNameArg, + in unsigned short attrChangeArg); + }; + + // Introduced in DOM Level 3: + interface MutationNameEvent : MutationEvent { + readonly attribute DOMString prevNamespaceURI; + readonly attribute DOMString prevNodeName; + // Introduced in DOM Level 3: + void initMutationNameEvent(in DOMString typeArg, + in boolean canBubbleArg, + in boolean cancelableArg, + in Node relatedNodeArg, + in DOMString prevNamespaceURIArg, + in DOMString prevNodeNameArg); + // Introduced in DOM Level 3: + void initMutationNameEventNS(in DOMString namespaceURI, + in DOMString typeArg, + in boolean canBubbleArg, + in boolean cancelableArg, + in Node relatedNodeArg, + in DOMString prevNamespaceURIArg, + in DOMString prevNodeNameArg); + }; +}; + +#endif // _EVENTS_IDL_ + diff --git a/src/dom/inkscape.css b/src/dom/inkscape.css new file mode 100755 index 000000000..e73b15038 --- /dev/null +++ b/src/dom/inkscape.css @@ -0,0 +1,493 @@ +/* +* CSS for Inkscape Website (http://www.inkscape.org/) +* +* By: Tom von Schwerdtner | Etria LLP (http://www.etria.com/) +* +*/ + +body { + color: #000000; + background: #ffffff; + padding: 0px; + margin: 0px; + font-family: arial, Helvetica, 'Bitstream Vera Sans', 'Luxi Sans', Verdana, Sans-Serif; + font-size: 80%; +} + +a:link, a:visited, a:hover { + font-weight: bold; + color: #0081ac; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +a:visited { + color: #ac0011; +} + +div.top { + background:#0081ac; + width: 100%; + height: 100px; + border-bottom: 1px black solid; +} + +div.top a.logo, +div.top a.logo:link, +div.top a.logo:hover, +div.top a.logo:visited + { + color: #ffffff; + background: transparent; + border: none; + text-decoration: none; +} + +div.top h1 { + color: #ffffff; + background: transparent; + font-size: 40px; + font-family: arial; + margin: 0px; + padding: 20px; + float: left; +} + +img.logo { + float: right; + border: none; +} + +#menu { + float: left; + border: 1px #999999 solid; + width: 150px; + margin-left: 10px; + margin-top: 10px; + margin-bottom: 10px; +} + +#menu ul { + list-style: none; + padding: 0px; + margin: 0px; +} + +#menu li.sub { + color: #000000; + background: #d6d6d6; + font-weight: bold; + padding: 0px; +} + +#menu .title { + padding: 4px; + text-align: center; +} + +#menu ul.sub { + padding: 0px; + margin: 0px; + border-top: 1px #999999 solid; + border-bottom: 1px #999999 solid; + list-style: none; +} + +#menu li a { + display: block; + background: #f0f0f0; + color: #0081ac; + border-top: 1px #d6d6d6 solid; + border-bottom: 1px #d6d6d6 solid; + margin: 0px; + padding: 4px; + padding-left: 10px; + width: 100%; +} +#menu li a:visited { + color: #0081ac; +} + +html>body #menu li a { + width: auto; +} + +#menu li.item a:hover, ul.sub li.item a:hover { + display: block; + background: #ac0011; + /*background: #DC878F;*/ + color: #ffffff; + border-top: 1px #6F000B solid; + border-bottom: 1px #6F000B solid; + text-decoration: none; +} + +#sourceforge { + text-align: center; + border: none; +} + +html>body #sourceforge { + width: auto; +} + +div.content { + padding: 20px; + + /* this is a hack */ + margin-left: 160px; + margin-right: 20px; +} + + +#skipnav { + display: none; +} + +#togglecss { + position: absolute; + top: 110px; + right: 10px; +} + +div.news { +} + +div.news div.item { +} + +div.news div.item img { + margin: 10px; +} + +div.news div.item img.right { + float: right; + margin-right: 0px; +} + +div.news div.item img.left { + float: left; + margin-left: 0px; +} + +img.thumb { + border: 1px #999999 solid; +} + +div.news div.item h3 { + font-weight: bold; + border-left: 10px #999999 solid; + padding-left: 4px; + margin-bottom: 4px; + + clear: right; +} + +div.news div.item p { + margin-top: 0px; + margin-left: 20px; + margin-right: 20px; +} + +p { + text-align: justify; +} + +h2 { + border-bottom: 1px #000000 solid; + clear: right; +} + +pre { + border: 1px #006F02 solid; + background: #B0E4AE; + padding: 4px; +} + + +/* File Releases */ + +div.rss-files { +} + +div.rss-files div.file { + background: #f0f0f0; + color: #000000; + border: 1px #999999 solid; + padding: 2px; + margin: 20px; +} + +div.rss-files div.file div.title { + font-weight: bold; + padding: 4px; + padding-top: 2px; +} +div.rss-files div.file div.description { + font-weight: normal; + border: 1px #999999 solid; + background: #ffffff; + color: #000000; + padding: 4px; +} + +#footer { + text-align: center; + width: 100%; + border-top: 1px #000000 solid; + border-bottom: 1px #000000 solid; + padding-top: 4px; + padding-bottom: 4px; + background:#0081AC; + color: #ffffff; + clear: both; +} + +#footer img { + border: none; +} + +#footer a { + color: white; +} +/* We are overriding some earlier styles here, so this needs to be at the end + */ +/* +a.external:link, +a.external:visited, +a.external:hover, +#menu li a.external, +#menu li a.external:hover +{ + background-image: url('/images/globe.png'); + background-repeat: no-repeat; + background-position: center right; + padding-right: 18px; +} +*/ + +/* Doxygen Specific */ + +div.doxygen { +} + +div.doxygen pre { + background: #F8F8C6; + background: #fffff0; + color: #000000; + border: 1px #808000 solid; +} + +div.doxygen pre a:hover { +} + +div.doxygen pre .preprocessor { + font-weight: bold; + color: #006809; +} + +div.doxygen pre .keyword { + color: #68005F; +} + +div.doxygen pre .keywordflow { + color: #120053; + font-weight: bold; +} + +div.doxygen pre .keywordtype { + font-weight: bold; + color: #495300; +} + +div.doxygen pre .comment { + font-style: italic; + font-weight: bold; +} + + + + +table { + + margin-left: auto; + margin-right: auto; + width: 100%; + + clear: none; + + border-collapse: collapse; + + + background: none; + + +/* font-family: Arial, Helvetica, 'Bitstream Vera Sans', 'Luxi Sans', Verdana, Sans-Serif; */ + font-size: 13px; + +} + +td { + padding: 8px; + vertical-align: top; +} + + +table.roadmap { + background: #eee; +} + +table.roadmap td { + + border: 1px solid #999; + /* width: 50%; */ + + +} + +tr.header { + font-weight: bold; + background: #ddd; +} + +td.title { + text-align: center; + font-size: 16px; + font-weight: bold; + background: #ddd; +} + + + +img.float { + float: right; + + margin-top: 7px; + margin-left: 10px; + /* margin-right: 10px; */ + margin-bottom: 10px; +} + + + +/*@import: url(wiki.css)*/ + + + +/* FORM STUFF */ + +SELECT, option, textarea, input { + + color: #000000; + font-size: 10px; + text-decoration: none; + background: white; + border: 1px solid #666666; + + margin: 2px 0px 2px 0; + padding: 2px; + +} + +/* now make them all have nice hovers */ + + + +SELECT:hover, option:hover, textarea:hover, input:hover { + background: #ddd; +} + + +form.search { + /*display: inline; + background: #f0f0f0; + */ + + text-align: center; + + display: block; + background: #f0f0f0; + color: #0081ac; + border-top: 1px #d6d6d6 solid; + border-bottom: 1px #d6d6d6 solid; + margin: 0px; + padding: 4px; + padding-left: 10px; + + +} + +form.search:hover { + background: #ac0011; +} + + + +#post_news input +{ + width: 35%; +} + +#post_news textarea +{ + width: 100%; + height: 150px; +} + +input#login, +#post_news input#preview, +#post_news input#save, +#post_news input#reset +{ + width: 100px; +} + + +#post_news table +{ + padding: 0; +} + +#post_news td +{ + padding-left: 0; +} +#post_news table tr td.header +{ + width: 10%; + font-weight: bold; +} + +.alert +{ + color: red; +} + + +div.alert +{ + border: 1px solid red; + padding: 8px; +} + +.preview, +.message +{ + border: 1px solid #ccc; + padding: 8px; +} + + +#navbar +{ + padding: 8px; + background: #ccc; +} + +#navbar a +{ + padding-right: 12px; +} diff --git a/src/dom/inkscape.logo.svg b/src/dom/inkscape.logo.svg new file mode 100755 index 000000000..755603b11 --- /dev/null +++ b/src/dom/inkscape.logo.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + diff --git a/src/dom/js/Makefile b/src/dom/js/Makefile new file mode 100644 index 000000000..bd7ee7360 --- /dev/null +++ b/src/dom/js/Makefile @@ -0,0 +1,1471 @@ +# ################################# +# Unix/Linux makefile for libjs.a +# ################################# + + +CC=gcc +CFLAGS= -O2 -DXP_UNIX + +all: js + +INC += -Ifdlibm + +OBJ = \ + fdlibm/e_acos.o \ + fdlibm/e_acosh.o \ + fdlibm/e_asin.o \ + fdlibm/e_atan2.o \ + fdlibm/e_atanh.o \ + fdlibm/e_cosh.o \ + fdlibm/e_exp.o \ + fdlibm/e_fmod.o \ + fdlibm/e_gamma.o \ + fdlibm/e_gamma_r.o \ + fdlibm/e_hypot.o \ + fdlibm/e_j0.o \ + fdlibm/e_j1.o \ + fdlibm/e_jn.o \ + fdlibm/e_lgamma.o \ + fdlibm/e_lgamma_r.o \ + fdlibm/e_log.o \ + fdlibm/e_log10.o \ + fdlibm/e_pow.o \ + fdlibm/e_rem_pio2.o \ + fdlibm/e_remainder.o \ + fdlibm/e_scalb.o \ + fdlibm/e_sinh.o \ + fdlibm/e_sqrt.o \ + fdlibm/k_cos.o \ + fdlibm/k_rem_pio2.o \ + fdlibm/k_sin.o \ + fdlibm/k_standard.o \ + fdlibm/k_tan.o \ + fdlibm/s_asinh.o \ + fdlibm/s_atan.o \ + fdlibm/s_cbrt.o \ + fdlibm/s_ceil.o \ + fdlibm/s_copysign.o \ + fdlibm/s_cos.o \ + fdlibm/s_erf.o \ + fdlibm/s_expm1.o \ + fdlibm/s_fabs.o \ + fdlibm/s_finite.o \ + fdlibm/s_floor.o \ + fdlibm/s_frexp.o \ + fdlibm/s_ilogb.o \ + fdlibm/s_isnan.o \ + fdlibm/s_ldexp.o \ + fdlibm/s_lib_version.o \ + fdlibm/s_log1p.o \ + fdlibm/s_logb.o \ + fdlibm/s_matherr.o \ + fdlibm/s_modf.o \ + fdlibm/s_nextafter.o \ + fdlibm/s_rint.o \ + fdlibm/s_scalbn.o \ + fdlibm/s_signgam.o \ + fdlibm/s_significand.o \ + fdlibm/s_sin.o \ + fdlibm/s_tan.o \ + fdlibm/s_tanh.o \ + fdlibm/w_acos.o \ + fdlibm/w_acosh.o \ + fdlibm/w_asin.o \ + fdlibm/w_atan2.o \ + fdlibm/w_atanh.o \ + fdlibm/w_cosh.o \ + fdlibm/w_exp.o \ + fdlibm/w_fmod.o \ + fdlibm/w_gamma.o \ + fdlibm/w_gamma_r.o \ + fdlibm/w_hypot.o \ + fdlibm/w_j0.o \ + fdlibm/w_j1.o \ + fdlibm/w_jn.o \ + fdlibm/w_lgamma.o \ + fdlibm/w_lgamma_r.o \ + fdlibm/w_log.o \ + fdlibm/w_log10.o \ + fdlibm/w_pow.o \ + fdlibm/w_remainder.o \ + fdlibm/w_scalb.o \ + fdlibm/w_sinh.o \ + fdlibm/w_sqrt.o \ + jsapi.o \ + jsarena.o \ + jsarray.o \ + jsatom.o \ + jsbool.o \ + jscntxt.o \ + jscpucfg.o \ + jsdate.o \ + jsdbgapi.o \ + jsdhash.o \ + jsdtoa.o \ + jsemit.o \ + jsexn.o \ + jsfile.o \ + jsfun.o \ + jsgc.o \ + jshash.o \ + jsinterp.o \ + jslock.o \ + jslog2.o \ + jslong.o \ + jsmath.o \ + jsnum.o \ + jsobj.o \ + jsopcode.o \ + jsparse.o \ + jsprf.o \ + jsregexp.o \ + jsscan.o \ + jsscope.o \ + jsscript.o \ + jsstr.o \ + jsutil.o \ + jsxdrapi.o \ + prmjtime.o + +js: js.o libjs.a + $(CC) -o js js.o libjs.a -lm + +libjs.a: $(OBJ) + ar crv libjs.a $(OBJ) + ranlib libjs.a + +clean: + $(RM) js libjs.a $(OBJ) + +# ########################### +# DEPENDENCIES +# ########################### + + +fdlibm/e_acos.o: \ + fdlibm/e_acos.c \ + fdlibm/fdlibm.h + +fdlibm/e_acosh.o: \ + fdlibm/e_acosh.c \ + fdlibm/fdlibm.h + +fdlibm/e_asin.o: \ + fdlibm/e_asin.c \ + fdlibm/fdlibm.h + +fdlibm/e_atan2.o: \ + fdlibm/e_atan2.c \ + fdlibm/fdlibm.h + +fdlibm/e_atanh.o: \ + fdlibm/e_atanh.c \ + fdlibm/fdlibm.h + +fdlibm/e_cosh.o: \ + fdlibm/e_cosh.c \ + fdlibm/fdlibm.h + +fdlibm/e_exp.o: \ + fdlibm/e_exp.c \ + fdlibm/fdlibm.h + +fdlibm/e_fmod.o: \ + fdlibm/e_fmod.c \ + fdlibm/fdlibm.h + +fdlibm/e_gamma.o: \ + fdlibm/e_gamma.c \ + fdlibm/fdlibm.h + +fdlibm/e_gamma_r.o: \ + fdlibm/e_gamma_r.c \ + fdlibm/fdlibm.h + +fdlibm/e_hypot.o: \ + fdlibm/e_hypot.c \ + fdlibm/fdlibm.h + +fdlibm/e_j0.o: \ + fdlibm/e_j0.c \ + fdlibm/fdlibm.h + +fdlibm/e_j1.o: \ + fdlibm/e_j1.c \ + fdlibm/fdlibm.h + +fdlibm/e_jn.o: \ + fdlibm/e_jn.c \ + fdlibm/fdlibm.h + +fdlibm/e_lgamma.o: \ + fdlibm/e_lgamma.c \ + fdlibm/fdlibm.h + +fdlibm/e_lgamma_r.o: \ + fdlibm/e_lgamma_r.c \ + fdlibm/fdlibm.h + +fdlibm/e_log.o: \ + fdlibm/e_log.c \ + fdlibm/fdlibm.h + +fdlibm/e_log10.o: \ + fdlibm/e_log10.c \ + fdlibm/fdlibm.h + +fdlibm/e_pow.o: \ + fdlibm/e_pow.c \ + fdlibm/fdlibm.h + +fdlibm/e_rem_pio2.o: \ + fdlibm/e_rem_pio2.c \ + fdlibm/fdlibm.h + +fdlibm/e_remainder.o: \ + fdlibm/e_remainder.c \ + fdlibm/fdlibm.h + +fdlibm/e_scalb.o: \ + fdlibm/e_scalb.c \ + fdlibm/fdlibm.h + +fdlibm/e_sinh.o: \ + fdlibm/e_sinh.c \ + fdlibm/fdlibm.h + +fdlibm/e_sqrt.o: \ + fdlibm/e_sqrt.c \ + fdlibm/fdlibm.h + +fdlibm/k_cos.o: \ + fdlibm/k_cos.c \ + fdlibm/fdlibm.h + +fdlibm/k_rem_pio2.o: \ + fdlibm/k_rem_pio2.c \ + fdlibm/fdlibm.h + +fdlibm/k_sin.o: \ + fdlibm/k_sin.c \ + fdlibm/fdlibm.h + +fdlibm/k_standard.o: \ + fdlibm/k_standard.c \ + fdlibm/fdlibm.h + +fdlibm/k_tan.o: \ + fdlibm/k_tan.c \ + fdlibm/fdlibm.h + +fdlibm/s_asinh.o: \ + fdlibm/s_asinh.c \ + fdlibm/fdlibm.h + +fdlibm/s_atan.o: \ + fdlibm/s_atan.c \ + fdlibm/fdlibm.h + +fdlibm/s_cbrt.o: \ + fdlibm/s_cbrt.c \ + fdlibm/fdlibm.h + +fdlibm/s_ceil.o: \ + fdlibm/s_ceil.c \ + fdlibm/fdlibm.h + +fdlibm/s_copysign.o: \ + fdlibm/s_copysign.c \ + fdlibm/fdlibm.h + +fdlibm/s_cos.o: \ + fdlibm/s_cos.c \ + fdlibm/fdlibm.h + +fdlibm/s_erf.o: \ + fdlibm/s_erf.c \ + fdlibm/fdlibm.h + +fdlibm/s_expm1.o: \ + fdlibm/s_expm1.c \ + fdlibm/fdlibm.h + +fdlibm/s_fabs.o: \ + fdlibm/s_fabs.c \ + fdlibm/fdlibm.h + +fdlibm/s_finite.o: \ + fdlibm/s_finite.c \ + fdlibm/fdlibm.h + +fdlibm/s_floor.o: \ + fdlibm/s_floor.c \ + fdlibm/fdlibm.h + +fdlibm/s_frexp.o: \ + fdlibm/s_frexp.c \ + fdlibm/fdlibm.h + +fdlibm/s_ilogb.o: \ + fdlibm/s_ilogb.c \ + fdlibm/fdlibm.h + +fdlibm/s_isnan.o: \ + fdlibm/s_isnan.c \ + fdlibm/fdlibm.h + +fdlibm/s_ldexp.o: \ + fdlibm/s_ldexp.c \ + fdlibm/fdlibm.h + +fdlibm/s_lib_version.o: \ + fdlibm/s_lib_version.c \ + fdlibm/fdlibm.h + +fdlibm/s_log1p.o: \ + fdlibm/s_log1p.c \ + fdlibm/fdlibm.h + +fdlibm/s_logb.o: \ + fdlibm/s_logb.c \ + fdlibm/fdlibm.h + +fdlibm/s_matherr.o: \ + fdlibm/s_matherr.c \ + fdlibm/fdlibm.h + +fdlibm/s_modf.o: \ + fdlibm/s_modf.c \ + fdlibm/fdlibm.h + +fdlibm/s_nextafter.o: \ + fdlibm/s_nextafter.c \ + fdlibm/fdlibm.h + +fdlibm/s_rint.o: \ + fdlibm/s_rint.c \ + fdlibm/fdlibm.h + +fdlibm/s_scalbn.o: \ + fdlibm/s_scalbn.c \ + fdlibm/fdlibm.h + +fdlibm/s_signgam.o: \ + fdlibm/s_signgam.c \ + fdlibm/fdlibm.h + +fdlibm/s_significand.o: \ + fdlibm/s_significand.c \ + fdlibm/fdlibm.h + +fdlibm/s_sin.o: \ + fdlibm/s_sin.c \ + fdlibm/fdlibm.h + +fdlibm/s_tan.o: \ + fdlibm/s_tan.c \ + fdlibm/fdlibm.h + +fdlibm/s_tanh.o: \ + fdlibm/s_tanh.c \ + fdlibm/fdlibm.h + +fdlibm/w_acos.o: \ + fdlibm/w_acos.c \ + fdlibm/fdlibm.h + +fdlibm/w_acosh.o: \ + fdlibm/w_acosh.c \ + fdlibm/fdlibm.h + +fdlibm/w_asin.o: \ + fdlibm/w_asin.c \ + fdlibm/fdlibm.h + +fdlibm/w_atan2.o: \ + fdlibm/w_atan2.c \ + fdlibm/fdlibm.h + +fdlibm/w_atanh.o: \ + fdlibm/w_atanh.c \ + fdlibm/fdlibm.h + +fdlibm/w_cosh.o: \ + fdlibm/w_cosh.c \ + fdlibm/fdlibm.h + +fdlibm/w_exp.o: \ + fdlibm/w_exp.c \ + fdlibm/fdlibm.h + +fdlibm/w_fmod.o: \ + fdlibm/w_fmod.c \ + fdlibm/fdlibm.h + +fdlibm/w_gamma.o: \ + fdlibm/w_gamma.c \ + fdlibm/fdlibm.h + +fdlibm/w_gamma_r.o: \ + fdlibm/w_gamma_r.c \ + fdlibm/fdlibm.h + +fdlibm/w_hypot.o: \ + fdlibm/w_hypot.c \ + fdlibm/fdlibm.h + +fdlibm/w_j0.o: \ + fdlibm/w_j0.c \ + fdlibm/fdlibm.h + +fdlibm/w_j1.o: \ + fdlibm/w_j1.c \ + fdlibm/fdlibm.h + +fdlibm/w_jn.o: \ + fdlibm/w_jn.c \ + fdlibm/fdlibm.h + +fdlibm/w_lgamma.o: \ + fdlibm/w_lgamma.c \ + fdlibm/fdlibm.h + +fdlibm/w_lgamma_r.o: \ + fdlibm/w_lgamma_r.c \ + fdlibm/fdlibm.h + +fdlibm/w_log.o: \ + fdlibm/w_log.c \ + fdlibm/fdlibm.h + +fdlibm/w_log10.o: \ + fdlibm/w_log10.c \ + fdlibm/fdlibm.h + +fdlibm/w_pow.o: \ + fdlibm/w_pow.c \ + fdlibm/fdlibm.h + +fdlibm/w_remainder.o: \ + fdlibm/w_remainder.c \ + fdlibm/fdlibm.h + +fdlibm/w_scalb.o: \ + fdlibm/w_scalb.c \ + fdlibm/fdlibm.h + +fdlibm/w_sinh.o: \ + fdlibm/w_sinh.c \ + fdlibm/fdlibm.h + +fdlibm/w_sqrt.o: \ + fdlibm/w_sqrt.c \ + fdlibm/fdlibm.h + +js.o: \ + js.c \ + jsapi.h \ + jsarena.h \ + jsatom.h \ + jsclist.h \ + jscntxt.h \ + jscompat.h \ + jsconfig.h \ + jscpucfg.h \ + jsdbgapi.h \ + jsdhash.h \ + jsemit.h \ + jsfun.h \ + jsgc.h \ + jshash.h \ + jsinterp.h \ + jslock.h \ + jslong.h \ + jsobj.h \ + jsopcode.h \ + jsosdep.h \ + jsotypes.h \ + jsparse.h \ + jsprf.h \ + jsprvtd.h \ + jspubtd.h \ + jsregexp.h \ + jsscan.h \ + jsscope.h \ + jsscript.h \ + jsstddef.h \ + jsstr.h \ + jstypes.h \ + jsutil.h + +jsapi.o: \ + jsapi.c \ + jsapi.h \ + jsarena.h \ + jsarray.h \ + jsatom.h \ + jsbool.h \ + jsclist.h \ + jscntxt.h \ + jscompat.h \ + jsconfig.h \ + jscpucfg.h \ + jsdate.h \ + jsdhash.h \ + jsdtoa.h \ + jsemit.h \ + jsexn.h \ + jsfile.h \ + jsfun.h \ + jsgc.h \ + jshash.h \ + jsinterp.h \ + jslock.h \ + jslong.h \ + jsmath.h \ + jsnum.h \ + jsobj.h \ + jsopcode.h \ + jsosdep.h \ + jsotypes.h \ + jsparse.h \ + jsprf.h \ + jsprvtd.h \ + jspubtd.h \ + jsregexp.h \ + jsscan.h \ + jsscope.h \ + jsscript.h \ + jsstddef.h \ + jsstr.h \ + jstypes.h \ + jsutil.h \ + prmjtime.h + +jsarena.o: \ + jsarena.c \ + jsarena.h \ + jsbit.h \ + jscompat.h \ + jscpucfg.h \ + jshash.h \ + jslock.h \ + jslong.h \ + jsobj.h \ + jsosdep.h \ + jsotypes.h \ + jsprvtd.h \ + jspubtd.h \ + jsscope.h \ + jsstddef.h \ + jstypes.h \ + jsutil.h + +jsarray.o: \ + jsarray.c \ + jsapi.h \ + jsarena.h \ + jsarray.h \ + jsatom.h \ + jsclist.h \ + jscntxt.h \ + jscompat.h \ + jsconfig.h \ + jscpucfg.h \ + jsdhash.h \ + jsfun.h \ + jsgc.h \ + jshash.h \ + jsinterp.h \ + jslock.h \ + jslong.h \ + jsnum.h \ + jsobj.h \ + jsosdep.h \ + jsotypes.h \ + jsprvtd.h \ + jspubtd.h \ + jsregexp.h \ + jsscope.h \ + jsstddef.h \ + jsstr.h \ + jstypes.h \ + jsutil.h + +jsatom.o: \ + jsatom.c \ + jsapi.h \ + jsarena.h \ + jsatom.h \ + jsclist.h \ + jscntxt.h \ + jscompat.h \ + jsconfig.h \ + jscpucfg.h \ + jsdhash.h \ + jsgc.h \ + jshash.h \ + jsinterp.h \ + jslock.h \ + jslong.h \ + jsnum.h \ + jsobj.h \ + jsopcode.h \ + jsosdep.h \ + jsotypes.h \ + jsprf.h \ + jsprvtd.h \ + jspubtd.h \ + jsregexp.h \ + jsscope.h \ + jsstddef.h \ + jsstr.h \ + jstypes.h \ + jsutil.h + +jsbool.o: \ + jsbool.c \ + jsapi.h \ + jsarena.h \ + jsatom.h \ + jsbool.h \ + jsclist.h \ + jscntxt.h \ + jscompat.h \ + jsconfig.h \ + jscpucfg.h \ + jsdhash.h \ + jsgc.h \ + jshash.h \ + jsinterp.h \ + jslock.h \ + jslong.h \ + jsnum.h \ + jsobj.h \ + jsosdep.h \ + jsotypes.h \ + jsprf.h \ + jsprvtd.h \ + jspubtd.h \ + jsregexp.h \ + jsscope.h \ + jsstddef.h \ + jsstr.h \ + jstypes.h \ + jsutil.h + +jscntxt.o: \ + jscntxt.c \ + jsapi.h \ + jsarena.h \ + jsatom.h \ + jsclist.h \ + jscntxt.h \ + jscompat.h \ + jsconfig.h \ + jscpucfg.h \ + jsdbgapi.h \ + jsdhash.h \ + jsexn.h \ + jsgc.h \ + jshash.h \ + jsinterp.h \ + jslock.h \ + jslong.h \ + jsnum.h \ + jsobj.h \ + jsopcode.h \ + jsosdep.h \ + jsotypes.h \ + jsprf.h \ + jsprvtd.h \ + jspubtd.h \ + jsregexp.h \ + jsscan.h \ + jsscope.h \ + jsscript.h \ + jsstddef.h \ + jsstr.h \ + jstypes.h \ + jsutil.h + +jscpucfg.o: \ + jscpucfg.c + +jsdate.o: \ + jsdate.c \ + jsapi.h \ + jsarena.h \ + jsatom.h \ + jsclist.h \ + jscntxt.h \ + jscompat.h \ + jsconfig.h \ + jscpucfg.h \ + jsdate.h \ + jsdhash.h \ + jsdtoa.h \ + jsgc.h \ + jshash.h \ + jsinterp.h \ + jslock.h \ + jslong.h \ + jsnum.h \ + jsobj.h \ + jsosdep.h \ + jsotypes.h \ + jsprf.h \ + jsprvtd.h \ + jspubtd.h \ + jsregexp.h \ + jsscope.h \ + jsstddef.h \ + jsstr.h \ + jstypes.h \ + jsutil.h \ + prmjtime.h + +jsdbgapi.o: \ + jsdbgapi.c \ + jsapi.h \ + jsarena.h \ + jsatom.h \ + jsclist.h \ + jscntxt.h \ + jscompat.h \ + jsconfig.h \ + jscpucfg.h \ + jsdbgapi.h \ + jsdhash.h \ + jsemit.h \ + jsfun.h \ + jsgc.h \ + jshash.h \ + jsinterp.h \ + jslock.h \ + jslong.h \ + jsobj.h \ + jsopcode.h \ + jsosdep.h \ + jsotypes.h \ + jsprvtd.h \ + jspubtd.h \ + jsregexp.h \ + jsscope.h \ + jsscript.h \ + jsstddef.h \ + jsstr.h \ + jstypes.h \ + jsutil.h + +jsdhash.o: \ + jsdhash.c \ + jsbit.h \ + jscpucfg.h \ + jsdhash.h \ + jsosdep.h \ + jsotypes.h \ + jstypes.h \ + jsutil.h + +jsdtoa.o: \ + jsdtoa.c \ + jscompat.h \ + jsconfig.h \ + jscpucfg.h \ + jsdtoa.h \ + jslibmath.h \ + jslong.h \ + jsnum.h \ + jsosdep.h \ + jsotypes.h \ + jsprf.h \ + jspubtd.h \ + jsstddef.h \ + jstypes.h \ + jsutil.h + +jsemit.o: \ + jsemit.c \ + jsapi.h \ + jsarena.h \ + jsatom.h \ + jsbit.h \ + jsclist.h \ + jscntxt.h \ + jscompat.h \ + jsconfig.h \ + jscpucfg.h \ + jsdhash.h \ + jsemit.h \ + jsfun.h \ + jsgc.h \ + jshash.h \ + jsinterp.h \ + jslock.h \ + jslong.h \ + jsnum.h \ + jsobj.h \ + jsopcode.h \ + jsosdep.h \ + jsotypes.h \ + jsparse.h \ + jsprf.h \ + jsprvtd.h \ + jspubtd.h \ + jsregexp.h \ + jsscan.h \ + jsscope.h \ + jsscript.h \ + jsstddef.h \ + jsstr.h \ + jstypes.h \ + jsutil.h + +jsexn.o: \ + jsexn.c \ + jsapi.h \ + jsarena.h \ + jsatom.h \ + jsbit.h \ + jsclist.h \ + jscntxt.h \ + jscompat.h \ + jsconfig.h \ + jscpucfg.h \ + jsdhash.h \ + jsexn.h \ + jsfun.h \ + jsgc.h \ + jshash.h \ + jsinterp.h \ + jslock.h \ + jslong.h \ + jsnum.h \ + jsobj.h \ + jsopcode.h \ + jsosdep.h \ + jsotypes.h \ + jsprf.h \ + jsprvtd.h \ + jspubtd.h \ + jsregexp.h \ + jsscope.h \ + jsscript.h \ + jsstddef.h \ + jsstr.h \ + jstypes.h \ + jsutil.h + +jsfile.o: \ + jsfile.c \ + jsapi.h \ + jsarena.h \ + jsatom.h \ + jsclist.h \ + jscntxt.h \ + jscompat.h \ + jsconfig.h \ + jscpucfg.h \ + jsdate.h \ + jsdbgapi.h \ + jsdhash.h \ + jsemit.h \ + jsfun.h \ + jsgc.h \ + jshash.h \ + jsinterp.h \ + jslock.h \ + jslong.h \ + jsobj.h \ + jsopcode.h \ + jsosdep.h \ + jsotypes.h \ + jsparse.h \ + jsprvtd.h \ + jspubtd.h \ + jsregexp.h \ + jsscan.h \ + jsscope.h \ + jsscript.h \ + jsstddef.h \ + jsstr.h \ + jstypes.h \ + jsutil.h + +jsfun.o: \ + jsfun.c \ + jsapi.h \ + jsarena.h \ + jsarray.h \ + jsatom.h \ + jsbit.h \ + jsclist.h \ + jscntxt.h \ + jscompat.h \ + jsconfig.h \ + jscpucfg.h \ + jsdbgapi.h \ + jsdhash.h \ + jsexn.h \ + jsfun.h \ + jsgc.h \ + jshash.h \ + jsinterp.h \ + jslock.h \ + jslong.h \ + jsnum.h \ + jsobj.h \ + jsopcode.h \ + jsosdep.h \ + jsotypes.h \ + jsparse.h \ + jsprvtd.h \ + jspubtd.h \ + jsregexp.h \ + jsscan.h \ + jsscope.h \ + jsscript.h \ + jsstddef.h \ + jsstr.h \ + jstypes.h \ + jsutil.h \ + jsxdrapi.h + +jsgc.o: \ + jsgc.c \ + jsapi.h \ + jsarena.h \ + jsatom.h \ + jsclist.h \ + jscntxt.h \ + jscompat.h \ + jsconfig.h \ + jscpucfg.h \ + jsdbgapi.h \ + jsdhash.h \ + jsfun.h \ + jsgc.h \ + jshash.h \ + jsinterp.h \ + jslock.h \ + jslong.h \ + jsnum.h \ + jsobj.h \ + jsopcode.h \ + jsosdep.h \ + jsotypes.h \ + jsprf.h \ + jsprvtd.h \ + jspubtd.h \ + jsregexp.h \ + jsscope.h \ + jsscript.h \ + jsstddef.h \ + jsstr.h \ + jstypes.h \ + jsutil.h + +jshash.o: \ + jshash.c \ + jsbit.h \ + jscompat.h \ + jscpucfg.h \ + jshash.h \ + jslong.h \ + jsosdep.h \ + jsotypes.h \ + jsstddef.h \ + jstypes.h \ + jsutil.h + +jsinterp.o: \ + jsinterp.c \ + jsapi.h \ + jsarena.h \ + jsarray.h \ + jsatom.h \ + jsbool.h \ + jsclist.h \ + jscntxt.h \ + jscompat.h \ + jsconfig.h \ + jscpucfg.h \ + jsdbgapi.h \ + jsdhash.h \ + jsfun.h \ + jsgc.h \ + jshash.h \ + jsinterp.h \ + jslock.h \ + jslong.h \ + jsnum.h \ + jsobj.h \ + jsopcode.h \ + jsosdep.h \ + jsotypes.h \ + jsprf.h \ + jsprvtd.h \ + jspubtd.h \ + jsregexp.h \ + jsscope.h \ + jsscript.h \ + jsstddef.h \ + jsstr.h \ + jstypes.h \ + jsutil.h + +jslock.o: \ + jslock.c \ + jsapi.h \ + jsarena.h \ + jsatom.h \ + jsbit.h \ + jsclist.h \ + jscntxt.h \ + jscompat.h \ + jsconfig.h \ + jscpucfg.h \ + jsdhash.h \ + jsdtoa.h \ + jsgc.h \ + jshash.h \ + jsinterp.h \ + jslock.h \ + jslong.h \ + jsobj.h \ + jsosdep.h \ + jsotypes.h \ + jsprvtd.h \ + jspubtd.h \ + jsregexp.h \ + jsscope.h \ + jsstddef.h \ + jsstr.h \ + jstypes.h \ + jsutil.h + +jslog2.o: \ + jslog2.c \ + jsbit.h \ + jscpucfg.h \ + jsosdep.h \ + jsotypes.h \ + jsstddef.h \ + jstypes.h + +jslong.o: \ + jslong.c \ + jscpucfg.h \ + jslong.h \ + jsosdep.h \ + jsotypes.h \ + jsstddef.h \ + jstypes.h + +jsmath.o: \ + jsmath.c \ + jsapi.h \ + jsarena.h \ + jsatom.h \ + jsclist.h \ + jscntxt.h \ + jscompat.h \ + jsconfig.h \ + jscpucfg.h \ + jsdhash.h \ + jsgc.h \ + jshash.h \ + jsinterp.h \ + jslibmath.h \ + jslock.h \ + jslong.h \ + jsmath.h \ + jsnum.h \ + jsobj.h \ + jsosdep.h \ + jsotypes.h \ + jsprvtd.h \ + jspubtd.h \ + jsregexp.h \ + jsscope.h \ + jsstddef.h \ + jsstr.h \ + jstypes.h \ + prmjtime.h + +jsnum.o: \ + jsnum.c \ + jsapi.h \ + jsarena.h \ + jsatom.h \ + jsclist.h \ + jscntxt.h \ + jscompat.h \ + jsconfig.h \ + jscpucfg.h \ + jsdhash.h \ + jsdtoa.h \ + jsgc.h \ + jshash.h \ + jsinterp.h \ + jslock.h \ + jslong.h \ + jsnum.h \ + jsobj.h \ + jsopcode.h \ + jsosdep.h \ + jsotypes.h \ + jsprf.h \ + jsprvtd.h \ + jspubtd.h \ + jsregexp.h \ + jsscope.h \ + jsstddef.h \ + jsstr.h \ + jstypes.h \ + jsutil.h + +jsobj.o: \ + jsobj.c \ + jsapi.h \ + jsarena.h \ + jsarray.h \ + jsatom.h \ + jsbool.h \ + jsclist.h \ + jscntxt.h \ + jscompat.h \ + jsconfig.h \ + jscpucfg.h \ + jsdbgapi.h \ + jsdhash.h \ + jsfun.h \ + jsgc.h \ + jshash.h \ + jsinterp.h \ + jslock.h \ + jslong.h \ + jsnum.h \ + jsobj.h \ + jsopcode.h \ + jsosdep.h \ + jsotypes.h \ + jsprf.h \ + jsprvtd.h \ + jspubtd.h \ + jsregexp.h \ + jsscope.h \ + jsscript.h \ + jsstddef.h \ + jsstr.h \ + jstypes.h \ + jsutil.h \ + jsxdrapi.h + +jsopcode.o: \ + jsopcode.c \ + jsapi.h \ + jsarena.h \ + jsarray.h \ + jsatom.h \ + jsclist.h \ + jscntxt.h \ + jscompat.h \ + jsconfig.h \ + jscpucfg.h \ + jsdbgapi.h \ + jsdhash.h \ + jsdtoa.h \ + jsemit.h \ + jsfun.h \ + jsgc.h \ + jshash.h \ + jsinterp.h \ + jslock.h \ + jslong.h \ + jsobj.h \ + jsopcode.h \ + jsosdep.h \ + jsotypes.h \ + jsprf.h \ + jsprvtd.h \ + jspubtd.h \ + jsregexp.h \ + jsscope.h \ + jsscript.h \ + jsstddef.h \ + jsstr.h \ + jstypes.h \ + jsutil.h + +jsparse.o: \ + jsparse.c \ + jsapi.h \ + jsarena.h \ + jsatom.h \ + jsclist.h \ + jscntxt.h \ + jscompat.h \ + jsconfig.h \ + jscpucfg.h \ + jsdhash.h \ + jsemit.h \ + jsfun.h \ + jsgc.h \ + jshash.h \ + jsinterp.h \ + jslock.h \ + jslong.h \ + jsnum.h \ + jsobj.h \ + jsopcode.h \ + jsosdep.h \ + jsotypes.h \ + jsparse.h \ + jsprvtd.h \ + jspubtd.h \ + jsregexp.h \ + jsscan.h \ + jsscope.h \ + jsscript.h \ + jsstddef.h \ + jsstr.h \ + jstypes.h \ + jsutil.h + +jsprf.o: \ + jsprf.c \ + jscpucfg.h \ + jslong.h \ + jsosdep.h \ + jsotypes.h \ + jsprf.h \ + jsstddef.h \ + jstypes.h \ + jsutil.h + +jsregexp.o: \ + jsregexp.c \ + jsapi.h \ + jsarena.h \ + jsarray.h \ + jsatom.h \ + jsclist.h \ + jscntxt.h \ + jscompat.h \ + jsconfig.h \ + jscpucfg.h \ + jsdhash.h \ + jsfun.h \ + jsgc.h \ + jshash.h \ + jsinterp.h \ + jslock.h \ + jslong.h \ + jsnum.h \ + jsobj.h \ + jsopcode.h \ + jsosdep.h \ + jsotypes.h \ + jsprvtd.h \ + jspubtd.h \ + jsregexp.h \ + jsscan.h \ + jsscope.h \ + jsstddef.h \ + jsstr.h \ + jstypes.h \ + jsutil.h \ + jsxdrapi.h + +jsscan.o: \ + jsscan.c \ + jsapi.h \ + jsarena.h \ + jsatom.h \ + jsclist.h \ + jscntxt.h \ + jscompat.h \ + jsconfig.h \ + jscpucfg.h \ + jsdhash.h \ + jsdtoa.h \ + jsemit.h \ + jsexn.h \ + jsgc.h \ + jshash.h \ + jsinterp.h \ + jslock.h \ + jslong.h \ + jsnum.h \ + jsobj.h \ + jsopcode.h \ + jsosdep.h \ + jsotypes.h \ + jsprf.h \ + jsprvtd.h \ + jspubtd.h \ + jsregexp.h \ + jsscan.h \ + jsscope.h \ + jsstddef.h \ + jsstr.h \ + jstypes.h \ + jsutil.h + +jsscope.o: \ + jsscope.c \ + jsapi.h \ + jsarena.h \ + jsatom.h \ + jsbit.h \ + jsclist.h \ + jscntxt.h \ + jscompat.h \ + jsconfig.h \ + jscpucfg.h \ + jsdbgapi.h \ + jsdhash.h \ + jsgc.h \ + jshash.h \ + jsinterp.h \ + jslock.h \ + jslong.h \ + jsnum.h \ + jsobj.h \ + jsopcode.h \ + jsosdep.h \ + jsotypes.h \ + jsprvtd.h \ + jspubtd.h \ + jsregexp.h \ + jsscope.h \ + jsstddef.h \ + jsstr.h \ + jstypes.h \ + jsutil.h + +jsscript.o: \ + jsscript.c \ + jsapi.h \ + jsarena.h \ + jsatom.h \ + jsclist.h \ + jscntxt.h \ + jscompat.h \ + jsconfig.h \ + jscpucfg.h \ + jsdbgapi.h \ + jsdhash.h \ + jsemit.h \ + jsfun.h \ + jsgc.h \ + jshash.h \ + jsinterp.h \ + jslock.h \ + jslong.h \ + jsnum.h \ + jsobj.h \ + jsopcode.h \ + jsosdep.h \ + jsotypes.h \ + jsprf.h \ + jsprvtd.h \ + jspubtd.h \ + jsregexp.h \ + jsscope.h \ + jsscript.h \ + jsstddef.h \ + jsstr.h \ + jstypes.h \ + jsutil.h \ + jsxdrapi.h + +jsstr.o: \ + jsstr.c \ + jsapi.h \ + jsarena.h \ + jsarray.h \ + jsatom.h \ + jsbool.h \ + jsclist.h \ + jscntxt.h \ + jscompat.h \ + jsconfig.h \ + jscpucfg.h \ + jsdhash.h \ + jsgc.h \ + jshash.h \ + jsinterp.h \ + jslock.h \ + jslong.h \ + jsnum.h \ + jsobj.h \ + jsopcode.h \ + jsosdep.h \ + jsotypes.h \ + jsprf.h \ + jsprvtd.h \ + jspubtd.h \ + jsregexp.h \ + jsscope.h \ + jsstddef.h \ + jsstr.h \ + jstypes.h \ + jsutil.h + +jsutil.o: \ + jsutil.c \ + jscpucfg.h \ + jsosdep.h \ + jsotypes.h \ + jsprf.h \ + jsstddef.h \ + jstypes.h \ + jsutil.h + +jsxdrapi.o: \ + jsxdrapi.c \ + jsapi.h \ + jsarena.h \ + jsatom.h \ + jsclist.h \ + jscntxt.h \ + jscompat.h \ + jsconfig.h \ + jscpucfg.h \ + jsdhash.h \ + jsgc.h \ + jshash.h \ + jsinterp.h \ + jslock.h \ + jslong.h \ + jsobj.h \ + jsosdep.h \ + jsotypes.h \ + jsprf.h \ + jsprvtd.h \ + jspubtd.h \ + jsregexp.h \ + jsscope.h \ + jsscript.h \ + jsstddef.h \ + jsstr.h \ + jstypes.h \ + jsutil.h \ + jsxdrapi.h + +prmjtime.o: \ + prmjtime.c \ + jscompat.h \ + jscpucfg.h \ + jslong.h \ + jsosdep.h \ + jsotypes.h \ + jsprf.h \ + jsstddef.h \ + jstypes.h \ + jsutil.h \ + prmjtime.h + diff --git a/src/dom/js/README b/src/dom/js/README new file mode 100644 index 000000000..cca453245 --- /dev/null +++ b/src/dom/js/README @@ -0,0 +1,20 @@ +//######################################################################### +//# $Id$ +//######################################################################### +See: + +$PROJECT/src/ecma/README + +for more information. + +The source is unchanged from Mozilla.org's distribution with +the minor difference: + +Line 142 in jstypes.h: +#if defined(_WIN32) && !defined(__MWERKS__) +is modified to +#if defined(_WIN32) && !defined(__MWERKS__) && !defined(__GNUC__) +to allow for static linking in Mingw on Win32. + + + diff --git a/src/dom/js/fdlibm/e_acos.c b/src/dom/js/fdlibm/e_acos.c new file mode 100644 index 000000000..a07c1eebc --- /dev/null +++ b/src/dom/js/fdlibm/e_acos.c @@ -0,0 +1,147 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)e_acos.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* __ieee754_acos(x) + * Method : + * acos(x) = pi/2 - asin(x) + * acos(-x) = pi/2 + asin(x) + * For |x|<=0.5 + * acos(x) = pi/2 - (x + x*x^2*R(x^2)) (see asin.c) + * For x>0.5 + * acos(x) = pi/2 - (pi/2 - 2asin(sqrt((1-x)/2))) + * = 2asin(sqrt((1-x)/2)) + * = 2s + 2s*z*R(z) ...z=(1-x)/2, s=sqrt(z) + * = 2f + (2c + 2s*z*R(z)) + * where f=hi part of s, and c = (z-f*f)/(s+f) is the correction term + * for f so that f+c ~ sqrt(z). + * For x<-0.5 + * acos(x) = pi - 2asin(sqrt((1-|x|)/2)) + * = pi - 0.5*(s+s*z*R(z)), where z=(1-|x|)/2,s=sqrt(z) + * + * Special cases: + * if x is NaN, return x itself; + * if |x|>1, return NaN with invalid signal. + * + * Function needed: sqrt + */ + +#include "fdlibm.h" + +#ifdef __STDC__ +static const double +#else +static double +#endif +one= 1.00000000000000000000e+00, /* 0x3FF00000, 0x00000000 */ +pi = 3.14159265358979311600e+00, /* 0x400921FB, 0x54442D18 */ +pio2_hi = 1.57079632679489655800e+00, /* 0x3FF921FB, 0x54442D18 */ +pio2_lo = 6.12323399573676603587e-17, /* 0x3C91A626, 0x33145C07 */ +pS0 = 1.66666666666666657415e-01, /* 0x3FC55555, 0x55555555 */ +pS1 = -3.25565818622400915405e-01, /* 0xBFD4D612, 0x03EB6F7D */ +pS2 = 2.01212532134862925881e-01, /* 0x3FC9C155, 0x0E884455 */ +pS3 = -4.00555345006794114027e-02, /* 0xBFA48228, 0xB5688F3B */ +pS4 = 7.91534994289814532176e-04, /* 0x3F49EFE0, 0x7501B288 */ +pS5 = 3.47933107596021167570e-05, /* 0x3F023DE1, 0x0DFDF709 */ +qS1 = -2.40339491173441421878e+00, /* 0xC0033A27, 0x1C8A2D4B */ +qS2 = 2.02094576023350569471e+00, /* 0x40002AE5, 0x9C598AC8 */ +qS3 = -6.88283971605453293030e-01, /* 0xBFE6066C, 0x1B8D0159 */ +qS4 = 7.70381505559019352791e-02; /* 0x3FB3B8C5, 0xB12E9282 */ + +#ifdef __STDC__ + double __ieee754_acos(double x) +#else + double __ieee754_acos(x) + double x; +#endif +{ + fd_twoints u; + double df; + double z,p,q,r,w,s,c; + int hx,ix; + u.d = x; + hx = __HI(u); + ix = hx&0x7fffffff; + if(ix>=0x3ff00000) { /* |x| >= 1 */ + if(((ix-0x3ff00000)|__LO(u))==0) { /* |x|==1 */ + if(hx>0) return 0.0; /* acos(1) = 0 */ + else return pi+2.0*pio2_lo; /* acos(-1)= pi */ + } + return (x-x)/(x-x); /* acos(|x|>1) is NaN */ + } + if(ix<0x3fe00000) { /* |x| < 0.5 */ + if(ix<=0x3c600000) return pio2_hi+pio2_lo;/*if|x|<2**-57*/ + z = x*x; + p = z*(pS0+z*(pS1+z*(pS2+z*(pS3+z*(pS4+z*pS5))))); + q = one+z*(qS1+z*(qS2+z*(qS3+z*qS4))); + r = p/q; + return pio2_hi - (x - (pio2_lo-x*r)); + } else if (hx<0) { /* x < -0.5 */ + z = (one+x)*0.5; + p = z*(pS0+z*(pS1+z*(pS2+z*(pS3+z*(pS4+z*pS5))))); + q = one+z*(qS1+z*(qS2+z*(qS3+z*qS4))); + s = fd_sqrt(z); + r = p/q; + w = r*s-pio2_lo; + return pi - 2.0*(s+w); + } else { /* x > 0.5 */ + z = (one-x)*0.5; + s = fd_sqrt(z); + u.d = s; + __LO(u) = 0; + df = u.d; + c = (z-df*df)/(s+df); + p = z*(pS0+z*(pS1+z*(pS2+z*(pS3+z*(pS4+z*pS5))))); + q = one+z*(qS1+z*(qS2+z*(qS3+z*qS4))); + r = p/q; + w = r*s+c; + return 2.0*(df+w); + } +} diff --git a/src/dom/js/fdlibm/e_acosh.c b/src/dom/js/fdlibm/e_acosh.c new file mode 100644 index 000000000..725cceefb --- /dev/null +++ b/src/dom/js/fdlibm/e_acosh.c @@ -0,0 +1,105 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)e_acosh.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + * + */ + +/* __ieee754_acosh(x) + * Method : + * Based on + * acosh(x) = log [ x + sqrt(x*x-1) ] + * we have + * acosh(x) := log(x)+ln2, if x is large; else + * acosh(x) := log(2x-1/(sqrt(x*x-1)+x)) if x>2; else + * acosh(x) := log1p(t+sqrt(2.0*t+t*t)); where t=x-1. + * + * Special cases: + * acosh(x) is NaN with signal if x<1. + * acosh(NaN) is NaN without signal. + */ + +#include "fdlibm.h" + +#ifdef __STDC__ +static const double +#else +static double +#endif +one = 1.0, +ln2 = 6.93147180559945286227e-01; /* 0x3FE62E42, 0xFEFA39EF */ + +#ifdef __STDC__ + double __ieee754_acosh(double x) +#else + double __ieee754_acosh(x) + double x; +#endif +{ + fd_twoints u; + double t; + int hx; + u.d = x; + hx = __HI(u); + if(hx<0x3ff00000) { /* x < 1 */ + return (x-x)/(x-x); + } else if(hx >=0x41b00000) { /* x > 2**28 */ + if(hx >=0x7ff00000) { /* x is inf of NaN */ + return x+x; + } else + return __ieee754_log(x)+ln2; /* acosh(huge)=log(2x) */ + } else if(((hx-0x3ff00000)|__LO(u))==0) { + return 0.0; /* acosh(1) = 0 */ + } else if (hx > 0x40000000) { /* 2**28 > x > 2 */ + t=x*x; + return __ieee754_log(2.0*x-one/(x+fd_sqrt(t-one))); + } else { /* 10.98 + * asin(x) = pi/2 - 2*(s+s*z*R(z)) + * = pio2_hi - (2*(s+s*z*R(z)) - pio2_lo) + * For x<=0.98, let pio4_hi = pio2_hi/2, then + * f = hi part of s; + * c = sqrt(z) - f = (z-f*f)/(s+f) ...f+c=sqrt(z) + * and + * asin(x) = pi/2 - 2*(s+s*z*R(z)) + * = pio4_hi+(pio4-2s)-(2s*z*R(z)-pio2_lo) + * = pio4_hi+(pio4-2f)-(2s*z*R(z)-(pio2_lo+2c)) + * + * Special cases: + * if x is NaN, return x itself; + * if |x|>1, return NaN with invalid signal. + * + */ + + +#include "fdlibm.h" + +#ifdef __STDC__ +static const double +#else +static double +#endif +one = 1.00000000000000000000e+00, /* 0x3FF00000, 0x00000000 */ +really_big = 1.000e+300, +pio2_hi = 1.57079632679489655800e+00, /* 0x3FF921FB, 0x54442D18 */ +pio2_lo = 6.12323399573676603587e-17, /* 0x3C91A626, 0x33145C07 */ +pio4_hi = 7.85398163397448278999e-01, /* 0x3FE921FB, 0x54442D18 */ + /* coefficient for R(x^2) */ +pS0 = 1.66666666666666657415e-01, /* 0x3FC55555, 0x55555555 */ +pS1 = -3.25565818622400915405e-01, /* 0xBFD4D612, 0x03EB6F7D */ +pS2 = 2.01212532134862925881e-01, /* 0x3FC9C155, 0x0E884455 */ +pS3 = -4.00555345006794114027e-02, /* 0xBFA48228, 0xB5688F3B */ +pS4 = 7.91534994289814532176e-04, /* 0x3F49EFE0, 0x7501B288 */ +pS5 = 3.47933107596021167570e-05, /* 0x3F023DE1, 0x0DFDF709 */ +qS1 = -2.40339491173441421878e+00, /* 0xC0033A27, 0x1C8A2D4B */ +qS2 = 2.02094576023350569471e+00, /* 0x40002AE5, 0x9C598AC8 */ +qS3 = -6.88283971605453293030e-01, /* 0xBFE6066C, 0x1B8D0159 */ +qS4 = 7.70381505559019352791e-02; /* 0x3FB3B8C5, 0xB12E9282 */ + +#ifdef __STDC__ + double __ieee754_asin(double x) +#else + double __ieee754_asin(x) + double x; +#endif +{ + fd_twoints u; + double w,t,p,q,c,r,s; + int hx,ix; + u.d = x; + hx = __HI(u); + x = u.d; + ix = hx&0x7fffffff; + if(ix>= 0x3ff00000) { /* |x|>= 1 */ + if(((ix-0x3ff00000)|__LO(u))==0) + /* asin(1)=+-pi/2 with inexact */ + return x*pio2_hi+x*pio2_lo; + return (x-x)/(x-x); /* asin(|x|>1) is NaN */ + } else if (ix<0x3fe00000) { /* |x|<0.5 */ + if(ix<0x3e400000) { /* if |x| < 2**-27 */ + if(really_big+x>one) return x;/* return x with inexact if x!=0*/ + } else + t = x*x; + p = t*(pS0+t*(pS1+t*(pS2+t*(pS3+t*(pS4+t*pS5))))); + q = one+t*(qS1+t*(qS2+t*(qS3+t*qS4))); + w = p/q; + return x+x*w; + } + /* 1> |x|>= 0.5 */ + w = one-fd_fabs(x); + t = w*0.5; + p = t*(pS0+t*(pS1+t*(pS2+t*(pS3+t*(pS4+t*pS5))))); + q = one+t*(qS1+t*(qS2+t*(qS3+t*qS4))); + s = fd_sqrt(t); + if(ix>=0x3FEF3333) { /* if |x| > 0.975 */ + w = p/q; + t = pio2_hi-(2.0*(s+s*w)-pio2_lo); + } else { + u.d = s; + __LO(u) = 0; + w = u.d; + c = (t-w*w)/(s+w); + r = p/q; + p = 2.0*s*r-(pio2_lo-2.0*c); + q = pio4_hi-2.0*w; + t = pio4_hi-(p-q); + } + if(hx>0) return t; else return -t; +} diff --git a/src/dom/js/fdlibm/e_atan2.c b/src/dom/js/fdlibm/e_atan2.c new file mode 100644 index 000000000..9c9a2c01f --- /dev/null +++ b/src/dom/js/fdlibm/e_atan2.c @@ -0,0 +1,165 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)e_atan2.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + * + */ + +/* __ieee754_atan2(y,x) + * Method : + * 1. Reduce y to positive by atan2(y,x)=-atan2(-y,x). + * 2. Reduce x to positive by (if x and y are unexceptional): + * ARG (x+iy) = arctan(y/x) ... if x > 0, + * ARG (x+iy) = pi - arctan[y/(-x)] ... if x < 0, + * + * Special cases: + * + * ATAN2((anything), NaN ) is NaN; + * ATAN2(NAN , (anything) ) is NaN; + * ATAN2(+-0, +(anything but NaN)) is +-0 ; + * ATAN2(+-0, -(anything but NaN)) is +-pi ; + * ATAN2(+-(anything but 0 and NaN), 0) is +-pi/2; + * ATAN2(+-(anything but INF and NaN), +INF) is +-0 ; + * ATAN2(+-(anything but INF and NaN), -INF) is +-pi; + * ATAN2(+-INF,+INF ) is +-pi/4 ; + * ATAN2(+-INF,-INF ) is +-3pi/4; + * ATAN2(+-INF, (anything but,0,NaN, and INF)) is +-pi/2; + * + * Constants: + * The hexadecimal values are the intended ones for the following + * constants. The decimal values may be used, provided that the + * compiler will convert from decimal to binary accurately enough + * to produce the hexadecimal values shown. + */ + +#include "fdlibm.h" + +#ifdef __STDC__ +static const double +#else +static double +#endif +tiny = 1.0e-300, +zero = 0.0, +pi_o_4 = 7.8539816339744827900E-01, /* 0x3FE921FB, 0x54442D18 */ +pi_o_2 = 1.5707963267948965580E+00, /* 0x3FF921FB, 0x54442D18 */ +pi = 3.1415926535897931160E+00, /* 0x400921FB, 0x54442D18 */ +pi_lo = 1.2246467991473531772E-16; /* 0x3CA1A626, 0x33145C07 */ + +#ifdef __STDC__ + double __ieee754_atan2(double y, double x) +#else + double __ieee754_atan2(y,x) + double y,x; +#endif +{ + fd_twoints ux, uy, uz; + double z; + int k,m,hx,hy,ix,iy; + unsigned lx,ly; + + ux.d = x; uy.d = y; + hx = __HI(ux); ix = hx&0x7fffffff; + lx = __LO(ux); + hy = __HI(uy); iy = hy&0x7fffffff; + ly = __LO(uy); + if(((ix|((lx|-(int)lx)>>31))>0x7ff00000)|| + ((iy|((ly|-(int)ly)>>31))>0x7ff00000)) /* x or y is NaN */ + return x+y; + if(((hx-0x3ff00000)|lx)==0) return fd_atan(y); /* x=1.0 */ + m = ((hy>>31)&1)|((hx>>30)&2); /* 2*sign(x)+sign(y) */ + + /* when y = 0 */ + if((iy|ly)==0) { + switch(m) { + case 0: + case 1: return y; /* atan(+-0,+anything)=+-0 */ + case 2: return pi+tiny;/* atan(+0,-anything) = pi */ + case 3: return -pi-tiny;/* atan(-0,-anything) =-pi */ + } + } + /* when x = 0 */ + if((ix|lx)==0) return (hy<0)? -pi_o_2-tiny: pi_o_2+tiny; + + /* when x is INF */ + if(ix==0x7ff00000) { + if(iy==0x7ff00000) { + switch(m) { + case 0: return pi_o_4+tiny;/* atan(+INF,+INF) */ + case 1: return -pi_o_4-tiny;/* atan(-INF,+INF) */ + case 2: return 3.0*pi_o_4+tiny;/*atan(+INF,-INF)*/ + case 3: return -3.0*pi_o_4-tiny;/*atan(-INF,-INF)*/ + } + } else { + switch(m) { + case 0: return zero ; /* atan(+...,+INF) */ + case 1: return -zero ; /* atan(-...,+INF) */ + case 2: return pi+tiny ; /* atan(+...,-INF) */ + case 3: return -pi-tiny ; /* atan(-...,-INF) */ + } + } + } + /* when y is INF */ + if(iy==0x7ff00000) return (hy<0)? -pi_o_2-tiny: pi_o_2+tiny; + + /* compute y/x */ + k = (iy-ix)>>20; + if(k > 60) z=pi_o_2+0.5*pi_lo; /* |y/x| > 2**60 */ + else if(hx<0&&k<-60) z=0.0; /* |y|/x < -2**60 */ + else z=fd_atan(fd_fabs(y/x)); /* safe to do y/x */ + switch (m) { + case 0: return z ; /* atan(+,+) */ + case 1: uz.d = z; + __HI(uz) ^= 0x80000000; + z = uz.d; + return z ; /* atan(-,+) */ + case 2: return pi-(z-pi_lo);/* atan(+,-) */ + default: /* case 3 */ + return (z-pi_lo)-pi;/* atan(-,-) */ + } +} diff --git a/src/dom/js/fdlibm/e_atanh.c b/src/dom/js/fdlibm/e_atanh.c new file mode 100644 index 000000000..dc4a90c8e --- /dev/null +++ b/src/dom/js/fdlibm/e_atanh.c @@ -0,0 +1,110 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)e_atanh.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + * + */ + +/* __ieee754_atanh(x) + * Method : + * 1.Reduced x to positive by atanh(-x) = -atanh(x) + * 2.For x>=0.5 + * 1 2x x + * atanh(x) = --- * log(1 + -------) = 0.5 * log1p(2 * --------) + * 2 1 - x 1 - x + * + * For x<0.5 + * atanh(x) = 0.5*log1p(2x+2x*x/(1-x)) + * + * Special cases: + * atanh(x) is NaN if |x| > 1 with signal; + * atanh(NaN) is that NaN with no signal; + * atanh(+-1) is +-INF with signal. + * + */ + +#include "fdlibm.h" + +#ifdef __STDC__ +static const double one = 1.0, really_big = 1e300; +#else +static double one = 1.0, really_big = 1e300; +#endif + +static double zero = 0.0; + +#ifdef __STDC__ + double __ieee754_atanh(double x) +#else + double __ieee754_atanh(x) + double x; +#endif +{ + double t; + int hx,ix; + unsigned lx; + fd_twoints u; + u.d = x; + hx = __HI(u); /* high word */ + lx = __LO(u); /* low word */ + ix = hx&0x7fffffff; + if ((ix|((lx|(-(int)lx))>>31))>0x3ff00000) /* |x|>1 */ + return (x-x)/(x-x); + if(ix==0x3ff00000) + return x/zero; + if(ix<0x3e300000&&(really_big+x)>zero) return x; /* x<2**-28 */ + u.d = x; + __HI(u) = ix; /* x <- |x| */ + x = u.d; + if(ix<0x3fe00000) { /* x < 0.5 */ + t = x+x; + t = 0.5*fd_log1p(t+t*x/(one-x)); + } else + t = 0.5*fd_log1p((x+x)/(one-x)); + if(hx>=0) return t; else return -t; +} diff --git a/src/dom/js/fdlibm/e_cosh.c b/src/dom/js/fdlibm/e_cosh.c new file mode 100644 index 000000000..4f8d4f769 --- /dev/null +++ b/src/dom/js/fdlibm/e_cosh.c @@ -0,0 +1,133 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)e_cosh.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* __ieee754_cosh(x) + * Method : + * mathematically cosh(x) if defined to be (exp(x)+exp(-x))/2 + * 1. Replace x by |x| (cosh(x) = cosh(-x)). + * 2. + * [ exp(x) - 1 ]^2 + * 0 <= x <= ln2/2 : cosh(x) := 1 + ------------------- + * 2*exp(x) + * + * exp(x) + 1/exp(x) + * ln2/2 <= x <= 22 : cosh(x) := ------------------- + * 2 + * 22 <= x <= lnovft : cosh(x) := exp(x)/2 + * lnovft <= x <= ln2ovft: cosh(x) := exp(x/2)/2 * exp(x/2) + * ln2ovft < x : cosh(x) := huge*huge (overflow) + * + * Special cases: + * cosh(x) is |x| if x is +INF, -INF, or NaN. + * only cosh(0)=1 is exact for finite x. + */ + +#include "fdlibm.h" + +#ifdef _WIN32 +#define huge myhuge +#endif + +#ifdef __STDC__ +static const double one = 1.0, half=0.5, really_big = 1.0e300; +#else +static double one = 1.0, half=0.5, really_big = 1.0e300; +#endif + +#ifdef __STDC__ + double __ieee754_cosh(double x) +#else + double __ieee754_cosh(x) + double x; +#endif +{ + fd_twoints u; + double t,w; + int ix; + unsigned lx; + + /* High word of |x|. */ + u.d = x; + ix = __HI(u); + ix &= 0x7fffffff; + + /* x is INF or NaN */ + if(ix>=0x7ff00000) return x*x; + + /* |x| in [0,0.5*ln2], return 1+expm1(|x|)^2/(2*exp(|x|)) */ + if(ix<0x3fd62e43) { + t = fd_expm1(fd_fabs(x)); + w = one+t; + if (ix<0x3c800000) return w; /* cosh(tiny) = 1 */ + return one+(t*t)/(w+w); + } + + /* |x| in [0.5*ln2,22], return (exp(|x|)+1/exp(|x|)/2; */ + if (ix < 0x40360000) { + t = __ieee754_exp(fd_fabs(x)); + return half*t+half/t; + } + + /* |x| in [22, log(maxdouble)] return half*exp(|x|) */ + if (ix < 0x40862E42) return half*__ieee754_exp(fd_fabs(x)); + + /* |x| in [log(maxdouble), overflowthresold] */ + lx = *( (((*(unsigned*)&one)>>29)) + (unsigned*)&x); + if (ix<0x408633CE || + (ix==0x408633ce)&&(lx<=(unsigned)0x8fb9f87d)) { + w = __ieee754_exp(half*fd_fabs(x)); + t = half*w; + return t*w; + } + + /* |x| > overflowthresold, cosh(x) overflow */ + return really_big*really_big; +} diff --git a/src/dom/js/fdlibm/e_exp.c b/src/dom/js/fdlibm/e_exp.c new file mode 100644 index 000000000..ad9cec124 --- /dev/null +++ b/src/dom/js/fdlibm/e_exp.c @@ -0,0 +1,202 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)e_exp.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* __ieee754_exp(x) + * Returns the exponential of x. + * + * Method + * 1. Argument reduction: + * Reduce x to an r so that |r| <= 0.5*ln2 ~ 0.34658. + * Given x, find r and integer k such that + * + * x = k*ln2 + r, |r| <= 0.5*ln2. + * + * Here r will be represented as r = hi-lo for better + * accuracy. + * + * 2. Approximation of exp(r) by a special rational function on + * the interval [0,0.34658]: + * Write + * R(r**2) = r*(exp(r)+1)/(exp(r)-1) = 2 + r*r/6 - r**4/360 + ... + * We use a special Reme algorithm on [0,0.34658] to generate + * a polynomial of degree 5 to approximate R. The maximum error + * of this polynomial approximation is bounded by 2**-59. In + * other words, + * R(z) ~ 2.0 + P1*z + P2*z**2 + P3*z**3 + P4*z**4 + P5*z**5 + * (where z=r*r, and the values of P1 to P5 are listed below) + * and + * | 5 | -59 + * | 2.0+P1*z+...+P5*z - R(z) | <= 2 + * | | + * The computation of exp(r) thus becomes + * 2*r + * exp(r) = 1 + ------- + * R - r + * r*R1(r) + * = 1 + r + ----------- (for better accuracy) + * 2 - R1(r) + * where + * 2 4 10 + * R1(r) = r - (P1*r + P2*r + ... + P5*r ). + * + * 3. Scale back to obtain exp(x): + * From step 1, we have + * exp(x) = 2^k * exp(r) + * + * Special cases: + * exp(INF) is INF, exp(NaN) is NaN; + * exp(-INF) is 0, and + * for finite argument, only exp(0)=1 is exact. + * + * Accuracy: + * according to an error analysis, the error is always less than + * 1 ulp (unit in the last place). + * + * Misc. info. + * For IEEE double + * if x > 7.09782712893383973096e+02 then exp(x) overflow + * if x < -7.45133219101941108420e+02 then exp(x) underflow + * + * Constants: + * The hexadecimal values are the intended ones for the following + * constants. The decimal values may be used, provided that the + * compiler will convert from decimal to binary accurately enough + * to produce the hexadecimal values shown. + */ + +#include "fdlibm.h" + +#ifdef __STDC__ +static const double +#else +static double +#endif +one = 1.0, +halF[2] = {0.5,-0.5,}, +really_big = 1.0e+300, +twom1000= 9.33263618503218878990e-302, /* 2**-1000=0x01700000,0*/ +o_threshold= 7.09782712893383973096e+02, /* 0x40862E42, 0xFEFA39EF */ +u_threshold= -7.45133219101941108420e+02, /* 0xc0874910, 0xD52D3051 */ +ln2HI[2] ={ 6.93147180369123816490e-01, /* 0x3fe62e42, 0xfee00000 */ + -6.93147180369123816490e-01,},/* 0xbfe62e42, 0xfee00000 */ +ln2LO[2] ={ 1.90821492927058770002e-10, /* 0x3dea39ef, 0x35793c76 */ + -1.90821492927058770002e-10,},/* 0xbdea39ef, 0x35793c76 */ +invln2 = 1.44269504088896338700e+00, /* 0x3ff71547, 0x652b82fe */ +P1 = 1.66666666666666019037e-01, /* 0x3FC55555, 0x5555553E */ +P2 = -2.77777777770155933842e-03, /* 0xBF66C16C, 0x16BEBD93 */ +P3 = 6.61375632143793436117e-05, /* 0x3F11566A, 0xAF25DE2C */ +P4 = -1.65339022054652515390e-06, /* 0xBEBBBD41, 0xC5D26BF1 */ +P5 = 4.13813679705723846039e-08; /* 0x3E663769, 0x72BEA4D0 */ + + +#ifdef __STDC__ + double __ieee754_exp(double x) /* default IEEE double exp */ +#else + double __ieee754_exp(x) /* default IEEE double exp */ + double x; +#endif +{ + fd_twoints u; + double y,hi,lo,c,t; + int k, xsb; + unsigned hx; + + u.d = x; + hx = __HI(u); /* high word of x */ + xsb = (hx>>31)&1; /* sign bit of x */ + hx &= 0x7fffffff; /* high word of |x| */ + + /* filter out non-finite argument */ + if(hx >= 0x40862E42) { /* if |x|>=709.78... */ + if(hx>=0x7ff00000) { + u.d = x; + if(((hx&0xfffff)|__LO(u))!=0) + return x+x; /* NaN */ + else return (xsb==0)? x:0.0; /* exp(+-inf)={inf,0} */ + } + if(x > o_threshold) return really_big*really_big; /* overflow */ + if(x < u_threshold) return twom1000*twom1000; /* underflow */ + } + + /* argument reduction */ + if(hx > 0x3fd62e42) { /* if |x| > 0.5 ln2 */ + if(hx < 0x3FF0A2B2) { /* and |x| < 1.5 ln2 */ + hi = x-ln2HI[xsb]; lo=ln2LO[xsb]; k = 1-xsb-xsb; + } else { + k = (int)(invln2*x+halF[xsb]); + t = k; + hi = x - t*ln2HI[0]; /* t*ln2HI is exact here */ + lo = t*ln2LO[0]; + } + x = hi - lo; + } + else if(hx < 0x3e300000) { /* when |x|<2**-28 */ + if(really_big+x>one) return one+x;/* trigger inexact */ + } + else k = 0; + + /* x is now in primary range */ + t = x*x; + c = x - t*(P1+t*(P2+t*(P3+t*(P4+t*P5)))); + if(k==0) return one-((x*c)/(c-2.0)-x); + else y = one-((lo-(x*c)/(2.0-c))-hi); + if(k >= -1021) { + u.d = y; + __HI(u) += (k<<20); /* add k to y's exponent */ + y = u.d; + return y; + } else { + u.d = y; + __HI(u) += ((k+1000)<<20);/* add k to y's exponent */ + y = u.d; + return y*twom1000; + } +} diff --git a/src/dom/js/fdlibm/e_fmod.c b/src/dom/js/fdlibm/e_fmod.c new file mode 100644 index 000000000..7b5ce780f --- /dev/null +++ b/src/dom/js/fdlibm/e_fmod.c @@ -0,0 +1,184 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)e_fmod.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * __ieee754_fmod(x,y) + * Return x mod y in exact arithmetic + * Method: shift and subtract + */ + +#include "fdlibm.h" + +#ifdef __STDC__ +static const double one = 1.0, Zero[] = {0.0, -0.0,}; +#else +static double one = 1.0, Zero[] = {0.0, -0.0,}; +#endif + +#ifdef __STDC__ + double __ieee754_fmod(double x, double y) +#else + double __ieee754_fmod(x,y) + double x,y ; +#endif +{ + fd_twoints ux, uy; + int n,hx,hy,hz,ix,iy,sx,i; + unsigned lx,ly,lz; + + ux.d = x; uy.d = y; + hx = __HI(ux); /* high word of x */ + lx = __LO(ux); /* low word of x */ + hy = __HI(uy); /* high word of y */ + ly = __LO(uy); /* low word of y */ + sx = hx&0x80000000; /* sign of x */ + hx ^=sx; /* |x| */ + hy &= 0x7fffffff; /* |y| */ + + /* purge off exception values */ + if((hy|ly)==0||(hx>=0x7ff00000)|| /* y=0,or x not finite */ + ((hy|((ly|-(int)ly)>>31))>0x7ff00000)) /* or y is NaN */ + return (x*y)/(x*y); + if(hx<=hy) { + if((hx>31]; /* |x|=|y| return x*0*/ + } + + /* determine ix = ilogb(x) */ + if(hx<0x00100000) { /* subnormal x */ + if(hx==0) { + for (ix = -1043, i=lx; i>0; i<<=1) ix -=1; + } else { + for (ix = -1022,i=(hx<<11); i>0; i<<=1) ix -=1; + } + } else ix = (hx>>20)-1023; + + /* determine iy = ilogb(y) */ + if(hy<0x00100000) { /* subnormal y */ + if(hy==0) { + for (iy = -1043, i=ly; i>0; i<<=1) iy -=1; + } else { + for (iy = -1022,i=(hy<<11); i>0; i<<=1) iy -=1; + } + } else iy = (hy>>20)-1023; + + /* set up {hx,lx}, {hy,ly} and align y to x */ + if(ix >= -1022) + hx = 0x00100000|(0x000fffff&hx); + else { /* subnormal x, shift x to normal */ + n = -1022-ix; + if(n<=31) { + hx = (hx<>(32-n)); + lx <<= n; + } else { + hx = lx<<(n-32); + lx = 0; + } + } + if(iy >= -1022) + hy = 0x00100000|(0x000fffff&hy); + else { /* subnormal y, shift y to normal */ + n = -1022-iy; + if(n<=31) { + hy = (hy<>(32-n)); + ly <<= n; + } else { + hy = ly<<(n-32); + ly = 0; + } + } + + /* fix point fmod */ + n = ix - iy; + while(n--) { + hz=hx-hy;lz=lx-ly; if(lx>31); lx = lx+lx;} + else { + if((hz|lz)==0) /* return sign(x)*0 */ + return Zero[(unsigned)sx>>31]; + hx = hz+hz+(lz>>31); lx = lz+lz; + } + } + hz=hx-hy;lz=lx-ly; if(lx=0) {hx=hz;lx=lz;} + + /* convert back to floating value and restore the sign */ + if((hx|lx)==0) /* return sign(x)*0 */ + return Zero[(unsigned)sx>>31]; + while(hx<0x00100000) { /* normalize x */ + hx = hx+hx+(lx>>31); lx = lx+lx; + iy -= 1; + } + if(iy>= -1022) { /* normalize output */ + hx = ((hx-0x00100000)|((iy+1023)<<20)); + ux.d = x; + __HI(ux) = hx|sx; + __LO(ux) = lx; + x = ux.d; + } else { /* subnormal output */ + n = -1022 - iy; + if(n<=20) { + lx = (lx>>n)|((unsigned)hx<<(32-n)); + hx >>= n; + } else if (n<=31) { + lx = (hx<<(32-n))|(lx>>n); hx = sx; + } else { + lx = hx>>(n-32); hx = sx; + } + ux.d = x; + __HI(ux) = hx|sx; + __LO(ux) = lx; + x = ux.d; + x *= one; /* create necessary signal */ + } + return x; /* exact output */ +} diff --git a/src/dom/js/fdlibm/e_gamma.c b/src/dom/js/fdlibm/e_gamma.c new file mode 100644 index 000000000..a34faa32c --- /dev/null +++ b/src/dom/js/fdlibm/e_gamma.c @@ -0,0 +1,71 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)e_gamma.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + * + */ + +/* __ieee754_gamma(x) + * Return the logarithm of the Gamma function of x. + * + * Method: call __ieee754_gamma_r + */ + +#include "fdlibm.h" + +extern int signgam; + +#ifdef __STDC__ + double __ieee754_gamma(double x) +#else + double __ieee754_gamma(x) + double x; +#endif +{ + return __ieee754_gamma_r(x,&signgam); +} diff --git a/src/dom/js/fdlibm/e_gamma_r.c b/src/dom/js/fdlibm/e_gamma_r.c new file mode 100644 index 000000000..f10e32e36 --- /dev/null +++ b/src/dom/js/fdlibm/e_gamma_r.c @@ -0,0 +1,70 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)e_gamma_r.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + * + */ + +/* __ieee754_gamma_r(x, signgamp) + * Reentrant version of the logarithm of the Gamma function + * with user provide pointer for the sign of Gamma(x). + * + * Method: See __ieee754_lgamma_r + */ + +#include "fdlibm.h" + +#ifdef __STDC__ + double __ieee754_gamma_r(double x, int *signgamp) +#else + double __ieee754_gamma_r(x,signgamp) + double x; int *signgamp; +#endif +{ + return __ieee754_lgamma_r(x,signgamp); +} diff --git a/src/dom/js/fdlibm/e_hypot.c b/src/dom/js/fdlibm/e_hypot.c new file mode 100644 index 000000000..390023087 --- /dev/null +++ b/src/dom/js/fdlibm/e_hypot.c @@ -0,0 +1,173 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)e_hypot.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* __ieee754_hypot(x,y) + * + * Method : + * If (assume round-to-nearest) z=x*x+y*y + * has error less than sqrt(2)/2 ulp, than + * sqrt(z) has error less than 1 ulp (exercise). + * + * So, compute sqrt(x*x+y*y) with some care as + * follows to get the error below 1 ulp: + * + * Assume x>y>0; + * (if possible, set rounding to round-to-nearest) + * 1. if x > 2y use + * x1*x1+(y*y+(x2*(x+x1))) for x*x+y*y + * where x1 = x with lower 32 bits cleared, x2 = x-x1; else + * 2. if x <= 2y use + * t1*y1+((x-y)*(x-y)+(t1*y2+t2*y)) + * where t1 = 2x with lower 32 bits cleared, t2 = 2x-t1, + * y1= y with lower 32 bits chopped, y2 = y-y1. + * + * NOTE: scaling may be necessary if some argument is too + * large or too tiny + * + * Special cases: + * hypot(x,y) is INF if x or y is +INF or -INF; else + * hypot(x,y) is NAN if x or y is NAN. + * + * Accuracy: + * hypot(x,y) returns sqrt(x^2+y^2) with error less + * than 1 ulps (units in the last place) + */ + +#include "fdlibm.h" + +#ifdef __STDC__ + double __ieee754_hypot(double x, double y) +#else + double __ieee754_hypot(x,y) + double x, y; +#endif +{ + fd_twoints ux, uy; + double a=x,b=y,t1,t2,y1,y2,w; + int j,k,ha,hb; + + ux.d = x; uy.d = y; + ha = __HI(ux)&0x7fffffff; /* high word of x */ + hb = __HI(uy)&0x7fffffff; /* high word of y */ + if(hb > ha) {a=y;b=x;j=ha; ha=hb;hb=j;} else {a=x;b=y;} + ux.d = a; uy.d = b; + __HI(ux) = ha; /* a <- |a| */ + __HI(uy) = hb; /* b <- |b| */ + a = ux.d; b = uy.d; + if((ha-hb)>0x3c00000) {return a+b;} /* x/y > 2**60 */ + k=0; + if(ha > 0x5f300000) { /* a>2**500 */ + if(ha >= 0x7ff00000) { /* Inf or NaN */ + w = a+b; /* for sNaN */ + ux.d = a; uy.d = b; + if(((ha&0xfffff)|__LO(ux))==0) w = a; + if(((hb^0x7ff00000)|__LO(uy))==0) w = b; + return w; + } + /* scale a and b by 2**-600 */ + ha -= 0x25800000; hb -= 0x25800000; k += 600; + ux.d = a; uy.d = b; + __HI(ux) = ha; + __HI(uy) = hb; + a = ux.d; b = uy.d; + } + if(hb < 0x20b00000) { /* b < 2**-500 */ + if(hb <= 0x000fffff) { /* subnormal b or 0 */ + uy.d = b; + if((hb|(__LO(uy)))==0) return a; + t1=0; + ux.d = t1; + __HI(ux) = 0x7fd00000; /* t1=2^1022 */ + t1 = ux.d; + b *= t1; + a *= t1; + k -= 1022; + } else { /* scale a and b by 2^600 */ + ha += 0x25800000; /* a *= 2^600 */ + hb += 0x25800000; /* b *= 2^600 */ + k -= 600; + ux.d = a; uy.d = b; + __HI(ux) = ha; + __HI(uy) = hb; + a = ux.d; b = uy.d; + } + } + /* medium size a and b */ + w = a-b; + if (w>b) { + t1 = 0; + ux.d = t1; + __HI(ux) = ha; + t1 = ux.d; + t2 = a-t1; + w = fd_sqrt(t1*t1-(b*(-b)-t2*(a+t1))); + } else { + a = a+a; + y1 = 0; + ux.d = y1; + __HI(ux) = hb; + y1 = ux.d; + y2 = b - y1; + t1 = 0; + ux.d = t1; + __HI(ux) = ha+0x00100000; + t1 = ux.d; + t2 = a - t1; + w = fd_sqrt(t1*y1-(w*(-w)-(t1*y2+t2*b))); + } + if(k!=0) { + t1 = 1.0; + ux.d = t1; + __HI(ux) += (k<<20); + t1 = ux.d; + return t1*w; + } else return w; +} diff --git a/src/dom/js/fdlibm/e_j0.c b/src/dom/js/fdlibm/e_j0.c new file mode 100644 index 000000000..078e09641 --- /dev/null +++ b/src/dom/js/fdlibm/e_j0.c @@ -0,0 +1,524 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)e_j0.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* __ieee754_j0(x), __ieee754_y0(x) + * Bessel function of the first and second kinds of order zero. + * Method -- j0(x): + * 1. For tiny x, we use j0(x) = 1 - x^2/4 + x^4/64 - ... + * 2. Reduce x to |x| since j0(x)=j0(-x), and + * for x in (0,2) + * j0(x) = 1-z/4+ z^2*R0/S0, where z = x*x; + * (precision: |j0-1+z/4-z^2R0/S0 |<2**-63.67 ) + * for x in (2,inf) + * j0(x) = sqrt(2/(pi*x))*(p0(x)*cos(x0)-q0(x)*sin(x0)) + * where x0 = x-pi/4. It is better to compute sin(x0),cos(x0) + * as follow: + * cos(x0) = cos(x)cos(pi/4)+sin(x)sin(pi/4) + * = 1/sqrt(2) * (cos(x) + sin(x)) + * sin(x0) = sin(x)cos(pi/4)-cos(x)sin(pi/4) + * = 1/sqrt(2) * (sin(x) - cos(x)) + * (To avoid cancellation, use + * sin(x) +- cos(x) = -cos(2x)/(sin(x) -+ cos(x)) + * to compute the worse one.) + * + * 3 Special cases + * j0(nan)= nan + * j0(0) = 1 + * j0(inf) = 0 + * + * Method -- y0(x): + * 1. For x<2. + * Since + * y0(x) = 2/pi*(j0(x)*(ln(x/2)+Euler) + x^2/4 - ...) + * therefore y0(x)-2/pi*j0(x)*ln(x) is an even function. + * We use the following function to approximate y0, + * y0(x) = U(z)/V(z) + (2/pi)*(j0(x)*ln(x)), z= x^2 + * where + * U(z) = u00 + u01*z + ... + u06*z^6 + * V(z) = 1 + v01*z + ... + v04*z^4 + * with absolute approximation error bounded by 2**-72. + * Note: For tiny x, U/V = u0 and j0(x)~1, hence + * y0(tiny) = u0 + (2/pi)*ln(tiny), (choose tiny<2**-27) + * 2. For x>=2. + * y0(x) = sqrt(2/(pi*x))*(p0(x)*cos(x0)+q0(x)*sin(x0)) + * where x0 = x-pi/4. It is better to compute sin(x0),cos(x0) + * by the method mentioned above. + * 3. Special cases: y0(0)=-inf, y0(x<0)=NaN, y0(inf)=0. + */ + +#include "fdlibm.h" + +#ifdef __STDC__ +static double pzero(double), qzero(double); +#else +static double pzero(), qzero(); +#endif + +#ifdef __STDC__ +static const double +#else +static double +#endif +really_big = 1e300, +one = 1.0, +invsqrtpi= 5.64189583547756279280e-01, /* 0x3FE20DD7, 0x50429B6D */ +tpi = 6.36619772367581382433e-01, /* 0x3FE45F30, 0x6DC9C883 */ + /* R0/S0 on [0, 2.00] */ +R02 = 1.56249999999999947958e-02, /* 0x3F8FFFFF, 0xFFFFFFFD */ +R03 = -1.89979294238854721751e-04, /* 0xBF28E6A5, 0xB61AC6E9 */ +R04 = 1.82954049532700665670e-06, /* 0x3EBEB1D1, 0x0C503919 */ +R05 = -4.61832688532103189199e-09, /* 0xBE33D5E7, 0x73D63FCE */ +S01 = 1.56191029464890010492e-02, /* 0x3F8FFCE8, 0x82C8C2A4 */ +S02 = 1.16926784663337450260e-04, /* 0x3F1EA6D2, 0xDD57DBF4 */ +S03 = 5.13546550207318111446e-07, /* 0x3EA13B54, 0xCE84D5A9 */ +S04 = 1.16614003333790000205e-09; /* 0x3E1408BC, 0xF4745D8F */ + +static double zero = 0.0; + +#ifdef __STDC__ + double __ieee754_j0(double x) +#else + double __ieee754_j0(x) + double x; +#endif +{ + fd_twoints un; + double z, s,c,ss,cc,r,u,v; + int hx,ix; + + un.d = x; + hx = __HI(un); + ix = hx&0x7fffffff; + if(ix>=0x7ff00000) return one/(x*x); + x = fd_fabs(x); + if(ix >= 0x40000000) { /* |x| >= 2.0 */ + s = fd_sin(x); + c = fd_cos(x); + ss = s-c; + cc = s+c; + if(ix<0x7fe00000) { /* make sure x+x not overflow */ + z = -fd_cos(x+x); + if ((s*c)0x48000000) z = (invsqrtpi*cc)/fd_sqrt(x); + else { + u = pzero(x); v = qzero(x); + z = invsqrtpi*(u*cc-v*ss)/fd_sqrt(x); + } + return z; + } + if(ix<0x3f200000) { /* |x| < 2**-13 */ + if(really_big+x>one) { /* raise inexact if x != 0 */ + if(ix<0x3e400000) return one; /* |x|<2**-27 */ + else return one - 0.25*x*x; + } + } + z = x*x; + r = z*(R02+z*(R03+z*(R04+z*R05))); + s = one+z*(S01+z*(S02+z*(S03+z*S04))); + if(ix < 0x3FF00000) { /* |x| < 1.00 */ + return one + z*(-0.25+(r/s)); + } else { + u = 0.5*x; + return((one+u)*(one-u)+z*(r/s)); + } +} + +#ifdef __STDC__ +static const double +#else +static double +#endif +u00 = -7.38042951086872317523e-02, /* 0xBFB2E4D6, 0x99CBD01F */ +u01 = 1.76666452509181115538e-01, /* 0x3FC69D01, 0x9DE9E3FC */ +u02 = -1.38185671945596898896e-02, /* 0xBF8C4CE8, 0xB16CFA97 */ +u03 = 3.47453432093683650238e-04, /* 0x3F36C54D, 0x20B29B6B */ +u04 = -3.81407053724364161125e-06, /* 0xBECFFEA7, 0x73D25CAD */ +u05 = 1.95590137035022920206e-08, /* 0x3E550057, 0x3B4EABD4 */ +u06 = -3.98205194132103398453e-11, /* 0xBDC5E43D, 0x693FB3C8 */ +v01 = 1.27304834834123699328e-02, /* 0x3F8A1270, 0x91C9C71A */ +v02 = 7.60068627350353253702e-05, /* 0x3F13ECBB, 0xF578C6C1 */ +v03 = 2.59150851840457805467e-07, /* 0x3E91642D, 0x7FF202FD */ +v04 = 4.41110311332675467403e-10; /* 0x3DFE5018, 0x3BD6D9EF */ + +#ifdef __STDC__ + double __ieee754_y0(double x) +#else + double __ieee754_y0(x) + double x; +#endif +{ + fd_twoints un; + double z, s,c,ss,cc,u,v; + int hx,ix,lx; + + un.d = x; + hx = __HI(un); + ix = 0x7fffffff&hx; + lx = __LO(un); + /* Y0(NaN) is NaN, y0(-inf) is Nan, y0(inf) is 0 */ + if(ix>=0x7ff00000) return one/(x+x*x); + if((ix|lx)==0) return -one/zero; + if(hx<0) return zero/zero; + if(ix >= 0x40000000) { /* |x| >= 2.0 */ + /* y0(x) = sqrt(2/(pi*x))*(p0(x)*sin(x0)+q0(x)*cos(x0)) + * where x0 = x-pi/4 + * Better formula: + * cos(x0) = cos(x)cos(pi/4)+sin(x)sin(pi/4) + * = 1/sqrt(2) * (sin(x) + cos(x)) + * sin(x0) = sin(x)cos(3pi/4)-cos(x)sin(3pi/4) + * = 1/sqrt(2) * (sin(x) - cos(x)) + * To avoid cancellation, use + * sin(x) +- cos(x) = -cos(2x)/(sin(x) -+ cos(x)) + * to compute the worse one. + */ + s = fd_sin(x); + c = fd_cos(x); + ss = s-c; + cc = s+c; + /* + * j0(x) = 1/sqrt(pi) * (P(0,x)*cc - Q(0,x)*ss) / sqrt(x) + * y0(x) = 1/sqrt(pi) * (P(0,x)*ss + Q(0,x)*cc) / sqrt(x) + */ + if(ix<0x7fe00000) { /* make sure x+x not overflow */ + z = -fd_cos(x+x); + if ((s*c)0x48000000) z = (invsqrtpi*ss)/fd_sqrt(x); + else { + u = pzero(x); v = qzero(x); + z = invsqrtpi*(u*ss+v*cc)/fd_sqrt(x); + } + return z; + } + if(ix<=0x3e400000) { /* x < 2**-27 */ + return(u00 + tpi*__ieee754_log(x)); + } + z = x*x; + u = u00+z*(u01+z*(u02+z*(u03+z*(u04+z*(u05+z*u06))))); + v = one+z*(v01+z*(v02+z*(v03+z*v04))); + return(u/v + tpi*(__ieee754_j0(x)*__ieee754_log(x))); +} + +/* The asymptotic expansions of pzero is + * 1 - 9/128 s^2 + 11025/98304 s^4 - ..., where s = 1/x. + * For x >= 2, We approximate pzero by + * pzero(x) = 1 + (R/S) + * where R = pR0 + pR1*s^2 + pR2*s^4 + ... + pR5*s^10 + * S = 1 + pS0*s^2 + ... + pS4*s^10 + * and + * | pzero(x)-1-R/S | <= 2 ** ( -60.26) + */ +#ifdef __STDC__ +static const double pR8[6] = { /* for x in [inf, 8]=1/[0,0.125] */ +#else +static double pR8[6] = { /* for x in [inf, 8]=1/[0,0.125] */ +#endif + 0.00000000000000000000e+00, /* 0x00000000, 0x00000000 */ + -7.03124999999900357484e-02, /* 0xBFB1FFFF, 0xFFFFFD32 */ + -8.08167041275349795626e+00, /* 0xC02029D0, 0xB44FA779 */ + -2.57063105679704847262e+02, /* 0xC0701102, 0x7B19E863 */ + -2.48521641009428822144e+03, /* 0xC0A36A6E, 0xCD4DCAFC */ + -5.25304380490729545272e+03, /* 0xC0B4850B, 0x36CC643D */ +}; +#ifdef __STDC__ +static const double pS8[5] = { +#else +static double pS8[5] = { +#endif + 1.16534364619668181717e+02, /* 0x405D2233, 0x07A96751 */ + 3.83374475364121826715e+03, /* 0x40ADF37D, 0x50596938 */ + 4.05978572648472545552e+04, /* 0x40E3D2BB, 0x6EB6B05F */ + 1.16752972564375915681e+05, /* 0x40FC810F, 0x8F9FA9BD */ + 4.76277284146730962675e+04, /* 0x40E74177, 0x4F2C49DC */ +}; + +#ifdef __STDC__ +static const double pR5[6] = { /* for x in [8,4.5454]=1/[0.125,0.22001] */ +#else +static double pR5[6] = { /* for x in [8,4.5454]=1/[0.125,0.22001] */ +#endif + -1.14125464691894502584e-11, /* 0xBDA918B1, 0x47E495CC */ + -7.03124940873599280078e-02, /* 0xBFB1FFFF, 0xE69AFBC6 */ + -4.15961064470587782438e+00, /* 0xC010A370, 0xF90C6BBF */ + -6.76747652265167261021e+01, /* 0xC050EB2F, 0x5A7D1783 */ + -3.31231299649172967747e+02, /* 0xC074B3B3, 0x6742CC63 */ + -3.46433388365604912451e+02, /* 0xC075A6EF, 0x28A38BD7 */ +}; +#ifdef __STDC__ +static const double pS5[5] = { +#else +static double pS5[5] = { +#endif + 6.07539382692300335975e+01, /* 0x404E6081, 0x0C98C5DE */ + 1.05125230595704579173e+03, /* 0x40906D02, 0x5C7E2864 */ + 5.97897094333855784498e+03, /* 0x40B75AF8, 0x8FBE1D60 */ + 9.62544514357774460223e+03, /* 0x40C2CCB8, 0xFA76FA38 */ + 2.40605815922939109441e+03, /* 0x40A2CC1D, 0xC70BE864 */ +}; + +#ifdef __STDC__ +static const double pR3[6] = {/* for x in [4.547,2.8571]=1/[0.2199,0.35001] */ +#else +static double pR3[6] = {/* for x in [4.547,2.8571]=1/[0.2199,0.35001] */ +#endif + -2.54704601771951915620e-09, /* 0xBE25E103, 0x6FE1AA86 */ + -7.03119616381481654654e-02, /* 0xBFB1FFF6, 0xF7C0E24B */ + -2.40903221549529611423e+00, /* 0xC00345B2, 0xAEA48074 */ + -2.19659774734883086467e+01, /* 0xC035F74A, 0x4CB94E14 */ + -5.80791704701737572236e+01, /* 0xC04D0A22, 0x420A1A45 */ + -3.14479470594888503854e+01, /* 0xC03F72AC, 0xA892D80F */ +}; +#ifdef __STDC__ +static const double pS3[5] = { +#else +static double pS3[5] = { +#endif + 3.58560338055209726349e+01, /* 0x4041ED92, 0x84077DD3 */ + 3.61513983050303863820e+02, /* 0x40769839, 0x464A7C0E */ + 1.19360783792111533330e+03, /* 0x4092A66E, 0x6D1061D6 */ + 1.12799679856907414432e+03, /* 0x40919FFC, 0xB8C39B7E */ + 1.73580930813335754692e+02, /* 0x4065B296, 0xFC379081 */ +}; + +#ifdef __STDC__ +static const double pR2[6] = {/* for x in [2.8570,2]=1/[0.3499,0.5] */ +#else +static double pR2[6] = {/* for x in [2.8570,2]=1/[0.3499,0.5] */ +#endif + -8.87534333032526411254e-08, /* 0xBE77D316, 0xE927026D */ + -7.03030995483624743247e-02, /* 0xBFB1FF62, 0x495E1E42 */ + -1.45073846780952986357e+00, /* 0xBFF73639, 0x8A24A843 */ + -7.63569613823527770791e+00, /* 0xC01E8AF3, 0xEDAFA7F3 */ + -1.11931668860356747786e+01, /* 0xC02662E6, 0xC5246303 */ + -3.23364579351335335033e+00, /* 0xC009DE81, 0xAF8FE70F */ +}; +#ifdef __STDC__ +static const double pS2[5] = { +#else +static double pS2[5] = { +#endif + 2.22202997532088808441e+01, /* 0x40363865, 0x908B5959 */ + 1.36206794218215208048e+02, /* 0x4061069E, 0x0EE8878F */ + 2.70470278658083486789e+02, /* 0x4070E786, 0x42EA079B */ + 1.53875394208320329881e+02, /* 0x40633C03, 0x3AB6FAFF */ + 1.46576176948256193810e+01, /* 0x402D50B3, 0x44391809 */ +}; + +#ifdef __STDC__ + static double pzero(double x) +#else + static double pzero(x) + double x; +#endif +{ +#ifdef __STDC__ + const double *p,*q; +#else + double *p,*q; +#endif + fd_twoints u; + double z,r,s; + int ix; + u.d = x; + ix = 0x7fffffff&__HI(u); + if(ix>=0x40200000) {p = pR8; q= pS8;} + else if(ix>=0x40122E8B){p = pR5; q= pS5;} + else if(ix>=0x4006DB6D){p = pR3; q= pS3;} + else if(ix>=0x40000000){p = pR2; q= pS2;} + z = one/(x*x); + r = p[0]+z*(p[1]+z*(p[2]+z*(p[3]+z*(p[4]+z*p[5])))); + s = one+z*(q[0]+z*(q[1]+z*(q[2]+z*(q[3]+z*q[4])))); + return one+ r/s; +} + + +/* For x >= 8, the asymptotic expansions of qzero is + * -1/8 s + 75/1024 s^3 - ..., where s = 1/x. + * We approximate pzero by + * qzero(x) = s*(-1.25 + (R/S)) + * where R = qR0 + qR1*s^2 + qR2*s^4 + ... + qR5*s^10 + * S = 1 + qS0*s^2 + ... + qS5*s^12 + * and + * | qzero(x)/s +1.25-R/S | <= 2 ** ( -61.22) + */ +#ifdef __STDC__ +static const double qR8[6] = { /* for x in [inf, 8]=1/[0,0.125] */ +#else +static double qR8[6] = { /* for x in [inf, 8]=1/[0,0.125] */ +#endif + 0.00000000000000000000e+00, /* 0x00000000, 0x00000000 */ + 7.32421874999935051953e-02, /* 0x3FB2BFFF, 0xFFFFFE2C */ + 1.17682064682252693899e+01, /* 0x40278952, 0x5BB334D6 */ + 5.57673380256401856059e+02, /* 0x40816D63, 0x15301825 */ + 8.85919720756468632317e+03, /* 0x40C14D99, 0x3E18F46D */ + 3.70146267776887834771e+04, /* 0x40E212D4, 0x0E901566 */ +}; +#ifdef __STDC__ +static const double qS8[6] = { +#else +static double qS8[6] = { +#endif + 1.63776026895689824414e+02, /* 0x406478D5, 0x365B39BC */ + 8.09834494656449805916e+03, /* 0x40BFA258, 0x4E6B0563 */ + 1.42538291419120476348e+05, /* 0x41016652, 0x54D38C3F */ + 8.03309257119514397345e+05, /* 0x412883DA, 0x83A52B43 */ + 8.40501579819060512818e+05, /* 0x4129A66B, 0x28DE0B3D */ + -3.43899293537866615225e+05, /* 0xC114FD6D, 0x2C9530C5 */ +}; + +#ifdef __STDC__ +static const double qR5[6] = { /* for x in [8,4.5454]=1/[0.125,0.22001] */ +#else +static double qR5[6] = { /* for x in [8,4.5454]=1/[0.125,0.22001] */ +#endif + 1.84085963594515531381e-11, /* 0x3DB43D8F, 0x29CC8CD9 */ + 7.32421766612684765896e-02, /* 0x3FB2BFFF, 0xD172B04C */ + 5.83563508962056953777e+00, /* 0x401757B0, 0xB9953DD3 */ + 1.35111577286449829671e+02, /* 0x4060E392, 0x0A8788E9 */ + 1.02724376596164097464e+03, /* 0x40900CF9, 0x9DC8C481 */ + 1.98997785864605384631e+03, /* 0x409F17E9, 0x53C6E3A6 */ +}; +#ifdef __STDC__ +static const double qS5[6] = { +#else +static double qS5[6] = { +#endif + 8.27766102236537761883e+01, /* 0x4054B1B3, 0xFB5E1543 */ + 2.07781416421392987104e+03, /* 0x40A03BA0, 0xDA21C0CE */ + 1.88472887785718085070e+04, /* 0x40D267D2, 0x7B591E6D */ + 5.67511122894947329769e+04, /* 0x40EBB5E3, 0x97E02372 */ + 3.59767538425114471465e+04, /* 0x40E19118, 0x1F7A54A0 */ + -5.35434275601944773371e+03, /* 0xC0B4EA57, 0xBEDBC609 */ +}; + +#ifdef __STDC__ +static const double qR3[6] = {/* for x in [4.547,2.8571]=1/[0.2199,0.35001] */ +#else +static double qR3[6] = {/* for x in [4.547,2.8571]=1/[0.2199,0.35001] */ +#endif + 4.37741014089738620906e-09, /* 0x3E32CD03, 0x6ADECB82 */ + 7.32411180042911447163e-02, /* 0x3FB2BFEE, 0x0E8D0842 */ + 3.34423137516170720929e+00, /* 0x400AC0FC, 0x61149CF5 */ + 4.26218440745412650017e+01, /* 0x40454F98, 0x962DAEDD */ + 1.70808091340565596283e+02, /* 0x406559DB, 0xE25EFD1F */ + 1.66733948696651168575e+02, /* 0x4064D77C, 0x81FA21E0 */ +}; +#ifdef __STDC__ +static const double qS3[6] = { +#else +static double qS3[6] = { +#endif + 4.87588729724587182091e+01, /* 0x40486122, 0xBFE343A6 */ + 7.09689221056606015736e+02, /* 0x40862D83, 0x86544EB3 */ + 3.70414822620111362994e+03, /* 0x40ACF04B, 0xE44DFC63 */ + 6.46042516752568917582e+03, /* 0x40B93C6C, 0xD7C76A28 */ + 2.51633368920368957333e+03, /* 0x40A3A8AA, 0xD94FB1C0 */ + -1.49247451836156386662e+02, /* 0xC062A7EB, 0x201CF40F */ +}; + +#ifdef __STDC__ +static const double qR2[6] = {/* for x in [2.8570,2]=1/[0.3499,0.5] */ +#else +static double qR2[6] = {/* for x in [2.8570,2]=1/[0.3499,0.5] */ +#endif + 1.50444444886983272379e-07, /* 0x3E84313B, 0x54F76BDB */ + 7.32234265963079278272e-02, /* 0x3FB2BEC5, 0x3E883E34 */ + 1.99819174093815998816e+00, /* 0x3FFFF897, 0xE727779C */ + 1.44956029347885735348e+01, /* 0x402CFDBF, 0xAAF96FE5 */ + 3.16662317504781540833e+01, /* 0x403FAA8E, 0x29FBDC4A */ + 1.62527075710929267416e+01, /* 0x403040B1, 0x71814BB4 */ +}; +#ifdef __STDC__ +static const double qS2[6] = { +#else +static double qS2[6] = { +#endif + 3.03655848355219184498e+01, /* 0x403E5D96, 0xF7C07AED */ + 2.69348118608049844624e+02, /* 0x4070D591, 0xE4D14B40 */ + 8.44783757595320139444e+02, /* 0x408A6645, 0x22B3BF22 */ + 8.82935845112488550512e+02, /* 0x408B977C, 0x9C5CC214 */ + 2.12666388511798828631e+02, /* 0x406A9553, 0x0E001365 */ + -5.31095493882666946917e+00, /* 0xC0153E6A, 0xF8B32931 */ +}; + +#ifdef __STDC__ + static double qzero(double x) +#else + static double qzero(x) + double x; +#endif +{ +#ifdef __STDC__ + const double *p,*q; +#else + double *p,*q; +#endif + fd_twoints u; + double s,r,z; + int ix; + u.d = x; + ix = 0x7fffffff&__HI(u); + if(ix>=0x40200000) {p = qR8; q= qS8;} + else if(ix>=0x40122E8B){p = qR5; q= qS5;} + else if(ix>=0x4006DB6D){p = qR3; q= qS3;} + else if(ix>=0x40000000){p = qR2; q= qS2;} + z = one/(x*x); + r = p[0]+z*(p[1]+z*(p[2]+z*(p[3]+z*(p[4]+z*p[5])))); + s = one+z*(q[0]+z*(q[1]+z*(q[2]+z*(q[3]+z*(q[4]+z*q[5]))))); + return (-.125 + r/s)/x; +} diff --git a/src/dom/js/fdlibm/e_j1.c b/src/dom/js/fdlibm/e_j1.c new file mode 100644 index 000000000..8982ac86a --- /dev/null +++ b/src/dom/js/fdlibm/e_j1.c @@ -0,0 +1,523 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)e_j1.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* __ieee754_j1(x), __ieee754_y1(x) + * Bessel function of the first and second kinds of order zero. + * Method -- j1(x): + * 1. For tiny x, we use j1(x) = x/2 - x^3/16 + x^5/384 - ... + * 2. Reduce x to |x| since j1(x)=-j1(-x), and + * for x in (0,2) + * j1(x) = x/2 + x*z*R0/S0, where z = x*x; + * (precision: |j1/x - 1/2 - R0/S0 |<2**-61.51 ) + * for x in (2,inf) + * j1(x) = sqrt(2/(pi*x))*(p1(x)*cos(x1)-q1(x)*sin(x1)) + * y1(x) = sqrt(2/(pi*x))*(p1(x)*sin(x1)+q1(x)*cos(x1)) + * where x1 = x-3*pi/4. It is better to compute sin(x1),cos(x1) + * as follow: + * cos(x1) = cos(x)cos(3pi/4)+sin(x)sin(3pi/4) + * = 1/sqrt(2) * (sin(x) - cos(x)) + * sin(x1) = sin(x)cos(3pi/4)-cos(x)sin(3pi/4) + * = -1/sqrt(2) * (sin(x) + cos(x)) + * (To avoid cancellation, use + * sin(x) +- cos(x) = -cos(2x)/(sin(x) -+ cos(x)) + * to compute the worse one.) + * + * 3 Special cases + * j1(nan)= nan + * j1(0) = 0 + * j1(inf) = 0 + * + * Method -- y1(x): + * 1. screen out x<=0 cases: y1(0)=-inf, y1(x<0)=NaN + * 2. For x<2. + * Since + * y1(x) = 2/pi*(j1(x)*(ln(x/2)+Euler)-1/x-x/2+5/64*x^3-...) + * therefore y1(x)-2/pi*j1(x)*ln(x)-1/x is an odd function. + * We use the following function to approximate y1, + * y1(x) = x*U(z)/V(z) + (2/pi)*(j1(x)*ln(x)-1/x), z= x^2 + * where for x in [0,2] (abs err less than 2**-65.89) + * U(z) = U0[0] + U0[1]*z + ... + U0[4]*z^4 + * V(z) = 1 + v0[0]*z + ... + v0[4]*z^5 + * Note: For tiny x, 1/x dominate y1 and hence + * y1(tiny) = -2/pi/tiny, (choose tiny<2**-54) + * 3. For x>=2. + * y1(x) = sqrt(2/(pi*x))*(p1(x)*sin(x1)+q1(x)*cos(x1)) + * where x1 = x-3*pi/4. It is better to compute sin(x1),cos(x1) + * by method mentioned above. + */ + +#include "fdlibm.h" + +#ifdef __STDC__ +static double pone(double), qone(double); +#else +static double pone(), qone(); +#endif + +#ifdef __STDC__ +static const double +#else +static double +#endif +really_big = 1e300, +one = 1.0, +invsqrtpi= 5.64189583547756279280e-01, /* 0x3FE20DD7, 0x50429B6D */ +tpi = 6.36619772367581382433e-01, /* 0x3FE45F30, 0x6DC9C883 */ + /* R0/S0 on [0,2] */ +r00 = -6.25000000000000000000e-02, /* 0xBFB00000, 0x00000000 */ +r01 = 1.40705666955189706048e-03, /* 0x3F570D9F, 0x98472C61 */ +r02 = -1.59955631084035597520e-05, /* 0xBEF0C5C6, 0xBA169668 */ +r03 = 4.96727999609584448412e-08, /* 0x3E6AAAFA, 0x46CA0BD9 */ +s01 = 1.91537599538363460805e-02, /* 0x3F939D0B, 0x12637E53 */ +s02 = 1.85946785588630915560e-04, /* 0x3F285F56, 0xB9CDF664 */ +s03 = 1.17718464042623683263e-06, /* 0x3EB3BFF8, 0x333F8498 */ +s04 = 5.04636257076217042715e-09, /* 0x3E35AC88, 0xC97DFF2C */ +s05 = 1.23542274426137913908e-11; /* 0x3DAB2ACF, 0xCFB97ED8 */ + +static double zero = 0.0; + +#ifdef __STDC__ + double __ieee754_j1(double x) +#else + double __ieee754_j1(x) + double x; +#endif +{ + fd_twoints un; + double z, s,c,ss,cc,r,u,v,y; + int hx,ix; + + un.d = x; + hx = __HI(un); + ix = hx&0x7fffffff; + if(ix>=0x7ff00000) return one/x; + y = fd_fabs(x); + if(ix >= 0x40000000) { /* |x| >= 2.0 */ + s = fd_sin(y); + c = fd_cos(y); + ss = -s-c; + cc = s-c; + if(ix<0x7fe00000) { /* make sure y+y not overflow */ + z = fd_cos(y+y); + if ((s*c)>zero) cc = z/ss; + else ss = z/cc; + } + /* + * j1(x) = 1/sqrt(pi) * (P(1,x)*cc - Q(1,x)*ss) / sqrt(x) + * y1(x) = 1/sqrt(pi) * (P(1,x)*ss + Q(1,x)*cc) / sqrt(x) + */ + if(ix>0x48000000) z = (invsqrtpi*cc)/fd_sqrt(y); + else { + u = pone(y); v = qone(y); + z = invsqrtpi*(u*cc-v*ss)/fd_sqrt(y); + } + if(hx<0) return -z; + else return z; + } + if(ix<0x3e400000) { /* |x|<2**-27 */ + if(really_big+x>one) return 0.5*x;/* inexact if x!=0 necessary */ + } + z = x*x; + r = z*(r00+z*(r01+z*(r02+z*r03))); + s = one+z*(s01+z*(s02+z*(s03+z*(s04+z*s05)))); + r *= x; + return(x*0.5+r/s); +} + +#ifdef __STDC__ +static const double U0[5] = { +#else +static double U0[5] = { +#endif + -1.96057090646238940668e-01, /* 0xBFC91866, 0x143CBC8A */ + 5.04438716639811282616e-02, /* 0x3FA9D3C7, 0x76292CD1 */ + -1.91256895875763547298e-03, /* 0xBF5F55E5, 0x4844F50F */ + 2.35252600561610495928e-05, /* 0x3EF8AB03, 0x8FA6B88E */ + -9.19099158039878874504e-08, /* 0xBE78AC00, 0x569105B8 */ +}; +#ifdef __STDC__ +static const double V0[5] = { +#else +static double V0[5] = { +#endif + 1.99167318236649903973e-02, /* 0x3F94650D, 0x3F4DA9F0 */ + 2.02552581025135171496e-04, /* 0x3F2A8C89, 0x6C257764 */ + 1.35608801097516229404e-06, /* 0x3EB6C05A, 0x894E8CA6 */ + 6.22741452364621501295e-09, /* 0x3E3ABF1D, 0x5BA69A86 */ + 1.66559246207992079114e-11, /* 0x3DB25039, 0xDACA772A */ +}; + +#ifdef __STDC__ + double __ieee754_y1(double x) +#else + double __ieee754_y1(x) + double x; +#endif +{ + fd_twoints un; + double z, s,c,ss,cc,u,v; + int hx,ix,lx; + + un.d = x; + hx = __HI(un); + ix = 0x7fffffff&hx; + lx = __LO(un); + /* if Y1(NaN) is NaN, Y1(-inf) is NaN, Y1(inf) is 0 */ + if(ix>=0x7ff00000) return one/(x+x*x); + if((ix|lx)==0) return -one/zero; + if(hx<0) return zero/zero; + if(ix >= 0x40000000) { /* |x| >= 2.0 */ + s = fd_sin(x); + c = fd_cos(x); + ss = -s-c; + cc = s-c; + if(ix<0x7fe00000) { /* make sure x+x not overflow */ + z = fd_cos(x+x); + if ((s*c)>zero) cc = z/ss; + else ss = z/cc; + } + /* y1(x) = sqrt(2/(pi*x))*(p1(x)*sin(x0)+q1(x)*cos(x0)) + * where x0 = x-3pi/4 + * Better formula: + * cos(x0) = cos(x)cos(3pi/4)+sin(x)sin(3pi/4) + * = 1/sqrt(2) * (sin(x) - cos(x)) + * sin(x0) = sin(x)cos(3pi/4)-cos(x)sin(3pi/4) + * = -1/sqrt(2) * (cos(x) + sin(x)) + * To avoid cancellation, use + * sin(x) +- cos(x) = -cos(2x)/(sin(x) -+ cos(x)) + * to compute the worse one. + */ + if(ix>0x48000000) z = (invsqrtpi*ss)/fd_sqrt(x); + else { + u = pone(x); v = qone(x); + z = invsqrtpi*(u*ss+v*cc)/fd_sqrt(x); + } + return z; + } + if(ix<=0x3c900000) { /* x < 2**-54 */ + return(-tpi/x); + } + z = x*x; + u = U0[0]+z*(U0[1]+z*(U0[2]+z*(U0[3]+z*U0[4]))); + v = one+z*(V0[0]+z*(V0[1]+z*(V0[2]+z*(V0[3]+z*V0[4])))); + return(x*(u/v) + tpi*(__ieee754_j1(x)*__ieee754_log(x)-one/x)); +} + +/* For x >= 8, the asymptotic expansions of pone is + * 1 + 15/128 s^2 - 4725/2^15 s^4 - ..., where s = 1/x. + * We approximate pone by + * pone(x) = 1 + (R/S) + * where R = pr0 + pr1*s^2 + pr2*s^4 + ... + pr5*s^10 + * S = 1 + ps0*s^2 + ... + ps4*s^10 + * and + * | pone(x)-1-R/S | <= 2 ** ( -60.06) + */ + +#ifdef __STDC__ +static const double pr8[6] = { /* for x in [inf, 8]=1/[0,0.125] */ +#else +static double pr8[6] = { /* for x in [inf, 8]=1/[0,0.125] */ +#endif + 0.00000000000000000000e+00, /* 0x00000000, 0x00000000 */ + 1.17187499999988647970e-01, /* 0x3FBDFFFF, 0xFFFFFCCE */ + 1.32394806593073575129e+01, /* 0x402A7A9D, 0x357F7FCE */ + 4.12051854307378562225e+02, /* 0x4079C0D4, 0x652EA590 */ + 3.87474538913960532227e+03, /* 0x40AE457D, 0xA3A532CC */ + 7.91447954031891731574e+03, /* 0x40BEEA7A, 0xC32782DD */ +}; +#ifdef __STDC__ +static const double ps8[5] = { +#else +static double ps8[5] = { +#endif + 1.14207370375678408436e+02, /* 0x405C8D45, 0x8E656CAC */ + 3.65093083420853463394e+03, /* 0x40AC85DC, 0x964D274F */ + 3.69562060269033463555e+04, /* 0x40E20B86, 0x97C5BB7F */ + 9.76027935934950801311e+04, /* 0x40F7D42C, 0xB28F17BB */ + 3.08042720627888811578e+04, /* 0x40DE1511, 0x697A0B2D */ +}; + +#ifdef __STDC__ +static const double pr5[6] = { /* for x in [8,4.5454]=1/[0.125,0.22001] */ +#else +static double pr5[6] = { /* for x in [8,4.5454]=1/[0.125,0.22001] */ +#endif + 1.31990519556243522749e-11, /* 0x3DAD0667, 0xDAE1CA7D */ + 1.17187493190614097638e-01, /* 0x3FBDFFFF, 0xE2C10043 */ + 6.80275127868432871736e+00, /* 0x401B3604, 0x6E6315E3 */ + 1.08308182990189109773e+02, /* 0x405B13B9, 0x452602ED */ + 5.17636139533199752805e+02, /* 0x40802D16, 0xD052D649 */ + 5.28715201363337541807e+02, /* 0x408085B8, 0xBB7E0CB7 */ +}; +#ifdef __STDC__ +static const double ps5[5] = { +#else +static double ps5[5] = { +#endif + 5.92805987221131331921e+01, /* 0x404DA3EA, 0xA8AF633D */ + 9.91401418733614377743e+02, /* 0x408EFB36, 0x1B066701 */ + 5.35326695291487976647e+03, /* 0x40B4E944, 0x5706B6FB */ + 7.84469031749551231769e+03, /* 0x40BEA4B0, 0xB8A5BB15 */ + 1.50404688810361062679e+03, /* 0x40978030, 0x036F5E51 */ +}; + +#ifdef __STDC__ +static const double pr3[6] = { +#else +static double pr3[6] = {/* for x in [4.547,2.8571]=1/[0.2199,0.35001] */ +#endif + 3.02503916137373618024e-09, /* 0x3E29FC21, 0xA7AD9EDD */ + 1.17186865567253592491e-01, /* 0x3FBDFFF5, 0x5B21D17B */ + 3.93297750033315640650e+00, /* 0x400F76BC, 0xE85EAD8A */ + 3.51194035591636932736e+01, /* 0x40418F48, 0x9DA6D129 */ + 9.10550110750781271918e+01, /* 0x4056C385, 0x4D2C1837 */ + 4.85590685197364919645e+01, /* 0x4048478F, 0x8EA83EE5 */ +}; +#ifdef __STDC__ +static const double ps3[5] = { +#else +static double ps3[5] = { +#endif + 3.47913095001251519989e+01, /* 0x40416549, 0xA134069C */ + 3.36762458747825746741e+02, /* 0x40750C33, 0x07F1A75F */ + 1.04687139975775130551e+03, /* 0x40905B7C, 0x5037D523 */ + 8.90811346398256432622e+02, /* 0x408BD67D, 0xA32E31E9 */ + 1.03787932439639277504e+02, /* 0x4059F26D, 0x7C2EED53 */ +}; + +#ifdef __STDC__ +static const double pr2[6] = {/* for x in [2.8570,2]=1/[0.3499,0.5] */ +#else +static double pr2[6] = {/* for x in [2.8570,2]=1/[0.3499,0.5] */ +#endif + 1.07710830106873743082e-07, /* 0x3E7CE9D4, 0xF65544F4 */ + 1.17176219462683348094e-01, /* 0x3FBDFF42, 0xBE760D83 */ + 2.36851496667608785174e+00, /* 0x4002F2B7, 0xF98FAEC0 */ + 1.22426109148261232917e+01, /* 0x40287C37, 0x7F71A964 */ + 1.76939711271687727390e+01, /* 0x4031B1A8, 0x177F8EE2 */ + 5.07352312588818499250e+00, /* 0x40144B49, 0xA574C1FE */ +}; +#ifdef __STDC__ +static const double ps2[5] = { +#else +static double ps2[5] = { +#endif + 2.14364859363821409488e+01, /* 0x40356FBD, 0x8AD5ECDC */ + 1.25290227168402751090e+02, /* 0x405F5293, 0x14F92CD5 */ + 2.32276469057162813669e+02, /* 0x406D08D8, 0xD5A2DBD9 */ + 1.17679373287147100768e+02, /* 0x405D6B7A, 0xDA1884A9 */ + 8.36463893371618283368e+00, /* 0x4020BAB1, 0xF44E5192 */ +}; + +#ifdef __STDC__ + static double pone(double x) +#else + static double pone(x) + double x; +#endif +{ +#ifdef __STDC__ + const double *p,*q; +#else + double *p,*q; +#endif + fd_twoints un; + double z,r,s; + int ix; + un.d = x; + ix = 0x7fffffff&__HI(un); + if(ix>=0x40200000) {p = pr8; q= ps8;} + else if(ix>=0x40122E8B){p = pr5; q= ps5;} + else if(ix>=0x4006DB6D){p = pr3; q= ps3;} + else if(ix>=0x40000000){p = pr2; q= ps2;} + z = one/(x*x); + r = p[0]+z*(p[1]+z*(p[2]+z*(p[3]+z*(p[4]+z*p[5])))); + s = one+z*(q[0]+z*(q[1]+z*(q[2]+z*(q[3]+z*q[4])))); + return one+ r/s; +} + + +/* For x >= 8, the asymptotic expansions of qone is + * 3/8 s - 105/1024 s^3 - ..., where s = 1/x. + * We approximate pone by + * qone(x) = s*(0.375 + (R/S)) + * where R = qr1*s^2 + qr2*s^4 + ... + qr5*s^10 + * S = 1 + qs1*s^2 + ... + qs6*s^12 + * and + * | qone(x)/s -0.375-R/S | <= 2 ** ( -61.13) + */ + +#ifdef __STDC__ +static const double qr8[6] = { /* for x in [inf, 8]=1/[0,0.125] */ +#else +static double qr8[6] = { /* for x in [inf, 8]=1/[0,0.125] */ +#endif + 0.00000000000000000000e+00, /* 0x00000000, 0x00000000 */ + -1.02539062499992714161e-01, /* 0xBFBA3FFF, 0xFFFFFDF3 */ + -1.62717534544589987888e+01, /* 0xC0304591, 0xA26779F7 */ + -7.59601722513950107896e+02, /* 0xC087BCD0, 0x53E4B576 */ + -1.18498066702429587167e+04, /* 0xC0C724E7, 0x40F87415 */ + -4.84385124285750353010e+04, /* 0xC0E7A6D0, 0x65D09C6A */ +}; +#ifdef __STDC__ +static const double qs8[6] = { +#else +static double qs8[6] = { +#endif + 1.61395369700722909556e+02, /* 0x40642CA6, 0xDE5BCDE5 */ + 7.82538599923348465381e+03, /* 0x40BE9162, 0xD0D88419 */ + 1.33875336287249578163e+05, /* 0x4100579A, 0xB0B75E98 */ + 7.19657723683240939863e+05, /* 0x4125F653, 0x72869C19 */ + 6.66601232617776375264e+05, /* 0x412457D2, 0x7719AD5C */ + -2.94490264303834643215e+05, /* 0xC111F969, 0x0EA5AA18 */ +}; + +#ifdef __STDC__ +static const double qr5[6] = { /* for x in [8,4.5454]=1/[0.125,0.22001] */ +#else +static double qr5[6] = { /* for x in [8,4.5454]=1/[0.125,0.22001] */ +#endif + -2.08979931141764104297e-11, /* 0xBDB6FA43, 0x1AA1A098 */ + -1.02539050241375426231e-01, /* 0xBFBA3FFF, 0xCB597FEF */ + -8.05644828123936029840e+00, /* 0xC0201CE6, 0xCA03AD4B */ + -1.83669607474888380239e+02, /* 0xC066F56D, 0x6CA7B9B0 */ + -1.37319376065508163265e+03, /* 0xC09574C6, 0x6931734F */ + -2.61244440453215656817e+03, /* 0xC0A468E3, 0x88FDA79D */ +}; +#ifdef __STDC__ +static const double qs5[6] = { +#else +static double qs5[6] = { +#endif + 8.12765501384335777857e+01, /* 0x405451B2, 0xFF5A11B2 */ + 1.99179873460485964642e+03, /* 0x409F1F31, 0xE77BF839 */ + 1.74684851924908907677e+04, /* 0x40D10F1F, 0x0D64CE29 */ + 4.98514270910352279316e+04, /* 0x40E8576D, 0xAABAD197 */ + 2.79480751638918118260e+04, /* 0x40DB4B04, 0xCF7C364B */ + -4.71918354795128470869e+03, /* 0xC0B26F2E, 0xFCFFA004 */ +}; + +#ifdef __STDC__ +static const double qr3[6] = { +#else +static double qr3[6] = {/* for x in [4.547,2.8571]=1/[0.2199,0.35001] */ +#endif + -5.07831226461766561369e-09, /* 0xBE35CFA9, 0xD38FC84F */ + -1.02537829820837089745e-01, /* 0xBFBA3FEB, 0x51AEED54 */ + -4.61011581139473403113e+00, /* 0xC01270C2, 0x3302D9FF */ + -5.78472216562783643212e+01, /* 0xC04CEC71, 0xC25D16DA */ + -2.28244540737631695038e+02, /* 0xC06C87D3, 0x4718D55F */ + -2.19210128478909325622e+02, /* 0xC06B66B9, 0x5F5C1BF6 */ +}; +#ifdef __STDC__ +static const double qs3[6] = { +#else +static double qs3[6] = { +#endif + 4.76651550323729509273e+01, /* 0x4047D523, 0xCCD367E4 */ + 6.73865112676699709482e+02, /* 0x40850EEB, 0xC031EE3E */ + 3.38015286679526343505e+03, /* 0x40AA684E, 0x448E7C9A */ + 5.54772909720722782367e+03, /* 0x40B5ABBA, 0xA61D54A6 */ + 1.90311919338810798763e+03, /* 0x409DBC7A, 0x0DD4DF4B */ + -1.35201191444307340817e+02, /* 0xC060E670, 0x290A311F */ +}; + +#ifdef __STDC__ +static const double qr2[6] = {/* for x in [2.8570,2]=1/[0.3499,0.5] */ +#else +static double qr2[6] = {/* for x in [2.8570,2]=1/[0.3499,0.5] */ +#endif + -1.78381727510958865572e-07, /* 0xBE87F126, 0x44C626D2 */ + -1.02517042607985553460e-01, /* 0xBFBA3E8E, 0x9148B010 */ + -2.75220568278187460720e+00, /* 0xC0060484, 0x69BB4EDA */ + -1.96636162643703720221e+01, /* 0xC033A9E2, 0xC168907F */ + -4.23253133372830490089e+01, /* 0xC04529A3, 0xDE104AAA */ + -2.13719211703704061733e+01, /* 0xC0355F36, 0x39CF6E52 */ +}; +#ifdef __STDC__ +static const double qs2[6] = { +#else +static double qs2[6] = { +#endif + 2.95333629060523854548e+01, /* 0x403D888A, 0x78AE64FF */ + 2.52981549982190529136e+02, /* 0x406F9F68, 0xDB821CBA */ + 7.57502834868645436472e+02, /* 0x4087AC05, 0xCE49A0F7 */ + 7.39393205320467245656e+02, /* 0x40871B25, 0x48D4C029 */ + 1.55949003336666123687e+02, /* 0x40637E5E, 0x3C3ED8D4 */ + -4.95949898822628210127e+00, /* 0xC013D686, 0xE71BE86B */ +}; + +#ifdef __STDC__ + static double qone(double x) +#else + static double qone(x) + double x; +#endif +{ +#ifdef __STDC__ + const double *p,*q; +#else + double *p,*q; +#endif + fd_twoints un; + double s,r,z; + int ix; + un.d = x; + ix = 0x7fffffff&__HI(un); + if(ix>=0x40200000) {p = qr8; q= qs8;} + else if(ix>=0x40122E8B){p = qr5; q= qs5;} + else if(ix>=0x4006DB6D){p = qr3; q= qs3;} + else if(ix>=0x40000000){p = qr2; q= qs2;} + z = one/(x*x); + r = p[0]+z*(p[1]+z*(p[2]+z*(p[3]+z*(p[4]+z*p[5])))); + s = one+z*(q[0]+z*(q[1]+z*(q[2]+z*(q[3]+z*(q[4]+z*q[5]))))); + return (.375 + r/s)/x; +} diff --git a/src/dom/js/fdlibm/e_jn.c b/src/dom/js/fdlibm/e_jn.c new file mode 100644 index 000000000..2b61b4439 --- /dev/null +++ b/src/dom/js/fdlibm/e_jn.c @@ -0,0 +1,315 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)e_jn.c 1.4 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * __ieee754_jn(n, x), __ieee754_yn(n, x) + * floating point Bessel's function of the 1st and 2nd kind + * of order n + * + * Special cases: + * y0(0)=y1(0)=yn(n,0) = -inf with division by zero signal; + * y0(-ve)=y1(-ve)=yn(n,-ve) are NaN with invalid signal. + * Note 2. About jn(n,x), yn(n,x) + * For n=0, j0(x) is called, + * for n=1, j1(x) is called, + * for nx, a continued fraction approximation to + * j(n,x)/j(n-1,x) is evaluated and then backward + * recursion is used starting from a supposed value + * for j(n,x). The resulting value of j(0,x) is + * compared with the actual value to correct the + * supposed value of j(n,x). + * + * yn(n,x) is similar in all respects, except + * that forward recursion is used for all + * values of n>1. + * + */ + +#include "fdlibm.h" + +#ifdef __STDC__ +static const double +#else +static double +#endif +invsqrtpi= 5.64189583547756279280e-01, /* 0x3FE20DD7, 0x50429B6D */ +two = 2.00000000000000000000e+00, /* 0x40000000, 0x00000000 */ +one = 1.00000000000000000000e+00; /* 0x3FF00000, 0x00000000 */ + +static double zero = 0.00000000000000000000e+00; + +#ifdef __STDC__ + double __ieee754_jn(int n, double x) +#else + double __ieee754_jn(n,x) + int n; double x; +#endif +{ + fd_twoints u; + int i,hx,ix,lx, sgn; + double a, b, temp, di; + double z, w; + + /* J(-n,x) = (-1)^n * J(n, x), J(n, -x) = (-1)^n * J(n, x) + * Thus, J(-n,x) = J(n,-x) + */ + u.d = x; + hx = __HI(u); + ix = 0x7fffffff&hx; + lx = __LO(u); + /* if J(n,NaN) is NaN */ + if((ix|((unsigned)(lx|-lx))>>31)>0x7ff00000) return x+x; + if(n<0){ + n = -n; + x = -x; + hx ^= 0x80000000; + } + if(n==0) return(__ieee754_j0(x)); + if(n==1) return(__ieee754_j1(x)); + sgn = (n&1)&(hx>>31); /* even n -- 0, odd n -- sign(x) */ + x = fd_fabs(x); + if((ix|lx)==0||ix>=0x7ff00000) /* if x is 0 or inf */ + b = zero; + else if((double)n<=x) { + /* Safe to use J(n+1,x)=2n/x *J(n,x)-J(n-1,x) */ + if(ix>=0x52D00000) { /* x > 2**302 */ + /* (x >> n**2) + * Jn(x) = cos(x-(2n+1)*pi/4)*sqrt(2/x*pi) + * Yn(x) = sin(x-(2n+1)*pi/4)*sqrt(2/x*pi) + * Let s=sin(x), c=cos(x), + * xn=x-(2n+1)*pi/4, sqt2 = sqrt(2),then + * + * n sin(xn)*sqt2 cos(xn)*sqt2 + * ---------------------------------- + * 0 s-c c+s + * 1 -s-c -c+s + * 2 -s+c -c-s + * 3 s+c c-s + */ + switch(n&3) { + case 0: temp = fd_cos(x)+fd_sin(x); break; + case 1: temp = -fd_cos(x)+fd_sin(x); break; + case 2: temp = -fd_cos(x)-fd_sin(x); break; + case 3: temp = fd_cos(x)-fd_sin(x); break; + } + b = invsqrtpi*temp/fd_sqrt(x); + } else { + a = __ieee754_j0(x); + b = __ieee754_j1(x); + for(i=1;i33) /* underflow */ + b = zero; + else { + temp = x*0.5; b = temp; + for (a=one,i=2;i<=n;i++) { + a *= (double)i; /* a = n! */ + b *= temp; /* b = (x/2)^n */ + } + b = b/a; + } + } else { + /* use backward recurrence */ + /* x x^2 x^2 + * J(n,x)/J(n-1,x) = ---- ------ ------ ..... + * 2n - 2(n+1) - 2(n+2) + * + * 1 1 1 + * (for large x) = ---- ------ ------ ..... + * 2n 2(n+1) 2(n+2) + * -- - ------ - ------ - + * x x x + * + * Let w = 2n/x and h=2/x, then the above quotient + * is equal to the continued fraction: + * 1 + * = ----------------------- + * 1 + * w - ----------------- + * 1 + * w+h - --------- + * w+2h - ... + * + * To determine how many terms needed, let + * Q(0) = w, Q(1) = w(w+h) - 1, + * Q(k) = (w+k*h)*Q(k-1) - Q(k-2), + * When Q(k) > 1e4 good for single + * When Q(k) > 1e9 good for double + * When Q(k) > 1e17 good for quadruple + */ + /* determine k */ + double t,v; + double q0,q1,h,tmp; int k,m; + w = (n+n)/(double)x; h = 2.0/(double)x; + q0 = w; z = w+h; q1 = w*z - 1.0; k=1; + while(q1<1.0e9) { + k += 1; z += h; + tmp = z*q1 - q0; + q0 = q1; + q1 = tmp; + } + m = n+n; + for(t=zero, i = 2*(n+k); i>=m; i -= 2) t = one/(i/x-t); + a = t; + b = one; + /* estimate log((2/x)^n*n!) = n*log(2/x)+n*ln(n) + * Hence, if n*(log(2n/x)) > ... + * single 8.8722839355e+01 + * double 7.09782712893383973096e+02 + * long double 1.1356523406294143949491931077970765006170e+04 + * then recurrent value may overflow and the result is + * likely underflow to zero + */ + tmp = n; + v = two/x; + tmp = tmp*__ieee754_log(fd_fabs(v*tmp)); + if(tmp<7.09782712893383973096e+02) { + for(i=n-1,di=(double)(i+i);i>0;i--){ + temp = b; + b *= di; + b = b/x - a; + a = temp; + di -= two; + } + } else { + for(i=n-1,di=(double)(i+i);i>0;i--){ + temp = b; + b *= di; + b = b/x - a; + a = temp; + di -= two; + /* scale b to avoid spurious overflow */ + if(b>1e100) { + a /= b; + t /= b; + b = one; + } + } + } + b = (t*__ieee754_j0(x)/b); + } + } + if(sgn==1) return -b; else return b; +} + +#ifdef __STDC__ + double __ieee754_yn(int n, double x) +#else + double __ieee754_yn(n,x) + int n; double x; +#endif +{ + fd_twoints u; + int i,hx,ix,lx; + int sign; + double a, b, temp; + + u.d = x; + hx = __HI(u); + ix = 0x7fffffff&hx; + lx = __LO(u); + /* if Y(n,NaN) is NaN */ + if((ix|((unsigned)(lx|-lx))>>31)>0x7ff00000) return x+x; + if((ix|lx)==0) return -one/zero; + if(hx<0) return zero/zero; + sign = 1; + if(n<0){ + n = -n; + sign = 1 - ((n&1)<<1); + } + if(n==0) return(__ieee754_y0(x)); + if(n==1) return(sign*__ieee754_y1(x)); + if(ix==0x7ff00000) return zero; + if(ix>=0x52D00000) { /* x > 2**302 */ + /* (x >> n**2) + * Jn(x) = cos(x-(2n+1)*pi/4)*sqrt(2/x*pi) + * Yn(x) = sin(x-(2n+1)*pi/4)*sqrt(2/x*pi) + * Let s=sin(x), c=cos(x), + * xn=x-(2n+1)*pi/4, sqt2 = sqrt(2),then + * + * n sin(xn)*sqt2 cos(xn)*sqt2 + * ---------------------------------- + * 0 s-c c+s + * 1 -s-c -c+s + * 2 -s+c -c-s + * 3 s+c c-s + */ + switch(n&3) { + case 0: temp = fd_sin(x)-fd_cos(x); break; + case 1: temp = -fd_sin(x)-fd_cos(x); break; + case 2: temp = -fd_sin(x)+fd_cos(x); break; + case 3: temp = fd_sin(x)+fd_cos(x); break; + } + b = invsqrtpi*temp/fd_sqrt(x); + } else { + a = __ieee754_y0(x); + b = __ieee754_y1(x); + /* quit if b is -inf */ + u.d = b; + for(i=1;i0) return b; else return -b; +} diff --git a/src/dom/js/fdlibm/e_lgamma.c b/src/dom/js/fdlibm/e_lgamma.c new file mode 100644 index 000000000..beb3bd932 --- /dev/null +++ b/src/dom/js/fdlibm/e_lgamma.c @@ -0,0 +1,71 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)e_lgamma.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + * + */ + +/* __ieee754_lgamma(x) + * Return the logarithm of the Gamma function of x. + * + * Method: call __ieee754_lgamma_r + */ + +#include "fdlibm.h" + +extern int signgam; + +#ifdef __STDC__ + double __ieee754_lgamma(double x) +#else + double __ieee754_lgamma(x) + double x; +#endif +{ + return __ieee754_lgamma_r(x,&signgam); +} diff --git a/src/dom/js/fdlibm/e_lgamma_r.c b/src/dom/js/fdlibm/e_lgamma_r.c new file mode 100644 index 000000000..df92e7a26 --- /dev/null +++ b/src/dom/js/fdlibm/e_lgamma_r.c @@ -0,0 +1,347 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)e_lgamma_r.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + * + */ + +/* __ieee754_lgamma_r(x, signgamp) + * Reentrant version of the logarithm of the Gamma function + * with user provide pointer for the sign of Gamma(x). + * + * Method: + * 1. Argument Reduction for 0 < x <= 8 + * Since gamma(1+s)=s*gamma(s), for x in [0,8], we may + * reduce x to a number in [1.5,2.5] by + * lgamma(1+s) = log(s) + lgamma(s) + * for example, + * lgamma(7.3) = log(6.3) + lgamma(6.3) + * = log(6.3*5.3) + lgamma(5.3) + * = log(6.3*5.3*4.3*3.3*2.3) + lgamma(2.3) + * 2. Polynomial approximation of lgamma around its + * minimun ymin=1.461632144968362245 to maintain monotonicity. + * On [ymin-0.23, ymin+0.27] (i.e., [1.23164,1.73163]), use + * Let z = x-ymin; + * lgamma(x) = -1.214862905358496078218 + z^2*poly(z) + * where + * poly(z) is a 14 degree polynomial. + * 2. Rational approximation in the primary interval [2,3] + * We use the following approximation: + * s = x-2.0; + * lgamma(x) = 0.5*s + s*P(s)/Q(s) + * with accuracy + * |P/Q - (lgamma(x)-0.5s)| < 2**-61.71 + * Our algorithms are based on the following observation + * + * zeta(2)-1 2 zeta(3)-1 3 + * lgamma(2+s) = s*(1-Euler) + --------- * s - --------- * s + ... + * 2 3 + * + * where Euler = 0.5771... is the Euler constant, which is very + * close to 0.5. + * + * 3. For x>=8, we have + * lgamma(x)~(x-0.5)log(x)-x+0.5*log(2pi)+1/(12x)-1/(360x**3)+.... + * (better formula: + * lgamma(x)~(x-0.5)*(log(x)-1)-.5*(log(2pi)-1) + ...) + * Let z = 1/x, then we approximation + * f(z) = lgamma(x) - (x-0.5)(log(x)-1) + * by + * 3 5 11 + * w = w0 + w1*z + w2*z + w3*z + ... + w6*z + * where + * |w - f(z)| < 2**-58.74 + * + * 4. For negative x, since (G is gamma function) + * -x*G(-x)*G(x) = pi/sin(pi*x), + * we have + * G(x) = pi/(sin(pi*x)*(-x)*G(-x)) + * since G(-x) is positive, sign(G(x)) = sign(sin(pi*x)) for x<0 + * Hence, for x<0, signgam = sign(sin(pi*x)) and + * lgamma(x) = log(|Gamma(x)|) + * = log(pi/(|x*sin(pi*x)|)) - lgamma(-x); + * Note: one should avoid compute pi*(-x) directly in the + * computation of sin(pi*(-x)). + * + * 5. Special Cases + * lgamma(2+s) ~ s*(1-Euler) for tiny s + * lgamma(1)=lgamma(2)=0 + * lgamma(x) ~ -log(x) for tiny x + * lgamma(0) = lgamma(inf) = inf + * lgamma(-integer) = +-inf + * + */ + +#include "fdlibm.h" + +#ifdef __STDC__ +static const double +#else +static double +#endif +two52= 4.50359962737049600000e+15, /* 0x43300000, 0x00000000 */ +half= 5.00000000000000000000e-01, /* 0x3FE00000, 0x00000000 */ +one = 1.00000000000000000000e+00, /* 0x3FF00000, 0x00000000 */ +pi = 3.14159265358979311600e+00, /* 0x400921FB, 0x54442D18 */ +a0 = 7.72156649015328655494e-02, /* 0x3FB3C467, 0xE37DB0C8 */ +a1 = 3.22467033424113591611e-01, /* 0x3FD4A34C, 0xC4A60FAD */ +a2 = 6.73523010531292681824e-02, /* 0x3FB13E00, 0x1A5562A7 */ +a3 = 2.05808084325167332806e-02, /* 0x3F951322, 0xAC92547B */ +a4 = 7.38555086081402883957e-03, /* 0x3F7E404F, 0xB68FEFE8 */ +a5 = 2.89051383673415629091e-03, /* 0x3F67ADD8, 0xCCB7926B */ +a6 = 1.19270763183362067845e-03, /* 0x3F538A94, 0x116F3F5D */ +a7 = 5.10069792153511336608e-04, /* 0x3F40B6C6, 0x89B99C00 */ +a8 = 2.20862790713908385557e-04, /* 0x3F2CF2EC, 0xED10E54D */ +a9 = 1.08011567247583939954e-04, /* 0x3F1C5088, 0x987DFB07 */ +a10 = 2.52144565451257326939e-05, /* 0x3EFA7074, 0x428CFA52 */ +a11 = 4.48640949618915160150e-05, /* 0x3F07858E, 0x90A45837 */ +tc = 1.46163214496836224576e+00, /* 0x3FF762D8, 0x6356BE3F */ +tf = -1.21486290535849611461e-01, /* 0xBFBF19B9, 0xBCC38A42 */ +/* tt = -(tail of tf) */ +tt = -3.63867699703950536541e-18, /* 0xBC50C7CA, 0xA48A971F */ +t0 = 4.83836122723810047042e-01, /* 0x3FDEF72B, 0xC8EE38A2 */ +t1 = -1.47587722994593911752e-01, /* 0xBFC2E427, 0x8DC6C509 */ +t2 = 6.46249402391333854778e-02, /* 0x3FB08B42, 0x94D5419B */ +t3 = -3.27885410759859649565e-02, /* 0xBFA0C9A8, 0xDF35B713 */ +t4 = 1.79706750811820387126e-02, /* 0x3F9266E7, 0x970AF9EC */ +t5 = -1.03142241298341437450e-02, /* 0xBF851F9F, 0xBA91EC6A */ +t6 = 6.10053870246291332635e-03, /* 0x3F78FCE0, 0xE370E344 */ +t7 = -3.68452016781138256760e-03, /* 0xBF6E2EFF, 0xB3E914D7 */ +t8 = 2.25964780900612472250e-03, /* 0x3F6282D3, 0x2E15C915 */ +t9 = -1.40346469989232843813e-03, /* 0xBF56FE8E, 0xBF2D1AF1 */ +t10 = 8.81081882437654011382e-04, /* 0x3F4CDF0C, 0xEF61A8E9 */ +t11 = -5.38595305356740546715e-04, /* 0xBF41A610, 0x9C73E0EC */ +t12 = 3.15632070903625950361e-04, /* 0x3F34AF6D, 0x6C0EBBF7 */ +t13 = -3.12754168375120860518e-04, /* 0xBF347F24, 0xECC38C38 */ +t14 = 3.35529192635519073543e-04, /* 0x3F35FD3E, 0xE8C2D3F4 */ +u0 = -7.72156649015328655494e-02, /* 0xBFB3C467, 0xE37DB0C8 */ +u1 = 6.32827064025093366517e-01, /* 0x3FE4401E, 0x8B005DFF */ +u2 = 1.45492250137234768737e+00, /* 0x3FF7475C, 0xD119BD6F */ +u3 = 9.77717527963372745603e-01, /* 0x3FEF4976, 0x44EA8450 */ +u4 = 2.28963728064692451092e-01, /* 0x3FCD4EAE, 0xF6010924 */ +u5 = 1.33810918536787660377e-02, /* 0x3F8B678B, 0xBF2BAB09 */ +v1 = 2.45597793713041134822e+00, /* 0x4003A5D7, 0xC2BD619C */ +v2 = 2.12848976379893395361e+00, /* 0x40010725, 0xA42B18F5 */ +v3 = 7.69285150456672783825e-01, /* 0x3FE89DFB, 0xE45050AF */ +v4 = 1.04222645593369134254e-01, /* 0x3FBAAE55, 0xD6537C88 */ +v5 = 3.21709242282423911810e-03, /* 0x3F6A5ABB, 0x57D0CF61 */ +s0 = -7.72156649015328655494e-02, /* 0xBFB3C467, 0xE37DB0C8 */ +s1 = 2.14982415960608852501e-01, /* 0x3FCB848B, 0x36E20878 */ +s2 = 3.25778796408930981787e-01, /* 0x3FD4D98F, 0x4F139F59 */ +s3 = 1.46350472652464452805e-01, /* 0x3FC2BB9C, 0xBEE5F2F7 */ +s4 = 2.66422703033638609560e-02, /* 0x3F9B481C, 0x7E939961 */ +s5 = 1.84028451407337715652e-03, /* 0x3F5E26B6, 0x7368F239 */ +s6 = 3.19475326584100867617e-05, /* 0x3F00BFEC, 0xDD17E945 */ +r1 = 1.39200533467621045958e+00, /* 0x3FF645A7, 0x62C4AB74 */ +r2 = 7.21935547567138069525e-01, /* 0x3FE71A18, 0x93D3DCDC */ +r3 = 1.71933865632803078993e-01, /* 0x3FC601ED, 0xCCFBDF27 */ +r4 = 1.86459191715652901344e-02, /* 0x3F9317EA, 0x742ED475 */ +r5 = 7.77942496381893596434e-04, /* 0x3F497DDA, 0xCA41A95B */ +r6 = 7.32668430744625636189e-06, /* 0x3EDEBAF7, 0xA5B38140 */ +w0 = 4.18938533204672725052e-01, /* 0x3FDACFE3, 0x90C97D69 */ +w1 = 8.33333333333329678849e-02, /* 0x3FB55555, 0x5555553B */ +w2 = -2.77777777728775536470e-03, /* 0xBF66C16C, 0x16B02E5C */ +w3 = 7.93650558643019558500e-04, /* 0x3F4A019F, 0x98CF38B6 */ +w4 = -5.95187557450339963135e-04, /* 0xBF4380CB, 0x8C0FE741 */ +w5 = 8.36339918996282139126e-04, /* 0x3F4B67BA, 0x4CDAD5D1 */ +w6 = -1.63092934096575273989e-03; /* 0xBF5AB89D, 0x0B9E43E4 */ + +static double zero= 0.00000000000000000000e+00; + +#ifdef __STDC__ + static double sin_pi(double x) +#else + static double sin_pi(x) + double x; +#endif +{ + fd_twoints u; + double y,z; + int n,ix; + + u.d = x; + ix = 0x7fffffff&__HI(u); + + if(ix<0x3fd00000) return __kernel_sin(pi*x,zero,0); + y = -x; /* x is assume negative */ + + /* + * argument reduction, make sure inexact flag not raised if input + * is an integer + */ + z = fd_floor(y); + if(z!=y) { /* inexact anyway */ + y *= 0.5; + y = 2.0*(y - fd_floor(y)); /* y = |x| mod 2.0 */ + n = (int) (y*4.0); + } else { + if(ix>=0x43400000) { + y = zero; n = 0; /* y must be even */ + } else { + if(ix<0x43300000) z = y+two52; /* exact */ + u.d = z; + n = __LO(u)&1; /* lower word of z */ + y = n; + n<<= 2; + } + } + switch (n) { + case 0: y = __kernel_sin(pi*y,zero,0); break; + case 1: + case 2: y = __kernel_cos(pi*(0.5-y),zero); break; + case 3: + case 4: y = __kernel_sin(pi*(one-y),zero,0); break; + case 5: + case 6: y = -__kernel_cos(pi*(y-1.5),zero); break; + default: y = __kernel_sin(pi*(y-2.0),zero,0); break; + } + return -y; +} + + +#ifdef __STDC__ + double __ieee754_lgamma_r(double x, int *signgamp) +#else + double __ieee754_lgamma_r(x,signgamp) + double x; int *signgamp; +#endif +{ + fd_twoints u; + double t,y,z,nadj,p,p1,p2,p3,q,r,w; + int i,hx,lx,ix; + + u.d = x; + hx = __HI(u); + lx = __LO(u); + + /* purge off +-inf, NaN, +-0, and negative arguments */ + *signgamp = 1; + ix = hx&0x7fffffff; + if(ix>=0x7ff00000) return x*x; + if((ix|lx)==0) return one/zero; + if(ix<0x3b900000) { /* |x|<2**-70, return -log(|x|) */ + if(hx<0) { + *signgamp = -1; + return -__ieee754_log(-x); + } else return -__ieee754_log(x); + } + if(hx<0) { + if(ix>=0x43300000) /* |x|>=2**52, must be -integer */ + return one/zero; + t = sin_pi(x); + if(t==zero) return one/zero; /* -integer */ + nadj = __ieee754_log(pi/fd_fabs(t*x)); + if(t=0x3FE76944) {y = one-x; i= 0;} + else if(ix>=0x3FCDA661) {y= x-(tc-one); i=1;} + else {y = x; i=2;} + } else { + r = zero; + if(ix>=0x3FFBB4C3) {y=2.0-x;i=0;} /* [1.7316,2] */ + else if(ix>=0x3FF3B4C4) {y=x-tc;i=1;} /* [1.23,1.73] */ + else {y=x-one;i=2;} + } + switch(i) { + case 0: + z = y*y; + p1 = a0+z*(a2+z*(a4+z*(a6+z*(a8+z*a10)))); + p2 = z*(a1+z*(a3+z*(a5+z*(a7+z*(a9+z*a11))))); + p = y*p1+p2; + r += (p-0.5*y); break; + case 1: + z = y*y; + w = z*y; + p1 = t0+w*(t3+w*(t6+w*(t9 +w*t12))); /* parallel comp */ + p2 = t1+w*(t4+w*(t7+w*(t10+w*t13))); + p3 = t2+w*(t5+w*(t8+w*(t11+w*t14))); + p = z*p1-(tt-w*(p2+y*p3)); + r += (tf + p); break; + case 2: + p1 = y*(u0+y*(u1+y*(u2+y*(u3+y*(u4+y*u5))))); + p2 = one+y*(v1+y*(v2+y*(v3+y*(v4+y*v5)))); + r += (-0.5*y + p1/p2); + } + } + else if(ix<0x40200000) { /* x < 8.0 */ + i = (int)x; + t = zero; + y = x-(double)i; + p = y*(s0+y*(s1+y*(s2+y*(s3+y*(s4+y*(s5+y*s6)))))); + q = one+y*(r1+y*(r2+y*(r3+y*(r4+y*(r5+y*r6))))); + r = half*y+p/q; + z = one; /* lgamma(1+s) = log(s) + lgamma(s) */ + switch(i) { + case 7: z *= (y+6.0); /* FALLTHRU */ + case 6: z *= (y+5.0); /* FALLTHRU */ + case 5: z *= (y+4.0); /* FALLTHRU */ + case 4: z *= (y+3.0); /* FALLTHRU */ + case 3: z *= (y+2.0); /* FALLTHRU */ + r += __ieee754_log(z); break; + } + /* 8.0 <= x < 2**58 */ + } else if (ix < 0x43900000) { + t = __ieee754_log(x); + z = one/x; + y = z*z; + w = w0+z*(w1+y*(w2+y*(w3+y*(w4+y*(w5+y*w6))))); + r = (x-half)*(t-one)+w; + } else + /* 2**58 <= x <= inf */ + r = x*(__ieee754_log(x)-one); + if(hx<0) r = nadj - r; + return r; +} diff --git a/src/dom/js/fdlibm/e_log.c b/src/dom/js/fdlibm/e_log.c new file mode 100644 index 000000000..8645d6efd --- /dev/null +++ b/src/dom/js/fdlibm/e_log.c @@ -0,0 +1,184 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)e_log.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* __ieee754_log(x) + * Return the logrithm of x + * + * Method : + * 1. Argument Reduction: find k and f such that + * x = 2^k * (1+f), + * where sqrt(2)/2 < 1+f < sqrt(2) . + * + * 2. Approximation of log(1+f). + * Let s = f/(2+f) ; based on log(1+f) = log(1+s) - log(1-s) + * = 2s + 2/3 s**3 + 2/5 s**5 + ....., + * = 2s + s*R + * We use a special Reme algorithm on [0,0.1716] to generate + * a polynomial of degree 14 to approximate R The maximum error + * of this polynomial approximation is bounded by 2**-58.45. In + * other words, + * 2 4 6 8 10 12 14 + * R(z) ~ Lg1*s +Lg2*s +Lg3*s +Lg4*s +Lg5*s +Lg6*s +Lg7*s + * (the values of Lg1 to Lg7 are listed in the program) + * and + * | 2 14 | -58.45 + * | Lg1*s +...+Lg7*s - R(z) | <= 2 + * | | + * Note that 2s = f - s*f = f - hfsq + s*hfsq, where hfsq = f*f/2. + * In order to guarantee error in log below 1ulp, we compute log + * by + * log(1+f) = f - s*(f - R) (if f is not too large) + * log(1+f) = f - (hfsq - s*(hfsq+R)). (better accuracy) + * + * 3. Finally, log(x) = k*ln2 + log(1+f). + * = k*ln2_hi+(f-(hfsq-(s*(hfsq+R)+k*ln2_lo))) + * Here ln2 is split into two floating point number: + * ln2_hi + ln2_lo, + * where n*ln2_hi is always exact for |n| < 2000. + * + * Special cases: + * log(x) is NaN with signal if x < 0 (including -INF) ; + * log(+INF) is +INF; log(0) is -INF with signal; + * log(NaN) is that NaN with no signal. + * + * Accuracy: + * according to an error analysis, the error is always less than + * 1 ulp (unit in the last place). + * + * Constants: + * The hexadecimal values are the intended ones for the following + * constants. The decimal values may be used, provided that the + * compiler will convert from decimal to binary accurately enough + * to produce the hexadecimal values shown. + */ + +#include "fdlibm.h" + +#ifdef __STDC__ +static const double +#else +static double +#endif +ln2_hi = 6.93147180369123816490e-01, /* 3fe62e42 fee00000 */ +ln2_lo = 1.90821492927058770002e-10, /* 3dea39ef 35793c76 */ +two54 = 1.80143985094819840000e+16, /* 43500000 00000000 */ +Lg1 = 6.666666666666735130e-01, /* 3FE55555 55555593 */ +Lg2 = 3.999999999940941908e-01, /* 3FD99999 9997FA04 */ +Lg3 = 2.857142874366239149e-01, /* 3FD24924 94229359 */ +Lg4 = 2.222219843214978396e-01, /* 3FCC71C5 1D8E78AF */ +Lg5 = 1.818357216161805012e-01, /* 3FC74664 96CB03DE */ +Lg6 = 1.531383769920937332e-01, /* 3FC39A09 D078C69F */ +Lg7 = 1.479819860511658591e-01; /* 3FC2F112 DF3E5244 */ + +static double zero = 0.0; + +#ifdef __STDC__ + double __ieee754_log(double x) +#else + double __ieee754_log(x) + double x; +#endif +{ + fd_twoints u; + double hfsq,f,s,z,R,w,t1,t2,dk; + int k,hx,i,j; + unsigned lx; + + u.d = x; + hx = __HI(u); /* high word of x */ + lx = __LO(u); /* low word of x */ + + k=0; + if (hx < 0x00100000) { /* x < 2**-1022 */ + if (((hx&0x7fffffff)|lx)==0) + return -two54/zero; /* log(+-0)=-inf */ + if (hx<0) return (x-x)/zero; /* log(-#) = NaN */ + k -= 54; x *= two54; /* subnormal number, scale up x */ + u.d = x; + hx = __HI(u); /* high word of x */ + } + if (hx >= 0x7ff00000) return x+x; + k += (hx>>20)-1023; + hx &= 0x000fffff; + i = (hx+0x95f64)&0x100000; + u.d = x; + __HI(u) = hx|(i^0x3ff00000); /* normalize x or x/2 */ + x = u.d; + k += (i>>20); + f = x-1.0; + if((0x000fffff&(2+hx))<3) { /* |f| < 2**-20 */ + if(f==zero) { + if(k==0) return zero; else {dk=(double)k; + return dk*ln2_hi+dk*ln2_lo;} + } + R = f*f*(0.5-0.33333333333333333*f); + if(k==0) return f-R; else {dk=(double)k; + return dk*ln2_hi-((R-dk*ln2_lo)-f);} + } + s = f/(2.0+f); + dk = (double)k; + z = s*s; + i = hx-0x6147a; + w = z*z; + j = 0x6b851-hx; + t1= w*(Lg2+w*(Lg4+w*Lg6)); + t2= z*(Lg1+w*(Lg3+w*(Lg5+w*Lg7))); + i |= j; + R = t2+t1; + if(i>0) { + hfsq=0.5*f*f; + if(k==0) return f-(hfsq-s*(hfsq+R)); else + return dk*ln2_hi-((hfsq-(s*(hfsq+R)+dk*ln2_lo))-f); + } else { + if(k==0) return f-s*(f-R); else + return dk*ln2_hi-((s*(f-R)-dk*ln2_lo)-f); + } +} diff --git a/src/dom/js/fdlibm/e_log10.c b/src/dom/js/fdlibm/e_log10.c new file mode 100644 index 000000000..5f88f4b4c --- /dev/null +++ b/src/dom/js/fdlibm/e_log10.c @@ -0,0 +1,134 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)e_log10.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* __ieee754_log10(x) + * Return the base 10 logarithm of x + * + * Method : + * Let log10_2hi = leading 40 bits of log10(2) and + * log10_2lo = log10(2) - log10_2hi, + * ivln10 = 1/log(10) rounded. + * Then + * n = ilogb(x), + * if(n<0) n = n+1; + * x = scalbn(x,-n); + * log10(x) := n*log10_2hi + (n*log10_2lo + ivln10*log(x)) + * + * Note 1: + * To guarantee log10(10**n)=n, where 10**n is normal, the rounding + * mode must set to Round-to-Nearest. + * Note 2: + * [1/log(10)] rounded to 53 bits has error .198 ulps; + * log10 is monotonic at all binary break points. + * + * Special cases: + * log10(x) is NaN with signal if x < 0; + * log10(+INF) is +INF with no signal; log10(0) is -INF with signal; + * log10(NaN) is that NaN with no signal; + * log10(10**N) = N for N=0,1,...,22. + * + * Constants: + * The hexadecimal values are the intended ones for the following constants. + * The decimal values may be used, provided that the compiler will convert + * from decimal to binary accurately enough to produce the hexadecimal values + * shown. + */ + +#include "fdlibm.h" + +#ifdef __STDC__ +static const double +#else +static double +#endif +two54 = 1.80143985094819840000e+16, /* 0x43500000, 0x00000000 */ +ivln10 = 4.34294481903251816668e-01, /* 0x3FDBCB7B, 0x1526E50E */ +log10_2hi = 3.01029995663611771306e-01, /* 0x3FD34413, 0x509F6000 */ +log10_2lo = 3.69423907715893078616e-13; /* 0x3D59FEF3, 0x11F12B36 */ + +static double zero = 0.0; + +#ifdef __STDC__ + double __ieee754_log10(double x) +#else + double __ieee754_log10(x) + double x; +#endif +{ + fd_twoints u; + double y,z; + int i,k,hx; + unsigned lx; + + u.d = x; + hx = __HI(u); /* high word of x */ + lx = __LO(u); /* low word of x */ + + k=0; + if (hx < 0x00100000) { /* x < 2**-1022 */ + if (((hx&0x7fffffff)|lx)==0) + return -two54/zero; /* log(+-0)=-inf */ + if (hx<0) return (x-x)/zero; /* log(-#) = NaN */ + k -= 54; x *= two54; /* subnormal number, scale up x */ + u.d = x; + hx = __HI(u); /* high word of x */ + } + if (hx >= 0x7ff00000) return x+x; + k += (hx>>20)-1023; + i = ((unsigned)k&0x80000000)>>31; + hx = (hx&0x000fffff)|((0x3ff-i)<<20); + y = (double)(k+i); + u.d = x; + __HI(u) = hx; + x = u.d; + z = y*log10_2lo + ivln10*__ieee754_log(x); + return z+y*log10_2hi; +} diff --git a/src/dom/js/fdlibm/e_pow.c b/src/dom/js/fdlibm/e_pow.c new file mode 100644 index 000000000..18c8d0695 --- /dev/null +++ b/src/dom/js/fdlibm/e_pow.c @@ -0,0 +1,386 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)e_pow.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* __ieee754_pow(x,y) return x**y + * + * n + * Method: Let x = 2 * (1+f) + * 1. Compute and return log2(x) in two pieces: + * log2(x) = w1 + w2, + * where w1 has 53-24 = 29 bit trailing zeros. + * 2. Perform y*log2(x) = n+y' by simulating muti-precision + * arithmetic, where |y'|<=0.5. + * 3. Return x**y = 2**n*exp(y'*log2) + * + * Special cases: + * 1. (anything) ** 0 is 1 + * 2. (anything) ** 1 is itself + * 3. (anything) ** NAN is NAN + * 4. NAN ** (anything except 0) is NAN + * 5. +-(|x| > 1) ** +INF is +INF + * 6. +-(|x| > 1) ** -INF is +0 + * 7. +-(|x| < 1) ** +INF is +0 + * 8. +-(|x| < 1) ** -INF is +INF + * 9. +-1 ** +-INF is NAN + * 10. +0 ** (+anything except 0, NAN) is +0 + * 11. -0 ** (+anything except 0, NAN, odd integer) is +0 + * 12. +0 ** (-anything except 0, NAN) is +INF + * 13. -0 ** (-anything except 0, NAN, odd integer) is +INF + * 14. -0 ** (odd integer) = -( +0 ** (odd integer) ) + * 15. +INF ** (+anything except 0,NAN) is +INF + * 16. +INF ** (-anything except 0,NAN) is +0 + * 17. -INF ** (anything) = -0 ** (-anything) + * 18. (-anything) ** (integer) is (-1)**(integer)*(+anything**integer) + * 19. (-anything except 0 and inf) ** (non-integer) is NAN + * + * Accuracy: + * pow(x,y) returns x**y nearly rounded. In particular + * pow(integer,integer) + * always returns the correct integer provided it is + * representable. + * + * Constants : + * The hexadecimal values are the intended ones for the following + * constants. The decimal values may be used, provided that the + * compiler will convert from decimal to binary accurately enough + * to produce the hexadecimal values shown. + */ + +#include "fdlibm.h" + +#if defined(_MSC_VER) +/* Microsoft Compiler */ +#pragma warning( disable : 4723 ) /* disables potential divide by 0 warning */ +#endif + +#ifdef __STDC__ +static const double +#else +static double +#endif +bp[] = {1.0, 1.5,}, +dp_h[] = { 0.0, 5.84962487220764160156e-01,}, /* 0x3FE2B803, 0x40000000 */ +dp_l[] = { 0.0, 1.35003920212974897128e-08,}, /* 0x3E4CFDEB, 0x43CFD006 */ +zero = 0.0, +one = 1.0, +two = 2.0, +two53 = 9007199254740992.0, /* 0x43400000, 0x00000000 */ +really_big = 1.0e300, +tiny = 1.0e-300, + /* poly coefs for (3/2)*(log(x)-2s-2/3*s**3 */ +L1 = 5.99999999999994648725e-01, /* 0x3FE33333, 0x33333303 */ +L2 = 4.28571428578550184252e-01, /* 0x3FDB6DB6, 0xDB6FABFF */ +L3 = 3.33333329818377432918e-01, /* 0x3FD55555, 0x518F264D */ +L4 = 2.72728123808534006489e-01, /* 0x3FD17460, 0xA91D4101 */ +L5 = 2.30660745775561754067e-01, /* 0x3FCD864A, 0x93C9DB65 */ +L6 = 2.06975017800338417784e-01, /* 0x3FCA7E28, 0x4A454EEF */ +P1 = 1.66666666666666019037e-01, /* 0x3FC55555, 0x5555553E */ +P2 = -2.77777777770155933842e-03, /* 0xBF66C16C, 0x16BEBD93 */ +P3 = 6.61375632143793436117e-05, /* 0x3F11566A, 0xAF25DE2C */ +P4 = -1.65339022054652515390e-06, /* 0xBEBBBD41, 0xC5D26BF1 */ +P5 = 4.13813679705723846039e-08, /* 0x3E663769, 0x72BEA4D0 */ +lg2 = 6.93147180559945286227e-01, /* 0x3FE62E42, 0xFEFA39EF */ +lg2_h = 6.93147182464599609375e-01, /* 0x3FE62E43, 0x00000000 */ +lg2_l = -1.90465429995776804525e-09, /* 0xBE205C61, 0x0CA86C39 */ +ovt = 8.0085662595372944372e-0017, /* -(1024-log2(ovfl+.5ulp)) */ +cp = 9.61796693925975554329e-01, /* 0x3FEEC709, 0xDC3A03FD =2/(3ln2) */ +cp_h = 9.61796700954437255859e-01, /* 0x3FEEC709, 0xE0000000 =(float)cp */ +cp_l = -7.02846165095275826516e-09, /* 0xBE3E2FE0, 0x145B01F5 =tail of cp_h*/ +ivln2 = 1.44269504088896338700e+00, /* 0x3FF71547, 0x652B82FE =1/ln2 */ +ivln2_h = 1.44269502162933349609e+00, /* 0x3FF71547, 0x60000000 =24b 1/ln2*/ +ivln2_l = 1.92596299112661746887e-08; /* 0x3E54AE0B, 0xF85DDF44 =1/ln2 tail*/ + +#ifdef __STDC__ + double __ieee754_pow(double x, double y) +#else + double __ieee754_pow(x,y) + double x, y; +#endif +{ + fd_twoints ux, uy, uz; + double y1,t1,p_h,t,z,ax; + double z_h,z_l,p_l; + double t2,r,s,u,v,w; + int i,j,k,yisint,n; + int hx,hy,ix,iy; + unsigned lx,ly; + + ux.d = x; uy.d = y; + hx = __HI(ux); lx = __LO(ux); + hy = __HI(uy); ly = __LO(uy); + ix = hx&0x7fffffff; iy = hy&0x7fffffff; + + /* y==zero: x**0 = 1 */ + if((iy|ly)==0) return one; + + /* +-NaN return x+y */ + if(ix > 0x7ff00000 || ((ix==0x7ff00000)&&(lx!=0)) || + iy > 0x7ff00000 || ((iy==0x7ff00000)&&(ly!=0))) + return x+y; + + /* determine if y is an odd int when x < 0 + * yisint = 0 ... y is not an integer + * yisint = 1 ... y is an odd int + * yisint = 2 ... y is an even int + */ + yisint = 0; + if(hx<0) { + if(iy>=0x43400000) yisint = 2; /* even integer y */ + else if(iy>=0x3ff00000) { + k = (iy>>20)-0x3ff; /* exponent */ + if(k>20) { + j = ly>>(52-k); + if((j<<(52-k))==(int)ly) yisint = 2-(j&1); + } else if(ly==0) { + j = iy>>(20-k); + if((j<<(20-k))==iy) yisint = 2-(j&1); + } + } + } + + /* special value of y */ + if(ly==0) { + if (iy==0x7ff00000) { /* y is +-inf */ + if(((ix-0x3ff00000)|lx)==0) +#ifdef _WIN32 +/* VC++ optimizer reduces y - y to 0 */ + return y / y; +#else + return y - y; /* inf**+-1 is NaN */ +#endif + else if (ix >= 0x3ff00000)/* (|x|>1)**+-inf = inf,0 */ + return (hy>=0)? y: zero; + else /* (|x|<1)**-,+inf = inf,0 */ + return (hy<0)?-y: zero; + } + if(iy==0x3ff00000) { /* y is +-1 */ + if(hy<0) return one/x; else return x; + } + if(hy==0x40000000) return x*x; /* y is 2 */ + if(hy==0x3fe00000) { /* y is 0.5 */ + if(hx>=0) /* x >= +0 */ + return fd_sqrt(x); + } + } + + ax = fd_fabs(x); + /* special value of x */ + if(lx==0) { + if(ix==0x7ff00000||ix==0||ix==0x3ff00000){ + z = ax; /*x is +-0,+-inf,+-1*/ + if(hy<0) z = one/z; /* z = (1/|x|) */ + if(hx<0) { + if(((ix-0x3ff00000)|yisint)==0) { + z = (z-z)/(z-z); /* (-1)**non-int is NaN */ + } else if(yisint==1) { +#ifdef HPUX + uz.d = z; + __HI(uz) ^= 1<<31; /* some HPUXes cannot negate 0.. */ + z = uz.d; +#else + z = -z; /* (x<0)**odd = -(|x|**odd) */ +#endif + } + } + return z; + } + } + + /* (x<0)**(non-int) is NaN */ + if((((hx>>31)+1)|yisint)==0) return (x-x)/(x-x); + + /* |y| is really_big */ + if(iy>0x41e00000) { /* if |y| > 2**31 */ + if(iy>0x43f00000){ /* if |y| > 2**64, must o/uflow */ + if(ix<=0x3fefffff) return (hy<0)? really_big*really_big:tiny*tiny; + if(ix>=0x3ff00000) return (hy>0)? really_big*really_big:tiny*tiny; + } + /* over/underflow if x is not close to one */ + if(ix<0x3fefffff) return (hy<0)? really_big*really_big:tiny*tiny; + if(ix>0x3ff00000) return (hy>0)? really_big*really_big:tiny*tiny; + /* now |1-x| is tiny <= 2**-20, suffice to compute + log(x) by x-x^2/2+x^3/3-x^4/4 */ + t = x-1; /* t has 20 trailing zeros */ + w = (t*t)*(0.5-t*(0.3333333333333333333333-t*0.25)); + u = ivln2_h*t; /* ivln2_h has 21 sig. bits */ + v = t*ivln2_l-w*ivln2; + t1 = u+v; + uz.d = t1; + __LO(uz) = 0; + t1 = uz.d; + t2 = v-(t1-u); + } else { + double s_h,t_h; + double s2,s_l,t_l; + n = 0; + /* take care subnormal number */ + if(ix<0x00100000) + {ax *= two53; n -= 53; uz.d = ax; ix = __HI(uz); } + n += ((ix)>>20)-0x3ff; + j = ix&0x000fffff; + /* determine interval */ + ix = j|0x3ff00000; /* normalize ix */ + if(j<=0x3988E) k=0; /* |x|>1)|0x20000000)+0x00080000+(k<<18); + t_h = uz.d; + t_l = ax - (t_h-bp[k]); + s_l = v*((u-s_h*t_h)-s_h*t_l); + /* compute log(ax) */ + s2 = s*s; + r = s2*s2*(L1+s2*(L2+s2*(L3+s2*(L4+s2*(L5+s2*L6))))); + r += s_l*(s_h+s); + s2 = s_h*s_h; + t_h = 3.0+s2+r; + uz.d = t_h; + __LO(uz) = 0; + t_h = uz.d; + t_l = r-((t_h-3.0)-s2); + /* u+v = s*(1+...) */ + u = s_h*t_h; + v = s_l*t_h+t_l*s; + /* 2/(3log2)*(s+...) */ + p_h = u+v; + uz.d = p_h; + __LO(uz) = 0; + p_h = uz.d; + p_l = v-(p_h-u); + z_h = cp_h*p_h; /* cp_h+cp_l = 2/(3*log2) */ + z_l = cp_l*p_h+p_l*cp+dp_l[k]; + /* log2(ax) = (s+..)*2/(3*log2) = n + dp_h + z_h + z_l */ + t = (double)n; + t1 = (((z_h+z_l)+dp_h[k])+t); + uz.d = t1; + __LO(uz) = 0; + t1 = uz.d; + t2 = z_l-(((t1-t)-dp_h[k])-z_h); + } + + s = one; /* s (sign of result -ve**odd) = -1 else = 1 */ + if((((hx>>31)+1)|(yisint-1))==0) s = -one;/* (-ve)**(odd int) */ + + /* split up y into y1+y2 and compute (y1+y2)*(t1+t2) */ + y1 = y; + uy.d = y1; + __LO(uy) = 0; + y1 = uy.d; + p_l = (y-y1)*t1+y*t2; + p_h = y1*t1; + z = p_l+p_h; + uz.d = z; + j = __HI(uz); + i = __LO(uz); + + if (j>=0x40900000) { /* z >= 1024 */ + if(((j-0x40900000)|i)!=0) /* if z > 1024 */ + return s*really_big*really_big; /* overflow */ + else { + if(p_l+ovt>z-p_h) return s*really_big*really_big; /* overflow */ + } + } else if((j&0x7fffffff)>=0x4090cc00 ) { /* z <= -1075 */ + if(((j-0xc090cc00)|i)!=0) /* z < -1075 */ + return s*tiny*tiny; /* underflow */ + else { + if(p_l<=z-p_h) return s*tiny*tiny; /* underflow */ + } + } + /* + * compute 2**(p_h+p_l) + */ + i = j&0x7fffffff; + k = (i>>20)-0x3ff; + n = 0; + if(i>0x3fe00000) { /* if |z| > 0.5, set n = [z+0.5] */ + n = j+(0x00100000>>(k+1)); + k = ((n&0x7fffffff)>>20)-0x3ff; /* new k for n */ + t = zero; + uz.d = t; + __HI(uz) = (n&~(0x000fffff>>k)); + t = uz.d; + n = ((n&0x000fffff)|0x00100000)>>(20-k); + if(j<0) n = -n; + p_h -= t; + } + t = p_l+p_h; + uz.d = t; + __LO(uz) = 0; + t = uz.d; + u = t*lg2_h; + v = (p_l-(t-p_h))*lg2+t*lg2_l; + z = u+v; + w = v-(z-u); + t = z*z; + t1 = z - t*(P1+t*(P2+t*(P3+t*(P4+t*P5)))); + r = (z*t1)/(t1-two)-(w+z*w); + z = one-(r-z); + uz.d = z; + j = __HI(uz); + j += (n<<20); + if((j>>20)<=0) z = fd_scalbn(z,n); /* subnormal output */ + else { uz.d = z; __HI(uz) += (n<<20); z = uz.d; } + return s*z; +} diff --git a/src/dom/js/fdlibm/e_rem_pio2.c b/src/dom/js/fdlibm/e_rem_pio2.c new file mode 100644 index 000000000..6f9423551 --- /dev/null +++ b/src/dom/js/fdlibm/e_rem_pio2.c @@ -0,0 +1,221 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)e_rem_pio2.c 1.4 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + * + */ + +/* __ieee754_rem_pio2(x,y) + * + * return the remainder of x rem pi/2 in y[0]+y[1] + * use __kernel_rem_pio2() + */ + +#include "fdlibm.h" + +/* + * Table of constants for 2/pi, 396 Hex digits (476 decimal) of 2/pi + */ +#ifdef __STDC__ +static const int two_over_pi[] = { +#else +static int two_over_pi[] = { +#endif +0xA2F983, 0x6E4E44, 0x1529FC, 0x2757D1, 0xF534DD, 0xC0DB62, +0x95993C, 0x439041, 0xFE5163, 0xABDEBB, 0xC561B7, 0x246E3A, +0x424DD2, 0xE00649, 0x2EEA09, 0xD1921C, 0xFE1DEB, 0x1CB129, +0xA73EE8, 0x8235F5, 0x2EBB44, 0x84E99C, 0x7026B4, 0x5F7E41, +0x3991D6, 0x398353, 0x39F49C, 0x845F8B, 0xBDF928, 0x3B1FF8, +0x97FFDE, 0x05980F, 0xEF2F11, 0x8B5A0A, 0x6D1F6D, 0x367ECF, +0x27CB09, 0xB74F46, 0x3F669E, 0x5FEA2D, 0x7527BA, 0xC7EBE5, +0xF17B3D, 0x0739F7, 0x8A5292, 0xEA6BFB, 0x5FB11F, 0x8D5D08, +0x560330, 0x46FC7B, 0x6BABF0, 0xCFBC20, 0x9AF436, 0x1DA9E3, +0x91615E, 0xE61B08, 0x659985, 0x5F14A0, 0x68408D, 0xFFD880, +0x4D7327, 0x310606, 0x1556CA, 0x73A8C9, 0x60E27B, 0xC08C6B, +}; + +#ifdef __STDC__ +static const int npio2_hw[] = { +#else +static int npio2_hw[] = { +#endif +0x3FF921FB, 0x400921FB, 0x4012D97C, 0x401921FB, 0x401F6A7A, 0x4022D97C, +0x4025FDBB, 0x402921FB, 0x402C463A, 0x402F6A7A, 0x4031475C, 0x4032D97C, +0x40346B9C, 0x4035FDBB, 0x40378FDB, 0x403921FB, 0x403AB41B, 0x403C463A, +0x403DD85A, 0x403F6A7A, 0x40407E4C, 0x4041475C, 0x4042106C, 0x4042D97C, +0x4043A28C, 0x40446B9C, 0x404534AC, 0x4045FDBB, 0x4046C6CB, 0x40478FDB, +0x404858EB, 0x404921FB, +}; + +/* + * invpio2: 53 bits of 2/pi + * pio2_1: first 33 bit of pi/2 + * pio2_1t: pi/2 - pio2_1 + * pio2_2: second 33 bit of pi/2 + * pio2_2t: pi/2 - (pio2_1+pio2_2) + * pio2_3: third 33 bit of pi/2 + * pio2_3t: pi/2 - (pio2_1+pio2_2+pio2_3) + */ + +#ifdef __STDC__ +static const double +#else +static double +#endif +zero = 0.00000000000000000000e+00, /* 0x00000000, 0x00000000 */ +half = 5.00000000000000000000e-01, /* 0x3FE00000, 0x00000000 */ +two24 = 1.67772160000000000000e+07, /* 0x41700000, 0x00000000 */ +invpio2 = 6.36619772367581382433e-01, /* 0x3FE45F30, 0x6DC9C883 */ +pio2_1 = 1.57079632673412561417e+00, /* 0x3FF921FB, 0x54400000 */ +pio2_1t = 6.07710050650619224932e-11, /* 0x3DD0B461, 0x1A626331 */ +pio2_2 = 6.07710050630396597660e-11, /* 0x3DD0B461, 0x1A600000 */ +pio2_2t = 2.02226624879595063154e-21, /* 0x3BA3198A, 0x2E037073 */ +pio2_3 = 2.02226624871116645580e-21, /* 0x3BA3198A, 0x2E000000 */ +pio2_3t = 8.47842766036889956997e-32; /* 0x397B839A, 0x252049C1 */ + +#ifdef __STDC__ + int __ieee754_rem_pio2(double x, double *y) +#else + int __ieee754_rem_pio2(x,y) + double x,y[]; +#endif +{ + fd_twoints u, ux, uz; + double z,w,t,r,fn; + double tx[3]; + int e0,i,j,nx,n,ix,hx; + + u.d = x; + hx = __HI(u); /* high word of x */ + ix = hx&0x7fffffff; + if(ix<=0x3fe921fb) /* |x| ~<= pi/4 , no need for reduction */ + {y[0] = x; y[1] = 0; return 0;} + if(ix<0x4002d97c) { /* |x| < 3pi/4, special case with n=+-1 */ + if(hx>0) { + z = x - pio2_1; + if(ix!=0x3ff921fb) { /* 33+53 bit pi is good enough */ + y[0] = z - pio2_1t; + y[1] = (z-y[0])-pio2_1t; + } else { /* near pi/2, use 33+33+53 bit pi */ + z -= pio2_2; + y[0] = z - pio2_2t; + y[1] = (z-y[0])-pio2_2t; + } + return 1; + } else { /* negative x */ + z = x + pio2_1; + if(ix!=0x3ff921fb) { /* 33+53 bit pi is good enough */ + y[0] = z + pio2_1t; + y[1] = (z-y[0])+pio2_1t; + } else { /* near pi/2, use 33+33+53 bit pi */ + z += pio2_2; + y[0] = z + pio2_2t; + y[1] = (z-y[0])+pio2_2t; + } + return -1; + } + } + if(ix<=0x413921fb) { /* |x| ~<= 2^19*(pi/2), medium size */ + t = fd_fabs(x); + n = (int) (t*invpio2+half); + fn = (double)n; + r = t-fn*pio2_1; + w = fn*pio2_1t; /* 1st round good to 85 bit */ + if(n<32&&ix!=npio2_hw[n-1]) { + y[0] = r-w; /* quick check no cancellation */ + } else { + j = ix>>20; + y[0] = r-w; + u.d = y[0]; + i = j-(((__HI(u))>>20)&0x7ff); + if(i>16) { /* 2nd iteration needed, good to 118 */ + t = r; + w = fn*pio2_2; + r = t-w; + w = fn*pio2_2t-((t-r)-w); + y[0] = r-w; + u.d = y[0]; + i = j-(((__HI(u))>>20)&0x7ff); + if(i>49) { /* 3rd iteration need, 151 bits acc */ + t = r; /* will cover all possible cases */ + w = fn*pio2_3; + r = t-w; + w = fn*pio2_3t-((t-r)-w); + y[0] = r-w; + } + } + } + y[1] = (r-y[0])-w; + if(hx<0) {y[0] = -y[0]; y[1] = -y[1]; return -n;} + else return n; + } + /* + * all other (large) arguments + */ + if(ix>=0x7ff00000) { /* x is inf or NaN */ + y[0]=y[1]=x-x; return 0; + } + /* set z = scalbn(|x|,ilogb(x)-23) */ + ux.d = x; uz.d = z; + __LO(uz) = __LO(ux); + z = uz.d; + e0 = (ix>>20)-1046; /* e0 = ilogb(z)-23; */ + uz.d = z; + __HI(uz) = ix - (e0<<20); + z = uz.d; + for(i=0;i<2;i++) { + tx[i] = (double)((int)(z)); + z = (z-tx[i])*two24; + } + tx[2] = z; + nx = 3; + while(tx[nx-1]==zero) nx--; /* skip zero term */ + n = __kernel_rem_pio2(tx,y,e0,nx,2,two_over_pi); + if(hx<0) {y[0] = -y[0]; y[1] = -y[1]; return -n;} + return n; +} diff --git a/src/dom/js/fdlibm/e_remainder.c b/src/dom/js/fdlibm/e_remainder.c new file mode 100644 index 000000000..de40f0c2a --- /dev/null +++ b/src/dom/js/fdlibm/e_remainder.c @@ -0,0 +1,120 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)e_remainder.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* __ieee754_remainder(x,p) + * Return : + * returns x REM p = x - [x/p]*p as if in infinite + * precise arithmetic, where [x/p] is the (infinite bit) + * integer nearest x/p (in half way case choose the even one). + * Method : + * Based on fmod() return x-[x/p]chopped*p exactlp. + */ + +#include "fdlibm.h" + +#ifdef __STDC__ +static const double zero = 0.0; +#else +static double zero = 0.0; +#endif + + +#ifdef __STDC__ + double __ieee754_remainder(double x, double p) +#else + double __ieee754_remainder(x,p) + double x,p; +#endif +{ + fd_twoints u; + int hx,hp; + unsigned sx,lx,lp; + double p_half; + + u.d = x; + hx = __HI(u); /* high word of x */ + lx = __LO(u); /* low word of x */ + u.d = p; + hp = __HI(u); /* high word of p */ + lp = __LO(u); /* low word of p */ + sx = hx&0x80000000; + hp &= 0x7fffffff; + hx &= 0x7fffffff; + + /* purge off exception values */ + if((hp|lp)==0) return (x*p)/(x*p); /* p = 0 */ + if((hx>=0x7ff00000)|| /* x not finite */ + ((hp>=0x7ff00000)&& /* p is NaN */ + (((hp-0x7ff00000)|lp)!=0))) + return (x*p)/(x*p); + + + if (hp<=0x7fdfffff) x = __ieee754_fmod(x,p+p); /* now x < 2p */ + if (((hx-hp)|(lx-lp))==0) return zero*x; + x = fd_fabs(x); + p = fd_fabs(p); + if (hp<0x00200000) { + if(x+x>p) { + x-=p; + if(x+x>=p) x -= p; + } + } else { + p_half = 0.5*p; + if(x>p_half) { + x-=p; + if(x>=p_half) x -= p; + } + } + u.d = x; + __HI(u) ^= sx; + x = u.d; + return x; +} diff --git a/src/dom/js/fdlibm/e_scalb.c b/src/dom/js/fdlibm/e_scalb.c new file mode 100644 index 000000000..621704ea0 --- /dev/null +++ b/src/dom/js/fdlibm/e_scalb.c @@ -0,0 +1,89 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)e_scalb.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * __ieee754_scalb(x, fn) is provide for + * passing various standard test suite. One + * should use scalbn() instead. + */ + +#include "fdlibm.h" + +#ifdef _SCALB_INT +#ifdef __STDC__ + double __ieee754_scalb(double x, int fn) +#else + double __ieee754_scalb(x,fn) + double x; int fn; +#endif +#else +#ifdef __STDC__ + double __ieee754_scalb(double x, double fn) +#else + double __ieee754_scalb(x,fn) + double x, fn; +#endif +#endif +{ +#ifdef _SCALB_INT + return fd_scalbn(x,fn); +#else + if (fd_isnan(x)||fd_isnan(fn)) return x*fn; + if (!fd_finite(fn)) { + if(fn>0.0) return x*fn; + else return x/(-fn); + } + if (fd_rint(fn)!=fn) return (fn-fn)/(fn-fn); + if ( fn > 65000.0) return fd_scalbn(x, 65000); + if (-fn > 65000.0) return fd_scalbn(x,-65000); + return fd_scalbn(x,(int)fn); +#endif +} diff --git a/src/dom/js/fdlibm/e_sinh.c b/src/dom/js/fdlibm/e_sinh.c new file mode 100644 index 000000000..98ab9b5a3 --- /dev/null +++ b/src/dom/js/fdlibm/e_sinh.c @@ -0,0 +1,122 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)e_sinh.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* __ieee754_sinh(x) + * Method : + * mathematically sinh(x) if defined to be (exp(x)-exp(-x))/2 + * 1. Replace x by |x| (sinh(-x) = -sinh(x)). + * 2. + * E + E/(E+1) + * 0 <= x <= 22 : sinh(x) := --------------, E=expm1(x) + * 2 + * + * 22 <= x <= lnovft : sinh(x) := exp(x)/2 + * lnovft <= x <= ln2ovft: sinh(x) := exp(x/2)/2 * exp(x/2) + * ln2ovft < x : sinh(x) := x*shuge (overflow) + * + * Special cases: + * sinh(x) is |x| if x is +INF, -INF, or NaN. + * only sinh(0)=0 is exact for finite x. + */ + +#include "fdlibm.h" + +#ifdef __STDC__ +static const double one = 1.0, shuge = 1.0e307; +#else +static double one = 1.0, shuge = 1.0e307; +#endif + +#ifdef __STDC__ + double __ieee754_sinh(double x) +#else + double __ieee754_sinh(x) + double x; +#endif +{ + fd_twoints u; + double t,w,h; + int ix,jx; + unsigned lx; + + /* High word of |x|. */ + u.d = x; + jx = __HI(u); + ix = jx&0x7fffffff; + + /* x is INF or NaN */ + if(ix>=0x7ff00000) return x+x; + + h = 0.5; + if (jx<0) h = -h; + /* |x| in [0,22], return sign(x)*0.5*(E+E/(E+1))) */ + if (ix < 0x40360000) { /* |x|<22 */ + if (ix<0x3e300000) /* |x|<2**-28 */ + if(shuge+x>one) return x;/* sinh(tiny) = tiny with inexact */ + t = fd_expm1(fd_fabs(x)); + if(ix<0x3ff00000) return h*(2.0*t-t*t/(t+one)); + return h*(t+t/(t+one)); + } + + /* |x| in [22, log(maxdouble)] return 0.5*exp(|x|) */ + if (ix < 0x40862E42) return h*__ieee754_exp(fd_fabs(x)); + + /* |x| in [log(maxdouble), overflowthresold] */ + lx = *( (((*(unsigned*)&one)>>29)) + (unsigned*)&x); + if (ix<0x408633CE || (ix==0x408633ce)&&(lx<=(unsigned)0x8fb9f87d)) { + w = __ieee754_exp(0.5*fd_fabs(x)); + t = h*w; + return t*w; + } + + /* |x| > overflowthresold, sinh(x) overflow */ + return x*shuge; +} diff --git a/src/dom/js/fdlibm/e_sqrt.c b/src/dom/js/fdlibm/e_sqrt.c new file mode 100644 index 000000000..91802839b --- /dev/null +++ b/src/dom/js/fdlibm/e_sqrt.c @@ -0,0 +1,497 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +/* @(#)e_sqrt.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* __ieee754_sqrt(x) + * Return correctly rounded sqrt. + * ------------------------------------------ + * | Use the hardware sqrt if you have one | + * ------------------------------------------ + * Method: + * Bit by bit method using integer arithmetic. (Slow, but portable) + * 1. Normalization + * Scale x to y in [1,4) with even powers of 2: + * find an integer k such that 1 <= (y=x*2^(2k)) < 4, then + * sqrt(y) = 2^k * sqrt(x) + * 2. Bit by bit computation + * Let q = sqrt(y) truncated to i bit after binary point (q = 1), + * i 0 + * i+1 2 + * s = 2*q , and y = 2 * ( y - q ). (1) + * i i i i + * + * To compute q from q , one checks whether + * i+1 i + * + * -(i+1) 2 + * (q + 2 ) <= y. (2) + * i + * -(i+1) + * If (2) is false, then q = q ; otherwise q = q + 2 . + * i+1 i i+1 i + * + * With some algebric manipulation, it is not difficult to see + * that (2) is equivalent to + * -(i+1) + * s + 2 <= y (3) + * i i + * + * The advantage of (3) is that s and y can be computed by + * i i + * the following recurrence formula: + * if (3) is false + * + * s = s , y = y ; (4) + * i+1 i i+1 i + * + * otherwise, + * -i -(i+1) + * s = s + 2 , y = y - s - 2 (5) + * i+1 i i+1 i i + * + * One may easily use induction to prove (4) and (5). + * Note. Since the left hand side of (3) contain only i+2 bits, + * it does not necessary to do a full (53-bit) comparison + * in (3). + * 3. Final rounding + * After generating the 53 bits result, we compute one more bit. + * Together with the remainder, we can decide whether the + * result is exact, bigger than 1/2ulp, or less than 1/2ulp + * (it will never equal to 1/2ulp). + * The rounding mode can be detected by checking whether + * huge + tiny is equal to huge, and whether huge - tiny is + * equal to huge for some floating point number "huge" and "tiny". + * + * Special cases: + * sqrt(+-0) = +-0 ... exact + * sqrt(inf) = inf + * sqrt(-ve) = NaN ... with invalid signal + * sqrt(NaN) = NaN ... with invalid signal for signaling NaN + * + * Other methods : see the appended file at the end of the program below. + *--------------- + */ + +#include "fdlibm.h" + +#if defined(_MSC_VER) +/* Microsoft Compiler */ +#pragma warning( disable : 4723 ) /* disables potential divide by 0 warning */ +#endif + +#ifdef __STDC__ +static const double one = 1.0, tiny=1.0e-300; +#else +static double one = 1.0, tiny=1.0e-300; +#endif + +#ifdef __STDC__ + double __ieee754_sqrt(double x) +#else + double __ieee754_sqrt(x) + double x; +#endif +{ + fd_twoints u; + double z; + int sign = (int)0x80000000; + unsigned r,t1,s1,ix1,q1; + int ix0,s0,q,m,t,i; + + u.d = x; + ix0 = __HI(u); /* high word of x */ + ix1 = __LO(u); /* low word of x */ + + /* take care of Inf and NaN */ + if((ix0&0x7ff00000)==0x7ff00000) { + return x*x+x; /* sqrt(NaN)=NaN, sqrt(+inf)=+inf + sqrt(-inf)=sNaN */ + } + /* take care of zero */ + if(ix0<=0) { + if(((ix0&(~sign))|ix1)==0) return x;/* sqrt(+-0) = +-0 */ + else if(ix0<0) + return (x-x)/(x-x); /* sqrt(-ve) = sNaN */ + } + /* normalize x */ + m = (ix0>>20); + if(m==0) { /* subnormal x */ + while(ix0==0) { + m -= 21; + ix0 |= (ix1>>11); ix1 <<= 21; + } + for(i=0;(ix0&0x00100000)==0;i++) ix0<<=1; + m -= i-1; + ix0 |= (ix1>>(32-i)); + ix1 <<= i; + } + m -= 1023; /* unbias exponent */ + ix0 = (ix0&0x000fffff)|0x00100000; + if(m&1){ /* odd m, double x to make it even */ + ix0 += ix0 + ((ix1&sign)>>31); + ix1 += ix1; + } + m >>= 1; /* m = [m/2] */ + + /* generate sqrt(x) bit by bit */ + ix0 += ix0 + ((ix1&sign)>>31); + ix1 += ix1; + q = q1 = s0 = s1 = 0; /* [q,q1] = sqrt(x) */ + r = 0x00200000; /* r = moving bit from right to left */ + + while(r!=0) { + t = s0+r; + if(t<=ix0) { + s0 = t+r; + ix0 -= t; + q += r; + } + ix0 += ix0 + ((ix1&sign)>>31); + ix1 += ix1; + r>>=1; + } + + r = sign; + while(r!=0) { + t1 = s1+r; + t = s0; + if((t>31); + ix1 += ix1; + r>>=1; + } + + /* use floating add to find out rounding direction */ + if((ix0|ix1)!=0) { + z = one-tiny; /* trigger inexact flag */ + if (z>=one) { + z = one+tiny; + if (q1==(unsigned)0xffffffff) { q1=0; q += 1;} + else if (z>one) { + if (q1==(unsigned)0xfffffffe) q+=1; + q1+=2; + } else + q1 += (q1&1); + } + } + ix0 = (q>>1)+0x3fe00000; + ix1 = q1>>1; + if ((q&1)==1) ix1 |= sign; + ix0 += (m <<20); + u.d = z; + __HI(u) = ix0; + __LO(u) = ix1; + z = u.d; + return z; +} + +/* +Other methods (use floating-point arithmetic) +------------- +(This is a copy of a drafted paper by Prof W. Kahan +and K.C. Ng, written in May, 1986) + + Two algorithms are given here to implement sqrt(x) + (IEEE double precision arithmetic) in software. + Both supply sqrt(x) correctly rounded. The first algorithm (in + Section A) uses newton iterations and involves four divisions. + The second one uses reciproot iterations to avoid division, but + requires more multiplications. Both algorithms need the ability + to chop results of arithmetic operations instead of round them, + and the INEXACT flag to indicate when an arithmetic operation + is executed exactly with no roundoff error, all part of the + standard (IEEE 754-1985). The ability to perform shift, add, + subtract and logical AND operations upon 32-bit words is needed + too, though not part of the standard. + +A. sqrt(x) by Newton Iteration + + (1) Initial approximation + + Let x0 and x1 be the leading and the trailing 32-bit words of + a floating point number x (in IEEE double format) respectively + + 1 11 52 ...widths + ------------------------------------------------------ + x: |s| e | f | + ------------------------------------------------------ + msb lsb msb lsb ...order + + + ------------------------ ------------------------ + x0: |s| e | f1 | x1: | f2 | + ------------------------ ------------------------ + + By performing shifts and subtracts on x0 and x1 (both regarded + as integers), we obtain an 8-bit approximation of sqrt(x) as + follows. + + k := (x0>>1) + 0x1ff80000; + y0 := k - T1[31&(k>>15)]. ... y ~ sqrt(x) to 8 bits + Here k is a 32-bit integer and T1[] is an integer array containing + correction terms. Now magically the floating value of y (y's + leading 32-bit word is y0, the value of its trailing word is 0) + approximates sqrt(x) to almost 8-bit. + + Value of T1: + static int T1[32]= { + 0, 1024, 3062, 5746, 9193, 13348, 18162, 23592, + 29598, 36145, 43202, 50740, 58733, 67158, 75992, 85215, + 83599, 71378, 60428, 50647, 41945, 34246, 27478, 21581, + 16499, 12183, 8588, 5674, 3403, 1742, 661, 130,}; + + (2) Iterative refinement + + Apply Heron's rule three times to y, we have y approximates + sqrt(x) to within 1 ulp (Unit in the Last Place): + + y := (y+x/y)/2 ... almost 17 sig. bits + y := (y+x/y)/2 ... almost 35 sig. bits + y := y-(y-x/y)/2 ... within 1 ulp + + + Remark 1. + Another way to improve y to within 1 ulp is: + + y := (y+x/y) ... almost 17 sig. bits to 2*sqrt(x) + y := y - 0x00100006 ... almost 18 sig. bits to sqrt(x) + + 2 + (x-y )*y + y := y + 2* ---------- ...within 1 ulp + 2 + 3y + x + + + This formula has one division fewer than the one above; however, + it requires more multiplications and additions. Also x must be + scaled in advance to avoid spurious overflow in evaluating the + expression 3y*y+x. Hence it is not recommended uless division + is slow. If division is very slow, then one should use the + reciproot algorithm given in section B. + + (3) Final adjustment + + By twiddling y's last bit it is possible to force y to be + correctly rounded according to the prevailing rounding mode + as follows. Let r and i be copies of the rounding mode and + inexact flag before entering the square root program. Also we + use the expression y+-ulp for the next representable floating + numbers (up and down) of y. Note that y+-ulp = either fixed + point y+-1, or multiply y by nextafter(1,+-inf) in chopped + mode. + + I := FALSE; ... reset INEXACT flag I + R := RZ; ... set rounding mode to round-toward-zero + z := x/y; ... chopped quotient, possibly inexact + If(not I) then { ... if the quotient is exact + if(z=y) { + I := i; ... restore inexact flag + R := r; ... restore rounded mode + return sqrt(x):=y. + } else { + z := z - ulp; ... special rounding + } + } + i := TRUE; ... sqrt(x) is inexact + If (r=RN) then z=z+ulp ... rounded-to-nearest + If (r=RP) then { ... round-toward-+inf + y = y+ulp; z=z+ulp; + } + y := y+z; ... chopped sum + y0:=y0-0x00100000; ... y := y/2 is correctly rounded. + I := i; ... restore inexact flag + R := r; ... restore rounded mode + return sqrt(x):=y. + + (4) Special cases + + Square root of +inf, +-0, or NaN is itself; + Square root of a negative number is NaN with invalid signal. + + +B. sqrt(x) by Reciproot Iteration + + (1) Initial approximation + + Let x0 and x1 be the leading and the trailing 32-bit words of + a floating point number x (in IEEE double format) respectively + (see section A). By performing shifs and subtracts on x0 and y0, + we obtain a 7.8-bit approximation of 1/sqrt(x) as follows. + + k := 0x5fe80000 - (x0>>1); + y0:= k - T2[63&(k>>14)]. ... y ~ 1/sqrt(x) to 7.8 bits + + Here k is a 32-bit integer and T2[] is an integer array + containing correction terms. Now magically the floating + value of y (y's leading 32-bit word is y0, the value of + its trailing word y1 is set to zero) approximates 1/sqrt(x) + to almost 7.8-bit. + + Value of T2: + static int T2[64]= { + 0x1500, 0x2ef8, 0x4d67, 0x6b02, 0x87be, 0xa395, 0xbe7a, 0xd866, + 0xf14a, 0x1091b,0x11fcd,0x13552,0x14999,0x15c98,0x16e34,0x17e5f, + 0x18d03,0x19a01,0x1a545,0x1ae8a,0x1b5c4,0x1bb01,0x1bfde,0x1c28d, + 0x1c2de,0x1c0db,0x1ba73,0x1b11c,0x1a4b5,0x1953d,0x18266,0x16be0, + 0x1683e,0x179d8,0x18a4d,0x19992,0x1a789,0x1b445,0x1bf61,0x1c989, + 0x1d16d,0x1d77b,0x1dddf,0x1e2ad,0x1e5bf,0x1e6e8,0x1e654,0x1e3cd, + 0x1df2a,0x1d635,0x1cb16,0x1be2c,0x1ae4e,0x19bde,0x1868e,0x16e2e, + 0x1527f,0x1334a,0x11051,0xe951, 0xbe01, 0x8e0d, 0x5924, 0x1edd,}; + + (2) Iterative refinement + + Apply Reciproot iteration three times to y and multiply the + result by x to get an approximation z that matches sqrt(x) + to about 1 ulp. To be exact, we will have + -1ulp < sqrt(x)-z<1.0625ulp. + + ... set rounding mode to Round-to-nearest + y := y*(1.5-0.5*x*y*y) ... almost 15 sig. bits to 1/sqrt(x) + y := y*((1.5-2^-30)+0.5*x*y*y)... about 29 sig. bits to 1/sqrt(x) + ... special arrangement for better accuracy + z := x*y ... 29 bits to sqrt(x), with z*y<1 + z := z + 0.5*z*(1-z*y) ... about 1 ulp to sqrt(x) + + Remark 2. The constant 1.5-2^-30 is chosen to bias the error so that + (a) the term z*y in the final iteration is always less than 1; + (b) the error in the final result is biased upward so that + -1 ulp < sqrt(x) - z < 1.0625 ulp + instead of |sqrt(x)-z|<1.03125ulp. + + (3) Final adjustment + + By twiddling y's last bit it is possible to force y to be + correctly rounded according to the prevailing rounding mode + as follows. Let r and i be copies of the rounding mode and + inexact flag before entering the square root program. Also we + use the expression y+-ulp for the next representable floating + numbers (up and down) of y. Note that y+-ulp = either fixed + point y+-1, or multiply y by nextafter(1,+-inf) in chopped + mode. + + R := RZ; ... set rounding mode to round-toward-zero + switch(r) { + case RN: ... round-to-nearest + if(x<= z*(z-ulp)...chopped) z = z - ulp; else + if(x<= z*(z+ulp)...chopped) z = z; else z = z+ulp; + break; + case RZ:case RM: ... round-to-zero or round-to--inf + R:=RP; ... reset rounding mod to round-to-+inf + if(x=(z+ulp)*(z+ulp) ...rounded up) z = z+ulp; + break; + case RP: ... round-to-+inf + if(x>(z+ulp)*(z+ulp)...chopped) z = z+2*ulp; else + if(x>z*z ...chopped) z = z+ulp; + break; + } + + Remark 3. The above comparisons can be done in fixed point. For + example, to compare x and w=z*z chopped, it suffices to compare + x1 and w1 (the trailing parts of x and w), regarding them as + two's complement integers. + + ...Is z an exact square root? + To determine whether z is an exact square root of x, let z1 be the + trailing part of z, and also let x0 and x1 be the leading and + trailing parts of x. + + If ((z1&0x03ffffff)!=0) ... not exact if trailing 26 bits of z!=0 + I := 1; ... Raise Inexact flag: z is not exact + else { + j := 1 - [(x0>>20)&1] ... j = logb(x) mod 2 + k := z1 >> 26; ... get z's 25-th and 26-th + fraction bits + I := i or (k&j) or ((k&(j+j+1))!=(x1&3)); + } + R:= r ... restore rounded mode + return sqrt(x):=z. + + If multiplication is cheaper then the foregoing red tape, the + Inexact flag can be evaluated by + + I := i; + I := (z*z!=x) or I. + + Note that z*z can overwrite I; this value must be sensed if it is + True. + + Remark 4. If z*z = x exactly, then bit 25 to bit 0 of z1 must be + zero. + + -------------------- + z1: | f2 | + -------------------- + bit 31 bit 0 + + Further more, bit 27 and 26 of z1, bit 0 and 1 of x1, and the odd + or even of logb(x) have the following relations: + + ------------------------------------------------- + bit 27,26 of z1 bit 1,0 of x1 logb(x) + ------------------------------------------------- + 00 00 odd and even + 01 01 even + 10 10 odd + 10 00 even + 11 01 even + ------------------------------------------------- + + (4) Special cases (see (4) of Section A). + + */ + diff --git a/src/dom/js/fdlibm/fdlibm.h b/src/dom/js/fdlibm/fdlibm.h new file mode 100644 index 000000000..b4063f82f --- /dev/null +++ b/src/dom/js/fdlibm/fdlibm.h @@ -0,0 +1,273 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)fdlibm.h 1.5 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* Modified defines start here.. */ +#undef __LITTLE_ENDIAN + +#ifdef _WIN32 +#define huge myhuge +#define __LITTLE_ENDIAN +#endif + +#ifdef XP_OS2 +#define __LITTLE_ENDIAN +#endif + +#if defined(linux) && defined(__i386__) +#define __LITTLE_ENDIAN +#endif + +/* End here. The rest is the standard file. */ + +#ifdef __NEWVALID /* special setup for Sun test regime */ +#if defined(i386) || defined(i486) || \ + defined(intel) || defined(x86) || defined(i86pc) +#define __LITTLE_ENDIAN +#endif +#endif + +typedef union { +#ifdef __LITTLE_ENDIAN + struct { int lo, hi; } ints; +#else + struct { int hi, lo; } ints; +#endif + double d; +} fd_twoints; + +#define __HI(x) x.ints.hi +#define __LO(x) x.ints.lo + +#undef __P +#ifdef __STDC__ +#define __P(p) p +#else +#define __P(p) () +#endif + +/* + * ANSI/POSIX + */ + +extern int signgam; + +#define MAXFLOAT ((float)3.40282346638528860e+38) + +enum fdversion {fdlibm_ieee = -1, fdlibm_svid, fdlibm_xopen, fdlibm_posix}; + +#define _LIB_VERSION_TYPE enum fdversion +#define _LIB_VERSION _fdlib_version + +/* if global variable _LIB_VERSION is not desirable, one may + * change the following to be a constant by: + * #define _LIB_VERSION_TYPE const enum version + * In that case, after one initializes the value _LIB_VERSION (see + * s_lib_version.c) during compile time, it cannot be modified + * in the middle of a program + */ +extern _LIB_VERSION_TYPE _LIB_VERSION; + +#define _IEEE_ fdlibm_ieee +#define _SVID_ fdlibm_svid +#define _XOPEN_ fdlibm_xopen +#define _POSIX_ fdlibm_posix + +struct exception { + int type; + char *name; + double arg1; + double arg2; + double retval; +}; + +#define HUGE MAXFLOAT + +/* + * set X_TLOSS = pi*2**52, which is possibly defined in + * (one may replace the following line by "#include ") + */ + +#define X_TLOSS 1.41484755040568800000e+16 + +#define DOMAIN 1 +#define SING 2 +#define OVERFLOW 3 +#define UNDERFLOW 4 +#define TLOSS 5 +#define PLOSS 6 + +/* + * ANSI/POSIX + */ + +extern double fd_acos __P((double)); +extern double fd_asin __P((double)); +extern double fd_atan __P((double)); +extern double fd_atan2 __P((double, double)); +extern double fd_cos __P((double)); +extern double fd_sin __P((double)); +extern double fd_tan __P((double)); + +extern double fd_cosh __P((double)); +extern double fd_sinh __P((double)); +extern double fd_tanh __P((double)); + +extern double fd_exp __P((double)); +extern double fd_frexp __P((double, int *)); +extern double fd_ldexp __P((double, int)); +extern double fd_log __P((double)); +extern double fd_log10 __P((double)); +extern double fd_modf __P((double, double *)); + +extern double fd_pow __P((double, double)); +extern double fd_sqrt __P((double)); + +extern double fd_ceil __P((double)); +extern double fd_fabs __P((double)); +extern double fd_floor __P((double)); +extern double fd_fmod __P((double, double)); + +extern double fd_erf __P((double)); +extern double fd_erfc __P((double)); +extern double fd_gamma __P((double)); +extern double fd_hypot __P((double, double)); +extern int fd_isnan __P((double)); +extern int fd_finite __P((double)); +extern double fd_j0 __P((double)); +extern double fd_j1 __P((double)); +extern double fd_jn __P((int, double)); +extern double fd_lgamma __P((double)); +extern double fd_y0 __P((double)); +extern double fd_y1 __P((double)); +extern double fd_yn __P((int, double)); + +extern double fd_acosh __P((double)); +extern double fd_asinh __P((double)); +extern double fd_atanh __P((double)); +extern double fd_cbrt __P((double)); +extern double fd_logb __P((double)); +extern double fd_nextafter __P((double, double)); +extern double fd_remainder __P((double, double)); +#ifdef _SCALB_INT +extern double fd_scalb __P((double, int)); +#else +extern double fd_scalb __P((double, double)); +#endif + +extern int fd_matherr __P((struct exception *)); + +/* + * IEEE Test Vector + */ +extern double significand __P((double)); + +/* + * Functions callable from C, intended to support IEEE arithmetic. + */ +extern double fd_copysign __P((double, double)); +extern int fd_ilogb __P((double)); +extern double fd_rint __P((double)); +extern double fd_scalbn __P((double, int)); + +/* + * BSD math library entry points + */ +extern double fd_expm1 __P((double)); +extern double fd_log1p __P((double)); + +/* + * Reentrant version of gamma & lgamma; passes signgam back by reference + * as the second argument; user must allocate space for signgam. + */ +#ifdef _REENTRANT +extern double gamma_r __P((double, int *)); +extern double lgamma_r __P((double, int *)); +#endif /* _REENTRANT */ + +/* ieee style elementary functions */ +extern double __ieee754_sqrt __P((double)); +extern double __ieee754_acos __P((double)); +extern double __ieee754_acosh __P((double)); +extern double __ieee754_log __P((double)); +extern double __ieee754_atanh __P((double)); +extern double __ieee754_asin __P((double)); +extern double __ieee754_atan2 __P((double,double)); +extern double __ieee754_exp __P((double)); +extern double __ieee754_cosh __P((double)); +extern double __ieee754_fmod __P((double,double)); +extern double __ieee754_pow __P((double,double)); +extern double __ieee754_lgamma_r __P((double,int *)); +extern double __ieee754_gamma_r __P((double,int *)); +extern double __ieee754_lgamma __P((double)); +extern double __ieee754_gamma __P((double)); +extern double __ieee754_log10 __P((double)); +extern double __ieee754_sinh __P((double)); +extern double __ieee754_hypot __P((double,double)); +extern double __ieee754_j0 __P((double)); +extern double __ieee754_j1 __P((double)); +extern double __ieee754_y0 __P((double)); +extern double __ieee754_y1 __P((double)); +extern double __ieee754_jn __P((int,double)); +extern double __ieee754_yn __P((int,double)); +extern double __ieee754_remainder __P((double,double)); +extern int __ieee754_rem_pio2 __P((double,double*)); +#ifdef _SCALB_INT +extern double __ieee754_scalb __P((double,int)); +#else +extern double __ieee754_scalb __P((double,double)); +#endif + +/* fdlibm kernel function */ +extern double __kernel_standard __P((double,double,int,int*)); +extern double __kernel_sin __P((double,double,int)); +extern double __kernel_cos __P((double,double)); +extern double __kernel_tan __P((double,double,int)); +extern int __kernel_rem_pio2 __P((double*,double*,int,int,int,const int*)); diff --git a/src/dom/js/fdlibm/k_cos.c b/src/dom/js/fdlibm/k_cos.c new file mode 100644 index 000000000..17b505d58 --- /dev/null +++ b/src/dom/js/fdlibm/k_cos.c @@ -0,0 +1,134 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)k_cos.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * __kernel_cos( x, y ) + * kernel cos function on [-pi/4, pi/4], pi/4 ~ 0.785398164 + * Input x is assumed to be bounded by ~pi/4 in magnitude. + * Input y is the tail of x. + * + * Algorithm + * 1. Since cos(-x) = cos(x), we need only to consider positive x. + * 2. if x < 2^-27 (hx<0x3e400000 0), return 1 with inexact if x!=0. + * 3. cos(x) is approximated by a polynomial of degree 14 on + * [0,pi/4] + * 4 14 + * cos(x) ~ 1 - x*x/2 + C1*x + ... + C6*x + * where the remez error is + * + * | 2 4 6 8 10 12 14 | -58 + * |cos(x)-(1-.5*x +C1*x +C2*x +C3*x +C4*x +C5*x +C6*x )| <= 2 + * | | + * + * 4 6 8 10 12 14 + * 4. let r = C1*x +C2*x +C3*x +C4*x +C5*x +C6*x , then + * cos(x) = 1 - x*x/2 + r + * since cos(x+y) ~ cos(x) - sin(x)*y + * ~ cos(x) - x*y, + * a correction term is necessary in cos(x) and hence + * cos(x+y) = 1 - (x*x/2 - (r - x*y)) + * For better accuracy when x > 0.3, let qx = |x|/4 with + * the last 32 bits mask off, and if x > 0.78125, let qx = 0.28125. + * Then + * cos(x+y) = (1-qx) - ((x*x/2-qx) - (r-x*y)). + * Note that 1-qx and (x*x/2-qx) is EXACT here, and the + * magnitude of the latter is at least a quarter of x*x/2, + * thus, reducing the rounding error in the subtraction. + */ + +#include "fdlibm.h" + +#ifdef __STDC__ +static const double +#else +static double +#endif +one = 1.00000000000000000000e+00, /* 0x3FF00000, 0x00000000 */ +C1 = 4.16666666666666019037e-02, /* 0x3FA55555, 0x5555554C */ +C2 = -1.38888888888741095749e-03, /* 0xBF56C16C, 0x16C15177 */ +C3 = 2.48015872894767294178e-05, /* 0x3EFA01A0, 0x19CB1590 */ +C4 = -2.75573143513906633035e-07, /* 0xBE927E4F, 0x809C52AD */ +C5 = 2.08757232129817482790e-09, /* 0x3E21EE9E, 0xBDB4B1C4 */ +C6 = -1.13596475577881948265e-11; /* 0xBDA8FAE9, 0xBE8838D4 */ + +#ifdef __STDC__ + double __kernel_cos(double x, double y) +#else + double __kernel_cos(x, y) + double x,y; +#endif +{ + fd_twoints u; + double a,hz,z,r,qx; + int ix; + u.d = x; + ix = __HI(u)&0x7fffffff; /* ix = |x|'s high word*/ + if(ix<0x3e400000) { /* if x < 2**27 */ + if(((int)x)==0) return one; /* generate inexact */ + } + z = x*x; + r = z*(C1+z*(C2+z*(C3+z*(C4+z*(C5+z*C6))))); + if(ix < 0x3FD33333) /* if |x| < 0.3 */ + return one - (0.5*z - (z*r - x*y)); + else { + if(ix > 0x3fe90000) { /* x > 0.78125 */ + qx = 0.28125; + } else { + u.d = qx; + __HI(u) = ix-0x00200000; /* x/4 */ + __LO(u) = 0; + qx = u.d; + } + hz = 0.5*z-qx; + a = one-qx; + return a - (hz - (z*r-x*y)); + } +} diff --git a/src/dom/js/fdlibm/k_rem_pio2.c b/src/dom/js/fdlibm/k_rem_pio2.c new file mode 100644 index 000000000..d261e190a --- /dev/null +++ b/src/dom/js/fdlibm/k_rem_pio2.c @@ -0,0 +1,354 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)k_rem_pio2.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * __kernel_rem_pio2(x,y,e0,nx,prec,ipio2) + * double x[],y[]; int e0,nx,prec; int ipio2[]; + * + * __kernel_rem_pio2 return the last three digits of N with + * y = x - N*pi/2 + * so that |y| < pi/2. + * + * The method is to compute the integer (mod 8) and fraction parts of + * (2/pi)*x without doing the full multiplication. In general we + * skip the part of the product that are known to be a huge integer ( + * more accurately, = 0 mod 8 ). Thus the number of operations are + * independent of the exponent of the input. + * + * (2/pi) is represented by an array of 24-bit integers in ipio2[]. + * + * Input parameters: + * x[] The input value (must be positive) is broken into nx + * pieces of 24-bit integers in double precision format. + * x[i] will be the i-th 24 bit of x. The scaled exponent + * of x[0] is given in input parameter e0 (i.e., x[0]*2^e0 + * match x's up to 24 bits. + * + * Example of breaking a double positive z into x[0]+x[1]+x[2]: + * e0 = ilogb(z)-23 + * z = scalbn(z,-e0) + * for i = 0,1,2 + * x[i] = floor(z) + * z = (z-x[i])*2**24 + * + * + * y[] ouput result in an array of double precision numbers. + * The dimension of y[] is: + * 24-bit precision 1 + * 53-bit precision 2 + * 64-bit precision 2 + * 113-bit precision 3 + * The actual value is the sum of them. Thus for 113-bit + * precison, one may have to do something like: + * + * long double t,w,r_head, r_tail; + * t = (long double)y[2] + (long double)y[1]; + * w = (long double)y[0]; + * r_head = t+w; + * r_tail = w - (r_head - t); + * + * e0 The exponent of x[0] + * + * nx dimension of x[] + * + * prec an integer indicating the precision: + * 0 24 bits (single) + * 1 53 bits (double) + * 2 64 bits (extended) + * 3 113 bits (quad) + * + * ipio2[] + * integer array, contains the (24*i)-th to (24*i+23)-th + * bit of 2/pi after binary point. The corresponding + * floating value is + * + * ipio2[i] * 2^(-24(i+1)). + * + * External function: + * double scalbn(), floor(); + * + * + * Here is the description of some local variables: + * + * jk jk+1 is the initial number of terms of ipio2[] needed + * in the computation. The recommended value is 2,3,4, + * 6 for single, double, extended,and quad. + * + * jz local integer variable indicating the number of + * terms of ipio2[] used. + * + * jx nx - 1 + * + * jv index for pointing to the suitable ipio2[] for the + * computation. In general, we want + * ( 2^e0*x[0] * ipio2[jv-1]*2^(-24jv) )/8 + * is an integer. Thus + * e0-3-24*jv >= 0 or (e0-3)/24 >= jv + * Hence jv = max(0,(e0-3)/24). + * + * jp jp+1 is the number of terms in PIo2[] needed, jp = jk. + * + * q[] double array with integral value, representing the + * 24-bits chunk of the product of x and 2/pi. + * + * q0 the corresponding exponent of q[0]. Note that the + * exponent for q[i] would be q0-24*i. + * + * PIo2[] double precision array, obtained by cutting pi/2 + * into 24 bits chunks. + * + * f[] ipio2[] in floating point + * + * iq[] integer array by breaking up q[] in 24-bits chunk. + * + * fq[] final product of x*(2/pi) in fq[0],..,fq[jk] + * + * ih integer. If >0 it indicates q[] is >= 0.5, hence + * it also indicates the *sign* of the result. + * + */ + + +/* + * Constants: + * The hexadecimal values are the intended ones for the following + * constants. The decimal values may be used, provided that the + * compiler will convert from decimal to binary accurately enough + * to produce the hexadecimal values shown. + */ + +#include "fdlibm.h" + +#ifdef __STDC__ +static const int init_jk[] = {2,3,4,6}; /* initial value for jk */ +#else +static int init_jk[] = {2,3,4,6}; +#endif + +#ifdef __STDC__ +static const double PIo2[] = { +#else +static double PIo2[] = { +#endif + 1.57079625129699707031e+00, /* 0x3FF921FB, 0x40000000 */ + 7.54978941586159635335e-08, /* 0x3E74442D, 0x00000000 */ + 5.39030252995776476554e-15, /* 0x3CF84698, 0x80000000 */ + 3.28200341580791294123e-22, /* 0x3B78CC51, 0x60000000 */ + 1.27065575308067607349e-29, /* 0x39F01B83, 0x80000000 */ + 1.22933308981111328932e-36, /* 0x387A2520, 0x40000000 */ + 2.73370053816464559624e-44, /* 0x36E38222, 0x80000000 */ + 2.16741683877804819444e-51, /* 0x3569F31D, 0x00000000 */ +}; + +#ifdef __STDC__ +static const double +#else +static double +#endif +zero = 0.0, +one = 1.0, +two24 = 1.67772160000000000000e+07, /* 0x41700000, 0x00000000 */ +twon24 = 5.96046447753906250000e-08; /* 0x3E700000, 0x00000000 */ + +#ifdef __STDC__ + int __kernel_rem_pio2(double *x, double *y, int e0, int nx, int prec, const int *ipio2) +#else + int __kernel_rem_pio2(x,y,e0,nx,prec,ipio2) + double x[], y[]; int e0,nx,prec; int ipio2[]; +#endif +{ + int jz,jx,jv,jp,jk,carry,n,iq[20],i,j,k,m,q0,ih; + double z,fw,f[20],fq[20],q[20]; + + /* initialize jk*/ + jk = init_jk[prec]; + jp = jk; + + /* determine jx,jv,q0, note that 3>q0 */ + jx = nx-1; + jv = (e0-3)/24; if(jv<0) jv=0; + q0 = e0-24*(jv+1); + + /* set up f[0] to f[jx+jk] where f[jx+jk] = ipio2[jv+jk] */ + j = jv-jx; m = jx+jk; + for(i=0;i<=m;i++,j++) f[i] = (j<0)? zero : (double) ipio2[j]; + + /* compute q[0],q[1],...q[jk] */ + for (i=0;i<=jk;i++) { + for(j=0,fw=0.0;j<=jx;j++) fw += x[j]*f[jx+i-j]; q[i] = fw; + } + + jz = jk; +recompute: + /* distill q[] into iq[] reversingly */ + for(i=0,j=jz,z=q[jz];j>0;i++,j--) { + fw = (double)((int)(twon24* z)); + iq[i] = (int)(z-two24*fw); + z = q[j-1]+fw; + } + + /* compute n */ + z = fd_scalbn(z,q0); /* actual value of z */ + z -= 8.0*fd_floor(z*0.125); /* trim off integer >= 8 */ + n = (int) z; + z -= (double)n; + ih = 0; + if(q0>0) { /* need iq[jz-1] to determine n */ + i = (iq[jz-1]>>(24-q0)); n += i; + iq[jz-1] -= i<<(24-q0); + ih = iq[jz-1]>>(23-q0); + } + else if(q0==0) ih = iq[jz-1]>>23; + else if(z>=0.5) ih=2; + + if(ih>0) { /* q > 0.5 */ + n += 1; carry = 0; + for(i=0;i0) { /* rare case: chance is 1 in 12 */ + switch(q0) { + case 1: + iq[jz-1] &= 0x7fffff; break; + case 2: + iq[jz-1] &= 0x3fffff; break; + } + } + if(ih==2) { + z = one - z; + if(carry!=0) z -= fd_scalbn(one,q0); + } + } + + /* check if recomputation is needed */ + if(z==zero) { + j = 0; + for (i=jz-1;i>=jk;i--) j |= iq[i]; + if(j==0) { /* need recomputation */ + for(k=1;iq[jk-k]==0;k++); /* k = no. of terms needed */ + + for(i=jz+1;i<=jz+k;i++) { /* add q[jz+1] to q[jz+k] */ + f[jx+i] = (double) ipio2[jv+i]; + for(j=0,fw=0.0;j<=jx;j++) fw += x[j]*f[jx+i-j]; + q[i] = fw; + } + jz += k; + goto recompute; + } + } + + /* chop off zero terms */ + if(z==0.0) { + jz -= 1; q0 -= 24; + while(iq[jz]==0) { jz--; q0-=24;} + } else { /* break z into 24-bit if necessary */ + z = fd_scalbn(z,-q0); + if(z>=two24) { + fw = (double)((int)(twon24*z)); + iq[jz] = (int)(z-two24*fw); + jz += 1; q0 += 24; + iq[jz] = (int) fw; + } else iq[jz] = (int) z ; + } + + /* convert integer "bit" chunk to floating-point value */ + fw = fd_scalbn(one,q0); + for(i=jz;i>=0;i--) { + q[i] = fw*(double)iq[i]; fw*=twon24; + } + + /* compute PIo2[0,...,jp]*q[jz,...,0] */ + for(i=jz;i>=0;i--) { + for(fw=0.0,k=0;k<=jp&&k<=jz-i;k++) fw += PIo2[k]*q[i+k]; + fq[jz-i] = fw; + } + + /* compress fq[] into y[] */ + switch(prec) { + case 0: + fw = 0.0; + for (i=jz;i>=0;i--) fw += fq[i]; + y[0] = (ih==0)? fw: -fw; + break; + case 1: + case 2: + fw = 0.0; + for (i=jz;i>=0;i--) fw += fq[i]; + y[0] = (ih==0)? fw: -fw; + fw = fq[0]-fw; + for (i=1;i<=jz;i++) fw += fq[i]; + y[1] = (ih==0)? fw: -fw; + break; + case 3: /* painful */ + for (i=jz;i>0;i--) { + fw = fq[i-1]+fq[i]; + fq[i] += fq[i-1]-fw; + fq[i-1] = fw; + } + for (i=jz;i>1;i--) { + fw = fq[i-1]+fq[i]; + fq[i] += fq[i-1]-fw; + fq[i-1] = fw; + } + for (fw=0.0,i=jz;i>=2;i--) fw += fq[i]; + if(ih==0) { + y[0] = fq[0]; y[1] = fq[1]; y[2] = fw; + } else { + y[0] = -fq[0]; y[1] = -fq[1]; y[2] = -fw; + } + } + return n&7; +} diff --git a/src/dom/js/fdlibm/k_sin.c b/src/dom/js/fdlibm/k_sin.c new file mode 100644 index 000000000..d2bdabd6d --- /dev/null +++ b/src/dom/js/fdlibm/k_sin.c @@ -0,0 +1,114 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)k_sin.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* __kernel_sin( x, y, iy) + * kernel sin function on [-pi/4, pi/4], pi/4 ~ 0.7854 + * Input x is assumed to be bounded by ~pi/4 in magnitude. + * Input y is the tail of x. + * Input iy indicates whether y is 0. (if iy=0, y assume to be 0). + * + * Algorithm + * 1. Since sin(-x) = -sin(x), we need only to consider positive x. + * 2. if x < 2^-27 (hx<0x3e400000 0), return x with inexact if x!=0. + * 3. sin(x) is approximated by a polynomial of degree 13 on + * [0,pi/4] + * 3 13 + * sin(x) ~ x + S1*x + ... + S6*x + * where + * + * |sin(x) 2 4 6 8 10 12 | -58 + * |----- - (1+S1*x +S2*x +S3*x +S4*x +S5*x +S6*x )| <= 2 + * | x | + * + * 4. sin(x+y) = sin(x) + sin'(x')*y + * ~ sin(x) + (1-x*x/2)*y + * For better accuracy, let + * 3 2 2 2 2 + * r = x *(S2+x *(S3+x *(S4+x *(S5+x *S6)))) + * then 3 2 + * sin(x) = x + (S1*x + (x *(r-y/2)+y)) + */ + +#include "fdlibm.h" + +#ifdef __STDC__ +static const double +#else +static double +#endif +half = 5.00000000000000000000e-01, /* 0x3FE00000, 0x00000000 */ +S1 = -1.66666666666666324348e-01, /* 0xBFC55555, 0x55555549 */ +S2 = 8.33333333332248946124e-03, /* 0x3F811111, 0x1110F8A6 */ +S3 = -1.98412698298579493134e-04, /* 0xBF2A01A0, 0x19C161D5 */ +S4 = 2.75573137070700676789e-06, /* 0x3EC71DE3, 0x57B1FE7D */ +S5 = -2.50507602534068634195e-08, /* 0xBE5AE5E6, 0x8A2B9CEB */ +S6 = 1.58969099521155010221e-10; /* 0x3DE5D93A, 0x5ACFD57C */ + +#ifdef __STDC__ + double __kernel_sin(double x, double y, int iy) +#else + double __kernel_sin(x, y, iy) + double x,y; int iy; /* iy=0 if y is zero */ +#endif +{ + fd_twoints u; + double z,r,v; + int ix; + u.d = x; + ix = __HI(u)&0x7fffffff; /* high word of x */ + if(ix<0x3e400000) /* |x| < 2**-27 */ + {if((int)x==0) return x;} /* generate inexact */ + z = x*x; + v = z*x; + r = S2+z*(S3+z*(S4+z*(S5+z*S6))); + if(iy==0) return x+v*(S1+z*r); + else return x-((z*(half*y-v*r)-y)-v*S1); +} diff --git a/src/dom/js/fdlibm/k_standard.c b/src/dom/js/fdlibm/k_standard.c new file mode 100644 index 000000000..720109c9d --- /dev/null +++ b/src/dom/js/fdlibm/k_standard.c @@ -0,0 +1,785 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)k_standard.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + * + */ + +#include "fdlibm.h" + +/* XXX ugly hack to get msvc to link without error. */ +#if _LIB_VERSION == _IEEE_ && !(defined(DARWIN) || defined(XP_MACOSX)) + int errno; +# define EDOM 0 +# define ERANGE 0 +#else +# include +#endif + + +#ifndef _USE_WRITE +#include /* fputs(), stderr */ +#define WRITE2(u,v) fputs(u, stderr) +#else /* !defined(_USE_WRITE) */ +#include /* write */ +#define WRITE2(u,v) write(2, u, v) +#undef fflush +#endif /* !defined(_USE_WRITE) */ + +static double zero = 0.0; /* used as const */ + +/* + * Standard conformance (non-IEEE) on exception cases. + * Mapping: + * 1 -- acos(|x|>1) + * 2 -- asin(|x|>1) + * 3 -- atan2(+-0,+-0) + * 4 -- hypot overflow + * 5 -- cosh overflow + * 6 -- exp overflow + * 7 -- exp underflow + * 8 -- y0(0) + * 9 -- y0(-ve) + * 10-- y1(0) + * 11-- y1(-ve) + * 12-- yn(0) + * 13-- yn(-ve) + * 14-- lgamma(finite) overflow + * 15-- lgamma(-integer) + * 16-- log(0) + * 17-- log(x<0) + * 18-- log10(0) + * 19-- log10(x<0) + * 20-- pow(0.0,0.0) + * 21-- pow(x,y) overflow + * 22-- pow(x,y) underflow + * 23-- pow(0,negative) + * 24-- pow(neg,non-integral) + * 25-- sinh(finite) overflow + * 26-- sqrt(negative) + * 27-- fmod(x,0) + * 28-- remainder(x,0) + * 29-- acosh(x<1) + * 30-- atanh(|x|>1) + * 31-- atanh(|x|=1) + * 32-- scalb overflow + * 33-- scalb underflow + * 34-- j0(|x|>X_TLOSS) + * 35-- y0(x>X_TLOSS) + * 36-- j1(|x|>X_TLOSS) + * 37-- y1(x>X_TLOSS) + * 38-- jn(|x|>X_TLOSS, n) + * 39-- yn(x>X_TLOSS, n) + * 40-- gamma(finite) overflow + * 41-- gamma(-integer) + * 42-- pow(NaN,0.0) + */ + + +#ifdef __STDC__ + double __kernel_standard(double x, double y, int type, int *err) +#else + double __kernel_standard(x,y,type, err) + double x,y; int type;int *err; +#endif +{ + struct exception exc; +#ifndef HUGE_VAL /* this is the only routine that uses HUGE_VAL */ +#define HUGE_VAL inf + double inf = 0.0; + fd_twoints u; + + u.d = inf; + __HI(u) = 0x7ff00000; /* set inf to infinite */ + inf = u.d; +#endif + + *err = 0; + +#ifdef _USE_WRITE + (void) fflush(stdout); +#endif + exc.arg1 = x; + exc.arg2 = y; + switch(type) { + case 1: + /* acos(|x|>1) */ + exc.type = DOMAIN; + exc.name = "acos"; + exc.retval = zero; + if (_LIB_VERSION == _POSIX_) + *err = EDOM; + else if (!fd_matherr(&exc)) { + if(_LIB_VERSION == _SVID_) { + (void) WRITE2("acos: DOMAIN error\n", 19); + } + *err = EDOM; + } + break; + case 2: + /* asin(|x|>1) */ + exc.type = DOMAIN; + exc.name = "asin"; + exc.retval = zero; + if(_LIB_VERSION == _POSIX_) + *err = EDOM; + else if (!fd_matherr(&exc)) { + if(_LIB_VERSION == _SVID_) { + (void) WRITE2("asin: DOMAIN error\n", 19); + } + *err = EDOM; + } + break; + case 3: + /* atan2(+-0,+-0) */ + exc.arg1 = y; + exc.arg2 = x; + exc.type = DOMAIN; + exc.name = "atan2"; + exc.retval = zero; + if(_LIB_VERSION == _POSIX_) + *err = EDOM; + else if (!fd_matherr(&exc)) { + if(_LIB_VERSION == _SVID_) { + (void) WRITE2("atan2: DOMAIN error\n", 20); + } + *err = EDOM; + } + break; + case 4: + /* hypot(finite,finite) overflow */ + exc.type = OVERFLOW; + exc.name = "hypot"; + if (_LIB_VERSION == _SVID_) + exc.retval = HUGE; + else + exc.retval = HUGE_VAL; + if (_LIB_VERSION == _POSIX_) + *err = ERANGE; + else if (!fd_matherr(&exc)) { + *err = ERANGE; + } + break; + case 5: + /* cosh(finite) overflow */ + exc.type = OVERFLOW; + exc.name = "cosh"; + if (_LIB_VERSION == _SVID_) + exc.retval = HUGE; + else + exc.retval = HUGE_VAL; + if (_LIB_VERSION == _POSIX_) + *err = ERANGE; + else if (!fd_matherr(&exc)) { + *err = ERANGE; + } + break; + case 6: + /* exp(finite) overflow */ + exc.type = OVERFLOW; + exc.name = "exp"; + if (_LIB_VERSION == _SVID_) + exc.retval = HUGE; + else + exc.retval = HUGE_VAL; + if (_LIB_VERSION == _POSIX_) + *err = ERANGE; + else if (!fd_matherr(&exc)) { + *err = ERANGE; + } + break; + case 7: + /* exp(finite) underflow */ + exc.type = UNDERFLOW; + exc.name = "exp"; + exc.retval = zero; + if (_LIB_VERSION == _POSIX_) + *err = ERANGE; + else if (!fd_matherr(&exc)) { + *err = ERANGE; + } + break; + case 8: + /* y0(0) = -inf */ + exc.type = DOMAIN; /* should be SING for IEEE */ + exc.name = "y0"; + if (_LIB_VERSION == _SVID_) + exc.retval = -HUGE; + else + exc.retval = -HUGE_VAL; + if (_LIB_VERSION == _POSIX_) + *err = EDOM; + else if (!fd_matherr(&exc)) { + if (_LIB_VERSION == _SVID_) { + (void) WRITE2("y0: DOMAIN error\n", 17); + } + *err = EDOM; + } + break; + case 9: + /* y0(x<0) = NaN */ + exc.type = DOMAIN; + exc.name = "y0"; + if (_LIB_VERSION == _SVID_) + exc.retval = -HUGE; + else + exc.retval = -HUGE_VAL; + if (_LIB_VERSION == _POSIX_) + *err = EDOM; + else if (!fd_matherr(&exc)) { + if (_LIB_VERSION == _SVID_) { + (void) WRITE2("y0: DOMAIN error\n", 17); + } + *err = EDOM; + } + break; + case 10: + /* y1(0) = -inf */ + exc.type = DOMAIN; /* should be SING for IEEE */ + exc.name = "y1"; + if (_LIB_VERSION == _SVID_) + exc.retval = -HUGE; + else + exc.retval = -HUGE_VAL; + if (_LIB_VERSION == _POSIX_) + *err = EDOM; + else if (!fd_matherr(&exc)) { + if (_LIB_VERSION == _SVID_) { + (void) WRITE2("y1: DOMAIN error\n", 17); + } + *err = EDOM; + } + break; + case 11: + /* y1(x<0) = NaN */ + exc.type = DOMAIN; + exc.name = "y1"; + if (_LIB_VERSION == _SVID_) + exc.retval = -HUGE; + else + exc.retval = -HUGE_VAL; + if (_LIB_VERSION == _POSIX_) + *err = EDOM; + else if (!fd_matherr(&exc)) { + if (_LIB_VERSION == _SVID_) { + (void) WRITE2("y1: DOMAIN error\n", 17); + } + *err = EDOM; + } + break; + case 12: + /* yn(n,0) = -inf */ + exc.type = DOMAIN; /* should be SING for IEEE */ + exc.name = "yn"; + if (_LIB_VERSION == _SVID_) + exc.retval = -HUGE; + else + exc.retval = -HUGE_VAL; + if (_LIB_VERSION == _POSIX_) + *err = EDOM; + else if (!fd_matherr(&exc)) { + if (_LIB_VERSION == _SVID_) { + (void) WRITE2("yn: DOMAIN error\n", 17); + } + *err = EDOM; + } + break; + case 13: + /* yn(x<0) = NaN */ + exc.type = DOMAIN; + exc.name = "yn"; + if (_LIB_VERSION == _SVID_) + exc.retval = -HUGE; + else + exc.retval = -HUGE_VAL; + if (_LIB_VERSION == _POSIX_) + *err = EDOM; + else if (!fd_matherr(&exc)) { + if (_LIB_VERSION == _SVID_) { + (void) WRITE2("yn: DOMAIN error\n", 17); + } + *err = EDOM; + } + break; + case 14: + /* lgamma(finite) overflow */ + exc.type = OVERFLOW; + exc.name = "lgamma"; + if (_LIB_VERSION == _SVID_) + exc.retval = HUGE; + else + exc.retval = HUGE_VAL; + if (_LIB_VERSION == _POSIX_) + *err = ERANGE; + else if (!fd_matherr(&exc)) { + *err = ERANGE; + } + break; + case 15: + /* lgamma(-integer) or lgamma(0) */ + exc.type = SING; + exc.name = "lgamma"; + if (_LIB_VERSION == _SVID_) + exc.retval = HUGE; + else + exc.retval = HUGE_VAL; + if (_LIB_VERSION == _POSIX_) + *err = EDOM; + else if (!fd_matherr(&exc)) { + if (_LIB_VERSION == _SVID_) { + (void) WRITE2("lgamma: SING error\n", 19); + } + *err = EDOM; + } + break; + case 16: + /* log(0) */ + exc.type = SING; + exc.name = "log"; + if (_LIB_VERSION == _SVID_) + exc.retval = -HUGE; + else + exc.retval = -HUGE_VAL; + if (_LIB_VERSION == _POSIX_) + *err = ERANGE; + else if (!fd_matherr(&exc)) { + if (_LIB_VERSION == _SVID_) { + (void) WRITE2("log: SING error\n", 16); + } + *err = EDOM; + } + break; + case 17: + /* log(x<0) */ + exc.type = DOMAIN; + exc.name = "log"; + if (_LIB_VERSION == _SVID_) + exc.retval = -HUGE; + else + exc.retval = -HUGE_VAL; + if (_LIB_VERSION == _POSIX_) + *err = EDOM; + else if (!fd_matherr(&exc)) { + if (_LIB_VERSION == _SVID_) { + (void) WRITE2("log: DOMAIN error\n", 18); + } + *err = EDOM; + } + break; + case 18: + /* log10(0) */ + exc.type = SING; + exc.name = "log10"; + if (_LIB_VERSION == _SVID_) + exc.retval = -HUGE; + else + exc.retval = -HUGE_VAL; + if (_LIB_VERSION == _POSIX_) + *err = ERANGE; + else if (!fd_matherr(&exc)) { + if (_LIB_VERSION == _SVID_) { + (void) WRITE2("log10: SING error\n", 18); + } + *err = EDOM; + } + break; + case 19: + /* log10(x<0) */ + exc.type = DOMAIN; + exc.name = "log10"; + if (_LIB_VERSION == _SVID_) + exc.retval = -HUGE; + else + exc.retval = -HUGE_VAL; + if (_LIB_VERSION == _POSIX_) + *err = EDOM; + else if (!fd_matherr(&exc)) { + if (_LIB_VERSION == _SVID_) { + (void) WRITE2("log10: DOMAIN error\n", 20); + } + *err = EDOM; + } + break; + case 20: + /* pow(0.0,0.0) */ + /* error only if _LIB_VERSION == _SVID_ */ + exc.type = DOMAIN; + exc.name = "pow"; + exc.retval = zero; + if (_LIB_VERSION != _SVID_) exc.retval = 1.0; + else if (!fd_matherr(&exc)) { + (void) WRITE2("pow(0,0): DOMAIN error\n", 23); + *err = EDOM; + } + break; + case 21: + /* pow(x,y) overflow */ + exc.type = OVERFLOW; + exc.name = "pow"; + if (_LIB_VERSION == _SVID_) { + exc.retval = HUGE; + y *= 0.5; + if(xzero) ? HUGE : -HUGE); + else + exc.retval = ( (x>zero) ? HUGE_VAL : -HUGE_VAL); + if (_LIB_VERSION == _POSIX_) + *err = ERANGE; + else if (!fd_matherr(&exc)) { + *err = ERANGE; + } + break; + case 26: + /* sqrt(x<0) */ + exc.type = DOMAIN; + exc.name = "sqrt"; + if (_LIB_VERSION == _SVID_) + exc.retval = zero; + else + exc.retval = zero/zero; + if (_LIB_VERSION == _POSIX_) + *err = EDOM; + else if (!fd_matherr(&exc)) { + if (_LIB_VERSION == _SVID_) { + (void) WRITE2("sqrt: DOMAIN error\n", 19); + } + *err = EDOM; + } + break; + case 27: + /* fmod(x,0) */ + exc.type = DOMAIN; + exc.name = "fmod"; + if (_LIB_VERSION == _SVID_) + exc.retval = x; + else + exc.retval = zero/zero; + if (_LIB_VERSION == _POSIX_) + *err = EDOM; + else if (!fd_matherr(&exc)) { + if (_LIB_VERSION == _SVID_) { + (void) WRITE2("fmod: DOMAIN error\n", 20); + } + *err = EDOM; + } + break; + case 28: + /* remainder(x,0) */ + exc.type = DOMAIN; + exc.name = "remainder"; + exc.retval = zero/zero; + if (_LIB_VERSION == _POSIX_) + *err = EDOM; + else if (!fd_matherr(&exc)) { + if (_LIB_VERSION == _SVID_) { + (void) WRITE2("remainder: DOMAIN error\n", 24); + } + *err = EDOM; + } + break; + case 29: + /* acosh(x<1) */ + exc.type = DOMAIN; + exc.name = "acosh"; + exc.retval = zero/zero; + if (_LIB_VERSION == _POSIX_) + *err = EDOM; + else if (!fd_matherr(&exc)) { + if (_LIB_VERSION == _SVID_) { + (void) WRITE2("acosh: DOMAIN error\n", 20); + } + *err = EDOM; + } + break; + case 30: + /* atanh(|x|>1) */ + exc.type = DOMAIN; + exc.name = "atanh"; + exc.retval = zero/zero; + if (_LIB_VERSION == _POSIX_) + *err = EDOM; + else if (!fd_matherr(&exc)) { + if (_LIB_VERSION == _SVID_) { + (void) WRITE2("atanh: DOMAIN error\n", 20); + } + *err = EDOM; + } + break; + case 31: + /* atanh(|x|=1) */ + exc.type = SING; + exc.name = "atanh"; + exc.retval = x/zero; /* sign(x)*inf */ + if (_LIB_VERSION == _POSIX_) + *err = EDOM; + else if (!fd_matherr(&exc)) { + if (_LIB_VERSION == _SVID_) { + (void) WRITE2("atanh: SING error\n", 18); + } + *err = EDOM; + } + break; + case 32: + /* scalb overflow; SVID also returns +-HUGE_VAL */ + exc.type = OVERFLOW; + exc.name = "scalb"; + exc.retval = x > zero ? HUGE_VAL : -HUGE_VAL; + if (_LIB_VERSION == _POSIX_) + *err = ERANGE; + else if (!fd_matherr(&exc)) { + *err = ERANGE; + } + break; + case 33: + /* scalb underflow */ + exc.type = UNDERFLOW; + exc.name = "scalb"; + exc.retval = fd_copysign(zero,x); + if (_LIB_VERSION == _POSIX_) + *err = ERANGE; + else if (!fd_matherr(&exc)) { + *err = ERANGE; + } + break; + case 34: + /* j0(|x|>X_TLOSS) */ + exc.type = TLOSS; + exc.name = "j0"; + exc.retval = zero; + if (_LIB_VERSION == _POSIX_) + *err = ERANGE; + else if (!fd_matherr(&exc)) { + if (_LIB_VERSION == _SVID_) { + (void) WRITE2(exc.name, 2); + (void) WRITE2(": TLOSS error\n", 14); + } + *err = ERANGE; + } + break; + case 35: + /* y0(x>X_TLOSS) */ + exc.type = TLOSS; + exc.name = "y0"; + exc.retval = zero; + if (_LIB_VERSION == _POSIX_) + *err = ERANGE; + else if (!fd_matherr(&exc)) { + if (_LIB_VERSION == _SVID_) { + (void) WRITE2(exc.name, 2); + (void) WRITE2(": TLOSS error\n", 14); + } + *err = ERANGE; + } + break; + case 36: + /* j1(|x|>X_TLOSS) */ + exc.type = TLOSS; + exc.name = "j1"; + exc.retval = zero; + if (_LIB_VERSION == _POSIX_) + *err = ERANGE; + else if (!fd_matherr(&exc)) { + if (_LIB_VERSION == _SVID_) { + (void) WRITE2(exc.name, 2); + (void) WRITE2(": TLOSS error\n", 14); + } + *err = ERANGE; + } + break; + case 37: + /* y1(x>X_TLOSS) */ + exc.type = TLOSS; + exc.name = "y1"; + exc.retval = zero; + if (_LIB_VERSION == _POSIX_) + *err = ERANGE; + else if (!fd_matherr(&exc)) { + if (_LIB_VERSION == _SVID_) { + (void) WRITE2(exc.name, 2); + (void) WRITE2(": TLOSS error\n", 14); + } + *err = ERANGE; + } + break; + case 38: + /* jn(|x|>X_TLOSS) */ + exc.type = TLOSS; + exc.name = "jn"; + exc.retval = zero; + if (_LIB_VERSION == _POSIX_) + *err = ERANGE; + else if (!fd_matherr(&exc)) { + if (_LIB_VERSION == _SVID_) { + (void) WRITE2(exc.name, 2); + (void) WRITE2(": TLOSS error\n", 14); + } + *err = ERANGE; + } + break; + case 39: + /* yn(x>X_TLOSS) */ + exc.type = TLOSS; + exc.name = "yn"; + exc.retval = zero; + if (_LIB_VERSION == _POSIX_) + *err = ERANGE; + else if (!fd_matherr(&exc)) { + if (_LIB_VERSION == _SVID_) { + (void) WRITE2(exc.name, 2); + (void) WRITE2(": TLOSS error\n", 14); + } + *err = ERANGE; + } + break; + case 40: + /* gamma(finite) overflow */ + exc.type = OVERFLOW; + exc.name = "gamma"; + if (_LIB_VERSION == _SVID_) + exc.retval = HUGE; + else + exc.retval = HUGE_VAL; + if (_LIB_VERSION == _POSIX_) + *err = ERANGE; + else if (!fd_matherr(&exc)) { + *err = ERANGE; + } + break; + case 41: + /* gamma(-integer) or gamma(0) */ + exc.type = SING; + exc.name = "gamma"; + if (_LIB_VERSION == _SVID_) + exc.retval = HUGE; + else + exc.retval = HUGE_VAL; + if (_LIB_VERSION == _POSIX_) + *err = EDOM; + else if (!fd_matherr(&exc)) { + if (_LIB_VERSION == _SVID_) { + (void) WRITE2("gamma: SING error\n", 18); + } + *err = EDOM; + } + break; + case 42: + /* pow(NaN,0.0) */ + /* error only if _LIB_VERSION == _SVID_ & _XOPEN_ */ + exc.type = DOMAIN; + exc.name = "pow"; + exc.retval = x; + if (_LIB_VERSION == _IEEE_ || + _LIB_VERSION == _POSIX_) exc.retval = 1.0; + else if (!fd_matherr(&exc)) { + *err = EDOM; + } + break; + } + return exc.retval; +} diff --git a/src/dom/js/fdlibm/k_tan.c b/src/dom/js/fdlibm/k_tan.c new file mode 100644 index 000000000..1e7681b88 --- /dev/null +++ b/src/dom/js/fdlibm/k_tan.c @@ -0,0 +1,170 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)k_tan.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* __kernel_tan( x, y, k ) + * kernel tan function on [-pi/4, pi/4], pi/4 ~ 0.7854 + * Input x is assumed to be bounded by ~pi/4 in magnitude. + * Input y is the tail of x. + * Input k indicates whether tan (if k=1) or + * -1/tan (if k= -1) is returned. + * + * Algorithm + * 1. Since tan(-x) = -tan(x), we need only to consider positive x. + * 2. if x < 2^-28 (hx<0x3e300000 0), return x with inexact if x!=0. + * 3. tan(x) is approximated by a odd polynomial of degree 27 on + * [0,0.67434] + * 3 27 + * tan(x) ~ x + T1*x + ... + T13*x + * where + * + * |tan(x) 2 4 26 | -59.2 + * |----- - (1+T1*x +T2*x +.... +T13*x )| <= 2 + * | x | + * + * Note: tan(x+y) = tan(x) + tan'(x)*y + * ~ tan(x) + (1+x*x)*y + * Therefore, for better accuracy in computing tan(x+y), let + * 3 2 2 2 2 + * r = x *(T2+x *(T3+x *(...+x *(T12+x *T13)))) + * then + * 3 2 + * tan(x+y) = x + (T1*x + (x *(r+y)+y)) + * + * 4. For x in [0.67434,pi/4], let y = pi/4 - x, then + * tan(x) = tan(pi/4-y) = (1-tan(y))/(1+tan(y)) + * = 1 - 2*(tan(y) - (tan(y)^2)/(1+tan(y))) + */ + +#include "fdlibm.h" +#ifdef __STDC__ +static const double +#else +static double +#endif +one = 1.00000000000000000000e+00, /* 0x3FF00000, 0x00000000 */ +pio4 = 7.85398163397448278999e-01, /* 0x3FE921FB, 0x54442D18 */ +pio4lo= 3.06161699786838301793e-17, /* 0x3C81A626, 0x33145C07 */ +T[] = { + 3.33333333333334091986e-01, /* 0x3FD55555, 0x55555563 */ + 1.33333333333201242699e-01, /* 0x3FC11111, 0x1110FE7A */ + 5.39682539762260521377e-02, /* 0x3FABA1BA, 0x1BB341FE */ + 2.18694882948595424599e-02, /* 0x3F9664F4, 0x8406D637 */ + 8.86323982359930005737e-03, /* 0x3F8226E3, 0xE96E8493 */ + 3.59207910759131235356e-03, /* 0x3F6D6D22, 0xC9560328 */ + 1.45620945432529025516e-03, /* 0x3F57DBC8, 0xFEE08315 */ + 5.88041240820264096874e-04, /* 0x3F4344D8, 0xF2F26501 */ + 2.46463134818469906812e-04, /* 0x3F3026F7, 0x1A8D1068 */ + 7.81794442939557092300e-05, /* 0x3F147E88, 0xA03792A6 */ + 7.14072491382608190305e-05, /* 0x3F12B80F, 0x32F0A7E9 */ + -1.85586374855275456654e-05, /* 0xBEF375CB, 0xDB605373 */ + 2.59073051863633712884e-05, /* 0x3EFB2A70, 0x74BF7AD4 */ +}; + +#ifdef __STDC__ + double __kernel_tan(double x, double y, int iy) +#else + double __kernel_tan(x, y, iy) + double x,y; int iy; +#endif +{ + fd_twoints u; + double z,r,v,w,s; + int ix,hx; + u.d = x; + hx = __HI(u); /* high word of x */ + ix = hx&0x7fffffff; /* high word of |x| */ + if(ix<0x3e300000) /* x < 2**-28 */ + {if((int)x==0) { /* generate inexact */ + u.d =x; + if(((ix|__LO(u))|(iy+1))==0) return one/fd_fabs(x); + else return (iy==1)? x: -one/x; + } + } + if(ix>=0x3FE59428) { /* |x|>=0.6744 */ + if(hx<0) {x = -x; y = -y;} + z = pio4-x; + w = pio4lo-y; + x = z+w; y = 0.0; + } + z = x*x; + w = z*z; + /* Break x^5*(T[1]+x^2*T[2]+...) into + * x^5(T[1]+x^4*T[3]+...+x^20*T[11]) + + * x^5(x^2*(T[2]+x^4*T[4]+...+x^22*[T12])) + */ + r = T[1]+w*(T[3]+w*(T[5]+w*(T[7]+w*(T[9]+w*T[11])))); + v = z*(T[2]+w*(T[4]+w*(T[6]+w*(T[8]+w*(T[10]+w*T[12]))))); + s = z*x; + r = y + z*(s*(r+v)+y); + r += T[0]*s; + w = x+r; + if(ix>=0x3FE59428) { + v = (double)iy; + return (double)(1-((hx>>30)&2))*(v-2.0*(x-(w*w/(w+v)-r))); + } + if(iy==1) return w; + else { /* if allow error up to 2 ulp, + simply return -1.0/(x+r) here */ + /* compute -1.0/(x+r) accurately */ + double a,t; + z = w; + u.d = z; + __LO(u) = 0; + z = u.d; + v = r-(z - x); /* z+v = r+x */ + t = a = -1.0/w; /* a = -1.0/w */ + u.d = t; + __LO(u) = 0; + t = u.d; + s = 1.0+t*z; + return t+a*(s+t*v); + } +} diff --git a/src/dom/js/fdlibm/s_asinh.c b/src/dom/js/fdlibm/s_asinh.c new file mode 100644 index 000000000..fdf70a91c --- /dev/null +++ b/src/dom/js/fdlibm/s_asinh.c @@ -0,0 +1,101 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)s_asinh.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* asinh(x) + * Method : + * Based on + * asinh(x) = sign(x) * log [ |x| + sqrt(x*x+1) ] + * we have + * asinh(x) := x if 1+x*x=1, + * := sign(x)*(log(x)+ln2)) for large |x|, else + * := sign(x)*log(2|x|+1/(|x|+sqrt(x*x+1))) if|x|>2, else + * := sign(x)*log1p(|x| + x^2/(1 + sqrt(1+x^2))) + */ + +#include "fdlibm.h" + +#ifdef __STDC__ +static const double +#else +static double +#endif +one = 1.00000000000000000000e+00, /* 0x3FF00000, 0x00000000 */ +ln2 = 6.93147180559945286227e-01, /* 0x3FE62E42, 0xFEFA39EF */ +really_big= 1.00000000000000000000e+300; + +#ifdef __STDC__ + double fd_asinh(double x) +#else + double fd_asinh(x) + double x; +#endif +{ + fd_twoints u; + double t,w; + int hx,ix; + u.d = x; + hx = __HI(u); + ix = hx&0x7fffffff; + if(ix>=0x7ff00000) return x+x; /* x is inf or NaN */ + if(ix< 0x3e300000) { /* |x|<2**-28 */ + if(really_big+x>one) return x; /* return x inexact except 0 */ + } + if(ix>0x41b00000) { /* |x| > 2**28 */ + w = __ieee754_log(fd_fabs(x))+ln2; + } else if (ix>0x40000000) { /* 2**28 > |x| > 2.0 */ + t = fd_fabs(x); + w = __ieee754_log(2.0*t+one/(fd_sqrt(x*x+one)+t)); + } else { /* 2.0 > |x| > 2**-28 */ + t = x*x; + w =fd_log1p(fd_fabs(x)+t/(one+fd_sqrt(one+t))); + } + if(hx>0) return w; else return -w; +} diff --git a/src/dom/js/fdlibm/s_atan.c b/src/dom/js/fdlibm/s_atan.c new file mode 100644 index 000000000..99a00c686 --- /dev/null +++ b/src/dom/js/fdlibm/s_atan.c @@ -0,0 +1,175 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)s_atan.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + * + */ + +/* atan(x) + * Method + * 1. Reduce x to positive by atan(x) = -atan(-x). + * 2. According to the integer k=4t+0.25 chopped, t=x, the argument + * is further reduced to one of the following intervals and the + * arctangent of t is evaluated by the corresponding formula: + * + * [0,7/16] atan(x) = t-t^3*(a1+t^2*(a2+...(a10+t^2*a11)...) + * [7/16,11/16] atan(x) = atan(1/2) + atan( (t-0.5)/(1+t/2) ) + * [11/16.19/16] atan(x) = atan( 1 ) + atan( (t-1)/(1+t) ) + * [19/16,39/16] atan(x) = atan(3/2) + atan( (t-1.5)/(1+1.5t) ) + * [39/16,INF] atan(x) = atan(INF) + atan( -1/t ) + * + * Constants: + * The hexadecimal values are the intended ones for the following + * constants. The decimal values may be used, provided that the + * compiler will convert from decimal to binary accurately enough + * to produce the hexadecimal values shown. + */ + +#include "fdlibm.h" + +#ifdef __STDC__ +static const double atanhi[] = { +#else +static double atanhi[] = { +#endif + 4.63647609000806093515e-01, /* atan(0.5)hi 0x3FDDAC67, 0x0561BB4F */ + 7.85398163397448278999e-01, /* atan(1.0)hi 0x3FE921FB, 0x54442D18 */ + 9.82793723247329054082e-01, /* atan(1.5)hi 0x3FEF730B, 0xD281F69B */ + 1.57079632679489655800e+00, /* atan(inf)hi 0x3FF921FB, 0x54442D18 */ +}; + +#ifdef __STDC__ +static const double atanlo[] = { +#else +static double atanlo[] = { +#endif + 2.26987774529616870924e-17, /* atan(0.5)lo 0x3C7A2B7F, 0x222F65E2 */ + 3.06161699786838301793e-17, /* atan(1.0)lo 0x3C81A626, 0x33145C07 */ + 1.39033110312309984516e-17, /* atan(1.5)lo 0x3C700788, 0x7AF0CBBD */ + 6.12323399573676603587e-17, /* atan(inf)lo 0x3C91A626, 0x33145C07 */ +}; + +#ifdef __STDC__ +static const double aT[] = { +#else +static double aT[] = { +#endif + 3.33333333333329318027e-01, /* 0x3FD55555, 0x5555550D */ + -1.99999999998764832476e-01, /* 0xBFC99999, 0x9998EBC4 */ + 1.42857142725034663711e-01, /* 0x3FC24924, 0x920083FF */ + -1.11111104054623557880e-01, /* 0xBFBC71C6, 0xFE231671 */ + 9.09088713343650656196e-02, /* 0x3FB745CD, 0xC54C206E */ + -7.69187620504482999495e-02, /* 0xBFB3B0F2, 0xAF749A6D */ + 6.66107313738753120669e-02, /* 0x3FB10D66, 0xA0D03D51 */ + -5.83357013379057348645e-02, /* 0xBFADDE2D, 0x52DEFD9A */ + 4.97687799461593236017e-02, /* 0x3FA97B4B, 0x24760DEB */ + -3.65315727442169155270e-02, /* 0xBFA2B444, 0x2C6A6C2F */ + 1.62858201153657823623e-02, /* 0x3F90AD3A, 0xE322DA11 */ +}; + +#ifdef __STDC__ + static const double +#else + static double +#endif +one = 1.0, +really_big = 1.0e300; + +#ifdef __STDC__ + double fd_atan(double x) +#else + double fd_atan(x) + double x; +#endif +{ + fd_twoints u; + double w,s1,s2,z; + int ix,hx,id; + + u.d = x; + hx = __HI(u); + ix = hx&0x7fffffff; + if(ix>=0x44100000) { /* if |x| >= 2^66 */ + u.d = x; + if(ix>0x7ff00000|| + (ix==0x7ff00000&&(__LO(u)!=0))) + return x+x; /* NaN */ + if(hx>0) return atanhi[3]+atanlo[3]; + else return -atanhi[3]-atanlo[3]; + } if (ix < 0x3fdc0000) { /* |x| < 0.4375 */ + if (ix < 0x3e200000) { /* |x| < 2^-29 */ + if(really_big+x>one) return x; /* raise inexact */ + } + id = -1; + } else { + x = fd_fabs(x); + if (ix < 0x3ff30000) { /* |x| < 1.1875 */ + if (ix < 0x3fe60000) { /* 7/16 <=|x|<11/16 */ + id = 0; x = (2.0*x-one)/(2.0+x); + } else { /* 11/16<=|x|< 19/16 */ + id = 1; x = (x-one)/(x+one); + } + } else { + if (ix < 0x40038000) { /* |x| < 2.4375 */ + id = 2; x = (x-1.5)/(one+1.5*x); + } else { /* 2.4375 <= |x| < 2^66 */ + id = 3; x = -1.0/x; + } + }} + /* end of argument reduction */ + z = x*x; + w = z*z; + /* break sum from i=0 to 10 aT[i]z**(i+1) into odd and even poly */ + s1 = z*(aT[0]+w*(aT[2]+w*(aT[4]+w*(aT[6]+w*(aT[8]+w*aT[10]))))); + s2 = w*(aT[1]+w*(aT[3]+w*(aT[5]+w*(aT[7]+w*aT[9])))); + if (id<0) return x - x*(s1+s2); + else { + z = atanhi[id] - ((x*(s1+s2) - atanlo[id]) - x); + return (hx<0)? -z:z; + } +} diff --git a/src/dom/js/fdlibm/s_cbrt.c b/src/dom/js/fdlibm/s_cbrt.c new file mode 100644 index 000000000..4aed19b1b --- /dev/null +++ b/src/dom/js/fdlibm/s_cbrt.c @@ -0,0 +1,133 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)s_cbrt.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + * + */ + +#include "fdlibm.h" + +/* cbrt(x) + * Return cube root of x + */ +#ifdef __STDC__ +static const unsigned +#else +static unsigned +#endif + B1 = 715094163, /* B1 = (682-0.03306235651)*2**20 */ + B2 = 696219795; /* B2 = (664-0.03306235651)*2**20 */ + +#ifdef __STDC__ +static const double +#else +static double +#endif +C = 5.42857142857142815906e-01, /* 19/35 = 0x3FE15F15, 0xF15F15F1 */ +D = -7.05306122448979611050e-01, /* -864/1225 = 0xBFE691DE, 0x2532C834 */ +E = 1.41428571428571436819e+00, /* 99/70 = 0x3FF6A0EA, 0x0EA0EA0F */ +F = 1.60714285714285720630e+00, /* 45/28 = 0x3FF9B6DB, 0x6DB6DB6E */ +G = 3.57142857142857150787e-01; /* 5/14 = 0x3FD6DB6D, 0xB6DB6DB7 */ + +#ifdef __STDC__ + double fd_cbrt(double x) +#else + double fd_cbrt(x) + double x; +#endif +{ + fd_twoints u; + int hx; + double r,s,t=0.0,w; + unsigned sign; + + u.d = x; + hx = __HI(u); /* high word of x */ + sign=hx&0x80000000; /* sign= sign(x) */ + hx ^=sign; + if(hx>=0x7ff00000) return(x+x); /* cbrt(NaN,INF) is itself */ + if((hx|__LO(u))==0) { + x = u.d; + return(x); /* cbrt(0) is itself */ + } + u.d = x; + __HI(u) = hx; /* x <- |x| */ + x = u.d; + /* rough cbrt to 5 bits */ + if(hx<0x00100000) /* subnormal number */ + {u.d = t; __HI(u)=0x43500000; t=u.d; /* set t= 2**54 */ + t*=x; __HI(u)=__HI(u)/3+B2; + } + else { + u.d = t; __HI(u)=hx/3+B1; t = u.d; + } + + + /* new cbrt to 23 bits, may be implemented in single precision */ + r=t*t/x; + s=C+r*t; + t*=G+F/(s+E+D/s); + + /* chopped to 20 bits and make it larger than cbrt(x) */ + u.d = t; + __LO(u)=0; __HI(u)+=0x00000001; + t = u.d; + + /* one step newton iteration to 53 bits with error less than 0.667 ulps */ + s=t*t; /* t*t is exact */ + r=x/s; + w=t+t; + r=(r-t)/(w+r); /* r-s is exact */ + t=t+t*r; + + /* retore the sign bit */ + u.d = t; + __HI(u) |= sign; + t = u.d; + return(t); +} diff --git a/src/dom/js/fdlibm/s_ceil.c b/src/dom/js/fdlibm/s_ceil.c new file mode 100644 index 000000000..826bcac6c --- /dev/null +++ b/src/dom/js/fdlibm/s_ceil.c @@ -0,0 +1,120 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)s_ceil.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * ceil(x) + * Return x rounded toward -inf to integral value + * Method: + * Bit twiddling. + * Exception: + * Inexact flag raised if x not equal to ceil(x). + */ + +#include "fdlibm.h" + +#ifdef __STDC__ +static const double really_big = 1.0e300; +#else +static double really_big = 1.0e300; +#endif + +#ifdef __STDC__ + double fd_ceil(double x) +#else + double fd_ceil(x) + double x; +#endif +{ + fd_twoints u; + int i0,i1,j0; + unsigned i,j; + u.d = x; + i0 = __HI(u); + i1 = __LO(u); + j0 = ((i0>>20)&0x7ff)-0x3ff; + if(j0<20) { + if(j0<0) { /* raise inexact if x != 0 */ + if(really_big+x>0.0) {/* return 0*sign(x) if |x|<1 */ + if(i0<0) {i0=0x80000000;i1=0;} + else if((i0|i1)!=0) { i0=0x3ff00000;i1=0;} + } + } else { + i = (0x000fffff)>>j0; + if(((i0&i)|i1)==0) return x; /* x is integral */ + if(really_big+x>0.0) { /* raise inexact flag */ + if(i0>0) i0 += (0x00100000)>>j0; + i0 &= (~i); i1=0; + } + } + } else if (j0>51) { + if(j0==0x400) return x+x; /* inf or NaN */ + else return x; /* x is integral */ + } else { + i = ((unsigned)(0xffffffff))>>(j0-20); + if((i1&i)==0) return x; /* x is integral */ + if(really_big+x>0.0) { /* raise inexact flag */ + if(i0>0) { + if(j0==20) i0+=1; + else { + j = i1 + (1<<(52-j0)); + if((int)j=0x7ff00000) return x-x; + + /* argument reduction needed */ + else { + n = __ieee754_rem_pio2(x,y); + switch(n&3) { + case 0: return __kernel_cos(y[0],y[1]); + case 1: return -__kernel_sin(y[0],y[1],1); + case 2: return -__kernel_cos(y[0],y[1]); + default: + return __kernel_sin(y[0],y[1],1); + } + } +} diff --git a/src/dom/js/fdlibm/s_erf.c b/src/dom/js/fdlibm/s_erf.c new file mode 100644 index 000000000..6eae8de3b --- /dev/null +++ b/src/dom/js/fdlibm/s_erf.c @@ -0,0 +1,356 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)s_erf.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* double erf(double x) + * double erfc(double x) + * x + * 2 |\ + * erf(x) = --------- | exp(-t*t)dt + * sqrt(pi) \| + * 0 + * + * erfc(x) = 1-erf(x) + * Note that + * erf(-x) = -erf(x) + * erfc(-x) = 2 - erfc(x) + * + * Method: + * 1. For |x| in [0, 0.84375] + * erf(x) = x + x*R(x^2) + * erfc(x) = 1 - erf(x) if x in [-.84375,0.25] + * = 0.5 + ((0.5-x)-x*R) if x in [0.25,0.84375] + * where R = P/Q where P is an odd poly of degree 8 and + * Q is an odd poly of degree 10. + * -57.90 + * | R - (erf(x)-x)/x | <= 2 + * + * + * Remark. The formula is derived by noting + * erf(x) = (2/sqrt(pi))*(x - x^3/3 + x^5/10 - x^7/42 + ....) + * and that + * 2/sqrt(pi) = 1.128379167095512573896158903121545171688 + * is close to one. The interval is chosen because the fix + * point of erf(x) is near 0.6174 (i.e., erf(x)=x when x is + * near 0.6174), and by some experiment, 0.84375 is chosen to + * guarantee the error is less than one ulp for erf. + * + * 2. For |x| in [0.84375,1.25], let s = |x| - 1, and + * c = 0.84506291151 rounded to single (24 bits) + * erf(x) = sign(x) * (c + P1(s)/Q1(s)) + * erfc(x) = (1-c) - P1(s)/Q1(s) if x > 0 + * 1+(c+P1(s)/Q1(s)) if x < 0 + * |P1/Q1 - (erf(|x|)-c)| <= 2**-59.06 + * Remark: here we use the taylor series expansion at x=1. + * erf(1+s) = erf(1) + s*Poly(s) + * = 0.845.. + P1(s)/Q1(s) + * That is, we use rational approximation to approximate + * erf(1+s) - (c = (single)0.84506291151) + * Note that |P1/Q1|< 0.078 for x in [0.84375,1.25] + * where + * P1(s) = degree 6 poly in s + * Q1(s) = degree 6 poly in s + * + * 3. For x in [1.25,1/0.35(~2.857143)], + * erfc(x) = (1/x)*exp(-x*x-0.5625+R1/S1) + * erf(x) = 1 - erfc(x) + * where + * R1(z) = degree 7 poly in z, (z=1/x^2) + * S1(z) = degree 8 poly in z + * + * 4. For x in [1/0.35,28] + * erfc(x) = (1/x)*exp(-x*x-0.5625+R2/S2) if x > 0 + * = 2.0 - (1/x)*exp(-x*x-0.5625+R2/S2) if -6 x >= 28 + * erf(x) = sign(x) *(1 - tiny) (raise inexact) + * erfc(x) = tiny*tiny (raise underflow) if x > 0 + * = 2 - tiny if x<0 + * + * 7. Special case: + * erf(0) = 0, erf(inf) = 1, erf(-inf) = -1, + * erfc(0) = 1, erfc(inf) = 0, erfc(-inf) = 2, + * erfc/erf(NaN) is NaN + */ + + +#include "fdlibm.h" + +#ifdef __STDC__ +static const double +#else +static double +#endif +tiny = 1e-300, +half= 5.00000000000000000000e-01, /* 0x3FE00000, 0x00000000 */ +one = 1.00000000000000000000e+00, /* 0x3FF00000, 0x00000000 */ +two = 2.00000000000000000000e+00, /* 0x40000000, 0x00000000 */ + /* c = (float)0.84506291151 */ +erx = 8.45062911510467529297e-01, /* 0x3FEB0AC1, 0x60000000 */ +/* + * Coefficients for approximation to erf on [0,0.84375] + */ +efx = 1.28379167095512586316e-01, /* 0x3FC06EBA, 0x8214DB69 */ +efx8= 1.02703333676410069053e+00, /* 0x3FF06EBA, 0x8214DB69 */ +pp0 = 1.28379167095512558561e-01, /* 0x3FC06EBA, 0x8214DB68 */ +pp1 = -3.25042107247001499370e-01, /* 0xBFD4CD7D, 0x691CB913 */ +pp2 = -2.84817495755985104766e-02, /* 0xBF9D2A51, 0xDBD7194F */ +pp3 = -5.77027029648944159157e-03, /* 0xBF77A291, 0x236668E4 */ +pp4 = -2.37630166566501626084e-05, /* 0xBEF8EAD6, 0x120016AC */ +qq1 = 3.97917223959155352819e-01, /* 0x3FD97779, 0xCDDADC09 */ +qq2 = 6.50222499887672944485e-02, /* 0x3FB0A54C, 0x5536CEBA */ +qq3 = 5.08130628187576562776e-03, /* 0x3F74D022, 0xC4D36B0F */ +qq4 = 1.32494738004321644526e-04, /* 0x3F215DC9, 0x221C1A10 */ +qq5 = -3.96022827877536812320e-06, /* 0xBED09C43, 0x42A26120 */ +/* + * Coefficients for approximation to erf in [0.84375,1.25] + */ +pa0 = -2.36211856075265944077e-03, /* 0xBF6359B8, 0xBEF77538 */ +pa1 = 4.14856118683748331666e-01, /* 0x3FDA8D00, 0xAD92B34D */ +pa2 = -3.72207876035701323847e-01, /* 0xBFD7D240, 0xFBB8C3F1 */ +pa3 = 3.18346619901161753674e-01, /* 0x3FD45FCA, 0x805120E4 */ +pa4 = -1.10894694282396677476e-01, /* 0xBFBC6398, 0x3D3E28EC */ +pa5 = 3.54783043256182359371e-02, /* 0x3FA22A36, 0x599795EB */ +pa6 = -2.16637559486879084300e-03, /* 0xBF61BF38, 0x0A96073F */ +qa1 = 1.06420880400844228286e-01, /* 0x3FBB3E66, 0x18EEE323 */ +qa2 = 5.40397917702171048937e-01, /* 0x3FE14AF0, 0x92EB6F33 */ +qa3 = 7.18286544141962662868e-02, /* 0x3FB2635C, 0xD99FE9A7 */ +qa4 = 1.26171219808761642112e-01, /* 0x3FC02660, 0xE763351F */ +qa5 = 1.36370839120290507362e-02, /* 0x3F8BEDC2, 0x6B51DD1C */ +qa6 = 1.19844998467991074170e-02, /* 0x3F888B54, 0x5735151D */ +/* + * Coefficients for approximation to erfc in [1.25,1/0.35] + */ +ra0 = -9.86494403484714822705e-03, /* 0xBF843412, 0x600D6435 */ +ra1 = -6.93858572707181764372e-01, /* 0xBFE63416, 0xE4BA7360 */ +ra2 = -1.05586262253232909814e+01, /* 0xC0251E04, 0x41B0E726 */ +ra3 = -6.23753324503260060396e+01, /* 0xC04F300A, 0xE4CBA38D */ +ra4 = -1.62396669462573470355e+02, /* 0xC0644CB1, 0x84282266 */ +ra5 = -1.84605092906711035994e+02, /* 0xC067135C, 0xEBCCABB2 */ +ra6 = -8.12874355063065934246e+01, /* 0xC0545265, 0x57E4D2F2 */ +ra7 = -9.81432934416914548592e+00, /* 0xC023A0EF, 0xC69AC25C */ +sa1 = 1.96512716674392571292e+01, /* 0x4033A6B9, 0xBD707687 */ +sa2 = 1.37657754143519042600e+02, /* 0x4061350C, 0x526AE721 */ +sa3 = 4.34565877475229228821e+02, /* 0x407B290D, 0xD58A1A71 */ +sa4 = 6.45387271733267880336e+02, /* 0x40842B19, 0x21EC2868 */ +sa5 = 4.29008140027567833386e+02, /* 0x407AD021, 0x57700314 */ +sa6 = 1.08635005541779435134e+02, /* 0x405B28A3, 0xEE48AE2C */ +sa7 = 6.57024977031928170135e+00, /* 0x401A47EF, 0x8E484A93 */ +sa8 = -6.04244152148580987438e-02, /* 0xBFAEEFF2, 0xEE749A62 */ +/* + * Coefficients for approximation to erfc in [1/.35,28] + */ +rb0 = -9.86494292470009928597e-03, /* 0xBF843412, 0x39E86F4A */ +rb1 = -7.99283237680523006574e-01, /* 0xBFE993BA, 0x70C285DE */ +rb2 = -1.77579549177547519889e+01, /* 0xC031C209, 0x555F995A */ +rb3 = -1.60636384855821916062e+02, /* 0xC064145D, 0x43C5ED98 */ +rb4 = -6.37566443368389627722e+02, /* 0xC083EC88, 0x1375F228 */ +rb5 = -1.02509513161107724954e+03, /* 0xC0900461, 0x6A2E5992 */ +rb6 = -4.83519191608651397019e+02, /* 0xC07E384E, 0x9BDC383F */ +sb1 = 3.03380607434824582924e+01, /* 0x403E568B, 0x261D5190 */ +sb2 = 3.25792512996573918826e+02, /* 0x40745CAE, 0x221B9F0A */ +sb3 = 1.53672958608443695994e+03, /* 0x409802EB, 0x189D5118 */ +sb4 = 3.19985821950859553908e+03, /* 0x40A8FFB7, 0x688C246A */ +sb5 = 2.55305040643316442583e+03, /* 0x40A3F219, 0xCEDF3BE6 */ +sb6 = 4.74528541206955367215e+02, /* 0x407DA874, 0xE79FE763 */ +sb7 = -2.24409524465858183362e+01; /* 0xC03670E2, 0x42712D62 */ + +#ifdef __STDC__ + double fd_erf(double x) +#else + double fd_erf(x) + double x; +#endif +{ + fd_twoints u; + int hx,ix,i; + double R,S,P,Q,s,y,z,r; + u.d = x; + hx = __HI(u); + ix = hx&0x7fffffff; + if(ix>=0x7ff00000) { /* erf(nan)=nan */ + i = ((unsigned)hx>>31)<<1; + return (double)(1-i)+one/x; /* erf(+-inf)=+-1 */ + } + + if(ix < 0x3feb0000) { /* |x|<0.84375 */ + if(ix < 0x3e300000) { /* |x|<2**-28 */ + if (ix < 0x00800000) + return 0.125*(8.0*x+efx8*x); /*avoid underflow */ + return x + efx*x; + } + z = x*x; + r = pp0+z*(pp1+z*(pp2+z*(pp3+z*pp4))); + s = one+z*(qq1+z*(qq2+z*(qq3+z*(qq4+z*qq5)))); + y = r/s; + return x + x*y; + } + if(ix < 0x3ff40000) { /* 0.84375 <= |x| < 1.25 */ + s = fd_fabs(x)-one; + P = pa0+s*(pa1+s*(pa2+s*(pa3+s*(pa4+s*(pa5+s*pa6))))); + Q = one+s*(qa1+s*(qa2+s*(qa3+s*(qa4+s*(qa5+s*qa6))))); + if(hx>=0) return erx + P/Q; else return -erx - P/Q; + } + if (ix >= 0x40180000) { /* inf>|x|>=6 */ + if(hx>=0) return one-tiny; else return tiny-one; + } + x = fd_fabs(x); + s = one/(x*x); + if(ix< 0x4006DB6E) { /* |x| < 1/0.35 */ + R=ra0+s*(ra1+s*(ra2+s*(ra3+s*(ra4+s*( + ra5+s*(ra6+s*ra7)))))); + S=one+s*(sa1+s*(sa2+s*(sa3+s*(sa4+s*( + sa5+s*(sa6+s*(sa7+s*sa8))))))); + } else { /* |x| >= 1/0.35 */ + R=rb0+s*(rb1+s*(rb2+s*(rb3+s*(rb4+s*( + rb5+s*rb6))))); + S=one+s*(sb1+s*(sb2+s*(sb3+s*(sb4+s*( + sb5+s*(sb6+s*sb7)))))); + } + z = x; + u.d = z; + __LO(u) = 0; + z = u.d; + r = __ieee754_exp(-z*z-0.5625)*__ieee754_exp((z-x)*(z+x)+R/S); + if(hx>=0) return one-r/x; else return r/x-one; +} + +#ifdef __STDC__ + double erfc(double x) +#else + double erfc(x) + double x; +#endif +{ + fd_twoints u; + int hx,ix; + double R,S,P,Q,s,y,z,r; + u.d = x; + hx = __HI(u); + ix = hx&0x7fffffff; + if(ix>=0x7ff00000) { /* erfc(nan)=nan */ + /* erfc(+-inf)=0,2 */ + return (double)(((unsigned)hx>>31)<<1)+one/x; + } + + if(ix < 0x3feb0000) { /* |x|<0.84375 */ + if(ix < 0x3c700000) /* |x|<2**-56 */ + return one-x; + z = x*x; + r = pp0+z*(pp1+z*(pp2+z*(pp3+z*pp4))); + s = one+z*(qq1+z*(qq2+z*(qq3+z*(qq4+z*qq5)))); + y = r/s; + if(hx < 0x3fd00000) { /* x<1/4 */ + return one-(x+x*y); + } else { + r = x*y; + r += (x-half); + return half - r ; + } + } + if(ix < 0x3ff40000) { /* 0.84375 <= |x| < 1.25 */ + s = fd_fabs(x)-one; + P = pa0+s*(pa1+s*(pa2+s*(pa3+s*(pa4+s*(pa5+s*pa6))))); + Q = one+s*(qa1+s*(qa2+s*(qa3+s*(qa4+s*(qa5+s*qa6))))); + if(hx>=0) { + z = one-erx; return z - P/Q; + } else { + z = erx+P/Q; return one+z; + } + } + if (ix < 0x403c0000) { /* |x|<28 */ + x = fd_fabs(x); + s = one/(x*x); + if(ix< 0x4006DB6D) { /* |x| < 1/.35 ~ 2.857143*/ + R=ra0+s*(ra1+s*(ra2+s*(ra3+s*(ra4+s*( + ra5+s*(ra6+s*ra7)))))); + S=one+s*(sa1+s*(sa2+s*(sa3+s*(sa4+s*( + sa5+s*(sa6+s*(sa7+s*sa8))))))); + } else { /* |x| >= 1/.35 ~ 2.857143 */ + if(hx<0&&ix>=0x40180000) return two-tiny;/* x < -6 */ + R=rb0+s*(rb1+s*(rb2+s*(rb3+s*(rb4+s*( + rb5+s*rb6))))); + S=one+s*(sb1+s*(sb2+s*(sb3+s*(sb4+s*( + sb5+s*(sb6+s*sb7)))))); + } + z = x; + u.d = z; + __LO(u) = 0; + z = u.d; + r = __ieee754_exp(-z*z-0.5625)* + __ieee754_exp((z-x)*(z+x)+R/S); + if(hx>0) return r/x; else return two-r/x; + } else { + if(hx>0) return tiny*tiny; else return two-tiny; + } +} diff --git a/src/dom/js/fdlibm/s_expm1.c b/src/dom/js/fdlibm/s_expm1.c new file mode 100644 index 000000000..578d2e144 --- /dev/null +++ b/src/dom/js/fdlibm/s_expm1.c @@ -0,0 +1,267 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)s_expm1.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* expm1(x) + * Returns exp(x)-1, the exponential of x minus 1. + * + * Method + * 1. Argument reduction: + * Given x, find r and integer k such that + * + * x = k*ln2 + r, |r| <= 0.5*ln2 ~ 0.34658 + * + * Here a correction term c will be computed to compensate + * the error in r when rounded to a floating-point number. + * + * 2. Approximating expm1(r) by a special rational function on + * the interval [0,0.34658]: + * Since + * r*(exp(r)+1)/(exp(r)-1) = 2+ r^2/6 - r^4/360 + ... + * we define R1(r*r) by + * r*(exp(r)+1)/(exp(r)-1) = 2+ r^2/6 * R1(r*r) + * That is, + * R1(r**2) = 6/r *((exp(r)+1)/(exp(r)-1) - 2/r) + * = 6/r * ( 1 + 2.0*(1/(exp(r)-1) - 1/r)) + * = 1 - r^2/60 + r^4/2520 - r^6/100800 + ... + * We use a special Reme algorithm on [0,0.347] to generate + * a polynomial of degree 5 in r*r to approximate R1. The + * maximum error of this polynomial approximation is bounded + * by 2**-61. In other words, + * R1(z) ~ 1.0 + Q1*z + Q2*z**2 + Q3*z**3 + Q4*z**4 + Q5*z**5 + * where Q1 = -1.6666666666666567384E-2, + * Q2 = 3.9682539681370365873E-4, + * Q3 = -9.9206344733435987357E-6, + * Q4 = 2.5051361420808517002E-7, + * Q5 = -6.2843505682382617102E-9; + * (where z=r*r, and the values of Q1 to Q5 are listed below) + * with error bounded by + * | 5 | -61 + * | 1.0+Q1*z+...+Q5*z - R1(z) | <= 2 + * | | + * + * expm1(r) = exp(r)-1 is then computed by the following + * specific way which minimize the accumulation rounding error: + * 2 3 + * r r [ 3 - (R1 + R1*r/2) ] + * expm1(r) = r + --- + --- * [--------------------] + * 2 2 [ 6 - r*(3 - R1*r/2) ] + * + * To compensate the error in the argument reduction, we use + * expm1(r+c) = expm1(r) + c + expm1(r)*c + * ~ expm1(r) + c + r*c + * Thus c+r*c will be added in as the correction terms for + * expm1(r+c). Now rearrange the term to avoid optimization + * screw up: + * ( 2 2 ) + * ({ ( r [ R1 - (3 - R1*r/2) ] ) } r ) + * expm1(r+c)~r - ({r*(--- * [--------------------]-c)-c} - --- ) + * ({ ( 2 [ 6 - r*(3 - R1*r/2) ] ) } 2 ) + * ( ) + * + * = r - E + * 3. Scale back to obtain expm1(x): + * From step 1, we have + * expm1(x) = either 2^k*[expm1(r)+1] - 1 + * = or 2^k*[expm1(r) + (1-2^-k)] + * 4. Implementation notes: + * (A). To save one multiplication, we scale the coefficient Qi + * to Qi*2^i, and replace z by (x^2)/2. + * (B). To achieve maximum accuracy, we compute expm1(x) by + * (i) if x < -56*ln2, return -1.0, (raise inexact if x!=inf) + * (ii) if k=0, return r-E + * (iii) if k=-1, return 0.5*(r-E)-0.5 + * (iv) if k=1 if r < -0.25, return 2*((r+0.5)- E) + * else return 1.0+2.0*(r-E); + * (v) if (k<-2||k>56) return 2^k(1-(E-r)) - 1 (or exp(x)-1) + * (vi) if k <= 20, return 2^k((1-2^-k)-(E-r)), else + * (vii) return 2^k(1-((E+2^-k)-r)) + * + * Special cases: + * expm1(INF) is INF, expm1(NaN) is NaN; + * expm1(-INF) is -1, and + * for finite argument, only expm1(0)=0 is exact. + * + * Accuracy: + * according to an error analysis, the error is always less than + * 1 ulp (unit in the last place). + * + * Misc. info. + * For IEEE double + * if x > 7.09782712893383973096e+02 then expm1(x) overflow + * + * Constants: + * The hexadecimal values are the intended ones for the following + * constants. The decimal values may be used, provided that the + * compiler will convert from decimal to binary accurately enough + * to produce the hexadecimal values shown. + */ + +#include "fdlibm.h" + +#ifdef __STDC__ +static const double +#else +static double +#endif +one = 1.0, +really_big = 1.0e+300, +tiny = 1.0e-300, +o_threshold = 7.09782712893383973096e+02,/* 0x40862E42, 0xFEFA39EF */ +ln2_hi = 6.93147180369123816490e-01,/* 0x3fe62e42, 0xfee00000 */ +ln2_lo = 1.90821492927058770002e-10,/* 0x3dea39ef, 0x35793c76 */ +invln2 = 1.44269504088896338700e+00,/* 0x3ff71547, 0x652b82fe */ + /* scaled coefficients related to expm1 */ +Q1 = -3.33333333333331316428e-02, /* BFA11111 111110F4 */ +Q2 = 1.58730158725481460165e-03, /* 3F5A01A0 19FE5585 */ +Q3 = -7.93650757867487942473e-05, /* BF14CE19 9EAADBB7 */ +Q4 = 4.00821782732936239552e-06, /* 3ED0CFCA 86E65239 */ +Q5 = -2.01099218183624371326e-07; /* BE8AFDB7 6E09C32D */ + +#ifdef __STDC__ + double fd_expm1(double x) +#else + double fd_expm1(x) + double x; +#endif +{ + fd_twoints u; + double y,hi,lo,c,t,e,hxs,hfx,r1; + int k,xsb; + unsigned hx; + + u.d = x; + hx = __HI(u); /* high word of x */ + xsb = hx&0x80000000; /* sign bit of x */ + if(xsb==0) y=x; else y= -x; /* y = |x| */ + hx &= 0x7fffffff; /* high word of |x| */ + + /* filter out huge and non-finite argument */ + if(hx >= 0x4043687A) { /* if |x|>=56*ln2 */ + if(hx >= 0x40862E42) { /* if |x|>=709.78... */ + if(hx>=0x7ff00000) { + u.d = x; + if(((hx&0xfffff)|__LO(u))!=0) + return x+x; /* NaN */ + else return (xsb==0)? x:-1.0;/* exp(+-inf)={inf,-1} */ + } + if(x > o_threshold) return really_big*really_big; /* overflow */ + } + if(xsb!=0) { /* x < -56*ln2, return -1.0 with inexact */ + if(x+tiny<0.0) /* raise inexact */ + return tiny-one; /* return -1 */ + } + } + + /* argument reduction */ + if(hx > 0x3fd62e42) { /* if |x| > 0.5 ln2 */ + if(hx < 0x3FF0A2B2) { /* and |x| < 1.5 ln2 */ + if(xsb==0) + {hi = x - ln2_hi; lo = ln2_lo; k = 1;} + else + {hi = x + ln2_hi; lo = -ln2_lo; k = -1;} + } else { + k = (int)(invln2*x+((xsb==0)?0.5:-0.5)); + t = k; + hi = x - t*ln2_hi; /* t*ln2_hi is exact here */ + lo = t*ln2_lo; + } + x = hi - lo; + c = (hi-x)-lo; + } + else if(hx < 0x3c900000) { /* when |x|<2**-54, return x */ + t = really_big+x; /* return x with inexact flags when x!=0 */ + return x - (t-(really_big+x)); + } + else k = 0; + + /* x is now in primary range */ + hfx = 0.5*x; + hxs = x*hfx; + r1 = one+hxs*(Q1+hxs*(Q2+hxs*(Q3+hxs*(Q4+hxs*Q5)))); + t = 3.0-r1*hfx; + e = hxs*((r1-t)/(6.0 - x*t)); + if(k==0) return x - (x*e-hxs); /* c is 0 */ + else { + e = (x*(e-c)-c); + e -= hxs; + if(k== -1) return 0.5*(x-e)-0.5; + if(k==1) + if(x < -0.25) return -2.0*(e-(x+0.5)); + else return one+2.0*(x-e); + if (k <= -2 || k>56) { /* suffice to return exp(x)-1 */ + y = one-(e-x); + u.d = y; + __HI(u) += (k<<20); /* add k to y's exponent */ + y = u.d; + return y-one; + } + t = one; + if(k<20) { + u.d = t; + __HI(u) = 0x3ff00000 - (0x200000>>k); /* t=1-2^-k */ + t = u.d; + y = t-(e-x); + u.d = y; + __HI(u) += (k<<20); /* add k to y's exponent */ + y = u.d; + } else { + u.d = t; + __HI(u) = ((0x3ff-k)<<20); /* 2^-k */ + t = u.d; + y = x-(e+t); + y += one; + u.d = y; + __HI(u) += (k<<20); /* add k to y's exponent */ + y = u.d; + } + } + return y; +} diff --git a/src/dom/js/fdlibm/s_fabs.c b/src/dom/js/fdlibm/s_fabs.c new file mode 100644 index 000000000..6b029da10 --- /dev/null +++ b/src/dom/js/fdlibm/s_fabs.c @@ -0,0 +1,70 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)s_fabs.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * fabs(x) returns the absolute value of x. + */ + +#include "fdlibm.h" + +#ifdef __STDC__ + double fd_fabs(double x) +#else + double fd_fabs(x) + double x; +#endif +{ + fd_twoints u; + u.d = x; + __HI(u) &= 0x7fffffff; + x = u.d; + return x; +} diff --git a/src/dom/js/fdlibm/s_finite.c b/src/dom/js/fdlibm/s_finite.c new file mode 100644 index 000000000..4a0a4d3c6 --- /dev/null +++ b/src/dom/js/fdlibm/s_finite.c @@ -0,0 +1,71 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)s_finite.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * finite(x) returns 1 is x is finite, else 0; + * no branching! + */ + +#include "fdlibm.h" + +#ifdef __STDC__ + int fd_finite(double x) +#else + int fd_finite(x) + double x; +#endif +{ + fd_twoints u; + int hx; + u.d = x; + hx = __HI(u); + return (unsigned)((hx&0x7fffffff)-0x7ff00000)>>31; +} diff --git a/src/dom/js/fdlibm/s_floor.c b/src/dom/js/fdlibm/s_floor.c new file mode 100644 index 000000000..6c23495f0 --- /dev/null +++ b/src/dom/js/fdlibm/s_floor.c @@ -0,0 +1,121 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)s_floor.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * floor(x) + * Return x rounded toward -inf to integral value + * Method: + * Bit twiddling. + * Exception: + * Inexact flag raised if x not equal to floor(x). + */ + +#include "fdlibm.h" + +#ifdef __STDC__ +static const double really_big = 1.0e300; +#else +static double really_big = 1.0e300; +#endif + +#ifdef __STDC__ + double fd_floor(double x) +#else + double fd_floor(x) + double x; +#endif +{ + fd_twoints u; + int i0,i1,j0; + unsigned i,j; + u.d = x; + i0 = __HI(u); + i1 = __LO(u); + j0 = ((i0>>20)&0x7ff)-0x3ff; + if(j0<20) { + if(j0<0) { /* raise inexact if x != 0 */ + if(really_big+x>0.0) {/* return 0*sign(x) if |x|<1 */ + if(i0>=0) {i0=i1=0;} + else if(((i0&0x7fffffff)|i1)!=0) + { i0=0xbff00000;i1=0;} + } + } else { + i = (0x000fffff)>>j0; + if(((i0&i)|i1)==0) return x; /* x is integral */ + if(really_big+x>0.0) { /* raise inexact flag */ + if(i0<0) i0 += (0x00100000)>>j0; + i0 &= (~i); i1=0; + } + } + } else if (j0>51) { + if(j0==0x400) return x+x; /* inf or NaN */ + else return x; /* x is integral */ + } else { + i = ((unsigned)(0xffffffff))>>(j0-20); + if((i1&i)==0) return x; /* x is integral */ + if(really_big+x>0.0) { /* raise inexact flag */ + if(i0<0) { + if(j0==20) i0+=1; + else { + j = i1+(1<<(52-j0)); + if((int)j=0x7ff00000||((ix|lx)==0)) return x; /* 0,inf,nan */ + if (ix<0x00100000) { /* subnormal */ + x *= two54; + u.d = x; + hx = __HI(u); + ix = hx&0x7fffffff; + *eptr = -54; + } + *eptr += (ix>>20)-1022; + hx = (hx&0x800fffff)|0x3fe00000; + u.d = x; + __HI(u) = hx; + x = u.d; + return x; +} diff --git a/src/dom/js/fdlibm/s_ilogb.c b/src/dom/js/fdlibm/s_ilogb.c new file mode 100644 index 000000000..f769781a6 --- /dev/null +++ b/src/dom/js/fdlibm/s_ilogb.c @@ -0,0 +1,85 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)s_ilogb.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* ilogb(double x) + * return the binary exponent of non-zero x + * ilogb(0) = 0x80000001 + * ilogb(inf/NaN) = 0x7fffffff (no signal is raised) + */ + +#include "fdlibm.h" + +#ifdef __STDC__ + int fd_ilogb(double x) +#else + int fd_ilogb(x) + double x; +#endif +{ + int hx,lx,ix; + fd_twoints u; + u.d = x; + hx = (__HI(u))&0x7fffffff; /* high word of x */ + if(hx<0x00100000) { + lx = __LO(u); + if((hx|lx)==0) + return 0x80000001; /* ilogb(0) = 0x80000001 */ + else /* subnormal x */ + if(hx==0) { + for (ix = -1043; lx>0; lx<<=1) ix -=1; + } else { + for (ix = -1022,hx<<=11; hx>0; hx<<=1) ix -=1; + } + return ix; + } + else if (hx<0x7ff00000) return (hx>>20)-1023; + else return 0x7fffffff; +} diff --git a/src/dom/js/fdlibm/s_isnan.c b/src/dom/js/fdlibm/s_isnan.c new file mode 100644 index 000000000..52f8759c7 --- /dev/null +++ b/src/dom/js/fdlibm/s_isnan.c @@ -0,0 +1,74 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)s_isnan.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * isnan(x) returns 1 is x is nan, else 0; + * no branching! + */ + +#include "fdlibm.h" + +#ifdef __STDC__ + int fd_isnan(double x) +#else + int fd_isnan(x) + double x; +#endif +{ + fd_twoints u; + int hx,lx; + u.d = x; + hx = (__HI(u)&0x7fffffff); + lx = __LO(u); + hx |= (unsigned)(lx|(-lx))>>31; + hx = 0x7ff00000 - hx; + return ((unsigned)(hx))>>31; +} diff --git a/src/dom/js/fdlibm/s_ldexp.c b/src/dom/js/fdlibm/s_ldexp.c new file mode 100644 index 000000000..9475520d4 --- /dev/null +++ b/src/dom/js/fdlibm/s_ldexp.c @@ -0,0 +1,66 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)s_ldexp.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +#include "fdlibm.h" +#include + +#ifdef __STDC__ + double fd_ldexp(double value, int exp) +#else + double fd_ldexp(value, exp) + double value; int exp; +#endif +{ + if(!fd_finite(value)||value==0.0) return value; + value = fd_scalbn(value,exp); + if(!fd_finite(value)||value==0.0) errno = ERANGE; + return value; +} diff --git a/src/dom/js/fdlibm/s_lib_version.c b/src/dom/js/fdlibm/s_lib_version.c new file mode 100644 index 000000000..2ccf67d51 --- /dev/null +++ b/src/dom/js/fdlibm/s_lib_version.c @@ -0,0 +1,73 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)s_lib_version.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * MACRO for standards + */ + +#include "fdlibm.h" + +/* + * define and initialize _LIB_VERSION + */ +#ifdef _POSIX_MODE +_LIB_VERSION_TYPE _LIB_VERSION = _POSIX_; +#else +#ifdef _XOPEN_MODE +_LIB_VERSION_TYPE _LIB_VERSION = _XOPEN_; +#else +#ifdef _SVID3_MODE +_LIB_VERSION_TYPE _LIB_VERSION = _SVID_; +#else /* default _IEEE_MODE */ +_LIB_VERSION_TYPE _LIB_VERSION = _IEEE_; +#endif +#endif +#endif diff --git a/src/dom/js/fdlibm/s_log1p.c b/src/dom/js/fdlibm/s_log1p.c new file mode 100644 index 000000000..1840156b1 --- /dev/null +++ b/src/dom/js/fdlibm/s_log1p.c @@ -0,0 +1,211 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)s_log1p.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* double log1p(double x) + * + * Method : + * 1. Argument Reduction: find k and f such that + * 1+x = 2^k * (1+f), + * where sqrt(2)/2 < 1+f < sqrt(2) . + * + * Note. If k=0, then f=x is exact. However, if k!=0, then f + * may not be representable exactly. In that case, a correction + * term is need. Let u=1+x rounded. Let c = (1+x)-u, then + * log(1+x) - log(u) ~ c/u. Thus, we proceed to compute log(u), + * and add back the correction term c/u. + * (Note: when x > 2**53, one can simply return log(x)) + * + * 2. Approximation of log1p(f). + * Let s = f/(2+f) ; based on log(1+f) = log(1+s) - log(1-s) + * = 2s + 2/3 s**3 + 2/5 s**5 + ....., + * = 2s + s*R + * We use a special Reme algorithm on [0,0.1716] to generate + * a polynomial of degree 14 to approximate R The maximum error + * of this polynomial approximation is bounded by 2**-58.45. In + * other words, + * 2 4 6 8 10 12 14 + * R(z) ~ Lp1*s +Lp2*s +Lp3*s +Lp4*s +Lp5*s +Lp6*s +Lp7*s + * (the values of Lp1 to Lp7 are listed in the program) + * and + * | 2 14 | -58.45 + * | Lp1*s +...+Lp7*s - R(z) | <= 2 + * | | + * Note that 2s = f - s*f = f - hfsq + s*hfsq, where hfsq = f*f/2. + * In order to guarantee error in log below 1ulp, we compute log + * by + * log1p(f) = f - (hfsq - s*(hfsq+R)). + * + * 3. Finally, log1p(x) = k*ln2 + log1p(f). + * = k*ln2_hi+(f-(hfsq-(s*(hfsq+R)+k*ln2_lo))) + * Here ln2 is split into two floating point number: + * ln2_hi + ln2_lo, + * where n*ln2_hi is always exact for |n| < 2000. + * + * Special cases: + * log1p(x) is NaN with signal if x < -1 (including -INF) ; + * log1p(+INF) is +INF; log1p(-1) is -INF with signal; + * log1p(NaN) is that NaN with no signal. + * + * Accuracy: + * according to an error analysis, the error is always less than + * 1 ulp (unit in the last place). + * + * Constants: + * The hexadecimal values are the intended ones for the following + * constants. The decimal values may be used, provided that the + * compiler will convert from decimal to binary accurately enough + * to produce the hexadecimal values shown. + * + * Note: Assuming log() return accurate answer, the following + * algorithm can be used to compute log1p(x) to within a few ULP: + * + * u = 1+x; + * if(u==1.0) return x ; else + * return log(u)*(x/(u-1.0)); + * + * See HP-15C Advanced Functions Handbook, p.193. + */ + +#include "fdlibm.h" + +#ifdef __STDC__ +static const double +#else +static double +#endif +ln2_hi = 6.93147180369123816490e-01, /* 3fe62e42 fee00000 */ +ln2_lo = 1.90821492927058770002e-10, /* 3dea39ef 35793c76 */ +two54 = 1.80143985094819840000e+16, /* 43500000 00000000 */ +Lp1 = 6.666666666666735130e-01, /* 3FE55555 55555593 */ +Lp2 = 3.999999999940941908e-01, /* 3FD99999 9997FA04 */ +Lp3 = 2.857142874366239149e-01, /* 3FD24924 94229359 */ +Lp4 = 2.222219843214978396e-01, /* 3FCC71C5 1D8E78AF */ +Lp5 = 1.818357216161805012e-01, /* 3FC74664 96CB03DE */ +Lp6 = 1.531383769920937332e-01, /* 3FC39A09 D078C69F */ +Lp7 = 1.479819860511658591e-01; /* 3FC2F112 DF3E5244 */ + +static double zero = 0.0; + +#ifdef __STDC__ + double fd_log1p(double x) +#else + double fd_log1p(x) + double x; +#endif +{ + double hfsq,f,c,s,z,R,u; + int k,hx,hu,ax; + fd_twoints un; + + un.d = x; + hx = __HI(un); /* high word of x */ + ax = hx&0x7fffffff; + + k = 1; + if (hx < 0x3FDA827A) { /* x < 0.41422 */ + if(ax>=0x3ff00000) { /* x <= -1.0 */ + if(x==-1.0) return -two54/zero; /* log1p(-1)=+inf */ + else return (x-x)/(x-x); /* log1p(x<-1)=NaN */ + } + if(ax<0x3e200000) { /* |x| < 2**-29 */ + if(two54+x>zero /* raise inexact */ + &&ax<0x3c900000) /* |x| < 2**-54 */ + return x; + else + return x - x*x*0.5; + } + if(hx>0||hx<=((int)0xbfd2bec3)) { + k=0;f=x;hu=1;} /* -0.2929= 0x7ff00000) return x+x; + if(k!=0) { + if(hx<0x43400000) { + u = 1.0+x; + un.d = u; + hu = __HI(un); /* high word of u */ + k = (hu>>20)-1023; + c = (k>0)? 1.0-(u-x):x-(u-1.0);/* correction term */ + c /= u; + } else { + u = x; + un.d = u; + hu = __HI(un); /* high word of u */ + k = (hu>>20)-1023; + c = 0; + } + hu &= 0x000fffff; + if(hu<0x6a09e) { + un.d = u; + __HI(un) = hu|0x3ff00000; /* normalize u */ + u = un.d; + } else { + k += 1; + un.d = u; + __HI(un) = hu|0x3fe00000; /* normalize u/2 */ + u = un.d; + hu = (0x00100000-hu)>>2; + } + f = u-1.0; + } + hfsq=0.5*f*f; + if(hu==0) { /* |f| < 2**-20 */ + if(f==zero) if(k==0) return zero; + else {c += k*ln2_lo; return k*ln2_hi+c;} + R = hfsq*(1.0-0.66666666666666666*f); + if(k==0) return f-R; else + return k*ln2_hi-((R-(k*ln2_lo+c))-f); + } + s = f/(2.0+f); + z = s*s; + R = z*(Lp1+z*(Lp2+z*(Lp3+z*(Lp4+z*(Lp5+z*(Lp6+z*Lp7)))))); + if(k==0) return f-(hfsq-s*(hfsq+R)); else + return k*ln2_hi-((hfsq-(s*(hfsq+R)+(k*ln2_lo+c)))-f); +} diff --git a/src/dom/js/fdlibm/s_logb.c b/src/dom/js/fdlibm/s_logb.c new file mode 100644 index 000000000..f885c4dc3 --- /dev/null +++ b/src/dom/js/fdlibm/s_logb.c @@ -0,0 +1,79 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)s_logb.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * double logb(x) + * IEEE 754 logb. Included to pass IEEE test suite. Not recommend. + * Use ilogb instead. + */ + +#include "fdlibm.h" + +#ifdef __STDC__ + double fd_logb(double x) +#else + double fd_logb(x) + double x; +#endif +{ + int lx,ix; + fd_twoints u; + + u.d = x; + ix = (__HI(u))&0x7fffffff; /* high |x| */ + lx = __LO(u); /* low x */ + if((ix|lx)==0) return -1.0/fd_fabs(x); + if(ix>=0x7ff00000) return x*x; + if((ix>>=20)==0) /* IEEE 754 logb */ + return -1022.0; + else + return (double) (ix-1023); +} diff --git a/src/dom/js/fdlibm/s_matherr.c b/src/dom/js/fdlibm/s_matherr.c new file mode 100644 index 000000000..cd99ca88f --- /dev/null +++ b/src/dom/js/fdlibm/s_matherr.c @@ -0,0 +1,64 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)s_matherr.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +#include "fdlibm.h" + +#ifdef __STDC__ + int fd_matherr(struct exception *x) +#else + int fd_matherr(x) + struct exception *x; +#endif +{ + int n=0; + if(x->arg1!=x->arg1) return 0; + return n; +} diff --git a/src/dom/js/fdlibm/s_modf.c b/src/dom/js/fdlibm/s_modf.c new file mode 100644 index 000000000..3b182bd3b --- /dev/null +++ b/src/dom/js/fdlibm/s_modf.c @@ -0,0 +1,132 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)s_modf.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * modf(double x, double *iptr) + * return fraction part of x, and return x's integral part in *iptr. + * Method: + * Bit twiddling. + * + * Exception: + * No exception. + */ + +#include "fdlibm.h" + +#ifdef __STDC__ +static const double one = 1.0; +#else +static double one = 1.0; +#endif + +#ifdef __STDC__ + double fd_modf(double x, double *iptr) +#else + double fd_modf(x, iptr) + double x,*iptr; +#endif +{ + int i0,i1,j0; + unsigned i; + fd_twoints u; + u.d = x; + i0 = __HI(u); /* high x */ + i1 = __LO(u); /* low x */ + j0 = ((i0>>20)&0x7ff)-0x3ff; /* exponent of x */ + if(j0<20) { /* integer part in high x */ + if(j0<0) { /* |x|<1 */ + u.d = *iptr; + __HI(u) = i0&0x80000000; + __LO(u) = 0; /* *iptr = +-0 */ + *iptr = u.d; + return x; + } else { + i = (0x000fffff)>>j0; + if(((i0&i)|i1)==0) { /* x is integral */ + *iptr = x; + u.d = x; + __HI(u) &= 0x80000000; + __LO(u) = 0; /* return +-0 */ + x = u.d; + return x; + } else { + u.d = *iptr; + __HI(u) = i0&(~i); + __LO(u) = 0; + *iptr = u.d; + return x - *iptr; + } + } + } else if (j0>51) { /* no fraction part */ + *iptr = x*one; + u.d = x; + __HI(u) &= 0x80000000; + __LO(u) = 0; /* return +-0 */ + x = u.d; + return x; + } else { /* fraction part in low x */ + i = ((unsigned)(0xffffffff))>>(j0-20); + if((i1&i)==0) { /* x is integral */ + *iptr = x; + u.d = x; + __HI(u) &= 0x80000000; + __LO(u) = 0; /* return +-0 */ + x = u.d; + return x; + } else { + u.d = *iptr; + __HI(u) = i0; + __LO(u) = i1&(~i); + *iptr = u.d; + return x - *iptr; + } + } +} diff --git a/src/dom/js/fdlibm/s_nextafter.c b/src/dom/js/fdlibm/s_nextafter.c new file mode 100644 index 000000000..f71c5c835 --- /dev/null +++ b/src/dom/js/fdlibm/s_nextafter.c @@ -0,0 +1,124 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)s_nextafter.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* IEEE functions + * nextafter(x,y) + * return the next machine floating-point number of x in the + * direction toward y. + * Special cases: + */ + +#include "fdlibm.h" + +#ifdef __STDC__ + double fd_nextafter(double x, double y) +#else + double fd_nextafter(x,y) + double x,y; +#endif +{ + int hx,hy,ix,iy; + unsigned lx,ly; + fd_twoints ux, uy; + + ux.d = x; uy.d = y; + hx = __HI(ux); /* high word of x */ + lx = __LO(ux); /* low word of x */ + hy = __HI(uy); /* high word of y */ + ly = __LO(uy); /* low word of y */ + ix = hx&0x7fffffff; /* |x| */ + iy = hy&0x7fffffff; /* |y| */ + + if(((ix>=0x7ff00000)&&((ix-0x7ff00000)|lx)!=0) || /* x is nan */ + ((iy>=0x7ff00000)&&((iy-0x7ff00000)|ly)!=0)) /* y is nan */ + return x+y; + if(x==y) return x; /* x=y, return x */ + if((ix|lx)==0) { /* x == 0 */ + ux.d = x; + __HI(ux) = hy&0x80000000; /* return +-minsubnormal */ + __LO(ux) = 1; + x = ux.d; + y = x*x; + if(y==x) return y; else return x; /* raise underflow flag */ + } + if(hx>=0) { /* x > 0 */ + if(hx>hy||((hx==hy)&&(lx>ly))) { /* x > y, x -= ulp */ + if(lx==0) hx -= 1; + lx -= 1; + } else { /* x < y, x += ulp */ + lx += 1; + if(lx==0) hx += 1; + } + } else { /* x < 0 */ + if(hy>=0||hx>hy||((hx==hy)&&(lx>ly))){/* x < y, x -= ulp */ + if(lx==0) hx -= 1; + lx -= 1; + } else { /* x > y, x += ulp */ + lx += 1; + if(lx==0) hx += 1; + } + } + hy = hx&0x7ff00000; + if(hy>=0x7ff00000) return x+x; /* overflow */ + if(hy<0x00100000) { /* underflow */ + y = x*x; + if(y!=x) { /* raise underflow flag */ + uy.d = y; + __HI(uy) = hx; __LO(uy) = lx; + y = uy.d; + return y; + } + } + ux.d = x; + __HI(ux) = hx; __LO(ux) = lx; + x = ux.d; + return x; +} diff --git a/src/dom/js/fdlibm/s_rint.c b/src/dom/js/fdlibm/s_rint.c new file mode 100644 index 000000000..3c4fab6d9 --- /dev/null +++ b/src/dom/js/fdlibm/s_rint.c @@ -0,0 +1,131 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)s_rint.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * rint(x) + * Return x rounded to integral value according to the prevailing + * rounding mode. + * Method: + * Using floating addition. + * Exception: + * Inexact flag raised if x not equal to rint(x). + */ + +#include "fdlibm.h" + +#ifdef __STDC__ +static const double +#else +static double +#endif +TWO52[2]={ + 4.50359962737049600000e+15, /* 0x43300000, 0x00000000 */ + -4.50359962737049600000e+15, /* 0xC3300000, 0x00000000 */ +}; + +#ifdef __STDC__ + double fd_rint(double x) +#else + double fd_rint(x) + double x; +#endif +{ + int i0,j0,sx; + unsigned i,i1; + double w,t; + fd_twoints u; + + u.d = x; + i0 = __HI(u); + sx = (i0>>31)&1; + i1 = __LO(u); + j0 = ((i0>>20)&0x7ff)-0x3ff; + if(j0<20) { + if(j0<0) { + if(((i0&0x7fffffff)|i1)==0) return x; + i1 |= (i0&0x0fffff); + i0 &= 0xfffe0000; + i0 |= ((i1|-(int)i1)>>12)&0x80000; + u.d = x; + __HI(u)=i0; + x = u.d; + w = TWO52[sx]+x; + t = w-TWO52[sx]; + u.d = t; + i0 = __HI(u); + __HI(u) = (i0&0x7fffffff)|(sx<<31); + t = u.d; + return t; + } else { + i = (0x000fffff)>>j0; + if(((i0&i)|i1)==0) return x; /* x is integral */ + i>>=1; + if(((i0&i)|i1)!=0) { + if(j0==19) i1 = 0x40000000; else + i0 = (i0&(~i))|((0x20000)>>j0); + } + } + } else if (j0>51) { + if(j0==0x400) return x+x; /* inf or NaN */ + else return x; /* x is integral */ + } else { + i = ((unsigned)(0xffffffff))>>(j0-20); + if((i1&i)==0) return x; /* x is integral */ + i>>=1; + if((i1&i)!=0) i1 = (i1&(~i))|((0x40000000)>>(j0-20)); + } + u.d = x; + __HI(u) = i0; + __LO(u) = i1; + x = u.d; + w = TWO52[sx]+x; + return w-TWO52[sx]; +} diff --git a/src/dom/js/fdlibm/s_scalbn.c b/src/dom/js/fdlibm/s_scalbn.c new file mode 100644 index 000000000..3deeaa305 --- /dev/null +++ b/src/dom/js/fdlibm/s_scalbn.c @@ -0,0 +1,107 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)s_scalbn.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * scalbn (double x, int n) + * scalbn(x,n) returns x* 2**n computed by exponent + * manipulation rather than by actually performing an + * exponentiation or a multiplication. + */ + +#include "fdlibm.h" + +#ifdef __STDC__ +static const double +#else +static double +#endif +two54 = 1.80143985094819840000e+16, /* 0x43500000, 0x00000000 */ +twom54 = 5.55111512312578270212e-17, /* 0x3C900000, 0x00000000 */ +really_big = 1.0e+300, +tiny = 1.0e-300; + +#ifdef __STDC__ + double fd_scalbn (double x, int n) +#else + double fd_scalbn (x,n) + double x; int n; +#endif +{ + fd_twoints u; + int k,hx,lx; + u.d = x; + hx = __HI(u); + lx = __LO(u); + k = (hx&0x7ff00000)>>20; /* extract exponent */ + if (k==0) { /* 0 or subnormal x */ + if ((lx|(hx&0x7fffffff))==0) return x; /* +-0 */ + x *= two54; + u.d = x; + hx = __HI(u); + k = ((hx&0x7ff00000)>>20) - 54; + if (n< -50000) return tiny*x; /*underflow*/ + } + if (k==0x7ff) return x+x; /* NaN or Inf */ + k = k+n; + if (k > 0x7fe) return really_big*fd_copysign(really_big,x); /* overflow */ + if (k > 0) /* normal result */ + {u.d = x; __HI(u) = (hx&0x800fffff)|(k<<20); x = u.d; return x;} + if (k <= -54) { + if (n > 50000) /* in case integer overflow in n+k */ + return really_big*fd_copysign(really_big,x); /*overflow*/ + else return tiny*fd_copysign(tiny,x); /*underflow*/ + } + k += 54; /* subnormal result */ + u.d = x; + __HI(u) = (hx&0x800fffff)|(k<<20); + x = u.d; + return x*twom54; +} diff --git a/src/dom/js/fdlibm/s_signgam.c b/src/dom/js/fdlibm/s_signgam.c new file mode 100644 index 000000000..4eb8ce72f --- /dev/null +++ b/src/dom/js/fdlibm/s_signgam.c @@ -0,0 +1,40 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +#include "fdlibm.h" +int signgam = 0; diff --git a/src/dom/js/fdlibm/s_significand.c b/src/dom/js/fdlibm/s_significand.c new file mode 100644 index 000000000..2e1c0b28f --- /dev/null +++ b/src/dom/js/fdlibm/s_significand.c @@ -0,0 +1,68 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)s_significand.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * significand(x) computes just + * scalb(x, (double) -ilogb(x)), + * for exercising the fraction-part(F) IEEE 754-1985 test vector. + */ + +#include "fdlibm.h" + +#ifdef __STDC__ + double fd_significand(double x) +#else + double fd_significand(x) + double x; +#endif +{ + return __ieee754_scalb(x,(double) -fd_ilogb(x)); +} diff --git a/src/dom/js/fdlibm/s_sin.c b/src/dom/js/fdlibm/s_sin.c new file mode 100644 index 000000000..8bbc5c62d --- /dev/null +++ b/src/dom/js/fdlibm/s_sin.c @@ -0,0 +1,118 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)s_sin.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* sin(x) + * Return sine function of x. + * + * kernel function: + * __kernel_sin ... sine function on [-pi/4,pi/4] + * __kernel_cos ... cose function on [-pi/4,pi/4] + * __ieee754_rem_pio2 ... argument reduction routine + * + * Method. + * Let S,C and T denote the sin, cos and tan respectively on + * [-PI/4, +PI/4]. Reduce the argument x to y1+y2 = x-k*pi/2 + * in [-pi/4 , +pi/4], and let n = k mod 4. + * We have + * + * n sin(x) cos(x) tan(x) + * ---------------------------------------------------------- + * 0 S C T + * 1 C -S -1/T + * 2 -S -C T + * 3 -C S -1/T + * ---------------------------------------------------------- + * + * Special cases: + * Let trig be any of sin, cos, or tan. + * trig(+-INF) is NaN, with signals; + * trig(NaN) is that NaN; + * + * Accuracy: + * TRIG(x) returns trig(x) nearly rounded + */ + +#include "fdlibm.h" + +#ifdef __STDC__ + double fd_sin(double x) +#else + double fd_sin(x) + double x; +#endif +{ + fd_twoints u; + double y[2],z=0.0; + int n, ix; + + /* High word of x. */ + u.d = x; + ix = __HI(u); + + /* |x| ~< pi/4 */ + ix &= 0x7fffffff; + if(ix <= 0x3fe921fb) return __kernel_sin(x,z,0); + + /* sin(Inf or NaN) is NaN */ + else if (ix>=0x7ff00000) return x-x; + + /* argument reduction needed */ + else { + n = __ieee754_rem_pio2(x,y); + switch(n&3) { + case 0: return __kernel_sin(y[0],y[1],1); + case 1: return __kernel_cos(y[0],y[1]); + case 2: return -__kernel_sin(y[0],y[1],1); + default: + return -__kernel_cos(y[0],y[1]); + } + } +} diff --git a/src/dom/js/fdlibm/s_tan.c b/src/dom/js/fdlibm/s_tan.c new file mode 100644 index 000000000..ded36c1d7 --- /dev/null +++ b/src/dom/js/fdlibm/s_tan.c @@ -0,0 +1,112 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)s_tan.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* tan(x) + * Return tangent function of x. + * + * kernel function: + * __kernel_tan ... tangent function on [-pi/4,pi/4] + * __ieee754_rem_pio2 ... argument reduction routine + * + * Method. + * Let S,C and T denote the sin, cos and tan respectively on + * [-PI/4, +PI/4]. Reduce the argument x to y1+y2 = x-k*pi/2 + * in [-pi/4 , +pi/4], and let n = k mod 4. + * We have + * + * n sin(x) cos(x) tan(x) + * ---------------------------------------------------------- + * 0 S C T + * 1 C -S -1/T + * 2 -S -C T + * 3 -C S -1/T + * ---------------------------------------------------------- + * + * Special cases: + * Let trig be any of sin, cos, or tan. + * trig(+-INF) is NaN, with signals; + * trig(NaN) is that NaN; + * + * Accuracy: + * TRIG(x) returns trig(x) nearly rounded + */ + +#include "fdlibm.h" + +#ifdef __STDC__ + double fd_tan(double x) +#else + double fd_tan(x) + double x; +#endif +{ + fd_twoints u; + double y[2],z=0.0; + int n, ix; + + /* High word of x. */ + u.d = x; + ix = __HI(u); + + /* |x| ~< pi/4 */ + ix &= 0x7fffffff; + if(ix <= 0x3fe921fb) return __kernel_tan(x,z,1); + + /* tan(Inf or NaN) is NaN */ + else if (ix>=0x7ff00000) return x-x; /* NaN */ + + /* argument reduction needed */ + else { + n = __ieee754_rem_pio2(x,y); + return __kernel_tan(y[0],y[1],1-((n&1)<<1)); /* 1 -- n even + -1 -- n odd */ + } +} diff --git a/src/dom/js/fdlibm/s_tanh.c b/src/dom/js/fdlibm/s_tanh.c new file mode 100644 index 000000000..aa6809f85 --- /dev/null +++ b/src/dom/js/fdlibm/s_tanh.c @@ -0,0 +1,122 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)s_tanh.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* Tanh(x) + * Return the Hyperbolic Tangent of x + * + * Method : + * x -x + * e - e + * 0. tanh(x) is defined to be ----------- + * x -x + * e + e + * 1. reduce x to non-negative by tanh(-x) = -tanh(x). + * 2. 0 <= x <= 2**-55 : tanh(x) := x*(one+x) + * -t + * 2**-55 < x <= 1 : tanh(x) := -----; t = expm1(-2x) + * t + 2 + * 2 + * 1 <= x <= 22.0 : tanh(x) := 1- ----- ; t=expm1(2x) + * t + 2 + * 22.0 < x <= INF : tanh(x) := 1. + * + * Special cases: + * tanh(NaN) is NaN; + * only tanh(0)=0 is exact for finite argument. + */ + +#include "fdlibm.h" + +#ifdef __STDC__ +static const double one=1.0, two=2.0, tiny = 1.0e-300; +#else +static double one=1.0, two=2.0, tiny = 1.0e-300; +#endif + +#ifdef __STDC__ + double fd_tanh(double x) +#else + double fd_tanh(x) + double x; +#endif +{ + double t,z; + int jx,ix; + fd_twoints u; + + /* High word of |x|. */ + u.d = x; + jx = __HI(u); + ix = jx&0x7fffffff; + + /* x is INF or NaN */ + if(ix>=0x7ff00000) { + if (jx>=0) return one/x+one; /* tanh(+-inf)=+-1 */ + else return one/x-one; /* tanh(NaN) = NaN */ + } + + /* |x| < 22 */ + if (ix < 0x40360000) { /* |x|<22 */ + if (ix<0x3c800000) /* |x|<2**-55 */ + return x*(one+x); /* tanh(small) = small */ + if (ix>=0x3ff00000) { /* |x|>=1 */ + t = fd_expm1(two*fd_fabs(x)); + z = one - two/(t+two); + } else { + t = fd_expm1(-two*fd_fabs(x)); + z= -t/(t+two); + } + /* |x| > 22, return +-1 */ + } else { + z = one - tiny; /* raised inexact flag */ + } + return (jx>=0)? z: -z; +} diff --git a/src/dom/js/fdlibm/w_acos.c b/src/dom/js/fdlibm/w_acos.c new file mode 100644 index 000000000..872c81d20 --- /dev/null +++ b/src/dom/js/fdlibm/w_acos.c @@ -0,0 +1,78 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)w_acos.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * wrap_acos(x) + */ + +#include "fdlibm.h" + + +#ifdef __STDC__ + double fd_acos(double x) /* wrapper acos */ +#else + double fd_acos(x) /* wrapper acos */ + double x; +#endif +{ +#ifdef _IEEE_LIBM + return __ieee754_acos(x); +#else + double z; + z = __ieee754_acos(x); + if(_LIB_VERSION == _IEEE_ || fd_isnan(x)) return z; + if(fd_fabs(x)>1.0) { + int err; + return __kernel_standard(x,x,1,&err); /* acos(|x|>1) */ + } else + return z; +#endif +} diff --git a/src/dom/js/fdlibm/w_acosh.c b/src/dom/js/fdlibm/w_acosh.c new file mode 100644 index 000000000..745d402ea --- /dev/null +++ b/src/dom/js/fdlibm/w_acosh.c @@ -0,0 +1,78 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)w_acosh.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + * + */ + +/* + * wrapper acosh(x) + */ + +#include "fdlibm.h" + +#ifdef __STDC__ + double fd_acosh(double x) /* wrapper acosh */ +#else + double fd_acosh(x) /* wrapper acosh */ + double x; +#endif +{ +#ifdef _IEEE_LIBM + return __ieee754_acosh(x); +#else + double z; + z = __ieee754_acosh(x); + if(_LIB_VERSION == _IEEE_ || fd_isnan(x)) return z; + if(x<1.0) { + int err; + return __kernel_standard(x,x,29,&err); /* acosh(x<1) */ + } else + return z; +#endif +} diff --git a/src/dom/js/fdlibm/w_asin.c b/src/dom/js/fdlibm/w_asin.c new file mode 100644 index 000000000..18aaefde9 --- /dev/null +++ b/src/dom/js/fdlibm/w_asin.c @@ -0,0 +1,80 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)w_asin.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + * + */ + +/* + * wrapper asin(x) + */ + + +#include "fdlibm.h" + + +#ifdef __STDC__ + double fd_asin(double x) /* wrapper asin */ +#else + double fd_asin(x) /* wrapper asin */ + double x; +#endif +{ +#ifdef _IEEE_LIBM + return __ieee754_asin(x); +#else + double z; + z = __ieee754_asin(x); + if(_LIB_VERSION == _IEEE_ || fd_isnan(x)) return z; + if(fd_fabs(x)>1.0) { + int err; + return __kernel_standard(x,x,2,&err); /* asin(|x|>1) */ + } else + return z; +#endif +} diff --git a/src/dom/js/fdlibm/w_atan2.c b/src/dom/js/fdlibm/w_atan2.c new file mode 100644 index 000000000..8cfa4bbbd --- /dev/null +++ b/src/dom/js/fdlibm/w_atan2.c @@ -0,0 +1,79 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)w_atan2.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + * + */ + +/* + * wrapper atan2(y,x) + */ + +#include "fdlibm.h" + + +#ifdef __STDC__ + double fd_atan2(double y, double x) /* wrapper atan2 */ +#else + double fd_atan2(y,x) /* wrapper atan2 */ + double y,x; +#endif +{ +#ifdef _IEEE_LIBM + return __ieee754_atan2(y,x); +#else + double z; + z = __ieee754_atan2(y,x); + if(_LIB_VERSION == _IEEE_||fd_isnan(x)||fd_isnan(y)) return z; + if(x==0.0&&y==0.0) { + int err; + return __kernel_standard(y,x,3,&err); /* atan2(+-0,+-0) */ + } else + return z; +#endif +} diff --git a/src/dom/js/fdlibm/w_atanh.c b/src/dom/js/fdlibm/w_atanh.c new file mode 100644 index 000000000..6ba52d1e2 --- /dev/null +++ b/src/dom/js/fdlibm/w_atanh.c @@ -0,0 +1,81 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)w_atanh.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ +/* + * wrapper atanh(x) + */ + +#include "fdlibm.h" + + +#ifdef __STDC__ + double fd_atanh(double x) /* wrapper atanh */ +#else + double fd_atanh(x) /* wrapper atanh */ + double x; +#endif +{ +#ifdef _IEEE_LIBM + return __ieee754_atanh(x); +#else + double z,y; + z = __ieee754_atanh(x); + if(_LIB_VERSION == _IEEE_ || fd_isnan(x)) return z; + y = fd_fabs(x); + if(y>=1.0) { + int err; + if(y>1.0) + return __kernel_standard(x,x,30,&err); /* atanh(|x|>1) */ + else + return __kernel_standard(x,x,31,&err); /* atanh(|x|==1) */ + } else + return z; +#endif +} diff --git a/src/dom/js/fdlibm/w_cosh.c b/src/dom/js/fdlibm/w_cosh.c new file mode 100644 index 000000000..146449e02 --- /dev/null +++ b/src/dom/js/fdlibm/w_cosh.c @@ -0,0 +1,77 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)w_cosh.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * wrapper cosh(x) + */ + +#include "fdlibm.h" + +#ifdef __STDC__ + double fd_cosh(double x) /* wrapper cosh */ +#else + double fd_cosh(x) /* wrapper cosh */ + double x; +#endif +{ +#ifdef _IEEE_LIBM + return __ieee754_cosh(x); +#else + double z; + z = __ieee754_cosh(x); + if(_LIB_VERSION == _IEEE_ || fd_isnan(x)) return z; + if(fd_fabs(x)>7.10475860073943863426e+02) { + int err; + return __kernel_standard(x,x,5,&err); /* cosh overflow */ + } else + return z; +#endif +} diff --git a/src/dom/js/fdlibm/w_exp.c b/src/dom/js/fdlibm/w_exp.c new file mode 100644 index 000000000..f5dea0b01 --- /dev/null +++ b/src/dom/js/fdlibm/w_exp.c @@ -0,0 +1,88 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)w_exp.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * wrapper exp(x) + */ + +#include "fdlibm.h" + +#ifdef __STDC__ +static const double +#else +static double +#endif +o_threshold= 7.09782712893383973096e+02, /* 0x40862E42, 0xFEFA39EF */ +u_threshold= -7.45133219101941108420e+02; /* 0xc0874910, 0xD52D3051 */ + +#ifdef __STDC__ + double fd_exp(double x) /* wrapper exp */ +#else + double fd_exp(x) /* wrapper exp */ + double x; +#endif +{ +#ifdef _IEEE_LIBM + return __ieee754_exp(x); +#else + double z; + z = __ieee754_exp(x); + if(_LIB_VERSION == _IEEE_) return z; + if(fd_finite(x)) { + int err; + if(x>o_threshold) + return __kernel_standard(x,x,6,&err); /* exp overflow */ + else if(xX_TLOSS) { + int err; + return __kernel_standard(x,x,34,&err); /* j0(|x|>X_TLOSS) */ + } else + return z; +#endif +} + +#ifdef __STDC__ + double y0(double x) /* wrapper y0 */ +#else + double y0(x) /* wrapper y0 */ + double x; +#endif +{ +#ifdef _IEEE_LIBM + return __ieee754_y0(x); +#else + double z; + int err; + z = __ieee754_y0(x); + if(_LIB_VERSION == _IEEE_ || fd_isnan(x) ) return z; + if(x <= 0.0){ + if(x==0.0) + /* d= -one/(x-x); */ + return __kernel_standard(x,x,8,&err); + else + /* d = zero/(x-x); */ + return __kernel_standard(x,x,9,&err); + } + if(x>X_TLOSS) { + return __kernel_standard(x,x,35,&err); /* y0(x>X_TLOSS) */ + } else + return z; +#endif +} diff --git a/src/dom/js/fdlibm/w_j1.c b/src/dom/js/fdlibm/w_j1.c new file mode 100644 index 000000000..86a506bc2 --- /dev/null +++ b/src/dom/js/fdlibm/w_j1.c @@ -0,0 +1,106 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)w_j1.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * wrapper of j1,y1 + */ + +#include "fdlibm.h" + +#ifdef __STDC__ + double fd_j1(double x) /* wrapper j1 */ +#else + double fd_j1(x) /* wrapper j1 */ + double x; +#endif +{ +#ifdef _IEEE_LIBM + return __ieee754_j1(x); +#else + double z; + z = __ieee754_j1(x); + if(_LIB_VERSION == _IEEE_ || fd_isnan(x) ) return z; + if(fd_fabs(x)>X_TLOSS) { + int err; + return __kernel_standard(x,x,36,&err); /* j1(|x|>X_TLOSS) */ + } else + return z; +#endif +} + +#ifdef __STDC__ + double y1(double x) /* wrapper y1 */ +#else + double y1(x) /* wrapper y1 */ + double x; +#endif +{ +#ifdef _IEEE_LIBM + return __ieee754_y1(x); +#else + double z; + int err; + z = __ieee754_y1(x); + if(_LIB_VERSION == _IEEE_ || fd_isnan(x) ) return z; + if(x <= 0.0){ + if(x==0.0) + /* d= -one/(x-x); */ + return __kernel_standard(x,x,10,&err); + else + /* d = zero/(x-x); */ + return __kernel_standard(x,x,11,&err); + } + if(x>X_TLOSS) { + return __kernel_standard(x,x,37,&err); /* y1(x>X_TLOSS) */ + } else + return z; +#endif +} diff --git a/src/dom/js/fdlibm/w_jn.c b/src/dom/js/fdlibm/w_jn.c new file mode 100644 index 000000000..6926b0da8 --- /dev/null +++ b/src/dom/js/fdlibm/w_jn.c @@ -0,0 +1,128 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)w_jn.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * wrapper jn(int n, double x), yn(int n, double x) + * floating point Bessel's function of the 1st and 2nd kind + * of order n + * + * Special cases: + * y0(0)=y1(0)=yn(n,0) = -inf with division by zero signal; + * y0(-ve)=y1(-ve)=yn(n,-ve) are NaN with invalid signal. + * Note 2. About jn(n,x), yn(n,x) + * For n=0, j0(x) is called, + * for n=1, j1(x) is called, + * for nx, a continued fraction approximation to + * j(n,x)/j(n-1,x) is evaluated and then backward + * recursion is used starting from a supposed value + * for j(n,x). The resulting value of j(0,x) is + * compared with the actual value to correct the + * supposed value of j(n,x). + * + * yn(n,x) is similar in all respects, except + * that forward recursion is used for all + * values of n>1. + * + */ + +#include "fdlibm.h" + +#ifdef __STDC__ + double fd_jn(int n, double x) /* wrapper jn */ +#else + double fd_jn(n,x) /* wrapper jn */ + double x; int n; +#endif +{ +#ifdef _IEEE_LIBM + return __ieee754_jn(n,x); +#else + double z; + z = __ieee754_jn(n,x); + if(_LIB_VERSION == _IEEE_ || fd_isnan(x) ) return z; + if(fd_fabs(x)>X_TLOSS) { + int err; + return __kernel_standard((double)n,x,38,&err); /* jn(|x|>X_TLOSS,n) */ + } else + return z; +#endif +} + +#ifdef __STDC__ + double yn(int n, double x) /* wrapper yn */ +#else + double yn(n,x) /* wrapper yn */ + double x; int n; +#endif +{ +#ifdef _IEEE_LIBM + return __ieee754_yn(n,x); +#else + double z; + int err; + z = __ieee754_yn(n,x); + if(_LIB_VERSION == _IEEE_ || fd_isnan(x) ) return z; + if(x <= 0.0){ + if(x==0.0) + /* d= -one/(x-x); */ + return __kernel_standard((double)n,x,12,&err); + else + /* d = zero/(x-x); */ + return __kernel_standard((double)n,x,13,&err); + } + if(x>X_TLOSS) { + return __kernel_standard((double)n,x,39,&err); /* yn(x>X_TLOSS,n) */ + } else + return z; +#endif +} diff --git a/src/dom/js/fdlibm/w_lgamma.c b/src/dom/js/fdlibm/w_lgamma.c new file mode 100644 index 000000000..f7576e892 --- /dev/null +++ b/src/dom/js/fdlibm/w_lgamma.c @@ -0,0 +1,85 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)w_lgamma.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + * + */ + +/* double lgamma(double x) + * Return the logarithm of the Gamma function of x. + * + * Method: call __ieee754_lgamma_r + */ + +#include "fdlibm.h" + +extern int signgam; + +#ifdef __STDC__ + double fd_lgamma(double x) +#else + double fd_lgamma(x) + double x; +#endif +{ +#ifdef _IEEE_LIBM + return __ieee754_lgamma_r(x,&signgam); +#else + double y; + y = __ieee754_lgamma_r(x,&signgam); + if(_LIB_VERSION == _IEEE_) return y; + if(!fd_finite(y)&&fd_finite(x)) { + int err; + if(fd_floor(x)==x&&x<=0.0) + return __kernel_standard(x,x,15,&err); /* lgamma pole */ + else + return __kernel_standard(x,x,14,&err); /* lgamma overflow */ + } else + return y; +#endif +} diff --git a/src/dom/js/fdlibm/w_lgamma_r.c b/src/dom/js/fdlibm/w_lgamma_r.c new file mode 100644 index 000000000..ba2ad5933 --- /dev/null +++ b/src/dom/js/fdlibm/w_lgamma_r.c @@ -0,0 +1,81 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)w_lgamma_r.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * wrapper double lgamma_r(double x, int *signgamp) + */ + +#include "fdlibm.h" + + +#ifdef __STDC__ + double fd_lgamma_r(double x, int *signgamp) /* wrapper lgamma_r */ +#else + double fd_lgamma_r(x,signgamp) /* wrapper lgamma_r */ + double x; int *signgamp; +#endif +{ +#ifdef _IEEE_LIBM + return __ieee754_lgamma_r(x,signgamp); +#else + double y; + y = __ieee754_lgamma_r(x,signgamp); + if(_LIB_VERSION == _IEEE_) return y; + if(!fd_finite(y)&&fd_finite(x)) { + int err; + if(fd_floor(x)==x&&x<=0.0) + return __kernel_standard(x,x,15,&err); /* lgamma pole */ + else + return __kernel_standard(x,x,14,&err); /* lgamma overflow */ + } else + return y; +#endif +} diff --git a/src/dom/js/fdlibm/w_log.c b/src/dom/js/fdlibm/w_log.c new file mode 100644 index 000000000..7e358fcf1 --- /dev/null +++ b/src/dom/js/fdlibm/w_log.c @@ -0,0 +1,78 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)w_log.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * wrapper log(x) + */ + +#include "fdlibm.h" + + +#ifdef __STDC__ + double fd_log(double x) /* wrapper log */ +#else + double fd_log(x) /* wrapper log */ + double x; +#endif +{ +#ifdef _IEEE_LIBM + return __ieee754_log(x); +#else + double z; + int err; + z = __ieee754_log(x); + if(_LIB_VERSION == _IEEE_ || fd_isnan(x) || x > 0.0) return z; + if(x==0.0) + return __kernel_standard(x,x,16,&err); /* log(0) */ + else + return __kernel_standard(x,x,17,&err); /* log(x<0) */ +#endif +} diff --git a/src/dom/js/fdlibm/w_log10.c b/src/dom/js/fdlibm/w_log10.c new file mode 100644 index 000000000..6b298b236 --- /dev/null +++ b/src/dom/js/fdlibm/w_log10.c @@ -0,0 +1,81 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)w_log10.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * wrapper log10(X) + */ + +#include "fdlibm.h" + + +#ifdef __STDC__ + double fd_log10(double x) /* wrapper log10 */ +#else + double fd_log10(x) /* wrapper log10 */ + double x; +#endif +{ +#ifdef _IEEE_LIBM + return __ieee754_log10(x); +#else + double z; + z = __ieee754_log10(x); + if(_LIB_VERSION == _IEEE_ || fd_isnan(x)) return z; + if(x<=0.0) { + int err; + if(x==0.0) + return __kernel_standard(x,x,18,&err); /* log10(0) */ + else + return __kernel_standard(x,x,19,&err); /* log10(x<0) */ + } else + return z; +#endif +} diff --git a/src/dom/js/fdlibm/w_pow.c b/src/dom/js/fdlibm/w_pow.c new file mode 100644 index 000000000..3d2c15ad3 --- /dev/null +++ b/src/dom/js/fdlibm/w_pow.c @@ -0,0 +1,99 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + + +/* @(#)w_pow.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * wrapper pow(x,y) return x**y + */ + +#include "fdlibm.h" + + +#ifdef __STDC__ + double fd_pow(double x, double y) /* wrapper pow */ +#else + double fd_pow(x,y) /* wrapper pow */ + double x,y; +#endif +{ +#ifdef _IEEE_LIBM + return __ieee754_pow(x,y); +#else + double z; + int err; + z=__ieee754_pow(x,y); + if(_LIB_VERSION == _IEEE_|| fd_isnan(y)) return z; + if(fd_isnan(x)) { + if(y==0.0) + return __kernel_standard(x,y,42,&err); /* pow(NaN,0.0) */ + else + return z; + } + if(x==0.0){ + if(y==0.0) + return __kernel_standard(x,y,20,&err); /* pow(0.0,0.0) */ + if(fd_finite(y)&&y<0.0) + return __kernel_standard(x,y,23,&err); /* pow(0.0,negative) */ + return z; + } + if(!fd_finite(z)) { + if(fd_finite(x)&&fd_finite(y)) { + if(fd_isnan(z)) + return __kernel_standard(x,y,24,&err); /* pow neg**non-int */ + else + return __kernel_standard(x,y,21,&err); /* pow overflow */ + } + } + if(z==0.0&&fd_finite(x)&&fd_finite(y)) + return __kernel_standard(x,y,22,&err); /* pow underflow */ + return z; +#endif +} diff --git a/src/dom/js/fdlibm/w_remainder.c b/src/dom/js/fdlibm/w_remainder.c new file mode 100644 index 000000000..25d1ba171 --- /dev/null +++ b/src/dom/js/fdlibm/w_remainder.c @@ -0,0 +1,77 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)w_remainder.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * wrapper remainder(x,p) + */ + +#include "fdlibm.h" + +#ifdef __STDC__ + double fd_remainder(double x, double y) /* wrapper remainder */ +#else + double fd_remainder(x,y) /* wrapper remainder */ + double x,y; +#endif +{ +#ifdef _IEEE_LIBM + return __ieee754_remainder(x,y); +#else + double z; + z = __ieee754_remainder(x,y); + if(_LIB_VERSION == _IEEE_ || fd_isnan(y)) return z; + if(y==0.0) { + int err; + return __kernel_standard(x,y,28,&err); /* remainder(x,0) */ + } else + return z; +#endif +} diff --git a/src/dom/js/fdlibm/w_scalb.c b/src/dom/js/fdlibm/w_scalb.c new file mode 100644 index 000000000..35c16a500 --- /dev/null +++ b/src/dom/js/fdlibm/w_scalb.c @@ -0,0 +1,95 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)w_scalb.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * wrapper scalb(double x, double fn) is provide for + * passing various standard test suite. One + * should use scalbn() instead. + */ + +#include "fdlibm.h" + +#include + +#ifdef __STDC__ +#ifdef _SCALB_INT + double fd_scalb(double x, int fn) /* wrapper scalb */ +#else + double fd_scalb(double x, double fn) /* wrapper scalb */ +#endif +#else + double fd_scalb(x,fn) /* wrapper scalb */ +#ifdef _SCALB_INT + double x; int fn; +#else + double x,fn; +#endif +#endif +{ +#ifdef _IEEE_LIBM + return __ieee754_scalb(x,fn); +#else + double z; + int err; + z = __ieee754_scalb(x,fn); + if(_LIB_VERSION == _IEEE_) return z; + if(!(fd_finite(z)||fd_isnan(z))&&fd_finite(x)) { + return __kernel_standard(x,(double)fn,32,&err); /* scalb overflow */ + } + if(z==0.0&&z!=x) { + return __kernel_standard(x,(double)fn,33,&err); /* scalb underflow */ + } +#ifndef _SCALB_INT + if(!fd_finite(fn)) errno = ERANGE; +#endif + return z; +#endif +} diff --git a/src/dom/js/fdlibm/w_sinh.c b/src/dom/js/fdlibm/w_sinh.c new file mode 100644 index 000000000..8b04ecb7f --- /dev/null +++ b/src/dom/js/fdlibm/w_sinh.c @@ -0,0 +1,77 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)w_sinh.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * wrapper sinh(x) + */ + +#include "fdlibm.h" + +#ifdef __STDC__ + double fd_sinh(double x) /* wrapper sinh */ +#else + double fd_sinh(x) /* wrapper sinh */ + double x; +#endif +{ +#ifdef _IEEE_LIBM + return __ieee754_sinh(x); +#else + double z; + z = __ieee754_sinh(x); + if(_LIB_VERSION == _IEEE_) return z; + if(!fd_finite(z)&&fd_finite(x)) { + int err; + return __kernel_standard(x,x,25,&err); /* sinh overflow */ + } else + return z; +#endif +} diff --git a/src/dom/js/fdlibm/w_sqrt.c b/src/dom/js/fdlibm/w_sqrt.c new file mode 100644 index 000000000..462d776f8 --- /dev/null +++ b/src/dom/js/fdlibm/w_sqrt.c @@ -0,0 +1,77 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* @(#)w_sqrt.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * wrapper sqrt(x) + */ + +#include "fdlibm.h" + +#ifdef __STDC__ + double fd_sqrt(double x) /* wrapper sqrt */ +#else + double fd_sqrt(x) /* wrapper sqrt */ + double x; +#endif +{ +#ifdef _IEEE_LIBM + return __ieee754_sqrt(x); +#else + double z; + z = __ieee754_sqrt(x); + if(_LIB_VERSION == _IEEE_ || fd_isnan(x)) return z; + if(x<0.0) { + int err; + return __kernel_standard(x,x,26,&err); /* sqrt(negative) */ + } else + return z; +#endif +} diff --git a/src/dom/js/js.c b/src/dom/js/js.c new file mode 100644 index 000000000..5bd296223 --- /dev/null +++ b/src/dom/js/js.c @@ -0,0 +1,2333 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS shell. + */ +#include "jsstddef.h" +#include +#include +#include +#include +#include "jstypes.h" +#include "jsarena.h" +#include "jsutil.h" +#include "jsprf.h" +#include "jsapi.h" +#include "jsatom.h" +#include "jscntxt.h" +#include "jsdbgapi.h" +#include "jsemit.h" +#include "jsfun.h" +#include "jsgc.h" +#include "jslock.h" +#include "jsobj.h" +#include "jsparse.h" +#include "jsscope.h" +#include "jsscript.h" + +#ifdef PERLCONNECT +#include "perlconnect/jsperl.h" +#endif + +#ifdef LIVECONNECT +#include "jsjava.h" +#endif + +#ifdef JSDEBUGGER +#include "jsdebug.h" +#ifdef JSDEBUGGER_JAVA_UI +#include "jsdjava.h" +#endif /* JSDEBUGGER_JAVA_UI */ +#ifdef JSDEBUGGER_C_UI +#include "jsdb.h" +#endif /* JSDEBUGGER_C_UI */ +#endif /* JSDEBUGGER */ + +#ifdef XP_UNIX +#include +#include +#include +#include +#endif + +#if defined(XP_WIN) || defined(XP_OS2) +#include /* for isatty() */ +#endif + +#define EXITCODE_RUNTIME_ERROR 3 +#define EXITCODE_FILE_NOT_FOUND 4 + +size_t gStackChunkSize = 8192; +static size_t gMaxStackSize = 0; +static jsuword gStackBase; +int gExitCode = 0; +JSBool gQuitting = JS_FALSE; +FILE *gErrFile = NULL; +FILE *gOutFile = NULL; + +#ifdef XP_MAC +#if defined(MAC_TEST_HACK) || defined(XP_MAC_MPW) +/* this is the data file that all Print strings will be echoed into */ +FILE *gTestResultFile = NULL; +#define isatty(f) 0 +#else +#define isatty(f) 1 +#endif + +char *strdup(const char *str) +{ + char *copy = (char *) malloc(strlen(str)+1); + if (copy) + strcpy(copy, str); + return copy; +} + +#ifdef XP_MAC_MPW +/* Macintosh MPW replacements for the ANSI routines. These translate LF's to CR's because + the MPW libraries supplied by Metrowerks don't do that for some reason. */ +static void translateLFtoCR(char *str, int length) +{ + char *limit = str + length; + while (str != limit) { + if (*str == '\n') + *str = '\r'; + str++; + } +} + +int fputc(int c, FILE *file) +{ + char buffer = c; + if (buffer == '\n') + buffer = '\r'; + return fwrite(&buffer, 1, 1, file); +} + +int fputs(const char *s, FILE *file) +{ + char buffer[4096]; + int n = strlen(s); + int extra = 0; + + while (n > sizeof buffer) { + memcpy(buffer, s, sizeof buffer); + translateLFtoCR(buffer, sizeof buffer); + extra += fwrite(buffer, 1, sizeof buffer, file); + n -= sizeof buffer; + s += sizeof buffer; + } + memcpy(buffer, s, n); + translateLFtoCR(buffer, n); + return extra + fwrite(buffer, 1, n, file); +} + +int fprintf(FILE* file, const char *format, ...) +{ + va_list args; + char smallBuffer[4096]; + int n; + int bufferSize = sizeof smallBuffer; + char *buffer = smallBuffer; + int result; + + va_start(args, format); + n = vsnprintf(buffer, bufferSize, format, args); + va_end(args); + while (n < 0) { + if (buffer != smallBuffer) + free(buffer); + bufferSize <<= 1; + buffer = malloc(bufferSize); + if (!buffer) { + JS_ASSERT(JS_FALSE); + return 0; + } + va_start(args, format); + n = vsnprintf(buffer, bufferSize, format, args); + va_end(args); + } + translateLFtoCR(buffer, n); + result = fwrite(buffer, 1, n, file); + if (buffer != smallBuffer) + free(buffer); + return result; +} + + +#else +#include +#include + +static char* mac_argv[] = { "js", NULL }; + +static void initConsole(StringPtr consoleName, const char* startupMessage, int *argc, char** *argv) +{ + SIOUXSettings.autocloseonquit = true; + SIOUXSettings.asktosaveonclose = false; + /* SIOUXSettings.initializeTB = false; + SIOUXSettings.showstatusline = true;*/ + puts(startupMessage); + SIOUXSetTitle(consoleName); + + /* set up a buffer for stderr (otherwise it's a pig). */ + setvbuf(stderr, (char *) malloc(BUFSIZ), _IOLBF, BUFSIZ); + + *argc = 1; + *argv = mac_argv; +} + +#ifdef LIVECONNECT +/* Little hack to provide a default CLASSPATH on the Mac. */ +#define getenv(var) mac_getenv(var) +static char* mac_getenv(const char* var) +{ + if (strcmp(var, "CLASSPATH") == 0) { + static char class_path[] = "liveconnect.jar"; + return class_path; + } + return NULL; +} +#endif /* LIVECONNECT */ + +#endif +#endif + +#ifdef JSDEBUGGER +static JSDContext *_jsdc; +#ifdef JSDEBUGGER_JAVA_UI +static JSDJContext *_jsdjc; +#endif /* JSDEBUGGER_JAVA_UI */ +#endif /* JSDEBUGGER */ + +static JSBool reportWarnings = JS_TRUE; + +typedef enum JSShellErrNum { +#define MSG_DEF(name, number, count, exception, format) \ + name = number, +#include "jsshell.msg" +#undef MSG_DEF + JSShellErr_Limit +#undef MSGDEF +} JSShellErrNum; + +static const JSErrorFormatString * +my_GetErrorMessage(void *userRef, const char *locale, const uintN errorNumber); + +#ifdef EDITLINE +extern char *readline(const char *prompt); +extern void add_history(char *line); +#endif + +static JSBool +GetLine(JSContext *cx, char *bufp, FILE *file, const char *prompt) { +#ifdef EDITLINE + /* + * Use readline only if file is stdin, because there's no way to specify + * another handle. Are other filehandles interactive? + */ + if (file == stdin) { + char *linep = readline(prompt); + if (!linep) + return JS_FALSE; + if (linep[0] != '\0') + add_history(linep); + strcpy(bufp, linep); + JS_free(cx, linep); + bufp += strlen(bufp); + *bufp++ = '\n'; + *bufp = '\0'; + } else +#endif + { + char line[256]; + fprintf(gOutFile, prompt); + fflush(gOutFile); +#ifdef XP_MAC_MPW + /* Print a CR after the prompt because MPW grabs the entire line when entering an interactive command */ + fputc('\n', gOutFile); +#endif + if (!fgets(line, sizeof line, file)) + return JS_FALSE; + strcpy(bufp, line); + } + return JS_TRUE; +} + +static void +Process(JSContext *cx, JSObject *obj, char *filename) +{ + JSBool ok, hitEOF; + JSScript *script; + jsval result; + JSString *str; + char buffer[4096]; + char *bufp; + int lineno; + int startline; + FILE *file; + jsuword stackLimit; + + if (!filename || strcmp(filename, "-") == 0) { + file = stdin; + } else { + file = fopen(filename, "r"); + if (!file) { + JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, + JSSMSG_CANT_OPEN, filename, strerror(errno)); + gExitCode = EXITCODE_FILE_NOT_FOUND; + return; + } + } + + if (gMaxStackSize == 0) { + /* + * Disable checking for stack overflow if limit is zero. + */ + stackLimit = 0; + } else { +#if JS_STACK_GROWTH_DIRECTION > 0 + stackLimit = gStackBase + gMaxStackSize; +#else + stackLimit = gStackBase - gMaxStackSize; +#endif + } + JS_SetThreadStackLimit(cx, stackLimit); + + if (!isatty(fileno(file))) { + /* + * It's not interactive - just execute it. + * + * Support the UNIX #! shell hack; gobble the first line if it starts + * with '#'. TODO - this isn't quite compatible with sharp variables, + * as a legal js program (using sharp variables) might start with '#'. + * But that would require multi-character lookahead. + */ + int ch = fgetc(file); + if (ch == '#') { + while((ch = fgetc(file)) != EOF) { + if (ch == '\n' || ch == '\r') + break; + } + } + ungetc(ch, file); + script = JS_CompileFileHandle(cx, obj, filename, file); + if (script) { + (void)JS_ExecuteScript(cx, obj, script, &result); + JS_DestroyScript(cx, script); + } + return; + } + + /* It's an interactive filehandle; drop into read-eval-print loop. */ + lineno = 1; + hitEOF = JS_FALSE; + do { + bufp = buffer; + *bufp = '\0'; + + /* + * Accumulate lines until we get a 'compilable unit' - one that either + * generates an error (before running out of source) or that compiles + * cleanly. This should be whenever we get a complete statement that + * coincides with the end of a line. + */ + startline = lineno; + do { + if (!GetLine(cx, bufp, file, startline == lineno ? "js> " : "")) { + hitEOF = JS_TRUE; + break; + } + bufp += strlen(bufp); + lineno++; + } while (!JS_BufferIsCompilableUnit(cx, obj, buffer, strlen(buffer))); + + /* Clear any pending exception from previous failed compiles. */ + JS_ClearPendingException(cx); + script = JS_CompileScript(cx, obj, buffer, strlen(buffer), +#ifdef JSDEBUGGER + "typein", +#else + NULL, +#endif + startline); + if (script) { + ok = JS_ExecuteScript(cx, obj, script, &result); + if (ok && result != JSVAL_VOID) { + str = JS_ValueToString(cx, result); + if (str) + fprintf(gOutFile, "%s\n", JS_GetStringBytes(str)); + else + ok = JS_FALSE; + } + JS_DestroyScript(cx, script); + } + } while (!hitEOF && !gQuitting); + fprintf(gOutFile, "\n"); + return; +} + +static int +usage(void) +{ + fprintf(gErrFile, "%s\n", JS_GetImplementationVersion()); + fprintf(gErrFile, "usage: js [-PswW] [-b branchlimit] [-c stackchunksize] [-v version] [-f scriptfile] [-S maxstacksize] [scriptfile] [scriptarg...]\n"); + return 2; +} + +static uint32 gBranchCount; +static uint32 gBranchLimit; + +static JSBool +my_BranchCallback(JSContext *cx, JSScript *script) +{ + if (++gBranchCount == gBranchLimit) { + if (script->filename) + fprintf(gErrFile, "%s:", script->filename); + fprintf(gErrFile, "%u: script branches too much (%u callbacks)\n", + script->lineno, gBranchLimit); + gBranchCount = 0; + return JS_FALSE; + } + return JS_TRUE; +} + +extern JSClass global_class; + +static int +ProcessArgs(JSContext *cx, JSObject *obj, char **argv, int argc) +{ + int i, j, length; + JSObject *argsObj; + char *filename = NULL; + JSBool isInteractive = JS_TRUE; + + /* + * Scan past all optional arguments so we can create the arguments object + * before processing any -f options, which must interleave properly with + * -v and -w options. This requires two passes, and without getopt, we'll + * have to keep the option logic here and in the second for loop in sync. + */ + for (i = 0; i < argc; i++) { + if (argv[i][0] != '-' || argv[i][1] == '\0') { + ++i; + break; + } + switch (argv[i][1]) { + case 'b': + case 'c': + case 'f': + case 'v': + case 'S': + ++i; + break; + } + } + + /* + * Create arguments early and define it to root it, so it's safe from any + * GC calls nested below, and so it is available to -f arguments. + */ + argsObj = JS_NewArrayObject(cx, 0, NULL); + if (!argsObj) + return 1; + if (!JS_DefineProperty(cx, obj, "arguments", OBJECT_TO_JSVAL(argsObj), + NULL, NULL, 0)) { + return 1; + } + + length = argc - i; + for (j = 0; j < length; j++) { + JSString *str = JS_NewStringCopyZ(cx, argv[i++]); + if (!str) + return 1; + if (!JS_DefineElement(cx, argsObj, j, STRING_TO_JSVAL(str), + NULL, NULL, JSPROP_ENUMERATE)) { + return 1; + } + } + + for (i = 0; i < argc; i++) { + if (argv[i][0] != '-' || argv[i][1] == '\0') { + filename = argv[i++]; + isInteractive = JS_FALSE; + break; + } + + switch (argv[i][1]) { + case 'v': + if (++i == argc) { + return usage(); + } + JS_SetVersion(cx, (JSVersion) atoi(argv[i])); + break; + + case 'w': + reportWarnings = JS_TRUE; + break; + + case 'W': + reportWarnings = JS_FALSE; + break; + + case 's': + JS_ToggleOptions(cx, JSOPTION_STRICT); + break; + + case 'P': + if (JS_GET_CLASS(cx, JS_GetPrototype(cx, obj)) != &global_class) { + JSObject *gobj; + + if (!JS_SealObject(cx, obj, JS_TRUE)) + return JS_FALSE; + gobj = JS_NewObject(cx, &global_class, NULL, NULL); + if (!gobj) + return JS_FALSE; + if (!JS_SetPrototype(cx, gobj, obj)) + return JS_FALSE; + JS_SetParent(cx, gobj, NULL); + JS_SetGlobalObject(cx, gobj); + obj = gobj; + } + break; + + case 'b': + gBranchLimit = atoi(argv[++i]); + JS_SetBranchCallback(cx, my_BranchCallback); + break; + + case 'c': + /* set stack chunk size */ + gStackChunkSize = atoi(argv[++i]); + break; + + case 'f': + if (++i == argc) { + return usage(); + } + Process(cx, obj, argv[i]); + /* + * XXX: js -f foo.js should interpret foo.js and then + * drop into interactive mode, but that breaks test + * harness. Just execute foo.js for now. + */ + isInteractive = JS_FALSE; + break; + + case 'S': + if (++i == argc) { + return usage(); + } + /* Set maximum stack size. */ + gMaxStackSize = atoi(argv[i]); + break; + + default: + return usage(); + } + } + + if (filename || isInteractive) + Process(cx, obj, filename); + return gExitCode; +} + + +static JSBool +Version(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + if (argc > 0 && JSVAL_IS_INT(argv[0])) + *rval = INT_TO_JSVAL(JS_SetVersion(cx, (JSVersion) JSVAL_TO_INT(argv[0]))); + else + *rval = INT_TO_JSVAL(JS_GetVersion(cx)); + return JS_TRUE; +} + +static struct { + const char *name; + uint32 flag; +} js_options[] = { + {"strict", JSOPTION_STRICT}, + {"werror", JSOPTION_WERROR}, + {0, 0} +}; + +static JSBool +Options(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + uint32 optset, flag; + uintN i, j, found; + JSString *str; + const char *opt; + char *names; + + optset = 0; + for (i = 0; i < argc; i++) { + str = JS_ValueToString(cx, argv[i]); + if (!str) + return JS_FALSE; + opt = JS_GetStringBytes(str); + for (j = 0; js_options[j].name; j++) { + if (strcmp(js_options[j].name, opt) == 0) { + optset |= js_options[j].flag; + break; + } + } + } + optset = JS_ToggleOptions(cx, optset); + + names = NULL; + found = 0; + while (optset != 0) { + flag = optset; + optset &= optset - 1; + flag &= ~optset; + for (j = 0; js_options[j].name; j++) { + if (js_options[j].flag == flag) { + names = JS_sprintf_append(names, "%s%s", + names ? "," : "", js_options[j].name); + found++; + break; + } + } + } + if (!found) + names = strdup(""); + if (!names) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + + str = JS_NewString(cx, names, strlen(names)); + if (!str) { + free(names); + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +static void +my_LoadErrorReporter(JSContext *cx, const char *message, JSErrorReport *report); + +static void +my_ErrorReporter(JSContext *cx, const char *message, JSErrorReport *report); + +static JSBool +Load(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + uintN i; + JSString *str; + const char *filename; + JSScript *script; + JSBool ok; + jsval result; + JSErrorReporter older; + + for (i = 0; i < argc; i++) { + str = JS_ValueToString(cx, argv[i]); + if (!str) + return JS_FALSE; + argv[i] = STRING_TO_JSVAL(str); + filename = JS_GetStringBytes(str); + errno = 0; + older = JS_SetErrorReporter(cx, my_LoadErrorReporter); + script = JS_CompileFile(cx, obj, filename); + if (!script) + ok = JS_FALSE; + else { + ok = JS_ExecuteScript(cx, obj, script, &result); + JS_DestroyScript(cx, script); + } + JS_SetErrorReporter(cx, older); + if (!ok) + return JS_FALSE; + } + + return JS_TRUE; +} + +static JSBool +Print(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + uintN i, n; + JSString *str; + + for (i = n = 0; i < argc; i++) { + str = JS_ValueToString(cx, argv[i]); + if (!str) + return JS_FALSE; + fprintf(gOutFile, "%s%s", i ? " " : "", JS_GetStringBytes(str)); + } + n++; + if (n) + fputc('\n', gOutFile); + return JS_TRUE; +} + +static JSBool +Help(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval); + +static JSBool +Quit(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ +#ifdef LIVECONNECT + JSJ_SimpleShutdown(); +#endif + + JS_ConvertArguments(cx, argc, argv,"/ i", &gExitCode); + + gQuitting = JS_TRUE; + return JS_FALSE; +} + +#ifdef GC_MARK_DEBUG +extern JS_FRIEND_DATA(FILE *) js_DumpGCHeap; +#endif + +static JSBool +GC(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSRuntime *rt; + uint32 preBytes; + + rt = cx->runtime; + preBytes = rt->gcBytes; +#ifdef GC_MARK_DEBUG + if (argc && JSVAL_IS_STRING(argv[0])) { + char *name = JS_GetStringBytes(JSVAL_TO_STRING(argv[0])); + FILE *file = fopen(name, "w"); + if (!file) { + fprintf(gErrFile, "gc: can't open %s: %s\n", strerror(errno)); + return JS_FALSE; + } + js_DumpGCHeap = file; + } else { + js_DumpGCHeap = stdout; + } +#endif + JS_GC(cx); +#ifdef GC_MARK_DEBUG + if (js_DumpGCHeap != stdout) + fclose(js_DumpGCHeap); + js_DumpGCHeap = NULL; +#endif + fprintf(gOutFile, "before %lu, after %lu, break %08lx\n", + (unsigned long)preBytes, (unsigned long)rt->gcBytes, +#ifdef XP_UNIX + (unsigned long)sbrk(0) +#else + 0 +#endif + ); +#ifdef JS_GCMETER + js_DumpGCStats(rt, stdout); +#endif + return JS_TRUE; +} + +static JSScript * +ValueToScript(JSContext *cx, jsval v) +{ + JSScript *script; + JSFunction *fun; + + if (JSVAL_IS_OBJECT(v) && + JS_GET_CLASS(cx, JSVAL_TO_OBJECT(v)) == &js_ScriptClass) { + script = (JSScript *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(v)); + } else { + fun = JS_ValueToFunction(cx, v); + if (!fun) + return NULL; + script = fun->script; + } + return script; +} + +static JSBool +GetTrapArgs(JSContext *cx, uintN argc, jsval *argv, JSScript **scriptp, + int32 *ip) +{ + uintN intarg; + JSScript *script; + + *scriptp = cx->fp->down->script; + *ip = 0; + if (argc != 0) { + intarg = 0; + if (JS_TypeOfValue(cx, argv[0]) == JSTYPE_FUNCTION) { + script = ValueToScript(cx, argv[0]); + if (!script) + return JS_FALSE; + *scriptp = script; + intarg++; + } + if (argc > intarg) { + if (!JS_ValueToInt32(cx, argv[intarg], ip)) + return JS_FALSE; + } + } + return JS_TRUE; +} + +static JSTrapStatus +TrapHandler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, + void *closure) +{ + JSString *str; + JSStackFrame *caller; + + str = (JSString *) closure; + caller = JS_GetScriptedCaller(cx, NULL); + if (!JS_EvaluateScript(cx, caller->scopeChain, + JS_GetStringBytes(str), JS_GetStringLength(str), + caller->script->filename, caller->script->lineno, + rval)) { + return JSTRAP_ERROR; + } + if (*rval != JSVAL_VOID) + return JSTRAP_RETURN; + return JSTRAP_CONTINUE; +} + +static JSBool +Trap(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str; + JSScript *script; + int32 i; + + if (argc == 0) { + JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_TRAP_USAGE); + return JS_FALSE; + } + argc--; + str = JS_ValueToString(cx, argv[argc]); + if (!str) + return JS_FALSE; + argv[argc] = STRING_TO_JSVAL(str); + if (!GetTrapArgs(cx, argc, argv, &script, &i)) + return JS_FALSE; + return JS_SetTrap(cx, script, script->code + i, TrapHandler, str); +} + +static JSBool +Untrap(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSScript *script; + int32 i; + + if (!GetTrapArgs(cx, argc, argv, &script, &i)) + return JS_FALSE; + JS_ClearTrap(cx, script, script->code + i, NULL, NULL); + return JS_TRUE; +} + +static JSBool +LineToPC(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSScript *script; + int32 i; + uintN lineno; + jsbytecode *pc; + + if (argc == 0) { + JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_LINE2PC_USAGE); + return JS_FALSE; + } + script = cx->fp->down->script; + if (!GetTrapArgs(cx, argc, argv, &script, &i)) + return JS_FALSE; + lineno = (i == 0) ? script->lineno : (uintN)i; + pc = JS_LineNumberToPC(cx, script, lineno); + if (!pc) + return JS_FALSE; + *rval = INT_TO_JSVAL(PTRDIFF(pc, script->code, jsbytecode)); + return JS_TRUE; +} + +static JSBool +PCToLine(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSScript *script; + int32 i; + uintN lineno; + + if (!GetTrapArgs(cx, argc, argv, &script, &i)) + return JS_FALSE; + lineno = JS_PCToLineNumber(cx, script, script->code + i); + if (!lineno) + return JS_FALSE; + *rval = INT_TO_JSVAL(lineno); + return JS_TRUE; +} + +#ifdef DEBUG + +static void +SrcNotes(JSContext *cx, JSScript *script) +{ + uintN offset, delta, caseOff; + jssrcnote *notes, *sn; + JSSrcNoteType type; + jsatomid atomIndex; + JSAtom *atom; + + fprintf(gOutFile, "\nSource notes:\n"); + offset = 0; + notes = SCRIPT_NOTES(script); + for (sn = notes; !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) { + delta = SN_DELTA(sn); + offset += delta; + fprintf(gOutFile, "%3u: %5u [%4u] %-8s", + PTRDIFF(sn, notes, jssrcnote), offset, delta, + js_SrcNoteSpec[SN_TYPE(sn)].name); + type = (JSSrcNoteType) SN_TYPE(sn); + switch (type) { + case SRC_SETLINE: + fprintf(gOutFile, " lineno %u", (uintN) js_GetSrcNoteOffset(sn, 0)); + break; + case SRC_FOR: + fprintf(gOutFile, " cond %u update %u tail %u", + (uintN) js_GetSrcNoteOffset(sn, 0), + (uintN) js_GetSrcNoteOffset(sn, 1), + (uintN) js_GetSrcNoteOffset(sn, 2)); + break; + case SRC_COND: + case SRC_IF_ELSE: + case SRC_WHILE: + case SRC_PCBASE: + case SRC_PCDELTA: + fprintf(gOutFile, " offset %u", (uintN) js_GetSrcNoteOffset(sn, 0)); + break; + case SRC_LABEL: + case SRC_LABELBRACE: + case SRC_BREAK2LABEL: + case SRC_CONT2LABEL: + case SRC_FUNCDEF: { + const char *bytes; + JSFunction *fun; + JSString *str; + + atomIndex = (jsatomid) js_GetSrcNoteOffset(sn, 0); + atom = js_GetAtom(cx, &script->atomMap, atomIndex); + if (type != SRC_FUNCDEF) { + bytes = js_AtomToPrintableString(cx, atom); + } else { + fun = (JSFunction *) + JS_GetPrivate(cx, ATOM_TO_OBJECT(atom)); + str = JS_DecompileFunction(cx, fun, JS_DONT_PRETTY_PRINT); + bytes = str ? JS_GetStringBytes(str) : "N/A"; + } + fprintf(gOutFile, " atom %u (%s)", (uintN)atomIndex, bytes); + break; + } + case SRC_SWITCH: + fprintf(gOutFile, " length %u", (uintN) js_GetSrcNoteOffset(sn, 0)); + caseOff = (uintN) js_GetSrcNoteOffset(sn, 1); + if (caseOff) + fprintf(gOutFile, " first case offset %u", caseOff); + break; + case SRC_CATCH: + delta = (uintN) js_GetSrcNoteOffset(sn, 0); + if (delta) + fprintf(gOutFile, " guard size %u", delta); + break; + default:; + } + fputc('\n', gOutFile); + } +} + +static JSBool +Notes(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + uintN i; + JSScript *script; + + for (i = 0; i < argc; i++) { + script = ValueToScript(cx, argv[i]); + if (!script) + continue; + + SrcNotes(cx, script); + } + return JS_TRUE; +} + +static JSBool +TryNotes(JSContext *cx, JSScript *script) +{ + JSTryNote *tn = script->trynotes; + + if (!tn) + return JS_TRUE; + fprintf(gOutFile, "\nException table:\nstart\tend\tcatch\n"); + while (tn->start && tn->catchStart) { + fprintf(gOutFile, " %d\t%d\t%d\n", + tn->start, tn->start + tn->length, tn->catchStart); + tn++; + } + return JS_TRUE; +} + +static JSBool +Disassemble(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSBool lines; + uintN i; + JSScript *script; + + if (argc > 0 && + JSVAL_IS_STRING(argv[0]) && + !strcmp(JS_GetStringBytes(JSVAL_TO_STRING(argv[0])), "-l")) { + lines = JS_TRUE; + argv++, argc--; + } else { + lines = JS_FALSE; + } + for (i = 0; i < argc; i++) { + script = ValueToScript(cx, argv[i]); + if (!script) + continue; + + if (JSVAL_IS_FUNCTION(cx, argv[i])) { + JSFunction *fun = JS_ValueToFunction(cx, argv[i]); + if (fun && (fun->flags & JSFUN_FLAGS_MASK)) { + uint8 flags = fun->flags; + fputs("flags:", stdout); + +#define SHOW_FLAG(flag) if (flags & JSFUN_##flag) fputs(" " #flag, stdout); + + SHOW_FLAG(LAMBDA); + SHOW_FLAG(SETTER); + SHOW_FLAG(GETTER); + SHOW_FLAG(BOUND_METHOD); + SHOW_FLAG(HEAVYWEIGHT); + +#undef SHOW_FLAG + putchar('\n'); + } + } + + js_Disassemble(cx, script, lines, stdout); + SrcNotes(cx, script); + TryNotes(cx, script); + } + return JS_TRUE; +} + +static JSBool +DisassWithSrc(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ +#define LINE_BUF_LEN 512 + uintN i, len, line1, line2, bupline; + JSScript *script; + FILE *file; + char linebuf[LINE_BUF_LEN]; + jsbytecode *pc, *end; + static char sep[] = ";-------------------------"; + + for (i = 0; i < argc; i++) { + script = ValueToScript(cx, argv[i]); + if (!script) + continue; + + if (!script || !script->filename) { + JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, + JSSMSG_FILE_SCRIPTS_ONLY); + return JS_FALSE; + } + + file = fopen(script->filename, "r"); + if (!file) { + JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, + JSSMSG_CANT_OPEN, + script->filename, strerror(errno)); + return JS_FALSE; + } + + pc = script->code; + end = pc + script->length; + + /* burn the leading lines */ + line2 = JS_PCToLineNumber(cx, script, pc); + for (line1 = 0; line1 < line2 - 1; line1++) + fgets(linebuf, LINE_BUF_LEN, file); + + bupline = 0; + while (pc < end) { + line2 = JS_PCToLineNumber(cx, script, pc); + + if (line2 < line1) { + if (bupline != line2) { + bupline = line2; + fprintf(gOutFile, "%s %3u: BACKUP\n", sep, line2); + } + } else { + if (bupline && line1 == line2) + fprintf(gOutFile, "%s %3u: RESTORE\n", sep, line2); + bupline = 0; + while (line1 < line2) { + if (!fgets(linebuf, LINE_BUF_LEN, file)) { + JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, + JSSMSG_UNEXPECTED_EOF, + script->filename); + goto bail; + } + line1++; + fprintf(gOutFile, "%s %3u: %s", sep, line1, linebuf); + } + } + + len = js_Disassemble1(cx, script, pc, + PTRDIFF(pc, script->code, jsbytecode), + JS_TRUE, stdout); + if (!len) + return JS_FALSE; + pc += len; + } + + bail: + fclose(file); + } + return JS_TRUE; +#undef LINE_BUF_LEN +} + +static JSBool +Tracing(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSBool bval; + JSString *str; + + if (argc == 0) { + *rval = BOOLEAN_TO_JSVAL(cx->tracefp != 0); + return JS_TRUE; + } + + switch (JS_TypeOfValue(cx, argv[0])) { + case JSTYPE_NUMBER: + bval = JSVAL_IS_INT(argv[0]) + ? JSVAL_TO_INT(argv[0]) + : (jsint) *JSVAL_TO_DOUBLE(argv[0]); + break; + case JSTYPE_BOOLEAN: + bval = JSVAL_TO_BOOLEAN(argv[0]); + break; + default: + str = JS_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + fprintf(gErrFile, "tracing: illegal argument %s\n", + JS_GetStringBytes(str)); + return JS_TRUE; + } + cx->tracefp = bval ? stderr : NULL; + return JS_TRUE; +} + +typedef struct DumpAtomArgs { + JSContext *cx; + FILE *fp; +} DumpAtomArgs; + +static int +DumpAtom(JSHashEntry *he, int i, void *arg) +{ + DumpAtomArgs *args = (DumpAtomArgs *)arg; + FILE *fp = args->fp; + JSAtom *atom = (JSAtom *)he; + + fprintf(fp, "%3d %08x %5lu ", + i, (uintN)he->keyHash, (unsigned long)atom->number); + if (ATOM_IS_STRING(atom)) + fprintf(fp, "\"%s\"\n", js_AtomToPrintableString(args->cx, atom)); + else if (ATOM_IS_INT(atom)) + fprintf(fp, "%ld\n", (long)ATOM_TO_INT(atom)); + else + fprintf(fp, "%.16g\n", *ATOM_TO_DOUBLE(atom)); + return HT_ENUMERATE_NEXT; +} + +static void +DumpScope(JSContext *cx, JSObject *obj, FILE *fp) +{ + uintN i; + JSScope *scope; + JSScopeProperty *sprop; + + i = 0; + scope = OBJ_SCOPE(obj); + for (sprop = SCOPE_LAST_PROP(scope); sprop; sprop = sprop->parent) { + if (SCOPE_HAD_MIDDLE_DELETE(scope) && !SCOPE_HAS_PROPERTY(scope, sprop)) + continue; + fprintf(fp, "%3u %p", i, sprop); + if (JSVAL_IS_INT(sprop->id)) { + fprintf(fp, " [%ld]", (long)JSVAL_TO_INT(sprop->id)); + } else { + fprintf(fp, " \"%s\"", + js_AtomToPrintableString(cx, (JSAtom *)sprop->id)); + } + +#define DUMP_ATTR(name) if (sprop->attrs & JSPROP_##name) fputs(" " #name, fp) + DUMP_ATTR(ENUMERATE); + DUMP_ATTR(READONLY); + DUMP_ATTR(PERMANENT); + DUMP_ATTR(EXPORTED); + DUMP_ATTR(GETTER); + DUMP_ATTR(SETTER); +#undef DUMP_ATTR + + fprintf(fp, " slot %lu flags %x shortid %d\n", + sprop->slot, sprop->flags, sprop->shortid); + } +} + +static JSBool +DumpStats(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + uintN i; + JSString *str; + const char *bytes; + JSAtom *atom; + JSObject *obj2; + JSProperty *prop; + jsval value; + + for (i = 0; i < argc; i++) { + str = JS_ValueToString(cx, argv[i]); + if (!str) + return JS_FALSE; + bytes = JS_GetStringBytes(str); + if (strcmp(bytes, "arena") == 0) { +#ifdef JS_ARENAMETER + JS_DumpArenaStats(stdout); +#endif + } else if (strcmp(bytes, "atom") == 0) { + DumpAtomArgs args; + + fprintf(gOutFile, "\natom table contents:\n"); + args.cx = cx; + args.fp = stdout; + JS_HashTableEnumerateEntries(cx->runtime->atomState.table, + DumpAtom, + &args); +#ifdef HASHMETER + JS_HashTableDumpMeter(cx->runtime->atomState.table, + DumpAtom, + stdout); +#endif + } else if (strcmp(bytes, "global") == 0) { + DumpScope(cx, cx->globalObject, stdout); + } else { + atom = js_Atomize(cx, bytes, JS_GetStringLength(str), 0); + if (!atom) + return JS_FALSE; + if (!js_FindProperty(cx, (jsid)atom, &obj, &obj2, &prop)) + return JS_FALSE; + if (prop) { + OBJ_DROP_PROPERTY(cx, obj2, prop); + if (!OBJ_GET_PROPERTY(cx, obj, (jsid)atom, &value)) + return JS_FALSE; + } + if (!prop || !JSVAL_IS_OBJECT(value)) { + fprintf(gErrFile, "js: invalid stats argument %s\n", + bytes); + continue; + } + obj = JSVAL_TO_OBJECT(value); + if (obj) + DumpScope(cx, obj, stdout); + } + } + return JS_TRUE; +} + +#endif /* DEBUG */ + +#ifdef TEST_EXPORT +static JSBool +DoExport(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSAtom *atom; + JSObject *obj2; + JSProperty *prop; + JSBool ok; + uintN attrs; + + if (argc != 2) { + JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_DOEXP_USAGE); + return JS_FALSE; + } + if (!JS_ValueToObject(cx, argv[0], &obj)) + return JS_FALSE; + argv[0] = OBJECT_TO_JSVAL(obj); + atom = js_ValueToStringAtom(cx, argv[1]); + if (!atom) + return JS_FALSE; + if (!OBJ_LOOKUP_PROPERTY(cx, obj, (jsid)atom, &obj2, &prop)) + return JS_FALSE; + if (!prop) { + ok = OBJ_DEFINE_PROPERTY(cx, obj, id, JSVAL_VOID, NULL, NULL, + JSPROP_EXPORTED, NULL); + } else { + ok = OBJ_GET_ATTRIBUTES(cx, obj, (jsid)atom, prop, &attrs); + if (ok) { + attrs |= JSPROP_EXPORTED; + ok = OBJ_SET_ATTRIBUTES(cx, obj, (jsid)atom, prop, &attrs); + } + OBJ_DROP_PROPERTY(cx, obj2, prop); + } + return ok; +} +#endif + +#ifdef TEST_CVTARGS +#include + +static const char * +EscapeWideString(jschar *w) +{ + static char enuf[80]; + static char hex[] = "0123456789abcdef"; + jschar u; + unsigned char b, c; + int i, j; + + if (!w) + return ""; + for (i = j = 0; i < sizeof enuf - 1; i++, j++) { + u = w[j]; + if (u == 0) + break; + b = (unsigned char)(u >> 8); + c = (unsigned char)(u); + if (b) { + if (i >= sizeof enuf - 6) + break; + enuf[i++] = '\\'; + enuf[i++] = 'u'; + enuf[i++] = hex[b >> 4]; + enuf[i++] = hex[b & 15]; + enuf[i++] = hex[c >> 4]; + enuf[i] = hex[c & 15]; + } else if (!isprint(c)) { + if (i >= sizeof enuf - 4) + break; + enuf[i++] = '\\'; + enuf[i++] = 'x'; + enuf[i++] = hex[c >> 4]; + enuf[i] = hex[c & 15]; + } else { + enuf[i] = (char)c; + } + } + enuf[i] = 0; + return enuf; +} + +#include + +static JSBool +ZZ_formatter(JSContext *cx, const char *format, JSBool fromJS, jsval **vpp, + va_list *app) +{ + jsval *vp; + va_list ap; + jsdouble re, im; + + printf("entering ZZ_formatter"); + vp = *vpp; + ap = *app; + if (fromJS) { + if (!JS_ValueToNumber(cx, vp[0], &re)) + return JS_FALSE; + if (!JS_ValueToNumber(cx, vp[1], &im)) + return JS_FALSE; + *va_arg(ap, jsdouble *) = re; + *va_arg(ap, jsdouble *) = im; + } else { + re = va_arg(ap, jsdouble); + im = va_arg(ap, jsdouble); + if (!JS_NewNumberValue(cx, re, &vp[0])) + return JS_FALSE; + if (!JS_NewNumberValue(cx, im, &vp[1])) + return JS_FALSE; + } + *vpp = vp + 2; + *app = ap; + printf("leaving ZZ_formatter"); + return JS_TRUE; +} + +static JSBool +ConvertArgs(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSBool b = JS_FALSE; + jschar c = 0; + int32 i = 0, j = 0; + uint32 u = 0; + jsdouble d = 0, I = 0, re = 0, im = 0; + char *s = NULL; + JSString *str = NULL; + jschar *w = NULL; + JSObject *obj2 = NULL; + JSFunction *fun = NULL; + jsval v = JSVAL_VOID; + JSBool ok; + + if (!JS_AddArgumentFormatter(cx, "ZZ", ZZ_formatter)) + return JS_FALSE;; + ok = JS_ConvertArguments(cx, argc, argv, "b/ciujdIsSWofvZZ*", + &b, &c, &i, &u, &j, &d, &I, &s, &str, &w, &obj2, + &fun, &v, &re, &im); + JS_RemoveArgumentFormatter(cx, "ZZ"); + if (!ok) + return JS_FALSE; + fprintf(gOutFile, + "b %u, c %x (%c), i %ld, u %lu, j %ld\n", + b, c, (char)c, i, u, j); + fprintf(gOutFile, + "d %g, I %g, s %s, S %s, W %s, obj %s, fun %s\n" + "v %s, re %g, im %g\n", + d, I, s, str ? JS_GetStringBytes(str) : "", EscapeWideString(w), + JS_GetStringBytes(JS_ValueToString(cx, OBJECT_TO_JSVAL(obj2))), + fun ? JS_GetStringBytes(JS_DecompileFunction(cx, fun, 4)) : "", + JS_GetStringBytes(JS_ValueToString(cx, v)), re, im); + return JS_TRUE; +} +#endif + +static JSBool +BuildDate(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + fprintf(gOutFile, "built on %s at %s\n", __DATE__, __TIME__); + return JS_TRUE; +} + +static JSBool +Clear(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + if (argc != 0 && !JS_ValueToObject(cx, argv[0], &obj)) + return JS_FALSE; + JS_ClearScope(cx, obj); + return JS_TRUE; +} + +static JSBool +Intern(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str; + + str = JS_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + if (!JS_InternUCStringN(cx, JS_GetStringChars(str), + JS_GetStringLength(str))) { + return JS_FALSE; + } + return JS_TRUE; +} + +static JSBool +Clone(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFunction *fun; + JSObject *funobj, *parent, *clone; + + fun = JS_ValueToFunction(cx, argv[0]); + if (!fun) + return JS_FALSE; + funobj = JS_GetFunctionObject(fun); + if (argc > 1) { + if (!JS_ValueToObject(cx, argv[1], &parent)) + return JS_FALSE; + } else { + parent = JS_GetParent(cx, funobj); + } + clone = JS_CloneFunctionObject(cx, funobj, parent); + if (!clone) + return JS_FALSE; + *rval = OBJECT_TO_JSVAL(clone); + return JS_TRUE; +} + +static JSBool +Seal(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSObject *target; + JSBool deep = JS_FALSE; + + if (!JS_ConvertArguments(cx, argc, argv, "o/b", &target, &deep)) + return JS_FALSE; + if (!target) + return JS_TRUE; + return JS_SealObject(cx, target, deep); +} + +static JSFunctionSpec shell_functions[] = { + {"version", Version, 0}, + {"options", Options, 0}, + {"load", Load, 1}, + {"print", Print, 0}, + {"help", Help, 0}, + {"quit", Quit, 0}, + {"gc", GC, 0}, + {"trap", Trap, 3}, + {"untrap", Untrap, 2}, + {"line2pc", LineToPC, 0}, + {"pc2line", PCToLine, 0}, +#ifdef DEBUG + {"dis", Disassemble, 1}, + {"dissrc", DisassWithSrc, 1}, + {"notes", Notes, 1}, + {"tracing", Tracing, 0}, + {"stats", DumpStats, 1}, +#endif +#ifdef TEST_EXPORT + {"xport", DoExport, 2}, +#endif +#ifdef TEST_CVTARGS + {"cvtargs", ConvertArgs, 0, 0, 12}, +#endif + {"build", BuildDate, 0}, + {"clear", Clear, 0}, + {"intern", Intern, 1}, + {"clone", Clone, 1}, + {"seal", Seal, 1, 0, 1}, + {0} +}; + +/* NOTE: These must be kept in sync with the above. */ + +static char *shell_help_messages[] = { + "version([number]) Get or set JavaScript version number", + "options([option ...]) Get or toggle JavaScript options", + "load(['foo.js' ...]) Load files named by string arguments", + "print([exp ...]) Evaluate and print expressions", + "help([name ...]) Display usage and help messages", + "quit() Quit the shell", + "gc() Run the garbage collector", + "trap([fun, [pc,]] exp) Trap bytecode execution", + "untrap(fun[, pc]) Remove a trap", + "line2pc([fun,] line) Map line number to PC", + "pc2line(fun[, pc]) Map PC to line number", +#ifdef DEBUG + "dis([fun]) Disassemble functions into bytecodes", + "dissrc([fun]) Disassemble functions with source lines", + "notes([fun]) Show source notes for functions", + "tracing([toggle]) Turn tracing on or off", + "stats([string ...]) Dump 'arena', 'atom', 'global' stats", +#endif +#ifdef TEST_EXPORT + "xport(obj, id) Export identified property from object", +#endif +#ifdef TEST_CVTARGS + "cvtargs(b, c, ...) Test JS_ConvertArguments", +#endif + "build() Show build date and time", + "clear([obj]) Clear properties of object", + "intern(str) Internalize str in the atom table", + "clone(fun[, scope]) Clone function object", + "seal(obj[, deep]) Seal object, or object graph if deep", + 0 +}; + +static void +ShowHelpHeader(void) +{ + fprintf(gOutFile, "%-9s %-22s %s\n", "Command", "Usage", "Description"); + fprintf(gOutFile, "%-9s %-22s %s\n", "=======", "=====", "==========="); +} + +static void +ShowHelpForCommand(uintN n) +{ + fprintf(gOutFile, "%-9.9s %s\n", shell_functions[n].name, shell_help_messages[n]); +} + +static JSBool +Help(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + uintN i, j; + int did_header, did_something; + JSType type; + JSFunction *fun; + JSString *str; + const char *bytes; + + fprintf(gOutFile, "%s\n", JS_GetImplementationVersion()); + if (argc == 0) { + ShowHelpHeader(); + for (i = 0; shell_functions[i].name; i++) + ShowHelpForCommand(i); + } else { + did_header = 0; + for (i = 0; i < argc; i++) { + did_something = 0; + type = JS_TypeOfValue(cx, argv[i]); + if (type == JSTYPE_FUNCTION) { + fun = JS_ValueToFunction(cx, argv[i]); + str = fun->atom ? ATOM_TO_STRING(fun->atom) : NULL; + } else if (type == JSTYPE_STRING) { + str = JSVAL_TO_STRING(argv[i]); + } else { + str = NULL; + } + if (str) { + bytes = JS_GetStringBytes(str); + for (j = 0; shell_functions[j].name; j++) { + if (!strcmp(bytes, shell_functions[j].name)) { + if (!did_header) { + did_header = 1; + ShowHelpHeader(); + } + did_something = 1; + ShowHelpForCommand(j); + break; + } + } + } + if (!did_something) { + str = JS_ValueToString(cx, argv[i]); + if (!str) + return JS_FALSE; + fprintf(gErrFile, "Sorry, no help for %s\n", + JS_GetStringBytes(str)); + } + } + } + return JS_TRUE; +} + +/* + * Define a JS object called "it". Give it class operations that printf why + * they're being called for tutorial purposes. + */ +enum its_tinyid { + ITS_COLOR, ITS_HEIGHT, ITS_WIDTH, ITS_FUNNY, ITS_ARRAY, ITS_RDONLY +}; + +static JSPropertySpec its_props[] = { + {"color", ITS_COLOR, JSPROP_ENUMERATE}, + {"height", ITS_HEIGHT, JSPROP_ENUMERATE}, + {"width", ITS_WIDTH, JSPROP_ENUMERATE}, + {"funny", ITS_FUNNY, JSPROP_ENUMERATE}, + {"array", ITS_ARRAY, JSPROP_ENUMERATE}, + {"rdonly", ITS_RDONLY, JSPROP_READONLY}, + {0} +}; + +static JSBool +its_item(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + *rval = OBJECT_TO_JSVAL(obj); + if (argc != 0) + JS_SetCallReturnValue2(cx, argv[0]); + return JS_TRUE; +} + +static JSFunctionSpec its_methods[] = { + {"item", its_item, 0}, + {0} +}; + +#ifdef JSD_LOWLEVEL_SOURCE +/* + * This facilitates sending source to JSD (the debugger system) in the shell + * where the source is loaded using the JSFILE hack in jsscan. The function + * below is used as a callback for the jsdbgapi JS_SetSourceHandler hook. + * A more normal embedding (e.g. mozilla) loads source itself and can send + * source directly to JSD without using this hook scheme. + */ +static void +SendSourceToJSDebugger(const char *filename, uintN lineno, + jschar *str, size_t length, + void **listenerTSData, JSDContext* jsdc) +{ + JSDSourceText *jsdsrc = (JSDSourceText *) *listenerTSData; + + if (!jsdsrc) { + if (!filename) + filename = "typein"; + if (1 == lineno) { + jsdsrc = JSD_NewSourceText(jsdc, filename); + } else { + jsdsrc = JSD_FindSourceForURL(jsdc, filename); + if (jsdsrc && JSD_SOURCE_PARTIAL != + JSD_GetSourceStatus(jsdc, jsdsrc)) { + jsdsrc = NULL; + } + } + } + if (jsdsrc) { + jsdsrc = JSD_AppendUCSourceText(jsdc,jsdsrc, str, length, + JSD_SOURCE_PARTIAL); + } + *listenerTSData = jsdsrc; +} +#endif /* JSD_LOWLEVEL_SOURCE */ + +static JSBool its_noisy; /* whether to be noisy when finalizing it */ + +static JSBool +its_addProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + if (its_noisy) { + fprintf(gOutFile, "adding its property %s,", + JS_GetStringBytes(JS_ValueToString(cx, id))); + fprintf(gOutFile, " initial value %s\n", + JS_GetStringBytes(JS_ValueToString(cx, *vp))); + } + return JS_TRUE; +} + +static JSBool +its_delProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + if (its_noisy) { + fprintf(gOutFile, "deleting its property %s,", + JS_GetStringBytes(JS_ValueToString(cx, id))); + fprintf(gOutFile, " current value %s\n", + JS_GetStringBytes(JS_ValueToString(cx, *vp))); + } + return JS_TRUE; +} + +static JSBool +its_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + if (its_noisy) { + fprintf(gOutFile, "getting its property %s,", + JS_GetStringBytes(JS_ValueToString(cx, id))); + fprintf(gOutFile, " current value %s\n", + JS_GetStringBytes(JS_ValueToString(cx, *vp))); + } + return JS_TRUE; +} + +static JSBool +its_setProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + if (its_noisy) { + fprintf(gOutFile, "setting its property %s,", + JS_GetStringBytes(JS_ValueToString(cx, id))); + fprintf(gOutFile, " new value %s\n", + JS_GetStringBytes(JS_ValueToString(cx, *vp))); + } + if (JSVAL_IS_STRING(id) && + !strcmp(JS_GetStringBytes(JSVAL_TO_STRING(id)), "noisy")) { + return JS_ValueToBoolean(cx, *vp, &its_noisy); + } + return JS_TRUE; +} + +static JSBool +its_enumerate(JSContext *cx, JSObject *obj) +{ + if (its_noisy) + fprintf(gOutFile, "enumerate its properties\n"); + return JS_TRUE; +} + +static JSBool +its_resolve(JSContext *cx, JSObject *obj, jsval id) +{ + if (its_noisy) { + fprintf(gOutFile, "resolving its property %s\n", + JS_GetStringBytes(JS_ValueToString(cx, id))); + } + return JS_TRUE; +} + +static JSBool +its_convert(JSContext *cx, JSObject *obj, JSType type, jsval *vp) +{ + if (its_noisy) + fprintf(gOutFile, "converting it to %s type\n", JS_GetTypeName(cx, type)); + return JS_TRUE; +} + +static void +its_finalize(JSContext *cx, JSObject *obj) +{ + if (its_noisy) + fprintf(gOutFile, "finalizing it\n"); +} + +static JSClass its_class = { + "It", 0, + its_addProperty, its_delProperty, its_getProperty, its_setProperty, + its_enumerate, its_resolve, its_convert, its_finalize +}; + +JSErrorFormatString jsShell_ErrorFormatString[JSErr_Limit] = { +#if JS_HAS_DFLT_MSG_STRINGS +#define MSG_DEF(name, number, count, exception, format) \ + { format, count } , +#else +#define MSG_DEF(name, number, count, exception, format) \ + { NULL, count } , +#endif +#include "jsshell.msg" +#undef MSG_DEF +}; + +static const JSErrorFormatString * +my_GetErrorMessage(void *userRef, const char *locale, const uintN errorNumber) +{ + if ((errorNumber > 0) && (errorNumber < JSShellErr_Limit)) + return &jsShell_ErrorFormatString[errorNumber]; + return NULL; +} + +static void +my_LoadErrorReporter(JSContext *cx, const char *message, JSErrorReport *report) +{ + if (!report) { + fprintf(gErrFile, "%s\n", message); + return; + } + + /* Ignore any exceptions */ + if (JSREPORT_IS_EXCEPTION(report->flags)) + return; + + /* Otherwise, fall back to the ordinary error reporter. */ + my_ErrorReporter(cx, message, report); +} + +static void +my_ErrorReporter(JSContext *cx, const char *message, JSErrorReport *report) +{ + int i, j, k, n; + char *prefix, *tmp; + const char *ctmp; + + if (!report) { + fprintf(gErrFile, "%s\n", message); + return; + } + + /* Conditionally ignore reported warnings. */ + if (JSREPORT_IS_WARNING(report->flags) && !reportWarnings) + return; + + prefix = NULL; + if (report->filename) + prefix = JS_smprintf("%s:", report->filename); + if (report->lineno) { + tmp = prefix; + prefix = JS_smprintf("%s%u: ", tmp ? tmp : "", report->lineno); + JS_free(cx, tmp); + } + if (JSREPORT_IS_WARNING(report->flags)) { + tmp = prefix; + prefix = JS_smprintf("%s%swarning: ", + tmp ? tmp : "", + JSREPORT_IS_STRICT(report->flags) ? "strict " : ""); + JS_free(cx, tmp); + } + + /* embedded newlines -- argh! */ + while ((ctmp = strchr(message, '\n')) != 0) { + ctmp++; + if (prefix) + fputs(prefix, gErrFile); + fwrite(message, 1, ctmp - message, gErrFile); + message = ctmp; + } + + /* If there were no filename or lineno, the prefix might be empty */ + if (prefix) + fputs(prefix, gErrFile); + fputs(message, gErrFile); + + if (!report->linebuf) { + fputc('\n', gErrFile); + goto out; + } + + /* report->linebuf usually ends with a newline. */ + n = strlen(report->linebuf); + fprintf(gErrFile, ":\n%s%s%s%s", + prefix, + report->linebuf, + (n > 0 && report->linebuf[n-1] == '\n') ? "" : "\n", + prefix); + n = PTRDIFF(report->tokenptr, report->linebuf, char); + for (i = j = 0; i < n; i++) { + if (report->linebuf[i] == '\t') { + for (k = (j + 8) & ~7; j < k; j++) { + fputc('.', gErrFile); + } + continue; + } + fputc('.', gErrFile); + j++; + } + fputs("^\n", gErrFile); + out: + if (!JSREPORT_IS_WARNING(report->flags)) + gExitCode = EXITCODE_RUNTIME_ERROR; + JS_free(cx, prefix); +} + +#if defined(SHELL_HACK) && defined(DEBUG) && defined(XP_UNIX) +static JSBool +Exec(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFunction *fun; + const char *name, **nargv; + uintN i, nargc; + JSString *str; + pid_t pid; + int status; + + fun = JS_ValueToFunction(cx, argv[-2]); + if (!fun) + return JS_FALSE; + if (!fun->atom) + return JS_TRUE; + name = JS_GetStringBytes(ATOM_TO_STRING(fun->atom)); + nargc = 1 + argc; + nargv = JS_malloc(cx, (nargc + 1) * sizeof(char *)); + if (!nargv) + return JS_FALSE; + nargv[0] = name; + for (i = 1; i < nargc; i++) { + str = JS_ValueToString(cx, argv[i-1]); + if (!str) { + JS_free(cx, nargv); + return JS_FALSE; + } + nargv[i] = JS_GetStringBytes(str); + } + nargv[nargc] = 0; + pid = fork(); + switch (pid) { + case -1: + perror("js"); + break; + case 0: + (void) execvp(name, (char **)nargv); + perror("js"); + exit(127); + default: + while (waitpid(pid, &status, 0) < 0 && errno == EINTR) + continue; + break; + } + JS_free(cx, nargv); + return JS_TRUE; +} +#endif + +#define LAZY_STANDARD_CLASSES + +static JSBool +global_enumerate(JSContext *cx, JSObject *obj) +{ +#ifdef LAZY_STANDARD_CLASSES + return JS_EnumerateStandardClasses(cx, obj); +#else + return JS_TRUE; +#endif +} + +static JSBool +global_resolve(JSContext *cx, JSObject *obj, jsval id, uintN flags, + JSObject **objp) +{ +#ifdef LAZY_STANDARD_CLASSES + if ((flags & JSRESOLVE_ASSIGNING) == 0) { + JSBool resolved; + + if (!JS_ResolveStandardClass(cx, obj, id, &resolved)) + return JS_FALSE; + if (resolved) { + *objp = obj; + return JS_TRUE; + } + } +#endif + +#if defined(SHELL_HACK) && defined(DEBUG) && defined(XP_UNIX) + if ((flags & (JSRESOLVE_QUALIFIED | JSRESOLVE_ASSIGNING)) == 0) { + /* + * Do this expensive hack only for unoptimized Unix builds, which are + * not used for benchmarking. + */ + char *path, *comp, *full; + const char *name; + JSBool ok, found; + JSFunction *fun; + + if (!JSVAL_IS_STRING(id)) + return JS_TRUE; + path = getenv("PATH"); + if (!path) + return JS_TRUE; + path = JS_strdup(cx, path); + if (!path) + return JS_FALSE; + name = JS_GetStringBytes(JSVAL_TO_STRING(id)); + ok = JS_TRUE; + for (comp = strtok(path, ":"); comp; comp = strtok(NULL, ":")) { + if (*comp != '\0') { + full = JS_smprintf("%s/%s", comp, name); + if (!full) { + JS_ReportOutOfMemory(cx); + ok = JS_FALSE; + break; + } + } else { + full = (char *)name; + } + found = (access(full, X_OK) == 0); + if (*comp != '\0') + free(full); + if (found) { + fun = JS_DefineFunction(cx, obj, name, Exec, 0, + JSPROP_ENUMERATE); + ok = (fun != NULL); + if (ok) + *objp = obj; + break; + } + } + JS_free(cx, path); + return ok; + } +#else + return JS_TRUE; +#endif +} + +JSClass global_class = { + "global", JSCLASS_NEW_RESOLVE, + JS_PropertyStub, JS_PropertyStub, + JS_PropertyStub, JS_PropertyStub, + global_enumerate, (JSResolveOp) global_resolve, + JS_ConvertStub, JS_FinalizeStub +}; + +static JSBool +env_setProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ +/* XXX porting may be easy, but these don't seem to supply setenv by default */ +#if !defined XP_BEOS && !defined XP_OS2 && !defined SOLARIS + JSString *idstr, *valstr; + const char *name, *value; + int rv; + + idstr = JS_ValueToString(cx, id); + valstr = JS_ValueToString(cx, *vp); + if (!idstr || !valstr) + return JS_FALSE; + name = JS_GetStringBytes(idstr); + value = JS_GetStringBytes(valstr); +#if defined XP_WIN || defined HPUX || defined OSF1 || defined IRIX + { + char *waste = JS_smprintf("%s=%s", name, value); + if (!waste) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + rv = putenv(waste); +#ifdef XP_WIN + /* + * HPUX9 at least still has the bad old non-copying putenv. + * + * Per mail from , OSF1 also has a putenv + * that will crash if you pass it an auto char array (so it must place + * its argument directly in the char *environ[] array). + */ + free(waste); +#endif + } +#else + rv = setenv(name, value, 1); +#endif + if (rv < 0) { + JS_ReportError(cx, "can't set envariable %s to %s", name, value); + return JS_FALSE; + } + *vp = STRING_TO_JSVAL(valstr); +#endif /* !defined XP_BEOS && !defined XP_OS2 && !defined SOLARIS */ + return JS_TRUE; +} + +static JSBool +env_enumerate(JSContext *cx, JSObject *obj) +{ + static JSBool reflected; + char **evp, *name, *value; + JSString *valstr; + JSBool ok; + + if (reflected) + return JS_TRUE; + + for (evp = (char **)JS_GetPrivate(cx, obj); (name = *evp) != NULL; evp++) { + value = strchr(name, '='); + if (!value) + continue; + *value++ = '\0'; + valstr = JS_NewStringCopyZ(cx, value); + if (!valstr) { + ok = JS_FALSE; + } else { + ok = JS_DefineProperty(cx, obj, name, STRING_TO_JSVAL(valstr), + NULL, NULL, JSPROP_ENUMERATE); + } + value[-1] = '='; + if (!ok) + return JS_FALSE; + } + + reflected = JS_TRUE; + return JS_TRUE; +} + +static JSBool +env_resolve(JSContext *cx, JSObject *obj, jsval id, uintN flags, + JSObject **objp) +{ + JSString *idstr, *valstr; + const char *name, *value; + + if (flags & JSRESOLVE_ASSIGNING) + return JS_TRUE; + + idstr = JS_ValueToString(cx, id); + if (!idstr) + return JS_FALSE; + name = JS_GetStringBytes(idstr); + value = getenv(name); + if (value) { + valstr = JS_NewStringCopyZ(cx, value); + if (!valstr) + return JS_FALSE; + if (!JS_DefineProperty(cx, obj, name, STRING_TO_JSVAL(valstr), + NULL, NULL, JSPROP_ENUMERATE)) { + return JS_FALSE; + } + *objp = obj; + } + return JS_TRUE; +} + +static JSClass env_class = { + "environment", JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE, + JS_PropertyStub, JS_PropertyStub, + JS_PropertyStub, env_setProperty, + env_enumerate, (JSResolveOp) env_resolve, + JS_ConvertStub, JS_FinalizeStub +}; + +int +main(int argc, char **argv, char **envp) +{ + int stackDummy; + JSVersion version; + JSRuntime *rt; + JSContext *cx; + JSObject *glob, *it, *envobj; + int result; +#ifdef LIVECONNECT + JavaVM *java_vm = NULL; +#endif +#ifdef JSDEBUGGER_JAVA_UI + JNIEnv *java_env; +#endif + + gStackBase = (jsuword)&stackDummy; + +#ifdef XP_OS2 + /* these streams are normally line buffered on OS/2 and need a \n, * + * so we need to unbuffer then to get a reasonable prompt */ + setbuf(stdout,0); + setbuf(stderr,0); +#endif + + gErrFile = stderr; + gOutFile = stdout; + +#ifdef XP_MAC +#ifndef XP_MAC_MPW + initConsole("\pJavaScript Shell", "Welcome to js shell.", &argc, &argv); +#endif +#endif + +#ifdef MAC_TEST_HACK +/* + Open a file "testArgs.txt" and read each line into argc/argv. + Re-direct all output to "results.txt" +*/ + { + char argText[256]; + FILE *f = fopen("testargs.txt", "r"); + if (f) { + int maxArgs = 32; /* arbitrary max !!! */ + int argText_strlen; + argc = 1; + argv = malloc(sizeof(char *) * maxArgs); + argv[0] = NULL; + while (fgets(argText, 255, f)) { + /* argText includes '\n' */ + argText_strlen = strlen(argText); + argv[argc] = malloc(argText_strlen); + strncpy(argv[argc], argText, argText_strlen - 1); + argv[argc][argText_strlen - 1] = '\0'; + argc++; + if (argc >= maxArgs) + break; + } + fclose(f); + } + gTestResultFile = fopen("results.txt", "w"); + } + + gErrFile = gTestResultFile; + gOutFile = gTestResultFile; +#endif + + version = JSVERSION_DEFAULT; + + argc--; + argv++; + + rt = JS_NewRuntime(8L * 1024L * 1024L); + if (!rt) + return 1; + + cx = JS_NewContext(rt, gStackChunkSize); + if (!cx) + return 1; + JS_SetErrorReporter(cx, my_ErrorReporter); + + glob = JS_NewObject(cx, &global_class, NULL, NULL); + if (!glob) + return 1; +#ifdef LAZY_STANDARD_CLASSES + JS_SetGlobalObject(cx, glob); +#else + if (!JS_InitStandardClasses(cx, glob)) + return 1; +#endif + if (!JS_DefineFunctions(cx, glob, shell_functions)) + return 1; + + /* Set version only after there is a global object. */ + if (version != JSVERSION_DEFAULT) + JS_SetVersion(cx, version); + + it = JS_DefineObject(cx, glob, "it", &its_class, NULL, 0); + if (!it) + return 1; + if (!JS_DefineProperties(cx, it, its_props)) + return 1; + if (!JS_DefineFunctions(cx, it, its_methods)) + return 1; + +#ifdef PERLCONNECT + if (!JS_InitPerlClass(cx, glob)) + return 1; +#endif + +#ifdef JSDEBUGGER + /* + * XXX A command line option to enable debugging (or not) would be good + */ + _jsdc = JSD_DebuggerOnForUser(rt, NULL, NULL); + if (!_jsdc) + return 1; + JSD_JSContextInUse(_jsdc, cx); +#ifdef JSD_LOWLEVEL_SOURCE + JS_SetSourceHandler(rt, SendSourceToJSDebugger, _jsdc); +#endif /* JSD_LOWLEVEL_SOURCE */ +#ifdef JSDEBUGGER_JAVA_UI + _jsdjc = JSDJ_CreateContext(); + if (! _jsdjc) + return 1; + JSDJ_SetJSDContext(_jsdjc, _jsdc); + java_env = JSDJ_CreateJavaVMAndStartDebugger(_jsdjc); +#ifdef LIVECONNECT + if (java_env) + (*java_env)->GetJavaVM(java_env, &java_vm); +#endif + /* + * XXX This would be the place to wait for the debugger to start. + * Waiting would be nice in general, but especially when a js file + * is passed on the cmd line. + */ +#endif /* JSDEBUGGER_JAVA_UI */ +#ifdef JSDEBUGGER_C_UI + JSDB_InitDebugger(rt, _jsdc, 0); +#endif /* JSDEBUGGER_C_UI */ +#endif /* JSDEBUGGER */ + +#ifdef LIVECONNECT + if (!JSJ_SimpleInit(cx, glob, java_vm, getenv("CLASSPATH"))) + return 1; +#endif + + envobj = JS_DefineObject(cx, glob, "environment", &env_class, NULL, 0); + if (!envobj || !JS_SetPrivate(cx, envobj, envp)) + return 1; + + result = ProcessArgs(cx, glob, argv, argc); + +#ifdef JSDEBUGGER + if (_jsdc) + JSD_DebuggerOff(_jsdc); +#endif /* JSDEBUGGER */ + +#ifdef MAC_TEST_HACK + fclose(gTestResultFile); +#endif + + JS_DestroyContext(cx); + JS_DestroyRuntime(rt); + JS_ShutDown(); + return result; +} diff --git a/src/dom/js/js.mak b/src/dom/js/js.mak new file mode 100644 index 000000000..0f8b8f53c --- /dev/null +++ b/src/dom/js/js.mak @@ -0,0 +1,4025 @@ +# Microsoft Developer Studio Generated NMAKE File, Format Version 4.20 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +!IF "$(CFG)" == "" +CFG=jsshell - Win32 Debug +!MESSAGE No configuration specified. Defaulting to jsshell - Win32 Debug. +!ENDIF + +!IF "$(CFG)" != "js - Win32 Release" && "$(CFG)" != "js - Win32 Debug" &&\ + "$(CFG)" != "jsshell - Win32 Release" && "$(CFG)" != "jsshell - Win32 Debug" &&\ + "$(CFG)" != "fdlibm - Win32 Release" && "$(CFG)" != "fdlibm - Win32 Debug" +!MESSAGE Invalid configuration "$(CFG)" specified. +!MESSAGE You can specify a configuration when running NMAKE on this makefile +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "js.mak" CFG="jsshell - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "js - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "js - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "jsshell - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "jsshell - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE "fdlibm - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "fdlibm - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE +!ERROR An invalid configuration is specified. +!ENDIF + +!IF "$(OS)" == "Windows_NT" +NULL= +!ELSE +NULL=nul +!ENDIF +################################################################################ +# Begin Project +# PROP Target_Last_Scanned "jsshell - Win32 Debug" + +!IF "$(CFG)" == "js - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "js___Wi1" +# PROP BASE Intermediate_Dir "js___Wi1" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +OUTDIR=.\Release +INTDIR=.\Release + +ALL : "fdlibm - Win32 Release" "$(OUTDIR)\js32.dll" + +CLEAN : + -@erase "$(INTDIR)\jsapi.obj" + -@erase "$(INTDIR)\jsarena.obj" + -@erase "$(INTDIR)\jsarray.obj" + -@erase "$(INTDIR)\jsatom.obj" + -@erase "$(INTDIR)\jsbool.obj" + -@erase "$(INTDIR)\jscntxt.obj" + -@erase "$(INTDIR)\jsdate.obj" + -@erase "$(INTDIR)\jsdbgapi.obj" + -@erase "$(INTDIR)\jsdhash.obj" + -@erase "$(INTDIR)\jsdtoa.obj" + -@erase "$(INTDIR)\jsemit.obj" + -@erase "$(INTDIR)\jsexn.obj" + -@erase "$(INTDIR)\jsfun.obj" + -@erase "$(INTDIR)\jsgc.obj" + -@erase "$(INTDIR)\jshash.obj" + -@erase "$(INTDIR)\jsinterp.obj" + -@erase "$(INTDIR)\jslock.obj" + -@erase "$(INTDIR)\jslog2.obj" + -@erase "$(INTDIR)\jslong.obj" + -@erase "$(INTDIR)\jsmath.obj" + -@erase "$(INTDIR)\jsnum.obj" + -@erase "$(INTDIR)\jsobj.obj" + -@erase "$(INTDIR)\jsopcode.obj" + -@erase "$(INTDIR)\jsparse.obj" + -@erase "$(INTDIR)\jsprf.obj" + -@erase "$(INTDIR)\jsregexp.obj" + -@erase "$(INTDIR)\jsscan.obj" + -@erase "$(INTDIR)\jsscope.obj" + -@erase "$(INTDIR)\jsscript.obj" + -@erase "$(INTDIR)\jsstr.obj" + -@erase "$(INTDIR)\jsutil.obj" + -@erase "$(INTDIR)\jsxdrapi.obj" + -@erase "$(INTDIR)\prmjtime.obj" + -@erase "$(OUTDIR)\js32.dll" + -@erase "$(OUTDIR)\js32.exp" + -@erase "$(OUTDIR)\js32.lib" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP=cl.exe +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D _X86_=1 /D "_WINDOWS" /YX /c +# ADD CPP /nologo /MD /W3 /GX /O2 /D "NDEBUG" /D _X86_=1 /D "_WINDOWS" /D "WIN32" /D "XP_WIN" /D "JSFILE" /D "EXPORT_JS_API" /YX /c +CPP_PROJ=/nologo /MD /W3 /GX /O2 /D "NDEBUG" /D _X86_=1 /D "_WINDOWS" /D "WIN32" /D\ + "XP_WIN" /D "JSFILE" /D "EXPORT_JS_API" /Fp"$(INTDIR)/js.pch" /YX\ + /Fo"$(INTDIR)/" /c +CPP_OBJS=.\Release/ +CPP_SBRS=.\. + +.c{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.cpp{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.cxx{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.c{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +.cpp{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +.cxx{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +MTL=mktyplib.exe +# ADD BASE MTL /nologo /D "NDEBUG" /win32 +# ADD MTL /nologo /D "NDEBUG" /win32 +MTL_PROJ=/nologo /D "NDEBUG" /win32 +RSC=rc.exe +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +BSC32_FLAGS=/nologo /o"$(OUTDIR)/js.bsc" +BSC32_SBRS= \ + +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /machine:I386 /out:"Release/js32.dll" +# SUBTRACT LINK32 /nodefaultlib +LINK32_FLAGS=kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib\ + advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib\ + odbccp32.lib /nologo /subsystem:windows /dll /incremental:no\ + /pdb:"$(OUTDIR)/js32.pdb" /machine:I386 /out:"$(OUTDIR)/js32.dll"\ + /implib:"$(OUTDIR)/js32.lib" /opt:ref /opt:noicf +LINK32_OBJS= \ + "$(INTDIR)\jsapi.obj" \ + "$(INTDIR)\jsarena.obj" \ + "$(INTDIR)\jsarray.obj" \ + "$(INTDIR)\jsatom.obj" \ + "$(INTDIR)\jsbool.obj" \ + "$(INTDIR)\jscntxt.obj" \ + "$(INTDIR)\jsdate.obj" \ + "$(INTDIR)\jsdbgapi.obj" \ + "$(INTDIR)\jsdhash.obj" \ + "$(INTDIR)\jsdtoa.obj" \ + "$(INTDIR)\jsemit.obj" \ + "$(INTDIR)\jsexn.obj" \ + "$(INTDIR)\jsfun.obj" \ + "$(INTDIR)\jsgc.obj" \ + "$(INTDIR)\jshash.obj" \ + "$(INTDIR)\jsinterp.obj" \ + "$(INTDIR)\jslock.obj" \ + "$(INTDIR)\jslog2.obj" \ + "$(INTDIR)\jslong.obj" \ + "$(INTDIR)\jsmath.obj" \ + "$(INTDIR)\jsnum.obj" \ + "$(INTDIR)\jsobj.obj" \ + "$(INTDIR)\jsopcode.obj" \ + "$(INTDIR)\jsparse.obj" \ + "$(INTDIR)\jsprf.obj" \ + "$(INTDIR)\jsregexp.obj" \ + "$(INTDIR)\jsscan.obj" \ + "$(INTDIR)\jsscope.obj" \ + "$(INTDIR)\jsscript.obj" \ + "$(INTDIR)\jsstr.obj" \ + "$(INTDIR)\jsutil.obj" \ + "$(INTDIR)\jsxdrapi.obj" \ + "$(INTDIR)\prmjtime.obj" \ + "$(OUTDIR)\fdlibm.lib" + +"$(OUTDIR)\js32.dll" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "js___Wi2" +# PROP BASE Intermediate_Dir "js___Wi2" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +OUTDIR=.\Debug +INTDIR=.\Debug + +ALL : "fdlibm - Win32 Debug" "$(OUTDIR)\js32.dll" + +CLEAN : + -@erase "$(INTDIR)\jsapi.obj" + -@erase "$(INTDIR)\jsarena.obj" + -@erase "$(INTDIR)\jsarray.obj" + -@erase "$(INTDIR)\jsatom.obj" + -@erase "$(INTDIR)\jsbool.obj" + -@erase "$(INTDIR)\jscntxt.obj" + -@erase "$(INTDIR)\jsdate.obj" + -@erase "$(INTDIR)\jsdbgapi.obj" + -@erase "$(INTDIR)\jsdhash.obj" + -@erase "$(INTDIR)\jsdtoa.obj" + -@erase "$(INTDIR)\jsemit.obj" + -@erase "$(INTDIR)\jsexn.obj" + -@erase "$(INTDIR)\jsfun.obj" + -@erase "$(INTDIR)\jsgc.obj" + -@erase "$(INTDIR)\jshash.obj" + -@erase "$(INTDIR)\jsinterp.obj" + -@erase "$(INTDIR)\jslock.obj" + -@erase "$(INTDIR)\jslog2.obj" + -@erase "$(INTDIR)\jslong.obj" + -@erase "$(INTDIR)\jsmath.obj" + -@erase "$(INTDIR)\jsnum.obj" + -@erase "$(INTDIR)\jsobj.obj" + -@erase "$(INTDIR)\jsopcode.obj" + -@erase "$(INTDIR)\jsparse.obj" + -@erase "$(INTDIR)\jsprf.obj" + -@erase "$(INTDIR)\jsregexp.obj" + -@erase "$(INTDIR)\jsscan.obj" + -@erase "$(INTDIR)\jsscope.obj" + -@erase "$(INTDIR)\jsscript.obj" + -@erase "$(INTDIR)\jsstr.obj" + -@erase "$(INTDIR)\jsutil.obj" + -@erase "$(INTDIR)\jsxdrapi.obj" + -@erase "$(INTDIR)\prmjtime.obj" + -@erase "$(INTDIR)\vc40.idb" + -@erase "$(INTDIR)\vc40.pdb" + -@erase "$(OUTDIR)\js32.dll" + -@erase "$(OUTDIR)\js32.exp" + -@erase "$(OUTDIR)\js32.ilk" + -@erase "$(OUTDIR)\js32.lib" + -@erase "$(OUTDIR)\js32.pdb" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP=cl.exe +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D _X86_=1 /D "_WINDOWS" /YX /c +# ADD CPP /nologo /MDd /W3 /Gm /GX /Zi /Od /D "_DEBUG" /D "DEBUG" /D _X86_=1 /D "_WINDOWS" /D "WIN32" /D "XP_WIN" /D "JSFILE" /D "EXPORT_JS_API" /YX /c +CPP_PROJ=/nologo /MDd /W3 /Gm /GX /Zi /Od /D "_DEBUG" /D "DEBUG" /D _X86_=1 /D "_WINDOWS"\ + /D "WIN32" /D "XP_WIN" /D "JSFILE" /D "EXPORT_JS_API" /Fp"$(INTDIR)/js.pch" /YX\ + /Fo"$(INTDIR)/" /Fd"$(INTDIR)/" /c +CPP_OBJS=.\Debug/ +CPP_SBRS=.\. + +.c{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.cpp{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.cxx{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.c{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +.cpp{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +.cxx{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +MTL=mktyplib.exe +# ADD BASE MTL /nologo /D "_DEBUG" /win32 +# ADD MTL /nologo /D "_DEBUG" /win32 +MTL_PROJ=/nologo /D "_DEBUG" /win32 +RSC=rc.exe +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +BSC32_FLAGS=/nologo /o"$(OUTDIR)/js.bsc" +BSC32_SBRS= \ + +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /debug /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /debug /machine:I386 /out:"Debug/js32.dll" +# SUBTRACT LINK32 /nodefaultlib +LINK32_FLAGS=kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib\ + advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib\ + odbccp32.lib /nologo /subsystem:windows /dll /incremental:yes\ + /pdb:"$(OUTDIR)/js32.pdb" /debug /machine:I386 /out:"$(OUTDIR)/js32.dll"\ + /implib:"$(OUTDIR)/js32.lib" +LINK32_OBJS= \ + "$(INTDIR)\jsapi.obj" \ + "$(INTDIR)\jsarena.obj" \ + "$(INTDIR)\jsarray.obj" \ + "$(INTDIR)\jsatom.obj" \ + "$(INTDIR)\jsbool.obj" \ + "$(INTDIR)\jscntxt.obj" \ + "$(INTDIR)\jsdate.obj" \ + "$(INTDIR)\jsdbgapi.obj" \ + "$(INTDIR)\jsdhash.obj" \ + "$(INTDIR)\jsdtoa.obj" \ + "$(INTDIR)\jsemit.obj" \ + "$(INTDIR)\jsexn.obj" \ + "$(INTDIR)\jsfun.obj" \ + "$(INTDIR)\jsgc.obj" \ + "$(INTDIR)\jshash.obj" \ + "$(INTDIR)\jsinterp.obj" \ + "$(INTDIR)\jslock.obj" \ + "$(INTDIR)\jslog2.obj" \ + "$(INTDIR)\jslong.obj" \ + "$(INTDIR)\jsmath.obj" \ + "$(INTDIR)\jsnum.obj" \ + "$(INTDIR)\jsobj.obj" \ + "$(INTDIR)\jsopcode.obj" \ + "$(INTDIR)\jsparse.obj" \ + "$(INTDIR)\jsprf.obj" \ + "$(INTDIR)\jsregexp.obj" \ + "$(INTDIR)\jsscan.obj" \ + "$(INTDIR)\jsscope.obj" \ + "$(INTDIR)\jsscript.obj" \ + "$(INTDIR)\jsstr.obj" \ + "$(INTDIR)\jsutil.obj" \ + "$(INTDIR)\jsxdrapi.obj" \ + "$(INTDIR)\prmjtime.obj" \ + "$(OUTDIR)\fdlibm.lib" + +"$(OUTDIR)\js32.dll" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +!ELSEIF "$(CFG)" == "jsshell - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "jsshell\Release" +# PROP BASE Intermediate_Dir "jsshell\Release" +# PROP BASE Target_Dir "jsshell" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "jsshell" +OUTDIR=.\Release +INTDIR=.\Release + +ALL : "js - Win32 Release" "$(OUTDIR)\jsshell.exe" + +CLEAN : + -@erase "$(INTDIR)\js.obj" + -@erase "$(OUTDIR)\jsshell.exe" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP=cl.exe +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /MD /W3 /GX /O2 /D "NDEBUG" /D "_CONSOLE" /D "WIN32" /D "XP_WIN" /D "JSFILE" /YX /c +CPP_PROJ=/nologo /MD /W3 /GX /O2 /D "NDEBUG" /D "_CONSOLE" /D "WIN32" /D\ + "XP_WIN" /D "JSFILE" /Fp"$(INTDIR)/jsshell.pch" /YX /Fo"$(INTDIR)/" /c +CPP_OBJS=.\Release/ +CPP_SBRS=.\. + +.c{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.cpp{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.cxx{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.c{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +.cpp{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +.cxx{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +RSC=rc.exe +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +BSC32_FLAGS=/nologo /o"$(OUTDIR)/jsshell.bsc" +BSC32_SBRS= \ + +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +LINK32_FLAGS=kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib\ + advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib\ + odbccp32.lib /nologo /subsystem:console /incremental:no\ + /pdb:"$(OUTDIR)/jsshell.pdb" /machine:I386 /out:"$(OUTDIR)/jsshell.exe" +LINK32_OBJS= \ + "$(INTDIR)\js.obj" \ + "$(OUTDIR)\js32.lib" + +"$(OUTDIR)\jsshell.exe" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +!ELSEIF "$(CFG)" == "jsshell - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "jsshell\jsshell_" +# PROP BASE Intermediate_Dir "jsshell\jsshell_" +# PROP BASE Target_Dir "jsshell" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "jsshell" +OUTDIR=.\Debug +INTDIR=.\Debug + +ALL : "js - Win32 Debug" "$(OUTDIR)\jsshell.exe" + +CLEAN : + -@erase "$(INTDIR)\js.obj" + -@erase "$(INTDIR)\vc40.idb" + -@erase "$(INTDIR)\vc40.pdb" + -@erase "$(OUTDIR)\jsshell.exe" + -@erase "$(OUTDIR)\jsshell.ilk" + -@erase "$(OUTDIR)\jsshell.pdb" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP=cl.exe +# ADD BASE CPP /nologo /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /MDd /W3 /Gm /GX /Zi /Od /D "_CONSOLE" /D "_DEBUG" /D "WIN32" /D "XP_WIN" /D "JSFILE" /D "DEBUG" /YX /c +CPP_PROJ=/nologo /MDd /W3 /Gm /GX /Zi /Od /D "_CONSOLE" /D "_DEBUG" /D "WIN32"\ + /D "XP_WIN" /D "JSFILE" /D "DEBUG" /Fp"$(INTDIR)/jsshell.pch" /YX\ + /Fo"$(INTDIR)/" /Fd"$(INTDIR)/" /c +CPP_OBJS=.\Debug/ +CPP_SBRS=.\. + +.c{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.cpp{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.cxx{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.c{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +.cpp{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +.cxx{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +RSC=rc.exe +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +BSC32_FLAGS=/nologo /o"$(OUTDIR)/jsshell.bsc" +BSC32_SBRS= \ + +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 +LINK32_FLAGS=kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib\ + advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib\ + odbccp32.lib /nologo /subsystem:console /incremental:yes\ + /pdb:"$(OUTDIR)/jsshell.pdb" /debug /machine:I386 /out:"$(OUTDIR)/jsshell.exe" +LINK32_OBJS= \ + "$(INTDIR)\js.obj" \ + "$(OUTDIR)\js32.lib" + +"$(OUTDIR)\jsshell.exe" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +!ELSEIF "$(CFG)" == "fdlibm - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "fdlibm\Release" +# PROP BASE Intermediate_Dir "fdlibm\Release" +# PROP BASE Target_Dir "fdlibm" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "fdlibm" +OUTDIR=.\Release +INTDIR=.\Release + +ALL : "$(OUTDIR)\fdlibm.lib" + +CLEAN : + -@erase "$(INTDIR)\e_atan2.obj" + -@erase "$(INTDIR)\e_pow.obj" + -@erase "$(INTDIR)\e_sqrt.obj" + -@erase "$(INTDIR)\k_standard.obj" + -@erase "$(INTDIR)\s_atan.obj" + -@erase "$(INTDIR)\s_copysign.obj" + -@erase "$(INTDIR)\s_fabs.obj" + -@erase "$(INTDIR)\s_finite.obj" + -@erase "$(INTDIR)\s_isnan.obj" + -@erase "$(INTDIR)\s_matherr.obj" + -@erase "$(INTDIR)\s_rint.obj" + -@erase "$(INTDIR)\s_scalbn.obj" + -@erase "$(INTDIR)\w_atan2.obj" + -@erase "$(INTDIR)\w_pow.obj" + -@erase "$(INTDIR)\w_sqrt.obj" + -@erase "$(OUTDIR)\fdlibm.lib" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP=cl.exe +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D _X86_=1 /D "_WINDOWS" /YX /c +# ADD CPP /nologo /MD /W3 /GX /O2 /D "NDEBUG" /D "WIN32" /D _X86_=1 /D "_WINDOWS" /D "_IEEE_LIBM" /YX /c +CPP_PROJ=/nologo /MD /W3 /GX /O2 /D "NDEBUG" /D "WIN32" /D _X86_=1 /D "_WINDOWS" /D\ + "_IEEE_LIBM" /D "XP_WIN" /I .\ /Fp"$(INTDIR)/fdlibm.pch" /YX /Fo"$(INTDIR)/" /c +CPP_OBJS=.\Release/ +CPP_SBRS=.\. + +.c{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.cpp{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.cxx{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.c{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +.cpp{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +.cxx{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +BSC32_FLAGS=/nologo /o"$(OUTDIR)/fdlibm.bsc" +BSC32_SBRS= \ + +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo +LIB32_FLAGS=/nologo /out:"$(OUTDIR)/fdlibm.lib" +LIB32_OBJS= \ + "$(INTDIR)\e_atan2.obj" \ + "$(INTDIR)\e_pow.obj" \ + "$(INTDIR)\e_sqrt.obj" \ + "$(INTDIR)\k_standard.obj" \ + "$(INTDIR)\s_atan.obj" \ + "$(INTDIR)\s_copysign.obj" \ + "$(INTDIR)\s_fabs.obj" \ + "$(INTDIR)\s_finite.obj" \ + "$(INTDIR)\s_isnan.obj" \ + "$(INTDIR)\s_matherr.obj" \ + "$(INTDIR)\s_rint.obj" \ + "$(INTDIR)\s_scalbn.obj" \ + "$(INTDIR)\w_atan2.obj" \ + "$(INTDIR)\w_pow.obj" \ + "$(INTDIR)\w_sqrt.obj" + +"$(OUTDIR)\fdlibm.lib" : "$(OUTDIR)" $(DEF_FILE) $(LIB32_OBJS) + $(LIB32) @<< + $(LIB32_FLAGS) $(DEF_FLAGS) $(LIB32_OBJS) +<< + +!ELSEIF "$(CFG)" == "fdlibm - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "fdlibm\Debug" +# PROP BASE Intermediate_Dir "fdlibm\Debug" +# PROP BASE Target_Dir "fdlibm" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "fdlibm" +OUTDIR=.\Debug +INTDIR=.\Debug + +ALL : "$(OUTDIR)\fdlibm.lib" + +CLEAN : + -@erase "$(INTDIR)\e_atan2.obj" + -@erase "$(INTDIR)\e_pow.obj" + -@erase "$(INTDIR)\e_sqrt.obj" + -@erase "$(INTDIR)\k_standard.obj" + -@erase "$(INTDIR)\s_atan.obj" + -@erase "$(INTDIR)\s_copysign.obj" + -@erase "$(INTDIR)\s_fabs.obj" + -@erase "$(INTDIR)\s_finite.obj" + -@erase "$(INTDIR)\s_isnan.obj" + -@erase "$(INTDIR)\s_matherr.obj" + -@erase "$(INTDIR)\s_rint.obj" + -@erase "$(INTDIR)\s_scalbn.obj" + -@erase "$(INTDIR)\w_atan2.obj" + -@erase "$(INTDIR)\w_pow.obj" + -@erase "$(INTDIR)\w_sqrt.obj" + -@erase "$(OUTDIR)\fdlibm.lib" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP=cl.exe +# ADD BASE CPP /nologo /W3 /GX /Z7 /Od /D "WIN32" /D "_DEBUG" /D _X86_=1 /D "_WINDOWS" /YX /c +# ADD CPP /nologo /MDd /W3 /GX /Z7 /Od /D "_DEBUG" /D "WIN32" /D _X86_=1 /D "_WINDOWS" /D "_IEEE_LIBM" /YX /c +CPP_PROJ=/nologo /MDd /W3 /GX /Z7 /Od /D "_DEBUG" /D "WIN32" /D _X86_=1 /D "_WINDOWS" /D\ + "_IEEE_LIBM" /D "XP_WIN" -I .\ /Fp"$(INTDIR)/fdlibm.pch" /YX /Fo"$(INTDIR)/" /c +CPP_OBJS=.\Debug/ +CPP_SBRS=.\. + +.c{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.cpp{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.cxx{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.c{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +.cpp{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +.cxx{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +BSC32_FLAGS=/nologo /o"$(OUTDIR)/fdlibm.bsc" +BSC32_SBRS= \ + +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo +LIB32_FLAGS=/nologo /out:"$(OUTDIR)/fdlibm.lib" +LIB32_OBJS= \ + "$(INTDIR)\e_atan2.obj" \ + "$(INTDIR)\e_pow.obj" \ + "$(INTDIR)\e_sqrt.obj" \ + "$(INTDIR)\k_standard.obj" \ + "$(INTDIR)\s_atan.obj" \ + "$(INTDIR)\s_copysign.obj" \ + "$(INTDIR)\s_fabs.obj" \ + "$(INTDIR)\s_finite.obj" \ + "$(INTDIR)\s_isnan.obj" \ + "$(INTDIR)\s_matherr.obj" \ + "$(INTDIR)\s_rint.obj" \ + "$(INTDIR)\s_scalbn.obj" \ + "$(INTDIR)\w_atan2.obj" \ + "$(INTDIR)\w_pow.obj" \ + "$(INTDIR)\w_sqrt.obj" + +"$(OUTDIR)\fdlibm.lib" : "$(OUTDIR)" $(DEF_FILE) $(LIB32_OBJS) + $(LIB32) @<< + $(LIB32_FLAGS) $(DEF_FLAGS) $(LIB32_OBJS) +<< + +!ENDIF + +################################################################################ +# Begin Target + +# Name "js - Win32 Release" +# Name "js - Win32 Debug" + +!IF "$(CFG)" == "js - Win32 Release" + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +!ENDIF + +################################################################################ +# Begin Source File + +SOURCE=.\jsapi.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSAPI=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsarray.h"\ + ".\jsatom.h"\ + ".\jsbool.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsdate.h"\ + ".\jsemit.h"\ + ".\jsexn.h"\ + ".\jsfile.h"\ + ".\jsfun.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsmath.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsopcode.h"\ + ".\jsopcode.tbl"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsparse.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscan.h"\ + ".\jsscope.h"\ + ".\jsscript.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSAPI=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsapi.obj" : $(SOURCE) $(DEP_CPP_JSAPI) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSAPI=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsarray.h"\ + ".\jsatom.h"\ + ".\jsbool.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsdate.h"\ + ".\jsemit.h"\ + ".\jsexn.h"\ + ".\jsfile.h"\ + ".\jsfun.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsmath.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsopcode.h"\ + ".\jsopcode.tbl"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsparse.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscan.h"\ + ".\jsscope.h"\ + ".\jsscript.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSAPI=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsapi.obj" : $(SOURCE) $(DEP_CPP_JSAPI) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\jsarena.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSARE=\ + ".\jsarena.h"\ + ".\jsbit.h"\ + ".\jscompat.h"\ + ".\jscpucfg.h"\ + ".\jslong.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsstddef.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSARE=\ + ".\jsautocfg.h"\ + + +"$(INTDIR)\jsarena.obj" : $(SOURCE) $(DEP_CPP_JSARE) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSARE=\ + ".\jsarena.h"\ + ".\jsbit.h"\ + ".\jscompat.h"\ + ".\jscpucfg.h"\ + ".\jslong.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsstddef.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSARE=\ + ".\jsautocfg.h"\ + + +"$(INTDIR)\jsarena.obj" : $(SOURCE) $(DEP_CPP_JSARE) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\jsarray.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSARR=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsarray.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsfun.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSARR=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsarray.obj" : $(SOURCE) $(DEP_CPP_JSARR) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSARR=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsarray.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsfun.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSARR=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsarray.obj" : $(SOURCE) $(DEP_CPP_JSARR) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\jsatom.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSATO=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsopcode.h"\ + ".\jsopcode.tbl"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSATO=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsatom.obj" : $(SOURCE) $(DEP_CPP_JSATO) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSATO=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsopcode.h"\ + ".\jsopcode.tbl"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSATO=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsatom.obj" : $(SOURCE) $(DEP_CPP_JSATO) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\jsbool.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSBOO=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsbool.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSBOO=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsbool.obj" : $(SOURCE) $(DEP_CPP_JSBOO) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSBOO=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsbool.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSBOO=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsbool.obj" : $(SOURCE) $(DEP_CPP_JSBOO) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\jscntxt.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSCNT=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsdbgapi.h"\ + ".\jsexn.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsobj.h"\ + ".\jsopcode.h"\ + ".\jsopcode.tbl"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscan.h"\ + ".\jsscope.h"\ + ".\jsscript.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSCNT=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jscntxt.obj" : $(SOURCE) $(DEP_CPP_JSCNT) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSCNT=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsdbgapi.h"\ + ".\jsexn.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsobj.h"\ + ".\jsopcode.h"\ + ".\jsopcode.tbl"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscan.h"\ + ".\jsscope.h"\ + ".\jsscript.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSCNT=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jscntxt.obj" : $(SOURCE) $(DEP_CPP_JSCNT) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\jsdate.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSDAT=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsdate.h"\ + ".\jsdtoa.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + ".\prmjtime.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSDAT=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsdate.obj" : $(SOURCE) $(DEP_CPP_JSDAT) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSDAT=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsdate.h"\ + ".\jsdtoa.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + ".\prmjtime.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSDAT=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsdate.obj" : $(SOURCE) $(DEP_CPP_JSDAT) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\jsdbgapi.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSDBG=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsdbgapi.h"\ + ".\jsfun.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsobj.h"\ + ".\jsopcode.h"\ + ".\jsopcode.tbl"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsscript.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSDBG=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsdbgapi.obj" : $(SOURCE) $(DEP_CPP_JSDBG) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSDBG=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsdbgapi.h"\ + ".\jsfun.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsobj.h"\ + ".\jsopcode.h"\ + ".\jsopcode.tbl"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsscript.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSDBG=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsdbgapi.obj" : $(SOURCE) $(DEP_CPP_JSDBG) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\jsdhash.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSDHA=\ + ".\jsbit.h"\ + ".\jscompat.h"\ + ".\jscpucfg.h"\ + ".\jsdhash.h"\ + ".\jslong.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSDHA=\ + ".\jsautocfg.h"\ + + +"$(INTDIR)\jsdhash.obj" : $(SOURCE) $(DEP_CPP_JSDHA) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSDHA=\ + ".\jsbit.h"\ + ".\jscompat.h"\ + ".\jscpucfg.h"\ + ".\jsdhash.h"\ + ".\jslong.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSDHA=\ + ".\jsautocfg.h"\ + + +"$(INTDIR)\jsdhash.obj" : $(SOURCE) $(DEP_CPP_JSDHA) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\jsdtoa.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSDTO=\ + ".\jscompat.h"\ + ".\jscpucfg.h"\ + ".\jsdtoa.h"\ + ".\jslong.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jsstddef.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSDTO=\ + ".\jsautocfg.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsdtoa.obj" : $(SOURCE) $(DEP_CPP_JSDTO) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSDTO=\ + ".\jscompat.h"\ + ".\jscpucfg.h"\ + ".\jsdtoa.h"\ + ".\jslong.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jsstddef.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSDTO=\ + ".\jsautocfg.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsdtoa.obj" : $(SOURCE) $(DEP_CPP_JSDTO) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\jsemit.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSEMI=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsemit.h"\ + ".\jsfun.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsopcode.h"\ + ".\jsopcode.tbl"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsparse.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscan.h"\ + ".\jsscope.h"\ + ".\jsscript.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSEMI=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsemit.obj" : $(SOURCE) $(DEP_CPP_JSEMI) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSEMI=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsemit.h"\ + ".\jsfun.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsopcode.h"\ + ".\jsopcode.tbl"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsparse.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscan.h"\ + ".\jsscope.h"\ + ".\jsscript.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSEMI=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsemit.obj" : $(SOURCE) $(DEP_CPP_JSEMI) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\jsexn.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSEXN=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsexn.h"\ + ".\jsfun.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsobj.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSEXN=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsexn.obj" : $(SOURCE) $(DEP_CPP_JSEXN) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSEXN=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsexn.h"\ + ".\jsfun.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsobj.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSEXN=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsexn.obj" : $(SOURCE) $(DEP_CPP_JSEXN) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\jsfun.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSFUN=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsarray.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsfun.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsopcode.h"\ + ".\jsopcode.tbl"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsparse.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscan.h"\ + ".\jsscope.h"\ + ".\jsscript.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + ".\jsxdrapi.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSFUN=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsfun.obj" : $(SOURCE) $(DEP_CPP_JSFUN) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSFUN=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsarray.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsfun.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsopcode.h"\ + ".\jsopcode.tbl"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsparse.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscan.h"\ + ".\jsscope.h"\ + ".\jsscript.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + ".\jsxdrapi.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSFUN=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsfun.obj" : $(SOURCE) $(DEP_CPP_JSFUN) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\jsgc.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSGC_=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsfun.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsscript.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSGC_=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsgc.obj" : $(SOURCE) $(DEP_CPP_JSGC_) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSGC_=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsfun.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsscript.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSGC_=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsgc.obj" : $(SOURCE) $(DEP_CPP_JSGC_) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\jshash.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSHAS=\ + ".\jsbit.h"\ + ".\jscompat.h"\ + ".\jscpucfg.h"\ + ".\jshash.h"\ + ".\jslong.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSHAS=\ + ".\jsautocfg.h"\ + + +"$(INTDIR)\jshash.obj" : $(SOURCE) $(DEP_CPP_JSHAS) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSHAS=\ + ".\jsbit.h"\ + ".\jscompat.h"\ + ".\jscpucfg.h"\ + ".\jshash.h"\ + ".\jslong.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSHAS=\ + ".\jsautocfg.h"\ + + +"$(INTDIR)\jshash.obj" : $(SOURCE) $(DEP_CPP_JSHAS) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\jsinterp.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSINT=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsarray.h"\ + ".\jsatom.h"\ + ".\jsbool.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsdbgapi.h"\ + ".\jsfun.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsopcode.h"\ + ".\jsopcode.tbl"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsscript.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSINT=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsinterp.obj" : $(SOURCE) $(DEP_CPP_JSINT) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSINT=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsarray.h"\ + ".\jsatom.h"\ + ".\jsbool.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsdbgapi.h"\ + ".\jsfun.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsopcode.h"\ + ".\jsopcode.tbl"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsscript.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSINT=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsinterp.obj" : $(SOURCE) $(DEP_CPP_JSINT) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\jslock.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSLOC=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsobj.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSLOC=\ + ".\jsautocfg.h"\ + ".\pratom.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + ".\prthread.h"\ + + +"$(INTDIR)\jslock.obj" : $(SOURCE) $(DEP_CPP_JSLOC) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSLOC=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsobj.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSLOC=\ + ".\jsautocfg.h"\ + ".\pratom.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + ".\prthread.h"\ + + +"$(INTDIR)\jslock.obj" : $(SOURCE) $(DEP_CPP_JSLOC) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\jslog2.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSLOG=\ + ".\jsbit.h"\ + ".\jscpucfg.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jstypes.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSLOG=\ + ".\jsautocfg.h"\ + + +"$(INTDIR)\jslog2.obj" : $(SOURCE) $(DEP_CPP_JSLOG) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSLOG=\ + ".\jsbit.h"\ + ".\jscpucfg.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jstypes.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSLOG=\ + ".\jsautocfg.h"\ + + +"$(INTDIR)\jslog2.obj" : $(SOURCE) $(DEP_CPP_JSLOG) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\jslong.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSLON=\ + ".\jscpucfg.h"\ + ".\jslong.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jstypes.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSLON=\ + ".\jsautocfg.h"\ + + +"$(INTDIR)\jslong.obj" : $(SOURCE) $(DEP_CPP_JSLON) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSLON=\ + ".\jscpucfg.h"\ + ".\jslong.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jstypes.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSLON=\ + ".\jsautocfg.h"\ + + +"$(INTDIR)\jslong.obj" : $(SOURCE) $(DEP_CPP_JSLON) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\jsmath.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSMAT=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslibmath.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsmath.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\prmjtime.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSMAT=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsmath.obj" : $(SOURCE) $(DEP_CPP_JSMAT) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSMAT=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslibmath.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsmath.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\prmjtime.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSMAT=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsmath.obj" : $(SOURCE) $(DEP_CPP_JSMAT) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\jsnum.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSNUM=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsdtoa.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsopcode.h"\ + ".\jsopcode.tbl"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSNUM=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsnum.obj" : $(SOURCE) $(DEP_CPP_JSNUM) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSNUM=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsdtoa.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsopcode.h"\ + ".\jsopcode.tbl"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSNUM=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsnum.obj" : $(SOURCE) $(DEP_CPP_JSNUM) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\jsobj.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSOBJ=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsbool.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsdbgapi.h"\ + ".\jsfun.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsopcode.h"\ + ".\jsopcode.tbl"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsscript.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + ".\jsxdrapi.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSOBJ=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsobj.obj" : $(SOURCE) $(DEP_CPP_JSOBJ) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSOBJ=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsbool.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsdbgapi.h"\ + ".\jsfun.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsopcode.h"\ + ".\jsopcode.tbl"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsscript.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + ".\jsxdrapi.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSOBJ=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsobj.obj" : $(SOURCE) $(DEP_CPP_JSOBJ) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\jsopcode.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSOPC=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsarray.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsdbgapi.h"\ + ".\jsdtoa.h"\ + ".\jsemit.h"\ + ".\jsfun.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsobj.h"\ + ".\jsopcode.h"\ + ".\jsopcode.tbl"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsscript.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSOPC=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsopcode.obj" : $(SOURCE) $(DEP_CPP_JSOPC) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSOPC=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsarray.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsdbgapi.h"\ + ".\jsdtoa.h"\ + ".\jsemit.h"\ + ".\jsfun.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsobj.h"\ + ".\jsopcode.h"\ + ".\jsopcode.tbl"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsscript.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSOPC=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsopcode.obj" : $(SOURCE) $(DEP_CPP_JSOPC) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\jsparse.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSPAR=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsemit.h"\ + ".\jsfun.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsopcode.h"\ + ".\jsopcode.tbl"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsparse.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscan.h"\ + ".\jsscope.h"\ + ".\jsscript.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSPAR=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsparse.obj" : $(SOURCE) $(DEP_CPP_JSPAR) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSPAR=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsemit.h"\ + ".\jsfun.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsopcode.h"\ + ".\jsopcode.tbl"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsparse.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscan.h"\ + ".\jsscope.h"\ + ".\jsscript.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSPAR=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsparse.obj" : $(SOURCE) $(DEP_CPP_JSPAR) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\jsprf.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSPRF=\ + ".\jscpucfg.h"\ + ".\jslong.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSPRF=\ + ".\jsautocfg.h"\ + + +"$(INTDIR)\jsprf.obj" : $(SOURCE) $(DEP_CPP_JSPRF) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSPRF=\ + ".\jscpucfg.h"\ + ".\jslong.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSPRF=\ + ".\jsautocfg.h"\ + + +"$(INTDIR)\jsprf.obj" : $(SOURCE) $(DEP_CPP_JSPRF) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\jsregexp.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSREG=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsarray.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsfun.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsopcode.h"\ + ".\jsopcode.tbl"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + ".\jsxdrapi.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSREG=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsregexp.obj" : $(SOURCE) $(DEP_CPP_JSREG) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSREG=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsarray.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsfun.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsopcode.h"\ + ".\jsopcode.tbl"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + ".\jsxdrapi.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSREG=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsregexp.obj" : $(SOURCE) $(DEP_CPP_JSREG) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\jsscan.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSSCA=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsdtoa.h"\ + ".\jsexn.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsopcode.h"\ + ".\jsopcode.tbl"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscan.h"\ + ".\jsscope.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSSCA=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsscan.obj" : $(SOURCE) $(DEP_CPP_JSSCA) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSSCA=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsdtoa.h"\ + ".\jsexn.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsopcode.h"\ + ".\jsopcode.tbl"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscan.h"\ + ".\jsscope.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSSCA=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsscan.obj" : $(SOURCE) $(DEP_CPP_JSSCA) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\jsscope.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSSCO=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSSCO=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsscope.obj" : $(SOURCE) $(DEP_CPP_JSSCO) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSSCO=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSSCO=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsscope.obj" : $(SOURCE) $(DEP_CPP_JSSCO) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\jsscript.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSSCR=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsdbgapi.h"\ + ".\jsemit.h"\ + ".\jsfun.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsopcode.h"\ + ".\jsopcode.tbl"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsscript.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + ".\jsxdrapi.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSSCR=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsscript.obj" : $(SOURCE) $(DEP_CPP_JSSCR) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSSCR=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsdbgapi.h"\ + ".\jsemit.h"\ + ".\jsfun.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsopcode.h"\ + ".\jsopcode.tbl"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsscript.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + ".\jsxdrapi.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSSCR=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsscript.obj" : $(SOURCE) $(DEP_CPP_JSSCR) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\jsstr.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSSTR=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsarray.h"\ + ".\jsatom.h"\ + ".\jsbool.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsopcode.h"\ + ".\jsopcode.tbl"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSSTR=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsstr.obj" : $(SOURCE) $(DEP_CPP_JSSTR) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSSTR=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsarray.h"\ + ".\jsatom.h"\ + ".\jsbool.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsnum.h"\ + ".\jsobj.h"\ + ".\jsopcode.h"\ + ".\jsopcode.tbl"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSSTR=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsstr.obj" : $(SOURCE) $(DEP_CPP_JSSTR) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\jsutil.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSUTI=\ + ".\jscpucfg.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSUTI=\ + ".\jsautocfg.h"\ + + +"$(INTDIR)\jsutil.obj" : $(SOURCE) $(DEP_CPP_JSUTI) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSUTI=\ + ".\jscpucfg.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSUTI=\ + ".\jsautocfg.h"\ + + +"$(INTDIR)\jsutil.obj" : $(SOURCE) $(DEP_CPP_JSUTI) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\jsxdrapi.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_JSXDR=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsobj.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + ".\jsxdrapi.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSXDR=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsxdrapi.obj" : $(SOURCE) $(DEP_CPP_JSXDR) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_JSXDR=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsobj.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscope.h"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + ".\jsxdrapi.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JSXDR=\ + ".\jsautocfg.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\jsxdrapi.obj" : $(SOURCE) $(DEP_CPP_JSXDR) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\prmjtime.c + +!IF "$(CFG)" == "js - Win32 Release" + +DEP_CPP_PRMJT=\ + ".\jscompat.h"\ + ".\jscpucfg.h"\ + ".\jslong.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jstypes.h"\ + ".\prmjtime.h"\ + {$(INCLUDE)}"\sys\TIMEB.H"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_PRMJT=\ + ".\jsautocfg.h"\ + + +"$(INTDIR)\prmjtime.obj" : $(SOURCE) $(DEP_CPP_PRMJT) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "js - Win32 Debug" + +DEP_CPP_PRMJT=\ + ".\jscompat.h"\ + ".\jscpucfg.h"\ + ".\jslong.h"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsprf.h"\ + ".\jstypes.h"\ + ".\prmjtime.h"\ + {$(INCLUDE)}"\sys\TIMEB.H"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_PRMJT=\ + ".\jsautocfg.h"\ + + +"$(INTDIR)\prmjtime.obj" : $(SOURCE) $(DEP_CPP_PRMJT) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Project Dependency + +# Project_Dep_Name "fdlibm" + +!IF "$(CFG)" == "js - Win32 Debug" + +"fdlibm - Win32 Debug" : + $(MAKE) /$(MAKEFLAGS) /F ".\js.mak" CFG="fdlibm - Win32 Debug" + +!ELSEIF "$(CFG)" == "js - Win32 Release" + +"fdlibm - Win32 Release" : + $(MAKE) /$(MAKEFLAGS) /F ".\js.mak" CFG="fdlibm - Win32 Release" + +!ENDIF + +# End Project Dependency +# End Target +################################################################################ +# Begin Target + +# Name "jsshell - Win32 Release" +# Name "jsshell - Win32 Debug" + +!IF "$(CFG)" == "jsshell - Win32 Release" + +!ELSEIF "$(CFG)" == "jsshell - Win32 Debug" + +!ENDIF + +################################################################################ +# Begin Source File + +SOURCE=.\js.c +DEP_CPP_JS_C42=\ + ".\js.msg"\ + ".\jsapi.h"\ + ".\jsarena.h"\ + ".\jsatom.h"\ + ".\jsclist.h"\ + ".\jscntxt.h"\ + ".\jscompat.h"\ + ".\jsconfig.h"\ + ".\jscpucfg.h"\ + ".\jsdbgapi.h"\ + ".\jsemit.h"\ + ".\jsfun.h"\ + ".\jsgc.h"\ + ".\jshash.h"\ + ".\jsinterp.h"\ + ".\jslock.h"\ + ".\jslong.h"\ + ".\jsobj.h"\ + ".\jsopcode.h"\ + ".\jsopcode.tbl"\ + ".\jsosdep.h"\ + ".\jsotypes.h"\ + ".\jsparse.h"\ + ".\jsprf.h"\ + ".\jsprvtd.h"\ + ".\jspubtd.h"\ + ".\jsregexp.h"\ + ".\jsscan.h"\ + ".\jsscope.h"\ + ".\jsscript.h"\ + ".\jsshell.msg"\ + ".\jsstddef.h"\ + ".\jsstr.h"\ + ".\jstypes.h"\ + ".\jsutil.h"\ + {$(INCLUDE)}"\sys\types.h"\ + +NODEP_CPP_JS_C42=\ + ".\jsautocfg.h"\ + ".\jsdb.h"\ + ".\jsdebug.h"\ + ".\jsdjava.h"\ + ".\jsjava.h"\ + ".\jsperl.h"\ + ".\prcvar.h"\ + ".\prlock.h"\ + + +"$(INTDIR)\js.obj" : $(SOURCE) $(DEP_CPP_JS_C42) "$(INTDIR)" + + +# End Source File +################################################################################ +# Begin Project Dependency + +# Project_Dep_Name "js" + +!IF "$(CFG)" == "jsshell - Win32 Release" + +"js - Win32 Release" : + $(MAKE) /$(MAKEFLAGS) /F ".\js.mak" CFG="js - Win32 Release" + +!ELSEIF "$(CFG)" == "jsshell - Win32 Debug" + +"js - Win32 Debug" : + $(MAKE) /$(MAKEFLAGS) /F ".\js.mak" CFG="js - Win32 Debug" + +!ENDIF + +# End Project Dependency +# End Target +################################################################################ +# Begin Target + +# Name "fdlibm - Win32 Release" +# Name "fdlibm - Win32 Debug" + +!IF "$(CFG)" == "fdlibm - Win32 Release" + +!ELSEIF "$(CFG)" == "fdlibm - Win32 Debug" + +!ENDIF + +################################################################################ +# Begin Source File + +SOURCE=.\fdlibm\w_atan2.c + +!IF "$(CFG)" == "fdlibm - Win32 Release" + +DEP_CPP_W_ATA=\ + ".\fdlibm\fdlibm.h"\ + + +"$(INTDIR)\w_atan2.obj" : $(SOURCE) $(DEP_CPP_W_ATA) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ELSEIF "$(CFG)" == "fdlibm - Win32 Debug" + +DEP_CPP_W_ATA=\ + ".\fdlibm\fdlibm.h"\ + + +"$(INTDIR)\w_atan2.obj" : $(SOURCE) $(DEP_CPP_W_ATA) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\fdlibm\s_copysign.c + +!IF "$(CFG)" == "fdlibm - Win32 Release" + +DEP_CPP_S_COP=\ + ".\fdlibm\fdlibm.h"\ + + +"$(INTDIR)\s_copysign.obj" : $(SOURCE) $(DEP_CPP_S_COP) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ELSEIF "$(CFG)" == "fdlibm - Win32 Debug" + +DEP_CPP_S_COP=\ + ".\fdlibm\fdlibm.h"\ + + +"$(INTDIR)\s_copysign.obj" : $(SOURCE) $(DEP_CPP_S_COP) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\fdlibm\w_pow.c + +!IF "$(CFG)" == "fdlibm - Win32 Release" + +DEP_CPP_W_POW=\ + ".\fdlibm\fdlibm.h"\ + + +"$(INTDIR)\w_pow.obj" : $(SOURCE) $(DEP_CPP_W_POW) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ELSEIF "$(CFG)" == "fdlibm - Win32 Debug" + +DEP_CPP_W_POW=\ + ".\fdlibm\fdlibm.h"\ + + +"$(INTDIR)\w_pow.obj" : $(SOURCE) $(DEP_CPP_W_POW) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\fdlibm\e_pow.c + +!IF "$(CFG)" == "fdlibm - Win32 Release" + +DEP_CPP_E_POW=\ + ".\fdlibm\fdlibm.h"\ + + +"$(INTDIR)\e_pow.obj" : $(SOURCE) $(DEP_CPP_E_POW) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ELSEIF "$(CFG)" == "fdlibm - Win32 Debug" + +DEP_CPP_E_POW=\ + ".\fdlibm\fdlibm.h"\ + + +"$(INTDIR)\e_pow.obj" : $(SOURCE) $(DEP_CPP_E_POW) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\fdlibm\k_standard.c + +!IF "$(CFG)" == "fdlibm - Win32 Release" + +DEP_CPP_K_STA=\ + ".\fdlibm\fdlibm.h"\ + + +"$(INTDIR)\k_standard.obj" : $(SOURCE) $(DEP_CPP_K_STA) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ELSEIF "$(CFG)" == "fdlibm - Win32 Debug" + +DEP_CPP_K_STA=\ + ".\fdlibm\fdlibm.h"\ + + +"$(INTDIR)\k_standard.obj" : $(SOURCE) $(DEP_CPP_K_STA) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\fdlibm\e_atan2.c + +!IF "$(CFG)" == "fdlibm - Win32 Release" + +DEP_CPP_E_ATA=\ + ".\fdlibm\fdlibm.h"\ + + +"$(INTDIR)\e_atan2.obj" : $(SOURCE) $(DEP_CPP_E_ATA) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ELSEIF "$(CFG)" == "fdlibm - Win32 Debug" + +DEP_CPP_E_ATA=\ + ".\fdlibm\fdlibm.h"\ + + +"$(INTDIR)\e_atan2.obj" : $(SOURCE) $(DEP_CPP_E_ATA) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\fdlibm\s_isnan.c + +!IF "$(CFG)" == "fdlibm - Win32 Release" + +DEP_CPP_S_ISN=\ + ".\fdlibm\fdlibm.h"\ + + +"$(INTDIR)\s_isnan.obj" : $(SOURCE) $(DEP_CPP_S_ISN) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ELSEIF "$(CFG)" == "fdlibm - Win32 Debug" + +DEP_CPP_S_ISN=\ + ".\fdlibm\fdlibm.h"\ + + +"$(INTDIR)\s_isnan.obj" : $(SOURCE) $(DEP_CPP_S_ISN) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\fdlibm\s_fabs.c + +!IF "$(CFG)" == "fdlibm - Win32 Release" + +DEP_CPP_S_FAB=\ + ".\fdlibm\fdlibm.h"\ + + +"$(INTDIR)\s_fabs.obj" : $(SOURCE) $(DEP_CPP_S_FAB) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ELSEIF "$(CFG)" == "fdlibm - Win32 Debug" + +DEP_CPP_S_FAB=\ + ".\fdlibm\fdlibm.h"\ + + +"$(INTDIR)\s_fabs.obj" : $(SOURCE) $(DEP_CPP_S_FAB) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\fdlibm\w_sqrt.c + +!IF "$(CFG)" == "fdlibm - Win32 Release" + +DEP_CPP_W_SQR=\ + ".\fdlibm\fdlibm.h"\ + + +"$(INTDIR)\w_sqrt.obj" : $(SOURCE) $(DEP_CPP_W_SQR) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ELSEIF "$(CFG)" == "fdlibm - Win32 Debug" + +DEP_CPP_W_SQR=\ + ".\fdlibm\fdlibm.h"\ + + +"$(INTDIR)\w_sqrt.obj" : $(SOURCE) $(DEP_CPP_W_SQR) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\fdlibm\s_scalbn.c + +!IF "$(CFG)" == "fdlibm - Win32 Release" + +DEP_CPP_S_SCA=\ + ".\fdlibm\fdlibm.h"\ + + +"$(INTDIR)\s_scalbn.obj" : $(SOURCE) $(DEP_CPP_S_SCA) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ELSEIF "$(CFG)" == "fdlibm - Win32 Debug" + +DEP_CPP_S_SCA=\ + ".\fdlibm\fdlibm.h"\ + + +"$(INTDIR)\s_scalbn.obj" : $(SOURCE) $(DEP_CPP_S_SCA) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\fdlibm\e_sqrt.c + +!IF "$(CFG)" == "fdlibm - Win32 Release" + +DEP_CPP_E_SQR=\ + ".\fdlibm\fdlibm.h"\ + + +"$(INTDIR)\e_sqrt.obj" : $(SOURCE) $(DEP_CPP_E_SQR) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ELSEIF "$(CFG)" == "fdlibm - Win32 Debug" + +DEP_CPP_E_SQR=\ + ".\fdlibm\fdlibm.h"\ + + +"$(INTDIR)\e_sqrt.obj" : $(SOURCE) $(DEP_CPP_E_SQR) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\fdlibm\s_rint.c + +!IF "$(CFG)" == "fdlibm - Win32 Release" + +DEP_CPP_S_RIN=\ + ".\fdlibm\fdlibm.h"\ + + +"$(INTDIR)\s_rint.obj" : $(SOURCE) $(DEP_CPP_S_RIN) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ELSEIF "$(CFG)" == "fdlibm - Win32 Debug" + +DEP_CPP_S_RIN=\ + ".\fdlibm\fdlibm.h"\ + + +"$(INTDIR)\s_rint.obj" : $(SOURCE) $(DEP_CPP_S_RIN) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\fdlibm\s_atan.c + +!IF "$(CFG)" == "fdlibm - Win32 Release" + +DEP_CPP_S_ATA=\ + ".\fdlibm\fdlibm.h"\ + + +"$(INTDIR)\s_atan.obj" : $(SOURCE) $(DEP_CPP_S_ATA) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ELSEIF "$(CFG)" == "fdlibm - Win32 Debug" + +DEP_CPP_S_ATA=\ + ".\fdlibm\fdlibm.h"\ + + +"$(INTDIR)\s_atan.obj" : $(SOURCE) $(DEP_CPP_S_ATA) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\fdlibm\s_finite.c + +!IF "$(CFG)" == "fdlibm - Win32 Release" + +DEP_CPP_S_FIN=\ + ".\fdlibm\fdlibm.h"\ + + +"$(INTDIR)\s_finite.obj" : $(SOURCE) $(DEP_CPP_S_FIN) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ELSEIF "$(CFG)" == "fdlibm - Win32 Debug" + +DEP_CPP_S_FIN=\ + ".\fdlibm\fdlibm.h"\ + + +"$(INTDIR)\s_finite.obj" : $(SOURCE) $(DEP_CPP_S_FIN) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\fdlibm\s_matherr.c + +!IF "$(CFG)" == "fdlibm - Win32 Release" + +DEP_CPP_S_MAT=\ + ".\fdlibm\fdlibm.h"\ + + +"$(INTDIR)\s_matherr.obj" : $(SOURCE) $(DEP_CPP_S_MAT) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ELSEIF "$(CFG)" == "fdlibm - Win32 Debug" + +DEP_CPP_S_MAT=\ + ".\fdlibm\fdlibm.h"\ + + +"$(INTDIR)\s_matherr.obj" : $(SOURCE) $(DEP_CPP_S_MAT) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ENDIF + +# End Source File +# End Target +# End Project +################################################################################ diff --git a/src/dom/js/js.msg b/src/dom/js/js.msg new file mode 100644 index 000000000..dbc571fdf --- /dev/null +++ b/src/dom/js/js.msg @@ -0,0 +1,251 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * This is the JavaScript error message file. + * + * The format for each JS error message is: + * + * MSG_DEF(, , , , + * ) + * + * where ; + * is a legal C identifer that will be used in the + * JS engine source. + * + * is an unique integral value identifying this error. + * + * is an integer literal specifying the total number of + * replaceable arguments in the following format string. + * + * is an exception index from the enum in jsexn.c; + * JSEXN_NONE for none. The given exception index will be raised by the + * engine when the corresponding error occurs. + * + * is a string literal, optionally containing sequences + * {X} where X is an integer representing the argument number that will + * be replaced with a string value when the error is reported. + * + * e.g. + * + * MSG_DEF(JSMSG_NOT_A_SUBSPECIES, 73, JSEXN_NONE, 2, + * "{0} is not a member of the {1} family") + * + * can be used: + * + * JS_ReportErrorNumber(JSMSG_NOT_A_SUBSPECIES, "Rhino", "Monkey"); + * + * to report: + * + * "Rhino is not a member of the Monkey family" + * + * Before adding a new MSG_DEF at the end, look for JSMSG_UNUSED free + * index placeholders in the middle of the list. + */ + +MSG_DEF(JSMSG_NOT_AN_ERROR, 0, 0, JSEXN_NONE, "") +MSG_DEF(JSMSG_NOT_DEFINED, 1, 1, JSEXN_REFERENCEERR, "{0} is not defined") +MSG_DEF(JSMSG_NO_REG_EXPS, 2, 1, JSEXN_INTERNALERR, "sorry, regular expression are not supported") +MSG_DEF(JSMSG_MORE_ARGS_NEEDED, 3, 3, JSEXN_NONE, "{0} requires more than {1} argument{2}") +MSG_DEF(JSMSG_BAD_CHAR, 4, 1, JSEXN_NONE, "invalid format character {0}") +MSG_DEF(JSMSG_BAD_TYPE, 5, 1, JSEXN_NONE, "unknown type {0}") +MSG_DEF(JSMSG_CANT_LOCK, 6, 0, JSEXN_NONE, "can't lock memory") +MSG_DEF(JSMSG_CANT_UNLOCK, 7, 0, JSEXN_NONE, "can't unlock memory") +MSG_DEF(JSMSG_INCOMPATIBLE_PROTO, 8, 3, JSEXN_TYPEERR, "{0}.prototype.{1} called on incompatible {2}") +MSG_DEF(JSMSG_NO_CONSTRUCTOR, 9, 1, JSEXN_NONE, "{0} has no constructor") +MSG_DEF(JSMSG_CANT_ALIAS, 10, 3, JSEXN_NONE, "can't alias {0} to {1} in class {2}") +MSG_DEF(JSMSG_UNUSED11, 11, 0, JSEXN_NONE, "") +MSG_DEF(JSMSG_BAD_SORT_ARG, 12, 0, JSEXN_TYPEERR, "invalid Array.prototype.sort argument") +MSG_DEF(JSMSG_BAD_ATOMIC_NUMBER, 13, 1, JSEXN_INTERNALERR, "internal error: no index for atom {0}") +MSG_DEF(JSMSG_TOO_MANY_LITERALS, 14, 0, JSEXN_INTERNALERR, "too many literals") +MSG_DEF(JSMSG_CANT_WATCH, 15, 1, JSEXN_NONE, "can't watch non-native objects of class {0}") +MSG_DEF(JSMSG_STACK_UNDERFLOW, 16, 2, JSEXN_INTERNALERR, "internal error compiling {0}: stack underflow at pc {1}") +MSG_DEF(JSMSG_NEED_DIET, 17, 1, JSEXN_INTERNALERR, "{0} too large") +MSG_DEF(JSMSG_UNUSED18, 18, 0, JSEXN_NONE, "") +MSG_DEF(JSMSG_READ_ONLY, 19, 1, JSEXN_ERR, "{0} is read-only") +MSG_DEF(JSMSG_BAD_FORMAL, 20, 0, JSEXN_SYNTAXERR, "malformed formal parameter") +MSG_DEF(JSMSG_SAME_FORMAL, 21, 1, JSEXN_NONE, "duplicate formal argument {0}") +MSG_DEF(JSMSG_NOT_FUNCTION, 22, 1, JSEXN_TYPEERR, "{0} is not a function") +MSG_DEF(JSMSG_NOT_CONSTRUCTOR, 23, 1, JSEXN_TYPEERR, "{0} is not a constructor") +MSG_DEF(JSMSG_STACK_OVERFLOW, 24, 1, JSEXN_INTERNALERR, "stack overflow in {0}") +MSG_DEF(JSMSG_NOT_EXPORTED, 25, 1, JSEXN_NONE, "{0} is not exported") +MSG_DEF(JSMSG_OVER_RECURSED, 26, 0, JSEXN_INTERNALERR, "too much recursion") +MSG_DEF(JSMSG_IN_NOT_OBJECT, 27, 1, JSEXN_TYPEERR, "invalid 'in' operand {0}") +MSG_DEF(JSMSG_BAD_NEW_RESULT, 28, 1, JSEXN_NONE, "invalid new expression result {0}") +MSG_DEF(JSMSG_BAD_SHARP_DEF, 29, 1, JSEXN_ERR, "invalid sharp variable definition #{0}=") +MSG_DEF(JSMSG_BAD_SHARP_USE, 30, 1, JSEXN_ERR, "invalid sharp variable use #{0}#") +MSG_DEF(JSMSG_BAD_INSTANCEOF_RHS, 31, 1, JSEXN_TYPEERR, "invalid 'instanceof' operand {0}") +MSG_DEF(JSMSG_BAD_BYTECODE, 32, 1, JSEXN_INTERNALERR, "unimplemented JavaScript bytecode {0}") +MSG_DEF(JSMSG_BAD_RADIX, 33, 1, JSEXN_ERR, "illegal radix {0}") +MSG_DEF(JSMSG_NAN, 34, 1, JSEXN_ERR, "{0} is not a number") +MSG_DEF(JSMSG_CANT_CONVERT, 35, 1, JSEXN_NONE, "can't convert {0} to an integer") +MSG_DEF(JSMSG_CYCLIC_VALUE, 36, 1, JSEXN_ERR, "cyclic {0} value") +MSG_DEF(JSMSG_PERMANENT, 37, 1, JSEXN_ERR, "{0} is permanent") +MSG_DEF(JSMSG_CANT_CONVERT_TO, 38, 2, JSEXN_TYPEERR, "can't convert {0} to {1}") +MSG_DEF(JSMSG_NO_PROPERTIES, 39, 1, JSEXN_TYPEERR, "{0} has no properties") +MSG_DEF(JSMSG_CANT_FIND_CLASS, 40, 1, JSEXN_NONE, "can't find class id {0}") +MSG_DEF(JSMSG_CANT_XDR_CLASS, 41, 1, JSEXN_NONE, "can't XDR class {0}") +MSG_DEF(JSMSG_BYTECODE_TOO_BIG, 42, 2, JSEXN_INTERNALERR, "bytecode {0} too large (limit {1})") +MSG_DEF(JSMSG_UNKNOWN_FORMAT, 43, 1, JSEXN_INTERNALERR, "unknown bytecode format {0}") +MSG_DEF(JSMSG_TOO_MANY_CON_ARGS, 44, 0, JSEXN_SYNTAXERR, "too many constructor arguments") +MSG_DEF(JSMSG_TOO_MANY_FUN_ARGS, 45, 0, JSEXN_SYNTAXERR, "too many function arguments") +MSG_DEF(JSMSG_BAD_QUANTIFIER, 46, 1, JSEXN_SYNTAXERR, "invalid quantifier {0}") +MSG_DEF(JSMSG_MIN_TOO_BIG, 47, 1, JSEXN_SYNTAXERR, "overlarge minimum {0}") +MSG_DEF(JSMSG_MAX_TOO_BIG, 48, 1, JSEXN_SYNTAXERR, "overlarge maximum {0}") +MSG_DEF(JSMSG_OUT_OF_ORDER, 49, 1, JSEXN_SYNTAXERR, "maximum {0} less than minimum") +MSG_DEF(JSMSG_ZERO_QUANTIFIER, 50, 1, JSEXN_SYNTAXERR, "zero quantifier {0}") +MSG_DEF(JSMSG_UNTERM_QUANTIFIER, 51, 1, JSEXN_SYNTAXERR, "unterminated quantifier {0}") +MSG_DEF(JSMSG_EMPTY_BEFORE_STAR, 52, 0, JSEXN_SYNTAXERR, "regular expression before * could be empty") +MSG_DEF(JSMSG_EMPTY_BEFORE_PLUS, 53, 0, JSEXN_SYNTAXERR, "regular expression before + could be empty") +MSG_DEF(JSMSG_MISSING_PAREN, 54, 0, JSEXN_SYNTAXERR, "unterminated parenthetical") +MSG_DEF(JSMSG_UNTERM_CLASS, 55, 1, JSEXN_SYNTAXERR, "unterminated character class {0}") +MSG_DEF(JSMSG_TRAILING_SLASH, 56, 0, JSEXN_SYNTAXERR, "trailing \\ in regular expression") +MSG_DEF(JSMSG_BAD_CLASS_RANGE, 57, 0, JSEXN_SYNTAXERR, "invalid range in character class") +MSG_DEF(JSMSG_BAD_FLAG, 58, 1, JSEXN_SYNTAXERR, "invalid regular expression flag {0}") +MSG_DEF(JSMSG_NO_INPUT, 59, 3, JSEXN_SYNTAXERR, "no input for /{0}/{1}{2}") +MSG_DEF(JSMSG_CANT_OPEN, 60, 2, JSEXN_NONE, "can't open {0}: {1}") +MSG_DEF(JSMSG_BAD_STRING_MASK, 61, 1, JSEXN_ERR, "invalid string escape mask {0}") +MSG_DEF(JSMSG_UNMATCHED_RIGHT_PAREN, 62, 0, JSEXN_SYNTAXERR, "unmatched ) in regular expression") +MSG_DEF(JSMSG_END_OF_DATA, 63, 0, JSEXN_NONE, "unexpected end of data") +MSG_DEF(JSMSG_SEEK_BEYOND_START, 64, 0, JSEXN_NONE, "illegal seek beyond start") +MSG_DEF(JSMSG_SEEK_BEYOND_END, 65, 0, JSEXN_NONE, "illegal seek beyond end") +MSG_DEF(JSMSG_END_SEEK, 66, 0, JSEXN_NONE, "illegal end-based seek") +MSG_DEF(JSMSG_WHITHER_WHENCE, 67, 1, JSEXN_NONE, "unknown seek whence: {0}") +MSG_DEF(JSMSG_BAD_SCRIPT_MAGIC, 68, 0, JSEXN_NONE, "bad script XDR magic number") +MSG_DEF(JSMSG_PAREN_BEFORE_FORMAL, 69, 0, JSEXN_SYNTAXERR, "missing ( before formal parameters") +MSG_DEF(JSMSG_MISSING_FORMAL, 70, 0, JSEXN_SYNTAXERR, "missing formal parameter") +MSG_DEF(JSMSG_PAREN_AFTER_FORMAL, 71, 0, JSEXN_SYNTAXERR, "missing ) after formal parameters") +MSG_DEF(JSMSG_CURLY_BEFORE_BODY, 72, 0, JSEXN_SYNTAXERR, "missing { before function body") +MSG_DEF(JSMSG_CURLY_AFTER_BODY, 73, 0, JSEXN_SYNTAXERR, "missing } after function body") +MSG_DEF(JSMSG_PAREN_BEFORE_COND, 74, 0, JSEXN_SYNTAXERR, "missing ( before condition") +MSG_DEF(JSMSG_PAREN_AFTER_COND, 75, 0, JSEXN_SYNTAXERR, "missing ) after condition") +MSG_DEF(JSMSG_NO_IMPORT_NAME, 76, 0, JSEXN_SYNTAXERR, "missing name in import statement") +MSG_DEF(JSMSG_NAME_AFTER_DOT, 77, 0, JSEXN_SYNTAXERR, "missing name after . operator") +MSG_DEF(JSMSG_BRACKET_IN_INDEX, 78, 0, JSEXN_SYNTAXERR, "missing ] in index expression") +MSG_DEF(JSMSG_NO_EXPORT_NAME, 79, 0, JSEXN_SYNTAXERR, "missing name in export statement") +MSG_DEF(JSMSG_PAREN_BEFORE_SWITCH, 80, 0, JSEXN_SYNTAXERR, "missing ( before switch expression") +MSG_DEF(JSMSG_PAREN_AFTER_SWITCH, 81, 0, JSEXN_SYNTAXERR, "missing ) after switch expression") +MSG_DEF(JSMSG_CURLY_BEFORE_SWITCH, 82, 0, JSEXN_SYNTAXERR, "missing { before switch body") +MSG_DEF(JSMSG_COLON_AFTER_CASE, 83, 0, JSEXN_SYNTAXERR, "missing : after case label") +MSG_DEF(JSMSG_WHILE_AFTER_DO, 84, 0, JSEXN_SYNTAXERR, "missing while after do-loop body") +MSG_DEF(JSMSG_PAREN_AFTER_FOR, 85, 0, JSEXN_SYNTAXERR, "missing ( after for") +MSG_DEF(JSMSG_SEMI_AFTER_FOR_INIT, 86, 0, JSEXN_SYNTAXERR, "missing ; after for-loop initializer") +MSG_DEF(JSMSG_SEMI_AFTER_FOR_COND, 87, 0, JSEXN_SYNTAXERR, "missing ; after for-loop condition") +MSG_DEF(JSMSG_PAREN_AFTER_FOR_CTRL, 88, 0, JSEXN_SYNTAXERR, "missing ) after for-loop control") +MSG_DEF(JSMSG_CURLY_BEFORE_TRY, 89, 0, JSEXN_SYNTAXERR, "missing { before try block") +MSG_DEF(JSMSG_CURLY_AFTER_TRY, 90, 0, JSEXN_SYNTAXERR, "missing } after try block") +MSG_DEF(JSMSG_PAREN_BEFORE_CATCH, 91, 0, JSEXN_SYNTAXERR, "missing ( before catch") +MSG_DEF(JSMSG_CATCH_IDENTIFIER, 92, 0, JSEXN_SYNTAXERR, "missing identifier in catch") +MSG_DEF(JSMSG_PAREN_AFTER_CATCH, 93, 0, JSEXN_SYNTAXERR, "missing ) after catch") +MSG_DEF(JSMSG_CURLY_BEFORE_CATCH, 94, 0, JSEXN_SYNTAXERR, "missing { before catch block") +MSG_DEF(JSMSG_CURLY_AFTER_CATCH, 95, 0, JSEXN_SYNTAXERR, "missing } after catch block") +MSG_DEF(JSMSG_CURLY_BEFORE_FINALLY, 96, 0, JSEXN_SYNTAXERR, "missing { before finally block") +MSG_DEF(JSMSG_CURLY_AFTER_FINALLY, 97, 0, JSEXN_SYNTAXERR, "missing } after finally block") +MSG_DEF(JSMSG_CATCH_OR_FINALLY, 98, 0, JSEXN_SYNTAXERR, "missing catch or finally after try") +MSG_DEF(JSMSG_PAREN_BEFORE_WITH, 99, 0, JSEXN_SYNTAXERR, "missing ( before with-statement object") +MSG_DEF(JSMSG_PAREN_AFTER_WITH, 100, 0, JSEXN_SYNTAXERR, "missing ) after with-statement object") +MSG_DEF(JSMSG_CURLY_IN_COMPOUND, 101, 0, JSEXN_SYNTAXERR, "missing } in compound statement") +MSG_DEF(JSMSG_NO_VARIABLE_NAME, 102, 0, JSEXN_SYNTAXERR, "missing variable name") +MSG_DEF(JSMSG_COLON_IN_COND, 103, 0, JSEXN_SYNTAXERR, "missing : in conditional expression") +MSG_DEF(JSMSG_PAREN_AFTER_ARGS, 104, 0, JSEXN_SYNTAXERR, "missing ) after argument list") +MSG_DEF(JSMSG_BRACKET_AFTER_LIST, 105, 0, JSEXN_SYNTAXERR, "missing ] after element list") +MSG_DEF(JSMSG_COLON_AFTER_ID, 106, 0, JSEXN_SYNTAXERR, "missing : after property id") +MSG_DEF(JSMSG_CURLY_AFTER_LIST, 107, 0, JSEXN_SYNTAXERR, "missing } after property list") +MSG_DEF(JSMSG_PAREN_IN_PAREN, 108, 0, JSEXN_SYNTAXERR, "missing ) in parenthetical") +MSG_DEF(JSMSG_SEMI_BEFORE_STMNT, 109, 0, JSEXN_SYNTAXERR, "missing ; before statement") +MSG_DEF(JSMSG_NO_RETURN_VALUE, 110, 1, JSEXN_TYPEERR, "function {0} does not always return a value") +MSG_DEF(JSMSG_DUPLICATE_FORMAL, 111, 1, JSEXN_TYPEERR, "duplicate formal argument {0}") +MSG_DEF(JSMSG_EQUAL_AS_ASSIGN, 112, 1, JSEXN_NONE, "test for equality (==) mistyped as assignment (=)?{0}") +MSG_DEF(JSMSG_BAD_IMPORT, 113, 0, JSEXN_SYNTAXERR, "invalid import expression") +MSG_DEF(JSMSG_TOO_MANY_DEFAULTS, 114, 0, JSEXN_SYNTAXERR, "more than one switch default") +MSG_DEF(JSMSG_TOO_MANY_CASES, 115, 0, JSEXN_INTERNALERR, "too many switch cases") +MSG_DEF(JSMSG_BAD_SWITCH, 116, 0, JSEXN_SYNTAXERR, "invalid switch statement") +MSG_DEF(JSMSG_BAD_FOR_LEFTSIDE, 117, 0, JSEXN_SYNTAXERR, "invalid for/in left-hand side") +MSG_DEF(JSMSG_CATCH_AFTER_GENERAL, 118, 0, JSEXN_SYNTAXERR, "catch after unconditional catch") +MSG_DEF(JSMSG_CATCH_WITHOUT_TRY, 119, 0, JSEXN_SYNTAXERR, "catch without try") +MSG_DEF(JSMSG_FINALLY_WITHOUT_TRY, 120, 0, JSEXN_SYNTAXERR, "finally without try") +MSG_DEF(JSMSG_LABEL_NOT_FOUND, 121, 0, JSEXN_SYNTAXERR, "label not found") +MSG_DEF(JSMSG_TOUGH_BREAK, 122, 0, JSEXN_SYNTAXERR, "invalid break") +MSG_DEF(JSMSG_BAD_CONTINUE, 123, 0, JSEXN_SYNTAXERR, "invalid continue") +MSG_DEF(JSMSG_BAD_RETURN, 124, 0, JSEXN_SYNTAXERR, "invalid return") +MSG_DEF(JSMSG_BAD_LABEL, 125, 0, JSEXN_SYNTAXERR, "invalid label") +MSG_DEF(JSMSG_DUPLICATE_LABEL, 126, 0, JSEXN_SYNTAXERR, "duplicate label") +MSG_DEF(JSMSG_VAR_HIDES_ARG, 127, 1, JSEXN_TYPEERR, "variable {0} hides argument") +MSG_DEF(JSMSG_BAD_VAR_INIT, 128, 0, JSEXN_SYNTAXERR, "invalid variable initialization") +MSG_DEF(JSMSG_BAD_LEFTSIDE_OF_ASS, 129, 0, JSEXN_SYNTAXERR, "invalid assignment left-hand side") +MSG_DEF(JSMSG_BAD_OPERAND, 130, 1, JSEXN_SYNTAXERR, "invalid {0} operand") +MSG_DEF(JSMSG_BAD_PROP_ID, 131, 0, JSEXN_SYNTAXERR, "invalid property id") +MSG_DEF(JSMSG_RESERVED_ID, 132, 1, JSEXN_SYNTAXERR, "{0} is a reserved identifier") +MSG_DEF(JSMSG_SYNTAX_ERROR, 133, 0, JSEXN_SYNTAXERR, "syntax error") +MSG_DEF(JSMSG_BAD_SHARP_VAR_DEF, 134, 0, JSEXN_SYNTAXERR, "invalid sharp variable definition") +MSG_DEF(JSMSG_BAD_PROTOTYPE, 135, 1, JSEXN_TYPEERR, "'prototype' property of {0} is not an object") +MSG_DEF(JSMSG_MISSING_EXPONENT, 136, 0, JSEXN_SYNTAXERR, "missing exponent") +MSG_DEF(JSMSG_OUT_OF_MEMORY, 137, 0, JSEXN_ERR, "out of memory") +MSG_DEF(JSMSG_UNTERMINATED_STRING, 138, 0, JSEXN_SYNTAXERR, "unterminated string literal") +MSG_DEF(JSMSG_TOO_MANY_PARENS, 139, 0, JSEXN_INTERNALERR, "too many parentheses in regular expression") +MSG_DEF(JSMSG_UNTERMINATED_COMMENT, 140, 0, JSEXN_SYNTAXERR, "unterminated comment") +MSG_DEF(JSMSG_UNTERMINATED_REGEXP, 141, 0, JSEXN_SYNTAXERR, "unterminated regular expression literal") +MSG_DEF(JSMSG_BAD_REGEXP_FLAG, 142, 0, JSEXN_SYNTAXERR, "invalid flag after regular expression") +MSG_DEF(JSMSG_SHARPVAR_TOO_BIG, 143, 0, JSEXN_SYNTAXERR, "overlarge sharp variable number") +MSG_DEF(JSMSG_ILLEGAL_CHARACTER, 144, 0, JSEXN_SYNTAXERR, "illegal character") +MSG_DEF(JSMSG_BAD_OCTAL, 145, 1, JSEXN_NONE, "{0} is not a legal ECMA-262 octal constant") +MSG_DEF(JSMSG_BAD_INDIRECT_CALL, 146, 1, JSEXN_EVALERR, "function {0} must be called directly, and not by way of a function of another name.") +MSG_DEF(JSMSG_UNCAUGHT_EXCEPTION, 147, 1, JSEXN_NONE, "uncaught exception: {0}") +MSG_DEF(JSMSG_INVALID_BACKREF, 148, 0, JSEXN_SYNTAXERR, "non-octal digit in an escape sequence that doesn't match a back-reference") +MSG_DEF(JSMSG_BAD_BACKREF, 149, 0, JSEXN_SYNTAXERR, "back-reference exceeds number of capturing parentheses") +MSG_DEF(JSMSG_PRECISION_RANGE, 150, 1, JSEXN_RANGEERR, "precision {0} out of range") +MSG_DEF(JSMSG_BAD_GETTER_OR_SETTER, 151, 1, JSEXN_SYNTAXERR, "invalid {0} usage") +MSG_DEF(JSMSG_BAD_ARRAY_LENGTH, 152, 0, JSEXN_RANGEERR, "invalid array length") +MSG_DEF(JSMSG_CANT_DESCRIBE_PROPS, 153, 1, JSEXN_NONE, "can't describe non-native properties of class {0}") +MSG_DEF(JSMSG_BAD_APPLY_ARGS, 154, 0, JSEXN_TYPEERR, "second argument to Function.prototype.apply must be an array") +MSG_DEF(JSMSG_REDECLARED_VAR, 155, 2, JSEXN_TYPEERR, "redeclaration of {0} {1}") +MSG_DEF(JSMSG_UNDECLARED_VAR, 156, 1, JSEXN_TYPEERR, "assignment to undeclared variable {0}") +MSG_DEF(JSMSG_ANON_NO_RETURN_VALUE, 157, 0, JSEXN_TYPEERR, "anonymous function does not always return a value") +MSG_DEF(JSMSG_DEPRECATED_USAGE, 158, 1, JSEXN_REFERENCEERR, "deprecated {0} usage") +MSG_DEF(JSMSG_BAD_URI, 159, 0, JSEXN_URIERR, "malformed URI sequence") +MSG_DEF(JSMSG_GETTER_ONLY, 160, 0, JSEXN_TYPEERR, "setting a property that has only a getter") +MSG_DEF(JSMSG_TRAILING_COMMA, 161, 0, JSEXN_SYNTAXERR, "trailing comma is not legal in ECMA-262 object initializers") +MSG_DEF(JSMSG_UNDEFINED_PROP, 162, 1, JSEXN_TYPEERR, "reference to undefined property {0}") +MSG_DEF(JSMSG_USELESS_EXPR, 163, 0, JSEXN_TYPEERR, "useless expression") +MSG_DEF(JSMSG_REDECLARED_PARAM, 164, 1, JSEXN_TYPEERR, "redeclaration of formal parameter {0}") +MSG_DEF(JSMSG_NEWREGEXP_FLAGGED, 165, 0, JSEXN_TYPEERR, "can't supply flags when constructing one RegExp from another") +MSG_DEF(JSMSG_RESERVED_SLOT_RANGE, 166, 0, JSEXN_RANGEERR, "reserved slot index out of range") +MSG_DEF(JSMSG_CANT_DECODE_PRINCIPALS, 167, 0, JSEXN_INTERNALERR, "can't decode JSPrincipals") +MSG_DEF(JSMSG_CANT_SEAL_OBJECT, 168, 1, JSEXN_ERR, "can't seal {0} objects") +MSG_DEF(JSMSG_CANT_UNSEAL_OBJECT, 169, 1, JSEXN_ERR, "can't unseal {0} objects") diff --git a/src/dom/js/jsapi.c b/src/dom/js/jsapi.c new file mode 100644 index 000000000..faf02d983 --- /dev/null +++ b/src/dom/js/jsapi.c @@ -0,0 +1,4187 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JavaScript API. + */ +#include "jsstddef.h" +#include +#include +#include +#include +#include "jstypes.h" +#include "jsarena.h" /* Added by JSIFY */ +#include "jsutil.h" /* Added by JSIFY */ +#include "jsclist.h" +#include "jsdhash.h" +#include "jsprf.h" +#include "jsapi.h" +#include "jsarray.h" +#include "jsatom.h" +#include "jsbool.h" +#include "jscntxt.h" +#include "jsconfig.h" +#include "jsdate.h" +#include "jsdtoa.h" +#include "jsemit.h" +#include "jsexn.h" +#include "jsfun.h" +#include "jsgc.h" +#include "jsinterp.h" +#include "jslock.h" +#include "jsmath.h" +#include "jsnum.h" +#include "jsobj.h" +#include "jsopcode.h" +#include "jsparse.h" +#include "jsregexp.h" +#include "jsscan.h" +#include "jsscope.h" +#include "jsscript.h" +#include "jsstr.h" +#include "prmjtime.h" + +#if JS_HAS_FILE_OBJECT +#include "jsfile.h" +#endif + +#ifdef HAVE_VA_LIST_AS_ARRAY +#define JS_ADDRESSOF_VA_LIST(ap) (ap) +#else +#define JS_ADDRESSOF_VA_LIST(ap) (&(ap)) +#endif + +#if defined(JS_PARANOID_REQUEST) && defined(JS_THREADSAFE) +#define CHECK_REQUEST(cx) JS_ASSERT(cx->requestDepth) +#else +#define CHECK_REQUEST(cx) ((void)0) +#endif + +JS_PUBLIC_API(int64) +JS_Now() +{ + return PRMJ_Now(); +} + +JS_PUBLIC_API(jsval) +JS_GetNaNValue(JSContext *cx) +{ + return DOUBLE_TO_JSVAL(cx->runtime->jsNaN); +} + +JS_PUBLIC_API(jsval) +JS_GetNegativeInfinityValue(JSContext *cx) +{ + return DOUBLE_TO_JSVAL(cx->runtime->jsNegativeInfinity); +} + +JS_PUBLIC_API(jsval) +JS_GetPositiveInfinityValue(JSContext *cx) +{ + return DOUBLE_TO_JSVAL(cx->runtime->jsPositiveInfinity); +} + +JS_PUBLIC_API(jsval) +JS_GetEmptyStringValue(JSContext *cx) +{ + return STRING_TO_JSVAL(cx->runtime->emptyString); +} + +static JSBool +TryArgumentFormatter(JSContext *cx, const char **formatp, JSBool fromJS, + jsval **vpp, va_list *app) +{ + const char *format; + JSArgumentFormatMap *map; + + format = *formatp; + for (map = cx->argumentFormatMap; map; map = map->next) { + if (!strncmp(format, map->format, map->length)) { + *formatp = format + map->length; + return map->formatter(cx, format, fromJS, vpp, app); + } + } + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_CHAR, format); + return JS_FALSE; +} + +JS_PUBLIC_API(JSBool) +JS_ConvertArguments(JSContext *cx, uintN argc, jsval *argv, const char *format, + ...) +{ + va_list ap; + JSBool ok; + + va_start(ap, format); + ok = JS_ConvertArgumentsVA(cx, argc, argv, format, ap); + va_end(ap); + return ok; +} + +JS_PUBLIC_API(JSBool) +JS_ConvertArgumentsVA(JSContext *cx, uintN argc, jsval *argv, + const char *format, va_list ap) +{ + jsval *sp; + JSBool required; + char c; + JSFunction *fun; + jsdouble d; + JSString *str; + JSObject *obj; + + CHECK_REQUEST(cx); + sp = argv; + required = JS_TRUE; + while ((c = *format++) != '\0') { + if (isspace(c)) + continue; + if (c == '/') { + required = JS_FALSE; + continue; + } + if (sp == argv + argc) { + if (required) { + fun = js_ValueToFunction(cx, &argv[-2], 0); + if (fun) { + char numBuf[12]; + JS_snprintf(numBuf, sizeof numBuf, "%u", argc); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_MORE_ARGS_NEEDED, + JS_GetFunctionName(fun), numBuf, + (argc == 1) ? "" : "s"); + } + return JS_FALSE; + } + break; + } + switch (c) { + case 'b': + if (!js_ValueToBoolean(cx, *sp, va_arg(ap, JSBool *))) + return JS_FALSE; + break; + case 'c': + if (!js_ValueToUint16(cx, *sp, va_arg(ap, uint16 *))) + return JS_FALSE; + break; + case 'i': + if (!js_ValueToECMAInt32(cx, *sp, va_arg(ap, int32 *))) + return JS_FALSE; + break; + case 'u': + if (!js_ValueToECMAUint32(cx, *sp, va_arg(ap, uint32 *))) + return JS_FALSE; + break; + case 'j': + if (!js_ValueToInt32(cx, *sp, va_arg(ap, int32 *))) + return JS_FALSE; + break; + case 'd': + if (!js_ValueToNumber(cx, *sp, va_arg(ap, jsdouble *))) + return JS_FALSE; + break; + case 'I': + if (!js_ValueToNumber(cx, *sp, &d)) + return JS_FALSE; + *va_arg(ap, jsdouble *) = js_DoubleToInteger(d); + break; + case 's': + case 'S': + case 'W': + str = js_ValueToString(cx, *sp); + if (!str) + return JS_FALSE; + *sp = STRING_TO_JSVAL(str); + if (c == 's') + *va_arg(ap, char **) = JS_GetStringBytes(str); + else if (c == 'W') + *va_arg(ap, jschar **) = JS_GetStringChars(str); + else + *va_arg(ap, JSString **) = str; + break; + case 'o': + if (!js_ValueToObject(cx, *sp, &obj)) + return JS_FALSE; + *sp = OBJECT_TO_JSVAL(obj); + *va_arg(ap, JSObject **) = obj; + break; + case 'f': + fun = js_ValueToFunction(cx, sp, 0); + if (!fun) + return JS_FALSE; + *sp = OBJECT_TO_JSVAL(fun->object); + *va_arg(ap, JSFunction **) = fun; + break; + case 'v': + *va_arg(ap, jsval *) = *sp; + break; + case '*': + break; + default: + format--; + if (!TryArgumentFormatter(cx, &format, JS_TRUE, &sp, + JS_ADDRESSOF_VA_LIST(ap))) { + return JS_FALSE; + } + /* NB: the formatter already updated sp, so we continue here. */ + continue; + } + sp++; + } + return JS_TRUE; +} + +JS_PUBLIC_API(jsval *) +JS_PushArguments(JSContext *cx, void **markp, const char *format, ...) +{ + va_list ap; + jsval *argv; + + va_start(ap, format); + argv = JS_PushArgumentsVA(cx, markp, format, ap); + va_end(ap); + return argv; +} + +JS_PUBLIC_API(jsval *) +JS_PushArgumentsVA(JSContext *cx, void **markp, const char *format, va_list ap) +{ + uintN argc; + jsval *argv, *sp; + char c; + const char *cp; + JSString *str; + JSFunction *fun; + JSStackHeader *sh; + + CHECK_REQUEST(cx); + *markp = NULL; + argc = 0; + for (cp = format; (c = *cp) != '\0'; cp++) { + /* + * Count non-space non-star characters as individual jsval arguments. + * This may over-allocate stack, but we'll fix below. + */ + if (isspace(c) || c == '*') + continue; + argc++; + } + sp = js_AllocStack(cx, argc, markp); + if (!sp) + return NULL; + argv = sp; + while ((c = *format++) != '\0') { + if (isspace(c) || c == '*') + continue; + switch (c) { + case 'b': + *sp = BOOLEAN_TO_JSVAL((JSBool) va_arg(ap, int)); + break; + case 'c': + *sp = INT_TO_JSVAL((uint16) va_arg(ap, unsigned int)); + break; + case 'i': + case 'j': + if (!js_NewNumberValue(cx, (jsdouble) va_arg(ap, int32), sp)) + goto bad; + break; + case 'u': + if (!js_NewNumberValue(cx, (jsdouble) va_arg(ap, uint32), sp)) + goto bad; + break; + case 'd': + case 'I': + if (!js_NewDoubleValue(cx, va_arg(ap, jsdouble), sp)) + goto bad; + break; + case 's': + str = JS_NewStringCopyZ(cx, va_arg(ap, char *)); + if (!str) + goto bad; + *sp = STRING_TO_JSVAL(str); + break; + case 'W': + str = JS_NewUCStringCopyZ(cx, va_arg(ap, jschar *)); + if (!str) + goto bad; + *sp = STRING_TO_JSVAL(str); + break; + case 'S': + str = va_arg(ap, JSString *); + *sp = STRING_TO_JSVAL(str); + break; + case 'o': + *sp = OBJECT_TO_JSVAL(va_arg(ap, JSObject *)); + break; + case 'f': + fun = va_arg(ap, JSFunction *); + *sp = fun ? OBJECT_TO_JSVAL(fun->object) : JSVAL_NULL; + break; + case 'v': + *sp = va_arg(ap, jsval); + break; + default: + format--; + if (!TryArgumentFormatter(cx, &format, JS_FALSE, &sp, + JS_ADDRESSOF_VA_LIST(ap))) { + goto bad; + } + /* NB: the formatter already updated sp, so we continue here. */ + continue; + } + sp++; + } + + /* + * We may have overallocated stack due to a multi-character format code + * handled by a JSArgumentFormatter. Give back that stack space! + */ + JS_ASSERT(sp <= argv + argc); + if (sp < argv + argc) { + /* Return slots not pushed to the current stack arena. */ + cx->stackPool.current->avail = (jsuword)sp; + + /* Reduce the count of slots the GC will scan in this stack segment. */ + sh = cx->stackHeaders; + JS_ASSERT(JS_STACK_SEGMENT(sh) + sh->nslots == argv + argc); + sh->nslots -= argc - (sp - argv); + } + return argv; + +bad: + js_FreeStack(cx, *markp); + return NULL; +} + +JS_PUBLIC_API(void) +JS_PopArguments(JSContext *cx, void *mark) +{ + CHECK_REQUEST(cx); + js_FreeStack(cx, mark); +} + +JS_PUBLIC_API(JSBool) +JS_AddArgumentFormatter(JSContext *cx, const char *format, + JSArgumentFormatter formatter) +{ + size_t length; + JSArgumentFormatMap **mpp, *map; + + length = strlen(format); + mpp = &cx->argumentFormatMap; + while ((map = *mpp) != NULL) { + /* Insert before any shorter string to match before prefixes. */ + if (map->length < length) + break; + if (map->length == length && !strcmp(map->format, format)) + goto out; + mpp = &map->next; + } + map = (JSArgumentFormatMap *) JS_malloc(cx, sizeof *map); + if (!map) + return JS_FALSE; + map->format = format; + map->length = length; + map->next = *mpp; + *mpp = map; +out: + map->formatter = formatter; + return JS_TRUE; +} + +JS_PUBLIC_API(void) +JS_RemoveArgumentFormatter(JSContext *cx, const char *format) +{ + size_t length; + JSArgumentFormatMap **mpp, *map; + + length = strlen(format); + mpp = &cx->argumentFormatMap; + while ((map = *mpp) != NULL) { + if (map->length == length && !strcmp(map->format, format)) { + *mpp = map->next; + JS_free(cx, map); + return; + } + mpp = &map->next; + } +} + +JS_PUBLIC_API(JSBool) +JS_ConvertValue(JSContext *cx, jsval v, JSType type, jsval *vp) +{ + JSBool ok, b; + JSObject *obj; + JSFunction *fun; + JSString *str; + jsdouble d, *dp; + + CHECK_REQUEST(cx); + switch (type) { + case JSTYPE_VOID: + *vp = JSVAL_VOID; + ok = JS_TRUE; + break; + case JSTYPE_OBJECT: + ok = js_ValueToObject(cx, v, &obj); + if (ok) + *vp = OBJECT_TO_JSVAL(obj); + break; + case JSTYPE_FUNCTION: + fun = js_ValueToFunction(cx, &v, JSV2F_SEARCH_STACK); + ok = (fun != NULL); + if (ok) + *vp = OBJECT_TO_JSVAL(fun->object); + break; + case JSTYPE_STRING: + str = js_ValueToString(cx, v); + ok = (str != NULL); + if (ok) + *vp = STRING_TO_JSVAL(str); + break; + case JSTYPE_NUMBER: + ok = js_ValueToNumber(cx, v, &d); + if (ok) { + dp = js_NewDouble(cx, d); + ok = (dp != NULL); + if (ok) + *vp = DOUBLE_TO_JSVAL(dp); + } + break; + case JSTYPE_BOOLEAN: + ok = js_ValueToBoolean(cx, v, &b); + if (ok) + *vp = BOOLEAN_TO_JSVAL(b); + break; + default: { + char numBuf[12]; + JS_snprintf(numBuf, sizeof numBuf, "%d", (int)type); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_TYPE, + numBuf); + ok = JS_FALSE; + break; + } + } + return ok; +} + +JS_PUBLIC_API(JSBool) +JS_ValueToObject(JSContext *cx, jsval v, JSObject **objp) +{ + CHECK_REQUEST(cx); + return js_ValueToObject(cx, v, objp); +} + +JS_PUBLIC_API(JSFunction *) +JS_ValueToFunction(JSContext *cx, jsval v) +{ + CHECK_REQUEST(cx); + return js_ValueToFunction(cx, &v, JSV2F_SEARCH_STACK); +} + +JS_PUBLIC_API(JSFunction *) +JS_ValueToConstructor(JSContext *cx, jsval v) +{ + CHECK_REQUEST(cx); + return js_ValueToFunction(cx, &v, JSV2F_SEARCH_STACK); +} + +JS_PUBLIC_API(JSString *) +JS_ValueToString(JSContext *cx, jsval v) +{ + CHECK_REQUEST(cx); + return js_ValueToString(cx, v); +} + +JS_PUBLIC_API(JSBool) +JS_ValueToNumber(JSContext *cx, jsval v, jsdouble *dp) +{ + CHECK_REQUEST(cx); + return js_ValueToNumber(cx, v, dp); +} + +JS_PUBLIC_API(JSBool) +JS_ValueToECMAInt32(JSContext *cx, jsval v, int32 *ip) +{ + CHECK_REQUEST(cx); + return js_ValueToECMAInt32(cx, v, ip); +} + +JS_PUBLIC_API(JSBool) +JS_ValueToECMAUint32(JSContext *cx, jsval v, uint32 *ip) +{ + CHECK_REQUEST(cx); + return js_ValueToECMAUint32(cx, v, ip); +} + +JS_PUBLIC_API(JSBool) +JS_ValueToInt32(JSContext *cx, jsval v, int32 *ip) +{ + CHECK_REQUEST(cx); + return js_ValueToInt32(cx, v, ip); +} + +JS_PUBLIC_API(JSBool) +JS_ValueToUint16(JSContext *cx, jsval v, uint16 *ip) +{ + CHECK_REQUEST(cx); + return js_ValueToUint16(cx, v, ip); +} + +JS_PUBLIC_API(JSBool) +JS_ValueToBoolean(JSContext *cx, jsval v, JSBool *bp) +{ + CHECK_REQUEST(cx); + return js_ValueToBoolean(cx, v, bp); +} + +JS_PUBLIC_API(JSType) +JS_TypeOfValue(JSContext *cx, jsval v) +{ + JSType type; + JSObject *obj; + JSObjectOps *ops; + JSClass *clasp; + + CHECK_REQUEST(cx); + if (JSVAL_IS_OBJECT(v)) { + /* XXX JSVAL_IS_OBJECT(v) is true for null too! Can we change ECMA? */ + obj = JSVAL_TO_OBJECT(v); + if (obj && + (ops = obj->map->ops, + ops == &js_ObjectOps + ? (clasp = OBJ_GET_CLASS(cx, obj), + clasp->call || clasp == &js_FunctionClass) + : ops->call != 0)) { + type = JSTYPE_FUNCTION; + } else { + type = JSTYPE_OBJECT; + } + } else if (JSVAL_IS_NUMBER(v)) { + type = JSTYPE_NUMBER; + } else if (JSVAL_IS_STRING(v)) { + type = JSTYPE_STRING; + } else if (JSVAL_IS_BOOLEAN(v)) { + type = JSTYPE_BOOLEAN; + } else { + type = JSTYPE_VOID; + } + return type; +} + +JS_PUBLIC_API(const char *) +JS_GetTypeName(JSContext *cx, JSType type) +{ + if ((uintN)type >= (uintN)JSTYPE_LIMIT) + return NULL; + return js_type_str[type]; +} + +/************************************************************************/ + +JS_PUBLIC_API(JSRuntime *) +JS_NewRuntime(uint32 maxbytes) +{ + JSRuntime *rt; + +#ifdef DEBUG + JS_BEGIN_MACRO + /* + * This code asserts that the numbers associated with the error names in + * jsmsg.def are monotonically increasing. It uses values for the error + * names enumerated in jscntxt.c. It's not a compiletime check, but it's + * better than nothing. + */ + int errorNumber = 0; +#define MSG_DEF(name, number, count, exception, format) \ + JS_ASSERT(name == errorNumber++); +#include "js.msg" +#undef MSG_DEF + JS_END_MACRO; +#endif /* DEBUG */ + + if (!js_InitScriptGlobals()) + return NULL; + if (!js_InitStringGlobals()) + return NULL; + rt = (JSRuntime *) malloc(sizeof(JSRuntime)); + if (!rt) + return NULL; + + /* Initialize infallibly first, so we can goto bad and JS_DestroyRuntime. */ + memset(rt, 0, sizeof(JSRuntime)); + JS_INIT_CLIST(&rt->contextList); + JS_INIT_CLIST(&rt->trapList); + JS_INIT_CLIST(&rt->watchPointList); + + if (!js_InitGC(rt, maxbytes)) + goto bad; +#ifdef JS_THREADSAFE + rt->gcLock = JS_NEW_LOCK(); + if (!rt->gcLock) + goto bad; + rt->gcDone = JS_NEW_CONDVAR(rt->gcLock); + if (!rt->gcDone) + goto bad; + rt->requestDone = JS_NEW_CONDVAR(rt->gcLock); + if (!rt->requestDone) + goto bad; + js_SetupLocks(8, 16); /* this is asymmetric with JS_ShutDown. */ + rt->rtLock = JS_NEW_LOCK(); + if (!rt->rtLock) + goto bad; + rt->stateChange = JS_NEW_CONDVAR(rt->gcLock); + if (!rt->stateChange) + goto bad; + rt->setSlotLock = JS_NEW_LOCK(); + if (!rt->setSlotLock) + goto bad; + rt->setSlotDone = JS_NEW_CONDVAR(rt->setSlotLock); + if (!rt->setSlotDone) + goto bad; + rt->scopeSharingDone = JS_NEW_CONDVAR(rt->gcLock); + if (!rt->scopeSharingDone) + goto bad; + rt->scopeSharingTodo = NO_SCOPE_SHARING_TODO; +#endif + rt->propertyCache.empty = JS_TRUE; + if (!js_InitPropertyTree(rt)) + goto bad; + return rt; + +bad: + JS_DestroyRuntime(rt); + return NULL; +} + +JS_PUBLIC_API(void) +JS_DestroyRuntime(JSRuntime *rt) +{ +#ifdef DEBUG + /* Don't hurt everyone in leaky ol' Mozilla with a fatal JS_ASSERT! */ + if (!JS_CLIST_IS_EMPTY(&rt->contextList)) { + JSContext *cx, *iter = NULL; + uintN cxcount = 0; + while ((cx = js_ContextIterator(rt, JS_TRUE, &iter)) != NULL) + cxcount++; + fprintf(stderr, +"JS API usage error: %u contexts left in runtime upon JS_DestroyRuntime.\n", + cxcount); + } +#endif + + js_FinishAtomState(&rt->atomState); + js_FinishGC(rt); +#ifdef JS_THREADSAFE + if (rt->gcLock) + JS_DESTROY_LOCK(rt->gcLock); + if (rt->gcDone) + JS_DESTROY_CONDVAR(rt->gcDone); + if (rt->requestDone) + JS_DESTROY_CONDVAR(rt->requestDone); + if (rt->rtLock) + JS_DESTROY_LOCK(rt->rtLock); + if (rt->stateChange) + JS_DESTROY_CONDVAR(rt->stateChange); + if (rt->setSlotLock) + JS_DESTROY_LOCK(rt->setSlotLock); + if (rt->setSlotDone) + JS_DESTROY_CONDVAR(rt->setSlotDone); + if (rt->scopeSharingDone) + JS_DESTROY_CONDVAR(rt->scopeSharingDone); +#endif + js_FinishPropertyTree(rt); + free(rt); +} + +JS_PUBLIC_API(void) +JS_ShutDown(void) +{ + JS_ArenaShutDown(); + js_FinishDtoa(); + js_FreeScriptGlobals(); + js_FreeStringGlobals(); +#ifdef JS_THREADSAFE + js_CleanupLocks(); +#endif +} + +JS_PUBLIC_API(void *) +JS_GetRuntimePrivate(JSRuntime *rt) +{ + return rt->data; +} + +JS_PUBLIC_API(void) +JS_SetRuntimePrivate(JSRuntime *rt, void *data) +{ + rt->data = data; +} + +#ifdef JS_THREADSAFE + +JS_PUBLIC_API(void) +JS_BeginRequest(JSContext *cx) +{ + JSRuntime *rt; + + JS_ASSERT(cx->thread); + if (!cx->requestDepth) { + /* Wait until the GC is finished. */ + rt = cx->runtime; + JS_LOCK_GC(rt); + + /* NB: we use cx->thread here, not js_CurrentThreadId(). */ + if (rt->gcThread != cx->thread) { + while (rt->gcLevel > 0) + JS_AWAIT_GC_DONE(rt); + } + + /* Indicate that a request is running. */ + rt->requestCount++; + cx->requestDepth = 1; + JS_UNLOCK_GC(rt); + return; + } + cx->requestDepth++; +} + +JS_PUBLIC_API(void) +JS_EndRequest(JSContext *cx) +{ + JSRuntime *rt; + JSScope *scope, **todop; + uintN nshares; + + CHECK_REQUEST(cx); + JS_ASSERT(cx->requestDepth > 0); + if (cx->requestDepth == 1) { + /* Lock before clearing to interlock with ClaimScope, in jslock.c. */ + rt = cx->runtime; + JS_LOCK_GC(rt); + cx->requestDepth = 0; + + /* See whether cx has any single-threaded scopes to start sharing. */ + todop = &rt->scopeSharingTodo; + nshares = 0; + while ((scope = *todop) != NO_SCOPE_SHARING_TODO) { + if (scope->ownercx != cx) { + todop = &scope->u.link; + continue; + } + *todop = scope->u.link; + scope->u.link = NULL; /* null u.link for sanity ASAP */ + + /* + * If js_DropObjectMap returns null, we held the last ref to scope. + * The waiting thread(s) must have been killed, after which the GC + * collected the object that held this scope. Unlikely, because it + * requires that the GC ran (e.g., from a branch callback) during + * this request, but possible. + */ + if (js_DropObjectMap(cx, &scope->map, NULL)) { + js_InitLock(&scope->lock); + scope->u.count = 0; /* NULL may not pun as 0 */ + js_FinishSharingScope(rt, scope); /* set ownercx = NULL */ + nshares++; + } + } + if (nshares) + JS_NOTIFY_ALL_CONDVAR(rt->scopeSharingDone); + + /* Give the GC a chance to run if this was the last request running. */ + JS_ASSERT(rt->requestCount > 0); + rt->requestCount--; + if (rt->requestCount == 0) + JS_NOTIFY_REQUEST_DONE(rt); + + JS_UNLOCK_GC(rt); + return; + } + + cx->requestDepth--; +} + +/* Yield to pending GC operations, regardless of request depth */ +JS_PUBLIC_API(void) +JS_YieldRequest(JSContext *cx) +{ + JSRuntime *rt; + + JS_ASSERT(cx->thread); + CHECK_REQUEST(cx); + + rt = cx->runtime; + JS_LOCK_GC(rt); + JS_ASSERT(rt->requestCount > 0); + rt->requestCount--; + if (rt->requestCount == 0) + JS_NOTIFY_REQUEST_DONE(rt); + JS_UNLOCK_GC(rt); + /* XXXbe give the GC or another request calling it a chance to run here? + Assumes FIFO scheduling */ + JS_LOCK_GC(rt); + rt->requestCount++; + JS_UNLOCK_GC(rt); +} + +JS_PUBLIC_API(jsrefcount) +JS_SuspendRequest(JSContext *cx) +{ + jsrefcount saveDepth = cx->requestDepth; + + while (cx->requestDepth) + JS_EndRequest(cx); + return saveDepth; +} + +JS_PUBLIC_API(void) +JS_ResumeRequest(JSContext *cx, jsrefcount saveDepth) +{ + JS_ASSERT(!cx->requestDepth); + while (--saveDepth >= 0) + JS_BeginRequest(cx); +} + +#endif /* JS_THREADSAFE */ + +JS_PUBLIC_API(void) +JS_Lock(JSRuntime *rt) +{ + JS_LOCK_RUNTIME(rt); +} + +JS_PUBLIC_API(void) +JS_Unlock(JSRuntime *rt) +{ + JS_UNLOCK_RUNTIME(rt); +} + +JS_PUBLIC_API(JSContext *) +JS_NewContext(JSRuntime *rt, size_t stackChunkSize) +{ + return js_NewContext(rt, stackChunkSize); +} + +JS_PUBLIC_API(void) +JS_DestroyContext(JSContext *cx) +{ + js_DestroyContext(cx, JS_FORCE_GC); +} + +JS_PUBLIC_API(void) +JS_DestroyContextNoGC(JSContext *cx) +{ + js_DestroyContext(cx, JS_NO_GC); +} + +JS_PUBLIC_API(void) +JS_DestroyContextMaybeGC(JSContext *cx) +{ + js_DestroyContext(cx, JS_MAYBE_GC); +} + +JS_PUBLIC_API(void *) +JS_GetContextPrivate(JSContext *cx) +{ + return cx->data; +} + +JS_PUBLIC_API(void) +JS_SetContextPrivate(JSContext *cx, void *data) +{ + cx->data = data; +} + +JS_PUBLIC_API(JSRuntime *) +JS_GetRuntime(JSContext *cx) +{ + return cx->runtime; +} + +JS_PUBLIC_API(JSContext *) +JS_ContextIterator(JSRuntime *rt, JSContext **iterp) +{ + return js_ContextIterator(rt, JS_TRUE, iterp); +} + +JS_PUBLIC_API(JSVersion) +JS_GetVersion(JSContext *cx) +{ + return cx->version; +} + +JS_PUBLIC_API(JSVersion) +JS_SetVersion(JSContext *cx, JSVersion version) +{ + JSVersion oldVersion; + + oldVersion = cx->version; + if (version == oldVersion) + return oldVersion; + + cx->version = version; + +#if !JS_BUG_FALLIBLE_EQOPS + if (cx->version == JSVERSION_1_2) { + cx->jsop_eq = JSOP_NEW_EQ; + cx->jsop_ne = JSOP_NEW_NE; + } else { + cx->jsop_eq = JSOP_EQ; + cx->jsop_ne = JSOP_NE; + } +#endif /* !JS_BUG_FALLIBLE_EQOPS */ + + return oldVersion; +} + +static struct v2smap { + JSVersion version; + const char *string; +} v2smap[] = { + {JSVERSION_1_0, "1.0"}, + {JSVERSION_1_1, "1.1"}, + {JSVERSION_1_2, "1.2"}, + {JSVERSION_1_3, "1.3"}, + {JSVERSION_1_4, "1.4"}, + {JSVERSION_ECMA_3, "ECMAv3"}, + {JSVERSION_1_5, "1.5"}, + {JSVERSION_DEFAULT, "default"}, + {JSVERSION_UNKNOWN, NULL}, /* must be last, NULL is sentinel */ +}; + +JS_PUBLIC_API(const char *) +JS_VersionToString(JSVersion version) +{ + int i; + + for (i = 0; v2smap[i].string; i++) + if (v2smap[i].version == version) + return v2smap[i].string; + return "unknown"; +} + +JS_PUBLIC_API(JSVersion) +JS_StringToVersion(const char *string) +{ + int i; + + for (i = 0; v2smap[i].string; i++) + if (strcmp(v2smap[i].string, string) == 0) + return v2smap[i].version; + return JSVERSION_UNKNOWN; +} + +JS_PUBLIC_API(uint32) +JS_GetOptions(JSContext *cx) +{ + return cx->options; +} + +JS_PUBLIC_API(uint32) +JS_SetOptions(JSContext *cx, uint32 options) +{ + uint32 oldopts = cx->options; + cx->options = options; + return oldopts; +} + +JS_PUBLIC_API(uint32) +JS_ToggleOptions(JSContext *cx, uint32 options) +{ + uint32 oldopts = cx->options; + cx->options ^= options; + return oldopts; +} + +JS_PUBLIC_API(const char *) +JS_GetImplementationVersion(void) +{ + return "JavaScript-C 1.5 pre-release 6 2004-01-27"; +} + + +JS_PUBLIC_API(JSObject *) +JS_GetGlobalObject(JSContext *cx) +{ + return cx->globalObject; +} + +JS_PUBLIC_API(void) +JS_SetGlobalObject(JSContext *cx, JSObject *obj) +{ + cx->globalObject = obj; +} + +static JSObject * +InitFunctionAndObjectClasses(JSContext *cx, JSObject *obj) +{ + JSDHashTable *table; + JSBool resolving; + JSRuntime *rt; + JSResolvingKey key; + JSResolvingEntry *entry; + JSObject *fun_proto, *obj_proto; + + /* If cx has no global object, use obj so prototypes can be found. */ + if (!cx->globalObject) + cx->globalObject = obj; + + /* Record Function and Object in cx->resolvingTable, if we are resolving. */ + table = cx->resolvingTable; + resolving = (table && table->entryCount); + if (resolving) { + rt = cx->runtime; + key.obj = obj; + key.id = (jsid) rt->atomState.FunctionAtom; + entry = (JSResolvingEntry *) + JS_DHashTableOperate(table, &key, JS_DHASH_ADD); + if (entry && entry->key.obj && (entry->flags & JSRESFLAG_LOOKUP)) { + /* Already resolving Function, record Object too. */ + JS_ASSERT(entry->key.obj == obj); + key.id = (jsid) rt->atomState.ObjectAtom; + entry = (JSResolvingEntry *) + JS_DHashTableOperate(table, &key, JS_DHASH_ADD); + } + if (!entry) { + JS_ReportOutOfMemory(cx); + return NULL; + } + JS_ASSERT(!entry->key.obj && entry->flags == 0); + entry->key = key; + entry->flags = JSRESFLAG_LOOKUP; + } + + /* Initialize the function class first so constructors can be made. */ + fun_proto = js_InitFunctionClass(cx, obj); + if (!fun_proto) + goto out; + + /* Initialize the object class next so Object.prototype works. */ + obj_proto = js_InitObjectClass(cx, obj); + if (!obj_proto) { + fun_proto = NULL; + goto out; + } + + /* Function.prototype and the global object delegate to Object.prototype. */ + OBJ_SET_PROTO(cx, fun_proto, obj_proto); + if (!OBJ_GET_PROTO(cx, obj)) + OBJ_SET_PROTO(cx, obj, obj_proto); + +out: + /* If resolving, remove the other entry (Object or Function) from table. */ + if (resolving) + JS_DHashTableOperate(table, &key, JS_DHASH_REMOVE); + return fun_proto; +} + +JS_PUBLIC_API(JSBool) +JS_InitStandardClasses(JSContext *cx, JSObject *obj) +{ + CHECK_REQUEST(cx); + +#if JS_HAS_UNDEFINED +{ + /* Define a top-level property 'undefined' with the undefined value. */ + JSAtom *atom = cx->runtime->atomState.typeAtoms[JSTYPE_VOID]; + if (!OBJ_DEFINE_PROPERTY(cx, obj, (jsid)atom, JSVAL_VOID, NULL, NULL, + JSPROP_PERMANENT, NULL)) { + return JS_FALSE; + } +} +#endif + + /* Function and Object require cooperative bootstrapping magic. */ + if (!InitFunctionAndObjectClasses(cx, obj)) + return JS_FALSE; + + /* Initialize the rest of the standard objects and functions. */ + return js_InitArrayClass(cx, obj) && + js_InitBooleanClass(cx, obj) && + js_InitMathClass(cx, obj) && + js_InitNumberClass(cx, obj) && + js_InitStringClass(cx, obj) && +#if JS_HAS_CALL_OBJECT + js_InitCallClass(cx, obj) && +#endif +#if JS_HAS_REGEXPS + js_InitRegExpClass(cx, obj) && +#endif +#if JS_HAS_SCRIPT_OBJECT + js_InitScriptClass(cx, obj) && +#endif +#if JS_HAS_ERROR_EXCEPTIONS + js_InitExceptionClasses(cx, obj) && +#endif +#if JS_HAS_FILE_OBJECT + js_InitFileClass(cx, obj, JS_TRUE) && +#endif + js_InitDateClass(cx, obj); +} + +#define ATOM_OFFSET(name) offsetof(JSAtomState, name##Atom) +#define OFFSET_TO_ATOM(rt,off) (*(JSAtom **)((char*)&(rt)->atomState + (off))) + +/* + * Table of class initializers and their atom offsets in rt->atomState. + * If you add a "standard" class, remember to update this table. + */ +static struct { + JSObjectOp init; + size_t atomOffset; +} standard_class_atoms[] = { + {InitFunctionAndObjectClasses, ATOM_OFFSET(Function)}, + {InitFunctionAndObjectClasses, ATOM_OFFSET(Object)}, + {js_InitArrayClass, ATOM_OFFSET(Array)}, + {js_InitBooleanClass, ATOM_OFFSET(Boolean)}, + {js_InitDateClass, ATOM_OFFSET(Date)}, + {js_InitMathClass, ATOM_OFFSET(Math)}, + {js_InitNumberClass, ATOM_OFFSET(Number)}, + {js_InitStringClass, ATOM_OFFSET(String)}, +#if JS_HAS_CALL_OBJECT + {js_InitCallClass, ATOM_OFFSET(Call)}, +#endif +#if JS_HAS_ERROR_EXCEPTIONS + {js_InitExceptionClasses, ATOM_OFFSET(Error)}, +#endif +#if JS_HAS_REGEXPS + {js_InitRegExpClass, ATOM_OFFSET(RegExp)}, +#endif +#if JS_HAS_SCRIPT_OBJECT + {js_InitScriptClass, ATOM_OFFSET(Script)}, +#endif + {NULL, 0} +}; + +/* + * Table of top-level function and constant names and their init functions. + * If you add a "standard" global function or property, remember to update + * this table. + */ +typedef struct JSStdName { + JSObjectOp init; + size_t atomOffset; /* offset of atom pointer in JSAtomState */ + const char *name; /* null if atom is pre-pinned, else name */ +} JSStdName; + +static JSAtom * +StdNameToAtom(JSContext *cx, JSStdName *stdn) +{ + size_t offset; + JSAtom *atom; + const char *name; + + offset = stdn->atomOffset; + atom = OFFSET_TO_ATOM(cx->runtime, offset); + if (!atom) { + name = stdn->name; + if (name) { + atom = js_Atomize(cx, name, strlen(name), ATOM_PINNED); + OFFSET_TO_ATOM(cx->runtime, offset) = atom; + } + } + return atom; +} + +#define EAGERLY_PINNED_ATOM(name) ATOM_OFFSET(name), NULL +#define LAZILY_PINNED_ATOM(name) ATOM_OFFSET(lazy.name), js_##name##_str + +static JSStdName standard_class_names[] = { + /* ECMA requires that eval be a direct property of the global object. */ + {js_InitObjectClass, EAGERLY_PINNED_ATOM(eval)}, + + /* Global properties and functions defined by the Number class. */ + {js_InitNumberClass, LAZILY_PINNED_ATOM(NaN)}, + {js_InitNumberClass, LAZILY_PINNED_ATOM(Infinity)}, + {js_InitNumberClass, LAZILY_PINNED_ATOM(isNaN)}, + {js_InitNumberClass, LAZILY_PINNED_ATOM(isFinite)}, + {js_InitNumberClass, LAZILY_PINNED_ATOM(parseFloat)}, + {js_InitNumberClass, LAZILY_PINNED_ATOM(parseInt)}, + + /* String global functions. */ + {js_InitStringClass, LAZILY_PINNED_ATOM(escape)}, + {js_InitStringClass, LAZILY_PINNED_ATOM(unescape)}, + {js_InitStringClass, LAZILY_PINNED_ATOM(decodeURI)}, + {js_InitStringClass, LAZILY_PINNED_ATOM(encodeURI)}, + {js_InitStringClass, LAZILY_PINNED_ATOM(decodeURIComponent)}, + {js_InitStringClass, LAZILY_PINNED_ATOM(encodeURIComponent)}, +#if JS_HAS_UNEVAL + {js_InitStringClass, LAZILY_PINNED_ATOM(uneval)}, +#endif + + /* Exception constructors. */ +#if JS_HAS_ERROR_EXCEPTIONS + {js_InitExceptionClasses, EAGERLY_PINNED_ATOM(Error)}, + {js_InitExceptionClasses, LAZILY_PINNED_ATOM(InternalError)}, + {js_InitExceptionClasses, LAZILY_PINNED_ATOM(EvalError)}, + {js_InitExceptionClasses, LAZILY_PINNED_ATOM(RangeError)}, + {js_InitExceptionClasses, LAZILY_PINNED_ATOM(ReferenceError)}, + {js_InitExceptionClasses, LAZILY_PINNED_ATOM(SyntaxError)}, + {js_InitExceptionClasses, LAZILY_PINNED_ATOM(TypeError)}, + {js_InitExceptionClasses, LAZILY_PINNED_ATOM(URIError)}, +#endif + + {NULL, 0, NULL} +}; + +static JSStdName object_prototype_names[] = { + /* Object.prototype properties (global delegates to Object.prototype). */ + {js_InitObjectClass, EAGERLY_PINNED_ATOM(proto)}, + {js_InitObjectClass, EAGERLY_PINNED_ATOM(parent)}, + {js_InitObjectClass, EAGERLY_PINNED_ATOM(count)}, +#if JS_HAS_TOSOURCE + {js_InitObjectClass, EAGERLY_PINNED_ATOM(toSource)}, +#endif + {js_InitObjectClass, EAGERLY_PINNED_ATOM(toString)}, + {js_InitObjectClass, EAGERLY_PINNED_ATOM(toLocaleString)}, + {js_InitObjectClass, EAGERLY_PINNED_ATOM(valueOf)}, +#if JS_HAS_OBJ_WATCHPOINT + {js_InitObjectClass, LAZILY_PINNED_ATOM(watch)}, + {js_InitObjectClass, LAZILY_PINNED_ATOM(unwatch)}, +#endif +#if JS_HAS_NEW_OBJ_METHODS + {js_InitObjectClass, LAZILY_PINNED_ATOM(hasOwnProperty)}, + {js_InitObjectClass, LAZILY_PINNED_ATOM(isPrototypeOf)}, + {js_InitObjectClass, LAZILY_PINNED_ATOM(propertyIsEnumerable)}, +#endif +#if JS_HAS_GETTER_SETTER + {js_InitObjectClass, LAZILY_PINNED_ATOM(defineGetter)}, + {js_InitObjectClass, LAZILY_PINNED_ATOM(defineSetter)}, + {js_InitObjectClass, LAZILY_PINNED_ATOM(lookupGetter)}, + {js_InitObjectClass, LAZILY_PINNED_ATOM(lookupSetter)}, +#endif + + {NULL, 0, NULL} +}; + +#undef EAGERLY_PINNED_ATOM +#undef LAZILY_PINNED_ATOM + +JS_PUBLIC_API(JSBool) +JS_ResolveStandardClass(JSContext *cx, JSObject *obj, jsval id, + JSBool *resolved) +{ + JSString *idstr; + JSRuntime *rt; + JSAtom *atom; + JSObjectOp init; + uintN i; + + CHECK_REQUEST(cx); + *resolved = JS_FALSE; + + if (!JSVAL_IS_STRING(id)) + return JS_TRUE; + idstr = JSVAL_TO_STRING(id); + rt = cx->runtime; + +#if JS_HAS_UNDEFINED + /* See if we're resolving 'undefined', and define it if so. */ + atom = rt->atomState.typeAtoms[JSTYPE_VOID]; + if (idstr == ATOM_TO_STRING(atom)) { + *resolved = JS_TRUE; + return OBJ_DEFINE_PROPERTY(cx, obj, (jsid)atom, JSVAL_VOID, NULL, NULL, + JSPROP_PERMANENT, NULL); + } +#endif + + /* Try for class constructors/prototypes named by well-known atoms. */ + init = NULL; + for (i = 0; standard_class_atoms[i].init; i++) { + atom = OFFSET_TO_ATOM(rt, standard_class_atoms[i].atomOffset); + if (idstr == ATOM_TO_STRING(atom)) { + init = standard_class_atoms[i].init; + break; + } + } + + if (!init) { + /* Try less frequently used top-level functions and constants. */ + for (i = 0; standard_class_names[i].init; i++) { + atom = StdNameToAtom(cx, &standard_class_names[i]); + if (!atom) + return JS_FALSE; + if (idstr == ATOM_TO_STRING(atom)) { + init = standard_class_names[i].init; + break; + } + } + + if (!init && !OBJ_GET_PROTO(cx, obj)) { + /* + * Try even less frequently used names delegated from the global + * object to Object.prototype, but only if the Object class hasn't + * yet been initialized. + */ + for (i = 0; object_prototype_names[i].init; i++) { + atom = StdNameToAtom(cx, &object_prototype_names[i]); + if (!atom) + return JS_FALSE; + if (idstr == ATOM_TO_STRING(atom)) { + init = standard_class_names[i].init; + break; + } + } + } + } + + if (init) { + if (!init(cx, obj)) + return JS_FALSE; + *resolved = JS_TRUE; + } + return JS_TRUE; +} + +static JSBool +HasOwnProperty(JSContext *cx, JSObject *obj, JSAtom *atom, JSBool *ownp) +{ + JSObject *pobj; + JSProperty *prop; + + if (!OBJ_LOOKUP_PROPERTY(cx, obj, (jsid)atom, &pobj, &prop)) + return JS_FALSE; + if (prop) + OBJ_DROP_PROPERTY(cx, pobj, prop); + *ownp = (pobj == obj && prop); + return JS_TRUE; +} + +JS_PUBLIC_API(JSBool) +JS_EnumerateStandardClasses(JSContext *cx, JSObject *obj) +{ + JSRuntime *rt; + JSAtom *atom; + JSBool found; + uintN i; + + CHECK_REQUEST(cx); + rt = cx->runtime; + +#if JS_HAS_UNDEFINED + /* See if we need to bind 'undefined' and define it if so. */ + atom = rt->atomState.typeAtoms[JSTYPE_VOID]; + if (!HasOwnProperty(cx, obj, atom, &found)) + return JS_FALSE; + if (!found && + !OBJ_DEFINE_PROPERTY(cx, obj, (jsid)atom, JSVAL_VOID, NULL, NULL, + JSPROP_PERMANENT, NULL)) { + return JS_FALSE; + } +#endif + + /* Initialize any classes that have not been resolved yet. */ + for (i = 0; standard_class_atoms[i].init; i++) { + atom = OFFSET_TO_ATOM(rt, standard_class_atoms[i].atomOffset); + if (!HasOwnProperty(cx, obj, atom, &found)) + return JS_FALSE; + if (!found && !standard_class_atoms[i].init(cx, obj)) + return JS_FALSE; + } + + return JS_TRUE; +} + +#undef ATOM_OFFSET +#undef OFFSET_TO_ATOM + +JS_PUBLIC_API(JSObject *) +JS_GetScopeChain(JSContext *cx) +{ + return cx->fp ? cx->fp->scopeChain : NULL; +} + +JS_PUBLIC_API(void *) +JS_malloc(JSContext *cx, size_t nbytes) +{ + void *p; + + JS_ASSERT(nbytes != 0); + if (nbytes == 0) + nbytes = 1; + cx->runtime->gcMallocBytes += nbytes; + p = malloc(nbytes); + if (!p) + JS_ReportOutOfMemory(cx); + return p; +} + +JS_PUBLIC_API(void *) +JS_realloc(JSContext *cx, void *p, size_t nbytes) +{ + p = realloc(p, nbytes); + if (!p) + JS_ReportOutOfMemory(cx); + return p; +} + +JS_PUBLIC_API(void) +JS_free(JSContext *cx, void *p) +{ + if (p) + free(p); +} + +JS_PUBLIC_API(char *) +JS_strdup(JSContext *cx, const char *s) +{ + size_t n; + void *p; + + n = strlen(s) + 1; + p = JS_malloc(cx, n); + if (!p) + return NULL; + return (char *)memcpy(p, s, n); +} + +JS_PUBLIC_API(jsdouble *) +JS_NewDouble(JSContext *cx, jsdouble d) +{ + CHECK_REQUEST(cx); + return js_NewDouble(cx, d); +} + +JS_PUBLIC_API(JSBool) +JS_NewDoubleValue(JSContext *cx, jsdouble d, jsval *rval) +{ + CHECK_REQUEST(cx); + return js_NewDoubleValue(cx, d, rval); +} + +JS_PUBLIC_API(JSBool) +JS_NewNumberValue(JSContext *cx, jsdouble d, jsval *rval) +{ + CHECK_REQUEST(cx); + return js_NewNumberValue(cx, d, rval); +} + +#undef JS_AddRoot +JS_PUBLIC_API(JSBool) +JS_AddRoot(JSContext *cx, void *rp) +{ + CHECK_REQUEST(cx); + return js_AddRoot(cx, rp, NULL); +} + +JS_PUBLIC_API(JSBool) +JS_AddNamedRootRT(JSRuntime *rt, void *rp, const char *name) +{ + return js_AddRootRT(rt, rp, name); +} + +JS_PUBLIC_API(JSBool) +JS_RemoveRoot(JSContext *cx, void *rp) +{ + CHECK_REQUEST(cx); + return js_RemoveRoot(cx->runtime, rp); +} + +JS_PUBLIC_API(JSBool) +JS_RemoveRootRT(JSRuntime *rt, void *rp) +{ + return js_RemoveRoot(rt, rp); +} + +JS_PUBLIC_API(JSBool) +JS_AddNamedRoot(JSContext *cx, void *rp, const char *name) +{ + CHECK_REQUEST(cx); + return js_AddRoot(cx, rp, name); +} + +JS_PUBLIC_API(void) +JS_ClearNewbornRoots(JSContext *cx) +{ + uintN i; + + for (i = 0; i < GCX_NTYPES; i++) + cx->newborn[i] = NULL; + cx->lastAtom = NULL; +} + +#include "jshash.h" /* Added by JSIFY */ + +#ifdef DEBUG + +typedef struct NamedRootDumpArgs { + void (*dump)(const char *name, void *rp, void *data); + void *data; +} NamedRootDumpArgs; + +JS_STATIC_DLL_CALLBACK(JSDHashOperator) +js_named_root_dumper(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 number, + void *arg) +{ + NamedRootDumpArgs *args = (NamedRootDumpArgs *) arg; + JSGCRootHashEntry *rhe = (JSGCRootHashEntry *)hdr; + + if (rhe->name) + args->dump(rhe->name, rhe->root, args->data); + return JS_DHASH_NEXT; +} + +JS_PUBLIC_API(void) +JS_DumpNamedRoots(JSRuntime *rt, + void (*dump)(const char *name, void *rp, void *data), + void *data) +{ + NamedRootDumpArgs args; + + args.dump = dump; + args.data = data; + JS_DHashTableEnumerate(&rt->gcRootsHash, js_named_root_dumper, &args); +} + +#endif /* DEBUG */ + +typedef struct GCRootMapArgs { + JSGCRootMapFun map; + void *data; +} GCRootMapArgs; + +JS_STATIC_DLL_CALLBACK(JSDHashOperator) +js_gcroot_mapper(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 number, + void *arg) +{ + GCRootMapArgs *args = (GCRootMapArgs *) arg; + JSGCRootHashEntry *rhe = (JSGCRootHashEntry *)hdr; + intN mapflags; + JSDHashOperator op; + + mapflags = args->map(rhe->root, rhe->name, args->data); + +#if JS_MAP_GCROOT_NEXT == JS_DHASH_NEXT && \ + JS_MAP_GCROOT_STOP == JS_DHASH_STOP && \ + JS_MAP_GCROOT_REMOVE == JS_DHASH_REMOVE + op = (JSDHashOperator)mapflags; +#else + op = JS_DHASH_NEXT; + if (mapflags & JS_MAP_GCROOT_STOP) + op |= JS_DHASH_STOP; + if (mapflags & JS_MAP_GCROOT_REMOVE) + op |= JS_DHASH_REMOVE; +#endif + + return op; +} + +JS_PUBLIC_API(uint32) +JS_MapGCRoots(JSRuntime *rt, JSGCRootMapFun map, void *data) +{ + GCRootMapArgs args; + uint32 rv; + + args.map = map; + args.data = data; + JS_LOCK_GC(rt); + rv = JS_DHashTableEnumerate(&rt->gcRootsHash, js_gcroot_mapper, &args); + JS_UNLOCK_GC(rt); + return rv; +} + +JS_PUBLIC_API(JSBool) +JS_LockGCThing(JSContext *cx, void *thing) +{ + JSBool ok; + + CHECK_REQUEST(cx); + ok = js_LockGCThing(cx, thing); + if (!ok) + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_LOCK); + return ok; +} + +JS_PUBLIC_API(JSBool) +JS_LockGCThingRT(JSRuntime *rt, void *thing) +{ + return js_LockGCThingRT(rt, thing); +} + +JS_PUBLIC_API(JSBool) +JS_UnlockGCThing(JSContext *cx, void *thing) +{ + JSBool ok; + + CHECK_REQUEST(cx); + ok = js_UnlockGCThingRT(cx->runtime, thing); + if (!ok) + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_UNLOCK); + return ok; +} + +JS_PUBLIC_API(JSBool) +JS_UnlockGCThingRT(JSRuntime *rt, void *thing) +{ + return js_UnlockGCThingRT(rt, thing); +} + +JS_PUBLIC_API(void) +JS_MarkGCThing(JSContext *cx, void *thing, const char *name, void *arg) +{ + JS_ASSERT(cx->runtime->gcLevel > 0); +#ifdef JS_THREADSAFE + JS_ASSERT(cx->runtime->gcThread == js_CurrentThreadId()); +#endif + + GC_MARK(cx, thing, name, arg); +} + +JS_PUBLIC_API(void) +JS_GC(JSContext *cx) +{ + /* Don't nuke active arenas if executing or compiling. */ + if (cx->stackPool.current == &cx->stackPool.first) + JS_FinishArenaPool(&cx->stackPool); + if (cx->tempPool.current == &cx->tempPool.first) + JS_FinishArenaPool(&cx->tempPool); + js_ForceGC(cx, 0); +} + +JS_PUBLIC_API(void) +JS_MaybeGC(JSContext *cx) +{ + JSRuntime *rt; + uint32 bytes, lastBytes; + + rt = cx->runtime; + bytes = rt->gcBytes; + lastBytes = rt->gcLastBytes; + if ((bytes > 8192 && bytes > lastBytes + lastBytes / 2) || + rt->gcMallocBytes > rt->gcMaxBytes) { + /* + * Run the GC if we have half again as many bytes of GC-things as + * the last time we GC'd, or if we have malloc'd more bytes through + * JS_malloc than we were told to allocate by JS_NewRuntime. + */ + JS_GC(cx); + } +} + +JS_PUBLIC_API(JSGCCallback) +JS_SetGCCallback(JSContext *cx, JSGCCallback cb) +{ + return JS_SetGCCallbackRT(cx->runtime, cb); +} + +JS_PUBLIC_API(JSGCCallback) +JS_SetGCCallbackRT(JSRuntime *rt, JSGCCallback cb) +{ + JSGCCallback oldcb; + + oldcb = rt->gcCallback; + rt->gcCallback = cb; + return oldcb; +} + +JS_PUBLIC_API(JSBool) +JS_IsAboutToBeFinalized(JSContext *cx, void *thing) +{ + JS_ASSERT(thing); + return js_IsAboutToBeFinalized(cx, thing); +} + +JS_PUBLIC_API(intN) +JS_AddExternalStringFinalizer(JSStringFinalizeOp finalizer) +{ + return js_ChangeExternalStringFinalizer(NULL, finalizer); +} + +JS_PUBLIC_API(intN) +JS_RemoveExternalStringFinalizer(JSStringFinalizeOp finalizer) +{ + return js_ChangeExternalStringFinalizer(finalizer, NULL); +} + +JS_PUBLIC_API(JSString *) +JS_NewExternalString(JSContext *cx, jschar *chars, size_t length, intN type) +{ + JSString *str; + + CHECK_REQUEST(cx); + JS_ASSERT(GCX_EXTERNAL_STRING <= type && type < (intN) GCX_NTYPES); + + str = (JSString *) js_AllocGCThing(cx, (uintN) type); + if (!str) + return NULL; + str->length = length; + str->chars = chars; + return str; +} + +JS_PUBLIC_API(intN) +JS_GetExternalStringGCType(JSRuntime *rt, JSString *str) +{ + uint8 type = (uint8) (*js_GetGCThingFlags(str) & GCF_TYPEMASK); + + if (type >= GCX_EXTERNAL_STRING) + return (intN)type; + JS_ASSERT(type == GCX_STRING || type == GCX_MUTABLE_STRING); + return -1; +} + +#ifdef DEBUG +static void +CheckStackGrowthDirection(int *dummy1addr, jsuword limitAddr) +{ + int dummy2; + +#if JS_STACK_GROWTH_DIRECTION > 0 + JS_ASSERT(dummy1addr < &dummy2); + JS_ASSERT((jsuword)&dummy2 < limitAddr); +#else + /* Stack grows downward, the common case on modern architectures. */ + JS_ASSERT(&dummy2 < dummy1addr); + JS_ASSERT(limitAddr < (jsuword)&dummy2); +#endif +} +#endif + +JS_PUBLIC_API(void) +JS_SetThreadStackLimit(JSContext *cx, jsuword limitAddr) +{ +#ifdef DEBUG + int dummy1; + + CheckStackGrowthDirection(&dummy1, limitAddr); +#endif + +#if JS_STACK_GROWTH_DIRECTION > 0 + if (limitAddr == 0) + limitAddr = (jsuword)-1; +#endif + cx->stackLimit = limitAddr; +} + +/************************************************************************/ + +JS_PUBLIC_API(void) +JS_DestroyIdArray(JSContext *cx, JSIdArray *ida) +{ + JS_free(cx, ida); +} + +JS_PUBLIC_API(JSBool) +JS_ValueToId(JSContext *cx, jsval v, jsid *idp) +{ + JSAtom *atom; + + CHECK_REQUEST(cx); + if (JSVAL_IS_INT(v)) { + *idp = v; + } else { + atom = js_ValueToStringAtom(cx, v); + if (!atom) + return JS_FALSE; + *idp = (jsid)atom; + } + return JS_TRUE; +} + +JS_PUBLIC_API(JSBool) +JS_IdToValue(JSContext *cx, jsid id, jsval *vp) +{ + CHECK_REQUEST(cx); + *vp = ID_TO_VALUE(id); + return JS_TRUE; +} + +JS_PUBLIC_API(JSBool) +JS_PropertyStub(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + return JS_TRUE; +} + +JS_PUBLIC_API(JSBool) +JS_EnumerateStub(JSContext *cx, JSObject *obj) +{ + return JS_TRUE; +} + +JS_PUBLIC_API(JSBool) +JS_ResolveStub(JSContext *cx, JSObject *obj, jsval id) +{ + return JS_TRUE; +} + +JS_PUBLIC_API(JSBool) +JS_ConvertStub(JSContext *cx, JSObject *obj, JSType type, jsval *vp) +{ +#if JS_BUG_EAGER_TOSTRING + if (type == JSTYPE_STRING) + return JS_TRUE; +#endif + return js_TryValueOf(cx, obj, type, vp); +} + +JS_PUBLIC_API(void) +JS_FinalizeStub(JSContext *cx, JSObject *obj) +{ +} + +JS_PUBLIC_API(JSObject *) +JS_InitClass(JSContext *cx, JSObject *obj, JSObject *parent_proto, + JSClass *clasp, JSNative constructor, uintN nargs, + JSPropertySpec *ps, JSFunctionSpec *fs, + JSPropertySpec *static_ps, JSFunctionSpec *static_fs) +{ + JSAtom *atom; + JSObject *proto, *ctor; + JSBool named; + JSFunction *fun; + jsval junk; + + CHECK_REQUEST(cx); + atom = js_Atomize(cx, clasp->name, strlen(clasp->name), 0); + if (!atom) + return NULL; + + /* Create a prototype object for this class. */ + proto = js_NewObject(cx, clasp, parent_proto, obj); + if (!proto) + return NULL; + + if (!constructor) { + /* Lacking a constructor, name the prototype (e.g., Math). */ + named = OBJ_DEFINE_PROPERTY(cx, obj, (jsid)atom, OBJECT_TO_JSVAL(proto), + NULL, NULL, 0, NULL); + if (!named) + goto bad; + ctor = proto; + } else { + /* Define the constructor function in obj's scope. */ + fun = js_DefineFunction(cx, obj, atom, constructor, nargs, 0); + named = (fun != NULL); + if (!fun) + goto bad; + + /* + * Remember the class this function is a constructor for so that + * we know to create an object of this class when we call the + * constructor. + */ + fun->clasp = clasp; + + /* Connect constructor and prototype by named properties. */ + ctor = fun->object; + if (!js_SetClassPrototype(cx, ctor, proto, + JSPROP_READONLY | JSPROP_PERMANENT)) { + goto bad; + } + + /* Bootstrap Function.prototype (see also JS_InitStandardClasses). */ + if (OBJ_GET_CLASS(cx, ctor) == clasp) { + /* XXXMLM - this fails in framesets that are writing over + * themselves! + * JS_ASSERT(!OBJ_GET_PROTO(cx, ctor)); + */ + OBJ_SET_PROTO(cx, ctor, proto); + } + } + + /* Add properties and methods to the prototype and the constructor. */ + if ((ps && !JS_DefineProperties(cx, proto, ps)) || + (fs && !JS_DefineFunctions(cx, proto, fs)) || + (static_ps && !JS_DefineProperties(cx, ctor, static_ps)) || + (static_fs && !JS_DefineFunctions(cx, ctor, static_fs))) { + goto bad; + } + return proto; + +bad: + if (named) + (void) OBJ_DELETE_PROPERTY(cx, obj, (jsid)atom, &junk); + cx->newborn[GCX_OBJECT] = NULL; + return NULL; +} + +#ifdef JS_THREADSAFE +JS_PUBLIC_API(JSClass *) +JS_GetClass(JSContext *cx, JSObject *obj) +{ + return (JSClass *) + JSVAL_TO_PRIVATE(GC_AWARE_GET_SLOT(cx, obj, JSSLOT_CLASS)); +} +#else +JS_PUBLIC_API(JSClass *) +JS_GetClass(JSObject *obj) +{ + return LOCKED_OBJ_GET_CLASS(obj); +} +#endif + +JS_PUBLIC_API(JSBool) +JS_InstanceOf(JSContext *cx, JSObject *obj, JSClass *clasp, jsval *argv) +{ + JSFunction *fun; + + CHECK_REQUEST(cx); + if (OBJ_GET_CLASS(cx, obj) == clasp) + return JS_TRUE; + if (argv) { + fun = js_ValueToFunction(cx, &argv[-2], 0); + if (fun) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_INCOMPATIBLE_PROTO, + clasp->name, JS_GetFunctionName(fun), + OBJ_GET_CLASS(cx, obj)->name); + } + } + return JS_FALSE; +} + +JS_PUBLIC_API(void *) +JS_GetPrivate(JSContext *cx, JSObject *obj) +{ + jsval v; + + JS_ASSERT(OBJ_GET_CLASS(cx, obj)->flags & JSCLASS_HAS_PRIVATE); + v = GC_AWARE_GET_SLOT(cx, obj, JSSLOT_PRIVATE); + if (!JSVAL_IS_INT(v)) + return NULL; + return JSVAL_TO_PRIVATE(v); +} + +JS_PUBLIC_API(JSBool) +JS_SetPrivate(JSContext *cx, JSObject *obj, void *data) +{ + JS_ASSERT(OBJ_GET_CLASS(cx, obj)->flags & JSCLASS_HAS_PRIVATE); + OBJ_SET_SLOT(cx, obj, JSSLOT_PRIVATE, PRIVATE_TO_JSVAL(data)); + return JS_TRUE; +} + +JS_PUBLIC_API(void *) +JS_GetInstancePrivate(JSContext *cx, JSObject *obj, JSClass *clasp, + jsval *argv) +{ + if (!JS_InstanceOf(cx, obj, clasp, argv)) + return NULL; + return JS_GetPrivate(cx, obj); +} + +JS_PUBLIC_API(JSObject *) +JS_GetPrototype(JSContext *cx, JSObject *obj) +{ + JSObject *proto; + + CHECK_REQUEST(cx); + proto = JSVAL_TO_OBJECT(GC_AWARE_GET_SLOT(cx, obj, JSSLOT_PROTO)); + + /* Beware ref to dead object (we may be called from obj's finalizer). */ + return proto && proto->map ? proto : NULL; +} + +JS_PUBLIC_API(JSBool) +JS_SetPrototype(JSContext *cx, JSObject *obj, JSObject *proto) +{ + CHECK_REQUEST(cx); + if (obj->map->ops->setProto) + return obj->map->ops->setProto(cx, obj, JSSLOT_PROTO, proto); + OBJ_SET_SLOT(cx, obj, JSSLOT_PROTO, OBJECT_TO_JSVAL(proto)); + return JS_TRUE; +} + +JS_PUBLIC_API(JSObject *) +JS_GetParent(JSContext *cx, JSObject *obj) +{ + JSObject *parent; + + parent = JSVAL_TO_OBJECT(GC_AWARE_GET_SLOT(cx, obj, JSSLOT_PARENT)); + + /* Beware ref to dead object (we may be called from obj's finalizer). */ + return parent && parent->map ? parent : NULL; +} + +JS_PUBLIC_API(JSBool) +JS_SetParent(JSContext *cx, JSObject *obj, JSObject *parent) +{ + CHECK_REQUEST(cx); + if (obj->map->ops->setParent) + return obj->map->ops->setParent(cx, obj, JSSLOT_PARENT, parent); + OBJ_SET_SLOT(cx, obj, JSSLOT_PARENT, OBJECT_TO_JSVAL(parent)); + return JS_TRUE; +} + +JS_PUBLIC_API(JSObject *) +JS_GetConstructor(JSContext *cx, JSObject *proto) +{ + jsval cval; + + CHECK_REQUEST(cx); + if (!OBJ_GET_PROPERTY(cx, proto, + (jsid)cx->runtime->atomState.constructorAtom, + &cval)) { + return NULL; + } + if (!JSVAL_IS_FUNCTION(cx, cval)) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NO_CONSTRUCTOR, + OBJ_GET_CLASS(cx, proto)->name); + return NULL; + } + return JSVAL_TO_OBJECT(cval); +} + +JS_PUBLIC_API(JSBool) +JS_GetObjectId(JSContext *cx, JSObject *obj, jsid *idp) +{ + *idp = (jsid) obj; + return JS_TRUE; +} + +JS_PUBLIC_API(JSObject *) +JS_NewObject(JSContext *cx, JSClass *clasp, JSObject *proto, JSObject *parent) +{ + CHECK_REQUEST(cx); + if (!clasp) + clasp = &js_ObjectClass; /* default class is Object */ + return js_NewObject(cx, clasp, proto, parent); +} + +JS_PUBLIC_API(JSBool) +JS_SealObject(JSContext *cx, JSObject *obj, JSBool deep) +{ + JSScope *scope; + JSIdArray *ida; + uint32 nslots; + jsval v, *vp, *end; + + if (!OBJ_IS_NATIVE(obj)) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_CANT_SEAL_OBJECT, + OBJ_GET_CLASS(cx, obj)->name); + return JS_FALSE; + } + + scope = OBJ_SCOPE(obj); + +#if defined JS_THREADSAFE && defined DEBUG + /* Insist on scope being used exclusively by cx's thread. */ + if (scope->ownercx != cx) { + JS_LOCK_OBJ(cx, obj); + JS_ASSERT(OBJ_SCOPE(obj) == scope); + JS_ASSERT(scope->ownercx == cx); + JS_UNLOCK_SCOPE(cx, scope); + } +#endif + + /* Nothing to do if obj's scope is already sealed. */ + if (SCOPE_IS_SEALED(scope)) + return JS_TRUE; + + /* XXX Enumerate lazy properties now, as they can't be added later. */ + ida = JS_Enumerate(cx, obj); + if (!ida) + return JS_FALSE; + JS_DestroyIdArray(cx, ida); + + /* Ensure that obj has its own, mutable scope, and seal that scope. */ + JS_LOCK_OBJ(cx, obj); + scope = js_GetMutableScope(cx, obj); + if (scope) + SCOPE_SET_SEALED(scope); + JS_UNLOCK_SCOPE(cx, scope); + if (!scope) + return JS_FALSE; + + /* If we are not sealing an entire object graph, we're done. */ + if (!deep) + return JS_TRUE; + + /* Walk obj->slots and if any value is a non-null object, seal it. */ + nslots = JS_MIN(scope->map.freeslot, scope->map.nslots); + for (vp = obj->slots, end = vp + nslots; vp < end; vp++) { + v = *vp; + if (JSVAL_IS_PRIMITIVE(v)) + continue; + if (!JS_SealObject(cx, JSVAL_TO_OBJECT(v), deep)) + return JS_FALSE; + } + return JS_TRUE; +} + +JS_PUBLIC_API(JSObject *) +JS_ConstructObject(JSContext *cx, JSClass *clasp, JSObject *proto, + JSObject *parent) +{ + CHECK_REQUEST(cx); + if (!clasp) + clasp = &js_ObjectClass; /* default class is Object */ + return js_ConstructObject(cx, clasp, proto, parent, 0, NULL); +} + +JS_PUBLIC_API(JSObject *) +JS_ConstructObjectWithArguments(JSContext *cx, JSClass *clasp, JSObject *proto, + JSObject *parent, uintN argc, jsval *argv) +{ + CHECK_REQUEST(cx); + if (!clasp) + clasp = &js_ObjectClass; /* default class is Object */ + return js_ConstructObject(cx, clasp, proto, parent, argc, argv); +} + +static JSBool +DefineProperty(JSContext *cx, JSObject *obj, const char *name, jsval value, + JSPropertyOp getter, JSPropertyOp setter, uintN attrs, + uintN flags, intN tinyid) +{ + jsid id; + JSAtom *atom; + + if (attrs & JSPROP_INDEX) { + id = INT_TO_JSVAL((jsint)name); + atom = NULL; + attrs &= ~JSPROP_INDEX; + } else { + atom = js_Atomize(cx, name, strlen(name), 0); + if (!atom) + return JS_FALSE; + id = (jsid)atom; + } + if (flags != 0 && OBJ_IS_NATIVE(obj)) { + return js_DefineNativeProperty(cx, obj, id, value, getter, setter, + attrs, flags, tinyid, NULL); + } + return OBJ_DEFINE_PROPERTY(cx, obj, id, value, getter, setter, attrs, + NULL); +} + +#define AUTO_NAMELEN(s,n) (((n) == (size_t)-1) ? js_strlen(s) : (n)) + +static JSBool +DefineUCProperty(JSContext *cx, JSObject *obj, + const jschar *name, size_t namelen, jsval value, + JSPropertyOp getter, JSPropertyOp setter, uintN attrs, + uintN flags, intN tinyid) +{ + JSAtom *atom; + + atom = js_AtomizeChars(cx, name, AUTO_NAMELEN(name,namelen), 0); + if (!atom) + return JS_FALSE; + if (flags != 0 && OBJ_IS_NATIVE(obj)) { + return js_DefineNativeProperty(cx, obj, (jsid)atom, value, + getter, setter, attrs, flags, tinyid, + NULL); + } + return OBJ_DEFINE_PROPERTY(cx, obj, (jsid)atom, value, getter, setter, + attrs, NULL); +} + +JS_PUBLIC_API(JSObject *) +JS_DefineObject(JSContext *cx, JSObject *obj, const char *name, JSClass *clasp, + JSObject *proto, uintN attrs) +{ + JSObject *nobj; + + CHECK_REQUEST(cx); + if (!clasp) + clasp = &js_ObjectClass; /* default class is Object */ + nobj = js_NewObject(cx, clasp, proto, obj); + if (!nobj) + return NULL; + if (!DefineProperty(cx, obj, name, OBJECT_TO_JSVAL(nobj), NULL, NULL, attrs, + 0, 0)) { + cx->newborn[GCX_OBJECT] = NULL; + return NULL; + } + return nobj; +} + +JS_PUBLIC_API(JSBool) +JS_DefineConstDoubles(JSContext *cx, JSObject *obj, JSConstDoubleSpec *cds) +{ + JSBool ok; + jsval value; + uintN flags; + + CHECK_REQUEST(cx); + for (ok = JS_TRUE; cds->name; cds++) { + ok = js_NewNumberValue(cx, cds->dval, &value); + if (!ok) + break; + flags = cds->flags; + if (!flags) + flags = JSPROP_READONLY | JSPROP_PERMANENT; + ok = DefineProperty(cx, obj, cds->name, value, NULL, NULL, flags, 0, 0); + if (!ok) + break; + } + return ok; +} + +JS_PUBLIC_API(JSBool) +JS_DefineProperties(JSContext *cx, JSObject *obj, JSPropertySpec *ps) +{ + JSBool ok; + + CHECK_REQUEST(cx); + for (ok = JS_TRUE; ps->name; ps++) { + ok = DefineProperty(cx, obj, ps->name, JSVAL_VOID, + ps->getter, ps->setter, ps->flags, + SPROP_HAS_SHORTID, ps->tinyid); + if (!ok) + break; + } + return ok; +} + +JS_PUBLIC_API(JSBool) +JS_DefineProperty(JSContext *cx, JSObject *obj, const char *name, jsval value, + JSPropertyOp getter, JSPropertyOp setter, uintN attrs) +{ + CHECK_REQUEST(cx); + return DefineProperty(cx, obj, name, value, getter, setter, attrs, 0, 0); +} + +JS_PUBLIC_API(JSBool) +JS_DefinePropertyWithTinyId(JSContext *cx, JSObject *obj, const char *name, + int8 tinyid, jsval value, + JSPropertyOp getter, JSPropertyOp setter, + uintN attrs) +{ + CHECK_REQUEST(cx); + return DefineProperty(cx, obj, name, value, getter, setter, attrs, + SPROP_HAS_SHORTID, tinyid); +} + +static JSBool +LookupProperty(JSContext *cx, JSObject *obj, const char *name, JSObject **objp, + JSProperty **propp) +{ + JSAtom *atom; + + atom = js_Atomize(cx, name, strlen(name), 0); + if (!atom) + return JS_FALSE; + return OBJ_LOOKUP_PROPERTY(cx, obj, (jsid)atom, objp, propp); +} + +static JSBool +LookupUCProperty(JSContext *cx, JSObject *obj, + const jschar *name, size_t namelen, + JSObject **objp, JSProperty **propp) +{ + JSAtom *atom; + + atom = js_AtomizeChars(cx, name, AUTO_NAMELEN(name,namelen), 0); + if (!atom) + return JS_FALSE; + return OBJ_LOOKUP_PROPERTY(cx, obj, (jsid)atom, objp, propp); +} + +JS_PUBLIC_API(JSBool) +JS_AliasProperty(JSContext *cx, JSObject *obj, const char *name, + const char *alias) +{ + JSObject *obj2; + JSProperty *prop; + JSAtom *atom; + JSBool ok; + JSScopeProperty *sprop; + + CHECK_REQUEST(cx); + if (!LookupProperty(cx, obj, name, &obj2, &prop)) + return JS_FALSE; + if (!prop) { + js_ReportIsNotDefined(cx, name); + return JS_FALSE; + } + if (obj2 != obj || !OBJ_IS_NATIVE(obj)) { + OBJ_DROP_PROPERTY(cx, obj2, prop); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_ALIAS, + alias, name, OBJ_GET_CLASS(cx, obj2)->name); + return JS_FALSE; + } + atom = js_Atomize(cx, alias, strlen(alias), 0); + if (!atom) { + ok = JS_FALSE; + } else { + sprop = (JSScopeProperty *)prop; + ok = (js_AddNativeProperty(cx, obj, (jsid)atom, + sprop->getter, sprop->setter, sprop->slot, + sprop->attrs, sprop->flags | SPROP_IS_ALIAS, + sprop->shortid) + != NULL); + } + OBJ_DROP_PROPERTY(cx, obj, prop); + return ok; +} + +static jsval +LookupResult(JSContext *cx, JSObject *obj, JSObject *obj2, JSProperty *prop) +{ + JSScopeProperty *sprop; + jsval rval; + + if (!prop) { + /* XXX bad API: no way to tell "not defined" from "void value" */ + return JSVAL_VOID; + } + if (OBJ_IS_NATIVE(obj2)) { + /* Peek at the native property's slot value, without doing a Get. */ + sprop = (JSScopeProperty *)prop; + rval = SPROP_HAS_VALID_SLOT(sprop, OBJ_SCOPE(obj2)) + ? LOCKED_OBJ_GET_SLOT(obj2, sprop->slot) + : JSVAL_TRUE; + } else { + /* XXX bad API: no way to return "defined but value unknown" */ + rval = JSVAL_TRUE; + } + OBJ_DROP_PROPERTY(cx, obj2, prop); + return rval; +} + +static JSBool +GetPropertyAttributes(JSContext *cx, JSObject *obj, JSAtom *atom, + uintN *attrsp, JSBool *foundp) +{ + JSObject *obj2; + JSProperty *prop; + JSBool ok; + + if (!atom) + return JS_FALSE; + if (!OBJ_LOOKUP_PROPERTY(cx, obj, (jsid)atom, &obj2, &prop)) + return JS_FALSE; + if (!prop || obj != obj2) { + *foundp = JS_FALSE; + if (prop) + OBJ_DROP_PROPERTY(cx, obj2, prop); + return JS_TRUE; + } + + *foundp = JS_TRUE; + ok = OBJ_GET_ATTRIBUTES(cx, obj, (jsid)atom, prop, attrsp); + OBJ_DROP_PROPERTY(cx, obj, prop); + return ok; +} + +static JSBool +SetPropertyAttributes(JSContext *cx, JSObject *obj, JSAtom *atom, + uintN attrs, JSBool *foundp) +{ + JSObject *obj2; + JSProperty *prop; + JSBool ok; + + if (!atom) + return JS_FALSE; + if (!OBJ_LOOKUP_PROPERTY(cx, obj, (jsid)atom, &obj2, &prop)) + return JS_FALSE; + if (!prop || obj != obj2) { + *foundp = JS_FALSE; + if (prop) + OBJ_DROP_PROPERTY(cx, obj2, prop); + return JS_TRUE; + } + + *foundp = JS_TRUE; + ok = OBJ_SET_ATTRIBUTES(cx, obj, (jsid)atom, prop, &attrs); + OBJ_DROP_PROPERTY(cx, obj, prop); + return ok; +} + + +JS_PUBLIC_API(JSBool) +JS_GetPropertyAttributes(JSContext *cx, JSObject *obj, const char *name, + uintN *attrsp, JSBool *foundp) +{ + CHECK_REQUEST(cx); + return GetPropertyAttributes(cx, obj, + js_Atomize(cx, name, strlen(name), 0), + attrsp, foundp); +} + +JS_PUBLIC_API(JSBool) +JS_SetPropertyAttributes(JSContext *cx, JSObject *obj, const char *name, + uintN attrs, JSBool *foundp) +{ + CHECK_REQUEST(cx); + return SetPropertyAttributes(cx, obj, + js_Atomize(cx, name, strlen(name), 0), + attrs, foundp); +} + +JS_PUBLIC_API(JSBool) +JS_LookupProperty(JSContext *cx, JSObject *obj, const char *name, jsval *vp) +{ + JSBool ok; + JSObject *obj2; + JSProperty *prop; + + CHECK_REQUEST(cx); + ok = LookupProperty(cx, obj, name, &obj2, &prop); + if (ok) + *vp = LookupResult(cx, obj, obj2, prop); + return ok; +} + +JS_PUBLIC_API(JSBool) +JS_GetProperty(JSContext *cx, JSObject *obj, const char *name, jsval *vp) +{ + JSAtom *atom; + + CHECK_REQUEST(cx); + atom = js_Atomize(cx, name, strlen(name), 0); + if (!atom) + return JS_FALSE; + return OBJ_GET_PROPERTY(cx, obj, (jsid)atom, vp); +} + +JS_PUBLIC_API(JSBool) +JS_SetProperty(JSContext *cx, JSObject *obj, const char *name, jsval *vp) +{ + JSAtom *atom; + + CHECK_REQUEST(cx); + atom = js_Atomize(cx, name, strlen(name), 0); + if (!atom) + return JS_FALSE; + return OBJ_SET_PROPERTY(cx, obj, (jsid)atom, vp); +} + +JS_PUBLIC_API(JSBool) +JS_DeleteProperty(JSContext *cx, JSObject *obj, const char *name) +{ + jsval junk; + + CHECK_REQUEST(cx); + return JS_DeleteProperty2(cx, obj, name, &junk); +} + +JS_PUBLIC_API(JSBool) +JS_DeleteProperty2(JSContext *cx, JSObject *obj, const char *name, + jsval *rval) +{ + JSAtom *atom; + + CHECK_REQUEST(cx); + atom = js_Atomize(cx, name, strlen(name), 0); + if (!atom) + return JS_FALSE; + return OBJ_DELETE_PROPERTY(cx, obj, (jsid)atom, rval); +} + +JS_PUBLIC_API(JSBool) +JS_DefineUCProperty(JSContext *cx, JSObject *obj, + const jschar *name, size_t namelen, jsval value, + JSPropertyOp getter, JSPropertyOp setter, + uintN attrs) +{ + CHECK_REQUEST(cx); + return DefineUCProperty(cx, obj, name, namelen, value, getter, setter, + attrs, 0, 0); +} + +JS_PUBLIC_API(JSBool) +JS_GetUCPropertyAttributes(JSContext *cx, JSObject *obj, + const jschar *name, size_t namelen, + uintN *attrsp, JSBool *foundp) +{ + CHECK_REQUEST(cx); + return GetPropertyAttributes(cx, obj, + js_AtomizeChars(cx, name, AUTO_NAMELEN(name,namelen), 0), + attrsp, foundp); +} + +JS_PUBLIC_API(JSBool) +JS_SetUCPropertyAttributes(JSContext *cx, JSObject *obj, + const jschar *name, size_t namelen, + uintN attrs, JSBool *foundp) +{ + CHECK_REQUEST(cx); + return SetPropertyAttributes(cx, obj, + js_AtomizeChars(cx, name, AUTO_NAMELEN(name,namelen), 0), + attrs, foundp); +} + +JS_PUBLIC_API(JSBool) +JS_DefineUCPropertyWithTinyId(JSContext *cx, JSObject *obj, + const jschar *name, size_t namelen, + int8 tinyid, jsval value, + JSPropertyOp getter, JSPropertyOp setter, + uintN attrs) +{ + CHECK_REQUEST(cx); + return DefineUCProperty(cx, obj, name, namelen, value, getter, setter, + attrs, SPROP_HAS_SHORTID, tinyid); +} + +JS_PUBLIC_API(JSBool) +JS_LookupUCProperty(JSContext *cx, JSObject *obj, + const jschar *name, size_t namelen, + jsval *vp) +{ + JSBool ok; + JSObject *obj2; + JSProperty *prop; + + CHECK_REQUEST(cx); + ok = LookupUCProperty(cx, obj, name, namelen, &obj2, &prop); + if (ok) + *vp = LookupResult(cx, obj, obj2, prop); + return ok; +} + +JS_PUBLIC_API(JSBool) +JS_GetUCProperty(JSContext *cx, JSObject *obj, + const jschar *name, size_t namelen, + jsval *vp) +{ + JSAtom *atom; + + CHECK_REQUEST(cx); + atom = js_AtomizeChars(cx, name, AUTO_NAMELEN(name,namelen), 0); + if (!atom) + return JS_FALSE; + return OBJ_GET_PROPERTY(cx, obj, (jsid)atom, vp); +} + +JS_PUBLIC_API(JSBool) +JS_SetUCProperty(JSContext *cx, JSObject *obj, + const jschar *name, size_t namelen, + jsval *vp) +{ + JSAtom *atom; + + CHECK_REQUEST(cx); + atom = js_AtomizeChars(cx, name, AUTO_NAMELEN(name,namelen), 0); + if (!atom) + return JS_FALSE; + return OBJ_SET_PROPERTY(cx, obj, (jsid)atom, vp); +} + +JS_PUBLIC_API(JSBool) +JS_DeleteUCProperty2(JSContext *cx, JSObject *obj, + const jschar *name, size_t namelen, + jsval *rval) +{ + JSAtom *atom; + + CHECK_REQUEST(cx); + atom = js_AtomizeChars(cx, name, AUTO_NAMELEN(name,namelen), 0); + if (!atom) + return JS_FALSE; + return OBJ_DELETE_PROPERTY(cx, obj, (jsid)atom, rval); +} + +JS_PUBLIC_API(JSObject *) +JS_NewArrayObject(JSContext *cx, jsint length, jsval *vector) +{ + CHECK_REQUEST(cx); + /* NB: jsuint cast does ToUint32. */ + return js_NewArrayObject(cx, (jsuint)length, vector); +} + +JS_PUBLIC_API(JSBool) +JS_IsArrayObject(JSContext *cx, JSObject *obj) +{ + return OBJ_GET_CLASS(cx, obj) == &js_ArrayClass; +} + +JS_PUBLIC_API(JSBool) +JS_GetArrayLength(JSContext *cx, JSObject *obj, jsuint *lengthp) +{ + CHECK_REQUEST(cx); + return js_GetLengthProperty(cx, obj, lengthp); +} + +JS_PUBLIC_API(JSBool) +JS_SetArrayLength(JSContext *cx, JSObject *obj, jsuint length) +{ + CHECK_REQUEST(cx); + return js_SetLengthProperty(cx, obj, length); +} + +JS_PUBLIC_API(JSBool) +JS_HasArrayLength(JSContext *cx, JSObject *obj, jsuint *lengthp) +{ + CHECK_REQUEST(cx); + return js_HasLengthProperty(cx, obj, lengthp); +} + +JS_PUBLIC_API(JSBool) +JS_DefineElement(JSContext *cx, JSObject *obj, jsint index, jsval value, + JSPropertyOp getter, JSPropertyOp setter, uintN attrs) +{ + CHECK_REQUEST(cx); + return OBJ_DEFINE_PROPERTY(cx, obj, INT_TO_JSVAL(index), value, + getter, setter, attrs, NULL); +} + +JS_PUBLIC_API(JSBool) +JS_AliasElement(JSContext *cx, JSObject *obj, const char *name, jsint alias) +{ + JSObject *obj2; + JSProperty *prop; + JSScopeProperty *sprop; + JSBool ok; + + CHECK_REQUEST(cx); + if (!LookupProperty(cx, obj, name, &obj2, &prop)) + return JS_FALSE; + if (!prop) { + js_ReportIsNotDefined(cx, name); + return JS_FALSE; + } + if (obj2 != obj || !OBJ_IS_NATIVE(obj)) { + char numBuf[12]; + OBJ_DROP_PROPERTY(cx, obj2, prop); + JS_snprintf(numBuf, sizeof numBuf, "%ld", (long)alias); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_ALIAS, + numBuf, name, OBJ_GET_CLASS(cx, obj2)->name); + return JS_FALSE; + } + sprop = (JSScopeProperty *)prop; + ok = (js_AddNativeProperty(cx, obj, INT_TO_JSVAL(alias), + sprop->getter, sprop->setter, sprop->slot, + sprop->attrs, sprop->flags | SPROP_IS_ALIAS, + sprop->shortid) + != NULL); + OBJ_DROP_PROPERTY(cx, obj, prop); + return ok; +} + +JS_PUBLIC_API(JSBool) +JS_LookupElement(JSContext *cx, JSObject *obj, jsint index, jsval *vp) +{ + JSBool ok; + JSObject *obj2; + JSProperty *prop; + + CHECK_REQUEST(cx); + ok = OBJ_LOOKUP_PROPERTY(cx, obj, INT_TO_JSVAL(index), &obj2, &prop); + if (ok) + *vp = LookupResult(cx, obj, obj2, prop); + return ok; +} + +JS_PUBLIC_API(JSBool) +JS_GetElement(JSContext *cx, JSObject *obj, jsint index, jsval *vp) +{ + CHECK_REQUEST(cx); + return OBJ_GET_PROPERTY(cx, obj, INT_TO_JSVAL(index), vp); +} + +JS_PUBLIC_API(JSBool) +JS_SetElement(JSContext *cx, JSObject *obj, jsint index, jsval *vp) +{ + CHECK_REQUEST(cx); + return OBJ_SET_PROPERTY(cx, obj, INT_TO_JSVAL(index), vp); +} + +JS_PUBLIC_API(JSBool) +JS_DeleteElement(JSContext *cx, JSObject *obj, jsint index) +{ + jsval junk; + + CHECK_REQUEST(cx); + return JS_DeleteElement2(cx, obj, index, &junk); +} + +JS_PUBLIC_API(JSBool) +JS_DeleteElement2(JSContext *cx, JSObject *obj, jsint index, jsval *rval) +{ + CHECK_REQUEST(cx); + return OBJ_DELETE_PROPERTY(cx, obj, INT_TO_JSVAL(index), rval); +} + +JS_PUBLIC_API(void) +JS_ClearScope(JSContext *cx, JSObject *obj) +{ + CHECK_REQUEST(cx); + + if (obj->map->ops->clear) + obj->map->ops->clear(cx, obj); +} + +JS_PUBLIC_API(JSIdArray *) +JS_Enumerate(JSContext *cx, JSObject *obj) +{ + jsint i, n; + jsval iter_state, num_properties; + jsid id; + JSIdArray *ida; + jsval *vector; + + CHECK_REQUEST(cx); + + ida = NULL; + iter_state = JSVAL_NULL; + + /* Get the number of properties to enumerate. */ + if (!OBJ_ENUMERATE(cx, obj, JSENUMERATE_INIT, &iter_state, &num_properties)) + goto error; + if (!JSVAL_IS_INT(num_properties)) { + JS_ASSERT(0); + goto error; + } + + /* Grow as needed if we don't know the exact amount ahead of time. */ + n = JSVAL_TO_INT(num_properties); + if (n <= 0) + n = 8; + + /* Create an array of jsids large enough to hold all the properties */ + ida = js_NewIdArray(cx, n); + if (!ida) + goto error; + + i = 0; + vector = &ida->vector[0]; + for (;;) { + if (i == ida->length) { + /* Grow length by factor of 1.5 instead of doubling. */ + jsint newlen = ida->length + (((jsuint)ida->length + 1) >> 1); + ida = js_GrowIdArray(cx, ida, newlen); + if (!ida) + goto error; + vector = &ida->vector[0]; + } + + if (!OBJ_ENUMERATE(cx, obj, JSENUMERATE_NEXT, &iter_state, &id)) + goto error; + + /* No more jsid's to enumerate ? */ + if (iter_state == JSVAL_NULL) + break; + vector[i++] = id; + } + ida->length = i; + return ida; + +error: + if (iter_state != JSVAL_NULL) + OBJ_ENUMERATE(cx, obj, JSENUMERATE_DESTROY, &iter_state, 0); + if (ida) + JS_DestroyIdArray(cx, ida); + return NULL; +} + +JS_PUBLIC_API(JSBool) +JS_CheckAccess(JSContext *cx, JSObject *obj, jsid id, JSAccessMode mode, + jsval *vp, uintN *attrsp) +{ + CHECK_REQUEST(cx); + return OBJ_CHECK_ACCESS(cx, obj, id, mode, vp, attrsp); +} + +JS_PUBLIC_API(JSCheckAccessOp) +JS_SetCheckObjectAccessCallback(JSRuntime *rt, JSCheckAccessOp acb) +{ + JSCheckAccessOp oldacb; + + oldacb = rt->checkObjectAccess; + rt->checkObjectAccess = acb; + return oldacb; +} + +JS_PUBLIC_API(JSBool) +JS_GetReservedSlot(JSContext *cx, JSObject *obj, uint32 index, jsval *vp) +{ + JSClass *clasp; + uint32 slot; + + CHECK_REQUEST(cx); + clasp = OBJ_GET_CLASS(cx, obj); + if (index >= JSCLASS_RESERVED_SLOTS(clasp)) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_RESERVED_SLOT_RANGE); + return JS_FALSE; + } + slot = JSSLOT_START(clasp) + index; + *vp = OBJ_GET_REQUIRED_SLOT(cx, obj, slot); + return JS_TRUE; +} + +JS_PUBLIC_API(JSBool) +JS_SetReservedSlot(JSContext *cx, JSObject *obj, uint32 index, jsval v) +{ + JSClass *clasp; + uint32 slot; + + CHECK_REQUEST(cx); + clasp = OBJ_GET_CLASS(cx, obj); + if (index >= JSCLASS_RESERVED_SLOTS(clasp)) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_RESERVED_SLOT_RANGE); + return JS_FALSE; + } + slot = JSSLOT_START(clasp) + index; + OBJ_SET_REQUIRED_SLOT(cx, obj, slot, v); + return JS_TRUE; +} + +#ifdef JS_THREADSAFE +JS_PUBLIC_API(jsrefcount) +JS_HoldPrincipals(JSContext *cx, JSPrincipals *principals) +{ + return JS_ATOMIC_INCREMENT(&principals->refcount); +} + +JS_PUBLIC_API(jsrefcount) +JS_DropPrincipals(JSContext *cx, JSPrincipals *principals) +{ + jsrefcount rc = JS_ATOMIC_DECREMENT(&principals->refcount); + if (rc == 0) + principals->destroy(cx, principals); + return rc; +} +#endif + +JS_PUBLIC_API(JSPrincipalsTranscoder) +JS_SetPrincipalsTranscoder(JSRuntime *rt, JSPrincipalsTranscoder px) +{ + JSPrincipalsTranscoder oldpx; + + oldpx = rt->principalsTranscoder; + rt->principalsTranscoder = px; + return oldpx; +} + +JS_PUBLIC_API(JSObjectPrincipalsFinder) +JS_SetObjectPrincipalsFinder(JSContext *cx, JSObjectPrincipalsFinder fop) +{ + JSObjectPrincipalsFinder oldfop; + + oldfop = cx->findObjectPrincipals; + cx->findObjectPrincipals = fop; + return oldfop; +} + +JS_PUBLIC_API(JSFunction *) +JS_NewFunction(JSContext *cx, JSNative native, uintN nargs, uintN flags, + JSObject *parent, const char *name) +{ + JSAtom *atom; + + CHECK_REQUEST(cx); + + if (!name) { + atom = NULL; + } else { + atom = js_Atomize(cx, name, strlen(name), 0); + if (!atom) + return NULL; + } + return js_NewFunction(cx, NULL, native, nargs, flags, parent, atom); +} + +JS_PUBLIC_API(JSObject *) +JS_CloneFunctionObject(JSContext *cx, JSObject *funobj, JSObject *parent) +{ + CHECK_REQUEST(cx); + if (OBJ_GET_CLASS(cx, funobj) != &js_FunctionClass) { + /* Indicate we cannot clone this object. */ + return funobj; + } + return js_CloneFunctionObject(cx, funobj, parent); +} + +JS_PUBLIC_API(JSObject *) +JS_GetFunctionObject(JSFunction *fun) +{ + return fun->object; +} + +JS_PUBLIC_API(const char *) +JS_GetFunctionName(JSFunction *fun) +{ + return fun->atom + ? JS_GetStringBytes(ATOM_TO_STRING(fun->atom)) + : js_anonymous_str; +} + +JS_PUBLIC_API(JSString *) +JS_GetFunctionId(JSFunction *fun) +{ + return fun->atom ? ATOM_TO_STRING(fun->atom) : NULL; +} + +JS_PUBLIC_API(uintN) +JS_GetFunctionFlags(JSFunction *fun) +{ + return fun->flags; +} + +JS_PUBLIC_API(JSBool) +JS_ObjectIsFunction(JSContext *cx, JSObject *obj) +{ + return OBJ_GET_CLASS(cx, obj) == &js_FunctionClass; +} + +JS_PUBLIC_API(JSBool) +JS_DefineFunctions(JSContext *cx, JSObject *obj, JSFunctionSpec *fs) +{ + JSFunction *fun; + + CHECK_REQUEST(cx); + for (; fs->name; fs++) { + fun = JS_DefineFunction(cx, obj, fs->name, fs->call, fs->nargs, + fs->flags); + if (!fun) + return JS_FALSE; + fun->extra = fs->extra; + } + return JS_TRUE; +} + +JS_PUBLIC_API(JSFunction *) +JS_DefineFunction(JSContext *cx, JSObject *obj, const char *name, JSNative call, + uintN nargs, uintN attrs) +{ + JSAtom *atom; + + CHECK_REQUEST(cx); + atom = js_Atomize(cx, name, strlen(name), 0); + if (!atom) + return NULL; + return js_DefineFunction(cx, obj, atom, call, nargs, attrs); +} + +static JSScript * +CompileTokenStream(JSContext *cx, JSObject *obj, JSTokenStream *ts, + void *tempMark, JSBool *eofp) +{ + JSBool eof; + JSArenaPool codePool, notePool; + JSCodeGenerator cg; + JSScript *script; + + CHECK_REQUEST(cx); + eof = JS_FALSE; + JS_InitArenaPool(&codePool, "code", 1024, sizeof(jsbytecode)); + JS_InitArenaPool(¬ePool, "note", 1024, sizeof(jssrcnote)); + if (!js_InitCodeGenerator(cx, &cg, &codePool, ¬ePool, + ts->filename, ts->lineno, + ts->principals)) { + script = NULL; + } else if (!js_CompileTokenStream(cx, obj, ts, &cg)) { + script = NULL; + eof = (ts->flags & TSF_EOF) != 0; + } else { + script = js_NewScriptFromCG(cx, &cg, NULL); + } + if (eofp) + *eofp = eof; + if (!js_CloseTokenStream(cx, ts)) { + if (script) + js_DestroyScript(cx, script); + script = NULL; + } + cg.tempMark = tempMark; + js_FinishCodeGenerator(cx, &cg); + JS_FinishArenaPool(&codePool); + JS_FinishArenaPool(¬ePool); + return script; +} + +JS_PUBLIC_API(JSScript *) +JS_CompileScript(JSContext *cx, JSObject *obj, + const char *bytes, size_t length, + const char *filename, uintN lineno) +{ + jschar *chars; + JSScript *script; + + CHECK_REQUEST(cx); + chars = js_InflateString(cx, bytes, length); + if (!chars) + return NULL; + script = JS_CompileUCScript(cx, obj, chars, length, filename, lineno); + JS_free(cx, chars); + return script; +} + +JS_PUBLIC_API(JSScript *) +JS_CompileScriptForPrincipals(JSContext *cx, JSObject *obj, + JSPrincipals *principals, + const char *bytes, size_t length, + const char *filename, uintN lineno) +{ + jschar *chars; + JSScript *script; + + CHECK_REQUEST(cx); + chars = js_InflateString(cx, bytes, length); + if (!chars) + return NULL; + script = JS_CompileUCScriptForPrincipals(cx, obj, principals, + chars, length, filename, lineno); + JS_free(cx, chars); + return script; +} + +JS_PUBLIC_API(JSScript *) +JS_CompileUCScript(JSContext *cx, JSObject *obj, + const jschar *chars, size_t length, + const char *filename, uintN lineno) +{ + CHECK_REQUEST(cx); + return JS_CompileUCScriptForPrincipals(cx, obj, NULL, chars, length, + filename, lineno); +} + +JS_PUBLIC_API(JSScript *) +JS_CompileUCScriptForPrincipals(JSContext *cx, JSObject *obj, + JSPrincipals *principals, + const jschar *chars, size_t length, + const char *filename, uintN lineno) +{ + void *mark; + JSTokenStream *ts; + JSScript *script; + + CHECK_REQUEST(cx); + mark = JS_ARENA_MARK(&cx->tempPool); + ts = js_NewTokenStream(cx, chars, length, filename, lineno, principals); + if (!ts) + return NULL; + script = CompileTokenStream(cx, obj, ts, mark, NULL); +#if JS_HAS_EXCEPTIONS + if (!script && !cx->fp) + js_ReportUncaughtException(cx); +#endif + return script; +} + +JS_PUBLIC_API(JSBool) +JS_BufferIsCompilableUnit(JSContext *cx, JSObject *obj, + const char *bytes, size_t length) +{ + jschar *chars; + JSBool result; + JSExceptionState *exnState; + void *tempMark; + JSTokenStream *ts; + JSErrorReporter older; + + CHECK_REQUEST(cx); + chars = js_InflateString(cx, bytes, length); + if (!chars) + return JS_TRUE; + + /* + * Return true on any out-of-memory error, so our caller doesn't try to + * collect more buffered source. + */ + result = JS_TRUE; + exnState = JS_SaveExceptionState(cx); + tempMark = JS_ARENA_MARK(&cx->tempPool); + ts = js_NewTokenStream(cx, chars, length, NULL, 0, NULL); + if (ts) { + older = JS_SetErrorReporter(cx, NULL); + if (!js_ParseTokenStream(cx, obj, ts)) { + /* + * We ran into an error. If it was because we ran out of source, + * we return false, so our caller will know to try to collect more + * buffered source. + */ + result = (ts->flags & TSF_EOF) == 0; + } + + JS_SetErrorReporter(cx, older); + js_CloseTokenStream(cx, ts); + JS_ARENA_RELEASE(&cx->tempPool, tempMark); + } + + JS_free(cx, chars); + JS_RestoreExceptionState(cx, exnState); + return result; +} + +JS_PUBLIC_API(JSScript *) +JS_CompileFile(JSContext *cx, JSObject *obj, const char *filename) +{ + void *mark; + JSTokenStream *ts; + JSScript *script; + + CHECK_REQUEST(cx); + mark = JS_ARENA_MARK(&cx->tempPool); + ts = js_NewFileTokenStream(cx, filename, stdin); + if (!ts) + return NULL; + script = CompileTokenStream(cx, obj, ts, mark, NULL); +#if JS_HAS_EXCEPTIONS + if (!script && !cx->fp) + js_ReportUncaughtException(cx); +#endif + return script; +} + +JS_PUBLIC_API(JSScript *) +JS_CompileFileHandle(JSContext *cx, JSObject *obj, const char *filename, + FILE *file) +{ + return JS_CompileFileHandleForPrincipals(cx, obj, filename, file, NULL); +} + +JS_PUBLIC_API(JSScript *) +JS_CompileFileHandleForPrincipals(JSContext *cx, JSObject *obj, + const char *filename, FILE *file, + JSPrincipals *principals) +{ + void *mark; + JSTokenStream *ts; + JSScript *script; + + CHECK_REQUEST(cx); + mark = JS_ARENA_MARK(&cx->tempPool); + ts = js_NewFileTokenStream(cx, NULL, file); + if (!ts) + return NULL; + ts->filename = filename; + /* XXXshaver js_NewFileTokenStream should do this, because it drops */ + if (principals) { + ts->principals = principals; + JSPRINCIPALS_HOLD(cx, ts->principals); + } + script = CompileTokenStream(cx, obj, ts, mark, NULL); +#if JS_HAS_EXCEPTIONS + if (!script && !cx->fp) + js_ReportUncaughtException(cx); +#endif + return script; +} + +JS_PUBLIC_API(JSObject *) +JS_NewScriptObject(JSContext *cx, JSScript *script) +{ + JSObject *obj; + + /* + * We use a dummy stack frame to protect the script from a GC caused + * by debugger-hook execution. + * + * XXX We really need a way to manage local roots and such more + * XXX automatically, at which point we can remove this one-off hack + * XXX and others within the engine. See bug 40757 for discussion. + */ + JSStackFrame dummy; + + CHECK_REQUEST(cx); + + memset(&dummy, 0, sizeof dummy); + dummy.down = cx->fp; + dummy.script = script; + cx->fp = &dummy; + + obj = js_NewObject(cx, &js_ScriptClass, NULL, NULL); + + cx->fp = dummy.down; + if (!obj) + return NULL; + + if (script) { + if (!JS_SetPrivate(cx, obj, script)) + return NULL; + script->object = obj; + } + return obj; +} + +JS_PUBLIC_API(JSObject *) +JS_GetScriptObject(JSScript *script) +{ + return script->object; +} + +JS_PUBLIC_API(void) +JS_DestroyScript(JSContext *cx, JSScript *script) +{ + CHECK_REQUEST(cx); + js_DestroyScript(cx, script); +} + +JS_PUBLIC_API(JSFunction *) +JS_CompileFunction(JSContext *cx, JSObject *obj, const char *name, + uintN nargs, const char **argnames, + const char *bytes, size_t length, + const char *filename, uintN lineno) +{ + jschar *chars; + JSFunction *fun; + + CHECK_REQUEST(cx); + chars = js_InflateString(cx, bytes, length); + if (!chars) + return NULL; + fun = JS_CompileUCFunction(cx, obj, name, nargs, argnames, chars, length, + filename, lineno); + JS_free(cx, chars); + return fun; +} + +JS_PUBLIC_API(JSFunction *) +JS_CompileFunctionForPrincipals(JSContext *cx, JSObject *obj, + JSPrincipals *principals, const char *name, + uintN nargs, const char **argnames, + const char *bytes, size_t length, + const char *filename, uintN lineno) +{ + jschar *chars; + JSFunction *fun; + + CHECK_REQUEST(cx); + chars = js_InflateString(cx, bytes, length); + if (!chars) + return NULL; + fun = JS_CompileUCFunctionForPrincipals(cx, obj, principals, name, + nargs, argnames, chars, length, + filename, lineno); + JS_free(cx, chars); + return fun; +} + +JS_PUBLIC_API(JSFunction *) +JS_CompileUCFunction(JSContext *cx, JSObject *obj, const char *name, + uintN nargs, const char **argnames, + const jschar *chars, size_t length, + const char *filename, uintN lineno) +{ + CHECK_REQUEST(cx); + return JS_CompileUCFunctionForPrincipals(cx, obj, NULL, name, + nargs, argnames, + chars, length, + filename, lineno); +} + +JS_PUBLIC_API(JSFunction *) +JS_CompileUCFunctionForPrincipals(JSContext *cx, JSObject *obj, + JSPrincipals *principals, const char *name, + uintN nargs, const char **argnames, + const jschar *chars, size_t length, + const char *filename, uintN lineno) +{ + void *mark; + JSTokenStream *ts; + JSFunction *fun; + JSAtom *funAtom, *argAtom; + uintN i; + + CHECK_REQUEST(cx); + mark = JS_ARENA_MARK(&cx->tempPool); + ts = js_NewTokenStream(cx, chars, length, filename, lineno, principals); + if (!ts) { + fun = NULL; + goto out; + } + if (!name) { + funAtom = NULL; + } else { + funAtom = js_Atomize(cx, name, strlen(name), 0); + if (!funAtom) { + fun = NULL; + goto out; + } + } + fun = js_NewFunction(cx, NULL, NULL, nargs, 0, obj, funAtom); + if (!fun) + goto out; + if (nargs) { + for (i = 0; i < nargs; i++) { + argAtom = js_Atomize(cx, argnames[i], strlen(argnames[i]), 0); + if (!argAtom) + break; + if (!js_AddNativeProperty(cx, fun->object, (jsid)argAtom, + js_GetArgument, js_SetArgument, + SPROP_INVALID_SLOT, + JSPROP_ENUMERATE | JSPROP_PERMANENT | + JSPROP_SHARED, + SPROP_HAS_SHORTID, i)) { + break; + } + } + if (i < nargs) { + fun = NULL; + goto out; + } + } + if (!js_CompileFunctionBody(cx, ts, fun)) { + fun = NULL; + goto out; + } + if (obj && funAtom) { + if (!OBJ_DEFINE_PROPERTY(cx, obj, (jsid)funAtom, + OBJECT_TO_JSVAL(fun->object), + NULL, NULL, 0, NULL)) { + return NULL; + } + } +out: + if (ts) + js_CloseTokenStream(cx, ts); + JS_ARENA_RELEASE(&cx->tempPool, mark); +#if JS_HAS_EXCEPTIONS + if (!fun && !cx->fp) + js_ReportUncaughtException(cx); +#endif + return fun; +} + +JS_PUBLIC_API(JSString *) +JS_DecompileScript(JSContext *cx, JSScript *script, const char *name, + uintN indent) +{ + JSPrinter *jp; + JSString *str; + + CHECK_REQUEST(cx); + jp = js_NewPrinter(cx, name, + indent & ~JS_DONT_PRETTY_PRINT, + !(indent & JS_DONT_PRETTY_PRINT)); + if (!jp) + return NULL; + if (js_DecompileScript(jp, script)) + str = js_GetPrinterOutput(jp); + else + str = NULL; + js_DestroyPrinter(jp); + return str; +} + +JS_PUBLIC_API(JSString *) +JS_DecompileFunction(JSContext *cx, JSFunction *fun, uintN indent) +{ + JSPrinter *jp; + JSString *str; + + CHECK_REQUEST(cx); + jp = js_NewPrinter(cx, JS_GetFunctionName(fun), + indent & ~JS_DONT_PRETTY_PRINT, + !(indent & JS_DONT_PRETTY_PRINT)); + if (!jp) + return NULL; + if (js_DecompileFunction(jp, fun)) + str = js_GetPrinterOutput(jp); + else + str = NULL; + js_DestroyPrinter(jp); + return str; +} + +JS_PUBLIC_API(JSString *) +JS_DecompileFunctionBody(JSContext *cx, JSFunction *fun, uintN indent) +{ + JSPrinter *jp; + JSString *str; + + CHECK_REQUEST(cx); + jp = js_NewPrinter(cx, JS_GetFunctionName(fun), + indent & ~JS_DONT_PRETTY_PRINT, + !(indent & JS_DONT_PRETTY_PRINT)); + if (!jp) + return NULL; + if (js_DecompileFunctionBody(jp, fun)) + str = js_GetPrinterOutput(jp); + else + str = NULL; + js_DestroyPrinter(jp); + return str; +} + +JS_PUBLIC_API(JSBool) +JS_ExecuteScript(JSContext *cx, JSObject *obj, JSScript *script, jsval *rval) +{ + CHECK_REQUEST(cx); + if (!js_Execute(cx, obj, script, NULL, 0, rval)) { +#if JS_HAS_EXCEPTIONS + if (!cx->fp) + js_ReportUncaughtException(cx); +#endif + return JS_FALSE; + } + return JS_TRUE; +} + +JS_PUBLIC_API(JSBool) +JS_ExecuteScriptPart(JSContext *cx, JSObject *obj, JSScript *script, + JSExecPart part, jsval *rval) +{ + JSScript tmp; + JSRuntime *rt; + JSBool ok; + + /* Make a temporary copy of the JSScript structure and farble it a bit. */ + tmp = *script; + if (part == JSEXEC_PROLOG) { + tmp.length = PTRDIFF(tmp.main, tmp.code, jsbytecode); + } else { + tmp.length -= PTRDIFF(tmp.main, tmp.code, jsbytecode); + tmp.code = tmp.main; + } + + /* Tell the debugger about our temporary copy of the script structure. */ + rt = cx->runtime; + if (rt->newScriptHook) { + rt->newScriptHook(cx, tmp.filename, tmp.lineno, &tmp, NULL, + rt->newScriptHookData); + } + + /* Execute the farbled struct and tell the debugger to forget about it. */ + ok = JS_ExecuteScript(cx, obj, &tmp, rval); + if (rt->destroyScriptHook) + rt->destroyScriptHook(cx, &tmp, rt->destroyScriptHookData); + return ok; +} + +JS_PUBLIC_API(JSBool) +JS_EvaluateScript(JSContext *cx, JSObject *obj, + const char *bytes, uintN length, + const char *filename, uintN lineno, + jsval *rval) +{ + jschar *chars; + JSBool ok; + + CHECK_REQUEST(cx); + chars = js_InflateString(cx, bytes, length); + if (!chars) + return JS_FALSE; + ok = JS_EvaluateUCScript(cx, obj, chars, length, filename, lineno, rval); + JS_free(cx, chars); + return ok; +} + +JS_PUBLIC_API(JSBool) +JS_EvaluateScriptForPrincipals(JSContext *cx, JSObject *obj, + JSPrincipals *principals, + const char *bytes, uintN length, + const char *filename, uintN lineno, + jsval *rval) +{ + jschar *chars; + JSBool ok; + + CHECK_REQUEST(cx); + chars = js_InflateString(cx, bytes, length); + if (!chars) + return JS_FALSE; + ok = JS_EvaluateUCScriptForPrincipals(cx, obj, principals, chars, length, + filename, lineno, rval); + JS_free(cx, chars); + return ok; +} + +JS_PUBLIC_API(JSBool) +JS_EvaluateUCScript(JSContext *cx, JSObject *obj, + const jschar *chars, uintN length, + const char *filename, uintN lineno, + jsval *rval) +{ + CHECK_REQUEST(cx); + return JS_EvaluateUCScriptForPrincipals(cx, obj, NULL, chars, length, + filename, lineno, rval); +} + +JS_PUBLIC_API(JSBool) +JS_EvaluateUCScriptForPrincipals(JSContext *cx, JSObject *obj, + JSPrincipals *principals, + const jschar *chars, uintN length, + const char *filename, uintN lineno, + jsval *rval) +{ + JSScript *script; + JSBool ok; + + CHECK_REQUEST(cx); + script = JS_CompileUCScriptForPrincipals(cx, obj, principals, chars, length, + filename, lineno); + if (!script) + return JS_FALSE; + ok = js_Execute(cx, obj, script, NULL, 0, rval); +#if JS_HAS_EXCEPTIONS + if (!ok && !cx->fp) + js_ReportUncaughtException(cx); +#endif + JS_DestroyScript(cx, script); + return ok; +} + +JS_PUBLIC_API(JSBool) +JS_CallFunction(JSContext *cx, JSObject *obj, JSFunction *fun, uintN argc, + jsval *argv, jsval *rval) +{ + CHECK_REQUEST(cx); + if (!js_InternalCall(cx, obj, OBJECT_TO_JSVAL(fun->object), argc, argv, + rval)) { +#if JS_HAS_EXCEPTIONS + if (!cx->fp) + js_ReportUncaughtException(cx); +#endif + return JS_FALSE; + } + return JS_TRUE; +} + +JS_PUBLIC_API(JSBool) +JS_CallFunctionName(JSContext *cx, JSObject *obj, const char *name, uintN argc, + jsval *argv, jsval *rval) +{ + jsval fval; + + CHECK_REQUEST(cx); + if (!JS_GetProperty(cx, obj, name, &fval)) + return JS_FALSE; + if (!js_InternalCall(cx, obj, fval, argc, argv, rval)) { +#if JS_HAS_EXCEPTIONS + if (!cx->fp) + js_ReportUncaughtException(cx); +#endif + return JS_FALSE; + } + return JS_TRUE; +} + +JS_PUBLIC_API(JSBool) +JS_CallFunctionValue(JSContext *cx, JSObject *obj, jsval fval, uintN argc, + jsval *argv, jsval *rval) +{ + CHECK_REQUEST(cx); + if (!js_InternalCall(cx, obj, fval, argc, argv, rval)) { +#if JS_HAS_EXCEPTIONS + if (!cx->fp) + js_ReportUncaughtException(cx); +#endif + return JS_FALSE; + } + return JS_TRUE; +} + +JS_PUBLIC_API(JSBranchCallback) +JS_SetBranchCallback(JSContext *cx, JSBranchCallback cb) +{ + JSBranchCallback oldcb; + + oldcb = cx->branchCallback; + cx->branchCallback = cb; + return oldcb; +} + +JS_PUBLIC_API(JSBool) +JS_IsRunning(JSContext *cx) +{ + return cx->fp != NULL; +} + +JS_PUBLIC_API(JSBool) +JS_IsConstructing(JSContext *cx) +{ + return cx->fp && (cx->fp->flags & JSFRAME_CONSTRUCTING); +} + +JS_FRIEND_API(JSBool) +JS_IsAssigning(JSContext *cx) +{ + JSStackFrame *fp; + jsbytecode *pc; + + for (fp = cx->fp; fp && !fp->script; fp = fp->down) + continue; + if (!fp || !(pc = fp->pc)) + return JS_FALSE; + return (js_CodeSpec[*pc].format & JOF_ASSIGNING) != 0; +} + +JS_PUBLIC_API(void) +JS_SetCallReturnValue2(JSContext *cx, jsval v) +{ +#if JS_HAS_LVALUE_RETURN + cx->rval2 = v; + cx->rval2set = JS_TRUE; +#endif +} + +/************************************************************************/ + +JS_PUBLIC_API(JSString *) +JS_NewString(JSContext *cx, char *bytes, size_t length) +{ + jschar *chars; + JSString *str; + + CHECK_REQUEST(cx); + /* Make a Unicode vector from the 8-bit char codes in bytes. */ + chars = js_InflateString(cx, bytes, length); + if (!chars) + return NULL; + + /* Free chars (but not bytes, which caller frees on error) if we fail. */ + str = js_NewString(cx, chars, length, 0); + if (!str) { + JS_free(cx, chars); + return NULL; + } + + /* Hand off bytes to the deflated string cache, if possible. */ + if (!js_SetStringBytes(str, bytes, length)) + JS_free(cx, bytes); + return str; +} + +JS_PUBLIC_API(JSString *) +JS_NewStringCopyN(JSContext *cx, const char *s, size_t n) +{ + jschar *js; + JSString *str; + + CHECK_REQUEST(cx); + js = js_InflateString(cx, s, n); + if (!js) + return NULL; + str = js_NewString(cx, js, n, 0); + if (!str) + JS_free(cx, js); + return str; +} + +JS_PUBLIC_API(JSString *) +JS_NewStringCopyZ(JSContext *cx, const char *s) +{ + size_t n; + jschar *js; + JSString *str; + + CHECK_REQUEST(cx); + if (!s) + return cx->runtime->emptyString; + n = strlen(s); + js = js_InflateString(cx, s, n); + if (!js) + return NULL; + str = js_NewString(cx, js, n, 0); + if (!str) + JS_free(cx, js); + return str; +} + +JS_PUBLIC_API(JSString *) +JS_InternString(JSContext *cx, const char *s) +{ + JSAtom *atom; + + CHECK_REQUEST(cx); + atom = js_Atomize(cx, s, strlen(s), ATOM_INTERNED); + if (!atom) + return NULL; + return ATOM_TO_STRING(atom); +} + +JS_PUBLIC_API(JSString *) +JS_NewUCString(JSContext *cx, jschar *chars, size_t length) +{ + CHECK_REQUEST(cx); + return js_NewString(cx, chars, length, 0); +} + +JS_PUBLIC_API(JSString *) +JS_NewUCStringCopyN(JSContext *cx, const jschar *s, size_t n) +{ + CHECK_REQUEST(cx); + return js_NewStringCopyN(cx, s, n, 0); +} + +JS_PUBLIC_API(JSString *) +JS_NewUCStringCopyZ(JSContext *cx, const jschar *s) +{ + CHECK_REQUEST(cx); + if (!s) + return cx->runtime->emptyString; + return js_NewStringCopyZ(cx, s, 0); +} + +JS_PUBLIC_API(JSString *) +JS_InternUCStringN(JSContext *cx, const jschar *s, size_t length) +{ + JSAtom *atom; + + CHECK_REQUEST(cx); + atom = js_AtomizeChars(cx, s, length, ATOM_INTERNED); + if (!atom) + return NULL; + return ATOM_TO_STRING(atom); +} + +JS_PUBLIC_API(JSString *) +JS_InternUCString(JSContext *cx, const jschar *s) +{ + return JS_InternUCStringN(cx, s, js_strlen(s)); +} + +JS_PUBLIC_API(char *) +JS_GetStringBytes(JSString *str) +{ + char *bytes; + + bytes = js_GetStringBytes(str); + return bytes ? bytes : ""; +} + +JS_PUBLIC_API(jschar *) +JS_GetStringChars(JSString *str) +{ + /* + * API botch (again, shades of JS_GetStringBytes): we have no cx to pass + * to js_UndependString (called by js_GetStringChars) for out-of-memory + * error reports, so js_UndependString passes NULL and suppresses errors. + * If it fails to convert a dependent string into an independent one, our + * caller will not be guaranteed a \u0000 terminator as a backstop. This + * may break some clients who already misbehave on embedded NULs. + * + * The gain of dependent strings, which cure quadratic and cubic growth + * rate bugs in string concatenation, is worth this slight loss in API + * compatibility. + */ + jschar *chars; + + chars = js_GetStringChars(str); + return chars ? chars : JSSTRING_CHARS(str); +} + +JS_PUBLIC_API(size_t) +JS_GetStringLength(JSString *str) +{ + return JSSTRING_LENGTH(str); +} + +JS_PUBLIC_API(intN) +JS_CompareStrings(JSString *str1, JSString *str2) +{ + return js_CompareStrings(str1, str2); +} + +JS_PUBLIC_API(JSString *) +JS_NewGrowableString(JSContext *cx, jschar *chars, size_t length) +{ + CHECK_REQUEST(cx); + return js_NewString(cx, chars, length, GCF_MUTABLE); +} + +JS_PUBLIC_API(JSString *) +JS_NewDependentString(JSContext *cx, JSString *str, size_t start, + size_t length) +{ + CHECK_REQUEST(cx); + return js_NewDependentString(cx, str, start, length, 0); +} + +JS_PUBLIC_API(JSString *) +JS_ConcatStrings(JSContext *cx, JSString *left, JSString *right) +{ + CHECK_REQUEST(cx); + return js_ConcatStrings(cx, left, right); +} + +JS_PUBLIC_API(const jschar *) +JS_UndependString(JSContext *cx, JSString *str) +{ + CHECK_REQUEST(cx); + return js_UndependString(cx, str); +} + +JS_PUBLIC_API(JSBool) +JS_MakeStringImmutable(JSContext *cx, JSString *str) +{ + CHECK_REQUEST(cx); + if (!js_UndependString(cx, str)) + return JS_FALSE; + + *js_GetGCThingFlags(str) &= ~GCF_MUTABLE; + return JS_TRUE; +} + +/************************************************************************/ + +JS_PUBLIC_API(void) +JS_ReportError(JSContext *cx, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + js_ReportErrorVA(cx, JSREPORT_ERROR, format, ap); + va_end(ap); +} + +JS_PUBLIC_API(void) +JS_ReportErrorNumber(JSContext *cx, JSErrorCallback errorCallback, + void *userRef, const uintN errorNumber, ...) +{ + va_list ap; + + va_start(ap, errorNumber); + js_ReportErrorNumberVA(cx, JSREPORT_ERROR, errorCallback, userRef, + errorNumber, JS_TRUE, ap); + va_end(ap); +} + +JS_PUBLIC_API(void) +JS_ReportErrorNumberUC(JSContext *cx, JSErrorCallback errorCallback, + void *userRef, const uintN errorNumber, ...) +{ + va_list ap; + + va_start(ap, errorNumber); + js_ReportErrorNumberVA(cx, JSREPORT_ERROR, errorCallback, userRef, + errorNumber, JS_FALSE, ap); + va_end(ap); +} + +JS_PUBLIC_API(JSBool) +JS_ReportWarning(JSContext *cx, const char *format, ...) +{ + va_list ap; + JSBool ok; + + va_start(ap, format); + ok = js_ReportErrorVA(cx, JSREPORT_WARNING, format, ap); + va_end(ap); + return ok; +} + +JS_PUBLIC_API(JSBool) +JS_ReportErrorFlagsAndNumber(JSContext *cx, uintN flags, + JSErrorCallback errorCallback, void *userRef, + const uintN errorNumber, ...) +{ + va_list ap; + JSBool ok; + + va_start(ap, errorNumber); + ok = js_ReportErrorNumberVA(cx, flags, errorCallback, userRef, + errorNumber, JS_TRUE, ap); + va_end(ap); + return ok; +} + +JS_PUBLIC_API(JSBool) +JS_ReportErrorFlagsAndNumberUC(JSContext *cx, uintN flags, + JSErrorCallback errorCallback, void *userRef, + const uintN errorNumber, ...) +{ + va_list ap; + JSBool ok; + + va_start(ap, errorNumber); + ok = js_ReportErrorNumberVA(cx, flags, errorCallback, userRef, + errorNumber, JS_FALSE, ap); + va_end(ap); + return ok; +} + +JS_PUBLIC_API(void) +JS_ReportOutOfMemory(JSContext *cx) +{ + js_ReportOutOfMemory(cx, js_GetErrorMessage); +} + +JS_PUBLIC_API(JSErrorReporter) +JS_SetErrorReporter(JSContext *cx, JSErrorReporter er) +{ + JSErrorReporter older; + + older = cx->errorReporter; + cx->errorReporter = er; + return older; +} + +/************************************************************************/ + +/* + * Regular Expressions. + */ +JS_PUBLIC_API(JSObject *) +JS_NewRegExpObject(JSContext *cx, char *bytes, size_t length, uintN flags) +{ +#if JS_HAS_REGEXPS + jschar *chars; + JSObject *obj; + + CHECK_REQUEST(cx); + chars = js_InflateString(cx, bytes, length); + if (!chars) + return NULL; + obj = js_NewRegExpObject(cx, NULL, chars, length, flags); + JS_free(cx, chars); + return obj; +#else + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NO_REG_EXPS); + return NULL; +#endif +} + +JS_PUBLIC_API(JSObject *) +JS_NewUCRegExpObject(JSContext *cx, jschar *chars, size_t length, uintN flags) +{ + CHECK_REQUEST(cx); +#if JS_HAS_REGEXPS + return js_NewRegExpObject(cx, NULL, chars, length, flags); +#else + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NO_REG_EXPS); + return NULL; +#endif +} + +JS_PUBLIC_API(void) +JS_SetRegExpInput(JSContext *cx, JSString *input, JSBool multiline) +{ + JSRegExpStatics *res; + + CHECK_REQUEST(cx); + /* No locking required, cx is thread-private and input must be live. */ + res = &cx->regExpStatics; + res->input = input; + res->multiline = multiline; + cx->runtime->gcPoke = JS_TRUE; +} + +JS_PUBLIC_API(void) +JS_ClearRegExpStatics(JSContext *cx) +{ + JSRegExpStatics *res; + + /* No locking required, cx is thread-private and input must be live. */ + res = &cx->regExpStatics; + res->input = NULL; + res->multiline = JS_FALSE; + res->parenCount = 0; + res->lastMatch = res->lastParen = js_EmptySubString; + res->leftContext = res->rightContext = js_EmptySubString; + cx->runtime->gcPoke = JS_TRUE; +} + +JS_PUBLIC_API(void) +JS_ClearRegExpRoots(JSContext *cx) +{ + JSRegExpStatics *res; + + /* No locking required, cx is thread-private and input must be live. */ + res = &cx->regExpStatics; + res->input = NULL; + cx->runtime->gcPoke = JS_TRUE; +} + +/* TODO: compile, execute, get/set other statics... */ + +/************************************************************************/ + +JS_PUBLIC_API(void) +JS_SetLocaleCallbacks(JSContext *cx, JSLocaleCallbacks *callbacks) +{ + cx->localeCallbacks = callbacks; +} + +JS_PUBLIC_API(JSLocaleCallbacks *) +JS_GetLocaleCallbacks(JSContext *cx) +{ + return cx->localeCallbacks; +} + +/************************************************************************/ + +JS_PUBLIC_API(JSBool) +JS_IsExceptionPending(JSContext *cx) +{ +#if JS_HAS_EXCEPTIONS + return (JSBool) cx->throwing; +#else + return JS_FALSE; +#endif +} + +JS_PUBLIC_API(JSBool) +JS_GetPendingException(JSContext *cx, jsval *vp) +{ +#if JS_HAS_EXCEPTIONS + CHECK_REQUEST(cx); + if (!cx->throwing) + return JS_FALSE; + *vp = cx->exception; + return JS_TRUE; +#else + return JS_FALSE; +#endif +} + +JS_PUBLIC_API(void) +JS_SetPendingException(JSContext *cx, jsval v) +{ + CHECK_REQUEST(cx); +#if JS_HAS_EXCEPTIONS + cx->throwing = JS_TRUE; + cx->exception = v; +#endif +} + +JS_PUBLIC_API(void) +JS_ClearPendingException(JSContext *cx) +{ +#if JS_HAS_EXCEPTIONS + cx->throwing = JS_FALSE; + cx->exception = JSVAL_VOID; +#endif +} + +#if JS_HAS_EXCEPTIONS +struct JSExceptionState { + JSBool throwing; + jsval exception; +}; +#endif + +JS_PUBLIC_API(JSExceptionState *) +JS_SaveExceptionState(JSContext *cx) +{ +#if JS_HAS_EXCEPTIONS + JSExceptionState *state; + + CHECK_REQUEST(cx); + state = (JSExceptionState *) JS_malloc(cx, sizeof(JSExceptionState)); + if (state) { + state->throwing = JS_GetPendingException(cx, &state->exception); + if (state->throwing && JSVAL_IS_GCTHING(state->exception)) + js_AddRoot(cx, &state->exception, "JSExceptionState.exception"); + } + return state; +#else + return NULL; +#endif +} + +JS_PUBLIC_API(void) +JS_RestoreExceptionState(JSContext *cx, JSExceptionState *state) +{ +#if JS_HAS_EXCEPTIONS + CHECK_REQUEST(cx); + if (state) { + if (state->throwing) + JS_SetPendingException(cx, state->exception); + else + JS_ClearPendingException(cx); + JS_DropExceptionState(cx, state); + } +#endif +} + +JS_PUBLIC_API(void) +JS_DropExceptionState(JSContext *cx, JSExceptionState *state) +{ +#if JS_HAS_EXCEPTIONS + CHECK_REQUEST(cx); + if (state) { + if (state->throwing && JSVAL_IS_GCTHING(state->exception)) + JS_RemoveRoot(cx, &state->exception); + JS_free(cx, state); + } +#endif +} + +JS_PUBLIC_API(JSErrorReport *) +JS_ErrorFromException(JSContext *cx, jsval v) +{ +#if JS_HAS_EXCEPTIONS + CHECK_REQUEST(cx); + return js_ErrorFromException(cx, v); +#else + return NULL; +#endif +} + +#ifdef JS_THREADSAFE +JS_PUBLIC_API(jsword) +JS_GetContextThread(JSContext *cx) +{ + return cx->thread; +} + +JS_PUBLIC_API(jsword) +JS_SetContextThread(JSContext *cx) +{ + intN old = cx->thread; + cx->thread = js_CurrentThreadId(); + return old; +} + +JS_PUBLIC_API(intN) +JS_ClearContextThread(JSContext *cx) +{ + intN old = cx->thread; + cx->thread = 0; + return old; +} +#endif + +/************************************************************************/ + +#if defined(XP_WIN) +#include +/* + * Initialization routine for the JS DLL... + */ + +/* + * Global Instance handle... + * In Win32 this is the module handle of the DLL. + * + * In Win16 this is the instance handle of the application + * which loaded the DLL. + */ + +#ifdef _WIN32 +BOOL WINAPI DllMain (HINSTANCE hDLL, DWORD dwReason, LPVOID lpReserved) +{ + return TRUE; +} + +#else /* !_WIN32 */ + +int CALLBACK LibMain( HINSTANCE hInst, WORD wDataSeg, + WORD cbHeapSize, LPSTR lpszCmdLine ) +{ + return TRUE; +} + +BOOL CALLBACK __loadds WEP(BOOL fSystemExit) +{ + return TRUE; +} + +#endif /* !_WIN32 */ +#endif /* XP_WIN */ diff --git a/src/dom/js/jsapi.h b/src/dom/js/jsapi.h new file mode 100644 index 000000000..92b4e1d59 --- /dev/null +++ b/src/dom/js/jsapi.h @@ -0,0 +1,1752 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsapi_h___ +#define jsapi_h___ +/* + * JavaScript API. + */ +#include +#include +#include "jspubtd.h" + +JS_BEGIN_EXTERN_C + +/* + * Type tags stored in the low bits of a jsval. + */ +#define JSVAL_OBJECT 0x0 /* untagged reference to object */ +#define JSVAL_INT 0x1 /* tagged 31-bit integer value */ +#define JSVAL_DOUBLE 0x2 /* tagged reference to double */ +#define JSVAL_STRING 0x4 /* tagged reference to string */ +#define JSVAL_BOOLEAN 0x6 /* tagged boolean value */ + +/* Type tag bitfield length and derived macros. */ +#define JSVAL_TAGBITS 3 +#define JSVAL_TAGMASK JS_BITMASK(JSVAL_TAGBITS) +#define JSVAL_TAG(v) ((v) & JSVAL_TAGMASK) +#define JSVAL_SETTAG(v,t) ((v) | (t)) +#define JSVAL_CLRTAG(v) ((v) & ~(jsval)JSVAL_TAGMASK) +#define JSVAL_ALIGN JS_BIT(JSVAL_TAGBITS) + +/* Predicates for type testing. */ +#define JSVAL_IS_OBJECT(v) (JSVAL_TAG(v) == JSVAL_OBJECT) +#define JSVAL_IS_NUMBER(v) (JSVAL_IS_INT(v) || JSVAL_IS_DOUBLE(v)) +#define JSVAL_IS_INT(v) (((v) & JSVAL_INT) && (v) != JSVAL_VOID) +#define JSVAL_IS_DOUBLE(v) (JSVAL_TAG(v) == JSVAL_DOUBLE) +#define JSVAL_IS_STRING(v) (JSVAL_TAG(v) == JSVAL_STRING) +#define JSVAL_IS_BOOLEAN(v) (JSVAL_TAG(v) == JSVAL_BOOLEAN) +#define JSVAL_IS_NULL(v) ((v) == JSVAL_NULL) +#define JSVAL_IS_VOID(v) ((v) == JSVAL_VOID) +#define JSVAL_IS_PRIMITIVE(v) (!JSVAL_IS_OBJECT(v) || JSVAL_IS_NULL(v)) + +/* Objects, strings, and doubles are GC'ed. */ +#define JSVAL_IS_GCTHING(v) (!((v) & JSVAL_INT) && !JSVAL_IS_BOOLEAN(v)) +#define JSVAL_TO_GCTHING(v) ((void *)JSVAL_CLRTAG(v)) +#define JSVAL_TO_OBJECT(v) ((JSObject *)JSVAL_TO_GCTHING(v)) +#define JSVAL_TO_DOUBLE(v) ((jsdouble *)JSVAL_TO_GCTHING(v)) +#define JSVAL_TO_STRING(v) ((JSString *)JSVAL_TO_GCTHING(v)) +#define OBJECT_TO_JSVAL(obj) ((jsval)(obj)) +#define DOUBLE_TO_JSVAL(dp) JSVAL_SETTAG((jsval)(dp), JSVAL_DOUBLE) +#define STRING_TO_JSVAL(str) JSVAL_SETTAG((jsval)(str), JSVAL_STRING) + +/* Lock and unlock the GC thing held by a jsval. */ +#define JSVAL_LOCK(cx,v) (JSVAL_IS_GCTHING(v) \ + ? JS_LockGCThing(cx, JSVAL_TO_GCTHING(v)) \ + : JS_TRUE) +#define JSVAL_UNLOCK(cx,v) (JSVAL_IS_GCTHING(v) \ + ? JS_UnlockGCThing(cx, JSVAL_TO_GCTHING(v)) \ + : JS_TRUE) + +/* Domain limits for the jsval int type. */ +#define JSVAL_INT_BITS 31 +#define JSVAL_INT_POW2(n) ((jsval)1 << (n)) +#define JSVAL_INT_MIN ((jsval)1 - JSVAL_INT_POW2(30)) +#define JSVAL_INT_MAX (JSVAL_INT_POW2(30) - 1) +#define INT_FITS_IN_JSVAL(i) ((jsuint)((i)+JSVAL_INT_MAX) <= 2*JSVAL_INT_MAX) +#define JSVAL_TO_INT(v) ((jsint)(v) >> 1) +#define INT_TO_JSVAL(i) (((jsval)(i) << 1) | JSVAL_INT) + +/* Convert between boolean and jsval. */ +#define JSVAL_TO_BOOLEAN(v) ((JSBool)((v) >> JSVAL_TAGBITS)) +#define BOOLEAN_TO_JSVAL(b) JSVAL_SETTAG((jsval)(b) << JSVAL_TAGBITS, \ + JSVAL_BOOLEAN) + +/* A private data pointer (2-byte-aligned) can be stored as an int jsval. */ +#define JSVAL_TO_PRIVATE(v) ((void *)((v) & ~JSVAL_INT)) +#define PRIVATE_TO_JSVAL(p) ((jsval)(p) | JSVAL_INT) + +/* Property attributes, set in JSPropertySpec and passed to API functions. */ +#define JSPROP_ENUMERATE 0x01 /* property is visible to for/in loop */ +#define JSPROP_READONLY 0x02 /* not settable: assignment is no-op */ +#define JSPROP_PERMANENT 0x04 /* property cannot be deleted */ +#define JSPROP_EXPORTED 0x08 /* property is exported from object */ +#define JSPROP_GETTER 0x10 /* property holds getter function */ +#define JSPROP_SETTER 0x20 /* property holds setter function */ +#define JSPROP_SHARED 0x40 /* don't allocate a value slot for this + property; don't copy the property on + set of the same-named property in an + object that delegates to a prototype + containing this property */ +#define JSPROP_INDEX 0x80 /* name is actually (jsint) index */ + +/* Function flags, set in JSFunctionSpec and passed to JS_NewFunction etc. */ +#define JSFUN_LAMBDA 0x08 /* expressed, not declared, function */ +#define JSFUN_GETTER JSPROP_GETTER +#define JSFUN_SETTER JSPROP_SETTER +#define JSFUN_BOUND_METHOD 0x40 /* bind this to fun->object's parent */ +#define JSFUN_HEAVYWEIGHT 0x80 /* activation requires a Call object */ +#define JSFUN_FLAGS_MASK 0xf8 /* overlay JSFUN_* attributes */ + +/* + * Well-known JS values. The extern'd variables are initialized when the + * first JSContext is created by JS_NewContext (see below). + */ +#define JSVAL_VOID INT_TO_JSVAL(0 - JSVAL_INT_POW2(30)) +#define JSVAL_NULL OBJECT_TO_JSVAL(0) +#define JSVAL_ZERO INT_TO_JSVAL(0) +#define JSVAL_ONE INT_TO_JSVAL(1) +#define JSVAL_FALSE BOOLEAN_TO_JSVAL(JS_FALSE) +#define JSVAL_TRUE BOOLEAN_TO_JSVAL(JS_TRUE) + +/* + * Microseconds since the epoch, midnight, January 1, 1970 UTC. See the + * comment in jstypes.h regarding safe int64 usage. + */ +extern JS_PUBLIC_API(int64) +JS_Now(); + +/* Don't want to export data, so provide accessors for non-inline jsvals. */ +extern JS_PUBLIC_API(jsval) +JS_GetNaNValue(JSContext *cx); + +extern JS_PUBLIC_API(jsval) +JS_GetNegativeInfinityValue(JSContext *cx); + +extern JS_PUBLIC_API(jsval) +JS_GetPositiveInfinityValue(JSContext *cx); + +extern JS_PUBLIC_API(jsval) +JS_GetEmptyStringValue(JSContext *cx); + +/* + * Format is a string of the following characters (spaces are insignificant), + * specifying the tabulated type conversions: + * + * b JSBool Boolean + * c uint16/jschar ECMA uint16, Unicode char + * i int32 ECMA int32 + * u uint32 ECMA uint32 + * j int32 Rounded int32 (coordinate) + * d jsdouble IEEE double + * I jsdouble Integral IEEE double + * s char * C string + * S JSString * Unicode string, accessed by a JSString pointer + * W jschar * Unicode character vector, 0-terminated (W for wide) + * o JSObject * Object reference + * f JSFunction * Function private + * v jsval Argument value (no conversion) + * * N/A Skip this argument (no vararg) + * / N/A End of required arguments + * + * The variable argument list after format must consist of &b, &c, &s, e.g., + * where those variables have the types given above. For the pointer types + * char *, JSString *, and JSObject *, the pointed-at memory returned belongs + * to the JS runtime, not to the calling native code. The runtime promises + * to keep this memory valid so long as argv refers to allocated stack space + * (so long as the native function is active). + * + * Fewer arguments than format specifies may be passed only if there is a / + * in format after the last required argument specifier and argc is at least + * the number of required arguments. More arguments than format specifies + * may be passed without error; it is up to the caller to deal with trailing + * unconverted arguments. + */ +extern JS_PUBLIC_API(JSBool) +JS_ConvertArguments(JSContext *cx, uintN argc, jsval *argv, const char *format, + ...); + +#ifdef va_start +extern JS_PUBLIC_API(JSBool) +JS_ConvertArgumentsVA(JSContext *cx, uintN argc, jsval *argv, + const char *format, va_list ap); +#endif + +/* + * Inverse of JS_ConvertArguments: scan format and convert trailing arguments + * into jsvals, GC-rooted if necessary by the JS stack. Return null on error, + * and a pointer to the new argument vector on success. Also return a stack + * mark on success via *markp, in which case the caller must eventually clean + * up by calling JS_PopArguments. + * + * Note that the number of actual arguments supplied is specified exclusively + * by format, so there is no argc parameter. + */ +extern JS_PUBLIC_API(jsval *) +JS_PushArguments(JSContext *cx, void **markp, const char *format, ...); + +#ifdef va_start +extern JS_PUBLIC_API(jsval *) +JS_PushArgumentsVA(JSContext *cx, void **markp, const char *format, va_list ap); +#endif + +extern JS_PUBLIC_API(void) +JS_PopArguments(JSContext *cx, void *mark); + +#ifdef JS_ARGUMENT_FORMATTER_DEFINED + +/* + * Add and remove a format string handler for JS_{Convert,Push}Arguments{,VA}. + * The handler function has this signature (see jspubtd.h): + * + * JSBool MyArgumentFormatter(JSContext *cx, const char *format, + * JSBool fromJS, jsval **vpp, va_list *app); + * + * It should return true on success, and return false after reporting an error + * or detecting an already-reported error. + * + * For a given format string, for example "AA", the formatter is called from + * JS_ConvertArgumentsVA like so: + * + * formatter(cx, "AA...", JS_TRUE, &sp, &ap); + * + * sp points into the arguments array on the JS stack, while ap points into + * the stdarg.h va_list on the C stack. The JS_TRUE passed for fromJS tells + * the formatter to convert zero or more jsvals at sp to zero or more C values + * accessed via pointers-to-values at ap, updating both sp (via *vpp) and ap + * (via *app) to point past the converted arguments and their result pointers + * on the C stack. + * + * When called from JS_PushArgumentsVA, the formatter is invoked thus: + * + * formatter(cx, "AA...", JS_FALSE, &sp, &ap); + * + * where JS_FALSE for fromJS means to wrap the C values at ap according to the + * format specifier and store them at sp, updating ap and sp appropriately. + * + * The "..." after "AA" is the rest of the format string that was passed into + * JS_{Convert,Push}Arguments{,VA}. The actual format trailing substring used + * in each Convert or PushArguments call is passed to the formatter, so that + * one such function may implement several formats, in order to share code. + * + * Remove just forgets about any handler associated with format. Add does not + * copy format, it points at the string storage allocated by the caller, which + * is typically a string constant. If format is in dynamic storage, it is up + * to the caller to keep the string alive until Remove is called. + */ +extern JS_PUBLIC_API(JSBool) +JS_AddArgumentFormatter(JSContext *cx, const char *format, + JSArgumentFormatter formatter); + +extern JS_PUBLIC_API(void) +JS_RemoveArgumentFormatter(JSContext *cx, const char *format); + +#endif /* JS_ARGUMENT_FORMATTER_DEFINED */ + +extern JS_PUBLIC_API(JSBool) +JS_ConvertValue(JSContext *cx, jsval v, JSType type, jsval *vp); + +extern JS_PUBLIC_API(JSBool) +JS_ValueToObject(JSContext *cx, jsval v, JSObject **objp); + +extern JS_PUBLIC_API(JSFunction *) +JS_ValueToFunction(JSContext *cx, jsval v); + +extern JS_PUBLIC_API(JSFunction *) +JS_ValueToConstructor(JSContext *cx, jsval v); + +extern JS_PUBLIC_API(JSString *) +JS_ValueToString(JSContext *cx, jsval v); + +extern JS_PUBLIC_API(JSBool) +JS_ValueToNumber(JSContext *cx, jsval v, jsdouble *dp); + +/* + * Convert a value to a number, then to an int32, according to the ECMA rules + * for ToInt32. + */ +extern JS_PUBLIC_API(JSBool) +JS_ValueToECMAInt32(JSContext *cx, jsval v, int32 *ip); + +/* + * Convert a value to a number, then to a uint32, according to the ECMA rules + * for ToUint32. + */ +extern JS_PUBLIC_API(JSBool) +JS_ValueToECMAUint32(JSContext *cx, jsval v, uint32 *ip); + +/* + * Convert a value to a number, then to an int32 if it fits by rounding to + * nearest; but failing with an error report if the double is out of range + * or unordered. + */ +extern JS_PUBLIC_API(JSBool) +JS_ValueToInt32(JSContext *cx, jsval v, int32 *ip); + +/* + * ECMA ToUint16, for mapping a jsval to a Unicode point. + */ +extern JS_PUBLIC_API(JSBool) +JS_ValueToUint16(JSContext *cx, jsval v, uint16 *ip); + +extern JS_PUBLIC_API(JSBool) +JS_ValueToBoolean(JSContext *cx, jsval v, JSBool *bp); + +extern JS_PUBLIC_API(JSType) +JS_TypeOfValue(JSContext *cx, jsval v); + +extern JS_PUBLIC_API(const char *) +JS_GetTypeName(JSContext *cx, JSType type); + +/************************************************************************/ + +/* + * Initialization, locking, contexts, and memory allocation. + */ +#define JS_NewRuntime JS_Init +#define JS_DestroyRuntime JS_Finish +#define JS_LockRuntime JS_Lock +#define JS_UnlockRuntime JS_Unlock + +extern JS_PUBLIC_API(JSRuntime *) +JS_NewRuntime(uint32 maxbytes); + +extern JS_PUBLIC_API(void) +JS_DestroyRuntime(JSRuntime *rt); + +extern JS_PUBLIC_API(void) +JS_ShutDown(void); + +JS_PUBLIC_API(void *) +JS_GetRuntimePrivate(JSRuntime *rt); + +JS_PUBLIC_API(void) +JS_SetRuntimePrivate(JSRuntime *rt, void *data); + +#ifdef JS_THREADSAFE + +extern JS_PUBLIC_API(void) +JS_BeginRequest(JSContext *cx); + +extern JS_PUBLIC_API(void) +JS_EndRequest(JSContext *cx); + +/* Yield to pending GC operations, regardless of request depth */ +extern JS_PUBLIC_API(void) +JS_YieldRequest(JSContext *cx); + +extern JS_PUBLIC_API(jsrefcount) +JS_SuspendRequest(JSContext *cx); + +extern JS_PUBLIC_API(void) +JS_ResumeRequest(JSContext *cx, jsrefcount saveDepth); + +#endif /* JS_THREADSAFE */ + +extern JS_PUBLIC_API(void) +JS_Lock(JSRuntime *rt); + +extern JS_PUBLIC_API(void) +JS_Unlock(JSRuntime *rt); + +extern JS_PUBLIC_API(JSContext *) +JS_NewContext(JSRuntime *rt, size_t stackChunkSize); + +extern JS_PUBLIC_API(void) +JS_DestroyContext(JSContext *cx); + +extern JS_PUBLIC_API(void) +JS_DestroyContextNoGC(JSContext *cx); + +extern JS_PUBLIC_API(void) +JS_DestroyContextMaybeGC(JSContext *cx); + +extern JS_PUBLIC_API(void *) +JS_GetContextPrivate(JSContext *cx); + +extern JS_PUBLIC_API(void) +JS_SetContextPrivate(JSContext *cx, void *data); + +extern JS_PUBLIC_API(JSRuntime *) +JS_GetRuntime(JSContext *cx); + +extern JS_PUBLIC_API(JSContext *) +JS_ContextIterator(JSRuntime *rt, JSContext **iterp); + +extern JS_PUBLIC_API(JSVersion) +JS_GetVersion(JSContext *cx); + +extern JS_PUBLIC_API(JSVersion) +JS_SetVersion(JSContext *cx, JSVersion version); + +extern JS_PUBLIC_API(const char *) +JS_VersionToString(JSVersion version); + +extern JS_PUBLIC_API(JSVersion) +JS_StringToVersion(const char *string); + +/* + * JS options are orthogonal to version, and may be freely composed with one + * another as well as with version. + * + * JSOPTION_VAROBJFIX is recommended -- see the comments associated with the + * prototypes for JS_ExecuteScript, JS_EvaluateScript, etc. + */ +#define JSOPTION_STRICT JS_BIT(0) /* warn on dubious practice */ +#define JSOPTION_WERROR JS_BIT(1) /* convert warning to error */ +#define JSOPTION_VAROBJFIX JS_BIT(2) /* make JS_EvaluateScript use + the last object on its 'obj' + param's scope chain as the + ECMA 'variables object' */ +#define JSOPTION_PRIVATE_IS_NSISUPPORTS \ + JS_BIT(3) /* context private data points + to an nsISupports subclass */ + +extern JS_PUBLIC_API(uint32) +JS_GetOptions(JSContext *cx); + +extern JS_PUBLIC_API(uint32) +JS_SetOptions(JSContext *cx, uint32 options); + +extern JS_PUBLIC_API(uint32) +JS_ToggleOptions(JSContext *cx, uint32 options); + +extern JS_PUBLIC_API(const char *) +JS_GetImplementationVersion(void); + +extern JS_PUBLIC_API(JSObject *) +JS_GetGlobalObject(JSContext *cx); + +extern JS_PUBLIC_API(void) +JS_SetGlobalObject(JSContext *cx, JSObject *obj); + +/* + * Initialize standard JS class constructors, prototypes, and any top-level + * functions and constants associated with the standard classes (e.g. isNaN + * for Number). + * + * NB: This sets cx's global object to obj if it was null. + */ +extern JS_PUBLIC_API(JSBool) +JS_InitStandardClasses(JSContext *cx, JSObject *obj); + +/* + * Resolve id, which must contain either a string or an int, to a standard + * class name in obj if possible, defining the class's constructor and/or + * prototype and storing true in *resolved. If id does not name a standard + * class or a top-level property induced by initializing a standard class, + * store false in *resolved and just return true. Return false on error, + * as usual for JSBool result-typed API entry points. + * + * This API can be called directly from a global object class's resolve op, + * to define standard classes lazily. The class's enumerate op should call + * JS_EnumerateStandardClasses(cx, obj), to define eagerly during for..in + * loops any classes not yet resolved lazily. + */ +extern JS_PUBLIC_API(JSBool) +JS_ResolveStandardClass(JSContext *cx, JSObject *obj, jsval id, + JSBool *resolved); + +extern JS_PUBLIC_API(JSBool) +JS_EnumerateStandardClasses(JSContext *cx, JSObject *obj); + +extern JS_PUBLIC_API(JSObject *) +JS_GetScopeChain(JSContext *cx); + +extern JS_PUBLIC_API(void *) +JS_malloc(JSContext *cx, size_t nbytes); + +extern JS_PUBLIC_API(void *) +JS_realloc(JSContext *cx, void *p, size_t nbytes); + +extern JS_PUBLIC_API(void) +JS_free(JSContext *cx, void *p); + +extern JS_PUBLIC_API(char *) +JS_strdup(JSContext *cx, const char *s); + +extern JS_PUBLIC_API(jsdouble *) +JS_NewDouble(JSContext *cx, jsdouble d); + +extern JS_PUBLIC_API(JSBool) +JS_NewDoubleValue(JSContext *cx, jsdouble d, jsval *rval); + +extern JS_PUBLIC_API(JSBool) +JS_NewNumberValue(JSContext *cx, jsdouble d, jsval *rval); + +/* + * A JS GC root is a pointer to a JSObject *, JSString *, or jsdouble * that + * itself points into the GC heap (more recently, we support this extension: + * a root may be a pointer to a jsval v for which JSVAL_IS_GCTHING(v) is true). + * + * Therefore, you never pass JSObject *obj to JS_AddRoot(cx, obj). You always + * call JS_AddRoot(cx, &obj), passing obj by reference. And later, before obj + * or the structure it is embedded within goes out of scope or is freed, you + * must call JS_RemoveRoot(cx, &obj). + * + * Also, use JS_AddNamedRoot(cx, &structPtr->memberObj, "structPtr->memberObj") + * in preference to JS_AddRoot(cx, &structPtr->memberObj), in order to identify + * roots by their source callsites. This way, you can find the callsite while + * debugging if you should fail to do JS_RemoveRoot(cx, &structPtr->memberObj) + * before freeing structPtr's memory. + */ +extern JS_PUBLIC_API(JSBool) +JS_AddRoot(JSContext *cx, void *rp); + +#ifdef NAME_ALL_GC_ROOTS +#define JS_DEFINE_TO_TOKEN(def) #def +#define JS_DEFINE_TO_STRING(def) JS_DEFINE_TO_TOKEN(def) +#define JS_AddRoot(cx,rp) JS_AddNamedRoot((cx), (rp), (__FILE__ ":" JS_TOKEN_TO_STRING(__LINE__)) +#endif + +extern JS_PUBLIC_API(JSBool) +JS_AddNamedRoot(JSContext *cx, void *rp, const char *name); + +extern JS_PUBLIC_API(JSBool) +JS_AddNamedRootRT(JSRuntime *rt, void *rp, const char *name); + +extern JS_PUBLIC_API(JSBool) +JS_RemoveRoot(JSContext *cx, void *rp); + +extern JS_PUBLIC_API(JSBool) +JS_RemoveRootRT(JSRuntime *rt, void *rp); + +/* + * The last GC thing of each type (object, string, double, external string + * types) created on a given context is kept alive until another thing of the + * same type is created, using a newborn root in the context. These newborn + * roots help native code protect newly-created GC-things from GC invocations + * activated before those things can be rooted using local or global roots. + * + * However, the newborn roots can also entrain great gobs of garbage, so the + * JS_GC entry point clears them for the context on which GC is being forced. + * Embeddings may need to do likewise for all contexts. + * + * XXXbe See bug 40757 (http://bugzilla.mozilla.org/show_bug.cgi?id=40757), + * which proposes switching (with an #ifdef, alas, if we want to maintain API + * compatibility) to a JNI-like extensible local root frame stack model. + */ +extern JS_PUBLIC_API(void) +JS_ClearNewbornRoots(JSContext *cx); + +#ifdef DEBUG +extern JS_PUBLIC_API(void) +JS_DumpNamedRoots(JSRuntime *rt, + void (*dump)(const char *name, void *rp, void *data), + void *data); +#endif + +/* + * Call JS_MapGCRoots to map the GC's roots table using map(rp, name, data). + * The root is pointed at by rp; if the root is unnamed, name is null; data is + * supplied from the third parameter to JS_MapGCRoots. + * + * The map function should return JS_MAP_GCROOT_REMOVE to cause the currently + * enumerated root to be removed. To stop enumeration, set JS_MAP_GCROOT_STOP + * in the return value. To keep on mapping, return JS_MAP_GCROOT_NEXT. These + * constants are flags; you can OR them together. + * + * This function acquires and releases rt's GC lock around the mapping of the + * roots table, so the map function should run to completion in as few cycles + * as possible. Of course, map cannot call JS_GC, JS_MaybeGC, JS_BeginRequest, + * or any JS API entry point that acquires locks, without double-tripping or + * deadlocking on the GC lock. + * + * JS_MapGCRoots returns the count of roots that were successfully mapped. + */ +#define JS_MAP_GCROOT_NEXT 0 /* continue mapping entries */ +#define JS_MAP_GCROOT_STOP 1 /* stop mapping entries */ +#define JS_MAP_GCROOT_REMOVE 2 /* remove and free the current entry */ + +typedef intN +(* JS_DLL_CALLBACK JSGCRootMapFun)(void *rp, const char *name, void *data); + +extern JS_PUBLIC_API(uint32) +JS_MapGCRoots(JSRuntime *rt, JSGCRootMapFun map, void *data); + +extern JS_PUBLIC_API(JSBool) +JS_LockGCThing(JSContext *cx, void *thing); + +extern JS_PUBLIC_API(JSBool) +JS_LockGCThingRT(JSRuntime *rt, void *thing); + +extern JS_PUBLIC_API(JSBool) +JS_UnlockGCThing(JSContext *cx, void *thing); + +extern JS_PUBLIC_API(JSBool) +JS_UnlockGCThingRT(JSRuntime *rt, void *thing); + +/* + * For implementors of JSObjectOps.mark, to mark a GC-thing reachable via a + * property or other strong ref identified for debugging purposes by name. + * The name argument's storage needs to live only as long as the call to + * this routine. + * + * The final arg is used by GC_MARK_DEBUG code to build a ref path through + * the GC's live thing graph. Implementors of JSObjectOps.mark should pass + * its final arg through to this function when marking all GC-things that are + * directly reachable from the object being marked. + * + * See the JSMarkOp typedef in jspubtd.h, and the JSObjectOps struct below. + */ +extern JS_PUBLIC_API(void) +JS_MarkGCThing(JSContext *cx, void *thing, const char *name, void *arg); + +extern JS_PUBLIC_API(void) +JS_GC(JSContext *cx); + +extern JS_PUBLIC_API(void) +JS_MaybeGC(JSContext *cx); + +extern JS_PUBLIC_API(JSGCCallback) +JS_SetGCCallback(JSContext *cx, JSGCCallback cb); + +extern JS_PUBLIC_API(JSGCCallback) +JS_SetGCCallbackRT(JSRuntime *rt, JSGCCallback cb); + +extern JS_PUBLIC_API(JSBool) +JS_IsAboutToBeFinalized(JSContext *cx, void *thing); + +/* + * Add an external string finalizer, one created by JS_NewExternalString (see + * below) using a type-code returned from this function, and that understands + * how to free or release the memory pointed at by JS_GetStringChars(str). + * + * Return a nonnegative type index if there is room for finalizer in the + * global GC finalizers table, else return -1. If the engine is compiled + * JS_THREADSAFE and used in a multi-threaded environment, this function must + * be invoked on the primordial thread only, at startup -- or else the entire + * program must single-thread itself while loading a module that calls this + * function. + */ +extern JS_PUBLIC_API(intN) +JS_AddExternalStringFinalizer(JSStringFinalizeOp finalizer); + +/* + * Remove finalizer from the global GC finalizers table, returning its type + * code if found, -1 if not found. + * + * As with JS_AddExternalStringFinalizer, there is a threading restriction + * if you compile the engine JS_THREADSAFE: this function may be called for a + * given finalizer pointer on only one thread; different threads may call to + * remove distinct finalizers safely. + * + * You must ensure that all strings with finalizer's type have been collected + * before calling this function. Otherwise, string data will be leaked by the + * GC, for want of a finalizer to call. + */ +extern JS_PUBLIC_API(intN) +JS_RemoveExternalStringFinalizer(JSStringFinalizeOp finalizer); + +/* + * Create a new JSString whose chars member refers to external memory, i.e., + * memory requiring special, type-specific finalization. The type code must + * be a nonnegative return value from JS_AddExternalStringFinalizer. + */ +extern JS_PUBLIC_API(JSString *) +JS_NewExternalString(JSContext *cx, jschar *chars, size_t length, intN type); + +/* + * Returns the external-string finalizer index for this string, or -1 if it is + * an "internal" (native to JS engine) string. + */ +extern JS_PUBLIC_API(intN) +JS_GetExternalStringGCType(JSRuntime *rt, JSString *str); + +/* + * Sets maximum (if stack grows upward) or minimum (downward) legal stack byte + * address in limitAddr for the thread or process stack used by cx. To disable + * stack size checking, pass 0 for limitAddr. + */ +extern JS_PUBLIC_API(void) +JS_SetThreadStackLimit(JSContext *cx, jsuword limitAddr); + +/************************************************************************/ + +/* + * Classes, objects, and properties. + */ + +/* For detailed comments on the function pointer types, see jspubtd.h. */ +struct JSClass { + const char *name; + uint32 flags; + + /* Mandatory non-null function pointer members. */ + JSPropertyOp addProperty; + JSPropertyOp delProperty; + JSPropertyOp getProperty; + JSPropertyOp setProperty; + JSEnumerateOp enumerate; + JSResolveOp resolve; + JSConvertOp convert; + JSFinalizeOp finalize; + + /* Optionally non-null members start here. */ + JSGetObjectOps getObjectOps; + JSCheckAccessOp checkAccess; + JSNative call; + JSNative construct; + JSXDRObjectOp xdrObject; + JSHasInstanceOp hasInstance; + JSMarkOp mark; + jsword spare; +}; + +#define JSCLASS_HAS_PRIVATE (1<<0) /* objects have private slot */ +#define JSCLASS_NEW_ENUMERATE (1<<1) /* has JSNewEnumerateOp hook */ +#define JSCLASS_NEW_RESOLVE (1<<2) /* has JSNewResolveOp hook */ +#define JSCLASS_PRIVATE_IS_NSISUPPORTS (1<<3) /* private is (nsISupports *) */ +#define JSCLASS_SHARE_ALL_PROPERTIES (1<<4) /* all properties are SHARED */ +#define JSCLASS_NEW_RESOLVE_GETS_START (1<<5) /* JSNewResolveOp gets starting + object in prototype chain + passed in via *objp in/out + parameter */ + +/* + * To reserve slots fetched and stored via JS_Get/SetReservedSlot, bitwise-or + * JSCLASS_HAS_RESERVED_SLOTS(n) into the initializer for JSClass.flags, where + * n is a constant in [1, 255]. Reserved slots are indexed from 0 to n-1. + */ +#define JSCLASS_RESERVED_SLOTS_SHIFT 8 /* room for 8 flags below */ +#define JSCLASS_RESERVED_SLOTS_WIDTH 8 /* and 16 above this field */ +#define JSCLASS_RESERVED_SLOTS_MASK JS_BITMASK(JSCLASS_RESERVED_SLOTS_WIDTH) +#define JSCLASS_HAS_RESERVED_SLOTS(n) (((n) & JSCLASS_RESERVED_SLOTS_MASK) \ + << JSCLASS_RESERVED_SLOTS_SHIFT) +#define JSCLASS_RESERVED_SLOTS(clasp) (((clasp)->flags \ + >> JSCLASS_RESERVED_SLOTS_SHIFT) \ + & JSCLASS_RESERVED_SLOTS_MASK) + +/* Initializer for unused members of statically initialized JSClass structs. */ +#define JSCLASS_NO_OPTIONAL_MEMBERS 0,0,0,0,0,0,0,0 + +/* For detailed comments on these function pointer types, see jspubtd.h. */ +struct JSObjectOps { + /* Mandatory non-null function pointer members. */ + JSNewObjectMapOp newObjectMap; + JSObjectMapOp destroyObjectMap; + JSLookupPropOp lookupProperty; + JSDefinePropOp defineProperty; + JSPropertyIdOp getProperty; + JSPropertyIdOp setProperty; + JSAttributesOp getAttributes; + JSAttributesOp setAttributes; + JSPropertyIdOp deleteProperty; + JSConvertOp defaultValue; + JSNewEnumerateOp enumerate; + JSCheckAccessIdOp checkAccess; + + /* Optionally non-null members start here. */ + JSObjectOp thisObject; + JSPropertyRefOp dropProperty; + JSNative call; + JSNative construct; + JSXDRObjectOp xdrObject; + JSHasInstanceOp hasInstance; + JSSetObjectSlotOp setProto; + JSSetObjectSlotOp setParent; + JSMarkOp mark; + JSFinalizeOp clear; + JSGetRequiredSlotOp getRequiredSlot; + JSSetRequiredSlotOp setRequiredSlot; +}; + +/* + * Classes that expose JSObjectOps via a non-null getObjectOps class hook may + * derive a property structure from this struct, return a pointer to it from + * lookupProperty and defineProperty, and use the pointer to avoid rehashing + * in getAttributes and setAttributes. + * + * The jsid type contains either an int jsval (see JSVAL_IS_INT above), or an + * internal pointer that is opaque to users of this API, but which users may + * convert from and to a jsval using JS_ValueToId and JS_IdToValue. + */ +struct JSProperty { + jsid id; +}; + +struct JSIdArray { + jsint length; + jsid vector[1]; /* actually, length jsid words */ +}; + +extern JS_PUBLIC_API(void) +JS_DestroyIdArray(JSContext *cx, JSIdArray *ida); + +extern JS_PUBLIC_API(JSBool) +JS_ValueToId(JSContext *cx, jsval v, jsid *idp); + +extern JS_PUBLIC_API(JSBool) +JS_IdToValue(JSContext *cx, jsid id, jsval *vp); + +#define JSRESOLVE_QUALIFIED 0x01 /* resolve a qualified property id */ +#define JSRESOLVE_ASSIGNING 0x02 /* resolve on the left of assignment */ + +extern JS_PUBLIC_API(JSBool) +JS_PropertyStub(JSContext *cx, JSObject *obj, jsval id, jsval *vp); + +extern JS_PUBLIC_API(JSBool) +JS_EnumerateStub(JSContext *cx, JSObject *obj); + +extern JS_PUBLIC_API(JSBool) +JS_ResolveStub(JSContext *cx, JSObject *obj, jsval id); + +extern JS_PUBLIC_API(JSBool) +JS_ConvertStub(JSContext *cx, JSObject *obj, JSType type, jsval *vp); + +extern JS_PUBLIC_API(void) +JS_FinalizeStub(JSContext *cx, JSObject *obj); + +struct JSConstDoubleSpec { + jsdouble dval; + const char *name; + uint8 flags; + uint8 spare[3]; +}; + +/* + * To define an array element rather than a named property member, cast the + * element's index to (const char *) and initialize name with it, and set the + * JSPROP_INDEX bit in flags. + */ +struct JSPropertySpec { + const char *name; + int8 tinyid; + uint8 flags; + JSPropertyOp getter; + JSPropertyOp setter; +}; + +struct JSFunctionSpec { + const char *name; + JSNative call; + uint8 nargs; + uint8 flags; + uint16 extra; /* number of arg slots for local GC roots */ +}; + +extern JS_PUBLIC_API(JSObject *) +JS_InitClass(JSContext *cx, JSObject *obj, JSObject *parent_proto, + JSClass *clasp, JSNative constructor, uintN nargs, + JSPropertySpec *ps, JSFunctionSpec *fs, + JSPropertySpec *static_ps, JSFunctionSpec *static_fs); + +#ifdef JS_THREADSAFE +extern JS_PUBLIC_API(JSClass *) +JS_GetClass(JSContext *cx, JSObject *obj); + +#define JS_GET_CLASS(cx,obj) JS_GetClass(cx, obj) +#else +extern JS_PUBLIC_API(JSClass *) +JS_GetClass(JSObject *obj); + +#define JS_GET_CLASS(cx,obj) JS_GetClass(obj) +#endif + +extern JS_PUBLIC_API(JSBool) +JS_InstanceOf(JSContext *cx, JSObject *obj, JSClass *clasp, jsval *argv); + +extern JS_PUBLIC_API(void *) +JS_GetPrivate(JSContext *cx, JSObject *obj); + +extern JS_PUBLIC_API(JSBool) +JS_SetPrivate(JSContext *cx, JSObject *obj, void *data); + +extern JS_PUBLIC_API(void *) +JS_GetInstancePrivate(JSContext *cx, JSObject *obj, JSClass *clasp, + jsval *argv); + +extern JS_PUBLIC_API(JSObject *) +JS_GetPrototype(JSContext *cx, JSObject *obj); + +extern JS_PUBLIC_API(JSBool) +JS_SetPrototype(JSContext *cx, JSObject *obj, JSObject *proto); + +extern JS_PUBLIC_API(JSObject *) +JS_GetParent(JSContext *cx, JSObject *obj); + +extern JS_PUBLIC_API(JSBool) +JS_SetParent(JSContext *cx, JSObject *obj, JSObject *parent); + +extern JS_PUBLIC_API(JSObject *) +JS_GetConstructor(JSContext *cx, JSObject *proto); + +/* + * Get a unique identifier for obj, good for the lifetime of obj (even if it + * is moved by a copying GC). Return false on failure (likely out of memory), + * and true with *idp containing the unique id on success. + */ +extern JS_PUBLIC_API(JSBool) +JS_GetObjectId(JSContext *cx, JSObject *obj, jsid *idp); + +extern JS_PUBLIC_API(JSObject *) +JS_NewObject(JSContext *cx, JSClass *clasp, JSObject *proto, JSObject *parent); + +extern JS_PUBLIC_API(JSBool) +JS_SealObject(JSContext *cx, JSObject *obj, JSBool deep); + +extern JS_PUBLIC_API(JSObject *) +JS_ConstructObject(JSContext *cx, JSClass *clasp, JSObject *proto, + JSObject *parent); + +extern JS_PUBLIC_API(JSObject *) +JS_ConstructObjectWithArguments(JSContext *cx, JSClass *clasp, JSObject *proto, + JSObject *parent, uintN argc, jsval *argv); + +extern JS_PUBLIC_API(JSObject *) +JS_DefineObject(JSContext *cx, JSObject *obj, const char *name, JSClass *clasp, + JSObject *proto, uintN attrs); + +extern JS_PUBLIC_API(JSBool) +JS_DefineConstDoubles(JSContext *cx, JSObject *obj, JSConstDoubleSpec *cds); + +extern JS_PUBLIC_API(JSBool) +JS_DefineProperties(JSContext *cx, JSObject *obj, JSPropertySpec *ps); + +extern JS_PUBLIC_API(JSBool) +JS_DefineProperty(JSContext *cx, JSObject *obj, const char *name, jsval value, + JSPropertyOp getter, JSPropertyOp setter, uintN attrs); + +/* + * Determine the attributes (JSPROP_* flags) of a property on a given object. + * + * If the object does not have a property by that name, *foundp will be + * JS_FALSE and the value of *attrsp is undefined. + */ +extern JS_PUBLIC_API(JSBool) +JS_GetPropertyAttributes(JSContext *cx, JSObject *obj, const char *name, + uintN *attrsp, JSBool *foundp); + +/* + * Set the attributes of a property on a given object. + * + * If the object does not have a property by that name, *foundp will be + * JS_FALSE and nothing will be altered. + */ +extern JS_PUBLIC_API(JSBool) +JS_SetPropertyAttributes(JSContext *cx, JSObject *obj, const char *name, + uintN attrs, JSBool *foundp); + +extern JS_PUBLIC_API(JSBool) +JS_DefinePropertyWithTinyId(JSContext *cx, JSObject *obj, const char *name, + int8 tinyid, jsval value, + JSPropertyOp getter, JSPropertyOp setter, + uintN attrs); + +extern JS_PUBLIC_API(JSBool) +JS_AliasProperty(JSContext *cx, JSObject *obj, const char *name, + const char *alias); + +extern JS_PUBLIC_API(JSBool) +JS_LookupProperty(JSContext *cx, JSObject *obj, const char *name, jsval *vp); + +extern JS_PUBLIC_API(JSBool) +JS_GetProperty(JSContext *cx, JSObject *obj, const char *name, jsval *vp); + +extern JS_PUBLIC_API(JSBool) +JS_SetProperty(JSContext *cx, JSObject *obj, const char *name, jsval *vp); + +extern JS_PUBLIC_API(JSBool) +JS_DeleteProperty(JSContext *cx, JSObject *obj, const char *name); + +extern JS_PUBLIC_API(JSBool) +JS_DeleteProperty2(JSContext *cx, JSObject *obj, const char *name, + jsval *rval); + +extern JS_PUBLIC_API(JSBool) +JS_DefineUCProperty(JSContext *cx, JSObject *obj, + const jschar *name, size_t namelen, jsval value, + JSPropertyOp getter, JSPropertyOp setter, + uintN attrs); + +/* + * Determine the attributes (JSPROP_* flags) of a property on a given object. + * + * If the object does not have a property by that name, *foundp will be + * JS_FALSE and the value of *attrsp is undefined. + */ +extern JS_PUBLIC_API(JSBool) +JS_GetUCPropertyAttributes(JSContext *cx, JSObject *obj, + const jschar *name, size_t namelen, + uintN *attrsp, JSBool *foundp); + +/* + * Set the attributes of a property on a given object. + * + * If the object does not have a property by that name, *foundp will be + * JS_FALSE and nothing will be altered. + */ +extern JS_PUBLIC_API(JSBool) +JS_SetUCPropertyAttributes(JSContext *cx, JSObject *obj, + const jschar *name, size_t namelen, + uintN attrs, JSBool *foundp); + + +extern JS_PUBLIC_API(JSBool) +JS_DefineUCPropertyWithTinyId(JSContext *cx, JSObject *obj, + const jschar *name, size_t namelen, + int8 tinyid, jsval value, + JSPropertyOp getter, JSPropertyOp setter, + uintN attrs); + +extern JS_PUBLIC_API(JSBool) +JS_LookupUCProperty(JSContext *cx, JSObject *obj, + const jschar *name, size_t namelen, + jsval *vp); + +extern JS_PUBLIC_API(JSBool) +JS_GetUCProperty(JSContext *cx, JSObject *obj, + const jschar *name, size_t namelen, + jsval *vp); + +extern JS_PUBLIC_API(JSBool) +JS_SetUCProperty(JSContext *cx, JSObject *obj, + const jschar *name, size_t namelen, + jsval *vp); + +extern JS_PUBLIC_API(JSBool) +JS_DeleteUCProperty2(JSContext *cx, JSObject *obj, + const jschar *name, size_t namelen, + jsval *rval); + +extern JS_PUBLIC_API(JSObject *) +JS_NewArrayObject(JSContext *cx, jsint length, jsval *vector); + +extern JS_PUBLIC_API(JSBool) +JS_IsArrayObject(JSContext *cx, JSObject *obj); + +extern JS_PUBLIC_API(JSBool) +JS_GetArrayLength(JSContext *cx, JSObject *obj, jsuint *lengthp); + +extern JS_PUBLIC_API(JSBool) +JS_SetArrayLength(JSContext *cx, JSObject *obj, jsuint length); + +extern JS_PUBLIC_API(JSBool) +JS_HasArrayLength(JSContext *cx, JSObject *obj, jsuint *lengthp); + +extern JS_PUBLIC_API(JSBool) +JS_DefineElement(JSContext *cx, JSObject *obj, jsint index, jsval value, + JSPropertyOp getter, JSPropertyOp setter, uintN attrs); + +extern JS_PUBLIC_API(JSBool) +JS_AliasElement(JSContext *cx, JSObject *obj, const char *name, jsint alias); + +extern JS_PUBLIC_API(JSBool) +JS_LookupElement(JSContext *cx, JSObject *obj, jsint index, jsval *vp); + +extern JS_PUBLIC_API(JSBool) +JS_GetElement(JSContext *cx, JSObject *obj, jsint index, jsval *vp); + +extern JS_PUBLIC_API(JSBool) +JS_SetElement(JSContext *cx, JSObject *obj, jsint index, jsval *vp); + +extern JS_PUBLIC_API(JSBool) +JS_DeleteElement(JSContext *cx, JSObject *obj, jsint index); + +extern JS_PUBLIC_API(JSBool) +JS_DeleteElement2(JSContext *cx, JSObject *obj, jsint index, jsval *rval); + +extern JS_PUBLIC_API(void) +JS_ClearScope(JSContext *cx, JSObject *obj); + +extern JS_PUBLIC_API(JSIdArray *) +JS_Enumerate(JSContext *cx, JSObject *obj); + +extern JS_PUBLIC_API(JSBool) +JS_CheckAccess(JSContext *cx, JSObject *obj, jsid id, JSAccessMode mode, + jsval *vp, uintN *attrsp); + +extern JS_PUBLIC_API(JSCheckAccessOp) +JS_SetCheckObjectAccessCallback(JSRuntime *rt, JSCheckAccessOp acb); + +extern JS_PUBLIC_API(JSBool) +JS_GetReservedSlot(JSContext *cx, JSObject *obj, uint32 index, jsval *vp); + +extern JS_PUBLIC_API(JSBool) +JS_SetReservedSlot(JSContext *cx, JSObject *obj, uint32 index, jsval v); + +/************************************************************************/ + +/* + * Security protocol. + */ +struct JSPrincipals { + char *codebase; + void * (* JS_DLL_CALLBACK getPrincipalArray)(JSContext *cx, JSPrincipals *); + JSBool (* JS_DLL_CALLBACK globalPrivilegesEnabled)(JSContext *cx, JSPrincipals *); + + /* Don't call "destroy"; use reference counting macros below. */ + jsrefcount refcount; + void (* JS_DLL_CALLBACK destroy)(JSContext *cx, struct JSPrincipals *); +}; + +#ifdef JS_THREADSAFE +#define JSPRINCIPALS_HOLD(cx, principals) JS_HoldPrincipals(cx,principals) +#define JSPRINCIPALS_DROP(cx, principals) JS_DropPrincipals(cx,principals) + +extern JS_PUBLIC_API(jsrefcount) +JS_HoldPrincipals(JSContext *cx, JSPrincipals *principals); + +extern JS_PUBLIC_API(jsrefcount) +JS_DropPrincipals(JSContext *cx, JSPrincipals *principals); + +#else +#define JSPRINCIPALS_HOLD(cx, principals) (++(principals)->refcount) +#define JSPRINCIPALS_DROP(cx, principals) \ + ((--(principals)->refcount == 0) \ + ? ((*(principals)->destroy)((cx), (principals)), 0) \ + : (principals)->refcount) +#endif + +extern JS_PUBLIC_API(JSPrincipalsTranscoder) +JS_SetPrincipalsTranscoder(JSRuntime *rt, JSPrincipalsTranscoder px); + +extern JS_PUBLIC_API(JSObjectPrincipalsFinder) +JS_SetObjectPrincipalsFinder(JSContext *cx, JSObjectPrincipalsFinder fop); + +/************************************************************************/ + +/* + * Functions and scripts. + */ +extern JS_PUBLIC_API(JSFunction *) +JS_NewFunction(JSContext *cx, JSNative call, uintN nargs, uintN flags, + JSObject *parent, const char *name); + +extern JS_PUBLIC_API(JSObject *) +JS_GetFunctionObject(JSFunction *fun); + +/* + * Deprecated, useful only for diagnostics. Use JS_GetFunctionId instead for + * anonymous vs. "anonymous" disambiguation and Unicode fidelity. + */ +extern JS_PUBLIC_API(const char *) +JS_GetFunctionName(JSFunction *fun); + +/* + * Return the function's identifier as a JSString, or null if fun is unnamed. + * The returned string lives as long as fun, so you don't need to root a saved + * reference to it if fun is well-connected or rooted, and provided you bound + * the use of the saved reference by fun's lifetime. + * + * Prefer JS_GetFunctionId over JS_GetFunctionName because it returns null for + * truly anonymous functions, and because it doesn't chop to ISO-Latin-1 chars + * from UTF-16-ish jschars. + */ +extern JS_PUBLIC_API(JSString *) +JS_GetFunctionId(JSFunction *fun); + +/* + * Return JSFUN_* flags for fun. + */ +extern JS_PUBLIC_API(uintN) +JS_GetFunctionFlags(JSFunction *fun); + +/* + * Infallible predicate to test whether obj is a function object (faster than + * comparing obj's class name to "Function", but equivalent unless someone has + * overwritten the "Function" identifier with a different constructor and then + * created instances using that constructor that might be passed in as obj). + */ +extern JS_PUBLIC_API(JSBool) +JS_ObjectIsFunction(JSContext *cx, JSObject *obj); + +extern JS_PUBLIC_API(JSBool) +JS_DefineFunctions(JSContext *cx, JSObject *obj, JSFunctionSpec *fs); + +extern JS_PUBLIC_API(JSFunction *) +JS_DefineFunction(JSContext *cx, JSObject *obj, const char *name, JSNative call, + uintN nargs, uintN attrs); + +extern JS_PUBLIC_API(JSObject *) +JS_CloneFunctionObject(JSContext *cx, JSObject *funobj, JSObject *parent); + +/* + * Given a buffer, return JS_FALSE if the buffer might become a valid + * javascript statement with the addition of more lines. Otherwise return + * JS_TRUE. The intent is to support interactive compilation - accumulate + * lines in a buffer until JS_BufferIsCompilableUnit is true, then pass it to + * the compiler. + */ +extern JS_PUBLIC_API(JSBool) +JS_BufferIsCompilableUnit(JSContext *cx, JSObject *obj, + const char *bytes, size_t length); + +/* + * The JSScript objects returned by the following functions refer to string and + * other kinds of literals, including doubles and RegExp objects. These + * literals are vulnerable to garbage collection; to root script objects and + * prevent literals from being collected, create a rootable object using + * JS_NewScriptObject, and root the resulting object using JS_Add[Named]Root. + */ +extern JS_PUBLIC_API(JSScript *) +JS_CompileScript(JSContext *cx, JSObject *obj, + const char *bytes, size_t length, + const char *filename, uintN lineno); + +extern JS_PUBLIC_API(JSScript *) +JS_CompileScriptForPrincipals(JSContext *cx, JSObject *obj, + JSPrincipals *principals, + const char *bytes, size_t length, + const char *filename, uintN lineno); + +extern JS_PUBLIC_API(JSScript *) +JS_CompileUCScript(JSContext *cx, JSObject *obj, + const jschar *chars, size_t length, + const char *filename, uintN lineno); + +extern JS_PUBLIC_API(JSScript *) +JS_CompileUCScriptForPrincipals(JSContext *cx, JSObject *obj, + JSPrincipals *principals, + const jschar *chars, size_t length, + const char *filename, uintN lineno); + +extern JS_PUBLIC_API(JSScript *) +JS_CompileFile(JSContext *cx, JSObject *obj, const char *filename); + +extern JS_PUBLIC_API(JSScript *) +JS_CompileFileHandle(JSContext *cx, JSObject *obj, const char *filename, + FILE *fh); + +extern JS_PUBLIC_API(JSScript *) +JS_CompileFileHandleForPrincipals(JSContext *cx, JSObject *obj, + const char *filename, FILE *fh, + JSPrincipals *principals); + +/* + * NB: you must use JS_NewScriptObject and root a pointer to its return value + * in order to keep a JSScript and its atoms safe from garbage collection after + * creating the script via JS_Compile* and before a JS_ExecuteScript* call. + * E.g., and without error checks: + * + * JSScript *script = JS_CompileFile(cx, global, filename); + * JSObject *scrobj = JS_NewScriptObject(cx, script); + * JS_AddNamedRoot(cx, &scrobj, "scrobj"); + * do { + * jsval result; + * JS_ExecuteScript(cx, global, script, &result); + * JS_GC(); + * } while (!JSVAL_IS_BOOLEAN(result) || JSVAL_TO_BOOLEAN(result)); + * JS_RemoveRoot(cx, &scrobj); + */ +extern JS_PUBLIC_API(JSObject *) +JS_NewScriptObject(JSContext *cx, JSScript *script); + +/* + * Infallible getter for a script's object. If JS_NewScriptObject has not been + * called on script yet, the return value will be null. + */ +extern JS_PUBLIC_API(JSObject *) +JS_GetScriptObject(JSScript *script); + +extern JS_PUBLIC_API(void) +JS_DestroyScript(JSContext *cx, JSScript *script); + +extern JS_PUBLIC_API(JSFunction *) +JS_CompileFunction(JSContext *cx, JSObject *obj, const char *name, + uintN nargs, const char **argnames, + const char *bytes, size_t length, + const char *filename, uintN lineno); + +extern JS_PUBLIC_API(JSFunction *) +JS_CompileFunctionForPrincipals(JSContext *cx, JSObject *obj, + JSPrincipals *principals, const char *name, + uintN nargs, const char **argnames, + const char *bytes, size_t length, + const char *filename, uintN lineno); + +extern JS_PUBLIC_API(JSFunction *) +JS_CompileUCFunction(JSContext *cx, JSObject *obj, const char *name, + uintN nargs, const char **argnames, + const jschar *chars, size_t length, + const char *filename, uintN lineno); + +extern JS_PUBLIC_API(JSFunction *) +JS_CompileUCFunctionForPrincipals(JSContext *cx, JSObject *obj, + JSPrincipals *principals, const char *name, + uintN nargs, const char **argnames, + const jschar *chars, size_t length, + const char *filename, uintN lineno); + +extern JS_PUBLIC_API(JSString *) +JS_DecompileScript(JSContext *cx, JSScript *script, const char *name, + uintN indent); + +/* + * API extension: OR this into indent to avoid pretty-printing the decompiled + * source resulting from JS_DecompileFunction{,Body}. + */ +#define JS_DONT_PRETTY_PRINT ((uintN)0x8000) + +extern JS_PUBLIC_API(JSString *) +JS_DecompileFunction(JSContext *cx, JSFunction *fun, uintN indent); + +extern JS_PUBLIC_API(JSString *) +JS_DecompileFunctionBody(JSContext *cx, JSFunction *fun, uintN indent); + +/* + * NB: JS_ExecuteScript, JS_ExecuteScriptPart, and the JS_Evaluate*Script* + * quadruplets all use the obj parameter as the initial scope chain header, + * the 'this' keyword value, and the variables object (ECMA parlance for where + * 'var' and 'function' bind names) of the execution context for script. + * + * Using obj as the variables object is problematic if obj's parent (which is + * the scope chain link; see JS_SetParent and JS_NewObject) is not null: in + * this case, variables created by 'var x = 0', e.g., go in obj, but variables + * created by assignment to an unbound id, 'x = 0', go in the last object on + * the scope chain linked by parent. + * + * ECMA calls that last scoping object the "global object", but note that many + * embeddings have several such objects. ECMA requires that "global code" be + * executed with the variables object equal to this global object. But these + * JS API entry points provide freedom to execute code against a "sub-global", + * i.e., a parented or scoped object, in which case the variables object will + * differ from the last object on the scope chain, resulting in confusing and + * non-ECMA explicit vs. implicit variable creation. + * + * Caveat embedders: unless you already depend on this buggy variables object + * binding behavior, you should call JS_SetOptions(cx, JSOPTION_VAROBJFIX) or + * JS_SetOptions(cx, JS_GetOptions(cx) | JSOPTION_VAROBJFIX) -- the latter if + * someone may have set other options on cx already -- for each context in the + * application, if you pass parented objects as the obj parameter, or may ever + * pass such objects in the future. + * + * Why a runtime option? The alternative is to add six or so new API entry + * points with signatures matching the following six, and that doesn't seem + * worth the code bloat cost. Such new entry points would probably have less + * obvious names, too, so would not tend to be used. The JS_SetOption call, + * OTOH, can be more easily hacked into existing code that does not depend on + * the bug; such code can continue to use the familiar JS_EvaluateScript, + * etc., entry points. + */ +extern JS_PUBLIC_API(JSBool) +JS_ExecuteScript(JSContext *cx, JSObject *obj, JSScript *script, jsval *rval); + +/* + * Execute either the function-defining prolog of a script, or the script's + * main body, but not both. + */ +typedef enum JSExecPart { JSEXEC_PROLOG, JSEXEC_MAIN } JSExecPart; + +extern JS_PUBLIC_API(JSBool) +JS_ExecuteScriptPart(JSContext *cx, JSObject *obj, JSScript *script, + JSExecPart part, jsval *rval); + +extern JS_PUBLIC_API(JSBool) +JS_EvaluateScript(JSContext *cx, JSObject *obj, + const char *bytes, uintN length, + const char *filename, uintN lineno, + jsval *rval); + +extern JS_PUBLIC_API(JSBool) +JS_EvaluateScriptForPrincipals(JSContext *cx, JSObject *obj, + JSPrincipals *principals, + const char *bytes, uintN length, + const char *filename, uintN lineno, + jsval *rval); + +extern JS_PUBLIC_API(JSBool) +JS_EvaluateUCScript(JSContext *cx, JSObject *obj, + const jschar *chars, uintN length, + const char *filename, uintN lineno, + jsval *rval); + +extern JS_PUBLIC_API(JSBool) +JS_EvaluateUCScriptForPrincipals(JSContext *cx, JSObject *obj, + JSPrincipals *principals, + const jschar *chars, uintN length, + const char *filename, uintN lineno, + jsval *rval); + +extern JS_PUBLIC_API(JSBool) +JS_CallFunction(JSContext *cx, JSObject *obj, JSFunction *fun, uintN argc, + jsval *argv, jsval *rval); + +extern JS_PUBLIC_API(JSBool) +JS_CallFunctionName(JSContext *cx, JSObject *obj, const char *name, uintN argc, + jsval *argv, jsval *rval); + +extern JS_PUBLIC_API(JSBool) +JS_CallFunctionValue(JSContext *cx, JSObject *obj, jsval fval, uintN argc, + jsval *argv, jsval *rval); + +extern JS_PUBLIC_API(JSBranchCallback) +JS_SetBranchCallback(JSContext *cx, JSBranchCallback cb); + +extern JS_PUBLIC_API(JSBool) +JS_IsRunning(JSContext *cx); + +extern JS_PUBLIC_API(JSBool) +JS_IsConstructing(JSContext *cx); + +/* + * Returns true if a script is executing and its current bytecode is a set + * (assignment) operation, even if there are native (no script) stack frames + * between the script and the caller to JS_IsAssigning. + */ +extern JS_FRIEND_API(JSBool) +JS_IsAssigning(JSContext *cx); + +/* + * Set the second return value, which should be a string or int jsval that + * identifies a property in the returned object, to form an ECMA reference + * type value (obj, id). Only native methods can return reference types, + * and if the returned value is used on the left-hand side of an assignment + * op, the identified property will be set. If the return value is in an + * r-value, the interpreter just gets obj[id]'s value. + */ +extern JS_PUBLIC_API(void) +JS_SetCallReturnValue2(JSContext *cx, jsval v); + +/************************************************************************/ + +/* + * Strings. + * + * NB: JS_NewString takes ownership of bytes on success, avoiding a copy; but + * on error (signified by null return), it leaves bytes owned by the caller. + * So the caller must free bytes in the error case, if it has no use for them. + * In contrast, all the JS_New*StringCopy* functions do not take ownership of + * the character memory passed to them -- they copy it. + */ +extern JS_PUBLIC_API(JSString *) +JS_NewString(JSContext *cx, char *bytes, size_t length); + +extern JS_PUBLIC_API(JSString *) +JS_NewStringCopyN(JSContext *cx, const char *s, size_t n); + +extern JS_PUBLIC_API(JSString *) +JS_NewStringCopyZ(JSContext *cx, const char *s); + +extern JS_PUBLIC_API(JSString *) +JS_InternString(JSContext *cx, const char *s); + +extern JS_PUBLIC_API(JSString *) +JS_NewUCString(JSContext *cx, jschar *chars, size_t length); + +extern JS_PUBLIC_API(JSString *) +JS_NewUCStringCopyN(JSContext *cx, const jschar *s, size_t n); + +extern JS_PUBLIC_API(JSString *) +JS_NewUCStringCopyZ(JSContext *cx, const jschar *s); + +extern JS_PUBLIC_API(JSString *) +JS_InternUCStringN(JSContext *cx, const jschar *s, size_t length); + +extern JS_PUBLIC_API(JSString *) +JS_InternUCString(JSContext *cx, const jschar *s); + +extern JS_PUBLIC_API(char *) +JS_GetStringBytes(JSString *str); + +extern JS_PUBLIC_API(jschar *) +JS_GetStringChars(JSString *str); + +extern JS_PUBLIC_API(size_t) +JS_GetStringLength(JSString *str); + +extern JS_PUBLIC_API(intN) +JS_CompareStrings(JSString *str1, JSString *str2); + +/* + * Mutable string support. A string's characters are never mutable in this JS + * implementation, but a growable string has a buffer that can be reallocated, + * and a dependent string is a substring of another (growable, dependent, or + * immutable) string. The direct data members of the (opaque to API clients) + * JSString struct may be changed in a single-threaded way for growable and + * dependent strings. + * + * Therefore mutable strings cannot be used by more than one thread at a time. + * You may call JS_MakeStringImmutable to convert the string from a mutable + * (growable or dependent) string to an immutable (and therefore thread-safe) + * string. The engine takes care of converting growable and dependent strings + * to immutable for you if you store strings in multi-threaded objects using + * JS_SetProperty or kindred API entry points. + * + * If you store a JSString pointer in a native data structure that is (safely) + * accessible to multiple threads, you must call JS_MakeStringImmutable before + * retiring the store. + */ +extern JS_PUBLIC_API(JSString *) +JS_NewGrowableString(JSContext *cx, jschar *chars, size_t length); + +/* + * Create a dependent string, i.e., a string that owns no character storage, + * but that refers to a slice of another string's chars. Dependent strings + * are mutable by definition, so the thread safety comments above apply. + */ +extern JS_PUBLIC_API(JSString *) +JS_NewDependentString(JSContext *cx, JSString *str, size_t start, + size_t length); + +/* + * Concatenate two strings, resulting in a new growable string. If you create + * the left string and pass it to JS_ConcatStrings on a single thread, try to + * use JS_NewGrowableString to create the left string -- doing so helps Concat + * avoid allocating a new buffer for the result and copying left's chars into + * the new buffer. See above for thread safety comments. + */ +extern JS_PUBLIC_API(JSString *) +JS_ConcatStrings(JSContext *cx, JSString *left, JSString *right); + +/* + * Convert a dependent string into an independent one. This function does not + * change the string's mutability, so the thread safety comments above apply. + */ +extern JS_PUBLIC_API(const jschar *) +JS_UndependString(JSContext *cx, JSString *str); + +/* + * Convert a mutable string (either growable or dependent) into an immutable, + * thread-safe one. + */ +extern JS_PUBLIC_API(JSBool) +JS_MakeStringImmutable(JSContext *cx, JSString *str); + +/************************************************************************/ + +/* + * Locale specific string conversion callback. + */ +struct JSLocaleCallbacks { + JSLocaleToUpperCase localeToUpperCase; + JSLocaleToLowerCase localeToLowerCase; + JSLocaleCompare localeCompare; +}; + +/* + * Establish locale callbacks. The pointer must persist as long as the + * JSContext. Passing NULL restores the default behaviour. + */ +extern JS_PUBLIC_API(void) +JS_SetLocaleCallbacks(JSContext *cx, JSLocaleCallbacks *callbacks); + +/* + * Return the address of the current locale callbacks struct, which may + * be NULL. + */ +extern JS_PUBLIC_API(JSLocaleCallbacks *) +JS_GetLocaleCallbacks(JSContext *cx); + +/************************************************************************/ + +/* + * Error reporting. + */ + +/* + * Report an exception represented by the sprintf-like conversion of format + * and its arguments. This exception message string is passed to a pre-set + * JSErrorReporter function (set by JS_SetErrorReporter; see jspubtd.h for + * the JSErrorReporter typedef). + */ +extern JS_PUBLIC_API(void) +JS_ReportError(JSContext *cx, const char *format, ...); + +/* + * Use an errorNumber to retrieve the format string, args are char * + */ +extern JS_PUBLIC_API(void) +JS_ReportErrorNumber(JSContext *cx, JSErrorCallback errorCallback, + void *userRef, const uintN errorNumber, ...); + +/* + * Use an errorNumber to retrieve the format string, args are jschar * + */ +extern JS_PUBLIC_API(void) +JS_ReportErrorNumberUC(JSContext *cx, JSErrorCallback errorCallback, + void *userRef, const uintN errorNumber, ...); + +/* + * As above, but report a warning instead (JSREPORT_IS_WARNING(report.flags)). + * Return true if there was no error trying to issue the warning, and if the + * warning was not converted into an error due to the JSOPTION_WERROR option + * being set, false otherwise. + */ +extern JS_PUBLIC_API(JSBool) +JS_ReportWarning(JSContext *cx, const char *format, ...); + +extern JS_PUBLIC_API(JSBool) +JS_ReportErrorFlagsAndNumber(JSContext *cx, uintN flags, + JSErrorCallback errorCallback, void *userRef, + const uintN errorNumber, ...); + +extern JS_PUBLIC_API(JSBool) +JS_ReportErrorFlagsAndNumberUC(JSContext *cx, uintN flags, + JSErrorCallback errorCallback, void *userRef, + const uintN errorNumber, ...); + +/* + * Complain when out of memory. + */ +extern JS_PUBLIC_API(void) +JS_ReportOutOfMemory(JSContext *cx); + +struct JSErrorReport { + const char *filename; /* source file name, URL, etc., or null */ + uintN lineno; /* source line number */ + const char *linebuf; /* offending source line without final \n */ + const char *tokenptr; /* pointer to error token in linebuf */ + const jschar *uclinebuf; /* unicode (original) line buffer */ + const jschar *uctokenptr; /* unicode (original) token pointer */ + uintN flags; /* error/warning, etc. */ + uintN errorNumber; /* the error number, e.g. see js.msg */ + const jschar *ucmessage; /* the (default) error message */ + const jschar **messageArgs; /* arguments for the error message */ +}; + +/* + * JSErrorReport flag values. These may be freely composed. + */ +#define JSREPORT_ERROR 0x0 /* pseudo-flag for default case */ +#define JSREPORT_WARNING 0x1 /* reported via JS_ReportWarning */ +#define JSREPORT_EXCEPTION 0x2 /* exception was thrown */ +#define JSREPORT_STRICT 0x4 /* error or warning due to strict option */ + +/* + * If JSREPORT_EXCEPTION is set, then a JavaScript-catchable exception + * has been thrown for this runtime error, and the host should ignore it. + * Exception-aware hosts should also check for JS_IsExceptionPending if + * JS_ExecuteScript returns failure, and signal or propagate the exception, as + * appropriate. + */ +#define JSREPORT_IS_WARNING(flags) (((flags) & JSREPORT_WARNING) != 0) +#define JSREPORT_IS_EXCEPTION(flags) (((flags) & JSREPORT_EXCEPTION) != 0) +#define JSREPORT_IS_STRICT(flags) (((flags) & JSREPORT_STRICT) != 0) + +extern JS_PUBLIC_API(JSErrorReporter) +JS_SetErrorReporter(JSContext *cx, JSErrorReporter er); + +/************************************************************************/ + +/* + * Regular Expressions. + */ +#define JSREG_FOLD 0x01 /* fold uppercase to lowercase */ +#define JSREG_GLOB 0x02 /* global exec, creates array of matches */ +#define JSREG_MULTILINE 0x04 /* treat ^ and $ as begin and end of line */ + +extern JS_PUBLIC_API(JSObject *) +JS_NewRegExpObject(JSContext *cx, char *bytes, size_t length, uintN flags); + +extern JS_PUBLIC_API(JSObject *) +JS_NewUCRegExpObject(JSContext *cx, jschar *chars, size_t length, uintN flags); + +extern JS_PUBLIC_API(void) +JS_SetRegExpInput(JSContext *cx, JSString *input, JSBool multiline); + +extern JS_PUBLIC_API(void) +JS_ClearRegExpStatics(JSContext *cx); + +extern JS_PUBLIC_API(void) +JS_ClearRegExpRoots(JSContext *cx); + +/* TODO: compile, exec, get/set other statics... */ + +/************************************************************************/ + +extern JS_PUBLIC_API(JSBool) +JS_IsExceptionPending(JSContext *cx); + +extern JS_PUBLIC_API(JSBool) +JS_GetPendingException(JSContext *cx, jsval *vp); + +extern JS_PUBLIC_API(void) +JS_SetPendingException(JSContext *cx, jsval v); + +extern JS_PUBLIC_API(void) +JS_ClearPendingException(JSContext *cx); + +/* + * Save the current exception state. This takes a snapshot of cx's current + * exception state without making any change to that state. + * + * The returned state pointer MUST be passed later to JS_RestoreExceptionState + * (to restore that saved state, overriding any more recent state) or else to + * JS_DropExceptionState (to free the state struct in case it is not correct + * or desirable to restore it). Both Restore and Drop free the state struct, + * so callers must stop using the pointer returned from Save after calling the + * Release or Drop API. + */ +extern JS_PUBLIC_API(JSExceptionState *) +JS_SaveExceptionState(JSContext *cx); + +extern JS_PUBLIC_API(void) +JS_RestoreExceptionState(JSContext *cx, JSExceptionState *state); + +extern JS_PUBLIC_API(void) +JS_DropExceptionState(JSContext *cx, JSExceptionState *state); + +/* + * If the given value is an exception object that originated from an error, + * the exception will contain an error report struct, and this API will return + * the address of that struct. Otherwise, it returns NULL. The lifetime of + * the error report struct that might be returned is the same as the lifetime + * of the exception object. + */ +extern JS_PUBLIC_API(JSErrorReport *) +JS_ErrorFromException(JSContext *cx, jsval v); + +#ifdef JS_THREADSAFE + +/* + * Associate the current thread with the given context. This is done + * implicitly by JS_NewContext. + * + * Returns the old thread id for this context, which should be treated as + * an opaque value. This value is provided for comparison to 0, which + * indicates that ClearContextThread has been called on this context + * since the last SetContextThread, or non-0, which indicates the opposite. + */ +extern JS_PUBLIC_API(jsword) +JS_GetContextThread(JSContext *cx); + +extern JS_PUBLIC_API(jsword) +JS_SetContextThread(JSContext *cx); + +extern JS_PUBLIC_API(intN) +JS_ClearContextThread(JSContext *cx); + +#endif /* JS_THREADSAFE */ + +/************************************************************************/ + +JS_END_EXTERN_C + +#endif /* jsapi_h___ */ diff --git a/src/dom/js/jsarena.c b/src/dom/js/jsarena.c new file mode 100644 index 000000000..4cc2e7c53 --- /dev/null +++ b/src/dom/js/jsarena.c @@ -0,0 +1,565 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * Lifetime-based fast allocation, inspired by much prior art, including + * "Fast Allocation and Deallocation of Memory Based on Object Lifetimes" + * David R. Hanson, Software -- Practice and Experience, Vol. 20(1). + */ +#include "jsstddef.h" +#include +#include +#include "jstypes.h" +#include "jsbit.h" +#include "jsarena.h" /* Added by JSIFY */ +#include "jsutil.h" /* Added by JSIFY */ +#include "jslock.h" + +static JSArena *arena_freelist; + +#ifdef JS_THREADSAFE +static JSLock *arena_freelist_lock; +#endif + +#ifdef JS_ARENAMETER +static JSArenaStats *arena_stats_list; + +#define COUNT(pool,what) (pool)->stats.what++ +#else +#define COUNT(pool,what) /* nothing */ +#endif + +#define JS_ARENA_DEFAULT_ALIGN sizeof(double) + +JS_PUBLIC_API(void) +JS_InitArenaPool(JSArenaPool *pool, const char *name, size_t size, size_t align) +{ +#ifdef JS_THREADSAFE + /* Must come through here once in primordial thread to init safely! */ + if (!arena_freelist_lock) { + arena_freelist_lock = JS_NEW_LOCK(); + JS_ASSERT(arena_freelist_lock); + } +#endif + if (align == 0) + align = JS_ARENA_DEFAULT_ALIGN; + pool->mask = JS_BITMASK(JS_CeilingLog2(align)); + pool->first.next = NULL; + pool->first.base = pool->first.avail = pool->first.limit = + JS_ARENA_ALIGN(pool, &pool->first + 1); + pool->current = &pool->first; + pool->arenasize = size; +#ifdef JS_ARENAMETER + memset(&pool->stats, 0, sizeof pool->stats); + pool->stats.name = strdup(name); + pool->stats.next = arena_stats_list; + arena_stats_list = &pool->stats; +#endif +} + +/* + * An allocation that consumes more than pool->arenasize also has a header + * pointing back to its previous arena's next member. This header is not + * included in [a->base, a->limit), so its space can't be wrongly claimed. + * + * As the header is a pointer, it must be well-aligned. If pool->mask is + * greater than or equal to POINTER_MASK, the header just preceding a->base + * for an oversized arena a is well-aligned, because a->base is well-aligned. + * However, we may need to add more space to pad the JSArena ** back-pointer + * so that it lies just behind a->base, because a might not be aligned such + * that (jsuword)(a + 1) is on a pointer boundary. + * + * By how much must we pad? Let M be the alignment modulus for pool and P + * the modulus for a pointer. Given M >= P, the greatest distance between a + * pointer aligned on an M boundary and one aligned on a P boundary is M-P. + * If M and P are powers of two, then M-P = (pool->mask - POINTER_MASK). + * + * How much extra padding might spill over unused into the remainder of the + * allocation, in the worst case (where M > P)? + * + * If we add M-P to the nominal back-pointer address and then round down to + * align on a P boundary, we will use at most M-P bytes of padding, and at + * least P (M > P => M >= 2P; M == 2P gives the least padding, P). So if we + * use P bytes of padding, then we will overallocate a by P+M-1 bytes, as we + * also add M-1 to the estimated size in case malloc returns an odd pointer. + * a->limit must include this overestimation to satisfy a->avail in [a->base, + * a->limit]. + * + * Similarly, if pool->mask is less than POINTER_MASK, we must include enough + * space in the header size to align the back-pointer on a P boundary so that + * it can be found by subtracting P from a->base. This means a->base must be + * on a P boundary, even though subsequent allocations from a may be aligned + * on a lesser (M) boundary. Given powers of two M and P as above, the extra + * space needed when P > M is P-M or POINTER_MASK - pool->mask. + * + * The size of a header including padding is given by the HEADER_SIZE macro, + * below, for any pool (for any value of M). + * + * The mask to align a->base for any pool is (pool->mask | POINTER_MASK), or + * HEADER_BASE_MASK(pool). + * + * PTR_TO_HEADER computes the address of the back-pointer, given an oversized + * allocation at p. By definition, p must be a->base for the arena a that + * contains p. GET_HEADER and SET_HEADER operate on an oversized arena a, in + * the case of SET_HEADER with back-pointer ap. + */ +#define POINTER_MASK ((jsuword)(JS_ALIGN_OF_POINTER - 1)) +#define HEADER_SIZE(pool) (sizeof(JSArena **) \ + + (((pool)->mask < POINTER_MASK) \ + ? POINTER_MASK - (pool)->mask \ + : (pool)->mask - POINTER_MASK)) +#define HEADER_BASE_MASK(pool) ((pool)->mask | POINTER_MASK) +#define PTR_TO_HEADER(pool,p) (JS_ASSERT(((jsuword)(p) \ + & HEADER_BASE_MASK(pool)) \ + == 0), \ + (JSArena ***)(p) - 1) +#define GET_HEADER(pool,a) (*PTR_TO_HEADER(pool, (a)->base)) +#define SET_HEADER(pool,a,ap) (*PTR_TO_HEADER(pool, (a)->base) = (ap)) + +JS_PUBLIC_API(void *) +JS_ArenaAllocate(JSArenaPool *pool, size_t nb) +{ + JSArena **ap, **bp, *a, *b; + jsuword extra, hdrsz, gross, sz; + void *p; + + /* Search pool from current forward till we find or make enough space. */ + JS_ASSERT((nb & pool->mask) == 0); + for (a = pool->current; a->avail + nb > a->limit; pool->current = a) { + ap = &a->next; + if (!*ap) { + /* Not enough space in pool -- try to reclaim a free arena. */ + extra = (nb > pool->arenasize) ? HEADER_SIZE(pool) : 0; + hdrsz = sizeof *a + extra + pool->mask; + gross = hdrsz + JS_MAX(nb, pool->arenasize); + bp = &arena_freelist; + JS_ACQUIRE_LOCK(arena_freelist_lock); + while ((b = *bp) != NULL) { + /* + * Insist on exact arenasize match if nb is not greater than + * arenasize. Otherwise take any arena big enough, but not by + * more than gross + arenasize. + */ + sz = JS_UPTRDIFF(b->limit, b); + if (extra + ? sz >= gross && sz <= gross + pool->arenasize + : sz == gross) { + *bp = b->next; + JS_RELEASE_LOCK(arena_freelist_lock); + b->next = NULL; + COUNT(pool, nreclaims); + goto claim; + } + bp = &b->next; + } + + /* Nothing big enough on the freelist, so we must malloc. */ + JS_RELEASE_LOCK(arena_freelist_lock); + b = (JSArena *) malloc(gross); + if (!b) + return 0; + b->next = NULL; + b->limit = (jsuword)b + gross; + JS_COUNT_ARENA(pool,++); + COUNT(pool, nmallocs); + + claim: + /* If oversized, store ap in the header, just before a->base. */ + *ap = a = b; + JS_ASSERT(gross <= JS_UPTRDIFF(a->limit, a)); + if (extra) { + a->base = a->avail = + ((jsuword)a + hdrsz) & ~HEADER_BASE_MASK(pool); + SET_HEADER(pool, a, ap); + } else { + a->base = a->avail = JS_ARENA_ALIGN(pool, a + 1); + } + continue; + } + a = *ap; /* move to next arena */ + } + + p = (void *)a->avail; + a->avail += nb; + JS_ASSERT(a->base <= a->avail && a->avail <= a->limit); + return p; +} + +JS_PUBLIC_API(void *) +JS_ArenaRealloc(JSArenaPool *pool, void *p, size_t size, size_t incr) +{ + JSArena **ap, *a, *b; + jsuword boff, aoff, extra, hdrsz, gross; + + /* + * Use the oversized-single-allocation header to avoid searching for ap. + * See JS_ArenaAllocate, the SET_HEADER call. + */ + if (size > pool->arenasize) { + ap = *PTR_TO_HEADER(pool, p); + a = *ap; + } else { + ap = &pool->first.next; + while ((a = *ap) != pool->current) + ap = &a->next; + } + + JS_ASSERT(a->base == (jsuword)p); + boff = JS_UPTRDIFF(a->base, a); + aoff = size + incr; + JS_ASSERT(aoff > pool->arenasize); + extra = HEADER_SIZE(pool); /* oversized header holds ap */ + hdrsz = sizeof *a + extra + pool->mask; /* header and alignment slop */ + gross = hdrsz + aoff; + a = (JSArena *) realloc(a, gross); + if (!a) + return NULL; +#ifdef JS_ARENAMETER + pool->stats.nreallocs++; +#endif + + if (a != *ap) { + /* Oops, realloc moved the allocation: update other pointers to a. */ + if (pool->current == *ap) + pool->current = a; + b = a->next; + if (b && b->avail - b->base > pool->arenasize) { + JS_ASSERT(GET_HEADER(pool, b) == &(*ap)->next); + SET_HEADER(pool, b, &a->next); + } + + /* Now update *ap, the next link of the arena before a. */ + *ap = a; + } + + a->base = ((jsuword)a + hdrsz) & ~HEADER_BASE_MASK(pool); + a->limit = (jsuword)a + gross; + a->avail = JS_ARENA_ALIGN(pool, a->base + aoff); + JS_ASSERT(a->base <= a->avail && a->avail <= a->limit); + + /* Check whether realloc aligned differently, and copy if necessary. */ + if (boff != JS_UPTRDIFF(a->base, a)) + memmove((void *)a->base, (char *)a + boff, size); + + /* Store ap in the oversized-load arena header. */ + SET_HEADER(pool, a, ap); + return (void *)a->base; +} + +JS_PUBLIC_API(void *) +JS_ArenaGrow(JSArenaPool *pool, void *p, size_t size, size_t incr) +{ + void *newp; + + /* + * If p points to an oversized allocation, it owns an entire arena, so we + * can simply realloc the arena. + */ + if (size > pool->arenasize) + return JS_ArenaRealloc(pool, p, size, incr); + + JS_ARENA_ALLOCATE(newp, pool, size + incr); + if (newp) + memcpy(newp, p, size); + return newp; +} + +/* + * Free tail arenas linked after head, which may not be the true list head. + * Reset pool->current to point to head in case it pointed at a tail arena. + */ +static void +FreeArenaList(JSArenaPool *pool, JSArena *head, JSBool reallyFree) +{ + JSArena **ap, *a; + + ap = &head->next; + a = *ap; + if (!a) + return; + +#ifdef DEBUG + do { + JS_ASSERT(a->base <= a->avail && a->avail <= a->limit); + a->avail = a->base; + JS_CLEAR_UNUSED(a); + } while ((a = a->next) != NULL); + a = *ap; +#endif + + if (reallyFree) { + do { + *ap = a->next; + JS_CLEAR_ARENA(a); + JS_COUNT_ARENA(pool,--); + free(a); + } while ((a = *ap) != NULL); + } else { + /* Insert the whole arena chain at the front of the freelist. */ + do { + ap = &(*ap)->next; + } while (*ap); + JS_ACQUIRE_LOCK(arena_freelist_lock); + *ap = arena_freelist; + arena_freelist = a; + JS_RELEASE_LOCK(arena_freelist_lock); + head->next = NULL; + } + + pool->current = head; +} + +JS_PUBLIC_API(void) +JS_ArenaRelease(JSArenaPool *pool, char *mark) +{ + JSArena *a; + + for (a = &pool->first; a; a = a->next) { + JS_ASSERT(a->base <= a->avail && a->avail <= a->limit); + + if (JS_UPTRDIFF(mark, a->base) <= JS_UPTRDIFF(a->avail, a->base)) { + a->avail = JS_ARENA_ALIGN(pool, mark); + JS_ASSERT(a->avail <= a->limit); + FreeArenaList(pool, a, JS_TRUE); + return; + } + } +} + +JS_PUBLIC_API(void) +JS_ArenaFreeAllocation(JSArenaPool *pool, void *p, size_t size) +{ + JSArena **ap, *a, *b; + jsuword q; + + /* + * If the allocation is oversized, it consumes an entire arena, and it has + * a header just before the allocation pointing back to its predecessor's + * next member. Otherwise, we have to search pool for a. + */ + if (size > pool->arenasize) { + ap = *PTR_TO_HEADER(pool, p); + a = *ap; + } else { + q = (jsuword)p + size; + q = JS_ARENA_ALIGN(pool, q); + ap = &pool->first.next; + while ((a = *ap) != NULL) { + JS_ASSERT(a->base <= a->avail && a->avail <= a->limit); + + if (a->avail == q) { + /* + * If a is consumed by the allocation at p, we can free it to + * the malloc heap. + */ + if (a->base == (jsuword)p) + break; + + /* + * We can't free a, but we can "retract" its avail cursor -- + * whether there are others after it in pool. + */ + a->avail = (jsuword)p; + return; + } + ap = &a->next; + } + } + + /* + * At this point, a is doomed, so ensure that pool->current doesn't point + * at it. What's more, force future allocations to scavenge all arenas on + * pool, in case some have free space. + */ + if (pool->current == a) + pool->current = &pool->first; + + /* + * This is a non-LIFO deallocation, so take care to fix up a->next's back + * pointer in its header, if a->next is oversized. + */ + *ap = b = a->next; + if (b && b->avail - b->base > pool->arenasize) { + JS_ASSERT(GET_HEADER(pool, b) == &a->next); + SET_HEADER(pool, b, ap); + } + JS_CLEAR_ARENA(a); + JS_COUNT_ARENA(pool,--); + free(a); +} + +JS_PUBLIC_API(void) +JS_FreeArenaPool(JSArenaPool *pool) +{ + FreeArenaList(pool, &pool->first, JS_FALSE); + COUNT(pool, ndeallocs); +} + +JS_PUBLIC_API(void) +JS_FinishArenaPool(JSArenaPool *pool) +{ + FreeArenaList(pool, &pool->first, JS_TRUE); +#ifdef JS_ARENAMETER + { + JSArenaStats *stats, **statsp; + + if (pool->stats.name) + free(pool->stats.name); + for (statsp = &arena_stats_list; (stats = *statsp) != 0; + statsp = &stats->next) { + if (stats == &pool->stats) { + *statsp = stats->next; + return; + } + } + } +#endif +} + +JS_PUBLIC_API(void) +JS_ArenaFinish() +{ + JSArena *a, *next; + + JS_ACQUIRE_LOCK(arena_freelist_lock); + a = arena_freelist; + arena_freelist = NULL; + JS_RELEASE_LOCK(arena_freelist_lock); + for (; a; a = next) { + next = a->next; + free(a); + } +} + +JS_PUBLIC_API(void) +JS_ArenaShutDown(void) +{ +#ifdef JS_THREADSAFE + /* Must come through here once in the process's last thread! */ + if (arena_freelist_lock) { + JS_DESTROY_LOCK(arena_freelist_lock); + arena_freelist_lock = NULL; + } +#endif +} + +#ifdef JS_ARENAMETER +JS_PUBLIC_API(void) +JS_ArenaCountAllocation(JSArenaPool *pool, size_t nb) +{ + pool->stats.nallocs++; + pool->stats.nbytes += nb; + if (nb > pool->stats.maxalloc) + pool->stats.maxalloc = nb; + pool->stats.variance += nb * nb; +} + +JS_PUBLIC_API(void) +JS_ArenaCountInplaceGrowth(JSArenaPool *pool, size_t size, size_t incr) +{ + pool->stats.ninplace++; +} + +JS_PUBLIC_API(void) +JS_ArenaCountGrowth(JSArenaPool *pool, size_t size, size_t incr) +{ + pool->stats.ngrows++; + pool->stats.nbytes += incr; + pool->stats.variance -= size * size; + size += incr; + if (size > pool->stats.maxalloc) + pool->stats.maxalloc = size; + pool->stats.variance += size * size; +} + +JS_PUBLIC_API(void) +JS_ArenaCountRelease(JSArenaPool *pool, char *mark) +{ + pool->stats.nreleases++; +} + +JS_PUBLIC_API(void) +JS_ArenaCountRetract(JSArenaPool *pool, char *mark) +{ + pool->stats.nfastrels++; +} + +#include +#include + +JS_PUBLIC_API(void) +JS_DumpArenaStats(FILE *fp) +{ + JSArenaStats *stats; + uint32 nallocs, nbytes; + double mean, variance, sigma; + + for (stats = arena_stats_list; stats; stats = stats->next) { + nallocs = stats->nallocs; + if (nallocs != 0) { + nbytes = stats->nbytes; + mean = (double)nbytes / nallocs; + variance = stats->variance * nallocs - nbytes * nbytes; + if (variance < 0 || nallocs == 1) + variance = 0; + else + variance /= nallocs * (nallocs - 1); + sigma = sqrt(variance); + } else { + mean = variance = sigma = 0; + } + + fprintf(fp, "\n%s allocation statistics:\n", stats->name); + fprintf(fp, " number of arenas: %u\n", stats->narenas); + fprintf(fp, " number of allocations: %u\n", stats->nallocs); + fprintf(fp, " number of free arena reclaims: %u\n", stats->nreclaims); + fprintf(fp, " number of malloc calls: %u\n", stats->nmallocs); + fprintf(fp, " number of deallocations: %u\n", stats->ndeallocs); + fprintf(fp, " number of allocation growths: %u\n", stats->ngrows); + fprintf(fp, " number of in-place growths: %u\n", stats->ninplace); + fprintf(fp, " number of realloc'ing growths: %u\n", stats->nreallocs); + fprintf(fp, "number of released allocations: %u\n", stats->nreleases); + fprintf(fp, " number of fast releases: %u\n", stats->nfastrels); + fprintf(fp, " total bytes allocated: %u\n", stats->nbytes); + fprintf(fp, " mean allocation size: %g\n", mean); + fprintf(fp, " standard deviation: %g\n", sigma); + fprintf(fp, " maximum allocation size: %u\n", stats->maxalloc); + } +} +#endif /* JS_ARENAMETER */ diff --git a/src/dom/js/jsarena.h b/src/dom/js/jsarena.h new file mode 100644 index 000000000..e52398a1a --- /dev/null +++ b/src/dom/js/jsarena.h @@ -0,0 +1,302 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsarena_h___ +#define jsarena_h___ +/* + * Lifetime-based fast allocation, inspired by much prior art, including + * "Fast Allocation and Deallocation of Memory Based on Object Lifetimes" + * David R. Hanson, Software -- Practice and Experience, Vol. 20(1). + * + * Also supports LIFO allocation (JS_ARENA_MARK/JS_ARENA_RELEASE). + */ +#include +#include "jstypes.h" +#include "jscompat.h" + +JS_BEGIN_EXTERN_C + +typedef struct JSArena JSArena; +typedef struct JSArenaPool JSArenaPool; + +struct JSArena { + JSArena *next; /* next arena for this lifetime */ + jsuword base; /* aligned base address, follows this header */ + jsuword limit; /* one beyond last byte in arena */ + jsuword avail; /* points to next available byte */ +}; + +#ifdef JS_ARENAMETER +typedef struct JSArenaStats JSArenaStats; + +struct JSArenaStats { + JSArenaStats *next; /* next in arenaStats list */ + char *name; /* name for debugging */ + uint32 narenas; /* number of arenas in pool */ + uint32 nallocs; /* number of JS_ARENA_ALLOCATE() calls */ + uint32 nreclaims; /* number of reclaims from freeArenas */ + uint32 nmallocs; /* number of malloc() calls */ + uint32 ndeallocs; /* number of lifetime deallocations */ + uint32 ngrows; /* number of JS_ARENA_GROW() calls */ + uint32 ninplace; /* number of in-place growths */ + uint32 nreallocs; /* number of arena grow extending reallocs */ + uint32 nreleases; /* number of JS_ARENA_RELEASE() calls */ + uint32 nfastrels; /* number of "fast path" releases */ + size_t nbytes; /* total bytes allocated */ + size_t maxalloc; /* maximum allocation size in bytes */ + double variance; /* size variance accumulator */ +}; +#endif + +struct JSArenaPool { + JSArena first; /* first arena in pool list */ + JSArena *current; /* arena from which to allocate space */ + size_t arenasize; /* net exact size of a new arena */ + jsuword mask; /* alignment mask (power-of-2 - 1) */ +#ifdef JS_ARENAMETER + JSArenaStats stats; +#endif +}; + +/* + * If the including .c file uses only one power-of-2 alignment, it may define + * JS_ARENA_CONST_ALIGN_MASK to the alignment mask and save a few instructions + * per ALLOCATE and GROW. + */ +#ifdef JS_ARENA_CONST_ALIGN_MASK +#define JS_ARENA_ALIGN(pool, n) (((jsuword)(n) + JS_ARENA_CONST_ALIGN_MASK) \ + & ~(jsuword)JS_ARENA_CONST_ALIGN_MASK) + +#define JS_INIT_ARENA_POOL(pool, name, size) \ + JS_InitArenaPool(pool, name, size, JS_ARENA_CONST_ALIGN_MASK + 1) +#else +#define JS_ARENA_ALIGN(pool, n) (((jsuword)(n) + (pool)->mask) & ~(pool)->mask) +#endif + +#define JS_ARENA_ALLOCATE(p, pool, nb) \ + JS_ARENA_ALLOCATE_CAST(p, void *, pool, nb) + +#define JS_ARENA_ALLOCATE_TYPE(p, type, pool) \ + JS_ARENA_ALLOCATE_CAST(p, type *, pool, sizeof(type)) + +#define JS_ARENA_ALLOCATE_CAST(p, type, pool, nb) \ + JS_BEGIN_MACRO \ + JSArena *_a = (pool)->current; \ + size_t _nb = JS_ARENA_ALIGN(pool, nb); \ + jsuword _p = _a->avail; \ + jsuword _q = _p + _nb; \ + JS_ASSERT(_q >= _p); \ + if (_q > _a->limit) \ + _p = (jsuword)JS_ArenaAllocate(pool, _nb); \ + else \ + _a->avail = _q; \ + p = (type) _p; \ + JS_ArenaCountAllocation(pool, nb); \ + JS_END_MACRO + +#define JS_ARENA_GROW(p, pool, size, incr) \ + JS_ARENA_GROW_CAST(p, void *, pool, size, incr) + +#define JS_ARENA_GROW_CAST(p, type, pool, size, incr) \ + JS_BEGIN_MACRO \ + JSArena *_a = (pool)->current; \ + if (_a->avail == (jsuword)(p) + JS_ARENA_ALIGN(pool, size)) { \ + size_t _nb = (size) + (incr); \ + jsuword _q = (jsuword)(p) + JS_ARENA_ALIGN(pool, _nb); \ + if (_q <= _a->limit) { \ + _a->avail = _q; \ + JS_ArenaCountInplaceGrowth(pool, size, incr); \ + } else if ((jsuword)(p) == _a->base) { \ + p = (type) JS_ArenaRealloc(pool, p, size, incr); \ + } else { \ + p = (type) JS_ArenaGrow(pool, p, size, incr); \ + } \ + } else { \ + p = (type) JS_ArenaGrow(pool, p, size, incr); \ + } \ + JS_ArenaCountGrowth(pool, size, incr); \ + JS_END_MACRO + +#define JS_ARENA_MARK(pool) ((void *) (pool)->current->avail) +#define JS_UPTRDIFF(p,q) ((jsuword)(p) - (jsuword)(q)) + +#ifdef DEBUG +#define JS_FREE_PATTERN 0xDA +#define JS_CLEAR_UNUSED(a) (JS_ASSERT((a)->avail <= (a)->limit), \ + memset((void*)(a)->avail, JS_FREE_PATTERN, \ + (a)->limit - (a)->avail)) +#define JS_CLEAR_ARENA(a) memset((void*)(a), JS_FREE_PATTERN, \ + (a)->limit - (jsuword)(a)) +#else +#define JS_CLEAR_UNUSED(a) /* nothing */ +#define JS_CLEAR_ARENA(a) /* nothing */ +#endif + +#define JS_ARENA_RELEASE(pool, mark) \ + JS_BEGIN_MACRO \ + char *_m = (char *)(mark); \ + JSArena *_a = (pool)->current; \ + if (_a != &(pool)->first && \ + JS_UPTRDIFF(_m, _a->base) <= JS_UPTRDIFF(_a->avail, _a->base)) { \ + _a->avail = (jsuword)JS_ARENA_ALIGN(pool, _m); \ + JS_ASSERT(_a->avail <= _a->limit); \ + JS_CLEAR_UNUSED(_a); \ + JS_ArenaCountRetract(pool, _m); \ + } else { \ + JS_ArenaRelease(pool, _m); \ + } \ + JS_ArenaCountRelease(pool, _m); \ + JS_END_MACRO + +#ifdef JS_ARENAMETER +#define JS_COUNT_ARENA(pool,op) ((pool)->stats.narenas op) +#else +#define JS_COUNT_ARENA(pool,op) +#endif + +#define JS_ARENA_DESTROY(pool, a, pnext) \ + JS_BEGIN_MACRO \ + JS_COUNT_ARENA(pool,--); \ + if ((pool)->current == (a)) (pool)->current = &(pool)->first; \ + *(pnext) = (a)->next; \ + JS_CLEAR_ARENA(a); \ + free(a); \ + (a) = NULL; \ + JS_END_MACRO + +/* + * Initialize an arena pool with the given name for debugging and metering, + * with a minimum size per arena of size bytes. + */ +extern JS_PUBLIC_API(void) +JS_InitArenaPool(JSArenaPool *pool, const char *name, size_t size, + size_t align); + +/* + * Free the arenas in pool. The user may continue to allocate from pool + * after calling this function. There is no need to call JS_InitArenaPool() + * again unless JS_FinishArenaPool(pool) has been called. + */ +extern JS_PUBLIC_API(void) +JS_FreeArenaPool(JSArenaPool *pool); + +/* + * Free the arenas in pool and finish using it altogether. + */ +extern JS_PUBLIC_API(void) +JS_FinishArenaPool(JSArenaPool *pool); + +/* + * Finish using arenas, freeing all memory associated with them except for + * any locks needed for thread safety. + */ +extern JS_PUBLIC_API(void) +JS_ArenaFinish(void); + +/* + * Free any locks or other memory needed for thread safety, just before + * shutting down. At that point, we must be called by a single thread. + * + * After shutting down, the next thread to call JS_InitArenaPool must not + * race with any other thread. Once a pool has been initialized, threads + * may safely call jsarena.c functions on thread-local pools. The upshot + * is that pools are per-thread, but the underlying global freelist is + * thread-safe, provided that both the first pool initialization and the + * shut-down call are single-threaded. + */ +extern JS_PUBLIC_API(void) +JS_ArenaShutDown(void); + +/* + * Friend functions used by the JS_ARENA_*() macros. + */ +extern JS_PUBLIC_API(void *) +JS_ArenaAllocate(JSArenaPool *pool, size_t nb); + +extern JS_PUBLIC_API(void *) +JS_ArenaRealloc(JSArenaPool *pool, void *p, size_t size, size_t incr); + +extern JS_PUBLIC_API(void *) +JS_ArenaGrow(JSArenaPool *pool, void *p, size_t size, size_t incr); + +extern JS_PUBLIC_API(void) +JS_ArenaRelease(JSArenaPool *pool, char *mark); + +/* + * Function to be used directly when an allocation has likely grown to consume + * an entire JSArena, in which case the arena is returned to the malloc heap. + */ +extern JS_PUBLIC_API(void) +JS_ArenaFreeAllocation(JSArenaPool *pool, void *p, size_t size); + +#ifdef JS_ARENAMETER + +#include + +extern JS_PUBLIC_API(void) +JS_ArenaCountAllocation(JSArenaPool *pool, size_t nb); + +extern JS_PUBLIC_API(void) +JS_ArenaCountInplaceGrowth(JSArenaPool *pool, size_t size, size_t incr); + +extern JS_PUBLIC_API(void) +JS_ArenaCountGrowth(JSArenaPool *pool, size_t size, size_t incr); + +extern JS_PUBLIC_API(void) +JS_ArenaCountRelease(JSArenaPool *pool, char *mark); + +extern JS_PUBLIC_API(void) +JS_ArenaCountRetract(JSArenaPool *pool, char *mark); + +extern JS_PUBLIC_API(void) +JS_DumpArenaStats(FILE *fp); + +#else /* !JS_ARENAMETER */ + +#define JS_ArenaCountAllocation(ap, nb) /* nothing */ +#define JS_ArenaCountInplaceGrowth(ap, size, incr) /* nothing */ +#define JS_ArenaCountGrowth(ap, size, incr) /* nothing */ +#define JS_ArenaCountRelease(ap, mark) /* nothing */ +#define JS_ArenaCountRetract(ap, mark) /* nothing */ + +#endif /* !JS_ARENAMETER */ + +JS_END_EXTERN_C + +#endif /* jsarena_h___ */ diff --git a/src/dom/js/jsarray.c b/src/dom/js/jsarray.c new file mode 100644 index 000000000..250c67557 --- /dev/null +++ b/src/dom/js/jsarray.c @@ -0,0 +1,1429 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS array class. + */ +#include "jsstddef.h" +#include +#include +#include "jstypes.h" +#include "jsutil.h" /* Added by JSIFY */ +#include "jsapi.h" +#include "jsarray.h" +#include "jsatom.h" +#include "jscntxt.h" +#include "jsconfig.h" +#include "jsfun.h" +#include "jsgc.h" +#include "jsinterp.h" +#include "jslock.h" +#include "jsnum.h" +#include "jsobj.h" +#include "jsstr.h" + +/* 2^32 - 1 as a number and a string */ +#define MAXINDEX 4294967295u +#define MAXSTR "4294967295" + +/* + * Determine if the id represents an array index. + * + * An id is an array index according to ECMA by (15.4): + * + * "Array objects give special treatment to a certain class of property names. + * A property name P (in the form of a string value) is an array index if and + * only if ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal + * to 2^32-1." + * + * In our implementation, it would be sufficient to check for JSVAL_IS_INT(id) + * except that by using signed 32-bit integers we miss the top half of the + * valid range. This function checks the string representation itself; note + * that calling a standard conversion routine might allow strings such as + * "08" or "4.0" as array indices, which they are not. + */ +static JSBool +IdIsIndex(jsid id, jsuint *indexp) +{ + JSString *str; + jschar *cp; + + if (JSVAL_IS_INT(id)) { + jsint i; + i = JSVAL_TO_INT(id); + if (i < 0) + return JS_FALSE; + *indexp = (jsuint)i; + return JS_TRUE; + } + + /* It must be a string. */ + str = JSVAL_TO_STRING(id); + cp = JSSTRING_CHARS(str); + if (JS7_ISDEC(*cp) && JSSTRING_LENGTH(str) < sizeof(MAXSTR)) { + jsuint index = JS7_UNDEC(*cp++); + jsuint oldIndex = 0; + jsuint c = 0; + if (index != 0) { + while (JS7_ISDEC(*cp)) { + oldIndex = index; + c = JS7_UNDEC(*cp); + index = 10*index + c; + cp++; + } + } + /* Make sure all characters were consumed and that it couldn't + * have overflowed. + */ + if (*cp == 0 && + (oldIndex < (MAXINDEX / 10) || + (oldIndex == (MAXINDEX / 10) && c < (MAXINDEX % 10)))) + { + *indexp = index; + return JS_TRUE; + } + } + return JS_FALSE; +} + +static JSBool +ValueIsLength(JSContext *cx, jsval v, jsuint *lengthp) +{ + jsint i; + jsdouble d; + + if (JSVAL_IS_INT(v)) { + i = JSVAL_TO_INT(v); + if (i < 0) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_ARRAY_LENGTH); + return JS_FALSE; + } + *lengthp = (jsuint) i; + return JS_TRUE; + } + + if (!js_ValueToNumber(cx, v, &d)) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_ARRAY_LENGTH); + return JS_FALSE; + } + if (!js_DoubleToECMAUint32(cx, d, (uint32 *)lengthp)) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_ARRAY_LENGTH); + return JS_FALSE; + } + if (JSDOUBLE_IS_NaN(d) || d != *lengthp) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_ARRAY_LENGTH); + return JS_FALSE; + } + return JS_TRUE; +} + +JSBool +js_GetLengthProperty(JSContext *cx, JSObject *obj, jsuint *lengthp) +{ + jsid id; + jsint i; + jsval v; + + id = (jsid) cx->runtime->atomState.lengthAtom; + if (!OBJ_GET_PROPERTY(cx, obj, id, &v)) + return JS_FALSE; + + /* Short-circuit, because js_ValueToECMAUint32 fails when + * called during init time. + */ + if (JSVAL_IS_INT(v)) { + i = JSVAL_TO_INT(v); + /* jsuint cast does ToUint32. */ + *lengthp = (jsuint)i; + return JS_TRUE; + } + return js_ValueToECMAUint32(cx, v, (uint32 *)lengthp); +} + +static JSBool +IndexToValue(JSContext *cx, jsuint length, jsval *vp) +{ + if (length <= JSVAL_INT_MAX) { + *vp = INT_TO_JSVAL(length); + return JS_TRUE; + } + return js_NewDoubleValue(cx, (jsdouble)length, vp); +} + +static JSBool +IndexToId(JSContext *cx, jsuint length, jsid *idp) +{ + JSString *str; + JSAtom *atom; + + if (length <= JSVAL_INT_MAX) { + *idp = (jsid) INT_TO_JSVAL(length); + } else { + str = js_NumberToString(cx, (jsdouble)length); + if (!str) + return JS_FALSE; + atom = js_AtomizeString(cx, str, 0); + if (!atom) + return JS_FALSE; + *idp = (jsid)atom; + + } + return JS_TRUE; +} + +JSBool +js_SetLengthProperty(JSContext *cx, JSObject *obj, jsuint length) +{ + jsval v; + jsid id; + + if (!IndexToValue(cx, length, &v)) + return JS_FALSE; + id = (jsid) cx->runtime->atomState.lengthAtom; + return OBJ_SET_PROPERTY(cx, obj, id, &v); +} + +JSBool +js_HasLengthProperty(JSContext *cx, JSObject *obj, jsuint *lengthp) +{ + JSErrorReporter older; + jsid id; + JSBool ok; + jsval v; + + older = JS_SetErrorReporter(cx, NULL); + id = (jsid) cx->runtime->atomState.lengthAtom; + ok = OBJ_GET_PROPERTY(cx, obj, id, &v); + JS_SetErrorReporter(cx, older); + if (!ok) + return JS_FALSE; + return ValueIsLength(cx, v, lengthp); +} + +/* + * This get function is specific to Array.prototype.length and other array + * instance length properties. It calls back through the class get function + * in case some magic happens there (see call_getProperty in jsfun.c). + */ +static JSBool +array_length_getter(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + return OBJ_GET_CLASS(cx, obj)->getProperty(cx, obj, id, vp); +} + +static JSBool +array_length_setter(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + jsuint newlen, oldlen, slot; + jsid id2; + jsval junk; + + if (!ValueIsLength(cx, *vp, &newlen)) + return JS_FALSE; + if (!js_GetLengthProperty(cx, obj, &oldlen)) + return JS_FALSE; + slot = oldlen; + while (slot > newlen) { + --slot; + if (!IndexToId(cx, slot, &id2)) + return JS_FALSE; + if (!OBJ_DELETE_PROPERTY(cx, obj, id2, &junk)) + return JS_FALSE; + } + return IndexToValue(cx, newlen, vp); +} + +static JSBool +array_addProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + jsuint index, length; + + if (!(IdIsIndex(id, &index))) + return JS_TRUE; + if (!js_GetLengthProperty(cx, obj, &length)) + return JS_FALSE; + if (index >= length) { + length = index + 1; + return js_SetLengthProperty(cx, obj, length); + } + return JS_TRUE; +} + +static JSBool +array_convert(JSContext *cx, JSObject *obj, JSType type, jsval *vp) +{ + jsuint length; + + if (cx->version == JSVERSION_1_2) { + if (!js_GetLengthProperty(cx, obj, &length)) + return JS_FALSE; + switch (type) { + case JSTYPE_NUMBER: + return IndexToValue(cx, length, vp); + case JSTYPE_BOOLEAN: + *vp = BOOLEAN_TO_JSVAL(length > 0); + return JS_TRUE; + default: + return JS_TRUE; + } + } + return js_TryValueOf(cx, obj, type, vp); +} + +JSClass js_ArrayClass = { + "Array", + 0, + array_addProperty, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, array_convert, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS +}; + +static JSBool +array_join_sub(JSContext *cx, JSObject *obj, JSString *sep, JSBool literalize, + jsval *rval, JSBool localeString) +{ + JSBool ok; + jsval v; + jsuint length, index; + jschar *chars, *ochars; + size_t nchars, growth, seplen, tmplen; + const jschar *sepstr; + JSString *str; + JSHashEntry *he; + JSObject *obj2; + + ok = js_GetLengthProperty(cx, obj, &length); + if (!ok) + return JS_FALSE; + ok = JS_TRUE; + + he = js_EnterSharpObject(cx, obj, NULL, &chars); + if (!he) + return JS_FALSE; + if (literalize) { + if (IS_SHARP(he)) { +#if JS_HAS_SHARP_VARS + nchars = js_strlen(chars); +#else + chars[0] = '['; + chars[1] = ']'; + chars[2] = 0; + nchars = 2; +#endif + goto make_string; + } + + /* + * Allocate 1 + 3 + 1 for "[", the worst-case closing ", ]", and the + * terminating 0. + */ + growth = (1 + 3 + 1) * sizeof(jschar); + if (!chars) { + nchars = 0; + chars = (jschar *) malloc(growth); + if (!chars) + goto done; + } else { + MAKE_SHARP(he); + nchars = js_strlen(chars); + chars = (jschar *) + realloc((ochars = chars), nchars * sizeof(jschar) + growth); + if (!chars) { + free(ochars); + goto done; + } + } + chars[nchars++] = '['; + } else { + /* + * Free any sharp variable definition in chars. Normally, we would + * MAKE_SHARP(he) so that only the first sharp variable annotation is + * a definition, and all the rest are references, but in the current + * case of (!literalize), we don't need chars at all. + */ + if (chars) + JS_free(cx, chars); + chars = NULL; + nchars = 0; + + /* Return the empty string on a cycle as well as on empty join. */ + if (IS_BUSY(he) || length == 0) { + js_LeaveSharpObject(cx, NULL); + *rval = JS_GetEmptyStringValue(cx); + return ok; + } + + /* Flag he as BUSY so we can distinguish a cycle from a join-point. */ + MAKE_BUSY(he); + } + sepstr = NULL; + seplen = JSSTRING_LENGTH(sep); + + v = JSVAL_NULL; + for (index = 0; index < length; index++) { + ok = JS_GetElement(cx, obj, index, &v); + if (!ok) + goto done; + + if (JSVAL_IS_VOID(v) || JSVAL_IS_NULL(v)) { + str = cx->runtime->emptyString; + } else { + if (localeString) { + if (!js_ValueToObject(cx, v, &obj2) || + !js_TryMethod(cx, obj2, + cx->runtime->atomState.toLocaleStringAtom, + 0, NULL, &v)) { + str = NULL; + } else { + str = js_ValueToString(cx, v); + } + } else { + str = (literalize ? js_ValueToSource : js_ValueToString)(cx, v); + } + if (!str) { + ok = JS_FALSE; + goto done; + } + } + + /* Allocate 3 + 1 at end for ", ", closing bracket, and zero. */ + growth = (nchars + (sepstr ? seplen : 0) + + JSSTRING_LENGTH(str) + + 3 + 1) * sizeof(jschar); + if (!chars) { + chars = (jschar *) malloc(growth); + if (!chars) + goto done; + } else { + chars = (jschar *) realloc((ochars = chars), growth); + if (!chars) { + free(ochars); + goto done; + } + } + + if (sepstr) { + js_strncpy(&chars[nchars], sepstr, seplen); + nchars += seplen; + } + sepstr = JSSTRING_CHARS(sep); + + tmplen = JSSTRING_LENGTH(str); + js_strncpy(&chars[nchars], JSSTRING_CHARS(str), tmplen); + nchars += tmplen; + } + + done: + if (literalize) { + if (chars) { + if (JSVAL_IS_VOID(v)) { + chars[nchars++] = ','; + chars[nchars++] = ' '; + } + chars[nchars++] = ']'; + } + } else { + CLEAR_BUSY(he); + } + js_LeaveSharpObject(cx, NULL); + if (!ok) { + if (chars) + free(chars); + return ok; + } + + make_string: + if (!chars) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + chars[nchars] = 0; + str = js_NewString(cx, chars, nchars, 0); + if (!str) { + free(chars); + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +static jschar comma_space_ucstr[] = {',', ' ', 0}; +static jschar comma_ucstr[] = {',', 0}; +static JSString comma_space = {2, comma_space_ucstr}; +static JSString comma = {1, comma_ucstr}; + +#if JS_HAS_TOSOURCE +static JSBool +array_toSource(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + return array_join_sub(cx, obj, &comma_space, JS_TRUE, rval, JS_FALSE); +} +#endif + +static JSBool +array_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSBool literalize; + + /* + * JS1.2 arrays convert to array literals, with a comma followed by a space + * between each element. + */ + literalize = (cx->version == JSVERSION_1_2); + return array_join_sub(cx, obj, literalize ? &comma_space : &comma, + literalize, rval, JS_FALSE); +} + +static JSBool +array_toLocaleString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + /* + * Passing comma here as the separator. Need a way to get a + * locale-specific version. + */ + return array_join_sub(cx, obj, &comma, JS_FALSE, rval, JS_TRUE); +} + +static JSBool +InitArrayElements(JSContext *cx, JSObject *obj, jsuint length, jsval *vector) +{ + jsuint index; + jsid id; + + for (index = 0; index < length; index++) { + if (!IndexToId(cx, index, &id)) + return JS_FALSE; + if (!OBJ_SET_PROPERTY(cx, obj, id, &vector[index])) + return JS_FALSE; + } + return JS_TRUE; +} + +static JSBool +InitArrayObject(JSContext *cx, JSObject *obj, jsuint length, jsval *vector) +{ + jsval v; + jsid id; + + if (!IndexToValue(cx, length, &v)) + return JS_FALSE; + id = (jsid) cx->runtime->atomState.lengthAtom; + if (!OBJ_DEFINE_PROPERTY(cx, obj, id, v, + array_length_getter, array_length_setter, + JSPROP_PERMANENT, + NULL)) { + return JS_FALSE; + } + if (!vector) + return JS_TRUE; + return InitArrayElements(cx, obj, length, vector); +} + +#if JS_HAS_SOME_PERL_FUN +/* + * Perl-inspired join, reverse, and sort. + */ +static JSBool +array_join(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str; + + if (JSVAL_IS_VOID(argv[0])) + return array_join_sub(cx, obj, &comma, JS_FALSE, rval, JS_FALSE); + str = js_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + argv[0] = STRING_TO_JSVAL(str); + return array_join_sub(cx, obj, str, JS_FALSE, rval, JS_FALSE); +} + +static JSBool +array_reverse(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + jsuint len, half, i; + jsid id, id2; + jsval v, v2; + + if (!js_GetLengthProperty(cx, obj, &len)) + return JS_FALSE; + + half = len / 2; + for (i = 0; i < half; i++) { + if (!IndexToId(cx, i, &id)) + return JS_FALSE; + if (!IndexToId(cx, len - i - 1, &id2)) + return JS_FALSE; + if (!OBJ_GET_PROPERTY(cx, obj, id, &v)) + return JS_FALSE; + if (!OBJ_GET_PROPERTY(cx, obj, id2, &v2)) + return JS_FALSE; + +#if JS_HAS_SPARSE_ARRAYS + /* This part isn't done yet. */ + + if (!OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop)) + return JS_FALSE; + if (!prop) { + OBJ_DELETE_PROPERTY(cx, obj, id2, &v); /* v is junk. */ + continue; + } + OBJ_DROP_PROPERTY(cx, obj2, prop); +#endif + + if (!OBJ_SET_PROPERTY(cx, obj, id, &v2)) + return JS_FALSE; + if (!OBJ_SET_PROPERTY(cx, obj, id2, &v)) + return JS_FALSE; + } + + *rval = OBJECT_TO_JSVAL(obj); + return JS_TRUE; +} + +typedef struct HSortArgs { + void *vec; + size_t elsize; + void *pivot; + JSComparator cmp; + void *arg; + JSBool fastcopy; +} HSortArgs; + +static int +sort_compare(const void *a, const void *b, void *arg); + +static int +sort_compare_strings(const void *a, const void *b, void *arg); + +static void +HeapSortHelper(JSBool building, HSortArgs *hsa, size_t lo, size_t hi) +{ + void *pivot, *vec, *vec2, *arg, *a, *b; + size_t elsize; + JSComparator cmp; + JSBool fastcopy; + size_t j, hiDiv2; + + pivot = hsa->pivot; + vec = hsa->vec; + elsize = hsa->elsize; + vec2 = (char *)vec - 2 * elsize; + cmp = hsa->cmp; + arg = hsa->arg; + + fastcopy = hsa->fastcopy; +#define MEMCPY(p,q,n) \ + (fastcopy ? (void)(*(jsval*)(p) = *(jsval*)(q)) : (void)memcpy(p, q, n)) + + if (lo == 1) { + j = 2; + b = (char *)vec + elsize; + if (j < hi && cmp(vec, b, arg) < 0) + j++; + a = (char *)vec + (hi - 1) * elsize; + b = (char *)vec2 + j * elsize; + + /* + * During sorting phase b points to a member of heap that cannot be + * bigger then biggest of vec[0] and vec[1], and cmp(a, b, arg) <= 0 + * always holds. + */ + if ((building || hi == 2) && cmp(a, b, arg) >= 0) + return; + + MEMCPY(pivot, a, elsize); + MEMCPY(a, b, elsize); + lo = j; + } else { + a = (char *)vec2 + lo * elsize; + MEMCPY(pivot, a, elsize); + } + + hiDiv2 = hi/2; + while (lo <= hiDiv2) { + j = lo + lo; + a = (char *)vec2 + j * elsize; + b = (char *)vec + (j - 1) * elsize; + if (j < hi && cmp(a, b, arg) < 0) + j++; + b = (char *)vec2 + j * elsize; + if (cmp(pivot, b, arg) >= 0) + break; + + a = (char *)vec2 + lo * elsize; + MEMCPY(a, b, elsize); + lo = j; + } + + a = (char *)vec2 + lo * elsize; + MEMCPY(a, pivot, elsize); +#undef MEMCPY +} + +JSBool +js_HeapSort(void *vec, size_t nel, size_t elsize, JSComparator cmp, void *arg) +{ + void *pivot; + HSortArgs hsa; + size_t i; + + pivot = malloc(elsize); + if (!pivot) + return JS_FALSE; + hsa.vec = vec; + hsa.elsize = elsize; + hsa.pivot = pivot; + hsa.cmp = cmp; + hsa.arg = arg; + hsa.fastcopy = (cmp == sort_compare || cmp == sort_compare_strings); + + for (i = nel/2; i != 0; i--) + HeapSortHelper(JS_TRUE, &hsa, i, nel); + while (nel > 2) + HeapSortHelper(JS_FALSE, &hsa, 1, --nel); + + free(pivot); + return JS_TRUE; +} + +typedef struct CompareArgs { + JSContext *context; + jsval fval; + JSBool status; +} CompareArgs; + +static int +sort_compare(const void *a, const void *b, void *arg) +{ + jsval av = *(const jsval *)a, bv = *(const jsval *)b; + CompareArgs *ca = (CompareArgs *) arg; + JSContext *cx = ca->context; + jsdouble cmp = -1; + jsval fval, argv[2], rval; + JSBool ok; + + fval = ca->fval; + if (fval == JSVAL_NULL) { + JSString *astr, *bstr; + + if (av == bv) { + cmp = 0; + } else if (av == JSVAL_VOID || bv == JSVAL_VOID) { + /* Put undefined properties at the end. */ + cmp = (av == JSVAL_VOID) ? 1 : -1; + } else if ((astr = js_ValueToString(cx, av)) != NULL && + (bstr = js_ValueToString(cx, bv)) != NULL) { + cmp = js_CompareStrings(astr, bstr); + } else { + ca->status = JS_FALSE; + } + } else { + argv[0] = av; + argv[1] = bv; + ok = js_InternalCall(cx, + OBJ_GET_PARENT(cx, JSVAL_TO_OBJECT(fval)), + fval, 2, argv, &rval); + if (ok) { + ok = js_ValueToNumber(cx, rval, &cmp); + /* Clamp cmp to -1, 0, 1. */ + if (JSDOUBLE_IS_NaN(cmp)) { + /* XXX report some kind of error here? ECMA talks about + * 'consistent compare functions' that don't return NaN, but is + * silent about what the result should be. So we currently + * ignore it. + */ + cmp = 0; + } else if (cmp != 0) { + cmp = cmp > 0 ? 1 : -1; + } + } else { + ca->status = ok; + } + } + return (int)cmp; +} + +static int +sort_compare_strings(const void *a, const void *b, void *arg) +{ + jsval av = *(const jsval *)a, bv = *(const jsval *)b; + + return (int) js_CompareStrings(JSVAL_TO_STRING(av), JSVAL_TO_STRING(bv)); +} + +/* XXXmccabe do the sort helper functions need to take int? (Or can we claim + * that 2^32 * 32 is too large to worry about?) Something dumps when I change + * to unsigned int; is qsort using -1 as a fencepost? + */ +static JSBool +array_sort(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsval fval; + CompareArgs ca; + jsuint len, newlen, i; + jsval *vec; + jsid id; + size_t nbytes; + + /* + * Optimize the default compare function case if all of obj's elements + * have values of type string. + */ + JSBool all_strings; + + if (argc > 0) { + if (JSVAL_IS_PRIMITIVE(argv[0])) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_SORT_ARG); + return JS_FALSE; + } + fval = argv[0]; + all_strings = JS_FALSE; /* non-default compare function */ + } else { + fval = JSVAL_NULL; + all_strings = JS_TRUE; /* check for all string values */ + } + + if (!js_GetLengthProperty(cx, obj, &len)) + return JS_FALSE; + if (len == 0) { + *rval = OBJECT_TO_JSVAL(obj); + return JS_TRUE; + } + + /* + * Test for size_t overflow, which could lead to indexing beyond the end + * of the malloc'd vector. + */ + nbytes = len * sizeof(jsval); + if (nbytes != (double) len * sizeof(jsval)) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + vec = (jsval *) JS_malloc(cx, nbytes); + if (!vec) + return JS_FALSE; + +#if JS_HAS_SPARSE_ARRAYS + newlen = 0; +#else + newlen = len; +#endif + + for (i = 0; i < len; i++) { + ca.status = IndexToId(cx, i, &id); + if (!ca.status) + goto out; +#if JS_HAS_SPARSE_ARRAYS + { + JSObject *obj2; + JSProperty *prop; + ca.status = OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop); + if (!ca.status) + goto out; + if (!prop) { + vec[i] = JSVAL_VOID; + continue; + } + OBJ_DROP_PROPERTY(cx, obj2, prop); + newlen++; + } +#endif + ca.status = OBJ_GET_PROPERTY(cx, obj, id, &vec[i]); + if (!ca.status) + goto out; + + /* We know JSVAL_IS_STRING yields 0 or 1, so avoid a branch via &=. */ + all_strings &= JSVAL_IS_STRING(vec[i]); + } + + ca.context = cx; + ca.fval = fval; + ca.status = JS_TRUE; + if (!js_HeapSort(vec, (size_t) len, sizeof(jsval), + all_strings ? sort_compare_strings : sort_compare, + &ca)) { + JS_ReportOutOfMemory(cx); + ca.status = JS_FALSE; + } + + if (ca.status) { + ca.status = InitArrayElements(cx, obj, newlen, vec); + if (ca.status) + *rval = OBJECT_TO_JSVAL(obj); +#if JS_HAS_SPARSE_ARRAYS + /* set length of newly-created array object to old length. */ + if (ca.status && newlen < len) { + ca.status = js_SetLengthProperty(cx, obj, len); + + /* Delete any leftover properties greater than newlen. */ + while (ca.status && newlen < len) { + jsval junk; + + ca.status = !IndexToId(cx, newlen, &id) || + !OBJ_DELETE_PROPERTY(cx, obj, id, &junk); + newlen++; + } + } +#endif + } + +out: + if (vec) + JS_free(cx, vec); + return ca.status; +} +#endif /* JS_HAS_SOME_PERL_FUN */ + +#if JS_HAS_MORE_PERL_FUN +/* + * Perl-inspired push, pop, shift, unshift, and splice methods. + */ +static JSBool +array_push(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsuint length; + uintN i; + jsid id; + + if (!js_GetLengthProperty(cx, obj, &length)) + return JS_FALSE; + for (i = 0; i < argc; i++) { + if (!IndexToId(cx, length + i, &id)) + return JS_FALSE; + if (!OBJ_SET_PROPERTY(cx, obj, id, &argv[i])) + return JS_FALSE; + } + + /* + * If JS1.2, follow Perl4 by returning the last thing pushed. Otherwise, + * return the new array length. + */ + length += argc; + if (cx->version == JSVERSION_1_2) { + *rval = argc ? argv[argc-1] : JSVAL_VOID; + } else { + if (!IndexToValue(cx, length, rval)) + return JS_FALSE; + } + return js_SetLengthProperty(cx, obj, length); +} + +static JSBool +array_pop(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsuint index; + jsid id; + jsval junk; + + if (!js_GetLengthProperty(cx, obj, &index)) + return JS_FALSE; + if (index > 0) { + index--; + if (!IndexToId(cx, index, &id)) + return JS_FALSE; + + /* Get the to-be-deleted property's value into rval. */ + if (!OBJ_GET_PROPERTY(cx, obj, id, rval)) + return JS_FALSE; + + if (!OBJ_DELETE_PROPERTY(cx, obj, id, &junk)) + return JS_FALSE; + } + return js_SetLengthProperty(cx, obj, index); +} + +static JSBool +array_shift(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsuint length, i; + jsid id, id2; + jsval v, junk; + + if (!js_GetLengthProperty(cx, obj, &length)) + return JS_FALSE; + if (length > 0) { + length--; + id = JSVAL_ZERO; + + /* Get the to-be-deleted property's value into rval ASAP. */ + if (!OBJ_GET_PROPERTY(cx, obj, id, rval)) + return JS_FALSE; + + /* + * Slide down the array above the first element. + */ + if (length > 0) { + for (i = 1; i <= length; i++) { + if (!IndexToId(cx, i, &id)) + return JS_FALSE; + if (!IndexToId(cx, i - 1, &id2)) + return JS_FALSE; + if (!OBJ_GET_PROPERTY(cx, obj, id, &v)) + return JS_FALSE; + if (!OBJ_SET_PROPERTY(cx, obj, id2, &v)) + return JS_FALSE; + } + } + + /* Delete the only or last element. */ + if (!OBJ_DELETE_PROPERTY(cx, obj, id, &junk)) + return JS_FALSE; + } + return js_SetLengthProperty(cx, obj, length); +} + +static JSBool +array_unshift(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + jsuint length, last; + uintN i; + jsid id, id2; + jsval v; +#if JS_HAS_SPARSE_ARRAYS + JSObject *obj2; + JSProperty *prop; +#endif + + if (!js_GetLengthProperty(cx, obj, &length)) + return JS_FALSE; + if (argc > 0) { + /* Slide up the array to make room for argc at the bottom. */ + if (length > 0) { + last = length; + while (last--) { + if (!IndexToId(cx, last, &id)) + return JS_FALSE; + if (!IndexToId(cx, last + argc, &id2)) + return JS_FALSE; +#if JS_HAS_SPARSE_ARRAYS + if (!OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop)) + return JS_FALSE; + if (!prop) { + OBJ_DELETE_PROPERTY(cx, obj, id2, &v); /* v is junk. */ + continue; + } + OBJ_DROP_PROPERTY(cx, obj2, prop); +#endif + if (!OBJ_GET_PROPERTY(cx, obj, id, &v)) + return JS_FALSE; + if (!OBJ_SET_PROPERTY(cx, obj, id2, &v)) + return JS_FALSE; + } + } + + /* Copy from argv to the bottom of the array. */ + for (i = 0; i < argc; i++) { + if (!IndexToId(cx, i, &id)) + return JS_FALSE; + if (!OBJ_SET_PROPERTY(cx, obj, id, &argv[i])) + return JS_FALSE; + } + + /* Follow Perl by returning the new array length. */ + length += argc; + if (!js_SetLengthProperty(cx, obj, length)) + return JS_FALSE; + } + return IndexToValue(cx, length, rval); +} + +static JSBool +array_splice(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsuint length, begin, end, count, delta, last; + uintN i; + jsdouble d; + jsid id, id2; + jsval v; + JSObject *obj2; + + /* Nothing to do if no args. Otherwise lock and load length. */ + if (argc == 0) + return JS_TRUE; + if (!js_GetLengthProperty(cx, obj, &length)) + return JS_FALSE; + + /* Convert the first argument into a starting index. */ + if (!js_ValueToNumber(cx, *argv, &d)) + return JS_FALSE; + d = js_DoubleToInteger(d); + if (d < 0) { + d += length; + if (d < 0) + d = 0; + } else if (d > length) { + d = length; + } + begin = (jsuint)d; /* d has been clamped to uint32 */ + argc--; + argv++; + + /* Convert the second argument from a count into a fencepost index. */ + delta = length - begin; + if (argc == 0) { + count = delta; + end = length; + } else { + if (!js_ValueToNumber(cx, *argv, &d)) + return JS_FALSE; + d = js_DoubleToInteger(d); + if (d < 0) + d = 0; + else if (d > delta) + d = delta; + count = (jsuint)d; + end = begin + count; + argc--; + argv++; + } + + if (count == 1 && cx->version == JSVERSION_1_2) { + /* + * JS lacks "list context", whereby in Perl one turns the single + * scalar that's spliced out into an array just by assigning it to + * @single instead of $single, or by using it as Perl push's first + * argument, for instance. + * + * JS1.2 emulated Perl too closely and returned a non-Array for + * the single-splice-out case, requiring callers to test and wrap + * in [] if necessary. So JS1.3, default, and other versions all + * return an array of length 1 for uniformity. + */ + if (!IndexToId(cx, begin, &id)) + return JS_FALSE; + if (!OBJ_GET_PROPERTY(cx, obj, id, rval)) + return JS_FALSE; + } else { + if (cx->version != JSVERSION_1_2 || count > 0) { + /* + * Create a new array value to return. Our ECMA v2 proposal specs + * that splice always returns an array value, even when given no + * arguments. We think this is best because it eliminates the need + * for callers to do an extra test to handle the empty splice case. + */ + obj2 = js_NewArrayObject(cx, 0, NULL); + if (!obj2) + return JS_FALSE; + *rval = OBJECT_TO_JSVAL(obj2); + + /* If there are elements to remove, put them into the return value. */ + if (count > 0) { + for (last = begin; last < end; last++) { + if (!IndexToId(cx, last, &id)) + return JS_FALSE; + if (!IndexToId(cx, last - begin, &id2)) + return JS_FALSE; + if (!OBJ_GET_PROPERTY(cx, obj, id, &v)) + return JS_FALSE; + if (!OBJ_SET_PROPERTY(cx, obj2, id2, &v)) + return JS_FALSE; + } + } + } + } + + /* Find the direction (up or down) to copy and make way for argv. */ + if (argc > count) { + delta = (jsuint)argc - count; + last = length; + /* (uint) end could be 0, so can't use vanilla >= test */ + while (last-- > end) { + if (!IndexToId(cx, last, &id)) + return JS_FALSE; + if (!IndexToId(cx, last + delta, &id2)) + return JS_FALSE; + if (!OBJ_GET_PROPERTY(cx, obj, id, &v)) + return JS_FALSE; + if (!OBJ_SET_PROPERTY(cx, obj, id2, &v)) + return JS_FALSE; + } + length += delta; + } else if (argc < count) { + delta = count - (jsuint)argc; + for (last = end; last < length; last++) { + if (!IndexToId(cx, last, &id)) + return JS_FALSE; + if (!IndexToId(cx, last - delta, &id2)) + return JS_FALSE; + if (!OBJ_GET_PROPERTY(cx, obj, id, &v)) + return JS_FALSE; + if (!OBJ_SET_PROPERTY(cx, obj, id2, &v)) + return JS_FALSE; + } + length -= delta; + } + + /* Copy from argv into the hole to complete the splice. */ + for (i = 0; i < argc; i++) { + if (!IndexToId(cx, begin + i, &id)) + return JS_FALSE; + if (!OBJ_SET_PROPERTY(cx, obj, id, &argv[i])) + return JS_FALSE; + } + + /* Update length in case we deleted elements from the end. */ + return js_SetLengthProperty(cx, obj, length); +} +#endif /* JS_HAS_MORE_PERL_FUN */ + +#if JS_HAS_SEQUENCE_OPS +/* + * Python-esque sequence operations. + */ +static JSBool +array_concat(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSObject *nobj, *aobj; + jsuint length, alength, slot; + uintN i; + jsval v; + jsid id, id2; + + /* Treat obj as the first argument; see ECMA 15.4.4.4. */ + --argv; + JS_ASSERT(obj == JSVAL_TO_OBJECT(argv[0])); + + /* Create a new Array object and store it in the rval local root. */ + nobj = js_NewArrayObject(cx, 0, NULL); + if (!nobj) + return JS_FALSE; + *rval = OBJECT_TO_JSVAL(nobj); + + /* Loop over [0, argc] to concat args into nobj, expanding all Arrays. */ + length = 0; + for (i = 0; i <= argc; i++) { + v = argv[i]; + if (JSVAL_IS_OBJECT(v)) { + aobj = JSVAL_TO_OBJECT(v); + if (aobj && OBJ_GET_CLASS(cx, aobj) == &js_ArrayClass) { + if (!OBJ_GET_PROPERTY(cx, aobj, + (jsid)cx->runtime->atomState.lengthAtom, + &v)) { + return JS_FALSE; + } + if (!ValueIsLength(cx, v, &alength)) + return JS_FALSE; + for (slot = 0; slot < alength; slot++) { + if (!IndexToId(cx, slot, &id)) + return JS_FALSE; + if (!IndexToId(cx, length + slot, &id2)) + return JS_FALSE; + if (!OBJ_GET_PROPERTY(cx, aobj, id, &v)) + return JS_FALSE; + if (!OBJ_SET_PROPERTY(cx, nobj, id2, &v)) + return JS_FALSE; + } + length += alength; + continue; + } + } + + if (!IndexToId(cx, length, &id)) + return JS_FALSE; + if (!OBJ_SET_PROPERTY(cx, nobj, id, &v)) + return JS_FALSE; + length++; + } + + return JS_TRUE; +} + +static JSBool +array_slice(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSObject *nobj; + jsuint length, begin, end, slot; + jsdouble d; + jsid id, id2; + jsval v; + + nobj = js_NewArrayObject(cx, 0, NULL); + if (!nobj) + return JS_FALSE; + + if (!js_GetLengthProperty(cx, obj, &length)) + return JS_FALSE; + begin = 0; + end = length; + + if (argc > 0) { + if (!js_ValueToNumber(cx, argv[0], &d)) + return JS_FALSE; + d = js_DoubleToInteger(d); + if (d < 0) { + d += length; + if (d < 0) + d = 0; + } else if (d > length) { + d = length; + } + begin = (jsuint)d; + + if (argc > 1) { + if (!js_ValueToNumber(cx, argv[1], &d)) + return JS_FALSE; + d = js_DoubleToInteger(d); + if (d < 0) { + d += length; + if (d < 0) + d = 0; + } else if (d > length) { + d = length; + } + end = (jsuint)d; + } + } + + for (slot = begin; slot < end; slot++) { + if (!IndexToId(cx, slot, &id)) + return JS_FALSE; + if (!IndexToId(cx, slot - begin, &id2)) + return JS_FALSE; + if (!OBJ_GET_PROPERTY(cx, obj, id, &v)) + return JS_FALSE; + if (!OBJ_SET_PROPERTY(cx, nobj, id2, &v)) + return JS_FALSE; + } + *rval = OBJECT_TO_JSVAL(nobj); + return JS_TRUE; +} +#endif /* JS_HAS_SEQUENCE_OPS */ + +static JSFunctionSpec array_methods[] = { +#if JS_HAS_TOSOURCE + {js_toSource_str, array_toSource, 0,0,0}, +#endif + {js_toString_str, array_toString, 0,0,0}, + {js_toLocaleString_str, array_toLocaleString, 0,0,0}, + + /* Perl-ish methods. */ +#if JS_HAS_SOME_PERL_FUN + {"join", array_join, 1,0,0}, + {"reverse", array_reverse, 0,0,0}, + {"sort", array_sort, 1,0,0}, +#endif +#if JS_HAS_MORE_PERL_FUN + {"push", array_push, 1,0,0}, + {"pop", array_pop, 0,0,0}, + {"shift", array_shift, 0,0,0}, + {"unshift", array_unshift, 1,0,0}, + {"splice", array_splice, 1,0,0}, +#endif + + /* Python-esque sequence methods. */ +#if JS_HAS_SEQUENCE_OPS + {"concat", array_concat, 0,0,0}, + {"slice", array_slice, 0,0,0}, +#endif + + {0,0,0,0,0} +}; + +static JSBool +Array(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsuint length; + jsval *vector; + + /* If called without new, replace obj with a new Array object. */ + if (!(cx->fp->flags & JSFRAME_CONSTRUCTING)) { + obj = js_NewObject(cx, &js_ArrayClass, NULL, NULL); + if (!obj) + return JS_FALSE; + *rval = OBJECT_TO_JSVAL(obj); + } + + if (argc == 0) { + length = 0; + vector = NULL; + } else if (cx->version == JSVERSION_1_2) { + length = (jsuint) argc; + vector = argv; + } else if (argc > 1) { + length = (jsuint) argc; + vector = argv; + } else if (!JSVAL_IS_NUMBER(argv[0])) { + length = 1; + vector = argv; + } else { + if (!ValueIsLength(cx, argv[0], &length)) + return JS_FALSE; + vector = NULL; + } + return InitArrayObject(cx, obj, length, vector); +} + +JSObject * +js_InitArrayClass(JSContext *cx, JSObject *obj) +{ + JSObject *proto; + + proto = JS_InitClass(cx, obj, NULL, &js_ArrayClass, Array, 1, + NULL, array_methods, NULL, NULL); + + /* Initialize the Array prototype object so it gets a length property. */ + if (!proto || !InitArrayObject(cx, proto, 0, NULL)) + return NULL; + return proto; +} + +JSObject * +js_NewArrayObject(JSContext *cx, jsuint length, jsval *vector) +{ + JSObject *obj; + + obj = js_NewObject(cx, &js_ArrayClass, NULL, NULL); + if (!obj) + return NULL; + if (!InitArrayObject(cx, obj, length, vector)) { + cx->newborn[GCX_OBJECT] = NULL; + return NULL; + } + return obj; +} diff --git a/src/dom/js/jsarray.h b/src/dom/js/jsarray.h new file mode 100644 index 000000000..cbb2aedf1 --- /dev/null +++ b/src/dom/js/jsarray.h @@ -0,0 +1,77 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsarray_h___ +#define jsarray_h___ +/* + * JS Array interface. + */ +#include "jsprvtd.h" +#include "jspubtd.h" + +JS_BEGIN_EXTERN_C + +extern JSClass js_ArrayClass; + +extern JSObject * +js_InitArrayClass(JSContext *cx, JSObject *obj); + +extern JSObject * +js_NewArrayObject(JSContext *cx, jsuint length, jsval *vector); + +extern JSBool +js_GetLengthProperty(JSContext *cx, JSObject *obj, jsuint *lengthp); + +extern JSBool +js_SetLengthProperty(JSContext *cx, JSObject *obj, jsuint length); + +extern JSBool +js_HasLengthProperty(JSContext *cx, JSObject *obj, jsuint *lengthp); + +/* + * JS-specific heap sort function. + */ +typedef int (*JSComparator)(const void *a, const void *b, void *arg); + +extern JSBool +js_HeapSort(void *vec, size_t nel, size_t elsize, JSComparator cmp, void *arg); + +JS_END_EXTERN_C + +#endif /* jsarray_h___ */ diff --git a/src/dom/js/jsatom.c b/src/dom/js/jsatom.c new file mode 100644 index 000000000..59cb4b801 --- /dev/null +++ b/src/dom/js/jsatom.c @@ -0,0 +1,911 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS atom table. + */ +#include "jsstddef.h" +#include +#include +#include "jstypes.h" +#include "jsutil.h" /* Added by JSIFY */ +#include "jshash.h" /* Added by JSIFY */ +#include "jsprf.h" +#include "jsapi.h" +#include "jsatom.h" +#include "jscntxt.h" +#include "jsgc.h" +#include "jslock.h" +#include "jsnum.h" +#include "jsopcode.h" +#include "jsstr.h" + +JS_FRIEND_API(const char *) +js_AtomToPrintableString(JSContext *cx, JSAtom *atom) +{ + JSString *str; + const char *bytes; + + str = js_QuoteString(cx, ATOM_TO_STRING(atom), 0); + if (!str) + return NULL; + bytes = js_GetStringBytes(str); + if (!bytes) + JS_ReportOutOfMemory(cx); + return bytes; +} + +extern const char js_Error_str[]; /* trivial, from jsexn.h */ + +/* + * Keep this in sync with jspubtd.h -- an assertion below will insist that + * its length match the JSType enum's JSTYPE_LIMIT limit value. + */ +const char *js_type_str[] = { + "undefined", + "object", + "function", + "string", + "number", + "boolean", +}; + +const char *js_boolean_str[] = { + js_false_str, + js_true_str +}; + +const char js_Arguments_str[] = "Arguments"; +const char js_Array_str[] = "Array"; +const char js_Boolean_str[] = "Boolean"; +const char js_Call_str[] = "Call"; +const char js_Date_str[] = "Date"; +const char js_Function_str[] = "Function"; +const char js_Math_str[] = "Math"; +const char js_Number_str[] = "Number"; +const char js_Object_str[] = "Object"; +const char js_RegExp_str[] = "RegExp"; +const char js_Script_str[] = "Script"; +const char js_String_str[] = "String"; +const char js_anonymous_str[] = "anonymous"; +const char js_arguments_str[] = "arguments"; +const char js_arity_str[] = "arity"; +const char js_callee_str[] = "callee"; +const char js_caller_str[] = "caller"; +const char js_class_prototype_str[] = "prototype"; +const char js_constructor_str[] = "constructor"; +const char js_count_str[] = "__count__"; +const char js_eval_str[] = "eval"; +const char js_getter_str[] = "getter"; +const char js_get_str[] = "get"; +const char js_index_str[] = "index"; +const char js_input_str[] = "input"; +const char js_length_str[] = "length"; +const char js_name_str[] = "name"; +const char js_noSuchMethod_str[] = "__noSuchMethod__"; +const char js_parent_str[] = "__parent__"; +const char js_proto_str[] = "__proto__"; +const char js_setter_str[] = "setter"; +const char js_set_str[] = "set"; +const char js_toSource_str[] = "toSource"; +const char js_toString_str[] = "toString"; +const char js_toLocaleString_str[] = "toLocaleString"; +const char js_valueOf_str[] = "valueOf"; + +#define HASH_OBJECT(o) ((JSHashNumber)(o) >> JSVAL_TAGBITS) +#define HASH_INT(i) ((JSHashNumber)(i)) +#define HASH_DOUBLE(dp) ((JSHashNumber)(((uint32*)(dp))[0] ^ ((uint32*)(dp))[1])) +#define HASH_BOOLEAN(b) ((JSHashNumber)(b)) + +JS_STATIC_DLL_CALLBACK(JSHashNumber) +js_hash_atom_key(const void *key) +{ + jsval v; + jsdouble *dp; + + /* Order JSVAL_IS_* tests by likelihood of success. */ + v = (jsval)key; + if (JSVAL_IS_STRING(v)) + return js_HashString(JSVAL_TO_STRING(v)); + if (JSVAL_IS_INT(v)) + return HASH_INT(JSVAL_TO_INT(v)); + if (JSVAL_IS_DOUBLE(v)) { + dp = JSVAL_TO_DOUBLE(v); + return HASH_DOUBLE(dp); + } + if (JSVAL_IS_OBJECT(v)) + return HASH_OBJECT(JSVAL_TO_OBJECT(v)); + if (JSVAL_IS_BOOLEAN(v)) + return HASH_BOOLEAN(JSVAL_TO_BOOLEAN(v)); + return (JSHashNumber)v; +} + +JS_STATIC_DLL_CALLBACK(intN) +js_compare_atom_keys(const void *k1, const void *k2) +{ + jsval v1, v2; + + v1 = (jsval)k1, v2 = (jsval)k2; + if (JSVAL_IS_STRING(v1) && JSVAL_IS_STRING(v2)) + return !js_CompareStrings(JSVAL_TO_STRING(v1), JSVAL_TO_STRING(v2)); + if (JSVAL_IS_DOUBLE(v1) && JSVAL_IS_DOUBLE(v2)) { + double d1 = *JSVAL_TO_DOUBLE(v1); + double d2 = *JSVAL_TO_DOUBLE(v2); + if (JSDOUBLE_IS_NaN(d1)) + return JSDOUBLE_IS_NaN(d2); +#if defined(XP_WIN) + /* XXX MSVC miscompiles such that (NaN == 0) */ + if (JSDOUBLE_IS_NaN(d2)) + return JS_FALSE; +#endif + return d1 == d2; + } + return v1 == v2; +} + +JS_STATIC_DLL_CALLBACK(int) +js_compare_stub(const void *v1, const void *v2) +{ + return 1; +} + +/* These next two are exported to jsscript.c and used similarly there. */ +void * JS_DLL_CALLBACK +js_alloc_table_space(void *priv, size_t size) +{ + return malloc(size); +} + +void JS_DLL_CALLBACK +js_free_table_space(void *priv, void *item) +{ + free(item); +} + +JS_STATIC_DLL_CALLBACK(JSHashEntry *) +js_alloc_atom(void *priv, const void *key) +{ + JSAtomState *state = (JSAtomState *) priv; + JSAtom *atom; + + atom = (JSAtom *) malloc(sizeof(JSAtom)); + if (!atom) + return NULL; +#ifdef JS_THREADSAFE + state->tablegen++; +#endif + atom->entry.key = key; + atom->entry.value = NULL; + atom->flags = 0; + atom->number = state->number++; + return &atom->entry; +} + +JS_STATIC_DLL_CALLBACK(void) +js_free_atom(void *priv, JSHashEntry *he, uintN flag) +{ + if (flag != HT_FREE_ENTRY) + return; +#ifdef JS_THREADSAFE + ((JSAtomState *)priv)->tablegen++; +#endif + free(he); +} + +static JSHashAllocOps atom_alloc_ops = { + js_alloc_table_space, js_free_table_space, + js_alloc_atom, js_free_atom +}; + +#define JS_ATOM_HASH_SIZE 1024 + +JSBool +js_InitAtomState(JSContext *cx, JSAtomState *state) +{ + state->table = JS_NewHashTable(JS_ATOM_HASH_SIZE, js_hash_atom_key, + js_compare_atom_keys, js_compare_stub, + &atom_alloc_ops, state); + if (!state->table) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + + state->runtime = cx->runtime; +#ifdef JS_THREADSAFE + js_InitLock(&state->lock); + state->tablegen = 0; +#endif + + if (!js_InitPinnedAtoms(cx, state)) { + js_FreeAtomState(cx, state); + return JS_FALSE; + } + return JS_TRUE; +} + +JSBool +js_InitPinnedAtoms(JSContext *cx, JSAtomState *state) +{ + uintN i; + +#define FROB(lval,str) \ + JS_BEGIN_MACRO \ + if (!(state->lval = js_Atomize(cx, str, strlen(str), ATOM_PINNED))) \ + return JS_FALSE; \ + JS_END_MACRO + + JS_ASSERT(sizeof js_type_str / sizeof js_type_str[0] == JSTYPE_LIMIT); + for (i = 0; i < JSTYPE_LIMIT; i++) + FROB(typeAtoms[i], js_type_str[i]); + + FROB(booleanAtoms[0], js_false_str); + FROB(booleanAtoms[1], js_true_str); + FROB(nullAtom, js_null_str); + + FROB(ArgumentsAtom, js_Arguments_str); + FROB(ArrayAtom, js_Array_str); + FROB(BooleanAtom, js_Boolean_str); + FROB(CallAtom, js_Call_str); + FROB(DateAtom, js_Date_str); + FROB(ErrorAtom, js_Error_str); + FROB(FunctionAtom, js_Function_str); + FROB(MathAtom, js_Math_str); + FROB(NumberAtom, js_Number_str); + FROB(ObjectAtom, js_Object_str); + FROB(RegExpAtom, js_RegExp_str); + FROB(ScriptAtom, js_Script_str); + FROB(StringAtom, js_String_str); + FROB(anonymousAtom, js_anonymous_str); + FROB(argumentsAtom, js_arguments_str); + FROB(arityAtom, js_arity_str); + FROB(calleeAtom, js_callee_str); + FROB(callerAtom, js_caller_str); + FROB(classPrototypeAtom, js_class_prototype_str); + FROB(constructorAtom, js_constructor_str); + FROB(countAtom, js_count_str); + FROB(evalAtom, js_eval_str); + FROB(getAtom, js_get_str); + FROB(getterAtom, js_getter_str); + FROB(indexAtom, js_index_str); + FROB(inputAtom, js_input_str); + FROB(lengthAtom, js_length_str); + FROB(nameAtom, js_name_str); + FROB(noSuchMethodAtom, js_noSuchMethod_str); + FROB(parentAtom, js_parent_str); + FROB(protoAtom, js_proto_str); + FROB(setAtom, js_set_str); + FROB(setterAtom, js_setter_str); + FROB(toSourceAtom, js_toSource_str); + FROB(toStringAtom, js_toString_str); + FROB(toLocaleStringAtom, js_toLocaleString_str); + FROB(valueOfAtom, js_valueOf_str); + +#undef FROB + + memset(&state->lazy, 0, sizeof state->lazy); + return JS_TRUE; +} + +/* NB: cx unused; js_FinishAtomState calls us with null cx. */ +void +js_FreeAtomState(JSContext *cx, JSAtomState *state) +{ + if (state->table) + JS_HashTableDestroy(state->table); +#ifdef JS_THREADSAFE + js_FinishLock(&state->lock); +#endif + memset(state, 0, sizeof *state); +} + +typedef struct UninternArgs { + JSRuntime *rt; + jsatomid leaks; +} UninternArgs; + +JS_STATIC_DLL_CALLBACK(intN) +js_atom_uninterner(JSHashEntry *he, intN i, void *arg) +{ + JSAtom *atom; + UninternArgs *args; + + atom = (JSAtom *)he; + args = (UninternArgs *)arg; + if (ATOM_IS_STRING(atom)) + js_FinalizeStringRT(args->rt, ATOM_TO_STRING(atom)); + else if (ATOM_IS_OBJECT(atom)) + args->leaks++; + return HT_ENUMERATE_NEXT; +} + +void +js_FinishAtomState(JSAtomState *state) +{ + UninternArgs args; + + if (!state->table) + return; + args.rt = state->runtime; + args.leaks = 0; + JS_HashTableEnumerateEntries(state->table, js_atom_uninterner, &args); +#ifdef DEBUG + if (args.leaks != 0) { + fprintf(stderr, +"JS engine warning: %lu atoms remain after destroying the JSRuntime.\n" +" These atoms may point to freed memory. Things reachable\n" +" through them have not been finalized.\n", + (unsigned long) args.leaks); + } +#endif + js_FreeAtomState(NULL, state); +} + +typedef struct MarkArgs { + uintN gcflags; + JSGCThingMarker mark; + void *data; +} MarkArgs; + +JS_STATIC_DLL_CALLBACK(intN) +js_atom_marker(JSHashEntry *he, intN i, void *arg) +{ + JSAtom *atom; + MarkArgs *args; + jsval key; + + atom = (JSAtom *)he; + args = (MarkArgs *)arg; + if ((atom->flags & (ATOM_PINNED | ATOM_INTERNED)) || + (args->gcflags & GC_KEEP_ATOMS)) { + atom->flags |= ATOM_MARK; + key = ATOM_KEY(atom); + if (JSVAL_IS_GCTHING(key)) + args->mark(JSVAL_TO_GCTHING(key), args->data); + } + return HT_ENUMERATE_NEXT; +} + +void +js_MarkAtomState(JSAtomState *state, uintN gcflags, JSGCThingMarker mark, + void *data) +{ + MarkArgs args; + + if (!state->table) + return; + args.gcflags = gcflags; + args.mark = mark; + args.data = data; + JS_HashTableEnumerateEntries(state->table, js_atom_marker, &args); +} + +JS_STATIC_DLL_CALLBACK(intN) +js_atom_sweeper(JSHashEntry *he, intN i, void *arg) +{ + JSAtom *atom; + JSAtomState *state; + + atom = (JSAtom *)he; + if (atom->flags & ATOM_MARK) { + atom->flags &= ~ATOM_MARK; + state = (JSAtomState *)arg; + state->liveAtoms++; + return HT_ENUMERATE_NEXT; + } + JS_ASSERT((atom->flags & (ATOM_PINNED | ATOM_INTERNED)) == 0); + atom->entry.key = NULL; + atom->flags = 0; + return HT_ENUMERATE_REMOVE; +} + +void +js_SweepAtomState(JSAtomState *state) +{ + state->liveAtoms = 0; + if (state->table) + JS_HashTableEnumerateEntries(state->table, js_atom_sweeper, state); +} + +JS_STATIC_DLL_CALLBACK(intN) +js_atom_unpinner(JSHashEntry *he, intN i, void *arg) +{ + JSAtom *atom; + + atom = (JSAtom *)he; + atom->flags &= ~ATOM_PINNED; + return HT_ENUMERATE_NEXT; +} + +void +js_UnpinPinnedAtoms(JSAtomState *state) +{ + if (state->table) + JS_HashTableEnumerateEntries(state->table, js_atom_unpinner, NULL); +} + +static JSAtom * +js_AtomizeHashedKey(JSContext *cx, jsval key, JSHashNumber keyHash, uintN flags) +{ + JSAtomState *state; + JSHashTable *table; + JSHashEntry *he, **hep; + JSAtom *atom; + + state = &cx->runtime->atomState; + JS_LOCK(&state->lock, cx); + table = state->table; + hep = JS_HashTableRawLookup(table, keyHash, (void *)key); + if ((he = *hep) == NULL) { + he = JS_HashTableRawAdd(table, hep, keyHash, (void *)key, NULL); + if (!he) { + JS_ReportOutOfMemory(cx); + atom = NULL; + goto out; + } + } + + atom = (JSAtom *)he; + atom->flags |= flags; + cx->lastAtom = atom; +out: + JS_UNLOCK(&state->lock,cx); + return atom; +} + +JSAtom * +js_AtomizeObject(JSContext *cx, JSObject *obj, uintN flags) +{ + jsval key; + JSHashNumber keyHash; + + /* XXX must be set in the following order or MSVC1.52 will crash */ + keyHash = HASH_OBJECT(obj); + key = OBJECT_TO_JSVAL(obj); + return js_AtomizeHashedKey(cx, key, keyHash, flags); +} + +JSAtom * +js_AtomizeBoolean(JSContext *cx, JSBool b, uintN flags) +{ + jsval key; + JSHashNumber keyHash; + + key = BOOLEAN_TO_JSVAL(b); + keyHash = HASH_BOOLEAN(b); + return js_AtomizeHashedKey(cx, key, keyHash, flags); +} + +JSAtom * +js_AtomizeInt(JSContext *cx, jsint i, uintN flags) +{ + jsval key; + JSHashNumber keyHash; + + key = INT_TO_JSVAL(i); + keyHash = HASH_INT(i); + return js_AtomizeHashedKey(cx, key, keyHash, flags); +} + +/* Worst-case alignment grain and aligning macro for 2x-sized buffer. */ +#define ALIGNMENT(t) JS_MAX(JSVAL_ALIGN, sizeof(t)) +#define ALIGN(b,t) ((t*) &(b)[ALIGNMENT(t) - (jsuword)(b) % ALIGNMENT(t)]) + +JSAtom * +js_AtomizeDouble(JSContext *cx, jsdouble d, uintN flags) +{ + jsdouble *dp; + JSHashNumber keyHash; + jsval key; + JSAtomState *state; + JSHashTable *table; + JSHashEntry *he, **hep; + JSAtom *atom; + char buf[2 * ALIGNMENT(double)]; + + dp = ALIGN(buf, double); + *dp = d; + keyHash = HASH_DOUBLE(dp); + key = DOUBLE_TO_JSVAL(dp); + state = &cx->runtime->atomState; + JS_LOCK(&state->lock, cx); + table = state->table; + hep = JS_HashTableRawLookup(table, keyHash, (void *)key); + if ((he = *hep) == NULL) { +#ifdef JS_THREADSAFE + uint32 gen = state->tablegen; +#endif + JS_UNLOCK(&state->lock,cx); + if (!js_NewDoubleValue(cx, d, &key)) + return NULL; + JS_LOCK(&state->lock, cx); +#ifdef JS_THREADSAFE + if (state->tablegen != gen) { + hep = JS_HashTableRawLookup(table, keyHash, (void *)key); + if ((he = *hep) != NULL) { + atom = (JSAtom *)he; + goto out; + } + } +#endif + he = JS_HashTableRawAdd(table, hep, keyHash, (void *)key, NULL); + if (!he) { + JS_ReportOutOfMemory(cx); + atom = NULL; + goto out; + } + } + + atom = (JSAtom *)he; + atom->flags |= flags; + cx->lastAtom = atom; +out: + JS_UNLOCK(&state->lock,cx); + return atom; +} + +JSAtom * +js_AtomizeString(JSContext *cx, JSString *str, uintN flags) +{ + JSHashNumber keyHash; + jsval key; + JSAtomState *state; + JSHashTable *table; + JSHashEntry *he, **hep; + JSAtom *atom; + + keyHash = js_HashString(str); + key = STRING_TO_JSVAL(str); + state = &cx->runtime->atomState; + JS_LOCK(&state->lock, cx); + table = state->table; + hep = JS_HashTableRawLookup(table, keyHash, (void *)key); + if ((he = *hep) == NULL) { +#ifdef JS_THREADSAFE + uint32 gen = state->tablegen; + JS_UNLOCK(&state->lock, cx); +#endif + + if (flags & ATOM_TMPSTR) { + str = (flags & ATOM_NOCOPY) + ? js_NewString(cx, str->chars, str->length, 0) + : js_NewStringCopyN(cx, str->chars, str->length, 0); + if (!str) + return NULL; + key = STRING_TO_JSVAL(str); + } else { + if (!JS_MakeStringImmutable(cx, str)) + return NULL; + } + +#ifdef JS_THREADSAFE + JS_LOCK(&state->lock, cx); + if (state->tablegen != gen) { + hep = JS_HashTableRawLookup(table, keyHash, (void *)key); + if ((he = *hep) != NULL) { + atom = (JSAtom *)he; + if (flags & ATOM_NOCOPY) + str->chars = NULL; + goto out; + } + } +#endif + + he = JS_HashTableRawAdd(table, hep, keyHash, (void *)key, NULL); + if (!he) { + JS_ReportOutOfMemory(cx); + atom = NULL; + goto out; + } + } + + atom = (JSAtom *)he; + atom->flags |= flags & (ATOM_PINNED | ATOM_INTERNED); + cx->lastAtom = atom; +out: + JS_UNLOCK(&state->lock,cx); + return atom; +} + +JS_FRIEND_API(JSAtom *) +js_Atomize(JSContext *cx, const char *bytes, size_t length, uintN flags) +{ + jschar *chars; + JSString *str; + JSAtom *atom; + char buf[2 * ALIGNMENT(JSString)]; + + /* + * Avoiding the malloc in js_InflateString on shorter strings saves us + * over 20,000 malloc calls on mozilla browser startup. This compares to + * only 131 calls where the string is longer than a 31 char (net) buffer. + * The vast majority of atomized strings are already in the hashtable. So + * js_AtomizeString rarely has to copy the temp string we make. + */ +#define ATOMIZE_BUF_MAX 32 + jschar inflated[ATOMIZE_BUF_MAX]; + + if (length < ATOMIZE_BUF_MAX) { + js_InflateStringToBuffer(inflated, bytes, length); + chars = inflated; + } else { + chars = js_InflateString(cx, bytes, length); + if (!chars) + return NULL; + flags |= ATOM_NOCOPY; + } + + str = ALIGN(buf, JSString); + + str->chars = chars; + str->length = length; + atom = js_AtomizeString(cx, str, ATOM_TMPSTR | flags); + if (chars != inflated && (!atom || ATOM_TO_STRING(atom)->chars != chars)) + JS_free(cx, chars); + return atom; +} + +JS_FRIEND_API(JSAtom *) +js_AtomizeChars(JSContext *cx, const jschar *chars, size_t length, uintN flags) +{ + JSString *str; + char buf[2 * ALIGNMENT(JSString)]; + + str = ALIGN(buf, JSString); + str->chars = (jschar *)chars; + str->length = length; + return js_AtomizeString(cx, str, ATOM_TMPSTR | flags); +} + +JSAtom * +js_AtomizeValue(JSContext *cx, jsval value, uintN flags) +{ + if (JSVAL_IS_STRING(value)) + return js_AtomizeString(cx, JSVAL_TO_STRING(value), flags); + if (JSVAL_IS_INT(value)) + return js_AtomizeInt(cx, JSVAL_TO_INT(value), flags); + if (JSVAL_IS_DOUBLE(value)) + return js_AtomizeDouble(cx, *JSVAL_TO_DOUBLE(value), flags); + if (JSVAL_IS_OBJECT(value)) + return js_AtomizeObject(cx, JSVAL_TO_OBJECT(value), flags); + if (JSVAL_IS_BOOLEAN(value)) + return js_AtomizeBoolean(cx, JSVAL_TO_BOOLEAN(value), flags); + return js_AtomizeHashedKey(cx, value, (JSHashNumber)value, flags); +} + +JSAtom * +js_ValueToStringAtom(JSContext *cx, jsval v) +{ + JSString *str; + + str = js_ValueToString(cx, v); + if (!str) + return NULL; + return js_AtomizeString(cx, str, 0); +} + +JS_STATIC_DLL_CALLBACK(JSHashNumber) +js_hash_atom_ptr(const void *key) +{ + const JSAtom *atom = key; + return atom->number; +} + +JS_STATIC_DLL_CALLBACK(void *) +js_alloc_temp_space(void *priv, size_t size) +{ + JSContext *cx = priv; + void *space; + + JS_ARENA_ALLOCATE(space, &cx->tempPool, size); + if (!space) + JS_ReportOutOfMemory(cx); + return space; +} + +JS_STATIC_DLL_CALLBACK(void) +js_free_temp_space(void *priv, void *item) +{ +} + +JS_STATIC_DLL_CALLBACK(JSHashEntry *) +js_alloc_temp_entry(void *priv, const void *key) +{ + JSContext *cx = priv; + JSAtomListElement *ale; + + JS_ARENA_ALLOCATE_TYPE(ale, JSAtomListElement, &cx->tempPool); + if (!ale) { + JS_ReportOutOfMemory(cx); + return NULL; + } + return &ale->entry; +} + +JS_STATIC_DLL_CALLBACK(void) +js_free_temp_entry(void *priv, JSHashEntry *he, uintN flag) +{ +} + +static JSHashAllocOps temp_alloc_ops = { + js_alloc_temp_space, js_free_temp_space, + js_alloc_temp_entry, js_free_temp_entry +}; + +JSAtomListElement * +js_IndexAtom(JSContext *cx, JSAtom *atom, JSAtomList *al) +{ + JSAtomListElement *ale, *ale2, *next; + JSHashEntry **hep; + + ATOM_LIST_LOOKUP(ale, hep, al, atom); + if (!ale) { + if (al->count <= 5) { + /* Few enough for linear search, no hash table needed. */ + JS_ASSERT(!al->table); + ale = (JSAtomListElement *)js_alloc_temp_entry(cx, atom); + if (!ale) + return NULL; + ALE_SET_ATOM(ale, atom); + ALE_SET_NEXT(ale, al->list); + al->list = ale; + } else { + /* We want to hash. Have we already made a hash table? */ + if (!al->table) { + /* No hash table yet, so hep had better be null! */ + JS_ASSERT(!hep); + al->table = JS_NewHashTable(8, js_hash_atom_ptr, + JS_CompareValues, JS_CompareValues, + &temp_alloc_ops, cx); + if (!al->table) + return NULL; + + /* Insert each ale on al->list into the new hash table. */ + for (ale2 = al->list; ale2; ale2 = next) { + next = ALE_NEXT(ale2); + ale2->entry.keyHash = ALE_ATOM(ale2)->number; + hep = JS_HashTableRawLookup(al->table, ale2->entry.keyHash, + ale2->entry.key); + ALE_SET_NEXT(ale2, *hep); + *hep = &ale2->entry; + } + al->list = NULL; + + /* Set hep for insertion of atom's ale, immediately below. */ + hep = JS_HashTableRawLookup(al->table, atom->number, atom); + } + + /* Finally, add an entry for atom into the hash bucket at hep. */ + ale = (JSAtomListElement *) + JS_HashTableRawAdd(al->table, hep, atom->number, atom, NULL); + if (!ale) + return NULL; + } + + ALE_SET_INDEX(ale, al->count++); + } + return ale; +} + +JS_FRIEND_API(JSAtom *) +js_GetAtom(JSContext *cx, JSAtomMap *map, jsatomid i) +{ + JSAtom *atom; + static JSAtom dummy; + + JS_ASSERT(map->vector && i < map->length); + if (!map->vector || i >= map->length) { + char numBuf[12]; + JS_snprintf(numBuf, sizeof numBuf, "%lu", (unsigned long)i); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_ATOMIC_NUMBER, numBuf); + return &dummy; + } + atom = map->vector[i]; + JS_ASSERT(atom); + return atom; +} + +JS_STATIC_DLL_CALLBACK(intN) +js_map_atom(JSHashEntry *he, intN i, void *arg) +{ + JSAtomListElement *ale = (JSAtomListElement *)he; + JSAtom **vector = arg; + + vector[ALE_INDEX(ale)] = ALE_ATOM(ale); + return HT_ENUMERATE_NEXT; +} + +#ifdef DEBUG +jsrefcount js_atom_map_count; +jsrefcount js_atom_map_hash_table_count; +#endif + +JS_FRIEND_API(JSBool) +js_InitAtomMap(JSContext *cx, JSAtomMap *map, JSAtomList *al) +{ + JSAtom **vector; + JSAtomListElement *ale; + uint32 count; + +#ifdef DEBUG + JS_ATOMIC_INCREMENT(&js_atom_map_count); +#endif + ale = al->list; + if (!ale && !al->table) { + map->vector = NULL; + map->length = 0; + return JS_TRUE; + } + + count = al->count; + if (count >= ATOM_INDEX_LIMIT) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_TOO_MANY_LITERALS); + return JS_FALSE; + } + vector = (JSAtom **) JS_malloc(cx, (size_t) count * sizeof *vector); + if (!vector) + return JS_FALSE; + + if (al->table) { +#ifdef DEBUG + JS_ATOMIC_INCREMENT(&js_atom_map_hash_table_count); +#endif + JS_HashTableEnumerateEntries(al->table, js_map_atom, vector); + } else { + do { + vector[ALE_INDEX(ale)] = ALE_ATOM(ale); + } while ((ale = ALE_NEXT(ale)) != NULL); + } + ATOM_LIST_INIT(al); + + map->vector = vector; + map->length = (jsatomid)count; + return JS_TRUE; +} + +JS_FRIEND_API(void) +js_FreeAtomMap(JSContext *cx, JSAtomMap *map) +{ + if (map->vector) { + JS_free(cx, map->vector); + map->vector = NULL; + } + map->length = 0; +} diff --git a/src/dom/js/jsatom.h b/src/dom/js/jsatom.h new file mode 100644 index 000000000..ec9b52f80 --- /dev/null +++ b/src/dom/js/jsatom.h @@ -0,0 +1,409 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsatom_h___ +#define jsatom_h___ +/* + * JS atom table. + */ +#include +#include "jstypes.h" +#include "jshash.h" /* Added by JSIFY */ +#include "jsapi.h" +#include "jsprvtd.h" +#include "jspubtd.h" + +#ifdef JS_THREADSAFE +#include "jslock.h" +#endif + +JS_BEGIN_EXTERN_C + +#define ATOM_PINNED 0x01 /* atom is pinned against GC */ +#define ATOM_INTERNED 0x02 /* pinned variant for JS_Intern* API */ +#define ATOM_MARK 0x04 /* atom is reachable via GC */ +#define ATOM_NOCOPY 0x40 /* don't copy atom string bytes */ +#define ATOM_TMPSTR 0x80 /* internal, to avoid extra string */ + +struct JSAtom { + JSHashEntry entry; /* key is jsval, value keyword info */ + uint32 flags; /* pinned, interned, and mark flags */ + jsatomid number; /* atom serial number and hash code */ +}; + +#define ATOM_KEY(atom) ((jsval)(atom)->entry.key) +#define ATOM_IS_OBJECT(atom) JSVAL_IS_OBJECT(ATOM_KEY(atom)) +#define ATOM_TO_OBJECT(atom) JSVAL_TO_OBJECT(ATOM_KEY(atom)) +#define ATOM_IS_INT(atom) JSVAL_IS_INT(ATOM_KEY(atom)) +#define ATOM_TO_INT(atom) JSVAL_TO_INT(ATOM_KEY(atom)) +#define ATOM_IS_DOUBLE(atom) JSVAL_IS_DOUBLE(ATOM_KEY(atom)) +#define ATOM_TO_DOUBLE(atom) JSVAL_TO_DOUBLE(ATOM_KEY(atom)) +#define ATOM_IS_STRING(atom) JSVAL_IS_STRING(ATOM_KEY(atom)) +#define ATOM_TO_STRING(atom) JSVAL_TO_STRING(ATOM_KEY(atom)) +#define ATOM_IS_BOOLEAN(atom) JSVAL_IS_BOOLEAN(ATOM_KEY(atom)) +#define ATOM_TO_BOOLEAN(atom) JSVAL_TO_BOOLEAN(ATOM_KEY(atom)) + +/* + * Return a printable, lossless char[] representation of a string-type atom. + * The lifetime of the result extends at least until the next GC activation, + * longer if cx's string newborn root is not overwritten. + */ +extern JS_FRIEND_API(const char *) +js_AtomToPrintableString(JSContext *cx, JSAtom *atom); + +#define ATOM_KEYWORD(atom) ((struct keyword *)(atom)->entry.value) +#define ATOM_SET_KEYWORD(atom,kw) ((atom)->entry.value = (kw)) + +struct JSAtomListElement { + JSHashEntry entry; +}; + +#define ALE_ATOM(ale) ((JSAtom *) (ale)->entry.key) +#define ALE_INDEX(ale) ((jsatomid) (ale)->entry.value) +#define ALE_JSOP(ale) ((JSOp) (ale)->entry.value) +#define ALE_NEXT(ale) ((JSAtomListElement *) (ale)->entry.next) + +#define ALE_SET_ATOM(ale,atom) ((ale)->entry.key = (const void *)(atom)) +#define ALE_SET_INDEX(ale,index)((ale)->entry.value = (void *)(index)) +#define ALE_SET_JSOP(ale,op) ((ale)->entry.value = (void *)(op)) +#define ALE_SET_NEXT(ale,link) ((ale)->entry.next = (JSHashEntry *)(link)) + +struct JSAtomList { + JSAtomListElement *list; /* literals indexed for mapping */ + JSHashTable *table; /* hash table if list gets too long */ + jsuint count; /* count of indexed literals */ +}; + +#define ATOM_LIST_INIT(al) ((al)->list = NULL, (al)->table = NULL, \ + (al)->count = 0) + +#define ATOM_LIST_SEARCH(_ale,_al,_atom) \ + JS_BEGIN_MACRO \ + JSHashEntry **_hep; \ + ATOM_LIST_LOOKUP(_ale, _hep, _al, _atom); \ + JS_END_MACRO + +#define ATOM_LIST_LOOKUP(_ale,_hep,_al,_atom) \ + JS_BEGIN_MACRO \ + if ((_al)->table) { \ + _hep = JS_HashTableRawLookup((_al)->table, _atom->number, _atom); \ + _ale = *_hep ? (JSAtomListElement *) *_hep : NULL; \ + } else { \ + JSAtomListElement **_alep = &(_al)->list; \ + _hep = NULL; \ + while ((_ale = *_alep) != NULL) { \ + if (ALE_ATOM(_ale) == (_atom)) { \ + /* Hit, move atom's element to the front of the list. */ \ + *_alep = ALE_NEXT(_ale); \ + ALE_SET_NEXT(_ale, (_al)->list); \ + (_al)->list = _ale; \ + break; \ + } \ + _alep = (JSAtomListElement **)&_ale->entry.next; \ + } \ + } \ + JS_END_MACRO + +struct JSAtomMap { + JSAtom **vector; /* array of ptrs to indexed atoms */ + jsatomid length; /* count of (to-be-)indexed atoms */ +}; + +struct JSAtomState { + JSRuntime *runtime; /* runtime that owns us */ + JSHashTable *table; /* hash table containing all atoms */ + jsatomid number; /* one beyond greatest atom number */ + jsatomid liveAtoms; /* number of live atoms after last GC */ + + /* Type names and value literals. */ + JSAtom *typeAtoms[JSTYPE_LIMIT]; + JSAtom *booleanAtoms[2]; + JSAtom *nullAtom; + + /* Various built-in or commonly-used atoms, pinned on first context. */ + JSAtom *ArgumentsAtom; + JSAtom *ArrayAtom; + JSAtom *BooleanAtom; + JSAtom *CallAtom; + JSAtom *DateAtom; + JSAtom *ErrorAtom; + JSAtom *FunctionAtom; + JSAtom *MathAtom; + JSAtom *NumberAtom; + JSAtom *ObjectAtom; + JSAtom *RegExpAtom; + JSAtom *ScriptAtom; + JSAtom *StringAtom; + JSAtom *anonymousAtom; + JSAtom *argumentsAtom; + JSAtom *arityAtom; + JSAtom *calleeAtom; + JSAtom *callerAtom; + JSAtom *classPrototypeAtom; + JSAtom *constructorAtom; + JSAtom *countAtom; + JSAtom *evalAtom; + JSAtom *getAtom; + JSAtom *getterAtom; + JSAtom *indexAtom; + JSAtom *inputAtom; + JSAtom *lengthAtom; + JSAtom *nameAtom; + JSAtom *noSuchMethodAtom; + JSAtom *parentAtom; + JSAtom *protoAtom; + JSAtom *setAtom; + JSAtom *setterAtom; + JSAtom *toLocaleStringAtom; + JSAtom *toSourceAtom; + JSAtom *toStringAtom; + JSAtom *valueOfAtom; + + /* Less frequently used atoms, pinned lazily by JS_ResolveStandardClass. */ + struct { + JSAtom *EvalErrorAtom; + JSAtom *InfinityAtom; + JSAtom *InternalErrorAtom; + JSAtom *NaNAtom; + JSAtom *RangeErrorAtom; + JSAtom *ReferenceErrorAtom; + JSAtom *SyntaxErrorAtom; + JSAtom *TypeErrorAtom; + JSAtom *URIErrorAtom; + JSAtom *decodeURIAtom; + JSAtom *decodeURIComponentAtom; + JSAtom *defineGetterAtom; + JSAtom *defineSetterAtom; + JSAtom *encodeURIAtom; + JSAtom *encodeURIComponentAtom; + JSAtom *escapeAtom; + JSAtom *hasOwnPropertyAtom; + JSAtom *isFiniteAtom; + JSAtom *isNaNAtom; + JSAtom *isPrototypeOfAtom; + JSAtom *lookupGetterAtom; + JSAtom *lookupSetterAtom; + JSAtom *parseFloatAtom; + JSAtom *parseIntAtom; + JSAtom *propertyIsEnumerableAtom; + JSAtom *unescapeAtom; + JSAtom *unevalAtom; + JSAtom *unwatchAtom; + JSAtom *watchAtom; + } lazy; + +#ifdef JS_THREADSAFE + JSThinLock lock; + volatile uint32 tablegen; +#endif +}; + +/* Well-known predefined strings and their atoms. */ +extern const char *js_type_str[]; +extern const char *js_boolean_str[]; + +extern const char js_Arguments_str[]; +extern const char js_Array_str[]; +extern const char js_Boolean_str[]; +extern const char js_Call_str[]; +extern const char js_Date_str[]; +extern const char js_Function_str[]; +extern const char js_Math_str[]; +extern const char js_Number_str[]; +extern const char js_Object_str[]; +extern const char js_RegExp_str[]; +extern const char js_Script_str[]; +extern const char js_String_str[]; +extern const char js_anonymous_str[]; +extern const char js_arguments_str[]; +extern const char js_arity_str[]; +extern const char js_callee_str[]; +extern const char js_caller_str[]; +extern const char js_class_prototype_str[]; +extern const char js_constructor_str[]; +extern const char js_count_str[]; +extern const char js_eval_str[]; +extern const char js_getter_str[]; +extern const char js_get_str[]; +extern const char js_index_str[]; +extern const char js_input_str[]; +extern const char js_length_str[]; +extern const char js_name_str[]; +extern const char js_noSuchMethod_str[]; +extern const char js_parent_str[]; +extern const char js_proto_str[]; +extern const char js_setter_str[]; +extern const char js_set_str[]; +extern const char js_toSource_str[]; +extern const char js_toString_str[]; +extern const char js_toLocaleString_str[]; +extern const char js_valueOf_str[]; + +/* + * Initialize atom state. Return true on success, false with an out of + * memory error report on failure. + */ +extern JSBool +js_InitAtomState(JSContext *cx, JSAtomState *state); + +/* + * Free and clear atom state (except for any interned string atoms). + */ +extern void +js_FreeAtomState(JSContext *cx, JSAtomState *state); + +/* + * Interned strings are atoms that live until state's runtime is destroyed. + * This function frees all interned string atoms, and then frees and clears + * state's members (just as js_FreeAtomState does), unless there aren't any + * interned strings in state -- in which case state must be "free" already. + * + * NB: js_FreeAtomState is called for each "last" context being destroyed in + * a runtime, where there may yet be another context created in the runtime; + * whereas js_FinishAtomState is called from JS_DestroyRuntime, when we know + * that no more contexts will be created. Thus we minimize garbage during + * context-free episodes on a runtime, while preserving atoms created by the + * JS_Intern*String APIs for the life of the runtime. + */ +extern void +js_FinishAtomState(JSAtomState *state); + +/* + * Atom garbage collection hooks. + */ +typedef void +(*JSGCThingMarker)(void *thing, void *data); + +extern void +js_MarkAtomState(JSAtomState *state, uintN gcflags, JSGCThingMarker mark, + void *data); + +extern void +js_SweepAtomState(JSAtomState *state); + +extern JSBool +js_InitPinnedAtoms(JSContext *cx, JSAtomState *state); + +extern void +js_UnpinPinnedAtoms(JSAtomState *state); + +/* + * Find or create the atom for an object. If we create a new atom, give it the + * type indicated in flags. Return 0 on failure to allocate memory. + */ +extern JSAtom * +js_AtomizeObject(JSContext *cx, JSObject *obj, uintN flags); + +/* + * Find or create the atom for a Boolean value. If we create a new atom, give + * it the type indicated in flags. Return 0 on failure to allocate memory. + */ +extern JSAtom * +js_AtomizeBoolean(JSContext *cx, JSBool b, uintN flags); + +/* + * Find or create the atom for an integer value. If we create a new atom, give + * it the type indicated in flags. Return 0 on failure to allocate memory. + */ +extern JSAtom * +js_AtomizeInt(JSContext *cx, jsint i, uintN flags); + +/* + * Find or create the atom for a double value. If we create a new atom, give + * it the type indicated in flags. Return 0 on failure to allocate memory. + */ +extern JSAtom * +js_AtomizeDouble(JSContext *cx, jsdouble d, uintN flags); + +/* + * Find or create the atom for a string. If we create a new atom, give it the + * type indicated in flags. Return 0 on failure to allocate memory. + */ +extern JSAtom * +js_AtomizeString(JSContext *cx, JSString *str, uintN flags); + +extern JS_FRIEND_API(JSAtom *) +js_Atomize(JSContext *cx, const char *bytes, size_t length, uintN flags); + +extern JS_FRIEND_API(JSAtom *) +js_AtomizeChars(JSContext *cx, const jschar *chars, size_t length, uintN flags); + +/* + * This variant handles all value tag types. + */ +extern JSAtom * +js_AtomizeValue(JSContext *cx, jsval value, uintN flags); + +/* + * Convert v to an atomized string. + */ +extern JSAtom * +js_ValueToStringAtom(JSContext *cx, jsval v); + +/* + * Assign atom an index and insert it on al. + */ +extern JSAtomListElement * +js_IndexAtom(JSContext *cx, JSAtom *atom, JSAtomList *al); + +/* + * Get the atom with index i from map. + */ +extern JS_FRIEND_API(JSAtom *) +js_GetAtom(JSContext *cx, JSAtomMap *map, jsatomid i); + +/* + * For all unmapped atoms recorded in al, add a mapping from the atom's index + * to its address. The GC must not run until all indexed atoms in atomLists + * have been mapped by scripts connected to live objects (Function and Script + * class objects have scripts as/in their private data -- the GC knows about + * these two classes). + */ +extern JS_FRIEND_API(JSBool) +js_InitAtomMap(JSContext *cx, JSAtomMap *map, JSAtomList *al); + +/* + * Free map->vector and clear map. + */ +extern JS_FRIEND_API(void) +js_FreeAtomMap(JSContext *cx, JSAtomMap *map); + +JS_END_EXTERN_C + +#endif /* jsatom_h___ */ diff --git a/src/dom/js/jsautocfg.h b/src/dom/js/jsautocfg.h new file mode 100644 index 000000000..a7f5d6bc8 --- /dev/null +++ b/src/dom/js/jsautocfg.h @@ -0,0 +1,50 @@ +#ifndef js_cpucfg___ +#define js_cpucfg___ + +/* AUTOMATICALLY GENERATED - DO NOT EDIT */ + +#define IS_LITTLE_ENDIAN 1 +#undef IS_BIG_ENDIAN + +#define JS_BYTES_PER_BYTE 1L +#define JS_BYTES_PER_SHORT 2L +#define JS_BYTES_PER_INT 4L +#define JS_BYTES_PER_INT64 8L +#define JS_BYTES_PER_LONG 4L +#define JS_BYTES_PER_FLOAT 4L +#define JS_BYTES_PER_DOUBLE 8L +#define JS_BYTES_PER_WORD 4L +#define JS_BYTES_PER_DWORD 8L + +#define JS_BITS_PER_BYTE 8L +#define JS_BITS_PER_SHORT 16L +#define JS_BITS_PER_INT 32L +#define JS_BITS_PER_INT64 64L +#define JS_BITS_PER_LONG 32L +#define JS_BITS_PER_FLOAT 32L +#define JS_BITS_PER_DOUBLE 64L +#define JS_BITS_PER_WORD 32L + +#define JS_BITS_PER_BYTE_LOG2 3L +#define JS_BITS_PER_SHORT_LOG2 4L +#define JS_BITS_PER_INT_LOG2 5L +#define JS_BITS_PER_INT64_LOG2 6L +#define JS_BITS_PER_LONG_LOG2 5L +#define JS_BITS_PER_FLOAT_LOG2 5L +#define JS_BITS_PER_DOUBLE_LOG2 6L +#define JS_BITS_PER_WORD_LOG2 5L + +#define JS_ALIGN_OF_SHORT 2L +#define JS_ALIGN_OF_INT 4L +#define JS_ALIGN_OF_LONG 4L +#define JS_ALIGN_OF_INT64 4L +#define JS_ALIGN_OF_FLOAT 4L +#define JS_ALIGN_OF_DOUBLE 4L +#define JS_ALIGN_OF_POINTER 4L +#define JS_ALIGN_OF_WORD 4L + +#define JS_BYTES_PER_WORD_LOG2 2L +#define JS_BYTES_PER_DWORD_LOG2 3L +#define JS_WORDS_PER_DWORD_LOG2 1L + +#endif /* js_cpucfg___ */ diff --git a/src/dom/js/jsbit.h b/src/dom/js/jsbit.h new file mode 100644 index 000000000..db41acf9f --- /dev/null +++ b/src/dom/js/jsbit.h @@ -0,0 +1,113 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsbit_h___ +#define jsbit_h___ + +#include "jstypes.h" +JS_BEGIN_EXTERN_C + +/* +** A jsbitmap_t is a long integer that can be used for bitmaps +*/ +typedef JSUword jsbitmap_t; /* NSPR name, a la Unix system types */ +typedef jsbitmap_t jsbitmap; /* JS-style scalar typedef name */ + +#define JS_TEST_BIT(_map,_bit) \ + ((_map)[(_bit)>>JS_BITS_PER_WORD_LOG2] & (1L << ((_bit) & (JS_BITS_PER_WORD-1)))) +#define JS_SET_BIT(_map,_bit) \ + ((_map)[(_bit)>>JS_BITS_PER_WORD_LOG2] |= (1L << ((_bit) & (JS_BITS_PER_WORD-1)))) +#define JS_CLEAR_BIT(_map,_bit) \ + ((_map)[(_bit)>>JS_BITS_PER_WORD_LOG2] &= ~(1L << ((_bit) & (JS_BITS_PER_WORD-1)))) + +/* +** Compute the log of the least power of 2 greater than or equal to n +*/ +extern JS_PUBLIC_API(JSIntn) JS_CeilingLog2(JSUint32 i); + +/* +** Compute the log of the greatest power of 2 less than or equal to n +*/ +extern JS_PUBLIC_API(JSIntn) JS_FloorLog2(JSUint32 i); + +/* +** Macro version of JS_CeilingLog2: Compute the log of the least power of +** 2 greater than or equal to _n. The result is returned in _log2. +*/ +#define JS_CEILING_LOG2(_log2,_n) \ + JS_BEGIN_MACRO \ + JSUint32 j_ = (JSUint32)(_n); \ + (_log2) = 0; \ + if ((j_) & ((j_)-1)) \ + (_log2) += 1; \ + if ((j_) >> 16) \ + (_log2) += 16, (j_) >>= 16; \ + if ((j_) >> 8) \ + (_log2) += 8, (j_) >>= 8; \ + if ((j_) >> 4) \ + (_log2) += 4, (j_) >>= 4; \ + if ((j_) >> 2) \ + (_log2) += 2, (j_) >>= 2; \ + if ((j_) >> 1) \ + (_log2) += 1; \ + JS_END_MACRO + +/* +** Macro version of JS_FloorLog2: Compute the log of the greatest power of +** 2 less than or equal to _n. The result is returned in _log2. +** +** This is equivalent to finding the highest set bit in the word. +*/ +#define JS_FLOOR_LOG2(_log2,_n) \ + JS_BEGIN_MACRO \ + JSUint32 j_ = (JSUint32)(_n); \ + (_log2) = 0; \ + if ((j_) >> 16) \ + (_log2) += 16, (j_) >>= 16; \ + if ((j_) >> 8) \ + (_log2) += 8, (j_) >>= 8; \ + if ((j_) >> 4) \ + (_log2) += 4, (j_) >>= 4; \ + if ((j_) >> 2) \ + (_log2) += 2, (j_) >>= 2; \ + if ((j_) >> 1) \ + (_log2) += 1; \ + JS_END_MACRO + +JS_END_EXTERN_C +#endif /* jsbit_h___ */ diff --git a/src/dom/js/jsbool.c b/src/dom/js/jsbool.c new file mode 100644 index 000000000..05f596556 --- /dev/null +++ b/src/dom/js/jsbool.c @@ -0,0 +1,244 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS boolean implementation. + */ +#include "jsstddef.h" +#include "jstypes.h" +#include "jsutil.h" /* Added by JSIFY */ +#include "jsapi.h" +#include "jsatom.h" +#include "jsbool.h" +#include "jscntxt.h" +#include "jsconfig.h" +#include "jsinterp.h" +#include "jslock.h" +#include "jsnum.h" +#include "jsobj.h" +#include "jsstr.h" + +static JSClass boolean_class = { + "Boolean", + JSCLASS_HAS_PRIVATE, + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS +}; + +#if JS_HAS_TOSOURCE +#include "jsprf.h" + +static JSBool +bool_toSource(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + jsval v; + char buf[32]; + JSString *str; + + if (!JS_InstanceOf(cx, obj, &boolean_class, argv)) + return JS_FALSE; + v = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE); + if (!JSVAL_IS_BOOLEAN(v)) + return js_obj_toSource(cx, obj, argc, argv, rval); + JS_snprintf(buf, sizeof buf, "(new %s(%s))", + boolean_class.name, + js_boolean_str[JSVAL_TO_BOOLEAN(v) ? 1 : 0]); + str = JS_NewStringCopyZ(cx, buf); + if (!str) + return JS_FALSE; + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} +#endif + +static JSBool +bool_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + jsval v; + JSAtom *atom; + JSString *str; + + if (!JS_InstanceOf(cx, obj, &boolean_class, argv)) + return JS_FALSE; + v = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE); + if (!JSVAL_IS_BOOLEAN(v)) + return js_obj_toString(cx, obj, argc, argv, rval); + atom = cx->runtime->atomState.booleanAtoms[JSVAL_TO_BOOLEAN(v) ? 1 : 0]; + str = ATOM_TO_STRING(atom); + if (!str) + return JS_FALSE; + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +static JSBool +bool_valueOf(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + if (!JS_InstanceOf(cx, obj, &boolean_class, argv)) + return JS_FALSE; + *rval = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE); + return JS_TRUE; +} + +static JSFunctionSpec boolean_methods[] = { +#if JS_HAS_TOSOURCE + {js_toSource_str, bool_toSource, 0,0,0}, +#endif + {js_toString_str, bool_toString, 0,0,0}, + {js_valueOf_str, bool_valueOf, 0,0,0}, + {0,0,0,0,0} +}; + +#ifdef XP_MAC +#undef Boolean +#define Boolean js_Boolean +#endif + +static JSBool +Boolean(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSBool b; + jsval bval; + + if (argc != 0) { + if (!js_ValueToBoolean(cx, argv[0], &b)) + return JS_FALSE; + bval = BOOLEAN_TO_JSVAL(b); + } else { + bval = JSVAL_FALSE; + } + if (!(cx->fp->flags & JSFRAME_CONSTRUCTING)) { + *rval = bval; + return JS_TRUE; + } + OBJ_SET_SLOT(cx, obj, JSSLOT_PRIVATE, bval); + return JS_TRUE; +} + +JSObject * +js_InitBooleanClass(JSContext *cx, JSObject *obj) +{ + JSObject *proto; + + proto = JS_InitClass(cx, obj, NULL, &boolean_class, Boolean, 1, + NULL, boolean_methods, NULL, NULL); + if (!proto) + return NULL; + OBJ_SET_SLOT(cx, proto, JSSLOT_PRIVATE, JSVAL_FALSE); + return proto; +} + +JSObject * +js_BooleanToObject(JSContext *cx, JSBool b) +{ + JSObject *obj; + + obj = js_NewObject(cx, &boolean_class, NULL, NULL); + if (!obj) + return NULL; + OBJ_SET_SLOT(cx, obj, JSSLOT_PRIVATE, BOOLEAN_TO_JSVAL(b)); + return obj; +} + +JSString * +js_BooleanToString(JSContext *cx, JSBool b) +{ + return ATOM_TO_STRING(cx->runtime->atomState.booleanAtoms[b ? 1 : 0]); +} + +JSBool +js_ValueToBoolean(JSContext *cx, jsval v, JSBool *bp) +{ + JSBool b; + jsdouble d; + +#if defined _MSC_VER && _MSC_VER <= 800 + /* MSVC1.5 coredumps */ + if (!bp) + return JS_TRUE; + /* This should be an if-else chain, but MSVC1.5 crashes if it is. */ +#define ELSE +#else +#define ELSE else +#endif + + if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v)) { + /* Must return early to avoid falling thru to JSVAL_IS_OBJECT case. */ + *bp = JS_FALSE; + return JS_TRUE; + } + if (JSVAL_IS_OBJECT(v)) { + if (!JSVERSION_IS_ECMA(cx->version)) { + if (!OBJ_DEFAULT_VALUE(cx, JSVAL_TO_OBJECT(v), JSTYPE_BOOLEAN, &v)) + return JS_FALSE; + if (!JSVAL_IS_BOOLEAN(v)) + v = JSVAL_TRUE; /* non-null object is true */ + b = JSVAL_TO_BOOLEAN(v); + } else { + b = JS_TRUE; + } + } ELSE + if (JSVAL_IS_STRING(v)) { + b = JSSTRING_LENGTH(JSVAL_TO_STRING(v)) ? JS_TRUE : JS_FALSE; + } ELSE + if (JSVAL_IS_INT(v)) { + b = JSVAL_TO_INT(v) ? JS_TRUE : JS_FALSE; + } ELSE + if (JSVAL_IS_DOUBLE(v)) { + d = *JSVAL_TO_DOUBLE(v); + b = (!JSDOUBLE_IS_NaN(d) && d != 0) ? JS_TRUE : JS_FALSE; + } ELSE +#if defined _MSC_VER && _MSC_VER <= 800 + if (JSVAL_IS_BOOLEAN(v)) { + b = JSVAL_TO_BOOLEAN(v); + } +#else + { + JS_ASSERT(JSVAL_IS_BOOLEAN(v)); + b = JSVAL_TO_BOOLEAN(v); + } +#endif + +#undef ELSE + *bp = b; + return JS_TRUE; +} diff --git a/src/dom/js/jsbool.h b/src/dom/js/jsbool.h new file mode 100644 index 000000000..c07910f59 --- /dev/null +++ b/src/dom/js/jsbool.h @@ -0,0 +1,62 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsbool_h___ +#define jsbool_h___ +/* + * JS boolean interface. + */ + +JS_BEGIN_EXTERN_C + +extern JSObject * +js_InitBooleanClass(JSContext *cx, JSObject *obj); + +extern JSObject * +js_BooleanToObject(JSContext *cx, JSBool b); + +extern JSString * +js_BooleanToString(JSContext *cx, JSBool b); + +extern JSBool +js_ValueToBoolean(JSContext *cx, jsval v, JSBool *bp); + +JS_END_EXTERN_C + +#endif /* jsbool_h___ */ diff --git a/src/dom/js/jsclist.h b/src/dom/js/jsclist.h new file mode 100644 index 000000000..2eafe8e40 --- /dev/null +++ b/src/dom/js/jsclist.h @@ -0,0 +1,139 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsclist_h___ +#define jsclist_h___ + +#include "jstypes.h" + +/* +** Circular linked list +*/ +typedef struct JSCListStr { + struct JSCListStr *next; + struct JSCListStr *prev; +} JSCList; + +/* +** Insert element "_e" into the list, before "_l". +*/ +#define JS_INSERT_BEFORE(_e,_l) \ + JS_BEGIN_MACRO \ + (_e)->next = (_l); \ + (_e)->prev = (_l)->prev; \ + (_l)->prev->next = (_e); \ + (_l)->prev = (_e); \ + JS_END_MACRO + +/* +** Insert element "_e" into the list, after "_l". +*/ +#define JS_INSERT_AFTER(_e,_l) \ + JS_BEGIN_MACRO \ + (_e)->next = (_l)->next; \ + (_e)->prev = (_l); \ + (_l)->next->prev = (_e); \ + (_l)->next = (_e); \ + JS_END_MACRO + +/* +** Return the element following element "_e" +*/ +#define JS_NEXT_LINK(_e) \ + ((_e)->next) +/* +** Return the element preceding element "_e" +*/ +#define JS_PREV_LINK(_e) \ + ((_e)->prev) + +/* +** Append an element "_e" to the end of the list "_l" +*/ +#define JS_APPEND_LINK(_e,_l) JS_INSERT_BEFORE(_e,_l) + +/* +** Insert an element "_e" at the head of the list "_l" +*/ +#define JS_INSERT_LINK(_e,_l) JS_INSERT_AFTER(_e,_l) + +/* Return the head/tail of the list */ +#define JS_LIST_HEAD(_l) (_l)->next +#define JS_LIST_TAIL(_l) (_l)->prev + +/* +** Remove the element "_e" from it's circular list. +*/ +#define JS_REMOVE_LINK(_e) \ + JS_BEGIN_MACRO \ + (_e)->prev->next = (_e)->next; \ + (_e)->next->prev = (_e)->prev; \ + JS_END_MACRO + +/* +** Remove the element "_e" from it's circular list. Also initializes the +** linkage. +*/ +#define JS_REMOVE_AND_INIT_LINK(_e) \ + JS_BEGIN_MACRO \ + (_e)->prev->next = (_e)->next; \ + (_e)->next->prev = (_e)->prev; \ + (_e)->next = (_e); \ + (_e)->prev = (_e); \ + JS_END_MACRO + +/* +** Return non-zero if the given circular list "_l" is empty, zero if the +** circular list is not empty +*/ +#define JS_CLIST_IS_EMPTY(_l) \ + ((_l)->next == (_l)) + +/* +** Initialize a circular list +*/ +#define JS_INIT_CLIST(_l) \ + JS_BEGIN_MACRO \ + (_l)->next = (_l); \ + (_l)->prev = (_l); \ + JS_END_MACRO + +#define JS_INIT_STATIC_CLIST(_l) \ + {(_l), (_l)} + +#endif /* jsclist_h___ */ diff --git a/src/dom/js/jscntxt.c b/src/dom/js/jscntxt.c new file mode 100644 index 000000000..85496b89b --- /dev/null +++ b/src/dom/js/jscntxt.c @@ -0,0 +1,702 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS execution context. + */ +#include "jsstddef.h" +#include +#include +#include +#include "jstypes.h" +#include "jsarena.h" /* Added by JSIFY */ +#include "jsutil.h" /* Added by JSIFY */ +#include "jsclist.h" +#include "jsprf.h" +#include "jsatom.h" +#include "jscntxt.h" +#include "jsconfig.h" +#include "jsdbgapi.h" +#include "jsexn.h" +#include "jsgc.h" +#include "jslock.h" +#include "jsnum.h" +#include "jsobj.h" +#include "jsopcode.h" +#include "jsscan.h" +#include "jsscript.h" +#include "jsstr.h" + +JSContext * +js_NewContext(JSRuntime *rt, size_t stackChunkSize) +{ + JSContext *cx; + JSBool ok, first; + + cx = (JSContext *) malloc(sizeof *cx); + if (!cx) + return NULL; + memset(cx, 0, sizeof *cx); + + cx->runtime = rt; +#if JS_STACK_GROWTH_DIRECTION > 0 + cx->stackLimit = (jsuword)-1; +#endif +#ifdef JS_THREADSAFE + js_InitContextForLocking(cx); +#endif + + JS_LOCK_GC(rt); + for (;;) { + first = (rt->contextList.next == &rt->contextList); + if (rt->state == JSRTS_UP) { + JS_ASSERT(!first); + break; + } + if (rt->state == JSRTS_DOWN) { + JS_ASSERT(first); + rt->state = JSRTS_LAUNCHING; + break; + } + JS_WAIT_CONDVAR(rt->stateChange, JS_NO_TIMEOUT); + } + JS_APPEND_LINK(&cx->links, &rt->contextList); + JS_UNLOCK_GC(rt); + + /* + * First we do the infallible, every-time per-context initializations. + * Should a later, fallible initialization (js_InitRegExpStatics, e.g., + * or the stuff under 'if (first)' below) fail, at least the version + * and arena-pools will be valid and safe to use (say, from the last GC + * done by js_DestroyContext). + */ + cx->version = JSVERSION_DEFAULT; + cx->jsop_eq = JSOP_EQ; + cx->jsop_ne = JSOP_NE; + JS_InitArenaPool(&cx->stackPool, "stack", stackChunkSize, sizeof(jsval)); + JS_InitArenaPool(&cx->tempPool, "temp", 1024, sizeof(jsdouble)); + +#if JS_HAS_REGEXPS + if (!js_InitRegExpStatics(cx, &cx->regExpStatics)) { + js_DestroyContext(cx, JS_NO_GC); + return NULL; + } +#endif +#if JS_HAS_EXCEPTIONS + cx->throwing = JS_FALSE; +#endif + + /* + * If cx is the first context on this runtime, initialize well-known atoms, + * keywords, numbers, and strings. If one of these steps should fail, the + * runtime will be left in a partially initialized state, with zeroes and + * nulls stored in the default-initialized remainder of the struct. We'll + * clean the runtime up under js_DestroyContext, because cx will be "last" + * as well as "first". + */ + if (first) { + ok = (rt->atomState.liveAtoms == 0) + ? js_InitAtomState(cx, &rt->atomState) + : js_InitPinnedAtoms(cx, &rt->atomState); + if (ok) + ok = js_InitScanner(cx); + if (ok) + ok = js_InitRuntimeNumberState(cx); + if (ok) + ok = js_InitRuntimeStringState(cx); + if (!ok) { + js_DestroyContext(cx, JS_NO_GC); + return NULL; + } + + JS_LOCK_GC(rt); + rt->state = JSRTS_UP; + JS_NOTIFY_ALL_CONDVAR(rt->stateChange); + JS_UNLOCK_GC(rt); + } + + return cx; +} + +void +js_DestroyContext(JSContext *cx, JSGCMode gcmode) +{ + JSRuntime *rt; + JSBool last; + JSArgumentFormatMap *map; + + rt = cx->runtime; + + /* Remove cx from context list first. */ + JS_LOCK_GC(rt); + JS_ASSERT(rt->state == JSRTS_UP || rt->state == JSRTS_LAUNCHING); + JS_REMOVE_LINK(&cx->links); + last = (rt->contextList.next == &rt->contextList); + if (last) + rt->state = JSRTS_LANDING; + JS_UNLOCK_GC(rt); + + if (last) { +#ifdef JS_THREADSAFE + /* + * If cx is not in a request already, begin one now so that we wait + * for any racing GC started on a not-last context to finish, before + * we plow ahead and unpin atoms. Note that even though we begin a + * request here if necessary, we end all requests on cx below before + * forcing a final GC. This lets any not-last context destruction + * racing in another thread try to force or maybe run the GC, but by + * that point, rt->state will not be JSRTS_UP, and that GC attempt + * will return early. + */ + if (cx->requestDepth == 0) + JS_BeginRequest(cx); +#endif + + /* Unpin all pinned atoms before final GC. */ + js_UnpinPinnedAtoms(&rt->atomState); + + /* Unlock and clear GC things held by runtime pointers. */ + js_FinishRuntimeNumberState(cx); + js_FinishRuntimeStringState(cx); + + /* Clear debugging state to remove GC roots. */ + JS_ClearAllTraps(cx); + JS_ClearAllWatchPoints(cx); + } + +#if JS_HAS_REGEXPS + /* + * Remove more GC roots in regExpStatics, then collect garbage. + * XXX anti-modularity alert: we rely on the call to js_RemoveRoot within + * XXX this function call to wait for any racing GC to complete, in the + * XXX case where JS_DestroyContext is called outside of a request on cx + */ + js_FreeRegExpStatics(cx, &cx->regExpStatics); +#endif + +#ifdef JS_THREADSAFE + /* + * Destroying a context implicitly calls JS_EndRequest(). Also, we must + * end our request here in case we are "last" -- in that event, another + * js_DestroyContext that was not last might be waiting in the GC for our + * request to end. We'll let it run below, just before we do the truly + * final GC and then free atom state. + * + * At this point, cx must be inaccessible to other threads. It's off the + * rt->contextList, and it should not be reachable via any object private + * data structure. + */ + while (cx->requestDepth != 0) + JS_EndRequest(cx); +#endif + + if (last) { + /* Always force, so we wait for any racing GC to finish. */ + js_ForceGC(cx, GC_LAST_CONTEXT); + + /* Iterate until no finalizer removes a GC root or lock. */ + while (rt->gcPoke) + js_GC(cx, GC_LAST_CONTEXT); + + /* Try to free atom state, now that no unrooted scripts survive. */ + if (rt->atomState.liveAtoms == 0) + js_FreeAtomState(cx, &rt->atomState); + + /* Take the runtime down, now that it has no contexts or atoms. */ + JS_LOCK_GC(rt); + rt->state = JSRTS_DOWN; + JS_NOTIFY_ALL_CONDVAR(rt->stateChange); + JS_UNLOCK_GC(rt); + } else { + if (gcmode == JS_FORCE_GC) + js_ForceGC(cx, 0); + else if (gcmode == JS_MAYBE_GC) + JS_MaybeGC(cx); + } + + /* Free the stuff hanging off of cx. */ + JS_FinishArenaPool(&cx->stackPool); + JS_FinishArenaPool(&cx->tempPool); + if (cx->lastMessage) + free(cx->lastMessage); + + /* Remove any argument formatters. */ + map = cx->argumentFormatMap; + while (map) { + JSArgumentFormatMap *temp = map; + map = map->next; + JS_free(cx, temp); + } + + /* Destroy the resolve recursion damper. */ + if (cx->resolvingTable) { + JS_DHashTableDestroy(cx->resolvingTable); + cx->resolvingTable = NULL; + } + + /* Finally, free cx itself. */ + free(cx); +} + +JSBool +js_ValidContextPointer(JSRuntime *rt, JSContext *cx) +{ + JSCList *cl; + + for (cl = rt->contextList.next; cl != &rt->contextList; cl = cl->next) { + if (cl == &cx->links) + return JS_TRUE; + } + JS_RUNTIME_METER(rt, deadContexts); + return JS_FALSE; +} + +JSContext * +js_ContextIterator(JSRuntime *rt, JSBool unlocked, JSContext **iterp) +{ + JSContext *cx = *iterp; + + if (unlocked) + JS_LOCK_GC(rt); + if (!cx) + cx = (JSContext *)&rt->contextList; + cx = (JSContext *)cx->links.next; + if (&cx->links == &rt->contextList) + cx = NULL; + *iterp = cx; + if (unlocked) + JS_UNLOCK_GC(rt); + return cx; +} + +static void +ReportError(JSContext *cx, const char *message, JSErrorReport *reportp) +{ + /* + * Check the error report, and set a JavaScript-catchable exception + * if the error is defined to have an associated exception. If an + * exception is thrown, then the JSREPORT_EXCEPTION flag will be set + * on the error report, and exception-aware hosts should ignore it. + */ + if (reportp && reportp->errorNumber == JSMSG_UNCAUGHT_EXCEPTION) + reportp->flags |= JSREPORT_EXCEPTION; + +#if JS_HAS_ERROR_EXCEPTIONS + /* + * Call the error reporter only if an exception wasn't raised. + * + * If an exception was raised, then we call the debugErrorHook + * (if present) to give it a chance to see the error before it + * propagates out of scope. This is needed for compatability + * with the old scheme. + */ + if (!js_ErrorToException(cx, message, reportp)) { + js_ReportErrorAgain(cx, message, reportp); + } else if (cx->runtime->debugErrorHook && cx->errorReporter) { + JSDebugErrorHook hook = cx->runtime->debugErrorHook; + /* test local in case debugErrorHook changed on another thread */ + if (hook) + hook(cx, message, reportp, cx->runtime->debugErrorHookData); + } +#else + js_ReportErrorAgain(cx, message, reportp); +#endif +} + +/* + * We don't post an exception in this case, since doing so runs into + * complications of pre-allocating an exception object which required + * running the Exception class initializer early etc. + * Instead we just invoke the errorReporter with an "Out Of Memory" + * type message, and then hope the process ends swiftly. + */ +void +js_ReportOutOfMemory(JSContext *cx, JSErrorCallback callback) +{ + JSStackFrame *fp; + JSErrorReport report; + JSErrorReporter onError = cx->errorReporter; + + /* Get the message for this error, but we won't expand any arguments. */ + const JSErrorFormatString *efs = callback(NULL, NULL, JSMSG_OUT_OF_MEMORY); + const char *msg = efs ? efs->format : "Out of memory"; + + /* Fill out the report, but don't do anything that requires allocation. */ + memset(&report, 0, sizeof (struct JSErrorReport)); + report.flags = JSREPORT_ERROR; + report.errorNumber = JSMSG_OUT_OF_MEMORY; + + /* + * Walk stack until we find a frame that is associated with some script + * rather than a native frame. + */ + for (fp = cx->fp; fp; fp = fp->down) { + if (fp->script && fp->pc) { + report.filename = fp->script->filename; + report.lineno = js_PCToLineNumber(cx, fp->script, fp->pc); + break; + } + } + + /* + * If debugErrorHook is present then we give it a chance to veto + * sending the error on to the regular ErrorReporter. + */ + if (onError) { + JSDebugErrorHook hook = cx->runtime->debugErrorHook; + if (hook && + !hook(cx, msg, &report, cx->runtime->debugErrorHookData)) { + onError = NULL; + } + } + + if (onError) + onError(cx, msg, &report); +} + +JSBool +js_ReportErrorVA(JSContext *cx, uintN flags, const char *format, va_list ap) +{ + char *last; + JSStackFrame *fp; + JSErrorReport report; + JSBool warning; + + if ((flags & JSREPORT_STRICT) && !JS_HAS_STRICT_OPTION(cx)) + return JS_TRUE; + + last = JS_vsmprintf(format, ap); + if (!last) + return JS_FALSE; + + memset(&report, 0, sizeof (struct JSErrorReport)); + report.flags = flags; + + /* Find the top-most active script frame, for best line number blame. */ + for (fp = cx->fp; fp; fp = fp->down) { + if (fp->script && fp->pc) { + report.filename = fp->script->filename; + report.lineno = js_PCToLineNumber(cx, fp->script, fp->pc); + break; + } + } + + warning = JSREPORT_IS_WARNING(report.flags); + if (warning && JS_HAS_WERROR_OPTION(cx)) { + report.flags &= ~JSREPORT_WARNING; + warning = JS_FALSE; + } + + ReportError(cx, last, &report); + free(last); + return warning; +} + +/* + * The arguments from ap need to be packaged up into an array and stored + * into the report struct. + * + * The format string addressed by the error number may contain operands + * identified by the format {N}, where N is a decimal digit. Each of these + * is to be replaced by the Nth argument from the va_list. The complete + * message is placed into reportp->ucmessage converted to a JSString. + * + * Returns true if the expansion succeeds (can fail if out of memory). + */ +JSBool +js_ExpandErrorArguments(JSContext *cx, JSErrorCallback callback, + void *userRef, const uintN errorNumber, + char **messagep, JSErrorReport *reportp, + JSBool *warningp, JSBool charArgs, va_list ap) +{ + const JSErrorFormatString *efs; + int i; + int argCount; + + *warningp = JSREPORT_IS_WARNING(reportp->flags); + if (*warningp && JS_HAS_WERROR_OPTION(cx)) { + reportp->flags &= ~JSREPORT_WARNING; + *warningp = JS_FALSE; + } + + *messagep = NULL; + if (callback) { + efs = callback(userRef, NULL, errorNumber); + if (efs) { + size_t totalArgsLength = 0; + size_t argLengths[10]; /* only {0} thru {9} supported */ + argCount = efs->argCount; + JS_ASSERT(argCount <= 10); + if (argCount > 0) { + /* + * Gather the arguments into an array, and accumulate + * their sizes. We allocate 1 more than necessary and + * null it out to act as the caboose when we free the + * pointers later. + */ + reportp->messageArgs = (const jschar **) + JS_malloc(cx, sizeof(jschar *) * (argCount + 1)); + if (!reportp->messageArgs) + return JS_FALSE; + reportp->messageArgs[argCount] = NULL; + for (i = 0; i < argCount; i++) { + if (charArgs) { + char *charArg = va_arg(ap, char *); + reportp->messageArgs[i] + = js_InflateString(cx, charArg, strlen(charArg)); + if (!reportp->messageArgs[i]) + goto error; + } + else + reportp->messageArgs[i] = va_arg(ap, jschar *); + argLengths[i] = js_strlen(reportp->messageArgs[i]); + totalArgsLength += argLengths[i]; + } + /* NULL-terminate for easy copying. */ + reportp->messageArgs[i] = NULL; + } + /* + * Parse the error format, substituting the argument X + * for {X} in the format. + */ + if (argCount > 0) { + if (efs->format) { + const char *fmt; + const jschar *arg; + jschar *out; + int expandedArgs = 0; + size_t expandedLength + = strlen(efs->format) + - (3 * argCount) /* exclude the {n} */ + + totalArgsLength; + /* + * Note - the above calculation assumes that each argument + * is used once and only once in the expansion !!! + */ + reportp->ucmessage = out = (jschar *) + JS_malloc(cx, (expandedLength + 1) * sizeof(jschar)); + if (!out) + goto error; + fmt = efs->format; + while (*fmt) { + if (*fmt == '{') { + if (isdigit(fmt[1])) { + int d = JS7_UNDEC(fmt[1]); + JS_ASSERT(expandedArgs < argCount); + arg = reportp->messageArgs[d]; + js_strncpy(out, arg, argLengths[d]); + out += argLengths[d]; + fmt += 3; + expandedArgs++; + continue; + } + } + /* + * is this kosher? + */ + *out++ = (unsigned char)(*fmt++); + } + JS_ASSERT(expandedArgs == argCount); + *out = 0; + *messagep = + js_DeflateString(cx, reportp->ucmessage, + (size_t)(out - reportp->ucmessage)); + if (!*messagep) + goto error; + } + } else { + /* + * Zero arguments: the format string (if it exists) is the + * entire message. + */ + if (efs->format) { + *messagep = JS_strdup(cx, efs->format); + if (!*messagep) + goto error; + reportp->ucmessage + = js_InflateString(cx, *messagep, strlen(*messagep)); + if (!reportp->ucmessage) + goto error; + } + } + } + } + if (*messagep == NULL) { + /* where's the right place for this ??? */ + const char *defaultErrorMessage + = "No error message available for error number %d"; + size_t nbytes = strlen(defaultErrorMessage) + 16; + *messagep = (char *)JS_malloc(cx, nbytes); + if (!*messagep) + goto error; + JS_snprintf(*messagep, nbytes, defaultErrorMessage, errorNumber); + } + return JS_TRUE; + +error: + if (reportp->messageArgs) { + i = 0; + while (reportp->messageArgs[i]) + JS_free(cx, (void *)reportp->messageArgs[i++]); + JS_free(cx, (void *)reportp->messageArgs); + reportp->messageArgs = NULL; + } + if (reportp->ucmessage) { + JS_free(cx, (void *)reportp->ucmessage); + reportp->ucmessage = NULL; + } + if (*messagep) { + JS_free(cx, (void *)*messagep); + *messagep = NULL; + } + return JS_FALSE; +} + +JSBool +js_ReportErrorNumberVA(JSContext *cx, uintN flags, JSErrorCallback callback, + void *userRef, const uintN errorNumber, + JSBool charArgs, va_list ap) +{ + JSStackFrame *fp; + JSErrorReport report; + char *message; + JSBool warning; + + if ((flags & JSREPORT_STRICT) && !JS_HAS_STRICT_OPTION(cx)) + return JS_TRUE; + + memset(&report, 0, sizeof (struct JSErrorReport)); + report.flags = flags; + report.errorNumber = errorNumber; + + /* + * If we can't find out where the error was based on the current frame, + * see if the next frame has a script/pc combo we can use. + */ + for (fp = cx->fp; fp; fp = fp->down) { + if (fp->script && fp->pc) { + report.filename = fp->script->filename; + report.lineno = js_PCToLineNumber(cx, fp->script, fp->pc); + break; + } + } + + if (!js_ExpandErrorArguments(cx, callback, userRef, errorNumber, + &message, &report, &warning, charArgs, ap)) { + return JS_FALSE; + } + + ReportError(cx, message, &report); + + if (message) + JS_free(cx, message); + if (report.messageArgs) { + int i = 0; + while (report.messageArgs[i]) + JS_free(cx, (void *)report.messageArgs[i++]); + JS_free(cx, (void *)report.messageArgs); + } + if (report.ucmessage) + JS_free(cx, (void *)report.ucmessage); + + return warning; +} + +JS_FRIEND_API(void) +js_ReportErrorAgain(JSContext *cx, const char *message, JSErrorReport *reportp) +{ + JSErrorReporter onError; + + if (!message) + return; + + if (cx->lastMessage) + free(cx->lastMessage); + cx->lastMessage = JS_strdup(cx, message); + if (!cx->lastMessage) + return; + onError = cx->errorReporter; + + /* + * If debugErrorHook is present then we give it a chance to veto + * sending the error on to the regular ErrorReporter. + */ + if (onError) { + JSDebugErrorHook hook = cx->runtime->debugErrorHook; + if (hook && + !hook(cx, cx->lastMessage, reportp, + cx->runtime->debugErrorHookData)) { + onError = NULL; + } + } + if (onError) + onError(cx, cx->lastMessage, reportp); +} + +void +js_ReportIsNotDefined(JSContext *cx, const char *name) +{ + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NOT_DEFINED, name); +} + +#if defined DEBUG && defined XP_UNIX +/* For gdb usage. */ +void js_traceon(JSContext *cx) { cx->tracefp = stderr; } +void js_traceoff(JSContext *cx) { cx->tracefp = NULL; } +#endif + +JSErrorFormatString js_ErrorFormatString[JSErr_Limit] = { +#if JS_HAS_DFLT_MSG_STRINGS +#define MSG_DEF(name, number, count, exception, format) \ + { format, count } , +#else +#define MSG_DEF(name, number, count, exception, format) \ + { NULL, count } , +#endif +#include "js.msg" +#undef MSG_DEF +}; + +const JSErrorFormatString * +js_GetErrorMessage(void *userRef, const char *locale, const uintN errorNumber) +{ + if ((errorNumber > 0) && (errorNumber < JSErr_Limit)) + return &js_ErrorFormatString[errorNumber]; + return NULL; +} diff --git a/src/dom/js/jscntxt.h b/src/dom/js/jscntxt.h new file mode 100644 index 000000000..f9aab41a8 --- /dev/null +++ b/src/dom/js/jscntxt.h @@ -0,0 +1,496 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jscntxt_h___ +#define jscntxt_h___ +/* + * JS execution context. + */ +#include "jsarena.h" /* Added by JSIFY */ +#include "jsclist.h" +#include "jslong.h" +#include "jsatom.h" +#include "jsconfig.h" +#include "jsdhash.h" +#include "jsgc.h" +#include "jsinterp.h" +#include "jsobj.h" +#include "jsprvtd.h" +#include "jspubtd.h" +#include "jsregexp.h" + +JS_BEGIN_EXTERN_C + +typedef enum JSGCMode { JS_NO_GC, JS_MAYBE_GC, JS_FORCE_GC } JSGCMode; + +typedef enum JSRuntimeState { + JSRTS_DOWN, + JSRTS_LAUNCHING, + JSRTS_UP, + JSRTS_LANDING +} JSRuntimeState; + +typedef struct JSPropertyTreeEntry { + JSDHashEntryHdr hdr; + JSScopeProperty *child; +} JSPropertyTreeEntry; + +struct JSRuntime { + /* Runtime state, synchronized by the stateChange/gcLock condvar/lock. */ + JSRuntimeState state; + + /* Garbage collector state, used by jsgc.c. */ + JSArenaPool gcArenaPool; + JSDHashTable gcRootsHash; + JSDHashTable *gcLocksHash; + JSGCThing *gcFreeList; + jsrefcount gcKeepAtoms; + uint32 gcBytes; + uint32 gcLastBytes; + uint32 gcMaxBytes; + uint32 gcLevel; + uint32 gcNumber; + JSPackedBool gcPoke; + JSPackedBool gcRunning; + JSGCCallback gcCallback; + uint32 gcMallocBytes; +#ifdef JS_GCMETER + JSGCStats gcStats; +#endif + + /* Literal table maintained by jsatom.c functions. */ + JSAtomState atomState; + + /* Random number generator state, used by jsmath.c. */ + JSBool rngInitialized; + int64 rngMultiplier; + int64 rngAddend; + int64 rngMask; + int64 rngSeed; + jsdouble rngDscale; + + /* Well-known numbers held for use by this runtime's contexts. */ + jsdouble *jsNaN; + jsdouble *jsNegativeInfinity; + jsdouble *jsPositiveInfinity; + + /* Empty string held for use by this runtime's contexts. */ + JSString *emptyString; + + /* List of active contexts sharing this runtime; protected by gcLock. */ + JSCList contextList; + + /* These are used for debugging -- see jsprvtd.h and jsdbgapi.h. */ + JSTrapHandler interruptHandler; + void *interruptHandlerData; + JSNewScriptHook newScriptHook; + void *newScriptHookData; + JSDestroyScriptHook destroyScriptHook; + void *destroyScriptHookData; + JSTrapHandler debuggerHandler; + void *debuggerHandlerData; + JSSourceHandler sourceHandler; + void *sourceHandlerData; + JSInterpreterHook executeHook; + void *executeHookData; + JSInterpreterHook callHook; + void *callHookData; + JSObjectHook objectHook; + void *objectHookData; + JSTrapHandler throwHook; + void *throwHookData; + JSDebugErrorHook debugErrorHook; + void *debugErrorHookData; + + /* More debugging state, see jsdbgapi.c. */ + JSCList trapList; + JSCList watchPointList; + + /* Weak links to properties, indexed by quickened get/set opcodes. */ + /* XXX must come after JSCLists or MSVC alignment bug bites empty lists */ + JSPropertyCache propertyCache; + + /* Client opaque pointer */ + void *data; + +#ifdef JS_THREADSAFE + /* These combine to interlock the GC and new requests. */ + PRLock *gcLock; + PRCondVar *gcDone; + PRCondVar *requestDone; + uint32 requestCount; + jsword gcThread; + + /* Lock and owning thread pointer for JS_LOCK_RUNTIME. */ + PRLock *rtLock; +#ifdef DEBUG + jsword rtLockOwner; +#endif + + /* Used to synchronize down/up state change; protected by gcLock. */ + PRCondVar *stateChange; + + /* Used to serialize cycle checks when setting __proto__ or __parent__. */ + PRLock *setSlotLock; + PRCondVar *setSlotDone; + JSBool setSlotBusy; + JSScope *setSlotScope; /* deadlock avoidance, see jslock.c */ + + /* + * State for sharing single-threaded scopes, once a second thread tries to + * lock a scope. The scopeSharingDone condvar is protected by rt->gcLock, + * to minimize number of locks taken in JS_EndRequest. + * + * The scopeSharingTodo linked list is likewise "global" per runtime, not + * one-list-per-context, to conserve space over all contexts, optimizing + * for the likely case that scopes become shared rarely, and among a very + * small set of threads (contexts). + */ + PRCondVar *scopeSharingDone; + JSScope *scopeSharingTodo; + +/* + * Magic terminator for the rt->scopeSharingTodo linked list, threaded through + * scope->u.link. This hack allows us to test whether a scope is on the list + * by asking whether scope->u.link is non-null. We use a large, likely bogus + * pointer here to distinguish this value from any valid u.count (small int) + * value. + */ +#define NO_SCOPE_SHARING_TODO ((JSScope *) 0xfeedbeef) +#endif /* JS_THREADSAFE */ + + /* + * Check property accessibility for objects of arbitrary class. Used at + * present to check f.caller accessibility for any function object f. + */ + JSCheckAccessOp checkObjectAccess; + + /* Security principals serialization support. */ + JSPrincipalsTranscoder principalsTranscoder; + + /* Shared scope property tree, and allocator for its nodes. */ + JSDHashTable propertyTreeHash; + JSScopeProperty *propertyFreeList; + JSArenaPool propertyArenaPool; + +#ifdef DEBUG + /* Function invocation metering. */ + jsrefcount inlineCalls; + jsrefcount nativeCalls; + jsrefcount nonInlineCalls; + jsrefcount constructs; + + /* Scope lock and property metering. */ + jsrefcount claimAttempts; + jsrefcount claimedScopes; + jsrefcount deadContexts; + jsrefcount deadlocksAvoided; + jsrefcount liveScopes; + jsrefcount sharedScopes; + jsrefcount totalScopes; + jsrefcount badUndependStrings; + jsrefcount liveScopeProps; + jsrefcount totalScopeProps; + jsrefcount livePropTreeNodes; + jsrefcount duplicatePropTreeNodes; + jsrefcount totalPropTreeNodes; + jsrefcount propTreeKidsChunks; + jsrefcount middleDeleteFixups; + + /* String instrumentation. */ + jsrefcount liveStrings; + jsrefcount totalStrings; + jsrefcount liveDependentStrings; + jsrefcount totalDependentStrings; + double lengthSum; + double lengthSquaredSum; + double strdepLengthSum; + double strdepLengthSquaredSum; +#endif +}; + +#ifdef DEBUG +# define JS_RUNTIME_METER(rt, which) JS_ATOMIC_INCREMENT(&(rt)->which) +# define JS_RUNTIME_UNMETER(rt, which) JS_ATOMIC_DECREMENT(&(rt)->which) +#else +# define JS_RUNTIME_METER(rt, which) /* nothing */ +# define JS_RUNTIME_UNMETER(rt, which) /* nothing */ +#endif + +#define JS_KEEP_ATOMS(rt) JS_ATOMIC_INCREMENT(&(rt)->gcKeepAtoms); +#define JS_UNKEEP_ATOMS(rt) JS_ATOMIC_DECREMENT(&(rt)->gcKeepAtoms); + +#ifdef JS_ARGUMENT_FORMATTER_DEFINED +/* + * Linked list mapping format strings for JS_{Convert,Push}Arguments{,VA} to + * formatter functions. Elements are sorted in non-increasing format string + * length order. + */ +struct JSArgumentFormatMap { + const char *format; + size_t length; + JSArgumentFormatter formatter; + JSArgumentFormatMap *next; +}; +#endif + +struct JSStackHeader { + uintN nslots; + JSStackHeader *down; +}; + +#define JS_STACK_SEGMENT(sh) ((jsval *)(sh) + 2) + +/* + * Key and entry types for the JSContext.resolvingTable hash table, typedef'd + * here because all consumers need to see these declarations (and not just the + * typedef names, as would be the case for an opaque pointer-to-typedef'd-type + * declaration), along with cx->resolvingTable. + */ +typedef struct JSResolvingKey { + JSObject *obj; + jsid id; +} JSResolvingKey; + +typedef struct JSResolvingEntry { + JSDHashEntryHdr hdr; + JSResolvingKey key; + uint32 flags; +} JSResolvingEntry; + +#define JSRESFLAG_LOOKUP 0x1 /* resolving id from lookup */ +#define JSRESFLAG_WATCH 0x2 /* resolving id from watch */ + +struct JSContext { + JSCList links; + + /* Interpreter activation count. */ + uintN interpLevel; + + /* Limit pointer for checking stack consumption during recursion. */ + jsuword stackLimit; + + /* Runtime version control identifier and equality operators. */ + JSVersion version; + jsbytecode jsop_eq; + jsbytecode jsop_ne; + + /* Data shared by threads in an address space. */ + JSRuntime *runtime; + + /* Stack arena pool and frame pointer register. */ + JSArenaPool stackPool; + JSStackFrame *fp; + + /* Temporary arena pool used while compiling and decompiling. */ + JSArenaPool tempPool; + + /* Top-level object and pointer to top stack frame's scope chain. */ + JSObject *globalObject; + + /* Most recently created things by type, members of the GC's root set. */ + JSGCThing *newborn[GCX_NTYPES]; + + /* Atom root for the last-looked-up atom on this context. */ + JSAtom *lastAtom; + + /* Regular expression class statics (XXX not shared globally). */ + JSRegExpStatics regExpStatics; + + /* State for object and array toSource conversion. */ + JSSharpObjectMap sharpObjectMap; + + /* Argument formatter support for JS_{Convert,Push}Arguments{,VA}. */ + JSArgumentFormatMap *argumentFormatMap; + + /* Last message string and trace file for debugging. */ + char *lastMessage; +#ifdef DEBUG + void *tracefp; +#endif + + /* Per-context optional user callbacks. */ + JSBranchCallback branchCallback; + JSErrorReporter errorReporter; + + /* Client opaque pointer */ + void *data; + + /* GC and thread-safe state. */ + JSStackFrame *dormantFrameChain; /* dormant stack frame to scan */ +#ifdef JS_THREADSAFE + jsword thread; + jsrefcount requestDepth; + JSScope *scopeToShare; /* weak reference, see jslock.c */ + JSScope *lockedSealedScope; /* weak ref, for low-cost sealed + scope locking */ +#endif + +#if JS_HAS_LVALUE_RETURN + /* + * Secondary return value from native method called on the left-hand side + * of an assignment operator. The native should store the object in which + * to set a property in *rval, and return the property's id expressed as a + * jsval by calling JS_SetCallReturnValue2(cx, idval). + */ + jsval rval2; + JSPackedBool rval2set; +#endif + + /* + * True if creating an exception object, to prevent runaway recursion. + * NB: creatingException packs with rval2set, #if JS_HAS_LVALUE_RETURN, + * and with throwing, below. + */ + JSPackedBool creatingException; + + /* + * Exception state -- the exception member is a GC root by definition. + * NB: throwing packs with creatingException and rval2set, above. + */ + JSPackedBool throwing; /* is there a pending exception? */ + jsval exception; /* most-recently-thrown exception */ + + /* Per-context options. */ + uint32 options; /* see jsapi.h for JSOPTION_* */ + + /* Locale specific callbacks for string conversion. */ + JSLocaleCallbacks *localeCallbacks; + + /* + * cx->resolvingTable is non-null and non-empty if we are initializing + * standard classes lazily, or if we are otherwise recursing indirectly + * from js_LookupProperty through a JSClass.resolve hook. It is used to + * limit runaway recursion (see jsapi.c and jsobj.c). + */ + JSDHashTable *resolvingTable; + + /* PDL of stack headers describing stack slots not rooted by argv, etc. */ + JSStackHeader *stackHeaders; + + /* Optional hook to find principals for an object being accessed on cx. */ + JSObjectPrincipalsFinder findObjectPrincipals; +}; + +/* Slightly more readable macros, also to hide bitset implementation detail. */ +#define JS_HAS_STRICT_OPTION(cx) ((cx)->options & JSOPTION_STRICT) +#define JS_HAS_WERROR_OPTION(cx) ((cx)->options & JSOPTION_WERROR) + +extern JSContext * +js_NewContext(JSRuntime *rt, size_t stackChunkSize); + +extern void +js_DestroyContext(JSContext *cx, JSGCMode gcmode); + +/* + * Return true if cx points to a context in rt->contextList, else return false. + * NB: the caller (see jslock.c:ClaimScope) must hold rt->gcLock. + */ +extern JSBool +js_ValidContextPointer(JSRuntime *rt, JSContext *cx); + +/* + * If unlocked, acquire and release rt->gcLock around *iterp update; otherwise + * the caller must be holding rt->gcLock. + */ +extern JSContext * +js_ContextIterator(JSRuntime *rt, JSBool unlocked, JSContext **iterp); + +/* + * Report an exception, which is currently realized as a printf-style format + * string and its arguments. + */ +typedef enum JSErrNum { +#define MSG_DEF(name, number, count, exception, format) \ + name = number, +#include "js.msg" +#undef MSG_DEF + JSErr_Limit +} JSErrNum; + +extern const JSErrorFormatString * +js_GetErrorMessage(void *userRef, const char *locale, const uintN errorNumber); + +#ifdef va_start +extern JSBool +js_ReportErrorVA(JSContext *cx, uintN flags, const char *format, va_list ap); + +extern JSBool +js_ReportErrorNumberVA(JSContext *cx, uintN flags, JSErrorCallback callback, + void *userRef, const uintN errorNumber, + JSBool charArgs, va_list ap); + +extern JSBool +js_ExpandErrorArguments(JSContext *cx, JSErrorCallback callback, + void *userRef, const uintN errorNumber, + char **message, JSErrorReport *reportp, + JSBool *warningp, JSBool charArgs, va_list ap); +#endif + +extern void +js_ReportOutOfMemory(JSContext *cx, JSErrorCallback errorCallback); + +/* + * Report an exception using a previously composed JSErrorReport. + * XXXbe remove from "friend" API + */ +extern JS_FRIEND_API(void) +js_ReportErrorAgain(JSContext *cx, const char *message, JSErrorReport *report); + +extern void +js_ReportIsNotDefined(JSContext *cx, const char *name); + +extern JSErrorFormatString js_ErrorFormatString[JSErr_Limit]; + +/* + * See JS_SetThreadStackLimit in jsapi.c, where we check that the stack grows + * in the expected direction. On Unix-y systems, JS_STACK_GROWTH_DIRECTION is + * computed on the build host by jscpucfg.c and written into jsautocfg.h. The + * macro is hardcoded in jscpucfg.h on Windows and Mac systems (for historical + * reasons pre-dating autoconf usage). + */ +#if JS_STACK_GROWTH_DIRECTION > 0 +# define JS_CHECK_STACK_SIZE(cx, lval) ((jsuword)&(lval) < (cx)->stackLimit) +#else +# define JS_CHECK_STACK_SIZE(cx, lval) ((jsuword)&(lval) > (cx)->stackLimit) +#endif + +JS_END_EXTERN_C + +#endif /* jscntxt_h___ */ diff --git a/src/dom/js/jscompat.h b/src/dom/js/jscompat.h new file mode 100644 index 000000000..6ba036a5e --- /dev/null +++ b/src/dom/js/jscompat.h @@ -0,0 +1,57 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* -*- Mode: C; tab-width: 8 -*- + * Copyright © 1996-1999 Netscape Communications Corporation, All Rights Reserved. + */ +#ifndef jscompat_h___ +#define jscompat_h___ +/* + * Compatibility glue for various NSPR versions. We must always define int8, + * int16, jsword, and so on to minimize differences with js/ref, no matter what + * the NSPR typedef names may be. + */ +#include "jstypes.h" +#include "jslong.h" + +typedef JSIntn intN; +typedef JSUintn uintN; +typedef JSUword jsuword; +typedef JSWord jsword; +typedef float float32; +#define allocPriv allocPool +#endif /* jscompat_h___ */ diff --git a/src/dom/js/jsconfig.h b/src/dom/js/jsconfig.h new file mode 100644 index 000000000..fae86d3a7 --- /dev/null +++ b/src/dom/js/jsconfig.h @@ -0,0 +1,489 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS configuration macros. + */ +#ifndef JS_VERSION +#define JS_VERSION 150 +#endif + +/* + * Compile-time JS version configuration. The JS version numbers lie on the + * number line like so: + * + * 1.0 1.1 1.2 1.3 1.4 ECMAv3 1.5 + * ^ ^ + * | | + * basis for ECMAv1 close to ECMAv2 + * + * where ECMAv3 stands for ECMA-262 Edition 3. See the runtime version enum + * JSVersion in jspubtd.h. Code in the engine can therefore count on version + * <= JSVERSION_1_4 to mean "before the Third Edition of ECMA-262" and version + * > JSVERSION_1_4 to mean "at or after the Third Edition". + * + * In the unlikely event that SpiderMonkey ever implements JavaScript 2.0, or + * ECMA-262 Edition 4 (JS2 without certain extensions), the version number to + * use would be near 200, or greater. + * + * The JS_VERSION_ECMA_3 version is the minimal configuration conforming to + * the ECMA-262 Edition 3 specification. Use it for minimal embeddings, where + * you're sure you don't need any of the extensions disabled in this version. + * In order to facilitate testing, JS_HAS_OBJ_PROTO_PROP is defined as part of + * the JS_VERSION_ECMA_3_TEST version. + */ +#define JS_VERSION_ECMA_3 148 +#define JS_VERSION_ECMA_3_TEST 149 + +#if JS_VERSION == JS_VERSION_ECMA_3 || \ + JS_VERSION == JS_VERSION_ECMA_3_TEST + +#define JS_BUG_NULL_INDEX_PROPS 0 /* o[0] defaults to null, not void */ +#define JS_BUG_EMPTY_INDEX_ZERO 0 /* o[""] is equivalent to o[0] */ +#define JS_BUG_EAGER_TOSTRING 0 /* o.toString() trumps o.valueOf() */ +#define JS_BUG_VOID_TOSTRING 0 /* void 0 + 0 == "undefined0" */ +#define JS_BUG_EVAL_THIS_FUN 0 /* eval('this') in function f is f */ +#define JS_BUG_EVAL_THIS_SCOPE 0 /* Math.eval('sin(x)') vs. local x */ +#define JS_BUG_FALLIBLE_EQOPS 0 /* fallible/intransitive equality ops */ +#define JS_BUG_FALLIBLE_TONUM 0 /* fallible ValueToNumber primitive */ +#define JS_BUG_WITH_CLOSURE 0 /* with(o)function f(){} sets o.f */ + +#define JS_HAS_PROP_DELETE 1 /* delete o.p removes p from o */ +#define JS_HAS_CALL_OBJECT 1 /* fun.caller is stack frame obj */ +#define JS_HAS_LABEL_STATEMENT 1 /* has break/continue to label: */ +#define JS_HAS_DO_WHILE_LOOP 1 /* has do {...} while (b) */ +#define JS_HAS_SWITCH_STATEMENT 1 /* has switch (v) {case c: ...} */ +#define JS_HAS_SOME_PERL_FUN 1 /* has array.join/reverse/sort */ +#define JS_HAS_MORE_PERL_FUN 1 /* has array.push, array.pop, etc */ +#define JS_HAS_STR_HTML_HELPERS 0 /* has str.anchor, str.bold, etc. */ +#define JS_HAS_PERL_SUBSTR 0 /* has str.substr */ +#define JS_HAS_VALUEOF_HINT 1 /* valueOf(hint) where hint is typeof */ +#define JS_HAS_LEXICAL_CLOSURE 1 /* nested functions, lexically closed */ +#define JS_HAS_APPLY_FUNCTION 1 /* has fun.apply(obj, argArray) */ +#define JS_HAS_CALL_FUNCTION 1 /* has fun.call(obj, arg1, ... argN) */ +#if JS_VERSION == JS_VERSION_ECMA_3_TEST +#define JS_HAS_OBJ_PROTO_PROP 1 /* has o.__proto__ etc. */ +#else +#define JS_HAS_OBJ_PROTO_PROP 0 /* has o.__proto__ etc. */ +#endif +#define JS_HAS_REGEXPS 1 /* has perl r.e.s via RegExp, /pat/ */ +#define JS_HAS_SEQUENCE_OPS 1 /* has array.slice, string.concat */ +#define JS_HAS_INITIALIZERS 1 /* has var o = {'foo': 42, 'bar':3} */ +#define JS_HAS_OBJ_WATCHPOINT 0 /* has o.watch and o.unwatch */ +#define JS_HAS_EXPORT_IMPORT 0 /* has export fun; import obj.fun */ +#define JS_HAS_EVAL_THIS_SCOPE 0 /* Math.eval is same as with (Math) */ +#define JS_HAS_TRIPLE_EQOPS 1 /* has === and !== identity eqops */ +#define JS_HAS_SHARP_VARS 0 /* has #n=, #n# for object literals */ +#define JS_HAS_REPLACE_LAMBDA 1 /* has string.replace(re, lambda) */ +#define JS_HAS_SCRIPT_OBJECT 0 /* has (new Script("x++")).exec() */ +#define JS_HAS_XDR 0 /* has XDR API and internal support */ +#define JS_HAS_XDR_FREEZE_THAW 0 /* has XDR freeze/thaw script methods */ +#define JS_HAS_EXCEPTIONS 1 /* has exception handling */ +#define JS_HAS_UNDEFINED 1 /* has global "undefined" property */ +#define JS_HAS_TOSOURCE 0 /* has Object/Array toSource method */ +#define JS_HAS_IN_OPERATOR 1 /* has in operator ('p' in {p:1}) */ +#define JS_HAS_INSTANCEOF 1 /* has {p:1} instanceof Object */ +#define JS_HAS_ARGS_OBJECT 1 /* has minimal ECMA arguments object */ +#define JS_HAS_DEBUGGER_KEYWORD 0 /* has hook for debugger keyword */ +#define JS_HAS_ERROR_EXCEPTIONS 1 /* has error object hierarchy */ +#define JS_HAS_CATCH_GUARD 0 /* has exception handling catch guard */ +#define JS_HAS_NEW_OBJ_METHODS 1 /* has Object.prototype query methods */ +#define JS_HAS_SPARSE_ARRAYS 0 /* array methods preserve empty elems */ +#define JS_HAS_DFLT_MSG_STRINGS 1 /* provides English error messages */ +#define JS_HAS_NUMBER_FORMATS 1 /* numbers have formatting methods */ +#define JS_HAS_GETTER_SETTER 0 /* has JS2 getter/setter functions */ +#define JS_HAS_UNEVAL 0 /* has uneval() top-level function */ +#define JS_HAS_CONST 0 /* has JS2 const as alternative var */ +#define JS_HAS_FUN_EXPR_STMT 0 /* has function expression statement */ +#define JS_HAS_LVALUE_RETURN 1 /* has o.item(i) = j; for native item */ +#define JS_HAS_NO_SUCH_METHOD 0 /* has o.__noSuchMethod__ handler */ + +#elif JS_VERSION == 100 + +#define JS_BUG_NULL_INDEX_PROPS 1 /* o[0] defaults to null, not void */ +#define JS_BUG_EMPTY_INDEX_ZERO 1 /* o[""] is equivalent to o[0] */ +#define JS_BUG_EAGER_TOSTRING 1 /* o.toString() trumps o.valueOf() */ +#define JS_BUG_VOID_TOSTRING 0 /* void 0 + 0 == "undefined0" */ +#define JS_BUG_EVAL_THIS_FUN 0 /* eval('this') in function f is f */ +#define JS_BUG_EVAL_THIS_SCOPE 0 /* Math.eval('sin(x)') vs. local x */ +#define JS_BUG_FALLIBLE_EQOPS 1 /* fallible/intransitive equality ops */ +#define JS_BUG_FALLIBLE_TONUM 1 /* fallible ValueToNumber primitive */ +#define JS_BUG_WITH_CLOSURE 0 /* with(o)function f(){} sets o.f */ + +#define JS_HAS_PROP_DELETE 0 /* delete o.p removes p from o */ +#define JS_HAS_CALL_OBJECT 0 /* fun.caller is stack frame obj */ +#define JS_HAS_LABEL_STATEMENT 0 /* has break/continue to label: */ +#define JS_HAS_DO_WHILE_LOOP 0 /* has do {...} while (b) */ +#define JS_HAS_SWITCH_STATEMENT 0 /* has switch (v) {case c: ...} */ +#define JS_HAS_SOME_PERL_FUN 0 /* has array.join/reverse/sort */ +#define JS_HAS_MORE_PERL_FUN 0 /* has array.push, str.substr, etc */ +#define JS_HAS_STR_HTML_HELPERS 1 /* has str.anchor, str.bold, etc. */ +#define JS_HAS_PERL_SUBSTR 0 /* has str.substr */ +#define JS_HAS_VALUEOF_HINT 0 /* valueOf(hint) where hint is typeof */ +#define JS_HAS_LEXICAL_CLOSURE 0 /* nested functions, lexically closed */ +#define JS_HAS_APPLY_FUNCTION 0 /* has fun.apply(obj, argArray) */ +#define JS_HAS_CALL_FUNCTION 0 /* has fun.call(obj, arg1, ... argN) */ +#define JS_HAS_OBJ_PROTO_PROP 0 /* has o.__proto__ etc. */ +#define JS_HAS_REGEXPS 0 /* has perl r.e.s via RegExp, /pat/ */ +#define JS_HAS_SEQUENCE_OPS 0 /* has array.slice, string.concat */ +#define JS_HAS_INITIALIZERS 0 /* has var o = {'foo': 42, 'bar':3} */ +#define JS_HAS_OBJ_WATCHPOINT 0 /* has o.watch and o.unwatch */ +#define JS_HAS_EXPORT_IMPORT 0 /* has export fun; import obj.fun */ +#define JS_HAS_EVAL_THIS_SCOPE 0 /* Math.eval is same as with (Math) */ +#define JS_HAS_TRIPLE_EQOPS 0 /* has === and !== identity eqops */ +#define JS_HAS_SHARP_VARS 0 /* has #n=, #n# for object literals */ +#define JS_HAS_REPLACE_LAMBDA 0 /* has string.replace(re, lambda) */ +#define JS_HAS_SCRIPT_OBJECT 0 /* has (new Script("x++")).exec() */ +#define JS_HAS_XDR 0 /* has XDR API and internal support */ +#define JS_HAS_XDR_FREEZE_THAW 0 /* has XDR freeze/thaw script methods */ +#define JS_HAS_EXCEPTIONS 0 /* has exception handling */ +#define JS_HAS_UNDEFINED 0 /* has global "undefined" property */ +#define JS_HAS_TOSOURCE 0 /* has Object/Array toSource method */ +#define JS_HAS_IN_OPERATOR 0 /* has in operator ('p' in {p:1}) */ +#define JS_HAS_INSTANCEOF 0 /* has {p:1} instanceof Object */ +#define JS_HAS_ARGS_OBJECT 0 /* has minimal ECMA arguments object */ +#define JS_HAS_DEBUGGER_KEYWORD 0 /* has hook for debugger keyword */ +#define JS_HAS_ERROR_EXCEPTIONS 0 /* has error object hierarchy */ +#define JS_HAS_CATCH_GUARD 0 /* has exception handling catch guard */ +#define JS_HAS_NEW_OBJ_METHODS 0 /* has Object.prototype query methods */ +#define JS_HAS_SPARSE_ARRAYS 0 /* array methods preserve empty elems */ +#define JS_HAS_DFLT_MSG_STRINGS 1 /* provides English error messages */ +#define JS_HAS_NUMBER_FORMATS 0 /* numbers have formatting methods */ +#define JS_HAS_GETTER_SETTER 0 /* has JS2 getter/setter functions */ +#define JS_HAS_UNEVAL 0 /* has uneval() top-level function */ +#define JS_HAS_CONST 0 /* has JS2 const as alternative var */ +#define JS_HAS_FUN_EXPR_STMT 0 /* has function expression statement */ +#define JS_HAS_LVALUE_RETURN 0 /* has o.item(i) = j; for native item */ +#define JS_HAS_NO_SUCH_METHOD 0 /* has o.__noSuchMethod__ handler */ + +#elif JS_VERSION == 110 + +#define JS_BUG_NULL_INDEX_PROPS 1 /* o[0] defaults to null, not void */ +#define JS_BUG_EMPTY_INDEX_ZERO 1 /* o[""] is equivalent to o[0] */ +#define JS_BUG_EAGER_TOSTRING 1 /* o.toString() trumps o.valueOf() */ +#define JS_BUG_VOID_TOSTRING 0 /* void 0 + 0 == "undefined0" */ +#define JS_BUG_EVAL_THIS_FUN 1 /* eval('this') in function f is f */ +#define JS_BUG_EVAL_THIS_SCOPE 1 /* Math.eval('sin(x)') vs. local x */ +#define JS_BUG_FALLIBLE_EQOPS 1 /* fallible/intransitive equality ops */ +#define JS_BUG_FALLIBLE_TONUM 1 /* fallible ValueToNumber primitive */ +#define JS_BUG_WITH_CLOSURE 0 /* with(o)function f(){} sets o.f */ + +#define JS_HAS_PROP_DELETE 0 /* delete o.p removes p from o */ +#define JS_HAS_CALL_OBJECT 0 /* fun.caller is stack frame obj */ +#define JS_HAS_LABEL_STATEMENT 0 /* has break/continue to label: */ +#define JS_HAS_DO_WHILE_LOOP 0 /* has do {...} while (b) */ +#define JS_HAS_SWITCH_STATEMENT 0 /* has switch (v) {case c: ...} */ +#define JS_HAS_SOME_PERL_FUN 1 /* has array.join/reverse/sort */ +#define JS_HAS_MORE_PERL_FUN 0 /* has array.push, str.substr, etc */ +#define JS_HAS_STR_HTML_HELPERS 1 /* has str.anchor, str.bold, etc. */ +#define JS_HAS_PERL_SUBSTR 0 /* has str.substr */ +#define JS_HAS_VALUEOF_HINT 0 /* valueOf(hint) where hint is typeof */ +#define JS_HAS_LEXICAL_CLOSURE 0 /* nested functions, lexically closed */ +#define JS_HAS_APPLY_FUNCTION 0 /* has apply(fun, arg1, ... argN) */ +#define JS_HAS_CALL_FUNCTION 0 /* has fun.call(obj, arg1, ... argN) */ +#define JS_HAS_OBJ_PROTO_PROP 0 /* has o.__proto__ etc. */ +#define JS_HAS_REGEXPS 0 /* has perl r.e.s via RegExp, /pat/ */ +#define JS_HAS_SEQUENCE_OPS 0 /* has array.slice, string.concat */ +#define JS_HAS_INITIALIZERS 0 /* has var o = {'foo': 42, 'bar':3} */ +#define JS_HAS_OBJ_WATCHPOINT 0 /* has o.watch and o.unwatch */ +#define JS_HAS_EXPORT_IMPORT 0 /* has export fun; import obj.fun */ +#define JS_HAS_EVAL_THIS_SCOPE 0 /* Math.eval is same as with (Math) */ +#define JS_HAS_TRIPLE_EQOPS 0 /* has === and !== identity eqops */ +#define JS_HAS_SHARP_VARS 0 /* has #n=, #n# for object literals */ +#define JS_HAS_REPLACE_LAMBDA 0 /* has string.replace(re, lambda) */ +#define JS_HAS_SCRIPT_OBJECT 0 /* has (new Script("x++")).exec() */ +#define JS_HAS_XDR 0 /* has XDR API and internal support */ +#define JS_HAS_XDR_FREEZE_THAW 0 /* has XDR freeze/thaw script methods */ +#define JS_HAS_EXCEPTIONS 0 /* has exception handling */ +#define JS_HAS_UNDEFINED 0 /* has global "undefined" property */ +#define JS_HAS_TOSOURCE 0 /* has Object/Array toSource method */ +#define JS_HAS_IN_OPERATOR 0 /* has in operator ('p' in {p:1}) */ +#define JS_HAS_INSTANCEOF 0 /* has {p:1} instanceof Object */ +#define JS_HAS_ARGS_OBJECT 0 /* has minimal ECMA arguments object */ +#define JS_HAS_DEBUGGER_KEYWORD 0 /* has hook for debugger keyword */ +#define JS_HAS_ERROR_EXCEPTIONS 0 /* has error object hierarchy */ +#define JS_HAS_CATCH_GUARD 0 /* has exception handling catch guard */ +#define JS_HAS_NEW_OBJ_METHODS 0 /* has Object.prototype query methods */ +#define JS_HAS_SPARSE_ARRAYS 0 /* array methods preserve empty elems */ +#define JS_HAS_DFLT_MSG_STRINGS 1 /* provides English error messages */ +#define JS_HAS_NUMBER_FORMATS 0 /* numbers have formatting methods */ +#define JS_HAS_GETTER_SETTER 0 /* has JS2 getter/setter functions */ +#define JS_HAS_UNEVAL 0 /* has uneval() top-level function */ +#define JS_HAS_CONST 0 /* has JS2 const as alternative var */ +#define JS_HAS_FUN_EXPR_STMT 0 /* has function expression statement */ +#define JS_HAS_LVALUE_RETURN 0 /* has o.item(i) = j; for native item */ +#define JS_HAS_NO_SUCH_METHOD 0 /* has o.__noSuchMethod__ handler */ + +#elif JS_VERSION == 120 + +#define JS_BUG_NULL_INDEX_PROPS 0 /* o[0] defaults to null, not void */ +#define JS_BUG_EMPTY_INDEX_ZERO 0 /* o[""] is equivalent to o[0] */ +#define JS_BUG_EAGER_TOSTRING 0 /* o.toString() trumps o.valueOf() */ +#define JS_BUG_VOID_TOSTRING 1 /* void 0 + 0 == "undefined0" */ +#define JS_BUG_EVAL_THIS_FUN 0 /* eval('this') in function f is f */ +#define JS_BUG_EVAL_THIS_SCOPE 0 /* Math.eval('sin(x)') vs. local x */ +#define JS_BUG_FALLIBLE_EQOPS 0 /* fallible/intransitive equality ops */ +#define JS_BUG_FALLIBLE_TONUM 0 /* fallible ValueToNumber primitive */ +#define JS_BUG_WITH_CLOSURE 1 /* with(o)function f(){} sets o.f */ + +#define JS_HAS_PROP_DELETE 1 /* delete o.p removes p from o */ +#define JS_HAS_CALL_OBJECT 1 /* fun.caller is stack frame obj */ +#define JS_HAS_LABEL_STATEMENT 1 /* has break/continue to label: */ +#define JS_HAS_DO_WHILE_LOOP 1 /* has do {...} while (b) */ +#define JS_HAS_SWITCH_STATEMENT 1 /* has switch (v) {case c: ...} */ +#define JS_HAS_SOME_PERL_FUN 1 /* has array.join/reverse/sort */ +#define JS_HAS_MORE_PERL_FUN 1 /* has array.push, str.substr, etc */ +#define JS_HAS_STR_HTML_HELPERS 1 /* has str.anchor, str.bold, etc. */ +#define JS_HAS_PERL_SUBSTR 1 /* has str.substr */ +#define JS_HAS_VALUEOF_HINT 1 /* valueOf(hint) where hint is typeof */ +#define JS_HAS_LEXICAL_CLOSURE 1 /* nested functions, lexically closed */ +#define JS_HAS_APPLY_FUNCTION 1 /* has apply(fun, arg1, ... argN) */ +#define JS_HAS_CALL_FUNCTION 0 /* has fun.call(obj, arg1, ... argN) */ +#define JS_HAS_OBJ_PROTO_PROP 1 /* has o.__proto__ etc. */ +#define JS_HAS_REGEXPS 1 /* has perl r.e.s via RegExp, /pat/ */ +#define JS_HAS_SEQUENCE_OPS 1 /* has array.slice, string.concat */ +#define JS_HAS_INITIALIZERS 1 /* has var o = {'foo': 42, 'bar':3} */ +#define JS_HAS_OBJ_WATCHPOINT 1 /* has o.watch and o.unwatch */ +#define JS_HAS_EXPORT_IMPORT 1 /* has export fun; import obj.fun */ +#define JS_HAS_EVAL_THIS_SCOPE 1 /* Math.eval is same as with (Math) */ +#define JS_HAS_TRIPLE_EQOPS 0 /* has === and !== identity eqops */ +#define JS_HAS_SHARP_VARS 0 /* has #n=, #n# for object literals */ +#define JS_HAS_REPLACE_LAMBDA 0 /* has string.replace(re, lambda) */ +#define JS_HAS_SCRIPT_OBJECT 0 /* has (new Script("x++")).exec() */ +#define JS_HAS_XDR 0 /* has XDR API and internal support */ +#define JS_HAS_XDR_FREEZE_THAW 0 /* has XDR freeze/thaw script methods */ +#define JS_HAS_EXCEPTIONS 0 /* has exception handling */ +#define JS_HAS_UNDEFINED 0 /* has global "undefined" property */ +#define JS_HAS_TOSOURCE 0 /* has Object/Array toSource method */ +#define JS_HAS_IN_OPERATOR 0 /* has in operator ('p' in {p:1}) */ +#define JS_HAS_INSTANCEOF 0 /* has {p:1} instanceof Object */ +#define JS_HAS_ARGS_OBJECT 0 /* has minimal ECMA arguments object */ +#define JS_HAS_DEBUGGER_KEYWORD 0 /* has hook for debugger keyword */ +#define JS_HAS_ERROR_EXCEPTIONS 0 /* has error object hierarchy */ +#define JS_HAS_CATCH_GUARD 0 /* has exception handling catch guard */ +#define JS_HAS_NEW_OBJ_METHODS 0 /* has Object.prototype query methods */ +#define JS_HAS_SPARSE_ARRAYS 0 /* array methods preserve empty elems */ +#define JS_HAS_DFLT_MSG_STRINGS 1 /* provides English error messages */ +#define JS_HAS_NUMBER_FORMATS 0 /* numbers have formatting methods */ +#define JS_HAS_GETTER_SETTER 0 /* has JS2 getter/setter functions */ +#define JS_HAS_UNEVAL 0 /* has uneval() top-level function */ +#define JS_HAS_CONST 0 /* has JS2 const as alternative var */ +#define JS_HAS_FUN_EXPR_STMT 0 /* has function expression statement */ +#define JS_HAS_LVALUE_RETURN 0 /* has o.item(i) = j; for native item */ +#define JS_HAS_NO_SUCH_METHOD 0 /* has o.__noSuchMethod__ handler */ + +#elif JS_VERSION == 130 + +#define JS_BUG_NULL_INDEX_PROPS 0 /* o[0] defaults to null, not void */ +#define JS_BUG_EMPTY_INDEX_ZERO 0 /* o[""] is equivalent to o[0] */ +#define JS_BUG_EAGER_TOSTRING 0 /* o.toString() trumps o.valueOf() */ +#define JS_BUG_VOID_TOSTRING 0 /* void 0 + 0 == "undefined0" */ +#define JS_BUG_EVAL_THIS_FUN 0 /* eval('this') in function f is f */ +#define JS_BUG_EVAL_THIS_SCOPE 0 /* Math.eval('sin(x)') vs. local x */ +#define JS_BUG_FALLIBLE_EQOPS 0 /* fallible/intransitive equality ops */ +#define JS_BUG_FALLIBLE_TONUM 0 /* fallible ValueToNumber primitive */ +#define JS_BUG_WITH_CLOSURE 1 /* with(o)function f(){} sets o.f */ + +#define JS_HAS_PROP_DELETE 1 /* delete o.p removes p from o */ +#define JS_HAS_CALL_OBJECT 1 /* fun.caller is stack frame obj */ +#define JS_HAS_LABEL_STATEMENT 1 /* has break/continue to label: */ +#define JS_HAS_DO_WHILE_LOOP 1 /* has do {...} while (b) */ +#define JS_HAS_SWITCH_STATEMENT 1 /* has switch (v) {case c: ...} */ +#define JS_HAS_SOME_PERL_FUN 1 /* has array.join/reverse/sort */ +#define JS_HAS_MORE_PERL_FUN 1 /* has array.push, str.substr, etc */ +#define JS_HAS_STR_HTML_HELPERS 1 /* has str.anchor, str.bold, etc. */ +#define JS_HAS_PERL_SUBSTR 1 /* has str.substr */ +#define JS_HAS_VALUEOF_HINT 1 /* valueOf(hint) where hint is typeof */ +#define JS_HAS_LEXICAL_CLOSURE 1 /* nested functions, lexically closed */ +#define JS_HAS_APPLY_FUNCTION 1 /* has apply(fun, arg1, ... argN) */ +#define JS_HAS_CALL_FUNCTION 1 /* has fun.call(obj, arg1, ... argN) */ +#define JS_HAS_OBJ_PROTO_PROP 1 /* has o.__proto__ etc. */ +#define JS_HAS_REGEXPS 1 /* has perl r.e.s via RegExp, /pat/ */ +#define JS_HAS_SEQUENCE_OPS 1 /* has array.slice, string.concat */ +#define JS_HAS_INITIALIZERS 1 /* has var o = {'foo': 42, 'bar':3} */ +#define JS_HAS_OBJ_WATCHPOINT 1 /* has o.watch and o.unwatch */ +#define JS_HAS_EXPORT_IMPORT 1 /* has export fun; import obj.fun */ +#define JS_HAS_EVAL_THIS_SCOPE 1 /* Math.eval is same as with (Math) */ +#define JS_HAS_TRIPLE_EQOPS 1 /* has === and !== identity eqops */ +#define JS_HAS_SHARP_VARS 1 /* has #n=, #n# for object literals */ +#define JS_HAS_REPLACE_LAMBDA 1 /* has string.replace(re, lambda) */ +#define JS_HAS_SCRIPT_OBJECT 1 /* has (new Script("x++")).exec() */ +#define JS_HAS_XDR 1 /* has XDR API and internal support */ +#define JS_HAS_XDR_FREEZE_THAW 0 /* has XDR freeze/thaw script methods */ +#define JS_HAS_EXCEPTIONS 0 /* has exception handling */ +#define JS_HAS_UNDEFINED 1 /* has global "undefined" property */ +#define JS_HAS_TOSOURCE 1 /* has Object/Array toSource method */ +#define JS_HAS_IN_OPERATOR 0 /* has in operator ('p' in {p:1}) */ +#define JS_HAS_INSTANCEOF 0 /* has {p:1} instanceof Object */ +#define JS_HAS_ARGS_OBJECT 1 /* has minimal ECMA arguments object */ +#define JS_HAS_DEBUGGER_KEYWORD 1 /* has hook for debugger keyword */ +#define JS_HAS_ERROR_EXCEPTIONS 0 /* has error object hierarchy */ +#define JS_HAS_CATCH_GUARD 0 /* has exception handling catch guard */ +#define JS_HAS_NEW_OBJ_METHODS 0 /* has Object.prototype query methods */ +#define JS_HAS_SPARSE_ARRAYS 0 /* array methods preserve empty elems */ +#define JS_HAS_DFLT_MSG_STRINGS 1 /* provides English error messages */ +#define JS_HAS_NUMBER_FORMATS 0 /* numbers have formatting methods */ +#define JS_HAS_GETTER_SETTER 0 /* has JS2 getter/setter functions */ +#define JS_HAS_UNEVAL 0 /* has uneval() top-level function */ +#define JS_HAS_CONST 0 /* has JS2 const as alternative var */ +#define JS_HAS_FUN_EXPR_STMT 0 /* has function expression statement */ +#define JS_HAS_LVALUE_RETURN 0 /* has o.item(i) = j; for native item */ +#define JS_HAS_NO_SUCH_METHOD 0 /* has o.__noSuchMethod__ handler */ + +#elif JS_VERSION == 140 + +#define JS_BUG_NULL_INDEX_PROPS 0 /* o[0] defaults to null, not void */ +#define JS_BUG_EMPTY_INDEX_ZERO 0 /* o[""] is equivalent to o[0] */ +#define JS_BUG_EAGER_TOSTRING 0 /* o.toString() trumps o.valueOf() */ +#define JS_BUG_VOID_TOSTRING 0 /* void 0 + 0 == "undefined0" */ +#define JS_BUG_EVAL_THIS_FUN 0 /* eval('this') in function f is f */ +#define JS_BUG_EVAL_THIS_SCOPE 0 /* Math.eval('sin(x)') vs. local x */ +#define JS_BUG_FALLIBLE_EQOPS 0 /* fallible/intransitive equality ops */ +#define JS_BUG_FALLIBLE_TONUM 0 /* fallible ValueToNumber primitive */ +#define JS_BUG_WITH_CLOSURE 1 /* with(o)function f(){} sets o.f */ + +#define JS_HAS_PROP_DELETE 1 /* delete o.p removes p from o */ +#define JS_HAS_CALL_OBJECT 1 /* fun.caller is stack frame obj */ +#define JS_HAS_LABEL_STATEMENT 1 /* has break/continue to label: */ +#define JS_HAS_DO_WHILE_LOOP 1 /* has do {...} while (b) */ +#define JS_HAS_SWITCH_STATEMENT 1 /* has switch (v) {case c: ...} */ +#define JS_HAS_SOME_PERL_FUN 1 /* has array.join/reverse/sort */ +#define JS_HAS_MORE_PERL_FUN 1 /* has array.push, str.substr, etc */ +#define JS_HAS_STR_HTML_HELPERS 1 /* has str.anchor, str.bold, etc. */ +#define JS_HAS_PERL_SUBSTR 1 /* has str.substr */ +#define JS_HAS_VALUEOF_HINT 1 /* valueOf(hint) where hint is typeof */ +#define JS_HAS_LEXICAL_CLOSURE 1 /* nested functions, lexically closed */ +#define JS_HAS_APPLY_FUNCTION 1 /* has apply(fun, arg1, ... argN) */ +#define JS_HAS_CALL_FUNCTION 1 /* has fun.call(obj, arg1, ... argN) */ +#define JS_HAS_OBJ_PROTO_PROP 1 /* has o.__proto__ etc. */ +#define JS_HAS_REGEXPS 1 /* has perl r.e.s via RegExp, /pat/ */ +#define JS_HAS_SEQUENCE_OPS 1 /* has array.slice, string.concat */ +#define JS_HAS_INITIALIZERS 1 /* has var o = {'foo': 42, 'bar':3} */ +#define JS_HAS_OBJ_WATCHPOINT 1 /* has o.watch and o.unwatch */ +#define JS_HAS_EXPORT_IMPORT 1 /* has export fun; import obj.fun */ +#define JS_HAS_EVAL_THIS_SCOPE 1 /* Math.eval is same as with (Math) */ +#define JS_HAS_TRIPLE_EQOPS 1 /* has === and !== identity eqops */ +#define JS_HAS_SHARP_VARS 1 /* has #n=, #n# for object literals */ +#define JS_HAS_REPLACE_LAMBDA 1 /* has string.replace(re, lambda) */ +#define JS_HAS_SCRIPT_OBJECT 1 /* has (new Script("x++")).exec() */ +#define JS_HAS_XDR 1 /* has XDR API and internal support */ +#define JS_HAS_XDR_FREEZE_THAW 0 /* has XDR freeze/thaw script methods */ +#define JS_HAS_EXCEPTIONS 1 /* has exception handling */ +#define JS_HAS_UNDEFINED 1 /* has global "undefined" property */ +#define JS_HAS_TOSOURCE 1 /* has Object/Array toSource method */ +#define JS_HAS_IN_OPERATOR 1 /* has in operator ('p' in {p:1}) */ +#define JS_HAS_INSTANCEOF 1 /* has {p:1} instanceof Object */ +#define JS_HAS_ARGS_OBJECT 1 /* has minimal ECMA arguments object */ +#define JS_HAS_DEBUGGER_KEYWORD 1 /* has hook for debugger keyword */ +#define JS_HAS_ERROR_EXCEPTIONS 0 /* rt errors reflected as exceptions */ +#define JS_HAS_CATCH_GUARD 0 /* has exception handling catch guard */ +#define JS_HAS_NEW_OBJ_METHODS 0 /* has Object.prototype query methods */ +#define JS_HAS_SPARSE_ARRAYS 0 /* array methods preserve empty elems */ +#define JS_HAS_DFLT_MSG_STRINGS 1 /* provides English error messages */ +#define JS_HAS_NUMBER_FORMATS 0 /* numbers have formatting methods */ +#define JS_HAS_GETTER_SETTER 0 /* has JS2 getter/setter functions */ +#define JS_HAS_UNEVAL 0 /* has uneval() top-level function */ +#define JS_HAS_CONST 0 /* has JS2 const as alternative var */ +#define JS_HAS_FUN_EXPR_STMT 0 /* has function expression statement */ +#define JS_HAS_LVALUE_RETURN 0 /* has o.item(i) = j; for native item */ +#define JS_HAS_NO_SUCH_METHOD 0 /* has o.__noSuchMethod__ handler */ + +#elif JS_VERSION == 150 + +#define JS_BUG_NULL_INDEX_PROPS 0 /* o[0] defaults to null, not void */ +#define JS_BUG_EMPTY_INDEX_ZERO 0 /* o[""] is equivalent to o[0] */ +#define JS_BUG_EAGER_TOSTRING 0 /* o.toString() trumps o.valueOf() */ +#define JS_BUG_VOID_TOSTRING 0 /* void 0 + 0 == "undefined0" */ +#define JS_BUG_EVAL_THIS_FUN 0 /* eval('this') in function f is f */ +#define JS_BUG_EVAL_THIS_SCOPE 0 /* Math.eval('sin(x)') vs. local x */ +#define JS_BUG_FALLIBLE_EQOPS 0 /* fallible/intransitive equality ops */ +#define JS_BUG_FALLIBLE_TONUM 0 /* fallible ValueToNumber primitive */ +#define JS_BUG_WITH_CLOSURE 0 /* with(o)function f(){} sets o.f */ + +#define JS_HAS_PROP_DELETE 1 /* delete o.p removes p from o */ +#define JS_HAS_CALL_OBJECT 1 /* fun.caller is stack frame obj */ +#define JS_HAS_LABEL_STATEMENT 1 /* has break/continue to label: */ +#define JS_HAS_DO_WHILE_LOOP 1 /* has do {...} while (b) */ +#define JS_HAS_SWITCH_STATEMENT 1 /* has switch (v) {case c: ...} */ +#define JS_HAS_SOME_PERL_FUN 1 /* has array.join/reverse/sort */ +#define JS_HAS_MORE_PERL_FUN 1 /* has array.push, str.substr, etc */ +#define JS_HAS_STR_HTML_HELPERS 1 /* has str.anchor, str.bold, etc. */ +#define JS_HAS_PERL_SUBSTR 1 /* has str.substr */ +#define JS_HAS_VALUEOF_HINT 1 /* valueOf(hint) where hint is typeof */ +#define JS_HAS_LEXICAL_CLOSURE 1 /* nested functions, lexically closed */ +#define JS_HAS_APPLY_FUNCTION 1 /* has apply(fun, arg1, ... argN) */ +#define JS_HAS_CALL_FUNCTION 1 /* has fun.call(obj, arg1, ... argN) */ +#define JS_HAS_OBJ_PROTO_PROP 1 /* has o.__proto__ etc. */ +#define JS_HAS_REGEXPS 1 /* has perl r.e.s via RegExp, /pat/ */ +#define JS_HAS_SEQUENCE_OPS 1 /* has array.slice, string.concat */ +#define JS_HAS_INITIALIZERS 1 /* has var o = {'foo': 42, 'bar':3} */ +#define JS_HAS_OBJ_WATCHPOINT 1 /* has o.watch and o.unwatch */ +#define JS_HAS_EXPORT_IMPORT 0 /* has export fun; import obj.fun */ +#define JS_HAS_EVAL_THIS_SCOPE 1 /* Math.eval is same as with (Math) */ +#define JS_HAS_TRIPLE_EQOPS 1 /* has === and !== identity eqops */ +#define JS_HAS_SHARP_VARS 1 /* has #n=, #n# for object literals */ +#define JS_HAS_REPLACE_LAMBDA 1 /* has string.replace(re, lambda) */ +#define JS_HAS_SCRIPT_OBJECT 1 /* has (new Script("x++")).exec() */ +#define JS_HAS_XDR 1 /* has XDR API and internal support */ +#define JS_HAS_XDR_FREEZE_THAW 0 /* has XDR freeze/thaw script methods */ +#define JS_HAS_EXCEPTIONS 1 /* has exception handling */ +#define JS_HAS_UNDEFINED 1 /* has global "undefined" property */ +#define JS_HAS_TOSOURCE 1 /* has Object/Array toSource method */ +#define JS_HAS_IN_OPERATOR 1 /* has in operator ('p' in {p:1}) */ +#define JS_HAS_INSTANCEOF 1 /* has {p:1} instanceof Object */ +#define JS_HAS_ARGS_OBJECT 1 /* has minimal ECMA arguments object */ +#define JS_HAS_DEBUGGER_KEYWORD 1 /* has hook for debugger keyword */ +#define JS_HAS_ERROR_EXCEPTIONS 1 /* rt errors reflected as exceptions */ +#define JS_HAS_CATCH_GUARD 1 /* has exception handling catch guard */ +#define JS_HAS_NEW_OBJ_METHODS 1 /* has Object.prototype query methods */ +#define JS_HAS_SPARSE_ARRAYS 0 /* array methods preserve empty elems */ +#define JS_HAS_DFLT_MSG_STRINGS 1 /* provides English error messages */ +#define JS_HAS_NUMBER_FORMATS 1 /* numbers have formatting methods */ +#define JS_HAS_GETTER_SETTER 1 /* has JS2 getter/setter functions */ +#define JS_HAS_UNEVAL 1 /* has uneval() top-level function */ +#define JS_HAS_CONST 1 /* has JS2 const as alternative var */ +#define JS_HAS_FUN_EXPR_STMT 1 /* has function expression statement */ +#define JS_HAS_LVALUE_RETURN 1 /* has o.item(i) = j; for native item */ +#define JS_HAS_NO_SUCH_METHOD 1 /* has o.__noSuchMethod__ handler */ + +#else + +#error "unknown JS_VERSION" + +#endif diff --git a/src/dom/js/jscpucfg.c b/src/dom/js/jscpucfg.c new file mode 100644 index 000000000..aa6c04c4d --- /dev/null +++ b/src/dom/js/jscpucfg.c @@ -0,0 +1,377 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Roland Mainz + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * Generate CPU-specific bit-size and similar #defines. + */ +#include +#include + +#ifdef CROSS_COMPILE +#include +#define INT64 PRInt64 +#else + +#ifdef __MWERKS__ +#define XP_MAC 1 +#endif + +/************************************************************************/ + +/* Generate cpucfg.h */ +#ifdef XP_MAC +#include +#define INT64 UnsignedWide +#else +#if defined(XP_WIN) || defined(XP_OS2) +#ifdef WIN32 +#if defined(__GNUC__) +#define INT64 long long +#else +#define INT64 _int64 +#endif /* __GNUC__ */ +#else +#define INT64 long +#endif +#else +#if defined(HPUX) || defined(__QNX__) || defined(_SCO_DS) || defined(UNIXWARE) +#define INT64 long +#else +#define INT64 long long +#endif +#endif +#endif + +#endif /* CROSS_COMPILE */ + +typedef void *prword; + +struct align_short { + char c; + short a; +}; +struct align_int { + char c; + int a; +}; +struct align_long { + char c; + long a; +}; +struct align_int64 { + char c; + INT64 a; +}; +struct align_fakelonglong { + char c; + struct { + long hi, lo; + } a; +}; +struct align_float { + char c; + float a; +}; +struct align_double { + char c; + double a; +}; +struct align_pointer { + char c; + void *a; +}; +struct align_prword { + char c; + prword a; +}; + +#define ALIGN_OF(type) \ + (((char*)&(((struct align_##type *)0)->a)) - ((char*)0)) + +unsigned int bpb; + +static int Log2(unsigned int n) +{ + int log2 = 0; + + if (n & (n-1)) + log2++; + if (n >> 16) + log2 += 16, n >>= 16; + if (n >> 8) + log2 += 8, n >>= 8; + if (n >> 4) + log2 += 4, n >>= 4; + if (n >> 2) + log2 += 2, n >>= 2; + if (n >> 1) + log2++; + return log2; +} + +/* + * Conceivably this could actually be used, but there is lots of code out + * there with ands and shifts in it that assumes a byte is exactly 8 bits, + * so forget about porting THIS code to all those non 8 bit byte machines. + */ +static void BitsPerByte(void) +{ + bpb = 8; +} + +static int StackGrowthDirection(int *dummy1addr) +{ + int dummy2; + + return (&dummy2 < dummy1addr) ? -1 : 1; +} + +int main(int argc, char **argv) +{ + int sizeof_char, sizeof_short, sizeof_int, sizeof_int64, sizeof_long, + sizeof_float, sizeof_double, sizeof_word, sizeof_dword; + int bits_per_int64_log2, align_of_short, align_of_int, align_of_long, + align_of_int64, align_of_float, align_of_double, align_of_pointer, + align_of_word; + int dummy1; + + BitsPerByte(); + + printf("#ifndef js_cpucfg___\n"); + printf("#define js_cpucfg___\n\n"); + + printf("/* AUTOMATICALLY GENERATED - DO NOT EDIT */\n\n"); + +#ifdef CROSS_COMPILE +#if defined(IS_LITTLE_ENDIAN) + printf("#define IS_LITTLE_ENDIAN 1\n"); + printf("#undef IS_BIG_ENDIAN\n\n"); +#elif defined(IS_BIG_ENDIAN) + printf("#undef IS_LITTLE_ENDIAN\n"); + printf("#define IS_BIG_ENDIAN 1\n\n"); +#else +#error "Endianess not defined." +#endif + + sizeof_char = PR_BYTES_PER_BYTE; + sizeof_short = PR_BYTES_PER_SHORT; + sizeof_int = PR_BYTES_PER_INT; + sizeof_int64 = PR_BYTES_PER_INT64; + sizeof_long = PR_BYTES_PER_LONG; + sizeof_float = PR_BYTES_PER_FLOAT; + sizeof_double = PR_BYTES_PER_DOUBLE; + sizeof_word = PR_BYTES_PER_WORD; + sizeof_dword = PR_BYTES_PER_DWORD; + + bits_per_int64_log2 = PR_BITS_PER_INT64_LOG2; + + align_of_short = PR_ALIGN_OF_SHORT; + align_of_int = PR_ALIGN_OF_INT; + align_of_long = PR_ALIGN_OF_LONG; + align_of_int64 = PR_ALIGN_OF_INT64; + align_of_float = PR_ALIGN_OF_FLOAT; + align_of_double = PR_ALIGN_OF_DOUBLE; + align_of_pointer = PR_ALIGN_OF_POINTER; + align_of_word = PR_ALIGN_OF_WORD; + +#else /* !CROSS_COMPILE */ + + /* + * We don't handle PDP-endian or similar orders: if a short is big-endian, + * so must int and long be big-endian for us to generate the IS_BIG_ENDIAN + * #define and the IS_LITTLE_ENDIAN #undef. + */ + { + int big_endian = 0, little_endian = 0, ntests = 0; + + if (sizeof(short) == 2) { + /* force |volatile| here to get rid of any compiler optimisations + * (var in register etc.) which may be appiled to |auto| vars - + * even those in |union|s... + * (|static| is used to get the same functionality for compilers + * which do not honor |volatile|...). + */ + volatile static union { + short i; + char c[2]; + } u; + + u.i = 0x0102; + big_endian += (u.c[0] == 0x01 && u.c[1] == 0x02); + little_endian += (u.c[0] == 0x02 && u.c[1] == 0x01); + ntests++; + } + + if (sizeof(int) == 4) { + /* force |volatile| here ... */ + volatile static union { + int i; + char c[4]; + } u; + + u.i = 0x01020304; + big_endian += (u.c[0] == 0x01 && u.c[1] == 0x02 && + u.c[2] == 0x03 && u.c[3] == 0x04); + little_endian += (u.c[0] == 0x04 && u.c[1] == 0x03 && + u.c[2] == 0x02 && u.c[3] == 0x01); + ntests++; + } + + if (sizeof(long) == 8) { + /* force |volatile| here ... */ + volatile static union { + long i; + char c[8]; + } u; + + /* + * Write this as portably as possible: avoid 0x0102030405060708L + * and <<= 32. + */ + u.i = 0x01020304; + u.i <<= 16, u.i <<= 16; + u.i |= 0x05060708; + big_endian += (u.c[0] == 0x01 && u.c[1] == 0x02 && + u.c[2] == 0x03 && u.c[3] == 0x04 && + u.c[4] == 0x05 && u.c[5] == 0x06 && + u.c[6] == 0x07 && u.c[7] == 0x08); + little_endian += (u.c[0] == 0x08 && u.c[1] == 0x07 && + u.c[2] == 0x06 && u.c[3] == 0x05 && + u.c[4] == 0x04 && u.c[5] == 0x03 && + u.c[6] == 0x02 && u.c[7] == 0x01); + ntests++; + } + + if (big_endian && big_endian == ntests) { + printf("#undef IS_LITTLE_ENDIAN\n"); + printf("#define IS_BIG_ENDIAN 1\n\n"); + } else if (little_endian && little_endian == ntests) { + printf("#define IS_LITTLE_ENDIAN 1\n"); + printf("#undef IS_BIG_ENDIAN\n\n"); + } else { + fprintf(stderr, "%s: unknown byte order" + "(big_endian=%d, little_endian=%d, ntests=%d)!\n", + argv[0], big_endian, little_endian, ntests); + return EXIT_FAILURE; + } + } + + sizeof_char = sizeof(char); + sizeof_short = sizeof(short); + sizeof_int = sizeof(int); + sizeof_int64 = 8; + sizeof_long = sizeof(long); + sizeof_float = sizeof(float); + sizeof_double = sizeof(double); + sizeof_word = sizeof(prword); + sizeof_dword = 8; + + bits_per_int64_log2 = 6; + + align_of_short = ALIGN_OF(short); + align_of_int = ALIGN_OF(int); + align_of_long = ALIGN_OF(long); + if (sizeof(INT64) < 8) { + /* this machine doesn't actually support int64's */ + align_of_int64 = ALIGN_OF(fakelonglong); + } else { + align_of_int64 = ALIGN_OF(int64); + } + align_of_float = ALIGN_OF(float); + align_of_double = ALIGN_OF(double); + align_of_pointer = ALIGN_OF(pointer); + align_of_word = ALIGN_OF(prword); + +#endif /* CROSS_COMPILE */ + + printf("#define JS_BYTES_PER_BYTE %dL\n", sizeof_char); + printf("#define JS_BYTES_PER_SHORT %dL\n", sizeof_short); + printf("#define JS_BYTES_PER_INT %dL\n", sizeof_int); + printf("#define JS_BYTES_PER_INT64 %dL\n", sizeof_int64); + printf("#define JS_BYTES_PER_LONG %dL\n", sizeof_long); + printf("#define JS_BYTES_PER_FLOAT %dL\n", sizeof_float); + printf("#define JS_BYTES_PER_DOUBLE %dL\n", sizeof_double); + printf("#define JS_BYTES_PER_WORD %dL\n", sizeof_word); + printf("#define JS_BYTES_PER_DWORD %dL\n", sizeof_dword); + printf("\n"); + + printf("#define JS_BITS_PER_BYTE %dL\n", bpb); + printf("#define JS_BITS_PER_SHORT %dL\n", bpb * sizeof_short); + printf("#define JS_BITS_PER_INT %dL\n", bpb * sizeof_int); + printf("#define JS_BITS_PER_INT64 %dL\n", bpb * sizeof_int64); + printf("#define JS_BITS_PER_LONG %dL\n", bpb * sizeof_long); + printf("#define JS_BITS_PER_FLOAT %dL\n", bpb * sizeof_float); + printf("#define JS_BITS_PER_DOUBLE %dL\n", bpb * sizeof_double); + printf("#define JS_BITS_PER_WORD %dL\n", bpb * sizeof_word); + printf("\n"); + + printf("#define JS_BITS_PER_BYTE_LOG2 %dL\n", Log2(bpb)); + printf("#define JS_BITS_PER_SHORT_LOG2 %dL\n", Log2(bpb * sizeof_short)); + printf("#define JS_BITS_PER_INT_LOG2 %dL\n", Log2(bpb * sizeof_int)); + printf("#define JS_BITS_PER_INT64_LOG2 %dL\n", bits_per_int64_log2); + printf("#define JS_BITS_PER_LONG_LOG2 %dL\n", Log2(bpb * sizeof_long)); + printf("#define JS_BITS_PER_FLOAT_LOG2 %dL\n", Log2(bpb * sizeof_float)); + printf("#define JS_BITS_PER_DOUBLE_LOG2 %dL\n", Log2(bpb * sizeof_double)); + printf("#define JS_BITS_PER_WORD_LOG2 %dL\n", Log2(bpb * sizeof_word)); + printf("\n"); + + printf("#define JS_ALIGN_OF_SHORT %dL\n", align_of_short); + printf("#define JS_ALIGN_OF_INT %dL\n", align_of_int); + printf("#define JS_ALIGN_OF_LONG %dL\n", align_of_long); + printf("#define JS_ALIGN_OF_INT64 %dL\n", align_of_int64); + printf("#define JS_ALIGN_OF_FLOAT %dL\n", align_of_float); + printf("#define JS_ALIGN_OF_DOUBLE %dL\n", align_of_double); + printf("#define JS_ALIGN_OF_POINTER %dL\n", align_of_pointer); + printf("#define JS_ALIGN_OF_WORD %dL\n", align_of_word); + printf("\n"); + + printf("#define JS_BYTES_PER_WORD_LOG2 %dL\n", Log2(sizeof_word)); + printf("#define JS_BYTES_PER_DWORD_LOG2 %dL\n", Log2(sizeof_dword)); + printf("#define JS_WORDS_PER_DWORD_LOG2 %dL\n", Log2(sizeof_dword/sizeof_word)); + printf("\n"); + + printf("#define JS_STACK_GROWTH_DIRECTION (%d)\n", StackGrowthDirection(&dummy1)); + printf("\n"); + + printf("#endif /* js_cpucfg___ */\n"); + + return EXIT_SUCCESS; +} + diff --git a/src/dom/js/jscpucfg.h b/src/dom/js/jscpucfg.h new file mode 100644 index 000000000..e93f5531d --- /dev/null +++ b/src/dom/js/jscpucfg.h @@ -0,0 +1,200 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef js_cpucfg___ +#define js_cpucfg___ + +#include "jsosdep.h" + +#ifdef XP_MAC +#undef IS_LITTLE_ENDIAN +#define IS_BIG_ENDIAN 1 + +#define JS_BYTES_PER_BYTE 1L +#define JS_BYTES_PER_SHORT 2L +#define JS_BYTES_PER_INT 4L +#define JS_BYTES_PER_INT64 8L +#define JS_BYTES_PER_LONG 4L +#define JS_BYTES_PER_FLOAT 4L +#define JS_BYTES_PER_DOUBLE 8L +#define JS_BYTES_PER_WORD 4L +#define JS_BYTES_PER_DWORD 8L + +#define JS_BITS_PER_BYTE 8L +#define JS_BITS_PER_SHORT 16L +#define JS_BITS_PER_INT 32L +#define JS_BITS_PER_INT64 64L +#define JS_BITS_PER_LONG 32L +#define JS_BITS_PER_FLOAT 32L +#define JS_BITS_PER_DOUBLE 64L +#define JS_BITS_PER_WORD 32L + +#define JS_BITS_PER_BYTE_LOG2 3L +#define JS_BITS_PER_SHORT_LOG2 4L +#define JS_BITS_PER_INT_LOG2 5L +#define JS_BITS_PER_INT64_LOG2 6L +#define JS_BITS_PER_LONG_LOG2 5L +#define JS_BITS_PER_FLOAT_LOG2 5L +#define JS_BITS_PER_DOUBLE_LOG2 6L +#define JS_BITS_PER_WORD_LOG2 5L + +#define JS_ALIGN_OF_SHORT 2L +#define JS_ALIGN_OF_INT 4L +#define JS_ALIGN_OF_LONG 4L +#define JS_ALIGN_OF_INT64 2L +#define JS_ALIGN_OF_FLOAT 4L +#define JS_ALIGN_OF_DOUBLE 4L +#define JS_ALIGN_OF_POINTER 4L +#define JS_ALIGN_OF_WORD 4L + +#define JS_BYTES_PER_WORD_LOG2 2L +#define JS_BYTES_PER_DWORD_LOG2 3L +#define PR_WORDS_PER_DWORD_LOG2 1L + +#elif defined(XP_WIN) || defined(XP_OS2) + +#if defined( _WIN32) || defined(XP_OS2) +#define IS_LITTLE_ENDIAN 1 +#undef IS_BIG_ENDIAN + +#define JS_BYTES_PER_BYTE 1L +#define JS_BYTES_PER_SHORT 2L +#define JS_BYTES_PER_INT 4L +#define JS_BYTES_PER_INT64 8L +#define JS_BYTES_PER_LONG 4L +#define JS_BYTES_PER_FLOAT 4L +#define JS_BYTES_PER_DOUBLE 8L +#define JS_BYTES_PER_WORD 4L +#define JS_BYTES_PER_DWORD 8L + +#define JS_BITS_PER_BYTE 8L +#define JS_BITS_PER_SHORT 16L +#define JS_BITS_PER_INT 32L +#define JS_BITS_PER_INT64 64L +#define JS_BITS_PER_LONG 32L +#define JS_BITS_PER_FLOAT 32L +#define JS_BITS_PER_DOUBLE 64L +#define JS_BITS_PER_WORD 32L + +#define JS_BITS_PER_BYTE_LOG2 3L +#define JS_BITS_PER_SHORT_LOG2 4L +#define JS_BITS_PER_INT_LOG2 5L +#define JS_BITS_PER_INT64_LOG2 6L +#define JS_BITS_PER_LONG_LOG2 5L +#define JS_BITS_PER_FLOAT_LOG2 5L +#define JS_BITS_PER_DOUBLE_LOG2 6L +#define JS_BITS_PER_WORD_LOG2 5L + +#define JS_ALIGN_OF_SHORT 2L +#define JS_ALIGN_OF_INT 4L +#define JS_ALIGN_OF_LONG 4L +#define JS_ALIGN_OF_INT64 8L +#define JS_ALIGN_OF_FLOAT 4L +#define JS_ALIGN_OF_DOUBLE 4L +#define JS_ALIGN_OF_POINTER 4L +#define JS_ALIGN_OF_WORD 4L + +#define JS_BYTES_PER_WORD_LOG2 2L +#define JS_BYTES_PER_DWORD_LOG2 3L +#define PR_WORDS_PER_DWORD_LOG2 1L +#endif /* _WIN32 || XP_OS2 */ + +#if defined(_WINDOWS) && !defined(_WIN32) /* WIN16 */ +#define IS_LITTLE_ENDIAN 1 +#undef IS_BIG_ENDIAN + +#define JS_BYTES_PER_BYTE 1L +#define JS_BYTES_PER_SHORT 2L +#define JS_BYTES_PER_INT 2L +#define JS_BYTES_PER_INT64 8L +#define JS_BYTES_PER_LONG 4L +#define JS_BYTES_PER_FLOAT 4L +#define JS_BYTES_PER_DOUBLE 8L +#define JS_BYTES_PER_WORD 4L +#define JS_BYTES_PER_DWORD 8L + +#define JS_BITS_PER_BYTE 8L +#define JS_BITS_PER_SHORT 16L +#define JS_BITS_PER_INT 16L +#define JS_BITS_PER_INT64 64L +#define JS_BITS_PER_LONG 32L +#define JS_BITS_PER_FLOAT 32L +#define JS_BITS_PER_DOUBLE 64L +#define JS_BITS_PER_WORD 32L + +#define JS_BITS_PER_BYTE_LOG2 3L +#define JS_BITS_PER_SHORT_LOG2 4L +#define JS_BITS_PER_INT_LOG2 4L +#define JS_BITS_PER_INT64_LOG2 6L +#define JS_BITS_PER_LONG_LOG2 5L +#define JS_BITS_PER_FLOAT_LOG2 5L +#define JS_BITS_PER_DOUBLE_LOG2 6L +#define JS_BITS_PER_WORD_LOG2 5L + +#define JS_ALIGN_OF_SHORT 2L +#define JS_ALIGN_OF_INT 2L +#define JS_ALIGN_OF_LONG 2L +#define JS_ALIGN_OF_INT64 2L +#define JS_ALIGN_OF_FLOAT 2L +#define JS_ALIGN_OF_DOUBLE 2L +#define JS_ALIGN_OF_POINTER 2L +#define JS_ALIGN_OF_WORD 2L + +#define JS_BYTES_PER_WORD_LOG2 2L +#define JS_BYTES_PER_DWORD_LOG2 3L +#define PR_WORDS_PER_DWORD_LOG2 1L +#endif /* defined(_WINDOWS) && !defined(_WIN32) */ + +#elif defined(XP_UNIX) || defined(XP_BEOS) + +#error "This file is supposed to be auto-generated on UNIX platforms, but the" +#error "static version for Mac and Windows platforms is being used." +#error "Something's probably wrong with paths/headers/dependencies/Makefiles." + +#else + +#error "Must define one of XP_BEOS, XP_MAC, XP_OS2, XP_WIN, or XP_UNIX" + +#endif + +#ifndef JS_STACK_GROWTH_DIRECTION +#define JS_STACK_GROWTH_DIRECTION (-1) +#endif + +#endif /* js_cpucfg___ */ diff --git a/src/dom/js/jsdate.c b/src/dom/js/jsdate.c new file mode 100644 index 000000000..a88df1830 --- /dev/null +++ b/src/dom/js/jsdate.c @@ -0,0 +1,2234 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS date methods. + */ + +/* + * "For example, OS/360 devotes 26 bytes of the permanently + * resident date-turnover routine to the proper handling of + * December 31 on leap years (when it is Day 366). That + * might have been left to the operator." + * + * Frederick Brooks, 'The Second-System Effect'. + */ + +#include "jsstddef.h" +#include +#include +#include +#include +#include "jstypes.h" +#include "jsprf.h" +#include "prmjtime.h" +#include "jsutil.h" /* Added by JSIFY */ +#include "jsapi.h" +#include "jsconfig.h" +#include "jscntxt.h" +#include "jsdate.h" +#include "jsinterp.h" +#include "jsnum.h" +#include "jsobj.h" +#include "jsstr.h" + +/* + * The JS 'Date' object is patterned after the Java 'Date' object. + * Here is an script: + * + * today = new Date(); + * + * print(today.toLocaleString()); + * + * weekDay = today.getDay(); + * + * + * These Java (and ECMA-262) methods are supported: + * + * UTC + * getDate (getUTCDate) + * getDay (getUTCDay) + * getHours (getUTCHours) + * getMinutes (getUTCMinutes) + * getMonth (getUTCMonth) + * getSeconds (getUTCSeconds) + * getMilliseconds (getUTCMilliseconds) + * getTime + * getTimezoneOffset + * getYear + * getFullYear (getUTCFullYear) + * parse + * setDate (setUTCDate) + * setHours (setUTCHours) + * setMinutes (setUTCMinutes) + * setMonth (setUTCMonth) + * setSeconds (setUTCSeconds) + * setMilliseconds (setUTCMilliseconds) + * setTime + * setYear (setFullYear, setUTCFullYear) + * toGMTString (toUTCString) + * toLocaleString + * toString + * + * + * These Java methods are not supported + * + * setDay + * before + * after + * equals + * hashCode + */ + +/* + * 11/97 - jsdate.c has been rewritten to conform to the ECMA-262 language + * definition and reduce dependence on NSPR. NSPR is used to get the current + * time in milliseconds, the time zone offset, and the daylight savings time + * offset for a given time. NSPR is also used for Date.toLocaleString(), for + * locale-specific formatting, and to get a string representing the timezone. + * (Which turns out to be platform-dependent.) + * + * To do: + * (I did some performance tests by timing how long it took to run what + * I had of the js ECMA conformance tests.) + * + * - look at saving results across multiple calls to supporting + * functions; the toString functions compute some of the same values + * multiple times. Although - I took a quick stab at this, and I lost + * rather than gained. (Fractionally.) Hard to tell what compilers/processors + * are doing these days. + * + * - look at tweaking function return types to return double instead + * of int; this seems to make things run slightly faster sometimes. + * (though it could be architecture-dependent.) It'd be good to see + * how this does on win32. (Tried it on irix.) Types could use a + * general going-over. + */ + +/* + * Supporting functions - ECMA 15.9.1.* + */ + +#define HalfTimeDomain 8.64e15 +#define HoursPerDay 24.0 +#define MinutesPerDay (HoursPerDay * MinutesPerHour) +#define MinutesPerHour 60.0 +#define SecondsPerDay (MinutesPerDay * SecondsPerMinute) +#define SecondsPerHour (MinutesPerHour * SecondsPerMinute) +#define SecondsPerMinute 60.0 + +#if defined(XP_WIN) || defined(XP_OS2) +/* Work around msvc double optimization bug by making these runtime values; if + * they're available at compile time, msvc optimizes division by them by + * computing the reciprocal and multiplying instead of dividing - this loses + * when the reciprocal isn't representable in a double. + */ +static jsdouble msPerSecond = 1000.0; +static jsdouble msPerDay = SecondsPerDay * 1000.0; +static jsdouble msPerHour = SecondsPerHour * 1000.0; +static jsdouble msPerMinute = SecondsPerMinute * 1000.0; +#else +#define msPerDay (SecondsPerDay * msPerSecond) +#define msPerHour (SecondsPerHour * msPerSecond) +#define msPerMinute (SecondsPerMinute * msPerSecond) +#define msPerSecond 1000.0 +#endif + +#define Day(t) floor((t) / msPerDay) + +static jsdouble +TimeWithinDay(jsdouble t) +{ + jsdouble result; + result = fmod(t, msPerDay); + if (result < 0) + result += msPerDay; + return result; +} + +#define DaysInYear(y) ((y) % 4 == 0 && ((y) % 100 || ((y) % 400 == 0)) \ + ? 366 : 365) + +/* math here has to be f.p, because we need + * floor((1968 - 1969) / 4) == -1 + */ +#define DayFromYear(y) (365 * ((y)-1970) + floor(((y)-1969)/4.0) \ + - floor(((y)-1901)/100.0) + floor(((y)-1601)/400.0)) +#define TimeFromYear(y) (DayFromYear(y) * msPerDay) + +static jsint +YearFromTime(jsdouble t) +{ + jsint y = (jsint) floor(t /(msPerDay*365.2425)) + 1970; + jsdouble t2 = (jsdouble) TimeFromYear(y); + + if (t2 > t) { + y--; + } else { + if (t2 + msPerDay * DaysInYear(y) <= t) + y++; + } + return y; +} + +#define InLeapYear(t) (JSBool) (DaysInYear(YearFromTime(t)) == 366) + +#define DayWithinYear(t, year) ((intN) (Day(t) - DayFromYear(year))) + +/* + * The following array contains the day of year for the first day of + * each month, where index 0 is January, and day 0 is January 1. + */ +static jsdouble firstDayOfMonth[2][12] = { + {0.0, 31.0, 59.0, 90.0, 120.0, 151.0, 181.0, 212.0, 243.0, 273.0, 304.0, 334.0}, + {0.0, 31.0, 60.0, 91.0, 121.0, 152.0, 182.0, 213.0, 244.0, 274.0, 305.0, 335.0} +}; + +#define DayFromMonth(m, leap) firstDayOfMonth[leap][(intN)m]; + +static intN +MonthFromTime(jsdouble t) +{ + intN d, step; + jsint year = YearFromTime(t); + d = DayWithinYear(t, year); + + if (d < (step = 31)) + return 0; + step += (InLeapYear(t) ? 29 : 28); + if (d < step) + return 1; + if (d < (step += 31)) + return 2; + if (d < (step += 30)) + return 3; + if (d < (step += 31)) + return 4; + if (d < (step += 30)) + return 5; + if (d < (step += 31)) + return 6; + if (d < (step += 31)) + return 7; + if (d < (step += 30)) + return 8; + if (d < (step += 31)) + return 9; + if (d < (step += 30)) + return 10; + return 11; +} + +static intN +DateFromTime(jsdouble t) +{ + intN d, step, next; + jsint year = YearFromTime(t); + d = DayWithinYear(t, year); + + if (d <= (next = 30)) + return d + 1; + step = next; + next += (InLeapYear(t) ? 29 : 28); + if (d <= next) + return d - step; + step = next; + if (d <= (next += 31)) + return d - step; + step = next; + if (d <= (next += 30)) + return d - step; + step = next; + if (d <= (next += 31)) + return d - step; + step = next; + if (d <= (next += 30)) + return d - step; + step = next; + if (d <= (next += 31)) + return d - step; + step = next; + if (d <= (next += 31)) + return d - step; + step = next; + if (d <= (next += 30)) + return d - step; + step = next; + if (d <= (next += 31)) + return d - step; + step = next; + if (d <= (next += 30)) + return d - step; + step = next; + return d - step; +} + +static intN +WeekDay(jsdouble t) +{ + jsint result; + result = (jsint) Day(t) + 4; + result = result % 7; + if (result < 0) + result += 7; + return (intN) result; +} + +/* LocalTZA gets set by js_InitDateClass() */ +static jsdouble LocalTZA; + +static jsdouble +DaylightSavingTA(jsdouble t) +{ + volatile int64 PR_t; + int64 ms2us; + int64 offset; + jsdouble result; + + /* abort if NaN */ + if (JSDOUBLE_IS_NaN(t)) + return t; + + /* put our t in an LL, and map it to usec for prtime */ + JSLL_D2L(PR_t, t); + JSLL_I2L(ms2us, PRMJ_USEC_PER_MSEC); + JSLL_MUL(PR_t, PR_t, ms2us); + + offset = PRMJ_DSTOffset(PR_t); + + JSLL_DIV(offset, offset, ms2us); + JSLL_L2D(result, offset); + return result; +} + + +#define AdjustTime(t) fmod(LocalTZA + DaylightSavingTA(t), msPerDay) + +#define LocalTime(t) ((t) + AdjustTime(t)) + +static jsdouble +UTC(jsdouble t) +{ + return t - AdjustTime(t - LocalTZA); +} + +static intN +HourFromTime(jsdouble t) +{ + intN result = (intN) fmod(floor(t/msPerHour), HoursPerDay); + if (result < 0) + result += (intN)HoursPerDay; + return result; +} + +static intN +MinFromTime(jsdouble t) +{ + intN result = (intN) fmod(floor(t / msPerMinute), MinutesPerHour); + if (result < 0) + result += (intN)MinutesPerHour; + return result; +} + +static intN +SecFromTime(jsdouble t) +{ + intN result = (intN) fmod(floor(t / msPerSecond), SecondsPerMinute); + if (result < 0) + result += (intN)SecondsPerMinute; + return result; +} + +static intN +msFromTime(jsdouble t) +{ + intN result = (intN) fmod(t, msPerSecond); + if (result < 0) + result += (intN)msPerSecond; + return result; +} + +#define MakeTime(hour, min, sec, ms) \ +(((hour * MinutesPerHour + min) * SecondsPerMinute + sec) * msPerSecond + ms) + +static jsdouble +MakeDay(jsdouble year, jsdouble month, jsdouble date) +{ + jsdouble result; + JSBool leap; + jsdouble yearday; + jsdouble monthday; + + year += floor(month / 12); + + month = fmod(month, 12.0); + if (month < 0) + month += 12; + + leap = (DaysInYear((jsint) year) == 366); + + yearday = floor(TimeFromYear(year) / msPerDay); + monthday = DayFromMonth(month, leap); + + result = yearday + + monthday + + date - 1; + return result; +} + +#define MakeDate(day, time) (day * msPerDay + time) + +#define TIMECLIP(d) ((JSDOUBLE_IS_FINITE(d) \ + && !((d < 0 ? -d : d) > HalfTimeDomain)) \ + ? js_DoubleToInteger(d + (+0.)) : *cx->runtime->jsNaN) + +/** + * end of ECMA 'support' functions + */ + +/* + * Other Support routines and definitions + */ + +static JSClass date_class = { + js_Date_str, + JSCLASS_HAS_PRIVATE, + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS +}; + +/* for use by date_parse */ + +static const char* wtb[] = { + "am", "pm", + "monday", "tuesday", "wednesday", "thursday", "friday", + "saturday", "sunday", + "january", "february", "march", "april", "may", "june", + "july", "august", "september", "october", "november", "december", + "gmt", "ut", "utc", + "est", "edt", + "cst", "cdt", + "mst", "mdt", + "pst", "pdt" + /* time zone table needs to be expanded */ +}; + +static int ttb[] = { + -1, -2, 0, 0, 0, 0, 0, 0, 0, /* AM/PM */ + 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, + 10000 + 0, 10000 + 0, 10000 + 0, /* GMT/UT/UTC */ + 10000 + 5 * 60, 10000 + 4 * 60, /* EST/EDT */ + 10000 + 6 * 60, 10000 + 5 * 60, /* CST/CDT */ + 10000 + 7 * 60, 10000 + 6 * 60, /* MST/MDT */ + 10000 + 8 * 60, 10000 + 7 * 60 /* PST/PDT */ +}; + +/* helper for date_parse */ +static JSBool +date_regionMatches(const char* s1, int s1off, const jschar* s2, int s2off, + int count, int ignoreCase) +{ + JSBool result = JS_FALSE; + /* return true if matches, otherwise, false */ + + while (count > 0 && s1[s1off] && s2[s2off]) { + if (ignoreCase) { + if (JS_TOLOWER((jschar)s1[s1off]) != JS_TOLOWER(s2[s2off])) { + break; + } + } else { + if ((jschar)s1[s1off] != s2[s2off]) { + break; + } + } + s1off++; + s2off++; + count--; + } + + if (count == 0) { + result = JS_TRUE; + } + + return result; +} + +/* find UTC time from given date... no 1900 correction! */ +static jsdouble +date_msecFromDate(jsdouble year, jsdouble mon, jsdouble mday, jsdouble hour, + jsdouble min, jsdouble sec, jsdouble msec) +{ + jsdouble day; + jsdouble msec_time; + jsdouble result; + + day = MakeDay(year, mon, mday); + msec_time = MakeTime(hour, min, sec, msec); + result = MakeDate(day, msec_time); + return result; +} + +/* + * See ECMA 15.9.4.[3-10]; + */ +/* XXX this function must be above date_parseString to avoid a + horrid bug in the Win16 1.52 compiler */ +#define MAXARGS 7 +static JSBool +date_UTC(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsdouble array[MAXARGS]; + uintN loop; + jsdouble d; + + for (loop = 0; loop < MAXARGS; loop++) { + if (loop < argc) { + if (!js_ValueToNumber(cx, argv[loop], &d)) + return JS_FALSE; + /* return NaN if any arg is NaN */ + if (!JSDOUBLE_IS_FINITE(d)) { + return js_NewNumberValue(cx, d, rval); + } + array[loop] = floor(d); + } else { + array[loop] = 0; + } + } + + /* adjust 2-digit years into the 20th century */ + if (array[0] >= 0 && array[0] <= 99) + array[0] += 1900; + + /* if we got a 0 for 'date' (which is out of range) + * pretend it's a 1. (So Date.UTC(1972, 5) works) */ + if (array[2] < 1) + array[2] = 1; + + d = date_msecFromDate(array[0], array[1], array[2], + array[3], array[4], array[5], array[6]); + d = TIMECLIP(d); + + return js_NewNumberValue(cx, d, rval); +} + +static JSBool +date_parseString(JSString *str, jsdouble *result) +{ + jsdouble msec; + + const jschar *s = JSSTRING_CHARS(str); + size_t limit = JSSTRING_LENGTH(str); + size_t i = 0; + int year = -1; + int mon = -1; + int mday = -1; + int hour = -1; + int min = -1; + int sec = -1; + int c = -1; + int n = -1; + jsdouble tzoffset = -1; /* was an int, overflowed on win16!!! */ + int prevc = 0; + JSBool seenplusminus = JS_FALSE; + + if (limit == 0) + goto syntax; + while (i < limit) { + c = s[i]; + i++; + if (c <= ' ' || c == ',' || c == '-') { + if (c == '-' && '0' <= s[i] && s[i] <= '9') { + prevc = c; + } + continue; + } + if (c == '(') { /* comments) */ + int depth = 1; + while (i < limit) { + c = s[i]; + i++; + if (c == '(') depth++; + else if (c == ')') + if (--depth <= 0) + break; + } + continue; + } + if ('0' <= c && c <= '9') { + n = c - '0'; + while (i < limit && '0' <= (c = s[i]) && c <= '9') { + n = n * 10 + c - '0'; + i++; + } + + /* allow TZA before the year, so + * 'Wed Nov 05 21:49:11 GMT-0800 1997' + * works */ + + /* uses of seenplusminus allow : in TZA, so Java + * no-timezone style of GMT+4:30 works + */ + + if ((prevc == '+' || prevc == '-')/* && year>=0 */) { + /* make ':' case below change tzoffset */ + seenplusminus = JS_TRUE; + + /* offset */ + if (n < 24) + n = n * 60; /* EG. "GMT-3" */ + else + n = n % 100 + n / 100 * 60; /* eg "GMT-0430" */ + if (prevc == '+') /* plus means east of GMT */ + n = -n; + if (tzoffset != 0 && tzoffset != -1) + goto syntax; + tzoffset = n; + } else if (n >= 70 || + (prevc == '/' && mon >= 0 && mday >= 0 && year < 0)) { + if (year >= 0) + goto syntax; + else if (c <= ' ' || c == ',' || c == '/' || i >= limit) + year = n < 100 ? n + 1900 : n; + else + goto syntax; + } else if (c == ':') { + if (hour < 0) + hour = /*byte*/ n; + else if (min < 0) + min = /*byte*/ n; + else + goto syntax; + } else if (c == '/') { + if (mon < 0) + mon = /*byte*/ n-1; + else if (mday < 0) + mday = /*byte*/ n; + else + goto syntax; + } else if (i < limit && c != ',' && c > ' ' && c != '-') { + goto syntax; + } else if (seenplusminus && n < 60) { /* handle GMT-3:30 */ + if (tzoffset < 0) + tzoffset -= n; + else + tzoffset += n; + } else if (hour >= 0 && min < 0) { + min = /*byte*/ n; + } else if (min >= 0 && sec < 0) { + sec = /*byte*/ n; + } else if (mday < 0) { + mday = /*byte*/ n; + } else { + goto syntax; + } + prevc = 0; + } else if (c == '/' || c == ':' || c == '+' || c == '-') { + prevc = c; + } else { + size_t st = i - 1; + int k; + while (i < limit) { + c = s[i]; + if (!(('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z'))) + break; + i++; + } + if (i <= st + 1) + goto syntax; + for (k = (sizeof(wtb)/sizeof(char*)); --k >= 0;) + if (date_regionMatches(wtb[k], 0, s, st, i-st, 1)) { + int action = ttb[k]; + if (action != 0) { + if (action < 0) { + /* + * AM/PM. Count 12:30 AM as 00:30, 12:30 PM as + * 12:30, instead of blindly adding 12 if PM. + */ + JS_ASSERT(action == -1 || action == -2); + if (hour > 12 || hour < 0) { + goto syntax; + } else { + if (action == -1 && hour == 12) { /* am */ + hour = 0; + } else if (action == -2 && hour != 12) { /* pm */ + hour += 12; + } + } + } else if (action <= 13) { /* month! */ + if (mon < 0) { + mon = /*byte*/ (action - 2); + } else { + goto syntax; + } + } else { + tzoffset = action - 10000; + } + } + break; + } + if (k < 0) + goto syntax; + prevc = 0; + } + } + if (year < 0 || mon < 0 || mday < 0) + goto syntax; + if (sec < 0) + sec = 0; + if (min < 0) + min = 0; + if (hour < 0) + hour = 0; + if (tzoffset == -1) { /* no time zone specified, have to use local */ + jsdouble msec_time; + msec_time = date_msecFromDate(year, mon, mday, hour, min, sec, 0); + + *result = UTC(msec_time); + return JS_TRUE; + } + + msec = date_msecFromDate(year, mon, mday, hour, min, sec, 0); + msec += tzoffset * msPerMinute; + *result = msec; + return JS_TRUE; + +syntax: + /* syntax error */ + *result = 0; + return JS_FALSE; +} + +static JSBool +date_parse(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str; + jsdouble result; + + str = js_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + if (!date_parseString(str, &result)) { + *rval = DOUBLE_TO_JSVAL(cx->runtime->jsNaN); + return JS_TRUE; + } + + result = TIMECLIP(result); + return js_NewNumberValue(cx, result, rval); +} + +static JSBool +date_now(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + int64 us, ms, us2ms; + jsdouble msec_time; + + us = PRMJ_Now(); + JSLL_UI2L(us2ms, PRMJ_USEC_PER_MSEC); + JSLL_DIV(ms, us, us2ms); + JSLL_L2D(msec_time, ms); + + return js_NewDoubleValue(cx, msec_time, rval); +} + +/* + * Check that obj is an object of class Date, and get the date value. + * Return NULL on failure. + */ +static jsdouble * +date_getProlog(JSContext *cx, JSObject *obj, jsval *argv) +{ + if (!JS_InstanceOf(cx, obj, &date_class, argv)) + return NULL; + return JSVAL_TO_DOUBLE(OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE)); +} + +/* + * See ECMA 15.9.5.4 thru 15.9.5.23 + */ +static JSBool +date_getTime(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsdouble *date = date_getProlog(cx, obj, argv); + if (!date) + return JS_FALSE; + + return js_NewNumberValue(cx, *date, rval); +} + +static JSBool +date_getYear(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsdouble result; + jsdouble *date = date_getProlog(cx, obj, argv); + if (!date) + return JS_FALSE; + result = *date; + + if (!JSDOUBLE_IS_FINITE(result)) + return js_NewNumberValue(cx, result, rval); + + result = YearFromTime(LocalTime(result)); + + /* + * During the great date rewrite of 1.3, we tried to track the evolving ECMA + * standard, which then had a definition of getYear which always subtracted + * 1900. Which we implemented, not realizing that it was incompatible with + * the old behavior... now, rather than thrash the behavior yet again, + * we've decided to leave it with the - 1900 behavior and point people to + * the getFullYear method. But we try to protect existing scripts that + * have specified a version... + */ + if (cx->version == JSVERSION_1_0 || + cx->version == JSVERSION_1_1 || + cx->version == JSVERSION_1_2) + { + if (result >= 1900 && result < 2000) + result -= 1900; + } else { + result -= 1900; + } + return js_NewNumberValue(cx, result, rval); +} + +static JSBool +date_getFullYear(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + jsdouble result; + jsdouble *date = date_getProlog(cx, obj, argv); + if (!date) + return JS_FALSE; + result = *date; + + if (!JSDOUBLE_IS_FINITE(result)) + return js_NewNumberValue(cx, result, rval); + + result = YearFromTime(LocalTime(result)); + return js_NewNumberValue(cx, result, rval); +} + +static JSBool +date_getUTCFullYear(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + jsdouble result; + jsdouble *date = date_getProlog(cx, obj, argv); + if (!date) + return JS_FALSE; + result = *date; + + if (!JSDOUBLE_IS_FINITE(result)) + return js_NewNumberValue(cx, result, rval); + + result = YearFromTime(result); + return js_NewNumberValue(cx, result, rval); +} + +static JSBool +date_getMonth(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + jsdouble result; + jsdouble *date = date_getProlog(cx, obj, argv); + if (!date) + return JS_FALSE; + result = *date; + + if (!JSDOUBLE_IS_FINITE(result)) + return js_NewNumberValue(cx, result, rval); + + result = MonthFromTime(LocalTime(result)); + return js_NewNumberValue(cx, result, rval); +} + +static JSBool +date_getUTCMonth(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + jsdouble result; + jsdouble *date = date_getProlog(cx, obj, argv); + if (!date) + return JS_FALSE; + result = *date; + + if (!JSDOUBLE_IS_FINITE(result)) + return js_NewNumberValue(cx, result, rval); + + result = MonthFromTime(result); + return js_NewNumberValue(cx, result, rval); +} + +static JSBool +date_getDate(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsdouble result; + jsdouble *date = date_getProlog(cx, obj, argv); + if (!date) + return JS_FALSE; + result = *date; + + if (!JSDOUBLE_IS_FINITE(result)) + return js_NewNumberValue(cx, result, rval); + + result = LocalTime(result); + result = DateFromTime(result); + return js_NewNumberValue(cx, result, rval); +} + +static JSBool +date_getUTCDate(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + jsdouble result; + jsdouble *date = date_getProlog(cx, obj, argv); + if (!date) + return JS_FALSE; + result = *date; + + if (!JSDOUBLE_IS_FINITE(result)) + return js_NewNumberValue(cx, result, rval); + + result = DateFromTime(result); + return js_NewNumberValue(cx, result, rval); +} + +static JSBool +date_getDay(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsdouble result; + jsdouble *date = date_getProlog(cx, obj, argv); + if (!date) + return JS_FALSE; + result = *date; + + if (!JSDOUBLE_IS_FINITE(result)) + return js_NewNumberValue(cx, result, rval); + + result = LocalTime(result); + result = WeekDay(result); + return js_NewNumberValue(cx, result, rval); +} + +static JSBool +date_getUTCDay(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + jsdouble result; + jsdouble *date = date_getProlog(cx, obj, argv); + if (!date) + return JS_FALSE; + result = *date; + + if (!JSDOUBLE_IS_FINITE(result)) + return js_NewNumberValue(cx, result, rval); + + result = WeekDay(result); + return js_NewNumberValue(cx, result, rval); +} + +static JSBool +date_getHours(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + jsdouble result; + jsdouble *date = date_getProlog(cx, obj, argv); + if (!date) + return JS_FALSE; + result = *date; + + if (!JSDOUBLE_IS_FINITE(result)) + return js_NewNumberValue(cx, result, rval); + + result = HourFromTime(LocalTime(result)); + return js_NewNumberValue(cx, result, rval); +} + +static JSBool +date_getUTCHours(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + jsdouble result; + jsdouble *date = date_getProlog(cx, obj, argv); + if (!date) + return JS_FALSE; + result = *date; + + if (!JSDOUBLE_IS_FINITE(result)) + return js_NewNumberValue(cx, result, rval); + + result = HourFromTime(result); + return js_NewNumberValue(cx, result, rval); +} + +static JSBool +date_getMinutes(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + jsdouble result; + jsdouble *date = date_getProlog(cx, obj, argv); + if (!date) + return JS_FALSE; + result = *date; + + if (!JSDOUBLE_IS_FINITE(result)) + return js_NewNumberValue(cx, result, rval); + + result = MinFromTime(LocalTime(result)); + return js_NewNumberValue(cx, result, rval); +} + +static JSBool +date_getUTCMinutes(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + jsdouble result; + jsdouble *date = date_getProlog(cx, obj, argv); + if (!date) + return JS_FALSE; + result = *date; + + if (!JSDOUBLE_IS_FINITE(result)) + return js_NewNumberValue(cx, result, rval); + + result = MinFromTime(result); + return js_NewNumberValue(cx, result, rval); +} + +/* Date.getSeconds is mapped to getUTCSeconds */ + +static JSBool +date_getUTCSeconds(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + jsdouble result; + jsdouble *date = date_getProlog(cx, obj, argv); + if (!date) + return JS_FALSE; + result = *date; + + if (!JSDOUBLE_IS_FINITE(result)) + return js_NewNumberValue(cx, result, rval); + + result = SecFromTime(result); + return js_NewNumberValue(cx, result, rval); +} + +/* Date.getMilliseconds is mapped to getUTCMilliseconds */ + +static JSBool +date_getUTCMilliseconds(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + jsdouble result; + jsdouble *date = date_getProlog(cx, obj, argv); + if (!date) + return JS_FALSE; + result = *date; + + if (!JSDOUBLE_IS_FINITE(result)) + return js_NewNumberValue(cx, result, rval); + + result = msFromTime(result); + return js_NewNumberValue(cx, result, rval); +} + +static JSBool +date_getTimezoneOffset(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + jsdouble result; + jsdouble *date = date_getProlog(cx, obj, argv); + if (!date) + return JS_FALSE; + result = *date; + + /* + * Return the time zone offset in minutes for the current locale + * that is appropriate for this time. This value would be a + * constant except for daylight savings time. + */ + result = (result - LocalTime(result)) / msPerMinute; + return js_NewNumberValue(cx, result, rval); +} + +static JSBool +date_setTime(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsdouble result; + jsdouble *date = date_getProlog(cx, obj, argv); + if (!date) + return JS_FALSE; + + if (!js_ValueToNumber(cx, argv[0], &result)) + return JS_FALSE; + + result = TIMECLIP(result); + + *date = result; + return js_NewNumberValue(cx, result, rval); +} + +static JSBool +date_makeTime(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + uintN maxargs, JSBool local, jsval *rval) +{ + uintN i; + jsdouble args[4], *argp, *stop; + jsdouble hour, min, sec, msec; + jsdouble lorutime; /* Local or UTC version of *date */ + + jsdouble msec_time; + jsdouble result; + + jsdouble *date = date_getProlog(cx, obj, argv); + if (!date) + return JS_FALSE; + + result = *date; + + /* just return NaN if the date is already NaN */ + if (!JSDOUBLE_IS_FINITE(result)) + return js_NewNumberValue(cx, result, rval); + + /* Satisfy the ECMA rule that if a function is called with + * fewer arguments than the specified formal arguments, the + * remaining arguments are set to undefined. Seems like all + * the Date.setWhatever functions in ECMA are only varargs + * beyond the first argument; this should be set to undefined + * if it's not given. This means that "d = new Date(); + * d.setMilliseconds()" returns NaN. Blech. + */ + if (argc == 0) + argc = 1; /* should be safe, because length of all setters is 1 */ + else if (argc > maxargs) + argc = maxargs; /* clamp argc */ + + for (i = 0; i < argc; i++) { + if (!js_ValueToNumber(cx, argv[i], &args[i])) + return JS_FALSE; + if (!JSDOUBLE_IS_FINITE(args[i])) { + *date = *cx->runtime->jsNaN; + return js_NewNumberValue(cx, *date, rval); + } + args[i] = js_DoubleToInteger(args[i]); + } + + if (local) + lorutime = LocalTime(result); + else + lorutime = result; + + argp = args; + stop = argp + argc; + if (maxargs >= 4 && argp < stop) + hour = *argp++; + else + hour = HourFromTime(lorutime); + + if (maxargs >= 3 && argp < stop) + min = *argp++; + else + min = MinFromTime(lorutime); + + if (maxargs >= 2 && argp < stop) + sec = *argp++; + else + sec = SecFromTime(lorutime); + + if (maxargs >= 1 && argp < stop) + msec = *argp; + else + msec = msFromTime(lorutime); + + msec_time = MakeTime(hour, min, sec, msec); + result = MakeDate(Day(lorutime), msec_time); + +/* fprintf(stderr, "%f\n", result); */ + + if (local) + result = UTC(result); + +/* fprintf(stderr, "%f\n", result); */ + + *date = TIMECLIP(result); + return js_NewNumberValue(cx, *date, rval); +} + +static JSBool +date_setMilliseconds(JSContext *cx, JSObject *obj, uintN argc, + jsval *argv, jsval *rval) +{ + return date_makeTime(cx, obj, argc, argv, 1, JS_TRUE, rval); +} + +static JSBool +date_setUTCMilliseconds(JSContext *cx, JSObject *obj, uintN argc, + jsval *argv, jsval *rval) +{ + return date_makeTime(cx, obj, argc, argv, 1, JS_FALSE, rval); +} + +static JSBool +date_setSeconds(JSContext *cx, JSObject *obj, uintN argc, + jsval *argv, jsval *rval) +{ + return date_makeTime(cx, obj, argc, argv, 2, JS_TRUE, rval); +} + +static JSBool +date_setUTCSeconds(JSContext *cx, JSObject *obj, uintN argc, + jsval *argv, jsval *rval) +{ + return date_makeTime(cx, obj, argc, argv, 2, JS_FALSE, rval); +} + +static JSBool +date_setMinutes(JSContext *cx, JSObject *obj, uintN argc, + jsval *argv, jsval *rval) +{ + return date_makeTime(cx, obj, argc, argv, 3, JS_TRUE, rval); +} + +static JSBool +date_setUTCMinutes(JSContext *cx, JSObject *obj, uintN argc, + jsval *argv, jsval *rval) +{ + return date_makeTime(cx, obj, argc, argv, 3, JS_FALSE, rval); +} + +static JSBool +date_setHours(JSContext *cx, JSObject *obj, uintN argc, + jsval *argv, jsval *rval) +{ + return date_makeTime(cx, obj, argc, argv, 4, JS_TRUE, rval); +} + +static JSBool +date_setUTCHours(JSContext *cx, JSObject *obj, uintN argc, + jsval *argv, jsval *rval) +{ + return date_makeTime(cx, obj, argc, argv, 4, JS_FALSE, rval); +} + +static JSBool +date_makeDate(JSContext *cx, JSObject *obj, uintN argc, + jsval *argv, uintN maxargs, JSBool local, jsval *rval) +{ + uintN i; + jsdouble lorutime; /* local or UTC version of *date */ + jsdouble args[3], *argp, *stop; + jsdouble year, month, day; + jsdouble result; + + jsdouble *date = date_getProlog(cx, obj, argv); + if (!date) + return JS_FALSE; + + result = *date; + + /* see complaint about ECMA in date_MakeTime */ + if (argc == 0) + argc = 1; /* should be safe, because length of all setters is 1 */ + else if (argc > maxargs) + argc = maxargs; /* clamp argc */ + + for (i = 0; i < argc; i++) { + if (!js_ValueToNumber(cx, argv[i], &args[i])) + return JS_FALSE; + if (!JSDOUBLE_IS_FINITE(args[i])) { + *date = *cx->runtime->jsNaN; + return js_NewNumberValue(cx, *date, rval); + } + args[i] = js_DoubleToInteger(args[i]); + } + + /* return NaN if date is NaN and we're not setting the year, + * If we are, use 0 as the time. */ + if (!(JSDOUBLE_IS_FINITE(result))) { + if (argc < 3) + return js_NewNumberValue(cx, result, rval); + else + lorutime = +0.; + } else { + if (local) + lorutime = LocalTime(result); + else + lorutime = result; + } + + argp = args; + stop = argp + argc; + if (maxargs >= 3 && argp < stop) + year = *argp++; + else + year = YearFromTime(lorutime); + + if (maxargs >= 2 && argp < stop) + month = *argp++; + else + month = MonthFromTime(lorutime); + + if (maxargs >= 1 && argp < stop) + day = *argp++; + else + day = DateFromTime(lorutime); + + day = MakeDay(year, month, day); /* day within year */ + result = MakeDate(day, TimeWithinDay(lorutime)); + + if (local) + result = UTC(result); + + *date = TIMECLIP(result); + return js_NewNumberValue(cx, *date, rval); +} + +static JSBool +date_setDate(JSContext *cx, JSObject *obj, uintN argc, + jsval *argv, jsval *rval) +{ + return date_makeDate(cx, obj, argc, argv, 1, JS_TRUE, rval); +} + +static JSBool +date_setUTCDate(JSContext *cx, JSObject *obj, uintN argc, + jsval *argv, jsval *rval) +{ + return date_makeDate(cx, obj, argc, argv, 1, JS_FALSE, rval); +} + +static JSBool +date_setMonth(JSContext *cx, JSObject *obj, uintN argc, + jsval *argv, jsval *rval) +{ + return date_makeDate(cx, obj, argc, argv, 2, JS_TRUE, rval); +} + +static JSBool +date_setUTCMonth(JSContext *cx, JSObject *obj, uintN argc, + jsval *argv, jsval *rval) +{ + return date_makeDate(cx, obj, argc, argv, 2, JS_FALSE, rval); +} + +static JSBool +date_setFullYear(JSContext *cx, JSObject *obj, uintN argc, + jsval *argv, jsval *rval) +{ + return date_makeDate(cx, obj, argc, argv, 3, JS_TRUE, rval); +} + +static JSBool +date_setUTCFullYear(JSContext *cx, JSObject *obj, uintN argc, + jsval *argv, jsval *rval) +{ + return date_makeDate(cx, obj, argc, argv, 3, JS_FALSE, rval); +} + +static JSBool +date_setYear(JSContext *cx, JSObject *obj, uintN argc, + jsval *argv, jsval *rval) +{ + jsdouble t; + jsdouble year; + jsdouble day; + jsdouble result; + + jsdouble *date = date_getProlog(cx, obj, argv); + if (!date) + return JS_FALSE; + + result = *date; + + if (!js_ValueToNumber(cx, argv[0], &year)) + return JS_FALSE; + if (!JSDOUBLE_IS_FINITE(year)) { + *date = *cx->runtime->jsNaN; + return js_NewNumberValue(cx, *date, rval); + } + + year = js_DoubleToInteger(year); + + if (!JSDOUBLE_IS_FINITE(result)) { + t = +0.0; + } else { + t = LocalTime(result); + } + + if (year >= 0 && year <= 99) + year += 1900; + + day = MakeDay(year, MonthFromTime(t), DateFromTime(t)); + result = MakeDate(day, TimeWithinDay(t)); + result = UTC(result); + + *date = TIMECLIP(result); + return js_NewNumberValue(cx, *date, rval); +} + +/* constants for toString, toUTCString */ +static char js_NaN_date_str[] = "Invalid Date"; +static const char* days[] = +{ + "Sun","Mon","Tue","Wed","Thu","Fri","Sat" +}; +static const char* months[] = +{ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +static JSBool +date_toGMTString(JSContext *cx, JSObject *obj, uintN argc, + jsval *argv, jsval *rval) +{ + char buf[100]; + JSString *str; + jsdouble *date = date_getProlog(cx, obj, argv); + if (!date) + return JS_FALSE; + + if (!JSDOUBLE_IS_FINITE(*date)) { + JS_snprintf(buf, sizeof buf, js_NaN_date_str); + } else { + jsdouble temp = *date; + + /* Avoid dependence on PRMJ_FormatTimeUSEnglish, because it + * requires a PRMJTime... which only has 16-bit years. Sub-ECMA. + */ + JS_snprintf(buf, sizeof buf, "%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT", + days[WeekDay(temp)], + DateFromTime(temp), + months[MonthFromTime(temp)], + YearFromTime(temp), + HourFromTime(temp), + MinFromTime(temp), + SecFromTime(temp)); + } + str = JS_NewStringCopyZ(cx, buf); + if (!str) + return JS_FALSE; + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +/* for Date.toLocaleString; interface to PRMJTime date struct. + * If findEquivalent is true, then try to map the year to an equivalent year + * that's in range. + */ +static void +new_explode(jsdouble timeval, PRMJTime *split, JSBool findEquivalent) +{ + jsint year = YearFromTime(timeval); + int16 adjustedYear; + + /* If the year doesn't fit in a PRMJTime, find something to do about it. */ + if (year > 32767 || year < -32768) { + if (findEquivalent) { + /* We're really just trying to get a timezone string; map the year + * to some equivalent year in the range 0 to 2800. Borrowed from + * A. D. Olsen. + */ + jsint cycles; +#define CYCLE_YEARS 2800L + cycles = (year >= 0) ? year / CYCLE_YEARS + : -1 - (-1 - year) / CYCLE_YEARS; + adjustedYear = (int16)(year - cycles * CYCLE_YEARS); + } else { + /* Clamp it to the nearest representable year. */ + adjustedYear = (int16)((year > 0) ? 32767 : - 32768); + } + } else { + adjustedYear = (int16)year; + } + + split->tm_usec = (int32) msFromTime(timeval) * 1000; + split->tm_sec = (int8) SecFromTime(timeval); + split->tm_min = (int8) MinFromTime(timeval); + split->tm_hour = (int8) HourFromTime(timeval); + split->tm_mday = (int8) DateFromTime(timeval); + split->tm_mon = (int8) MonthFromTime(timeval); + split->tm_wday = (int8) WeekDay(timeval); + split->tm_year = (int16) adjustedYear; + split->tm_yday = (int16) DayWithinYear(timeval, year); + + /* not sure how this affects things, but it doesn't seem + to matter. */ + split->tm_isdst = (DaylightSavingTA(timeval) != 0); +} + +typedef enum formatspec { + FORMATSPEC_FULL, FORMATSPEC_DATE, FORMATSPEC_TIME +} formatspec; + +/* helper function */ +static JSBool +date_format(JSContext *cx, jsdouble date, formatspec format, jsval *rval) +{ + char buf[100]; + JSString *str; + char tzbuf[100]; + JSBool usetz; + size_t i, tzlen; + PRMJTime split; + + if (!JSDOUBLE_IS_FINITE(date)) { + JS_snprintf(buf, sizeof buf, js_NaN_date_str); + } else { + jsdouble local = LocalTime(date); + + /* offset from GMT in minutes. The offset includes daylight savings, + if it applies. */ + jsint minutes = (jsint) floor(AdjustTime(date) / msPerMinute); + + /* map 510 minutes to 0830 hours */ + intN offset = (minutes / 60) * 100 + minutes % 60; + + /* print as "Wed Nov 05 19:38:03 GMT-0800 (PST) 1997" The TZA is + * printed as 'GMT-0800' rather than as 'PST' to avoid + * operating-system dependence on strftime (which + * PRMJ_FormatTimeUSEnglish calls, for %Z only.) win32 prints + * PST as 'Pacific Standard Time.' This way we always know + * what we're getting, and can parse it if we produce it. + * The OS TZA string is included as a comment. + */ + + /* get a timezone string from the OS to include as a + comment. */ + new_explode(date, &split, JS_TRUE); + if (PRMJ_FormatTime(tzbuf, sizeof tzbuf, "(%Z)", &split) != 0) { + + /* Decide whether to use the resulting timezone string. + * + * Reject it if it contains any non-ASCII, non-alphanumeric + * characters. It's then likely in some other character + * encoding, and we probably won't display it correctly. + */ + usetz = JS_TRUE; + tzlen = strlen(tzbuf); + if (tzlen > 100) { + usetz = JS_FALSE; + } else { + for (i = 0; i < tzlen; i++) { + jschar c = tzbuf[i]; + if (c > 127 || + !(isalpha(c) || isdigit(c) || + c == ' ' || c == '(' || c == ')')) { + usetz = JS_FALSE; + } + } + } + + /* Also reject it if it's not parenthesized or if it's '()'. */ + if (tzbuf[0] != '(' || tzbuf[1] == ')') + usetz = JS_FALSE; + } else + usetz = JS_FALSE; + + switch (format) { + case FORMATSPEC_FULL: + /* + * Avoid dependence on PRMJ_FormatTimeUSEnglish, because it + * requires a PRMJTime... which only has 16-bit years. Sub-ECMA. + */ + /* Tue Oct 31 2000 09:41:40 GMT-0800 (PST) */ + JS_snprintf(buf, sizeof buf, + "%s %s %.2d %.4d %.2d:%.2d:%.2d GMT%+.4d%s%s", + days[WeekDay(local)], + months[MonthFromTime(local)], + DateFromTime(local), + YearFromTime(local), + HourFromTime(local), + MinFromTime(local), + SecFromTime(local), + offset, + usetz ? " " : "", + usetz ? tzbuf : ""); + break; + case FORMATSPEC_DATE: + /* Tue Oct 31 2000 */ + JS_snprintf(buf, sizeof buf, + "%s %s %.2d %.4d", + days[WeekDay(local)], + months[MonthFromTime(local)], + DateFromTime(local), + YearFromTime(local)); + break; + case FORMATSPEC_TIME: + /* 09:41:40 GMT-0800 (PST) */ + JS_snprintf(buf, sizeof buf, + "%.2d:%.2d:%.2d GMT%+.4d%s%s", + HourFromTime(local), + MinFromTime(local), + SecFromTime(local), + offset, + usetz ? " " : "", + usetz ? tzbuf : ""); + break; + } + } + + str = JS_NewStringCopyZ(cx, buf); + if (!str) + return JS_FALSE; + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +static JSBool +date_toLocaleHelper(JSContext *cx, JSObject *obj, uintN argc, + jsval *argv, jsval *rval, char *format) +{ + char buf[100]; + JSString *str; + PRMJTime split; + jsdouble *date = date_getProlog(cx, obj, argv); + if (!date) + return JS_FALSE; + + if (!JSDOUBLE_IS_FINITE(*date)) { + JS_snprintf(buf, sizeof buf, js_NaN_date_str); + } else { + intN result_len; + jsdouble local = LocalTime(*date); + new_explode(local, &split, JS_FALSE); + + /* let PRMJTime format it. */ + result_len = PRMJ_FormatTime(buf, sizeof buf, format, &split); + + /* If it failed, default to toString. */ + if (result_len == 0) + return date_format(cx, *date, FORMATSPEC_FULL, rval); + + /* Hacked check against undesired 2-digit year 00/00/00 form. */ + if (buf[result_len - 3] == '/' && + isdigit(buf[result_len - 2]) && isdigit(buf[result_len - 1])) { + JS_snprintf(buf + (result_len - 2), (sizeof buf) - (result_len - 2), + "%d", js_DateGetYear(cx, obj)); + } + + } + + str = JS_NewStringCopyZ(cx, buf); + if (!str) + return JS_FALSE; + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +static JSBool +date_toLocaleString(JSContext *cx, JSObject *obj, uintN argc, + jsval *argv, jsval *rval) +{ + /* Use '%#c' for windows, because '%c' is + * backward-compatible and non-y2k with msvc; '%#c' requests that a + * full year be used in the result string. + */ + return date_toLocaleHelper(cx, obj, argc, argv, rval, +#if defined(_WIN32) && !defined(__MWERKS__) + "%#c" +#else + "%c" +#endif + ); +} + +static JSBool +date_toLocaleDateString(JSContext *cx, JSObject *obj, uintN argc, + jsval *argv, jsval *rval) +{ + /* Use '%#x' for windows, because '%x' is + * backward-compatible and non-y2k with msvc; '%#x' requests that a + * full year be used in the result string. + */ + return date_toLocaleHelper(cx, obj, argc, argv, rval, +#if defined(_WIN32) && !defined(__MWERKS__) + "%#x" +#else + "%x" +#endif + ); +} + +static JSBool +date_toLocaleTimeString(JSContext *cx, JSObject *obj, uintN argc, + jsval *argv, jsval *rval) +{ + return date_toLocaleHelper(cx, obj, argc, argv, rval, "%X"); +} + +static JSBool +date_toTimeString(JSContext *cx, JSObject *obj, uintN argc, + jsval *argv, jsval *rval) +{ + jsdouble *date = date_getProlog(cx, obj, argv); + if (!date) + return JS_FALSE; + return date_format(cx, *date, FORMATSPEC_TIME, rval); +} + +static JSBool +date_toDateString(JSContext *cx, JSObject *obj, uintN argc, + jsval *argv, jsval *rval) +{ + jsdouble *date = date_getProlog(cx, obj, argv); + if (!date) + return JS_FALSE; + return date_format(cx, *date, FORMATSPEC_DATE, rval); +} + +#if JS_HAS_TOSOURCE +#include +#include "jsdtoa.h" + +static JSBool +date_toSource(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + jsdouble *date; + char buf[DTOSTR_STANDARD_BUFFER_SIZE], *numStr, *bytes; + JSString *str; + + date = date_getProlog(cx, obj, argv); + if (!date) + return JS_FALSE; + + numStr = JS_dtostr(buf, sizeof buf, DTOSTR_STANDARD, 0, *date); + if (!numStr) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + + bytes = JS_smprintf("(new %s(%s))", date_class.name, numStr); + if (!bytes) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + + str = JS_NewString(cx, bytes, strlen(bytes)); + if (!str) { + free(bytes); + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} +#endif + +static JSBool +date_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + jsdouble *date = date_getProlog(cx, obj, argv); + if (!date) + return JS_FALSE; + return date_format(cx, *date, FORMATSPEC_FULL, rval); +} + +#if JS_HAS_VALUEOF_HINT +static JSBool +date_valueOf(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + /* It is an error to call date_valueOf on a non-date object, but we don't + * need to check for that explicitly here because every path calls + * date_getProlog, which does the check. + */ + + /* If called directly with no arguments, convert to a time number. */ + if (argc == 0) + return date_getTime(cx, obj, argc, argv, rval); + + /* Convert to number only if the hint was given, otherwise favor string. */ + if (argc == 1) { + JSString *str, *str2; + + str = js_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + str2 = ATOM_TO_STRING(cx->runtime->atomState.typeAtoms[JSTYPE_NUMBER]); + if (!js_CompareStrings(str, str2)) + return date_getTime(cx, obj, argc, argv, rval); + } + return date_toString(cx, obj, argc, argv, rval); +} +#else +#define date_valueOf date_getTime +#endif + + +/* + * creation and destruction + */ + +static JSFunctionSpec date_static_methods[] = { + {"UTC", date_UTC, MAXARGS,0,0 }, + {"parse", date_parse, 1,0,0 }, + {"now", date_now, 0,0,0 }, + {0,0,0,0,0} +}; + +static JSFunctionSpec date_methods[] = { + {"getTime", date_getTime, 0,0,0 }, + {"getTimezoneOffset", date_getTimezoneOffset, 0,0,0 }, + {"getYear", date_getYear, 0,0,0 }, + {"getFullYear", date_getFullYear, 0,0,0 }, + {"getUTCFullYear", date_getUTCFullYear, 0,0,0 }, + {"getMonth", date_getMonth, 0,0,0 }, + {"getUTCMonth", date_getUTCMonth, 0,0,0 }, + {"getDate", date_getDate, 0,0,0 }, + {"getUTCDate", date_getUTCDate, 0,0,0 }, + {"getDay", date_getDay, 0,0,0 }, + {"getUTCDay", date_getUTCDay, 0,0,0 }, + {"getHours", date_getHours, 0,0,0 }, + {"getUTCHours", date_getUTCHours, 0,0,0 }, + {"getMinutes", date_getMinutes, 0,0,0 }, + {"getUTCMinutes", date_getUTCMinutes, 0,0,0 }, + {"getSeconds", date_getUTCSeconds, 0,0,0 }, + {"getUTCSeconds", date_getUTCSeconds, 0,0,0 }, + {"getMilliseconds", date_getUTCMilliseconds,0,0,0 }, + {"getUTCMilliseconds", date_getUTCMilliseconds,0,0,0 }, + {"setTime", date_setTime, 1,0,0 }, + {"setYear", date_setYear, 1,0,0 }, + {"setFullYear", date_setFullYear, 3,0,0 }, + {"setUTCFullYear", date_setUTCFullYear, 3,0,0 }, + {"setMonth", date_setMonth, 2,0,0 }, + {"setUTCMonth", date_setUTCMonth, 2,0,0 }, + {"setDate", date_setDate, 1,0,0 }, + {"setUTCDate", date_setUTCDate, 1,0,0 }, + {"setHours", date_setHours, 4,0,0 }, + {"setUTCHours", date_setUTCHours, 4,0,0 }, + {"setMinutes", date_setMinutes, 3,0,0 }, + {"setUTCMinutes", date_setUTCMinutes, 3,0,0 }, + {"setSeconds", date_setSeconds, 2,0,0 }, + {"setUTCSeconds", date_setUTCSeconds, 2,0,0 }, + {"setMilliseconds", date_setMilliseconds, 1,0,0 }, + {"setUTCMilliseconds", date_setUTCMilliseconds,1,0,0 }, + {"toUTCString", date_toGMTString, 0,0,0 }, + {js_toLocaleString_str, date_toLocaleString, 0,0,0 }, + {"toLocaleDateString", date_toLocaleDateString,0,0,0 }, + {"toLocaleTimeString", date_toLocaleTimeString,0,0,0 }, + {"toDateString", date_toDateString, 0,0,0 }, + {"toTimeString", date_toTimeString, 0,0,0 }, +#if JS_HAS_TOSOURCE + {js_toSource_str, date_toSource, 0,0,0 }, +#endif + {js_toString_str, date_toString, 0,0,0 }, + {js_valueOf_str, date_valueOf, 0,0,0 }, + {0,0,0,0,0} +}; + +static jsdouble * +date_constructor(JSContext *cx, JSObject* obj) +{ + jsdouble *date; + + date = js_NewDouble(cx, 0.0); + if (!date) + return NULL; + OBJ_SET_SLOT(cx, obj, JSSLOT_PRIVATE, DOUBLE_TO_JSVAL(date)); + return date; +} + +static JSBool +Date(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsdouble *date; + JSString *str; + jsdouble d; + + /* Date called as function */ + if (!(cx->fp->flags & JSFRAME_CONSTRUCTING)) { + int64 us, ms, us2ms; + jsdouble msec_time; + + /* NSPR 2.0 docs say 'We do not support PRMJ_NowMS and PRMJ_NowS', + * so compute ms from PRMJ_Now. + */ + us = PRMJ_Now(); + JSLL_UI2L(us2ms, PRMJ_USEC_PER_MSEC); + JSLL_DIV(ms, us, us2ms); + JSLL_L2D(msec_time, ms); + + return date_format(cx, msec_time, FORMATSPEC_FULL, rval); + } + + /* Date called as constructor */ + if (argc == 0) { + int64 us, ms, us2ms; + jsdouble msec_time; + + date = date_constructor(cx, obj); + if (!date) + return JS_FALSE; + + us = PRMJ_Now(); + JSLL_UI2L(us2ms, PRMJ_USEC_PER_MSEC); + JSLL_DIV(ms, us, us2ms); + JSLL_L2D(msec_time, ms); + + *date = msec_time; + } else if (argc == 1) { + if (!JSVAL_IS_STRING(argv[0])) { + /* the argument is a millisecond number */ + if (!js_ValueToNumber(cx, argv[0], &d)) + return JS_FALSE; + date = date_constructor(cx, obj); + if (!date) + return JS_FALSE; + *date = TIMECLIP(d); + } else { + /* the argument is a string; parse it. */ + date = date_constructor(cx, obj); + if (!date) + return JS_FALSE; + + str = js_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + + if (!date_parseString(str, date)) + *date = *cx->runtime->jsNaN; + *date = TIMECLIP(*date); + } + } else { + jsdouble array[MAXARGS]; + uintN loop; + jsdouble double_arg; + jsdouble day; + jsdouble msec_time; + + for (loop = 0; loop < MAXARGS; loop++) { + if (loop < argc) { + if (!js_ValueToNumber(cx, argv[loop], &double_arg)) + return JS_FALSE; + /* if any arg is NaN, make a NaN date object + and return */ + if (!JSDOUBLE_IS_FINITE(double_arg)) { + date = date_constructor(cx, obj); + if (!date) + return JS_FALSE; + *date = *cx->runtime->jsNaN; + return JS_TRUE; + } + array[loop] = js_DoubleToInteger(double_arg); + } else { + if (loop == 2) { + array[loop] = 1; /* Default the date argument to 1. */ + } else { + array[loop] = 0; + } + } + } + + date = date_constructor(cx, obj); + if (!date) + return JS_FALSE; + + /* adjust 2-digit years into the 20th century */ + if (array[0] >= 0 && array[0] <= 99) + array[0] += 1900; + + day = MakeDay(array[0], array[1], array[2]); + msec_time = MakeTime(array[3], array[4], array[5], array[6]); + msec_time = MakeDate(day, msec_time); + msec_time = UTC(msec_time); + *date = TIMECLIP(msec_time); + } + return JS_TRUE; +} + +JSObject * +js_InitDateClass(JSContext *cx, JSObject *obj) +{ + JSObject *proto; + jsdouble *proto_date; + + /* set static LocalTZA */ + LocalTZA = -(PRMJ_LocalGMTDifference() * msPerSecond); + proto = JS_InitClass(cx, obj, NULL, &date_class, Date, MAXARGS, + NULL, date_methods, NULL, date_static_methods); + if (!proto) + return NULL; + + /* Alias toUTCString with toGMTString. (ECMA B.2.6) */ + if (!JS_AliasProperty(cx, proto, "toUTCString", "toGMTString")) + return NULL; + + /* Set the value of the Date.prototype date to NaN */ + proto_date = date_constructor(cx, proto); + if (!proto_date) + return NULL; + *proto_date = *cx->runtime->jsNaN; + + return proto; +} + +JS_FRIEND_API(JSObject *) +js_NewDateObjectMsec(JSContext *cx, jsdouble msec_time) +{ + JSObject *obj; + jsdouble *date; + + obj = js_NewObject(cx, &date_class, NULL, NULL); + if (!obj) + return NULL; + + date = date_constructor(cx, obj); + if (!date) + return NULL; + + *date = msec_time; + return obj; +} + +JS_FRIEND_API(JSObject *) +js_NewDateObject(JSContext* cx, int year, int mon, int mday, + int hour, int min, int sec) +{ + JSObject *obj; + jsdouble msec_time; + + msec_time = date_msecFromDate(year, mon, mday, hour, min, sec, 0); + obj = js_NewDateObjectMsec(cx, UTC(msec_time)); + return obj; +} + +JS_FRIEND_API(JSBool) +js_DateIsValid(JSContext *cx, JSObject* obj) +{ + jsdouble *date = date_getProlog(cx, obj, NULL); + + if (!date || JSDOUBLE_IS_NaN(*date)) + return JS_FALSE; + else + return JS_TRUE; +} + +JS_FRIEND_API(int) +js_DateGetYear(JSContext *cx, JSObject* obj) +{ + jsdouble *date = date_getProlog(cx, obj, NULL); + + /* Preserve legacy API behavior of returning 0 for invalid dates. */ + if (!date || JSDOUBLE_IS_NaN(*date)) + return 0; + return (int) YearFromTime(LocalTime(*date)); +} + +JS_FRIEND_API(int) +js_DateGetMonth(JSContext *cx, JSObject* obj) +{ + jsdouble *date = date_getProlog(cx, obj, NULL); + + if (!date || JSDOUBLE_IS_NaN(*date)) + return 0; + return (int) MonthFromTime(LocalTime(*date)); +} + +JS_FRIEND_API(int) +js_DateGetDate(JSContext *cx, JSObject* obj) +{ + jsdouble *date = date_getProlog(cx, obj, NULL); + + if (!date || JSDOUBLE_IS_NaN(*date)) + return 0; + return (int) DateFromTime(LocalTime(*date)); +} + +JS_FRIEND_API(int) +js_DateGetHours(JSContext *cx, JSObject* obj) +{ + jsdouble *date = date_getProlog(cx, obj, NULL); + + if (!date || JSDOUBLE_IS_NaN(*date)) + return 0; + return (int) HourFromTime(LocalTime(*date)); +} + +JS_FRIEND_API(int) +js_DateGetMinutes(JSContext *cx, JSObject* obj) +{ + jsdouble *date = date_getProlog(cx, obj, NULL); + + if (!date || JSDOUBLE_IS_NaN(*date)) + return 0; + return (int) MinFromTime(LocalTime(*date)); +} + +JS_FRIEND_API(int) +js_DateGetSeconds(JSContext *cx, JSObject* obj) +{ + jsdouble *date = date_getProlog(cx, obj, NULL); + + if (!date || JSDOUBLE_IS_NaN(*date)) + return 0; + return (int) SecFromTime(*date); +} + +JS_FRIEND_API(void) +js_DateSetYear(JSContext *cx, JSObject *obj, int year) +{ + jsdouble local; + jsdouble *date = date_getProlog(cx, obj, NULL); + if (!date) + return; + local = LocalTime(*date); + /* reset date if it was NaN */ + if (JSDOUBLE_IS_NaN(local)) + local = 0; + local = date_msecFromDate(year, + MonthFromTime(local), + DateFromTime(local), + HourFromTime(local), + MinFromTime(local), + SecFromTime(local), + msFromTime(local)); + *date = UTC(local); +} + +JS_FRIEND_API(void) +js_DateSetMonth(JSContext *cx, JSObject *obj, int month) +{ + jsdouble local; + jsdouble *date = date_getProlog(cx, obj, NULL); + if (!date) + return; + local = LocalTime(*date); + /* bail if date was NaN */ + if (JSDOUBLE_IS_NaN(local)) + return; + local = date_msecFromDate(YearFromTime(local), + month, + DateFromTime(local), + HourFromTime(local), + MinFromTime(local), + SecFromTime(local), + msFromTime(local)); + *date = UTC(local); +} + +JS_FRIEND_API(void) +js_DateSetDate(JSContext *cx, JSObject *obj, int date) +{ + jsdouble local; + jsdouble *datep = date_getProlog(cx, obj, NULL); + if (!datep) + return; + local = LocalTime(*datep); + if (JSDOUBLE_IS_NaN(local)) + return; + local = date_msecFromDate(YearFromTime(local), + MonthFromTime(local), + date, + HourFromTime(local), + MinFromTime(local), + SecFromTime(local), + msFromTime(local)); + *datep = UTC(local); +} + +JS_FRIEND_API(void) +js_DateSetHours(JSContext *cx, JSObject *obj, int hours) +{ + jsdouble local; + jsdouble *date = date_getProlog(cx, obj, NULL); + if (!date) + return; + local = LocalTime(*date); + if (JSDOUBLE_IS_NaN(local)) + return; + local = date_msecFromDate(YearFromTime(local), + MonthFromTime(local), + DateFromTime(local), + hours, + MinFromTime(local), + SecFromTime(local), + msFromTime(local)); + *date = UTC(local); +} + +JS_FRIEND_API(void) +js_DateSetMinutes(JSContext *cx, JSObject *obj, int minutes) +{ + jsdouble local; + jsdouble *date = date_getProlog(cx, obj, NULL); + if (!date) + return; + local = LocalTime(*date); + if (JSDOUBLE_IS_NaN(local)) + return; + local = date_msecFromDate(YearFromTime(local), + MonthFromTime(local), + DateFromTime(local), + HourFromTime(local), + minutes, + SecFromTime(local), + msFromTime(local)); + *date = UTC(local); +} + +JS_FRIEND_API(void) +js_DateSetSeconds(JSContext *cx, JSObject *obj, int seconds) +{ + jsdouble local; + jsdouble *date = date_getProlog(cx, obj, NULL); + if (!date) + return; + local = LocalTime(*date); + if (JSDOUBLE_IS_NaN(local)) + return; + local = date_msecFromDate(YearFromTime(local), + MonthFromTime(local), + DateFromTime(local), + HourFromTime(local), + MinFromTime(local), + seconds, + msFromTime(local)); + *date = UTC(local); +} + +JS_FRIEND_API(jsdouble) +js_DateGetMsecSinceEpoch(JSContext *cx, JSObject *obj) +{ + jsdouble *date = date_getProlog(cx, obj, NULL); + if (!date || JSDOUBLE_IS_NaN(*date)) + return 0; + return (*date); +} diff --git a/src/dom/js/jsdate.h b/src/dom/js/jsdate.h new file mode 100644 index 000000000..790b4daf6 --- /dev/null +++ b/src/dom/js/jsdate.h @@ -0,0 +1,118 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS Date class interface. + */ + +#ifndef jsdate_h___ +#define jsdate_h___ + +JS_BEGIN_EXTERN_C + +extern JSObject * +js_InitDateClass(JSContext *cx, JSObject *obj); + +/* + * These functions provide a C interface to the date/time object + */ + +/* + * Construct a new Date Object from a time value given in milliseconds UTC + * since the epoch. + */ +extern JS_FRIEND_API(JSObject*) +js_NewDateObjectMsec(JSContext* cx, jsdouble msec_time); + +/* + * Construct a new Date Object from an exploded local time value. + */ +extern JS_FRIEND_API(JSObject*) +js_NewDateObject(JSContext* cx, int year, int mon, int mday, + int hour, int min, int sec); + +/* + * Detect whether the internal date value is NaN. (Because failure is + * out-of-band for js_DateGet*) + */ +extern JS_FRIEND_API(JSBool) +js_DateIsValid(JSContext *cx, JSObject* obj); + +extern JS_FRIEND_API(int) +js_DateGetYear(JSContext *cx, JSObject* obj); + +extern JS_FRIEND_API(int) +js_DateGetMonth(JSContext *cx, JSObject* obj); + +extern JS_FRIEND_API(int) +js_DateGetDate(JSContext *cx, JSObject* obj); + +extern JS_FRIEND_API(int) +js_DateGetHours(JSContext *cx, JSObject* obj); + +extern JS_FRIEND_API(int) +js_DateGetMinutes(JSContext *cx, JSObject* obj); + +extern JS_FRIEND_API(int) +js_DateGetSeconds(JSContext *cx, JSObject* obj); + +extern JS_FRIEND_API(void) +js_DateSetYear(JSContext *cx, JSObject *obj, int year); + +extern JS_FRIEND_API(void) +js_DateSetMonth(JSContext *cx, JSObject *obj, int year); + +extern JS_FRIEND_API(void) +js_DateSetDate(JSContext *cx, JSObject *obj, int date); + +extern JS_FRIEND_API(void) +js_DateSetHours(JSContext *cx, JSObject *obj, int hours); + +extern JS_FRIEND_API(void) +js_DateSetMinutes(JSContext *cx, JSObject *obj, int minutes); + +extern JS_FRIEND_API(void) +js_DateSetSeconds(JSContext *cx, JSObject *obj, int seconds); + +extern JS_FRIEND_API(jsdouble) +js_DateGetMsecSinceEpoch(JSContext *cx, JSObject *obj); + +JS_END_EXTERN_C + +#endif /* jsdate_h___ */ diff --git a/src/dom/js/jsdbgapi.c b/src/dom/js/jsdbgapi.c new file mode 100644 index 000000000..2aa684941 --- /dev/null +++ b/src/dom/js/jsdbgapi.c @@ -0,0 +1,1240 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS debugging API. + */ +#include "jsstddef.h" +#include +#include "jstypes.h" +#include "jsutil.h" /* Added by JSIFY */ +#include "jsclist.h" +#include "jsapi.h" +#include "jscntxt.h" +#include "jsconfig.h" +#include "jsdbgapi.h" +#include "jsfun.h" +#include "jsgc.h" +#include "jsinterp.h" +#include "jslock.h" +#include "jsobj.h" +#include "jsopcode.h" +#include "jsscope.h" +#include "jsscript.h" +#include "jsstr.h" + +typedef struct JSTrap { + JSCList links; + JSScript *script; + jsbytecode *pc; + JSOp op; + JSTrapHandler handler; + void *closure; +} JSTrap; + +static JSTrap * +FindTrap(JSRuntime *rt, JSScript *script, jsbytecode *pc) +{ + JSTrap *trap; + + for (trap = (JSTrap *)rt->trapList.next; + trap != (JSTrap *)&rt->trapList; + trap = (JSTrap *)trap->links.next) { + if (trap->script == script && trap->pc == pc) + return trap; + } + return NULL; +} + +void +js_PatchOpcode(JSContext *cx, JSScript *script, jsbytecode *pc, JSOp op) +{ + JSTrap *trap; + + trap = FindTrap(cx->runtime, script, pc); + if (trap) + trap->op = op; + else + *pc = (jsbytecode)op; +} + +JS_PUBLIC_API(JSBool) +JS_SetTrap(JSContext *cx, JSScript *script, jsbytecode *pc, + JSTrapHandler handler, void *closure) +{ + JSRuntime *rt; + JSTrap *trap; + + rt = cx->runtime; + trap = FindTrap(rt, script, pc); + if (trap) { + JS_ASSERT(trap->script == script && trap->pc == pc); + JS_ASSERT(*pc == JSOP_TRAP); + } else { + trap = (JSTrap *) JS_malloc(cx, sizeof *trap); + if (!trap || !js_AddRoot(cx, &trap->closure, "trap->closure")) { + if (trap) + JS_free(cx, trap); + return JS_FALSE; + } + JS_APPEND_LINK(&trap->links, &rt->trapList); + trap->script = script; + trap->pc = pc; + trap->op = (JSOp)*pc; + *pc = JSOP_TRAP; + } + trap->handler = handler; + trap->closure = closure; + return JS_TRUE; +} + +JS_PUBLIC_API(JSOp) +JS_GetTrapOpcode(JSContext *cx, JSScript *script, jsbytecode *pc) +{ + JSTrap *trap; + + trap = FindTrap(cx->runtime, script, pc); + if (!trap) { + JS_ASSERT(0); /* XXX can't happen */ + return JSOP_LIMIT; + } + return trap->op; +} + +static void +DestroyTrap(JSContext *cx, JSTrap *trap) +{ + JS_REMOVE_LINK(&trap->links); + *trap->pc = (jsbytecode)trap->op; + js_RemoveRoot(cx->runtime, &trap->closure); + JS_free(cx, trap); +} + +JS_PUBLIC_API(void) +JS_ClearTrap(JSContext *cx, JSScript *script, jsbytecode *pc, + JSTrapHandler *handlerp, void **closurep) +{ + JSTrap *trap; + + trap = FindTrap(cx->runtime, script, pc); + if (handlerp) + *handlerp = trap ? trap->handler : NULL; + if (closurep) + *closurep = trap ? trap->closure : NULL; + if (trap) + DestroyTrap(cx, trap); +} + +JS_PUBLIC_API(void) +JS_ClearScriptTraps(JSContext *cx, JSScript *script) +{ + JSRuntime *rt; + JSTrap *trap, *next; + + rt = cx->runtime; + for (trap = (JSTrap *)rt->trapList.next; + trap != (JSTrap *)&rt->trapList; + trap = next) { + next = (JSTrap *)trap->links.next; + if (trap->script == script) + DestroyTrap(cx, trap); + } +} + +JS_PUBLIC_API(void) +JS_ClearAllTraps(JSContext *cx) +{ + JSRuntime *rt; + JSTrap *trap, *next; + + rt = cx->runtime; + for (trap = (JSTrap *)rt->trapList.next; + trap != (JSTrap *)&rt->trapList; + trap = next) { + next = (JSTrap *)trap->links.next; + DestroyTrap(cx, trap); + } +} + +JS_PUBLIC_API(JSTrapStatus) +JS_HandleTrap(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval) +{ + JSTrap *trap; + JSTrapStatus status; + jsint op; + + trap = FindTrap(cx->runtime, script, pc); + if (!trap) { + JS_ASSERT(0); /* XXX can't happen */ + return JSTRAP_ERROR; + } + /* + * It's important that we not use 'trap->' after calling the callback -- + * the callback might remove the trap! + */ + op = (jsint)trap->op; + status = trap->handler(cx, script, pc, rval, trap->closure); + if (status == JSTRAP_CONTINUE) { + /* By convention, return the true op to the interpreter in rval. */ + *rval = INT_TO_JSVAL(op); + } + return status; +} + +JS_PUBLIC_API(JSBool) +JS_SetInterrupt(JSRuntime *rt, JSTrapHandler handler, void *closure) +{ + rt->interruptHandler = handler; + rt->interruptHandlerData = closure; + return JS_TRUE; +} + +JS_PUBLIC_API(JSBool) +JS_ClearInterrupt(JSRuntime *rt, JSTrapHandler *handlerp, void **closurep) +{ + if (handlerp) + *handlerp = (JSTrapHandler)rt->interruptHandler; + if (closurep) + *closurep = rt->interruptHandlerData; + rt->interruptHandler = 0; + rt->interruptHandlerData = 0; + return JS_TRUE; +} + +/************************************************************************/ + +typedef struct JSWatchPoint { + JSCList links; + JSObject *object; /* weak link, see js_FinalizeObject */ + JSScopeProperty *sprop; + JSPropertyOp setter; + JSWatchPointHandler handler; + void *closure; + jsrefcount nrefs; +} JSWatchPoint; + +#define HoldWatchPoint(wp) ((wp)->nrefs++) + +static JSBool +DropWatchPoint(JSContext *cx, JSWatchPoint *wp) +{ + JSScopeProperty *sprop; + + if (--wp->nrefs != 0) + return JS_TRUE; + + /* + * Remove wp from the list, then if there are no other watchpoints for + * wp->sprop in any scope, restore wp->sprop->setter from wp. + */ + JS_REMOVE_LINK(&wp->links); + sprop = wp->sprop; + if (!js_GetWatchedSetter(cx->runtime, NULL, sprop)) { + sprop = js_ChangeNativePropertyAttrs(cx, wp->object, sprop, + 0, sprop->attrs, + sprop->getter, wp->setter); + if (!sprop) + return JS_FALSE; + } + js_RemoveRoot(cx->runtime, &wp->closure); + JS_free(cx, wp); + return JS_TRUE; +} + +void +js_MarkWatchPoints(JSRuntime *rt) +{ + JSWatchPoint *wp; + + for (wp = (JSWatchPoint *)rt->watchPointList.next; + wp != (JSWatchPoint *)&rt->watchPointList; + wp = (JSWatchPoint *)wp->links.next) { + MARK_SCOPE_PROPERTY(wp->sprop); + } +} + +static JSWatchPoint * +FindWatchPoint(JSRuntime *rt, JSScope *scope, jsid id) +{ + JSWatchPoint *wp; + + for (wp = (JSWatchPoint *)rt->watchPointList.next; + wp != (JSWatchPoint *)&rt->watchPointList; + wp = (JSWatchPoint *)wp->links.next) { + if (wp->object == scope->object && wp->sprop->id == id) + return wp; + } + return NULL; +} + +JSScopeProperty * +js_FindWatchPoint(JSRuntime *rt, JSScope *scope, jsid id) +{ + JSWatchPoint *wp; + + wp = FindWatchPoint(rt, scope, id); + if (!wp) + return NULL; + return wp->sprop; +} + +JSPropertyOp +js_GetWatchedSetter(JSRuntime *rt, JSScope *scope, + const JSScopeProperty *sprop) +{ + JSWatchPoint *wp; + + for (wp = (JSWatchPoint *)rt->watchPointList.next; + wp != (JSWatchPoint *)&rt->watchPointList; + wp = (JSWatchPoint *)wp->links.next) { + if ((!scope || wp->object == scope->object) && wp->sprop == sprop) + return wp->setter; + } + return NULL; +} + +JSBool JS_DLL_CALLBACK +js_watch_set(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + JSRuntime *rt; + JSWatchPoint *wp; + JSScopeProperty *sprop; + jsval userid; + JSScope *scope; + JSBool ok; + + rt = cx->runtime; + for (wp = (JSWatchPoint *)rt->watchPointList.next; + wp != (JSWatchPoint *)&rt->watchPointList; + wp = (JSWatchPoint *)wp->links.next) { + sprop = wp->sprop; + if (wp->object == obj && SPROP_USERID(sprop) == id) { + JS_LOCK_OBJ(cx, obj); + userid = SPROP_USERID(sprop); + scope = OBJ_SCOPE(obj); + JS_UNLOCK_OBJ(cx, obj); + HoldWatchPoint(wp); + ok = wp->handler(cx, obj, userid, + SPROP_HAS_VALID_SLOT(sprop, scope) + ? OBJ_GET_SLOT(cx, obj, wp->sprop->slot) + : JSVAL_VOID, + vp, wp->closure); + if (ok) { + /* + * Create pseudo-frame for call to setter so that any + * stack-walking security code in the setter will correctly + * identify the guilty party. + */ + JSObject *funobj = (JSObject *) wp->closure; + JSFunction *fun = (JSFunction *) JS_GetPrivate(cx, funobj); + JSStackFrame frame; + + memset(&frame, 0, sizeof(frame)); + frame.script = fun->script; + frame.fun = fun; + frame.down = cx->fp; + cx->fp = &frame; + ok = !wp->setter || + ((sprop->attrs & JSPROP_SETTER) + ? js_InternalCall(cx, obj, OBJECT_TO_JSVAL(wp->setter), + 1, vp, vp) + : wp->setter(cx, OBJ_THIS_OBJECT(cx, obj), userid, vp)); + cx->fp = frame.down; + } + return DropWatchPoint(cx, wp); + } + } + JS_ASSERT(0); /* XXX can't happen */ + return JS_FALSE; +} + +JSBool JS_DLL_CALLBACK +js_watch_set_wrapper(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSObject *funobj; + JSFunction *wrapper; + jsval userid; + + funobj = JSVAL_TO_OBJECT(argv[-2]); + wrapper = (JSFunction *) JS_GetPrivate(cx, funobj); + userid = ATOM_KEY(wrapper->atom); + *rval = argv[0]; + return js_watch_set(cx, obj, userid, rval); +} + +JSPropertyOp +js_WrapWatchedSetter(JSContext *cx, jsid id, uintN attrs, JSPropertyOp setter) +{ + JSAtom *atom; + JSFunction *wrapper; + + if (!(attrs & JSPROP_SETTER)) + return &js_watch_set; /* & to silence schoolmarmish MSVC */ + + if (!JSVAL_IS_INT(id)) { + atom = (JSAtom *)id; + } else { + atom = js_AtomizeInt(cx, JSVAL_TO_INT(id), 0); + if (!atom) + return NULL; + } + wrapper = js_NewFunction(cx, NULL, js_watch_set_wrapper, 1, 0, + OBJ_GET_PARENT(cx, (JSObject *)setter), + atom); + if (!wrapper) + return NULL; + return (JSPropertyOp) wrapper->object; +} + +JS_PUBLIC_API(JSBool) +JS_SetWatchPoint(JSContext *cx, JSObject *obj, jsval id, + JSWatchPointHandler handler, void *closure) +{ + JSAtom *atom; + jsid propid; + JSObject *pobj; + JSScopeProperty *sprop; + JSRuntime *rt; + JSWatchPoint *wp; + JSPropertyOp watcher; + + if (!OBJ_IS_NATIVE(obj)) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_WATCH, + OBJ_GET_CLASS(cx, obj)->name); + return JS_FALSE; + } + + if (JSVAL_IS_INT(id)) { + propid = (jsid)id; + atom = NULL; + } else { + atom = js_ValueToStringAtom(cx, id); + if (!atom) + return JS_FALSE; + propid = (jsid)atom; + } + + if (!js_LookupProperty(cx, obj, propid, &pobj, (JSProperty **)&sprop)) + return JS_FALSE; + rt = cx->runtime; + if (!sprop) { + /* Check for a deleted symbol watchpoint, which holds its property. */ + sprop = js_FindWatchPoint(rt, OBJ_SCOPE(obj), propid); + if (!sprop) { + /* Make a new property in obj so we can watch for the first set. */ + if (!js_DefineProperty(cx, obj, propid, JSVAL_VOID, + NULL, NULL, JSPROP_ENUMERATE, + (JSProperty **)&sprop)) { + sprop = NULL; + } + } + } else if (pobj != obj) { + /* Clone the prototype property so we can watch the right object. */ + jsval value; + JSPropertyOp getter, setter; + uintN attrs; + + if (OBJ_IS_NATIVE(pobj)) { + value = SPROP_HAS_VALID_SLOT(sprop, OBJ_SCOPE(pobj)) + ? LOCKED_OBJ_GET_SLOT(pobj, sprop->slot) + : JSVAL_VOID; + getter = sprop->getter; + setter = sprop->setter; + attrs = sprop->attrs; + } else { + if (!OBJ_GET_PROPERTY(cx, pobj, id, &value)) { + OBJ_DROP_PROPERTY(cx, pobj, (JSProperty *)sprop); + return JS_FALSE; + } + getter = setter = JS_PropertyStub; + attrs = JSPROP_ENUMERATE; + } + OBJ_DROP_PROPERTY(cx, pobj, (JSProperty *)sprop); + + if (!js_DefineProperty(cx, obj, propid, value, getter, setter, attrs, + (JSProperty **)&sprop)) { + sprop = NULL; + } + } + if (!sprop) + return JS_FALSE; + + wp = FindWatchPoint(rt, OBJ_SCOPE(obj), propid); + if (!wp) { + watcher = js_WrapWatchedSetter(cx, propid, sprop->attrs, sprop->setter); + if (!watcher) + return JS_FALSE; + + wp = (JSWatchPoint *) JS_malloc(cx, sizeof *wp); + if (!wp) + return JS_FALSE; + wp->handler = NULL; + wp->closure = NULL; + if (!js_AddRoot(cx, &wp->closure, "wp->closure")) { + JS_free(cx, wp); + return JS_FALSE; + } + JS_APPEND_LINK(&wp->links, &rt->watchPointList); + wp->object = obj; + wp->sprop = sprop; + JS_ASSERT(sprop->setter != js_watch_set); + wp->setter = sprop->setter; + wp->nrefs = 1; + sprop = js_ChangeNativePropertyAttrs(cx, obj, sprop, 0, sprop->attrs, + sprop->getter, watcher); + if (!sprop) + return DropWatchPoint(cx, wp); + } + wp->handler = handler; + wp->closure = closure; + OBJ_DROP_PROPERTY(cx, obj, (JSProperty *)sprop); + return JS_TRUE; +} + +JS_PUBLIC_API(JSBool) +JS_ClearWatchPoint(JSContext *cx, JSObject *obj, jsval id, + JSWatchPointHandler *handlerp, void **closurep) +{ + JSRuntime *rt; + JSWatchPoint *wp; + + rt = cx->runtime; + for (wp = (JSWatchPoint *)rt->watchPointList.next; + wp != (JSWatchPoint *)&rt->watchPointList; + wp = (JSWatchPoint *)wp->links.next) { + if (wp->object == obj && SPROP_USERID(wp->sprop) == id) { + if (handlerp) + *handlerp = wp->handler; + if (closurep) + *closurep = wp->closure; + return DropWatchPoint(cx, wp); + } + } + if (handlerp) + *handlerp = NULL; + if (closurep) + *closurep = NULL; + return JS_TRUE; +} + +JS_PUBLIC_API(JSBool) +JS_ClearWatchPointsForObject(JSContext *cx, JSObject *obj) +{ + JSRuntime *rt; + JSWatchPoint *wp, *next; + + rt = cx->runtime; + for (wp = (JSWatchPoint *)rt->watchPointList.next; + wp != (JSWatchPoint *)&rt->watchPointList; + wp = next) { + next = (JSWatchPoint *)wp->links.next; + if (wp->object == obj && !DropWatchPoint(cx, wp)) + return JS_FALSE; + } + return JS_TRUE; +} + +JS_PUBLIC_API(JSBool) +JS_ClearAllWatchPoints(JSContext *cx) +{ + JSRuntime *rt; + JSWatchPoint *wp, *next; + + rt = cx->runtime; + for (wp = (JSWatchPoint *)rt->watchPointList.next; + wp != (JSWatchPoint *)&rt->watchPointList; + wp = next) { + next = (JSWatchPoint *)wp->links.next; + if (!DropWatchPoint(cx, wp)) + return JS_FALSE; + } + return JS_TRUE; +} + +/************************************************************************/ + +JS_PUBLIC_API(uintN) +JS_PCToLineNumber(JSContext *cx, JSScript *script, jsbytecode *pc) +{ + return js_PCToLineNumber(cx, script, pc); +} + +JS_PUBLIC_API(jsbytecode *) +JS_LineNumberToPC(JSContext *cx, JSScript *script, uintN lineno) +{ + return js_LineNumberToPC(script, lineno); +} + +JS_PUBLIC_API(JSScript *) +JS_GetFunctionScript(JSContext *cx, JSFunction *fun) +{ + return fun->script; +} + +JS_PUBLIC_API(JSPrincipals *) +JS_GetScriptPrincipals(JSContext *cx, JSScript *script) +{ + return script->principals; +} + +/************************************************************************/ + +/* + * Stack Frame Iterator + */ +JS_PUBLIC_API(JSStackFrame *) +JS_FrameIterator(JSContext *cx, JSStackFrame **iteratorp) +{ + *iteratorp = (*iteratorp == NULL) ? cx->fp : (*iteratorp)->down; + return *iteratorp; +} + +JS_PUBLIC_API(JSScript *) +JS_GetFrameScript(JSContext *cx, JSStackFrame *fp) +{ + return fp->script; +} + +JS_PUBLIC_API(jsbytecode *) +JS_GetFramePC(JSContext *cx, JSStackFrame *fp) +{ + return fp->pc; +} + +JS_PUBLIC_API(JSStackFrame *) +JS_GetScriptedCaller(JSContext *cx, JSStackFrame *fp) +{ + if (!fp) + fp = cx->fp; + while ((fp = fp->down) != NULL) { + if (fp->script) + return fp; + } + return NULL; +} + +JS_PUBLIC_API(JSPrincipals *) +JS_StackFramePrincipals(JSContext *cx, JSStackFrame *fp) +{ + if (fp->fun && cx->findObjectPrincipals) { + JSObject *callee = JSVAL_TO_OBJECT(fp->argv[-2]); + + if (fp->fun->object != callee) + return cx->findObjectPrincipals(cx, callee); + /* FALL THROUGH */ + } + if (fp->script) + return fp->script->principals; + return NULL; +} + +JS_PUBLIC_API(JSPrincipals *) +JS_EvalFramePrincipals(JSContext *cx, JSStackFrame *fp, JSStackFrame *caller) +{ + if (cx->findObjectPrincipals) + return cx->findObjectPrincipals(cx, JSVAL_TO_OBJECT(fp->argv[-2])); + if (!caller) + return NULL; + return JS_StackFramePrincipals(cx, caller); +} + +JS_PUBLIC_API(void *) +JS_GetFrameAnnotation(JSContext *cx, JSStackFrame *fp) +{ + if (fp->annotation && fp->script) { + JSPrincipals *principals = JS_StackFramePrincipals(cx, fp); + + if (principals && principals->globalPrivilegesEnabled(cx, principals)) { + /* + * Give out an annotation only if privileges have not been revoked + * or disabled globally. + */ + return fp->annotation; + } + } + + return NULL; +} + +JS_PUBLIC_API(void) +JS_SetFrameAnnotation(JSContext *cx, JSStackFrame *fp, void *annotation) +{ + fp->annotation = annotation; +} + +JS_PUBLIC_API(void *) +JS_GetFramePrincipalArray(JSContext *cx, JSStackFrame *fp) +{ + JSPrincipals *principals; + + principals = JS_StackFramePrincipals(cx, fp); + if (!principals) + return NULL; + return principals->getPrincipalArray(cx, principals); +} + +JS_PUBLIC_API(JSBool) +JS_IsNativeFrame(JSContext *cx, JSStackFrame *fp) +{ + return !fp->script; +} + +/* this is deprecated, use JS_GetFrameScopeChain instead */ +JS_PUBLIC_API(JSObject *) +JS_GetFrameObject(JSContext *cx, JSStackFrame *fp) +{ + return fp->scopeChain; +} + +JS_PUBLIC_API(JSObject *) +JS_GetFrameScopeChain(JSContext *cx, JSStackFrame *fp) +{ + /* Force creation of argument and call objects if not yet created */ + (void) JS_GetFrameCallObject(cx, fp); + return fp->scopeChain; +} + +JS_PUBLIC_API(JSObject *) +JS_GetFrameCallObject(JSContext *cx, JSStackFrame *fp) +{ + if (! fp->fun) + return NULL; +#if JS_HAS_ARGS_OBJECT + /* Force creation of argument object if not yet created */ + (void) js_GetArgsObject(cx, fp); +#endif +#if JS_HAS_CALL_OBJECT + /* + * XXX ill-defined: null return here means error was reported, unlike a + * null returned above or in the #else + */ + return js_GetCallObject(cx, fp, NULL); +#else + return NULL; +#endif /* JS_HAS_CALL_OBJECT */ +} + + +JS_PUBLIC_API(JSObject *) +JS_GetFrameThis(JSContext *cx, JSStackFrame *fp) +{ + return fp->thisp; +} + +JS_PUBLIC_API(JSFunction *) +JS_GetFrameFunction(JSContext *cx, JSStackFrame *fp) +{ + return fp->fun; +} + +JS_PUBLIC_API(JSObject *) +JS_GetFrameFunctionObject(JSContext *cx, JSStackFrame *fp) +{ + return fp->argv && fp->fun ? JSVAL_TO_OBJECT(fp->argv[-2]) : NULL; +} + +JS_PUBLIC_API(JSBool) +JS_IsConstructorFrame(JSContext *cx, JSStackFrame *fp) +{ + return (fp->flags & JSFRAME_CONSTRUCTING) != 0; +} + +JS_PUBLIC_API(JSBool) +JS_IsDebuggerFrame(JSContext *cx, JSStackFrame *fp) +{ + return (fp->flags & JSFRAME_DEBUGGER) != 0; +} + +JS_PUBLIC_API(jsval) +JS_GetFrameReturnValue(JSContext *cx, JSStackFrame *fp) +{ + return fp->rval; +} + +JS_PUBLIC_API(void) +JS_SetFrameReturnValue(JSContext *cx, JSStackFrame *fp, jsval rval) +{ + fp->rval = rval; +} + +/************************************************************************/ + +JS_PUBLIC_API(const char *) +JS_GetScriptFilename(JSContext *cx, JSScript *script) +{ + return script->filename; +} + +JS_PUBLIC_API(uintN) +JS_GetScriptBaseLineNumber(JSContext *cx, JSScript *script) +{ + return script->lineno; +} + +JS_PUBLIC_API(uintN) +JS_GetScriptLineExtent(JSContext *cx, JSScript *script) +{ + return js_GetScriptLineExtent(script); +} + +JS_PUBLIC_API(JSVersion) +JS_GetScriptVersion(JSContext *cx, JSScript *script) +{ + return script->version; +} + +/***************************************************************************/ + +JS_PUBLIC_API(void) +JS_SetNewScriptHook(JSRuntime *rt, JSNewScriptHook hook, void *callerdata) +{ + rt->newScriptHook = hook; + rt->newScriptHookData = callerdata; +} + +JS_PUBLIC_API(void) +JS_SetDestroyScriptHook(JSRuntime *rt, JSDestroyScriptHook hook, + void *callerdata) +{ + rt->destroyScriptHook = hook; + rt->destroyScriptHookData = callerdata; +} + +/***************************************************************************/ + +JS_PUBLIC_API(JSBool) +JS_EvaluateUCInStackFrame(JSContext *cx, JSStackFrame *fp, + const jschar *bytes, uintN length, + const char *filename, uintN lineno, + jsval *rval) +{ + uint32 flags; + JSScript *script; + JSBool ok; + + /* + * XXX Hack around ancient compiler API to propagate the JSFRAME_SPECIAL + * flags to the code generator (see js_EmitTree's TOK_SEMI case). + */ + flags = fp->flags; + fp->flags |= JSFRAME_DEBUGGER | JSFRAME_EVAL; + script = JS_CompileUCScriptForPrincipals(cx, fp->scopeChain, + JS_StackFramePrincipals(cx, fp), + bytes, length, filename, lineno); + fp->flags = flags; + if (!script) + return JS_FALSE; + + ok = js_Execute(cx, fp->scopeChain, script, fp, + JSFRAME_DEBUGGER | JSFRAME_EVAL, rval); + js_DestroyScript(cx, script); + return ok; +} + +JS_PUBLIC_API(JSBool) +JS_EvaluateInStackFrame(JSContext *cx, JSStackFrame *fp, + const char *bytes, uintN length, + const char *filename, uintN lineno, + jsval *rval) +{ + jschar *chars; + JSBool ok; + + chars = js_InflateString(cx, bytes, length); + if (!chars) + return JS_FALSE; + ok = JS_EvaluateUCInStackFrame(cx, fp, chars, length, filename, lineno, + rval); + JS_free(cx, chars); + + return ok; +} + +/************************************************************************/ + +/* XXXbe this all needs to be reworked to avoid requiring JSScope types. */ + +JS_PUBLIC_API(JSScopeProperty *) +JS_PropertyIterator(JSObject *obj, JSScopeProperty **iteratorp) +{ + JSScopeProperty *sprop; + JSScope *scope; + + sprop = *iteratorp; + scope = OBJ_SCOPE(obj); + + /* XXXbe minor(?) incompatibility: iterate in reverse definition order */ + if (!sprop) { + sprop = SCOPE_LAST_PROP(scope); + } else { + while ((sprop = sprop->parent) != NULL) { + if (!SCOPE_HAD_MIDDLE_DELETE(scope)) + break; + if (SCOPE_HAS_PROPERTY(scope, sprop)) + break; + } + } + *iteratorp = sprop; + return sprop; +} + +JS_PUBLIC_API(JSBool) +JS_GetPropertyDesc(JSContext *cx, JSObject *obj, JSScopeProperty *sprop, + JSPropertyDesc *pd) +{ + JSPropertyOp getter; + JSScope *scope; + JSScopeProperty *aprop; + jsval lastException; + JSBool wasThrowing; + + pd->id = ID_TO_VALUE(sprop->id); + + wasThrowing = cx->throwing; + if (wasThrowing) { + lastException = cx->exception; + if (JSVAL_IS_GCTHING(lastException) && + !js_AddRoot(cx, &lastException, "lastException")) { + return JS_FALSE; + } + cx->throwing = JS_FALSE; + } + + if (!js_GetProperty(cx, obj, sprop->id, &pd->value)) { + if (!cx->throwing) { + pd->flags = JSPD_ERROR; + pd->value = JSVAL_VOID; + } else { + pd->flags = JSPD_EXCEPTION; + pd->value = cx->exception; + } + } else { + pd->flags = 0; + } + + cx->throwing = wasThrowing; + if (wasThrowing) { + cx->exception = lastException; + if (JSVAL_IS_GCTHING(lastException)) + js_RemoveRoot(cx->runtime, &lastException); + } + + getter = sprop->getter; + pd->flags |= ((sprop->attrs & JSPROP_ENUMERATE) ? JSPD_ENUMERATE : 0) + | ((sprop->attrs & JSPROP_READONLY) ? JSPD_READONLY : 0) + | ((sprop->attrs & JSPROP_PERMANENT) ? JSPD_PERMANENT : 0) +#if JS_HAS_CALL_OBJECT + | ((getter == js_GetCallVariable) ? JSPD_VARIABLE : 0) +#endif /* JS_HAS_CALL_OBJECT */ + | ((getter == js_GetArgument) ? JSPD_ARGUMENT : 0) + | ((getter == js_GetLocalVariable) ? JSPD_VARIABLE : 0); +#if JS_HAS_CALL_OBJECT + /* for Call Object 'real' getter isn't passed in to us */ + if (OBJ_GET_CLASS(cx, obj) == &js_CallClass && + getter == js_CallClass.getProperty) { + /* + * Property of a heavyweight function's variable object having the + * class-default getter. It's either an argument if permanent, or a + * nested function if impermanent. Local variables have a special + * getter (js_GetCallVariable, tested above) and setter, and not the + * class default. + */ + pd->flags |= (sprop->attrs & JSPROP_PERMANENT) + ? JSPD_ARGUMENT + : JSPD_VARIABLE; + } +#endif /* JS_HAS_CALL_OBJECT */ + pd->spare = 0; + pd->slot = (pd->flags & (JSPD_ARGUMENT | JSPD_VARIABLE)) + ? sprop->shortid + : 0; + pd->alias = JSVAL_VOID; + scope = OBJ_SCOPE(obj); + if (SPROP_HAS_VALID_SLOT(sprop, scope)) { + for (aprop = SCOPE_LAST_PROP(scope); aprop; aprop = aprop->parent) { + if (aprop != sprop && aprop->slot == sprop->slot) { + pd->alias = ID_TO_VALUE(aprop->id); + break; + } + } + } + return JS_TRUE; +} + +JS_PUBLIC_API(JSBool) +JS_GetPropertyDescArray(JSContext *cx, JSObject *obj, JSPropertyDescArray *pda) +{ + JSClass *clasp; + JSScope *scope; + uint32 i, n; + JSPropertyDesc *pd; + JSScopeProperty *sprop; + + clasp = OBJ_GET_CLASS(cx, obj); + if (!OBJ_IS_NATIVE(obj) || (clasp->flags & JSCLASS_NEW_ENUMERATE)) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_CANT_DESCRIBE_PROPS, clasp->name); + return JS_FALSE; + } + if (!clasp->enumerate(cx, obj)) + return JS_FALSE; + + /* have no props, or object's scope has not mutated from that of proto */ + scope = OBJ_SCOPE(obj); + if (scope->object != obj || scope->entryCount == 0) { + pda->length = 0; + pda->array = NULL; + return JS_TRUE; + } + + n = scope->entryCount; + if (n > scope->map.nslots) + n = scope->map.nslots; + pd = (JSPropertyDesc *) JS_malloc(cx, (size_t)n * sizeof(JSPropertyDesc)); + if (!pd) + return JS_FALSE; + i = 0; + for (sprop = SCOPE_LAST_PROP(scope); sprop; sprop = sprop->parent) { + if (SCOPE_HAD_MIDDLE_DELETE(scope) && !SCOPE_HAS_PROPERTY(scope, sprop)) + continue; + if (!js_AddRoot(cx, &pd[i].id, NULL)) + goto bad; + if (!js_AddRoot(cx, &pd[i].value, NULL)) + goto bad; + if (!JS_GetPropertyDesc(cx, obj, sprop, &pd[i])) + goto bad; + if ((pd[i].flags & JSPD_ALIAS) && !js_AddRoot(cx, &pd[i].alias, NULL)) + goto bad; + if (++i == n) + break; + } + pda->length = i; + pda->array = pd; + return JS_TRUE; + +bad: + pda->length = i + 1; + pda->array = pd; + JS_PutPropertyDescArray(cx, pda); + return JS_FALSE; +} + +JS_PUBLIC_API(void) +JS_PutPropertyDescArray(JSContext *cx, JSPropertyDescArray *pda) +{ + JSPropertyDesc *pd; + uint32 i; + + pd = pda->array; + for (i = 0; i < pda->length; i++) { + js_RemoveRoot(cx->runtime, &pd[i].id); + js_RemoveRoot(cx->runtime, &pd[i].value); + if (pd[i].flags & JSPD_ALIAS) + js_RemoveRoot(cx->runtime, &pd[i].alias); + } + JS_free(cx, pd); +} + +/************************************************************************/ + +JS_PUBLIC_API(JSBool) +JS_SetDebuggerHandler(JSRuntime *rt, JSTrapHandler handler, void *closure) +{ + rt->debuggerHandler = handler; + rt->debuggerHandlerData = closure; + return JS_TRUE; +} + +JS_PUBLIC_API(JSBool) +JS_SetSourceHandler(JSRuntime *rt, JSSourceHandler handler, void *closure) +{ + rt->sourceHandler = handler; + rt->sourceHandlerData = closure; + return JS_TRUE; +} + +JS_PUBLIC_API(JSBool) +JS_SetExecuteHook(JSRuntime *rt, JSInterpreterHook hook, void *closure) +{ + rt->executeHook = hook; + rt->executeHookData = closure; + return JS_TRUE; +} + +JS_PUBLIC_API(JSBool) +JS_SetCallHook(JSRuntime *rt, JSInterpreterHook hook, void *closure) +{ + rt->callHook = hook; + rt->callHookData = closure; + return JS_TRUE; +} + +JS_PUBLIC_API(JSBool) +JS_SetObjectHook(JSRuntime *rt, JSObjectHook hook, void *closure) +{ + rt->objectHook = hook; + rt->objectHookData = closure; + return JS_TRUE; +} + +JS_PUBLIC_API(JSBool) +JS_SetThrowHook(JSRuntime *rt, JSTrapHandler hook, void *closure) +{ + rt->throwHook = hook; + rt->throwHookData = closure; + return JS_TRUE; +} + +JS_PUBLIC_API(JSBool) +JS_SetDebugErrorHook(JSRuntime *rt, JSDebugErrorHook hook, void *closure) +{ + rt->debugErrorHook = hook; + rt->debugErrorHookData = closure; + return JS_TRUE; +} + +/************************************************************************/ + +JS_PUBLIC_API(size_t) +JS_GetObjectTotalSize(JSContext *cx, JSObject *obj) +{ + size_t nbytes; + JSScope *scope; + + nbytes = sizeof *obj + obj->map->nslots * sizeof obj->slots[0]; + if (OBJ_IS_NATIVE(obj)) { + scope = OBJ_SCOPE(obj); + if (scope->object == obj) { + nbytes += sizeof *scope; + nbytes += SCOPE_CAPACITY(scope) * sizeof(JSScopeProperty *); + } + } + return nbytes; +} + +static size_t +GetAtomTotalSize(JSContext *cx, JSAtom *atom) +{ + size_t nbytes; + + nbytes = sizeof *atom; + if (ATOM_IS_STRING(atom)) { + nbytes += sizeof(JSString); + nbytes += (ATOM_TO_STRING(atom)->length + 1) * sizeof(jschar); + } else if (ATOM_IS_DOUBLE(atom)) { + nbytes += sizeof(jsdouble); + } else if (ATOM_IS_OBJECT(atom)) { + nbytes += JS_GetObjectTotalSize(cx, ATOM_TO_OBJECT(atom)); + } + return nbytes; +} + +JS_PUBLIC_API(size_t) +JS_GetFunctionTotalSize(JSContext *cx, JSFunction *fun) +{ + size_t nbytes, obytes; + JSObject *obj; + JSAtom *atom; + + nbytes = sizeof *fun; + JS_ASSERT(fun->nrefs); + obj = fun->object; + if (obj) { + obytes = JS_GetObjectTotalSize(cx, obj); + if (fun->nrefs > 1) + obytes = JS_HOWMANY(obytes, fun->nrefs); + nbytes += obytes; + } + if (fun->script) + nbytes += JS_GetScriptTotalSize(cx, fun->script); + atom = fun->atom; + if (atom) + nbytes += GetAtomTotalSize(cx, atom); + return nbytes; +} + +#include "jsemit.h" + +JS_PUBLIC_API(size_t) +JS_GetScriptTotalSize(JSContext *cx, JSScript *script) +{ + size_t nbytes, pbytes; + JSObject *obj; + jsatomid i; + jssrcnote *sn, *notes; + JSTryNote *tn, *tnotes; + JSPrincipals *principals; + + nbytes = sizeof *script; + obj = script->object; + if (obj) + nbytes += JS_GetObjectTotalSize(cx, obj); + + nbytes += script->length * sizeof script->code[0]; + nbytes += script->atomMap.length * sizeof script->atomMap.vector[0]; + for (i = 0; i < script->atomMap.length; i++) + nbytes += GetAtomTotalSize(cx, script->atomMap.vector[i]); + + if (script->filename) + nbytes += strlen(script->filename) + 1; + + notes = SCRIPT_NOTES(script); + for (sn = notes; !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) + continue; + nbytes += (sn - notes + 1) * sizeof *sn; + + tnotes = script->trynotes; + if (tnotes) { + for (tn = tnotes; tn->catchStart; tn++) + continue; + nbytes += (tn - tnotes + 1) * sizeof *tn; + } + + principals = script->principals; + if (principals) { + JS_ASSERT(principals->refcount); + pbytes = sizeof *principals; + if (principals->refcount > 1) + pbytes = JS_HOWMANY(pbytes, principals->refcount); + nbytes += pbytes; + } + + return nbytes; +} diff --git a/src/dom/js/jsdbgapi.h b/src/dom/js/jsdbgapi.h new file mode 100644 index 000000000..90215a275 --- /dev/null +++ b/src/dom/js/jsdbgapi.h @@ -0,0 +1,345 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsdbgapi_h___ +#define jsdbgapi_h___ +/* + * JS debugger API. + */ +#include "jsapi.h" +#include "jsopcode.h" +#include "jsprvtd.h" + +JS_BEGIN_EXTERN_C + +extern void +js_PatchOpcode(JSContext *cx, JSScript *script, jsbytecode *pc, JSOp op); + +extern JS_PUBLIC_API(JSBool) +JS_SetTrap(JSContext *cx, JSScript *script, jsbytecode *pc, + JSTrapHandler handler, void *closure); + +extern JS_PUBLIC_API(JSOp) +JS_GetTrapOpcode(JSContext *cx, JSScript *script, jsbytecode *pc); + +extern JS_PUBLIC_API(void) +JS_ClearTrap(JSContext *cx, JSScript *script, jsbytecode *pc, + JSTrapHandler *handlerp, void **closurep); + +extern JS_PUBLIC_API(void) +JS_ClearScriptTraps(JSContext *cx, JSScript *script); + +extern JS_PUBLIC_API(void) +JS_ClearAllTraps(JSContext *cx); + +extern JS_PUBLIC_API(JSTrapStatus) +JS_HandleTrap(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval); + +extern JS_PUBLIC_API(JSBool) +JS_SetInterrupt(JSRuntime *rt, JSTrapHandler handler, void *closure); + +extern JS_PUBLIC_API(JSBool) +JS_ClearInterrupt(JSRuntime *rt, JSTrapHandler *handlerp, void **closurep); + +/************************************************************************/ + +extern JS_PUBLIC_API(JSBool) +JS_SetWatchPoint(JSContext *cx, JSObject *obj, jsval id, + JSWatchPointHandler handler, void *closure); + +extern JS_PUBLIC_API(JSBool) +JS_ClearWatchPoint(JSContext *cx, JSObject *obj, jsval id, + JSWatchPointHandler *handlerp, void **closurep); + +extern JS_PUBLIC_API(JSBool) +JS_ClearWatchPointsForObject(JSContext *cx, JSObject *obj); + +extern JS_PUBLIC_API(JSBool) +JS_ClearAllWatchPoints(JSContext *cx); + +#ifdef JS_HAS_OBJ_WATCHPOINT +/* + * Hide these non-API function prototypes by testing whether the internal + * header file "jsconfig.h" has been included. + */ +extern void +js_MarkWatchPoints(JSRuntime *rt); + +extern JSScopeProperty * +js_FindWatchPoint(JSRuntime *rt, JSScope *scope, jsid id); + +extern JSPropertyOp +js_GetWatchedSetter(JSRuntime *rt, JSScope *scope, + const JSScopeProperty *sprop); + +extern JSBool JS_DLL_CALLBACK +js_watch_set(JSContext *cx, JSObject *obj, jsval id, jsval *vp); + +extern JSBool JS_DLL_CALLBACK +js_watch_set_wrapper(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval); + +extern JSPropertyOp +js_WrapWatchedSetter(JSContext *cx, jsid id, uintN attrs, JSPropertyOp setter); + +#endif /* JS_HAS_OBJ_WATCHPOINT */ + +/************************************************************************/ + +extern JS_PUBLIC_API(uintN) +JS_PCToLineNumber(JSContext *cx, JSScript *script, jsbytecode *pc); + +extern JS_PUBLIC_API(jsbytecode *) +JS_LineNumberToPC(JSContext *cx, JSScript *script, uintN lineno); + +extern JS_PUBLIC_API(JSScript *) +JS_GetFunctionScript(JSContext *cx, JSFunction *fun); + +extern JS_PUBLIC_API(JSPrincipals *) +JS_GetScriptPrincipals(JSContext *cx, JSScript *script); + +/* + * Stack Frame Iterator + * + * Used to iterate through the JS stack frames to extract + * information from the frames. + */ + +extern JS_PUBLIC_API(JSStackFrame *) +JS_FrameIterator(JSContext *cx, JSStackFrame **iteratorp); + +extern JS_PUBLIC_API(JSScript *) +JS_GetFrameScript(JSContext *cx, JSStackFrame *fp); + +extern JS_PUBLIC_API(jsbytecode *) +JS_GetFramePC(JSContext *cx, JSStackFrame *fp); + +/* + * Get the closest scripted frame below fp. If fp is null, start from cx->fp. + */ +extern JS_PUBLIC_API(JSStackFrame *) +JS_GetScriptedCaller(JSContext *cx, JSStackFrame *fp); + +/* + * Return a weak reference to fp's principals. A null return does not denote + * an error, it means there are no principals. + */ +extern JS_PUBLIC_API(JSPrincipals *) +JS_StackFramePrincipals(JSContext *cx, JSStackFrame *fp); + +/* + * Like JS_StackFramePrincipals(cx, caller), but if cx->findObjectPrincipals + * is non-null, return the object principals for fp's callee function object + * (fp->argv[-2]), which is eval, Function, or a similar eval-like method. + * The caller parameter should be the result of JS_GetScriptedCaller(cx, fp). + * + * All eval-like methods must use JS_EvalFramePrincipals to acquire a weak + * reference to the correct principals for the eval call to be secure, given + * an embedding that calls JS_SetObjectPrincipalsFinder (see jsapi.h). + */ +extern JS_PUBLIC_API(JSPrincipals *) +JS_EvalFramePrincipals(JSContext *cx, JSStackFrame *fp, JSStackFrame *caller); + +extern JS_PUBLIC_API(void *) +JS_GetFrameAnnotation(JSContext *cx, JSStackFrame *fp); + +extern JS_PUBLIC_API(void) +JS_SetFrameAnnotation(JSContext *cx, JSStackFrame *fp, void *annotation); + +extern JS_PUBLIC_API(void *) +JS_GetFramePrincipalArray(JSContext *cx, JSStackFrame *fp); + +extern JS_PUBLIC_API(JSBool) +JS_IsNativeFrame(JSContext *cx, JSStackFrame *fp); + +/* this is deprecated, use JS_GetFrameScopeChain instead */ +extern JS_PUBLIC_API(JSObject *) +JS_GetFrameObject(JSContext *cx, JSStackFrame *fp); + +extern JS_PUBLIC_API(JSObject *) +JS_GetFrameScopeChain(JSContext *cx, JSStackFrame *fp); + +extern JS_PUBLIC_API(JSObject *) +JS_GetFrameCallObject(JSContext *cx, JSStackFrame *fp); + +extern JS_PUBLIC_API(JSObject *) +JS_GetFrameThis(JSContext *cx, JSStackFrame *fp); + +extern JS_PUBLIC_API(JSFunction *) +JS_GetFrameFunction(JSContext *cx, JSStackFrame *fp); + +extern JS_PUBLIC_API(JSObject *) +JS_GetFrameFunctionObject(JSContext *cx, JSStackFrame *fp); + +/* XXXrginda Initially published with typo */ +#define JS_IsContructorFrame JS_IsConstructorFrame +extern JS_PUBLIC_API(JSBool) +JS_IsConstructorFrame(JSContext *cx, JSStackFrame *fp); + +extern JS_PUBLIC_API(JSBool) +JS_IsDebuggerFrame(JSContext *cx, JSStackFrame *fp); + +extern JS_PUBLIC_API(jsval) +JS_GetFrameReturnValue(JSContext *cx, JSStackFrame *fp); + +extern JS_PUBLIC_API(void) +JS_SetFrameReturnValue(JSContext *cx, JSStackFrame *fp, jsval rval); + +/************************************************************************/ + +extern JS_PUBLIC_API(const char *) +JS_GetScriptFilename(JSContext *cx, JSScript *script); + +extern JS_PUBLIC_API(uintN) +JS_GetScriptBaseLineNumber(JSContext *cx, JSScript *script); + +extern JS_PUBLIC_API(uintN) +JS_GetScriptLineExtent(JSContext *cx, JSScript *script); + +extern JS_PUBLIC_API(JSVersion) +JS_GetScriptVersion(JSContext *cx, JSScript *script); + +/************************************************************************/ + +/* + * Hook setters for script creation and destruction, see jsprvtd.h for the + * typedefs. These macros provide binary compatibility and newer, shorter + * synonyms. + */ +#define JS_SetNewScriptHook JS_SetNewScriptHookProc +#define JS_SetDestroyScriptHook JS_SetDestroyScriptHookProc + +extern JS_PUBLIC_API(void) +JS_SetNewScriptHook(JSRuntime *rt, JSNewScriptHook hook, void *callerdata); + +extern JS_PUBLIC_API(void) +JS_SetDestroyScriptHook(JSRuntime *rt, JSDestroyScriptHook hook, + void *callerdata); + +/************************************************************************/ + +extern JS_PUBLIC_API(JSBool) +JS_EvaluateUCInStackFrame(JSContext *cx, JSStackFrame *fp, + const jschar *bytes, uintN length, + const char *filename, uintN lineno, + jsval *rval); + +extern JS_PUBLIC_API(JSBool) +JS_EvaluateInStackFrame(JSContext *cx, JSStackFrame *fp, + const char *bytes, uintN length, + const char *filename, uintN lineno, + jsval *rval); + +/************************************************************************/ + +typedef struct JSPropertyDesc { + jsval id; /* primary id, a string or int */ + jsval value; /* property value */ + uint8 flags; /* flags, see below */ + uint8 spare; /* unused */ + uint16 slot; /* argument/variable slot */ + jsval alias; /* alias id if JSPD_ALIAS flag */ +} JSPropertyDesc; + +#define JSPD_ENUMERATE 0x01 /* visible to for/in loop */ +#define JSPD_READONLY 0x02 /* assignment is error */ +#define JSPD_PERMANENT 0x04 /* property cannot be deleted */ +#define JSPD_ALIAS 0x08 /* property has an alias id */ +#define JSPD_ARGUMENT 0x10 /* argument to function */ +#define JSPD_VARIABLE 0x20 /* local variable in function */ +#define JSPD_EXCEPTION 0x40 /* exception occurred fetching the property, */ + /* value is exception */ +#define JSPD_ERROR 0x80 /* native getter returned JS_FALSE without */ + /* throwing an exception */ + +typedef struct JSPropertyDescArray { + uint32 length; /* number of elements in array */ + JSPropertyDesc *array; /* alloc'd by Get, freed by Put */ +} JSPropertyDescArray; + +extern JS_PUBLIC_API(JSScopeProperty *) +JS_PropertyIterator(JSObject *obj, JSScopeProperty **iteratorp); + +extern JS_PUBLIC_API(JSBool) +JS_GetPropertyDesc(JSContext *cx, JSObject *obj, JSScopeProperty *sprop, + JSPropertyDesc *pd); + +extern JS_PUBLIC_API(JSBool) +JS_GetPropertyDescArray(JSContext *cx, JSObject *obj, JSPropertyDescArray *pda); + +extern JS_PUBLIC_API(void) +JS_PutPropertyDescArray(JSContext *cx, JSPropertyDescArray *pda); + +/************************************************************************/ + +extern JS_PUBLIC_API(JSBool) +JS_SetDebuggerHandler(JSRuntime *rt, JSTrapHandler handler, void *closure); + +extern JS_PUBLIC_API(JSBool) +JS_SetSourceHandler(JSRuntime *rt, JSSourceHandler handler, void *closure); + +extern JS_PUBLIC_API(JSBool) +JS_SetExecuteHook(JSRuntime *rt, JSInterpreterHook hook, void *closure); + +extern JS_PUBLIC_API(JSBool) +JS_SetCallHook(JSRuntime *rt, JSInterpreterHook hook, void *closure); + +extern JS_PUBLIC_API(JSBool) +JS_SetObjectHook(JSRuntime *rt, JSObjectHook hook, void *closure); + +extern JS_PUBLIC_API(JSBool) +JS_SetThrowHook(JSRuntime *rt, JSTrapHandler hook, void *closure); + +extern JS_PUBLIC_API(JSBool) +JS_SetDebugErrorHook(JSRuntime *rt, JSDebugErrorHook hook, void *closure); + +/************************************************************************/ + +extern JS_PUBLIC_API(size_t) +JS_GetObjectTotalSize(JSContext *cx, JSObject *obj); + +extern JS_PUBLIC_API(size_t) +JS_GetFunctionTotalSize(JSContext *cx, JSFunction *fun); + +extern JS_PUBLIC_API(size_t) +JS_GetScriptTotalSize(JSContext *cx, JSScript *script); + +JS_END_EXTERN_C + +#endif /* jsdbgapi_h___ */ diff --git a/src/dom/js/jsdhash.c b/src/dom/js/jsdhash.c new file mode 100644 index 000000000..736de04ad --- /dev/null +++ b/src/dom/js/jsdhash.c @@ -0,0 +1,763 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla JavaScript code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1999-2001 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brendan Eich (Original Author) + * Chris Waterson + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * Double hashing implementation. + */ +#include +#include +#include +#include "jsbit.h" +#include "jsdhash.h" +#include "jsutil.h" /* for JS_ASSERT */ + +#ifdef JS_DHASHMETER +# if defined MOZILLA_CLIENT && defined DEBUG_XXXbrendan +# include "nsTraceMalloc.h" +# endif +# define METER(x) x +#else +# define METER(x) /* nothing */ +#endif + +JS_PUBLIC_API(void *) +JS_DHashAllocTable(JSDHashTable *table, uint32 nbytes) +{ + return malloc(nbytes); +} + +JS_PUBLIC_API(void) +JS_DHashFreeTable(JSDHashTable *table, void *ptr) +{ + free(ptr); +} + +JS_PUBLIC_API(JSDHashNumber) +JS_DHashStringKey(JSDHashTable *table, const void *key) +{ + JSDHashNumber h; + const unsigned char *s; + + h = 0; + for (s = key; *s != '\0'; s++) + h = (h >> (JS_DHASH_BITS - 4)) ^ (h << 4) ^ *s; + return h; +} + +JS_PUBLIC_API(const void *) +JS_DHashGetKeyStub(JSDHashTable *table, JSDHashEntryHdr *entry) +{ + JSDHashEntryStub *stub = (JSDHashEntryStub *)entry; + + return stub->key; +} + +JS_PUBLIC_API(JSDHashNumber) +JS_DHashVoidPtrKeyStub(JSDHashTable *table, const void *key) +{ + return (JSDHashNumber)key >> 2; +} + +JS_PUBLIC_API(JSBool) +JS_DHashMatchEntryStub(JSDHashTable *table, + const JSDHashEntryHdr *entry, + const void *key) +{ + const JSDHashEntryStub *stub = (const JSDHashEntryStub *)entry; + + return stub->key == key; +} + +JS_PUBLIC_API(JSBool) +JS_DHashMatchStringKey(JSDHashTable *table, + const JSDHashEntryHdr *entry, + const void *key) +{ + const JSDHashEntryStub *stub = (const JSDHashEntryStub *)entry; + + /* XXX tolerate null keys on account of sloppy Mozilla callers. */ + return stub->key == key || + (stub->key && key && strcmp(stub->key, key) == 0); +} + +JS_PUBLIC_API(void) +JS_DHashMoveEntryStub(JSDHashTable *table, + const JSDHashEntryHdr *from, + JSDHashEntryHdr *to) +{ + memcpy(to, from, table->entrySize); +} + +JS_PUBLIC_API(void) +JS_DHashClearEntryStub(JSDHashTable *table, JSDHashEntryHdr *entry) +{ + memset(entry, 0, table->entrySize); +} + +JS_PUBLIC_API(void) +JS_DHashFreeStringKey(JSDHashTable *table, JSDHashEntryHdr *entry) +{ + const JSDHashEntryStub *stub = (const JSDHashEntryStub *)entry; + + free((void *) stub->key); + memset(entry, 0, table->entrySize); +} + +JS_PUBLIC_API(void) +JS_DHashFinalizeStub(JSDHashTable *table) +{ +} + +static const JSDHashTableOps stub_ops = { + JS_DHashAllocTable, + JS_DHashFreeTable, + JS_DHashGetKeyStub, + JS_DHashVoidPtrKeyStub, + JS_DHashMatchEntryStub, + JS_DHashMoveEntryStub, + JS_DHashClearEntryStub, + JS_DHashFinalizeStub, + NULL +}; + +JS_PUBLIC_API(const JSDHashTableOps *) +JS_DHashGetStubOps(void) +{ + return &stub_ops; +} + +JS_PUBLIC_API(JSDHashTable *) +JS_NewDHashTable(const JSDHashTableOps *ops, void *data, uint32 entrySize, + uint32 capacity) +{ + JSDHashTable *table; + + table = (JSDHashTable *) malloc(sizeof *table); + if (!table) + return NULL; + if (!JS_DHashTableInit(table, ops, data, entrySize, capacity)) { + free(table); + return NULL; + } + return table; +} + +JS_PUBLIC_API(void) +JS_DHashTableDestroy(JSDHashTable *table) +{ + JS_DHashTableFinish(table); + free(table); +} + +JS_PUBLIC_API(JSBool) +JS_DHashTableInit(JSDHashTable *table, const JSDHashTableOps *ops, void *data, + uint32 entrySize, uint32 capacity) +{ + int log2; + uint32 nbytes; + +#ifdef DEBUG + if (entrySize > 10 * sizeof(void *)) { + fprintf(stderr, + "jsdhash: for the table at address %p, the given entrySize" + " of %lu %s favors chaining over double hashing.\n", + (void *)table, + (unsigned long) entrySize, + (entrySize > 16 * sizeof(void*)) ? "definitely" : "probably"); + } +#endif + + table->ops = ops; + table->data = data; + if (capacity < JS_DHASH_MIN_SIZE) + capacity = JS_DHASH_MIN_SIZE; + log2 = JS_CeilingLog2(capacity); + capacity = JS_BIT(log2); + if (capacity >= JS_DHASH_SIZE_LIMIT) + return JS_FALSE; + table->hashShift = JS_DHASH_BITS - log2; + table->maxAlphaFrac = 0xC0; /* .75 */ + table->minAlphaFrac = 0x40; /* .25 */ + table->entrySize = entrySize; + table->entryCount = table->removedCount = 0; + table->generation = 0; + nbytes = capacity * entrySize; + + table->entryStore = ops->allocTable(table, nbytes); + if (!table->entryStore) + return JS_FALSE; + memset(table->entryStore, 0, nbytes); + METER(memset(&table->stats, 0, sizeof table->stats)); + return JS_TRUE; +} + +/* + * Compute max and min load numbers (entry counts) from table params. + */ +#define MAX_LOAD(table, size) (((table)->maxAlphaFrac * (size)) >> 8) +#define MIN_LOAD(table, size) (((table)->minAlphaFrac * (size)) >> 8) + +JS_PUBLIC_API(void) +JS_DHashTableSetAlphaBounds(JSDHashTable *table, + float maxAlpha, + float minAlpha) +{ + uint32 size; + + /* + * Reject obviously insane bounds, rather than trying to guess what the + * buggy caller intended. + */ + JS_ASSERT(0.5 <= maxAlpha && maxAlpha < 1 && 0 <= minAlpha); + if (maxAlpha < 0.5 || 1 <= maxAlpha || minAlpha < 0) + return; + + /* + * Ensure that at least one entry will always be free. If maxAlpha at + * minimum size leaves no entries free, reduce maxAlpha based on minimum + * size and the precision limit of maxAlphaFrac's fixed point format. + */ + JS_ASSERT(JS_DHASH_MIN_SIZE - (maxAlpha * JS_DHASH_MIN_SIZE) >= 1); + if (JS_DHASH_MIN_SIZE - (maxAlpha * JS_DHASH_MIN_SIZE) < 1) { + maxAlpha = (float) + (JS_DHASH_MIN_SIZE - JS_MAX(JS_DHASH_MIN_SIZE / 256, 1)) + / JS_DHASH_MIN_SIZE; + } + + /* + * Ensure that minAlpha is strictly less than half maxAlpha. Take care + * not to truncate an entry's worth of alpha when storing in minAlphaFrac + * (8-bit fixed point format). + */ + JS_ASSERT(minAlpha < maxAlpha / 2); + if (minAlpha >= maxAlpha / 2) { + size = JS_DHASH_TABLE_SIZE(table); + minAlpha = (size * maxAlpha - JS_MAX(size / 256, 1)) / (2 * size); + } + + table->maxAlphaFrac = (uint8)(maxAlpha * 256); + table->minAlphaFrac = (uint8)(minAlpha * 256); +} + +/* + * Double hashing needs the second hash code to be relatively prime to table + * size, so we simply make hash2 odd. + */ +#define HASH1(hash0, shift) ((hash0) >> (shift)) +#define HASH2(hash0,log2,shift) ((((hash0) << (log2)) >> (shift)) | 1) + +/* + * Reserve keyHash 0 for free entries and 1 for removed-entry sentinels. Note + * that a removed-entry sentinel need be stored only if the removed entry had + * a colliding entry added after it. Therefore we can use 1 as the collision + * flag in addition to the removed-entry sentinel value. Multiplicative hash + * uses the high order bits of keyHash, so this least-significant reservation + * should not hurt the hash function's effectiveness much. + * + * If you change any of these magic numbers, also update JS_DHASH_ENTRY_IS_LIVE + * in jsdhash.h. It used to be private to jsdhash.c, but then became public to + * assist iterator writers who inspect table->entryStore directly. + */ +#define COLLISION_FLAG ((JSDHashNumber) 1) +#define MARK_ENTRY_FREE(entry) ((entry)->keyHash = 0) +#define MARK_ENTRY_REMOVED(entry) ((entry)->keyHash = 1) +#define ENTRY_IS_REMOVED(entry) ((entry)->keyHash == 1) +#define ENTRY_IS_LIVE(entry) JS_DHASH_ENTRY_IS_LIVE(entry) +#define ENSURE_LIVE_KEYHASH(hash0) if (hash0 < 2) hash0 -= 2; else (void)0 + +/* Match an entry's keyHash against an unstored one computed from a key. */ +#define MATCH_ENTRY_KEYHASH(entry,hash0) \ + (((entry)->keyHash & ~COLLISION_FLAG) == (hash0)) + +/* Compute the address of the indexed entry in table. */ +#define ADDRESS_ENTRY(table, index) \ + ((JSDHashEntryHdr *)((table)->entryStore + (index) * (table)->entrySize)) + +JS_PUBLIC_API(void) +JS_DHashTableFinish(JSDHashTable *table) +{ + char *entryAddr, *entryLimit; + uint32 entrySize; + JSDHashEntryHdr *entry; + +#ifdef DEBUG_XXXbrendan + static FILE *dumpfp = NULL; + if (!dumpfp) dumpfp = fopen("/tmp/jsdhash.bigdump", "w"); + if (dumpfp) { +#ifdef MOZILLA_CLIENT + NS_TraceStack(1, dumpfp); +#endif + JS_DHashTableDumpMeter(table, NULL, dumpfp); + fputc('\n', dumpfp); + } +#endif + + /* Call finalize before clearing entries, so it can enumerate them. */ + table->ops->finalize(table); + + /* Clear any remaining live entries. */ + entryAddr = table->entryStore; + entrySize = table->entrySize; + entryLimit = entryAddr + JS_DHASH_TABLE_SIZE(table) * entrySize; + while (entryAddr < entryLimit) { + entry = (JSDHashEntryHdr *)entryAddr; + if (ENTRY_IS_LIVE(entry)) { + METER(table->stats.removeEnums++); + table->ops->clearEntry(table, entry); + } + entryAddr += entrySize; + } + + /* Free entry storage last. */ + table->ops->freeTable(table, table->entryStore); +} + +static JSDHashEntryHdr * +SearchTable(JSDHashTable *table, const void *key, JSDHashNumber keyHash, + JSDHashOperator op) +{ + JSDHashNumber hash1, hash2; + int hashShift, sizeLog2; + JSDHashEntryHdr *entry, *firstRemoved; + JSDHashMatchEntry matchEntry; + uint32 sizeMask; + + METER(table->stats.searches++); + JS_ASSERT(!(keyHash & COLLISION_FLAG)); + + /* Compute the primary hash address. */ + hashShift = table->hashShift; + hash1 = HASH1(keyHash, hashShift); + entry = ADDRESS_ENTRY(table, hash1); + + /* Miss: return space for a new entry. */ + if (JS_DHASH_ENTRY_IS_FREE(entry)) { + METER(table->stats.misses++); + return entry; + } + + /* Hit: return entry. */ + matchEntry = table->ops->matchEntry; + if (MATCH_ENTRY_KEYHASH(entry, keyHash) && matchEntry(table, entry, key)) { + METER(table->stats.hits++); + return entry; + } + + /* Collision: double hash. */ + sizeLog2 = JS_DHASH_BITS - table->hashShift; + hash2 = HASH2(keyHash, sizeLog2, hashShift); + sizeMask = JS_BITMASK(sizeLog2); + + /* Save the first removed entry pointer so JS_DHASH_ADD can recycle it. */ + if (ENTRY_IS_REMOVED(entry)) { + firstRemoved = entry; + } else { + firstRemoved = NULL; + if (op == JS_DHASH_ADD) + entry->keyHash |= COLLISION_FLAG; + } + + for (;;) { + METER(table->stats.steps++); + hash1 -= hash2; + hash1 &= sizeMask; + + entry = ADDRESS_ENTRY(table, hash1); + if (JS_DHASH_ENTRY_IS_FREE(entry)) { + METER(table->stats.misses++); + return (firstRemoved && op == JS_DHASH_ADD) ? firstRemoved : entry; + } + + if (MATCH_ENTRY_KEYHASH(entry, keyHash) && + matchEntry(table, entry, key)) { + METER(table->stats.hits++); + return entry; + } + + if (ENTRY_IS_REMOVED(entry)) { + if (!firstRemoved) + firstRemoved = entry; + } else { + if (op == JS_DHASH_ADD) + entry->keyHash |= COLLISION_FLAG; + } + } + + /* NOTREACHED */ + return NULL; +} + +static JSBool +ChangeTable(JSDHashTable *table, int deltaLog2) +{ + int oldLog2, newLog2; + uint32 oldCapacity, newCapacity; + char *newEntryStore, *oldEntryStore, *oldEntryAddr; + uint32 entrySize, i, nbytes; + JSDHashEntryHdr *oldEntry, *newEntry; + JSDHashGetKey getKey; + JSDHashMoveEntry moveEntry; + + /* Look, but don't touch, until we succeed in getting new entry store. */ + oldLog2 = JS_DHASH_BITS - table->hashShift; + newLog2 = oldLog2 + deltaLog2; + oldCapacity = JS_BIT(oldLog2); + newCapacity = JS_BIT(newLog2); + if (newCapacity >= JS_DHASH_SIZE_LIMIT) + return JS_FALSE; + entrySize = table->entrySize; + nbytes = newCapacity * entrySize; + + newEntryStore = table->ops->allocTable(table, nbytes); + if (!newEntryStore) + return JS_FALSE; + + /* We can't fail from here on, so update table parameters. */ + table->hashShift = JS_DHASH_BITS - newLog2; + table->removedCount = 0; + table->generation++; + + /* Assign the new entry store to table. */ + memset(newEntryStore, 0, nbytes); + oldEntryAddr = oldEntryStore = table->entryStore; + table->entryStore = newEntryStore; + getKey = table->ops->getKey; + moveEntry = table->ops->moveEntry; + + /* Copy only live entries, leaving removed ones behind. */ + for (i = 0; i < oldCapacity; i++) { + oldEntry = (JSDHashEntryHdr *)oldEntryAddr; + if (ENTRY_IS_LIVE(oldEntry)) { + oldEntry->keyHash &= ~COLLISION_FLAG; + newEntry = SearchTable(table, getKey(table, oldEntry), + oldEntry->keyHash, JS_DHASH_ADD); + JS_ASSERT(JS_DHASH_ENTRY_IS_FREE(newEntry)); + moveEntry(table, oldEntry, newEntry); + newEntry->keyHash = oldEntry->keyHash; + } + oldEntryAddr += entrySize; + } + + table->ops->freeTable(table, oldEntryStore); + return JS_TRUE; +} + +JS_PUBLIC_API(JSDHashEntryHdr *) +JS_DHashTableOperate(JSDHashTable *table, const void *key, JSDHashOperator op) +{ + JSDHashNumber keyHash; + JSDHashEntryHdr *entry; + uint32 size; + int deltaLog2; + + keyHash = table->ops->hashKey(table, key); + keyHash *= JS_DHASH_GOLDEN_RATIO; + + /* Avoid 0 and 1 hash codes, they indicate free and removed entries. */ + ENSURE_LIVE_KEYHASH(keyHash); + keyHash &= ~COLLISION_FLAG; + + switch (op) { + case JS_DHASH_LOOKUP: + METER(table->stats.lookups++); + entry = SearchTable(table, key, keyHash, op); + break; + + case JS_DHASH_ADD: + /* + * If alpha is >= .75, grow or compress the table. If key is already + * in the table, we may grow once more than necessary, but only if we + * are on the edge of being overloaded. + */ + size = JS_DHASH_TABLE_SIZE(table); + if (table->entryCount + table->removedCount >= MAX_LOAD(table, size)) { + /* Compress if a quarter or more of all entries are removed. */ + if (table->removedCount >= size >> 2) { + METER(table->stats.compresses++); + deltaLog2 = 0; + } else { + METER(table->stats.grows++); + deltaLog2 = 1; + } + + /* + * Grow or compress table, returning null if ChangeTable fails and + * falling through might claim the last free entry. + */ + if (!ChangeTable(table, deltaLog2) && + table->entryCount + table->removedCount == size - 1) { + METER(table->stats.addFailures++); + return NULL; + } + } + + /* + * Look for entry after possibly growing, so we don't have to add it, + * then skip it while growing the table and re-add it after. + */ + entry = SearchTable(table, key, keyHash, op); + if (!ENTRY_IS_LIVE(entry)) { + /* Initialize the entry, indicating that it's no longer free. */ + METER(table->stats.addMisses++); + if (ENTRY_IS_REMOVED(entry)) { + METER(table->stats.addOverRemoved++); + table->removedCount--; + keyHash |= COLLISION_FLAG; + } + if (table->ops->initEntry && + !table->ops->initEntry(table, entry, key)) { + /* We haven't claimed entry yet; fail with null return. */ + memset(entry + 1, 0, table->entrySize - sizeof *entry); + return NULL; + } + entry->keyHash = keyHash; + table->entryCount++; + } + METER(else table->stats.addHits++); + break; + + case JS_DHASH_REMOVE: + entry = SearchTable(table, key, keyHash, op); + if (ENTRY_IS_LIVE(entry)) { + /* Clear this entry and mark it as "removed". */ + METER(table->stats.removeHits++); + JS_DHashTableRawRemove(table, entry); + + /* Shrink if alpha is <= .25 and table isn't too small already. */ + size = JS_DHASH_TABLE_SIZE(table); + if (size > JS_DHASH_MIN_SIZE && + table->entryCount <= MIN_LOAD(table, size)) { + METER(table->stats.shrinks++); + (void) ChangeTable(table, -1); + } + } + METER(else table->stats.removeMisses++); + entry = NULL; + break; + + default: + JS_ASSERT(0); + entry = NULL; + } + + return entry; +} + +JS_PUBLIC_API(void) +JS_DHashTableRawRemove(JSDHashTable *table, JSDHashEntryHdr *entry) +{ + JSDHashNumber keyHash; /* load first in case clearEntry goofs it */ + + JS_ASSERT(JS_DHASH_ENTRY_IS_LIVE(entry)); + keyHash = entry->keyHash; + table->ops->clearEntry(table, entry); + if (keyHash & COLLISION_FLAG) { + MARK_ENTRY_REMOVED(entry); + table->removedCount++; + } else { + METER(table->stats.removeFrees++); + MARK_ENTRY_FREE(entry); + } + table->entryCount--; +} + +JS_PUBLIC_API(uint32) +JS_DHashTableEnumerate(JSDHashTable *table, JSDHashEnumerator etor, void *arg) +{ + char *entryAddr, *entryLimit; + uint32 i, capacity, entrySize; + JSBool didRemove; + JSDHashEntryHdr *entry; + JSDHashOperator op; + + entryAddr = table->entryStore; + entrySize = table->entrySize; + capacity = JS_DHASH_TABLE_SIZE(table); + entryLimit = entryAddr + capacity * entrySize; + i = 0; + didRemove = JS_FALSE; + while (entryAddr < entryLimit) { + entry = (JSDHashEntryHdr *)entryAddr; + if (ENTRY_IS_LIVE(entry)) { + op = etor(table, entry, i++, arg); + if (op & JS_DHASH_REMOVE) { + METER(table->stats.removeEnums++); + JS_DHashTableRawRemove(table, entry); + didRemove = JS_TRUE; + } + if (op & JS_DHASH_STOP) + break; + } + entryAddr += entrySize; + } + + /* + * Shrink or compress if a quarter or more of all entries are removed, or + * if the table is underloaded according to the configured minimum alpha, + * and is not minimal-size already. Do this only if we removed above, so + * non-removing enumerations can count on stable table->entryStore until + * the next non-lookup-Operate or removing-Enumerate. + */ + if (didRemove && + (table->removedCount >= capacity >> 2 || + (capacity > JS_DHASH_MIN_SIZE && + table->entryCount <= MIN_LOAD(table, capacity)))) { + METER(table->stats.enumShrinks++); + capacity = table->entryCount; + capacity += capacity >> 1; + if (capacity < JS_DHASH_MIN_SIZE) + capacity = JS_DHASH_MIN_SIZE; + (void) ChangeTable(table, + JS_CeilingLog2(capacity) + - (JS_DHASH_BITS - table->hashShift)); + } + return i; +} + +#ifdef JS_DHASHMETER +#include + +JS_PUBLIC_API(void) +JS_DHashTableDumpMeter(JSDHashTable *table, JSDHashEnumerator dump, FILE *fp) +{ + char *entryAddr; + uint32 entrySize, entryCount; + int hashShift, sizeLog2; + uint32 i, tableSize, sizeMask, chainLen, maxChainLen, chainCount; + JSDHashNumber hash1, hash2, saveHash1, maxChainHash1, maxChainHash2; + double sqsum, mean, variance, sigma; + JSDHashEntryHdr *entry, *probe; + + entryAddr = table->entryStore; + entrySize = table->entrySize; + hashShift = table->hashShift; + sizeLog2 = JS_DHASH_BITS - hashShift; + tableSize = JS_DHASH_TABLE_SIZE(table); + sizeMask = JS_BITMASK(sizeLog2); + chainCount = maxChainLen = 0; + hash2 = 0; + sqsum = 0; + + for (i = 0; i < tableSize; i++) { + entry = (JSDHashEntryHdr *)entryAddr; + entryAddr += entrySize; + if (!ENTRY_IS_LIVE(entry)) + continue; + hash1 = HASH1(entry->keyHash & ~COLLISION_FLAG, hashShift); + saveHash1 = hash1; + probe = ADDRESS_ENTRY(table, hash1); + chainLen = 1; + if (probe == entry) { + /* Start of a (possibly unit-length) chain. */ + chainCount++; + } else { + hash2 = HASH2(entry->keyHash & ~COLLISION_FLAG, sizeLog2, + hashShift); + do { + chainLen++; + hash1 -= hash2; + hash1 &= sizeMask; + probe = ADDRESS_ENTRY(table, hash1); + } while (probe != entry); + } + sqsum += chainLen * chainLen; + if (chainLen > maxChainLen) { + maxChainLen = chainLen; + maxChainHash1 = saveHash1; + maxChainHash2 = hash2; + } + } + + entryCount = table->entryCount; + if (entryCount && chainCount) { + mean = (double)entryCount / chainCount; + variance = chainCount * sqsum - entryCount * entryCount; + if (variance < 0 || chainCount == 1) + variance = 0; + else + variance /= chainCount * (chainCount - 1); + sigma = sqrt(variance); + } else { + mean = sigma = 0; + } + + fprintf(fp, "Double hashing statistics:\n"); + fprintf(fp, " table size (in entries): %u\n", tableSize); + fprintf(fp, " number of entries: %u\n", table->entryCount); + fprintf(fp, " number of removed entries: %u\n", table->removedCount); + fprintf(fp, " number of searches: %u\n", table->stats.searches); + fprintf(fp, " number of hits: %u\n", table->stats.hits); + fprintf(fp, " number of misses: %u\n", table->stats.misses); + fprintf(fp, " mean steps per search: %g\n", table->stats.searches ? + (double)table->stats.steps + / table->stats.searches : + 0.); + fprintf(fp, " mean hash chain length: %g\n", mean); + fprintf(fp, " standard deviation: %g\n", sigma); + fprintf(fp, " maximum hash chain length: %u\n", maxChainLen); + fprintf(fp, " number of lookups: %u\n", table->stats.lookups); + fprintf(fp, " adds that made a new entry: %u\n", table->stats.addMisses); + fprintf(fp, "adds that recycled removeds: %u\n", table->stats.addOverRemoved); + fprintf(fp, " adds that found an entry: %u\n", table->stats.addHits); + fprintf(fp, " add failures: %u\n", table->stats.addFailures); + fprintf(fp, " useful removes: %u\n", table->stats.removeHits); + fprintf(fp, " useless removes: %u\n", table->stats.removeMisses); + fprintf(fp, "removes that freed an entry: %u\n", table->stats.removeFrees); + fprintf(fp, " removes while enumerating: %u\n", table->stats.removeEnums); + fprintf(fp, " number of grows: %u\n", table->stats.grows); + fprintf(fp, " number of shrinks: %u\n", table->stats.shrinks); + fprintf(fp, " number of compresses: %u\n", table->stats.compresses); + fprintf(fp, "number of enumerate shrinks: %u\n", table->stats.enumShrinks); + + if (dump && maxChainLen && hash2) { + fputs("Maximum hash chain:\n", fp); + hash1 = maxChainHash1; + hash2 = maxChainHash2; + entry = ADDRESS_ENTRY(table, hash1); + i = 0; + do { + if (dump(table, entry, i++, fp) != JS_DHASH_NEXT) + break; + hash1 -= hash2; + hash1 &= sizeMask; + entry = ADDRESS_ENTRY(table, hash1); + } while (JS_DHASH_ENTRY_IS_BUSY(entry)); + } +} +#endif /* JS_DHASHMETER */ diff --git a/src/dom/js/jsdhash.h b/src/dom/js/jsdhash.h new file mode 100644 index 000000000..39982ce05 --- /dev/null +++ b/src/dom/js/jsdhash.h @@ -0,0 +1,573 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla JavaScript code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1999-2001 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brendan Eich (Original Author) + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsdhash_h___ +#define jsdhash_h___ +/* + * Double hashing, a la Knuth 6. + */ +#include "jstypes.h" + +JS_BEGIN_EXTERN_C + +#ifdef DEBUG_XXXbrendan +#define JS_DHASHMETER 1 +#endif + +/* Table size limit, do not equal or exceed (see min&maxAlphaFrac, below). */ +#undef JS_DHASH_SIZE_LIMIT +#define JS_DHASH_SIZE_LIMIT JS_BIT(24) + +/* Minimum table size, or gross entry count (net is at most .75 loaded). */ +#ifndef JS_DHASH_MIN_SIZE +#define JS_DHASH_MIN_SIZE 16 +#elif (JS_DHASH_MIN_SIZE & (JS_DHASH_MIN_SIZE - 1)) != 0 +#error "JS_DHASH_MIN_SIZE must be a power of two!" +#endif + +/* + * Multiplicative hash uses an unsigned 32 bit integer and the golden ratio, + * expressed as a fixed-point 32-bit fraction. + */ +#define JS_DHASH_BITS 32 +#define JS_DHASH_GOLDEN_RATIO 0x9E3779B9U + +/* Primitive and forward-struct typedefs. */ +typedef uint32 JSDHashNumber; +typedef struct JSDHashEntryHdr JSDHashEntryHdr; +typedef struct JSDHashEntryStub JSDHashEntryStub; +typedef struct JSDHashTable JSDHashTable; +typedef struct JSDHashTableOps JSDHashTableOps; + +/* + * Table entry header structure. + * + * In order to allow in-line allocation of key and value, we do not declare + * either here. Instead, the API uses const void *key as a formal parameter, + * and asks each entry for its key when necessary via a getKey callback, used + * when growing or shrinking the table. Other callback types are defined + * below and grouped into the JSDHashTableOps structure, for single static + * initialization per hash table sub-type. + * + * Each hash table sub-type should nest the JSDHashEntryHdr structure at the + * front of its particular entry type. The keyHash member contains the result + * of multiplying the hash code returned from the hashKey callback (see below) + * by JS_DHASH_GOLDEN_RATIO, then constraining the result to avoid the magic 0 + * and 1 values. The stored keyHash value is table size invariant, and it is + * maintained automatically by JS_DHashTableOperate -- users should never set + * it, and its only uses should be via the entry macros below. + * + * The JS_DHASH_ENTRY_IS_LIVE macro tests whether entry is neither free nor + * removed. An entry may be either busy or free; if busy, it may be live or + * removed. Consumers of this API should not access members of entries that + * are not live. + * + * However, use JS_DHASH_ENTRY_IS_BUSY for faster liveness testing of entries + * returned by JS_DHashTableOperate, as JS_DHashTableOperate never returns a + * non-live, busy (i.e., removed) entry pointer to its caller. See below for + * more details on JS_DHashTableOperate's calling rules. + */ +struct JSDHashEntryHdr { + JSDHashNumber keyHash; /* every entry must begin like this */ +}; + +#define JS_DHASH_ENTRY_IS_FREE(entry) ((entry)->keyHash == 0) +#define JS_DHASH_ENTRY_IS_BUSY(entry) (!JS_DHASH_ENTRY_IS_FREE(entry)) +#define JS_DHASH_ENTRY_IS_LIVE(entry) ((entry)->keyHash >= 2) + +/* + * A JSDHashTable is currently 8 words (without the JS_DHASHMETER overhead) + * on most architectures, and may be allocated on the stack or within another + * structure or class (see below for the Init and Finish functions to use). + * + * To decide whether to use double hashing vs. chaining, we need to develop a + * trade-off relation, as follows: + * + * Let alpha be the load factor, esize the entry size in words, count the + * entry count, and pow2 the power-of-two table size in entries. + * + * (JSDHashTable overhead) > (JSHashTable overhead) + * (unused table entry space) > (malloc and .next overhead per entry) + + * (buckets overhead) + * (1 - alpha) * esize * pow2 > 2 * count + pow2 + * + * Notice that alpha is by definition (count / pow2): + * + * (1 - alpha) * esize * pow2 > 2 * alpha * pow2 + pow2 + * (1 - alpha) * esize > 2 * alpha + 1 + * + * esize > (1 + 2 * alpha) / (1 - alpha) + * + * This assumes both tables must keep keyHash, key, and value for each entry, + * where key and value point to separately allocated strings or structures. + * If key and value can be combined into one pointer, then the trade-off is: + * + * esize > (1 + 3 * alpha) / (1 - alpha) + * + * If the entry value can be a subtype of JSDHashEntryHdr, rather than a type + * that must be allocated separately and referenced by an entry.value pointer + * member, and provided key's allocation can be fused with its entry's, then + * k (the words wasted per entry with chaining) is 4. + * + * To see these curves, feed gnuplot input like so: + * + * gnuplot> f(x,k) = (1 + k * x) / (1 - x) + * gnuplot> plot [0:.75] f(x,2), f(x,3), f(x,4) + * + * For k of 2 and a well-loaded table (alpha > .5), esize must be more than 4 + * words for chaining to be more space-efficient than double hashing. + * + * Solving for alpha helps us decide when to shrink an underloaded table: + * + * esize > (1 + k * alpha) / (1 - alpha) + * esize - alpha * esize > 1 + k * alpha + * esize - 1 > (k + esize) * alpha + * (esize - 1) / (k + esize) > alpha + * + * alpha < (esize - 1) / (esize + k) + * + * Therefore double hashing should keep alpha >= (esize - 1) / (esize + k), + * assuming esize is not too large (in which case, chaining should probably be + * used for any alpha). For esize=2 and k=3, we want alpha >= .2; for esize=3 + * and k=2, we want alpha >= .4. For k=4, esize could be 6, and alpha >= .5 + * would still obtain. See the JS_DHASH_MIN_ALPHA macro further below. + * + * The current implementation uses a configurable lower bound on alpha, which + * defaults to .25, when deciding to shrink the table (while still respecting + * JS_DHASH_MIN_SIZE). + * + * Note a qualitative difference between chaining and double hashing: under + * chaining, entry addresses are stable across table shrinks and grows. With + * double hashing, you can't safely hold an entry pointer and use it after an + * ADD or REMOVE operation, unless you sample table->generation before adding + * or removing, and compare the sample after, dereferencing the entry pointer + * only if table->generation has not changed. + * + * The moral of this story: there is no one-size-fits-all hash table scheme, + * but for small table entry size, and assuming entry address stability is not + * required, double hashing wins. + */ +struct JSDHashTable { + const JSDHashTableOps *ops; /* virtual operations, see below */ + void *data; /* ops- and instance-specific data */ + int16 hashShift; /* multiplicative hash shift */ + uint8 maxAlphaFrac; /* 8-bit fixed point max alpha */ + uint8 minAlphaFrac; /* 8-bit fixed point min alpha */ + uint32 entrySize; /* number of bytes in an entry */ + uint32 entryCount; /* number of entries in table */ + uint32 removedCount; /* removed entry sentinels in table */ + uint32 generation; /* entry storage generation number */ + char *entryStore; /* entry storage */ +#ifdef JS_DHASHMETER + struct JSDHashStats { + uint32 searches; /* total number of table searches */ + uint32 steps; /* hash chain links traversed */ + uint32 hits; /* searches that found key */ + uint32 misses; /* searches that didn't find key */ + uint32 lookups; /* number of JS_DHASH_LOOKUPs */ + uint32 addMisses; /* adds that miss, and do work */ + uint32 addOverRemoved; /* adds that recycled a removed entry */ + uint32 addHits; /* adds that hit an existing entry */ + uint32 addFailures; /* out-of-memory during add growth */ + uint32 removeHits; /* removes that hit, and do work */ + uint32 removeMisses; /* useless removes that miss */ + uint32 removeFrees; /* removes that freed entry directly */ + uint32 removeEnums; /* removes done by Enumerate */ + uint32 grows; /* table expansions */ + uint32 shrinks; /* table contractions */ + uint32 compresses; /* table compressions */ + uint32 enumShrinks; /* contractions after Enumerate */ + } stats; +#endif +}; + +/* + * Size in entries (gross, not net of free and removed sentinels) for table. + * We store hashShift rather than sizeLog2 to optimize the collision-free case + * in SearchTable. + */ +#define JS_DHASH_TABLE_SIZE(table) JS_BIT(JS_DHASH_BITS - (table)->hashShift) + +/* + * Table space at entryStore is allocated and freed using these callbacks. + * The allocator should return null on error only (not if called with nbytes + * equal to 0; but note that jsdhash.c code will never call with 0 nbytes). + */ +typedef void * +(* JS_DLL_CALLBACK JSDHashAllocTable)(JSDHashTable *table, uint32 nbytes); + +typedef void +(* JS_DLL_CALLBACK JSDHashFreeTable) (JSDHashTable *table, void *ptr); + +/* + * When a table grows or shrinks, each entry is queried for its key using this + * callback. NB: in that event, entry is not in table any longer; it's in the + * old entryStore vector, which is due to be freed once all entries have been + * moved via moveEntry callbacks. + */ +typedef const void * +(* JS_DLL_CALLBACK JSDHashGetKey) (JSDHashTable *table, + JSDHashEntryHdr *entry); + +/* + * Compute the hash code for a given key to be looked up, added, or removed + * from table. A hash code may have any JSDHashNumber value. + */ +typedef JSDHashNumber +(* JS_DLL_CALLBACK JSDHashHashKey) (JSDHashTable *table, const void *key); + +/* + * Compare the key identifying entry in table with the provided key parameter. + * Return JS_TRUE if keys match, JS_FALSE otherwise. + */ +typedef JSBool +(* JS_DLL_CALLBACK JSDHashMatchEntry)(JSDHashTable *table, + const JSDHashEntryHdr *entry, + const void *key); + +/* + * Copy the data starting at from to the new entry storage at to. Do not add + * reference counts for any strong references in the entry, however, as this + * is a "move" operation: the old entry storage at from will be freed without + * any reference-decrementing callback shortly. + */ +typedef void +(* JS_DLL_CALLBACK JSDHashMoveEntry)(JSDHashTable *table, + const JSDHashEntryHdr *from, + JSDHashEntryHdr *to); + +/* + * Clear the entry and drop any strong references it holds. This callback is + * invoked during a JS_DHASH_REMOVE operation (see below for operation codes), + * but only if the given key is found in the table. + */ +typedef void +(* JS_DLL_CALLBACK JSDHashClearEntry)(JSDHashTable *table, + JSDHashEntryHdr *entry); + +/* + * Called when a table (whether allocated dynamically by itself, or nested in + * a larger structure, or allocated on the stack) is finished. This callback + * allows table->ops-specific code to finalize table->data. + */ +typedef void +(* JS_DLL_CALLBACK JSDHashFinalize) (JSDHashTable *table); + +/* + * Initialize a new entry, apart from keyHash. This function is called when + * JS_DHashTableOperate's JS_DHASH_ADD case finds no existing entry for the + * given key, and must add a new one. At that point, entry->keyHash is not + * set yet, to avoid claiming the last free entry in a severely overloaded + * table. + */ +typedef JSBool +(* JS_DLL_CALLBACK JSDHashInitEntry)(JSDHashTable *table, + JSDHashEntryHdr *entry, + const void *key); + +/* + * Finally, the "vtable" structure for JSDHashTable. The first eight hooks + * must be provided by implementations; they're called unconditionally by the + * generic jsdhash.c code. Hooks after these may be null. + * + * Summary of allocation-related hook usage with C++ placement new emphasis: + * allocTable Allocate raw bytes with malloc, no ctors run. + * freeTable Free raw bytes with free, no dtors run. + * initEntry Call placement new using default key-based ctor. + * Return JS_TRUE on success, JS_FALSE on error. + * moveEntry Call placement new using copy ctor, run dtor on old + * entry storage. + * clearEntry Run dtor on entry. + * finalize Stub unless table->data was initialized and needs to + * be finalized. + * + * Note the reason why initEntry is optional: the default hooks (stubs) clear + * entry storage: On successful JS_DHashTableOperate(tbl, key, JS_DHASH_ADD), + * the returned entry pointer addresses an entry struct whose keyHash member + * has been set non-zero, but all other entry members are still clear (null). + * JS_DHASH_ADD callers can test such members to see whether the entry was + * newly created by the JS_DHASH_ADD call that just succeeded. If placement + * new or similar initialization is required, define an initEntry hook. Of + * course, the clearEntry hook must zero or null appropriately. + * + * XXX assumes 0 is null for pointer types. + */ +struct JSDHashTableOps { + /* Mandatory hooks. All implementations must provide these. */ + JSDHashAllocTable allocTable; + JSDHashFreeTable freeTable; + JSDHashGetKey getKey; + JSDHashHashKey hashKey; + JSDHashMatchEntry matchEntry; + JSDHashMoveEntry moveEntry; + JSDHashClearEntry clearEntry; + JSDHashFinalize finalize; + + /* Optional hooks start here. If null, these are not called. */ + JSDHashInitEntry initEntry; +}; + +/* + * Default implementations for the above ops. + */ +extern JS_PUBLIC_API(void *) +JS_DHashAllocTable(JSDHashTable *table, uint32 nbytes); + +extern JS_PUBLIC_API(void) +JS_DHashFreeTable(JSDHashTable *table, void *ptr); + +extern JS_PUBLIC_API(JSDHashNumber) +JS_DHashStringKey(JSDHashTable *table, const void *key); + +/* A minimal entry contains a keyHash header and a void key pointer. */ +struct JSDHashEntryStub { + JSDHashEntryHdr hdr; + const void *key; +}; + +extern JS_PUBLIC_API(const void *) +JS_DHashGetKeyStub(JSDHashTable *table, JSDHashEntryHdr *entry); + +extern JS_PUBLIC_API(JSDHashNumber) +JS_DHashVoidPtrKeyStub(JSDHashTable *table, const void *key); + +extern JS_PUBLIC_API(JSBool) +JS_DHashMatchEntryStub(JSDHashTable *table, + const JSDHashEntryHdr *entry, + const void *key); + +extern JS_PUBLIC_API(JSBool) +JS_DHashMatchStringKey(JSDHashTable *table, + const JSDHashEntryHdr *entry, + const void *key); + +extern JS_PUBLIC_API(void) +JS_DHashMoveEntryStub(JSDHashTable *table, + const JSDHashEntryHdr *from, + JSDHashEntryHdr *to); + +extern JS_PUBLIC_API(void) +JS_DHashClearEntryStub(JSDHashTable *table, JSDHashEntryHdr *entry); + +extern JS_PUBLIC_API(void) +JS_DHashFreeStringKey(JSDHashTable *table, JSDHashEntryHdr *entry); + +extern JS_PUBLIC_API(void) +JS_DHashFinalizeStub(JSDHashTable *table); + +/* + * If you use JSDHashEntryStub or a subclass of it as your entry struct, and + * if your entries move via memcpy and clear via memset(0), you can use these + * stub operations. + */ +extern JS_PUBLIC_API(const JSDHashTableOps *) +JS_DHashGetStubOps(void); + +/* + * Dynamically allocate a new JSDHashTable using malloc, initialize it using + * JS_DHashTableInit, and return its address. Return null on malloc failure. + * Note that the entry storage at table->entryStore will be allocated using + * the ops->allocTable callback. + */ +extern JS_PUBLIC_API(JSDHashTable *) +JS_NewDHashTable(const JSDHashTableOps *ops, void *data, uint32 entrySize, + uint32 capacity); + +/* + * Finalize table's data, free its entry storage (via table->ops->freeTable), + * and return the memory starting at table to the malloc heap. + */ +extern JS_PUBLIC_API(void) +JS_DHashTableDestroy(JSDHashTable *table); + +/* + * Initialize table with ops, data, entrySize, and capacity. Capacity is a + * guess for the smallest table size at which the table will usually be less + * than 75% loaded (the table will grow or shrink as needed; capacity serves + * only to avoid inevitable early growth from JS_DHASH_MIN_SIZE). + */ +extern JS_PUBLIC_API(JSBool) +JS_DHashTableInit(JSDHashTable *table, const JSDHashTableOps *ops, void *data, + uint32 entrySize, uint32 capacity); + +/* + * Set maximum and minimum alpha for table. The defaults are 0.75 and .25. + * maxAlpha must be in [0.5, 0.9375] for the default JS_DHASH_MIN_SIZE; or if + * MinSize=JS_DHASH_MIN_SIZE <= 256, in [0.5, (float)(MinSize-1)/MinSize]; or + * else in [0.5, 255.0/256]. minAlpha must be in [0, maxAlpha / 2), so that + * we don't shrink on the very next remove after growing a table upon adding + * an entry that brings entryCount past maxAlpha * tableSize. + */ +JS_PUBLIC_API(void) +JS_DHashTableSetAlphaBounds(JSDHashTable *table, + float maxAlpha, + float minAlpha); + +/* + * Call this macro with k, the number of pointer-sized words wasted per entry + * under chaining, to compute the minimum alpha at which double hashing still + * beats chaining. + */ +#define JS_DHASH_MIN_ALPHA(table, k) \ + ((float)((table)->entrySize / sizeof(void *) - 1) \ + / ((table)->entrySize / sizeof(void *) + (k))) + +/* + * Finalize table's data, free its entry storage using table->ops->freeTable, + * and leave its members unchanged from their last live values (which leaves + * pointers dangling). If you want to burn cycles clearing table, it's up to + * your code to call memset. + */ +extern JS_PUBLIC_API(void) +JS_DHashTableFinish(JSDHashTable *table); + +/* + * To consolidate keyHash computation and table grow/shrink code, we use a + * single entry point for lookup, add, and remove operations. The operation + * codes are declared here, along with codes returned by JSDHashEnumerator + * functions, which control JS_DHashTableEnumerate's behavior. + */ +typedef enum JSDHashOperator { + JS_DHASH_LOOKUP = 0, /* lookup entry */ + JS_DHASH_ADD = 1, /* add entry */ + JS_DHASH_REMOVE = 2, /* remove entry, or enumerator says remove */ + JS_DHASH_NEXT = 0, /* enumerator says continue */ + JS_DHASH_STOP = 1 /* enumerator says stop */ +} JSDHashOperator; + +/* + * To lookup a key in table, call: + * + * entry = JS_DHashTableOperate(table, key, JS_DHASH_LOOKUP); + * + * If JS_DHASH_ENTRY_IS_BUSY(entry) is true, key was found and it identifies + * entry. If JS_DHASH_ENTRY_IS_FREE(entry) is true, key was not found. + * + * To add an entry identified by key to table, call: + * + * entry = JS_DHashTableOperate(table, key, JS_DHASH_ADD); + * + * If entry is null upon return, then either the table is severely overloaded, + * and memory can't be allocated for entry storage via table->ops->allocTable; + * Or if table->ops->initEntry is non-null, the table->ops->initEntry op may + * have returned false. + * + * Otherwise, entry->keyHash has been set so that JS_DHASH_ENTRY_IS_BUSY(entry) + * is true, and it is up to the caller to initialize the key and value parts + * of the entry sub-type, if they have not been set already (i.e. if entry was + * not already in the table, and if the optional initEntry hook was not used). + * + * To remove an entry identified by key from table, call: + * + * (void) JS_DHashTableOperate(table, key, JS_DHASH_REMOVE); + * + * If key's entry is found, it is cleared (via table->ops->clearEntry) and + * the entry is marked so that JS_DHASH_ENTRY_IS_FREE(entry). This operation + * returns null unconditionally; you should ignore its return value. + */ +extern JS_PUBLIC_API(JSDHashEntryHdr *) +JS_DHashTableOperate(JSDHashTable *table, const void *key, JSDHashOperator op); + +/* + * Remove an entry already accessed via LOOKUP or ADD. + * + * NB: this is a "raw" or low-level routine, intended to be used only where + * the inefficiency of a full JS_DHashTableOperate (which rehashes in order + * to find the entry given its key) is not tolerable. This function does not + * shrink the table if it is underloaded. It does not update stats #ifdef + * JS_DHASHMETER, either. + */ +extern JS_PUBLIC_API(void) +JS_DHashTableRawRemove(JSDHashTable *table, JSDHashEntryHdr *entry); + +/* + * Enumerate entries in table using etor: + * + * count = JS_DHashTableEnumerate(table, etor, arg); + * + * JS_DHashTableEnumerate calls etor like so: + * + * op = etor(table, entry, number, arg); + * + * where number is a zero-based ordinal assigned to live entries according to + * their order in table->entryStore. + * + * The return value, op, is treated as a set of flags. If op is JS_DHASH_NEXT, + * then continue enumerating. If op contains JS_DHASH_REMOVE, then clear (via + * table->ops->clearEntry) and free entry. Then we check whether op contains + * JS_DHASH_STOP; if so, stop enumerating and return the number of live entries + * that were enumerated so far. Return the total number of live entries when + * enumeration completes normally. + * + * If etor calls JS_DHashTableOperate on table with op != JS_DHASH_LOOKUP, it + * must return JS_DHASH_STOP; otherwise undefined behavior results. + * + * If any enumerator returns JS_DHASH_REMOVE, table->entryStore may be shrunk + * or compressed after enumeration, but before JS_DHashTableEnumerate returns. + * Such an enumerator therefore can't safely set aside entry pointers, but an + * enumerator that never returns JS_DHASH_REMOVE can set pointers to entries + * aside, e.g., to avoid copying live entries into an array of the entry type. + * Copying entry pointers is cheaper, and safe so long as the caller of such a + * "stable" Enumerate doesn't use the set-aside pointers after any call either + * to PL_DHashTableOperate, or to an "unstable" form of Enumerate, which might + * grow or shrink entryStore. + * + * If your enumerator wants to remove certain entries, but set aside pointers + * to other entries that it retains, it can use JS_DHashTableRawRemove on the + * entries to be removed, returning JS_DHASH_NEXT to skip them. Likewise, if + * you want to remove entries, but for some reason you do not want entryStore + * to be shrunk or compressed, you can call JS_DHashTableRawRemove safely on + * the entry being enumerated, rather than returning JS_DHASH_REMOVE. + */ +typedef JSDHashOperator +(* JS_DLL_CALLBACK JSDHashEnumerator)(JSDHashTable *table, JSDHashEntryHdr *hdr, + uint32 number, void *arg); + +extern JS_PUBLIC_API(uint32) +JS_DHashTableEnumerate(JSDHashTable *table, JSDHashEnumerator etor, void *arg); + +#ifdef JS_DHASHMETER +#include + +extern JS_PUBLIC_API(void) +JS_DHashTableDumpMeter(JSDHashTable *table, JSDHashEnumerator dump, FILE *fp); +#endif + +JS_END_EXTERN_C + +#endif /* jsdhash_h___ */ diff --git a/src/dom/js/jsdtoa.c b/src/dom/js/jsdtoa.c new file mode 100644 index 000000000..2bd163d75 --- /dev/null +++ b/src/dom/js/jsdtoa.c @@ -0,0 +1,3155 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * Portable double to alphanumeric string and back converters. + */ +#include "jsstddef.h" +#include "jslibmath.h" +#include "jstypes.h" +#include "jsdtoa.h" +#include "jsprf.h" +#include "jsutil.h" /* Added by JSIFY */ +#include "jspubtd.h" +#include "jsnum.h" + +#ifdef JS_THREADSAFE +#include "prlock.h" +#endif + +/**************************************************************** + * + * The author of this software is David M. Gay. + * + * Copyright (c) 1991 by Lucent Technologies. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose without fee is hereby granted, provided that this entire notice + * is included in all copies of any software which is or includes a copy + * or modification of this software and in all copies of the supporting + * documentation for such software. + * + * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY + * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY + * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. + * + ***************************************************************/ + +/* Please send bug reports to + David M. Gay + Bell Laboratories, Room 2C-463 + 600 Mountain Avenue + Murray Hill, NJ 07974-0636 + U.S.A. + dmg@bell-labs.com + */ + +/* On a machine with IEEE extended-precision registers, it is + * necessary to specify double-precision (53-bit) rounding precision + * before invoking strtod or dtoa. If the machine uses (the equivalent + * of) Intel 80x87 arithmetic, the call + * _control87(PC_53, MCW_PC); + * does this with many compilers. Whether this or another call is + * appropriate depends on the compiler; for this to work, it may be + * necessary to #include "float.h" or another system-dependent header + * file. + */ + +/* strtod for IEEE-arithmetic machines. + * + * This strtod returns a nearest machine number to the input decimal + * string (or sets err to JS_DTOA_ERANGE or JS_DTOA_ENOMEM). With IEEE + * arithmetic, ties are broken by the IEEE round-even rule. Otherwise + * ties are broken by biased rounding (add half and chop). + * + * Inspired loosely by William D. Clinger's paper "How to Read Floating + * Point Numbers Accurately" [Proc. ACM SIGPLAN '90, pp. 92-101]. + * + * Modifications: + * + * 1. We only require IEEE double-precision + * arithmetic (not IEEE double-extended). + * 2. We get by with floating-point arithmetic in a case that + * Clinger missed -- when we're computing d * 10^n + * for a small integer d and the integer n is not too + * much larger than 22 (the maximum integer k for which + * we can represent 10^k exactly), we may be able to + * compute (d*10^k) * 10^(e-k) with just one roundoff. + * 3. Rather than a bit-at-a-time adjustment of the binary + * result in the hard case, we use floating-point + * arithmetic to determine the adjustment to within + * one bit; only in really hard cases do we need to + * compute a second residual. + * 4. Because of 3., we don't need a large table of powers of 10 + * for ten-to-e (just some small tables, e.g. of 10^k + * for 0 <= k <= 22). + */ + +/* + * #define IEEE_8087 for IEEE-arithmetic machines where the least + * significant byte has the lowest address. + * #define IEEE_MC68k for IEEE-arithmetic machines where the most + * significant byte has the lowest address. + * #define Long int on machines with 32-bit ints and 64-bit longs. + * #define Sudden_Underflow for IEEE-format machines without gradual + * underflow (i.e., that flush to zero on underflow). + * #define No_leftright to omit left-right logic in fast floating-point + * computation of js_dtoa. + * #define Check_FLT_ROUNDS if FLT_ROUNDS can assume the values 2 or 3. + * #define RND_PRODQUOT to use rnd_prod and rnd_quot (assembly routines + * that use extended-precision instructions to compute rounded + * products and quotients) with IBM. + * #define ROUND_BIASED for IEEE-format with biased rounding. + * #define Inaccurate_Divide for IEEE-format with correctly rounded + * products but inaccurate quotients, e.g., for Intel i860. + * #define JS_HAVE_LONG_LONG on machines that have a "long long" + * integer type (of >= 64 bits). If long long is available and the name is + * something other than "long long", #define Llong to be the name, + * and if "unsigned Llong" does not work as an unsigned version of + * Llong, #define #ULLong to be the corresponding unsigned type. + * #define Bad_float_h if your system lacks a float.h or if it does not + * define some or all of DBL_DIG, DBL_MAX_10_EXP, DBL_MAX_EXP, + * FLT_RADIX, FLT_ROUNDS, and DBL_MAX. + * #define MALLOC your_malloc, where your_malloc(n) acts like malloc(n) + * if memory is available and otherwise does something you deem + * appropriate. If MALLOC is undefined, malloc will be invoked + * directly -- and assumed always to succeed. + * #define Omit_Private_Memory to omit logic (added Jan. 1998) for making + * memory allocations from a private pool of memory when possible. + * When used, the private pool is PRIVATE_MEM bytes long: 2000 bytes, + * unless #defined to be a different length. This default length + * suffices to get rid of MALLOC calls except for unusual cases, + * such as decimal-to-binary conversion of a very long string of + * digits. + * #define INFNAN_CHECK on IEEE systems to cause strtod to check for + * Infinity and NaN (case insensitively). On some systems (e.g., + * some HP systems), it may be necessary to #define NAN_WORD0 + * appropriately -- to the most significant word of a quiet NaN. + * (On HP Series 700/800 machines, -DNAN_WORD0=0x7ff40000 works.) + * #define MULTIPLE_THREADS if the system offers preemptively scheduled + * multiple threads. In this case, you must provide (or suitably + * #define) two locks, acquired by ACQUIRE_DTOA_LOCK() and released + * by RELEASE_DTOA_LOCK(). (The second lock, accessed + * in pow5mult, ensures lazy evaluation of only one copy of high + * powers of 5; omitting this lock would introduce a small + * probability of wasting memory, but would otherwise be harmless.) + * You must also invoke freedtoa(s) to free the value s returned by + * dtoa. You may do so whether or not MULTIPLE_THREADS is #defined. + * #define NO_IEEE_Scale to disable new (Feb. 1997) logic in strtod that + * avoids underflows on inputs whose result does not underflow. + */ +#ifdef IS_LITTLE_ENDIAN +#define IEEE_8087 +#else +#define IEEE_MC68k +#endif + +#ifndef Long +#define Long int32 +#endif + +#ifndef ULong +#define ULong uint32 +#endif + +#define Bug(errorMessageString) JS_ASSERT(!errorMessageString) + +#include "stdlib.h" +#include "string.h" + +#ifdef MALLOC +extern void *MALLOC(size_t); +#else +#define MALLOC malloc +#endif + +#define Omit_Private_Memory +/* Private memory currently doesn't work with JS_THREADSAFE */ +#ifndef Omit_Private_Memory +#ifndef PRIVATE_MEM +#define PRIVATE_MEM 2000 +#endif +#define PRIVATE_mem ((PRIVATE_MEM+sizeof(double)-1)/sizeof(double)) +static double private_mem[PRIVATE_mem], *pmem_next = private_mem; +#endif + +#ifdef Bad_float_h +#undef __STDC__ + +#define DBL_DIG 15 +#define DBL_MAX_10_EXP 308 +#define DBL_MAX_EXP 1024 +#define FLT_RADIX 2 +#define FLT_ROUNDS 1 +#define DBL_MAX 1.7976931348623157e+308 + + + +#ifndef LONG_MAX +#define LONG_MAX 2147483647 +#endif + +#else /* ifndef Bad_float_h */ +#include "float.h" +/* + * MacOS 10.2 defines the macro FLT_ROUNDS to an internal function + * which does not exist on 10.1. We can safely #define it to 1 here + * to allow 10.2 builds to run on 10.1, since we can't use fesetround() + * (which does not exist on 10.1 either). + */ +#if defined(MACOS_DEPLOYMENT_TARGET) && (MACOS_DEPLOYMENT_TARGET < 100200) +#undef FLT_ROUNDS +#define FLT_ROUNDS 1 +#endif +#endif /* Bad_float_h */ + +#ifndef __MATH_H__ +#include "math.h" +#endif + +#ifndef CONST +#define CONST const +#endif + +#if defined(IEEE_8087) + defined(IEEE_MC68k) != 1 +Exactly one of IEEE_8087 or IEEE_MC68k should be defined. +#endif + +#define word0(x) JSDOUBLE_HI32(x) +#define set_word0(x, y) JSDOUBLE_SET_HI32(x, y) +#define word1(x) JSDOUBLE_LO32(x) +#define set_word1(x, y) JSDOUBLE_SET_LO32(x, y) + +#define Storeinc(a,b,c) (*(a)++ = (b) << 16 | (c) & 0xffff) + +/* #define P DBL_MANT_DIG */ +/* Ten_pmax = floor(P*log(2)/log(5)) */ +/* Bletch = (highest power of 2 < DBL_MAX_10_EXP) / 16 */ +/* Quick_max = floor((P-1)*log(FLT_RADIX)/log(10) - 1) */ +/* Int_max = floor(P*log(FLT_RADIX)/log(10) - 1) */ + +#define Exp_shift 20 +#define Exp_shift1 20 +#define Exp_msk1 0x100000 +#define Exp_msk11 0x100000 +#define Exp_mask 0x7ff00000 +#define P 53 +#define Bias 1023 +#define Emin (-1022) +#define Exp_1 0x3ff00000 +#define Exp_11 0x3ff00000 +#define Ebits 11 +#define Frac_mask 0xfffff +#define Frac_mask1 0xfffff +#define Ten_pmax 22 +#define Bletch 0x10 +#define Bndry_mask 0xfffff +#define Bndry_mask1 0xfffff +#define LSB 1 +#define Sign_bit 0x80000000 +#define Log2P 1 +#define Tiny0 0 +#define Tiny1 1 +#define Quick_max 14 +#define Int_max 14 +#define Infinite(x) (word0(x) == 0x7ff00000) /* sufficient test for here */ +#ifndef NO_IEEE_Scale +#define Avoid_Underflow +#endif + + + +#ifdef RND_PRODQUOT +#define rounded_product(a,b) a = rnd_prod(a, b) +#define rounded_quotient(a,b) a = rnd_quot(a, b) +extern double rnd_prod(double, double), rnd_quot(double, double); +#else +#define rounded_product(a,b) a *= b +#define rounded_quotient(a,b) a /= b +#endif + +#define Big0 (Frac_mask1 | Exp_msk1*(DBL_MAX_EXP+Bias-1)) +#define Big1 0xffffffff + +#ifndef JS_HAVE_LONG_LONG +#undef ULLong +#else /* long long available */ +#ifndef Llong +#define Llong JSInt64 +#endif +#ifndef ULLong +#define ULLong JSUint64 +#endif +#endif /* JS_HAVE_LONG_LONG */ + +#ifdef JS_THREADSAFE +#define MULTIPLE_THREADS +static PRLock *freelist_lock; +#define ACQUIRE_DTOA_LOCK() \ + JS_BEGIN_MACRO \ + if (!initialized) \ + InitDtoa(); \ + PR_Lock(freelist_lock); \ + JS_END_MACRO +#define RELEASE_DTOA_LOCK() PR_Unlock(freelist_lock) +#else +#undef MULTIPLE_THREADS +#define ACQUIRE_DTOA_LOCK() /*nothing*/ +#define RELEASE_DTOA_LOCK() /*nothing*/ +#endif + +#define Kmax 15 + +struct Bigint { + struct Bigint *next; /* Free list link */ + int32 k; /* lg2(maxwds) */ + int32 maxwds; /* Number of words allocated for x */ + int32 sign; /* Zero if positive, 1 if negative. Ignored by most Bigint routines! */ + int32 wds; /* Actual number of words. If value is nonzero, the most significant word must be nonzero. */ + ULong x[1]; /* wds words of number in little endian order */ +}; + +#ifdef ENABLE_OOM_TESTING +/* Out-of-memory testing. Use a good testcase (over and over) and then use + * these routines to cause a memory failure on every possible Balloc allocation, + * to make sure that all out-of-memory paths can be followed. See bug 14044. + */ + +static int allocationNum; /* which allocation is next? */ +static int desiredFailure; /* which allocation should fail? */ + +/** + * js_BigintTestingReset + * + * Call at the beginning of a test run to set the allocation failure position. + * (Set to 0 to just have the engine count allocations without failing.) + */ +JS_PUBLIC_API(void) +js_BigintTestingReset(int newFailure) +{ + allocationNum = 0; + desiredFailure = newFailure; +} + +/** + * js_BigintTestingWhere + * + * Report the current allocation position. This is really only useful when you + * want to learn how many allocations a test run has. + */ +JS_PUBLIC_API(int) +js_BigintTestingWhere() +{ + return allocationNum; +} + + +/* + * So here's what you do: Set up a fantastic test case that exercises the + * elements of the code you wish. Set the failure point at 0 and run the test, + * then get the allocation position. This number is the number of allocations + * your test makes. Now loop from 1 to that number, setting the failure point + * at each loop count, and run the test over and over, causing failures at each + * step. Any memory failure *should* cause a Out-Of-Memory exception; if it + * doesn't, then there's still an error here. + */ +#endif + +typedef struct Bigint Bigint; + +static Bigint *freelist[Kmax+1]; + +/* + * Allocate a Bigint with 2^k words. + * This is not threadsafe. The caller must use thread locks + */ +static Bigint *Balloc(int32 k) +{ + int32 x; + Bigint *rv; +#ifndef Omit_Private_Memory + uint32 len; +#endif + +#ifdef ENABLE_OOM_TESTING + if (++allocationNum == desiredFailure) { + printf("Forced Failing Allocation number %d\n", allocationNum); + return NULL; + } +#endif + + if ((rv = freelist[k]) != NULL) + freelist[k] = rv->next; + if (rv == NULL) { + x = 1 << k; +#ifdef Omit_Private_Memory + rv = (Bigint *)MALLOC(sizeof(Bigint) + (x-1)*sizeof(ULong)); +#else + len = (sizeof(Bigint) + (x-1)*sizeof(ULong) + sizeof(double) - 1) + /sizeof(double); + if (pmem_next - private_mem + len <= PRIVATE_mem) { + rv = (Bigint*)pmem_next; + pmem_next += len; + } + else + rv = (Bigint*)MALLOC(len*sizeof(double)); +#endif + if (!rv) + return NULL; + rv->k = k; + rv->maxwds = x; + } + rv->sign = rv->wds = 0; + return rv; +} + +static void Bfree(Bigint *v) +{ + if (v) { + v->next = freelist[v->k]; + freelist[v->k] = v; + } +} + +#define Bcopy(x,y) memcpy((char *)&x->sign, (char *)&y->sign, \ + y->wds*sizeof(Long) + 2*sizeof(int32)) + +/* Return b*m + a. Deallocate the old b. Both a and m must be between 0 and + * 65535 inclusive. NOTE: old b is deallocated on memory failure. + */ +static Bigint *multadd(Bigint *b, int32 m, int32 a) +{ + int32 i, wds; +#ifdef ULLong + ULong *x; + ULLong carry, y; +#else + ULong carry, *x, y; + ULong xi, z; +#endif + Bigint *b1; + +#ifdef ENABLE_OOM_TESTING + if (++allocationNum == desiredFailure) { + /* Faux allocation, because I'm not getting all of the failure paths + * without it. + */ + printf("Forced Failing Allocation number %d\n", allocationNum); + Bfree(b); + return NULL; + } +#endif + + wds = b->wds; + x = b->x; + i = 0; + carry = a; + do { +#ifdef ULLong + y = *x * (ULLong)m + carry; + carry = y >> 32; + *x++ = (ULong)(y & 0xffffffffUL); +#else + xi = *x; + y = (xi & 0xffff) * m + carry; + z = (xi >> 16) * m + (y >> 16); + carry = z >> 16; + *x++ = (z << 16) + (y & 0xffff); +#endif + } + while(++i < wds); + if (carry) { + if (wds >= b->maxwds) { + b1 = Balloc(b->k+1); + if (!b1) { + Bfree(b); + return NULL; + } + Bcopy(b1, b); + Bfree(b); + b = b1; + } + b->x[wds++] = (ULong)carry; + b->wds = wds; + } + return b; +} + +static Bigint *s2b(CONST char *s, int32 nd0, int32 nd, ULong y9) +{ + Bigint *b; + int32 i, k; + Long x, y; + + x = (nd + 8) / 9; + for(k = 0, y = 1; x > y; y <<= 1, k++) ; + b = Balloc(k); + if (!b) + return NULL; + b->x[0] = y9; + b->wds = 1; + + i = 9; + if (9 < nd0) { + s += 9; + do { + b = multadd(b, 10, *s++ - '0'); + if (!b) + return NULL; + } while(++i < nd0); + s++; + } + else + s += 10; + for(; i < nd; i++) { + b = multadd(b, 10, *s++ - '0'); + if (!b) + return NULL; + } + return b; +} + + +/* Return the number (0 through 32) of most significant zero bits in x. */ +static int32 hi0bits(register ULong x) +{ + register int32 k = 0; + + if (!(x & 0xffff0000)) { + k = 16; + x <<= 16; + } + if (!(x & 0xff000000)) { + k += 8; + x <<= 8; + } + if (!(x & 0xf0000000)) { + k += 4; + x <<= 4; + } + if (!(x & 0xc0000000)) { + k += 2; + x <<= 2; + } + if (!(x & 0x80000000)) { + k++; + if (!(x & 0x40000000)) + return 32; + } + return k; +} + + +/* Return the number (0 through 32) of least significant zero bits in y. + * Also shift y to the right past these 0 through 32 zeros so that y's + * least significant bit will be set unless y was originally zero. */ +static int32 lo0bits(ULong *y) +{ + register int32 k; + register ULong x = *y; + + if (x & 7) { + if (x & 1) + return 0; + if (x & 2) { + *y = x >> 1; + return 1; + } + *y = x >> 2; + return 2; + } + k = 0; + if (!(x & 0xffff)) { + k = 16; + x >>= 16; + } + if (!(x & 0xff)) { + k += 8; + x >>= 8; + } + if (!(x & 0xf)) { + k += 4; + x >>= 4; + } + if (!(x & 0x3)) { + k += 2; + x >>= 2; + } + if (!(x & 1)) { + k++; + x >>= 1; + if (!x & 1) + return 32; + } + *y = x; + return k; +} + +/* Return a new Bigint with the given integer value, which must be nonnegative. */ +static Bigint *i2b(int32 i) +{ + Bigint *b; + + b = Balloc(1); + if (!b) + return NULL; + b->x[0] = i; + b->wds = 1; + return b; +} + +/* Return a newly allocated product of a and b. */ +static Bigint *mult(CONST Bigint *a, CONST Bigint *b) +{ + CONST Bigint *t; + Bigint *c; + int32 k, wa, wb, wc; + ULong y; + ULong *xc, *xc0, *xce; + CONST ULong *x, *xa, *xae, *xb, *xbe; +#ifdef ULLong + ULLong carry, z; +#else + ULong carry, z; + ULong z2; +#endif + + if (a->wds < b->wds) { + t = a; + a = b; + b = t; + } + k = a->k; + wa = a->wds; + wb = b->wds; + wc = wa + wb; + if (wc > a->maxwds) + k++; + c = Balloc(k); + if (!c) + return NULL; + for(xc = c->x, xce = xc + wc; xc < xce; xc++) + *xc = 0; + xa = a->x; + xae = xa + wa; + xb = b->x; + xbe = xb + wb; + xc0 = c->x; +#ifdef ULLong + for(; xb < xbe; xc0++) { + if ((y = *xb++) != 0) { + x = xa; + xc = xc0; + carry = 0; + do { + z = *x++ * (ULLong)y + *xc + carry; + carry = z >> 32; + *xc++ = (ULong)(z & 0xffffffffUL); + } + while(x < xae); + *xc = (ULong)carry; + } + } +#else + for(; xb < xbe; xb++, xc0++) { + if ((y = *xb & 0xffff) != 0) { + x = xa; + xc = xc0; + carry = 0; + do { + z = (*x & 0xffff) * y + (*xc & 0xffff) + carry; + carry = z >> 16; + z2 = (*x++ >> 16) * y + (*xc >> 16) + carry; + carry = z2 >> 16; + Storeinc(xc, z2, z); + } + while(x < xae); + *xc = carry; + } + if ((y = *xb >> 16) != 0) { + x = xa; + xc = xc0; + carry = 0; + z2 = *xc; + do { + z = (*x & 0xffff) * y + (*xc >> 16) + carry; + carry = z >> 16; + Storeinc(xc, z, z2); + z2 = (*x++ >> 16) * y + (*xc & 0xffff) + carry; + carry = z2 >> 16; + } + while(x < xae); + *xc = z2; + } + } +#endif + for(xc0 = c->x, xc = xc0 + wc; wc > 0 && !*--xc; --wc) ; + c->wds = wc; + return c; +} + +/* + * 'p5s' points to a linked list of Bigints that are powers of 5. + * This list grows on demand, and it can only grow: it won't change + * in any other way. So if we read 'p5s' or the 'next' field of + * some Bigint on the list, and it is not NULL, we know it won't + * change to NULL or some other value. Only when the value of + * 'p5s' or 'next' is NULL do we need to acquire the lock and add + * a new Bigint to the list. + */ + +static Bigint *p5s; + +#ifdef JS_THREADSAFE +static PRLock *p5s_lock; +#endif + +/* Return b * 5^k. Deallocate the old b. k must be nonnegative. */ +/* NOTE: old b is deallocated on memory failure. */ +static Bigint *pow5mult(Bigint *b, int32 k) +{ + Bigint *b1, *p5, *p51; + int32 i; + static CONST int32 p05[3] = { 5, 25, 125 }; + + if ((i = k & 3) != 0) { + b = multadd(b, p05[i-1], 0); + if (!b) + return NULL; + } + + if (!(k >>= 2)) + return b; + if (!(p5 = p5s)) { +#ifdef JS_THREADSAFE + /* + * We take great care to not call i2b() and Bfree() + * while holding the lock. + */ + Bigint *wasted_effort = NULL; + p5 = i2b(625); + if (!p5) { + Bfree(b); + return NULL; + } + /* lock and check again */ + PR_Lock(p5s_lock); + if (!p5s) { + /* first time */ + p5s = p5; + p5->next = 0; + } else { + /* some other thread just beat us */ + wasted_effort = p5; + p5 = p5s; + } + PR_Unlock(p5s_lock); + if (wasted_effort) { + Bfree(wasted_effort); + } +#else + /* first time */ + p5 = p5s = i2b(625); + if (!p5) { + Bfree(b); + return NULL; + } + p5->next = 0; +#endif + } + for(;;) { + if (k & 1) { + b1 = mult(b, p5); + Bfree(b); + if (!b1) + return NULL; + b = b1; + } + if (!(k >>= 1)) + break; + if (!(p51 = p5->next)) { +#ifdef JS_THREADSAFE + Bigint *wasted_effort = NULL; + p51 = mult(p5, p5); + if (!p51) { + Bfree(b); + return NULL; + } + PR_Lock(p5s_lock); + if (!p5->next) { + p5->next = p51; + p51->next = 0; + } else { + wasted_effort = p51; + p51 = p5->next; + } + PR_Unlock(p5s_lock); + if (wasted_effort) { + Bfree(wasted_effort); + } +#else + p51 = mult(p5,p5); + if (!p51) { + Bfree(b); + return NULL; + } + p51->next = 0; + p5->next = p51; +#endif + } + p5 = p51; + } + return b; +} + +/* Return b * 2^k. Deallocate the old b. k must be nonnegative. + * NOTE: on memory failure, old b is deallocated. */ +static Bigint *lshift(Bigint *b, int32 k) +{ + int32 i, k1, n, n1; + Bigint *b1; + ULong *x, *x1, *xe, z; + + n = k >> 5; + k1 = b->k; + n1 = n + b->wds + 1; + for(i = b->maxwds; n1 > i; i <<= 1) + k1++; + b1 = Balloc(k1); + if (!b1) + goto done; + x1 = b1->x; + for(i = 0; i < n; i++) + *x1++ = 0; + x = b->x; + xe = x + b->wds; + if (k &= 0x1f) { + k1 = 32 - k; + z = 0; + do { + *x1++ = *x << k | z; + z = *x++ >> k1; + } + while(x < xe); + if ((*x1 = z) != 0) + ++n1; + } + else do + *x1++ = *x++; + while(x < xe); + b1->wds = n1 - 1; +done: + Bfree(b); + return b1; +} + +/* Return -1, 0, or 1 depending on whether ab, respectively. */ +static int32 cmp(Bigint *a, Bigint *b) +{ + ULong *xa, *xa0, *xb, *xb0; + int32 i, j; + + i = a->wds; + j = b->wds; +#ifdef DEBUG + if (i > 1 && !a->x[i-1]) + Bug("cmp called with a->x[a->wds-1] == 0"); + if (j > 1 && !b->x[j-1]) + Bug("cmp called with b->x[b->wds-1] == 0"); +#endif + if (i -= j) + return i; + xa0 = a->x; + xa = xa0 + j; + xb0 = b->x; + xb = xb0 + j; + for(;;) { + if (*--xa != *--xb) + return *xa < *xb ? -1 : 1; + if (xa <= xa0) + break; + } + return 0; +} + +static Bigint *diff(Bigint *a, Bigint *b) +{ + Bigint *c; + int32 i, wa, wb; + ULong *xa, *xae, *xb, *xbe, *xc; +#ifdef ULLong + ULLong borrow, y; +#else + ULong borrow, y; + ULong z; +#endif + + i = cmp(a,b); + if (!i) { + c = Balloc(0); + if (!c) + return NULL; + c->wds = 1; + c->x[0] = 0; + return c; + } + if (i < 0) { + c = a; + a = b; + b = c; + i = 1; + } + else + i = 0; + c = Balloc(a->k); + if (!c) + return NULL; + c->sign = i; + wa = a->wds; + xa = a->x; + xae = xa + wa; + wb = b->wds; + xb = b->x; + xbe = xb + wb; + xc = c->x; + borrow = 0; +#ifdef ULLong + do { + y = (ULLong)*xa++ - *xb++ - borrow; + borrow = y >> 32 & 1UL; + *xc++ = (ULong)(y & 0xffffffffUL); + } + while(xb < xbe); + while(xa < xae) { + y = *xa++ - borrow; + borrow = y >> 32 & 1UL; + *xc++ = (ULong)(y & 0xffffffffUL); + } +#else + do { + y = (*xa & 0xffff) - (*xb & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + z = (*xa++ >> 16) - (*xb++ >> 16) - borrow; + borrow = (z & 0x10000) >> 16; + Storeinc(xc, z, y); + } + while(xb < xbe); + while(xa < xae) { + y = (*xa & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + z = (*xa++ >> 16) - borrow; + borrow = (z & 0x10000) >> 16; + Storeinc(xc, z, y); + } +#endif + while(!*--xc) + wa--; + c->wds = wa; + return c; +} + +/* Return the absolute difference between x and the adjacent greater-magnitude double number (ignoring exponent overflows). */ +static double ulp(double x) +{ + register Long L; + double a; + + L = (word0(x) & Exp_mask) - (P-1)*Exp_msk1; +#ifndef Sudden_Underflow + if (L > 0) { +#endif + set_word0(a, L); + set_word1(a, 0); +#ifndef Sudden_Underflow + } + else { + L = -L >> Exp_shift; + if (L < Exp_shift) { + set_word0(a, 0x80000 >> L); + set_word1(a, 0); + } + else { + set_word0(a, 0); + L -= Exp_shift; + set_word1(a, L >= 31 ? 1 : 1 << (31 - L)); + } + } +#endif + return a; +} + + +static double b2d(Bigint *a, int32 *e) +{ + ULong *xa, *xa0, w, y, z; + int32 k; + double d; +#define d0 word0(d) +#define d1 word1(d) +#define set_d0(x) set_word0(d, x) +#define set_d1(x) set_word1(d, x) + + xa0 = a->x; + xa = xa0 + a->wds; + y = *--xa; +#ifdef DEBUG + if (!y) Bug("zero y in b2d"); +#endif + k = hi0bits(y); + *e = 32 - k; + if (k < Ebits) { + set_d0(Exp_1 | y >> (Ebits - k)); + w = xa > xa0 ? *--xa : 0; + set_d1(y << (32-Ebits + k) | w >> (Ebits - k)); + goto ret_d; + } + z = xa > xa0 ? *--xa : 0; + if (k -= Ebits) { + set_d0(Exp_1 | y << k | z >> (32 - k)); + y = xa > xa0 ? *--xa : 0; + set_d1(z << k | y >> (32 - k)); + } + else { + set_d0(Exp_1 | y); + set_d1(z); + } + ret_d: +#undef d0 +#undef d1 +#undef set_d0 +#undef set_d1 + return d; +} + + +/* Convert d into the form b*2^e, where b is an odd integer. b is the returned + * Bigint and e is the returned binary exponent. Return the number of significant + * bits in b in bits. d must be finite and nonzero. */ +static Bigint *d2b(double d, int32 *e, int32 *bits) +{ + Bigint *b; + int32 de, i, k; + ULong *x, y, z; +#define d0 word0(d) +#define d1 word1(d) +#define set_d0(x) set_word0(d, x) +#define set_d1(x) set_word1(d, x) + + b = Balloc(1); + if (!b) + return NULL; + x = b->x; + + z = d0 & Frac_mask; + set_d0(d0 & 0x7fffffff); /* clear sign bit, which we ignore */ +#ifdef Sudden_Underflow + de = (int32)(d0 >> Exp_shift); + z |= Exp_msk11; +#else + if ((de = (int32)(d0 >> Exp_shift)) != 0) + z |= Exp_msk1; +#endif + if ((y = d1) != 0) { + if ((k = lo0bits(&y)) != 0) { + x[0] = y | z << (32 - k); + z >>= k; + } + else + x[0] = y; + i = b->wds = (x[1] = z) ? 2 : 1; + } + else { + JS_ASSERT(z); + k = lo0bits(&z); + x[0] = z; + i = b->wds = 1; + k += 32; + } +#ifndef Sudden_Underflow + if (de) { +#endif + *e = de - Bias - (P-1) + k; + *bits = P - k; +#ifndef Sudden_Underflow + } + else { + *e = de - Bias - (P-1) + 1 + k; + *bits = 32*i - hi0bits(x[i-1]); + } +#endif + return b; +} +#undef d0 +#undef d1 +#undef set_d0 +#undef set_d1 + + +static double ratio(Bigint *a, Bigint *b) +{ + double da, db; + int32 k, ka, kb; + + da = b2d(a, &ka); + db = b2d(b, &kb); + k = ka - kb + 32*(a->wds - b->wds); + if (k > 0) + set_word0(da, word0(da) + k*Exp_msk1); + else { + k = -k; + set_word0(db, word0(db) + k*Exp_msk1); + } + return da / db; +} + +static CONST double +tens[] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, + 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, + 1e20, 1e21, 1e22 +}; + +static CONST double bigtens[] = { 1e16, 1e32, 1e64, 1e128, 1e256 }; +static CONST double tinytens[] = { 1e-16, 1e-32, 1e-64, 1e-128, +#ifdef Avoid_Underflow + 9007199254740992.e-256 +#else + 1e-256 +#endif + }; +/* The factor of 2^53 in tinytens[4] helps us avoid setting the underflow */ +/* flag unnecessarily. It leads to a song and dance at the end of strtod. */ +#define Scale_Bit 0x10 +#define n_bigtens 5 + + +#ifdef INFNAN_CHECK + +#ifndef NAN_WORD0 +#define NAN_WORD0 0x7ff80000 +#endif + +#ifndef NAN_WORD1 +#define NAN_WORD1 0 +#endif + +static int match(CONST char **sp, char *t) +{ + int c, d; + CONST char *s = *sp; + + while(d = *t++) { + if ((c = *++s) >= 'A' && c <= 'Z') + c += 'a' - 'A'; + if (c != d) + return 0; + } + *sp = s + 1; + return 1; + } +#endif /* INFNAN_CHECK */ + + +#ifdef JS_THREADSAFE +static JSBool initialized = JS_FALSE; + +/* hacked replica of nspr _PR_InitDtoa */ +static void InitDtoa(void) +{ + freelist_lock = PR_NewLock(); + p5s_lock = PR_NewLock(); + initialized = JS_TRUE; +} +#endif + +void js_FinishDtoa(void) +{ + int count; + Bigint *temp; + +#ifdef JS_THREADSAFE + if (initialized == JS_TRUE) { + PR_DestroyLock(freelist_lock); + PR_DestroyLock(p5s_lock); + initialized = JS_FALSE; + } +#endif + + /* clear down the freelist array and p5s */ + + /* static Bigint *freelist[Kmax+1]; */ + for (count = 0; count <= Kmax; count++) { + Bigint **listp = &freelist[count]; + while ((temp = *listp) != NULL) { + *listp = temp->next; + free(temp); + } + freelist[count] = NULL; + } + + /* static Bigint *p5s; */ + while (p5s) { + temp = p5s; + p5s = p5s->next; + free(temp); + } +} + +/* nspr2 watcom bug ifdef omitted */ + +JS_FRIEND_API(double) +JS_strtod(CONST char *s00, char **se, int *err) +{ + int32 scale; + int32 bb2, bb5, bbe, bd2, bd5, bbbits, bs2, c, dsign, + e, e1, esign, i, j, k, nd, nd0, nf, nz, nz0, sign; + CONST char *s, *s0, *s1; + double aadj, aadj1, adj, rv, rv0; + Long L; + ULong y, z; + Bigint *bb, *bb1, *bd, *bd0, *bs, *delta; + + SET_FPU(); + + *err = 0; + + bb = bd = bs = delta = NULL; + sign = nz0 = nz = 0; + rv = 0.; + + /* Locking for Balloc's shared buffers that will be used in this block */ + ACQUIRE_DTOA_LOCK(); + + for(s = s00;;s++) switch(*s) { + case '-': + sign = 1; + /* no break */ + case '+': + if (*++s) + goto break2; + /* no break */ + case 0: + s = s00; + goto ret; + case '\t': + case '\n': + case '\v': + case '\f': + case '\r': + case ' ': + continue; + default: + goto break2; + } +break2: + + if (*s == '0') { + nz0 = 1; + while(*++s == '0') ; + if (!*s) + goto ret; + } + s0 = s; + y = z = 0; + for(nd = nf = 0; (c = *s) >= '0' && c <= '9'; nd++, s++) + if (nd < 9) + y = 10*y + c - '0'; + else if (nd < 16) + z = 10*z + c - '0'; + nd0 = nd; + if (c == '.') { + c = *++s; + if (!nd) { + for(; c == '0'; c = *++s) + nz++; + if (c > '0' && c <= '9') { + s0 = s; + nf += nz; + nz = 0; + goto have_dig; + } + goto dig_done; + } + for(; c >= '0' && c <= '9'; c = *++s) { + have_dig: + nz++; + if (c -= '0') { + nf += nz; + for(i = 1; i < nz; i++) + if (nd++ < 9) + y *= 10; + else if (nd <= DBL_DIG + 1) + z *= 10; + if (nd++ < 9) + y = 10*y + c; + else if (nd <= DBL_DIG + 1) + z = 10*z + c; + nz = 0; + } + } + } +dig_done: + e = 0; + if (c == 'e' || c == 'E') { + if (!nd && !nz && !nz0) { + s = s00; + goto ret; + } + s00 = s; + esign = 0; + switch(c = *++s) { + case '-': + esign = 1; + case '+': + c = *++s; + } + if (c >= '0' && c <= '9') { + while(c == '0') + c = *++s; + if (c > '0' && c <= '9') { + L = c - '0'; + s1 = s; + while((c = *++s) >= '0' && c <= '9') + L = 10*L + c - '0'; + if (s - s1 > 8 || L > 19999) + /* Avoid confusion from exponents + * so large that e might overflow. + */ + e = 19999; /* safe for 16 bit ints */ + else + e = (int32)L; + if (esign) + e = -e; + } + else + e = 0; + } + else + s = s00; + } + if (!nd) { + if (!nz && !nz0) { +#ifdef INFNAN_CHECK + /* Check for Nan and Infinity */ + switch(c) { + case 'i': + case 'I': + if (match(&s,"nfinity")) { + word0(rv) = 0x7ff00000; + word1(rv) = 0; + goto ret; + } + break; + case 'n': + case 'N': + if (match(&s, "an")) { + word0(rv) = NAN_WORD0; + word1(rv) = NAN_WORD1; + goto ret; + } + } +#endif /* INFNAN_CHECK */ + s = s00; + } + goto ret; + } + e1 = e -= nf; + + /* Now we have nd0 digits, starting at s0, followed by a + * decimal point, followed by nd-nd0 digits. The number we're + * after is the integer represented by those digits times + * 10**e */ + + if (!nd0) + nd0 = nd; + k = nd < DBL_DIG + 1 ? nd : DBL_DIG + 1; + rv = y; + if (k > 9) + rv = tens[k - 9] * rv + z; + bd0 = 0; + if (nd <= DBL_DIG +#ifndef RND_PRODQUOT + && FLT_ROUNDS == 1 +#endif + ) { + if (!e) + goto ret; + if (e > 0) { + if (e <= Ten_pmax) { + /* rv = */ rounded_product(rv, tens[e]); + goto ret; + } + i = DBL_DIG - nd; + if (e <= Ten_pmax + i) { + /* A fancier test would sometimes let us do + * this for larger i values. + */ + e -= i; + rv *= tens[i]; + /* rv = */ rounded_product(rv, tens[e]); + goto ret; + } + } +#ifndef Inaccurate_Divide + else if (e >= -Ten_pmax) { + /* rv = */ rounded_quotient(rv, tens[-e]); + goto ret; + } +#endif + } + e1 += nd - k; + + scale = 0; + + /* Get starting approximation = rv * 10**e1 */ + + if (e1 > 0) { + if ((i = e1 & 15) != 0) + rv *= tens[i]; + if (e1 &= ~15) { + if (e1 > DBL_MAX_10_EXP) { + ovfl: + *err = JS_DTOA_ERANGE; +#ifdef __STDC__ + rv = HUGE_VAL; +#else + /* Can't trust HUGE_VAL */ + word0(rv) = Exp_mask; + word1(rv) = 0; +#endif + if (bd0) + goto retfree; + goto ret; + } + e1 >>= 4; + for(j = 0; e1 > 1; j++, e1 >>= 1) + if (e1 & 1) + rv *= bigtens[j]; + /* The last multiplication could overflow. */ + set_word0(rv, word0(rv) - P*Exp_msk1); + rv *= bigtens[j]; + if ((z = word0(rv) & Exp_mask) > Exp_msk1*(DBL_MAX_EXP+Bias-P)) + goto ovfl; + if (z > Exp_msk1*(DBL_MAX_EXP+Bias-1-P)) { + /* set to largest number */ + /* (Can't trust DBL_MAX) */ + set_word0(rv, Big0); + set_word1(rv, Big1); + } + else + set_word0(rv, word0(rv) + P*Exp_msk1); + } + } + else if (e1 < 0) { + e1 = -e1; + if ((i = e1 & 15) != 0) + rv /= tens[i]; + if (e1 &= ~15) { + e1 >>= 4; + if (e1 >= 1 << n_bigtens) + goto undfl; +#ifdef Avoid_Underflow + if (e1 & Scale_Bit) + scale = P; + for(j = 0; e1 > 0; j++, e1 >>= 1) + if (e1 & 1) + rv *= tinytens[j]; + if (scale && (j = P + 1 - ((word0(rv) & Exp_mask) + >> Exp_shift)) > 0) { + /* scaled rv is denormal; zap j low bits */ + if (j >= 32) { + set_word1(rv, 0); + set_word0(rv, word0(rv) & (0xffffffff << (j-32))); + if (!word0(rv)) + set_word0(rv, 1); + } + else + set_word1(rv, word1(rv) & (0xffffffff << j)); + } +#else + for(j = 0; e1 > 1; j++, e1 >>= 1) + if (e1 & 1) + rv *= tinytens[j]; + /* The last multiplication could underflow. */ + rv0 = rv; + rv *= tinytens[j]; + if (!rv) { + rv = 2.*rv0; + rv *= tinytens[j]; +#endif + if (!rv) { + undfl: + rv = 0.; + *err = JS_DTOA_ERANGE; + if (bd0) + goto retfree; + goto ret; + } +#ifndef Avoid_Underflow + set_word0(rv, Tiny0); + set_word1(rv, Tiny1); + /* The refinement below will clean + * this approximation up. + */ + } +#endif + } + } + + /* Now the hard part -- adjusting rv to the correct value.*/ + + /* Put digits into bd: true value = bd * 10^e */ + + bd0 = s2b(s0, nd0, nd, y); + if (!bd0) + goto nomem; + + for(;;) { + bd = Balloc(bd0->k); + if (!bd) + goto nomem; + Bcopy(bd, bd0); + bb = d2b(rv, &bbe, &bbbits); /* rv = bb * 2^bbe */ + if (!bb) + goto nomem; + bs = i2b(1); + if (!bs) + goto nomem; + + if (e >= 0) { + bb2 = bb5 = 0; + bd2 = bd5 = e; + } + else { + bb2 = bb5 = -e; + bd2 = bd5 = 0; + } + if (bbe >= 0) + bb2 += bbe; + else + bd2 -= bbe; + bs2 = bb2; +#ifdef Sudden_Underflow + j = P + 1 - bbbits; +#else +#ifdef Avoid_Underflow + j = bbe - scale; +#else + j = bbe; +#endif + i = j + bbbits - 1; /* logb(rv) */ + if (i < Emin) /* denormal */ + j += P - Emin; + else + j = P + 1 - bbbits; +#endif + bb2 += j; + bd2 += j; +#ifdef Avoid_Underflow + bd2 += scale; +#endif + i = bb2 < bd2 ? bb2 : bd2; + if (i > bs2) + i = bs2; + if (i > 0) { + bb2 -= i; + bd2 -= i; + bs2 -= i; + } + if (bb5 > 0) { + bs = pow5mult(bs, bb5); + if (!bs) + goto nomem; + bb1 = mult(bs, bb); + if (!bb1) + goto nomem; + Bfree(bb); + bb = bb1; + } + if (bb2 > 0) { + bb = lshift(bb, bb2); + if (!bb) + goto nomem; + } + if (bd5 > 0) { + bd = pow5mult(bd, bd5); + if (!bd) + goto nomem; + } + if (bd2 > 0) { + bd = lshift(bd, bd2); + if (!bd) + goto nomem; + } + if (bs2 > 0) { + bs = lshift(bs, bs2); + if (!bs) + goto nomem; + } + delta = diff(bb, bd); + if (!delta) + goto nomem; + dsign = delta->sign; + delta->sign = 0; + i = cmp(delta, bs); + if (i < 0) { + /* Error is less than half an ulp -- check for + * special case of mantissa a power of two. + */ + if (dsign || word1(rv) || word0(rv) & Bndry_mask +#ifdef Avoid_Underflow + || (word0(rv) & Exp_mask) <= Exp_msk1 + P*Exp_msk1 +#else + || (word0(rv) & Exp_mask) <= Exp_msk1 +#endif + ) { +#ifdef Avoid_Underflow + if (!delta->x[0] && delta->wds == 1) + dsign = 2; +#endif + break; + } + delta = lshift(delta,Log2P); + if (!delta) + goto nomem; + if (cmp(delta, bs) > 0) + goto drop_down; + break; + } + if (i == 0) { + /* exactly half-way between */ + if (dsign) { + if ((word0(rv) & Bndry_mask1) == Bndry_mask1 + && word1(rv) == 0xffffffff) { + /*boundary case -- increment exponent*/ + set_word0(rv, (word0(rv) & Exp_mask) + Exp_msk1); + set_word1(rv, 0); +#ifdef Avoid_Underflow + dsign = 0; +#endif + break; + } + } + else if (!(word0(rv) & Bndry_mask) && !word1(rv)) { +#ifdef Avoid_Underflow + dsign = 2; +#endif + drop_down: + /* boundary case -- decrement exponent */ +#ifdef Sudden_Underflow + L = word0(rv) & Exp_mask; + if (L <= Exp_msk1) + goto undfl; + L -= Exp_msk1; +#else + L = (word0(rv) & Exp_mask) - Exp_msk1; +#endif + set_word0(rv, L | Bndry_mask1); + set_word1(rv, 0xffffffff); + break; + } +#ifndef ROUND_BIASED + if (!(word1(rv) & LSB)) + break; +#endif + if (dsign) + rv += ulp(rv); +#ifndef ROUND_BIASED + else { + rv -= ulp(rv); +#ifndef Sudden_Underflow + if (!rv) + goto undfl; +#endif + } +#ifdef Avoid_Underflow + dsign = 1 - dsign; +#endif +#endif + break; + } + if ((aadj = ratio(delta, bs)) <= 2.) { + if (dsign) + aadj = aadj1 = 1.; + else if (word1(rv) || word0(rv) & Bndry_mask) { +#ifndef Sudden_Underflow + if (word1(rv) == Tiny1 && !word0(rv)) + goto undfl; +#endif + aadj = 1.; + aadj1 = -1.; + } + else { + /* special case -- power of FLT_RADIX to be */ + /* rounded down... */ + + if (aadj < 2./FLT_RADIX) + aadj = 1./FLT_RADIX; + else + aadj *= 0.5; + aadj1 = -aadj; + } + } + else { + aadj *= 0.5; + aadj1 = dsign ? aadj : -aadj; +#ifdef Check_FLT_ROUNDS + switch(FLT_ROUNDS) { + case 2: /* towards +infinity */ + aadj1 -= 0.5; + break; + case 0: /* towards 0 */ + case 3: /* towards -infinity */ + aadj1 += 0.5; + } +#else + if (FLT_ROUNDS == 0) + aadj1 += 0.5; +#endif + } + y = word0(rv) & Exp_mask; + + /* Check for overflow */ + + if (y == Exp_msk1*(DBL_MAX_EXP+Bias-1)) { + rv0 = rv; + set_word0(rv, word0(rv) - P*Exp_msk1); + adj = aadj1 * ulp(rv); + rv += adj; + if ((word0(rv) & Exp_mask) >= + Exp_msk1*(DBL_MAX_EXP+Bias-P)) { + if (word0(rv0) == Big0 && word1(rv0) == Big1) + goto ovfl; + set_word0(rv, Big0); + set_word1(rv, Big1); + goto cont; + } + else + set_word0(rv, word0(rv) + P*Exp_msk1); + } + else { +#ifdef Sudden_Underflow + if ((word0(rv) & Exp_mask) <= P*Exp_msk1) { + rv0 = rv; + set_word0(rv, word0(rv) + P*Exp_msk1); + adj = aadj1 * ulp(rv); + rv += adj; + if ((word0(rv) & Exp_mask) <= P*Exp_msk1) + { + if (word0(rv0) == Tiny0 + && word1(rv0) == Tiny1) + goto undfl; + set_word0(rv, Tiny0); + set_word1(rv, Tiny1); + goto cont; + } + else + set_word0(rv, word0(rv) - P*Exp_msk1); + } + else { + adj = aadj1 * ulp(rv); + rv += adj; + } +#else + /* Compute adj so that the IEEE rounding rules will + * correctly round rv + adj in some half-way cases. + * If rv * ulp(rv) is denormalized (i.e., + * y <= (P-1)*Exp_msk1), we must adjust aadj to avoid + * trouble from bits lost to denormalization; + * example: 1.2e-307 . + */ +#ifdef Avoid_Underflow + if (y <= P*Exp_msk1 && aadj > 1.) +#else + if (y <= (P-1)*Exp_msk1 && aadj > 1.) +#endif + { + aadj1 = (double)(int32)(aadj + 0.5); + if (!dsign) + aadj1 = -aadj1; + } +#ifdef Avoid_Underflow + if (scale && y <= P*Exp_msk1) + set_word0(aadj1, word0(aadj1) + (P+1)*Exp_msk1 - y); +#endif + adj = aadj1 * ulp(rv); + rv += adj; +#endif + } + z = word0(rv) & Exp_mask; +#ifdef Avoid_Underflow + if (!scale) +#endif + if (y == z) { + /* Can we stop now? */ + L = (Long)aadj; + aadj -= L; + /* The tolerances below are conservative. */ + if (dsign || word1(rv) || word0(rv) & Bndry_mask) { + if (aadj < .4999999 || aadj > .5000001) + break; + } + else if (aadj < .4999999/FLT_RADIX) + break; + } + cont: + Bfree(bb); + Bfree(bd); + Bfree(bs); + Bfree(delta); + bb = bd = bs = delta = NULL; + } +#ifdef Avoid_Underflow + if (scale) { + set_word0(rv0, Exp_1 - P*Exp_msk1); + set_word1(rv0, 0); + if ((word0(rv) & Exp_mask) <= P*Exp_msk1 + && word1(rv) & 1 + && dsign != 2) { + if (dsign) { +#ifdef Sudden_Underflow + /* rv will be 0, but this would give the */ + /* right result if only rv *= rv0 worked. */ + set_word0(rv, word0(rv) + P*Exp_msk1); + set_word0(rv0, Exp_1 - 2*P*Exp_msk1); +#endif + rv += ulp(rv); + } + else + set_word1(rv, word1(rv) & ~1); + } + rv *= rv0; + } +#endif /* Avoid_Underflow */ +retfree: + Bfree(bb); + Bfree(bd); + Bfree(bs); + Bfree(bd0); + Bfree(delta); +ret: + RELEASE_DTOA_LOCK(); + if (se) + *se = (char *)s; + rv0 = sign ? -rv : rv; + goto ret1; + +nomem: + Bfree(bb); + Bfree(bd); + Bfree(bs); + Bfree(bd0); + Bfree(delta); + *err = JS_DTOA_ENOMEM; + rv0 = 0; + +ret1: + RESTORE_FPU(); + return rv0; +} + + +/* Return floor(b/2^k) and set b to be the remainder. The returned quotient must be less than 2^32. */ +static uint32 quorem2(Bigint *b, int32 k) +{ + ULong mask; + ULong result; + ULong *bx, *bxe; + int32 w; + int32 n = k >> 5; + k &= 0x1F; + mask = (1<wds - n; + if (w <= 0) + return 0; + JS_ASSERT(w <= 2); + bx = b->x; + bxe = bx + n; + result = *bxe >> k; + *bxe &= mask; + if (w == 2) { + JS_ASSERT(!(bxe[1] & ~mask)); + if (k) + result |= bxe[1] << (32 - k); + } + n++; + while (!*bxe && bxe != bx) { + n--; + bxe--; + } + b->wds = n; + return result; +} + +/* Return floor(b/S) and set b to be the remainder. As added restrictions, b must not have + * more words than S, the most significant word of S must not start with a 1 bit, and the + * returned quotient must be less than 36. */ +static int32 quorem(Bigint *b, Bigint *S) +{ + int32 n; + ULong *bx, *bxe, q, *sx, *sxe; +#ifdef ULLong + ULLong borrow, carry, y, ys; +#else + ULong borrow, carry, y, ys; + ULong si, z, zs; +#endif + + n = S->wds; + JS_ASSERT(b->wds <= n); + if (b->wds < n) + return 0; + sx = S->x; + sxe = sx + --n; + bx = b->x; + bxe = bx + n; + JS_ASSERT(*sxe <= 0x7FFFFFFF); + q = *bxe / (*sxe + 1); /* ensure q <= true quotient */ + JS_ASSERT(q < 36); + if (q) { + borrow = 0; + carry = 0; + do { +#ifdef ULLong + ys = *sx++ * (ULLong)q + carry; + carry = ys >> 32; + y = *bx - (ys & 0xffffffffUL) - borrow; + borrow = y >> 32 & 1UL; + *bx++ = (ULong)(y & 0xffffffffUL); +#else + si = *sx++; + ys = (si & 0xffff) * q + carry; + zs = (si >> 16) * q + (ys >> 16); + carry = zs >> 16; + y = (*bx & 0xffff) - (ys & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + z = (*bx >> 16) - (zs & 0xffff) - borrow; + borrow = (z & 0x10000) >> 16; + Storeinc(bx, z, y); +#endif + } + while(sx <= sxe); + if (!*bxe) { + bx = b->x; + while(--bxe > bx && !*bxe) + --n; + b->wds = n; + } + } + if (cmp(b, S) >= 0) { + q++; + borrow = 0; + carry = 0; + bx = b->x; + sx = S->x; + do { +#ifdef ULLong + ys = *sx++ + carry; + carry = ys >> 32; + y = *bx - (ys & 0xffffffffUL) - borrow; + borrow = y >> 32 & 1UL; + *bx++ = (ULong)(y & 0xffffffffUL); +#else + si = *sx++; + ys = (si & 0xffff) + carry; + zs = (si >> 16) + (ys >> 16); + carry = zs >> 16; + y = (*bx & 0xffff) - (ys & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + z = (*bx >> 16) - (zs & 0xffff) - borrow; + borrow = (z & 0x10000) >> 16; + Storeinc(bx, z, y); +#endif + } while(sx <= sxe); + bx = b->x; + bxe = bx + n; + if (!*bxe) { + while(--bxe > bx && !*bxe) + --n; + b->wds = n; + } + } + return (int32)q; +} + +/* dtoa for IEEE arithmetic (dmg): convert double to ASCII string. + * + * Inspired by "How to Print Floating-Point Numbers Accurately" by + * Guy L. Steele, Jr. and Jon L. White [Proc. ACM SIGPLAN '90, pp. 92-101]. + * + * Modifications: + * 1. Rather than iterating, we use a simple numeric overestimate + * to determine k = floor(log10(d)). We scale relevant + * quantities using O(log2(k)) rather than O(k) multiplications. + * 2. For some modes > 2 (corresponding to ecvt and fcvt), we don't + * try to generate digits strictly left to right. Instead, we + * compute with fewer bits and propagate the carry if necessary + * when rounding the final digit up. This is often faster. + * 3. Under the assumption that input will be rounded nearest, + * mode 0 renders 1e23 as 1e23 rather than 9.999999999999999e22. + * That is, we allow equality in stopping tests when the + * round-nearest rule will give the same floating-point value + * as would satisfaction of the stopping test with strict + * inequality. + * 4. We remove common factors of powers of 2 from relevant + * quantities. + * 5. When converting floating-point integers less than 1e16, + * we use floating-point arithmetic rather than resorting + * to multiple-precision integers. + * 6. When asked to produce fewer than 15 digits, we first try + * to get by with floating-point arithmetic; we resort to + * multiple-precision integer arithmetic only if we cannot + * guarantee that the floating-point calculation has given + * the correctly rounded result. For k requested digits and + * "uniformly" distributed input, the probability is + * something like 10^(k-15) that we must resort to the Long + * calculation. + */ + +/* Always emits at least one digit. */ +/* If biasUp is set, then rounding in modes 2 and 3 will round away from zero + * when the number is exactly halfway between two representable values. For example, + * rounding 2.5 to zero digits after the decimal point will return 3 and not 2. + * 2.49 will still round to 2, and 2.51 will still round to 3. */ +/* bufsize should be at least 20 for modes 0 and 1. For the other modes, + * bufsize should be two greater than the maximum number of output characters expected. */ +static JSBool +js_dtoa(double d, int mode, JSBool biasUp, int ndigits, + int *decpt, int *sign, char **rve, char *buf, size_t bufsize) +{ + /* Arguments ndigits, decpt, sign are similar to those + of ecvt and fcvt; trailing zeros are suppressed from + the returned string. If not null, *rve is set to point + to the end of the return value. If d is +-Infinity or NaN, + then *decpt is set to 9999. + + mode: + 0 ==> shortest string that yields d when read in + and rounded to nearest. + 1 ==> like 0, but with Steele & White stopping rule; + e.g. with IEEE P754 arithmetic , mode 0 gives + 1e23 whereas mode 1 gives 9.999999999999999e22. + 2 ==> max(1,ndigits) significant digits. This gives a + return value similar to that of ecvt, except + that trailing zeros are suppressed. + 3 ==> through ndigits past the decimal point. This + gives a return value similar to that from fcvt, + except that trailing zeros are suppressed, and + ndigits can be negative. + 4-9 should give the same return values as 2-3, i.e., + 4 <= mode <= 9 ==> same return as mode + 2 + (mode & 1). These modes are mainly for + debugging; often they run slower but sometimes + faster than modes 2-3. + 4,5,8,9 ==> left-to-right digit generation. + 6-9 ==> don't try fast floating-point estimate + (if applicable). + + Values of mode other than 0-9 are treated as mode 0. + + Sufficient space is allocated to the return value + to hold the suppressed trailing zeros. + */ + + int32 bbits, b2, b5, be, dig, i, ieps, ilim, ilim0, ilim1, + j, j1, k, k0, k_check, leftright, m2, m5, s2, s5, + spec_case, try_quick; + Long L; +#ifndef Sudden_Underflow + int32 denorm; + ULong x; +#endif + Bigint *b, *b1, *delta, *mlo, *mhi, *S; + double d2, ds, eps; + char *s; + JSBool ok; + + SET_FPU(); + + if (word0(d) & Sign_bit) { + /* set sign for everything, including 0's and NaNs */ + *sign = 1; + set_word0(d, word0(d) & ~Sign_bit); /* clear sign bit */ + } + else + *sign = 0; + + if ((word0(d) & Exp_mask) == Exp_mask) { + /* Infinity or NaN */ + *decpt = 9999; + s = !word1(d) && !(word0(d) & Frac_mask) ? "Infinity" : "NaN"; + if ((s[0] == 'I' && bufsize < 9) || (s[0] == 'N' && bufsize < 4)) { + JS_ASSERT(JS_FALSE); +/* JS_SetError(JS_BUFFER_OVERFLOW_ERROR, 0); */ + ok = JS_FALSE; + goto ret2; + } + strcpy(buf, s); + if (rve) { + *rve = buf[3] ? buf + 8 : buf + 3; + JS_ASSERT(**rve == '\0'); + } + ok = JS_TRUE; + goto ret2; + } + + b = NULL; /* initialize for abort protection */ + S = NULL; + mlo = mhi = NULL; + + if (!d) { + no_digits: + *decpt = 1; + if (bufsize < 2) { + JS_ASSERT(JS_FALSE); +/* JS_SetError(JS_BUFFER_OVERFLOW_ERROR, 0); */ + ok = JS_FALSE; + goto ret2; + } + buf[0] = '0'; buf[1] = '\0'; /* copy "0" to buffer */ + if (rve) + *rve = buf + 1; + /* We might have jumped to "no_digits" from below, so we need + * to be sure to free the potentially allocated Bigints to avoid + * memory leaks. */ + Bfree(b); + Bfree(S); + if (mlo != mhi) + Bfree(mlo); + Bfree(mhi); + ok = JS_TRUE; + goto ret2; + } + + b = d2b(d, &be, &bbits); + if (!b) + goto nomem; +#ifdef Sudden_Underflow + i = (int32)(word0(d) >> Exp_shift1 & (Exp_mask>>Exp_shift1)); +#else + if ((i = (int32)(word0(d) >> Exp_shift1 & (Exp_mask>>Exp_shift1))) != 0) { +#endif + d2 = d; + set_word0(d2, word0(d2) & Frac_mask1); + set_word0(d2, word0(d2) | Exp_11); + + /* log(x) ~=~ log(1.5) + (x-1.5)/1.5 + * log10(x) = log(x) / log(10) + * ~=~ log(1.5)/log(10) + (x-1.5)/(1.5*log(10)) + * log10(d) = (i-Bias)*log(2)/log(10) + log10(d2) + * + * This suggests computing an approximation k to log10(d) by + * + * k = (i - Bias)*0.301029995663981 + * + ( (d2-1.5)*0.289529654602168 + 0.176091259055681 ); + * + * We want k to be too large rather than too small. + * The error in the first-order Taylor series approximation + * is in our favor, so we just round up the constant enough + * to compensate for any error in the multiplication of + * (i - Bias) by 0.301029995663981; since |i - Bias| <= 1077, + * and 1077 * 0.30103 * 2^-52 ~=~ 7.2e-14, + * adding 1e-13 to the constant term more than suffices. + * Hence we adjust the constant term to 0.1760912590558. + * (We could get a more accurate k by invoking log10, + * but this is probably not worthwhile.) + */ + + i -= Bias; +#ifndef Sudden_Underflow + denorm = 0; + } + else { + /* d is denormalized */ + + i = bbits + be + (Bias + (P-1) - 1); + x = i > 32 ? word0(d) << (64 - i) | word1(d) >> (i - 32) : word1(d) << (32 - i); + d2 = x; + set_word0(d2, word0(d2) - 31*Exp_msk1); /* adjust exponent */ + i -= (Bias + (P-1) - 1) + 1; + denorm = 1; + } +#endif + /* At this point d = f*2^i, where 1 <= f < 2. d2 is an approximation of f. */ + ds = (d2-1.5)*0.289529654602168 + 0.1760912590558 + i*0.301029995663981; + k = (int32)ds; + if (ds < 0. && ds != k) + k--; /* want k = floor(ds) */ + k_check = 1; + if (k >= 0 && k <= Ten_pmax) { + if (d < tens[k]) + k--; + k_check = 0; + } + /* At this point floor(log10(d)) <= k <= floor(log10(d))+1. + If k_check is zero, we're guaranteed that k = floor(log10(d)). */ + j = bbits - i - 1; + /* At this point d = b/2^j, where b is an odd integer. */ + if (j >= 0) { + b2 = 0; + s2 = j; + } + else { + b2 = -j; + s2 = 0; + } + if (k >= 0) { + b5 = 0; + s5 = k; + s2 += k; + } + else { + b2 -= k; + b5 = -k; + s5 = 0; + } + /* At this point d/10^k = (b * 2^b2 * 5^b5) / (2^s2 * 5^s5), where b is an odd integer, + b2 >= 0, b5 >= 0, s2 >= 0, and s5 >= 0. */ + if (mode < 0 || mode > 9) + mode = 0; + try_quick = 1; + if (mode > 5) { + mode -= 4; + try_quick = 0; + } + leftright = 1; + ilim = ilim1 = 0; + switch(mode) { + case 0: + case 1: + ilim = ilim1 = -1; + i = 18; + ndigits = 0; + break; + case 2: + leftright = 0; + /* no break */ + case 4: + if (ndigits <= 0) + ndigits = 1; + ilim = ilim1 = i = ndigits; + break; + case 3: + leftright = 0; + /* no break */ + case 5: + i = ndigits + k + 1; + ilim = i; + ilim1 = i - 1; + if (i <= 0) + i = 1; + } + /* ilim is the maximum number of significant digits we want, based on k and ndigits. */ + /* ilim1 is the maximum number of significant digits we want, based on k and ndigits, + when it turns out that k was computed too high by one. */ + + /* Ensure space for at least i+1 characters, including trailing null. */ + if (bufsize <= (size_t)i) { + Bfree(b); + JS_ASSERT(JS_FALSE); + ok = JS_FALSE; + goto ret2; + } + s = buf; + + if (ilim >= 0 && ilim <= Quick_max && try_quick) { + + /* Try to get by with floating-point arithmetic. */ + + i = 0; + d2 = d; + k0 = k; + ilim0 = ilim; + ieps = 2; /* conservative */ + /* Divide d by 10^k, keeping track of the roundoff error and avoiding overflows. */ + if (k > 0) { + ds = tens[k&0xf]; + j = k >> 4; + if (j & Bletch) { + /* prevent overflows */ + j &= Bletch - 1; + d /= bigtens[n_bigtens-1]; + ieps++; + } + for(; j; j >>= 1, i++) + if (j & 1) { + ieps++; + ds *= bigtens[i]; + } + d /= ds; + } + else if ((j1 = -k) != 0) { + d *= tens[j1 & 0xf]; + for(j = j1 >> 4; j; j >>= 1, i++) + if (j & 1) { + ieps++; + d *= bigtens[i]; + } + } + /* Check that k was computed correctly. */ + if (k_check && d < 1. && ilim > 0) { + if (ilim1 <= 0) + goto fast_failed; + ilim = ilim1; + k--; + d *= 10.; + ieps++; + } + /* eps bounds the cumulative error. */ + eps = ieps*d + 7.; + set_word0(eps, word0(eps) - (P-1)*Exp_msk1); + if (ilim == 0) { + S = mhi = 0; + d -= 5.; + if (d > eps) + goto one_digit; + if (d < -eps) + goto no_digits; + goto fast_failed; + } +#ifndef No_leftright + if (leftright) { + /* Use Steele & White method of only + * generating digits needed. + */ + eps = 0.5/tens[ilim-1] - eps; + for(i = 0;;) { + L = (Long)d; + d -= L; + *s++ = '0' + (char)L; + if (d < eps) + goto ret1; + if (1. - d < eps) + goto bump_up; + if (++i >= ilim) + break; + eps *= 10.; + d *= 10.; + } + } + else { +#endif + /* Generate ilim digits, then fix them up. */ + eps *= tens[ilim-1]; + for(i = 1;; i++, d *= 10.) { + L = (Long)d; + d -= L; + *s++ = '0' + (char)L; + if (i == ilim) { + if (d > 0.5 + eps) + goto bump_up; + else if (d < 0.5 - eps) { + while(*--s == '0') ; + s++; + goto ret1; + } + break; + } + } +#ifndef No_leftright + } +#endif + fast_failed: + s = buf; + d = d2; + k = k0; + ilim = ilim0; + } + + /* Do we have a "small" integer? */ + + if (be >= 0 && k <= Int_max) { + /* Yes. */ + ds = tens[k]; + if (ndigits < 0 && ilim <= 0) { + S = mhi = 0; + if (ilim < 0 || d < 5*ds || (!biasUp && d == 5*ds)) + goto no_digits; + goto one_digit; + } + for(i = 1;; i++) { + L = (Long) (d / ds); + d -= L*ds; +#ifdef Check_FLT_ROUNDS + /* If FLT_ROUNDS == 2, L will usually be high by 1 */ + if (d < 0) { + L--; + d += ds; + } +#endif + *s++ = '0' + (char)L; + if (i == ilim) { + d += d; + if ((d > ds) || (d == ds && (L & 1 || biasUp))) { + bump_up: + while(*--s == '9') + if (s == buf) { + k++; + *s = '0'; + break; + } + ++*s++; + } + break; + } + if (!(d *= 10.)) + break; + } + goto ret1; + } + + m2 = b2; + m5 = b5; + if (leftright) { + if (mode < 2) { + i = +#ifndef Sudden_Underflow + denorm ? be + (Bias + (P-1) - 1 + 1) : +#endif + 1 + P - bbits; + /* i is 1 plus the number of trailing zero bits in d's significand. Thus, + (2^m2 * 5^m5) / (2^(s2+i) * 5^s5) = (1/2 lsb of d)/10^k. */ + } + else { + j = ilim - 1; + if (m5 >= j) + m5 -= j; + else { + s5 += j -= m5; + b5 += j; + m5 = 0; + } + if ((i = ilim) < 0) { + m2 -= i; + i = 0; + } + /* (2^m2 * 5^m5) / (2^(s2+i) * 5^s5) = (1/2 * 10^(1-ilim))/10^k. */ + } + b2 += i; + s2 += i; + mhi = i2b(1); + if (!mhi) + goto nomem; + /* (mhi * 2^m2 * 5^m5) / (2^s2 * 5^s5) = one-half of last printed (when mode >= 2) or + input (when mode < 2) significant digit, divided by 10^k. */ + } + /* We still have d/10^k = (b * 2^b2 * 5^b5) / (2^s2 * 5^s5). Reduce common factors in + b2, m2, and s2 without changing the equalities. */ + if (m2 > 0 && s2 > 0) { + i = m2 < s2 ? m2 : s2; + b2 -= i; + m2 -= i; + s2 -= i; + } + + /* Fold b5 into b and m5 into mhi. */ + if (b5 > 0) { + if (leftright) { + if (m5 > 0) { + mhi = pow5mult(mhi, m5); + if (!mhi) + goto nomem; + b1 = mult(mhi, b); + if (!b1) + goto nomem; + Bfree(b); + b = b1; + } + if ((j = b5 - m5) != 0) { + b = pow5mult(b, j); + if (!b) + goto nomem; + } + } + else { + b = pow5mult(b, b5); + if (!b) + goto nomem; + } + } + /* Now we have d/10^k = (b * 2^b2) / (2^s2 * 5^s5) and + (mhi * 2^m2) / (2^s2 * 5^s5) = one-half of last printed or input significant digit, divided by 10^k. */ + + S = i2b(1); + if (!S) + goto nomem; + if (s5 > 0) { + S = pow5mult(S, s5); + if (!S) + goto nomem; + } + /* Now we have d/10^k = (b * 2^b2) / (S * 2^s2) and + (mhi * 2^m2) / (S * 2^s2) = one-half of last printed or input significant digit, divided by 10^k. */ + + /* Check for special case that d is a normalized power of 2. */ + spec_case = 0; + if (mode < 2) { + if (!word1(d) && !(word0(d) & Bndry_mask) +#ifndef Sudden_Underflow + && word0(d) & (Exp_mask & Exp_mask << 1) +#endif + ) { + /* The special case. Here we want to be within a quarter of the last input + significant digit instead of one half of it when the decimal output string's value is less than d. */ + b2 += Log2P; + s2 += Log2P; + spec_case = 1; + } + } + + /* Arrange for convenient computation of quotients: + * shift left if necessary so divisor has 4 leading 0 bits. + * + * Perhaps we should just compute leading 28 bits of S once + * and for all and pass them and a shift to quorem, so it + * can do shifts and ors to compute the numerator for q. + */ + if ((i = ((s5 ? 32 - hi0bits(S->x[S->wds-1]) : 1) + s2) & 0x1f) != 0) + i = 32 - i; + /* i is the number of leading zero bits in the most significant word of S*2^s2. */ + if (i > 4) { + i -= 4; + b2 += i; + m2 += i; + s2 += i; + } + else if (i < 4) { + i += 28; + b2 += i; + m2 += i; + s2 += i; + } + /* Now S*2^s2 has exactly four leading zero bits in its most significant word. */ + if (b2 > 0) { + b = lshift(b, b2); + if (!b) + goto nomem; + } + if (s2 > 0) { + S = lshift(S, s2); + if (!S) + goto nomem; + } + /* Now we have d/10^k = b/S and + (mhi * 2^m2) / S = maximum acceptable error, divided by 10^k. */ + if (k_check) { + if (cmp(b,S) < 0) { + k--; + b = multadd(b, 10, 0); /* we botched the k estimate */ + if (!b) + goto nomem; + if (leftright) { + mhi = multadd(mhi, 10, 0); + if (!mhi) + goto nomem; + } + ilim = ilim1; + } + } + /* At this point 1 <= d/10^k = b/S < 10. */ + + if (ilim <= 0 && mode > 2) { + /* We're doing fixed-mode output and d is less than the minimum nonzero output in this mode. + Output either zero or the minimum nonzero output depending on which is closer to d. */ + if (ilim < 0) + goto no_digits; + S = multadd(S,5,0); + if (!S) + goto nomem; + i = cmp(b,S); + if (i < 0 || (i == 0 && !biasUp)) { + /* Always emit at least one digit. If the number appears to be zero + using the current mode, then emit one '0' digit and set decpt to 1. */ + /*no_digits: + k = -1 - ndigits; + goto ret; */ + goto no_digits; + } + one_digit: + *s++ = '1'; + k++; + goto ret; + } + if (leftright) { + if (m2 > 0) { + mhi = lshift(mhi, m2); + if (!mhi) + goto nomem; + } + + /* Compute mlo -- check for special case + * that d is a normalized power of 2. + */ + + mlo = mhi; + if (spec_case) { + mhi = Balloc(mhi->k); + if (!mhi) + goto nomem; + Bcopy(mhi, mlo); + mhi = lshift(mhi, Log2P); + if (!mhi) + goto nomem; + } + /* mlo/S = maximum acceptable error, divided by 10^k, if the output is less than d. */ + /* mhi/S = maximum acceptable error, divided by 10^k, if the output is greater than d. */ + + for(i = 1;;i++) { + dig = quorem(b,S) + '0'; + /* Do we yet have the shortest decimal string + * that will round to d? + */ + j = cmp(b, mlo); + /* j is b/S compared with mlo/S. */ + delta = diff(S, mhi); + if (!delta) + goto nomem; + j1 = delta->sign ? 1 : cmp(b, delta); + Bfree(delta); + /* j1 is b/S compared with 1 - mhi/S. */ +#ifndef ROUND_BIASED + if (j1 == 0 && !mode && !(word1(d) & 1)) { + if (dig == '9') + goto round_9_up; + if (j > 0) + dig++; + *s++ = (char)dig; + goto ret; + } +#endif + if ((j < 0) || (j == 0 && !mode +#ifndef ROUND_BIASED + && !(word1(d) & 1) +#endif + )) { + if (j1 > 0) { + /* Either dig or dig+1 would work here as the least significant decimal digit. + Use whichever would produce a decimal value closer to d. */ + b = lshift(b, 1); + if (!b) + goto nomem; + j1 = cmp(b, S); + if (((j1 > 0) || (j1 == 0 && (dig & 1 || biasUp))) + && (dig++ == '9')) + goto round_9_up; + } + *s++ = (char)dig; + goto ret; + } + if (j1 > 0) { + if (dig == '9') { /* possible if i == 1 */ + round_9_up: + *s++ = '9'; + goto roundoff; + } + *s++ = dig + 1; + goto ret; + } + *s++ = (char)dig; + if (i == ilim) + break; + b = multadd(b, 10, 0); + if (!b) + goto nomem; + if (mlo == mhi) { + mlo = mhi = multadd(mhi, 10, 0); + if (!mhi) + goto nomem; + } + else { + mlo = multadd(mlo, 10, 0); + if (!mlo) + goto nomem; + mhi = multadd(mhi, 10, 0); + if (!mhi) + goto nomem; + } + } + } + else + for(i = 1;; i++) { + *s++ = (char)(dig = quorem(b,S) + '0'); + if (i >= ilim) + break; + b = multadd(b, 10, 0); + if (!b) + goto nomem; + } + + /* Round off last digit */ + + b = lshift(b, 1); + if (!b) + goto nomem; + j = cmp(b, S); + if ((j > 0) || (j == 0 && (dig & 1 || biasUp))) { + roundoff: + while(*--s == '9') + if (s == buf) { + k++; + *s++ = '1'; + goto ret; + } + ++*s++; + } + else { + /* Strip trailing zeros */ + while(*--s == '0') ; + s++; + } + ret: + Bfree(S); + if (mhi) { + if (mlo && mlo != mhi) + Bfree(mlo); + Bfree(mhi); + } + ret1: + Bfree(b); + JS_ASSERT(s < buf + bufsize); + *s = '\0'; + if (rve) + *rve = s; + *decpt = k + 1; + ok = JS_TRUE; + goto ret2; + +nomem: + Bfree(S); + if (mhi) { + if (mlo && mlo != mhi) + Bfree(mlo); + Bfree(mhi); + } + Bfree(b); + ok = JS_FALSE; + +ret2: + RESTORE_FPU(); + return ok; +} + + +/* Mapping of JSDToStrMode -> js_dtoa mode */ +static const int dtoaModes[] = { + 0, /* DTOSTR_STANDARD */ + 0, /* DTOSTR_STANDARD_EXPONENTIAL, */ + 3, /* DTOSTR_FIXED, */ + 2, /* DTOSTR_EXPONENTIAL, */ + 2}; /* DTOSTR_PRECISION */ + +JS_FRIEND_API(char *) +JS_dtostr(char *buffer, size_t bufferSize, JSDToStrMode mode, int precision, double d) +{ + int decPt; /* Position of decimal point relative to first digit returned by js_dtoa */ + int sign; /* Nonzero if the sign bit was set in d */ + int nDigits; /* Number of significand digits returned by js_dtoa */ + char *numBegin = buffer+2; /* Pointer to the digits returned by js_dtoa; the +2 leaves space for */ + /* the sign and/or decimal point */ + char *numEnd; /* Pointer past the digits returned by js_dtoa */ + JSBool dtoaRet; + + JS_ASSERT(bufferSize >= (size_t)(mode <= DTOSTR_STANDARD_EXPONENTIAL ? DTOSTR_STANDARD_BUFFER_SIZE : + DTOSTR_VARIABLE_BUFFER_SIZE(precision))); + + if (mode == DTOSTR_FIXED && (d >= 1e21 || d <= -1e21)) + mode = DTOSTR_STANDARD; /* Change mode here rather than below because the buffer may not be large enough to hold a large integer. */ + + /* Locking for Balloc's shared buffers */ + ACQUIRE_DTOA_LOCK(); + dtoaRet = js_dtoa(d, dtoaModes[mode], mode >= DTOSTR_FIXED, precision, &decPt, &sign, &numEnd, numBegin, bufferSize-2); + RELEASE_DTOA_LOCK(); + if (!dtoaRet) + return 0; + + nDigits = numEnd - numBegin; + + /* If Infinity, -Infinity, or NaN, return the string regardless of the mode. */ + if (decPt != 9999) { + JSBool exponentialNotation = JS_FALSE; + int minNDigits = 0; /* Minimum number of significand digits required by mode and precision */ + char *p; + char *q; + + switch (mode) { + case DTOSTR_STANDARD: + if (decPt < -5 || decPt > 21) + exponentialNotation = JS_TRUE; + else + minNDigits = decPt; + break; + + case DTOSTR_FIXED: + if (precision >= 0) + minNDigits = decPt + precision; + else + minNDigits = decPt; + break; + + case DTOSTR_EXPONENTIAL: + JS_ASSERT(precision > 0); + minNDigits = precision; + /* Fall through */ + case DTOSTR_STANDARD_EXPONENTIAL: + exponentialNotation = JS_TRUE; + break; + + case DTOSTR_PRECISION: + JS_ASSERT(precision > 0); + minNDigits = precision; + if (decPt < -5 || decPt > precision) + exponentialNotation = JS_TRUE; + break; + } + + /* If the number has fewer than minNDigits, pad it with zeros at the end */ + if (nDigits < minNDigits) { + p = numBegin + minNDigits; + nDigits = minNDigits; + do { + *numEnd++ = '0'; + } while (numEnd != p); + *numEnd = '\0'; + } + + if (exponentialNotation) { + /* Insert a decimal point if more than one significand digit */ + if (nDigits != 1) { + numBegin--; + numBegin[0] = numBegin[1]; + numBegin[1] = '.'; + } + JS_snprintf(numEnd, bufferSize - (numEnd - buffer), "e%+d", decPt-1); + } else if (decPt != nDigits) { + /* Some kind of a fraction in fixed notation */ + JS_ASSERT(decPt <= nDigits); + if (decPt > 0) { + /* dd...dd . dd...dd */ + p = --numBegin; + do { + *p = p[1]; + p++; + } while (--decPt); + *p = '.'; + } else { + /* 0 . 00...00dd...dd */ + p = numEnd; + numEnd += 1 - decPt; + q = numEnd; + JS_ASSERT(numEnd < buffer + bufferSize); + *numEnd = '\0'; + while (p != numBegin) + *--q = *--p; + for (p = numBegin + 1; p != q; p++) + *p = '0'; + *numBegin = '.'; + *--numBegin = '0'; + } + } + } + + /* If negative and neither -0.0 nor NaN, output a leading '-'. */ + if (sign && + !(word0(d) == Sign_bit && word1(d) == 0) && + !((word0(d) & Exp_mask) == Exp_mask && + (word1(d) || (word0(d) & Frac_mask)))) { + *--numBegin = '-'; + } + return numBegin; +} + + +/* Let b = floor(b / divisor), and return the remainder. b must be nonnegative. + * divisor must be between 1 and 65536. + * This function cannot run out of memory. */ +static uint32 +divrem(Bigint *b, uint32 divisor) +{ + int32 n = b->wds; + uint32 remainder = 0; + ULong *bx; + ULong *bp; + + JS_ASSERT(divisor > 0 && divisor <= 65536); + + if (!n) + return 0; /* b is zero */ + bx = b->x; + bp = bx + n; + do { + ULong a = *--bp; + ULong dividend = remainder << 16 | a >> 16; + ULong quotientHi = dividend / divisor; + ULong quotientLo; + + remainder = dividend - quotientHi*divisor; + JS_ASSERT(quotientHi <= 0xFFFF && remainder < divisor); + dividend = remainder << 16 | (a & 0xFFFF); + quotientLo = dividend / divisor; + remainder = dividend - quotientLo*divisor; + JS_ASSERT(quotientLo <= 0xFFFF && remainder < divisor); + *bp = quotientHi << 16 | quotientLo; + } while (bp != bx); + /* Decrease the size of the number if its most significant word is now zero. */ + if (bx[n-1] == 0) + b->wds--; + return remainder; +} + + +/* "-0.0000...(1073 zeros after decimal point)...0001\0" is the longest string that we could produce, + * which occurs when printing -5e-324 in binary. We could compute a better estimate of the size of + * the output string and malloc fewer bytes depending on d and base, but why bother? */ +#define DTOBASESTR_BUFFER_SIZE 1078 +#define BASEDIGIT(digit) ((char)(((digit) >= 10) ? 'a' - 10 + (digit) : '0' + (digit))) + +JS_FRIEND_API(char *) +JS_dtobasestr(int base, double d) +{ + char *buffer; /* The output string */ + char *p; /* Pointer to current position in the buffer */ + char *pInt; /* Pointer to the beginning of the integer part of the string */ + char *q; + uint32 digit; + double di; /* d truncated to an integer */ + double df; /* The fractional part of d */ + + JS_ASSERT(base >= 2 && base <= 36); + + buffer = (char*) malloc(DTOBASESTR_BUFFER_SIZE); + if (buffer) { + p = buffer; + if (d < 0.0 +#if defined(XP_WIN) || defined(XP_OS2) + && !((word0(d) & Exp_mask) == Exp_mask && ((word0(d) & Frac_mask) || word1(d))) /* Visual C++ doesn't know how to compare against NaN */ +#endif + ) { + *p++ = '-'; + d = -d; + } + + /* Check for Infinity and NaN */ + if ((word0(d) & Exp_mask) == Exp_mask) { + strcpy(p, !word1(d) && !(word0(d) & Frac_mask) ? "Infinity" : "NaN"); + return buffer; + } + + /* Locking for Balloc's shared buffers */ + ACQUIRE_DTOA_LOCK(); + + /* Output the integer part of d with the digits in reverse order. */ + pInt = p; + di = fd_floor(d); + if (di <= 4294967295.0) { + uint32 n = (uint32)di; + if (n) + do { + uint32 m = n / base; + digit = n - m*base; + n = m; + JS_ASSERT(digit < (uint32)base); + *p++ = BASEDIGIT(digit); + } while (n); + else *p++ = '0'; + } else { + int32 e; + int32 bits; /* Number of significant bits in di; not used. */ + Bigint *b = d2b(di, &e, &bits); + if (!b) + goto nomem1; + b = lshift(b, e); + if (!b) { + nomem1: + Bfree(b); + return NULL; + } + do { + digit = divrem(b, base); + JS_ASSERT(digit < (uint32)base); + *p++ = BASEDIGIT(digit); + } while (b->wds); + Bfree(b); + } + /* Reverse the digits of the integer part of d. */ + q = p-1; + while (q > pInt) { + char ch = *pInt; + *pInt++ = *q; + *q-- = ch; + } + + df = d - di; + if (df != 0.0) { + /* We have a fraction. */ + int32 e, bbits, s2, done; + Bigint *b, *s, *mlo, *mhi; + + b = s = mlo = mhi = NULL; + + *p++ = '.'; + b = d2b(df, &e, &bbits); + if (!b) { + nomem2: + Bfree(b); + Bfree(s); + if (mlo != mhi) + Bfree(mlo); + Bfree(mhi); + return NULL; + } + JS_ASSERT(e < 0); + /* At this point df = b * 2^e. e must be less than zero because 0 < df < 1. */ + + s2 = -(int32)(word0(d) >> Exp_shift1 & Exp_mask>>Exp_shift1); +#ifndef Sudden_Underflow + if (!s2) + s2 = -1; +#endif + s2 += Bias + P; + /* 1/2^s2 = (nextDouble(d) - d)/2 */ + JS_ASSERT(-s2 < e); + mlo = i2b(1); + if (!mlo) + goto nomem2; + mhi = mlo; + if (!word1(d) && !(word0(d) & Bndry_mask) +#ifndef Sudden_Underflow + && word0(d) & (Exp_mask & Exp_mask << 1) +#endif + ) { + /* The special case. Here we want to be within a quarter of the last input + significant digit instead of one half of it when the output string's value is less than d. */ + s2 += Log2P; + mhi = i2b(1< df = b/2^s2 > 0; + * (d - prevDouble(d))/2 = mlo/2^s2; + * (nextDouble(d) - d)/2 = mhi/2^s2. */ + + done = JS_FALSE; + do { + int32 j, j1; + Bigint *delta; + + b = multadd(b, base, 0); + if (!b) + goto nomem2; + digit = quorem2(b, s2); + if (mlo == mhi) { + mlo = mhi = multadd(mlo, base, 0); + if (!mhi) + goto nomem2; + } + else { + mlo = multadd(mlo, base, 0); + if (!mlo) + goto nomem2; + mhi = multadd(mhi, base, 0); + if (!mhi) + goto nomem2; + } + + /* Do we yet have the shortest string that will round to d? */ + j = cmp(b, mlo); + /* j is b/2^s2 compared with mlo/2^s2. */ + delta = diff(s, mhi); + if (!delta) + goto nomem2; + j1 = delta->sign ? 1 : cmp(b, delta); + Bfree(delta); + /* j1 is b/2^s2 compared with 1 - mhi/2^s2. */ + +#ifndef ROUND_BIASED + if (j1 == 0 && !(word1(d) & 1)) { + if (j > 0) + digit++; + done = JS_TRUE; + } else +#endif + if (j < 0 || (j == 0 +#ifndef ROUND_BIASED + && !(word1(d) & 1) +#endif + )) { + if (j1 > 0) { + /* Either dig or dig+1 would work here as the least significant digit. + Use whichever would produce an output value closer to d. */ + b = lshift(b, 1); + if (!b) + goto nomem2; + j1 = cmp(b, s); + if (j1 > 0) /* The even test (|| (j1 == 0 && (digit & 1))) is not here because it messes up odd base output + * such as 3.5 in base 3. */ + digit++; + } + done = JS_TRUE; + } else if (j1 > 0) { + digit++; + done = JS_TRUE; + } + JS_ASSERT(digit < (uint32)base); + *p++ = BASEDIGIT(digit); + } while (!done); + Bfree(b); + Bfree(s); + if (mlo != mhi) + Bfree(mlo); + Bfree(mhi); + } + JS_ASSERT(p < buffer + DTOBASESTR_BUFFER_SIZE); + *p = '\0'; + RELEASE_DTOA_LOCK(); + } + return buffer; +} diff --git a/src/dom/js/jsdtoa.h b/src/dom/js/jsdtoa.h new file mode 100644 index 000000000..409f45454 --- /dev/null +++ b/src/dom/js/jsdtoa.h @@ -0,0 +1,130 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsdtoa_h___ +#define jsdtoa_h___ +/* + * Public interface to portable double-precision floating point to string + * and back conversion package. + */ + +#include "jscompat.h" + +JS_BEGIN_EXTERN_C + +/* + * JS_strtod() returns as a double-precision floating-point number + * the value represented by the character string pointed to by + * s00. The string is scanned up to the first unrecognized + * character. + * If the value of se is not (char **)NULL, a pointer to + * the character terminating the scan is returned in the location pointed + * to by se. If no number can be formed, se is set to s00r, and + * zero is returned. + * + * *err is set to zero on success; it's set to JS_DTOA_ERANGE on range + * errors and JS_DTOA_ENOMEM on memory failure. + */ +#define JS_DTOA_ERANGE 1 +#define JS_DTOA_ENOMEM 2 +JS_FRIEND_API(double) +JS_strtod(const char *s00, char **se, int *err); + +/* + * Modes for converting floating-point numbers to strings. + * + * Some of the modes can round-trip; this means that if the number is converted to + * a string using one of these mode and then converted back to a number, the result + * will be identical to the original number (except that, due to ECMA, -0 will get converted + * to +0). These round-trip modes return the minimum number of significand digits that + * permit the round trip. + * + * Some of the modes take an integer parameter . + */ +/* NB: Keep this in sync with number_constants[]. */ +typedef enum JSDToStrMode { + DTOSTR_STANDARD, /* Either fixed or exponential format; round-trip */ + DTOSTR_STANDARD_EXPONENTIAL, /* Always exponential format; round-trip */ + DTOSTR_FIXED, /* Round to digits after the decimal point; exponential if number is large */ + DTOSTR_EXPONENTIAL, /* Always exponential format; significant digits */ + DTOSTR_PRECISION /* Either fixed or exponential format; significant digits */ +} JSDToStrMode; + + +/* Maximum number of characters (including trailing null) that a DTOSTR_STANDARD or DTOSTR_STANDARD_EXPONENTIAL + * conversion can produce. This maximum is reached for a number like -0.0000012345678901234567. */ +#define DTOSTR_STANDARD_BUFFER_SIZE 26 + +/* Maximum number of characters (including trailing null) that one of the other conversions + * can produce. This maximum is reached for TO_FIXED, which can generate up to 21 digits before the decimal point. */ +#define DTOSTR_VARIABLE_BUFFER_SIZE(precision) ((precision)+24 > DTOSTR_STANDARD_BUFFER_SIZE ? (precision)+24 : DTOSTR_STANDARD_BUFFER_SIZE) + +/* + * Convert dval according to the given mode and return a pointer to the resulting ASCII string. + * The result is held somewhere in buffer, but not necessarily at the beginning. The size of + * buffer is given in bufferSize, and must be at least as large as given by the above macros. + * + * Return NULL if out of memory. + */ +JS_FRIEND_API(char *) +JS_dtostr(char *buffer, size_t bufferSize, JSDToStrMode mode, int precision, double dval); + +/* + * Convert d to a string in the given base. The integral part of d will be printed exactly + * in that base, regardless of how large it is, because there is no exponential notation for non-base-ten + * numbers. The fractional part will be rounded to as few digits as possible while still preserving + * the round-trip property (analogous to that of printing decimal numbers). In other words, if one were + * to read the resulting string in via a hypothetical base-number-reading routine that rounds to the nearest + * IEEE double (and to an even significand if there are two equally near doubles), then the result would + * equal d (except for -0.0, which converts to "0", and NaN, which is not equal to itself). + * + * Return NULL if out of memory. If the result is not NULL, it must be released via free(). + */ +JS_FRIEND_API(char *) +JS_dtobasestr(int base, double d); + +/* + * Clean up any persistent RAM allocated during the execution of DtoA + * routines, and remove any locks that might have been created. + */ +extern void js_FinishDtoa(void); + +JS_END_EXTERN_C + +#endif /* jsdtoa_h___ */ diff --git a/src/dom/js/jsemit.c b/src/dom/js/jsemit.c new file mode 100644 index 000000000..13e105c07 --- /dev/null +++ b/src/dom/js/jsemit.c @@ -0,0 +1,4471 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS bytecode generation. + */ +#include "jsstddef.h" +#ifdef HAVE_MEMORY_H +#include +#endif +#include +#include "jstypes.h" +#include "jsarena.h" /* Added by JSIFY */ +#include "jsutil.h" /* Added by JSIFY */ +#include "jsbit.h" +#include "jsprf.h" +#include "jsapi.h" +#include "jsatom.h" +#include "jscntxt.h" +#include "jsconfig.h" +#include "jsemit.h" +#include "jsfun.h" +#include "jsnum.h" +#include "jsopcode.h" +#include "jsparse.h" +#include "jsscan.h" +#include "jsscope.h" +#include "jsscript.h" + +/* Allocation chunk counts, must be powers of two in general. */ +#define BYTECODE_CHUNK 256 /* code allocation increment */ +#define SRCNOTE_CHUNK 64 /* initial srcnote allocation increment */ +#define TRYNOTE_CHUNK 64 /* trynote allocation increment */ + +/* Macros to compute byte sizes from typed element counts. */ +#define BYTECODE_SIZE(n) ((n) * sizeof(jsbytecode)) +#define SRCNOTE_SIZE(n) ((n) * sizeof(jssrcnote)) +#define TRYNOTE_SIZE(n) ((n) * sizeof(JSTryNote)) + +JS_FRIEND_API(JSBool) +js_InitCodeGenerator(JSContext *cx, JSCodeGenerator *cg, + JSArenaPool *codePool, JSArenaPool *notePool, + const char *filename, uintN lineno, + JSPrincipals *principals) +{ + memset(cg, 0, sizeof *cg); + TREE_CONTEXT_INIT(&cg->treeContext); + cg->treeContext.flags |= TCF_COMPILING; + cg->codePool = codePool; + cg->notePool = notePool; + cg->codeMark = JS_ARENA_MARK(codePool); + cg->noteMark = JS_ARENA_MARK(notePool); + cg->tempMark = JS_ARENA_MARK(&cx->tempPool); + cg->current = &cg->main; + cg->filename = filename; + cg->firstLine = cg->prolog.currentLine = cg->main.currentLine = lineno; + cg->principals = principals; + ATOM_LIST_INIT(&cg->atomList); + cg->prolog.noteMask = cg->main.noteMask = SRCNOTE_CHUNK - 1; + return JS_TRUE; +} + +JS_FRIEND_API(void) +js_FinishCodeGenerator(JSContext *cx, JSCodeGenerator *cg) +{ + TREE_CONTEXT_FINISH(&cg->treeContext); + JS_ARENA_RELEASE(cg->codePool, cg->codeMark); + JS_ARENA_RELEASE(cg->notePool, cg->noteMark); + JS_ARENA_RELEASE(&cx->tempPool, cg->tempMark); +} + +static ptrdiff_t +EmitCheck(JSContext *cx, JSCodeGenerator *cg, JSOp op, ptrdiff_t delta) +{ + jsbytecode *base, *limit, *next; + ptrdiff_t offset, length; + size_t incr, size; + + base = CG_BASE(cg); + next = CG_NEXT(cg); + limit = CG_LIMIT(cg); + offset = PTRDIFF(next, base, jsbytecode); + if (next + delta > limit) { + length = offset + delta; + length = (length <= BYTECODE_CHUNK) + ? BYTECODE_CHUNK + : JS_BIT(JS_CeilingLog2(length)); + incr = BYTECODE_SIZE(length); + if (!base) { + JS_ARENA_ALLOCATE_CAST(base, jsbytecode *, cg->codePool, incr); + } else { + size = BYTECODE_SIZE(PTRDIFF(limit, base, jsbytecode)); + incr -= size; + JS_ARENA_GROW_CAST(base, jsbytecode *, cg->codePool, size, incr); + } + if (!base) { + JS_ReportOutOfMemory(cx); + return -1; + } + CG_BASE(cg) = base; + CG_LIMIT(cg) = base + length; + CG_NEXT(cg) = base + offset; + } + return offset; +} + +static void +UpdateDepth(JSContext *cx, JSCodeGenerator *cg, ptrdiff_t target) +{ + jsbytecode *pc; + const JSCodeSpec *cs; + intN nuses; + + pc = CG_CODE(cg, target); + cs = &js_CodeSpec[pc[0]]; + nuses = cs->nuses; + if (nuses < 0) + nuses = 2 + GET_ARGC(pc); /* stack: fun, this, [argc arguments] */ + cg->stackDepth -= nuses; + if (cg->stackDepth < 0) { + char numBuf[12]; + JS_snprintf(numBuf, sizeof numBuf, "%d", target); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_STACK_UNDERFLOW, + cg->filename ? cg->filename : "stdin", numBuf); + } + cg->stackDepth += cs->ndefs; + if ((uintN)cg->stackDepth > cg->maxStackDepth) + cg->maxStackDepth = cg->stackDepth; +} + +ptrdiff_t +js_Emit1(JSContext *cx, JSCodeGenerator *cg, JSOp op) +{ + ptrdiff_t offset = EmitCheck(cx, cg, op, 1); + + if (offset >= 0) { + *CG_NEXT(cg)++ = (jsbytecode)op; + UpdateDepth(cx, cg, offset); + } + return offset; +} + +ptrdiff_t +js_Emit2(JSContext *cx, JSCodeGenerator *cg, JSOp op, jsbytecode op1) +{ + ptrdiff_t offset = EmitCheck(cx, cg, op, 2); + + if (offset >= 0) { + jsbytecode *next = CG_NEXT(cg); + next[0] = (jsbytecode)op; + next[1] = op1; + CG_NEXT(cg) = next + 2; + UpdateDepth(cx, cg, offset); + } + return offset; +} + +ptrdiff_t +js_Emit3(JSContext *cx, JSCodeGenerator *cg, JSOp op, jsbytecode op1, + jsbytecode op2) +{ + ptrdiff_t offset = EmitCheck(cx, cg, op, 3); + + if (offset >= 0) { + jsbytecode *next = CG_NEXT(cg); + next[0] = (jsbytecode)op; + next[1] = op1; + next[2] = op2; + CG_NEXT(cg) = next + 3; + UpdateDepth(cx, cg, offset); + } + return offset; +} + +ptrdiff_t +js_EmitN(JSContext *cx, JSCodeGenerator *cg, JSOp op, size_t extra) +{ + ptrdiff_t length = 1 + (ptrdiff_t)extra; + ptrdiff_t offset = EmitCheck(cx, cg, op, length); + + if (offset >= 0) { + jsbytecode *next = CG_NEXT(cg); + *next = (jsbytecode)op; + memset(next + 1, 0, BYTECODE_SIZE(extra)); + CG_NEXT(cg) = next + length; + UpdateDepth(cx, cg, offset); + } + return offset; +} + +/* XXX too many "... statement" L10N gaffes below -- fix via js.msg! */ +const char js_with_statement_str[] = "with statement"; + +static const char *statementName[] = { + "block", /* BLOCK */ + "label statement", /* LABEL */ + "if statement", /* IF */ + "else statement", /* ELSE */ + "switch statement", /* SWITCH */ + js_with_statement_str, /* WITH */ + "try statement", /* TRY */ + "catch block", /* CATCH */ + "finally statement", /* FINALLY */ + "do loop", /* DO_LOOP */ + "for loop", /* FOR_LOOP */ + "for/in loop", /* FOR_IN_LOOP */ + "while loop", /* WHILE_LOOP */ +}; + +static const char * +StatementName(JSCodeGenerator *cg) +{ + if (!cg->treeContext.topStmt) + return "script"; + return statementName[cg->treeContext.topStmt->type]; +} + +static void +ReportStatementTooLarge(JSContext *cx, JSCodeGenerator *cg) +{ + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NEED_DIET, + StatementName(cg)); +} + +/** + Span-dependent instructions in JS bytecode consist of the jump (JOF_JUMP) + and switch (JOF_LOOKUPSWITCH, JOF_TABLESWITCH) format opcodes, subdivided + into unconditional (gotos and gosubs), and conditional jumps or branches + (which pop a value, test it, and jump depending on its value). Most jumps + have just one immediate operand, a signed offset from the jump opcode's pc + to the target bytecode. The lookup and table switch opcodes may contain + many jump offsets. + + Mozilla bug #80981 (http://bugzilla.mozilla.org/show_bug.cgi?id=80981) was + fixed by adding extended "X" counterparts to the opcodes/formats (NB: X is + suffixed to prefer JSOP_ORX thereby avoiding a JSOP_XOR name collision for + the extended form of the JSOP_OR branch opcode). The unextended or short + formats have 16-bit signed immediate offset operands, the extended or long + formats have 32-bit signed immediates. The span-dependency problem consists + of selecting as few long instructions as possible, or about as few -- since + jumps can span other jumps, extending one jump may cause another to need to + be extended. + + Most JS scripts are short, so need no extended jumps. We optimize for this + case by generating short jumps until we know a long jump is needed. After + that point, we keep generating short jumps, but each jump's 16-bit immediate + offset operand is actually an unsigned index into cg->spanDeps, an array of + JSSpanDep structs. Each struct tells the top offset in the script of the + opcode, the "before" offset of the jump (which will be the same as top for + simplex jumps, but which will index further into the bytecode array for a + non-initial jump offset in a lookup or table switch), the after "offset" + adjusted during span-dependent instruction selection (initially the same + value as the "before" offset), and the jump target (more below). + + Since we generate cg->spanDeps lazily, from within js_SetJumpOffset, we must + ensure that all bytecode generated so far can be inspected to discover where + the jump offset immediate operands lie within CG_CODE(cg). But the bonus is + that we generate span-dependency records sorted by their offsets, so we can + binary-search when trying to find a JSSpanDep for a given bytecode offset, + or the nearest JSSpanDep at or above a given pc. + + To avoid limiting scripts to 64K jumps, if the cg->spanDeps index overflows + 65534, we store SPANDEP_INDEX_HUGE in the jump's immediate operand. This + tells us that we need to binary-search for the cg->spanDeps entry by the + jump opcode's bytecode offset (sd->before). + + Jump targets need to be maintained in a data structure that lets us look + up an already-known target by its address (jumps may have a common target), + and that also lets us update the addresses (script-relative, a.k.a. absolute + offsets) of targets that come after a jump target (for when a jump below + that target needs to be extended). We use an AVL tree, implemented using + recursion, but with some tricky optimizations to its height-balancing code + (see http://www.enteract.com/~bradapp/ftp/src/libs/C++/AvlTrees.html). + + A final wrinkle: backpatch chains are linked by jump-to-jump offsets with + positive sign, even though they link "backward" (i.e., toward lower bytecode + address). We don't want to waste space and search time in the AVL tree for + such temporary backpatch deltas, so we use a single-bit wildcard scheme to + tag true JSJumpTarget pointers and encode untagged, signed (positive) deltas + in JSSpanDep.target pointers, depending on whether the JSSpanDep has a known + target, or is still awaiting backpatching. + + Note that backpatch chains would present a problem for BuildSpanDepTable, + which inspects bytecode to build cg->spanDeps on demand, when the first + short jump offset overflows. To solve this temporary problem, we emit a + proxy bytecode (JSOP_BACKPATCH; JSOP_BACKPATCH_PUSH for jumps that push a + result on the interpreter's stack, namely JSOP_GOSUB; or JSOP_BACKPATCH_POP + for branch ops) whose nuses/ndefs counts help keep the stack balanced, but + whose opcode format distinguishes its backpatch delta immediate operand from + a normal jump offset. + */ +static int +BalanceJumpTargets(JSJumpTarget **jtp) +{ + JSJumpTarget *jt, *jt2, *root; + int dir, otherDir, heightChanged; + JSBool doubleRotate; + + jt = *jtp; + JS_ASSERT(jt->balance != 0); + + if (jt->balance < -1) { + dir = JT_RIGHT; + doubleRotate = (jt->kids[JT_LEFT]->balance > 0); + } else if (jt->balance > 1) { + dir = JT_LEFT; + doubleRotate = (jt->kids[JT_RIGHT]->balance < 0); + } else { + return 0; + } + + otherDir = JT_OTHER_DIR(dir); + if (doubleRotate) { + jt2 = jt->kids[otherDir]; + *jtp = root = jt2->kids[dir]; + + jt->kids[otherDir] = root->kids[dir]; + root->kids[dir] = jt; + + jt2->kids[dir] = root->kids[otherDir]; + root->kids[otherDir] = jt2; + + heightChanged = 1; + root->kids[JT_LEFT]->balance = -JS_MAX(root->balance, 0); + root->kids[JT_RIGHT]->balance = -JS_MIN(root->balance, 0); + root->balance = 0; + } else { + *jtp = root = jt->kids[otherDir]; + jt->kids[otherDir] = root->kids[dir]; + root->kids[dir] = jt; + + heightChanged = (root->balance != 0); + jt->balance = -((dir == JT_LEFT) ? --root->balance : ++root->balance); + } + + return heightChanged; +} + +typedef struct AddJumpTargetArgs { + JSContext *cx; + JSCodeGenerator *cg; + ptrdiff_t offset; + JSJumpTarget *node; +} AddJumpTargetArgs; + +static int +AddJumpTarget(AddJumpTargetArgs *args, JSJumpTarget **jtp) +{ + JSJumpTarget *jt; + int balanceDelta; + + jt = *jtp; + if (!jt) { + JSCodeGenerator *cg = args->cg; + + jt = cg->jtFreeList; + if (jt) { + cg->jtFreeList = jt->kids[JT_LEFT]; + } else { + JS_ARENA_ALLOCATE_CAST(jt, JSJumpTarget *, &args->cx->tempPool, + sizeof *jt); + if (!jt) { + JS_ReportOutOfMemory(args->cx); + return 0; + } + } + jt->offset = args->offset; + jt->balance = 0; + jt->kids[JT_LEFT] = jt->kids[JT_RIGHT] = NULL; + cg->numJumpTargets++; + args->node = jt; + *jtp = jt; + return 1; + } + + if (jt->offset == args->offset) { + args->node = jt; + return 0; + } + + if (args->offset < jt->offset) + balanceDelta = -AddJumpTarget(args, &jt->kids[JT_LEFT]); + else + balanceDelta = AddJumpTarget(args, &jt->kids[JT_RIGHT]); + if (!args->node) + return 0; + + jt->balance += balanceDelta; + return (balanceDelta && jt->balance) + ? 1 - BalanceJumpTargets(jtp) + : 0; +} + +#ifdef DEBUG_brendan +static int AVLCheck(JSJumpTarget *jt) +{ + int lh, rh; + + if (!jt) return 0; + JS_ASSERT(-1 <= jt->balance && jt->balance <= 1); + lh = AVLCheck(jt->kids[JT_LEFT]); + rh = AVLCheck(jt->kids[JT_RIGHT]); + JS_ASSERT(jt->balance == rh - lh); + return 1 + JS_MAX(lh, rh); +} +#endif + +static JSBool +SetSpanDepTarget(JSContext *cx, JSCodeGenerator *cg, JSSpanDep *sd, + ptrdiff_t off) +{ + AddJumpTargetArgs args; + + if (off < JUMPX_OFFSET_MIN || JUMPX_OFFSET_MAX < off) { + ReportStatementTooLarge(cx, cg); + return JS_FALSE; + } + + args.cx = cx; + args.cg = cg; + args.offset = sd->top + off; + args.node = NULL; + AddJumpTarget(&args, &cg->jumpTargets); + if (!args.node) + return JS_FALSE; + +#ifdef DEBUG_brendan + AVLCheck(cg->jumpTargets); +#endif + + SD_SET_TARGET(sd, args.node); + return JS_TRUE; +} + +#define SPANDEPS_MIN 256 +#define SPANDEPS_SIZE(n) ((n) * sizeof(JSSpanDep)) +#define SPANDEPS_SIZE_MIN SPANDEPS_SIZE(SPANDEPS_MIN) + +static JSBool +AddSpanDep(JSContext *cx, JSCodeGenerator *cg, jsbytecode *pc, jsbytecode *pc2, + ptrdiff_t off) +{ + uintN index; + JSSpanDep *sdbase, *sd; + size_t size; + + index = cg->numSpanDeps; + if (index + 1 == 0) { + ReportStatementTooLarge(cx, cg); + return JS_FALSE; + } + + if ((index & (index - 1)) == 0 && + (!(sdbase = cg->spanDeps) || index >= SPANDEPS_MIN)) { + if (!sdbase) { + size = SPANDEPS_SIZE_MIN; + JS_ARENA_ALLOCATE_CAST(sdbase, JSSpanDep *, &cx->tempPool, size); + } else { + size = SPANDEPS_SIZE(index); + JS_ARENA_GROW_CAST(sdbase, JSSpanDep *, &cx->tempPool, size, size); + } + if (!sdbase) + return JS_FALSE; + cg->spanDeps = sdbase; + } + + cg->numSpanDeps = index + 1; + sd = cg->spanDeps + index; + sd->top = PTRDIFF(pc, CG_BASE(cg), jsbytecode); + sd->offset = sd->before = PTRDIFF(pc2, CG_BASE(cg), jsbytecode); + + if (js_CodeSpec[*pc].format & JOF_BACKPATCH) { + /* Jump offset will be backpatched if off is a non-zero "bpdelta". */ + if (off != 0) { + JS_ASSERT(off >= 1 + JUMP_OFFSET_LEN); + if (off > BPDELTA_MAX) { + ReportStatementTooLarge(cx, cg); + return JS_FALSE; + } + } + SD_SET_BPDELTA(sd, off); + } else if (off == 0) { + /* Jump offset will be patched directly, without backpatch chaining. */ + SD_SET_TARGET(sd, NULL); + } else { + /* The jump offset in off is non-zero, therefore it's already known. */ + if (!SetSpanDepTarget(cx, cg, sd, off)) + return JS_FALSE; + } + + if (index > SPANDEP_INDEX_MAX) + index = SPANDEP_INDEX_HUGE; + SET_SPANDEP_INDEX(pc2, index); + return JS_TRUE; +} + +static JSBool +BuildSpanDepTable(JSContext *cx, JSCodeGenerator *cg) +{ + jsbytecode *pc, *end; + JSOp op; + const JSCodeSpec *cs; + ptrdiff_t len, off; + + pc = CG_BASE(cg); + end = CG_NEXT(cg); + while (pc < end) { + op = (JSOp)*pc; + cs = &js_CodeSpec[op]; + len = (ptrdiff_t)cs->length; + + switch (cs->format & JOF_TYPEMASK) { + case JOF_JUMP: + off = GET_JUMP_OFFSET(pc); + if (!AddSpanDep(cx, cg, pc, pc, off)) + return JS_FALSE; + break; + +#if JS_HAS_SWITCH_STATEMENT + case JOF_TABLESWITCH: + { + jsbytecode *pc2; + jsint i, low, high; + + pc2 = pc; + off = GET_JUMP_OFFSET(pc2); + if (!AddSpanDep(cx, cg, pc, pc2, off)) + return JS_FALSE; + pc2 += JUMP_OFFSET_LEN; + low = GET_JUMP_OFFSET(pc2); + pc2 += JUMP_OFFSET_LEN; + high = GET_JUMP_OFFSET(pc2); + pc2 += JUMP_OFFSET_LEN; + for (i = low; i <= high; i++) { + off = GET_JUMP_OFFSET(pc2); + if (!AddSpanDep(cx, cg, pc, pc2, off)) + return JS_FALSE; + pc2 += JUMP_OFFSET_LEN; + } + len = 1 + pc2 - pc; + break; + } + + case JOF_LOOKUPSWITCH: + { + jsbytecode *pc2; + jsint npairs; + + pc2 = pc; + off = GET_JUMP_OFFSET(pc2); + if (!AddSpanDep(cx, cg, pc, pc2, off)) + return JS_FALSE; + pc2 += JUMP_OFFSET_LEN; + npairs = (jsint) GET_ATOM_INDEX(pc2); + pc2 += ATOM_INDEX_LEN; + while (npairs) { + pc2 += ATOM_INDEX_LEN; + off = GET_JUMP_OFFSET(pc2); + if (!AddSpanDep(cx, cg, pc, pc2, off)) + return JS_FALSE; + pc2 += JUMP_OFFSET_LEN; + npairs--; + } + len = 1 + pc2 - pc; + break; + } +#endif /* JS_HAS_SWITCH_STATEMENT */ + } + + pc += len; + } + + return JS_TRUE; +} + +static JSSpanDep * +GetSpanDep(JSCodeGenerator *cg, jsbytecode *pc) +{ + uintN index; + ptrdiff_t offset; + int lo, hi, mid; + JSSpanDep *sd; + + index = GET_SPANDEP_INDEX(pc); + if (index != SPANDEP_INDEX_HUGE) + return cg->spanDeps + index; + + offset = PTRDIFF(pc, CG_BASE(cg), jsbytecode); + lo = 0; + hi = cg->numSpanDeps - 1; + while (lo <= hi) { + mid = (lo + hi) / 2; + sd = cg->spanDeps + mid; + if (sd->before == offset) + return sd; + if (sd->before < offset) + lo = mid + 1; + else + hi = mid - 1; + } + + JS_ASSERT(0); + return NULL; +} + +static JSBool +SetBackPatchDelta(JSContext *cx, JSCodeGenerator *cg, jsbytecode *pc, + ptrdiff_t delta) +{ + JSSpanDep *sd; + + JS_ASSERT(delta >= 1 + JUMP_OFFSET_LEN); + if (!cg->spanDeps && delta < JUMP_OFFSET_MAX) { + SET_JUMP_OFFSET(pc, delta); + return JS_TRUE; + } + + if (delta > BPDELTA_MAX) { + ReportStatementTooLarge(cx, cg); + return JS_FALSE; + } + + if (!cg->spanDeps && !BuildSpanDepTable(cx, cg)) + return JS_FALSE; + + sd = GetSpanDep(cg, pc); + JS_ASSERT(SD_GET_BPDELTA(sd) == 0); + SD_SET_BPDELTA(sd, delta); + return JS_TRUE; +} + +static void +UpdateJumpTargets(JSJumpTarget *jt, ptrdiff_t pivot, ptrdiff_t delta) +{ + if (jt->offset > pivot) { + jt->offset += delta; + if (jt->kids[JT_LEFT]) + UpdateJumpTargets(jt->kids[JT_LEFT], pivot, delta); + } + if (jt->kids[JT_RIGHT]) + UpdateJumpTargets(jt->kids[JT_RIGHT], pivot, delta); +} + +static JSSpanDep * +FindNearestSpanDep(JSCodeGenerator *cg, ptrdiff_t offset, int lo, + JSSpanDep *guard) +{ + int num, hi, mid; + JSSpanDep *sdbase, *sd; + + num = cg->numSpanDeps; + JS_ASSERT(num > 0); + hi = num - 1; + sdbase = cg->spanDeps; + while (lo <= hi) { + mid = (lo + hi) / 2; + sd = sdbase + mid; + if (sd->before == offset) + return sd; + if (sd->before < offset) + lo = mid + 1; + else + hi = mid - 1; + } + if (lo == num) + return guard; + sd = sdbase + lo; + JS_ASSERT(sd->before >= offset && (lo == 0 || sd[-1].before < offset)); + return sd; +} + +static void +FreeJumpTargets(JSCodeGenerator *cg, JSJumpTarget *jt) +{ + if (jt->kids[JT_LEFT]) + FreeJumpTargets(cg, jt->kids[JT_LEFT]); + if (jt->kids[JT_RIGHT]) + FreeJumpTargets(cg, jt->kids[JT_RIGHT]); + jt->kids[JT_LEFT] = cg->jtFreeList; + cg->jtFreeList = jt; +} + +static JSBool +OptimizeSpanDeps(JSContext *cx, JSCodeGenerator *cg) +{ + jsbytecode *pc, *oldpc, *base, *limit, *next; + JSSpanDep *sd, *sd2, *sdbase, *sdlimit, *sdtop, guard; + ptrdiff_t offset, growth, delta, top, pivot, span, length, target; + JSBool done; + JSOp op; + uint32 type; + size_t size, incr; + jssrcnote *sn, *snlimit; + JSSrcNoteSpec *spec; + uintN i, n, noteIndex; + JSTryNote *tn, *tnlimit; +#ifdef DEBUG_brendan + int passes = 0; +#endif + + base = CG_BASE(cg); + sdbase = cg->spanDeps; + sdlimit = sdbase + cg->numSpanDeps; + offset = CG_OFFSET(cg); + growth = 0; + + do { + done = JS_TRUE; + delta = 0; + top = pivot = -1; + sdtop = NULL; + pc = NULL; + op = JSOP_NOP; + type = 0; +#ifdef DEBUG_brendan + passes++; +#endif + + for (sd = sdbase; sd < sdlimit; sd++) { + JS_ASSERT(JT_HAS_TAG(sd->target)); + sd->offset += delta; + + if (sd->top != top) { + sdtop = sd; + top = sd->top; + JS_ASSERT(top == sd->before); + pivot = sd->offset; + pc = base + top; + op = (JSOp) *pc; + type = (js_CodeSpec[op].format & JOF_TYPEMASK); + if (JOF_TYPE_IS_EXTENDED_JUMP(type)) { + /* + * We already extended all the jump offset operands for + * the opcode at sd->top. Jumps and branches have only + * one jump offset operand, but switches have many, all + * of which are adjacent in cg->spanDeps. + */ + continue; + } + + JS_ASSERT(type == JOF_JUMP || + type == JOF_TABLESWITCH || + type == JOF_LOOKUPSWITCH); + } + + if (!JOF_TYPE_IS_EXTENDED_JUMP(type)) { + span = SD_TARGET_OFFSET(sd) - pivot; + if (span < JUMP_OFFSET_MIN || JUMP_OFFSET_MAX < span) { + ptrdiff_t deltaFromTop = 0; + + done = JS_FALSE; + + switch (op) { + case JSOP_GOTO: op = JSOP_GOTOX; break; + case JSOP_IFEQ: op = JSOP_IFEQX; break; + case JSOP_IFNE: op = JSOP_IFNEX; break; + case JSOP_OR: op = JSOP_ORX; break; + case JSOP_AND: op = JSOP_ANDX; break; + case JSOP_GOSUB: op = JSOP_GOSUBX; break; + case JSOP_CASE: op = JSOP_CASEX; break; + case JSOP_DEFAULT: op = JSOP_DEFAULTX; break; + case JSOP_TABLESWITCH: op = JSOP_TABLESWITCHX; break; + case JSOP_LOOKUPSWITCH: op = JSOP_LOOKUPSWITCHX; break; + default: JS_ASSERT(0); + } + *pc = (jsbytecode) op; + + for (sd2 = sdtop; sd2 < sdlimit && sd2->top == top; sd2++) { + if (sd2 <= sd) { + /* + * sd2->offset already includes delta as it stood + * before we entered this loop, but it must also + * include the delta relative to top due to all the + * extended jump offset immediates for the opcode + * starting at top, which we extend in this loop. + * + * If there is only one extended jump offset, then + * sd2->offset won't change and this for loop will + * iterate once only. + */ + sd2->offset += deltaFromTop; + deltaFromTop += JUMPX_OFFSET_LEN - JUMP_OFFSET_LEN; + } else { + /* + * sd2 comes after sd, and won't be revisited by + * the outer for loop, so we have to increase its + * offset by delta, not merely by deltaFromTop. + */ + sd2->offset += delta; + } + + delta += JUMPX_OFFSET_LEN - JUMP_OFFSET_LEN; + UpdateJumpTargets(cg->jumpTargets, sd2->offset, + JUMPX_OFFSET_LEN - JUMP_OFFSET_LEN); + } + sd = sd2 - 1; + } + } + } + + growth += delta; + } while (!done); + + if (growth) { +#ifdef DEBUG_brendan + printf("%s:%u: %u/%u jumps extended in %d passes (%d=%d+%d)\n", + cg->filename ? cg->filename : "stdin", cg->firstLine, + growth / (JUMPX_OFFSET_LEN - JUMP_OFFSET_LEN), cg->numSpanDeps, + passes, offset + growth, offset, growth); +#endif + + /* + * Ensure that we have room for the extended jumps, but don't round up + * to a power of two -- we're done generating code, so we cut to fit. + */ + limit = CG_LIMIT(cg); + length = offset + growth; + next = base + length; + if (next > limit) { + JS_ASSERT(length > BYTECODE_CHUNK); + size = BYTECODE_SIZE(PTRDIFF(limit, base, jsbytecode)); + incr = BYTECODE_SIZE(length) - size; + JS_ARENA_GROW_CAST(base, jsbytecode *, cg->codePool, size, incr); + if (!base) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + CG_BASE(cg) = base; + CG_LIMIT(cg) = next = base + length; + } + CG_NEXT(cg) = next; + + /* + * Set up a fake span dependency record to guard the end of the code + * being generated. This guard record is returned as a fencepost by + * FindNearestSpanDep if there is no real spandep at or above a given + * unextended code offset. + */ + guard.top = -1; + guard.offset = offset + growth; + guard.before = offset; + guard.target = NULL; + } + + /* + * Now work backwards through the span dependencies, copying chunks of + * bytecode between each extended jump toward the end of the grown code + * space, and restoring immediate offset operands for all jump bytecodes. + * The first chunk of bytecodes, starting at base and ending at the first + * extended jump offset (NB: this chunk includes the operation bytecode + * just before that immediate jump offset), doesn't need to be copied. + */ + JS_ASSERT(sd == sdlimit); + top = -1; + while (--sd >= sdbase) { + if (sd->top != top) { + top = sd->top; + op = (JSOp) base[top]; + type = (js_CodeSpec[op].format & JOF_TYPEMASK); + + for (sd2 = sd - 1; sd2 >= sdbase && sd2->top == top; sd2--) + continue; + sd2++; + pivot = sd2->offset; + JS_ASSERT(top == sd2->before); + } + + oldpc = base + sd->before; + span = SD_TARGET_OFFSET(sd) - pivot; + + /* + * If this jump didn't need to be extended, restore its span immediate + * offset operand now, overwriting the index of sd within cg->spanDeps + * that was stored temporarily after *pc when BuildSpanDepTable ran. + * + * Note that span might fit in 16 bits even for an extended jump op, + * if the op has multiple span operands, not all of which overflowed + * (e.g. JSOP_LOOKUPSWITCH or JSOP_TABLESWITCH where some cases are in + * range for a short jump, but others are not). + */ + if (!JOF_TYPE_IS_EXTENDED_JUMP(type)) { + JS_ASSERT(JUMP_OFFSET_MIN <= span && span <= JUMP_OFFSET_MAX); + SET_JUMP_OFFSET(oldpc, span); + continue; + } + + /* + * Set up parameters needed to copy the next run of bytecode starting + * at offset (which is a cursor into the unextended, original bytecode + * vector), down to sd->before (a cursor of the same scale as offset, + * it's the index of the original jump pc). Reuse delta to count the + * nominal number of bytes to copy. + */ + pc = base + sd->offset; + delta = offset - sd->before; + JS_ASSERT(delta >= 1 + JUMP_OFFSET_LEN); + + /* + * Don't bother copying the jump offset we're about to reset, but do + * copy the bytecode at oldpc (which comes just before its immediate + * jump offset operand), on the next iteration through the loop, by + * including it in offset's new value. + */ + offset = sd->before + 1; + size = BYTECODE_SIZE(delta - (1 + JUMP_OFFSET_LEN)); + if (size) { + memmove(pc + 1 + JUMPX_OFFSET_LEN, + oldpc + 1 + JUMP_OFFSET_LEN, + size); + } + + SET_JUMPX_OFFSET(pc, span); + } + + if (growth) { + /* + * Fix source note deltas. Don't hardwire the delta fixup adjustment, + * even though currently it must be JUMPX_OFFSET_LEN - JUMP_OFFSET_LEN + * at each sd that moved. The future may bring different offset sizes + * for span-dependent instruction operands. However, we fix only main + * notes here, not prolog notes -- we know that prolog opcodes are not + * span-dependent, and aren't likely ever to be. + */ + offset = growth = 0; + sd = sdbase; + for (sn = cg->main.notes, snlimit = sn + cg->main.noteCount; + sn < snlimit; + sn = SN_NEXT(sn)) { + /* + * Recall that the offset of a given note includes its delta, and + * tells the offset of the annotated bytecode from the main entry + * point of the script. + */ + offset += SN_DELTA(sn); + while (sd < sdlimit && sd->before < offset) { + /* + * To compute the delta to add to sn, we need to look at the + * spandep after sd, whose offset - (before + growth) tells by + * how many bytes sd's instruction grew. + */ + sd2 = sd + 1; + if (sd2 == sdlimit) + sd2 = &guard; + delta = sd2->offset - (sd2->before + growth); + if (delta > 0) { + JS_ASSERT(delta == JUMPX_OFFSET_LEN - JUMP_OFFSET_LEN); + sn = js_AddToSrcNoteDelta(cx, cg, sn, delta); + if (!sn) + return JS_FALSE; + snlimit = cg->main.notes + cg->main.noteCount; + growth += delta; + } + sd++; + } + + /* + * If sn has span-dependent offset operands, check whether each + * covers further span-dependencies, and increase those operands + * accordingly. Some source notes measure offset not from the + * annotated pc, but from that pc plus some small bias. NB: we + * assume that spec->offsetBias can't itself span span-dependent + * instructions! + */ + spec = &js_SrcNoteSpec[SN_TYPE(sn)]; + if (spec->isSpanDep) { + pivot = offset + spec->offsetBias; + n = spec->arity; + for (i = 0; i < n; i++) { + span = js_GetSrcNoteOffset(sn, i); + if (span == 0) + continue; + target = pivot + span * spec->isSpanDep; + sd2 = FindNearestSpanDep(cg, target, + (target >= pivot) + ? sd - sdbase + : 0, + &guard); + + /* + * Increase target by sd2's before-vs-after offset delta, + * which is absolute (i.e., relative to start of script, + * as is target). Recompute the span by subtracting its + * adjusted pivot from target. + */ + target += sd2->offset - sd2->before; + span = target - (pivot + growth); + span *= spec->isSpanDep; + noteIndex = sn - cg->main.notes; + if (!js_SetSrcNoteOffset(cx, cg, noteIndex, i, span)) + return JS_FALSE; + sn = cg->main.notes + noteIndex; + snlimit = cg->main.notes + cg->main.noteCount; + } + } + } + + /* + * Fix try/catch notes (O(numTryNotes * log2(numSpanDeps)), but it's + * not clear how we can beat that). + */ + for (tn = cg->tryBase, tnlimit = cg->tryNext; tn < tnlimit; tn++) { + /* + * First, look for the nearest span dependency at/above tn->start. + * There may not be any such spandep, in which case the guard will + * be returned. + */ + offset = tn->start; + sd = FindNearestSpanDep(cg, offset, 0, &guard); + delta = sd->offset - sd->before; + tn->start = offset + delta; + + /* + * Next, find the nearest spandep at/above tn->start + tn->length. + * Use its delta minus tn->start's delta to increase tn->length. + */ + length = tn->length; + sd2 = FindNearestSpanDep(cg, offset + length, sd - sdbase, &guard); + if (sd2 != sd) + tn->length = length + sd2->offset - sd2->before - delta; + + /* + * Finally, adjust tn->catchStart upward only if it is non-zero, + * and provided there are spandeps below it that grew. + */ + offset = tn->catchStart; + if (offset != 0) { + sd = FindNearestSpanDep(cg, offset, sd2 - sdbase, &guard); + tn->catchStart = offset + sd->offset - sd->before; + } + } + } + +#ifdef DEBUG_brendan + { + uintN bigspans = 0; + top = -1; + for (sd = sdbase; sd < sdlimit; sd++) { + offset = sd->offset; + + /* NB: sd->top cursors into the original, unextended bytecode vector. */ + if (sd->top != top) { + JS_ASSERT(top == -1 || + !JOF_TYPE_IS_EXTENDED_JUMP(type) || + bigspans != 0); + bigspans = 0; + top = sd->top; + JS_ASSERT(top == sd->before); + op = (JSOp) base[offset]; + type = (js_CodeSpec[op].format & JOF_TYPEMASK); + JS_ASSERT(type == JOF_JUMP || + type == JOF_JUMPX || + type == JOF_TABLESWITCH || + type == JOF_TABLESWITCHX || + type == JOF_LOOKUPSWITCH || + type == JOF_LOOKUPSWITCHX); + pivot = offset; + } + + pc = base + offset; + if (JOF_TYPE_IS_EXTENDED_JUMP(type)) { + span = GET_JUMPX_OFFSET(pc); + if (span < JUMP_OFFSET_MIN || JUMP_OFFSET_MAX < span) { + bigspans++; + } else { + JS_ASSERT(type == JOF_TABLESWITCHX || + type == JOF_LOOKUPSWITCHX); + } + } else { + span = GET_JUMP_OFFSET(pc); + } + JS_ASSERT(SD_TARGET_OFFSET(sd) == pivot + span); + } + JS_ASSERT(!JOF_TYPE_IS_EXTENDED_JUMP(type) || bigspans != 0); + } +#endif + + /* + * Reset so we optimize at most once -- cg may be used for further code + * generation of successive, independent, top-level statements. No jump + * can span top-level statements, because JS lacks goto. + */ + size = SPANDEPS_SIZE(JS_BIT(JS_CeilingLog2(cg->numSpanDeps))); + JS_ArenaFreeAllocation(&cx->tempPool, cg->spanDeps, + JS_MAX(size, SPANDEPS_SIZE_MIN)); + cg->spanDeps = NULL; + FreeJumpTargets(cg, cg->jumpTargets); + cg->jumpTargets = NULL; + cg->numSpanDeps = cg->numJumpTargets = 0; + return JS_TRUE; +} + +static JSBool +EmitJump(JSContext *cx, JSCodeGenerator *cg, JSOp op, ptrdiff_t off) +{ + ptrdiff_t jmp; + jsbytecode *pc; + + if (off < JUMP_OFFSET_MIN || JUMP_OFFSET_MAX < off) { + if (!cg->spanDeps && !BuildSpanDepTable(cx, cg)) + return JS_FALSE; + } + + jmp = js_Emit3(cx, cg, op, JUMP_OFFSET_HI(off), JUMP_OFFSET_LO(off)); + if (jmp >= 0 && cg->spanDeps) { + pc = CG_CODE(cg, jmp); + if (!AddSpanDep(cx, cg, pc, pc, off)) + return JS_FALSE; + } + return jmp; +} + +static ptrdiff_t +GetJumpOffset(JSCodeGenerator *cg, jsbytecode *pc) +{ + JSSpanDep *sd; + JSJumpTarget *jt; + ptrdiff_t top; + + if (!cg->spanDeps) + return GET_JUMP_OFFSET(pc); + + sd = GetSpanDep(cg, pc); + jt = sd->target; + if (!JT_HAS_TAG(jt)) + return JT_TO_BPDELTA(jt); + + top = sd->top; + while (--sd >= cg->spanDeps && sd->top == top) + continue; + sd++; + return JT_CLR_TAG(jt)->offset - sd->offset; +} + +JSBool +js_SetJumpOffset(JSContext *cx, JSCodeGenerator *cg, jsbytecode *pc, + ptrdiff_t off) +{ + if (!cg->spanDeps) { + if (JUMP_OFFSET_MIN <= off && off <= JUMP_OFFSET_MAX) { + SET_JUMP_OFFSET(pc, off); + return JS_TRUE; + } + + if (!BuildSpanDepTable(cx, cg)) + return JS_FALSE; + } + + return SetSpanDepTarget(cx, cg, GetSpanDep(cg, pc), off); +} + +JSBool +js_InWithStatement(JSTreeContext *tc) +{ + JSStmtInfo *stmt; + + for (stmt = tc->topStmt; stmt; stmt = stmt->down) { + if (stmt->type == STMT_WITH) + return JS_TRUE; + } + return JS_FALSE; +} + +JSBool +js_InCatchBlock(JSTreeContext *tc, JSAtom *atom) +{ + JSStmtInfo *stmt; + + for (stmt = tc->topStmt; stmt; stmt = stmt->down) { + if (stmt->type == STMT_CATCH && stmt->label == atom) + return JS_TRUE; + } + return JS_FALSE; +} + +void +js_PushStatement(JSTreeContext *tc, JSStmtInfo *stmt, JSStmtType type, + ptrdiff_t top) +{ + stmt->type = type; + SET_STATEMENT_TOP(stmt, top); + stmt->label = NULL; + stmt->down = tc->topStmt; + tc->topStmt = stmt; +} + +/* + * Emit a backpatch op with offset pointing to the previous jump of this type, + * so that we can walk back up the chain fixing up the op and jump offset. + */ +#define EMIT_BACKPATCH_OP(cx, cg, last, op, jmp) \ + JS_BEGIN_MACRO \ + ptrdiff_t offset, delta; \ + offset = CG_OFFSET(cg); \ + delta = offset - (last); \ + last = offset; \ + JS_ASSERT(delta > 0); \ + jmp = EmitJump((cx), (cg), (op), (delta)); \ + JS_END_MACRO + +/* Emit additional bytecode(s) for non-local jumps. */ +static JSBool +EmitNonLocalJumpFixup(JSContext *cx, JSCodeGenerator *cg, JSStmtInfo *toStmt, + JSOp *returnop) +{ + intN depth; + JSStmtInfo *stmt; + ptrdiff_t jmp; + + /* + * Return from within a try block that has a finally clause must be split + * into two ops: JSOP_SETRVAL, to pop the r.v. and store it in fp->rval; + * and JSOP_RETRVAL, which makes control flow go back to the caller, who + * picks up fp->rval as usual. Otherwise, the stack will be unbalanced + * when executing the finally clause. + * + * We mutate *returnop once only if we find an enclosing try-block (viz, + * STMT_FINALLY) to ensure that we emit just one JSOP_SETRVAL before one + * or more JSOP_GOSUBs and other fixup opcodes emitted by this function. + * Our caller (the TOK_RETURN case of js_EmitTree) then emits *returnop. + * The fixup opcodes and gosubs must interleave in the proper order, from + * inner statement to outer, so that finally clauses run at the correct + * stack depth. + */ + if (returnop) { + JS_ASSERT(*returnop == JSOP_RETURN); + for (stmt = cg->treeContext.topStmt; stmt != toStmt; + stmt = stmt->down) { + if (stmt->type == STMT_FINALLY) { + if (js_Emit1(cx, cg, JSOP_SETRVAL) < 0) + return JS_FALSE; + *returnop = JSOP_RETRVAL; + break; + } + } + + /* + * If there are no try-with-finally blocks open around this return + * statement, we can generate a return forthwith and skip generating + * any fixup code. + */ + if (*returnop == JSOP_RETURN) + return JS_TRUE; + } + + /* + * The non-local jump fixup we emit will unbalance cg->stackDepth, because + * the fixup replicates balanced code such as JSOP_LEAVEWITH emitted at the + * end of a with statement, so we save cg->stackDepth here and restore it + * just before a successful return. + */ + depth = cg->stackDepth; + for (stmt = cg->treeContext.topStmt; stmt != toStmt; stmt = stmt->down) { + switch (stmt->type) { + case STMT_FINALLY: + if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0) + return JS_FALSE; + EMIT_BACKPATCH_OP(cx, cg, stmt->gosub, JSOP_BACKPATCH_PUSH, jmp); + if (jmp < 0) + return JS_FALSE; + break; + + case STMT_WITH: + case STMT_CATCH: + /* There's a With object on the stack that we need to pop. */ + if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0) + return JS_FALSE; + if (js_Emit1(cx, cg, JSOP_LEAVEWITH) < 0) + return JS_FALSE; + break; + + case STMT_FOR_IN_LOOP: + /* + * The iterator and the object being iterated need to be popped. + * JSOP_POP2 isn't decompiled, so it doesn't need to be HIDDEN. + */ + if (js_Emit1(cx, cg, JSOP_POP2) < 0) + return JS_FALSE; + break; + + case STMT_SUBROUTINE: + /* There's a retsub pc-offset on the stack that we need to pop. */ + if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0) + return JS_FALSE; + if (js_Emit1(cx, cg, JSOP_POP) < 0) + return JS_FALSE; + break; + + default:; + } + } + + cg->stackDepth = depth; + return JS_TRUE; +} + +static ptrdiff_t +EmitGoto(JSContext *cx, JSCodeGenerator *cg, JSStmtInfo *toStmt, + ptrdiff_t *last, JSAtomListElement *label, JSSrcNoteType noteType) +{ + intN index; + ptrdiff_t jmp; + + if (!EmitNonLocalJumpFixup(cx, cg, toStmt, NULL)) + return -1; + + if (label) { + index = js_NewSrcNote(cx, cg, noteType); + if (index < 0) + return -1; + if (!js_SetSrcNoteOffset(cx, cg, (uintN)index, 0, + (ptrdiff_t) ALE_INDEX(label))) { + return -1; + } + } else if (noteType != SRC_NULL) { + if (js_NewSrcNote(cx, cg, noteType) < 0) + return -1; + } + + EMIT_BACKPATCH_OP(cx, cg, *last, JSOP_BACKPATCH, jmp); + return jmp; +} + +static JSBool +BackPatch(JSContext *cx, JSCodeGenerator *cg, ptrdiff_t last, + jsbytecode *target, jsbytecode op) +{ + jsbytecode *pc, *stop; + ptrdiff_t delta, span; + + pc = CG_CODE(cg, last); + stop = CG_CODE(cg, -1); + while (pc != stop) { + delta = GetJumpOffset(cg, pc); + span = PTRDIFF(target, pc, jsbytecode); + CHECK_AND_SET_JUMP_OFFSET(cx, cg, pc, span); + + /* + * Set *pc after jump offset in case bpdelta didn't overflow, but span + * does (if so, CHECK_AND_SET_JUMP_OFFSET might call BuildSpanDepTable + * and need to see the JSOP_BACKPATCH* op at *pc). + */ + *pc = op; + pc -= delta; + } + return JS_TRUE; +} + +void +js_PopStatement(JSTreeContext *tc) +{ + tc->topStmt = tc->topStmt->down; +} + +JSBool +js_PopStatementCG(JSContext *cx, JSCodeGenerator *cg) +{ + JSStmtInfo *stmt; + + stmt = cg->treeContext.topStmt; + if (!BackPatch(cx, cg, stmt->breaks, CG_NEXT(cg), JSOP_GOTO) || + !BackPatch(cx, cg, stmt->continues, CG_CODE(cg, stmt->update), + JSOP_GOTO)) { + return JS_FALSE; + } + js_PopStatement(&cg->treeContext); + return JS_TRUE; +} + +/* + * Emit a bytecode and its 2-byte constant (atom) index immediate operand. + * NB: We use cx and cg from our caller's lexical environment, and return + * false on error. + */ +#define EMIT_ATOM_INDEX_OP(op, atomIndex) \ + JS_BEGIN_MACRO \ + if (js_Emit3(cx, cg, op, ATOM_INDEX_HI(atomIndex), \ + ATOM_INDEX_LO(atomIndex)) < 0) { \ + return JS_FALSE; \ + } \ + JS_END_MACRO + +static JSBool +EmitAtomOp(JSContext *cx, JSParseNode *pn, JSOp op, JSCodeGenerator *cg) +{ + JSAtomListElement *ale; + + ale = js_IndexAtom(cx, pn->pn_atom, &cg->atomList); + if (!ale) + return JS_FALSE; + EMIT_ATOM_INDEX_OP(op, ALE_INDEX(ale)); + return JS_TRUE; +} + +/* + * This routine tries to optimize name gets and sets to stack slot loads and + * stores, given the variables object and scope chain in cx's top frame, the + * compile-time context in tc, and a TOK_NAME node pn. It returns false on + * error, true on success. + * + * The caller can inspect pn->pn_slot for a non-negative slot number to tell + * whether optimization occurred, in which case LookupArgOrVar also updated + * pn->pn_op. If pn->pn_slot is still -1 on return, pn->pn_op nevertheless + * may have been optimized, e.g., from JSOP_NAME to JSOP_ARGUMENTS. Whether + * or not pn->pn_op was modified, if this function finds an argument or local + * variable name, pn->pn_attrs will contain the property's attributes after a + * successful return. + */ +static JSBool +LookupArgOrVar(JSContext *cx, JSTreeContext *tc, JSParseNode *pn) +{ + JSObject *obj, *pobj; + JSClass *clasp; + JSAtom *atom; + JSScopeProperty *sprop; + JSOp op; + + JS_ASSERT(pn->pn_type == TOK_NAME); + if (pn->pn_slot >= 0 || pn->pn_op == JSOP_ARGUMENTS) + return JS_TRUE; + + /* + * We can't optimize if var and closure (a local function not in a larger + * expression and not at top-level within another's body) collide. + * XXX suboptimal: keep track of colliding names and deoptimize only those + */ + if (tc->flags & TCF_FUN_CLOSURE_VS_VAR) + return JS_TRUE; + + /* + * We can't optimize if we're not compiling a function body, whether via + * eval, or directly when compiling a function statement or expression. + */ + obj = cx->fp->varobj; + clasp = OBJ_GET_CLASS(cx, obj); + if (clasp != &js_FunctionClass && clasp != &js_CallClass) + return JS_TRUE; + + /* + * We can't optimize if we're in an eval called inside a with statement, + * or we're compiling a with statement and its body, or we're in a catch + * block whose exception variable has the same name as pn. + */ + atom = pn->pn_atom; + if (cx->fp->scopeChain != obj || + js_InWithStatement(tc) || + js_InCatchBlock(tc, atom)) { + return JS_TRUE; + } + + /* + * Ok, we may be able to optimize name to stack slot. Look for an argument + * or variable property in the function, or its call object, not found in + * any prototype object. Rewrite pn_op and update pn accordingly. NB: We + * know that JSOP_DELNAME on an argument or variable must evaluate to + * false, due to JSPROP_PERMANENT. + */ + if (!js_LookupProperty(cx, obj, (jsid)atom, &pobj, (JSProperty **)&sprop)) + return JS_FALSE; + op = pn->pn_op; + if (sprop) { + if (pobj == obj) { + JSPropertyOp getter = sprop->getter; + + if (getter == js_GetArgument) { + switch (op) { + case JSOP_NAME: op = JSOP_GETARG; break; + case JSOP_SETNAME: op = JSOP_SETARG; break; + case JSOP_INCNAME: op = JSOP_INCARG; break; + case JSOP_NAMEINC: op = JSOP_ARGINC; break; + case JSOP_DECNAME: op = JSOP_DECARG; break; + case JSOP_NAMEDEC: op = JSOP_ARGDEC; break; + case JSOP_FORNAME: op = JSOP_FORARG; break; + case JSOP_DELNAME: op = JSOP_FALSE; break; + default: JS_ASSERT(0); + } + } else if (getter == js_GetLocalVariable || + getter == js_GetCallVariable) + { + switch (op) { + case JSOP_NAME: op = JSOP_GETVAR; break; + case JSOP_SETNAME: op = JSOP_SETVAR; break; + case JSOP_SETCONST: op = JSOP_SETVAR; break; + case JSOP_INCNAME: op = JSOP_INCVAR; break; + case JSOP_NAMEINC: op = JSOP_VARINC; break; + case JSOP_DECNAME: op = JSOP_DECVAR; break; + case JSOP_NAMEDEC: op = JSOP_VARDEC; break; + case JSOP_FORNAME: op = JSOP_FORVAR; break; + case JSOP_DELNAME: op = JSOP_FALSE; break; + default: JS_ASSERT(0); + } + } + if (op != pn->pn_op) { + pn->pn_op = op; + pn->pn_slot = sprop->shortid; + } + pn->pn_attrs = sprop->attrs; + } + OBJ_DROP_PROPERTY(cx, pobj, (JSProperty *)sprop); + } + + if (pn->pn_slot < 0) { + /* + * We couldn't optimize it, so it's not an arg or local var name. Now + * we must check for the predefined arguments variable. It may be + * overridden by assignment, in which case the function is heavyweight + * and the interpreter will look up 'arguments' in the function's call + * object. + */ + if (pn->pn_op == JSOP_NAME && + atom == cx->runtime->atomState.argumentsAtom) { + pn->pn_op = JSOP_ARGUMENTS; + return JS_TRUE; + } + + tc->flags |= TCF_FUN_USES_NONLOCALS; + } + return JS_TRUE; +} + +/* + * If pn contains a useful expression, return true with *answer set to true. + * If pn contains a useless expression, return true with *answer set to false. + * Return false on error. + * + * The caller should initialize *answer to false and invoke this function on + * an expression statement or similar subtree to decide whether the tree could + * produce code that has any side effects. For an expression statement, we + * define useless code as code with no side effects, because the main effect, + * the value left on the stack after the code executes, will be discarded by a + * pop bytecode. + */ +static JSBool +CheckSideEffects(JSContext *cx, JSTreeContext *tc, JSParseNode *pn, + JSBool *answer) +{ + JSBool ok; + JSFunction *fun; + JSParseNode *pn2; + + ok = JS_TRUE; + if (!pn || *answer) + return ok; + + switch (pn->pn_arity) { + case PN_FUNC: + /* + * A named function is presumed useful: we can't yet know that it is + * not called. The side effects are the creation of a scope object + * to parent this function object, and the binding of the function's + * name in that scope object. See comments at case JSOP_NAMEDFUNOBJ: + * in jsinterp.c. + */ + fun = (JSFunction *) JS_GetPrivate(cx, ATOM_TO_OBJECT(pn->pn_funAtom)); + if (fun->atom) + *answer = JS_TRUE; + break; + + case PN_LIST: + if (pn->pn_type == TOK_NEW || + pn->pn_type == TOK_LP || + pn->pn_type == TOK_LB) { + /* + * All invocation operations (construct: TOK_NEW, call: TOK_LP) + * are presumed to be useful, because they may have side effects + * even if their main effect (their return value) is discarded. + * + * TOK_LB binary trees of 3 or more nodes are flattened into lists + * to avoid too much recursion. All such lists must be presumed + * to be useful because each index operation could invoke a getter + * (the JSOP_ARGUMENTS special case below, in the PN_BINARY case, + * does not apply here: arguments[i][j] might invoke a getter). + */ + *answer = JS_TRUE; + } else { + for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) + ok &= CheckSideEffects(cx, tc, pn2, answer); + } + break; + + case PN_TERNARY: + ok = CheckSideEffects(cx, tc, pn->pn_kid1, answer) && + CheckSideEffects(cx, tc, pn->pn_kid2, answer) && + CheckSideEffects(cx, tc, pn->pn_kid3, answer); + break; + + case PN_BINARY: + if (pn->pn_type == TOK_ASSIGN) { + /* + * Assignment is presumed to be useful, even if the next operation + * is another assignment overwriting this one's ostensible effect, + * because the left operand may be a property with a setter that + * has side effects. + */ + *answer = JS_TRUE; + } else { + if (pn->pn_type == TOK_LB) { + pn2 = pn->pn_left; + if (pn2->pn_type == TOK_NAME && !LookupArgOrVar(cx, tc, pn2)) + return JS_FALSE; + if (pn2->pn_op != JSOP_ARGUMENTS) { + /* + * Any indexed property reference could call a getter with + * side effects, except for arguments[i] where arguments is + * unambiguous. + */ + *answer = JS_TRUE; + } + } + ok = CheckSideEffects(cx, tc, pn->pn_left, answer) && + CheckSideEffects(cx, tc, pn->pn_right, answer); + } + break; + + case PN_UNARY: + if (pn->pn_type == TOK_INC || pn->pn_type == TOK_DEC || + pn->pn_type == TOK_DELETE || + pn->pn_type == TOK_THROW || + pn->pn_type == TOK_DEFSHARP) { + /* All these operations have effects that we must commit. */ + *answer = JS_TRUE; + } else { + ok = CheckSideEffects(cx, tc, pn->pn_kid, answer); + } + break; + + case PN_NAME: + if (pn->pn_type == TOK_NAME) { + if (!LookupArgOrVar(cx, tc, pn)) + return JS_FALSE; + if (pn->pn_slot < 0 && pn->pn_op != JSOP_ARGUMENTS) { + /* + * Not an argument or local variable use, so this expression + * could invoke a getter that has side effects. + */ + *answer = JS_TRUE; + } + } + pn2 = pn->pn_expr; + if (pn->pn_type == TOK_DOT && pn2->pn_type == TOK_NAME) { + if (!LookupArgOrVar(cx, tc, pn2)) + return JS_FALSE; + if (!(pn2->pn_op == JSOP_ARGUMENTS && + pn->pn_atom == cx->runtime->atomState.lengthAtom)) { + /* + * Any dotted property reference could call a getter, except + * for arguments.length where arguments is unambiguous. + */ + *answer = JS_TRUE; + } + } + ok = CheckSideEffects(cx, tc, pn2, answer); + break; + + case PN_NULLARY: + if (pn->pn_type == TOK_DEBUGGER) + *answer = JS_TRUE; + break; + } + return ok; +} + +static JSBool +EmitPropOp(JSContext *cx, JSParseNode *pn, JSOp op, JSCodeGenerator *cg) +{ + JSParseNode *pn2, *pndot, *pnup, *pndown; + ptrdiff_t top; + JSAtomListElement *ale; + + pn2 = pn->pn_expr; + if (op == JSOP_GETPROP && + pn->pn_type == TOK_DOT && + pn2->pn_type == TOK_NAME) { + /* Try to optimize arguments.length into JSOP_ARGCNT. */ + if (!LookupArgOrVar(cx, &cg->treeContext, pn2)) + return JS_FALSE; + if (pn2->pn_op == JSOP_ARGUMENTS && + pn->pn_atom == cx->runtime->atomState.lengthAtom) { + return js_Emit1(cx, cg, JSOP_ARGCNT) >= 0; + } + } + + /* + * If the object operand is also a dotted property reference, reverse the + * list linked via pn_expr temporarily so we can iterate over it from the + * bottom up (reversing again as we go), to avoid excessive recursion. + */ + if (pn2->pn_type == TOK_DOT) { + pndot = pn2; + pnup = NULL; + top = CG_OFFSET(cg); + for (;;) { + /* Reverse pndot->pn_expr to point up, not down. */ + pndot->pn_offset = top; + pndown = pndot->pn_expr; + pndot->pn_expr = pnup; + if (pndown->pn_type != TOK_DOT) + break; + pnup = pndot; + pndot = pndown; + } + + /* pndown is a primary expression, not a dotted property reference. */ + if (!js_EmitTree(cx, cg, pndown)) + return JS_FALSE; + + do { + /* Walk back up the list, emitting annotated name ops. */ + if (js_NewSrcNote2(cx, cg, SRC_PCBASE, + CG_OFFSET(cg) - pndown->pn_offset) < 0) { + return JS_FALSE; + } + ale = js_IndexAtom(cx, pndot->pn_atom, &cg->atomList); + if (!ale) + return JS_FALSE; + EMIT_ATOM_INDEX_OP(pndot->pn_op, ALE_INDEX(ale)); + + /* Reverse the pn_expr link again. */ + pnup = pndot->pn_expr; + pndot->pn_expr = pndown; + pndown = pndot; + } while ((pndot = pnup) != NULL); + } else { + if (!js_EmitTree(cx, cg, pn2)) + return JS_FALSE; + } + + if (js_NewSrcNote2(cx, cg, SRC_PCBASE, CG_OFFSET(cg) - pn2->pn_offset) < 0) + return JS_FALSE; + if (!pn->pn_atom) { + JS_ASSERT(op == JSOP_IMPORTALL); + if (js_Emit1(cx, cg, op) < 0) + return JS_FALSE; + } else { + ale = js_IndexAtom(cx, pn->pn_atom, &cg->atomList); + if (!ale) + return JS_FALSE; + EMIT_ATOM_INDEX_OP(op, ALE_INDEX(ale)); + } + return JS_TRUE; +} + +static JSBool +EmitElemOp(JSContext *cx, JSParseNode *pn, JSOp op, JSCodeGenerator *cg) +{ + ptrdiff_t top; + JSParseNode *left, *right, *next; + jsint slot; + + top = CG_OFFSET(cg); + if (pn->pn_arity == PN_LIST) { + /* Left-associative operator chain to avoid too much recursion. */ + JS_ASSERT(pn->pn_op == JSOP_GETELEM); + JS_ASSERT(pn->pn_count >= 3); + left = pn->pn_head; + right = PN_LAST(pn); + next = left->pn_next; + JS_ASSERT(next != right); + + /* + * Try to optimize arguments[0][j]... into JSOP_ARGSUB<0> followed by + * one or more index expression and JSOP_GETELEM op pairs. + */ + if (left->pn_type == TOK_NAME && next->pn_type == TOK_NUMBER) { + if (!LookupArgOrVar(cx, &cg->treeContext, left)) + return JS_FALSE; + if (left->pn_op == JSOP_ARGUMENTS && + JSDOUBLE_IS_INT(next->pn_dval, slot) && + (jsuint)slot < ATOM_INDEX_LIMIT) { + left->pn_offset = next->pn_offset = top; + EMIT_ATOM_INDEX_OP(JSOP_ARGSUB, (jsatomid)slot); + left = next; + next = left->pn_next; + } + } + + /* + * Check whether we generated JSOP_ARGSUB, just above, and have only + * one more index expression to emit. Given arguments[0][j], we must + * skip the while loop altogether, falling through to emit code for j + * (in the subtree referenced by right), followed by the annotated op, + * at the bottom of this function. + */ + JS_ASSERT(next != right || pn->pn_count == 3); + if (left == pn->pn_head) { + if (!js_EmitTree(cx, cg, left)) + return JS_FALSE; + } + while (next != right) { + if (!js_EmitTree(cx, cg, next)) + return JS_FALSE; + if (js_NewSrcNote2(cx, cg, SRC_PCBASE, CG_OFFSET(cg) - top) < 0) + return JS_FALSE; + if (js_Emit1(cx, cg, JSOP_GETELEM) < 0) + return JS_FALSE; + next = next->pn_next; + } + } else { + JS_ASSERT(pn->pn_arity == PN_BINARY); + left = pn->pn_left; + right = pn->pn_right; + + /* Try to optimize arguments[0] (e.g.) into JSOP_ARGSUB<0>. */ + if (op == JSOP_GETELEM && + left->pn_type == TOK_NAME && + right->pn_type == TOK_NUMBER) { + if (!LookupArgOrVar(cx, &cg->treeContext, left)) + return JS_FALSE; + if (left->pn_op == JSOP_ARGUMENTS && + JSDOUBLE_IS_INT(right->pn_dval, slot) && + (jsuint)slot < ATOM_INDEX_LIMIT) { + left->pn_offset = right->pn_offset = top; + EMIT_ATOM_INDEX_OP(JSOP_ARGSUB, (jsatomid)slot); + return JS_TRUE; + } + } + + if (!js_EmitTree(cx, cg, left)) + return JS_FALSE; + } + if (!js_EmitTree(cx, cg, right)) + return JS_FALSE; + if (js_NewSrcNote2(cx, cg, SRC_PCBASE, CG_OFFSET(cg) - top) < 0) + return JS_FALSE; + return js_Emit1(cx, cg, op) >= 0; +} + +static JSBool +EmitNumberOp(JSContext *cx, jsdouble dval, JSCodeGenerator *cg) +{ + jsint ival; + jsatomid atomIndex; + JSAtom *atom; + JSAtomListElement *ale; + + if (JSDOUBLE_IS_INT(dval, ival) && INT_FITS_IN_JSVAL(ival)) { + if (ival == 0) + return js_Emit1(cx, cg, JSOP_ZERO) >= 0; + if (ival == 1) + return js_Emit1(cx, cg, JSOP_ONE) >= 0; + if ((jsuint)ival < (jsuint)ATOM_INDEX_LIMIT) { + atomIndex = (jsatomid)ival; + EMIT_ATOM_INDEX_OP(JSOP_UINT16, atomIndex); + return JS_TRUE; + } + atom = js_AtomizeInt(cx, ival, 0); + } else { + atom = js_AtomizeDouble(cx, dval, 0); + } + if (!atom) + return JS_FALSE; + ale = js_IndexAtom(cx, atom, &cg->atomList); + if (!ale) + return JS_FALSE; + EMIT_ATOM_INDEX_OP(JSOP_NUMBER, ALE_INDEX(ale)); + return JS_TRUE; +} + +#if JS_HAS_SWITCH_STATEMENT +static JSBool +EmitSwitch(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn, + JSStmtInfo *stmtInfo) +{ + JSOp switchop; + JSBool ok, hasDefault; + ptrdiff_t top, off, defaultOffset; + JSParseNode *pn2, *pn3, *pn4; + uint32 ncases, tablen; + jsdouble d; + jsint i, low, high; + JSAtom *atom; + JSAtomListElement *ale; + intN noteIndex; + size_t switchsize, tablesize; + JSParseNode **table; + jsbytecode *pc; + + /* Try for most optimal, fall back if not dense ints, and per ECMAv2. */ + switchop = JSOP_TABLESWITCH; + ok = JS_TRUE; + hasDefault = JS_FALSE; + defaultOffset = -1; + + /* Emit code for the discriminant first. */ + if (!js_EmitTree(cx, cg, pn->pn_kid1)) + return JS_FALSE; + + /* Switch bytecodes run from here till end of final case. */ + top = CG_OFFSET(cg); + js_PushStatement(&cg->treeContext, stmtInfo, STMT_SWITCH, top); + + pn2 = pn->pn_kid2; + ncases = pn2->pn_count; + tablen = 0; + + if (ncases == 0 || + (ncases == 1 && + (hasDefault = (pn2->pn_head->pn_type == TOK_DEFAULT)))) { + ncases = 0; + low = 0; + high = -1; + } else { +#define INTMAP_LENGTH 256 + jsbitmap intmap_space[INTMAP_LENGTH]; + jsbitmap *intmap = NULL; + int32 intmap_bitlen = 0; + + low = JSVAL_INT_MAX; + high = JSVAL_INT_MIN; + + for (pn3 = pn2->pn_head; pn3; pn3 = pn3->pn_next) { + if (pn3->pn_type == TOK_DEFAULT) { + hasDefault = JS_TRUE; + ncases--; /* one of the "cases" was the default */ + continue; + } + + JS_ASSERT(pn3->pn_type == TOK_CASE); + if (switchop == JSOP_CONDSWITCH) + continue; + + pn4 = pn3->pn_left; + switch (pn4->pn_type) { + case TOK_NUMBER: + d = pn4->pn_dval; + if (JSDOUBLE_IS_INT(d, i) && INT_FITS_IN_JSVAL(i)) { + pn3->pn_val = INT_TO_JSVAL(i); + } else { + atom = js_AtomizeDouble(cx, d, 0); + if (!atom) { + ok = JS_FALSE; + goto release; + } + pn3->pn_val = ATOM_KEY(atom); + } + break; + case TOK_STRING: + pn3->pn_val = ATOM_KEY(pn4->pn_atom); + break; + case TOK_PRIMARY: + if (pn4->pn_op == JSOP_TRUE) { + pn3->pn_val = JSVAL_TRUE; + break; + } + if (pn4->pn_op == JSOP_FALSE) { + pn3->pn_val = JSVAL_FALSE; + break; + } + /* FALL THROUGH */ + default: + switchop = JSOP_CONDSWITCH; + continue; + } + + JS_ASSERT(JSVAL_IS_NUMBER(pn3->pn_val) || + JSVAL_IS_STRING(pn3->pn_val) || + JSVAL_IS_BOOLEAN(pn3->pn_val)); + + if (switchop != JSOP_TABLESWITCH) + continue; + if (!JSVAL_IS_INT(pn3->pn_val)) { + switchop = JSOP_LOOKUPSWITCH; + continue; + } + i = JSVAL_TO_INT(pn3->pn_val); + if ((jsuint)(i + (jsint)JS_BIT(15)) >= (jsuint)JS_BIT(16)) { + switchop = JSOP_LOOKUPSWITCH; + continue; + } + if (i < low) + low = i; + if (high < i) + high = i; + + /* + * Check for duplicates, which require a JSOP_LOOKUPSWITCH. + * We bias i by 65536 if it's negative, and hope that's a rare + * case (because it requires a malloc'd bitmap). + */ + if (i < 0) + i += JS_BIT(16); + if (i >= intmap_bitlen) { + if (!intmap && + i < (INTMAP_LENGTH << JS_BITS_PER_WORD_LOG2)) { + intmap = intmap_space; + intmap_bitlen = INTMAP_LENGTH << JS_BITS_PER_WORD_LOG2; + } else { + /* Just grab 8K for the worst-case bitmap. */ + intmap_bitlen = JS_BIT(16); + intmap = (jsbitmap *) + JS_malloc(cx, + (JS_BIT(16) >> JS_BITS_PER_WORD_LOG2) + * sizeof(jsbitmap)); + if (!intmap) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + } + memset(intmap, 0, intmap_bitlen >> JS_BITS_PER_BYTE_LOG2); + } + if (JS_TEST_BIT(intmap, i)) { + switchop = JSOP_LOOKUPSWITCH; + continue; + } + JS_SET_BIT(intmap, i); + } + + release: + if (intmap && intmap != intmap_space) + JS_free(cx, intmap); + if (!ok) + return JS_FALSE; + + /* + * Compute table length and select lookup instead if overlarge or + * more than half-sparse. + */ + if (switchop == JSOP_TABLESWITCH) { + tablen = (uint32)(high - low + 1); + if (tablen >= JS_BIT(16) || tablen > 2 * ncases) + switchop = JSOP_LOOKUPSWITCH; + } + } + + /* + * Emit a note with two offsets: first tells total switch code length, + * second tells offset to first JSOP_CASE if condswitch. + */ + noteIndex = js_NewSrcNote3(cx, cg, SRC_SWITCH, 0, 0); + if (noteIndex < 0) + return JS_FALSE; + + if (switchop == JSOP_CONDSWITCH) { + /* + * 0 bytes of immediate for unoptimized ECMAv2 switch. + */ + switchsize = 0; + } else if (switchop == JSOP_TABLESWITCH) { + /* + * 3 offsets (len, low, high) before the table, 1 per entry. + */ + switchsize = (size_t)(JUMP_OFFSET_LEN * (3 + tablen)); + } else { + /* + * JSOP_LOOKUPSWITCH: + * 1 offset (len) and 1 atom index (npairs) before the table, + * 1 atom index and 1 jump offset per entry. + */ + switchsize = (size_t)(JUMP_OFFSET_LEN + ATOM_INDEX_LEN + + (ATOM_INDEX_LEN + JUMP_OFFSET_LEN) * ncases); + } + + /* + * Emit switchop followed by switchsize bytes of jump or lookup table. + * + * If switchop is JSOP_LOOKUPSWITCH or JSOP_TABLESWITCH, it is crucial + * to emit the immediate operand(s) by which bytecode readers such as + * BuildSpanDepTable discover the length of the switch opcode *before* + * calling js_SetJumpOffset (which may call BuildSpanDepTable). It's + * also important to zero all unknown jump offset immediate operands, + * so they can be converted to span dependencies with null targets to + * be computed later (js_EmitN zeros switchsize bytes after switchop). + */ + if (js_EmitN(cx, cg, switchop, switchsize) < 0) + return JS_FALSE; + + off = -1; + if (switchop == JSOP_CONDSWITCH) { + intN caseNoteIndex = -1; + + /* Emit code for evaluating cases and jumping to case statements. */ + for (pn3 = pn2->pn_head; pn3; pn3 = pn3->pn_next) { + pn4 = pn3->pn_left; + if (pn4 && !js_EmitTree(cx, cg, pn4)) + return JS_FALSE; + if (caseNoteIndex >= 0) { + /* off is the previous JSOP_CASE's bytecode offset. */ + if (!js_SetSrcNoteOffset(cx, cg, (uintN)caseNoteIndex, 0, + CG_OFFSET(cg) - off)) { + return JS_FALSE; + } + } + if (pn3->pn_type == TOK_DEFAULT) + continue; + caseNoteIndex = js_NewSrcNote2(cx, cg, SRC_PCDELTA, 0); + if (caseNoteIndex < 0) + return JS_FALSE; + off = EmitJump(cx, cg, JSOP_CASE, 0); + if (off < 0) + return JS_FALSE; + pn3->pn_offset = off; + if (pn3 == pn2->pn_head) { + /* Switch note's second offset is to first JSOP_CASE. */ + if (!js_SetSrcNoteOffset(cx, cg, (uintN)noteIndex, 1, + off - top)) { + return JS_FALSE; + } + } + } + + /* Emit default even if no explicit default statement. */ + defaultOffset = EmitJump(cx, cg, JSOP_DEFAULT, 0); + if (defaultOffset < 0) + return JS_FALSE; + } else if (switchop == JSOP_TABLESWITCH) { + /* Fill in switch bounds, which we know fit in 16-bit offsets. */ + pc = CG_CODE(cg, top + JUMP_OFFSET_LEN); + SET_JUMP_OFFSET(pc, low); + pc += JUMP_OFFSET_LEN; + SET_JUMP_OFFSET(pc, high); + pc += JUMP_OFFSET_LEN; + } else { + JS_ASSERT(switchop == JSOP_LOOKUPSWITCH); + + /* Fill in the number of cases. */ + pc = CG_CODE(cg, top + JUMP_OFFSET_LEN); + SET_ATOM_INDEX(pc, ncases); + } + + /* Emit code for each case's statements, copying pn_offset up to pn3. */ + for (pn3 = pn2->pn_head; pn3; pn3 = pn3->pn_next) { + if (switchop == JSOP_CONDSWITCH && pn3->pn_type != TOK_DEFAULT) + CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, pn3->pn_offset); + pn4 = pn3->pn_right; + if (!js_EmitTree(cx, cg, pn4)) + return JS_FALSE; + pn3->pn_offset = pn4->pn_offset; + if (pn3->pn_type == TOK_DEFAULT) + off = pn3->pn_offset - top; + } + + if (!hasDefault) { + /* If no default case, offset for default is to end of switch. */ + off = CG_OFFSET(cg) - top; + } + + /* We better have set "off" by now. */ + JS_ASSERT(off != -1); + + /* Set the default offset (to end of switch if no default). */ + pc = NULL; + if (switchop == JSOP_CONDSWITCH) { + JS_ASSERT(defaultOffset != -1); + if (!js_SetJumpOffset(cx, cg, CG_CODE(cg, defaultOffset), + off - (defaultOffset - top))) { + return JS_FALSE; + } + } else { + pc = CG_CODE(cg, top); + if (!js_SetJumpOffset(cx, cg, pc, off)) + return JS_FALSE; + pc += JUMP_OFFSET_LEN; + } + + /* Set the SRC_SWITCH note's offset operand to tell end of switch. */ + off = CG_OFFSET(cg) - top; + if (!js_SetSrcNoteOffset(cx, cg, (uintN)noteIndex, 0, off)) + return JS_FALSE; + + if (switchop == JSOP_TABLESWITCH) { + /* Skip over the already-initialized switch bounds. */ + pc += 2 * JUMP_OFFSET_LEN; + + /* Fill in the jump table, if there is one. */ + if (tablen) { + /* Avoid bloat for a compilation unit with many switches. */ + tablesize = (size_t)tablen * sizeof *table; + table = (JSParseNode **) JS_malloc(cx, tablesize); + if (!table) + return JS_FALSE; + memset(table, 0, tablesize); + for (pn3 = pn2->pn_head; pn3; pn3 = pn3->pn_next) { + if (pn3->pn_type == TOK_DEFAULT) + continue; + i = JSVAL_TO_INT(pn3->pn_val); + i -= low; + JS_ASSERT((uint32)i < tablen); + table[i] = pn3; + } + for (i = 0; i < (jsint)tablen; i++) { + pn3 = table[i]; + off = pn3 ? pn3->pn_offset - top : 0; + ok = js_SetJumpOffset(cx, cg, pc, off); + if (!ok) + break; + pc += JUMP_OFFSET_LEN; + } + JS_free(cx, table); + if (!ok) + return JS_FALSE; + } + } else if (switchop == JSOP_LOOKUPSWITCH) { + /* Skip over the already-initialized number of cases. */ + pc += ATOM_INDEX_LEN; + + for (pn3 = pn2->pn_head; pn3; pn3 = pn3->pn_next) { + if (pn3->pn_type == TOK_DEFAULT) + continue; + atom = js_AtomizeValue(cx, pn3->pn_val, 0); + if (!atom) + return JS_FALSE; + ale = js_IndexAtom(cx, atom, &cg->atomList); + if (!ale) + return JS_FALSE; + SET_ATOM_INDEX(pc, ALE_INDEX(ale)); + pc += ATOM_INDEX_LEN; + + off = pn3->pn_offset - top; + if (!js_SetJumpOffset(cx, cg, pc, off)) + return JS_FALSE; + pc += JUMP_OFFSET_LEN; + } + } + + return js_PopStatementCG(cx, cg); +} +#endif /* JS_HAS_SWITCH_STATEMENT */ + +JSBool +js_EmitFunctionBody(JSContext *cx, JSCodeGenerator *cg, JSParseNode *body, + JSFunction *fun) +{ + JSStackFrame *fp, frame; + JSObject *funobj; + JSBool ok; + + if (!js_AllocTryNotes(cx, cg)) + return JS_FALSE; + + fp = cx->fp; + funobj = fun->object; + if (!fp || fp->fun != fun || fp->varobj != funobj || + fp->scopeChain != funobj) { + memset(&frame, 0, sizeof frame); + frame.fun = fun; + frame.varobj = frame.scopeChain = funobj; + frame.down = fp; + cx->fp = &frame; + } + ok = js_EmitTree(cx, cg, body); + cx->fp = fp; + if (!ok) + return JS_FALSE; + + fun->script = js_NewScriptFromCG(cx, cg, fun); + if (!fun->script) + return JS_FALSE; + if (cg->treeContext.flags & TCF_FUN_HEAVYWEIGHT) + fun->flags |= JSFUN_HEAVYWEIGHT; + return JS_TRUE; +} + +/* A macro for inlining at the top of js_EmitTree (whence it came). */ +#define UPDATE_LINE_NUMBER_NOTES(cx, cg, pn) \ + JS_BEGIN_MACRO \ + uintN line_ = (pn)->pn_pos.begin.lineno; \ + uintN delta_ = line_ - CG_CURRENT_LINE(cg); \ + if (delta_ != 0) { \ + /* \ + * Encode any change in the current source line number by using \ + * either several SRC_NEWLINE notes or just one SRC_SETLINE note, \ + * whichever consumes less space. \ + * \ + * NB: We handle backward line number deltas (possible with for \ + * loops where the update part is emitted after the body, but its \ + * line number is <= any line number in the body) here by letting \ + * unsigned delta_ wrap to a very large number, which triggers a \ + * SRC_SETLINE. \ + */ \ + CG_CURRENT_LINE(cg) = line_; \ + if (delta_ >= (uintN)(2 + ((line_ > SN_3BYTE_OFFSET_MASK)<<1))) { \ + if (js_NewSrcNote2(cx, cg, SRC_SETLINE, (ptrdiff_t)line_) < 0)\ + return JS_FALSE; \ + } else { \ + do { \ + if (js_NewSrcNote(cx, cg, SRC_NEWLINE) < 0) \ + return JS_FALSE; \ + } while (--delta_ != 0); \ + } \ + } \ + JS_END_MACRO + +/* A function, so that we avoid macro-bloating all the other callsites. */ +static JSBool +UpdateLineNumberNotes(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn) +{ + UPDATE_LINE_NUMBER_NOTES(cx, cg, pn); + return JS_TRUE; +} + +JSBool +js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn) +{ + JSBool ok, useful, wantval; + JSStmtInfo *stmt, stmtInfo; + ptrdiff_t top, off, tmp, beq, jmp; + JSParseNode *pn2, *pn3; + JSAtom *atom; + JSAtomListElement *ale; + jsatomid atomIndex; + intN noteIndex; + JSSrcNoteType noteType; + jsbytecode *pc; + JSOp op; + uint32 argc; + int stackDummy; + + if (!JS_CHECK_STACK_SIZE(cx, stackDummy)) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_OVER_RECURSED); + return JS_FALSE; + } + + ok = JS_TRUE; + cg->emitLevel++; + pn->pn_offset = top = CG_OFFSET(cg); + + /* Emit notes to tell the current bytecode's source line number. */ + UPDATE_LINE_NUMBER_NOTES(cx, cg, pn); + + switch (pn->pn_type) { + case TOK_FUNCTION: + { + void *cg2mark; + JSCodeGenerator *cg2; + JSFunction *fun; + + /* Generate code for the function's body. */ + cg2mark = JS_ARENA_MARK(&cx->tempPool); + JS_ARENA_ALLOCATE_TYPE(cg2, JSCodeGenerator, &cx->tempPool); + if (!cg2) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + if (!js_InitCodeGenerator(cx, cg2, cg->codePool, cg->notePool, + cg->filename, pn->pn_pos.begin.lineno, + cg->principals)) { + return JS_FALSE; + } + cg2->treeContext.flags = pn->pn_flags | TCF_IN_FUNCTION; + cg2->treeContext.tryCount = pn->pn_tryCount; + fun = (JSFunction *) JS_GetPrivate(cx, ATOM_TO_OBJECT(pn->pn_funAtom)); + if (!js_EmitFunctionBody(cx, cg2, pn->pn_body, fun)) + return JS_FALSE; + + /* + * We need an activation object if an inner peeks out, or if such + * inner-peeking caused one of our inners to become heavyweight. + */ + if (cg2->treeContext.flags & + (TCF_FUN_USES_NONLOCALS | TCF_FUN_HEAVYWEIGHT)) { + cg->treeContext.flags |= TCF_FUN_HEAVYWEIGHT; + } + js_FinishCodeGenerator(cx, cg2); + JS_ARENA_RELEASE(&cx->tempPool, cg2mark); + + /* Make the function object a literal in the outer script's pool. */ + ale = js_IndexAtom(cx, pn->pn_funAtom, &cg->atomList); + if (!ale) + return JS_FALSE; + atomIndex = ALE_INDEX(ale); + +#if JS_HAS_LEXICAL_CLOSURE + /* Emit a bytecode pointing to the closure object in its immediate. */ + if (pn->pn_op != JSOP_NOP) { + EMIT_ATOM_INDEX_OP(pn->pn_op, atomIndex); + break; + } +#endif + + /* Top-level named functions need a nop for decompilation. */ + noteIndex = js_NewSrcNote2(cx, cg, SRC_FUNCDEF, (ptrdiff_t)atomIndex); + if (noteIndex < 0 || + js_Emit1(cx, cg, JSOP_NOP) < 0) { + return JS_FALSE; + } + + /* + * Top-levels also need a prolog op to predefine their names in the + * variable object, or if local, to fill their stack slots. + */ + CG_SWITCH_TO_PROLOG(cg); + +#if JS_HAS_LEXICAL_CLOSURE + if (cg->treeContext.flags & TCF_IN_FUNCTION) { + JSObject *obj, *pobj; + JSScopeProperty *sprop; + uintN slot; + + obj = OBJ_GET_PARENT(cx, fun->object); + if (!js_LookupProperty(cx, obj, (jsid)fun->atom, &pobj, + (JSProperty **)&sprop)) { + return JS_FALSE; + } + JS_ASSERT(sprop && pobj == obj); + slot = sprop->shortid; + OBJ_DROP_PROPERTY(cx, pobj, (JSProperty *)sprop); + + /* Emit [JSOP_DEFLOCALFUN, local variable slot, atomIndex]. */ + off = js_EmitN(cx, cg, JSOP_DEFLOCALFUN, VARNO_LEN+ATOM_INDEX_LEN); + if (off < 0) + return JS_FALSE; + pc = CG_CODE(cg, off); + SET_VARNO(pc, slot); + pc += VARNO_LEN; + SET_ATOM_INDEX(pc, atomIndex); + } else +#endif + EMIT_ATOM_INDEX_OP(JSOP_DEFFUN, atomIndex); + + CG_SWITCH_TO_MAIN(cg); + break; + } + +#if JS_HAS_EXPORT_IMPORT + case TOK_EXPORT: + pn2 = pn->pn_head; + if (pn2->pn_type == TOK_STAR) { + /* + * 'export *' must have no other elements in the list (what would + * be the point?). + */ + if (js_Emit1(cx, cg, JSOP_EXPORTALL) < 0) + return JS_FALSE; + } else { + /* + * If not 'export *', the list consists of NAME nodes identifying + * properties of the variables object to flag as exported. + */ + do { + ale = js_IndexAtom(cx, pn2->pn_atom, &cg->atomList); + if (!ale) + return JS_FALSE; + EMIT_ATOM_INDEX_OP(JSOP_EXPORTNAME, ALE_INDEX(ale)); + } while ((pn2 = pn2->pn_next) != NULL); + } + break; + + case TOK_IMPORT: + for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) { + /* + * Each subtree on an import list is rooted by a DOT or LB node. + * A DOT may have a null pn_atom member, in which case pn_op must + * be JSOP_IMPORTALL -- see EmitPropOp above. + */ + if (!js_EmitTree(cx, cg, pn2)) + return JS_FALSE; + } + break; +#endif /* JS_HAS_EXPORT_IMPORT */ + + case TOK_IF: + /* Initialize so we can detect else-if chains and avoid recursion. */ + stmtInfo.type = STMT_IF; + beq = jmp = -1; + noteIndex = -1; + + if_again: + /* Emit code for the condition before pushing stmtInfo. */ + if (!js_EmitTree(cx, cg, pn->pn_kid1)) + return JS_FALSE; + if (stmtInfo.type == STMT_IF) { + js_PushStatement(&cg->treeContext, &stmtInfo, STMT_IF, + CG_OFFSET(cg)); + } else { + /* + * We came here from the goto further below that detects else-if + * chains, so we must mutate stmtInfo back into a STMT_IF record. + * Also (see below for why) we need a note offset for SRC_IF_ELSE + * to help the decompiler. + */ + JS_ASSERT(stmtInfo.type == STMT_ELSE); + stmtInfo.type = STMT_IF; + if (!js_SetSrcNoteOffset(cx, cg, noteIndex, 0, jmp - beq)) + return JS_FALSE; + } + + /* Emit an annotated branch-if-false around the then part. */ + pn3 = pn->pn_kid3; + noteIndex = js_NewSrcNote(cx, cg, pn3 ? SRC_IF_ELSE : SRC_IF); + if (noteIndex < 0) + return JS_FALSE; + beq = EmitJump(cx, cg, JSOP_IFEQ, 0); + if (beq < 0) + return JS_FALSE; + + /* Emit code for the then and optional else parts. */ + if (!js_EmitTree(cx, cg, pn->pn_kid2)) + return JS_FALSE; + if (pn3) { + /* Modify stmtInfo so we know we're in the else part. */ + stmtInfo.type = STMT_ELSE; + + /* + * Emit a JSOP_BACKPATCH op to jump from the end of our then part + * around the else part. The js_PopStatementCG call at the bottom + * of this switch case will fix up the backpatch chain linked from + * stmtInfo.breaks. + */ + jmp = EmitGoto(cx, cg, &stmtInfo, &stmtInfo.breaks, NULL, SRC_NULL); + if (jmp < 0) + return JS_FALSE; + + /* Ensure the branch-if-false comes here, then emit the else. */ + CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, beq); + if (pn3->pn_type == TOK_IF) { + pn = pn3; + goto if_again; + } + + if (!js_EmitTree(cx, cg, pn3)) + return JS_FALSE; + + /* + * Annotate SRC_IF_ELSE with the offset from branch to jump, for + * the decompiler's benefit. We can't just "back up" from the pc + * of the else clause, because we don't know whether an extended + * jump was required to leap from the end of the then clause over + * the else clause. + */ + if (!js_SetSrcNoteOffset(cx, cg, noteIndex, 0, jmp - beq)) + return JS_FALSE; + } else { + /* No else part, fixup the branch-if-false to come here. */ + CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, beq); + } + ok = js_PopStatementCG(cx, cg); + break; + +#if JS_HAS_SWITCH_STATEMENT + case TOK_SWITCH: + /* Out of line to avoid bloating js_EmitTree's stack frame size. */ + ok = EmitSwitch(cx, cg, pn, &stmtInfo); + break; +#endif /* JS_HAS_SWITCH_STATEMENT */ + + case TOK_WHILE: + js_PushStatement(&cg->treeContext, &stmtInfo, STMT_WHILE_LOOP, top); + if (!js_EmitTree(cx, cg, pn->pn_left)) + return JS_FALSE; + noteIndex = js_NewSrcNote(cx, cg, SRC_WHILE); + if (noteIndex < 0) + return JS_FALSE; + beq = EmitJump(cx, cg, JSOP_IFEQ, 0); + if (beq < 0) + return JS_FALSE; + if (!js_EmitTree(cx, cg, pn->pn_right)) + return JS_FALSE; + jmp = EmitJump(cx, cg, JSOP_GOTO, top - CG_OFFSET(cg)); + if (jmp < 0) + return JS_FALSE; + CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, beq); + if (!js_SetSrcNoteOffset(cx, cg, noteIndex, 0, jmp - beq)) + return JS_FALSE; + ok = js_PopStatementCG(cx, cg); + break; + +#if JS_HAS_DO_WHILE_LOOP + case TOK_DO: + /* Emit an annotated nop so we know to decompile a 'do' keyword. */ + if (js_NewSrcNote(cx, cg, SRC_WHILE) < 0 || + js_Emit1(cx, cg, JSOP_NOP) < 0) { + return JS_FALSE; + } + + /* Compile the loop body. */ + top = CG_OFFSET(cg); + js_PushStatement(&cg->treeContext, &stmtInfo, STMT_DO_LOOP, top); + if (!js_EmitTree(cx, cg, pn->pn_left)) + return JS_FALSE; + + /* Set loop and enclosing label update offsets, for continue. */ + stmt = &stmtInfo; + do { + stmt->update = CG_OFFSET(cg); + } while ((stmt = stmt->down) != NULL && stmt->type == STMT_LABEL); + + /* Compile the loop condition, now that continues know where to go. */ + if (!js_EmitTree(cx, cg, pn->pn_right)) + return JS_FALSE; + + /* + * No source note needed, because JSOP_IFNE is used only for do-while. + * If we ever use JSOP_IFNE for other purposes, we can still avoid yet + * another note here, by storing (jmp - top) in the SRC_WHILE note's + * offset, and fetching that delta in order to decompile recursively. + */ + if (EmitJump(cx, cg, JSOP_IFNE, top - CG_OFFSET(cg)) < 0) + return JS_FALSE; + ok = js_PopStatementCG(cx, cg); + break; +#endif /* JS_HAS_DO_WHILE_LOOP */ + + case TOK_FOR: + beq = 0; /* suppress gcc warnings */ + pn2 = pn->pn_left; + js_PushStatement(&cg->treeContext, &stmtInfo, STMT_FOR_LOOP, top); + + if (pn2->pn_type == TOK_IN) { + /* Set stmtInfo type for later testing. */ + stmtInfo.type = STMT_FOR_IN_LOOP; + noteIndex = -1; + + /* If the left part is var x = i, bind x, evaluate i, and pop. */ + pn3 = pn2->pn_left; + if (pn3->pn_type == TOK_VAR && pn3->pn_head->pn_expr) { + if (!js_EmitTree(cx, cg, pn3)) + return JS_FALSE; + /* Set pn3 to the variable name, to avoid another var note. */ + pn3 = pn3->pn_head; + JS_ASSERT(pn3->pn_type == TOK_NAME); + } + + /* Emit a push to allocate the iterator. */ + if (js_Emit1(cx, cg, JSOP_PUSH) < 0) + return JS_FALSE; + + /* Compile the object expression to the right of 'in'. */ + if (!js_EmitTree(cx, cg, pn2->pn_right)) + return JS_FALSE; + if (js_Emit1(cx, cg, JSOP_TOOBJECT) < 0) + return JS_FALSE; + + top = CG_OFFSET(cg); + SET_STATEMENT_TOP(&stmtInfo, top); + + /* Compile a JSOP_FOR* bytecode based on the left hand side. */ + switch (pn3->pn_type) { + case TOK_VAR: + pn3 = pn3->pn_head; + if (js_NewSrcNote(cx, cg, SRC_VAR) < 0) + return JS_FALSE; + /* FALL THROUGH */ + case TOK_NAME: + pn3->pn_op = JSOP_FORNAME; + if (!LookupArgOrVar(cx, &cg->treeContext, pn3)) + return JS_FALSE; + op = pn3->pn_op; + if (pn3->pn_slot >= 0) { + if (pn3->pn_attrs & JSPROP_READONLY) + op = JSOP_GETVAR; + atomIndex = (jsatomid) pn3->pn_slot; + EMIT_ATOM_INDEX_OP(op, atomIndex); + } else { + if (!EmitAtomOp(cx, pn3, op, cg)) + return JS_FALSE; + } + break; + + case TOK_DOT: + if (!EmitPropOp(cx, pn3, JSOP_FORPROP, cg)) + return JS_FALSE; + break; + + case TOK_LB: + /* + * We separate the first/next bytecode from the enumerator + * variable binding to avoid any side-effects in the index + * expression (e.g., for (x[i++] in {}) should not bind x[i] + * or increment i at all). + */ + if (!js_Emit1(cx, cg, JSOP_FORELEM)) + return JS_FALSE; + + /* + * Emit a SRC_WHILE note with offset telling the distance to + * the loop-closing jump (we can't reckon from the branch at + * the top of the loop, because the loop-closing jump might + * need to be an extended jump, independent of whether the + * branch is short or long). + */ + noteIndex = js_NewSrcNote(cx, cg, SRC_WHILE); + if (noteIndex < 0) + return JS_FALSE; + beq = EmitJump(cx, cg, JSOP_IFEQ, 0); + if (beq < 0) + return JS_FALSE; + + /* Now that we're safely past the IFEQ, commit side effects. */ + if (!EmitElemOp(cx, pn3, JSOP_ENUMELEM, cg)) + return JS_FALSE; + break; + + default: + JS_ASSERT(0); + } + if (pn3->pn_type != TOK_LB) { + /* Annotate so the decompiler can find the loop-closing jump. */ + noteIndex = js_NewSrcNote(cx, cg, SRC_WHILE); + if (noteIndex < 0) + return JS_FALSE; + + /* Pop and test the loop condition generated by JSOP_FOR*. */ + beq = EmitJump(cx, cg, JSOP_IFEQ, 0); + if (beq < 0) + return JS_FALSE; + } + } else { + if (!pn2->pn_kid1) { + /* No initializer: emit an annotated nop for the decompiler. */ + op = JSOP_NOP; + } else { + if (!js_EmitTree(cx, cg, pn2->pn_kid1)) + return JS_FALSE; + op = JSOP_POP; + } + noteIndex = js_NewSrcNote(cx, cg, SRC_FOR); + if (noteIndex < 0 || + js_Emit1(cx, cg, op) < 0) { + return JS_FALSE; + } + + top = CG_OFFSET(cg); + SET_STATEMENT_TOP(&stmtInfo, top); + if (!pn2->pn_kid2) { + /* No loop condition: flag this fact in the source notes. */ + if (!js_SetSrcNoteOffset(cx, cg, (uintN)noteIndex, 0, 0)) + return JS_FALSE; + } else { + if (!js_EmitTree(cx, cg, pn2->pn_kid2)) + return JS_FALSE; + if (!js_SetSrcNoteOffset(cx, cg, (uintN)noteIndex, 0, + CG_OFFSET(cg) - top)) { + return JS_FALSE; + } + beq = EmitJump(cx, cg, JSOP_IFEQ, 0); + if (beq < 0) + return JS_FALSE; + } + + /* Set pn3 (used below) here to avoid spurious gcc warnings. */ + pn3 = pn2->pn_kid3; + } + + /* Emit code for the loop body. */ + if (!js_EmitTree(cx, cg, pn->pn_right)) + return JS_FALSE; + + if (pn2->pn_type != TOK_IN) { + /* Set the second note offset so we can find the update part. */ + JS_ASSERT(noteIndex != -1); + if (!js_SetSrcNoteOffset(cx, cg, (uintN)noteIndex, 1, + CG_OFFSET(cg) - top)) { + return JS_FALSE; + } + + if (pn3) { + /* Set loop and enclosing "update" offsets, for continue. */ + stmt = &stmtInfo; + do { + stmt->update = CG_OFFSET(cg); + } while ((stmt = stmt->down) != NULL && + stmt->type == STMT_LABEL); + + if (!js_EmitTree(cx, cg, pn3)) + return JS_FALSE; + if (js_Emit1(cx, cg, JSOP_POP) < 0) + return JS_FALSE; + + /* Restore the absolute line number for source note readers. */ + off = (ptrdiff_t) pn->pn_pos.end.lineno; + if (CG_CURRENT_LINE(cg) != (uintN) off) { + if (js_NewSrcNote2(cx, cg, SRC_SETLINE, off) < 0) + return JS_FALSE; + CG_CURRENT_LINE(cg) = (uintN) off; + } + } + + /* The third note offset helps us find the loop-closing jump. */ + if (!js_SetSrcNoteOffset(cx, cg, (uintN)noteIndex, 2, + CG_OFFSET(cg) - top)) { + return JS_FALSE; + } + } + + /* Emit the loop-closing jump and fixup all jump offsets. */ + jmp = EmitJump(cx, cg, JSOP_GOTO, top - CG_OFFSET(cg)); + if (jmp < 0) + return JS_FALSE; + if (beq > 0) + CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, beq); + if (pn2->pn_type == TOK_IN) { + /* Set the SRC_WHILE note offset so we can find the closing jump. */ + JS_ASSERT(noteIndex != -1); + if (!js_SetSrcNoteOffset(cx, cg, (uintN)noteIndex, 0, jmp - beq)) + return JS_FALSE; + } + + /* Now fixup all breaks and continues (before for/in's final POP2). */ + if (!js_PopStatementCG(cx, cg)) + return JS_FALSE; + + if (pn2->pn_type == TOK_IN) { + /* + * Generate the object and iterator pop opcodes after popping the + * stmtInfo stack, so breaks will go to this pop bytecode. + */ + if (pn3->pn_type != TOK_LB) { + if (js_Emit1(cx, cg, JSOP_POP2) < 0) + return JS_FALSE; + } else { + /* + * With 'for(x[i]...)', there's only the object on the stack, + * so we need to hide the pop. + */ + if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0) + return JS_FALSE; + if (js_Emit1(cx, cg, JSOP_POP) < 0) + return JS_FALSE; + } + } + break; + + case TOK_BREAK: + stmt = cg->treeContext.topStmt; + atom = pn->pn_atom; + if (atom) { + ale = js_IndexAtom(cx, atom, &cg->atomList); + if (!ale) + return JS_FALSE; + while (stmt->type != STMT_LABEL || stmt->label != atom) + stmt = stmt->down; + noteType = SRC_BREAK2LABEL; + } else { + ale = NULL; + while (!STMT_IS_LOOP(stmt) && stmt->type != STMT_SWITCH) + stmt = stmt->down; + noteType = SRC_NULL; + } + + if (EmitGoto(cx, cg, stmt, &stmt->breaks, ale, noteType) < 0) + return JS_FALSE; + break; + + case TOK_CONTINUE: + stmt = cg->treeContext.topStmt; + atom = pn->pn_atom; + if (atom) { + /* Find the loop statement enclosed by the matching label. */ + JSStmtInfo *loop = NULL; + ale = js_IndexAtom(cx, atom, &cg->atomList); + if (!ale) + return JS_FALSE; + while (stmt->type != STMT_LABEL || stmt->label != atom) { + if (STMT_IS_LOOP(stmt)) + loop = stmt; + stmt = stmt->down; + } + stmt = loop; + noteType = SRC_CONT2LABEL; + } else { + ale = NULL; + while (!STMT_IS_LOOP(stmt)) + stmt = stmt->down; + noteType = SRC_CONTINUE; + } + + if (EmitGoto(cx, cg, stmt, &stmt->continues, ale, noteType) < 0) + return JS_FALSE; + break; + + case TOK_WITH: + if (!js_EmitTree(cx, cg, pn->pn_left)) + return JS_FALSE; + js_PushStatement(&cg->treeContext, &stmtInfo, STMT_WITH, CG_OFFSET(cg)); + if (js_Emit1(cx, cg, JSOP_ENTERWITH) < 0) + return JS_FALSE; + if (!js_EmitTree(cx, cg, pn->pn_right)) + return JS_FALSE; + if (js_Emit1(cx, cg, JSOP_LEAVEWITH) < 0) + return JS_FALSE; + ok = js_PopStatementCG(cx, cg); + break; + +#if JS_HAS_EXCEPTIONS + + case TOK_TRY: + { + ptrdiff_t start, end, catchStart, finallyCatch, catchJump; + JSParseNode *iter; + intN depth; + + /* Quell GCC overwarnings. */ + end = catchStart = finallyCatch = catchJump = -1; + +/* Emit JSOP_GOTO that points to the first op after the catch/finally blocks */ +#define EMIT_CATCH_GOTO(cx, cg, jmp) \ + EMIT_BACKPATCH_OP(cx, cg, stmtInfo.catchJump, JSOP_BACKPATCH, jmp) + +/* Emit JSOP_GOSUB that points to the finally block. */ +#define EMIT_FINALLY_GOSUB(cx, cg, jmp) \ + EMIT_BACKPATCH_OP(cx, cg, stmtInfo.gosub, JSOP_BACKPATCH_PUSH, jmp) + + /* + * Push stmtInfo to track jumps-over-catches and gosubs-to-finally + * for later fixup. + * + * When a finally block is `active' (STMT_FINALLY on the treeContext), + * non-local jumps (including jumps-over-catches) result in a GOSUB + * being written into the bytecode stream and fixed-up later (c.f. + * EMIT_BACKPATCH_OP and BackPatch). + */ + js_PushStatement(&cg->treeContext, &stmtInfo, + pn->pn_kid3 ? STMT_FINALLY : STMT_BLOCK, + CG_OFFSET(cg)); + + /* + * About JSOP_SETSP: an exception can be thrown while the stack is in + * an unbalanced state, and this imbalance causes problems with things + * like function invocation later on. + * + * To fix this, we compute the `balanced' stack depth upon try entry, + * and then restore the stack to this depth when we hit the first catch + * or finally block. We can't just zero the stack, because things like + * for/in and with that are active upon entry to the block keep state + * variables on the stack. + */ + depth = cg->stackDepth; + + /* Mark try location for decompilation, then emit try block. */ + if (js_Emit1(cx, cg, JSOP_TRY) < 0) + return JS_FALSE; + start = CG_OFFSET(cg); + if (!js_EmitTree(cx, cg, pn->pn_kid1)) + return JS_FALSE; + + /* GOSUB to finally, if present. */ + if (pn->pn_kid3) { + if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0) + return JS_FALSE; + EMIT_FINALLY_GOSUB(cx, cg, jmp); + if (jmp < 0) + return JS_FALSE; + } + + /* Emit (hidden) jump over catch and/or finally. */ + if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0) + return JS_FALSE; + EMIT_CATCH_GOTO(cx, cg, jmp); + if (jmp < 0) + return JS_FALSE; + + end = CG_OFFSET(cg); + + /* If this try has a catch block, emit it. */ + iter = pn->pn_kid2; + if (iter) { + catchStart = end; + + /* + * The emitted code for a catch block looks like: + * + * [ popscope ] only if 2nd+ catch block + * name Object + * pushobj + * newinit + * exception + * initcatchvar + * enterwith + * [< catchguard code >] if there's a catchguard + * [ifeq ] " " + * < catch block contents > + * leavewith + * goto non-local; finally applies + * + * If there's no catch block without a catchguard, the last + * points to rethrow code. This + * code will GOSUB to the finally code if appropriate, and is + * also used for the catch-all trynote for capturing exceptions + * thrown from catch{} blocks. + */ + for (;;) { + JSStmtInfo stmtInfo2; + JSParseNode *disc; + ptrdiff_t guardnote; + + if (!UpdateLineNumberNotes(cx, cg, iter)) + return JS_FALSE; + + if (catchJump != -1) { + /* Fix up and clean up previous catch block. */ + CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, catchJump); + + /* Compensate for the [leavewith]. */ + cg->stackDepth++; + JS_ASSERT((uintN) cg->stackDepth <= cg->maxStackDepth); + + if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0 || + js_Emit1(cx, cg, JSOP_LEAVEWITH) < 0) { + return JS_FALSE; + } + } else { + /* Set stack to original depth (see SETSP comment above). */ + EMIT_ATOM_INDEX_OP(JSOP_SETSP, (jsatomid)depth); + cg->stackDepth = depth; + } + + /* Non-negative guardnote offset is length of catchguard. */ + guardnote = js_NewSrcNote2(cx, cg, SRC_CATCH, 0); + if (guardnote < 0 || + js_Emit1(cx, cg, JSOP_NOP) < 0) { + return JS_FALSE; + } + + /* Construct the scope holder and push it on. */ + ale = js_IndexAtom(cx, cx->runtime->atomState.ObjectAtom, + &cg->atomList); + if (!ale) + return JS_FALSE; + EMIT_ATOM_INDEX_OP(JSOP_NAME, ALE_INDEX(ale)); + + if (js_Emit1(cx, cg, JSOP_PUSHOBJ) < 0 || + js_Emit1(cx, cg, JSOP_NEWINIT) < 0 || + js_Emit1(cx, cg, JSOP_EXCEPTION) < 0) { + return JS_FALSE; + } + + /* initcatchvar */ + disc = iter->pn_kid1; + ale = js_IndexAtom(cx, disc->pn_atom, &cg->atomList); + if (!ale) + return JS_FALSE; + + EMIT_ATOM_INDEX_OP(JSOP_INITCATCHVAR, ALE_INDEX(ale)); + if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0 || + js_Emit1(cx, cg, JSOP_ENTERWITH) < 0) { + return JS_FALSE; + } + + /* boolean_expr */ + if (disc->pn_expr) { + ptrdiff_t guardstart = CG_OFFSET(cg); + if (!js_EmitTree(cx, cg, disc->pn_expr)) + return JS_FALSE; + if (!js_SetSrcNoteOffset(cx, cg, guardnote, 0, + CG_OFFSET(cg) - guardstart)) { + return JS_FALSE; + } + /* ifeq */ + catchJump = EmitJump(cx, cg, JSOP_IFEQ, 0); + if (catchJump < 0) + return JS_FALSE; + } + + /* Emit catch block. */ + js_PushStatement(&cg->treeContext, &stmtInfo2, STMT_CATCH, + CG_OFFSET(cg)); + stmtInfo2.label = disc->pn_atom; + if (!js_EmitTree(cx, cg, iter->pn_kid3)) + return JS_FALSE; + js_PopStatementCG(cx, cg); + + /* + * Jump over the remaining catch blocks. + * This counts as a non-local jump, so do the finally thing. + */ + + /* popscope */ + if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0 || + js_Emit1(cx, cg, JSOP_LEAVEWITH) < 0) { + return JS_FALSE; + } + + /* gosub , if required */ + if (pn->pn_kid3) { + EMIT_FINALLY_GOSUB(cx, cg, jmp); + if (jmp < 0) + return JS_FALSE; + } + + /* This will get fixed up to jump to after catch/finally. */ + if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0) + return JS_FALSE; + EMIT_CATCH_GOTO(cx, cg, jmp); + if (jmp < 0) + return JS_FALSE; + if (!iter->pn_kid2) /* leave iter at last catch */ + break; + iter = iter->pn_kid2; + } + } + + /* + * We use a [leavewith],[gosub],rethrow block for rethrowing + * when there's no unguarded catch, and also for running finally + * code while letting an uncaught exception pass through. + */ + if (pn->pn_kid3 || + (catchJump != -1 && iter->pn_kid1->pn_expr)) { + /* + * Emit another stack fixup, because the catch could itself + * throw an exception in an unbalanced state, and the finally + * may need to call functions. If there is no finally, only + * guarded catches, the rethrow code below nevertheless needs + * stack fixup. + */ + finallyCatch = CG_OFFSET(cg); + EMIT_ATOM_INDEX_OP(JSOP_SETSP, (jsatomid)depth); + cg->stackDepth = depth; + + /* Last discriminant jumps to rethrow if none match. */ + if (catchJump != -1 && iter->pn_kid1->pn_expr) { + CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, catchJump); + + /* Compensate for the [leavewith]. */ + cg->stackDepth++; + JS_ASSERT((uintN) cg->stackDepth <= cg->maxStackDepth); + + if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0 || + js_Emit1(cx, cg, JSOP_LEAVEWITH) < 0) { + return JS_FALSE; + } + } + + if (pn->pn_kid3) { + EMIT_FINALLY_GOSUB(cx, cg, jmp); + if (jmp < 0) + return JS_FALSE; + cg->stackDepth = depth; + } + + if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0 || + js_Emit1(cx, cg, JSOP_EXCEPTION) < 0 || + js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0 || + js_Emit1(cx, cg, JSOP_THROW) < 0) { + return JS_FALSE; + } + JS_ASSERT(cg->stackDepth == depth); + } + + /* + * If we have a finally, it belongs here, and we have to fix up the + * gosubs that might have been emitted before non-local jumps. + */ + if (pn->pn_kid3) { + if (!BackPatch(cx, cg, stmtInfo.gosub, CG_NEXT(cg), JSOP_GOSUB)) + return JS_FALSE; + + /* + * The stack budget must be balanced at this point, and we need + * one more slot for the JSOP_RETSUB return address pushed by a + * JSOP_GOSUB opcode that calls this finally clause. + */ + JS_ASSERT(cg->stackDepth == depth); + if ((uintN)++cg->stackDepth > cg->maxStackDepth) + cg->maxStackDepth = cg->stackDepth; + + /* Now indicate that we're emitting a subroutine body. */ + stmtInfo.type = STMT_SUBROUTINE; + if (!UpdateLineNumberNotes(cx, cg, pn->pn_kid3)) + return JS_FALSE; + if (js_Emit1(cx, cg, JSOP_FINALLY) < 0 || + !js_EmitTree(cx, cg, pn->pn_kid3) || + js_Emit1(cx, cg, JSOP_RETSUB) < 0) { + return JS_FALSE; + } + } + js_PopStatementCG(cx, cg); + + if (js_NewSrcNote(cx, cg, SRC_ENDBRACE) < 0 || + js_Emit1(cx, cg, JSOP_NOP) < 0) { + return JS_FALSE; + } + + /* Fix up the end-of-try/catch jumps to come here. */ + if (!BackPatch(cx, cg, stmtInfo.catchJump, CG_NEXT(cg), JSOP_GOTO)) + return JS_FALSE; + + /* + * Add the try note last, to let post-order give us the right ordering + * (first to last for a given nesting level, inner to outer by level). + */ + if (pn->pn_kid2) { + JS_ASSERT(end != -1 && catchStart != -1); + if (!js_NewTryNote(cx, cg, start, end, catchStart)) + return JS_FALSE; + } + + /* + * If we've got a finally, mark try+catch region with additional + * trynote to catch exceptions (re)thrown from a catch block or + * for the try{}finally{} case. + */ + if (pn->pn_kid3) { + JS_ASSERT(finallyCatch != -1); + if (!js_NewTryNote(cx, cg, start, finallyCatch, finallyCatch)) + return JS_FALSE; + } + break; + } + +#endif /* JS_HAS_EXCEPTIONS */ + + case TOK_VAR: + off = noteIndex = -1; + for (pn2 = pn->pn_head; ; pn2 = pn2->pn_next) { + JS_ASSERT(pn2->pn_type == TOK_NAME); + if (!LookupArgOrVar(cx, &cg->treeContext, pn2)) + return JS_FALSE; + op = pn2->pn_op; + if (op == JSOP_ARGUMENTS) { + JS_ASSERT(!pn2->pn_expr); /* JSOP_ARGUMENTS => no initializer */ +#ifdef __GNUC__ + atomIndex = 0; /* quell GCC overwarning */ +#endif + } else { + if (pn2->pn_slot >= 0) { + atomIndex = (jsatomid) pn2->pn_slot; + } else { + ale = js_IndexAtom(cx, pn2->pn_atom, &cg->atomList); + if (!ale) + return JS_FALSE; + atomIndex = ALE_INDEX(ale); + + if (!(cg->treeContext.flags & TCF_IN_FUNCTION) || + (cg->treeContext.flags & TCF_FUN_HEAVYWEIGHT)) { + /* Emit a prolog bytecode to predefine the variable. */ + CG_SWITCH_TO_PROLOG(cg); + if (!UpdateLineNumberNotes(cx, cg, pn2)) + return JS_FALSE; + EMIT_ATOM_INDEX_OP(pn->pn_op, atomIndex); + CG_SWITCH_TO_MAIN(cg); + } + } + if (pn2->pn_expr) { + if (op == JSOP_SETNAME) + EMIT_ATOM_INDEX_OP(JSOP_BINDNAME, atomIndex); + if (!js_EmitTree(cx, cg, pn2->pn_expr)) + return JS_FALSE; + } + } + if (pn2 == pn->pn_head && + js_NewSrcNote(cx, cg, + (pn->pn_op == JSOP_DEFCONST) + ? SRC_CONST + : SRC_VAR) < 0) { + return JS_FALSE; + } + if (op == JSOP_ARGUMENTS) { + if (js_Emit1(cx, cg, op) < 0) + return JS_FALSE; + } else { + EMIT_ATOM_INDEX_OP(op, atomIndex); + } + tmp = CG_OFFSET(cg); + if (noteIndex >= 0) { + if (!js_SetSrcNoteOffset(cx, cg, (uintN)noteIndex, 0, tmp-off)) + return JS_FALSE; + } + if (!pn2->pn_next) + break; + off = tmp; + noteIndex = js_NewSrcNote2(cx, cg, SRC_PCDELTA, 0); + if (noteIndex < 0 || + js_Emit1(cx, cg, JSOP_POP) < 0) { + return JS_FALSE; + } + } + if (pn->pn_extra) { + if (js_Emit1(cx, cg, JSOP_POP) < 0) + return JS_FALSE; + } + break; + + case TOK_RETURN: + /* Push a return value */ + pn2 = pn->pn_kid; + if (pn2) { + if (!js_EmitTree(cx, cg, pn2)) + return JS_FALSE; + } else { + if (js_Emit1(cx, cg, JSOP_PUSH) < 0) + return JS_FALSE; + } + + /* + * EmitNonLocalJumpFixup mutates op to JSOP_RETRVAL after emitting a + * JSOP_SETRVAL if there are open try blocks having finally clauses. + * We can't simply transfer control flow to our caller in that case, + * because we must gosub to those clauses from inner to outer, with + * the correct stack pointer (i.e., after popping any with, for/in, + * etc., slots nested inside the finally's try). + */ + op = JSOP_RETURN; + if (!EmitNonLocalJumpFixup(cx, cg, NULL, &op)) + return JS_FALSE; + if (js_Emit1(cx, cg, op) < 0) + return JS_FALSE; + break; + + case TOK_LC: + js_PushStatement(&cg->treeContext, &stmtInfo, STMT_BLOCK, top); + for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) { + if (!js_EmitTree(cx, cg, pn2)) + return JS_FALSE; + } + ok = js_PopStatementCG(cx, cg); + break; + + case TOK_SEMI: + pn2 = pn->pn_kid; + if (pn2) { + /* + * Top-level or called-from-a-native JS_Execute/EvaluateScript, + * debugger, and eval frames may need the value of the ultimate + * expression statement as the script's result, despite the fact + * that it appears useless to the compiler. + */ + useful = wantval = !cx->fp->fun || + cx->fp->fun->native || + (cx->fp->flags & JSFRAME_SPECIAL); + if (!useful) { + if (!CheckSideEffects(cx, &cg->treeContext, pn2, &useful)) + return JS_FALSE; + } + if (!useful) { + CG_CURRENT_LINE(cg) = pn2->pn_pos.begin.lineno; + if (!js_ReportCompileErrorNumber(cx, NULL, cg, + JSREPORT_WARNING | + JSREPORT_STRICT, + JSMSG_USELESS_EXPR)) { + return JS_FALSE; + } + } else { + if (!js_EmitTree(cx, cg, pn2)) + return JS_FALSE; + if (js_Emit1(cx, cg, wantval ? JSOP_POPV : JSOP_POP) < 0) + return JS_FALSE; + } + } + break; + + case TOK_COLON: + /* Emit an annotated nop so we know to decompile a label. */ + atom = pn->pn_atom; + ale = js_IndexAtom(cx, atom, &cg->atomList); + if (!ale) + return JS_FALSE; + pn2 = pn->pn_expr; + noteIndex = js_NewSrcNote2(cx, cg, + (pn2->pn_type == TOK_LC) + ? SRC_LABELBRACE + : SRC_LABEL, + (ptrdiff_t) ALE_INDEX(ale)); + if (noteIndex < 0 || + js_Emit1(cx, cg, JSOP_NOP) < 0) { + return JS_FALSE; + } + + /* Emit code for the labeled statement. */ + js_PushStatement(&cg->treeContext, &stmtInfo, STMT_LABEL, + CG_OFFSET(cg)); + stmtInfo.label = atom; + if (!js_EmitTree(cx, cg, pn2)) + return JS_FALSE; + if (!js_PopStatementCG(cx, cg)) + return JS_FALSE; + + /* If the statement was compound, emit a note for the end brace. */ + if (pn2->pn_type == TOK_LC) { + if (js_NewSrcNote(cx, cg, SRC_ENDBRACE) < 0 || + js_Emit1(cx, cg, JSOP_NOP) < 0) { + return JS_FALSE; + } + } + break; + + case TOK_COMMA: + /* + * Emit SRC_PCDELTA notes on each JSOP_POP between comma operands. + * These notes help the decompiler bracket the bytecodes generated + * from each sub-expression that follows a comma. + */ + off = noteIndex = -1; + for (pn2 = pn->pn_head; ; pn2 = pn2->pn_next) { + if (!js_EmitTree(cx, cg, pn2)) + return JS_FALSE; + tmp = CG_OFFSET(cg); + if (noteIndex >= 0) { + if (!js_SetSrcNoteOffset(cx, cg, (uintN)noteIndex, 0, tmp-off)) + return JS_FALSE; + } + if (!pn2->pn_next) + break; + off = tmp; + noteIndex = js_NewSrcNote2(cx, cg, SRC_PCDELTA, 0); + if (noteIndex < 0 || + js_Emit1(cx, cg, JSOP_POP) < 0) { + return JS_FALSE; + } + } + break; + + case TOK_ASSIGN: + /* + * Check left operand type and generate specialized code for it. + * Specialize to avoid ECMA "reference type" values on the operand + * stack, which impose pervasive runtime "GetValue" costs. + */ + pn2 = pn->pn_left; + JS_ASSERT(pn2->pn_type != TOK_RP); + atomIndex = (jsatomid) -1; /* Suppress warning. */ + switch (pn2->pn_type) { + case TOK_NAME: + if (!LookupArgOrVar(cx, &cg->treeContext, pn2)) + return JS_FALSE; + if (pn2->pn_slot >= 0) { + atomIndex = (jsatomid) pn2->pn_slot; + } else { + ale = js_IndexAtom(cx, pn2->pn_atom, &cg->atomList); + if (!ale) + return JS_FALSE; + atomIndex = ALE_INDEX(ale); + EMIT_ATOM_INDEX_OP(JSOP_BINDNAME, atomIndex); + } + break; + case TOK_DOT: + if (!js_EmitTree(cx, cg, pn2->pn_expr)) + return JS_FALSE; + ale = js_IndexAtom(cx, pn2->pn_atom, &cg->atomList); + if (!ale) + return JS_FALSE; + atomIndex = ALE_INDEX(ale); + break; + case TOK_LB: + JS_ASSERT(pn->pn_arity == PN_BINARY); + if (!js_EmitTree(cx, cg, pn2->pn_left)) + return JS_FALSE; + if (!js_EmitTree(cx, cg, pn2->pn_right)) + return JS_FALSE; + break; +#if JS_HAS_LVALUE_RETURN + case TOK_LP: + if (!js_EmitTree(cx, cg, pn2)) + return JS_FALSE; + break; +#endif + default: + JS_ASSERT(0); + } + + op = pn->pn_op; +#if JS_HAS_GETTER_SETTER + if (op == JSOP_GETTER || op == JSOP_SETTER) { + /* We'll emit these prefix bytecodes after emitting the r.h.s. */ + } else +#endif + /* If += or similar, dup the left operand and get its value. */ + if (op != JSOP_NOP) { + switch (pn2->pn_type) { + case TOK_NAME: + if (pn2->pn_op != JSOP_SETNAME) { + EMIT_ATOM_INDEX_OP((pn2->pn_op == JSOP_SETARG) + ? JSOP_GETARG + : JSOP_GETVAR, + atomIndex); + break; + } + /* FALL THROUGH */ + case TOK_DOT: + if (js_Emit1(cx, cg, JSOP_DUP) < 0) + return JS_FALSE; + EMIT_ATOM_INDEX_OP(JSOP_GETPROP, atomIndex); + break; + case TOK_LB: +#if JS_HAS_LVALUE_RETURN + case TOK_LP: +#endif + if (js_Emit1(cx, cg, JSOP_DUP2) < 0) + return JS_FALSE; + if (js_Emit1(cx, cg, JSOP_GETELEM) < 0) + return JS_FALSE; + break; + default:; + } + } + + /* Now emit the right operand (it may affect the namespace). */ + if (!js_EmitTree(cx, cg, pn->pn_right)) + return JS_FALSE; + + /* If += etc., emit the binary operator with a decompiler note. */ + if (op != JSOP_NOP) { + if (js_NewSrcNote(cx, cg, SRC_ASSIGNOP) < 0 || + js_Emit1(cx, cg, op) < 0) { + return JS_FALSE; + } + } + + /* Left parts such as a.b.c and a[b].c need a decompiler note. */ + if (pn2->pn_type != TOK_NAME) { + if (js_NewSrcNote2(cx, cg, SRC_PCBASE, CG_OFFSET(cg) - top) < 0) + return JS_FALSE; + } + + /* Finally, emit the specialized assignment bytecode. */ + switch (pn2->pn_type) { + case TOK_NAME: + if (pn2->pn_slot < 0 || !(pn2->pn_attrs & JSPROP_READONLY)) { + case TOK_DOT: + EMIT_ATOM_INDEX_OP(pn2->pn_op, atomIndex); + } + break; + case TOK_LB: +#if JS_HAS_LVALUE_RETURN + case TOK_LP: +#endif + if (js_Emit1(cx, cg, JSOP_SETELEM) < 0) + return JS_FALSE; + break; + default:; + } + break; + + case TOK_HOOK: + /* Emit the condition, then branch if false to the else part. */ + if (!js_EmitTree(cx, cg, pn->pn_kid1)) + return JS_FALSE; + noteIndex = js_NewSrcNote(cx, cg, SRC_COND); + if (noteIndex < 0) + return JS_FALSE; + beq = EmitJump(cx, cg, JSOP_IFEQ, 0); + if (beq < 0 || !js_EmitTree(cx, cg, pn->pn_kid2)) + return JS_FALSE; + + /* Jump around else, fixup the branch, emit else, fixup jump. */ + jmp = EmitJump(cx, cg, JSOP_GOTO, 0); + if (jmp < 0) + return JS_FALSE; + CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, beq); + if (!js_EmitTree(cx, cg, pn->pn_kid3)) + return JS_FALSE; + CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, jmp); + if (!js_SetSrcNoteOffset(cx, cg, noteIndex, 0, jmp - beq)) + return JS_FALSE; + + /* + * Because each branch pushes a single value, but our stack budgeting + * analysis ignores branches, we now have two values accounted for in + * cg->stackDepth. Execution will follow only one path, so we must + * decrement cg->stackDepth here. Failing to do this will foil code, + * such as the try/catch/finally exception handling code generator, + * that samples cg->stackDepth for use at runtime (JSOP_SETSP). + */ + JS_ASSERT(cg->stackDepth > 1); + cg->stackDepth--; + break; + + case TOK_OR: + case TOK_AND: + /* + * JSOP_OR converts the operand on the stack to boolean, and if true, + * leaves the original operand value on the stack and jumps; otherwise + * it pops and falls into the next bytecode, which evaluates the right + * operand. The jump goes around the right operand evaluation. + * + * JSOP_AND converts the operand on the stack to boolean, and if false, + * leaves the original operand value on the stack and jumps; otherwise + * it pops and falls into the right operand's bytecode. + * + * Avoid tail recursion for long ||...|| expressions and long &&...&& + * expressions or long mixtures of ||'s and &&'s that can easily blow + * the stack, by forward-linking and then backpatching all the JSOP_OR + * and JSOP_AND bytecodes' immediate jump-offset operands. + */ + pn3 = pn; + if (!js_EmitTree(cx, cg, pn->pn_left)) + return JS_FALSE; + top = EmitJump(cx, cg, JSOP_BACKPATCH_POP, 0); + if (top < 0) + return JS_FALSE; + jmp = top; + pn2 = pn->pn_right; + while (pn2->pn_type == TOK_OR || pn2->pn_type == TOK_AND) { + pn = pn2; + if (!js_EmitTree(cx, cg, pn->pn_left)) + return JS_FALSE; + off = EmitJump(cx, cg, JSOP_BACKPATCH_POP, 0); + if (off < 0) + return JS_FALSE; + if (!SetBackPatchDelta(cx, cg, CG_CODE(cg, jmp), off - jmp)) + return JS_FALSE; + jmp = off; + pn2 = pn->pn_right; + } + if (!js_EmitTree(cx, cg, pn2)) + return JS_FALSE; + off = CG_OFFSET(cg); + do { + pc = CG_CODE(cg, top); + tmp = GetJumpOffset(cg, pc); + CHECK_AND_SET_JUMP_OFFSET(cx, cg, pc, off - top); + *pc = pn3->pn_op; + top += tmp; + } while ((pn3 = pn3->pn_right) != pn2); + break; + + case TOK_BITOR: + case TOK_BITXOR: + case TOK_BITAND: + case TOK_EQOP: + case TOK_RELOP: +#if JS_HAS_IN_OPERATOR + case TOK_IN: +#endif +#if JS_HAS_INSTANCEOF + case TOK_INSTANCEOF: +#endif + case TOK_SHOP: + case TOK_PLUS: + case TOK_MINUS: + case TOK_STAR: + case TOK_DIVOP: + if (pn->pn_arity == PN_LIST) { + /* Left-associative operator chain: avoid too much recursion. */ + pn2 = pn->pn_head; + if (!js_EmitTree(cx, cg, pn2)) + return JS_FALSE; + op = pn->pn_op; + while ((pn2 = pn2->pn_next) != NULL) { + if (!js_EmitTree(cx, cg, pn2)) + return JS_FALSE; + if (js_Emit1(cx, cg, op) < 0) + return JS_FALSE; + } + } else { + /* Binary operators that evaluate both operands unconditionally. */ + if (!js_EmitTree(cx, cg, pn->pn_left)) + return JS_FALSE; + if (!js_EmitTree(cx, cg, pn->pn_right)) + return JS_FALSE; + if (js_Emit1(cx, cg, pn->pn_op) < 0) + return JS_FALSE; + } + break; + +#if JS_HAS_EXCEPTIONS + case TOK_THROW: +#endif + case TOK_UNARYOP: + /* Unary op, including unary +/-. */ + if (!js_EmitTree(cx, cg, pn->pn_kid)) + return JS_FALSE; + if (js_Emit1(cx, cg, pn->pn_op) < 0) + return JS_FALSE; + break; + + case TOK_INC: + case TOK_DEC: + /* Emit lvalue-specialized code for ++/-- operators. */ + pn2 = pn->pn_kid; + JS_ASSERT(pn2->pn_type != TOK_RP); + op = pn->pn_op; + switch (pn2->pn_type) { + case TOK_NAME: + pn2->pn_op = op; + if (!LookupArgOrVar(cx, &cg->treeContext, pn2)) + return JS_FALSE; + op = pn2->pn_op; + if (pn2->pn_slot >= 0) { + if (pn2->pn_attrs & JSPROP_READONLY) + op = JSOP_GETVAR; + atomIndex = (jsatomid) pn2->pn_slot; + EMIT_ATOM_INDEX_OP(op, atomIndex); + } else { + if (!EmitAtomOp(cx, pn2, op, cg)) + return JS_FALSE; + } + break; + case TOK_DOT: + if (!EmitPropOp(cx, pn2, op, cg)) + return JS_FALSE; + break; + case TOK_LB: + if (!EmitElemOp(cx, pn2, op, cg)) + return JS_FALSE; + break; +#if JS_HAS_LVALUE_RETURN + case TOK_LP: + if (!js_EmitTree(cx, cg, pn2)) + return JS_FALSE; + if (js_NewSrcNote2(cx, cg, SRC_PCBASE, + CG_OFFSET(cg) - pn2->pn_offset) < 0) { + return JS_FALSE; + } + if (js_Emit1(cx, cg, op) < 0) + return JS_FALSE; + break; +#endif + default: + JS_ASSERT(0); + } + break; + + case TOK_DELETE: + /* Under ECMA 3, deleting a non-reference returns true. */ + pn2 = pn->pn_kid; + switch (pn2->pn_type) { + case TOK_NAME: + pn2->pn_op = JSOP_DELNAME; + if (!LookupArgOrVar(cx, &cg->treeContext, pn2)) + return JS_FALSE; + op = pn2->pn_op; + if (op == JSOP_FALSE) { + if (js_Emit1(cx, cg, op) < 0) + return JS_FALSE; + } else { + if (!EmitAtomOp(cx, pn2, op, cg)) + return JS_FALSE; + } + break; + case TOK_DOT: + if (!EmitPropOp(cx, pn2, JSOP_DELPROP, cg)) + return JS_FALSE; + break; + case TOK_LB: + if (!EmitElemOp(cx, pn2, JSOP_DELELEM, cg)) + return JS_FALSE; + break; + default: + if (js_Emit1(cx, cg, JSOP_TRUE) < 0) + return JS_FALSE; + } + break; + + case TOK_DOT: + /* + * Pop a stack operand, convert it to object, get a property named by + * this bytecode's immediate-indexed atom operand, and push its value + * (not a reference to it). This bytecode sets the virtual machine's + * "obj" register to the left operand's ToObject conversion result, + * for use by JSOP_PUSHOBJ. + */ + ok = EmitPropOp(cx, pn, pn->pn_op, cg); + break; + + case TOK_LB: + /* + * Pop two operands, convert the left one to object and the right one + * to property name (atom or tagged int), get the named property, and + * push its value. Set the "obj" register to the result of ToObject + * on the left operand. + */ + ok = EmitElemOp(cx, pn, pn->pn_op, cg); + break; + + case TOK_NEW: + case TOK_LP: + /* + * Emit function call or operator new (constructor call) code. + * First, emit code for the left operand to evaluate the callable or + * constructable object expression. + */ + pn2 = pn->pn_head; + if (!js_EmitTree(cx, cg, pn2)) + return JS_FALSE; + + /* Remember start of callable-object bytecode for decompilation hint. */ + off = pn2->pn_offset; + + /* + * Push the virtual machine's "obj" register, which was set by a name, + * property, or element get (or set) bytecode. + */ + if (js_Emit1(cx, cg, JSOP_PUSHOBJ) < 0) + return JS_FALSE; + + /* + * Emit code for each argument in order, then emit the JSOP_*CALL or + * JSOP_NEW bytecode with a two-byte immediate telling how many args + * were pushed on the operand stack. + */ + for (pn2 = pn2->pn_next; pn2; pn2 = pn2->pn_next) { + if (!js_EmitTree(cx, cg, pn2)) + return JS_FALSE; + } + if (js_NewSrcNote2(cx, cg, SRC_PCBASE, CG_OFFSET(cg) - off) < 0) + return JS_FALSE; + argc = pn->pn_count - 1; + if (js_Emit3(cx, cg, pn->pn_op, ARGC_HI(argc), ARGC_LO(argc)) < 0) + return JS_FALSE; + break; + +#if JS_HAS_INITIALIZERS + case TOK_RB: + /* + * Emit code for [a, b, c] of the form: + * t = new Array; t[0] = a; t[1] = b; t[2] = c; t; + * but use a stack slot for t and avoid dup'ing and popping it via + * the JSOP_NEWINIT and JSOP_INITELEM bytecodes. + */ + ale = js_IndexAtom(cx, cx->runtime->atomState.ArrayAtom, + &cg->atomList); + if (!ale) + return JS_FALSE; + EMIT_ATOM_INDEX_OP(JSOP_NAME, ALE_INDEX(ale)); + if (js_Emit1(cx, cg, JSOP_PUSHOBJ) < 0) + return JS_FALSE; + if (js_Emit1(cx, cg, JSOP_NEWINIT) < 0) + return JS_FALSE; + + pn2 = pn->pn_head; +#if JS_HAS_SHARP_VARS + if (pn2 && pn2->pn_type == TOK_DEFSHARP) { + EMIT_ATOM_INDEX_OP(JSOP_DEFSHARP, (jsatomid)pn2->pn_num); + pn2 = pn2->pn_next; + } +#endif + + for (atomIndex = 0; pn2; pn2 = pn2->pn_next) { + /* PrimaryExpr enforced ATOM_INDEX_LIMIT, so in-line optimize. */ + JS_ASSERT(atomIndex < ATOM_INDEX_LIMIT); + if (atomIndex == 0) { + if (js_Emit1(cx, cg, JSOP_ZERO) < 0) + return JS_FALSE; + } else if (atomIndex == 1) { + if (js_Emit1(cx, cg, JSOP_ONE) < 0) + return JS_FALSE; + } else { + EMIT_ATOM_INDEX_OP(JSOP_UINT16, (jsatomid)atomIndex); + } + + /* Sub-optimal: holes in a sparse initializer are void-filled. */ + if (pn2->pn_type == TOK_COMMA) { + if (js_Emit1(cx, cg, JSOP_PUSH) < 0) + return JS_FALSE; + } else { + if (!js_EmitTree(cx, cg, pn2)) + return JS_FALSE; + } + if (js_Emit1(cx, cg, JSOP_INITELEM) < 0) + return JS_FALSE; + + atomIndex++; + } + + if (pn->pn_extra) { + /* Emit a source note so we know to decompile an extra comma. */ + if (js_NewSrcNote(cx, cg, SRC_CONTINUE) < 0) + return JS_FALSE; + } + + /* Emit an op for sharp array cleanup and decompilation. */ + if (js_Emit1(cx, cg, JSOP_ENDINIT) < 0) + return JS_FALSE; + break; + + case TOK_RC: + /* + * Emit code for {p:a, '%q':b, 2:c} of the form: + * t = new Object; t.p = a; t['%q'] = b; t[2] = c; t; + * but use a stack slot for t and avoid dup'ing and popping it via + * the JSOP_NEWINIT and JSOP_INITELEM bytecodes. + */ + ale = js_IndexAtom(cx, cx->runtime->atomState.ObjectAtom, + &cg->atomList); + if (!ale) + return JS_FALSE; + EMIT_ATOM_INDEX_OP(JSOP_NAME, ALE_INDEX(ale)); + + if (js_Emit1(cx, cg, JSOP_PUSHOBJ) < 0) + return JS_FALSE; + if (js_Emit1(cx, cg, JSOP_NEWINIT) < 0) + return JS_FALSE; + + pn2 = pn->pn_head; +#if JS_HAS_SHARP_VARS + if (pn2 && pn2->pn_type == TOK_DEFSHARP) { + EMIT_ATOM_INDEX_OP(JSOP_DEFSHARP, (jsatomid)pn2->pn_num); + pn2 = pn2->pn_next; + } +#endif + + for (; pn2; pn2 = pn2->pn_next) { + /* Emit an index for t[2], else map an atom for t.p or t['%q']. */ + pn3 = pn2->pn_left; + switch (pn3->pn_type) { + case TOK_NUMBER: + if (!EmitNumberOp(cx, pn3->pn_dval, cg)) + return JS_FALSE; + break; + case TOK_NAME: + case TOK_STRING: + ale = js_IndexAtom(cx, pn3->pn_atom, &cg->atomList); + if (!ale) + return JS_FALSE; + break; + default: + JS_ASSERT(0); + } + + /* Emit code for the property initializer. */ + if (!js_EmitTree(cx, cg, pn2->pn_right)) + return JS_FALSE; + +#if JS_HAS_GETTER_SETTER + op = pn2->pn_op; + if (op == JSOP_GETTER || op == JSOP_SETTER) { + if (js_Emit1(cx, cg, op) < 0) + return JS_FALSE; + } +#endif + /* Annotate JSOP_INITELEM so we decompile 2:c and not just c. */ + if (pn3->pn_type == TOK_NUMBER) { + if (js_NewSrcNote(cx, cg, SRC_LABEL) < 0) + return JS_FALSE; + if (js_Emit1(cx, cg, JSOP_INITELEM) < 0) + return JS_FALSE; + } else { + EMIT_ATOM_INDEX_OP(JSOP_INITPROP, ALE_INDEX(ale)); + } + } + + /* Emit an op for sharpArray cleanup and decompilation. */ + if (js_Emit1(cx, cg, JSOP_ENDINIT) < 0) + return JS_FALSE; + break; + +#if JS_HAS_SHARP_VARS + case TOK_DEFSHARP: + if (!js_EmitTree(cx, cg, pn->pn_kid)) + return JS_FALSE; + EMIT_ATOM_INDEX_OP(JSOP_DEFSHARP, (jsatomid) pn->pn_num); + break; + + case TOK_USESHARP: + EMIT_ATOM_INDEX_OP(JSOP_USESHARP, (jsatomid) pn->pn_num); + break; +#endif /* JS_HAS_SHARP_VARS */ +#endif /* JS_HAS_INITIALIZERS */ + + case TOK_RP: + /* + * The node for (e) has e as its kid, enabling users who want to nest + * assignment expressions in conditions to avoid the error correction + * done by Condition (from x = y to x == y) by double-parenthesizing. + */ + if (!js_EmitTree(cx, cg, pn->pn_kid)) + return JS_FALSE; + if (js_Emit1(cx, cg, JSOP_GROUP) < 0) + return JS_FALSE; + break; + + case TOK_NAME: + if (!LookupArgOrVar(cx, &cg->treeContext, pn)) + return JS_FALSE; + op = pn->pn_op; + if (op == JSOP_ARGUMENTS) { + if (js_Emit1(cx, cg, op) < 0) + return JS_FALSE; + break; + } + if (pn->pn_slot >= 0) { + atomIndex = (jsatomid) pn->pn_slot; + EMIT_ATOM_INDEX_OP(op, atomIndex); + break; + } + /* FALL THROUGH */ + case TOK_STRING: + case TOK_OBJECT: + /* + * The scanner and parser associate JSOP_NAME with TOK_NAME, although + * other bytecodes may result instead (JSOP_BINDNAME/JSOP_SETNAME, + * JSOP_FORNAME, etc.). Among JSOP_*NAME* variants, only JSOP_NAME + * may generate the first operand of a call or new expression, so only + * it sets the "obj" virtual machine register to the object along the + * scope chain in which the name was found. + * + * Token types for STRING and OBJECT have corresponding bytecode ops + * in pn_op and emit the same format as NAME, so they share this code. + */ + ok = EmitAtomOp(cx, pn, pn->pn_op, cg); + break; + + case TOK_NUMBER: + ok = EmitNumberOp(cx, pn->pn_dval, cg); + break; + + case TOK_PRIMARY: + if (js_Emit1(cx, cg, pn->pn_op) < 0) + return JS_FALSE; + break; + +#if JS_HAS_DEBUGGER_KEYWORD + case TOK_DEBUGGER: + if (js_Emit1(cx, cg, JSOP_DEBUGGER) < 0) + return JS_FALSE; + break; +#endif /* JS_HAS_DEBUGGER_KEYWORD */ + + default: + JS_ASSERT(0); + } + + if (ok && --cg->emitLevel == 0 && cg->spanDeps) + ok = OptimizeSpanDeps(cx, cg); + + return ok; +} + +JS_FRIEND_DATA(JSSrcNoteSpec) js_SrcNoteSpec[] = { + {"null", 0, 0, 0}, + {"if", 0, 0, 0}, + {"if-else", 1, 0, 1}, + {"while", 1, 0, 1}, + {"for", 3, 1, 1}, + {"continue", 0, 0, 0}, + {"var", 0, 0, 0}, + {"pcdelta", 1, 0, 1}, + {"assignop", 0, 0, 0}, + {"cond", 1, 0, 1}, + {"reserved0", 0, 0, 0}, + {"hidden", 0, 0, 0}, + {"pcbase", 1, 0, -1}, + {"label", 1, 0, 0}, + {"labelbrace", 1, 0, 0}, + {"endbrace", 0, 0, 0}, + {"break2label", 1, 0, 0}, + {"cont2label", 1, 0, 0}, + {"switch", 2, 0, 1}, + {"funcdef", 1, 0, 0}, + {"catch", 1, 11, 1}, + {"const", 0, 0, 0}, + {"newline", 0, 0, 0}, + {"setline", 1, 0, 0}, + {"xdelta", 0, 0, 0}, +}; + +static intN +AllocSrcNote(JSContext *cx, JSCodeGenerator *cg) +{ + intN index; + JSArenaPool *pool; + size_t size; + + index = CG_NOTE_COUNT(cg); + if (((uintN)index & CG_NOTE_MASK(cg)) == 0) { + pool = cg->notePool; + size = SRCNOTE_SIZE(CG_NOTE_MASK(cg) + 1); + if (!CG_NOTES(cg)) { + /* Allocate the first note array lazily; leave noteMask alone. */ + JS_ARENA_ALLOCATE_CAST(CG_NOTES(cg), jssrcnote *, pool, size); + } else { + /* Grow by doubling note array size; update noteMask on success. */ + JS_ARENA_GROW_CAST(CG_NOTES(cg), jssrcnote *, pool, size, size); + if (CG_NOTES(cg)) + CG_NOTE_MASK(cg) = (CG_NOTE_MASK(cg) << 1) | 1; + } + if (!CG_NOTES(cg)) { + JS_ReportOutOfMemory(cx); + return -1; + } + } + + CG_NOTE_COUNT(cg) = index + 1; + return index; +} + +intN +js_NewSrcNote(JSContext *cx, JSCodeGenerator *cg, JSSrcNoteType type) +{ + intN index, n; + jssrcnote *sn; + ptrdiff_t offset, delta, xdelta; + + /* + * Claim a note slot in CG_NOTES(cg) by growing it if necessary and then + * incrementing CG_NOTE_COUNT(cg). + */ + index = AllocSrcNote(cx, cg); + if (index < 0) + return -1; + sn = &CG_NOTES(cg)[index]; + + /* + * Compute delta from the last annotated bytecode's offset. If it's too + * big to fit in sn, allocate one or more xdelta notes and reset sn. + */ + offset = CG_OFFSET(cg); + delta = offset - CG_LAST_NOTE_OFFSET(cg); + CG_LAST_NOTE_OFFSET(cg) = offset; + if (delta >= SN_DELTA_LIMIT) { + do { + xdelta = JS_MIN(delta, SN_XDELTA_MASK); + SN_MAKE_XDELTA(sn, xdelta); + delta -= xdelta; + index = AllocSrcNote(cx, cg); + if (index < 0) + return -1; + sn = &CG_NOTES(cg)[index]; + } while (delta >= SN_DELTA_LIMIT); + } + + /* + * Initialize type and delta, then allocate the minimum number of notes + * needed for type's arity. Usually, we won't need more, but if an offset + * does take two bytes, js_SetSrcNoteOffset will grow CG_NOTES(cg). + */ + SN_MAKE_NOTE(sn, type, delta); + for (n = (intN)js_SrcNoteSpec[type].arity; n > 0; n--) { + if (js_NewSrcNote(cx, cg, SRC_NULL) < 0) + return -1; + } + return index; +} + +intN +js_NewSrcNote2(JSContext *cx, JSCodeGenerator *cg, JSSrcNoteType type, + ptrdiff_t offset) +{ + intN index; + + index = js_NewSrcNote(cx, cg, type); + if (index >= 0) { + if (!js_SetSrcNoteOffset(cx, cg, index, 0, offset)) + return -1; + } + return index; +} + +intN +js_NewSrcNote3(JSContext *cx, JSCodeGenerator *cg, JSSrcNoteType type, + ptrdiff_t offset1, ptrdiff_t offset2) +{ + intN index; + + index = js_NewSrcNote(cx, cg, type); + if (index >= 0) { + if (!js_SetSrcNoteOffset(cx, cg, index, 0, offset1)) + return -1; + if (!js_SetSrcNoteOffset(cx, cg, index, 1, offset2)) + return -1; + } + return index; +} + +static JSBool +GrowSrcNotes(JSContext *cx, JSCodeGenerator *cg) +{ + JSArenaPool *pool; + size_t size; + + /* Grow by doubling note array size; update noteMask on success. */ + pool = cg->notePool; + size = SRCNOTE_SIZE(CG_NOTE_MASK(cg) + 1); + JS_ARENA_GROW_CAST(CG_NOTES(cg), jssrcnote *, pool, size, size); + if (!CG_NOTES(cg)) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + CG_NOTE_MASK(cg) = (CG_NOTE_MASK(cg) << 1) | 1; + return JS_TRUE; +} + +jssrcnote * +js_AddToSrcNoteDelta(JSContext *cx, JSCodeGenerator *cg, jssrcnote *sn, + ptrdiff_t delta) +{ + ptrdiff_t base, limit, newdelta, diff; + intN index; + + /* + * Called only from OptimizeSpanDeps and js_FinishTakingSrcNotes to add to + * main script note deltas, and only by a small positive amount. + */ + JS_ASSERT(cg->current == &cg->main); + JS_ASSERT((unsigned) delta < (unsigned) SN_XDELTA_LIMIT); + + base = SN_DELTA(sn); + limit = SN_IS_XDELTA(sn) ? SN_XDELTA_LIMIT : SN_DELTA_LIMIT; + newdelta = base + delta; + if (newdelta < limit) { + SN_SET_DELTA(sn, newdelta); + } else { + index = sn - cg->main.notes; + if ((cg->main.noteCount & cg->main.noteMask) == 0) { + if (!GrowSrcNotes(cx, cg)) + return NULL; + sn = cg->main.notes + index; + } + diff = cg->main.noteCount - index; + cg->main.noteCount++; + memmove(sn + 1, sn, SRCNOTE_SIZE(diff)); + SN_MAKE_XDELTA(sn, delta); + sn++; + } + return sn; +} + +uintN +js_SrcNoteLength(jssrcnote *sn) +{ + uintN arity; + jssrcnote *base; + + arity = (intN)js_SrcNoteSpec[SN_TYPE(sn)].arity; + for (base = sn++; arity; sn++, arity--) { + if (*sn & SN_3BYTE_OFFSET_FLAG) + sn += 2; + } + return sn - base; +} + +JS_FRIEND_API(ptrdiff_t) +js_GetSrcNoteOffset(jssrcnote *sn, uintN which) +{ + /* Find the offset numbered which (i.e., skip exactly which offsets). */ + JS_ASSERT(SN_TYPE(sn) != SRC_XDELTA); + JS_ASSERT(which < js_SrcNoteSpec[SN_TYPE(sn)].arity); + for (sn++; which; sn++, which--) { + if (*sn & SN_3BYTE_OFFSET_FLAG) + sn += 2; + } + if (*sn & SN_3BYTE_OFFSET_FLAG) { + return (ptrdiff_t)(((uint32)(sn[0] & SN_3BYTE_OFFSET_MASK) << 16) + | (sn[1] << 8) + | sn[2]); + } + return (ptrdiff_t)*sn; +} + +JSBool +js_SetSrcNoteOffset(JSContext *cx, JSCodeGenerator *cg, uintN index, + uintN which, ptrdiff_t offset) +{ + jssrcnote *sn; + ptrdiff_t diff; + + if ((jsuword)offset >= (jsuword)((ptrdiff_t)SN_3BYTE_OFFSET_FLAG << 16)) { + ReportStatementTooLarge(cx, cg); + return JS_FALSE; + } + + /* Find the offset numbered which (i.e., skip exactly which offsets). */ + sn = &CG_NOTES(cg)[index]; + JS_ASSERT(SN_TYPE(sn) != SRC_XDELTA); + JS_ASSERT(which < js_SrcNoteSpec[SN_TYPE(sn)].arity); + for (sn++; which; sn++, which--) { + if (*sn & SN_3BYTE_OFFSET_FLAG) + sn += 2; + } + + /* See if the new offset requires three bytes. */ + if (offset > (ptrdiff_t)SN_3BYTE_OFFSET_MASK) { + /* Maybe this offset was already set to a three-byte value. */ + if (!(*sn & SN_3BYTE_OFFSET_FLAG)) { + /* Losing, need to insert another two bytes for this offset. */ + index = PTRDIFF(sn, CG_NOTES(cg), jssrcnote); + + /* + * Simultaneously test to see if the source note array must grow to + * accomodate either the first or second byte of additional storage + * required by this 3-byte offset. + */ + if (((CG_NOTE_COUNT(cg) + 1) & CG_NOTE_MASK(cg)) <= 1) { + if (!GrowSrcNotes(cx, cg)) + return JS_FALSE; + sn = CG_NOTES(cg) + index; + } + CG_NOTE_COUNT(cg) += 2; + + diff = CG_NOTE_COUNT(cg) - (index + 3); + JS_ASSERT(diff >= 0); + if (diff > 0) + memmove(sn + 3, sn + 1, SRCNOTE_SIZE(diff)); + } + *sn++ = (jssrcnote)(SN_3BYTE_OFFSET_FLAG | (offset >> 16)); + *sn++ = (jssrcnote)(offset >> 8); + } + *sn = (jssrcnote)offset; + return JS_TRUE; +} + +#ifdef DEBUG_brendan +#define NBINS 10 +static uint32 hist[NBINS]; + +void DumpSrcNoteSizeHist() +{ + static FILE *fp; + int i, n; + + if (!fp) { + fp = fopen("/tmp/srcnotes.hist", "w"); + if (!fp) + return; + setlinebuf(fp); + } + fprintf(fp, "SrcNote size histogram:\n"); + for (i = 0; i < NBINS; i++) { + fprintf(fp, "%4u %4u ", JS_BIT(i), hist[i]); + for (n = (int) JS_HOWMANY(hist[i], 10); n > 0; --n) + fputc('*', fp); + fputc('\n', fp); + } + fputc('\n', fp); +} +#endif + +/* + * Fill in the storage at notes with prolog and main srcnotes; the space at + * notes was allocated using the CG_COUNT_FINAL_SRCNOTES macro from jsemit.h. + * SO DON'T CHANGE THIS FUNCTION WITHOUT AT LEAST CHECKING WHETHER jsemit.h's + * CG_COUNT_FINAL_SRCNOTES MACRO NEEDS CORRESPONDING CHANGES! + */ +JSBool +js_FinishTakingSrcNotes(JSContext *cx, JSCodeGenerator *cg, jssrcnote *notes) +{ + uintN prologCount, mainCount, totalCount; + ptrdiff_t offset, delta; + jssrcnote *sn; + + JS_ASSERT(cg->current == &cg->main); + + prologCount = cg->prolog.noteCount; + if (prologCount && cg->prolog.currentLine != cg->firstLine) { + CG_SWITCH_TO_PROLOG(cg); + if (js_NewSrcNote2(cx, cg, SRC_SETLINE, (ptrdiff_t)cg->firstLine) < 0) + return JS_FALSE; + prologCount = cg->prolog.noteCount; + CG_SWITCH_TO_MAIN(cg); + } else { + /* + * Either no prolog srcnotes, or no line number change over prolog. + * We don't need a SRC_SETLINE, but we may need to adjust the offset + * of the first main note, by adding to its delta and possibly even + * prepending SRC_XDELTA notes to it to account for prolog bytecodes + * that came at and after the last annotated bytecode. + */ + offset = CG_PROLOG_OFFSET(cg) - cg->prolog.lastNoteOffset; + JS_ASSERT(offset >= 0); + if (offset > 0) { + /* NB: Use as much of the first main note's delta as we can. */ + sn = cg->main.notes; + delta = SN_IS_XDELTA(sn) + ? SN_XDELTA_MASK - (*sn & SN_XDELTA_MASK) + : SN_DELTA_MASK - (*sn & SN_DELTA_MASK); + if (offset < delta) + delta = offset; + for (;;) { + if (!js_AddToSrcNoteDelta(cx, cg, sn, delta)) + return JS_FALSE; + offset -= delta; + if (offset == 0) + break; + delta = JS_MIN(offset, SN_XDELTA_MASK); + sn = cg->main.notes; + } + } + } + + mainCount = cg->main.noteCount; + totalCount = prologCount + mainCount; + if (prologCount) + memcpy(notes, cg->prolog.notes, SRCNOTE_SIZE(prologCount)); + memcpy(notes + prologCount, cg->main.notes, SRCNOTE_SIZE(mainCount)); + SN_MAKE_TERMINATOR(¬es[totalCount]); + +#ifdef DEBUG_brendan + { int bin = JS_CeilingLog2(totalCount); + if (bin >= NBINS) + bin = NBINS - 1; + ++hist[bin]; + } +#endif + return JS_TRUE; +} + +JSBool +js_AllocTryNotes(JSContext *cx, JSCodeGenerator *cg) +{ + size_t size, incr; + ptrdiff_t delta; + + size = TRYNOTE_SIZE(cg->treeContext.tryCount); + if (size <= cg->tryNoteSpace) + return JS_TRUE; + + /* + * Allocate trynotes from cx->tempPool. + * XXX Too much growing and we bloat, as other tempPool allocators block + * in-place growth, and we never recycle old free space in an arena. + * YYY But once we consume an entire arena, we'll realloc it, letting the + * malloc heap recycle old space, while still freeing _en masse_ via the + * arena pool. + */ + if (!cg->tryBase) { + size = JS_ROUNDUP(size, TRYNOTE_SIZE(TRYNOTE_CHUNK)); + JS_ARENA_ALLOCATE_CAST(cg->tryBase, JSTryNote *, &cx->tempPool, size); + if (!cg->tryBase) + return JS_FALSE; + cg->tryNoteSpace = size; + cg->tryNext = cg->tryBase; + } else { + delta = PTRDIFF((char *)cg->tryNext, (char *)cg->tryBase, char); + incr = size - cg->tryNoteSpace; + incr = JS_ROUNDUP(incr, TRYNOTE_SIZE(TRYNOTE_CHUNK)); + size = cg->tryNoteSpace; + JS_ARENA_GROW_CAST(cg->tryBase, JSTryNote *, &cx->tempPool, size, incr); + if (!cg->tryBase) + return JS_FALSE; + cg->tryNoteSpace = size + incr; + cg->tryNext = (JSTryNote *)((char *)cg->tryBase + delta); + } + return JS_TRUE; +} + +JSTryNote * +js_NewTryNote(JSContext *cx, JSCodeGenerator *cg, ptrdiff_t start, + ptrdiff_t end, ptrdiff_t catchStart) +{ + JSTryNote *tn; + + JS_ASSERT(cg->tryBase <= cg->tryNext); + JS_ASSERT(catchStart >= 0); + tn = cg->tryNext++; + tn->start = start; + tn->length = end - start; + tn->catchStart = catchStart; + return tn; +} + +void +js_FinishTakingTryNotes(JSContext *cx, JSCodeGenerator *cg, JSTryNote *notes) +{ + uintN count; + + count = PTRDIFF(cg->tryNext, cg->tryBase, JSTryNote); + if (!count) + return; + + memcpy(notes, cg->tryBase, TRYNOTE_SIZE(count)); + notes[count].start = 0; + notes[count].length = CG_OFFSET(cg); + notes[count].catchStart = 0; +} diff --git a/src/dom/js/jsemit.h b/src/dom/js/jsemit.h new file mode 100644 index 000000000..43b520c6e --- /dev/null +++ b/src/dom/js/jsemit.h @@ -0,0 +1,547 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsemit_h___ +#define jsemit_h___ +/* + * JS bytecode generation. + */ + +#include "jsstddef.h" +#include "jstypes.h" +#include "jsatom.h" +#include "jsopcode.h" +#include "jsprvtd.h" +#include "jspubtd.h" + +JS_BEGIN_EXTERN_C + +/* + * NB: If you add non-loop STMT_* enumerators, do so before STMT_DO_LOOP or + * you will break the STMT_IS_LOOP macro, just below this enum. + */ +typedef enum JSStmtType { + STMT_BLOCK = 0, /* compound statement: { s1[;... sN] } */ + STMT_LABEL = 1, /* labeled statement: L: s */ + STMT_IF = 2, /* if (then) statement */ + STMT_ELSE = 3, /* else clause of if statement */ + STMT_SWITCH = 4, /* switch statement */ + STMT_WITH = 5, /* with statement */ + STMT_TRY = 6, /* try statement */ + STMT_CATCH = 7, /* catch block */ + STMT_FINALLY = 8, /* finally statement */ + STMT_SUBROUTINE = 9, /* gosub-target subroutine body */ + STMT_DO_LOOP = 10, /* do/while loop statement */ + STMT_FOR_LOOP = 11, /* for loop statement */ + STMT_FOR_IN_LOOP = 12, /* for/in loop statement */ + STMT_WHILE_LOOP = 13 /* while loop statement */ +} JSStmtType; + +#define STMT_IS_LOOP(stmt) ((stmt)->type >= STMT_DO_LOOP) + +typedef struct JSStmtInfo JSStmtInfo; + +struct JSStmtInfo { + JSStmtType type; /* statement type */ + ptrdiff_t update; /* loop update offset (top if none) */ + ptrdiff_t breaks; /* offset of last break in loop */ + ptrdiff_t continues; /* offset of last continue in loop */ + ptrdiff_t gosub; /* offset of last GOSUB for this finally */ + ptrdiff_t catchJump; /* offset of last end-of-catch jump */ + JSAtom *label; /* name of LABEL or CATCH var */ + JSStmtInfo *down; /* info for enclosing statement */ +}; + +#define SET_STATEMENT_TOP(stmt, top) \ + ((stmt)->update = (top), (stmt)->breaks = \ + (stmt)->continues = (stmt)->catchJump = (stmt)->gosub = (-1)) + +struct JSTreeContext { /* tree context for semantic checks */ + uint32 flags; /* statement state flags, see below */ + uint32 tryCount; /* total count of try statements parsed */ + JSStmtInfo *topStmt; /* top of statement info stack */ + JSAtomList decls; /* function, const, and var declarations */ + JSParseNode *nodeList; /* list of recyclable parse-node structs */ +}; + +#define TCF_COMPILING 0x01 /* generating bytecode; this tc is a cg */ +#define TCF_IN_FUNCTION 0x02 /* parsing inside function body */ +#define TCF_RETURN_EXPR 0x04 /* function has 'return expr;' */ +#define TCF_RETURN_VOID 0x08 /* function has 'return;' */ +#define TCF_IN_FOR_INIT 0x10 /* parsing init expr of for; exclude 'in' */ +#define TCF_FUN_CLOSURE_VS_VAR 0x20 /* function and var with same name */ +#define TCF_FUN_USES_NONLOCALS 0x40 /* function refers to non-local names */ +#define TCF_FUN_HEAVYWEIGHT 0x80 /* function needs Call object per call */ +#define TCF_FUN_FLAGS 0xE0 /* flags to propagate from FunctionBody */ + +#define TREE_CONTEXT_INIT(tc) \ + ((tc)->flags = 0, (tc)->tryCount = 0, (tc)->topStmt = NULL, \ + ATOM_LIST_INIT(&(tc)->decls), (tc)->nodeList = NULL) + +#define TREE_CONTEXT_FINISH(tc) \ + ((void)0) + +/* + * Span-dependent instructions are jumps whose span (from the jump bytecode to + * the jump target) may require 2 or 4 bytes of immediate operand. + */ +typedef struct JSSpanDep JSSpanDep; +typedef struct JSJumpTarget JSJumpTarget; + +struct JSSpanDep { + ptrdiff_t top; /* offset of first bytecode in an opcode */ + ptrdiff_t offset; /* offset - 1 within opcode of jump operand */ + ptrdiff_t before; /* original offset - 1 of jump operand */ + JSJumpTarget *target; /* tagged target pointer or backpatch delta */ +}; + +/* + * Jump targets are stored in an AVL tree, for O(log(n)) lookup with targets + * sorted by offset from left to right, so that targets after a span-dependent + * instruction whose jump offset operand must be extended can be found quickly + * and adjusted upward (toward higher offsets). + */ +struct JSJumpTarget { + ptrdiff_t offset; /* offset of span-dependent jump target */ + int balance; /* AVL tree balance number */ + JSJumpTarget *kids[2]; /* left and right AVL tree child pointers */ +}; + +#define JT_LEFT 0 +#define JT_RIGHT 1 +#define JT_OTHER_DIR(dir) (1 - (dir)) +#define JT_IMBALANCE(dir) (((dir) << 1) - 1) +#define JT_DIR(imbalance) (((imbalance) + 1) >> 1) + +/* + * Backpatch deltas are encoded in JSSpanDep.target if JT_TAG_BIT is clear, + * so we can maintain backpatch chains when using span dependency records to + * hold jump offsets that overflow 16 bits. + */ +#define JT_TAG_BIT ((jsword) 1) +#define JT_UNTAG_SHIFT 1 +#define JT_SET_TAG(jt) ((JSJumpTarget *)((jsword)(jt) | JT_TAG_BIT)) +#define JT_CLR_TAG(jt) ((JSJumpTarget *)((jsword)(jt) & ~JT_TAG_BIT)) +#define JT_HAS_TAG(jt) ((jsword)(jt) & JT_TAG_BIT) + +#define BITS_PER_PTRDIFF (sizeof(ptrdiff_t) * JS_BITS_PER_BYTE) +#define BITS_PER_BPDELTA (BITS_PER_PTRDIFF - 1 - JT_UNTAG_SHIFT) +#define BPDELTA_MAX (((ptrdiff_t)1 << BITS_PER_BPDELTA) - 1) +#define BPDELTA_TO_JT(bp) ((JSJumpTarget *)((bp) << JT_UNTAG_SHIFT)) +#define JT_TO_BPDELTA(jt) ((ptrdiff_t)((jsword)(jt) >> JT_UNTAG_SHIFT)) + +#define SD_SET_TARGET(sd,jt) ((sd)->target = JT_SET_TAG(jt)) +#define SD_SET_BPDELTA(sd,bp) ((sd)->target = BPDELTA_TO_JT(bp)) +#define SD_GET_BPDELTA(sd) (JS_ASSERT(!JT_HAS_TAG((sd)->target)), \ + JT_TO_BPDELTA((sd)->target)) +#define SD_TARGET_OFFSET(sd) (JS_ASSERT(JT_HAS_TAG((sd)->target)), \ + JT_CLR_TAG((sd)->target)->offset) + +struct JSCodeGenerator { + JSTreeContext treeContext; /* base state: statement info stack, etc. */ + JSArenaPool *codePool; /* pointer to thread code arena pool */ + JSArenaPool *notePool; /* pointer to thread srcnote arena pool */ + void *codeMark; /* low watermark in cg->codePool */ + void *noteMark; /* low watermark in cg->notePool */ + void *tempMark; /* low watermark in cx->tempPool */ + struct { + jsbytecode *base; /* base of JS bytecode vector */ + jsbytecode *limit; /* one byte beyond end of bytecode */ + jsbytecode *next; /* pointer to next free bytecode */ + jssrcnote *notes; /* source notes, see below */ + uintN noteCount; /* number of source notes so far */ + uintN noteMask; /* growth increment for notes */ + ptrdiff_t lastNoteOffset; /* code offset for last source note */ + uintN currentLine; /* line number for tree-based srcnote gen */ + } prolog, main, *current; + const char *filename; /* null or weak link to source filename */ + uintN firstLine; /* first line, for js_NewScriptFromCG */ + JSPrincipals *principals; /* principals for constant folding eval */ + JSAtomList atomList; /* literals indexed for mapping */ + intN stackDepth; /* current stack depth in script frame */ + uintN maxStackDepth; /* maximum stack depth so far */ + JSTryNote *tryBase; /* first exception handling note */ + JSTryNote *tryNext; /* next available note */ + size_t tryNoteSpace; /* # of bytes allocated at tryBase */ + JSSpanDep *spanDeps; /* span dependent instruction records */ + JSJumpTarget *jumpTargets; /* AVL tree of jump target offsets */ + JSJumpTarget *jtFreeList; /* JT_LEFT-linked list of free structs */ + uintN numSpanDeps; /* number of span dependencies */ + uintN numJumpTargets; /* number of jump targets */ + uintN emitLevel; /* js_EmitTree recursion level */ +}; + +#define CG_BASE(cg) ((cg)->current->base) +#define CG_LIMIT(cg) ((cg)->current->limit) +#define CG_NEXT(cg) ((cg)->current->next) +#define CG_CODE(cg,offset) (CG_BASE(cg) + (offset)) +#define CG_OFFSET(cg) PTRDIFF(CG_NEXT(cg), CG_BASE(cg), jsbytecode) + +#define CG_NOTES(cg) ((cg)->current->notes) +#define CG_NOTE_COUNT(cg) ((cg)->current->noteCount) +#define CG_NOTE_MASK(cg) ((cg)->current->noteMask) +#define CG_LAST_NOTE_OFFSET(cg) ((cg)->current->lastNoteOffset) +#define CG_CURRENT_LINE(cg) ((cg)->current->currentLine) + +#define CG_PROLOG_BASE(cg) ((cg)->prolog.base) +#define CG_PROLOG_LIMIT(cg) ((cg)->prolog.limit) +#define CG_PROLOG_NEXT(cg) ((cg)->prolog.next) +#define CG_PROLOG_CODE(cg,poff) (CG_PROLOG_BASE(cg) + (poff)) +#define CG_PROLOG_OFFSET(cg) PTRDIFF(CG_PROLOG_NEXT(cg), CG_PROLOG_BASE(cg),\ + jsbytecode) + +#define CG_SWITCH_TO_MAIN(cg) ((cg)->current = &(cg)->main) +#define CG_SWITCH_TO_PROLOG(cg) ((cg)->current = &(cg)->prolog) + +/* + * Initialize cg to allocate bytecode space from codePool, source note space + * from notePool, and all other arena-allocated temporaries from cx->tempPool. + * Return true on success. Report an error and return false if the initial + * code segment can't be allocated. + */ +extern JS_FRIEND_API(JSBool) +js_InitCodeGenerator(JSContext *cx, JSCodeGenerator *cg, + JSArenaPool *codePool, JSArenaPool *notePool, + const char *filename, uintN lineno, + JSPrincipals *principals); + +/* + * Release cg->codePool, cg->notePool, and cx->tempPool to marks set by + * js_InitCodeGenerator. Note that cgs are magic: they own the arena pool + * "tops-of-stack" space above their codeMark, noteMark, and tempMark points. + * This means you cannot alloc from tempPool and save the pointer beyond the + * next JS_FinishCodeGenerator. + */ +extern JS_FRIEND_API(void) +js_FinishCodeGenerator(JSContext *cx, JSCodeGenerator *cg); + +/* + * Emit one bytecode. + */ +extern ptrdiff_t +js_Emit1(JSContext *cx, JSCodeGenerator *cg, JSOp op); + +/* + * Emit two bytecodes, an opcode (op) with a byte of immediate operand (op1). + */ +extern ptrdiff_t +js_Emit2(JSContext *cx, JSCodeGenerator *cg, JSOp op, jsbytecode op1); + +/* + * Emit three bytecodes, an opcode with two bytes of immediate operands. + */ +extern ptrdiff_t +js_Emit3(JSContext *cx, JSCodeGenerator *cg, JSOp op, jsbytecode op1, + jsbytecode op2); + +/* + * Emit (1 + extra) bytecodes, for N bytes of op and its immediate operand. + */ +extern ptrdiff_t +js_EmitN(JSContext *cx, JSCodeGenerator *cg, JSOp op, size_t extra); + +/* + * Unsafe macro to call js_SetJumpOffset and return false if it does. + */ +#define CHECK_AND_SET_JUMP_OFFSET(cx,cg,pc,off) \ + JS_BEGIN_MACRO \ + if (!js_SetJumpOffset(cx, cg, pc, off)) \ + return JS_FALSE; \ + JS_END_MACRO + +#define CHECK_AND_SET_JUMP_OFFSET_AT(cx,cg,off) \ + CHECK_AND_SET_JUMP_OFFSET(cx, cg, CG_CODE(cg,off), CG_OFFSET(cg) - (off)) + +extern JSBool +js_SetJumpOffset(JSContext *cx, JSCodeGenerator *cg, jsbytecode *pc, + ptrdiff_t off); + +/* Test whether we're in a with statement. */ +extern JSBool +js_InWithStatement(JSTreeContext *tc); + +/* Test whether we're in a catch block with exception named by atom. */ +extern JSBool +js_InCatchBlock(JSTreeContext *tc, JSAtom *atom); + +/* + * Push the C-stack-allocated struct at stmt onto the stmtInfo stack. + */ +extern void +js_PushStatement(JSTreeContext *tc, JSStmtInfo *stmt, JSStmtType type, + ptrdiff_t top); + +/* + * Pop tc->topStmt. If the top JSStmtInfo struct is not stack-allocated, it + * is up to the caller to free it. + */ +extern void +js_PopStatement(JSTreeContext *tc); + +/* + * Like js_PopStatement(&cg->treeContext), also patch breaks and continues. + * May fail if a jump offset overflows. + */ +extern JSBool +js_PopStatementCG(JSContext *cx, JSCodeGenerator *cg); + +/* + * Emit code into cg for the tree rooted at pn. + */ +extern JSBool +js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn); + +/* + * Emit code into cg for the tree rooted at body, then create a persistent + * script for fun from cg. + */ +extern JSBool +js_EmitFunctionBody(JSContext *cx, JSCodeGenerator *cg, JSParseNode *body, + JSFunction *fun); + +/* + * Source notes generated along with bytecode for decompiling and debugging. + * A source note is a uint8 with 5 bits of type and 3 of offset from the pc of + * the previous note. If 3 bits of offset aren't enough, extended delta notes + * (SRC_XDELTA) consisting of 2 set high order bits followed by 6 offset bits + * are emitted before the next note. Some notes have operand offsets encoded + * immediately after them, in note bytes or byte-triples. + * + * Source Note Extended Delta + * +7-6-5-4-3+2-1-0+ +7-6-5+4-3-2-1-0+ + * |note-type|delta| |1 1| ext-delta | + * +---------+-----+ +---+-----------+ + * + * At most one "gettable" note (i.e., a note of type other than SRC_NEWLINE, + * SRC_SETLINE, and SRC_XDELTA) applies to a given bytecode. + * + * NB: the js_SrcNoteSpec array in jsemit.c is indexed by this enum, so its + * initializers need to match the order here. + */ +typedef enum JSSrcNoteType { + SRC_NULL = 0, /* terminates a note vector */ + SRC_IF = 1, /* JSOP_IFEQ bytecode is from an if-then */ + SRC_IF_ELSE = 2, /* JSOP_IFEQ bytecode is from an if-then-else */ + SRC_WHILE = 3, /* JSOP_IFEQ is from a while loop */ + SRC_FOR = 4, /* JSOP_NOP or JSOP_POP in for loop head */ + SRC_CONTINUE = 5, /* JSOP_GOTO is a continue, not a break; + also used on JSOP_ENDINIT if extra comma + at end of array literal: [1,2,,] */ + SRC_VAR = 6, /* JSOP_NAME/SETNAME/FORNAME in a var decl */ + SRC_PCDELTA = 7, /* offset from comma-operator to next POP, + or from CONDSWITCH to first CASE opcode */ + SRC_ASSIGNOP = 8, /* += or another assign-op follows */ + SRC_COND = 9, /* JSOP_IFEQ is from conditional ?: operator */ + SRC_RESERVED0 = 10, /* reserved for future use */ + SRC_HIDDEN = 11, /* opcode shouldn't be decompiled */ + SRC_PCBASE = 12, /* offset of first obj.prop.subprop bytecode */ + SRC_LABEL = 13, /* JSOP_NOP for label: with atomid immediate */ + SRC_LABELBRACE = 14, /* JSOP_NOP for label: {...} begin brace */ + SRC_ENDBRACE = 15, /* JSOP_NOP for label: {...} end brace */ + SRC_BREAK2LABEL = 16, /* JSOP_GOTO for 'break label' with atomid */ + SRC_CONT2LABEL = 17, /* JSOP_GOTO for 'continue label' with atomid */ + SRC_SWITCH = 18, /* JSOP_*SWITCH with offset to end of switch, + 2nd off to first JSOP_CASE if condswitch */ + SRC_FUNCDEF = 19, /* JSOP_NOP for function f() with atomid */ + SRC_CATCH = 20, /* catch block has guard */ + SRC_CONST = 21, /* JSOP_SETCONST in a const decl */ + SRC_NEWLINE = 22, /* bytecode follows a source newline */ + SRC_SETLINE = 23, /* a file-absolute source line number note */ + SRC_XDELTA = 24 /* 24-31 are for extended delta notes */ +} JSSrcNoteType; + +#define SN_TYPE_BITS 5 +#define SN_DELTA_BITS 3 +#define SN_XDELTA_BITS 6 +#define SN_TYPE_MASK (JS_BITMASK(SN_TYPE_BITS) << SN_DELTA_BITS) +#define SN_DELTA_MASK ((ptrdiff_t)JS_BITMASK(SN_DELTA_BITS)) +#define SN_XDELTA_MASK ((ptrdiff_t)JS_BITMASK(SN_XDELTA_BITS)) + +#define SN_MAKE_NOTE(sn,t,d) (*(sn) = (jssrcnote) \ + (((t) << SN_DELTA_BITS) \ + | ((d) & SN_DELTA_MASK))) +#define SN_MAKE_XDELTA(sn,d) (*(sn) = (jssrcnote) \ + ((SRC_XDELTA << SN_DELTA_BITS) \ + | ((d) & SN_XDELTA_MASK))) + +#define SN_IS_XDELTA(sn) ((*(sn) >> SN_DELTA_BITS) >= SRC_XDELTA) +#define SN_TYPE(sn) (SN_IS_XDELTA(sn) ? SRC_XDELTA \ + : *(sn) >> SN_DELTA_BITS) +#define SN_SET_TYPE(sn,type) SN_MAKE_NOTE(sn, type, SN_DELTA(sn)) +#define SN_IS_GETTABLE(sn) (SN_TYPE(sn) < SRC_NEWLINE) + +#define SN_DELTA(sn) ((ptrdiff_t)(SN_IS_XDELTA(sn) \ + ? *(sn) & SN_XDELTA_MASK \ + : *(sn) & SN_DELTA_MASK)) +#define SN_SET_DELTA(sn,delta) (SN_IS_XDELTA(sn) \ + ? SN_MAKE_XDELTA(sn, delta) \ + : SN_MAKE_NOTE(sn, SN_TYPE(sn), delta)) + +#define SN_DELTA_LIMIT ((ptrdiff_t)JS_BIT(SN_DELTA_BITS)) +#define SN_XDELTA_LIMIT ((ptrdiff_t)JS_BIT(SN_XDELTA_BITS)) + +/* + * Offset fields follow certain notes and are frequency-encoded: an offset in + * [0,0x7f] consumes one byte, an offset in [0x80,0x7fffff] takes three, and + * the high bit of the first byte is set. + */ +#define SN_3BYTE_OFFSET_FLAG 0x80 +#define SN_3BYTE_OFFSET_MASK 0x7f + +typedef struct JSSrcNoteSpec { + const char *name; /* name for disassembly/debugging output */ + uint8 arity; /* number of offset operands */ + uint8 offsetBias; /* bias of offset(s) from annotated pc */ + int8 isSpanDep; /* 1 or -1 if offsets could span extended ops, + 0 otherwise; sign tells span direction */ +} JSSrcNoteSpec; + +extern JS_FRIEND_DATA(JSSrcNoteSpec) js_SrcNoteSpec[]; +extern JS_FRIEND_API(uintN) js_SrcNoteLength(jssrcnote *sn); + +#define SN_LENGTH(sn) ((js_SrcNoteSpec[SN_TYPE(sn)].arity == 0) ? 1 \ + : js_SrcNoteLength(sn)) +#define SN_NEXT(sn) ((sn) + SN_LENGTH(sn)) + +/* A source note array is terminated by an all-zero element. */ +#define SN_MAKE_TERMINATOR(sn) (*(sn) = SRC_NULL) +#define SN_IS_TERMINATOR(sn) (*(sn) == SRC_NULL) + +/* + * Append a new source note of the given type (and therefore size) to cg's + * notes dynamic array, updating cg->noteCount. Return the new note's index + * within the array pointed at by cg->current->notes. Return -1 if out of + * memory. + */ +extern intN +js_NewSrcNote(JSContext *cx, JSCodeGenerator *cg, JSSrcNoteType type); + +extern intN +js_NewSrcNote2(JSContext *cx, JSCodeGenerator *cg, JSSrcNoteType type, + ptrdiff_t offset); + +extern intN +js_NewSrcNote3(JSContext *cx, JSCodeGenerator *cg, JSSrcNoteType type, + ptrdiff_t offset1, ptrdiff_t offset2); + +/* + * NB: this function can add at most one extra extended delta note. + */ +extern jssrcnote * +js_AddToSrcNoteDelta(JSContext *cx, JSCodeGenerator *cg, jssrcnote *sn, + ptrdiff_t delta); + +/* + * Get and set the offset operand identified by which (0 for the first, etc.). + */ +extern JS_FRIEND_API(ptrdiff_t) +js_GetSrcNoteOffset(jssrcnote *sn, uintN which); + +extern JSBool +js_SetSrcNoteOffset(JSContext *cx, JSCodeGenerator *cg, uintN index, + uintN which, ptrdiff_t offset); + +/* + * Finish taking source notes in cx's notePool, copying final notes to the new + * stable store allocated by the caller and passed in via notes. Return false + * on malloc failure, which means this function reported an error. + * + * To compute the number of jssrcnotes to allocate and pass in via notes, use + * the CG_COUNT_FINAL_SRCNOTES macro. This macro knows a lot about details of + * js_FinishTakingSrcNotes, SO DON'T CHANGE jsemit.c's js_FinishTakingSrcNotes + * FUNCTION WITHOUT CHECKING WHETHER THIS MACRO NEEDS CORRESPONDING CHANGES! + */ +#define CG_COUNT_FINAL_SRCNOTES(cg, cnt) \ + JS_BEGIN_MACRO \ + ptrdiff_t diff_ = CG_PROLOG_OFFSET(cg) - (cg)->prolog.lastNoteOffset; \ + cnt = (cg)->prolog.noteCount + (cg)->main.noteCount + 1; \ + if ((cg)->prolog.noteCount && \ + (cg)->prolog.currentLine != (cg)->firstLine) { \ + if (diff_ > SN_DELTA_MASK) \ + cnt += JS_HOWMANY(diff_ - SN_DELTA_MASK, SN_XDELTA_MASK); \ + cnt += 2 + (((cg)->firstLine > SN_3BYTE_OFFSET_MASK) << 1); \ + } else if (diff_ > 0) { \ + if (cg->main.noteCount) { \ + jssrcnote *sn_ = (cg)->main.notes; \ + diff_ -= SN_IS_XDELTA(sn_) \ + ? SN_XDELTA_MASK - (*sn_ & SN_XDELTA_MASK) \ + : SN_DELTA_MASK - (*sn_ & SN_DELTA_MASK); \ + } \ + if (diff_ > 0) \ + cnt += JS_HOWMANY(diff_, SN_XDELTA_MASK); \ + } \ + JS_END_MACRO + +extern JSBool +js_FinishTakingSrcNotes(JSContext *cx, JSCodeGenerator *cg, jssrcnote *notes); + +/* + * Allocate cg->treeContext.tryCount notes (plus one for the end sentinel) + * from cx->tempPool and set up cg->tryBase/tryNext for exactly tryCount + * js_NewTryNote calls. The storage is freed by js_FinishCodeGenerator. + */ +extern JSBool +js_AllocTryNotes(JSContext *cx, JSCodeGenerator *cg); + +/* + * Grab the next trynote slot in cg, filling it in appropriately. + */ +extern JSTryNote * +js_NewTryNote(JSContext *cx, JSCodeGenerator *cg, ptrdiff_t start, + ptrdiff_t end, ptrdiff_t catchStart); + +/* + * Finish generating exception information into the space at notes. As with + * js_FinishTakingSrcNotes, the caller must use CG_COUNT_FINAL_TRYNOTES(cg) to + * preallocate enough space in a JSTryNote[] to pass as the notes parameter of + * js_FinishTakingTryNotes. + */ +#define CG_COUNT_FINAL_TRYNOTES(cg, cnt) \ + JS_BEGIN_MACRO \ + cnt = ((cg)->tryNext > (cg)->tryBase) \ + ? PTRDIFF(cg->tryNext, cg->tryBase, JSTryNote) + 1 \ + : 0; \ + JS_END_MACRO + +extern void +js_FinishTakingTryNotes(JSContext *cx, JSCodeGenerator *cg, JSTryNote *notes); + +JS_END_EXTERN_C + +#endif /* jsemit_h___ */ diff --git a/src/dom/js/jsexn.c b/src/dom/js/jsexn.c new file mode 100644 index 000000000..c407c1628 --- /dev/null +++ b/src/dom/js/jsexn.c @@ -0,0 +1,1081 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS standard exception implementation. + */ + +#include "jsstddef.h" +#include +#include +#include "jstypes.h" +#include "jsbit.h" +#include "jsutil.h" /* Added by JSIFY */ +#include "jsprf.h" +#include "jsapi.h" +#include "jscntxt.h" +#include "jsconfig.h" +#include "jsexn.h" +#include "jsfun.h" +#include "jsinterp.h" +#include "jsopcode.h" +#include "jsnum.h" +#include "jsscript.h" + +#if JS_HAS_ERROR_EXCEPTIONS +#if !JS_HAS_EXCEPTIONS +# error "JS_HAS_EXCEPTIONS must be defined to use JS_HAS_ERROR_EXCEPTIONS" +#endif + +/* XXX consider adding rt->atomState.messageAtom */ +static char js_message_str[] = "message"; +static char js_filename_str[] = "fileName"; +static char js_lineno_str[] = "lineNumber"; +static char js_stack_str[] = "stack"; + +/* Forward declarations for ExceptionClass's initializer. */ +static JSBool +Exception(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval); + +static void +exn_finalize(JSContext *cx, JSObject *obj); + +static JSClass ExceptionClass = { + "Error", + JSCLASS_HAS_PRIVATE, + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, exn_finalize, + NULL, NULL, NULL, Exception, + NULL, NULL, NULL, 0 +}; + +/* + * A copy of the JSErrorReport originally generated. + */ +typedef struct JSExnPrivate { + JSErrorReport *errorReport; +} JSExnPrivate; + +/* + * Undo all the damage done by exn_newPrivate. + */ +static void +exn_destroyPrivate(JSContext *cx, JSExnPrivate *privateData) +{ + JSErrorReport *report; + const jschar **args; + + if (!privateData) + return; + report = privateData->errorReport; + if (report) { + if (report->uclinebuf) + JS_free(cx, (void *)report->uclinebuf); + if (report->filename) + JS_free(cx, (void *)report->filename); + if (report->ucmessage) + JS_free(cx, (void *)report->ucmessage); + if (report->messageArgs) { + args = report->messageArgs; + while (*args != NULL) + JS_free(cx, (void *)*args++); + JS_free(cx, (void *)report->messageArgs); + } + JS_free(cx, report); + } + JS_free(cx, privateData); +} + +/* + * Copy everything interesting about an error into allocated memory. + */ +static JSExnPrivate * +exn_newPrivate(JSContext *cx, JSErrorReport *report) +{ + intN i; + JSExnPrivate *newPrivate; + JSErrorReport *newReport; + size_t capacity; + + newPrivate = (JSExnPrivate *)JS_malloc(cx, sizeof (JSExnPrivate)); + if (!newPrivate) + return NULL; + memset(newPrivate, 0, sizeof (JSExnPrivate)); + + /* Copy the error report */ + newReport = (JSErrorReport *)JS_malloc(cx, sizeof (JSErrorReport)); + if (!newReport) + goto error; + memset(newReport, 0, sizeof (JSErrorReport)); + newPrivate->errorReport = newReport; + + if (report->filename != NULL) { + newReport->filename = JS_strdup(cx, report->filename); + if (!newReport->filename) + goto error; + } else { + newReport->filename = NULL; + } + + newReport->lineno = report->lineno; + + /* + * We don't need to copy linebuf and tokenptr, because they + * point into the deflated string cache. (currently?) + */ + newReport->linebuf = report->linebuf; + newReport->tokenptr = report->tokenptr; + + /* + * But we do need to copy uclinebuf, uctokenptr, because they're + * pointers into internal tokenstream structs, and may go away. + */ + if (report->uclinebuf != NULL) { + capacity = js_strlen(report->uclinebuf) + 1; + newReport->uclinebuf = + (const jschar *)JS_malloc(cx, capacity * sizeof(jschar)); + if (!newReport->uclinebuf) + goto error; + js_strncpy((jschar *)newReport->uclinebuf, report->uclinebuf, capacity); + newReport->uctokenptr = newReport->uclinebuf + (report->uctokenptr - + report->uclinebuf); + } else { + newReport->uclinebuf = newReport->uctokenptr = NULL; + } + + if (report->ucmessage != NULL) { + capacity = js_strlen(report->ucmessage) + 1; + newReport->ucmessage = (const jschar *) + JS_malloc(cx, capacity * sizeof(jschar)); + if (!newReport->ucmessage) + goto error; + js_strncpy((jschar *)newReport->ucmessage, report->ucmessage, capacity); + + if (report->messageArgs) { + for (i = 0; report->messageArgs[i] != NULL; i++) + continue; + JS_ASSERT(i); + newReport->messageArgs = + (const jschar **)JS_malloc(cx, (i + 1) * sizeof(jschar *)); + if (!newReport->messageArgs) + goto error; + for (i = 0; report->messageArgs[i] != NULL; i++) { + capacity = js_strlen(report->messageArgs[i]) + 1; + newReport->messageArgs[i] = + (const jschar *)JS_malloc(cx, capacity * sizeof(jschar)); + if (!newReport->messageArgs[i]) + goto error; + js_strncpy((jschar *)(newReport->messageArgs[i]), + report->messageArgs[i], capacity); + } + newReport->messageArgs[i] = NULL; + } else { + newReport->messageArgs = NULL; + } + } else { + newReport->ucmessage = NULL; + newReport->messageArgs = NULL; + } + newReport->errorNumber = report->errorNumber; + + /* Note that this is before it gets flagged with JSREPORT_EXCEPTION */ + newReport->flags = report->flags; + + return newPrivate; +error: + exn_destroyPrivate(cx, newPrivate); + return NULL; +} + +static void +exn_finalize(JSContext *cx, JSObject *obj) +{ + JSExnPrivate *privateData; + jsval privateValue; + + privateValue = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE); + + if (!JSVAL_IS_VOID(privateValue)) { + privateData = (JSExnPrivate*) JSVAL_TO_PRIVATE(privateValue); + if (privateData) + exn_destroyPrivate(cx, privateData); + } +} + +JSErrorReport * +js_ErrorFromException(JSContext *cx, jsval exn) +{ + JSObject *obj; + JSExnPrivate *privateData; + jsval privateValue; + + if (JSVAL_IS_PRIMITIVE(exn)) + return NULL; + obj = JSVAL_TO_OBJECT(exn); + if (OBJ_GET_CLASS(cx, obj) != &ExceptionClass) + return NULL; + privateValue = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE); + if (JSVAL_IS_VOID(privateValue)) + return NULL; + privateData = (JSExnPrivate*) JSVAL_TO_PRIVATE(privateValue); + if (!privateData) + return NULL; + + JS_ASSERT(privateData->errorReport); + return privateData->errorReport; +} + +/* + * This must be kept in synch with the exceptions array below. + * XXX use a jsexn.tbl file a la jsopcode.tbl + */ +typedef enum JSExnType { + JSEXN_NONE = -1, + JSEXN_ERR, + JSEXN_INTERNALERR, + JSEXN_EVALERR, + JSEXN_RANGEERR, + JSEXN_REFERENCEERR, + JSEXN_SYNTAXERR, + JSEXN_TYPEERR, + JSEXN_URIERR, + JSEXN_LIMIT +} JSExnType; + +struct JSExnSpec { + int protoIndex; + const char *name; + JSNative native; +}; + +/* + * All *Error constructors share the same JSClass, ExceptionClass. But each + * constructor function for an *Error class must have a distinct native 'call' + * function pointer, in order for instanceof to work properly across multiple + * standard class sets. See jsfun.c:fun_hasInstance. + */ +#define MAKE_EXCEPTION_CTOR(name) \ +const char js_##name##_str[] = #name; \ +static JSBool \ +name(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) \ +{ \ + return Exception(cx, obj, argc, argv, rval); \ +} + +MAKE_EXCEPTION_CTOR(Error) +MAKE_EXCEPTION_CTOR(InternalError) +MAKE_EXCEPTION_CTOR(EvalError) +MAKE_EXCEPTION_CTOR(RangeError) +MAKE_EXCEPTION_CTOR(ReferenceError) +MAKE_EXCEPTION_CTOR(SyntaxError) +MAKE_EXCEPTION_CTOR(TypeError) +MAKE_EXCEPTION_CTOR(URIError) + +#undef MAKE_EXCEPTION_CTOR + +static struct JSExnSpec exceptions[] = { + { JSEXN_NONE, js_Error_str, Error }, + { JSEXN_ERR, js_InternalError_str, InternalError }, + { JSEXN_ERR, js_EvalError_str, EvalError }, + { JSEXN_ERR, js_RangeError_str, RangeError }, + { JSEXN_ERR, js_ReferenceError_str, ReferenceError }, + { JSEXN_ERR, js_SyntaxError_str, SyntaxError }, + { JSEXN_ERR, js_TypeError_str, TypeError }, + { JSEXN_ERR, js_URIError_str, URIError }, + {0,NULL,NULL} +}; + +static JSBool +InitExceptionObject(JSContext *cx, JSObject *obj, JSString *message, + JSString *filename, uintN lineno) +{ + JSCheckAccessOp checkAccess; + JSErrorReporter older; + JSExceptionState *state; + jschar *stackbuf; + size_t stacklen, stackmax; + JSStackFrame *fp; + jsval callerid, v; + JSBool ok; + JSString *argsrc, *stack; + uintN i, ulineno; + const char *cp; + char ulnbuf[11]; + + if (!JS_DefineProperty(cx, obj, js_message_str, STRING_TO_JSVAL(message), + NULL, NULL, JSPROP_ENUMERATE)) { + return JS_FALSE; + } + + if (!JS_DefineProperty(cx, obj, js_filename_str, + STRING_TO_JSVAL(filename), + NULL, NULL, JSPROP_ENUMERATE)) { + return JS_FALSE; + } + + if (!JS_DefineProperty(cx, obj, js_lineno_str, + INT_TO_JSVAL(lineno), + NULL, NULL, JSPROP_ENUMERATE)) { + return JS_FALSE; + } + + /* + * Set the 'stack' property. + * + * First, set aside any error reporter for cx and save its exception state + * so we can suppress any checkAccess failures. Such failures should stop + * the backtrace procedure, not result in a failure of this constructor. + */ + checkAccess = cx->runtime->checkObjectAccess; + if (checkAccess) { + older = JS_SetErrorReporter(cx, NULL); + state = JS_SaveExceptionState(cx); + } +#ifdef __GNUC__ /* suppress bogus gcc warnings */ + else { + older = NULL; + state = NULL; + } +#endif + callerid = ATOM_KEY(cx->runtime->atomState.callerAtom); + + /* + * Prepare to allocate a jschar buffer at stackbuf, where stacklen indexes + * the next free jschar slot, and with room for at most stackmax non-null + * jschars. If stackbuf is non-null, it always contains an extra slot for + * the null terminator we'll store at the end, as a backstop. + * + * All early returns must goto done after this point, till the after-loop + * cleanup code has run! + */ + stackbuf = NULL; + stacklen = stackmax = 0; + ok = JS_TRUE; + +#define APPEND_CHAR_TO_STACK(c) \ + JS_BEGIN_MACRO \ + if (stacklen == stackmax) { \ + void *ptr_; \ + stackmax = stackmax ? 2 * stackmax : 64; \ + ptr_ = JS_realloc(cx, stackbuf, (stackmax+1) * sizeof(jschar)); \ + if (!ptr_) { \ + ok = JS_FALSE; \ + goto done; \ + } \ + stackbuf = ptr_; \ + } \ + stackbuf[stacklen++] = (c); \ + JS_END_MACRO + +#define APPEND_STRING_TO_STACK(str) \ + JS_BEGIN_MACRO \ + JSString *str_ = str; \ + size_t length_ = JSSTRING_LENGTH(str_); \ + if (stacklen + length_ > stackmax) { \ + void *ptr_; \ + stackmax = JS_BIT(JS_CeilingLog2(stacklen + length_)); \ + ptr_ = JS_realloc(cx, stackbuf, (stackmax+1) * sizeof(jschar)); \ + if (!ptr_) { \ + ok = JS_FALSE; \ + goto done; \ + } \ + stackbuf = ptr_; \ + } \ + js_strncpy(stackbuf + stacklen, JSSTRING_CHARS(str_), length_); \ + stacklen += length_; \ + JS_END_MACRO + + for (fp = cx->fp; fp; fp = fp->down) { + if (checkAccess) { + v = (fp->fun && fp->argv) ? fp->argv[-2] : JSVAL_NULL; + if (!JSVAL_IS_PRIMITIVE(v)) { + ok = checkAccess(cx, fp->fun->object, callerid, JSACC_READ, &v); + if (!ok) { + ok = JS_TRUE; + break; + } + } + } + + if (fp->fun) { + if (fp->fun->atom) + APPEND_STRING_TO_STACK(ATOM_TO_STRING(fp->fun->atom)); + + APPEND_CHAR_TO_STACK('('); + for (i = 0; i < fp->argc; i++) { + /* Avoid toSource bloat and fallibility for object types. */ + v = fp->argv[i]; + if (JSVAL_IS_PRIMITIVE(v)) { + argsrc = js_ValueToSource(cx, v); + } else if (JSVAL_IS_FUNCTION(cx, v)) { + /* XXX Avoid function decompilation bloat for now. */ + argsrc = JS_GetFunctionId(JS_ValueToFunction(cx, v)); + if (!argsrc) + argsrc = js_ValueToSource(cx, v); + } else { + /* XXX Avoid toString on objects, it takes too long and + uses too much memory, for too many classes (see + Mozilla bug 166743). */ + char buf[100]; + JS_snprintf(buf, sizeof buf, "[object %s]", + OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(v))->name); + argsrc = JS_NewStringCopyZ(cx, buf); + } + if (!argsrc) { + ok = JS_FALSE; + goto done; + } + if (i > 0) + APPEND_CHAR_TO_STACK(','); + APPEND_STRING_TO_STACK(argsrc); + } + APPEND_CHAR_TO_STACK(')'); + } + + APPEND_CHAR_TO_STACK('@'); + if (fp->script && fp->script->filename) { + for (cp = fp->script->filename; *cp; cp++) + APPEND_CHAR_TO_STACK(*cp); + } + APPEND_CHAR_TO_STACK(':'); + if (fp->script && fp->pc) { + ulineno = js_PCToLineNumber(cx, fp->script, fp->pc); + JS_snprintf(ulnbuf, sizeof ulnbuf, "%u", ulineno); + for (cp = ulnbuf; *cp; cp++) + APPEND_CHAR_TO_STACK(*cp); + } else { + APPEND_CHAR_TO_STACK('0'); + } + APPEND_CHAR_TO_STACK('\n'); + } + +#undef APPEND_CHAR_TO_STACK +#undef APPEND_STRING_TO_STACK + +done: + if (checkAccess) { + if (ok) + JS_RestoreExceptionState(cx, state); + else + JS_DropExceptionState(cx, state); + JS_SetErrorReporter(cx, older); + } + if (!ok) { + JS_free(cx, stackbuf); + return JS_FALSE; + } + + if (!stackbuf) { + stack = cx->runtime->emptyString; + } else { + /* NB: if stackbuf was allocated, it has room for the terminator. */ + JS_ASSERT(stacklen <= stackmax); + if (stacklen < stackmax) { + /* + * Realloc can fail when shrinking on some FreeBSD versions, so + * don't use JS_realloc here; simply let the oversized allocation + * be owned by the string in that rare case. + */ + void *shrunk = realloc(stackbuf, (stacklen+1) * sizeof(jschar)); + if (shrunk) + stackbuf = shrunk; + } + stackbuf[stacklen] = 0; + stack = js_NewString(cx, stackbuf, stacklen, 0); + if (!stack) { + JS_free(cx, stackbuf); + return JS_FALSE; + } + } + return JS_DefineProperty(cx, obj, js_stack_str, + STRING_TO_JSVAL(stack), + NULL, NULL, JSPROP_ENUMERATE); +} + +static JSBool +Exception(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSBool ok; + jsval pval; + int32 lineno; + JSString *message, *filename; + + if (cx->creatingException) + return JS_FALSE; + cx->creatingException = JS_TRUE; + + if (!(cx->fp->flags & JSFRAME_CONSTRUCTING)) { + /* + * ECMA ed. 3, 15.11.1 requires Error, etc., to construct even when + * called as functions, without operator new. But as we do not give + * each constructor a distinct JSClass, whose .name member is used by + * js_NewObject to find the class prototype, we must get the class + * prototype ourselves. + */ + ok = OBJ_GET_PROPERTY(cx, JSVAL_TO_OBJECT(argv[-2]), + (jsid)cx->runtime->atomState.classPrototypeAtom, + &pval); + if (!ok) + goto out; + obj = js_NewObject(cx, &ExceptionClass, JSVAL_TO_OBJECT(pval), NULL); + if (!obj) { + ok = JS_FALSE; + goto out; + } + *rval = OBJECT_TO_JSVAL(obj); + } + + /* + * If it's a new object of class Exception, then null out the private + * data so that the finalizer doesn't attempt to free it. + */ + if (OBJ_GET_CLASS(cx, obj) == &ExceptionClass) + OBJ_SET_SLOT(cx, obj, JSSLOT_PRIVATE, JSVAL_VOID); + + /* Set the 'message' property. */ + if (argc != 0) { + message = js_ValueToString(cx, argv[0]); + if (!message) { + ok = JS_FALSE; + goto out; + } + } else { + message = cx->runtime->emptyString; + } + + /* Set the 'fileName' property. */ + if (argc > 1) { + filename = js_ValueToString(cx, argv[1]); + if (!filename) { + ok = JS_FALSE; + goto out; + } + } else { + filename = cx->runtime->emptyString; + } + + /* Set the 'lineNumber' property. */ + if (argc > 2) { + ok = js_ValueToInt32(cx, argv[2], &lineno); + if (!ok) + goto out; + } else { + lineno = 0; + } + + ok = InitExceptionObject(cx, obj, message, filename, lineno); + +out: + cx->creatingException = JS_FALSE; + return ok; +} + +/* + * Convert to string. + * + * This method only uses JavaScript-modifiable properties name, message. It + * is left to the host to check for private data and report filename and line + * number information along with this message. + */ +static JSBool +exn_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsval v; + JSString *name, *message, *result; + jschar *chars, *cp; + size_t name_length, message_length, length; + + if (!OBJ_GET_PROPERTY(cx, obj, (jsid)cx->runtime->atomState.nameAtom, &v)) + return JS_FALSE; + name = js_ValueToString(cx, v); + if (!name) + return JS_FALSE; + + if (!JS_GetProperty(cx, obj, js_message_str, &v) || + !(message = js_ValueToString(cx, v))) { + return JS_FALSE; + } + + if (JSSTRING_LENGTH(message) != 0) { + name_length = JSSTRING_LENGTH(name); + message_length = JSSTRING_LENGTH(message); + length = name_length + message_length + 2; + cp = chars = (jschar*) JS_malloc(cx, (length + 1) * sizeof(jschar)); + if (!chars) + return JS_FALSE; + + js_strncpy(cp, JSSTRING_CHARS(name), name_length); + cp += name_length; + *cp++ = ':'; *cp++ = ' '; + js_strncpy(cp, JSSTRING_CHARS(message), message_length); + cp += message_length; + *cp = 0; + + result = js_NewString(cx, chars, length, 0); + if (!result) { + JS_free(cx, chars); + return JS_FALSE; + } + } else { + result = name; + } + + *rval = STRING_TO_JSVAL(result); + return JS_TRUE; +} + +#if JS_HAS_TOSOURCE +/* + * Return a string that may eval to something similar to the original object. + */ +static JSBool +exn_toSource(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsval v; + JSString *name, *message, *filename, *lineno_as_str, *result; + int32 lineno; + size_t lineno_length, name_length, message_length, filename_length, length; + jschar *chars, *cp; + + if (!OBJ_GET_PROPERTY(cx, obj, (jsid)cx->runtime->atomState.nameAtom, &v)) + return JS_FALSE; + name = js_ValueToString(cx, v); + if (!name) + return JS_FALSE; + + if (!JS_GetProperty(cx, obj, js_message_str, &v) || + !(message = js_ValueToSource(cx, v))) { + return JS_FALSE; + } + + if (!JS_GetProperty(cx, obj, js_filename_str, &v) || + !(filename = js_ValueToSource(cx, v))) { + return JS_FALSE; + } + + if (!JS_GetProperty(cx, obj, js_lineno_str, &v) || + !js_ValueToInt32 (cx, v, &lineno)) { + return JS_FALSE; + } + + if (lineno != 0) { + if (!(lineno_as_str = js_ValueToString(cx, v))) { + return JS_FALSE; + } + lineno_length = JSSTRING_LENGTH(lineno_as_str); + } else { + lineno_as_str = NULL; + lineno_length = 0; + } + + /* Magic 8, for the characters in ``(new ())''. */ + name_length = JSSTRING_LENGTH(name); + message_length = JSSTRING_LENGTH(message); + length = 8 + name_length + message_length; + + filename_length = JSSTRING_LENGTH(filename); + if (filename_length != 0) { + /* append filename as ``, {filename}'' */ + length += 2 + filename_length; + if (lineno_as_str) { + /* append lineno as ``, {lineno_as_str}'' */ + length += 2 + lineno_length; + } + } else { + if (lineno_as_str) { + /* + * no filename, but have line number, + * need to append ``, "", {lineno_as_str}'' + */ + length += 6 + lineno_length; + } + } + + cp = chars = (jschar*) JS_malloc(cx, (length + 1) * sizeof(jschar)); + if (!chars) + return JS_FALSE; + + *cp++ = '('; *cp++ = 'n'; *cp++ = 'e'; *cp++ = 'w'; *cp++ = ' '; + js_strncpy(cp, JSSTRING_CHARS(name), name_length); + cp += name_length; + *cp++ = '('; + if (message_length != 0) { + js_strncpy(cp, JSSTRING_CHARS(message), message_length); + cp += message_length; + } + + if (filename_length != 0) { + /* append filename as ``, {filename}'' */ + *cp++ = ','; *cp++ = ' '; + js_strncpy(cp, JSSTRING_CHARS(filename), filename_length); + cp += filename_length; + } else { + if (lineno_as_str) { + /* + * no filename, but have line number, + * need to append ``, "", {lineno_as_str}'' + */ + *cp++ = ','; *cp++ = ' '; *cp++ = '"'; *cp++ = '"'; + } + } + if (lineno_as_str) { + /* append lineno as ``, {lineno_as_str}'' */ + *cp++ = ','; *cp++ = ' '; + js_strncpy(cp, JSSTRING_CHARS(lineno_as_str), lineno_length); + cp += lineno_length; + } + + *cp++ = ')'; *cp++ = ')'; *cp = 0; + + result = js_NewString(cx, chars, length, 0); + if (!result) { + JS_free(cx, chars); + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(result); + return JS_TRUE; +} +#endif + +static JSFunctionSpec exception_methods[] = { +#if JS_HAS_TOSOURCE + {js_toSource_str, exn_toSource, 0,0,0}, +#endif + {js_toString_str, exn_toString, 0,0,0}, + {0,0,0,0,0} +}; + +JSObject * +js_InitExceptionClasses(JSContext *cx, JSObject *obj) +{ + int i; + JSObject *protos[JSEXN_LIMIT]; + + /* Initialize the prototypes first. */ + for (i = 0; exceptions[i].name != 0; i++) { + JSAtom *atom; + JSFunction *fun; + JSString *nameString; + int protoIndex = exceptions[i].protoIndex; + + /* Make the prototype for the current constructor name. */ + protos[i] = js_NewObject(cx, &ExceptionClass, + (protoIndex != JSEXN_NONE) + ? protos[protoIndex] + : NULL, + obj); + if (!protos[i]) + return NULL; + + /* So exn_finalize knows whether to destroy private data. */ + OBJ_SET_SLOT(cx, protos[i], JSSLOT_PRIVATE, JSVAL_VOID); + + atom = js_Atomize(cx, exceptions[i].name, strlen(exceptions[i].name), 0); + if (!atom) + return NULL; + + /* Make a constructor function for the current name. */ + fun = js_DefineFunction(cx, obj, atom, exceptions[i].native, 3, 0); + if (!fun) + return NULL; + + /* Make this constructor make objects of class Exception. */ + fun->clasp = &ExceptionClass; + + /* Make the prototype and constructor links. */ + if (!js_SetClassPrototype(cx, fun->object, protos[i], + JSPROP_READONLY | JSPROP_PERMANENT)) { + return NULL; + } + + /* proto bootstrap bit from JS_InitClass omitted. */ + nameString = JS_NewStringCopyZ(cx, exceptions[i].name); + if (!nameString) + return NULL; + + /* Add the name property to the prototype. */ + if (!JS_DefineProperty(cx, protos[i], js_name_str, + STRING_TO_JSVAL(nameString), + NULL, NULL, + JSPROP_ENUMERATE)) { + return NULL; + } + } + + /* + * Add an empty message property. (To Exception.prototype only, + * because this property will be the same for all the exception + * protos.) + */ + if (!JS_DefineProperty(cx, protos[0], js_message_str, + STRING_TO_JSVAL(cx->runtime->emptyString), + NULL, NULL, JSPROP_ENUMERATE)) { + return NULL; + } + if (!JS_DefineProperty(cx, protos[0], js_filename_str, + STRING_TO_JSVAL(cx->runtime->emptyString), + NULL, NULL, JSPROP_ENUMERATE)) { + return NULL; + } + if (!JS_DefineProperty(cx, protos[0], js_lineno_str, + INT_TO_JSVAL(0), + NULL, NULL, JSPROP_ENUMERATE)) { + return NULL; + } + + /* + * Add methods only to Exception.prototype, because ostensibly all + * exception types delegate to that. + */ + if (!JS_DefineFunctions(cx, protos[0], exception_methods)) + return NULL; + + return protos[0]; +} + +static JSExnType errorToExceptionNum[] = { +#define MSG_DEF(name, number, count, exception, format) \ + exception, +#include "js.msg" +#undef MSG_DEF +}; + +#if defined ( DEBUG_mccabe ) && defined ( PRINTNAMES ) +/* For use below... get character strings for error name and exception name */ +static struct exnname { char *name; char *exception; } errortoexnname[] = { +#define MSG_DEF(name, number, count, exception, format) \ + {#name, #exception}, +#include "js.msg" +#undef MSG_DEF +}; +#endif /* DEBUG */ + +JSBool +js_ErrorToException(JSContext *cx, const char *message, JSErrorReport *reportp) +{ + JSErrNum errorNumber; + JSExnType exn; + JSBool ok; + JSObject *errProto, *errObject; + JSString *messageStr, *filenameStr; + uintN lineno; + JSExnPrivate *privateData; + + /* + * Tell our caller to report immediately if cx has no active frames, or if + * this report is just a warning. + */ + JS_ASSERT(reportp); + if (!cx->fp || JSREPORT_IS_WARNING(reportp->flags)) + return JS_FALSE; + + /* Find the exception index associated with this error. */ + errorNumber = (JSErrNum) reportp->errorNumber; + exn = errorToExceptionNum[errorNumber]; + JS_ASSERT(exn < JSEXN_LIMIT); + +#if defined( DEBUG_mccabe ) && defined ( PRINTNAMES ) + /* Print the error name and the associated exception name to stderr */ + fprintf(stderr, "%s\t%s\n", + errortoexnname[errorNumber].name, + errortoexnname[errorNumber].exception); +#endif + + /* + * Return false (no exception raised) if no exception is associated + * with the given error number. + */ + if (exn == JSEXN_NONE) + return JS_FALSE; + + /* + * Prevent runaway recursion, just as the Exception native constructor + * must do, via cx->creatingException. If an out-of-memory error occurs, + * no exception object will be created, but we don't assume that OOM is + * the only kind of error that subroutines of this function called below + * might raise. + */ + if (cx->creatingException) + return JS_FALSE; + cx->creatingException = JS_TRUE; + + /* + * Try to get an appropriate prototype by looking up the corresponding + * exception constructor name in the scope chain of the current context's + * top stack frame, or in the global object if no frame is active. + * + * XXXbe hack around JSCLASS_NEW_RESOLVE code in js_LookupProperty that + * checks cx->fp, cx->fp->pc, and js_CodeSpec[*cx->fp->pc] in order + * to compute resolve flags such as JSRESOLVE_ASSIGNING. The bug + * is that this "internal" js_GetClassPrototype call may trigger a + * resolve of exceptions[exn].name if the global object uses a lazy + * standard class resolver (see JS_ResolveStandardClass), but the + * current frame and bytecode end up affecting the resolve flags. + */ + { + JSStackFrame *fp = cx->fp; + jsbytecode *pc = NULL; + + if (fp) { + pc = fp->pc; + fp->pc = NULL; + } + ok = js_GetClassPrototype(cx, exceptions[exn].name, &errProto); + if (pc) + fp->pc = pc; + if (!ok) + goto out; + } + + errObject = js_NewObject(cx, &ExceptionClass, errProto, NULL); + if (!errObject) { + ok = JS_FALSE; + goto out; + } + + /* + * Set the generated Exception object early, so it won't be GC'd by a last + * ditch attempt to collect garbage, or a GC that otherwise nests or races + * under any of the following calls. If one of the following calls fails, + * it will overwrite this exception object with one of its own (except in + * case of OOM errors, of course). + */ + JS_SetPendingException(cx, OBJECT_TO_JSVAL(errObject)); + + messageStr = JS_NewStringCopyZ(cx, message); + if (!messageStr) { + ok = JS_FALSE; + goto out; + } + + if (reportp) { + filenameStr = JS_NewStringCopyZ(cx, reportp->filename); + if (!filenameStr) { + ok = JS_FALSE; + goto out; + } + lineno = reportp->lineno; + } else { + filenameStr = cx->runtime->emptyString; + lineno = 0; + } + ok = InitExceptionObject(cx, errObject, messageStr, filenameStr, lineno); + if (!ok) + goto out; + + /* + * Construct a new copy of the error report struct, and store it in the + * exception object's private data. We can't use the error report struct + * that was passed in, because it's stack-allocated, and also because it + * may point to transient data in the JSTokenStream. + */ + privateData = exn_newPrivate(cx, reportp); + if (!privateData) { + ok = JS_FALSE; + goto out; + } + OBJ_SET_SLOT(cx, errObject, JSSLOT_PRIVATE, PRIVATE_TO_JSVAL(privateData)); + + /* Flag the error report passed in to indicate an exception was raised. */ + reportp->flags |= JSREPORT_EXCEPTION; + +out: + cx->creatingException = JS_FALSE; + return ok; +} +#endif /* JS_HAS_ERROR_EXCEPTIONS */ + +#if JS_HAS_EXCEPTIONS + +JSBool +js_ReportUncaughtException(JSContext *cx) +{ + JSObject *exnObject; + JSString *str; + jsval exn; + JSErrorReport *reportp; + const char *bytes; + + if (!JS_IsExceptionPending(cx)) + return JS_FALSE; + + if (!JS_GetPendingException(cx, &exn)) + return JS_FALSE; + + /* + * Because js_ValueToString below could error and an exception object + * could become unrooted, we root it here. + */ + if (JSVAL_IS_OBJECT(exn) && exn != JSVAL_NULL) { + exnObject = JSVAL_TO_OBJECT(exn); + if (!js_AddRoot(cx, &exnObject, "exn.report.root")) + return JS_FALSE; + } else { + exnObject = NULL; + } + +#if JS_HAS_ERROR_EXCEPTIONS + reportp = js_ErrorFromException(cx, exn); +#else + reportp = NULL; +#endif + + str = js_ValueToString(cx, exn); + bytes = str ? js_GetStringBytes(str) : "null"; + + if (reportp == NULL) { + /* + * XXXmccabe todo: Instead of doing this, synthesize an error report + * struct that includes the filename, lineno where the exception was + * originally thrown. + */ + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_UNCAUGHT_EXCEPTION, bytes); + } else { + /* Flag the error as an exception. */ + reportp->flags |= JSREPORT_EXCEPTION; + js_ReportErrorAgain(cx, bytes, reportp); + } + + if (exnObject != NULL) + js_RemoveRoot(cx->runtime, &exnObject); + return JS_TRUE; +} + +#endif /* JS_HAS_EXCEPTIONS */ diff --git a/src/dom/js/jsexn.h b/src/dom/js/jsexn.h new file mode 100644 index 000000000..aeaab0a58 --- /dev/null +++ b/src/dom/js/jsexn.h @@ -0,0 +1,102 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS runtime exception classes. + */ + +#ifndef jsexn_h___ +#define jsexn_h___ + +JS_BEGIN_EXTERN_C + +/* + * Initialize the exception constructor/prototype hierarchy. + */ +extern JSObject * +js_InitExceptionClasses(JSContext *cx, JSObject *obj); + +/* + * String constants naming the exception classes. + */ +extern const char js_Error_str[]; +extern const char js_InternalError_str[]; +extern const char js_EvalError_str[]; +extern const char js_RangeError_str[]; +extern const char js_ReferenceError_str[]; +extern const char js_SyntaxError_str[]; +extern const char js_TypeError_str[]; +extern const char js_URIError_str[]; + +/* + * Given a JSErrorReport, check to see if there is an exception associated with + * the error number. If there is, then create an appropriate exception object, + * set it as the pending exception, and set the JSREPORT_EXCEPTION flag on the + * error report. Exception-aware host error reporters should probably ignore + * error reports so flagged. Returns JS_TRUE if an associated exception is + * found and set, JS_FALSE otherwise.. + */ +extern JSBool +js_ErrorToException(JSContext *cx, const char *message, JSErrorReport *reportp); + +/* + * Called if a JS API call to js_Execute or js_InternalCall fails; calls the + * error reporter with the error report associated with any uncaught exception + * that has been raised. Returns true if there was an exception pending, and + * the error reporter was actually called. + * + * The JSErrorReport * that the error reporter is called with is currently + * associated with a JavaScript object, and is not guaranteed to persist after + * the object is collected. Any persistent uses of the JSErrorReport contents + * should make their own copy. + * + * The flags field of the JSErrorReport will have the JSREPORT_EXCEPTION flag + * set; embeddings that want to silently propagate JavaScript exceptions to + * other contexts may want to use an error reporter that ignores errors with + * this flag. + */ +extern JSBool +js_ReportUncaughtException(JSContext *cx); + +extern JSErrorReport * +js_ErrorFromException(JSContext *cx, jsval exn); + +JS_END_EXTERN_C + +#endif /* jsexn_h___ */ diff --git a/src/dom/js/jsfile.c b/src/dom/js/jsfile.c new file mode 100644 index 000000000..ef5e93b29 --- /dev/null +++ b/src/dom/js/jsfile.c @@ -0,0 +1,2610 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS File object + */ +#if JS_HAS_FILE_OBJECT + +#include "jsstddef.h" + +/* ----------------- Platform-specific includes and defines ----------------- */ +#ifdef XP_MAC +# define FILESEPARATOR ':' +# define FILESEPARATOR2 '\0' +# define CURRENT_DIR "HARD DISK:Desktop Folder" +/* TODO: #include */ +#elif defined(XP_WIN) || defined(XP_OS2) +# include +# include +# include +# include +# define FILESEPARATOR '\\' +# define FILESEPARATOR2 '/' +# define CURRENT_DIR "c:\\" +# define POPEN _popen +# define PCLOSE _pclose +#elif defined(XP_UNIX) || defined(XP_BEOS) +# include +# include +# include +# include +# define FILESEPARATOR '/' +# define FILESEPARATOR2 '\0' +# define CURRENT_DIR "/" +# define POPEN popen +# define PCLOSE pclose +#endif + +/* --------------- Platform-independent includes and defines ---------------- */ +#include "jsapi.h" +#include "jsatom.h" +#include "jscntxt.h" +#include "jsdate.h" +#include "jsdbgapi.h" +#include "jsemit.h" +#include "jsfun.h" +#include "jslock.h" +#include "jsobj.h" +#include "jsparse.h" +#include "jsscan.h" +#include "jsscope.h" +#include "jsscript.h" +#include "jsstr.h" +#include "jsutil.h" /* Added by JSIFY */ +#include + +/* NSPR dependencies */ +#include "prio.h" +#include "prerror.h" + +#define SPECIAL_FILE_STRING "Special File" +#define CURRENTDIR_PROPERTY "currentDir" +#define SEPARATOR_PROPERTY "separator" +#define FILE_CONSTRUCTOR "File" +#define PIPE_SYMBOL '|' + +#define ASCII 0 +#define UTF8 1 +#define UCS2 2 + +#define asciistring "text" +#define utfstring "binary" +#define unicodestring "unicode" + +#define MAX_PATH_LENGTH 1024 +#define MODE_SIZE 256 +#define NUMBER_SIZE 32 +#define MAX_LINE_LENGTH 256 +#define URL_PREFIX "file://" + +#define STDINPUT_NAME "Standard input stream" +#define STDOUTPUT_NAME "Standard output stream" +#define STDERROR_NAME "Standard error stream" + +#define RESOLVE_PATH js_canonicalPath /* js_absolutePath */ + +/* Error handling */ +typedef enum JSFileErrNum { +#define MSG_DEF(name, number, count, exception, format) \ + name = number, +#include "jsfile.msg" +#undef MSG_DEF + JSFileErr_Limit +#undef MSGDEF +} JSFileErrNum; + +#define JSFILE_HAS_DFLT_MSG_STRINGS 1 + +JSErrorFormatString JSFile_ErrorFormatString[JSFileErr_Limit] = { +#if JSFILE_HAS_DFLT_MSG_STRINGS +#define MSG_DEF(name, number, count, exception, format) \ + { format, count } , +#else +#define MSG_DEF(name, number, count, exception, format) \ + { NULL, count } , +#endif +#include "jsfile.msg" +#undef MSG_DEF +}; + +const JSErrorFormatString * +JSFile_GetErrorMessage(void *userRef, const char *locale, + const uintN errorNumber) +{ + if ((errorNumber > 0) && (errorNumber < JSFileErr_Limit)) + return &JSFile_ErrorFormatString[errorNumber]; + else + return NULL; +} + +#define JSFILE_CHECK_NATIVE(op) \ + if(file->isNative){ \ + JS_ReportWarning(cx, "Cannot call or access \"%s\" on native file %s", \ + op, file->path); \ + goto out; \ + } + +#define JSFILE_CHECK_WRITE \ + if (!file->isOpen){ \ + JS_ReportWarning(cx, \ + "File %s is closed, will open it for writing, proceeding", \ + file->path); \ + js_FileOpen(cx, obj, file, "write,append,create"); \ + }else \ + if(!js_canWrite(cx, file)){ \ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, \ + JSFILEMSG_CANNOT_WRITE, file->path); \ + goto out; \ + } + +#define JSFILE_CHECK_READ \ + if (!file->isOpen){ \ + JS_ReportWarning(cx, \ + "File %s is closed, will open it for reading, proceeding", \ + file->path); \ + js_FileOpen(cx, obj, file, "read"); \ + }else \ + if(!js_canRead(cx, file)){ \ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, \ + JSFILEMSG_CANNOT_READ, file->path); \ + goto out; \ + } + +#define JSFILE_CHECK_OPEN(op) \ + if(!file->isOpen){ \ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, \ + JSFILEMSG_FILE_MUST_BE_CLOSED, op); \ + goto out; \ + } + +#define JSFILE_CHECK_CLOSED(op) \ + if(file->isOpen){ \ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, \ + JSFILEMSG_FILE_MUST_BE_OPEN, op); \ + goto out; \ + } + +#define JSFILE_CHECK_ONE_ARG(op) \ + if (argc!=1){ \ + char str[NUMBER_SIZE]; \ + \ + sprintf(str, "%d", argc); \ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, \ + JSFILEMSG_EXPECTS_ONE_ARG_ERROR, op, str); \ + goto out; \ + } + + +/* + Security mechanism, should define a callback for this. + The parameters are as follows: + SECURITY_CHECK(JSContext *cx, JSPrincipals *ps, char *op_name, JSFile *file) +*/ +#define SECURITY_CHECK(cx, ps, op, file) \ + /* Define a callback here... */ + + +/* Structure representing the file internally */ +typedef struct JSFile { + char *path; /* the path to the file. */ + JSBool isOpen; + JSString *linebuffer; /* temp buffer used by readln. */ + int32 mode; /* mode used to open the file: read, write, append, create, etc.. */ + int32 type; /* Asciiz, utf, unicode */ + char byteBuffer[3]; /* bytes read in advance by js_FileRead ( UTF8 encoding ) */ + jsint nbBytesInBuf; /* number of bytes stored in the buffer above */ + jschar charBuffer; /* character read in advance by readln ( mac files only ) */ + JSBool charBufferUsed; /* flag indicating if the buffer above is being used */ + JSBool hasRandomAccess; /* can the file be randomly accessed? false for stdin, and + UTF-encoded files. */ + JSBool hasAutoflush; /* should we force a flush for each line break? */ + JSBool isNative; /* if the file is using OS-specific file FILE type */ + /* We can actually put the following two in a union since they should never be used at the same time */ + PRFileDesc *handle; /* the handle for the file, if open. */ + FILE *nativehandle; /* native handle, for stuff NSPR doesn't do. */ + JSBool isPipe; /* if the file is really an OS pipe */ +} JSFile; + +/* a few forward declarations... */ +static JSClass file_class; +JS_PUBLIC_API(JSObject*) js_NewFileObject(JSContext *cx, char *filename); +static JSBool file_open(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval); +static JSBool file_close(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval); + +/* --------------------------- New filename manipulation procesures -------------------------- */ +/* assumes we don't have leading/trailing spaces */ +static JSBool +js_filenameHasAPipe(const char *filename) +{ +#ifdef XP_MAC + /* pipes are not supported on the MAC */ + return JS_FALSE; +#else + if(!filename) return JS_FALSE; + return filename[0]==PIPE_SYMBOL || + filename[strlen(filename)-1]==PIPE_SYMBOL; +#endif +} + +static JSBool +js_isAbsolute(const char *name) +{ +#if defined(XP_WIN) || defined(XP_OS2) + return (strlen(name)>1)?((name[1]==':')?JS_TRUE:JS_FALSE):JS_FALSE; +#else + return (name[0] +# if defined(XP_UNIX) || defined(XP_BEOS) + == +# else + != +# endif + FILESEPARATOR)?JS_TRUE:JS_FALSE; +#endif +} + +/* + Concatinates base and name to produce a valid filename. + Returned string must be freed. +*/ +static char* +js_combinePath(JSContext *cx, const char *base, const char *name) +{ + int len = strlen(base); + char* result = (char*)JS_malloc(cx, len+strlen(name)+2); + + if (!result) return NULL; + + strcpy(result, base); + + if (base[len-1]!=FILESEPARATOR +#if defined(XP_WIN) || defined(XP_OS2) + && base[len-1]!=FILESEPARATOR2 +#endif + ) { + result[len] = FILESEPARATOR; + result[len+1] = '\0'; + } + strcat(result, name); + return result; +} + +/* Extract the last component from a path name. Returned string must be freed */ +static char * +js_fileBaseName(JSContext *cx, const char *pathname) +{ + jsint index, aux; + char *result; + +#if defined(XP_WIN) || defined(XP_OS2) + /* First, get rid of the drive selector */ + if ((strlen(pathname)>=2)&&(pathname[1]==':')) { + pathname = &pathname[2]; + } +#endif + index = strlen(pathname)-1; + /* + remove trailing separators -- don't necessarily need to check for + FILESEPARATOR2, but that's fine + */ + while ((index>0)&&((pathname[index]==FILESEPARATOR)|| + (pathname[index]==FILESEPARATOR2))) index--; + aux = index; + /* now find the next separator */ + while ((index>=0)&&(pathname[index]!=FILESEPARATOR)&& + (pathname[index]!=FILESEPARATOR2)) index--; + /* allocate and copy */ + result = (char*)JS_malloc(cx, aux-index+1); + if (!result) return NULL; + strncpy(result, &pathname[index+1], aux-index); + result[aux-index] = '\0'; + return result; +} + +/* + Returns everytynig but the last component from a path name. + Returned string must be freed. Returned string must be freed. +*/ +static char * +js_fileDirectoryName(JSContext *cx, const char *pathname) +{ + jsint index; + char *result; + +#if defined(XP_WIN) || defined(XP_OS2) + char drive = '\0'; + const char *oldpathname = pathname; + + /* First, get rid of the drive selector */ + if ((strlen(pathname)>=2)&&(pathname[1]==':')) { + drive = pathname[0]; + pathname = &pathname[2]; + } +#endif + index = strlen(pathname)-1; + while ((index>0)&&((pathname[index]==FILESEPARATOR)|| + (pathname[index]==FILESEPARATOR2))) index--; + while ((index>0)&&(pathname[index]!=FILESEPARATOR)&& + (pathname[index]!=FILESEPARATOR2)) index--; + + if (index>=0){ + result = (char*)JS_malloc(cx, index+4); + if (!result) return NULL; +#if defined(XP_WIN) || defined(XP_OS2) + if (drive!='\0') { + result[0] = toupper(drive); + result[1] = ':'; + strncpy(&result[2], pathname, index); + result[index+3] = '\0'; + }else +#endif + { + strncpy(result, pathname, index); + result[index] = '\0'; + } + + /* add terminating separator */ + index = strlen(result)-1; + result[index] = FILESEPARATOR; + result[index+1] = '\0'; + } else{ +#if defined(XP_WIN) || defined(XP_OS2) + result = JS_strdup(cx, oldpathname); /* may include drive selector */ +#else + result = JS_strdup(cx, pathname); +#endif + } + + return result; +} + +static char * +js_absolutePath(JSContext *cx, const char * path) +{ + JSObject *obj; + JSString *str; + jsval prop; + + if (js_isAbsolute(path)){ + return JS_strdup(cx, path); + }else{ + obj = JS_GetGlobalObject(cx); + if (!JS_GetProperty(cx, obj, FILE_CONSTRUCTOR, &prop)) { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_FILE_CONSTRUCTOR_UNDEFINED_ERROR); + return JS_strdup(cx, path); + } + obj = JSVAL_TO_OBJECT(prop); + if (!JS_GetProperty(cx, obj, CURRENTDIR_PROPERTY, &prop)) { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_FILE_CURRENTDIR_UNDEFINED_ERROR); + return JS_strdup(cx, path); + } + str = JS_ValueToString(cx, prop); + if (!str ) { + return JS_strdup(cx, path); + } + /* should we have an array of curr dirs indexed by drive for windows? */ + return js_combinePath(cx, JS_GetStringBytes(str), path); + } +} + +/* Side effect: will remove spaces in the beginning/end of the filename */ +static char * +js_canonicalPath(JSContext *cx, char *oldpath) +{ + char *tmp; + char *path = oldpath; + char *base, *dir, *current, *result; + jsint c; + jsint back = 0; + unsigned int i = 0, j = strlen(path)-1; + + /* This is probably optional */ + /* Remove possible spaces in the beginning and end */ + while(i=0 && path[j]==' ') j--; + + tmp = JS_malloc(cx, j-i+2); + strncpy(tmp, &path[i], j-i+1); + tmp[j-i+1] = '\0'; + + path = tmp; + + /* pipe support */ + if(js_filenameHasAPipe(path)) + return JS_strdup(cx, path); + /* file:// support */ + if(!strncmp(path, URL_PREFIX, strlen(URL_PREFIX))) + return js_canonicalPath(cx, &path[strlen(URL_PREFIX)-1]); + + if (!js_isAbsolute(path)) + path = js_absolutePath(cx, path); + else + path = JS_strdup(cx, path); + + result = JS_strdup(cx, ""); + + current = path; + + base = js_fileBaseName(cx, current); + dir = js_fileDirectoryName(cx, current); + + /* TODO: MAC -- not going to work??? */ + while (strcmp(dir, current)) { + if (!strcmp(base, "..")) { + back++; + } else + if(!strcmp(base, ".")){ + /* ??? */ + } else { + if (back>0) + back--; + else { + tmp = result; + result = JS_malloc(cx, strlen(base)+1+strlen(tmp)+1); + if (!result) { + JS_free(cx, dir); + JS_free(cx, base); + JS_free(cx, current); + return NULL; + } + strcpy(result, base); + c = strlen(result); + if (*tmp) { + result[c] = FILESEPARATOR; + result[c+1] = '\0'; + strcat(result, tmp); + } + JS_free(cx, tmp); + } + } + JS_free(cx, current); + JS_free(cx, base); + current = dir; + base = js_fileBaseName(cx, current); + dir = js_fileDirectoryName(cx, current); + } + + tmp = result; + result = JS_malloc(cx, strlen(dir)+1+strlen(tmp)+1); + if (!result) { + JS_free(cx, dir); + JS_free(cx, base); + JS_free(cx, current); + return NULL; + } + strcpy(result, dir); + c = strlen(result); + if (tmp[0]!='\0') { + if ((result[c-1]!=FILESEPARATOR)&&(result[c-1]!=FILESEPARATOR2)) { + result[c] = FILESEPARATOR; + result[c+1] = '\0'; + } + strcat(result, tmp); + } + JS_free(cx, tmp); + JS_free(cx, dir); + JS_free(cx, base); + JS_free(cx, current); + + return result; +} + +/* -------------------------- Text conversion ------------------------------- */ +/* The following is ripped from libi18n/unicvt.c and include files.. */ + +/* + * UTF8 defines and macros + */ +#define ONE_OCTET_BASE 0x00 /* 0xxxxxxx */ +#define ONE_OCTET_MASK 0x7F /* x1111111 */ +#define CONTINUING_OCTET_BASE 0x80 /* 10xxxxxx */ +#define CONTINUING_OCTET_MASK 0x3F /* 00111111 */ +#define TWO_OCTET_BASE 0xC0 /* 110xxxxx */ +#define TWO_OCTET_MASK 0x1F /* 00011111 */ +#define THREE_OCTET_BASE 0xE0 /* 1110xxxx */ +#define THREE_OCTET_MASK 0x0F /* 00001111 */ +#define FOUR_OCTET_BASE 0xF0 /* 11110xxx */ +#define FOUR_OCTET_MASK 0x07 /* 00000111 */ +#define FIVE_OCTET_BASE 0xF8 /* 111110xx */ +#define FIVE_OCTET_MASK 0x03 /* 00000011 */ +#define SIX_OCTET_BASE 0xFC /* 1111110x */ +#define SIX_OCTET_MASK 0x01 /* 00000001 */ + +#define IS_UTF8_1ST_OF_1(x) (( (x)&~ONE_OCTET_MASK ) == ONE_OCTET_BASE) +#define IS_UTF8_1ST_OF_2(x) (( (x)&~TWO_OCTET_MASK ) == TWO_OCTET_BASE) +#define IS_UTF8_1ST_OF_3(x) (( (x)&~THREE_OCTET_MASK) == THREE_OCTET_BASE) +#define IS_UTF8_1ST_OF_4(x) (( (x)&~FOUR_OCTET_MASK ) == FOUR_OCTET_BASE) +#define IS_UTF8_1ST_OF_5(x) (( (x)&~FIVE_OCTET_MASK ) == FIVE_OCTET_BASE) +#define IS_UTF8_1ST_OF_6(x) (( (x)&~SIX_OCTET_MASK ) == SIX_OCTET_BASE) +#define IS_UTF8_2ND_THRU_6TH(x) \ + (( (x)&~CONTINUING_OCTET_MASK ) == CONTINUING_OCTET_BASE) +#define IS_UTF8_1ST_OF_UCS2(x) \ + IS_UTF8_1ST_OF_1(x) \ + || IS_UTF8_1ST_OF_2(x) \ + || IS_UTF8_1ST_OF_3(x) + + +#define MAX_UCS2 0xFFFF +#define DEFAULT_CHAR 0x003F /* Default char is "?" */ +#define BYTE_MASK 0xBF +#define BYTE_MARK 0x80 + + +/* Function: one_ucs2_to_utf8_char + * + * Function takes one UCS-2 char and writes it to a UTF-8 buffer. + * We need a UTF-8 buffer because we don't know before this + * function how many bytes of utf-8 data will be written. It also + * takes a pointer to the end of the UTF-8 buffer so that we don't + * overwrite data. This function returns the number of UTF-8 bytes + * of data written, or -1 if the buffer would have been overrun. + */ + +#define LINE_SEPARATOR 0x2028 +#define PARAGRAPH_SEPARATOR 0x2029 +static int16 one_ucs2_to_utf8_char(unsigned char *tobufp, + unsigned char *tobufendp, uint16 onechar) +{ + + int16 numUTF8bytes = 0; + + if((onechar == LINE_SEPARATOR)||(onechar == PARAGRAPH_SEPARATOR)) + { + strcpy((char*)tobufp, "\n"); + return strlen((char*)tobufp);; + } + + if (onechar < 0x80) { numUTF8bytes = 1; + } else if (onechar < 0x800) { numUTF8bytes = 2; + } else if (onechar <= MAX_UCS2) { numUTF8bytes = 3; + } else { numUTF8bytes = 2; + onechar = DEFAULT_CHAR; + } + + tobufp += numUTF8bytes; + + /* return error if we don't have space for the whole character */ + if (tobufp > tobufendp) { + return(-1); + } + + + switch(numUTF8bytes) { + + case 3: *--tobufp = (onechar | BYTE_MARK) & BYTE_MASK; onechar >>=6; + *--tobufp = (onechar | BYTE_MARK) & BYTE_MASK; onechar >>=6; + *--tobufp = onechar | THREE_OCTET_BASE; + break; + + case 2: *--tobufp = (onechar | BYTE_MARK) & BYTE_MASK; onechar >>=6; + *--tobufp = onechar | TWO_OCTET_BASE; + break; + case 1: *--tobufp = (unsigned char)onechar; break; + } + + return(numUTF8bytes); +} + +/* + * utf8_to_ucs2_char + * + * Convert a utf8 multibyte character to ucs2 + * + * inputs: pointer to utf8 character(s) + * length of utf8 buffer ("read" length limit) + * pointer to return ucs2 character + * + * outputs: number of bytes in the utf8 character + * -1 if not a valid utf8 character sequence + * -2 if the buffer is too short + */ +static int16 +utf8_to_ucs2_char(const unsigned char *utf8p, int16 buflen, uint16 *ucs2p) +{ + uint16 lead, cont1, cont2; + + /* + * Check for minimum buffer length + */ + if ((buflen < 1) || (utf8p == NULL)) { + return -2; + } + lead = (uint16) (*utf8p); + + /* + * Check for a one octet sequence + */ + if (IS_UTF8_1ST_OF_1(lead)) { + *ucs2p = lead & ONE_OCTET_MASK; + return 1; + } + + /* + * Check for a two octet sequence + */ + if (IS_UTF8_1ST_OF_2(*utf8p)) { + if (buflen < 2) + return -2; + cont1 = (uint16) *(utf8p+1); + if (!IS_UTF8_2ND_THRU_6TH(cont1)) + return -1; + *ucs2p = (lead & TWO_OCTET_MASK) << 6; + *ucs2p |= cont1 & CONTINUING_OCTET_MASK; + return 2; + } + + /* + * Check for a three octet sequence + */ + else if (IS_UTF8_1ST_OF_3(lead)) { + if (buflen < 3) + return -2; + cont1 = (uint16) *(utf8p+1); + cont2 = (uint16) *(utf8p+2); + if ( (!IS_UTF8_2ND_THRU_6TH(cont1)) + || (!IS_UTF8_2ND_THRU_6TH(cont2))) + return -1; + *ucs2p = (lead & THREE_OCTET_MASK) << 12; + *ucs2p |= (cont1 & CONTINUING_OCTET_MASK) << 6; + *ucs2p |= cont2 & CONTINUING_OCTET_MASK; + return 3; + } + else { /* not a valid utf8/ucs2 character */ + return -1; + } +} + +/* ----------------------------- Helper functions --------------------------- */ +/* Ripped off from lm_win.c .. */ +/* where is strcasecmp?.. for now, it's case sensitive.. + * + * strcasecmp is in strings.h, but on windows it's called _stricmp... + * will need to #ifdef this +*/ + +static int32 +js_FileHasOption(JSContext *cx, const char *oldoptions, const char *name) +{ + char *comma, *equal, *current; + char *options = JS_strdup(cx, oldoptions); + int32 found = 0; + + current = options; + for (;;) { + comma = strchr(current, ','); + if (comma) *comma = '\0'; + equal = strchr(current, '='); + if (equal) *equal = '\0'; + if (strcmp(current, name) == 0) { + if (!equal || strcmp(equal + 1, "yes") == 0) + found = 1; + else + found = atoi(equal + 1); + } + if (equal) *equal = '='; + if (comma) *comma = ','; + if (found || !comma) + break; + current = comma + 1; + } + JS_free(cx, options); + return found; +} + +/* empty the buffer */ +static void +js_ResetBuffers(JSFile * file) +{ + file->charBufferUsed = JS_FALSE; + file->nbBytesInBuf = 0; + file->linebuffer = NULL; /* TODO: check for mem. leak? */ +} + +/* Reset file attributes */ +static void +js_ResetAttributes(JSFile * file){ + file->mode = file->type = 0; + file->isOpen = JS_FALSE; + file->handle = NULL; + file->nativehandle = NULL; + file->hasRandomAccess = JS_TRUE; /* innocent until proven guilty */ + file->hasAutoflush = JS_FALSE; + file->isNative = JS_FALSE; + file->isPipe = JS_FALSE; + + js_ResetBuffers(file); +} + +static JSBool +js_FileOpen(JSContext *cx, JSObject *obj, JSFile *file, char *mode){ + JSString *type, *mask; + jsval v[2]; + jsval rval; + + type = JS_InternString(cx, asciistring); + mask = JS_NewStringCopyZ(cx, mode); + v[0] = STRING_TO_JSVAL(mask); + v[1] = STRING_TO_JSVAL(type); + + if (!file_open(cx, obj, 2, v, &rval)) { + return JS_FALSE; + } + return JS_TRUE; +} + +/* Buffered version of PR_Read. Used by js_FileRead */ +static int32 +js_BufferedRead(JSFile * f, char *buf, int32 len) +{ + int32 count = 0; + + while (f->nbBytesInBuf>0&&len>0) { + buf[0] = f->byteBuffer[0]; + f->byteBuffer[0] = f->byteBuffer[1]; + f->byteBuffer[1] = f->byteBuffer[2]; + f->nbBytesInBuf--; + len--; + buf+=1; + count++; + } + + if (len>0) { + count+= (!f->isNative)? + PR_Read(f->handle, buf, len): + fread(buf, 1, len, f->nativehandle); + } + return count; +} + +static int32 +js_FileRead(JSContext *cx, JSFile * file, jschar*buf, int32 len, int32 mode) +{ + unsigned char*aux; + int32 count, i; + jsint remainder; + unsigned char utfbuf[3]; + + if (file->charBufferUsed) { + buf[0] = file->charBuffer; + buf++; + len--; + file->charBufferUsed = JS_FALSE; + } + + switch (mode) { + case ASCII: + aux = (unsigned char*)JS_malloc(cx, len); + if (!aux) { + return 0; + } + count = js_BufferedRead(file, aux, len); + if (count==-1) { + JS_free(cx, aux); + return 0; + } + for (i = 0;i0) { + file->byteBuffer[file->nbBytesInBuf] = utfbuf[0]; + file->nbBytesInBuf++; + utfbuf[0] = utfbuf[1]; + utfbuf[1] = utfbuf[2]; + remainder--; + } + break; + case UCS2: + count = js_BufferedRead(file, (char*)buf, len*2)>>1; + if (count==-1) { + return 0; + } + break; + } + + if(count==-1){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OP_FAILED, "read", file->path); + } + + return count; +} + +static int32 +js_FileSeek(JSContext *cx, JSFile *file, int32 len, int32 mode) +{ + int32 count, i; + jsint remainder; + unsigned char utfbuf[3]; + jschar tmp; + + switch (mode) { + case ASCII: + count = PR_Seek(file->handle, len, PR_SEEK_CUR); + break; + case UTF8: + remainder = 0; + for (count = 0;count0) { + file->byteBuffer[file->nbBytesInBuf] = utfbuf[0]; + file->nbBytesInBuf++; + utfbuf[0] = utfbuf[1]; + utfbuf[1] = utfbuf[2]; + remainder--; + } + break; + case UCS2: + count = PR_Seek(file->handle, len*2, PR_SEEK_CUR)/2; + break; + } + + if(count==-1){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OP_FAILED, "seek", file->path); + } + + return count; +} + +static int32 +js_FileWrite(JSContext *cx, JSFile *file, jschar *buf, int32 len, int32 mode) +{ + unsigned char *aux; + int32 count, i, j; + unsigned char *utfbuf; + + switch (mode) { + case ASCII: + aux = (unsigned char*)JS_malloc(cx, len); + if (!aux) return 0; + + for (i = 0; iisNative)? + PR_Write(file->handle, aux, len): + fwrite(aux, 1, len, file->nativehandle); + + if (count==-1) { + JS_free(cx, aux); + return 0; + } + JS_free(cx, aux); + break; + case UTF8: + utfbuf = (unsigned char*)JS_malloc(cx, len*3); + if (!utfbuf) return 0; + i = 0; + for (count = 0;countisNative)? + PR_Write(file->handle, utfbuf, i): + fwrite(utfbuf, 1, i, file->nativehandle); + + if (jisNative)? + PR_Write(file->handle, buf, len*2)>>1: + fwrite(buf, 1, len*2, file->nativehandle)>>1; + + if (count==-1) { + return 0; + } + break; + } + if(count==-1){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OP_FAILED, "write", file->path); + } + return count; +} + +/* ----------------------------- Property checkers -------------------------- */ +static JSBool +js_exists(JSContext *cx, JSFile *file) +{ + if(!file->isNative){ + return (PR_Access(file->path, PR_ACCESS_EXISTS)==PR_SUCCESS); + }else{ + /* doesn't make sense for a pipe of stdstream */ + return JS_FALSE; + } +} + +static JSBool +js_canRead(JSContext *cx, JSFile *file) +{ + if(!file->isNative){ + if(file->isOpen&&!(file->mode&PR_RDONLY)) return JS_FALSE; + return (PR_Access(file->path, PR_ACCESS_READ_OK)==PR_SUCCESS); + }else{ + if(file->isPipe){ + /* pipe open for reading */ + return file->path[0]==PIPE_SYMBOL; + }else{ + return !strcmp(file->path, STDINPUT_NAME); + } + } +} + +static JSBool +js_canWrite(JSContext *cx, JSFile *file) +{ + if(!file->isNative){ + if(file->isOpen&&!(file->mode&PR_WRONLY)) return JS_FALSE; + return (PR_Access(file->path, PR_ACCESS_WRITE_OK)==PR_SUCCESS); + }else{ + if(file->isPipe){ + /* pipe open for writing */ + return file->path[strlen(file->path)-1]==PIPE_SYMBOL; + }else{ + return !strcmp(file->path, STDOUTPUT_NAME) || + !strcmp(file->path, STDERROR_NAME); + } + } +} + +static JSBool +js_isFile(JSContext *cx, JSFile *file) +{ + if(!file->isNative){ + PRFileInfo info; + + if ((file->isOpen)? + PR_GetOpenFileInfo(file->handle, &info): + PR_GetFileInfo(file->path, &info)!=PR_SUCCESS){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_CANNOT_ACCESS_FILE_STATUS, file->path); + return JS_FALSE; + }else + return (info.type==PR_FILE_FILE); + }else{ + /* doesn't make sense for a pipe of stdstream */ + return JS_FALSE; + } +} + +static JSBool +js_isDirectory(JSContext *cx, JSFile *file) +{ + if(!file->isNative){ + PRFileInfo info; + + /* hack needed to get get_property to work */ + if(!js_exists(cx, file)) return JS_FALSE; + + if ((file->isOpen)? + PR_GetOpenFileInfo(file->handle, &info): + PR_GetFileInfo(file->path, &info)!=PR_SUCCESS){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_CANNOT_ACCESS_FILE_STATUS, file->path); + return JS_FALSE; + }else + return (info.type==PR_FILE_DIRECTORY); + }else{ + /* doesn't make sense for a pipe of stdstream */ + return JS_FALSE; + } +} + +static jsval +js_size(JSContext *cx, JSFile *file) +{ + PRFileInfo info; + + JSFILE_CHECK_NATIVE("size"); + + if ((file->isOpen)? + PR_GetOpenFileInfo(file->handle, &info): + PR_GetFileInfo(file->path, &info)!=PR_SUCCESS){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_CANNOT_ACCESS_FILE_STATUS, file->path); + goto out; + }else + return INT_TO_JSVAL(info.size); +out: + return JSVAL_VOID; +} + +/* Return the parent object */ +static jsval +js_parent(JSContext *cx, JSFile *file) +{ + char *str; + + /* since we only care about pipes and native files, return NULL */ + if(file->isNative) return JSVAL_VOID; + + str = js_fileDirectoryName(cx, file->path); + /* root.parent = null ??? */ + if(!strcmp(file->path, str) || + (!strncmp(str, file->path, strlen(str)-1)&& + file->path[strlen(file->path)]-1)==FILESEPARATOR){ + return JSVAL_NULL; + }else{ + return OBJECT_TO_JSVAL(js_NewFileObject(cx, str)); + JS_free(cx, str); + } +} + +static jsval +js_name(JSContext *cx, JSFile *file){ + return file->isPipe? + JSVAL_VOID: + STRING_TO_JSVAL(JS_NewStringCopyZ(cx, js_fileBaseName(cx, file->path))); +} + +/* ------------------------------ File object methods ---------------------------- */ +static JSBool +file_open(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + JSString *strmode, *strtype; + char *ctype, *mode; + int32 mask, type; + int len; + + SECURITY_CHECK(cx, NULL, "open", file); + + /* A native file that is already open */ + if(file->isOpen && file->isNative){ + JS_ReportWarning(cx, "Native file %s is already open, proceeding", + file->path); + goto good; + } + + /* Close before proceeding */ + if (file->isOpen) { + JS_ReportWarning(cx, + "File %s is already open, we will close it and reopen, proceeding", + file->path); + if(!file_close(cx, obj, 0, NULL, rval)) goto out; + } + + if(js_isDirectory(cx, file)){ + JS_ReportWarning(cx, "%s seems to be a directory, there is no point in " + "trying to open it, proceeding", file->path); + goto good; + } + + /* Path must be defined at this point */ + len = strlen(file->path); + + /* Mode */ + if (argc>=1){ + strmode = JS_ValueToString(cx, argv[0]); + if (!strmode){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_FIRST_ARGUMENT_OPEN_NOT_STRING_ERROR, argv[0]); + goto out; + } + mode = JS_strdup(cx, JS_GetStringBytes(strmode)); + }else{ + if(file->path[0]==PIPE_SYMBOL){ + /* pipe default mode */ + mode = JS_strdup(cx, "read"); + }else + if(file->path[len-1]==PIPE_SYMBOL){ + /* pipe default mode */ + mode = JS_strdup(cx, "write"); + }else{ + /* non-destructive, permissive defaults. */ + mode = JS_strdup(cx, "readWrite,append,create"); + } + } + + /* Process the mode */ + mask = 0; + /* TODO: this is pretty ugly, BTW, we walk thru the string too many times */ + mask|=(js_FileHasOption(cx, mode, "read"))? PR_RDONLY : 0; + mask|=(js_FileHasOption(cx, mode, "write"))? PR_WRONLY : 0; + mask|=(js_FileHasOption(cx, mode, "readWrite"))? PR_RDWR : 0; + mask|=(js_FileHasOption(cx, mode, "append"))? PR_APPEND : 0; + mask|=(js_FileHasOption(cx, mode, "create"))? PR_CREATE_FILE : 0; + mask|=(js_FileHasOption(cx, mode, "replace"))? PR_TRUNCATE : 0; + + if((mask&PR_RDWR)) mask|=(PR_RDONLY|PR_WRONLY); + if((mask&PR_RDONLY)&&(mask&PR_WRONLY)) mask|=PR_RDWR; + + file->hasAutoflush|=(js_FileHasOption(cx, mode, "autoflush")); + + /* Type */ + if (argc>1) { + strtype = JS_ValueToString(cx, argv[1]); + if (!strtype) { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_SECOND_ARGUMENT_OPEN_NOT_STRING_ERROR, argv[1]); + goto out; + } + ctype = JS_GetStringBytes(strtype); + + if(!strcmp(ctype, utfstring)) + type = UTF8; + else + if (!strcmp(ctype, unicodestring)) + type = UCS2; + else{ + if(strcmp(ctype, asciistring)){ + JS_ReportWarning(cx, "File type %s is not supported, using " + "'text' instead, proceeding", ctype); + } + type = ASCII; + } + }else{ + type = ASCII; + } + + /* Save the relevant fields */ + file->type = type; + file->mode = mask; + file->nativehandle = NULL; + file->hasRandomAccess = (type!=UTF8); + + /* + Deal with pipes here. We can't use NSPR for pipes, + so we have to use POPEN. + */ + if(file->path[0]==PIPE_SYMBOL || file->path[len-1]==PIPE_SYMBOL){ + if(file->path[0]==PIPE_SYMBOL && file->path[len-1]==PIPE_SYMBOL){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_BIDIRECTIONAL_PIPE_NOT_SUPPORTED); + goto out; + }else{ + char pipemode[3]; + SECURITY_CHECK(cx, NULL, "pipe_open", file); + + if(file->path[0] == PIPE_SYMBOL){ + if(mask & (PR_WRONLY | PR_APPEND | PR_CREATE_FILE | PR_TRUNCATE)){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OPEN_MODE_NOT_SUPPORTED_WITH_PIPES, + mode, file->path); + goto out; + } + /* open(SPOOLER, "| cat -v | lpr -h 2>/dev/null") -- pipe for writing */ + pipemode[0] = 'r'; + pipemode[1] = file->type==UTF8?'b':'t'; + pipemode[2] = '\0'; + file->nativehandle = POPEN(&file->path[1], pipemode); + }else + if(file->path[len-1] == PIPE_SYMBOL){ + char *command = JS_malloc(cx, len); + + strncpy(command, file->path, len-1); + command[len-1] = '\0'; + /* open(STATUS, "netstat -an 2>&1 |") */ + pipemode[0] = 'w'; + pipemode[1] = file->type==UTF8?'b':'t'; + pipemode[2] = '\0'; + file->nativehandle = POPEN(command, pipemode); + JS_free(cx, command); + } + /* set the flags */ + file->isNative = JS_TRUE; + file->isPipe = JS_TRUE; + file->hasRandomAccess = JS_FALSE; + } + }else{ + /* TODO: what about the permissions?? Java ignores the problem... */ + file->handle = PR_Open(file->path, mask, 0644); + } + + js_ResetBuffers(file); + JS_free(cx, mode); + mode = NULL; + + /* Set the open flag and return result */ + if (file->handle==NULL && file->nativehandle==NULL){ + file->isOpen = JS_FALSE; + + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OP_FAILED, "open", file->path); + goto out; + }else + goto good; +good: + file->isOpen = JS_TRUE; + *rval = JSVAL_TRUE; + return JS_TRUE; +out: + if(mode) JS_free(cx, mode); + *rval = JSVAL_VOID; + return JS_FALSE; +} + +static JSBool +file_close(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + + SECURITY_CHECK(cx, NULL, "close", file); + + if(!file->isOpen){ + JS_ReportWarning(cx, "File %s is not open, can't close it, proceeding", + file->path); + goto out; + } + + if(!file->isPipe){ + if(file->isNative){ + JS_ReportWarning(cx, "Unable to close a native file, proceeding", file->path); + goto out; + }else{ + if(file->handle && PR_Close(file->handle)){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OP_FAILED, "close", file->path); + + goto out; + } + } + }else{ + if(PCLOSE(file->nativehandle)==-1){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OP_FAILED, "pclose", file->path); + goto out; + } + } + + js_ResetAttributes(file); + *rval = JSVAL_TRUE; + return JS_TRUE; +out: + *rval = JSVAL_FALSE; + return JS_FALSE; +} + + +static JSBool +file_remove(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + + SECURITY_CHECK(cx, NULL, "remove", file); + JSFILE_CHECK_NATIVE("remove"); + JSFILE_CHECK_CLOSED("remove"); + + if ((js_isDirectory(cx, file) ? + PR_RmDir(file->path) : PR_Delete(file->path))==PR_SUCCESS) { + js_ResetAttributes(file); + *rval = JSVAL_TRUE; + return JS_TRUE; + } else { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OP_FAILED, "remove", file->path); + goto out; + } +out: + *rval = JSVAL_FALSE; + return JS_FALSE; +} + +/* Raw PR-based function. No text processing. Just raw data copying. */ +static JSBool +file_copyTo(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + char *dest = NULL; + PRFileDesc *handle = NULL; + char *buffer; + jsval count, size; + JSBool fileInitiallyOpen=JS_FALSE; + + SECURITY_CHECK(cx, NULL, "copyTo", file); /* may need a second argument!*/ + JSFILE_CHECK_ONE_ARG("copyTo"); + JSFILE_CHECK_NATIVE("copyTo"); + /* remeber the state */ + fileInitiallyOpen = file->isOpen; + JSFILE_CHECK_READ; + + dest = JS_GetStringBytes(JS_ValueToString(cx, argv[0])); + + /* make sure we are not reading a file open for writing */ + if (file->isOpen && !js_canRead(cx, file)) { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_CANNOT_COPY_FILE_OPEN_FOR_WRITING_ERROR, file->path); + goto out; + } + + if (file->handle==NULL){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OP_FAILED, "open", file->path); + goto out; + } + + handle = PR_Open(dest, PR_WRONLY|PR_CREATE_FILE|PR_TRUNCATE, 0644); + + if(!handle){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OP_FAILED, "open", dest); + goto out; + } + + if ((size=js_size(cx, file))==JSVAL_VOID) { + goto out; + } + + buffer = JS_malloc(cx, size); + + count = INT_TO_JSVAL(PR_Read(file->handle, buffer, size)); + + /* reading panic */ + if (count!=size) { + JS_free(cx, buffer); + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_COPY_READ_ERROR, file->path); + goto out; + } + + count = INT_TO_JSVAL(PR_Write(handle, buffer, JSVAL_TO_INT(size))); + + /* writing panic */ + if (count!=size) { + JS_free(cx, buffer); + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_COPY_WRITE_ERROR, file->path); + goto out; + } + + JS_free(cx, buffer); + + if(!fileInitiallyOpen){ + if(!file_close(cx, obj, 0, NULL, rval)) goto out; + } + + if(PR_Close(handle)!=PR_SUCCESS){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OP_FAILED, "close", dest); + goto out; + } + + *rval = JSVAL_TRUE; + return JS_TRUE; +out: + if(file->isOpen && !fileInitiallyOpen){ + if(PR_Close(file->handle)!=PR_SUCCESS){ + JS_ReportWarning(cx, "Can't close %s, proceeding", file->path); + } + } + + if(handle && PR_Close(handle)!=PR_SUCCESS){ + JS_ReportWarning(cx, "Can't close %s, proceeding", dest); + } + + *rval = JSVAL_FALSE; + return JS_FALSE; +} + +static JSBool +file_renameTo(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + char *dest; + + SECURITY_CHECK(cx, NULL, "renameTo", file); /* may need a second argument!*/ + JSFILE_CHECK_ONE_ARG("renameTo"); + JSFILE_CHECK_NATIVE("renameTo"); + JSFILE_CHECK_CLOSED("renameTo"); + + dest = RESOLVE_PATH(cx, JS_GetStringBytes(JS_ValueToString(cx, argv[0]))); + + if (PR_Rename(file->path, dest)==PR_SUCCESS){ + /* copy the new filename */ + JS_free(cx, file->path); + file->path = dest; + *rval = JSVAL_TRUE; + return JS_TRUE; + }else{ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_RENAME_FAILED, file->path, dest); + goto out; + } +out: + *rval = JSVAL_FALSE; + return JS_FALSE; +} + +static JSBool +file_flush(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + + SECURITY_CHECK(cx, NULL, "flush", file); + JSFILE_CHECK_NATIVE("flush"); + JSFILE_CHECK_OPEN("flush"); + + if (PR_Sync(file->handle)==PR_SUCCESS){ + *rval = JSVAL_TRUE; + return JS_TRUE; + }else{ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OP_FAILED, "flush", file->path); + goto out; + } +out: + *rval = JSVAL_FALSE; + return JS_FALSE; +} + +static JSBool +file_write(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + JSString *str; + int32 count; + uintN i; + + SECURITY_CHECK(cx, NULL, "write", file); + JSFILE_CHECK_WRITE; + + for (i = 0; itype); + if (count==-1){ + *rval = JSVAL_FALSE; + return JS_FALSE; + } + } + + *rval = JSVAL_TRUE; + return JS_TRUE; +out: + *rval = JSVAL_FALSE; + return JS_FALSE; +} + +static JSBool +file_writeln(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + JSString *str; + + SECURITY_CHECK(cx, NULL, "writeln", file); + JSFILE_CHECK_WRITE; + + /* don't report an error here */ + if(!file_write(cx, obj, argc, argv, rval)) return JS_FALSE; + /* don't do security here -- we passed the check in file_write */ + str = JS_NewStringCopyZ(cx, "\n"); + + if (js_FileWrite(cx, file, JS_GetStringChars(str), JS_GetStringLength(str), + file->type)==-1){ + *rval = JSVAL_FALSE; + return JS_FALSE; + } + + /* eol causes flush if hasAutoflush is turned on */ + if (file->hasAutoflush) + file_flush(cx, obj, 0, NULL, rval); + + *rval = JSVAL_TRUE; + return JS_TRUE; +out: + *rval = JSVAL_FALSE; + return JS_FALSE; +} + +static JSBool +file_writeAll(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + jsuint i; + jsuint limit; + JSObject *array; + JSObject *elem; + jsval elemval; + + SECURITY_CHECK(cx, NULL, "writeAll", file); + JSFILE_CHECK_ONE_ARG("writeAll"); + JSFILE_CHECK_WRITE; + + if (!JS_IsArrayObject(cx, JSVAL_TO_OBJECT(argv[0]))) { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_FIRST_ARGUMENT_WRITEALL_NOT_ARRAY_ERROR); + goto out; + } + + array = JSVAL_TO_OBJECT(argv[0]); + + JS_GetArrayLength(cx, array, &limit); + + for (i = 0; i262144)?262144:want; * arbitrary size limitation */ + + buf = JS_malloc(cx, want*sizeof buf[0]); + if (!buf) goto out; + + count = js_FileRead(cx, file, buf, want, file->type); + if (count>0) { + str = JS_NewUCStringCopyN(cx, buf, count); + *rval = STRING_TO_JSVAL(str); + JS_free(cx, buf); + return JS_TRUE; + } else { + JS_free(cx, buf); + goto out; + } +out: + *rval = JSVAL_FALSE; + return JS_FALSE; +} + +static JSBool +file_readln(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + JSString *str; + jschar *buf; + int32 offset; + intN room; + jschar data, data2; + JSBool endofline; + + SECURITY_CHECK(cx, NULL, "readln", file); + JSFILE_CHECK_READ; + + if (!file->linebuffer) { + buf = JS_malloc(cx, MAX_LINE_LENGTH*(sizeof data)); + if (!buf) goto out; + file->linebuffer = JS_NewUCString(cx, buf, MAX_LINE_LENGTH); + } + room = JS_GetStringLength(file->linebuffer); + offset = 0; + + /* XXX TEST ME!! TODO: yes, please do */ + for(;;) { + if (!js_FileRead(cx, file, &data, 1, file->type)) { + endofline = JS_FALSE; + goto loop; + } + switch (data) { + case '\n' : + endofline = JS_TRUE; + goto loop; + case '\r' : + if (!js_FileRead(cx, file, &data2, 1, file->type)) { + endofline = JS_TRUE; + goto loop; + } + if (data2!='\n') { /* We read one char too far. Buffer it. */ + file->charBuffer = data2; + file->charBufferUsed = JS_TRUE; + } + endofline = JS_TRUE; + goto loop; + default: + if (--room < 0) { + buf = JS_malloc(cx, (offset+MAX_LINE_LENGTH)*sizeof data); + if (!buf) return JS_FALSE; + room = MAX_LINE_LENGTH-1; + memcpy(buf, JS_GetStringChars(file->linebuffer), + JS_GetStringLength(file->linebuffer)); + /* what follows may not be the cleanest way. */ + file->linebuffer->chars = buf; + file->linebuffer->length = offset+MAX_LINE_LENGTH; + } + file->linebuffer->chars[offset++] = data; + break; + } + } +loop: + file->linebuffer->chars[offset] = 0; + if ((endofline==JS_TRUE)) { + str = JS_NewUCStringCopyN(cx, JS_GetStringChars(file->linebuffer), + offset); + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; + }else{ + goto out; + } +out: + *rval = JSVAL_NULL; + return JS_FALSE; +} + +static JSBool +file_readAll(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + JSObject *array; + jsint len; + jsval line; + + SECURITY_CHECK(cx, NULL, "readAll", file); + JSFILE_CHECK_READ; + + array = JS_NewArrayObject(cx, 0, NULL); + len = 0; + + while(file_readln(cx, obj, 0, NULL, &line)){ + JS_SetElement(cx, array, len, &line); + len++; + } + + *rval = OBJECT_TO_JSVAL(array); + return JS_TRUE; +out: + *rval = JSVAL_FALSE; + return JS_FALSE; +} + +static JSBool +file_seek(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + int32 toskip; + int32 pos; + + SECURITY_CHECK(cx, NULL, "seek", file); + JSFILE_CHECK_ONE_ARG("seek"); + JSFILE_CHECK_NATIVE("seek"); + JSFILE_CHECK_READ; + + if (!JS_ValueToInt32(cx, argv[0], &toskip)){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_FIRST_ARGUMENT_MUST_BE_A_NUMBER, "seek", argv[0]); + goto out; + } + + if(!file->hasRandomAccess){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_NO_RANDOM_ACCESS, file->path); + goto out; + } + + if(js_isDirectory(cx, file)){ + JS_ReportWarning(cx,"Seek on directories is not supported, proceeding"); + goto out; + } + + pos = js_FileSeek(cx, file, toskip, file->type); + + if (pos!=-1) { + *rval = INT_TO_JSVAL(pos); + return JS_TRUE; + } +out: + *rval = JSVAL_VOID; + return JS_FALSE; +} + +static JSBool +file_list(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + PRDir *dir; + PRDirEntry *entry; + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + JSObject *array; + JSObject *eachFile; + jsint len; + jsval v; + JSRegExp *re = NULL; + JSFunction *func = NULL; + JSString *str; + jsval args[1]; + char *filePath; + + SECURITY_CHECK(cx, NULL, "list", file); + JSFILE_CHECK_NATIVE("list"); + + if (argc==1) { + if (JSVAL_IS_REGEXP(cx, argv[0])) { + re = JS_GetPrivate(cx, JSVAL_TO_OBJECT(argv[0])); + }else + if (JSVAL_IS_FUNCTION(cx, argv[0])) { + func = JS_GetPrivate(cx, JSVAL_TO_OBJECT(argv[0])); + }else{ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_FIRST_ARGUMENT_MUST_BE_A_FUNCTION_OR_REGEX, argv[0]); + goto out; + } + } + + if (!js_isDirectory(cx, file)) { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_CANNOT_DO_LIST_ON_A_FILE, file->path); + goto out; + } + + dir = PR_OpenDir(file->path); + if(!dir){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OP_FAILED, "open", file->path); + goto out; + } + + /* create JSArray here... */ + array = JS_NewArrayObject(cx, 0, NULL); + len = 0; + + while ((entry = PR_ReadDir(dir, PR_SKIP_BOTH))!=NULL) { + /* first, check if we have a regexp */ + if (re!=NULL) { + size_t index = 0; + + str = JS_NewStringCopyZ(cx, entry->name); + if(!js_ExecuteRegExp(cx, re, str, &index, JS_TRUE, &v)){ + /* don't report anything here */ + goto out; + } + /* not matched! */ + if (JSVAL_IS_NULL(v)) { + continue; + } + }else + if (func!=NULL) { + str = JS_NewStringCopyZ(cx, entry->name); + args[0] = STRING_TO_JSVAL(str); + if(!JS_CallFunction(cx, obj, func, 1, args, &v)){ + goto out; + } + + if (v==JSVAL_FALSE) { + continue; + } + } + + filePath = js_combinePath(cx, file->path, (char*)entry->name); + + eachFile = js_NewFileObject(cx, filePath); + JS_free(cx, filePath); + if (!eachFile){ + JS_ReportWarning(cx, "File %s cannot be retrieved", filePath); + continue; + } + v = OBJECT_TO_JSVAL(eachFile); + JS_SetElement(cx, array, len, &v); + JS_SetProperty(cx, array, entry->name, &v); + len++; + } + + if(PR_CloseDir(dir)!=PR_SUCCESS){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OP_FAILED, "close", file->path); + goto out; + } + *rval = OBJECT_TO_JSVAL(array); + return JS_TRUE; +out: + *rval = JSVAL_NULL; + return JS_FALSE; +} + +static JSBool +file_mkdir(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + + SECURITY_CHECK(cx, NULL, "mkdir", file); + JSFILE_CHECK_ONE_ARG("mkdir"); + JSFILE_CHECK_NATIVE("mkdir"); + + /* if the current file is not a directory, find out the directory name */ + if (!js_isDirectory(cx, file)) { + char *dir = js_fileDirectoryName(cx, file->path); + JSObject *dirObj = js_NewFileObject(cx, dir); + + JS_free(cx, dir); + + /* call file_mkdir with the right set of parameters if needed */ + if (file_mkdir(cx, dirObj, argc, argv, rval)) + return JS_TRUE; + else + goto out; + }else{ + char *dirName = JS_GetStringBytes(JS_ValueToString(cx, argv[0])); + char *fullName; + + fullName = js_combinePath(cx, file->path, dirName); + if (PR_MkDir(fullName, 0755)==PR_SUCCESS){ + *rval = JSVAL_TRUE; + JS_free(cx, fullName); + return JS_TRUE; + }else{ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OP_FAILED, "mkdir", fullName); + JS_free(cx, fullName); + goto out; + } + } +out: + *rval = JSVAL_FALSE; + return JS_FALSE; +} + +static JSBool +file_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval*rval) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + + *rval = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, file->path)); + return JS_TRUE; +} + +static JSBool +file_toURL(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + char url[MAX_PATH_LENGTH]; + jschar *urlChars; + + JSFILE_CHECK_NATIVE("toURL"); + + sprintf(url, "file://%s", file->path); + /* TODO: js_escape in jsstr.h may go away at some point */ + + urlChars = js_InflateString(cx, url, strlen(url)); + if (urlChars == NULL) return JS_FALSE; + *rval = STRING_TO_JSVAL(js_NewString(cx, urlChars, strlen(url), 0)); + if (!js_str_escape(cx, obj, 0, rval, rval)) return JS_FALSE; + + return JS_TRUE; +out: + *rval = JSVAL_VOID; + return JS_FALSE; +} + + +static void +file_finalize(JSContext *cx, JSObject *obj) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + + if(file){ + /* close the file before exiting */ + if(file->isOpen && !file->isNative){ + jsval vp; + file_close(cx, obj, 0, NULL, &vp); + } + + if (file->path) + JS_free(cx, file->path); + + JS_free(cx, file); + } +} + +/* + Allocates memory for the file object, sets fields to defaults. +*/ +static JSFile* +file_init(JSContext *cx, JSObject *obj, char *bytes) +{ + JSFile *file; + + file = JS_malloc(cx, sizeof *file); + if (!file) return NULL; + memset(file, 0 , sizeof *file); + + js_ResetAttributes(file); + + file->path = RESOLVE_PATH(cx, bytes); + + if (!JS_SetPrivate(cx, obj, file)) { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_CANNOT_SET_PRIVATE_FILE, file->path); + JS_free(cx, file); + return NULL; + }else + return file; +} + +/* Returns a JSObject. This function is globally visible */ +JS_PUBLIC_API(JSObject*) +js_NewFileObject(JSContext *cx, char *filename) +{ + JSObject *obj; + JSFile *file; + + obj = JS_NewObject(cx, &file_class, NULL, NULL); + if (!obj){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OBJECT_CREATION_FAILED, "js_NewFileObject"); + return NULL; + } + file = file_init(cx, obj, filename); + if(!file) return NULL; + return obj; +} + +/* Internal function, used for cases which NSPR file support doesn't cover */ +JSObject* +js_NewFileObjectFromFILE(JSContext *cx, FILE *nativehandle, char *filename, + int32 mode, JSBool open, JSBool randomAccess) +{ + JSObject *obj; + JSFile *file; +#ifdef XP_MAC + JS_ReportWarning(cx, "Native files are not fully supported on the MAC"); +#endif + + obj = JS_NewObject(cx, &file_class, NULL, NULL); + if (!obj){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OBJECT_CREATION_FAILED, "js_NewFileObjectFromFILE"); + return NULL; + } + file = file_init(cx, obj, filename); + if(!file) return NULL; + + file->nativehandle = nativehandle; + + /* free result of RESOLVE_PATH from file_init. */ + JS_ASSERT(file->path != NULL); + JS_free(cx, file->path); + + file->path = strdup(filename); + file->isOpen = open; + file->mode = mode; + file->hasRandomAccess = randomAccess; + file->isNative = JS_TRUE; + return obj; +} + +/* + Real file constructor that is called from JavaScript. + Basically, does error processing and calls file_init. +*/ +static JSBool +file_constructor(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSString *str; + JSFile *file; + + str = (argc==0)?JS_InternString(cx, ""):JS_ValueToString(cx, argv[0]); + + if (!str){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_FIRST_ARGUMENT_CONSTRUCTOR_NOT_STRING_ERROR, argv[0]); + goto out; + } + + file = file_init(cx, obj, JS_GetStringBytes(str)); + if (!file) goto out; + + SECURITY_CHECK(cx, NULL, "constructor", file); + + return JS_TRUE; +out: + *rval = JSVAL_VOID; + return JS_FALSE; +} + +/* -------------------- File methods and properties ------------------------- */ +static JSFunctionSpec file_functions[] = { + { "open", file_open, 0}, + { "close", file_close, 0}, + { "remove", file_remove, 0}, + { "copyTo", file_copyTo, 0}, + { "renameTo", file_renameTo, 0}, + { "flush", file_flush, 0}, + { "seek", file_seek, 0}, + { "read", file_read, 0}, + { "readln", file_readln, 0}, + { "readAll", file_readAll, 0}, + { "write", file_write, 0}, + { "writeln", file_writeln, 0}, + { "writeAll", file_writeAll, 0}, + { "list", file_list, 0}, + { "mkdir", file_mkdir, 0}, + { "toString", file_toString, 0}, + { "toURL", file_toURL, 0}, + {0} +}; + +enum file_tinyid { + FILE_LENGTH = -2, + FILE_PARENT = -3, + FILE_PATH = -4, + FILE_NAME = -5, + FILE_ISDIR = -6, + FILE_ISFILE = -7, + FILE_EXISTS = -8, + FILE_CANREAD = -9, + FILE_CANWRITE = -10, + FILE_OPEN = -11, + FILE_TYPE = -12, + FILE_MODE = -13, + FILE_CREATED = -14, + FILE_MODIFIED = -15, + FILE_SIZE = -16, + FILE_RANDOMACCESS = -17, + FILE_POSITION = -18, + FILE_APPEND = -19, + FILE_REPLACE = -20, + FILE_AUTOFLUSH = -21, + FILE_ISNATIVE = -22, +}; + +static JSPropertySpec file_props[] = { + {"length", FILE_LENGTH, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"parent", FILE_PARENT, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"path", FILE_PATH, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"name", FILE_NAME, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"isDirectory", FILE_ISDIR, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"isFile", FILE_ISFILE, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"exists", FILE_EXISTS, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"canRead", FILE_CANREAD, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"canWrite", FILE_CANWRITE, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"canAppend", FILE_APPEND, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"canReplace", FILE_REPLACE, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"isOpen", FILE_OPEN, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"type", FILE_TYPE, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"mode", FILE_MODE, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"creationTime", FILE_CREATED, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"lastModified", FILE_MODIFIED, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"size", FILE_SIZE, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"hasRandomAccess", FILE_RANDOMACCESS, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"hasAutoFlush", FILE_AUTOFLUSH, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"position", FILE_POSITION, JSPROP_ENUMERATE }, + {"isNative", FILE_ISNATIVE, JSPROP_ENUMERATE | JSPROP_READONLY }, + {0} +}; + +/* ------------------------- Property getter/setter ------------------------- */ +static JSBool +file_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + char *str; + jsint tiny; + PRFileInfo info; + JSBool flag; + PRExplodedTime + expandedTime; + + tiny = JSVAL_TO_INT(id); + if(!file) return JS_TRUE; + + switch (tiny) { + case FILE_PARENT: + SECURITY_CHECK(cx, NULL, "parent", file); + *vp = js_parent(cx, file); + break; + case FILE_PATH: + *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, file->path)); + break; + case FILE_NAME: + *vp = js_name(cx, file); + break; + case FILE_ISDIR: + SECURITY_CHECK(cx, NULL, "isDirectory", file); + *vp = BOOLEAN_TO_JSVAL(js_isDirectory(cx, file)); + break; + case FILE_ISFILE: + SECURITY_CHECK(cx, NULL, "isFile", file); + *vp = BOOLEAN_TO_JSVAL(js_isFile(cx, file)); + break; + case FILE_EXISTS: + SECURITY_CHECK(cx, NULL, "exists", file); + *vp = BOOLEAN_TO_JSVAL(js_exists(cx, file)); + break; + case FILE_ISNATIVE: + SECURITY_CHECK(cx, NULL, "isNative", file); + *vp = BOOLEAN_TO_JSVAL(file->isNative); + break; + case FILE_CANREAD: + SECURITY_CHECK(cx, NULL, "canRead", file); + *vp = BOOLEAN_TO_JSVAL(js_canRead(cx, file)); + break; + case FILE_CANWRITE: + SECURITY_CHECK(cx, NULL, "canWrite", file); + *vp = BOOLEAN_TO_JSVAL(js_canWrite(cx, file)); + break; + case FILE_OPEN: + SECURITY_CHECK(cx, NULL, "isOpen", file); + *vp = BOOLEAN_TO_JSVAL(file->isOpen); + break; + case FILE_APPEND : + SECURITY_CHECK(cx, NULL, "canAppend", file); + JSFILE_CHECK_OPEN("canAppend"); + *vp = BOOLEAN_TO_JSVAL(!file->isNative && + (file->mode&PR_APPEND)==PR_APPEND); + break; + case FILE_REPLACE : + SECURITY_CHECK(cx, NULL, "canReplace", file); + JSFILE_CHECK_OPEN("canReplace"); + *vp = BOOLEAN_TO_JSVAL(!file->isNative && + (file->mode&PR_TRUNCATE)==PR_TRUNCATE); + break; + case FILE_AUTOFLUSH : + SECURITY_CHECK(cx, NULL, "hasAutoFlush", file); + JSFILE_CHECK_OPEN("hasAutoFlush"); + *vp = BOOLEAN_TO_JSVAL(!file->isNative && file->hasAutoflush); + break; + case FILE_TYPE: + SECURITY_CHECK(cx, NULL, "type", file); + JSFILE_CHECK_OPEN("type"); + if(js_isDirectory(cx, file)){ + *vp = JSVAL_VOID; + break; + } + + switch (file->type) { + case ASCII: + *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, asciistring)); + break; + case UTF8: + *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, utfstring)); + break; + case UCS2: + *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, unicodestring)); + break; + default: + JS_ReportWarning(cx, "Unsupported file type %d, proceeding", + file->type); + } + break; + case FILE_MODE: + SECURITY_CHECK(cx, NULL, "mode", file); + JSFILE_CHECK_OPEN("mode"); + str = (char*)JS_malloc(cx, MODE_SIZE); + str[0] = '\0'; + flag = JS_FALSE; + + if ((file->mode&PR_RDONLY)==PR_RDONLY) { + if (flag) strcat(str, ","); + strcat(str, "read"); + flag = JS_TRUE; + } + if ((file->mode&PR_WRONLY)==PR_WRONLY) { + if (flag) strcat(str, ","); + strcat(str, "write"); + flag = JS_TRUE; + } + if ((file->mode&PR_RDWR)==PR_RDWR) { + if (flag) strcat(str, ","); + strcat(str, "readWrite"); + flag = JS_TRUE; + } + if ((file->mode&PR_APPEND)==PR_APPEND) { + if (flag) strcat(str, ","); + strcat(str, "append"); + flag = JS_TRUE; + } + if ((file->mode&PR_CREATE_FILE)==PR_CREATE_FILE) { + if (flag) strcat(str, ","); + strcat(str, "create"); + flag = JS_TRUE; + } + if ((file->mode&PR_TRUNCATE)==PR_TRUNCATE) { + if (flag) strcat(str, ","); + strcat(str, "replace"); + flag = JS_TRUE; + } + if (file->hasAutoflush) { + if (flag) strcat(str, ","); + strcat(str, "hasAutoFlush"); + flag = JS_TRUE; + } + *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, str)); + JS_free(cx, str); + break; + case FILE_CREATED: + SECURITY_CHECK(cx, NULL, "creationTime", file); + JSFILE_CHECK_NATIVE("creationTime"); + if(((file->isOpen)? + PR_GetOpenFileInfo(file->handle, &info): + PR_GetFileInfo(file->path, &info))!=PR_SUCCESS){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_CANNOT_ACCESS_FILE_STATUS, file->path); + goto out; + } + + PR_ExplodeTime(info.creationTime, PR_LocalTimeParameters,&expandedTime); + *vp = OBJECT_TO_JSVAL(js_NewDateObject(cx, expandedTime.tm_year, + expandedTime.tm_month, + expandedTime.tm_mday, + expandedTime.tm_hour, + expandedTime.tm_min, + expandedTime.tm_sec)); + break; + case FILE_MODIFIED: + SECURITY_CHECK(cx, NULL, "lastModified", file); + JSFILE_CHECK_NATIVE("lastModified"); + if(((file->isOpen)? + PR_GetOpenFileInfo(file->handle, &info): + PR_GetFileInfo(file->path, &info))!=PR_SUCCESS){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_CANNOT_ACCESS_FILE_STATUS, file->path); + goto out; + } + + PR_ExplodeTime(info.modifyTime, PR_LocalTimeParameters, &expandedTime); + *vp = OBJECT_TO_JSVAL(js_NewDateObject(cx, expandedTime.tm_year, + expandedTime.tm_month, + expandedTime.tm_mday, + expandedTime.tm_hour, + expandedTime.tm_min, + expandedTime.tm_sec)); + break; + case FILE_SIZE: + SECURITY_CHECK(cx, NULL, "size", file); + *vp = js_size(cx, file); + break; + case FILE_LENGTH: + SECURITY_CHECK(cx, NULL, "length", file); + JSFILE_CHECK_NATIVE("length"); + + if (js_isDirectory(cx, file)) { /* XXX debug me */ + PRDir *dir; + PRDirEntry *entry; + jsint count = 0; + + if(!(dir = PR_OpenDir(file->path))){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_CANNOT_OPEN_DIR, file->path); + goto out; + } + + while ((entry = PR_ReadDir(dir, PR_SKIP_BOTH))) { + count++; + } + + if(!PR_CloseDir(dir)){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OP_FAILED, "close", file->path); + + goto out; + } + + *vp = INT_TO_JSVAL(count); + break; + }else{ + /* return file size */ + *vp = js_size(cx, file); + } + break; + case FILE_RANDOMACCESS: + SECURITY_CHECK(cx, NULL, "hasRandomAccess", file); + JSFILE_CHECK_OPEN("hasRandomAccess"); + *vp = BOOLEAN_TO_JSVAL(file->hasRandomAccess); + break; + case FILE_POSITION: + SECURITY_CHECK(cx, NULL, "position", file); + JSFILE_CHECK_NATIVE("position"); + JSFILE_CHECK_OPEN("position"); + + if(!file->hasRandomAccess){ + JS_ReportWarning(cx, "File %s doesn't support random access, can't report the position, proceeding"); + *vp = JSVAL_VOID; + break; + } + + if (file->isOpen && js_isFile(cx, file)) { + int pos = PR_Seek(file->handle, 0, PR_SEEK_CUR); + if(pos!=-1){ + *vp = INT_TO_JSVAL(pos); + }else{ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_CANNOT_REPORT_POSITION, file->path); + goto out; + } + }else { + JS_ReportWarning(cx, "File %s is closed or not a plain file," + " can't report position, proceeding"); + goto out; + } + break; + default: + SECURITY_CHECK(cx, NULL, "file_access", file); + /* this is some other property -- try to use the dir["file"] syntax */ + if(js_isDirectory(cx, file)){ + PRDir *dir = NULL; + PRDirEntry *entry = NULL; + char *prop_name = JS_GetStringBytes(JS_ValueToString(cx, id)); + + /* no native files past this point */ + dir = PR_OpenDir(file->path); + if(!dir) { + /* This is probably not a directory */ + JS_ReportWarning(cx, "Can't open directory %s", file->path); + return JS_FALSE; + } + + while((entry = PR_ReadDir(dir, PR_SKIP_NONE))!=NULL){ + if(!strcmp(entry->name, prop_name)){ + str = js_combinePath(cx, file->path, prop_name); + *vp = OBJECT_TO_JSVAL(js_NewFileObject(cx, str)); + JS_free(cx, str); + return JS_TRUE; + } + } + } + } + return JS_TRUE; +out: + *vp = JSVAL_VOID; + return JS_FALSE; +} + +static JSBool +file_setProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + jsint slot; + + if (JSVAL_IS_STRING(id)){ + return JS_TRUE; + } + + slot = JSVAL_TO_INT(id); + + switch (slot) { + /* File.position = 10 */ + case FILE_POSITION: + SECURITY_CHECK(cx, NULL, "set_position", file); + JSFILE_CHECK_NATIVE("set_position"); + + if(!file->hasRandomAccess){ + JS_ReportWarning(cx, "File %s doesn't support random access, can't " + "report the position, proceeding"); + goto out; + } + + if (file->isOpen && js_isFile(cx, file)) { + int32 pos; + int32 offset; + + if (!JS_ValueToInt32(cx, *vp, &offset)){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_FIRST_ARGUMENT_MUST_BE_A_NUMBER, "position", *vp); + goto out; + } + + pos = PR_Seek(file->handle, offset, PR_SEEK_SET); + + if(pos!=-1){ + *vp = INT_TO_JSVAL(pos); + }else{ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_CANNOT_SET_POSITION, file->path); + goto out; + } + } else { + JS_ReportWarning(cx, "File %s is closed or not a file, can't set " + "position, proceeding", file->path); + goto out; + } + } + + return JS_TRUE; +out: + *vp = JSVAL_VOID; + return JS_FALSE; +} + +/* + File.currentDir = new File("D:\") or File.currentDir = "D:\" +*/ +static JSBool +file_currentDirSetter(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + JSObject *rhsObject; + char *path; + JSFile *file = JS_GetInstancePrivate(cx, rhsObject, &file_class, NULL); + + /* Look at the rhs and extract a file object from it */ + if (JSVAL_IS_OBJECT(*vp)){ + if (JS_InstanceOf(cx, rhsObject, &file_class, NULL)){ + /* Braindamaged rhs -- just return the old value */ + if (file && (!js_exists(cx, file) || !js_isDirectory(cx, file))){ + JS_GetProperty(cx, obj, CURRENTDIR_PROPERTY, vp); + goto out; + }else{ + rhsObject = JSVAL_TO_OBJECT(*vp); + chdir(file->path); + return JS_TRUE; + } + }else + goto out; + }else{ + path = JS_GetStringBytes(JS_ValueToString(cx, *vp)); + rhsObject = js_NewFileObject(cx, path); + if (!rhsObject) goto out; + + if (!file || !js_exists(cx, file) || !js_isDirectory(cx, file)){ + JS_GetProperty(cx, obj, CURRENTDIR_PROPERTY, vp); + }else{ + *vp = OBJECT_TO_JSVAL(rhsObject); + chdir(path); + } + } + return JS_TRUE; +out: + *vp = JSVAL_VOID; + return JS_FALSE; +} + +/* Declare class */ +static JSClass file_class = { + FILE_CONSTRUCTOR, JSCLASS_HAS_PRIVATE, + JS_PropertyStub, JS_PropertyStub, file_getProperty, file_setProperty, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, file_finalize +}; + +/* -------------------- Functions exposed to the outside -------------------- */ +JS_PUBLIC_API(JSObject*) +js_InitFileClass(JSContext *cx, JSObject* obj, JSBool initStandardStreams) +{ + JSObject *file, *ctor, *afile; + jsval vp; + char *currentdir; + char separator[2]; + + file = JS_InitClass(cx, obj, NULL, &file_class, file_constructor, 1, + file_props, file_functions, NULL, NULL); + if (!file) { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_INIT_FAILED); + return NULL; + } + + ctor = JS_GetConstructor(cx, file); + if (!ctor) return NULL; + + /* Define CURRENTDIR property. We are doing this to get a + slash at the end of the current dir */ + afile = js_NewFileObject(cx, CURRENT_DIR); + currentdir = JS_malloc(cx, MAX_PATH_LENGTH); + currentdir = getcwd(currentdir, MAX_PATH_LENGTH); + afile = js_NewFileObject(cx, currentdir); + JS_free(cx, currentdir); + vp = OBJECT_TO_JSVAL(afile); + JS_DefinePropertyWithTinyId(cx, ctor, CURRENTDIR_PROPERTY, 0, vp, + JS_PropertyStub, file_currentDirSetter, + JSPROP_ENUMERATE | JSPROP_READONLY ); + + if(initStandardStreams){ + /* Code to create stdin, stdout, and stderr. Insert in the appropriate place. */ + /* Define input */ + vp = OBJECT_TO_JSVAL(js_NewFileObjectFromFILE(cx, stdin, + STDINPUT_NAME, PR_RDONLY, JS_TRUE, JS_FALSE)); + JS_SetProperty(cx, ctor, "input", &vp); + + /* Define output */ + vp = OBJECT_TO_JSVAL(js_NewFileObjectFromFILE(cx, stdout, + STDOUTPUT_NAME, PR_WRONLY, JS_TRUE, JS_FALSE)); + JS_SetProperty(cx, ctor, "output", &vp); + + /* Define error */ + vp = OBJECT_TO_JSVAL(js_NewFileObjectFromFILE(cx, stderr, + STDERROR_NAME, PR_WRONLY, JS_TRUE, JS_FALSE)); + JS_SetProperty(cx, ctor, "error", &vp); + } + separator[0] = FILESEPARATOR; + separator[1] = '\0'; + vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, separator)); + JS_DefinePropertyWithTinyId(cx, ctor, SEPARATOR_PROPERTY, 0, vp, + JS_PropertyStub, JS_PropertyStub, + JSPROP_ENUMERATE | JSPROP_READONLY ); + return file; +} +#endif /* JS_HAS_FILE_OBJECT */ diff --git a/src/dom/js/jsfile.h b/src/dom/js/jsfile.h new file mode 100644 index 000000000..47a8692d8 --- /dev/null +++ b/src/dom/js/jsfile.h @@ -0,0 +1,50 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef _jsfile_h__ +#define _jsfile_h__ + +#if JS_HAS_FILE_OBJECT +extern JS_PUBLIC_API(JSObject*) +js_InitFileClass(JSContext *cx, JSObject* obj, JSBool initStandardStreams); + +extern JS_PUBLIC_API(JSObject*) +js_NewFileObject(JSContext *cx, char *bytes); +#endif /* JS_HAS_FILE_OBJECT */ +#endif /* _jsfile_h__ */ diff --git a/src/dom/js/jsfun.c b/src/dom/js/jsfun.c new file mode 100644 index 000000000..93cd4009d --- /dev/null +++ b/src/dom/js/jsfun.c @@ -0,0 +1,2059 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS function support. + */ +#include "jsstddef.h" +#include +#include "jstypes.h" +#include "jsbit.h" +#include "jsutil.h" /* Added by JSIFY */ +#include "jsapi.h" +#include "jsarray.h" +#include "jsatom.h" +#include "jscntxt.h" +#include "jsconfig.h" +#include "jsdbgapi.h" +#include "jsfun.h" +#include "jsgc.h" +#include "jsinterp.h" +#include "jslock.h" +#include "jsnum.h" +#include "jsobj.h" +#include "jsopcode.h" +#include "jsparse.h" +#include "jsscan.h" +#include "jsscope.h" +#include "jsscript.h" +#include "jsstr.h" +#include "jsexn.h" + +/* Generic function/call/arguments tinyids -- also reflected bit numbers. */ +enum { + CALL_ARGUMENTS = -1, /* predefined arguments local variable */ + CALL_CALLEE = -2, /* reference to active function's object */ + ARGS_LENGTH = -3, /* number of actual args, arity if inactive */ + ARGS_CALLEE = -4, /* reference from arguments to active funobj */ + FUN_ARITY = -5, /* number of formal parameters; desired argc */ + FUN_NAME = -6, /* function name, "" if anonymous */ + FUN_CALLER = -7 /* Function.prototype.caller, backward compat */ +}; + +#if JSFRAME_OVERRIDE_BITS < 8 +# error "not enough override bits in JSStackFrame.flags!" +#endif + +#define TEST_OVERRIDE_BIT(fp, tinyid) \ + ((fp)->flags & JS_BIT(JSFRAME_OVERRIDE_SHIFT - ((tinyid) + 1))) + +#define SET_OVERRIDE_BIT(fp, tinyid) \ + ((fp)->flags |= JS_BIT(JSFRAME_OVERRIDE_SHIFT - ((tinyid) + 1))) + +#if JS_HAS_ARGS_OBJECT + +JSBool +js_GetArgsValue(JSContext *cx, JSStackFrame *fp, jsval *vp) +{ + JSObject *argsobj; + + if (TEST_OVERRIDE_BIT(fp, CALL_ARGUMENTS)) { + JS_ASSERT(fp->callobj); + return OBJ_GET_PROPERTY(cx, fp->callobj, + (jsid) cx->runtime->atomState.argumentsAtom, + vp); + } + argsobj = js_GetArgsObject(cx, fp); + if (!argsobj) + return JS_FALSE; + *vp = OBJECT_TO_JSVAL(argsobj); + return JS_TRUE; +} + +static JSBool +MarkArgDeleted(JSContext *cx, JSStackFrame *fp, uintN slot) +{ + JSObject *argsobj; + jsval bmapval, bmapint; + size_t nbits, nbytes; + jsbitmap *bitmap; + + argsobj = fp->argsobj; + (void) JS_GetReservedSlot(cx, argsobj, 0, &bmapval); + nbits = JS_MAX(fp->argc, fp->fun->nargs); + JS_ASSERT(slot < nbits); + if (JSVAL_IS_VOID(bmapval)) { + if (nbits <= JSVAL_INT_BITS) { + bmapint = 0; + bitmap = (jsbitmap *) &bmapint; + } else { + nbytes = JS_HOWMANY(nbits, JS_BITS_PER_WORD) * sizeof(jsbitmap); + bitmap = (jsbitmap *) JS_malloc(cx, nbytes); + if (!bitmap) + return JS_FALSE; + memset(bitmap, 0, nbytes); + bmapval = PRIVATE_TO_JSVAL(bitmap); + JS_SetReservedSlot(cx, argsobj, 0, bmapval); + } + } else { + if (nbits <= JSVAL_INT_BITS) { + bmapint = JSVAL_TO_INT(bmapval); + bitmap = (jsbitmap *) &bmapint; + } else { + bitmap = (jsbitmap *) JSVAL_TO_PRIVATE(bmapval); + } + } + JS_SET_BIT(bitmap, slot); + if (bitmap == (jsbitmap *) &bmapint) { + bmapval = INT_TO_JSVAL(bmapint); + JS_SetReservedSlot(cx, argsobj, 0, bmapval); + } + return JS_TRUE; +} + +/* NB: Infallible predicate, false does not mean error/exception. */ +static JSBool +ArgWasDeleted(JSContext *cx, JSStackFrame *fp, uintN slot) +{ + JSObject *argsobj; + jsval bmapval, bmapint; + jsbitmap *bitmap; + + argsobj = fp->argsobj; + (void) JS_GetReservedSlot(cx, argsobj, 0, &bmapval); + if (JSVAL_IS_VOID(bmapval)) + return JS_FALSE; + if (JS_MAX(fp->argc, fp->fun->nargs) <= JSVAL_INT_BITS) { + bmapint = JSVAL_TO_INT(bmapval); + bitmap = (jsbitmap *) &bmapint; + } else { + bitmap = (jsbitmap *) JSVAL_TO_PRIVATE(bmapval); + } + return JS_TEST_BIT(bitmap, slot) != 0; +} + +JSBool +js_GetArgsProperty(JSContext *cx, JSStackFrame *fp, jsid id, + JSObject **objp, jsval *vp) +{ + jsval val; + JSObject *obj; + uintN slot; + + if (TEST_OVERRIDE_BIT(fp, CALL_ARGUMENTS)) { + JS_ASSERT(fp->callobj); + if (!OBJ_GET_PROPERTY(cx, fp->callobj, + (jsid) cx->runtime->atomState.argumentsAtom, + &val)) { + return JS_FALSE; + } + if (JSVAL_IS_PRIMITIVE(val)) { + obj = js_ValueToNonNullObject(cx, val); + if (!obj) + return JS_FALSE; + } else { + obj = JSVAL_TO_OBJECT(val); + } + *objp = obj; + return OBJ_GET_PROPERTY(cx, obj, id, vp); + } + + *objp = NULL; + *vp = JSVAL_VOID; + if (JSVAL_IS_INT(id)) { + slot = (uintN) JSVAL_TO_INT(id); + if (slot < JS_MAX(fp->argc, fp->fun->nargs)) { + if (fp->argsobj && ArgWasDeleted(cx, fp, slot)) + return OBJ_GET_PROPERTY(cx, fp->argsobj, id, vp); + *vp = fp->argv[slot]; + } + } else { + if (id == (jsid) cx->runtime->atomState.lengthAtom) { + if (fp->argsobj && TEST_OVERRIDE_BIT(fp, ARGS_LENGTH)) + return OBJ_GET_PROPERTY(cx, fp->argsobj, id, vp); + *vp = INT_TO_JSVAL((jsint) fp->argc); + } + } + return JS_TRUE; +} + +JSObject * +js_GetArgsObject(JSContext *cx, JSStackFrame *fp) +{ + JSObject *argsobj; + + /* Create an arguments object for fp only if it lacks one. */ + JS_ASSERT(fp->fun); + argsobj = fp->argsobj; + if (argsobj) + return argsobj; + + /* Link the new object to fp so it can get actual argument values. */ + argsobj = js_NewObject(cx, &js_ArgumentsClass, NULL, NULL); + if (!argsobj || !JS_SetPrivate(cx, argsobj, fp)) { + cx->newborn[GCX_OBJECT] = NULL; + return NULL; + } + fp->argsobj = argsobj; + return argsobj; +} + +static JSBool +args_enumerate(JSContext *cx, JSObject *obj); + +JSBool +js_PutArgsObject(JSContext *cx, JSStackFrame *fp) +{ + JSObject *argsobj; + jsval bmapval, rval; + JSBool ok; + JSRuntime *rt; + + /* + * Reuse args_enumerate here to reflect fp's actual arguments as indexed + * elements of argsobj. Do this first, before clearing and freeing the + * deleted argument slot bitmap, because args_enumerate depends on that. + */ + argsobj = fp->argsobj; + ok = args_enumerate(cx, argsobj); + + /* + * Now clear the deleted argument number bitmap slot and free the bitmap, + * if one was actually created due to 'delete arguments[0]' or similar. + */ + (void) JS_GetReservedSlot(cx, argsobj, 0, &bmapval); + if (!JSVAL_IS_VOID(bmapval)) { + JS_SetReservedSlot(cx, argsobj, 0, JSVAL_VOID); + if (JS_MAX(fp->argc, fp->fun->nargs) > JSVAL_INT_BITS) + JS_free(cx, JSVAL_TO_PRIVATE(bmapval)); + } + + /* + * Now get the prototype properties so we snapshot fp->fun and fp->argc + * before fp goes away. + */ + rt = cx->runtime; + ok &= js_GetProperty(cx, argsobj, (jsid)rt->atomState.calleeAtom, &rval); + ok &= js_SetProperty(cx, argsobj, (jsid)rt->atomState.calleeAtom, &rval); + ok &= js_GetProperty(cx, argsobj, (jsid)rt->atomState.lengthAtom, &rval); + ok &= js_SetProperty(cx, argsobj, (jsid)rt->atomState.lengthAtom, &rval); + + /* + * Clear the private pointer to fp, which is about to go away (js_Invoke). + * Do this last because the args_enumerate and js_GetProperty calls above + * need to follow the private slot to find fp. + */ + ok &= JS_SetPrivate(cx, argsobj, NULL); + fp->argsobj = NULL; + return ok; +} + +static JSBool +args_delProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + jsint slot; + JSStackFrame *fp; + + if (!JSVAL_IS_INT(id)) + return JS_TRUE; + fp = (JSStackFrame *) + JS_GetInstancePrivate(cx, obj, &js_ArgumentsClass, NULL); + if (!fp) + return JS_TRUE; + JS_ASSERT(fp->argsobj); + JS_ASSERT(fp->fun); + + slot = JSVAL_TO_INT(id); + switch (slot) { + case ARGS_CALLEE: + case ARGS_LENGTH: + SET_OVERRIDE_BIT(fp, slot); + break; + + default: + if ((uintN)slot < JS_MAX(fp->argc, fp->fun->nargs) && + !MarkArgDeleted(cx, fp, slot)) { + return JS_FALSE; + } + break; + } + return JS_TRUE; +} + +static JSBool +args_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + jsint slot; + JSStackFrame *fp; + + if (!JSVAL_IS_INT(id)) + return JS_TRUE; + fp = (JSStackFrame *) + JS_GetInstancePrivate(cx, obj, &js_ArgumentsClass, NULL); + if (!fp) + return JS_TRUE; + JS_ASSERT(fp->argsobj); + JS_ASSERT(fp->fun); + + slot = JSVAL_TO_INT(id); + switch (slot) { + case ARGS_CALLEE: + if (!TEST_OVERRIDE_BIT(fp, slot)) + *vp = fp->argv ? fp->argv[-2] : OBJECT_TO_JSVAL(fp->fun->object); + break; + + case ARGS_LENGTH: + if (!TEST_OVERRIDE_BIT(fp, slot)) + *vp = INT_TO_JSVAL((jsint)fp->argc); + break; + + default: + if ((uintN)slot < JS_MAX(fp->argc, fp->fun->nargs) && + !ArgWasDeleted(cx, fp, slot)) { + *vp = fp->argv[slot]; + } + break; + } + return JS_TRUE; +} + +static JSBool +args_setProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + JSStackFrame *fp; + jsint slot; + + if (!JSVAL_IS_INT(id)) + return JS_TRUE; + fp = (JSStackFrame *) + JS_GetInstancePrivate(cx, obj, &js_ArgumentsClass, NULL); + if (!fp) + return JS_TRUE; + JS_ASSERT(fp->argsobj); + JS_ASSERT(fp->fun); + + slot = JSVAL_TO_INT(id); + switch (slot) { + case ARGS_CALLEE: + case ARGS_LENGTH: + SET_OVERRIDE_BIT(fp, slot); + break; + + default: + if ((uintN)slot < JS_MAX(fp->argc, fp->fun->nargs) && + !ArgWasDeleted(cx, fp, slot)) { + fp->argv[slot] = *vp; + } + break; + } + return JS_TRUE; +} + +static JSBool +args_resolve(JSContext *cx, JSObject *obj, jsval id, uintN flags, + JSObject **objp) +{ + JSStackFrame *fp; + uintN slot; + JSString *str; + JSAtom *atom; + intN tinyid; + jsval value; + + *objp = NULL; + fp = (JSStackFrame *) + JS_GetInstancePrivate(cx, obj, &js_ArgumentsClass, NULL); + if (!fp) + return JS_TRUE; + JS_ASSERT(fp->argsobj); + JS_ASSERT(fp->fun); + + if (JSVAL_IS_INT(id)) { + slot = JSVAL_TO_INT(id); + if (slot < JS_MAX(fp->argc, fp->fun->nargs) && + !ArgWasDeleted(cx, fp, slot)) { + /* XXX ECMA specs DontEnum, contrary to other array-like objects */ + if (!js_DefineProperty(cx, obj, (jsid) id, fp->argv[slot], + args_getProperty, args_setProperty, + JSVERSION_IS_ECMA(cx->version) + ? 0 + : JSPROP_ENUMERATE, + NULL)) { + return JS_FALSE; + } + *objp = obj; + } + } else { + str = JSVAL_TO_STRING(id); + atom = cx->runtime->atomState.lengthAtom; + if (str == ATOM_TO_STRING(atom)) { + tinyid = ARGS_LENGTH; + value = INT_TO_JSVAL(fp->argc); + } else { + atom = cx->runtime->atomState.calleeAtom; + if (str == ATOM_TO_STRING(atom)) { + tinyid = ARGS_CALLEE; + value = fp->argv ? fp->argv[-2] + : OBJECT_TO_JSVAL(fp->fun->object); + } else { + atom = NULL; + + /* Quell GCC overwarnings. */ + tinyid = 0; + value = JSVAL_NULL; + } + } + + if (atom && !TEST_OVERRIDE_BIT(fp, tinyid)) { + if (!js_DefineNativeProperty(cx, obj, (jsid) atom, value, + args_getProperty, args_setProperty, 0, + SPROP_HAS_SHORTID, tinyid, NULL)) { + return JS_FALSE; + } + *objp = obj; + } + } + + return JS_TRUE; +} + +static JSBool +args_enumerate(JSContext *cx, JSObject *obj) +{ + JSStackFrame *fp; + JSObject *pobj; + JSProperty *prop; + uintN slot, nargs; + + fp = (JSStackFrame *) + JS_GetInstancePrivate(cx, obj, &js_ArgumentsClass, NULL); + if (!fp) + return JS_TRUE; + JS_ASSERT(fp->argsobj); + JS_ASSERT(fp->fun); + + /* + * Trigger reflection with value snapshot in args_resolve using a series + * of js_LookupProperty calls. We handle length, callee, and the indexed + * argument properties. We know that args_resolve covers all these cases + * and creates direct properties of obj, but that it may fail to resolve + * length or callee if overridden. + */ + if (!js_LookupProperty(cx, obj, (jsid) cx->runtime->atomState.lengthAtom, + &pobj, &prop)) { + return JS_FALSE; + } + if (prop) + OBJ_DROP_PROPERTY(cx, pobj, prop); + + if (!js_LookupProperty(cx, obj, (jsid) cx->runtime->atomState.calleeAtom, + &pobj, &prop)) { + return JS_FALSE; + } + if (prop) + OBJ_DROP_PROPERTY(cx, pobj, prop); + + nargs = JS_MAX(fp->argc, fp->fun->nargs); + for (slot = 0; slot < nargs; slot++) { + if (!js_LookupProperty(cx, obj, (jsid) INT_TO_JSVAL((jsint)slot), + &pobj, &prop)) { + return JS_FALSE; + } + if (prop) + OBJ_DROP_PROPERTY(cx, pobj, prop); + } + return JS_TRUE; +} + +/* + * The Arguments class is not initialized via JS_InitClass, and must not be, + * because its name is "Object". Per ECMA, that causes instances of it to + * delegate to the object named by Object.prototype. It also ensures that + * arguments.toString() returns "[object Object]". + * + * The JSClass functions below collaborate to lazily reflect and synchronize + * actual argument values, argument count, and callee function object stored + * in a JSStackFrame with their corresponding property values in the frame's + * arguments object. + */ +JSClass js_ArgumentsClass = { + js_Object_str, + JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE | JSCLASS_HAS_RESERVED_SLOTS(1), + JS_PropertyStub, args_delProperty, + args_getProperty, args_setProperty, + args_enumerate, (JSResolveOp) args_resolve, + JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS +}; + +#endif /* JS_HAS_ARGS_OBJECT */ + +#if JS_HAS_CALL_OBJECT + +JSObject * +js_GetCallObject(JSContext *cx, JSStackFrame *fp, JSObject *parent) +{ + JSObject *callobj, *funobj; + + /* Create a call object for fp only if it lacks one. */ + JS_ASSERT(fp->fun); + callobj = fp->callobj; + if (callobj) + return callobj; + JS_ASSERT(fp->fun); + + /* The default call parent is its function's parent (static link). */ + if (!parent) { + funobj = fp->argv ? JSVAL_TO_OBJECT(fp->argv[-2]) : fp->fun->object; + if (funobj) + parent = OBJ_GET_PARENT(cx, funobj); + } + + /* Create the call object and link it to its stack frame. */ + callobj = js_NewObject(cx, &js_CallClass, NULL, parent); + if (!callobj || !JS_SetPrivate(cx, callobj, fp)) { + cx->newborn[GCX_OBJECT] = NULL; + return NULL; + } + fp->callobj = callobj; + + /* Make callobj be the scope chain and the variables object. */ + fp->scopeChain = callobj; + fp->varobj = callobj; + return callobj; +} + +static JSBool +call_enumerate(JSContext *cx, JSObject *obj); + +JSBool +js_PutCallObject(JSContext *cx, JSStackFrame *fp) +{ + JSObject *callobj; + JSBool ok; + jsid argsid; + jsval aval; + + /* + * Reuse call_enumerate here to reflect all actual args and vars into the + * call object from fp. + */ + callobj = fp->callobj; + if (!callobj) + return JS_TRUE; + ok = call_enumerate(cx, callobj); + + /* + * Get the arguments object to snapshot fp's actual argument values. + */ + if (fp->argsobj) { + argsid = (jsid) cx->runtime->atomState.argumentsAtom; + ok &= js_GetProperty(cx, callobj, argsid, &aval); + ok &= js_SetProperty(cx, callobj, argsid, &aval); + ok &= js_PutArgsObject(cx, fp); + } + + /* + * Clear the private pointer to fp, which is about to go away (js_Invoke). + * Do this last because the call_enumerate and js_GetProperty calls above + * need to follow the private slot to find fp. + */ + ok &= JS_SetPrivate(cx, callobj, NULL); + fp->callobj = NULL; + return ok; +} + +static JSPropertySpec call_props[] = { + {js_arguments_str, CALL_ARGUMENTS, JSPROP_PERMANENT,0,0}, + {"__callee__", CALL_CALLEE, 0,0,0}, + {0,0,0,0,0} +}; + +static JSBool +call_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + JSStackFrame *fp; + jsint slot; + + if (!JSVAL_IS_INT(id)) + return JS_TRUE; + fp = (JSStackFrame *) JS_GetPrivate(cx, obj); + if (!fp) + return JS_TRUE; + JS_ASSERT(fp->fun); + + slot = JSVAL_TO_INT(id); + switch (slot) { + case CALL_ARGUMENTS: + if (!TEST_OVERRIDE_BIT(fp, slot)) { + JSObject *argsobj = js_GetArgsObject(cx, fp); + if (!argsobj) + return JS_FALSE; + *vp = OBJECT_TO_JSVAL(argsobj); + } + break; + + case CALL_CALLEE: + if (!TEST_OVERRIDE_BIT(fp, slot)) + *vp = fp->argv ? fp->argv[-2] : OBJECT_TO_JSVAL(fp->fun->object); + break; + + default: + if ((uintN)slot < JS_MAX(fp->argc, fp->fun->nargs)) + *vp = fp->argv[slot]; + break; + } + return JS_TRUE; +} + +static JSBool +call_setProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + JSStackFrame *fp; + jsint slot; + + if (!JSVAL_IS_INT(id)) + return JS_TRUE; + fp = (JSStackFrame *) JS_GetPrivate(cx, obj); + if (!fp) + return JS_TRUE; + JS_ASSERT(fp->fun); + + slot = JSVAL_TO_INT(id); + switch (slot) { + case CALL_ARGUMENTS: + case CALL_CALLEE: + SET_OVERRIDE_BIT(fp, slot); + break; + + default: + if ((uintN)slot < JS_MAX(fp->argc, fp->fun->nargs)) + fp->argv[slot] = *vp; + break; + } + return JS_TRUE; +} + +JSBool +js_GetCallVariable(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + JSStackFrame *fp; + + JS_ASSERT(JSVAL_IS_INT(id)); + fp = (JSStackFrame *) JS_GetPrivate(cx, obj); + if (fp) { + /* XXX no jsint slot commoning here to avoid MSVC1.52 crashes */ + if ((uintN)JSVAL_TO_INT(id) < fp->nvars) + *vp = fp->vars[JSVAL_TO_INT(id)]; + } + return JS_TRUE; +} + +JSBool +js_SetCallVariable(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + JSStackFrame *fp; + + JS_ASSERT(JSVAL_IS_INT(id)); + fp = (JSStackFrame *) JS_GetPrivate(cx, obj); + if (fp) { + /* XXX jsint slot is block-local here to avoid MSVC1.52 crashes */ + jsint slot = JSVAL_TO_INT(id); + if ((uintN)slot < fp->nvars) + fp->vars[slot] = *vp; + } + return JS_TRUE; +} + +static JSBool +call_enumerate(JSContext *cx, JSObject *obj) +{ + JSStackFrame *fp; + JSObject *funobj; + JSScope *scope; + JSScopeProperty *sprop, *cprop; + JSPropertyOp getter; + jsval *vec; + JSProperty *prop; + + fp = (JSStackFrame *) JS_GetPrivate(cx, obj); + if (!fp) + return JS_TRUE; + + /* + * Do not enumerate a cloned function object at fp->argv[-2], it may have + * gained its own (mutable) scope (e.g., a brutally-shared XUL script sets + * the clone's prototype property). We must enumerate the function object + * that was decorated with parameter and local variable properties by the + * compiler when the compiler created fp->fun, namely fp->fun->object. + * + * Contrast with call_resolve, where we prefer fp->argv[-2], because we'll + * use js_LookupProperty to find any overridden properties in that object, + * if it was a mutated clone; and if not, we will search its prototype, + * fp->fun->object, to find compiler-created params and locals. + */ + funobj = fp->fun->object; + if (!funobj) + return JS_TRUE; + + /* + * Reflect actual args from fp->argv for formal parameters, and local vars + * and functions in fp->vars for declared variables and nested-at-top-level + * local functions. + */ + scope = OBJ_SCOPE(funobj); + for (sprop = SCOPE_LAST_PROP(scope); sprop; sprop = sprop->parent) { + getter = sprop->getter; + if (getter == js_GetArgument) + vec = fp->argv; + else if (getter == js_GetLocalVariable) + vec = fp->vars; + else + continue; + + /* Trigger reflection in call_resolve by doing a lookup. */ + if (!js_LookupProperty(cx, obj, sprop->id, &obj, &prop)) + return JS_FALSE; + JS_ASSERT(obj && prop); + cprop = (JSScopeProperty *)prop; + LOCKED_OBJ_SET_SLOT(obj, cprop->slot, vec[sprop->shortid]); + OBJ_DROP_PROPERTY(cx, obj, prop); + } + + return JS_TRUE; +} + +static JSBool +call_resolve(JSContext *cx, JSObject *obj, jsval id, uintN flags, + JSObject **objp) +{ + JSStackFrame *fp; + JSObject *funobj; + JSString *str; + JSAtom *atom; + JSObject *obj2; + JSScopeProperty *sprop; + jsid propid; + JSPropertyOp getter, setter; + uintN attrs, slot, nslots, spflags; + jsval *vp, value; + intN shortid; + + fp = (JSStackFrame *) JS_GetPrivate(cx, obj); + if (!fp) + return JS_TRUE; + JS_ASSERT(fp->fun); + + if (!JSVAL_IS_STRING(id)) + return JS_TRUE; + + funobj = fp->argv ? JSVAL_TO_OBJECT(fp->argv[-2]) : fp->fun->object; + if (!funobj) + return JS_TRUE; + + str = JSVAL_TO_STRING(id); + atom = js_AtomizeString(cx, str, 0); + if (!atom) + return JS_FALSE; + if (!js_LookupProperty(cx, funobj, (jsid)atom, &obj2, + (JSProperty **)&sprop)) { + return JS_FALSE; + } + + if (sprop && OBJ_IS_NATIVE(obj2)) { + propid = sprop->id; + getter = sprop->getter; + attrs = sprop->attrs & ~JSPROP_SHARED; + slot = (uintN) sprop->shortid; + OBJ_DROP_PROPERTY(cx, obj2, (JSProperty *)sprop); + if (getter == js_GetArgument || getter == js_GetLocalVariable) { + if (getter == js_GetArgument) { + vp = fp->argv; + nslots = JS_MAX(fp->argc, fp->fun->nargs); + getter = setter = NULL; + } else { + vp = fp->vars; + nslots = fp->nvars; + getter = js_GetCallVariable; + setter = js_SetCallVariable; + } + if (slot < nslots) { + value = vp[slot]; + spflags = SPROP_HAS_SHORTID; + shortid = (intN) slot; + } else { + value = JSVAL_VOID; + spflags = 0; + shortid = 0; + } + if (!js_DefineNativeProperty(cx, obj, propid, value, + getter, setter, attrs, + spflags, shortid, NULL)) { + return JS_FALSE; + } + *objp = obj; + } + } + return JS_TRUE; +} + +static JSBool +call_convert(JSContext *cx, JSObject *obj, JSType type, jsval *vp) +{ + JSStackFrame *fp; + + if (type == JSTYPE_FUNCTION) { + fp = (JSStackFrame *) JS_GetPrivate(cx, obj); + if (fp) { + JS_ASSERT(fp->fun); + *vp = fp->argv ? fp->argv[-2] : OBJECT_TO_JSVAL(fp->fun->object); + } + } + return JS_TRUE; +} + +JSClass js_CallClass = { + js_Call_str, + JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE, + JS_PropertyStub, JS_PropertyStub, + call_getProperty, call_setProperty, + call_enumerate, (JSResolveOp)call_resolve, + call_convert, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS +}; + +#endif /* JS_HAS_CALL_OBJECT */ + +/* SHARED because fun_getProperty always computes a new value. */ +#define FUNCTION_PROP_ATTRS (JSPROP_READONLY|JSPROP_PERMANENT|JSPROP_SHARED) + +static JSPropertySpec function_props[] = { + {js_arguments_str, CALL_ARGUMENTS, FUNCTION_PROP_ATTRS,0,0}, + {js_arity_str, FUN_ARITY, FUNCTION_PROP_ATTRS,0,0}, + {js_length_str, ARGS_LENGTH, FUNCTION_PROP_ATTRS,0,0}, + {js_name_str, FUN_NAME, FUNCTION_PROP_ATTRS,0,0}, + {js_caller_str, FUN_CALLER, FUNCTION_PROP_ATTRS,0,0}, + {0,0,0,0,0} +}; + +static JSBool +fun_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + jsint slot; + JSFunction *fun; + JSStackFrame *fp; +#if defined _MSC_VER &&_MSC_VER <= 800 + /* MSVC1.5 coredumps */ + jsval bogus = *vp; +#endif + + if (!JSVAL_IS_INT(id)) + return JS_TRUE; + slot = JSVAL_TO_INT(id); + + /* No valid function object should lack private data, but check anyway. */ + fun = (JSFunction *)JS_GetInstancePrivate(cx, obj, &js_FunctionClass, NULL); + if (!fun) + return JS_TRUE; + + /* Find fun's top-most activation record. */ + for (fp = cx->fp; fp && (fp->fun != fun || (fp->flags & JSFRAME_SPECIAL)); + fp = fp->down) { + continue; + } + + switch (slot) { + case CALL_ARGUMENTS: +#if JS_HAS_ARGS_OBJECT + /* Warn if strict about f.arguments or equivalent unqualified uses. */ + if (!JS_ReportErrorFlagsAndNumber(cx, + JSREPORT_WARNING | JSREPORT_STRICT, + js_GetErrorMessage, NULL, + JSMSG_DEPRECATED_USAGE, + js_arguments_str)) { + return JS_FALSE; + } + if (fp) { + if (!js_GetArgsValue(cx, fp, vp)) + return JS_FALSE; + } else { + *vp = JSVAL_NULL; + } + break; +#else /* !JS_HAS_ARGS_OBJECT */ + *vp = OBJECT_TO_JSVAL(fp ? obj : NULL); + break; +#endif /* !JS_HAS_ARGS_OBJECT */ + + case ARGS_LENGTH: + if (!JSVERSION_IS_ECMA(cx->version)) + *vp = INT_TO_JSVAL((jsint)(fp && fp->fun ? fp->argc : fun->nargs)); + else + case FUN_ARITY: + *vp = INT_TO_JSVAL((jsint)fun->nargs); + break; + + case FUN_NAME: + *vp = fun->atom + ? ATOM_KEY(fun->atom) + : STRING_TO_JSVAL(cx->runtime->emptyString); + break; + + case FUN_CALLER: + while (fp && (fp->flags & JSFRAME_SKIP_CALLER) && fp->down) + fp = fp->down; + if (fp && fp->down && fp->down->fun && fp->down->argv) + *vp = fp->down->argv[-2]; + else + *vp = JSVAL_NULL; + if (!JSVAL_IS_PRIMITIVE(*vp) && cx->runtime->checkObjectAccess) { + id = ATOM_KEY(cx->runtime->atomState.callerAtom); + if (!cx->runtime->checkObjectAccess(cx, obj, id, JSACC_READ, vp)) + return JS_FALSE; + } + break; + + default: + /* XXX fun[0] and fun.arguments[0] are equivalent. */ + if (fp && fp->fun && (uintN)slot < fp->fun->nargs) +#if defined _MSC_VER &&_MSC_VER <= 800 + /* MSVC1.5 coredumps */ + if (bogus == *vp) +#endif + *vp = fp->argv[slot]; + break; + } + + return JS_TRUE; +} + +static JSBool +fun_resolve(JSContext *cx, JSObject *obj, jsval id, uintN flags, + JSObject **objp) +{ + JSFunction *fun; + JSString *str; + JSAtom *prototypeAtom; + + if (!JSVAL_IS_STRING(id)) + return JS_TRUE; + + /* No valid function object should lack private data, but check anyway. */ + fun = (JSFunction *)JS_GetInstancePrivate(cx, obj, &js_FunctionClass, NULL); + if (!fun || !fun->object) + return JS_TRUE; + + /* No need to reflect fun.prototype in 'fun.prototype = ...'. */ + if (flags & JSRESOLVE_ASSIGNING) + return JS_TRUE; + + /* + * Ok, check whether id is 'prototype' and bootstrap the function object's + * prototype property. + */ + str = JSVAL_TO_STRING(id); + prototypeAtom = cx->runtime->atomState.classPrototypeAtom; + if (str == ATOM_TO_STRING(prototypeAtom)) { + JSObject *proto, *parentProto; + jsval pval; + + proto = parentProto = NULL; + if (fun->object != obj && fun->object) { + /* + * Clone of a function: make its prototype property value have the + * same class as the clone-parent's prototype. + */ + if (!OBJ_GET_PROPERTY(cx, fun->object, (jsid)prototypeAtom, &pval)) + return JS_FALSE; + if (JSVAL_IS_OBJECT(pval)) + parentProto = JSVAL_TO_OBJECT(pval); + } + + /* + * Beware of the wacky case of a user function named Object -- trying + * to find a prototype for that will recur back here ad perniciem. + */ + if (!parentProto && fun->atom == cx->runtime->atomState.ObjectAtom) + return JS_TRUE; + + /* + * If resolving "prototype" in a clone, clone the parent's prototype. + * Pass the constructor's (obj's) parent as the prototype parent, to + * avoid defaulting to parentProto.constructor.__parent__. + */ + proto = js_NewObject(cx, &js_ObjectClass, parentProto, + OBJ_GET_PARENT(cx, obj)); + if (!proto) + return JS_FALSE; + + /* + * ECMA says that constructor.prototype is DontEnum | DontDelete for + * user-defined functions, but DontEnum | ReadOnly | DontDelete for + * native "system" constructors such as Object or Function. So lazily + * set the former here in fun_resolve, but eagerly define the latter + * in JS_InitClass, with the right attributes. + */ + if (!js_SetClassPrototype(cx, obj, proto, JSPROP_PERMANENT)) { + cx->newborn[GCX_OBJECT] = NULL; + return JS_FALSE; + } + *objp = obj; + } + + return JS_TRUE; +} + +static JSBool +fun_convert(JSContext *cx, JSObject *obj, JSType type, jsval *vp) +{ + switch (type) { + case JSTYPE_FUNCTION: + *vp = OBJECT_TO_JSVAL(obj); + return JS_TRUE; + default: + return js_TryValueOf(cx, obj, type, vp); + } +} + +static void +fun_finalize(JSContext *cx, JSObject *obj) +{ + JSFunction *fun; + + /* No valid function object should lack private data, but check anyway. */ + fun = (JSFunction *) JS_GetPrivate(cx, obj); + if (!fun) + return; + if (fun->object == obj) + fun->object = NULL; + JS_ATOMIC_DECREMENT(&fun->nrefs); + if (fun->nrefs) + return; + if (fun->script) + js_DestroyScript(cx, fun->script); + JS_free(cx, fun); +} + +#if JS_HAS_XDR + +#include "jsxdrapi.h" + +enum { + JSXDR_FUNARG = 1, + JSXDR_FUNVAR = 2, + JSXDR_FUNCONST = 3 +}; + +/* XXX store parent and proto, if defined */ +static JSBool +fun_xdrObject(JSXDRState *xdr, JSObject **objp) +{ + JSContext *cx; + JSFunction *fun; + JSString *atomstr; + char *propname; + JSScopeProperty *sprop; + uint32 userid; /* NB: holds a signed int-tagged jsval */ + JSAtom *atom; + uintN i, n, dupflag; + uint32 type; +#ifdef DEBUG + uintN nvars = 0, nargs = 0; +#endif + + cx = xdr->cx; + if (xdr->mode == JSXDR_ENCODE) { + /* + * No valid function object should lack private data, but fail soft + * (return true, no error report) in case one does due to API pilot + * or internal error. + */ + fun = (JSFunction *) JS_GetPrivate(cx, *objp); + if (!fun) + return JS_TRUE; + atomstr = fun->atom ? ATOM_TO_STRING(fun->atom) : NULL; + } else { + fun = js_NewFunction(cx, NULL, NULL, 0, 0, NULL, NULL); + if (!fun) + return JS_FALSE; + atomstr = NULL; + } + + if (!JS_XDRStringOrNull(xdr, &atomstr) || + !JS_XDRUint16(xdr, &fun->nargs) || + !JS_XDRUint16(xdr, &fun->extra) || + !JS_XDRUint16(xdr, &fun->nvars) || + !JS_XDRUint8(xdr, &fun->flags)) { + return JS_FALSE; + } + + /* do arguments and local vars */ + if (fun->object) { + n = fun->nargs + fun->nvars; + if (xdr->mode == JSXDR_ENCODE) { + JSScope *scope; + JSScopeProperty **spvec, *auto_spvec[8]; + void *mark; + + if (n <= sizeof auto_spvec / sizeof auto_spvec[0]) { + spvec = auto_spvec; + mark = NULL; + } else { + mark = JS_ARENA_MARK(&cx->tempPool); + JS_ARENA_ALLOCATE_CAST(spvec, JSScopeProperty **, &cx->tempPool, + n * sizeof(JSScopeProperty *)); + if (!spvec) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + } + scope = OBJ_SCOPE(fun->object); + for (sprop = SCOPE_LAST_PROP(scope); sprop; + sprop = sprop->parent) { + if (sprop->getter == js_GetArgument) { + JS_ASSERT(nargs++ <= fun->nargs); + spvec[sprop->shortid] = sprop; + } else if (sprop->getter == js_GetLocalVariable) { + JS_ASSERT(nvars++ <= fun->nvars); + spvec[fun->nargs + sprop->shortid] = sprop; + } + } + for (i = 0; i < n; i++) { + sprop = spvec[i]; + JS_ASSERT(sprop->flags & SPROP_HAS_SHORTID); + type = (i < fun->nargs) + ? JSXDR_FUNARG + : (sprop->attrs & JSPROP_READONLY) + ? JSXDR_FUNCONST + : JSXDR_FUNVAR; + userid = INT_TO_JSVAL(sprop->shortid); + /* XXX lossy conversion, need new XDR version for ECMAv3 */ + propname = JS_GetStringBytes(ATOM_TO_STRING((JSAtom *)sprop->id)); + if (!propname || + !JS_XDRUint32(xdr, &type) || + !JS_XDRUint32(xdr, &userid) || + !JS_XDRCString(xdr, &propname)) { + if (mark) + JS_ARENA_RELEASE(&cx->tempPool, mark); + return JS_FALSE; + } + } + if (mark) + JS_ARENA_RELEASE(&cx->tempPool, mark); + } else { + JSPropertyOp getter, setter; + + for (i = n; i != 0; i--) { + uintN attrs = JSPROP_ENUMERATE | JSPROP_PERMANENT; + + if (!JS_XDRUint32(xdr, &type) || + !JS_XDRUint32(xdr, &userid) || + !JS_XDRCString(xdr, &propname)) { + return JS_FALSE; + } + JS_ASSERT(type == JSXDR_FUNARG || type == JSXDR_FUNVAR || + type == JSXDR_FUNCONST); + if (type == JSXDR_FUNARG) { + getter = js_GetArgument; + setter = js_SetArgument; + JS_ASSERT(nargs++ <= fun->nargs); + } else if (type == JSXDR_FUNVAR || type == JSXDR_FUNCONST) { + getter = js_GetLocalVariable; + setter = js_SetLocalVariable; + if (type == JSXDR_FUNCONST) + attrs |= JSPROP_READONLY; + JS_ASSERT(nvars++ <= fun->nvars); + } else { + getter = NULL; + setter = NULL; + } + atom = js_Atomize(cx, propname, strlen(propname), 0); + JS_free(cx, propname); + if (!atom) + return JS_FALSE; + + /* Flag duplicate argument if atom is bound in fun->object. */ + dupflag = SCOPE_GET_PROPERTY(OBJ_SCOPE(fun->object), (jsid)atom) + ? SPROP_IS_DUPLICATE + : 0; + + if (!js_AddNativeProperty(cx, fun->object, (jsid)atom, + getter, setter, SPROP_INVALID_SLOT, + attrs | JSPROP_SHARED, + SPROP_HAS_SHORTID | dupflag, + JSVAL_TO_INT(userid))) { + return JS_FALSE; + } + } + } + } + + if (!js_XDRScript(xdr, &fun->script, NULL)) + return JS_FALSE; + + if (xdr->mode == JSXDR_DECODE) { + *objp = fun->object; + if (atomstr) { + fun->atom = js_AtomizeString(cx, atomstr, 0); + if (!fun->atom) + return JS_FALSE; + + if (!OBJ_DEFINE_PROPERTY(cx, cx->globalObject, + (jsid)fun->atom, OBJECT_TO_JSVAL(*objp), + NULL, NULL, JSPROP_ENUMERATE, + NULL)) { + return JS_FALSE; + } + } + + js_CallNewScriptHook(cx, fun->script, fun); + } + + return JS_TRUE; +} + +#else /* !JS_HAS_XDR */ + +#define fun_xdrObject NULL + +#endif /* !JS_HAS_XDR */ + +#if JS_HAS_INSTANCEOF + +/* + * [[HasInstance]] internal method for Function objects: fetch the .prototype + * property of its 'this' parameter, and walks the prototype chain of v (only + * if v is an object) returning true if .prototype is found. + */ +static JSBool +fun_hasInstance(JSContext *cx, JSObject *obj, jsval v, JSBool *bp) +{ + jsval pval, cval; + JSString *str; + JSObject *proto, *obj2; + JSFunction *cfun, *ofun; + + if (!OBJ_GET_PROPERTY(cx, obj, + (jsid)cx->runtime->atomState.classPrototypeAtom, + &pval)) { + return JS_FALSE; + } + + if (JSVAL_IS_PRIMITIVE(pval)) { + /* + * Throw a runtime error if instanceof is called on a function that + * has a non-object as its .prototype value. + */ + str = js_DecompileValueGenerator(cx, -1, OBJECT_TO_JSVAL(obj), NULL); + if (str) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_PROTOTYPE, JS_GetStringBytes(str)); + } + return JS_FALSE; + } + + proto = JSVAL_TO_OBJECT(pval); + if (!js_IsDelegate(cx, proto, v, bp)) + return JS_FALSE; + + if (!*bp && !JSVAL_IS_PRIMITIVE(v)) { + /* + * Extension for "brutal sharing" of standard class constructors: if + * a script is compiled using a single, shared set of constructors, in + * particular Function and RegExp, but executed many times using other + * sets of standard constructors, then (/re/ instanceof RegExp), e.g., + * will be false. + * + * We extend instanceof in this case to look for a matching native or + * script underlying the function object found in the 'constructor' + * property of the object in question (which is JSVAL_TO_OBJECT(v)), + * or found in the 'constructor' property of one of its prototypes. + * + * See also jsexn.c, where the *Error constructors are defined, each + * with its own native function, to satisfy (e instanceof Error) even + * when exceptions cross standard-class sharing boundaries. Note that + * Error.prototype may not lie on e's __proto__ chain in that case. + */ + obj2 = JSVAL_TO_OBJECT(v); + do { + if (!OBJ_GET_PROPERTY(cx, obj2, + (jsid)cx->runtime->atomState.constructorAtom, + &cval)) { + return JS_FALSE; + } + + if (JSVAL_IS_FUNCTION(cx, cval)) { + cfun = (JSFunction *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(cval)); + ofun = (JSFunction *) JS_GetPrivate(cx, obj); + if (cfun->native == ofun->native && + cfun->script == ofun->script) { + *bp = JS_TRUE; + break; + } + } + } while ((obj2 = OBJ_GET_PROTO(cx, obj2)) != NULL); + } + + return JS_TRUE; +} + +#else /* !JS_HAS_INSTANCEOF */ + +#define fun_hasInstance NULL + +#endif /* !JS_HAS_INSTANCEOF */ + +static uint32 +fun_mark(JSContext *cx, JSObject *obj, void *arg) +{ + JSFunction *fun; + + fun = (JSFunction *) JS_GetPrivate(cx, obj); + if (fun) { + if (fun->atom) + GC_MARK_ATOM(cx, fun->atom, arg); + if (fun->script) + js_MarkScript(cx, fun->script, arg); + } + return 0; +} + +/* + * Reserve two slots in all function objects for XPConnect. Note that this + * does not bloat every instance, only those on which reserved slots are set, + * and those on which ad-hoc properties are defined. + */ +JSClass js_FunctionClass = { + js_Function_str, + JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE | JSCLASS_HAS_RESERVED_SLOTS(2), + JS_PropertyStub, JS_PropertyStub, + fun_getProperty, JS_PropertyStub, + JS_EnumerateStub, (JSResolveOp)fun_resolve, + fun_convert, fun_finalize, + NULL, NULL, + NULL, NULL, + fun_xdrObject, fun_hasInstance, + fun_mark, 0 +}; + +JSBool +js_fun_toString(JSContext *cx, JSObject *obj, uint32 indent, + uintN argc, jsval *argv, jsval *rval) +{ + jsval fval; + JSFunction *fun; + JSString *str; + + if (!argv) { + JS_ASSERT(JS_ObjectIsFunction(cx, obj)); + } else { + fval = argv[-1]; + if (!JSVAL_IS_FUNCTION(cx, fval)) { + /* + * If we don't have a function to start off with, try converting + * the object to a function. If that doesn't work, complain. + */ + if (JSVAL_IS_OBJECT(fval)) { + obj = JSVAL_TO_OBJECT(fval); + if (!OBJ_GET_CLASS(cx, obj)->convert(cx, obj, JSTYPE_FUNCTION, + &fval)) { + return JS_FALSE; + } + } + if (!JSVAL_IS_FUNCTION(cx, fval)) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_INCOMPATIBLE_PROTO, + js_Function_str, js_toString_str, + JS_GetTypeName(cx, + JS_TypeOfValue(cx, fval))); + return JS_FALSE; + } + } + + obj = JSVAL_TO_OBJECT(fval); + } + + fun = (JSFunction *) JS_GetPrivate(cx, obj); + if (!fun) + return JS_TRUE; + if (argc && !js_ValueToECMAUint32(cx, argv[0], &indent)) + return JS_FALSE; + str = JS_DecompileFunction(cx, fun, (uintN)indent); + if (!str) + return JS_FALSE; + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +static JSBool +fun_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + return js_fun_toString(cx, obj, 0, argc, argv, rval); +} + +#if JS_HAS_TOSOURCE +static JSBool +fun_toSource(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + return js_fun_toString(cx, obj, JS_DONT_PRETTY_PRINT, argc, argv, rval); +} +#endif + +static const char js_call_str[] = "call"; + +#if JS_HAS_CALL_FUNCTION +static JSBool +fun_call(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsval fval, *sp, *oldsp; + void *mark; + uintN i; + JSStackFrame *fp; + JSBool ok; + + if (!OBJ_DEFAULT_VALUE(cx, obj, JSTYPE_FUNCTION, &argv[-1])) + return JS_FALSE; + fval = argv[-1]; + + if (!JSVAL_IS_FUNCTION(cx, fval)) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_INCOMPATIBLE_PROTO, + js_Function_str, js_call_str, + JS_GetStringBytes(JS_ValueToString(cx, fval))); + return JS_FALSE; + } + + if (argc == 0) { + /* Call fun with its parent as the 'this' parameter if no args. */ + obj = OBJ_GET_PARENT(cx, obj); + } else { + /* Otherwise convert the first arg to 'this' and skip over it. */ + if (!js_ValueToObject(cx, argv[0], &obj)) + return JS_FALSE; + argc--; + argv++; + } + + /* Allocate stack space for fval, obj, and the args. */ + sp = js_AllocStack(cx, 2 + argc, &mark); + if (!sp) + return JS_FALSE; + + /* Push fval, obj, and the args. */ + *sp++ = fval; + *sp++ = OBJECT_TO_JSVAL(obj); + for (i = 0; i < argc; i++) + *sp++ = argv[i]; + + /* Lift current frame to include the args and do the call. */ + fp = cx->fp; + oldsp = fp->sp; + fp->sp = sp; + ok = js_Invoke(cx, argc, JSINVOKE_INTERNAL | JSINVOKE_SKIP_CALLER); + + /* Store rval and pop stack back to our frame's sp. */ + *rval = fp->sp[-1]; + fp->sp = oldsp; + js_FreeStack(cx, mark); + return ok; +} +#endif /* JS_HAS_CALL_FUNCTION */ + +#if JS_HAS_APPLY_FUNCTION +static JSBool +fun_apply(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsval fval, *sp, *oldsp; + JSObject *aobj; + jsuint length; + void *mark; + uintN i; + JSBool ok; + JSStackFrame *fp; + + if (argc == 0) { + /* Will get globalObject as 'this' and no other agurments. */ + return fun_call(cx, obj, argc, argv, rval); + } + + if (!OBJ_DEFAULT_VALUE(cx, obj, JSTYPE_FUNCTION, &argv[-1])) + return JS_FALSE; + fval = argv[-1]; + + if (!JSVAL_IS_FUNCTION(cx, fval)) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_INCOMPATIBLE_PROTO, + js_Function_str, "apply", + JS_GetStringBytes(JS_ValueToString(cx, fval))); + return JS_FALSE; + } + + /* Quell GCC overwarnings. */ + aobj = NULL; + length = 0; + + if (argc >= 2) { + /* If the 2nd arg is null or void, call the function with 0 args. */ + if (JSVAL_IS_NULL(argv[1]) || JSVAL_IS_VOID(argv[1])) { + argc = 0; + } else { + /* The second arg must be an array (or arguments object). */ + if (JSVAL_IS_PRIMITIVE(argv[1]) || + (aobj = JSVAL_TO_OBJECT(argv[1]), + OBJ_GET_CLASS(cx, aobj) != &js_ArgumentsClass && + OBJ_GET_CLASS(cx, aobj) != &js_ArrayClass)) + { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_APPLY_ARGS); + return JS_FALSE; + } + if (!js_GetLengthProperty(cx, aobj, &length)) + return JS_FALSE; + } + } + + /* Convert the first arg to 'this' and skip over it. */ + if (!js_ValueToObject(cx, argv[0], &obj)) + return JS_FALSE; + + /* Allocate stack space for fval, obj, and the args. */ + argc = (uintN)JS_MIN(length, ARGC_LIMIT - 1); + sp = js_AllocStack(cx, 2 + argc, &mark); + if (!sp) + return JS_FALSE; + + /* Push fval, obj, and aobj's elements as args. */ + *sp++ = fval; + *sp++ = OBJECT_TO_JSVAL(obj); + for (i = 0; i < argc; i++) { + ok = JS_GetElement(cx, aobj, (jsint)i, sp); + if (!ok) + goto out; + sp++; + } + + /* Lift current frame to include the args and do the call. */ + fp = cx->fp; + oldsp = fp->sp; + fp->sp = sp; + ok = js_Invoke(cx, argc, JSINVOKE_INTERNAL | JSINVOKE_SKIP_CALLER); + + /* Store rval and pop stack back to our frame's sp. */ + *rval = fp->sp[-1]; + fp->sp = oldsp; +out: + js_FreeStack(cx, mark); + return ok; +} +#endif /* JS_HAS_APPLY_FUNCTION */ + +static JSFunctionSpec function_methods[] = { +#if JS_HAS_TOSOURCE + {js_toSource_str, fun_toSource, 0,0,0}, +#endif + {js_toString_str, fun_toString, 1,0,0}, +#if JS_HAS_APPLY_FUNCTION + {"apply", fun_apply, 2,0,0}, +#endif +#if JS_HAS_CALL_FUNCTION + {js_call_str, fun_call, 1,0,0}, +#endif + {0,0,0,0,0} +}; + +JSBool +js_IsIdentifier(JSString *str) +{ + size_t n; + jschar *s, c; + + n = JSSTRING_LENGTH(str); + if (n == 0) + return JS_FALSE; + s = JSSTRING_CHARS(str); + c = *s; + if (!JS_ISIDENT_START(c)) + return JS_FALSE; + for (n--; n != 0; n--) { + c = *++s; + if (!JS_ISIDENT(c)) + return JS_FALSE; + } + return JS_TRUE; +} + +static JSBool +Function(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSStackFrame *fp, *caller; + JSFunction *fun; + JSObject *parent; + uintN i, n, lineno, dupflag; + JSAtom *atom; + const char *filename; + JSObject *obj2; + JSScopeProperty *sprop; + JSString *str, *arg; + void *mark; + JSTokenStream *ts; + JSPrincipals *principals; + jschar *collected_args, *cp; + size_t arg_length, args_length; + JSTokenType tt; + JSBool ok; + + fp = cx->fp; + if (fp && !(fp->flags & JSFRAME_CONSTRUCTING)) { + obj = js_NewObject(cx, &js_FunctionClass, NULL, NULL); + if (!obj) + return JS_FALSE; + *rval = OBJECT_TO_JSVAL(obj); + } + fun = (JSFunction *) JS_GetPrivate(cx, obj); + if (fun) + return JS_TRUE; + +#if JS_HAS_CALL_OBJECT + /* + * NB: (new Function) is not lexically closed by its caller, it's just an + * anonymous function in the top-level scope that its constructor inhabits. + * Thus 'var x = 42; f = new Function("return x"); print(f())' prints 42, + * and so would a call to f from another top-level's script or function. + * + * In older versions, before call objects, a new Function was adopted by + * its running context's globalObject, which might be different from the + * top-level reachable from scopeChain (in HTML frames, e.g.). + */ + parent = OBJ_GET_PARENT(cx, JSVAL_TO_OBJECT(argv[-2])); +#else + /* Set up for dynamic parenting (see js_Invoke in jsinterp.c). */ + parent = NULL; +#endif + + fun = js_NewFunction(cx, obj, NULL, 0, JSFUN_LAMBDA, parent, + JSVERSION_IS_ECMA(cx->version) + ? cx->runtime->atomState.anonymousAtom + : NULL); + + if (!fun) + return JS_FALSE; + + /* + * Function is static and not called directly by other functions in this + * file, therefore it is callable only as a native function by js_Invoke. + * Find the scripted caller, possibly skipping other native frames such as + * are built for Function.prototype.call or .apply activations that invoke + * Function indirectly from a script. + */ + JS_ASSERT(!fp->script && fp->fun && fp->fun->native == Function); + caller = JS_GetScriptedCaller(cx, fp); + if (caller) { + filename = caller->script->filename; + lineno = js_PCToLineNumber(cx, caller->script, caller->pc); + principals = JS_EvalFramePrincipals(cx, fp, caller); + } else { + filename = NULL; + lineno = 0; + principals = NULL; + } + + n = argc ? argc - 1 : 0; + if (n > 0) { + /* + * Collect the function-argument arguments into one string, separated + * by commas, then make a tokenstream from that string, and scan it to + * get the arguments. We need to throw the full scanner at the + * problem, because the argument string can legitimately contain + * comments and linefeeds. XXX It might be better to concatenate + * everything up into a function definition and pass it to the + * compiler, but doing it this way is less of a delta from the old + * code. See ECMA 15.3.2.1. + */ + args_length = 0; + for (i = 0; i < n; i++) { + /* Collect the lengths for all the function-argument arguments. */ + arg = js_ValueToString(cx, argv[i]); + if (!arg) + return JS_FALSE; + argv[i] = STRING_TO_JSVAL(arg); + args_length += JSSTRING_LENGTH(arg); + } + /* Add 1 for each joining comma. */ + args_length += n - 1; + + /* + * Allocate a string to hold the concatenated arguments, including room + * for a terminating 0. Mark cx->tempPool for later release, to free + * collected_args and its tokenstream in one swoop. + */ + mark = JS_ARENA_MARK(&cx->tempPool); + JS_ARENA_ALLOCATE_CAST(cp, jschar *, &cx->tempPool, + (args_length+1) * sizeof(jschar)); + if (!cp) + return JS_FALSE; + collected_args = cp; + + /* + * Concatenate the arguments into the new string, separated by commas. + */ + for (i = 0; i < n; i++) { + arg = JSVAL_TO_STRING(argv[i]); + arg_length = JSSTRING_LENGTH(arg); + (void) js_strncpy(cp, JSSTRING_CHARS(arg), arg_length); + cp += arg_length; + + /* Add separating comma or terminating 0. */ + *cp++ = (i + 1 < n) ? ',' : 0; + } + + /* + * Make a tokenstream (allocated from cx->tempPool) that reads from + * the given string. + */ + ts = js_NewTokenStream(cx, collected_args, args_length, filename, + lineno, principals); + if (!ts) { + JS_ARENA_RELEASE(&cx->tempPool, mark); + return JS_FALSE; + } + + /* The argument string may be empty or contain no tokens. */ + tt = js_GetToken(cx, ts); + if (tt != TOK_EOF) { + for (;;) { + /* + * Check that it's a name. This also implicitly guards against + * TOK_ERROR, which was already reported. + */ + if (tt != TOK_NAME) + goto bad_formal; + + /* + * Get the atom corresponding to the name from the tokenstream; + * we're assured at this point that it's a valid identifier. + */ + atom = CURRENT_TOKEN(ts).t_atom; + if (!js_LookupProperty(cx, obj, (jsid)atom, &obj2, + (JSProperty **)&sprop)) { + goto bad_formal; + } + dupflag = 0; + if (sprop) { + ok = JS_TRUE; + if (obj2 == obj) { + const char *name = js_AtomToPrintableString(cx, atom); + + /* + * A duplicate parameter name. We force a duplicate + * node on the SCOPE_LAST_PROP(scope) list with the + * same id, distinguished by the SPROP_IS_DUPLICATE + * flag, and not mapped by an entry in scope. + */ + JS_ASSERT(sprop->getter == js_GetArgument); + ok = name && + js_ReportCompileErrorNumber(cx, ts, NULL, + JSREPORT_WARNING | + JSREPORT_STRICT, + JSMSG_DUPLICATE_FORMAL, + name); + + dupflag = SPROP_IS_DUPLICATE; + } + OBJ_DROP_PROPERTY(cx, obj2, (JSProperty *)sprop); + if (!ok) + goto bad_formal; + sprop = NULL; + } + if (!js_AddNativeProperty(cx, fun->object, (jsid)atom, + js_GetArgument, js_SetArgument, + SPROP_INVALID_SLOT, + JSPROP_ENUMERATE | JSPROP_PERMANENT | + JSPROP_SHARED, + SPROP_HAS_SHORTID | dupflag, + fun->nargs)) { + goto bad_formal; + } + fun->nargs++; + + /* + * Get the next token. Stop on end of stream. Otherwise + * insist on a comma, get another name, and iterate. + */ + tt = js_GetToken(cx, ts); + if (tt == TOK_EOF) + break; + if (tt != TOK_COMMA) + goto bad_formal; + tt = js_GetToken(cx, ts); + } + } + + /* Clean up. */ + ok = js_CloseTokenStream(cx, ts); + JS_ARENA_RELEASE(&cx->tempPool, mark); + if (!ok) + return JS_FALSE; + } + + if (argc) { + str = js_ValueToString(cx, argv[argc-1]); + } else { + /* Can't use cx->runtime->emptyString because we're called too early. */ + str = js_NewStringCopyZ(cx, js_empty_ucstr, 0); + } + if (!str) + return JS_FALSE; + if (argv) { + /* Use the last arg (or this if argc == 0) as a local GC root. */ + argv[(intN)(argc-1)] = STRING_TO_JSVAL(str); + } + + mark = JS_ARENA_MARK(&cx->tempPool); + ts = js_NewTokenStream(cx, JSSTRING_CHARS(str), JSSTRING_LENGTH(str), + filename, lineno, principals); + if (!ts) { + ok = JS_FALSE; + } else { + ok = js_CompileFunctionBody(cx, ts, fun) && + js_CloseTokenStream(cx, ts); + } + JS_ARENA_RELEASE(&cx->tempPool, mark); + return ok; + +bad_formal: + /* + * Report "malformed formal parameter" iff no illegal char or similar + * scanner error was already reported. + */ + if (!(ts->flags & TSF_ERROR)) + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_FORMAL); + + /* + * Clean up the arguments string and tokenstream if we failed to parse + * the arguments. + */ + (void)js_CloseTokenStream(cx, ts); + JS_ARENA_RELEASE(&cx->tempPool, mark); + return JS_FALSE; +} + +JSObject * +js_InitFunctionClass(JSContext *cx, JSObject *obj) +{ + JSObject *proto; + JSAtom *atom; + JSFunction *fun; + + proto = JS_InitClass(cx, obj, NULL, &js_FunctionClass, Function, 1, + function_props, function_methods, NULL, NULL); + if (!proto) + return NULL; + atom = js_Atomize(cx, js_FunctionClass.name, strlen(js_FunctionClass.name), + 0); + if (!atom) + goto bad; + fun = js_NewFunction(cx, proto, NULL, 0, 0, obj, NULL); + if (!fun) + goto bad; + fun->script = js_NewScript(cx, 0, 0, 0); + if (!fun->script) + goto bad; + return proto; + +bad: + cx->newborn[GCX_OBJECT] = NULL; + return NULL; +} + +#if JS_HAS_CALL_OBJECT +JSObject * +js_InitCallClass(JSContext *cx, JSObject *obj) +{ + return JS_InitClass(cx, obj, NULL, &js_CallClass, NULL, 0, + call_props, NULL, NULL, NULL); +} +#endif + +JSFunction * +js_NewFunction(JSContext *cx, JSObject *funobj, JSNative native, uintN nargs, + uintN flags, JSObject *parent, JSAtom *atom) +{ + JSFunction *fun; + + /* Allocate a function struct. */ + fun = (JSFunction *) JS_malloc(cx, sizeof *fun); + if (!fun) + return NULL; + + /* If funobj is null, allocate an object for it. */ + if (funobj) { + OBJ_SET_PARENT(cx, funobj, parent); + } else { + funobj = js_NewObject(cx, &js_FunctionClass, NULL, parent); + if (!funobj) { + JS_free(cx, fun); + return NULL; + } + } + + /* Initialize all function members. */ + fun->nrefs = 0; + fun->object = NULL; + fun->native = native; + fun->script = NULL; + fun->nargs = nargs; + fun->extra = 0; + fun->nvars = 0; + fun->flags = flags & JSFUN_FLAGS_MASK; + fun->spare = 0; + fun->atom = atom; + fun->clasp = NULL; + + /* Link fun to funobj and vice versa. */ + if (!js_LinkFunctionObject(cx, fun, funobj)) { + cx->newborn[GCX_OBJECT] = NULL; + JS_free(cx, fun); + return NULL; + } + return fun; +} + +JSObject * +js_CloneFunctionObject(JSContext *cx, JSObject *funobj, JSObject *parent) +{ + JSObject *newfunobj; + JSFunction *fun; + + JS_ASSERT(OBJ_GET_CLASS(cx, funobj) == &js_FunctionClass); + newfunobj = js_NewObject(cx, &js_FunctionClass, funobj, parent); + if (!newfunobj) + return NULL; + fun = (JSFunction *) JS_GetPrivate(cx, funobj); + if (!js_LinkFunctionObject(cx, fun, newfunobj)) { + cx->newborn[GCX_OBJECT] = NULL; + return NULL; + } + return newfunobj; +} + +JSBool +js_LinkFunctionObject(JSContext *cx, JSFunction *fun, JSObject *funobj) +{ + if (!fun->object) + fun->object = funobj; + if (!JS_SetPrivate(cx, funobj, fun)) + return JS_FALSE; + JS_ATOMIC_INCREMENT(&fun->nrefs); + return JS_TRUE; +} + +JSFunction * +js_DefineFunction(JSContext *cx, JSObject *obj, JSAtom *atom, JSNative native, + uintN nargs, uintN attrs) +{ + JSFunction *fun; + + fun = js_NewFunction(cx, NULL, native, nargs, attrs, obj, atom); + if (!fun) + return NULL; + if (!OBJ_DEFINE_PROPERTY(cx, obj, (jsid)atom, OBJECT_TO_JSVAL(fun->object), + NULL, NULL, attrs & ~JSFUN_FLAGS_MASK, NULL)) { + return NULL; + } + return fun; +} + +#if (JSV2F_CONSTRUCT & JSV2F_SEARCH_STACK) +# error "JSINVOKE_CONSTRUCT and JSV2F_SEARCH_STACK are not disjoint!" +#endif + +JSFunction * +js_ValueToFunction(JSContext *cx, jsval *vp, uintN flags) +{ + jsval v; + JSObject *obj; + + v = *vp; + obj = NULL; + if (JSVAL_IS_OBJECT(v)) { + obj = JSVAL_TO_OBJECT(v); + if (obj && OBJ_GET_CLASS(cx, obj) != &js_FunctionClass) { + if (!OBJ_DEFAULT_VALUE(cx, obj, JSTYPE_FUNCTION, &v)) + return NULL; + obj = JSVAL_IS_FUNCTION(cx, v) ? JSVAL_TO_OBJECT(v) : NULL; + } + } + if (!obj) { + js_ReportIsNotFunction(cx, vp, flags); + return NULL; + } + return (JSFunction *) JS_GetPrivate(cx, obj); +} + +void +js_ReportIsNotFunction(JSContext *cx, jsval *vp, uintN flags) +{ + JSType type; + JSString *fallback; + JSString *str; + + /* + * We provide the typename as the fallback to handle the case when + * valueOf is not a function, which prevents ValueToString from being + * called as the default case inside js_DecompileValueGenerator (and + * so recursing back to here). + */ + type = JS_TypeOfValue(cx, *vp); + fallback = ATOM_TO_STRING(cx->runtime->atomState.typeAtoms[type]); + str = js_DecompileValueGenerator(cx, + (flags & JSV2F_SEARCH_STACK) + ? JSDVG_SEARCH_STACK + : cx->fp + ? vp - cx->fp->sp + : JSDVG_IGNORE_STACK, + *vp, + fallback); + if (str) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + (uintN)((flags & JSV2F_CONSTRUCT) + ? JSMSG_NOT_CONSTRUCTOR + : JSMSG_NOT_FUNCTION), + JS_GetStringBytes(str)); + } +} diff --git a/src/dom/js/jsfun.h b/src/dom/js/jsfun.h new file mode 100644 index 000000000..0a3237921 --- /dev/null +++ b/src/dom/js/jsfun.h @@ -0,0 +1,151 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsfun_h___ +#define jsfun_h___ +/* + * JS function definitions. + */ +#include "jsprvtd.h" +#include "jspubtd.h" + +JS_BEGIN_EXTERN_C + +struct JSFunction { + jsrefcount nrefs; /* number of referencing objects */ + JSObject *object; /* back-pointer to GC'ed object header */ + JSNative native; /* native method pointer or null */ + JSScript *script; /* interpreted bytecode descriptor or null */ + uint16 nargs; /* minimum number of actual arguments */ + uint16 extra; /* number of arg slots for local GC roots */ + uint16 nvars; /* number of local variables */ + uint8 flags; /* bound method and other flags, see jsapi.h */ + uint8 spare; /* reserved for future use */ + JSAtom *atom; /* name for diagnostics and decompiling */ + JSClass *clasp; /* if non-null, constructor for this class */ +}; + +extern JSClass js_ArgumentsClass; +extern JSClass js_CallClass; + +/* JS_FRIEND_DATA so that JSVAL_IS_FUNCTION is callable from outside */ +extern JS_FRIEND_DATA(JSClass) js_FunctionClass; + +/* + * NB: jsapi.h and jsobj.h must be included before any call to this macro. + */ +#define JSVAL_IS_FUNCTION(cx, v) \ + (JSVAL_IS_OBJECT(v) && JSVAL_TO_OBJECT(v) && \ + OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(v)) == &js_FunctionClass) + +extern JSBool +js_fun_toString(JSContext *cx, JSObject *obj, uint32 indent, + uintN argc, jsval *argv, jsval *rval); + +extern JSBool +js_IsIdentifier(JSString *str); + +extern JSObject * +js_InitFunctionClass(JSContext *cx, JSObject *obj); + +extern JSObject * +js_InitArgumentsClass(JSContext *cx, JSObject *obj); + +extern JSObject * +js_InitCallClass(JSContext *cx, JSObject *obj); + +extern JSFunction * +js_NewFunction(JSContext *cx, JSObject *funobj, JSNative native, uintN nargs, + uintN flags, JSObject *parent, JSAtom *atom); + +extern JSObject * +js_CloneFunctionObject(JSContext *cx, JSObject *funobj, JSObject *parent); + +extern JSBool +js_LinkFunctionObject(JSContext *cx, JSFunction *fun, JSObject *object); + +extern JSFunction * +js_DefineFunction(JSContext *cx, JSObject *obj, JSAtom *atom, JSNative native, + uintN nargs, uintN flags); + +/* + * Flags for js_ValueToFunction and js_ReportIsNotFunction. We depend on the + * fact that JSINVOKE_CONSTRUCT (aka JSFRAME_CONSTRUCTING) is 1, and test that + * with #if/#error in jsfun.c. + */ +#define JSV2F_CONSTRUCT JSINVOKE_CONSTRUCT +#define JSV2F_SEARCH_STACK 2 + +extern JSFunction * +js_ValueToFunction(JSContext *cx, jsval *vp, uintN flags); + +extern void +js_ReportIsNotFunction(JSContext *cx, jsval *vp, uintN flags); + +extern JSObject * +js_GetCallObject(JSContext *cx, JSStackFrame *fp, JSObject *parent); + +extern JSBool +js_PutCallObject(JSContext *cx, JSStackFrame *fp); + +extern JSBool +js_GetCallVariable(JSContext *cx, JSObject *obj, jsval id, jsval *vp); + +extern JSBool +js_SetCallVariable(JSContext *cx, JSObject *obj, jsval id, jsval *vp); + +extern JSBool +js_GetArgsValue(JSContext *cx, JSStackFrame *fp, jsval *vp); + +extern JSBool +js_GetArgsProperty(JSContext *cx, JSStackFrame *fp, jsid id, + JSObject **objp, jsval *vp); + +extern JSObject * +js_GetArgsObject(JSContext *cx, JSStackFrame *fp); + +extern JSBool +js_PutArgsObject(JSContext *cx, JSStackFrame *fp); + +extern JSBool +js_XDRFunction(JSXDRState *xdr, JSObject **objp); + +JS_END_EXTERN_C + +#endif /* jsfun_h___ */ diff --git a/src/dom/js/jsgc.c b/src/dom/js/jsgc.c new file mode 100644 index 000000000..a0efec4d7 --- /dev/null +++ b/src/dom/js/jsgc.c @@ -0,0 +1,1423 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS Mark-and-Sweep Garbage Collector. + * + * This GC allocates only fixed-sized things big enough to contain two words + * (pointers) on any host architecture. It allocates from an arena pool (see + * jsarena.h). It uses an ideally parallel array of flag bytes to hold the + * mark bit, finalizer type index, etc. + * + * XXX swizzle page to freelist for better locality of reference + */ +#include "jsstddef.h" +#include /* for free, called by JS_ARENA_DESTROY */ +#include /* for memset, called by jsarena.h macros if DEBUG */ +#include "jstypes.h" +#include "jsarena.h" /* Added by JSIFY */ +#include "jsutil.h" /* Added by JSIFY */ +#include "jshash.h" /* Added by JSIFY */ +#include "jsapi.h" +#include "jsatom.h" +#include "jscntxt.h" +#include "jsconfig.h" +#include "jsdbgapi.h" +#include "jsfun.h" +#include "jsgc.h" +#include "jsinterp.h" +#include "jslock.h" +#include "jsnum.h" +#include "jsobj.h" +#include "jsscope.h" +#include "jsscript.h" +#include "jsstr.h" + +/* + * GC arena sizing depends on amortizing arena overhead using a large number + * of things per arena, and on the thing/flags ratio of 8:1 on most platforms. + * + * On 64-bit platforms, we would have half as many things per arena because + * pointers are twice as big, so we double the bytes for things per arena. + * This preserves the 1024 byte flags sub-arena size, which relates to the + * GC_PAGE_SIZE (see below for why). + */ +#if JS_BYTES_PER_WORD == 8 +# define GC_THINGS_SHIFT 14 /* 16KB for things on Alpha, etc. */ +#else +# define GC_THINGS_SHIFT 13 /* 8KB for things on most platforms */ +#endif +#define GC_THINGS_SIZE JS_BIT(GC_THINGS_SHIFT) +#define GC_FLAGS_SIZE (GC_THINGS_SIZE / sizeof(JSGCThing)) +#define GC_ARENA_SIZE (GC_THINGS_SIZE + GC_FLAGS_SIZE) + +/* + * The private JSGCThing struct, which describes a gcFreeList element. + */ +struct JSGCThing { + JSGCThing *next; + uint8 *flagp; +}; + +/* + * A GC arena contains one flag byte for each thing in its heap, and supports + * O(1) lookup of a flag given its thing's address. + * + * To implement this, we take advantage of the thing/flags numerology: given + * the 8K bytes worth of GC-things, there are 1K flag bytes. We mask a thing's + * address with ~1023 to find a JSGCPageInfo record at the front of a mythical + * "GC page" within the larger 8K thing arena. That JSGCPageInfo contains a + * pointer to the 128 flag bytes corresponding to the things in the page, so we + * index into this flags array using the thing's index within its page. + * + * To align thing pages on 1024-byte boundaries, we must allocate the 9KB of + * flags+things arena payload, then find the first 0 mod 1024 boundary after + * the first payload address. That's where things start, with a JSGCPageInfo + * taking up the first thing-slot, as usual for 0 mod 1024 byte boundaries. + * The effect of this alignment trick is to split the flags into at most 2 + * discontiguous spans, one before the things and one after (if we're really + * lucky, and the arena payload starts on a 0 mod 1024 byte boundary, no need + * to split). + * + * The overhead of this scheme for most platforms is (16+8*(8+1))/(16+9K) or + * .95% (assuming 16 byte JSArena header size, and 8 byte JSGCThing size). + * + * Here's some ASCII art showing an arena: + * + * split + * | + * V + * +--+-------+-------+-------+-------+-------+-------+-------+-------+-----+ + * |fB| tp0 | tp1 | tp2 | tp3 | tp4 | tp5 | tp6 | tp7 | fA | + * +--+-------+-------+-------+-------+-------+-------+-------+-------+-----+ + * ^ ^ + * tI ---------+ | + * tJ -------------------------------------------+ + * + * - fB are the "before split" flags, fA are the "after split" flags + * - tp0-tp7 are the 8 thing pages + * - thing tI points into tp1, whose flags are below the split, in fB + * - thing tJ points into tp5, clearly above the split + * + * In general, one of the thing pages will have some of its things' flags on + * the low side of the split, and the rest of its things' flags on the high + * side. All the other pages have flags only below or only above. Therefore + * we'll have to test something to decide whether the split divides flags in + * a given thing's page. So we store the split pointer (the pointer to tp0) + * in each JSGCPageInfo, along with the flags pointer for the 128 flag bytes + * ideally starting, for tp0 things, at the beginning of the arena's payload + * (at the start of fB). + * + * That is, each JSGCPageInfo's flags pointer is 128 bytes from the previous, + * or at the start of the arena if there is no previous page in this arena. + * Thus these ideal 128-byte flag pages run contiguously from the start of the + * arena (right over the split!), and the JSGCPageInfo flags pointers contain + * no discontinuities over the split created by the thing pages. So if, for a + * given JSGCPageInfo *pi, we find that + * + * pi->flags + ((jsuword)thing % 1024) / sizeof(JSGCThing) >= pi->split + * + * then we must add GC_THINGS_SIZE to the nominal flags pointer to jump over + * all the thing pages that split the flags into two discontiguous spans. + * + * (If we need to implement card-marking for an incremental GC write barrier, + * we can use the low byte of the pi->split pointer as the card-mark, for an + * extremely efficient write barrier: when mutating an object obj, just store + * a 1 byte at (uint8 *) ((jsuword)obj & ~1023) for little-endian platforms. + * When finding flags, we'll of course have to mask split with ~255, but it is + * guaranteed to be 1024-byte aligned, so no information is lost by overlaying + * the card-mark byte on split's low byte.) + */ +#define GC_PAGE_SHIFT 10 +#define GC_PAGE_MASK ((jsuword) JS_BITMASK(GC_PAGE_SHIFT)) +#define GC_PAGE_SIZE JS_BIT(GC_PAGE_SHIFT) + +typedef struct JSGCPageInfo { + uint8 *split; + uint8 *flags; +} JSGCPageInfo; + +#define FIRST_THING_PAGE(a) (((a)->base + GC_FLAGS_SIZE) & ~GC_PAGE_MASK) + +static JSGCThing * +gc_new_arena(JSArenaPool *pool) +{ + uint8 *flagp, *split, *pagep, *limit; + JSArena *a; + JSGCThing *thing; + JSGCPageInfo *pi; + + /* Use JS_ArenaAllocate to grab another 9K-net-size hunk of space. */ + flagp = (uint8 *) JS_ArenaAllocate(pool, GC_ARENA_SIZE); + if (!flagp) + return NULL; + a = pool->current; + + /* Reset a->avail to start at the flags split, aka the first thing page. */ + a->avail = FIRST_THING_PAGE(a); + split = pagep = (uint8 *) a->avail; + a->avail += sizeof(JSGCPageInfo); + thing = (JSGCThing *) a->avail; + a->avail += sizeof(JSGCThing); + + /* Initialize the JSGCPageInfo records at the start of every thing page. */ + limit = pagep + GC_THINGS_SIZE; + do { + pi = (JSGCPageInfo *) pagep; + pi->split = split; + pi->flags = flagp; + flagp += GC_PAGE_SIZE >> (GC_THINGS_SHIFT - GC_PAGE_SHIFT); + pagep += GC_PAGE_SIZE; + } while (pagep < limit); + return thing; +} + +uint8 * +js_GetGCThingFlags(void *thing) +{ + JSGCPageInfo *pi; + uint8 *flagp; + + pi = (JSGCPageInfo *) ((jsuword)thing & ~GC_PAGE_MASK); + flagp = pi->flags + ((jsuword)thing & GC_PAGE_MASK) / sizeof(JSGCThing); + if (flagp >= pi->split) + flagp += GC_THINGS_SIZE; + return flagp; +} + +JSBool +js_IsAboutToBeFinalized(JSContext *cx, void *thing) +{ + uint8 flags = *js_GetGCThingFlags(thing); + + return !(flags & (GCF_MARK | GCF_LOCKMASK | GCF_FINAL)); +} + +typedef void (*GCFinalizeOp)(JSContext *cx, JSGCThing *thing); + +static GCFinalizeOp gc_finalizers[GCX_NTYPES]; + +intN +js_ChangeExternalStringFinalizer(JSStringFinalizeOp oldop, + JSStringFinalizeOp newop) +{ + uintN i; + + for (i = GCX_EXTERNAL_STRING; i < GCX_NTYPES; i++) { + if (gc_finalizers[i] == (GCFinalizeOp) oldop) { + gc_finalizers[i] = (GCFinalizeOp) newop; + return (intN) i; + } + } + return -1; +} + +#ifdef JS_GCMETER +#define METER(x) x +#else +#define METER(x) /* nothing */ +#endif + +/* Initial size of the gcRootsHash table (SWAG, small enough to amortize). */ +#define GC_ROOTS_SIZE 256 +#define GC_FINALIZE_LEN 1024 + +JSBool +js_InitGC(JSRuntime *rt, uint32 maxbytes) +{ + JS_ASSERT(sizeof(JSGCThing) == sizeof(JSGCPageInfo)); + JS_ASSERT(sizeof(JSGCThing) >= sizeof(JSObject)); + JS_ASSERT(sizeof(JSGCThing) >= sizeof(JSString)); + JS_ASSERT(sizeof(JSGCThing) >= sizeof(jsdouble)); + JS_ASSERT(GC_FLAGS_SIZE >= GC_PAGE_SIZE); + JS_ASSERT(sizeof(JSStackHeader) >= 2 * sizeof(jsval)); + + if (!gc_finalizers[GCX_OBJECT]) { + gc_finalizers[GCX_OBJECT] = (GCFinalizeOp)js_FinalizeObject; + gc_finalizers[GCX_STRING] = (GCFinalizeOp)js_FinalizeString; +#ifdef DEBUG + gc_finalizers[GCX_DOUBLE] = (GCFinalizeOp)js_FinalizeDouble; +#endif + gc_finalizers[GCX_MUTABLE_STRING] = (GCFinalizeOp)js_FinalizeString; + } + + JS_InitArenaPool(&rt->gcArenaPool, "gc-arena", GC_ARENA_SIZE, + sizeof(JSGCThing)); + if (!JS_DHashTableInit(&rt->gcRootsHash, JS_DHashGetStubOps(), NULL, + sizeof(JSGCRootHashEntry), GC_ROOTS_SIZE)) { + rt->gcRootsHash.ops = NULL; + return JS_FALSE; + } + rt->gcLocksHash = NULL; /* create lazily */ + rt->gcMaxBytes = maxbytes; + return JS_TRUE; +} + +#ifdef JS_GCMETER +void +js_DumpGCStats(JSRuntime *rt, FILE *fp) +{ + fprintf(fp, "\nGC allocation statistics:\n"); + fprintf(fp, " bytes currently allocated: %lu\n", rt->gcBytes); + fprintf(fp, " alloc attempts: %lu\n", rt->gcStats.alloc); + fprintf(fp, " GC freelist length: %lu\n", rt->gcStats.freelen); + fprintf(fp, " recycles through GC freelist: %lu\n", rt->gcStats.recycle); + fprintf(fp, "alloc retries after running GC: %lu\n", rt->gcStats.retry); + fprintf(fp, " allocation failures: %lu\n", rt->gcStats.fail); + fprintf(fp, " valid lock calls: %lu\n", rt->gcStats.lock); + fprintf(fp, " valid unlock calls: %lu\n", rt->gcStats.unlock); + fprintf(fp, " locks that hit stuck counts: %lu\n", rt->gcStats.stuck); + fprintf(fp, " unlocks that saw stuck counts: %lu\n", rt->gcStats.unstuck); + fprintf(fp, " mark recursion depth: %lu\n", rt->gcStats.depth); + fprintf(fp, " maximum mark recursion depth: %lu\n", rt->gcStats.maxdepth); + fprintf(fp, " maximum GC nesting level: %lu\n", rt->gcStats.maxlevel); + fprintf(fp, " potentially useful GC calls: %lu\n", rt->gcStats.poke); + fprintf(fp, " useless GC calls: %lu\n", rt->gcStats.nopoke); + fprintf(fp, " thing arenas freed so far: %lu\n", rt->gcStats.afree); + fprintf(fp, " extra stack segments scanned: %lu\n", rt->gcStats.stackseg); + fprintf(fp, " stack segment slots scanned: %lu\n", rt->gcStats.segslots); +#ifdef JS_ARENAMETER + JS_DumpArenaStats(fp); +#endif +} +#endif + +#ifdef DEBUG +JS_STATIC_DLL_CALLBACK(JSDHashOperator) +js_root_printer(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 i, void *arg) +{ + uint32 *leakedroots = (uint32 *)arg; + JSGCRootHashEntry *rhe = (JSGCRootHashEntry *)hdr; + + (*leakedroots)++; + fprintf(stderr, + "JS engine warning: leaking GC root \'%s\' at %p\n", + rhe->name ? (char *)rhe->name : "", rhe->root); + + return JS_DHASH_NEXT; +} +#endif + +void +js_FinishGC(JSRuntime *rt) +{ +#ifdef JS_ARENAMETER + JS_DumpArenaStats(stdout); +#endif +#ifdef JS_GCMETER + js_DumpGCStats(rt, stdout); +#endif + JS_FinishArenaPool(&rt->gcArenaPool); + JS_ArenaFinish(); + + if (rt->gcRootsHash.ops) { +#ifdef DEBUG + uint32 leakedroots = 0; + + /* Warn (but don't assert) debug builds of any remaining roots. */ + JS_DHashTableEnumerate(&rt->gcRootsHash, js_root_printer, + &leakedroots); + if (leakedroots > 0) { + if (leakedroots == 1) { + fprintf(stderr, +"JS engine warning: 1 GC root remains after destroying the JSRuntime.\n" +" This root may point to freed memory. Objects reachable\n" +" through it have not been finalized.\n"); + } else { + fprintf(stderr, +"JS engine warning: %lu GC roots remain after destroying the JSRuntime.\n" +" These roots may point to freed memory. Objects reachable\n" +" through them have not been finalized.\n", + (unsigned long) leakedroots); + } + } +#endif + + JS_DHashTableFinish(&rt->gcRootsHash); + rt->gcRootsHash.ops = NULL; + } + if (rt->gcLocksHash) { + JS_DHashTableDestroy(rt->gcLocksHash); + rt->gcLocksHash = NULL; + } + rt->gcFreeList = NULL; +} + +JSBool +js_AddRoot(JSContext *cx, void *rp, const char *name) +{ + JSBool ok = js_AddRootRT(cx->runtime, rp, name); + if (!ok) + JS_ReportOutOfMemory(cx); + return ok; +} + +JSBool +js_AddRootRT(JSRuntime *rt, void *rp, const char *name) +{ + JSBool ok; + JSGCRootHashEntry *rhe; + + /* + * Due to the long-standing, but now removed, use of rt->gcLock across the + * bulk of js_GC, API users have come to depend on JS_AddRoot etc. locking + * properly with a racing GC, without calling JS_AddRoot from a request. + * We have to preserve API compatibility here, now that we avoid holding + * rt->gcLock across the mark phase (including the root hashtable mark). + * + * If the GC is running and we're called on another thread, wait for this + * GC activation to finish. We can safely wait here (in the case where we + * are called within a request on another thread's context) without fear + * of deadlock because the GC doesn't set rt->gcRunning until after it has + * waited for all active requests to end. + */ + JS_LOCK_GC(rt); +#ifdef JS_THREADSAFE + JS_ASSERT(!rt->gcRunning || rt->gcLevel > 0); + if (rt->gcRunning && rt->gcThread != js_CurrentThreadId()) { + do { + JS_AWAIT_GC_DONE(rt); + } while (rt->gcLevel > 0); + } +#endif + rhe = (JSGCRootHashEntry *) JS_DHashTableOperate(&rt->gcRootsHash, rp, + JS_DHASH_ADD); + if (rhe) { + rhe->root = rp; + rhe->name = name; + ok = JS_TRUE; + } else { + ok = JS_FALSE; + } + JS_UNLOCK_GC(rt); + return ok; +} + +JSBool +js_RemoveRoot(JSRuntime *rt, void *rp) +{ + /* + * Due to the JS_RemoveRootRT API, we may be called outside of a request. + * Same synchronization drill as above in js_AddRoot. + */ + JS_LOCK_GC(rt); +#ifdef JS_THREADSAFE + JS_ASSERT(!rt->gcRunning || rt->gcLevel > 0); + if (rt->gcRunning && rt->gcThread != js_CurrentThreadId()) { + do { + JS_AWAIT_GC_DONE(rt); + } while (rt->gcLevel > 0); + } +#endif + (void) JS_DHashTableOperate(&rt->gcRootsHash, rp, JS_DHASH_REMOVE); + rt->gcPoke = JS_TRUE; + JS_UNLOCK_GC(rt); + return JS_TRUE; +} + +void * +js_AllocGCThing(JSContext *cx, uintN flags) +{ + JSBool tried_gc; + JSRuntime *rt; + JSGCThing *thing; + uint8 *flagp; + +#ifdef TOO_MUCH_GC + js_GC(cx, GC_KEEP_ATOMS); + tried_gc = JS_TRUE; +#else + tried_gc = JS_FALSE; +#endif + + rt = cx->runtime; + JS_LOCK_GC(rt); + JS_ASSERT(!rt->gcRunning); + if (rt->gcRunning) { + METER(rt->gcStats.finalfail++); + JS_UNLOCK_GC(rt); + return NULL; + } + METER(rt->gcStats.alloc++); +retry: + thing = rt->gcFreeList; + if (thing) { + rt->gcFreeList = thing->next; + flagp = thing->flagp; + METER(rt->gcStats.freelen--); + METER(rt->gcStats.recycle++); + } else { + if (rt->gcBytes < rt->gcMaxBytes && + (tried_gc || rt->gcMallocBytes < rt->gcMaxBytes)) + { + /* + * Inline form of JS_ARENA_ALLOCATE adapted to truncate the current + * arena's limit to a GC_PAGE_SIZE boundary, and to skip over every + * GC_PAGE_SIZE-byte-aligned thing (which is actually not a thing, + * it's a JSGCPageInfo record). + */ + JSArenaPool *pool = &rt->gcArenaPool; + JSArena *a = pool->current; + size_t nb = sizeof(JSGCThing); + jsuword p = a->avail; + jsuword q = p + nb; + + if (q > (a->limit & ~GC_PAGE_MASK)) { + thing = gc_new_arena(pool); + } else { + if ((p & GC_PAGE_MASK) == 0) { + /* Beware, p points to a JSGCPageInfo record! */ + p = q; + q += nb; + JS_ArenaCountAllocation(pool, nb); + } + a->avail = q; + thing = (JSGCThing *)p; + } + JS_ArenaCountAllocation(pool, nb); + } + + /* + * Consider doing a "last ditch" GC if thing couldn't be allocated. + * + * Keep rt->gcLock across the call into js_GC so we don't starve and + * lose to racing threads who deplete the heap just after js_GC has + * replenished it (or has synchronized with a racing GC that collected + * a bunch of garbage). This unfair scheduling can happen on certain + * operating systems. For the gory details, see Mozilla bug 162779 + * (http://bugzilla.mozilla.org/show_bug.cgi?id=162779). + */ + if (!thing) { + if (!tried_gc) { + rt->gcPoke = JS_TRUE; + js_GC(cx, GC_KEEP_ATOMS | GC_ALREADY_LOCKED); + tried_gc = JS_TRUE; + METER(rt->gcStats.retry++); + goto retry; + } + METER(rt->gcStats.fail++); + JS_UNLOCK_GC(rt); + JS_ReportOutOfMemory(cx); + return NULL; + } + + /* Find the flags pointer given thing's address. */ + flagp = js_GetGCThingFlags(thing); + } + *flagp = (uint8)flags; + rt->gcBytes += sizeof(JSGCThing) + sizeof(uint8); + cx->newborn[flags & GCF_TYPEMASK] = thing; + + /* + * Clear thing before unlocking in case a GC run is about to scan it, + * finding it via cx->newborn[]. + */ + thing->next = NULL; + thing->flagp = NULL; + JS_UNLOCK_GC(rt); + return thing; +} + +JSBool +js_LockGCThing(JSContext *cx, void *thing) +{ + JSBool ok = js_LockGCThingRT(cx->runtime, thing); + if (!ok) + JS_ReportOutOfMemory(cx); + return ok; +} + +JSBool +js_LockGCThingRT(JSRuntime *rt, void *thing) +{ + uint8 *flagp, flags, lockbits; + JSBool ok; + JSGCLockHashEntry *lhe; + + if (!thing) + return JS_TRUE; + flagp = js_GetGCThingFlags(thing); + flags = *flagp; + + ok = JS_FALSE; + JS_LOCK_GC(rt); + lockbits = (flags & GCF_LOCKMASK); + + if (lockbits != GCF_LOCKMASK) { + if ((flags & GCF_TYPEMASK) == GCX_OBJECT) { + /* Objects may require "deep locking", i.e., rooting by value. */ + if (lockbits == 0) { + if (!rt->gcLocksHash) { + rt->gcLocksHash = + JS_NewDHashTable(JS_DHashGetStubOps(), NULL, + sizeof(JSGCLockHashEntry), + GC_ROOTS_SIZE); + if (!rt->gcLocksHash) + goto error; + } else { +#ifdef DEBUG + JSDHashEntryHdr *hdr = + JS_DHashTableOperate(rt->gcLocksHash, thing, + JS_DHASH_LOOKUP); + JS_ASSERT(JS_DHASH_ENTRY_IS_FREE(hdr)); +#endif + } + lhe = (JSGCLockHashEntry *) + JS_DHashTableOperate(rt->gcLocksHash, thing, JS_DHASH_ADD); + if (!lhe) + goto error; + lhe->thing = thing; + lhe->count = 1; + *flagp = (uint8)(flags + GCF_LOCK); + } else { + JS_ASSERT(lockbits == GCF_LOCK); + lhe = (JSGCLockHashEntry *) + JS_DHashTableOperate(rt->gcLocksHash, thing, + JS_DHASH_LOOKUP); + JS_ASSERT(JS_DHASH_ENTRY_IS_BUSY(&lhe->hdr)); + if (JS_DHASH_ENTRY_IS_BUSY(&lhe->hdr)) { + JS_ASSERT(lhe->count >= 1); + lhe->count++; + } + } + } else { + *flagp = (uint8)(flags + GCF_LOCK); + } + } else { + METER(rt->gcStats.stuck++); + } + + METER(rt->gcStats.lock++); + ok = JS_TRUE; +error: + JS_UNLOCK_GC(rt); + return ok; +} + +JSBool +js_UnlockGCThingRT(JSRuntime *rt, void *thing) +{ + uint8 *flagp, flags, lockbits; + JSGCLockHashEntry *lhe; + + if (!thing) + return JS_TRUE; + flagp = js_GetGCThingFlags(thing); + flags = *flagp; + + JS_LOCK_GC(rt); + lockbits = (flags & GCF_LOCKMASK); + + if (lockbits != GCF_LOCKMASK) { + if ((flags & GCF_TYPEMASK) == GCX_OBJECT) { + /* Defend against a call on an unlocked object. */ + if (lockbits != 0) { + JS_ASSERT(lockbits == GCF_LOCK); + lhe = (JSGCLockHashEntry *) + JS_DHashTableOperate(rt->gcLocksHash, thing, + JS_DHASH_LOOKUP); + JS_ASSERT(JS_DHASH_ENTRY_IS_BUSY(&lhe->hdr)); + if (JS_DHASH_ENTRY_IS_BUSY(&lhe->hdr) && + --lhe->count == 0) { + (void) JS_DHashTableOperate(rt->gcLocksHash, thing, + JS_DHASH_REMOVE); + *flagp = (uint8)(flags & ~GCF_LOCKMASK); + } + } + } else { + *flagp = (uint8)(flags - GCF_LOCK); + } + } else { + METER(rt->gcStats.unstuck++); + } + + rt->gcPoke = JS_TRUE; + METER(rt->gcStats.unlock++); + JS_UNLOCK_GC(rt); + return JS_TRUE; +} + +#ifdef GC_MARK_DEBUG + +#include +#include +#include "jsprf.h" + +JS_FRIEND_DATA(FILE *) js_DumpGCHeap; +JS_EXPORT_DATA(void *) js_LiveThingToFind; + +#ifdef HAVE_XPCONNECT +#include "dump_xpc.h" +#endif + +static const char * +gc_object_class_name(void* thing) +{ + uint8 *flagp = js_GetGCThingFlags(thing); + const char *className = ""; + static char depbuf[32]; + + switch (*flagp & GCF_TYPEMASK) { + case GCX_OBJECT: { + JSObject *obj = (JSObject *)thing; + JSClass *clasp = JSVAL_TO_PRIVATE(obj->slots[JSSLOT_CLASS]); + className = clasp->name; +#ifdef HAVE_XPCONNECT + if (clasp->flags & JSCLASS_PRIVATE_IS_NSISUPPORTS) { + jsval privateValue = obj->slots[JSSLOT_PRIVATE]; + + JS_ASSERT(clasp->flags & JSCLASS_HAS_PRIVATE); + if (!JSVAL_IS_VOID(privateValue)) { + void *privateThing = JSVAL_TO_PRIVATE(privateValue); + const char *xpcClassName = GetXPCObjectClassName(privateThing); + + if (xpcClassName) + className = xpcClassName; + } + } +#endif + break; + } + + case GCX_STRING: + case GCX_MUTABLE_STRING: { + JSString *str = (JSString *)thing; + if (JSSTRING_IS_DEPENDENT(str)) { + JS_snprintf(depbuf, sizeof depbuf, "start:%u, length:%u", + JSSTRDEP_START(str), JSSTRDEP_LENGTH(str)); + className = depbuf; + } else { + className = "string"; + } + break; + } + + case GCX_DOUBLE: + className = "double"; + break; + } + + return className; +} + +static void +gc_dump_thing(JSGCThing *thing, uint8 flags, GCMarkNode *prev, FILE *fp) +{ + GCMarkNode *next = NULL; + char *path = NULL; + + while (prev) { + next = prev; + prev = prev->prev; + } + while (next) { + path = JS_sprintf_append(path, "%s(%s).", + next->name, + gc_object_class_name(next->thing)); + next = next->next; + } + if (!path) + return; + + fprintf(fp, "%08lx ", (long)thing); + switch (flags & GCF_TYPEMASK) { + case GCX_OBJECT: + { + JSObject *obj = (JSObject *)thing; + jsval privateValue = obj->slots[JSSLOT_PRIVATE]; + void *privateThing = JSVAL_IS_VOID(privateValue) + ? NULL + : JSVAL_TO_PRIVATE(privateValue); + const char *className = gc_object_class_name(thing); + fprintf(fp, "object %8p %s", privateThing, className); + break; + } + case GCX_DOUBLE: + fprintf(fp, "double %g", *(jsdouble *)thing); + break; + default: + fprintf(fp, "string %s", JS_GetStringBytes((JSString *)thing)); + break; + } + fprintf(fp, " via %s\n", path); + free(path); +} + +#endif /* !GC_MARK_DEBUG */ + +static void +gc_mark_atom_key_thing(void *thing, void *arg) +{ + JSContext *cx = (JSContext *) arg; + + GC_MARK(cx, thing, "atom", NULL); +} + +void +js_MarkAtom(JSContext *cx, JSAtom *atom, void *arg) +{ + jsval key; + + if (atom->flags & ATOM_MARK) + return; + atom->flags |= ATOM_MARK; + key = ATOM_KEY(atom); + if (JSVAL_IS_GCTHING(key)) { +#ifdef GC_MARK_DEBUG + char name[32]; + + if (JSVAL_IS_STRING(key)) { + JS_snprintf(name, sizeof name, "'%s'", + JS_GetStringBytes(JSVAL_TO_STRING(key))); + } else { + JS_snprintf(name, sizeof name, "<%x>", key); + } +#endif + GC_MARK(cx, JSVAL_TO_GCTHING(key), name, arg); + } +} + +void +js_MarkGCThing(JSContext *cx, void *thing, void *arg) +{ + uint8 flags, *flagp; + JSRuntime *rt; + JSObject *obj; + uint32 nslots; + jsval v, *vp, *end; + JSString *str; +#ifdef GC_MARK_DEBUG + JSScope *scope; + JSScopeProperty *sprop; +#endif + + if (!thing) + return; + + flagp = js_GetGCThingFlags(thing); + flags = *flagp; + JS_ASSERT(flags != GCF_FINAL); +#ifdef GC_MARK_DEBUG + if (js_LiveThingToFind == thing) + gc_dump_thing(thing, flags, arg, stderr); +#endif + + if (flags & GCF_MARK) + return; + + *flagp |= GCF_MARK; + rt = cx->runtime; + METER(if (++rt->gcStats.depth > rt->gcStats.maxdepth) + rt->gcStats.maxdepth = rt->gcStats.depth); + +#ifdef GC_MARK_DEBUG + if (js_DumpGCHeap) + gc_dump_thing(thing, flags, arg, js_DumpGCHeap); +#endif + + switch (flags & GCF_TYPEMASK) { + case GCX_OBJECT: + obj = (JSObject *) thing; + vp = obj->slots; + if (!vp) { + /* If obj->slots is null, obj must be a newborn. */ + JS_ASSERT(!obj->map); + goto out; + } + nslots = (obj->map->ops->mark) + ? obj->map->ops->mark(cx, obj, arg) + : JS_MIN(obj->map->freeslot, obj->map->nslots); +#ifdef GC_MARK_DEBUG + scope = OBJ_IS_NATIVE(obj) ? OBJ_SCOPE(obj) : NULL; +#endif + for (end = vp + nslots; vp < end; vp++) { + v = *vp; + if (JSVAL_IS_GCTHING(v)) { +#ifdef GC_MARK_DEBUG + char name[32]; + + if (scope) { + uint32 slot; + jsval nval; + + slot = vp - obj->slots; + for (sprop = SCOPE_LAST_PROP(scope); ; + sprop = sprop->parent) { + if (!sprop) { + switch (slot) { + case JSSLOT_PROTO: + strcpy(name, "__proto__"); + break; + case JSSLOT_PARENT: + strcpy(name, "__parent__"); + break; + case JSSLOT_PRIVATE: + strcpy(name, "__private__"); + break; + default: + JS_snprintf(name, sizeof name, + "**UNKNOWN SLOT %ld**", + (long)slot); + break; + } + break; + } + if (sprop->slot == slot) { + nval = ID_TO_VALUE(sprop->id); + if (JSVAL_IS_INT(nval)) { + JS_snprintf(name, sizeof name, "%ld", + (long)JSVAL_TO_INT(nval)); + } else if (JSVAL_IS_STRING(nval)) { + JS_snprintf(name, sizeof name, "%s", + JS_GetStringBytes(JSVAL_TO_STRING(nval))); + } else { + strcpy(name, "**FINALIZED ATOM KEY**"); + } + break; + } + } + } else { + strcpy(name, "**UNKNOWN OBJECT MAP ENTRY**"); + } +#endif + GC_MARK(cx, JSVAL_TO_GCTHING(v), name, arg); + } + } + break; + +#ifdef DEBUG + case GCX_STRING: + str = (JSString *)thing; + JS_ASSERT(!JSSTRING_IS_DEPENDENT(str)); + break; +#endif + + case GCX_MUTABLE_STRING: + str = (JSString *)thing; + if (JSSTRING_IS_DEPENDENT(str)) + GC_MARK(cx, JSSTRDEP_BASE(str), "base", arg); + break; + } + +out: + METER(rt->gcStats.depth--); +} + +JS_STATIC_DLL_CALLBACK(JSDHashOperator) +gc_root_marker(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 num, void *arg) +{ + JSGCRootHashEntry *rhe = (JSGCRootHashEntry *)hdr; + jsval *rp = (jsval *)rhe->root; + jsval v = *rp; + + /* Ignore null object and scalar values. */ + if (!JSVAL_IS_NULL(v) && JSVAL_IS_GCTHING(v)) { + JSContext *cx = (JSContext *)arg; +#ifdef DEBUG + JSArena *a; + jsuword firstpage; + JSBool root_points_to_gcArenaPool = JS_FALSE; + void *thing = JSVAL_TO_GCTHING(v); + + for (a = cx->runtime->gcArenaPool.first.next; a; a = a->next) { + firstpage = FIRST_THING_PAGE(a); + if (JS_UPTRDIFF(thing, firstpage) < a->avail - firstpage) { + root_points_to_gcArenaPool = JS_TRUE; + break; + } + } + if (!root_points_to_gcArenaPool && rhe->name) { + fprintf(stderr, +"JS API usage error: the address passed to JS_AddNamedRoot currently holds an\n" +"invalid jsval. This is usually caused by a missing call to JS_RemoveRoot.\n" +"The root's name is \"%s\".\n", + rhe->name); + } + JS_ASSERT(root_points_to_gcArenaPool); +#endif + + GC_MARK(cx, JSVAL_TO_GCTHING(v), rhe->name ? rhe->name : "root", NULL); + } + return JS_DHASH_NEXT; +} + +JS_STATIC_DLL_CALLBACK(JSDHashOperator) +gc_lock_marker(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 num, void *arg) +{ + JSGCLockHashEntry *lhe = (JSGCLockHashEntry *)hdr; + void *thing = (void *)lhe->thing; + JSContext *cx = (JSContext *)arg; + + GC_MARK(cx, thing, "locked object", NULL); + return JS_DHASH_NEXT; +} + +void +js_ForceGC(JSContext *cx, uintN gcflags) +{ + uintN i; + + for (i = 0; i < GCX_NTYPES; i++) + cx->newborn[i] = NULL; + cx->lastAtom = NULL; + cx->runtime->gcPoke = JS_TRUE; + js_GC(cx, gcflags); + JS_ArenaFinish(); +} + +#define GC_MARK_JSVALS(cx, len, vec, name) \ + JS_BEGIN_MACRO \ + jsval _v, *_vp, *_end; \ + \ + for (_vp = vec, _end = _vp + len; _vp < _end; _vp++) { \ + _v = *_vp; \ + if (JSVAL_IS_GCTHING(_v)) \ + GC_MARK(cx, JSVAL_TO_GCTHING(_v), name, NULL); \ + } \ + JS_END_MACRO + +void +js_GC(JSContext *cx, uintN gcflags) +{ + JSRuntime *rt; + JSContext *iter, *acx; + JSStackFrame *fp, *chain; + uintN i, depth, nslots, type; + JSStackHeader *sh; + JSArena *a, **ap; + uint8 flags, *flagp, *split; + JSGCThing *thing, *limit, **flp, **oflp; + GCFinalizeOp finalizer; + JSBool all_clear; +#ifdef JS_THREADSAFE + jsword currentThread; + uint32 requestDebit; +#endif + + rt = cx->runtime; +#ifdef JS_THREADSAFE + /* Avoid deadlock. */ + JS_ASSERT(!JS_IS_RUNTIME_LOCKED(rt)); +#endif + + /* + * Don't collect garbage if the runtime isn't up, and cx is not the last + * context in the runtime. The last context must force a GC, and nothing + * should suppress that final collection or there may be shutdown leaks, + * or runtime bloat until the next context is created. + */ + if (rt->state != JSRTS_UP && !(gcflags & GC_LAST_CONTEXT)) + return; + + /* + * Let the API user decide to defer a GC if it wants to (unless this + * is the last context). Invoke the callback regardless. + */ + if (rt->gcCallback) { + if (!rt->gcCallback(cx, JSGC_BEGIN) && !(gcflags & GC_LAST_CONTEXT)) + return; + } + + /* Lock out other GC allocator and collector invocations. */ + if (!(gcflags & GC_ALREADY_LOCKED)) + JS_LOCK_GC(rt); + + /* Do nothing if no assignment has executed since the last GC. */ + if (!rt->gcPoke) { + METER(rt->gcStats.nopoke++); + if (!(gcflags & GC_ALREADY_LOCKED)) + JS_UNLOCK_GC(rt); + return; + } + METER(rt->gcStats.poke++); + +#ifdef JS_THREADSAFE + /* Bump gcLevel and return rather than nest on this thread. */ + currentThread = js_CurrentThreadId(); + if (rt->gcThread == currentThread) { + JS_ASSERT(rt->gcLevel > 0); + rt->gcLevel++; + METER(if (rt->gcLevel > rt->gcStats.maxlevel) + rt->gcStats.maxlevel = rt->gcLevel); + if (!(gcflags & GC_ALREADY_LOCKED)) + JS_UNLOCK_GC(rt); + return; + } + + /* + * If we're in one or more requests (possibly on more than one context) + * running on the current thread, indicate, temporarily, that all these + * requests are inactive. NB: if cx->thread is 0, then cx is not using + * the request model, and does not contribute to rt->requestCount. + */ + requestDebit = 0; + if (cx->thread) { + /* + * Check all contexts for any with the same thread-id. XXX should we + * keep a sub-list of contexts having the same id? + */ + iter = NULL; + while ((acx = js_ContextIterator(rt, JS_FALSE, &iter)) != NULL) { + if (acx->thread == cx->thread && acx->requestDepth) + requestDebit++; + } + } else { + /* + * We assert, but check anyway, in case someone is misusing the API. + * Avoiding the loop over all of rt's contexts is a win in the event + * that the GC runs only on request-less contexts with 0 thread-ids, + * in a special thread such as might be used by the UI/DOM/Layout + * "mozilla" or "main" thread in Mozilla-the-browser. + */ + JS_ASSERT(cx->requestDepth == 0); + if (cx->requestDepth) + requestDebit = 1; + } + if (requestDebit) { + JS_ASSERT(requestDebit <= rt->requestCount); + rt->requestCount -= requestDebit; + if (rt->requestCount == 0) + JS_NOTIFY_REQUEST_DONE(rt); + } + + /* If another thread is already in GC, don't attempt GC; wait instead. */ + if (rt->gcLevel > 0) { + /* Bump gcLevel to restart the current GC, so it finds new garbage. */ + rt->gcLevel++; + METER(if (rt->gcLevel > rt->gcStats.maxlevel) + rt->gcStats.maxlevel = rt->gcLevel); + + /* Wait for the other thread to finish, then resume our request. */ + while (rt->gcLevel > 0) + JS_AWAIT_GC_DONE(rt); + if (requestDebit) + rt->requestCount += requestDebit; + if (!(gcflags & GC_ALREADY_LOCKED)) + JS_UNLOCK_GC(rt); + return; + } + + /* No other thread is in GC, so indicate that we're now in GC. */ + rt->gcLevel = 1; + rt->gcThread = currentThread; + + /* Wait for all other requests to finish. */ + while (rt->requestCount > 0) + JS_AWAIT_REQUEST_DONE(rt); + +#else /* !JS_THREADSAFE */ + + /* Bump gcLevel and return rather than nest; the outer gc will restart. */ + rt->gcLevel++; + METER(if (rt->gcLevel > rt->gcStats.maxlevel) + rt->gcStats.maxlevel = rt->gcLevel); + if (rt->gcLevel > 1) + return; + +#endif /* !JS_THREADSAFE */ + + /* + * Set rt->gcRunning here within the GC lock, and after waiting for any + * active requests to end, so that new requests that try to JS_AddRoot, + * JS_RemoveRoot, or JS_RemoveRootRT block in JS_BeginRequest waiting for + * rt->gcLevel to drop to zero, while request-less calls to the *Root* + * APIs block in js_AddRoot or js_RemoveRoot (see above in this file), + * waiting for GC to finish. + */ + rt->gcRunning = JS_TRUE; + JS_UNLOCK_GC(rt); + + /* If a suspended compile is running on another context, keep atoms. */ + if (rt->gcKeepAtoms) + gcflags |= GC_KEEP_ATOMS; + + /* Reset malloc counter. */ + rt->gcMallocBytes = 0; + + /* Drop atoms held by the property cache, and clear property weak links. */ + js_DisablePropertyCache(cx); + js_FlushPropertyCache(cx); +#ifdef DEBUG_brendan + { extern void js_DumpScopeMeters(JSRuntime *rt); + js_DumpScopeMeters(rt); + } +#endif + +restart: + rt->gcNumber++; + + /* + * Mark phase. + */ + JS_DHashTableEnumerate(&rt->gcRootsHash, gc_root_marker, cx); + if (rt->gcLocksHash) + JS_DHashTableEnumerate(rt->gcLocksHash, gc_lock_marker, cx); + js_MarkAtomState(&rt->atomState, gcflags, gc_mark_atom_key_thing, cx); + js_MarkWatchPoints(rt); + iter = NULL; + while ((acx = js_ContextIterator(rt, JS_TRUE, &iter)) != NULL) { + /* + * Iterate frame chain and dormant chains. Temporarily tack current + * frame onto the head of the dormant list to ease iteration. + * + * (NB: see comment on this whole "dormant" thing in js_Execute.) + */ + chain = acx->fp; + if (chain) { + JS_ASSERT(!chain->dormantNext); + chain->dormantNext = acx->dormantFrameChain; + } else { + chain = acx->dormantFrameChain; + } + + for (fp = chain; fp; fp = chain = chain->dormantNext) { + do { + if (fp->callobj) + GC_MARK(cx, fp->callobj, "call object", NULL); + if (fp->argsobj) + GC_MARK(cx, fp->argsobj, "arguments object", NULL); + if (fp->varobj) + GC_MARK(cx, fp->varobj, "variables object", NULL); + if (fp->script) { + js_MarkScript(cx, fp->script, NULL); + if (fp->spbase) { + /* + * Don't mark what has not been pushed yet, or what + * has been popped already. + */ + depth = fp->script->depth; + nslots = (JS_UPTRDIFF(fp->sp, fp->spbase) + < depth * sizeof(jsval)) + ? (uintN)(fp->sp - fp->spbase) + : depth; + GC_MARK_JSVALS(cx, nslots, fp->spbase, "operand"); + } + } + GC_MARK(cx, fp->thisp, "this", NULL); + if (fp->argv) { + nslots = fp->argc; + if (fp->fun && fp->fun->nargs > nslots) + nslots = fp->fun->nargs; + GC_MARK_JSVALS(cx, nslots, fp->argv, "arg"); + } + if (JSVAL_IS_GCTHING(fp->rval)) + GC_MARK(cx, JSVAL_TO_GCTHING(fp->rval), "rval", NULL); + if (fp->vars) + GC_MARK_JSVALS(cx, fp->nvars, fp->vars, "var"); + GC_MARK(cx, fp->scopeChain, "scope chain", NULL); + if (fp->sharpArray) + GC_MARK(cx, fp->sharpArray, "sharp array", NULL); + + if (fp->objAtomMap) { + JSAtom **vector, *atom; + + nslots = fp->objAtomMap->length; + vector = fp->objAtomMap->vector; + for (i = 0; i < nslots; i++) { + atom = vector[i]; + if (atom) + GC_MARK_ATOM(cx, atom, NULL); + } + } + } while ((fp = fp->down) != NULL); + } + + /* Cleanup temporary "dormant" linkage. */ + if (acx->fp) + acx->fp->dormantNext = NULL; + + /* Mark other roots-by-definition in acx. */ + GC_MARK(cx, acx->globalObject, "global object", NULL); + GC_MARK(cx, acx->newborn[GCX_OBJECT], "newborn object", NULL); + GC_MARK(cx, acx->newborn[GCX_STRING], "newborn string", NULL); + GC_MARK(cx, acx->newborn[GCX_DOUBLE], "newborn double", NULL); + GC_MARK(cx, acx->newborn[GCX_MUTABLE_STRING], "newborn mutable string", + NULL); + for (i = GCX_EXTERNAL_STRING; i < GCX_NTYPES; i++) + GC_MARK(cx, acx->newborn[i], "newborn external string", NULL); + if (acx->lastAtom) + GC_MARK_ATOM(cx, acx->lastAtom, NULL); +#if JS_HAS_EXCEPTIONS + if (acx->throwing && JSVAL_IS_GCTHING(acx->exception)) + GC_MARK(cx, JSVAL_TO_GCTHING(acx->exception), "exception", NULL); +#endif +#if JS_HAS_LVALUE_RETURN + if (acx->rval2set && JSVAL_IS_GCTHING(acx->rval2)) + GC_MARK(cx, JSVAL_TO_GCTHING(acx->rval2), "rval2", NULL); +#endif + + for (sh = acx->stackHeaders; sh; sh = sh->down) { + METER(rt->gcStats.stackseg++); + METER(rt->gcStats.segslots += sh->nslots); + GC_MARK_JSVALS(cx, sh->nslots, JS_STACK_SEGMENT(sh), "stack"); + } + } + + if (rt->gcCallback) + (void) rt->gcCallback(cx, JSGC_MARK_END); + + /* + * Sweep phase. + * Finalize as we sweep, outside of rt->gcLock, but with rt->gcRunning set + * so that any attempt to allocate a GC-thing from a finalizer will fail, + * rather than nest badly and leave the unmarked newborn to be swept. + */ + js_SweepAtomState(&rt->atomState); + js_SweepScopeProperties(rt); + js_SweepScriptFilenames(rt); + for (a = rt->gcArenaPool.first.next; a; a = a->next) { + flagp = (uint8 *) a->base; + split = (uint8 *) FIRST_THING_PAGE(a); + limit = (JSGCThing *) a->avail; + for (thing = (JSGCThing *) split; thing < limit; thing++) { + if (((jsuword)thing & GC_PAGE_MASK) == 0) { + flagp++; + thing++; + } + flags = *flagp; + if (flags & GCF_MARK) { + *flagp &= ~GCF_MARK; + } else if (!(flags & (GCF_LOCKMASK | GCF_FINAL))) { + /* Call the finalizer with GCF_FINAL ORed into flags. */ + type = flags & GCF_TYPEMASK; + finalizer = gc_finalizers[type]; + if (finalizer) { + *flagp = (uint8)(flags | GCF_FINAL); + if (type >= GCX_EXTERNAL_STRING) + js_PurgeDeflatedStringCache((JSString *)thing); + finalizer(cx, thing); + } + + /* Set flags to GCF_FINAL, signifying that thing is free. */ + *flagp = GCF_FINAL; + + JS_ASSERT(rt->gcBytes >= sizeof(JSGCThing) + sizeof(uint8)); + rt->gcBytes -= sizeof(JSGCThing) + sizeof(uint8); + } + if (++flagp == split) + flagp += GC_THINGS_SIZE; + } + } + + /* + * Free phase. + * Free any unused arenas and rebuild the JSGCThing freelist. + */ + ap = &rt->gcArenaPool.first.next; + a = *ap; + if (!a) + goto out; + all_clear = JS_TRUE; + flp = oflp = &rt->gcFreeList; + *flp = NULL; + METER(rt->gcStats.freelen = 0); + + do { + flagp = (uint8 *) a->base; + split = (uint8 *) FIRST_THING_PAGE(a); + limit = (JSGCThing *) a->avail; + for (thing = (JSGCThing *) split; thing < limit; thing++) { + if (((jsuword)thing & GC_PAGE_MASK) == 0) { + flagp++; + thing++; + } + if (*flagp != GCF_FINAL) { + all_clear = JS_FALSE; + } else { + thing->flagp = flagp; + *flp = thing; + flp = &thing->next; + METER(rt->gcStats.freelen++); + } + if (++flagp == split) + flagp += GC_THINGS_SIZE; + } + + if (all_clear) { + JS_ARENA_DESTROY(&rt->gcArenaPool, a, ap); + flp = oflp; + METER(rt->gcStats.afree++); + } else { + ap = &a->next; + all_clear = JS_TRUE; + oflp = flp; + } + } while ((a = *ap) != NULL); + + /* Terminate the new freelist. */ + *flp = NULL; + + if (rt->gcCallback) + (void) rt->gcCallback(cx, JSGC_FINALIZE_END); +#ifdef DEBUG_brendan + { extern void DumpSrcNoteSizeHist(); + DumpSrcNoteSizeHist(); + } +#endif + +out: + JS_LOCK_GC(rt); + if (rt->gcLevel > 1) { + rt->gcLevel = 1; + JS_UNLOCK_GC(rt); + goto restart; + } + js_EnablePropertyCache(cx); + rt->gcLevel = 0; + rt->gcLastBytes = rt->gcBytes; + rt->gcPoke = rt->gcRunning = JS_FALSE; + +#ifdef JS_THREADSAFE + /* If we were invoked during a request, pay back the temporary debit. */ + if (requestDebit) + rt->requestCount += requestDebit; + rt->gcThread = 0; + JS_NOTIFY_GC_DONE(rt); + if (!(gcflags & GC_ALREADY_LOCKED)) + JS_UNLOCK_GC(rt); +#endif + + if (rt->gcCallback) { + if (gcflags & GC_ALREADY_LOCKED) + JS_UNLOCK_GC(rt); + (void) rt->gcCallback(cx, JSGC_END); + if (gcflags & GC_ALREADY_LOCKED) + JS_LOCK_GC(rt); + } +} diff --git a/src/dom/js/jsgc.h b/src/dom/js/jsgc.h new file mode 100644 index 000000000..f501aca23 --- /dev/null +++ b/src/dom/js/jsgc.h @@ -0,0 +1,230 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsgc_h___ +#define jsgc_h___ +/* + * JS Garbage Collector. + */ +#include "jsprvtd.h" +#include "jspubtd.h" +#include "jsdhash.h" + +JS_BEGIN_EXTERN_C + +/* GC thing type indexes. */ +#define GCX_OBJECT 0 /* JSObject */ +#define GCX_STRING 1 /* JSString */ +#define GCX_DOUBLE 2 /* jsdouble */ +#define GCX_MUTABLE_STRING 3 /* JSString that's mutable -- + single-threaded only! */ +#define GCX_EXTERNAL_STRING 4 /* JSString w/ external chars */ +#define GCX_NTYPES_LOG2 3 /* type index bits */ +#define GCX_NTYPES JS_BIT(GCX_NTYPES_LOG2) + +/* GC flag definitions, must fit in 8 bits (type index goes in the low bits). */ +#define GCF_TYPEMASK JS_BITMASK(GCX_NTYPES_LOG2) +#define GCF_MARK JS_BIT(GCX_NTYPES_LOG2) +#define GCF_FINAL JS_BIT(GCX_NTYPES_LOG2 + 1) +#define GCF_LOCKSHIFT (GCX_NTYPES_LOG2 + 2) /* lock bit shift and mask */ +#define GCF_LOCKMASK (JS_BITMASK(8 - GCF_LOCKSHIFT) << GCF_LOCKSHIFT) +#define GCF_LOCK JS_BIT(GCF_LOCKSHIFT) /* lock request bit in API */ + +/* Pseudo-flag that modifies GCX_STRING to make GCX_MUTABLE_STRING. */ +#define GCF_MUTABLE 2 + +#if (GCX_STRING | GCF_MUTABLE) != GCX_MUTABLE_STRING +# error "mutable string type index botch!" +#endif + +extern uint8 * +js_GetGCThingFlags(void *thing); + +/* These are compatible with JSDHashEntryStub. */ +struct JSGCRootHashEntry { + JSDHashEntryHdr hdr; + void *root; + const char *name; +}; + +struct JSGCLockHashEntry { + JSDHashEntryHdr hdr; + const JSGCThing *thing; + uint32 count; +}; + +#if 1 +/* + * Since we're forcing a GC from JS_GC anyway, don't bother wasting cycles + * loading oldval. XXX remove implied force, fix jsinterp.c's "second arg + * ignored", etc. + */ +#define GC_POKE(cx, oldval) ((cx)->runtime->gcPoke = JS_TRUE) +#else +#define GC_POKE(cx, oldval) ((cx)->runtime->gcPoke = JSVAL_IS_GCTHING(oldval)) +#endif + +extern intN +js_ChangeExternalStringFinalizer(JSStringFinalizeOp oldop, + JSStringFinalizeOp newop); + +extern JSBool +js_InitGC(JSRuntime *rt, uint32 maxbytes); + +extern void +js_FinishGC(JSRuntime *rt); + +extern JSBool +js_AddRoot(JSContext *cx, void *rp, const char *name); + +extern JSBool +js_AddRootRT(JSRuntime *rt, void *rp, const char *name); + +extern JSBool +js_RemoveRoot(JSRuntime *rt, void *rp); + +extern void * +js_AllocGCThing(JSContext *cx, uintN flags); + +extern JSBool +js_LockGCThing(JSContext *cx, void *thing); + +extern JSBool +js_LockGCThingRT(JSRuntime *rt, void *thing); + +extern JSBool +js_UnlockGCThingRT(JSRuntime *rt, void *thing); + +extern JSBool +js_IsAboutToBeFinalized(JSContext *cx, void *thing); + +extern void +js_MarkAtom(JSContext *cx, JSAtom *atom, void *arg); + +/* We avoid a large number of unnecessary calls by doing the flag check first */ +#define GC_MARK_ATOM(cx, atom, arg) \ + JS_BEGIN_MACRO \ + if (!((atom)->flags & ATOM_MARK)) \ + js_MarkAtom(cx, atom, arg); \ + JS_END_MACRO + +extern void +js_MarkGCThing(JSContext *cx, void *thing, void *arg); + +#ifdef GC_MARK_DEBUG + +typedef struct GCMarkNode GCMarkNode; + +struct GCMarkNode { + void *thing; + const char *name; + GCMarkNode *next; + GCMarkNode *prev; +}; + +#define GC_MARK(_cx, _thing, _name, _prev) \ + JS_BEGIN_MACRO \ + GCMarkNode _node; \ + _node.thing = _thing; \ + _node.name = _name; \ + _node.next = NULL; \ + _node.prev = _prev; \ + if (_prev) ((GCMarkNode *)(_prev))->next = &_node; \ + js_MarkGCThing(_cx, _thing, &_node); \ + JS_END_MACRO + +#else /* !GC_MARK_DEBUG */ + +#define GC_MARK(cx, thing, name, prev) js_MarkGCThing(cx, thing, NULL) + +#endif /* !GC_MARK_DEBUG */ + +/* + * Flags to modify how a GC marks and sweeps: + * GC_KEEP_ATOMS Don't sweep unmarked atoms, they may be in use by the + * compiler, or by an API function that calls js_Atomize, + * when the GC is called from js_AllocGCThing, due to a + * malloc failure or runtime GC-thing limit. + * GC_LAST_CONTEXT Called from js_DestroyContext for last JSContext in a + * JSRuntime, when it is imperative that rt->gcPoke gets + * cleared early in js_GC, if it is set. + * GC_ALREADY_LOCKED rt->gcLock is already held on entry to js_GC, and kept + * on return to its caller. + */ +#define GC_KEEP_ATOMS 0x1 +#define GC_LAST_CONTEXT 0x2 +#define GC_ALREADY_LOCKED 0x4 + +extern void +js_ForceGC(JSContext *cx, uintN gcflags); + +extern void +js_GC(JSContext *cx, uintN gcflags); + +#ifdef JS_GCMETER + +typedef struct JSGCStats { + uint32 alloc; /* number of allocation attempts */ + uint32 freelen; /* gcFreeList length */ + uint32 recycle; /* number of things recycled through gcFreeList */ + uint32 retry; /* allocation attempt retries after running the GC */ + uint32 fail; /* allocation failures */ + uint32 finalfail; /* finalizer calls allocator failures */ + uint32 lock; /* valid lock calls */ + uint32 unlock; /* valid unlock calls */ + uint32 stuck; /* stuck reference counts seen by lock calls */ + uint32 unstuck; /* unlock calls that saw a stuck lock count */ + uint32 depth; /* mark recursion depth */ + uint32 maxdepth; /* maximum mark recursion depth */ + uint32 maxlevel; /* maximum GC nesting (indirect recursion) level */ + uint32 poke; /* number of potentially useful GC calls */ + uint32 nopoke; /* useless GC calls where js_PokeGC was not set */ + uint32 afree; /* thing arenas freed so far */ + uint32 stackseg; /* total extraordinary stack segments scanned */ + uint32 segslots; /* total stack segment jsval slots scanned */ +} JSGCStats; + +extern void +js_DumpGCStats(JSRuntime *rt, FILE *fp); + +#endif /* JS_GCMETER */ + +JS_END_EXTERN_C + +#endif /* jsgc_h___ */ diff --git a/src/dom/js/jshash.c b/src/dom/js/jshash.c new file mode 100644 index 000000000..3382daadb --- /dev/null +++ b/src/dom/js/jshash.c @@ -0,0 +1,479 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * PR hash table package. + */ +#include "jsstddef.h" +#include +#include +#include "jstypes.h" +#include "jsbit.h" +#include "jsutil.h" /* Added by JSIFY */ +#include "jshash.h" /* Added by JSIFY */ + +/* Compute the number of buckets in ht */ +#define NBUCKETS(ht) JS_BIT(JS_HASH_BITS - (ht)->shift) + +/* The smallest table has 16 buckets */ +#define MINBUCKETSLOG2 4 +#define MINBUCKETS JS_BIT(MINBUCKETSLOG2) + +/* Compute the maximum entries given n buckets that we will tolerate, ~90% */ +#define OVERLOADED(n) ((n) - ((n) >> 3)) + +/* Compute the number of entries below which we shrink the table by half */ +#define UNDERLOADED(n) (((n) > MINBUCKETS) ? ((n) >> 2) : 0) + +/* +** Stubs for default hash allocator ops. +*/ +static void * +DefaultAllocTable(void *pool, size_t size) +{ + return malloc(size); +} + +static void +DefaultFreeTable(void *pool, void *item) +{ + free(item); +} + +static JSHashEntry * +DefaultAllocEntry(void *pool, const void *key) +{ + return (JSHashEntry*) malloc(sizeof(JSHashEntry)); +} + +static void +DefaultFreeEntry(void *pool, JSHashEntry *he, uintN flag) +{ + if (flag == HT_FREE_ENTRY) + free(he); +} + +static JSHashAllocOps defaultHashAllocOps = { + DefaultAllocTable, DefaultFreeTable, + DefaultAllocEntry, DefaultFreeEntry +}; + +JS_PUBLIC_API(JSHashTable *) +JS_NewHashTable(uint32 n, JSHashFunction keyHash, + JSHashComparator keyCompare, JSHashComparator valueCompare, + JSHashAllocOps *allocOps, void *allocPriv) +{ + JSHashTable *ht; + size_t nb; + + if (n <= MINBUCKETS) { + n = MINBUCKETSLOG2; + } else { + n = JS_CeilingLog2(n); + if ((int32)n < 0) + return NULL; + } + + if (!allocOps) allocOps = &defaultHashAllocOps; + + ht = (JSHashTable*) (*allocOps->allocTable)(allocPriv, sizeof *ht); + if (!ht) + return NULL; + memset(ht, 0, sizeof *ht); + ht->shift = JS_HASH_BITS - n; + n = JS_BIT(n); +#if defined _MSC_VER && _MSC_VER <= 800 + if (n > 16000) { + (*allocOps->freeTable)(allocPriv, ht); + return NULL; + } +#endif /* WIN16 */ + nb = n * sizeof(JSHashEntry *); + ht->buckets = (JSHashEntry**) (*allocOps->allocTable)(allocPriv, nb); + if (!ht->buckets) { + (*allocOps->freeTable)(allocPriv, ht); + return NULL; + } + memset(ht->buckets, 0, nb); + + ht->keyHash = keyHash; + ht->keyCompare = keyCompare; + ht->valueCompare = valueCompare; + ht->allocOps = allocOps; + ht->allocPriv = allocPriv; + return ht; +} + +JS_PUBLIC_API(void) +JS_HashTableDestroy(JSHashTable *ht) +{ + uint32 i, n; + JSHashEntry *he, **hep; + JSHashAllocOps *allocOps = ht->allocOps; + void *allocPriv = ht->allocPriv; + + n = NBUCKETS(ht); + for (i = 0; i < n; i++) { + hep = &ht->buckets[i]; + while ((he = *hep) != NULL) { + *hep = he->next; + (*allocOps->freeEntry)(allocPriv, he, HT_FREE_ENTRY); + } + } +#ifdef DEBUG + memset(ht->buckets, 0xDB, n * sizeof ht->buckets[0]); +#endif + (*allocOps->freeTable)(allocPriv, ht->buckets); +#ifdef DEBUG + memset(ht, 0xDB, sizeof *ht); +#endif + (*allocOps->freeTable)(allocPriv, ht); +} + +/* +** Multiplicative hash, from Knuth 6.4. +*/ +JS_PUBLIC_API(JSHashEntry **) +JS_HashTableRawLookup(JSHashTable *ht, JSHashNumber keyHash, const void *key) +{ + JSHashEntry *he, **hep, **hep0; + JSHashNumber h; + +#ifdef HASHMETER + ht->nlookups++; +#endif + h = keyHash * JS_GOLDEN_RATIO; + h >>= ht->shift; + hep = hep0 = &ht->buckets[h]; + while ((he = *hep) != NULL) { + if (he->keyHash == keyHash && (*ht->keyCompare)(key, he->key)) { + /* Move to front of chain if not already there */ + if (hep != hep0) { + *hep = he->next; + he->next = *hep0; + *hep0 = he; + } + return hep0; + } + hep = &he->next; +#ifdef HASHMETER + ht->nsteps++; +#endif + } + return hep; +} + +JS_PUBLIC_API(JSHashEntry *) +JS_HashTableRawAdd(JSHashTable *ht, JSHashEntry **hep, + JSHashNumber keyHash, const void *key, void *value) +{ + uint32 i, n; + JSHashEntry *he, *next, **oldbuckets; + size_t nb; + + /* Grow the table if it is overloaded */ + n = NBUCKETS(ht); + if (ht->nentries >= OVERLOADED(n)) { + oldbuckets = ht->buckets; +#if defined _MSC_VER && _MSC_VER <= 800 + if (2 * n > 16000) + return NULL; +#endif /* WIN16 */ + nb = 2 * n * sizeof(JSHashEntry *); + ht->buckets = (JSHashEntry**) (*ht->allocOps->allocTable)(ht->allocPriv, nb); + if (!ht->buckets) { + ht->buckets = oldbuckets; + return NULL; + } + memset(ht->buckets, 0, nb); +#ifdef HASHMETER + ht->ngrows++; +#endif + ht->shift--; + + for (i = 0; i < n; i++) { + for (he = oldbuckets[i]; he; he = next) { + next = he->next; + hep = JS_HashTableRawLookup(ht, he->keyHash, he->key); + JS_ASSERT(*hep == NULL); + he->next = NULL; + *hep = he; + } + } +#ifdef DEBUG + memset(oldbuckets, 0xDB, n * sizeof oldbuckets[0]); +#endif + (*ht->allocOps->freeTable)(ht->allocPriv, oldbuckets); + hep = JS_HashTableRawLookup(ht, keyHash, key); + } + + /* Make a new key value entry */ + he = (*ht->allocOps->allocEntry)(ht->allocPriv, key); + if (!he) + return NULL; + he->keyHash = keyHash; + he->key = key; + he->value = value; + he->next = *hep; + *hep = he; + ht->nentries++; + return he; +} + +JS_PUBLIC_API(JSHashEntry *) +JS_HashTableAdd(JSHashTable *ht, const void *key, void *value) +{ + JSHashNumber keyHash; + JSHashEntry *he, **hep; + + keyHash = (*ht->keyHash)(key); + hep = JS_HashTableRawLookup(ht, keyHash, key); + if ((he = *hep) != NULL) { + /* Hit; see if values match */ + if ((*ht->valueCompare)(he->value, value)) { + /* key,value pair is already present in table */ + return he; + } + if (he->value) + (*ht->allocOps->freeEntry)(ht->allocPriv, he, HT_FREE_VALUE); + he->value = value; + return he; + } + return JS_HashTableRawAdd(ht, hep, keyHash, key, value); +} + +JS_PUBLIC_API(void) +JS_HashTableRawRemove(JSHashTable *ht, JSHashEntry **hep, JSHashEntry *he) +{ + uint32 i, n; + JSHashEntry *next, **oldbuckets; + size_t nb; + + *hep = he->next; + (*ht->allocOps->freeEntry)(ht->allocPriv, he, HT_FREE_ENTRY); + + /* Shrink table if it's underloaded */ + n = NBUCKETS(ht); + if (--ht->nentries < UNDERLOADED(n)) { + oldbuckets = ht->buckets; + nb = n * sizeof(JSHashEntry*) / 2; + ht->buckets = (JSHashEntry**) (*ht->allocOps->allocTable)(ht->allocPriv, nb); + if (!ht->buckets) { + ht->buckets = oldbuckets; + return; + } + memset(ht->buckets, 0, nb); +#ifdef HASHMETER + ht->nshrinks++; +#endif + ht->shift++; + + for (i = 0; i < n; i++) { + for (he = oldbuckets[i]; he; he = next) { + next = he->next; + hep = JS_HashTableRawLookup(ht, he->keyHash, he->key); + JS_ASSERT(*hep == NULL); + he->next = NULL; + *hep = he; + } + } +#ifdef DEBUG + memset(oldbuckets, 0xDB, n * sizeof oldbuckets[0]); +#endif + (*ht->allocOps->freeTable)(ht->allocPriv, oldbuckets); + } +} + +JS_PUBLIC_API(JSBool) +JS_HashTableRemove(JSHashTable *ht, const void *key) +{ + JSHashNumber keyHash; + JSHashEntry *he, **hep; + + keyHash = (*ht->keyHash)(key); + hep = JS_HashTableRawLookup(ht, keyHash, key); + if ((he = *hep) == NULL) + return JS_FALSE; + + /* Hit; remove element */ + JS_HashTableRawRemove(ht, hep, he); + return JS_TRUE; +} + +JS_PUBLIC_API(void *) +JS_HashTableLookup(JSHashTable *ht, const void *key) +{ + JSHashNumber keyHash; + JSHashEntry *he, **hep; + + keyHash = (*ht->keyHash)(key); + hep = JS_HashTableRawLookup(ht, keyHash, key); + if ((he = *hep) != NULL) { + return he->value; + } + return NULL; +} + +/* +** Iterate over the entries in the hash table calling func for each +** entry found. Stop if "f" says to (return value & JS_ENUMERATE_STOP). +** Return a count of the number of elements scanned. +*/ +JS_PUBLIC_API(int) +JS_HashTableEnumerateEntries(JSHashTable *ht, JSHashEnumerator f, void *arg) +{ + JSHashEntry *he, **hep; + uint32 i, nbuckets; + int rv, n = 0; + JSHashEntry *todo = NULL; + + nbuckets = NBUCKETS(ht); + for (i = 0; i < nbuckets; i++) { + hep = &ht->buckets[i]; + while ((he = *hep) != NULL) { + rv = (*f)(he, n, arg); + n++; + if (rv & (HT_ENUMERATE_REMOVE | HT_ENUMERATE_UNHASH)) { + *hep = he->next; + if (rv & HT_ENUMERATE_REMOVE) { + he->next = todo; + todo = he; + } + } else { + hep = &he->next; + } + if (rv & HT_ENUMERATE_STOP) { + goto out; + } + } + } + +out: + hep = &todo; + while ((he = *hep) != NULL) { + JS_HashTableRawRemove(ht, hep, he); + } + return n; +} + +#ifdef HASHMETER +#include +#include + +JS_PUBLIC_API(void) +JS_HashTableDumpMeter(JSHashTable *ht, JSHashEnumerator dump, FILE *fp) +{ + double sqsum, mean, variance, sigma; + uint32 nchains, nbuckets, nentries; + uint32 i, n, maxChain, maxChainLen; + JSHashEntry *he; + + sqsum = 0; + nchains = 0; + maxChainLen = 0; + nbuckets = NBUCKETS(ht); + for (i = 0; i < nbuckets; i++) { + he = ht->buckets[i]; + if (!he) + continue; + nchains++; + for (n = 0; he; he = he->next) + n++; + sqsum += n * n; + if (n > maxChainLen) { + maxChainLen = n; + maxChain = i; + } + } + nentries = ht->nentries; + mean = (double)nentries / nchains; + variance = nchains * sqsum - nentries * nentries; + if (variance < 0 || nchains == 1) + variance = 0; + else + variance /= nchains * (nchains - 1); + sigma = sqrt(variance); + + fprintf(fp, "\nHash table statistics:\n"); + fprintf(fp, " number of lookups: %u\n", ht->nlookups); + fprintf(fp, " number of entries: %u\n", ht->nentries); + fprintf(fp, " number of grows: %u\n", ht->ngrows); + fprintf(fp, " number of shrinks: %u\n", ht->nshrinks); + fprintf(fp, " mean steps per hash: %g\n", (double)ht->nsteps + / ht->nlookups); + fprintf(fp, "mean hash chain length: %g\n", mean); + fprintf(fp, " standard deviation: %g\n", sigma); + fprintf(fp, " max hash chain length: %u\n", maxChainLen); + fprintf(fp, " max hash chain: [%u]\n", maxChain); + + for (he = ht->buckets[maxChain], i = 0; he; he = he->next, i++) + if ((*dump)(he, i, fp) != HT_ENUMERATE_NEXT) + break; +} +#endif /* HASHMETER */ + +JS_PUBLIC_API(int) +JS_HashTableDump(JSHashTable *ht, JSHashEnumerator dump, FILE *fp) +{ + int count; + + count = JS_HashTableEnumerateEntries(ht, dump, fp); +#ifdef HASHMETER + JS_HashTableDumpMeter(ht, dump, fp); +#endif + return count; +} + +JS_PUBLIC_API(JSHashNumber) +JS_HashString(const void *key) +{ + JSHashNumber h; + const unsigned char *s; + + h = 0; + for (s = (const unsigned char *)key; *s; s++) + h = (h >> (JS_HASH_BITS - 4)) ^ (h << 4) ^ *s; + return h; +} + +JS_PUBLIC_API(int) +JS_CompareValues(const void *v1, const void *v2) +{ + return v1 == v2; +} diff --git a/src/dom/js/jshash.h b/src/dom/js/jshash.h new file mode 100644 index 000000000..85e3cf811 --- /dev/null +++ b/src/dom/js/jshash.h @@ -0,0 +1,152 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jshash_h___ +#define jshash_h___ +/* + * API to portable hash table code. + */ +#include +#include +#include "jstypes.h" +#include "jscompat.h" + +JS_BEGIN_EXTERN_C + +typedef uint32 JSHashNumber; +typedef struct JSHashEntry JSHashEntry; +typedef struct JSHashTable JSHashTable; + +#define JS_HASH_BITS 32 +#define JS_GOLDEN_RATIO 0x9E3779B9U + +typedef JSHashNumber (* JS_DLL_CALLBACK JSHashFunction)(const void *key); +typedef intN (* JS_DLL_CALLBACK JSHashComparator)(const void *v1, const void *v2); +typedef intN (* JS_DLL_CALLBACK JSHashEnumerator)(JSHashEntry *he, intN i, void *arg); + +/* Flag bits in JSHashEnumerator's return value */ +#define HT_ENUMERATE_NEXT 0 /* continue enumerating entries */ +#define HT_ENUMERATE_STOP 1 /* stop enumerating entries */ +#define HT_ENUMERATE_REMOVE 2 /* remove and free the current entry */ +#define HT_ENUMERATE_UNHASH 4 /* just unhash the current entry */ + +typedef struct JSHashAllocOps { + void * (*allocTable)(void *pool, size_t size); + void (*freeTable)(void *pool, void *item); + JSHashEntry * (*allocEntry)(void *pool, const void *key); + void (*freeEntry)(void *pool, JSHashEntry *he, uintN flag); +} JSHashAllocOps; + +#define HT_FREE_VALUE 0 /* just free the entry's value */ +#define HT_FREE_ENTRY 1 /* free value and entire entry */ + +struct JSHashEntry { + JSHashEntry *next; /* hash chain linkage */ + JSHashNumber keyHash; /* key hash function result */ + const void *key; /* ptr to opaque key */ + void *value; /* ptr to opaque value */ +}; + +struct JSHashTable { + JSHashEntry **buckets; /* vector of hash buckets */ + uint32 nentries; /* number of entries in table */ + uint32 shift; /* multiplicative hash shift */ + JSHashFunction keyHash; /* key hash function */ + JSHashComparator keyCompare; /* key comparison function */ + JSHashComparator valueCompare; /* value comparison function */ + JSHashAllocOps *allocOps; /* allocation operations */ + void *allocPriv; /* allocation private data */ +#ifdef HASHMETER + uint32 nlookups; /* total number of lookups */ + uint32 nsteps; /* number of hash chains traversed */ + uint32 ngrows; /* number of table expansions */ + uint32 nshrinks; /* number of table contractions */ +#endif +}; + +/* + * Create a new hash table. + * If allocOps is null, use default allocator ops built on top of malloc(). + */ +extern JS_PUBLIC_API(JSHashTable *) +JS_NewHashTable(uint32 n, JSHashFunction keyHash, + JSHashComparator keyCompare, JSHashComparator valueCompare, + JSHashAllocOps *allocOps, void *allocPriv); + +extern JS_PUBLIC_API(void) +JS_HashTableDestroy(JSHashTable *ht); + +/* Low level access methods */ +extern JS_PUBLIC_API(JSHashEntry **) +JS_HashTableRawLookup(JSHashTable *ht, JSHashNumber keyHash, const void *key); + +extern JS_PUBLIC_API(JSHashEntry *) +JS_HashTableRawAdd(JSHashTable *ht, JSHashEntry **hep, JSHashNumber keyHash, + const void *key, void *value); + +extern JS_PUBLIC_API(void) +JS_HashTableRawRemove(JSHashTable *ht, JSHashEntry **hep, JSHashEntry *he); + +/* Higher level access methods */ +extern JS_PUBLIC_API(JSHashEntry *) +JS_HashTableAdd(JSHashTable *ht, const void *key, void *value); + +extern JS_PUBLIC_API(JSBool) +JS_HashTableRemove(JSHashTable *ht, const void *key); + +extern JS_PUBLIC_API(intN) +JS_HashTableEnumerateEntries(JSHashTable *ht, JSHashEnumerator f, void *arg); + +extern JS_PUBLIC_API(void *) +JS_HashTableLookup(JSHashTable *ht, const void *key); + +extern JS_PUBLIC_API(intN) +JS_HashTableDump(JSHashTable *ht, JSHashEnumerator dump, FILE *fp); + +/* General-purpose C string hash function. */ +extern JS_PUBLIC_API(JSHashNumber) +JS_HashString(const void *key); + +/* Stub function just returns v1 == v2 */ +extern JS_PUBLIC_API(intN) +JS_CompareValues(const void *v1, const void *v2); + +JS_END_EXTERN_C + +#endif /* jshash_h___ */ diff --git a/src/dom/js/jsinterp.c b/src/dom/js/jsinterp.c new file mode 100644 index 000000000..60f39b3ce --- /dev/null +++ b/src/dom/js/jsinterp.c @@ -0,0 +1,4284 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* build on macs with low memory */ +#if defined(XP_MAC) && defined(MOZ_MAC_LOWMEM) +#pragma optimization_level 1 +#endif + +/* + * JavaScript bytecode interpreter. + */ +#include "jsstddef.h" +#include +#include +#include +#include "jstypes.h" +#include "jsarena.h" /* Added by JSIFY */ +#include "jsutil.h" /* Added by JSIFY */ +#include "jsprf.h" +#include "jsapi.h" +#include "jsarray.h" +#include "jsatom.h" +#include "jsbool.h" +#include "jscntxt.h" +#include "jsconfig.h" +#include "jsdbgapi.h" +#include "jsfun.h" +#include "jsgc.h" +#include "jsinterp.h" +#include "jslock.h" +#include "jsnum.h" +#include "jsobj.h" +#include "jsopcode.h" +#include "jsscope.h" +#include "jsscript.h" +#include "jsstr.h" + +#if JS_HAS_JIT +#include "jsjit.h" +#endif + +#ifdef DEBUG +#define ASSERT_CACHE_IS_EMPTY(cache) \ + JS_BEGIN_MACRO \ + JSPropertyCacheEntry *end_, *pce_, entry_; \ + JSPropertyCache *cache_ = (cache); \ + JS_ASSERT(cache_->empty); \ + end_ = &cache_->table[PROPERTY_CACHE_SIZE]; \ + for (pce_ = &cache_->table[0]; pce_ < end_; pce_++) { \ + PCE_LOAD(cache_, pce_, entry_); \ + JS_ASSERT(!PCE_OBJECT(entry_)); \ + JS_ASSERT(!PCE_PROPERTY(entry_)); \ + } \ + JS_END_MACRO +#else +#define ASSERT_CACHE_IS_EMPTY(cache) ((void)0) +#endif + +void +js_FlushPropertyCache(JSContext *cx) +{ + JSPropertyCache *cache; + + cache = &cx->runtime->propertyCache; + if (cache->empty) { + ASSERT_CACHE_IS_EMPTY(cache); + return; + } + memset(cache->table, 0, sizeof cache->table); + cache->empty = JS_TRUE; +#ifdef JS_PROPERTY_CACHE_METERING + cache->flushes++; +#endif +} + +void +js_DisablePropertyCache(JSContext *cx) +{ + JS_ASSERT(!cx->runtime->propertyCache.disabled); + cx->runtime->propertyCache.disabled = JS_TRUE; +} + +void +js_EnablePropertyCache(JSContext *cx) +{ + JS_ASSERT(cx->runtime->propertyCache.disabled); + ASSERT_CACHE_IS_EMPTY(&cx->runtime->propertyCache); + cx->runtime->propertyCache.disabled = JS_FALSE; +} + +/* + * Class for for/in loop property iterator objects. + */ +#define JSSLOT_ITER_STATE JSSLOT_PRIVATE + +static void +prop_iterator_finalize(JSContext *cx, JSObject *obj) +{ + jsval iter_state; + jsval iteratee; + + /* Protect against stillborn iterators. */ + iter_state = obj->slots[JSSLOT_ITER_STATE]; + iteratee = obj->slots[JSSLOT_PARENT]; + if (!JSVAL_IS_NULL(iter_state) && !JSVAL_IS_PRIMITIVE(iteratee)) { + OBJ_ENUMERATE(cx, JSVAL_TO_OBJECT(iteratee), JSENUMERATE_DESTROY, + &iter_state, NULL); + } + js_RemoveRoot(cx->runtime, &obj->slots[JSSLOT_PARENT]); + + /* XXX force the GC to restart so we can collect iteratee, if possible, + during the current collector activation */ + cx->runtime->gcLevel++; +} + +static JSClass prop_iterator_class = { + "PropertyIterator", + 0, + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, prop_iterator_finalize, + JSCLASS_NO_OPTIONAL_MEMBERS +}; + +/* + * Stack macros and functions. These all use a local variable, jsval *sp, to + * point to the next free stack slot. SAVE_SP must be called before any call + * to a function that may invoke the interpreter. RESTORE_SP must be called + * only after return from js_Invoke, because only js_Invoke changes fp->sp. + */ +#define PUSH(v) (*sp++ = (v)) +#define POP() (*--sp) +#ifdef DEBUG +#define SAVE_SP(fp) \ + (JS_ASSERT((fp)->script || !(fp)->spbase || (sp) == (fp)->spbase), \ + (fp)->sp = sp) +#else +#define SAVE_SP(fp) ((fp)->sp = sp) +#endif +#define RESTORE_SP(fp) (sp = (fp)->sp) + +/* + * Push the generating bytecode's pc onto the parallel pc stack that runs + * depth slots below the operands. + * + * NB: PUSH_OPND uses sp, depth, and pc from its lexical environment. See + * js_Interpret for these local variables' declarations and uses. + */ +#define PUSH_OPND(v) (sp[-depth] = (jsval)pc, PUSH(v)) +#define STORE_OPND(n,v) (sp[(n)-depth] = (jsval)pc, sp[n] = (v)) +#define POP_OPND() POP() +#define FETCH_OPND(n) (sp[n]) + +/* + * Push the jsdouble d using sp, depth, and pc from the lexical environment. + * Try to convert d to a jsint that fits in a jsval, otherwise GC-alloc space + * for it and push a reference. + */ +#define STORE_NUMBER(cx, n, d) \ + JS_BEGIN_MACRO \ + jsint i_; \ + jsval v_; \ + \ + if (JSDOUBLE_IS_INT(d, i_) && INT_FITS_IN_JSVAL(i_)) { \ + v_ = INT_TO_JSVAL(i_); \ + } else { \ + ok = js_NewDoubleValue(cx, d, &v_); \ + if (!ok) \ + goto out; \ + } \ + STORE_OPND(n, v_); \ + JS_END_MACRO + +#define FETCH_NUMBER(cx, n, d) \ + JS_BEGIN_MACRO \ + jsval v_; \ + \ + v_ = FETCH_OPND(n); \ + VALUE_TO_NUMBER(cx, v_, d); \ + JS_END_MACRO + +#define FETCH_INT(cx, n, i) \ + JS_BEGIN_MACRO \ + jsval v_ = FETCH_OPND(n); \ + if (JSVAL_IS_INT(v_)) { \ + i = JSVAL_TO_INT(v_); \ + } else { \ + SAVE_SP(fp); \ + ok = js_ValueToECMAInt32(cx, v_, &i); \ + if (!ok) \ + goto out; \ + } \ + JS_END_MACRO + +#define FETCH_UINT(cx, n, ui) \ + JS_BEGIN_MACRO \ + jsval v_ = FETCH_OPND(n); \ + jsint i_; \ + if (JSVAL_IS_INT(v_) && (i_ = JSVAL_TO_INT(v_)) >= 0) { \ + ui = (uint32) i_; \ + } else { \ + SAVE_SP(fp); \ + ok = js_ValueToECMAUint32(cx, v_, &ui); \ + if (!ok) \ + goto out; \ + } \ + JS_END_MACRO + +/* + * Optimized conversion macros that test for the desired type in v before + * homing sp and calling a conversion function. + */ +#define VALUE_TO_NUMBER(cx, v, d) \ + JS_BEGIN_MACRO \ + if (JSVAL_IS_INT(v)) { \ + d = (jsdouble)JSVAL_TO_INT(v); \ + } else if (JSVAL_IS_DOUBLE(v)) { \ + d = *JSVAL_TO_DOUBLE(v); \ + } else { \ + SAVE_SP(fp); \ + ok = js_ValueToNumber(cx, v, &d); \ + if (!ok) \ + goto out; \ + } \ + JS_END_MACRO + +#define POP_BOOLEAN(cx, v, b) \ + JS_BEGIN_MACRO \ + v = FETCH_OPND(-1); \ + if (v == JSVAL_NULL) { \ + b = JS_FALSE; \ + } else if (JSVAL_IS_BOOLEAN(v)) { \ + b = JSVAL_TO_BOOLEAN(v); \ + } else { \ + SAVE_SP(fp); \ + ok = js_ValueToBoolean(cx, v, &b); \ + if (!ok) \ + goto out; \ + } \ + sp--; \ + JS_END_MACRO + +#define VALUE_TO_OBJECT(cx, v, obj) \ + JS_BEGIN_MACRO \ + if (JSVAL_IS_OBJECT(v) && v != JSVAL_NULL) { \ + obj = JSVAL_TO_OBJECT(v); \ + } else { \ + SAVE_SP(fp); \ + obj = js_ValueToNonNullObject(cx, v); \ + if (!obj) { \ + ok = JS_FALSE; \ + goto out; \ + } \ + } \ + JS_END_MACRO + +#if JS_BUG_VOID_TOSTRING +#define CHECK_VOID_TOSTRING(cx, v) \ + if (JSVAL_IS_VOID(v)) { \ + JSString *str_; \ + str_ = ATOM_TO_STRING(cx->runtime->atomState.typeAtoms[JSTYPE_VOID]); \ + v = STRING_TO_JSVAL(str_); \ + } +#else +#define CHECK_VOID_TOSTRING(cx, v) ((void)0) +#endif + +#if JS_BUG_EAGER_TOSTRING +#define CHECK_EAGER_TOSTRING(hint) (hint = JSTYPE_STRING) +#else +#define CHECK_EAGER_TOSTRING(hint) ((void)0) +#endif + +#define VALUE_TO_PRIMITIVE(cx, v, hint, vp) \ + JS_BEGIN_MACRO \ + if (JSVAL_IS_PRIMITIVE(v)) { \ + CHECK_VOID_TOSTRING(cx, v); \ + *vp = v; \ + } else { \ + SAVE_SP(fp); \ + CHECK_EAGER_TOSTRING(hint); \ + ok = OBJ_DEFAULT_VALUE(cx, JSVAL_TO_OBJECT(v), hint, vp); \ + if (!ok) \ + goto out; \ + } \ + JS_END_MACRO + +JS_FRIEND_API(jsval *) +js_AllocRawStack(JSContext *cx, uintN nslots, void **markp) +{ + jsval *sp; + + if (markp) + *markp = JS_ARENA_MARK(&cx->stackPool); + JS_ARENA_ALLOCATE_CAST(sp, jsval *, &cx->stackPool, nslots * sizeof(jsval)); + if (!sp) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_STACK_OVERFLOW, + (cx->fp && cx->fp->fun) + ? JS_GetFunctionName(cx->fp->fun) + : "script"); + } + return sp; +} + +JS_FRIEND_API(void) +js_FreeRawStack(JSContext *cx, void *mark) +{ + JS_ARENA_RELEASE(&cx->stackPool, mark); +} + +JS_FRIEND_API(jsval *) +js_AllocStack(JSContext *cx, uintN nslots, void **markp) +{ + jsval *sp, *vp, *end; + JSArena *a; + JSStackHeader *sh; + JSStackFrame *fp; + + /* Callers don't check for zero nslots: we do to avoid empty segments. */ + if (nslots == 0) { + *markp = NULL; + return JS_ARENA_MARK(&cx->stackPool); + } + + /* Allocate 2 extra slots for the stack segment header we'll likely need. */ + sp = js_AllocRawStack(cx, 2 + nslots, markp); + if (!sp) + return NULL; + + /* Try to avoid another header if we can piggyback on the last segment. */ + a = cx->stackPool.current; + sh = cx->stackHeaders; + if (sh && JS_STACK_SEGMENT(sh) + sh->nslots == sp) { + /* Extend the last stack segment, give back the 2 header slots. */ + sh->nslots += nslots; + a->avail -= 2 * sizeof(jsval); + } else { + /* + * Need a new stack segment, so we must initialize unused slots in the + * current frame. See js_GC, just before marking the "operand" jsvals, + * where we scan from fp->spbase to fp->sp or through fp->script->depth + * (whichever covers fewer slots). + */ + fp = cx->fp; + if (fp && fp->script && fp->spbase) { +#ifdef DEBUG + jsuword depthdiff = fp->script->depth * sizeof(jsval); + JS_ASSERT(JS_UPTRDIFF(fp->sp, fp->spbase) <= depthdiff); + JS_ASSERT(JS_UPTRDIFF(*markp, fp->spbase) >= depthdiff); +#endif + end = fp->spbase + fp->script->depth; + for (vp = fp->sp; vp < end; vp++) + *vp = JSVAL_VOID; + } + + /* Allocate and push a stack segment header from the 2 extra slots. */ + sh = (JSStackHeader *)sp; + sh->nslots = nslots; + sh->down = cx->stackHeaders; + cx->stackHeaders = sh; + sp += 2; + } + + return sp; +} + +JS_FRIEND_API(void) +js_FreeStack(JSContext *cx, void *mark) +{ + JSStackHeader *sh; + jsuword slotdiff; + + /* Check for zero nslots allocation special case. */ + if (!mark) + return; + + /* We can assert because js_FreeStack always balances js_AllocStack. */ + sh = cx->stackHeaders; + JS_ASSERT(sh); + + /* If mark is in the current segment, reduce sh->nslots, else pop sh. */ + slotdiff = JS_UPTRDIFF(mark, JS_STACK_SEGMENT(sh)) / sizeof(jsval); + if (slotdiff < (jsuword)sh->nslots) + sh->nslots = slotdiff; + else + cx->stackHeaders = sh->down; + + /* Release the stackPool space allocated since mark was set. */ + JS_ARENA_RELEASE(&cx->stackPool, mark); +} + +/* + * To economize on slots space in functions, the compiler records arguments and + * local variables as shared (JSPROP_SHARED) properties with well-known getters + * and setters: js_{Get,Set}Argument, js_{Get,Set}LocalVariable. Now, we could + * record args and vars in lists or hash tables in function-private data, but + * that means more duplication in code, and more data at runtime in the hash + * table case due to round-up to powers of two, just to recapitulate the scope + * machinery in the function object. + * + * What's more, for a long time (to the dawn of "Mocha" in 1995), these getters + * and setters knew how to search active stack frames in a context to find the + * top activation of the function f, in order to satisfy a get or set of f.a, + * for argument a, or f.x, for local variable x. You could use f.a instead of + * just a in function f(a) { return f.a }, for example, to return the actual + * parameter. + * + * ECMA requires that we give up on this ancient extension, because it is not + * compatible with the standard as used by real-world scripts. While Chapter + * 16 does allow for additional properties to be defined on native objects by + * a conforming implementation, these magic getters and setters cause f.a's + * meaning to vary unexpectedly. Real-world scripts set f.A = 42 to define + * "class static" (after Java) constants, for example, but if A also names an + * arg or var in f, the constant is not available while f is active, and any + * non-constant class-static can't be set while f is active. + * + * So, to label arg and var properties in functions without giving them magic + * abilities to affect active frame stack slots, while keeping the properties + * shared (slot-less) to save space in the common case (where no assignment + * sets a function property with the same name as an arg or var), the setters + * for args and vars must handle two special cases here. + * + * XXX functions tend to have few args and vars, so we risk O(n^2) growth here + * XXX ECMA *really* wants args and vars to be stored in function-private data, + * not as function object properties. + */ +static JSBool +SetFunctionSlot(JSContext *cx, JSObject *obj, JSPropertyOp setter, jsid id, + jsval v) +{ + uintN slot; + JSObject *origobj; + JSScope *scope; + JSScopeProperty *sprop; + JSString *str; + JSBool ok; + + slot = (uintN) JSVAL_TO_INT(id); + if (OBJ_GET_CLASS(cx, obj) != &js_FunctionClass) { + /* + * Given a non-function object obj that has a function object in its + * prototype chain, where an argument or local variable property named + * by (setter, slot) is being set, override the shared property in the + * prototype with an unshared property in obj. This situation arises + * in real-world JS due to .prototype setting and collisions among a + * function's "static property" names and arg or var names, believe it + * or not. + */ + origobj = obj; + do { + obj = OBJ_GET_PROTO(cx, obj); + if (!obj) + return JS_TRUE; + } while (OBJ_GET_CLASS(cx, obj) != &js_FunctionClass); + + JS_LOCK_OBJ(cx, obj); + scope = OBJ_SCOPE(obj); + for (sprop = SCOPE_LAST_PROP(scope); sprop; sprop = sprop->parent) { + if (sprop->setter == setter) { + JS_ASSERT(!JSVAL_IS_INT(sprop->id) && + ATOM_IS_STRING((JSAtom *)sprop->id) && + (sprop->flags & SPROP_HAS_SHORTID)); + + if ((uintN) sprop->shortid == slot) { + str = ATOM_TO_STRING((JSAtom *)sprop->id); + JS_UNLOCK_SCOPE(cx, scope); + + return JS_DefineUCProperty(cx, origobj, + JSSTRING_CHARS(str), + JSSTRING_LENGTH(str), + v, NULL, NULL, + JSPROP_ENUMERATE); + } + } + } + JS_UNLOCK_SCOPE(cx, scope); + return JS_TRUE; + } + + /* + * Argument and local variable properties of function objects are shared + * by default (JSPROP_SHARED), therefore slot-less. But if for function + * f(a) {}, f.a = 42 is evaluated, f.a should be 42 after the assignment, + * whether or not f is active. So js_SetArgument and js_SetLocalVariable + * must be prepared to change an arg or var from shared to unshared status, + * allocating a slot in obj to hold v. + */ + ok = JS_TRUE; + JS_LOCK_OBJ(cx, obj); + scope = OBJ_SCOPE(obj); + for (sprop = SCOPE_LAST_PROP(scope); sprop; sprop = sprop->parent) { + if (sprop->setter == setter && (uintN) sprop->shortid == slot) { + if (sprop->attrs & JSPROP_SHARED) { + sprop = js_ChangeScopePropertyAttrs(cx, scope, sprop, + 0, ~JSPROP_SHARED, + sprop->getter, setter); + if (!sprop) { + ok = JS_FALSE; + } else { + /* See js_SetProperty, near the bottom. */ + GC_POKE(cx, pval); + LOCKED_OBJ_SET_SLOT(obj, sprop->slot, v); + } + } + break; + } + } + JS_UNLOCK_SCOPE(cx, scope); + return ok; +} + +JSBool +js_GetArgument(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + return JS_TRUE; +} + +JSBool +js_SetArgument(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + return SetFunctionSlot(cx, obj, js_SetArgument, id, *vp); +} + +JSBool +js_GetLocalVariable(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + return JS_TRUE; +} + +JSBool +js_SetLocalVariable(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + return SetFunctionSlot(cx, obj, js_SetLocalVariable, id, *vp); +} + +/* + * Compute the 'this' parameter and store it in frame as frame.thisp. + * Activation objects ("Call" objects not created with "new Call()", i.e., + * "Call" objects that have private data) may not be referred to by 'this', + * as dictated by ECMA. + * + * N.B.: fp->argv must be set, fp->argv[-1] the nominal 'this' paramter as + * a jsval, and fp->argv[-2] must be the callee object reference, usually a + * function object. Also, fp->flags must contain JSFRAME_CONSTRUCTING if we + * are preparing for a constructor call. + */ +static JSBool +ComputeThis(JSContext *cx, JSObject *thisp, JSStackFrame *fp) +{ + JSObject *parent; + + if (thisp && OBJ_GET_CLASS(cx, thisp) != &js_CallClass) { + /* Some objects (e.g., With) delegate 'this' to another object. */ + thisp = OBJ_THIS_OBJECT(cx, thisp); + if (!thisp) + return JS_FALSE; + + /* Default return value for a constructor is the new object. */ + if (fp->flags & JSFRAME_CONSTRUCTING) + fp->rval = OBJECT_TO_JSVAL(thisp); + } else { + /* + * ECMA requires "the global object", but in the presence of multiple + * top-level objects (windows, frames, or certain layers in the client + * object model), we prefer fun's parent. An example that causes this + * code to run: + * + * // in window w1 + * function f() { return this } + * function g() { return f } + * + * // in window w2 + * var h = w1.g() + * alert(h() == w1) + * + * The alert should display "true". + */ + JS_ASSERT(!(fp->flags & JSFRAME_CONSTRUCTING)); + if (JSVAL_IS_PRIMITIVE(fp->argv[-2]) || + !(parent = OBJ_GET_PARENT(cx, JSVAL_TO_OBJECT(fp->argv[-2])))) { + thisp = cx->globalObject; + } else { + /* walk up to find the top-level object */ + thisp = parent; + while ((parent = OBJ_GET_PARENT(cx, thisp)) != NULL) + thisp = parent; + } + } + fp->thisp = thisp; + fp->argv[-1] = OBJECT_TO_JSVAL(thisp); + return JS_TRUE; +} + +/* + * Find a function reference and its 'this' object implicit first parameter + * under argc arguments on cx's stack, and call the function. Push missing + * required arguments, allocate declared local variables, and pop everything + * when done. Then push the return value. + */ +JS_FRIEND_API(JSBool) +js_Invoke(JSContext *cx, uintN argc, uintN flags) +{ + void *mark; + JSStackFrame *fp, frame; + jsval *sp, *newsp, *limit; + jsval *vp, v; + JSObject *funobj, *parent, *thisp; + JSBool ok; + JSClass *clasp; + JSObjectOps *ops; + JSNative native; + JSFunction *fun; + JSScript *script; + uintN minargs, nvars; + intN nslots, nalloc, surplus; + JSInterpreterHook hook; + void *hookData; + + /* Mark the top of stack and load frequently-used registers. */ + mark = JS_ARENA_MARK(&cx->stackPool); + fp = cx->fp; + sp = fp->sp; + + /* + * Set vp to the callee value's stack slot (it's where rval goes). + * Once vp is set, control should flow through label out2: to return. + * Set frame.rval early so native class and object ops can throw and + * return false, causing a goto out2 with ok set to false. Also set + * frame.flags to flags so that ComputeThis can test bits in it. + */ + vp = sp - (2 + argc); + v = *vp; + frame.rval = JSVAL_VOID; + frame.flags = flags; + thisp = JSVAL_TO_OBJECT(vp[1]); + + /* + * A callee must be an object reference, unless its |this| parameter + * implements the __noSuchMethod__ method, in which case that method will + * be called like so: + * + * thisp.__noSuchMethod__(id, args) + * + * where id is the name of the method that this invocation attempted to + * call by name, and args is an Array containing this invocation's actual + * parameters. + */ + if (JSVAL_IS_PRIMITIVE(v)) { +#if JS_HAS_NO_SUCH_METHOD + jsbytecode *pc; + jsatomid atomIndex; + JSAtom *atom; + JSObject *argsobj; + JSArena *a; + + if (!fp->script || (flags & JSINVOKE_INTERNAL)) + goto bad; + + /* + * We must ComputeThis here to censor Call objects; performance hit, + * but at least it's idempotent. + * + * Normally, we call ComputeThis after all frame members have been + * set, and in particular, after any revision of the callee value at + * *vp due to clasp->convert (see below). This matters because + * ComputeThis may access *vp via fp->argv[-2], to follow the parent + * chain to a global object to use as the |this| parameter. + * + * Obviously, here in the JSVAL_IS_PRIMITIVE(v) case, there can't be + * any such defaulting of |this| to callee (v, *vp) ancestor. + */ + frame.argv = vp + 2; + ok = ComputeThis(cx, thisp, &frame); + if (!ok) + goto out2; + thisp = frame.thisp; + + ok = OBJ_GET_PROPERTY(cx, thisp, + (jsid)cx->runtime->atomState.noSuchMethodAtom, + &v); + if (!ok) + goto out2; + if (JSVAL_IS_PRIMITIVE(v)) + goto bad; + + pc = (jsbytecode *) vp[-(intN)fp->script->depth]; + switch ((JSOp) *pc) { + case JSOP_NAME: + case JSOP_GETPROP: + atomIndex = GET_ATOM_INDEX(pc); + atom = js_GetAtom(cx, &fp->script->atomMap, atomIndex); + argsobj = js_NewArrayObject(cx, argc, vp + 2); + if (!argsobj) { + ok = JS_FALSE; + goto out2; + } + + sp = vp + 4; + if (argc < 2) { + a = cx->stackPool.current; + if ((jsuword)sp > a->limit) { + /* + * Arguments must be contiguous, and must include argv[-1] + * and argv[-2], so allocate more stack, advance sp, and + * set newsp[1] to thisp (vp[1]). The other argv elements + * will be set below, using negative indexing from sp. + */ + newsp = js_AllocRawStack(cx, 4, NULL); + if (!newsp) { + ok = JS_FALSE; + goto out2; + } + newsp[1] = OBJECT_TO_JSVAL(thisp); + sp = newsp + 4; + } else if ((jsuword)sp > a->avail) { + /* + * Inline, optimized version of JS_ARENA_ALLOCATE to claim + * the small number of words not already allocated as part + * of the caller's operand stack. + */ + JS_ArenaCountAllocation(pool, (jsuword)sp - a->avail); + a->avail = (jsuword)sp; + } + } + + sp[-4] = v; + JS_ASSERT(sp[-3] == OBJECT_TO_JSVAL(thisp)); + sp[-2] = ATOM_KEY(atom); + sp[-1] = OBJECT_TO_JSVAL(argsobj); + fp->sp = sp; + argc = 2; + break; + + default: + goto bad; + } +#else + goto bad; +#endif + } + + funobj = JSVAL_TO_OBJECT(v); + parent = OBJ_GET_PARENT(cx, funobj); + clasp = OBJ_GET_CLASS(cx, funobj); + if (clasp != &js_FunctionClass) { + /* Function is inlined, all other classes use object ops. */ + ops = funobj->map->ops; + + /* + * XXX + * Try converting to function, for closure and API compatibility. + * We attempt the conversion under all circumstances for 1.2, but + * only if there is a call op defined otherwise. + */ + if (cx->version == JSVERSION_1_2 || + ((ops == &js_ObjectOps) ? clasp->call : ops->call)) { + ok = clasp->convert(cx, funobj, JSTYPE_FUNCTION, &v); + if (!ok) + goto out2; + + if (JSVAL_IS_FUNCTION(cx, v)) { + /* Make vp refer to funobj to keep it available as argv[-2]. */ + *vp = v; + funobj = JSVAL_TO_OBJECT(v); + parent = OBJ_GET_PARENT(cx, funobj); + goto have_fun; + } + } + fun = NULL; + script = NULL; + minargs = nvars = 0; + + /* Try a call or construct native object op. */ + native = (flags & JSINVOKE_CONSTRUCT) ? ops->construct : ops->call; + if (!native) + goto bad; + } else { +have_fun: + /* Get private data and set derived locals from it. */ + fun = (JSFunction *) JS_GetPrivate(cx, funobj); + native = fun->native; + script = fun->script; + minargs = fun->nargs + fun->extra; + nvars = fun->nvars; + + /* Handle bound method special case. */ + if (fun->flags & JSFUN_BOUND_METHOD) + thisp = parent; + } + + /* Initialize the rest of frame, except for sp (set by SAVE_SP later). */ + frame.varobj = NULL; + frame.callobj = frame.argsobj = NULL; + frame.script = script; + frame.fun = fun; + frame.argc = argc; + frame.argv = sp - argc; + frame.nvars = nvars; + frame.vars = sp; + frame.down = fp; + frame.annotation = NULL; + frame.scopeChain = NULL; /* set below for real, after cx->fp is set */ + frame.pc = NULL; + frame.spbase = NULL; + frame.sharpDepth = 0; + frame.sharpArray = NULL; + frame.dormantNext = NULL; + frame.objAtomMap = NULL; + + /* Compute the 'this' parameter and store it in frame as frame.thisp. */ + ok = ComputeThis(cx, thisp, &frame); + if (!ok) + goto out2; + + /* From here on, control must flow through label out: to return. */ + cx->fp = &frame; + + /* Init these now in case we goto out before first hook call. */ + hook = cx->runtime->callHook; + hookData = NULL; + + /* Check for missing arguments expected by the function. */ + nslots = (intN)((argc < minargs) ? minargs - argc : 0); + if (nslots) { + /* All arguments must be contiguous, so we may have to copy actuals. */ + nalloc = nslots; + limit = (jsval *) cx->stackPool.current->limit; + if (sp + nslots > limit) { + /* Hit end of arena: we have to copy argv[-2..(argc+nslots-1)]. */ + nalloc += 2 + argc; + } else { + /* Take advantage of surplus slots in the caller's frame depth. */ + surplus = (jsval *)mark - sp; + JS_ASSERT(surplus >= 0); + nalloc -= surplus; + } + + /* Check whether we have enough space in the caller's frame. */ + if (nalloc > 0) { + /* Need space for actuals plus missing formals minus surplus. */ + newsp = js_AllocRawStack(cx, (uintN)nalloc, NULL); + if (!newsp) { + ok = JS_FALSE; + goto out; + } + + /* If we couldn't allocate contiguous args, copy actuals now. */ + if (newsp != mark) { + JS_ASSERT(sp + nslots > limit); + JS_ASSERT(2 + argc + nslots == (uintN)nalloc); + *newsp++ = vp[0]; + *newsp++ = vp[1]; + if (argc) + memcpy(newsp, frame.argv, argc * sizeof(jsval)); + frame.argv = newsp; + sp = frame.vars = newsp + argc; + } + } + + /* Advance frame.vars to make room for the missing args. */ + frame.vars += nslots; + + /* Push void to initialize missing args. */ + while (--nslots >= 0) + PUSH(JSVAL_VOID); + } + + /* Now allocate stack space for local variables. */ + nslots = (intN)frame.nvars; + if (nslots) { + surplus = (intN)((jsval *)cx->stackPool.current->avail - frame.vars); + if (surplus < nslots) { + newsp = js_AllocRawStack(cx, (uintN)nslots, NULL); + if (!newsp) { + ok = JS_FALSE; + goto out; + } + if (newsp != sp) { + /* NB: Discontinuity between argv and vars. */ + sp = frame.vars = newsp; + } + } + + /* Push void to initialize local variables. */ + while (--nslots >= 0) + PUSH(JSVAL_VOID); + } + + /* Store the current sp in frame before calling fun. */ + SAVE_SP(&frame); + + /* call the hook if present */ + if (hook && (native || script)) + hookData = hook(cx, &frame, JS_TRUE, 0, cx->runtime->callHookData); + + /* Call the function, either a native method or an interpreted script. */ + if (native) { +#if JS_HAS_LVALUE_RETURN + /* Set by JS_SetCallReturnValue2, used to return reference types. */ + cx->rval2set = JS_FALSE; +#endif + + /* If native, use caller varobj and scopeChain for eval. */ + frame.varobj = fp->varobj; + frame.scopeChain = fp->scopeChain; + ok = native(cx, frame.thisp, argc, frame.argv, &frame.rval); + JS_RUNTIME_METER(cx->runtime, nativeCalls); + } else if (script) { + /* Use parent scope so js_GetCallObject can find the right "Call". */ + frame.scopeChain = parent; + if (fun->flags & JSFUN_HEAVYWEIGHT) { +#if JS_HAS_CALL_OBJECT + /* Scope with a call object parented by the callee's parent. */ + if (!js_GetCallObject(cx, &frame, parent)) { + ok = JS_FALSE; + goto out; + } +#else + /* Bad old code used the function as a proxy for all calls to it. */ + frame.scopeChain = funobj; +#endif + } + ok = js_Interpret(cx, &v); + } else { + /* fun might be onerror trying to report a syntax error in itself. */ + frame.scopeChain = NULL; + ok = JS_TRUE; + } + +out: + if (hookData) { + hook = cx->runtime->callHook; + if (hook) + hook(cx, &frame, JS_FALSE, &ok, hookData); + } +#if JS_HAS_CALL_OBJECT + /* If frame has a call object, sync values and clear back-pointer. */ + if (frame.callobj) + ok &= js_PutCallObject(cx, &frame); +#endif +#if JS_HAS_ARGS_OBJECT + /* If frame has an arguments object, sync values and clear back-pointer. */ + if (frame.argsobj) + ok &= js_PutArgsObject(cx, &frame); +#endif + + /* Restore cx->fp now that we're done releasing frame objects. */ + cx->fp = fp; + +out2: + /* Pop everything we may have allocated off the stack. */ + JS_ARENA_RELEASE(&cx->stackPool, mark); + + /* Store the return value and restore sp just above it. */ + *vp = frame.rval; + fp->sp = vp + 1; + + /* + * Store the location of the JSOP_CALL or JSOP_EVAL that generated the + * return value, but only if this is an external (compiled from script + * source) call that has stack budget for the generating pc. + */ + if (fp->script && !(flags & JSINVOKE_INTERNAL)) + vp[-(intN)fp->script->depth] = (jsval)fp->pc; + return ok; + +bad: + js_ReportIsNotFunction(cx, vp, flags & JSINVOKE_CONSTRUCT); + ok = JS_FALSE; + goto out2; +} + +JSBool +js_InternalInvoke(JSContext *cx, JSObject *obj, jsval fval, uintN flags, + uintN argc, jsval *argv, jsval *rval) +{ + JSStackFrame *fp, *oldfp, frame; + jsval *oldsp, *sp; + void *mark; + uintN i; + JSBool ok; + + fp = oldfp = cx->fp; + if (!fp) { + memset(&frame, 0, sizeof frame); + cx->fp = fp = &frame; + } + oldsp = fp->sp; + sp = js_AllocStack(cx, 2 + argc, &mark); + if (!sp) { + ok = JS_FALSE; + goto out; + } + + PUSH(fval); + PUSH(OBJECT_TO_JSVAL(obj)); + for (i = 0; i < argc; i++) + PUSH(argv[i]); + fp->sp = sp; + ok = js_Invoke(cx, argc, flags | JSINVOKE_INTERNAL); + if (ok) { + RESTORE_SP(fp); + *rval = POP_OPND(); + } + + js_FreeStack(cx, mark); +out: + fp->sp = oldsp; + if (oldfp != fp) + cx->fp = oldfp; + + return ok; +} + +JSBool +js_InternalGetOrSet(JSContext *cx, JSObject *obj, jsid id, jsval fval, + JSAccessMode mode, uintN argc, jsval *argv, jsval *rval) +{ + /* + * Check general (not object-ops/class-specific) access from the running + * script to obj.id only if id has a scripted getter or setter that we're + * about to invoke. If we don't check this case, nothing else will -- no + * other native code has the chance to check. + * + * Contrast this non-native (scripted) case with native getter and setter + * accesses, where the native itself must do an access check, if security + * policies requires it. We make a checkAccess or checkObjectAccess call + * back to the embedding program only in those cases where we're not going + * to call an embedding-defined native function, getter, setter, or class + * hook anyway. Where we do call such a native, there's no need for the + * engine to impose a separate access check callback on all embeddings -- + * many embeddings have no security policy at all. + */ + JS_ASSERT(mode == JSACC_READ || mode == JSACC_WRITE); + if (cx->runtime->checkObjectAccess && + JSVAL_IS_FUNCTION(cx, fval) && + ((JSFunction *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(fval)))->script && + !cx->runtime->checkObjectAccess(cx, obj, ID_TO_VALUE(id), mode, + &fval)) { + return JS_FALSE; + } + + return js_InternalCall(cx, obj, fval, argc, argv, rval); +} + +JSBool +js_Execute(JSContext *cx, JSObject *chain, JSScript *script, + JSStackFrame *down, uintN special, jsval *result) +{ + JSStackFrame *oldfp, frame; + JSObject *obj, *tmp; + JSBool ok; + JSInterpreterHook hook; + void *hookData; + + hook = cx->runtime->executeHook; + hookData = NULL; + oldfp = cx->fp; + frame.callobj = frame.argsobj = NULL; + frame.script = script; + if (down) { + /* Propagate arg/var state for eval and the debugger API. */ + frame.varobj = down->varobj; + frame.fun = down->fun; + frame.thisp = down->thisp; + frame.argc = down->argc; + frame.argv = down->argv; + frame.nvars = down->nvars; + frame.vars = down->vars; + frame.annotation = down->annotation; + frame.sharpArray = down->sharpArray; + } else { + obj = chain; + if (cx->options & JSOPTION_VAROBJFIX) { + while ((tmp = OBJ_GET_PARENT(cx, obj)) != NULL) + obj = tmp; + } + frame.varobj = obj; + frame.fun = NULL; + frame.thisp = chain; + frame.argc = frame.nvars = 0; + frame.argv = frame.vars = NULL; + frame.annotation = NULL; + frame.sharpArray = NULL; + } + frame.rval = JSVAL_VOID; + frame.down = down; + frame.scopeChain = chain; + frame.pc = NULL; + frame.sp = oldfp ? oldfp->sp : NULL; + frame.spbase = NULL; + frame.sharpDepth = 0; + frame.flags = special; + frame.dormantNext = NULL; + frame.objAtomMap = NULL; + + /* + * Here we wrap the call to js_Interpret with code to (conditionally) + * save and restore the old stack frame chain into a chain of 'dormant' + * frame chains. Since we are replacing cx->fp, we were running into + * the problem that if GC was called under this frame, some of the GC + * things associated with the old frame chain (available here only in + * the C variable 'oldfp') were not rooted and were being collected. + * + * So, now we preserve the links to these 'dormant' frame chains in cx + * before calling js_Interpret and cleanup afterwards. The GC walks + * these dormant chains and marks objects in the same way that it marks + * objects in the primary cx->fp chain. + */ + if (oldfp && oldfp != down) { + JS_ASSERT(!oldfp->dormantNext); + oldfp->dormantNext = cx->dormantFrameChain; + cx->dormantFrameChain = oldfp; + } + + cx->fp = &frame; + if (hook) + hookData = hook(cx, &frame, JS_TRUE, 0, cx->runtime->executeHookData); + + ok = js_Interpret(cx, result); + + if (hookData) { + hook = cx->runtime->executeHook; + if (hook) + hook(cx, &frame, JS_FALSE, &ok, hookData); + } + cx->fp = oldfp; + + if (oldfp && oldfp != down) { + JS_ASSERT(cx->dormantFrameChain == oldfp); + cx->dormantFrameChain = oldfp->dormantNext; + oldfp->dormantNext = NULL; + } + + return ok; +} + +#if JS_HAS_EXPORT_IMPORT +/* + * If id is JSVAL_VOID, import all exported properties from obj. + */ +static JSBool +ImportProperty(JSContext *cx, JSObject *obj, jsid id) +{ + JSBool ok; + JSIdArray *ida; + JSProperty *prop; + JSObject *obj2, *target, *funobj, *closure; + JSString *str; + uintN attrs; + jsint i; + jsval value; + + if (JSVAL_IS_VOID(id)) { + ida = JS_Enumerate(cx, obj); + if (!ida) + return JS_FALSE; + ok = JS_TRUE; + if (ida->length == 0) + goto out; + } else { + ida = NULL; + if (!OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop)) + return JS_FALSE; + if (!prop) { + str = js_DecompileValueGenerator(cx, JSDVG_IGNORE_STACK, + ID_TO_VALUE(id), NULL); + if (str) + js_ReportIsNotDefined(cx, JS_GetStringBytes(str)); + return JS_FALSE; + } + ok = OBJ_GET_ATTRIBUTES(cx, obj, id, prop, &attrs); + OBJ_DROP_PROPERTY(cx, obj2, prop); + if (!ok) + return JS_FALSE; + if (!(attrs & JSPROP_EXPORTED)) { + str = js_DecompileValueGenerator(cx, JSDVG_IGNORE_STACK, + ID_TO_VALUE(id), NULL); + if (str) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_NOT_EXPORTED, + JS_GetStringBytes(str)); + } + return JS_FALSE; + } + } + + target = cx->fp->varobj; + i = 0; + do { + if (ida) { + id = ida->vector[i]; + ok = OBJ_GET_ATTRIBUTES(cx, obj, id, NULL, &attrs); + if (!ok) + goto out; + if (!(attrs & JSPROP_EXPORTED)) + continue; + } + ok = OBJ_CHECK_ACCESS(cx, obj, id, JSACC_IMPORT, &value, &attrs); + if (!ok) + goto out; + if (JSVAL_IS_FUNCTION(cx, value)) { + funobj = JSVAL_TO_OBJECT(value); + closure = js_CloneFunctionObject(cx, funobj, obj); + if (!closure) { + ok = JS_FALSE; + goto out; + } + value = OBJECT_TO_JSVAL(closure); + } + + /* + * Handle the case of importing a property that refers to a local + * variable or formal parameter of a function activation. These + * properties are accessed by opcodes using stack slot numbers + * generated by the compiler rather than runtime name-lookup. These + * local references, therefore, bypass the normal scope chain lookup. + * So, instead of defining a new property in the activation object, + * modify the existing value in the stack slot. + */ + if (OBJ_GET_CLASS(cx, target) == &js_CallClass) { + ok = OBJ_LOOKUP_PROPERTY(cx, target, id, &obj2, &prop); + if (!ok) + goto out; + } else { + prop = NULL; + } + if (prop && target == obj2) { + ok = OBJ_SET_PROPERTY(cx, target, id, &value); + } else { + ok = OBJ_DEFINE_PROPERTY(cx, target, id, value, NULL, NULL, + attrs & ~JSPROP_EXPORTED, + NULL); + } + if (prop) + OBJ_DROP_PROPERTY(cx, obj2, prop); + if (!ok) + goto out; + } while (ida && ++i < ida->length); + +out: + if (ida) + JS_DestroyIdArray(cx, ida); + return ok; +} +#endif /* JS_HAS_EXPORT_IMPORT */ + +JSBool +js_CheckRedeclaration(JSContext *cx, JSObject *obj, jsid id, uintN attrs, + JSBool *foundp) +{ + JSObject *obj2; + JSProperty *prop; + JSBool ok; + uintN oldAttrs, report; + JSBool isFunction; + jsval value; + const char *type, *name; + + if (!OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop)) + return JS_FALSE; + *foundp = (prop != NULL); + if (!prop) + return JS_TRUE; + ok = OBJ_GET_ATTRIBUTES(cx, obj2, id, prop, &oldAttrs); + OBJ_DROP_PROPERTY(cx, obj2, prop); + if (!ok) + return JS_FALSE; + + /* If either property is readonly, we have an error. */ + report = ((oldAttrs | attrs) & JSPROP_READONLY) + ? JSREPORT_ERROR + : JSREPORT_WARNING | JSREPORT_STRICT; + + if (report != JSREPORT_ERROR) { + /* + * Allow redeclaration of variables and functions, but insist that the + * new value is not a getter if the old value was, ditto for setters -- + * unless prop is impermanent (in which case anyone could delete it and + * redefine it, willy-nilly). + */ + if (!(attrs & (JSPROP_GETTER | JSPROP_SETTER))) + return JS_TRUE; + if ((~(oldAttrs ^ attrs) & (JSPROP_GETTER | JSPROP_SETTER)) == 0) + return JS_TRUE; + if (!(oldAttrs & JSPROP_PERMANENT)) + return JS_TRUE; + report = JSREPORT_ERROR; + } + + isFunction = (oldAttrs & (JSPROP_GETTER | JSPROP_SETTER)) != 0; + if (!isFunction) { + if (!OBJ_GET_PROPERTY(cx, obj, id, &value)) + return JS_FALSE; + isFunction = JSVAL_IS_FUNCTION(cx, value); + } + type = (oldAttrs & attrs & JSPROP_GETTER) + ? js_getter_str + : (oldAttrs & attrs & JSPROP_SETTER) + ? js_setter_str + : (oldAttrs & JSPROP_READONLY) + ? js_const_str + : isFunction + ? js_function_str + : js_var_str; + name = js_AtomToPrintableString(cx, (JSAtom *)id); + if (!name) + return JS_FALSE; + return JS_ReportErrorFlagsAndNumber(cx, report, + js_GetErrorMessage, NULL, + JSMSG_REDECLARED_VAR, + type, name); +} + +#ifndef MAX_INTERP_LEVEL +#if defined(XP_OS2) +#define MAX_INTERP_LEVEL 250 +#elif defined _MSC_VER && _MSC_VER <= 800 +#define MAX_INTERP_LEVEL 30 +#else +#define MAX_INTERP_LEVEL 1000 +#endif +#endif + +#define MAX_INLINE_CALL_COUNT 1000 + +JSBool +js_Interpret(JSContext *cx, jsval *result) +{ + JSRuntime *rt; + JSStackFrame *fp; + JSScript *script; + uintN inlineCallCount; + JSObject *obj, *obj2, *proto, *parent; + JSVersion currentVersion, originalVersion; + JSBranchCallback onbranch; + JSBool ok, cond; + JSTrapHandler interruptHandler; + jsint depth, len; + jsval *sp, *newsp; + void *mark; + jsbytecode *pc, *pc2, *endpc; + JSOp op, op2; + const JSCodeSpec *cs; + JSAtom *atom; + uintN argc, slot, attrs; + jsval *vp, lval, rval, ltmp, rtmp; + jsid id; + JSObject *withobj, *origobj, *propobj; + jsval iter_state; + JSProperty *prop; + JSScopeProperty *sprop; + JSString *str, *str2; + jsint i, j; + jsdouble d, d2; + JSClass *clasp, *funclasp; + JSFunction *fun; + JSType type; +#ifdef DEBUG + FILE *tracefp; +#endif +#if JS_HAS_EXPORT_IMPORT + JSIdArray *ida; +#endif +#if JS_HAS_SWITCH_STATEMENT + jsint low, high, off, npairs; + JSBool match; +#endif +#if JS_HAS_GETTER_SETTER + JSPropertyOp getter, setter; +#endif + int stackDummy; + + *result = JSVAL_VOID; + rt = cx->runtime; + + /* Set registerized frame pointer and derived script pointer. */ + fp = cx->fp; + script = fp->script; + + /* Count of JS function calls that nest in this C js_Interpret frame. */ + inlineCallCount = 0; + + /* + * Optimized Get and SetVersion for proper script language versioning. + * + * If any native method or JSClass/JSObjectOps hook calls JS_SetVersion + * and changes cx->version, the effect will "stick" and we will stop + * maintaining currentVersion. This is relied upon by testsuites, for + * the most part -- web browsers select version before compiling and not + * at run-time. + */ + currentVersion = script->version; + originalVersion = cx->version; + if (currentVersion != originalVersion) + JS_SetVersion(cx, currentVersion); + + /* + * Prepare to call a user-supplied branch handler, and abort the script + * if it returns false. + */ + onbranch = cx->branchCallback; + ok = JS_TRUE; +#define CHECK_BRANCH(len) \ + JS_BEGIN_MACRO \ + if (len <= 0 && onbranch) { \ + SAVE_SP(fp); \ + if (!(ok = (*onbranch)(cx, script))) \ + goto out; \ + } \ + JS_END_MACRO + + /* + * Load the debugger's interrupt hook here and after calling out to native + * functions (but not to getters, setters, or other native hooks), so we do + * not have to reload it each time through the interpreter loop -- we hope + * the compiler can keep it in a register. + * XXX if it spills, we still lose + */ +#define LOAD_INTERRUPT_HANDLER(rt) (interruptHandler = (rt)->interruptHandler) + + LOAD_INTERRUPT_HANDLER(rt); + + pc = script->code; + endpc = pc + script->length; + depth = (jsint) script->depth; + len = -1; + + /* Check for too much js_Interpret nesting, or too deep a C stack. */ + if (++cx->interpLevel == MAX_INTERP_LEVEL || + !JS_CHECK_STACK_SIZE(cx, stackDummy)) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_OVER_RECURSED); + ok = JS_FALSE; + goto out; + } + + /* + * Allocate operand and pc stack slots for the script's worst-case depth. + */ + newsp = js_AllocRawStack(cx, (uintN)(2 * depth), &mark); + if (!newsp) { + ok = JS_FALSE; + goto out; + } + sp = newsp + depth; + fp->spbase = sp; + SAVE_SP(fp); + + while (pc < endpc) { + fp->pc = pc; + op = (JSOp) *pc; + do_op: + cs = &js_CodeSpec[op]; + len = cs->length; + +#ifdef DEBUG + tracefp = (FILE *) cx->tracefp; + if (tracefp) { + intN nuses, n; + + fprintf(tracefp, "%4u: ", js_PCToLineNumber(cx, script, pc)); + js_Disassemble1(cx, script, pc, + PTRDIFF(pc, script->code, jsbytecode), JS_FALSE, + tracefp); + nuses = cs->nuses; + if (nuses) { + SAVE_SP(fp); + for (n = -nuses; n < 0; n++) { + str = js_DecompileValueGenerator(cx, n, sp[n], NULL); + if (str != NULL) { + fprintf(tracefp, "%s %s", + (n == -nuses) ? " inputs:" : ",", + JS_GetStringBytes(str)); + } + } + fprintf(tracefp, " @ %d\n", sp - fp->spbase); + } + } +#endif + + if (interruptHandler) { + SAVE_SP(fp); + switch (interruptHandler(cx, script, pc, &rval, + rt->interruptHandlerData)) { + case JSTRAP_ERROR: + ok = JS_FALSE; + goto out; + case JSTRAP_CONTINUE: + break; + case JSTRAP_RETURN: + fp->rval = rval; + goto out; +#if JS_HAS_EXCEPTIONS + case JSTRAP_THROW: + cx->throwing = JS_TRUE; + cx->exception = rval; + ok = JS_FALSE; + goto out; +#endif /* JS_HAS_EXCEPTIONS */ + default:; + } + LOAD_INTERRUPT_HANDLER(rt); + } + + switch (op) { + case JSOP_NOP: + break; + + case JSOP_GROUP: + obj = NULL; + break; + + case JSOP_PUSH: + PUSH_OPND(JSVAL_VOID); + break; + + case JSOP_POP: + sp--; + break; + + case JSOP_POP2: + sp -= 2; + break; + + case JSOP_SWAP: + /* + * N.B. JSOP_SWAP doesn't swap the corresponding generating pcs + * for the operands it swaps. + */ + ltmp = sp[-1]; + sp[-1] = sp[-2]; + sp[-2] = ltmp; + break; + + case JSOP_POPV: + *result = POP_OPND(); + break; + + case JSOP_ENTERWITH: + rval = FETCH_OPND(-1); + VALUE_TO_OBJECT(cx, rval, obj); + withobj = js_NewObject(cx, &js_WithClass, obj, fp->scopeChain); + if (!withobj) + goto out; + fp->scopeChain = withobj; + STORE_OPND(-1, OBJECT_TO_JSVAL(withobj)); + break; + + case JSOP_LEAVEWITH: + rval = POP_OPND(); + JS_ASSERT(JSVAL_IS_OBJECT(rval)); + withobj = JSVAL_TO_OBJECT(rval); + JS_ASSERT(OBJ_GET_CLASS(cx, withobj) == &js_WithClass); + + rval = OBJ_GET_SLOT(cx, withobj, JSSLOT_PARENT); + JS_ASSERT(JSVAL_IS_OBJECT(rval)); + fp->scopeChain = JSVAL_TO_OBJECT(rval); + break; + + case JSOP_SETRVAL: + fp->rval = POP_OPND(); + break; + + case JSOP_RETURN: + CHECK_BRANCH(-1); + fp->rval = POP_OPND(); + /* FALL THROUGH */ + + case JSOP_RETRVAL: /* fp->rval already set */ + if (inlineCallCount) + inline_return: + { + JSInlineFrame *ifp = (JSInlineFrame *) fp; + void *hookData = ifp->hookData; + + if (hookData) { + JSInterpreterHook hook = cx->runtime->callHook; + if (hook) { + hook(cx, fp, JS_FALSE, &ok, hookData); + LOAD_INTERRUPT_HANDLER(rt); + } + } +#if JS_HAS_ARGS_OBJECT + if (fp->argsobj) + ok &= js_PutArgsObject(cx, fp); +#endif + + /* Restore context version only if callee hasn't set version. */ + if (cx->version == currentVersion) { + currentVersion = ifp->callerVersion; + if (currentVersion != cx->version) + JS_SetVersion(cx, currentVersion); + } + + /* Store the return value in the caller's operand frame. */ + vp = fp->argv - 2; + *vp = fp->rval; + + /* Restore cx->fp and release the inline frame's space. */ + cx->fp = fp = fp->down; + JS_ARENA_RELEASE(&cx->stackPool, ifp->mark); + + /* Restore sp to point just above the return value. */ + fp->sp = vp + 1; + RESTORE_SP(fp); + + /* Restore the calling script's interpreter registers. */ + script = fp->script; + depth = (jsint) script->depth; + pc = fp->pc; + endpc = script->code + script->length; + + /* Store the generating pc for the return value. */ + vp[-depth] = (jsval)pc; + + /* Set remaining variables for 'goto advance_pc'. */ + op = (JSOp) *pc; + cs = &js_CodeSpec[op]; + len = cs->length; + + /* Resume execution in the calling frame. */ + inlineCallCount--; + if (ok) + goto advance_pc; + } + goto out; + +#if JS_HAS_SWITCH_STATEMENT + case JSOP_DEFAULT: + (void) POP(); + /* FALL THROUGH */ +#endif + case JSOP_GOTO: + len = GET_JUMP_OFFSET(pc); + CHECK_BRANCH(len); + break; + + case JSOP_IFEQ: + POP_BOOLEAN(cx, rval, cond); + if (cond == JS_FALSE) { + len = GET_JUMP_OFFSET(pc); + CHECK_BRANCH(len); + } + break; + + case JSOP_IFNE: + POP_BOOLEAN(cx, rval, cond); + if (cond != JS_FALSE) { + len = GET_JUMP_OFFSET(pc); + CHECK_BRANCH(len); + } + break; + + case JSOP_OR: + POP_BOOLEAN(cx, rval, cond); + if (cond == JS_TRUE) { + len = GET_JUMP_OFFSET(pc); + PUSH_OPND(rval); + } + break; + + case JSOP_AND: + POP_BOOLEAN(cx, rval, cond); + if (cond == JS_FALSE) { + len = GET_JUMP_OFFSET(pc); + PUSH_OPND(rval); + } + break; + + +#if JS_HAS_SWITCH_STATEMENT + case JSOP_DEFAULTX: + (void) POP(); + /* FALL THROUGH */ +#endif + case JSOP_GOTOX: + len = GET_JUMPX_OFFSET(pc); + CHECK_BRANCH(len); + break; + + case JSOP_IFEQX: + POP_BOOLEAN(cx, rval, cond); + if (cond == JS_FALSE) { + len = GET_JUMPX_OFFSET(pc); + CHECK_BRANCH(len); + } + break; + + case JSOP_IFNEX: + POP_BOOLEAN(cx, rval, cond); + if (cond != JS_FALSE) { + len = GET_JUMPX_OFFSET(pc); + CHECK_BRANCH(len); + } + break; + + case JSOP_ORX: + POP_BOOLEAN(cx, rval, cond); + if (cond == JS_TRUE) { + len = GET_JUMPX_OFFSET(pc); + PUSH_OPND(rval); + } + break; + + case JSOP_ANDX: + POP_BOOLEAN(cx, rval, cond); + if (cond == JS_FALSE) { + len = GET_JUMPX_OFFSET(pc); + PUSH_OPND(rval); + } + break; + + case JSOP_TOOBJECT: + SAVE_SP(fp); + ok = js_ValueToObject(cx, FETCH_OPND(-1), &obj); + if (!ok) + goto out; + STORE_OPND(-1, OBJECT_TO_JSVAL(obj)); + break; + +#define FETCH_ELEMENT_ID(n, id) \ + JS_BEGIN_MACRO \ + /* If the index is not a jsint, atomize it. */ \ + id = (jsid) FETCH_OPND(n); \ + if (JSVAL_IS_INT(id)) { \ + atom = NULL; \ + } else { \ + SAVE_SP(fp); \ + atom = js_ValueToStringAtom(cx, (jsval)id); \ + if (!atom) { \ + ok = JS_FALSE; \ + goto out; \ + } \ + id = (jsid)atom; \ + } \ + JS_END_MACRO + +#define POP_ELEMENT_ID(id) \ + JS_BEGIN_MACRO \ + FETCH_ELEMENT_ID(-1, id); \ + sp--; \ + JS_END_MACRO + +#if JS_HAS_IN_OPERATOR + case JSOP_IN: + rval = FETCH_OPND(-1); + if (JSVAL_IS_PRIMITIVE(rval)) { + str = js_DecompileValueGenerator(cx, -1, rval, NULL); + if (str) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_IN_NOT_OBJECT, + JS_GetStringBytes(str)); + } + ok = JS_FALSE; + goto out; + } + sp--; + obj = JSVAL_TO_OBJECT(rval); + FETCH_ELEMENT_ID(-1, id); + ok = OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop); + if (!ok) + goto out; + STORE_OPND(-1, BOOLEAN_TO_JSVAL(prop != NULL)); + if (prop) + OBJ_DROP_PROPERTY(cx, obj2, prop); + break; +#endif /* JS_HAS_IN_OPERATOR */ + + case JSOP_FORPROP: + /* + * Handle JSOP_FORPROP first, so the cost of the goto do_forinloop + * is not paid for the more common cases. + */ + lval = FETCH_OPND(-1); + atom = GET_ATOM(cx, script, pc); + id = (jsid)atom; + i = -2; + goto do_forinloop; + + case JSOP_FORNAME: + atom = GET_ATOM(cx, script, pc); + id = (jsid)atom; + + /* + * ECMA 12.6.3 says to eval the LHS after looking for properties + * to enumerate, and bail without LHS eval if there are no props. + * We do Find here to share the most code at label do_forinloop. + * If looking for enumerable properties could have side effects, + * then we'd have to move this into the common code and condition + * it on op == JSOP_FORNAME. + */ + SAVE_SP(fp); + ok = js_FindProperty(cx, id, &obj, &obj2, &prop); + if (!ok) + goto out; + if (prop) + OBJ_DROP_PROPERTY(cx, obj2, prop); + lval = OBJECT_TO_JSVAL(obj); + /* FALL THROUGH */ + + case JSOP_FORARG: + case JSOP_FORVAR: + /* + * JSOP_FORARG and JSOP_FORVAR don't require any lval computation + * here, because they address slots on the stack (in fp->args and + * fp->vars, respectively). + */ + /* FALL THROUGH */ + + case JSOP_FORELEM: + /* + * JSOP_FORELEM simply initializes or updates the iteration state + * and leaves the index expression evaluation and assignment to the + * enumerator until after the next property has been acquired, via + * a JSOP_ENUMELEM bytecode. + */ + i = -1; + + do_forinloop: + /* + * ECMA-compatible for/in evals the object just once, before loop. + * Bad old bytecodes (since removed) did it on every iteration. + */ + obj = JSVAL_TO_OBJECT(sp[i]); + + /* If the thing to the right of 'in' has no properties, break. */ + if (!obj) { + rval = JSVAL_FALSE; + goto end_forinloop; + } + + /* + * Save the thing to the right of 'in' as origobj. Later on, we + * use this variable to suppress enumeration of shadowed prototype + * properties. + */ + origobj = obj; + + /* + * Reach under the top of stack to find our property iterator, a + * JSObject that contains the iteration state. (An object is used + * rather than a native struct so that the iteration state is + * cleaned up via GC if the for-in loop terminates abruptly.) + */ + vp = &sp[i - 1]; + rval = *vp; + + /* Is this the first iteration ? */ + if (JSVAL_IS_VOID(rval)) { + /* Yes, create a new JSObject to hold the iterator state */ + propobj = js_NewObject(cx, &prop_iterator_class, NULL, obj); + if (!propobj) { + ok = JS_FALSE; + goto out; + } + propobj->slots[JSSLOT_ITER_STATE] = JSVAL_NULL; + + /* + * Root the parent slot so we can get it even in our finalizer + * (otherwise, it would live as long as we do, but it might be + * finalized first). + */ + ok = js_AddRoot(cx, &propobj->slots[JSSLOT_PARENT], + "propobj->parent"); + if (!ok) + goto out; + + /* + * Rewrite the iterator so we know to do the next case. + * Do this before calling the enumerator, which could + * displace cx->newborn and cause GC. + */ + *vp = OBJECT_TO_JSVAL(propobj); + + ok = OBJ_ENUMERATE(cx, obj, JSENUMERATE_INIT, &iter_state, 0); + + /* + * Stash private iteration state into property iterator object. + * We do this before checking 'ok' to ensure that propobj is + * in a valid state even if OBJ_ENUMERATE returned JS_FALSE. + * NB: This code knows that the first slots are pre-allocated. + */ +#if JS_INITIAL_NSLOTS < 5 +#error JS_INITIAL_NSLOTS must be greater than or equal to 5. +#endif + propobj->slots[JSSLOT_ITER_STATE] = iter_state; + if (!ok) + goto out; + } else { + /* This is not the first iteration. Recover iterator state. */ + propobj = JSVAL_TO_OBJECT(rval); + obj = JSVAL_TO_OBJECT(propobj->slots[JSSLOT_PARENT]); + iter_state = propobj->slots[JSSLOT_ITER_STATE]; + } + + enum_next_property: + /* Get the next jsid to be enumerated and store it in rval. */ + OBJ_ENUMERATE(cx, obj, JSENUMERATE_NEXT, &iter_state, &rval); + propobj->slots[JSSLOT_ITER_STATE] = iter_state; + + /* No more jsids to iterate in obj? */ + if (iter_state == JSVAL_NULL) { + /* Enumerate the properties on obj's prototype chain. */ + obj = OBJ_GET_PROTO(cx, obj); + if (!obj) { + /* End of property list -- terminate loop. */ + rval = JSVAL_FALSE; + goto end_forinloop; + } + + ok = OBJ_ENUMERATE(cx, obj, JSENUMERATE_INIT, &iter_state, 0); + + /* + * Stash private iteration state into property iterator object. + * We do this before checking 'ok' to ensure that propobj is + * in a valid state even if OBJ_ENUMERATE returned JS_FALSE. + * NB: This code knows that the first slots are pre-allocated. + */ + propobj->slots[JSSLOT_ITER_STATE] = iter_state; + if (!ok) + goto out; + + /* + * Update the iterator JSObject's parent link to refer to the + * current object. This is used in the iterator JSObject's + * finalizer. + */ + propobj->slots[JSSLOT_PARENT] = OBJECT_TO_JSVAL(obj); + goto enum_next_property; + } + + /* Skip properties not owned by obj, and leave next id in rval. */ + ok = OBJ_LOOKUP_PROPERTY(cx, origobj, rval, &obj2, &prop); + if (!ok) + goto out; + if (prop) { + OBJ_DROP_PROPERTY(cx, obj2, prop); + + /* Yes, don't enumerate again. Go to the next property. */ + if (obj2 != obj) + goto enum_next_property; + } + + /* Make sure rval is a string for uniformity and compatibility. */ + if (!JSVAL_IS_INT(rval)) { + rval = ATOM_KEY((JSAtom *)rval); + } else if (cx->version != JSVERSION_1_2) { + str = js_NumberToString(cx, (jsdouble) JSVAL_TO_INT(rval)); + if (!str) { + ok = JS_FALSE; + goto out; + } + + rval = STRING_TO_JSVAL(str); + } + + switch (op) { + case JSOP_FORARG: + slot = GET_ARGNO(pc); + JS_ASSERT(slot < fp->fun->nargs); + fp->argv[slot] = rval; + break; + + case JSOP_FORVAR: + slot = GET_VARNO(pc); + JS_ASSERT(slot < fp->fun->nvars); + fp->vars[slot] = rval; + break; + + case JSOP_FORELEM: + /* FORELEM is not a SET operation, it's more like BINDNAME. */ + PUSH_OPND(rval); + break; + + default: + /* Convert lval to a non-null object containing id. */ + VALUE_TO_OBJECT(cx, lval, obj); + + /* Set the variable obj[id] to refer to rval. */ + fp->flags |= JSFRAME_ASSIGNING; + ok = OBJ_SET_PROPERTY(cx, obj, id, &rval); + fp->flags &= ~JSFRAME_ASSIGNING; + if (!ok) + goto out; + break; + } + + /* Push true to keep looping through properties. */ + rval = JSVAL_TRUE; + + end_forinloop: + sp += i + 1; + PUSH_OPND(rval); + break; + + case JSOP_DUP: + JS_ASSERT(sp > fp->spbase); + rval = sp[-1]; + PUSH_OPND(rval); + break; + + case JSOP_DUP2: + JS_ASSERT(sp - 1 > fp->spbase); + lval = FETCH_OPND(-2); + rval = FETCH_OPND(-1); + PUSH_OPND(lval); + PUSH_OPND(rval); + break; + +#define PROPERTY_OP(n, call) \ + JS_BEGIN_MACRO \ + /* Pop the left part and resolve it to a non-null object. */ \ + lval = FETCH_OPND(n); \ + VALUE_TO_OBJECT(cx, lval, obj); \ + \ + /* Get or set the property, set ok false if error, true if success. */\ + SAVE_SP(fp); \ + call; \ + if (!ok) \ + goto out; \ + JS_END_MACRO + +#define ELEMENT_OP(n, call) \ + JS_BEGIN_MACRO \ + FETCH_ELEMENT_ID(n, id); \ + PROPERTY_OP(n-1, call); \ + JS_END_MACRO + +/* + * Direct callers, i.e. those who do not wrap CACHED_GET and CACHED_SET calls + * in PROPERTY_OP or ELEMENT_OP macro calls must SAVE_SP(fp); beforehand, just + * in case a getter or setter function is invoked. + */ +#define CACHED_GET(call) \ + JS_BEGIN_MACRO \ + if (!OBJ_IS_NATIVE(obj)) { \ + ok = call; \ + } else { \ + JS_LOCK_OBJ(cx, obj); \ + PROPERTY_CACHE_TEST(&rt->propertyCache, obj, id, sprop); \ + if (sprop) { \ + JSScope *scope_ = OBJ_SCOPE(obj); \ + slot = (uintN)sprop->slot; \ + rval = (slot != SPROP_INVALID_SLOT) \ + ? LOCKED_OBJ_GET_SLOT(obj, slot) \ + : JSVAL_VOID; \ + JS_UNLOCK_SCOPE(cx, scope_); \ + ok = SPROP_GET(cx, sprop, obj, obj, &rval); \ + JS_LOCK_SCOPE(cx, scope_); \ + if (ok && SPROP_HAS_VALID_SLOT(sprop, scope_)) \ + LOCKED_OBJ_SET_SLOT(obj, slot, rval); \ + JS_UNLOCK_SCOPE(cx, scope_); \ + } else { \ + JS_UNLOCK_OBJ(cx, obj); \ + ok = call; \ + /* No fill here: js_GetProperty fills the cache. */ \ + } \ + } \ + JS_END_MACRO + +#define CACHED_SET(call) \ + JS_BEGIN_MACRO \ + if (!OBJ_IS_NATIVE(obj)) { \ + ok = call; \ + } else { \ + JSScope *scope_; \ + JS_LOCK_OBJ(cx, obj); \ + PROPERTY_CACHE_TEST(&rt->propertyCache, obj, id, sprop); \ + if (sprop && \ + !(sprop->attrs & JSPROP_READONLY) && \ + (scope_ = OBJ_SCOPE(obj), !SCOPE_IS_SEALED(scope_))) { \ + JS_UNLOCK_SCOPE(cx, scope_); \ + ok = SPROP_SET(cx, sprop, obj, obj, &rval); \ + JS_LOCK_SCOPE(cx, scope_); \ + if (ok && SPROP_HAS_VALID_SLOT(sprop, scope_)) { \ + LOCKED_OBJ_SET_SLOT(obj, sprop->slot, rval); \ + GC_POKE(cx, JSVAL_NULL); /* XXX second arg ignored */ \ + } \ + JS_UNLOCK_SCOPE(cx, scope_); \ + } else { \ + JS_UNLOCK_OBJ(cx, obj); \ + ok = call; \ + /* No fill here: js_SetProperty writes through the cache. */ \ + } \ + } \ + JS_END_MACRO + + case JSOP_SETCONST: + obj = fp->varobj; + atom = GET_ATOM(cx, script, pc); + rval = FETCH_OPND(-1); + ok = OBJ_DEFINE_PROPERTY(cx, obj, (jsid)atom, rval, NULL, NULL, + JSPROP_ENUMERATE | JSPROP_PERMANENT | + JSPROP_READONLY, + NULL); + if (!ok) + goto out; + STORE_OPND(-1, rval); + break; + + case JSOP_BINDNAME: + atom = GET_ATOM(cx, script, pc); + SAVE_SP(fp); + obj = js_FindIdentifierBase(cx, (jsid)atom); + if (!obj) { + ok = JS_FALSE; + goto out; + } + PUSH_OPND(OBJECT_TO_JSVAL(obj)); + break; + + case JSOP_SETNAME: + atom = GET_ATOM(cx, script, pc); + id = (jsid)atom; + rval = FETCH_OPND(-1); + lval = FETCH_OPND(-2); + JS_ASSERT(!JSVAL_IS_PRIMITIVE(lval)); + obj = JSVAL_TO_OBJECT(lval); + SAVE_SP(fp); + CACHED_SET(OBJ_SET_PROPERTY(cx, obj, id, &rval)); + if (!ok) + goto out; + sp--; + STORE_OPND(-1, rval); + break; + +#define INTEGER_OP(OP, EXTRA_CODE) \ + JS_BEGIN_MACRO \ + FETCH_INT(cx, -1, j); \ + FETCH_INT(cx, -2, i); \ + if (!ok) \ + goto out; \ + EXTRA_CODE \ + d = i OP j; \ + sp--; \ + STORE_NUMBER(cx, -1, d); \ + JS_END_MACRO + +#define BITWISE_OP(OP) INTEGER_OP(OP, (void) 0;) +#define SIGNED_SHIFT_OP(OP) INTEGER_OP(OP, j &= 31;) + + case JSOP_BITOR: + BITWISE_OP(|); + break; + + case JSOP_BITXOR: + BITWISE_OP(^); + break; + + case JSOP_BITAND: + BITWISE_OP(&); + break; + +#if defined(XP_WIN) +#define COMPARE_DOUBLES(LVAL, OP, RVAL, IFNAN) \ + ((JSDOUBLE_IS_NaN(LVAL) || JSDOUBLE_IS_NaN(RVAL)) \ + ? (IFNAN) \ + : (LVAL) OP (RVAL)) +#else +#define COMPARE_DOUBLES(LVAL, OP, RVAL, IFNAN) ((LVAL) OP (RVAL)) +#endif + +#define RELATIONAL_OP(OP) \ + JS_BEGIN_MACRO \ + rval = FETCH_OPND(-1); \ + lval = FETCH_OPND(-2); \ + /* Optimize for two int-tagged operands (typical loop control). */ \ + if ((lval & rval) & JSVAL_INT) { \ + ltmp = lval ^ JSVAL_VOID; \ + rtmp = rval ^ JSVAL_VOID; \ + if (ltmp && rtmp) { \ + cond = JSVAL_TO_INT(lval) OP JSVAL_TO_INT(rval); \ + } else { \ + d = ltmp ? JSVAL_TO_INT(lval) : *rt->jsNaN; \ + d2 = rtmp ? JSVAL_TO_INT(rval) : *rt->jsNaN; \ + cond = COMPARE_DOUBLES(d, OP, d2, JS_FALSE); \ + } \ + } else { \ + VALUE_TO_PRIMITIVE(cx, lval, JSTYPE_NUMBER, &lval); \ + VALUE_TO_PRIMITIVE(cx, rval, JSTYPE_NUMBER, &rval); \ + if (JSVAL_IS_STRING(lval) && JSVAL_IS_STRING(rval)) { \ + str = JSVAL_TO_STRING(lval); \ + str2 = JSVAL_TO_STRING(rval); \ + cond = js_CompareStrings(str, str2) OP 0; \ + } else { \ + VALUE_TO_NUMBER(cx, lval, d); \ + VALUE_TO_NUMBER(cx, rval, d2); \ + cond = COMPARE_DOUBLES(d, OP, d2, JS_FALSE); \ + } \ + } \ + sp--; \ + STORE_OPND(-1, BOOLEAN_TO_JSVAL(cond)); \ + JS_END_MACRO + +#define EQUALITY_OP(OP, IFNAN) \ + JS_BEGIN_MACRO \ + rval = FETCH_OPND(-1); \ + lval = FETCH_OPND(-2); \ + ltmp = JSVAL_TAG(lval); \ + rtmp = JSVAL_TAG(rval); \ + if (ltmp == rtmp) { \ + if (ltmp == JSVAL_STRING) { \ + str = JSVAL_TO_STRING(lval); \ + str2 = JSVAL_TO_STRING(rval); \ + cond = js_CompareStrings(str, str2) OP 0; \ + } else if (ltmp == JSVAL_DOUBLE) { \ + d = *JSVAL_TO_DOUBLE(lval); \ + d2 = *JSVAL_TO_DOUBLE(rval); \ + cond = COMPARE_DOUBLES(d, OP, d2, IFNAN); \ + } else { \ + /* Handle all undefined (=>NaN) and int combinations. */ \ + cond = lval OP rval; \ + } \ + } else { \ + if (JSVAL_IS_NULL(lval) || JSVAL_IS_VOID(lval)) { \ + cond = (JSVAL_IS_NULL(rval) || JSVAL_IS_VOID(rval)) OP 1; \ + } else if (JSVAL_IS_NULL(rval) || JSVAL_IS_VOID(rval)) { \ + cond = 1 OP 0; \ + } else { \ + if (ltmp == JSVAL_OBJECT) { \ + VALUE_TO_PRIMITIVE(cx, lval, JSTYPE_VOID, &lval); \ + ltmp = JSVAL_TAG(lval); \ + } else if (rtmp == JSVAL_OBJECT) { \ + VALUE_TO_PRIMITIVE(cx, rval, JSTYPE_VOID, &rval); \ + rtmp = JSVAL_TAG(rval); \ + } \ + if (ltmp == JSVAL_STRING && rtmp == JSVAL_STRING) { \ + str = JSVAL_TO_STRING(lval); \ + str2 = JSVAL_TO_STRING(rval); \ + cond = js_CompareStrings(str, str2) OP 0; \ + } else { \ + VALUE_TO_NUMBER(cx, lval, d); \ + VALUE_TO_NUMBER(cx, rval, d2); \ + cond = COMPARE_DOUBLES(d, OP, d2, IFNAN); \ + } \ + } \ + } \ + sp--; \ + STORE_OPND(-1, BOOLEAN_TO_JSVAL(cond)); \ + JS_END_MACRO + + case JSOP_EQ: + EQUALITY_OP(==, JS_FALSE); + break; + + case JSOP_NE: + EQUALITY_OP(!=, JS_TRUE); + break; + +#if !JS_BUG_FALLIBLE_EQOPS +#define NEW_EQUALITY_OP(OP, IFNAN) \ + JS_BEGIN_MACRO \ + rval = FETCH_OPND(-1); \ + lval = FETCH_OPND(-2); \ + ltmp = JSVAL_TAG(lval); \ + rtmp = JSVAL_TAG(rval); \ + if (ltmp == rtmp) { \ + if (ltmp == JSVAL_STRING) { \ + str = JSVAL_TO_STRING(lval); \ + str2 = JSVAL_TO_STRING(rval); \ + cond = js_CompareStrings(str, str2) OP 0; \ + } else if (ltmp == JSVAL_DOUBLE) { \ + d = *JSVAL_TO_DOUBLE(lval); \ + d2 = *JSVAL_TO_DOUBLE(rval); \ + cond = COMPARE_DOUBLES(d, OP, d2, IFNAN); \ + } else { \ + cond = lval OP rval; \ + } \ + } else { \ + if (ltmp == JSVAL_DOUBLE && JSVAL_IS_INT(rval)) { \ + d = *JSVAL_TO_DOUBLE(lval); \ + d2 = JSVAL_TO_INT(rval); \ + cond = COMPARE_DOUBLES(d, OP, d2, IFNAN); \ + } else if (JSVAL_IS_INT(lval) && rtmp == JSVAL_DOUBLE) { \ + d = JSVAL_TO_INT(lval); \ + d2 = *JSVAL_TO_DOUBLE(rval); \ + cond = COMPARE_DOUBLES(d, OP, d2, IFNAN); \ + } else { \ + cond = lval OP rval; \ + } \ + } \ + sp--; \ + STORE_OPND(-1, BOOLEAN_TO_JSVAL(cond)); \ + JS_END_MACRO + + case JSOP_NEW_EQ: + NEW_EQUALITY_OP(==, JS_FALSE); + break; + + case JSOP_NEW_NE: + NEW_EQUALITY_OP(!=, JS_TRUE); + break; + +#if JS_HAS_SWITCH_STATEMENT + case JSOP_CASE: + NEW_EQUALITY_OP(==, JS_FALSE); + (void) POP(); + if (cond) { + len = GET_JUMP_OFFSET(pc); + CHECK_BRANCH(len); + } else { + PUSH(lval); + } + break; + + case JSOP_CASEX: + NEW_EQUALITY_OP(==, JS_FALSE); + (void) POP(); + if (cond) { + len = GET_JUMPX_OFFSET(pc); + CHECK_BRANCH(len); + } else { + PUSH(lval); + } + break; +#endif + +#endif /* !JS_BUG_FALLIBLE_EQOPS */ + + case JSOP_LT: + RELATIONAL_OP(<); + break; + + case JSOP_LE: + RELATIONAL_OP(<=); + break; + + case JSOP_GT: + RELATIONAL_OP(>); + break; + + case JSOP_GE: + RELATIONAL_OP(>=); + break; + +#undef EQUALITY_OP +#undef RELATIONAL_OP + + case JSOP_LSH: + SIGNED_SHIFT_OP(<<); + break; + + case JSOP_RSH: + SIGNED_SHIFT_OP(>>); + break; + + case JSOP_URSH: + { + uint32 u; + + FETCH_INT(cx, -1, j); + FETCH_UINT(cx, -2, u); + j &= 31; + d = u >> j; + sp--; + STORE_NUMBER(cx, -1, d); + break; + } + +#undef INTEGER_OP +#undef BITWISE_OP +#undef SIGNED_SHIFT_OP + + case JSOP_ADD: + rval = FETCH_OPND(-1); + lval = FETCH_OPND(-2); + VALUE_TO_PRIMITIVE(cx, lval, JSTYPE_VOID, <mp); + VALUE_TO_PRIMITIVE(cx, rval, JSTYPE_VOID, &rtmp); + if ((cond = JSVAL_IS_STRING(ltmp)) || JSVAL_IS_STRING(rtmp)) { + if (cond) { + str = JSVAL_TO_STRING(ltmp); + SAVE_SP(fp); + ok = (str2 = js_ValueToString(cx, rtmp)) != NULL; + } else { + str2 = JSVAL_TO_STRING(rtmp); + SAVE_SP(fp); + ok = (str = js_ValueToString(cx, ltmp)) != NULL; + } + if (!ok) + goto out; + str = js_ConcatStrings(cx, str, str2); + if (!str) { + ok = JS_FALSE; + goto out; + } + sp--; + STORE_OPND(-1, STRING_TO_JSVAL(str)); + } else { + VALUE_TO_NUMBER(cx, lval, d); + VALUE_TO_NUMBER(cx, rval, d2); + d += d2; + sp--; + STORE_NUMBER(cx, -1, d); + } + break; + +#define BINARY_OP(OP) \ + JS_BEGIN_MACRO \ + FETCH_NUMBER(cx, -1, d2); \ + FETCH_NUMBER(cx, -2, d); \ + d = d OP d2; \ + sp--; \ + STORE_NUMBER(cx, -1, d); \ + JS_END_MACRO + + case JSOP_SUB: + BINARY_OP(-); + break; + + case JSOP_MUL: + BINARY_OP(*); + break; + + case JSOP_DIV: + FETCH_NUMBER(cx, -1, d2); + FETCH_NUMBER(cx, -2, d); + sp--; + if (d2 == 0) { +#if defined(XP_WIN) + /* XXX MSVC miscompiles such that (NaN == 0) */ + if (JSDOUBLE_IS_NaN(d2)) + rval = DOUBLE_TO_JSVAL(rt->jsNaN); + else +#endif + if (d == 0 || JSDOUBLE_IS_NaN(d)) + rval = DOUBLE_TO_JSVAL(rt->jsNaN); + else if ((JSDOUBLE_HI32(d) ^ JSDOUBLE_HI32(d2)) >> 31) + rval = DOUBLE_TO_JSVAL(rt->jsNegativeInfinity); + else + rval = DOUBLE_TO_JSVAL(rt->jsPositiveInfinity); + STORE_OPND(-1, rval); + } else { + d /= d2; + STORE_NUMBER(cx, -1, d); + } + break; + + case JSOP_MOD: + FETCH_NUMBER(cx, -1, d2); + FETCH_NUMBER(cx, -2, d); + sp--; + if (d2 == 0) { + STORE_OPND(-1, DOUBLE_TO_JSVAL(rt->jsNaN)); + } else { +#if defined(XP_WIN) + /* Workaround MS fmod bug where 42 % (1/0) => NaN, not 42. */ + if (!(JSDOUBLE_IS_FINITE(d) && JSDOUBLE_IS_INFINITE(d2))) +#endif + d = fmod(d, d2); + STORE_NUMBER(cx, -1, d); + } + break; + + case JSOP_NOT: + POP_BOOLEAN(cx, rval, cond); + PUSH_OPND(BOOLEAN_TO_JSVAL(!cond)); + break; + + case JSOP_BITNOT: + FETCH_INT(cx, -1, i); + d = (jsdouble) ~i; + STORE_NUMBER(cx, -1, d); + break; + + case JSOP_NEG: + FETCH_NUMBER(cx, -1, d); +#ifdef HPUX + /* + * Negation of a zero doesn't produce a negative + * zero on HPUX. Perform the operation by bit + * twiddling. + */ + JSDOUBLE_HI32(d) ^= JSDOUBLE_HI32_SIGNBIT; +#else + d = -d; +#endif + STORE_NUMBER(cx, -1, d); + break; + + case JSOP_POS: + FETCH_NUMBER(cx, -1, d); + STORE_NUMBER(cx, -1, d); + break; + + case JSOP_NEW: + /* Get immediate argc and find the constructor function. */ + argc = GET_ARGC(pc); + +#if JS_HAS_INITIALIZERS + do_new: +#endif + vp = sp - (2 + argc); + JS_ASSERT(vp >= fp->spbase); + + fun = NULL; + obj2 = NULL; + lval = *vp; + if (!JSVAL_IS_OBJECT(lval) || + (obj2 = JSVAL_TO_OBJECT(lval)) == NULL || + /* XXX clean up to avoid special cases above ObjectOps layer */ + OBJ_GET_CLASS(cx, obj2) == &js_FunctionClass || + !obj2->map->ops->construct) + { + SAVE_SP(fp); + fun = js_ValueToFunction(cx, vp, JSV2F_CONSTRUCT); + if (!fun) { + ok = JS_FALSE; + goto out; + } + } + + clasp = &js_ObjectClass; + if (!obj2) { + proto = parent = NULL; + fun = NULL; + } else { + /* Get the constructor prototype object for this function. */ + ok = OBJ_GET_PROPERTY(cx, obj2, + (jsid)rt->atomState.classPrototypeAtom, + &rval); + if (!ok) + goto out; + proto = JSVAL_IS_OBJECT(rval) ? JSVAL_TO_OBJECT(rval) : NULL; + parent = OBJ_GET_PARENT(cx, obj2); + + if (OBJ_GET_CLASS(cx, obj2) == &js_FunctionClass) { + funclasp = ((JSFunction *)JS_GetPrivate(cx, obj2))->clasp; + if (funclasp) + clasp = funclasp; + } + } + obj = js_NewObject(cx, clasp, proto, parent); + if (!obj) { + ok = JS_FALSE; + goto out; + } + + /* Now we have an object with a constructor method; call it. */ + vp[1] = OBJECT_TO_JSVAL(obj); + SAVE_SP(fp); + ok = js_Invoke(cx, argc, JSINVOKE_CONSTRUCT); + RESTORE_SP(fp); + LOAD_INTERRUPT_HANDLER(rt); + if (!ok) { + cx->newborn[GCX_OBJECT] = NULL; + goto out; + } + + /* Check the return value and update obj from it. */ + rval = *vp; + if (JSVAL_IS_PRIMITIVE(rval)) { + if (fun || !JSVERSION_IS_ECMA(cx->version)) { + *vp = OBJECT_TO_JSVAL(obj); + break; + } + /* native [[Construct]] returning primitive is error */ + str = js_ValueToString(cx, rval); + if (str) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_NEW_RESULT, + JS_GetStringBytes(str)); + } + ok = JS_FALSE; + goto out; + } + obj = JSVAL_TO_OBJECT(rval); + JS_RUNTIME_METER(rt, constructs); + break; + + case JSOP_DELNAME: + atom = GET_ATOM(cx, script, pc); + id = (jsid)atom; + + SAVE_SP(fp); + ok = js_FindProperty(cx, id, &obj, &obj2, &prop); + if (!ok) + goto out; + + /* ECMA says to return true if name is undefined or inherited. */ + rval = JSVAL_TRUE; + if (prop) { + OBJ_DROP_PROPERTY(cx, obj2, prop); + ok = OBJ_DELETE_PROPERTY(cx, obj, id, &rval); + if (!ok) + goto out; + } + PUSH_OPND(rval); + break; + + case JSOP_DELPROP: + atom = GET_ATOM(cx, script, pc); + id = (jsid)atom; + PROPERTY_OP(-1, ok = OBJ_DELETE_PROPERTY(cx, obj, id, &rval)); + STORE_OPND(-1, rval); + break; + + case JSOP_DELELEM: + ELEMENT_OP(-1, ok = OBJ_DELETE_PROPERTY(cx, obj, id, &rval)); + sp--; + STORE_OPND(-1, rval); + break; + + case JSOP_TYPEOF: + rval = POP_OPND(); + type = JS_TypeOfValue(cx, rval); + atom = rt->atomState.typeAtoms[type]; + str = ATOM_TO_STRING(atom); + PUSH_OPND(STRING_TO_JSVAL(str)); + break; + + case JSOP_VOID: + (void) POP_OPND(); + PUSH_OPND(JSVAL_VOID); + break; + + case JSOP_INCNAME: + case JSOP_DECNAME: + case JSOP_NAMEINC: + case JSOP_NAMEDEC: + atom = GET_ATOM(cx, script, pc); + id = (jsid)atom; + + SAVE_SP(fp); + ok = js_FindProperty(cx, id, &obj, &obj2, &prop); + if (!ok) + goto out; + if (!prop) + goto atom_not_defined; + + OBJ_DROP_PROPERTY(cx, obj2, prop); + lval = OBJECT_TO_JSVAL(obj); + goto do_incop; + + case JSOP_INCPROP: + case JSOP_DECPROP: + case JSOP_PROPINC: + case JSOP_PROPDEC: + atom = GET_ATOM(cx, script, pc); + id = (jsid)atom; + lval = POP_OPND(); + goto do_incop; + + case JSOP_INCELEM: + case JSOP_DECELEM: + case JSOP_ELEMINC: + case JSOP_ELEMDEC: + POP_ELEMENT_ID(id); + lval = POP_OPND(); + + do_incop: + VALUE_TO_OBJECT(cx, lval, obj); + + /* The operand must contain a number. */ + SAVE_SP(fp); + CACHED_GET(OBJ_GET_PROPERTY(cx, obj, id, &rval)); + if (!ok) + goto out; + + /* The expression result goes in rtmp, the updated value in rval. */ + if (JSVAL_IS_INT(rval) && + rval != INT_TO_JSVAL(JSVAL_INT_MIN) && + rval != INT_TO_JSVAL(JSVAL_INT_MAX)) { + if (cs->format & JOF_POST) { + rtmp = rval; + (cs->format & JOF_INC) ? (rval += 2) : (rval -= 2); + } else { + (cs->format & JOF_INC) ? (rval += 2) : (rval -= 2); + rtmp = rval; + } + } else { + +/* + * Initially, rval contains the value to increment or decrement, which is not + * yet converted. As above, the expression result goes in rtmp, the updated + * value goes in rval. + */ +#define NONINT_INCREMENT_OP() \ + JS_BEGIN_MACRO \ + VALUE_TO_NUMBER(cx, rval, d); \ + if (cs->format & JOF_POST) { \ + rtmp = rval; \ + if (!JSVAL_IS_NUMBER(rtmp)) { \ + ok = js_NewNumberValue(cx, d, &rtmp); \ + if (!ok) \ + goto out; \ + } \ + (cs->format & JOF_INC) ? d++ : d--; \ + ok = js_NewNumberValue(cx, d, &rval); \ + } else { \ + (cs->format & JOF_INC) ? ++d : --d; \ + ok = js_NewNumberValue(cx, d, &rval); \ + rtmp = rval; \ + } \ + if (!ok) \ + goto out; \ + JS_END_MACRO + + NONINT_INCREMENT_OP(); + } + + CACHED_SET(OBJ_SET_PROPERTY(cx, obj, id, &rval)); + if (!ok) + goto out; + PUSH_OPND(rtmp); + break; + +/* + * NB: This macro can't use JS_BEGIN_MACRO/JS_END_MACRO around its body because + * it must break from the switch case that calls it, not from the do...while(0) + * loop created by the JS_BEGIN/END_MACRO brackets. + */ +#define FAST_INCREMENT_OP(SLOT,COUNT,BASE,PRE,OP,MINMAX) \ + slot = (uintN)SLOT; \ + JS_ASSERT(slot < fp->fun->COUNT); \ + vp = fp->BASE + slot; \ + rval = *vp; \ + if (JSVAL_IS_INT(rval) && \ + rval != INT_TO_JSVAL(JSVAL_INT_##MINMAX)) { \ + PRE = rval; \ + rval OP 2; \ + *vp = rval; \ + PUSH_OPND(PRE); \ + break; \ + } \ + goto do_nonint_fast_incop; + + case JSOP_INCARG: + FAST_INCREMENT_OP(GET_ARGNO(pc), nargs, argv, rval, +=, MAX); + case JSOP_DECARG: + FAST_INCREMENT_OP(GET_ARGNO(pc), nargs, argv, rval, -=, MIN); + case JSOP_ARGINC: + FAST_INCREMENT_OP(GET_ARGNO(pc), nargs, argv, rtmp, +=, MAX); + case JSOP_ARGDEC: + FAST_INCREMENT_OP(GET_ARGNO(pc), nargs, argv, rtmp, -=, MIN); + + case JSOP_INCVAR: + FAST_INCREMENT_OP(GET_VARNO(pc), nvars, vars, rval, +=, MAX); + case JSOP_DECVAR: + FAST_INCREMENT_OP(GET_VARNO(pc), nvars, vars, rval, -=, MIN); + case JSOP_VARINC: + FAST_INCREMENT_OP(GET_VARNO(pc), nvars, vars, rtmp, +=, MAX); + case JSOP_VARDEC: + FAST_INCREMENT_OP(GET_VARNO(pc), nvars, vars, rtmp, -=, MIN); + +#undef FAST_INCREMENT_OP + + do_nonint_fast_incop: + NONINT_INCREMENT_OP(); + *vp = rval; + PUSH_OPND(rtmp); + break; + + case JSOP_GETPROP: + /* Get an immediate atom naming the property. */ + atom = GET_ATOM(cx, script, pc); + id = (jsid)atom; + PROPERTY_OP(-1, CACHED_GET(OBJ_GET_PROPERTY(cx, obj, id, &rval))); + STORE_OPND(-1, rval); + break; + + case JSOP_SETPROP: + /* Pop the right-hand side into rval for OBJ_SET_PROPERTY. */ + rval = FETCH_OPND(-1); + + /* Get an immediate atom naming the property. */ + atom = GET_ATOM(cx, script, pc); + id = (jsid)atom; + PROPERTY_OP(-2, CACHED_SET(OBJ_SET_PROPERTY(cx, obj, id, &rval))); + sp--; + STORE_OPND(-1, rval); + break; + + case JSOP_GETELEM: + ELEMENT_OP(-1, CACHED_GET(OBJ_GET_PROPERTY(cx, obj, id, &rval))); + sp--; + STORE_OPND(-1, rval); + break; + + case JSOP_SETELEM: + rval = FETCH_OPND(-1); + ELEMENT_OP(-2, CACHED_SET(OBJ_SET_PROPERTY(cx, obj, id, &rval))); + sp -= 2; + STORE_OPND(-1, rval); + break; + + case JSOP_ENUMELEM: + /* Funky: the value to set is under the [obj, id] pair. */ + FETCH_ELEMENT_ID(-1, id); + lval = FETCH_OPND(-2); + VALUE_TO_OBJECT(cx, lval, obj); + rval = FETCH_OPND(-3); + SAVE_SP(fp); + ok = OBJ_SET_PROPERTY(cx, obj, id, &rval); + if (!ok) + goto out; + sp -= 3; + break; + +/* + * LAZY_ARGS_THISP allows the JSOP_ARGSUB bytecode to defer creation of the + * arguments object until it is truly needed. JSOP_ARGSUB optimizes away + * arguments objects when the only uses of the 'arguments' parameter are to + * fetch individual actual parameters. But if such a use were then invoked, + * e.g., arguments[i](), the 'this' parameter would and must bind to the + * caller's arguments object. So JSOP_ARGSUB sets obj to LAZY_ARGS_THISP. + */ +#define LAZY_ARGS_THISP ((JSObject *) 1) + + case JSOP_PUSHOBJ: + if (obj == LAZY_ARGS_THISP && !(obj = js_GetArgsObject(cx, fp))) { + ok = JS_FALSE; + goto out; + } + PUSH_OPND(OBJECT_TO_JSVAL(obj)); + break; + + case JSOP_CALL: + case JSOP_EVAL: + argc = GET_ARGC(pc); + vp = sp - (argc + 2); + lval = *vp; + SAVE_SP(fp); + + if (JSVAL_IS_FUNCTION(cx, lval) && + (obj = JSVAL_TO_OBJECT(lval), + fun = (JSFunction *) JS_GetPrivate(cx, obj), + !fun->native && + !(fun->flags & (JSFUN_HEAVYWEIGHT | JSFUN_BOUND_METHOD)) && + argc >= (uintN)(fun->nargs + fun->extra))) + /* inline_call: */ + { + uintN nframeslots, nvars; + void *newmark; + JSInlineFrame *newifp; + JSInterpreterHook hook; + + /* Restrict recursion of lightweight functions. */ + if (inlineCallCount == MAX_INLINE_CALL_COUNT) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_OVER_RECURSED); + ok = JS_FALSE; + goto out; + } + +#if JS_HAS_JIT + /* ZZZbe should do this only if interpreted often enough. */ + ok = jsjit_Compile(cx, fun); + if (!ok) + goto out; +#endif + + /* Compute the number of stack slots needed for fun. */ + nframeslots = (sizeof(JSInlineFrame) + sizeof(jsval) - 1) + / sizeof(jsval); + nvars = fun->nvars; + script = fun->script; + depth = (jsint) script->depth; + + /* Allocate the frame and space for vars and operands. */ + newsp = js_AllocRawStack(cx, nframeslots + nvars + 2 * depth, + &newmark); + if (!newsp) { + ok = JS_FALSE; + goto bad_inline_call; + } + newifp = (JSInlineFrame *) newsp; + newsp += nframeslots; + + /* Initialize the stack frame. */ + memset(newifp, 0, sizeof(JSInlineFrame)); + newifp->frame.script = script; + newifp->frame.fun = fun; + newifp->frame.argc = argc; + newifp->frame.argv = vp + 2; + newifp->frame.rval = JSVAL_VOID; + newifp->frame.nvars = nvars; + newifp->frame.vars = newsp; + newifp->frame.down = fp; + newifp->frame.scopeChain = OBJ_GET_PARENT(cx, obj); + newifp->mark = newmark; + + /* Compute the 'this' parameter now that argv is set. */ + ok = ComputeThis(cx, JSVAL_TO_OBJECT(vp[1]), &newifp->frame); + if (!ok) { + js_FreeRawStack(cx, newmark); + goto bad_inline_call; + } + + /* Push void to initialize local variables. */ + sp = newsp; + while (nvars--) + PUSH(JSVAL_VOID); + sp += depth; + newifp->frame.spbase = sp; + SAVE_SP(&newifp->frame); + + /* Call the debugger hook if present. */ + hook = cx->runtime->callHook; + if (hook) { + newifp->hookData = hook(cx, &newifp->frame, JS_TRUE, 0, + cx->runtime->callHookData); + LOAD_INTERRUPT_HANDLER(rt); + } + + /* Switch to new version if currentVersion wasn't overridden. */ + newifp->callerVersion = cx->version; + if (cx->version == currentVersion) { + currentVersion = script->version; + if (currentVersion != cx->version) + JS_SetVersion(cx, currentVersion); + } + + /* Push the frame and set interpreter registers. */ + cx->fp = fp = &newifp->frame; + pc = script->code; + endpc = pc + script->length; + inlineCallCount++; + JS_RUNTIME_METER(rt, inlineCalls); + continue; + + bad_inline_call: + script = fp->script; + depth = (jsint) script->depth; + goto out; + } + + ok = js_Invoke(cx, argc, 0); + RESTORE_SP(fp); + LOAD_INTERRUPT_HANDLER(rt); + if (!ok) + goto out; + JS_RUNTIME_METER(rt, nonInlineCalls); +#if JS_HAS_LVALUE_RETURN + if (cx->rval2set) { + /* + * Sneaky: use the stack depth we didn't claim in our budget, + * but that we know is there on account of [fun, this] already + * having been pushed, at a minimum (if no args). Those two + * slots have been popped and [rval] has been pushed, which + * leaves one more slot for rval2 before we might overflow. + * + * NB: rval2 must be the property identifier, and rval the + * object from which to get the property. The pair form an + * ECMA "reference type", which can be used on the right- or + * left-hand side of assignment ops. Only native methods can + * return reference types. See JSOP_SETCALL just below for + * the left-hand-side case. + */ + PUSH_OPND(cx->rval2); + cx->rval2set = JS_FALSE; + ELEMENT_OP(-1, ok = OBJ_GET_PROPERTY(cx, obj, id, &rval)); + sp--; + STORE_OPND(-1, rval); + } +#endif + obj = NULL; + break; + +#if JS_HAS_LVALUE_RETURN + case JSOP_SETCALL: + argc = GET_ARGC(pc); + SAVE_SP(fp); + ok = js_Invoke(cx, argc, 0); + RESTORE_SP(fp); + LOAD_INTERRUPT_HANDLER(rt); + if (!ok) + goto out; + if (!cx->rval2set) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_LEFTSIDE_OF_ASS); + ok = JS_FALSE; + goto out; + } + PUSH_OPND(cx->rval2); + cx->rval2set = JS_FALSE; + obj = NULL; + break; +#endif + + case JSOP_NAME: + atom = GET_ATOM(cx, script, pc); + id = (jsid)atom; + + SAVE_SP(fp); + ok = js_FindProperty(cx, id, &obj, &obj2, &prop); + if (!ok) + goto out; + if (!prop) { + /* Kludge to allow (typeof foo == "undefined") tests. */ + for (pc2 = pc + len; pc2 < endpc; pc2++) { + op2 = (JSOp)*pc2; + if (op2 == JSOP_TYPEOF) { + PUSH_OPND(JSVAL_VOID); + goto advance_pc; + } + if (op2 != JSOP_GROUP) + break; + } + goto atom_not_defined; + } + + /* Take the slow path if prop was not found in a native object. */ + if (!OBJ_IS_NATIVE(obj) || !OBJ_IS_NATIVE(obj2)) { + OBJ_DROP_PROPERTY(cx, obj2, prop); + ok = OBJ_GET_PROPERTY(cx, obj, id, &rval); + if (!ok) + goto out; + PUSH_OPND(rval); + break; + } + + /* Get and push the obj[id] property's value. */ + sprop = (JSScopeProperty *)prop; + slot = (uintN)sprop->slot; + rval = (slot != SPROP_INVALID_SLOT) + ? LOCKED_OBJ_GET_SLOT(obj2, slot) + : JSVAL_VOID; + JS_UNLOCK_OBJ(cx, obj2); + ok = SPROP_GET(cx, sprop, obj, obj2, &rval); + JS_LOCK_OBJ(cx, obj2); + if (!ok) { + OBJ_DROP_PROPERTY(cx, obj2, prop); + goto out; + } + if (SPROP_HAS_VALID_SLOT(sprop, OBJ_SCOPE(obj2))) + LOCKED_OBJ_SET_SLOT(obj2, slot, rval); + OBJ_DROP_PROPERTY(cx, obj2, prop); + PUSH_OPND(rval); + break; + + case JSOP_UINT16: + i = (jsint) GET_ATOM_INDEX(pc); + rval = INT_TO_JSVAL(i); + PUSH_OPND(rval); + obj = NULL; + break; + + case JSOP_NUMBER: + case JSOP_STRING: + atom = GET_ATOM(cx, script, pc); + PUSH_OPND(ATOM_KEY(atom)); + obj = NULL; + break; + + case JSOP_OBJECT: + { +#if 0 + jsatomid atomIndex; + JSAtomMap *atomMap; + + /* + * Get a suitable object from an atom mapped by the bytecode at pc. + * + * We must handle the case where a regexp object literal is used in + * a different global at execution time from the global with which + * it was scanned at compile time, in order to rewrap the JSRegExp + * struct with a new object having the right prototype and parent. + * + * Unlike JSOP_DEFFUN and other prolog bytecodes, we don't want to + * pay a script prolog execution price for all regexp literals in a + * script (many may not be used by a particular execution of that + * script, depending on control flow), so we do all fp->objAtomMap + * initialization lazily, here under JSOP_OBJECT. + * + * XXX This code is specific to regular expression objects. If we + * need JSOP_OBJECT for other kinds of object literals, we should + * push cloning down under JSObjectOps. Also, fp->objAtomMap is + * used only for object atoms, so it's sparse (wasting some stack + * space) and as its name implies, you can't get non-object atoms + * from it. + */ + atomIndex = GET_ATOM_INDEX(pc); + atomMap = fp->objAtomMap; + atom = atomMap ? atomMap->vector[atomIndex] : NULL; + if (!atom) { + /* Let atom and obj denote the regexp (object) mapped by pc. */ + atom = js_GetAtom(cx, &script->atomMap, atomIndex); + JS_ASSERT(ATOM_IS_OBJECT(atom)); + obj = ATOM_TO_OBJECT(atom); + JS_ASSERT(OBJ_GET_CLASS(cx, obj) == &js_RegExpClass); + + /* Compute the current global object in obj2. */ + obj2 = fp->scopeChain; + while ((parent = OBJ_GET_PARENT(cx, obj2)) != NULL) + obj2 = parent; + + /* + * If obj's parent is not obj2, we must clone obj so that it + * has the right parent, and therefore, the right prototype. + * + * Yes, this means we assume that the correct RegExp.prototype + * to which regexp instances (including literals) delegate can + * be distinguished solely by the instance's parent, which was + * set to the parent of the RegExp constructor function object + * when the instance was created. In other words, + * + * (/x/.__parent__ == RegExp.__parent__) implies + * (/x/.__proto__ == RegExp.prototype) + * + * (unless you assign a different object to RegExp.prototype + * at runtime, in which case, ECMA doesn't specify operation, + * and you get what you deserve). + * + * This same coupling between instance parent and constructor + * parent turns up elsewhere (see jsobj.c's FindConstructor, + * js_ConstructObject, and js_NewObject). It's fundamental. + */ + if (OBJ_GET_PARENT(cx, obj) != obj2) { + obj = js_CloneRegExpObject(cx, obj, obj2); + if (!obj) { + ok = JS_FALSE; + goto out; + } + + atom = js_AtomizeObject(cx, obj, 0); + if (!atom) { + ok = JS_FALSE; + goto out; + } + } + + /* + * If fp->objAtomMap is null, initialize it now so we can map + * atom (whether atom is newly created for a cloned object, or + * the original atom mapped by script) for faster performance + * next time through JSOP_OBJECT. + */ + if (!atomMap) { + jsatomid mapLength = script->atomMap.length; + size_t vectorBytes = mapLength * sizeof(JSAtom *); + + /* Allocate an override atom map from cx->stackPool. */ + JS_ARENA_ALLOCATE_CAST(atomMap, JSAtomMap *, + &cx->stackPool, + sizeof(JSAtomMap) + vectorBytes); + if (!atomMap) { + JS_ReportOutOfMemory(cx); + ok = JS_FALSE; + goto out; + } + + atomMap->length = mapLength; + atomMap->vector = (JSAtom **)(atomMap + 1); + memset(atomMap->vector, 0, vectorBytes); + fp->objAtomMap = atomMap; + } + atomMap->vector[atomIndex] = atom; + } +#else + atom = GET_ATOM(cx, script, pc); + JS_ASSERT(ATOM_IS_OBJECT(atom)); +#endif + rval = ATOM_KEY(atom); + PUSH_OPND(rval); + obj = NULL; + break; + } + + case JSOP_ZERO: + PUSH_OPND(JSVAL_ZERO); + obj = NULL; + break; + + case JSOP_ONE: + PUSH_OPND(JSVAL_ONE); + obj = NULL; + break; + + case JSOP_NULL: + PUSH_OPND(JSVAL_NULL); + obj = NULL; + break; + + case JSOP_THIS: + PUSH_OPND(OBJECT_TO_JSVAL(fp->thisp)); + obj = NULL; + break; + + case JSOP_FALSE: + PUSH_OPND(JSVAL_FALSE); + obj = NULL; + break; + + case JSOP_TRUE: + PUSH_OPND(JSVAL_TRUE); + obj = NULL; + break; + +#if JS_HAS_SWITCH_STATEMENT + case JSOP_TABLESWITCH: + pc2 = pc; + len = GET_JUMP_OFFSET(pc2); + + /* + * ECMAv2 forbids conversion of discriminant, so we will skip to + * the default case if the discriminant isn't already an int jsval. + * (This opcode is emitted only for dense jsint-domain switches.) + */ + if (cx->version == JSVERSION_DEFAULT || + cx->version >= JSVERSION_1_4) { + rval = POP_OPND(); + if (!JSVAL_IS_INT(rval)) + break; + i = JSVAL_TO_INT(rval); + } else { + FETCH_INT(cx, -1, i); + sp--; + } + + pc2 += JUMP_OFFSET_LEN; + low = GET_JUMP_OFFSET(pc2); + pc2 += JUMP_OFFSET_LEN; + high = GET_JUMP_OFFSET(pc2); + + i -= low; + if ((jsuint)i < (jsuint)(high - low + 1)) { + pc2 += JUMP_OFFSET_LEN + JUMP_OFFSET_LEN * i; + off = (jsint) GET_JUMP_OFFSET(pc2); + if (off) + len = off; + } + break; + + case JSOP_LOOKUPSWITCH: + lval = POP_OPND(); + pc2 = pc; + len = GET_JUMP_OFFSET(pc2); + + if (!JSVAL_IS_NUMBER(lval) && + !JSVAL_IS_STRING(lval) && + !JSVAL_IS_BOOLEAN(lval)) { + goto advance_pc; + } + + pc2 += JUMP_OFFSET_LEN; + npairs = (jsint) GET_ATOM_INDEX(pc2); + pc2 += ATOM_INDEX_LEN; + +#define SEARCH_PAIRS(MATCH_CODE) \ + while (npairs) { \ + atom = GET_ATOM(cx, script, pc2); \ + rval = ATOM_KEY(atom); \ + MATCH_CODE \ + if (match) { \ + pc2 += ATOM_INDEX_LEN; \ + len = GET_JUMP_OFFSET(pc2); \ + goto advance_pc; \ + } \ + pc2 += ATOM_INDEX_LEN + JUMP_OFFSET_LEN; \ + npairs--; \ + } + if (JSVAL_IS_STRING(lval)) { + str = JSVAL_TO_STRING(lval); + SEARCH_PAIRS( + match = (JSVAL_IS_STRING(rval) && + ((str2 = JSVAL_TO_STRING(rval)) == str || + !js_CompareStrings(str2, str))); + ) + } else if (JSVAL_IS_DOUBLE(lval)) { + d = *JSVAL_TO_DOUBLE(lval); + SEARCH_PAIRS( + match = (JSVAL_IS_DOUBLE(rval) && + *JSVAL_TO_DOUBLE(rval) == d); + ) + } else { + SEARCH_PAIRS( + match = (lval == rval); + ) + } +#undef SEARCH_PAIRS + break; + + case JSOP_TABLESWITCHX: + pc2 = pc; + len = GET_JUMPX_OFFSET(pc2); + + /* + * ECMAv2 forbids conversion of discriminant, so we will skip to + * the default case if the discriminant isn't already an int jsval. + * (This opcode is emitted only for dense jsint-domain switches.) + */ + if (cx->version == JSVERSION_DEFAULT || + cx->version >= JSVERSION_1_4) { + rval = POP_OPND(); + if (!JSVAL_IS_INT(rval)) + break; + i = JSVAL_TO_INT(rval); + } else { + FETCH_INT(cx, -1, i); + sp--; + } + + pc2 += JUMPX_OFFSET_LEN; + low = GET_JUMP_OFFSET(pc2); + pc2 += JUMP_OFFSET_LEN; + high = GET_JUMP_OFFSET(pc2); + + i -= low; + if ((jsuint)i < (jsuint)(high - low + 1)) { + pc2 += JUMP_OFFSET_LEN + JUMPX_OFFSET_LEN * i; + off = (jsint) GET_JUMPX_OFFSET(pc2); + if (off) + len = off; + } + break; + + case JSOP_LOOKUPSWITCHX: + lval = POP_OPND(); + pc2 = pc; + len = GET_JUMPX_OFFSET(pc2); + + if (!JSVAL_IS_NUMBER(lval) && + !JSVAL_IS_STRING(lval) && + !JSVAL_IS_BOOLEAN(lval)) { + goto advance_pc; + } + + pc2 += JUMPX_OFFSET_LEN; + npairs = (jsint) GET_ATOM_INDEX(pc2); + pc2 += ATOM_INDEX_LEN; + +#define SEARCH_EXTENDED_PAIRS(MATCH_CODE) \ + while (npairs) { \ + atom = GET_ATOM(cx, script, pc2); \ + rval = ATOM_KEY(atom); \ + MATCH_CODE \ + if (match) { \ + pc2 += ATOM_INDEX_LEN; \ + len = GET_JUMPX_OFFSET(pc2); \ + goto advance_pc; \ + } \ + pc2 += ATOM_INDEX_LEN + JUMPX_OFFSET_LEN; \ + npairs--; \ + } + if (JSVAL_IS_STRING(lval)) { + str = JSVAL_TO_STRING(lval); + SEARCH_EXTENDED_PAIRS( + match = (JSVAL_IS_STRING(rval) && + ((str2 = JSVAL_TO_STRING(rval)) == str || + !js_CompareStrings(str2, str))); + ) + } else if (JSVAL_IS_DOUBLE(lval)) { + d = *JSVAL_TO_DOUBLE(lval); + SEARCH_EXTENDED_PAIRS( + match = (JSVAL_IS_DOUBLE(rval) && + *JSVAL_TO_DOUBLE(rval) == d); + ) + } else { + SEARCH_EXTENDED_PAIRS( + match = (lval == rval); + ) + } +#undef SEARCH_EXTENDED_PAIRS + break; + + case JSOP_CONDSWITCH: + break; + +#endif /* JS_HAS_SWITCH_STATEMENT */ + +#if JS_HAS_EXPORT_IMPORT + case JSOP_EXPORTALL: + obj = fp->varobj; + ida = JS_Enumerate(cx, obj); + if (!ida) { + ok = JS_FALSE; + } else { + for (i = 0, j = ida->length; i < j; i++) { + id = ida->vector[i]; + ok = OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop); + if (!ok) + break; + if (!prop) + continue; + ok = OBJ_GET_ATTRIBUTES(cx, obj, id, prop, &attrs); + if (ok) { + attrs |= JSPROP_EXPORTED; + ok = OBJ_SET_ATTRIBUTES(cx, obj, id, prop, &attrs); + } + OBJ_DROP_PROPERTY(cx, obj2, prop); + if (!ok) + break; + } + JS_DestroyIdArray(cx, ida); + } + break; + + case JSOP_EXPORTNAME: + atom = GET_ATOM(cx, script, pc); + id = (jsid)atom; + obj = fp->varobj; + ok = OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop); + if (!ok) + goto out; + if (!prop) { + ok = OBJ_DEFINE_PROPERTY(cx, obj, id, JSVAL_VOID, NULL, NULL, + JSPROP_EXPORTED, NULL); + } else { + ok = OBJ_GET_ATTRIBUTES(cx, obj, id, prop, &attrs); + if (ok) { + attrs |= JSPROP_EXPORTED; + ok = OBJ_SET_ATTRIBUTES(cx, obj, id, prop, &attrs); + } + OBJ_DROP_PROPERTY(cx, obj2, prop); + } + if (!ok) + goto out; + break; + + case JSOP_IMPORTALL: + id = (jsid)JSVAL_VOID; + PROPERTY_OP(-1, ok = ImportProperty(cx, obj, id)); + sp--; + break; + + case JSOP_IMPORTPROP: + /* Get an immediate atom naming the property. */ + atom = GET_ATOM(cx, script, pc); + id = (jsid)atom; + PROPERTY_OP(-1, ok = ImportProperty(cx, obj, id)); + sp--; + break; + + case JSOP_IMPORTELEM: + ELEMENT_OP(-1, ok = ImportProperty(cx, obj, id)); + sp -= 2; + break; +#endif /* JS_HAS_EXPORT_IMPORT */ + + case JSOP_TRAP: + switch (JS_HandleTrap(cx, script, pc, &rval)) { + case JSTRAP_ERROR: + ok = JS_FALSE; + goto out; + case JSTRAP_CONTINUE: + JS_ASSERT(JSVAL_IS_INT(rval)); + op = (JSOp) JSVAL_TO_INT(rval); + JS_ASSERT((uintN)op < (uintN)JSOP_LIMIT); + LOAD_INTERRUPT_HANDLER(rt); + goto do_op; + case JSTRAP_RETURN: + fp->rval = rval; + goto out; +#if JS_HAS_EXCEPTIONS + case JSTRAP_THROW: + cx->throwing = JS_TRUE; + cx->exception = rval; + ok = JS_FALSE; + goto out; +#endif /* JS_HAS_EXCEPTIONS */ + default:; + } + LOAD_INTERRUPT_HANDLER(rt); + break; + + case JSOP_ARGUMENTS: + SAVE_SP(fp); + ok = js_GetArgsValue(cx, fp, &rval); + if (!ok) + goto out; + PUSH_OPND(rval); + break; + + case JSOP_ARGSUB: + id = (jsid) INT_TO_JSVAL(GET_ARGNO(pc)); + SAVE_SP(fp); + ok = js_GetArgsProperty(cx, fp, id, &obj, &rval); + if (!ok) + goto out; + if (!obj) { + /* + * If arguments was not overridden by eval('arguments = ...'), + * set obj to the magic cookie respected by JSOP_PUSHOBJ, just + * in case this bytecode is part of an 'arguments[i](j, k)' or + * similar such invocation sequence, where the function that + * is invoked expects its 'this' parameter to be the caller's + * arguments object. + */ + obj = LAZY_ARGS_THISP; + } + PUSH_OPND(rval); + break; + +#undef LAZY_ARGS_THISP + + case JSOP_ARGCNT: + id = (jsid) rt->atomState.lengthAtom; + SAVE_SP(fp); + ok = js_GetArgsProperty(cx, fp, id, &obj, &rval); + if (!ok) + goto out; + PUSH_OPND(rval); + break; + + case JSOP_GETARG: + slot = GET_ARGNO(pc); + JS_ASSERT(slot < fp->fun->nargs); + PUSH_OPND(fp->argv[slot]); + obj = NULL; + break; + + case JSOP_SETARG: + slot = GET_ARGNO(pc); + JS_ASSERT(slot < fp->fun->nargs); + vp = &fp->argv[slot]; + GC_POKE(cx, *vp); + *vp = FETCH_OPND(-1); + obj = NULL; + break; + + case JSOP_GETVAR: + slot = GET_VARNO(pc); + JS_ASSERT(slot < fp->fun->nvars); + PUSH_OPND(fp->vars[slot]); + obj = NULL; + break; + + case JSOP_SETVAR: + slot = GET_VARNO(pc); + JS_ASSERT(slot < fp->fun->nvars); + vp = &fp->vars[slot]; + GC_POKE(cx, *vp); + *vp = FETCH_OPND(-1); + obj = NULL; + break; + + case JSOP_DEFCONST: + case JSOP_DEFVAR: + { + JSBool defined; + + atom = GET_ATOM(cx, script, pc); + obj = fp->varobj; + attrs = JSPROP_ENUMERATE; + if (!(fp->flags & JSFRAME_EVAL)) + attrs |= JSPROP_PERMANENT; + if (op == JSOP_DEFCONST) + attrs |= JSPROP_READONLY; + + /* Lookup id in order to check for redeclaration problems. */ + id = (jsid)atom; + ok = js_CheckRedeclaration(cx, obj, id, attrs, &defined); + if (!ok) + goto out; + + /* Bind a variable only if it's not yet defined. */ + if (!defined) { + ok = OBJ_DEFINE_PROPERTY(cx, obj, id, JSVAL_VOID, NULL, NULL, + attrs, NULL); + if (!ok) + goto out; + } + break; + } + + case JSOP_DEFFUN: + { + uintN flags; + + atom = GET_ATOM(cx, script, pc); + obj = ATOM_TO_OBJECT(atom); + fun = (JSFunction *) JS_GetPrivate(cx, obj); + id = (jsid) fun->atom; + + /* + * We must be at top-level (either outermost block that forms a + * function's body, or a global) scope, not inside an expression + * (JSOP_{ANON,NAMED}FUNOBJ) or compound statement (JSOP_CLOSURE) + * in the same compilation unit (ECMA Program). + * + * However, we could be in a Program being eval'd from inside a + * with statement, so we need to distinguish variables object from + * scope chain head. Hence the two assignments to parent below. + * First we make sure the function object we're defining has the + * right scope chain. Then we define its name in fp->varobj. + * + * If static link is not current scope, clone fun's object to link + * to the current scope via parent. This clause exists to enable + * sharing of compiled functions among multiple equivalent scopes, + * splitting the cost of compilation evenly among the scopes and + * amortizing it over a number of executions. Examples include XUL + * scripts and event handlers shared among Mozilla chrome windows, + * and server-side JS user-defined functions shared among requests. + * + * NB: The Script object exposes compile and exec in the language, + * such that this clause introduces an incompatible change from old + * JS versions that supported Script. Such a JS version supported + * executing a script that defined and called functions scoped by + * the compile-time static link, not by the exec-time scope chain. + * + * We sacrifice compatibility, breaking such scripts, in order to + * promote compile-cost sharing and amortizing, and because Script + * is not and will not be standardized. + */ + parent = fp->scopeChain; + if (OBJ_GET_PARENT(cx, obj) != parent) { + obj = js_CloneFunctionObject(cx, obj, parent); + if (!obj) { + ok = JS_FALSE; + goto out; + } + } + + /* + * ECMA requires functions defined when entering Global code to be + * permanent, and functions defined when entering Eval code to be + * impermanent. + */ + attrs = JSPROP_ENUMERATE; + if (!(fp->flags & JSFRAME_EVAL)) + attrs |= JSPROP_PERMANENT; + + /* + * Load function flags that are also property attributes. Getters + * and setters do not need a slot, their value is stored elsewhere + * in the property itself, not in obj->slots. + */ + flags = fun->flags & (JSFUN_GETTER | JSFUN_SETTER); + if (flags) + attrs |= flags | JSPROP_SHARED; + + /* + * Check for a const property of the same name -- or any kind + * of property if executing with the strict option. We check + * here at runtime as well as at compile-time, to handle eval + * as well as multiple HTML script tags. + */ + parent = fp->varobj; + ok = js_CheckRedeclaration(cx, parent, id, attrs, &cond); + if (!ok) + goto out; + + ok = OBJ_DEFINE_PROPERTY(cx, parent, id, + flags ? JSVAL_VOID : OBJECT_TO_JSVAL(obj), + (flags & JSFUN_GETTER) + ? (JSPropertyOp) obj + : NULL, + (flags & JSFUN_SETTER) + ? (JSPropertyOp) obj + : NULL, + attrs, + NULL); + if (!ok) + goto out; + break; + } + +#if JS_HAS_LEXICAL_CLOSURE + case JSOP_DEFLOCALFUN: + /* + * Define a local function (i.e., one nested at the top level of + * another function), parented by the current scope chain, and + * stored in a local variable slot that the compiler allocated. + * This is an optimization over JSOP_DEFFUN that avoids requiring + * a call object for the outer function's activation. + */ + pc2 = pc; + slot = GET_VARNO(pc2); + pc2 += VARNO_LEN; + atom = GET_ATOM(cx, script, pc2); + obj = ATOM_TO_OBJECT(atom); + fun = (JSFunction *) JS_GetPrivate(cx, obj); + + parent = fp->scopeChain; + if (OBJ_GET_PARENT(cx, obj) != parent) { + obj = js_CloneFunctionObject(cx, obj, parent); + if (!obj) { + ok = JS_FALSE; + goto out; + } + } + fp->vars[slot] = OBJECT_TO_JSVAL(obj); + break; + + case JSOP_ANONFUNOBJ: + /* Push the specified function object literal. */ + atom = GET_ATOM(cx, script, pc); + obj = ATOM_TO_OBJECT(atom); + + /* If re-parenting, push a clone of the function object. */ + parent = fp->scopeChain; + if (OBJ_GET_PARENT(cx, obj) != parent) { + obj = js_CloneFunctionObject(cx, obj, parent); + if (!obj) { + ok = JS_FALSE; + goto out; + } + } + PUSH_OPND(OBJECT_TO_JSVAL(obj)); + break; + + case JSOP_NAMEDFUNOBJ: + /* ECMA ed. 3 FunctionExpression: function Identifier [etc.]. */ + atom = GET_ATOM(cx, script, pc); + rval = ATOM_KEY(atom); + JS_ASSERT(JSVAL_IS_FUNCTION(cx, rval)); + + /* + * 1. Create a new object as if by the expression new Object(). + * 2. Add Result(1) to the front of the scope chain. + * + * Step 2 is achieved by making the new object's parent be the + * current scope chain, and then making the new object the parent + * of the Function object clone. + */ + SAVE_SP(fp); + parent = js_ConstructObject(cx, &js_ObjectClass, NULL, + fp->scopeChain, 0, NULL); + if (!parent) { + ok = JS_FALSE; + goto out; + } + + /* + * 3. Create a new Function object as specified in section 13.2 + * with [parameters and body specified by the function expression + * that was parsed by the compiler into a Function object, and + * saved in the script's atom map]. + */ + obj = js_CloneFunctionObject(cx, JSVAL_TO_OBJECT(rval), parent); + if (!obj) { + ok = JS_FALSE; + goto out; + } + + /* + * 4. Create a property in the object Result(1). The property's + * name is [fun->atom, the identifier parsed by the compiler], + * value is Result(3), and attributes are { DontDelete, ReadOnly }. + */ + fun = (JSFunction *) JS_GetPrivate(cx, obj); + attrs = fun->flags & (JSFUN_GETTER | JSFUN_SETTER); + if (attrs) + attrs |= JSPROP_SHARED; + ok = OBJ_DEFINE_PROPERTY(cx, parent, (jsid)fun->atom, + attrs ? JSVAL_VOID : OBJECT_TO_JSVAL(obj), + (attrs & JSFUN_GETTER) + ? (JSPropertyOp) obj + : NULL, + (attrs & JSFUN_SETTER) + ? (JSPropertyOp) obj + : NULL, + attrs | + JSPROP_ENUMERATE | JSPROP_PERMANENT | + JSPROP_READONLY, + NULL); + if (!ok) { + cx->newborn[GCX_OBJECT] = NULL; + goto out; + } + + /* + * 5. Remove Result(1) from the front of the scope chain [no-op]. + * 6. Return Result(3). + */ + PUSH_OPND(OBJECT_TO_JSVAL(obj)); + break; + + case JSOP_CLOSURE: + /* + * ECMA ed. 3 extension: a named function expression in a compound + * statement (not at the top statement level of global code, or at + * the top level of a function body). + * + * Get immediate operand atom, which is a function object literal. + * From it, get the function to close. + */ + atom = GET_ATOM(cx, script, pc); + JS_ASSERT(JSVAL_IS_FUNCTION(cx, ATOM_KEY(atom))); + obj = ATOM_TO_OBJECT(atom); + + /* + * Clone the function object with the current scope chain as the + * clone's parent. The original function object is the prototype + * of the clone. Do this only if re-parenting; the compiler may + * have seen the right parent already and created a sufficiently + * well-scoped function object. + */ + parent = fp->scopeChain; + if (OBJ_GET_PARENT(cx, obj) != parent) { + obj = js_CloneFunctionObject(cx, obj, parent); + if (!obj) { + ok = JS_FALSE; + goto out; + } + } + + /* + * Make a property in fp->varobj with id fun->atom and value obj, + * unless fun is a getter or setter (in which case, obj is cast to + * a JSPropertyOp and passed accordingly). + */ + fun = (JSFunction *) JS_GetPrivate(cx, obj); + attrs = fun->flags & (JSFUN_GETTER | JSFUN_SETTER); + if (attrs) + attrs |= JSPROP_SHARED; + ok = OBJ_DEFINE_PROPERTY(cx, fp->varobj, (jsid)fun->atom, + attrs ? JSVAL_VOID : OBJECT_TO_JSVAL(obj), + (attrs & JSFUN_GETTER) + ? (JSPropertyOp) obj + : NULL, + (attrs & JSFUN_SETTER) + ? (JSPropertyOp) obj + : NULL, + attrs | JSPROP_ENUMERATE, + NULL); + if (!ok) { + cx->newborn[GCX_OBJECT] = NULL; + goto out; + } + break; +#endif /* JS_HAS_LEXICAL_CLOSURE */ + +#if JS_HAS_GETTER_SETTER + case JSOP_GETTER: + case JSOP_SETTER: + JS_ASSERT(len == 1); + op2 = (JSOp) *++pc; + cs = &js_CodeSpec[op2]; + len = cs->length; + switch (op2) { + case JSOP_SETNAME: + case JSOP_SETPROP: + atom = GET_ATOM(cx, script, pc); + id = (jsid)atom; + i = -1; + rval = FETCH_OPND(i); + goto gs_pop_lval; + + case JSOP_SETELEM: + rval = FETCH_OPND(-1); + i = -2; + FETCH_ELEMENT_ID(i, id); + gs_pop_lval: + lval = FETCH_OPND(i-1); + VALUE_TO_OBJECT(cx, lval, obj); + break; + +#if JS_HAS_INITIALIZERS + case JSOP_INITPROP: + JS_ASSERT(sp - fp->spbase >= 2); + i = -1; + rval = FETCH_OPND(i); + atom = GET_ATOM(cx, script, pc); + id = (jsid)atom; + goto gs_get_lval; + + case JSOP_INITELEM: + JS_ASSERT(sp - fp->spbase >= 3); + rval = FETCH_OPND(-1); + i = -2; + FETCH_ELEMENT_ID(i, id); + gs_get_lval: + lval = FETCH_OPND(i-1); + JS_ASSERT(JSVAL_IS_OBJECT(lval)); + obj = JSVAL_TO_OBJECT(lval); + break; +#endif /* JS_HAS_INITIALIZERS */ + + default: + JS_ASSERT(0); + } + + if (JS_TypeOfValue(cx, rval) != JSTYPE_FUNCTION) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_GETTER_OR_SETTER, + (op == JSOP_GETTER) + ? js_getter_str + : js_setter_str); + ok = JS_FALSE; + goto out; + } + + /* + * Getters and setters are just like watchpoints from an access + * control point of view. + */ + ok = OBJ_CHECK_ACCESS(cx, obj, id, JSACC_WATCH, &rtmp, &attrs); + if (!ok) + goto out; + + if (op == JSOP_GETTER) { + getter = (JSPropertyOp) JSVAL_TO_OBJECT(rval); + setter = NULL; + attrs = JSPROP_GETTER; + } else { + getter = NULL; + setter = (JSPropertyOp) JSVAL_TO_OBJECT(rval); + attrs = JSPROP_SETTER; + } + attrs |= JSPROP_ENUMERATE | JSPROP_SHARED; + + /* Check for a readonly or permanent property of the same name. */ + ok = js_CheckRedeclaration(cx, obj, id, attrs, &cond); + if (!ok) + goto out; + + ok = OBJ_DEFINE_PROPERTY(cx, obj, id, JSVAL_VOID, getter, setter, + attrs, NULL); + if (!ok) + goto out; + + sp += i; + if (cs->ndefs) + STORE_OPND(-1, rval); + break; +#endif /* JS_HAS_GETTER_SETTER */ + +#if JS_HAS_INITIALIZERS + case JSOP_NEWINIT: + argc = 0; + fp->sharpDepth++; + goto do_new; + + case JSOP_ENDINIT: + if (--fp->sharpDepth == 0) + fp->sharpArray = NULL; + + /* Re-set the newborn root to the top of this object tree. */ + JS_ASSERT(sp - fp->spbase >= 1); + lval = FETCH_OPND(-1); + JS_ASSERT(JSVAL_IS_OBJECT(lval)); + cx->newborn[GCX_OBJECT] = JSVAL_TO_GCTHING(lval); + break; + + case JSOP_INITPROP: + /* Pop the property's value into rval. */ + JS_ASSERT(sp - fp->spbase >= 2); + rval = FETCH_OPND(-1); + + /* Get the immediate property name into id. */ + atom = GET_ATOM(cx, script, pc); + id = (jsid)atom; + i = -1; + goto do_init; + + case JSOP_INITELEM: + /* Pop the element's value into rval. */ + JS_ASSERT(sp - fp->spbase >= 3); + rval = FETCH_OPND(-1); + + /* Pop and conditionally atomize the element id. */ + FETCH_ELEMENT_ID(-2, id); + i = -2; + + do_init: + /* Find the object being initialized at top of stack. */ + lval = FETCH_OPND(i-1); + JS_ASSERT(JSVAL_IS_OBJECT(lval)); + obj = JSVAL_TO_OBJECT(lval); + + /* Set the property named by obj[id] to rval. */ + ok = OBJ_SET_PROPERTY(cx, obj, id, &rval); + if (!ok) + goto out; + sp += i; + break; + +#if JS_HAS_SHARP_VARS + case JSOP_DEFSHARP: + obj = fp->sharpArray; + if (!obj) { + obj = js_NewArrayObject(cx, 0, NULL); + if (!obj) { + ok = JS_FALSE; + goto out; + } + fp->sharpArray = obj; + } + i = (jsint) GET_ATOM_INDEX(pc); + id = (jsid) INT_TO_JSVAL(i); + rval = FETCH_OPND(-1); + if (JSVAL_IS_PRIMITIVE(rval)) { + char numBuf[12]; + JS_snprintf(numBuf, sizeof numBuf, "%u", (unsigned) i); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_SHARP_DEF, numBuf); + ok = JS_FALSE; + goto out; + } + ok = OBJ_SET_PROPERTY(cx, obj, id, &rval); + if (!ok) + goto out; + break; + + case JSOP_USESHARP: + i = (jsint) GET_ATOM_INDEX(pc); + id = (jsid) INT_TO_JSVAL(i); + obj = fp->sharpArray; + if (!obj) { + rval = JSVAL_VOID; + } else { + ok = OBJ_GET_PROPERTY(cx, obj, id, &rval); + if (!ok) + goto out; + } + if (!JSVAL_IS_OBJECT(rval)) { + char numBuf[12]; + JS_snprintf(numBuf, sizeof numBuf, "%u", (unsigned) i); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_SHARP_USE, numBuf); + ok = JS_FALSE; + goto out; + } + PUSH_OPND(rval); + break; +#endif /* JS_HAS_SHARP_VARS */ +#endif /* JS_HAS_INITIALIZERS */ + +#if JS_HAS_EXCEPTIONS + /* No-ops for ease of decompilation and jit'ing. */ + case JSOP_TRY: + case JSOP_FINALLY: + break; + + /* Reset the stack to the given depth. */ + case JSOP_SETSP: + i = (jsint) GET_ATOM_INDEX(pc); + JS_ASSERT(i >= 0); + sp = fp->spbase + i; + break; + + case JSOP_GOSUB: + i = PTRDIFF(pc, script->main, jsbytecode) + len; + len = GET_JUMP_OFFSET(pc); + PUSH(INT_TO_JSVAL(i)); + break; + + case JSOP_GOSUBX: + i = PTRDIFF(pc, script->main, jsbytecode) + len; + len = GET_JUMPX_OFFSET(pc); + PUSH(INT_TO_JSVAL(i)); + break; + + case JSOP_RETSUB: + rval = POP(); + JS_ASSERT(JSVAL_IS_INT(rval)); + i = JSVAL_TO_INT(rval); + pc = script->main + i; + len = 0; + break; + + case JSOP_EXCEPTION: + PUSH(cx->exception); + break; + + case JSOP_THROW: + cx->throwing = JS_TRUE; + cx->exception = POP_OPND(); + ok = JS_FALSE; + /* let the code at out try to catch the exception. */ + goto out; + + case JSOP_INITCATCHVAR: + /* Pop the property's value into rval. */ + JS_ASSERT(sp - fp->spbase >= 2); + rval = POP_OPND(); + + /* Get the immediate catch variable name into id. */ + atom = GET_ATOM(cx, script, pc); + id = (jsid)atom; + + /* Find the object being initialized at top of stack. */ + lval = FETCH_OPND(-1); + JS_ASSERT(JSVAL_IS_OBJECT(lval)); + obj = JSVAL_TO_OBJECT(lval); + + /* Define obj[id] to contain rval and to be permanent. */ + ok = OBJ_DEFINE_PROPERTY(cx, obj, id, rval, NULL, NULL, + JSPROP_PERMANENT, NULL); + if (!ok) + goto out; + break; +#endif /* JS_HAS_EXCEPTIONS */ + +#if JS_HAS_INSTANCEOF + case JSOP_INSTANCEOF: + rval = FETCH_OPND(-1); + if (JSVAL_IS_PRIMITIVE(rval)) { + SAVE_SP(fp); + str = js_DecompileValueGenerator(cx, -1, rval, NULL); + if (str) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_INSTANCEOF_RHS, + JS_GetStringBytes(str)); + } + ok = JS_FALSE; + goto out; + } + obj = JSVAL_TO_OBJECT(rval); + lval = FETCH_OPND(-2); + cond = JS_FALSE; + if (obj->map->ops->hasInstance) { + SAVE_SP(fp); + ok = obj->map->ops->hasInstance(cx, obj, lval, &cond); + if (!ok) + goto out; + } + sp--; + STORE_OPND(-1, BOOLEAN_TO_JSVAL(cond)); + break; +#endif /* JS_HAS_INSTANCEOF */ + +#if JS_HAS_DEBUGGER_KEYWORD + case JSOP_DEBUGGER: + { + JSTrapHandler handler = rt->debuggerHandler; + if (handler) { + SAVE_SP(fp); + switch (handler(cx, script, pc, &rval, + rt->debuggerHandlerData)) { + case JSTRAP_ERROR: + ok = JS_FALSE; + goto out; + case JSTRAP_CONTINUE: + break; + case JSTRAP_RETURN: + fp->rval = rval; + goto out; +#if JS_HAS_EXCEPTIONS + case JSTRAP_THROW: + cx->throwing = JS_TRUE; + cx->exception = rval; + ok = JS_FALSE; + goto out; +#endif /* JS_HAS_EXCEPTIONS */ + default:; + } + LOAD_INTERRUPT_HANDLER(rt); + } + break; + } +#endif /* JS_HAS_DEBUGGER_KEYWORD */ + + default: { + char numBuf[12]; + JS_snprintf(numBuf, sizeof numBuf, "%d", op); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_BYTECODE, numBuf); + ok = JS_FALSE; + goto out; + } + } + + advance_pc: + pc += len; + +#ifdef DEBUG + if (tracefp) { + intN ndefs, n; + jsval *siter; + + ndefs = cs->ndefs; + if (ndefs) { + SAVE_SP(fp); + for (n = -ndefs; n < 0; n++) { + str = js_DecompileValueGenerator(cx, n, sp[n], NULL); + if (str) { + fprintf(tracefp, "%s %s", + (n == -ndefs) ? " output:" : ",", + JS_GetStringBytes(str)); + } + } + fprintf(tracefp, " @ %d\n", sp - fp->spbase); + } + fprintf(tracefp, " stack: "); + for (siter = fp->spbase; siter < sp; siter++) { + str = js_ValueToSource(cx, *siter); + fprintf(tracefp, "%s ", + str ? JS_GetStringBytes(str) : ""); + } + fputc('\n', tracefp); + } +#endif + } +out: + +#if JS_HAS_EXCEPTIONS + /* + * Has an exception been raised? + */ + if (!ok && cx->throwing) { + /* + * Call debugger throw hook if set (XXX thread safety?). + */ + JSTrapHandler handler = rt->throwHook; + if (handler) { + SAVE_SP(fp); + switch (handler(cx, script, pc, &rval, rt->throwHookData)) { + case JSTRAP_ERROR: + cx->throwing = JS_FALSE; + goto no_catch; + case JSTRAP_RETURN: + ok = JS_TRUE; + cx->throwing = JS_FALSE; + fp->rval = rval; + goto no_catch; + case JSTRAP_THROW: + cx->exception = rval; + case JSTRAP_CONTINUE: + default:; + } + LOAD_INTERRUPT_HANDLER(rt); + } + + /* + * Look for a try block within this frame that can catch the exception. + */ + SCRIPT_FIND_CATCH_START(script, pc, pc); + if (pc) { + len = 0; + cx->throwing = JS_FALSE; /* caught */ + ok = JS_TRUE; + goto advance_pc; + } + } +no_catch: +#endif + + /* + * Check whether control fell off the end of a lightweight function, or an + * exception thrown under such a function was not caught by it. If so, go + * to the inline code under JSOP_RETURN. + */ + if (inlineCallCount) + goto inline_return; + + /* + * Reset sp before freeing stack slots, because our caller may GC soon. + * Clear spbase to indicate that we've popped the 2 * depth operand slots. + * Restore the previous frame's execution state. + */ + fp->sp = fp->spbase; + fp->spbase = NULL; + js_FreeRawStack(cx, mark); + if (cx->version == currentVersion && currentVersion != originalVersion) + JS_SetVersion(cx, originalVersion); + cx->interpLevel--; + return ok; + +atom_not_defined: + { + const char *printable = js_AtomToPrintableString(cx, atom); + if (printable) + js_ReportIsNotDefined(cx, printable); + ok = JS_FALSE; + goto out; + } +} diff --git a/src/dom/js/jsinterp.h b/src/dom/js/jsinterp.h new file mode 100644 index 000000000..3e0634e4d --- /dev/null +++ b/src/dom/js/jsinterp.h @@ -0,0 +1,292 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsinterp_h___ +#define jsinterp_h___ +/* + * JS interpreter interface. + */ +#include "jsprvtd.h" +#include "jspubtd.h" + +JS_BEGIN_EXTERN_C + +/* + * JS stack frame, allocated on the C stack. + */ +struct JSStackFrame { + JSObject *callobj; /* lazily created Call object */ + JSObject *argsobj; /* lazily created arguments object */ + JSObject *varobj; /* variables object, where vars go */ + JSScript *script; /* script being interpreted */ + JSFunction *fun; /* function being called or null */ + JSObject *thisp; /* "this" pointer if in method */ + uintN argc; /* actual argument count */ + jsval *argv; /* base of argument stack slots */ + jsval rval; /* function return value */ + uintN nvars; /* local variable count */ + jsval *vars; /* base of variable stack slots */ + JSStackFrame *down; /* previous frame */ + void *annotation; /* used by Java security */ + JSObject *scopeChain; /* scope chain */ + jsbytecode *pc; /* program counter */ + jsval *sp; /* stack pointer */ + jsval *spbase; /* operand stack base */ + uintN sharpDepth; /* array/object initializer depth */ + JSObject *sharpArray; /* scope for #n= initializer vars */ + uint32 flags; /* frame flags -- see below */ + JSStackFrame *dormantNext; /* next dormant frame chain */ + JSAtomMap *objAtomMap; /* object atom map, non-null only if we + hit a regexp object literal */ +}; + +typedef struct JSInlineFrame { + JSStackFrame frame; /* base struct */ + void *mark; /* mark before inline frame */ + void *hookData; /* debugger call hook data */ + JSVersion callerVersion; /* dynamic version of calling script */ +} JSInlineFrame; + +/* JS stack frame flags. */ +#define JSFRAME_CONSTRUCTING 0x01 /* frame is for a constructor invocation */ +#define JSFRAME_INTERNAL 0x02 /* internal call, not invoked by a script */ +#define JSFRAME_SKIP_CALLER 0x04 /* skip one link when evaluating f.caller + for this invocation of f */ +#define JSFRAME_ASSIGNING 0x08 /* a complex (not simplex JOF_ASSIGNING) op + is currently assigning to a property */ +#define JSFRAME_DEBUGGER 0x10 /* frame for JS_EvaluateInStackFrame */ +#define JSFRAME_EVAL 0x20 /* frame for obj_eval */ +#define JSFRAME_SPECIAL 0x30 /* special evaluation frame flags */ + +#define JSFRAME_OVERRIDE_SHIFT 24 /* override bit-set params; see jsfun.c */ +#define JSFRAME_OVERRIDE_BITS 8 + +/* + * Property cache for quickened get/set property opcodes. + */ +#define PROPERTY_CACHE_LOG2 10 +#define PROPERTY_CACHE_SIZE JS_BIT(PROPERTY_CACHE_LOG2) +#define PROPERTY_CACHE_MASK JS_BITMASK(PROPERTY_CACHE_LOG2) + +#define PROPERTY_CACHE_HASH(obj, id) \ + ((((jsuword)(obj) >> JSVAL_TAGBITS) ^ (jsuword)(id)) & PROPERTY_CACHE_MASK) + +#ifdef JS_THREADSAFE + +#if HAVE_ATOMIC_DWORD_ACCESS + +#define PCE_LOAD(cache, pce, entry) JS_ATOMIC_DWORD_LOAD(pce, entry) +#define PCE_STORE(cache, pce, entry) JS_ATOMIC_DWORD_STORE(pce, entry) + +#else /* !HAVE_ATOMIC_DWORD_ACCESS */ + +#define JS_PROPERTY_CACHE_METERING 1 + +#define PCE_LOAD(cache, pce, entry) \ + JS_BEGIN_MACRO \ + uint32 prefills_; \ + uint32 fills_ = (cache)->fills; \ + do { \ + /* Load until cache->fills is stable (see FILL macro below). */ \ + prefills_ = fills_; \ + (entry) = *(pce); \ + } while ((fills_ = (cache)->fills) != prefills_); \ + JS_END_MACRO + +#define PCE_STORE(cache, pce, entry) \ + JS_BEGIN_MACRO \ + do { \ + /* Store until no racing collider stores half or all of pce. */ \ + *(pce) = (entry); \ + } while (PCE_OBJECT(*pce) != PCE_OBJECT(entry) || \ + PCE_PROPERTY(*pce) != PCE_PROPERTY(entry)); \ + JS_END_MACRO + +#endif /* !HAVE_ATOMIC_DWORD_ACCESS */ + +#else /* !JS_THREADSAFE */ + +#define PCE_LOAD(cache, pce, entry) ((entry) = *(pce)) +#define PCE_STORE(cache, pce, entry) (*(pce) = (entry)) + +#endif /* !JS_THREADSAFE */ + +typedef union JSPropertyCacheEntry { + struct { + JSObject *object; /* weak link to object */ + JSScopeProperty *property; /* weak link to property */ + } s; +#ifdef HAVE_ATOMIC_DWORD_ACCESS + prdword align; +#endif +} JSPropertyCacheEntry; + +/* These may be called in lvalue or rvalue position. */ +#define PCE_OBJECT(entry) ((entry).s.object) +#define PCE_PROPERTY(entry) ((entry).s.property) + +typedef struct JSPropertyCache { + JSPropertyCacheEntry table[PROPERTY_CACHE_SIZE]; + JSBool empty; + JSBool disabled; +#ifdef JS_PROPERTY_CACHE_METERING + uint32 fills; + uint32 recycles; + uint32 tests; + uint32 misses; + uint32 flushes; +# define PCMETER(x) x +#else +# define PCMETER(x) /* nothing */ +#endif +} JSPropertyCache; + +#define PROPERTY_CACHE_FILL(cache, obj, id, sprop) \ + JS_BEGIN_MACRO \ + JSPropertyCache *cache_ = (cache); \ + if (!cache_->disabled) { \ + uintN hashIndex_ = (uintN) PROPERTY_CACHE_HASH(obj, id); \ + JSPropertyCacheEntry *pce_ = &cache_->table[hashIndex_]; \ + JSPropertyCacheEntry entry_; \ + JSScopeProperty *pce_sprop_; \ + PCE_LOAD(cache_, pce_, entry_); \ + pce_sprop_ = PCE_PROPERTY(entry_); \ + PCMETER(if (pce_sprop_ && pce_sprop_ != sprop) \ + cache_->recycles++); \ + PCE_OBJECT(entry_) = obj; \ + PCE_PROPERTY(entry_) = sprop; \ + cache_->empty = JS_FALSE; \ + PCMETER(cache_->fills++); \ + PCE_STORE(cache_, pce_, entry_); \ + } \ + JS_END_MACRO + +#define PROPERTY_CACHE_TEST(cache, obj, id, sprop) \ + JS_BEGIN_MACRO \ + uintN hashIndex_ = (uintN) PROPERTY_CACHE_HASH(obj, id); \ + JSPropertyCache *cache_ = (cache); \ + JSPropertyCacheEntry *pce_ = &cache_->table[hashIndex_]; \ + JSPropertyCacheEntry entry_; \ + JSScopeProperty *pce_sprop_; \ + PCE_LOAD(cache_, pce_, entry_); \ + pce_sprop_ = PCE_PROPERTY(entry_); \ + PCMETER(cache_->tests++); \ + if (pce_sprop_ && \ + PCE_OBJECT(entry_) == obj && \ + pce_sprop_->id == id) { \ + sprop = pce_sprop_; \ + } else { \ + PCMETER(cache_->misses++); \ + sprop = NULL; \ + } \ + JS_END_MACRO + +extern void +js_FlushPropertyCache(JSContext *cx); + +extern void +js_DisablePropertyCache(JSContext *cx); + +extern void +js_EnablePropertyCache(JSContext *cx); + +extern JS_FRIEND_API(jsval *) +js_AllocStack(JSContext *cx, uintN nslots, void **markp); + +extern JS_FRIEND_API(void) +js_FreeStack(JSContext *cx, void *mark); + +extern JSBool +js_GetArgument(JSContext *cx, JSObject *obj, jsval id, jsval *vp); + +extern JSBool +js_SetArgument(JSContext *cx, JSObject *obj, jsval id, jsval *vp); + +extern JSBool +js_GetLocalVariable(JSContext *cx, JSObject *obj, jsval id, jsval *vp); + +extern JSBool +js_SetLocalVariable(JSContext *cx, JSObject *obj, jsval id, jsval *vp); + +/* + * NB: js_Invoke requires that cx is currently running JS (i.e., that cx->fp + * is non-null). + */ +extern JS_FRIEND_API(JSBool) +js_Invoke(JSContext *cx, uintN argc, uintN flags); + +/* + * Consolidated js_Invoke flags simply rename the low JSFRAME_* flags. + */ +#define JSINVOKE_CONSTRUCT JSFRAME_CONSTRUCTING +#define JSINVOKE_INTERNAL JSFRAME_INTERNAL +#define JSINVOKE_SKIP_CALLER JSFRAME_SKIP_CALLER + +/* + * "Internal" calls may come from C or C++ code using a JSContext on which no + * JS is running (!cx->fp), so they may need to push a dummy JSStackFrame. + */ +#define js_InternalCall(cx,obj,fval,argc,argv,rval) \ + js_InternalInvoke(cx, obj, fval, 0, argc, argv, rval) + +#define js_InternalConstruct(cx,obj,fval,argc,argv,rval) \ + js_InternalInvoke(cx, obj, fval, JSINVOKE_CONSTRUCT, argc, argv, rval) + +extern JSBool +js_InternalInvoke(JSContext *cx, JSObject *obj, jsval fval, uintN flags, + uintN argc, jsval *argv, jsval *rval); + +extern JSBool +js_InternalGetOrSet(JSContext *cx, JSObject *obj, jsid id, jsval fval, + JSAccessMode mode, uintN argc, jsval *argv, jsval *rval); + +extern JSBool +js_Execute(JSContext *cx, JSObject *chain, JSScript *script, + JSStackFrame *down, uintN flags, jsval *result); + +extern JSBool +js_CheckRedeclaration(JSContext *cx, JSObject *obj, jsid id, uintN attrs, + JSBool *foundp); + +extern JSBool +js_Interpret(JSContext *cx, jsval *result); + +JS_END_EXTERN_C + +#endif /* jsinterp_h___ */ diff --git a/src/dom/js/jslibmath.h b/src/dom/js/jslibmath.h new file mode 100644 index 000000000..7cbcf767a --- /dev/null +++ b/src/dom/js/jslibmath.h @@ -0,0 +1,290 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * IBM Corp. + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * By default all math calls go to fdlibm. The defines for each platform + * remap the math calls to native routines. + */ + +#ifndef _LIBMATH_H +#define _LIBMATH_H + +#include +#include "jsconfig.h" + +/* + * Define which platforms on which to use fdlibm. Not used + * by default since there can be problems with endian-ness and such. + */ + +#if defined(_WIN32) && !defined(__MWERKS__) +#define JS_USE_FDLIBM_MATH 1 + +#elif defined(SUNOS4) +#define JS_USE_FDLIBM_MATH 1 + +#elif defined(IRIX) +#define JS_USE_FDLIBM_MATH 1 + +#elif defined(SOLARIS) +#define JS_USE_FDLIBM_MATH 1 + +#elif defined(HPUX) +#define JS_USE_FDLIBM_MATH 1 + +#elif defined(linux) +#define JS_USE_FDLIBM_MATH 1 + +#elif defined(OSF1) +/* Want to use some fdlibm functions but fdlibm broken on OSF1/alpha. */ +#define JS_USE_FDLIBM_MATH 0 + +#elif defined(AIX) +#define JS_USE_FDLIBM_MATH 1 + +#else +#define JS_USE_FDLIBM_MATH 0 +#endif + +#if !JS_USE_FDLIBM_MATH + +/* + * Use system provided math routines. + */ + +#define fd_acos acos +#define fd_asin asin +#define fd_atan atan +#define fd_atan2 atan2 +#define fd_ceil ceil +#define fd_copysign copysign +#define fd_cos cos +#define fd_exp exp +#define fd_fabs fabs +#define fd_floor floor +#define fd_fmod fmod +#define fd_log log +#define fd_pow pow +#define fd_sin sin +#define fd_sqrt sqrt +#define fd_tan tan + +#else + +/* + * Use math routines in fdlibm. + */ + +#undef __P +#ifdef __STDC__ +#define __P(p) p +#else +#define __P(p) () +#endif + +#if defined _WIN32 || defined SUNOS4 + +#define fd_acos acos +#define fd_asin asin +#define fd_atan atan +#define fd_cos cos +#define fd_sin sin +#define fd_tan tan +#define fd_exp exp +#define fd_log log +#define fd_sqrt sqrt +#define fd_ceil ceil +#define fd_fabs fabs +#define fd_floor floor +#define fd_fmod fmod + +extern double fd_atan2 __P((double, double)); +extern double fd_copysign __P((double, double)); +extern double fd_pow __P((double, double)); + +#elif defined IRIX + +#define fd_acos acos +#define fd_asin asin +#define fd_atan atan +#define fd_exp exp +#define fd_log log +#define fd_log10 log10 +#define fd_sqrt sqrt +#define fd_fabs fabs +#define fd_floor floor +#define fd_fmod fmod + +extern double fd_cos __P((double)); +extern double fd_sin __P((double)); +extern double fd_tan __P((double)); +extern double fd_atan2 __P((double, double)); +extern double fd_pow __P((double, double)); +extern double fd_ceil __P((double)); +extern double fd_copysign __P((double, double)); + +#elif defined SOLARIS + +#define fd_atan atan +#define fd_cos cos +#define fd_sin sin +#define fd_tan tan +#define fd_exp exp +#define fd_sqrt sqrt +#define fd_ceil ceil +#define fd_fabs fabs +#define fd_floor floor +#define fd_fmod fmod + +extern double fd_acos __P((double)); +extern double fd_asin __P((double)); +extern double fd_log __P((double)); +extern double fd_atan2 __P((double, double)); +extern double fd_pow __P((double, double)); +extern double fd_copysign __P((double, double)); + +#elif defined HPUX + +#define fd_cos cos +#define fd_sin sin +#define fd_exp exp +#define fd_sqrt sqrt +#define fd_fabs fabs +#define fd_floor floor +#define fd_fmod fmod + +extern double fd_ceil __P((double)); +extern double fd_acos __P((double)); +extern double fd_log __P((double)); +extern double fd_atan2 __P((double, double)); +extern double fd_tan __P((double)); +extern double fd_pow __P((double, double)); +extern double fd_asin __P((double)); +extern double fd_atan __P((double)); +extern double fd_copysign __P((double, double)); + +#elif defined(linux) + +#define fd_atan atan +#define fd_atan2 atan2 +#define fd_ceil ceil +#define fd_cos cos +#define fd_fabs fabs +#define fd_floor floor +#define fd_fmod fmod +#define fd_sin sin +#define fd_sqrt sqrt +#define fd_tan tan +#define fd_copysign copysign + +extern double fd_asin __P((double)); +extern double fd_acos __P((double)); +extern double fd_exp __P((double)); +extern double fd_log __P((double)); +extern double fd_pow __P((double, double)); + +#elif defined(OSF1) + +#define fd_acos acos +#define fd_asin asin +#define fd_atan atan +#define fd_copysign copysign +#define fd_cos cos +#define fd_exp exp +#define fd_fabs fabs +#define fd_fmod fmod +#define fd_sin sin +#define fd_sqrt sqrt +#define fd_tan tan + +extern double fd_atan2 __P((double, double)); +extern double fd_ceil __P((double)); +extern double fd_floor __P((double)); +extern double fd_log __P((double)); +extern double fd_pow __P((double, double)); + +#elif defined(AIX) + +#define fd_acos acos +#define fd_asin asin +#define fd_atan2 atan2 +#define fd_copysign copysign +#define fd_cos cos +#define fd_exp exp +#define fd_fabs fabs +#define fd_floor floor +#define fd_fmod fmod +#define fd_log log +#define fd_sin sin +#define fd_sqrt sqrt + +extern double fd_atan __P((double)); +extern double fd_ceil __P((double)); +extern double fd_pow __P((double,double)); +extern double fd_tan __P((double)); + +#else /* other platform.. generic paranoid slow fdlibm */ + +extern double fd_acos __P((double)); +extern double fd_asin __P((double)); +extern double fd_atan __P((double)); +extern double fd_cos __P((double)); +extern double fd_sin __P((double)); +extern double fd_tan __P((double)); + +extern double fd_exp __P((double)); +extern double fd_log __P((double)); +extern double fd_sqrt __P((double)); + +extern double fd_ceil __P((double)); +extern double fd_fabs __P((double)); +extern double fd_floor __P((double)); +extern double fd_fmod __P((double, double)); + +extern double fd_atan2 __P((double, double)); +extern double fd_pow __P((double, double)); +extern double fd_copysign __P((double, double)); + +#endif + +#endif /* JS_USE_FDLIBM_MATH */ + +#endif /* _LIBMATH_H */ + diff --git a/src/dom/js/jslock.c b/src/dom/js/jslock.c new file mode 100644 index 000000000..9709c8cc0 --- /dev/null +++ b/src/dom/js/jslock.c @@ -0,0 +1,1241 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifdef JS_THREADSAFE + +/* + * JS locking stubs. + */ +#include "jsstddef.h" +#include +#include "jspubtd.h" +#include "prthread.h" +#include "jsutil.h" /* Added by JSIFY */ +#include "jstypes.h" +#include "jsbit.h" +#include "jscntxt.h" +#include "jsdtoa.h" +#include "jsgc.h" +#include "jslock.h" +#include "jsscope.h" +#include "jsstr.h" + +#define ReadWord(W) (W) + +#ifndef NSPR_LOCK + +#include + +static PRLock **global_locks; +static uint32 global_lock_count = 1; +static uint32 global_locks_log2 = 0; +static uint32 global_locks_mask = 0; + +#define GLOBAL_LOCK_INDEX(id) (((uint32)(id) >> 2) & global_locks_mask) + +static void +js_LockGlobal(void *id) +{ + uint32 i = GLOBAL_LOCK_INDEX(id); + PR_Lock(global_locks[i]); +} + +static void +js_UnlockGlobal(void *id) +{ + uint32 i = GLOBAL_LOCK_INDEX(id); + PR_Unlock(global_locks[i]); +} + +/* Exclude Alpha NT. */ +#if defined(_WIN32) && defined(_M_IX86) +#pragma warning( disable : 4035 ) + +static JS_INLINE int +js_CompareAndSwap(jsword *w, jsword ov, jsword nv) +{ + __asm { + mov eax, ov + mov ecx, nv + mov ebx, w + lock cmpxchg [ebx], ecx + sete al + and eax, 1h + } +} + +#elif defined(__GNUC__) && defined(__i386__) + +/* Note: This fails on 386 cpus, cmpxchgl is a >= 486 instruction */ +static JS_INLINE int +js_CompareAndSwap(jsword *w, jsword ov, jsword nv) +{ + unsigned int res; + + __asm__ __volatile__ ( + "lock\n" + "cmpxchgl %2, (%1)\n" + "sete %%al\n" + "andl $1, %%eax\n" + : "=a" (res) + : "r" (w), "r" (nv), "a" (ov) + : "cc", "memory"); + return (int)res; +} + +#elif defined(SOLARIS) && defined(sparc) && defined(ULTRA_SPARC) + +static JS_INLINE int +js_CompareAndSwap(jsword *w, jsword ov, jsword nv) +{ +#if defined(__GNUC__) + unsigned int res; + JS_ASSERT(ov != nv); + asm volatile ("\ +stbar\n\ +cas [%1],%2,%3\n\ +cmp %2,%3\n\ +be,a 1f\n\ +mov 1,%0\n\ +mov 0,%0\n\ +1:" + : "=r" (res) + : "r" (w), "r" (ov), "r" (nv)); + return (int)res; +#else /* !__GNUC__ */ + extern int compare_and_swap(jsword*, jsword, jsword); + JS_ASSERT(ov != nv); + return compare_and_swap(w, ov, nv); +#endif +} + +#elif defined(AIX) + +#include + +static JS_INLINE int +js_CompareAndSwap(jsword *w, jsword ov, jsword nv) +{ + return !_check_lock((atomic_p)w, ov, nv); +} + +#else + +#error "Define NSPR_LOCK if your platform lacks a compare-and-swap instruction." + +#endif /* arch-tests */ + +#endif /* !NSPR_LOCK */ + +jsword +js_CurrentThreadId() +{ + return CurrentThreadId(); +} + +void +js_InitLock(JSThinLock *tl) +{ +#ifdef NSPR_LOCK + tl->owner = 0; + tl->fat = (JSFatLock*)JS_NEW_LOCK(); +#else + memset(tl, 0, sizeof(JSThinLock)); +#endif +} + +void +js_FinishLock(JSThinLock *tl) +{ +#ifdef NSPR_LOCK + tl->owner = 0xdeadbeef; + if (tl->fat) + JS_DESTROY_LOCK(((JSLock*)tl->fat)); +#else + JS_ASSERT(tl->owner == 0); + JS_ASSERT(tl->fat == NULL); +#endif +} + +static void js_Dequeue(JSThinLock *); + +#ifdef DEBUG_SCOPE_COUNT + +#include +#include "jsdhash.h" + +static FILE *logfp; +static JSDHashTable logtbl; + +typedef struct logentry { + JSDHashEntryStub stub; + char op; + const char *file; + int line; +} logentry; + +static void +logit(JSScope *scope, char op, const char *file, int line) +{ + logentry *entry; + + if (!logfp) { + logfp = fopen("/tmp/scope.log", "w"); + if (!logfp) + return; + setvbuf(logfp, NULL, _IONBF, 0); + } + fprintf(logfp, "%p %c %s %d\n", scope, op, file, line); + + if (!logtbl.entryStore && + !JS_DHashTableInit(&logtbl, JS_DHashGetStubOps(), NULL, + sizeof(logentry), 100)) { + return; + } + entry = (logentry *) JS_DHashTableOperate(&logtbl, scope, JS_DHASH_ADD); + if (!entry) + return; + entry->stub.key = scope; + entry->op = op; + entry->file = file; + entry->line = line; +} + +void +js_unlog_scope(JSScope *scope) +{ + if (!logtbl.entryStore) + return; + (void) JS_DHashTableOperate(&logtbl, scope, JS_DHASH_REMOVE); +} + +# define LOGIT(scope,op) logit(scope, op, __FILE__, __LINE__) + +#else + +# define LOGIT(scope,op) /* nothing */ + +#endif /* DEBUG_SCOPE_COUNT */ + +/* + * Return true if scope's ownercx, or the ownercx of a single-threaded scope + * for which ownercx is waiting to become multi-threaded and shared, is cx. + * That condition implies deadlock in ClaimScope if cx's thread were to wait + * to share scope. + * + * (i) rt->gcLock held + */ +static JSBool +WillDeadlock(JSScope *scope, JSContext *cx) +{ + JSContext *ownercx; + + do { + ownercx = scope->ownercx; + if (ownercx == cx) { + JS_RUNTIME_METER(cx->runtime, deadlocksAvoided); + return JS_TRUE; + } + } while (ownercx && (scope = ownercx->scopeToShare) != NULL); + return JS_FALSE; +} + +/* + * Make scope multi-threaded, i.e. share its ownership among contexts in rt + * using a "thin" or (if necessary due to contention) "fat" lock. Called only + * from ClaimScope, immediately below, when we detect deadlock were we to wait + * for scope's lock, because its ownercx is waiting on a scope owned by the + * calling cx. + * + * (i) rt->gcLock held + */ +static void +ShareScope(JSRuntime *rt, JSScope *scope) +{ + JSScope **todop; + + if (scope->u.link) { + for (todop = &rt->scopeSharingTodo; *todop != scope; + todop = &(*todop)->u.link) { + JS_ASSERT(*todop != NO_SCOPE_SHARING_TODO); + } + *todop = scope->u.link; + scope->u.link = NULL; /* null u.link for sanity ASAP */ + JS_NOTIFY_ALL_CONDVAR(rt->scopeSharingDone); + } + js_InitLock(&scope->lock); + if (scope == rt->setSlotScope) { + /* + * Nesting locks on another thread that's using scope->ownercx: give + * the held lock a reentrancy count of 1 and set its lock.owner field + * directly (no compare-and-swap needed while scope->ownercx is still + * non-null). See below in ClaimScope, before the ShareScope call, + * for more on why this is necessary. + * + * If NSPR_LOCK is defined, we cannot deadlock holding rt->gcLock and + * acquiring scope->lock.fat here, against another thread holding that + * fat lock and trying to grab rt->gcLock. This is because no other + * thread can attempt to acquire scope->lock.fat until scope->ownercx + * is null *and* our thread has released rt->gcLock, which interlocks + * scope->ownercx's transition to null against tests of that member + * in ClaimScope. + */ + scope->lock.owner = scope->ownercx->thread; +#ifdef NSPR_LOCK + JS_ACQUIRE_LOCK((JSLock*)scope->lock.fat); +#endif + scope->u.count = 1; + } else { + scope->u.count = 0; + } + js_FinishSharingScope(rt, scope); +} + +/* + * js_FinishSharingScope is the tail part of ShareScope, split out to become a + * subroutine of JS_EndRequest too. The bulk of the work here involves making + * mutable strings in the scope's object's slots be immutable. We have to do + * this because such strings will soon be available to multiple threads, so + * their buffers can't be realloc'd any longer in js_ConcatStrings, and their + * members can't be modified by js_ConcatStrings, js_MinimizeDependentStrings, + * or js_UndependString. + * + * The last bit of work done by js_FinishSharingScope nulls scope->ownercx and + * updates rt->sharedScopes. + */ +#define MAKE_STRING_IMMUTABLE(rt, v, vp) \ + JS_BEGIN_MACRO \ + JSString *str_ = JSVAL_TO_STRING(v); \ + uint8 *flagp_ = js_GetGCThingFlags(str_); \ + if (*flagp_ & GCF_MUTABLE) { \ + if (JSSTRING_IS_DEPENDENT(str_) && \ + !js_UndependString(NULL, str_)) { \ + JS_RUNTIME_METER(rt, badUndependStrings); \ + *vp = JSVAL_VOID; \ + } else { \ + *flagp_ &= ~GCF_MUTABLE; \ + } \ + } \ + JS_END_MACRO + +void +js_FinishSharingScope(JSRuntime *rt, JSScope *scope) +{ + JSObject *obj; + uint32 nslots; + jsval v, *vp, *end; + + obj = scope->object; + nslots = JS_MIN(obj->map->freeslot, obj->map->nslots); + for (vp = obj->slots, end = vp + nslots; vp < end; vp++) { + v = *vp; + if (JSVAL_IS_STRING(v)) + MAKE_STRING_IMMUTABLE(rt, v, vp); + } + + scope->ownercx = NULL; /* NB: set last, after lock init */ + JS_RUNTIME_METER(rt, sharedScopes); +} + +/* + * Given a scope with apparently non-null ownercx different from cx, try to + * set ownercx to cx, claiming exclusive (single-threaded) ownership of scope. + * If we claim ownership, return true. Otherwise, we wait for ownercx to be + * set to null (indicating that scope is multi-threaded); or if waiting would + * deadlock, we set ownercx to null ourselves via ShareScope. In any case, + * once ownercx is null we return false. + */ +static JSBool +ClaimScope(JSScope *scope, JSContext *cx) +{ + JSRuntime *rt; + JSContext *ownercx; + jsrefcount saveDepth; + PRStatus stat; + + rt = cx->runtime; + JS_RUNTIME_METER(rt, claimAttempts); + JS_LOCK_GC(rt); + + /* Reload in case ownercx went away while we blocked on the lock. */ + while ((ownercx = scope->ownercx) != NULL) { + /* + * Avoid selflock if ownercx is dead, or is not running a request, or + * has the same thread as cx. Set scope->ownercx to cx so that the + * matching JS_UNLOCK_SCOPE or JS_UNLOCK_OBJ macro call will take the + * fast path around the corresponding js_UnlockScope or js_UnlockObj + * function call. + * + * If scope->u.link is non-null, scope has already been inserted on + * the rt->scopeSharingTodo list, because another thread's context + * already wanted to lock scope while ownercx was running a request. + * We can't claim any scope whose u.link is non-null at this point, + * even if ownercx->requestDepth is 0 (see below where we suspend our + * request before waiting on rt->scopeSharingDone). + */ + if (!scope->u.link && + (!js_ValidContextPointer(rt, ownercx) || + !ownercx->requestDepth || + ownercx->thread == cx->thread)) { + JS_ASSERT(scope->u.count == 0); + scope->ownercx = cx; + JS_UNLOCK_GC(rt); + JS_RUNTIME_METER(rt, claimedScopes); + return JS_TRUE; + } + + /* + * Avoid deadlock if scope's owner context is waiting on a scope that + * we own, by revoking scope's ownership. This approach to deadlock + * avoidance works because the engine never nests scope locks, except + * for the notable case of js_SetProtoOrParent (see jsobj.c). + * + * If cx could hold locks on ownercx->scopeToShare, or if ownercx + * could hold locks on scope, we would need to keep reentrancy counts + * for all such "flyweight" (ownercx != NULL) locks, so that control + * would unwind properly once these locks became "thin" or "fat". + * Apart from the js_SetProtoOrParent exception, the engine promotes + * a scope from exclusive to shared access only when locking, never + * when holding or unlocking. + * + * If ownercx's thread is calling js_SetProtoOrParent, trying to lock + * the inner scope (the scope of the object being set as the prototype + * of the outer object), ShareScope will find the outer object's scope + * at rt->setSlotScope. If it's the same as scope, we give it a lock + * held by ownercx's thread with reentrancy count of 1, then we return + * here and break. After that we unwind to js_[GS]etSlotThreadSafe or + * js_LockScope (our caller), where we wait on the newly-fattened lock + * until ownercx's thread unwinds from js_SetProtoOrParent. + * + * Avoid deadlock before any of this scope/context cycle detection if + * cx is on the active GC's thread, because in that case, no requests + * will run until the GC completes. Any scope wanted by the GC (from + * a finalizer) that can't be claimed must be slated for sharing. + */ + if (rt->gcThread == cx->thread || + (ownercx->scopeToShare && + WillDeadlock(ownercx->scopeToShare, cx))) { + ShareScope(rt, scope); + break; + } + + /* + * Thanks to the non-zero NO_SCOPE_SHARING_TODO link terminator, we + * can decide whether scope is on rt->scopeSharingTodo with a single + * non-null test, and avoid double-insertion bugs. + */ + if (!scope->u.link) { + scope->u.link = rt->scopeSharingTodo; + rt->scopeSharingTodo = scope; + js_HoldObjectMap(cx, &scope->map); + } + + /* + * Inline JS_SuspendRequest before we wait on rt->scopeSharingDone, + * saving and clearing cx->requestDepth so we don't deadlock if the + * GC needs to run on ownercx. + * + * Unlike JS_SuspendRequest and JS_EndRequest, we must take care not + * to decrement rt->requestCount if cx is active on the GC's thread, + * because the GC has already reduced rt->requestCount to exclude all + * such such contexts. + */ + saveDepth = cx->requestDepth; + if (saveDepth) { + cx->requestDepth = 0; + if (rt->gcThread != cx->thread) { + JS_ASSERT(rt->requestCount > 0); + rt->requestCount--; + if (rt->requestCount == 0) + JS_NOTIFY_REQUEST_DONE(rt); + } + } + + /* + * We know that some other thread's context owns scope, which is now + * linked onto rt->scopeSharingTodo, awaiting the end of that other + * thread's request. So it is safe to wait on rt->scopeSharingDone. + */ + cx->scopeToShare = scope; + stat = PR_WaitCondVar(rt->scopeSharingDone, PR_INTERVAL_NO_TIMEOUT); + JS_ASSERT(stat != PR_FAILURE); + + /* + * Inline JS_ResumeRequest after waiting on rt->scopeSharingDone, + * restoring cx->requestDepth. Same note as above for the inlined, + * specialized JS_SuspendRequest code: beware rt->gcThread. + */ + if (saveDepth) { + if (rt->gcThread != cx->thread) { + while (rt->gcLevel > 0) + JS_AWAIT_GC_DONE(rt); + rt->requestCount++; + } + cx->requestDepth = saveDepth; + } + + /* + * Don't clear cx->scopeToShare until after we're through waiting on + * all condition variables protected by rt->gcLock -- that includes + * rt->scopeSharingDone *and* rt->gcDone (hidden in JS_AWAIT_GC_DONE, + * in the inlined JS_ResumeRequest code immediately above). + * + * Otherwise, the GC could easily deadlock with another thread that + * owns a scope wanted by a finalizer. By keeping cx->scopeToShare + * set till here, we ensure that such deadlocks are detected, which + * results in the finalized object's scope being shared (it must, of + * course, have other, live objects sharing it). + */ + cx->scopeToShare = NULL; + } + + JS_UNLOCK_GC(rt); + return JS_FALSE; +} + +/* Exported to js.c, which calls it via OBJ_GET_* and JSVAL_IS_* macros. */ +JS_FRIEND_API(jsval) +js_GetSlotThreadSafe(JSContext *cx, JSObject *obj, uint32 slot) +{ + jsval v; + JSScope *scope; +#ifndef NSPR_LOCK + JSThinLock *tl; + jsword me; +#endif + + /* + * We handle non-native objects via JSObjectOps.getRequiredSlot, treating + * all slots starting from 0 as required slots. A property definition or + * some prior arrangement must have allocated slot. + * + * Note once again (see jspubtd.h, before JSGetRequiredSlotOp's typedef) + * the crucial distinction between a |required slot number| that's passed + * to the get/setRequiredSlot JSObjectOps, and a |reserved slot index| + * passed to the JS_Get/SetReservedSlot APIs. + */ + if (!OBJ_IS_NATIVE(obj)) + return OBJ_GET_REQUIRED_SLOT(cx, obj, slot); + + /* + * Native object locking is inlined here to optimize the single-threaded + * and contention-free multi-threaded cases. + */ + scope = OBJ_SCOPE(obj); + JS_ASSERT(scope->ownercx != cx); + JS_ASSERT(obj->slots && slot < obj->map->freeslot); + + /* + * Avoid locking if called from the GC (see GC_AWARE_GET_SLOT in jsobj.h). + * Also avoid locking an object owning a sealed scope. If neither of those + * special cases applies, try to claim scope's flyweight lock from whatever + * context may have had it in an earlier request. + */ + if (CX_THREAD_IS_RUNNING_GC(cx) || + (SCOPE_IS_SEALED(scope) && scope->object == obj) || + (scope->ownercx && ClaimScope(scope, cx))) { + return obj->slots[slot]; + } + +#ifndef NSPR_LOCK + tl = &scope->lock; + me = cx->thread; + JS_ASSERT(me == CurrentThreadId()); + if (js_CompareAndSwap(&tl->owner, 0, me)) { + /* + * Got the lock with one compare-and-swap. Even so, someone else may + * have mutated obj so it now has its own scope and lock, which would + * require either a restart from the top of this routine, or a thin + * lock release followed by fat lock acquisition. + */ + if (scope == OBJ_SCOPE(obj)) { + v = obj->slots[slot]; + if (!js_CompareAndSwap(&tl->owner, me, 0)) { + /* Assert that scope locks never revert to flyweight. */ + JS_ASSERT(scope->ownercx != cx); + LOGIT(scope, '1'); + scope->u.count = 1; + js_UnlockObj(cx, obj); + } + return v; + } + if (!js_CompareAndSwap(&tl->owner, me, 0)) + js_Dequeue(tl); + } + else if (Thin_RemoveWait(ReadWord(tl->owner)) == me) { + return obj->slots[slot]; + } +#endif + + js_LockObj(cx, obj); + v = obj->slots[slot]; + + /* + * Test whether cx took ownership of obj's scope during js_LockObj. + * + * This does not mean that a given scope reverted to flyweight from "thin" + * or "fat" -- it does mean that obj's map pointer changed due to another + * thread setting a property, requiring obj to cease sharing a prototype + * object's scope (whose lock was not flyweight, else we wouldn't be here + * in the first place!). + */ + scope = OBJ_SCOPE(obj); + if (scope->ownercx != cx) + js_UnlockScope(cx, scope); + return v; +} + +void +js_SetSlotThreadSafe(JSContext *cx, JSObject *obj, uint32 slot, jsval v) +{ + JSScope *scope; +#ifndef NSPR_LOCK + JSThinLock *tl; + jsword me; +#endif + + /* Any string stored in a thread-safe object must be immutable. */ + if (JSVAL_IS_STRING(v)) + MAKE_STRING_IMMUTABLE(cx->runtime, v, &v); + + /* + * We handle non-native objects via JSObjectOps.setRequiredSlot, as above + * for the Get case. + */ + if (!OBJ_IS_NATIVE(obj)) { + OBJ_SET_REQUIRED_SLOT(cx, obj, slot, v); + return; + } + + /* + * Native object locking is inlined here to optimize the single-threaded + * and contention-free multi-threaded cases. + */ + scope = OBJ_SCOPE(obj); + JS_ASSERT(scope->ownercx != cx); + JS_ASSERT(obj->slots && slot < obj->map->freeslot); + + /* + * Avoid locking if called from the GC (see GC_AWARE_GET_SLOT in jsobj.h). + * Also avoid locking an object owning a sealed scope. If neither of those + * special cases applies, try to claim scope's flyweight lock from whatever + * context may have had it in an earlier request. + */ + if (CX_THREAD_IS_RUNNING_GC(cx) || + (SCOPE_IS_SEALED(scope) && scope->object == obj) || + (scope->ownercx && ClaimScope(scope, cx))) { + obj->slots[slot] = v; + return; + } + +#ifndef NSPR_LOCK + tl = &scope->lock; + me = cx->thread; + JS_ASSERT(me == CurrentThreadId()); + if (js_CompareAndSwap(&tl->owner, 0, me)) { + if (scope == OBJ_SCOPE(obj)) { + obj->slots[slot] = v; + if (!js_CompareAndSwap(&tl->owner, me, 0)) { + /* Assert that scope locks never revert to flyweight. */ + JS_ASSERT(scope->ownercx != cx); + LOGIT(scope, '1'); + scope->u.count = 1; + js_UnlockObj(cx, obj); + } + return; + } + if (!js_CompareAndSwap(&tl->owner, me, 0)) + js_Dequeue(tl); + } + else if (Thin_RemoveWait(ReadWord(tl->owner)) == me) { + obj->slots[slot] = v; + return; + } +#endif + + js_LockObj(cx, obj); + obj->slots[slot] = v; + + /* + * Same drill as above, in js_GetSlotThreadSafe. Note that we cannot + * assume obj has its own mutable scope (where scope->object == obj) yet, + * because OBJ_SET_SLOT is called for the "universal", common slots such + * as JSSLOT_PROTO and JSSLOT_PARENT, without a prior js_GetMutableScope. + * See also the JSPROP_SHARED attribute and its usage. + */ + scope = OBJ_SCOPE(obj); + if (scope->ownercx != cx) + js_UnlockScope(cx, scope); +} + +#ifndef NSPR_LOCK + +static JSFatLock * +NewFatlock() +{ + JSFatLock *fl = (JSFatLock *)malloc(sizeof(JSFatLock)); /* for now */ + if (!fl) return NULL; + fl->susp = 0; + fl->next = NULL; + fl->prevp = NULL; + fl->slock = PR_NewLock(); + fl->svar = PR_NewCondVar(fl->slock); + return fl; +} + +static void +DestroyFatlock(JSFatLock *fl) +{ + PR_DestroyLock(fl->slock); + PR_DestroyCondVar(fl->svar); + free(fl); +} + +static JSFatLock * +ListOfFatlocks(int listc) +{ + JSFatLock *m; + JSFatLock *m0; + int i; + + JS_ASSERT(listc>0); + m0 = m = NewFatlock(); + for (i=1; inext = NewFatlock(); + m = m->next; + } + return m0; +} + +static void +DeleteListOfFatlocks(JSFatLock *m) +{ + JSFatLock *m0; + for (; m; m=m0) { + m0 = m->next; + DestroyFatlock(m); + } +} + +static JSFatLockTable *fl_list_table = NULL; +static uint32 fl_list_table_len = 0; +static uint32 fl_list_chunk_len = 0; + +static JSFatLock * +GetFatlock(void *id) +{ + JSFatLock *m; + + uint32 i = GLOBAL_LOCK_INDEX(id); + if (fl_list_table[i].free == NULL) { +#ifdef DEBUG + if (fl_list_table[i].taken) + printf("Ran out of fat locks!\n"); +#endif + fl_list_table[i].free = ListOfFatlocks(fl_list_chunk_len); + } + m = fl_list_table[i].free; + fl_list_table[i].free = m->next; + m->susp = 0; + m->next = fl_list_table[i].taken; + m->prevp = &fl_list_table[i].taken; + if (fl_list_table[i].taken) + fl_list_table[i].taken->prevp = &m->next; + fl_list_table[i].taken = m; + return m; +} + +static void +PutFatlock(JSFatLock *m, void *id) +{ + uint32 i; + if (m == NULL) + return; + + /* Unlink m from fl_list_table[i].taken. */ + *m->prevp = m->next; + if (m->next) + m->next->prevp = m->prevp; + + /* Insert m in fl_list_table[i].free. */ + i = GLOBAL_LOCK_INDEX(id); + m->next = fl_list_table[i].free; + fl_list_table[i].free = m; +} + +#endif /* !NSPR_LOCK */ + +JSBool +js_SetupLocks(int listc, int globc) +{ +#ifndef NSPR_LOCK + uint32 i; + + if (global_locks) + return JS_TRUE; +#ifdef DEBUG + if (listc > 10000 || listc < 0) /* listc == fat lock list chunk length */ + printf("Bad number %d in js_SetupLocks()!\n", listc); + if (globc > 100 || globc < 0) /* globc == number of global locks */ + printf("Bad number %d in js_SetupLocks()!\n", listc); +#endif + global_locks_log2 = JS_CeilingLog2(globc); + global_locks_mask = JS_BITMASK(global_locks_log2); + global_lock_count = JS_BIT(global_locks_log2); + global_locks = (PRLock **) malloc(global_lock_count * sizeof(PRLock*)); + if (!global_locks) + return JS_FALSE; + for (i = 0; i < global_lock_count; i++) { + global_locks[i] = PR_NewLock(); + if (!global_locks[i]) { + global_lock_count = i; + js_CleanupLocks(); + return JS_FALSE; + } + } + fl_list_table = (JSFatLockTable *) malloc(i * sizeof(JSFatLockTable)); + if (!fl_list_table) { + js_CleanupLocks(); + return JS_FALSE; + } + fl_list_table_len = global_lock_count; + for (i = 0; i < global_lock_count; i++) + fl_list_table[i].free = fl_list_table[i].taken = NULL; + fl_list_chunk_len = listc; +#endif /* !NSPR_LOCK */ + return JS_TRUE; +} + +void +js_CleanupLocks() +{ +#ifndef NSPR_LOCK + uint32 i; + + if (global_locks) { + for (i = 0; i < global_lock_count; i++) + PR_DestroyLock(global_locks[i]); + free(global_locks); + global_locks = NULL; + global_lock_count = 1; + global_locks_log2 = 0; + global_locks_mask = 0; + } + if (fl_list_table) { + for (i = 0; i < fl_list_table_len; i++) { + DeleteListOfFatlocks(fl_list_table[i].free); + fl_list_table[i].free = NULL; + DeleteListOfFatlocks(fl_list_table[i].taken); + fl_list_table[i].taken = NULL; + } + free(fl_list_table); + fl_list_table = NULL; + fl_list_table_len = 0; + } +#endif /* !NSPR_LOCK */ +} + +void +js_InitContextForLocking(JSContext *cx) +{ + cx->thread = CurrentThreadId(); + JS_ASSERT(Thin_GetWait(cx->thread) == 0); +} + +#ifndef NSPR_LOCK + +/* + * Fast locking and unlocking is implemented by delaying the allocation of a + * system lock (fat lock) until contention. As long as a locking thread A + * runs uncontended, the lock is represented solely by storing A's identity in + * the object being locked. + * + * If another thread B tries to lock the object currently locked by A, B is + * enqueued into a fat lock structure (which might have to be allocated and + * pointed to by the object), and suspended using NSPR conditional variables + * (wait). A wait bit (Bacon bit) is set in the lock word of the object, + * signalling to A that when releasing the lock, B must be dequeued and + * notified. + * + * The basic operation of the locking primitives (js_Lock, js_Unlock, + * js_Enqueue, and js_Dequeue) is compare-and-swap. Hence, when locking into + * the word pointed at by p, compare-and-swap(p, 0, A) success implies that p + * is unlocked. Similarly, when unlocking p, if compare-and-swap(p, A, 0) + * succeeds this implies that p is uncontended (no one is waiting because the + * wait bit is not set). + * + * When dequeueing, the lock is released, and one of the threads suspended on + * the lock is notified. If other threads still are waiting, the wait bit is + * kept (in js_Enqueue), and if not, the fat lock is deallocated. + * + * The functions js_Enqueue, js_Dequeue, js_SuspendThread, and js_ResumeThread + * are serialized using a global lock. For scalability, a hashtable of global + * locks is used, which is indexed modulo the thin lock pointer. + */ + +/* + * Invariants: + * (i) global lock is held + * (ii) fl->susp >= 0 + */ +static int +js_SuspendThread(JSThinLock *tl) +{ + JSFatLock *fl; + PRStatus stat; + + if (tl->fat == NULL) + fl = tl->fat = GetFatlock(tl); + else + fl = tl->fat; + JS_ASSERT(fl->susp >= 0); + fl->susp++; + PR_Lock(fl->slock); + js_UnlockGlobal(tl); + stat = PR_WaitCondVar(fl->svar, PR_INTERVAL_NO_TIMEOUT); + JS_ASSERT(stat != PR_FAILURE); + PR_Unlock(fl->slock); + js_LockGlobal(tl); + fl->susp--; + if (fl->susp == 0) { + PutFatlock(fl, tl); + tl->fat = NULL; + } + return tl->fat == NULL; +} + +/* + * (i) global lock is held + * (ii) fl->susp > 0 + */ +static void +js_ResumeThread(JSThinLock *tl) +{ + JSFatLock *fl = tl->fat; + PRStatus stat; + + JS_ASSERT(fl != NULL); + JS_ASSERT(fl->susp > 0); + PR_Lock(fl->slock); + js_UnlockGlobal(tl); + stat = PR_NotifyCondVar(fl->svar); + JS_ASSERT(stat != PR_FAILURE); + PR_Unlock(fl->slock); +} + +static void +js_Enqueue(JSThinLock *tl, jsword me) +{ + jsword o, n; + + js_LockGlobal(tl); + for (;;) { + o = ReadWord(tl->owner); + n = Thin_SetWait(o); + if (o != 0 && js_CompareAndSwap(&tl->owner, o, n)) { + if (js_SuspendThread(tl)) + me = Thin_RemoveWait(me); + else + me = Thin_SetWait(me); + } + else if (js_CompareAndSwap(&tl->owner, 0, me)) { + js_UnlockGlobal(tl); + return; + } + } +} + +static void +js_Dequeue(JSThinLock *tl) +{ + jsword o; + + js_LockGlobal(tl); + o = ReadWord(tl->owner); + JS_ASSERT(Thin_GetWait(o) != 0); + JS_ASSERT(tl->fat != NULL); + if (!js_CompareAndSwap(&tl->owner, o, 0)) /* release it */ + JS_ASSERT(0); + js_ResumeThread(tl); +} + +JS_INLINE void +js_Lock(JSThinLock *tl, jsword me) +{ + JS_ASSERT(me == CurrentThreadId()); + if (js_CompareAndSwap(&tl->owner, 0, me)) + return; + if (Thin_RemoveWait(ReadWord(tl->owner)) != me) + js_Enqueue(tl, me); +#ifdef DEBUG + else + JS_ASSERT(0); +#endif +} + +JS_INLINE void +js_Unlock(JSThinLock *tl, jsword me) +{ + JS_ASSERT(me == CurrentThreadId()); + if (js_CompareAndSwap(&tl->owner, me, 0)) + return; + if (Thin_RemoveWait(ReadWord(tl->owner)) == me) + js_Dequeue(tl); +#ifdef DEBUG + else + JS_ASSERT(0); +#endif +} + +#endif /* !NSPR_LOCK */ + +void +js_LockRuntime(JSRuntime *rt) +{ + PR_Lock(rt->rtLock); +#ifdef DEBUG + rt->rtLockOwner = CurrentThreadId(); +#endif +} + +void +js_UnlockRuntime(JSRuntime *rt) +{ +#ifdef DEBUG + rt->rtLockOwner = 0; +#endif + PR_Unlock(rt->rtLock); +} + +void +js_LockScope(JSContext *cx, JSScope *scope) +{ + jsword me = cx->thread; + + JS_ASSERT(me == CurrentThreadId()); + JS_ASSERT(scope->ownercx != cx); + if (CX_THREAD_IS_RUNNING_GC(cx)) + return; + if (scope->ownercx && ClaimScope(scope, cx)) + return; + + if (Thin_RemoveWait(ReadWord(scope->lock.owner)) == me) { + JS_ASSERT(scope->u.count > 0); + LOGIT(scope, '+'); + scope->u.count++; + } else { + JSThinLock *tl = &scope->lock; + JS_LOCK0(tl, me); + JS_ASSERT(scope->u.count == 0); + LOGIT(scope, '1'); + scope->u.count = 1; + } +} + +void +js_UnlockScope(JSContext *cx, JSScope *scope) +{ + jsword me = cx->thread; + + /* We hope compilers use me instead of reloading cx->thread in the macro. */ + if (CX_THREAD_IS_RUNNING_GC(cx)) + return; + if (cx->lockedSealedScope == scope) { + cx->lockedSealedScope = NULL; + return; + } + + JS_ASSERT(scope->ownercx == NULL); + JS_ASSERT(scope->u.count > 0); + if (Thin_RemoveWait(ReadWord(scope->lock.owner)) != me) { + JS_ASSERT(0); /* unbalanced unlock */ + return; + } + LOGIT(scope, '-'); + if (--scope->u.count == 0) { + JSThinLock *tl = &scope->lock; + JS_UNLOCK0(tl, me); + } +} + +/* + * NB: oldscope may be null if our caller is js_GetMutableScope and it just + * dropped the last reference to oldscope. + */ +void +js_TransferScopeLock(JSContext *cx, JSScope *oldscope, JSScope *newscope) +{ + jsword me; + JSThinLock *tl; + + JS_ASSERT(JS_IS_SCOPE_LOCKED(cx, newscope)); + + /* + * If the last reference to oldscope went away, newscope needs no lock + * state update. + */ + if (!oldscope) + return; + JS_ASSERT(JS_IS_SCOPE_LOCKED(cx, oldscope)); + + /* + * Special case in js_LockScope and js_UnlockScope for the GC calling + * code that locks, unlocks, or mutates. Nothing to do in these cases, + * because scope and newscope were "locked" by the GC thread, so neither + * was actually locked. + */ + if (CX_THREAD_IS_RUNNING_GC(cx)) + return; + + /* + * Special case in js_LockObj and js_UnlockScope for locking the sealed + * scope of an object that owns that scope (the prototype or mutated obj + * for which OBJ_SCOPE(obj)->object == obj), and unlocking it. + */ + JS_ASSERT(cx->lockedSealedScope != newscope); + if (cx->lockedSealedScope == oldscope) { + JS_ASSERT(newscope->ownercx == cx || + (!newscope->ownercx && newscope->u.count == 1)); + cx->lockedSealedScope = NULL; + return; + } + + /* + * If oldscope is single-threaded, there's nothing to do. + */ + if (oldscope->ownercx) { + JS_ASSERT(oldscope->ownercx == cx); + JS_ASSERT(newscope->ownercx == cx || + (!newscope->ownercx && newscope->u.count == 1)); + return; + } + + /* + * We transfer oldscope->u.count only if newscope is not single-threaded. + * Flow unwinds from here through some number of JS_UNLOCK_SCOPE and/or + * JS_UNLOCK_OBJ macro calls, which will decrement newscope->u.count only + * if they find newscope->ownercx != cx. + */ + if (newscope->ownercx != cx) { + JS_ASSERT(!newscope->ownercx); + newscope->u.count = oldscope->u.count; + } + + /* + * Reset oldscope's lock state so that it is completely unlocked. + */ + LOGIT(oldscope, '0'); + oldscope->u.count = 0; + tl = &oldscope->lock; + me = cx->thread; + JS_UNLOCK0(tl, me); +} + +void +js_LockObj(JSContext *cx, JSObject *obj) +{ + JSScope *scope; + + JS_ASSERT(OBJ_IS_NATIVE(obj)); + for (;;) { + scope = OBJ_SCOPE(obj); + if (SCOPE_IS_SEALED(scope) && scope->object == obj && + !cx->lockedSealedScope) { + cx->lockedSealedScope = scope; + return; + } + + js_LockScope(cx, scope); + + /* If obj still has this scope, we're done. */ + if (scope == OBJ_SCOPE(obj)) + return; + + /* Lost a race with a mutator; retry with obj's new scope. */ + js_UnlockScope(cx, scope); + } +} + +void +js_UnlockObj(JSContext *cx, JSObject *obj) +{ + JS_ASSERT(OBJ_IS_NATIVE(obj)); + js_UnlockScope(cx, OBJ_SCOPE(obj)); +} + +#ifdef DEBUG + +JSBool +js_IsRuntimeLocked(JSRuntime *rt) +{ + return CurrentThreadId() == rt->rtLockOwner; +} + +JSBool +js_IsObjLocked(JSContext *cx, JSObject *obj) +{ + JSScope *scope = OBJ_SCOPE(obj); + + return MAP_IS_NATIVE(&scope->map) && js_IsScopeLocked(cx, scope); +} + +JSBool +js_IsScopeLocked(JSContext *cx, JSScope *scope) +{ + /* Special case: the GC locking any object's scope, see js_LockScope. */ + if (CX_THREAD_IS_RUNNING_GC(cx)) + return JS_TRUE; + + /* Special case: locked object owning a sealed scope, see js_LockObj. */ + if (cx->lockedSealedScope == scope) + return JS_TRUE; + + /* + * General case: the scope is either exclusively owned (by cx), or it has + * a thin or fat lock to cope with shared (concurrent) ownership. + */ + if (scope->ownercx) { + JS_ASSERT(scope->ownercx == cx); + return JS_TRUE; + } + return CurrentThreadId() == Thin_RemoveWait(ReadWord(scope->lock.owner)); +} + +#endif /* DEBUG */ +#endif /* JS_THREADSAFE */ diff --git a/src/dom/js/jslock.h b/src/dom/js/jslock.h new file mode 100644 index 000000000..9ece59c9b --- /dev/null +++ b/src/dom/js/jslock.h @@ -0,0 +1,289 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +#ifndef jslock_h__ +#define jslock_h__ + +#ifdef JS_THREADSAFE + +#include "jstypes.h" +#include "pratom.h" +#include "prlock.h" +#include "prcvar.h" + +#include "jsprvtd.h" /* for JSScope, etc. */ +#include "jspubtd.h" /* for JSRuntime, etc. */ + +#define Thin_GetWait(W) ((jsword)(W) & 0x1) +#define Thin_SetWait(W) ((jsword)(W) | 0x1) +#define Thin_RemoveWait(W) ((jsword)(W) & ~0x1) + +typedef struct JSFatLock JSFatLock; + +struct JSFatLock { + int susp; + PRLock *slock; + PRCondVar *svar; + JSFatLock *next; + JSFatLock **prevp; +}; + +typedef struct JSThinLock { + jsword owner; + JSFatLock *fat; +} JSThinLock; + +typedef PRLock JSLock; + +typedef struct JSFatLockTable { + JSFatLock *free; + JSFatLock *taken; +} JSFatLockTable; + +/* + * Atomic increment and decrement for a reference counter, given jsrefcount *p. + * NB: jsrefcount is int32, aka PRInt32, so that pratom.h functions work. + */ +#define JS_ATOMIC_INCREMENT(p) PR_AtomicIncrement((PRInt32 *)(p)) +#define JS_ATOMIC_DECREMENT(p) PR_AtomicDecrement((PRInt32 *)(p)) +#define JS_ATOMIC_ADD(p,v) PR_AtomicAdd((PRInt32 *)(p), (PRInt32)(v)) + +#define CurrentThreadId() (jsword)PR_GetCurrentThread() +#define JS_CurrentThreadId() js_CurrentThreadId() +#define JS_NEW_LOCK() PR_NewLock() +#define JS_DESTROY_LOCK(l) PR_DestroyLock(l) +#define JS_ACQUIRE_LOCK(l) PR_Lock(l) +#define JS_RELEASE_LOCK(l) PR_Unlock(l) +#define JS_LOCK0(P,M) js_Lock(P,M) +#define JS_UNLOCK0(P,M) js_Unlock(P,M) + +#define JS_NEW_CONDVAR(l) PR_NewCondVar(l) +#define JS_DESTROY_CONDVAR(cv) PR_DestroyCondVar(cv) +#define JS_WAIT_CONDVAR(cv,to) PR_WaitCondVar(cv,to) +#define JS_NO_TIMEOUT PR_INTERVAL_NO_TIMEOUT +#define JS_NOTIFY_CONDVAR(cv) PR_NotifyCondVar(cv) +#define JS_NOTIFY_ALL_CONDVAR(cv) PR_NotifyAllCondVar(cv) + +/* + * Include jsscope.h so JS_LOCK_OBJ macro callers don't have to include it. + * Since there is a JSThinLock member in JSScope, we can't nest this include + * much earlier (see JSThinLock's typedef, above). Yes, that means there is + * an #include cycle between jslock.h and jsscope.h: moderate-sized XXX here, + * to be fixed by moving JS_LOCK_SCOPE to jsscope.h, JS_LOCK_OBJ to jsobj.h, + * and so on. + * + * We also need jsscope.h #ifdef DEBUG for SET_OBJ_INFO and SET_SCOPE_INFO, + * but we do not want any nested includes that depend on DEBUG. Those lead + * to build bustage when someone makes a change that depends in a subtle way + * on jsscope.h being included directly or indirectly, but does not test by + * building optimized as well as DEBUG. + */ +#include "jsscope.h" + +#ifdef DEBUG + +#define SET_OBJ_INFO(obj_,file_,line_) \ + SET_SCOPE_INFO(OBJ_SCOPE(obj_),file_,line_) + +#define SET_SCOPE_INFO(scope_,file_,line_) \ + ((scope_)->ownercx ? (void)0 : \ + (JS_ASSERT((0 < (scope_)->u.count && (scope_)->u.count <= 4) || \ + SCOPE_IS_SEALED(scope_)), \ + (void)((scope_)->file[(scope_)->u.count-1] = (file_), \ + (scope_)->line[(scope_)->u.count-1] = (line_)))) +#endif /* DEBUG */ + +#define JS_LOCK_RUNTIME(rt) js_LockRuntime(rt) +#define JS_UNLOCK_RUNTIME(rt) js_UnlockRuntime(rt) + +/* + * NB: The JS_LOCK_OBJ and JS_UNLOCK_OBJ macros work *only* on native objects + * (objects for which OBJ_IS_NATIVE returns true). All uses of these macros in + * the engine are predicated on OBJ_IS_NATIVE or equivalent checks. These uses + * are for optimizations above the JSObjectOps layer, under which object locks + * normally hide. + */ +#define JS_LOCK_OBJ(cx,obj) ((OBJ_SCOPE(obj)->ownercx == (cx)) \ + ? (void)0 \ + : (js_LockObj(cx, obj), \ + SET_OBJ_INFO(obj,__FILE__,__LINE__))) +#define JS_UNLOCK_OBJ(cx,obj) ((OBJ_SCOPE(obj)->ownercx == (cx)) \ + ? (void)0 : js_UnlockObj(cx, obj)) + +#define JS_LOCK_SCOPE(cx,scope) ((scope)->ownercx == (cx) ? (void)0 : \ + (js_LockScope(cx, scope), \ + SET_SCOPE_INFO(scope,__FILE__,__LINE__))) +#define JS_UNLOCK_SCOPE(cx,scope) ((scope)->ownercx == (cx) ? (void)0 : \ + js_UnlockScope(cx, scope)) +#define JS_TRANSFER_SCOPE_LOCK(cx, scope, newscope) \ + js_TransferScopeLock(cx, scope, newscope) + +extern jsword js_CurrentThreadId(); +extern void js_LockRuntime(JSRuntime *rt); +extern void js_UnlockRuntime(JSRuntime *rt); +extern void js_LockObj(JSContext *cx, JSObject *obj); +extern void js_UnlockObj(JSContext *cx, JSObject *obj); +extern void js_LockScope(JSContext *cx, JSScope *scope); +extern void js_UnlockScope(JSContext *cx, JSScope *scope); +extern int js_SetupLocks(int,int); +extern void js_CleanupLocks(); +extern void js_InitContextForLocking(JSContext *); +extern void js_TransferScopeLock(JSContext *, JSScope *, JSScope *); +extern JS_FRIEND_API(jsval) +js_GetSlotThreadSafe(JSContext *, JSObject *, uint32); +extern void js_SetSlotThreadSafe(JSContext *, JSObject *, uint32, jsval); +extern void js_InitLock(JSThinLock *); +extern void js_FinishLock(JSThinLock *); +extern void js_FinishSharingScope(JSRuntime *rt, JSScope *scope); + +#ifdef DEBUG + +#define JS_IS_RUNTIME_LOCKED(rt) js_IsRuntimeLocked(rt) +#define JS_IS_OBJ_LOCKED(cx,obj) js_IsObjLocked(cx,obj) +#define JS_IS_SCOPE_LOCKED(cx,scope) js_IsScopeLocked(cx,scope) + +extern JSBool js_IsRuntimeLocked(JSRuntime *rt); +extern JSBool js_IsObjLocked(JSContext *cx, JSObject *obj); +extern JSBool js_IsScopeLocked(JSContext *cx, JSScope *scope); + +#else + +#define JS_IS_RUNTIME_LOCKED(rt) 0 +#define JS_IS_OBJ_LOCKED(cx,obj) 1 +#define JS_IS_SCOPE_LOCKED(cx,scope) 1 + +#endif /* DEBUG */ + +#define JS_LOCK_OBJ_VOID(cx, obj, e) \ + JS_BEGIN_MACRO \ + JS_LOCK_OBJ(cx, obj); \ + e; \ + JS_UNLOCK_OBJ(cx, obj); \ + JS_END_MACRO + +#define JS_LOCK_VOID(cx, e) \ + JS_BEGIN_MACRO \ + JSRuntime *_rt = (cx)->runtime; \ + JS_LOCK_RUNTIME_VOID(_rt, e); \ + JS_END_MACRO + +#if defined(JS_USE_ONLY_NSPR_LOCKS) || \ + !( (defined(_WIN32) && defined(_M_IX86)) || \ + (defined(__GNUC__) && defined(__i386__)) || \ + (defined(SOLARIS) && defined(sparc) && defined(ULTRA_SPARC)) || \ + defined(AIX) ) + +#define NSPR_LOCK 1 + +#undef JS_LOCK0 +#undef JS_UNLOCK0 +#define JS_LOCK0(P,M) (JS_ACQUIRE_LOCK(((JSLock*)(P)->fat)), (P)->owner = (M)) +#define JS_UNLOCK0(P,M) ((P)->owner = 0, JS_RELEASE_LOCK(((JSLock*)(P)->fat))) + +#else /* arch-tests */ + +#undef NSPR_LOCK + +extern JS_INLINE void js_Lock(JSThinLock *tl, jsword me); +extern JS_INLINE void js_Unlock(JSThinLock *tl, jsword me); + +#endif /* arch-tests */ + +#else /* !JS_THREADSAFE */ + +#define JS_ATOMIC_INCREMENT(p) (++*(p)) +#define JS_ATOMIC_DECREMENT(p) (--*(p)) +#define JS_ATOMIC_ADD(p,v) (*(p) += (v)) + +#define JS_CurrentThreadId() 0 +#define JS_NEW_LOCK() NULL +#define JS_DESTROY_LOCK(l) ((void)0) +#define JS_ACQUIRE_LOCK(l) ((void)0) +#define JS_RELEASE_LOCK(l) ((void)0) +#define JS_LOCK0(P,M) ((void)0) +#define JS_UNLOCK0(P,M) ((void)0) + +#define JS_NEW_CONDVAR(l) NULL +#define JS_DESTROY_CONDVAR(cv) ((void)0) +#define JS_WAIT_CONDVAR(cv,to) ((void)0) +#define JS_NOTIFY_CONDVAR(cv) ((void)0) +#define JS_NOTIFY_ALL_CONDVAR(cv) ((void)0) + +#define JS_LOCK_RUNTIME(rt) ((void)0) +#define JS_UNLOCK_RUNTIME(rt) ((void)0) +#define JS_LOCK_OBJ(cx,obj) ((void)0) +#define JS_UNLOCK_OBJ(cx,obj) ((void)0) +#define JS_LOCK_OBJ_VOID(cx,obj,e) (e) +#define JS_LOCK_SCOPE(cx,scope) ((void)0) +#define JS_UNLOCK_SCOPE(cx,scope) ((void)0) +#define JS_TRANSFER_SCOPE_LOCK(c,o,n) ((void)0) + +#define JS_IS_RUNTIME_LOCKED(rt) 1 +#define JS_IS_OBJ_LOCKED(cx,obj) 1 +#define JS_IS_SCOPE_LOCKED(cx,scope) 1 +#define JS_LOCK_VOID(cx, e) JS_LOCK_RUNTIME_VOID((cx)->runtime, e) + +#endif /* !JS_THREADSAFE */ + +#define JS_LOCK_RUNTIME_VOID(rt,e) \ + JS_BEGIN_MACRO \ + JS_LOCK_RUNTIME(rt); \ + e; \ + JS_UNLOCK_RUNTIME(rt); \ + JS_END_MACRO + +#define JS_LOCK_GC(rt) JS_ACQUIRE_LOCK((rt)->gcLock) +#define JS_UNLOCK_GC(rt) JS_RELEASE_LOCK((rt)->gcLock) +#define JS_LOCK_GC_VOID(rt,e) (JS_LOCK_GC(rt), (e), JS_UNLOCK_GC(rt)) +#define JS_AWAIT_GC_DONE(rt) JS_WAIT_CONDVAR((rt)->gcDone, JS_NO_TIMEOUT) +#define JS_NOTIFY_GC_DONE(rt) JS_NOTIFY_ALL_CONDVAR((rt)->gcDone) +#define JS_AWAIT_REQUEST_DONE(rt) JS_WAIT_CONDVAR((rt)->requestDone, \ + JS_NO_TIMEOUT) +#define JS_NOTIFY_REQUEST_DONE(rt) JS_NOTIFY_CONDVAR((rt)->requestDone) + +#define JS_LOCK(P,CX) JS_LOCK0(P,(CX)->thread) +#define JS_UNLOCK(P,CX) JS_UNLOCK0(P,(CX)->thread) + +#ifndef SET_OBJ_INFO +#define SET_OBJ_INFO(obj,f,l) ((void)0) +#endif +#ifndef SET_SCOPE_INFO +#define SET_SCOPE_INFO(scope,f,l) ((void)0) +#endif + +#endif /* jslock_h___ */ diff --git a/src/dom/js/jslocko.asm b/src/dom/js/jslocko.asm new file mode 100644 index 000000000..2589bb7f9 --- /dev/null +++ b/src/dom/js/jslocko.asm @@ -0,0 +1,59 @@ +COMMENT | -*- Mode: asm; tab-width: 8; c-basic-offset: 4 -*- +***** BEGIN LICENSE BLOCK ***** +Version: MPL 1.1/GPL 2.0/LGPL 2.1 + +The contents of this file are subject to the Mozilla Public License Version +1.1 (the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at +http://www.mozilla.org/MPL/ + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +for the specific language governing rights and limitations under the +License. + +The Original Code is an OS/2 implementation of js_CompareAndSwap in assembly + +The Initial Developer of the Original Code is IBM Corporation. +Portions created by the Initial Developer are Copyright (C) 2001 +the Initial Developer. All Rights Reserved. + +Contributor(s): + +Alternatively, the contents of this file may be used under the terms of +either the GNU General Public License Version 2 or later (the "GPL"), or +the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +in which case the provisions of the GPL or the LGPL are applicable instead +of those above. If you wish to allow use of your version of this file only +under the terms of either the GPL or the LGPL, and not to allow others to +use your version of this file under the terms of the MPL, indicate your +decision by deleting the provisions above and replace them with the notice +and other provisions required by the GPL or the LGPL. If you do not delete +the provisions above, a recipient may use your version of this file under +the terms of any one of the MPL, the GPL or the LGPL. + +***** END LICENSE BLOCK ***** + | + + .486P + .MODEL FLAT, OPTLINK + .STACK + + .CODE + +;;;--------------------------------------------------------------------- +;;; int _Optlink js_CompareAndSwap(jsword *w, jsword ov, jsword nv) +;;;--------------------------------------------------------------------- +js_CompareAndSwap PROC OPTLINK EXPORT + push ebx + mov ebx, eax + mov eax, edx + mov edx, ebx + lock cmpxchg [ebx], ecx + sete al + and eax, 1h + pop ebx + ret +js_CompareAndSwap endp + + END diff --git a/src/dom/js/jslog2.c b/src/dom/js/jslog2.c new file mode 100644 index 000000000..113c276d0 --- /dev/null +++ b/src/dom/js/jslog2.c @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "jsstddef.h" +#include "jsbit.h" + +/* +** Compute the log of the least power of 2 greater than or equal to n +*/ +JS_PUBLIC_API(JSIntn) JS_CeilingLog2(JSUint32 n) +{ + JSIntn log2 = 0; + + if (n & (n-1)) + log2++; + if (n >> 16) + log2 += 16, n >>= 16; + if (n >> 8) + log2 += 8, n >>= 8; + if (n >> 4) + log2 += 4, n >>= 4; + if (n >> 2) + log2 += 2, n >>= 2; + if (n >> 1) + log2++; + return log2; +} + +/* +** Compute the log of the greatest power of 2 less than or equal to n. +** This really just finds the highest set bit in the word. +*/ +JS_PUBLIC_API(JSIntn) JS_FloorLog2(JSUint32 n) +{ + JSIntn log2 = 0; + + if (n >> 16) + log2 += 16, n >>= 16; + if (n >> 8) + log2 += 8, n >>= 8; + if (n >> 4) + log2 += 4, n >>= 4; + if (n >> 2) + log2 += 2, n >>= 2; + if (n >> 1) + log2++; + return log2; +} diff --git a/src/dom/js/jslong.c b/src/dom/js/jslong.c new file mode 100644 index 000000000..76259e502 --- /dev/null +++ b/src/dom/js/jslong.c @@ -0,0 +1,281 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "jsstddef.h" +#include "jstypes.h" +#include "jslong.h" + +static JSInt64 ll_zero = JSLL_INIT( 0x00000000,0x00000000 ); +static JSInt64 ll_maxint = JSLL_INIT( 0x7fffffff, 0xffffffff ); +static JSInt64 ll_minint = JSLL_INIT( 0x80000000, 0x00000000 ); + +#ifdef HAVE_WATCOM_BUG_2 +JSInt64 __pascal __loadds __export + JSLL_Zero(void) { return ll_zero; } +JSInt64 __pascal __loadds __export + JSLL_MaxInt(void) { return ll_maxint; } +JSInt64 __pascal __loadds __export + JSLL_MinInt(void) { return ll_minint; } +#else +JS_PUBLIC_API(JSInt64) JSLL_Zero(void) { return ll_zero; } +JS_PUBLIC_API(JSInt64) JSLL_MaxInt(void) { return ll_maxint; } +JS_PUBLIC_API(JSInt64) JSLL_MinInt(void) { return ll_minint; } +#endif + +#ifndef JS_HAVE_LONG_LONG +/* +** Divide 64-bit a by 32-bit b, which must be normalized so its high bit is 1. +*/ +static void norm_udivmod32(JSUint32 *qp, JSUint32 *rp, JSUint64 a, JSUint32 b) +{ + JSUint32 d1, d0, q1, q0; + JSUint32 r1, r0, m; + + d1 = jshi16(b); + d0 = jslo16(b); + r1 = a.hi % d1; + q1 = a.hi / d1; + m = q1 * d0; + r1 = (r1 << 16) | jshi16(a.lo); + if (r1 < m) { + q1--, r1 += b; + if (r1 >= b /* i.e., we didn't get a carry when adding to r1 */ + && r1 < m) { + q1--, r1 += b; + } + } + r1 -= m; + r0 = r1 % d1; + q0 = r1 / d1; + m = q0 * d0; + r0 = (r0 << 16) | jslo16(a.lo); + if (r0 < m) { + q0--, r0 += b; + if (r0 >= b + && r0 < m) { + q0--, r0 += b; + } + } + *qp = (q1 << 16) | q0; + *rp = r0 - m; +} + +static JSUint32 CountLeadingZeros(JSUint32 a) +{ + JSUint32 t; + JSUint32 r = 32; + + if ((t = a >> 16) != 0) + r -= 16, a = t; + if ((t = a >> 8) != 0) + r -= 8, a = t; + if ((t = a >> 4) != 0) + r -= 4, a = t; + if ((t = a >> 2) != 0) + r -= 2, a = t; + if ((t = a >> 1) != 0) + r -= 1, a = t; + if (a & 1) + r--; + return r; +} + +JS_PUBLIC_API(void) jsll_udivmod(JSUint64 *qp, JSUint64 *rp, JSUint64 a, JSUint64 b) +{ + JSUint32 n0, n1, n2; + JSUint32 q0, q1; + JSUint32 rsh, lsh; + + n0 = a.lo; + n1 = a.hi; + + if (b.hi == 0) { + if (b.lo > n1) { + /* (0 q0) = (n1 n0) / (0 D0) */ + + lsh = CountLeadingZeros(b.lo); + + if (lsh) { + /* + * Normalize, i.e. make the most significant bit of the + * denominator be set. + */ + b.lo = b.lo << lsh; + n1 = (n1 << lsh) | (n0 >> (32 - lsh)); + n0 = n0 << lsh; + } + + a.lo = n0, a.hi = n1; + norm_udivmod32(&q0, &n0, a, b.lo); + q1 = 0; + + /* remainder is in n0 >> lsh */ + } else { + /* (q1 q0) = (n1 n0) / (0 d0) */ + + if (b.lo == 0) /* user wants to divide by zero! */ + b.lo = 1 / b.lo; /* so go ahead and crash */ + + lsh = CountLeadingZeros(b.lo); + + if (lsh == 0) { + /* + * From (n1 >= b.lo) + * && (the most significant bit of b.lo is set), + * conclude that + * (the most significant bit of n1 is set) + * && (the leading quotient digit q1 = 1). + * + * This special case is necessary, not an optimization + * (Shifts counts of 32 are undefined). + */ + n1 -= b.lo; + q1 = 1; + } else { + /* + * Normalize. + */ + rsh = 32 - lsh; + + b.lo = b.lo << lsh; + n2 = n1 >> rsh; + n1 = (n1 << lsh) | (n0 >> rsh); + n0 = n0 << lsh; + + a.lo = n1, a.hi = n2; + norm_udivmod32(&q1, &n1, a, b.lo); + } + + /* n1 != b.lo... */ + + a.lo = n0, a.hi = n1; + norm_udivmod32(&q0, &n0, a, b.lo); + + /* remainder in n0 >> lsh */ + } + + if (rp) { + rp->lo = n0 >> lsh; + rp->hi = 0; + } + } else { + if (b.hi > n1) { + /* (0 0) = (n1 n0) / (D1 d0) */ + + q0 = 0; + q1 = 0; + + /* remainder in (n1 n0) */ + if (rp) { + rp->lo = n0; + rp->hi = n1; + } + } else { + /* (0 q0) = (n1 n0) / (d1 d0) */ + + lsh = CountLeadingZeros(b.hi); + if (lsh == 0) { + /* + * From (n1 >= b.hi) + * && (the most significant bit of b.hi is set), + * conclude that + * (the most significant bit of n1 is set) + * && (the quotient digit q0 = 0 or 1). + * + * This special case is necessary, not an optimization. + */ + + /* + * The condition on the next line takes advantage of that + * n1 >= b.hi (true due to control flow). + */ + if (n1 > b.hi || n0 >= b.lo) { + q0 = 1; + a.lo = n0, a.hi = n1; + JSLL_SUB(a, a, b); + } else { + q0 = 0; + } + q1 = 0; + + if (rp) { + rp->lo = n0; + rp->hi = n1; + } + } else { + JSInt64 m; + + /* + * Normalize. + */ + rsh = 32 - lsh; + + b.hi = (b.hi << lsh) | (b.lo >> rsh); + b.lo = b.lo << lsh; + n2 = n1 >> rsh; + n1 = (n1 << lsh) | (n0 >> rsh); + n0 = n0 << lsh; + + a.lo = n1, a.hi = n2; + norm_udivmod32(&q0, &n1, a, b.hi); + JSLL_MUL32(m, q0, b.lo); + + if ((m.hi > n1) || ((m.hi == n1) && (m.lo > n0))) { + q0--; + JSLL_SUB(m, m, b); + } + + q1 = 0; + + /* Remainder is ((n1 n0) - (m1 m0)) >> lsh */ + if (rp) { + a.lo = n0, a.hi = n1; + JSLL_SUB(a, a, m); + rp->lo = (a.hi << rsh) | (a.lo >> lsh); + rp->hi = a.hi >> lsh; + } + } + } + } + + if (qp) { + qp->lo = q0; + qp->hi = q1; + } +} +#endif /* !JS_HAVE_LONG_LONG */ diff --git a/src/dom/js/jslong.h b/src/dom/js/jslong.h new file mode 100644 index 000000000..bde8bfbbf --- /dev/null +++ b/src/dom/js/jslong.h @@ -0,0 +1,437 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* +** File: jslong.h +** Description: Portable access to 64 bit numerics +** +** Long-long (64-bit signed integer type) support. Some C compilers +** don't support 64 bit integers yet, so we use these macros to +** support both machines that do and don't. +**/ +#ifndef jslong_h___ +#define jslong_h___ + +#include "jstypes.h" + +JS_BEGIN_EXTERN_C + +/*********************************************************************** +** DEFINES: JSLL_MaxInt +** JSLL_MinInt +** JSLL_Zero +** DESCRIPTION: +** Various interesting constants and static variable +** initializer +***********************************************************************/ +#ifdef HAVE_WATCOM_BUG_2 +JSInt64 __pascal __loadds __export + JSLL_MaxInt(void); +JSInt64 __pascal __loadds __export + JSLL_MinInt(void); +JSInt64 __pascal __loadds __export + JSLL_Zero(void); +#else +extern JS_PUBLIC_API(JSInt64) JSLL_MaxInt(void); +extern JS_PUBLIC_API(JSInt64) JSLL_MinInt(void); +extern JS_PUBLIC_API(JSInt64) JSLL_Zero(void); +#endif + +#define JSLL_MAXINT JSLL_MaxInt() +#define JSLL_MININT JSLL_MinInt() +#define JSLL_ZERO JSLL_Zero() + +#ifdef JS_HAVE_LONG_LONG + +#if JS_BYTES_PER_LONG == 8 +#define JSLL_INIT(hi, lo) ((hi ## L << 32) + lo ## L) +#elif (defined(WIN32) || defined(WIN16)) && !defined(__GNUC__) +#define JSLL_INIT(hi, lo) ((hi ## i64 << 32) + lo ## i64) +#else +#define JSLL_INIT(hi, lo) ((hi ## LL << 32) + lo ## LL) +#endif + +/*********************************************************************** +** MACROS: JSLL_* +** DESCRIPTION: +** The following macros define portable access to the 64 bit +** math facilities. +** +***********************************************************************/ + +/*********************************************************************** +** MACROS: JSLL_ +** +** JSLL_IS_ZERO Test for zero +** JSLL_EQ Test for equality +** JSLL_NE Test for inequality +** JSLL_GE_ZERO Test for zero or positive +** JSLL_CMP Compare two values +***********************************************************************/ +#define JSLL_IS_ZERO(a) ((a) == 0) +#define JSLL_EQ(a, b) ((a) == (b)) +#define JSLL_NE(a, b) ((a) != (b)) +#define JSLL_GE_ZERO(a) ((a) >= 0) +#define JSLL_CMP(a, op, b) ((JSInt64)(a) op (JSInt64)(b)) +#define JSLL_UCMP(a, op, b) ((JSUint64)(a) op (JSUint64)(b)) + +/*********************************************************************** +** MACROS: JSLL_ +** +** JSLL_AND Logical and +** JSLL_OR Logical or +** JSLL_XOR Logical exclusion +** JSLL_OR2 A disgusting deviation +** JSLL_NOT Negation (one's compliment) +***********************************************************************/ +#define JSLL_AND(r, a, b) ((r) = (a) & (b)) +#define JSLL_OR(r, a, b) ((r) = (a) | (b)) +#define JSLL_XOR(r, a, b) ((r) = (a) ^ (b)) +#define JSLL_OR2(r, a) ((r) = (r) | (a)) +#define JSLL_NOT(r, a) ((r) = ~(a)) + +/*********************************************************************** +** MACROS: JSLL_ +** +** JSLL_NEG Negation (two's compliment) +** JSLL_ADD Summation (two's compliment) +** JSLL_SUB Difference (two's compliment) +***********************************************************************/ +#define JSLL_NEG(r, a) ((r) = -(a)) +#define JSLL_ADD(r, a, b) ((r) = (a) + (b)) +#define JSLL_SUB(r, a, b) ((r) = (a) - (b)) + +/*********************************************************************** +** MACROS: JSLL_ +** +** JSLL_MUL Product (two's compliment) +** JSLL_DIV Quotient (two's compliment) +** JSLL_MOD Modulus (two's compliment) +***********************************************************************/ +#define JSLL_MUL(r, a, b) ((r) = (a) * (b)) +#define JSLL_DIV(r, a, b) ((r) = (a) / (b)) +#define JSLL_MOD(r, a, b) ((r) = (a) % (b)) + +/*********************************************************************** +** MACROS: JSLL_ +** +** JSLL_SHL Shift left [0..64] bits +** JSLL_SHR Shift right [0..64] bits with sign extension +** JSLL_USHR Unsigned shift right [0..64] bits +** JSLL_ISHL Signed shift left [0..64] bits +***********************************************************************/ +#define JSLL_SHL(r, a, b) ((r) = (JSInt64)(a) << (b)) +#define JSLL_SHR(r, a, b) ((r) = (JSInt64)(a) >> (b)) +#define JSLL_USHR(r, a, b) ((r) = (JSUint64)(a) >> (b)) +#define JSLL_ISHL(r, a, b) ((r) = (JSInt64)(a) << (b)) + +/*********************************************************************** +** MACROS: JSLL_ +** +** JSLL_L2I Convert to signed 32 bit +** JSLL_L2UI Convert to unsigned 32 bit +** JSLL_L2F Convert to floating point +** JSLL_L2D Convert to floating point +** JSLL_I2L Convert signed to 64 bit +** JSLL_UI2L Convert unsigned to 64 bit +** JSLL_F2L Convert float to 64 bit +** JSLL_D2L Convert float to 64 bit +***********************************************************************/ +#define JSLL_L2I(i, l) ((i) = (JSInt32)(l)) +#define JSLL_L2UI(ui, l) ((ui) = (JSUint32)(l)) +#define JSLL_L2F(f, l) ((f) = (JSFloat64)(l)) +#define JSLL_L2D(d, l) ((d) = (JSFloat64)(l)) + +#define JSLL_I2L(l, i) ((l) = (JSInt64)(i)) +#define JSLL_UI2L(l, ui) ((l) = (JSInt64)(ui)) +#define JSLL_F2L(l, f) ((l) = (JSInt64)(f)) +#define JSLL_D2L(l, d) ((l) = (JSInt64)(d)) + +/*********************************************************************** +** MACROS: JSLL_UDIVMOD +** DESCRIPTION: +** Produce both a quotient and a remainder given an unsigned +** INPUTS: JSUint64 a: The dividend of the operation +** JSUint64 b: The quotient of the operation +** OUTPUTS: JSUint64 *qp: pointer to quotient +** JSUint64 *rp: pointer to remainder +***********************************************************************/ +#define JSLL_UDIVMOD(qp, rp, a, b) \ + (*(qp) = ((JSUint64)(a) / (b)), \ + *(rp) = ((JSUint64)(a) % (b))) + +#else /* !JS_HAVE_LONG_LONG */ + +#ifdef IS_LITTLE_ENDIAN +#define JSLL_INIT(hi, lo) {JS_INT32(lo), JS_INT32(hi)} +#else +#define JSLL_INIT(hi, lo) {JS_INT32(hi), JS_INT32(lo)} +#endif + +#define JSLL_IS_ZERO(a) (((a).hi == 0) && ((a).lo == 0)) +#define JSLL_EQ(a, b) (((a).hi == (b).hi) && ((a).lo == (b).lo)) +#define JSLL_NE(a, b) (((a).hi != (b).hi) || ((a).lo != (b).lo)) +#define JSLL_GE_ZERO(a) (((a).hi >> 31) == 0) + +#ifdef DEBUG +#define JSLL_CMP(a, op, b) (JS_ASSERT((#op)[1] != '='), JSLL_REAL_CMP(a, op, b)) +#define JSLL_UCMP(a, op, b) (JS_ASSERT((#op)[1] != '='), JSLL_REAL_UCMP(a, op, b)) +#else +#define JSLL_CMP(a, op, b) JSLL_REAL_CMP(a, op, b) +#define JSLL_UCMP(a, op, b) JSLL_REAL_UCMP(a, op, b) +#endif + +#define JSLL_REAL_CMP(a,op,b) (((JSInt32)(a).hi op (JSInt32)(b).hi) || \ + (((a).hi == (b).hi) && ((a).lo op (b).lo))) +#define JSLL_REAL_UCMP(a,op,b) (((a).hi op (b).hi) || \ + (((a).hi == (b).hi) && ((a).lo op (b).lo))) + +#define JSLL_AND(r, a, b) ((r).lo = (a).lo & (b).lo, \ + (r).hi = (a).hi & (b).hi) +#define JSLL_OR(r, a, b) ((r).lo = (a).lo | (b).lo, \ + (r).hi = (a).hi | (b).hi) +#define JSLL_XOR(r, a, b) ((r).lo = (a).lo ^ (b).lo, \ + (r).hi = (a).hi ^ (b).hi) +#define JSLL_OR2(r, a) ((r).lo = (r).lo | (a).lo, \ + (r).hi = (r).hi | (a).hi) +#define JSLL_NOT(r, a) ((r).lo = ~(a).lo, \ + (r).hi = ~(a).hi) + +#define JSLL_NEG(r, a) ((r).lo = -(JSInt32)(a).lo, \ + (r).hi = -(JSInt32)(a).hi - ((r).lo != 0)) +#define JSLL_ADD(r, a, b) { \ + JSInt64 _a, _b; \ + _a = a; _b = b; \ + (r).lo = _a.lo + _b.lo; \ + (r).hi = _a.hi + _b.hi + ((r).lo < _b.lo); \ +} + +#define JSLL_SUB(r, a, b) { \ + JSInt64 _a, _b; \ + _a = a; _b = b; \ + (r).lo = _a.lo - _b.lo; \ + (r).hi = _a.hi - _b.hi - (_a.lo < _b.lo); \ +} + +#define JSLL_MUL(r, a, b) { \ + JSInt64 _a, _b; \ + _a = a; _b = b; \ + JSLL_MUL32(r, _a.lo, _b.lo); \ + (r).hi += _a.hi * _b.lo + _a.lo * _b.hi; \ +} + +#define jslo16(a) ((a) & JS_BITMASK(16)) +#define jshi16(a) ((a) >> 16) + +#define JSLL_MUL32(r, a, b) { \ + JSUint32 _a1, _a0, _b1, _b0, _y0, _y1, _y2, _y3; \ + _a1 = jshi16(a), _a0 = jslo16(a); \ + _b1 = jshi16(b), _b0 = jslo16(b); \ + _y0 = _a0 * _b0; \ + _y1 = _a0 * _b1; \ + _y2 = _a1 * _b0; \ + _y3 = _a1 * _b1; \ + _y1 += jshi16(_y0); /* can't carry */ \ + _y1 += _y2; /* might carry */ \ + if (_y1 < _y2) \ + _y3 += (JSUint32)(JS_BIT(16)); /* propagate */ \ + (r).lo = (jslo16(_y1) << 16) + jslo16(_y0); \ + (r).hi = _y3 + jshi16(_y1); \ +} + +#define JSLL_UDIVMOD(qp, rp, a, b) jsll_udivmod(qp, rp, a, b) + +extern JS_PUBLIC_API(void) jsll_udivmod(JSUint64 *qp, JSUint64 *rp, JSUint64 a, JSUint64 b); + +#define JSLL_DIV(r, a, b) { \ + JSInt64 _a, _b; \ + JSUint32 _negative = (JSInt32)(a).hi < 0; \ + if (_negative) { \ + JSLL_NEG(_a, a); \ + } else { \ + _a = a; \ + } \ + if ((JSInt32)(b).hi < 0) { \ + _negative ^= 1; \ + JSLL_NEG(_b, b); \ + } else { \ + _b = b; \ + } \ + JSLL_UDIVMOD(&(r), 0, _a, _b); \ + if (_negative) \ + JSLL_NEG(r, r); \ +} + +#define JSLL_MOD(r, a, b) { \ + JSInt64 _a, _b; \ + JSUint32 _negative = (JSInt32)(a).hi < 0; \ + if (_negative) { \ + JSLL_NEG(_a, a); \ + } else { \ + _a = a; \ + } \ + if ((JSInt32)(b).hi < 0) { \ + JSLL_NEG(_b, b); \ + } else { \ + _b = b; \ + } \ + JSLL_UDIVMOD(0, &(r), _a, _b); \ + if (_negative) \ + JSLL_NEG(r, r); \ +} + +#define JSLL_SHL(r, a, b) { \ + if (b) { \ + JSInt64 _a; \ + _a = a; \ + if ((b) < 32) { \ + (r).lo = _a.lo << ((b) & 31); \ + (r).hi = (_a.hi << ((b) & 31)) | (_a.lo >> (32 - (b))); \ + } else { \ + (r).lo = 0; \ + (r).hi = _a.lo << ((b) & 31); \ + } \ + } else { \ + (r) = (a); \ + } \ +} + +/* a is an JSInt32, b is JSInt32, r is JSInt64 */ +#define JSLL_ISHL(r, a, b) { \ + if (b) { \ + JSInt64 _a; \ + _a.lo = (a); \ + _a.hi = 0; \ + if ((b) < 32) { \ + (r).lo = (a) << ((b) & 31); \ + (r).hi = ((a) >> (32 - (b))); \ + } else { \ + (r).lo = 0; \ + (r).hi = (a) << ((b) & 31); \ + } \ + } else { \ + (r).lo = (a); \ + (r).hi = 0; \ + } \ +} + +#define JSLL_SHR(r, a, b) { \ + if (b) { \ + JSInt64 _a; \ + _a = a; \ + if ((b) < 32) { \ + (r).lo = (_a.hi << (32 - (b))) | (_a.lo >> ((b) & 31)); \ + (r).hi = (JSInt32)_a.hi >> ((b) & 31); \ + } else { \ + (r).lo = (JSInt32)_a.hi >> ((b) & 31); \ + (r).hi = (JSInt32)_a.hi >> 31; \ + } \ + } else { \ + (r) = (a); \ + } \ +} + +#define JSLL_USHR(r, a, b) { \ + if (b) { \ + JSInt64 _a; \ + _a = a; \ + if ((b) < 32) { \ + (r).lo = (_a.hi << (32 - (b))) | (_a.lo >> ((b) & 31)); \ + (r).hi = _a.hi >> ((b) & 31); \ + } else { \ + (r).lo = _a.hi >> ((b) & 31); \ + (r).hi = 0; \ + } \ + } else { \ + (r) = (a); \ + } \ +} + +#define JSLL_L2I(i, l) ((i) = (l).lo) +#define JSLL_L2UI(ui, l) ((ui) = (l).lo) +#define JSLL_L2F(f, l) { double _d; JSLL_L2D(_d, l); (f) = (JSFloat64)_d; } + +#define JSLL_L2D(d, l) { \ + int _negative; \ + JSInt64 _absval; \ + \ + _negative = (l).hi >> 31; \ + if (_negative) { \ + JSLL_NEG(_absval, l); \ + } else { \ + _absval = l; \ + } \ + (d) = (double)_absval.hi * 4.294967296e9 + _absval.lo; \ + if (_negative) \ + (d) = -(d); \ +} + +#define JSLL_I2L(l, i) { JSInt32 _i = (i) >> 31; (l).lo = (i); (l).hi = _i; } +#define JSLL_UI2L(l, ui) ((l).lo = (ui), (l).hi = 0) +#define JSLL_F2L(l, f) { double _d = (double)f; JSLL_D2L(l, _d); } + +#define JSLL_D2L(l, d) { \ + int _negative; \ + double _absval, _d_hi; \ + JSInt64 _lo_d; \ + \ + _negative = ((d) < 0); \ + _absval = _negative ? -(d) : (d); \ + \ + (l).hi = _absval / 4.294967296e9; \ + (l).lo = 0; \ + JSLL_L2D(_d_hi, l); \ + _absval -= _d_hi; \ + _lo_d.hi = 0; \ + if (_absval < 0) { \ + _lo_d.lo = -_absval; \ + JSLL_SUB(l, l, _lo_d); \ + } else { \ + _lo_d.lo = _absval; \ + JSLL_ADD(l, l, _lo_d); \ + } \ + \ + if (_negative) \ + JSLL_NEG(l, l); \ +} + +#endif /* !JS_HAVE_LONG_LONG */ + +JS_END_EXTERN_C + +#endif /* jslong_h___ */ diff --git a/src/dom/js/jsmath.c b/src/dom/js/jsmath.c new file mode 100644 index 000000000..9c6fcec0e --- /dev/null +++ b/src/dom/js/jsmath.c @@ -0,0 +1,477 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS math package. + */ +#include "jsstddef.h" +#include "jslibmath.h" +#include +#include "jstypes.h" +#include "jslong.h" +#include "prmjtime.h" +#include "jsapi.h" +#include "jsatom.h" +#include "jscntxt.h" +#include "jsconfig.h" +#include "jslock.h" +#include "jsmath.h" +#include "jsnum.h" +#include "jsobj.h" + +#ifndef M_E +#define M_E 2.7182818284590452354 +#endif +#ifndef M_LOG2E +#define M_LOG2E 1.4426950408889634074 +#endif +#ifndef M_LOG10E +#define M_LOG10E 0.43429448190325182765 +#endif +#ifndef M_LN2 +#define M_LN2 0.69314718055994530942 +#endif +#ifndef M_LN10 +#define M_LN10 2.30258509299404568402 +#endif +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif +#ifndef M_SQRT2 +#define M_SQRT2 1.41421356237309504880 +#endif +#ifndef M_SQRT1_2 +#define M_SQRT1_2 0.70710678118654752440 +#endif + +static JSConstDoubleSpec math_constants[] = { + {M_E, "E", 0, {0,0,0}}, + {M_LOG2E, "LOG2E", 0, {0,0,0}}, + {M_LOG10E, "LOG10E", 0, {0,0,0}}, + {M_LN2, "LN2", 0, {0,0,0}}, + {M_LN10, "LN10", 0, {0,0,0}}, + {M_PI, "PI", 0, {0,0,0}}, + {M_SQRT2, "SQRT2", 0, {0,0,0}}, + {M_SQRT1_2, "SQRT1_2", 0, {0,0,0}}, + {0,0,0,{0,0,0}} +}; + +static JSClass math_class = { + "Math", + 0, + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS +}; + +static JSBool +math_abs(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsdouble x, z; + + if (!js_ValueToNumber(cx, argv[0], &x)) + return JS_FALSE; + z = fd_fabs(x); + return js_NewNumberValue(cx, z, rval); +} + +static JSBool +math_acos(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsdouble x, z; + + if (!js_ValueToNumber(cx, argv[0], &x)) + return JS_FALSE; + z = fd_acos(x); + return js_NewNumberValue(cx, z, rval); +} + +static JSBool +math_asin(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsdouble x, z; + + if (!js_ValueToNumber(cx, argv[0], &x)) + return JS_FALSE; +#ifdef XP_MAC + if (x == 0) + return js_NewNumberValue(cx, x, rval); +#endif + z = fd_asin(x); + return js_NewNumberValue(cx, z, rval); +} + +static JSBool +math_atan(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsdouble x, z; + + if (!js_ValueToNumber(cx, argv[0], &x)) + return JS_FALSE; +#ifdef XP_MAC + if (x == 0) + return js_NewNumberValue(cx, x, rval); +#endif + z = fd_atan(x); + return js_NewNumberValue(cx, z, rval); +} + +static JSBool +math_atan2(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsdouble x, y, z; + + if (!js_ValueToNumber(cx, argv[0], &x)) + return JS_FALSE; + if (!js_ValueToNumber(cx, argv[1], &y)) + return JS_FALSE; + z = fd_atan2(x, y); + return js_NewNumberValue(cx, z, rval); +} + +static JSBool +math_ceil(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsdouble x, z; + + if (!js_ValueToNumber(cx, argv[0], &x)) + return JS_FALSE; + z = fd_ceil(x); + return js_NewNumberValue(cx, z, rval); +} + +static JSBool +math_cos(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsdouble x, z; + + if (!js_ValueToNumber(cx, argv[0], &x)) + return JS_FALSE; + z = fd_cos(x); + return js_NewNumberValue(cx, z, rval); +} + +static JSBool +math_exp(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsdouble x, z; + + if (!js_ValueToNumber(cx, argv[0], &x)) + return JS_FALSE; +#ifdef _WIN32 + if (!JSDOUBLE_IS_NaN(x)) { + if (x == *cx->runtime->jsPositiveInfinity) { + *rval = DOUBLE_TO_JSVAL(cx->runtime->jsPositiveInfinity); + return JS_TRUE; + } + if (x == *cx->runtime->jsNegativeInfinity) { + *rval = JSVAL_ZERO; + return JS_TRUE; + } + } +#endif + z = fd_exp(x); + return js_NewNumberValue(cx, z, rval); +} + +static JSBool +math_floor(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsdouble x, z; + + if (!js_ValueToNumber(cx, argv[0], &x)) + return JS_FALSE; + z = fd_floor(x); + return js_NewNumberValue(cx, z, rval); +} + +static JSBool +math_log(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsdouble x, z; + + if (!js_ValueToNumber(cx, argv[0], &x)) + return JS_FALSE; + z = fd_log(x); + return js_NewNumberValue(cx, z, rval); +} + +static JSBool +math_max(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsdouble x, z = *cx->runtime->jsNegativeInfinity; + uintN i; + + if (argc == 0) { + *rval = DOUBLE_TO_JSVAL(cx->runtime->jsNegativeInfinity); + return JS_TRUE; + } + for (i = 0; i < argc; i++) { + if (!js_ValueToNumber(cx, argv[i], &x)) + return JS_FALSE; + if (JSDOUBLE_IS_NaN(x)) { + *rval = DOUBLE_TO_JSVAL(cx->runtime->jsNaN); + return JS_TRUE; + } + if ((x==0)&&(x==z)&&(fd_copysign(1.0,z)==-1)) + z = x; + else + z = (x > z) ? x : z; + } + return js_NewNumberValue(cx, z, rval); +} + +static JSBool +math_min(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsdouble x, z = *cx->runtime->jsPositiveInfinity; + uintN i; + + if (argc == 0) { + *rval = DOUBLE_TO_JSVAL(cx->runtime->jsPositiveInfinity); + return JS_TRUE; + } + for (i = 0; i < argc; i++) { + if (!js_ValueToNumber(cx, argv[i], &x)) + return JS_FALSE; + if (JSDOUBLE_IS_NaN(x)) { + *rval = DOUBLE_TO_JSVAL(cx->runtime->jsNaN); + return JS_TRUE; + } + if ((x==0)&&(x==z)&&(fd_copysign(1.0,x)==-1)) + z = x; + else + z = (x < z) ? x : z; + } + return js_NewNumberValue(cx, z, rval); +} + +static JSBool +math_pow(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsdouble x, y, z; + + if (!js_ValueToNumber(cx, argv[0], &x)) + return JS_FALSE; + if (!js_ValueToNumber(cx, argv[1], &y)) + return JS_FALSE; + z = fd_pow(x, y); + return js_NewNumberValue(cx, z, rval); +} + +/* + * Math.random() support, lifted from java.util.Random.java. + */ +static void +random_setSeed(JSRuntime *rt, int64 seed) +{ + int64 tmp; + + JSLL_I2L(tmp, 1000); + JSLL_DIV(seed, seed, tmp); + JSLL_XOR(tmp, seed, rt->rngMultiplier); + JSLL_AND(rt->rngSeed, tmp, rt->rngMask); +} + +static void +random_init(JSRuntime *rt) +{ + int64 tmp, tmp2; + + /* Do at most once. */ + if (rt->rngInitialized) + return; + rt->rngInitialized = JS_TRUE; + + /* rt->rngMultiplier = 0x5DEECE66DL */ + JSLL_ISHL(tmp, 0x5, 32); + JSLL_UI2L(tmp2, 0xDEECE66DL); + JSLL_OR(rt->rngMultiplier, tmp, tmp2); + + /* rt->rngAddend = 0xBL */ + JSLL_I2L(rt->rngAddend, 0xBL); + + /* rt->rngMask = (1L << 48) - 1 */ + JSLL_I2L(tmp, 1); + JSLL_SHL(tmp2, tmp, 48); + JSLL_SUB(rt->rngMask, tmp2, tmp); + + /* rt->rngDscale = (jsdouble)(1L << 53) */ + JSLL_SHL(tmp2, tmp, 53); + JSLL_L2D(rt->rngDscale, tmp2); + + /* Finally, set the seed from current time. */ + random_setSeed(rt, PRMJ_Now()); +} + +static uint32 +random_next(JSRuntime *rt, int bits) +{ + int64 nextseed, tmp; + uint32 retval; + + JSLL_MUL(nextseed, rt->rngSeed, rt->rngMultiplier); + JSLL_ADD(nextseed, nextseed, rt->rngAddend); + JSLL_AND(nextseed, nextseed, rt->rngMask); + rt->rngSeed = nextseed; + JSLL_USHR(tmp, nextseed, 48 - bits); + JSLL_L2I(retval, tmp); + return retval; +} + +static jsdouble +random_nextDouble(JSRuntime *rt) +{ + int64 tmp, tmp2; + jsdouble d; + + JSLL_ISHL(tmp, random_next(rt, 26), 27); + JSLL_UI2L(tmp2, random_next(rt, 27)); + JSLL_ADD(tmp, tmp, tmp2); + JSLL_L2D(d, tmp); + return d / rt->rngDscale; +} + +static JSBool +math_random(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSRuntime *rt; + jsdouble z; + + rt = cx->runtime; + JS_LOCK_RUNTIME(rt); + random_init(rt); + z = random_nextDouble(rt); + JS_UNLOCK_RUNTIME(rt); + return js_NewNumberValue(cx, z, rval); +} + +static JSBool +math_round(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsdouble x, z; + + if (!js_ValueToNumber(cx, argv[0], &x)) + return JS_FALSE; + z = fd_copysign(fd_floor(x + 0.5), x); + return js_NewNumberValue(cx, z, rval); +} + +static JSBool +math_sin(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsdouble x, z; + + if (!js_ValueToNumber(cx, argv[0], &x)) + return JS_FALSE; + z = fd_sin(x); + return js_NewNumberValue(cx, z, rval); +} + +static JSBool +math_sqrt(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsdouble x, z; + + if (!js_ValueToNumber(cx, argv[0], &x)) + return JS_FALSE; + z = fd_sqrt(x); + return js_NewNumberValue(cx, z, rval); +} + +static JSBool +math_tan(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsdouble x, z; + + if (!js_ValueToNumber(cx, argv[0], &x)) + return JS_FALSE; + z = fd_tan(x); + return js_NewNumberValue(cx, z, rval); +} + +#if JS_HAS_TOSOURCE +static JSBool +math_toSource(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + *rval = ATOM_KEY(cx->runtime->atomState.MathAtom); + return JS_TRUE; +} +#endif + +static JSFunctionSpec math_static_methods[] = { +#if JS_HAS_TOSOURCE + {js_toSource_str, math_toSource, 0, 0, 0}, +#endif + {"abs", math_abs, 1, 0, 0}, + {"acos", math_acos, 1, 0, 0}, + {"asin", math_asin, 1, 0, 0}, + {"atan", math_atan, 1, 0, 0}, + {"atan2", math_atan2, 2, 0, 0}, + {"ceil", math_ceil, 1, 0, 0}, + {"cos", math_cos, 1, 0, 0}, + {"exp", math_exp, 1, 0, 0}, + {"floor", math_floor, 1, 0, 0}, + {"log", math_log, 1, 0, 0}, + {"max", math_max, 2, 0, 0}, + {"min", math_min, 2, 0, 0}, + {"pow", math_pow, 2, 0, 0}, + {"random", math_random, 0, 0, 0}, + {"round", math_round, 1, 0, 0}, + {"sin", math_sin, 1, 0, 0}, + {"sqrt", math_sqrt, 1, 0, 0}, + {"tan", math_tan, 1, 0, 0}, + {0,0,0,0,0} +}; + +JSObject * +js_InitMathClass(JSContext *cx, JSObject *obj) +{ + JSObject *Math; + + Math = JS_DefineObject(cx, obj, "Math", &math_class, NULL, 0); + if (!Math) + return NULL; + if (!JS_DefineFunctions(cx, Math, math_static_methods)) + return NULL; + if (!JS_DefineConstDoubles(cx, Math, math_constants)) + return NULL; + return Math; +} diff --git a/src/dom/js/jsmath.h b/src/dom/js/jsmath.h new file mode 100644 index 000000000..7a6b21657 --- /dev/null +++ b/src/dom/js/jsmath.h @@ -0,0 +1,55 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* -*- Mode: C; tab-width: 8 -*- + * Copyright (C) 1998-1999 Netscape Communications Corporation, All Rights Reserved. + */ + +#ifndef jsmath_h___ +#define jsmath_h___ +/* + * JS math functions. + */ + +JS_BEGIN_EXTERN_C + +extern JSObject * +js_InitMathClass(JSContext *cx, JSObject *obj); + +JS_END_EXTERN_C + +#endif /* jsmath_h___ */ diff --git a/src/dom/js/jsnum.c b/src/dom/js/jsnum.c new file mode 100644 index 000000000..6f6963b69 --- /dev/null +++ b/src/dom/js/jsnum.c @@ -0,0 +1,1058 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * IBM Corp. + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS number type and wrapper class. + */ +#include "jsstddef.h" +#if defined(XP_WIN) || defined(XP_OS2) +#include +#endif +#include +#include +#include +#include "jstypes.h" +#include "jsutil.h" /* Added by JSIFY */ +#include "jsapi.h" +#include "jsatom.h" +#include "jscntxt.h" +#include "jsconfig.h" +#include "jsdtoa.h" +#include "jsgc.h" +#include "jsinterp.h" +#include "jsnum.h" +#include "jsobj.h" +#include "jsopcode.h" +#include "jsprf.h" +#include "jsstr.h" + +static JSBool +num_isNaN(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsdouble x; + + if (!js_ValueToNumber(cx, argv[0], &x)) + return JS_FALSE; + *rval = BOOLEAN_TO_JSVAL(JSDOUBLE_IS_NaN(x)); + return JS_TRUE; +} + +static JSBool +num_isFinite(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsdouble x; + + if (!js_ValueToNumber(cx, argv[0], &x)) + return JS_FALSE; + *rval = BOOLEAN_TO_JSVAL(JSDOUBLE_IS_FINITE(x)); + return JS_TRUE; +} + +static JSBool +num_parseFloat(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str; + jsdouble d; + const jschar *bp, *ep; + + str = js_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + /* XXXbe js_strtod shouldn't require NUL termination */ + bp = js_UndependString(cx, str); + if (!bp) + return JS_FALSE; + if (!js_strtod(cx, bp, &ep, &d)) + return JS_FALSE; + if (ep == bp) { + *rval = DOUBLE_TO_JSVAL(cx->runtime->jsNaN); + return JS_TRUE; + } + return js_NewNumberValue(cx, d, rval); +} + +/* See ECMA 15.1.2.2. */ +static JSBool +num_parseInt(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str; + jsint radix; + jsdouble d; + const jschar *bp, *ep; + + str = js_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + + if (argc > 1) { + if (!js_ValueToECMAInt32(cx, argv[1], &radix)) + return JS_FALSE; + } else + radix = 0; + + if (radix != 0 && (radix < 2 || radix > 36)) { + *rval = DOUBLE_TO_JSVAL(cx->runtime->jsNaN); + return JS_TRUE; + } + /* XXXbe js_strtointeger shouldn't require NUL termination */ + bp = js_UndependString(cx, str); + if (!bp) + return JS_FALSE; + if (!js_strtointeger(cx, bp, &ep, radix, &d)) + return JS_FALSE; + if (ep == bp) { + *rval = DOUBLE_TO_JSVAL(cx->runtime->jsNaN); + return JS_TRUE; + } + return js_NewNumberValue(cx, d, rval); +} + +const char js_Infinity_str[] = "Infinity"; +const char js_NaN_str[] = "NaN"; +const char js_isNaN_str[] = "isNaN"; +const char js_isFinite_str[] = "isFinite"; +const char js_parseFloat_str[] = "parseFloat"; +const char js_parseInt_str[] = "parseInt"; + +static JSFunctionSpec number_functions[] = { + {"isNaN", num_isNaN, 1,0,0}, + {"isFinite", num_isFinite, 1,0,0}, + {"parseFloat", num_parseFloat, 1,0,0}, + {"parseInt", num_parseInt, 2,0,0}, + {0,0,0,0,0} +}; + +static JSClass number_class = { + "Number", + JSCLASS_HAS_PRIVATE, + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS +}; + +static JSBool +Number(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsdouble d; + jsval v; + + if (argc != 0) { + if (!js_ValueToNumber(cx, argv[0], &d)) + return JS_FALSE; + } else { + d = 0.0; + } + if (!js_NewNumberValue(cx, d, &v)) + return JS_FALSE; + if (!(cx->fp->flags & JSFRAME_CONSTRUCTING)) { + *rval = v; + return JS_TRUE; + } + OBJ_SET_SLOT(cx, obj, JSSLOT_PRIVATE, v); + return JS_TRUE; +} + +#if JS_HAS_TOSOURCE +static JSBool +num_toSource(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsval v; + jsdouble d; + char numBuf[DTOSTR_STANDARD_BUFFER_SIZE], *numStr; + char buf[64]; + JSString *str; + + if (!JS_InstanceOf(cx, obj, &number_class, argv)) + return JS_FALSE; + v = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE); + JS_ASSERT(JSVAL_IS_NUMBER(v)); + d = JSVAL_IS_INT(v) ? (jsdouble)JSVAL_TO_INT(v) : *JSVAL_TO_DOUBLE(v); + numStr = JS_dtostr(numBuf, sizeof numBuf, DTOSTR_STANDARD, 0, d); + if (!numStr) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + JS_snprintf(buf, sizeof buf, "(new %s(%s))", number_class.name, numStr); + str = JS_NewStringCopyZ(cx, buf); + if (!str) + return JS_FALSE; + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} +#endif + +/* The buf must be big enough for MIN_INT to fit including '-' and '\0'. */ +static char * +IntToString(jsint i, char *buf, size_t bufSize) +{ + char *cp; + jsuint u; + + u = (i < 0) ? -i : i; + + cp = buf + bufSize; /* one past last buffer cell */ + *--cp = '\0'; /* null terminate the string to be */ + + /* + * Build the string from behind. We use multiply and subtraction + * instead of modulus because that's much faster. + */ + do { + jsuint newu = u / 10; + *--cp = (char)(u - newu * 10) + '0'; + u = newu; + } while (u != 0); + + if (i < 0) + *--cp = '-'; + + return cp; +} + +static JSBool +num_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsval v; + jsdouble d; + jsint base; + JSString *str; + + if (!JS_InstanceOf(cx, obj, &number_class, argv)) + return JS_FALSE; + v = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE); + JS_ASSERT(JSVAL_IS_NUMBER(v)); + d = JSVAL_IS_INT(v) ? (jsdouble)JSVAL_TO_INT(v) : *JSVAL_TO_DOUBLE(v); + base = 10; + if (argc != 0) { + if (!js_ValueToECMAInt32(cx, argv[0], &base)) + return JS_FALSE; + if (base < 2 || base > 36) { + char numBuf[12]; + char *numStr = IntToString(base, numBuf, sizeof numBuf); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_RADIX, + numStr); + return JS_FALSE; + } + } + if (base == 10) + str = js_NumberToString(cx, d); + else { + char *dStr = JS_dtobasestr(base, d); + if (!dStr) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + str = JS_NewStringCopyZ(cx, dStr); + free(dStr); + } + if (!str) + return JS_FALSE; + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +static JSBool +num_toLocaleString(JSContext *cx, JSObject *obj, uintN argc, + jsval *argv, jsval *rval) +{ +/* + * For now, forcibly ignore the first (or any) argument and return toString(). + * ECMA allows this, although it doesn't 'encourage it'. + * [The first argument is being reserved by ECMA and we don't want it confused + * with a radix] + */ + return num_toString(cx, obj, 0, argv, rval); +} + +static JSBool +num_valueOf(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + if (!JS_InstanceOf(cx, obj, &number_class, argv)) + return JS_FALSE; + *rval = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE); + return JS_TRUE; +} + + +#if JS_HAS_NUMBER_FORMATS +#define MAX_PRECISION 100 + +static JSBool +num_to(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval, JSDToStrMode zeroArgMode, + JSDToStrMode oneArgMode, jsint precisionMin, jsint precisionMax, jsint precisionOffset) +{ + jsval v; + jsdouble d, precision; + JSString *str; + char buf[DTOSTR_VARIABLE_BUFFER_SIZE(MAX_PRECISION+1)], *numStr; /* Use MAX_PRECISION+1 because precisionOffset can be 1 */ + + if (!JS_InstanceOf(cx, obj, &number_class, argv)) + return JS_FALSE; + v = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE); + JS_ASSERT(JSVAL_IS_NUMBER(v)); + d = JSVAL_IS_INT(v) ? (jsdouble)JSVAL_TO_INT(v) : *JSVAL_TO_DOUBLE(v); + + if (JSVAL_IS_VOID(argv[0])) { + precision = 0.0; + oneArgMode = zeroArgMode; + } else { + if (!js_ValueToNumber(cx, argv[0], &precision)) + return JS_FALSE; + precision = js_DoubleToInteger(precision); + if (precision < precisionMin || precision > precisionMax) { + numStr = JS_dtostr(buf, sizeof buf, DTOSTR_STANDARD, 0, precision); + if (!numStr) + JS_ReportOutOfMemory(cx); + else + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_PRECISION_RANGE, numStr); + return JS_FALSE; + } + } + + numStr = JS_dtostr(buf, sizeof buf, oneArgMode, (jsint)precision + precisionOffset, d); + if (!numStr) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + str = JS_NewStringCopyZ(cx, numStr); + if (!str) + return JS_FALSE; + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +static JSBool +num_toFixed(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + /* We allow a larger range of precision than ECMA requires; this is permitted by ECMA. */ + return num_to(cx, obj, argc, argv, rval, DTOSTR_FIXED, DTOSTR_FIXED, -20, MAX_PRECISION, 0); +} + +static JSBool +num_toExponential(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + /* We allow a larger range of precision than ECMA requires; this is permitted by ECMA. */ + return num_to(cx, obj, argc, argv, rval, DTOSTR_STANDARD_EXPONENTIAL, DTOSTR_EXPONENTIAL, 0, MAX_PRECISION, 1); +} + +static JSBool +num_toPrecision(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + /* We allow a larger range of precision than ECMA requires; this is permitted by ECMA. */ + return num_to(cx, obj, argc, argv, rval, DTOSTR_STANDARD, DTOSTR_PRECISION, 1, MAX_PRECISION, 0); +} +#endif /* JS_HAS_NUMBER_FORMATS */ + + +static JSFunctionSpec number_methods[] = { +#if JS_HAS_TOSOURCE + {js_toSource_str, num_toSource, 0,0,0}, +#endif + {js_toString_str, num_toString, 0,0,0}, + {js_toLocaleString_str, num_toLocaleString, 0,0,0}, + {js_valueOf_str, num_valueOf, 0,0,0}, +#if JS_HAS_NUMBER_FORMATS + {"toFixed", num_toFixed, 1,0,0}, + {"toExponential", num_toExponential, 1,0,0}, + {"toPrecision", num_toPrecision, 1,0,0}, +#endif + {0,0,0,0,0} +}; + +/* NB: Keep this in synch with number_constants[]. */ +enum nc_slot { + NC_NaN, + NC_POSITIVE_INFINITY, + NC_NEGATIVE_INFINITY, + NC_MAX_VALUE, + NC_MIN_VALUE, + NC_LIMIT +}; + +/* + * Some to most C compilers forbid spelling these at compile time, or barf + * if you try, so all but MAX_VALUE are set up by js_InitRuntimeNumberState + * using union jsdpun. + */ +static JSConstDoubleSpec number_constants[] = { + {0, js_NaN_str, 0,{0,0,0}}, + {0, "POSITIVE_INFINITY", 0,{0,0,0}}, + {0, "NEGATIVE_INFINITY", 0,{0,0,0}}, + {1.7976931348623157E+308, "MAX_VALUE", 0,{0,0,0}}, + {0, "MIN_VALUE", 0,{0,0,0}}, + {0,0,0,{0,0,0}} +}; + +static jsdouble NaN; + + +#if defined XP_WIN && \ + !defined __MWERKS__ && \ + (defined _M_IX86 || \ + (defined __GNUC__ && !defined __MINGW32__)) + +/* + * Set the exception mask to mask all exceptions and set the FPU precision + * to 53 bit mantissa. + * On Alpha platform this is handled via Compiler option. + */ +#define FIX_FPU() _control87(MCW_EM | PC_53, MCW_EM | MCW_PC) + +#else + +#define FIX_FPU() ((void)0) + +#endif + +JSBool +js_InitRuntimeNumberState(JSContext *cx) +{ + JSRuntime *rt; + jsdpun u; + + rt = cx->runtime; + JS_ASSERT(!rt->jsNaN); + + FIX_FPU(); + + u.s.hi = JSDOUBLE_HI32_EXPMASK | JSDOUBLE_HI32_MANTMASK; + u.s.lo = 0xffffffff; + number_constants[NC_NaN].dval = NaN = u.d; + rt->jsNaN = js_NewDouble(cx, NaN); + if (!rt->jsNaN || !js_LockGCThing(cx, rt->jsNaN)) + return JS_FALSE; + + u.s.hi = JSDOUBLE_HI32_EXPMASK; + u.s.lo = 0x00000000; + number_constants[NC_POSITIVE_INFINITY].dval = u.d; + rt->jsPositiveInfinity = js_NewDouble(cx, u.d); + if (!rt->jsPositiveInfinity || + !js_LockGCThing(cx, rt->jsPositiveInfinity)) { + return JS_FALSE; + } + + u.s.hi = JSDOUBLE_HI32_SIGNBIT | JSDOUBLE_HI32_EXPMASK; + u.s.lo = 0x00000000; + number_constants[NC_NEGATIVE_INFINITY].dval = u.d; + rt->jsNegativeInfinity = js_NewDouble(cx, u.d); + if (!rt->jsNegativeInfinity || + !js_LockGCThing(cx, rt->jsNegativeInfinity)) { + return JS_FALSE; + } + + u.s.hi = 0; + u.s.lo = 1; + number_constants[NC_MIN_VALUE].dval = u.d; + + return JS_TRUE; +} + +void +js_FinishRuntimeNumberState(JSContext *cx) +{ + JSRuntime *rt = cx->runtime; + + js_UnlockGCThingRT(rt, rt->jsNaN); + js_UnlockGCThingRT(rt, rt->jsNegativeInfinity); + js_UnlockGCThingRT(rt, rt->jsPositiveInfinity); + + rt->jsNaN = NULL; + rt->jsNegativeInfinity = NULL; + rt->jsPositiveInfinity = NULL; +} + +JSObject * +js_InitNumberClass(JSContext *cx, JSObject *obj) +{ + JSObject *proto, *ctor; + JSRuntime *rt; + + /* XXX must do at least once per new thread, so do it per JSContext... */ + FIX_FPU(); + + if (!JS_DefineFunctions(cx, obj, number_functions)) + return NULL; + + proto = JS_InitClass(cx, obj, NULL, &number_class, Number, 1, + NULL, number_methods, NULL, NULL); + if (!proto || !(ctor = JS_GetConstructor(cx, proto))) + return NULL; + OBJ_SET_SLOT(cx, proto, JSSLOT_PRIVATE, JSVAL_ZERO); + if (!JS_DefineConstDoubles(cx, ctor, number_constants)) + return NULL; + + /* ECMA 15.1.1.1 */ + rt = cx->runtime; + if (!JS_DefineProperty(cx, obj, js_NaN_str, DOUBLE_TO_JSVAL(rt->jsNaN), + NULL, NULL, JSPROP_PERMANENT)) { + return NULL; + } + + /* ECMA 15.1.1.2 */ + if (!JS_DefineProperty(cx, obj, js_Infinity_str, + DOUBLE_TO_JSVAL(rt->jsPositiveInfinity), + NULL, NULL, JSPROP_PERMANENT)) { + return NULL; + } + return proto; +} + +jsdouble * +js_NewDouble(JSContext *cx, jsdouble d) +{ + jsdouble *dp; + + dp = (jsdouble *) js_AllocGCThing(cx, GCX_DOUBLE); + if (!dp) + return NULL; + *dp = d; + return dp; +} + +void +js_FinalizeDouble(JSContext *cx, jsdouble *dp) +{ + *dp = NaN; +} + +JSBool +js_NewDoubleValue(JSContext *cx, jsdouble d, jsval *rval) +{ + jsdouble *dp; + + dp = js_NewDouble(cx, d); + if (!dp) + return JS_FALSE; + *rval = DOUBLE_TO_JSVAL(dp); + return JS_TRUE; +} + +JSBool +js_NewNumberValue(JSContext *cx, jsdouble d, jsval *rval) +{ + jsint i; + JSBool ok; + + SET_FPU(); + + if (JSDOUBLE_IS_INT(d, i) && INT_FITS_IN_JSVAL(i)) { + *rval = INT_TO_JSVAL(i); + ok = JS_TRUE; + } else { + ok = js_NewDoubleValue(cx, d, rval); + } + + RESTORE_FPU(); + return ok; +} + +JSObject * +js_NumberToObject(JSContext *cx, jsdouble d) +{ + JSObject *obj; + jsval v; + + obj = js_NewObject(cx, &number_class, NULL, NULL); + if (!obj) + return NULL; + if (!js_NewNumberValue(cx, d, &v)) { + cx->newborn[GCX_OBJECT] = NULL; + return NULL; + } + OBJ_SET_SLOT(cx, obj, JSSLOT_PRIVATE, v); + return obj; +} + +JSString * +js_NumberToString(JSContext *cx, jsdouble d) +{ + jsint i; + char buf[DTOSTR_STANDARD_BUFFER_SIZE]; + char *numStr; + + if (JSDOUBLE_IS_INT(d, i)) + numStr = IntToString(i, buf, sizeof buf); + else { + numStr = JS_dtostr(buf, sizeof buf, DTOSTR_STANDARD, 0, d); + if (!numStr) { + JS_ReportOutOfMemory(cx); + return NULL; + } + } + return JS_NewStringCopyZ(cx, numStr); +} + +JSBool +js_ValueToNumber(JSContext *cx, jsval v, jsdouble *dp) +{ + JSObject *obj; + JSString *str; + const jschar *bp, *ep; + + if (JSVAL_IS_OBJECT(v)) { + obj = JSVAL_TO_OBJECT(v); + if (!obj) { + *dp = 0; + return JS_TRUE; + } + if (!OBJ_DEFAULT_VALUE(cx, obj, JSTYPE_NUMBER, &v)) + return JS_FALSE; + } + if (JSVAL_IS_INT(v)) { + *dp = (jsdouble)JSVAL_TO_INT(v); + } else if (JSVAL_IS_DOUBLE(v)) { + *dp = *JSVAL_TO_DOUBLE(v); + } else if (JSVAL_IS_STRING(v)) { + str = JSVAL_TO_STRING(v); + /* + * Note that ECMA doesn't treat a string beginning with a '0' as an + * octal number here. This works because all such numbers will be + * interpreted as decimal by js_strtod and will never get passed to + * js_strtointeger (which would interpret them as octal). + */ + /* XXXbe js_strtod shouldn't require NUL termination */ + bp = js_UndependString(cx, str); + if (!bp) + return JS_FALSE; + if ((!js_strtod(cx, bp, &ep, dp) || + js_SkipWhiteSpace(ep) != bp + str->length) && + (!js_strtointeger(cx, bp, &ep, 0, dp) || + js_SkipWhiteSpace(ep) != bp + str->length)) { + goto badstr; + } + } else if (JSVAL_IS_BOOLEAN(v)) { + *dp = JSVAL_TO_BOOLEAN(v) ? 1 : 0; + } else { +#if JS_BUG_FALLIBLE_TONUM + str = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, v, NULL); +badstr: + if (str) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NAN, + JS_GetStringBytes(str)); + + } + return JS_FALSE; +#else +badstr: + *dp = *cx->runtime->jsNaN; +#endif + } + return JS_TRUE; +} + +JSBool +js_ValueToECMAInt32(JSContext *cx, jsval v, int32 *ip) +{ + jsdouble d; + + if (!js_ValueToNumber(cx, v, &d)) + return JS_FALSE; + return js_DoubleToECMAInt32(cx, d, ip); +} + +JSBool +js_DoubleToECMAInt32(JSContext *cx, jsdouble d, int32 *ip) +{ + jsdouble two32 = 4294967296.0; + jsdouble two31 = 2147483648.0; + + if (!JSDOUBLE_IS_FINITE(d) || d == 0) { + *ip = 0; + return JS_TRUE; + } + d = fmod(d, two32); + d = (d >= 0) ? floor(d) : ceil(d) + two32; + if (d >= two31) + *ip = (int32)(d - two32); + else + *ip = (int32)d; + return JS_TRUE; +} + +JSBool +js_ValueToECMAUint32(JSContext *cx, jsval v, uint32 *ip) +{ + jsdouble d; + + if (!js_ValueToNumber(cx, v, &d)) + return JS_FALSE; + return js_DoubleToECMAUint32(cx, d, ip); +} + +JSBool +js_DoubleToECMAUint32(JSContext *cx, jsdouble d, uint32 *ip) +{ + JSBool neg; + jsdouble two32 = 4294967296.0; + + if (!JSDOUBLE_IS_FINITE(d) || d == 0) { + *ip = 0; + return JS_TRUE; + } + + neg = (d < 0); + d = floor(neg ? -d : d); + d = neg ? -d : d; + + d = fmod(d, two32); + + d = (d >= 0) ? d : d + two32; + *ip = (uint32)d; + return JS_TRUE; +} + +JSBool +js_ValueToInt32(JSContext *cx, jsval v, int32 *ip) +{ + jsdouble d; + JSString *str; + + if (JSVAL_IS_INT(v)) { + *ip = JSVAL_TO_INT(v); + return JS_TRUE; + } + if (!js_ValueToNumber(cx, v, &d)) + return JS_FALSE; + if (JSDOUBLE_IS_NaN(d) || d <= -2147483649.0 || 2147483648.0 <= d) { + str = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, v, NULL); + if (str) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_CANT_CONVERT, JS_GetStringBytes(str)); + + } + return JS_FALSE; + } + *ip = (int32)floor(d + 0.5); /* Round to nearest */ + return JS_TRUE; +} + +JSBool +js_ValueToUint16(JSContext *cx, jsval v, uint16 *ip) +{ + jsdouble d; + jsuint i, m; + JSBool neg; + + if (!js_ValueToNumber(cx, v, &d)) + return JS_FALSE; + if (d == 0 || !JSDOUBLE_IS_FINITE(d)) { + *ip = 0; + return JS_TRUE; + } + i = (jsuint)d; + if ((jsdouble)i == d) { + *ip = (uint16)i; + return JS_TRUE; + } + neg = (d < 0); + d = floor(neg ? -d : d); + d = neg ? -d : d; + m = JS_BIT(16); + d = fmod(d, (double)m); + if (d < 0) + d += m; + *ip = (uint16) d; + return JS_TRUE; +} + +jsdouble +js_DoubleToInteger(jsdouble d) +{ + JSBool neg; + + if (d == 0) + return d; + if (!JSDOUBLE_IS_FINITE(d)) { + if (JSDOUBLE_IS_NaN(d)) + return 0; + return d; + } + neg = (d < 0); + d = floor(neg ? -d : d); + return neg ? -d : d; +} + + +JSBool +js_strtod(JSContext *cx, const jschar *s, const jschar **ep, jsdouble *dp) +{ + char cbuf[32]; + size_t i; + char *cstr, *istr, *estr; + JSBool negative; + jsdouble d; + const jschar *s1 = js_SkipWhiteSpace(s); + size_t length = js_strlen(s1); + + /* Use cbuf to avoid malloc */ + if (length >= sizeof cbuf) { + cstr = (char *) JS_malloc(cx, length + 1); + if (!cstr) + return JS_FALSE; + } else { + cstr = cbuf; + } + + for (i = 0; i <= length; i++) { + if (s1[i] >> 8) { + cstr[i] = 0; + break; + } + cstr[i] = (char)s1[i]; + } + + istr = cstr; + if ((negative = (*istr == '-')) != 0 || *istr == '+') + istr++; + if (!strncmp(istr, js_Infinity_str, sizeof js_Infinity_str - 1)) { + d = *(negative ? cx->runtime->jsNegativeInfinity : cx->runtime->jsPositiveInfinity); + estr = istr + 8; + } else { + int err; + d = JS_strtod(cstr, &estr, &err); + if (err == JS_DTOA_ENOMEM) { + JS_ReportOutOfMemory(cx); + if (cstr != cbuf) + JS_free(cx, cstr); + return JS_FALSE; + } + if (err == JS_DTOA_ERANGE) { + if (d == HUGE_VAL) + d = *cx->runtime->jsPositiveInfinity; + else if (d == -HUGE_VAL) + d = *cx->runtime->jsNegativeInfinity; + } +#ifdef HPUX + if (d == 0.0 && negative) { + /* + * "-0", "-1e-2000" come out as positive zero + * here on HPUX. Force a negative zero instead. + */ + JSDOUBLE_HI32(d) = JSDOUBLE_HI32_SIGNBIT; + JSDOUBLE_LO32(d) = 0; + } +#endif + } + + i = estr - cstr; + if (cstr != cbuf) + JS_free(cx, cstr); + *ep = i ? s1 + i : s; + *dp = d; + return JS_TRUE; +} + +struct BinaryDigitReader +{ + uintN base; /* Base of number; must be a power of 2 */ + uintN digit; /* Current digit value in radix given by base */ + uintN digitMask; /* Mask to extract the next bit from digit */ + const jschar *digits; /* Pointer to the remaining digits */ + const jschar *end; /* Pointer to first non-digit */ +}; + +/* Return the next binary digit from the number or -1 if done */ +static intN GetNextBinaryDigit(struct BinaryDigitReader *bdr) +{ + intN bit; + + if (bdr->digitMask == 0) { + uintN c; + + if (bdr->digits == bdr->end) + return -1; + + c = *bdr->digits++; + if ('0' <= c && c <= '9') + bdr->digit = c - '0'; + else if ('a' <= c && c <= 'z') + bdr->digit = c - 'a' + 10; + else bdr->digit = c - 'A' + 10; + bdr->digitMask = bdr->base >> 1; + } + bit = (bdr->digit & bdr->digitMask) != 0; + bdr->digitMask >>= 1; + return bit; +} + +JSBool +js_strtointeger(JSContext *cx, const jschar *s, const jschar **ep, jsint base, jsdouble *dp) +{ + JSBool negative; + jsdouble value; + const jschar *start; + const jschar *s1 = js_SkipWhiteSpace(s); + + if ((negative = (*s1 == '-')) != 0 || *s1 == '+') + s1++; + + if (base == 0) { + /* No base supplied, or some base that evaluated to 0. */ + if (*s1 == '0') { + /* It's either hex or octal; only increment char if str isn't '0' */ + if (s1[1] == 'X' || s1[1] == 'x') { /* Hex */ + s1 += 2; + base = 16; + } else { /* Octal */ + base = 8; + } + } else { + base = 10; /* Default to decimal. */ + } + } else if (base == 16 && *s1 == '0' && (s1[1] == 'X' || s1[1] == 'x')) { + /* If base is 16, ignore hex prefix. */ + s1 += 2; + } + + /* + * Done with the preliminaries; find some prefix of the string that's + * a number in the given base. + */ + start = s1; /* Mark - if string is empty, we return NaN. */ + value = 0.0; + for (;;) { + uintN digit; + jschar c = *s1; + if ('0' <= c && c <= '9') + digit = c - '0'; + else if ('a' <= c && c <= 'z') + digit = c - 'a' + 10; + else if ('A' <= c && c <= 'Z') + digit = c - 'A' + 10; + else + break; + if (digit >= (uintN)base) + break; + value = value * base + digit; + s1++; + } + + if (value >= 9007199254740992.0) { + if (base == 10) { + /* + * If we're accumulating a decimal number and the number is >= + * 2^53, then the result from the repeated multiply-add above may + * be inaccurate. Call JS_strtod to get the correct answer. + */ + size_t i; + size_t length = s1 - start; + char *cstr = (char *) JS_malloc(cx, length + 1); + char *estr; + int err=0; + + if (!cstr) + return JS_FALSE; + for (i = 0; i != length; i++) + cstr[i] = (char)start[i]; + cstr[length] = 0; + + value = JS_strtod(cstr, &estr, &err); + if (err == JS_DTOA_ENOMEM) { + JS_ReportOutOfMemory(cx); + JS_free(cx, cstr); + return JS_FALSE; + } + if (err == JS_DTOA_ERANGE && value == HUGE_VAL) + value = *cx->runtime->jsPositiveInfinity; + JS_free(cx, cstr); + } else if ((base & (base - 1)) == 0) { + /* + * The number may also be inaccurate for power-of-two bases. This + * happens if the addition in value * base + digit causes a round- + * down to an even least significant mantissa bit when the first + * dropped bit is a one. If any of the following digits in the + * number (which haven't been added in yet) are nonzero, then the + * correct action would have been to round up instead of down. An + * example occurs when reading the number 0x1000000000000081, which + * rounds to 0x1000000000000000 instead of 0x1000000000000100. + */ + struct BinaryDigitReader bdr; + intN bit, bit2; + intN j; + + bdr.base = base; + bdr.digitMask = 0; + bdr.digits = start; + bdr.end = s1; + value = 0.0; + + /* Skip leading zeros. */ + do { + bit = GetNextBinaryDigit(&bdr); + } while (bit == 0); + + if (bit == 1) { + /* Gather the 53 significant bits (including the leading 1) */ + value = 1.0; + for (j = 52; j; j--) { + bit = GetNextBinaryDigit(&bdr); + if (bit < 0) + goto done; + value = value*2 + bit; + } + /* bit2 is the 54th bit (the first dropped from the mantissa) */ + bit2 = GetNextBinaryDigit(&bdr); + if (bit2 >= 0) { + jsdouble factor = 2.0; + intN sticky = 0; /* sticky is 1 if any bit beyond the 54th is 1 */ + intN bit3; + + while ((bit3 = GetNextBinaryDigit(&bdr)) >= 0) { + sticky |= bit3; + factor *= 2; + } + value += bit2 & (bit | sticky); + value *= factor; + } + done:; + } + } + } + /* We don't worry about inaccurate numbers for any other base. */ + + if (s1 == start) { + *dp = 0.0; + *ep = s; + } else { + *dp = negative ? -value : value; + *ep = s1; + } + return JS_TRUE; +} diff --git a/src/dom/js/jsnum.h b/src/dom/js/jsnum.h new file mode 100644 index 000000000..46992e2b6 --- /dev/null +++ b/src/dom/js/jsnum.h @@ -0,0 +1,280 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsnum_h___ +#define jsnum_h___ +/* + * JS number (IEEE double) interface. + * + * JS numbers are optimistically stored in the top 31 bits of 32-bit integers, + * but floating point literals, results that overflow 31 bits, and division and + * modulus operands and results require a 64-bit IEEE double. These are GC'ed + * and pointed to by 32-bit jsvals on the stack and in object properties. + * + * When a JS number is treated as an object (followed by . or []), the runtime + * wraps it with a JSObject whose valueOf method returns the unwrapped number. + */ + +JS_BEGIN_EXTERN_C + +/* + * Stefan Hanske reports: + * ARM is a little endian architecture but 64 bit double words are stored + * differently: the 32 bit words are in little endian byte order, the two words + * are stored in big endian`s way. + */ + +#if defined(__arm) || defined(__arm32__) || defined(__arm26__) || defined(__arm__) +#define CPU_IS_ARM +#endif + +typedef union jsdpun { + struct { +#if defined(IS_LITTLE_ENDIAN) && !defined(CPU_IS_ARM) + uint32 lo, hi; +#else + uint32 hi, lo; +#endif + } s; + jsdouble d; +} jsdpun; + +#if (__GNUC__ == 2 && __GNUC_MINOR__ > 95) || __GNUC__ > 2 +/* + * This version of the macros is safe for the alias optimizations that gcc + * does, but uses gcc-specific extensions. + */ + +#define JSDOUBLE_HI32(x) (__extension__ ({ jsdpun u; u.d = (x); u.s.hi; })) +#define JSDOUBLE_LO32(x) (__extension__ ({ jsdpun u; u.d = (x); u.s.lo; })) +#define JSDOUBLE_SET_HI32(x, y) \ + (__extension__ ({ jsdpun u; u.d = (x); u.s.hi = (y); (x) = u.d; })) +#define JSDOUBLE_SET_LO32(x, y) \ + (__extension__ ({ jsdpun u; u.d = (x); u.s.lo = (y); (x) = u.d; })) + +#else /* not or old GNUC */ + +/* + * We don't know of any non-gcc compilers that perform alias optimization, + * so this code should work. + */ + +#if defined(IS_LITTLE_ENDIAN) && !defined(CPU_IS_ARM) +#define JSDOUBLE_HI32(x) (((uint32 *)&(x))[1]) +#define JSDOUBLE_LO32(x) (((uint32 *)&(x))[0]) +#else +#define JSDOUBLE_HI32(x) (((uint32 *)&(x))[0]) +#define JSDOUBLE_LO32(x) (((uint32 *)&(x))[1]) +#endif + +#define JSDOUBLE_SET_HI32(x, y) (JSDOUBLE_HI32(x)=(y)) +#define JSDOUBLE_SET_LO32(x, y) (JSDOUBLE_LO32(x)=(y)) + +#endif /* not or old GNUC */ + +#define JSDOUBLE_HI32_SIGNBIT 0x80000000 +#define JSDOUBLE_HI32_EXPMASK 0x7ff00000 +#define JSDOUBLE_HI32_MANTMASK 0x000fffff + +#define JSDOUBLE_IS_NaN(x) \ + ((JSDOUBLE_HI32(x) & JSDOUBLE_HI32_EXPMASK) == JSDOUBLE_HI32_EXPMASK && \ + (JSDOUBLE_LO32(x) || (JSDOUBLE_HI32(x) & JSDOUBLE_HI32_MANTMASK))) + +#define JSDOUBLE_IS_INFINITE(x) \ + ((JSDOUBLE_HI32(x) & ~JSDOUBLE_HI32_SIGNBIT) == JSDOUBLE_HI32_EXPMASK && \ + !JSDOUBLE_LO32(x)) + +#define JSDOUBLE_IS_FINITE(x) \ + ((JSDOUBLE_HI32(x) & JSDOUBLE_HI32_EXPMASK) != JSDOUBLE_HI32_EXPMASK) + +#define JSDOUBLE_IS_NEGZERO(d) (JSDOUBLE_HI32(d) == JSDOUBLE_HI32_SIGNBIT && \ + JSDOUBLE_LO32(d) == 0) + +/* + * JSDOUBLE_IS_INT first checks that d is neither NaN nor infinite, to avoid + * raising SIGFPE on platforms such as Alpha Linux, then (only if the cast is + * safe) leaves i as (jsint)d. This also avoid anomalous NaN floating point + * comparisons under MSVC. + */ +#define JSDOUBLE_IS_INT(d, i) (JSDOUBLE_IS_FINITE(d) \ + && !JSDOUBLE_IS_NEGZERO(d) \ + && ((d) == (i = (jsint)(d)))) + +/* Initialize number constants and runtime state for the first context. */ +extern JSBool +js_InitRuntimeNumberState(JSContext *cx); + +extern void +js_FinishRuntimeNumberState(JSContext *cx); + +/* Initialize the Number class, returning its prototype object. */ +extern JSObject * +js_InitNumberClass(JSContext *cx, JSObject *obj); + +/* + * String constants for global function names, used in jsapi.c and jsnum.c. + */ +extern const char js_Infinity_str[]; +extern const char js_NaN_str[]; +extern const char js_isNaN_str[]; +extern const char js_isFinite_str[]; +extern const char js_parseFloat_str[]; +extern const char js_parseInt_str[]; + +/* GC-allocate a new JS number. */ +extern jsdouble * +js_NewDouble(JSContext *cx, jsdouble d); + +extern void +js_FinalizeDouble(JSContext *cx, jsdouble *dp); + +extern JSBool +js_NewDoubleValue(JSContext *cx, jsdouble d, jsval *rval); + +extern JSBool +js_NewNumberValue(JSContext *cx, jsdouble d, jsval *rval); + +/* Construct a Number instance that wraps around d. */ +extern JSObject * +js_NumberToObject(JSContext *cx, jsdouble d); + +/* Convert a number to a GC'ed string. */ +extern JSString * +js_NumberToString(JSContext *cx, jsdouble d); + +/* + * Convert a value to a number, returning false after reporting any error, + * otherwise returning true with *dp set. + */ +extern JSBool +js_ValueToNumber(JSContext *cx, jsval v, jsdouble *dp); + +/* + * Convert a value or a double to an int32, according to the ECMA rules + * for ToInt32. + */ +extern JSBool +js_ValueToECMAInt32(JSContext *cx, jsval v, int32 *ip); + +extern JSBool +js_DoubleToECMAInt32(JSContext *cx, jsdouble d, int32 *ip); + +/* + * Convert a value or a double to a uint32, according to the ECMA rules + * for ToUint32. + */ +extern JSBool +js_ValueToECMAUint32(JSContext *cx, jsval v, uint32 *ip); + +extern JSBool +js_DoubleToECMAUint32(JSContext *cx, jsdouble d, uint32 *ip); + +/* + * Convert a value to a number, then to an int32 if it fits by rounding to + * nearest; but failing with an error report if the double is out of range + * or unordered. + */ +extern JSBool +js_ValueToInt32(JSContext *cx, jsval v, int32 *ip); + +/* + * Convert a value to a number, then to a uint16 according to the ECMA rules + * for ToUint16. + */ +extern JSBool +js_ValueToUint16(JSContext *cx, jsval v, uint16 *ip); + +/* + * Convert a jsdouble to an integral number, stored in a jsdouble. + * If d is NaN, return 0. If d is an infinity, return it without conversion. + */ +extern jsdouble +js_DoubleToInteger(jsdouble d); + +/* + * Similar to strtod except that it replaces overflows with infinities of the + * correct sign, and underflows with zeros of the correct sign. Guaranteed to + * return the closest double number to the given input in dp. + * + * Also allows inputs of the form [+|-]Infinity, which produce an infinity of + * the appropriate sign. The case of the "Infinity" string must match exactly. + * If the string does not contain a number, set *ep to s and return 0.0 in dp. + * Return false if out of memory. + */ +extern JSBool +js_strtod(JSContext *cx, const jschar *s, const jschar **ep, jsdouble *dp); + +/* + * Similar to strtol except that it handles integers of arbitrary size. + * Guaranteed to return the closest double number to the given input when radix + * is 10 or a power of 2. Callers may see round-off errors for very large + * numbers of a different radix than 10 or a power of 2. + * + * If the string does not contain a number, set *ep to s and return 0.0 in dp. + * Return false if out of memory. + */ +extern JSBool +js_strtointeger(JSContext *cx, const jschar *s, const jschar **ep, jsint radix, jsdouble *dp); + + +#ifdef XP_OS2 +/* XXX see bug 224487 + * On OS/2, some system function calls seem to change the FPU control word, + * such that we crash with a floating underflow exception. The FIX_FPU() call + * in jsnum.c does not always work, as sometimes FIX_FPU() is called BEFORE the + * OS/2 system call that horks the FPU control word. So, on OS/2, we need to do + * what the comment at the top of this file (around line 80) suggests: setting + * the FPU precision (& exceptions mask) before calling strtod or dtoa. Also, + * we play nice by reverting to the previous FPU control word at the end of + * those functions. + * + * Set the exception mask to mask all exceptions and set the FPU precision + * to 53 bit mantissa. + */ + #define SET_FPU() unsigned cw = _control87(0,0); \ + _control87((CW_DEFAULT & ~MCW_PC) | PC_53, 0xffff) + #define RESTORE_FPU() _control87(cw, MCW_EM | MCW_IC | MCW_RC | MCW_PC) +#else + #define SET_FPU() ((void)0) + #define RESTORE_FPU() ((void)0) +#endif + +JS_END_EXTERN_C + +#endif /* jsnum_h___ */ diff --git a/src/dom/js/jsobj.c b/src/dom/js/jsobj.c new file mode 100644 index 000000000..1da1f138b --- /dev/null +++ b/src/dom/js/jsobj.c @@ -0,0 +1,3900 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS object implementation. + */ +#include "jsstddef.h" +#include +#include +#include "jstypes.h" +#include "jsarena.h" /* Added by JSIFY */ +#include "jsutil.h" /* Added by JSIFY */ +#include "jshash.h" /* Added by JSIFY */ +#include "jsdhash.h" +#include "jsprf.h" +#include "jsapi.h" +#include "jsarray.h" +#include "jsatom.h" +#include "jsbool.h" +#include "jscntxt.h" +#include "jsconfig.h" +#include "jsfun.h" +#include "jsgc.h" +#include "jsinterp.h" +#include "jslock.h" +#include "jsnum.h" +#include "jsobj.h" +#include "jsscope.h" +#include "jsscript.h" +#include "jsstr.h" +#include "jsopcode.h" + +#include "jsdbgapi.h" /* whether or not JS_HAS_OBJ_WATCHPOINT */ + +#ifdef JS_THREADSAFE +#define NATIVE_DROP_PROPERTY js_DropProperty + +extern void +js_DropProperty(JSContext *cx, JSObject *obj, JSProperty *prop); +#else +#define NATIVE_DROP_PROPERTY NULL +#endif + +#ifdef XP_MAC +#pragma export on +#endif + +JS_FRIEND_DATA(JSObjectOps) js_ObjectOps = { + js_NewObjectMap, js_DestroyObjectMap, +#if defined JS_THREADSAFE && defined DEBUG + _js_LookupProperty, js_DefineProperty, +#else + js_LookupProperty, js_DefineProperty, +#endif + js_GetProperty, js_SetProperty, + js_GetAttributes, js_SetAttributes, + js_DeleteProperty, js_DefaultValue, + js_Enumerate, js_CheckAccess, + NULL, NATIVE_DROP_PROPERTY, + js_Call, js_Construct, + NULL, js_HasInstance, + js_SetProtoOrParent, js_SetProtoOrParent, + js_Mark, js_Clear, + js_GetRequiredSlot, js_SetRequiredSlot +}; + +#ifdef XP_MAC +#pragma export off +#endif + +JSClass js_ObjectClass = { + js_Object_str, + 0, + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS +}; + +#if JS_HAS_OBJ_PROTO_PROP + +static JSBool +obj_getSlot(JSContext *cx, JSObject *obj, jsval id, jsval *vp); + +static JSBool +obj_setSlot(JSContext *cx, JSObject *obj, jsval id, jsval *vp); + +static JSBool +obj_getCount(JSContext *cx, JSObject *obj, jsval id, jsval *vp); + +static JSPropertySpec object_props[] = { + /* These two must come first; see object_props[slot].name usage below. */ + {js_proto_str, JSSLOT_PROTO, JSPROP_PERMANENT|JSPROP_SHARED, + obj_getSlot, obj_setSlot}, + {js_parent_str,JSSLOT_PARENT,JSPROP_READONLY|JSPROP_PERMANENT|JSPROP_SHARED, + obj_getSlot, obj_setSlot}, + {js_count_str, 0, JSPROP_PERMANENT,obj_getCount, obj_getCount}, + {0,0,0,0,0} +}; + +/* NB: JSSLOT_PROTO and JSSLOT_PARENT are already indexes into object_props. */ +#define JSSLOT_COUNT 2 + +static JSBool +ReportStrictSlot(JSContext *cx, uint32 slot) +{ + if (slot == JSSLOT_PROTO) + return JS_TRUE; + return JS_ReportErrorFlagsAndNumber(cx, + JSREPORT_WARNING | JSREPORT_STRICT, + js_GetErrorMessage, NULL, + JSMSG_DEPRECATED_USAGE, + object_props[slot].name); +} + +static JSBool +obj_getSlot(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + uint32 slot; + JSAccessMode mode; + uintN attrs; + + slot = (uint32) JSVAL_TO_INT(id); + if (JS_HAS_STRICT_OPTION(cx) && !ReportStrictSlot(cx, slot)) + return JS_FALSE; + if (id == INT_TO_JSVAL(JSSLOT_PROTO)) { + id = (jsid)cx->runtime->atomState.protoAtom; + mode = JSACC_PROTO; + } else { + id = (jsid)cx->runtime->atomState.parentAtom; + mode = JSACC_PARENT; + } + if (!OBJ_CHECK_ACCESS(cx, obj, id, mode, vp, &attrs)) + return JS_FALSE; + *vp = OBJ_GET_SLOT(cx, obj, slot); + return JS_TRUE; +} + +static JSBool +obj_setSlot(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + JSObject *pobj; + uint32 slot; + uintN attrs; + + if (!JSVAL_IS_OBJECT(*vp)) + return JS_TRUE; + pobj = JSVAL_TO_OBJECT(*vp); + slot = (uint32) JSVAL_TO_INT(id); + if (JS_HAS_STRICT_OPTION(cx) && !ReportStrictSlot(cx, slot)) + return JS_FALSE; + + /* __parent__ is readonly and permanent, only __proto__ may be set. */ + if (!OBJ_CHECK_ACCESS(cx, obj, id, JSACC_PROTO | JSACC_WRITE, vp, &attrs)) + return JS_FALSE; + + return js_SetProtoOrParent(cx, obj, slot, pobj); +} + +static JSBool +obj_getCount(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + jsval iter_state; + jsid num_properties; + JSBool ok; + + if (JS_HAS_STRICT_OPTION(cx) && !ReportStrictSlot(cx, JSSLOT_COUNT)) + return JS_FALSE; + + /* Get the number of properties to enumerate. */ + iter_state = JSVAL_NULL; + ok = OBJ_ENUMERATE(cx, obj, JSENUMERATE_INIT, &iter_state, &num_properties); + if (!ok) + goto out; + + if (!JSVAL_IS_INT(num_properties)) { + JS_ASSERT(0); + *vp = JSVAL_ZERO; + goto out; + } + *vp = num_properties; + +out: + if (iter_state != JSVAL_NULL) + ok = OBJ_ENUMERATE(cx, obj, JSENUMERATE_DESTROY, &iter_state, 0); + return ok; +} + +#else /* !JS_HAS_OBJ_PROTO_PROP */ + +#define object_props NULL + +#endif /* !JS_HAS_OBJ_PROTO_PROP */ + +JSBool +js_SetProtoOrParent(JSContext *cx, JSObject *obj, uint32 slot, JSObject *pobj) +{ + JSRuntime *rt; + JSObject *obj2, *oldproto; + JSScope *scope, *newscope; + + /* + * Serialize all proto and parent setting in order to detect cycles. + * We nest locks in this function, and only here, in the following orders: + * + * (1) rt->setSlotLock < pobj's scope lock; + * rt->setSlotLock < pobj's proto-or-parent's scope lock; + * rt->setSlotLock < pobj's grand-proto-or-parent's scope lock; + * etc... + * (2) rt->setSlotLock < obj's scope lock < pobj's scope lock. + * + * We avoid AB-BA deadlock by restricting obj from being on pobj's parent + * or proto chain (pobj may already be on obj's parent or proto chain; it + * could be moving up or down). We finally order obj with respect to pobj + * at the bottom of this routine (just before releasing rt->setSlotLock), + * by making pobj be obj's prototype or parent. + * + * After we have set the slot and released rt->setSlotLock, another call + * to js_SetProtoOrParent could nest locks according to the first order + * list above, but it cannot deadlock with any other thread. For there + * to be a deadlock, other parts of the engine would have to nest scope + * locks in the opposite order. XXXbe ensure they don't! + */ + rt = cx->runtime; +#ifdef JS_THREADSAFE + + JS_ACQUIRE_LOCK(rt->setSlotLock); + while (rt->setSlotBusy) { + jsrefcount saveDepth; + + /* Take pains to avoid nesting rt->gcLock inside rt->setSlotLock! */ + JS_RELEASE_LOCK(rt->setSlotLock); + saveDepth = JS_SuspendRequest(cx); + JS_ACQUIRE_LOCK(rt->setSlotLock); + if (rt->setSlotBusy) + JS_WAIT_CONDVAR(rt->setSlotDone, JS_NO_TIMEOUT); + JS_RELEASE_LOCK(rt->setSlotLock); + JS_ResumeRequest(cx, saveDepth); + JS_ACQUIRE_LOCK(rt->setSlotLock); + } + rt->setSlotBusy = JS_TRUE; + JS_RELEASE_LOCK(rt->setSlotLock); + +#define SET_SLOT_DONE(rt) \ + JS_BEGIN_MACRO \ + JS_ACQUIRE_LOCK((rt)->setSlotLock); \ + (rt)->setSlotBusy = JS_FALSE; \ + JS_NOTIFY_ALL_CONDVAR((rt)->setSlotDone); \ + JS_RELEASE_LOCK((rt)->setSlotLock); \ + JS_END_MACRO + +#else + +#define SET_SLOT_DONE(rt) /* nothing */ + +#endif + + obj2 = pobj; + while (obj2) { + if (obj2 == obj) { + SET_SLOT_DONE(rt); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_CYCLIC_VALUE, +#if JS_HAS_OBJ_PROTO_PROP + object_props[slot].name +#else + (slot == JSSLOT_PROTO) ? js_proto_str + : js_parent_str +#endif + ); + return JS_FALSE; + } + obj2 = JSVAL_TO_OBJECT(OBJ_GET_SLOT(cx, obj2, slot)); + } + + if (slot == JSSLOT_PROTO && OBJ_IS_NATIVE(obj)) { + /* Check to see whether obj shares its prototype's scope. */ + JS_LOCK_OBJ(cx, obj); + scope = OBJ_SCOPE(obj); + oldproto = JSVAL_TO_OBJECT(LOCKED_OBJ_GET_SLOT(obj, JSSLOT_PROTO)); + if (oldproto && OBJ_SCOPE(oldproto) == scope) { + /* Either obj needs a new empty scope, or it should share pobj's. */ + if (!pobj || + !OBJ_IS_NATIVE(pobj) || + OBJ_GET_CLASS(cx, pobj) != LOCKED_OBJ_GET_CLASS(oldproto)) { + /* + * With no proto and no scope of its own, obj is truly empty. + * + * If pobj is not native, obj needs its own empty scope -- it + * should not continue to share oldproto's scope once oldproto + * is not on obj's prototype chain. That would put properties + * from oldproto's scope ahead of properties defined by pobj, + * in lookup order. + * + * If pobj's class differs from oldproto's, we may need a new + * scope to handle differences in private and reserved slots, + * so we suboptimally but safely make one. + */ + scope = js_GetMutableScope(cx, obj); + if (!scope) { + JS_UNLOCK_OBJ(cx, obj); + SET_SLOT_DONE(rt); + return JS_FALSE; + } + } else if (OBJ_SCOPE(pobj) != scope) { +#ifdef JS_THREADSAFE + /* + * We are about to nest scope locks. Help jslock.c:ShareScope + * keep scope->u.count balanced for the JS_UNLOCK_SCOPE, while + * avoiding deadlock, by recording scope in rt->setSlotScope. + */ + if (scope->ownercx) { + JS_ASSERT(scope->ownercx == cx); + rt->setSlotScope = scope; + } +#endif + + /* We can't deadlock because we checked for cycles above (2). */ + JS_LOCK_OBJ(cx, pobj); + newscope = (JSScope *) js_HoldObjectMap(cx, pobj->map); + obj->map = &newscope->map; + js_DropObjectMap(cx, &scope->map, obj); + JS_TRANSFER_SCOPE_LOCK(cx, scope, newscope); + scope = newscope; +#ifdef JS_THREADSAFE + rt->setSlotScope = NULL; +#endif + } + } + LOCKED_OBJ_SET_SLOT(obj, JSSLOT_PROTO, OBJECT_TO_JSVAL(pobj)); + JS_UNLOCK_SCOPE(cx, scope); + } else { + OBJ_SET_SLOT(cx, obj, slot, OBJECT_TO_JSVAL(pobj)); + } + + SET_SLOT_DONE(rt); + return JS_TRUE; + +#undef SET_SLOT_DONE +} + +JS_STATIC_DLL_CALLBACK(JSHashNumber) +js_hash_object(const void *key) +{ + return (JSHashNumber)key >> JSVAL_TAGBITS; +} + +static JSHashEntry * +MarkSharpObjects(JSContext *cx, JSObject *obj, JSIdArray **idap) +{ + JSSharpObjectMap *map; + JSHashTable *table; + JSHashNumber hash; + JSHashEntry **hep, *he; + jsatomid sharpid; + JSIdArray *ida; + JSBool ok; + jsint i, length; + jsid id; +#if JS_HAS_GETTER_SETTER + JSObject *obj2; + JSProperty *prop; + uintN attrs; +#endif + jsval val; + + map = &cx->sharpObjectMap; + table = map->table; + hash = js_hash_object(obj); + hep = JS_HashTableRawLookup(table, hash, obj); + he = *hep; + if (!he) { + sharpid = 0; + he = JS_HashTableRawAdd(table, hep, hash, obj, (void *)sharpid); + if (!he) { + JS_ReportOutOfMemory(cx); + return NULL; + } + ida = JS_Enumerate(cx, obj); + if (!ida) + return NULL; + ok = JS_TRUE; + for (i = 0, length = ida->length; i < length; i++) { + id = ida->vector[i]; +#if JS_HAS_GETTER_SETTER + ok = OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop); + if (!ok) + break; + if (prop) { + ok = OBJ_GET_ATTRIBUTES(cx, obj2, id, prop, &attrs); + if (ok) { + if (OBJ_IS_NATIVE(obj2) && + (attrs & (JSPROP_GETTER | JSPROP_SETTER))) { + val = JSVAL_NULL; + if (attrs & JSPROP_GETTER) + val = (jsval) ((JSScopeProperty*)prop)->getter; + if (attrs & JSPROP_SETTER) { + if (val != JSVAL_NULL) { + /* Mark the getter, then set val to setter. */ + ok = (MarkSharpObjects(cx, JSVAL_TO_OBJECT(val), + NULL) + != NULL); + } + val = (jsval) ((JSScopeProperty*)prop)->setter; + } + } else { + ok = OBJ_GET_PROPERTY(cx, obj, id, &val); + } + } + OBJ_DROP_PROPERTY(cx, obj2, prop); + } +#else + ok = OBJ_GET_PROPERTY(cx, obj, id, &val); +#endif + if (!ok) + break; + if (!JSVAL_IS_PRIMITIVE(val) && + !MarkSharpObjects(cx, JSVAL_TO_OBJECT(val), NULL)) { + ok = JS_FALSE; + break; + } + } + if (!ok || !idap) + JS_DestroyIdArray(cx, ida); + if (!ok) + return NULL; + } else { + sharpid = (jsatomid) he->value; + if (sharpid == 0) { + sharpid = ++map->sharpgen << SHARP_ID_SHIFT; + he->value = (void *) sharpid; + } + ida = NULL; + } + if (idap) + *idap = ida; + return he; +} + +JSHashEntry * +js_EnterSharpObject(JSContext *cx, JSObject *obj, JSIdArray **idap, + jschar **sp) +{ + JSSharpObjectMap *map; + JSHashTable *table; + JSIdArray *ida; + JSHashNumber hash; + JSHashEntry *he, **hep; + jsatomid sharpid; + char buf[20]; + size_t len; + + /* Set to null in case we return an early error. */ + *sp = NULL; + map = &cx->sharpObjectMap; + table = map->table; + if (!table) { + table = JS_NewHashTable(8, js_hash_object, JS_CompareValues, + JS_CompareValues, NULL, NULL); + if (!table) { + JS_ReportOutOfMemory(cx); + return NULL; + } + map->table = table; + } + + ida = NULL; + if (map->depth == 0) { + he = MarkSharpObjects(cx, obj, &ida); + if (!he) + goto bad; + JS_ASSERT((((jsatomid) he->value) & SHARP_BIT) == 0); + if (!idap) { + JS_DestroyIdArray(cx, ida); + ida = NULL; + } + } else { + hash = js_hash_object(obj); + hep = JS_HashTableRawLookup(table, hash, obj); + he = *hep; + + /* + * It's possible that the value of a property has changed from the + * first time the object's properties are traversed (when the property + * ids are entered into the hash table) to the second (when they are + * converted to strings), i.e., the OBJ_GET_PROPERTY() call is not + * idempotent. + */ + if (!he) { + he = JS_HashTableRawAdd(table, hep, hash, obj, NULL); + if (!he) { + JS_ReportOutOfMemory(cx); + goto bad; + } + *sp = NULL; + sharpid = 0; + goto out; + } + } + + sharpid = (jsatomid) he->value; + if (sharpid == 0) { + *sp = NULL; + } else { + len = JS_snprintf(buf, sizeof buf, "#%u%c", + sharpid >> SHARP_ID_SHIFT, + (sharpid & SHARP_BIT) ? '#' : '='); + *sp = js_InflateString(cx, buf, len); + if (!*sp) { + if (ida) + JS_DestroyIdArray(cx, ida); + goto bad; + } + } + +out: + JS_ASSERT(he); + if ((sharpid & SHARP_BIT) == 0) { + if (idap && !ida) { + ida = JS_Enumerate(cx, obj); + if (!ida) { + if (*sp) { + JS_free(cx, *sp); + *sp = NULL; + } + goto bad; + } + } + map->depth++; + } + + if (idap) + *idap = ida; + return he; + +bad: + /* Clean up the sharpObjectMap table on outermost error. */ + if (map->depth == 0) { + map->sharpgen = 0; + JS_HashTableDestroy(map->table); + map->table = NULL; + } + return NULL; +} + +void +js_LeaveSharpObject(JSContext *cx, JSIdArray **idap) +{ + JSSharpObjectMap *map; + JSIdArray *ida; + + map = &cx->sharpObjectMap; + JS_ASSERT(map->depth > 0); + if (--map->depth == 0) { + map->sharpgen = 0; + JS_HashTableDestroy(map->table); + map->table = NULL; + } + if (idap) { + ida = *idap; + if (ida) { + JS_DestroyIdArray(cx, ida); + *idap = NULL; + } + } +} + +#define OBJ_TOSTRING_EXTRA 3 /* for 3 local GC roots */ + +#if JS_HAS_INITIALIZERS || JS_HAS_TOSOURCE +JSBool +js_obj_toSource(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSBool ok, outermost; + JSHashEntry *he; + JSIdArray *ida; + jschar *chars, *ochars, *vsharp; + const jschar *idstrchars, *vchars; + size_t nchars, idstrlength, gsoplength, vlength, vsharplength; + char *comma; + jsint i, j, length, valcnt; + jsid id; +#if JS_HAS_GETTER_SETTER + JSObject *obj2; + JSProperty *prop; + uintN attrs; +#endif + jsval val[2]; + JSString *gsop[2]; + JSAtom *atom; + JSString *idstr, *valstr, *str; + int stackDummy; + + if (!JS_CHECK_STACK_SIZE(cx, stackDummy)) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_OVER_RECURSED); + return JS_FALSE; + } + + /* + * obj_toString for 1.2 calls toSource, and doesn't want the extra parens + * on the outside. + */ + outermost = (cx->version != JSVERSION_1_2 && cx->sharpObjectMap.depth == 0); + he = js_EnterSharpObject(cx, obj, &ida, &chars); + if (!he) + return JS_FALSE; + if (IS_SHARP(he)) { + /* + * We didn't enter -- obj is already "sharp", meaning we've visited it + * already in our depth first search, and therefore chars contains a + * string of the form "#n#". + */ + JS_ASSERT(!ida); +#if JS_HAS_SHARP_VARS + nchars = js_strlen(chars); +#else + chars[0] = '{'; + chars[1] = '}'; + chars[2] = 0; + nchars = 2; +#endif + goto make_string; + } + JS_ASSERT(ida); + ok = JS_TRUE; + + if (!chars) { + /* If outermost, allocate 4 + 1 for "({})" and the terminator. */ + chars = (jschar *) malloc(((outermost ? 4 : 2) + 1) * sizeof(jschar)); + nchars = 0; + if (!chars) + goto error; + if (outermost) + chars[nchars++] = '('; + } else { + /* js_EnterSharpObject returned a string of the form "#n=" in chars. */ + MAKE_SHARP(he); + nchars = js_strlen(chars); + chars = (jschar *) + realloc((ochars = chars), (nchars + 2 + 1) * sizeof(jschar)); + if (!chars) { + free(ochars); + goto error; + } + if (outermost) { + /* + * No need for parentheses around the whole shebang, because #n= + * unambiguously begins an object initializer, and never a block + * statement. + */ + outermost = JS_FALSE; + } + } + + chars[nchars++] = '{'; + + comma = NULL; + + for (i = 0, length = ida->length; i < length; i++) { + /* Get strings for id and value and GC-root them via argv. */ + id = ida->vector[i]; + +#if JS_HAS_GETTER_SETTER + + ok = OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop); + if (!ok) + goto error; + valcnt = 0; + if (prop) { + ok = OBJ_GET_ATTRIBUTES(cx, obj2, id, prop, &attrs); + if (!ok) { + OBJ_DROP_PROPERTY(cx, obj2, prop); + goto error; + } + if (OBJ_IS_NATIVE(obj2) && + (attrs & (JSPROP_GETTER | JSPROP_SETTER))) { + if (attrs & JSPROP_GETTER) { + val[valcnt] = (jsval) ((JSScopeProperty *)prop)->getter; +#ifdef OLD_GETTER_SETTER + gsop[valcnt] = + ATOM_TO_STRING(cx->runtime->atomState.getterAtom); +#else + gsop[valcnt] = + ATOM_TO_STRING(cx->runtime->atomState.getAtom); +#endif + valcnt++; + } + if (attrs & JSPROP_SETTER) { + val[valcnt] = (jsval) ((JSScopeProperty *)prop)->setter; +#ifdef OLD_GETTER_SETTER + gsop[valcnt] = + ATOM_TO_STRING(cx->runtime->atomState.setterAtom); +#else + gsop[valcnt] = + ATOM_TO_STRING(cx->runtime->atomState.setAtom); +#endif + valcnt++; + } + } else { + valcnt = 1; + gsop[0] = NULL; + ok = OBJ_GET_PROPERTY(cx, obj, id, &val[0]); + } + OBJ_DROP_PROPERTY(cx, obj2, prop); + } + +#else /* !JS_HAS_GETTER_SETTER */ + + valcnt = 1; + gsop[0] = NULL; + ok = OBJ_GET_PROPERTY(cx, obj, id, &val[0]); + +#endif /* !JS_HAS_GETTER_SETTER */ + + if (!ok) + goto error; + + /* Convert id to a jsval and then to a string. */ + atom = JSVAL_IS_INT(id) ? NULL : (JSAtom *)id; + id = ID_TO_VALUE(id); + idstr = js_ValueToString(cx, id); + if (!idstr) { + ok = JS_FALSE; + goto error; + } + argv[0] = STRING_TO_JSVAL(idstr); + + /* + * If id is a string that's a reserved identifier, or else id is not + * an identifier at all, then it needs to be quoted. + */ + if (atom && (ATOM_KEYWORD(atom) || !js_IsIdentifier(idstr))) { + idstr = js_QuoteString(cx, idstr, (jschar)'\''); + if (!idstr) { + ok = JS_FALSE; + goto error; + } + argv[0] = STRING_TO_JSVAL(idstr); + } + idstrchars = JSSTRING_CHARS(idstr); + idstrlength = JSSTRING_LENGTH(idstr); + + for (j = 0; j < valcnt; j++) { + /* Convert val[j] to its canonical source form. */ + valstr = js_ValueToSource(cx, val[j]); + if (!valstr) { + ok = JS_FALSE; + goto error; + } + argv[1+j] = STRING_TO_JSVAL(valstr); + vchars = JSSTRING_CHARS(valstr); + vlength = JSSTRING_LENGTH(valstr); + +#ifndef OLD_GETTER_SETTER + /* Remove 'function ' from beginning of valstr. */ + if (gsop[j]) { + int n = strlen(js_function_str) + 1; + vchars += n; + vlength -= n; + } +#endif + + /* If val[j] is a non-sharp object, consider sharpening it. */ + vsharp = NULL; + vsharplength = 0; +#if JS_HAS_SHARP_VARS + if (!JSVAL_IS_PRIMITIVE(val[j]) && vchars[0] != '#') { + he = js_EnterSharpObject(cx, JSVAL_TO_OBJECT(val[j]), NULL, + &vsharp); + if (!he) { + ok = JS_FALSE; + goto error; + } + if (IS_SHARP(he)) { + vchars = vsharp; + vlength = js_strlen(vchars); + } else { + if (vsharp) { + vsharplength = js_strlen(vsharp); + MAKE_SHARP(he); + } + js_LeaveSharpObject(cx, NULL); + } + } +#endif + + /* Allocate 1 + 1 at end for closing brace and terminating 0. */ + chars = (jschar *) + realloc((ochars = chars), + (nchars + (comma ? 2 : 0) + + idstrlength + 1 + + (gsop[j] ? 1 + JSSTRING_LENGTH(gsop[j]) : 0) + + vsharplength + vlength + + (outermost ? 2 : 1) + 1) * sizeof(jschar)); + if (!chars) { + /* Save code space on error: let JS_free ignore null vsharp. */ + JS_free(cx, vsharp); + free(ochars); + goto error; + } + + if (comma) { + chars[nchars++] = comma[0]; + chars[nchars++] = comma[1]; + } + comma = ", "; + +#ifdef OLD_GETTER_SETTER + js_strncpy(&chars[nchars], idstrchars, idstrlength); + nchars += idstrlength; + if (gsop[j]) { + chars[nchars++] = ' '; + gsoplength = JSSTRING_LENGTH(gsop[j]); + js_strncpy(&chars[nchars], JSSTRING_CHARS(gsop[j]), gsoplength); + nchars += gsoplength; + } + chars[nchars++] = ':'; +#else + if (gsop[j]) { + gsoplength = JSSTRING_LENGTH(gsop[j]); + js_strncpy(&chars[nchars], JSSTRING_CHARS(gsop[j]), gsoplength); + nchars += gsoplength; + chars[nchars++] = ' '; + } + js_strncpy(&chars[nchars], idstrchars, idstrlength); + nchars += idstrlength; + if (!gsop[j]) + chars[nchars++] = ':'; +#endif + if (vsharplength) { + js_strncpy(&chars[nchars], vsharp, vsharplength); + nchars += vsharplength; + } + js_strncpy(&chars[nchars], vchars, vlength); + nchars += vlength; + + if (vsharp) + JS_free(cx, vsharp); + } + } + + chars[nchars++] = '}'; + if (outermost) + chars[nchars++] = ')'; + chars[nchars] = 0; + + error: + js_LeaveSharpObject(cx, &ida); + + if (!ok) { + if (chars) + free(chars); + return ok; + } + + if (!chars) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + make_string: + str = js_NewString(cx, chars, nchars, 0); + if (!str) { + free(chars); + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} +#endif /* JS_HAS_INITIALIZERS || JS_HAS_TOSOURCE */ + +JSBool +js_obj_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + jschar *chars; + size_t nchars; + const char *clazz, *prefix; + JSString *str; + +#if JS_HAS_INITIALIZERS + if (cx->version == JSVERSION_1_2) + return js_obj_toSource(cx, obj, argc, argv, rval); +#endif + + clazz = OBJ_GET_CLASS(cx, obj)->name; + nchars = 9 + strlen(clazz); /* 9 for "[object ]" */ + chars = (jschar *) JS_malloc(cx, (nchars + 1) * sizeof(jschar)); + if (!chars) + return JS_FALSE; + + prefix = "[object "; + nchars = 0; + while ((chars[nchars] = (jschar)*prefix) != 0) + nchars++, prefix++; + while ((chars[nchars] = (jschar)*clazz) != 0) + nchars++, clazz++; + chars[nchars++] = ']'; + chars[nchars] = 0; + + str = js_NewString(cx, chars, nchars, 0); + if (!str) { + JS_free(cx, chars); + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +static JSBool +obj_valueOf(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + *rval = OBJECT_TO_JSVAL(obj); + return JS_TRUE; +} + +static JSBool +obj_eval(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSStackFrame *fp, *caller; + JSBool indirectCall; + JSObject *scopeobj; + JSString *str; + const char *file; + uintN line; + JSPrincipals *principals; + JSScript *script; + JSBool ok; +#if JS_HAS_EVAL_THIS_SCOPE + JSObject *callerScopeChain = NULL, *callerVarObj = NULL; + JSBool setCallerScopeChain = JS_FALSE, setCallerVarObj = JS_FALSE; +#endif + + fp = cx->fp; + caller = JS_GetScriptedCaller(cx, fp); + indirectCall = (caller && caller->pc && *caller->pc != JSOP_EVAL); + + if (JSVERSION_IS_ECMA(cx->version) && + indirectCall && + !JS_ReportErrorFlagsAndNumber(cx, + JSREPORT_WARNING | JSREPORT_STRICT, + js_GetErrorMessage, NULL, + JSMSG_BAD_INDIRECT_CALL, + js_eval_str)) { + return JS_FALSE; + } + + if (!JSVAL_IS_STRING(argv[0])) { + *rval = argv[0]; + return JS_TRUE; + } + +#if JS_HAS_SCRIPT_OBJECT + /* + * Script.prototype.compile/exec and Object.prototype.eval all take an + * optional trailing argument that overrides the scope object. + */ + scopeobj = NULL; + if (argc >= 2) { + if (!js_ValueToObject(cx, argv[1], &scopeobj)) + return JS_FALSE; + argv[1] = OBJECT_TO_JSVAL(scopeobj); + } + if (!scopeobj) +#endif + { +#if JS_HAS_EVAL_THIS_SCOPE + /* If obj.eval(str), emulate 'with (obj) eval(str)' in the caller. */ + if (indirectCall) { + callerScopeChain = caller->scopeChain; + if (obj != callerScopeChain) { + scopeobj = js_NewObject(cx, &js_WithClass, obj, + callerScopeChain); + if (!scopeobj) + return JS_FALSE; + + /* Set fp->scopeChain too, for the compiler. */ + caller->scopeChain = fp->scopeChain = scopeobj; + setCallerScopeChain = JS_TRUE; + } + + callerVarObj = caller->varobj; + if (obj != callerVarObj) { + /* Set fp->varobj too, for the compiler. */ + caller->varobj = fp->varobj = obj; + setCallerVarObj = JS_TRUE; + } + } + /* From here on, control must exit through label out with ok set. */ +#endif + +#if JS_BUG_EVAL_THIS_SCOPE + /* An old version used the object in which eval was found for scope. */ + scopeobj = obj; +#else + /* Compile using caller's current scope object. */ + if (caller) + scopeobj = caller->scopeChain; +#endif + } + + str = JSVAL_TO_STRING(argv[0]); + if (caller) { + file = caller->script->filename; + line = js_PCToLineNumber(cx, caller->script, caller->pc); + principals = JS_EvalFramePrincipals(cx, fp, caller); + } else { + file = NULL; + line = 0; + principals = NULL; + } + + /* XXXbe set only for the compiler, which does not currently test it */ + fp->flags |= JSFRAME_EVAL; + script = JS_CompileUCScriptForPrincipals(cx, scopeobj, principals, + JSSTRING_CHARS(str), + JSSTRING_LENGTH(str), + file, line); + if (!script) { + ok = JS_FALSE; + goto out; + } + +#if !JS_BUG_EVAL_THIS_SCOPE +#if JS_HAS_SCRIPT_OBJECT + if (argc < 2) +#endif + { + /* Execute using caller's new scope object (might be a Call object). */ + if (caller) + scopeobj = caller->scopeChain; + } +#endif + ok = js_Execute(cx, scopeobj, script, caller, JSFRAME_EVAL, rval); + JS_DestroyScript(cx, script); + +out: +#if JS_HAS_EVAL_THIS_SCOPE + /* Restore OBJ_GET_PARENT(scopeobj) not callerScopeChain in case of Call. */ + if (setCallerScopeChain) + caller->scopeChain = callerScopeChain; + if (setCallerVarObj) + caller->varobj = callerVarObj; +#endif + return ok; +} + +JS_STATIC_DLL_CALLBACK(const void *) +resolving_GetKey(JSDHashTable *table, JSDHashEntryHdr *hdr) +{ + JSResolvingEntry *entry = (JSResolvingEntry *)hdr; + + return &entry->key; +} + +JS_STATIC_DLL_CALLBACK(JSDHashNumber) +resolving_HashKey(JSDHashTable *table, const void *ptr) +{ + const JSResolvingKey *key = (const JSResolvingKey *)ptr; + + return ((JSDHashNumber)key->obj >> JSVAL_TAGBITS) ^ key->id; +} + +JS_PUBLIC_API(JSBool) +resolving_MatchEntry(JSDHashTable *table, + const JSDHashEntryHdr *hdr, + const void *ptr) +{ + const JSResolvingEntry *entry = (const JSResolvingEntry *)hdr; + const JSResolvingKey *key = (const JSResolvingKey *)ptr; + + return entry->key.obj == key->obj && entry->key.id == key->id; +} + +static const JSDHashTableOps resolving_dhash_ops = { + JS_DHashAllocTable, + JS_DHashFreeTable, + resolving_GetKey, + resolving_HashKey, + resolving_MatchEntry, + JS_DHashMoveEntryStub, + JS_DHashClearEntryStub, + JS_DHashFinalizeStub, + NULL +}; + +static JSBool +StartResolving(JSContext *cx, JSResolvingKey *key, uint32 flag, + JSResolvingEntry **entryp) +{ + JSDHashTable *table; + JSResolvingEntry *entry; + + table = cx->resolvingTable; + if (!table) { + table = JS_NewDHashTable(&resolving_dhash_ops, NULL, + sizeof(JSResolvingEntry), + JS_DHASH_MIN_SIZE); + if (!table) + goto outofmem; + cx->resolvingTable = table; + } + + entry = (JSResolvingEntry *) + JS_DHashTableOperate(table, key, JS_DHASH_ADD); + if (!entry) + goto outofmem; + + if (entry->flags & flag) { + /* An entry for (key, flag) exists already -- dampen recursion. */ + entry = NULL; + } else { + /* Fill in key if we were the first to add entry, then set flag. */ + if (!entry->key.obj) + entry->key = *key; + entry->flags |= flag; + } + *entryp = entry; + return JS_TRUE; + +outofmem: + JS_ReportOutOfMemory(cx); + return JS_FALSE; +} + +static void +StopResolving(JSContext *cx, JSResolvingKey *key, uint32 flag, + JSResolvingEntry *entry, uint32 generation) +{ + JSDHashTable *table; + + /* + * Clear flag from entry->flags and return early if other flags remain. + * We must take care to re-lookup entry if the table has changed since + * it was found by StartResolving. + */ + table = cx->resolvingTable; + if (table->generation != generation) { + entry = (JSResolvingEntry *) + JS_DHashTableOperate(table, key, JS_DHASH_LOOKUP); + } + entry->flags &= ~flag; + if (entry->flags) + return; + + /* + * Do a raw remove only if fewer entries were removed than would cause + * alpha to be less than .5 (alpha is at most .75). Otherwise, we just + * call JS_DHashTableOperate to re-lookup the key and remove its entry, + * compressing or shrinking the table as needed. + */ + if (table->removedCount < JS_DHASH_TABLE_SIZE(table) >> 2) + JS_DHashTableRawRemove(table, &entry->hdr); + else + JS_DHashTableOperate(table, key, JS_DHASH_REMOVE); +} + +#if JS_HAS_OBJ_WATCHPOINT + +static JSBool +obj_watch_handler(JSContext *cx, JSObject *obj, jsval id, jsval old, jsval *nvp, + void *closure) +{ + JSResolvingKey key; + JSResolvingEntry *entry; + uint32 generation; + JSObject *funobj; + jsval argv[3]; + JSBool ok; + + /* Avoid recursion on (obj, id) already being watched on cx. */ + key.obj = obj; + key.id = id; + if (!StartResolving(cx, &key, JSRESFLAG_WATCH, &entry)) + return JS_FALSE; + if (!entry) + return JS_TRUE; + generation = cx->resolvingTable->generation; + + funobj = (JSObject *) closure; + argv[0] = id; + argv[1] = old; + argv[2] = *nvp; + ok = js_InternalCall(cx, obj, OBJECT_TO_JSVAL(funobj), 3, argv, nvp); + StopResolving(cx, &key, JSRESFLAG_WATCH, entry, generation); + return ok; +} + +static JSBool +obj_watch(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFunction *fun; + jsval userid, value; + jsid propid; + uintN attrs; + + fun = js_ValueToFunction(cx, &argv[1], 0); + if (!fun) + return JS_FALSE; + argv[1] = OBJECT_TO_JSVAL(fun->object); + + /* Compute the unique int/atom symbol id needed by js_LookupProperty. */ + userid = argv[0]; + if (!JS_ValueToId(cx, userid, &propid)) + return JS_FALSE; + + if (!OBJ_CHECK_ACCESS(cx, obj, propid, JSACC_WATCH, &value, &attrs)) + return JS_FALSE; + if (attrs & JSPROP_READONLY) + return JS_TRUE; + return JS_SetWatchPoint(cx, obj, userid, obj_watch_handler, fun->object); +} + +static JSBool +obj_unwatch(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + return JS_ClearWatchPoint(cx, obj, argv[0], NULL, NULL); +} + +#endif /* JS_HAS_OBJ_WATCHPOINT */ + +#if JS_HAS_NEW_OBJ_METHODS +/* + * Prototype and property query methods, to complement the 'in' and + * 'instanceof' operators. + */ + +/* Proposed ECMA 15.2.4.5. */ +static JSBool +obj_hasOwnProperty(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + jsid id; + JSObject *obj2; + JSProperty *prop; + JSScopeProperty *sprop; + + if (!JS_ValueToId(cx, argv[0], &id)) + return JS_FALSE; + if (!OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop)) + return JS_FALSE; + if (!prop) { + *rval = JSVAL_FALSE; + } else if (obj2 == obj) { + *rval = JSVAL_TRUE; + } else if (OBJ_IS_NATIVE(obj2)) { + sprop = (JSScopeProperty *)prop; + *rval = BOOLEAN_TO_JSVAL(SPROP_IS_SHARED_PERMANENT(sprop)); + } else { + *rval = JSVAL_FALSE; + } + if (prop) + OBJ_DROP_PROPERTY(cx, obj2, prop); + return JS_TRUE; +} + +/* Proposed ECMA 15.2.4.6. */ +static JSBool +obj_isPrototypeOf(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSBool b; + + if (!js_IsDelegate(cx, obj, *argv, &b)) + return JS_FALSE; + *rval = BOOLEAN_TO_JSVAL(b); + return JS_TRUE; +} + +/* Proposed ECMA 15.2.4.7. */ +static JSBool +obj_propertyIsEnumerable(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + jsid id; + uintN attrs; + JSObject *obj2; + JSProperty *prop; + JSBool ok; + + if (!JS_ValueToId(cx, argv[0], &id)) + return JS_FALSE; + + if (!OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop)) + return JS_FALSE; + + if (!prop) { + *rval = JSVAL_FALSE; + return JS_TRUE; + } + + /* + * XXX ECMA spec error compatible: return false unless hasOwnProperty. + * The ECMA spec really should be fixed so propertyIsEnumerable and the + * for..in loop agree on whether prototype properties are enumerable, + * obviously by fixing this method (not by breaking the for..in loop!). + * + * We check here for shared permanent prototype properties, which should + * be treated as if they are local to obj. They are an implementation + * technique used to satisfy ECMA requirements; users should not be able + * to distinguish a shared permanent proto-property from a local one. + */ + if (obj2 != obj && + !(OBJ_IS_NATIVE(obj2) && + SPROP_IS_SHARED_PERMANENT((JSScopeProperty *)prop))) { + OBJ_DROP_PROPERTY(cx, obj2, prop); + *rval = JSVAL_FALSE; + return JS_TRUE; + } + + ok = OBJ_GET_ATTRIBUTES(cx, obj2, id, prop, &attrs); + OBJ_DROP_PROPERTY(cx, obj2, prop); + if (ok) + *rval = BOOLEAN_TO_JSVAL((attrs & JSPROP_ENUMERATE) != 0); + return ok; +} +#endif /* JS_HAS_NEW_OBJ_METHODS */ + +#if JS_HAS_GETTER_SETTER +static JSBool +obj_defineGetter(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + jsval fval, junk; + jsid id; + JSBool found; + uintN attrs; + + fval = argv[1]; + if (JS_TypeOfValue(cx, fval) != JSTYPE_FUNCTION) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_GETTER_OR_SETTER, + js_getter_str); + return JS_FALSE; + } + + if (!JS_ValueToId(cx, argv[0], &id)) + return JS_FALSE; + if (!js_CheckRedeclaration(cx, obj, id, JSPROP_GETTER, &found)) + return JS_FALSE; + /* + * Getters and setters are just like watchpoints from an access + * control point of view. + */ + if (!OBJ_CHECK_ACCESS(cx, obj, id, JSACC_WATCH, &junk, &attrs)) + return JS_FALSE; + return OBJ_DEFINE_PROPERTY(cx, obj, id, JSVAL_VOID, + (JSPropertyOp) JSVAL_TO_OBJECT(fval), NULL, + JSPROP_GETTER | JSPROP_SHARED, NULL); +} + +static JSBool +obj_defineSetter(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + jsval fval, junk; + jsid id; + JSBool found; + uintN attrs; + + fval = argv[1]; + if (JS_TypeOfValue(cx, fval) != JSTYPE_FUNCTION) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_GETTER_OR_SETTER, + js_setter_str); + return JS_FALSE; + } + + if (!JS_ValueToId(cx, argv[0], &id)) + return JS_FALSE; + if (!js_CheckRedeclaration(cx, obj, id, JSPROP_SETTER, &found)) + return JS_FALSE; + /* + * Getters and setters are just like watchpoints from an access + * control point of view. + */ + if (!OBJ_CHECK_ACCESS(cx, obj, id, JSACC_WATCH, &junk, &attrs)) + return JS_FALSE; + return OBJ_DEFINE_PROPERTY(cx, obj, id, JSVAL_VOID, + NULL, (JSPropertyOp) JSVAL_TO_OBJECT(fval), + JSPROP_SETTER | JSPROP_SHARED, NULL); +} + +static JSBool +obj_lookupGetter(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + jsid id; + JSObject *pobj; + JSScopeProperty *sprop; + + if (!JS_ValueToId(cx, argv[0], &id)) + return JS_FALSE; + if (!OBJ_LOOKUP_PROPERTY(cx, obj, id, &pobj, (JSProperty **) &sprop)) + return JS_FALSE; + if (sprop) { + if (sprop->attrs & JSPROP_GETTER) + *rval = OBJECT_TO_JSVAL(sprop->getter); + OBJ_DROP_PROPERTY(cx, pobj, (JSProperty *)sprop); + } + return JS_TRUE; +} + +static JSBool +obj_lookupSetter(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + jsid id; + JSObject *pobj; + JSScopeProperty *sprop; + + if (!JS_ValueToId(cx, argv[0], &id)) + return JS_FALSE; + if (!OBJ_LOOKUP_PROPERTY(cx, obj, id, &pobj, (JSProperty **) &sprop)) + return JS_FALSE; + if (sprop) { + if (sprop->attrs & JSPROP_SETTER) + *rval = OBJECT_TO_JSVAL(sprop->setter); + OBJ_DROP_PROPERTY(cx, pobj, (JSProperty *)sprop); + } + return JS_TRUE; +} +#endif /* JS_HAS_GETTER_SETTER */ + +#if JS_HAS_OBJ_WATCHPOINT +const char js_watch_str[] = "watch"; +const char js_unwatch_str[] = "unwatch"; +#endif +#if JS_HAS_NEW_OBJ_METHODS +const char js_hasOwnProperty_str[] = "hasOwnProperty"; +const char js_isPrototypeOf_str[] = "isPrototypeOf"; +const char js_propertyIsEnumerable_str[] = "propertyIsEnumerable"; +#endif +#if JS_HAS_GETTER_SETTER +const char js_defineGetter_str[] = "__defineGetter__"; +const char js_defineSetter_str[] = "__defineSetter__"; +const char js_lookupGetter_str[] = "__lookupGetter__"; +const char js_lookupSetter_str[] = "__lookupSetter__"; +#endif + +static JSFunctionSpec object_methods[] = { +#if JS_HAS_TOSOURCE + {js_toSource_str, js_obj_toSource, 0, 0, OBJ_TOSTRING_EXTRA}, +#endif + {js_toString_str, js_obj_toString, 0, 0, OBJ_TOSTRING_EXTRA}, + {js_toLocaleString_str, js_obj_toString, 0, 0, OBJ_TOSTRING_EXTRA}, + {js_valueOf_str, obj_valueOf, 0,0,0}, + {js_eval_str, obj_eval, 1,0,0}, +#if JS_HAS_OBJ_WATCHPOINT + {js_watch_str, obj_watch, 2,0,0}, + {js_unwatch_str, obj_unwatch, 1,0,0}, +#endif +#if JS_HAS_NEW_OBJ_METHODS + {js_hasOwnProperty_str, obj_hasOwnProperty, 1,0,0}, + {js_isPrototypeOf_str, obj_isPrototypeOf, 1,0,0}, + {js_propertyIsEnumerable_str, obj_propertyIsEnumerable, 1,0,0}, +#endif +#if JS_HAS_GETTER_SETTER + {js_defineGetter_str, obj_defineGetter, 2,0,0}, + {js_defineSetter_str, obj_defineSetter, 2,0,0}, + {js_lookupGetter_str, obj_lookupGetter, 1,0,0}, + {js_lookupSetter_str, obj_lookupSetter, 1,0,0}, +#endif + {0,0,0,0,0} +}; + +static JSBool +Object(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + if (argc == 0) { + /* Trigger logic below to construct a blank object. */ + obj = NULL; + } else { + /* If argv[0] is null or undefined, obj comes back null. */ + if (!js_ValueToObject(cx, argv[0], &obj)) + return JS_FALSE; + } + if (!obj) { + JS_ASSERT(!argc || JSVAL_IS_NULL(argv[0]) || JSVAL_IS_VOID(argv[0])); + if (cx->fp->flags & JSFRAME_CONSTRUCTING) + return JS_TRUE; + obj = js_NewObject(cx, &js_ObjectClass, NULL, NULL); + if (!obj) + return JS_FALSE; + } + *rval = OBJECT_TO_JSVAL(obj); + return JS_TRUE; +} + +/* + * ObjectOps and Class for with-statement stack objects. + */ +static JSBool +with_LookupProperty(JSContext *cx, JSObject *obj, jsid id, JSObject **objp, + JSProperty **propp +#if defined JS_THREADSAFE && defined DEBUG + , const char *file, uintN line +#endif + ) +{ + JSObject *proto; + JSScopeProperty *sprop; + JSStackFrame *fp; + + proto = OBJ_GET_PROTO(cx, obj); + if (!proto) + return js_LookupProperty(cx, obj, id, objp, propp); + if (!OBJ_LOOKUP_PROPERTY(cx, proto, id, objp, propp)) + return JS_FALSE; + + /* + * Check whether id names an argument or local variable in an active + * function. If so, pretend we didn't find it, so that the real arg or + * var property can be found in the function's call object, later on in + * the scope chain. But skip unshared arg and var properties -- those + * result when a script explicitly sets a function "static" property of + * the same name. See jsinterp.c:SetFunctionSlot. + * + * XXX blame pre-ECMA reflection of function args and vars as properties + */ + if ((sprop = (JSScopeProperty *) *propp) && + (proto = *objp, OBJ_IS_NATIVE(proto)) && + (sprop->getter == js_GetArgument || + sprop->getter == js_GetLocalVariable) && + (sprop->attrs & JSPROP_SHARED)) { + JS_ASSERT(OBJ_GET_CLASS(cx, proto) == &js_FunctionClass); + for (fp = cx->fp; fp && (!fp->fun || fp->fun->native); fp = fp->down) + continue; + if (fp && fp->fun == (JSFunction *) JS_GetPrivate(cx, proto)) { + OBJ_DROP_PROPERTY(cx, proto, *propp); + *objp = NULL; + *propp = NULL; + } + } + return JS_TRUE; +} + +static JSBool +with_GetProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp) +{ + JSObject *proto = OBJ_GET_PROTO(cx, obj); + if (!proto) + return js_GetProperty(cx, obj, id, vp); + return OBJ_GET_PROPERTY(cx, proto, id, vp); +} + +static JSBool +with_SetProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp) +{ + JSObject *proto = OBJ_GET_PROTO(cx, obj); + if (!proto) + return js_SetProperty(cx, obj, id, vp); + return OBJ_SET_PROPERTY(cx, proto, id, vp); +} + +static JSBool +with_GetAttributes(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop, + uintN *attrsp) +{ + JSObject *proto = OBJ_GET_PROTO(cx, obj); + if (!proto) + return js_GetAttributes(cx, obj, id, prop, attrsp); + return OBJ_GET_ATTRIBUTES(cx, proto, id, prop, attrsp); +} + +static JSBool +with_SetAttributes(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop, + uintN *attrsp) +{ + JSObject *proto = OBJ_GET_PROTO(cx, obj); + if (!proto) + return js_SetAttributes(cx, obj, id, prop, attrsp); + return OBJ_SET_ATTRIBUTES(cx, proto, id, prop, attrsp); +} + +static JSBool +with_DeleteProperty(JSContext *cx, JSObject *obj, jsid id, jsval *rval) +{ + JSObject *proto = OBJ_GET_PROTO(cx, obj); + if (!proto) + return js_DeleteProperty(cx, obj, id, rval); + return OBJ_DELETE_PROPERTY(cx, proto, id, rval); +} + +static JSBool +with_DefaultValue(JSContext *cx, JSObject *obj, JSType hint, jsval *vp) +{ + JSObject *proto = OBJ_GET_PROTO(cx, obj); + if (!proto) + return js_DefaultValue(cx, obj, hint, vp); + return OBJ_DEFAULT_VALUE(cx, proto, hint, vp); +} + +static JSBool +with_Enumerate(JSContext *cx, JSObject *obj, JSIterateOp enum_op, + jsval *statep, jsid *idp) +{ + JSObject *proto = OBJ_GET_PROTO(cx, obj); + if (!proto) + return js_Enumerate(cx, obj, enum_op, statep, idp); + return OBJ_ENUMERATE(cx, proto, enum_op, statep, idp); +} + +static JSBool +with_CheckAccess(JSContext *cx, JSObject *obj, jsid id, JSAccessMode mode, + jsval *vp, uintN *attrsp) +{ + JSObject *proto = OBJ_GET_PROTO(cx, obj); + if (!proto) + return js_CheckAccess(cx, obj, id, mode, vp, attrsp); + return OBJ_CHECK_ACCESS(cx, proto, id, mode, vp, attrsp); +} + +static JSObject * +with_ThisObject(JSContext *cx, JSObject *obj) +{ + JSObject *proto = OBJ_GET_PROTO(cx, obj); + if (!proto) + return obj; + return OBJ_THIS_OBJECT(cx, proto); +} + +JS_FRIEND_DATA(JSObjectOps) js_WithObjectOps = { + js_NewObjectMap, js_DestroyObjectMap, + with_LookupProperty, js_DefineProperty, + with_GetProperty, with_SetProperty, + with_GetAttributes, with_SetAttributes, + with_DeleteProperty, with_DefaultValue, + with_Enumerate, with_CheckAccess, + with_ThisObject, NATIVE_DROP_PROPERTY, + NULL, NULL, + NULL, NULL, + js_SetProtoOrParent, js_SetProtoOrParent, + js_Mark, js_Clear, + NULL, NULL +}; + +static JSObjectOps * +with_getObjectOps(JSContext *cx, JSClass *clasp) +{ + return &js_WithObjectOps; +} + +JSClass js_WithClass = { + "With", + 0, + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, + with_getObjectOps, + 0,0,0,0,0,0,0 +}; + +#if JS_HAS_OBJ_PROTO_PROP +static JSBool +With(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSObject *parent, *proto; + jsval v; + + if (!JS_ReportErrorFlagsAndNumber(cx, + JSREPORT_WARNING | JSREPORT_STRICT, + js_GetErrorMessage, NULL, + JSMSG_DEPRECATED_USAGE, + js_WithClass.name)) { + return JS_FALSE; + } + + if (!(cx->fp->flags & JSFRAME_CONSTRUCTING)) { + obj = js_NewObject(cx, &js_WithClass, NULL, NULL); + if (!obj) + return JS_FALSE; + *rval = OBJECT_TO_JSVAL(obj); + } + + parent = cx->fp->scopeChain; + if (argc > 0) { + if (!js_ValueToObject(cx, argv[0], &proto)) + return JS_FALSE; + v = OBJECT_TO_JSVAL(proto); + if (!obj_setSlot(cx, obj, INT_TO_JSVAL(JSSLOT_PROTO), &v)) + return JS_FALSE; + if (argc > 1) { + if (!js_ValueToObject(cx, argv[1], &parent)) + return JS_FALSE; + } + } + v = OBJECT_TO_JSVAL(parent); + return obj_setSlot(cx, obj, INT_TO_JSVAL(JSSLOT_PARENT), &v); +} +#endif + +JSObject * +js_InitObjectClass(JSContext *cx, JSObject *obj) +{ + JSObject *proto; + jsval eval; + +#if JS_HAS_SHARP_VARS + JS_ASSERT(sizeof(jsatomid) * JS_BITS_PER_BYTE >= ATOM_INDEX_LIMIT_LOG2 + 1); +#endif + + proto = JS_InitClass(cx, obj, NULL, &js_ObjectClass, Object, 1, + object_props, object_methods, NULL, NULL); + if (!proto) + return NULL; + +#if JS_HAS_OBJ_PROTO_PROP + if (!JS_InitClass(cx, obj, NULL, &js_WithClass, With, 0, + NULL, NULL, NULL, NULL)) { + return NULL; + } +#endif + + /* ECMA (15.1.2.1) says 'eval' is also a property of the global object. */ + if (!OBJ_GET_PROPERTY(cx, proto, (jsid)cx->runtime->atomState.evalAtom, + &eval)) { + return NULL; + } + if (!OBJ_DEFINE_PROPERTY(cx, obj, (jsid)cx->runtime->atomState.evalAtom, + eval, NULL, NULL, 0, NULL)) { + return NULL; + } + + return proto; +} + +void +js_InitObjectMap(JSObjectMap *map, jsrefcount nrefs, JSObjectOps *ops, + JSClass *clasp) +{ + map->nrefs = nrefs; + map->ops = ops; + map->nslots = JS_INITIAL_NSLOTS; + map->freeslot = JSSLOT_FREE(clasp); +} + +JSObjectMap * +js_NewObjectMap(JSContext *cx, jsrefcount nrefs, JSObjectOps *ops, + JSClass *clasp, JSObject *obj) +{ + return (JSObjectMap *) js_NewScope(cx, nrefs, ops, clasp, obj); +} + +void +js_DestroyObjectMap(JSContext *cx, JSObjectMap *map) +{ + js_DestroyScope(cx, (JSScope *)map); +} + +JSObjectMap * +js_HoldObjectMap(JSContext *cx, JSObjectMap *map) +{ + JS_ASSERT(map->nrefs >= 0); + JS_ATOMIC_INCREMENT(&map->nrefs); + return map; +} + +JSObjectMap * +js_DropObjectMap(JSContext *cx, JSObjectMap *map, JSObject *obj) +{ + JS_ASSERT(map->nrefs > 0); + JS_ATOMIC_DECREMENT(&map->nrefs); + if (map->nrefs == 0) { + map->ops->destroyObjectMap(cx, map); + return NULL; + } + if (MAP_IS_NATIVE(map) && ((JSScope *)map)->object == obj) + ((JSScope *)map)->object = NULL; + return map; +} + +static JSBool +GetClassPrototype(JSContext *cx, JSObject *scope, const char *name, + JSObject **protop); + +JSObject * +js_NewObject(JSContext *cx, JSClass *clasp, JSObject *proto, JSObject *parent) +{ + JSObject *obj, *ctor; + JSObjectOps *ops; + JSObjectMap *map; + jsval cval; + uint32 nslots, i; + jsval *newslots; + + /* Allocate an object from the GC heap and zero it. */ + obj = (JSObject *) js_AllocGCThing(cx, GCX_OBJECT); + if (!obj) + return NULL; + + /* Bootstrap the ur-object, and make it the default prototype object. */ + if (!proto) { + if (!GetClassPrototype(cx, parent, clasp->name, &proto)) + goto bad; + if (!proto && !GetClassPrototype(cx, parent, js_Object_str, &proto)) + goto bad; + } + + /* Always call the class's getObjectOps hook if it has one. */ + ops = clasp->getObjectOps + ? clasp->getObjectOps(cx, clasp) + : &js_ObjectOps; + + /* + * Share proto's map only if it has the same JSObjectOps, and only if + * proto's class has the same private and reserved slots, as obj's map + * and class have. + */ + if (proto && + (map = proto->map)->ops == ops && + ((clasp->flags ^ OBJ_GET_CLASS(cx, proto)->flags) & + (JSCLASS_HAS_PRIVATE | + (JSCLASS_RESERVED_SLOTS_MASK << JSCLASS_RESERVED_SLOTS_SHIFT))) + == 0) { + /* Default parent to the parent of the prototype's constructor. */ + if (!parent) { + if (!OBJ_GET_PROPERTY(cx, proto, + (jsid)cx->runtime->atomState.constructorAtom, + &cval)) { + goto bad; + } + if (JSVAL_IS_OBJECT(cval) && (ctor = JSVAL_TO_OBJECT(cval)) != NULL) + parent = OBJ_GET_PARENT(cx, ctor); + } + + /* Share the given prototype's map. */ + obj->map = js_HoldObjectMap(cx, map); + + /* Ensure that obj starts with the minimum slots for clasp. */ + nslots = JS_INITIAL_NSLOTS; + } else { + /* Leave parent alone. Allocate a new map for obj. */ + map = ops->newObjectMap(cx, 1, ops, clasp, obj); + if (!map) + goto bad; + obj->map = map; + + /* Let ops->newObjectMap set nslots so as to reserve slots. */ + nslots = map->nslots; + } + + /* Allocate a slots vector, with a -1'st element telling its length. */ + newslots = (jsval *) JS_malloc(cx, (nslots + 1) * sizeof(jsval)); + if (!newslots) { + js_DropObjectMap(cx, obj->map, obj); + obj->map = NULL; + goto bad; + } + newslots[0] = nslots; + newslots++; + + /* Set the proto, parent, and class properties. */ + newslots[JSSLOT_PROTO] = OBJECT_TO_JSVAL(proto); + newslots[JSSLOT_PARENT] = OBJECT_TO_JSVAL(parent); + newslots[JSSLOT_CLASS] = PRIVATE_TO_JSVAL(clasp); + + /* Clear above JSSLOT_CLASS so the GC doesn't load uninitialized memory. */ + for (i = JSSLOT_CLASS + 1; i < nslots; i++) + newslots[i] = JSVAL_VOID; + + /* Store newslots after initializing all of 'em, just in case. */ + obj->slots = newslots; + + if (cx->runtime->objectHook) + cx->runtime->objectHook(cx, obj, JS_TRUE, cx->runtime->objectHookData); + + return obj; + +bad: + cx->newborn[GCX_OBJECT] = NULL; + return NULL; +} + +static JSBool +FindConstructor(JSContext *cx, JSObject *scope, const char *name, jsval *vp) +{ + JSAtom *atom; + JSObject *obj; + JSObject *pobj; + JSScopeProperty *sprop; + + atom = js_Atomize(cx, name, strlen(name), 0); + if (!atom) + return JS_FALSE; + + if (scope || (cx->fp && (scope = cx->fp->scopeChain) != NULL)) { + /* Find the topmost object in the scope chain. */ + do { + obj = scope; + scope = OBJ_GET_PARENT(cx, obj); + } while (scope); + } else { + obj = cx->globalObject; + if (!obj) { + *vp = JSVAL_VOID; + return JS_TRUE; + } + } + + if (!OBJ_LOOKUP_PROPERTY(cx, obj, (jsid)atom, &pobj, (JSProperty**)&sprop)) + return JS_FALSE; + if (!sprop) { + *vp = JSVAL_VOID; + return JS_TRUE; + } + + JS_ASSERT(OBJ_IS_NATIVE(pobj)); + JS_ASSERT(SPROP_HAS_VALID_SLOT(sprop, OBJ_SCOPE(pobj))); + *vp = OBJ_GET_SLOT(cx, pobj, sprop->slot); + OBJ_DROP_PROPERTY(cx, pobj, (JSProperty *)sprop); + return JS_TRUE; +} + +JSObject * +js_ConstructObject(JSContext *cx, JSClass *clasp, JSObject *proto, + JSObject *parent, uintN argc, jsval *argv) +{ + jsval cval, rval; + JSObject *obj, *ctor; + + if (!FindConstructor(cx, parent, clasp->name, &cval)) + return NULL; + if (JSVAL_IS_PRIMITIVE(cval)) { + js_ReportIsNotFunction(cx, &cval, JSV2F_CONSTRUCT | JSV2F_SEARCH_STACK); + return NULL; + } + + /* + * If proto or parent are NULL, set them to Constructor.prototype and/or + * Constructor.__parent__, just like JSOP_NEW does. + */ + ctor = JSVAL_TO_OBJECT(cval); + if (!parent) + parent = OBJ_GET_PARENT(cx, ctor); + if (!proto) { + if (!OBJ_GET_PROPERTY(cx, ctor, + (jsid)cx->runtime->atomState.classPrototypeAtom, + &rval)) { + return NULL; + } + if (JSVAL_IS_OBJECT(rval)) + proto = JSVAL_TO_OBJECT(rval); + } + + obj = js_NewObject(cx, clasp, proto, parent); + if (!obj) + return NULL; + + if (!js_InternalConstruct(cx, obj, cval, argc, argv, &rval)) + goto bad; + return JSVAL_IS_OBJECT(rval) ? JSVAL_TO_OBJECT(rval) : obj; +bad: + cx->newborn[GCX_OBJECT] = NULL; + return NULL; +} + +void +js_FinalizeObject(JSContext *cx, JSObject *obj) +{ + JSObjectMap *map; + + /* Cope with stillborn objects that have no map. */ + map = obj->map; + if (!map) + return; + JS_ASSERT(obj->slots); + + if (cx->runtime->objectHook) + cx->runtime->objectHook(cx, obj, JS_FALSE, cx->runtime->objectHookData); + + /* Remove all watchpoints with weak links to obj. */ + JS_ClearWatchPointsForObject(cx, obj); + + /* + * Finalize obj first, in case it needs map and slots. Optimized to use + * LOCKED_OBJ_GET_CLASS instead of OBJ_GET_CLASS, so we avoid "promoting" + * obj's scope from lock-free to lock-full (see jslock.c:ClaimScope) when + * we're called from the GC. Only the GC should call js_FinalizeObject, + * and no other threads run JS (and possibly racing to update obj->slots) + * while the GC is running. + */ + LOCKED_OBJ_GET_CLASS(obj)->finalize(cx, obj); + + /* Drop map and free slots. */ + js_DropObjectMap(cx, map, obj); + obj->map = NULL; + JS_free(cx, obj->slots - 1); + obj->slots = NULL; +} + +/* XXXbe if one adds props, deletes earlier props, adds more, the last added + won't recycle the deleted props' slots. */ +JSBool +js_AllocSlot(JSContext *cx, JSObject *obj, uint32 *slotp) +{ + JSObjectMap *map; + uint32 nslots, i; + size_t nbytes; + jsval *newslots; + + map = obj->map; + JS_ASSERT(!MAP_IS_NATIVE(map) || ((JSScope *)map)->object == obj); + nslots = map->nslots; + if (map->freeslot >= nslots) { + nslots = map->freeslot; + JS_ASSERT(nslots >= JS_INITIAL_NSLOTS); + nslots += (nslots + 1) / 2; + + nbytes = (nslots + 1) * sizeof(jsval); +#if defined _MSC_VER && _MSC_VER <= 800 + if (nbytes > 60000U) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } +#endif + + newslots = (jsval *) JS_realloc(cx, obj->slots - 1, nbytes); + if (!newslots) + return JS_FALSE; + for (i = 1 + newslots[0]; i <= nslots; i++) + newslots[i] = JSVAL_VOID; + newslots[0] = map->nslots = nslots; + obj->slots = newslots + 1; + } + +#ifdef TOO_MUCH_GC + obj->slots[map->freeslot] = JSVAL_VOID; +#endif + *slotp = map->freeslot++; + return JS_TRUE; +} + +void +js_FreeSlot(JSContext *cx, JSObject *obj, uint32 slot) +{ + JSObjectMap *map; + uint32 nslots; + size_t nbytes; + jsval *newslots; + + OBJ_CHECK_SLOT(obj, slot); + obj->slots[slot] = JSVAL_VOID; + map = obj->map; + JS_ASSERT(!MAP_IS_NATIVE(map) || ((JSScope *)map)->object == obj); + if (map->freeslot == slot + 1) + map->freeslot = slot; + nslots = map->nslots; + if (nslots > JS_INITIAL_NSLOTS && map->freeslot < nslots / 2) { + nslots = map->freeslot; + nslots += nslots / 2; + if (nslots < JS_INITIAL_NSLOTS) + nslots = JS_INITIAL_NSLOTS; + nbytes = (nslots + 1) * sizeof(jsval); + newslots = (jsval *) JS_realloc(cx, obj->slots - 1, nbytes); + if (!newslots) + return; + newslots[0] = map->nslots = nslots; + obj->slots = newslots + 1; + } +} + +#if JS_BUG_EMPTY_INDEX_ZERO +#define CHECK_FOR_EMPTY_INDEX(id) \ + JS_BEGIN_MACRO \ + if (JSSTRING_LENGTH(_str) == 0) \ + id = JSVAL_ZERO; \ + JS_END_MACRO +#else +#define CHECK_FOR_EMPTY_INDEX(id) /* nothing */ +#endif + +/* JSVAL_INT_MAX as a string */ +#define JSVAL_INT_MAX_STRING "1073741823" + +#define CHECK_FOR_FUNNY_INDEX(id) \ + JS_BEGIN_MACRO \ + if (!JSVAL_IS_INT(id)) { \ + JSAtom *atom_ = (JSAtom *)id; \ + JSString *str_ = ATOM_TO_STRING(atom_); \ + const jschar *cp_ = str_->chars; \ + JSBool negative_ = (*cp_ == '-'); \ + if (negative_) cp_++; \ + if (JS7_ISDEC(*cp_) && \ + str_->length - negative_ <= sizeof(JSVAL_INT_MAX_STRING)-1) { \ + id = CheckForFunnyIndex(id, cp_, negative_); \ + } else { \ + CHECK_FOR_EMPTY_INDEX(id); \ + } \ + } \ + JS_END_MACRO + +static jsid +CheckForFunnyIndex(jsid id, const jschar *cp, JSBool negative) +{ + jsuint index = JS7_UNDEC(*cp++); + jsuint oldIndex = 0; + jsuint c = 0; + + if (index != 0) { + while (JS7_ISDEC(*cp)) { + oldIndex = index; + c = JS7_UNDEC(*cp); + index = 10 * index + c; + cp++; + } + } + if (*cp == 0 && + (oldIndex < (JSVAL_INT_MAX / 10) || + (oldIndex == (JSVAL_INT_MAX / 10) && + c <= (JSVAL_INT_MAX % 10)))) { + if (negative) + index = 0 - index; + id = INT_TO_JSVAL((jsint)index); + } + return id; +} + +JSScopeProperty * +js_AddNativeProperty(JSContext *cx, JSObject *obj, jsid id, + JSPropertyOp getter, JSPropertyOp setter, uint32 slot, + uintN attrs, uintN flags, intN shortid) +{ + JSScope *scope; + JSScopeProperty *sprop; + + JS_LOCK_OBJ(cx, obj); + scope = js_GetMutableScope(cx, obj); + if (!scope) { + sprop = NULL; + } else { + /* + * Handle old bug that took empty string as zero index. Also convert + * string indices to integers if appropriate. + */ + CHECK_FOR_FUNNY_INDEX(id); + sprop = js_AddScopeProperty(cx, scope, id, getter, setter, slot, attrs, + flags, shortid); + } + JS_UNLOCK_OBJ(cx, obj); + return sprop; +} + +JSScopeProperty * +js_ChangeNativePropertyAttrs(JSContext *cx, JSObject *obj, + JSScopeProperty *sprop, uintN attrs, uintN mask, + JSPropertyOp getter, JSPropertyOp setter) +{ + JSScope *scope; + + JS_LOCK_OBJ(cx, obj); + scope = js_GetMutableScope(cx, obj); + if (!scope) { + sprop = NULL; + } else { + sprop = js_ChangeScopePropertyAttrs(cx, scope, sprop, attrs, mask, + getter, setter); + if (sprop) { + PROPERTY_CACHE_FILL(&cx->runtime->propertyCache, obj, sprop->id, + sprop); + } + } + JS_UNLOCK_OBJ(cx, obj); + return sprop; +} + +JSBool +js_DefineProperty(JSContext *cx, JSObject *obj, jsid id, jsval value, + JSPropertyOp getter, JSPropertyOp setter, uintN attrs, + JSProperty **propp) +{ + return js_DefineNativeProperty(cx, obj, id, value, getter, setter, attrs, + 0, 0, propp); +} + +JSBool +js_DefineNativeProperty(JSContext *cx, JSObject *obj, jsid id, jsval value, + JSPropertyOp getter, JSPropertyOp setter, uintN attrs, + uintN flags, intN shortid, JSProperty **propp) +{ + JSClass *clasp; + JSScope *scope; + JSScopeProperty *sprop; + + /* + * Handle old bug that took empty string as zero index. Also convert + * string indices to integers if appropriate. + */ + CHECK_FOR_FUNNY_INDEX(id); + +#if JS_HAS_GETTER_SETTER + /* + * If defining a getter or setter, we must check for its counterpart and + * update the attributes and property ops. A getter or setter is really + * only half of a property. + */ + if (attrs & (JSPROP_GETTER | JSPROP_SETTER)) { + JSObject *pobj; + + /* + * If JS_THREADSAFE and id is found, js_LookupProperty returns with + * sprop non-null and pobj locked. If pobj == obj, the property is + * already in obj and obj has its own (mutable) scope. So if we are + * defining a getter whose setter was already defined, or vice versa, + * finish the job via js_ChangeScopePropertyAttributes, and refresh + * the property cache line for (obj, id) to map sprop. + */ + if (!js_LookupProperty(cx, obj, id, &pobj, (JSProperty **)&sprop)) + return JS_FALSE; + if (sprop && + pobj == obj && + (sprop->attrs & (JSPROP_GETTER | JSPROP_SETTER))) { + sprop = js_ChangeScopePropertyAttrs(cx, OBJ_SCOPE(obj), sprop, + attrs, sprop->attrs, + (attrs & JSPROP_GETTER) + ? getter + : sprop->getter, + (attrs & JSPROP_SETTER) + ? setter + : sprop->setter); + + /* NB: obj == pobj, so we can share unlock code at the bottom. */ + if (!sprop) + goto bad; + goto out; + } + + if (sprop) { + /* NB: call OBJ_DROP_PROPERTY, as pobj might not be native. */ + OBJ_DROP_PROPERTY(cx, pobj, (JSProperty *)sprop); + sprop = NULL; + } + } +#endif /* JS_HAS_GETTER_SETTER */ + + /* Lock if object locking is required by this implementation. */ + JS_LOCK_OBJ(cx, obj); + + /* Use the object's class getter and setter by default. */ + clasp = LOCKED_OBJ_GET_CLASS(obj); + if (!getter) + getter = clasp->getProperty; + if (!setter) + setter = clasp->setProperty; + + /* Get obj's own scope if it has one, or create a new one for obj. */ + scope = js_GetMutableScope(cx, obj); + if (!scope) + goto bad; + + /* Add the property to scope, or replace an existing one of the same id. */ + if (clasp->flags & JSCLASS_SHARE_ALL_PROPERTIES) + attrs |= JSPROP_SHARED; + sprop = js_AddScopeProperty(cx, scope, id, getter, setter, + SPROP_INVALID_SLOT, attrs, flags, shortid); + if (!sprop) + goto bad; + + /* XXXbe called with lock held */ + if (!clasp->addProperty(cx, obj, SPROP_USERID(sprop), &value)) { + (void) js_RemoveScopeProperty(cx, scope, id); + goto bad; + } + + if (SPROP_HAS_VALID_SLOT(sprop, scope)) + LOCKED_OBJ_SET_SLOT(obj, sprop->slot, value); + +#if JS_HAS_GETTER_SETTER +out: +#endif + PROPERTY_CACHE_FILL(&cx->runtime->propertyCache, obj, id, sprop); + if (propp) + *propp = (JSProperty *) sprop; + else + JS_UNLOCK_OBJ(cx, obj); + return JS_TRUE; + +bad: + JS_UNLOCK_OBJ(cx, obj); + return JS_FALSE; +} + +#if defined JS_THREADSAFE && defined DEBUG +JS_FRIEND_API(JSBool) +_js_LookupProperty(JSContext *cx, JSObject *obj, jsid id, JSObject **objp, + JSProperty **propp, const char *file, uintN line) +#else +JS_FRIEND_API(JSBool) +js_LookupProperty(JSContext *cx, JSObject *obj, jsid id, JSObject **objp, + JSProperty **propp) +#endif +{ + JSObject *start, *obj2, *proto; + JSScope *scope; + JSScopeProperty *sprop; + JSClass *clasp; + JSResolveOp resolve; + JSResolvingKey key; + JSResolvingEntry *entry; + uint32 generation; + JSNewResolveOp newresolve; + uintN flags; + uint32 format; + JSBool ok; + + /* + * Handle old bug that took empty string as zero index. Also convert + * string indices to integers if appropriate. + */ + CHECK_FOR_FUNNY_INDEX(id); + + /* Search scopes starting with obj and following the prototype link. */ + start = obj; + for (;;) { + JS_LOCK_OBJ(cx, obj); + SET_OBJ_INFO(obj, file, line); + scope = OBJ_SCOPE(obj); + if (scope->object == obj) { + sprop = SCOPE_GET_PROPERTY(scope, id); + } else { + /* Shared prototype scope: try resolve before lookup. */ + sprop = NULL; + } + + /* Try obj's class resolve hook if id was not found in obj's scope. */ + if (!sprop) { + clasp = LOCKED_OBJ_GET_CLASS(obj); + resolve = clasp->resolve; + if (resolve != JS_ResolveStub) { + /* Avoid recursion on (obj, id) already being resolved on cx. */ + key.obj = obj; + key.id = id; + + /* + * Once we have successfully added an entry for (obj, key) to + * cx->resolvingTable, control must go through cleanup: before + * returning. But note that JS_DHASH_ADD may find an existing + * entry, in which case we bail to suppress runaway recursion. + */ + if (!StartResolving(cx, &key, JSRESFLAG_LOOKUP, &entry)) { + JS_UNLOCK_OBJ(cx, obj); + return JS_FALSE; + } + if (!entry) { + /* Already resolving id in obj -- dampen recursion. */ + JS_UNLOCK_OBJ(cx, obj); + goto out; + } + generation = cx->resolvingTable->generation; + + /* Null *propp here so we can test it at cleanup: safely. */ + *propp = NULL; + + if (clasp->flags & JSCLASS_NEW_RESOLVE) { + newresolve = (JSNewResolveOp)resolve; + flags = 0; + if (cx->fp && cx->fp->pc) { + format = js_CodeSpec[*cx->fp->pc].format; + if ((format & JOF_MODEMASK) != JOF_NAME) + flags |= JSRESOLVE_QUALIFIED; + if ((format & JOF_ASSIGNING) || + (cx->fp->flags & JSFRAME_ASSIGNING)) { + flags |= JSRESOLVE_ASSIGNING; + } + } + obj2 = (clasp->flags & JSCLASS_NEW_RESOLVE_GETS_START) + ? start + : NULL; + JS_UNLOCK_OBJ(cx, obj); + + /* Protect id and all atoms from a GC nested in resolve. */ + JS_KEEP_ATOMS(cx->runtime); + ok = newresolve(cx, obj, ID_TO_VALUE(id), flags, &obj2); + JS_UNKEEP_ATOMS(cx->runtime); + if (!ok) + goto cleanup; + + JS_LOCK_OBJ(cx, obj); + SET_OBJ_INFO(obj, file, line); + if (obj2) { + /* Resolved: juggle locks and lookup id again. */ + if (obj2 != obj) { + JS_UNLOCK_OBJ(cx, obj); + JS_LOCK_OBJ(cx, obj2); + } + scope = OBJ_SCOPE(obj2); + if (!MAP_IS_NATIVE(&scope->map)) { + /* Whoops, newresolve handed back a foreign obj2. */ + JS_ASSERT(obj2 != obj); + JS_UNLOCK_OBJ(cx, obj2); + ok = OBJ_LOOKUP_PROPERTY(cx, obj2, id, objp, propp); + if (!ok || *propp) + goto cleanup; + JS_LOCK_OBJ(cx, obj2); + } else { + /* + * Require that obj2 have its own scope now, as we + * do for old-style resolve. If it doesn't, then + * id was not truly resolved, and we'll find it in + * the proto chain, or miss it if obj2's proto is + * not on obj's proto chain. That last case is a + * "too bad!" case. + */ + if (scope->object == obj2) + sprop = SCOPE_GET_PROPERTY(scope, id); + } + if (obj2 != obj && !sprop) { + JS_UNLOCK_OBJ(cx, obj2); + JS_LOCK_OBJ(cx, obj); + } + } + } else { + /* + * Old resolve always requires id re-lookup if obj owns + * its scope after resolve returns. + */ + JS_UNLOCK_OBJ(cx, obj); + ok = resolve(cx, obj, ID_TO_VALUE(id)); + if (!ok) + goto cleanup; + JS_LOCK_OBJ(cx, obj); + SET_OBJ_INFO(obj, file, line); + scope = OBJ_SCOPE(obj); + JS_ASSERT(MAP_IS_NATIVE(&scope->map)); + if (scope->object == obj) + sprop = SCOPE_GET_PROPERTY(scope, id); + } + + cleanup: + StopResolving(cx, &key, JSRESFLAG_LOOKUP, entry, generation); + if (!ok || *propp) + return ok; + } + } + + if (sprop) { + JS_ASSERT(OBJ_SCOPE(obj) == scope); + *objp = scope->object; /* XXXbe hide in jsscope.[ch] */ + + *propp = (JSProperty *) sprop; + return JS_TRUE; + } + + proto = LOCKED_OBJ_GET_PROTO(obj); + JS_UNLOCK_OBJ(cx, obj); + if (!proto) + break; + if (!OBJ_IS_NATIVE(proto)) + return OBJ_LOOKUP_PROPERTY(cx, proto, id, objp, propp); + obj = proto; + } + +out: + *objp = NULL; + *propp = NULL; + return JS_TRUE; +} + +JS_FRIEND_API(JSBool) +js_FindProperty(JSContext *cx, jsid id, JSObject **objp, JSObject **pobjp, + JSProperty **propp) +{ + JSRuntime *rt; + JSObject *obj, *pobj, *lastobj; + JSScopeProperty *sprop; + JSProperty *prop; + + rt = cx->runtime; + obj = cx->fp->scopeChain; + do { + /* Try the property cache and return immediately on cache hit. */ + if (OBJ_IS_NATIVE(obj)) { + JS_LOCK_OBJ(cx, obj); + PROPERTY_CACHE_TEST(&rt->propertyCache, obj, id, sprop); + if (sprop) { + JS_ASSERT(OBJ_IS_NATIVE(obj)); + *objp = obj; + *pobjp = obj; + *propp = (JSProperty *) sprop; + return JS_TRUE; + } + JS_UNLOCK_OBJ(cx, obj); + } + + /* If cache miss, take the slow path. */ + if (!OBJ_LOOKUP_PROPERTY(cx, obj, id, &pobj, &prop)) + return JS_FALSE; + if (prop) { + if (OBJ_IS_NATIVE(pobj)) { + sprop = (JSScopeProperty *) prop; + PROPERTY_CACHE_FILL(&rt->propertyCache, pobj, id, sprop); + } + *objp = obj; + *pobjp = pobj; + *propp = prop; + return JS_TRUE; + } + lastobj = obj; + } while ((obj = OBJ_GET_PARENT(cx, obj)) != NULL); + + *objp = lastobj; + *pobjp = NULL; + *propp = NULL; + return JS_TRUE; +} + +JSObject * +js_FindIdentifierBase(JSContext *cx, jsid id) +{ + JSObject *obj, *pobj; + JSProperty *prop; + + /* + * Look for id's property along the "with" statement chain and the + * statically-linked scope chain. + */ + if (!js_FindProperty(cx, id, &obj, &pobj, &prop)) + return NULL; + if (prop) { + OBJ_DROP_PROPERTY(cx, pobj, prop); + return obj; + } + + /* + * Use the top-level scope from the scope chain, which won't end in the + * same scope as cx->globalObject for cross-context function calls. + */ + JS_ASSERT(obj); + + /* + * Property not found. Give a strict warning if binding an undeclared + * top-level variable. + */ + if (JS_HAS_STRICT_OPTION(cx)) { + JSString *str = JSVAL_TO_STRING(ID_TO_VALUE(id)); + if (!JS_ReportErrorFlagsAndNumber(cx, + JSREPORT_WARNING | JSREPORT_STRICT, + js_GetErrorMessage, NULL, + JSMSG_UNDECLARED_VAR, + JS_GetStringBytes(str))) { + return NULL; + } + } + return obj; +} + +JSBool +js_GetProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp) +{ + JSObject *obj2; + JSScopeProperty *sprop; + JSScope *scope; + uint32 slot; + + /* + * Handle old bug that took empty string as zero index. Also convert + * string indices to integers if appropriate. + */ + CHECK_FOR_FUNNY_INDEX(id); + + if (!js_LookupProperty(cx, obj, id, &obj2, (JSProperty **)&sprop)) + return JS_FALSE; + if (!sprop) { + jsval default_val; + +#if JS_BUG_NULL_INDEX_PROPS + /* Indexed properties defaulted to null in old versions. */ + default_val = (JSVAL_IS_INT(id) && JSVAL_TO_INT(id) >= 0) + ? JSVAL_NULL + : JSVAL_VOID; +#else + default_val = JSVAL_VOID; +#endif + *vp = default_val; + + if (!OBJ_GET_CLASS(cx, obj)->getProperty(cx, obj, ID_TO_VALUE(id), vp)) + return JS_FALSE; + + /* + * Give a strict warning if foo.bar is evaluated by a script for an + * object foo with no property named 'bar'. + */ + if (JS_HAS_STRICT_OPTION(cx) && + *vp == default_val && + cx->fp && cx->fp->pc && + (*cx->fp->pc == JSOP_GETPROP || *cx->fp->pc == JSOP_GETELEM)) + { + jsbytecode *pc, *endpc; + JSString *str; + + /* Kludge to allow (typeof foo == "undefined") tests. */ + JS_ASSERT(cx->fp->script); + pc = cx->fp->pc; + pc += js_CodeSpec[*pc].length; + endpc = cx->fp->script->code + cx->fp->script->length; + while (pc < endpc) { + if (*pc == JSOP_TYPEOF) + return JS_TRUE; + if (*pc != JSOP_GROUP) + break; + pc++; + } + + /* Ok, bad undefined property reference: whine about it. */ + str = js_DecompileValueGenerator(cx, JS_FALSE, ID_TO_VALUE(id), + NULL); + if (!str || + !JS_ReportErrorFlagsAndNumber(cx, + JSREPORT_WARNING|JSREPORT_STRICT, + js_GetErrorMessage, NULL, + JSMSG_UNDEFINED_PROP, + JS_GetStringBytes(str))) { + return JS_FALSE; + } + } + return JS_TRUE; + } + + if (!OBJ_IS_NATIVE(obj2)) { + OBJ_DROP_PROPERTY(cx, obj2, (JSProperty *)sprop); + return OBJ_GET_PROPERTY(cx, obj2, id, vp); + } + + /* Unlock obj2 before calling getter, relock after to avoid deadlock. */ + scope = OBJ_SCOPE(obj2); + slot = sprop->slot; + if (slot != SPROP_INVALID_SLOT) { + JS_ASSERT(slot < obj2->map->freeslot); + *vp = LOCKED_OBJ_GET_SLOT(obj2, slot); + + /* If sprop has a stub getter, we're done. */ + if (!sprop->getter) + goto out; + } else { + *vp = JSVAL_VOID; + } + + JS_UNLOCK_SCOPE(cx, scope); + if (!SPROP_GET(cx, sprop, obj, obj2, vp)) + return JS_FALSE; + JS_LOCK_SCOPE(cx, scope); + + if (SPROP_HAS_VALID_SLOT(sprop, scope)) { + LOCKED_OBJ_SET_SLOT(obj2, slot, *vp); + PROPERTY_CACHE_FILL(&cx->runtime->propertyCache, obj2, id, sprop); + } + +out: + JS_UNLOCK_SCOPE(cx, scope); + return JS_TRUE; +} + +JSBool +js_SetProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp) +{ + JSObject *pobj; + JSScopeProperty *sprop; + JSScope *scope; + uintN attrs, flags; + intN shortid; + JSClass *clasp; + JSPropertyOp getter, setter; + jsval pval; + uint32 slot; + + /* + * Handle old bug that took empty string as zero index. Also convert + * string indices to integers if appropriate. + */ + CHECK_FOR_FUNNY_INDEX(id); + + if (!js_LookupProperty(cx, obj, id, &pobj, (JSProperty **)&sprop)) + return JS_FALSE; + + if (sprop && !OBJ_IS_NATIVE(pobj)) { + OBJ_DROP_PROPERTY(cx, pobj, (JSProperty *)sprop); + sprop = NULL; + } + + /* + * Now either sprop is null, meaning id was not found in obj or one of its + * prototypes; or sprop is non-null, meaning id was found in pobj's scope. + * If JS_THREADSAFE and sprop is non-null, then scope is locked, and sprop + * is held: we must OBJ_DROP_PROPERTY or JS_UNLOCK_SCOPE before we return + * (the two are equivalent for native objects, but we use JS_UNLOCK_SCOPE + * because it is cheaper). + */ + attrs = JSPROP_ENUMERATE; + flags = 0; + shortid = 0; + clasp = OBJ_GET_CLASS(cx, obj); + getter = clasp->getProperty; + setter = clasp->setProperty; + + if (sprop) { + /* + * Set scope for use below. It was locked by js_LookupProperty, and + * we know pobj owns it (i.e., scope->object == pobj). Therefore we + * optimize JS_UNLOCK_OBJ(cx, pobj) into JS_UNLOCK_SCOPE(cx, scope). + */ + scope = OBJ_SCOPE(pobj); + + attrs = sprop->attrs; + if ((attrs & JSPROP_READONLY) || + (SCOPE_IS_SEALED(scope) && pobj == obj)) { + JS_UNLOCK_SCOPE(cx, scope); + if ((attrs & JSPROP_READONLY) && JSVERSION_IS_ECMA(cx->version)) + return JS_TRUE; + goto read_only_error; + } + + if (pobj != obj) { + /* + * We found id in a prototype object: prepare to share or shadow. + * NB: Thanks to the immutable, garbage-collected property tree + * maintained by jsscope.c in cx->runtime, we needn't worry about + * sprop going away behind our back after we've unlocked scope. + */ + JS_UNLOCK_SCOPE(cx, scope); + + /* Don't clone a shared prototype property. */ + if (attrs & JSPROP_SHARED) + return SPROP_SET(cx, sprop, obj, pobj, vp); + + /* Restore attrs to the ECMA default for new properties. */ + attrs = JSPROP_ENUMERATE; + + /* + * Preserve the shortid, getter, and setter when shadowing any + * property that has a shortid. An old API convention requires + * that the property's getter and setter functions receive the + * shortid, not id, when they are called on the shadow we are + * about to create in obj's scope. + */ + if (sprop->flags & SPROP_HAS_SHORTID) { + flags = SPROP_HAS_SHORTID; + shortid = sprop->shortid; + getter = sprop->getter; + setter = sprop->setter; + } + + /* + * Forget we found the proto-property now that we've copied any + * needed member values. + */ + sprop = NULL; + } +#ifdef __GNUC__ /* suppress bogus gcc warnings */ + } else { + scope = NULL; +#endif + } + + if (!sprop) { + if (SCOPE_IS_SEALED(OBJ_SCOPE(obj)) && OBJ_SCOPE(obj)->object == obj) + goto read_only_error; + + /* Find or make a property descriptor with the right heritage. */ + JS_LOCK_OBJ(cx, obj); + scope = js_GetMutableScope(cx, obj); + if (!scope) { + JS_UNLOCK_OBJ(cx, obj); + return JS_FALSE; + } + if (clasp->flags & JSCLASS_SHARE_ALL_PROPERTIES) + attrs |= JSPROP_SHARED; + sprop = js_AddScopeProperty(cx, scope, id, getter, setter, + SPROP_INVALID_SLOT, attrs, flags, shortid); + if (!sprop) { + JS_UNLOCK_SCOPE(cx, scope); + return JS_FALSE; + } + + /* XXXbe called with obj locked */ + if (!clasp->addProperty(cx, obj, SPROP_USERID(sprop), vp)) { + (void) js_RemoveScopeProperty(cx, scope, id); + JS_UNLOCK_SCOPE(cx, scope); + return JS_FALSE; + } + + /* Initialize new property value (passed to setter) to undefined. */ + if (SPROP_HAS_VALID_SLOT(sprop, scope)) + LOCKED_OBJ_SET_SLOT(obj, sprop->slot, JSVAL_VOID); + + PROPERTY_CACHE_FILL(&cx->runtime->propertyCache, obj, id, sprop); + } + + /* Get the current property value from its slot. */ + slot = sprop->slot; + if (slot != SPROP_INVALID_SLOT) { + JS_ASSERT(slot < obj->map->freeslot); + pval = LOCKED_OBJ_GET_SLOT(obj, slot); + + /* If sprop has a stub setter, keep scope locked and just store *vp. */ + if (!sprop->setter) + goto set_slot; + } + + /* Avoid deadlock by unlocking obj's scope while calling sprop's setter. */ + JS_UNLOCK_SCOPE(cx, scope); + + /* Let the setter modify vp before copying from it to obj->slots[slot]. */ + if (!SPROP_SET(cx, sprop, obj, obj, vp)) + return JS_FALSE; + + /* Relock obj's scope until we are done with sprop. */ + JS_LOCK_SCOPE(cx, scope); + + /* + * Check whether sprop is still around (was not deleted), and whether it + * has a slot (it may never have had one, or we may have lost a race with + * someone who cleared scope). + */ + if (SPROP_HAS_VALID_SLOT(sprop, scope)) { + set_slot: + GC_POKE(cx, pval); + LOCKED_OBJ_SET_SLOT(obj, slot, *vp); + } + JS_UNLOCK_SCOPE(cx, scope); + return JS_TRUE; + + read_only_error: { + JSString *str = js_DecompileValueGenerator(cx, + JSDVG_IGNORE_STACK, + ID_TO_VALUE(id), + NULL); + if (str) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_READ_ONLY, + JS_GetStringBytes(str)); + } + return JS_FALSE; + } +} + +JSBool +js_GetAttributes(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop, + uintN *attrsp) +{ + JSBool noprop, ok; + JSScopeProperty *sprop; + + noprop = !prop; + if (noprop) { + if (!js_LookupProperty(cx, obj, id, &obj, &prop)) + return JS_FALSE; + if (!prop) { + *attrsp = 0; + return JS_TRUE; + } + if (!OBJ_IS_NATIVE(obj)) { + ok = OBJ_GET_ATTRIBUTES(cx, obj, id, prop, attrsp); + OBJ_DROP_PROPERTY(cx, obj, prop); + return ok; + } + } + sprop = (JSScopeProperty *)prop; + *attrsp = sprop->attrs; + if (noprop) + OBJ_DROP_PROPERTY(cx, obj, prop); + return JS_TRUE; +} + +JSBool +js_SetAttributes(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop, + uintN *attrsp) +{ + JSBool noprop, ok; + JSScopeProperty *sprop; + + noprop = !prop; + if (noprop) { + if (!js_LookupProperty(cx, obj, id, &obj, &prop)) + return JS_FALSE; + if (!prop) + return JS_TRUE; + if (!OBJ_IS_NATIVE(obj)) { + ok = OBJ_SET_ATTRIBUTES(cx, obj, id, prop, attrsp); + OBJ_DROP_PROPERTY(cx, obj, prop); + return ok; + } + } + sprop = (JSScopeProperty *)prop; + sprop = js_ChangeNativePropertyAttrs(cx, obj, sprop, + *attrsp & + ~(JSPROP_GETTER | JSPROP_SETTER), 0, + sprop->getter, sprop->setter); + if (noprop) + OBJ_DROP_PROPERTY(cx, obj, prop); + return (sprop != NULL); +} + +JSBool +js_DeleteProperty(JSContext *cx, JSObject *obj, jsid id, jsval *rval) +{ +#if JS_HAS_PROP_DELETE + + JSObject *proto; + JSProperty *prop; + JSScopeProperty *sprop; + JSString *str; + JSScope *scope; + JSBool ok; + + *rval = JSVERSION_IS_ECMA(cx->version) ? JSVAL_TRUE : JSVAL_VOID; + + /* + * Handle old bug that took empty string as zero index. Also convert + * string indices to integers if appropriate. + */ + CHECK_FOR_FUNNY_INDEX(id); + + if (!js_LookupProperty(cx, obj, id, &proto, &prop)) + return JS_FALSE; + if (!prop || proto != obj) { + /* + * If the property was found in a native prototype, check whether it's + * shared and permanent. Such a property stands for direct properties + * in all delegating objects, matching ECMA semantics without bloating + * each delegating object. + */ + if (prop) { + if (OBJ_IS_NATIVE(proto)) { + sprop = (JSScopeProperty *)prop; + if (SPROP_IS_SHARED_PERMANENT(sprop)) + *rval = JSVAL_FALSE; + } + OBJ_DROP_PROPERTY(cx, proto, prop); + if (*rval == JSVAL_FALSE) + return JS_TRUE; + } + + /* + * If no property, or the property comes unshared or impermanent from + * a prototype, call the class's delProperty hook, passing rval as the + * result parameter. + */ + return OBJ_GET_CLASS(cx, obj)->delProperty(cx, obj, ID_TO_VALUE(id), + rval); + } + + sprop = (JSScopeProperty *)prop; + if (sprop->attrs & JSPROP_PERMANENT) { + OBJ_DROP_PROPERTY(cx, obj, prop); + if (JSVERSION_IS_ECMA(cx->version)) { + *rval = JSVAL_FALSE; + return JS_TRUE; + } + str = js_DecompileValueGenerator(cx, JSDVG_IGNORE_STACK, + ID_TO_VALUE(id), NULL); + if (str) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_PERMANENT, JS_GetStringBytes(str)); + } + return JS_FALSE; + } + + /* XXXbe called with obj locked */ + if (!LOCKED_OBJ_GET_CLASS(obj)->delProperty(cx, obj, SPROP_USERID(sprop), + rval)) { + OBJ_DROP_PROPERTY(cx, obj, prop); + return JS_FALSE; + } + + scope = OBJ_SCOPE(obj); + if (SPROP_HAS_VALID_SLOT(sprop, scope)) + GC_POKE(cx, LOCKED_OBJ_GET_SLOT(obj, sprop->slot)); + + PROPERTY_CACHE_FILL(&cx->runtime->propertyCache, obj, id, NULL); + ok = js_RemoveScopeProperty(cx, scope, id); + OBJ_DROP_PROPERTY(cx, obj, prop); + return ok; + +#else /* !JS_HAS_PROP_DELETE */ + + jsval null = JSVAL_NULL; + + *rval = JSVAL_VOID; + return js_SetProperty(cx, obj, id, &null); + +#endif /* !JS_HAS_PROP_DELETE */ +} + +JSBool +js_DefaultValue(JSContext *cx, JSObject *obj, JSType hint, jsval *vp) +{ + jsval v; + JSString *str; + + v = OBJECT_TO_JSVAL(obj); + switch (hint) { + case JSTYPE_STRING: + /* + * Propagate the exception if js_TryMethod finds an appropriate + * method, and calling that method returned failure. + */ + if (!js_TryMethod(cx, obj, cx->runtime->atomState.toStringAtom, 0, NULL, + &v)) + return JS_FALSE; + + if (!JSVAL_IS_PRIMITIVE(v)) { + if (!OBJ_GET_CLASS(cx, obj)->convert(cx, obj, hint, &v)) + return JS_FALSE; + + /* + * JS1.2 never failed (except for malloc failure) to convert an + * object to a string. ECMA requires an error if both toString + * and valueOf fail to produce a primitive value. + */ + if (!JSVAL_IS_PRIMITIVE(v) && cx->version == JSVERSION_1_2) { + char *bytes = JS_smprintf("[object %s]", + OBJ_GET_CLASS(cx, obj)->name); + if (!bytes) + return JS_FALSE; + str = JS_NewString(cx, bytes, strlen(bytes)); + if (!str) { + free(bytes); + return JS_FALSE; + } + v = STRING_TO_JSVAL(str); + goto out; + } + } + break; + + default: + if (!OBJ_GET_CLASS(cx, obj)->convert(cx, obj, hint, &v)) + return JS_FALSE; + if (!JSVAL_IS_PRIMITIVE(v)) { + JSType type = JS_TypeOfValue(cx, v); + if (type == hint || + (type == JSTYPE_FUNCTION && hint == JSTYPE_OBJECT)) { + goto out; + } + /* Don't convert to string (source object literal) for JS1.2. */ + if (cx->version == JSVERSION_1_2 && hint == JSTYPE_BOOLEAN) + goto out; + if (!js_TryMethod(cx, obj, cx->runtime->atomState.toStringAtom, 0, + NULL, &v)) + return JS_FALSE; + } + break; + } + if (!JSVAL_IS_PRIMITIVE(v)) { + /* Avoid recursive death through js_DecompileValueGenerator. */ + if (hint == JSTYPE_STRING) { + str = JS_InternString(cx, OBJ_GET_CLASS(cx, obj)->name); + if (!str) + return JS_FALSE; + } else { + str = NULL; + } + *vp = OBJECT_TO_JSVAL(obj); + str = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, v, str); + if (str) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_CANT_CONVERT_TO, + JS_GetStringBytes(str), + (hint == JSTYPE_VOID) + ? "primitive type" + : js_type_str[hint]); + } + return JS_FALSE; + } +out: + *vp = v; + return JS_TRUE; +} + +JSIdArray * +js_NewIdArray(JSContext *cx, jsint length) +{ + JSIdArray *ida; + + ida = (JSIdArray *) + JS_malloc(cx, sizeof(JSIdArray) + (length - 1) * sizeof(jsval)); + if (ida) + ida->length = length; + return ida; +} + +JSIdArray * +js_GrowIdArray(JSContext *cx, JSIdArray *ida, jsint length) +{ + ida = (JSIdArray *) + JS_realloc(cx, ida, sizeof(JSIdArray) + (length - 1) * sizeof(jsval)); + if (ida) + ida->length = length; + return ida; +} + +/* Private type used to iterate over all properties of a native JS object */ +typedef struct JSNativeIteratorState { + jsint next_index; /* index into jsid array */ + JSIdArray *ida; /* All property ids in enumeration */ +} JSNativeIteratorState; + +/* + * This function is used to enumerate the properties of native JSObjects + * and those host objects that do not define a JSNewEnumerateOp-style iterator + * function. + */ +JSBool +js_Enumerate(JSContext *cx, JSObject *obj, JSIterateOp enum_op, + jsval *statep, jsid *idp) +{ + JSObject *proto_obj; + JSClass *clasp; + JSEnumerateOp enumerate; + JSScopeProperty *sprop, *lastProp; + jsint i, length; + JSScope *scope; + JSIdArray *ida; + JSNativeIteratorState *state; + + clasp = OBJ_GET_CLASS(cx, obj); + enumerate = clasp->enumerate; + if (clasp->flags & JSCLASS_NEW_ENUMERATE) + return ((JSNewEnumerateOp) enumerate)(cx, obj, enum_op, statep, idp); + + switch (enum_op) { + + case JSENUMERATE_INIT: + if (!enumerate(cx, obj)) + goto init_error; + length = 0; + + /* + * The set of all property ids is pre-computed when the iterator + * is initialized so as to avoid problems with properties being + * deleted during the iteration. + */ + JS_LOCK_OBJ(cx, obj); + scope = OBJ_SCOPE(obj); + + /* + * If this object shares a scope with its prototype, don't enumerate + * its properties. Otherwise they will be enumerated a second time + * when the prototype object is enumerated. + */ + proto_obj = OBJ_GET_PROTO(cx, obj); + if (proto_obj && scope == OBJ_SCOPE(proto_obj)) { + ida = js_NewIdArray(cx, 0); + if (!ida) { + JS_UNLOCK_OBJ(cx, obj); + goto init_error; + } + } else { + /* Object has a private scope; Enumerate all props in scope. */ + for (sprop = lastProp = SCOPE_LAST_PROP(scope); sprop; + sprop = sprop->parent) { + if ((sprop->attrs & JSPROP_ENUMERATE) && + !(sprop->flags & SPROP_IS_ALIAS) && + (!SCOPE_HAD_MIDDLE_DELETE(scope) || + SCOPE_HAS_PROPERTY(scope, sprop))) { + length++; + } + } + ida = js_NewIdArray(cx, length); + if (!ida) { + JS_UNLOCK_OBJ(cx, obj); + goto init_error; + } + i = length; + for (sprop = lastProp; sprop; sprop = sprop->parent) { + if ((sprop->attrs & JSPROP_ENUMERATE) && + !(sprop->flags & SPROP_IS_ALIAS) && + (!SCOPE_HAD_MIDDLE_DELETE(scope) || + SCOPE_HAS_PROPERTY(scope, sprop))) { + JS_ASSERT(i > 0); + ida->vector[--i] = sprop->id; + } + } + } + JS_UNLOCK_OBJ(cx, obj); + + state = (JSNativeIteratorState *) + JS_malloc(cx, sizeof(JSNativeIteratorState)); + if (!state) { + JS_DestroyIdArray(cx, ida); + goto init_error; + } + state->ida = ida; + state->next_index = 0; + *statep = PRIVATE_TO_JSVAL(state); + if (idp) + *idp = INT_TO_JSVAL(length); + return JS_TRUE; + + case JSENUMERATE_NEXT: + state = (JSNativeIteratorState *) JSVAL_TO_PRIVATE(*statep); + ida = state->ida; + length = ida->length; + if (state->next_index != length) { + *idp = ida->vector[state->next_index++]; + return JS_TRUE; + } + + /* Fall through ... */ + + case JSENUMERATE_DESTROY: + state = (JSNativeIteratorState *) JSVAL_TO_PRIVATE(*statep); + JS_DestroyIdArray(cx, state->ida); + JS_free(cx, state); + *statep = JSVAL_NULL; + return JS_TRUE; + + default: + JS_ASSERT(0); + return JS_FALSE; + } + +init_error: + *statep = JSVAL_NULL; + return JS_FALSE; +} + +JSBool +js_CheckAccess(JSContext *cx, JSObject *obj, jsid id, JSAccessMode mode, + jsval *vp, uintN *attrsp) +{ + JSObject *pobj; + JSProperty *prop; + JSScopeProperty *sprop; + JSClass *clasp; + JSBool ok; + + if (!js_LookupProperty(cx, obj, id, &pobj, &prop)) + return JS_FALSE; + if (!prop) { + *vp = JSVAL_VOID; + *attrsp = 0; + clasp = OBJ_GET_CLASS(cx, obj); + return !clasp->checkAccess || + clasp->checkAccess(cx, obj, ID_TO_VALUE(id), mode, vp); + } + if (!OBJ_IS_NATIVE(pobj)) { + OBJ_DROP_PROPERTY(cx, pobj, prop); + return OBJ_CHECK_ACCESS(cx, pobj, id, mode, vp, attrsp); + } + sprop = (JSScopeProperty *)prop; + *vp = (SPROP_HAS_VALID_SLOT(sprop, OBJ_SCOPE(pobj))) + ? LOCKED_OBJ_GET_SLOT(pobj, sprop->slot) + : JSVAL_VOID; + *attrsp = sprop->attrs; + clasp = LOCKED_OBJ_GET_CLASS(obj); + if (clasp->checkAccess) { + JS_UNLOCK_OBJ(cx, pobj); + ok = clasp->checkAccess(cx, obj, ID_TO_VALUE(id), mode, vp); + JS_LOCK_OBJ(cx, pobj); + } else { + ok = JS_TRUE; + } + OBJ_DROP_PROPERTY(cx, pobj, prop); + return ok; +} + +#ifdef JS_THREADSAFE +void +js_DropProperty(JSContext *cx, JSObject *obj, JSProperty *prop) +{ + JS_UNLOCK_OBJ(cx, obj); +} +#endif + +static void +ReportIsNotFunction(JSContext *cx, jsval *vp, uintN flags) +{ + /* + * The decompiler may need to access the args of the function in + * progress rather than the one we had hoped to call. + * So we switch the cx->fp to the frame below us. We stick the + * current frame in the dormantFrameChain to protect it from gc. + */ + + JSStackFrame *fp = cx->fp; + if (fp->down) { + JS_ASSERT(!fp->dormantNext); + fp->dormantNext = cx->dormantFrameChain; + cx->dormantFrameChain = fp; + cx->fp = fp->down; + } + + js_ReportIsNotFunction(cx, vp, flags); + + if (fp->down) { + JS_ASSERT(cx->dormantFrameChain == fp); + cx->dormantFrameChain = fp->dormantNext; + fp->dormantNext = NULL; + cx->fp = fp; + } +} + +JSBool +js_Call(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSClass *clasp; + + clasp = OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(argv[-2])); + if (!clasp->call) { + ReportIsNotFunction(cx, &argv[-2], 0); + return JS_FALSE; + } + return clasp->call(cx, obj, argc, argv, rval); +} + +JSBool +js_Construct(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSClass *clasp; + + clasp = OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(argv[-2])); + if (!clasp->construct) { + ReportIsNotFunction(cx, &argv[-2], JSV2F_CONSTRUCT); + return JS_FALSE; + } + return clasp->construct(cx, obj, argc, argv, rval); +} + +JSBool +js_HasInstance(JSContext *cx, JSObject *obj, jsval v, JSBool *bp) +{ + JSClass *clasp; + + clasp = OBJ_GET_CLASS(cx, obj); + if (clasp->hasInstance) + return clasp->hasInstance(cx, obj, v, bp); + *bp = JS_FALSE; + return JS_TRUE; +} + +JSBool +js_IsDelegate(JSContext *cx, JSObject *obj, jsval v, JSBool *bp) +{ + JSObject *obj2; + + *bp = JS_FALSE; + if (JSVAL_IS_PRIMITIVE(v)) + return JS_TRUE; + obj2 = JSVAL_TO_OBJECT(v); + while ((obj2 = OBJ_GET_PROTO(cx, obj2)) != NULL) { + if (obj2 == obj) { + *bp = JS_TRUE; + break; + } + } + return JS_TRUE; +} + +JSBool +js_GetClassPrototype(JSContext *cx, const char *name, JSObject **protop) +{ + return GetClassPrototype(cx, NULL, name, protop); +} + +static JSBool +GetClassPrototype(JSContext *cx, JSObject *scope, const char *name, + JSObject **protop) +{ + jsval v; + JSObject *ctor; + + if (!FindConstructor(cx, scope, name, &v)) + return JS_FALSE; + if (JSVAL_IS_FUNCTION(cx, v)) { + ctor = JSVAL_TO_OBJECT(v); + if (!OBJ_GET_PROPERTY(cx, ctor, + (jsid)cx->runtime->atomState.classPrototypeAtom, + &v)) { + return JS_FALSE; + } + } + *protop = JSVAL_IS_OBJECT(v) ? JSVAL_TO_OBJECT(v) : NULL; + return JS_TRUE; +} + +JSBool +js_SetClassPrototype(JSContext *cx, JSObject *ctor, JSObject *proto, + uintN attrs) +{ + /* + * Use the given attributes for the prototype property of the constructor, + * as user-defined constructors have a DontEnum | DontDelete prototype (it + * may be reset), while native or "system" constructors require DontEnum | + * ReadOnly | DontDelete. + */ + if (!OBJ_DEFINE_PROPERTY(cx, ctor, + (jsid)cx->runtime->atomState.classPrototypeAtom, + OBJECT_TO_JSVAL(proto), NULL, NULL, + attrs, NULL)) { + return JS_FALSE; + } + + /* + * ECMA says that Object.prototype.constructor, or f.prototype.constructor + * for a user-defined function f, is DontEnum. + */ + return OBJ_DEFINE_PROPERTY(cx, proto, + (jsid)cx->runtime->atomState.constructorAtom, + OBJECT_TO_JSVAL(ctor), NULL, NULL, + 0, NULL); +} + +JSBool +js_ValueToObject(JSContext *cx, jsval v, JSObject **objp) +{ + JSObject *obj; + + if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v)) { + obj = NULL; + } else if (JSVAL_IS_OBJECT(v)) { + obj = JSVAL_TO_OBJECT(v); + if (!OBJ_DEFAULT_VALUE(cx, obj, JSTYPE_OBJECT, &v)) + return JS_FALSE; + if (JSVAL_IS_OBJECT(v)) + obj = JSVAL_TO_OBJECT(v); + } else { + if (JSVAL_IS_STRING(v)) { + obj = js_StringToObject(cx, JSVAL_TO_STRING(v)); + } else if (JSVAL_IS_INT(v)) { + obj = js_NumberToObject(cx, (jsdouble)JSVAL_TO_INT(v)); + } else if (JSVAL_IS_DOUBLE(v)) { + obj = js_NumberToObject(cx, *JSVAL_TO_DOUBLE(v)); + } else { + JS_ASSERT(JSVAL_IS_BOOLEAN(v)); + obj = js_BooleanToObject(cx, JSVAL_TO_BOOLEAN(v)); + } + if (!obj) + return JS_FALSE; + } + *objp = obj; + return JS_TRUE; +} + +JSObject * +js_ValueToNonNullObject(JSContext *cx, jsval v) +{ + JSObject *obj; + JSString *str; + + if (!js_ValueToObject(cx, v, &obj)) + return NULL; + if (!obj) { + str = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, v, NULL); + if (str) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_NO_PROPERTIES, JS_GetStringBytes(str)); + } + } + return obj; +} + +JSBool +js_TryValueOf(JSContext *cx, JSObject *obj, JSType type, jsval *rval) +{ +#if JS_HAS_VALUEOF_HINT + jsval argv[1]; + + argv[0] = ATOM_KEY(cx->runtime->atomState.typeAtoms[type]); + return js_TryMethod(cx, obj, cx->runtime->atomState.valueOfAtom, 1, argv, + rval); +#else + return js_TryMethod(cx, obj, cx->runtime->atomState.valueOfAtom, 0, NULL, + rval); +#endif +} + +JSBool +js_TryMethod(JSContext *cx, JSObject *obj, JSAtom *atom, + uintN argc, jsval *argv, jsval *rval) +{ + JSErrorReporter older; + jsval fval; + JSBool ok; + + /* + * Report failure only if an appropriate method was found, and calling it + * returned failure. We propagate failure in this case to make exceptions + * behave properly. + */ + older = JS_SetErrorReporter(cx, NULL); + if (OBJ_GET_PROPERTY(cx, obj, (jsid)atom, &fval) && + !JSVAL_IS_PRIMITIVE(fval)) { + ok = js_InternalCall(cx, obj, fval, argc, argv, rval); + } else { + ok = JS_TRUE; + } + JS_SetErrorReporter(cx, older); + return ok; +} + +#if JS_HAS_XDR + +#include "jsxdrapi.h" + +JSBool +js_XDRObject(JSXDRState *xdr, JSObject **objp) +{ + JSContext *cx; + JSClass *clasp; + const char *className; + uint32 classId, classDef; + JSBool ok; + JSObject *proto; + + cx = xdr->cx; + if (xdr->mode == JSXDR_ENCODE) { + clasp = OBJ_GET_CLASS(cx, *objp); + className = clasp->name; + classId = JS_XDRFindClassIdByName(xdr, className); + classDef = !classId; + if (classDef && !JS_XDRRegisterClass(xdr, clasp, &classId)) + return JS_FALSE; + } else { + classDef = 0; + className = NULL; + clasp = NULL; /* quell GCC overwarning */ + } + + /* XDR a flag word followed (if true) by the class name. */ + if (!JS_XDRUint32(xdr, &classDef)) + return JS_FALSE; + if (classDef && !JS_XDRCString(xdr, (char **) &className)) + return JS_FALSE; + + /* From here on, return through out: to free className if it was set. */ + ok = JS_XDRUint32(xdr, &classId); + if (!ok) + goto out; + + if (xdr->mode != JSXDR_ENCODE) { + if (classDef) { + ok = js_GetClassPrototype(cx, className, &proto); + if (!ok) + goto out; + clasp = OBJ_GET_CLASS(cx, proto); + ok = JS_XDRRegisterClass(xdr, clasp, &classId); + if (!ok) + goto out; + } else { + clasp = JS_XDRFindClassById(xdr, classId); + if (!clasp) { + char numBuf[12]; + JS_snprintf(numBuf, sizeof numBuf, "%ld", (long)classId); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_CANT_FIND_CLASS, numBuf); + ok = JS_FALSE; + goto out; + } + } + } + + if (!clasp->xdrObject) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_CANT_XDR_CLASS, clasp->name); + ok = JS_FALSE; + } else { + ok = clasp->xdrObject(xdr, objp); + } +out: + if (xdr->mode != JSXDR_ENCODE && className) + JS_free(cx, (void *)className); + return ok; +} + +#endif /* JS_HAS_XDR */ + +#ifdef DEBUG_brendan + +#include +#include + +uint32 js_entry_count_max; +uint32 js_entry_count_sum; +double js_entry_count_sqsum; +uint32 js_entry_count_hist[11]; + +static void +MeterEntryCount(uintN count) +{ + if (count) { + js_entry_count_sum += count; + js_entry_count_sqsum += (double)count * count; + if (count > js_entry_count_max) + js_entry_count_max = count; + } + js_entry_count_hist[JS_MIN(count, 10)]++; +} + +void +js_DumpScopeMeters(JSRuntime *rt) +{ + static FILE *logfp; + if (!logfp) + logfp = fopen("/tmp/scope.stats", "a"); + + { + double mean = 0., var = 0., sigma = 0.; + double nscopes = rt->liveScopes; + double nentrys = js_entry_count_sum; + if (nscopes > 0 && nentrys >= 0) { + mean = nentrys / nscopes; + var = nscopes * js_entry_count_sqsum - nentrys * nentrys; + if (var < 0.0 || nscopes <= 1) + var = 0.0; + else + var /= nscopes * (nscopes - 1); + + /* Windows says sqrt(0.0) is "-1.#J" (?!) so we must test. */ + sigma = (var != 0.) ? sqrt(var) : 0.; + } + + fprintf(logfp, + "scopes %g entries %g mean %g sigma %g max %u", + nscopes, nentrys, mean, sigma, js_entry_count_max); + } + + fprintf(logfp, " histogram %u %u %u %u %u %u %u %u %u %u %u\n", + js_entry_count_hist[0], js_entry_count_hist[1], + js_entry_count_hist[2], js_entry_count_hist[3], + js_entry_count_hist[4], js_entry_count_hist[5], + js_entry_count_hist[6], js_entry_count_hist[7], + js_entry_count_hist[8], js_entry_count_hist[9], + js_entry_count_hist[10]); + js_entry_count_sum = js_entry_count_max = 0; + js_entry_count_sqsum = 0; + memset(js_entry_count_hist, 0, sizeof js_entry_count_hist); + fflush(logfp); +} + +#endif /* DEBUG_brendan */ + +uint32 +js_Mark(JSContext *cx, JSObject *obj, void *arg) +{ + JSScope *scope; + JSScopeProperty *sprop; + JSClass *clasp; + + JS_ASSERT(OBJ_IS_NATIVE(obj)); + scope = OBJ_SCOPE(obj); +#ifdef DEBUG_brendan + if (scope->object == obj) + MeterEntryCount(scope->entryCount); +#endif + + JS_ASSERT(!SCOPE_LAST_PROP(scope) || + SCOPE_HAS_PROPERTY(scope, SCOPE_LAST_PROP(scope))); + + for (sprop = SCOPE_LAST_PROP(scope); sprop; sprop = sprop->parent) { + if (SCOPE_HAD_MIDDLE_DELETE(scope) && !SCOPE_HAS_PROPERTY(scope, sprop)) + continue; + MARK_SCOPE_PROPERTY(sprop); + if (!JSVAL_IS_INT(sprop->id)) + GC_MARK_ATOM(cx, (JSAtom *)sprop->id, arg); + +#if JS_HAS_GETTER_SETTER + if (sprop->attrs & (JSPROP_GETTER | JSPROP_SETTER)) { +#ifdef GC_MARK_DEBUG + char buf[64]; + JSAtom *atom = (JSAtom *)sprop->id; + const char *id = (atom && ATOM_IS_STRING(atom)) + ? JS_GetStringBytes(ATOM_TO_STRING(atom)) + : "unknown"; +#endif + + if (sprop->attrs & JSPROP_GETTER) { +#ifdef GC_MARK_DEBUG + JS_snprintf(buf, sizeof buf, "%s %s", + id, js_getter_str); +#endif + GC_MARK(cx, + JSVAL_TO_GCTHING((jsval) sprop->getter), + buf, + arg); + } + if (sprop->attrs & JSPROP_SETTER) { +#ifdef GC_MARK_DEBUG + JS_snprintf(buf, sizeof buf, "%s %s", + id, js_setter_str); +#endif + GC_MARK(cx, + JSVAL_TO_GCTHING((jsval) sprop->setter), + buf, + arg); + } + } +#endif /* JS_HAS_GETTER_SETTER */ + } + + /* No one runs while the GC is running, so we can use LOCKED_... here. */ + clasp = LOCKED_OBJ_GET_CLASS(obj); + if (clasp->mark) + (void) clasp->mark(cx, obj, arg); + + if (scope->object != obj) { + /* + * An unmutated object that shares a prototype's scope. We can't tell + * how many slots are allocated and in use at obj->slots by looking at + * scope, so we get obj->slots' length from its -1'st element. + */ + return (uint32) obj->slots[-1]; + } + return JS_MIN(obj->map->freeslot, obj->map->nslots); +} + +void +js_Clear(JSContext *cx, JSObject *obj) +{ + JSScope *scope; + JSRuntime *rt; + JSScopeProperty *sprop; + uint32 i, n; + + /* + * Clear our scope and the property cache of all obj's properties only if + * obj owns the scope (i.e., not if obj is unmutated and therefore sharing + * its prototype's scope). NB: we do not clear any reserved slots lying + * below JSSLOT_FREE(clasp). + */ + JS_LOCK_OBJ(cx, obj); + scope = OBJ_SCOPE(obj); + if (scope->object == obj) { + /* Clear the property cache before we clear the scope. */ + rt = cx->runtime; + for (sprop = SCOPE_LAST_PROP(scope); sprop; sprop = sprop->parent) { + if (!SCOPE_HAD_MIDDLE_DELETE(scope) || + SCOPE_HAS_PROPERTY(scope, sprop)) { + PROPERTY_CACHE_FILL(&rt->propertyCache, obj, sprop->id, NULL); + } + } + + /* Now that we're done using scope->lastProp/table, clear scope. */ + js_ClearScope(cx, scope); + + /* Clear slot values and reset freeslot so we're consistent. */ + i = scope->map.nslots; + n = JSSLOT_FREE(LOCKED_OBJ_GET_CLASS(obj)); + while (--i >= n) + obj->slots[i] = JSVAL_VOID; + scope->map.freeslot = n; + } + JS_UNLOCK_OBJ(cx, obj); +} + +jsval +js_GetRequiredSlot(JSContext *cx, JSObject *obj, uint32 slot) +{ + jsval v; + + JS_LOCK_OBJ(cx, obj); + v = (slot < (uint32) obj->slots[-1]) ? obj->slots[slot] : JSVAL_VOID; + JS_UNLOCK_OBJ(cx, obj); + return v; +} + +void +js_SetRequiredSlot(JSContext *cx, JSObject *obj, uint32 slot, jsval v) +{ + uint32 nslots, rlimit, i; + JSClass *clasp; + jsval *newslots; + + JS_LOCK_OBJ(cx, obj); + nslots = (uint32) obj->slots[-1]; + if (slot >= nslots) { + clasp = LOCKED_OBJ_GET_CLASS(obj); + rlimit = JSSLOT_START(clasp) + JSCLASS_RESERVED_SLOTS(clasp); + JS_ASSERT(slot < rlimit); + if (rlimit > nslots) + nslots = rlimit; + + newslots = (jsval *) + JS_realloc(cx, obj->slots - 1, (nslots + 1) * sizeof(jsval)); + if (!newslots) { + JS_UNLOCK_OBJ(cx, obj); + return; + } + for (i = 1 + newslots[0]; i <= rlimit; i++) + newslots[i] = JSVAL_VOID; + newslots[0] = nslots; + if (OBJ_SCOPE(obj)->object == obj) + obj->map->nslots = nslots; + obj->slots = newslots + 1; + } + + obj->slots[slot] = v; + JS_UNLOCK_OBJ(cx, obj); +} + +#ifdef DEBUG + +/* Routines to print out values during debugging. */ + +void printChar(jschar *cp) { + fprintf(stderr, "jschar* (0x%p) \"", (void *)cp); + while (*cp) + fputc(*cp++, stderr); + fputc('"', stderr); + fputc('\n', stderr); +} + +void printString(JSString *str) { + size_t i, n; + jschar *s; + fprintf(stderr, "string (0x%p) \"", (void *)str); + s = JSSTRING_CHARS(str); + for (i=0, n=JSSTRING_LENGTH(str); i < n; i++) + fputc(s[i], stderr); + fputc('"', stderr); + fputc('\n', stderr); +} + +void printVal(JSContext *cx, jsval val); + +void printObj(JSContext *cx, JSObject *jsobj) { + jsuint i; + jsval val; + JSClass *clasp; + + fprintf(stderr, "object 0x%p\n", (void *)jsobj); + clasp = OBJ_GET_CLASS(cx, jsobj); + fprintf(stderr, "class 0x%p %s\n", (void *)clasp, clasp->name); + for (i=0; i < jsobj->map->nslots; i++) { + fprintf(stderr, "slot %3d ", i); + val = jsobj->slots[i]; + if (JSVAL_IS_OBJECT(val)) + fprintf(stderr, "object 0x%p\n", (void *)JSVAL_TO_OBJECT(val)); + else + printVal(cx, val); + } +} + +void printVal(JSContext *cx, jsval val) { + fprintf(stderr, "val %d (0x%p) = ", (int)val, (void *)val); + if (JSVAL_IS_NULL(val)) { + fprintf(stderr, "null\n"); + } else if (JSVAL_IS_VOID(val)) { + fprintf(stderr, "undefined\n"); + } else if (JSVAL_IS_OBJECT(val)) { + printObj(cx, JSVAL_TO_OBJECT(val)); + } else if (JSVAL_IS_INT(val)) { + fprintf(stderr, "(int) %d\n", JSVAL_TO_INT(val)); + } else if (JSVAL_IS_STRING(val)) { + printString(JSVAL_TO_STRING(val)); + } else if (JSVAL_IS_DOUBLE(val)) { + fprintf(stderr, "(double) %g\n", *JSVAL_TO_DOUBLE(val)); + } else { + JS_ASSERT(JSVAL_IS_BOOLEAN(val)); + fprintf(stderr, "(boolean) %s\n", + JSVAL_TO_BOOLEAN(val) ? "true" : "false"); + } + fflush(stderr); +} + +void printId(JSContext *cx, jsid id) { + fprintf(stderr, "id %d (0x%p) is ", (int)id, (void *)id); + printVal(cx, ID_TO_VALUE(id)); +} + +void printAtom(JSAtom *atom) { + printString(ATOM_TO_STRING(atom)); +} + +#endif diff --git a/src/dom/js/jsobj.h b/src/dom/js/jsobj.h new file mode 100644 index 000000000..a523194db --- /dev/null +++ b/src/dom/js/jsobj.h @@ -0,0 +1,464 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsobj_h___ +#define jsobj_h___ +/* + * JS object definitions. + * + * A JS object consists of a possibly-shared object descriptor containing + * ordered property names, called the map; and a dense vector of property + * values, called slots. The map/slot pointer pair is GC'ed, while the map + * is reference counted and the slot vector is malloc'ed. + */ +#include "jshash.h" /* Added by JSIFY */ +#include "jsprvtd.h" +#include "jspubtd.h" + +JS_BEGIN_EXTERN_C + +struct JSObjectMap { + jsrefcount nrefs; /* count of all referencing objects */ + JSObjectOps *ops; /* high level object operation vtable */ + uint32 nslots; /* length of obj->slots vector */ + uint32 freeslot; /* index of next free obj->slots element */ +}; + +/* Shorthand macros for frequently-made calls. */ +#if defined JS_THREADSAFE && defined DEBUG +#define OBJ_LOOKUP_PROPERTY(cx,obj,id,objp,propp) \ + (obj)->map->ops->lookupProperty(cx,obj,id,objp,propp,__FILE__,__LINE__) +#else +#define OBJ_LOOKUP_PROPERTY(cx,obj,id,objp,propp) \ + (obj)->map->ops->lookupProperty(cx,obj,id,objp,propp) +#endif +#define OBJ_DEFINE_PROPERTY(cx,obj,id,value,getter,setter,attrs,propp) \ + (obj)->map->ops->defineProperty(cx,obj,id,value,getter,setter,attrs,propp) +#define OBJ_GET_PROPERTY(cx,obj,id,vp) \ + (obj)->map->ops->getProperty(cx,obj,id,vp) +#define OBJ_SET_PROPERTY(cx,obj,id,vp) \ + (obj)->map->ops->setProperty(cx,obj,id,vp) +#define OBJ_GET_ATTRIBUTES(cx,obj,id,prop,attrsp) \ + (obj)->map->ops->getAttributes(cx,obj,id,prop,attrsp) +#define OBJ_SET_ATTRIBUTES(cx,obj,id,prop,attrsp) \ + (obj)->map->ops->setAttributes(cx,obj,id,prop,attrsp) +#define OBJ_DELETE_PROPERTY(cx,obj,id,rval) \ + (obj)->map->ops->deleteProperty(cx,obj,id,rval) +#define OBJ_DEFAULT_VALUE(cx,obj,hint,vp) \ + (obj)->map->ops->defaultValue(cx,obj,hint,vp) +#define OBJ_ENUMERATE(cx,obj,enum_op,statep,idp) \ + (obj)->map->ops->enumerate(cx,obj,enum_op,statep,idp) +#define OBJ_CHECK_ACCESS(cx,obj,id,mode,vp,attrsp) \ + (obj)->map->ops->checkAccess(cx,obj,id,mode,vp,attrsp) + +/* These four are time-optimized to avoid stub calls. */ +#define OBJ_THIS_OBJECT(cx,obj) \ + ((obj)->map->ops->thisObject \ + ? (obj)->map->ops->thisObject(cx,obj) \ + : (obj)) +#define OBJ_DROP_PROPERTY(cx,obj,prop) \ + ((obj)->map->ops->dropProperty \ + ? (obj)->map->ops->dropProperty(cx,obj,prop) \ + : (void)0) +#define OBJ_GET_REQUIRED_SLOT(cx,obj,slot) \ + ((obj)->map->ops->getRequiredSlot \ + ? (obj)->map->ops->getRequiredSlot(cx, obj, slot) \ + : JSVAL_VOID) +#define OBJ_SET_REQUIRED_SLOT(cx,obj,slot,v) \ + ((obj)->map->ops->setRequiredSlot \ + ? (obj)->map->ops->setRequiredSlot(cx, obj, slot, v) \ + : (void)0) + +/* + * In the original JS engine design, obj->slots pointed to a vector of length + * JS_INITIAL_NSLOTS words if obj->map was shared with a prototype object, + * else of length obj->map->nslots. With the advent of JS_GetReservedSlot, + * JS_SetReservedSlot, and JSCLASS_HAS_RESERVED_SLOTS (see jsapi.h), the size + * of the minimum length slots vector in the case where map is shared cannot + * be constant. This length starts at JS_INITIAL_NSLOTS, but may advance to + * include all the reserved slots. + * + * Therefore slots must be self-describing. Rather than tag its low order bit + * (a bit is all we need) to distinguish initial length from reserved length, + * we do "the BSTR thing": over-allocate slots by one jsval, and store the + * *net* length (counting usable slots, which have non-negative obj->slots[] + * indices) in obj->slots[-1]. All code that sets obj->slots must be aware of + * this hack -- you have been warned, and jsobj.c has been updated! + */ +struct JSObject { + JSObjectMap *map; + jsval *slots; +}; + +#define JSSLOT_PROTO 0 +#define JSSLOT_PARENT 1 +#define JSSLOT_CLASS 2 +#define JSSLOT_PRIVATE 3 +#define JSSLOT_START(clasp) (((clasp)->flags & JSCLASS_HAS_PRIVATE) \ + ? JSSLOT_PRIVATE + 1 \ + : JSSLOT_CLASS + 1) + +#define JSSLOT_FREE(clasp) (JSSLOT_START(clasp) \ + + JSCLASS_RESERVED_SLOTS(clasp)) + +#define JS_INITIAL_NSLOTS 5 + +#ifdef DEBUG +#define MAP_CHECK_SLOT(map,slot) \ + JS_ASSERT((uint32)slot < JS_MIN((map)->freeslot, (map)->nslots)) +#define OBJ_CHECK_SLOT(obj,slot) \ + MAP_CHECK_SLOT((obj)->map, slot) +#else +#define OBJ_CHECK_SLOT(obj,slot) ((void)0) +#endif + +/* Fast macros for accessing obj->slots while obj is locked (if thread-safe). */ +#define LOCKED_OBJ_GET_SLOT(obj,slot) \ + (OBJ_CHECK_SLOT(obj, slot), (obj)->slots[slot]) +#define LOCKED_OBJ_SET_SLOT(obj,slot,value) \ + (OBJ_CHECK_SLOT(obj, slot), (obj)->slots[slot] = (value)) +#define LOCKED_OBJ_GET_PROTO(obj) \ + JSVAL_TO_OBJECT(LOCKED_OBJ_GET_SLOT(obj, JSSLOT_PROTO)) +#define LOCKED_OBJ_GET_CLASS(obj) \ + ((JSClass *)JSVAL_TO_PRIVATE(LOCKED_OBJ_GET_SLOT(obj, JSSLOT_CLASS))) + +#ifdef JS_THREADSAFE + +/* Thread-safe functions and wrapper macros for accessing obj->slots. */ +#define OBJ_GET_SLOT(cx,obj,slot) \ + (OBJ_CHECK_SLOT(obj, slot), \ + (OBJ_IS_NATIVE(obj) && OBJ_SCOPE(obj)->ownercx == cx) \ + ? LOCKED_OBJ_GET_SLOT(obj, slot) \ + : js_GetSlotThreadSafe(cx, obj, slot)) + +#define OBJ_SET_SLOT(cx,obj,slot,value) \ + (OBJ_CHECK_SLOT(obj, slot), \ + (OBJ_IS_NATIVE(obj) && OBJ_SCOPE(obj)->ownercx == cx) \ + ? (void) LOCKED_OBJ_SET_SLOT(obj, slot, value) \ + : js_SetSlotThreadSafe(cx, obj, slot, value)) + +/* + * If thread-safe, define an OBJ_GET_SLOT wrapper that bypasses, for a native + * object, the lock-free "fast path" test of (OBJ_SCOPE(obj)->ownercx == cx), + * to avoid needlessly switching from lock-free to lock-full scope when doing + * GC on a different context from the last one to own the scope. The caller + * in this case is probably a JSClass.mark function, e.g., fun_mark, or maybe + * a finalizer. + * + * The GC runs only when all threads except the one on which the GC is active + * are suspended at GC-safe points, so there is no hazard in directly accessing + * obj->slots[slot] from the GC's thread, once rt->gcRunning has been set. See + * jsgc.c for details. + */ +#define THREAD_IS_RUNNING_GC(rt, thread) \ + ((rt)->gcRunning && (rt)->gcThread == (thread)) + +#define CX_THREAD_IS_RUNNING_GC(cx) \ + THREAD_IS_RUNNING_GC((cx)->runtime, (cx)->thread) + +#define GC_AWARE_GET_SLOT(cx, obj, slot) \ + ((OBJ_IS_NATIVE(obj) && CX_THREAD_IS_RUNNING_GC(cx)) \ + ? (obj)->slots[slot] \ + : OBJ_GET_SLOT(cx, obj, slot)) + +#else /* !JS_THREADSAFE */ + +#define OBJ_GET_SLOT(cx,obj,slot) LOCKED_OBJ_GET_SLOT(obj,slot) +#define OBJ_SET_SLOT(cx,obj,slot,value) LOCKED_OBJ_SET_SLOT(obj,slot,value) +#define GC_AWARE_GET_SLOT(cx,obj,slot) LOCKED_OBJ_GET_SLOT(obj,slot) + +#endif /* !JS_THREADSAFE */ + +/* Thread-safe proto, parent, and class access macros. */ +#define OBJ_GET_PROTO(cx,obj) \ + JSVAL_TO_OBJECT(OBJ_GET_SLOT(cx, obj, JSSLOT_PROTO)) +#define OBJ_SET_PROTO(cx,obj,proto) \ + OBJ_SET_SLOT(cx, obj, JSSLOT_PROTO, OBJECT_TO_JSVAL(proto)) + +#define OBJ_GET_PARENT(cx,obj) \ + JSVAL_TO_OBJECT(OBJ_GET_SLOT(cx, obj, JSSLOT_PARENT)) +#define OBJ_SET_PARENT(cx,obj,parent) \ + OBJ_SET_SLOT(cx, obj, JSSLOT_PARENT, OBJECT_TO_JSVAL(parent)) + +#define OBJ_GET_CLASS(cx,obj) \ + ((JSClass *)JSVAL_TO_PRIVATE(OBJ_GET_SLOT(cx, obj, JSSLOT_CLASS))) + +/* Test whether a map or object is native. */ +#define MAP_IS_NATIVE(map) \ + ((map)->ops == &js_ObjectOps || \ + ((map)->ops && (map)->ops->newObjectMap == js_ObjectOps.newObjectMap)) + +#define OBJ_IS_NATIVE(obj) MAP_IS_NATIVE((obj)->map) + +extern JS_FRIEND_DATA(JSObjectOps) js_ObjectOps; +extern JS_FRIEND_DATA(JSObjectOps) js_WithObjectOps; +extern JSClass js_ObjectClass; +extern JSClass js_WithClass; + +struct JSSharpObjectMap { + jsrefcount depth; + jsatomid sharpgen; + JSHashTable *table; +}; + +#define SHARP_BIT ((jsatomid) 1) +#define BUSY_BIT ((jsatomid) 2) +#define SHARP_ID_SHIFT 2 +#define IS_SHARP(he) ((jsatomid)(he)->value & SHARP_BIT) +#define MAKE_SHARP(he) ((he)->value = (void*)((jsatomid)(he)->value|SHARP_BIT)) +#define IS_BUSY(he) ((jsatomid)(he)->value & BUSY_BIT) +#define MAKE_BUSY(he) ((he)->value = (void*)((jsatomid)(he)->value|BUSY_BIT)) +#define CLEAR_BUSY(he) ((he)->value = (void*)((jsatomid)(he)->value&~BUSY_BIT)) + +extern JSHashEntry * +js_EnterSharpObject(JSContext *cx, JSObject *obj, JSIdArray **idap, + jschar **sp); + +extern void +js_LeaveSharpObject(JSContext *cx, JSIdArray **idap); + +extern JSBool +js_obj_toSource(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval); + +extern JSBool +js_obj_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval); + +extern JSObject * +js_InitObjectClass(JSContext *cx, JSObject *obj); + +/* Select Object.prototype method names shared between jsapi.c and jsobj.c. */ +extern const char js_watch_str[]; +extern const char js_unwatch_str[]; +extern const char js_hasOwnProperty_str[]; +extern const char js_isPrototypeOf_str[]; +extern const char js_propertyIsEnumerable_str[]; +extern const char js_defineGetter_str[]; +extern const char js_defineSetter_str[]; +extern const char js_lookupGetter_str[]; +extern const char js_lookupSetter_str[]; + +extern void +js_InitObjectMap(JSObjectMap *map, jsrefcount nrefs, JSObjectOps *ops, + JSClass *clasp); + +extern JSObjectMap * +js_NewObjectMap(JSContext *cx, jsrefcount nrefs, JSObjectOps *ops, + JSClass *clasp, JSObject *obj); + +extern void +js_DestroyObjectMap(JSContext *cx, JSObjectMap *map); + +extern JSObjectMap * +js_HoldObjectMap(JSContext *cx, JSObjectMap *map); + +extern JSObjectMap * +js_DropObjectMap(JSContext *cx, JSObjectMap *map, JSObject *obj); + +extern JSObject * +js_NewObject(JSContext *cx, JSClass *clasp, JSObject *proto, JSObject *parent); + +extern JSObject * +js_ConstructObject(JSContext *cx, JSClass *clasp, JSObject *proto, + JSObject *parent, uintN argc, jsval *argv); + +extern void +js_FinalizeObject(JSContext *cx, JSObject *obj); + +extern JSBool +js_AllocSlot(JSContext *cx, JSObject *obj, uint32 *slotp); + +extern void +js_FreeSlot(JSContext *cx, JSObject *obj, uint32 slot); + +/* + * Find or create a property named by id in obj's scope, with the given getter + * and setter, slot, attributes, and other members. + */ +extern JSScopeProperty * +js_AddNativeProperty(JSContext *cx, JSObject *obj, jsid id, + JSPropertyOp getter, JSPropertyOp setter, uint32 slot, + uintN attrs, uintN flags, intN shortid); + +/* + * Change sprop to have the given attrs, getter, and setter in scope, morphing + * it into a potentially new JSScopeProperty. Return a pointer to the changed + * or identical property. + */ +extern JSScopeProperty * +js_ChangeNativePropertyAttrs(JSContext *cx, JSObject *obj, + JSScopeProperty *sprop, uintN attrs, uintN mask, + JSPropertyOp getter, JSPropertyOp setter); + +/* + * On error, return false. On success, if propp is non-null, return true with + * obj locked and with a held property in *propp; if propp is null, return true + * but release obj's lock first. Therefore all callers who pass non-null propp + * result parameters must later call OBJ_DROP_PROPERTY(cx, obj, *propp) both to + * drop the held property, and to release the lock on obj. + */ +extern JSBool +js_DefineProperty(JSContext *cx, JSObject *obj, jsid id, jsval value, + JSPropertyOp getter, JSPropertyOp setter, uintN attrs, + JSProperty **propp); + +extern JSBool +js_DefineNativeProperty(JSContext *cx, JSObject *obj, jsid id, jsval value, + JSPropertyOp getter, JSPropertyOp setter, uintN attrs, + uintN flags, intN shortid, JSProperty **propp); + +/* + * Unlike js_DefineProperty, propp must be non-null. On success, and if id was + * found, return true with *objp non-null and locked, and with a held property + * stored in *propp. If successful but id was not found, return true with both + * *objp and *propp null. Therefore all callers who receive a non-null *propp + * must later call OBJ_DROP_PROPERTY(cx, *objp, *propp). + */ +#if defined JS_THREADSAFE && defined DEBUG +extern JS_FRIEND_API(JSBool) +_js_LookupProperty(JSContext *cx, JSObject *obj, jsid id, JSObject **objp, + JSProperty **propp, const char *file, uintN line); + +#define js_LookupProperty(cx,obj,id,objp,propp) \ + _js_LookupProperty(cx,obj,id,objp,propp,__FILE__,__LINE__) +#else +extern JS_FRIEND_API(JSBool) +js_LookupProperty(JSContext *cx, JSObject *obj, jsid id, JSObject **objp, + JSProperty **propp); +#endif + +extern JS_FRIEND_API(JSBool) +js_FindProperty(JSContext *cx, jsid id, JSObject **objp, JSObject **pobjp, + JSProperty **propp); + +extern JSObject * +js_FindIdentifierBase(JSContext *cx, jsid id); + +extern JSObject * +js_FindVariableScope(JSContext *cx, JSFunction **funp); + +extern JSBool +js_GetProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp); + +extern JSBool +js_SetProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp); + +extern JSBool +js_GetAttributes(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop, + uintN *attrsp); + +extern JSBool +js_SetAttributes(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop, + uintN *attrsp); + +extern JSBool +js_DeleteProperty(JSContext *cx, JSObject *obj, jsid id, jsval *rval); + +extern JSBool +js_DefaultValue(JSContext *cx, JSObject *obj, JSType hint, jsval *vp); + +extern JSIdArray * +js_NewIdArray(JSContext *cx, jsint length); + +extern JSIdArray * +js_GrowIdArray(JSContext *cx, JSIdArray *ida, jsint length); + +extern JSBool +js_Enumerate(JSContext *cx, JSObject *obj, JSIterateOp enum_op, + jsval *statep, jsid *idp); + +extern JSBool +js_CheckAccess(JSContext *cx, JSObject *obj, jsid id, JSAccessMode mode, + jsval *vp, uintN *attrsp); + +extern JSBool +js_Call(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval); + +extern JSBool +js_Construct(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval); + +extern JSBool +js_HasInstance(JSContext *cx, JSObject *obj, jsval v, JSBool *bp); + +extern JSBool +js_SetProtoOrParent(JSContext *cx, JSObject *obj, uint32 slot, JSObject *pobj); + +extern JSBool +js_IsDelegate(JSContext *cx, JSObject *obj, jsval v, JSBool *bp); + +extern JSBool +js_GetClassPrototype(JSContext *cx, const char *name, JSObject **protop); + +extern JSBool +js_SetClassPrototype(JSContext *cx, JSObject *ctor, JSObject *proto, + uintN attrs); + +extern JSBool +js_ValueToObject(JSContext *cx, jsval v, JSObject **objp); + +extern JSObject * +js_ValueToNonNullObject(JSContext *cx, jsval v); + +extern JSBool +js_TryValueOf(JSContext *cx, JSObject *obj, JSType type, jsval *rval); + +extern JSBool +js_TryMethod(JSContext *cx, JSObject *obj, JSAtom *atom, + uintN argc, jsval *argv, jsval *rval); + +extern JSBool +js_XDRObject(JSXDRState *xdr, JSObject **objp); + +extern uint32 +js_Mark(JSContext *cx, JSObject *obj, void *arg); + +extern void +js_Clear(JSContext *cx, JSObject *obj); + +extern jsval +js_GetRequiredSlot(JSContext *cx, JSObject *obj, uint32 slot); + +extern void +js_SetRequiredSlot(JSContext *cx, JSObject *obj, uint32 slot, jsval v); + +JS_END_EXTERN_C + +#endif /* jsobj_h___ */ diff --git a/src/dom/js/jsopcode.c b/src/dom/js/jsopcode.c new file mode 100644 index 000000000..166f602d5 --- /dev/null +++ b/src/dom/js/jsopcode.c @@ -0,0 +1,2660 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS bytecode descriptors, disassemblers, and decompilers. + */ +#include "jsstddef.h" +#ifdef HAVE_MEMORY_H +#include +#endif +#include +#include +#include +#include +#include "jstypes.h" +#include "jsarena.h" /* Added by JSIFY */ +#include "jsutil.h" /* Added by JSIFY */ +#include "jsdtoa.h" +#include "jsprf.h" +#include "jsapi.h" +#include "jsarray.h" +#include "jsatom.h" +#include "jscntxt.h" +#include "jsconfig.h" +#include "jsdbgapi.h" +#include "jsemit.h" +#include "jsfun.h" +#include "jslock.h" +#include "jsobj.h" +#include "jsopcode.h" +#include "jsscope.h" +#include "jsscript.h" +#include "jsstr.h" + +const char js_const_str[] = "const"; +const char js_var_str[] = "var"; +const char js_function_str[] = "function"; +const char js_in_str[] = "in"; +const char js_instanceof_str[] = "instanceof"; +const char js_new_str[] = "new"; +const char js_delete_str[] = "delete"; +const char js_typeof_str[] = "typeof"; +const char js_void_str[] = "void"; +const char js_null_str[] = "null"; +const char js_this_str[] = "this"; +const char js_false_str[] = "false"; +const char js_true_str[] = "true"; + +const char *js_incop_str[] = {"++", "--"}; + +/* Pollute the namespace locally for MSVC Win16, but not for WatCom. */ +#ifdef __WINDOWS_386__ + #ifdef FAR + #undef FAR + #endif +#else /* !__WINDOWS_386__ */ +#ifndef FAR +#define FAR +#endif +#endif /* !__WINDOWS_386__ */ + +const JSCodeSpec FAR js_CodeSpec[] = { +#define OPDEF(op,val,name,token,length,nuses,ndefs,prec,format) \ + {name,token,length,nuses,ndefs,prec,format}, +#include "jsopcode.tbl" +#undef OPDEF +}; + +uintN js_NumCodeSpecs = sizeof (js_CodeSpec) / sizeof js_CodeSpec[0]; + +/************************************************************************/ + +static ptrdiff_t +GetJumpOffset(jsbytecode *pc, jsbytecode *pc2) +{ + uint32 type; + + type = (js_CodeSpec[*pc].format & JOF_TYPEMASK); + if (JOF_TYPE_IS_EXTENDED_JUMP(type)) + return GET_JUMPX_OFFSET(pc2); + return GET_JUMP_OFFSET(pc2); +} + +#ifdef DEBUG + +JS_FRIEND_API(void) +js_Disassemble(JSContext *cx, JSScript *script, JSBool lines, FILE *fp) +{ + jsbytecode *pc, *end; + uintN len; + + pc = script->code; + end = pc + script->length; + while (pc < end) { + if (pc == script->main) + fputs("main:\n", fp); + len = js_Disassemble1(cx, script, pc, + PTRDIFF(pc, script->code, jsbytecode), + lines, fp); + if (!len) + return; + pc += len; + } +} + +JS_FRIEND_API(uintN) +js_Disassemble1(JSContext *cx, JSScript *script, jsbytecode *pc, uintN loc, + JSBool lines, FILE *fp) +{ + JSOp op; + const JSCodeSpec *cs; + intN len, off; + JSAtom *atom; + JSString *str; + char *cstr; + + op = (JSOp)*pc; + if (op >= JSOP_LIMIT) { + char numBuf1[12], numBuf2[12]; + JS_snprintf(numBuf1, sizeof numBuf1, "%d", op); + JS_snprintf(numBuf2, sizeof numBuf2, "%d", JSOP_LIMIT); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BYTECODE_TOO_BIG, numBuf1, numBuf2); + return 0; + } + cs = &js_CodeSpec[op]; + len = (intN)cs->length; + fprintf(fp, "%05u:", loc); + if (lines) + fprintf(fp, "%4u", JS_PCToLineNumber(cx, script, pc)); + fprintf(fp, " %s", cs->name); + switch (cs->format & JOF_TYPEMASK) { + case JOF_BYTE: + if (op == JSOP_TRAP) { + op = JS_GetTrapOpcode(cx, script, pc); + if (op == JSOP_LIMIT) + return 0; + len = (intN)js_CodeSpec[op].length; + } + break; + + case JOF_JUMP: + case JOF_JUMPX: + off = GetJumpOffset(pc, pc); + fprintf(fp, " %u (%d)", loc + off, off); + break; + + case JOF_CONST: + atom = GET_ATOM(cx, script, pc); + str = js_ValueToSource(cx, ATOM_KEY(atom)); + if (!str) + return 0; + cstr = js_DeflateString(cx, JSSTRING_CHARS(str), JSSTRING_LENGTH(str)); + if (!cstr) + return 0; + fprintf(fp, " %s", cstr); + JS_free(cx, cstr); + break; + + case JOF_UINT16: + fprintf(fp, " %u", GET_ARGC(pc)); + break; + +#if JS_HAS_SWITCH_STATEMENT + case JOF_TABLESWITCH: + { + jsbytecode *pc2; + jsint i, low, high; + + pc2 = pc; + off = GetJumpOffset(pc, pc2); + pc2 += JUMP_OFFSET_LEN; + low = GetJumpOffset(pc, pc2); + pc2 += JUMP_OFFSET_LEN; + high = GetJumpOffset(pc, pc2); + pc2 += JUMP_OFFSET_LEN; + fprintf(fp, " defaultOffset %d low %d high %d", off, low, high); + for (i = low; i <= high; i++) { + off = GetJumpOffset(pc, pc2); + fprintf(fp, "\n\t%d: %d", i, off); + pc2 += JUMP_OFFSET_LEN; + } + len = 1 + pc2 - pc; + break; + } + + case JOF_LOOKUPSWITCH: + { + jsbytecode *pc2; + jsint npairs; + + pc2 = pc; + off = GetJumpOffset(pc, pc2); + pc2 += JUMP_OFFSET_LEN; + npairs = (jsint) GET_ATOM_INDEX(pc2); + pc2 += ATOM_INDEX_LEN; + fprintf(fp, " offset %d npairs %u", off, (uintN) npairs); + while (npairs) { + atom = GET_ATOM(cx, script, pc2); + pc2 += ATOM_INDEX_LEN; + off = GetJumpOffset(pc, pc2); + pc2 += JUMP_OFFSET_LEN; + + str = js_ValueToSource(cx, ATOM_KEY(atom)); + if (!str) + return 0; + cstr = js_DeflateString(cx, JSSTRING_CHARS(str), + JSSTRING_LENGTH(str)); + if (!cstr) + return 0; + fprintf(fp, "\n\t%s: %d", cstr, off); + JS_free(cx, cstr); + npairs--; + } + len = 1 + pc2 - pc; + break; + } +#endif /* JS_HAS_SWITCH_STATEMENT */ + + case JOF_QARG: + fprintf(fp, " %u", GET_ARGNO(pc)); + break; + + case JOF_QVAR: + fprintf(fp, " %u", GET_VARNO(pc)); + break; + +#if JS_HAS_LEXICAL_CLOSURE + case JOF_DEFLOCALVAR: + fprintf(fp, " %u", GET_VARNO(pc)); + pc += VARNO_LEN; + atom = GET_ATOM(cx, script, pc); + str = js_ValueToSource(cx, ATOM_KEY(atom)); + if (!str) + return 0; + cstr = js_DeflateString(cx, JSSTRING_CHARS(str), JSSTRING_LENGTH(str)); + if (!cstr) + return 0; + fprintf(fp, " %s", cstr); + JS_free(cx, cstr); + break; +#endif + + default: { + char numBuf[12]; + JS_snprintf(numBuf, sizeof numBuf, "%lx", (unsigned long) cs->format); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_UNKNOWN_FORMAT, numBuf); + return 0; + } + } + fputs("\n", fp); + return len; +} + +#endif /* DEBUG */ + +/************************************************************************/ + +/* + * Sprintf, but with unlimited and automatically allocated buffering. + */ +typedef struct Sprinter { + JSContext *context; /* context executing the decompiler */ + JSArenaPool *pool; /* string allocation pool */ + char *base; /* base address of buffer in pool */ + size_t size; /* size of buffer allocated at base */ + ptrdiff_t offset; /* offset of next free char in buffer */ +} Sprinter; + +#define INIT_SPRINTER(cx, sp, ap, off) \ + ((sp)->context = cx, (sp)->pool = ap, (sp)->base = NULL, (sp)->size = 0, \ + (sp)->offset = off) + +#define OFF2STR(sp,off) ((sp)->base + (off)) +#define STR2OFF(sp,str) ((str) - (sp)->base) +#define RETRACT(sp,str) ((sp)->offset = STR2OFF(sp, str)) + +static JSBool +SprintAlloc(Sprinter *sp, size_t nb) +{ + if (!sp->base) { + JS_ARENA_ALLOCATE_CAST(sp->base, char *, sp->pool, nb); + } else { + JS_ARENA_GROW_CAST(sp->base, char *, sp->pool, sp->size, nb); + } + if (!sp->base) { + JS_ReportOutOfMemory(sp->context); + return JS_FALSE; + } + sp->size += nb; + return JS_TRUE; +} + +static ptrdiff_t +SprintPut(Sprinter *sp, const char *s, size_t len) +{ + ptrdiff_t nb, offset; + char *bp; + + /* Allocate space for s, including the '\0' at the end. */ + nb = (sp->offset + len + 1) - sp->size; + if (nb > 0 && !SprintAlloc(sp, nb)) + return -1; + + /* Advance offset and copy s into sp's buffer. */ + offset = sp->offset; + sp->offset += len; + bp = sp->base + offset; + memmove(bp, s, len); + bp[len] = 0; + return offset; +} + +static ptrdiff_t +Sprint(Sprinter *sp, const char *format, ...) +{ + va_list ap; + char *bp; + ptrdiff_t offset; + + va_start(ap, format); + bp = JS_vsmprintf(format, ap); /* XXX vsaprintf */ + va_end(ap); + if (!bp) { + JS_ReportOutOfMemory(sp->context); + return -1; + } + offset = SprintPut(sp, bp, strlen(bp)); + free(bp); + return offset; +} + +const jschar js_EscapeMap[] = { + '\b', 'b', + '\f', 'f', + '\n', 'n', + '\r', 'r', + '\t', 't', + '\v', 'v', + '"', '"', + '\'', '\'', + '\\', '\\', + 0 +}; + +static char * +QuoteString(Sprinter *sp, JSString *str, jschar quote) +{ + ptrdiff_t off, len, nb; + const jschar *s, *t, *u, *z; + char *bp; + jschar c; + JSBool ok; + + /* Sample off first for later return value pointer computation. */ + off = sp->offset; + if (quote && Sprint(sp, "%c", (char)quote) < 0) + return NULL; + + /* Loop control variables: z points at end of string sentinel. */ + s = JSSTRING_CHARS(str); + z = s + JSSTRING_LENGTH(str); + for (t = s; t < z; s = ++t) { + /* Move t forward from s past un-quote-worthy characters. */ + c = *t; + while (JS_ISPRINT(c) && c != quote && c != '\\' && !(c >> 8)) { + c = *++t; + if (t == z) + break; + } + len = PTRDIFF(t, s, jschar); + + /* Allocate space for s, including the '\0' at the end. */ + nb = (sp->offset + len + 1) - sp->size; + if (nb > 0 && !SprintAlloc(sp, nb)) + return NULL; + + /* Advance sp->offset and copy s into sp's buffer. */ + bp = sp->base + sp->offset; + sp->offset += len; + while (--len >= 0) + *bp++ = (char) *s++; + *bp = '\0'; + + if (t == z) + break; + + /* Use js_EscapeMap, \u, or \x only if necessary. */ + if ((u = js_strchr(js_EscapeMap, c)) != NULL) + ok = Sprint(sp, "\\%c", (char)u[1]) >= 0; + else + ok = Sprint(sp, (c >> 8) ? "\\u%04X" : "\\x%02X", c) >= 0; + if (!ok) + return NULL; + } + + /* Sprint the closing quote and return the quoted string. */ + if (quote && Sprint(sp, "%c", (char)quote) < 0) + return NULL; + return OFF2STR(sp, off); +} + +JSString * +js_QuoteString(JSContext *cx, JSString *str, jschar quote) +{ + void *mark; + Sprinter sprinter; + char *bytes; + JSString *escstr; + + mark = JS_ARENA_MARK(&cx->tempPool); + INIT_SPRINTER(cx, &sprinter, &cx->tempPool, 0); + bytes = QuoteString(&sprinter, str, quote); + escstr = bytes ? JS_NewStringCopyZ(cx, bytes) : NULL; + JS_ARENA_RELEASE(&cx->tempPool, mark); + return escstr; +} + +/************************************************************************/ + +struct JSPrinter { + Sprinter sprinter; /* base class state */ + JSArenaPool pool; /* string allocation pool */ + uintN indent; /* indentation in spaces */ + JSBool pretty; /* pretty-print: indent, use newlines */ + JSScript *script; /* script being printed */ + JSScope *scope; /* script function scope */ +}; + +JSPrinter * +js_NewPrinter(JSContext *cx, const char *name, uintN indent, JSBool pretty) +{ + JSPrinter *jp; + JSStackFrame *fp; + JSObjectMap *map; + + jp = (JSPrinter *) JS_malloc(cx, sizeof(JSPrinter)); + if (!jp) + return NULL; + INIT_SPRINTER(cx, &jp->sprinter, &jp->pool, 0); + JS_InitArenaPool(&jp->pool, name, 256, 1); + jp->indent = indent; + jp->pretty = pretty; + jp->script = NULL; + jp->scope = NULL; + fp = cx->fp; + if (fp && fp->fun && fp->fun->object) { + map = fp->fun->object->map; + if (MAP_IS_NATIVE(map)) + jp->scope = (JSScope *)map; + } + return jp; +} + +void +js_DestroyPrinter(JSPrinter *jp) +{ + JS_FinishArenaPool(&jp->pool); + JS_free(jp->sprinter.context, jp); +} + +JSString * +js_GetPrinterOutput(JSPrinter *jp) +{ + JSContext *cx; + JSString *str; + + cx = jp->sprinter.context; + if (!jp->sprinter.base) + return cx->runtime->emptyString; + str = JS_NewStringCopyZ(cx, jp->sprinter.base); + if (!str) + return NULL; + JS_FreeArenaPool(&jp->pool); + INIT_SPRINTER(cx, &jp->sprinter, &jp->pool, 0); + return str; +} + +int +js_printf(JSPrinter *jp, const char *format, ...) +{ + va_list ap; + char *bp, *fp; + int cc; + + if (*format == '\0') + return 0; + + va_start(ap, format); + + /* If pretty-printing, expand magic tab into a run of jp->indent spaces. */ + if (*format == '\t') { + if (jp->pretty && Sprint(&jp->sprinter, "%*s", jp->indent, "") < 0) + return -1; + format++; + } + + /* Suppress newlines (must be once per format, at the end) if not pretty. */ + fp = NULL; + if (!jp->pretty && format[cc = strlen(format)-1] == '\n') { + fp = JS_strdup(jp->sprinter.context, format); + if (!fp) + return -1; + fp[cc] = '\0'; + format = fp; + } + + /* Allocate temp space, convert format, and put. */ + bp = JS_vsmprintf(format, ap); /* XXX vsaprintf */ + if (fp) { + JS_free(jp->sprinter.context, fp); + format = NULL; + } + if (!bp) { + JS_ReportOutOfMemory(jp->sprinter.context); + return -1; + } + + cc = strlen(bp); + if (SprintPut(&jp->sprinter, bp, (size_t)cc) < 0) + cc = -1; + free(bp); + + va_end(ap); + return cc; +} + +JSBool +js_puts(JSPrinter *jp, const char *s) +{ + return SprintPut(&jp->sprinter, s, strlen(s)) >= 0; +} + +/************************************************************************/ + +typedef struct SprintStack { + Sprinter sprinter; /* sprinter for postfix to infix buffering */ + ptrdiff_t *offsets; /* stack of postfix string offsets */ + jsbytecode *opcodes; /* parallel stack of JS opcodes */ + uintN top; /* top of stack index */ + JSPrinter *printer; /* permanent output goes here */ +} SprintStack; + +/* Gap between stacked strings to allow for insertion of parens and commas. */ +#define PAREN_SLOP (2 + 1) + +/* + * These pseudo-ops help js_DecompileValueGenerator decompile JSOP_SETNAME, + * JSOP_SETPROP, and JSOP_SETELEM, respectively. See the first assertion in + * PushOff. + */ +#define JSOP_GETPROP2 254 +#define JSOP_GETELEM2 255 + +static JSBool +PushOff(SprintStack *ss, ptrdiff_t off, JSOp op) +{ + uintN top; + +#if JSOP_LIMIT > JSOP_GETPROP2 +#error JSOP_LIMIT must be <= JSOP_GETPROP2 +#endif + if (!SprintAlloc(&ss->sprinter, PAREN_SLOP)) + return JS_FALSE; + + /* ss->top points to the next free slot; be paranoid about overflow. */ + top = ss->top; + JS_ASSERT(top < ss->printer->script->depth); + if (top >= ss->printer->script->depth) { + JS_ReportOutOfMemory(ss->sprinter.context); + return JS_FALSE; + } + + /* The opcodes stack must contain real bytecodes that index js_CodeSpec. */ + ss->offsets[top] = off; + ss->opcodes[top] = (op == JSOP_GETPROP2) ? JSOP_GETPROP + : (op == JSOP_GETELEM2) ? JSOP_GETELEM + : (jsbytecode) op; + ss->top = ++top; + ss->sprinter.offset += PAREN_SLOP; + return JS_TRUE; +} + +static ptrdiff_t +PopOff(SprintStack *ss, JSOp op) +{ + uintN top; + const JSCodeSpec *cs, *topcs; + ptrdiff_t off; + + /* ss->top points to the next free slot; be paranoid about underflow. */ + top = ss->top; + JS_ASSERT(top != 0); + if (top == 0) + return 0; + + ss->top = --top; + topcs = &js_CodeSpec[ss->opcodes[top]]; + cs = &js_CodeSpec[op]; + if (topcs->prec != 0 && topcs->prec < cs->prec) { + ss->offsets[top] -= 2; + ss->sprinter.offset = ss->offsets[top]; + off = Sprint(&ss->sprinter, "(%s)", + OFF2STR(&ss->sprinter, ss->sprinter.offset + 2)); + } else { + off = ss->sprinter.offset = ss->offsets[top]; + } + return off; +} + +#if JS_HAS_SWITCH_STATEMENT +typedef struct TableEntry { + jsval key; + ptrdiff_t offset; +} TableEntry; + +static int +CompareOffsets(const void *v1, const void *v2, void *arg) +{ + const TableEntry *te1 = (const TableEntry *) v1, + *te2 = (const TableEntry *) v2; + + return te1->offset - te2->offset; +} + +static JSBool +Decompile(SprintStack *ss, jsbytecode *pc, intN nb); + +static JSBool +DecompileSwitch(SprintStack *ss, TableEntry *table, uintN tableLength, + jsbytecode *pc, ptrdiff_t switchLength, + ptrdiff_t defaultOffset, JSBool isCondSwitch) +{ + JSContext *cx; + JSPrinter *jp; + char *lval, *rval; + uintN i; + ptrdiff_t diff, off, off2, caseExprOff; + jsval key; + JSString *str; + + cx = ss->sprinter.context; + jp = ss->printer; + + lval = OFF2STR(&ss->sprinter, PopOff(ss, JSOP_NOP)); + js_printf(jp, "\tswitch (%s) {\n", lval); + + if (tableLength) { + diff = table[0].offset - defaultOffset; + if (diff > 0) { + jp->indent += 2; + js_printf(jp, "\tdefault:\n"); + jp->indent += 2; + if (!Decompile(ss, pc + defaultOffset, diff)) + return JS_FALSE; + jp->indent -= 4; + } + + caseExprOff = isCondSwitch + ? (ptrdiff_t) js_CodeSpec[JSOP_CONDSWITCH].length + : 0; + + for (i = 0; i < tableLength; i++) { + off = table[i].offset; + if (i + 1 < tableLength) + off2 = table[i + 1].offset; + else + off2 = switchLength; + + key = table[i].key; + if (isCondSwitch) { + ptrdiff_t nextCaseExprOff; + + /* + * key encodes the JSOP_CASE bytecode's offset from switchtop. + * The next case expression follows immediately, unless we are + * at the last case. + */ + nextCaseExprOff = (ptrdiff_t)JSVAL_TO_INT(key); + nextCaseExprOff += js_CodeSpec[pc[nextCaseExprOff]].length; + jp->indent += 2; + if (!Decompile(ss, pc + caseExprOff, + nextCaseExprOff - caseExprOff)) { + return JS_FALSE; + } + caseExprOff = nextCaseExprOff; + } else { + /* + * key comes from an atom, not the decompiler, so we need to + * quote it if it's a string literal. + */ + str = js_ValueToString(cx, key); + if (!str) + return JS_FALSE; + jp->indent += 2; + if (JSVAL_IS_STRING(key)) { + rval = QuoteString(&ss->sprinter, str, (jschar)'"'); + if (!rval) + return JS_FALSE; + RETRACT(&ss->sprinter, rval); + } else { + rval = JS_GetStringBytes(str); + } + js_printf(jp, "\tcase %s:\n", rval); + } + + jp->indent += 2; + if (off <= defaultOffset && defaultOffset < off2) { + diff = defaultOffset - off; + if (diff != 0) { + if (!Decompile(ss, pc + off, diff)) + return JS_FALSE; + off = defaultOffset; + } + jp->indent -= 2; + js_printf(jp, "\tdefault:\n"); + jp->indent += 2; + } + if (!Decompile(ss, pc + off, off2 - off)) + return JS_FALSE; + jp->indent -= 4; + } + } + + if (defaultOffset == switchLength) { + jp->indent += 2; + js_printf(jp, "\tdefault:\n"); + jp->indent -= 2; + } + js_printf(jp, "\t}\n"); + return JS_TRUE; +} +#endif + +static JSAtom * +GetSlotAtom(JSPrinter *jp, JSPropertyOp getter, uintN slot) +{ + JSScope *scope; + JSScopeProperty *sprop; + JSObject *obj, *proto; + + scope = jp->scope; + while (scope) { + for (sprop = SCOPE_LAST_PROP(scope); sprop; sprop = sprop->parent) { + if (sprop->getter != getter) + continue; + JS_ASSERT(sprop->flags & SPROP_HAS_SHORTID); + JS_ASSERT(!JSVAL_IS_INT(sprop->id)); + if ((uintN) sprop->shortid == slot) + return (JSAtom *) sprop->id; + } + obj = scope->object; + if (!obj) + break; + proto = OBJ_GET_PROTO(jp->sprinter.context, obj); + if (!proto) + break; + scope = OBJ_SCOPE(proto); + } + return NULL; +} + +static const char * +VarPrefix(jssrcnote *sn) +{ + const char *kw; + static char buf[8]; + + kw = NULL; + if (sn) { + if (SN_TYPE(sn) == SRC_VAR) + kw = js_var_str; + else if (SN_TYPE(sn) == SRC_CONST) + kw = js_const_str; + } + if (!kw) + return ""; + JS_snprintf(buf, sizeof buf, "%s ", kw); + return buf; +} + +static JSBool +Decompile(SprintStack *ss, jsbytecode *pc, intN nb) +{ + JSContext *cx; + JSPrinter *jp, *jp2; + jsbytecode *endpc, *done, *forelem_tail, *forelem_done; + ptrdiff_t len, todo, oplen, cond, next, tail; + JSOp op, lastop, saveop; + const JSCodeSpec *cs, *topcs; + jssrcnote *sn, *sn2; + const char *lval, *rval, *xval, *fmt; + jsint i, argc; + char **argv; + JSAtom *atom; + JSObject *obj; + JSFunction *fun; + JSString *str; + JSBool ok; + jsval val; + +/* + * Local macros + */ +#define DECOMPILE_CODE(pc,nb) if (!Decompile(ss, pc, nb)) return JS_FALSE +#define POP_STR() OFF2STR(&ss->sprinter, PopOff(ss, op)) +#define LOCAL_ASSERT(expr) JS_ASSERT(expr); if (!(expr)) return JS_FALSE + +/* + * Get atom from script's atom map, quote/escape its string appropriately into + * rval, and select fmt from the quoted and unquoted alternatives. + */ +#define GET_ATOM_QUOTE_AND_FMT(qfmt, ufmt, rval) \ + JS_BEGIN_MACRO \ + jschar quote_; \ + atom = GET_ATOM(cx, jp->script, pc); \ + if (ATOM_KEYWORD(atom)) { \ + quote_ = '\''; \ + fmt = qfmt; \ + } else { \ + quote_ = 0; \ + fmt = ufmt; \ + } \ + rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), quote_); \ + if (!rval) \ + return JS_FALSE; \ + JS_END_MACRO + + cx = ss->sprinter.context; + jp = ss->printer; + endpc = pc + nb; + forelem_tail = forelem_done = NULL; + todo = -2; /* NB: different from Sprint() error return. */ + tail = -1; + op = JSOP_NOP; + sn = NULL; + rval = NULL; + + while (pc < endpc) { + lastop = op; + op = saveop = (JSOp) *pc; + if (op >= JSOP_LIMIT) { + switch (op) { + case JSOP_GETPROP2: + saveop = JSOP_GETPROP; + break; + case JSOP_GETELEM2: + saveop = JSOP_GETELEM; + break; + default:; + } + } + cs = &js_CodeSpec[saveop]; + len = oplen = cs->length; + + if (cs->token) { + switch (cs->nuses) { + case 2: + rval = POP_STR(); + lval = POP_STR(); + sn = js_GetSrcNote(jp->script, pc); + if (sn && SN_TYPE(sn) == SRC_ASSIGNOP) { + /* Print only the right operand of the assignment-op. */ + todo = SprintPut(&ss->sprinter, rval, strlen(rval)); + } else { + todo = Sprint(&ss->sprinter, "%s %s %s", + lval, cs->token, rval); + } + break; + + case 1: + rval = POP_STR(); + todo = Sprint(&ss->sprinter, "%s%s", cs->token, rval); + break; + + case 0: +#if JS_HAS_GETTER_SETTER + if (op == JSOP_GETTER || op == JSOP_SETTER) { + todo = -2; + break; + } +#endif + todo = SprintPut(&ss->sprinter, cs->token, strlen(cs->token)); + break; + + default: + todo = -2; + break; + } + } else { + switch (op) { + case JSOP_NOP: + /* + * Check for a do-while loop, a for-loop with an empty + * initializer part, a labeled statement, a function + * definition, or try/finally. + */ + sn = js_GetSrcNote(jp->script, pc); + todo = -2; + switch (sn ? SN_TYPE(sn) : SRC_NULL) { +#if JS_HAS_DO_WHILE_LOOP + case SRC_WHILE: + js_printf(jp, "\tdo {\n"); + jp->indent += 4; + break; +#endif /* JS_HAS_DO_WHILE_LOOP */ + + case SRC_FOR: + rval = ""; + + do_forloop: + /* Skip the JSOP_NOP or JSOP_POP bytecode. */ + pc++; + + /* Get the cond, next, and loop-closing tail offsets. */ + cond = js_GetSrcNoteOffset(sn, 0); + next = js_GetSrcNoteOffset(sn, 1); + tail = js_GetSrcNoteOffset(sn, 2); + LOCAL_ASSERT(tail + GetJumpOffset(pc+tail, pc+tail) == 0); + + /* Print the keyword and the possibly empty init-part. */ + js_printf(jp, "\tfor (%s;", rval); + + if (pc[cond] == JSOP_IFEQ || pc[cond] == JSOP_IFEQX) { + /* Decompile the loop condition. */ + DECOMPILE_CODE(pc, cond); + js_printf(jp, " %s", POP_STR()); + } + + /* Need a semicolon whether or not there was a cond. */ + js_puts(jp, ";"); + + if (pc[next] != JSOP_GOTO && pc[next] != JSOP_GOTOX) { + /* Decompile the loop updater. */ + DECOMPILE_CODE(pc + next, tail - next - 1); + js_printf(jp, " %s", POP_STR()); + } + + /* Do the loop body. */ + js_puts(jp, ") {\n"); + jp->indent += 4; + oplen = (cond) ? js_CodeSpec[pc[cond]].length : 0; + DECOMPILE_CODE(pc + cond + oplen, next - cond - oplen); + jp->indent -= 4; + js_printf(jp, "\t}\n"); + + /* Set len so pc skips over the entire loop. */ + len = tail + js_CodeSpec[pc[tail]].length; + break; + + case SRC_LABEL: + atom = js_GetAtom(cx, &jp->script->atomMap, + (jsatomid) js_GetSrcNoteOffset(sn, 0)); + jp->indent -= 4; + rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0); + if (!rval) + return JS_FALSE; + RETRACT(&ss->sprinter, rval); + js_printf(jp, "\t%s:\n", rval); + jp->indent += 4; + break; + + case SRC_LABELBRACE: + atom = js_GetAtom(cx, &jp->script->atomMap, + (jsatomid) js_GetSrcNoteOffset(sn, 0)); + rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0); + if (!rval) + return JS_FALSE; + RETRACT(&ss->sprinter, rval); + js_printf(jp, "\t%s: {\n", rval); + jp->indent += 4; + break; + + case SRC_ENDBRACE: + jp->indent -= 4; + js_printf(jp, "\t}\n"); + break; + + case SRC_CATCH: + jp->indent -= 4; + sn = js_GetSrcNote(jp->script, pc); + pc += oplen; + js_printf(jp, "\t} catch ("); + + LOCAL_ASSERT(*pc == JSOP_NAME); + pc += js_CodeSpec[JSOP_NAME].length; + LOCAL_ASSERT(*pc == JSOP_PUSHOBJ); + pc += js_CodeSpec[JSOP_PUSHOBJ].length; + LOCAL_ASSERT(*pc == JSOP_NEWINIT); + pc += js_CodeSpec[JSOP_NEWINIT].length; + LOCAL_ASSERT(*pc == JSOP_EXCEPTION); + pc += js_CodeSpec[JSOP_EXCEPTION].length; + LOCAL_ASSERT(*pc == JSOP_INITCATCHVAR); + atom = GET_ATOM(cx, jp->script, pc); + rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0); + if (!rval) + return JS_FALSE; + RETRACT(&ss->sprinter, rval); + js_printf(jp, "%s", rval); + pc += js_CodeSpec[JSOP_INITCATCHVAR].length; + LOCAL_ASSERT(*pc == JSOP_ENTERWITH); + pc += js_CodeSpec[JSOP_ENTERWITH].length; + + len = js_GetSrcNoteOffset(sn, 0); + if (len) { + js_printf(jp, " if "); + DECOMPILE_CODE(pc, len); + js_printf(jp, "%s", POP_STR()); + pc += len; + LOCAL_ASSERT(*pc == JSOP_IFEQ || *pc == JSOP_IFEQX); + pc += js_CodeSpec[*pc].length; + } + + js_printf(jp, ") {\n"); + jp->indent += 4; + len = 0; + break; + + case SRC_FUNCDEF: + atom = js_GetAtom(cx, &jp->script->atomMap, + (jsatomid) js_GetSrcNoteOffset(sn, 0)); + JS_ASSERT(ATOM_IS_OBJECT(atom)); + do_function: + obj = ATOM_TO_OBJECT(atom); + fun = (JSFunction *) JS_GetPrivate(cx, obj); + jp2 = js_NewPrinter(cx, JS_GetFunctionName(fun), + jp->indent, jp->pretty); + if (!jp2) + return JS_FALSE; + jp2->scope = jp->scope; + if (js_DecompileFunction(jp2, fun)) { + str = js_GetPrinterOutput(jp2); + if (str) + js_printf(jp, "%s\n", JS_GetStringBytes(str)); + } + js_DestroyPrinter(jp2); + break; + + default:; + } + case JSOP_RETRVAL: + break; + + case JSOP_GROUP: + /* Use last real op so PopOff adds parens if needed. */ + todo = PopOff(ss, lastop); + + /* Now add user-supplied parens only if PopOff did not. */ + cs = &js_CodeSpec[lastop]; + topcs = &js_CodeSpec[ss->opcodes[ss->top]]; + if (topcs->prec >= cs->prec) { + todo = Sprint(&ss->sprinter, "(%s)", + OFF2STR(&ss->sprinter, todo)); + } + break; + + case JSOP_PUSH: + case JSOP_PUSHOBJ: + case JSOP_BINDNAME: + todo = Sprint(&ss->sprinter, ""); + break; + +#if JS_HAS_EXCEPTIONS + case JSOP_TRY: + js_printf(jp, "\ttry {\n"); + jp->indent += 4; + todo = -2; + break; + + { + static const char finally_cookie[] = "finally-cookie"; + + case JSOP_FINALLY: + jp->indent -= 4; + js_printf(jp, "\t} finally {\n"); + jp->indent += 4; + + /* + * We must push an empty string placeholder for gosub's return + * address, popped by JSOP_RETSUB and counted by script->depth + * but not by ss->top (see JSOP_SETSP, below). + */ + todo = Sprint(&ss->sprinter, finally_cookie); + break; + + case JSOP_RETSUB: + rval = POP_STR(); + LOCAL_ASSERT(strcmp(rval, finally_cookie) == 0); + todo = -2; + break; + } + + case JSOP_SWAP: + /* + * We don't generate this opcode currently, and previously we + * did not need to decompile it. If old, serialized bytecode + * uses it still, we should fall through and set todo = -2. + */ + /* FALL THROUGH */ + + case JSOP_GOSUB: + case JSOP_GOSUBX: + /* + * JSOP_GOSUB and GOSUBX have no effect on the decompiler's + * string stack because the next op in bytecode order finds + * the stack balanced by a JSOP_RETSUB executed elsewhere. + */ + todo = -2; + break; + + case JSOP_SETSP: + /* + * The compiler models operand stack depth and fixes the stack + * pointer on entry to a catch clause based on its depth model. + * The decompiler must match the code generator's model, which + * is why JSOP_FINALLY pushes a cookie that JSOP_RETSUB pops. + */ + ss->top = (uintN) GET_ATOM_INDEX(pc); + break; + + case JSOP_EXCEPTION: + /* + * The only other JSOP_EXCEPTION case occurs as part of a code + * sequence that follows a SRC_CATCH-annotated JSOP_NOP. + */ + sn = js_GetSrcNote(jp->script, pc); + LOCAL_ASSERT(sn && SN_TYPE(sn) == SRC_HIDDEN); + todo = -2; + break; +#endif /* JS_HAS_EXCEPTIONS */ + + case JSOP_POP: + case JSOP_POPV: + sn = js_GetSrcNote(jp->script, pc); + switch (sn ? SN_TYPE(sn) : SRC_NULL) { + case SRC_FOR: + rval = POP_STR(); + todo = -2; + goto do_forloop; + + case SRC_PCDELTA: + /* Pop and save to avoid blowing stack depth budget. */ + lval = JS_strdup(cx, POP_STR()); + if (!lval) + return JS_FALSE; + + /* + * The offset tells distance to the end of the right-hand + * operand of the comma operator. + */ + done = pc + len; + pc += js_GetSrcNoteOffset(sn, 0); + len = 0; + + if (!Decompile(ss, done, pc - done)) { + JS_free(cx, (char *)lval); + return JS_FALSE; + } + + /* Pop Decompile result and print comma expression. */ + rval = POP_STR(); + todo = Sprint(&ss->sprinter, "%s, %s", lval, rval); + JS_free(cx, (char *)lval); + break; + + case SRC_HIDDEN: + /* Hide this pop, it's from a goto in a with or for/in. */ + todo = -2; + break; + + default: + rval = POP_STR(); + if (*rval != '\0') + js_printf(jp, "\t%s;\n", rval); + todo = -2; + break; + } + break; + + case JSOP_POP2: + (void) PopOff(ss, op); + (void) PopOff(ss, op); + todo = -2; + break; + + { + static const char with_cookie[] = "with-cookie"; + + case JSOP_ENTERWITH: + sn = js_GetSrcNote(jp->script, pc); + if (sn && SN_TYPE(sn) == SRC_HIDDEN) { + todo = -2; + break; + } + rval = POP_STR(); + js_printf(jp, "\twith (%s) {\n", rval); + jp->indent += 4; + todo = Sprint(&ss->sprinter, with_cookie); + break; + + case JSOP_LEAVEWITH: + sn = js_GetSrcNote(jp->script, pc); + todo = -2; + if (sn && SN_TYPE(sn) == SRC_HIDDEN) + break; + rval = POP_STR(); + LOCAL_ASSERT(strcmp(rval, with_cookie) == 0); + jp->indent -= 4; + js_printf(jp, "\t}\n"); + break; + } + + case JSOP_SETRVAL: + case JSOP_RETURN: + rval = POP_STR(); + if (*rval != '\0') + js_printf(jp, "\t%s %s;\n", cs->name, rval); + else + js_printf(jp, "\t%s;\n", cs->name); + todo = -2; + break; + +#if JS_HAS_EXCEPTIONS + case JSOP_THROW: + sn = js_GetSrcNote(jp->script, pc); + todo = -2; + if (sn && SN_TYPE(sn) == SRC_HIDDEN) + break; + rval = POP_STR(); + js_printf(jp, "\t%s %s;\n", cs->name, rval); + break; +#endif /* JS_HAS_EXCEPTIONS */ + + case JSOP_GOTO: + case JSOP_GOTOX: + sn = js_GetSrcNote(jp->script, pc); + switch (sn ? SN_TYPE(sn) : SRC_NULL) { + case SRC_CONT2LABEL: + atom = js_GetAtom(cx, &jp->script->atomMap, + (jsatomid) js_GetSrcNoteOffset(sn, 0)); + rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0); + if (!rval) + return JS_FALSE; + RETRACT(&ss->sprinter, rval); + js_printf(jp, "\tcontinue %s;\n", rval); + break; + case SRC_CONTINUE: + js_printf(jp, "\tcontinue;\n"); + break; + case SRC_BREAK2LABEL: + atom = js_GetAtom(cx, &jp->script->atomMap, + (jsatomid) js_GetSrcNoteOffset(sn, 0)); + rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0); + if (!rval) + return JS_FALSE; + RETRACT(&ss->sprinter, rval); + js_printf(jp, "\tbreak %s;\n", rval); + break; + case SRC_HIDDEN: + break; + default: + js_printf(jp, "\tbreak;\n"); + break; + } + todo = -2; + break; + + case JSOP_IFEQ: + case JSOP_IFEQX: + len = GetJumpOffset(pc, pc); + sn = js_GetSrcNote(jp->script, pc); + + switch (sn ? SN_TYPE(sn) : SRC_NULL) { + case SRC_IF: + case SRC_IF_ELSE: + rval = POP_STR(); + js_printf(jp, "\tif (%s) {\n", rval); + jp->indent += 4; + if (SN_TYPE(sn) == SRC_IF) { + DECOMPILE_CODE(pc + oplen, len - oplen); + } else { + len = js_GetSrcNoteOffset(sn, 0); + DECOMPILE_CODE(pc + oplen, len - oplen); + jp->indent -= 4; + pc += len; + LOCAL_ASSERT(*pc == JSOP_GOTO || *pc == JSOP_GOTOX); + oplen = js_CodeSpec[*pc].length; + len = GetJumpOffset(pc, pc); + js_printf(jp, "\t} else {\n"); + jp->indent += 4; + DECOMPILE_CODE(pc + oplen, len - oplen); + } + jp->indent -= 4; + js_printf(jp, "\t}\n"); + todo = -2; + break; + + case SRC_WHILE: + rval = POP_STR(); + js_printf(jp, "\twhile (%s) {\n", rval); + jp->indent += 4; + tail = js_GetSrcNoteOffset(sn, 0); + DECOMPILE_CODE(pc + oplen, tail - oplen); + jp->indent -= 4; + js_printf(jp, "\t}\n"); + todo = -2; + break; + + case SRC_COND: + xval = JS_strdup(cx, POP_STR()); + if (!xval) + return JS_FALSE; + len = js_GetSrcNoteOffset(sn, 0); + DECOMPILE_CODE(pc + oplen, len - oplen); + lval = JS_strdup(cx, POP_STR()); + if (!lval) { + JS_free(cx, (void *)xval); + return JS_FALSE; + } + pc += len; + LOCAL_ASSERT(*pc == JSOP_GOTO || *pc == JSOP_GOTOX); + oplen = js_CodeSpec[*pc].length; + len = GetJumpOffset(pc, pc); + DECOMPILE_CODE(pc + oplen, len - oplen); + rval = POP_STR(); + todo = Sprint(&ss->sprinter, "%s ? %s : %s", + xval, lval, rval); + JS_free(cx, (void *)xval); + JS_free(cx, (void *)lval); + break; + + default: + break; + } + break; + + case JSOP_IFNE: + case JSOP_IFNEX: +#if JS_HAS_DO_WHILE_LOOP + /* Currently, this must be a do-while loop's upward branch. */ + jp->indent -= 4; + js_printf(jp, "\t} while (%s);\n", POP_STR()); + todo = -2; +#else + JS_ASSERT(0); +#endif /* JS_HAS_DO_WHILE_LOOP */ + break; + + case JSOP_OR: + case JSOP_ORX: + xval = "||"; + + do_logical_connective: + /* Top of stack is the first clause in a disjunction (||). */ + lval = JS_strdup(cx, POP_STR()); + if (!lval) + return JS_FALSE; + done = pc + GetJumpOffset(pc, pc); + pc += len; + len = PTRDIFF(done, pc, jsbytecode); + DECOMPILE_CODE(pc, len); + rval = POP_STR(); + if (jp->pretty && + jp->indent + 4 + strlen(lval) + 4 + strlen(rval) > 75) { + rval = JS_strdup(cx, rval); + if (!rval) { + tail = -1; + } else { + todo = Sprint(&ss->sprinter, "%s %s\n", lval, xval); + tail = Sprint(&ss->sprinter, "%*s%s", + jp->indent + 4, "", rval); + JS_free(cx, (char *)rval); + } + if (tail < 0) + todo = -1; + } else { + todo = Sprint(&ss->sprinter, "%s %s %s", lval, xval, rval); + } + JS_free(cx, (char *)lval); + break; + + case JSOP_AND: + case JSOP_ANDX: + xval = "&&"; + goto do_logical_connective; + + case JSOP_FORARG: + atom = GetSlotAtom(jp, js_GetArgument, GET_ARGNO(pc)); + LOCAL_ASSERT(atom); + goto do_fornameinloop; + + case JSOP_FORVAR: + atom = GetSlotAtom(jp, js_GetLocalVariable, GET_VARNO(pc)); + LOCAL_ASSERT(atom); + goto do_fornameinloop; + + case JSOP_FORNAME: + atom = GET_ATOM(cx, jp->script, pc); + + do_fornameinloop: + sn = js_GetSrcNote(jp->script, pc); + xval = NULL; + lval = ""; + goto do_forinloop; + + case JSOP_FORPROP: + xval = NULL; + atom = GET_ATOM(cx, jp->script, pc); + if (ATOM_KEYWORD(atom)) { + xval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), + (jschar)'\''); + if (!xval) + return JS_FALSE; + atom = NULL; + } + lval = POP_STR(); + sn = NULL; + + do_forinloop: + pc += oplen; + LOCAL_ASSERT(*pc == JSOP_IFEQ || *pc == JSOP_IFEQX); + oplen = js_CodeSpec[*pc].length; + len = GetJumpOffset(pc, pc); + sn2 = js_GetSrcNote(jp->script, pc); + tail = js_GetSrcNoteOffset(sn2, 0); + + do_forinbody: + js_printf(jp, "\tfor (%s%s", VarPrefix(sn), lval); + if (atom) { + xval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0); + if (!xval) + return JS_FALSE; + RETRACT(&ss->sprinter, xval); + js_printf(jp, *lval ? ".%s" : "%s", xval); + } else if (xval) { + js_printf(jp, "[%s]", xval); + } + rval = OFF2STR(&ss->sprinter, ss->offsets[ss->top-1]); + js_printf(jp, " in %s) {\n", rval); + jp->indent += 4; + DECOMPILE_CODE(pc + oplen, tail - oplen); + jp->indent -= 4; + js_printf(jp, "\t}\n"); + todo = -2; + break; + + case JSOP_FORELEM: + pc++; + LOCAL_ASSERT(*pc == JSOP_IFEQ || *pc == JSOP_IFEQX); + len = js_CodeSpec[*pc].length; + + /* + * Arrange for the JSOP_ENUMELEM case to set tail for use by + * do_forinbody: code that uses on it to find the loop-closing + * jump (whatever its format, normal or extended), in order to + * bound the recursively decompiled loop body. + */ + sn = js_GetSrcNote(jp->script, pc); + JS_ASSERT(!forelem_tail); + forelem_tail = pc + js_GetSrcNoteOffset(sn, 0); + + /* + * This gets a little wacky. Only the length of the for loop + * body PLUS the element-indexing expression is known here, so + * we pass the after-loop pc to the JSOP_ENUMELEM case, which + * is immediately below, to decompile that helper bytecode via + * the 'forelem_done' local. + * + * Since a for..in loop can't nest in the head of another for + * loop, we can use forelem_{tail,done} singletons to remember + * state from JSOP_FORELEM to JSOP_ENUMELEM, thence (via goto) + * to label do_forinbody. + */ + JS_ASSERT(!forelem_done); + forelem_done = pc + GetJumpOffset(pc, pc); + break; + + case JSOP_ENUMELEM: + /* + * The stack has the object under the (top) index expression. + * The "rval" property id is underneath those two on the stack. + * The for loop body net and gross lengths can now be adjusted + * to account for the length of the indexing expression that + * came after JSOP_FORELEM and before JSOP_ENUMELEM. + */ + atom = NULL; + xval = POP_STR(); + lval = POP_STR(); + rval = OFF2STR(&ss->sprinter, ss->offsets[ss->top-1]); + JS_ASSERT(forelem_tail > pc); + tail = forelem_tail - pc; + forelem_tail = NULL; + JS_ASSERT(forelem_done > pc); + len = forelem_done - pc; + forelem_done = NULL; + goto do_forinbody; + + case JSOP_DUP2: + rval = OFF2STR(&ss->sprinter, ss->offsets[ss->top-2]); + todo = SprintPut(&ss->sprinter, rval, strlen(rval)); + if (todo < 0 || !PushOff(ss, todo, ss->opcodes[ss->top-2])) + return JS_FALSE; + /* FALL THROUGH */ + + case JSOP_DUP: + rval = OFF2STR(&ss->sprinter, ss->offsets[ss->top-1]); + op = ss->opcodes[ss->top-1]; + todo = SprintPut(&ss->sprinter, rval, strlen(rval)); + break; + + case JSOP_SETARG: + atom = GetSlotAtom(jp, js_GetArgument, GET_ARGNO(pc)); + LOCAL_ASSERT(atom); + goto do_setname; + + case JSOP_SETVAR: + atom = GetSlotAtom(jp, js_GetLocalVariable, GET_VARNO(pc)); + LOCAL_ASSERT(atom); + goto do_setname; + + case JSOP_SETCONST: + case JSOP_SETNAME: + atom = GET_ATOM(cx, jp->script, pc); + do_setname: + lval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0); + if (!lval) + return JS_FALSE; + rval = POP_STR(); + if (op == JSOP_SETNAME) + (void) PopOff(ss, op); + do_setlval: + sn = js_GetSrcNote(jp->script, pc - 1); + if (sn && SN_TYPE(sn) == SRC_ASSIGNOP) { + todo = Sprint(&ss->sprinter, "%s %s= %s", + lval, js_CodeSpec[lastop].token, rval); + } else { + sn = js_GetSrcNote(jp->script, pc); + todo = Sprint(&ss->sprinter, "%s%s = %s", + VarPrefix(sn), lval, rval); + } + break; + + case JSOP_NEW: + case JSOP_CALL: + case JSOP_EVAL: +#if JS_HAS_LVALUE_RETURN + case JSOP_SETCALL: +#endif + saveop = op; + op = JSOP_NOP; /* turn off parens */ + argc = GET_ARGC(pc); + argv = (char **) + JS_malloc(cx, (size_t)(argc + 1) * sizeof *argv); + if (!argv) + return JS_FALSE; + + ok = JS_TRUE; + for (i = argc; i > 0; i--) { + argv[i] = JS_strdup(cx, POP_STR()); + if (!argv[i]) { + ok = JS_FALSE; + break; + } + } + + /* Skip the JSOP_PUSHOBJ-created empty string. */ + LOCAL_ASSERT(ss->top >= 2); + (void) PopOff(ss, op); + + /* Get the callee's decompiled image in argv[0]. */ + argv[0] = JS_strdup(cx, POP_STR()); + if (!argv[i]) + ok = JS_FALSE; + + lval = "(", rval = ")"; + if (saveop == JSOP_NEW) { + todo = Sprint(&ss->sprinter, "%s %s%s", + js_new_str, argv[0], lval); + } else { + todo = Sprint(&ss->sprinter, "%s%s", + argv[0], lval); + } + if (todo < 0) + ok = JS_FALSE; + + for (i = 1; i <= argc; i++) { + if (!argv[i] || + Sprint(&ss->sprinter, "%s%s", + argv[i], (i < argc) ? ", " : "") < 0) { + ok = JS_FALSE; + break; + } + } + if (Sprint(&ss->sprinter, rval) < 0) + ok = JS_FALSE; + + for (i = 0; i <= argc; i++) { + if (argv[i]) + JS_free(cx, argv[i]); + } + JS_free(cx, argv); + if (!ok) + return JS_FALSE; + op = saveop; +#if JS_HAS_LVALUE_RETURN + if (op == JSOP_SETCALL) { + if (!PushOff(ss, todo, op)) + return JS_FALSE; + todo = Sprint(&ss->sprinter, ""); + } +#endif + break; + + case JSOP_DELNAME: + atom = GET_ATOM(cx, jp->script, pc); + lval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0); + if (!lval) + return JS_FALSE; + RETRACT(&ss->sprinter, lval); + todo = Sprint(&ss->sprinter, "%s %s", js_delete_str, lval); + break; + + case JSOP_DELPROP: + GET_ATOM_QUOTE_AND_FMT("%s %s[%s]", "%s %s.%s", rval); + lval = POP_STR(); + todo = Sprint(&ss->sprinter, fmt, js_delete_str, lval, rval); + break; + + case JSOP_DELELEM: + xval = POP_STR(); + lval = POP_STR(); + todo = Sprint(&ss->sprinter, "%s %s[%s]", + js_delete_str, lval, xval); + break; + + case JSOP_TYPEOF: + case JSOP_VOID: + rval = POP_STR(); + todo = Sprint(&ss->sprinter, "%s %s", cs->name, rval); + break; + + case JSOP_INCARG: + case JSOP_DECARG: + atom = GetSlotAtom(jp, js_GetArgument, GET_ARGNO(pc)); + LOCAL_ASSERT(atom); + goto do_incatom; + + case JSOP_INCVAR: + case JSOP_DECVAR: + atom = GetSlotAtom(jp, js_GetLocalVariable, GET_VARNO(pc)); + LOCAL_ASSERT(atom); + goto do_incatom; + + case JSOP_INCNAME: + case JSOP_DECNAME: + atom = GET_ATOM(cx, jp->script, pc); + do_incatom: + lval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0); + if (!lval) + return JS_FALSE; + RETRACT(&ss->sprinter, lval); + todo = Sprint(&ss->sprinter, "%s%s", + js_incop_str[!(cs->format & JOF_INC)], lval); + break; + + case JSOP_INCPROP: + case JSOP_DECPROP: + GET_ATOM_QUOTE_AND_FMT("%s%s[%s]", "%s%s.%s", rval); + lval = POP_STR(); + todo = Sprint(&ss->sprinter, fmt, + js_incop_str[!(cs->format & JOF_INC)], + lval, rval); + break; + + case JSOP_INCELEM: + case JSOP_DECELEM: + xval = POP_STR(); + lval = POP_STR(); + todo = Sprint(&ss->sprinter, "%s%s[%s]", + js_incop_str[!(cs->format & JOF_INC)], + lval, xval); + break; + + case JSOP_ARGINC: + case JSOP_ARGDEC: + atom = GetSlotAtom(jp, js_GetArgument, GET_ARGNO(pc)); + LOCAL_ASSERT(atom); + goto do_atominc; + + case JSOP_VARINC: + case JSOP_VARDEC: + atom = GetSlotAtom(jp, js_GetLocalVariable, GET_VARNO(pc)); + LOCAL_ASSERT(atom); + goto do_atominc; + + case JSOP_NAMEINC: + case JSOP_NAMEDEC: + atom = GET_ATOM(cx, jp->script, pc); + do_atominc: + lval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0); + if (!lval) + return JS_FALSE; + todo = STR2OFF(&ss->sprinter, lval); + SprintPut(&ss->sprinter, + js_incop_str[!(cs->format & JOF_INC)], + 2); + break; + + case JSOP_PROPINC: + case JSOP_PROPDEC: + GET_ATOM_QUOTE_AND_FMT("%s[%s]%s", "%s.%s%s", rval); + lval = POP_STR(); + todo = Sprint(&ss->sprinter, fmt, lval, rval, + js_incop_str[!(cs->format & JOF_INC)]); + break; + + case JSOP_ELEMINC: + case JSOP_ELEMDEC: + xval = POP_STR(); + lval = POP_STR(); + todo = Sprint(&ss->sprinter, "%s[%s]%s", + lval, xval, + js_incop_str[!(cs->format & JOF_INC)]); + break; + + case JSOP_GETPROP2: + op = JSOP_GETPROP; + (void) PopOff(ss, lastop); + /* FALL THROUGH */ + + case JSOP_GETPROP: + GET_ATOM_QUOTE_AND_FMT("%s[%s]", "%s.%s", rval); + lval = POP_STR(); + todo = Sprint(&ss->sprinter, fmt, lval, rval); + break; + + case JSOP_SETPROP: + GET_ATOM_QUOTE_AND_FMT("%s[%s] %s= %s", "%s.%s %s= %s", xval); + rval = POP_STR(); + lval = POP_STR(); + sn = js_GetSrcNote(jp->script, pc - 1); + todo = Sprint(&ss->sprinter, fmt, lval, xval, + (sn && SN_TYPE(sn) == SRC_ASSIGNOP) + ? js_CodeSpec[lastop].token + : "", + rval); + break; + + case JSOP_GETELEM2: + op = JSOP_GETELEM; + (void) PopOff(ss, lastop); + /* FALL THROUGH */ + + case JSOP_GETELEM: + op = JSOP_NOP; /* turn off parens */ + xval = POP_STR(); + op = JSOP_GETELEM; + lval = POP_STR(); + if (*xval == '\0') + todo = Sprint(&ss->sprinter, "%s", lval); + else + todo = Sprint(&ss->sprinter, "%s[%s]", lval, xval); + break; + + case JSOP_SETELEM: + op = JSOP_NOP; /* turn off parens */ + rval = POP_STR(); + xval = POP_STR(); + op = JSOP_SETELEM; + lval = POP_STR(); + if (*xval == '\0') + goto do_setlval; + sn = js_GetSrcNote(jp->script, pc - 1); + todo = Sprint(&ss->sprinter, "%s[%s] %s= %s", + lval, xval, + (sn && SN_TYPE(sn) == SRC_ASSIGNOP) + ? js_CodeSpec[lastop].token + : "", + rval); + break; + + case JSOP_ARGSUB: + i = (jsint) GET_ATOM_INDEX(pc); + todo = Sprint(&ss->sprinter, "%s[%d]", + js_arguments_str, (int) i); + break; + + case JSOP_ARGCNT: + todo = Sprint(&ss->sprinter, "%s.%s", + js_arguments_str, js_length_str); + break; + + case JSOP_GETARG: + atom = GetSlotAtom(jp, js_GetArgument, GET_ARGNO(pc)); + LOCAL_ASSERT(atom); + goto do_name; + + case JSOP_GETVAR: + atom = GetSlotAtom(jp, js_GetLocalVariable, GET_VARNO(pc)); + LOCAL_ASSERT(atom); + goto do_name; + + case JSOP_NAME: + atom = GET_ATOM(cx, jp->script, pc); + do_name: + sn = js_GetSrcNote(jp->script, pc); + rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0); + if (!rval) + return JS_FALSE; + RETRACT(&ss->sprinter, rval); + todo = Sprint(&ss->sprinter, "%s%s", VarPrefix(sn), rval); + break; + + case JSOP_UINT16: + i = (jsint) GET_ATOM_INDEX(pc); + todo = Sprint(&ss->sprinter, "%u", (unsigned) i); + break; + + case JSOP_NUMBER: + atom = GET_ATOM(cx, jp->script, pc); + val = ATOM_KEY(atom); + if (JSVAL_IS_INT(val)) { + long ival = (long)JSVAL_TO_INT(val); + todo = Sprint(&ss->sprinter, "%ld", ival); + } else { + char buf[DTOSTR_STANDARD_BUFFER_SIZE]; + char *numStr = JS_dtostr(buf, sizeof buf, DTOSTR_STANDARD, + 0, *JSVAL_TO_DOUBLE(val)); + if (!numStr) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + todo = Sprint(&ss->sprinter, numStr); + } + break; + + case JSOP_STRING: + atom = GET_ATOM(cx, jp->script, pc); + rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), + (jschar)'"'); + if (!rval) + return JS_FALSE; + todo = STR2OFF(&ss->sprinter, rval); + break; + + case JSOP_OBJECT: + case JSOP_ANONFUNOBJ: + case JSOP_NAMEDFUNOBJ: + atom = GET_ATOM(cx, jp->script, pc); + if (op == JSOP_OBJECT) { + str = js_ValueToSource(cx, ATOM_KEY(atom)); + if (!str) + return JS_FALSE; + } else { + if (!js_fun_toString(cx, ATOM_TO_OBJECT(atom), + JS_DONT_PRETTY_PRINT, 0, NULL, + &val)) { + return JS_FALSE; + } + str = JSVAL_TO_STRING(val); + } + todo = SprintPut(&ss->sprinter, JS_GetStringBytes(str), + JSSTRING_LENGTH(str)); + break; + +#if JS_HAS_SWITCH_STATEMENT + case JSOP_TABLESWITCH: + case JSOP_TABLESWITCHX: + { + jsbytecode *pc2; + ptrdiff_t off, off2; + jsint j, n, low, high; + TableEntry *table; + + sn = js_GetSrcNote(jp->script, pc); + JS_ASSERT(sn && SN_TYPE(sn) == SRC_SWITCH); + len = js_GetSrcNoteOffset(sn, 0); + pc2 = pc; + off = GetJumpOffset(pc, pc2); + pc2 += JUMP_OFFSET_LEN; + low = GetJumpOffset(pc, pc2); + pc2 += JUMP_OFFSET_LEN; + high = GetJumpOffset(pc, pc2); + + n = high - low + 1; + if (n == 0) { + table = NULL; + j = 0; + } else { + table = (TableEntry *) + JS_malloc(cx, (size_t)n * sizeof *table); + if (!table) + return JS_FALSE; + for (i = j = 0; i < n; i++) { + pc2 += JUMP_OFFSET_LEN; + off2 = GetJumpOffset(pc, pc2); + if (off2) { + table[j].key = INT_TO_JSVAL(low + i); + table[j++].offset = off2; + } + } + js_HeapSort(table, (size_t) j, sizeof *table, + CompareOffsets, NULL); + } + + ok = DecompileSwitch(ss, table, (uintN)j, pc, len, off, + JS_FALSE); + JS_free(cx, table); + if (!ok) + return ok; + todo = -2; + break; + } + + case JSOP_LOOKUPSWITCH: + case JSOP_LOOKUPSWITCHX: + { + jsbytecode *pc2; + ptrdiff_t off, off2; + jsint npairs; + TableEntry *table; + + sn = js_GetSrcNote(jp->script, pc); + JS_ASSERT(sn && SN_TYPE(sn) == SRC_SWITCH); + len = js_GetSrcNoteOffset(sn, 0); + pc2 = pc; + off = GetJumpOffset(pc, pc2); + pc2 += JUMP_OFFSET_LEN; + npairs = (jsint) GET_ATOM_INDEX(pc2); + pc2 += ATOM_INDEX_LEN; + + table = (TableEntry *) + JS_malloc(cx, (size_t)npairs * sizeof *table); + if (!table) + return JS_FALSE; + for (i = 0; i < npairs; i++) { + atom = GET_ATOM(cx, jp->script, pc2); + pc2 += ATOM_INDEX_LEN; + off2 = GetJumpOffset(pc, pc2); + pc2 += JUMP_OFFSET_LEN; + table[i].key = ATOM_KEY(atom); + table[i].offset = off2; + } + + ok = DecompileSwitch(ss, table, (uintN)npairs, pc, len, off, + JS_FALSE); + JS_free(cx, table); + if (!ok) + return ok; + todo = -2; + break; + } + + case JSOP_CONDSWITCH: + { + jsbytecode *pc2; + ptrdiff_t off, off2, caseOff; + jsint ncases; + TableEntry *table; + + sn = js_GetSrcNote(jp->script, pc); + JS_ASSERT(sn && SN_TYPE(sn) == SRC_SWITCH); + len = js_GetSrcNoteOffset(sn, 0); + off = js_GetSrcNoteOffset(sn, 1); + + /* + * Count the cases using offsets from switch to first case, + * and case to case, stored in srcnote immediates. + */ + pc2 = pc; + off2 = off; + for (ncases = 0; off2 != 0; ncases++) { + pc2 += off2; + JS_ASSERT(*pc2 == JSOP_CASE || *pc2 == JSOP_DEFAULT || + *pc2 == JSOP_CASEX || *pc2 == JSOP_DEFAULTX); + if (*pc2 == JSOP_DEFAULT || *pc2 == JSOP_DEFAULTX) { + /* End of cases, but count default as a case. */ + off2 = 0; + } else { + sn = js_GetSrcNote(jp->script, pc2); + JS_ASSERT(sn && SN_TYPE(sn) == SRC_PCDELTA); + off2 = js_GetSrcNoteOffset(sn, 0); + } + } + + /* + * Allocate table and rescan the cases using their srcnotes, + * stashing each case's delta from switch top in table[i].key, + * and the distance to its statements in table[i].offset. + */ + table = (TableEntry *) + JS_malloc(cx, (size_t)ncases * sizeof *table); + if (!table) + return JS_FALSE; + pc2 = pc; + off2 = off; + for (i = 0; i < ncases; i++) { + pc2 += off2; + JS_ASSERT(*pc2 == JSOP_CASE || *pc2 == JSOP_DEFAULT || + *pc2 == JSOP_CASEX || *pc2 == JSOP_DEFAULTX); + caseOff = pc2 - pc; + table[i].key = INT_TO_JSVAL((jsint) caseOff); + table[i].offset = caseOff + GetJumpOffset(pc2, pc2); + if (*pc2 == JSOP_CASE || *pc2 == JSOP_CASEX) { + sn = js_GetSrcNote(jp->script, pc2); + JS_ASSERT(sn && SN_TYPE(sn) == SRC_PCDELTA); + off2 = js_GetSrcNoteOffset(sn, 0); + } + } + + /* + * Find offset of default code by fetching the default offset + * from the end of table. JSOP_CONDSWITCH always has a default + * case at the end. + */ + off = JSVAL_TO_INT(table[ncases-1].key); + pc2 = pc + off; + off += GetJumpOffset(pc2, pc2); + + ok = DecompileSwitch(ss, table, (uintN)ncases, pc, len, off, + JS_TRUE); + JS_free(cx, table); + if (!ok) + return ok; + todo = -2; + break; + } + + case JSOP_CASE: + case JSOP_CASEX: + { + lval = POP_STR(); + if (!lval) + return JS_FALSE; + js_printf(jp, "\tcase %s:\n", lval); + todo = -2; + break; + } + +#endif /* JS_HAS_SWITCH_STATEMENT */ + +#if !JS_BUG_FALLIBLE_EQOPS + case JSOP_NEW_EQ: + case JSOP_NEW_NE: + rval = POP_STR(); + lval = POP_STR(); + todo = Sprint(&ss->sprinter, "%s %c%s %s", + lval, + (op == JSOP_NEW_EQ) ? '=' : '!', +#if JS_HAS_TRIPLE_EQOPS + JSVERSION_IS_ECMA(cx->version) ? "==" : +#endif + "=", + rval); + break; +#endif /* !JS_BUG_FALLIBLE_EQOPS */ + +#if JS_HAS_LEXICAL_CLOSURE + case JSOP_CLOSURE: + atom = GET_ATOM(cx, jp->script, pc); + JS_ASSERT(ATOM_IS_OBJECT(atom)); + goto do_function; +#endif /* JS_HAS_LEXICAL_CLOSURE */ + +#if JS_HAS_EXPORT_IMPORT + case JSOP_EXPORTALL: + js_printf(jp, "\texport *\n"); + todo = -2; + break; + + case JSOP_EXPORTNAME: + atom = GET_ATOM(cx, jp->script, pc); + rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0); + if (!rval) + return JS_FALSE; + RETRACT(&ss->sprinter, rval); + js_printf(jp, "\texport %s\n", rval); + todo = -2; + break; + + case JSOP_IMPORTALL: + lval = POP_STR(); + js_printf(jp, "\timport %s.*\n", lval); + todo = -2; + break; + + case JSOP_IMPORTPROP: + GET_ATOM_QUOTE_AND_FMT("\timport %s[%s]\n", "\timport %s.%s\n", + rval); + lval = POP_STR(); + js_printf(jp, fmt, lval, rval); + todo = -2; + break; + + case JSOP_IMPORTELEM: + xval = POP_STR(); + op = JSOP_GETELEM; + lval = POP_STR(); + js_printf(jp, "\timport %s[%s]\n", lval, xval); + todo = -2; + break; +#endif /* JS_HAS_EXPORT_IMPORT */ + + case JSOP_TRAP: + op = JS_GetTrapOpcode(cx, jp->script, pc); + if (op == JSOP_LIMIT) + return JS_FALSE; + *pc = op; + cs = &js_CodeSpec[op]; + len = cs->length; + DECOMPILE_CODE(pc, len); + *pc = JSOP_TRAP; + todo = -2; + break; + +#if JS_HAS_INITIALIZERS + case JSOP_NEWINIT: + LOCAL_ASSERT(ss->top >= 2); + (void) PopOff(ss, op); + lval = POP_STR(); +#if JS_HAS_SHARP_VARS + op = (JSOp)pc[len]; + if (op == JSOP_DEFSHARP) { + pc += len; + cs = &js_CodeSpec[op]; + len = cs->length; + i = (jsint) GET_ATOM_INDEX(pc); + todo = Sprint(&ss->sprinter, "#%u=%c", + (unsigned) i, + (*lval == 'O') ? '{' : '['); + } else +#endif /* JS_HAS_SHARP_VARS */ + { + todo = Sprint(&ss->sprinter, (*lval == 'O') ? "{" : "["); + } + break; + + case JSOP_ENDINIT: + rval = POP_STR(); + sn = js_GetSrcNote(jp->script, pc); + todo = Sprint(&ss->sprinter, "%s%s%c", + rval, + (sn && SN_TYPE(sn) == SRC_CONTINUE) ? ", " : "", + (*rval == '{') ? '}' : ']'); + break; + + case JSOP_INITPROP: + case JSOP_INITCATCHVAR: + atom = GET_ATOM(cx, jp->script, pc); + xval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), + ATOM_KEYWORD(atom) ? '\'' : 0); + if (!xval) + return JS_FALSE; + rval = POP_STR(); + lval = POP_STR(); + do_initprop: +#ifdef OLD_GETTER_SETTER + todo = Sprint(&ss->sprinter, "%s%s%s%s%s:%s", + lval, + (lval[1] != '\0') ? ", " : "", + xval, + (lastop == JSOP_GETTER || lastop == JSOP_SETTER) + ? " " : "", + (lastop == JSOP_GETTER) ? js_getter_str : + (lastop == JSOP_SETTER) ? js_setter_str : + "", + rval); +#else + if (lastop == JSOP_GETTER || lastop == JSOP_SETTER) { + todo = Sprint(&ss->sprinter, "%s%s%s %s%s", + lval, + (lval[1] != '\0') ? ", " : "", + (lastop == JSOP_GETTER) + ? js_get_str : js_set_str, + xval, + rval + strlen(js_function_str) + 1); + } else { + todo = Sprint(&ss->sprinter, "%s%s%s:%s", + lval, + (lval[1] != '\0') ? ", " : "", + xval, + rval); + } +#endif + break; + + case JSOP_INITELEM: + rval = POP_STR(); + xval = POP_STR(); + lval = POP_STR(); + sn = js_GetSrcNote(jp->script, pc); + if (sn && SN_TYPE(sn) == SRC_LABEL) + goto do_initprop; + todo = Sprint(&ss->sprinter, "%s%s%s", + lval, + (lval[1] != '\0' || *xval != '0') ? ", " : "", + rval); + break; + +#if JS_HAS_SHARP_VARS + case JSOP_DEFSHARP: + i = (jsint) GET_ATOM_INDEX(pc); + rval = POP_STR(); + todo = Sprint(&ss->sprinter, "#%u=%s", (unsigned) i, rval); + break; + + case JSOP_USESHARP: + i = (jsint) GET_ATOM_INDEX(pc); + todo = Sprint(&ss->sprinter, "#%u#", (unsigned) i); + break; +#endif /* JS_HAS_SHARP_VARS */ +#endif /* JS_HAS_INITIALIZERS */ + +#if JS_HAS_DEBUGGER_KEYWORD + case JSOP_DEBUGGER: + js_printf(jp, "\tdebugger;\n"); + todo = -2; + break; +#endif /* JS_HAS_DEBUGGER_KEYWORD */ + + default: + todo = -2; + break; + } + } + + if (todo < 0) { + /* -2 means "don't push", -1 means reported error. */ + if (todo == -1) + return JS_FALSE; + } else { + if (!PushOff(ss, todo, op)) + return JS_FALSE; + } + pc += len; + } + +/* + * Undefine local macros. + */ +#undef DECOMPILE_CODE +#undef POP_STR +#undef LOCAL_ASSERT +#undef GET_ATOM_QUOTE_AND_FMT + + return JS_TRUE; +} + + +JSBool +js_DecompileCode(JSPrinter *jp, JSScript *script, jsbytecode *pc, uintN len) +{ + SprintStack ss; + JSContext *cx; + void *mark, *space; + size_t offsetsz, opcodesz; + JSBool ok; + JSScript *oldscript; + char *last; + + /* Initialize a sprinter for use with the offset stack. */ + ss.printer = jp; + cx = jp->sprinter.context; + mark = JS_ARENA_MARK(&cx->tempPool); + INIT_SPRINTER(cx, &ss.sprinter, &cx->tempPool, PAREN_SLOP); + + /* Allocate the parallel (to avoid padding) offset and opcode stacks. */ + offsetsz = script->depth * sizeof(ptrdiff_t); + opcodesz = script->depth * sizeof(jsbytecode); + JS_ARENA_ALLOCATE(space, &cx->tempPool, offsetsz + opcodesz); + if (!space) { + ok = JS_FALSE; + goto out; + } + ss.offsets = (ptrdiff_t *) space; + ss.opcodes = (jsbytecode *) ((char *)space + offsetsz); + ss.top = 0; + + /* Call recursive subroutine to do the hard work. */ + oldscript = jp->script; + jp->script = script; + ok = Decompile(&ss, pc, len); + jp->script = oldscript; + + /* If the given code didn't empty the stack, do it now. */ + if (ss.top) { + do { + last = OFF2STR(&ss.sprinter, PopOff(&ss, JSOP_NOP)); + } while (ss.top); + js_printf(jp, "%s", last); + } + +out: + /* Free all temporary stuff allocated under this call. */ + JS_ARENA_RELEASE(&cx->tempPool, mark); + return ok; +} + +JSBool +js_DecompileScript(JSPrinter *jp, JSScript *script) +{ + return js_DecompileCode(jp, script, script->code, (uintN)script->length); +} + +static const char native_code_str[] = "\t[native code]\n"; + +JSBool +js_DecompileFunctionBody(JSPrinter *jp, JSFunction *fun) +{ + JSScript *script; + JSScope *scope, *save; + JSBool ok; + + script = fun->script; + if (!script) { + js_printf(jp, native_code_str); + return JS_TRUE; + } + scope = fun->object ? OBJ_SCOPE(fun->object) : NULL; + save = jp->scope; + jp->scope = scope; + ok = js_DecompileCode(jp, script, script->code, (uintN)script->length); + jp->scope = save; + return ok; +} + +JSBool +js_DecompileFunction(JSPrinter *jp, JSFunction *fun) +{ + JSContext *cx; + uintN i, nargs, indent; + void *mark; + JSAtom **params; + JSScope *scope, *oldscope; + JSScopeProperty *sprop; + JSBool ok; + + /* + * If pretty, conform to ECMA-262 Edition 3, 15.3.4.2, by decompiling a + * FunctionDeclaration. Otherwise, check the JSFUN_LAMBDA flag and force + * an expression by parenthesizing. + */ + if (jp->pretty) { + js_puts(jp, "\n"); + js_printf(jp, "\t"); + } else { + if (fun->flags & JSFUN_LAMBDA) + js_puts(jp, "("); + } + if (fun->flags & JSFUN_GETTER) + js_printf(jp, "%s ", js_getter_str); + else if (fun->flags & JSFUN_SETTER) + js_printf(jp, "%s ", js_setter_str); + + js_printf(jp, "%s ", js_function_str); + if (fun->atom && !QuoteString(&jp->sprinter, ATOM_TO_STRING(fun->atom), 0)) + return JS_FALSE; + js_puts(jp, "("); + + if (fun->script && fun->object) { + /* + * Print the parameters. + * + * This code is complicated by the need to handle duplicate parameter + * names, as required by ECMA (bah!). A duplicate parameter is stored + * as another node with the same id (the parameter name) but different + * shortid (the argument index) along the property tree ancestor line + * starting at SCOPE_LAST_PROP(scope). Only the last duplicate param + * is mapped by the scope's hash table. + */ + cx = jp->sprinter.context; + nargs = fun->nargs; + mark = JS_ARENA_MARK(&cx->tempPool); + JS_ARENA_ALLOCATE_CAST(params, JSAtom **, &cx->tempPool, + nargs * sizeof(JSAtom *)); + if (!params) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + scope = OBJ_SCOPE(fun->object); + for (sprop = SCOPE_LAST_PROP(scope); sprop; sprop = sprop->parent) { + if (sprop->getter != js_GetArgument) + continue; + JS_ASSERT(sprop->flags & SPROP_HAS_SHORTID); + JS_ASSERT((uintN) sprop->shortid < nargs); + JS_ASSERT(!JSVAL_IS_INT(sprop->id)); + params[(uintN) sprop->shortid] = (JSAtom *) sprop->id; + } + for (i = 0; i < nargs; i++) { + if (i > 0) + js_puts(jp, ", "); + if (!QuoteString(&jp->sprinter, ATOM_TO_STRING(params[i]), 0)) + return JS_FALSE; + } + JS_ARENA_RELEASE(&cx->tempPool, mark); +#ifdef __GNUC__ + } else { + scope = NULL; +#endif + } + + js_printf(jp, ") {\n"); + indent = jp->indent; + jp->indent += 4; + if (fun->script && fun->object) { + oldscope = jp->scope; + jp->scope = scope; + ok = js_DecompileScript(jp, fun->script); + jp->scope = oldscope; + if (!ok) { + jp->indent = indent; + return JS_FALSE; + } + } else { + js_printf(jp, native_code_str); + } + jp->indent -= 4; + js_printf(jp, "\t}"); + + if (jp->pretty) { + js_puts(jp, "\n"); + } else { + if (fun->flags & JSFUN_LAMBDA) + js_puts(jp, ")"); + } + return JS_TRUE; +} + +JSString * +js_DecompileValueGenerator(JSContext *cx, intN spindex, jsval v, + JSString *fallback) +{ + JSStackFrame *fp, *down; + jsbytecode *pc, *begin, *end, *tmp; + jsval *sp, *base, *limit; + JSScript *script; + JSOp op; + const JSCodeSpec *cs; + uint32 format, mode; + intN depth; + jssrcnote *sn; + uintN len, off; + JSPrinter *jp; + JSString *name; + + fp = cx->fp; + if (!fp) + goto do_fallback; + + /* Try to find sp's generating pc depth slots under it on the stack. */ + pc = fp->pc; + if (spindex == JSDVG_SEARCH_STACK) { + if (!pc) { + /* + * Current frame is native: look under it for a scripted call + * in which a decompilable bytecode string that generated the + * value as an actual argument might exist. + */ + JS_ASSERT(!fp->script && fp->fun && fp->fun->native); + down = fp->down; + if (!down) + goto do_fallback; + script = down->script; + base = fp->argv; + limit = base + fp->argc; + } else { + /* + * This should be a script activation, either a top-level + * script or a scripted function. But be paranoid about calls + * to js_DecompileValueGenerator from code that hasn't fully + * initialized a (default-all-zeroes) frame. + */ + script = fp->script; + base = fp->spbase; + limit = fp->sp; + } + + /* + * Pure paranoia about default-zeroed frames being active while + * js_DecompileValueGenerator is called. It can't hurt much now; + * error reporting performance is not an issue. + */ + if (!script || !base || !limit) + goto do_fallback; + + /* + * Try to find operand-generating pc depth slots below sp. + * + * In the native case, we know the arguments have generating pc's + * under them, on account of fp->down->script being non-null: all + * compiled scripts get depth slots for generating pc's allocated + * upon activation, at the top of js_Interpret. + * + * In the script or scripted function case, the same reasoning + * applies to fp rather than to fp->down. + */ + for (sp = base; sp < limit; sp++) { + if (*sp == v) { + depth = (intN)script->depth; + pc = (jsbytecode *) sp[-depth]; + break; + } + } + } else { + /* + * At this point, pc may or may not be null, i.e., we could be in + * a script activation, or we could be in a native frame that was + * called by another native function. Check pc and script. + */ + if (!pc) + goto do_fallback; + script = fp->script; + if (!script) + goto do_fallback; + + if (spindex != JSDVG_IGNORE_STACK) { + JS_ASSERT(spindex < 0); + depth = (intN)script->depth; +#if !JS_HAS_NO_SUCH_METHOD + JS_ASSERT(-depth <= spindex); +#endif + spindex -= depth; + + base = (jsval *) cx->stackPool.current->base; + limit = (jsval *) cx->stackPool.current->avail; + sp = fp->sp + spindex; + if (JS_UPTRDIFF(sp, base) < JS_UPTRDIFF(limit, base)) + pc = (jsbytecode *) *sp; + } + } + + /* + * Again, be paranoid, this time about possibly loading an invalid pc + * from sp[-(1+depth)]. + */ + if (JS_UPTRDIFF(pc, script->code) >= (jsuword)script->length) { + pc = fp->pc; + if (!pc) + goto do_fallback; + } + op = (JSOp) *pc; + if (op == JSOP_TRAP) + op = JS_GetTrapOpcode(cx, script, pc); + + /* XXX handle null as a special case, to avoid calling null "object" */ + if (op == JSOP_NULL) + return ATOM_TO_STRING(cx->runtime->atomState.nullAtom); + + cs = &js_CodeSpec[op]; + format = cs->format; + mode = (format & JOF_MODEMASK); + + /* NAME ops are self-contained, but others require left context. */ + if (mode == JOF_NAME) { + begin = pc; + } else { + sn = js_GetSrcNote(script, pc); + if (!sn || SN_TYPE(sn) != SRC_PCBASE) + goto do_fallback; + begin = pc - js_GetSrcNoteOffset(sn, 0); + } + end = pc + cs->length; + len = PTRDIFF(end, begin, jsbytecode); + + if (format & (JOF_SET | JOF_DEL | JOF_INCDEC | JOF_IMPORT | JOF_FOR)) { + tmp = (jsbytecode *) JS_malloc(cx, len * sizeof(jsbytecode)); + if (!tmp) + return NULL; + memcpy(tmp, begin, len * sizeof(jsbytecode)); + if (mode == JOF_NAME) { + tmp[0] = JSOP_NAME; + } else { + /* + * We must replace the faulting pc's bytecode with a corresponding + * JSOP_GET* code. For JSOP_SET{PROP,ELEM}, we must use the "2nd" + * form of JSOP_GET{PROP,ELEM}, to throw away the assignment op's + * right-hand operand and decompile it as if it were a GET of its + * left-hand operand. + */ + off = len - cs->length; + JS_ASSERT(off == (uintN) PTRDIFF(pc, begin, jsbytecode)); + if (mode == JOF_PROP) { + tmp[off] = (format & JOF_SET) ? JSOP_GETPROP2 : JSOP_GETPROP; + } else if (mode == JOF_ELEM) { + tmp[off] = (format & JOF_SET) ? JSOP_GETELEM2 : JSOP_GETELEM; + } else { + /* + * A zero mode means precisely that op is uncategorized for our + * purposes, so we must write per-op special case code here. + */ + switch (op) { + case JSOP_ENUMELEM: + tmp[off] = JSOP_GETELEM; + break; +#if JS_HAS_LVALUE_RETURN + case JSOP_SETCALL: + tmp[off] = JSOP_CALL; + break; +#endif + default: + JS_ASSERT(0); + } + } + } + begin = tmp; + } else { + /* No need to revise script bytecode. */ + tmp = NULL; + } + + jp = js_NewPrinter(cx, "js_DecompileValueGenerator", 0, JS_FALSE); + if (jp && js_DecompileCode(jp, script, begin, len)) + name = js_GetPrinterOutput(jp); + else + name = NULL; + js_DestroyPrinter(jp); + if (tmp) + JS_free(cx, tmp); + return name; + + do_fallback: + return fallback ? fallback : js_ValueToString(cx, v); +} diff --git a/src/dom/js/jsopcode.h b/src/dom/js/jsopcode.h new file mode 100644 index 000000000..e24fb2bf8 --- /dev/null +++ b/src/dom/js/jsopcode.h @@ -0,0 +1,273 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsopcode_h___ +#define jsopcode_h___ +/* + * JS bytecode definitions. + */ +#include +#include "jsprvtd.h" +#include "jspubtd.h" + +JS_BEGIN_EXTERN_C + +/* + * JS operation bytecodes. + */ +typedef enum JSOp { +#define OPDEF(op,val,name,token,length,nuses,ndefs,prec,format) \ + op = val, +#include "jsopcode.tbl" +#undef OPDEF + JSOP_LIMIT +} JSOp; + +/* + * JS bytecode formats. + */ +#define JOF_BYTE 0 /* single bytecode, no immediates */ +#define JOF_JUMP 1 /* signed 16-bit jump offset immediate */ +#define JOF_CONST 2 /* unsigned 16-bit constant pool index */ +#define JOF_UINT16 3 /* unsigned 16-bit immediate operand */ +#define JOF_TABLESWITCH 4 /* table switch */ +#define JOF_LOOKUPSWITCH 5 /* lookup switch */ +#define JOF_QARG 6 /* quickened get/set function argument ops */ +#define JOF_QVAR 7 /* quickened get/set local variable ops */ +#define JOF_DEFLOCALVAR 8 /* define local var with initial value */ +#define JOF_JUMPX 9 /* signed 32-bit jump offset immediate */ +#define JOF_TABLESWITCHX 10 /* extended (32-bit offset) table switch */ +#define JOF_LOOKUPSWITCHX 11 /* extended (32-bit offset) lookup switch */ +#define JOF_TYPEMASK 0x000f /* mask for above immediate types */ +#define JOF_NAME 0x0010 /* name operation */ +#define JOF_PROP 0x0020 /* obj.prop operation */ +#define JOF_ELEM 0x0030 /* obj[index] operation */ +#define JOF_MODEMASK 0x0030 /* mask for above addressing modes */ +#define JOF_SET 0x0040 /* set (i.e., assignment) operation */ +#define JOF_DEL 0x0080 /* delete operation */ +#define JOF_DEC 0x0100 /* decrement (--, not ++) opcode */ +#define JOF_INC 0x0200 /* increment (++, not --) opcode */ +#define JOF_INCDEC 0x0300 /* increment or decrement opcode */ +#define JOF_POST 0x0400 /* postorder increment or decrement */ +#define JOF_IMPORT 0x0800 /* import property op */ +#define JOF_FOR 0x1000 /* for-in property op */ +#define JOF_ASSIGNING JOF_SET /* hint for JSClass.resolve, used for ops + that do simplex assignment */ +#define JOF_BACKPATCH 0x4000 /* backpatch placeholder during codegen */ +#define JOF_LEFTASSOC 0x8000 /* left-associative operator */ + +#define JOF_TYPE_IS_EXTENDED_JUMP(t) \ + ((unsigned)((t) - JOF_JUMPX) <= (unsigned)(JOF_LOOKUPSWITCHX - JOF_JUMPX)) + +/* + * Immediate operand getters, setters, and bounds. + */ + +/* Short (2-byte signed offset) relative jump macros. */ +#define JUMP_OFFSET_LEN 2 +#define JUMP_OFFSET_HI(off) ((jsbytecode)((off) >> 8)) +#define JUMP_OFFSET_LO(off) ((jsbytecode)(off)) +#define GET_JUMP_OFFSET(pc) ((int16)(((pc)[1] << 8) | (pc)[2])) +#define SET_JUMP_OFFSET(pc,off) ((pc)[1] = JUMP_OFFSET_HI(off), \ + (pc)[2] = JUMP_OFFSET_LO(off)) +#define JUMP_OFFSET_MIN ((int16)0x8000) +#define JUMP_OFFSET_MAX ((int16)0x7fff) + +/* + * When a short jump won't hold a relative offset, its 2-byte immediate offset + * operand is an unsigned index of a span-dependency record, maintained until + * code generation finishes -- after which some (but we hope not nearly all) + * span-dependent jumps must be extended (see OptimizeSpanDeps in jsemit.c). + * + * If the span-dependency record index overflows SPANDEP_INDEX_MAX, the jump + * offset will contain SPANDEP_INDEX_HUGE, indicating that the record must be + * found (via binary search) by its "before span-dependency optimization" pc + * offset (from script main entry point). + */ +#define GET_SPANDEP_INDEX(pc) ((uint16)(((pc)[1] << 8) | (pc)[2])) +#define SET_SPANDEP_INDEX(pc,i) ((pc)[1] = JUMP_OFFSET_HI(i), \ + (pc)[2] = JUMP_OFFSET_LO(i)) +#define SPANDEP_INDEX_MAX ((uint16)0xfffe) +#define SPANDEP_INDEX_HUGE ((uint16)0xffff) + +/* Ultimately, if short jumps won't do, emit long (4-byte signed) offsets. */ +#define JUMPX_OFFSET_LEN 4 +#define JUMPX_OFFSET_B3(off) ((jsbytecode)((off) >> 24)) +#define JUMPX_OFFSET_B2(off) ((jsbytecode)((off) >> 16)) +#define JUMPX_OFFSET_B1(off) ((jsbytecode)((off) >> 8)) +#define JUMPX_OFFSET_B0(off) ((jsbytecode)(off)) +#define GET_JUMPX_OFFSET(pc) ((int32)(((pc)[1] << 24) | ((pc)[2] << 16) \ + | ((pc)[3] << 8) | (pc)[4])) +#define SET_JUMPX_OFFSET(pc,off)((pc)[1] = JUMPX_OFFSET_B3(off), \ + (pc)[2] = JUMPX_OFFSET_B2(off), \ + (pc)[3] = JUMPX_OFFSET_B1(off), \ + (pc)[4] = JUMPX_OFFSET_B0(off)) +#define JUMPX_OFFSET_MIN ((int32)0x80000000) +#define JUMPX_OFFSET_MAX ((int32)0x7fffffff) + +/* A literal is indexed by a per-script atom map. */ +#define ATOM_INDEX_LEN 2 +#define ATOM_INDEX_HI(index) ((jsbytecode)((index) >> 8)) +#define ATOM_INDEX_LO(index) ((jsbytecode)(index)) +#define GET_ATOM_INDEX(pc) ((jsatomid)(((pc)[1] << 8) | (pc)[2])) +#define SET_ATOM_INDEX(pc,index)((pc)[1] = ATOM_INDEX_HI(index), \ + (pc)[2] = ATOM_INDEX_LO(index)) +#define GET_ATOM(cx,script,pc) js_GetAtom((cx), &(script)->atomMap, \ + GET_ATOM_INDEX(pc)) +#define ATOM_INDEX_LIMIT_LOG2 16 +#define ATOM_INDEX_LIMIT ((uint32)1 << ATOM_INDEX_LIMIT_LOG2) + +/* Actual argument count operand format helpers. */ +#define ARGC_HI(argc) ((jsbytecode)((argc) >> 8)) +#define ARGC_LO(argc) ((jsbytecode)(argc)) +#define GET_ARGC(pc) ((uintN)(((pc)[1] << 8) | (pc)[2])) +#define ARGC_LIMIT ((uint32)1 << 16) + +/* Synonyms for quick JOF_QARG and JOF_QVAR bytecodes. */ +#define GET_ARGNO(pc) GET_ARGC(pc) +#define SET_ARGNO(pc,argno) SET_JUMP_OFFSET(pc,argno) +#define ARGNO_LEN JUMP_OFFSET_LEN +#define GET_VARNO(pc) GET_ARGC(pc) +#define SET_VARNO(pc,varno) SET_JUMP_OFFSET(pc,varno) +#define VARNO_LEN JUMP_OFFSET_LEN + +struct JSCodeSpec { + const char *name; /* JS bytecode name */ + const char *token; /* JS source literal or null */ + int8 length; /* length including opcode byte */ + int8 nuses; /* arity, -1 if variadic */ + int8 ndefs; /* number of stack results */ + uint8 prec; /* operator precedence */ + uint32 format; /* immediate operand format */ +}; + +extern const char js_const_str[]; +extern const char js_var_str[]; +extern const char js_function_str[]; +extern const char js_in_str[]; +extern const char js_instanceof_str[]; +extern const char js_new_str[]; +extern const char js_delete_str[]; +extern const char js_typeof_str[]; +extern const char js_void_str[]; +extern const char js_null_str[]; +extern const char js_this_str[]; +extern const char js_false_str[]; +extern const char js_true_str[]; +extern const JSCodeSpec js_CodeSpec[]; +extern uintN js_NumCodeSpecs; +extern const jschar js_EscapeMap[]; + +/* + * Return a GC'ed string containing the chars in str, with any non-printing + * chars or quotes (' or " as specified by the quote argument) escaped, and + * with the quote character at the beginning and end of the result string. + */ +extern JSString * +js_QuoteString(JSContext *cx, JSString *str, jschar quote); + +/* + * JSPrinter operations, for printf style message formatting. The return + * value from js_GetPrinterOutput() is the printer's cumulative output, in + * a GC'ed string. + */ +extern JSPrinter * +js_NewPrinter(JSContext *cx, const char *name, uintN indent, JSBool pretty); + +extern void +js_DestroyPrinter(JSPrinter *jp); + +extern JSString * +js_GetPrinterOutput(JSPrinter *jp); + +extern int +js_printf(JSPrinter *jp, const char *format, ...); + +extern JSBool +js_puts(JSPrinter *jp, const char *s); + +#ifdef DEBUG +/* + * Disassemblers, for debugging only. + */ +#include + +extern JS_FRIEND_API(void) +js_Disassemble(JSContext *cx, JSScript *script, JSBool lines, FILE *fp); + +extern JS_FRIEND_API(uintN) +js_Disassemble1(JSContext *cx, JSScript *script, jsbytecode *pc, uintN loc, + JSBool lines, FILE *fp); +#endif /* DEBUG */ + +/* + * Decompilers, for script, function, and expression pretty-printing. + */ +extern JSBool +js_DecompileCode(JSPrinter *jp, JSScript *script, jsbytecode *pc, uintN len); + +extern JSBool +js_DecompileScript(JSPrinter *jp, JSScript *script); + +extern JSBool +js_DecompileFunctionBody(JSPrinter *jp, JSFunction *fun); + +extern JSBool +js_DecompileFunction(JSPrinter *jp, JSFunction *fun); + +/* + * Find the source expression that resulted in v, and return a new string + * containing it. Fall back on v's string conversion (fallback) if we can't + * find the bytecode that generated and pushed v on the operand stack. + * + * Search the current stack frame if spindex is JSDVG_SEARCH_STACK. Don't + * look for v on the stack if spindex is JSDVG_IGNORE_STACK. Otherwise, + * spindex is the negative index of v, measured from cx->fp->sp, or from a + * lower frame's sp if cx->fp is native. + */ +extern JSString * +js_DecompileValueGenerator(JSContext *cx, intN spindex, jsval v, + JSString *fallback); + +#define JSDVG_IGNORE_STACK 0 +#define JSDVG_SEARCH_STACK 1 + +JS_END_EXTERN_C + +#endif /* jsopcode_h___ */ diff --git a/src/dom/js/jsopcode.tbl b/src/dom/js/jsopcode.tbl new file mode 100644 index 000000000..d4bad0fd9 --- /dev/null +++ b/src/dom/js/jsopcode.tbl @@ -0,0 +1,333 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JavaScript operation bytecodes. If you need to allocate a bytecode, look + * for a name of the form JSOP_UNUSED* and claim it. Otherwise, always add at + * the end of the table. + * + * Includers must define an OPDEF macro of the following form: + * + * #define OPDEF(op,val,name,image,length,nuses,ndefs,prec,format) ... + * + * Selected arguments can be expanded in initializers. The op argument is + * expanded followed by comma in the JSOp enum (jsopcode.h), e.g. The value + * field must be dense for now, because jsopcode.c uses an OPDEF() expansion + * inside the js_CodeSpec[] initializer. + * + * Field Description + * op Bytecode name, which is the JSOp enumerator name + * value Bytecode value, which is the JSOp enumerator value + * name C string containing name for disassembler + * image C string containing "image" for pretty-printer, null if ugly + * length Number of bytes including any immediate operands + * nuses Number of stack slots consumed by bytecode, -1 if variadic + * ndefs Number of stack slots produced by bytecode + * prec Operator precedence, zero if not an operator + * format Bytecode plus immediate operand encoding format + * + * This file is best viewed with 116 columns: +01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345 + */ + +/* legend: op val name image len use def prec format */ + +/* Longstanding JavaScript bytecodes. */ +OPDEF(JSOP_NOP, 0, "nop", NULL, 1, 0, 0, 0, JOF_BYTE) +OPDEF(JSOP_PUSH, 1, "push", NULL, 1, 0, 1, 0, JOF_BYTE) +OPDEF(JSOP_POPV, 2, "popv", NULL, 1, 1, 0, 0, JOF_BYTE) +OPDEF(JSOP_ENTERWITH, 3, "enterwith", NULL, 1, 1, 1, 0, JOF_BYTE) +OPDEF(JSOP_LEAVEWITH, 4, "leavewith", NULL, 1, 1, 0, 0, JOF_BYTE) +OPDEF(JSOP_RETURN, 5, "return", NULL, 1, 1, 0, 0, JOF_BYTE) +OPDEF(JSOP_GOTO, 6, "goto", NULL, 3, 0, 0, 0, JOF_JUMP) +OPDEF(JSOP_IFEQ, 7, "ifeq", NULL, 3, 1, 0, 0, JOF_JUMP) +OPDEF(JSOP_IFNE, 8, "ifne", NULL, 3, 1, 0, 0, JOF_JUMP) + +/* Get the arguments object for the current, lightweight function activation. */ +OPDEF(JSOP_ARGUMENTS, 9, js_arguments_str, js_arguments_str, 1, 0, 1, 12, JOF_BYTE) + +/* ECMA-compliant for-in loop with argument or local variable loop control. */ +OPDEF(JSOP_FORARG, 10, "forarg", NULL, 3, 0, 1, 0, JOF_QARG|JOF_NAME|JOF_FOR) +OPDEF(JSOP_FORVAR, 11, "forvar", NULL, 3, 0, 1, 0, JOF_QVAR|JOF_NAME|JOF_FOR) + +/* More longstanding bytecodes. */ +OPDEF(JSOP_DUP, 12, "dup", NULL, 1, 1, 2, 0, JOF_BYTE) +OPDEF(JSOP_DUP2, 13, "dup2", NULL, 1, 2, 4, 0, JOF_BYTE) +OPDEF(JSOP_SETCONST, 14, "setconst", NULL, 3, 1, 1, 1, JOF_CONST|JOF_NAME|JOF_SET|JOF_ASSIGNING) +OPDEF(JSOP_BITOR, 15, "bitor", "|", 1, 2, 1, 2, JOF_BYTE|JOF_LEFTASSOC) +OPDEF(JSOP_BITXOR, 16, "bitxor", "^", 1, 2, 1, 3, JOF_BYTE|JOF_LEFTASSOC) +OPDEF(JSOP_BITAND, 17, "bitand", "&", 1, 2, 1, 4, JOF_BYTE|JOF_LEFTASSOC) +OPDEF(JSOP_EQ, 18, "eq", "==", 1, 2, 1, 5, JOF_BYTE|JOF_LEFTASSOC) +OPDEF(JSOP_NE, 19, "ne", "!=", 1, 2, 1, 5, JOF_BYTE|JOF_LEFTASSOC) +OPDEF(JSOP_LT, 20, "lt", "<", 1, 2, 1, 6, JOF_BYTE|JOF_LEFTASSOC) +OPDEF(JSOP_LE, 21, "le", "<=", 1, 2, 1, 6, JOF_BYTE|JOF_LEFTASSOC) +OPDEF(JSOP_GT, 22, "gt", ">", 1, 2, 1, 6, JOF_BYTE|JOF_LEFTASSOC) +OPDEF(JSOP_GE, 23, "ge", ">=", 1, 2, 1, 6, JOF_BYTE|JOF_LEFTASSOC) +OPDEF(JSOP_LSH, 24, "lsh", "<<", 1, 2, 1, 7, JOF_BYTE|JOF_LEFTASSOC) +OPDEF(JSOP_RSH, 25, "rsh", ">>", 1, 2, 1, 7, JOF_BYTE|JOF_LEFTASSOC) +OPDEF(JSOP_URSH, 26, "ursh", ">>>", 1, 2, 1, 7, JOF_BYTE|JOF_LEFTASSOC) +OPDEF(JSOP_ADD, 27, "add", "+", 1, 2, 1, 8, JOF_BYTE|JOF_LEFTASSOC) +OPDEF(JSOP_SUB, 28, "sub", "-", 1, 2, 1, 8, JOF_BYTE|JOF_LEFTASSOC) +OPDEF(JSOP_MUL, 29, "mul", "*", 1, 2, 1, 9, JOF_BYTE|JOF_LEFTASSOC) +OPDEF(JSOP_DIV, 30, "div", "/", 1, 2, 1, 9, JOF_BYTE|JOF_LEFTASSOC) +OPDEF(JSOP_MOD, 31, "mod", "%", 1, 2, 1, 9, JOF_BYTE|JOF_LEFTASSOC) +OPDEF(JSOP_NOT, 32, "not", "!", 1, 1, 1, 10, JOF_BYTE) +OPDEF(JSOP_BITNOT, 33, "bitnot", "~", 1, 1, 1, 10, JOF_BYTE) +OPDEF(JSOP_NEG, 34, "neg", "-", 1, 1, 1, 10, JOF_BYTE) +OPDEF(JSOP_NEW, 35, js_new_str, NULL, 3, -1, 1, 10, JOF_UINT16) +OPDEF(JSOP_DELNAME, 36, "delname", NULL, 3, 0, 1, 10, JOF_CONST|JOF_NAME|JOF_DEL) +OPDEF(JSOP_DELPROP, 37, "delprop", NULL, 3, 1, 1, 10, JOF_CONST|JOF_PROP|JOF_DEL) +OPDEF(JSOP_DELELEM, 38, "delelem", NULL, 1, 2, 1, 10, JOF_BYTE |JOF_ELEM|JOF_DEL) +OPDEF(JSOP_TYPEOF, 39, js_typeof_str,NULL, 1, 1, 1, 10, JOF_BYTE) +OPDEF(JSOP_VOID, 40, js_void_str, NULL, 1, 1, 1, 10, JOF_BYTE) +OPDEF(JSOP_INCNAME, 41, "incname", NULL, 3, 0, 1, 10, JOF_CONST|JOF_NAME|JOF_INC) +OPDEF(JSOP_INCPROP, 42, "incprop", NULL, 3, 1, 1, 10, JOF_CONST|JOF_PROP|JOF_INC) +OPDEF(JSOP_INCELEM, 43, "incelem", NULL, 1, 2, 1, 10, JOF_BYTE |JOF_ELEM|JOF_INC) +OPDEF(JSOP_DECNAME, 44, "decname", NULL, 3, 0, 1, 10, JOF_CONST|JOF_NAME|JOF_DEC) +OPDEF(JSOP_DECPROP, 45, "decprop", NULL, 3, 1, 1, 10, JOF_CONST|JOF_PROP|JOF_DEC) +OPDEF(JSOP_DECELEM, 46, "decelem", NULL, 1, 2, 1, 10, JOF_BYTE |JOF_ELEM|JOF_DEC) +OPDEF(JSOP_NAMEINC, 47, "nameinc", NULL, 3, 0, 1, 10, JOF_CONST|JOF_NAME|JOF_INC|JOF_POST) +OPDEF(JSOP_PROPINC, 48, "propinc", NULL, 3, 1, 1, 10, JOF_CONST|JOF_PROP|JOF_INC|JOF_POST) +OPDEF(JSOP_ELEMINC, 49, "eleminc", NULL, 1, 2, 1, 10, JOF_BYTE |JOF_ELEM|JOF_INC|JOF_POST) +OPDEF(JSOP_NAMEDEC, 50, "namedec", NULL, 3, 0, 1, 10, JOF_CONST|JOF_NAME|JOF_DEC|JOF_POST) +OPDEF(JSOP_PROPDEC, 51, "propdec", NULL, 3, 1, 1, 10, JOF_CONST|JOF_PROP|JOF_DEC|JOF_POST) +OPDEF(JSOP_ELEMDEC, 52, "elemdec", NULL, 1, 2, 1, 10, JOF_BYTE |JOF_ELEM|JOF_DEC|JOF_POST) +OPDEF(JSOP_GETPROP, 53, "getprop", NULL, 3, 1, 1, 11, JOF_CONST|JOF_PROP) +OPDEF(JSOP_SETPROP, 54, "setprop", NULL, 3, 2, 1, 1, JOF_CONST|JOF_PROP|JOF_SET|JOF_ASSIGNING) +OPDEF(JSOP_GETELEM, 55, "getelem", NULL, 1, 2, 1, 11, JOF_BYTE |JOF_ELEM|JOF_LEFTASSOC) +OPDEF(JSOP_SETELEM, 56, "setelem", NULL, 1, 3, 1, 1, JOF_BYTE |JOF_ELEM|JOF_SET|JOF_ASSIGNING) +OPDEF(JSOP_PUSHOBJ, 57, "pushobj", NULL, 1, 0, 1, 0, JOF_BYTE) +OPDEF(JSOP_CALL, 58, "call", NULL, 3, -1, 1, 11, JOF_UINT16) +OPDEF(JSOP_NAME, 59, "name", NULL, 3, 0, 1, 12, JOF_CONST|JOF_NAME) +OPDEF(JSOP_NUMBER, 60, "number", NULL, 3, 0, 1, 12, JOF_CONST) +OPDEF(JSOP_STRING, 61, "string", NULL, 3, 0, 1, 12, JOF_CONST) +OPDEF(JSOP_ZERO, 62, "zero", "0", 1, 0, 1, 12, JOF_BYTE) +OPDEF(JSOP_ONE, 63, "one", "1", 1, 0, 1, 12, JOF_BYTE) +OPDEF(JSOP_NULL, 64, js_null_str, js_null_str, 1, 0, 1, 12, JOF_BYTE) +OPDEF(JSOP_THIS, 65, js_this_str, js_this_str, 1, 0, 1, 12, JOF_BYTE) +OPDEF(JSOP_FALSE, 66, js_false_str, js_false_str, 1, 0, 1, 12, JOF_BYTE) +OPDEF(JSOP_TRUE, 67, js_true_str, js_true_str, 1, 0, 1, 12, JOF_BYTE) +OPDEF(JSOP_OR, 68, "or", NULL, 3, 1, 0, 0, JOF_JUMP) +OPDEF(JSOP_AND, 69, "and", NULL, 3, 1, 0, 0, JOF_JUMP) + +/* The switch bytecodes have variable length. */ +OPDEF(JSOP_TABLESWITCH, 70, "tableswitch", NULL, -1, 1, 0, 0, JOF_TABLESWITCH) +OPDEF(JSOP_LOOKUPSWITCH, 71, "lookupswitch", NULL, -1, 1, 0, 0, JOF_LOOKUPSWITCH) + +/* New, infallible/transitive identity ops. */ +OPDEF(JSOP_NEW_EQ, 72, "eq", NULL, 1, 2, 1, 5, JOF_BYTE) +OPDEF(JSOP_NEW_NE, 73, "ne", NULL, 1, 2, 1, 5, JOF_BYTE) + +/* Lexical closure constructor. */ +OPDEF(JSOP_CLOSURE, 74, "closure", NULL, 3, 0, 0, 0, JOF_CONST) + +/* Export and import ops. */ +OPDEF(JSOP_EXPORTALL, 75, "exportall", NULL, 1, 0, 0, 0, JOF_BYTE) +OPDEF(JSOP_EXPORTNAME,76, "exportname", NULL, 3, 0, 0, 0, JOF_CONST|JOF_NAME) +OPDEF(JSOP_IMPORTALL, 77, "importall", NULL, 1, 1, 0, 0, JOF_BYTE) +OPDEF(JSOP_IMPORTPROP,78, "importprop", NULL, 3, 1, 0, 0, JOF_CONST|JOF_PROP|JOF_IMPORT) +OPDEF(JSOP_IMPORTELEM,79, "importelem", NULL, 1, 2, 0, 0, JOF_BYTE |JOF_ELEM|JOF_IMPORT) + +/* Push object literal. */ +OPDEF(JSOP_OBJECT, 80, "object", NULL, 3, 0, 1, 12, JOF_CONST) + +/* Pop value and discard it. */ +OPDEF(JSOP_POP, 81, "pop", NULL, 1, 1, 0, 0, JOF_BYTE) + +/* Convert value to number, for unary +. */ +OPDEF(JSOP_POS, 82, "pos", "+", 1, 1, 1, 10, JOF_BYTE) + +/* Trap into debugger for breakpoint, etc. */ +OPDEF(JSOP_TRAP, 83, "trap", NULL, 1, 0, 0, 0, JOF_BYTE) + +/* Fast get/set ops for function arguments and local variables. */ +OPDEF(JSOP_GETARG, 84, "getarg", NULL, 3, 0, 1, 12, JOF_QARG |JOF_NAME) +OPDEF(JSOP_SETARG, 85, "setarg", NULL, 3, 1, 1, 1, JOF_QARG |JOF_NAME|JOF_SET|JOF_ASSIGNING) +OPDEF(JSOP_GETVAR, 86, "getvar", NULL, 3, 0, 1, 12, JOF_QVAR |JOF_NAME) +OPDEF(JSOP_SETVAR, 87, "setvar", NULL, 3, 1, 1, 1, JOF_QVAR |JOF_NAME|JOF_SET|JOF_ASSIGNING) + +/* Push unsigned 16-bit int constant. */ +OPDEF(JSOP_UINT16, 88, "uint16", NULL, 3, 0, 1, 12, JOF_UINT16) + +/* Object and array literal support. */ +OPDEF(JSOP_NEWINIT, 89, "newinit", NULL, 1, 2, 1, 10, JOF_BYTE) +OPDEF(JSOP_ENDINIT, 90, "endinit", NULL, 1, 0, 0, 0, JOF_BYTE) +OPDEF(JSOP_INITPROP, 91, "initprop", NULL, 3, 1, 0, 0, JOF_CONST|JOF_PROP) +OPDEF(JSOP_INITELEM, 92, "initelem", NULL, 1, 2, 0, 0, JOF_BYTE |JOF_ELEM) +OPDEF(JSOP_DEFSHARP, 93, "defsharp", NULL, 3, 0, 0, 0, JOF_UINT16) +OPDEF(JSOP_USESHARP, 94, "usesharp", NULL, 3, 0, 1, 0, JOF_UINT16) + +/* Fast inc/dec ops for args and local vars. */ +OPDEF(JSOP_INCARG, 95, "incarg", NULL, 3, 0, 1, 10, JOF_QARG |JOF_NAME|JOF_INC) +OPDEF(JSOP_INCVAR, 96, "incvar", NULL, 3, 0, 1, 10, JOF_QVAR |JOF_NAME|JOF_INC) +OPDEF(JSOP_DECARG, 97, "decarg", NULL, 3, 0, 1, 10, JOF_QARG |JOF_NAME|JOF_DEC) +OPDEF(JSOP_DECVAR, 98, "decvar", NULL, 3, 0, 1, 10, JOF_QVAR |JOF_NAME|JOF_DEC) +OPDEF(JSOP_ARGINC, 99, "arginc", NULL, 3, 0, 1, 10, JOF_QARG |JOF_NAME|JOF_INC|JOF_POST) +OPDEF(JSOP_VARINC, 100,"varinc", NULL, 3, 0, 1, 10, JOF_QVAR |JOF_NAME|JOF_INC|JOF_POST) +OPDEF(JSOP_ARGDEC, 101,"argdec", NULL, 3, 0, 1, 10, JOF_QARG |JOF_NAME|JOF_DEC|JOF_POST) +OPDEF(JSOP_VARDEC, 102,"vardec", NULL, 3, 0, 1, 10, JOF_QVAR |JOF_NAME|JOF_DEC|JOF_POST) + +/* ECMA-compliant for/in ops. */ +OPDEF(JSOP_TOOBJECT, 103,"toobject", NULL, 1, 1, 1, 0, JOF_BYTE) +OPDEF(JSOP_FORNAME, 104,"forname", NULL, 3, 0, 1, 0, JOF_CONST|JOF_NAME|JOF_FOR) +OPDEF(JSOP_FORPROP, 105,"forprop", NULL, 3, 1, 1, 0, JOF_CONST|JOF_PROP|JOF_FOR) +OPDEF(JSOP_FORELEM, 106,"forelem", NULL, 1, 2, 4, 0, JOF_BYTE |JOF_ELEM|JOF_FOR) +OPDEF(JSOP_POP2, 107,"pop2", NULL, 1, 2, 0, 0, JOF_BYTE) + +/* ECMA-compliant assignment ops. */ +OPDEF(JSOP_BINDNAME, 108,"bindname", NULL, 3, 0, 1, 0, JOF_CONST|JOF_NAME|JOF_SET|JOF_ASSIGNING) +OPDEF(JSOP_SETNAME, 109,"setname", NULL, 3, 2, 1, 1, JOF_CONST|JOF_NAME|JOF_SET|JOF_ASSIGNING) + +/* Exception handling ops. */ +OPDEF(JSOP_THROW, 110,"throw", NULL, 1, 1, 0, 0, JOF_BYTE) + +/* 'in' and 'instanceof' ops. */ +OPDEF(JSOP_IN, 111,js_in_str, js_in_str, 1, 2, 1, 6, JOF_BYTE|JOF_LEFTASSOC) +OPDEF(JSOP_INSTANCEOF,112,js_instanceof_str,js_instanceof_str,1,2,1,6,JOF_BYTE|JOF_LEFTASSOC) + +/* debugger op */ +OPDEF(JSOP_DEBUGGER, 113,"debugger", NULL, 1, 0, 0, 0, JOF_BYTE) + +/* gosub/retsub for finally handling */ +OPDEF(JSOP_GOSUB, 114,"gosub", NULL, 3, 0, 1, 0, JOF_JUMP) +OPDEF(JSOP_RETSUB, 115,"retsub", NULL, 1, 1, 0, 0, JOF_BYTE) + +/* More exception handling ops. */ +OPDEF(JSOP_EXCEPTION, 116,"exception", NULL, 1, 0, 1, 0, JOF_BYTE) +OPDEF(JSOP_SETSP, 117,"setsp", NULL, 3, 0, 0, 0, JOF_UINT16) + +/* + * ECMA-compliant switch statement ops. + * CONDSWITCH is a decompilable NOP; CASE is ===, POP, jump if true, re-push + * lval if false; and DEFAULT is POP lval and GOTO. + */ +OPDEF(JSOP_CONDSWITCH,118,"condswitch", NULL, 1, 0, 0, 0, JOF_BYTE) +OPDEF(JSOP_CASE, 119,"case", NULL, 3, 1, 0, 0, JOF_JUMP) +OPDEF(JSOP_DEFAULT, 120,"default", NULL, 3, 1, 0, 0, JOF_JUMP) + +/* + * ECMA-compliant call to eval op + */ +OPDEF(JSOP_EVAL, 121,"eval", NULL, 3, -1, 1, 11, JOF_UINT16) + +/* + * ECMA-compliant helper for 'for (x[i] in o)' loops. + */ +OPDEF(JSOP_ENUMELEM, 122,"enumelem", NULL, 1, 3, 0, 1, JOF_BYTE |JOF_SET|JOF_ASSIGNING) + +/* + * Getter and setter prefix bytecodes. These modify the next bytecode, either + * an assignment or a property initializer code, which then defines a property + * getter or setter. + */ +OPDEF(JSOP_GETTER, 123,js_getter_str,js_getter_str,1, 0, 0, 0, JOF_BYTE) +OPDEF(JSOP_SETTER, 124,js_setter_str,js_setter_str,1, 0, 0, 0, JOF_BYTE) + +/* + * Prolog bytecodes for defining function, var, and const names. + */ +OPDEF(JSOP_DEFFUN, 125,"deffun", NULL, 3, 0, 0, 0, JOF_CONST) +OPDEF(JSOP_DEFCONST, 126,"defconst", NULL, 3, 0, 0, 0, JOF_CONST|JOF_NAME) +OPDEF(JSOP_DEFVAR, 127,"defvar", NULL, 3, 0, 0, 0, JOF_CONST|JOF_NAME) + +/* Auto-clone (if needed due to re-parenting) and push an anonymous function. */ +OPDEF(JSOP_ANONFUNOBJ, 128, "anonfunobj", NULL, 3, 0, 1, 12, JOF_CONST) + +/* ECMA ed. 3 named function expression. */ +OPDEF(JSOP_NAMEDFUNOBJ, 129, "namedfunobj", NULL, 3, 0, 1, 12, JOF_CONST) + +/* Like JSOP_INITPROP, but specialized to make a DontDelete property for ECMA ed. 3 catch variables. */ +OPDEF(JSOP_INITCATCHVAR,130, "initcatchvar",NULL, 3, 1, 0, 0, JOF_CONST|JOF_PROP) + +/* ECMA-mandated parenthesization opcode, which nulls the reference base register, obj; see jsinterp.c. */ +OPDEF(JSOP_GROUP, 131, "group", NULL, 1, 0, 0, 0, JOF_BYTE) + +/* Host object extension: given 'o.item(i) = j', the left-hand side compiles JSOP_SETCALL, rather than JSOP_CALL. */ +OPDEF(JSOP_SETCALL, 132, "setcall", NULL, 3, -1, 2, 11, JOF_UINT16|JOF_SET|JOF_ASSIGNING) + +/* + * Exception handling no-ops, for more economical byte-coding than SRC_TRYFIN + * srcnote-annotated JSOP_NOPs. + */ +OPDEF(JSOP_TRY, 133,"try", NULL, 1, 0, 0, 0, JOF_BYTE) +OPDEF(JSOP_FINALLY, 134,"finally", NULL, 1, 0, 0, 0, JOF_BYTE) + +/* + * Swap the top two stack elements. + * N.B. JSOP_SWAP doesn't swap the corresponding pc stack generating pcs, as + * they're not needed for the current use of preserving the top-of-stack return + * value when popping scopes while returning from catch blocks. + */ +OPDEF(JSOP_SWAP, 135,"swap", NULL, 1, 2, 2, 0, JOF_BYTE) + +/* + * Bytecodes that avoid making an arguments object in most cases: + * JSOP_ARGSUB gets arguments[i] from fp->argv, iff i is in [0, fp->argc-1]. + * JSOP_ARGCNT returns fp->argc. + */ +OPDEF(JSOP_ARGSUB, 136,"argsub", NULL, 3, 0, 1, 12, JOF_QARG |JOF_NAME) +OPDEF(JSOP_ARGCNT, 137,"argcnt", NULL, 1, 0, 1, 12, JOF_BYTE) + +/* + * Define a local function object as a local variable. + * The local variable's slot number is the first immediate two-byte operand. + * The function object's atom index is the second immediate operand. + */ +OPDEF(JSOP_DEFLOCALFUN, 138,"deflocalfun",NULL, 5, 0, 0, 0, JOF_DEFLOCALVAR) + +/* Extended jumps. */ +OPDEF(JSOP_GOTOX, 139,"gotox", NULL, 5, 0, 0, 0, JOF_JUMPX) +OPDEF(JSOP_IFEQX, 140,"ifeqx", NULL, 5, 1, 0, 0, JOF_JUMPX) +OPDEF(JSOP_IFNEX, 141,"ifnex", NULL, 5, 1, 0, 0, JOF_JUMPX) +OPDEF(JSOP_ORX, 142,"orx", NULL, 5, 1, 0, 0, JOF_JUMPX) +OPDEF(JSOP_ANDX, 143,"andx", NULL, 5, 1, 0, 0, JOF_JUMPX) +OPDEF(JSOP_GOSUBX, 144,"gosubx", NULL, 5, 0, 1, 0, JOF_JUMPX) +OPDEF(JSOP_CASEX, 145,"casex", NULL, 5, 1, 0, 0, JOF_JUMPX) +OPDEF(JSOP_DEFAULTX, 146,"defaultx", NULL, 5, 1, 0, 0, JOF_JUMPX) +OPDEF(JSOP_TABLESWITCHX, 147,"tableswitchx",NULL, -1, 1, 0, 0, JOF_TABLESWITCHX) +OPDEF(JSOP_LOOKUPSWITCHX, 148,"lookupswitchx",NULL, -1, 1, 0, 0, JOF_LOOKUPSWITCHX) + +/* Placeholders for a real jump opcode set during backpatch chain fixup. */ +OPDEF(JSOP_BACKPATCH, 149,"backpatch",NULL, 3, 0, 0, 0, JOF_JUMP|JOF_BACKPATCH) +OPDEF(JSOP_BACKPATCH_POP, 150,"backpatch_pop",NULL, 3, 1, 0, 0, JOF_JUMP|JOF_BACKPATCH) +OPDEF(JSOP_BACKPATCH_PUSH,151,"backpatch_push",NULL, 3, 0, 1, 0, JOF_JUMP|JOF_BACKPATCH) + +/* Set and get return value pseudo-register in stack frame. */ +OPDEF(JSOP_SETRVAL, 152,"setrval", NULL, 1, 1, 0, 0, JOF_BYTE) +OPDEF(JSOP_RETRVAL, 153,"retrval", NULL, 1, 0, 0, 0, JOF_BYTE) diff --git a/src/dom/js/jsosdep.h b/src/dom/js/jsosdep.h new file mode 100644 index 000000000..c93eee2f1 --- /dev/null +++ b/src/dom/js/jsosdep.h @@ -0,0 +1,127 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsosdep_h___ +#define jsosdep_h___ +/* + * OS (and machine, and compiler XXX) dependent information. + */ + +#if defined(XP_WIN) || defined(XP_OS2) + +#if defined(_WIN32) || defined (XP_OS2) +#define JS_HAVE_LONG_LONG +#else +#undef JS_HAVE_LONG_LONG +#endif +#endif /* XP_WIN || XP_OS2 */ + +#ifdef XP_MAC +#define JS_HAVE_LONG_LONG + +JS_BEGIN_EXTERN_C + +#include + +extern void* reallocSmaller(void* block, size_t newSize); + +extern char* strdup(const char* str); + +JS_END_EXTERN_C + +#endif /* XP_MAC */ + +#ifdef XP_BEOS +#define JS_HAVE_LONG_LONG +#endif + + +#ifdef XP_UNIX + +/* + * Get OS specific header information. + */ +#if defined(AIXV3) || defined(AIX) +#define JS_HAVE_LONG_LONG + +#elif defined(BSDI) +#define JS_HAVE_LONG_LONG + +#elif defined(HPUX) +#define JS_HAVE_LONG_LONG + +#elif defined(IRIX) +#define JS_HAVE_LONG_LONG + +#elif defined(linux) +#define JS_HAVE_LONG_LONG + +#elif defined(OSF1) +#define JS_HAVE_LONG_LONG + +#elif defined(_SCO_DS) +#undef JS_HAVE_LONG_LONG + +#elif defined(SOLARIS) +#define JS_HAVE_LONG_LONG + +#elif defined(FREEBSD) +#define JS_HAVE_LONG_LONG + +#elif defined(SUNOS4) +#undef JS_HAVE_LONG_LONG + +/* +** Missing function prototypes +*/ + +extern void *sbrk(int); + +#elif defined(UNIXWARE) +#undef JS_HAVE_LONG_LONG + +#elif defined(VMS) && defined(__ALPHA) +#define JS_HAVE_LONG_LONG + +#endif + +#endif /* XP_UNIX */ + +#endif /* jsosdep_h___ */ + diff --git a/src/dom/js/jsotypes.h b/src/dom/js/jsotypes.h new file mode 100644 index 000000000..ad8b5203e --- /dev/null +++ b/src/dom/js/jsotypes.h @@ -0,0 +1,211 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * This section typedefs the old 'native' types to the new PRs. + * These definitions are scheduled to be eliminated at the earliest + * possible time. The NSPR API is implemented and documented using + * the new definitions. + */ + +/* + * Note that we test for PROTYPES_H, not JSOTYPES_H. This is to avoid + * double-definitions of scalar types such as uint32, if NSPR's + * protypes.h is also included. + */ +#ifndef PROTYPES_H +#define PROTYPES_H + +#ifdef XP_BEOS +/* BeOS defines most int types in SupportDefs.h (int8, uint8, int16, + * uint16, int32, uint32, int64, uint64), so in the interest of + * not conflicting with other definitions elsewhere we have to skip the + * #ifdef jungle below, duplicate some definitions, and do our stuff. + */ +#include + +typedef JSUintn uintn; +#ifndef _XP_Core_ +typedef JSIntn intn; +#endif + +#else + +/* SVR4 typedef of uint is commonly found on UNIX machines. */ +#ifdef XP_UNIX +#include +#else +typedef JSUintn uint; +#endif + +typedef JSUintn uintn; +typedef JSUint64 uint64; +#if !defined(XP_MAC) && !defined(_WIN32) && !defined(XP_OS2) +typedef JSUint32 uint32; +#else +typedef unsigned long uint32; +#endif +typedef JSUint16 uint16; +typedef JSUint8 uint8; + +#ifndef _XP_Core_ +typedef JSIntn intn; +#endif + +/* + * On AIX 4.3, sys/inttypes.h (which is included by sys/types.h, a very + * common header file) defines the types int8, int16, int32, and int64. + * So we don't define these four types here to avoid conflicts in case + * the code also includes sys/types.h. + */ +#if defined(AIX) && defined(HAVE_SYS_INTTYPES_H) +#include +#else +typedef JSInt64 int64; + +/* /usr/include/model.h on HP-UX defines int8, int16, and int32 */ +#ifdef HPUX +#include +#else +#if !defined(XP_MAC) && !defined(_WIN32) && !defined(XP_OS2) +typedef JSInt32 int32; +#else +typedef long int32; +#endif +typedef JSInt16 int16; +typedef JSInt8 int8; +#endif /* HPUX */ +#endif /* AIX && HAVE_SYS_INTTYPES_H */ + +#endif /* XP_BEOS */ + +typedef JSFloat64 float64; + +/* Re: jsbit.h */ +#define TEST_BIT JS_TEST_BIT +#define SET_BIT JS_SET_BIT +#define CLEAR_BIT JS_CLEAR_BIT + +/* Re: prarena.h->plarena.h */ +#define PRArena PLArena +#define PRArenaPool PLArenaPool +#define PRArenaStats PLArenaStats +#define PR_ARENA_ALIGN PL_ARENA_ALIGN +#define PR_INIT_ARENA_POOL PL_INIT_ARENA_POOL +#define PR_ARENA_ALLOCATE PL_ARENA_ALLOCATE +#define PR_ARENA_GROW PL_ARENA_GROW +#define PR_ARENA_MARK PL_ARENA_MARK +#define PR_CLEAR_UNUSED PL_CLEAR_UNUSED +#define PR_CLEAR_ARENA PL_CLEAR_ARENA +#define PR_ARENA_RELEASE PL_ARENA_RELEASE +#define PR_COUNT_ARENA PL_COUNT_ARENA +#define PR_ARENA_DESTROY PL_ARENA_DESTROY +#define PR_InitArenaPool PL_InitArenaPool +#define PR_FreeArenaPool PL_FreeArenaPool +#define PR_FinishArenaPool PL_FinishArenaPool +#define PR_CompactArenaPool PL_CompactArenaPool +#define PR_ArenaFinish PL_ArenaFinish +#define PR_ArenaAllocate PL_ArenaAllocate +#define PR_ArenaGrow PL_ArenaGrow +#define PR_ArenaRelease PL_ArenaRelease +#define PR_ArenaCountAllocation PL_ArenaCountAllocation +#define PR_ArenaCountInplaceGrowth PL_ArenaCountInplaceGrowth +#define PR_ArenaCountGrowth PL_ArenaCountGrowth +#define PR_ArenaCountRelease PL_ArenaCountRelease +#define PR_ArenaCountRetract PL_ArenaCountRetract + +/* Re: prevent.h->plevent.h */ +#define PREvent PLEvent +#define PREventQueue PLEventQueue +#define PR_CreateEventQueue PL_CreateEventQueue +#define PR_DestroyEventQueue PL_DestroyEventQueue +#define PR_GetEventQueueMonitor PL_GetEventQueueMonitor +#define PR_ENTER_EVENT_QUEUE_MONITOR PL_ENTER_EVENT_QUEUE_MONITOR +#define PR_EXIT_EVENT_QUEUE_MONITOR PL_EXIT_EVENT_QUEUE_MONITOR +#define PR_PostEvent PL_PostEvent +#define PR_PostSynchronousEvent PL_PostSynchronousEvent +#define PR_GetEvent PL_GetEvent +#define PR_EventAvailable PL_EventAvailable +#define PREventFunProc PLEventFunProc +#define PR_MapEvents PL_MapEvents +#define PR_RevokeEvents PL_RevokeEvents +#define PR_ProcessPendingEvents PL_ProcessPendingEvents +#define PR_WaitForEvent PL_WaitForEvent +#define PR_EventLoop PL_EventLoop +#define PR_GetEventQueueSelectFD PL_GetEventQueueSelectFD +#define PRHandleEventProc PLHandleEventProc +#define PRDestroyEventProc PLDestroyEventProc +#define PR_InitEvent PL_InitEvent +#define PR_GetEventOwner PL_GetEventOwner +#define PR_HandleEvent PL_HandleEvent +#define PR_DestroyEvent PL_DestroyEvent +#define PR_DequeueEvent PL_DequeueEvent +#define PR_GetMainEventQueue PL_GetMainEventQueue + +/* Re: prhash.h->plhash.h */ +#define PRHashEntry PLHashEntry +#define PRHashTable PLHashTable +#define PRHashNumber PLHashNumber +#define PRHashFunction PLHashFunction +#define PRHashComparator PLHashComparator +#define PRHashEnumerator PLHashEnumerator +#define PRHashAllocOps PLHashAllocOps +#define PR_NewHashTable PL_NewHashTable +#define PR_HashTableDestroy PL_HashTableDestroy +#define PR_HashTableRawLookup PL_HashTableRawLookup +#define PR_HashTableRawAdd PL_HashTableRawAdd +#define PR_HashTableRawRemove PL_HashTableRawRemove +#define PR_HashTableAdd PL_HashTableAdd +#define PR_HashTableRemove PL_HashTableRemove +#define PR_HashTableEnumerateEntries PL_HashTableEnumerateEntries +#define PR_HashTableLookup PL_HashTableLookup +#define PR_HashTableDump PL_HashTableDump +#define PR_HashString PL_HashString +#define PR_CompareStrings PL_CompareStrings +#define PR_CompareValues PL_CompareValues + +#ifdef XP_MAC +#ifndef TRUE /* Mac standard is lower case true */ + #define TRUE 1 +#endif +#ifndef FALSE /* Mac standard is lower case false */ + #define FALSE 0 +#endif +#endif + +#endif /* !defined(PROTYPES_H) */ diff --git a/src/dom/js/jsparse.c b/src/dom/js/jsparse.c new file mode 100644 index 000000000..7cf18d454 --- /dev/null +++ b/src/dom/js/jsparse.c @@ -0,0 +1,3547 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS parser. + * + * This is a recursive-descent parser for the JavaScript language specified by + * "The JavaScript 1.5 Language Specification". It uses lexical and semantic + * feedback to disambiguate non-LL(1) structures. It generates trees of nodes + * induced by the recursive parsing (not precise syntax trees, see jsparse.h). + * After tree construction, it rewrites trees to fold constants and evaluate + * compile-time expressions. Finally, it calls js_EmitTree (see jsemit.h) to + * generate bytecode. + * + * This parser attempts no error recovery. The dense JSTokenType enumeration + * was designed with error recovery built on 64-bit first and follow bitsets + * in mind, however. + */ +#include "jsstddef.h" +#include +#include +#include +#include "jstypes.h" +#include "jsarena.h" /* Added by JSIFY */ +#include "jsutil.h" /* Added by JSIFY */ +#include "jsapi.h" +#include "jsatom.h" +#include "jscntxt.h" +#include "jsconfig.h" +#include "jsemit.h" +#include "jsfun.h" +#include "jsinterp.h" +#include "jslock.h" +#include "jsnum.h" +#include "jsobj.h" +#include "jsopcode.h" +#include "jsparse.h" +#include "jsscan.h" +#include "jsscope.h" +#include "jsscript.h" +#include "jsstr.h" + +/* + * JS parsers, from lowest to highest precedence. + * + * Each parser takes a context and a token stream, and emits bytecode using + * a code generator. + */ + +typedef JSParseNode * +JSParser(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc); + +typedef JSParseNode * +JSMemberParser(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, + JSBool allowCallSyntax); + +static JSParser FunctionStmt; +#if JS_HAS_LEXICAL_CLOSURE +static JSParser FunctionExpr; +#endif +static JSParser Statements; +static JSParser Statement; +static JSParser Variables; +static JSParser Expr; +static JSParser AssignExpr; +static JSParser CondExpr; +static JSParser OrExpr; +static JSParser AndExpr; +static JSParser BitOrExpr; +static JSParser BitXorExpr; +static JSParser BitAndExpr; +static JSParser EqExpr; +static JSParser RelExpr; +static JSParser ShiftExpr; +static JSParser AddExpr; +static JSParser MulExpr; +static JSParser UnaryExpr; +static JSMemberParser MemberExpr; +static JSParser PrimaryExpr; + +/* + * Insist that the next token be of type tt, or report errno and return null. + * NB: this macro uses cx and ts from its lexical environment. + */ +#define MUST_MATCH_TOKEN(tt, errno) \ + JS_BEGIN_MACRO \ + if (js_GetToken(cx, ts) != tt) { \ + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, errno); \ + return NULL; \ + } \ + JS_END_MACRO + +#define CHECK_RECURSION() \ + JS_BEGIN_MACRO \ + int stackDummy; \ + if (!JS_CHECK_STACK_SIZE(cx, stackDummy)) { \ + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, \ + JSMSG_OVER_RECURSED); \ + return NULL; \ + } \ + JS_END_MACRO + +#ifdef METER_PARSENODES +static uint32 parsenodes = 0; +static uint32 maxparsenodes = 0; +static uint32 recyclednodes = 0; +#endif + +static void +RecycleTree(JSParseNode *pn, JSTreeContext *tc) +{ + if (!pn) + return; + JS_ASSERT(pn != tc->nodeList); /* catch back-to-back dup recycles */ + pn->pn_next = tc->nodeList; + tc->nodeList = pn; +#ifdef METER_PARSENODES + recyclednodes++; +#endif +} + +static JSParseNode * +NewOrRecycledNode(JSContext *cx, JSTreeContext *tc) +{ + JSParseNode *pn; + + pn = tc->nodeList; + if (!pn) { + JS_ARENA_ALLOCATE_TYPE(pn, JSParseNode, &cx->tempPool); + if (!pn) + JS_ReportOutOfMemory(cx); + } else { + tc->nodeList = pn->pn_next; + + /* Recycle immediate descendents only, to save work and working set. */ + switch (pn->pn_arity) { + case PN_FUNC: + RecycleTree(pn->pn_body, tc); + break; + case PN_LIST: + if (pn->pn_head) { + /* XXX check for dup recycles in the list */ + *pn->pn_tail = tc->nodeList; + tc->nodeList = pn->pn_head; +#ifdef METER_PARSENODES + recyclednodes += pn->pn_count; +#endif + } + break; + case PN_TERNARY: + RecycleTree(pn->pn_kid1, tc); + RecycleTree(pn->pn_kid2, tc); + RecycleTree(pn->pn_kid3, tc); + break; + case PN_BINARY: + RecycleTree(pn->pn_left, tc); + RecycleTree(pn->pn_right, tc); + break; + case PN_UNARY: + RecycleTree(pn->pn_kid, tc); + break; + case PN_NAME: + RecycleTree(pn->pn_expr, tc); + break; + case PN_NULLARY: + break; + } + } + return pn; +} + +/* + * Allocate a JSParseNode from cx's temporary arena. + */ +static JSParseNode * +NewParseNode(JSContext *cx, JSToken *tok, JSParseNodeArity arity, + JSTreeContext *tc) +{ + JSParseNode *pn; + + pn = NewOrRecycledNode(cx, tc); + if (!pn) + return NULL; + pn->pn_type = tok->type; + pn->pn_pos = tok->pos; + pn->pn_op = JSOP_NOP; + pn->pn_arity = arity; + pn->pn_next = NULL; +#ifdef METER_PARSENODES + parsenodes++; + if (parsenodes - recyclednodes > maxparsenodes) + maxparsenodes = parsenodes - recyclednodes; +#endif + return pn; +} + +static JSParseNode * +NewBinary(JSContext *cx, JSTokenType tt, + JSOp op, JSParseNode *left, JSParseNode *right, + JSTreeContext *tc) +{ + JSParseNode *pn, *pn1, *pn2; + + if (!left || !right) + return NULL; + + /* + * Flatten a left-associative (left-heavy) tree of a given operator into + * a list, to reduce js_FoldConstants and js_EmitTree recursion. + */ + if (left->pn_type == tt && + left->pn_op == op && + (js_CodeSpec[op].format & JOF_LEFTASSOC)) { + if (left->pn_arity != PN_LIST) { + pn1 = left->pn_left, pn2 = left->pn_right; + left->pn_arity = PN_LIST; + PN_INIT_LIST_1(left, pn1); + PN_APPEND(left, pn2); + left->pn_extra = 0; + if (tt == TOK_PLUS) { + if (pn1->pn_type == TOK_STRING) + left->pn_extra |= PNX_STRCAT; + else if (pn1->pn_type != TOK_NUMBER) + left->pn_extra |= PNX_CANTFOLD; + if (pn2->pn_type == TOK_STRING) + left->pn_extra |= PNX_STRCAT; + else if (pn2->pn_type != TOK_NUMBER) + left->pn_extra |= PNX_CANTFOLD; + } + } + PN_APPEND(left, right); + left->pn_pos.end = right->pn_pos.end; + if (tt == TOK_PLUS) { + if (right->pn_type == TOK_STRING) + left->pn_extra |= PNX_STRCAT; + else if (right->pn_type != TOK_NUMBER) + left->pn_extra |= PNX_CANTFOLD; + } + return left; + } + + /* + * Fold constant addition immediately, to conserve node space and, what's + * more, so js_FoldConstants never sees mixed addition and concatenation + * operations with more than one leading non-string operand in a PN_LIST + * generated for expressions such as 1 + 2 + "pt" (which should evaluate + * to "3pt", not "12pt"). + */ + if (tt == TOK_PLUS && + left->pn_type == TOK_NUMBER && + right->pn_type == TOK_NUMBER) { + left->pn_dval += right->pn_dval; + RecycleTree(right, tc); + return left; + } + + pn = NewOrRecycledNode(cx, tc); + if (!pn) + return NULL; + pn->pn_type = tt; + pn->pn_pos.begin = left->pn_pos.begin; + pn->pn_pos.end = right->pn_pos.end; + pn->pn_op = op; + pn->pn_arity = PN_BINARY; + pn->pn_left = left; + pn->pn_right = right; + pn->pn_next = NULL; +#ifdef METER_PARSENODES + parsenodes++; + if (parsenodes - recyclednodes > maxparsenodes) + maxparsenodes = parsenodes - recyclednodes; +#endif + return pn; +} + +#if JS_HAS_GETTER_SETTER +static JSTokenType +CheckGetterOrSetter(JSContext *cx, JSTokenStream *ts, JSTokenType tt) +{ + JSAtom *atom; + JSRuntime *rt; + JSOp op; + const char *name; + + JS_ASSERT(CURRENT_TOKEN(ts).type == TOK_NAME); + atom = CURRENT_TOKEN(ts).t_atom; + rt = cx->runtime; + if (atom == rt->atomState.getterAtom) + op = JSOP_GETTER; + else if (atom == rt->atomState.setterAtom) + op = JSOP_SETTER; + else + return TOK_NAME; + if (js_PeekTokenSameLine(cx, ts) != tt) + return TOK_NAME; + (void) js_GetToken(cx, ts); + if (CURRENT_TOKEN(ts).t_op != JSOP_NOP) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_BAD_GETTER_OR_SETTER, + (op == JSOP_GETTER) + ? js_getter_str + : js_setter_str); + return TOK_ERROR; + } + CURRENT_TOKEN(ts).t_op = op; + name = js_AtomToPrintableString(cx, atom); + if (!name || + !js_ReportCompileErrorNumber(cx, ts, NULL, + JSREPORT_WARNING | + JSREPORT_STRICT, + JSMSG_DEPRECATED_USAGE, + name)) { + return TOK_ERROR; + } + return tt; +} +#endif + +/* + * Parse a top-level JS script. + */ +JS_FRIEND_API(JSParseNode *) +js_ParseTokenStream(JSContext *cx, JSObject *chain, JSTokenStream *ts) +{ + JSStackFrame *fp, frame; + JSTreeContext tc; + JSParseNode *pn; + + /* + * Push a compiler frame if we have no frames, or if the top frame is a + * lightweight function activation, or if its scope chain doesn't match + * the one passed to us. + */ + fp = cx->fp; + if (!fp || !fp->varobj || fp->scopeChain != chain) { + memset(&frame, 0, sizeof frame); + frame.varobj = frame.scopeChain = chain; + if (cx->options & JSOPTION_VAROBJFIX) { + while ((chain = JS_GetParent(cx, chain)) != NULL) + frame.varobj = chain; + } + frame.down = fp; + cx->fp = &frame; + } + + /* + * Protect atoms from being collected by a GC activation, which might + * - nest on this thread due to out of memory (the so-called "last ditch" + * GC attempted within js_AllocGCThing), or + * - run for any reason on another thread if this thread is suspended on + * an object lock before it finishes generating bytecode into a script + * protected from the GC by a root or a stack frame reference. + */ + JS_KEEP_ATOMS(cx->runtime); + TREE_CONTEXT_INIT(&tc); + pn = Statements(cx, ts, &tc); + if (pn) { + if (!js_MatchToken(cx, ts, TOK_EOF)) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_SYNTAX_ERROR); + pn = NULL; + } else { + pn->pn_type = TOK_LC; + if (!js_FoldConstants(cx, pn, &tc)) + pn = NULL; + } + } + + TREE_CONTEXT_FINISH(&tc); + JS_UNKEEP_ATOMS(cx->runtime); + cx->fp = fp; + return pn; +} + +/* + * Compile a top-level script. + */ +JS_FRIEND_API(JSBool) +js_CompileTokenStream(JSContext *cx, JSObject *chain, JSTokenStream *ts, + JSCodeGenerator *cg) +{ + JSStackFrame *fp, frame; + JSParseNode *pn; + JSBool ok; +#ifdef METER_PARSENODES + void *sbrk(ptrdiff_t), *before = sbrk(0); +#endif + + /* + * Push a compiler frame if we have no frames, or if the top frame is a + * lightweight function activation, or if its scope chain doesn't match + * the one passed to us. + */ + fp = cx->fp; + if (!fp || !fp->varobj || fp->scopeChain != chain) { + memset(&frame, 0, sizeof frame); + frame.varobj = frame.scopeChain = chain; + if (cx->options & JSOPTION_VAROBJFIX) { + while ((chain = JS_GetParent(cx, chain)) != NULL) + frame.varobj = chain; + } + frame.down = fp; + cx->fp = &frame; + } + + /* Prevent GC activation while compiling. */ + JS_KEEP_ATOMS(cx->runtime); + + pn = Statements(cx, ts, &cg->treeContext); + if (!pn) { + ok = JS_FALSE; + } else if (!js_MatchToken(cx, ts, TOK_EOF)) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_SYNTAX_ERROR); + ok = JS_FALSE; + } else { +#ifdef METER_PARSENODES + printf("Parser growth: %d (%u nodes, %u max, %u unrecycled)\n", + (char *)sbrk(0) - (char *)before, + parsenodes, + maxparsenodes, + parsenodes - recyclednodes); + before = sbrk(0); +#endif + + /* + * No need to emit code here -- Statements already has, for each + * statement in turn. Search for TCF_COMPILING in Statements, below. + * That flag is set for every tc == &cg->treeContext, and it implies + * that the tc can be downcast to a cg and used to emit code during + * parsing, rather than at the end of the parse phase. + */ + JS_ASSERT(cg->treeContext.flags & TCF_COMPILING); + ok = JS_TRUE; + } + +#ifdef METER_PARSENODES + printf("Code-gen growth: %d (%u bytecodes, %u srcnotes)\n", + (char *)sbrk(0) - (char *)before, CG_OFFSET(cg), cg->noteCount); +#endif +#ifdef JS_ARENAMETER + JS_DumpArenaStats(stdout); +#endif + JS_UNKEEP_ATOMS(cx->runtime); + cx->fp = fp; + return ok; +} + +/* + * Insist on a final return before control flows out of pn, but don't be too + * smart about loops (do {...; return e2;} while(0) at the end of a function + * that contains an early return e1 will get a strict-option-only warning). + */ +static JSBool +HasFinalReturn(JSParseNode *pn) +{ + JSBool ok, hasDefault; + JSParseNode *pn2, *pn3; + + switch (pn->pn_type) { + case TOK_LC: + if (!pn->pn_head) + return JS_FALSE; + return HasFinalReturn(PN_LAST(pn)); + + case TOK_IF: + ok = HasFinalReturn(pn->pn_kid2); + ok &= pn->pn_kid3 && HasFinalReturn(pn->pn_kid3); + return ok; + +#if JS_HAS_SWITCH_STATEMENT + case TOK_SWITCH: + ok = JS_TRUE; + hasDefault = JS_FALSE; + for (pn2 = pn->pn_kid2->pn_head; ok && pn2; pn2 = pn2->pn_next) { + if (pn2->pn_type == TOK_DEFAULT) + hasDefault = JS_TRUE; + pn3 = pn2->pn_right; + JS_ASSERT(pn3->pn_type == TOK_LC); + if (pn3->pn_head) + ok &= HasFinalReturn(PN_LAST(pn3)); + } + /* If a final switch has no default case, we judge it harshly. */ + ok &= hasDefault; + return ok; +#endif /* JS_HAS_SWITCH_STATEMENT */ + + case TOK_WITH: + return HasFinalReturn(pn->pn_right); + + case TOK_RETURN: + return JS_TRUE; + +#if JS_HAS_EXCEPTIONS + case TOK_THROW: + return JS_TRUE; + + case TOK_TRY: + /* If we have a finally block that returns, we are done. */ + if (pn->pn_kid3 && HasFinalReturn(pn->pn_kid3)) + return JS_TRUE; + + /* Else check the try block and any and all catch statements. */ + ok = HasFinalReturn(pn->pn_kid1); + if (pn->pn_kid2) + ok &= HasFinalReturn(pn->pn_kid2); + return ok; + + case TOK_CATCH: + /* Check this block's code and iterate over further catch blocks. */ + ok = HasFinalReturn(pn->pn_kid3); + for (pn2 = pn->pn_kid2; pn2; pn2 = pn2->pn_kid2) + ok &= HasFinalReturn(pn2->pn_kid3); + return ok; +#endif + + default: + return JS_FALSE; + } +} + +static JSBool +ReportNoReturnValue(JSContext *cx, JSTokenStream *ts) +{ + JSFunction *fun; + JSBool ok; + + fun = cx->fp->fun; + if (fun->atom) { + char *name = js_GetStringBytes(ATOM_TO_STRING(fun->atom)); + ok = js_ReportCompileErrorNumber(cx, ts, NULL, + JSREPORT_WARNING | + JSREPORT_STRICT, + JSMSG_NO_RETURN_VALUE, name); + } else { + ok = js_ReportCompileErrorNumber(cx, ts, NULL, + JSREPORT_WARNING | + JSREPORT_STRICT, + JSMSG_ANON_NO_RETURN_VALUE); + } + return ok; +} + +static JSBool +CheckFinalReturn(JSContext *cx, JSTokenStream *ts, JSParseNode *pn) +{ + return HasFinalReturn(pn) || ReportNoReturnValue(cx, ts); +} + +static JSParseNode * +FunctionBody(JSContext *cx, JSTokenStream *ts, JSFunction *fun, + JSTreeContext *tc) +{ + JSStackFrame *fp, frame; + JSObject *funobj; + uintN oldflags; + JSParseNode *pn; + + fp = cx->fp; + funobj = fun->object; + if (!fp || fp->fun != fun || fp->varobj != funobj || + fp->scopeChain != funobj) { + memset(&frame, 0, sizeof frame); + frame.fun = fun; + frame.varobj = frame.scopeChain = funobj; + frame.down = fp; + cx->fp = &frame; + } + + oldflags = tc->flags; + tc->flags &= ~(TCF_RETURN_EXPR | TCF_RETURN_VOID); + tc->flags |= TCF_IN_FUNCTION; + pn = Statements(cx, ts, tc); + + /* Check for falling off the end of a function that returns a value. */ + if (pn && JS_HAS_STRICT_OPTION(cx) && (tc->flags & TCF_RETURN_EXPR)) { + if (!CheckFinalReturn(cx, ts, pn)) + pn = NULL; + } + + cx->fp = fp; + tc->flags = oldflags | (tc->flags & TCF_FUN_FLAGS); + return pn; +} + +/* + * Compile a JS function body, which might appear as the value of an event + * handler attribute in an HTML tag. + */ +JSBool +js_CompileFunctionBody(JSContext *cx, JSTokenStream *ts, JSFunction *fun) +{ + JSArenaPool codePool, notePool; + JSCodeGenerator funcg; + JSStackFrame *fp, frame; + JSObject *funobj; + JSParseNode *pn; + JSBool ok; + + JS_InitArenaPool(&codePool, "code", 1024, sizeof(jsbytecode)); + JS_InitArenaPool(¬ePool, "note", 1024, sizeof(jssrcnote)); + if (!js_InitCodeGenerator(cx, &funcg, &codePool, ¬ePool, + ts->filename, ts->lineno, + ts->principals)) { + return JS_FALSE; + } + + /* Prevent GC activation while compiling. */ + JS_KEEP_ATOMS(cx->runtime); + + /* Push a JSStackFrame for use by FunctionBody and js_EmitFunctionBody. */ + fp = cx->fp; + funobj = fun->object; + JS_ASSERT(!fp || fp->fun != fun || fp->varobj != funobj || + fp->scopeChain != funobj); + memset(&frame, 0, sizeof frame); + frame.fun = fun; + frame.varobj = frame.scopeChain = funobj; + frame.down = fp; + cx->fp = &frame; + + /* Ensure that the body looks like a block statement to js_EmitTree. */ + CURRENT_TOKEN(ts).type = TOK_LC; + pn = FunctionBody(cx, ts, fun, &funcg.treeContext); + if (!pn) { + ok = JS_FALSE; + } else { + /* + * No need to emit code here -- Statements (via FunctionBody) already + * has. See similar comment in js_CompileTokenStream, and bug 108257. + */ + fun->script = js_NewScriptFromCG(cx, &funcg, fun); + if (!fun->script) { + ok = JS_FALSE; + } else { + if (funcg.treeContext.flags & TCF_FUN_HEAVYWEIGHT) + fun->flags |= JSFUN_HEAVYWEIGHT; + ok = JS_TRUE; + } + } + + /* Restore saved state and release code generation arenas. */ + cx->fp = fp; + JS_UNKEEP_ATOMS(cx->runtime); + js_FinishCodeGenerator(cx, &funcg); + JS_FinishArenaPool(&codePool); + JS_FinishArenaPool(¬ePool); + return ok; +} + +static JSParseNode * +FunctionDef(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, + JSBool lambda) +{ + JSParseNode *pn, *body; + JSOp op, prevop; + JSAtom *funAtom, *argAtom; + JSFunction *fun; + JSObject *parent; + JSObject *pobj; + JSScopeProperty *sprop; + uintN dupflag; + JSBool ok; + JSTreeContext funtc; + JSAtomListElement *ale; + + /* Make a TOK_FUNCTION node. */ + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_FUNC, tc); + if (!pn) + return NULL; +#if JS_HAS_GETTER_SETTER + op = CURRENT_TOKEN(ts).t_op; +#endif + + /* Scan the optional function name into funAtom. */ + if (js_MatchToken(cx, ts, TOK_NAME)) + funAtom = CURRENT_TOKEN(ts).t_atom; + else + funAtom = NULL; + + /* Find the nearest variable-declaring scope and use it as our parent. */ + parent = cx->fp->varobj; + fun = js_NewFunction(cx, NULL, NULL, 0, lambda ? JSFUN_LAMBDA : 0, parent, + funAtom); + if (!fun) + return NULL; + +#if JS_HAS_GETTER_SETTER + if (op != JSOP_NOP) + fun->flags |= (op == JSOP_GETTER) ? JSPROP_GETTER : JSPROP_SETTER; +#endif + + /* Now parse formal argument list and compute fun->nargs. */ + MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_FORMAL); + if (!js_MatchToken(cx, ts, TOK_RP)) { + do { + MUST_MATCH_TOKEN(TOK_NAME, JSMSG_MISSING_FORMAL); + argAtom = CURRENT_TOKEN(ts).t_atom; + pobj = NULL; + if (!js_LookupProperty(cx, fun->object, (jsid)argAtom, &pobj, + (JSProperty **)&sprop)) { + return NULL; + } + dupflag = 0; + if (sprop) { + ok = JS_TRUE; + if (pobj == fun->object && + sprop->getter == js_GetArgument) { + const char *name = js_AtomToPrintableString(cx, argAtom); + + /* + * A duplicate parameter name. We force a duplicate node + * on the SCOPE_LAST_PROP(scope) list with the same id, + * distinguished by the SPROP_IS_DUPLICATE flag, and not + * mapped by an entry in scope. + */ + ok = name && + js_ReportCompileErrorNumber(cx, ts, NULL, + JSREPORT_WARNING | + JSREPORT_STRICT, + JSMSG_DUPLICATE_FORMAL, + name); + + dupflag = SPROP_IS_DUPLICATE; + } + OBJ_DROP_PROPERTY(cx, pobj, (JSProperty *)sprop); + if (!ok) + return NULL; + sprop = NULL; + } + if (!js_AddNativeProperty(cx, fun->object, (jsid)argAtom, + js_GetArgument, js_SetArgument, + SPROP_INVALID_SLOT, + JSPROP_ENUMERATE | JSPROP_PERMANENT | + JSPROP_SHARED, + SPROP_HAS_SHORTID | dupflag, + fun->nargs)) { + return NULL; + } + fun->nargs++; + } while (js_MatchToken(cx, ts, TOK_COMMA)); + + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_FORMAL); + } + + MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_BODY); + pn->pn_pos.begin = CURRENT_TOKEN(ts).pos.begin; + + TREE_CONTEXT_INIT(&funtc); + body = FunctionBody(cx, ts, fun, &funtc); + if (!body) + return NULL; + + MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_BODY); + pn->pn_pos.end = CURRENT_TOKEN(ts).pos.end; + +#if JS_HAS_LEXICAL_CLOSURE + /* + * If we collected flags that indicate nested heavyweight functions, or + * this function contains heavyweight-making statements (references to + * __parent__ or __proto__; use of with, eval, import, or export; and + * assignment to arguments), flag the function as heavyweight (requiring + * a call object per invocation). + */ + if (funtc.flags & TCF_FUN_HEAVYWEIGHT) { + fun->flags |= JSFUN_HEAVYWEIGHT; + tc->flags |= TCF_FUN_HEAVYWEIGHT; + } else { + /* + * If this function is a named statement function not at top-level + * (i.e. a JSOP_CLOSURE), or if it refers to unqualified names that + * are not local args or vars (TCF_FUN_USES_NONLOCALS), then our + * enclosing function, if any, must be heavyweight. + */ + if ((!lambda && funAtom && tc->topStmt) || + (funtc.flags & TCF_FUN_USES_NONLOCALS)) { + tc->flags |= TCF_FUN_HEAVYWEIGHT; + } + } +#endif + + /* + * Record names for function statements in tc->decls so we know when to + * avoid optimizing variable references that might name a function. + */ + if (!lambda && funAtom) { + ATOM_LIST_SEARCH(ale, &tc->decls, funAtom); + if (ale) { + prevop = ALE_JSOP(ale); + if (JS_HAS_STRICT_OPTION(cx) || prevop == JSOP_DEFCONST) { + const char *name = js_AtomToPrintableString(cx, funAtom); + if (!name || + !js_ReportCompileErrorNumber(cx, ts, NULL, + (prevop != JSOP_DEFCONST) + ? JSREPORT_WARNING | + JSREPORT_STRICT + : JSREPORT_ERROR, + JSMSG_REDECLARED_VAR, + (prevop == JSOP_DEFFUN || + prevop == JSOP_CLOSURE) + ? js_function_str + : (prevop == JSOP_DEFCONST) + ? js_const_str + : js_var_str, + name)) { + return NULL; + } + } + if (tc->topStmt && prevop == JSOP_DEFVAR) + tc->flags |= TCF_FUN_CLOSURE_VS_VAR; + } else { + ale = js_IndexAtom(cx, funAtom, &tc->decls); + if (!ale) + return NULL; + } + ALE_SET_JSOP(ale, tc->topStmt ? JSOP_CLOSURE : JSOP_DEFFUN); + +#if JS_HAS_LEXICAL_CLOSURE + /* + * A function nested at top level inside another's body needs only a + * local variable to bind its name to its value, and not an activation + * object property (it might also need the activation property, if the + * outer function contains with statements, e.g., but the stack slot + * wins when jsemit.c's LookupArgOrVar can optimize a JSOP_NAME into a + * JSOP_GETVAR bytecode). + */ + if (!tc->topStmt && (tc->flags & TCF_IN_FUNCTION)) { + JSStackFrame *fp; + JSObject *varobj; + + /* + * Define a property on the outer function so that LookupArgOrVar + * can properly optimize accesses. + * + * XXX Here and in Variables, we use the function object's scope, + * XXX arguably polluting it, when we could use a compiler-private + * XXX scope structure. Tradition! + */ + fp = cx->fp; + varobj = fp->varobj; + JS_ASSERT(OBJ_GET_CLASS(cx, varobj) == &js_FunctionClass); + JS_ASSERT(fp->fun == (JSFunction *) JS_GetPrivate(cx, varobj)); + if (!js_DefineNativeProperty(cx, varobj, (jsid)funAtom, + OBJECT_TO_JSVAL(fun->object), + js_GetLocalVariable, + js_SetLocalVariable, + JSPROP_ENUMERATE, + SPROP_HAS_SHORTID, fp->fun->nvars, + NULL)) { + return NULL; + } + fp->fun->nvars++; + } +#endif + } + +#if JS_HAS_LEXICAL_CLOSURE + if (lambda || !funAtom) { + /* + * ECMA ed. 3 standard: function expression, possibly anonymous (even + * if at top-level, an unnamed function is an expression statement, not + * a function declaration). + */ + op = fun->atom ? JSOP_NAMEDFUNOBJ : JSOP_ANONFUNOBJ; + } else if (tc->topStmt) { + /* + * ECMA ed. 3 extension: a function expression statement not at the + * top level, e.g., in a compound statement such as the "then" part + * of an "if" statement, binds a closure only if control reaches that + * sub-statement. + */ + op = JSOP_CLOSURE; + } else +#endif + op = JSOP_NOP; + + /* + * Pending a better automatic GC root management scheme (see Mozilla bug + * 40757, http://bugzilla.mozilla.org/show_bug.cgi?id=40757), we need to + * atomize here to protect against a GC activation. + */ + pn->pn_funAtom = js_AtomizeObject(cx, fun->object, 0); + if (!pn->pn_funAtom) + return NULL; + + pn->pn_op = op; + pn->pn_body = body; + pn->pn_flags = funtc.flags & TCF_FUN_FLAGS; + pn->pn_tryCount = funtc.tryCount; + TREE_CONTEXT_FINISH(&funtc); + return pn; +} + +static JSParseNode * +FunctionStmt(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) +{ + return FunctionDef(cx, ts, tc, JS_FALSE); +} + +#if JS_HAS_LEXICAL_CLOSURE +static JSParseNode * +FunctionExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) +{ + return FunctionDef(cx, ts, tc, JS_TRUE); +} +#endif + +/* + * Parse the statements in a block, creating a TOK_LC node that lists the + * statements' trees. If called from block-parsing code, the caller must + * match { before and } after. + */ +static JSParseNode * +Statements(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) +{ + JSParseNode *pn, *pn2; + JSTokenType tt; + + CHECK_RECURSION(); + + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_LIST, tc); + if (!pn) + return NULL; + PN_INIT_LIST(pn); + + ts->flags |= TSF_REGEXP; + while ((tt = js_PeekToken(cx, ts)) > TOK_EOF && tt != TOK_RC) { + ts->flags &= ~TSF_REGEXP; + pn2 = Statement(cx, ts, tc); + if (!pn2) + return NULL; + ts->flags |= TSF_REGEXP; + + /* If compiling top-level statements, emit as we go to save space. */ + if (!tc->topStmt && (tc->flags & TCF_COMPILING)) { + if (cx->fp->fun && + JS_HAS_STRICT_OPTION(cx) && + (tc->flags & TCF_RETURN_EXPR)) { + /* + * Check pn2 for lack of a final return statement if it is the + * last statement in the block. + */ + tt = js_PeekToken(cx, ts); + if ((tt == TOK_EOF || tt == TOK_RC) && + !CheckFinalReturn(cx, ts, pn2)) { + tt = TOK_ERROR; + break; + } + + /* + * Clear TCF_RETURN_EXPR so FunctionBody doesn't try to + * CheckFinalReturn again. + */ + tc->flags &= ~TCF_RETURN_EXPR; + } + if (!js_FoldConstants(cx, pn2, tc) || + !js_AllocTryNotes(cx, (JSCodeGenerator *)tc) || + !js_EmitTree(cx, (JSCodeGenerator *)tc, pn2)) { + tt = TOK_ERROR; + break; + } + RecycleTree(pn2, tc); + } else { + PN_APPEND(pn, pn2); + } + } + ts->flags &= ~TSF_REGEXP; + if (tt == TOK_ERROR) + return NULL; + + pn->pn_pos.end = CURRENT_TOKEN(ts).pos.end; + return pn; +} + +static JSParseNode * +Condition(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) +{ + JSParseNode *pn, *pn2; + + MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_COND); + pn = Expr(cx, ts, tc); + if (!pn) + return NULL; + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_COND); + + /* + * Check for (a = b) and "correct" it to (a == b) iff b's operator has + * greater precedence than ==. + * XXX not ECMA, but documented in several books -- now a strict warning. + */ + if (pn->pn_type == TOK_ASSIGN && + pn->pn_op == JSOP_NOP && + pn->pn_right->pn_type > TOK_EQOP) + { + JSBool rewrite = !JSVERSION_IS_ECMA(cx->version); + if (!js_ReportCompileErrorNumber(cx, ts, NULL, + JSREPORT_WARNING | JSREPORT_STRICT, + JSMSG_EQUAL_AS_ASSIGN, + rewrite + ? "\nAssuming equality test" + : "")) { + return NULL; + } + if (rewrite) { + pn->pn_type = TOK_EQOP; + pn->pn_op = (JSOp)cx->jsop_eq; + pn2 = pn->pn_left; + switch (pn2->pn_op) { + case JSOP_SETNAME: + pn2->pn_op = JSOP_NAME; + break; + case JSOP_SETPROP: + pn2->pn_op = JSOP_GETPROP; + break; + case JSOP_SETELEM: + pn2->pn_op = JSOP_GETELEM; + break; + default: + JS_ASSERT(0); + } + } + } + return pn; +} + +static JSBool +MatchLabel(JSContext *cx, JSTokenStream *ts, JSParseNode *pn) +{ + JSAtom *label; +#if JS_HAS_LABEL_STATEMENT + JSTokenType tt; + + tt = js_PeekTokenSameLine(cx, ts); + if (tt == TOK_ERROR) + return JS_FALSE; + if (tt == TOK_NAME) { + (void) js_GetToken(cx, ts); + label = CURRENT_TOKEN(ts).t_atom; + } else { + label = NULL; + } +#else + label = NULL; +#endif + pn->pn_atom = label; + return JS_TRUE; +} + +#if JS_HAS_EXPORT_IMPORT +static JSParseNode * +ImportExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) +{ + JSParseNode *pn, *pn2, *pn3; + JSTokenType tt; + + MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_IMPORT_NAME); + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_NAME, tc); + if (!pn) + return NULL; + pn->pn_op = JSOP_NAME; + pn->pn_atom = CURRENT_TOKEN(ts).t_atom; + pn->pn_expr = NULL; + pn->pn_slot = -1; + pn->pn_attrs = 0; + + ts->flags |= TSF_REGEXP; + while ((tt = js_GetToken(cx, ts)) == TOK_DOT || tt == TOK_LB) { + ts->flags &= ~TSF_REGEXP; + if (pn->pn_op == JSOP_IMPORTALL) + goto bad_import; + + if (tt == TOK_DOT) { + pn2 = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_NAME, tc); + if (!pn2) + return NULL; + if (js_MatchToken(cx, ts, TOK_STAR)) { + pn2->pn_op = JSOP_IMPORTALL; + pn2->pn_atom = NULL; + } else { + MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NAME_AFTER_DOT); + pn2->pn_op = JSOP_GETPROP; + pn2->pn_atom = CURRENT_TOKEN(ts).t_atom; + pn2->pn_slot = -1; + pn2->pn_attrs = 0; + } + pn2->pn_expr = pn; + pn2->pn_pos.begin = pn->pn_pos.begin; + pn2->pn_pos.end = CURRENT_TOKEN(ts).pos.end; + } else { + /* Make a TOK_LB node. */ + pn2 = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_BINARY, tc); + if (!pn2) + return NULL; + pn3 = Expr(cx, ts, tc); + if (!pn3) + return NULL; + + MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_IN_INDEX); + pn2->pn_pos.begin = pn->pn_pos.begin; + pn2->pn_pos.end = CURRENT_TOKEN(ts).pos.end; + + pn2->pn_op = JSOP_GETELEM; + pn2->pn_left = pn; + pn2->pn_right = pn3; + } + + pn = pn2; + ts->flags |= TSF_REGEXP; + } + ts->flags &= ~TSF_REGEXP; + if (tt == TOK_ERROR) + return NULL; + js_UngetToken(ts); + + switch (pn->pn_op) { + case JSOP_GETPROP: + pn->pn_op = JSOP_IMPORTPROP; + break; + case JSOP_GETELEM: + pn->pn_op = JSOP_IMPORTELEM; + break; + case JSOP_IMPORTALL: + break; + default: + goto bad_import; + } + return pn; + + bad_import: + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, JSMSG_BAD_IMPORT); + return NULL; +} +#endif /* JS_HAS_EXPORT_IMPORT */ + +extern const char js_with_statement_str[]; + +static JSParseNode * +Statement(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) +{ + JSTokenType tt; + JSParseNode *pn, *pn1, *pn2, *pn3, *pn4; + JSStmtInfo stmtInfo, *stmt, *stmt2; + JSAtom *label; + + ts->flags |= TSF_REGEXP; + tt = js_GetToken(cx, ts); + ts->flags &= ~TSF_REGEXP; + +#if JS_HAS_GETTER_SETTER + if (tt == TOK_NAME) { + tt = CheckGetterOrSetter(cx, ts, TOK_FUNCTION); + if (tt == TOK_ERROR) + return NULL; + } +#endif + + switch (tt) { +#if JS_HAS_EXPORT_IMPORT + case TOK_EXPORT: + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_LIST, tc); + if (!pn) + return NULL; + PN_INIT_LIST(pn); + if (js_MatchToken(cx, ts, TOK_STAR)) { + pn2 = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_NULLARY, tc); + if (!pn2) + return NULL; + PN_APPEND(pn, pn2); + } else { + do { + MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_EXPORT_NAME); + pn2 = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_NAME, tc); + if (!pn2) + return NULL; + pn2->pn_op = JSOP_NAME; + pn2->pn_atom = CURRENT_TOKEN(ts).t_atom; + pn2->pn_expr = NULL; + pn2->pn_slot = -1; + pn2->pn_attrs = 0; + PN_APPEND(pn, pn2); + } while (js_MatchToken(cx, ts, TOK_COMMA)); + } + pn->pn_pos.end = PN_LAST(pn)->pn_pos.end; + tc->flags |= TCF_FUN_HEAVYWEIGHT; + break; + + case TOK_IMPORT: + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_LIST, tc); + if (!pn) + return NULL; + PN_INIT_LIST(pn); + do { + pn2 = ImportExpr(cx, ts, tc); + if (!pn2) + return NULL; + PN_APPEND(pn, pn2); + } while (js_MatchToken(cx, ts, TOK_COMMA)); + pn->pn_pos.end = PN_LAST(pn)->pn_pos.end; + tc->flags |= TCF_FUN_HEAVYWEIGHT; + break; +#endif /* JS_HAS_EXPORT_IMPORT */ + + case TOK_FUNCTION: + return FunctionStmt(cx, ts, tc); + + case TOK_IF: + /* An IF node has three kids: condition, then, and optional else. */ + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_TERNARY, tc); + if (!pn) + return NULL; + pn1 = Condition(cx, ts, tc); + if (!pn1) + return NULL; + js_PushStatement(tc, &stmtInfo, STMT_IF, -1); + pn2 = Statement(cx, ts, tc); + if (!pn2) + return NULL; + if (js_MatchToken(cx, ts, TOK_ELSE)) { + stmtInfo.type = STMT_ELSE; + pn3 = Statement(cx, ts, tc); + if (!pn3) + return NULL; + pn->pn_pos.end = pn3->pn_pos.end; + } else { + pn3 = NULL; + pn->pn_pos.end = pn2->pn_pos.end; + } + js_PopStatement(tc); + pn->pn_kid1 = pn1; + pn->pn_kid2 = pn2; + pn->pn_kid3 = pn3; + return pn; + +#if JS_HAS_SWITCH_STATEMENT + case TOK_SWITCH: + { + JSParseNode *pn5; + JSBool seenDefault = JS_FALSE; + + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_BINARY, tc); + if (!pn) + return NULL; + MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_SWITCH); + + /* pn1 points to the switch's discriminant. */ + pn1 = Expr(cx, ts, tc); + if (!pn1) + return NULL; + + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_SWITCH); + MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_SWITCH); + + /* pn2 is a list of case nodes. The default case has pn_left == NULL */ + pn2 = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_LIST, tc); + if (!pn2) + return NULL; + PN_INIT_LIST(pn2); + + js_PushStatement(tc, &stmtInfo, STMT_SWITCH, -1); + + while ((tt = js_GetToken(cx, ts)) != TOK_RC) { + switch (tt) { + case TOK_DEFAULT: + if (seenDefault) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_TOO_MANY_DEFAULTS); + return NULL; + } + seenDefault = JS_TRUE; + /* fall through */ + + case TOK_CASE: + pn3 = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_BINARY, tc); + if (!pn3) + return NULL; + if (tt == TOK_DEFAULT) { + pn3->pn_left = NULL; + } else { + pn3->pn_left = Expr(cx, ts, tc); + if (!pn3->pn_left) + return NULL; + } + PN_APPEND(pn2, pn3); + if (pn2->pn_count == JS_BIT(16)) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_TOO_MANY_CASES); + return NULL; + } + break; + + case TOK_ERROR: + return NULL; + + default: + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_BAD_SWITCH); + return NULL; + } + MUST_MATCH_TOKEN(TOK_COLON, JSMSG_COLON_AFTER_CASE); + + pn4 = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_LIST, tc); + if (!pn4) + return NULL; + pn4->pn_type = TOK_LC; + PN_INIT_LIST(pn4); + while ((tt = js_PeekToken(cx, ts)) != TOK_RC && + tt != TOK_CASE && tt != TOK_DEFAULT) { + if (tt == TOK_ERROR) + return NULL; + pn5 = Statement(cx, ts, tc); + if (!pn5) + return NULL; + pn4->pn_pos.end = pn5->pn_pos.end; + PN_APPEND(pn4, pn5); + } + + /* Fix the PN_LIST so it doesn't begin at the TOK_COLON. */ + if (pn4->pn_head) + pn4->pn_pos.begin = pn4->pn_head->pn_pos.begin; + pn3->pn_pos.end = pn4->pn_pos.end; + pn3->pn_right = pn4; + } + + js_PopStatement(tc); + + pn->pn_pos.end = pn2->pn_pos.end = CURRENT_TOKEN(ts).pos.end; + pn->pn_kid1 = pn1; + pn->pn_kid2 = pn2; + return pn; + } +#endif /* JS_HAS_SWITCH_STATEMENT */ + + case TOK_WHILE: + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_BINARY, tc); + if (!pn) + return NULL; + js_PushStatement(tc, &stmtInfo, STMT_WHILE_LOOP, -1); + pn2 = Condition(cx, ts, tc); + if (!pn2) + return NULL; + pn->pn_left = pn2; + pn2 = Statement(cx, ts, tc); + if (!pn2) + return NULL; + js_PopStatement(tc); + pn->pn_pos.end = pn2->pn_pos.end; + pn->pn_right = pn2; + return pn; + +#if JS_HAS_DO_WHILE_LOOP + case TOK_DO: + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_BINARY, tc); + if (!pn) + return NULL; + js_PushStatement(tc, &stmtInfo, STMT_DO_LOOP, -1); + pn2 = Statement(cx, ts, tc); + if (!pn2) + return NULL; + pn->pn_left = pn2; + MUST_MATCH_TOKEN(TOK_WHILE, JSMSG_WHILE_AFTER_DO); + pn2 = Condition(cx, ts, tc); + if (!pn2) + return NULL; + js_PopStatement(tc); + pn->pn_pos.end = pn2->pn_pos.end; + pn->pn_right = pn2; + break; +#endif /* JS_HAS_DO_WHILE_LOOP */ + + case TOK_FOR: + /* A FOR node is binary, left is loop control and right is the body. */ + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_BINARY, tc); + if (!pn) + return NULL; + js_PushStatement(tc, &stmtInfo, STMT_FOR_LOOP, -1); + + MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR); + tt = js_PeekToken(cx, ts); + if (tt == TOK_SEMI) { + /* No initializer -- set first kid of left sub-node to null. */ + pn1 = NULL; + } else { + /* Set pn1 to a var list or an initializing expression. */ +#if JS_HAS_IN_OPERATOR + /* + * Set the TCF_IN_FOR_INIT flag during parsing of the first clause + * of the for statement. This flag will be used by the RelExpr + * production; if it is set, then the 'in' keyword will not be + * recognized as an operator, leaving it available to be parsed as + * part of a for/in loop. A side effect of this restriction is + * that (unparenthesized) expressions involving an 'in' operator + * are illegal in the init clause of an ordinary for loop. + */ + tc->flags |= TCF_IN_FOR_INIT; +#endif /* JS_HAS_IN_OPERATOR */ + if (tt == TOK_VAR) { + (void) js_GetToken(cx, ts); + pn1 = Variables(cx, ts, tc); + } else { + pn1 = Expr(cx, ts, tc); + } +#if JS_HAS_IN_OPERATOR + tc->flags &= ~TCF_IN_FOR_INIT; +#endif /* JS_HAS_IN_OPERATOR */ + if (!pn1) + return NULL; + } + + /* + * We can be sure that it's a for/in loop if there's still an 'in' + * keyword here, even if JavaScript recognizes 'in' as an operator, + * as we've excluded 'in' from being parsed in RelExpr by setting + * the TCF_IN_FOR_INIT flag in our JSTreeContext. + */ + if (pn1 && js_MatchToken(cx, ts, TOK_IN)) { + stmtInfo.type = STMT_FOR_IN_LOOP; + + /* Check that the left side of the 'in' is valid. */ + if ((pn1->pn_type == TOK_VAR) + ? (pn1->pn_count > 1 || pn1->pn_op == JSOP_DEFCONST) + : (pn1->pn_type != TOK_NAME && + pn1->pn_type != TOK_DOT && + pn1->pn_type != TOK_LB)) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_BAD_FOR_LEFTSIDE); + return NULL; + } + + if (pn1->pn_type == TOK_VAR) { + /* Tell js_EmitTree(TOK_VAR) to generate a final POP. */ + pn1->pn_extra = JS_TRUE; + pn2 = pn1->pn_head; + } else { + pn2 = pn1; + } + + /* Beware 'for (arguments in ...)' with or without a 'var'. */ + if (pn2->pn_type == TOK_NAME && + pn2->pn_atom == cx->runtime->atomState.argumentsAtom) { + tc->flags |= TCF_FUN_HEAVYWEIGHT; + } + + /* Parse the object expression as the right operand of 'in'. */ + pn2 = NewBinary(cx, TOK_IN, JSOP_NOP, pn1, Expr(cx, ts, tc), tc); + if (!pn2) + return NULL; + pn->pn_left = pn2; + } else { + /* Parse the loop condition or null into pn2. */ + MUST_MATCH_TOKEN(TOK_SEMI, JSMSG_SEMI_AFTER_FOR_INIT); + if (js_PeekToken(cx, ts) == TOK_SEMI) { + pn2 = NULL; + } else { + pn2 = Expr(cx, ts, tc); + if (!pn2) + return NULL; + } + + /* Parse the update expression or null into pn3. */ + MUST_MATCH_TOKEN(TOK_SEMI, JSMSG_SEMI_AFTER_FOR_COND); + if (js_PeekToken(cx, ts) == TOK_RP) { + pn3 = NULL; + } else { + pn3 = Expr(cx, ts, tc); + if (!pn3) + return NULL; + } + + /* Build the RESERVED node to use as the left kid of pn. */ + pn4 = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_TERNARY, tc); + if (!pn4) + return NULL; + pn4->pn_type = TOK_RESERVED; + pn4->pn_op = JSOP_NOP; + pn4->pn_kid1 = pn1; + pn4->pn_kid2 = pn2; + pn4->pn_kid3 = pn3; + pn->pn_left = pn4; + } + + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_FOR_CTRL); + + /* Parse the loop body into pn->pn_right. */ + pn2 = Statement(cx, ts, tc); + if (!pn2) + return NULL; + pn->pn_right = pn2; + js_PopStatement(tc); + + /* Record the absolute line number for source note emission. */ + pn->pn_pos.end = pn2->pn_pos.end; + return pn; + +#if JS_HAS_EXCEPTIONS + case TOK_TRY: { + JSParseNode *catchtail = NULL; + /* + * try nodes are ternary. + * kid1 is the try Statement + * kid2 is the catch node + * kid3 is the finally Statement + * + * catch nodes are ternary. + * kid1 is the discriminant + * kid2 is the next catch node, or NULL + * kid3 is the catch block (on kid3 so that we can always append a + * new catch pn on catchtail->kid2) + * + * catch discriminant nodes are binary + * atom is the receptacle + * expr is the discriminant code + * + * finally nodes are unary (just the finally expression) + */ + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_TERNARY, tc); + pn->pn_op = JSOP_NOP; + + MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_TRY); + js_PushStatement(tc, &stmtInfo, STMT_TRY, -1); + pn->pn_kid1 = Statements(cx, ts, tc); + if (!pn->pn_kid1) + return NULL; + MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_TRY); + js_PopStatement(tc); + + catchtail = pn; + while (js_PeekToken(cx, ts) == TOK_CATCH) { + /* check for another catch after unconditional catch */ + if (catchtail != pn && !catchtail->pn_kid1->pn_expr) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_CATCH_AFTER_GENERAL); + return NULL; + } + + /* + * legal catch forms are: + * catch (v) + * catch (v if ) + * (the latter is legal only #ifdef JS_HAS_CATCH_GUARD) + */ + (void) js_GetToken(cx, ts); /* eat `catch' */ + pn2 = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_TERNARY, tc); + if (!pn2) + return NULL; + + /* + * We use a PN_NAME for the discriminant (catchguard) node + * with the actual discriminant code in the initializer spot + */ + MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_CATCH); + MUST_MATCH_TOKEN(TOK_NAME, JSMSG_CATCH_IDENTIFIER); + pn3 = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_NAME, tc); + if (!pn3) + return NULL; + + pn3->pn_atom = CURRENT_TOKEN(ts).t_atom; + pn3->pn_expr = NULL; +#if JS_HAS_CATCH_GUARD + /* + * We use `catch (x if x === 5)' (not `catch (x : x === 5)') to + * avoid conflicting with the JS2/ECMA2 proposed catchguard syntax. + */ + if (js_PeekToken(cx, ts) == TOK_IF) { + (void)js_GetToken(cx, ts); /* eat `if' */ + pn3->pn_expr = Expr(cx, ts, tc); + if (!pn3->pn_expr) + return NULL; + } +#endif + pn2->pn_kid1 = pn3; + + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_CATCH); + + MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_CATCH); + js_PushStatement(tc, &stmtInfo, STMT_CATCH, -1); + stmtInfo.label = pn3->pn_atom; + pn2->pn_kid3 = Statements(cx, ts, tc); + if (!pn2->pn_kid3) + return NULL; + MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_CATCH); + js_PopStatement(tc); + + catchtail = catchtail->pn_kid2 = pn2; + } + catchtail->pn_kid2 = NULL; + + if (js_MatchToken(cx, ts, TOK_FINALLY)) { + tc->tryCount++; + MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_FINALLY); + js_PushStatement(tc, &stmtInfo, STMT_FINALLY, -1); + pn->pn_kid3 = Statements(cx, ts, tc); + if (!pn->pn_kid3) + return NULL; + MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_FINALLY); + js_PopStatement(tc); + } else { + pn->pn_kid3 = NULL; + } + if (!pn->pn_kid2 && !pn->pn_kid3) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_CATCH_OR_FINALLY); + return NULL; + } + tc->tryCount++; + return pn; + } + + case TOK_THROW: + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_UNARY, tc); + if (!pn) + return NULL; + pn2 = Expr(cx, ts, tc); + if (!pn2) + return NULL; + pn->pn_pos.end = pn2->pn_pos.end; + pn->pn_op = JSOP_THROW; + pn->pn_kid = pn2; + break; + + /* TOK_CATCH and TOK_FINALLY are both handled in the TOK_TRY case */ + case TOK_CATCH: + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_CATCH_WITHOUT_TRY); + return NULL; + + case TOK_FINALLY: + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_FINALLY_WITHOUT_TRY); + return NULL; + +#endif /* JS_HAS_EXCEPTIONS */ + + case TOK_BREAK: + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_NULLARY, tc); + if (!pn) + return NULL; + if (!MatchLabel(cx, ts, pn)) + return NULL; + stmt = tc->topStmt; + label = pn->pn_atom; + if (label) { + for (; ; stmt = stmt->down) { + if (!stmt) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_LABEL_NOT_FOUND); + return NULL; + } + if (stmt->type == STMT_LABEL && stmt->label == label) + break; + } + } else { + for (; ; stmt = stmt->down) { + if (!stmt) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_TOUGH_BREAK); + return NULL; + } + if (STMT_IS_LOOP(stmt) || stmt->type == STMT_SWITCH) + break; + } + } + if (label) + pn->pn_pos.end = CURRENT_TOKEN(ts).pos.end; + break; + + case TOK_CONTINUE: + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_NULLARY, tc); + if (!pn) + return NULL; + if (!MatchLabel(cx, ts, pn)) + return NULL; + stmt = tc->topStmt; + label = pn->pn_atom; + if (label) { + for (stmt2 = NULL; ; stmt = stmt->down) { + if (!stmt) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_LABEL_NOT_FOUND); + return NULL; + } + if (stmt->type == STMT_LABEL) { + if (stmt->label == label) { + if (!stmt2 || !STMT_IS_LOOP(stmt2)) { + js_ReportCompileErrorNumber(cx, ts, NULL, + JSREPORT_ERROR, + JSMSG_BAD_CONTINUE); + return NULL; + } + break; + } + } else { + stmt2 = stmt; + } + } + } else { + for (; ; stmt = stmt->down) { + if (!stmt) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_BAD_CONTINUE); + return NULL; + } + if (STMT_IS_LOOP(stmt)) + break; + } + } + if (label) + pn->pn_pos.end = CURRENT_TOKEN(ts).pos.end; + break; + + case TOK_WITH: + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_BINARY, tc); + if (!pn) + return NULL; + MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_WITH); + pn2 = Expr(cx, ts, tc); + if (!pn2) + return NULL; + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_WITH); + pn->pn_left = pn2; + + js_PushStatement(tc, &stmtInfo, STMT_WITH, -1); + pn2 = Statement(cx, ts, tc); + if (!pn2) + return NULL; + js_PopStatement(tc); + + /* Deprecate after parsing, in case of WERROR option. */ + if (!js_ReportCompileErrorNumber(cx, ts, NULL, + JSREPORT_WARNING | JSREPORT_STRICT, + JSMSG_DEPRECATED_USAGE, + js_with_statement_str)) { + return NULL; + } + + pn->pn_pos.end = pn2->pn_pos.end; + pn->pn_right = pn2; + tc->flags |= TCF_FUN_HEAVYWEIGHT; + return pn; + + case TOK_VAR: + pn = Variables(cx, ts, tc); + if (!pn) + return NULL; + + /* Tell js_EmitTree to generate a final POP. */ + pn->pn_extra = JS_TRUE; + break; + + case TOK_RETURN: + if (!(tc->flags & TCF_IN_FUNCTION)) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_BAD_RETURN); + return NULL; + } + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_UNARY, tc); + if (!pn) + return NULL; + + /* This is ugly, but we don't want to require a semicolon. */ + ts->flags |= TSF_REGEXP; + tt = js_PeekTokenSameLine(cx, ts); + ts->flags &= ~TSF_REGEXP; + if (tt == TOK_ERROR) + return NULL; + + if (tt != TOK_EOF && tt != TOK_EOL && tt != TOK_SEMI && tt != TOK_RC) { + pn2 = Expr(cx, ts, tc); + if (!pn2) + return NULL; + tc->flags |= TCF_RETURN_EXPR; + pn->pn_pos.end = pn2->pn_pos.end; + pn->pn_kid = pn2; + } else { + tc->flags |= TCF_RETURN_VOID; + pn->pn_kid = NULL; + } + + if (JS_HAS_STRICT_OPTION(cx) && + (~tc->flags & (TCF_RETURN_EXPR | TCF_RETURN_VOID)) == 0) { + /* + * We must be in a frame with a non-native function, because + * we're compiling one. + */ + if (!ReportNoReturnValue(cx, ts)) + return NULL; + } + break; + + case TOK_LC: + js_PushStatement(tc, &stmtInfo, STMT_BLOCK, -1); + pn = Statements(cx, ts, tc); + if (!pn) + return NULL; + + MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_IN_COMPOUND); + js_PopStatement(tc); + return pn; + + case TOK_EOL: + case TOK_SEMI: + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_UNARY, tc); + if (!pn) + return NULL; + pn->pn_type = TOK_SEMI; + pn->pn_kid = NULL; + return pn; + +#if JS_HAS_DEBUGGER_KEYWORD + case TOK_DEBUGGER: + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_NULLARY, tc); + if (!pn) + return NULL; + pn->pn_type = TOK_DEBUGGER; + tc->flags |= TCF_FUN_HEAVYWEIGHT; + break; +#endif /* JS_HAS_DEBUGGER_KEYWORD */ + + case TOK_ERROR: + return NULL; + + default: + js_UngetToken(ts); + pn2 = Expr(cx, ts, tc); + if (!pn2) + return NULL; + + if (js_PeekToken(cx, ts) == TOK_COLON) { + if (pn2->pn_type != TOK_NAME) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_BAD_LABEL); + return NULL; + } + label = pn2->pn_atom; + for (stmt = tc->topStmt; stmt; stmt = stmt->down) { + if (stmt->type == STMT_LABEL && stmt->label == label) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_DUPLICATE_LABEL); + return NULL; + } + } + (void) js_GetToken(cx, ts); + + /* Push a label struct and parse the statement. */ + js_PushStatement(tc, &stmtInfo, STMT_LABEL, -1); + stmtInfo.label = label; + pn = Statement(cx, ts, tc); + if (!pn) + return NULL; + + /* Pop the label, set pn_expr, and return early. */ + js_PopStatement(tc); + pn2->pn_type = TOK_COLON; + pn2->pn_pos.end = pn->pn_pos.end; + pn2->pn_expr = pn; + return pn2; + } + + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_UNARY, tc); + if (!pn) + return NULL; + pn->pn_type = TOK_SEMI; + pn->pn_pos = pn2->pn_pos; + pn->pn_kid = pn2; + break; + } + + /* Check termination of this primitive statement. */ + if (ON_CURRENT_LINE(ts, pn->pn_pos)) { + tt = js_PeekTokenSameLine(cx, ts); + if (tt == TOK_ERROR) + return NULL; + if (tt != TOK_EOF && tt != TOK_EOL && tt != TOK_SEMI && tt != TOK_RC) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_SEMI_BEFORE_STMNT); + return NULL; + } + } + + (void) js_MatchToken(cx, ts, TOK_SEMI); + return pn; +} + +static JSParseNode * +Variables(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) +{ + JSParseNode *pn, *pn2; + JSObject *obj, *pobj; + JSStackFrame *fp; + JSFunction *fun; + JSClass *clasp; + JSPropertyOp getter, setter, currentGetter, currentSetter; + JSAtom *atom; + JSAtomListElement *ale; + JSOp prevop; + JSProperty *prop; + JSScopeProperty *sprop; + JSBool ok; + + /* + * The tricky part of this code is to create special parsenode opcodes for + * getting and setting variables (which will be stored as special slots in + * the frame). The complex special case is an eval() inside a function. + * If the evaluated string references variables in the enclosing function, + * then we need to generate the special variable opcodes. We determine + * this by looking up the variable id in the current variable scope. + */ + JS_ASSERT(CURRENT_TOKEN(ts).type == TOK_VAR); + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_LIST, tc); + if (!pn) + return NULL; + pn->pn_op = CURRENT_TOKEN(ts).t_op; + pn->pn_extra = JS_FALSE; /* assume no JSOP_POP needed */ + PN_INIT_LIST(pn); + + /* + * Skip eval and debugger frames when looking for the function whose code + * is being compiled. If we are called from FunctionBody, TCF_IN_FUNCTION + * will be set in tc->flags, and we can be sure fp->fun is the function to + * use. But if a function calls eval, the string argument is treated as a + * Program (per ECMA), so TCF_IN_FUNCTION won't be set. + * + * What's more, when the following code is reached from eval, cx->fp->fun + * is eval's JSFunction (a native function), so we need to skip its frame. + * We should find the scripted caller's function frame just below it, but + * we code a loop out of paranoia. + */ + for (fp = cx->fp; (fp->flags & JSFRAME_SPECIAL) && fp->down; fp = fp->down) + continue; + obj = fp->varobj; + fun = fp->fun; + clasp = OBJ_GET_CLASS(cx, obj); + if (fun && clasp == &js_FunctionClass) { + /* We are compiling code inside a function */ + getter = js_GetLocalVariable; + setter = js_SetLocalVariable; + } else if (fun && clasp == &js_CallClass) { + /* We are compiling code from an eval inside a function */ + getter = js_GetCallVariable; + setter = js_SetCallVariable; + } else { + getter = clasp->getProperty; + setter = clasp->setProperty; + } + + ok = JS_TRUE; + do { + currentGetter = getter; + currentSetter = setter; + MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_VARIABLE_NAME); + atom = CURRENT_TOKEN(ts).t_atom; + + ATOM_LIST_SEARCH(ale, &tc->decls, atom); + if (ale) { + prevop = ALE_JSOP(ale); + if (JS_HAS_STRICT_OPTION(cx) || + pn->pn_op == JSOP_DEFCONST || + prevop == JSOP_DEFCONST) { + const char *name = js_AtomToPrintableString(cx, atom); + if (!name || + !js_ReportCompileErrorNumber(cx, ts, NULL, + (pn->pn_op != JSOP_DEFCONST && + prevop != JSOP_DEFCONST) + ? JSREPORT_WARNING | + JSREPORT_STRICT + : JSREPORT_ERROR, + JSMSG_REDECLARED_VAR, + (prevop == JSOP_DEFFUN || + prevop == JSOP_CLOSURE) + ? js_function_str + : (prevop == JSOP_DEFCONST) + ? js_const_str + : js_var_str, + name)) { + return NULL; + } + } + if (pn->pn_op == JSOP_DEFVAR && prevop == JSOP_CLOSURE) + tc->flags |= TCF_FUN_CLOSURE_VS_VAR; + } else { + ale = js_IndexAtom(cx, atom, &tc->decls); + if (!ale) + return NULL; + } + ALE_SET_JSOP(ale, pn->pn_op); + + pn2 = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_NAME, tc); + if (!pn2) + return NULL; + pn2->pn_op = JSOP_NAME; + pn2->pn_atom = atom; + pn2->pn_expr = NULL; + pn2->pn_slot = -1; + pn2->pn_attrs = (pn->pn_op == JSOP_DEFCONST) + ? JSPROP_ENUMERATE | JSPROP_PERMANENT | + JSPROP_READONLY + : JSPROP_ENUMERATE | JSPROP_PERMANENT; + PN_APPEND(pn, pn2); + + if (!OBJ_LOOKUP_PROPERTY(cx, obj, (jsid)atom, &pobj, &prop)) + return NULL; + if (pobj == obj && + OBJ_IS_NATIVE(pobj) && + (sprop = (JSScopeProperty *)prop) != NULL) { + if (sprop->getter == js_GetArgument) { + const char *name = js_AtomToPrintableString(cx, atom); + if (!name) { + ok = JS_FALSE; + } else if (pn->pn_op == JSOP_DEFCONST) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_REDECLARED_PARAM, + name); + ok = JS_FALSE; + } else { + currentGetter = js_GetArgument; + currentSetter = js_SetArgument; + ok = js_ReportCompileErrorNumber(cx, ts, NULL, + JSREPORT_WARNING | + JSREPORT_STRICT, + JSMSG_VAR_HIDES_ARG, + name); + } + } else { + if (fun) { + /* Not an argument, must be a redeclared local var. */ + if (clasp == &js_FunctionClass) { + JS_ASSERT(sprop->getter == js_GetLocalVariable); + JS_ASSERT((sprop->flags & SPROP_HAS_SHORTID) && + sprop->shortid < fun->nvars); + } else if (clasp == &js_CallClass) { + if (sprop->getter == js_GetCallVariable) { + /* + * Referencing a variable introduced by a var + * statement in the enclosing function. Check + * that the slot number we have is in range. + */ + JS_ASSERT((sprop->flags & SPROP_HAS_SHORTID) && + sprop->shortid < fun->nvars); + } else { + /* + * A variable introduced through another eval: + * don't use the special getters and setters + * since we can't allocate a slot in the frame. + */ + currentGetter = sprop->getter; + currentSetter = sprop->setter; + } + } + + /* Override the old getter and setter, to handle eval. */ + sprop = js_ChangeNativePropertyAttrs(cx, obj, sprop, + 0, sprop->attrs, + currentGetter, + currentSetter); + if (!sprop) + ok = JS_FALSE; + } + } + } else { + /* + * Property not found in current variable scope: we have not seen + * this variable before. Define a new local variable by adding a + * property to the function's scope, allocating one slot in the + * function's frame. Global variables and any locals declared in + * with statement bodies are handled at runtime, by script prolog + * JSOP_DEFVAR bytecodes generated for slot-less vars. + */ + sprop = NULL; + if (prop) { + OBJ_DROP_PROPERTY(cx, pobj, prop); + prop = NULL; + } + if (currentGetter == js_GetCallVariable) { + /* Can't increase fun->nvars in an active frame! */ + currentGetter = clasp->getProperty; + currentSetter = clasp->setProperty; + } + if (currentGetter == js_GetLocalVariable && + atom != cx->runtime->atomState.argumentsAtom && + fp->scopeChain == obj && + !js_InWithStatement(tc)) { + if (!js_AddNativeProperty(cx, obj, (jsid)atom, + currentGetter, currentSetter, + SPROP_INVALID_SLOT, + pn2->pn_attrs | JSPROP_SHARED, + SPROP_HAS_SHORTID, fun->nvars)) { + ok = JS_FALSE; + } + fun->nvars++; + } + } + + if (js_MatchToken(cx, ts, TOK_ASSIGN)) { + if (CURRENT_TOKEN(ts).t_op != JSOP_NOP) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_BAD_VAR_INIT); + ok = JS_FALSE; + } else { + pn2->pn_expr = AssignExpr(cx, ts, tc); + if (!pn2->pn_expr) { + ok = JS_FALSE; + } else { + pn2->pn_op = (pn->pn_op == JSOP_DEFCONST) + ? JSOP_SETCONST + : JSOP_SETNAME; + if (atom == cx->runtime->atomState.argumentsAtom) + tc->flags |= TCF_FUN_HEAVYWEIGHT; + } + } + } + + if (prop) + OBJ_DROP_PROPERTY(cx, pobj, prop); + if (!ok) + return NULL; + } while (js_MatchToken(cx, ts, TOK_COMMA)); + + pn->pn_pos.end = PN_LAST(pn)->pn_pos.end; + return pn; +} + +static JSParseNode * +Expr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) +{ + JSParseNode *pn, *pn2; + + pn = AssignExpr(cx, ts, tc); + if (pn && js_MatchToken(cx, ts, TOK_COMMA)) { + pn2 = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_LIST, tc); + if (!pn2) + return NULL; + pn2->pn_pos.begin = pn->pn_pos.begin; + PN_INIT_LIST_1(pn2, pn); + pn = pn2; + do { + pn2 = AssignExpr(cx, ts, tc); + if (!pn2) + return NULL; + PN_APPEND(pn, pn2); + } while (js_MatchToken(cx, ts, TOK_COMMA)); + pn->pn_pos.end = PN_LAST(pn)->pn_pos.end; + } + return pn; +} + +static JSParseNode * +AssignExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) +{ + JSParseNode *pn, *pn2; + JSTokenType tt; + JSOp op; + + CHECK_RECURSION(); + + pn = CondExpr(cx, ts, tc); + if (!pn) + return NULL; + + tt = js_GetToken(cx, ts); +#if JS_HAS_GETTER_SETTER + if (tt == TOK_NAME) { + tt = CheckGetterOrSetter(cx, ts, TOK_ASSIGN); + if (tt == TOK_ERROR) + return NULL; + } +#endif + if (tt != TOK_ASSIGN) { + js_UngetToken(ts); + return pn; + } + + op = CURRENT_TOKEN(ts).t_op; + for (pn2 = pn; pn2->pn_type == TOK_RP; pn2 = pn2->pn_kid) + continue; + switch (pn2->pn_type) { + case TOK_NAME: + pn2->pn_op = JSOP_SETNAME; + if (pn2->pn_atom == cx->runtime->atomState.argumentsAtom) + tc->flags |= TCF_FUN_HEAVYWEIGHT; + break; + case TOK_DOT: + pn2->pn_op = JSOP_SETPROP; + break; + case TOK_LB: + pn2->pn_op = JSOP_SETELEM; + break; +#if JS_HAS_LVALUE_RETURN + case TOK_LP: + pn2->pn_op = JSOP_SETCALL; + break; +#endif + default: + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_BAD_LEFTSIDE_OF_ASS); + return NULL; + } + pn = NewBinary(cx, TOK_ASSIGN, op, pn2, AssignExpr(cx, ts, tc), tc); + return pn; +} + +static JSParseNode * +CondExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) +{ + JSParseNode *pn, *pn1, *pn2, *pn3; +#if JS_HAS_IN_OPERATOR + uintN oldflags; +#endif /* JS_HAS_IN_OPERATOR */ + + pn = OrExpr(cx, ts, tc); + if (pn && js_MatchToken(cx, ts, TOK_HOOK)) { + pn1 = pn; + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_TERNARY, tc); + if (!pn) + return NULL; +#if JS_HAS_IN_OPERATOR + /* + * Always accept the 'in' operator in the middle clause of a ternary, + * where it's unambiguous, even if we might be parsing the init of a + * for statement. + */ + oldflags = tc->flags; + tc->flags &= ~TCF_IN_FOR_INIT; +#endif /* JS_HAS_IN_OPERATOR */ + pn2 = AssignExpr(cx, ts, tc); +#if JS_HAS_IN_OPERATOR + tc->flags = oldflags | (tc->flags & TCF_FUN_FLAGS); +#endif /* JS_HAS_IN_OPERATOR */ + + if (!pn2) + return NULL; + MUST_MATCH_TOKEN(TOK_COLON, JSMSG_COLON_IN_COND); + pn3 = AssignExpr(cx, ts, tc); + if (!pn3) + return NULL; + pn->pn_pos.begin = pn1->pn_pos.begin; + pn->pn_pos.end = pn3->pn_pos.end; + pn->pn_kid1 = pn1; + pn->pn_kid2 = pn2; + pn->pn_kid3 = pn3; + } + return pn; +} + +static JSParseNode * +OrExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) +{ + JSParseNode *pn; + + pn = AndExpr(cx, ts, tc); + if (pn && js_MatchToken(cx, ts, TOK_OR)) + pn = NewBinary(cx, TOK_OR, JSOP_OR, pn, OrExpr(cx, ts, tc), tc); + return pn; +} + +static JSParseNode * +AndExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) +{ + JSParseNode *pn; + + pn = BitOrExpr(cx, ts, tc); + if (pn && js_MatchToken(cx, ts, TOK_AND)) + pn = NewBinary(cx, TOK_AND, JSOP_AND, pn, AndExpr(cx, ts, tc), tc); + return pn; +} + +static JSParseNode * +BitOrExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) +{ + JSParseNode *pn; + + pn = BitXorExpr(cx, ts, tc); + while (pn && js_MatchToken(cx, ts, TOK_BITOR)) { + pn = NewBinary(cx, TOK_BITOR, JSOP_BITOR, pn, BitXorExpr(cx, ts, tc), + tc); + } + return pn; +} + +static JSParseNode * +BitXorExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) +{ + JSParseNode *pn; + + pn = BitAndExpr(cx, ts, tc); + while (pn && js_MatchToken(cx, ts, TOK_BITXOR)) { + pn = NewBinary(cx, TOK_BITXOR, JSOP_BITXOR, pn, BitAndExpr(cx, ts, tc), + tc); + } + return pn; +} + +static JSParseNode * +BitAndExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) +{ + JSParseNode *pn; + + pn = EqExpr(cx, ts, tc); + while (pn && js_MatchToken(cx, ts, TOK_BITAND)) + pn = NewBinary(cx, TOK_BITAND, JSOP_BITAND, pn, EqExpr(cx, ts, tc), tc); + return pn; +} + +static JSParseNode * +EqExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) +{ + JSParseNode *pn; + JSOp op; + + pn = RelExpr(cx, ts, tc); + while (pn && js_MatchToken(cx, ts, TOK_EQOP)) { + op = CURRENT_TOKEN(ts).t_op; + pn = NewBinary(cx, TOK_EQOP, op, pn, RelExpr(cx, ts, tc), tc); + } + return pn; +} + +static JSParseNode * +RelExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) +{ + JSParseNode *pn; + JSTokenType tt; + JSOp op; +#if JS_HAS_IN_OPERATOR + uintN inForInitFlag = tc->flags & TCF_IN_FOR_INIT; + + /* + * Uses of the in operator in ShiftExprs are always unambiguous, + * so unset the flag that prohibits recognizing it. + */ + tc->flags &= ~TCF_IN_FOR_INIT; +#endif /* JS_HAS_IN_OPERATOR */ + + pn = ShiftExpr(cx, ts, tc); + while (pn && + (js_MatchToken(cx, ts, TOK_RELOP) +#if JS_HAS_IN_OPERATOR + /* + * Recognize the 'in' token as an operator only if we're not + * currently in the init expr of a for loop. + */ + || (inForInitFlag == 0 && js_MatchToken(cx, ts, TOK_IN)) +#endif /* JS_HAS_IN_OPERATOR */ +#if JS_HAS_INSTANCEOF + || js_MatchToken(cx, ts, TOK_INSTANCEOF) +#endif /* JS_HAS_INSTANCEOF */ + )) { + tt = CURRENT_TOKEN(ts).type; + op = CURRENT_TOKEN(ts).t_op; + pn = NewBinary(cx, tt, op, pn, ShiftExpr(cx, ts, tc), tc); + } +#if JS_HAS_IN_OPERATOR + /* Restore previous state of inForInit flag. */ + tc->flags |= inForInitFlag; +#endif /* JS_HAS_IN_OPERATOR */ + + return pn; +} + +static JSParseNode * +ShiftExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) +{ + JSParseNode *pn; + JSOp op; + + pn = AddExpr(cx, ts, tc); + while (pn && js_MatchToken(cx, ts, TOK_SHOP)) { + op = CURRENT_TOKEN(ts).t_op; + pn = NewBinary(cx, TOK_SHOP, op, pn, AddExpr(cx, ts, tc), tc); + } + return pn; +} + +static JSParseNode * +AddExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) +{ + JSParseNode *pn; + JSTokenType tt; + JSOp op; + + pn = MulExpr(cx, ts, tc); + while (pn && + (js_MatchToken(cx, ts, TOK_PLUS) || + js_MatchToken(cx, ts, TOK_MINUS))) { + tt = CURRENT_TOKEN(ts).type; + op = (tt == TOK_PLUS) ? JSOP_ADD : JSOP_SUB; + pn = NewBinary(cx, tt, op, pn, MulExpr(cx, ts, tc), tc); + } + return pn; +} + +static JSParseNode * +MulExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) +{ + JSParseNode *pn; + JSTokenType tt; + JSOp op; + + pn = UnaryExpr(cx, ts, tc); + while (pn && + (js_MatchToken(cx, ts, TOK_STAR) || + js_MatchToken(cx, ts, TOK_DIVOP))) { + tt = CURRENT_TOKEN(ts).type; + op = CURRENT_TOKEN(ts).t_op; + pn = NewBinary(cx, tt, op, pn, UnaryExpr(cx, ts, tc), tc); + } + return pn; +} + +static JSParseNode * +SetLvalKid(JSContext *cx, JSTokenStream *ts, JSParseNode *pn, JSParseNode *kid, + const char *name) +{ + while (kid->pn_type == TOK_RP) + kid = kid->pn_kid; + if (kid->pn_type != TOK_NAME && + kid->pn_type != TOK_DOT && +#if JS_HAS_LVALUE_RETURN + (kid->pn_type != TOK_LP || kid->pn_op != JSOP_CALL) && +#endif + kid->pn_type != TOK_LB) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_BAD_OPERAND, name); + return NULL; + } + pn->pn_kid = kid; + return kid; +} + +static const char *incop_name_str[] = {"increment", "decrement"}; + +static JSBool +SetIncOpKid(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, + JSParseNode *pn, JSParseNode *kid, + JSTokenType tt, JSBool preorder) +{ + JSOp op; + + kid = SetLvalKid(cx, ts, pn, kid, incop_name_str[tt == TOK_DEC]); + if (!kid) + return JS_FALSE; + switch (kid->pn_type) { + case TOK_NAME: + op = (tt == TOK_INC) + ? (preorder ? JSOP_INCNAME : JSOP_NAMEINC) + : (preorder ? JSOP_DECNAME : JSOP_NAMEDEC); + if (kid->pn_atom == cx->runtime->atomState.argumentsAtom) + tc->flags |= TCF_FUN_HEAVYWEIGHT; + break; + + case TOK_DOT: + op = (tt == TOK_INC) + ? (preorder ? JSOP_INCPROP : JSOP_PROPINC) + : (preorder ? JSOP_DECPROP : JSOP_PROPDEC); + break; + +#if JS_HAS_LVALUE_RETURN + case TOK_LP: + kid->pn_op = JSOP_SETCALL; +#endif + case TOK_LB: + op = (tt == TOK_INC) + ? (preorder ? JSOP_INCELEM : JSOP_ELEMINC) + : (preorder ? JSOP_DECELEM : JSOP_ELEMDEC); + break; + + default: + JS_ASSERT(0); + op = JSOP_NOP; + } + pn->pn_op = op; + return JS_TRUE; +} + +static JSParseNode * +UnaryExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) +{ + JSTokenType tt; + JSParseNode *pn, *pn2; + + ts->flags |= TSF_REGEXP; + tt = js_GetToken(cx, ts); + ts->flags &= ~TSF_REGEXP; + + switch (tt) { + case TOK_UNARYOP: + case TOK_PLUS: + case TOK_MINUS: + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_UNARY, tc); + if (!pn) + return NULL; + pn->pn_type = TOK_UNARYOP; /* PLUS and MINUS are binary */ + pn->pn_op = CURRENT_TOKEN(ts).t_op; + pn2 = UnaryExpr(cx, ts, tc); + if (!pn2) + return NULL; + pn->pn_pos.end = pn2->pn_pos.end; + pn->pn_kid = pn2; + break; + + case TOK_INC: + case TOK_DEC: + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_UNARY, tc); + if (!pn) + return NULL; + pn2 = MemberExpr(cx, ts, tc, JS_TRUE); + if (!pn2) + return NULL; + if (!SetIncOpKid(cx, ts, tc, pn, pn2, tt, JS_TRUE)) + return NULL; + pn->pn_pos.end = pn2->pn_pos.end; + break; + + case TOK_DELETE: + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_UNARY, tc); + if (!pn) + return NULL; + pn2 = UnaryExpr(cx, ts, tc); + if (!pn2) + return NULL; + pn->pn_pos.end = pn2->pn_pos.end; + + /* + * Under ECMA3, deleting any unary expression is valid -- it simply + * returns true. Here we strip off any parentheses. + */ + while (pn2->pn_type == TOK_RP) + pn2 = pn2->pn_kid; + pn->pn_kid = pn2; + break; + + case TOK_ERROR: + return NULL; + + default: + js_UngetToken(ts); + pn = MemberExpr(cx, ts, tc, JS_TRUE); + if (!pn) + return NULL; + + /* Don't look across a newline boundary for a postfix incop. */ + if (ON_CURRENT_LINE(ts, pn->pn_pos)) { + tt = js_PeekTokenSameLine(cx, ts); + if (tt == TOK_INC || tt == TOK_DEC) { + (void) js_GetToken(cx, ts); + pn2 = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_UNARY, tc); + if (!pn2) + return NULL; + if (!SetIncOpKid(cx, ts, tc, pn2, pn, tt, JS_FALSE)) + return NULL; + pn2->pn_pos.begin = pn->pn_pos.begin; + pn = pn2; + } + } + break; + } + return pn; +} + +static JSBool +ArgumentList(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, + JSParseNode *listNode) +{ + JSBool matched; + + ts->flags |= TSF_REGEXP; + matched = js_MatchToken(cx, ts, TOK_RP); + ts->flags &= ~TSF_REGEXP; + if (!matched) { + do { + JSParseNode *argNode = AssignExpr(cx, ts, tc); + if (!argNode) + return JS_FALSE; + PN_APPEND(listNode, argNode); + } while (js_MatchToken(cx, ts, TOK_COMMA)); + + if (js_GetToken(cx, ts) != TOK_RP) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_PAREN_AFTER_ARGS); + return JS_FALSE; + } + } + return JS_TRUE; +} + +static JSParseNode * +MemberExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, + JSBool allowCallSyntax) +{ + JSParseNode *pn, *pn2, *pn3; + JSTokenType tt; + + CHECK_RECURSION(); + + /* Check for new expression first. */ + ts->flags |= TSF_REGEXP; + tt = js_PeekToken(cx, ts); + ts->flags &= ~TSF_REGEXP; + if (tt == TOK_NEW) { + (void) js_GetToken(cx, ts); + + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_LIST, tc); + if (!pn) + return NULL; + pn2 = MemberExpr(cx, ts, tc, JS_FALSE); + if (!pn2) + return NULL; + pn->pn_op = JSOP_NEW; + PN_INIT_LIST_1(pn, pn2); + pn->pn_pos.begin = pn2->pn_pos.begin; + + if (js_MatchToken(cx, ts, TOK_LP) && !ArgumentList(cx, ts, tc, pn)) + return NULL; + if (pn->pn_count > ARGC_LIMIT) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_TOO_MANY_CON_ARGS); + return NULL; + } + pn->pn_pos.end = PN_LAST(pn)->pn_pos.end; + } else { + pn = PrimaryExpr(cx, ts, tc); + if (!pn) + return NULL; + } + + while ((tt = js_GetToken(cx, ts)) > TOK_EOF) { + if (tt == TOK_DOT) { + pn2 = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_NAME, tc); + if (!pn2) + return NULL; + MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NAME_AFTER_DOT); + pn2->pn_pos.begin = pn->pn_pos.begin; + pn2->pn_pos.end = CURRENT_TOKEN(ts).pos.end; + pn2->pn_op = JSOP_GETPROP; + pn2->pn_expr = pn; + pn2->pn_atom = CURRENT_TOKEN(ts).t_atom; + } else if (tt == TOK_LB) { + pn2 = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_BINARY, tc); + if (!pn2) + return NULL; + pn3 = Expr(cx, ts, tc); + if (!pn3) + return NULL; + + MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_IN_INDEX); + pn2->pn_pos.begin = pn->pn_pos.begin; + pn2->pn_pos.end = CURRENT_TOKEN(ts).pos.end; + + /* Optimize o['p'] to o.p by rewriting pn2. */ + if (pn3->pn_type == TOK_STRING) { + pn2->pn_type = TOK_DOT; + pn2->pn_op = JSOP_GETPROP; + pn2->pn_arity = PN_NAME; + pn2->pn_expr = pn; + pn2->pn_atom = pn3->pn_atom; + } else { + pn2->pn_op = JSOP_GETELEM; + pn2->pn_left = pn; + pn2->pn_right = pn3; + } + } else if (allowCallSyntax && tt == TOK_LP) { + pn2 = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_LIST, tc); + if (!pn2) + return NULL; + + /* Pick JSOP_EVAL and flag tc as heavyweight if eval(...). */ + pn2->pn_op = JSOP_CALL; + if (pn->pn_op == JSOP_NAME && + pn->pn_atom == cx->runtime->atomState.evalAtom) { + pn2->pn_op = JSOP_EVAL; + tc->flags |= TCF_FUN_HEAVYWEIGHT; + } + + PN_INIT_LIST_1(pn2, pn); + pn2->pn_pos.begin = pn->pn_pos.begin; + + if (!ArgumentList(cx, ts, tc, pn2)) + return NULL; + if (pn2->pn_count > ARGC_LIMIT) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_TOO_MANY_FUN_ARGS); + return NULL; + } + pn2->pn_pos.end = PN_LAST(pn2)->pn_pos.end; + } else { + js_UngetToken(ts); + return pn; + } + + pn = pn2; + } + if (tt == TOK_ERROR) + return NULL; + return pn; +} + +static JSParseNode * +PrimaryExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) +{ + JSTokenType tt; + JSParseNode *pn, *pn2, *pn3; + char *badWord; +#if JS_HAS_GETTER_SETTER + JSAtom *atom; + JSRuntime *rt; +#endif + +#if JS_HAS_SHARP_VARS + JSParseNode *defsharp; + JSBool notsharp; + + defsharp = NULL; + notsharp = JS_FALSE; + again: + /* + * Control flows here after #n= is scanned. If the following primary is + * not valid after such a "sharp variable" definition, the token type case + * should set notsharp. + */ +#endif + + CHECK_RECURSION(); + + ts->flags |= TSF_REGEXP; + tt = js_GetToken(cx, ts); + ts->flags &= ~TSF_REGEXP; + +#if JS_HAS_GETTER_SETTER + if (tt == TOK_NAME) { + tt = CheckGetterOrSetter(cx, ts, TOK_FUNCTION); + if (tt == TOK_ERROR) + return NULL; + } +#endif + + switch (tt) { +#if JS_HAS_LEXICAL_CLOSURE + case TOK_FUNCTION: + pn = FunctionExpr(cx, ts, tc); + if (!pn) + return NULL; + break; +#endif + +#if JS_HAS_INITIALIZERS + case TOK_LB: + { + JSBool matched; + jsuint atomIndex; + + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_LIST, tc); + if (!pn) + return NULL; + pn->pn_type = TOK_RB; + pn->pn_extra = JS_FALSE; + +#if JS_HAS_SHARP_VARS + if (defsharp) { + PN_INIT_LIST_1(pn, defsharp); + defsharp = NULL; + } else +#endif + PN_INIT_LIST(pn); + + ts->flags |= TSF_REGEXP; + matched = js_MatchToken(cx, ts, TOK_RB); + ts->flags &= ~TSF_REGEXP; + if (!matched) { + for (atomIndex = 0; atomIndex < ATOM_INDEX_LIMIT; atomIndex++) { + ts->flags |= TSF_REGEXP; + tt = js_PeekToken(cx, ts); + ts->flags &= ~TSF_REGEXP; + if (tt == TOK_RB) { + pn->pn_extra = JS_TRUE; + break; + } + + if (tt == TOK_COMMA) { + /* So CURRENT_TOKEN gets TOK_COMMA and not TOK_LB. */ + js_MatchToken(cx, ts, TOK_COMMA); + pn2 = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_NULLARY, tc); + } else { + pn2 = AssignExpr(cx, ts, tc); + } + if (!pn2) + return NULL; + PN_APPEND(pn, pn2); + + if (tt != TOK_COMMA) { + /* If we didn't already match TOK_COMMA in above case. */ + if (!js_MatchToken(cx, ts, TOK_COMMA)) + break; + } + } + + MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_AFTER_LIST); + } + pn->pn_pos.end = CURRENT_TOKEN(ts).pos.end; + return pn; + } + + case TOK_LC: + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_LIST, tc); + if (!pn) + return NULL; + pn->pn_type = TOK_RC; + +#if JS_HAS_SHARP_VARS + if (defsharp) { + PN_INIT_LIST_1(pn, defsharp); + defsharp = NULL; + } else +#endif + PN_INIT_LIST(pn); + + if (!js_MatchToken(cx, ts, TOK_RC)) { + do { + JSOp op; + + tt = js_GetToken(cx, ts); + switch (tt) { + case TOK_NUMBER: + pn3 = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_NULLARY, tc); + if (pn3) + pn3->pn_dval = CURRENT_TOKEN(ts).t_dval; + break; + case TOK_NAME: +#if JS_HAS_GETTER_SETTER + atom = CURRENT_TOKEN(ts).t_atom; + rt = cx->runtime; + if (atom == rt->atomState.getAtom || + atom == rt->atomState.setAtom) { + op = (atom == rt->atomState.getAtom) + ? JSOP_GETTER + : JSOP_SETTER; + if (js_MatchToken(cx, ts, TOK_NAME)) { + pn3 = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_NAME, + tc); + if (!pn3) + return NULL; + pn3->pn_atom = CURRENT_TOKEN(ts).t_atom; + pn3->pn_expr = NULL; + + /* We have to fake a 'function' token here. */ + CURRENT_TOKEN(ts).t_op = JSOP_NOP; + CURRENT_TOKEN(ts).type = TOK_FUNCTION; + pn2 = FunctionExpr(cx, ts, tc); + pn2 = NewBinary(cx, TOK_COLON, op, pn3, pn2, tc); + goto skip; + } + } + /* else fall thru ... */ +#endif + case TOK_STRING: + pn3 = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_NULLARY, tc); + if (pn3) + pn3->pn_atom = CURRENT_TOKEN(ts).t_atom; + break; + case TOK_RC: + if (!js_ReportCompileErrorNumber(cx, ts, NULL, + JSREPORT_WARNING | + JSREPORT_STRICT, + JSMSG_TRAILING_COMMA)) { + return NULL; + } + goto end_obj_init; + default: + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_BAD_PROP_ID); + return NULL; + } + + tt = js_GetToken(cx, ts); +#if JS_HAS_GETTER_SETTER + if (tt == TOK_NAME) { + tt = CheckGetterOrSetter(cx, ts, TOK_COLON); + if (tt == TOK_ERROR) + return NULL; + } +#endif + if (tt != TOK_COLON) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_COLON_AFTER_ID); + return NULL; + } + op = CURRENT_TOKEN(ts).t_op; + pn2 = NewBinary(cx, TOK_COLON, op, pn3, AssignExpr(cx, ts, tc), + tc); +#if JS_HAS_GETTER_SETTER + skip: +#endif + if (!pn2) + return NULL; + PN_APPEND(pn, pn2); + } while (js_MatchToken(cx, ts, TOK_COMMA)); + + MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_LIST); + } + end_obj_init: + pn->pn_pos.end = CURRENT_TOKEN(ts).pos.end; + return pn; + +#if JS_HAS_SHARP_VARS + case TOK_DEFSHARP: + if (defsharp) + goto badsharp; + defsharp = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_UNARY, tc); + if (!defsharp) + return NULL; + defsharp->pn_kid = NULL; + defsharp->pn_num = (jsint) CURRENT_TOKEN(ts).t_dval; + goto again; + + case TOK_USESHARP: + /* Check for forward/dangling references at runtime, to allow eval. */ + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_NULLARY, tc); + if (!pn) + return NULL; + pn->pn_num = (jsint) CURRENT_TOKEN(ts).t_dval; + notsharp = JS_TRUE; + break; +#endif /* JS_HAS_SHARP_VARS */ +#endif /* JS_HAS_INITIALIZERS */ + + case TOK_LP: + { +#if JS_HAS_IN_OPERATOR + uintN oldflags; +#endif + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_UNARY, tc); + if (!pn) + return NULL; +#if JS_HAS_IN_OPERATOR + /* + * Always accept the 'in' operator in a parenthesized expression, + * where it's unambiguous, even if we might be parsing the init of a + * for statement. + */ + oldflags = tc->flags; + tc->flags &= ~TCF_IN_FOR_INIT; +#endif + pn2 = Expr(cx, ts, tc); +#if JS_HAS_IN_OPERATOR + tc->flags = oldflags | (tc->flags & TCF_FUN_FLAGS); +#endif + if (!pn2) + return NULL; + + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_IN_PAREN); + pn->pn_type = TOK_RP; + pn->pn_pos.end = CURRENT_TOKEN(ts).pos.end; + pn->pn_kid = pn2; + break; + } + + case TOK_STRING: +#if JS_HAS_SHARP_VARS + notsharp = JS_TRUE; +#endif + /* FALL THROUGH */ + case TOK_NAME: + case TOK_OBJECT: + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_NULLARY, tc); + if (!pn) + return NULL; + pn->pn_op = CURRENT_TOKEN(ts).t_op; + pn->pn_atom = CURRENT_TOKEN(ts).t_atom; + if (tt == TOK_NAME) { + pn->pn_arity = PN_NAME; + pn->pn_expr = NULL; + pn->pn_slot = -1; + pn->pn_attrs = 0; + + /* Unqualified __parent__ and __proto__ uses require activations. */ + if (pn->pn_atom == cx->runtime->atomState.parentAtom || + pn->pn_atom == cx->runtime->atomState.protoAtom) { + tc->flags |= TCF_FUN_HEAVYWEIGHT; + } + } + break; + + case TOK_NUMBER: + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_NULLARY, tc); + if (!pn) + return NULL; + pn->pn_dval = CURRENT_TOKEN(ts).t_dval; +#if JS_HAS_SHARP_VARS + notsharp = JS_TRUE; +#endif + break; + + case TOK_PRIMARY: + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_NULLARY, tc); + if (!pn) + return NULL; + pn->pn_op = CURRENT_TOKEN(ts).t_op; +#if JS_HAS_SHARP_VARS + notsharp = JS_TRUE; +#endif + break; + +#if !JS_HAS_EXPORT_IMPORT + case TOK_EXPORT: + case TOK_IMPORT: +#endif + case TOK_RESERVED: + badWord = js_DeflateString(cx, CURRENT_TOKEN(ts).ptr, + (size_t) CURRENT_TOKEN(ts).pos.end.index + - CURRENT_TOKEN(ts).pos.begin.index); + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_RESERVED_ID, badWord); + JS_free(cx, badWord); + return NULL; + + case TOK_ERROR: + /* The scanner or one of its subroutines reported the error. */ + return NULL; + + default: + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_SYNTAX_ERROR); + return NULL; + } + +#if JS_HAS_SHARP_VARS + if (defsharp) { + if (notsharp) { + badsharp: + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_BAD_SHARP_VAR_DEF); + return NULL; + } + defsharp->pn_kid = pn; + return defsharp; + } +#endif + return pn; +} + +static JSBool +ContainsVarStmt(JSParseNode *pn) +{ + JSParseNode *pn2; + + if (!pn) + return JS_FALSE; + switch (pn->pn_arity) { + case PN_LIST: + if (pn->pn_type == TOK_VAR) + return JS_TRUE; + for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) { + if (ContainsVarStmt(pn2)) + return JS_TRUE; + } + break; + case PN_TERNARY: + return ContainsVarStmt(pn->pn_kid1) || + ContainsVarStmt(pn->pn_kid2) || + ContainsVarStmt(pn->pn_kid3); + case PN_BINARY: + /* + * Limit recursion if pn is a binary expression, which can't contain a + * var statement. + */ + if (pn->pn_op != JSOP_NOP) + return JS_FALSE; + return ContainsVarStmt(pn->pn_left) || ContainsVarStmt(pn->pn_right); + case PN_UNARY: + if (pn->pn_op != JSOP_NOP) + return JS_FALSE; + return ContainsVarStmt(pn->pn_kid); + default:; + } + return JS_FALSE; +} + +/* + * Fold from one constant type to another. + * XXX handles only strings and numbers for now + */ +static JSBool +FoldType(JSContext *cx, JSParseNode *pn, JSTokenType type) +{ + if (pn->pn_type != type) { + switch (type) { + case TOK_NUMBER: + if (pn->pn_type == TOK_STRING) { + jsdouble d; + if (!js_ValueToNumber(cx, ATOM_KEY(pn->pn_atom), &d)) + return JS_FALSE; + pn->pn_dval = d; + pn->pn_type = TOK_NUMBER; + pn->pn_op = JSOP_NUMBER; + } + break; + + case TOK_STRING: + if (pn->pn_type == TOK_NUMBER) { + JSString *str = js_NumberToString(cx, pn->pn_dval); + if (!str) + return JS_FALSE; + pn->pn_atom = js_AtomizeString(cx, str, 0); + if (!pn->pn_atom) + return JS_FALSE; + pn->pn_type = TOK_STRING; + pn->pn_op = JSOP_STRING; + } + break; + + default:; + } + } + return JS_TRUE; +} + +/* + * Fold two numeric constants. Beware that pn1 and pn2 are recycled, unless + * one of them aliases pn, so you can't safely fetch pn2->pn_next, e.g., after + * a successful call to this function. + */ +static JSBool +FoldBinaryNumeric(JSContext *cx, JSOp op, JSParseNode *pn1, JSParseNode *pn2, + JSParseNode *pn, JSTreeContext *tc) +{ + jsdouble d, d2; + int32 i, j; + uint32 u; + + JS_ASSERT(pn1->pn_type == TOK_NUMBER && pn2->pn_type == TOK_NUMBER); + d = pn1->pn_dval; + d2 = pn2->pn_dval; + switch (op) { + case JSOP_LSH: + case JSOP_RSH: + if (!js_DoubleToECMAInt32(cx, d, &i)) + return JS_FALSE; + if (!js_DoubleToECMAInt32(cx, d2, &j)) + return JS_FALSE; + j &= 31; + d = (op == JSOP_LSH) ? i << j : i >> j; + break; + + case JSOP_URSH: + if (!js_DoubleToECMAUint32(cx, d, &u)) + return JS_FALSE; + if (!js_DoubleToECMAInt32(cx, d2, &j)) + return JS_FALSE; + j &= 31; + d = u >> j; + break; + + case JSOP_ADD: + d += d2; + break; + + case JSOP_SUB: + d -= d2; + break; + + case JSOP_MUL: + d *= d2; + break; + + case JSOP_DIV: + if (d2 == 0) { +#if defined(XP_WIN) + /* XXX MSVC miscompiles such that (NaN == 0) */ + if (JSDOUBLE_IS_NaN(d2)) + d = *cx->runtime->jsNaN; + else +#endif + if (d == 0 || JSDOUBLE_IS_NaN(d)) + d = *cx->runtime->jsNaN; + else if ((JSDOUBLE_HI32(d) ^ JSDOUBLE_HI32(d2)) >> 31) + d = *cx->runtime->jsNegativeInfinity; + else + d = *cx->runtime->jsPositiveInfinity; + } else { + d /= d2; + } + break; + + case JSOP_MOD: + if (d2 == 0) { + d = *cx->runtime->jsNaN; + } else { +#if defined(XP_WIN) + /* Workaround MS fmod bug where 42 % (1/0) => NaN, not 42. */ + if (!(JSDOUBLE_IS_FINITE(d) && JSDOUBLE_IS_INFINITE(d2))) +#endif + d = fmod(d, d2); + } + break; + + default:; + } + + /* Take care to allow pn1 or pn2 to alias pn. */ + if (pn1 != pn) + RecycleTree(pn1, tc); + if (pn2 != pn) + RecycleTree(pn2, tc); + pn->pn_type = TOK_NUMBER; + pn->pn_op = JSOP_NUMBER; + pn->pn_arity = PN_NULLARY; + pn->pn_dval = d; + return JS_TRUE; +} + +JSBool +js_FoldConstants(JSContext *cx, JSParseNode *pn, JSTreeContext *tc) +{ + JSParseNode *pn1 = NULL, *pn2 = NULL, *pn3 = NULL; + int stackDummy; + + if (!JS_CHECK_STACK_SIZE(cx, stackDummy)) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_OVER_RECURSED); + return JS_FALSE; + } + + switch (pn->pn_arity) { + case PN_FUNC: + if (!js_FoldConstants(cx, pn->pn_body, tc)) + return JS_FALSE; + break; + + case PN_LIST: + /* Save the list head in pn1 for later use. */ + for (pn1 = pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) { + if (!js_FoldConstants(cx, pn2, tc)) + return JS_FALSE; + } + break; + + case PN_TERNARY: + /* Any kid may be null (e.g. for (;;)). */ + pn1 = pn->pn_kid1; + pn2 = pn->pn_kid2; + pn3 = pn->pn_kid3; + if (pn1 && !js_FoldConstants(cx, pn1, tc)) + return JS_FALSE; + if (pn2 && !js_FoldConstants(cx, pn2, tc)) + return JS_FALSE; + if (pn3 && !js_FoldConstants(cx, pn3, tc)) + return JS_FALSE; + break; + + case PN_BINARY: + /* First kid may be null (for default case in switch). */ + pn1 = pn->pn_left; + pn2 = pn->pn_right; + if (pn1 && !js_FoldConstants(cx, pn1, tc)) + return JS_FALSE; + if (!js_FoldConstants(cx, pn2, tc)) + return JS_FALSE; + break; + + case PN_UNARY: + /* Our kid may be null (e.g. return; vs. return e;). */ + pn1 = pn->pn_kid; + if (pn1 && !js_FoldConstants(cx, pn1, tc)) + return JS_FALSE; + break; + + case PN_NAME: + /* + * Skip pn1 down along a chain of dotted member expressions to avoid + * excessive recursion. Our only goal here is to fold constants (if + * any) in the primary expression operand to the left of the first + * dot in the chain. + */ + pn1 = pn->pn_expr; + while (pn1 && pn1->pn_arity == PN_NAME) + pn1 = pn1->pn_expr; + if (pn1 && !js_FoldConstants(cx, pn1, tc)) + return JS_FALSE; + break; + + case PN_NULLARY: + break; + } + + switch (pn->pn_type) { + case TOK_IF: + if (ContainsVarStmt(pn2) || ContainsVarStmt(pn3)) + break; + /* FALL THROUGH */ + + case TOK_HOOK: + /* Reduce 'if (C) T; else E' into T for true C, E for false. */ + switch (pn1->pn_type) { + case TOK_NUMBER: + if (pn1->pn_dval == 0) + pn2 = pn3; + break; + case TOK_STRING: + if (JSSTRING_LENGTH(ATOM_TO_STRING(pn1->pn_atom)) == 0) + pn2 = pn3; + break; + case TOK_PRIMARY: + if (pn1->pn_op == JSOP_TRUE) + break; + if (pn1->pn_op == JSOP_FALSE || pn1->pn_op == JSOP_NULL) { + pn2 = pn3; + break; + } + /* FALL THROUGH */ + default: + /* Early return to dodge common code that copies pn2 to pn. */ + return JS_TRUE; + } + + if (pn2) { + /* pn2 is the then- or else-statement subtree to compile. */ + PN_MOVE_NODE(pn, pn2); + } else { + /* False condition and no else: make pn an empty statement. */ + pn->pn_type = TOK_SEMI; + pn->pn_arity = PN_UNARY; + pn->pn_kid = NULL; + } + RecycleTree(pn2, tc); + if (pn3 && pn3 != pn2) + RecycleTree(pn3, tc); + break; + + case TOK_PLUS: + if (pn->pn_arity == PN_LIST) { + size_t length, length2; + jschar *chars; + JSString *str, *str2; + + /* + * Any string literal term with all others number or string means + * this is a concatenation. If any term is not a string or number + * literal, we can't fold. + */ + JS_ASSERT(pn->pn_count > 2); + if (pn->pn_extra & PNX_CANTFOLD) + return JS_TRUE; + if (pn->pn_extra != PNX_STRCAT) + goto do_binary_op; + + /* Ok, we're concatenating: convert non-string constant operands. */ + length = 0; + for (pn2 = pn1; pn2; pn2 = pn2->pn_next) { + if (!FoldType(cx, pn2, TOK_STRING)) + return JS_FALSE; + /* XXX fold only if all operands convert to string */ + if (pn2->pn_type != TOK_STRING) + return JS_TRUE; + length += ATOM_TO_STRING(pn2->pn_atom)->length; + } + + /* Allocate a new buffer and string descriptor for the result. */ + chars = (jschar *) JS_malloc(cx, (length + 1) * sizeof(jschar)); + if (!chars) + return JS_FALSE; + str = js_NewString(cx, chars, length, 0); + if (!str) { + JS_free(cx, chars); + return JS_FALSE; + } + + /* Fill the buffer, advancing chars and recycling kids as we go. */ + for (pn2 = pn1; pn2; pn2 = pn3) { + str2 = ATOM_TO_STRING(pn2->pn_atom); + length2 = str2->length; + js_strncpy(chars, str2->chars, length2); + chars += length2; + pn3 = pn2->pn_next; + RecycleTree(pn2, tc); + } + *chars = 0; + + /* Atomize the result string and mutate pn to refer to it. */ + pn->pn_atom = js_AtomizeString(cx, str, 0); + if (!pn->pn_atom) + return JS_FALSE; + pn->pn_type = TOK_STRING; + pn->pn_op = JSOP_STRING; + pn->pn_arity = PN_NULLARY; + break; + } + + /* Handle a binary string concatenation. */ + JS_ASSERT(pn->pn_arity == PN_BINARY); + if (pn1->pn_type == TOK_STRING || pn2->pn_type == TOK_STRING) { + JSString *left, *right, *str; + + if (!FoldType(cx, (pn1->pn_type != TOK_STRING) ? pn1 : pn2, + TOK_STRING)) { + return JS_FALSE; + } + if (pn1->pn_type != TOK_STRING || pn2->pn_type != TOK_STRING) + return JS_TRUE; + left = ATOM_TO_STRING(pn1->pn_atom); + right = ATOM_TO_STRING(pn2->pn_atom); + str = js_ConcatStrings(cx, left, right); + if (!str) + return JS_FALSE; + pn->pn_atom = js_AtomizeString(cx, str, 0); + if (!pn->pn_atom) + return JS_FALSE; + pn->pn_type = TOK_STRING; + pn->pn_op = JSOP_STRING; + pn->pn_arity = PN_NULLARY; + RecycleTree(pn1, tc); + RecycleTree(pn2, tc); + break; + } + + /* Can't concatenate string literals, let's try numbers. */ + goto do_binary_op; + + case TOK_STAR: + /* The * in 'import *;' parses as a nullary star node. */ + if (pn->pn_arity == PN_NULLARY) + break; + /* FALL THROUGH */ + + case TOK_SHOP: + case TOK_MINUS: + case TOK_DIVOP: + do_binary_op: + if (pn->pn_arity == PN_LIST) { + JS_ASSERT(pn->pn_count > 2); + for (pn2 = pn1; pn2; pn2 = pn2->pn_next) { + if (!FoldType(cx, pn2, TOK_NUMBER)) + return JS_FALSE; + /* XXX fold only if all operands convert to number */ + if (pn2->pn_type != TOK_NUMBER) + break; + } + if (!pn2) { + JSOp op = pn->pn_op; + + pn2 = pn1->pn_next; + pn3 = pn2->pn_next; + if (!FoldBinaryNumeric(cx, op, pn1, pn2, pn, tc)) + return JS_FALSE; + while ((pn2 = pn3) != NULL) { + pn3 = pn2->pn_next; + if (!FoldBinaryNumeric(cx, op, pn, pn2, pn, tc)) + return JS_FALSE; + } + } + } else { + JS_ASSERT(pn->pn_arity == PN_BINARY); + if (!FoldType(cx, pn1, TOK_NUMBER) || + !FoldType(cx, pn2, TOK_NUMBER)) { + return JS_FALSE; + } + if (pn1->pn_type == TOK_NUMBER && pn2->pn_type == TOK_NUMBER) { + if (!FoldBinaryNumeric(cx, pn->pn_op, pn1, pn2, pn, tc)) + return JS_FALSE; + } + } + break; + + case TOK_UNARYOP: + if (pn1->pn_type == TOK_NUMBER) { + jsdouble d; + int32 i; + + /* Operate on one numeric constant. */ + d = pn1->pn_dval; + switch (pn->pn_op) { + case JSOP_BITNOT: + if (!js_DoubleToECMAInt32(cx, d, &i)) + return JS_FALSE; + d = ~i; + break; + + case JSOP_NEG: +#ifdef HPUX + /* + * Negation of a zero doesn't produce a negative + * zero on HPUX. Perform the operation by bit + * twiddling. + */ + JSDOUBLE_HI32(d) ^= JSDOUBLE_HI32_SIGNBIT; +#else + d = -d; +#endif + break; + + case JSOP_POS: + break; + + case JSOP_NOT: + pn->pn_type = TOK_PRIMARY; + pn->pn_op = (d == 0) ? JSOP_TRUE : JSOP_FALSE; + pn->pn_arity = PN_NULLARY; + /* FALL THROUGH */ + + default: + /* Return early to dodge the common TOK_NUMBER code. */ + return JS_TRUE; + } + pn->pn_type = TOK_NUMBER; + pn->pn_op = JSOP_NUMBER; + pn->pn_arity = PN_NULLARY; + pn->pn_dval = d; + RecycleTree(pn1, tc); + } + break; + + default:; + } + + return JS_TRUE; +} diff --git a/src/dom/js/jsparse.h b/src/dom/js/jsparse.h new file mode 100644 index 000000000..1c6ff893a --- /dev/null +++ b/src/dom/js/jsparse.h @@ -0,0 +1,337 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsparse_h___ +#define jsparse_h___ +/* + * JS parser definitions. + */ +#include "jsprvtd.h" +#include "jspubtd.h" +#include "jsscan.h" + +JS_BEGIN_EXTERN_C + +/* + * Parsing builds a tree of nodes that directs code generation. This tree is + * not a concrete syntax tree in all respects (for example, || and && are left + * associative, but (A && B && C) translates into the right-associated tree + * > so that code generation can emit a left-associative branch + * around when A is false). Nodes are labeled by token type, with a + * JSOp secondary label when needed: + * + * Label Variant Members + * ----- ------- ------- + * + * TOK_FUNCTION func pn_funAtom: atom holding function object containing + * arg and var properties. We create the function + * object at parse (not emit) time to specialize arg + * and var bytecodes early. + * pn_body: TOK_LC node for function body statements + * pn_flags: TCF_FUN_* flags (see jsemit.h) collected + * while parsing the function's body + * pn_tryCount: of try statements in function + * + * + * TOK_LC list pn_head: list of pn_count statements + * TOK_EXPORT list pn_head: list of pn_count TOK_NAMEs or one TOK_STAR + * (which is not a multiply node) + * TOK_IMPORT list pn_head: list of pn_count sub-trees of the form + * a.b.*, a[b].*, a.*, a.b, or a[b] -- but never a. + * Each member is expressed with TOK_DOT or TOK_LB. + * Each sub-tree's root node has a pn_op in the set + * JSOP_IMPORT{ALL,PROP,ELEM} + * TOK_IF ternary pn_kid1: cond, pn_kid2: then, pn_kid3: else or null + * TOK_SWITCH binary pn_left: discriminant + * pn_right: list of TOK_CASE nodes, with at most one + * TOK_DEFAULT node + * TOK_CASE, binary pn_left: case expr or null if TOK_DEFAULT + * TOK_DEFAULT pn_right: TOK_LC node for this case's statements + * TOK_WHILE binary pn_left: cond, pn_right: body + * TOK_DO binary pn_left: body, pn_right: cond + * TOK_FOR binary pn_left: either + * for/in loop: a binary TOK_IN node with + * pn_left: TOK_VAR or TOK_NAME to left of 'in' + * pn_right: object expr to right of 'in' + * for(;;) loop: a ternary TOK_RESERVED node with + * pn_kid1: init expr before first ';' + * pn_kid2: cond expr before second ';' + * pn_kid3: update expr after second ';' + * any kid may be null + * pn_right: body + * TOK_THROW unary pn_op: JSOP_THROW, pn_kid: exception + * TOK_TRY ternary pn_kid1: try block + * pn_kid2: catch blocks or null + * pn_kid3: finally block or null + * TOK_CATCH ternary pn_kid1: PN_NAME node for catch var (with pn_expr + * null or the catch guard expression) + * pn_kid2: more catch blocks or null + * pn_kid3: catch block statements + * TOK_BREAK name pn_atom: label or null + * TOK_CONTINUE name pn_atom: label or null + * TOK_WITH binary pn_left: head expr, pn_right: body + * TOK_VAR list pn_head: list of pn_count TOK_NAME nodes + * each name node has + * pn_atom: variable name + * pn_expr: initializer or null + * TOK_RETURN unary pn_kid: return expr or null + * TOK_SEMI unary pn_kid: expr or null statement + * TOK_COLON name pn_atom: label, pn_expr: labeled statement + * + * + * All left-associated binary trees of the same type are optimized into lists + * to avoid recursion when processing expression chains. + * TOK_COMMA list pn_head: list of pn_count comma-separated exprs + * TOK_ASSIGN binary pn_left: lvalue, pn_right: rvalue + * pn_op: JSOP_ADD for +=, etc. + * TOK_HOOK ternary pn_kid1: cond, pn_kid2: then, pn_kid3: else + * TOK_OR binary pn_left: first in || chain, pn_right: rest of chain + * TOK_AND binary pn_left: first in && chain, pn_right: rest of chain + * TOK_BITOR binary pn_left: left-assoc | expr, pn_right: ^ expr + * TOK_BITXOR binary pn_left: left-assoc ^ expr, pn_right: & expr + * TOK_BITAND binary pn_left: left-assoc & expr, pn_right: EQ expr + * TOK_EQOP binary pn_left: left-assoc EQ expr, pn_right: REL expr + * pn_op: JSOP_EQ, JSOP_NE, JSOP_NEW_EQ, JSOP_NEW_NE + * TOK_RELOP binary pn_left: left-assoc REL expr, pn_right: SH expr + * pn_op: JSOP_LT, JSOP_LE, JSOP_GT, JSOP_GE + * TOK_SHOP binary pn_left: left-assoc SH expr, pn_right: ADD expr + * pn_op: JSOP_LSH, JSOP_RSH, JSOP_URSH + * TOK_PLUS, binary pn_left: left-assoc ADD expr, pn_right: MUL expr + * pn_extra: if a left-associated binary TOK_PLUS + * tree has been flattened into a list (see above + * under ), pn_extra will contain + * PNX_STRCAT if at least one list element is a + * string literal (TOK_STRING); if such a list has + * any non-string, non-number term, pn_extra will + * contain PNX_CANTFOLD. + * pn_ + * TOK_MINUS pn_op: JSOP_ADD, JSOP_SUB + * TOK_STAR, binary pn_left: left-assoc MUL expr, pn_right: UNARY expr + * TOK_DIVOP pn_op: JSOP_MUL, JSOP_DIV, JSOP_MOD + * TOK_UNARYOP unary pn_kid: UNARY expr, pn_op: JSOP_NEG, JSOP_POS, + * JSOP_NOT, JSOP_BITNOT, JSOP_TYPEOF, JSOP_VOID + * TOK_INC, unary pn_kid: MEMBER expr + * TOK_DEC + * TOK_NEW list pn_head: list of ctor, arg1, arg2, ... argN + * pn_count: 1 + N (where N is number of args) + * ctor is a MEMBER expr + * TOK_DELETE unary pn_kid: MEMBER expr + * TOK_DOT name pn_expr: MEMBER expr to left of . + * pn_atom: name to right of . + * TOK_LB binary pn_left: MEMBER expr to left of [ + * pn_right: expr between [ and ] + * TOK_LP list pn_head: list of call, arg1, arg2, ... argN + * pn_count: 1 + N (where N is number of args) + * call is a MEMBER expr naming a callable object + * TOK_RB list pn_head: list of pn_count array element exprs + * [,,] holes are represented by TOK_COMMA nodes + * #n=[...] produces TOK_DEFSHARP at head of list + * pn_extra: true if extra comma at end + * TOK_RC list pn_head: list of pn_count TOK_COLON nodes where + * each has pn_left: property id, pn_right: value + * #n={...} produces TOK_DEFSHARP at head of list + * TOK_DEFSHARP unary pn_num: jsint value of n in #n= + * pn_kid: null for #n=[...] and #n={...}, primary + * if #n=primary for function, paren, name, object + * literal expressions + * TOK_USESHARP nullary pn_num: jsint value of n in #n# + * TOK_RP unary pn_kid: parenthesized expression + * TOK_NAME, name pn_atom: name, string, or object atom + * TOK_STRING, pn_op: JSOP_NAME, JSOP_STRING, or JSOP_OBJECT + * TOK_OBJECT If JSOP_NAME, pn_op may be JSOP_*ARG or JSOP_*VAR + * with pn_slot >= 0 and pn_attrs telling const-ness + * TOK_NUMBER dval pn_dval: double value of numeric literal + * TOK_PRIMARY nullary pn_op: JSOp bytecode + */ +typedef enum JSParseNodeArity { + PN_FUNC = -3, + PN_LIST = -2, + PN_TERNARY = 3, + PN_BINARY = 2, + PN_UNARY = 1, + PN_NAME = -1, + PN_NULLARY = 0 +} JSParseNodeArity; + +struct JSParseNode { + JSTokenType pn_type; + JSTokenPos pn_pos; + JSOp pn_op; + ptrdiff_t pn_offset; /* first generated bytecode offset */ + JSParseNodeArity pn_arity; + union { + struct { /* TOK_FUNCTION node */ + JSAtom *funAtom; /* atomized function object */ + JSParseNode *body; /* TOK_LC list of statements */ + uint32 flags; /* accumulated tree context flags */ + uint32 tryCount; /* count of try statements in body */ + } func; + struct { /* list of next-linked nodes */ + JSParseNode *head; /* first node in list */ + JSParseNode **tail; /* ptr to ptr to last node in list */ + uint32 count; /* number of nodes in list */ + uint32 extra; /* extra comma flag for [1,2,,] */ + } list; + struct { /* ternary: if, for(;;), ?: */ + JSParseNode *kid1; /* condition, discriminant, etc. */ + JSParseNode *kid2; /* then-part, case list, etc. */ + JSParseNode *kid3; /* else-part, default case, etc. */ + } ternary; + struct { /* two kids if binary */ + JSParseNode *left; + JSParseNode *right; + jsval val; /* switch case value */ + } binary; + struct { /* one kid if unary */ + JSParseNode *kid; + jsint num; /* -1 or sharp variable number */ + } unary; + struct { /* name, labeled statement, etc. */ + JSAtom *atom; /* name or label atom, null if slot */ + JSParseNode *expr; /* object or initializer */ + jsint slot; /* -1 or arg or local var slot */ + uintN attrs; /* attributes if local var or const */ + } name; + jsdouble dval; /* aligned numeric literal value */ + } pn_u; + JSParseNode *pn_next; /* to align dval and pn_u on RISCs */ +}; + +#define pn_funAtom pn_u.func.funAtom +#define pn_body pn_u.func.body +#define pn_flags pn_u.func.flags +#define pn_tryCount pn_u.func.tryCount +#define pn_head pn_u.list.head +#define pn_tail pn_u.list.tail +#define pn_count pn_u.list.count +#define pn_extra pn_u.list.extra +#define pn_kid1 pn_u.ternary.kid1 +#define pn_kid2 pn_u.ternary.kid2 +#define pn_kid3 pn_u.ternary.kid3 +#define pn_left pn_u.binary.left +#define pn_right pn_u.binary.right +#define pn_val pn_u.binary.val +#define pn_kid pn_u.unary.kid +#define pn_num pn_u.unary.num +#define pn_atom pn_u.name.atom +#define pn_expr pn_u.name.expr +#define pn_slot pn_u.name.slot +#define pn_attrs pn_u.name.attrs +#define pn_dval pn_u.dval + +/* PN_LIST pn_extra flags. */ +#define PNX_STRCAT 0x1 /* TOK_PLUS list has string term */ +#define PNX_CANTFOLD 0x2 /* TOK_PLUS list has unfoldable term */ + +/* + * Move pn2 into pn, preserving pn->pn_pos and pn->pn_offset and handing off + * any kids in pn2->pn_u, by clearing pn2. + */ +#define PN_MOVE_NODE(pn, pn2) \ + JS_BEGIN_MACRO \ + (pn)->pn_type = (pn2)->pn_type; \ + (pn)->pn_op = (pn2)->pn_op; \ + (pn)->pn_arity = (pn2)->pn_arity; \ + (pn)->pn_u = (pn2)->pn_u; \ + PN_CLEAR_NODE(pn2); \ + JS_END_MACRO + +#define PN_CLEAR_NODE(pn) \ + JS_BEGIN_MACRO \ + (pn)->pn_type = TOK_EOF; \ + (pn)->pn_op = JSOP_NOP; \ + (pn)->pn_arity = PN_NULLARY; \ + JS_END_MACRO + +/* True if pn is a parsenode representing a literal constant. */ +#define PN_IS_CONSTANT(pn) \ + ((pn)->pn_type == TOK_NUMBER || \ + (pn)->pn_type == TOK_STRING || \ + ((pn)->pn_type == TOK_PRIMARY && (pn)->pn_op != JSOP_THIS)) + +/* + * Compute a pointer to the last JSParseNode element in a singly-linked list. + * NB: list must be non-empty for correct PN_LAST usage! + */ +#define PN_LAST(list) \ + ((JSParseNode *)((char *)(list)->pn_tail - offsetof(JSParseNode, pn_next))) + +#define PN_INIT_LIST(list) \ + JS_BEGIN_MACRO \ + (list)->pn_head = NULL; \ + (list)->pn_tail = &(list)->pn_head; \ + (list)->pn_count = 0; \ + JS_END_MACRO + +#define PN_INIT_LIST_1(list, pn) \ + JS_BEGIN_MACRO \ + (list)->pn_head = (pn); \ + (list)->pn_tail = &(pn)->pn_next; \ + (list)->pn_count = 1; \ + JS_END_MACRO + +#define PN_APPEND(list, pn) \ + JS_BEGIN_MACRO \ + *(list)->pn_tail = (pn); \ + (list)->pn_tail = &(pn)->pn_next; \ + (list)->pn_count++; \ + JS_END_MACRO + +/* + * Parse a top-level JS script. + * + * The caller must prevent the GC from running while this function is active, + * because atoms and function newborns are not rooted yet. + */ +extern JS_FRIEND_API(JSParseNode *) +js_ParseTokenStream(JSContext *cx, JSObject *chain, JSTokenStream *ts); + +extern JS_FRIEND_API(JSBool) +js_CompileTokenStream(JSContext *cx, JSObject *chain, JSTokenStream *ts, + JSCodeGenerator *cg); + +extern JSBool +js_CompileFunctionBody(JSContext *cx, JSTokenStream *ts, JSFunction *fun); + +extern JSBool +js_FoldConstants(JSContext *cx, JSParseNode *pn, JSTreeContext *tc); + +JS_END_EXTERN_C + +#endif /* jsparse_h___ */ diff --git a/src/dom/js/jsprf.c b/src/dom/js/jsprf.c new file mode 100644 index 000000000..36bf92428 --- /dev/null +++ b/src/dom/js/jsprf.c @@ -0,0 +1,1212 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* +** Portable safe sprintf code. +** +** Author: Kipp E.B. Hickman +*/ +#include "jsstddef.h" +#include +#include +#include +#include +#include "jsprf.h" +#include "jslong.h" +#include "jsutil.h" /* Added by JSIFY */ + +/* +** Note: on some platforms va_list is defined as an array, +** and requires array notation. +*/ +#ifdef HAVE_VA_COPY +#define VARARGS_ASSIGN(foo, bar) VA_COPY(foo,bar) +#elif defined(HAVE_VA_LIST_AS_ARRAY) +#define VARARGS_ASSIGN(foo, bar) foo[0] = bar[0] +#else +#define VARARGS_ASSIGN(foo, bar) (foo) = (bar) +#endif + +/* +** WARNING: This code may *NOT* call JS_LOG (because JS_LOG calls it) +*/ + +/* +** XXX This needs to be internationalized! +*/ + +typedef struct SprintfStateStr SprintfState; + +struct SprintfStateStr { + int (*stuff)(SprintfState *ss, const char *sp, JSUint32 len); + + char *base; + char *cur; + JSUint32 maxlen; + + int (*func)(void *arg, const char *sp, JSUint32 len); + void *arg; +}; + +/* +** Numbered Arguement State +*/ +struct NumArgState{ + int type; /* type of the current ap */ + va_list ap; /* point to the corresponding position on ap */ +}; + +#define NAS_DEFAULT_NUM 20 /* default number of NumberedArgumentState array */ + + +#define TYPE_INT16 0 +#define TYPE_UINT16 1 +#define TYPE_INTN 2 +#define TYPE_UINTN 3 +#define TYPE_INT32 4 +#define TYPE_UINT32 5 +#define TYPE_INT64 6 +#define TYPE_UINT64 7 +#define TYPE_STRING 8 +#define TYPE_DOUBLE 9 +#define TYPE_INTSTR 10 +#define TYPE_UNKNOWN 20 + +#define FLAG_LEFT 0x1 +#define FLAG_SIGNED 0x2 +#define FLAG_SPACED 0x4 +#define FLAG_ZEROS 0x8 +#define FLAG_NEG 0x10 + +/* +** Fill into the buffer using the data in src +*/ +static int fill2(SprintfState *ss, const char *src, int srclen, int width, + int flags) +{ + char space = ' '; + int rv; + + width -= srclen; + if ((width > 0) && ((flags & FLAG_LEFT) == 0)) { /* Right adjusting */ + if (flags & FLAG_ZEROS) { + space = '0'; + } + while (--width >= 0) { + rv = (*ss->stuff)(ss, &space, 1); + if (rv < 0) { + return rv; + } + } + } + + /* Copy out the source data */ + rv = (*ss->stuff)(ss, src, (JSUint32)srclen); + if (rv < 0) { + return rv; + } + + if ((width > 0) && ((flags & FLAG_LEFT) != 0)) { /* Left adjusting */ + while (--width >= 0) { + rv = (*ss->stuff)(ss, &space, 1); + if (rv < 0) { + return rv; + } + } + } + return 0; +} + +/* +** Fill a number. The order is: optional-sign zero-filling conversion-digits +*/ +static int fill_n(SprintfState *ss, const char *src, int srclen, int width, + int prec, int type, int flags) +{ + int zerowidth = 0; + int precwidth = 0; + int signwidth = 0; + int leftspaces = 0; + int rightspaces = 0; + int cvtwidth; + int rv; + char sign; + + if ((type & 1) == 0) { + if (flags & FLAG_NEG) { + sign = '-'; + signwidth = 1; + } else if (flags & FLAG_SIGNED) { + sign = '+'; + signwidth = 1; + } else if (flags & FLAG_SPACED) { + sign = ' '; + signwidth = 1; + } + } + cvtwidth = signwidth + srclen; + + if (prec > 0) { + if (prec > srclen) { + precwidth = prec - srclen; /* Need zero filling */ + cvtwidth += precwidth; + } + } + + if ((flags & FLAG_ZEROS) && (prec < 0)) { + if (width > cvtwidth) { + zerowidth = width - cvtwidth; /* Zero filling */ + cvtwidth += zerowidth; + } + } + + if (flags & FLAG_LEFT) { + if (width > cvtwidth) { + /* Space filling on the right (i.e. left adjusting) */ + rightspaces = width - cvtwidth; + } + } else { + if (width > cvtwidth) { + /* Space filling on the left (i.e. right adjusting) */ + leftspaces = width - cvtwidth; + } + } + while (--leftspaces >= 0) { + rv = (*ss->stuff)(ss, " ", 1); + if (rv < 0) { + return rv; + } + } + if (signwidth) { + rv = (*ss->stuff)(ss, &sign, 1); + if (rv < 0) { + return rv; + } + } + while (--precwidth >= 0) { + rv = (*ss->stuff)(ss, "0", 1); + if (rv < 0) { + return rv; + } + } + while (--zerowidth >= 0) { + rv = (*ss->stuff)(ss, "0", 1); + if (rv < 0) { + return rv; + } + } + rv = (*ss->stuff)(ss, src, (JSUint32)srclen); + if (rv < 0) { + return rv; + } + while (--rightspaces >= 0) { + rv = (*ss->stuff)(ss, " ", 1); + if (rv < 0) { + return rv; + } + } + return 0; +} + +/* +** Convert a long into its printable form +*/ +static int cvt_l(SprintfState *ss, long num, int width, int prec, int radix, + int type, int flags, const char *hexp) +{ + char cvtbuf[100]; + char *cvt; + int digits; + + /* according to the man page this needs to happen */ + if ((prec == 0) && (num == 0)) { + return 0; + } + + /* + ** Converting decimal is a little tricky. In the unsigned case we + ** need to stop when we hit 10 digits. In the signed case, we can + ** stop when the number is zero. + */ + cvt = cvtbuf + sizeof(cvtbuf); + digits = 0; + while (num) { + int digit = (((unsigned long)num) % radix) & 0xF; + *--cvt = hexp[digit]; + digits++; + num = (long)(((unsigned long)num) / radix); + } + if (digits == 0) { + *--cvt = '0'; + digits++; + } + + /* + ** Now that we have the number converted without its sign, deal with + ** the sign and zero padding. + */ + return fill_n(ss, cvt, digits, width, prec, type, flags); +} + +/* +** Convert a 64-bit integer into its printable form +*/ +static int cvt_ll(SprintfState *ss, JSInt64 num, int width, int prec, int radix, + int type, int flags, const char *hexp) +{ + char cvtbuf[100]; + char *cvt; + int digits; + JSInt64 rad; + + /* according to the man page this needs to happen */ + if ((prec == 0) && (JSLL_IS_ZERO(num))) { + return 0; + } + + /* + ** Converting decimal is a little tricky. In the unsigned case we + ** need to stop when we hit 10 digits. In the signed case, we can + ** stop when the number is zero. + */ + JSLL_I2L(rad, radix); + cvt = cvtbuf + sizeof(cvtbuf); + digits = 0; + while (!JSLL_IS_ZERO(num)) { + JSInt32 digit; + JSInt64 quot, rem; + JSLL_UDIVMOD(", &rem, num, rad); + JSLL_L2I(digit, rem); + *--cvt = hexp[digit & 0xf]; + digits++; + num = quot; + } + if (digits == 0) { + *--cvt = '0'; + digits++; + } + + /* + ** Now that we have the number converted without its sign, deal with + ** the sign and zero padding. + */ + return fill_n(ss, cvt, digits, width, prec, type, flags); +} + +/* +** Convert a double precision floating point number into its printable +** form. +** +** XXX stop using sprintf to convert floating point +*/ +static int cvt_f(SprintfState *ss, double d, const char *fmt0, const char *fmt1) +{ + char fin[20]; + char fout[300]; + int amount = fmt1 - fmt0; + + JS_ASSERT((amount > 0) && (amount < (int)sizeof(fin))); + if (amount >= (int)sizeof(fin)) { + /* Totally bogus % command to sprintf. Just ignore it */ + return 0; + } + memcpy(fin, fmt0, (size_t)amount); + fin[amount] = 0; + + /* Convert floating point using the native sprintf code */ +#ifdef DEBUG + { + const char *p = fin; + while (*p) { + JS_ASSERT(*p != 'L'); + p++; + } + } +#endif + sprintf(fout, fin, d); + + /* + ** This assert will catch overflow's of fout, when building with + ** debugging on. At least this way we can track down the evil piece + ** of calling code and fix it! + */ + JS_ASSERT(strlen(fout) < sizeof(fout)); + + return (*ss->stuff)(ss, fout, strlen(fout)); +} + +/* +** Convert a string into its printable form. "width" is the output +** width. "prec" is the maximum number of characters of "s" to output, +** where -1 means until NUL. +*/ +static int cvt_s(SprintfState *ss, const char *s, int width, int prec, + int flags) +{ + int slen; + + if (prec == 0) + return 0; + + /* Limit string length by precision value */ + slen = s ? strlen(s) : 6; + if (prec > 0) { + if (prec < slen) { + slen = prec; + } + } + + /* and away we go */ + return fill2(ss, s ? s : "(null)", slen, width, flags); +} + +/* +** BiuldArgArray stands for Numbered Argument list Sprintf +** for example, +** fmp = "%4$i, %2$d, %3s, %1d"; +** the number must start from 1, and no gap among them +*/ + +static struct NumArgState* BuildArgArray( const char *fmt, va_list ap, int* rv, struct NumArgState* nasArray ) +{ + int number = 0, cn = 0, i; + const char* p; + char c; + struct NumArgState* nas; + + + /* + ** first pass: + ** detemine how many legal % I have got, then allocate space + */ + + p = fmt; + *rv = 0; + i = 0; + while( ( c = *p++ ) != 0 ){ + if( c != '%' ) + continue; + if( ( c = *p++ ) == '%' ) /* skip %% case */ + continue; + + while( c != 0 ){ + if( c > '9' || c < '0' ){ + if( c == '$' ){ /* numbered argument csae */ + if( i > 0 ){ + *rv = -1; + return NULL; + } + number++; + } else { /* non-numbered argument case */ + if( number > 0 ){ + *rv = -1; + return NULL; + } + i = 1; + } + break; + } + + c = *p++; + } + } + + if( number == 0 ){ + return NULL; + } + + + if( number > NAS_DEFAULT_NUM ){ + nas = (struct NumArgState*)malloc( number * sizeof( struct NumArgState ) ); + if( !nas ){ + *rv = -1; + return NULL; + } + } else { + nas = nasArray; + } + + for( i = 0; i < number; i++ ){ + nas[i].type = TYPE_UNKNOWN; + } + + + /* + ** second pass: + ** set nas[].type + */ + + p = fmt; + while( ( c = *p++ ) != 0 ){ + if( c != '%' ) continue; + c = *p++; + if( c == '%' ) continue; + + cn = 0; + while( c && c != '$' ){ /* should imporve error check later */ + cn = cn*10 + c - '0'; + c = *p++; + } + + if( !c || cn < 1 || cn > number ){ + *rv = -1; + break; + } + + /* nas[cn] starts from 0, and make sure nas[cn].type is not assigned */ + cn--; + if( nas[cn].type != TYPE_UNKNOWN ) + continue; + + c = *p++; + + /* width */ + if (c == '*') { + /* not supported feature, for the argument is not numbered */ + *rv = -1; + break; + } + + while ((c >= '0') && (c <= '9')) { + c = *p++; + } + + /* precision */ + if (c == '.') { + c = *p++; + if (c == '*') { + /* not supported feature, for the argument is not numbered */ + *rv = -1; + break; + } + + while ((c >= '0') && (c <= '9')) { + c = *p++; + } + } + + /* size */ + nas[cn].type = TYPE_INTN; + if (c == 'h') { + nas[cn].type = TYPE_INT16; + c = *p++; + } else if (c == 'L') { + /* XXX not quite sure here */ + nas[cn].type = TYPE_INT64; + c = *p++; + } else if (c == 'l') { + nas[cn].type = TYPE_INT32; + c = *p++; + if (c == 'l') { + nas[cn].type = TYPE_INT64; + c = *p++; + } + } + + /* format */ + switch (c) { + case 'd': + case 'c': + case 'i': + case 'o': + case 'u': + case 'x': + case 'X': + break; + + case 'e': + case 'f': + case 'g': + nas[ cn ].type = TYPE_DOUBLE; + break; + + case 'p': + /* XXX should use cpp */ + if (sizeof(void *) == sizeof(JSInt32)) { + nas[ cn ].type = TYPE_UINT32; + } else if (sizeof(void *) == sizeof(JSInt64)) { + nas[ cn ].type = TYPE_UINT64; + } else if (sizeof(void *) == sizeof(JSIntn)) { + nas[ cn ].type = TYPE_UINTN; + } else { + nas[ cn ].type = TYPE_UNKNOWN; + } + break; + + case 'C': + case 'S': + case 'E': + case 'G': + /* XXX not supported I suppose */ + JS_ASSERT(0); + nas[ cn ].type = TYPE_UNKNOWN; + break; + + case 's': + nas[ cn ].type = TYPE_STRING; + break; + + case 'n': + nas[ cn ].type = TYPE_INTSTR; + break; + + default: + JS_ASSERT(0); + nas[ cn ].type = TYPE_UNKNOWN; + break; + } + + /* get a legal para. */ + if( nas[ cn ].type == TYPE_UNKNOWN ){ + *rv = -1; + break; + } + } + + + /* + ** third pass + ** fill the nas[cn].ap + */ + + if( *rv < 0 ){ + if( nas != nasArray ) + JS_DELETE( nas ); + return NULL; + } + + cn = 0; + while( cn < number ){ + if( nas[cn].type == TYPE_UNKNOWN ){ + cn++; + continue; + } + + VARARGS_ASSIGN(nas[cn].ap, ap); + + switch( nas[cn].type ){ + case TYPE_INT16: + case TYPE_UINT16: + case TYPE_INTN: + case TYPE_UINTN: (void)va_arg( ap, JSIntn ); break; + + case TYPE_INT32: (void)va_arg( ap, JSInt32 ); break; + + case TYPE_UINT32: (void)va_arg( ap, JSUint32 ); break; + + case TYPE_INT64: (void)va_arg( ap, JSInt64 ); break; + + case TYPE_UINT64: (void)va_arg( ap, JSUint64 ); break; + + case TYPE_STRING: (void)va_arg( ap, char* ); break; + + case TYPE_INTSTR: (void)va_arg( ap, JSIntn* ); break; + + case TYPE_DOUBLE: (void)va_arg( ap, double ); break; + + default: + if( nas != nasArray ) + JS_DELETE( nas ); + *rv = -1; + return NULL; + } + + cn++; + } + + + return nas; +} + +/* +** The workhorse sprintf code. +*/ +static int dosprintf(SprintfState *ss, const char *fmt, va_list ap) +{ + char c; + int flags, width, prec, radix, type; + union { + char ch; + int i; + long l; + JSInt64 ll; + double d; + const char *s; + int *ip; + } u; + const char *fmt0; + static char *hex = "0123456789abcdef"; + static char *HEX = "0123456789ABCDEF"; + char *hexp; + int rv, i; + struct NumArgState* nas = NULL; + struct NumArgState nasArray[ NAS_DEFAULT_NUM ]; + char pattern[20]; + const char* dolPt = NULL; /* in "%4$.2f", dolPt will poiont to . */ + + + /* + ** build an argument array, IF the fmt is numbered argument + ** list style, to contain the Numbered Argument list pointers + */ + + nas = BuildArgArray( fmt, ap, &rv, nasArray ); + if( rv < 0 ){ + /* the fmt contains error Numbered Argument format, jliu@netscape.com */ + JS_ASSERT(0); + return rv; + } + + while ((c = *fmt++) != 0) { + if (c != '%') { + rv = (*ss->stuff)(ss, fmt - 1, 1); + if (rv < 0) { + return rv; + } + continue; + } + fmt0 = fmt - 1; + + /* + ** Gobble up the % format string. Hopefully we have handled all + ** of the strange cases! + */ + flags = 0; + c = *fmt++; + if (c == '%') { + /* quoting a % with %% */ + rv = (*ss->stuff)(ss, fmt - 1, 1); + if (rv < 0) { + return rv; + } + continue; + } + + if( nas != NULL ){ + /* the fmt contains the Numbered Arguments feature */ + i = 0; + while( c && c != '$' ){ /* should imporve error check later */ + i = ( i * 10 ) + ( c - '0' ); + c = *fmt++; + } + + if( nas[i-1].type == TYPE_UNKNOWN ){ + if( nas && ( nas != nasArray ) ) + JS_DELETE( nas ); + return -1; + } + + ap = nas[i-1].ap; + dolPt = fmt; + c = *fmt++; + } + + /* + * Examine optional flags. Note that we do not implement the + * '#' flag of sprintf(). The ANSI C spec. of the '#' flag is + * somewhat ambiguous and not ideal, which is perhaps why + * the various sprintf() implementations are inconsistent + * on this feature. + */ + while ((c == '-') || (c == '+') || (c == ' ') || (c == '0')) { + if (c == '-') flags |= FLAG_LEFT; + if (c == '+') flags |= FLAG_SIGNED; + if (c == ' ') flags |= FLAG_SPACED; + if (c == '0') flags |= FLAG_ZEROS; + c = *fmt++; + } + if (flags & FLAG_SIGNED) flags &= ~FLAG_SPACED; + if (flags & FLAG_LEFT) flags &= ~FLAG_ZEROS; + + /* width */ + if (c == '*') { + c = *fmt++; + width = va_arg(ap, int); + } else { + width = 0; + while ((c >= '0') && (c <= '9')) { + width = (width * 10) + (c - '0'); + c = *fmt++; + } + } + + /* precision */ + prec = -1; + if (c == '.') { + c = *fmt++; + if (c == '*') { + c = *fmt++; + prec = va_arg(ap, int); + } else { + prec = 0; + while ((c >= '0') && (c <= '9')) { + prec = (prec * 10) + (c - '0'); + c = *fmt++; + } + } + } + + /* size */ + type = TYPE_INTN; + if (c == 'h') { + type = TYPE_INT16; + c = *fmt++; + } else if (c == 'L') { + /* XXX not quite sure here */ + type = TYPE_INT64; + c = *fmt++; + } else if (c == 'l') { + type = TYPE_INT32; + c = *fmt++; + if (c == 'l') { + type = TYPE_INT64; + c = *fmt++; + } + } + + /* format */ + hexp = hex; + switch (c) { + case 'd': case 'i': /* decimal/integer */ + radix = 10; + goto fetch_and_convert; + + case 'o': /* octal */ + radix = 8; + type |= 1; + goto fetch_and_convert; + + case 'u': /* unsigned decimal */ + radix = 10; + type |= 1; + goto fetch_and_convert; + + case 'x': /* unsigned hex */ + radix = 16; + type |= 1; + goto fetch_and_convert; + + case 'X': /* unsigned HEX */ + radix = 16; + hexp = HEX; + type |= 1; + goto fetch_and_convert; + + fetch_and_convert: + switch (type) { + case TYPE_INT16: + u.l = va_arg(ap, int); + if (u.l < 0) { + u.l = -u.l; + flags |= FLAG_NEG; + } + goto do_long; + case TYPE_UINT16: + u.l = va_arg(ap, int) & 0xffff; + goto do_long; + case TYPE_INTN: + u.l = va_arg(ap, int); + if (u.l < 0) { + u.l = -u.l; + flags |= FLAG_NEG; + } + goto do_long; + case TYPE_UINTN: + u.l = (long)va_arg(ap, unsigned int); + goto do_long; + + case TYPE_INT32: + u.l = va_arg(ap, JSInt32); + if (u.l < 0) { + u.l = -u.l; + flags |= FLAG_NEG; + } + goto do_long; + case TYPE_UINT32: + u.l = (long)va_arg(ap, JSUint32); + do_long: + rv = cvt_l(ss, u.l, width, prec, radix, type, flags, hexp); + if (rv < 0) { + return rv; + } + break; + + case TYPE_INT64: + u.ll = va_arg(ap, JSInt64); + if (!JSLL_GE_ZERO(u.ll)) { + JSLL_NEG(u.ll, u.ll); + flags |= FLAG_NEG; + } + goto do_longlong; + case TYPE_UINT64: + u.ll = va_arg(ap, JSUint64); + do_longlong: + rv = cvt_ll(ss, u.ll, width, prec, radix, type, flags, hexp); + if (rv < 0) { + return rv; + } + break; + } + break; + + case 'e': + case 'E': + case 'f': + case 'g': + u.d = va_arg(ap, double); + if( nas != NULL ){ + i = fmt - dolPt; + if( i < (int)sizeof( pattern ) ){ + pattern[0] = '%'; + memcpy( &pattern[1], dolPt, (size_t)i ); + rv = cvt_f(ss, u.d, pattern, &pattern[i+1] ); + } + } else + rv = cvt_f(ss, u.d, fmt0, fmt); + + if (rv < 0) { + return rv; + } + break; + + case 'c': + u.ch = va_arg(ap, int); + if ((flags & FLAG_LEFT) == 0) { + while (width-- > 1) { + rv = (*ss->stuff)(ss, " ", 1); + if (rv < 0) { + return rv; + } + } + } + rv = (*ss->stuff)(ss, &u.ch, 1); + if (rv < 0) { + return rv; + } + if (flags & FLAG_LEFT) { + while (width-- > 1) { + rv = (*ss->stuff)(ss, " ", 1); + if (rv < 0) { + return rv; + } + } + } + break; + + case 'p': + if (sizeof(void *) == sizeof(JSInt32)) { + type = TYPE_UINT32; + } else if (sizeof(void *) == sizeof(JSInt64)) { + type = TYPE_UINT64; + } else if (sizeof(void *) == sizeof(int)) { + type = TYPE_UINTN; + } else { + JS_ASSERT(0); + break; + } + radix = 16; + goto fetch_and_convert; + +#if 0 + case 'C': + case 'S': + case 'E': + case 'G': + /* XXX not supported I suppose */ + JS_ASSERT(0); + break; +#endif + + case 's': + u.s = va_arg(ap, const char*); + rv = cvt_s(ss, u.s, width, prec, flags); + if (rv < 0) { + return rv; + } + break; + + case 'n': + u.ip = va_arg(ap, int*); + if (u.ip) { + *u.ip = ss->cur - ss->base; + } + break; + + default: + /* Not a % token after all... skip it */ +#if 0 + JS_ASSERT(0); +#endif + rv = (*ss->stuff)(ss, "%", 1); + if (rv < 0) { + return rv; + } + rv = (*ss->stuff)(ss, fmt - 1, 1); + if (rv < 0) { + return rv; + } + } + } + + /* Stuff trailing NUL */ + rv = (*ss->stuff)(ss, "\0", 1); + + if( nas && ( nas != nasArray ) ){ + JS_DELETE( nas ); + } + + return rv; +} + +/************************************************************************/ + +static int FuncStuff(SprintfState *ss, const char *sp, JSUint32 len) +{ + int rv; + + rv = (*ss->func)(ss->arg, sp, len); + if (rv < 0) { + return rv; + } + ss->maxlen += len; + return 0; +} + +JS_PUBLIC_API(JSUint32) JS_sxprintf(JSStuffFunc func, void *arg, + const char *fmt, ...) +{ + va_list ap; + int rv; + + va_start(ap, fmt); + rv = JS_vsxprintf(func, arg, fmt, ap); + va_end(ap); + return rv; +} + +JS_PUBLIC_API(JSUint32) JS_vsxprintf(JSStuffFunc func, void *arg, + const char *fmt, va_list ap) +{ + SprintfState ss; + int rv; + + ss.stuff = FuncStuff; + ss.func = func; + ss.arg = arg; + ss.maxlen = 0; + rv = dosprintf(&ss, fmt, ap); + return (rv < 0) ? (JSUint32)-1 : ss.maxlen; +} + +/* +** Stuff routine that automatically grows the malloc'd output buffer +** before it overflows. +*/ +static int GrowStuff(SprintfState *ss, const char *sp, JSUint32 len) +{ + ptrdiff_t off; + char *newbase; + JSUint32 newlen; + + off = ss->cur - ss->base; + if (off + len >= ss->maxlen) { + /* Grow the buffer */ + newlen = ss->maxlen + ((len > 32) ? len : 32); + if (ss->base) { + newbase = (char*) realloc(ss->base, newlen); + } else { + newbase = (char*) malloc(newlen); + } + if (!newbase) { + /* Ran out of memory */ + return -1; + } + ss->base = newbase; + ss->maxlen = newlen; + ss->cur = ss->base + off; + } + + /* Copy data */ + while (len) { + --len; + *ss->cur++ = *sp++; + } + JS_ASSERT((JSUint32)(ss->cur - ss->base) <= ss->maxlen); + return 0; +} + +/* +** sprintf into a malloc'd buffer +*/ +JS_PUBLIC_API(char *) JS_smprintf(const char *fmt, ...) +{ + va_list ap; + char *rv; + + va_start(ap, fmt); + rv = JS_vsmprintf(fmt, ap); + va_end(ap); + return rv; +} + +/* +** Free memory allocated, for the caller, by JS_smprintf +*/ +JS_PUBLIC_API(void) JS_smprintf_free(char *mem) +{ + JS_DELETE(mem); +} + +JS_PUBLIC_API(char *) JS_vsmprintf(const char *fmt, va_list ap) +{ + SprintfState ss; + int rv; + + ss.stuff = GrowStuff; + ss.base = 0; + ss.cur = 0; + ss.maxlen = 0; + rv = dosprintf(&ss, fmt, ap); + if (rv < 0) { + if (ss.base) { + JS_DELETE(ss.base); + } + return 0; + } + return ss.base; +} + +/* +** Stuff routine that discards overflow data +*/ +static int LimitStuff(SprintfState *ss, const char *sp, JSUint32 len) +{ + JSUint32 limit = ss->maxlen - (ss->cur - ss->base); + + if (len > limit) { + len = limit; + } + while (len) { + --len; + *ss->cur++ = *sp++; + } + return 0; +} + +/* +** sprintf into a fixed size buffer. Make sure there is a NUL at the end +** when finished. +*/ +JS_PUBLIC_API(JSUint32) JS_snprintf(char *out, JSUint32 outlen, const char *fmt, ...) +{ + va_list ap; + int rv; + + JS_ASSERT((JSInt32)outlen > 0); + if ((JSInt32)outlen <= 0) { + return 0; + } + + va_start(ap, fmt); + rv = JS_vsnprintf(out, outlen, fmt, ap); + va_end(ap); + return rv; +} + +JS_PUBLIC_API(JSUint32) JS_vsnprintf(char *out, JSUint32 outlen,const char *fmt, + va_list ap) +{ + SprintfState ss; + JSUint32 n; + + JS_ASSERT((JSInt32)outlen > 0); + if ((JSInt32)outlen <= 0) { + return 0; + } + + ss.stuff = LimitStuff; + ss.base = out; + ss.cur = out; + ss.maxlen = outlen; + (void) dosprintf(&ss, fmt, ap); + + /* If we added chars, and we didn't append a null, do it now. */ + if( (ss.cur != ss.base) && (*(ss.cur - 1) != '\0') ) + *(--ss.cur) = '\0'; + + n = ss.cur - ss.base; + return n ? n - 1 : n; +} + +JS_PUBLIC_API(char *) JS_sprintf_append(char *last, const char *fmt, ...) +{ + va_list ap; + char *rv; + + va_start(ap, fmt); + rv = JS_vsprintf_append(last, fmt, ap); + va_end(ap); + return rv; +} + +JS_PUBLIC_API(char *) JS_vsprintf_append(char *last, const char *fmt, va_list ap) +{ + SprintfState ss; + int rv; + + ss.stuff = GrowStuff; + if (last) { + int lastlen = strlen(last); + ss.base = last; + ss.cur = last + lastlen; + ss.maxlen = lastlen; + } else { + ss.base = 0; + ss.cur = 0; + ss.maxlen = 0; + } + rv = dosprintf(&ss, fmt, ap); + if (rv < 0) { + if (ss.base) { + JS_DELETE(ss.base); + } + return 0; + } + return ss.base; +} + diff --git a/src/dom/js/jsprf.h b/src/dom/js/jsprf.h new file mode 100644 index 000000000..e8cc46c0c --- /dev/null +++ b/src/dom/js/jsprf.h @@ -0,0 +1,148 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsprf_h___ +#define jsprf_h___ + +/* +** API for PR printf like routines. Supports the following formats +** %d - decimal +** %u - unsigned decimal +** %x - unsigned hex +** %X - unsigned uppercase hex +** %o - unsigned octal +** %hd, %hu, %hx, %hX, %ho - 16-bit versions of above +** %ld, %lu, %lx, %lX, %lo - 32-bit versions of above +** %lld, %llu, %llx, %llX, %llo - 64 bit versions of above +** %s - string +** %c - character +** %p - pointer (deals with machine dependent pointer size) +** %f - float +** %g - float +*/ +#include "jstypes.h" +#include +#include + +JS_BEGIN_EXTERN_C + +/* +** sprintf into a fixed size buffer. Guarantees that a NUL is at the end +** of the buffer. Returns the length of the written output, NOT including +** the NUL, or (JSUint32)-1 if an error occurs. +*/ +extern JS_PUBLIC_API(JSUint32) JS_snprintf(char *out, JSUint32 outlen, const char *fmt, ...); + +/* +** sprintf into a malloc'd buffer. Return a pointer to the malloc'd +** buffer on success, NULL on failure. Call "JS_smprintf_free" to release +** the memory returned. +*/ +extern JS_PUBLIC_API(char*) JS_smprintf(const char *fmt, ...); + +/* +** Free the memory allocated, for the caller, by JS_smprintf +*/ +extern JS_PUBLIC_API(void) JS_smprintf_free(char *mem); + +/* +** "append" sprintf into a malloc'd buffer. "last" is the last value of +** the malloc'd buffer. sprintf will append data to the end of last, +** growing it as necessary using realloc. If last is NULL, JS_sprintf_append +** will allocate the initial string. The return value is the new value of +** last for subsequent calls, or NULL if there is a malloc failure. +*/ +extern JS_PUBLIC_API(char*) JS_sprintf_append(char *last, const char *fmt, ...); + +/* +** sprintf into a function. The function "f" is called with a string to +** place into the output. "arg" is an opaque pointer used by the stuff +** function to hold any state needed to do the storage of the output +** data. The return value is a count of the number of characters fed to +** the stuff function, or (JSUint32)-1 if an error occurs. +*/ +typedef JSIntn (*JSStuffFunc)(void *arg, const char *s, JSUint32 slen); + +extern JS_PUBLIC_API(JSUint32) JS_sxprintf(JSStuffFunc f, void *arg, const char *fmt, ...); + +/* +** va_list forms of the above. +*/ +extern JS_PUBLIC_API(JSUint32) JS_vsnprintf(char *out, JSUint32 outlen, const char *fmt, va_list ap); +extern JS_PUBLIC_API(char*) JS_vsmprintf(const char *fmt, va_list ap); +extern JS_PUBLIC_API(char*) JS_vsprintf_append(char *last, const char *fmt, va_list ap); +extern JS_PUBLIC_API(JSUint32) JS_vsxprintf(JSStuffFunc f, void *arg, const char *fmt, va_list ap); + +/* +*************************************************************************** +** FUNCTION: JS_sscanf +** DESCRIPTION: +** JS_sscanf() scans the input character string, performs data +** conversions, and stores the converted values in the data objects +** pointed to by its arguments according to the format control +** string. +** +** JS_sscanf() behaves the same way as the sscanf() function in the +** Standard C Library (stdio.h), with the following exceptions: +** - JS_sscanf() handles the NSPR integer and floating point types, +** such as JSInt16, JSInt32, JSInt64, and JSFloat64, whereas +** sscanf() handles the standard C types like short, int, long, +** and double. +** - JS_sscanf() has no multibyte character support, while sscanf() +** does. +** INPUTS: +** const char *buf +** a character string holding the input to scan +** const char *fmt +** the format control string for the conversions +** ... +** variable number of arguments, each of them is a pointer to +** a data object in which the converted value will be stored +** OUTPUTS: none +** RETURNS: JSInt32 +** The number of values converted and stored. +** RESTRICTIONS: +** Multibyte characters in 'buf' or 'fmt' are not allowed. +*************************************************************************** +*/ + +extern JS_PUBLIC_API(JSInt32) JS_sscanf(const char *buf, const char *fmt, ...); + +JS_END_EXTERN_C + +#endif /* jsprf_h___ */ diff --git a/src/dom/js/jsprvtd.h b/src/dom/js/jsprvtd.h new file mode 100644 index 000000000..f5f1e77f6 --- /dev/null +++ b/src/dom/js/jsprvtd.h @@ -0,0 +1,174 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsprvtd_h___ +#define jsprvtd_h___ +/* + * JS private typename definitions. + * + * This header is included only in other .h files, for convenience and for + * simplicity of type naming. The alternative for structures is to use tags, + * which are named the same as their typedef names (legal in C/C++, and less + * noisy than suffixing the typedef name with "Struct" or "Str"). Instead, + * all .h files that include this file may use the same typedef name, whether + * declaring a pointer to struct type, or defining a member of struct type. + * + * A few fundamental scalar types are defined here too. Neither the scalar + * nor the struct typedefs should change much, therefore the nearly-global + * make dependency induced by this file should not prove painful. + */ + +#include "jspubtd.h" + +/* Scalar typedefs. */ +typedef uint8 jsbytecode; +typedef uint8 jssrcnote; +typedef uint32 jsatomid; + +/* Struct typedefs. */ +typedef struct JSArgumentFormatMap JSArgumentFormatMap; +typedef struct JSCodeGenerator JSCodeGenerator; +typedef struct JSDependentString JSDependentString; +typedef struct JSGCLockHashEntry JSGCLockHashEntry; +typedef struct JSGCRootHashEntry JSGCRootHashEntry; +typedef struct JSGCThing JSGCThing; +typedef struct JSParseNode JSParseNode; +typedef struct JSSharpObjectMap JSSharpObjectMap; +typedef struct JSToken JSToken; +typedef struct JSTokenPos JSTokenPos; +typedef struct JSTokenPtr JSTokenPtr; +typedef struct JSTokenStream JSTokenStream; +typedef struct JSTreeContext JSTreeContext; +typedef struct JSTryNote JSTryNote; + +/* Friend "Advanced API" typedefs. */ +typedef struct JSAtom JSAtom; +typedef struct JSAtomList JSAtomList; +typedef struct JSAtomListElement JSAtomListElement; +typedef struct JSAtomMap JSAtomMap; +typedef struct JSAtomState JSAtomState; +typedef struct JSCodeSpec JSCodeSpec; +typedef struct JSPrinter JSPrinter; +typedef struct JSRegExp JSRegExp; +typedef struct JSRegExpStatics JSRegExpStatics; +typedef struct JSScope JSScope; +typedef struct JSScopeOps JSScopeOps; +typedef struct JSScopeProperty JSScopeProperty; +typedef struct JSStackFrame JSStackFrame; +typedef struct JSStackHeader JSStackHeader; +typedef struct JSSubString JSSubString; + +/* "Friend" types used by jscntxt.h and jsdbgapi.h. */ +typedef enum JSTrapStatus { + JSTRAP_ERROR, + JSTRAP_CONTINUE, + JSTRAP_RETURN, + JSTRAP_THROW, + JSTRAP_LIMIT +} JSTrapStatus; + +typedef JSTrapStatus +(* JS_DLL_CALLBACK JSTrapHandler)(JSContext *cx, JSScript *script, jsbytecode *pc, + jsval *rval, void *closure); + +typedef JSBool +(* JS_DLL_CALLBACK JSWatchPointHandler)(JSContext *cx, JSObject *obj, jsval id, + jsval old, jsval *newp, void *closure); + +/* called just after script creation */ +typedef void +(* JS_DLL_CALLBACK JSNewScriptHook)(JSContext *cx, + const char *filename, /* URL of script */ + uintN lineno, /* line script starts */ + JSScript *script, + JSFunction *fun, + void *callerdata); + +/* called just before script destruction */ +typedef void +(* JS_DLL_CALLBACK JSDestroyScriptHook)(JSContext *cx, + JSScript *script, + void *callerdata); + +typedef void +(* JS_DLL_CALLBACK JSSourceHandler)(const char *filename, uintN lineno, + jschar *str, size_t length, + void **listenerTSData, void *closure); + +/* +* This hook captures high level script execution and function calls +* (JS or native). +* It is used by JS_SetExecuteHook to hook top level scripts and by +* JS_SetCallHook to hook function calls. +* It will get called twice per script or function call: +* just before execution begins and just after it finishes. In both cases +* the 'current' frame is that of the executing code. +* +* The 'before' param is JS_TRUE for the hook invocation before the execution +* and JS_FALSE for the invocation after the code has run. +* +* The 'ok' param is significant only on the post execution invocation to +* signify whether or not the code completed 'normally'. +* +* The 'closure' param is as passed to JS_SetExecuteHook or JS_SetCallHook +* for the 'before'invocation, but is whatever value is returned from that +* invocation for the 'after' invocation. Thus, the hook implementor *could* +* allocate a stucture in the 'before' invocation and return a pointer +* to that structure. The pointer would then be handed to the hook for +* the 'after' invocation. Alternately, the 'before' could just return the +* same value as in 'closure' to cause the 'after' invocation to be called +* with the same 'closure' value as the 'before'. +* +* Returning NULL in the 'before' hook will cause the 'after' hook to +* NOT be called. +*/ + +typedef void * +(* JS_DLL_CALLBACK JSInterpreterHook)(JSContext *cx, JSStackFrame *fp, JSBool before, + JSBool *ok, void *closure); + +typedef void +(* JS_DLL_CALLBACK JSObjectHook)(JSContext *cx, JSObject *obj, JSBool isNew, + void *closure); + +typedef JSBool +(* JS_DLL_CALLBACK JSDebugErrorHook)(JSContext *cx, const char *message, + JSErrorReport *report, void *closure); + +#endif /* jsprvtd_h___ */ diff --git a/src/dom/js/jspubtd.h b/src/dom/js/jspubtd.h new file mode 100644 index 000000000..e3a1a38b5 --- /dev/null +++ b/src/dom/js/jspubtd.h @@ -0,0 +1,564 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jspubtd_h___ +#define jspubtd_h___ +/* + * JS public API typedefs. + */ +#include "jstypes.h" +#include "jscompat.h" + +JS_BEGIN_EXTERN_C + +/* Scalar typedefs. */ +typedef uint16 jschar; +typedef int32 jsint; +typedef uint32 jsuint; +typedef float64 jsdouble; +typedef jsword jsval; +typedef jsword jsid; +typedef int32 jsrefcount; /* PRInt32 if JS_THREADSAFE, see jslock.h */ + +/* + * Run-time version enumeration. See jsconfig.h for compile-time counterparts + * to these values that may be selected by the JS_VERSION macro, and tested by + * #if expressions. + */ +typedef enum JSVersion { + JSVERSION_1_0 = 100, + JSVERSION_1_1 = 110, + JSVERSION_1_2 = 120, + JSVERSION_1_3 = 130, + JSVERSION_1_4 = 140, + JSVERSION_ECMA_3 = 148, + JSVERSION_1_5 = 150, + JSVERSION_DEFAULT = 0, + JSVERSION_UNKNOWN = -1 +} JSVersion; + +#define JSVERSION_IS_ECMA(version) \ + ((version) == JSVERSION_DEFAULT || (version) >= JSVERSION_1_3) + +/* Result of typeof operator enumeration. */ +typedef enum JSType { + JSTYPE_VOID, /* undefined */ + JSTYPE_OBJECT, /* object */ + JSTYPE_FUNCTION, /* function */ + JSTYPE_STRING, /* string */ + JSTYPE_NUMBER, /* number */ + JSTYPE_BOOLEAN, /* boolean */ + JSTYPE_LIMIT +} JSType; + +/* JSObjectOps.checkAccess mode enumeration. */ +typedef enum JSAccessMode { + JSACC_PROTO = 0, /* XXXbe redundant w.r.t. id */ + JSACC_PARENT = 1, /* XXXbe redundant w.r.t. id */ + JSACC_IMPORT = 2, /* import foo.bar */ + JSACC_WATCH = 3, /* a watchpoint on object foo for id 'bar' */ + JSACC_READ = 4, /* a "get" of foo.bar */ + JSACC_WRITE = 8, /* a "set" of foo.bar = baz */ + JSACC_LIMIT +} JSAccessMode; + +#define JSACC_TYPEMASK (JSACC_WRITE - 1) + +/* + * This enum type is used to control the behavior of a JSObject property + * iterator function that has type JSNewEnumerate. + */ +typedef enum JSIterateOp { + JSENUMERATE_INIT, /* Create new iterator state */ + JSENUMERATE_NEXT, /* Iterate once */ + JSENUMERATE_DESTROY /* Destroy iterator state */ +} JSIterateOp; + +/* Struct typedefs. */ +typedef struct JSClass JSClass; +typedef struct JSConstDoubleSpec JSConstDoubleSpec; +typedef struct JSContext JSContext; +typedef struct JSErrorReport JSErrorReport; +typedef struct JSFunction JSFunction; +typedef struct JSFunctionSpec JSFunctionSpec; +typedef struct JSIdArray JSIdArray; +typedef struct JSProperty JSProperty; +typedef struct JSPropertySpec JSPropertySpec; +typedef struct JSObject JSObject; +typedef struct JSObjectMap JSObjectMap; +typedef struct JSObjectOps JSObjectOps; +typedef struct JSRuntime JSRuntime; +typedef struct JSRuntime JSTaskState; /* XXX deprecated name */ +typedef struct JSScript JSScript; +typedef struct JSString JSString; +typedef struct JSXDRState JSXDRState; +typedef struct JSExceptionState JSExceptionState; +typedef struct JSLocaleCallbacks JSLocaleCallbacks; + +/* JSClass (and JSObjectOps where appropriate) function pointer typedefs. */ + +/* + * Add, delete, get or set a property named by id in obj. Note the jsval id + * type -- id may be a string (Unicode property identifier) or an int (element + * index). The *vp out parameter, on success, is the new property value after + * an add, get, or set. After a successful delete, *vp is JSVAL_FALSE iff + * obj[id] can't be deleted (because it's permanent). + */ +typedef JSBool +(* JS_DLL_CALLBACK JSPropertyOp)(JSContext *cx, JSObject *obj, jsval id, + jsval *vp); + +/* + * This function type is used for callbacks that enumerate the properties of + * a JSObject. The behavior depends on the value of enum_op: + * + * JSENUMERATE_INIT + * A new, opaque iterator state should be allocated and stored in *statep. + * (You can use PRIVATE_TO_JSVAL() to tag the pointer to be stored). + * + * The number of properties that will be enumerated should be returned as + * an integer jsval in *idp, if idp is non-null, and provided the number of + * enumerable properties is known. If idp is non-null and the number of + * enumerable properties can't be computed in advance, *idp should be set + * to JSVAL_ZERO. + * + * JSENUMERATE_NEXT + * A previously allocated opaque iterator state is passed in via statep. + * Return the next jsid in the iteration using *idp. The opaque iterator + * state pointed at by statep is destroyed and *statep is set to JSVAL_NULL + * if there are no properties left to enumerate. + * + * JSENUMERATE_DESTROY + * Destroy the opaque iterator state previously allocated in *statep by a + * call to this function when enum_op was JSENUMERATE_INIT. + * + * The return value is used to indicate success, with a value of JS_FALSE + * indicating failure. + */ +typedef JSBool +(* JS_DLL_CALLBACK JSNewEnumerateOp)(JSContext *cx, JSObject *obj, + JSIterateOp enum_op, + jsval *statep, jsid *idp); + +/* + * The old-style JSClass.enumerate op should define all lazy properties not + * yet reflected in obj. + */ +typedef JSBool +(* JS_DLL_CALLBACK JSEnumerateOp)(JSContext *cx, JSObject *obj); + +/* + * Resolve a lazy property named by id in obj by defining it directly in obj. + * Lazy properties are those reflected from some peer native property space + * (e.g., the DOM attributes for a given node reflected as obj) on demand. + * + * JS looks for a property in an object, and if not found, tries to resolve + * the given id. If resolve succeeds, the engine looks again in case resolve + * defined obj[id]. If no such property exists directly in obj, the process + * is repeated with obj's prototype, etc. + * + * NB: JSNewResolveOp provides a cheaper way to resolve lazy properties. + */ +typedef JSBool +(* JS_DLL_CALLBACK JSResolveOp)(JSContext *cx, JSObject *obj, jsval id); + +/* + * Like JSResolveOp, but flags provide contextual information as follows: + * + * JSRESOLVE_QUALIFIED a qualified property id: obj.id or obj[id], not id + * JSRESOLVE_ASSIGNING obj[id] is on the left-hand side of an assignment + * + * The *objp out parameter, on success, should be null to indicate that id + * was not resolved; and non-null, referring to obj or one of its prototypes, + * if id was resolved. + * + * This hook instead of JSResolveOp is called via the JSClass.resolve member + * if JSCLASS_NEW_RESOLVE is set in JSClass.flags. + * + * Setting JSCLASS_NEW_RESOLVE and JSCLASS_NEW_RESOLVE_GETS_START further + * extends this hook by passing in the starting object on the prototype chain + * via *objp. Thus a resolve hook implementation may define the property id + * being resolved in the object in which the id was first sought, rather than + * in a prototype object whose class led to the resolve hook being called. + * + * When using JSCLASS_NEW_RESOLVE_GETS_START, the resolve hook must therefore + * null *objp to signify "not resolved". With only JSCLASS_NEW_RESOLVE and no + * JSCLASS_NEW_RESOLVE_GETS_START, the hook can assume *objp is null on entry. + * This is not good practice, but enough existing hook implementations count + * on it that we can't break compatibility by passing the starting object in + * *objp without a new JSClass flag. + */ +typedef JSBool +(* JS_DLL_CALLBACK JSNewResolveOp)(JSContext *cx, JSObject *obj, jsval id, + uintN flags, JSObject **objp); + +/* + * Convert obj to the given type, returning true with the resulting value in + * *vp on success, and returning false on error or exception. + */ +typedef JSBool +(* JS_DLL_CALLBACK JSConvertOp)(JSContext *cx, JSObject *obj, JSType type, + jsval *vp); + +/* + * Finalize obj, which the garbage collector has determined to be unreachable + * from other live objects or from GC roots. Obviously, finalizers must never + * store a reference to obj. + */ +typedef void +(* JS_DLL_CALLBACK JSFinalizeOp)(JSContext *cx, JSObject *obj); + +/* + * Used by JS_AddExternalStringFinalizer and JS_RemoveExternalStringFinalizer + * to extend and reduce the set of string types finalized by the GC. + */ +typedef void +(* JS_DLL_CALLBACK JSStringFinalizeOp)(JSContext *cx, JSString *str); + +/* + * The signature for JSClass.getObjectOps, used by JS_NewObject's internals + * to discover the set of high-level object operations to use for new objects + * of the given class. All native objects have a JSClass, which is stored as + * a private (int-tagged) pointer in obj->slots[JSSLOT_CLASS]. In contrast, + * all native and host objects have a JSObjectMap at obj->map, which may be + * shared among a number of objects, and which contains the JSObjectOps *ops + * pointer used to dispatch object operations from API calls. + * + * Thus JSClass (which pre-dates JSObjectOps in the API) provides a low-level + * interface to class-specific code and data, while JSObjectOps allows for a + * higher level of operation, which does not use the object's class except to + * find the class's JSObjectOps struct, by calling clasp->getObjectOps. + * + * If this seems backwards, that's because it is! API compatibility requires + * a JSClass *clasp parameter to JS_NewObject, etc. Most host objects do not + * need to implement the larger JSObjectOps, and can share the common JSScope + * code and data used by the native (js_ObjectOps, see jsobj.c) ops. + */ +typedef JSObjectOps * +(* JS_DLL_CALLBACK JSGetObjectOps)(JSContext *cx, JSClass *clasp); + +/* + * JSClass.checkAccess type: check whether obj[id] may be accessed per mode, + * returning false on error/exception, true on success with obj[id]'s last-got + * value in *vp, and its attributes in *attrsp. As for JSPropertyOp above, id + * is either a string or an int jsval. + * + * See JSCheckAccessIdOp, below, for the JSObjectOps counterpart, which takes + * a jsid (a tagged int or aligned, unique identifier pointer) rather than a + * jsval. The native js_ObjectOps.checkAccess simply forwards to the object's + * clasp->checkAccess, so that both JSClass and JSObjectOps implementors may + * specialize access checks. + */ +typedef JSBool +(* JS_DLL_CALLBACK JSCheckAccessOp)(JSContext *cx, JSObject *obj, jsval id, + JSAccessMode mode, jsval *vp); + +/* + * Encode or decode an object, given an XDR state record representing external + * data. See jsxdrapi.h. + */ +typedef JSBool +(* JS_DLL_CALLBACK JSXDRObjectOp)(JSXDRState *xdr, JSObject **objp); + +/* + * Check whether v is an instance of obj. Return false on error or exception, + * true on success with JS_TRUE in *bp if v is an instance of obj, JS_FALSE in + * *bp otherwise. + */ +typedef JSBool +(* JS_DLL_CALLBACK JSHasInstanceOp)(JSContext *cx, JSObject *obj, jsval v, + JSBool *bp); + +/* + * Function type for JSClass.mark and JSObjectOps.mark, called from the GC to + * scan live GC-things reachable from obj's private data structure. For each + * such thing, a mark implementation must call + * + * JS_MarkGCThing(cx, thing, name, arg); + * + * The trailing name and arg parameters are used for GC_MARK_DEBUG-mode heap + * dumping and ref-path tracing. The mark function should pass a (typically + * literal) string naming the private data member for name, and it must pass + * the opaque arg parameter through from its caller. + * + * For the JSObjectOps.mark hook, the return value is the number of slots at + * obj->slots to scan. For JSClass.mark, the return value is ignored. + * + * NB: JSMarkOp implementations cannot allocate new GC-things (JS_NewObject + * called from a mark function will fail silently, e.g.). + */ +typedef uint32 +(* JS_DLL_CALLBACK JSMarkOp)(JSContext *cx, JSObject *obj, void *arg); + +/* JSObjectOps function pointer typedefs. */ + +/* + * Create a new subclass of JSObjectMap (see jsobj.h), with the nrefs and ops + * members initialized from the same-named parameters, and with the nslots and + * freeslot members initialized according to ops and clasp. Return null on + * error, non-null on success. + * + * JSObjectMaps are reference-counted by generic code in the engine. Usually, + * the nrefs parameter to JSObjectOps.newObjectMap will be 1, to count the ref + * returned to the caller on success. After a successful construction, some + * number of js_HoldObjectMap and js_DropObjectMap calls ensue. When nrefs + * reaches 0 due to a js_DropObjectMap call, JSObjectOps.destroyObjectMap will + * be called to dispose of the map. + */ +typedef JSObjectMap * +(* JS_DLL_CALLBACK JSNewObjectMapOp)(JSContext *cx, jsrefcount nrefs, + JSObjectOps *ops, JSClass *clasp, + JSObject *obj); + +/* + * Generic type for an infallible JSObjectMap operation, used currently by + * JSObjectOps.destroyObjectMap. + */ +typedef void +(* JS_DLL_CALLBACK JSObjectMapOp)(JSContext *cx, JSObjectMap *map); + +/* + * Look for id in obj and its prototype chain, returning false on error or + * exception, true on success. On success, return null in *propp if id was + * not found. If id was found, return the first object searching from obj + * along its prototype chain in which id names a direct property in *objp, and + * return a non-null, opaque property pointer in *propp. + * + * If JSLookupPropOp succeeds and returns with *propp non-null, that pointer + * may be passed as the prop parameter to a JSAttributesOp, as a short-cut + * that bypasses id re-lookup. In any case, a non-null *propp result after a + * successful lookup must be dropped via JSObjectOps.dropProperty. + * + * NB: successful return with non-null *propp means the implementation may + * have locked *objp and added a reference count associated with *propp, so + * callers should not risk deadlock by nesting or interleaving other lookups + * or any obj-bearing ops before dropping *propp. + */ +typedef JSBool +(* JS_DLL_CALLBACK JSLookupPropOp)(JSContext *cx, JSObject *obj, jsid id, + JSObject **objp, JSProperty **propp +#if defined JS_THREADSAFE && defined DEBUG + , const char *file, uintN line +#endif + ); + +/* + * Define obj[id], a direct property of obj named id, having the given initial + * value, with the specified getter, setter, and attributes. If the propp out + * param is non-null, *propp on successful return contains an opaque property + * pointer usable as a speedup hint with JSAttributesOp. But note that propp + * may be null, indicating that the caller is not interested in recovering an + * opaque pointer to the newly-defined property. + * + * If propp is non-null and JSDefinePropOp succeeds, its caller must be sure + * to drop *propp using JSObjectOps.dropProperty in short order, just as with + * JSLookupPropOp. + */ +typedef JSBool +(* JS_DLL_CALLBACK JSDefinePropOp)(JSContext *cx, JSObject *obj, + jsid id, jsval value, + JSPropertyOp getter, JSPropertyOp setter, + uintN attrs, JSProperty **propp); + +/* + * Get, set, or delete obj[id], returning false on error or exception, true + * on success. If getting or setting, the new value is returned in *vp on + * success. If deleting without error, *vp will be JSVAL_FALSE if obj[id] is + * permanent, and JSVAL_TRUE if id named a direct property of obj that was in + * fact deleted, or if id names no direct property of obj (id could name a + * prototype property, or no property in obj or its prototype chain). + */ +typedef JSBool +(* JS_DLL_CALLBACK JSPropertyIdOp)(JSContext *cx, JSObject *obj, jsid id, + jsval *vp); + +/* + * Get or set attributes of the property obj[id]. Return false on error or + * exception, true with current attributes in *attrsp. If prop is non-null, + * it must come from the *propp out parameter of a prior JSDefinePropOp or + * JSLookupPropOp call. + */ +typedef JSBool +(* JS_DLL_CALLBACK JSAttributesOp)(JSContext *cx, JSObject *obj, jsid id, + JSProperty *prop, uintN *attrsp); + +/* + * JSObjectOps.checkAccess type: check whether obj[id] may be accessed per + * mode, returning false on error/exception, true on success with obj[id]'s + * last-got value in *vp, and its attributes in *attrsp. + */ +typedef JSBool +(* JS_DLL_CALLBACK JSCheckAccessIdOp)(JSContext *cx, JSObject *obj, jsid id, + JSAccessMode mode, jsval *vp, + uintN *attrsp); + +/* + * A generic type for functions mapping an object to another object, or null + * if an error or exception was thrown on cx. Used by JSObjectOps.thisObject + * at present. + */ +typedef JSObject * +(* JS_DLL_CALLBACK JSObjectOp)(JSContext *cx, JSObject *obj); + +/* + * A generic type for functions taking a context, object, and property, with + * no return value. Used by JSObjectOps.dropProperty currently (see above, + * JSDefinePropOp and JSLookupPropOp, for the object-locking protocol in which + * dropProperty participates). + */ +typedef void +(* JS_DLL_CALLBACK JSPropertyRefOp)(JSContext *cx, JSObject *obj, + JSProperty *prop); + +/* + * Function type for JSObjectOps.setProto and JSObjectOps.setParent. These + * hooks must check for cycles without deadlocking, and otherwise take special + * steps. See jsobj.c, js_SetProtoOrParent, for an example. + */ +typedef JSBool +(* JS_DLL_CALLBACK JSSetObjectSlotOp)(JSContext *cx, JSObject *obj, + uint32 slot, JSObject *pobj); + +/* + * Get and set a required slot, one that should already have been allocated. + * These operations are infallible, so required slots must be pre-allocated, + * or implementations must suppress out-of-memory errors. The native ops + * (js_ObjectOps, see jsobj.c) access slots reserved by including a call to + * the JSCLASS_HAS_RESERVED_SLOTS(n) macro in the JSClass.flags initializer. + * + * NB: the slot parameter is a zero-based index into obj->slots[], unlike the + * index parameter to the JS_GetReservedSlot and JS_SetReservedSlot API entry + * points, which is a zero-based index into the JSCLASS_RESERVED_SLOTS(clasp) + * reserved slots that come after the initial well-known slots: proto, parent, + * class, and optionally, the private data slot. + */ +typedef jsval +(* JS_DLL_CALLBACK JSGetRequiredSlotOp)(JSContext *cx, JSObject *obj, + uint32 slot); + +typedef void +(* JS_DLL_CALLBACK JSSetRequiredSlotOp)(JSContext *cx, JSObject *obj, + uint32 slot, jsval v); + +/* Typedef for native functions called by the JS VM. */ + +typedef JSBool +(* JS_DLL_CALLBACK JSNative)(JSContext *cx, JSObject *obj, uintN argc, + jsval *argv, jsval *rval); + +/* Callbacks and their arguments. */ + +typedef enum JSGCStatus { + JSGC_BEGIN, + JSGC_END, + JSGC_MARK_END, + JSGC_FINALIZE_END +} JSGCStatus; + +typedef JSBool +(* JS_DLL_CALLBACK JSGCCallback)(JSContext *cx, JSGCStatus status); + +typedef JSBool +(* JS_DLL_CALLBACK JSBranchCallback)(JSContext *cx, JSScript *script); + +typedef void +(* JS_DLL_CALLBACK JSErrorReporter)(JSContext *cx, const char *message, + JSErrorReport *report); + +typedef struct JSErrorFormatString { + const char *format; + uintN argCount; +} JSErrorFormatString; + +typedef const JSErrorFormatString * +(* JS_DLL_CALLBACK JSErrorCallback)(void *userRef, const char *locale, + const uintN errorNumber); + +#ifdef va_start +#define JS_ARGUMENT_FORMATTER_DEFINED 1 + +typedef JSBool +(* JS_DLL_CALLBACK JSArgumentFormatter)(JSContext *cx, const char *format, + JSBool fromJS, jsval **vpp, + va_list *app); +#endif + +typedef JSBool +(* JS_DLL_CALLBACK JSLocaleToUpperCase)(JSContext *cx, JSString *src, + jsval *rval); + +typedef JSBool +(* JS_DLL_CALLBACK JSLocaleToLowerCase)(JSContext *cx, JSString *src, + jsval *rval); + +typedef JSBool +(* JS_DLL_CALLBACK JSLocaleCompare)(JSContext *cx, + JSString *src1, JSString *src2, + jsval *rval); + +/* + * Security protocol types. + */ +typedef struct JSPrincipals JSPrincipals; + +/* + * XDR-encode or -decode a principals instance, based on whether xdr->mode is + * JSXDR_ENCODE, in which case *principalsp should be encoded; or JSXDR_DECODE, + * in which case implementations must return a held (via JSPRINCIPALS_HOLD), + * non-null *principalsp out parameter. Return true on success, false on any + * error, which the implementation must have reported. + */ +typedef JSBool +(* JS_DLL_CALLBACK JSPrincipalsTranscoder)(JSXDRState *xdr, + JSPrincipals **principalsp); + +/* + * Return a weak reference to the principals associated with obj, possibly via + * the immutable parent chain leading from obj to a top-level container (e.g., + * a window object in the DOM level 0). If there are no principals associated + * with obj, return null. Therefore null does not mean an error was reported; + * in no event should an error be reported or an exception be thrown by this + * callback's implementation. + */ +typedef JSPrincipals * +(* JS_DLL_CALLBACK JSObjectPrincipalsFinder)(JSContext *cx, JSObject *obj); + +JS_END_EXTERN_C + +#endif /* jspubtd_h___ */ diff --git a/src/dom/js/jsregexp.c b/src/dom/js/jsregexp.c new file mode 100644 index 000000000..18ab92c35 --- /dev/null +++ b/src/dom/js/jsregexp.c @@ -0,0 +1,3773 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS regular expressions, after Perl. + */ +#include "jsstddef.h" +#include +#include +#include "jstypes.h" +#include "jsarena.h" /* Added by JSIFY */ +#include "jsutil.h" /* Added by JSIFY */ +#include "jsapi.h" +#include "jsarray.h" +#include "jsatom.h" +#include "jscntxt.h" +#include "jsconfig.h" +#include "jsfun.h" +#include "jsgc.h" +#include "jsinterp.h" +#include "jslock.h" +#include "jsnum.h" +#include "jsobj.h" +#include "jsopcode.h" +#include "jsregexp.h" +#include "jsscan.h" +#include "jsstr.h" + +#ifdef XP_MAC +#include +#endif + +#if JS_HAS_REGEXPS + +/* Note : contiguity of 'simple opcodes' is important for simpleMatch() */ +typedef enum REOp { + REOP_EMPTY = 0, /* match rest of input against rest of r.e. */ + REOP_ALT = 1, /* alternative subexpressions in kid and next */ + REOP_SIMPLE_START = 2, /* start of 'simple opcodes' */ + REOP_BOL = 2, /* beginning of input (or line if multiline) */ + REOP_EOL = 3, /* end of input (or line if multiline) */ + REOP_WBDRY = 4, /* match "" at word boundary */ + REOP_WNONBDRY = 5, /* match "" at word non-boundary */ + REOP_DOT = 6, /* stands for any character */ + REOP_DIGIT = 7, /* match a digit char: [0-9] */ + REOP_NONDIGIT = 8, /* match a non-digit char: [^0-9] */ + REOP_ALNUM = 9, /* match an alphanumeric char: [0-9a-z_A-Z] */ + REOP_NONALNUM = 10, /* match a non-alphanumeric char: [^0-9a-z_A-Z] */ + REOP_SPACE = 11, /* match a whitespace char */ + REOP_NONSPACE = 12, /* match a non-whitespace char */ + REOP_BACKREF = 13, /* back-reference (e.g., \1) to a parenthetical */ + REOP_FLAT = 14, /* match a flat string */ + REOP_FLAT1 = 15, /* match a single char */ + REOP_FLATi = 16, /* case-independent REOP_FLAT */ + REOP_FLAT1i = 17, /* case-independent REOP_FLAT1 */ + REOP_UCFLAT1 = 18, /* single Unicode char */ + REOP_UCFLAT1i = 19, /* case-independent REOP_UCFLAT1 */ + REOP_UCFLAT = 20, /* flat Unicode string; len immediate counts chars */ + REOP_UCFLATi = 21, /* case-independent REOP_UCFLAT */ + REOP_CLASS = 22, /* character class with index */ + REOP_NCLASS = 23, /* negated character class with index */ + REOP_SIMPLE_END = 23, /* end of 'simple opcodes' */ + REOP_QUANT = 25, /* quantified atom: atom{1,2} */ + REOP_STAR = 26, /* zero or more occurrences of kid */ + REOP_PLUS = 27, /* one or more occurrences of kid */ + REOP_OPT = 28, /* optional subexpression in kid */ + REOP_LPAREN = 29, /* left paren bytecode: kid is u.num'th sub-regexp */ + REOP_RPAREN = 30, /* right paren bytecode */ + REOP_JUMP = 31, /* for deoptimized closure loops */ + REOP_DOTSTAR = 32, /* optimize .* to use a single opcode */ + REOP_ANCHOR = 33, /* like .* but skips left context to unanchored r.e. */ + REOP_EOLONLY = 34, /* $ not preceded by any pattern */ + REOP_BACKREFi = 37, /* case-independent REOP_BACKREF */ + REOP_LPARENNON = 41, /* non-capturing version of REOP_LPAREN */ + REOP_ASSERT = 43, /* zero width positive lookahead assertion */ + REOP_ASSERT_NOT = 44, /* zero width negative lookahead assertion */ + REOP_ASSERTTEST = 45, /* sentinel at end of assertion child */ + REOP_ASSERTNOTTEST = 46, /* sentinel at end of !assertion child */ + REOP_MINIMALSTAR = 47, /* non-greedy version of * */ + REOP_MINIMALPLUS = 48, /* non-greedy version of + */ + REOP_MINIMALOPT = 49, /* non-greedy version of ? */ + REOP_MINIMALQUANT = 50, /* non-greedy version of {} */ + REOP_ENDCHILD = 51, /* sentinel at end of quantifier child */ + REOP_REPEAT = 52, /* directs execution of greedy quantifier */ + REOP_MINIMALREPEAT = 53, /* directs execution of non-greedy quantifier */ + REOP_ALTPREREQ = 54, /* prerequisite for ALT, either of two chars */ + REOP_ALTPREREQ2 = 55, /* prerequisite for ALT, a char or a class */ + REOP_ENDALT = 56, /* end of final alternate */ + REOP_CONCAT = 57, /* concatenation of terms (parse time only) */ + + REOP_END +} REOp; + +#define REOP_IS_SIMPLE(op) (((op) >= REOP_SIMPLE_START) && ((op) <= REOP_SIMPLE_END)) + +struct RENode { + REOp op; /* r.e. op bytecode */ + RENode *next; /* next in concatenation order */ + void *kid; /* first operand */ + union { + void *kid2; /* second operand */ + jsint num; /* could be a number */ + jsint parenIndex; /* or a parenthesis index */ + struct { /* or a quantifier range */ + uint16 min; + uint16 max; + JSBool greedy; + } range; + struct { /* or a character class */ + uint16 startIndex; + uint16 kidlen; /* length of string at kid, in jschars */ + uint16 bmsize; /* bitmap size, based on max char code */ + uint16 index; /* index into class list */ + JSBool sense; + } ucclass; + struct { /* or a literal sequence */ + jschar chr; /* of one character */ + uint16 length; /* or many (via the kid) */ + } flat; + struct { + RENode *kid2; /* second operand from ALT */ + jschar ch1; /* match char for ALTPREREQ */ + jschar ch2; /* ditto, or class index for ALTPREREQ2 */ + } altprereq; + } u; +}; + +#define RE_IS_LETTER(c) ( ((c >= 'A') && (c <= 'Z')) || \ + ((c >= 'a') && (c <= 'z')) ) +#define RE_IS_LINE_TERM(c) ( (c == '\n') || (c == '\r') || \ + (c == LINE_SEPARATOR) || (c == PARA_SEPARATOR)) + +#define CLASS_CACHE_SIZE (4) +typedef struct CompilerState { + JSContext *context; + JSTokenStream *tokenStream; /* For reporting errors */ + const jschar *cpbegin; + const jschar *cpend; + const jschar *cp; + uintN flags; + uint16 parenCount; + uint16 classCount; /* number of [] encountered */ + size_t progLength; /* estimated bytecode length */ + uintN treeDepth; /* maximum depth of parse tree */ + RENode *result; + struct { + const jschar *start; /* small cache of class strings */ + uint16 length; /* since they're often the same */ + uint16 index; + } classCache[CLASS_CACHE_SIZE]; +} CompilerState; + +typedef struct RECapture { + int32 index; /* start of contents, -1 for empty */ + uint16 length; /* length of capture */ +} RECapture; + +typedef struct REMatchState { + const jschar *cp; + RECapture parens[1]; /* first of 're->parenCount' captures, + * allocated at end of this struct. + */ +} REMatchState; + +struct REBackTrackData; + +typedef struct REProgState { + jsbytecode *continue_pc; /* current continuation data */ + jsbytecode continue_op; + uint16 index; /* progress in text */ + uintN parenSoFar; /* highest indexed paren started */ + union { + struct { + uint16 min; /* current quantifier limits */ + uint16 max; + } quantifier; + struct { + size_t top; /* backtrack stack state */ + size_t sz; + } assertion; + } u; +} REProgState; + +typedef struct REBackTrackData { + size_t sz; /* size of previous stack entry */ + jsbytecode *backtrack_pc; /* where to backtrack to */ + jsbytecode backtrack_op; + const jschar *cp; /* index in text of match at backtrack */ + intN parenIndex; /* start index of saved paren contents */ + uint16 parenCount; /* # of saved paren contents */ + uint16 precedingStateTop; /* number of parent states */ + /* saved parent states follow */ + /* saved paren contents follow */ +} REBackTrackData; + +#define INITIAL_STATESTACK (100) +#define INITIAL_BACKTRACK (8000) + +typedef struct REGlobalData { + JSContext *cx; + JSRegExp *regexp; /* the RE in execution */ + JSBool ok; /* runtime error (out_of_memory only?) */ + size_t start; /* offset to start at */ + ptrdiff_t skipped; /* chars skipped anchoring this r.e. */ + const jschar *cpbegin, *cpend; /* text base address and limit */ + + REProgState *stateStack; /* stack of state of current parents */ + uint16 stateStackTop; + uint16 maxStateStack; + + REBackTrackData *backTrackStack;/* stack of matched-so-far positions */ + REBackTrackData *backTrackSP; + size_t maxBackTrack; + size_t cursz; /* size of current stack entry */ + + JSArenaPool pool; /* I don't understand but it's faster to + * use this than to malloc/free the three + * items that are allocated from this pool + */ + +} REGlobalData; + + +/* + * 1. If IgnoreCase is false, return ch. + * 2. Let u be ch converted to upper case as if by calling + * String.prototype.toUpperCase on the one-character string ch. + * 3. If u does not consist of a single character, return ch. + * 4. Let cu be u's character. + * 5. If ch's code point value is greater than or equal to decimal 128 and cu's + * code point value is less than decimal 128, then return ch. + * 6. Return cu. + */ +static jschar +upcase(jschar ch) +{ + jschar cu = JS_TOUPPER(ch); + if ((ch >= 128) && (cu < 128)) return ch; + return cu; +} + +static jschar +downcase(jschar ch) +{ + jschar cl = JS_TOLOWER(ch); + if ((cl >= 128) && (ch < 128)) return ch; + return cl; +} + +/* Construct and initialize an RENode, returning NULL for out-of-memory */ +static RENode * +NewRENode(CompilerState *state, REOp op) +{ + JSContext *cx; + RENode *ren; + + cx = state->context; + JS_ARENA_ALLOCATE_CAST(ren, RENode *, &cx->tempPool, sizeof *ren); + if (!ren) { + JS_ReportOutOfMemory(cx); + return NULL; + } + ren->op = op; + ren->next = NULL; + ren->kid = NULL; + return ren; +} + +/* + * Validates and converts hex ascii value. + */ +static JSBool +isASCIIHexDigit(jschar c, uintN *digit) +{ + uintN cv = c; + + if (cv < '0') + return JS_FALSE; + if (cv <= '9') { + *digit = cv - '0'; + return JS_TRUE; + } + cv |= 0x20; + if (cv >= 'a' && cv <= 'f') { + *digit = cv - 'a' + 10; + return JS_TRUE; + } + return JS_FALSE; +} + + +typedef struct { + REOp op; + const jschar *errPos; + uint16 parenIndex; +} REOpData; + + +/* + * Process the op against the two top operands, reducing them to a single + * operand in the penultimate slot. Update progLength and treeDepth. + */ +static JSBool +processOp(CompilerState *state, REOpData *opData, RENode **operandStack, intN operandSP) +{ + RENode *result; + + switch (opData->op) { + case REOP_ALT: + result = NewRENode(state, REOP_ALT); + if (!result) + return JS_FALSE; + result->kid = operandStack[operandSP - 2]; + result->u.kid2 = operandStack[operandSP - 1]; + operandStack[operandSP - 2] = result; + /* + * look at both alternates to see if there's a FLAT or a CLASS at + * the start of each. If so, use a prerequisite match + */ + ++state->treeDepth; + if ((((RENode *)(result->kid))->op == REOP_FLAT) + && (((RENode *)(result->u.kid2))->op == REOP_FLAT) + && ((state->flags & JSREG_FOLD) == 0) ) { + result->op = REOP_ALTPREREQ; + result->u.altprereq.ch1 + = ((RENode *)(result->kid))->u.flat.chr; + result->u.altprereq.ch2 + = ((RENode *)(result->u.kid2))->u.flat.chr; + /* ALTPREREQ, , uch1, uch2, , ..., + JUMP, ... ENDALT */ + state->progLength += 13; + } + else + if ((((RENode *)(result->kid))->op == REOP_CLASS) + && (((RENode *)(result->kid))->u.ucclass.index < 256) + && (((RENode *)(result->u.kid2))->op == REOP_FLAT) + && ((state->flags & JSREG_FOLD) == 0) ) { + result->op = REOP_ALTPREREQ2; + result->u.altprereq.ch1 + = ((RENode *)(result->u.kid2))->u.flat.chr; + result->u.altprereq.ch2 + = ((RENode *)(result->kid))->u.ucclass.index; + /* ALTPREREQ2, , uch1, uch2, , ..., + JUMP, ... ENDALT */ + state->progLength += 13; + } + else + if ((((RENode *)(result->kid))->op == REOP_FLAT) + && (((RENode *)(result->u.kid2))->op == REOP_CLASS) + && (((RENode *)(result->u.kid2))->u.ucclass.index < 256) + && ((state->flags & JSREG_FOLD) == 0) ) { + result->op = REOP_ALTPREREQ2; + result->u.altprereq.ch1 + = ((RENode *)(result->kid))->u.flat.chr; + result->u.altprereq.ch2 + = ((RENode *)(result->u.kid2))->u.ucclass.index; + /* ALTPREREQ2, , uch1, uch2, , ..., + JUMP, ... ENDALT */ + state->progLength += 13; + } + else + /* ALT, , ..., JUMP, ... ENDALT */ + state->progLength += 7; + break; + case REOP_CONCAT: + result = operandStack[operandSP - 2]; + while (result->next) + result = result->next; + result->next = operandStack[operandSP - 1]; + break; + case REOP_ASSERT: + case REOP_ASSERT_NOT: + case REOP_LPARENNON: + case REOP_LPAREN: + /* These should have been processed by a close paren. */ + js_ReportCompileErrorNumber(state->context, state->tokenStream, + NULL, JSREPORT_ERROR, + JSMSG_MISSING_PAREN, opData->errPos); + return JS_FALSE; + default:; + } + return JS_TRUE; +} + +/* + * Parser forward declarations. + */ +static JSBool parseTerm(CompilerState *state); +static JSBool parseQuantifier(CompilerState *state); + +/* + * Top-down regular expression grammar, based closely on Perl4. + * + * regexp: altern A regular expression is one or more + * altern '|' regexp alternatives separated by vertical bar. + */ + +#define INITIAL_STACK_SIZE (128) +static JSBool +parseRegExp(CompilerState *state) +{ + uint16 parenIndex; + RENode *operand; + REOpData *operatorStack; + RENode **operandStack; + REOp op; + intN i; + JSBool result = JS_FALSE; + + intN operatorSP = 0, operatorStackSize = INITIAL_STACK_SIZE; + intN operandSP = 0, operandStackSize = INITIAL_STACK_SIZE; + + /* Watch out for empty regexp */ + if (state->cp == state->cpend) { + state->result = NewRENode(state, REOP_EMPTY); + return (state->result != NULL); + } + + operatorStack = (REOpData *)JS_malloc(state->context, + sizeof(REOpData) * operatorStackSize); + if (!operatorStack) + return JS_FALSE; + + operandStack = (RENode **)JS_malloc(state->context, + sizeof(RENode *) * operandStackSize); + if (!operandStack) + goto out; + + + while (JS_TRUE) { + if (state->cp == state->cpend) { + /* + * If we are at the end of the regexp and we're short an operand, + * the regexp must have the form /x|/ or some such. + */ + if (operatorSP == operandSP) { + operand = NewRENode(state, REOP_EMPTY); + if (!operand) + goto out; + goto pushOperand; + } + } else { + switch (*state->cp) { + /* balance '(' */ + case '(': /* balance ')' */ + ++state->cp; + if ((state->cp < state->cpend) && (*state->cp == '?') + && ( (state->cp[1] == '=') + || (state->cp[1] == '!') + || (state->cp[1] == ':') )) { + ++state->cp; + if (state->cp == state->cpend) { + js_ReportCompileErrorNumber(state->context, + state->tokenStream, + NULL, JSREPORT_ERROR, + JSMSG_MISSING_PAREN); + goto out; + } + switch (*state->cp++) { + case '=': + op = REOP_ASSERT; + /* ASSERT, , ... ASSERTTEST */ + state->progLength += 4; + break; + case '!': + op = REOP_ASSERT_NOT; + /* ASSERTNOT, , ... ASSERTNOTTEST */ + state->progLength += 4; + break; + case ':': + op = REOP_LPARENNON; + break; + } + parenIndex = state->parenCount; + } + else { + op = REOP_LPAREN; + /* LPAREN, , ... RPAREN, */ + state->progLength += 6; + parenIndex = state->parenCount++; + if (state->parenCount == 65535) { + js_ReportCompileErrorNumber(state->context, + state->tokenStream, + NULL, JSREPORT_ERROR, + JSMSG_TOO_MANY_PARENS); + goto out; + } + } + goto pushOperator; + case ')': + /* If there's not a stacked open parenthesis, throw + * a syntax error. + */ + for (i = operatorSP - 1; i >= 0; i--) + if ((operatorStack[i].op == REOP_ASSERT) + || (operatorStack[i].op == REOP_ASSERT_NOT) + || (operatorStack[i].op == REOP_LPARENNON) + || (operatorStack[i].op == REOP_LPAREN)) + break; + if (i == -1) { + js_ReportCompileErrorNumber(state->context, + state->tokenStream, + NULL, JSREPORT_ERROR, + JSMSG_UNMATCHED_RIGHT_PAREN); + goto out; + } + /* fall thru... */ + case '|': + /* Expected an operand before these, so make an empty one */ + operand = NewRENode(state, REOP_EMPTY); + if (!operand) + goto out; + goto pushOperand; + default: + if (!parseTerm(state)) + goto out; + operand = state->result; +pushOperand: + if (operandSP == operandStackSize) { + operandStackSize += operandStackSize; + operandStack = + (RENode **)JS_realloc(state->context, operandStack, + sizeof(RENode *) * operandStackSize); + if (!operandStack) + goto out; + } + operandStack[operandSP++] = operand; + break; + } + } + /* At the end; process remaining operators */ +restartOperator: + if (state->cp == state->cpend) { + while (operatorSP) { + --operatorSP; + if (!processOp(state, &operatorStack[operatorSP], + operandStack, operandSP)) + goto out; + --operandSP; + } + JS_ASSERT(operandSP == 1); + state->result = operandStack[0]; + result = JS_TRUE; + goto out; + } + switch (*state->cp) { + case '|': + /* Process any stacked 'concat' operators */ + ++state->cp; + while (operatorSP + && (operatorStack[operatorSP - 1].op == REOP_CONCAT)) { + --operatorSP; + if (!processOp(state, &operatorStack[operatorSP], + operandStack, operandSP)) + goto out; + --operandSP; + } + op = REOP_ALT; + goto pushOperator; + + case ')': + /* If there's not a stacked open parenthesis,we + * accept the close as a flat. + */ + for (i = operatorSP - 1; i >= 0; i--) + if ((operatorStack[i].op == REOP_ASSERT) + || (operatorStack[i].op == REOP_ASSERT_NOT) + || (operatorStack[i].op == REOP_LPARENNON) + || (operatorStack[i].op == REOP_LPAREN)) + break; + if (i == -1) { + js_ReportCompileErrorNumber(state->context, + state->tokenStream, + NULL, JSREPORT_ERROR, + JSMSG_UNMATCHED_RIGHT_PAREN); + goto out; + } + ++state->cp; + /* process everything on the stack until the open */ + while (JS_TRUE) { + JS_ASSERT(operatorSP); + --operatorSP; + switch (operatorStack[operatorSP].op) { + case REOP_ASSERT: + case REOP_ASSERT_NOT: + case REOP_LPAREN: + operand = NewRENode(state, operatorStack[operatorSP].op); + if (!operand) + goto out; + operand->u.parenIndex + = operatorStack[operatorSP].parenIndex; + JS_ASSERT(operandSP); + operand->kid = operandStack[operandSP - 1]; + operandStack[operandSP - 1] = operand; + ++state->treeDepth; + /* fall thru... */ + case REOP_LPARENNON: + state->result = operandStack[operandSP - 1]; + if (!parseQuantifier(state)) + goto out; + operandStack[operandSP - 1] = state->result; + goto restartOperator; + default: + if (!processOp(state, &operatorStack[operatorSP], + operandStack, operandSP)) + goto out; + --operandSP; + break; + } + } + break; + default: + /* Anything else is the start of the next term */ + op = REOP_CONCAT; +pushOperator: + if (operatorSP == operatorStackSize) { + operatorStackSize += operatorStackSize; + operatorStack = + (REOpData *)JS_realloc(state->context, operatorStack, + sizeof(REOpData) * operatorStackSize); + if (!operatorStack) + goto out; + } + operatorStack[operatorSP].op = op; + operatorStack[operatorSP].errPos = state->cp; + operatorStack[operatorSP++].parenIndex = parenIndex; + break; + } + } +out: + if (operatorStack) + JS_free(state->context, operatorStack); + if (operandStack) + JS_free(state->context, operandStack); + return result; +} + +/* + * Extract and return a decimal value at state->cp, the + * initial character 'c' has already been read. + */ +static intN +getDecimalValue(jschar c, CompilerState *state) +{ + intN value = JS7_UNDEC(c); + while (state->cp < state->cpend) { + c = *state->cp; + if (!JS7_ISDEC(c)) + break; + value = (10 * value) + JS7_UNDEC(c); + ++state->cp; + } + return value; +} + +/* + * Calculate the total size of the bitmap required for a class expression. + */ +static JSBool +calculateBitmapSize(CompilerState *state, RENode *target, const jschar *src, + const jschar *end) +{ + jschar rangeStart, c; + uintN n, digit, nDigits, i; + uintN max = 0; + JSBool inRange = JS_FALSE; + + target->u.ucclass.bmsize = 0; + target->u.ucclass.sense = JS_TRUE; + + if (src == end) + return JS_TRUE; + + if (*src == '^') { + ++src; + target->u.ucclass.sense = JS_FALSE; + } + + while (src != end) { + uintN localMax = 0; + switch (*src) { + case '\\': + ++src; + c = *src++; + switch (c) { + case 'b': + localMax = 0x8; + break; + case 'f': + localMax = 0xC; + break; + case 'n': + localMax = 0xA; + break; + case 'r': + localMax = 0xD; + break; + case 't': + localMax = 0x9; + break; + case 'v': + localMax = 0xB; + break; + case 'c': + if (((src + 1) < end) && RE_IS_LETTER(src[1])) + localMax = (jschar)(*src++ & 0x1F); + else + localMax = '\\'; + break; + case 'x': + nDigits = 2; + goto lexHex; + case 'u': + nDigits = 4; +lexHex: + n = 0; + for (i = 0; (i < nDigits) && (src < end); i++) { + c = *src++; + if (!isASCIIHexDigit(c, &digit)) { + /* + * Back off to accepting the original + *'\' as a literal. + */ + src -= (i + 1); + n = '\\'; + break; + } + n = (n << 4) | digit; + } + localMax = n; + break; + case 'd': + if (inRange) { + JS_ReportErrorNumber(state->context, + js_GetErrorMessage, NULL, + JSMSG_BAD_CLASS_RANGE); + return JS_FALSE; + } + localMax = '9'; + break; + case 'D': + case 's': + case 'S': + case 'w': + case 'W': + if (inRange) { + JS_ReportErrorNumber(state->context, + js_GetErrorMessage, NULL, + JSMSG_BAD_CLASS_RANGE); + return JS_FALSE; + } + target->u.ucclass.bmsize = 65535; + return JS_TRUE; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + /* + * This is a non-ECMA extension - decimal escapes (in this + * case, octal!) are supposed to be an error inside class + * ranges, but supported here for backwards compatibility. + * + */ + n = JS7_UNDEC(c); + c = *src; + if ('0' <= c && c <= '7') { + src++; + n = 8 * n + JS7_UNDEC(c); + c = *src; + if ('0' <= c && c <= '7') { + src++; + i = 8 * n + JS7_UNDEC(c); + if (i <= 0377) + n = i; + else + src--; + } + } + localMax = n; + break; + + default: + localMax = c; + break; + } + break; + default: + localMax = *src++; + break; + } + if (inRange) { + if (rangeStart > localMax) { + JS_ReportErrorNumber(state->context, + js_GetErrorMessage, NULL, + JSMSG_BAD_CLASS_RANGE); + return JS_FALSE; + } + inRange = JS_FALSE; + } + else { + if (src < (end - 1)) { + if (*src == '-') { + ++src; + inRange = JS_TRUE; + rangeStart = (jschar)localMax; + continue; + } + } + } + if (state->flags & JSREG_FOLD) { + c = JS_MAX(upcase((jschar)localMax), downcase((jschar)localMax)); + if (c > localMax) + localMax = c; + } + if (localMax > max) + max = localMax; + } + target->u.ucclass.bmsize = max; + return JS_TRUE; +} + +/* + * item: assertion An item is either an assertion or + * quantatom a quantified atom. + * + * assertion: '^' Assertions match beginning of string + * (or line if the class static property + * RegExp.multiline is true). + * '$' End of string (or line if the class + * static property RegExp.multiline is + * true). + * '\b' Word boundary (between \w and \W). + * '\B' Word non-boundary. + * + * quantatom: atom An unquantified atom. + * quantatom '{' n ',' m '}' + * Atom must occur between n and m times. + * quantatom '{' n ',' '}' Atom must occur at least n times. + * quantatom '{' n '}' Atom must occur exactly n times. + * quantatom '*' Zero or more times (same as {0,}). + * quantatom '+' One or more times (same as {1,}). + * quantatom '?' Zero or one time (same as {0,1}). + * + * any of which can be optionally followed by '?' for ungreedy + * + * atom: '(' regexp ')' A parenthesized regexp (what matched + * can be addressed using a backreference, + * see '\' n below). + * '.' Matches any char except '\n'. + * '[' classlist ']' A character class. + * '[' '^' classlist ']' A negated character class. + * '\f' Form Feed. + * '\n' Newline (Line Feed). + * '\r' Carriage Return. + * '\t' Horizontal Tab. + * '\v' Vertical Tab. + * '\d' A digit (same as [0-9]). + * '\D' A non-digit. + * '\w' A word character, [0-9a-z_A-Z]. + * '\W' A non-word character. + * '\s' A whitespace character, [ \b\f\n\r\t\v]. + * '\S' A non-whitespace character. + * '\' n A backreference to the nth (n decimal + * and positive) parenthesized expression. + * '\' octal An octal escape sequence (octal must be + * two or three digits long, unless it is + * 0 for the null character). + * '\x' hex A hex escape (hex must be two digits). + * '\u' unicode A unicode escape (must be four digits). + * '\c' ctrl A control character, ctrl is a letter. + * '\' literalatomchar Any character except one of the above + * that follow '\' in an atom. + * otheratomchar Any character not first among the other + * atom right-hand sides. + */ +static JSBool +parseTerm(CompilerState *state) +{ + jschar c = *state->cp++; + uintN nDigits; + uintN num, tmp, n, i; + const jschar *termStart; + JSBool foundCachedCopy; + + switch (c) { + /* assertions and atoms */ + case '^': + state->result = NewRENode(state, REOP_BOL); + if (!state->result) + return JS_FALSE; + state->progLength++; + return JS_TRUE; + case '$': + state->result = NewRENode(state, REOP_EOL); + if (!state->result) + return JS_FALSE; + state->progLength++; + return JS_TRUE; + case '\\': + if (state->cp >= state->cpend) { + /* a trailing '\' is an error */ + js_ReportCompileErrorNumber(state->context, state->tokenStream, + NULL, JSREPORT_ERROR, + JSMSG_TRAILING_SLASH); + return JS_FALSE; + } + c = *state->cp++; + switch (c) { + /* assertion escapes */ + case 'b' : + state->result = NewRENode(state, REOP_WBDRY); + if (!state->result) + return JS_FALSE; + state->progLength++; + return JS_TRUE; + case 'B': + state->result = NewRENode(state, REOP_WNONBDRY); + if (!state->result) + return JS_FALSE; + state->progLength++; + return JS_TRUE; + /* Decimal escape */ + case '0': + if (JS_HAS_STRICT_OPTION(state->context)) + c = 0; + else { + doOctal: + num = 0; + while (state->cp < state->cpend) { + if ('0' <= (c = *state->cp) && c <= '7') { + state->cp++; + tmp = 8 * num + (uintN)JS7_UNDEC(c); + if (tmp > 0377) + break; + num = tmp; + } + else + break; + } + c = (jschar)(num); + } + doFlat: + state->result = NewRENode(state, REOP_FLAT); + if (!state->result) + return JS_FALSE; + state->result->u.flat.chr = c; + state->result->u.flat.length = 1; + state->progLength += 3; + break; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + termStart = state->cp - 1; + num = (uintN)getDecimalValue(c, state); + if (num > 9 && + num > state->parenCount && + !JS_HAS_STRICT_OPTION(state->context)) { + state->cp = termStart; + goto doOctal; + } + state->result = NewRENode(state, REOP_BACKREF); + if (!state->result) + return JS_FALSE; + state->result->u.parenIndex = num - 1; + state->progLength += 3; + break; + /* Control escape */ + case 'f': + c = 0xC; + goto doFlat; + case 'n': + c = 0xA; + goto doFlat; + case 'r': + c = 0xD; + goto doFlat; + case 't': + c = 0x9; + goto doFlat; + case 'v': + c = 0xB; + goto doFlat; + /* Control letter */ + case 'c': + if (((state->cp + 1) < state->cpend) && + RE_IS_LETTER(state->cp[1])) + c = (jschar)(*state->cp++ & 0x1F); + else { + /* back off to accepting the original '\' as a literal */ + --state->cp; + c = '\\'; + } + goto doFlat; + /* HexEscapeSequence */ + case 'x': + nDigits = 2; + goto lexHex; + /* UnicodeEscapeSequence */ + case 'u': + nDigits = 4; +lexHex: + n = 0; + for (i = 0; (i < nDigits) + && (state->cp < state->cpend); i++) { + uintN digit; + c = *state->cp++; + if (!isASCIIHexDigit(c, &digit)) { + /* + * back off to accepting the original + * 'u' or 'x' as a literal + */ + state->cp -= (i + 2); + n = *state->cp++; + break; + } + n = (n << 4) | digit; + } + c = (jschar)(n); + goto doFlat; + /* Character class escapes */ + case 'd': + state->result = NewRENode(state, REOP_DIGIT); +doSimple: + if (!state->result) + return JS_FALSE; + state->progLength++; + break; + case 'D': + state->result = NewRENode(state, REOP_NONDIGIT); + goto doSimple; + case 's': + state->result = NewRENode(state, REOP_SPACE); + goto doSimple; + case 'S': + state->result = NewRENode(state, REOP_NONSPACE); + goto doSimple; + case 'w': + state->result = NewRENode(state, REOP_ALNUM); + goto doSimple; + case 'W': + state->result = NewRENode(state, REOP_NONALNUM); + goto doSimple; + /* IdentityEscape */ + default: + state->result = NewRENode(state, REOP_FLAT); + if (!state->result) + return JS_FALSE; + state->result->u.flat.chr = c; + state->result->u.flat.length = 1; + state->result->kid = (void *)(state->cp - 1); + state->progLength += 3; + break; + } + break; + case '[': + state->result = NewRENode(state, REOP_CLASS); + if (!state->result) + return JS_FALSE; + termStart = state->cp; + state->result->u.ucclass.startIndex = termStart - state->cpbegin; + while (JS_TRUE) { + if (state->cp == state->cpend) { + js_ReportCompileErrorNumber(state->context, state->tokenStream, + NULL, JSREPORT_ERROR, + JSMSG_UNTERM_CLASS, termStart); + return JS_FALSE; + } + if (*state->cp == '\\') + state->cp++; + else { + if (*state->cp == ']') { + state->result->u.ucclass.kidlen = state->cp - termStart; + break; + } + } + state->cp++; + } + foundCachedCopy = JS_FALSE; + for (i = 0; i < CLASS_CACHE_SIZE; i++) { + if (state->classCache[i].start) { + if (state->classCache[i].length == state->result->u.ucclass.kidlen) { + foundCachedCopy = JS_TRUE; + for (n = 0; n < state->classCache[i].length; n++) { + if (state->classCache[i].start[n] != termStart[n]) { + foundCachedCopy = JS_FALSE; + break; + } + } + if (foundCachedCopy) { + state->result->u.ucclass.index = state->classCache[i].index; + break; + } + } + } + else { + state->classCache[i].start = termStart; + state->classCache[i].length = state->result->u.ucclass.kidlen; + state->classCache[i].index = state->classCount; + break; + } + } + if (!foundCachedCopy) + state->result->u.ucclass.index = state->classCount++; + /* + * Call calculateBitmapSize now as we want any errors it finds + * to be reported during the parse phase, not at execution. + */ + if (!calculateBitmapSize(state, state->result, termStart, state->cp++)) + return JS_FALSE; + state->progLength += 3; /* CLASS, */ + break; + + case '.': + state->result = NewRENode(state, REOP_DOT); + goto doSimple; + case '*': + case '+': + case '?': + js_ReportCompileErrorNumber(state->context, state->tokenStream, + NULL, JSREPORT_ERROR, + JSMSG_BAD_QUANTIFIER, state->cp - 1); + return JS_FALSE; +#if 0 + case '{': /* balance '}' */ + /* Treat left-curly in a non-quantifier context as an error only + * if it's followed immediately by a decimal digit. + * This is an Perl extension. + */ + if ((state->cp != state->cpend) && JS7_ISDEC(*state->cp)) { + js_ReportCompileErrorNumber(state->context, state->tokenStream, + NULL, JSREPORT_ERROR, + JSMSG_BAD_QUANTIFIER, state->cp - 1); + return JS_FALSE; + } + /* fall thru... */ +#endif + default: + state->result = NewRENode(state, REOP_FLAT); + if (!state->result) + return JS_FALSE; + state->result->u.flat.chr = c; + state->result->u.flat.length = 1; + state->result->kid = (void *)(state->cp - 1); + state->progLength += 3; + break; + } + return parseQuantifier(state); +} + +static JSBool +parseQuantifier(CompilerState *state) +{ + RENode *term; + term = state->result; + if (state->cp < state->cpend) { + switch (*state->cp) { + case '+': + state->result = NewRENode(state, REOP_QUANT); + if (!state->result) + return JS_FALSE; + state->result->u.range.min = 1; + state->result->u.range.max = -1; + /* , ... */ + state->progLength += 4; + goto quantifier; + case '*': + state->result = NewRENode(state, REOP_QUANT); + if (!state->result) + return JS_FALSE; + state->result->u.range.min = 0; + state->result->u.range.max = -1; + /* , ... */ + state->progLength += 4; + goto quantifier; + case '?': + state->result = NewRENode(state, REOP_QUANT); + if (!state->result) + return JS_FALSE; + state->result->u.range.min = 0; + state->result->u.range.max = 1; + /* , ... */ + state->progLength += 4; + goto quantifier; + case '{': /* balance '}' */ + { + intN err; + intN min = 0; + intN max = -1; + jschar c; + const jschar *errp = state->cp++; + + c = *state->cp; + if (JS7_ISDEC(c)) { + ++state->cp; + min = getDecimalValue(c, state); + c = *state->cp; + + if ((min + 1) >> 16) { + err = JSMSG_MIN_TOO_BIG; + goto quantError; + } + if (c == ',') { + c = *++state->cp; + if (JS7_ISDEC(c)) { + ++state->cp; + max = getDecimalValue(c, state); + c = *state->cp; + if ((max + 1) >> 16) { + err = JSMSG_MAX_TOO_BIG; + goto quantError; + } + if (min > max) { + err = JSMSG_OUT_OF_ORDER; + goto quantError; + } + } + } + else { + max = min; + } + if (c == '}') { + state->result = NewRENode(state, REOP_QUANT); + if (!state->result) + return JS_FALSE; + state->result->u.range.min = min; + state->result->u.range.max = max; + /* QUANT, , , ... */ + state->progLength += 8; + goto quantifier; + } + } + state->cp = errp; + return JS_TRUE; +quantError: + js_ReportCompileErrorNumber(state->context, + state->tokenStream, + NULL, JSREPORT_ERROR, + err, errp); + return JS_FALSE; + } + } + } + return JS_TRUE; + +quantifier: + ++state->treeDepth; + ++state->cp; + state->result->kid = term; + if ((state->cp < state->cpend) && (*state->cp == '?')) { + ++state->cp; + state->result->u.range.greedy = JS_FALSE; + } + else + state->result->u.range.greedy = JS_TRUE; + return JS_TRUE; +} + +#define CHECK_OFFSET(diff) (JS_ASSERT(((diff) >= -32768) && ((diff) <= 32767))) +#define SET_OFFSET(pc,off) ((pc)[0] = JUMP_OFFSET_HI(off), \ + (pc)[1] = JUMP_OFFSET_LO(off)) +#define GET_OFFSET(pc) ((int16)(((pc)[0] << 8) | (pc)[1])) +#define OFFSET_LEN (2) +#define GET_ARG(pc) GET_OFFSET(pc) +#define SET_ARG(pc,arg) SET_OFFSET(pc,arg) +#define ARG_LEN OFFSET_LEN + +/* + * Recursively generate bytecode for the tree rooted at t. Iteratively. + */ + +typedef struct { + RENode *nextAlt; + jsbytecode *nextAltFixup, *nextTermFixup, *endTermFixup; + RENode *continueNode; + REOp continueOp; +} EmitStateStackEntry; + +static jsbytecode * +emitREBytecode(CompilerState *state, JSRegExp *re, intN treeDepth, + jsbytecode *pc, RENode *t) +{ + ptrdiff_t diff; + RECharSet *charSet; + EmitStateStackEntry *emitStateSP, *emitStateStack = NULL; + REOp op; + + if (treeDepth) { + emitStateStack = + (EmitStateStackEntry *)JS_malloc(state->context, + sizeof(EmitStateStackEntry) + * treeDepth); + if (!emitStateStack) + return NULL; + } + emitStateSP = emitStateStack; + op = t->op; + + while (JS_TRUE) { + *pc++ = op; + switch (op) { + case REOP_EMPTY: + --pc; + break; + + case REOP_ALTPREREQ2: + case REOP_ALTPREREQ: + JS_ASSERT(emitStateSP); + emitStateSP->endTermFixup = pc; + pc += OFFSET_LEN; + SET_ARG(pc, t->u.altprereq.ch1); + pc += ARG_LEN; + SET_ARG(pc, t->u.altprereq.ch2); + pc += ARG_LEN; + + emitStateSP->nextAltFixup = pc; /* address of next alternate */ + pc += OFFSET_LEN; + + emitStateSP->continueNode = t; + emitStateSP->continueOp = REOP_JUMP; + ++emitStateSP; + JS_ASSERT((emitStateSP - emitStateStack) <= treeDepth); + t = (RENode *)(t->kid); + op = t->op; + continue; + + case REOP_JUMP: + emitStateSP->nextTermFixup = pc; /* address of following term */ + pc += OFFSET_LEN; + diff = pc - emitStateSP->nextAltFixup; + CHECK_OFFSET(diff); + SET_OFFSET(emitStateSP->nextAltFixup, diff); + emitStateSP->continueOp = REOP_ENDALT; + ++emitStateSP; + JS_ASSERT((emitStateSP - emitStateStack) <= treeDepth); + t = (RENode *)(t->u.kid2); + op = t->op; + continue; + + case REOP_ENDALT: + diff = pc - emitStateSP->nextTermFixup; + CHECK_OFFSET(diff); + SET_OFFSET(emitStateSP->nextTermFixup, diff); + if (t->op != REOP_ALT) { + diff = pc - emitStateSP->endTermFixup; + CHECK_OFFSET(diff); + SET_OFFSET(emitStateSP->endTermFixup, diff); + } + break; + + case REOP_ALT: + JS_ASSERT(emitStateSP); + emitStateSP->nextAltFixup = pc; /* address of pointer to next alternate */ + pc += OFFSET_LEN; + emitStateSP->continueNode = t; + emitStateSP->continueOp = REOP_JUMP; + ++emitStateSP; + JS_ASSERT((emitStateSP - emitStateStack) <= treeDepth); + t = (RENode *)(t->kid); + op = t->op; + continue; + + case REOP_FLAT: + /* + * Consecutize FLAT's if possible. + */ + if (t->kid) { + while (t->next && (t->next->op == REOP_FLAT) + && (((jschar*)(t->kid) + t->u.flat.length) + == (jschar*)(t->next->kid))) { + t->u.flat.length += t->next->u.flat.length; + t->next = t->next->next; + } + } + if (t->kid && (t->u.flat.length > 1)) { + if (state->flags & JSREG_FOLD) + pc[-1] = REOP_FLATi; + else + pc[-1] = REOP_FLAT; + SET_ARG(pc, (jschar *)(t->kid) - state->cpbegin); + pc += ARG_LEN; + SET_ARG(pc, t->u.flat.length); + pc += ARG_LEN; + } + else { + if (t->u.flat.chr < 256) { + if (state->flags & JSREG_FOLD) + pc[-1] = REOP_FLAT1i; + else + pc[-1] = REOP_FLAT1; + *pc++ = (jsbytecode)(t->u.flat.chr); + } + else { + if (state->flags & JSREG_FOLD) + pc[-1] = REOP_UCFLAT1i; + else + pc[-1] = REOP_UCFLAT1; + SET_ARG(pc, t->u.flat.chr); + pc += ARG_LEN; + } + } + break; + + case REOP_LPAREN: + JS_ASSERT(emitStateSP); + SET_ARG(pc, t->u.parenIndex); + pc += ARG_LEN; + emitStateSP->continueNode = t; + emitStateSP->continueOp = REOP_RPAREN; + ++emitStateSP; + JS_ASSERT((emitStateSP - emitStateStack) <= treeDepth); + t = (RENode *)(t->kid); + op = t->op; + continue; + case REOP_RPAREN: + SET_ARG(pc, t->u.parenIndex); + pc += ARG_LEN; + break; + + case REOP_BACKREF: + SET_ARG(pc, t->u.parenIndex); + pc += ARG_LEN; + break; + case REOP_ASSERT: + JS_ASSERT(emitStateSP); + emitStateSP->nextTermFixup = pc; + pc += OFFSET_LEN; + emitStateSP->continueNode = t; + emitStateSP->continueOp = REOP_ASSERTTEST; + ++emitStateSP; + JS_ASSERT((emitStateSP - emitStateStack) <= treeDepth); + t = (RENode *)(t->kid); + op = t->op; + continue; + case REOP_ASSERTTEST: + case REOP_ASSERTNOTTEST: + diff = pc - emitStateSP->nextTermFixup; + CHECK_OFFSET(diff); + SET_OFFSET(emitStateSP->nextTermFixup, diff); + break; + case REOP_ASSERT_NOT: + JS_ASSERT(emitStateSP); + emitStateSP->nextTermFixup = pc; + pc += OFFSET_LEN; + emitStateSP->continueNode = t; + emitStateSP->continueOp = REOP_ASSERTNOTTEST; + ++emitStateSP; + JS_ASSERT((emitStateSP - emitStateStack) <= treeDepth); + t = (RENode *)(t->kid); + op = t->op; + continue; + case REOP_QUANT: + JS_ASSERT(emitStateSP); + if ((t->u.range.min == 0) && (t->u.range.max == (uint16)(-1))) + pc[-1] = (t->u.range.greedy) ? REOP_STAR : REOP_MINIMALSTAR; + else + if ((t->u.range.min == 0) && (t->u.range.max == 1)) + pc[-1] = (t->u.range.greedy) ? REOP_OPT : REOP_MINIMALOPT; + else + if ((t->u.range.min == 1) && (t->u.range.max == (uint16)(-1))) + pc[-1] = (t->u.range.greedy) ? REOP_PLUS : REOP_MINIMALPLUS; + else { + if (!t->u.range.greedy) pc[-1] = REOP_MINIMALQUANT; + SET_ARG(pc, t->u.range.min); + pc += ARG_LEN; + SET_ARG(pc, t->u.range.max); + pc += ARG_LEN; + } + emitStateSP->nextTermFixup = pc; + pc += OFFSET_LEN; + emitStateSP->continueNode = t; + emitStateSP->continueOp = REOP_ENDCHILD; + ++emitStateSP; + JS_ASSERT((emitStateSP - emitStateStack) <= treeDepth); + t = (RENode *)(t->kid); + op = t->op; + continue; + case REOP_ENDCHILD: + diff = pc - emitStateSP->nextTermFixup; + CHECK_OFFSET(diff); + SET_OFFSET(emitStateSP->nextTermFixup, diff); + break; + case REOP_CLASS: + if (!t->u.ucclass.sense) + pc[-1] = REOP_NCLASS; + SET_ARG(pc, t->u.ucclass.index); + pc += ARG_LEN; + charSet = &re->classList[t->u.ucclass.index]; + charSet->converted = JS_FALSE; + charSet->length = t->u.ucclass.bmsize; + charSet->u.src.startIndex = t->u.ucclass.startIndex; + charSet->u.src.length = t->u.ucclass.kidlen; + charSet->sense = t->u.ucclass.sense; + break; + default: + break; + } + t = t->next; + if (t == NULL) { + if (emitStateSP == emitStateStack) + break; + --emitStateSP; + t = emitStateSP->continueNode; + op = emitStateSP->continueOp; + } + else + op = t->op; + } + if (emitStateStack) + JS_free(state->context, emitStateStack); + return pc; +} + + +JSRegExp * +js_NewRegExp(JSContext *cx, JSTokenStream *ts, + JSString *str, uintN flags, JSBool flat) +{ + JSRegExp *re; + void *mark; + CompilerState state; + size_t resize; + jsbytecode *endPC; + uint32 i; + size_t len; + + re = NULL; + mark = JS_ARENA_MARK(&cx->tempPool); + + state.context = cx; + state.tokenStream = ts; + state.cpbegin = state.cp = JSSTRING_CHARS(str); + state.cpend = state.cp + JSSTRING_LENGTH(str); + state.flags = flags; + state.parenCount = 0; + state.classCount = 0; + state.progLength = 0; + state.treeDepth = 0; + for (i = 0; i < CLASS_CACHE_SIZE; i++) + state.classCache[i].start = NULL; + + len = JSSTRING_LENGTH(str); + + if (len != 0 && flat) { + state.result = NewRENode(&state, REOP_FLAT); + state.result->u.flat.chr = *state.cpbegin; + state.result->u.flat.length = JSSTRING_LENGTH(str); + state.result->kid = (void *)(state.cpbegin); + state.progLength += 5; + } + else { + if (!parseRegExp(&state)) + goto out; + } + resize = sizeof *re + state.progLength + 1; + re = (JSRegExp *) JS_malloc(cx, JS_ROUNDUP(resize, sizeof(jsword))); + if (!re) + goto out; + + re->classCount = state.classCount; + if (state.classCount) { + re->classList = (RECharSet *)JS_malloc(cx, sizeof(RECharSet) + * state.classCount); + if (!re->classList) + goto out; + } + else + re->classList = NULL; + endPC = emitREBytecode(&state, re, state.treeDepth, re->program, state.result); + if (!endPC) { + re = NULL; + goto out; + } + *endPC++ = REOP_END; + JS_ASSERT(endPC <= (re->program + (state.progLength + 1))); + + re->nrefs = 1; + re->parenCount = state.parenCount; + re->flags = flags; + re->source = str; + +out: + JS_ARENA_RELEASE(&cx->tempPool, mark); + return re; +} + +JSRegExp * +js_NewRegExpOpt(JSContext *cx, JSTokenStream *ts, + JSString *str, JSString *opt, JSBool flat) +{ + uintN flags; + jschar *s; + size_t i, n; + char charBuf[2]; + + flags = 0; + if (opt) { + s = JSSTRING_CHARS(opt); + for (i = 0, n = JSSTRING_LENGTH(opt); i < n; i++) { + switch (s[i]) { + case 'g': + flags |= JSREG_GLOB; + break; + case 'i': + flags |= JSREG_FOLD; + break; + case 'm': + flags |= JSREG_MULTILINE; + break; + default: + charBuf[0] = (char)s[i]; + charBuf[1] = '\0'; + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_BAD_FLAG, charBuf); + return NULL; + } + } + } + return js_NewRegExp(cx, ts, str, flags, flat); +} + + +#define HOLD_REGEXP(cx, re) JS_ATOMIC_INCREMENT(&(re)->nrefs) +#define DROP_REGEXP(cx, re) js_DestroyRegExp(cx, re) + +/* + * Save the current state of the match - the position in the input + * text as well as the position in the bytecode. The state of any + * parent expressions is also saved (preceding state). + * Contents of parenCount parentheses from parenIndex are also saved. + */ +static REBackTrackData * +pushBackTrackState(REGlobalData *gData, REOp op, + jsbytecode *target, REMatchState *x, const jschar *cp, + intN parenIndex, intN parenCount) +{ + intN i; + REBackTrackData *result + = (REBackTrackData *)((char *)(gData->backTrackSP) + gData->cursz); + + size_t sz = sizeof(REBackTrackData) + + gData->stateStackTop * sizeof(REProgState) + + parenCount * sizeof(RECapture); + + + if (((char *)result + sz) + > (char *)gData->backTrackStack + gData->maxBackTrack) { + ptrdiff_t offset = (char *)result - (char *)gData->backTrackStack; + gData->backTrackStack + = (REBackTrackData *)JS_ArenaGrow(&gData->pool, + gData->backTrackStack, + gData->maxBackTrack, + gData->maxBackTrack); + gData->maxBackTrack <<= 1; + if (!gData->backTrackStack) + return NULL; + result = (REBackTrackData *)((char *)gData->backTrackStack + offset); + } + gData->backTrackSP = result; + result->sz = gData->cursz; + gData->cursz = sz; + + result->backtrack_op = op; + result->backtrack_pc = target; + result->cp = cp; + result->parenCount = parenCount; + + result->precedingStateTop = gData->stateStackTop; + JS_ASSERT(gData->stateStackTop); + memcpy(result + 1, gData->stateStack, + sizeof(REProgState) * result->precedingStateTop); + + if (parenCount != -1) { + result->parenIndex = parenIndex; + memcpy((char *)(result + 1) + + sizeof(REProgState) * result->precedingStateTop, + &x->parens[parenIndex], + sizeof(RECapture) * parenCount); + for (i = 0; i < parenCount; i++) + x->parens[parenIndex + i].index = -1; + } + + return result; +} + + +/* + * Consecutive literal characters. + */ +#if 0 +static REMatchState * +flatNMatcher(REGlobalData *gData, REMatchState *x, jschar *matchChars, + intN length) +{ + intN i; + if ((x->cp + length) > gData->cpend) + return NULL; + for (i = 0; i < length; i++) { + if (matchChars[i] != x->cp[i]) + return NULL; + } + x->cp += length; + return x; +} +#endif + +static REMatchState * +flatNIMatcher(REGlobalData *gData, REMatchState *x, jschar *matchChars, + intN length) +{ + intN i; + if ((x->cp + length) > gData->cpend) + return NULL; + for (i = 0; i < length; i++) { + if (upcase(matchChars[i]) != upcase(x->cp[i])) + return NULL; + } + x->cp += length; + return x; +} + +/* + * 1. Evaluate DecimalEscape to obtain an EscapeValue E. + * 2. If E is not a character then go to step 6. + * 3. Let ch be E's character. + * 4. Let A be a one-element RECharSet containing the character ch. + * 5. Call CharacterSetMatcher(A, false) and return its Matcher result. + * 6. E must be an integer. Let n be that integer. + * 7. If n=0 or n>NCapturingParens then throw a SyntaxError exception. + * 8. Return an internal Matcher closure that takes two arguments, a State x + * and a Continuation c, and performs the following: + * 1. Let cap be x's captures internal array. + * 2. Let s be cap[n]. + * 3. If s is undefined, then call c(x) and return its result. + * 4. Let e be x's endIndex. + * 5. Let len be s's length. + * 6. Let f be e+len. + * 7. If f>InputLength, return failure. + * 8. If there exists an integer i between 0 (inclusive) and len (exclusive) + * such that Canonicalize(s[i]) is not the same character as + * Canonicalize(Input [e+i]), then return failure. + * 9. Let y be the State (f, cap). + * 10. Call c(y) and return its result. + */ +static REMatchState * +backrefMatcher(REGlobalData *gData, REMatchState *x, uintN parenIndex) +{ + uintN len; + uintN i; + const jschar *parenContent; + RECapture *s = &x->parens[parenIndex]; + if (s->index == -1) + return x; + + len = s->length; + if ((x->cp + len) > gData->cpend) + return NULL; + + parenContent = &gData->cpbegin[s->index]; + if (gData->regexp->flags & JSREG_FOLD) { + for (i = 0; i < len; i++) { + if (upcase(parenContent[i]) != upcase(x->cp[i])) + return NULL; + } + } + else { + for (i = 0; i < len; i++) { + if (parenContent[i] != x->cp[i]) + return NULL; + } + } + x->cp += len; + return x; +} + + +/* Add a single character to the RECharSet */ +static void +addCharacterToCharSet(RECharSet *cs, jschar c) +{ + uintN byteIndex = (uintN)(c / 8); + JS_ASSERT(c <= cs->length); + cs->u.bits[byteIndex] |= 1 << (c & 0x7); +} + + +/* Add a character range, c1 to c2 (inclusive) to the RECharSet */ +static void +addCharacterRangeToCharSet(RECharSet *cs, jschar c1, jschar c2) +{ + uintN i; + + uintN byteIndex1 = (uintN)(c1 / 8); + uintN byteIndex2 = (uintN)(c2 / 8); + + JS_ASSERT((c2 <= cs->length) && (c1 <= c2)); + + c1 &= 0x7; + c2 &= 0x7; + + if (byteIndex1 == byteIndex2) + cs->u.bits[byteIndex1] |= ((uint8)(0xFF) >> (7 - (c2 - c1))) << c1; + else { + cs->u.bits[byteIndex1] |= 0xFF << c1; + for (i = byteIndex1 + 1; i < byteIndex2; i++) + cs->u.bits[i] = 0xFF; + cs->u.bits[byteIndex2] |= (uint8)(0xFF) >> (7 - c2); + } +} + +/* Compile the source of the class into a RECharSet */ +static JSBool +processCharSet(REGlobalData *gData, RECharSet *charSet) +{ + const jschar *src = JSSTRING_CHARS(gData->regexp->source) + + charSet->u.src.startIndex; + const jschar *end = src + charSet->u.src.length; + + jschar rangeStart, thisCh; + uintN byteLength; + jschar c; + uintN n; + intN nDigits; + intN i; + JSBool inRange = JS_FALSE; + + JS_ASSERT(!charSet->converted); + charSet->converted = JS_TRUE; + + byteLength = (charSet->length / 8) + 1; + charSet->u.bits = (uint8 *)JS_malloc(gData->cx, byteLength); + if (!charSet->u.bits) + return JS_FALSE; + memset(charSet->u.bits, 0, byteLength); + + if (src == end) + return JS_TRUE; + + if (*src == '^') { + JS_ASSERT(charSet->sense == JS_FALSE); + ++src; + } + else + JS_ASSERT(charSet->sense == JS_TRUE); + + + while (src != end) { + switch (*src) { + case '\\': + ++src; + c = *src++; + switch (c) { + case 'b': + thisCh = 0x8; + break; + case 'f': + thisCh = 0xC; + break; + case 'n': + thisCh = 0xA; + break; + case 'r': + thisCh = 0xD; + break; + case 't': + thisCh = 0x9; + break; + case 'v': + thisCh = 0xB; + break; + case 'c': + if (((src + 1) < end) && JS_ISWORD(src[1])) + thisCh = (jschar)(*src++ & 0x1F); + else { + --src; + thisCh = '\\'; + } + break; + case 'x': + nDigits = 2; + goto lexHex; + case 'u': + nDigits = 4; +lexHex: + n = 0; + for (i = 0; (i < nDigits) && (src < end); i++) { + uintN digit; + c = *src++; + if (!isASCIIHexDigit(c, &digit)) { + /* + * Back off to accepting the original '\' + * as a literal + */ + src -= (i + 1); + n = '\\'; + break; + } + n = (n << 4) | digit; + } + thisCh = (jschar)(n); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + /* + * This is a non-ECMA extension - decimal escapes (in this + * case, octal!) are supposed to be an error inside class + * ranges, but supported here for backwards compatibility. + * + */ + n = JS7_UNDEC(c); + c = *src; + if ('0' <= c && c <= '7') { + src++; + n = 8 * n + JS7_UNDEC(c); + c = *src; + if ('0' <= c && c <= '7') { + src++; + i = 8 * n + JS7_UNDEC(c); + if (i <= 0377) + n = i; + else + src--; + } + } + thisCh = (jschar)(n); + break; + + case 'd': + addCharacterRangeToCharSet(charSet, '0', '9'); + continue; /* don't need range processing */ + case 'D': + addCharacterRangeToCharSet(charSet, 0, '0' - 1); + addCharacterRangeToCharSet(charSet, (jschar)('9' + 1), + (jschar)(charSet->length)); + continue; + case 's': + for (i = (intN)(charSet->length); i >= 0; i--) + if (JS_ISSPACE(i)) + addCharacterToCharSet(charSet, (jschar)(i)); + continue; + case 'S': + for (i = (intN)(charSet->length); i >= 0; i--) + if (!JS_ISSPACE(i)) + addCharacterToCharSet(charSet, (jschar)(i)); + continue; + case 'w': + for (i = (intN)(charSet->length); i >= 0; i--) + if (JS_ISWORD(i)) + addCharacterToCharSet(charSet, (jschar)(i)); + continue; + case 'W': + for (i = (intN)(charSet->length); i >= 0; i--) + if (!JS_ISWORD(i)) + addCharacterToCharSet(charSet, (jschar)(i)); + continue; + default: + thisCh = c; + break; + + } + break; + + default: + thisCh = *src++; + break; + + } + if (inRange) { + if (gData->regexp->flags & JSREG_FOLD) { + addCharacterRangeToCharSet(charSet, upcase(rangeStart), + upcase(thisCh)); + addCharacterRangeToCharSet(charSet, downcase(rangeStart), + downcase(thisCh)); + } else { + addCharacterRangeToCharSet(charSet, rangeStart, thisCh); + } + inRange = JS_FALSE; + } + else { + if (gData->regexp->flags & JSREG_FOLD) { + addCharacterToCharSet(charSet, upcase(thisCh)); + addCharacterToCharSet(charSet, downcase(thisCh)); + } else { + addCharacterToCharSet(charSet, thisCh); + } + if (src < (end - 1)) { + if (*src == '-') { + ++src; + inRange = JS_TRUE; + rangeStart = thisCh; + } + } + } + } + return JS_TRUE; +} + +void +js_DestroyRegExp(JSContext *cx, JSRegExp *re) +{ + uintN i; + if (JS_ATOMIC_DECREMENT(&re->nrefs) == 0) { + if (re->classList) { + for (i = 0; i < re->classCount; i++) { + if (re->classList[i].converted) + JS_free(cx, re->classList[i].u.bits); + re->classList[i].u.bits = NULL; + } + JS_free(cx, re->classList); + } + JS_free(cx, re); + } +} + +static JSBool +reallocStateStack(REGlobalData *gData) +{ + size_t sz = sizeof(REProgState) * gData->maxStateStack; + gData->maxStateStack <<= 1; + gData->stateStack + = (REProgState *)JS_ArenaGrow(&gData->pool, gData->stateStack, sz, sz); + if (!gData->stateStack) { + gData->ok = JS_FALSE; + return JS_FALSE; + } + return JS_TRUE; +} + +/* +* Apply the current op against the given input to see if +* it's going to match or fail. Return false if we don't +* get a match, true if we do and update the state of the +* input and pc if the update flag is true. +*/ +static REMatchState *simpleMatch(REGlobalData *gData, REMatchState *x, + REOp op, jsbytecode **startpc, JSBool update) +{ + REMatchState *result = NULL; + jschar matchCh; + intN parenIndex; + intN offset, length, index; + jsbytecode *pc = *startpc; /* pc has already been incremented past op */ + jschar *source; + const jschar *startcp = x->cp; + jschar ch; + RECharSet *charSet; + + + switch (op) { + default: + JS_ASSERT(JS_FALSE); + case REOP_BOL: + if (x->cp != gData->cpbegin) { + if (gData->cx->regExpStatics.multiline || + (gData->regexp->flags & JSREG_MULTILINE)) { + if (!RE_IS_LINE_TERM(x->cp[-1])) + break; + } + else + break; + } + result = x; + break; + case REOP_EOL: + if (x->cp != gData->cpend) { + if (gData->cx->regExpStatics.multiline || + (gData->regexp->flags & JSREG_MULTILINE)) { + if (!RE_IS_LINE_TERM(*x->cp)) + break; + } + else + break; + } + result = x; + break; + case REOP_WBDRY: + if ((x->cp == gData->cpbegin || !JS_ISWORD(x->cp[-1])) + ^ !((x->cp != gData->cpend) && JS_ISWORD(*x->cp))) + result = x; + break; + case REOP_WNONBDRY: + if ((x->cp == gData->cpbegin || !JS_ISWORD(x->cp[-1])) + ^ ((x->cp != gData->cpend) && JS_ISWORD(*x->cp))) + result = x; + break; + case REOP_DOT: + if (x->cp != gData->cpend && !RE_IS_LINE_TERM(*x->cp)) { + result = x; + result->cp++; + } + break; + case REOP_DIGIT: + if (x->cp != gData->cpend && JS_ISDIGIT(*x->cp)) { + result = x; + result->cp++; + } + break; + case REOP_NONDIGIT: + if (x->cp != gData->cpend && !JS_ISDIGIT(*x->cp)) { + result = x; + result->cp++; + } + break; + case REOP_ALNUM: + if (x->cp != gData->cpend && JS_ISWORD(*x->cp)) { + result = x; + result->cp++; + } + break; + case REOP_NONALNUM: + if (x->cp != gData->cpend && !JS_ISWORD(*x->cp)) { + result = x; + result->cp++; + } + break; + case REOP_SPACE: + if (x->cp != gData->cpend && JS_ISSPACE(*x->cp)) { + result = x; + result->cp++; + } + break; + case REOP_NONSPACE: + if (x->cp != gData->cpend && !JS_ISSPACE(*x->cp)) { + result = x; + result->cp++; + } + break; + case REOP_BACKREF: + parenIndex = GET_ARG(pc); + pc += ARG_LEN; + result = backrefMatcher(gData, x, parenIndex); + break; + case REOP_FLAT: + offset = GET_ARG(pc); + pc += ARG_LEN; + length = GET_ARG(pc); + pc += ARG_LEN; + source = JSSTRING_CHARS(gData->regexp->source) + offset; + if ((x->cp + length) <= gData->cpend) { + for (index = 0; index < length; index++) { + if (source[index] != x->cp[index]) + return NULL; + } + x->cp += length; + result = x; + } + break; + case REOP_FLAT1: + matchCh = *pc++; + if ((x->cp != gData->cpend) && (*x->cp == matchCh)) { + result = x; + result->cp++; + } + break; + case REOP_FLATi: + offset = GET_ARG(pc); + pc += ARG_LEN; + length = GET_ARG(pc); + pc += ARG_LEN; + source = JSSTRING_CHARS(gData->regexp->source); + result = flatNIMatcher(gData, x, source + offset, length); + break; + case REOP_FLAT1i: + matchCh = *pc++; + if ((x->cp != gData->cpend) && (upcase(*x->cp) == upcase(matchCh))) { + result = x; + result->cp++; + } + break; + case REOP_UCFLAT1: + matchCh = GET_ARG(pc); + pc += ARG_LEN; + if ((x->cp != gData->cpend) && (*x->cp == matchCh)) { + result = x; + result->cp++; + } + break; + case REOP_UCFLAT1i: + matchCh = GET_ARG(pc); + pc += ARG_LEN; + if ((x->cp != gData->cpend) && (upcase(*x->cp) == upcase(matchCh))) { + result = x; + result->cp++; + } + break; + case REOP_CLASS: + index = GET_ARG(pc); + pc += ARG_LEN; + if (x->cp != gData->cpend) { + charSet = &gData->regexp->classList[index]; + JS_ASSERT(charSet->converted); + ch = *x->cp; + index = ch / 8; + if ((charSet->length != 0) && + ( (ch <= charSet->length) + && ((charSet->u.bits[index] & (1 << (ch & 0x7))) != 0) )) { + result = x; + result->cp++; + } + } + break; + case REOP_NCLASS: + index = GET_ARG(pc); + pc += ARG_LEN; + if (x->cp != gData->cpend) { + charSet = &gData->regexp->classList[index]; + JS_ASSERT(charSet->converted); + ch = *x->cp; + index = ch / 8; + if ((charSet->length == 0) || + ( (ch > charSet->length) + || ((charSet->u.bits[index] & (1 << (ch & 0x7))) == 0) )) { + result = x; + result->cp++; + } + } + break; + } + if (result != NULL) { + if (update) + *startpc = pc; + else + x->cp = startcp; + return result; + } + x->cp = startcp; + return NULL; +} + +static REMatchState * +executeREBytecode(REGlobalData *gData, REMatchState *x) +{ + REMatchState *result; + REBackTrackData *backTrackData; + intN offset; + jsbytecode *nextpc; + REOp nextop; + RECapture *cap; + REProgState *curState; + const jschar *startcp; + uintN parenIndex, k; + uintN parenSoFar = 0; + + jschar matchCh1, matchCh2; + RECharSet *charSet; + + JSBool anchor; + jsbytecode *pc = gData->regexp->program; + REOp op = (REOp)(*pc++); + + /* + * If the first node is a simple match, step the index into + * the string until that match is made, or fail if it can't be + * found at all. + */ + if (REOP_IS_SIMPLE(op)) { + anchor = JS_FALSE; + while (x->cp <= gData->cpend) { + nextpc = pc; /* reset back to start each time */ + result = simpleMatch(gData, x, op, &nextpc, JS_TRUE); + if (result) { + anchor = JS_TRUE; + x = result; + pc = nextpc; /* accept skip to next opcode */ + op = (REOp)(*pc++); + break; + } + else { + gData->skipped++; + x->cp++; + } + } + if (!anchor) + return NULL; + } + + while (JS_TRUE) { + if (REOP_IS_SIMPLE(op)) + result = simpleMatch(gData, x, op, &pc, JS_TRUE); + else { + curState = &gData->stateStack[gData->stateStackTop]; + switch (op) { + case REOP_EMPTY: + result = x; + break; + + case REOP_ALTPREREQ2: + nextpc = pc + GET_OFFSET(pc); /* start of next op */ + pc += ARG_LEN; + matchCh2 = GET_ARG(pc); + pc += ARG_LEN; + k = GET_ARG(pc); + pc += ARG_LEN; + + if (x->cp != gData->cpend) { + if (*x->cp == matchCh2) + goto doAlt; + + charSet = &gData->regexp->classList[k]; + if (!charSet->converted) + if (!processCharSet(gData, charSet)) + return NULL; + matchCh1 = *x->cp; + k = matchCh1 / 8; + if ((charSet->length == 0 || + matchCh1 > charSet->length || + (charSet->u.bits[k] & (1 << (matchCh1 & 0x7))) == 0) + ^ charSet->sense) { + goto doAlt; + } + } + result = NULL; + break; + + case REOP_ALTPREREQ: + nextpc = pc + GET_OFFSET(pc); /* start of next op */ + pc += ARG_LEN; + matchCh1 = GET_ARG(pc); + pc += ARG_LEN; + matchCh2 = GET_ARG(pc); + pc += ARG_LEN; + if ((x->cp == gData->cpend) + || ((*x->cp != matchCh1) && (*x->cp != matchCh2))) { + result = NULL; + break; + } + /* else false thru... */ + + case REOP_ALT: +doAlt: + nextpc = pc + GET_OFFSET(pc); /* start of next alternate */ + pc += ARG_LEN; /* start of this alternate */ + curState->parenSoFar = parenSoFar; + ++gData->stateStackTop; + if (gData->stateStackTop == gData->maxStateStack) + if (!reallocStateStack(gData)) + return NULL; + op = (REOp)(*pc++); + startcp = x->cp; + if (REOP_IS_SIMPLE(op)) { + if (!simpleMatch(gData, x, op, &pc, JS_TRUE)) { + op = (REOp)(*nextpc++); + pc = nextpc; + continue; + } + else { /* accept the match and move on */ + result = x; + op = (REOp)(*pc++); + } + } + nextop = (REOp)(*nextpc++); + if (!pushBackTrackState(gData, nextop, nextpc, x, startcp, 0, 0)) + return NULL; + continue; + + /* + * Occurs at (succesful) end of REOP_ALT, + */ + case REOP_JUMP: + --gData->stateStackTop; + offset = GET_OFFSET(pc); + pc += offset; + op = (REOp)(*pc++); + continue; + + /* + * Occurs at last (succesful) end of REOP_ALT, + */ + case REOP_ENDALT: + --gData->stateStackTop; + op = (REOp)(*pc++); + continue; + + case REOP_LPAREN: + parenIndex = GET_ARG(pc); + if ((parenIndex + 1) > parenSoFar) + parenSoFar = parenIndex + 1; + pc += ARG_LEN; + x->parens[parenIndex].index = x->cp - gData->cpbegin; + x->parens[parenIndex].length = 0; + op = (REOp)(*pc++); + continue; + case REOP_RPAREN: + parenIndex = GET_ARG(pc); + pc += ARG_LEN; + cap = &x->parens[parenIndex]; + cap->length = x->cp - (gData->cpbegin + cap->index); + op = (REOp)(*pc++); + continue; + + case REOP_ASSERT: + nextpc = pc + GET_OFFSET(pc); /* start of term after ASSERT */ + pc += ARG_LEN; /* start of ASSERT child */ + op = (REOp)(*pc++); + if (REOP_IS_SIMPLE(op) + && !simpleMatch(gData, x, op, &pc, JS_FALSE)) { + result = NULL; + break; + } + else { + curState->u.assertion.top + = (char *)gData->backTrackSP + - (char *)gData->backTrackStack; + curState->u.assertion.sz = gData->cursz; + curState->index = x->cp - gData->cpbegin; + curState->parenSoFar = parenSoFar; + ++gData->stateStackTop; + if (gData->stateStackTop == gData->maxStateStack) + if (!reallocStateStack(gData)) + return NULL; + if (!pushBackTrackState(gData, REOP_ASSERTTEST, + nextpc, x, x->cp, 0, 0)) + return NULL; + } + continue; + case REOP_ASSERT_NOT: + nextpc = pc + GET_OFFSET(pc); + pc += ARG_LEN; + op = (REOp)(*pc++); + if (REOP_IS_SIMPLE(op) + /* Note - fail to fail! */ + && simpleMatch(gData, x, op, &pc, JS_FALSE)) { + result = NULL; + break; + } + else { + curState->u.assertion.top + = (char *)gData->backTrackSP + - (char *)gData->backTrackStack; + curState->u.assertion.sz = gData->cursz; + curState->index = x->cp - gData->cpbegin; + curState->parenSoFar = parenSoFar; + ++gData->stateStackTop; + if (gData->stateStackTop == gData->maxStateStack) + if (!reallocStateStack(gData)) + return NULL; + if (!pushBackTrackState(gData, REOP_ASSERTNOTTEST, + nextpc, x, x->cp, 0, 0)) + return NULL; + } + continue; + case REOP_ASSERTTEST: + --gData->stateStackTop; + --curState; + x->cp = gData->cpbegin + curState->index; + gData->backTrackSP + = (REBackTrackData *)((char *)gData->backTrackStack + + curState->u.assertion.top); + gData->cursz = curState->u.assertion.sz; + if (result != NULL) + result = x; + break; + case REOP_ASSERTNOTTEST: + --gData->stateStackTop; + --curState; + x->cp = gData->cpbegin + curState->index; + gData->backTrackSP + = (REBackTrackData *)((char *)gData->backTrackStack + + curState->u.assertion.top); + gData->cursz = curState->u.assertion.sz; + if (result == NULL) + result = x; + else + result = NULL; + break; + + case REOP_END: + if (x != NULL) + return x; + break; + + case REOP_STAR: + curState->u.quantifier.min = 0; + curState->u.quantifier.max = -1; + goto quantcommon; + case REOP_PLUS: + curState->u.quantifier.min = 1; + curState->u.quantifier.max = -1; + goto quantcommon; + case REOP_OPT: + curState->u.quantifier.min = 0; + curState->u.quantifier.max = 1; + goto quantcommon; + case REOP_QUANT: + curState->u.quantifier.min = GET_ARG(pc); + pc += ARG_LEN; + curState->u.quantifier.max = GET_ARG(pc); + pc += ARG_LEN; +quantcommon: + if (curState->u.quantifier.max == 0) { + pc = pc + GET_OFFSET(pc); + op = (REOp)(*pc++); + result = x; + continue; + } + /* Step over */ + nextpc = pc + ARG_LEN; + op = (REOp)(*nextpc++); + startcp = x->cp; + if (REOP_IS_SIMPLE(op)) { + if (!simpleMatch(gData, x, op, &nextpc, JS_TRUE)) { + if (curState->u.quantifier.min == 0) + result = x; + else + result = NULL; + pc = pc + GET_OFFSET(pc); + break; + } + else { + op = (REOp)(*nextpc++); + result = x; + } + } + curState->index = startcp - gData->cpbegin; + curState->continue_op = REOP_REPEAT; + curState->continue_pc = pc; + curState->parenSoFar = parenSoFar; + ++gData->stateStackTop; + if (gData->stateStackTop == gData->maxStateStack) + if (!reallocStateStack(gData)) + return NULL; + if (curState->u.quantifier.min == 0) + if (!pushBackTrackState(gData, REOP_REPEAT, + pc, x, startcp, 0, 0)) + return NULL; + pc = nextpc; + continue; + + case REOP_ENDCHILD: /* marks the end of a quantifier child */ + pc = curState[-1].continue_pc; + op = curState[-1].continue_op; + continue; + + case REOP_REPEAT: + --curState; +repeatAgain: + --gData->stateStackTop; + if (result == NULL) { + /* + * There's been a failure, see if we have enough children. + */ + if (curState->u.quantifier.min == 0) { + result = x; + goto repeatDone; + } + break; + } + else { + if ((curState->u.quantifier.min == 0) + && (x->cp == gData->cpbegin + curState->index)) { + /* matched an empty string, that'll get us nowhere */ + result = NULL; + break; + } + if (curState->u.quantifier.min != 0) + curState->u.quantifier.min--; + if (curState->u.quantifier.max != (uint16)(-1)) + curState->u.quantifier.max--; + if (curState->u.quantifier.max == 0) { + result = x; + goto repeatDone; + } + nextpc = pc + ARG_LEN; + nextop = (REOp)(*nextpc); + startcp = x->cp; + if (REOP_IS_SIMPLE(nextop)) { + nextpc++; + if (!simpleMatch(gData, x, nextop, &nextpc, JS_TRUE)) { + if (curState->u.quantifier.min == 0) { + result = x; + goto repeatDone; + } + else + result = NULL; + break; + } + result = x; + } + curState->index = startcp - gData->cpbegin; + ++gData->stateStackTop; + if (gData->stateStackTop == gData->maxStateStack) + if (!reallocStateStack(gData)) + return NULL; + if (curState->u.quantifier.min == 0) + if (!pushBackTrackState(gData, REOP_REPEAT, + pc, x, startcp, + curState->parenSoFar, + parenSoFar + - curState->parenSoFar)) + return NULL; + if (*nextpc == REOP_ENDCHILD) + goto repeatAgain; + pc = nextpc; + op = (REOp)(*pc++); + parenSoFar = curState->parenSoFar; + } + continue; +repeatDone: + pc = pc + GET_OFFSET(pc); + break; + + + case REOP_MINIMALSTAR: + curState->u.quantifier.min = 0; + curState->u.quantifier.max = -1; + goto minimalquantcommon; + case REOP_MINIMALPLUS: + curState->u.quantifier.min = 1; + curState->u.quantifier.max = -1; + goto minimalquantcommon; + case REOP_MINIMALOPT: + curState->u.quantifier.min = 0; + curState->u.quantifier.max = 1; + goto minimalquantcommon; + case REOP_MINIMALQUANT: + curState->u.quantifier.min = GET_ARG(pc); + pc += ARG_LEN; + curState->u.quantifier.max = GET_ARG(pc); + pc += ARG_LEN; +minimalquantcommon: + curState->index = x->cp - gData->cpbegin; + curState->parenSoFar = parenSoFar; + ++gData->stateStackTop; + if (gData->stateStackTop == gData->maxStateStack) + if (!reallocStateStack(gData)) + return NULL; + if (curState->u.quantifier.min != 0) { + curState->continue_op = REOP_MINIMALREPEAT; + curState->continue_pc = pc; + /* step over */ + pc += ARG_LEN; + op = (REOp)(*pc++); + } + else { + if (!pushBackTrackState(gData, REOP_MINIMALREPEAT, + pc, x, x->cp, 0, 0)) + return NULL; + --gData->stateStackTop; + pc = pc + GET_OFFSET(pc); + op = (REOp)(*pc++); + } + continue; + + case REOP_MINIMALREPEAT: + --gData->stateStackTop; + --curState; + + if (result == NULL) { + /* + * Non-greedy failure - try to consume another child. + */ + if ((curState->u.quantifier.max == (uint16)(-1)) + || (curState->u.quantifier.max > 0)) { + curState->index = x->cp - gData->cpbegin; + curState->continue_op = REOP_MINIMALREPEAT; + curState->continue_pc = pc; + pc += ARG_LEN; + for (k = curState->parenSoFar; k < parenSoFar; k++) + x->parens[k].index = -1; + ++gData->stateStackTop; + if (gData->stateStackTop == gData->maxStateStack) + if (!reallocStateStack(gData)) + return NULL; + op = (REOp)(*pc++); + continue; + } + else { + /* Don't need to adjust pc since we're going to pop. */ + break; + } + } + else { + if ((curState->u.quantifier.min == 0) + && (x->cp == gData->cpbegin + curState->index)) { + /* Matched an empty string, that'll get us nowhere. */ + result = NULL; + break; + } + if (curState->u.quantifier.min != 0) + curState->u.quantifier.min--; + if (curState->u.quantifier.max != (uint16)(-1)) + curState->u.quantifier.max--; + if (curState->u.quantifier.min != 0) { + curState->continue_op = REOP_MINIMALREPEAT; + curState->continue_pc = pc; + pc += ARG_LEN; + for (k = curState->parenSoFar; k < parenSoFar; k++) + x->parens[k].index = -1; + curState->index = x->cp - gData->cpbegin; + ++gData->stateStackTop; + if (gData->stateStackTop == gData->maxStateStack) + if (!reallocStateStack(gData)) + return NULL; + op = (REOp)(*pc++); + continue; + } + else { + curState->index = x->cp - gData->cpbegin; + curState->parenSoFar = parenSoFar; + ++gData->stateStackTop; + if (gData->stateStackTop == gData->maxStateStack) + if (!reallocStateStack(gData)) + return NULL; + if (!pushBackTrackState(gData, REOP_MINIMALREPEAT, + pc, x, x->cp, + curState->parenSoFar, + parenSoFar + - curState->parenSoFar)) + return NULL; + --gData->stateStackTop; + pc = pc + GET_OFFSET(pc); + op = (REOp)(*pc++); + continue; + } + } + + default: + JS_ASSERT(JS_FALSE); + result = NULL; + } + } + /* + * If the match failed and there's a backtrack option, take it. + * Otherwise this is a complete and utter failure. + */ + if (result == NULL) { + if (gData->cursz > 0) { + backTrackData = gData->backTrackSP; + gData->cursz = backTrackData->sz; + gData->backTrackSP + = (REBackTrackData *)((char *)backTrackData + - backTrackData->sz); + x->cp = backTrackData->cp; + pc = backTrackData->backtrack_pc; + op = backTrackData->backtrack_op; + gData->stateStackTop = backTrackData->precedingStateTop; + JS_ASSERT(gData->stateStackTop); + + memcpy(gData->stateStack, backTrackData + 1, + sizeof(REProgState) * backTrackData->precedingStateTop); + curState = &gData->stateStack[gData->stateStackTop - 1]; + + if (backTrackData->parenCount) { + memcpy(&x->parens[backTrackData->parenIndex], + (char *)(backTrackData + 1) + sizeof(REProgState) * backTrackData->precedingStateTop, + sizeof(RECapture) * backTrackData->parenCount); + parenSoFar = backTrackData->parenIndex + backTrackData->parenCount; + } + else { + for (k = curState->parenSoFar; k < parenSoFar; k++) + x->parens[k].index = -1; + parenSoFar = curState->parenSoFar; + } + continue; + } + else + return NULL; + } + else + x = result; + + /* + * Continue with the expression. + */ + op = (REOp)*pc++; + } + return NULL; +} + +static REMatchState * +MatchRegExp(REGlobalData *gData, REMatchState *x) +{ + REMatchState *result; + const jschar *cp = x->cp; + const jschar *cp2; + uintN j; + + /* + * Have to include the position beyond the last character + * in order to detect end-of-input/line condition. + */ + for (cp2 = cp; cp2 <= gData->cpend; cp2++) { + gData->skipped = cp2 - cp; + x->cp = cp2; + for (j = 0; j < gData->regexp->parenCount; j++) + x->parens[j].index = -1; + result = executeREBytecode(gData, x); + if (!gData->ok || result) + return result; + gData->backTrackSP = gData->backTrackStack; + gData->cursz = 0; + gData->stateStackTop = 0; + cp2 = cp + gData->skipped; + } + return NULL; +} + + +static REMatchState * +initMatch(JSContext *cx, REGlobalData *gData, JSRegExp *re) +{ + REMatchState *result; + uintN i; + + gData->maxBackTrack = INITIAL_BACKTRACK; + JS_ARENA_ALLOCATE_CAST(gData->backTrackStack, REBackTrackData *, + &gData->pool, + INITIAL_BACKTRACK); + if (!gData->backTrackStack) + return NULL; + gData->backTrackSP = gData->backTrackStack; + gData->cursz = 0; + + + gData->maxStateStack = INITIAL_STATESTACK; + JS_ARENA_ALLOCATE_CAST(gData->stateStack, REProgState *, + &gData->pool, + sizeof(REProgState) * INITIAL_STATESTACK); + if (!gData->stateStack) + return NULL; + gData->stateStackTop = 0; + + gData->cx = cx; + gData->regexp = re; + gData->ok = JS_TRUE; + + JS_ARENA_ALLOCATE_CAST(result, REMatchState *, + &gData->pool, + sizeof(REMatchState) + + (re->parenCount - 1) * sizeof(RECapture)); + if (!result) + return NULL; + + for (i = 0; i < re->classCount; i++) + if (!re->classList[i].converted) + if (!processCharSet(gData, &re->classList[i])) + return NULL; + + return result; +} + +JSBool +js_ExecuteRegExp(JSContext *cx, JSRegExp *re, JSString *str, size_t *indexp, + JSBool test, jsval *rval) +{ + REGlobalData gData; + REMatchState *x, *result; + + const jschar *cp, *ep; + size_t i, length, start; + JSSubString *morepar; + JSBool ok; + JSRegExpStatics *res; + ptrdiff_t matchlen; + uintN num, morenum; + JSString *parstr, *matchstr; + JSObject *obj; + + RECapture *parsub; + + /* + * It's safe to load from cp because JSStrings have a zero at the end, + * and we never let cp get beyond cpend. + */ + start = *indexp; + length = JSSTRING_LENGTH(str); + if (start > length) + start = length; + cp = JSSTRING_CHARS(str); + gData.cpbegin = cp; + gData.cpend = cp + length; + cp += start; + gData.start = start; + gData.skipped = 0; + + JS_InitArenaPool(&gData.pool, "RegExpPool", 8096, 4); + x = initMatch(cx, &gData, re); + if (!x) + return JS_FALSE; + x->cp = cp; + + /* + * Call the recursive matcher to do the real work. Return null on mismatch + * whether testing or not. On match, return an extended Array object. + */ + result = MatchRegExp(&gData, x); + if (!(ok = gData.ok)) goto out; + if (!result) { + *rval = JSVAL_NULL; + goto out; + } + cp = result->cp; + i = PTRDIFF(cp, gData.cpbegin, jschar); + *indexp = i; + matchlen = i - (start + gData.skipped); + ep = cp; + cp -= matchlen; + + if (test) { + /* + * Testing for a match and updating cx->regExpStatics: don't allocate + * an array object, do return true. + */ + *rval = JSVAL_TRUE; + + /* Avoid warning. (gcc doesn't detect that obj is needed iff !test); */ + obj = NULL; + } else { + /* + * The array returned on match has element 0 bound to the matched + * string, elements 1 through state.parenCount bound to the paren + * matches, an index property telling the length of the left context, + * and an input property referring to the input string. + */ + obj = js_NewArrayObject(cx, 0, NULL); + if (!obj) { + ok = JS_FALSE; + goto out; + } + *rval = OBJECT_TO_JSVAL(obj); + +#define DEFVAL(val, id) { \ + ok = js_DefineProperty(cx, obj, id, val, \ + JS_PropertyStub, JS_PropertyStub, \ + JSPROP_ENUMERATE, NULL); \ + if (!ok) { \ + cx->newborn[GCX_OBJECT] = NULL; \ + cx->newborn[GCX_STRING] = NULL; \ + goto out; \ + } \ +} + + matchstr = js_NewStringCopyN(cx, cp, matchlen, 0); + if (!matchstr) { + cx->newborn[GCX_OBJECT] = NULL; + ok = JS_FALSE; + goto out; + } + DEFVAL(STRING_TO_JSVAL(matchstr), INT_TO_JSVAL(0)); + } + + res = &cx->regExpStatics; + res->input = str; + res->parenCount = re->parenCount; + if (re->parenCount == 0) + res->lastParen = js_EmptySubString; + else { + for (num = 0; num < re->parenCount; num++) { + parsub = &result->parens[num]; + if (num < 9) { + if (parsub->index == -1) { + res->parens[num].chars = NULL; + res->parens[num].length = 0; + } + else { + res->parens[num].chars = gData.cpbegin + parsub->index; + res->parens[num].length = parsub->length; + } + } else { + morenum = num - 9; + morepar = res->moreParens; + if (!morepar) { + res->moreLength = 10; + morepar = (JSSubString*) JS_malloc(cx, + 10 * sizeof(JSSubString)); + } else if (morenum >= res->moreLength) { + res->moreLength += 10; + morepar = (JSSubString*) JS_realloc(cx, morepar, + res->moreLength * sizeof(JSSubString)); + } + if (!morepar) { + cx->newborn[GCX_OBJECT] = NULL; + cx->newborn[GCX_STRING] = NULL; + ok = JS_FALSE; + goto out; + } + res->moreParens = morepar; + if (parsub->index == -1) { + morepar[morenum].chars = NULL; + morepar[morenum].length = 0; + } + else { + morepar[morenum].chars = gData.cpbegin + parsub->index; + morepar[morenum].length = parsub->length; + } + } + if (test) + continue; + if (parsub->index == -1) + ok = js_DefineProperty(cx, obj, INT_TO_JSVAL(num + 1), + JSVAL_VOID, NULL, NULL, + JSPROP_ENUMERATE, NULL); + else { + parstr = js_NewStringCopyN(cx, gData.cpbegin + parsub->index, + parsub->length, 0); + if (!parstr) { + cx->newborn[GCX_OBJECT] = NULL; + cx->newborn[GCX_STRING] = NULL; + ok = JS_FALSE; + goto out; + } + ok = js_DefineProperty(cx, obj, INT_TO_JSVAL(num + 1), + STRING_TO_JSVAL(parstr), NULL, NULL, + JSPROP_ENUMERATE, NULL); + } + if (!ok) { + cx->newborn[GCX_OBJECT] = NULL; + cx->newborn[GCX_STRING] = NULL; + goto out; + } + } + if (parsub->index == -1) { + res->lastParen.chars = NULL; + res->lastParen.length = 0; + } + else { + res->lastParen.chars = gData.cpbegin + parsub->index; + res->lastParen.length = parsub->length; + } + } + + if (!test) { + /* + * Define the index and input properties last for better for/in loop + * order (so they come after the elements). + */ + DEFVAL(INT_TO_JSVAL(start + gData.skipped), + (jsid)cx->runtime->atomState.indexAtom); + DEFVAL(STRING_TO_JSVAL(str), + (jsid)cx->runtime->atomState.inputAtom); + } + +#undef DEFVAL + + res->lastMatch.chars = cp; + res->lastMatch.length = matchlen; + if (cx->version == JSVERSION_1_2) { + /* + * JS1.2 emulated Perl4.0.1.8 (patch level 36) for global regexps used + * in scalar contexts, and unintentionally for the string.match "list" + * psuedo-context. On "hi there bye", the following would result: + * + * Language while(/ /g){print("$`");} s/ /$`/g + * perl4.036 "hi", "there" "hihitherehi therebye" + * perl5 "hi", "hi there" "hihitherehi therebye" + * js1.2 "hi", "there" "hihitheretherebye" + */ + res->leftContext.chars = JSSTRING_CHARS(str) + start; + res->leftContext.length = gData.skipped; + } else { + /* + * For JS1.3 and ECMAv2, emulate Perl5 exactly: + * + * js1.3 "hi", "hi there" "hihitherehi therebye" + */ + res->leftContext.chars = JSSTRING_CHARS(str); + res->leftContext.length = start + gData.skipped; + } + res->rightContext.chars = ep; + res->rightContext.length = gData.cpend - ep; + +out: + JS_FinishArenaPool(&gData.pool); + return ok; +} + +/************************************************************************/ + +enum regexp_tinyid { + REGEXP_SOURCE = -1, + REGEXP_GLOBAL = -2, + REGEXP_IGNORE_CASE = -3, + REGEXP_LAST_INDEX = -4, + REGEXP_MULTILINE = -5 +}; + +#define REGEXP_PROP_ATTRS (JSPROP_PERMANENT|JSPROP_SHARED) + +static JSPropertySpec regexp_props[] = { + {"source", REGEXP_SOURCE, REGEXP_PROP_ATTRS | JSPROP_READONLY,0,0}, + {"global", REGEXP_GLOBAL, REGEXP_PROP_ATTRS | JSPROP_READONLY,0,0}, + {"ignoreCase", REGEXP_IGNORE_CASE, REGEXP_PROP_ATTRS | JSPROP_READONLY,0,0}, + {"lastIndex", REGEXP_LAST_INDEX, REGEXP_PROP_ATTRS,0,0}, + {"multiline", REGEXP_MULTILINE, REGEXP_PROP_ATTRS | JSPROP_READONLY,0,0}, + {0,0,0,0,0} +}; + +static JSBool +regexp_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + jsint slot; + JSRegExp *re; + + if (!JSVAL_IS_INT(id)) + return JS_TRUE; + slot = JSVAL_TO_INT(id); + if (slot == REGEXP_LAST_INDEX) + return JS_GetReservedSlot(cx, obj, 0, vp); + + JS_LOCK_OBJ(cx, obj); + re = (JSRegExp *) JS_GetInstancePrivate(cx, obj, &js_RegExpClass, NULL); + if (re) { + switch (slot) { + case REGEXP_SOURCE: + *vp = STRING_TO_JSVAL(re->source); + break; + case REGEXP_GLOBAL: + *vp = BOOLEAN_TO_JSVAL((re->flags & JSREG_GLOB) != 0); + break; + case REGEXP_IGNORE_CASE: + *vp = BOOLEAN_TO_JSVAL((re->flags & JSREG_FOLD) != 0); + break; + case REGEXP_MULTILINE: + *vp = BOOLEAN_TO_JSVAL((re->flags & JSREG_MULTILINE) != 0); + break; + } + } + JS_UNLOCK_OBJ(cx, obj); + return JS_TRUE; +} + +static JSBool +regexp_setProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + JSBool ok; + jsint slot; + jsdouble lastIndex; + + ok = JS_TRUE; + if (!JSVAL_IS_INT(id)) + return ok; + slot = JSVAL_TO_INT(id); + if (slot == REGEXP_LAST_INDEX) { + if (!js_ValueToNumber(cx, *vp, &lastIndex)) + return JS_FALSE; + lastIndex = js_DoubleToInteger(lastIndex); + ok = js_NewNumberValue(cx, lastIndex, vp) && + JS_SetReservedSlot(cx, obj, 0, *vp); + } + return ok; +} + +/* + * RegExp class static properties and their Perl counterparts: + * + * RegExp.input $_ + * RegExp.multiline $* + * RegExp.lastMatch $& + * RegExp.lastParen $+ + * RegExp.leftContext $` + * RegExp.rightContext $' + */ +enum regexp_static_tinyid { + REGEXP_STATIC_INPUT = -1, + REGEXP_STATIC_MULTILINE = -2, + REGEXP_STATIC_LAST_MATCH = -3, + REGEXP_STATIC_LAST_PAREN = -4, + REGEXP_STATIC_LEFT_CONTEXT = -5, + REGEXP_STATIC_RIGHT_CONTEXT = -6 +}; + +JSBool +js_InitRegExpStatics(JSContext *cx, JSRegExpStatics *res) +{ + JS_ClearRegExpStatics(cx); + return js_AddRoot(cx, &res->input, "res->input"); +} + +void +js_FreeRegExpStatics(JSContext *cx, JSRegExpStatics *res) +{ + if (res->moreParens) { + JS_free(cx, res->moreParens); + res->moreParens = NULL; + } + js_RemoveRoot(cx->runtime, &res->input); +} + +static JSBool +regexp_static_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + jsint slot; + JSRegExpStatics *res; + JSString *str; + JSSubString *sub; + + res = &cx->regExpStatics; + if (!JSVAL_IS_INT(id)) + return JS_TRUE; + slot = JSVAL_TO_INT(id); + switch (slot) { + case REGEXP_STATIC_INPUT: + *vp = res->input ? STRING_TO_JSVAL(res->input) + : JS_GetEmptyStringValue(cx); + return JS_TRUE; + case REGEXP_STATIC_MULTILINE: + *vp = BOOLEAN_TO_JSVAL(res->multiline); + return JS_TRUE; + case REGEXP_STATIC_LAST_MATCH: + sub = &res->lastMatch; + break; + case REGEXP_STATIC_LAST_PAREN: + sub = &res->lastParen; + break; + case REGEXP_STATIC_LEFT_CONTEXT: + sub = &res->leftContext; + break; + case REGEXP_STATIC_RIGHT_CONTEXT: + sub = &res->rightContext; + break; + default: + sub = REGEXP_PAREN_SUBSTRING(res, slot); + break; + } + str = js_NewStringCopyN(cx, sub->chars, sub->length, 0); + if (!str) + return JS_FALSE; + *vp = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +static JSBool +regexp_static_setProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + JSRegExpStatics *res; + + if (!JSVAL_IS_INT(id)) + return JS_TRUE; + res = &cx->regExpStatics; + /* XXX use if-else rather than switch to keep MSVC1.52 from crashing */ + if (JSVAL_TO_INT(id) == REGEXP_STATIC_INPUT) { + if (!JSVAL_IS_STRING(*vp) && + !JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp)) { + return JS_FALSE; + } + res->input = JSVAL_TO_STRING(*vp); + } else if (JSVAL_TO_INT(id) == REGEXP_STATIC_MULTILINE) { + if (!JSVAL_IS_BOOLEAN(*vp) && + !JS_ConvertValue(cx, *vp, JSTYPE_BOOLEAN, vp)) { + return JS_FALSE; + } + res->multiline = JSVAL_TO_BOOLEAN(*vp); + } + return JS_TRUE; +} + +static JSPropertySpec regexp_static_props[] = { + {"input", + REGEXP_STATIC_INPUT, + JSPROP_ENUMERATE|JSPROP_SHARED, + regexp_static_getProperty, regexp_static_setProperty}, + {"multiline", + REGEXP_STATIC_MULTILINE, + JSPROP_ENUMERATE|JSPROP_SHARED, + regexp_static_getProperty, regexp_static_setProperty}, + {"lastMatch", + REGEXP_STATIC_LAST_MATCH, + JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, + regexp_static_getProperty, regexp_static_getProperty}, + {"lastParen", + REGEXP_STATIC_LAST_PAREN, + JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, + regexp_static_getProperty, regexp_static_getProperty}, + {"leftContext", + REGEXP_STATIC_LEFT_CONTEXT, + JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, + regexp_static_getProperty, regexp_static_getProperty}, + {"rightContext", + REGEXP_STATIC_RIGHT_CONTEXT, + JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, + regexp_static_getProperty, regexp_static_getProperty}, + + /* XXX should have block scope and local $1, etc. */ + {"$1", 0, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, + regexp_static_getProperty, regexp_static_getProperty}, + {"$2", 1, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, + regexp_static_getProperty, regexp_static_getProperty}, + {"$3", 2, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, + regexp_static_getProperty, regexp_static_getProperty}, + {"$4", 3, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, + regexp_static_getProperty, regexp_static_getProperty}, + {"$5", 4, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, + regexp_static_getProperty, regexp_static_getProperty}, + {"$6", 5, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, + regexp_static_getProperty, regexp_static_getProperty}, + {"$7", 6, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, + regexp_static_getProperty, regexp_static_getProperty}, + {"$8", 7, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, + regexp_static_getProperty, regexp_static_getProperty}, + {"$9", 8, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, + regexp_static_getProperty, regexp_static_getProperty}, + + {0,0,0,0,0} +}; + +static void +regexp_finalize(JSContext *cx, JSObject *obj) +{ + JSRegExp *re; + + re = (JSRegExp *) JS_GetPrivate(cx, obj); + if (!re) + return; + js_DestroyRegExp(cx, re); +} + +/* Forward static prototype. */ +static JSBool +regexp_exec(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval); + +static JSBool +regexp_call(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + return regexp_exec(cx, JSVAL_TO_OBJECT(argv[-2]), argc, argv, rval); +} + +#if JS_HAS_XDR + +#include "jsxdrapi.h" + +static JSBool +regexp_xdrObject(JSXDRState *xdr, JSObject **objp) +{ + JSRegExp *re; + JSString *source; + uint8 flags; + + if (xdr->mode == JSXDR_ENCODE) { + re = (JSRegExp *) JS_GetPrivate(xdr->cx, *objp); + if (!re) + return JS_FALSE; + source = re->source; + flags = (uint8) re->flags; + } + if (!JS_XDRString(xdr, &source) || + !JS_XDRUint8(xdr, &flags)) { + return JS_FALSE; + } + if (xdr->mode == JSXDR_DECODE) { + *objp = js_NewObject(xdr->cx, &js_RegExpClass, NULL, NULL); + if (!*objp) + return JS_FALSE; + re = js_NewRegExp(xdr->cx, NULL, source, flags, JS_FALSE); + if (!re) + return JS_FALSE; + if (!JS_SetPrivate(xdr->cx, *objp, re) || + !js_SetLastIndex(xdr->cx, *objp, 0)) { + js_DestroyRegExp(xdr->cx, re); + return JS_FALSE; + } + } + return JS_TRUE; +} + +#else /* !JS_HAS_XDR */ + +#define regexp_xdrObject NULL + +#endif /* !JS_HAS_XDR */ + +static uint32 +regexp_mark(JSContext *cx, JSObject *obj, void *arg) +{ + JSRegExp *re = (JSRegExp *) JS_GetPrivate(cx, obj); + if (re) + JS_MarkGCThing(cx, re->source, "source", arg); + return 0; +} + +JSClass js_RegExpClass = { + js_RegExp_str, + JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(1), + JS_PropertyStub, JS_PropertyStub, regexp_getProperty, regexp_setProperty, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, regexp_finalize, + NULL, NULL, regexp_call, NULL, + regexp_xdrObject, NULL, regexp_mark, 0 +}; + +static const jschar empty_regexp_ucstr[] = {'(', '?', ':', ')', 0}; + +static JSBool +regexp_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSRegExp *re; + const jschar *source; + jschar *chars; + size_t length, nflags; + uintN flags; + JSString *str; + + if (!JS_InstanceOf(cx, obj, &js_RegExpClass, argv)) + return JS_FALSE; + JS_LOCK_OBJ(cx, obj); + re = (JSRegExp *) JS_GetPrivate(cx, obj); + if (!re) { + JS_UNLOCK_OBJ(cx, obj); + *rval = STRING_TO_JSVAL(cx->runtime->emptyString); + return JS_TRUE; + } + + source = JSSTRING_CHARS(re->source); + length = JSSTRING_LENGTH(re->source); + if (length == 0) { + source = empty_regexp_ucstr; + length = sizeof(empty_regexp_ucstr) / sizeof(jschar) - 1; + } + length += 2; + nflags = 0; + for (flags = re->flags; flags != 0; flags &= flags - 1) + nflags++; + chars = (jschar*) JS_malloc(cx, (length + nflags + 1) * sizeof(jschar)); + if (!chars) { + JS_UNLOCK_OBJ(cx, obj); + return JS_FALSE; + } + + chars[0] = '/'; + js_strncpy(&chars[1], source, length - 2); + chars[length-1] = '/'; + if (nflags) { + if (re->flags & JSREG_GLOB) + chars[length++] = 'g'; + if (re->flags & JSREG_FOLD) + chars[length++] = 'i'; + if (re->flags & JSREG_MULTILINE) + chars[length++] = 'm'; + } + JS_UNLOCK_OBJ(cx, obj); + chars[length] = 0; + + str = js_NewString(cx, chars, length, 0); + if (!str) { + JS_free(cx, chars); + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +static JSBool +regexp_compile(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSString *opt, *str; + JSRegExp *oldre, *re; + JSBool ok; + JSObject *obj2; + + if (!JS_InstanceOf(cx, obj, &js_RegExpClass, argv)) + return JS_FALSE; + opt = NULL; + if (argc == 0) { + str = cx->runtime->emptyString; + } else { + if (JSVAL_IS_OBJECT(argv[0])) { + /* + * If we get passed in a RegExp object we construct a new + * RegExp that is a duplicate of it by re-compiling the + * original source code. ECMA requires that it be an error + * here if the flags are specified. (We must use the flags + * from the original RegExp also). + */ + obj2 = JSVAL_TO_OBJECT(argv[0]); + if (obj2 && OBJ_GET_CLASS(cx, obj2) == &js_RegExpClass) { + if (argc >= 2 && !JSVAL_IS_VOID(argv[1])) { /* 'flags' passed */ + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_NEWREGEXP_FLAGGED); + return JS_FALSE; + } + JS_LOCK_OBJ(cx, obj2); + re = (JSRegExp *) JS_GetPrivate(cx, obj2); + if (!re) { + JS_UNLOCK_OBJ(cx, obj2); + return JS_FALSE; + } + re = js_NewRegExp(cx, NULL, re->source, re->flags, JS_FALSE); + JS_UNLOCK_OBJ(cx, obj2); + goto madeit; + } + } + str = js_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + argv[0] = STRING_TO_JSVAL(str); + if (argc > 1) { + if (JSVAL_IS_VOID(argv[1])) { + opt = NULL; + } else { + opt = js_ValueToString(cx, argv[1]); + if (!opt) + return JS_FALSE; + argv[1] = STRING_TO_JSVAL(opt); + } + } + } + re = js_NewRegExpOpt(cx, NULL, str, opt, JS_FALSE); +madeit: + if (!re) + return JS_FALSE; + JS_LOCK_OBJ(cx, obj); + oldre = (JSRegExp *) JS_GetPrivate(cx, obj); + ok = JS_SetPrivate(cx, obj, re) && js_SetLastIndex(cx, obj, 0); + JS_UNLOCK_OBJ(cx, obj); + if (!ok) { + js_DestroyRegExp(cx, re); + } else { + if (oldre) + js_DestroyRegExp(cx, oldre); + *rval = OBJECT_TO_JSVAL(obj); + } + return ok; +} + +static JSBool +regexp_exec_sub(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + JSBool test, jsval *rval) +{ + JSBool ok; + JSRegExp *re; + jsdouble lastIndex; + JSString *str; + size_t i; + + ok = JS_InstanceOf(cx, obj, &js_RegExpClass, argv); + if (!ok) + return JS_FALSE; + JS_LOCK_OBJ(cx, obj); + re = (JSRegExp *) JS_GetPrivate(cx, obj); + if (!re) { + JS_UNLOCK_OBJ(cx, obj); + return JS_TRUE; + } + + /* NB: we must reach out: after this paragraph, in order to drop re. */ + HOLD_REGEXP(cx, re); + if (re->flags & JSREG_GLOB) { + ok = js_GetLastIndex(cx, obj, &lastIndex); + } else { + lastIndex = 0; + } + JS_UNLOCK_OBJ(cx, obj); + if (!ok) + goto out; + + /* Now that obj is unlocked, it's safe to (potentially) grab the GC lock. */ + if (argc == 0) { + str = cx->regExpStatics.input; + if (!str) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_NO_INPUT, + JS_GetStringBytes(re->source), + (re->flags & JSREG_GLOB) ? "g" : "", + (re->flags & JSREG_FOLD) ? "i" : "", + (re->flags & JSREG_MULTILINE) ? "m" : ""); + ok = JS_FALSE; + goto out; + } + } else { + str = js_ValueToString(cx, argv[0]); + if (!str) + goto out; + argv[0] = STRING_TO_JSVAL(str); + } + + if (lastIndex < 0 || JSSTRING_LENGTH(str) < lastIndex) { + ok = js_SetLastIndex(cx, obj, 0); + *rval = JSVAL_NULL; + } else { + i = (size_t) lastIndex; + ok = js_ExecuteRegExp(cx, re, str, &i, test, rval); + if (ok && (re->flags & JSREG_GLOB)) + ok = js_SetLastIndex(cx, obj, (*rval == JSVAL_NULL) ? 0 : i); + } + +out: + DROP_REGEXP(cx, re); + return ok; +} + +static JSBool +regexp_exec(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + return regexp_exec_sub(cx, obj, argc, argv, JS_FALSE, rval); +} + +static JSBool +regexp_test(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + if (!regexp_exec_sub(cx, obj, argc, argv, JS_TRUE, rval)) + return JS_FALSE; + if (*rval != JSVAL_TRUE) + *rval = JSVAL_FALSE; + return JS_TRUE; +} + +static JSFunctionSpec regexp_methods[] = { +#if JS_HAS_TOSOURCE + {js_toSource_str, regexp_toString, 0,0,0}, +#endif + {js_toString_str, regexp_toString, 0,0,0}, + {"compile", regexp_compile, 1,0,0}, + {"exec", regexp_exec, 0,0,0}, + {"test", regexp_test, 0,0,0}, + {0,0,0,0,0} +}; + +static JSBool +RegExp(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + if (!(cx->fp->flags & JSFRAME_CONSTRUCTING)) { + /* + * If first arg is regexp and no flags are given, just return the arg. + * (regexp_compile detects the regexp + flags case and throws a + * TypeError.) See 10.15.3.1. + */ + if ((argc < 2 || JSVAL_IS_VOID(argv[1])) && JSVAL_IS_OBJECT(argv[0]) && + OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(argv[0])) == &js_RegExpClass) { + *rval = argv[0]; + return JS_TRUE; + } + + /* Otherwise, replace obj with a new RegExp object. */ + obj = js_NewObject(cx, &js_RegExpClass, NULL, NULL); + if (!obj) + return JS_FALSE; + } + return regexp_compile(cx, obj, argc, argv, rval); +} + +JSObject * +js_InitRegExpClass(JSContext *cx, JSObject *obj) +{ + JSObject *proto, *ctor; + jsval rval; + + proto = JS_InitClass(cx, obj, NULL, &js_RegExpClass, RegExp, 1, + regexp_props, regexp_methods, + regexp_static_props, NULL); + + if (!proto || !(ctor = JS_GetConstructor(cx, proto))) + return NULL; + if (!JS_AliasProperty(cx, ctor, "input", "$_") || + !JS_AliasProperty(cx, ctor, "multiline", "$*") || + !JS_AliasProperty(cx, ctor, "lastMatch", "$&") || + !JS_AliasProperty(cx, ctor, "lastParen", "$+") || + !JS_AliasProperty(cx, ctor, "leftContext", "$`") || + !JS_AliasProperty(cx, ctor, "rightContext", "$'")) { + goto bad; + } + + /* Give RegExp.prototype private data so it matches the empty string. */ + if (!regexp_compile(cx, proto, 0, NULL, &rval)) + goto bad; + return proto; + +bad: + JS_DeleteProperty(cx, obj, js_RegExpClass.name); + return NULL; +} + +JSObject * +js_NewRegExpObject(JSContext *cx, JSTokenStream *ts, + jschar *chars, size_t length, uintN flags) +{ + JSString *str; + JSObject *obj; + JSRegExp *re; + + str = js_NewStringCopyN(cx, chars, length, 0); + if (!str) + return NULL; + re = js_NewRegExp(cx, ts, str, flags, JS_FALSE); + if (!re) + return NULL; + obj = js_NewObject(cx, &js_RegExpClass, NULL, NULL); + if (!obj || !JS_SetPrivate(cx, obj, re) || !js_SetLastIndex(cx, obj, 0)) { + js_DestroyRegExp(cx, re); + return NULL; + } + return obj; +} + +JSObject * +js_CloneRegExpObject(JSContext *cx, JSObject *obj, JSObject *parent) +{ + JSObject *clone; + JSRegExp *re; + + JS_ASSERT(OBJ_GET_CLASS(cx, obj) == &js_RegExpClass); + clone = js_NewObject(cx, &js_RegExpClass, NULL, parent); + if (!clone) + return NULL; + re = JS_GetPrivate(cx, obj); + if (!JS_SetPrivate(cx, clone, re) || !js_SetLastIndex(cx, clone, 0)) { + cx->newborn[GCX_OBJECT] = NULL; + return NULL; + } + HOLD_REGEXP(cx, re); + return clone; +} + +JSBool +js_GetLastIndex(JSContext *cx, JSObject *obj, jsdouble *lastIndex) +{ + jsval v; + + return JS_GetReservedSlot(cx, obj, 0, &v) && + js_ValueToNumber(cx, v, lastIndex); +} + +JSBool +js_SetLastIndex(JSContext *cx, JSObject *obj, jsdouble lastIndex) +{ + jsval v; + + return js_NewNumberValue(cx, lastIndex, &v) && + JS_SetReservedSlot(cx, obj, 0, v); +} + +#endif /* JS_HAS_REGEXPS */ diff --git a/src/dom/js/jsregexp.h b/src/dom/js/jsregexp.h new file mode 100644 index 000000000..0485c2860 --- /dev/null +++ b/src/dom/js/jsregexp.h @@ -0,0 +1,168 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsregexp_h___ +#define jsregexp_h___ +/* + * JS regular expression interface. + */ +#include +#include "jspubtd.h" +#include "jsstr.h" + +#ifdef JS_THREADSAFE +#include "jsdhash.h" +#endif + +struct JSRegExpStatics { + JSString *input; /* input string to match (perl $_, GC root) */ + JSBool multiline; /* whether input contains newlines (perl $*) */ + uintN parenCount; /* number of valid elements in parens[] */ + uintN moreLength; /* number of allocated elements in moreParens */ + JSSubString parens[9]; /* last set of parens matched (perl $1, $2) */ + JSSubString *moreParens; /* null or realloc'd vector for $10, etc. */ + JSSubString lastMatch; /* last string matched (perl $&) */ + JSSubString lastParen; /* last paren matched (perl $+) */ + JSSubString leftContext; /* input to left of last match (perl $`) */ + JSSubString rightContext; /* input to right of last match (perl $') */ +}; + +/* + * This struct holds a bitmap representation of a class from a regexp. + * There's a list of these referenced by the classList field in the JSRegExp + * struct below. The initial state has startIndex set to the offset in the + * original regexp source of the beginning of the class contents. The first + * use of the class converts the source representation into a bitmap. + * + */ +typedef struct RECharSet { + JSBool converted; + JSBool sense; + uint16 length; + union { + uint8 *bits; + struct { + uint16 startIndex; + uint16 length; + } src; + } u; +} RECharSet; + +/* + * This macro is safe because moreParens is guaranteed to be allocated and big + * enough to hold parenCount, or else be null when parenCount is 0. + */ +#define REGEXP_PAREN_SUBSTRING(res, num) \ + (((jsuint)(num) < (jsuint)(res)->parenCount) \ + ? ((jsuint)(num) < 9) \ + ? &(res)->parens[num] \ + : &(res)->moreParens[(num) - 9] \ + : &js_EmptySubString) + +typedef struct RENode RENode; + +struct JSRegExp { + jsrefcount nrefs; /* reference count */ + uint32 parenCount:24, /* number of parenthesized submatches */ + flags:8; /* flags, see jsapi.h's JSREG_* defines */ + uint32 classCount; /* count [...] bitmaps */ + RECharSet *classList; /* list of [...] bitmaps */ + JSString *source; /* locked source string, sans // */ + jsbytecode program[1]; /* regular expression bytecode */ +}; + +extern JSRegExp * +js_NewRegExp(JSContext *cx, JSTokenStream *ts, + JSString *str, uintN flags, JSBool flat); + +extern JSRegExp * +js_NewRegExpOpt(JSContext *cx, JSTokenStream *ts, + JSString *str, JSString *opt, JSBool flat); + +extern void +js_DestroyRegExp(JSContext *cx, JSRegExp *re); + +/* + * Execute re on input str at *indexp, returning null in *rval on mismatch. + * On match, return true if test is true, otherwise return an array object. + * Update *indexp and cx->regExpStatics always on match. + */ +extern JSBool +js_ExecuteRegExp(JSContext *cx, JSRegExp *re, JSString *str, size_t *indexp, + JSBool test, jsval *rval); + +/* + * These two add and remove GC roots, respectively, so their calls must be + * well-ordered. + */ +extern JSBool +js_InitRegExpStatics(JSContext *cx, JSRegExpStatics *res); + +extern void +js_FreeRegExpStatics(JSContext *cx, JSRegExpStatics *res); + +#define JSVAL_IS_REGEXP(cx, v) \ + (JSVAL_IS_OBJECT(v) && JSVAL_TO_OBJECT(v) && \ + OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(v)) == &js_RegExpClass) + +extern JSClass js_RegExpClass; + +extern JSObject * +js_InitRegExpClass(JSContext *cx, JSObject *obj); + +/* + * Create a new RegExp object. + */ +extern JSObject * +js_NewRegExpObject(JSContext *cx, JSTokenStream *ts, + jschar *chars, size_t length, uintN flags); + +extern JSBool +js_XDRRegExp(JSXDRState *xdr, JSObject **objp); + +extern JSObject * +js_CloneRegExpObject(JSContext *cx, JSObject *obj, JSObject *parent); + +extern JSBool +js_GetLastIndex(JSContext *cx, JSObject *obj, jsdouble *lastIndex); + +extern JSBool +js_SetLastIndex(JSContext *cx, JSObject *obj, jsdouble lastIndex); + +#endif /* jsregexp_h___ */ diff --git a/src/dom/js/jsscan.c b/src/dom/js/jsscan.c new file mode 100644 index 000000000..e0bc5cd92 --- /dev/null +++ b/src/dom/js/jsscan.c @@ -0,0 +1,1315 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS lexical scanner. + */ +#include "jsstddef.h" +#include /* first to avoid trouble on some systems */ +#include +#include +#include +#ifdef HAVE_MEMORY_H +#include +#endif +#include +#include +#include +#include "jstypes.h" +#include "jsarena.h" /* Added by JSIFY */ +#include "jsutil.h" /* Added by JSIFY */ +#include "jsdtoa.h" +#include "jsprf.h" +#include "jsapi.h" +#include "jsatom.h" +#include "jscntxt.h" +#include "jsconfig.h" +#include "jsemit.h" +#include "jsexn.h" +#include "jsnum.h" +#include "jsopcode.h" +#include "jsregexp.h" +#include "jsscan.h" + +#define RESERVE_JAVA_KEYWORDS +#define RESERVE_ECMA_KEYWORDS + +static struct keyword { + const char *name; + JSTokenType tokentype; /* JSTokenType */ + JSOp op; /* JSOp */ + JSVersion version; /* JSVersion */ +} keywords[] = { + {"break", TOK_BREAK, JSOP_NOP, JSVERSION_DEFAULT}, + {"case", TOK_CASE, JSOP_NOP, JSVERSION_DEFAULT}, + {"continue", TOK_CONTINUE, JSOP_NOP, JSVERSION_DEFAULT}, + {"default", TOK_DEFAULT, JSOP_NOP, JSVERSION_DEFAULT}, + {js_delete_str, TOK_DELETE, JSOP_NOP, JSVERSION_DEFAULT}, + {"do", TOK_DO, JSOP_NOP, JSVERSION_DEFAULT}, + {"else", TOK_ELSE, JSOP_NOP, JSVERSION_DEFAULT}, + {"export", TOK_EXPORT, JSOP_NOP, JSVERSION_1_2}, + {js_false_str, TOK_PRIMARY, JSOP_FALSE, JSVERSION_DEFAULT}, + {"for", TOK_FOR, JSOP_NOP, JSVERSION_DEFAULT}, + {js_function_str, TOK_FUNCTION, JSOP_NOP, JSVERSION_DEFAULT}, + {"if", TOK_IF, JSOP_NOP, JSVERSION_DEFAULT}, + {js_in_str, TOK_IN, JSOP_IN, JSVERSION_DEFAULT}, + {js_new_str, TOK_NEW, JSOP_NEW, JSVERSION_DEFAULT}, + {js_null_str, TOK_PRIMARY, JSOP_NULL, JSVERSION_DEFAULT}, + {"return", TOK_RETURN, JSOP_NOP, JSVERSION_DEFAULT}, + {"switch", TOK_SWITCH, JSOP_NOP, JSVERSION_DEFAULT}, + {js_this_str, TOK_PRIMARY, JSOP_THIS, JSVERSION_DEFAULT}, + {js_true_str, TOK_PRIMARY, JSOP_TRUE, JSVERSION_DEFAULT}, + {js_typeof_str, TOK_UNARYOP, JSOP_TYPEOF,JSVERSION_DEFAULT}, + {"var", TOK_VAR, JSOP_DEFVAR,JSVERSION_DEFAULT}, + {js_void_str, TOK_UNARYOP, JSOP_VOID, JSVERSION_DEFAULT}, + {"while", TOK_WHILE, JSOP_NOP, JSVERSION_DEFAULT}, + {"with", TOK_WITH, JSOP_NOP, JSVERSION_DEFAULT}, + +#if JS_HAS_CONST + {js_const_str, TOK_VAR, JSOP_DEFCONST,JSVERSION_DEFAULT}, +#else + {js_const_str, TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, +#endif + +#if JS_HAS_EXCEPTIONS + {"try", TOK_TRY, JSOP_NOP, JSVERSION_DEFAULT}, + {"catch", TOK_CATCH, JSOP_NOP, JSVERSION_DEFAULT}, + {"finally", TOK_FINALLY, JSOP_NOP, JSVERSION_DEFAULT}, + {"throw", TOK_THROW, JSOP_NOP, JSVERSION_DEFAULT}, +#else + {"try", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"catch", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"finally", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"throw", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, +#endif + +#if JS_HAS_INSTANCEOF + {js_instanceof_str, TOK_INSTANCEOF, JSOP_INSTANCEOF,JSVERSION_1_4}, +#else + {js_instanceof_str, TOK_RESERVED, JSOP_NOP, JSVERSION_1_4}, +#endif + +#ifdef RESERVE_JAVA_KEYWORDS + {"abstract", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"boolean", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"byte", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"char", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"class", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"double", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"extends", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"final", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"float", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"goto", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"implements", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"import", TOK_IMPORT, JSOP_NOP, JSVERSION_DEFAULT}, + {"int", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"interface", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"long", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"native", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"package", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"private", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"protected", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"public", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"short", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"static", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"super", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"synchronized", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"throws", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"transient", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"volatile", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, +#endif + +#ifdef RESERVE_ECMA_KEYWORDS + {"enum", TOK_RESERVED, JSOP_NOP, JSVERSION_1_3}, +#endif + +#if JS_HAS_DEBUGGER_KEYWORD + {"debugger", TOK_DEBUGGER, JSOP_NOP, JSVERSION_1_3}, +#elif defined(RESERVE_ECMA_KEYWORDS) + {"debugger", TOK_RESERVED, JSOP_NOP, JSVERSION_1_3}, +#endif + {0, TOK_EOF, JSOP_NOP, JSVERSION_DEFAULT} +}; + +JSBool +js_InitScanner(JSContext *cx) +{ + struct keyword *kw; + JSAtom *atom; + + for (kw = keywords; kw->name; kw++) { + atom = js_Atomize(cx, kw->name, strlen(kw->name), ATOM_PINNED); + if (!atom) + return JS_FALSE; + ATOM_SET_KEYWORD(atom, kw); + } + return JS_TRUE; +} + +JS_FRIEND_API(void) +js_MapKeywords(void (*mapfun)(const char *)) +{ + struct keyword *kw; + + for (kw = keywords; kw->name; kw++) + mapfun(kw->name); +} + +JSTokenStream * +js_NewTokenStream(JSContext *cx, const jschar *base, size_t length, + const char *filename, uintN lineno, + JSPrincipals *principals) +{ + JSTokenStream *ts; + + ts = js_NewBufferTokenStream(cx, base, length); + if (!ts) + return NULL; + ts->filename = filename; + ts->lineno = lineno; + if (principals) + JSPRINCIPALS_HOLD(cx, principals); + ts->principals = principals; + return ts; +} + +JS_FRIEND_API(JSTokenStream *) +js_NewBufferTokenStream(JSContext *cx, const jschar *base, size_t length) +{ + size_t nb; + JSTokenStream *ts; + + nb = sizeof(JSTokenStream) + JS_LINE_LIMIT * sizeof(jschar); + JS_ARENA_ALLOCATE_CAST(ts, JSTokenStream *, &cx->tempPool, nb); + if (!ts) { + JS_ReportOutOfMemory(cx); + return NULL; + } + memset(ts, 0, nb); + ts->lineno = 1; + ts->linebuf.base = ts->linebuf.limit = ts->linebuf.ptr = (jschar *)(ts + 1); + ts->userbuf.base = (jschar *)base; + ts->userbuf.limit = (jschar *)base + length; + ts->userbuf.ptr = (jschar *)base; + ts->listener = cx->runtime->sourceHandler; + ts->listenerData = cx->runtime->sourceHandlerData; + return ts; +} + +JS_FRIEND_API(JSTokenStream *) +js_NewFileTokenStream(JSContext *cx, const char *filename, FILE *defaultfp) +{ + jschar *base; + JSTokenStream *ts; + FILE *file; + + JS_ARENA_ALLOCATE_CAST(base, jschar *, &cx->tempPool, + JS_LINE_LIMIT * sizeof(jschar)); + if (!base) + return NULL; + ts = js_NewBufferTokenStream(cx, base, JS_LINE_LIMIT); + if (!ts) + return NULL; + if (!filename || strcmp(filename, "-") == 0) { + file = defaultfp; + } else { + file = fopen(filename, "r"); + if (!file) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_OPEN, + filename, "No such file or directory"); + return NULL; + } + } + ts->userbuf.ptr = ts->userbuf.limit; + ts->file = file; + ts->filename = filename; + return ts; +} + +JS_FRIEND_API(JSBool) +js_CloseTokenStream(JSContext *cx, JSTokenStream *ts) +{ + if (ts->principals) + JSPRINCIPALS_DROP(cx, ts->principals); + return !ts->file || fclose(ts->file) == 0; +} + +static int +my_fgets(char *buf, int size, FILE *file) +{ + int n, i, c; + JSBool crflag; + + n = size - 1; + if (n < 0) + return -1; + + crflag = JS_FALSE; + for (i = 0; i < n && (c = getc(file)) != EOF; i++) { + buf[i] = c; + if (c == '\n') { /* any \n ends a line */ + i++; /* keep the \n; we know there is room for \0 */ + break; + } + if (crflag) { /* \r not followed by \n ends line at the \r */ + ungetc(c, file); + break; /* and overwrite c in buf with \0 */ + } + crflag = (c == '\r'); + } + + buf[i] = '\0'; + return i; +} + +static int32 +GetChar(JSTokenStream *ts) +{ + int32 c; + ptrdiff_t i, j, len, olen; + JSBool crflag; + char cbuf[JS_LINE_LIMIT]; + jschar *ubuf, *nl; + + if (ts->ungetpos != 0) { + c = ts->ungetbuf[--ts->ungetpos]; + } else { + do { + if (ts->linebuf.ptr == ts->linebuf.limit) { + len = PTRDIFF(ts->userbuf.limit, ts->userbuf.ptr, jschar); + if (len <= 0) { + if (!ts->file) { + ts->flags |= TSF_EOF; + return EOF; + } + + /* Fill ts->userbuf so that \r and \r\n convert to \n. */ + crflag = (ts->flags & TSF_CRFLAG) != 0; + len = my_fgets(cbuf, JS_LINE_LIMIT - crflag, ts->file); + if (len <= 0) { + ts->flags |= TSF_EOF; + return EOF; + } + olen = len; + ubuf = ts->userbuf.base; + i = 0; + if (crflag) { + ts->flags &= ~TSF_CRFLAG; + if (cbuf[0] != '\n') { + ubuf[i++] = '\n'; + len++; + ts->linepos--; + } + } + for (j = 0; i < len; i++, j++) + ubuf[i] = (jschar) (unsigned char) cbuf[j]; + ts->userbuf.limit = ubuf + len; + ts->userbuf.ptr = ubuf; + } + if (ts->listener) { + ts->listener(ts->filename, ts->lineno, ts->userbuf.ptr, len, + &ts->listenerTSData, ts->listenerData); + } + + /* + * Any one of \n, \r, or \r\n ends a line (longest match wins). + * Also allow the Unicode line and paragraph separators. + */ + for (nl = ts->userbuf.ptr; nl < ts->userbuf.limit; nl++) { + /* + * Try to prevent value-testing on most characters by + * filtering out characters that aren't 000x or 202x. + */ + if ((*nl & 0xDFD0) == 0) { + if (*nl == '\n') + break; + if (*nl == '\r') { + if (nl + 1 < ts->userbuf.limit && nl[1] == '\n') + nl++; + break; + } + if (*nl == LINE_SEPARATOR || *nl == PARA_SEPARATOR) + break; + } + } + + /* + * If there was a line terminator, copy thru it into linebuf. + * Else copy JS_LINE_LIMIT-1 bytes into linebuf. + */ + if (nl < ts->userbuf.limit) + len = PTRDIFF(nl, ts->userbuf.ptr, jschar) + 1; + if (len >= JS_LINE_LIMIT) + len = JS_LINE_LIMIT - 1; + js_strncpy(ts->linebuf.base, ts->userbuf.ptr, len); + ts->userbuf.ptr += len; + olen = len; + + /* + * Make sure linebuf contains \n for EOL (don't do this in + * userbuf because the user's string might be readonly). + */ + if (nl < ts->userbuf.limit) { + if (*nl == '\r') { + if (ts->linebuf.base[len-1] == '\r') { + /* + * Does the line segment end in \r? We must check + * for a \n at the front of the next segment before + * storing a \n into linebuf. This case matters + * only when we're reading from a file. + */ + if (nl + 1 == ts->userbuf.limit && ts->file) { + len--; + ts->flags |= TSF_CRFLAG; /* clear NLFLAG? */ + if (len == 0) { + /* + * This can happen when a segment ends in + * \r\r. Start over. ptr == limit in this + * case, so we'll fall into buffer-filling + * code. + */ + return GetChar(ts); + } + } else { + ts->linebuf.base[len-1] = '\n'; + } + } + } else if (*nl == '\n') { + if (nl > ts->userbuf.base && + nl[-1] == '\r' && + ts->linebuf.base[len-2] == '\r') { + len--; + JS_ASSERT(ts->linebuf.base[len] == '\n'); + ts->linebuf.base[len-1] = '\n'; + } + } else if (*nl == LINE_SEPARATOR || *nl == PARA_SEPARATOR) { + ts->linebuf.base[len-1] = '\n'; + } + } + + /* Reset linebuf based on adjusted segment length. */ + ts->linebuf.limit = ts->linebuf.base + len; + ts->linebuf.ptr = ts->linebuf.base; + + /* Update position of linebuf within physical userbuf line. */ + if (!(ts->flags & TSF_NLFLAG)) + ts->linepos += ts->linelen; + else + ts->linepos = 0; + if (ts->linebuf.limit[-1] == '\n') + ts->flags |= TSF_NLFLAG; + else + ts->flags &= ~TSF_NLFLAG; + + /* Update linelen from original segment length. */ + ts->linelen = olen; + } + c = *ts->linebuf.ptr++; + } while (JS_ISFORMAT(c)); + } + if (c == '\n') + ts->lineno++; + return c; +} + +static void +UngetChar(JSTokenStream *ts, int32 c) +{ + if (c == EOF) + return; + JS_ASSERT(ts->ungetpos < sizeof ts->ungetbuf / sizeof ts->ungetbuf[0]); + if (c == '\n') + ts->lineno--; + ts->ungetbuf[ts->ungetpos++] = (jschar)c; +} + +static int32 +PeekChar(JSTokenStream *ts) +{ + int32 c; + + c = GetChar(ts); + UngetChar(ts, c); + return c; +} + +static JSBool +PeekChars(JSTokenStream *ts, intN n, jschar *cp) +{ + intN i, j; + int32 c; + + for (i = 0; i < n; i++) { + c = GetChar(ts); + if (c == EOF) + break; + cp[i] = (jschar)c; + } + for (j = i - 1; j >= 0; j--) + UngetChar(ts, cp[j]); + return i == n; +} + +static void +SkipChars(JSTokenStream *ts, intN n) +{ + while (--n >= 0) + GetChar(ts); +} + +static JSBool +MatchChar(JSTokenStream *ts, int32 expect) +{ + int32 c; + + c = GetChar(ts); + if (c == expect) + return JS_TRUE; + UngetChar(ts, c); + return JS_FALSE; +} + +JSBool +js_ReportCompileErrorNumber(JSContext *cx, JSTokenStream *ts, + JSCodeGenerator *cg, uintN flags, + const uintN errorNumber, ...) +{ + va_list ap; + JSErrorReporter onError; + JSErrorReport report; + jschar *tokenptr; + JSString *linestr = NULL; + char *message; + JSBool warning; + + if ((flags & JSREPORT_STRICT) && !JS_HAS_STRICT_OPTION(cx)) + return JS_TRUE; + + memset(&report, 0, sizeof (struct JSErrorReport)); + report.flags = flags; + report.errorNumber = errorNumber; + message = NULL; + + va_start(ap, errorNumber); + if (!js_ExpandErrorArguments(cx, js_GetErrorMessage, NULL, + errorNumber, &message, &report, &warning, + JS_TRUE, ap)) { + return JS_FALSE; + } + va_end(ap); + + js_AddRoot(cx, &linestr, "error line buffer"); + + JS_ASSERT(!ts || ts->linebuf.limit < ts->linebuf.base + JS_LINE_LIMIT); + onError = cx->errorReporter; + if (onError) { + /* + * We are typically called with non-null ts and null cg from jsparse.c. + * We can be called with null ts from the regexp compilation functions. + * The code generator (jsemit.c) may pass null ts and non-null cg. + */ + if (ts) { + report.filename = ts->filename; + report.lineno = ts->lineno; + linestr = js_NewStringCopyN(cx, ts->linebuf.base, + ts->linebuf.limit - ts->linebuf.base, + 0); + report.linebuf = linestr + ? JS_GetStringBytes(linestr) + : NULL; + tokenptr = + ts->tokens[(ts->cursor + ts->lookahead) & NTOKENS_MASK].ptr; + report.tokenptr = linestr + ? report.linebuf + (tokenptr - ts->linebuf.base) + : NULL; + report.uclinebuf = linestr + ? JS_GetStringChars(linestr) + : NULL; + report.uctokenptr = linestr + ? report.uclinebuf + (tokenptr - ts->linebuf.base) + : NULL; + } else if (cg) { + report.filename = cg->filename; + report.lineno = CG_CURRENT_LINE(cg); + } + +#if JS_HAS_ERROR_EXCEPTIONS + /* + * If there's a runtime exception type associated with this error + * number, set that as the pending exception. For errors occuring at + * compile time, this is very likely to be a JSEXN_SYNTAXERR. + * + * If an exception is thrown but not caught, the JSREPORT_EXCEPTION + * flag will be set in report.flags. Proper behavior for an error + * reporter is to ignore a report with this flag for all but top-level + * compilation errors. The exception will remain pending, and so long + * as the non-top-level "load", "eval", or "compile" native function + * returns false, the top-level reporter will eventually receive the + * uncaught exception report. + * + * XXX it'd probably be best if there was only one call to this + * function, but there seem to be two error reporter call points. + */ + + /* + * Only try to raise an exception if there isn't one already set - + * otherwise the exception will describe only the last compile error, + * which is likely spurious. + */ + if (!(ts && (ts->flags & TSF_ERROR))) + if (js_ErrorToException(cx, message, &report)) + onError = NULL; + + /* + * Suppress any compiletime errors that don't occur at the top level. + * This may still fail, as interplevel may be zero in contexts where we + * don't really want to call the error reporter, as when js is called + * by other code which could catch the error. + */ + if (cx->interpLevel != 0) + onError = NULL; +#endif + if (cx->runtime->debugErrorHook && onError) { + JSDebugErrorHook hook = cx->runtime->debugErrorHook; + /* test local in case debugErrorHook changed on another thread */ + if (hook && !hook(cx, message, &report, + cx->runtime->debugErrorHookData)) { + onError = NULL; + } + } + if (onError) + (*onError)(cx, message, &report); + } + if (message) + JS_free(cx, message); + if (report.messageArgs) { + int i = 0; + while (report.messageArgs[i]) + JS_free(cx, (void *)report.messageArgs[i++]); + JS_free(cx, (void *)report.messageArgs); + } + if (report.ucmessage) + JS_free(cx, (void *)report.ucmessage); + + js_RemoveRoot(cx->runtime, &linestr); + + if (ts && !JSREPORT_IS_WARNING(flags)) { + /* Set the error flag to suppress spurious reports. */ + ts->flags |= TSF_ERROR; + } + return warning; +} + +JSTokenType +js_PeekToken(JSContext *cx, JSTokenStream *ts) +{ + JSTokenType tt; + + if (ts->lookahead != 0) { + tt = ts->tokens[(ts->cursor + ts->lookahead) & NTOKENS_MASK].type; + } else { + tt = js_GetToken(cx, ts); + js_UngetToken(ts); + } + return tt; +} + +JSTokenType +js_PeekTokenSameLine(JSContext *cx, JSTokenStream *ts) +{ + JSTokenType tt; + + JS_ASSERT(ts->lookahead == 0 || + ON_CURRENT_LINE(ts, CURRENT_TOKEN(ts).pos)); + ts->flags |= TSF_NEWLINES; + tt = js_PeekToken(cx, ts); + ts->flags &= ~TSF_NEWLINES; + return tt; +} + +#define TBMIN 64 + +static JSBool +GrowTokenBuf(JSContext *cx, JSTokenBuf *tb) +{ + jschar *base; + ptrdiff_t offset, length; + size_t tbsize; + JSArenaPool *pool; + + base = tb->base; + offset = PTRDIFF(tb->ptr, base, jschar); + pool = &cx->tempPool; + if (!base) { + tbsize = TBMIN * sizeof(jschar); + length = TBMIN; + JS_ARENA_ALLOCATE_CAST(base, jschar *, pool, tbsize); + } else { + length = PTRDIFF(tb->limit, base, jschar); + tbsize = length * sizeof(jschar); + length <<= 1; + JS_ARENA_GROW_CAST(base, jschar *, pool, tbsize, tbsize); + } + if (!base) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + tb->base = base; + tb->limit = base + length; + tb->ptr = base + offset; + return JS_TRUE; +} + +static JSBool +AddToTokenBuf(JSContext *cx, JSTokenBuf *tb, jschar c) +{ + if (tb->ptr == tb->limit && !GrowTokenBuf(cx, tb)) + return JS_FALSE; + *tb->ptr++ = c; + return JS_TRUE; +} + +/* + * We have encountered a '\': check for a Unicode escape sequence after it, + * returning the character code value if we found a Unicode escape sequence. + * Otherwise, non-destructively return the original '\'. + */ +static int32 +GetUnicodeEscape(JSTokenStream *ts) +{ + jschar cp[5]; + int32 c; + + if (PeekChars(ts, 5, cp) && cp[0] == 'u' && + JS7_ISHEX(cp[1]) && JS7_ISHEX(cp[2]) && + JS7_ISHEX(cp[3]) && JS7_ISHEX(cp[4])) + { + c = (((((JS7_UNHEX(cp[1]) << 4) + + JS7_UNHEX(cp[2])) << 4) + + JS7_UNHEX(cp[3])) << 4) + + JS7_UNHEX(cp[4]); + SkipChars(ts, 5); + return c; + } + return '\\'; +} + +JSTokenType +js_GetToken(JSContext *cx, JSTokenStream *ts) +{ + JSTokenType tt; + JSToken *tp; + int32 c; + JSAtom *atom; + JSBool hadUnicodeEscape; + +#define INIT_TOKENBUF(tb) ((tb)->ptr = (tb)->base) +#define FINISH_TOKENBUF(tb) if (!AddToTokenBuf(cx, tb, 0)) RETURN(TOK_ERROR) +#define TOKEN_LENGTH(tb) ((tb)->ptr - (tb)->base - 1) +#define RETURN(tt) { if (tt == TOK_ERROR) ts->flags |= TSF_ERROR; \ + tp->pos.end.index = ts->linepos + \ + (ts->linebuf.ptr - ts->linebuf.base) - \ + ts->ungetpos; \ + return (tp->type = tt); } + + /* If there was a fatal error, keep returning TOK_ERROR. */ + if (ts->flags & TSF_ERROR) + return TOK_ERROR; + + /* Check for a pushed-back token resulting from mismatching lookahead. */ + while (ts->lookahead != 0) { + ts->lookahead--; + ts->cursor = (ts->cursor + 1) & NTOKENS_MASK; + tt = CURRENT_TOKEN(ts).type; + if (tt != TOK_EOL || (ts->flags & TSF_NEWLINES)) + return tt; + } + +retry: + do { + c = GetChar(ts); + if (c == '\n') { + ts->flags &= ~TSF_DIRTYLINE; + if (ts->flags & TSF_NEWLINES) + break; + } + } while (JS_ISSPACE(c)); + + ts->cursor = (ts->cursor + 1) & NTOKENS_MASK; + tp = &CURRENT_TOKEN(ts); + tp->ptr = ts->linebuf.ptr - 1; + tp->pos.begin.index = ts->linepos + (tp->ptr - ts->linebuf.base); + tp->pos.begin.lineno = tp->pos.end.lineno = (uint16)ts->lineno; + + if (c == EOF) + RETURN(TOK_EOF); + if (c != '-' && c != '\n') + ts->flags |= TSF_DIRTYLINE; + + hadUnicodeEscape = JS_FALSE; + if (JS_ISIDENT_START(c) || + (c == '\\' && + (c = GetUnicodeEscape(ts), + hadUnicodeEscape = JS_ISIDENT_START(c)))) { + INIT_TOKENBUF(&ts->tokenbuf); + for (;;) { + if (!AddToTokenBuf(cx, &ts->tokenbuf, (jschar)c)) + RETURN(TOK_ERROR); + c = GetChar(ts); + if (c == '\\') { + c = GetUnicodeEscape(ts); + if (!JS_ISIDENT(c)) + break; + hadUnicodeEscape = JS_TRUE; + } else { + if (!JS_ISIDENT(c)) + break; + } + } + UngetChar(ts, c); + FINISH_TOKENBUF(&ts->tokenbuf); + + atom = js_AtomizeChars(cx, + ts->tokenbuf.base, + TOKEN_LENGTH(&ts->tokenbuf), + 0); + if (!atom) + RETURN(TOK_ERROR); + if (!hadUnicodeEscape && ATOM_KEYWORD(atom)) { + struct keyword *kw = ATOM_KEYWORD(atom); + + if (JSVERSION_IS_ECMA(cx->version) || kw->version <= cx->version) { + tp->t_op = (JSOp) kw->op; + RETURN(kw->tokentype); + } + } + tp->t_op = JSOP_NAME; + tp->t_atom = atom; + RETURN(TOK_NAME); + } + + if (JS7_ISDEC(c) || (c == '.' && JS7_ISDEC(PeekChar(ts)))) { + jsint radix; + const jschar *endptr; + jsdouble dval; + + radix = 10; + INIT_TOKENBUF(&ts->tokenbuf); + + if (c == '0') { + if (!AddToTokenBuf(cx, &ts->tokenbuf, (jschar)c)) + RETURN(TOK_ERROR); + c = GetChar(ts); + if (JS_TOLOWER(c) == 'x') { + if (!AddToTokenBuf(cx, &ts->tokenbuf, (jschar)c)) + RETURN(TOK_ERROR); + c = GetChar(ts); + radix = 16; + } else if (JS7_ISDEC(c)) { + radix = 8; + } + } + + while (JS7_ISHEX(c)) { + if (radix < 16) { + if (JS7_ISLET(c)) + break; + + /* + * We permit 08 and 09 as decimal numbers, which makes our + * behaviour a superset of the ECMA numeric grammar. We might + * not always be so permissive, so we warn about it. + */ + if (radix == 8 && c >= '8') { + if (!js_ReportCompileErrorNumber(cx, ts, NULL, + JSREPORT_WARNING, + JSMSG_BAD_OCTAL, + c == '8' ? "08" : "09")) { + RETURN(TOK_ERROR); + } + radix = 10; + } + } + if (!AddToTokenBuf(cx, &ts->tokenbuf, (jschar)c)) + RETURN(TOK_ERROR); + c = GetChar(ts); + } + + if (radix == 10 && (c == '.' || JS_TOLOWER(c) == 'e')) { + if (c == '.') { + do { + if (!AddToTokenBuf(cx, &ts->tokenbuf, (jschar)c)) + RETURN(TOK_ERROR); + c = GetChar(ts); + } while (JS7_ISDEC(c)); + } + if (JS_TOLOWER(c) == 'e') { + if (!AddToTokenBuf(cx, &ts->tokenbuf, (jschar)c)) + RETURN(TOK_ERROR); + c = GetChar(ts); + if (c == '+' || c == '-') { + if (!AddToTokenBuf(cx, &ts->tokenbuf, (jschar)c)) + RETURN(TOK_ERROR); + c = GetChar(ts); + } + if (!JS7_ISDEC(c)) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_MISSING_EXPONENT); + RETURN(TOK_ERROR); + } + do { + if (!AddToTokenBuf(cx, &ts->tokenbuf, (jschar)c)) + RETURN(TOK_ERROR); + c = GetChar(ts); + } while (JS7_ISDEC(c)); + } + } + + UngetChar(ts, c); + FINISH_TOKENBUF(&ts->tokenbuf); + + if (radix == 10) { + if (!js_strtod(cx, ts->tokenbuf.base, &endptr, &dval)) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_OUT_OF_MEMORY); + RETURN(TOK_ERROR); + } + } else { + if (!js_strtointeger(cx, ts->tokenbuf.base, &endptr, radix, &dval)) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_OUT_OF_MEMORY); + RETURN(TOK_ERROR); + } + } + tp->t_dval = dval; + RETURN(TOK_NUMBER); + } + + if (c == '"' || c == '\'') { + int32 val, qc = c; + + INIT_TOKENBUF(&ts->tokenbuf); + while ((c = GetChar(ts)) != qc) { + if (c == '\n' || c == EOF) { + UngetChar(ts, c); + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_UNTERMINATED_STRING); + RETURN(TOK_ERROR); + } + if (c == '\\') { + switch (c = GetChar(ts)) { + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'v': c = '\v'; break; + + default: + if ('0' <= c && c < '8') { + val = JS7_UNDEC(c); + c = PeekChar(ts); + if ('0' <= c && c < '8') { + val = 8 * val + JS7_UNDEC(c); + GetChar(ts); + c = PeekChar(ts); + if ('0' <= c && c < '8') { + int32 save = val; + val = 8 * val + JS7_UNDEC(c); + if (val <= 0377) + GetChar(ts); + else + val = save; + } + } + c = (jschar)val; + } else if (c == 'u') { + jschar cp[4]; + if (PeekChars(ts, 4, cp) && + JS7_ISHEX(cp[0]) && JS7_ISHEX(cp[1]) && + JS7_ISHEX(cp[2]) && JS7_ISHEX(cp[3])) { + c = (((((JS7_UNHEX(cp[0]) << 4) + + JS7_UNHEX(cp[1])) << 4) + + JS7_UNHEX(cp[2])) << 4) + + JS7_UNHEX(cp[3]); + SkipChars(ts, 4); + } + } else if (c == 'x') { + jschar cp[2]; + if (PeekChars(ts, 2, cp) && + JS7_ISHEX(cp[0]) && JS7_ISHEX(cp[1])) { + c = (JS7_UNHEX(cp[0]) << 4) + JS7_UNHEX(cp[1]); + SkipChars(ts, 2); + } + } else if (c == '\n' && JSVERSION_IS_ECMA(cx->version)) { + /* ECMA follows C by removing escaped newlines. */ + continue; + } + break; + } + } + if (!AddToTokenBuf(cx, &ts->tokenbuf, (jschar)c)) + RETURN(TOK_ERROR); + } + FINISH_TOKENBUF(&ts->tokenbuf); + atom = js_AtomizeChars(cx, + ts->tokenbuf.base, + TOKEN_LENGTH(&ts->tokenbuf), + 0); + if (!atom) + RETURN(TOK_ERROR); + tp->pos.end.lineno = (uint16)ts->lineno; + tp->t_op = JSOP_STRING; + tp->t_atom = atom; + RETURN(TOK_STRING); + } + + switch (c) { + case '\n': + c = TOK_EOL; + break; + + case ';': c = TOK_SEMI; break; + case '.': c = TOK_DOT; break; + case '[': c = TOK_LB; break; + case ']': c = TOK_RB; break; + case '{': c = TOK_LC; break; + case '}': c = TOK_RC; break; + case '(': c = TOK_LP; break; + case ')': c = TOK_RP; break; + case ',': c = TOK_COMMA; break; + case '?': c = TOK_HOOK; break; + + case ':': + /* + * Default so compiler can modify to JSOP_GETTER if 'p getter: v' in an + * object initializer, likewise for setter. + */ + tp->t_op = JSOP_NOP; + c = TOK_COLON; + break; + + case '|': + if (MatchChar(ts, c)) { + c = TOK_OR; + } else if (MatchChar(ts, '=')) { + tp->t_op = JSOP_BITOR; + c = TOK_ASSIGN; + } else { + c = TOK_BITOR; + } + break; + + case '^': + if (MatchChar(ts, '=')) { + tp->t_op = JSOP_BITXOR; + c = TOK_ASSIGN; + } else { + c = TOK_BITXOR; + } + break; + + case '&': + if (MatchChar(ts, c)) { + c = TOK_AND; + } else if (MatchChar(ts, '=')) { + tp->t_op = JSOP_BITAND; + c = TOK_ASSIGN; + } else { + c = TOK_BITAND; + } + break; + + case '=': + if (MatchChar(ts, c)) { +#if JS_HAS_TRIPLE_EQOPS + tp->t_op = MatchChar(ts, c) ? JSOP_NEW_EQ : (JSOp)cx->jsop_eq; +#else + tp->t_op = cx->jsop_eq; +#endif + c = TOK_EQOP; + } else { + tp->t_op = JSOP_NOP; + c = TOK_ASSIGN; + } + break; + + case '!': + if (MatchChar(ts, '=')) { +#if JS_HAS_TRIPLE_EQOPS + tp->t_op = MatchChar(ts, '=') ? JSOP_NEW_NE : (JSOp)cx->jsop_ne; +#else + tp->t_op = cx->jsop_ne; +#endif + c = TOK_EQOP; + } else { + tp->t_op = JSOP_NOT; + c = TOK_UNARYOP; + } + break; + + case '<': + /* NB: treat HTML begin-comment as comment-till-end-of-line */ + if (MatchChar(ts, '!')) { + if (MatchChar(ts, '-')) { + if (MatchChar(ts, '-')) + goto skipline; + UngetChar(ts, '-'); + } + UngetChar(ts, '!'); + } + if (MatchChar(ts, c)) { + tp->t_op = JSOP_LSH; + c = MatchChar(ts, '=') ? TOK_ASSIGN : TOK_SHOP; + } else { + tp->t_op = MatchChar(ts, '=') ? JSOP_LE : JSOP_LT; + c = TOK_RELOP; + } + break; + + case '>': + if (MatchChar(ts, c)) { + tp->t_op = MatchChar(ts, c) ? JSOP_URSH : JSOP_RSH; + c = MatchChar(ts, '=') ? TOK_ASSIGN : TOK_SHOP; + } else { + tp->t_op = MatchChar(ts, '=') ? JSOP_GE : JSOP_GT; + c = TOK_RELOP; + } + break; + + case '*': + tp->t_op = JSOP_MUL; + c = MatchChar(ts, '=') ? TOK_ASSIGN : TOK_STAR; + break; + + case '/': + if (MatchChar(ts, '/')) { +skipline: + while ((c = GetChar(ts)) != EOF && c != '\n') + /* skip to end of line */; + UngetChar(ts, c); + goto retry; + } + if (MatchChar(ts, '*')) { + while ((c = GetChar(ts)) != EOF && + !(c == '*' && MatchChar(ts, '/'))) { + /* Ignore all characters until comment close. */ + } + if (c == EOF) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_UNTERMINATED_COMMENT); + RETURN(TOK_ERROR); + } + goto retry; + } + +#if JS_HAS_REGEXPS + if (ts->flags & TSF_REGEXP) { + JSObject *obj; + uintN flags; + + INIT_TOKENBUF(&ts->tokenbuf); + while ((c = GetChar(ts)) != '/') { + if (c == '\n' || c == EOF) { + UngetChar(ts, c); + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_UNTERMINATED_REGEXP); + RETURN(TOK_ERROR); + } + if (c == '\\') { + if (!AddToTokenBuf(cx, &ts->tokenbuf, (jschar)c)) + RETURN(TOK_ERROR); + c = GetChar(ts); + } + if (!AddToTokenBuf(cx, &ts->tokenbuf, (jschar)c)) + RETURN(TOK_ERROR); + } + FINISH_TOKENBUF(&ts->tokenbuf); + for (flags = 0; ; ) { + if (MatchChar(ts, 'g')) + flags |= JSREG_GLOB; + else if (MatchChar(ts, 'i')) + flags |= JSREG_FOLD; + else if (MatchChar(ts, 'm')) + flags |= JSREG_MULTILINE; + else + break; + } + c = PeekChar(ts); + if (JS7_ISLET(c)) { + tp->ptr = ts->linebuf.ptr - 1; + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_BAD_REGEXP_FLAG); + (void) GetChar(ts); + RETURN(TOK_ERROR); + } + obj = js_NewRegExpObject(cx, ts, + ts->tokenbuf.base, + TOKEN_LENGTH(&ts->tokenbuf), + flags); + if (!obj) + RETURN(TOK_ERROR); + atom = js_AtomizeObject(cx, obj, 0); + if (!atom) + RETURN(TOK_ERROR); + tp->t_op = JSOP_OBJECT; + tp->t_atom = atom; + RETURN(TOK_OBJECT); + } +#endif /* JS_HAS_REGEXPS */ + + tp->t_op = JSOP_DIV; + c = MatchChar(ts, '=') ? TOK_ASSIGN : TOK_DIVOP; + break; + + case '%': + tp->t_op = JSOP_MOD; + c = MatchChar(ts, '=') ? TOK_ASSIGN : TOK_DIVOP; + break; + + case '~': + tp->t_op = JSOP_BITNOT; + c = TOK_UNARYOP; + break; + + case '+': + if (MatchChar(ts, '=')) { + tp->t_op = JSOP_ADD; + c = TOK_ASSIGN; + } else if (MatchChar(ts, c)) { + c = TOK_INC; + } else { + tp->t_op = JSOP_POS; + c = TOK_PLUS; + } + break; + + case '-': + if (MatchChar(ts, '=')) { + tp->t_op = JSOP_SUB; + c = TOK_ASSIGN; + } else if (MatchChar(ts, c)) { + if (PeekChar(ts) == '>' && !(ts->flags & TSF_DIRTYLINE)) + goto skipline; + c = TOK_DEC; + } else { + tp->t_op = JSOP_NEG; + c = TOK_MINUS; + } + ts->flags |= TSF_DIRTYLINE; + break; + +#if JS_HAS_SHARP_VARS + case '#': + { + uint32 n; + + c = GetChar(ts); + if (!JS7_ISDEC(c)) { + UngetChar(ts, c); + goto badchar; + } + n = (uint32)JS7_UNDEC(c); + for (;;) { + c = GetChar(ts); + if (!JS7_ISDEC(c)) + break; + n = 10 * n + JS7_UNDEC(c); + if (n >= ATOM_INDEX_LIMIT) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_SHARPVAR_TOO_BIG); + RETURN(TOK_ERROR); + } + } + tp->t_dval = (jsdouble) n; + if (JS_HAS_STRICT_OPTION(cx) && + (c == '=' || c == '#')) { + char buf[20]; + JS_snprintf(buf, sizeof buf, "#%u%c", n, c); + if (!js_ReportCompileErrorNumber(cx, ts, NULL, + JSREPORT_WARNING | + JSREPORT_STRICT, + JSMSG_DEPRECATED_USAGE, + buf)) { + RETURN(TOK_ERROR); + } + } + if (c == '=') + RETURN(TOK_DEFSHARP); + if (c == '#') + RETURN(TOK_USESHARP); + goto badchar; + } + + badchar: +#endif /* JS_HAS_SHARP_VARS */ + + default: + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_ILLEGAL_CHARACTER); + RETURN(TOK_ERROR); + } + + JS_ASSERT(c < TOK_LIMIT); + RETURN((JSTokenType)c); + +#undef INIT_TOKENBUF +#undef FINISH_TOKENBUF +#undef TOKEN_LENGTH +#undef RETURN +} + +void +js_UngetToken(JSTokenStream *ts) +{ + JS_ASSERT(ts->lookahead < NTOKENS_MASK); + if (ts->flags & TSF_ERROR) + return; + ts->lookahead++; + ts->cursor = (ts->cursor - 1) & NTOKENS_MASK; +} + +JSBool +js_MatchToken(JSContext *cx, JSTokenStream *ts, JSTokenType tt) +{ + if (js_GetToken(cx, ts) == tt) + return JS_TRUE; + js_UngetToken(ts); + return JS_FALSE; +} diff --git a/src/dom/js/jsscan.h b/src/dom/js/jsscan.h new file mode 100644 index 000000000..c61eff926 --- /dev/null +++ b/src/dom/js/jsscan.h @@ -0,0 +1,264 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsscan_h___ +#define jsscan_h___ +/* + * JS lexical scanner interface. + */ +#include +#include +#include "jsopcode.h" +#include "jsprvtd.h" +#include "jspubtd.h" + +JS_BEGIN_EXTERN_C + +typedef enum JSTokenType { + TOK_ERROR = -1, /* well-known as the only code < EOF */ + TOK_EOF = 0, /* end of file */ + TOK_EOL = 1, /* end of line */ + TOK_SEMI = 2, /* semicolon */ + TOK_COMMA = 3, /* comma operator */ + TOK_ASSIGN = 4, /* assignment ops (= += -= etc.) */ + TOK_HOOK = 5, TOK_COLON = 6, /* conditional (?:) */ + TOK_OR = 7, /* logical or (||) */ + TOK_AND = 8, /* logical and (&&) */ + TOK_BITOR = 9, /* bitwise-or (|) */ + TOK_BITXOR = 10, /* bitwise-xor (^) */ + TOK_BITAND = 11, /* bitwise-and (&) */ + TOK_EQOP = 12, /* equality ops (== !=) */ + TOK_RELOP = 13, /* relational ops (< <= > >=) */ + TOK_SHOP = 14, /* shift ops (<< >> >>>) */ + TOK_PLUS = 15, /* plus */ + TOK_MINUS = 16, /* minus */ + TOK_STAR = 17, TOK_DIVOP = 18, /* multiply/divide ops (* / %) */ + TOK_UNARYOP = 19, /* unary prefix operator */ + TOK_INC = 20, TOK_DEC = 21, /* increment/decrement (++ --) */ + TOK_DOT = 22, /* member operator (.) */ + TOK_LB = 23, TOK_RB = 24, /* left and right brackets */ + TOK_LC = 25, TOK_RC = 26, /* left and right curlies (braces) */ + TOK_LP = 27, TOK_RP = 28, /* left and right parentheses */ + TOK_NAME = 29, /* identifier */ + TOK_NUMBER = 30, /* numeric constant */ + TOK_STRING = 31, /* string constant */ + TOK_OBJECT = 32, /* RegExp or other object constant */ + TOK_PRIMARY = 33, /* true, false, null, this, super */ + TOK_FUNCTION = 34, /* function keyword */ + TOK_EXPORT = 35, /* export keyword */ + TOK_IMPORT = 36, /* import keyword */ + TOK_IF = 37, /* if keyword */ + TOK_ELSE = 38, /* else keyword */ + TOK_SWITCH = 39, /* switch keyword */ + TOK_CASE = 40, /* case keyword */ + TOK_DEFAULT = 41, /* default keyword */ + TOK_WHILE = 42, /* while keyword */ + TOK_DO = 43, /* do keyword */ + TOK_FOR = 44, /* for keyword */ + TOK_BREAK = 45, /* break keyword */ + TOK_CONTINUE = 46, /* continue keyword */ + TOK_IN = 47, /* in keyword */ + TOK_VAR = 48, /* var keyword */ + TOK_WITH = 49, /* with keyword */ + TOK_RETURN = 50, /* return keyword */ + TOK_NEW = 51, /* new keyword */ + TOK_DELETE = 52, /* delete keyword */ + TOK_DEFSHARP = 53, /* #n= for object/array initializers */ + TOK_USESHARP = 54, /* #n# for object/array initializers */ + TOK_TRY = 55, /* try keyword */ + TOK_CATCH = 56, /* catch keyword */ + TOK_FINALLY = 57, /* finally keyword */ + TOK_THROW = 58, /* throw keyword */ + TOK_INSTANCEOF = 59, /* instanceof keyword */ + TOK_DEBUGGER = 60, /* debugger keyword */ + TOK_RESERVED, /* reserved keywords */ + TOK_LIMIT /* domain size */ +} JSTokenType; + +#define IS_PRIMARY_TOKEN(tt) \ + ((uintN)((tt) - TOK_NAME) <= (uintN)(TOK_PRIMARY - TOK_NAME)) + +struct JSTokenPtr { + uint16 index; /* index of char in physical line */ + uint16 lineno; /* physical line number */ +}; + +struct JSTokenPos { + JSTokenPtr begin; /* first character and line of token */ + JSTokenPtr end; /* index 1 past last char, last line */ +}; + +struct JSToken { + JSTokenType type; /* char value or above enumerator */ + JSTokenPos pos; /* token position in file */ + jschar *ptr; /* beginning of token in line buffer */ + union { + struct { + JSOp op; /* operator, for minimal parser */ + JSAtom *atom; /* atom table entry */ + } s; + jsdouble dval; /* floating point number */ + } u; +}; + +#define t_op u.s.op +#define t_atom u.s.atom +#define t_dval u.dval + +typedef struct JSTokenBuf { + jschar *base; /* base of line or stream buffer */ + jschar *limit; /* limit for quick bounds check */ + jschar *ptr; /* next char to get, or slot to use */ +} JSTokenBuf; + +#define JS_LINE_LIMIT 256 /* logical line buffer size limit -- + physical line length is unlimited */ +#define NTOKENS 4 /* 1 current + 2 lookahead, rounded */ +#define NTOKENS_MASK (NTOKENS-1) /* to power of 2 to avoid divmod by 3 */ + +struct JSTokenStream { + JSToken tokens[NTOKENS];/* circular token buffer */ + uintN cursor; /* index of last parsed token */ + uintN lookahead; /* count of lookahead tokens */ + uintN lineno; /* current line number */ + uintN ungetpos; /* next free char slot in ungetbuf */ + jschar ungetbuf[6]; /* at most 6, for \uXXXX lookahead */ + uintN flags; /* flags -- see below */ + ptrdiff_t linelen; /* physical linebuf segment length */ + ptrdiff_t linepos; /* linebuf offset in physical line */ + JSTokenBuf linebuf; /* line buffer for diagnostics */ + JSTokenBuf userbuf; /* user input buffer if !file */ + JSTokenBuf tokenbuf; /* current token string buffer */ + const char *filename; /* input filename or null */ + FILE *file; /* stdio stream if reading from file */ + JSPrincipals *principals; /* principals associated with source */ + JSSourceHandler listener; /* callback for source; eg debugger */ + void *listenerData; /* listener 'this' data */ + void *listenerTSData;/* listener data for this TokenStream */ +}; + +#define CURRENT_TOKEN(ts) ((ts)->tokens[(ts)->cursor]) +#define ON_CURRENT_LINE(ts,pos) ((uint16)(ts)->lineno == (pos).end.lineno) + +/* JSTokenStream flags */ +#define TSF_ERROR 0x01 /* fatal error while compiling */ +#define TSF_EOF 0x02 /* hit end of file */ +#define TSF_NEWLINES 0x04 /* tokenize newlines */ +#define TSF_REGEXP 0x08 /* looking for a regular expression */ +#define TSF_NLFLAG 0x20 /* last linebuf ended with \n */ +#define TSF_CRFLAG 0x40 /* linebuf would have ended with \r */ +#define TSF_DIRTYLINE 0x80 /* stuff other than whitespace since start of line */ + +/* Unicode separators that are treated as line terminators, in addition to \n, \r */ +#define LINE_SEPARATOR 0x2028 +#define PARA_SEPARATOR 0x2029 + +/* + * Create a new token stream, either from an input buffer or from a file. + * Return null on file-open or memory-allocation failure. + * + * NB: All of js_New{,Buffer,File}TokenStream() return a pointer to transient + * memory in the current context's temp pool. This memory is deallocated via + * JS_ARENA_RELEASE() after parsing is finished. + */ +extern JSTokenStream * +js_NewTokenStream(JSContext *cx, const jschar *base, size_t length, + const char *filename, uintN lineno, JSPrincipals *principals); + +extern JS_FRIEND_API(JSTokenStream *) +js_NewBufferTokenStream(JSContext *cx, const jschar *base, size_t length); + +extern JS_FRIEND_API(JSTokenStream *) +js_NewFileTokenStream(JSContext *cx, const char *filename, FILE *defaultfp); + +extern JS_FRIEND_API(JSBool) +js_CloseTokenStream(JSContext *cx, JSTokenStream *ts); + +/* + * Initialize the scanner, installing JS keywords into cx's global scope. + */ +extern JSBool +js_InitScanner(JSContext *cx); + +/* + * Friend-exported API entry point to call a mapping function on each reserved + * identifier in the scanner's keyword table. + */ +extern JS_FRIEND_API(void) +js_MapKeywords(void (*mapfun)(const char *)); + +/* + * Report a compile-time error by its number, using ts or cg to show context. + * Return true for a warning, false for an error. + */ +extern JSBool +js_ReportCompileErrorNumber(JSContext *cx, JSTokenStream *ts, + JSCodeGenerator *cg, uintN flags, + const uintN errorNumber, ...); + +/* + * Look ahead one token and return its type. + */ +extern JSTokenType +js_PeekToken(JSContext *cx, JSTokenStream *ts); + +extern JSTokenType +js_PeekTokenSameLine(JSContext *cx, JSTokenStream *ts); + +/* + * Get the next token from ts. + */ +extern JSTokenType +js_GetToken(JSContext *cx, JSTokenStream *ts); + +/* + * Push back the last scanned token onto ts. + */ +extern void +js_UngetToken(JSTokenStream *ts); + +/* + * Get the next token from ts if its type is tt. + */ +extern JSBool +js_MatchToken(JSContext *cx, JSTokenStream *ts, JSTokenType tt); + +JS_END_EXTERN_C + +#endif /* jsscan_h___ */ diff --git a/src/dom/js/jsscope.c b/src/dom/js/jsscope.c new file mode 100644 index 000000000..20243e9b7 --- /dev/null +++ b/src/dom/js/jsscope.c @@ -0,0 +1,1581 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS symbol tables. + */ +#include "jsstddef.h" +#include +#include +#include "jstypes.h" +#include "jsarena.h" +#include "jsbit.h" +#include "jsclist.h" +#include "jsdhash.h" +#include "jsutil.h" /* Added by JSIFY */ +#include "jsapi.h" +#include "jsatom.h" +#include "jscntxt.h" +#include "jsdbgapi.h" +#include "jslock.h" +#include "jsnum.h" +#include "jsscope.h" +#include "jsstr.h" + +JSScope * +js_GetMutableScope(JSContext *cx, JSObject *obj) +{ + JSScope *scope, *newscope; + + scope = OBJ_SCOPE(obj); + JS_ASSERT(JS_IS_SCOPE_LOCKED(cx, scope)); + if (scope->object == obj) + return scope; + newscope = js_NewScope(cx, 0, scope->map.ops, LOCKED_OBJ_GET_CLASS(obj), + obj); + if (!newscope) + return NULL; + JS_LOCK_SCOPE(cx, newscope); + obj->map = js_HoldObjectMap(cx, &newscope->map); + scope = (JSScope *) js_DropObjectMap(cx, &scope->map, obj); + JS_TRANSFER_SCOPE_LOCK(cx, scope, newscope); + return newscope; +} + +/* + * JSScope uses multiplicative hashing, _a la_ jsdhash.[ch], but specialized + * to minimize footprint. But if a scope has fewer than SCOPE_HASH_THRESHOLD + * entries, we use linear search and avoid allocating scope->table. + */ +#define SCOPE_HASH_THRESHOLD 6 +#define MIN_SCOPE_SIZE_LOG2 4 +#define MIN_SCOPE_SIZE JS_BIT(MIN_SCOPE_SIZE_LOG2) +#define SCOPE_TABLE_NBYTES(n) ((n) * sizeof(JSScopeProperty *)) + +static void +InitMinimalScope(JSScope *scope) +{ + scope->hashShift = JS_DHASH_BITS - MIN_SCOPE_SIZE_LOG2; + scope->entryCount = scope->removedCount = 0; + scope->table = NULL; + scope->lastProp = NULL; +} + +static JSBool +CreateScopeTable(JSScope *scope) +{ + int sizeLog2; + JSScopeProperty *sprop, **spp; + + JS_ASSERT(!scope->table); + JS_ASSERT(scope->lastProp); + + if (scope->entryCount > SCOPE_HASH_THRESHOLD) { + /* + * Ouch: calloc failed at least once already -- let's try again, + * overallocating to hold at least twice the current population. + */ + sizeLog2 = JS_CeilingLog2(2 * scope->entryCount); + scope->hashShift = JS_DHASH_BITS - sizeLog2; + } else { + JS_ASSERT(scope->hashShift == JS_DHASH_BITS - MIN_SCOPE_SIZE_LOG2); + sizeLog2 = MIN_SCOPE_SIZE_LOG2; + } + + scope->table = (JSScopeProperty **) + calloc(JS_BIT(sizeLog2), sizeof(JSScopeProperty *)); + if (!scope->table) + return JS_FALSE; + + scope->hashShift = JS_DHASH_BITS - sizeLog2; + for (sprop = scope->lastProp; sprop; sprop = sprop->parent) { + spp = js_SearchScope(scope, sprop->id, JS_TRUE); + SPROP_STORE_PRESERVING_COLLISION(spp, sprop); + } + return JS_TRUE; +} + +JSScope * +js_NewScope(JSContext *cx, jsrefcount nrefs, JSObjectOps *ops, JSClass *clasp, + JSObject *obj) +{ + JSScope *scope; + + scope = (JSScope *) JS_malloc(cx, sizeof(JSScope)); + if (!scope) + return NULL; + + js_InitObjectMap(&scope->map, nrefs, ops, clasp); + scope->object = obj; + scope->flags = 0; + InitMinimalScope(scope); + +#ifdef JS_THREADSAFE + scope->ownercx = cx; + memset(&scope->lock, 0, sizeof scope->lock); + + /* + * Set u.link = NULL, not u.count = 0, in case the target architecture's + * null pointer has a non-zero integer representation. + */ + scope->u.link = NULL; + +#ifdef DEBUG + scope->file[0] = scope->file[1] = scope->file[2] = scope->file[3] = NULL; + scope->line[0] = scope->line[1] = scope->line[2] = scope->line[3] = 0; +#endif +#endif + + JS_RUNTIME_METER(cx->runtime, liveScopes); + JS_RUNTIME_METER(cx->runtime, totalScopes); + return scope; +} + +#ifdef DEBUG_SCOPE_COUNT +extern void +js_unlog_scope(JSScope *scope); +#endif + +void +js_DestroyScope(JSContext *cx, JSScope *scope) +{ +#ifdef DEBUG_SCOPE_COUNT + js_unlog_scope(scope); +#endif + +#ifdef JS_THREADSAFE + /* Scope must be single-threaded at this point, so set scope->ownercx. */ + JS_ASSERT(scope->u.count == 0); + scope->ownercx = cx; + js_FinishLock(&scope->lock); +#endif + if (scope->table) + JS_free(cx, scope->table); + +#ifdef DEBUG + JS_LOCK_RUNTIME_VOID(cx->runtime, + cx->runtime->liveScopeProps -= scope->entryCount); +#endif + JS_RUNTIME_UNMETER(cx->runtime, liveScopes); + JS_free(cx, scope); +} + +#ifdef DEBUG_brendan +typedef struct JSScopeStats { + jsrefcount searches; + jsrefcount steps; + jsrefcount hits; + jsrefcount misses; + jsrefcount stepHits; + jsrefcount stepMisses; + jsrefcount adds; + jsrefcount redundantAdds; + jsrefcount addFailures; + jsrefcount changeFailures; + jsrefcount compresses; + jsrefcount grows; + jsrefcount removes; + jsrefcount removeFrees; + jsrefcount uselessRemoves; + jsrefcount shrinks; +} JSScopeStats; + +JS_FRIEND_DATA(JSScopeStats) js_scope_stats; + +# define METER(x) JS_ATOMIC_INCREMENT(&js_scope_stats.x) +#else +# define METER(x) /* nothing */ +#endif + +/* + * Double hashing needs the second hash code to be relatively prime to table + * size, so we simply make hash2 odd. The inputs to multiplicative hash are + * the golden ratio, expressed as a fixed-point 32 bit fraction, and the int + * property index or named property's atom number (observe that most objects + * have either no indexed properties, or almost all indexed and a few names, + * so collisions between index and atom number are unlikely). + */ +#define SCOPE_HASH0(id) (HASH_ID(id) * JS_GOLDEN_RATIO) +#define SCOPE_HASH1(hash0,shift) ((hash0) >> (shift)) +#define SCOPE_HASH2(hash0,log2,shift) ((((hash0) << (log2)) >> (shift)) | 1) + +JS_FRIEND_API(JSScopeProperty **) +js_SearchScope(JSScope *scope, jsid id, JSBool adding) +{ + JSHashNumber hash0, hash1, hash2; + int hashShift, sizeLog2; + JSScopeProperty *stored, *sprop, **spp, **firstRemoved; + uint32 sizeMask; + + METER(searches); + if (!scope->table) { + /* Not enough properties to justify hashing: search from lastProp. */ + JS_ASSERT(!SCOPE_HAD_MIDDLE_DELETE(scope)); + for (spp = &scope->lastProp; (sprop = *spp); spp = &sprop->parent) { + if (sprop->id == id) { + METER(hits); + return spp; + } + } + METER(misses); + return spp; + } + + /* Compute the primary hash address. */ + hash0 = SCOPE_HASH0(id); + hashShift = scope->hashShift; + hash1 = SCOPE_HASH1(hash0, hashShift); + spp = scope->table + hash1; + + /* Miss: return space for a new entry. */ + stored = *spp; + if (SPROP_IS_FREE(stored)) { + METER(misses); + return spp; + } + + /* Hit: return entry. */ + sprop = SPROP_CLEAR_COLLISION(stored); + if (sprop && sprop->id == id) { + METER(hits); + return spp; + } + + /* Collision: double hash. */ + sizeLog2 = JS_DHASH_BITS - hashShift; + hash2 = SCOPE_HASH2(hash0, sizeLog2, hashShift); + sizeMask = JS_BITMASK(sizeLog2); + + /* Save the first removed entry pointer so we can recycle it if adding. */ + if (SPROP_IS_REMOVED(stored)) { + firstRemoved = spp; + } else { + firstRemoved = NULL; + if (adding && !SPROP_HAD_COLLISION(stored)) + SPROP_FLAG_COLLISION(spp, sprop); + } + + for (;;) { + METER(steps); + hash1 -= hash2; + hash1 &= sizeMask; + spp = scope->table + hash1; + + stored = *spp; + if (SPROP_IS_FREE(stored)) { + METER(stepMisses); + return (adding && firstRemoved) ? firstRemoved : spp; + } + + sprop = SPROP_CLEAR_COLLISION(stored); + if (sprop && sprop->id == id) { + METER(stepHits); + return spp; + } + + if (SPROP_IS_REMOVED(stored)) { + if (!firstRemoved) + firstRemoved = spp; + } else { + if (adding && !SPROP_HAD_COLLISION(stored)) + SPROP_FLAG_COLLISION(spp, sprop); + } + } + + /* NOTREACHED */ + return NULL; +} + +static JSBool +ChangeScope(JSContext *cx, JSScope *scope, int change) +{ + int oldlog2, newlog2; + uint32 oldsize, newsize, nbytes; + JSScopeProperty **table, **oldtable, **spp, **oldspp, *sprop; + + /* Grow, shrink, or compress by changing scope->table. */ + oldlog2 = JS_DHASH_BITS - scope->hashShift; + newlog2 = oldlog2 + change; + oldsize = JS_BIT(oldlog2); + newsize = JS_BIT(newlog2); + nbytes = SCOPE_TABLE_NBYTES(newsize); + table = (JSScopeProperty **) calloc(nbytes, 1); + if (!table) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + + /* Now that we have a new table allocated, update scope members. */ + scope->hashShift = JS_DHASH_BITS - newlog2; + scope->removedCount = 0; + oldtable = scope->table; + scope->table = table; + + /* Copy only live entries, leaving removed and free ones behind. */ + for (oldspp = oldtable; oldsize != 0; oldspp++) { + sprop = SPROP_FETCH(oldspp); + if (sprop) { + spp = js_SearchScope(scope, sprop->id, JS_TRUE); + JS_ASSERT(SPROP_IS_FREE(*spp)); + *spp = sprop; + } + oldsize--; + } + + /* Finally, free the old table storage. */ + JS_free(cx, oldtable); + return JS_TRUE; +} + +/* + * Take care to exclude the mark and duplicate bits, in case we're called from + * the GC, or we are searching for a property that has not yet been flagged as + * a duplicate when making a duplicate formal parameter. + */ +#define SPROP_FLAGS_NOT_MATCHED (SPROP_MARK | SPROP_IS_DUPLICATE) + +JS_STATIC_DLL_CALLBACK(JSDHashNumber) +js_HashScopeProperty(JSDHashTable *table, const void *key) +{ + const JSScopeProperty *sprop = (const JSScopeProperty *)key; + JSDHashNumber hash; + JSPropertyOp gsop; + + /* Accumulate from least to most random so the low bits are most random. */ + hash = 0; + gsop = sprop->getter; + if (gsop) + hash = (hash >> (JS_DHASH_BITS - 4)) ^ (hash << 4) ^ (jsword)gsop; + gsop = sprop->setter; + if (gsop) + hash = (hash >> (JS_DHASH_BITS - 4)) ^ (hash << 4) ^ (jsword)gsop; + + hash = (hash >> (JS_DHASH_BITS - 4)) ^ (hash << 4) + ^ (sprop->flags & ~SPROP_FLAGS_NOT_MATCHED); + + hash = (hash >> (JS_DHASH_BITS - 4)) ^ (hash << 4) ^ sprop->attrs; + hash = (hash >> (JS_DHASH_BITS - 4)) ^ (hash << 4) ^ sprop->shortid; + hash = (hash >> (JS_DHASH_BITS - 4)) ^ (hash << 4) ^ sprop->slot; + hash = (hash >> (JS_DHASH_BITS - 4)) ^ (hash << 4) ^ sprop->id; + return hash; +} + +#define SPROP_MATCH(sprop, child) \ + SPROP_MATCH_PARAMS(sprop, (child)->id, (child)->getter, (child)->setter, \ + (child)->slot, (child)->attrs, (child)->flags, \ + (child)->shortid) + +#define SPROP_MATCH_PARAMS(sprop, aid, agetter, asetter, aslot, aattrs, \ + aflags, ashortid) \ + ((sprop)->id == (aid) && \ + SPROP_MATCH_PARAMS_AFTER_ID(sprop, agetter, asetter, aslot, aattrs, \ + aflags, ashortid)) + +#define SPROP_MATCH_PARAMS_AFTER_ID(sprop, agetter, asetter, aslot, aattrs, \ + aflags, ashortid) \ + ((sprop)->getter == (agetter) && \ + (sprop)->setter == (asetter) && \ + (sprop)->slot == (aslot) && \ + (sprop)->attrs == (aattrs) && \ + (((sprop)->flags ^ (aflags)) & ~SPROP_FLAGS_NOT_MATCHED) == 0 && \ + (sprop)->shortid == (ashortid)) + +JS_STATIC_DLL_CALLBACK(JSBool) +js_MatchScopeProperty(JSDHashTable *table, + const JSDHashEntryHdr *hdr, + const void *key) +{ + const JSPropertyTreeEntry *entry = (const JSPropertyTreeEntry *)hdr; + const JSScopeProperty *sprop = entry->child; + const JSScopeProperty *kprop = (const JSScopeProperty *)key; + + return SPROP_MATCH(sprop, kprop); +} + +static const JSDHashTableOps PropertyTreeHashOps = { + JS_DHashAllocTable, + JS_DHashFreeTable, + JS_DHashGetKeyStub, + js_HashScopeProperty, + js_MatchScopeProperty, + JS_DHashMoveEntryStub, + JS_DHashClearEntryStub, + JS_DHashFinalizeStub, + NULL +}; + +/* + * A property tree node on rt->propertyFreeList overlays the following prefix + * struct on JSScopeProperty. + */ +typedef struct FreeNode { + jsid id; + JSScopeProperty *next; + JSScopeProperty **prevp; +} FreeNode; + +#define FREENODE(sprop) ((FreeNode *) (sprop)) + +#define FREENODE_INSERT(list, sprop) \ + JS_BEGIN_MACRO \ + FREENODE(sprop)->next = (list); \ + FREENODE(sprop)->prevp = &(list); \ + if (list) \ + FREENODE(list)->prevp = &FREENODE(sprop)->next; \ + (list) = (sprop); \ + JS_END_MACRO + +#define FREENODE_REMOVE(sprop) \ + JS_BEGIN_MACRO \ + *FREENODE(sprop)->prevp = FREENODE(sprop)->next; \ + if (FREENODE(sprop)->next) \ + FREENODE(FREENODE(sprop)->next)->prevp = FREENODE(sprop)->prevp; \ + JS_END_MACRO + +/* NB: Called with the runtime lock held. */ +static JSScopeProperty * +NewScopeProperty(JSRuntime *rt) +{ + JSScopeProperty *sprop; + + sprop = rt->propertyFreeList; + if (sprop) { + FREENODE_REMOVE(sprop); + } else { + JS_ARENA_ALLOCATE_CAST(sprop, JSScopeProperty *, + &rt->propertyArenaPool, + sizeof(JSScopeProperty)); + if (!sprop) + return NULL; + } + + JS_RUNTIME_METER(rt, livePropTreeNodes); + JS_RUNTIME_METER(rt, totalPropTreeNodes); + return sprop; +} + +#define CHUNKY_KIDS_TAG ((jsuword)1) +#define KIDS_IS_CHUNKY(kids) ((jsuword)(kids) & CHUNKY_KIDS_TAG) +#define KIDS_TO_CHUNK(kids) ((PropTreeKidsChunk *) \ + ((jsuword)(kids) & ~CHUNKY_KIDS_TAG)) +#define CHUNK_TO_KIDS(chunk) ((JSScopeProperty *) \ + ((jsuword)(chunk) | CHUNKY_KIDS_TAG)) +#define MAX_KIDS_PER_CHUNK 10 + +typedef struct PropTreeKidsChunk PropTreeKidsChunk; + +struct PropTreeKidsChunk { + JSScopeProperty *kids[MAX_KIDS_PER_CHUNK]; + PropTreeKidsChunk *next; +}; + +static PropTreeKidsChunk * +NewPropTreeKidsChunk(JSRuntime *rt) +{ + PropTreeKidsChunk *chunk; + + chunk = calloc(1, sizeof *chunk); + if (!chunk) + return NULL; + JS_ASSERT(((jsuword)chunk & CHUNKY_KIDS_TAG) == 0); + JS_RUNTIME_METER(rt, propTreeKidsChunks); + return chunk; +} + +static void +DestroyPropTreeKidsChunk(JSRuntime *rt, PropTreeKidsChunk *chunk) +{ + JS_RUNTIME_UNMETER(rt, propTreeKidsChunks); + free(chunk); +} + +/* NB: Called with the runtime lock held. */ +static JSBool +InsertPropertyTreeChild(JSRuntime *rt, JSScopeProperty *parent, + JSScopeProperty *child) +{ + JSPropertyTreeEntry *entry; + JSScopeProperty **childp, *kids, *sprop; + PropTreeKidsChunk *chunk, **chunkp; + uintN i; + + JS_ASSERT(!parent || child->parent != parent); + + if (!parent) { + entry = (JSPropertyTreeEntry *) + JS_DHashTableOperate(&rt->propertyTreeHash, child, JS_DHASH_ADD); + if (!entry) + return JS_FALSE; + childp = &entry->child; + sprop = *childp; + if (!sprop) { + *childp = child; + } else { + /* + * A "Duplicate child" case. + * + * We can't do away with child, as at least one live scope entry + * still points at it. What's more, that scope's lastProp chains + * through an ancestor line to reach child, and js_Enumerate and + * others count on this linkage. We must leave child out of the + * hash table, and not require it to be there when we eventually + * GC it (see RemovePropertyTreeChild, below). + * + * It is necessary to leave the duplicate child out of the hash + * table to preserve entry uniqueness. It is safe to leave the + * child out of the hash table (unlike the duplicate child cases + * below), because the child's parent link will be null, which + * can't dangle. + */ + JS_ASSERT(sprop != child && SPROP_MATCH(sprop, child)); + JS_RUNTIME_METER(rt, duplicatePropTreeNodes); + } + } else { + childp = &parent->kids; + kids = *childp; + if (kids) { + if (KIDS_IS_CHUNKY(kids)) { + chunk = KIDS_TO_CHUNK(kids); + do { + for (i = 0; i < MAX_KIDS_PER_CHUNK; i++) { + childp = &chunk->kids[i]; + sprop = *childp; + if (!sprop) + goto insert; + + JS_ASSERT(sprop != child); + if (SPROP_MATCH(sprop, child)) { + /* + * Duplicate child, see comment above. In this + * case, we must let the duplicate be inserted at + * this level in the tree, so we keep iterating, + * looking for an empty slot in which to insert. + */ + JS_ASSERT(sprop != child); + JS_RUNTIME_METER(rt, duplicatePropTreeNodes); + } + } + chunkp = &chunk->next; + } while ((chunk = *chunkp) != NULL); + + chunk = NewPropTreeKidsChunk(rt); + if (!chunk) + return JS_FALSE; + *chunkp = chunk; + childp = &chunk->kids[0]; + } else { + sprop = kids; + JS_ASSERT(sprop != child); + if (SPROP_MATCH(sprop, child)) { + /* + * Duplicate child, see comment above. Once again, we + * must let duplicates created by deletion pile up in a + * kids-chunk-list, in order to find them when sweeping + * and thereby avoid dangling parent pointers. + */ + JS_RUNTIME_METER(rt, duplicatePropTreeNodes); + } + + chunk = NewPropTreeKidsChunk(rt); + if (!chunk) + return JS_FALSE; + parent->kids = CHUNK_TO_KIDS(chunk); + chunk->kids[0] = sprop; + childp = &chunk->kids[1]; + } + } + insert: + *childp = child; + } + + child->parent = parent; + return JS_TRUE; +} + +/* NB: Called with the runtime lock held. */ +static void +RemovePropertyTreeChild(JSRuntime *rt, JSScopeProperty *child) +{ + JSPropertyTreeEntry *entry; + JSScopeProperty *parent, *kids, *kid; + PropTreeKidsChunk *list, *chunk, **chunkp, *lastChunk; + uintN i, j; + + parent = child->parent; + if (!parent) { + /* + * Don't remove child if it is not in rt->propertyTreeHash, but only + * matches a root child in the table that has compatible members. See + * the "Duplicate child" comments in InsertPropertyTreeChild, above. + */ + entry = (JSPropertyTreeEntry *) + JS_DHashTableOperate(&rt->propertyTreeHash, child, JS_DHASH_LOOKUP); + + if (entry->child == child) + JS_DHashTableRawRemove(&rt->propertyTreeHash, &entry->hdr); + } else { + kids = parent->kids; + if (KIDS_IS_CHUNKY(kids)) { + list = chunk = KIDS_TO_CHUNK(kids); + chunkp = &list; + + do { + for (i = 0; i < MAX_KIDS_PER_CHUNK; i++) { + if (chunk->kids[i] == child) { + lastChunk = chunk; + if (!lastChunk->next) { + j = i + 1; + } else { + j = 0; + do { + chunkp = &lastChunk->next; + lastChunk = *chunkp; + } while (lastChunk->next); + } + for (; j < MAX_KIDS_PER_CHUNK; j++) { + if (!lastChunk->kids[j]) + break; + } + --j; + if (chunk != lastChunk || j > i) + chunk->kids[i] = lastChunk->kids[j]; + lastChunk->kids[j] = NULL; + if (j == 0) { + *chunkp = NULL; + if (!list) + parent->kids = NULL; + DestroyPropTreeKidsChunk(rt, lastChunk); + } + return; + } + } + + chunkp = &chunk->next; + } while ((chunk = *chunkp) != NULL); + } else { + kid = kids; + if (kid == child) + parent->kids = NULL; + } + } +} + +/* + * Called *without* the runtime lock held, this function acquires that lock + * only when inserting a new child. Thus there may be races to find or add + * a node that result in duplicates. We expect such races to be rare! + */ +static JSScopeProperty * +GetPropertyTreeChild(JSContext *cx, JSScopeProperty *parent, + JSScopeProperty *child) +{ + JSRuntime *rt; + JSPropertyTreeEntry *entry; + JSScopeProperty *sprop; + PropTreeKidsChunk *chunk; + uintN i; + + rt = cx->runtime; + if (!parent) { + JS_LOCK_RUNTIME(rt); + + entry = (JSPropertyTreeEntry *) + JS_DHashTableOperate(&rt->propertyTreeHash, child, JS_DHASH_ADD); + if (!entry) + goto out_of_memory; + + sprop = entry->child; + if (sprop) + goto out; + } else { + /* + * Because chunks are appended at the end and never deleted except by + * the GC, we can search without taking the runtime lock. We may miss + * a matching sprop added by another thread, and make a duplicate one, + * but that is an unlikely, therefore small, cost. The property tree + * has extremely low fan-out below its root in popular embeddings with + * real-world workloads. + * + * If workload changes so as to increase fan-out significantly below + * the property tree root, we'll want to add another tag bit stored in + * parent->kids that indicates a JSDHashTable pointer. + */ + entry = NULL; + sprop = parent->kids; + if (sprop) { + if (KIDS_IS_CHUNKY(sprop)) { + chunk = KIDS_TO_CHUNK(sprop); + do { + for (i = 0; i < MAX_KIDS_PER_CHUNK; i++) { + sprop = chunk->kids[i]; + if (!sprop) + goto not_found; + + if (SPROP_MATCH(sprop, child)) + return sprop; + } + } while ((chunk = chunk->next) != NULL); + } else { + if (SPROP_MATCH(sprop, child)) + return sprop; + } + } + + not_found: + JS_LOCK_RUNTIME(rt); + } + + sprop = NewScopeProperty(rt); + if (!sprop) + goto out_of_memory; + + sprop->id = child->id; + sprop->getter = child->getter; + sprop->setter = child->setter; + sprop->slot = child->slot; + sprop->attrs = child->attrs; + sprop->flags = child->flags; + sprop->shortid = child->shortid; + sprop->parent = sprop->kids = NULL; + if (!parent) { + entry->child = sprop; + } else { + if (!InsertPropertyTreeChild(rt, parent, sprop)) + goto out_of_memory; + } + +out: + JS_UNLOCK_RUNTIME(rt); + return sprop; + +out_of_memory: + JS_UNLOCK_RUNTIME(rt); + JS_ReportOutOfMemory(cx); + return NULL; +} + +#ifdef DEBUG_notbrendan +#define CHECK_ANCESTOR_LINE(scope, sparse) \ + JS_BEGIN_MACRO \ + if ((scope)->table) CheckAncestorLine(scope, sparse); \ + JS_END_MACRO + +static void +CheckAncestorLine(JSScope *scope, JSBool sparse) +{ + uint32 size; + JSScopeProperty **spp, **start, **end, *ancestorLine, *sprop, *aprop; + uint32 entryCount, ancestorCount; + + ancestorLine = SCOPE_LAST_PROP(scope); + if (ancestorLine) + JS_ASSERT(SCOPE_HAS_PROPERTY(scope, ancestorLine)); + + entryCount = 0; + size = SCOPE_CAPACITY(scope); + start = scope->table; + for (spp = start, end = start + size; spp < end; spp++) { + sprop = SPROP_FETCH(spp); + if (sprop) { + entryCount++; + for (aprop = ancestorLine; aprop; aprop = aprop->parent) { + if (aprop == sprop) + break; + } + JS_ASSERT(aprop); + } + } + JS_ASSERT(entryCount == scope->entryCount); + + ancestorCount = 0; + for (sprop = ancestorLine; sprop; sprop = sprop->parent) { + if (SCOPE_HAD_MIDDLE_DELETE(scope) && + !SCOPE_HAS_PROPERTY(scope, sprop)) { + JS_ASSERT(sparse || (sprop->flags & SPROP_IS_DUPLICATE)); + continue; + } + ancestorCount++; + } + JS_ASSERT(ancestorCount == scope->entryCount); +} +#else +#define CHECK_ANCESTOR_LINE(scope, sparse) /* nothing */ +#endif + +static void +ReportReadOnlyScope(JSContext *cx, JSScope *scope) +{ + JSString *str; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(scope->object)); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_READ_ONLY, + str + ? JS_GetStringBytes(str) + : LOCKED_OBJ_GET_CLASS(scope->object)->name); +} + +JSScopeProperty * +js_AddScopeProperty(JSContext *cx, JSScope *scope, jsid id, + JSPropertyOp getter, JSPropertyOp setter, uint32 slot, + uintN attrs, uintN flags, intN shortid) +{ + JSScopeProperty **spp, *sprop, *overwriting, **spvec, **spp2, child; + uint32 size, splen, i; + int change; + + JS_ASSERT(JS_IS_SCOPE_LOCKED(cx, scope)); + CHECK_ANCESTOR_LINE(scope, JS_TRUE); + + /* + * You can't add properties to a sealed scope. But note well that you can + * change property attributes in a sealed scope, even though that replaces + * a JSScopeProperty * in the scope's hash table -- but no id is added, so + * the scope remains sealed. + */ + if (SCOPE_IS_SEALED(scope)) { + ReportReadOnlyScope(cx, scope); + return NULL; + } + + /* + * Normalize stub getter and setter values for faster is-stub testing in + * the SPROP_CALL_[GS]ETTER macros. + */ + if (getter == JS_PropertyStub) + getter = NULL; + if (setter == JS_PropertyStub) + setter = NULL; + + /* + * Search for id in order to claim its entry, allocating a property tree + * node if one doesn't already exist for our parameters. + */ + spp = js_SearchScope(scope, id, JS_TRUE); + sprop = overwriting = SPROP_FETCH(spp); + if (!sprop) { + /* Check whether we need to grow, if the load factor is >= .75. */ + size = SCOPE_CAPACITY(scope); + if (scope->entryCount + scope->removedCount >= size - (size >> 2)) { + if (scope->removedCount >= size >> 2) { + METER(compresses); + change = 0; + } else { + METER(grows); + change = 1; + } + if (!ChangeScope(cx, scope, change) && + scope->entryCount + scope->removedCount == size - 1) { + METER(addFailures); + return NULL; + } + spp = js_SearchScope(scope, id, JS_TRUE); + JS_ASSERT(!SPROP_FETCH(spp)); + } + } else { + /* Property exists: js_SearchScope must have returned a valid entry. */ + JS_ASSERT(!SPROP_IS_REMOVED(*spp)); + + /* + * If all property members match, this is a redundant add and we can + * return early. If the caller wants to allocate a slot, but doesn't + * care which slot, copy sprop->slot into slot so we can match sprop, + * if all other members match. + */ + if (!(attrs & JSPROP_SHARED) && + slot == SPROP_INVALID_SLOT && + SPROP_HAS_VALID_SLOT(sprop, scope)) { + slot = sprop->slot; + } + if (SPROP_MATCH_PARAMS_AFTER_ID(sprop, getter, setter, slot, attrs, + flags, shortid)) { + METER(redundantAdds); + return sprop; + } + + /* + * Duplicate formal parameters require us to leave the old property + * on the ancestor line, so the decompiler can find it, even though + * its entry in scope->table is overwritten to point at a new property + * descending from the old one. The SPROP_IS_DUPLICATE flag helps us + * cope with the consequent disparity between ancestor line height and + * scope->entryCount. + */ + if (flags & SPROP_IS_DUPLICATE) { + sprop->flags |= SPROP_IS_DUPLICATE; + } else { + /* + * If we are clearing sprop to force an existing property to be + * overwritten (apart from a duplicate formal parameter), we must + * unlink it from the ancestor line at scope->lastProp, lazily if + * sprop is not lastProp. And we must remove the entry at *spp, + * precisely so the lazy "middle delete" fixup code further below + * won't find sprop in scope->table, in spite of sprop being on + * the ancestor line. + * + * When we finally succeed in finding or creating a new sprop + * and storing its pointer at *spp, we'll use the |overwriting| + * local saved when we first looked up id to decide whether we're + * indeed creating a new entry, or merely overwriting an existing + * property. + */ + if (sprop == SCOPE_LAST_PROP(scope)) { + do { + SCOPE_REMOVE_LAST_PROP(scope); + if (!SCOPE_HAD_MIDDLE_DELETE(scope)) + break; + sprop = SCOPE_LAST_PROP(scope); + } while (sprop && !SCOPE_HAS_PROPERTY(scope, sprop)); + } else if (!SCOPE_HAD_MIDDLE_DELETE(scope)) { + /* + * If we have no hash table yet, we need one now. The middle + * delete code is simple-minded that way! + */ + if (!scope->table) { + if (!CreateScopeTable(scope)) { + JS_ReportOutOfMemory(cx); + return NULL; + } + spp = js_SearchScope(scope, id, JS_TRUE); + sprop = overwriting = SPROP_FETCH(spp); + } + SCOPE_SET_MIDDLE_DELETE(scope); + } + } + + /* + * If we fail later on trying to find or create a new sprop, we will + * goto fail_overwrite and restore *spp from |overwriting|. Note that + * we don't bother to keep scope->removedCount in sync, because we'll + * fix up *spp and scope->entryCount shortly, no matter how control + * flow returns from this function. + */ + if (scope->table) + SPROP_STORE_PRESERVING_COLLISION(spp, NULL); + scope->entryCount--; + CHECK_ANCESTOR_LINE(scope, JS_TRUE); + sprop = NULL; + } + + if (!sprop) { + /* + * If properties were deleted from the middle of the list starting at + * scope->lastProp, we may need to fork the property tree and squeeze + * all deleted properties out of scope's ancestor line. Otherwise we + * risk adding a node with the same id as a "middle" node, violating + * the rule that properties along an ancestor line have distinct ids + * (unless flagged SPROP_IS_DUPLICATE). + */ + if (SCOPE_HAD_MIDDLE_DELETE(scope)) { + JS_ASSERT(scope->table); + CHECK_ANCESTOR_LINE(scope, JS_TRUE); + + splen = scope->entryCount; + if (splen == 0) { + JS_ASSERT(scope->lastProp == NULL); + } else { + /* + * Enumerate live entries in scope->table using a temporary + * vector, by walking the (possibly sparse, due to deletions) + * ancestor line from scope->lastProp. + */ + spvec = (JSScopeProperty **) + JS_malloc(cx, SCOPE_TABLE_NBYTES(splen)); + if (!spvec) + goto fail_overwrite; + i = splen; + sprop = SCOPE_LAST_PROP(scope); + JS_ASSERT(sprop); + do { + /* + * NB: test SCOPE_GET_PROPERTY, not SCOPE_HAS_PROPERTY -- + * the latter insists that sprop->id maps to sprop, while + * the former simply tests whether sprop->id is bound in + * scope. We must allow for duplicate formal parameters + * along the ancestor line, and fork them as needed. + */ + if (!SCOPE_GET_PROPERTY(scope, sprop->id)) + continue; + + JS_ASSERT(sprop != overwriting); + if (i == 0) { + /* + * If our original splen estimate, scope->entryCount, + * is less than the ancestor line height, there must + * be duplicate formal parameters in this (function + * object) scope. Count remaining ancestors in order + * to realloc spvec. + */ + JSScopeProperty *tmp = sprop; + do { + if (SCOPE_GET_PROPERTY(scope, tmp->id)) + i++; + } while ((tmp = tmp->parent) != NULL); + spp2 = (JSScopeProperty **) + JS_realloc(cx, spvec, SCOPE_TABLE_NBYTES(splen+i)); + if (!spp2) { + JS_free(cx, spvec); + goto fail_overwrite; + } + + spvec = spp2; + memmove(spvec + i, spvec, SCOPE_TABLE_NBYTES(splen)); + splen += i; + } + + spvec[--i] = sprop; + } while ((sprop = sprop->parent) != NULL); + JS_ASSERT(i == 0); + + /* + * Now loop forward through spvec, forking the property tree + * whenever we see a "parent gap" due to deletions from scope. + * NB: sprop is null on first entry to the loop body. + */ + do { + if (spvec[i]->parent == sprop) { + sprop = spvec[i]; + } else { + sprop = GetPropertyTreeChild(cx, sprop, spvec[i]); + if (!sprop) { + JS_free(cx, spvec); + goto fail_overwrite; + } + + spp2 = js_SearchScope(scope, sprop->id, JS_FALSE); + JS_ASSERT(SPROP_FETCH(spp2) == spvec[i]); + SPROP_STORE_PRESERVING_COLLISION(spp2, sprop); + } + } while (++i < splen); + JS_free(cx, spvec); + + /* + * Now sprop points to the last property in scope, where the + * ancestor line from sprop to the root is dense w.r.t. scope: + * it contains no nodes not mapped by scope->table, apart from + * any stinking ECMA-mandated duplicate formal parameters. + */ + scope->lastProp = sprop; + CHECK_ANCESTOR_LINE(scope, JS_FALSE); + JS_RUNTIME_METER(cx->runtime, middleDeleteFixups); + } + + SCOPE_CLR_MIDDLE_DELETE(scope); + } + + /* + * Aliases share another property's slot, passed in the |slot| param. + * Shared properties have no slot. Unshared properties that do not + * alias another property's slot get one here, but may lose it due to + * a JS_ClearScope call. + */ + if (!(flags & SPROP_IS_ALIAS)) { + if (attrs & JSPROP_SHARED) { + slot = SPROP_INVALID_SLOT; + } else { + /* + * We may have set slot from a nearly-matching sprop, above. + * If so, we're overwriting that nearly-matching sprop, so we + * can reuse its slot -- we don't need to allocate a new one. + * Callers should therefore pass SPROP_INVALID_SLOT for all + * non-alias, unshared property adds. + */ + if (slot != SPROP_INVALID_SLOT) + JS_ASSERT(overwriting); + else if (!js_AllocSlot(cx, scope->object, &slot)) + goto fail_overwrite; + } + } + + /* + * Check for a watchpoint on a deleted property; if one exists, change + * setter to js_watch_set. + * XXXbe this could get expensive with lots of watchpoints... + */ + if (!JS_CLIST_IS_EMPTY(&cx->runtime->watchPointList) && + js_FindWatchPoint(cx->runtime, scope, id)) { + setter = js_WrapWatchedSetter(cx, id, attrs, setter); + if (!setter) + goto fail_overwrite; + } + + /* Find or create a property tree node labeled by our arguments. */ + child.id = id; + child.getter = getter; + child.setter = setter; + child.slot = slot; + child.attrs = attrs; + child.flags = flags; + child.shortid = shortid; + sprop = GetPropertyTreeChild(cx, scope->lastProp, &child); + if (!sprop) + goto fail_overwrite; + + /* Store the tree node pointer in the table entry for id. */ + if (scope->table) + SPROP_STORE_PRESERVING_COLLISION(spp, sprop); + scope->entryCount++; + scope->lastProp = sprop; + CHECK_ANCESTOR_LINE(scope, JS_FALSE); + if (!overwriting) { + JS_RUNTIME_METER(cx->runtime, liveScopeProps); + JS_RUNTIME_METER(cx->runtime, totalScopeProps); + } + + /* + * If we reach the hashing threshold, try to allocate scope->table. + * If we can't (a rare event, preceded by swapping to death on most + * modern OSes), stick with linear search rather than whining about + * this little set-back. Therefore we must test !scope->table and + * scope->entryCount >= SCOPE_HASH_THRESHOLD, not merely whether the + * entry count just reached the threshold. + */ + if (!scope->table && scope->entryCount >= SCOPE_HASH_THRESHOLD) + (void) CreateScopeTable(scope); + } + + METER(adds); + return sprop; + +fail_overwrite: + if (overwriting) { + /* + * We may or may not have forked overwriting out of scope's ancestor + * line, so we must check (the alternative is to set a flag above, but + * that hurts the common, non-error case). If we did fork overwriting + * out, we'll add it back at scope->lastProp. This means enumeration + * order can change due to a failure to overwrite an id. + * XXXbe very minor incompatibility + */ + for (sprop = SCOPE_LAST_PROP(scope); ; sprop = sprop->parent) { + if (!sprop) { + sprop = SCOPE_LAST_PROP(scope); + if (overwriting->parent == sprop) { + scope->lastProp = overwriting; + } else { + sprop = GetPropertyTreeChild(cx, sprop, overwriting); + if (sprop) { + JS_ASSERT(sprop != overwriting); + scope->lastProp = sprop; + } + overwriting = sprop; + } + break; + } + if (sprop == overwriting) + break; + } + if (overwriting) { + if (scope->table) + SPROP_STORE_PRESERVING_COLLISION(spp, overwriting); + scope->entryCount++; + } + CHECK_ANCESTOR_LINE(scope, JS_TRUE); + } + METER(addFailures); + return NULL; +} + +JSScopeProperty * +js_ChangeScopePropertyAttrs(JSContext *cx, JSScope *scope, + JSScopeProperty *sprop, uintN attrs, uintN mask, + JSPropertyOp getter, JSPropertyOp setter) +{ + JSScopeProperty child, *newsprop, **spp; + + CHECK_ANCESTOR_LINE(scope, JS_TRUE); + + /* Allow only shared (slot-less) => unshared (slot-full) transition. */ + attrs |= sprop->attrs & mask; + JS_ASSERT(!((attrs ^ sprop->attrs) & JSPROP_SHARED) || + !(attrs & JSPROP_SHARED)); + if (getter == JS_PropertyStub) + getter = NULL; + if (setter == JS_PropertyStub) + setter = NULL; + if (sprop->attrs == attrs && + sprop->getter == getter && + sprop->setter == setter) { + return sprop; + } + + child.id = sprop->id; + child.getter = getter; + child.setter = setter; + child.slot = sprop->slot; + child.attrs = attrs; + child.flags = sprop->flags; + child.shortid = sprop->shortid; + + if (SCOPE_LAST_PROP(scope) == sprop) { + /* + * Optimize the case where the last property added to scope is changed + * to have a different attrs, getter, or setter. In the last property + * case, we need not fork the property tree. But since we do not call + * js_AddScopeProperty, we may need to allocate a new slot directly. + */ + if ((sprop->attrs & JSPROP_SHARED) && !(attrs & JSPROP_SHARED)) { + JS_ASSERT(child.slot == SPROP_INVALID_SLOT); + if (!js_AllocSlot(cx, scope->object, &child.slot)) + return NULL; + } + + newsprop = GetPropertyTreeChild(cx, sprop->parent, &child); + if (newsprop) { + spp = js_SearchScope(scope, sprop->id, JS_FALSE); + JS_ASSERT(SPROP_FETCH(spp) == sprop); + + if (scope->table) + SPROP_STORE_PRESERVING_COLLISION(spp, newsprop); + scope->lastProp = newsprop; + CHECK_ANCESTOR_LINE(scope, JS_TRUE); + } + } else { + /* + * Let js_AddScopeProperty handle this |overwriting| case, including + * the conservation of sprop->slot (if it's valid). We must not call + * js_RemoveScopeProperty here, it will free a valid sprop->slot and + * js_AddScopeProperty won't re-allocate it. + */ + newsprop = js_AddScopeProperty(cx, scope, child.id, + child.getter, child.setter, child.slot, + child.attrs, child.flags, child.shortid); + } + +#ifdef DEBUG_brendan + if (!newsprop) + METER(changeFailures); +#endif + return newsprop; +} + +JSBool +js_RemoveScopeProperty(JSContext *cx, JSScope *scope, jsid id) +{ + JSScopeProperty **spp, *stored, *sprop; + uint32 size; + + JS_ASSERT(JS_IS_SCOPE_LOCKED(cx, scope)); + CHECK_ANCESTOR_LINE(scope, JS_TRUE); + if (SCOPE_IS_SEALED(scope)) { + ReportReadOnlyScope(cx, scope); + return JS_FALSE; + } + METER(removes); + + spp = js_SearchScope(scope, id, JS_FALSE); + stored = *spp; + sprop = SPROP_CLEAR_COLLISION(stored); + if (!sprop) { + METER(uselessRemoves); + return JS_TRUE; + } + + /* Convert from a list to a hash so we can handle "middle deletes". */ + if (!scope->table && sprop != scope->lastProp) { + if (!CreateScopeTable(scope)) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + spp = js_SearchScope(scope, id, JS_FALSE); + stored = *spp; + sprop = SPROP_CLEAR_COLLISION(stored); + } + + /* First, if sprop is unshared and not cleared, free its slot number. */ + if (SPROP_HAS_VALID_SLOT(sprop, scope)) + js_FreeSlot(cx, scope->object, sprop->slot); + + /* Next, remove id by setting its entry to a removed or free sentinel. */ + if (SPROP_HAD_COLLISION(stored)) { + JS_ASSERT(scope->table); + *spp = SPROP_REMOVED; + scope->removedCount++; + } else { + METER(removeFrees); + if (scope->table) + *spp = NULL; + } + scope->entryCount--; + JS_RUNTIME_UNMETER(cx->runtime, liveScopeProps); + + /* Update scope->lastProp directly, or set its deferred update flag. */ + if (sprop == SCOPE_LAST_PROP(scope)) { + do { + SCOPE_REMOVE_LAST_PROP(scope); + if (!SCOPE_HAD_MIDDLE_DELETE(scope)) + break; + sprop = SCOPE_LAST_PROP(scope); + } while (sprop && !SCOPE_HAS_PROPERTY(scope, sprop)); + } else if (!SCOPE_HAD_MIDDLE_DELETE(scope)) { + SCOPE_SET_MIDDLE_DELETE(scope); + } + CHECK_ANCESTOR_LINE(scope, JS_TRUE); + + /* Last, consider shrinking scope's table if its load factor is <= .25. */ + size = SCOPE_CAPACITY(scope); + if (size > MIN_SCOPE_SIZE && scope->entryCount <= size >> 2) { + METER(shrinks); + (void) ChangeScope(cx, scope, -1); + } + + return JS_TRUE; +} + +void +js_ClearScope(JSContext *cx, JSScope *scope) +{ + CHECK_ANCESTOR_LINE(scope, JS_TRUE); +#ifdef DEBUG + JS_LOCK_RUNTIME_VOID(cx->runtime, + cx->runtime->liveScopeProps -= scope->entryCount); +#endif + + if (scope->table) + free(scope->table); + SCOPE_CLR_MIDDLE_DELETE(scope); + InitMinimalScope(scope); +} + +#ifdef DEBUG_brendan + +#include +#include + +uint32 js_nkids_max; +uint32 js_nkids_sum; +double js_nkids_sqsum; +uint32 js_nkids_hist[11]; + +static void +MeterKidCount(uintN nkids) +{ + if (nkids) { + js_nkids_sum += nkids; + js_nkids_sqsum += (double)nkids * nkids; + if (nkids > js_nkids_max) + js_nkids_max = nkids; + } + js_nkids_hist[JS_MIN(nkids, 10)]++; +} + +static void +MeterPropertyTree(JSScopeProperty *node) +{ + uintN i, nkids; + JSScopeProperty *kids, *kid; + PropTreeKidsChunk *chunk; + + nkids = 0; + kids = node->kids; + if (kids) { + if (KIDS_IS_CHUNKY(kids)) { + for (chunk = KIDS_TO_CHUNK(kids); chunk; chunk = chunk->next) { + for (i = 0; i < MAX_KIDS_PER_CHUNK; i++) { + kid = chunk->kids[i]; + if (!kid) + break; + MeterPropertyTree(kid); + nkids++; + } + } + } else { + MeterPropertyTree(kids); + nkids = 1; + } + } + + MeterKidCount(nkids); +} + +JS_STATIC_DLL_CALLBACK(JSDHashOperator) +js_MeterPropertyTree(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 number, + void *arg) +{ + JSPropertyTreeEntry *entry = (JSPropertyTreeEntry *)hdr; + + MeterPropertyTree(entry->child); + return JS_DHASH_NEXT; +} + +#endif /* DEBUG_brendan */ + +void +js_SweepScopeProperties(JSRuntime *rt) +{ + JSArena **ap, *a; + JSScopeProperty *limit, *sprop, *parent, *kids, *kid; + uintN liveCount; + PropTreeKidsChunk *chunk, *nextChunk; + uintN i; + +#ifdef DEBUG_brendan + uint32 livePropCapacity = 0, totalLiveCount = 0; + static FILE *logfp; + if (!logfp) + logfp = fopen("/tmp/proptree.stats", "a"); + + MeterKidCount(rt->propertyTreeHash.entryCount); + JS_DHashTableEnumerate(&rt->propertyTreeHash, js_MeterPropertyTree, NULL); + + { + double mean = 0., var = 0., sigma = 0.; + double nodesum = rt->livePropTreeNodes; + double kidsum = js_nkids_sum; + if (nodesum > 0 && kidsum >= 0) { + mean = kidsum / nodesum; + var = nodesum * js_nkids_sqsum - kidsum * kidsum; + if (var < 0.0 || nodesum <= 1) + var = 0.0; + else + var /= nodesum * (nodesum - 1); + + /* Windows says sqrt(0.0) is "-1.#J" (?!) so we must test. */ + sigma = (var != 0.) ? sqrt(var) : 0.; + } + + fprintf(logfp, + "props %u nodes %g beta %g meankids %g sigma %g max %u", + rt->liveScopeProps, nodesum, nodesum / rt->liveScopeProps, + mean, sigma, js_nkids_max); + } + + fprintf(logfp, " histogram %u %u %u %u %u %u %u %u %u %u %u", + js_nkids_hist[0], js_nkids_hist[1], + js_nkids_hist[2], js_nkids_hist[3], + js_nkids_hist[4], js_nkids_hist[5], + js_nkids_hist[6], js_nkids_hist[7], + js_nkids_hist[8], js_nkids_hist[9], + js_nkids_hist[10]); + js_nkids_sum = js_nkids_max = 0; + js_nkids_sqsum = 0; + memset(js_nkids_hist, 0, sizeof js_nkids_hist); +#endif + + ap = &rt->propertyArenaPool.first.next; + while ((a = *ap) != NULL) { + limit = (JSScopeProperty *) a->avail; + liveCount = 0; + for (sprop = (JSScopeProperty *) a->base; sprop < limit; sprop++) { + /* If the id is null, sprop is already on the freelist. */ + if (sprop->id == JSVAL_NULL) + continue; + + /* If the mark bit is set, sprop is alive, so we skip it. */ + if (sprop->flags & SPROP_MARK) { + sprop->flags &= ~SPROP_MARK; + liveCount++; + continue; + } + + /* Ok, sprop is garbage to collect: unlink it from its parent. */ + RemovePropertyTreeChild(rt, sprop); + + /* Take care to reparent all sprop's kids to their grandparent. */ + kids = sprop->kids; + if (kids) { + sprop->kids = NULL; + parent = sprop->parent; + if (KIDS_IS_CHUNKY(kids)) { + chunk = KIDS_TO_CHUNK(kids); + do { + for (i = 0; i < MAX_KIDS_PER_CHUNK; i++) { + kid = chunk->kids[i]; + if (!kid) + break; + JS_ASSERT(kid->parent == sprop); + InsertPropertyTreeChild(rt, parent, kid); + } + nextChunk = chunk->next; + DestroyPropTreeKidsChunk(rt, chunk); + } while ((chunk = nextChunk) != NULL); + } else { + kid = kids; + InsertPropertyTreeChild(rt, parent, kid); + } + } + + /* Clear id so we know (above) that sprop is on the freelist. */ + sprop->id = JSVAL_NULL; + FREENODE_INSERT(rt->propertyFreeList, sprop); + JS_RUNTIME_UNMETER(rt, livePropTreeNodes); + } + + /* If a contains no live properties, return it to the malloc heap. */ + if (liveCount == 0) { + for (sprop = (JSScopeProperty *) a->base; sprop < limit; sprop++) + FREENODE_REMOVE(sprop); + JS_ARENA_DESTROY(&rt->propertyArenaPool, a, ap); + } else { +#ifdef DEBUG_brendan + livePropCapacity += limit - (JSScopeProperty *) a->base; + totalLiveCount += liveCount; +#endif + ap = &a->next; + } + } + +#ifdef DEBUG_brendan + fprintf(logfp, " arenautil %g%%\n", + (totalLiveCount * 100.) / livePropCapacity); + fflush(logfp); +#endif +} + +JSBool +js_InitPropertyTree(JSRuntime *rt) +{ + if (!JS_DHashTableInit(&rt->propertyTreeHash, &PropertyTreeHashOps, NULL, + sizeof(JSPropertyTreeEntry), JS_DHASH_MIN_SIZE)) { + rt->propertyTreeHash.ops = NULL; + return JS_FALSE; + } + JS_InitArenaPool(&rt->propertyArenaPool, "properties", + 256 * sizeof(JSScopeProperty), sizeof(void *)); + return JS_TRUE; +} + +void +js_FinishPropertyTree(JSRuntime *rt) +{ + if (rt->propertyTreeHash.ops) { + JS_DHashTableFinish(&rt->propertyTreeHash); + rt->propertyTreeHash.ops = NULL; + } + JS_FinishArenaPool(&rt->propertyArenaPool); +} diff --git a/src/dom/js/jsscope.h b/src/dom/js/jsscope.h new file mode 100644 index 000000000..4f66441b4 --- /dev/null +++ b/src/dom/js/jsscope.h @@ -0,0 +1,386 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsscope_h___ +#define jsscope_h___ +/* + * JS symbol tables. + */ +#include "jstypes.h" +#include "jsobj.h" +#include "jsprvtd.h" +#include "jspubtd.h" + +#ifdef JS_THREADSAFE +# include "jslock.h" +#endif + +/* + * Given P independent, non-unique properties each of size S words mapped by + * all scopes in a runtime, construct a property tree of N nodes each of size + * S+L words (L for tree linkage). A nominal L value is 2 for leftmost-child + * and right-sibling links. We hope that the N < P by enough that the space + * overhead of L, and the overhead of scope entries pointing at property tree + * nodes, is worth it. + * + * The tree construction goes as follows. If any empty scope in the runtime + * has a property X added to it, find or create a node under the tree root + * labeled X, and set scope->lastProp to point at that node. If any non-empty + * scope whose most recently added property is labeled Y has another property + * labeled Z added, find or create a node for Z under the node that was added + * for Y, and set scope->lastProp to point at that node. + * + * A property is labeled by its members' values: id, getter, setter, slot, + * attributes, tiny or short id, and a field telling for..in order. Note that + * labels are not unique in the tree, but they are unique among a node's kids + * (barring rare and benign multi-threaded race condition outcomes, see below) + * and along any ancestor line from the tree root to a given leaf node (except + * for the hard case of duplicate formal parameters to a function). + * + * Thus the root of the tree represents all empty scopes, and the first ply + * of the tree represents all scopes containing one property, etc. Each node + * in the tree can stand for any number of scopes having the same ordered set + * of properties, where that node was the last added to the scope. (We need + * not store the root of the tree as a node, and do not -- all we need are + * links to its kids.) + * + * Sidebar on for..in loop order: ECMA requires no particular order, but this + * implementation has promised and delivered property definition order, and + * compatibility is king. We could use an order number per property, which + * would require a sort in js_Enumerate, and an entry order generation number + * per scope. An order number beats a list, which should be doubly-linked for + * O(1) delete. An even better scheme is to use a parent link in the property + * tree, so that the ancestor line can be iterated from scope->lastProp when + * filling in a JSIdArray from back to front. This parent link also helps the + * GC to sweep properties iteratively. + * + * What if a property Y is deleted from a scope? If Y is the last property in + * the scope, we simply adjust the scope's lastProp member after we remove the + * scope's hash-table entry pointing at that property node. The parent link + * mentioned in the for..in sidebar above makes this adjustment O(1). But if + * Y comes between X and Z in the scope, then we might have to "fork" the tree + * at X, leaving X->Y->Z in case other scopes have those properties added in + * that order; and to finish the fork, we'd add a node labeled Z with the path + * X->Z, if it doesn't exist. This could lead to lots of extra nodes, and to + * O(n^2) growth when deleting lots of properties. + * + * Rather, for O(1) growth all around, we should share the path X->Y->Z among + * scopes having those three properties added in that order, and among scopes + * having only X->Z where Y was deleted. All such scopes have a lastProp that + * points to the Z child of Y. But a scope in which Y was deleted does not + * have a table entry for Y, and when iterating that scope by traversing the + * ancestor line from Z, we will have to test for a table entry for each node, + * skipping nodes that lack entries. + * + * What if we add Y again? X->Y->Z->Y is wrong and we'll enumerate Y twice. + * Therefore we must fork in such a case, if not earlier. Because delete is + * "bursty", we should not fork eagerly. Delaying a fork till we are at risk + * of adding Y after it was deleted already requires a flag in the JSScope, to + * wit, SCOPE_MIDDLE_DELETE. + * + * What about thread safety? If the property tree operations done by requests + * are find-node and insert-node, then the only hazard is duplicate insertion. + * This is harmless except for minor bloat. When all requests have ended or + * been suspended, the GC is free to sweep the tree after marking all nodes + * reachable from scopes, performing remove-node operations as needed. Note + * also that the stable storage of the property nodes during active requests + * permits the property cache (see jsinterp.h) to dereference JSScopeProperty + * weak references safely. + * + * Is the property tree worth it compared to property storage in each table's + * entries? To decide, we must find the relation <> between the words used + * with a property tree and the words required without a tree. + * + * Model all scopes as one super-scope of capacity T entries (T a power of 2). + * Let alpha be the load factor of this double hash-table. With the property + * tree, each entry in the table is a word-sized pointer to a node that can be + * shared by many scopes. But all such pointers are overhead compared to the + * situation without the property tree, where the table stores property nodes + * directly, as entries each of size S words. With the property tree, we need + * L=2 extra words per node for siblings and kids pointers. Without the tree, + * (1-alpha)*S*T words are wasted on free or removed sentinel-entries required + * by double hashing. + * + * Therefore, + * + * (property tree) <> (no property tree) + * N*(S+L) + T <> S*T + * N*(S+L) + T <> P*S + (1-alpha)*S*T + * N*(S+L) + alpha*T + (1-alpha)*T <> P*S + (1-alpha)*S*T + * + * Note that P is alpha*T by definition, so + * + * N*(S+L) + P + (1-alpha)*T <> P*S + (1-alpha)*S*T + * N*(S+L) <> P*S - P + (1-alpha)*S*T - (1-alpha)*T + * N*(S+L) <> (P + (1-alpha)*T) * (S-1) + * N*(S+L) <> (P + (1-alpha)*P/alpha) * (S-1) + * N*(S+L) <> P * (1/alpha) * (S-1) + * + * Let N = P*beta for a compression ratio beta, beta <= 1: + * + * P*beta*(S+L) <> P * (1/alpha) * (S-1) + * beta*(S+L) <> (S-1)/alpha + * beta <> (S-1)/((S+L)*alpha) + * + * For S = 6 (32-bit architectures) and L = 2, the property tree wins iff + * + * beta < 5/(8*alpha) + * + * We ensure that alpha <= .75, so the property tree wins if beta < .83_. An + * average beta from recent Mozilla browser startups was around .6. + * + * Can we reduce L? Observe that the property tree degenerates into a list of + * lists if at most one property Y follows X in all scopes. In or near such a + * case, we waste a word on the right-sibling link outside of the root ply of + * the tree. Note also that the root ply tends to be large, so O(n^2) growth + * searching it is likely, indicating the need for hashing (but with increased + * thread safety costs). + * + * If only K out of N nodes in the property tree have more than one child, we + * could eliminate the sibling link and overlay a children list or hash-table + * pointer on the leftmost-child link (which would then be either null or an + * only-child link; the overlay could be tagged in the low bit of the pointer, + * or flagged elsewhere in the property tree node, although such a flag must + * not be considered when comparing node labels during tree search). + * + * For such a system, L = 1 + (K * averageChildrenTableSize) / N instead of 2. + * If K << N, L approaches 1 and the property tree wins if beta < .95. + * + * We observe that fan-out below the root ply of the property tree appears to + * have extremely low degree (see the MeterPropertyTree code that histograms + * child-counts in jsscope.c), so instead of a hash-table we use a linked list + * of child node pointer arrays ("kid chunks"). The details are isolated in + * jsscope.c; others must treat JSScopeProperty.kids as opaque. We leave it + * strongly typed for debug-ability of the common (null or one-kid) cases. + * + * One final twist (can you stand it?): the mean number of entries per scope + * in Mozilla is < 5, with a large standard deviation (~8). Instead of always + * allocating scope->table, we leave it null while initializing all the other + * scope members as if it were non-null and minimal-length. Until a property + * is added that crosses the threshold of 6 or more entries for hashing, or + * until a "middle delete" occurs, we use linear search from scope->lastProp + * to find a given id, and save on the space overhead of a hash table. + */ + +struct JSScope { + JSObjectMap map; /* base class state */ + JSObject *object; /* object that owns this scope */ + uint16 flags; /* flags, see below */ + int16 hashShift; /* multiplicative hash shift */ + uint32 entryCount; /* number of entries in table */ + uint32 removedCount; /* removed entry sentinels in table */ + JSScopeProperty **table; /* table of ptrs to shared tree nodes */ + JSScopeProperty *lastProp; /* pointer to last property added */ +#ifdef JS_THREADSAFE + JSContext *ownercx; /* creating context, NULL if shared */ + JSThinLock lock; /* binary semaphore protecting scope */ + union { /* union lockful and lock-free state: */ + jsrefcount count; /* lock entry count for reentrancy */ + JSScope *link; /* next link in rt->scopeSharingTodo */ + } u; +#ifdef DEBUG + const char *file[4]; /* file where lock was (re-)taken */ + unsigned int line[4]; /* line where lock was (re-)taken */ +#endif +#endif +}; + +#define OBJ_SCOPE(obj) ((JSScope *)(obj)->map) + +/* By definition, hashShift = JS_DHASH_BITS - log2(capacity). */ +#define SCOPE_CAPACITY(scope) JS_BIT(JS_DHASH_BITS-(scope)->hashShift) + +/* Scope flags and some macros to hide them from other files than jsscope.c. */ +#define SCOPE_MIDDLE_DELETE 0x0001 +#define SCOPE_SEALED 0x0002 + +#define SCOPE_HAD_MIDDLE_DELETE(scope) ((scope)->flags & SCOPE_MIDDLE_DELETE) +#define SCOPE_SET_MIDDLE_DELETE(scope) ((scope)->flags |= SCOPE_MIDDLE_DELETE) +#define SCOPE_CLR_MIDDLE_DELETE(scope) ((scope)->flags &= ~SCOPE_MIDDLE_DELETE) + +#define SCOPE_IS_SEALED(scope) ((scope)->flags & SCOPE_SEALED) +#define SCOPE_SET_SEALED(scope) ((scope)->flags |= SCOPE_SEALED) +#if 0 +/* + * Don't define this, it can't be done safely because JS_LOCK_OBJ will avoid + * taking the lock if the object owns its scope and the scope is sealed. + */ +#define SCOPE_CLR_SEALED(scope) ((scope)->flags &= ~SCOPE_SEALED) +#endif + +/* + * A little information hiding for scope->lastProp, in case it ever becomes + * a tagged pointer again. + */ +#define SCOPE_LAST_PROP(scope) ((scope)->lastProp) +#define SCOPE_REMOVE_LAST_PROP(scope) ((scope)->lastProp = \ + (scope)->lastProp->parent) + +struct JSScopeProperty { + jsid id; /* int-tagged jsval/untagged JSAtom* */ + JSPropertyOp getter; /* getter and setter hooks or objects */ + JSPropertyOp setter; + uint32 slot; /* index in obj->slots vector */ + uint8 attrs; /* attributes, see jsapi.h JSPROP_* */ + uint8 flags; /* flags, see below for defines */ + int16 shortid; /* tinyid, or local arg/var index */ + JSScopeProperty *parent; /* parent node, reverse for..in order */ + JSScopeProperty *kids; /* null, single child, or a tagged ptr + to many-kids data structure */ +}; + +/* JSScopeProperty pointer tag bit indicating a collision. */ +#define SPROP_COLLISION ((jsuword)1) +#define SPROP_REMOVED ((JSScopeProperty *) SPROP_COLLISION) + +/* Macros to get and set sprop pointer values and collision flags. */ +#define SPROP_IS_FREE(sprop) ((sprop) == NULL) +#define SPROP_IS_REMOVED(sprop) ((sprop) == SPROP_REMOVED) +#define SPROP_IS_LIVE(sprop) ((sprop) > SPROP_REMOVED) +#define SPROP_FLAG_COLLISION(spp,sprop) (*(spp) = (JSScopeProperty *) \ + ((jsuword)(sprop) | SPROP_COLLISION)) +#define SPROP_HAD_COLLISION(sprop) ((jsuword)(sprop) & SPROP_COLLISION) +#define SPROP_FETCH(spp) SPROP_CLEAR_COLLISION(*(spp)) + +#define SPROP_CLEAR_COLLISION(sprop) \ + ((JSScopeProperty *) ((jsuword)(sprop) & ~SPROP_COLLISION)) + +#define SPROP_STORE_PRESERVING_COLLISION(spp, sprop) \ + (*(spp) = (JSScopeProperty *) ((jsuword)(sprop) \ + | SPROP_HAD_COLLISION(*(spp)))) + +/* Bits stored in sprop->flags. */ +#define SPROP_MARK 0x01 +#define SPROP_IS_DUPLICATE 0x02 +#define SPROP_IS_ALIAS 0x04 +#define SPROP_HAS_SHORTID 0x08 + +/* + * If SPROP_HAS_SHORTID is set in sprop->flags, we use sprop->shortid rather + * than id when calling sprop's getter or setter. + */ +#define SPROP_USERID(sprop) \ + (((sprop)->flags & SPROP_HAS_SHORTID) ? INT_TO_JSVAL((sprop)->shortid) \ + : ID_TO_VALUE((sprop)->id)) + +#define SPROP_INVALID_SLOT 0xffffffff + +#define SPROP_HAS_VALID_SLOT(sprop, scope) \ + ((sprop)->slot < (scope)->map.freeslot) + +#define SPROP_CALL_GETTER(cx,sprop,getter,obj,obj2,vp) \ + (!(getter) || \ + (getter)(cx, OBJ_THIS_OBJECT(cx,obj), SPROP_USERID(sprop), vp)) +#define SPROP_CALL_SETTER(cx,sprop,setter,obj,obj2,vp) \ + (!(setter) || \ + (setter)(cx, OBJ_THIS_OBJECT(cx,obj), SPROP_USERID(sprop), vp)) + +#define SPROP_GET(cx,sprop,obj,obj2,vp) \ + (((sprop)->attrs & JSPROP_GETTER) \ + ? js_InternalGetOrSet(cx, obj, (sprop)->id, \ + OBJECT_TO_JSVAL((sprop)->getter), JSACC_READ, \ + 0, 0, vp) \ + : SPROP_CALL_GETTER(cx, sprop, (sprop)->getter, obj, obj2, vp)) + +#define SPROP_SET(cx,sprop,obj,obj2,vp) \ + (((sprop)->attrs & JSPROP_SETTER) \ + ? js_InternalGetOrSet(cx, obj, (sprop)->id, \ + OBJECT_TO_JSVAL((sprop)->setter), JSACC_WRITE, \ + 1, vp, vp) \ + : ((sprop)->attrs & JSPROP_GETTER) \ + ? (JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, \ + JSMSG_GETTER_ONLY, NULL), JS_FALSE) \ + : SPROP_CALL_SETTER(cx, sprop, (sprop)->setter, obj, obj2, vp)) + +/* Macro for common expression to test for shared permanent attributes. */ +#define SPROP_IS_SHARED_PERMANENT(sprop) \ + ((~(sprop)->attrs & (JSPROP_SHARED | JSPROP_PERMANENT)) == 0) + +extern JSScope * +js_GetMutableScope(JSContext *cx, JSObject *obj); + +extern JSScope * +js_NewScope(JSContext *cx, jsrefcount nrefs, JSObjectOps *ops, JSClass *clasp, + JSObject *obj); + +extern void +js_DestroyScope(JSContext *cx, JSScope *scope); + +#define ID_TO_VALUE(id) (((id) & JSVAL_INT) ? id : ATOM_KEY((JSAtom *)(id))) +#define HASH_ID(id) (((id) & JSVAL_INT) \ + ? (jsatomid) JSVAL_TO_INT(id) \ + : ((JSAtom *)id)->number) + +extern JS_FRIEND_API(JSScopeProperty **) +js_SearchScope(JSScope *scope, jsid id, JSBool adding); + +#define SCOPE_GET_PROPERTY(scope, id) \ + SPROP_FETCH(js_SearchScope(scope, id, JS_FALSE)) + +#define SCOPE_HAS_PROPERTY(scope, sprop) \ + (SCOPE_GET_PROPERTY(scope, (sprop)->id) == (sprop)) + +extern JSScopeProperty * +js_AddScopeProperty(JSContext *cx, JSScope *scope, jsid id, + JSPropertyOp getter, JSPropertyOp setter, uint32 slot, + uintN attrs, uintN flags, intN shortid); + +extern JSScopeProperty * +js_ChangeScopePropertyAttrs(JSContext *cx, JSScope *scope, + JSScopeProperty *sprop, uintN attrs, uintN mask, + JSPropertyOp getter, JSPropertyOp setter); + +extern JSBool +js_RemoveScopeProperty(JSContext *cx, JSScope *scope, jsid id); + +extern void +js_ClearScope(JSContext *cx, JSScope *scope); + +#define MARK_SCOPE_PROPERTY(sprop) ((sprop)->flags |= SPROP_MARK) + +extern void +js_SweepScopeProperties(JSRuntime *rt); + +extern JSBool +js_InitPropertyTree(JSRuntime *rt); + +extern void +js_FinishPropertyTree(JSRuntime *rt); + +#endif /* jsscope_h___ */ diff --git a/src/dom/js/jsscript.c b/src/dom/js/jsscript.c new file mode 100644 index 000000000..504cbbd6d --- /dev/null +++ b/src/dom/js/jsscript.c @@ -0,0 +1,1287 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS script operations. + */ +#include "jsstddef.h" +#include +#include "jstypes.h" +#include "jsutil.h" /* Added by JSIFY */ +#include "jsprf.h" +#include "jsapi.h" +#include "jsatom.h" +#include "jscntxt.h" +#include "jsconfig.h" +#include "jsdbgapi.h" +#include "jsemit.h" +#include "jsfun.h" +#include "jsinterp.h" +#include "jslock.h" +#include "jsnum.h" +#include "jsopcode.h" +#include "jsscript.h" +#if JS_HAS_XDR +#include "jsxdrapi.h" +#endif + +#if JS_HAS_SCRIPT_OBJECT + +#if JS_HAS_TOSOURCE +static JSBool +script_toSource(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSScript *script; + size_t i, j, k, n; + char buf[16]; + jschar *s, *t; + uint32 indent; + JSString *str; + + if (!JS_InstanceOf(cx, obj, &js_ScriptClass, argv)) + return JS_FALSE; + script = (JSScript *) JS_GetPrivate(cx, obj); + + /* Let n count the source string length, j the "front porch" length. */ + j = JS_snprintf(buf, sizeof buf, "(new %s(", js_ScriptClass.name); + n = j + 2; + if (!script) { + /* Let k count the constructor argument string length. */ + k = 0; + s = NULL; /* quell GCC overwarning */ + } else { + indent = 0; + if (argc && !js_ValueToECMAUint32(cx, argv[0], &indent)) + return JS_FALSE; + str = JS_DecompileScript(cx, script, "Script.prototype.toSource", + (uintN)indent); + if (!str) + return JS_FALSE; + str = js_QuoteString(cx, str, '\''); + if (!str) + return JS_FALSE; + s = JSSTRING_CHARS(str); + k = JSSTRING_LENGTH(str); + n += k; + } + + /* Allocate the source string and copy into it. */ + t = (jschar *) JS_malloc(cx, (n + 1) * sizeof(jschar)); + if (!t) + return JS_FALSE; + for (i = 0; i < j; i++) + t[i] = buf[i]; + for (j = 0; j < k; i++, j++) + t[i] = s[j]; + t[i++] = ')'; + t[i++] = ')'; + t[i] = 0; + + /* Create and return a JS string for t. */ + str = JS_NewUCString(cx, t, n); + if (!str) { + JS_free(cx, t); + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} +#endif /* JS_HAS_TOSOURCE */ + +static JSBool +script_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSScript *script; + uint32 indent; + JSString *str; + + if (!JS_InstanceOf(cx, obj, &js_ScriptClass, argv)) + return JS_FALSE; + script = (JSScript *) JS_GetPrivate(cx, obj); + if (!script) { + *rval = STRING_TO_JSVAL(cx->runtime->emptyString); + return JS_TRUE; + } + + indent = 0; + if (argc && !js_ValueToECMAUint32(cx, argv[0], &indent)) + return JS_FALSE; + str = JS_DecompileScript(cx, script, "Script.prototype.toString", + (uintN)indent); + if (!str) + return JS_FALSE; + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +static JSBool +script_compile(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSScript *oldscript, *script; + JSString *str; + JSStackFrame *fp, *caller; + JSObject *scopeobj; + const char *file; + uintN line; + JSPrincipals *principals; + + /* Make sure obj is a Script object. */ + if (!JS_InstanceOf(cx, obj, &js_ScriptClass, argv)) + return JS_FALSE; + + /* If no args, leave private undefined and return early. */ + if (argc == 0) + goto out; + + /* Otherwise, the first arg is the script source to compile. */ + str = js_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + + /* Compile using the caller's scope chain, which js_Invoke passes to fp. */ + fp = cx->fp; + caller = JS_GetScriptedCaller(cx, fp); + JS_ASSERT(!caller || fp->scopeChain == caller->scopeChain); + + scopeobj = NULL; + if (argc >= 2) { + if (!js_ValueToObject(cx, argv[1], &scopeobj)) + return JS_FALSE; + argv[1] = OBJECT_TO_JSVAL(scopeobj); + } + if (caller) { + if (!scopeobj) + scopeobj = caller->scopeChain; + + file = caller->script->filename; + line = js_PCToLineNumber(cx, caller->script, caller->pc); + principals = JS_EvalFramePrincipals(cx, fp, caller); + } else { + file = NULL; + line = 0; + principals = NULL; + } + + /* XXXbe set only for the compiler, which does not currently test it */ + fp->flags |= JSFRAME_EVAL; + + /* Compile the new script using the caller's scope chain, a la eval(). */ + script = JS_CompileUCScriptForPrincipals(cx, scopeobj, principals, + JSSTRING_CHARS(str), + JSSTRING_LENGTH(str), + file, line); + if (!script) + return JS_FALSE; + + /* Swap script for obj's old script, if any. */ + oldscript = (JSScript *) JS_GetPrivate(cx, obj); + if (!JS_SetPrivate(cx, obj, script)) { + js_DestroyScript(cx, script); + return JS_FALSE; + } + if (oldscript) + js_DestroyScript(cx, oldscript); + + script->object = obj; +out: + /* Return the object. */ + *rval = OBJECT_TO_JSVAL(obj); + return JS_TRUE; +} + +static JSBool +script_exec(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSScript *script; + JSObject *scopeobj, *parent; + JSStackFrame *fp, *caller; + + if (!JS_InstanceOf(cx, obj, &js_ScriptClass, argv)) + return JS_FALSE; + script = (JSScript *) JS_GetPrivate(cx, obj); + if (!script) + return JS_TRUE; + + scopeobj = NULL; + if (argc) { + if (!js_ValueToObject(cx, argv[0], &scopeobj)) + return JS_FALSE; + argv[0] = OBJECT_TO_JSVAL(scopeobj); + } + + /* + * Emulate eval() by using caller's this, var object, sharp array, etc., + * all propagated by js_Execute via a non-null fourth (down) argument to + * js_Execute. If there is no scripted caller, js_Execute uses its second + * (chain) argument to set the exec frame's varobj, thisp, and scopeChain. + * + * Unlike eval, which the compiler detects, Script.prototype.exec may be + * called from a lightweight function, or even from native code (in which + * case fp->varobj and fp->scopeChain are null). If exec is called from + * a lightweight function, we will need to get a Call object representing + * its frame, to act as the var object and scope chain head. + */ + fp = cx->fp; + caller = JS_GetScriptedCaller(cx, fp); + if (caller && !caller->varobj) { + /* Called from a lightweight function. */ + JS_ASSERT(caller->fun && !(caller->fun->flags & JSFUN_HEAVYWEIGHT)); + + /* Scope chain links from Call object to callee's parent. */ + parent = OBJ_GET_PARENT(cx, JSVAL_TO_OBJECT(caller->argv[-2])); + if (!js_GetCallObject(cx, caller, parent)) + return JS_FALSE; + } + + if (!scopeobj) { + /* No scope object passed in: try to use the caller's scope chain. */ + if (caller) { + /* + * Load caller->scopeChain after the conditional js_GetCallObject + * call above, which resets scopeChain as well as varobj. + */ + scopeobj = caller->scopeChain; + } else { + /* + * Called from native code, so we don't know what scope object to + * use. We could use parent (see above), but Script.prototype.exec + * might be a shared/sealed "superglobal" method. A more general + * approach would use cx->globalObject, which will be the same as + * exec.__parent__ in the non-superglobal case. In the superglobal + * case it's the right object: the global, not the superglobal. + */ + scopeobj = cx->globalObject; + } + } + + return js_Execute(cx, scopeobj, script, caller, JSFRAME_EVAL, rval); +} + +#if JS_HAS_XDR + +static JSBool +XDRAtomListElement(JSXDRState *xdr, JSAtomListElement *ale) +{ + jsval value; + jsatomid index; + + if (xdr->mode == JSXDR_ENCODE) + value = ATOM_KEY(ALE_ATOM(ale)); + + index = ALE_INDEX(ale); + if (!JS_XDRUint32(xdr, &index)) + return JS_FALSE; + ALE_SET_INDEX(ale, index); + + if (!JS_XDRValue(xdr, &value)) + return JS_FALSE; + + if (xdr->mode == JSXDR_DECODE) { + if (!ALE_SET_ATOM(ale, js_AtomizeValue(xdr->cx, value, 0))) + return JS_FALSE; + } + return JS_TRUE; +} + +static JSBool +XDRAtomMap(JSXDRState *xdr, JSAtomMap *map) +{ + uint32 length; + uintN i; + JSBool ok; + + if (xdr->mode == JSXDR_ENCODE) + length = map->length; + + if (!JS_XDRUint32(xdr, &length)) + return JS_FALSE; + + if (xdr->mode == JSXDR_DECODE) { + JSContext *cx; + void *mark; + JSAtomList al; + JSAtomListElement *ale; + + cx = xdr->cx; + mark = JS_ARENA_MARK(&cx->tempPool); + ATOM_LIST_INIT(&al); + for (i = 0; i < length; i++) { + JS_ARENA_ALLOCATE_TYPE(ale, JSAtomListElement, &cx->tempPool); + if (!ale || + !XDRAtomListElement(xdr, ale)) { + if (!ale) + JS_ReportOutOfMemory(cx); + JS_ARENA_RELEASE(&cx->tempPool, mark); + return JS_FALSE; + } + ALE_SET_NEXT(ale, al.list); + al.count++; + al.list = ale; + } + ok = js_InitAtomMap(cx, map, &al); + JS_ARENA_RELEASE(&cx->tempPool, mark); + return ok; + } + + if (xdr->mode == JSXDR_ENCODE) { + JSAtomListElement ale; + + for (i = 0; i < map->length; i++) { + ALE_SET_ATOM(&ale, map->vector[i]); + ALE_SET_INDEX(&ale, i); + if (!XDRAtomListElement(xdr, &ale)) + return JS_FALSE; + } + } + return JS_TRUE; +} + +JSBool +js_XDRScript(JSXDRState *xdr, JSScript **scriptp, JSBool *hasMagic) +{ + JSContext *cx; + JSScript *script, *newscript; + uint32 length, lineno, depth, magic, nsrcnotes, ntrynotes; + uint32 prologLength, version; + JSBool filenameWasSaved; + jssrcnote *notes, *sn; + + cx = xdr->cx; + script = *scriptp; + nsrcnotes = ntrynotes = 0; + filenameWasSaved = JS_FALSE; + notes = NULL; + + /* + * Encode prologLength and version after script->length (_2 or greater), + * but decode both new (>= _2) and old, prolog&version-free (_1) scripts. + * Version _3 supports principals serialization. Version _4 reorders the + * nsrcnotes and ntrynotes fields to come before everything except magic, + * length, prologLength, and version, so that srcnote and trynote storage + * can be allocated as part of the JSScript (along with bytecode storage). + */ + if (xdr->mode == JSXDR_ENCODE) + magic = JSXDR_MAGIC_SCRIPT_CURRENT; + if (!JS_XDRUint32(xdr, &magic)) + return JS_FALSE; + if (magic != JSXDR_MAGIC_SCRIPT_4 && + magic != JSXDR_MAGIC_SCRIPT_3 && + magic != JSXDR_MAGIC_SCRIPT_2 && + magic != JSXDR_MAGIC_SCRIPT_1) { + if (!hasMagic) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_SCRIPT_MAGIC); + return JS_FALSE; + } + *hasMagic = JS_FALSE; + return JS_TRUE; + } + if (hasMagic) + *hasMagic = JS_TRUE; + + if (xdr->mode == JSXDR_ENCODE) { + length = script->length; + prologLength = PTRDIFF(script->main, script->code, jsbytecode); + version = (int32)script->version; + lineno = (uint32)script->lineno; + depth = (uint32)script->depth; + + /* Count the srcnotes, keeping notes pointing at the first one. */ + notes = SCRIPT_NOTES(script); + for (sn = notes; !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) + continue; + nsrcnotes = PTRDIFF(sn, notes, jssrcnote); + nsrcnotes++; /* room for the terminator */ + + /* Count the trynotes. */ + if (script->trynotes) { + while (script->trynotes[ntrynotes].catchStart) + ntrynotes++; + ntrynotes++; /* room for the end marker */ + } + } + + if (!JS_XDRUint32(xdr, &length)) + return JS_FALSE; + if (magic >= JSXDR_MAGIC_SCRIPT_2) { + if (!JS_XDRUint32(xdr, &prologLength)) + return JS_FALSE; + if (!JS_XDRUint32(xdr, &version)) + return JS_FALSE; + + /* To fuse allocations, we need srcnote and trynote counts early. */ + if (magic >= JSXDR_MAGIC_SCRIPT_4) { + if (!JS_XDRUint32(xdr, &nsrcnotes)) + return JS_FALSE; + if (!JS_XDRUint32(xdr, &ntrynotes)) + return JS_FALSE; + } + } + + if (xdr->mode == JSXDR_DECODE) { + script = js_NewScript(cx, length, nsrcnotes, ntrynotes); + if (!script) + return JS_FALSE; + if (magic >= JSXDR_MAGIC_SCRIPT_2) { + script->main += prologLength; + script->version = (JSVersion) version; + + /* If we know nsrcnotes, we allocated space for notes in script. */ + if (magic >= JSXDR_MAGIC_SCRIPT_4) + notes = SCRIPT_NOTES(script); + } + *scriptp = script; + } + + /* + * Control hereafter must goto error on failure, in order for the DECODE + * case to destroy script and conditionally free notes, which if non-null + * in the (DECODE and version < _4) case must point at a temporary vector + * allocated just below. + */ + if (!JS_XDRBytes(xdr, (char *)script->code, length * sizeof(jsbytecode)) || + !XDRAtomMap(xdr, &script->atomMap)) { + goto error; + } + + if (magic < JSXDR_MAGIC_SCRIPT_4) { + if (!JS_XDRUint32(xdr, &nsrcnotes)) + goto error; + if (xdr->mode == JSXDR_DECODE) { + notes = (jssrcnote *) JS_malloc(cx, nsrcnotes * sizeof(jssrcnote)); + if (!notes) + goto error; + } + } + + if (!JS_XDRBytes(xdr, (char *)notes, nsrcnotes * sizeof(jssrcnote)) || + !JS_XDRCStringOrNull(xdr, (char **)&script->filename) || + !JS_XDRUint32(xdr, &lineno) || + !JS_XDRUint32(xdr, &depth) || + (magic < JSXDR_MAGIC_SCRIPT_4 && !JS_XDRUint32(xdr, &ntrynotes))) { + goto error; + } + + /* Script principals transcoding support comes with versions >= _3. */ + if (magic >= JSXDR_MAGIC_SCRIPT_3) { + JSPrincipals *principals; + uint32 encodeable; + + if (xdr->mode == JSXDR_ENCODE) { + principals = script->principals; + encodeable = (cx->runtime->principalsTranscoder != NULL); + if (!JS_XDRUint32(xdr, &encodeable)) + goto error; + if (encodeable && + !cx->runtime->principalsTranscoder(xdr, &principals)) { + goto error; + } + } else { + if (!JS_XDRUint32(xdr, &encodeable)) + goto error; + if (encodeable) { + if (!cx->runtime->principalsTranscoder) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_CANT_DECODE_PRINCIPALS); + goto error; + } + if (!cx->runtime->principalsTranscoder(xdr, &principals)) + goto error; + script->principals = principals; + } + } + } + + if (xdr->mode == JSXDR_DECODE) { + const char *filename = script->filename; + if (filename) { + filename = js_SaveScriptFilename(cx, filename); + if (!filename) + goto error; + JS_free(cx, (void *) script->filename); + script->filename = filename; + filenameWasSaved = JS_TRUE; + } + script->lineno = (uintN)lineno; + script->depth = (uintN)depth; + + if (magic < JSXDR_MAGIC_SCRIPT_4) { + /* + * Argh, we have to reallocate script, copy notes into the extra + * space after the bytecodes, and free the temporary notes vector. + * First, add enough slop to nsrcnotes so we can align the address + * after the srcnotes of the first trynote. + */ + uint32 osrcnotes = nsrcnotes; + + if (ntrynotes) + nsrcnotes += JSTRYNOTE_ALIGNMASK; + newscript = (JSScript *) JS_realloc(cx, script, + sizeof(JSScript) + + length * sizeof(jsbytecode) + + nsrcnotes * sizeof(jssrcnote) + + ntrynotes * sizeof(JSTryNote)); + if (!newscript) + goto error; + + *scriptp = script = newscript; + script->code = (jsbytecode *)(script + 1); + script->main = script->code + prologLength; + memcpy(script->code + length, notes, osrcnotes * sizeof(jssrcnote)); + JS_free(cx, (void *) notes); + notes = NULL; + if (ntrynotes) { + script->trynotes = (JSTryNote *) + ((jsword)(SCRIPT_NOTES(script) + nsrcnotes) & + ~(jsword)JSTRYNOTE_ALIGNMASK); + } + } + } + + while (ntrynotes) { + JSTryNote *tn = &script->trynotes[--ntrynotes]; + uint32 start = (uint32) tn->start, + catchLength = (uint32) tn->length, + catchStart = (uint32) tn->catchStart; + + if (!JS_XDRUint32(xdr, &start) || + !JS_XDRUint32(xdr, &catchLength) || + !JS_XDRUint32(xdr, &catchStart)) { + goto error; + } + tn->start = (ptrdiff_t) start; + tn->length = (ptrdiff_t) catchLength; + tn->catchStart = (ptrdiff_t) catchStart; + } + return JS_TRUE; + + error: + if (xdr->mode == JSXDR_DECODE) { + if (script->filename && !filenameWasSaved) { + JS_free(cx, (void *) script->filename); + script->filename = NULL; + } + if (notes && magic < JSXDR_MAGIC_SCRIPT_4) + JS_free(cx, (void *) notes); + js_DestroyScript(cx, script); + *scriptp = NULL; + } + return JS_FALSE; +} + +#if JS_HAS_XDR_FREEZE_THAW +/* + * These cannot be exposed to web content, and chrome does not need them, so + * we take them out of the Mozilla client altogether. Fortunately, there is + * no way to serialize a native function (see fun_xdrObject in jsfun.c). + */ + +static JSBool +script_freeze(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSXDRState *xdr; + JSScript *script; + JSBool ok, hasMagic; + uint32 len; + void *buf; + JSString *str; + + if (!JS_InstanceOf(cx, obj, &js_ScriptClass, argv)) + return JS_FALSE; + script = (JSScript *) JS_GetPrivate(cx, obj); + if (!script) + return JS_TRUE; + + /* create new XDR */ + xdr = JS_XDRNewMem(cx, JSXDR_ENCODE); + if (!xdr) + return JS_FALSE; + + /* write */ + ok = js_XDRScript(xdr, &script, &hasMagic); + if (!ok) + goto out; + if (!hasMagic) { + *rval = JSVAL_VOID; + goto out; + } + + buf = JS_XDRMemGetData(xdr, &len); + if (!buf) { + ok = JS_FALSE; + goto out; + } + + JS_ASSERT((jsword)buf % sizeof(jschar) == 0); + len /= sizeof(jschar); + str = JS_NewUCStringCopyN(cx, (jschar *)buf, len); + if (!str) { + ok = JS_FALSE; + goto out; + } + +#if IS_BIG_ENDIAN + { + jschar *chars; + uint32 i; + + /* Swap bytes in Unichars to keep frozen strings machine-independent. */ + chars = JS_GetStringChars(str); + for (i = 0; i < len; i++) + chars[i] = JSXDR_SWAB16(chars[i]); + } +#endif + *rval = STRING_TO_JSVAL(str); + +out: + JS_XDRDestroy(xdr); + return ok; +} + +static JSBool +script_thaw(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSXDRState *xdr; + JSString *str; + void *buf; + uint32 len; + JSScript *script, *oldscript; + JSBool ok, hasMagic; + + if (!JS_InstanceOf(cx, obj, &js_ScriptClass, argv)) + return JS_FALSE; + + if (argc == 0) + return JS_TRUE; + str = js_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + + /* create new XDR */ + xdr = JS_XDRNewMem(cx, JSXDR_DECODE); + if (!xdr) + return JS_FALSE; + + buf = JS_GetStringChars(str); + len = JS_GetStringLength(str); +#if IS_BIG_ENDIAN + { + jschar *from, *to; + uint32 i; + + /* Swap bytes in Unichars to keep frozen strings machine-independent. */ + from = (jschar *)buf; + to = (jschar *) JS_malloc(cx, len * sizeof(jschar)); + if (!to) { + JS_XDRDestroy(xdr); + return JS_FALSE; + } + for (i = 0; i < len; i++) + to[i] = JSXDR_SWAB16(from[i]); + buf = (char *)to; + } +#endif + len *= sizeof(jschar); + JS_XDRMemSetData(xdr, buf, len); + + /* XXXbe should magic mismatch be error, or false return value? */ + ok = js_XDRScript(xdr, &script, &hasMagic); + if (!ok) + goto out; + if (!hasMagic) { + *rval = JSVAL_FALSE; + goto out; + } + + /* Swap script for obj's old script, if any. */ + oldscript = (JSScript *) JS_GetPrivate(cx, obj); + ok = JS_SetPrivate(cx, obj, script); + if (!ok) { + JS_free(cx, script); + goto out; + } + if (oldscript) + js_DestroyScript(cx, oldscript); + + script->object = obj; + js_CallNewScriptHook(cx, script, NULL); + +out: + /* + * We reset the buffer to be NULL so that it doesn't free the chars + * memory owned by str (argv[0]). + */ + JS_XDRMemSetData(xdr, NULL, 0); + JS_XDRDestroy(xdr); +#if IS_BIG_ENDIAN + JS_free(cx, buf); +#endif + *rval = JSVAL_TRUE; + return ok; +} + +static const char js_thaw_str[] = "thaw"; + +#endif /* JS_HAS_XDR_FREEZE_THAW */ +#endif /* JS_HAS_XDR */ + +static JSFunctionSpec script_methods[] = { +#if JS_HAS_TOSOURCE + {js_toSource_str, script_toSource, 0,0,0}, +#endif + {js_toString_str, script_toString, 0,0,0}, + {"compile", script_compile, 2,0,0}, + {"exec", script_exec, 1,0,0}, +#if JS_HAS_XDR_FREEZE_THAW + {"freeze", script_freeze, 0,0,0}, + {js_thaw_str, script_thaw, 1,0,0}, +#endif /* JS_HAS_XDR_FREEZE_THAW */ + {0,0,0,0,0} +}; + +#endif /* JS_HAS_SCRIPT_OBJECT */ + +static void +script_finalize(JSContext *cx, JSObject *obj) +{ + JSScript *script; + + script = (JSScript *) JS_GetPrivate(cx, obj); + if (script) + js_DestroyScript(cx, script); +} + +static JSBool +script_call(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ +#if JS_HAS_SCRIPT_OBJECT + return script_exec(cx, JSVAL_TO_OBJECT(argv[-2]), argc, argv, rval); +#else + return JS_FALSE; +#endif +} + +static uint32 +script_mark(JSContext *cx, JSObject *obj, void *arg) +{ + JSScript *script; + + script = (JSScript *) JS_GetPrivate(cx, obj); + if (script) + js_MarkScript(cx, script, arg); + return 0; +} + +JS_FRIEND_DATA(JSClass) js_ScriptClass = { + js_Script_str, + JSCLASS_HAS_PRIVATE, + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, script_finalize, + NULL, NULL, script_call, NULL,/*XXXbe xdr*/ + NULL, NULL, script_mark, 0 +}; + +#if JS_HAS_SCRIPT_OBJECT + +static JSBool +Script(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + /* If not constructing, replace obj with a new Script object. */ + if (!(cx->fp->flags & JSFRAME_CONSTRUCTING)) { + obj = js_NewObject(cx, &js_ScriptClass, NULL, NULL); + if (!obj) + return JS_FALSE; + } + return script_compile(cx, obj, argc, argv, rval); +} + +#if JS_HAS_XDR_FREEZE_THAW + +static JSBool +script_static_thaw(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + obj = js_NewObject(cx, &js_ScriptClass, NULL, NULL); + if (!obj) + return JS_FALSE; + if (!script_thaw(cx, obj, argc, argv, rval)) + return JS_FALSE; + *rval = OBJECT_TO_JSVAL(obj); + return JS_TRUE; +} + +static JSFunctionSpec script_static_methods[] = { + {js_thaw_str, script_static_thaw, 1,0,0}, + {0,0,0,0,0} +}; + +#else /* !JS_HAS_XDR_FREEZE_THAW */ + +#define script_static_methods NULL + +#endif /* !JS_HAS_XDR_FREEZE_THAW */ + +JSObject * +js_InitScriptClass(JSContext *cx, JSObject *obj) +{ + return JS_InitClass(cx, obj, NULL, &js_ScriptClass, Script, 1, + NULL, script_methods, NULL, script_static_methods); +} + +#endif /* JS_HAS_SCRIPT_OBJECT */ + +/* + * Shared script filename management. + */ +static JSHashTable *script_filename_table; +#ifdef JS_THREADSAFE +static JSLock *script_filename_table_lock; +#endif + +JS_STATIC_DLL_CALLBACK(int) +js_compare_strings(const void *k1, const void *k2) +{ + return strcmp(k1, k2) == 0; +} + +/* Shared with jsatom.c to save code space. */ +extern void * JS_DLL_CALLBACK +js_alloc_table_space(void *priv, size_t size); + +extern void JS_DLL_CALLBACK +js_free_table_space(void *priv, void *item); + +/* NB: This struct overlays JSHashEntry -- see jshash.h, do not reorganize. */ +typedef struct ScriptFilenameEntry { + JSHashEntry *next; /* hash chain linkage */ + JSHashNumber keyHash; /* key hash function result */ + const void *key; /* ptr to filename, below */ + JSPackedBool mark; /* mark flag, for GC */ + char filename[3]; /* two or more bytes, NUL-terminated */ +} ScriptFilenameEntry; + +JS_STATIC_DLL_CALLBACK(JSHashEntry *) +js_alloc_entry(void *priv, const void *key) +{ + size_t nbytes = offsetof(ScriptFilenameEntry, filename) + strlen(key) + 1; + + return (JSHashEntry *) malloc(JS_MAX(nbytes, sizeof(JSHashEntry))); +} + +JS_STATIC_DLL_CALLBACK(void) +js_free_entry(void *priv, JSHashEntry *he, uintN flag) +{ + if (flag != HT_FREE_ENTRY) + return; + free(he); +} + +static JSHashAllocOps table_alloc_ops = { + js_alloc_table_space, js_free_table_space, + js_alloc_entry, js_free_entry +}; + +JSBool +js_InitScriptGlobals() +{ +#ifdef JS_THREADSAFE + /* Must come through here once in primordial thread to init safely! */ + if (!script_filename_table_lock) { + script_filename_table_lock = JS_NEW_LOCK(); + if (!script_filename_table_lock) + return JS_FALSE; + } +#endif + if (!script_filename_table) { + JS_ACQUIRE_LOCK(script_filename_table_lock); + if (!script_filename_table) { + script_filename_table = + JS_NewHashTable(16, JS_HashString, js_compare_strings, NULL, + &table_alloc_ops, NULL); + } + JS_RELEASE_LOCK(script_filename_table_lock); + if (!script_filename_table) + return JS_FALSE; + } + return JS_TRUE; +} + +void +js_FreeScriptGlobals() +{ + if (script_filename_table) { + JS_HashTableDestroy(script_filename_table); + script_filename_table = NULL; + } +#ifdef JS_THREADSAFE + if (script_filename_table_lock) { + JS_DESTROY_LOCK(script_filename_table_lock); + script_filename_table_lock = NULL; + } +#endif +} + +#ifdef DEBUG_brendan +size_t sft_savings = 0; +#endif + +const char * +js_SaveScriptFilename(JSContext *cx, const char *filename) +{ + JSHashTable *table; + JSHashNumber hash; + JSHashEntry **hep; + ScriptFilenameEntry *sfe; + + JS_ACQUIRE_LOCK(script_filename_table_lock); + table = script_filename_table; + hash = JS_HashString(filename); + hep = JS_HashTableRawLookup(table, hash, filename); + sfe = (ScriptFilenameEntry *) *hep; +#ifdef DEBUG_brendan + if (sfe) + sft_savings += strlen(sfe->filename); +#endif + if (!sfe) { + sfe = (ScriptFilenameEntry *) + JS_HashTableRawAdd(table, hep, hash, filename, NULL); + if (sfe) { + sfe->key = strcpy(sfe->filename, filename); + JS_ASSERT(!sfe->mark); + } + } + JS_RELEASE_LOCK(script_filename_table_lock); + if (!sfe) { + JS_ReportOutOfMemory(cx); + return NULL; + } + return sfe->filename; +} + +void +js_MarkScriptFilename(const char *filename) +{ + ScriptFilenameEntry *sfe; + + /* + * Back up from filename by its offset within its hash table entry. + * The sfe->key member, redundant given sfe->filename but required by + * the old jshash.c code, here gives us a useful sanity check. This + * assertion will very likely botch if someone tries to mark a string + * that wasn't allocated as an sfe->filename. + */ + sfe = (ScriptFilenameEntry *) + (filename - offsetof(ScriptFilenameEntry, filename)); + JS_ASSERT(sfe->key == sfe->filename); + sfe->mark = JS_TRUE; +} + +JS_STATIC_DLL_CALLBACK(intN) +js_script_filename_sweeper(JSHashEntry *he, intN i, void *arg) +{ + ScriptFilenameEntry *sfe = (ScriptFilenameEntry *) he; + + if (!sfe->mark) + return HT_ENUMERATE_REMOVE; + sfe->mark = JS_FALSE; + return HT_ENUMERATE_NEXT; +} + +void +js_SweepScriptFilenames(JSRuntime *rt) +{ + JS_HashTableEnumerateEntries(script_filename_table, + js_script_filename_sweeper, + rt); +#ifdef DEBUG_brendan + printf("script filename table savings so far: %u\n", sft_savings); +#endif +} + +JSScript * +js_NewScript(JSContext *cx, uint32 length, uint32 nsrcnotes, uint32 ntrynotes) +{ + JSScript *script; + + /* Round up source note count to align script->trynotes for its type. */ + if (ntrynotes) + nsrcnotes += JSTRYNOTE_ALIGNMASK; + script = (JSScript *) JS_malloc(cx, + sizeof(JSScript) + + length * sizeof(jsbytecode) + + nsrcnotes * sizeof(jssrcnote) + + ntrynotes * sizeof(JSTryNote)); + if (!script) + return NULL; + memset(script, 0, sizeof(JSScript)); + script->code = script->main = (jsbytecode *)(script + 1); + script->length = length; + script->version = cx->version; + if (ntrynotes) { + script->trynotes = (JSTryNote *) + ((jsword)(SCRIPT_NOTES(script) + nsrcnotes) & + ~(jsword)JSTRYNOTE_ALIGNMASK); + } + return script; +} + +JS_FRIEND_API(JSScript *) +js_NewScriptFromCG(JSContext *cx, JSCodeGenerator *cg, JSFunction *fun) +{ + uint32 mainLength, prologLength, nsrcnotes, ntrynotes; + JSScript *script; + const char *filename; + + mainLength = CG_OFFSET(cg); + prologLength = CG_PROLOG_OFFSET(cg); + CG_COUNT_FINAL_SRCNOTES(cg, nsrcnotes); + CG_COUNT_FINAL_TRYNOTES(cg, ntrynotes); + script = js_NewScript(cx, prologLength + mainLength, nsrcnotes, ntrynotes); + if (!script) + return NULL; + + /* Now that we have script, error control flow must go to label bad. */ + script->main += prologLength; + memcpy(script->code, CG_PROLOG_BASE(cg), prologLength * sizeof(jsbytecode)); + memcpy(script->main, CG_BASE(cg), mainLength * sizeof(jsbytecode)); + if (!js_InitAtomMap(cx, &script->atomMap, &cg->atomList)) + goto bad; + + filename = cg->filename; + if (filename) { + script->filename = js_SaveScriptFilename(cx, filename); + if (!script->filename) + goto bad; + } + script->lineno = cg->firstLine; + script->depth = cg->maxStackDepth; + if (cg->principals) { + script->principals = cg->principals; + JSPRINCIPALS_HOLD(cx, script->principals); + } + + if (!js_FinishTakingSrcNotes(cx, cg, SCRIPT_NOTES(script))) + goto bad; + if (script->trynotes) + js_FinishTakingTryNotes(cx, cg, script->trynotes); + + /* Tell the debugger about this compiled script. */ + js_CallNewScriptHook(cx, script, fun); + return script; + +bad: + js_DestroyScript(cx, script); + return NULL; +} + +JS_FRIEND_API(void) +js_CallNewScriptHook(JSContext *cx, JSScript *script, JSFunction *fun) +{ + JSRuntime *rt; + JSNewScriptHook hook; + + rt = cx->runtime; + hook = rt->newScriptHook; + if (hook) { + /* + * We use a dummy stack frame to protect the script from a GC caused + * by debugger-hook execution. + * + * XXX We really need a way to manage local roots and such more + * XXX automatically, at which point we can remove this one-off hack + * XXX and others within the engine. See bug 40757 for discussion. + */ + JSStackFrame dummy; + + memset(&dummy, 0, sizeof dummy); + dummy.down = cx->fp; + dummy.script = script; + cx->fp = &dummy; + + hook(cx, script->filename, script->lineno, script, fun, + rt->newScriptHookData); + + cx->fp = dummy.down; + } +} + +void +js_DestroyScript(JSContext *cx, JSScript *script) +{ + JSRuntime *rt; + JSDestroyScriptHook hook; + + rt = cx->runtime; + hook = rt->destroyScriptHook; + if (hook) + hook(cx, script, rt->destroyScriptHookData); + + JS_ClearScriptTraps(cx, script); + js_FreeAtomMap(cx, &script->atomMap); + if (script->principals) + JSPRINCIPALS_DROP(cx, script->principals); + JS_free(cx, script); +} + +void +js_MarkScript(JSContext *cx, JSScript *script, void *arg) +{ + JSAtomMap *map; + uintN i, length; + JSAtom **vector; + + map = &script->atomMap; + length = map->length; + vector = map->vector; + for (i = 0; i < length; i++) + GC_MARK_ATOM(cx, vector[i], arg); + + if (script->filename) + js_MarkScriptFilename(script->filename); +} + +jssrcnote * +js_GetSrcNote(JSScript *script, jsbytecode *pc) +{ + jssrcnote *sn; + ptrdiff_t offset, target; + + target = PTRDIFF(pc, script->code, jsbytecode); + if ((uint32)target >= script->length) + return NULL; + offset = 0; + for (sn = SCRIPT_NOTES(script); !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) { + offset += SN_DELTA(sn); + if (offset == target && SN_IS_GETTABLE(sn)) + return sn; + } + return NULL; +} + +uintN +js_PCToLineNumber(JSContext *cx, JSScript *script, jsbytecode *pc) +{ + JSAtom *atom; + JSFunction *fun; + uintN lineno; + ptrdiff_t offset, target; + jssrcnote *sn; + JSSrcNoteType type; + + /* + * Special case: function definition needs no line number note because + * the function's script contains its starting line number. + */ + if (*pc == JSOP_DEFFUN) { + atom = GET_ATOM(cx, script, pc); + fun = (JSFunction *) JS_GetPrivate(cx, ATOM_TO_OBJECT(atom)); + return fun->script->lineno; + } + + /* + * General case: walk through source notes accumulating their deltas, + * keeping track of line-number notes, until we pass the note for pc's + * offset within script->code. + */ + lineno = script->lineno; + offset = 0; + target = PTRDIFF(pc, script->code, jsbytecode); + for (sn = SCRIPT_NOTES(script); !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) { + offset += SN_DELTA(sn); + type = (JSSrcNoteType) SN_TYPE(sn); + if (type == SRC_SETLINE) { + if (offset <= target) + lineno = (uintN) js_GetSrcNoteOffset(sn, 0); + } else if (type == SRC_NEWLINE) { + if (offset <= target) + lineno++; + } + if (offset > target) + break; + } + return lineno; +} + +jsbytecode * +js_LineNumberToPC(JSScript *script, uintN target) +{ + ptrdiff_t offset; + uintN lineno; + jssrcnote *sn; + JSSrcNoteType type; + + offset = 0; + lineno = script->lineno; + for (sn = SCRIPT_NOTES(script); !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) { + if (lineno >= target) + break; + offset += SN_DELTA(sn); + type = (JSSrcNoteType) SN_TYPE(sn); + if (type == SRC_SETLINE) { + lineno = (uintN) js_GetSrcNoteOffset(sn, 0); + } else if (type == SRC_NEWLINE) { + lineno++; + } + } + return script->code + offset; +} + +uintN +js_GetScriptLineExtent(JSScript *script) +{ + uintN lineno; + jssrcnote *sn; + JSSrcNoteType type; + + lineno = script->lineno; + for (sn = SCRIPT_NOTES(script); !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) { + type = (JSSrcNoteType) SN_TYPE(sn); + if (type == SRC_SETLINE) { + lineno = (uintN) js_GetSrcNoteOffset(sn, 0); + } else if (type == SRC_NEWLINE) { + lineno++; + } + } + return 1 + lineno - script->lineno; +} diff --git a/src/dom/js/jsscript.h b/src/dom/js/jsscript.h new file mode 100644 index 000000000..ffcc40de7 --- /dev/null +++ b/src/dom/js/jsscript.h @@ -0,0 +1,178 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsscript_h___ +#define jsscript_h___ +/* + * JS script descriptor. + */ +#include "jsatom.h" +#include "jsprvtd.h" + +JS_BEGIN_EXTERN_C + +/* + * Exception handling runtime information. + * + * All fields except length are code offsets, relative to the beginning of + * the script. If script->trynotes is not null, it points to a vector of + * these structs terminated by one with catchStart == 0. + */ +struct JSTryNote { + ptrdiff_t start; /* start of try statement */ + ptrdiff_t length; /* count of try statement bytecodes */ + ptrdiff_t catchStart; /* start of catch block (0 if end) */ +}; + +#define JSTRYNOTE_GRAIN sizeof(ptrdiff_t) +#define JSTRYNOTE_ALIGNMASK (JSTRYNOTE_GRAIN - 1) + +struct JSScript { + jsbytecode *code; /* bytecodes and their immediate operands */ + uint32 length; /* length of code vector */ + jsbytecode *main; /* main entry point, after predef'ing prolog */ + JSVersion version; /* JS version under which script was compiled */ + JSAtomMap atomMap; /* maps immediate index to literal struct */ + const char *filename; /* source filename or null */ + uintN lineno; /* base line number of script */ + uintN depth; /* maximum stack depth in slots */ + JSTryNote *trynotes; /* exception table for this script */ + JSPrincipals *principals; /* principals for this script */ + JSObject *object; /* optional Script-class object wrapper */ +}; + +/* No need to store script->notes now that it is allocated right after code. */ +#define SCRIPT_NOTES(script) ((jssrcnote*)((script)->code+(script)->length)) + +#define SCRIPT_FIND_CATCH_START(script, pc, catchpc) \ + JS_BEGIN_MACRO \ + JSTryNote *tn_ = (script)->trynotes; \ + jsbytecode *catchpc_ = NULL; \ + if (tn_) { \ + ptrdiff_t off_ = PTRDIFF(pc, (script)->main, jsbytecode); \ + if (off_ >= 0) { \ + while ((jsuword)(off_ - tn_->start) >= (jsuword)tn_->length) \ + ++tn_; \ + if (tn_->catchStart) \ + catchpc_ = (script)->main + tn_->catchStart; \ + } \ + } \ + catchpc = catchpc_; \ + JS_END_MACRO + +extern JS_FRIEND_DATA(JSClass) js_ScriptClass; + +extern JSObject * +js_InitScriptClass(JSContext *cx, JSObject *obj); + +extern JSBool +js_InitScriptGlobals(); + +extern void +js_FreeScriptGlobals(); + +extern const char * +js_SaveScriptFilename(JSContext *cx, const char *filename); + +extern void +js_MarkScriptFilename(const char *filename); + +extern void +js_SweepScriptFilenames(JSRuntime *rt); + +/* + * Two successively less primitive ways to make a new JSScript. The first + * does *not* call a non-null cx->runtime->newScriptHook -- only the second, + * js_NewScriptFromCG, calls this optional debugger hook. + * + * The js_NewScript function can't know whether the script it creates belongs + * to a function, or is top-level or eval code, but the debugger wants access + * to the newly made script's function, if any -- so callers of js_NewScript + * are responsible for notifying the debugger after successfully creating any + * kind (function or other) of new JSScript. + */ +extern JSScript * +js_NewScript(JSContext *cx, uint32 length, uint32 snlength, uint32 tnlength); + +extern JS_FRIEND_API(JSScript *) +js_NewScriptFromCG(JSContext *cx, JSCodeGenerator *cg, JSFunction *fun); + +/* + * New-script-hook calling is factored from js_NewScriptFromCG so that it + * and callers of js_XDRScript can share this code. In the case of callers + * of js_XDRScript, the hook should be invoked only after successful decode + * of any owning function (the fun parameter) or script object (null fun). + */ +extern JS_FRIEND_API(void) +js_CallNewScriptHook(JSContext *cx, JSScript *script, JSFunction *fun); + +extern void +js_DestroyScript(JSContext *cx, JSScript *script); + +extern void +js_MarkScript(JSContext *cx, JSScript *script, void *arg); + +extern jssrcnote * +js_GetSrcNote(JSScript *script, jsbytecode *pc); + +/* XXX need cx to lock function objects declared by prolog bytecodes. */ +extern uintN +js_PCToLineNumber(JSContext *cx, JSScript *script, jsbytecode *pc); + +extern jsbytecode * +js_LineNumberToPC(JSScript *script, uintN lineno); + +extern uintN +js_GetScriptLineExtent(JSScript *script); + +/* + * If magic is non-null, js_XDRScript succeeds on magic number mismatch but + * returns false in *magic; it reflects a match via a true *magic out param. + * If magic is null, js_XDRScript returns false on bad magic number errors, + * which it reports. + * + * NB: callers must call js_CallNewScriptHook after successful JSXDR_DECODE + * and subsequent set-up of owning function or script object, if any. + */ +extern JSBool +js_XDRScript(JSXDRState *xdr, JSScript **scriptp, JSBool *magic); + +JS_END_EXTERN_C + +#endif /* jsscript_h___ */ diff --git a/src/dom/js/jsshell.msg b/src/dom/js/jsshell.msg new file mode 100644 index 000000000..4b811ac01 --- /dev/null +++ b/src/dom/js/jsshell.msg @@ -0,0 +1,50 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + Error messages for JSShell. See js.msg for format. +*/ + +MSG_DEF(JSSMSG_NOT_AN_ERROR, 0, 0, JSEXN_NONE, "") +MSG_DEF(JSSMSG_CANT_OPEN, 1, 2, JSEXN_NONE, "can't open {0}: {1}") +MSG_DEF(JSSMSG_TRAP_USAGE, 2, 0, JSEXN_NONE, "usage: trap [fun] [pc] expr") +MSG_DEF(JSSMSG_LINE2PC_USAGE, 3, 0, JSEXN_NONE, "usage: line2pc [fun] line") +MSG_DEF(JSSMSG_FILE_SCRIPTS_ONLY, 4, 0, JSEXN_NONE, "only works on JS scripts read from files") +MSG_DEF(JSSMSG_UNEXPECTED_EOF, 5, 1, JSEXN_NONE, "unexpected EOF in {0}") +MSG_DEF(JSSMSG_DOEXP_USAGE, 6, 0, JSEXN_NONE, "usage: doexp obj id") diff --git a/src/dom/js/jsstddef.h b/src/dom/js/jsstddef.h new file mode 100644 index 000000000..0d87b0c0c --- /dev/null +++ b/src/dom/js/jsstddef.h @@ -0,0 +1,83 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * stddef inclusion here to first declare ptrdif as a signed long instead of a + * signed int. + */ + +#ifdef _WINDOWS +# ifndef XP_WIN +# define XP_WIN +# endif +#if defined(_WIN32) || defined(WIN32) +# ifndef XP_WIN32 +# define XP_WIN32 +# endif +#else +# ifndef XP_WIN16 +# define XP_WIN16 +# endif +#endif +#endif + +#ifdef XP_WIN16 +#ifndef _PTRDIFF_T_DEFINED +typedef long ptrdiff_t; + +/* + * The Win16 compiler treats pointer differences as 16-bit signed values. + * This macro allows us to treat them as 17-bit signed values, stored in + * a 32-bit type. + */ +#define PTRDIFF(p1, p2, type) \ + ((((unsigned long)(p1)) - ((unsigned long)(p2))) / sizeof(type)) + +#define _PTRDIFF_T_DEFINED +#endif /*_PTRDIFF_T_DEFINED*/ +#else /*WIN16*/ + +#define PTRDIFF(p1, p2, type) \ + ((p1) - (p2)) + +#endif + +#include + + diff --git a/src/dom/js/jsstr.c b/src/dom/js/jsstr.c new file mode 100644 index 000000000..e143ab8df --- /dev/null +++ b/src/dom/js/jsstr.c @@ -0,0 +1,4502 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS string type implementation. + * + * In order to avoid unnecessary js_LockGCThing/js_UnlockGCThing calls, these + * native methods store strings (possibly newborn) converted from their 'this' + * parameter and arguments on the stack: 'this' conversions at argv[-1], arg + * conversions at their index (argv[0], argv[1]). This is a legitimate method + * of rooting things that might lose their newborn root due to subsequent GC + * allocations in the same native method. + */ +#include "jsstddef.h" +#include +#include +#include "jstypes.h" +#include "jsutil.h" /* Added by JSIFY */ +#include "jshash.h" /* Added by JSIFY */ +#include "jsprf.h" +#include "jsapi.h" +#include "jsarray.h" +#include "jsatom.h" +#include "jsbool.h" +#include "jscntxt.h" +#include "jsconfig.h" +#include "jsgc.h" +#include "jsinterp.h" +#include "jslock.h" +#include "jsnum.h" +#include "jsobj.h" +#include "jsopcode.h" +#include "jsregexp.h" +#include "jsstr.h" + +#if JS_HAS_REPLACE_LAMBDA +#include "jsinterp.h" +#endif + +#define JSSTRDEP_RECURSION_LIMIT 100 + +size_t +js_MinimizeDependentStrings(JSString *str, int level, JSString **basep) +{ + JSString *base; + size_t start, length; + + JS_ASSERT(JSSTRING_IS_DEPENDENT(str)); + base = JSSTRDEP_BASE(str); + start = JSSTRDEP_START(str); + if (JSSTRING_IS_DEPENDENT(base)) { + if (level < JSSTRDEP_RECURSION_LIMIT) { + start += js_MinimizeDependentStrings(base, level + 1, &base); + } else { + do { + start += JSSTRDEP_START(base); + base = JSSTRDEP_BASE(base); + } while (JSSTRING_IS_DEPENDENT(base)); + } + if (start == 0) { + JS_ASSERT(JSSTRING_IS_PREFIX(str)); + JSPREFIX_SET_BASE(str, base); + } else if (start <= JSSTRDEP_START_MASK) { + length = JSSTRDEP_LENGTH(str); + JSSTRDEP_SET_START_AND_LENGTH(str, start, length); + JSSTRDEP_SET_BASE(str, base); + } + } + *basep = base; + return start; +} + +jschar * +js_GetDependentStringChars(JSString *str) +{ + size_t start; + JSString *base; + + start = js_MinimizeDependentStrings(str, 0, &base); + JS_ASSERT(!JSSTRING_IS_DEPENDENT(base)); + JS_ASSERT(start < base->length); + return base->chars + start; +} + +jschar * +js_GetStringChars(JSString *str) +{ + if (JSSTRING_IS_DEPENDENT(str) && !js_UndependString(NULL, str)) + return NULL; + + *js_GetGCThingFlags(str) &= ~GCF_MUTABLE; + return str->chars; +} + +JSString * +js_ConcatStrings(JSContext *cx, JSString *left, JSString *right) +{ + size_t rn, ln, lrdist, n; + jschar *rs, *ls, *s; + JSDependentString *ldep; /* non-null if left should become dependent */ + JSString *str; + + if (JSSTRING_IS_DEPENDENT(right)) { + rn = JSSTRDEP_LENGTH(right); + rs = JSSTRDEP_CHARS(right); + } else { + rn = right->length; + rs = right->chars; + } + if (rn == 0) + return left; + + if (JSSTRING_IS_DEPENDENT(left) || + !(*js_GetGCThingFlags(left) & GCF_MUTABLE)) { + /* We must copy if left does not own a buffer to realloc. */ + ln = JSSTRING_LENGTH(left); + if (ln == 0) + return right; + ls = JSSTRING_CHARS(left); + s = (jschar *) JS_malloc(cx, (ln + rn + 1) * sizeof(jschar)); + if (!s) + return NULL; + js_strncpy(s, ls, ln); + ldep = NULL; + } else { + /* We can realloc left's space and make it depend on our result. */ + ln = left->length; + if (ln == 0) + return right; + ls = left->chars; + s = (jschar *) JS_realloc(cx, ls, (ln + rn + 1) * sizeof(jschar)); + if (!s) + return NULL; + + /* Take care: right could depend on left! */ + lrdist = (size_t)(rs - ls); + if (lrdist < ln) + rs = s + lrdist; + left->chars = ls = s; + ldep = JSSTRDEP(left); + } + + js_strncpy(s + ln, rs, rn); + n = ln + rn; + s[n] = 0; + str = js_NewString(cx, s, n, GCF_MUTABLE); + if (!str) { + /* Out of memory: clean up any space we (re-)allocated. */ + if (!ldep) { + JS_free(cx, s); + } else { + s = JS_realloc(cx, ls, (ln + 1) * sizeof(jschar)); + if (s) + left->chars = s; + } + } else { + /* Morph left into a dependent prefix if we realloc'd its buffer. */ + if (ldep) { + JSPREFIX_SET_LENGTH(ldep, ln); + JSPREFIX_SET_BASE(ldep, str); +#ifdef DEBUG + { + JSRuntime *rt = cx->runtime; + JS_RUNTIME_METER(rt, liveDependentStrings); + JS_RUNTIME_METER(rt, totalDependentStrings); + JS_LOCK_RUNTIME_VOID(rt, + (rt->strdepLengthSum += (double)ln, + rt->strdepLengthSquaredSum += (double)ln * (double)ln)); + } +#endif + } + } + + return str; +} + +/* + * May be called with null cx by js_GetStringChars, above; and by the jslock.c + * MAKE_STRING_IMMUTABLE file-local macro. + */ +const jschar * +js_UndependString(JSContext *cx, JSString *str) +{ + size_t n, size; + jschar *s; + + if (JSSTRING_IS_DEPENDENT(str)) { + n = JSSTRDEP_LENGTH(str); + size = (n + 1) * sizeof(jschar); + s = (jschar *) (cx ? JS_malloc(cx, size) : malloc(size)); + if (!s) + return NULL; + + js_strncpy(s, JSSTRDEP_CHARS(str), n); + s[n] = 0; + str->length = n; + str->chars = s; + +#ifdef DEBUG + if (cx) { + JSRuntime *rt = cx->runtime; + JS_RUNTIME_UNMETER(rt, liveDependentStrings); + JS_RUNTIME_UNMETER(rt, totalDependentStrings); + JS_LOCK_RUNTIME_VOID(rt, + (rt->strdepLengthSum -= (double)n, + rt->strdepLengthSquaredSum -= (double)n * (double)n)); + } +#endif + } + + return str->chars; +} + +/* + * Forward declarations for URI encode/decode and helper routines + */ +static JSBool +str_decodeURI(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval); + +static JSBool +str_decodeURI_Component(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval); + +static JSBool +str_encodeURI(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval); + +static JSBool +str_encodeURI_Component(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval); + +static int +OneUcs4ToUtf8Char(uint8 *utf8Buffer, uint32 ucs4Char); + +static uint32 +Utf8ToOneUcs4Char(const uint8 *utf8Buffer, int utf8Length); + +/* + * Contributions from the String class to the set of methods defined for the + * global object. escape and unescape used to be defined in the Mocha library, + * but as ECMA decided to spec them, they've been moved to the core engine + * and made ECMA-compliant. (Incomplete escapes are interpreted as literal + * characters by unescape.) + */ + +/* + * Stuff to emulate the old libmocha escape, which took a second argument + * giving the type of escape to perform. Retained for compatibility, and + * copied here to avoid reliance on net.h, mkparse.c/NET_EscapeBytes. + */ + +#define URL_XALPHAS ((uint8) 1) +#define URL_XPALPHAS ((uint8) 2) +#define URL_PATH ((uint8) 4) + +static const uint8 urlCharType[256] = +/* Bit 0 xalpha -- the alphas + * Bit 1 xpalpha -- as xalpha but + * converts spaces to plus and plus to %20 + * Bit 2 ... path -- as xalphas but doesn't escape '/' + */ + /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0x */ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 1x */ + 0,0,0,0,0,0,0,0,0,0,7,4,0,7,7,4, /* 2x !"#$%&'()*+,-./ */ + 7,7,7,7,7,7,7,7,7,7,0,0,0,0,0,0, /* 3x 0123456789:;<=>? */ + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, /* 4x @ABCDEFGHIJKLMNO */ + 7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,7, /* 5X PQRSTUVWXYZ[\]^_ */ + 0,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, /* 6x `abcdefghijklmno */ + 7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,0, /* 7X pqrstuvwxyz{\}~ DEL */ + 0, }; + +/* This matches the ECMA escape set when mask is 7 (default.) */ + +#define IS_OK(C, mask) (urlCharType[((uint8) (C))] & (mask)) + +/* See ECMA-262 15.1.2.4. */ +JSBool +js_str_escape(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str; + size_t i, ni, length, newlength; + const jschar *chars; + jschar *newchars; + jschar ch; + jsint mask; + jsdouble d; + const char digits[] = {'0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + + mask = URL_XALPHAS | URL_XPALPHAS | URL_PATH; + if (argc > 1) { + if (!js_ValueToNumber(cx, argv[1], &d)) + return JS_FALSE; + if (!JSDOUBLE_IS_FINITE(d) || + (mask = (jsint)d) != d || + mask & ~(URL_XALPHAS | URL_XPALPHAS | URL_PATH)) + { + char numBuf[12]; + JS_snprintf(numBuf, sizeof numBuf, "%lx", (unsigned long) mask); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_STRING_MASK, numBuf); + return JS_FALSE; + } + } + + str = js_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + argv[0] = STRING_TO_JSVAL(str); + + chars = JSSTRING_CHARS(str); + length = newlength = JSSTRING_LENGTH(str); + + /* Take a first pass and see how big the result string will need to be. */ + for (i = 0; i < length; i++) { + if ((ch = chars[i]) < 128 && IS_OK(ch, mask)) + continue; + if (ch < 256) { + if (mask == URL_XPALPHAS && ch == ' ') + continue; /* The character will be encoded as '+' */ + newlength += 2; /* The character will be encoded as %XX */ + } else { + newlength += 5; /* The character will be encoded as %uXXXX */ + } + } + + newchars = (jschar *) JS_malloc(cx, (newlength + 1) * sizeof(jschar)); + if (!newchars) + return JS_FALSE; + for (i = 0, ni = 0; i < length; i++) { + if ((ch = chars[i]) < 128 && IS_OK(ch, mask)) { + newchars[ni++] = ch; + } else if (ch < 256) { + if (mask == URL_XPALPHAS && ch == ' ') { + newchars[ni++] = '+'; /* convert spaces to pluses */ + } else { + newchars[ni++] = '%'; + newchars[ni++] = digits[ch >> 4]; + newchars[ni++] = digits[ch & 0xF]; + } + } else { + newchars[ni++] = '%'; + newchars[ni++] = 'u'; + newchars[ni++] = digits[ch >> 12]; + newchars[ni++] = digits[(ch & 0xF00) >> 8]; + newchars[ni++] = digits[(ch & 0xF0) >> 4]; + newchars[ni++] = digits[ch & 0xF]; + } + } + JS_ASSERT(ni == newlength); + newchars[newlength] = 0; + + str = js_NewString(cx, newchars, newlength, 0); + if (!str) { + JS_free(cx, newchars); + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} +#undef IS_OK + +/* See ECMA-262 15.1.2.5 */ +static JSBool +str_unescape(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str; + size_t i, ni, length; + const jschar *chars; + jschar *newchars; + jschar ch; + + str = js_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + argv[0] = STRING_TO_JSVAL(str); + + chars = JSSTRING_CHARS(str); + length = JSSTRING_LENGTH(str); + + /* Don't bother allocating less space for the new string. */ + newchars = (jschar *) JS_malloc(cx, (length + 1) * sizeof(jschar)); + if (!newchars) + return JS_FALSE; + ni = i = 0; + while (i < length) { + ch = chars[i++]; + if (ch == '%') { + if (i + 1 < length && + JS7_ISHEX(chars[i]) && JS7_ISHEX(chars[i + 1])) + { + ch = JS7_UNHEX(chars[i]) * 16 + JS7_UNHEX(chars[i + 1]); + i += 2; + } else if (i + 4 < length && chars[i] == 'u' && + JS7_ISHEX(chars[i + 1]) && JS7_ISHEX(chars[i + 2]) && + JS7_ISHEX(chars[i + 3]) && JS7_ISHEX(chars[i + 4])) + { + ch = (((((JS7_UNHEX(chars[i + 1]) << 4) + + JS7_UNHEX(chars[i + 2])) << 4) + + JS7_UNHEX(chars[i + 3])) << 4) + + JS7_UNHEX(chars[i + 4]); + i += 5; + } + } + newchars[ni++] = ch; + } + newchars[ni] = 0; + + str = js_NewString(cx, newchars, ni, 0); + if (!str) { + JS_free(cx, newchars); + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +#if JS_HAS_UNEVAL +static JSBool +str_uneval(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str; + + str = js_ValueToSource(cx, argv[0]); + if (!str) + return JS_FALSE; + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} +#endif + +const char js_escape_str[] = "escape"; +const char js_unescape_str[] = "unescape"; +#if JS_HAS_UNEVAL +const char js_uneval_str[] = "uneval"; +#endif +const char js_decodeURI_str[] = "decodeURI"; +const char js_encodeURI_str[] = "encodeURI"; +const char js_decodeURIComponent_str[] = "decodeURIComponent"; +const char js_encodeURIComponent_str[] = "encodeURIComponent"; + +static JSFunctionSpec string_functions[] = { + {js_escape_str, js_str_escape, 1,0,0}, + {js_unescape_str, str_unescape, 1,0,0}, +#if JS_HAS_UNEVAL + {js_uneval_str, str_uneval, 1,0,0}, +#endif + {js_decodeURI_str, str_decodeURI, 1,0,0}, + {js_encodeURI_str, str_encodeURI, 1,0,0}, + {js_decodeURIComponent_str, str_decodeURI_Component, 1,0,0}, + {js_encodeURIComponent_str, str_encodeURI_Component, 1,0,0}, + + {0,0,0,0,0} +}; + +jschar js_empty_ucstr[] = {0}; +JSSubString js_EmptySubString = {0, js_empty_ucstr}; + +enum string_tinyid { + STRING_LENGTH = -1 +}; + +static JSPropertySpec string_props[] = { + {js_length_str, STRING_LENGTH, + JSPROP_READONLY|JSPROP_PERMANENT|JSPROP_SHARED, 0,0}, + {0,0,0,0,0} +}; + +static JSBool +str_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + JSString *str; + jsint slot; + + if (!JSVAL_IS_INT(id)) + return JS_TRUE; + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + slot = JSVAL_TO_INT(id); + if (slot == STRING_LENGTH) + *vp = INT_TO_JSVAL((jsint) JSSTRING_LENGTH(str)); + return JS_TRUE; +} + +#define STRING_ELEMENT_ATTRS (JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT) + +static JSBool +str_enumerate(JSContext *cx, JSObject *obj) +{ + JSString *str, *str1; + size_t i, length; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + length = JSSTRING_LENGTH(str); + for (i = 0; i < length; i++) { + str1 = js_NewDependentString(cx, str, i, 1, 0); + if (!str1) + return JS_FALSE; + if (!OBJ_DEFINE_PROPERTY(cx, obj, INT_TO_JSVAL(i), + STRING_TO_JSVAL(str1), NULL, NULL, + STRING_ELEMENT_ATTRS, NULL)) { + return JS_FALSE; + } + } + return JS_TRUE; +} + +static JSBool +str_resolve(JSContext *cx, JSObject *obj, jsval id) +{ + JSString *str, *str1; + jsint slot; + + if (!JSVAL_IS_INT(id)) + return JS_TRUE; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + slot = JSVAL_TO_INT(id); + if ((size_t)slot < JSSTRING_LENGTH(str)) { + str1 = js_NewDependentString(cx, str, (size_t)slot, 1, 0); + if (!str1) + return JS_FALSE; + if (!OBJ_DEFINE_PROPERTY(cx, obj, INT_TO_JSVAL(slot), + STRING_TO_JSVAL(str1), NULL, NULL, + STRING_ELEMENT_ATTRS, NULL)) { + return JS_FALSE; + } + } + return JS_TRUE; +} + +static JSClass string_class = { + js_String_str, + JSCLASS_HAS_PRIVATE, + JS_PropertyStub, JS_PropertyStub, str_getProperty, JS_PropertyStub, + str_enumerate, str_resolve, JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS +}; + +#if JS_HAS_TOSOURCE + +/* + * String.prototype.quote is generic (as are most string methods), unlike + * toSource, toString, and valueOf. + */ +static JSBool +str_quote(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + str = js_QuoteString(cx, str, '"'); + if (!str) + return JS_FALSE; + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +static JSBool +str_toSource(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsval v; + JSString *str; + size_t i, j, k, n; + char buf[16]; + jschar *s, *t; + + if (!JS_InstanceOf(cx, obj, &string_class, argv)) + return JS_FALSE; + v = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE); + if (!JSVAL_IS_STRING(v)) + return js_obj_toSource(cx, obj, argc, argv, rval); + str = js_QuoteString(cx, JSVAL_TO_STRING(v), '"'); + if (!str) + return JS_FALSE; + j = JS_snprintf(buf, sizeof buf, "(new %s(", string_class.name); + s = JSSTRING_CHARS(str); + k = JSSTRING_LENGTH(str); + n = j + k + 2; + t = (jschar *) JS_malloc(cx, (n + 1) * sizeof(jschar)); + if (!t) + return JS_FALSE; + for (i = 0; i < j; i++) + t[i] = buf[i]; + for (j = 0; j < k; i++, j++) + t[i] = s[j]; + t[i++] = ')'; + t[i++] = ')'; + t[i] = 0; + str = js_NewString(cx, t, n, 0); + if (!str) { + JS_free(cx, t); + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +#endif /* JS_HAS_TOSOURCE */ + +static JSBool +str_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsval v; + + if (!JS_InstanceOf(cx, obj, &string_class, argv)) + return JS_FALSE; + v = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE); + if (!JSVAL_IS_STRING(v)) + return js_obj_toString(cx, obj, argc, argv, rval); + *rval = v; + return JS_TRUE; +} + +static JSBool +str_valueOf(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + if (!JS_InstanceOf(cx, obj, &string_class, argv)) + return JS_FALSE; + *rval = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE); + return JS_TRUE; +} + +/* + * Java-like string native methods. + */ +static JSBool +str_substring(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSString *str; + jsdouble d; + jsdouble length, begin, end; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + argv[-1] = STRING_TO_JSVAL(str); + + if (argc != 0) { + if (!js_ValueToNumber(cx, argv[0], &d)) + return JS_FALSE; + length = JSSTRING_LENGTH(str); + begin = js_DoubleToInteger(d); + if (begin < 0) + begin = 0; + else if (begin > length) + begin = length; + + if (argc == 1) { + end = length; + } else { + if (!js_ValueToNumber(cx, argv[1], &d)) + return JS_FALSE; + end = js_DoubleToInteger(d); + if (end < 0) + end = 0; + else if (end > length) + end = length; + if (end < begin) { + if (cx->version != JSVERSION_1_2) { + /* XXX emulate old JDK1.0 java.lang.String.substring. */ + jsdouble tmp = begin; + begin = end; + end = tmp; + } else { + end = begin; + } + } + } + + str = js_NewDependentString(cx, str, (size_t)begin, + (size_t)(end - begin), 0); + if (!str) + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +static JSBool +str_toLowerCase(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSString *str; + size_t i, n; + jschar *s, *news; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + n = JSSTRING_LENGTH(str); + news = (jschar *) JS_malloc(cx, (n + 1) * sizeof(jschar)); + if (!news) + return JS_FALSE; + s = JSSTRING_CHARS(str); + for (i = 0; i < n; i++) + news[i] = JS_TOLOWER(s[i]); + news[n] = 0; + str = js_NewString(cx, news, n, 0); + if (!str) { + JS_free(cx, news); + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +static JSBool +str_toLocaleLowerCase(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSString *str; + + /* + * Forcefully ignore the first (or any) argument and return toLowerCase(), + * ECMA has reserved that argument, presumably for defining the locale. + */ + if (cx->localeCallbacks && cx->localeCallbacks->localeToLowerCase) { + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + return cx->localeCallbacks->localeToLowerCase(cx, str, rval); + } + return str_toLowerCase(cx, obj, 0, argv, rval); +} + +static JSBool +str_toUpperCase(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSString *str; + size_t i, n; + jschar *s, *news; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + n = JSSTRING_LENGTH(str); + news = (jschar *) JS_malloc(cx, (n + 1) * sizeof(jschar)); + if (!news) + return JS_FALSE; + s = JSSTRING_CHARS(str); + for (i = 0; i < n; i++) + news[i] = JS_TOUPPER(s[i]); + news[n] = 0; + str = js_NewString(cx, news, n, 0); + if (!str) { + JS_free(cx, news); + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +static JSBool +str_toLocaleUpperCase(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSString *str; + + /* + * Forcefully ignore the first (or any) argument and return toUpperCase(), + * ECMA has reserved that argument, presumbaly for defining the locale. + */ + if (cx->localeCallbacks && cx->localeCallbacks->localeToUpperCase) { + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + return cx->localeCallbacks->localeToUpperCase(cx, str, rval); + } + return str_toUpperCase(cx, obj, 0, argv, rval); +} + +static JSBool +str_localeCompare(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSString *str, *thatStr; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + argv[-1] = STRING_TO_JSVAL(str); + + if (argc == 0) { + *rval = JSVAL_ZERO; + } else { + thatStr = js_ValueToString(cx, argv[0]); + if (!thatStr) + return JS_FALSE; + if (cx->localeCallbacks && cx->localeCallbacks->localeCompare) + return cx->localeCallbacks->localeCompare(cx, str, thatStr, rval); + *rval = INT_TO_JSVAL(js_CompareStrings(str, thatStr)); + } + return JS_TRUE; +} + +static JSBool +str_charAt(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str; + jsdouble d; + size_t index; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + argv[-1] = STRING_TO_JSVAL(str); + + if (argc == 0) { + d = 0.0; + } else { + if (!js_ValueToNumber(cx, argv[0], &d)) + return JS_FALSE; + d = js_DoubleToInteger(d); + } + + if (d < 0 || JSSTRING_LENGTH(str) <= d) { + *rval = JS_GetEmptyStringValue(cx); + } else { + index = (size_t)d; + str = js_NewDependentString(cx, str, index, 1, 0); + if (!str) + return JS_FALSE; + *rval = STRING_TO_JSVAL(str); + } + return JS_TRUE; +} + +static JSBool +str_charCodeAt(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSString *str; + jsdouble d; + size_t index; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + argv[-1] = STRING_TO_JSVAL(str); + + if (argc == 0) { + d = 0.0; + } else { + if (!js_ValueToNumber(cx, argv[0], &d)) + return JS_FALSE; + d = js_DoubleToInteger(d); + } + + if (d < 0 || JSSTRING_LENGTH(str) <= d) { + *rval = JS_GetNaNValue(cx); + } else { + index = (size_t)d; + *rval = INT_TO_JSVAL((jsint) JSSTRING_CHARS(str)[index]); + } + return JS_TRUE; +} + +jsint +js_BoyerMooreHorspool(const jschar *text, jsint textlen, + const jschar *pat, jsint patlen, + jsint start) +{ + jsint i, j, k, m; + uint8 skip[BMH_CHARSET_SIZE]; + jschar c; + + JS_ASSERT(0 < patlen && patlen <= BMH_PATLEN_MAX); + for (i = 0; i < BMH_CHARSET_SIZE; i++) + skip[i] = (uint8)patlen; + m = patlen - 1; + for (i = 0; i < m; i++) { + c = pat[i]; + if (c >= BMH_CHARSET_SIZE) + return BMH_BAD_PATTERN; + skip[c] = (uint8)(m - i); + } + for (k = start + m; + k < textlen; + k += ((c = text[k]) >= BMH_CHARSET_SIZE) ? patlen : skip[c]) { + for (i = k, j = m; ; i--, j--) { + if (j < 0) + return i + 1; + if (text[i] != pat[j]) + break; + } + } + return -1; +} + +static JSBool +str_indexOf(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str, *str2; + jsint i, j, index, textlen, patlen; + const jschar *text, *pat; + jsdouble d; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + argv[-1] = STRING_TO_JSVAL(str); + text = JSSTRING_CHARS(str); + textlen = (jsint) JSSTRING_LENGTH(str); + + str2 = js_ValueToString(cx, argv[0]); + if (!str2) + return JS_FALSE; + argv[0] = STRING_TO_JSVAL(str2); + pat = JSSTRING_CHARS(str2); + patlen = (jsint) JSSTRING_LENGTH(str2); + + if (argc > 1) { + if (!js_ValueToNumber(cx, argv[1], &d)) + return JS_FALSE; + d = js_DoubleToInteger(d); + if (d < 0) + i = 0; + else if (d > textlen) + i = textlen; + else + i = (jsint)d; + } else { + i = 0; + } + if (patlen == 0) { + *rval = INT_TO_JSVAL(i); + return JS_TRUE; + } + + /* XXX tune the BMH threshold (512) */ + if ((jsuint)(patlen - 2) <= BMH_PATLEN_MAX - 2 && textlen >= 512) { + index = js_BoyerMooreHorspool(text, textlen, pat, patlen, i); + if (index != BMH_BAD_PATTERN) + goto out; + } + + index = -1; + j = 0; + while (i + j < textlen) { + if (text[i + j] == pat[j]) { + if (++j == patlen) { + index = i; + break; + } + } else { + i++; + j = 0; + } + } + +out: + *rval = INT_TO_JSVAL(index); + return JS_TRUE; +} + +static JSBool +str_lastIndexOf(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSString *str, *str2; + const jschar *text, *pat; + jsint i, j, textlen, patlen; + jsdouble d; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + argv[-1] = STRING_TO_JSVAL(str); + text = JSSTRING_CHARS(str); + textlen = (jsint) JSSTRING_LENGTH(str); + + str2 = js_ValueToString(cx, argv[0]); + if (!str2) + return JS_FALSE; + argv[0] = STRING_TO_JSVAL(str2); + pat = JSSTRING_CHARS(str2); + patlen = (jsint) JSSTRING_LENGTH(str2); + + if (argc > 1) { + if (!js_ValueToNumber(cx, argv[1], &d)) + return JS_FALSE; + if (JSDOUBLE_IS_NaN(d)) { + i = textlen; + } else { + d = js_DoubleToInteger(d); + if (d < 0) + i = 0; + else if (d > textlen - patlen) + i = textlen - patlen; + else + i = (jsint)d; + } + } else { + i = textlen; + } + + if (patlen == 0) { + *rval = INT_TO_JSVAL(i); + return JS_TRUE; + } + + j = 0; + while (i >= 0) { + /* Don't assume that text is NUL-terminated: it could be dependent. */ + if (i + j < textlen && text[i + j] == pat[j]) { + if (++j == patlen) + break; + } else { + i--; + j = 0; + } + } + *rval = INT_TO_JSVAL(i); + return JS_TRUE; +} + +/* + * Perl-inspired string functions. + */ +#if JS_HAS_REGEXPS +typedef struct GlobData { + uintN flags; /* inout: mode and flag bits, see below */ + uintN optarg; /* in: index of optional flags argument */ + JSString *str; /* out: 'this' parameter object as string */ + JSRegExp *regexp; /* out: regexp parameter object private data */ +} GlobData; + +/* + * Mode and flag bit definitions for match_or_replace's GlobData.flags field. + */ +#define MODE_MATCH 0x00 /* in: return match array on success */ +#define MODE_REPLACE 0x01 /* in: match and replace */ +#define MODE_SEARCH 0x02 /* in: search only, return match index or -1 */ +#define GET_MODE(f) ((f) & 0x03) +#define FORCE_FLAT 0x04 /* in: force flat (non-regexp) string match */ +#define KEEP_REGEXP 0x08 /* inout: keep GlobData.regexp alive for caller + of match_or_replace; if set on input + but clear on output, regexp ownership + does not pass to caller */ +#define GLOBAL_REGEXP 0x10 /* out: regexp had the 'g' flag */ + +static JSBool +match_or_replace(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + JSBool (*glob)(JSContext *cx, jsint count, GlobData *data), + GlobData *data, jsval *rval) +{ + JSString *str, *src, *opt; + JSObject *reobj; + JSRegExp *re; + size_t index, length; + JSBool ok, test; + jsint count; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + argv[-1] = STRING_TO_JSVAL(str); + data->str = str; + + if (JSVAL_IS_REGEXP(cx, argv[0])) { + reobj = JSVAL_TO_OBJECT(argv[0]); + re = (JSRegExp *) JS_GetPrivate(cx, reobj); + } else { + src = js_ValueToString(cx, argv[0]); + if (!src) + return JS_FALSE; + if (data->optarg < argc) { + argv[0] = STRING_TO_JSVAL(src); + opt = js_ValueToString(cx, argv[data->optarg]); + if (!opt) + return JS_FALSE; + } else { + opt = NULL; + } + re = js_NewRegExpOpt(cx, NULL, src, opt, + (data->flags & FORCE_FLAT) != 0); + if (!re) + return JS_FALSE; + reobj = NULL; + } + data->regexp = re; + + if (re->flags & JSREG_GLOB) + data->flags |= GLOBAL_REGEXP; + index = 0; + if (GET_MODE(data->flags) == MODE_SEARCH) { + ok = js_ExecuteRegExp(cx, re, str, &index, JS_TRUE, rval); + if (ok) { + *rval = (*rval == JSVAL_TRUE) + ? INT_TO_JSVAL(cx->regExpStatics.leftContext.length) + : INT_TO_JSVAL(-1); + } + } else if (data->flags & GLOBAL_REGEXP) { + if (reobj) { + /* Set the lastIndex property's reserved slot to 0. */ + ok = js_SetLastIndex(cx, reobj, 0); + if (!ok) + return JS_FALSE; + } else { + ok = JS_TRUE; + } + length = JSSTRING_LENGTH(str); + for (count = 0; index <= length; count++) { + ok = js_ExecuteRegExp(cx, re, str, &index, JS_TRUE, rval); + if (!ok || *rval != JSVAL_TRUE) + break; + ok = glob(cx, count, data); + if (!ok) + break; + if (cx->regExpStatics.lastMatch.length == 0) { + if (index == length) + break; + index++; + } + } + } else { + if (GET_MODE(data->flags) == MODE_REPLACE) { + test = JS_TRUE; + } else { + /* + * MODE_MATCH implies str_match is being called from a script or a + * scripted function. If the caller cares only about testing null + * vs. non-null return value, optimize away the array object that + * would normally be returned in *rval. + */ + JS_ASSERT(*cx->fp->down->pc == JSOP_CALL || + *cx->fp->down->pc == JSOP_NEW); + JS_ASSERT(js_CodeSpec[*cx->fp->down->pc].length == 3); + switch (cx->fp->down->pc[3]) { + case JSOP_POP: + case JSOP_IFEQ: + case JSOP_IFNE: + case JSOP_IFEQX: + case JSOP_IFNEX: + test = JS_TRUE; + break; + default: + test = JS_FALSE; + break; + } + } + ok = js_ExecuteRegExp(cx, re, str, &index, test, rval); + } + + if (reobj) { + /* Tell our caller that it doesn't need to destroy data->regexp. */ + data->flags &= ~KEEP_REGEXP; + } else if (!(data->flags & KEEP_REGEXP)) { + /* Caller didn't want to keep data->regexp, so null and destroy it. */ + data->regexp = NULL; + js_DestroyRegExp(cx, re); + } + return ok; +} + +typedef struct MatchData { + GlobData base; + jsval *arrayval; /* NB: local root pointer */ +} MatchData; + +static JSBool +match_glob(JSContext *cx, jsint count, GlobData *data) +{ + MatchData *mdata; + JSObject *arrayobj; + JSSubString *matchsub; + JSString *matchstr; + jsval v; + + mdata = (MatchData *)data; + arrayobj = JSVAL_TO_OBJECT(*mdata->arrayval); + if (!arrayobj) { + arrayobj = js_ConstructObject(cx, &js_ArrayClass, NULL, NULL, 0, NULL); + if (!arrayobj) + return JS_FALSE; + *mdata->arrayval = OBJECT_TO_JSVAL(arrayobj); + } + matchsub = &cx->regExpStatics.lastMatch; + matchstr = js_NewStringCopyN(cx, matchsub->chars, matchsub->length, 0); + if (!matchstr) + return JS_FALSE; + v = STRING_TO_JSVAL(matchstr); + return js_SetProperty(cx, arrayobj, INT_TO_JSVAL(count), &v); +} + +static JSBool +str_match(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + MatchData mdata; + JSBool ok; + + mdata.base.flags = MODE_MATCH; + mdata.base.optarg = 1; + mdata.arrayval = &argv[2]; + *mdata.arrayval = JSVAL_NULL; + ok = match_or_replace(cx, obj, argc, argv, match_glob, &mdata.base, rval); + if (ok && !JSVAL_IS_NULL(*mdata.arrayval)) + *rval = *mdata.arrayval; + return ok; +} + +static JSBool +str_search(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + GlobData data; + + data.flags = MODE_SEARCH; + data.optarg = 1; + return match_or_replace(cx, obj, argc, argv, NULL, &data, rval); +} + +typedef struct ReplaceData { + GlobData base; /* base struct state */ + JSObject *lambda; /* replacement function object or null */ + JSString *repstr; /* replacement string */ + jschar *dollar; /* null or pointer to first $ in repstr */ + jschar *dollarEnd; /* limit pointer for js_strchr_limit */ + jschar *chars; /* result chars, null initially */ + size_t length; /* result length, 0 initially */ + jsint index; /* index in result of next replacement */ + jsint leftIndex; /* left context index in base.str->chars */ + JSSubString dollarStr; /* for "$$" interpret_dollar result */ +} ReplaceData; + +static JSSubString * +interpret_dollar(JSContext *cx, jschar *dp, ReplaceData *rdata, size_t *skip) +{ + JSRegExpStatics *res; + jschar dc, *cp; + uintN num, tmp; + JSString *str; + + JS_ASSERT(*dp == '$'); + + /* + * Allow a real backslash (literal "\\" before "$1") to escape "$1", e.g. + * Do this only for versions strictly less than ECMAv3. + */ + if (cx->version != JSVERSION_DEFAULT && cx->version <= JSVERSION_1_4) { + if (dp > JSSTRING_CHARS(rdata->repstr) && dp[-1] == '\\') + return NULL; + } + + /* Interpret all Perl match-induced dollar variables. */ + res = &cx->regExpStatics; + dc = dp[1]; + if (JS7_ISDEC(dc)) { + if (cx->version != JSVERSION_DEFAULT && cx->version <= JSVERSION_1_4) { + if (dc == '0') + return NULL; + + /* Check for overflow to avoid gobbling arbitrary decimal digits. */ + num = 0; + cp = dp; + while ((dc = *++cp) != 0 && JS7_ISDEC(dc)) { + tmp = 10 * num + JS7_UNDEC(dc); + if (tmp < num) + break; + num = tmp; + } + } else { /* ECMA 3, 1-9 or 01-99 */ + num = JS7_UNDEC(dc); + if (num > res->parenCount) + return NULL; + cp = dp + 2; + dc = *cp; + if ((dc != 0) && JS7_ISDEC(dc)) { + tmp = 10 * num + JS7_UNDEC(dc); + if (tmp <= res->parenCount) { + cp++; + num = tmp; + } + } + if (num == 0) + return NULL; + } + /* Adjust num from 1 $n-origin to 0 array-index-origin. */ + num--; + *skip = cp - dp; + return REGEXP_PAREN_SUBSTRING(res, num); + } + + *skip = 2; + switch (dc) { + case '$': + rdata->dollarStr.chars = dp; + rdata->dollarStr.length = 1; + return &rdata->dollarStr; + case '&': + return &res->lastMatch; + case '+': + return &res->lastParen; + case '`': + if (cx->version == JSVERSION_1_2) { + /* + * JS1.2 imitated the Perl4 bug where left context at each step + * in an iterative use of a global regexp started from last match, + * not from the start of the target string. But Perl4 does start + * $` at the beginning of the target string when it is used in a + * substitution, so we emulate that special case here. + */ + str = rdata->base.str; + res->leftContext.chars = JSSTRING_CHARS(str); + res->leftContext.length = res->lastMatch.chars + - JSSTRING_CHARS(str); + } + return &res->leftContext; + case '\'': + return &res->rightContext; + } + return NULL; +} + +static JSBool +find_replen(JSContext *cx, ReplaceData *rdata, size_t *sizep) +{ + JSString *repstr; + size_t replen, skip; + jschar *dp, *ep; + JSSubString *sub; +#if JS_HAS_REPLACE_LAMBDA + JSObject *lambda; + + lambda = rdata->lambda; + if (lambda) { + uintN argc, i, j, m, n, p; + jsval *sp, *oldsp, rval; + void *mark; + JSStackFrame *fp; + JSBool ok; + + /* + * Save the rightContext from the current regexp, since it + * gets stuck at the end of the replacement string and may + * be clobbered by a RegExp usage in the lambda function. + */ + JSSubString saveRightContext = cx->regExpStatics.rightContext; + + /* + * In the lambda case, not only do we find the replacement string's + * length, we compute repstr and return it via rdata for use within + * do_replace. The lambda is called with arguments ($&, $1, $2, ..., + * index, input), i.e., all the properties of a regexp match array. + * For $&, etc., we must create string jsvals from cx->regExpStatics. + * We grab up stack space to keep the newborn strings GC-rooted. + */ + p = rdata->base.regexp->parenCount; + argc = 1 + p + 2; + sp = js_AllocStack(cx, 2 + argc, &mark); + if (!sp) + return JS_FALSE; + + /* Push lambda and its 'this' parameter. */ + *sp++ = OBJECT_TO_JSVAL(lambda); + *sp++ = OBJECT_TO_JSVAL(OBJ_GET_PARENT(cx, lambda)); + +#define PUSH_REGEXP_STATIC(sub) \ + JS_BEGIN_MACRO \ + JSString *str = js_NewStringCopyN(cx, \ + cx->regExpStatics.sub.chars, \ + cx->regExpStatics.sub.length, \ + 0); \ + if (!str) { \ + ok = JS_FALSE; \ + goto lambda_out; \ + } \ + *sp++ = STRING_TO_JSVAL(str); \ + JS_END_MACRO + + /* Push $&, $1, $2, ... */ + PUSH_REGEXP_STATIC(lastMatch); + i = 0; + m = cx->regExpStatics.parenCount; + n = JS_MIN(m, 9); + for (j = 0; i < n; i++, j++) + PUSH_REGEXP_STATIC(parens[j]); + for (j = 0; i < m; i++, j++) + PUSH_REGEXP_STATIC(moreParens[j]); + +#undef PUSH_REGEXP_STATIC + + /* Make sure to push undefined for any unmatched parens. */ + for (; i < p; i++) + *sp++ = JSVAL_VOID; + + /* Push match index and input string. */ + *sp++ = INT_TO_JSVAL((jsint)cx->regExpStatics.leftContext.length); + *sp++ = STRING_TO_JSVAL(rdata->base.str); + + /* Lift current frame to include the args and do the call. */ + fp = cx->fp; + oldsp = fp->sp; + fp->sp = sp; + ok = js_Invoke(cx, argc, JSINVOKE_INTERNAL); + rval = fp->sp[-1]; + fp->sp = oldsp; + + if (ok) { + /* + * NB: we count on the newborn string root to hold any string + * created by this js_ValueToString that would otherwise be GC- + * able, until we use rdata->repstr in do_replace. + */ + repstr = js_ValueToString(cx, rval); + if (!repstr) { + ok = JS_FALSE; + } else { + rdata->repstr = repstr; + *sizep = JSSTRING_LENGTH(repstr); + } + } + + lambda_out: + js_FreeStack(cx, mark); + cx->regExpStatics.rightContext = saveRightContext; + return ok; + } +#endif /* JS_HAS_REPLACE_LAMBDA */ + + repstr = rdata->repstr; + replen = JSSTRING_LENGTH(repstr); + for (dp = rdata->dollar, ep = rdata->dollarEnd; dp; + dp = js_strchr_limit(dp, '$', ep)) { + sub = interpret_dollar(cx, dp, rdata, &skip); + if (sub) { + replen += sub->length - skip; + dp += skip; + } + else + dp++; + } + *sizep = replen; + return JS_TRUE; +} + +static void +do_replace(JSContext *cx, ReplaceData *rdata, jschar *chars) +{ + JSString *repstr; + jschar *bp, *cp, *dp, *ep; + size_t len, skip; + JSSubString *sub; + + repstr = rdata->repstr; + bp = cp = JSSTRING_CHARS(repstr); + for (dp = rdata->dollar, ep = rdata->dollarEnd; dp; + dp = js_strchr_limit(dp, '$', ep)) { + len = dp - cp; + js_strncpy(chars, cp, len); + chars += len; + cp = dp; + sub = interpret_dollar(cx, dp, rdata, &skip); + if (sub) { + len = sub->length; + js_strncpy(chars, sub->chars, len); + chars += len; + cp += skip; + dp += skip; + } else { + dp++; + } + } + js_strncpy(chars, cp, JSSTRING_LENGTH(repstr) - (cp - bp)); +} + +static JSBool +replace_glob(JSContext *cx, jsint count, GlobData *data) +{ + ReplaceData *rdata; + JSString *str; + size_t leftoff, leftlen, replen, growth; + const jschar *left; + jschar *chars; + + rdata = (ReplaceData *)data; + str = data->str; + leftoff = rdata->leftIndex; + left = JSSTRING_CHARS(str) + leftoff; + leftlen = cx->regExpStatics.lastMatch.chars - left; + rdata->leftIndex = cx->regExpStatics.lastMatch.chars - JSSTRING_CHARS(str); + rdata->leftIndex += cx->regExpStatics.lastMatch.length; + if (!find_replen(cx, rdata, &replen)) + return JS_FALSE; + growth = leftlen + replen; + chars = (jschar *) + (rdata->chars + ? JS_realloc(cx, rdata->chars, (rdata->length + growth + 1) + * sizeof(jschar)) + : JS_malloc(cx, (growth + 1) * sizeof(jschar))); + if (!chars) { + JS_free(cx, rdata->chars); + rdata->chars = NULL; + return JS_FALSE; + } + rdata->chars = chars; + rdata->length += growth; + chars += rdata->index; + rdata->index += growth; + js_strncpy(chars, left, leftlen); + chars += leftlen; + do_replace(cx, rdata, chars); + return JS_TRUE; +} + +static JSBool +str_replace(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSObject *lambda; + JSString *repstr, *str; + ReplaceData rdata; + JSBool ok; + jschar *chars; + size_t leftlen, rightlen, length; + +#if JS_HAS_REPLACE_LAMBDA + if (JS_TypeOfValue(cx, argv[1]) == JSTYPE_FUNCTION) { + lambda = JSVAL_TO_OBJECT(argv[1]); + repstr = NULL; + } else +#endif + { + if (!JS_ConvertValue(cx, argv[1], JSTYPE_STRING, &argv[1])) + return JS_FALSE; + repstr = JSVAL_TO_STRING(argv[1]); + lambda = NULL; + } + + /* + * For ECMA Edition 3, the first argument is to be converted to a string + * to match in a "flat" sense (without regular expression metachars having + * special meanings) UNLESS the first arg is a RegExp object. + */ + rdata.base.flags = MODE_REPLACE | KEEP_REGEXP; + if (cx->version == JSVERSION_DEFAULT || cx->version > JSVERSION_1_4) + rdata.base.flags |= FORCE_FLAT; + rdata.base.optarg = 2; + + rdata.lambda = lambda; + rdata.repstr = repstr; + if (repstr) { + rdata.dollarEnd = JSSTRING_CHARS(repstr) + JSSTRING_LENGTH(repstr); + rdata.dollar = js_strchr_limit(JSSTRING_CHARS(repstr), '$', + rdata.dollarEnd); + } else { + rdata.dollar = rdata.dollarEnd = NULL; + } + rdata.chars = NULL; + rdata.length = 0; + rdata.index = 0; + rdata.leftIndex = 0; + + ok = match_or_replace(cx, obj, argc, argv, replace_glob, &rdata.base, rval); + if (!ok) + return JS_FALSE; + + if (!rdata.chars) { + if ((rdata.base.flags & GLOBAL_REGEXP) || *rval != JSVAL_TRUE) { + /* Didn't match even once. */ + *rval = STRING_TO_JSVAL(rdata.base.str); + goto out; + } + leftlen = cx->regExpStatics.leftContext.length; + ok = find_replen(cx, &rdata, &length); + if (!ok) + goto out; + length += leftlen; + chars = (jschar *) JS_malloc(cx, (length + 1) * sizeof(jschar)); + if (!chars) { + ok = JS_FALSE; + goto out; + } + js_strncpy(chars, cx->regExpStatics.leftContext.chars, leftlen); + do_replace(cx, &rdata, chars + leftlen); + rdata.chars = chars; + rdata.length = length; + } + + rightlen = cx->regExpStatics.rightContext.length; + length = rdata.length + rightlen; + chars = (jschar *) + JS_realloc(cx, rdata.chars, (length + 1) * sizeof(jschar)); + if (!chars) { + JS_free(cx, rdata.chars); + ok = JS_FALSE; + goto out; + } + js_strncpy(chars + rdata.length, cx->regExpStatics.rightContext.chars, + rightlen); + chars[length] = 0; + + str = js_NewString(cx, chars, length, 0); + if (!str) { + JS_free(cx, chars); + ok = JS_FALSE; + goto out; + } + *rval = STRING_TO_JSVAL(str); + +out: + /* If KEEP_REGEXP is still set, it's our job to destroy regexp now. */ + if (rdata.base.flags & KEEP_REGEXP) + js_DestroyRegExp(cx, rdata.base.regexp); + return ok; +} +#endif /* JS_HAS_REGEXPS */ + +/* + * Subroutine used by str_split to find the next split point in str, starting + * at offset *ip and looking either for the separator substring given by sep, + * or for the next re match. In the re case, return the matched separator in + * *sep, and the possibly updated offset in *ip. + * + * Return -2 on error, -1 on end of string, >= 0 for a valid index of the next + * separator occurrence if found, or str->length if no separator is found. + */ +static jsint +find_split(JSContext *cx, JSString *str, JSRegExp *re, jsint *ip, + JSSubString *sep) +{ + jsint i, j, k; + jschar *chars; + size_t length; + + /* + * Stop if past end of string. If at end of string, we will compare the + * null char stored there (by js_NewString*) to sep->chars[j] in the while + * loop at the end of this function, so that + * + * "ab,".split(',') => ["ab", ""] + * + * and the resulting array converts back to the string "ab," for symmetry. + * However, we ape Perl and do this only if there is a sufficiently large + * limit argument (see str_split). + */ + i = *ip; + if ((size_t)i > JSSTRING_LENGTH(str)) + return -1; + + /* + * Perl4 special case for str.split(' '), only if the user has selected + * JavaScript1.2 explicitly. Split on whitespace, and skip leading w/s. + * Strange but true, apparently modeled after awk. + * + * NB: we set sep->length to the length of the w/s run, so we must test + * sep->chars[1] == 0 to make sure sep is just one space. + */ + chars = JSSTRING_CHARS(str); + length = JSSTRING_LENGTH(str); + if (cx->version == JSVERSION_1_2 && + !re && *sep->chars == ' ' && sep->chars[1] == 0) { + + /* Skip leading whitespace if at front of str. */ + if (i == 0) { + while (JS_ISSPACE(chars[i])) + i++; + *ip = i; + } + + /* Don't delimit whitespace at end of string. */ + if ((size_t)i == length) + return -1; + + /* Skip over the non-whitespace chars. */ + while ((size_t)i < length && !JS_ISSPACE(chars[i])) + i++; + + /* Now skip the next run of whitespace. */ + j = i; + while ((size_t)j < length && JS_ISSPACE(chars[j])) + j++; + + /* Update sep->length to count delimiter chars. */ + sep->length = (size_t)(j - i); + return i; + } + +#if JS_HAS_REGEXPS + /* + * Match a regular expression against the separator at or above index i. + * Call js_ExecuteRegExp with true for the test argument. On successful + * match, get the separator from cx->regExpStatics.lastMatch. + */ + if (re) { + size_t index; + jsval rval; + + again: + /* JS1.2 deviated from Perl by never matching at end of string. */ + index = (size_t)i; + if (!js_ExecuteRegExp(cx, re, str, &index, JS_TRUE, &rval)) + return -2; + if (rval != JSVAL_TRUE) { + /* Mismatch: ensure our caller advances i past end of string. */ + sep->length = 1; + return length; + } + i = (jsint)index; + *sep = cx->regExpStatics.lastMatch; + if (sep->length == 0) { + /* + * Empty string match: never split on an empty match at the start + * of a find_split cycle. Same rule as for an empty global match + * in match_or_replace. + */ + if (i == *ip) { + /* + * "Bump-along" to avoid sticking at an empty match, but don't + * bump past end of string -- our caller must do that by adding + * sep->length to our return value. + */ + if ((size_t)i == length) { + if (cx->version == JSVERSION_1_2) { + sep->length = 1; + return i; + } + return -1; + } + i++; + goto again; + } + } + JS_ASSERT((size_t)i >= sep->length); + return i - sep->length; + } +#endif /* JS_HAS_REGEXPS */ + + /* + * Deviate from ECMA by never splitting an empty string by any separator + * string into a non-empty array (an array of length 1 that contains the + * empty string). + */ + if (!JSVERSION_IS_ECMA(cx->version) && length == 0) + return -1; + + /* + * Special case: if sep is the empty string, split str into one character + * substrings. Let our caller worry about whether to split once at end of + * string into an empty substring. + * + * For 1.2 compatibility, at the end of the string, we return the length as + * the result, and set the separator length to 1 -- this allows the caller + * to include an additional null string at the end of the substring list. + */ + if (sep->length == 0) { + if (cx->version == JSVERSION_1_2) { + if ((size_t)i == length) { + sep->length = 1; + return i; + } + return i + 1; + } + return ((size_t)i == length) ? -1 : i + 1; + } + + /* + * Now that we know sep is non-empty, search starting at i in str for an + * occurrence of all of sep's chars. If we find them, return the index of + * the first separator char. Otherwise, return length. + */ + j = 0; + while ((size_t)(k = i + j) < length) { + if (chars[k] == sep->chars[j]) { + if ((size_t)++j == sep->length) + return i; + } else { + i++; + j = 0; + } + } + return k; +} + +static JSBool +str_split(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str, *sub; + JSObject *arrayobj; + jsval v; + JSBool ok, limited; + JSRegExp *re; + JSSubString *sep, tmp; + jsdouble d; + jsint i, j; + uint32 len, limit; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + argv[-1] = STRING_TO_JSVAL(str); + + arrayobj = js_ConstructObject(cx, &js_ArrayClass, NULL, NULL, 0, NULL); + if (!arrayobj) + return JS_FALSE; + *rval = OBJECT_TO_JSVAL(arrayobj); + + if (argc == 0) { + v = STRING_TO_JSVAL(str); + ok = JS_SetElement(cx, arrayobj, 0, &v); + } else { +#if JS_HAS_REGEXPS + if (JSVAL_IS_REGEXP(cx, argv[0])) { + re = (JSRegExp *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(argv[0])); + sep = &tmp; + + /* Set a magic value so we can detect a successful re match. */ + sep->chars = NULL; + } else +#endif + { + JSString *str2 = js_ValueToString(cx, argv[0]); + if (!str2) + return JS_FALSE; + argv[0] = STRING_TO_JSVAL(str2); + + /* + * Point sep at a local copy of str2's header because find_split + * will modify sep->length. + */ + tmp.length = JSSTRING_LENGTH(str2); + tmp.chars = JSSTRING_CHARS(str2); + sep = &tmp; + re = NULL; + } + + /* Use the second argument as the split limit, if given. */ + limited = (argc > 1) && !JSVAL_IS_VOID(argv[1]); + limit = 0; /* Avoid warning. */ + if (limited) { + if (!js_ValueToNumber(cx, argv[1], &d)) + return JS_FALSE; + + /* Clamp limit between 0 and 1 + string length. */ + if (!js_DoubleToECMAUint32(cx, d, &limit)) + return JS_FALSE; + if (limit > JSSTRING_LENGTH(str)) + limit = 1 + JSSTRING_LENGTH(str); + } + + len = i = 0; + while ((j = find_split(cx, str, re, &i, sep)) >= 0) { + if (limited && len >= limit) + break; + sub = js_NewDependentString(cx, str, i, (size_t)(j - i), 0); + if (!sub) + return JS_FALSE; + v = STRING_TO_JSVAL(sub); + if (!JS_SetElement(cx, arrayobj, len, &v)) + return JS_FALSE; + len++; +#if JS_HAS_REGEXPS + /* + * Imitate perl's feature of including parenthesized substrings + * that matched part of the delimiter in the new array, after the + * split substring that was delimited. + */ + if (re && sep->chars) { + uintN num; + JSSubString *parsub; + + for (num = 0; num < cx->regExpStatics.parenCount; num++) { + if (limited && len >= limit) + break; + parsub = REGEXP_PAREN_SUBSTRING(&cx->regExpStatics, num); + sub = js_NewStringCopyN(cx, parsub->chars, parsub->length, + 0); + if (!sub) + return JS_FALSE; + v = STRING_TO_JSVAL(sub); + if (!JS_SetElement(cx, arrayobj, len, &v)) + return JS_FALSE; + len++; + } + sep->chars = NULL; + } +#endif + i = j + sep->length; + if (!JSVERSION_IS_ECMA(cx->version)) { + /* + * Deviate from ECMA to imitate Perl, which omits a final + * split unless a limit argument is given and big enough. + */ + if (!limited && (size_t)i == JSSTRING_LENGTH(str)) + break; + } + } + ok = (j != -2); + } + return ok; +} + +#if JS_HAS_PERL_SUBSTR +static JSBool +str_substr(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str; + jsdouble d; + jsdouble length, begin, end; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + + if (argc != 0) { + if (!js_ValueToNumber(cx, argv[0], &d)) + return JS_FALSE; + length = JSSTRING_LENGTH(str); + begin = js_DoubleToInteger(d); + if (begin < 0) { + begin += length; + if (begin < 0) + begin = 0; + } else if (begin > length) { + begin = length; + } + + if (argc == 1) { + end = length; + } else { + if (!js_ValueToNumber(cx, argv[1], &d)) + return JS_FALSE; + end = js_DoubleToInteger(d); + if (end < 0) + end = 0; + end += begin; + if (end > length) + end = length; + } + + str = js_NewDependentString(cx, str, (size_t)begin, + (size_t)(end - begin), 0); + if (!str) + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} +#endif /* JS_HAS_PERL_SUBSTR */ + +#if JS_HAS_SEQUENCE_OPS +/* + * Python-esque sequence operations. + */ +static JSBool +str_concat(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str, *str2; + uintN i; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + argv[-1] = STRING_TO_JSVAL(str); + + for (i = 0; i < argc; i++) { + str2 = js_ValueToString(cx, argv[i]); + if (!str2) + return JS_FALSE; + argv[i] = STRING_TO_JSVAL(str2); + + str = js_ConcatStrings(cx, str, str2); + if (!str) + return JS_FALSE; + } + + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +static JSBool +str_slice(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str; + jsdouble d; + jsdouble length, begin, end; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + argv[-1] = STRING_TO_JSVAL(str); + + if (argc != 0) { + if (!js_ValueToNumber(cx, argv[0], &d)) + return JS_FALSE; + length = JSSTRING_LENGTH(str); + begin = js_DoubleToInteger(d); + if (begin < 0) { + begin += length; + if (begin < 0) + begin = 0; + } else if (begin > length) { + begin = length; + } + + if (argc == 1) { + end = length; + } else { + if (!js_ValueToNumber(cx, argv[1], &d)) + return JS_FALSE; + end = js_DoubleToInteger(d); + if (end < 0) { + end += length; + if (end < 0) + end = 0; + } else if (end > length) { + end = length; + } + if (end < begin) + end = begin; + } + + str = js_NewDependentString(cx, str, (size_t)begin, + (size_t)(end - begin), 0); + if (!str) + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} +#endif /* JS_HAS_SEQUENCE_OPS */ + +#if JS_HAS_STR_HTML_HELPERS +/* + * HTML composition aids. + */ +static JSBool +tagify(JSContext *cx, JSObject *obj, jsval *argv, + const char *begin, const jschar *param, const char *end, + jsval *rval) +{ + JSString *str; + jschar *tagbuf; + size_t beglen, endlen, parlen, taglen; + size_t i, j; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + argv[-1] = STRING_TO_JSVAL(str); + + if (!end) + end = begin; + + beglen = strlen(begin); + taglen = 1 + beglen + 1; /* '' */ + parlen = 0; /* Avoid warning. */ + if (param) { + parlen = js_strlen(param); + taglen += 2 + parlen + 1; /* '="param"' */ + } + endlen = strlen(end); + taglen += JSSTRING_LENGTH(str) + 2 + endlen + 1; /* 'str' */ + + tagbuf = (jschar *) JS_malloc(cx, (taglen + 1) * sizeof(jschar)); + if (!tagbuf) + return JS_FALSE; + + j = 0; + tagbuf[j++] = '<'; + for (i = 0; i < beglen; i++) + tagbuf[j++] = (jschar)begin[i]; + if (param) { + tagbuf[j++] = '='; + tagbuf[j++] = '"'; + js_strncpy(&tagbuf[j], param, parlen); + j += parlen; + tagbuf[j++] = '"'; + } + tagbuf[j++] = '>'; + js_strncpy(&tagbuf[j], JSSTRING_CHARS(str), JSSTRING_LENGTH(str)); + j += JSSTRING_LENGTH(str); + tagbuf[j++] = '<'; + tagbuf[j++] = '/'; + for (i = 0; i < endlen; i++) + tagbuf[j++] = (jschar)end[i]; + tagbuf[j++] = '>'; + JS_ASSERT(j == taglen); + tagbuf[j] = 0; + + str = js_NewString(cx, tagbuf, taglen, 0); + if (!str) { + free((char *)tagbuf); + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +static JSBool +tagify_value(JSContext *cx, JSObject *obj, jsval *argv, + const char *begin, const char *end, + jsval *rval) +{ + JSString *param; + + param = js_ValueToString(cx, argv[0]); + if (!param) + return JS_FALSE; + argv[0] = STRING_TO_JSVAL(param); + return tagify(cx, obj, argv, begin, JSSTRING_CHARS(param), end, rval); +} + +static JSBool +str_bold(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + return tagify(cx, obj, argv, "b", NULL, NULL, rval); +} + +static JSBool +str_italics(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + return tagify(cx, obj, argv, "i", NULL, NULL, rval); +} + +static JSBool +str_fixed(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + return tagify(cx, obj, argv, "tt", NULL, NULL, rval); +} + +static JSBool +str_fontsize(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + return tagify_value(cx, obj, argv, "font size", "font", rval); +} + +static JSBool +str_fontcolor(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + return tagify_value(cx, obj, argv, "font color", "font", rval); +} + +static JSBool +str_link(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + return tagify_value(cx, obj, argv, "a href", "a", rval); +} + +static JSBool +str_anchor(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + return tagify_value(cx, obj, argv, "a name", "a", rval); +} + +static JSBool +str_strike(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + return tagify(cx, obj, argv, "strike", NULL, NULL, rval); +} + +static JSBool +str_small(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + return tagify(cx, obj, argv, "small", NULL, NULL, rval); +} + +static JSBool +str_big(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + return tagify(cx, obj, argv, "big", NULL, NULL, rval); +} + +static JSBool +str_blink(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + return tagify(cx, obj, argv, "blink", NULL, NULL, rval); +} + +static JSBool +str_sup(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + return tagify(cx, obj, argv, "sup", NULL, NULL, rval); +} + +static JSBool +str_sub(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + return tagify(cx, obj, argv, "sub", NULL, NULL, rval); +} +#endif /* JS_HAS_STR_HTML_HELPERS */ + +static JSFunctionSpec string_methods[] = { +#if JS_HAS_TOSOURCE + {"quote", str_quote, 0,0,0}, + {js_toSource_str, str_toSource, 0,0,0}, +#endif + + /* Java-like methods. */ + {js_toString_str, str_toString, 0,0,0}, + {js_valueOf_str, str_valueOf, 0,0,0}, + {"substring", str_substring, 2,0,0}, + {"toLowerCase", str_toLowerCase, 0,0,0}, + {"toUpperCase", str_toUpperCase, 0,0,0}, + {"charAt", str_charAt, 1,0,0}, + {"charCodeAt", str_charCodeAt, 1,0,0}, + {"indexOf", str_indexOf, 1,0,0}, + {"lastIndexOf", str_lastIndexOf, 1,0,0}, + {"toLocaleLowerCase", str_toLocaleLowerCase, 0,0,0}, + {"toLocaleUpperCase", str_toLocaleUpperCase, 0,0,0}, + {"localeCompare", str_localeCompare, 1,0,0}, + + /* Perl-ish methods (search is actually Python-esque). */ +#if JS_HAS_REGEXPS + {"match", str_match, 1,0,2}, + {"search", str_search, 1,0,0}, + {"replace", str_replace, 2,0,0}, + {"split", str_split, 2,0,0}, +#endif +#if JS_HAS_PERL_SUBSTR + {"substr", str_substr, 2,0,0}, +#endif + + /* Python-esque sequence methods. */ +#if JS_HAS_SEQUENCE_OPS + {"concat", str_concat, 0,0,0}, + {"slice", str_slice, 0,0,0}, +#endif + + /* HTML string methods. */ +#if JS_HAS_STR_HTML_HELPERS + {"bold", str_bold, 0,0,0}, + {"italics", str_italics, 0,0,0}, + {"fixed", str_fixed, 0,0,0}, + {"fontsize", str_fontsize, 1,0,0}, + {"fontcolor", str_fontcolor, 1,0,0}, + {"link", str_link, 1,0,0}, + {"anchor", str_anchor, 1,0,0}, + {"strike", str_strike, 0,0,0}, + {"small", str_small, 0,0,0}, + {"big", str_big, 0,0,0}, + {"blink", str_blink, 0,0,0}, + {"sup", str_sup, 0,0,0}, + {"sub", str_sub, 0,0,0}, +#endif + + {0,0,0,0,0} +}; + +static JSBool +String(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str; + + if (argc > 0) { + str = js_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + } else { + str = cx->runtime->emptyString; + } + if (!(cx->fp->flags & JSFRAME_CONSTRUCTING)) { + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; + } + OBJ_SET_SLOT(cx, obj, JSSLOT_PRIVATE, STRING_TO_JSVAL(str)); + return JS_TRUE; +} + +static JSBool +str_fromCharCode(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + jschar *chars; + uintN i; + uint16 code; + JSString *str; + + chars = (jschar *) JS_malloc(cx, (argc + 1) * sizeof(jschar)); + if (!chars) + return JS_FALSE; + for (i = 0; i < argc; i++) { + if (!js_ValueToUint16(cx, argv[i], &code)) { + JS_free(cx, chars); + return JS_FALSE; + } + chars[i] = (jschar)code; + } + chars[i] = 0; + str = js_NewString(cx, chars, argc, 0); + if (!str) { + JS_free(cx, chars); + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +static JSFunctionSpec string_static_methods[] = { + {"fromCharCode", str_fromCharCode, 1,0,0}, + {0,0,0,0,0} +}; + +static JSHashTable *deflated_string_cache; +#ifdef DEBUG +static uint32 deflated_string_cache_bytes; +#endif +#ifdef JS_THREADSAFE +static JSLock *deflated_string_cache_lock; +#endif + +JSBool +js_InitStringGlobals(void) +{ +#ifdef JS_THREADSAFE + /* Must come through here once in primordial thread to init safely! */ + if (!deflated_string_cache_lock) { + deflated_string_cache_lock = JS_NEW_LOCK(); + if (!deflated_string_cache_lock) + return JS_FALSE; + } +#endif + return JS_TRUE; +} + +void +js_FreeStringGlobals() +{ + if (deflated_string_cache) { + JS_HashTableDestroy(deflated_string_cache); + deflated_string_cache = NULL; + } +#ifdef JS_THREADSAFE + if (deflated_string_cache_lock) { + JS_DESTROY_LOCK(deflated_string_cache_lock); + deflated_string_cache_lock = NULL; + } +#endif +} + +JSBool +js_InitRuntimeStringState(JSContext *cx) +{ + JSRuntime *rt; + JSString *empty; + + rt = cx->runtime; + JS_ASSERT(!rt->emptyString); + + /* Make a permanently locked empty string. */ + empty = js_NewStringCopyN(cx, js_empty_ucstr, 0, GCF_LOCK); + if (!empty) + return JS_FALSE; + + /* Atomize it for scripts that use '' + x to convert x to string. */ + if (!js_AtomizeString(cx, empty, ATOM_PINNED)) + return JS_FALSE; + + rt->emptyString = empty; + return JS_TRUE; +} + +void +js_FinishRuntimeStringState(JSContext *cx) +{ + JSRuntime *rt = cx->runtime; + + js_UnlockGCThingRT(rt, rt->emptyString); + rt->emptyString = NULL; +} + +JSObject * +js_InitStringClass(JSContext *cx, JSObject *obj) +{ + JSObject *proto; + + /* Define the escape, unescape functions in the global object. */ + if (!JS_DefineFunctions(cx, obj, string_functions)) + return NULL; + + proto = JS_InitClass(cx, obj, NULL, &string_class, String, 1, + string_props, string_methods, + NULL, string_static_methods); + if (!proto) + return NULL; + OBJ_SET_SLOT(cx, proto, JSSLOT_PRIVATE, + STRING_TO_JSVAL(cx->runtime->emptyString)); + return proto; +} + +JSString * +js_NewString(JSContext *cx, jschar *chars, size_t length, uintN gcflag) +{ + JSString *str; + + if (length > JSSTRING_LENGTH_MASK) { + JS_ReportOutOfMemory(cx); + return NULL; + } + + str = (JSString *) js_AllocGCThing(cx, gcflag | GCX_STRING); + if (!str) + return NULL; + str->length = length; + str->chars = chars; +#ifdef DEBUG + { + JSRuntime *rt = cx->runtime; + JS_RUNTIME_METER(rt, liveStrings); + JS_RUNTIME_METER(rt, totalStrings); + JS_LOCK_RUNTIME_VOID(rt, + (rt->lengthSum += (double)length, + rt->lengthSquaredSum += (double)length * (double)length)); + } +#endif + return str; +} + +JSString * +js_NewDependentString(JSContext *cx, JSString *base, size_t start, + size_t length, uintN gcflag) +{ + JSDependentString *ds; + + if (length == 0) + return cx->runtime->emptyString; + + if (start > JSSTRDEP_START_MASK || + (start != 0 && length > JSSTRDEP_LENGTH_MASK)) { + return js_NewStringCopyN(cx, JSSTRING_CHARS(base) + start, length, + gcflag); + } + + ds = (JSDependentString *) js_AllocGCThing(cx, gcflag | GCX_MUTABLE_STRING); + if (!ds) + return NULL; + if (start == 0) { + JSPREFIX_SET_LENGTH(ds, length); + JSPREFIX_SET_BASE(ds, base); + } else { + JSSTRDEP_SET_START_AND_LENGTH(ds, start, length); + JSSTRDEP_SET_BASE(ds, base); + } +#ifdef DEBUG + { + JSRuntime *rt = cx->runtime; + JS_RUNTIME_METER(rt, liveDependentStrings); + JS_RUNTIME_METER(rt, totalDependentStrings); + JS_RUNTIME_METER(rt, liveStrings); + JS_RUNTIME_METER(rt, totalStrings); + JS_LOCK_RUNTIME_VOID(rt, + (rt->strdepLengthSum += (double)length, + rt->strdepLengthSquaredSum += (double)length * (double)length)); + JS_LOCK_RUNTIME_VOID(rt, + (rt->lengthSum += (double)length, + rt->lengthSquaredSum += (double)length * (double)length)); + } +#endif + return (JSString *)ds; +} + +#ifdef DEBUG +#include + +void printJSStringStats(JSRuntime *rt) { + double mean = 0., var = 0., sigma = 0.; + jsrefcount count = rt->totalStrings; + if (count > 0 && rt->lengthSum >= 0) { + mean = rt->lengthSum / count; + var = count * rt->lengthSquaredSum - rt->lengthSum * rt->lengthSum; + if (var < 0.0 || count <= 1) + var = 0.0; + else + var /= count * (count - 1); + + /* Windows says sqrt(0.0) is "-1.#J" (?!) so we must test. */ + sigma = (var != 0.) ? sqrt(var) : 0.; + } + fprintf(stderr, "%lu total strings, mean length %g (sigma %g)\n", + (unsigned long)count, mean, sigma); + + mean = var = sigma = 0.; + count = rt->totalDependentStrings; + if (count > 0 && rt->strdepLengthSum >= 0) { + mean = rt->strdepLengthSum / count; + var = count * rt->strdepLengthSquaredSum + - rt->strdepLengthSum * rt->strdepLengthSum; + if (var < 0.0 || count <= 1) + var = 0.0; + else + var /= count * (count - 1); + + /* Windows says sqrt(0.0) is "-1.#J" (?!) so we must test. */ + sigma = (var != 0.) ? sqrt(var) : 0.; + } + fprintf(stderr, "%lu total dependent strings, mean length %g (sigma %g)\n", + (unsigned long)count, mean, sigma); +} +#endif + +JSString * +js_NewStringCopyN(JSContext *cx, const jschar *s, size_t n, uintN gcflag) +{ + jschar *news; + JSString *str; + + news = (jschar *)JS_malloc(cx, (n + 1) * sizeof(jschar)); + if (!news) + return NULL; + js_strncpy(news, s, n); + news[n] = 0; + str = js_NewString(cx, news, n, gcflag); + if (!str) + JS_free(cx, news); + return str; +} + +JSString * +js_NewStringCopyZ(JSContext *cx, const jschar *s, uintN gcflag) +{ + size_t n, m; + jschar *news; + JSString *str; + + n = js_strlen(s); + m = (n + 1) * sizeof(jschar); + news = (jschar *) JS_malloc(cx, m); + if (!news) + return NULL; + memcpy(news, s, m); + str = js_NewString(cx, news, n, gcflag); + if (!str) + JS_free(cx, news); + return str; +} + +JS_STATIC_DLL_CALLBACK(JSHashNumber) +js_hash_string_pointer(const void *key) +{ + return (JSHashNumber)key >> JSVAL_TAGBITS; +} + +void +js_PurgeDeflatedStringCache(JSString *str) +{ + JSHashNumber hash; + JSHashEntry *he, **hep; + + if (!deflated_string_cache) + return; + + hash = js_hash_string_pointer(str); + JS_ACQUIRE_LOCK(deflated_string_cache_lock); + hep = JS_HashTableRawLookup(deflated_string_cache, hash, str); + he = *hep; + if (he) { +#ifdef DEBUG + deflated_string_cache_bytes -= JSSTRING_LENGTH(str); +#endif + free(he->value); + JS_HashTableRawRemove(deflated_string_cache, hep, he); + } + JS_RELEASE_LOCK(deflated_string_cache_lock); +} + +void +js_FinalizeString(JSContext *cx, JSString *str) +{ + js_FinalizeStringRT(cx->runtime, str); +} + +void +js_FinalizeStringRT(JSRuntime *rt, JSString *str) +{ + JSBool valid; + + JS_RUNTIME_UNMETER(rt, liveStrings); + if (JSSTRING_IS_DEPENDENT(str)) { + /* If JSSTRFLAG_DEPENDENT is set, this string must be valid. */ + JS_ASSERT(JSSTRDEP_BASE(str)); + JS_RUNTIME_UNMETER(rt, liveDependentStrings); + valid = JS_TRUE; + } else { + /* A stillborn string has null chars, so is not valid. */ + valid = (str->chars != NULL); + if (valid) + free(str->chars); + } + if (valid) { + js_PurgeDeflatedStringCache(str); + str->chars = NULL; + } + str->length = 0; +} + +JSObject * +js_StringToObject(JSContext *cx, JSString *str) +{ + JSObject *obj; + + obj = js_NewObject(cx, &string_class, NULL, NULL); + if (!obj) + return NULL; + OBJ_SET_SLOT(cx, obj, JSSLOT_PRIVATE, STRING_TO_JSVAL(str)); + return obj; +} + +JSString * +js_ValueToString(JSContext *cx, jsval v) +{ + JSObject *obj; + JSString *str; + + if (JSVAL_IS_OBJECT(v)) { + obj = JSVAL_TO_OBJECT(v); + if (!obj) + return ATOM_TO_STRING(cx->runtime->atomState.nullAtom); + if (!OBJ_DEFAULT_VALUE(cx, obj, JSTYPE_STRING, &v)) + return NULL; + } + if (JSVAL_IS_STRING(v)) { + str = JSVAL_TO_STRING(v); + } else if (JSVAL_IS_INT(v)) { + str = js_NumberToString(cx, JSVAL_TO_INT(v)); + } else if (JSVAL_IS_DOUBLE(v)) { + str = js_NumberToString(cx, *JSVAL_TO_DOUBLE(v)); + } else if (JSVAL_IS_BOOLEAN(v)) { + str = js_BooleanToString(cx, JSVAL_TO_BOOLEAN(v)); + } else { + str = ATOM_TO_STRING(cx->runtime->atomState.typeAtoms[JSTYPE_VOID]); + } + return str; +} + +JSString * +js_ValueToSource(JSContext *cx, jsval v) +{ + if (JSVAL_IS_STRING(v)) + return js_QuoteString(cx, JSVAL_TO_STRING(v), '"'); + if (JSVAL_IS_PRIMITIVE(v)) { + /* Special case to preserve negative zero, _contra_ toString. */ + if (JSVAL_IS_DOUBLE(v) && JSDOUBLE_IS_NEGZERO(*JSVAL_TO_DOUBLE(v))) { + /* NB: _ucNstr rather than _ucstr to indicate non-terminated. */ + static const jschar js_negzero_ucNstr[] = {'-', '0'}; + + return js_NewStringCopyN(cx, js_negzero_ucNstr, 2, 0); + } + } else { + if (!js_TryMethod(cx, JSVAL_TO_OBJECT(v), + cx->runtime->atomState.toSourceAtom, + 0, NULL, &v)) { + return NULL; + } + } + return js_ValueToString(cx, v); +} + +JSHashNumber +js_HashString(JSString *str) +{ + JSHashNumber h; + const jschar *s; + size_t n; + + h = 0; + for (s = JSSTRING_CHARS(str), n = JSSTRING_LENGTH(str); n; s++, n--) + h = (h >> (JS_HASH_BITS - 4)) ^ (h << 4) ^ *s; + return h; +} + +intN +js_CompareStrings(JSString *str1, JSString *str2) +{ + size_t l1, l2, n, i; + const jschar *s1, *s2; + intN cmp; + + l1 = JSSTRING_LENGTH(str1), l2 = JSSTRING_LENGTH(str2); + s1 = JSSTRING_CHARS(str1), s2 = JSSTRING_CHARS(str2); + n = JS_MIN(l1, l2); + for (i = 0; i < n; i++) { + cmp = s1[i] - s2[i]; + if (cmp != 0) + return cmp; + } + return (intN)(l1 - l2); +} + +size_t +js_strlen(const jschar *s) +{ + const jschar *t; + + for (t = s; *t != 0; t++) + continue; + return (size_t)(t - s); +} + +jschar * +js_strchr(const jschar *s, jschar c) +{ + while (*s != 0) { + if (*s == c) + return (jschar *)s; + s++; + } + return NULL; +} + +jschar * +js_strchr_limit(const jschar *s, jschar c, const jschar *limit) +{ + while (s < limit) { + if (*s == c) + return (jschar *)s; + s++; + } + return NULL; +} + +const jschar * +js_SkipWhiteSpace(const jschar *s) +{ + /* JS_ISSPACE is false on a null. */ + while (JS_ISSPACE(*s)) + s++; + return s; +} + +#define INFLATE_STRING_BODY \ + for (i = 0; i < length; i++) \ + chars[i] = (unsigned char) bytes[i]; \ + chars[i] = 0; + +void +js_InflateStringToBuffer(jschar *chars, const char *bytes, size_t length) +{ + size_t i; + + INFLATE_STRING_BODY +} + +jschar * +js_InflateString(JSContext *cx, const char *bytes, size_t length) +{ + jschar *chars; + size_t i; + + chars = (jschar *) JS_malloc(cx, (length + 1) * sizeof(jschar)); + if (!chars) + return NULL; + + INFLATE_STRING_BODY + + return chars; +} + +/* + * May be called with null cx by js_GetStringBytes, see below. + */ +char * +js_DeflateString(JSContext *cx, const jschar *chars, size_t length) +{ + size_t i, size; + char *bytes; + + size = (length + 1) * sizeof(char); + bytes = (char *) (cx ? JS_malloc(cx, size) : malloc(size)); + if (!bytes) + return NULL; + for (i = 0; i < length; i++) + bytes[i] = (char) chars[i]; + bytes[i] = 0; + return bytes; +} + +static JSHashTable * +GetDeflatedStringCache(void) +{ + JSHashTable *cache; + + cache = deflated_string_cache; + if (!cache) { + cache = JS_NewHashTable(8, js_hash_string_pointer, + JS_CompareValues, JS_CompareValues, + NULL, NULL); + deflated_string_cache = cache; + } + return cache; +} + +JSBool +js_SetStringBytes(JSString *str, char *bytes, size_t length) +{ + JSHashTable *cache; + JSBool ok; + JSHashNumber hash; + JSHashEntry **hep; + + JS_ACQUIRE_LOCK(deflated_string_cache_lock); + + cache = GetDeflatedStringCache(); + if (!cache) { + ok = JS_FALSE; + } else { + hash = js_hash_string_pointer(str); + hep = JS_HashTableRawLookup(cache, hash, str); + JS_ASSERT(*hep == NULL); + ok = JS_HashTableRawAdd(cache, hep, hash, str, bytes) != NULL; +#ifdef DEBUG + if (ok) + deflated_string_cache_bytes += length; +#endif + } + + JS_RELEASE_LOCK(deflated_string_cache_lock); + return ok; +} + +char * +js_GetStringBytes(JSString *str) +{ + JSHashTable *cache; + char *bytes; + JSHashNumber hash; + JSHashEntry *he, **hep; + + JS_ACQUIRE_LOCK(deflated_string_cache_lock); + + cache = GetDeflatedStringCache(); + if (!cache) { + bytes = NULL; + } else { + hash = js_hash_string_pointer(str); + hep = JS_HashTableRawLookup(cache, hash, str); + he = *hep; + if (he) { + bytes = (char *) he->value; + + /* Try to catch failure to JS_ShutDown between runtime epochs. */ + JS_ASSERT((*bytes == '\0' && JSSTRING_LENGTH(str) == 0) || + *bytes == (char) JSSTRING_CHARS(str)[0]); + } else { + bytes = js_DeflateString(NULL, JSSTRING_CHARS(str), + JSSTRING_LENGTH(str)); + if (bytes) { + if (JS_HashTableRawAdd(cache, hep, hash, str, bytes)) { +#ifdef DEBUG + deflated_string_cache_bytes += JSSTRING_LENGTH(str); +#endif + } else { + free(bytes); + bytes = NULL; + } + } + } + } + + JS_RELEASE_LOCK(deflated_string_cache_lock); + return bytes; +} + +/* + * From java.lang.Character.java: + * + * The character properties are currently encoded into 32 bits in the + * following manner: + * + * 10 bits signed offset used for converting case + * 1 bit if 1, adding the signed offset converts the character to + * lowercase + * 1 bit if 1, subtracting the signed offset converts the character to + * uppercase + * 1 bit if 1, character has a titlecase equivalent (possibly itself) + * 3 bits 0 may not be part of an identifier + * 1 ignorable control; may continue a Unicode identifier or JS + * identifier + * 2 may continue a JS identifier but not a Unicode identifier + * (unused) + * 3 may continue a Unicode identifier or JS identifier + * 4 is a JS whitespace character + * 5 may start or continue a JS identifier; + * may continue but not start a Unicode identifier (_) + * 6 may start or continue a JS identifier but not a Unicode + * identifier ($) + * 7 may start or continue a Unicode identifier or JS identifier + * Thus: + * 5, 6, 7 may start a JS identifier + * 1, 2, 3, 5, 6, 7 may continue a JS identifier + * 7 may start a Unicode identifier + * 1, 3, 5, 7 may continue a Unicode identifier + * 1 is ignorable within an identifier + * 4 is JS whitespace + * 2 bits 0 this character has no numeric property + * 1 adding the digit offset to the character code and then + * masking with 0x1F will produce the desired numeric value + * 2 this character has a "strange" numeric value + * 3 a JS supradecimal digit: adding the digit offset to the + * character code, then masking with 0x1F, then adding 10 + * will produce the desired numeric value + * 5 bits digit offset + * 4 bits reserved for future use + * 5 bits character type + */ + +/* The X table has 1024 entries for a total of 1024 bytes. */ + +const uint8 js_X[] = { + 0, 1, 2, 3, 4, 5, 6, 7, /* 0x0000 */ + 8, 9, 10, 11, 12, 13, 14, 15, /* 0x0200 */ + 16, 17, 18, 19, 20, 21, 22, 23, /* 0x0400 */ + 24, 25, 26, 27, 28, 28, 28, 28, /* 0x0600 */ + 28, 28, 28, 28, 29, 30, 31, 32, /* 0x0800 */ + 33, 34, 35, 36, 37, 38, 39, 40, /* 0x0A00 */ + 41, 42, 43, 44, 45, 46, 28, 28, /* 0x0C00 */ + 47, 48, 49, 50, 51, 52, 53, 28, /* 0x0E00 */ + 28, 28, 54, 55, 56, 57, 58, 59, /* 0x1000 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x1200 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x1400 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x1600 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x1800 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x1A00 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x1C00 */ + 60, 60, 61, 62, 63, 64, 65, 66, /* 0x1E00 */ + 67, 68, 69, 70, 71, 72, 73, 74, /* 0x2000 */ + 75, 75, 75, 76, 77, 78, 28, 28, /* 0x2200 */ + 79, 80, 81, 82, 83, 83, 84, 85, /* 0x2400 */ + 86, 85, 28, 28, 87, 88, 89, 28, /* 0x2600 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x2800 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x2A00 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x2C00 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x2E00 */ + 90, 91, 92, 93, 94, 56, 95, 28, /* 0x3000 */ + 96, 97, 98, 99, 83, 100, 83, 101, /* 0x3200 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x3400 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x3600 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x3800 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x3A00 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x3C00 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x3E00 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x4000 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x4200 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x4400 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x4600 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x4800 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x4A00 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x4C00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x4E00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x5000 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x5200 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x5400 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x5600 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x5800 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x5A00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x5C00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x5E00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x6000 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x6200 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x6400 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x6600 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x6800 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x6A00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x6C00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x6E00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x7000 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x7200 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x7400 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x7600 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x7800 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x7A00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x7C00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x7E00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x8000 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x8200 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x8400 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x8600 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x8800 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x8A00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x8C00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x8E00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x9000 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x9200 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x9400 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x9600 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x9800 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x9A00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x9C00 */ + 56, 56, 56, 56, 56, 56, 102, 28, /* 0x9E00 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0xA000 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0xA200 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0xA400 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0xA600 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0xA800 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0xAA00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xAC00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xAE00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xB000 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xB200 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xB400 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xB600 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xB800 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xBA00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xBC00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xBE00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xC000 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xC200 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xC400 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xC600 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xC800 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xCA00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xCC00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xCE00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xD000 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xD200 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xD400 */ + 56, 56, 56, 56, 56, 56, 103, 28, /* 0xD600 */ +104, 104, 104, 104, 104, 104, 104, 104, /* 0xD800 */ +104, 104, 104, 104, 104, 104, 104, 104, /* 0xDA00 */ +104, 104, 104, 104, 104, 104, 104, 104, /* 0xDC00 */ +104, 104, 104, 104, 104, 104, 104, 104, /* 0xDE00 */ +105, 105, 105, 105, 105, 105, 105, 105, /* 0xE000 */ +105, 105, 105, 105, 105, 105, 105, 105, /* 0xE200 */ +105, 105, 105, 105, 105, 105, 105, 105, /* 0xE400 */ +105, 105, 105, 105, 105, 105, 105, 105, /* 0xE600 */ +105, 105, 105, 105, 105, 105, 105, 105, /* 0xE800 */ +105, 105, 105, 105, 105, 105, 105, 105, /* 0xEA00 */ +105, 105, 105, 105, 105, 105, 105, 105, /* 0xEC00 */ +105, 105, 105, 105, 105, 105, 105, 105, /* 0xEE00 */ +105, 105, 105, 105, 105, 105, 105, 105, /* 0xF000 */ +105, 105, 105, 105, 105, 105, 105, 105, /* 0xF200 */ +105, 105, 105, 105, 105, 105, 105, 105, /* 0xF400 */ +105, 105, 105, 105, 105, 105, 105, 105, /* 0xF600 */ +105, 105, 105, 105, 56, 56, 56, 56, /* 0xF800 */ +106, 28, 28, 28, 107, 108, 109, 110, /* 0xFA00 */ + 56, 56, 56, 56, 111, 112, 113, 114, /* 0xFC00 */ +115, 116, 56, 117, 118, 119, 120, 121 /* 0xFE00 */ +}; + +/* The Y table has 7808 entries for a total of 7808 bytes. */ + +const uint8 js_Y[] = { + 0, 0, 0, 0, 0, 0, 0, 0, /* 0 */ + 0, 1, 1, 1, 1, 1, 0, 0, /* 0 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0 */ + 2, 3, 3, 3, 4, 3, 3, 3, /* 0 */ + 5, 6, 3, 7, 3, 8, 3, 3, /* 0 */ + 9, 9, 9, 9, 9, 9, 9, 9, /* 0 */ + 9, 9, 3, 3, 7, 7, 7, 3, /* 0 */ + 3, 10, 10, 10, 10, 10, 10, 10, /* 1 */ + 10, 10, 10, 10, 10, 10, 10, 10, /* 1 */ + 10, 10, 10, 10, 10, 10, 10, 10, /* 1 */ + 10, 10, 10, 5, 3, 6, 11, 12, /* 1 */ + 11, 13, 13, 13, 13, 13, 13, 13, /* 1 */ + 13, 13, 13, 13, 13, 13, 13, 13, /* 1 */ + 13, 13, 13, 13, 13, 13, 13, 13, /* 1 */ + 13, 13, 13, 5, 7, 6, 7, 0, /* 1 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 2 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 2 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 2 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 2 */ + 2, 3, 4, 4, 4, 4, 15, 15, /* 2 */ + 11, 15, 16, 5, 7, 8, 15, 11, /* 2 */ + 15, 7, 17, 17, 11, 16, 15, 3, /* 2 */ + 11, 18, 16, 6, 19, 19, 19, 3, /* 2 */ + 20, 20, 20, 20, 20, 20, 20, 20, /* 3 */ + 20, 20, 20, 20, 20, 20, 20, 20, /* 3 */ + 20, 20, 20, 20, 20, 20, 20, 7, /* 3 */ + 20, 20, 20, 20, 20, 20, 20, 16, /* 3 */ + 21, 21, 21, 21, 21, 21, 21, 21, /* 3 */ + 21, 21, 21, 21, 21, 21, 21, 21, /* 3 */ + 21, 21, 21, 21, 21, 21, 21, 7, /* 3 */ + 21, 21, 21, 21, 21, 21, 21, 22, /* 3 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 4 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 4 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 4 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 4 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 4 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 4 */ + 25, 26, 23, 24, 23, 24, 23, 24, /* 4 */ + 16, 23, 24, 23, 24, 23, 24, 23, /* 4 */ + 24, 23, 24, 23, 24, 23, 24, 23, /* 5 */ + 24, 16, 23, 24, 23, 24, 23, 24, /* 5 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 5 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 5 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 5 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 5 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 5 */ + 27, 23, 24, 23, 24, 23, 24, 28, /* 5 */ + 16, 29, 23, 24, 23, 24, 30, 23, /* 6 */ + 24, 31, 31, 23, 24, 16, 32, 32, /* 6 */ + 33, 23, 24, 31, 34, 16, 35, 36, /* 6 */ + 23, 24, 16, 16, 35, 37, 16, 38, /* 6 */ + 23, 24, 23, 24, 23, 24, 38, 23, /* 6 */ + 24, 39, 40, 16, 23, 24, 39, 23, /* 6 */ + 24, 41, 41, 23, 24, 23, 24, 42, /* 6 */ + 23, 24, 16, 40, 23, 24, 40, 40, /* 6 */ + 40, 40, 40, 40, 43, 44, 45, 43, /* 7 */ + 44, 45, 43, 44, 45, 23, 24, 23, /* 7 */ + 24, 23, 24, 23, 24, 23, 24, 23, /* 7 */ + 24, 23, 24, 23, 24, 16, 23, 24, /* 7 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 7 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 7 */ + 16, 43, 44, 45, 23, 24, 46, 46, /* 7 */ + 46, 46, 23, 24, 23, 24, 23, 24, /* 7 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 8 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 8 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 8 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 8 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 8 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 8 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 8 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 8 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 9 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 9 */ + 16, 16, 16, 47, 48, 16, 49, 49, /* 9 */ + 50, 50, 16, 51, 16, 16, 16, 16, /* 9 */ + 49, 16, 16, 52, 16, 16, 16, 16, /* 9 */ + 53, 54, 16, 16, 16, 16, 16, 54, /* 9 */ + 16, 16, 55, 16, 16, 16, 16, 16, /* 9 */ + 16, 16, 16, 16, 16, 16, 16, 16, /* 9 */ + 16, 16, 16, 56, 16, 16, 16, 16, /* 10 */ + 56, 16, 57, 57, 16, 16, 16, 16, /* 10 */ + 16, 16, 58, 16, 16, 16, 16, 16, /* 10 */ + 16, 16, 16, 16, 16, 16, 16, 16, /* 10 */ + 16, 16, 16, 16, 16, 16, 16, 16, /* 10 */ + 16, 46, 46, 46, 46, 46, 46, 46, /* 10 */ + 59, 59, 59, 59, 59, 59, 59, 59, /* 10 */ + 59, 11, 11, 59, 59, 59, 59, 59, /* 10 */ + 59, 59, 11, 11, 11, 11, 11, 11, /* 11 */ + 11, 11, 11, 11, 11, 11, 11, 11, /* 11 */ + 59, 59, 11, 11, 11, 11, 11, 11, /* 11 */ + 11, 11, 11, 11, 11, 11, 11, 46, /* 11 */ + 59, 59, 59, 59, 59, 11, 11, 11, /* 11 */ + 11, 11, 46, 46, 46, 46, 46, 46, /* 11 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 11 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 11 */ + 60, 60, 60, 60, 60, 60, 60, 60, /* 12 */ + 60, 60, 60, 60, 60, 60, 60, 60, /* 12 */ + 60, 60, 60, 60, 60, 60, 60, 60, /* 12 */ + 60, 60, 60, 60, 60, 60, 60, 60, /* 12 */ + 60, 60, 60, 60, 60, 60, 60, 60, /* 12 */ + 60, 60, 60, 60, 60, 60, 60, 60, /* 12 */ + 60, 60, 60, 60, 60, 60, 60, 60, /* 12 */ + 60, 60, 60, 60, 60, 60, 60, 60, /* 12 */ + 60, 60, 60, 60, 60, 60, 46, 46, /* 13 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 13 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 13 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 13 */ + 60, 60, 46, 46, 46, 46, 46, 46, /* 13 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 13 */ + 46, 46, 46, 46, 3, 3, 46, 46, /* 13 */ + 46, 46, 59, 46, 46, 46, 3, 46, /* 13 */ + 46, 46, 46, 46, 11, 11, 61, 3, /* 14 */ + 62, 62, 62, 46, 63, 46, 64, 64, /* 14 */ + 16, 20, 20, 20, 20, 20, 20, 20, /* 14 */ + 20, 20, 20, 20, 20, 20, 20, 20, /* 14 */ + 20, 20, 46, 20, 20, 20, 20, 20, /* 14 */ + 20, 20, 20, 20, 65, 66, 66, 66, /* 14 */ + 16, 21, 21, 21, 21, 21, 21, 21, /* 14 */ + 21, 21, 21, 21, 21, 21, 21, 21, /* 14 */ + 21, 21, 16, 21, 21, 21, 21, 21, /* 15 */ + 21, 21, 21, 21, 67, 68, 68, 46, /* 15 */ + 69, 70, 38, 38, 38, 71, 72, 46, /* 15 */ + 46, 46, 38, 46, 38, 46, 38, 46, /* 15 */ + 38, 46, 23, 24, 23, 24, 23, 24, /* 15 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 15 */ + 73, 74, 16, 40, 46, 46, 46, 46, /* 15 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 15 */ + 46, 75, 75, 75, 75, 75, 75, 75, /* 16 */ + 75, 75, 75, 75, 75, 46, 75, 75, /* 16 */ + 20, 20, 20, 20, 20, 20, 20, 20, /* 16 */ + 20, 20, 20, 20, 20, 20, 20, 20, /* 16 */ + 20, 20, 20, 20, 20, 20, 20, 20, /* 16 */ + 20, 20, 20, 20, 20, 20, 20, 20, /* 16 */ + 21, 21, 21, 21, 21, 21, 21, 21, /* 16 */ + 21, 21, 21, 21, 21, 21, 21, 21, /* 16 */ + 21, 21, 21, 21, 21, 21, 21, 21, /* 17 */ + 21, 21, 21, 21, 21, 21, 21, 21, /* 17 */ + 46, 74, 74, 74, 74, 74, 74, 74, /* 17 */ + 74, 74, 74, 74, 74, 46, 74, 74, /* 17 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 17 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 17 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 17 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 17 */ + 23, 24, 15, 60, 60, 60, 60, 46, /* 18 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 18 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 18 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 18 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 18 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 18 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 18 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 18 */ + 40, 23, 24, 23, 24, 46, 46, 23, /* 19 */ + 24, 46, 46, 23, 24, 46, 46, 46, /* 19 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 19 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 19 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 19 */ + 23, 24, 23, 24, 46, 46, 23, 24, /* 19 */ + 23, 24, 23, 24, 23, 24, 46, 46, /* 19 */ + 23, 24, 46, 46, 46, 46, 46, 46, /* 19 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 20 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 20 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 20 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 20 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 20 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 20 */ + 46, 76, 76, 76, 76, 76, 76, 76, /* 20 */ + 76, 76, 76, 76, 76, 76, 76, 76, /* 20 */ + 76, 76, 76, 76, 76, 76, 76, 76, /* 21 */ + 76, 76, 76, 76, 76, 76, 76, 76, /* 21 */ + 76, 76, 76, 76, 76, 76, 76, 46, /* 21 */ + 46, 59, 3, 3, 3, 3, 3, 3, /* 21 */ + 46, 77, 77, 77, 77, 77, 77, 77, /* 21 */ + 77, 77, 77, 77, 77, 77, 77, 77, /* 21 */ + 77, 77, 77, 77, 77, 77, 77, 77, /* 21 */ + 77, 77, 77, 77, 77, 77, 77, 77, /* 21 */ + 77, 77, 77, 77, 77, 77, 77, 16, /* 22 */ + 46, 3, 46, 46, 46, 46, 46, 46, /* 22 */ + 46, 60, 60, 60, 60, 60, 60, 60, /* 22 */ + 60, 60, 60, 60, 60, 60, 60, 60, /* 22 */ + 60, 60, 46, 60, 60, 60, 60, 60, /* 22 */ + 60, 60, 60, 60, 60, 60, 60, 60, /* 22 */ + 60, 60, 60, 60, 60, 60, 60, 60, /* 22 */ + 60, 60, 46, 60, 60, 60, 3, 60, /* 22 */ + 3, 60, 60, 3, 60, 46, 46, 46, /* 23 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 23 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 23 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 23 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 23 */ + 40, 40, 40, 46, 46, 46, 46, 46, /* 23 */ + 40, 40, 40, 3, 3, 46, 46, 46, /* 23 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 23 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 24 */ + 46, 46, 46, 46, 3, 46, 46, 46, /* 24 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 24 */ + 46, 46, 46, 3, 46, 46, 46, 3, /* 24 */ + 46, 40, 40, 40, 40, 40, 40, 40, /* 24 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 24 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 24 */ + 40, 40, 40, 46, 46, 46, 46, 46, /* 24 */ + 59, 40, 40, 40, 40, 40, 40, 40, /* 25 */ + 40, 40, 40, 60, 60, 60, 60, 60, /* 25 */ + 60, 60, 60, 46, 46, 46, 46, 46, /* 25 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 25 */ + 78, 78, 78, 78, 78, 78, 78, 78, /* 25 */ + 78, 78, 3, 3, 3, 3, 46, 46, /* 25 */ + 60, 40, 40, 40, 40, 40, 40, 40, /* 25 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 25 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 26 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 26 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 26 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 26 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 26 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 26 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 26 */ + 46, 46, 40, 40, 40, 40, 40, 46, /* 26 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 27 */ + 40, 40, 40, 40, 40, 40, 40, 46, /* 27 */ + 40, 40, 40, 40, 3, 40, 60, 60, /* 27 */ + 60, 60, 60, 60, 60, 79, 79, 60, /* 27 */ + 60, 60, 60, 60, 60, 59, 59, 60, /* 27 */ + 60, 15, 60, 60, 60, 60, 46, 46, /* 27 */ + 9, 9, 9, 9, 9, 9, 9, 9, /* 27 */ + 9, 9, 46, 46, 46, 46, 46, 46, /* 27 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 28 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 28 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 28 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 28 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 28 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 28 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 28 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 28 */ + 46, 60, 60, 80, 46, 40, 40, 40, /* 29 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 29 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 29 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 29 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 29 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 29 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 29 */ + 40, 40, 46, 46, 60, 40, 80, 80, /* 29 */ + 80, 60, 60, 60, 60, 60, 60, 60, /* 30 */ + 60, 80, 80, 80, 80, 60, 46, 46, /* 30 */ + 15, 60, 60, 60, 60, 46, 46, 46, /* 30 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 30 */ + 40, 40, 60, 60, 3, 3, 81, 81, /* 30 */ + 81, 81, 81, 81, 81, 81, 81, 81, /* 30 */ + 3, 46, 46, 46, 46, 46, 46, 46, /* 30 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 30 */ + 46, 60, 80, 80, 46, 40, 40, 40, /* 31 */ + 40, 40, 40, 40, 40, 46, 46, 40, /* 31 */ + 40, 46, 46, 40, 40, 40, 40, 40, /* 31 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 31 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 31 */ + 40, 46, 40, 40, 40, 40, 40, 40, /* 31 */ + 40, 46, 40, 46, 46, 46, 40, 40, /* 31 */ + 40, 40, 46, 46, 60, 46, 80, 80, /* 31 */ + 80, 60, 60, 60, 60, 46, 46, 80, /* 32 */ + 80, 46, 46, 80, 80, 60, 46, 46, /* 32 */ + 46, 46, 46, 46, 46, 46, 46, 80, /* 32 */ + 46, 46, 46, 46, 40, 40, 46, 40, /* 32 */ + 40, 40, 60, 60, 46, 46, 81, 81, /* 32 */ + 81, 81, 81, 81, 81, 81, 81, 81, /* 32 */ + 40, 40, 4, 4, 82, 82, 82, 82, /* 32 */ + 19, 83, 15, 46, 46, 46, 46, 46, /* 32 */ + 46, 46, 60, 46, 46, 40, 40, 40, /* 33 */ + 40, 40, 40, 46, 46, 46, 46, 40, /* 33 */ + 40, 46, 46, 40, 40, 40, 40, 40, /* 33 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 33 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 33 */ + 40, 46, 40, 40, 40, 40, 40, 40, /* 33 */ + 40, 46, 40, 40, 46, 40, 40, 46, /* 33 */ + 40, 40, 46, 46, 60, 46, 80, 80, /* 33 */ + 80, 60, 60, 46, 46, 46, 46, 60, /* 34 */ + 60, 46, 46, 60, 60, 60, 46, 46, /* 34 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 34 */ + 46, 40, 40, 40, 40, 46, 40, 46, /* 34 */ + 46, 46, 46, 46, 46, 46, 81, 81, /* 34 */ + 81, 81, 81, 81, 81, 81, 81, 81, /* 34 */ + 60, 60, 40, 40, 40, 46, 46, 46, /* 34 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 34 */ + 46, 60, 60, 80, 46, 40, 40, 40, /* 35 */ + 40, 40, 40, 40, 46, 40, 46, 40, /* 35 */ + 40, 40, 46, 40, 40, 40, 40, 40, /* 35 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 35 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 35 */ + 40, 46, 40, 40, 40, 40, 40, 40, /* 35 */ + 40, 46, 40, 40, 46, 40, 40, 40, /* 35 */ + 40, 40, 46, 46, 60, 40, 80, 80, /* 35 */ + 80, 60, 60, 60, 60, 60, 46, 60, /* 36 */ + 60, 80, 46, 80, 80, 60, 46, 46, /* 36 */ + 15, 46, 46, 46, 46, 46, 46, 46, /* 36 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 36 */ + 40, 46, 46, 46, 46, 46, 81, 81, /* 36 */ + 81, 81, 81, 81, 81, 81, 81, 81, /* 36 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 36 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 36 */ + 46, 60, 80, 80, 46, 40, 40, 40, /* 37 */ + 40, 40, 40, 40, 40, 46, 46, 40, /* 37 */ + 40, 46, 46, 40, 40, 40, 40, 40, /* 37 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 37 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 37 */ + 40, 46, 40, 40, 40, 40, 40, 40, /* 37 */ + 40, 46, 40, 40, 46, 46, 40, 40, /* 37 */ + 40, 40, 46, 46, 60, 40, 80, 60, /* 37 */ + 80, 60, 60, 60, 46, 46, 46, 80, /* 38 */ + 80, 46, 46, 80, 80, 60, 46, 46, /* 38 */ + 46, 46, 46, 46, 46, 46, 60, 80, /* 38 */ + 46, 46, 46, 46, 40, 40, 46, 40, /* 38 */ + 40, 40, 46, 46, 46, 46, 81, 81, /* 38 */ + 81, 81, 81, 81, 81, 81, 81, 81, /* 38 */ + 15, 46, 46, 46, 46, 46, 46, 46, /* 38 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 38 */ + 46, 46, 60, 80, 46, 40, 40, 40, /* 39 */ + 40, 40, 40, 46, 46, 46, 40, 40, /* 39 */ + 40, 46, 40, 40, 40, 40, 46, 46, /* 39 */ + 46, 40, 40, 46, 40, 46, 40, 40, /* 39 */ + 46, 46, 46, 40, 40, 46, 46, 46, /* 39 */ + 40, 40, 40, 46, 46, 46, 40, 40, /* 39 */ + 40, 40, 40, 40, 40, 40, 46, 40, /* 39 */ + 40, 40, 46, 46, 46, 46, 80, 80, /* 39 */ + 60, 80, 80, 46, 46, 46, 80, 80, /* 40 */ + 80, 46, 80, 80, 80, 60, 46, 46, /* 40 */ + 46, 46, 46, 46, 46, 46, 46, 80, /* 40 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 40 */ + 46, 46, 46, 46, 46, 46, 46, 81, /* 40 */ + 81, 81, 81, 81, 81, 81, 81, 81, /* 40 */ + 84, 19, 19, 46, 46, 46, 46, 46, /* 40 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 40 */ + 46, 80, 80, 80, 46, 40, 40, 40, /* 41 */ + 40, 40, 40, 40, 40, 46, 40, 40, /* 41 */ + 40, 46, 40, 40, 40, 40, 40, 40, /* 41 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 41 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 41 */ + 40, 46, 40, 40, 40, 40, 40, 40, /* 41 */ + 40, 40, 40, 40, 46, 40, 40, 40, /* 41 */ + 40, 40, 46, 46, 46, 46, 60, 60, /* 41 */ + 60, 80, 80, 80, 80, 46, 60, 60, /* 42 */ + 60, 46, 60, 60, 60, 60, 46, 46, /* 42 */ + 46, 46, 46, 46, 46, 60, 60, 46, /* 42 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 42 */ + 40, 40, 46, 46, 46, 46, 81, 81, /* 42 */ + 81, 81, 81, 81, 81, 81, 81, 81, /* 42 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 42 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 42 */ + 46, 46, 80, 80, 46, 40, 40, 40, /* 43 */ + 40, 40, 40, 40, 40, 46, 40, 40, /* 43 */ + 40, 46, 40, 40, 40, 40, 40, 40, /* 43 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 43 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 43 */ + 40, 46, 40, 40, 40, 40, 40, 40, /* 43 */ + 40, 40, 40, 40, 46, 40, 40, 40, /* 43 */ + 40, 40, 46, 46, 46, 46, 80, 60, /* 43 */ + 80, 80, 80, 80, 80, 46, 60, 80, /* 44 */ + 80, 46, 80, 80, 60, 60, 46, 46, /* 44 */ + 46, 46, 46, 46, 46, 80, 80, 46, /* 44 */ + 46, 46, 46, 46, 46, 46, 40, 46, /* 44 */ + 40, 40, 46, 46, 46, 46, 81, 81, /* 44 */ + 81, 81, 81, 81, 81, 81, 81, 81, /* 44 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 44 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 44 */ + 46, 46, 80, 80, 46, 40, 40, 40, /* 45 */ + 40, 40, 40, 40, 40, 46, 40, 40, /* 45 */ + 40, 46, 40, 40, 40, 40, 40, 40, /* 45 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 45 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 45 */ + 40, 46, 40, 40, 40, 40, 40, 40, /* 45 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 45 */ + 40, 40, 46, 46, 46, 46, 80, 80, /* 45 */ + 80, 60, 60, 60, 46, 46, 80, 80, /* 46 */ + 80, 46, 80, 80, 80, 60, 46, 46, /* 46 */ + 46, 46, 46, 46, 46, 46, 46, 80, /* 46 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 46 */ + 40, 40, 46, 46, 46, 46, 81, 81, /* 46 */ + 81, 81, 81, 81, 81, 81, 81, 81, /* 46 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 46 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 46 */ + 46, 40, 40, 40, 40, 40, 40, 40, /* 47 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 47 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 47 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 47 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 47 */ + 40, 40, 40, 40, 40, 40, 40, 3, /* 47 */ + 40, 60, 40, 40, 60, 60, 60, 60, /* 47 */ + 60, 60, 60, 46, 46, 46, 46, 4, /* 47 */ + 40, 40, 40, 40, 40, 40, 59, 60, /* 48 */ + 60, 60, 60, 60, 60, 60, 60, 15, /* 48 */ + 9, 9, 9, 9, 9, 9, 9, 9, /* 48 */ + 9, 9, 3, 3, 46, 46, 46, 46, /* 48 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 48 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 48 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 48 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 48 */ + 46, 40, 40, 46, 40, 46, 46, 40, /* 49 */ + 40, 46, 40, 46, 46, 40, 46, 46, /* 49 */ + 46, 46, 46, 46, 40, 40, 40, 40, /* 49 */ + 46, 40, 40, 40, 40, 40, 40, 40, /* 49 */ + 46, 40, 40, 40, 46, 40, 46, 40, /* 49 */ + 46, 46, 40, 40, 46, 40, 40, 3, /* 49 */ + 40, 60, 40, 40, 60, 60, 60, 60, /* 49 */ + 60, 60, 46, 60, 60, 40, 46, 46, /* 49 */ + 40, 40, 40, 40, 40, 46, 59, 46, /* 50 */ + 60, 60, 60, 60, 60, 60, 46, 46, /* 50 */ + 9, 9, 9, 9, 9, 9, 9, 9, /* 50 */ + 9, 9, 46, 46, 40, 40, 46, 46, /* 50 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 50 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 50 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 50 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 50 */ + 15, 15, 15, 15, 3, 3, 3, 3, /* 51 */ + 3, 3, 3, 3, 3, 3, 3, 3, /* 51 */ + 3, 3, 3, 15, 15, 15, 15, 15, /* 51 */ + 60, 60, 15, 15, 15, 15, 15, 15, /* 51 */ + 78, 78, 78, 78, 78, 78, 78, 78, /* 51 */ + 78, 78, 85, 85, 85, 85, 85, 85, /* 51 */ + 85, 85, 85, 85, 15, 60, 15, 60, /* 51 */ + 15, 60, 5, 6, 5, 6, 80, 80, /* 51 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 52 */ + 46, 40, 40, 40, 40, 40, 40, 40, /* 52 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 52 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 52 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 52 */ + 40, 40, 46, 46, 46, 46, 46, 46, /* 52 */ + 46, 60, 60, 60, 60, 60, 60, 60, /* 52 */ + 60, 60, 60, 60, 60, 60, 60, 80, /* 52 */ + 60, 60, 60, 60, 60, 3, 60, 60, /* 53 */ + 60, 60, 60, 60, 46, 46, 46, 46, /* 53 */ + 60, 60, 60, 60, 60, 60, 46, 60, /* 53 */ + 46, 60, 60, 60, 60, 60, 60, 60, /* 53 */ + 60, 60, 60, 60, 60, 60, 60, 60, /* 53 */ + 60, 60, 60, 60, 60, 60, 46, 46, /* 53 */ + 46, 60, 60, 60, 60, 60, 60, 60, /* 53 */ + 46, 60, 46, 46, 46, 46, 46, 46, /* 53 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 54 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 54 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 54 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 54 */ + 76, 76, 76, 76, 76, 76, 76, 76, /* 54 */ + 76, 76, 76, 76, 76, 76, 76, 76, /* 54 */ + 76, 76, 76, 76, 76, 76, 76, 76, /* 54 */ + 76, 76, 76, 76, 76, 76, 76, 76, /* 54 */ + 76, 76, 76, 76, 76, 76, 46, 46, /* 55 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 55 */ + 16, 16, 16, 16, 16, 16, 16, 16, /* 55 */ + 16, 16, 16, 16, 16, 16, 16, 16, /* 55 */ + 16, 16, 16, 16, 16, 16, 16, 16, /* 55 */ + 16, 16, 16, 16, 16, 16, 16, 16, /* 55 */ + 16, 16, 16, 16, 16, 16, 16, 46, /* 55 */ + 46, 46, 46, 3, 46, 46, 46, 46, /* 55 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 56 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 56 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 56 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 56 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 56 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 56 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 56 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 56 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 57 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 57 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 57 */ + 40, 40, 46, 46, 46, 46, 46, 40, /* 57 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 57 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 57 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 57 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 57 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 58 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 58 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 58 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 58 */ + 40, 40, 40, 46, 46, 46, 46, 46, /* 58 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 58 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 58 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 58 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 59 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 59 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 59 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 59 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 59 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 59 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 59 */ + 40, 40, 46, 46, 46, 46, 46, 46, /* 59 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 60 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 60 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 60 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 60 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 60 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 60 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 60 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 60 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 61 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 61 */ + 23, 24, 23, 24, 23, 24, 16, 16, /* 61 */ + 16, 16, 16, 16, 46, 46, 46, 46, /* 61 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 61 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 61 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 61 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 61 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 62 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 62 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 62 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 62 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 62 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 62 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 62 */ + 23, 24, 46, 46, 46, 46, 46, 46, /* 62 */ + 86, 86, 86, 86, 86, 86, 86, 86, /* 63 */ + 87, 87, 87, 87, 87, 87, 87, 87, /* 63 */ + 86, 86, 86, 86, 86, 86, 46, 46, /* 63 */ + 87, 87, 87, 87, 87, 87, 46, 46, /* 63 */ + 86, 86, 86, 86, 86, 86, 86, 86, /* 63 */ + 87, 87, 87, 87, 87, 87, 87, 87, /* 63 */ + 86, 86, 86, 86, 86, 86, 86, 86, /* 63 */ + 87, 87, 87, 87, 87, 87, 87, 87, /* 63 */ + 86, 86, 86, 86, 86, 86, 46, 46, /* 64 */ + 87, 87, 87, 87, 87, 87, 46, 46, /* 64 */ + 16, 86, 16, 86, 16, 86, 16, 86, /* 64 */ + 46, 87, 46, 87, 46, 87, 46, 87, /* 64 */ + 86, 86, 86, 86, 86, 86, 86, 86, /* 64 */ + 87, 87, 87, 87, 87, 87, 87, 87, /* 64 */ + 88, 88, 89, 89, 89, 89, 90, 90, /* 64 */ + 91, 91, 92, 92, 93, 93, 46, 46, /* 64 */ + 86, 86, 86, 86, 86, 86, 86, 86, /* 65 */ + 87, 87, 87, 87, 87, 87, 87, 87, /* 65 */ + 86, 86, 86, 86, 86, 86, 86, 86, /* 65 */ + 87, 87, 87, 87, 87, 87, 87, 87, /* 65 */ + 86, 86, 86, 86, 86, 86, 86, 86, /* 65 */ + 87, 87, 87, 87, 87, 87, 87, 87, /* 65 */ + 86, 86, 16, 94, 16, 46, 16, 16, /* 65 */ + 87, 87, 95, 95, 96, 11, 38, 11, /* 65 */ + 11, 11, 16, 94, 16, 46, 16, 16, /* 66 */ + 97, 97, 97, 97, 96, 11, 11, 11, /* 66 */ + 86, 86, 16, 16, 46, 46, 16, 16, /* 66 */ + 87, 87, 98, 98, 46, 11, 11, 11, /* 66 */ + 86, 86, 16, 16, 16, 99, 16, 16, /* 66 */ + 87, 87, 100, 100, 101, 11, 11, 11, /* 66 */ + 46, 46, 16, 94, 16, 46, 16, 16, /* 66 */ +102, 102, 103, 103, 96, 11, 11, 46, /* 66 */ + 2, 2, 2, 2, 2, 2, 2, 2, /* 67 */ + 2, 2, 2, 2, 104, 104, 104, 104, /* 67 */ + 8, 8, 8, 8, 8, 8, 3, 3, /* 67 */ + 5, 6, 5, 5, 5, 6, 5, 5, /* 67 */ + 3, 3, 3, 3, 3, 3, 3, 3, /* 67 */ +105, 106, 104, 104, 104, 104, 104, 46, /* 67 */ + 3, 3, 3, 3, 3, 3, 3, 3, /* 67 */ + 3, 5, 6, 3, 3, 3, 3, 12, /* 67 */ + 12, 3, 3, 3, 7, 5, 6, 46, /* 68 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 68 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 68 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 68 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 68 */ + 46, 46, 104, 104, 104, 104, 104, 104, /* 68 */ + 17, 46, 46, 46, 17, 17, 17, 17, /* 68 */ + 17, 17, 7, 7, 7, 5, 6, 16, /* 68 */ +107, 107, 107, 107, 107, 107, 107, 107, /* 69 */ +107, 107, 7, 7, 7, 5, 6, 46, /* 69 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 69 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 69 */ + 4, 4, 4, 4, 4, 4, 4, 4, /* 69 */ + 4, 4, 4, 4, 46, 46, 46, 46, /* 69 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 69 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 69 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 70 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 70 */ + 60, 60, 60, 60, 60, 60, 60, 60, /* 70 */ + 60, 60, 60, 60, 60, 79, 79, 79, /* 70 */ + 79, 60, 46, 46, 46, 46, 46, 46, /* 70 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 70 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 70 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 70 */ + 15, 15, 38, 15, 15, 15, 15, 38, /* 71 */ + 15, 15, 16, 38, 38, 38, 16, 16, /* 71 */ + 38, 38, 38, 16, 15, 38, 15, 15, /* 71 */ + 38, 38, 38, 38, 38, 38, 15, 15, /* 71 */ + 15, 15, 15, 15, 38, 15, 38, 15, /* 71 */ + 38, 15, 38, 38, 38, 38, 16, 16, /* 71 */ + 38, 38, 15, 38, 16, 40, 40, 40, /* 71 */ + 40, 46, 46, 46, 46, 46, 46, 46, /* 71 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 72 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 72 */ + 46, 46, 46, 19, 19, 19, 19, 19, /* 72 */ + 19, 19, 19, 19, 19, 19, 19, 108, /* 72 */ +109, 109, 109, 109, 109, 109, 109, 109, /* 72 */ +109, 109, 109, 109, 110, 110, 110, 110, /* 72 */ +111, 111, 111, 111, 111, 111, 111, 111, /* 72 */ +111, 111, 111, 111, 112, 112, 112, 112, /* 72 */ +113, 113, 113, 46, 46, 46, 46, 46, /* 73 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 73 */ + 7, 7, 7, 7, 7, 15, 15, 15, /* 73 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 73 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 73 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 73 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 73 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 73 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 74 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 74 */ + 15, 15, 7, 15, 7, 15, 15, 15, /* 74 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 74 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 74 */ + 15, 15, 15, 46, 46, 46, 46, 46, /* 74 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 74 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 74 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 75 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 75 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 75 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 75 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 75 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 75 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 75 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 75 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 76 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 76 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 76 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 76 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 76 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 76 */ + 7, 7, 46, 46, 46, 46, 46, 46, /* 76 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 76 */ + 15, 46, 15, 15, 15, 15, 15, 15, /* 77 */ + 7, 7, 7, 7, 15, 15, 15, 15, /* 77 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 77 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 77 */ + 7, 7, 15, 15, 15, 15, 15, 15, /* 77 */ + 15, 5, 6, 15, 15, 15, 15, 15, /* 77 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 77 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 77 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 78 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 78 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 78 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 78 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 78 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 78 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 78 */ + 15, 15, 15, 46, 46, 46, 46, 46, /* 78 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 79 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 79 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 79 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 79 */ + 15, 15, 15, 15, 15, 46, 46, 46, /* 79 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 79 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 79 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 79 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 80 */ + 15, 15, 15, 46, 46, 46, 46, 46, /* 80 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 80 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 80 */ +114, 114, 114, 114, 114, 114, 114, 114, /* 80 */ +114, 114, 114, 114, 114, 114, 114, 114, /* 80 */ +114, 114, 114, 114, 82, 82, 82, 82, /* 80 */ + 82, 82, 82, 82, 82, 82, 82, 82, /* 80 */ + 82, 82, 82, 82, 82, 82, 82, 82, /* 81 */ +115, 115, 115, 115, 115, 115, 115, 115, /* 81 */ +115, 115, 115, 115, 115, 115, 115, 115, /* 81 */ +115, 115, 115, 115, 15, 15, 15, 15, /* 81 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 81 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 81 */ + 15, 15, 15, 15, 15, 15, 116, 116, /* 81 */ +116, 116, 116, 116, 116, 116, 116, 116, /* 81 */ +116, 116, 116, 116, 116, 116, 116, 116, /* 82 */ +116, 116, 116, 116, 116, 116, 116, 116, /* 82 */ +117, 117, 117, 117, 117, 117, 117, 117, /* 82 */ +117, 117, 117, 117, 117, 117, 117, 117, /* 82 */ +117, 117, 117, 117, 117, 117, 117, 117, /* 82 */ +117, 117, 118, 46, 46, 46, 46, 46, /* 82 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 82 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 82 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 83 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 83 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 83 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 83 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 83 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 83 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 83 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 83 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 84 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 84 */ + 15, 15, 15, 15, 15, 15, 46, 46, /* 84 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 84 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 84 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 84 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 84 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 84 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 85 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 85 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 85 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 85 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 85 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 85 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 85 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 85 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 86 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 86 */ + 15, 15, 15, 15, 46, 46, 46, 46, /* 86 */ + 46, 46, 15, 15, 15, 15, 15, 15, /* 86 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 86 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 86 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 86 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 86 */ + 46, 15, 15, 15, 15, 46, 15, 15, /* 87 */ + 15, 15, 46, 46, 15, 15, 15, 15, /* 87 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 87 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 87 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 87 */ + 46, 15, 15, 15, 15, 15, 15, 15, /* 87 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 87 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 87 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 88 */ + 15, 15, 15, 15, 46, 15, 46, 15, /* 88 */ + 15, 15, 15, 46, 46, 46, 15, 46, /* 88 */ + 15, 15, 15, 15, 15, 15, 15, 46, /* 88 */ + 46, 15, 15, 15, 15, 15, 15, 15, /* 88 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 88 */ + 46, 46, 46, 46, 46, 46, 119, 119, /* 88 */ +119, 119, 119, 119, 119, 119, 119, 119, /* 88 */ +114, 114, 114, 114, 114, 114, 114, 114, /* 89 */ +114, 114, 83, 83, 83, 83, 83, 83, /* 89 */ + 83, 83, 83, 83, 15, 46, 46, 46, /* 89 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 89 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 89 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 89 */ + 46, 15, 15, 15, 15, 15, 15, 15, /* 89 */ + 15, 15, 15, 15, 15, 15, 15, 46, /* 89 */ + 2, 3, 3, 3, 15, 59, 3, 120, /* 90 */ + 5, 6, 5, 6, 5, 6, 5, 6, /* 90 */ + 5, 6, 15, 15, 5, 6, 5, 6, /* 90 */ + 5, 6, 5, 6, 8, 5, 6, 5, /* 90 */ + 15, 121, 121, 121, 121, 121, 121, 121, /* 90 */ +121, 121, 60, 60, 60, 60, 60, 60, /* 90 */ + 8, 59, 59, 59, 59, 59, 15, 15, /* 90 */ + 46, 46, 46, 46, 46, 46, 46, 15, /* 90 */ + 46, 40, 40, 40, 40, 40, 40, 40, /* 91 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 91 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 91 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 91 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 91 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 91 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 91 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 91 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 92 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 92 */ + 40, 40, 40, 40, 40, 46, 46, 46, /* 92 */ + 46, 60, 60, 59, 59, 59, 59, 46, /* 92 */ + 46, 40, 40, 40, 40, 40, 40, 40, /* 92 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 92 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 92 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 92 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 93 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 93 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 93 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 93 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 93 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 93 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 93 */ + 40, 40, 40, 3, 59, 59, 59, 46, /* 93 */ + 46, 46, 46, 46, 46, 40, 40, 40, /* 94 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 94 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 94 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 94 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 94 */ + 40, 40, 40, 40, 40, 46, 46, 46, /* 94 */ + 46, 40, 40, 40, 40, 40, 40, 40, /* 94 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 94 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 95 */ + 40, 40, 40, 40, 40, 40, 40, 46, /* 95 */ + 15, 15, 85, 85, 85, 85, 15, 15, /* 95 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 95 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 95 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 95 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 95 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 95 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 96 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 96 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 96 */ + 15, 15, 15, 15, 15, 46, 46, 46, /* 96 */ + 85, 85, 85, 85, 85, 85, 85, 85, /* 96 */ + 85, 85, 15, 15, 15, 15, 15, 15, /* 96 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 96 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 96 */ + 15, 15, 15, 15, 46, 46, 46, 46, /* 97 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 97 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 97 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 97 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 97 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 97 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 97 */ + 15, 15, 15, 15, 46, 46, 46, 15, /* 97 */ +114, 114, 114, 114, 114, 114, 114, 114, /* 98 */ +114, 114, 15, 15, 15, 15, 15, 15, /* 98 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 98 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 98 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 98 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 98 */ + 15, 46, 46, 46, 46, 46, 46, 46, /* 98 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 98 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 99 */ + 15, 15, 15, 15, 46, 46, 46, 46, /* 99 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 99 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 99 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 99 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 99 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 99 */ + 15, 15, 15, 15, 15, 15, 15, 46, /* 99 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 100 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 100 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 100 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 100 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 100 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 100 */ + 15, 15, 15, 15, 15, 15, 15, 46, /* 100 */ + 46, 46, 46, 15, 15, 15, 15, 15, /* 100 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 101 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 101 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 101 */ + 15, 15, 15, 15, 15, 15, 46, 46, /* 101 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 101 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 101 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 101 */ + 15, 15, 15, 15, 15, 15, 15, 46, /* 101 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 102 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 102 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 102 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 102 */ + 40, 40, 40, 40, 40, 40, 46, 46, /* 102 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 102 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 102 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 102 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 103 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 103 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 103 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 103 */ + 40, 40, 40, 40, 46, 46, 46, 46, /* 103 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 103 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 103 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 103 */ +122, 122, 122, 122, 122, 122, 122, 122, /* 104 */ +122, 122, 122, 122, 122, 122, 122, 122, /* 104 */ +122, 122, 122, 122, 122, 122, 122, 122, /* 104 */ +122, 122, 122, 122, 122, 122, 122, 122, /* 104 */ +122, 122, 122, 122, 122, 122, 122, 122, /* 104 */ +122, 122, 122, 122, 122, 122, 122, 122, /* 104 */ +122, 122, 122, 122, 122, 122, 122, 122, /* 104 */ +122, 122, 122, 122, 122, 122, 122, 122, /* 104 */ +123, 123, 123, 123, 123, 123, 123, 123, /* 105 */ +123, 123, 123, 123, 123, 123, 123, 123, /* 105 */ +123, 123, 123, 123, 123, 123, 123, 123, /* 105 */ +123, 123, 123, 123, 123, 123, 123, 123, /* 105 */ +123, 123, 123, 123, 123, 123, 123, 123, /* 105 */ +123, 123, 123, 123, 123, 123, 123, 123, /* 105 */ +123, 123, 123, 123, 123, 123, 123, 123, /* 105 */ +123, 123, 123, 123, 123, 123, 123, 123, /* 105 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 106 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 106 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 106 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 106 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 106 */ + 40, 40, 40, 40, 40, 40, 46, 46, /* 106 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 106 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 106 */ + 16, 16, 16, 16, 16, 16, 16, 46, /* 107 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 107 */ + 46, 46, 46, 16, 16, 16, 16, 16, /* 107 */ + 46, 46, 46, 46, 46, 46, 60, 40, /* 107 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 107 */ + 40, 7, 40, 40, 40, 40, 40, 40, /* 107 */ + 40, 40, 40, 40, 40, 40, 40, 46, /* 107 */ + 40, 40, 40, 40, 40, 46, 40, 46, /* 107 */ + 40, 40, 46, 40, 40, 46, 40, 40, /* 108 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 108 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 108 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 108 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 108 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 108 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 108 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 108 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 109 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 109 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 109 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 109 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 109 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 109 */ + 40, 40, 46, 46, 46, 46, 46, 46, /* 109 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 109 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 110 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 110 */ + 46, 46, 46, 40, 40, 40, 40, 40, /* 110 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 110 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 110 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 110 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 110 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 110 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 111 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 111 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 111 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 111 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 111 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 111 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 111 */ + 40, 40, 40, 40, 40, 40, 5, 6, /* 111 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 112 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 112 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 112 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 112 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 112 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 112 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 112 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 112 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 113 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 113 */ + 46, 46, 40, 40, 40, 40, 40, 40, /* 113 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 113 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 113 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 113 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 113 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 113 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 114 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 114 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 114 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 114 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 114 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 114 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 114 */ + 40, 40, 40, 40, 46, 46, 46, 46, /* 114 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 115 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 115 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 115 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 115 */ + 60, 60, 60, 60, 46, 46, 46, 46, /* 115 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 115 */ + 3, 8, 8, 12, 12, 5, 6, 5, /* 115 */ + 6, 5, 6, 5, 6, 5, 6, 5, /* 115 */ + 6, 5, 6, 5, 6, 46, 46, 46, /* 116 */ + 46, 3, 3, 3, 3, 12, 12, 12, /* 116 */ + 3, 3, 3, 46, 3, 3, 3, 3, /* 116 */ + 8, 5, 6, 5, 6, 5, 6, 3, /* 116 */ + 3, 3, 7, 8, 7, 7, 7, 46, /* 116 */ + 3, 4, 3, 3, 46, 46, 46, 46, /* 116 */ + 40, 40, 40, 46, 40, 46, 40, 40, /* 116 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 116 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 117 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 117 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 117 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 117 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 117 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 117 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 117 */ + 40, 40, 40, 40, 40, 46, 46, 104, /* 117 */ + 46, 3, 3, 3, 4, 3, 3, 3, /* 118 */ + 5, 6, 3, 7, 3, 8, 3, 3, /* 118 */ + 9, 9, 9, 9, 9, 9, 9, 9, /* 118 */ + 9, 9, 3, 3, 7, 7, 7, 3, /* 118 */ + 3, 10, 10, 10, 10, 10, 10, 10, /* 118 */ + 10, 10, 10, 10, 10, 10, 10, 10, /* 118 */ + 10, 10, 10, 10, 10, 10, 10, 10, /* 118 */ + 10, 10, 10, 5, 3, 6, 11, 12, /* 118 */ + 11, 13, 13, 13, 13, 13, 13, 13, /* 119 */ + 13, 13, 13, 13, 13, 13, 13, 13, /* 119 */ + 13, 13, 13, 13, 13, 13, 13, 13, /* 119 */ + 13, 13, 13, 5, 7, 6, 7, 46, /* 119 */ + 46, 3, 5, 6, 3, 3, 40, 40, /* 119 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 119 */ + 59, 40, 40, 40, 40, 40, 40, 40, /* 119 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 119 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 120 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 120 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 120 */ + 40, 40, 40, 40, 40, 40, 59, 59, /* 120 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 120 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 120 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 120 */ + 40, 40, 40, 40, 40, 40, 40, 46, /* 120 */ + 46, 46, 40, 40, 40, 40, 40, 40, /* 121 */ + 46, 46, 40, 40, 40, 40, 40, 40, /* 121 */ + 46, 46, 40, 40, 40, 40, 40, 40, /* 121 */ + 46, 46, 40, 40, 40, 46, 46, 46, /* 121 */ + 4, 4, 7, 11, 15, 4, 4, 46, /* 121 */ + 7, 7, 7, 7, 7, 15, 15, 46, /* 121 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 121 */ + 46, 46, 46, 46, 46, 15, 46, 46 /* 121 */ +}; + +/* The A table has 124 entries for a total of 496 bytes. */ + +const uint32 js_A[] = { +0x0001000F, /* 0 Cc, ignorable */ +0x0004000F, /* 1 Cc, whitespace */ +0x0004000C, /* 2 Zs, whitespace */ +0x00000018, /* 3 Po */ +0x0006001A, /* 4 Sc, currency */ +0x00000015, /* 5 Ps */ +0x00000016, /* 6 Pe */ +0x00000019, /* 7 Sm */ +0x00000014, /* 8 Pd */ +0x00036009, /* 9 Nd, identifier part, decimal 16 */ +0x0827FE01, /* 10 Lu, hasLower (add 32), identifier start, supradecimal 31 */ +0x0000001B, /* 11 Sk */ +0x00050017, /* 12 Pc, underscore */ +0x0817FE02, /* 13 Ll, hasUpper (subtract 32), identifier start, supradecimal 31 */ +0x0000000C, /* 14 Zs */ +0x0000001C, /* 15 So */ +0x00070002, /* 16 Ll, identifier start */ +0x0000600B, /* 17 No, decimal 16 */ +0x0000500B, /* 18 No, decimal 8 */ +0x0000800B, /* 19 No, strange */ +0x08270001, /* 20 Lu, hasLower (add 32), identifier start */ +0x08170002, /* 21 Ll, hasUpper (subtract 32), identifier start */ +0xE1D70002, /* 22 Ll, hasUpper (subtract -121), identifier start */ +0x00670001, /* 23 Lu, hasLower (add 1), identifier start */ +0x00570002, /* 24 Ll, hasUpper (subtract 1), identifier start */ +0xCE670001, /* 25 Lu, hasLower (add -199), identifier start */ +0x3A170002, /* 26 Ll, hasUpper (subtract 232), identifier start */ +0xE1E70001, /* 27 Lu, hasLower (add -121), identifier start */ +0x4B170002, /* 28 Ll, hasUpper (subtract 300), identifier start */ +0x34A70001, /* 29 Lu, hasLower (add 210), identifier start */ +0x33A70001, /* 30 Lu, hasLower (add 206), identifier start */ +0x33670001, /* 31 Lu, hasLower (add 205), identifier start */ +0x32A70001, /* 32 Lu, hasLower (add 202), identifier start */ +0x32E70001, /* 33 Lu, hasLower (add 203), identifier start */ +0x33E70001, /* 34 Lu, hasLower (add 207), identifier start */ +0x34E70001, /* 35 Lu, hasLower (add 211), identifier start */ +0x34670001, /* 36 Lu, hasLower (add 209), identifier start */ +0x35670001, /* 37 Lu, hasLower (add 213), identifier start */ +0x00070001, /* 38 Lu, identifier start */ +0x36A70001, /* 39 Lu, hasLower (add 218), identifier start */ +0x00070005, /* 40 Lo, identifier start */ +0x36670001, /* 41 Lu, hasLower (add 217), identifier start */ +0x36E70001, /* 42 Lu, hasLower (add 219), identifier start */ +0x00AF0001, /* 43 Lu, hasLower (add 2), hasTitle, identifier start */ +0x007F0003, /* 44 Lt, hasUpper (subtract 1), hasLower (add 1), hasTitle, identifier start */ +0x009F0002, /* 45 Ll, hasUpper (subtract 2), hasTitle, identifier start */ +0x00000000, /* 46 unassigned */ +0x34970002, /* 47 Ll, hasUpper (subtract 210), identifier start */ +0x33970002, /* 48 Ll, hasUpper (subtract 206), identifier start */ +0x33570002, /* 49 Ll, hasUpper (subtract 205), identifier start */ +0x32970002, /* 50 Ll, hasUpper (subtract 202), identifier start */ +0x32D70002, /* 51 Ll, hasUpper (subtract 203), identifier start */ +0x33D70002, /* 52 Ll, hasUpper (subtract 207), identifier start */ +0x34570002, /* 53 Ll, hasUpper (subtract 209), identifier start */ +0x34D70002, /* 54 Ll, hasUpper (subtract 211), identifier start */ +0x35570002, /* 55 Ll, hasUpper (subtract 213), identifier start */ +0x36970002, /* 56 Ll, hasUpper (subtract 218), identifier start */ +0x36570002, /* 57 Ll, hasUpper (subtract 217), identifier start */ +0x36D70002, /* 58 Ll, hasUpper (subtract 219), identifier start */ +0x00070004, /* 59 Lm, identifier start */ +0x00030006, /* 60 Mn, identifier part */ +0x09A70001, /* 61 Lu, hasLower (add 38), identifier start */ +0x09670001, /* 62 Lu, hasLower (add 37), identifier start */ +0x10270001, /* 63 Lu, hasLower (add 64), identifier start */ +0x0FE70001, /* 64 Lu, hasLower (add 63), identifier start */ +0x09970002, /* 65 Ll, hasUpper (subtract 38), identifier start */ +0x09570002, /* 66 Ll, hasUpper (subtract 37), identifier start */ +0x10170002, /* 67 Ll, hasUpper (subtract 64), identifier start */ +0x0FD70002, /* 68 Ll, hasUpper (subtract 63), identifier start */ +0x0F970002, /* 69 Ll, hasUpper (subtract 62), identifier start */ +0x0E570002, /* 70 Ll, hasUpper (subtract 57), identifier start */ +0x0BD70002, /* 71 Ll, hasUpper (subtract 47), identifier start */ +0x0D970002, /* 72 Ll, hasUpper (subtract 54), identifier start */ +0x15970002, /* 73 Ll, hasUpper (subtract 86), identifier start */ +0x14170002, /* 74 Ll, hasUpper (subtract 80), identifier start */ +0x14270001, /* 75 Lu, hasLower (add 80), identifier start */ +0x0C270001, /* 76 Lu, hasLower (add 48), identifier start */ +0x0C170002, /* 77 Ll, hasUpper (subtract 48), identifier start */ +0x00034009, /* 78 Nd, identifier part, decimal 0 */ +0x00000007, /* 79 Me */ +0x00030008, /* 80 Mc, identifier part */ +0x00037409, /* 81 Nd, identifier part, decimal 26 */ +0x00005A0B, /* 82 No, decimal 13 */ +0x00006E0B, /* 83 No, decimal 23 */ +0x0000740B, /* 84 No, decimal 26 */ +0x0000000B, /* 85 No */ +0xFE170002, /* 86 Ll, hasUpper (subtract -8), identifier start */ +0xFE270001, /* 87 Lu, hasLower (add -8), identifier start */ +0xED970002, /* 88 Ll, hasUpper (subtract -74), identifier start */ +0xEA970002, /* 89 Ll, hasUpper (subtract -86), identifier start */ +0xE7170002, /* 90 Ll, hasUpper (subtract -100), identifier start */ +0xE0170002, /* 91 Ll, hasUpper (subtract -128), identifier start */ +0xE4170002, /* 92 Ll, hasUpper (subtract -112), identifier start */ +0xE0970002, /* 93 Ll, hasUpper (subtract -126), identifier start */ +0xFDD70002, /* 94 Ll, hasUpper (subtract -9), identifier start */ +0xEDA70001, /* 95 Lu, hasLower (add -74), identifier start */ +0xFDE70001, /* 96 Lu, hasLower (add -9), identifier start */ +0xEAA70001, /* 97 Lu, hasLower (add -86), identifier start */ +0xE7270001, /* 98 Lu, hasLower (add -100), identifier start */ +0xFE570002, /* 99 Ll, hasUpper (subtract -7), identifier start */ +0xE4270001, /* 100 Lu, hasLower (add -112), identifier start */ +0xFE670001, /* 101 Lu, hasLower (add -7), identifier start */ +0xE0270001, /* 102 Lu, hasLower (add -128), identifier start */ +0xE0A70001, /* 103 Lu, hasLower (add -126), identifier start */ +0x00010010, /* 104 Cf, ignorable */ +0x0004000D, /* 105 Zl, whitespace */ +0x0004000E, /* 106 Zp, whitespace */ +0x0000400B, /* 107 No, decimal 0 */ +0x0000440B, /* 108 No, decimal 2 */ +0x0427420A, /* 109 Nl, hasLower (add 16), identifier start, decimal 1 */ +0x0427800A, /* 110 Nl, hasLower (add 16), identifier start, strange */ +0x0417620A, /* 111 Nl, hasUpper (subtract 16), identifier start, decimal 17 */ +0x0417800A, /* 112 Nl, hasUpper (subtract 16), identifier start, strange */ +0x0007800A, /* 113 Nl, identifier start, strange */ +0x0000420B, /* 114 No, decimal 1 */ +0x0000720B, /* 115 No, decimal 25 */ +0x06A0001C, /* 116 So, hasLower (add 26) */ +0x0690001C, /* 117 So, hasUpper (subtract 26) */ +0x00006C0B, /* 118 No, decimal 22 */ +0x0000560B, /* 119 No, decimal 11 */ +0x0007720A, /* 120 Nl, identifier start, decimal 25 */ +0x0007400A, /* 121 Nl, identifier start, decimal 0 */ +0x00000013, /* 122 Cs */ +0x00000012 /* 123 Co */ +}; + +const jschar js_uriReservedPlusPound_ucstr[] = + {';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '#', 0}; +const jschar js_uriUnescaped_ucstr[] = + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '-', '_', '.', '!', '~', '*', '\'', '(', ')', 0}; + +#define URI_CHUNK 64U + +/* Concatenate jschars onto an unshared/newborn JSString. */ +static JSBool +AddCharsToURI(JSContext *cx, JSString *str, const jschar *chars, size_t length) +{ + size_t total; + + JS_ASSERT(!JSSTRING_IS_DEPENDENT(str)); + total = str->length + length + 1; + if (!str->chars || + JS_HOWMANY(total, URI_CHUNK) > JS_HOWMANY(str->length + 1, URI_CHUNK)) { + total = JS_ROUNDUP(total, URI_CHUNK); + str->chars = JS_realloc(cx, str->chars, total * sizeof(jschar)); + if (!str->chars) + return JS_FALSE; + } + js_strncpy(str->chars + str->length, chars, length); + str->length += length; + str->chars[str->length] = 0; + return JS_TRUE; +} + +/* + * ECMA 3, 15.1.3 URI Handling Function Properties + * + * The following are implementations of the algorithms + * given in the ECMA specification for the hidden functions + * 'Encode' and 'Decode'. + */ +static JSBool +Encode(JSContext *cx, JSString *str, const jschar *unescapedSet, + const jschar *unescapedSet2, jsval *rval) +{ + size_t length, j, k, L; + jschar *chars, C, C2; + uint32 V; + uint8 utf8buf[6]; + jschar hexBuf[4]; + static const char HexDigits[] = "0123456789ABCDEF"; /* NB: uppercase */ + JSString *R; + + R = js_NewString(cx, NULL, 0, 0); + if (!R) + return JS_FALSE; + + hexBuf[0] = '%'; + hexBuf[3] = 0; + chars = JSSTRING_CHARS(str); + length = JSSTRING_LENGTH(str); + for (k = 0; k < length; k++) { + C = chars[k]; + if (js_strchr(unescapedSet, C) || + (unescapedSet2 && js_strchr(unescapedSet2, C))) { + if (!AddCharsToURI(cx, R, &C, 1)) + return JS_FALSE; + } else { + if ((C >= 0xDC00) && (C <= 0xDFFF)) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_URI, NULL); + return JS_FALSE; + } + if (C < 0xD800 || C > 0xDBFF) { + V = C; + } else { + k++; + if (k == length) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_URI, NULL); + return JS_FALSE; + } + C2 = chars[k]; + if ((C2 < 0xDC00) || (C2 > 0xDFFF)) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_URI, NULL); + return JS_FALSE; + } + V = ((C - 0xD800) << 10) + (C2 - 0xDC00) + 0x10000; + } + L = OneUcs4ToUtf8Char(utf8buf, V); + for (j = 0; j < L; j++) { + hexBuf[1] = HexDigits[utf8buf[j] >> 4]; + hexBuf[2] = HexDigits[utf8buf[j] & 0xf]; + if (!AddCharsToURI(cx, R, hexBuf, 3)) + return JS_FALSE; + } + } + } + + /* + * Shrinking realloc can fail (e.g., with a BSD-style allocator), but we + * don't worry about that case here. Worst case, R hangs onto URI_CHUNK-1 + * more jschars than it needs. + */ + chars = (jschar *) JS_realloc(cx, R->chars, (R->length+1) * sizeof(jschar)); + if (chars) + R->chars = chars; + *rval = STRING_TO_JSVAL(R); + return JS_TRUE; +} + +static JSBool +Decode(JSContext *cx, JSString *str, const jschar *reservedSet, jsval *rval) +{ + size_t length, start, k; + jschar *chars, C, H; + uint32 V; + jsuint B; + uint8 octets[6]; + JSString *R; + intN j, n; + + R = js_NewString(cx, NULL, 0, 0); + if (!R) + return JS_FALSE; + + chars = JSSTRING_CHARS(str); + length = JSSTRING_LENGTH(str); + for (k = 0; k < length; k++) { + C = chars[k]; + if (C == '%') { + start = k; + if ((k + 2) >= length) + goto bad; + if (!JS7_ISHEX(chars[k+1]) || !JS7_ISHEX(chars[k+2])) + goto bad; + B = JS7_UNHEX(chars[k+1]) * 16 + JS7_UNHEX(chars[k+2]); + k += 2; + if (!(B & 0x80)) { + C = (jschar)B; + } else { + n = 1; + while (B & (0x80 >> n)) + n++; + if (n == 1 || n > 6) + goto bad; + octets[0] = (uint8)B; + if (k + 3 * (n - 1) >= length) + goto bad; + for (j = 1; j < n; j++) { + k++; + if (chars[k] != '%') + goto bad; + if (!JS7_ISHEX(chars[k+1]) || !JS7_ISHEX(chars[k+2])) + goto bad; + B = JS7_UNHEX(chars[k+1]) * 16 + JS7_UNHEX(chars[k+2]); + if ((B & 0xC0) != 0x80) + goto bad; + k += 2; + octets[j] = (char)B; + } + V = Utf8ToOneUcs4Char(octets, n); + if (V >= 0x10000) { + V -= 0x10000; + if (V > 0xFFFFF) + goto bad; + C = (jschar)((V & 0x3FF) + 0xDC00); + H = (jschar)((V >> 10) + 0xD800); + if (!AddCharsToURI(cx, R, &H, 1)) + return JS_FALSE; + } else { + C = (jschar)V; + } + } + if (js_strchr(reservedSet, C)) { + if (!AddCharsToURI(cx, R, &chars[start], (k - start + 1))) + return JS_FALSE; + } else { + if (!AddCharsToURI(cx, R, &C, 1)) + return JS_FALSE; + } + } else { + if (!AddCharsToURI(cx, R, &C, 1)) + return JS_FALSE; + } + } + + /* + * Shrinking realloc can fail (e.g., with a BSD-style allocator), but we + * don't worry about that case here. Worst case, R hangs onto URI_CHUNK-1 + * more jschars than it needs. + */ + chars = (jschar *) JS_realloc(cx, R->chars, (R->length+1) * sizeof(jschar)); + if (chars) + R->chars = chars; + *rval = STRING_TO_JSVAL(R); + return JS_TRUE; + +bad: + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_URI); + return JS_FALSE; +} + +static JSBool +str_decodeURI(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSString *str; + + str = js_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + return Decode(cx, str, js_uriReservedPlusPound_ucstr, rval); +} + +static JSBool +str_decodeURI_Component(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSString *str; + + str = js_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + return Decode(cx, str, js_empty_ucstr, rval); +} + +static JSBool +str_encodeURI(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSString *str; + + str = js_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + return Encode(cx, str, js_uriReservedPlusPound_ucstr, js_uriUnescaped_ucstr, + rval); +} + +static JSBool +str_encodeURI_Component(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSString *str; + + str = js_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + return Encode(cx, str, js_uriUnescaped_ucstr, NULL, rval); +} + +/* + * Convert one UCS-4 char and write it into a UTF-8 buffer, which must be at + * least 6 bytes long. Return the number of UTF-8 bytes of data written. + */ +static int +OneUcs4ToUtf8Char(uint8 *utf8Buffer, uint32 ucs4Char) +{ + int utf8Length = 1; + + JS_ASSERT(ucs4Char <= 0x7FFFFFFF); + if (ucs4Char < 0x80) { + *utf8Buffer = (uint8)ucs4Char; + } else { + int i; + uint32 a = ucs4Char >> 11; + utf8Length = 2; + while (a) { + a >>= 5; + utf8Length++; + } + i = utf8Length; + while (--i) { + utf8Buffer[i] = (uint8)((ucs4Char & 0x3F) | 0x80); + ucs4Char >>= 6; + } + *utf8Buffer = (uint8)(0x100 - (1 << (8-utf8Length)) + ucs4Char); + } + return utf8Length; +} + +/* + * Convert a utf8 character sequence into a UCS-4 character and return that + * character. It is assumed that the caller already checked that the sequence + * is valid. + */ +static uint32 +Utf8ToOneUcs4Char(const uint8 *utf8Buffer, int utf8Length) +{ + uint32 ucs4Char; + uint32 minucs4Char; + /* from Unicode 3.1, non-shortest form is illegal */ + static const uint32 minucs4Table[] = { + 0x00000080, 0x00000800, 0x0001000, 0x0020000, 0x0400000 + }; + + JS_ASSERT(utf8Length >= 1 && utf8Length <= 6); + if (utf8Length == 1) { + ucs4Char = *utf8Buffer; + JS_ASSERT(!(ucs4Char & 0x80)); + } else { + JS_ASSERT((*utf8Buffer & (0x100 - (1 << (7-utf8Length)))) == + (0x100 - (1 << (8-utf8Length)))); + ucs4Char = *utf8Buffer++ & ((1<<(7-utf8Length))-1); + minucs4Char = minucs4Table[utf8Length-2]; + while (--utf8Length) { + JS_ASSERT((*utf8Buffer & 0xC0) == 0x80); + ucs4Char = ucs4Char<<6 | (*utf8Buffer++ & 0x3F); + } + if (ucs4Char < minucs4Char || + ucs4Char == 0xFFFE || ucs4Char == 0xFFFF) { + ucs4Char = 0xFFFD; + } + } + return ucs4Char; +} diff --git a/src/dom/js/jsstr.h b/src/dom/js/jsstr.h new file mode 100644 index 000000000..94ebe97b9 --- /dev/null +++ b/src/dom/js/jsstr.h @@ -0,0 +1,439 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsstr_h___ +#define jsstr_h___ +/* + * JS string type implementation. + * + * A JS string is a counted array of unicode characters. To support handoff + * of API client memory, the chars are allocated separately from the length, + * necessitating a pointer after the count, to form a separately allocated + * string descriptor. String descriptors are GC'ed, while their chars are + * allocated from the malloc heap. + * + * When a string is treated as an object (by following it with . or []), the + * runtime wraps it with a JSObject whose valueOf method returns the unwrapped + * string descriptor. + */ +#include +#include "jspubtd.h" +#include "jsprvtd.h" +#include "jshash.h" + +JS_BEGIN_EXTERN_C + +/* + * The original GC-thing "string" type, a flat character string owned by its + * GC-thing descriptor. The chars member points to a vector having byte size + * (length + 1) * sizeof(jschar), terminated at index length by a zero jschar. + * The terminator is purely a backstop, in case the chars pointer flows out to + * native code that requires \u0000 termination. + * + * NB: Always use the JSSTRING_LENGTH and JSSTRING_CHARS accessor macros, + * unless you guard str->member uses with !JSSTRING_IS_DEPENDENT(str). + */ +struct JSString { + size_t length; + jschar *chars; +}; + +/* + * Overlay structure for a string that depends on another string's characters. + * Distinguished by the JSSTRFLAG_DEPENDENT bit being set in length. The base + * member may point to another dependent string if JSSTRING_CHARS has not been + * called yet. The length chars in a dependent string are stored starting at + * base->chars + start, and are not necessarily zero-terminated. If start is + * 0, it is not stored, length is a full size_t (minus the JSSTRFLAG_* bits in + * the high two positions), and the JSSTRFLAG_PREFIX flag is set. + */ +struct JSDependentString { + size_t length; + JSString *base; +}; + +/* Definitions for flags stored in the high order bits of JSString.length. */ +#define JSSTRFLAG_BITS 2 +#define JSSTRFLAG_SHIFT(flg) ((size_t)(flg) << JSSTRING_LENGTH_BITS) +#define JSSTRFLAG_MASK JSSTRFLAG_SHIFT(JS_BITMASK(JSSTRFLAG_BITS)) +#define JSSTRFLAG_DEPENDENT JSSTRFLAG_SHIFT(1) +#define JSSTRFLAG_PREFIX JSSTRFLAG_SHIFT(2) + +/* Universal JSString type inquiry and accessor macros. */ +#define JSSTRING_BIT(n) ((size_t)1 << (n)) +#define JSSTRING_BITMASK(n) (JSSTRING_BIT(n) - 1) +#define JSSTRING_HAS_FLAG(str,flg) ((str)->length & (flg)) +#define JSSTRING_IS_DEPENDENT(str) JSSTRING_HAS_FLAG(str, JSSTRFLAG_DEPENDENT) +#define JSSTRING_IS_PREFIX(str) JSSTRING_HAS_FLAG(str, JSSTRFLAG_PREFIX) +#define JSSTRING_CHARS(str) (JSSTRING_IS_DEPENDENT(str) \ + ? JSSTRDEP_CHARS(str) \ + : (str)->chars) +#define JSSTRING_LENGTH(str) (JSSTRING_IS_DEPENDENT(str) \ + ? JSSTRDEP_LENGTH(str) \ + : (str)->length) +#define JSSTRING_LENGTH_BITS (sizeof(size_t) * JS_BITS_PER_BYTE \ + - JSSTRFLAG_BITS) +#define JSSTRING_LENGTH_MASK JSSTRING_BITMASK(JSSTRING_LENGTH_BITS) + +/* Specific JSDependentString shift/mask accessor and mutator macros. */ +#define JSSTRDEP_START_BITS (JSSTRING_LENGTH_BITS-JSSTRDEP_LENGTH_BITS) +#define JSSTRDEP_START_SHIFT JSSTRDEP_LENGTH_BITS +#define JSSTRDEP_START_MASK JSSTRING_BITMASK(JSSTRDEP_START_BITS) +#define JSSTRDEP_LENGTH_BITS (JSSTRING_LENGTH_BITS / 2) +#define JSSTRDEP_LENGTH_MASK JSSTRING_BITMASK(JSSTRDEP_LENGTH_BITS) + +#define JSSTRDEP(str) ((JSDependentString *)(str)) +#define JSSTRDEP_START(str) (JSSTRING_IS_PREFIX(str) ? 0 \ + : ((JSSTRDEP(str)->length \ + >> JSSTRDEP_START_SHIFT) \ + & JSSTRDEP_START_MASK)) +#define JSSTRDEP_LENGTH(str) (JSSTRDEP(str)->length \ + & (JSSTRING_IS_PREFIX(str) \ + ? JSSTRING_LENGTH_MASK \ + : JSSTRDEP_LENGTH_MASK)) + +#define JSSTRDEP_SET_START_AND_LENGTH(str,off,len) \ + (JSSTRDEP(str)->length = JSSTRFLAG_DEPENDENT \ + | ((off) << JSSTRDEP_START_SHIFT) \ + | (len)) +#define JSPREFIX_SET_LENGTH(str,len) \ + (JSSTRDEP(str)->length = JSSTRFLAG_DEPENDENT | JSSTRFLAG_PREFIX | (len)) + +#define JSSTRDEP_BASE(str) (JSSTRDEP(str)->base) +#define JSSTRDEP_SET_BASE(str,bstr) (JSSTRDEP(str)->base = (bstr)) +#define JSPREFIX_BASE(str) JSSTRDEP_BASE(str) +#define JSPREFIX_SET_BASE(str,bstr) JSSTRDEP_SET_BASE(str,bstr) + +#define JSSTRDEP_CHARS(str) \ + (JSSTRING_IS_DEPENDENT(JSSTRDEP_BASE(str)) \ + ? js_GetDependentStringChars(str) \ + : JSSTRDEP_BASE(str)->chars + JSSTRDEP_START(str)) + +extern size_t +js_MinimizeDependentStrings(JSString *str, int level, JSString **basep); + +extern jschar * +js_GetDependentStringChars(JSString *str); + +extern jschar * +js_GetStringChars(JSString *str); + +extern JSString * +js_ConcatStrings(JSContext *cx, JSString *left, JSString *right); + +extern const jschar * +js_UndependString(JSContext *cx, JSString *str); + +struct JSSubString { + size_t length; + const jschar *chars; +}; + +extern jschar js_empty_ucstr[]; +extern JSSubString js_EmptySubString; + +/* Unicode character attribute lookup tables. */ +extern const uint8 js_X[]; +extern const uint8 js_Y[]; +extern const uint32 js_A[]; + +/* Enumerated Unicode general category types. */ +typedef enum JSCharType { + JSCT_UNASSIGNED = 0, + JSCT_UPPERCASE_LETTER = 1, + JSCT_LOWERCASE_LETTER = 2, + JSCT_TITLECASE_LETTER = 3, + JSCT_MODIFIER_LETTER = 4, + JSCT_OTHER_LETTER = 5, + JSCT_NON_SPACING_MARK = 6, + JSCT_ENCLOSING_MARK = 7, + JSCT_COMBINING_SPACING_MARK = 8, + JSCT_DECIMAL_DIGIT_NUMBER = 9, + JSCT_LETTER_NUMBER = 10, + JSCT_OTHER_NUMBER = 11, + JSCT_SPACE_SEPARATOR = 12, + JSCT_LINE_SEPARATOR = 13, + JSCT_PARAGRAPH_SEPARATOR = 14, + JSCT_CONTROL = 15, + JSCT_FORMAT = 16, + JSCT_PRIVATE_USE = 18, + JSCT_SURROGATE = 19, + JSCT_DASH_PUNCTUATION = 20, + JSCT_START_PUNCTUATION = 21, + JSCT_END_PUNCTUATION = 22, + JSCT_CONNECTOR_PUNCTUATION = 23, + JSCT_OTHER_PUNCTUATION = 24, + JSCT_MATH_SYMBOL = 25, + JSCT_CURRENCY_SYMBOL = 26, + JSCT_MODIFIER_SYMBOL = 27, + JSCT_OTHER_SYMBOL = 28 +} JSCharType; + +/* Character classifying and mapping macros, based on java.lang.Character. */ +#define JS_CCODE(c) (js_A[js_Y[(js_X[(uint16)(c)>>6]<<6)|((c)&0x3F)]]) +#define JS_CTYPE(c) (JS_CCODE(c) & 0x1F) + +#define JS_ISALPHA(c) ((((1 << JSCT_UPPERCASE_LETTER) | \ + (1 << JSCT_LOWERCASE_LETTER) | \ + (1 << JSCT_TITLECASE_LETTER) | \ + (1 << JSCT_MODIFIER_LETTER) | \ + (1 << JSCT_OTHER_LETTER)) \ + >> JS_CTYPE(c)) & 1) + +#define JS_ISALNUM(c) ((((1 << JSCT_UPPERCASE_LETTER) | \ + (1 << JSCT_LOWERCASE_LETTER) | \ + (1 << JSCT_TITLECASE_LETTER) | \ + (1 << JSCT_MODIFIER_LETTER) | \ + (1 << JSCT_OTHER_LETTER) | \ + (1 << JSCT_DECIMAL_DIGIT_NUMBER)) \ + >> JS_CTYPE(c)) & 1) + +/* A unicode letter, suitable for use in an identifier. */ +#define JS_ISUC_LETTER(c) ((((1 << JSCT_UPPERCASE_LETTER) | \ + (1 << JSCT_LOWERCASE_LETTER) | \ + (1 << JSCT_TITLECASE_LETTER) | \ + (1 << JSCT_MODIFIER_LETTER) | \ + (1 << JSCT_OTHER_LETTER) | \ + (1 << JSCT_LETTER_NUMBER)) \ + >> JS_CTYPE(c)) & 1) + +/* +* 'IdentifierPart' from ECMA grammar, is Unicode letter or +* combining mark or digit or connector punctuation. +*/ +#define JS_ISID_PART(c) ((((1 << JSCT_UPPERCASE_LETTER) | \ + (1 << JSCT_LOWERCASE_LETTER) | \ + (1 << JSCT_TITLECASE_LETTER) | \ + (1 << JSCT_MODIFIER_LETTER) | \ + (1 << JSCT_OTHER_LETTER) | \ + (1 << JSCT_LETTER_NUMBER) | \ + (1 << JSCT_NON_SPACING_MARK) | \ + (1 << JSCT_COMBINING_SPACING_MARK) | \ + (1 << JSCT_DECIMAL_DIGIT_NUMBER) | \ + (1 << JSCT_CONNECTOR_PUNCTUATION)) \ + >> JS_CTYPE(c)) & 1) + +/* Unicode control-format characters, ignored in input */ +#define JS_ISFORMAT(c) (((1 << JSCT_FORMAT) >> JS_CTYPE(c)) & 1) + +#define JS_ISWORD(c) (JS_ISALNUM(c) || (c) == '_') + +/* XXXbe unify on A/X/Y tbls, avoid ctype.h? */ +#define JS_ISIDENT_START(c) (JS_ISUC_LETTER(c) || (c) == '_' || (c) == '$') +#define JS_ISIDENT(c) (JS_ISID_PART(c) || (c) == '_' || (c) == '$') + +#define JS_ISDIGIT(c) (JS_CTYPE(c) == JSCT_DECIMAL_DIGIT_NUMBER) + +/* XXXbe fs, etc. ? */ +#define JS_ISSPACE(c) ((JS_CCODE(c) & 0x00070000) == 0x00040000) +#define JS_ISPRINT(c) ((c) < 128 && isprint(c)) + +#define JS_ISUPPER(c) (JS_CTYPE(c) == JSCT_UPPERCASE_LETTER) +#define JS_ISLOWER(c) (JS_CTYPE(c) == JSCT_LOWERCASE_LETTER) + +#define JS_TOUPPER(c) ((JS_CCODE(c) & 0x00100000) ? (c) - ((int32)JS_CCODE(c) >> 22) : (c)) +#define JS_TOLOWER(c) ((JS_CCODE(c) & 0x00200000) ? (c) + ((int32)JS_CCODE(c) >> 22) : (c)) + +#define JS_TOCTRL(c) ((c) ^ 64) /* XXX unsafe! requires uppercase c */ + +/* Shorthands for ASCII (7-bit) decimal and hex conversion. */ +#define JS7_ISDEC(c) ((c) < 128 && isdigit(c)) +#define JS7_UNDEC(c) ((c) - '0') +#define JS7_ISHEX(c) ((c) < 128 && isxdigit(c)) +#define JS7_UNHEX(c) (uintN)(isdigit(c) ? (c) - '0' : 10 + tolower(c) - 'a') +#define JS7_ISLET(c) ((c) < 128 && isalpha(c)) + +/* Initialize truly global state associated with JS strings. */ +extern JSBool +js_InitStringGlobals(void); + +extern void +js_FreeStringGlobals(void); + +extern void +js_PurgeDeflatedStringCache(JSString *str); + +/* Initialize per-runtime string state for the first context in the runtime. */ +extern JSBool +js_InitRuntimeStringState(JSContext *cx); + +extern void +js_FinishRuntimeStringState(JSContext *cx); + +/* Initialize the String class, returning its prototype object. */ +extern JSObject * +js_InitStringClass(JSContext *cx, JSObject *obj); + +extern const char js_escape_str[]; +extern const char js_unescape_str[]; +extern const char js_uneval_str[]; +extern const char js_decodeURI_str[]; +extern const char js_encodeURI_str[]; +extern const char js_decodeURIComponent_str[]; +extern const char js_encodeURIComponent_str[]; + +/* GC-allocate a string descriptor for the given malloc-allocated chars. */ +extern JSString * +js_NewString(JSContext *cx, jschar *chars, size_t length, uintN gcflag); + +extern JSString * +js_NewDependentString(JSContext *cx, JSString *base, size_t start, + size_t length, uintN gcflag); + +/* Copy a counted string and GC-allocate a descriptor for it. */ +extern JSString * +js_NewStringCopyN(JSContext *cx, const jschar *s, size_t n, uintN gcflag); + +/* Copy a C string and GC-allocate a descriptor for it. */ +extern JSString * +js_NewStringCopyZ(JSContext *cx, const jschar *s, uintN gcflag); + +/* Free the chars held by str when it is finalized by the GC. */ +extern void +js_FinalizeString(JSContext *cx, JSString *str); + +extern void +js_FinalizeStringRT(JSRuntime *rt, JSString *str); + +/* Wrap a string value in a String object. */ +extern JSObject * +js_StringToObject(JSContext *cx, JSString *str); + +/* + * Convert a value to a string, returning null after reporting an error, + * otherwise returning a new string reference. + */ +extern JSString * +js_ValueToString(JSContext *cx, jsval v); + +/* + * Convert a value to its source expression, returning null after reporting + * an error, otherwise returning a new string reference. + */ +extern JSString * +js_ValueToSource(JSContext *cx, jsval v); + +#ifdef HT_ENUMERATE_NEXT /* XXX don't require jshash.h */ +/* + * Compute a hash function from str. + */ +extern JSHashNumber +js_HashString(JSString *str); +#endif + +/* + * Return less than, equal to, or greater than zero depending on whether + * str1 is less than, equal to, or greater than str2. + */ +extern intN +js_CompareStrings(JSString *str1, JSString *str2); + +/* + * Boyer-Moore-Horspool superlinear search for pat:patlen in text:textlen. + * The patlen argument must be positive and no greater than BMH_PATLEN_MAX. + * The start argument tells where in text to begin the search. + * + * Return the index of pat in text, or -1 if not found. + */ +#define BMH_CHARSET_SIZE 256 /* ISO-Latin-1 */ +#define BMH_PATLEN_MAX 255 /* skip table element is uint8 */ + +#define BMH_BAD_PATTERN (-2) /* return value if pat is not ISO-Latin-1 */ + +extern jsint +js_BoyerMooreHorspool(const jschar *text, jsint textlen, + const jschar *pat, jsint patlen, + jsint start); + +extern size_t +js_strlen(const jschar *s); + +extern jschar * +js_strchr(const jschar *s, jschar c); + +extern jschar * +js_strchr_limit(const jschar *s, jschar c, const jschar *limit); + +#define js_strncpy(t, s, n) memcpy((t), (s), (n) * sizeof(jschar)) + +/* + * Return s advanced past any Unicode white space characters. + */ +extern const jschar * +js_SkipWhiteSpace(const jschar *s); + +/* + * Inflate bytes to JS chars and vice versa. Report out of memory via cx + * and return null on error, otherwise return the jschar or byte vector that + * was JS_malloc'ed. + */ +extern jschar * +js_InflateString(JSContext *cx, const char *bytes, size_t length); + +extern char * +js_DeflateString(JSContext *cx, const jschar *chars, size_t length); + +/* + * Inflate bytes to JS chars into a buffer. + * 'chars' must be large enough for 'length'+1 jschars. + */ +extern void +js_InflateStringToBuffer(jschar *chars, const char *bytes, size_t length); + +/* + * Associate bytes with str in the deflated string cache, returning true on + * successful association, false on out of memory. + */ +extern JSBool +js_SetStringBytes(JSString *str, char *bytes, size_t length); + +/* + * Find or create a deflated string cache entry for str that contains its + * characters chopped from Unicode code points into bytes. + */ +extern char * +js_GetStringBytes(JSString *str); + +JSBool +js_str_escape(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval); + +JS_END_EXTERN_C + +#endif /* jsstr_h___ */ diff --git a/src/dom/js/jstypes.h b/src/dom/js/jstypes.h new file mode 100644 index 000000000..1709c6461 --- /dev/null +++ b/src/dom/js/jstypes.h @@ -0,0 +1,388 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * IBM Corp. + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* +** File: jstypes.h +** Description: Definitions of NSPR's basic types +** +** Prototypes and macros used to make up for deficiencies in ANSI environments +** that we have found. +** +** Since we do not wrap and all the other standard headers, authors +** of portable code will not know in general that they need these definitions. +** Instead of requiring these authors to find the dependent uses in their code +** and take the following steps only in those C files, we take steps once here +** for all C files. +**/ + +#ifndef jstypes_h___ +#define jstypes_h___ + +#include + +/*********************************************************************** +** MACROS: JS_EXTERN_API +** JS_EXPORT_API +** DESCRIPTION: +** These are only for externally visible routines and globals. For +** internal routines, just use "extern" for type checking and that +** will not export internal cross-file or forward-declared symbols. +** Define a macro for declaring procedures return types. We use this to +** deal with windoze specific type hackery for DLL definitions. Use +** JS_EXTERN_API when the prototype for the method is declared. Use +** JS_EXPORT_API for the implementation of the method. +** +** Example: +** in dowhim.h +** JS_EXTERN_API( void ) DoWhatIMean( void ); +** in dowhim.c +** JS_EXPORT_API( void ) DoWhatIMean( void ) { return; } +** +** +***********************************************************************/ +#ifdef WIN32 +/* These also work for __MWERKS__ */ +#define JS_EXTERN_API(__type) extern __declspec(dllexport) __type +#define JS_EXPORT_API(__type) __declspec(dllexport) __type +#define JS_EXTERN_DATA(__type) extern __declspec(dllexport) __type +#define JS_EXPORT_DATA(__type) __declspec(dllexport) __type + +#define JS_DLL_CALLBACK +#define JS_STATIC_DLL_CALLBACK(__x) static __x + +#elif defined(WIN16) + +#ifdef _WINDLL +#define JS_EXTERN_API(__type) extern __type _cdecl _export _loadds +#define JS_EXPORT_API(__type) __type _cdecl _export _loadds +#define JS_EXTERN_DATA(__type) extern __type _export +#define JS_EXPORT_DATA(__type) __type _export + +#define JS_DLL_CALLBACK __cdecl __loadds +#define JS_STATIC_DLL_CALLBACK(__x) static __x CALLBACK + +#else /* this must be .EXE */ +#define JS_EXTERN_API(__type) extern __type _cdecl _export +#define JS_EXPORT_API(__type) __type _cdecl _export +#define JS_EXTERN_DATA(__type) extern __type _export +#define JS_EXPORT_DATA(__type) __type _export + +#define JS_DLL_CALLBACK __cdecl __loadds +#define JS_STATIC_DLL_CALLBACK(__x) __x JS_DLL_CALLBACK +#endif /* _WINDLL */ + +#elif defined(XP_MAC) +#define JS_EXTERN_API(__type) extern __declspec(export) __type +#define JS_EXPORT_API(__type) __declspec(export) __type +#define JS_EXTERN_DATA(__type) extern __declspec(export) __type +#define JS_EXPORT_DATA(__type) __declspec(export) __type + +#define JS_DLL_CALLBACK +#define JS_STATIC_DLL_CALLBACK(__x) static __x + +#else /* Unix */ + +#define JS_EXTERN_API(__type) extern __type +#define JS_EXPORT_API(__type) __type +#define JS_EXTERN_DATA(__type) extern __type +#define JS_EXPORT_DATA(__type) __type + +#define JS_DLL_CALLBACK +#define JS_STATIC_DLL_CALLBACK(__x) static __x + +#endif + +#ifdef _WIN32 +# if defined(__MWERKS__) || defined(__GNUC__) +# define JS_IMPORT_API(__x) __x +# else +# define JS_IMPORT_API(__x) __declspec(dllimport) __x +# endif +#else +# define JS_IMPORT_API(__x) JS_EXPORT_API (__x) +#endif + +#if defined(_WIN32) && !defined(__MWERKS__) &&!defined(__GNUC__) +# define JS_IMPORT_DATA(__x) __declspec(dllimport) __x +#else +# define JS_IMPORT_DATA(__x) __x +#endif + +/* + * The linkage of JS API functions differs depending on whether the file is + * used within the JS library or not. Any source file within the JS + * interpreter should define EXPORT_JS_API whereas any client of the library + * should not. + */ +#ifdef EXPORT_JS_API +#define JS_PUBLIC_API(t) JS_EXPORT_API(t) +#define JS_PUBLIC_DATA(t) JS_EXPORT_DATA(t) +#else +#define JS_PUBLIC_API(t) JS_IMPORT_API(t) +#define JS_PUBLIC_DATA(t) JS_IMPORT_DATA(t) +#endif + +#define JS_FRIEND_API(t) JS_PUBLIC_API(t) +#define JS_FRIEND_DATA(t) JS_PUBLIC_DATA(t) + +#ifdef _WIN32 +# define JS_INLINE __inline +#elif defined(__GNUC__) +# define JS_INLINE +#else +# define JS_INLINE +#endif + +/*********************************************************************** +** MACROS: JS_BEGIN_MACRO +** JS_END_MACRO +** DESCRIPTION: +** Macro body brackets so that macros with compound statement definitions +** behave syntactically more like functions when called. +***********************************************************************/ +#define JS_BEGIN_MACRO do { +#define JS_END_MACRO } while (0) + +/*********************************************************************** +** MACROS: JS_BEGIN_EXTERN_C +** JS_END_EXTERN_C +** DESCRIPTION: +** Macro shorthands for conditional C++ extern block delimiters. +***********************************************************************/ +#ifdef __cplusplus +#define JS_BEGIN_EXTERN_C extern "C" { +#define JS_END_EXTERN_C } +#else +#define JS_BEGIN_EXTERN_C +#define JS_END_EXTERN_C +#endif + +/*********************************************************************** +** MACROS: JS_BIT +** JS_BITMASK +** DESCRIPTION: +** Bit masking macros. XXX n must be <= 31 to be portable +***********************************************************************/ +#define JS_BIT(n) ((JSUint32)1 << (n)) +#define JS_BITMASK(n) (JS_BIT(n) - 1) + +/*********************************************************************** +** MACROS: JS_HOWMANY +** JS_ROUNDUP +** JS_MIN +** JS_MAX +** DESCRIPTION: +** Commonly used macros for operations on compatible types. +***********************************************************************/ +#define JS_HOWMANY(x,y) (((x)+(y)-1)/(y)) +#define JS_ROUNDUP(x,y) (JS_HOWMANY(x,y)*(y)) +#define JS_MIN(x,y) ((x)<(y)?(x):(y)) +#define JS_MAX(x,y) ((x)>(y)?(x):(y)) + +#if (defined(XP_MAC) || defined(XP_WIN)) && !defined(CROSS_COMPILE) +# include "jscpucfg.h" /* Use standard Mac or Windows configuration */ +#elif defined(XP_UNIX) || defined(XP_BEOS) || defined(XP_OS2) || defined(CROSS_COMPILE) +# include "jsautocfg.h" /* Use auto-detected configuration */ +# include "jsosdep.h" /* ...and platform-specific flags */ +#else +# error "Must define one of XP_BEOS, XP_MAC, XP_OS2, XP_WIN or XP_UNIX" +#endif + +JS_BEGIN_EXTERN_C + +/************************************************************************ +** TYPES: JSUint8 +** JSInt8 +** DESCRIPTION: +** The int8 types are known to be 8 bits each. There is no type that +** is equivalent to a plain "char". +************************************************************************/ +#if JS_BYTES_PER_BYTE == 1 +typedef unsigned char JSUint8; +typedef signed char JSInt8; +#else +#error No suitable type for JSInt8/JSUint8 +#endif + +/************************************************************************ +** TYPES: JSUint16 +** JSInt16 +** DESCRIPTION: +** The int16 types are known to be 16 bits each. +************************************************************************/ +#if JS_BYTES_PER_SHORT == 2 +typedef unsigned short JSUint16; +typedef short JSInt16; +#else +#error No suitable type for JSInt16/JSUint16 +#endif + +/************************************************************************ +** TYPES: JSUint32 +** JSInt32 +** DESCRIPTION: +** The int32 types are known to be 32 bits each. +************************************************************************/ +#if JS_BYTES_PER_INT == 4 +typedef unsigned int JSUint32; +typedef int JSInt32; +#define JS_INT32(x) x +#define JS_UINT32(x) x ## U +#elif JS_BYTES_PER_LONG == 4 +typedef unsigned long JSUint32; +typedef long JSInt32; +#define JS_INT32(x) x ## L +#define JS_UINT32(x) x ## UL +#else +#error No suitable type for JSInt32/JSUint32 +#endif + +/************************************************************************ +** TYPES: JSUint64 +** JSInt64 +** DESCRIPTION: +** The int64 types are known to be 64 bits each. Care must be used when +** declaring variables of type JSUint64 or JSInt64. Different hardware +** architectures and even different compilers have varying support for +** 64 bit values. The only guaranteed portability requires the use of +** the JSLL_ macros (see jslong.h). +************************************************************************/ +#ifdef JS_HAVE_LONG_LONG +#if JS_BYTES_PER_LONG == 8 +typedef long JSInt64; +typedef unsigned long JSUint64; +#elif defined(WIN16) +typedef __int64 JSInt64; +typedef unsigned __int64 JSUint64; +#elif defined(WIN32) && !defined(__GNUC__) +typedef __int64 JSInt64; +typedef unsigned __int64 JSUint64; +#else +typedef long long JSInt64; +typedef unsigned long long JSUint64; +#endif /* JS_BYTES_PER_LONG == 8 */ +#else /* !JS_HAVE_LONG_LONG */ +typedef struct { +#ifdef IS_LITTLE_ENDIAN + JSUint32 lo, hi; +#else + JSUint32 hi, lo; +#endif +} JSInt64; +typedef JSInt64 JSUint64; +#endif /* !JS_HAVE_LONG_LONG */ + +/************************************************************************ +** TYPES: JSUintn +** JSIntn +** DESCRIPTION: +** The JSIntn types are most appropriate for automatic variables. They are +** guaranteed to be at least 16 bits, though various architectures may +** define them to be wider (e.g., 32 or even 64 bits). These types are +** never valid for fields of a structure. +************************************************************************/ +#if JS_BYTES_PER_INT >= 2 +typedef int JSIntn; +typedef unsigned int JSUintn; +#else +#error 'sizeof(int)' not sufficient for platform use +#endif + +/************************************************************************ +** TYPES: JSFloat64 +** DESCRIPTION: +** NSPR's floating point type is always 64 bits. +************************************************************************/ +typedef double JSFloat64; + +/************************************************************************ +** TYPES: JSSize +** DESCRIPTION: +** A type for representing the size of objects. +************************************************************************/ +typedef size_t JSSize; + +/************************************************************************ +** TYPES: JSPtrDiff +** DESCRIPTION: +** A type for pointer difference. Variables of this type are suitable +** for storing a pointer or pointer sutraction. +************************************************************************/ +typedef ptrdiff_t JSPtrdiff; + +/************************************************************************ +** TYPES: JSUptrdiff +** DESCRIPTION: +** A type for pointer difference. Variables of this type are suitable +** for storing a pointer or pointer sutraction. +************************************************************************/ +typedef unsigned long JSUptrdiff; + +/************************************************************************ +** TYPES: JSBool +** DESCRIPTION: +** Use JSBool for variables and parameter types. Use JS_FALSE and JS_TRUE +** for clarity of target type in assignments and actual arguments. Use +** 'if (bool)', 'while (!bool)', '(bool) ? x : y' etc., to test booleans +** just as you would C int-valued conditions. +************************************************************************/ +typedef JSIntn JSBool; +#define JS_TRUE (JSIntn)1 +#define JS_FALSE (JSIntn)0 + +/************************************************************************ +** TYPES: JSPackedBool +** DESCRIPTION: +** Use JSPackedBool within structs where bitfields are not desireable +** but minimum and consistant overhead matters. +************************************************************************/ +typedef JSUint8 JSPackedBool; + +/* +** A JSWord is an integer that is the same size as a void* +*/ +typedef long JSWord; +typedef unsigned long JSUword; + +#include "jsotypes.h" + +JS_END_EXTERN_C + +#endif /* jstypes_h___ */ + diff --git a/src/dom/js/jsutil.c b/src/dom/js/jsutil.c new file mode 100644 index 000000000..f64718ad4 --- /dev/null +++ b/src/dom/js/jsutil.c @@ -0,0 +1,157 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * IBM Corp. + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * PR assertion checker. + */ +#include "jsstddef.h" +#include +#include +#include "jstypes.h" +#include "jsutil.h" + +#ifdef WIN32 +# include +#endif + +#ifdef XP_MAC +# include +# include +# include "jsprf.h" +#endif + +#ifdef XP_MAC +/* + * PStrFromCStr converts the source C string to a destination + * pascal string as it copies. The dest string will + * be truncated to fit into an Str255 if necessary. + * If the C String pointer is NULL, the pascal string's length is + * set to zero. + */ +static void PStrFromCStr(const char* src, Str255 dst) +{ + short length = 0; + + /* handle case of overlapping strings */ + if ( (void*)src == (void*)dst ) + { + unsigned char* curdst = &dst[1]; + unsigned char thisChar; + + thisChar = *(const unsigned char*)src++; + while ( thisChar != '\0' ) + { + unsigned char nextChar; + + /* + * Use nextChar so we don't overwrite what we + * are about to read + */ + nextChar = *(const unsigned char*)src++; + *curdst++ = thisChar; + thisChar = nextChar; + + if ( ++length >= 255 ) + break; + } + } + else if ( src != NULL ) + { + unsigned char* curdst = &dst[1]; + /* count down so test it loop is faster */ + short overflow = 255; + register char temp; + + /* + * Can't do the K&R C thing of while (*s++ = *t++) + * because it will copy trailing zero which might + * overrun pascal buffer. Instead we use a temp variable. + */ + while ( (temp = *src++) != 0 ) + { + *(char*)curdst++ = temp; + + if ( --overflow <= 0 ) + break; + } + length = 255 - overflow; + } + dst[0] = length; +} + +static void jsdebugstr(const char *debuggerMsg) +{ + Str255 pStr; + + PStrFromCStr(debuggerMsg, pStr); + DebugStr(pStr); +} + +static void dprintf(const char *format, ...) +{ + va_list ap; + char *buffer; + + va_start(ap, format); + buffer = (char *)JS_vsmprintf(format, ap); + va_end(ap); + + jsdebugstr(buffer); + JS_DELETE(buffer); +} +#endif /* XP_MAC */ + +JS_PUBLIC_API(void) JS_Assert(const char *s, const char *file, JSIntn ln) +{ +#ifdef XP_MAC + dprintf("Assertion failure: %s, at %s:%d\n", s, file, ln); +#else + fprintf(stderr, "Assertion failure: %s, at %s:%d\n", s, file, ln); +#endif +#if defined(WIN32) + DebugBreak(); +#endif +#if defined(XP_OS2) + asm("int $3"); +#endif +#ifndef XP_MAC + abort(); +#endif +} diff --git a/src/dom/js/jsutil.h b/src/dom/js/jsutil.h new file mode 100644 index 000000000..d4edb919c --- /dev/null +++ b/src/dom/js/jsutil.h @@ -0,0 +1,106 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * PR assertion checker. + */ + +#ifndef jsutil_h___ +#define jsutil_h___ + +JS_BEGIN_EXTERN_C + +/*********************************************************************** +** FUNCTION: JS_MALLOC() +** DESCRIPTION: +** JS_NEW() allocates an untyped item of size _size from the heap. +** INPUTS: _size: size in bytes of item to be allocated +** OUTPUTS: untyped pointer to the node allocated +** RETURN: pointer to node or error returned from malloc(). +***********************************************************************/ +#define JS_MALLOC(_bytes) (malloc((_bytes))) + +/*********************************************************************** +** FUNCTION: JS_DELETE() +** DESCRIPTION: +** JS_DELETE() unallocates an object previosly allocated via JS_NEW() +** or JS_NEWZAP() to the heap. +** INPUTS: pointer to previously allocated object +** OUTPUTS: the referenced object is returned to the heap +** RETURN: void +***********************************************************************/ +#define JS_DELETE(_ptr) { free(_ptr); (_ptr) = NULL; } + +/*********************************************************************** +** FUNCTION: JS_NEW() +** DESCRIPTION: +** JS_NEW() allocates an item of type _struct from the heap. +** INPUTS: _struct: a data type +** OUTPUTS: pointer to _struct +** RETURN: pointer to _struct or error returns from malloc(). +***********************************************************************/ +#define JS_NEW(_struct) ((_struct *) JS_MALLOC(sizeof(_struct))) + +#ifdef DEBUG + +extern JS_PUBLIC_API(void) +JS_Assert(const char *s, const char *file, JSIntn ln); +#define JS_ASSERT(_expr) \ + ((_expr)?((void)0):JS_Assert(# _expr,__FILE__,__LINE__)) + +#define JS_NOT_REACHED(_reasonStr) \ + JS_Assert(_reasonStr,__FILE__,__LINE__) + +#else + +#define JS_ASSERT(expr) ((void) 0) +#define JS_NOT_REACHED(reasonStr) + +#endif /* defined(DEBUG) */ + +/* +** Abort the process in a non-graceful manner. This will cause a core file, +** call to the debugger or other moral equivalent as well as causing the +** entire process to stop. +*/ +extern JS_PUBLIC_API(void) JS_Abort(void); + +JS_END_EXTERN_C + +#endif /* jsutil_h___ */ diff --git a/src/dom/js/jsxdrapi.c b/src/dom/js/jsxdrapi.c new file mode 100644 index 000000000..1b092924d --- /dev/null +++ b/src/dom/js/jsxdrapi.c @@ -0,0 +1,690 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +#include "jsstddef.h" +#include "jsconfig.h" + +#if JS_HAS_XDR + +#include +#include "jstypes.h" +#include "jsutil.h" /* Added by JSIFY */ +#include "jsdhash.h" +#include "jsprf.h" +#include "jsapi.h" +#include "jscntxt.h" +#include "jsobj.h" /* js_XDRObject */ +#include "jsscript.h" /* js_XDRScript */ +#include "jsstr.h" +#include "jsxdrapi.h" + +#ifdef DEBUG +#define DBG(x) x +#else +#define DBG(x) ((void)0) +#endif + +typedef struct JSXDRMemState { + JSXDRState state; + char *base; + uint32 count; + uint32 limit; +} JSXDRMemState; + +#define MEM_BLOCK 8192 +#define MEM_PRIV(xdr) ((JSXDRMemState *)(xdr)) + +#define MEM_BASE(xdr) (MEM_PRIV(xdr)->base) +#define MEM_COUNT(xdr) (MEM_PRIV(xdr)->count) +#define MEM_LIMIT(xdr) (MEM_PRIV(xdr)->limit) + +#define MEM_LEFT(xdr, bytes) \ + JS_BEGIN_MACRO \ + if ((xdr)->mode == JSXDR_DECODE && \ + MEM_COUNT(xdr) + bytes > MEM_LIMIT(xdr)) { \ + JS_ReportErrorNumber((xdr)->cx, js_GetErrorMessage, NULL, \ + JSMSG_END_OF_DATA); \ + return 0; \ + } \ + JS_END_MACRO + +#define MEM_NEED(xdr, bytes) \ + JS_BEGIN_MACRO \ + if ((xdr)->mode == JSXDR_ENCODE) { \ + if (MEM_LIMIT(xdr) && \ + MEM_COUNT(xdr) + bytes > MEM_LIMIT(xdr)) { \ + uint32 limit_ = JS_ROUNDUP(MEM_COUNT(xdr) + bytes, MEM_BLOCK);\ + void *data_ = JS_realloc((xdr)->cx, MEM_BASE(xdr), limit_); \ + if (!data_) \ + return 0; \ + MEM_BASE(xdr) = data_; \ + MEM_LIMIT(xdr) = limit_; \ + } \ + } else { \ + MEM_LEFT(xdr, bytes); \ + } \ + JS_END_MACRO + +#define MEM_DATA(xdr) ((void *)(MEM_BASE(xdr) + MEM_COUNT(xdr))) +#define MEM_INCR(xdr,bytes) (MEM_COUNT(xdr) += (bytes)) + +static JSBool +mem_get32(JSXDRState *xdr, uint32 *lp) +{ + MEM_LEFT(xdr, 4); + *lp = *(uint32 *)MEM_DATA(xdr); + MEM_INCR(xdr, 4); + return JS_TRUE; +} + +static JSBool +mem_set32(JSXDRState *xdr, uint32 *lp) +{ + MEM_NEED(xdr, 4); + *(uint32 *)MEM_DATA(xdr) = *lp; + MEM_INCR(xdr, 4); + return JS_TRUE; +} + +static JSBool +mem_getbytes(JSXDRState *xdr, char *bytes, uint32 len) +{ + MEM_LEFT(xdr, len); + memcpy(bytes, MEM_DATA(xdr), len); + MEM_INCR(xdr, len); + return JS_TRUE; +} + +static JSBool +mem_setbytes(JSXDRState *xdr, char *bytes, uint32 len) +{ + MEM_NEED(xdr, len); + memcpy(MEM_DATA(xdr), bytes, len); + MEM_INCR(xdr, len); + return JS_TRUE; +} + +static void * +mem_raw(JSXDRState *xdr, uint32 len) +{ + void *data; + if (xdr->mode == JSXDR_ENCODE) { + MEM_NEED(xdr, len); + } else if (xdr->mode == JSXDR_DECODE) { + MEM_LEFT(xdr, len); + } + data = MEM_DATA(xdr); + MEM_INCR(xdr, len); + return data; +} + +static JSBool +mem_seek(JSXDRState *xdr, int32 offset, JSXDRWhence whence) +{ + switch (whence) { + case JSXDR_SEEK_CUR: + if ((int32)MEM_COUNT(xdr) + offset < 0) { + JS_ReportErrorNumber(xdr->cx, js_GetErrorMessage, NULL, + JSMSG_SEEK_BEYOND_START); + return JS_FALSE; + } + if (offset > 0) + MEM_NEED(xdr, offset); + MEM_COUNT(xdr) += offset; + return JS_TRUE; + case JSXDR_SEEK_SET: + if (offset < 0) { + JS_ReportErrorNumber(xdr->cx, js_GetErrorMessage, NULL, + JSMSG_SEEK_BEYOND_START); + return JS_FALSE; + } + if (xdr->mode == JSXDR_ENCODE) { + if ((uint32)offset > MEM_COUNT(xdr)) + MEM_NEED(xdr, offset - MEM_COUNT(xdr)); + MEM_COUNT(xdr) = offset; + } else { + if ((uint32)offset > MEM_LIMIT(xdr)) { + JS_ReportErrorNumber(xdr->cx, js_GetErrorMessage, NULL, + JSMSG_SEEK_BEYOND_END); + return JS_FALSE; + } + MEM_COUNT(xdr) = offset; + } + return JS_TRUE; + case JSXDR_SEEK_END: + if (offset >= 0 || + xdr->mode == JSXDR_ENCODE || + (int32)MEM_LIMIT(xdr) + offset < 0) { + JS_ReportErrorNumber(xdr->cx, js_GetErrorMessage, NULL, + JSMSG_END_SEEK); + return JS_FALSE; + } + MEM_COUNT(xdr) = MEM_LIMIT(xdr) + offset; + return JS_TRUE; + default: { + char numBuf[12]; + JS_snprintf(numBuf, sizeof numBuf, "%d", whence); + JS_ReportErrorNumber(xdr->cx, js_GetErrorMessage, NULL, + JSMSG_WHITHER_WHENCE, numBuf); + return JS_FALSE; + } + } +} + +static uint32 +mem_tell(JSXDRState *xdr) +{ + return MEM_COUNT(xdr); +} + +static void +mem_finalize(JSXDRState *xdr) +{ + JS_free(xdr->cx, MEM_BASE(xdr)); +} + +static JSXDROps xdrmem_ops = { + mem_get32, mem_set32, mem_getbytes, mem_setbytes, + mem_raw, mem_seek, mem_tell, mem_finalize +}; + +JS_PUBLIC_API(void) +JS_XDRInitBase(JSXDRState *xdr, JSXDRMode mode, JSContext *cx) +{ + xdr->mode = mode; + xdr->cx = cx; + xdr->registry = NULL; + xdr->numclasses = xdr->maxclasses = 0; + xdr->reghash = NULL; + xdr->userdata = NULL; +} + +JS_PUBLIC_API(JSXDRState *) +JS_XDRNewMem(JSContext *cx, JSXDRMode mode) +{ + JSXDRState *xdr = (JSXDRState *) JS_malloc(cx, sizeof(JSXDRMemState)); + if (!xdr) + return NULL; + JS_XDRInitBase(xdr, mode, cx); + if (mode == JSXDR_ENCODE) { + if (!(MEM_BASE(xdr) = JS_malloc(cx, MEM_BLOCK))) { + JS_free(cx, xdr); + return NULL; + } + } else { + /* XXXbe ok, so better not deref MEM_BASE(xdr) if not ENCODE */ + MEM_BASE(xdr) = NULL; + } + xdr->ops = &xdrmem_ops; + MEM_COUNT(xdr) = 0; + MEM_LIMIT(xdr) = MEM_BLOCK; + return xdr; +} + +JS_PUBLIC_API(void *) +JS_XDRMemGetData(JSXDRState *xdr, uint32 *lp) +{ + if (xdr->ops != &xdrmem_ops) + return NULL; + *lp = MEM_COUNT(xdr); + return MEM_BASE(xdr); +} + +JS_PUBLIC_API(void) +JS_XDRMemSetData(JSXDRState *xdr, void *data, uint32 len) +{ + if (xdr->ops != &xdrmem_ops) + return; + MEM_LIMIT(xdr) = len; + MEM_BASE(xdr) = data; + MEM_COUNT(xdr) = 0; +} + +JS_PUBLIC_API(uint32) +JS_XDRMemDataLeft(JSXDRState *xdr) +{ + if (xdr->ops != &xdrmem_ops) + return 0; + return MEM_LIMIT(xdr) - MEM_COUNT(xdr); +} + +JS_PUBLIC_API(void) +JS_XDRMemResetData(JSXDRState *xdr) +{ + if (xdr->ops != &xdrmem_ops) + return; + MEM_COUNT(xdr) = 0; +} + +JS_PUBLIC_API(void) +JS_XDRDestroy(JSXDRState *xdr) +{ + JSContext *cx = xdr->cx; + xdr->ops->finalize(xdr); + if (xdr->registry) { + JS_free(cx, xdr->registry); + if (xdr->reghash) + JS_DHashTableDestroy(xdr->reghash); + } + JS_free(cx, xdr); +} + +JS_PUBLIC_API(JSBool) +JS_XDRUint8(JSXDRState *xdr, uint8 *b) +{ + uint32 l = *b; + if (!JS_XDRUint32(xdr, &l)) + return JS_FALSE; + *b = (uint8) l; + return JS_TRUE; +} + +JS_PUBLIC_API(JSBool) +JS_XDRUint16(JSXDRState *xdr, uint16 *s) +{ + uint32 l = *s; + if (!JS_XDRUint32(xdr, &l)) + return JS_FALSE; + *s = (uint16) l; + return JS_TRUE; +} + +JS_PUBLIC_API(JSBool) +JS_XDRUint32(JSXDRState *xdr, uint32 *lp) +{ + JSBool ok = JS_TRUE; + if (xdr->mode == JSXDR_ENCODE) { + uint32 xl = JSXDR_SWAB32(*lp); + ok = xdr->ops->set32(xdr, &xl); + } else if (xdr->mode == JSXDR_DECODE) { + ok = xdr->ops->get32(xdr, lp); + *lp = JSXDR_SWAB32(*lp); + } + return ok; +} + +JS_PUBLIC_API(JSBool) +JS_XDRBytes(JSXDRState *xdr, char *bytes, uint32 len) +{ + uint32 padlen; + static char padbuf[JSXDR_ALIGN-1]; + + if (xdr->mode == JSXDR_ENCODE) { + if (!xdr->ops->setbytes(xdr, bytes, len)) + return JS_FALSE; + } else { + if (!xdr->ops->getbytes(xdr, bytes, len)) + return JS_FALSE; + } + len = xdr->ops->tell(xdr); + if (len % JSXDR_ALIGN) { + padlen = JSXDR_ALIGN - (len % JSXDR_ALIGN); + if (xdr->mode == JSXDR_ENCODE) { + if (!xdr->ops->setbytes(xdr, padbuf, padlen)) + return JS_FALSE; + } else { + if (!xdr->ops->seek(xdr, padlen, JSXDR_SEEK_CUR)) + return JS_FALSE; + } + } + return JS_TRUE; +} + +/** + * Convert between a C string and the XDR representation: + * leading 32-bit count, then counted vector of chars, + * then possibly \0 padding to multiple of 4. + */ +JS_PUBLIC_API(JSBool) +JS_XDRCString(JSXDRState *xdr, char **sp) +{ + uint32 len; + + if (xdr->mode == JSXDR_ENCODE) + len = strlen(*sp); + JS_XDRUint32(xdr, &len); + if (xdr->mode == JSXDR_DECODE) { + if (!(*sp = (char *) JS_malloc(xdr->cx, len + 1))) + return JS_FALSE; + } + if (!JS_XDRBytes(xdr, *sp, len)) { + if (xdr->mode == JSXDR_DECODE) + JS_free(xdr->cx, *sp); + return JS_FALSE; + } + if (xdr->mode == JSXDR_DECODE) { + (*sp)[len] = '\0'; + } else if (xdr->mode == JSXDR_FREE) { + JS_free(xdr->cx, *sp); + *sp = NULL; + } + return JS_TRUE; +} + +JS_PUBLIC_API(JSBool) +JS_XDRCStringOrNull(JSXDRState *xdr, char **sp) +{ + uint32 null = (*sp == NULL); + if (!JS_XDRUint32(xdr, &null)) + return JS_FALSE; + if (null) { + *sp = NULL; + return JS_TRUE; + } + return JS_XDRCString(xdr, sp); +} + +/* + * Convert between a JS (Unicode) string and the XDR representation. + */ +JS_PUBLIC_API(JSBool) +JS_XDRString(JSXDRState *xdr, JSString **strp) +{ + uint32 i, len, padlen, nbytes; + jschar *chars = NULL, *raw; + + if (xdr->mode == JSXDR_ENCODE) + len = JSSTRING_LENGTH(*strp); + if (!JS_XDRUint32(xdr, &len)) + return JS_FALSE; + nbytes = len * sizeof(jschar); + + if (xdr->mode == JSXDR_DECODE) { + if (!(chars = (jschar *) JS_malloc(xdr->cx, nbytes + sizeof(jschar)))) + return JS_FALSE; + } else { + chars = JSSTRING_CHARS(*strp); + } + + padlen = nbytes % JSXDR_ALIGN; + if (padlen) { + padlen = JSXDR_ALIGN - padlen; + nbytes += padlen; + } + if (!(raw = (jschar *) xdr->ops->raw(xdr, nbytes))) + goto bad; + if (xdr->mode == JSXDR_ENCODE) { + for (i = 0; i < len; i++) + raw[i] = JSXDR_SWAB16(chars[i]); + if (padlen) + memset((char *)raw + nbytes - padlen, 0, padlen); + } else if (xdr->mode == JSXDR_DECODE) { + for (i = 0; i < len; i++) + chars[i] = JSXDR_SWAB16(raw[i]); + chars[len] = 0; + + if (!(*strp = JS_NewUCString(xdr->cx, chars, len))) + goto bad; + } + return JS_TRUE; + +bad: + if (xdr->mode == JSXDR_DECODE) + JS_free(xdr->cx, chars); + return JS_FALSE; +} + +JS_PUBLIC_API(JSBool) +JS_XDRStringOrNull(JSXDRState *xdr, JSString **strp) +{ + uint32 null = (*strp == NULL); + if (!JS_XDRUint32(xdr, &null)) + return JS_FALSE; + if (null) { + *strp = NULL; + return JS_TRUE; + } + return JS_XDRString(xdr, strp); +} + +JS_PUBLIC_API(JSBool) +JS_XDRDouble(JSXDRState *xdr, jsdouble **dp) +{ + jsdouble d; + if (xdr->mode == JSXDR_ENCODE) + d = **dp; +#if IS_BIG_ENDIAN + if (!JS_XDRUint32(xdr, (uint32 *)&d + 1) || + !JS_XDRUint32(xdr, (uint32 *)&d)) +#else + if (!JS_XDRUint32(xdr, (uint32 *)&d) || + !JS_XDRUint32(xdr, (uint32 *)&d + 1)) +#endif + return JS_FALSE; + if (xdr->mode == JSXDR_DECODE) { + *dp = JS_NewDouble(xdr->cx, d); + if (!*dp) + return JS_FALSE; + } + return JS_TRUE; +} + +/* These are magic pseudo-tags: see jsapi.h, near the top, for real tags. */ +#define JSVAL_XDRNULL 0x8 +#define JSVAL_XDRVOID 0xA + +JS_PUBLIC_API(JSBool) +JS_XDRValue(JSXDRState *xdr, jsval *vp) +{ + uint32 type; + + if (xdr->mode == JSXDR_ENCODE) { + if (JSVAL_IS_NULL(*vp)) + type = JSVAL_XDRNULL; + else if (JSVAL_IS_VOID(*vp)) + type = JSVAL_XDRVOID; + else + type = JSVAL_TAG(*vp); + } + if (!JS_XDRUint32(xdr, &type)) + return JS_FALSE; + + switch (type) { + case JSVAL_XDRNULL: + *vp = JSVAL_NULL; + break; + case JSVAL_XDRVOID: + *vp = JSVAL_VOID; + break; + case JSVAL_STRING: { + JSString *str; + if (xdr->mode == JSXDR_ENCODE) + str = JSVAL_TO_STRING(*vp); + if (!JS_XDRString(xdr, &str)) + return JS_FALSE; + if (xdr->mode == JSXDR_DECODE) + *vp = STRING_TO_JSVAL(str); + break; + } + case JSVAL_DOUBLE: { + jsdouble *dp; + if (xdr->mode == JSXDR_ENCODE) + dp = JSVAL_TO_DOUBLE(*vp); + if (!JS_XDRDouble(xdr, &dp)) + return JS_FALSE; + if (xdr->mode == JSXDR_DECODE) + *vp = DOUBLE_TO_JSVAL(dp); + break; + } + case JSVAL_OBJECT: { + JSObject *obj; + if (xdr->mode == JSXDR_ENCODE) + obj = JSVAL_TO_OBJECT(*vp); + if (!js_XDRObject(xdr, &obj)) + return JS_FALSE; + if (xdr->mode == JSXDR_DECODE) + *vp = OBJECT_TO_JSVAL(obj); + break; + } + case JSVAL_BOOLEAN: { + uint32 b; + if (xdr->mode == JSXDR_ENCODE) + b = (uint32) JSVAL_TO_BOOLEAN(*vp); + if (!JS_XDRUint32(xdr, &b)) + return JS_FALSE; + if (xdr->mode == JSXDR_DECODE) + *vp = BOOLEAN_TO_JSVAL((JSBool) b); + break; + } + default: { + uint32 i; + + JS_ASSERT(type & JSVAL_INT); + if (xdr->mode == JSXDR_ENCODE) + i = (uint32) JSVAL_TO_INT(*vp); + if (!JS_XDRUint32(xdr, &i)) + return JS_FALSE; + if (xdr->mode == JSXDR_DECODE) + *vp = INT_TO_JSVAL((int32) i); + break; + } + } + return JS_TRUE; +} + +JS_PUBLIC_API(JSBool) +JS_XDRScript(JSXDRState *xdr, JSScript **scriptp) +{ + if (!js_XDRScript(xdr, scriptp, NULL)) + return JS_FALSE; + if (xdr->mode == JSXDR_DECODE) + js_CallNewScriptHook(xdr->cx, *scriptp, NULL); + return JS_TRUE; +} + +#define CLASS_REGISTRY_MIN 8 +#define CLASS_INDEX_TO_ID(i) ((i)+1) +#define CLASS_ID_TO_INDEX(id) ((id)-1) + +typedef struct JSRegHashEntry { + JSDHashEntryHdr hdr; + const char *name; + uint32 index; +} JSRegHashEntry; + +JS_PUBLIC_API(JSBool) +JS_XDRRegisterClass(JSXDRState *xdr, JSClass *clasp, uint32 *idp) +{ + uintN numclasses, maxclasses; + JSClass **registry; + + numclasses = xdr->numclasses; + maxclasses = xdr->maxclasses; + if (numclasses == maxclasses) { + maxclasses = (maxclasses == 0) ? CLASS_REGISTRY_MIN : maxclasses << 1; + registry = (JSClass **) + JS_realloc(xdr->cx, xdr->registry, maxclasses * sizeof(JSClass *)); + if (!registry) + return JS_FALSE; + xdr->registry = registry; + xdr->maxclasses = maxclasses; + } else { + JS_ASSERT(numclasses && numclasses < maxclasses); + registry = xdr->registry; + } + + registry[numclasses] = clasp; + if (xdr->reghash) { + JSRegHashEntry *entry = (JSRegHashEntry *) + JS_DHashTableOperate(xdr->reghash, clasp->name, JS_DHASH_ADD); + if (!entry) { + JS_ReportOutOfMemory(xdr->cx); + return JS_FALSE; + } + entry->name = clasp->name; + entry->index = numclasses; + } + *idp = CLASS_INDEX_TO_ID(numclasses); + xdr->numclasses = ++numclasses; + return JS_TRUE; +} + +JS_PUBLIC_API(uint32) +JS_XDRFindClassIdByName(JSXDRState *xdr, const char *name) +{ + uintN i, numclasses; + + numclasses = xdr->numclasses; + if (numclasses >= 10) { + JSRegHashEntry *entry; + + /* Bootstrap reghash from registry on first overpopulated Find. */ + if (!xdr->reghash) { + xdr->reghash = JS_NewDHashTable(JS_DHashGetStubOps(), NULL, + sizeof(JSRegHashEntry), + numclasses); + if (xdr->reghash) { + for (i = 0; i < numclasses; i++) { + JSClass *clasp = xdr->registry[i]; + entry = (JSRegHashEntry *) + JS_DHashTableOperate(xdr->reghash, clasp->name, + JS_DHASH_ADD); + entry->name = clasp->name; + entry->index = i; + } + } + } + + /* If we managed to create reghash, use it for O(1) Find. */ + if (xdr->reghash) { + entry = (JSRegHashEntry *) + JS_DHashTableOperate(xdr->reghash, name, JS_DHASH_LOOKUP); + if (JS_DHASH_ENTRY_IS_BUSY(&entry->hdr)) + return CLASS_INDEX_TO_ID(entry->index); + } + } + + /* Only a few classes, or we couldn't malloc reghash: use linear search. */ + for (i = 0; i < numclasses; i++) { + if (!strcmp(name, xdr->registry[i]->name)) + return CLASS_INDEX_TO_ID(i); + } + return 0; +} + +JS_PUBLIC_API(JSClass *) +JS_XDRFindClassById(JSXDRState *xdr, uint32 id) +{ + uintN i = CLASS_ID_TO_INDEX(id); + + if (i >= xdr->numclasses) + return NULL; + return xdr->registry[i]; +} + +#endif /* JS_HAS_XDR */ diff --git a/src/dom/js/jsxdrapi.h b/src/dom/js/jsxdrapi.h new file mode 100644 index 000000000..874a62eeb --- /dev/null +++ b/src/dom/js/jsxdrapi.h @@ -0,0 +1,193 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsxdrapi_h___ +#define jsxdrapi_h___ + +/* + * JS external data representation interface API. + * + * The XDR system is comprised of three major parts: + * + * - the state serialization/deserialization APIs, which allow consumers + * of the API to serialize JS runtime state (script bytecodes, atom maps, + * object graphs, etc.) for later restoration. These portions + * are implemented in various appropriate files, such as jsscript.c + * for the script portions and jsobj.c for object state. + * - the callback APIs through which the runtime requests an opaque + * representation of a native object, and through which the runtime + * constructs a live native object from an opaque representation. These + * portions are the responsibility of the native object implementor. + * - utility functions for en/decoding of primitive types, such as + * JSStrings. This portion is implemented in jsxdrapi.c. + * + * Spiritually guided by Sun's XDR, where appropriate. + */ + +#include "jspubtd.h" +#include "jsprvtd.h" + +JS_BEGIN_EXTERN_C + +/* We use little-endian byteorder for all encoded data */ + +#if defined IS_LITTLE_ENDIAN +#define JSXDR_SWAB32(x) x +#define JSXDR_SWAB16(x) x +#elif defined IS_BIG_ENDIAN +#define JSXDR_SWAB32(x) (((uint32)(x) >> 24) | \ + (((uint32)(x) >> 8) & 0xff00) | \ + (((uint32)(x) << 8) & 0xff0000) | \ + ((uint32)(x) << 24)) +#define JSXDR_SWAB16(x) (((uint16)(x) >> 8) | ((uint16)(x) << 8)) +#else +#error "unknown byte order" +#endif + +#define JSXDR_ALIGN 4 + +typedef enum JSXDRMode { + JSXDR_ENCODE, + JSXDR_DECODE, + JSXDR_FREE +} JSXDRMode; + +typedef enum JSXDRWhence { + JSXDR_SEEK_SET, + JSXDR_SEEK_CUR, + JSXDR_SEEK_END +} JSXDRWhence; + +typedef struct JSXDROps { + JSBool (*get32)(JSXDRState *, uint32 *); + JSBool (*set32)(JSXDRState *, uint32 *); + JSBool (*getbytes)(JSXDRState *, char *, uint32); + JSBool (*setbytes)(JSXDRState *, char *, uint32); + void * (*raw)(JSXDRState *, uint32); + JSBool (*seek)(JSXDRState *, int32, JSXDRWhence); + uint32 (*tell)(JSXDRState *); + void (*finalize)(JSXDRState *); +} JSXDROps; + +struct JSXDRState { + JSXDRMode mode; + JSXDROps *ops; + JSContext *cx; + JSClass **registry; + uintN numclasses; + uintN maxclasses; + void *reghash; + void *userdata; +}; + +extern JS_PUBLIC_API(void) +JS_XDRInitBase(JSXDRState *xdr, JSXDRMode mode, JSContext *cx); + +extern JS_PUBLIC_API(JSXDRState *) +JS_XDRNewMem(JSContext *cx, JSXDRMode mode); + +extern JS_PUBLIC_API(void *) +JS_XDRMemGetData(JSXDRState *xdr, uint32 *lp); + +extern JS_PUBLIC_API(void) +JS_XDRMemSetData(JSXDRState *xdr, void *data, uint32 len); + +extern JS_PUBLIC_API(uint32) +JS_XDRMemDataLeft(JSXDRState *xdr); + +extern JS_PUBLIC_API(void) +JS_XDRMemResetData(JSXDRState *xdr); + +extern JS_PUBLIC_API(void) +JS_XDRDestroy(JSXDRState *xdr); + +extern JS_PUBLIC_API(JSBool) +JS_XDRUint8(JSXDRState *xdr, uint8 *b); + +extern JS_PUBLIC_API(JSBool) +JS_XDRUint16(JSXDRState *xdr, uint16 *s); + +extern JS_PUBLIC_API(JSBool) +JS_XDRUint32(JSXDRState *xdr, uint32 *lp); + +extern JS_PUBLIC_API(JSBool) +JS_XDRBytes(JSXDRState *xdr, char *bytes, uint32 len); + +extern JS_PUBLIC_API(JSBool) +JS_XDRCString(JSXDRState *xdr, char **sp); + +extern JS_PUBLIC_API(JSBool) +JS_XDRCStringOrNull(JSXDRState *xdr, char **sp); + +extern JS_PUBLIC_API(JSBool) +JS_XDRString(JSXDRState *xdr, JSString **strp); + +extern JS_PUBLIC_API(JSBool) +JS_XDRStringOrNull(JSXDRState *xdr, JSString **strp); + +extern JS_PUBLIC_API(JSBool) +JS_XDRDouble(JSXDRState *xdr, jsdouble **dp); + +extern JS_PUBLIC_API(JSBool) +JS_XDRValue(JSXDRState *xdr, jsval *vp); + +extern JS_PUBLIC_API(JSBool) +JS_XDRScript(JSXDRState *xdr, JSScript **scriptp); + +extern JS_PUBLIC_API(JSBool) +JS_XDRRegisterClass(JSXDRState *xdr, JSClass *clasp, uint32 *lp); + +extern JS_PUBLIC_API(uint32) +JS_XDRFindClassIdByName(JSXDRState *xdr, const char *name); + +extern JS_PUBLIC_API(JSClass *) +JS_XDRFindClassById(JSXDRState *xdr, uint32 id); + +/* + * Magic numbers. + */ +#define JSXDR_MAGIC_SCRIPT_1 0xdead0001 +#define JSXDR_MAGIC_SCRIPT_2 0xdead0002 +#define JSXDR_MAGIC_SCRIPT_3 0xdead0003 +#define JSXDR_MAGIC_SCRIPT_4 0xdead0004 +#define JSXDR_MAGIC_SCRIPT_CURRENT JSXDR_MAGIC_SCRIPT_4 + +JS_END_EXTERN_C + +#endif /* ! jsxdrapi_h___ */ diff --git a/src/dom/js/prmjtime.c b/src/dom/js/prmjtime.c new file mode 100644 index 000000000..0a53f76bf --- /dev/null +++ b/src/dom/js/prmjtime.c @@ -0,0 +1,646 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * PR time code. + */ +#include "jsstddef.h" +#ifdef SOLARIS +#define _REENTRANT 1 +#endif +#include +#include +#include "jstypes.h" +#include "jsutil.h" + +#include "jsprf.h" +#include "prmjtime.h" + +#define PRMJ_DO_MILLISECONDS 1 + +#ifdef XP_OS2 +#include +#endif +#ifdef XP_WIN +#include +#include +#endif + +#ifdef XP_MAC +#include +#include +#include +#include +#include +#include +#include +#if !TARGET_CARBON +#include +#endif +#endif + +#if defined(XP_UNIX) || defined(XP_BEOS) + +#ifdef _SVID_GETTOD /* Defined only on Solaris, see Solaris */ +extern int gettimeofday(struct timeval *tv); +#endif + +#include + +#endif /* XP_UNIX */ + +#ifdef XP_MAC +static uint64 dstLocalBaseMicroseconds; +static unsigned long gJanuaryFirst1970Seconds; + +static void MacintoshInitializeTime(void) +{ + uint64 upTime; + unsigned long currentLocalTimeSeconds, + startupTimeSeconds; + uint64 startupTimeMicroSeconds; + uint32 upTimeSeconds; + uint64 oneMillion, upTimeSecondsLong, microSecondsToSeconds; + DateTimeRec firstSecondOfUnixTime; + + /* + * Figure out in local time what time the machine started up. This information can be added to + * upTime to figure out the current local time as well as GMT. + */ + + Microseconds((UnsignedWide*)&upTime); + + GetDateTime(¤tLocalTimeSeconds); + + JSLL_I2L(microSecondsToSeconds, PRMJ_USEC_PER_SEC); + JSLL_DIV(upTimeSecondsLong, upTime, microSecondsToSeconds); + JSLL_L2I(upTimeSeconds, upTimeSecondsLong); + + startupTimeSeconds = currentLocalTimeSeconds - upTimeSeconds; + + /* Make sure that we normalize the macintosh base seconds to the unix base of January 1, 1970. + */ + + firstSecondOfUnixTime.year = 1970; + firstSecondOfUnixTime.month = 1; + firstSecondOfUnixTime.day = 1; + firstSecondOfUnixTime.hour = 0; + firstSecondOfUnixTime.minute = 0; + firstSecondOfUnixTime.second = 0; + firstSecondOfUnixTime.dayOfWeek = 0; + + DateToSeconds(&firstSecondOfUnixTime, &gJanuaryFirst1970Seconds); + + startupTimeSeconds -= gJanuaryFirst1970Seconds; + + /* Now convert the startup time into a wide so that we can figure out GMT and DST. + */ + + JSLL_I2L(startupTimeMicroSeconds, startupTimeSeconds); + JSLL_I2L(oneMillion, PRMJ_USEC_PER_SEC); + JSLL_MUL(dstLocalBaseMicroseconds, oneMillion, startupTimeMicroSeconds); +} + +static SleepQRec gSleepQEntry = { NULL, sleepQType, NULL, 0 }; +static JSBool gSleepQEntryInstalled = JS_FALSE; + +static pascal long MySleepQProc(long message, SleepQRecPtr sleepQ) +{ + /* just woke up from sleeping, so must recompute dstLocalBaseMicroseconds. */ + if (message == kSleepWakeUp) + MacintoshInitializeTime(); + return 0; +} + +/* Because serial port and SLIP conflict with ReadXPram calls, + * we cache the call here + */ + +static void MyReadLocation(MachineLocation * loc) +{ + static MachineLocation storedLoc; /* InsideMac, OSUtilities, page 4-20 */ + static JSBool didReadLocation = JS_FALSE; + if (!didReadLocation) + { + MacintoshInitializeTime(); + ReadLocation(&storedLoc); + /* install a sleep queue routine, so that when the machine wakes up, time can be recomputed. */ + if (&SleepQInstall != (void*)kUnresolvedCFragSymbolAddress +#if !TARGET_CARBON + && NGetTrapAddress(0xA28A, OSTrap) != NGetTrapAddress(_Unimplemented, ToolTrap) +#endif + ) { + if ((gSleepQEntry.sleepQProc = NewSleepQUPP(MySleepQProc)) != NULL) { + SleepQInstall(&gSleepQEntry); + gSleepQEntryInstalled = JS_TRUE; + } + } + didReadLocation = JS_TRUE; + } + *loc = storedLoc; +} + + +#ifndef XP_MACOSX + +/* CFM library init and terminate routines. We'll use the terminate routine + to clean up the sleep Q entry. On Mach-O, the sleep Q entry gets cleaned + up for us, so nothing to do there. +*/ + +extern pascal OSErr __NSInitialize(const CFragInitBlock* initBlock); +extern pascal void __NSTerminate(); + +pascal OSErr __JSInitialize(const CFragInitBlock* initBlock); +pascal void __JSTerminate(void); + +pascal OSErr __JSInitialize(const CFragInitBlock* initBlock) +{ + return __NSInitialize(initBlock); +} + +pascal void __JSTerminate() +{ + /* clean up the sleepQ entry */ + if (gSleepQEntryInstalled) + SleepQRemove(&gSleepQEntry); + + __NSTerminate(); +} +#endif /* XP_MACOSX */ + +#endif /* XP_MAC */ + +#define IS_LEAP(year) \ + (year != 0 && ((((year & 0x3) == 0) && \ + ((year - ((year/100) * 100)) != 0)) || \ + (year - ((year/400) * 400)) == 0)) + +#define PRMJ_HOUR_SECONDS 3600L +#define PRMJ_DAY_SECONDS (24L * PRMJ_HOUR_SECONDS) +#define PRMJ_YEAR_SECONDS (PRMJ_DAY_SECONDS * 365L) +#define PRMJ_MAX_UNIX_TIMET 2145859200L /*time_t value equiv. to 12/31/2037 */ +/* function prototypes */ +static void PRMJ_basetime(JSInt64 tsecs, PRMJTime *prtm); +/* + * get the difference in seconds between this time zone and UTC (GMT) + */ +JSInt32 +PRMJ_LocalGMTDifference() +{ +#if defined(XP_UNIX) || defined(XP_WIN) || defined(XP_OS2) || defined(XP_BEOS) + struct tm ltime; + + /* get the difference between this time zone and GMT */ + memset((char *)<ime,0,sizeof(ltime)); + ltime.tm_mday = 2; + ltime.tm_year = 70; +#ifdef SUNOS4 + ltime.tm_zone = 0; + ltime.tm_gmtoff = 0; + return timelocal(<ime) - (24 * 3600); +#else + return mktime(<ime) - (24L * 3600L); +#endif +#endif +#if defined(XP_MAC) + static JSInt32 zone = -1L; + MachineLocation machineLocation; + JSInt32 gmtOffsetSeconds; + + /* difference has been set no need to recalculate */ + if (zone != -1) + return zone; + + /* Get the information about the local machine, including + * its GMT offset and its daylight savings time info. + * Convert each into wides that we can add to + * startupTimeMicroSeconds. + */ + + MyReadLocation(&machineLocation); + + /* Mask off top eight bits of gmtDelta, sign extend lower three. */ + gmtOffsetSeconds = (machineLocation.u.gmtDelta << 8); + gmtOffsetSeconds >>= 8; + + /* Backout OS adjustment for DST, to give consistent GMT offset. */ + if (machineLocation.u.dlsDelta != 0) + gmtOffsetSeconds -= PRMJ_HOUR_SECONDS; + return (zone = -gmtOffsetSeconds); +#endif +} + +/* Constants for GMT offset from 1970 */ +#define G1970GMTMICROHI 0x00dcdcad /* micro secs to 1970 hi */ +#define G1970GMTMICROLOW 0x8b3fa000 /* micro secs to 1970 low */ + +#define G2037GMTMICROHI 0x00e45fab /* micro secs to 2037 high */ +#define G2037GMTMICROLOW 0x7a238000 /* micro secs to 2037 low */ + +/* Convert from base time to extended time */ +static JSInt64 +PRMJ_ToExtendedTime(JSInt32 base_time) +{ + JSInt64 exttime; + JSInt64 g1970GMTMicroSeconds; + JSInt64 low; + JSInt32 diff; + JSInt64 tmp; + JSInt64 tmp1; + + diff = PRMJ_LocalGMTDifference(); + JSLL_UI2L(tmp, PRMJ_USEC_PER_SEC); + JSLL_I2L(tmp1,diff); + JSLL_MUL(tmp,tmp,tmp1); + + JSLL_UI2L(g1970GMTMicroSeconds,G1970GMTMICROHI); + JSLL_UI2L(low,G1970GMTMICROLOW); +#ifndef JS_HAVE_LONG_LONG + JSLL_SHL(g1970GMTMicroSeconds,g1970GMTMicroSeconds,16); + JSLL_SHL(g1970GMTMicroSeconds,g1970GMTMicroSeconds,16); +#else + JSLL_SHL(g1970GMTMicroSeconds,g1970GMTMicroSeconds,32); +#endif + JSLL_ADD(g1970GMTMicroSeconds,g1970GMTMicroSeconds,low); + + JSLL_I2L(exttime,base_time); + JSLL_ADD(exttime,exttime,g1970GMTMicroSeconds); + JSLL_SUB(exttime,exttime,tmp); + return exttime; +} + +JSInt64 +PRMJ_Now(void) +{ +#ifdef XP_OS2 + JSInt64 s, us, ms2us, s2us; + struct timeb b; +#endif +#ifdef XP_WIN + JSInt64 s, us, + win2un = JSLL_INIT(0x19DB1DE, 0xD53E8000), + ten = JSLL_INIT(0, 10); + FILETIME time, midnight; +#endif +#if defined(XP_UNIX) || defined(XP_BEOS) + struct timeval tv; + JSInt64 s, us, s2us; +#endif /* XP_UNIX */ +#ifdef XP_MAC + JSUint64 upTime; + JSInt64 localTime; + JSInt64 gmtOffset; + JSInt64 dstOffset; + JSInt32 gmtDiff; + JSInt64 s2us; +#endif /* XP_MAC */ + +#ifdef XP_OS2 + ftime(&b); + JSLL_UI2L(ms2us, PRMJ_USEC_PER_MSEC); + JSLL_UI2L(s2us, PRMJ_USEC_PER_SEC); + JSLL_UI2L(s, b.time); + JSLL_UI2L(us, b.millitm); + JSLL_MUL(us, us, ms2us); + JSLL_MUL(s, s, s2us); + JSLL_ADD(s, s, us); + return s; +#endif +#ifdef XP_WIN + /* The windows epoch is around 1600. The unix epoch is around 1970. + win2un is the difference (in windows time units which are 10 times + more precise than the JS time unit) */ + GetSystemTimeAsFileTime(&time); + /* Win9x gets confused at midnight + http://support.microsoft.com/default.aspx?scid=KB;en-us;q224423 + So if the low part (precision <8mins) is 0 then we get the time + again. */ + if (!time.dwLowDateTime) { + GetSystemTimeAsFileTime(&midnight); + time.dwHighDateTime = midnight.dwHighDateTime; + } + JSLL_UI2L(s, time.dwHighDateTime); + JSLL_UI2L(us, time.dwLowDateTime); + JSLL_SHL(s, s, 32); + JSLL_ADD(s, s, us); + JSLL_SUB(s, s, win2un); + JSLL_DIV(s, s, ten); + return s; +#endif + +#if defined(XP_UNIX) || defined(XP_BEOS) +#ifdef _SVID_GETTOD /* Defined only on Solaris, see Solaris */ + gettimeofday(&tv); +#else + gettimeofday(&tv, 0); +#endif /* _SVID_GETTOD */ + JSLL_UI2L(s2us, PRMJ_USEC_PER_SEC); + JSLL_UI2L(s, tv.tv_sec); + JSLL_UI2L(us, tv.tv_usec); + JSLL_MUL(s, s, s2us); + JSLL_ADD(s, s, us); + return s; +#endif /* XP_UNIX */ +#ifdef XP_MAC + JSLL_UI2L(localTime,0); + gmtDiff = PRMJ_LocalGMTDifference(); + JSLL_I2L(gmtOffset,gmtDiff); + JSLL_UI2L(s2us, PRMJ_USEC_PER_SEC); + JSLL_MUL(gmtOffset,gmtOffset,s2us); + + /* don't adjust for DST since it sets ctime and gmtime off on the MAC */ + Microseconds((UnsignedWide*)&upTime); + JSLL_ADD(localTime,localTime,gmtOffset); + JSLL_ADD(localTime,localTime, dstLocalBaseMicroseconds); + JSLL_ADD(localTime,localTime, upTime); + + dstOffset = PRMJ_DSTOffset(localTime); + JSLL_SUB(localTime,localTime,dstOffset); + + return *((JSUint64 *)&localTime); +#endif /* XP_MAC */ +} + +/* Get the DST timezone offset for the time passed in */ +JSInt64 +PRMJ_DSTOffset(JSInt64 local_time) +{ + JSInt64 us2s; +#ifdef XP_MAC + /* + * Convert the local time passed in to Macintosh epoch seconds. Use UTC utilities to convert + * to UTC time, then compare difference with our GMT offset. If they are the same, then + * DST must not be in effect for the input date/time. + */ + UInt32 macLocalSeconds = (local_time / PRMJ_USEC_PER_SEC) + gJanuaryFirst1970Seconds, utcSeconds; + ConvertLocalTimeToUTC(macLocalSeconds, &utcSeconds); + if ((utcSeconds - macLocalSeconds) == PRMJ_LocalGMTDifference()) + return 0; + else { + JSInt64 dlsOffset; + JSLL_UI2L(us2s, PRMJ_USEC_PER_SEC); + JSLL_UI2L(dlsOffset, PRMJ_HOUR_SECONDS); + JSLL_MUL(dlsOffset, dlsOffset, us2s); + return dlsOffset; + } +#else + time_t local; + JSInt32 diff; + JSInt64 maxtimet; + struct tm tm; + PRMJTime prtm; +#ifndef HAVE_LOCALTIME_R + struct tm *ptm; +#endif + + + JSLL_UI2L(us2s, PRMJ_USEC_PER_SEC); + JSLL_DIV(local_time, local_time, us2s); + + /* get the maximum of time_t value */ + JSLL_UI2L(maxtimet,PRMJ_MAX_UNIX_TIMET); + + if(JSLL_CMP(local_time,>,maxtimet)){ + JSLL_UI2L(local_time,PRMJ_MAX_UNIX_TIMET); + } else if(!JSLL_GE_ZERO(local_time)){ + /*go ahead a day to make localtime work (does not work with 0) */ + JSLL_UI2L(local_time,PRMJ_DAY_SECONDS); + } + JSLL_L2UI(local,local_time); + PRMJ_basetime(local_time,&prtm); +#ifndef HAVE_LOCALTIME_R + ptm = localtime(&local); + if(!ptm){ + return JSLL_ZERO; + } + tm = *ptm; +#else + localtime_r(&local,&tm); /* get dst information */ +#endif + + diff = ((tm.tm_hour - prtm.tm_hour) * PRMJ_HOUR_SECONDS) + + ((tm.tm_min - prtm.tm_min) * 60); + + if(diff < 0){ + diff += PRMJ_DAY_SECONDS; + } + + JSLL_UI2L(local_time,diff); + + JSLL_MUL(local_time,local_time,us2s); + + return(local_time); +#endif +} + +/* Format a time value into a buffer. Same semantics as strftime() */ +size_t +PRMJ_FormatTime(char *buf, int buflen, char *fmt, PRMJTime *prtm) +{ +#if defined(XP_UNIX) || defined(XP_WIN) || defined(XP_OS2) || defined(XP_MAC) || defined(XP_BEOS) + struct tm a; + + /* Zero out the tm struct. Linux, SunOS 4 struct tm has extra members int + * tm_gmtoff, char *tm_zone; when tm_zone is garbage, strftime gets + * confused and dumps core. NSPR20 prtime.c attempts to fill these in by + * calling mktime on the partially filled struct, but this doesn't seem to + * work as well; the result string has "can't get timezone" for ECMA-valid + * years. Might still make sense to use this, but find the range of years + * for which valid tz information exists, and map (per ECMA hint) from the + * given year into that range. + + * N.B. This hasn't been tested with anything that actually _uses_ + * tm_gmtoff; zero might be the wrong thing to set it to if you really need + * to format a time. This fix is for jsdate.c, which only uses + * JS_FormatTime to get a string representing the time zone. */ + memset(&a, 0, sizeof(struct tm)); + + a.tm_sec = prtm->tm_sec; + a.tm_min = prtm->tm_min; + a.tm_hour = prtm->tm_hour; + a.tm_mday = prtm->tm_mday; + a.tm_mon = prtm->tm_mon; + a.tm_wday = prtm->tm_wday; + a.tm_year = prtm->tm_year - 1900; + a.tm_yday = prtm->tm_yday; + a.tm_isdst = prtm->tm_isdst; + + /* Even with the above, SunOS 4 seems to detonate if tm_zone and tm_gmtoff + * are null. This doesn't quite work, though - the timezone is off by + * tzoff + dst. (And mktime seems to return -1 for the exact dst + * changeover time.) + + */ + +#if defined(SUNOS4) + if (mktime(&a) == -1) { + /* Seems to fail whenever the requested date is outside of the 32-bit + * UNIX epoch. We could proceed at this point (setting a.tm_zone to + * "") but then strftime returns a string with a 2-digit field of + * garbage for the year. So we return 0 and hope jsdate.c + * will fall back on toString. + */ + return 0; + } +#endif + + return strftime(buf, buflen, fmt, &a); +#endif +} + +/* table for number of days in a month */ +static int mtab[] = { + /* jan, feb,mar,apr,may,jun */ + 31,28,31,30,31,30, + /* july,aug,sep,oct,nov,dec */ + 31,31,30,31,30,31 +}; + +/* + * basic time calculation functionality for localtime and gmtime + * setups up prtm argument with correct values based upon input number + * of seconds. + */ +static void +PRMJ_basetime(JSInt64 tsecs, PRMJTime *prtm) +{ + /* convert tsecs back to year,month,day,hour,secs */ + JSInt32 year = 0; + JSInt32 month = 0; + JSInt32 yday = 0; + JSInt32 mday = 0; + JSInt32 wday = 6; /* start on a Sunday */ + JSInt32 days = 0; + JSInt32 seconds = 0; + JSInt32 minutes = 0; + JSInt32 hours = 0; + JSInt32 isleap = 0; + JSInt64 result; + JSInt64 result1; + JSInt64 result2; + JSInt64 base; + + JSLL_UI2L(result,0); + JSLL_UI2L(result1,0); + JSLL_UI2L(result2,0); + + /* get the base time via UTC */ + base = PRMJ_ToExtendedTime(0); + JSLL_UI2L(result, PRMJ_USEC_PER_SEC); + JSLL_DIV(base,base,result); + JSLL_ADD(tsecs,tsecs,base); + + JSLL_UI2L(result, PRMJ_YEAR_SECONDS); + JSLL_UI2L(result1,PRMJ_DAY_SECONDS); + JSLL_ADD(result2,result,result1); + + /* get the year */ + while ((isleap == 0) ? !JSLL_CMP(tsecs,<,result) : !JSLL_CMP(tsecs,<,result2)) { + /* subtract a year from tsecs */ + JSLL_SUB(tsecs,tsecs,result); + days += 365; + /* is it a leap year ? */ + if(IS_LEAP(year)){ + JSLL_SUB(tsecs,tsecs,result1); + days++; + } + year++; + isleap = IS_LEAP(year); + } + + JSLL_UI2L(result1,PRMJ_DAY_SECONDS); + + JSLL_DIV(result,tsecs,result1); + JSLL_L2I(mday,result); + + /* let's find the month */ + while(((month == 1 && isleap) ? + (mday >= mtab[month] + 1) : + (mday >= mtab[month]))){ + yday += mtab[month]; + days += mtab[month]; + + mday -= mtab[month]; + + /* it's a Feb, check if this is a leap year */ + if(month == 1 && isleap != 0){ + yday++; + days++; + mday--; + } + month++; + } + + /* now adjust tsecs */ + JSLL_MUL(result,result,result1); + JSLL_SUB(tsecs,tsecs,result); + + mday++; /* day of month always start with 1 */ + days += mday; + wday = (days + wday) % 7; + + yday += mday; + + /* get the hours */ + JSLL_UI2L(result1,PRMJ_HOUR_SECONDS); + JSLL_DIV(result,tsecs,result1); + JSLL_L2I(hours,result); + JSLL_MUL(result,result,result1); + JSLL_SUB(tsecs,tsecs,result); + + /* get minutes */ + JSLL_UI2L(result1,60); + JSLL_DIV(result,tsecs,result1); + JSLL_L2I(minutes,result); + JSLL_MUL(result,result,result1); + JSLL_SUB(tsecs,tsecs,result); + + JSLL_L2I(seconds,tsecs); + + prtm->tm_usec = 0L; + prtm->tm_sec = (JSInt8)seconds; + prtm->tm_min = (JSInt8)minutes; + prtm->tm_hour = (JSInt8)hours; + prtm->tm_mday = (JSInt8)mday; + prtm->tm_mon = (JSInt8)month; + prtm->tm_wday = (JSInt8)wday; + prtm->tm_year = (JSInt16)year; + prtm->tm_yday = (JSInt16)yday; +} diff --git a/src/dom/js/prmjtime.h b/src/dom/js/prmjtime.h new file mode 100644 index 000000000..6a94a11b1 --- /dev/null +++ b/src/dom/js/prmjtime.h @@ -0,0 +1,95 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef prmjtime_h___ +#define prmjtime_h___ +/* + * PR date stuff for mocha and java. Placed here temporarily not to break + * Navigator and localize changes to mocha. + */ +#include +#include "jslong.h" +#ifdef MOZILLA_CLIENT +#include "jscompat.h" +#endif + +JS_BEGIN_EXTERN_C + +typedef struct PRMJTime PRMJTime; + +/* + * Broken down form of 64 bit time value. + */ +struct PRMJTime { + JSInt32 tm_usec; /* microseconds of second (0-999999) */ + JSInt8 tm_sec; /* seconds of minute (0-59) */ + JSInt8 tm_min; /* minutes of hour (0-59) */ + JSInt8 tm_hour; /* hour of day (0-23) */ + JSInt8 tm_mday; /* day of month (1-31) */ + JSInt8 tm_mon; /* month of year (0-11) */ + JSInt8 tm_wday; /* 0=sunday, 1=monday, ... */ + JSInt16 tm_year; /* absolute year, AD */ + JSInt16 tm_yday; /* day of year (0 to 365) */ + JSInt8 tm_isdst; /* non-zero if DST in effect */ +}; + +/* Some handy constants */ +#define PRMJ_USEC_PER_SEC 1000000L +#define PRMJ_USEC_PER_MSEC 1000L + +/* Return the current local time in micro-seconds */ +extern JSInt64 +PRMJ_Now(void); + +/* get the difference between this time zone and gmt timezone in seconds */ +extern JSInt32 +PRMJ_LocalGMTDifference(void); + +/* Format a time value into a buffer. Same semantics as strftime() */ +extern size_t +PRMJ_FormatTime(char *buf, int buflen, char *fmt, PRMJTime *tm); + +/* Get the DST offset for the local time passed in */ +extern JSInt64 +PRMJ_DSTOffset(JSInt64 local_time); + +JS_END_EXTERN_C + +#endif /* prmjtime_h___ */ + diff --git a/src/dom/js/resource.h b/src/dom/js/resource.h new file mode 100644 index 000000000..9301810e4 --- /dev/null +++ b/src/dom/js/resource.h @@ -0,0 +1,15 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by js3240.rc +// + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/dom/knut.svg b/src/dom/knut.svg new file mode 100755 index 000000000..0d52bf913 --- /dev/null +++ b/src/dom/knut.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + diff --git a/src/dom/ls.h b/src/dom/ls.h new file mode 100755 index 000000000..658e602f9 --- /dev/null +++ b/src/dom/ls.h @@ -0,0 +1,940 @@ +#ifndef __LS_H__ +#define __LS_H__ +/** + * Phoebe DOM Implementation. + * + * This is a C++ approximation of the W3C DOM model, which follows + * fairly closely the specifications in the various .idl files, copies of + * which are provided for reference. Most important is this one: + * + * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/idl-definitions.html + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2005 Bob Jamison + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include "dom.h" +#include "events.h" +#include "traversal.h" + +#include "domstream.h" + +namespace org +{ +namespace w3c +{ +namespace dom +{ +namespace ls +{ + + + +//Local definitions +//The idl said Object. Since this is undefined, we will +//use our own class which is designed to be a bit similar to +//java.io streams + +typedef dom::InputStream LSInputStream; +typedef dom::OutputStream LSOutputStream; +typedef dom::Reader LSReader; +typedef dom::Writer LSWriter; + + +//local definitions +typedef dom::DOMString DOMString; +typedef dom::DOMConfiguration DOMConfiguration; +typedef dom::Node Node; +typedef dom::Document Document; +typedef dom::Element Element; + + +//forward declarations +class LSParser; +class LSSerializer; +class LSInput; +class LSOutput; +class LSParserFilter; +class LSSerializerFilter; + + + +/*######################################################################### +## LSException +#########################################################################*/ + +/** + * Maybe this should inherit from DOMException? + */ +class LSException +{ + +public: + + LSException(const DOMString &reasonMsg) + { msg = reasonMsg; } + + LSException(short theCode) + { + code = theCode; + } + + virtual ~LSException() throw() + {} + + /** + * + */ + unsigned short code; + + /** + * + */ + DOMString msg; + + /** + * Get a string, translated from the code. + * Like std::exception. Not in spec. + */ + const char *what() + { return msg.c_str(); } + + + +}; + + +/** + * LSExceptionCode + */ +typedef enum + { + PARSE_ERR = 81, + SERIALIZE_ERR = 82 + } XPathExceptionCode; + + +/*######################################################################### +## LSParserFilter +#########################################################################*/ + +/** + * + */ +class LSParserFilter +{ +public: + + // Constants returned by startElement and acceptNode + typedef enum + { + FILTER_ACCEPT = 1, + FILTER_REJECT = 2, + FILTER_SKIP = 3, + FILTER_INTERRUPT = 4 + } ReturnValues; + + + /** + * + */ + virtual unsigned short startElement(const Element *elementArg) =0; + + /** + * + */ + virtual unsigned short acceptNode(const Node *nodeArg) =0; + + /** + * + */ + virtual unsigned long getWhatToShow() =0; + + //################## + //# Non-API methods + //################## + + /** + * + */ + virtual ~LSParserFilter() {} + + + +}; + +/*######################################################################### +## LSInput +#########################################################################*/ + +/** + * + */ +class LSInput +{ +public: + + /** + * + */ + virtual LSReader *getCharacterStream() const + { return characterStream; } + + /** + * + */ + virtual void setCharacterStream(const LSReader *val) + { characterStream = (LSReader *)val; } + + /** + * + */ + virtual LSInputStream *getByteStream() const + { return byteStream; } + + /** + * + */ + virtual void setByteStream(const LSInputStream *val) + { byteStream = (LSInputStream *)val; } + + /** + * + */ + virtual DOMString getStringData() const + { return stringData; } + + /** + * + */ + virtual void setStringData(const DOMString &val) + { stringData = val; } + + /** + * + */ + virtual DOMString getSystemId() const + { return systemId; } + + /** + * + */ + virtual void setSystemId(const DOMString &val) + { systemId = val; } + + /** + * + */ + virtual DOMString getPublicId() const + { return publicId; } + + /** + * + */ + virtual void setPublicId(const DOMString &val) + { publicId = val; } + + /** + * + */ + virtual DOMString getBaseURI() const + { return baseURI; } + + /** + * + */ + virtual void setBaseURI(const DOMString &val) + { baseURI = val; } + + /** + * + */ + virtual DOMString getEncoding() const + { return encoding; } + + /** + * + */ + virtual void setEncoding(const DOMString &val) + { encoding = val; } + + /** + * + */ + virtual bool getCertifiedText() const + { return certifiedText; } + + /** + * + */ + virtual void setCertifiedText(bool val) + { certifiedText = val; } + + //################## + //# Non-API methods + //################## + + + /** + * + */ + LSInput() + { + characterStream = NULL; + byteStream = NULL; + stringData = ""; + systemId = ""; + publicId = ""; + baseURI = ""; + encoding = ""; + certifiedText = false; + } + + + + /** + * + */ + LSInput(const LSInput &other) + { + characterStream = other.characterStream; + byteStream = other.byteStream; + stringData = other.stringData; + systemId = other.systemId; + publicId = other.publicId; + baseURI = other.baseURI; + encoding = other.encoding; + certifiedText = other.certifiedText; + } + + /** + * + */ + virtual ~LSInput() + {} + +private: + + LSReader *characterStream; + LSInputStream *byteStream; + DOMString stringData; + DOMString systemId; + DOMString publicId; + DOMString baseURI; + DOMString encoding; + bool certifiedText; + + +}; + + +/*######################################################################### +## LSParser +#########################################################################*/ + +/** + * + */ +class LSParser +{ +public: + + + /** + * + */ + virtual DOMConfiguration *getDomConfig() + { return NULL; } + + /** + * + */ + virtual LSParserFilter *getFilter() + { return filter; } + + /** + * + */ + virtual void setFilter(const LSParserFilter *val) + { filter = (LSParserFilter *)val; } + + /** + * + */ + virtual bool getAsync() + { return false; } + + /** + * + */ + virtual bool getBusy() + { return false; } + + /** + * + */ + virtual Document *parse(const LSInput &input) + throw(dom::DOMException, LSException) + { return NULL; } + + + /** + * + */ + virtual Document *parseURI(const DOMString &uri) + throw(dom::DOMException, LSException) + { return NULL; } + + typedef enum + { + ACTION_APPEND_AS_CHILDREN = 1, + ACTION_REPLACE_CHILDREN = 2, + ACTION_INSERT_BEFORE = 3, + ACTION_INSERT_AFTER = 4, + ACTION_REPLACE = 5 + } ActionTypes; + + + /** + * + */ + virtual Node *parseWithContext(const LSInput &input, + const Node *contextArg, + unsigned short action) + throw(dom::DOMException, LSException) + { return NULL; } + + /** + * + */ + virtual void abort() + {} + + + + //################## + //# Non-API methods + //################## + + /** + * + */ + LSParser() + { + filter = NULL; + } + + /** + * + */ + LSParser(const LSParser &other) + { + filter = other.filter; + } + + /** + * + */ + virtual ~LSParser() {} + +protected: + + LSParserFilter *filter; +}; + + + +/*######################################################################### +## LSResourceResolver +#########################################################################*/ + +/** + * + */ +class LSResourceResolver +{ +public: + + /** + * + */ + virtual LSInput resolveResource(const DOMString &type, + const DOMString &namespaceURI, + const DOMString &publicId, + const DOMString &systemId, + const DOMString &baseURI) + { + LSInput input; + //do something + return input; + } + + //################## + //# Non-API methods + //################## + + /** + * + */ + LSResourceResolver() {} + + /** + * + */ + LSResourceResolver(const LSResourceResolver &other) + { + } + + /** + * + */ + virtual ~LSResourceResolver() {} + + + +}; + +/*######################################################################### +## LSOutput +#########################################################################*/ + +/** + * + */ +class LSOutput +{ +public: + + /** + * + */ + virtual LSWriter *getCharacterStream() const + { return characterStream; } + + /** + * + */ + virtual void setCharacterStream(const LSWriter *val) + { characterStream = (LSWriter *)val; } + + /** + * + */ + virtual LSOutputStream *getByteStream() const + { return byteStream; } + + /** + * + */ + virtual void setByteStream(const LSOutputStream *val) + { byteStream = (LSOutputStream *) val; } + + /** + * + */ + virtual DOMString getSystemId() const + { return systemId; } + + /** + * + */ + virtual void setSystemId(const DOMString &val) + { systemId = val; } + + /** + * + */ + virtual DOMString getEncoding() const + { return encoding; } + + /** + * + */ + virtual void setEncoding(const DOMString &val) + { encoding = val; } + + + //################## + //# Non-API methods + //################## + + /** + * + */ + LSOutput() + { + characterStream = NULL; + byteStream = NULL; + systemId = ""; + encoding = ""; + } + + + /** + * + */ + LSOutput(const LSOutput &other) + { + characterStream = other.characterStream; + byteStream = other.byteStream; + systemId = other.systemId; + encoding = other.encoding; + } + + /** + * + */ + virtual ~LSOutput() + {} + +private: + + LSWriter *characterStream; + LSOutputStream *byteStream; + DOMString systemId; + DOMString encoding; + +}; + + +/*######################################################################### +## LSSerializer +#########################################################################*/ + +/** + * + */ +class LSSerializer +{ +public: + + /** + * + */ + virtual DOMConfiguration *getDomConfig() + { return NULL; } + + /** + * + */ + virtual DOMString getNewLine() + { return newLine; } + /** + * + */ + virtual void setNewLine(const DOMString &val) + { newLine = val; } + + /** + * + */ + virtual LSSerializerFilter *getFilter() + { return filter; } + + /** + * + */ + virtual void setFilter(const LSSerializerFilter *val) + { filter = (LSSerializerFilter *)val; } + + /** + * + */ + virtual bool write(const Node *nodeArg, + const LSOutput &destination) + throw (LSException) + { return false; } + + /** + * + */ + virtual bool writeToURI(const Node *nodeArg, + const DOMString &uri) + throw(LSException) + { return false; } + + /** + * + */ + virtual DOMString writeToString(const Node *nodeArg) + throw(dom::DOMException, LSException) + { + DOMString str; + return str; + } + + //################## + //# Non-API methods + //################## + + /** + * + */ + LSSerializer() + { + filter = NULL; + newLine = "\n"; + } + + /** + * + */ + LSSerializer(const LSSerializer &other) + { + filter = other.filter; + newLine = other.newLine; + } + + /** + * + */ + virtual ~LSSerializer() {} + +protected: + + LSSerializerFilter *filter; + DOMString newLine; + +}; + +/*######################################################################### +## LSProgressEvent +#########################################################################*/ + +/** + * + */ +class LSProgressEvent : virtual public events::Event +{ +public: + + /** + * + */ + virtual LSInput &getInput() + { + return input; + } + + /** + * + */ + virtual unsigned long getPosition() + { return position; } + + /** + * + */ + virtual unsigned long getTotalSize() + { return totalSize; } + + //################## + //# Non-API methods + //################## + + /** + * + */ + LSProgressEvent(const LSInput &inputArg, unsigned long positionArg, + unsigned long totalSizeArg) : input((LSInput &)inputArg) + { + position = positionArg; + totalSize = totalSizeArg; + } + + + /** + * + */ + LSProgressEvent(const LSProgressEvent &other) + : events::Event(other) , input(other.input) + { + position = other.position; + totalSize = other.totalSize; + } + + + /** + * + */ + virtual ~LSProgressEvent() {} + +protected: + + LSInput &input; + unsigned long position; + unsigned long totalSize; + +}; + +/*######################################################################### +## LSLoadEvent +#########################################################################*/ + +/** + * + */ +class LSLoadEvent : public events::Event +{ +public: + + /** + * + */ + virtual Document *getNewDocument() + { return newDocument; } + + /** + * + */ + virtual LSInput &getInput() + { return input; } + + //################## + //# Non-API methods + //################## + + /** + * + */ + LSLoadEvent(const LSInput &inputArg, const Document *docArg) + : input((LSInput &)inputArg) + { newDocument = (Document *)docArg; } + + /** + * + */ + LSLoadEvent(const LSLoadEvent &other) : events::Event(other) , input(other.input) + { + newDocument = other.newDocument; + } + + /** + * + */ + virtual ~LSLoadEvent() {} + +protected: + + Document *newDocument; + + LSInput &input; + + +}; + + + +/*######################################################################### +## LSSerializerFilter +#########################################################################*/ + +/** + * + */ +class LSSerializerFilter : virtual public traversal::NodeFilter +{ +public: + + /** + * + */ + virtual unsigned long getWhatToShow() =0; + + //################## + //# Non-API methods + //################## + + /** + * + */ + virtual ~LSSerializerFilter() {} +}; + + + + +/*######################################################################### +## DOMImplementationLS +#########################################################################*/ + +/** + * + */ +class DOMImplementationLS +{ +public: + + typedef enum + { + MODE_SYNCHRONOUS = 1, + MODE_ASYNCHRONOUS = 2 + } DOMImplementationLSMode; + + /** + * To use, for this and subclasses: + * LSParser &parser = myImplementation.createLSParser(mode, schemaType); + */ + virtual LSParser &createLSParser(unsigned short mode, + const DOMString &schemaType) + throw (dom::DOMException) =0; + + /** + * To use, for this and subclasses: + * LSSerializer &serializer = myImplementation.createLSSerializer(); + * + */ + virtual LSSerializer &createLSSerializer() =0; + + /** + * + */ + virtual LSInput createLSInput() =0; + + /** + * + */ + virtual LSOutput createLSOutput() =0; + + //################## + //# Non-API methods + //################## + + /** + * + */ + virtual ~DOMImplementationLS() {} +}; + + + + +} //namespace ls +} //namespace dom +} //namespace w3c +} //namespace org + + +#endif // __LS_H__ + +/*######################################################################### +## E N D O F F I L E +#########################################################################*/ + diff --git a/src/dom/ls.idl b/src/dom/ls.idl new file mode 100755 index 000000000..db53ab79c --- /dev/null +++ b/src/dom/ls.idl @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2004 World Wide Web Consortium, + * + * (Massachusetts Institute of Technology, European Research Consortium for + * Informatics and Mathematics, Keio University). All Rights Reserved. This + * work is distributed under the W3C(r) Software License [1] in the hope that + * it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * [1] http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 + */ + +// File: http://www.w3.org/TR/2004/REC-DOM-Level-3-LS-20040407/ls.idl + +#ifndef _LS_IDL_ +#define _LS_IDL_ + +#include "dom.idl" +#include "events.idl" +#include "traversal.idl" + +#pragma prefix "dom.w3c.org" +module ls +{ + + typedef Object LSInputStream; + + typedef Object LSOutputStream; + + typedef Object LSReader; + + typedef Object LSWriter; + + typedef dom::DOMString DOMString; + typedef dom::DOMConfiguration DOMConfiguration; + typedef dom::Node Node; + typedef dom::Document Document; + typedef dom::Element Element; + + interface LSParser; + interface LSSerializer; + interface LSInput; + interface LSOutput; + interface LSParserFilter; + interface LSSerializerFilter; + + exception LSException { + unsigned short code; + }; + // LSExceptionCode + const unsigned short PARSE_ERR = 81; + const unsigned short SERIALIZE_ERR = 82; + + + interface DOMImplementationLS { + + // DOMImplementationLSMode + const unsigned short MODE_SYNCHRONOUS = 1; + const unsigned short MODE_ASYNCHRONOUS = 2; + + LSParser createLSParser(in unsigned short mode, + in DOMString schemaType) + raises(dom::DOMException); + LSSerializer createLSSerializer(); + LSInput createLSInput(); + LSOutput createLSOutput(); + }; + + interface LSParser { + readonly attribute DOMConfiguration domConfig; + attribute LSParserFilter filter; + readonly attribute boolean async; + readonly attribute boolean busy; + Document parse(in LSInput input) + raises(dom::DOMException, + LSException); + Document parseURI(in DOMString uri) + raises(dom::DOMException, + LSException); + + // ACTION_TYPES + const unsigned short ACTION_APPEND_AS_CHILDREN = 1; + const unsigned short ACTION_REPLACE_CHILDREN = 2; + const unsigned short ACTION_INSERT_BEFORE = 3; + const unsigned short ACTION_INSERT_AFTER = 4; + const unsigned short ACTION_REPLACE = 5; + + Node parseWithContext(in LSInput input, + in Node contextArg, + in unsigned short action) + raises(dom::DOMException, + LSException); + void abort(); + }; + + interface LSInput { + // Depending on the language binding in use, + // this attribute may not be available. + attribute LSReader characterStream; + attribute LSInputStream byteStream; + attribute DOMString stringData; + attribute DOMString systemId; + attribute DOMString publicId; + attribute DOMString baseURI; + attribute DOMString encoding; + attribute boolean certifiedText; + }; + + interface LSResourceResolver { + LSInput resolveResource(in DOMString type, + in DOMString namespaceURI, + in DOMString publicId, + in DOMString systemId, + in DOMString baseURI); + }; + + interface LSParserFilter { + + // Constants returned by startElement and acceptNode + const short FILTER_ACCEPT = 1; + const short FILTER_REJECT = 2; + const short FILTER_SKIP = 3; + const short FILTER_INTERRUPT = 4; + + unsigned short startElement(in Element elementArg); + unsigned short acceptNode(in Node nodeArg); + readonly attribute unsigned long whatToShow; + }; + + interface LSSerializer { + readonly attribute DOMConfiguration domConfig; + attribute DOMString newLine; + attribute LSSerializerFilter filter; + boolean write(in Node nodeArg, + in LSOutput destination) + raises(LSException); + boolean writeToURI(in Node nodeArg, + in DOMString uri) + raises(LSException); + DOMString writeToString(in Node nodeArg) + raises(dom::DOMException, + LSException); + }; + + interface LSOutput { + // Depending on the language binding in use, + // this attribute may not be available. + attribute LSWriter characterStream; + attribute LSOutputStream byteStream; + attribute DOMString systemId; + attribute DOMString encoding; + }; + + interface LSProgressEvent : events::Event { + readonly attribute LSInput input; + readonly attribute unsigned long position; + readonly attribute unsigned long totalSize; + }; + + interface LSLoadEvent : events::Event { + readonly attribute Document newDocument; + readonly attribute LSInput input; + }; + + interface LSSerializerFilter : traversal::NodeFilter { + readonly attribute unsigned long whatToShow; + }; +}; + +#endif // _LS_IDL_ + diff --git a/src/dom/lsimpl.cpp b/src/dom/lsimpl.cpp new file mode 100755 index 000000000..67c899f35 --- /dev/null +++ b/src/dom/lsimpl.cpp @@ -0,0 +1,437 @@ +/** + * Phoebe DOM Implementation. + * + * This is a C++ approximation of the W3C DOM model, which follows + * fairly closely the specifications in the various .idl files, copies of + * which are provided for reference. Most important is this one: + * + * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/idl-definitions.html + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2005 Bob Jamison + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "lsimpl.h" + +#include + +namespace org +{ +namespace w3c +{ +namespace dom +{ +namespace ls +{ + + + +/*######################################################################### +## LSParserImpl +#########################################################################*/ + + +/** + * + */ +bool LSParserImpl::getBusy() +{ + return false; +} + +/** + * + */ +Document *LSParserImpl::parse(const LSInput &input) + throw(dom::DOMException, LSException) +{ + + //#### Check the various inputs of 'input' in order, according + //# to the L&S spec + LSReader *lsreader = input.getCharacterStream(); + if (lsreader) + { + DOMString buf; + while (true) + { + int ch = lsreader->get(); + if (ch < 0) + break; + buf.push_back(ch); + } + XmlReader reader; + Document *doc = reader.parse(buf); + return doc; + } + + LSInputStream *inputStream = input.getByteStream(); + if (inputStream) + { + DOMString buf; + while (true) + { + int ch = inputStream->get(); + if (ch < 0) + break; + buf.push_back(ch); + } + XmlReader reader; + Document *doc = reader.parse(buf); + return doc; + } + + DOMString stringData = input.getStringData(); + if (stringData.size() > 0) + { + XmlReader reader; + Document *doc = reader.parse(stringData); + return doc; + } + + DOMString systemId = input.getSystemId(); + if (systemId.size() > 0) + { + //lets not do this yet + return NULL; + } + + DOMString publicId = input.getPublicId(); + if (publicId.size() > 0) + { + //lets not do this yet + return NULL; + } + + return NULL; +} + + +/** + * + */ +Document *LSParserImpl::parseURI(const DOMString &uri) + throw(dom::DOMException, LSException) +{ + return NULL; +} + + /** + * + */ +Node *LSParserImpl::parseWithContext(const LSInput &input, + const Node *contextArg, + unsigned short action) + throw(dom::DOMException, LSException) +{ + return NULL; +} + + + +//################## +//# Non-API methods +//################## + + + + + + + + + + + +/*######################################################################### +## LSSerializerImpl +#########################################################################*/ + + +/** + * + */ +bool LSSerializerImpl::write( + const Node *nodeArg, + const LSOutput &destination) + throw (LSException) +{ + outbuf = ""; + indent = 0; + + writeNode(nodeArg); + + //## Check in order specified in the L&S specs + LSWriter *writer = destination.getCharacterStream(); + if (writer) + { + for (unsigned int i=0 ; iput(ch); + } + return true; + } + + LSOutputStream *outputStream = destination.getByteStream(); + if (outputStream) + { + for (unsigned int i=0 ; iput(ch); + } + return true; + } + + DOMString systemId = destination.getSystemId(); + if (systemId.size() > 0) + { + //DO SOMETHING + return true; + } + + return false; +} + +/** + * + */ +bool LSSerializerImpl::writeToURI(const Node *nodeArg, + const DOMString &uriArg) + throw(LSException) +{ + outbuf = ""; + indent = 0; + + writeNode(nodeArg); + + DOMString uri = uriArg; + char *fileName = (char *) uri.c_str(); //temporary hack + FILE *f = fopen(fileName, "rb"); + if (!f) + return false; + for (unsigned int i=0 ; i') + outbuf.append(">"); + else if (ch == '"') + outbuf.append("""); + else if (ch == '\'') + outbuf.append("'"); + else + outbuf.push_back(ch); + } +} + +/** + * + */ +void LSSerializerImpl::writeNode(const Node *nodeArg) +{ + Node *node = (Node *)nodeArg; + + int type = node->getNodeType(); + + switch (type) + { + + //############# + //# DOCUMENT + //############# + case Node::DOCUMENT_NODE: + { + Document *doc = dynamic_cast(node); + writeNode(doc->getDocumentElement()); + } + break; + + //############# + //# TEXT + //############# + case Node::TEXT_NODE: + { + poxml(node->getNodeValue()); + } + break; + + + //############# + //# CDATA + //############# + case Node::CDATA_SECTION_NODE: + { + pos("getNodeValue()); + pos("]]>"); + } + break; + + + //############# + //# ELEMENT + //############# + case Node::ELEMENT_NODE: + { + + indent+=2; + + NamedNodeMap attributes = node->getAttributes(); + int nrAttrs = attributes.getLength(); + + //### Start open tag + spaces(); + po("<"); + pos(node->getNodeName()); + //if (nrAttrs>0) + // pos(newLine); + + //### Attributes + for (int i=0 ; igetNodeName()); + po("=\""); + pos(attr->getNodeValue()); + po("\""); + //pos(newLine); + } + + //### Finish open tag + //if (nrAttrs>0) + // spaces(); + po(">"); + //pos(newLine); + + //### Contents + //spaces(); + pos(node->getNodeValue()); + + //### Children + for (Node *child = node->getFirstChild() ; + child ; + child=child->getNextSibling()) + { + writeNode(child); + } + + //### Close tag + //spaces(); + po("getNodeName()); + po(">"); + pos(newLine); + + indent-=2; + } + break; + + }//switch + +} + + + + + + + + + + +} //namespace ls +} //namespace dom +} //namespace w3c +} //namespace org + + + + + +/*######################################################################### +## E N D O F F I L E +#########################################################################*/ + diff --git a/src/dom/lsimpl.cpp.orig b/src/dom/lsimpl.cpp.orig new file mode 100755 index 000000000..4397a2517 --- /dev/null +++ b/src/dom/lsimpl.cpp.orig @@ -0,0 +1,671 @@ +/* + * Copyright (c) 2004 World Wide Web Consortium, + * + * (Massachusetts Institute of Technology, European Research Consortium for + * Informatics and Mathematics, Keio University). All Rights Reserved. This + * work is distributed under the W3C(r) Software License [1] in the hope that + * it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * [1] http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 + */ + +// File: http://www.w3.org/TR/2004/REC-DOM-Level-3-LS-20040407/ls.idl + +#include "domimpl.h" +#include "eventsimpl.h" +#include "traversalimpl.h" +#include "lsimpl.h" + + + +namespace org { +namespace w3c { +namespace dom { +namespace ls { + + + + +/*######################################################################### +## DOMImplementationLSImpl +#########################################################################*/ + + +/** + * + */ +LSParser *DOMImplementationLSImpl::createLSParser(unsigned short mode, + const DOMString &schemaType) + throw (dom::DOMException) +{ + return NULL; +} + +/** + * + */ +LSSerializer *DOMImplementationLSImpl::createLSSerializer() +{ + return NULL; +} + +/** + * + */ +LSInput *DOMImplementationLSImpl::createLSInput() +{ + return NULL; +} + +/** + * + */ +LSOutput *DOMImplementationLSImpl::createLSOutput() +{ + return NULL; +} + +//################## +//# Non-API methods +//################## + +/** + * + */ +DOMImplementationLSImpl::~DOMImplementationLSImpl() +{ +} + +/*######################################################################### +## LSParserImpl +#########################################################################*/ + + +/** + * + */ +DOMConfiguration *LSParserImpl::getDomConfig() +{ + return NULL; +} + +/** + * + */ +LSParserFilter *LSParserImpl::getFilter() +{ + return NULL; +} + +/** + * + */ +void LSParserImpl::setFilter(const LSParserFilter *val) +{ +} + +/** + * + */ +bool LSParserImpl::getAsync() +{ + return false; +} + +/** + * + */ +bool LSParserImpl::getBusy() +{ + return false; +} + +/** + * + */ +Document *LSParserImpl::parse(const LSInput *input) + throw(dom::DOMException, LSException) +{ + return NULL; +} + + +/** + * + */ +Document *LSParserImpl::parseURI(const DOMString &uri) + throw(dom::DOMException, LSException) +{ + return NULL; +} + + /** + * + */ +Node *LSParserImpl::parseWithContext(const LSInput *input, + const Node *contextArg, + unsigned short action) + throw(dom::DOMException, LSException) +{ + return NULL; +} + +/** + * + */ +void LSParserImpl::abort() +{ +} + + + +//################## +//# Non-API methods +//################## + +/** + * + */ +LSParserImpl::~LSParserImpl() +{ +} + + +/*######################################################################### +## LSInputImpl +#########################################################################*/ + +/** + * + */ +LSReader *LSInputImpl::getCharacterStream() +{ + return NULL; +} + +/** + * + */ +void LSInputImpl::setCharacterStream(const LSReader *val) +{ +} + +/** + * + */ +LSInputStream *LSInputImpl::getByteStream() +{ + return NULL; +} + +/** + * + */ +void LSInputImpl::setByteStream(const LSInputStream *val) +{ +} + +/** + * + */ +DOMString LSInputImpl::getStringData() +{ + return DOMString(""); +} + +/** + * + */ +void LSInputImpl::setStringData(const DOMString &val) +{ +} + +/** + * + */ +DOMString LSInputImpl::getSystemId() +{ + return DOMString(""); +} + +/** + * + */ +void LSInputImpl::setSystemId(const DOMString &val) +{ +} + +/** + * + */ +DOMString LSInputImpl::getPublicId() +{ + return DOMString(""); +} + +/** + * + */ +void LSInputImpl::setPublicId(const DOMString &val) +{ +} + +/** + * + */ +DOMString LSInputImpl::getBaseURI() +{ + return DOMString(""); +} + +/** + * + */ +void LSInputImpl::setBaseURI(const DOMString &val) +{ +} + +/** + * + */ +DOMString LSInputImpl::getEncoding() +{ + return DOMString(""); +} + +/** + * + */ +void LSInputImpl::setEncoding(const DOMString &val) +{ +} + +/** + * + */ +bool LSInputImpl::getCertifiedText() +{ + return false; +} + +/** + * + */ +void LSInputImpl::setCertifiedText(bool val) +{ +} + +//################## +//# Non-API methods +//################## + +/** + * + */ +LSInputImpl::~LSInputImpl() +{ +} + + + + + + + +/*######################################################################### +## LSResourceResolverImpl +#########################################################################*/ + +/** + * + */ +LSInput *LSResourceResolverImpl::resolveResource(const DOMString &type, + const DOMString &namespaceURI, + const DOMString &publicId, + const DOMString *systemId, + const DOMString &baseURI) +{ +} + +//################## +//# Non-API methods +//################## + +/** + * + */ +LSResourceResolverImpl::~LSResourceResolverImpl() +{ +} + + + + +/*######################################################################### +## LSParserFilterImpl +#########################################################################*/ + + +/** + * + */ +unsigned short LSParserFilterImpl::startElement(const Element *elementArg) +{ + return 0; +} + +/** + * + */ +unsigned short LSParserFilterImpl::acceptNode(const Node *nodeArg) +{ + return 0; +} + +/** + * + */ +unsigned long LSParserFilterImpl::getWhatToShow() +{ + return 0L; +} + +//################## +//# Non-API methods +//################## + +/** + * + */ +LSParserFilterImpl::~LSParserFilterImpl() +{ +} + + + + +/*######################################################################### +## LSSerializerImpl +#########################################################################*/ + + +/** + * + */ +DOMConfiguration *LSSerializerImpl::getDomConfig() +{ + return NULL; +} + +/** + * + */ +DOMString LSSerializerImpl::getNewLine() +{ + return DOMString(""); +} + +/** + * + */ +void LSSerializerImpl::setNewLine(const DOMString &val) +{ +} + +/** + * + */ +LSSerializerFilter *LSSerializerImpl::getFilter() +{ + return NULL; +} + +/** + * + */ +void LSSerializerImpl::setFilter(const LSSerializerFilter *val) +{ +} + +/** + * + */ +bool LSSerializerImpl::write(const Node *nodeArg, + const LSOutput *destination) + throw (LSException) +{ + return false; +} + +/** + * + */ +bool LSSerializerImpl::writeToURI(const Node *nodeArg, + const DOMString &uri) + throw(LSException) +{ + return false; +} + +/** + * + */ +DOMString LSSerializerImpl::writeToString(const Node *nodeArg) + throw(dom::DOMException, LSException) +{ + return DOMString(""); +} + +//################## +//# Non-API methods +//################## + +/** + * + */ +LSSerializerImpl::~LSSerializerImpl() +{ +} + + + + +/*######################################################################### +## LSOutputImpl +#########################################################################*/ + +/** + * + */ +LSWriter *LSOutputImpl::getCharacterStream() +{ + return NULL; +} + +/** + * + */ +void LSOutputImpl::setCharacterStream(const LSWriter *val) +{ +} + +/** + * + */ +LSOutputStream *LSOutputImpl::getByteStream() +{ + return NULL; +} + +/** + * + */ +void LSOutputImpl::setByteStream(const LSOutputStream *val) +{ +} + +/** + * + */ +DOMString LSOutputImpl::getSystemId() +{ + return DOMString(""); +} + +/** + * + */ +void LSOutputImpl::setSystemId(const DOMString &val) +{ +} + +/** + * + */ +DOMString LSOutputImpl::getEncoding() +{ + return DOMString(""); +} + +/** + * + */ +void LSOutputImpl::setEncoding(const DOMString &val) +{ +} + + +//################## +//# Non-API methods +//################## + +/** + * + */ +LSOutputImpl::~LSOutputImpl() +{ +} + + + +/*######################################################################### +## LSProgressEventImpl +#########################################################################*/ + +/** + * + */ +LSInput *LSProgressEventImpl::getInput() +{ + return NULL; +} + +/** + * + */ +unsigned long LSProgressEventImpl::getPosition() +{ + return 0L; +} + +/** + * + */ +unsigned long LSProgressEventImpl::getTotalSize() +{ + return 0L; +} + +//################## +//# Non-API methods +//################## + +/** + * + */ +LSProgressEventImpl::~LSProgressEventImpl() +{ +} + + + +/*######################################################################### +## LSLoadEventImpl +#########################################################################*/ + +/** + * + */ +Document *LSLoadEventImpl::getNewDocument() +{ + return NULL; +} + +/** + * + */ +LSInput *LSLoadEventImpl::getInput() +{ + return NULL; +} + +//################## +//# Non-API methods +//################## + +/** + * + */ +LSLoadEventImpl::~LSLoadEventImpl() +{ +} + + + + + +/*######################################################################### +## LSSerializerFilterImpl +#########################################################################*/ + + +/** + * + */ +unsigned long LSSerializerFilterImpl::getWhatToShow() +{ + return 0L; +} + +//################## +//# Non-API methods +//################## + +/** + * + */ +LSSerializerFilterImpl::~LSSerializerFilterImpl() +{ +} + + + + + + + + +} //namespace ls +} //namespace dom +} //namespace w3c +} //namespace org + + + + + +/*######################################################################### +## E N D O F F I L E +#########################################################################*/ + diff --git a/src/dom/lsimpl.h b/src/dom/lsimpl.h new file mode 100755 index 000000000..ee6360143 --- /dev/null +++ b/src/dom/lsimpl.h @@ -0,0 +1,376 @@ +#ifndef __LSIMPL_H__ +#define __LSIMPL_H__ +/** + * Phoebe DOM Implementation. + * + * This is a C++ approximation of the W3C DOM model, which follows + * fairly closely the specifications in the various .idl files, copies of + * which are provided for reference. Most important is this one: + * + * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/idl-definitions.html + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2005 Bob Jamison + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "domimpl.h" +#include "events.h" +#include "traversal.h" +#include "ls.h" + + +#include "xmlreader.h" + +namespace org +{ +namespace w3c +{ +namespace dom +{ +namespace ls +{ + + +/*######################################################################### +## LSParserImpl +#########################################################################*/ + +/** + * + */ +class LSParserImpl : virtual public LSParser +{ +public: + + typedef enum + { + PARSE_AS_DATA = 0, + PARSE_AS_DOCUMENT = 1 + } ParsingModes; + + /** + * + */ + virtual bool getBusy(); + + /** + * + */ + virtual Document *parse(const LSInput &input) + throw(dom::DOMException, LSException); + + + /** + * + */ + virtual Document *parseURI(const DOMString &uri) + throw(dom::DOMException, LSException); + + /** + * + */ + virtual Node *parseWithContext(const LSInput &input, + const Node *contextArg, + unsigned short action) + throw(dom::DOMException, LSException); + + + //################## + //# Non-API methods + //################## + + /** + * + */ + LSParserImpl() + {} + + /** + * + */ + LSParserImpl(const LSParserImpl &other) : LSParser(other) + {} + + /** + * + */ + virtual ~LSParserImpl() + {} + + + + //################## + //# Internals + //################## + + +protected: + + XmlReader reader; + LSParserFilter *filter; + +}; + + + + +/*######################################################################### +## LSParserFilterImpl +#########################################################################*/ + +/** + * + */ +class LSParserFilterImpl : virtual public LSParserFilter +{ +public: + + /** + * + */ + virtual unsigned short startElement(const Element *elementArg) + { return 0; } + + /** + * + */ + virtual unsigned short acceptNode(const Node *nodeArg) + { return 0; } + + /** + * + */ + virtual unsigned long getWhatToShow() + { return 0; } + + //################## + //# Non-API methods + //################## + + /** + * + */ + virtual ~LSParserFilterImpl() + {} + + + +}; + +/*######################################################################### +## LSSerializerImpl +#########################################################################*/ + +/** + * + */ +class LSSerializerImpl : virtual public LSSerializer +{ +public: + + + /** + * + */ + virtual bool write(const Node *nodeArg, + const LSOutput &destination) + throw (LSException); + + /** + * + */ + virtual bool writeToURI(const Node *nodeArg, + const DOMString &uri) + throw(LSException); + + /** + * + */ + virtual DOMString writeToString(const Node *nodeArg) + throw(dom::DOMException, LSException); + + //################## + //# Non-API methods + //################## + + /** + * + */ + LSSerializerImpl() + { + indent = 0; + } + + /** + * + */ + virtual ~LSSerializerImpl() + {} + + + +protected: + + /** + * + */ + void writeNode(const Node *nodeArg); + +private: + + void spaces(); + + void po(char *fmt, ...); + + void pos(const DOMString &str); + + void poxml(const DOMString &str); + + DOMString outbuf; + + int indent; + + DOMConfiguration *domConfig; + + LSSerializerFilter *filter; + + + +}; + + + + +/*######################################################################### +## LSSerializerFilterImpl +#########################################################################*/ + +/** + * + */ +class LSSerializerFilterImpl : virtual public LSSerializerFilter +{ +public: + + /** + * + */ + virtual unsigned long getWhatToShow() + { return 0; } + + //################## + //# Non-API methods + //################## + + /** + * + */ + virtual ~LSSerializerFilterImpl() + {} +}; + + + +/*######################################################################### +## DOMImplementationLSImpl +#########################################################################*/ + +/** + * + */ +class DOMImplementationLSImpl : virtual public DOMImplementationLS +{ +public: + + /** + * + */ + virtual LSParser &createLSParser(unsigned short mode, + const DOMString &schemaType) + throw (dom::DOMException) + { + LSParserImpl newParser; + parser = newParser; + return parser; + } + + + /** + * + */ + virtual LSSerializer &createLSSerializer() + { + LSSerializerImpl newSerializer; + serializer = newSerializer; + return serializer; + } + + + /** + * + */ + virtual LSInput createLSInput() + { + LSInput input; + return input; + } + + /** + * + */ + virtual LSOutput createLSOutput() + { + LSOutput output; + return output; + } + + //################## + //# Non-API methods + //################## + + /** + * + */ + virtual ~DOMImplementationLSImpl() {} + +protected: + + LSParserImpl parser; + LSSerializerImpl serializer; +}; + + + + + + +} //namespace ls +} //namespace dom +} //namespace w3c +} //namespace org + + + + +#endif /* __LSIMPL_H__ */ + +/*######################################################################### +## E N D O F F I L E +#########################################################################*/ + diff --git a/src/dom/makefile.in b/src/dom/makefile.in new file mode 100644 index 000000000..1e0b42977 --- /dev/null +++ b/src/dom/makefile.in @@ -0,0 +1,17 @@ +# Convenience stub makefile to call the real Makefile. + +@SET_MAKE@ + +# Explicit so that it's the default rule. +all: + cd .. && $(MAKE) dom/all + +clean %.a %.o: + cd .. && $(MAKE) dom/$@ + +.PHONY: all clean + +OBJEXT = @OBJEXT@ + +.SUFFIXES: +.SUFFIXES: .a .$(OBJEXT) diff --git a/src/dom/meyerweb.css b/src/dom/meyerweb.css new file mode 100755 index 000000000..9cce6b65e --- /dev/null +++ b/src/dom/meyerweb.css @@ -0,0 +1,181 @@ +@import url(skel.css); + +/* generics */ + +* {font-size: 100%; padding: 0; margin: 0;} +body {font: 0.84em/1.333 Arial, sans-serif; margin: 0; padding: 0; + color: #202020; background: #FFF; + min-width: 40em; margin: 0 auto;} +a:link {color: #339;} +a:visited {color: #848;} +a img {border: none;} +h1 {font-size: 2em; margin: 2em 0 0.5em; padding: 0.25em 0;} +h2 {font-size: 1.5em; margin: 2em 0 0.33em; padding: 0.25em 0;} +h3 {font-size: 1.33em; margin: 2em 0 0.25em; padding: 0.125em 0;} +h4 {font-size: 1.1em; margin: 0.5em 0 0;} +h5 {font-size: 1em; margin: 0.5em 0 0;} +h6 {font-size: 0.85em; margin: 0.5em 0 0;} +p {margin: 0.33em 0 1em 0;} +ul, ol {margin: 1em 0; padding-left: 2.5em;} +dt {margin: 0.5em 0 0;} +dd {margin: 0.25em 0 0.5em 2.5em;} +pre, code, tt {font: 110% "Andale Mono", Courier, "Courier New", monospace;} +small {font-size: 85%;} +big {font-size: 115%;} +sup {font-size: smaller; vertical-align: 0.5em; line-height: 1px;} +img.pic {float: right; position: relative; margin: 0.25em 0 0.66em 1.5em;} +img.border {border: 3px double;} +img.standalone {display: block; margin: 0.5em auto; width: auto; max-width: 100%;} +p.standalone {text-align: center;} +p.standalone img {display: inline;} +.warning {background: #FF8; color: red; border: 2px solid; padding: 1em;} +.highlight {background: #B4D5FF; font-weight: bold;} + +table.chart {margin: 1em auto;} +table.chart caption {font-weight: bold; font-style: italic; font-size: 90%;} +table.chart th {text-align: left;} +table.chart thead th {border-bottom: 1px solid #CCC;} +table.chart th, table.chart td {border-bottom: 1px dotted #DDD;} +table.chart tbody th {padding-right: 1em;} + +/* masthead */ + +#sitemast {padding: 0; margin: 0; overflow: hidden; border-bottom: 1px solid #000; + height: 128px; width: 100%; position: relative; z-index: 1;} +#sitemast h1 {font-size: 2em; line-height: 1em; letter-spacing: 0.13em; + padding: 0; margin: 0; + position: absolute; left: 0; top: 100px; + /* hide-from-IE5/Mac hack \*/ + top: auto; bottom: 0; + /* end hack */} +#sitemast h1 a {padding: 0 0.25em;} +#sitemast h1 a, .panel a {text-decoration: none;} + +/* main content */ + +#main {margin: 2.25em 20em 0 12em; padding: 3.5em 0; + min-height: 30em;} +#main h2 {border-bottom: 1px solid #888; margin: 0; padding: 0; + font-size: 1.75em; line-height: 1;} +#main p.contact {margin: 0 1em; text-align: right; font-size: 90%;} + +#main p {line-height: 1.4;} +#main li {line-height: 1.33; margin-bottom: 0.33em;} +#main .compact li {line-height: normal; margin-bottom: 0;} +#main ul li {list-style: square;} +#main ol li {list-style: decimal;} + +#main blockquote {font-style: normal; margin: 1em 1em 1em 2em;} +#main blockquote em {font-style: italic; font-weight: inherit;} +#main blockquote p {margin: 0.33em 2.5% 0.33em 0 !important; + line-height: 1.2; text-indent: 2em;} +#main blockquote.book p {margin: 0 2.5% 0 0 !important;} +#main blockquote.lyric {font-style: italic; white-space: pre; + border: none; margin-left: 1em;} +#main blockquote.lyric p {text-indent: 0;} +.quoteattrib {margin: -0.75em 3em 0.66em; font-size: 87.5%;} +.quoteattrib cite {font-style: italic;} + +/* search bits */ + +#search {position: absolute; top: 129px; right: 0; + z-index: 10; + text-align: right; padding: 0.25em 0 1.25em 5px; + background: url(pix/logoogle2.gif) no-repeat 0% 100%;} +#search h4 {display: none;} +#search form {margin: 0; padding: 2px 1em 0;} +#search input[type="text"] {width: 14em; border: 2px inset #999;} +#search small {display: block; margin: 0 1.25em; padding: 0; + text-align: right; line-height: 1;} +#search small a {background: #FFF; color: #668; font-style: italic;} + +/* navbar */ + +#navigate {position: absolute; top: 129px; left: 0; right: 0; + padding: 0.25em 0 0.25em 1em; + z-index: 1; overflow: hidden; + height: auto; width: 85%; line-height: 2;} +#navigate h4 {display: none;} +#navigate ul, #navigate li {margin: 0; padding: 0;} +#navigate ul {padding-left: 0.5em;} + +#navlinks {float: left; width: 100%;} +#navlinks a {text-decoration: none;} +#navlinks li {float: left; list-style: none; margin-left: 1px;} +#navlinks li a {padding: 0.25em 1em; margin-right: 0.125em; + border-top: 0.75em solid #AAC; border-bottom: 1px dotted #FFF; + font-weight: bold; color: #668;} +#navlinks li ul {display: none; border: none;} +#navlinks li li a {font-weight: normal;} +#navlinks a:hover {border-top-color: #88A;} +#navlinks #otherLink {margin-left: 1.75em;} + +.arch #archLink a, +.css #cssLink a, +.tools #toolsLink a, +.write #writeLink a, +.speak #speakLink a, +.other #otherLink a +{border-color: #226 #FFF #FFF; background: #CCE; color: #226; font-style: italic;} + +/* 'sidebar' */ + +#extra {position: absolute; top: 129px; right: 0; z-index: 100; width: 18em; + font-size: 1em; line-height: 1.2; + padding: 1.75em 0 0; margin: 3em 0 0; + color: #5A5A5F;} +#extra a:link {color: #66A;} +#extra a:visited {color: #858;} + +#extra .panel {margin: 1em 0 0; padding: 1em 1em 0 3em; border: 1px dotted #FFF;} +#extra .panel h4, #extra .panel h5 {margin: 0 0 0.25em; padding: 0 0.5em 0 0; + font-size: 90%; line-height: 1; + border-bottom: 1px solid #AAA;} +#extra .panel ul {list-style: none; margin: 0 1em 0 0; padding: 0; font-size: 90%;} +#extra .panel li {margin-left: 1em; text-indent: -1em;} +#extra .panel .more {float: right; margin: -1.5em 1px 0 0.5em; + font-style: italic; text-align: right; font-size: smaller;} +#extra .panel .more a {padding-left: 15px; background: url(pix/morearr.gif) 0 66% no-repeat;} + +#extra #blogroll h5 {padding-right: 95px;} +#extra #blogroll ul {margin: 0.5em 1em 0 0;} +#extra #xfn-btn {float: right; margin: -20px 1px 0 5px;} + +#extra #excuse {text-align: center; padding: 0 0.25em 0.66em; margin: 2em 1em -2em 3em; + border: 1px solid #CCC;} +#extra #excuse h4 {display: inline; position: relative; top: -0.6em; + border: 0; padding: 0 0.25em; margin: 0; + background: #FFF; color: #666; + text-transform: capitalize; font-size: 1em; font-weight: normal;} +#extra #excuse p {margin: 0; padding: 0; color: #444;} + +#extra #extras {padding: 1em 0.5em 1em; margin: 2em 1em 0 3em; width: 13em; + color: #666; border: 1px solid #AAA; border-width: 1px 0;} +#extra #extras h4 {display: none;} +#extra #extras ul {margin: 0; text-align: center; list-style: none;} +#extra #extras li {margin-left: 0.25em; display: inline;} +#extra #extras a {margin-right: 0.25em;} + +/* miscellaneous */ + +#footer {margin: 3em 18em 0 12em; padding: 0.5em 0 3.5em; + border-top: 1px solid gray; + text-align: center; + color: gray; background: #FFF;} +#footer a {color: #558;} +#footer a:visited {color: #858;} +#footer p {line-height: 1; margin: 0; padding: 0.5em 0.25em 0; font-size: 0.85em; } + +#reading img {border: 1px solid silver;} +#extra #reading img {margin: 0.5em;} + +.book #main img.cover {float: right; margin: 1em 0 1em 2em; + border: 1px solid; border-color: #AAA #444 #444 #AAA;} + +/* Hack-o-rama! */ + +* html #navigate {padding-top: 0;} + +/*\*//*/ +body #search {width: 20em;} +/**/ diff --git a/src/dom/mingwenv.bat b/src/dom/mingwenv.bat new file mode 100755 index 000000000..48e8bf096 --- /dev/null +++ b/src/dom/mingwenv.bat @@ -0,0 +1,2 @@ +set PATH=c:\mingw\bin;%PATH% +set RM=del diff --git a/src/dom/phoebedom.h b/src/dom/phoebedom.h new file mode 100755 index 000000000..4f8143843 --- /dev/null +++ b/src/dom/phoebedom.h @@ -0,0 +1,86 @@ +#ifndef __PHOBEDOM_H__ +#define __PHOBEDOM_H__ +/** + * Phoebe DOM Implementation. + * + * This is a C++ approximation of the W3C DOM model, which follows + * fairly closely the specifications in the various .idl files, copies of + * which are provided for reference. Most important is this one: + * + * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/idl-definitions.html + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2005 Bob Jamison + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include "domimpl.h" +#include "lsimpl.h" +#include "svglsimpl.h" + +namespace phoebe +{ + +class PhoebeDOMImplementation : public org::w3c::dom::ls::DOMImplementationLSImpl +{ + +public: + + /** + * + */ + PhoebeDOMImplementation() + {} + + org::w3c::dom::ls::LSParser createLSParser(unsigned short mode, + const org::w3c::dom::DOMString &schemaType) + throw (org::w3c::dom::DOMException) + { + if (schemaType == "svg" || schemaType == "SVG") + { + org::w3c::dom::ls::SVGLSParserImpl parser; + return parser; + } + else + { + org::w3c::dom::ls::LSParser parser; + return parser; + } + } + + + /** + * + */ + virtual ~PhoebeDOMImplementation() + {} + +private: + + + +}; + + + +} // namespace phoebe + + +#endif /* __PHOBEDOM_H__ */ + diff --git a/src/dom/prop-css.cpp b/src/dom/prop-css.cpp new file mode 100755 index 000000000..59da4b54f --- /dev/null +++ b/src/dom/prop-css.cpp @@ -0,0 +1,1161 @@ +/** + * Phoebe DOM Implementation. + * + * This is a C++ approximation of the W3C DOM model, which follows + * fairly closely the specifications in the various .idl files, copies of + * which are provided for reference. Most important is this one: + * + * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/idl-definitions.html + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2005 Bob Jamison + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include + + +struct CssProp_def +{ + char *name; + char *values; + char *defaultValue; + char *appliesTo; + bool inherited; + char *percentages; + char *mediaGroups; +}; + +typedef struct CssProp_def CssProp; + +static CssProp cssProps[] = +{ + +{ +"azimuth", +" | [[ left-side | far-left | left | center-left | center | center-right | right | far-right | right-side ] || behind ] | leftwards | rightwards | inherit", +"center", +"", +true, +"", +"aural" +}, + + +{ +"background-attachment", +"scroll | fixed | inherit", +"scroll", +"", +false, +"", +"visual" +}, + + +{ +"background-color", +" | transparent | inherit", +"transparent", +"", +false, +"", +"visual" +}, + + +{ +"background-image", +" | none | inherit", +"none", +"", +false, +"", +"visual" +}, + + +{ +"background-position", +"[ [ | | left | center | right ] [ | | top | center | bottom ]? ] | [ [ left | center | right ] || [ top | center | bottom ] ] | inherit", +"0% 0%", +"", +false, +"refer to the size of the box itself", +"visual" +}, + + +{ +"background-repeat", +"repeat | repeat-x | repeat-y | no-repeat | inherit", +"repeat", +"", +false, +"", +"visual" +}, + + +{ +"background", +"['background-color' || 'background-image' || 'background-repeat' || 'background-attachment' || 'background-position'] | inherit", +"see individual properties", +"", +false, +"allowed on 'background-position", +"visual" +}, + + +{ +"border-collapse", +"collapse | separate | inherit", +"separate", +"table' and 'inline-table' elements", +true, +"", +"visual" +}, + + +{ +"border-color", +"[ | transparent ]{1,4} | inherit", +"see individual properties", +"", +false, +"", +"visual" +}, + + +{ +"border-spacing", +" ? | inherit", +"0", +"table' and 'inline-table' elements", +true, +"", +"visual" +}, + + +{ +"border-style", +"{1,4} | inherit", +"see individual properties", +"", +false, +"", +"visual" +}, + + +{ +"border-top' 'border-right' 'border-bottom' 'border-left", +"[ || || 'border-top-color' ] | inherit", +"see individual properties", +"", +false, +"", +"visual" +}, + + +{ +"border-top-color' 'border-right-color' 'border-bottom-color' 'border-left-color", +" | transparent | inherit", +"the value of the 'color' property", +"", +false, +"", +"visual" +}, + + +{ +"border-top-style' 'border-right-style' 'border-bottom-style' 'border-left-style", +" | inherit", +"none", +"", +false, +"", +"visual" +}, + + +{ +"border-top-width' 'border-right-width' 'border-bottom-width' 'border-left-width", +" | inherit", +"medium", +"", +false, +"", +"visual" +}, + + +{ +"border-width", +"{1,4} | inherit", +"see individual properties", +"", +false, +"", +"visual" +}, + + +{ +"border", +"[ || || 'border-top-color' ] | inherit", +"see individual properties", +"", +false, +"", +"visual" +}, + + +{ +"bottom", +" | | auto | inherit", +"auto", +"positioned elements", +false, +"refer to height of containing block", +"visual" +}, + + +{ +"caption-side", +"top | bottom | inherit", +"top", +"table-caption' elements", +true, +"", +"visual" +}, + + +{ +"clear", +"none | left | right | both | inherit", +"none", +"block-level elements", +false, +"", +"visual" +}, + + +{ +"clip", +" | auto | inherit", +"auto", +"absolutely positioned elements", +false, +"", +"visual" +}, + + +{ +"color", +" | inherit", +"depends on user agent", +"", +true, +"", +"visual" +}, + + +{ +"content", +"normal | [ | | | attr() | open-quote | close-quote | no-open-quote | no-close-quote ]+ | inherit", +"normal", +":before and :after pseudo-elements", +false, +"", +"all " +}, + + +{ +"counter-increment", +"[ ? ]+ | none | inherit", +"none", +"", +false, +"", +"all " +}, + + +{ +"counter-reset", +"[ ? ]+ | none | inherit", +"none", +"", +false, +"", +"all " +}, + + +{ +"cue-after", +" | none | inherit", +"none", +"", +false, +"", +"aural" +}, + + +{ +"cue-before", +" | none | inherit", +"none", +"", +false, +"", +"aural" +}, + + +{ +"cue", +"[ 'cue-before' || 'cue-after' ] | inherit", +"see individual properties", +"", +false, +"", +"aural" +}, + + +{ +"cursor", +"[ [ ,]* [ auto | crosshair | default | pointer | move | e-resize | ne-resize | nw-resize | n-resize | se-resize | sw-resize | s-resize | w-resize | text | wait | help | progress ] ] | inherit", +"auto", +"", +true, +"", +"visual, interactive " +}, + + +{ +"direction", +"ltr | rtl | inherit", +"ltr", +"all elements, but see prose", +true, +"", +"visual" +}, + + +{ +"display", +"inline | block | list-item | run-in | inline-block | table | inline-table | table-row-group | table-header-group | table-footer-group | table-row | table-column-group | table-column | table-cell | table-caption | none | inherit", +"inline", +"", +false, +"", +"all " +}, + + +{ +"elevation", +" | below | level | above | higher | lower | inherit", +"level", +"", +true, +"", +"aural" +}, + + +{ +"empty-cells", +"show | hide | inherit", +"show", +"table-cell' elements", +true, +"", +"visual" +}, + + +{ +"float", +"left | right | none | inherit", +"none", +"all, but see 9.7", +false, +"", +"visual" +}, + + +{ +"font-family", +"[[ | ] [, | ]* ] | inherit", +"depends on user agent", +"", +true, +"", +"visual" +}, + + +{ +"font-size", +" | | | | inherit", +"medium", +"", +true, +"refer to parent element's font size", +"visual" +}, + + +{ +"font-style", +"normal | italic | oblique | inherit", +"normal", +"", +true, +"", +"visual" +}, + + +{ +"font-variant", +"normal | small-caps | inherit", +"normal", +"", +true, +"", +"visual" +}, + + +{ +"font-weight", +"normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | inherit", +"normal", +"", +true, +"", +"visual" +}, + + +{ +"font", +"[ [ 'font-style' || 'font-variant' || 'font-weight' ]? 'font-size' [ / 'line-height' ]? 'font-family' ] | caption | icon | menu | message-box | small-caption | status-bar | inherit", +"see individual properties", +"", +true, +"see individual properties", +"visual" +}, + + +{ +"height", +" | | auto | inherit", +"auto", +"all elements but non-replaced inline elements, table columns, and column groups", +false, +"see prose", +"visual" +}, + + +{ +"left", +" | | auto | inherit", +"auto", +"positioned elements", +false, +"refer to width of containing block", +"visual" +}, + + +{ +"letter-spacing", +"normal | | inherit", +"normal", +"", +true, +"", +"visual" +}, + + +{ +"line-height", +"normal | | | | inherit", +"normal", +"", +true, +"refer to the font size of the element itself", +"visual" +}, + + +{ +"list-style-image", +" | none | inherit", +"none", +"elements with 'display: list-item", +true, +"", +"visual" +}, + + +{ +"list-style-position", +"inside | outside | inherit", +"outside", +"elements with 'display: list-item", +true, +"", +"visual" +}, + + +{ +"list-style-type", +"disc | circle | square | decimal | decimal-leading-zero | lower-roman | upper-roman | lower-greek | lower-latin | upper-latin | armenian | georgian | none | inherit", +"disc", +"elements with 'display: list-item", +true, +"", +"visual" +}, + + +{ +"list-style", +"[ 'list-style-type' || 'list-style-position' || 'list-style-image' ] | inherit", +"see individual properties", +"elements with 'display: list-item", +true, +"", +"visual" +}, + + +{ +"margin-right' 'margin-left", +" | inherit", +"0", +"all elements except elements with table display types other than table and inline-table", +false, +"refer to width of containing block", +"visual" +}, + + +{ +"margin-top' 'margin-bottom", +" | inherit", +"0", +"all elements except elements with table display types other than table and inline-table", +false, +"refer to width of containing block", +"visual" +}, + + +{ +"margin", +"{1,4} | inherit", +"see individual properties", +"all elements except elements with table display types other than table and inline-table", +false, +"refer to width of containing block", +"visual" +}, + + +{ +"max-height", +" | | none | inherit", +"none", +"all elements except non-replaced inline elements and table elements", +false, +"see prose", +"visual" +}, + + +{ +"max-width", +" | | none | inherit", +"none", +"all elements except non-replaced inline elements and table elements", +false, +"refer to width of containing block", +"visual" +}, + + +{ +"min-height", +" | | inherit", +"0", +"all elements except non-replaced inline elements and table elements", +false, +"see prose", +"visual" +}, + + +{ +"min-width", +" | | inherit", +"0", +"all elements except non-replaced inline elements and table elements", +false, +"refer to width of containing block", +"visual" +}, + + +{ +"orphans", +" | inherit", +"2", +"block-level elements", +true, +"", +"visual, paged " +}, + + +{ +"outline-color", +" | invert | inherit", +"invert", +"", +false, +"", +"visual, interactive " +}, + + +{ +"outline-style", +" | inherit", +"none", +"", +false, +"", +"visual, interactive " +}, + + +{ +"outline-width", +" | inherit", +"medium", +"", +false, +"", +"visual, interactive " +}, + + +{ +"outline", +"[ 'outline-color' || 'outline-style' || 'outline-width' ] | inherit", +"see individual properties", +"", +false, +"", +"visual, interactive " +}, + + +{ +"overflow", +"visible | hidden | scroll | auto | inherit", +"visible", +"block-level and replaced elements, table cells, inline blocks", +false, +"", +"visual" +}, + + +{ +"padding-top' 'padding-right' 'padding-bottom' 'padding-left", +" | inherit", +"0", +"all elements except elements with table display types other than table, inline-table, and table-cell", +false, +"refer to width of containing block", +"visual" +}, + + +{ +"padding", +"{1,4} | inherit", +"see individual properties", +"all elements except elements with table display types other than table, inline-table, and table-cell", +false, +"refer to width of containing block", +"visual" +}, + + +{ +"page-break-after", +"auto | always | avoid | left | right | inherit", +"auto", +"block-level elements", +false, +"", +"visual, paged " +}, + + +{ +"page-break-before", +"auto | always | avoid | left | right | inherit", +"auto", +"block-level elements", +false, +"", +"visual, paged " +}, + + +{ +"page-break-inside", +"avoid | auto | inherit", +"auto", +"block-level elements", +true, +"", +"visual, paged " +}, + + +{ +"pause-after", +"> so that code generation can emit a left-associative branch + * around when A is false). Nodes are labeled by token type, with a + * JSOp secondary label when needed: + * + * Label Variant Members + * ----- ------- ------- + * + * TOK_FUNCTION func pn_funAtom: atom holding function object containing + * arg and var properties. We create the function + * object at parse (not emit) time to specialize arg + * and var bytecodes early. + * pn_body: TOK_LC node for function body statements + * pn_flags: TCF_FUN_* flags (see jsemit.h) collected + * while parsing the function's body + * pn_tryCount: of try statements in function + * + * + * TOK_LC list pn_head: list of pn_count statements + * TOK_EXPORT list pn_head: list of pn_count TOK_NAMEs or one TOK_STAR + * (which is not a multiply node) + * TOK_IMPORT list pn_head: list of pn_count sub-trees of the form + * a.b.*, a[b].*, a.*, a.b, or a[b] -- but never a. + * Each member is expressed with TOK_DOT or TOK_LB. + * Each sub-tree's root node has a pn_op in the set + * JSOP_IMPORT{ALL,PROP,ELEM} + * TOK_IF ternary pn_kid1: cond, pn_kid2: then, pn_kid3: else or null + * TOK_SWITCH binary pn_left: discriminant + * pn_right: list of TOK_CASE nodes, with at most one + * TOK_DEFAULT node + * TOK_CASE, binary pn_left: case expr or null if TOK_DEFAULT + * TOK_DEFAULT pn_right: TOK_LC node for this case's statements + * TOK_WHILE binary pn_left: cond, pn_right: body + * TOK_DO binary pn_left: body, pn_right: cond + * TOK_FOR binary pn_left: either + * for/in loop: a binary TOK_IN node with + * pn_left: TOK_VAR or TOK_NAME to left of 'in' + * pn_right: object expr to right of 'in' + * for(;;) loop: a ternary TOK_RESERVED node with + * pn_kid1: init expr before first ';' + * pn_kid2: cond expr before second ';' + * pn_kid3: update expr after second ';' + * any kid may be null + * pn_right: body + * TOK_THROW unary pn_op: JSOP_THROW, pn_kid: exception + * TOK_TRY ternary pn_kid1: try block + * pn_kid2: catch blocks or null + * pn_kid3: finally block or null + * TOK_CATCH ternary pn_kid1: PN_NAME node for catch var (with pn_expr + * null or the catch guard expression) + * pn_kid2: more catch blocks or null + * pn_kid3: catch block statements + * TOK_BREAK name pn_atom: label or null + * TOK_CONTINUE name pn_atom: label or null + * TOK_WITH binary pn_left: head expr, pn_right: body + * TOK_VAR list pn_head: list of pn_count TOK_NAME nodes + * each name node has + * pn_atom: variable name + * pn_expr: initializer or null + * TOK_RETURN unary pn_kid: return expr or null + * TOK_SEMI unary pn_kid: expr or null statement + * TOK_COLON name pn_atom: label, pn_expr: labeled statement + * + * + * All left-associated binary trees of the same type are optimized into lists + * to avoid recursion when processing expression chains. + * TOK_COMMA list pn_head: list of pn_count comma-separated exprs + * TOK_ASSIGN binary pn_left: lvalue, pn_right: rvalue + * pn_op: JSOP_ADD for +=, etc. + * TOK_HOOK ternary pn_kid1: cond, pn_kid2: then, pn_kid3: else + * TOK_OR binary pn_left: first in || chain, pn_right: rest of chain + * TOK_AND binary pn_left: first in && chain, pn_right: rest of chain + * TOK_BITOR binary pn_left: left-assoc | expr, pn_right: ^ expr + * TOK_BITXOR binary pn_left: left-assoc ^ expr, pn_right: & expr + * TOK_BITAND binary pn_left: left-assoc & expr, pn_right: EQ expr + * TOK_EQOP binary pn_left: left-assoc EQ expr, pn_right: REL expr + * pn_op: JSOP_EQ, JSOP_NE, JSOP_NEW_EQ, JSOP_NEW_NE + * TOK_RELOP binary pn_left: left-assoc REL expr, pn_right: SH expr + * pn_op: JSOP_LT, JSOP_LE, JSOP_GT, JSOP_GE + * TOK_SHOP binary pn_left: left-assoc SH expr, pn_right: ADD expr + * pn_op: JSOP_LSH, JSOP_RSH, JSOP_URSH + * TOK_PLUS, binary pn_left: left-assoc ADD expr, pn_right: MUL expr + * pn_extra: if a left-associated binary TOK_PLUS + * tree has been flattened into a list (see above + * under ), pn_extra will contain + * PNX_STRCAT if at least one list element is a + * string literal (TOK_STRING); if such a list has + * any non-string, non-number term, pn_extra will + * contain PNX_CANTFOLD. + * pn_ + * TOK_MINUS pn_op: JSOP_ADD, JSOP_SUB + * TOK_STAR, binary pn_left: left-assoc MUL expr, pn_right: UNARY expr + * TOK_DIVOP pn_op: JSOP_MUL, JSOP_DIV, JSOP_MOD + * TOK_UNARYOP unary pn_kid: UNARY expr, pn_op: JSOP_NEG, JSOP_POS, + * JSOP_NOT, JSOP_BITNOT, JSOP_TYPEOF, JSOP_VOID + * TOK_INC, unary pn_kid: MEMBER expr + * TOK_DEC + * TOK_NEW list pn_head: list of ctor, arg1, arg2, ... argN + * pn_count: 1 + N (where N is number of args) + * ctor is a MEMBER expr + * TOK_DELETE unary pn_kid: MEMBER expr + * TOK_DOT name pn_expr: MEMBER expr to left of . + * pn_atom: name to right of . + * TOK_LB binary pn_left: MEMBER expr to left of [ + * pn_right: expr between [ and ] + * TOK_LP list pn_head: list of call, arg1, arg2, ... argN + * pn_count: 1 + N (where N is number of args) + * call is a MEMBER expr naming a callable object + * TOK_RB list pn_head: list of pn_count array element exprs + * [,,] holes are represented by TOK_COMMA nodes + * #n=[...] produces TOK_DEFSHARP at head of list + * pn_extra: true if extra comma at end + * TOK_RC list pn_head: list of pn_count TOK_COLON nodes where + * each has pn_left: property id, pn_right: value + * #n={...} produces TOK_DEFSHARP at head of list + * TOK_DEFSHARP unary pn_num: jsint value of n in #n= + * pn_kid: null for #n=[...] and #n={...}, primary + * if #n=primary for function, paren, name, object + * literal expressions + * TOK_USESHARP nullary pn_num: jsint value of n in #n# + * TOK_RP unary pn_kid: parenthesized expression + * TOK_NAME, name pn_atom: name, string, or object atom + * TOK_STRING, pn_op: JSOP_NAME, JSOP_STRING, or JSOP_OBJECT + * TOK_OBJECT If JSOP_NAME, pn_op may be JSOP_*ARG or JSOP_*VAR + * with pn_slot >= 0 and pn_attrs telling const-ness + * TOK_NUMBER dval pn_dval: double value of numeric literal + * TOK_PRIMARY nullary pn_op: JSOp bytecode + */ +typedef enum JSParseNodeArity { + PN_FUNC = -3, + PN_LIST = -2, + PN_TERNARY = 3, + PN_BINARY = 2, + PN_UNARY = 1, + PN_NAME = -1, + PN_NULLARY = 0 +} JSParseNodeArity; + +struct JSParseNode { + JSTokenType pn_type; + JSTokenPos pn_pos; + JSOp pn_op; + ptrdiff_t pn_offset; /* first generated bytecode offset */ + JSParseNodeArity pn_arity; + union { + struct { /* TOK_FUNCTION node */ + JSAtom *funAtom; /* atomized function object */ + JSParseNode *body; /* TOK_LC list of statements */ + uint32 flags; /* accumulated tree context flags */ + uint32 tryCount; /* count of try statements in body */ + } func; + struct { /* list of next-linked nodes */ + JSParseNode *head; /* first node in list */ + JSParseNode **tail; /* ptr to ptr to last node in list */ + uint32 count; /* number of nodes in list */ + uint32 extra; /* extra comma flag for [1,2,,] */ + } list; + struct { /* ternary: if, for(;;), ?: */ + JSParseNode *kid1; /* condition, discriminant, etc. */ + JSParseNode *kid2; /* then-part, case list, etc. */ + JSParseNode *kid3; /* else-part, default case, etc. */ + } ternary; + struct { /* two kids if binary */ + JSParseNode *left; + JSParseNode *right; + jsval val; /* switch case value */ + } binary; + struct { /* one kid if unary */ + JSParseNode *kid; + jsint num; /* -1 or sharp variable number */ + } unary; + struct { /* name, labeled statement, etc. */ + JSAtom *atom; /* name or label atom, null if slot */ + JSParseNode *expr; /* object or initializer */ + jsint slot; /* -1 or arg or local var slot */ + uintN attrs; /* attributes if local var or const */ + } name; + jsdouble dval; /* aligned numeric literal value */ + } pn_u; + JSParseNode *pn_next; /* to align dval and pn_u on RISCs */ +}; + +#define pn_funAtom pn_u.func.funAtom +#define pn_body pn_u.func.body +#define pn_flags pn_u.func.flags +#define pn_tryCount pn_u.func.tryCount +#define pn_head pn_u.list.head +#define pn_tail pn_u.list.tail +#define pn_count pn_u.list.count +#define pn_extra pn_u.list.extra +#define pn_kid1 pn_u.ternary.kid1 +#define pn_kid2 pn_u.ternary.kid2 +#define pn_kid3 pn_u.ternary.kid3 +#define pn_left pn_u.binary.left +#define pn_right pn_u.binary.right +#define pn_val pn_u.binary.val +#define pn_kid pn_u.unary.kid +#define pn_num pn_u.unary.num +#define pn_atom pn_u.name.atom +#define pn_expr pn_u.name.expr +#define pn_slot pn_u.name.slot +#define pn_attrs pn_u.name.attrs +#define pn_dval pn_u.dval + +/* PN_LIST pn_extra flags. */ +#define PNX_STRCAT 0x1 /* TOK_PLUS list has string term */ +#define PNX_CANTFOLD 0x2 /* TOK_PLUS list has unfoldable term */ + +/* + * Move pn2 into pn, preserving pn->pn_pos and pn->pn_offset and handing off + * any kids in pn2->pn_u, by clearing pn2. + */ +#define PN_MOVE_NODE(pn, pn2) \ + JS_BEGIN_MACRO \ + (pn)->pn_type = (pn2)->pn_type; \ + (pn)->pn_op = (pn2)->pn_op; \ + (pn)->pn_arity = (pn2)->pn_arity; \ + (pn)->pn_u = (pn2)->pn_u; \ + PN_CLEAR_NODE(pn2); \ + JS_END_MACRO + +#define PN_CLEAR_NODE(pn) \ + JS_BEGIN_MACRO \ + (pn)->pn_type = TOK_EOF; \ + (pn)->pn_op = JSOP_NOP; \ + (pn)->pn_arity = PN_NULLARY; \ + JS_END_MACRO + +/* True if pn is a parsenode representing a literal constant. */ +#define PN_IS_CONSTANT(pn) \ + ((pn)->pn_type == TOK_NUMBER || \ + (pn)->pn_type == TOK_STRING || \ + ((pn)->pn_type == TOK_PRIMARY && (pn)->pn_op != JSOP_THIS)) + +/* + * Compute a pointer to the last JSParseNode element in a singly-linked list. + * NB: list must be non-empty for correct PN_LAST usage! + */ +#define PN_LAST(list) \ + ((JSParseNode *)((char *)(list)->pn_tail - offsetof(JSParseNode, pn_next))) + +#define PN_INIT_LIST(list) \ + JS_BEGIN_MACRO \ + (list)->pn_head = NULL; \ + (list)->pn_tail = &(list)->pn_head; \ + (list)->pn_count = 0; \ + JS_END_MACRO + +#define PN_INIT_LIST_1(list, pn) \ + JS_BEGIN_MACRO \ + (list)->pn_head = (pn); \ + (list)->pn_tail = &(pn)->pn_next; \ + (list)->pn_count = 1; \ + JS_END_MACRO + +#define PN_APPEND(list, pn) \ + JS_BEGIN_MACRO \ + *(list)->pn_tail = (pn); \ + (list)->pn_tail = &(pn)->pn_next; \ + (list)->pn_count++; \ + JS_END_MACRO + +/* + * Parse a top-level JS script. + * + * The caller must prevent the GC from running while this function is active, + * because atoms and function newborns are not rooted yet. + */ +extern JS_FRIEND_API(JSParseNode *) +js_ParseTokenStream(JSContext *cx, JSObject *chain, JSTokenStream *ts); + +extern JS_FRIEND_API(JSBool) +js_CompileTokenStream(JSContext *cx, JSObject *chain, JSTokenStream *ts, + JSCodeGenerator *cg); + +extern JSBool +js_CompileFunctionBody(JSContext *cx, JSTokenStream *ts, JSFunction *fun); + +extern JSBool +js_FoldConstants(JSContext *cx, JSParseNode *pn, JSTreeContext *tc); + +JS_END_EXTERN_C + +#endif /* jsparse_h___ */ diff --git a/src/extension/script/js/jsprf.c b/src/extension/script/js/jsprf.c new file mode 100644 index 000000000..36bf92428 --- /dev/null +++ b/src/extension/script/js/jsprf.c @@ -0,0 +1,1212 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* +** Portable safe sprintf code. +** +** Author: Kipp E.B. Hickman +*/ +#include "jsstddef.h" +#include +#include +#include +#include +#include "jsprf.h" +#include "jslong.h" +#include "jsutil.h" /* Added by JSIFY */ + +/* +** Note: on some platforms va_list is defined as an array, +** and requires array notation. +*/ +#ifdef HAVE_VA_COPY +#define VARARGS_ASSIGN(foo, bar) VA_COPY(foo,bar) +#elif defined(HAVE_VA_LIST_AS_ARRAY) +#define VARARGS_ASSIGN(foo, bar) foo[0] = bar[0] +#else +#define VARARGS_ASSIGN(foo, bar) (foo) = (bar) +#endif + +/* +** WARNING: This code may *NOT* call JS_LOG (because JS_LOG calls it) +*/ + +/* +** XXX This needs to be internationalized! +*/ + +typedef struct SprintfStateStr SprintfState; + +struct SprintfStateStr { + int (*stuff)(SprintfState *ss, const char *sp, JSUint32 len); + + char *base; + char *cur; + JSUint32 maxlen; + + int (*func)(void *arg, const char *sp, JSUint32 len); + void *arg; +}; + +/* +** Numbered Arguement State +*/ +struct NumArgState{ + int type; /* type of the current ap */ + va_list ap; /* point to the corresponding position on ap */ +}; + +#define NAS_DEFAULT_NUM 20 /* default number of NumberedArgumentState array */ + + +#define TYPE_INT16 0 +#define TYPE_UINT16 1 +#define TYPE_INTN 2 +#define TYPE_UINTN 3 +#define TYPE_INT32 4 +#define TYPE_UINT32 5 +#define TYPE_INT64 6 +#define TYPE_UINT64 7 +#define TYPE_STRING 8 +#define TYPE_DOUBLE 9 +#define TYPE_INTSTR 10 +#define TYPE_UNKNOWN 20 + +#define FLAG_LEFT 0x1 +#define FLAG_SIGNED 0x2 +#define FLAG_SPACED 0x4 +#define FLAG_ZEROS 0x8 +#define FLAG_NEG 0x10 + +/* +** Fill into the buffer using the data in src +*/ +static int fill2(SprintfState *ss, const char *src, int srclen, int width, + int flags) +{ + char space = ' '; + int rv; + + width -= srclen; + if ((width > 0) && ((flags & FLAG_LEFT) == 0)) { /* Right adjusting */ + if (flags & FLAG_ZEROS) { + space = '0'; + } + while (--width >= 0) { + rv = (*ss->stuff)(ss, &space, 1); + if (rv < 0) { + return rv; + } + } + } + + /* Copy out the source data */ + rv = (*ss->stuff)(ss, src, (JSUint32)srclen); + if (rv < 0) { + return rv; + } + + if ((width > 0) && ((flags & FLAG_LEFT) != 0)) { /* Left adjusting */ + while (--width >= 0) { + rv = (*ss->stuff)(ss, &space, 1); + if (rv < 0) { + return rv; + } + } + } + return 0; +} + +/* +** Fill a number. The order is: optional-sign zero-filling conversion-digits +*/ +static int fill_n(SprintfState *ss, const char *src, int srclen, int width, + int prec, int type, int flags) +{ + int zerowidth = 0; + int precwidth = 0; + int signwidth = 0; + int leftspaces = 0; + int rightspaces = 0; + int cvtwidth; + int rv; + char sign; + + if ((type & 1) == 0) { + if (flags & FLAG_NEG) { + sign = '-'; + signwidth = 1; + } else if (flags & FLAG_SIGNED) { + sign = '+'; + signwidth = 1; + } else if (flags & FLAG_SPACED) { + sign = ' '; + signwidth = 1; + } + } + cvtwidth = signwidth + srclen; + + if (prec > 0) { + if (prec > srclen) { + precwidth = prec - srclen; /* Need zero filling */ + cvtwidth += precwidth; + } + } + + if ((flags & FLAG_ZEROS) && (prec < 0)) { + if (width > cvtwidth) { + zerowidth = width - cvtwidth; /* Zero filling */ + cvtwidth += zerowidth; + } + } + + if (flags & FLAG_LEFT) { + if (width > cvtwidth) { + /* Space filling on the right (i.e. left adjusting) */ + rightspaces = width - cvtwidth; + } + } else { + if (width > cvtwidth) { + /* Space filling on the left (i.e. right adjusting) */ + leftspaces = width - cvtwidth; + } + } + while (--leftspaces >= 0) { + rv = (*ss->stuff)(ss, " ", 1); + if (rv < 0) { + return rv; + } + } + if (signwidth) { + rv = (*ss->stuff)(ss, &sign, 1); + if (rv < 0) { + return rv; + } + } + while (--precwidth >= 0) { + rv = (*ss->stuff)(ss, "0", 1); + if (rv < 0) { + return rv; + } + } + while (--zerowidth >= 0) { + rv = (*ss->stuff)(ss, "0", 1); + if (rv < 0) { + return rv; + } + } + rv = (*ss->stuff)(ss, src, (JSUint32)srclen); + if (rv < 0) { + return rv; + } + while (--rightspaces >= 0) { + rv = (*ss->stuff)(ss, " ", 1); + if (rv < 0) { + return rv; + } + } + return 0; +} + +/* +** Convert a long into its printable form +*/ +static int cvt_l(SprintfState *ss, long num, int width, int prec, int radix, + int type, int flags, const char *hexp) +{ + char cvtbuf[100]; + char *cvt; + int digits; + + /* according to the man page this needs to happen */ + if ((prec == 0) && (num == 0)) { + return 0; + } + + /* + ** Converting decimal is a little tricky. In the unsigned case we + ** need to stop when we hit 10 digits. In the signed case, we can + ** stop when the number is zero. + */ + cvt = cvtbuf + sizeof(cvtbuf); + digits = 0; + while (num) { + int digit = (((unsigned long)num) % radix) & 0xF; + *--cvt = hexp[digit]; + digits++; + num = (long)(((unsigned long)num) / radix); + } + if (digits == 0) { + *--cvt = '0'; + digits++; + } + + /* + ** Now that we have the number converted without its sign, deal with + ** the sign and zero padding. + */ + return fill_n(ss, cvt, digits, width, prec, type, flags); +} + +/* +** Convert a 64-bit integer into its printable form +*/ +static int cvt_ll(SprintfState *ss, JSInt64 num, int width, int prec, int radix, + int type, int flags, const char *hexp) +{ + char cvtbuf[100]; + char *cvt; + int digits; + JSInt64 rad; + + /* according to the man page this needs to happen */ + if ((prec == 0) && (JSLL_IS_ZERO(num))) { + return 0; + } + + /* + ** Converting decimal is a little tricky. In the unsigned case we + ** need to stop when we hit 10 digits. In the signed case, we can + ** stop when the number is zero. + */ + JSLL_I2L(rad, radix); + cvt = cvtbuf + sizeof(cvtbuf); + digits = 0; + while (!JSLL_IS_ZERO(num)) { + JSInt32 digit; + JSInt64 quot, rem; + JSLL_UDIVMOD(", &rem, num, rad); + JSLL_L2I(digit, rem); + *--cvt = hexp[digit & 0xf]; + digits++; + num = quot; + } + if (digits == 0) { + *--cvt = '0'; + digits++; + } + + /* + ** Now that we have the number converted without its sign, deal with + ** the sign and zero padding. + */ + return fill_n(ss, cvt, digits, width, prec, type, flags); +} + +/* +** Convert a double precision floating point number into its printable +** form. +** +** XXX stop using sprintf to convert floating point +*/ +static int cvt_f(SprintfState *ss, double d, const char *fmt0, const char *fmt1) +{ + char fin[20]; + char fout[300]; + int amount = fmt1 - fmt0; + + JS_ASSERT((amount > 0) && (amount < (int)sizeof(fin))); + if (amount >= (int)sizeof(fin)) { + /* Totally bogus % command to sprintf. Just ignore it */ + return 0; + } + memcpy(fin, fmt0, (size_t)amount); + fin[amount] = 0; + + /* Convert floating point using the native sprintf code */ +#ifdef DEBUG + { + const char *p = fin; + while (*p) { + JS_ASSERT(*p != 'L'); + p++; + } + } +#endif + sprintf(fout, fin, d); + + /* + ** This assert will catch overflow's of fout, when building with + ** debugging on. At least this way we can track down the evil piece + ** of calling code and fix it! + */ + JS_ASSERT(strlen(fout) < sizeof(fout)); + + return (*ss->stuff)(ss, fout, strlen(fout)); +} + +/* +** Convert a string into its printable form. "width" is the output +** width. "prec" is the maximum number of characters of "s" to output, +** where -1 means until NUL. +*/ +static int cvt_s(SprintfState *ss, const char *s, int width, int prec, + int flags) +{ + int slen; + + if (prec == 0) + return 0; + + /* Limit string length by precision value */ + slen = s ? strlen(s) : 6; + if (prec > 0) { + if (prec < slen) { + slen = prec; + } + } + + /* and away we go */ + return fill2(ss, s ? s : "(null)", slen, width, flags); +} + +/* +** BiuldArgArray stands for Numbered Argument list Sprintf +** for example, +** fmp = "%4$i, %2$d, %3s, %1d"; +** the number must start from 1, and no gap among them +*/ + +static struct NumArgState* BuildArgArray( const char *fmt, va_list ap, int* rv, struct NumArgState* nasArray ) +{ + int number = 0, cn = 0, i; + const char* p; + char c; + struct NumArgState* nas; + + + /* + ** first pass: + ** detemine how many legal % I have got, then allocate space + */ + + p = fmt; + *rv = 0; + i = 0; + while( ( c = *p++ ) != 0 ){ + if( c != '%' ) + continue; + if( ( c = *p++ ) == '%' ) /* skip %% case */ + continue; + + while( c != 0 ){ + if( c > '9' || c < '0' ){ + if( c == '$' ){ /* numbered argument csae */ + if( i > 0 ){ + *rv = -1; + return NULL; + } + number++; + } else { /* non-numbered argument case */ + if( number > 0 ){ + *rv = -1; + return NULL; + } + i = 1; + } + break; + } + + c = *p++; + } + } + + if( number == 0 ){ + return NULL; + } + + + if( number > NAS_DEFAULT_NUM ){ + nas = (struct NumArgState*)malloc( number * sizeof( struct NumArgState ) ); + if( !nas ){ + *rv = -1; + return NULL; + } + } else { + nas = nasArray; + } + + for( i = 0; i < number; i++ ){ + nas[i].type = TYPE_UNKNOWN; + } + + + /* + ** second pass: + ** set nas[].type + */ + + p = fmt; + while( ( c = *p++ ) != 0 ){ + if( c != '%' ) continue; + c = *p++; + if( c == '%' ) continue; + + cn = 0; + while( c && c != '$' ){ /* should imporve error check later */ + cn = cn*10 + c - '0'; + c = *p++; + } + + if( !c || cn < 1 || cn > number ){ + *rv = -1; + break; + } + + /* nas[cn] starts from 0, and make sure nas[cn].type is not assigned */ + cn--; + if( nas[cn].type != TYPE_UNKNOWN ) + continue; + + c = *p++; + + /* width */ + if (c == '*') { + /* not supported feature, for the argument is not numbered */ + *rv = -1; + break; + } + + while ((c >= '0') && (c <= '9')) { + c = *p++; + } + + /* precision */ + if (c == '.') { + c = *p++; + if (c == '*') { + /* not supported feature, for the argument is not numbered */ + *rv = -1; + break; + } + + while ((c >= '0') && (c <= '9')) { + c = *p++; + } + } + + /* size */ + nas[cn].type = TYPE_INTN; + if (c == 'h') { + nas[cn].type = TYPE_INT16; + c = *p++; + } else if (c == 'L') { + /* XXX not quite sure here */ + nas[cn].type = TYPE_INT64; + c = *p++; + } else if (c == 'l') { + nas[cn].type = TYPE_INT32; + c = *p++; + if (c == 'l') { + nas[cn].type = TYPE_INT64; + c = *p++; + } + } + + /* format */ + switch (c) { + case 'd': + case 'c': + case 'i': + case 'o': + case 'u': + case 'x': + case 'X': + break; + + case 'e': + case 'f': + case 'g': + nas[ cn ].type = TYPE_DOUBLE; + break; + + case 'p': + /* XXX should use cpp */ + if (sizeof(void *) == sizeof(JSInt32)) { + nas[ cn ].type = TYPE_UINT32; + } else if (sizeof(void *) == sizeof(JSInt64)) { + nas[ cn ].type = TYPE_UINT64; + } else if (sizeof(void *) == sizeof(JSIntn)) { + nas[ cn ].type = TYPE_UINTN; + } else { + nas[ cn ].type = TYPE_UNKNOWN; + } + break; + + case 'C': + case 'S': + case 'E': + case 'G': + /* XXX not supported I suppose */ + JS_ASSERT(0); + nas[ cn ].type = TYPE_UNKNOWN; + break; + + case 's': + nas[ cn ].type = TYPE_STRING; + break; + + case 'n': + nas[ cn ].type = TYPE_INTSTR; + break; + + default: + JS_ASSERT(0); + nas[ cn ].type = TYPE_UNKNOWN; + break; + } + + /* get a legal para. */ + if( nas[ cn ].type == TYPE_UNKNOWN ){ + *rv = -1; + break; + } + } + + + /* + ** third pass + ** fill the nas[cn].ap + */ + + if( *rv < 0 ){ + if( nas != nasArray ) + JS_DELETE( nas ); + return NULL; + } + + cn = 0; + while( cn < number ){ + if( nas[cn].type == TYPE_UNKNOWN ){ + cn++; + continue; + } + + VARARGS_ASSIGN(nas[cn].ap, ap); + + switch( nas[cn].type ){ + case TYPE_INT16: + case TYPE_UINT16: + case TYPE_INTN: + case TYPE_UINTN: (void)va_arg( ap, JSIntn ); break; + + case TYPE_INT32: (void)va_arg( ap, JSInt32 ); break; + + case TYPE_UINT32: (void)va_arg( ap, JSUint32 ); break; + + case TYPE_INT64: (void)va_arg( ap, JSInt64 ); break; + + case TYPE_UINT64: (void)va_arg( ap, JSUint64 ); break; + + case TYPE_STRING: (void)va_arg( ap, char* ); break; + + case TYPE_INTSTR: (void)va_arg( ap, JSIntn* ); break; + + case TYPE_DOUBLE: (void)va_arg( ap, double ); break; + + default: + if( nas != nasArray ) + JS_DELETE( nas ); + *rv = -1; + return NULL; + } + + cn++; + } + + + return nas; +} + +/* +** The workhorse sprintf code. +*/ +static int dosprintf(SprintfState *ss, const char *fmt, va_list ap) +{ + char c; + int flags, width, prec, radix, type; + union { + char ch; + int i; + long l; + JSInt64 ll; + double d; + const char *s; + int *ip; + } u; + const char *fmt0; + static char *hex = "0123456789abcdef"; + static char *HEX = "0123456789ABCDEF"; + char *hexp; + int rv, i; + struct NumArgState* nas = NULL; + struct NumArgState nasArray[ NAS_DEFAULT_NUM ]; + char pattern[20]; + const char* dolPt = NULL; /* in "%4$.2f", dolPt will poiont to . */ + + + /* + ** build an argument array, IF the fmt is numbered argument + ** list style, to contain the Numbered Argument list pointers + */ + + nas = BuildArgArray( fmt, ap, &rv, nasArray ); + if( rv < 0 ){ + /* the fmt contains error Numbered Argument format, jliu@netscape.com */ + JS_ASSERT(0); + return rv; + } + + while ((c = *fmt++) != 0) { + if (c != '%') { + rv = (*ss->stuff)(ss, fmt - 1, 1); + if (rv < 0) { + return rv; + } + continue; + } + fmt0 = fmt - 1; + + /* + ** Gobble up the % format string. Hopefully we have handled all + ** of the strange cases! + */ + flags = 0; + c = *fmt++; + if (c == '%') { + /* quoting a % with %% */ + rv = (*ss->stuff)(ss, fmt - 1, 1); + if (rv < 0) { + return rv; + } + continue; + } + + if( nas != NULL ){ + /* the fmt contains the Numbered Arguments feature */ + i = 0; + while( c && c != '$' ){ /* should imporve error check later */ + i = ( i * 10 ) + ( c - '0' ); + c = *fmt++; + } + + if( nas[i-1].type == TYPE_UNKNOWN ){ + if( nas && ( nas != nasArray ) ) + JS_DELETE( nas ); + return -1; + } + + ap = nas[i-1].ap; + dolPt = fmt; + c = *fmt++; + } + + /* + * Examine optional flags. Note that we do not implement the + * '#' flag of sprintf(). The ANSI C spec. of the '#' flag is + * somewhat ambiguous and not ideal, which is perhaps why + * the various sprintf() implementations are inconsistent + * on this feature. + */ + while ((c == '-') || (c == '+') || (c == ' ') || (c == '0')) { + if (c == '-') flags |= FLAG_LEFT; + if (c == '+') flags |= FLAG_SIGNED; + if (c == ' ') flags |= FLAG_SPACED; + if (c == '0') flags |= FLAG_ZEROS; + c = *fmt++; + } + if (flags & FLAG_SIGNED) flags &= ~FLAG_SPACED; + if (flags & FLAG_LEFT) flags &= ~FLAG_ZEROS; + + /* width */ + if (c == '*') { + c = *fmt++; + width = va_arg(ap, int); + } else { + width = 0; + while ((c >= '0') && (c <= '9')) { + width = (width * 10) + (c - '0'); + c = *fmt++; + } + } + + /* precision */ + prec = -1; + if (c == '.') { + c = *fmt++; + if (c == '*') { + c = *fmt++; + prec = va_arg(ap, int); + } else { + prec = 0; + while ((c >= '0') && (c <= '9')) { + prec = (prec * 10) + (c - '0'); + c = *fmt++; + } + } + } + + /* size */ + type = TYPE_INTN; + if (c == 'h') { + type = TYPE_INT16; + c = *fmt++; + } else if (c == 'L') { + /* XXX not quite sure here */ + type = TYPE_INT64; + c = *fmt++; + } else if (c == 'l') { + type = TYPE_INT32; + c = *fmt++; + if (c == 'l') { + type = TYPE_INT64; + c = *fmt++; + } + } + + /* format */ + hexp = hex; + switch (c) { + case 'd': case 'i': /* decimal/integer */ + radix = 10; + goto fetch_and_convert; + + case 'o': /* octal */ + radix = 8; + type |= 1; + goto fetch_and_convert; + + case 'u': /* unsigned decimal */ + radix = 10; + type |= 1; + goto fetch_and_convert; + + case 'x': /* unsigned hex */ + radix = 16; + type |= 1; + goto fetch_and_convert; + + case 'X': /* unsigned HEX */ + radix = 16; + hexp = HEX; + type |= 1; + goto fetch_and_convert; + + fetch_and_convert: + switch (type) { + case TYPE_INT16: + u.l = va_arg(ap, int); + if (u.l < 0) { + u.l = -u.l; + flags |= FLAG_NEG; + } + goto do_long; + case TYPE_UINT16: + u.l = va_arg(ap, int) & 0xffff; + goto do_long; + case TYPE_INTN: + u.l = va_arg(ap, int); + if (u.l < 0) { + u.l = -u.l; + flags |= FLAG_NEG; + } + goto do_long; + case TYPE_UINTN: + u.l = (long)va_arg(ap, unsigned int); + goto do_long; + + case TYPE_INT32: + u.l = va_arg(ap, JSInt32); + if (u.l < 0) { + u.l = -u.l; + flags |= FLAG_NEG; + } + goto do_long; + case TYPE_UINT32: + u.l = (long)va_arg(ap, JSUint32); + do_long: + rv = cvt_l(ss, u.l, width, prec, radix, type, flags, hexp); + if (rv < 0) { + return rv; + } + break; + + case TYPE_INT64: + u.ll = va_arg(ap, JSInt64); + if (!JSLL_GE_ZERO(u.ll)) { + JSLL_NEG(u.ll, u.ll); + flags |= FLAG_NEG; + } + goto do_longlong; + case TYPE_UINT64: + u.ll = va_arg(ap, JSUint64); + do_longlong: + rv = cvt_ll(ss, u.ll, width, prec, radix, type, flags, hexp); + if (rv < 0) { + return rv; + } + break; + } + break; + + case 'e': + case 'E': + case 'f': + case 'g': + u.d = va_arg(ap, double); + if( nas != NULL ){ + i = fmt - dolPt; + if( i < (int)sizeof( pattern ) ){ + pattern[0] = '%'; + memcpy( &pattern[1], dolPt, (size_t)i ); + rv = cvt_f(ss, u.d, pattern, &pattern[i+1] ); + } + } else + rv = cvt_f(ss, u.d, fmt0, fmt); + + if (rv < 0) { + return rv; + } + break; + + case 'c': + u.ch = va_arg(ap, int); + if ((flags & FLAG_LEFT) == 0) { + while (width-- > 1) { + rv = (*ss->stuff)(ss, " ", 1); + if (rv < 0) { + return rv; + } + } + } + rv = (*ss->stuff)(ss, &u.ch, 1); + if (rv < 0) { + return rv; + } + if (flags & FLAG_LEFT) { + while (width-- > 1) { + rv = (*ss->stuff)(ss, " ", 1); + if (rv < 0) { + return rv; + } + } + } + break; + + case 'p': + if (sizeof(void *) == sizeof(JSInt32)) { + type = TYPE_UINT32; + } else if (sizeof(void *) == sizeof(JSInt64)) { + type = TYPE_UINT64; + } else if (sizeof(void *) == sizeof(int)) { + type = TYPE_UINTN; + } else { + JS_ASSERT(0); + break; + } + radix = 16; + goto fetch_and_convert; + +#if 0 + case 'C': + case 'S': + case 'E': + case 'G': + /* XXX not supported I suppose */ + JS_ASSERT(0); + break; +#endif + + case 's': + u.s = va_arg(ap, const char*); + rv = cvt_s(ss, u.s, width, prec, flags); + if (rv < 0) { + return rv; + } + break; + + case 'n': + u.ip = va_arg(ap, int*); + if (u.ip) { + *u.ip = ss->cur - ss->base; + } + break; + + default: + /* Not a % token after all... skip it */ +#if 0 + JS_ASSERT(0); +#endif + rv = (*ss->stuff)(ss, "%", 1); + if (rv < 0) { + return rv; + } + rv = (*ss->stuff)(ss, fmt - 1, 1); + if (rv < 0) { + return rv; + } + } + } + + /* Stuff trailing NUL */ + rv = (*ss->stuff)(ss, "\0", 1); + + if( nas && ( nas != nasArray ) ){ + JS_DELETE( nas ); + } + + return rv; +} + +/************************************************************************/ + +static int FuncStuff(SprintfState *ss, const char *sp, JSUint32 len) +{ + int rv; + + rv = (*ss->func)(ss->arg, sp, len); + if (rv < 0) { + return rv; + } + ss->maxlen += len; + return 0; +} + +JS_PUBLIC_API(JSUint32) JS_sxprintf(JSStuffFunc func, void *arg, + const char *fmt, ...) +{ + va_list ap; + int rv; + + va_start(ap, fmt); + rv = JS_vsxprintf(func, arg, fmt, ap); + va_end(ap); + return rv; +} + +JS_PUBLIC_API(JSUint32) JS_vsxprintf(JSStuffFunc func, void *arg, + const char *fmt, va_list ap) +{ + SprintfState ss; + int rv; + + ss.stuff = FuncStuff; + ss.func = func; + ss.arg = arg; + ss.maxlen = 0; + rv = dosprintf(&ss, fmt, ap); + return (rv < 0) ? (JSUint32)-1 : ss.maxlen; +} + +/* +** Stuff routine that automatically grows the malloc'd output buffer +** before it overflows. +*/ +static int GrowStuff(SprintfState *ss, const char *sp, JSUint32 len) +{ + ptrdiff_t off; + char *newbase; + JSUint32 newlen; + + off = ss->cur - ss->base; + if (off + len >= ss->maxlen) { + /* Grow the buffer */ + newlen = ss->maxlen + ((len > 32) ? len : 32); + if (ss->base) { + newbase = (char*) realloc(ss->base, newlen); + } else { + newbase = (char*) malloc(newlen); + } + if (!newbase) { + /* Ran out of memory */ + return -1; + } + ss->base = newbase; + ss->maxlen = newlen; + ss->cur = ss->base + off; + } + + /* Copy data */ + while (len) { + --len; + *ss->cur++ = *sp++; + } + JS_ASSERT((JSUint32)(ss->cur - ss->base) <= ss->maxlen); + return 0; +} + +/* +** sprintf into a malloc'd buffer +*/ +JS_PUBLIC_API(char *) JS_smprintf(const char *fmt, ...) +{ + va_list ap; + char *rv; + + va_start(ap, fmt); + rv = JS_vsmprintf(fmt, ap); + va_end(ap); + return rv; +} + +/* +** Free memory allocated, for the caller, by JS_smprintf +*/ +JS_PUBLIC_API(void) JS_smprintf_free(char *mem) +{ + JS_DELETE(mem); +} + +JS_PUBLIC_API(char *) JS_vsmprintf(const char *fmt, va_list ap) +{ + SprintfState ss; + int rv; + + ss.stuff = GrowStuff; + ss.base = 0; + ss.cur = 0; + ss.maxlen = 0; + rv = dosprintf(&ss, fmt, ap); + if (rv < 0) { + if (ss.base) { + JS_DELETE(ss.base); + } + return 0; + } + return ss.base; +} + +/* +** Stuff routine that discards overflow data +*/ +static int LimitStuff(SprintfState *ss, const char *sp, JSUint32 len) +{ + JSUint32 limit = ss->maxlen - (ss->cur - ss->base); + + if (len > limit) { + len = limit; + } + while (len) { + --len; + *ss->cur++ = *sp++; + } + return 0; +} + +/* +** sprintf into a fixed size buffer. Make sure there is a NUL at the end +** when finished. +*/ +JS_PUBLIC_API(JSUint32) JS_snprintf(char *out, JSUint32 outlen, const char *fmt, ...) +{ + va_list ap; + int rv; + + JS_ASSERT((JSInt32)outlen > 0); + if ((JSInt32)outlen <= 0) { + return 0; + } + + va_start(ap, fmt); + rv = JS_vsnprintf(out, outlen, fmt, ap); + va_end(ap); + return rv; +} + +JS_PUBLIC_API(JSUint32) JS_vsnprintf(char *out, JSUint32 outlen,const char *fmt, + va_list ap) +{ + SprintfState ss; + JSUint32 n; + + JS_ASSERT((JSInt32)outlen > 0); + if ((JSInt32)outlen <= 0) { + return 0; + } + + ss.stuff = LimitStuff; + ss.base = out; + ss.cur = out; + ss.maxlen = outlen; + (void) dosprintf(&ss, fmt, ap); + + /* If we added chars, and we didn't append a null, do it now. */ + if( (ss.cur != ss.base) && (*(ss.cur - 1) != '\0') ) + *(--ss.cur) = '\0'; + + n = ss.cur - ss.base; + return n ? n - 1 : n; +} + +JS_PUBLIC_API(char *) JS_sprintf_append(char *last, const char *fmt, ...) +{ + va_list ap; + char *rv; + + va_start(ap, fmt); + rv = JS_vsprintf_append(last, fmt, ap); + va_end(ap); + return rv; +} + +JS_PUBLIC_API(char *) JS_vsprintf_append(char *last, const char *fmt, va_list ap) +{ + SprintfState ss; + int rv; + + ss.stuff = GrowStuff; + if (last) { + int lastlen = strlen(last); + ss.base = last; + ss.cur = last + lastlen; + ss.maxlen = lastlen; + } else { + ss.base = 0; + ss.cur = 0; + ss.maxlen = 0; + } + rv = dosprintf(&ss, fmt, ap); + if (rv < 0) { + if (ss.base) { + JS_DELETE(ss.base); + } + return 0; + } + return ss.base; +} + diff --git a/src/extension/script/js/jsprf.h b/src/extension/script/js/jsprf.h new file mode 100644 index 000000000..e8cc46c0c --- /dev/null +++ b/src/extension/script/js/jsprf.h @@ -0,0 +1,148 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsprf_h___ +#define jsprf_h___ + +/* +** API for PR printf like routines. Supports the following formats +** %d - decimal +** %u - unsigned decimal +** %x - unsigned hex +** %X - unsigned uppercase hex +** %o - unsigned octal +** %hd, %hu, %hx, %hX, %ho - 16-bit versions of above +** %ld, %lu, %lx, %lX, %lo - 32-bit versions of above +** %lld, %llu, %llx, %llX, %llo - 64 bit versions of above +** %s - string +** %c - character +** %p - pointer (deals with machine dependent pointer size) +** %f - float +** %g - float +*/ +#include "jstypes.h" +#include +#include + +JS_BEGIN_EXTERN_C + +/* +** sprintf into a fixed size buffer. Guarantees that a NUL is at the end +** of the buffer. Returns the length of the written output, NOT including +** the NUL, or (JSUint32)-1 if an error occurs. +*/ +extern JS_PUBLIC_API(JSUint32) JS_snprintf(char *out, JSUint32 outlen, const char *fmt, ...); + +/* +** sprintf into a malloc'd buffer. Return a pointer to the malloc'd +** buffer on success, NULL on failure. Call "JS_smprintf_free" to release +** the memory returned. +*/ +extern JS_PUBLIC_API(char*) JS_smprintf(const char *fmt, ...); + +/* +** Free the memory allocated, for the caller, by JS_smprintf +*/ +extern JS_PUBLIC_API(void) JS_smprintf_free(char *mem); + +/* +** "append" sprintf into a malloc'd buffer. "last" is the last value of +** the malloc'd buffer. sprintf will append data to the end of last, +** growing it as necessary using realloc. If last is NULL, JS_sprintf_append +** will allocate the initial string. The return value is the new value of +** last for subsequent calls, or NULL if there is a malloc failure. +*/ +extern JS_PUBLIC_API(char*) JS_sprintf_append(char *last, const char *fmt, ...); + +/* +** sprintf into a function. The function "f" is called with a string to +** place into the output. "arg" is an opaque pointer used by the stuff +** function to hold any state needed to do the storage of the output +** data. The return value is a count of the number of characters fed to +** the stuff function, or (JSUint32)-1 if an error occurs. +*/ +typedef JSIntn (*JSStuffFunc)(void *arg, const char *s, JSUint32 slen); + +extern JS_PUBLIC_API(JSUint32) JS_sxprintf(JSStuffFunc f, void *arg, const char *fmt, ...); + +/* +** va_list forms of the above. +*/ +extern JS_PUBLIC_API(JSUint32) JS_vsnprintf(char *out, JSUint32 outlen, const char *fmt, va_list ap); +extern JS_PUBLIC_API(char*) JS_vsmprintf(const char *fmt, va_list ap); +extern JS_PUBLIC_API(char*) JS_vsprintf_append(char *last, const char *fmt, va_list ap); +extern JS_PUBLIC_API(JSUint32) JS_vsxprintf(JSStuffFunc f, void *arg, const char *fmt, va_list ap); + +/* +*************************************************************************** +** FUNCTION: JS_sscanf +** DESCRIPTION: +** JS_sscanf() scans the input character string, performs data +** conversions, and stores the converted values in the data objects +** pointed to by its arguments according to the format control +** string. +** +** JS_sscanf() behaves the same way as the sscanf() function in the +** Standard C Library (stdio.h), with the following exceptions: +** - JS_sscanf() handles the NSPR integer and floating point types, +** such as JSInt16, JSInt32, JSInt64, and JSFloat64, whereas +** sscanf() handles the standard C types like short, int, long, +** and double. +** - JS_sscanf() has no multibyte character support, while sscanf() +** does. +** INPUTS: +** const char *buf +** a character string holding the input to scan +** const char *fmt +** the format control string for the conversions +** ... +** variable number of arguments, each of them is a pointer to +** a data object in which the converted value will be stored +** OUTPUTS: none +** RETURNS: JSInt32 +** The number of values converted and stored. +** RESTRICTIONS: +** Multibyte characters in 'buf' or 'fmt' are not allowed. +*************************************************************************** +*/ + +extern JS_PUBLIC_API(JSInt32) JS_sscanf(const char *buf, const char *fmt, ...); + +JS_END_EXTERN_C + +#endif /* jsprf_h___ */ diff --git a/src/extension/script/js/jsprvtd.h b/src/extension/script/js/jsprvtd.h new file mode 100644 index 000000000..f5f1e77f6 --- /dev/null +++ b/src/extension/script/js/jsprvtd.h @@ -0,0 +1,174 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsprvtd_h___ +#define jsprvtd_h___ +/* + * JS private typename definitions. + * + * This header is included only in other .h files, for convenience and for + * simplicity of type naming. The alternative for structures is to use tags, + * which are named the same as their typedef names (legal in C/C++, and less + * noisy than suffixing the typedef name with "Struct" or "Str"). Instead, + * all .h files that include this file may use the same typedef name, whether + * declaring a pointer to struct type, or defining a member of struct type. + * + * A few fundamental scalar types are defined here too. Neither the scalar + * nor the struct typedefs should change much, therefore the nearly-global + * make dependency induced by this file should not prove painful. + */ + +#include "jspubtd.h" + +/* Scalar typedefs. */ +typedef uint8 jsbytecode; +typedef uint8 jssrcnote; +typedef uint32 jsatomid; + +/* Struct typedefs. */ +typedef struct JSArgumentFormatMap JSArgumentFormatMap; +typedef struct JSCodeGenerator JSCodeGenerator; +typedef struct JSDependentString JSDependentString; +typedef struct JSGCLockHashEntry JSGCLockHashEntry; +typedef struct JSGCRootHashEntry JSGCRootHashEntry; +typedef struct JSGCThing JSGCThing; +typedef struct JSParseNode JSParseNode; +typedef struct JSSharpObjectMap JSSharpObjectMap; +typedef struct JSToken JSToken; +typedef struct JSTokenPos JSTokenPos; +typedef struct JSTokenPtr JSTokenPtr; +typedef struct JSTokenStream JSTokenStream; +typedef struct JSTreeContext JSTreeContext; +typedef struct JSTryNote JSTryNote; + +/* Friend "Advanced API" typedefs. */ +typedef struct JSAtom JSAtom; +typedef struct JSAtomList JSAtomList; +typedef struct JSAtomListElement JSAtomListElement; +typedef struct JSAtomMap JSAtomMap; +typedef struct JSAtomState JSAtomState; +typedef struct JSCodeSpec JSCodeSpec; +typedef struct JSPrinter JSPrinter; +typedef struct JSRegExp JSRegExp; +typedef struct JSRegExpStatics JSRegExpStatics; +typedef struct JSScope JSScope; +typedef struct JSScopeOps JSScopeOps; +typedef struct JSScopeProperty JSScopeProperty; +typedef struct JSStackFrame JSStackFrame; +typedef struct JSStackHeader JSStackHeader; +typedef struct JSSubString JSSubString; + +/* "Friend" types used by jscntxt.h and jsdbgapi.h. */ +typedef enum JSTrapStatus { + JSTRAP_ERROR, + JSTRAP_CONTINUE, + JSTRAP_RETURN, + JSTRAP_THROW, + JSTRAP_LIMIT +} JSTrapStatus; + +typedef JSTrapStatus +(* JS_DLL_CALLBACK JSTrapHandler)(JSContext *cx, JSScript *script, jsbytecode *pc, + jsval *rval, void *closure); + +typedef JSBool +(* JS_DLL_CALLBACK JSWatchPointHandler)(JSContext *cx, JSObject *obj, jsval id, + jsval old, jsval *newp, void *closure); + +/* called just after script creation */ +typedef void +(* JS_DLL_CALLBACK JSNewScriptHook)(JSContext *cx, + const char *filename, /* URL of script */ + uintN lineno, /* line script starts */ + JSScript *script, + JSFunction *fun, + void *callerdata); + +/* called just before script destruction */ +typedef void +(* JS_DLL_CALLBACK JSDestroyScriptHook)(JSContext *cx, + JSScript *script, + void *callerdata); + +typedef void +(* JS_DLL_CALLBACK JSSourceHandler)(const char *filename, uintN lineno, + jschar *str, size_t length, + void **listenerTSData, void *closure); + +/* +* This hook captures high level script execution and function calls +* (JS or native). +* It is used by JS_SetExecuteHook to hook top level scripts and by +* JS_SetCallHook to hook function calls. +* It will get called twice per script or function call: +* just before execution begins and just after it finishes. In both cases +* the 'current' frame is that of the executing code. +* +* The 'before' param is JS_TRUE for the hook invocation before the execution +* and JS_FALSE for the invocation after the code has run. +* +* The 'ok' param is significant only on the post execution invocation to +* signify whether or not the code completed 'normally'. +* +* The 'closure' param is as passed to JS_SetExecuteHook or JS_SetCallHook +* for the 'before'invocation, but is whatever value is returned from that +* invocation for the 'after' invocation. Thus, the hook implementor *could* +* allocate a stucture in the 'before' invocation and return a pointer +* to that structure. The pointer would then be handed to the hook for +* the 'after' invocation. Alternately, the 'before' could just return the +* same value as in 'closure' to cause the 'after' invocation to be called +* with the same 'closure' value as the 'before'. +* +* Returning NULL in the 'before' hook will cause the 'after' hook to +* NOT be called. +*/ + +typedef void * +(* JS_DLL_CALLBACK JSInterpreterHook)(JSContext *cx, JSStackFrame *fp, JSBool before, + JSBool *ok, void *closure); + +typedef void +(* JS_DLL_CALLBACK JSObjectHook)(JSContext *cx, JSObject *obj, JSBool isNew, + void *closure); + +typedef JSBool +(* JS_DLL_CALLBACK JSDebugErrorHook)(JSContext *cx, const char *message, + JSErrorReport *report, void *closure); + +#endif /* jsprvtd_h___ */ diff --git a/src/extension/script/js/jspubtd.h b/src/extension/script/js/jspubtd.h new file mode 100644 index 000000000..e3a1a38b5 --- /dev/null +++ b/src/extension/script/js/jspubtd.h @@ -0,0 +1,564 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jspubtd_h___ +#define jspubtd_h___ +/* + * JS public API typedefs. + */ +#include "jstypes.h" +#include "jscompat.h" + +JS_BEGIN_EXTERN_C + +/* Scalar typedefs. */ +typedef uint16 jschar; +typedef int32 jsint; +typedef uint32 jsuint; +typedef float64 jsdouble; +typedef jsword jsval; +typedef jsword jsid; +typedef int32 jsrefcount; /* PRInt32 if JS_THREADSAFE, see jslock.h */ + +/* + * Run-time version enumeration. See jsconfig.h for compile-time counterparts + * to these values that may be selected by the JS_VERSION macro, and tested by + * #if expressions. + */ +typedef enum JSVersion { + JSVERSION_1_0 = 100, + JSVERSION_1_1 = 110, + JSVERSION_1_2 = 120, + JSVERSION_1_3 = 130, + JSVERSION_1_4 = 140, + JSVERSION_ECMA_3 = 148, + JSVERSION_1_5 = 150, + JSVERSION_DEFAULT = 0, + JSVERSION_UNKNOWN = -1 +} JSVersion; + +#define JSVERSION_IS_ECMA(version) \ + ((version) == JSVERSION_DEFAULT || (version) >= JSVERSION_1_3) + +/* Result of typeof operator enumeration. */ +typedef enum JSType { + JSTYPE_VOID, /* undefined */ + JSTYPE_OBJECT, /* object */ + JSTYPE_FUNCTION, /* function */ + JSTYPE_STRING, /* string */ + JSTYPE_NUMBER, /* number */ + JSTYPE_BOOLEAN, /* boolean */ + JSTYPE_LIMIT +} JSType; + +/* JSObjectOps.checkAccess mode enumeration. */ +typedef enum JSAccessMode { + JSACC_PROTO = 0, /* XXXbe redundant w.r.t. id */ + JSACC_PARENT = 1, /* XXXbe redundant w.r.t. id */ + JSACC_IMPORT = 2, /* import foo.bar */ + JSACC_WATCH = 3, /* a watchpoint on object foo for id 'bar' */ + JSACC_READ = 4, /* a "get" of foo.bar */ + JSACC_WRITE = 8, /* a "set" of foo.bar = baz */ + JSACC_LIMIT +} JSAccessMode; + +#define JSACC_TYPEMASK (JSACC_WRITE - 1) + +/* + * This enum type is used to control the behavior of a JSObject property + * iterator function that has type JSNewEnumerate. + */ +typedef enum JSIterateOp { + JSENUMERATE_INIT, /* Create new iterator state */ + JSENUMERATE_NEXT, /* Iterate once */ + JSENUMERATE_DESTROY /* Destroy iterator state */ +} JSIterateOp; + +/* Struct typedefs. */ +typedef struct JSClass JSClass; +typedef struct JSConstDoubleSpec JSConstDoubleSpec; +typedef struct JSContext JSContext; +typedef struct JSErrorReport JSErrorReport; +typedef struct JSFunction JSFunction; +typedef struct JSFunctionSpec JSFunctionSpec; +typedef struct JSIdArray JSIdArray; +typedef struct JSProperty JSProperty; +typedef struct JSPropertySpec JSPropertySpec; +typedef struct JSObject JSObject; +typedef struct JSObjectMap JSObjectMap; +typedef struct JSObjectOps JSObjectOps; +typedef struct JSRuntime JSRuntime; +typedef struct JSRuntime JSTaskState; /* XXX deprecated name */ +typedef struct JSScript JSScript; +typedef struct JSString JSString; +typedef struct JSXDRState JSXDRState; +typedef struct JSExceptionState JSExceptionState; +typedef struct JSLocaleCallbacks JSLocaleCallbacks; + +/* JSClass (and JSObjectOps where appropriate) function pointer typedefs. */ + +/* + * Add, delete, get or set a property named by id in obj. Note the jsval id + * type -- id may be a string (Unicode property identifier) or an int (element + * index). The *vp out parameter, on success, is the new property value after + * an add, get, or set. After a successful delete, *vp is JSVAL_FALSE iff + * obj[id] can't be deleted (because it's permanent). + */ +typedef JSBool +(* JS_DLL_CALLBACK JSPropertyOp)(JSContext *cx, JSObject *obj, jsval id, + jsval *vp); + +/* + * This function type is used for callbacks that enumerate the properties of + * a JSObject. The behavior depends on the value of enum_op: + * + * JSENUMERATE_INIT + * A new, opaque iterator state should be allocated and stored in *statep. + * (You can use PRIVATE_TO_JSVAL() to tag the pointer to be stored). + * + * The number of properties that will be enumerated should be returned as + * an integer jsval in *idp, if idp is non-null, and provided the number of + * enumerable properties is known. If idp is non-null and the number of + * enumerable properties can't be computed in advance, *idp should be set + * to JSVAL_ZERO. + * + * JSENUMERATE_NEXT + * A previously allocated opaque iterator state is passed in via statep. + * Return the next jsid in the iteration using *idp. The opaque iterator + * state pointed at by statep is destroyed and *statep is set to JSVAL_NULL + * if there are no properties left to enumerate. + * + * JSENUMERATE_DESTROY + * Destroy the opaque iterator state previously allocated in *statep by a + * call to this function when enum_op was JSENUMERATE_INIT. + * + * The return value is used to indicate success, with a value of JS_FALSE + * indicating failure. + */ +typedef JSBool +(* JS_DLL_CALLBACK JSNewEnumerateOp)(JSContext *cx, JSObject *obj, + JSIterateOp enum_op, + jsval *statep, jsid *idp); + +/* + * The old-style JSClass.enumerate op should define all lazy properties not + * yet reflected in obj. + */ +typedef JSBool +(* JS_DLL_CALLBACK JSEnumerateOp)(JSContext *cx, JSObject *obj); + +/* + * Resolve a lazy property named by id in obj by defining it directly in obj. + * Lazy properties are those reflected from some peer native property space + * (e.g., the DOM attributes for a given node reflected as obj) on demand. + * + * JS looks for a property in an object, and if not found, tries to resolve + * the given id. If resolve succeeds, the engine looks again in case resolve + * defined obj[id]. If no such property exists directly in obj, the process + * is repeated with obj's prototype, etc. + * + * NB: JSNewResolveOp provides a cheaper way to resolve lazy properties. + */ +typedef JSBool +(* JS_DLL_CALLBACK JSResolveOp)(JSContext *cx, JSObject *obj, jsval id); + +/* + * Like JSResolveOp, but flags provide contextual information as follows: + * + * JSRESOLVE_QUALIFIED a qualified property id: obj.id or obj[id], not id + * JSRESOLVE_ASSIGNING obj[id] is on the left-hand side of an assignment + * + * The *objp out parameter, on success, should be null to indicate that id + * was not resolved; and non-null, referring to obj or one of its prototypes, + * if id was resolved. + * + * This hook instead of JSResolveOp is called via the JSClass.resolve member + * if JSCLASS_NEW_RESOLVE is set in JSClass.flags. + * + * Setting JSCLASS_NEW_RESOLVE and JSCLASS_NEW_RESOLVE_GETS_START further + * extends this hook by passing in the starting object on the prototype chain + * via *objp. Thus a resolve hook implementation may define the property id + * being resolved in the object in which the id was first sought, rather than + * in a prototype object whose class led to the resolve hook being called. + * + * When using JSCLASS_NEW_RESOLVE_GETS_START, the resolve hook must therefore + * null *objp to signify "not resolved". With only JSCLASS_NEW_RESOLVE and no + * JSCLASS_NEW_RESOLVE_GETS_START, the hook can assume *objp is null on entry. + * This is not good practice, but enough existing hook implementations count + * on it that we can't break compatibility by passing the starting object in + * *objp without a new JSClass flag. + */ +typedef JSBool +(* JS_DLL_CALLBACK JSNewResolveOp)(JSContext *cx, JSObject *obj, jsval id, + uintN flags, JSObject **objp); + +/* + * Convert obj to the given type, returning true with the resulting value in + * *vp on success, and returning false on error or exception. + */ +typedef JSBool +(* JS_DLL_CALLBACK JSConvertOp)(JSContext *cx, JSObject *obj, JSType type, + jsval *vp); + +/* + * Finalize obj, which the garbage collector has determined to be unreachable + * from other live objects or from GC roots. Obviously, finalizers must never + * store a reference to obj. + */ +typedef void +(* JS_DLL_CALLBACK JSFinalizeOp)(JSContext *cx, JSObject *obj); + +/* + * Used by JS_AddExternalStringFinalizer and JS_RemoveExternalStringFinalizer + * to extend and reduce the set of string types finalized by the GC. + */ +typedef void +(* JS_DLL_CALLBACK JSStringFinalizeOp)(JSContext *cx, JSString *str); + +/* + * The signature for JSClass.getObjectOps, used by JS_NewObject's internals + * to discover the set of high-level object operations to use for new objects + * of the given class. All native objects have a JSClass, which is stored as + * a private (int-tagged) pointer in obj->slots[JSSLOT_CLASS]. In contrast, + * all native and host objects have a JSObjectMap at obj->map, which may be + * shared among a number of objects, and which contains the JSObjectOps *ops + * pointer used to dispatch object operations from API calls. + * + * Thus JSClass (which pre-dates JSObjectOps in the API) provides a low-level + * interface to class-specific code and data, while JSObjectOps allows for a + * higher level of operation, which does not use the object's class except to + * find the class's JSObjectOps struct, by calling clasp->getObjectOps. + * + * If this seems backwards, that's because it is! API compatibility requires + * a JSClass *clasp parameter to JS_NewObject, etc. Most host objects do not + * need to implement the larger JSObjectOps, and can share the common JSScope + * code and data used by the native (js_ObjectOps, see jsobj.c) ops. + */ +typedef JSObjectOps * +(* JS_DLL_CALLBACK JSGetObjectOps)(JSContext *cx, JSClass *clasp); + +/* + * JSClass.checkAccess type: check whether obj[id] may be accessed per mode, + * returning false on error/exception, true on success with obj[id]'s last-got + * value in *vp, and its attributes in *attrsp. As for JSPropertyOp above, id + * is either a string or an int jsval. + * + * See JSCheckAccessIdOp, below, for the JSObjectOps counterpart, which takes + * a jsid (a tagged int or aligned, unique identifier pointer) rather than a + * jsval. The native js_ObjectOps.checkAccess simply forwards to the object's + * clasp->checkAccess, so that both JSClass and JSObjectOps implementors may + * specialize access checks. + */ +typedef JSBool +(* JS_DLL_CALLBACK JSCheckAccessOp)(JSContext *cx, JSObject *obj, jsval id, + JSAccessMode mode, jsval *vp); + +/* + * Encode or decode an object, given an XDR state record representing external + * data. See jsxdrapi.h. + */ +typedef JSBool +(* JS_DLL_CALLBACK JSXDRObjectOp)(JSXDRState *xdr, JSObject **objp); + +/* + * Check whether v is an instance of obj. Return false on error or exception, + * true on success with JS_TRUE in *bp if v is an instance of obj, JS_FALSE in + * *bp otherwise. + */ +typedef JSBool +(* JS_DLL_CALLBACK JSHasInstanceOp)(JSContext *cx, JSObject *obj, jsval v, + JSBool *bp); + +/* + * Function type for JSClass.mark and JSObjectOps.mark, called from the GC to + * scan live GC-things reachable from obj's private data structure. For each + * such thing, a mark implementation must call + * + * JS_MarkGCThing(cx, thing, name, arg); + * + * The trailing name and arg parameters are used for GC_MARK_DEBUG-mode heap + * dumping and ref-path tracing. The mark function should pass a (typically + * literal) string naming the private data member for name, and it must pass + * the opaque arg parameter through from its caller. + * + * For the JSObjectOps.mark hook, the return value is the number of slots at + * obj->slots to scan. For JSClass.mark, the return value is ignored. + * + * NB: JSMarkOp implementations cannot allocate new GC-things (JS_NewObject + * called from a mark function will fail silently, e.g.). + */ +typedef uint32 +(* JS_DLL_CALLBACK JSMarkOp)(JSContext *cx, JSObject *obj, void *arg); + +/* JSObjectOps function pointer typedefs. */ + +/* + * Create a new subclass of JSObjectMap (see jsobj.h), with the nrefs and ops + * members initialized from the same-named parameters, and with the nslots and + * freeslot members initialized according to ops and clasp. Return null on + * error, non-null on success. + * + * JSObjectMaps are reference-counted by generic code in the engine. Usually, + * the nrefs parameter to JSObjectOps.newObjectMap will be 1, to count the ref + * returned to the caller on success. After a successful construction, some + * number of js_HoldObjectMap and js_DropObjectMap calls ensue. When nrefs + * reaches 0 due to a js_DropObjectMap call, JSObjectOps.destroyObjectMap will + * be called to dispose of the map. + */ +typedef JSObjectMap * +(* JS_DLL_CALLBACK JSNewObjectMapOp)(JSContext *cx, jsrefcount nrefs, + JSObjectOps *ops, JSClass *clasp, + JSObject *obj); + +/* + * Generic type for an infallible JSObjectMap operation, used currently by + * JSObjectOps.destroyObjectMap. + */ +typedef void +(* JS_DLL_CALLBACK JSObjectMapOp)(JSContext *cx, JSObjectMap *map); + +/* + * Look for id in obj and its prototype chain, returning false on error or + * exception, true on success. On success, return null in *propp if id was + * not found. If id was found, return the first object searching from obj + * along its prototype chain in which id names a direct property in *objp, and + * return a non-null, opaque property pointer in *propp. + * + * If JSLookupPropOp succeeds and returns with *propp non-null, that pointer + * may be passed as the prop parameter to a JSAttributesOp, as a short-cut + * that bypasses id re-lookup. In any case, a non-null *propp result after a + * successful lookup must be dropped via JSObjectOps.dropProperty. + * + * NB: successful return with non-null *propp means the implementation may + * have locked *objp and added a reference count associated with *propp, so + * callers should not risk deadlock by nesting or interleaving other lookups + * or any obj-bearing ops before dropping *propp. + */ +typedef JSBool +(* JS_DLL_CALLBACK JSLookupPropOp)(JSContext *cx, JSObject *obj, jsid id, + JSObject **objp, JSProperty **propp +#if defined JS_THREADSAFE && defined DEBUG + , const char *file, uintN line +#endif + ); + +/* + * Define obj[id], a direct property of obj named id, having the given initial + * value, with the specified getter, setter, and attributes. If the propp out + * param is non-null, *propp on successful return contains an opaque property + * pointer usable as a speedup hint with JSAttributesOp. But note that propp + * may be null, indicating that the caller is not interested in recovering an + * opaque pointer to the newly-defined property. + * + * If propp is non-null and JSDefinePropOp succeeds, its caller must be sure + * to drop *propp using JSObjectOps.dropProperty in short order, just as with + * JSLookupPropOp. + */ +typedef JSBool +(* JS_DLL_CALLBACK JSDefinePropOp)(JSContext *cx, JSObject *obj, + jsid id, jsval value, + JSPropertyOp getter, JSPropertyOp setter, + uintN attrs, JSProperty **propp); + +/* + * Get, set, or delete obj[id], returning false on error or exception, true + * on success. If getting or setting, the new value is returned in *vp on + * success. If deleting without error, *vp will be JSVAL_FALSE if obj[id] is + * permanent, and JSVAL_TRUE if id named a direct property of obj that was in + * fact deleted, or if id names no direct property of obj (id could name a + * prototype property, or no property in obj or its prototype chain). + */ +typedef JSBool +(* JS_DLL_CALLBACK JSPropertyIdOp)(JSContext *cx, JSObject *obj, jsid id, + jsval *vp); + +/* + * Get or set attributes of the property obj[id]. Return false on error or + * exception, true with current attributes in *attrsp. If prop is non-null, + * it must come from the *propp out parameter of a prior JSDefinePropOp or + * JSLookupPropOp call. + */ +typedef JSBool +(* JS_DLL_CALLBACK JSAttributesOp)(JSContext *cx, JSObject *obj, jsid id, + JSProperty *prop, uintN *attrsp); + +/* + * JSObjectOps.checkAccess type: check whether obj[id] may be accessed per + * mode, returning false on error/exception, true on success with obj[id]'s + * last-got value in *vp, and its attributes in *attrsp. + */ +typedef JSBool +(* JS_DLL_CALLBACK JSCheckAccessIdOp)(JSContext *cx, JSObject *obj, jsid id, + JSAccessMode mode, jsval *vp, + uintN *attrsp); + +/* + * A generic type for functions mapping an object to another object, or null + * if an error or exception was thrown on cx. Used by JSObjectOps.thisObject + * at present. + */ +typedef JSObject * +(* JS_DLL_CALLBACK JSObjectOp)(JSContext *cx, JSObject *obj); + +/* + * A generic type for functions taking a context, object, and property, with + * no return value. Used by JSObjectOps.dropProperty currently (see above, + * JSDefinePropOp and JSLookupPropOp, for the object-locking protocol in which + * dropProperty participates). + */ +typedef void +(* JS_DLL_CALLBACK JSPropertyRefOp)(JSContext *cx, JSObject *obj, + JSProperty *prop); + +/* + * Function type for JSObjectOps.setProto and JSObjectOps.setParent. These + * hooks must check for cycles without deadlocking, and otherwise take special + * steps. See jsobj.c, js_SetProtoOrParent, for an example. + */ +typedef JSBool +(* JS_DLL_CALLBACK JSSetObjectSlotOp)(JSContext *cx, JSObject *obj, + uint32 slot, JSObject *pobj); + +/* + * Get and set a required slot, one that should already have been allocated. + * These operations are infallible, so required slots must be pre-allocated, + * or implementations must suppress out-of-memory errors. The native ops + * (js_ObjectOps, see jsobj.c) access slots reserved by including a call to + * the JSCLASS_HAS_RESERVED_SLOTS(n) macro in the JSClass.flags initializer. + * + * NB: the slot parameter is a zero-based index into obj->slots[], unlike the + * index parameter to the JS_GetReservedSlot and JS_SetReservedSlot API entry + * points, which is a zero-based index into the JSCLASS_RESERVED_SLOTS(clasp) + * reserved slots that come after the initial well-known slots: proto, parent, + * class, and optionally, the private data slot. + */ +typedef jsval +(* JS_DLL_CALLBACK JSGetRequiredSlotOp)(JSContext *cx, JSObject *obj, + uint32 slot); + +typedef void +(* JS_DLL_CALLBACK JSSetRequiredSlotOp)(JSContext *cx, JSObject *obj, + uint32 slot, jsval v); + +/* Typedef for native functions called by the JS VM. */ + +typedef JSBool +(* JS_DLL_CALLBACK JSNative)(JSContext *cx, JSObject *obj, uintN argc, + jsval *argv, jsval *rval); + +/* Callbacks and their arguments. */ + +typedef enum JSGCStatus { + JSGC_BEGIN, + JSGC_END, + JSGC_MARK_END, + JSGC_FINALIZE_END +} JSGCStatus; + +typedef JSBool +(* JS_DLL_CALLBACK JSGCCallback)(JSContext *cx, JSGCStatus status); + +typedef JSBool +(* JS_DLL_CALLBACK JSBranchCallback)(JSContext *cx, JSScript *script); + +typedef void +(* JS_DLL_CALLBACK JSErrorReporter)(JSContext *cx, const char *message, + JSErrorReport *report); + +typedef struct JSErrorFormatString { + const char *format; + uintN argCount; +} JSErrorFormatString; + +typedef const JSErrorFormatString * +(* JS_DLL_CALLBACK JSErrorCallback)(void *userRef, const char *locale, + const uintN errorNumber); + +#ifdef va_start +#define JS_ARGUMENT_FORMATTER_DEFINED 1 + +typedef JSBool +(* JS_DLL_CALLBACK JSArgumentFormatter)(JSContext *cx, const char *format, + JSBool fromJS, jsval **vpp, + va_list *app); +#endif + +typedef JSBool +(* JS_DLL_CALLBACK JSLocaleToUpperCase)(JSContext *cx, JSString *src, + jsval *rval); + +typedef JSBool +(* JS_DLL_CALLBACK JSLocaleToLowerCase)(JSContext *cx, JSString *src, + jsval *rval); + +typedef JSBool +(* JS_DLL_CALLBACK JSLocaleCompare)(JSContext *cx, + JSString *src1, JSString *src2, + jsval *rval); + +/* + * Security protocol types. + */ +typedef struct JSPrincipals JSPrincipals; + +/* + * XDR-encode or -decode a principals instance, based on whether xdr->mode is + * JSXDR_ENCODE, in which case *principalsp should be encoded; or JSXDR_DECODE, + * in which case implementations must return a held (via JSPRINCIPALS_HOLD), + * non-null *principalsp out parameter. Return true on success, false on any + * error, which the implementation must have reported. + */ +typedef JSBool +(* JS_DLL_CALLBACK JSPrincipalsTranscoder)(JSXDRState *xdr, + JSPrincipals **principalsp); + +/* + * Return a weak reference to the principals associated with obj, possibly via + * the immutable parent chain leading from obj to a top-level container (e.g., + * a window object in the DOM level 0). If there are no principals associated + * with obj, return null. Therefore null does not mean an error was reported; + * in no event should an error be reported or an exception be thrown by this + * callback's implementation. + */ +typedef JSPrincipals * +(* JS_DLL_CALLBACK JSObjectPrincipalsFinder)(JSContext *cx, JSObject *obj); + +JS_END_EXTERN_C + +#endif /* jspubtd_h___ */ diff --git a/src/extension/script/js/jsregexp.c b/src/extension/script/js/jsregexp.c new file mode 100644 index 000000000..18ab92c35 --- /dev/null +++ b/src/extension/script/js/jsregexp.c @@ -0,0 +1,3773 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS regular expressions, after Perl. + */ +#include "jsstddef.h" +#include +#include +#include "jstypes.h" +#include "jsarena.h" /* Added by JSIFY */ +#include "jsutil.h" /* Added by JSIFY */ +#include "jsapi.h" +#include "jsarray.h" +#include "jsatom.h" +#include "jscntxt.h" +#include "jsconfig.h" +#include "jsfun.h" +#include "jsgc.h" +#include "jsinterp.h" +#include "jslock.h" +#include "jsnum.h" +#include "jsobj.h" +#include "jsopcode.h" +#include "jsregexp.h" +#include "jsscan.h" +#include "jsstr.h" + +#ifdef XP_MAC +#include +#endif + +#if JS_HAS_REGEXPS + +/* Note : contiguity of 'simple opcodes' is important for simpleMatch() */ +typedef enum REOp { + REOP_EMPTY = 0, /* match rest of input against rest of r.e. */ + REOP_ALT = 1, /* alternative subexpressions in kid and next */ + REOP_SIMPLE_START = 2, /* start of 'simple opcodes' */ + REOP_BOL = 2, /* beginning of input (or line if multiline) */ + REOP_EOL = 3, /* end of input (or line if multiline) */ + REOP_WBDRY = 4, /* match "" at word boundary */ + REOP_WNONBDRY = 5, /* match "" at word non-boundary */ + REOP_DOT = 6, /* stands for any character */ + REOP_DIGIT = 7, /* match a digit char: [0-9] */ + REOP_NONDIGIT = 8, /* match a non-digit char: [^0-9] */ + REOP_ALNUM = 9, /* match an alphanumeric char: [0-9a-z_A-Z] */ + REOP_NONALNUM = 10, /* match a non-alphanumeric char: [^0-9a-z_A-Z] */ + REOP_SPACE = 11, /* match a whitespace char */ + REOP_NONSPACE = 12, /* match a non-whitespace char */ + REOP_BACKREF = 13, /* back-reference (e.g., \1) to a parenthetical */ + REOP_FLAT = 14, /* match a flat string */ + REOP_FLAT1 = 15, /* match a single char */ + REOP_FLATi = 16, /* case-independent REOP_FLAT */ + REOP_FLAT1i = 17, /* case-independent REOP_FLAT1 */ + REOP_UCFLAT1 = 18, /* single Unicode char */ + REOP_UCFLAT1i = 19, /* case-independent REOP_UCFLAT1 */ + REOP_UCFLAT = 20, /* flat Unicode string; len immediate counts chars */ + REOP_UCFLATi = 21, /* case-independent REOP_UCFLAT */ + REOP_CLASS = 22, /* character class with index */ + REOP_NCLASS = 23, /* negated character class with index */ + REOP_SIMPLE_END = 23, /* end of 'simple opcodes' */ + REOP_QUANT = 25, /* quantified atom: atom{1,2} */ + REOP_STAR = 26, /* zero or more occurrences of kid */ + REOP_PLUS = 27, /* one or more occurrences of kid */ + REOP_OPT = 28, /* optional subexpression in kid */ + REOP_LPAREN = 29, /* left paren bytecode: kid is u.num'th sub-regexp */ + REOP_RPAREN = 30, /* right paren bytecode */ + REOP_JUMP = 31, /* for deoptimized closure loops */ + REOP_DOTSTAR = 32, /* optimize .* to use a single opcode */ + REOP_ANCHOR = 33, /* like .* but skips left context to unanchored r.e. */ + REOP_EOLONLY = 34, /* $ not preceded by any pattern */ + REOP_BACKREFi = 37, /* case-independent REOP_BACKREF */ + REOP_LPARENNON = 41, /* non-capturing version of REOP_LPAREN */ + REOP_ASSERT = 43, /* zero width positive lookahead assertion */ + REOP_ASSERT_NOT = 44, /* zero width negative lookahead assertion */ + REOP_ASSERTTEST = 45, /* sentinel at end of assertion child */ + REOP_ASSERTNOTTEST = 46, /* sentinel at end of !assertion child */ + REOP_MINIMALSTAR = 47, /* non-greedy version of * */ + REOP_MINIMALPLUS = 48, /* non-greedy version of + */ + REOP_MINIMALOPT = 49, /* non-greedy version of ? */ + REOP_MINIMALQUANT = 50, /* non-greedy version of {} */ + REOP_ENDCHILD = 51, /* sentinel at end of quantifier child */ + REOP_REPEAT = 52, /* directs execution of greedy quantifier */ + REOP_MINIMALREPEAT = 53, /* directs execution of non-greedy quantifier */ + REOP_ALTPREREQ = 54, /* prerequisite for ALT, either of two chars */ + REOP_ALTPREREQ2 = 55, /* prerequisite for ALT, a char or a class */ + REOP_ENDALT = 56, /* end of final alternate */ + REOP_CONCAT = 57, /* concatenation of terms (parse time only) */ + + REOP_END +} REOp; + +#define REOP_IS_SIMPLE(op) (((op) >= REOP_SIMPLE_START) && ((op) <= REOP_SIMPLE_END)) + +struct RENode { + REOp op; /* r.e. op bytecode */ + RENode *next; /* next in concatenation order */ + void *kid; /* first operand */ + union { + void *kid2; /* second operand */ + jsint num; /* could be a number */ + jsint parenIndex; /* or a parenthesis index */ + struct { /* or a quantifier range */ + uint16 min; + uint16 max; + JSBool greedy; + } range; + struct { /* or a character class */ + uint16 startIndex; + uint16 kidlen; /* length of string at kid, in jschars */ + uint16 bmsize; /* bitmap size, based on max char code */ + uint16 index; /* index into class list */ + JSBool sense; + } ucclass; + struct { /* or a literal sequence */ + jschar chr; /* of one character */ + uint16 length; /* or many (via the kid) */ + } flat; + struct { + RENode *kid2; /* second operand from ALT */ + jschar ch1; /* match char for ALTPREREQ */ + jschar ch2; /* ditto, or class index for ALTPREREQ2 */ + } altprereq; + } u; +}; + +#define RE_IS_LETTER(c) ( ((c >= 'A') && (c <= 'Z')) || \ + ((c >= 'a') && (c <= 'z')) ) +#define RE_IS_LINE_TERM(c) ( (c == '\n') || (c == '\r') || \ + (c == LINE_SEPARATOR) || (c == PARA_SEPARATOR)) + +#define CLASS_CACHE_SIZE (4) +typedef struct CompilerState { + JSContext *context; + JSTokenStream *tokenStream; /* For reporting errors */ + const jschar *cpbegin; + const jschar *cpend; + const jschar *cp; + uintN flags; + uint16 parenCount; + uint16 classCount; /* number of [] encountered */ + size_t progLength; /* estimated bytecode length */ + uintN treeDepth; /* maximum depth of parse tree */ + RENode *result; + struct { + const jschar *start; /* small cache of class strings */ + uint16 length; /* since they're often the same */ + uint16 index; + } classCache[CLASS_CACHE_SIZE]; +} CompilerState; + +typedef struct RECapture { + int32 index; /* start of contents, -1 for empty */ + uint16 length; /* length of capture */ +} RECapture; + +typedef struct REMatchState { + const jschar *cp; + RECapture parens[1]; /* first of 're->parenCount' captures, + * allocated at end of this struct. + */ +} REMatchState; + +struct REBackTrackData; + +typedef struct REProgState { + jsbytecode *continue_pc; /* current continuation data */ + jsbytecode continue_op; + uint16 index; /* progress in text */ + uintN parenSoFar; /* highest indexed paren started */ + union { + struct { + uint16 min; /* current quantifier limits */ + uint16 max; + } quantifier; + struct { + size_t top; /* backtrack stack state */ + size_t sz; + } assertion; + } u; +} REProgState; + +typedef struct REBackTrackData { + size_t sz; /* size of previous stack entry */ + jsbytecode *backtrack_pc; /* where to backtrack to */ + jsbytecode backtrack_op; + const jschar *cp; /* index in text of match at backtrack */ + intN parenIndex; /* start index of saved paren contents */ + uint16 parenCount; /* # of saved paren contents */ + uint16 precedingStateTop; /* number of parent states */ + /* saved parent states follow */ + /* saved paren contents follow */ +} REBackTrackData; + +#define INITIAL_STATESTACK (100) +#define INITIAL_BACKTRACK (8000) + +typedef struct REGlobalData { + JSContext *cx; + JSRegExp *regexp; /* the RE in execution */ + JSBool ok; /* runtime error (out_of_memory only?) */ + size_t start; /* offset to start at */ + ptrdiff_t skipped; /* chars skipped anchoring this r.e. */ + const jschar *cpbegin, *cpend; /* text base address and limit */ + + REProgState *stateStack; /* stack of state of current parents */ + uint16 stateStackTop; + uint16 maxStateStack; + + REBackTrackData *backTrackStack;/* stack of matched-so-far positions */ + REBackTrackData *backTrackSP; + size_t maxBackTrack; + size_t cursz; /* size of current stack entry */ + + JSArenaPool pool; /* I don't understand but it's faster to + * use this than to malloc/free the three + * items that are allocated from this pool + */ + +} REGlobalData; + + +/* + * 1. If IgnoreCase is false, return ch. + * 2. Let u be ch converted to upper case as if by calling + * String.prototype.toUpperCase on the one-character string ch. + * 3. If u does not consist of a single character, return ch. + * 4. Let cu be u's character. + * 5. If ch's code point value is greater than or equal to decimal 128 and cu's + * code point value is less than decimal 128, then return ch. + * 6. Return cu. + */ +static jschar +upcase(jschar ch) +{ + jschar cu = JS_TOUPPER(ch); + if ((ch >= 128) && (cu < 128)) return ch; + return cu; +} + +static jschar +downcase(jschar ch) +{ + jschar cl = JS_TOLOWER(ch); + if ((cl >= 128) && (ch < 128)) return ch; + return cl; +} + +/* Construct and initialize an RENode, returning NULL for out-of-memory */ +static RENode * +NewRENode(CompilerState *state, REOp op) +{ + JSContext *cx; + RENode *ren; + + cx = state->context; + JS_ARENA_ALLOCATE_CAST(ren, RENode *, &cx->tempPool, sizeof *ren); + if (!ren) { + JS_ReportOutOfMemory(cx); + return NULL; + } + ren->op = op; + ren->next = NULL; + ren->kid = NULL; + return ren; +} + +/* + * Validates and converts hex ascii value. + */ +static JSBool +isASCIIHexDigit(jschar c, uintN *digit) +{ + uintN cv = c; + + if (cv < '0') + return JS_FALSE; + if (cv <= '9') { + *digit = cv - '0'; + return JS_TRUE; + } + cv |= 0x20; + if (cv >= 'a' && cv <= 'f') { + *digit = cv - 'a' + 10; + return JS_TRUE; + } + return JS_FALSE; +} + + +typedef struct { + REOp op; + const jschar *errPos; + uint16 parenIndex; +} REOpData; + + +/* + * Process the op against the two top operands, reducing them to a single + * operand in the penultimate slot. Update progLength and treeDepth. + */ +static JSBool +processOp(CompilerState *state, REOpData *opData, RENode **operandStack, intN operandSP) +{ + RENode *result; + + switch (opData->op) { + case REOP_ALT: + result = NewRENode(state, REOP_ALT); + if (!result) + return JS_FALSE; + result->kid = operandStack[operandSP - 2]; + result->u.kid2 = operandStack[operandSP - 1]; + operandStack[operandSP - 2] = result; + /* + * look at both alternates to see if there's a FLAT or a CLASS at + * the start of each. If so, use a prerequisite match + */ + ++state->treeDepth; + if ((((RENode *)(result->kid))->op == REOP_FLAT) + && (((RENode *)(result->u.kid2))->op == REOP_FLAT) + && ((state->flags & JSREG_FOLD) == 0) ) { + result->op = REOP_ALTPREREQ; + result->u.altprereq.ch1 + = ((RENode *)(result->kid))->u.flat.chr; + result->u.altprereq.ch2 + = ((RENode *)(result->u.kid2))->u.flat.chr; + /* ALTPREREQ, , uch1, uch2, , ..., + JUMP, ... ENDALT */ + state->progLength += 13; + } + else + if ((((RENode *)(result->kid))->op == REOP_CLASS) + && (((RENode *)(result->kid))->u.ucclass.index < 256) + && (((RENode *)(result->u.kid2))->op == REOP_FLAT) + && ((state->flags & JSREG_FOLD) == 0) ) { + result->op = REOP_ALTPREREQ2; + result->u.altprereq.ch1 + = ((RENode *)(result->u.kid2))->u.flat.chr; + result->u.altprereq.ch2 + = ((RENode *)(result->kid))->u.ucclass.index; + /* ALTPREREQ2, , uch1, uch2, , ..., + JUMP, ... ENDALT */ + state->progLength += 13; + } + else + if ((((RENode *)(result->kid))->op == REOP_FLAT) + && (((RENode *)(result->u.kid2))->op == REOP_CLASS) + && (((RENode *)(result->u.kid2))->u.ucclass.index < 256) + && ((state->flags & JSREG_FOLD) == 0) ) { + result->op = REOP_ALTPREREQ2; + result->u.altprereq.ch1 + = ((RENode *)(result->kid))->u.flat.chr; + result->u.altprereq.ch2 + = ((RENode *)(result->u.kid2))->u.ucclass.index; + /* ALTPREREQ2, , uch1, uch2, , ..., + JUMP, ... ENDALT */ + state->progLength += 13; + } + else + /* ALT, , ..., JUMP, ... ENDALT */ + state->progLength += 7; + break; + case REOP_CONCAT: + result = operandStack[operandSP - 2]; + while (result->next) + result = result->next; + result->next = operandStack[operandSP - 1]; + break; + case REOP_ASSERT: + case REOP_ASSERT_NOT: + case REOP_LPARENNON: + case REOP_LPAREN: + /* These should have been processed by a close paren. */ + js_ReportCompileErrorNumber(state->context, state->tokenStream, + NULL, JSREPORT_ERROR, + JSMSG_MISSING_PAREN, opData->errPos); + return JS_FALSE; + default:; + } + return JS_TRUE; +} + +/* + * Parser forward declarations. + */ +static JSBool parseTerm(CompilerState *state); +static JSBool parseQuantifier(CompilerState *state); + +/* + * Top-down regular expression grammar, based closely on Perl4. + * + * regexp: altern A regular expression is one or more + * altern '|' regexp alternatives separated by vertical bar. + */ + +#define INITIAL_STACK_SIZE (128) +static JSBool +parseRegExp(CompilerState *state) +{ + uint16 parenIndex; + RENode *operand; + REOpData *operatorStack; + RENode **operandStack; + REOp op; + intN i; + JSBool result = JS_FALSE; + + intN operatorSP = 0, operatorStackSize = INITIAL_STACK_SIZE; + intN operandSP = 0, operandStackSize = INITIAL_STACK_SIZE; + + /* Watch out for empty regexp */ + if (state->cp == state->cpend) { + state->result = NewRENode(state, REOP_EMPTY); + return (state->result != NULL); + } + + operatorStack = (REOpData *)JS_malloc(state->context, + sizeof(REOpData) * operatorStackSize); + if (!operatorStack) + return JS_FALSE; + + operandStack = (RENode **)JS_malloc(state->context, + sizeof(RENode *) * operandStackSize); + if (!operandStack) + goto out; + + + while (JS_TRUE) { + if (state->cp == state->cpend) { + /* + * If we are at the end of the regexp and we're short an operand, + * the regexp must have the form /x|/ or some such. + */ + if (operatorSP == operandSP) { + operand = NewRENode(state, REOP_EMPTY); + if (!operand) + goto out; + goto pushOperand; + } + } else { + switch (*state->cp) { + /* balance '(' */ + case '(': /* balance ')' */ + ++state->cp; + if ((state->cp < state->cpend) && (*state->cp == '?') + && ( (state->cp[1] == '=') + || (state->cp[1] == '!') + || (state->cp[1] == ':') )) { + ++state->cp; + if (state->cp == state->cpend) { + js_ReportCompileErrorNumber(state->context, + state->tokenStream, + NULL, JSREPORT_ERROR, + JSMSG_MISSING_PAREN); + goto out; + } + switch (*state->cp++) { + case '=': + op = REOP_ASSERT; + /* ASSERT, , ... ASSERTTEST */ + state->progLength += 4; + break; + case '!': + op = REOP_ASSERT_NOT; + /* ASSERTNOT, , ... ASSERTNOTTEST */ + state->progLength += 4; + break; + case ':': + op = REOP_LPARENNON; + break; + } + parenIndex = state->parenCount; + } + else { + op = REOP_LPAREN; + /* LPAREN, , ... RPAREN, */ + state->progLength += 6; + parenIndex = state->parenCount++; + if (state->parenCount == 65535) { + js_ReportCompileErrorNumber(state->context, + state->tokenStream, + NULL, JSREPORT_ERROR, + JSMSG_TOO_MANY_PARENS); + goto out; + } + } + goto pushOperator; + case ')': + /* If there's not a stacked open parenthesis, throw + * a syntax error. + */ + for (i = operatorSP - 1; i >= 0; i--) + if ((operatorStack[i].op == REOP_ASSERT) + || (operatorStack[i].op == REOP_ASSERT_NOT) + || (operatorStack[i].op == REOP_LPARENNON) + || (operatorStack[i].op == REOP_LPAREN)) + break; + if (i == -1) { + js_ReportCompileErrorNumber(state->context, + state->tokenStream, + NULL, JSREPORT_ERROR, + JSMSG_UNMATCHED_RIGHT_PAREN); + goto out; + } + /* fall thru... */ + case '|': + /* Expected an operand before these, so make an empty one */ + operand = NewRENode(state, REOP_EMPTY); + if (!operand) + goto out; + goto pushOperand; + default: + if (!parseTerm(state)) + goto out; + operand = state->result; +pushOperand: + if (operandSP == operandStackSize) { + operandStackSize += operandStackSize; + operandStack = + (RENode **)JS_realloc(state->context, operandStack, + sizeof(RENode *) * operandStackSize); + if (!operandStack) + goto out; + } + operandStack[operandSP++] = operand; + break; + } + } + /* At the end; process remaining operators */ +restartOperator: + if (state->cp == state->cpend) { + while (operatorSP) { + --operatorSP; + if (!processOp(state, &operatorStack[operatorSP], + operandStack, operandSP)) + goto out; + --operandSP; + } + JS_ASSERT(operandSP == 1); + state->result = operandStack[0]; + result = JS_TRUE; + goto out; + } + switch (*state->cp) { + case '|': + /* Process any stacked 'concat' operators */ + ++state->cp; + while (operatorSP + && (operatorStack[operatorSP - 1].op == REOP_CONCAT)) { + --operatorSP; + if (!processOp(state, &operatorStack[operatorSP], + operandStack, operandSP)) + goto out; + --operandSP; + } + op = REOP_ALT; + goto pushOperator; + + case ')': + /* If there's not a stacked open parenthesis,we + * accept the close as a flat. + */ + for (i = operatorSP - 1; i >= 0; i--) + if ((operatorStack[i].op == REOP_ASSERT) + || (operatorStack[i].op == REOP_ASSERT_NOT) + || (operatorStack[i].op == REOP_LPARENNON) + || (operatorStack[i].op == REOP_LPAREN)) + break; + if (i == -1) { + js_ReportCompileErrorNumber(state->context, + state->tokenStream, + NULL, JSREPORT_ERROR, + JSMSG_UNMATCHED_RIGHT_PAREN); + goto out; + } + ++state->cp; + /* process everything on the stack until the open */ + while (JS_TRUE) { + JS_ASSERT(operatorSP); + --operatorSP; + switch (operatorStack[operatorSP].op) { + case REOP_ASSERT: + case REOP_ASSERT_NOT: + case REOP_LPAREN: + operand = NewRENode(state, operatorStack[operatorSP].op); + if (!operand) + goto out; + operand->u.parenIndex + = operatorStack[operatorSP].parenIndex; + JS_ASSERT(operandSP); + operand->kid = operandStack[operandSP - 1]; + operandStack[operandSP - 1] = operand; + ++state->treeDepth; + /* fall thru... */ + case REOP_LPARENNON: + state->result = operandStack[operandSP - 1]; + if (!parseQuantifier(state)) + goto out; + operandStack[operandSP - 1] = state->result; + goto restartOperator; + default: + if (!processOp(state, &operatorStack[operatorSP], + operandStack, operandSP)) + goto out; + --operandSP; + break; + } + } + break; + default: + /* Anything else is the start of the next term */ + op = REOP_CONCAT; +pushOperator: + if (operatorSP == operatorStackSize) { + operatorStackSize += operatorStackSize; + operatorStack = + (REOpData *)JS_realloc(state->context, operatorStack, + sizeof(REOpData) * operatorStackSize); + if (!operatorStack) + goto out; + } + operatorStack[operatorSP].op = op; + operatorStack[operatorSP].errPos = state->cp; + operatorStack[operatorSP++].parenIndex = parenIndex; + break; + } + } +out: + if (operatorStack) + JS_free(state->context, operatorStack); + if (operandStack) + JS_free(state->context, operandStack); + return result; +} + +/* + * Extract and return a decimal value at state->cp, the + * initial character 'c' has already been read. + */ +static intN +getDecimalValue(jschar c, CompilerState *state) +{ + intN value = JS7_UNDEC(c); + while (state->cp < state->cpend) { + c = *state->cp; + if (!JS7_ISDEC(c)) + break; + value = (10 * value) + JS7_UNDEC(c); + ++state->cp; + } + return value; +} + +/* + * Calculate the total size of the bitmap required for a class expression. + */ +static JSBool +calculateBitmapSize(CompilerState *state, RENode *target, const jschar *src, + const jschar *end) +{ + jschar rangeStart, c; + uintN n, digit, nDigits, i; + uintN max = 0; + JSBool inRange = JS_FALSE; + + target->u.ucclass.bmsize = 0; + target->u.ucclass.sense = JS_TRUE; + + if (src == end) + return JS_TRUE; + + if (*src == '^') { + ++src; + target->u.ucclass.sense = JS_FALSE; + } + + while (src != end) { + uintN localMax = 0; + switch (*src) { + case '\\': + ++src; + c = *src++; + switch (c) { + case 'b': + localMax = 0x8; + break; + case 'f': + localMax = 0xC; + break; + case 'n': + localMax = 0xA; + break; + case 'r': + localMax = 0xD; + break; + case 't': + localMax = 0x9; + break; + case 'v': + localMax = 0xB; + break; + case 'c': + if (((src + 1) < end) && RE_IS_LETTER(src[1])) + localMax = (jschar)(*src++ & 0x1F); + else + localMax = '\\'; + break; + case 'x': + nDigits = 2; + goto lexHex; + case 'u': + nDigits = 4; +lexHex: + n = 0; + for (i = 0; (i < nDigits) && (src < end); i++) { + c = *src++; + if (!isASCIIHexDigit(c, &digit)) { + /* + * Back off to accepting the original + *'\' as a literal. + */ + src -= (i + 1); + n = '\\'; + break; + } + n = (n << 4) | digit; + } + localMax = n; + break; + case 'd': + if (inRange) { + JS_ReportErrorNumber(state->context, + js_GetErrorMessage, NULL, + JSMSG_BAD_CLASS_RANGE); + return JS_FALSE; + } + localMax = '9'; + break; + case 'D': + case 's': + case 'S': + case 'w': + case 'W': + if (inRange) { + JS_ReportErrorNumber(state->context, + js_GetErrorMessage, NULL, + JSMSG_BAD_CLASS_RANGE); + return JS_FALSE; + } + target->u.ucclass.bmsize = 65535; + return JS_TRUE; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + /* + * This is a non-ECMA extension - decimal escapes (in this + * case, octal!) are supposed to be an error inside class + * ranges, but supported here for backwards compatibility. + * + */ + n = JS7_UNDEC(c); + c = *src; + if ('0' <= c && c <= '7') { + src++; + n = 8 * n + JS7_UNDEC(c); + c = *src; + if ('0' <= c && c <= '7') { + src++; + i = 8 * n + JS7_UNDEC(c); + if (i <= 0377) + n = i; + else + src--; + } + } + localMax = n; + break; + + default: + localMax = c; + break; + } + break; + default: + localMax = *src++; + break; + } + if (inRange) { + if (rangeStart > localMax) { + JS_ReportErrorNumber(state->context, + js_GetErrorMessage, NULL, + JSMSG_BAD_CLASS_RANGE); + return JS_FALSE; + } + inRange = JS_FALSE; + } + else { + if (src < (end - 1)) { + if (*src == '-') { + ++src; + inRange = JS_TRUE; + rangeStart = (jschar)localMax; + continue; + } + } + } + if (state->flags & JSREG_FOLD) { + c = JS_MAX(upcase((jschar)localMax), downcase((jschar)localMax)); + if (c > localMax) + localMax = c; + } + if (localMax > max) + max = localMax; + } + target->u.ucclass.bmsize = max; + return JS_TRUE; +} + +/* + * item: assertion An item is either an assertion or + * quantatom a quantified atom. + * + * assertion: '^' Assertions match beginning of string + * (or line if the class static property + * RegExp.multiline is true). + * '$' End of string (or line if the class + * static property RegExp.multiline is + * true). + * '\b' Word boundary (between \w and \W). + * '\B' Word non-boundary. + * + * quantatom: atom An unquantified atom. + * quantatom '{' n ',' m '}' + * Atom must occur between n and m times. + * quantatom '{' n ',' '}' Atom must occur at least n times. + * quantatom '{' n '}' Atom must occur exactly n times. + * quantatom '*' Zero or more times (same as {0,}). + * quantatom '+' One or more times (same as {1,}). + * quantatom '?' Zero or one time (same as {0,1}). + * + * any of which can be optionally followed by '?' for ungreedy + * + * atom: '(' regexp ')' A parenthesized regexp (what matched + * can be addressed using a backreference, + * see '\' n below). + * '.' Matches any char except '\n'. + * '[' classlist ']' A character class. + * '[' '^' classlist ']' A negated character class. + * '\f' Form Feed. + * '\n' Newline (Line Feed). + * '\r' Carriage Return. + * '\t' Horizontal Tab. + * '\v' Vertical Tab. + * '\d' A digit (same as [0-9]). + * '\D' A non-digit. + * '\w' A word character, [0-9a-z_A-Z]. + * '\W' A non-word character. + * '\s' A whitespace character, [ \b\f\n\r\t\v]. + * '\S' A non-whitespace character. + * '\' n A backreference to the nth (n decimal + * and positive) parenthesized expression. + * '\' octal An octal escape sequence (octal must be + * two or three digits long, unless it is + * 0 for the null character). + * '\x' hex A hex escape (hex must be two digits). + * '\u' unicode A unicode escape (must be four digits). + * '\c' ctrl A control character, ctrl is a letter. + * '\' literalatomchar Any character except one of the above + * that follow '\' in an atom. + * otheratomchar Any character not first among the other + * atom right-hand sides. + */ +static JSBool +parseTerm(CompilerState *state) +{ + jschar c = *state->cp++; + uintN nDigits; + uintN num, tmp, n, i; + const jschar *termStart; + JSBool foundCachedCopy; + + switch (c) { + /* assertions and atoms */ + case '^': + state->result = NewRENode(state, REOP_BOL); + if (!state->result) + return JS_FALSE; + state->progLength++; + return JS_TRUE; + case '$': + state->result = NewRENode(state, REOP_EOL); + if (!state->result) + return JS_FALSE; + state->progLength++; + return JS_TRUE; + case '\\': + if (state->cp >= state->cpend) { + /* a trailing '\' is an error */ + js_ReportCompileErrorNumber(state->context, state->tokenStream, + NULL, JSREPORT_ERROR, + JSMSG_TRAILING_SLASH); + return JS_FALSE; + } + c = *state->cp++; + switch (c) { + /* assertion escapes */ + case 'b' : + state->result = NewRENode(state, REOP_WBDRY); + if (!state->result) + return JS_FALSE; + state->progLength++; + return JS_TRUE; + case 'B': + state->result = NewRENode(state, REOP_WNONBDRY); + if (!state->result) + return JS_FALSE; + state->progLength++; + return JS_TRUE; + /* Decimal escape */ + case '0': + if (JS_HAS_STRICT_OPTION(state->context)) + c = 0; + else { + doOctal: + num = 0; + while (state->cp < state->cpend) { + if ('0' <= (c = *state->cp) && c <= '7') { + state->cp++; + tmp = 8 * num + (uintN)JS7_UNDEC(c); + if (tmp > 0377) + break; + num = tmp; + } + else + break; + } + c = (jschar)(num); + } + doFlat: + state->result = NewRENode(state, REOP_FLAT); + if (!state->result) + return JS_FALSE; + state->result->u.flat.chr = c; + state->result->u.flat.length = 1; + state->progLength += 3; + break; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + termStart = state->cp - 1; + num = (uintN)getDecimalValue(c, state); + if (num > 9 && + num > state->parenCount && + !JS_HAS_STRICT_OPTION(state->context)) { + state->cp = termStart; + goto doOctal; + } + state->result = NewRENode(state, REOP_BACKREF); + if (!state->result) + return JS_FALSE; + state->result->u.parenIndex = num - 1; + state->progLength += 3; + break; + /* Control escape */ + case 'f': + c = 0xC; + goto doFlat; + case 'n': + c = 0xA; + goto doFlat; + case 'r': + c = 0xD; + goto doFlat; + case 't': + c = 0x9; + goto doFlat; + case 'v': + c = 0xB; + goto doFlat; + /* Control letter */ + case 'c': + if (((state->cp + 1) < state->cpend) && + RE_IS_LETTER(state->cp[1])) + c = (jschar)(*state->cp++ & 0x1F); + else { + /* back off to accepting the original '\' as a literal */ + --state->cp; + c = '\\'; + } + goto doFlat; + /* HexEscapeSequence */ + case 'x': + nDigits = 2; + goto lexHex; + /* UnicodeEscapeSequence */ + case 'u': + nDigits = 4; +lexHex: + n = 0; + for (i = 0; (i < nDigits) + && (state->cp < state->cpend); i++) { + uintN digit; + c = *state->cp++; + if (!isASCIIHexDigit(c, &digit)) { + /* + * back off to accepting the original + * 'u' or 'x' as a literal + */ + state->cp -= (i + 2); + n = *state->cp++; + break; + } + n = (n << 4) | digit; + } + c = (jschar)(n); + goto doFlat; + /* Character class escapes */ + case 'd': + state->result = NewRENode(state, REOP_DIGIT); +doSimple: + if (!state->result) + return JS_FALSE; + state->progLength++; + break; + case 'D': + state->result = NewRENode(state, REOP_NONDIGIT); + goto doSimple; + case 's': + state->result = NewRENode(state, REOP_SPACE); + goto doSimple; + case 'S': + state->result = NewRENode(state, REOP_NONSPACE); + goto doSimple; + case 'w': + state->result = NewRENode(state, REOP_ALNUM); + goto doSimple; + case 'W': + state->result = NewRENode(state, REOP_NONALNUM); + goto doSimple; + /* IdentityEscape */ + default: + state->result = NewRENode(state, REOP_FLAT); + if (!state->result) + return JS_FALSE; + state->result->u.flat.chr = c; + state->result->u.flat.length = 1; + state->result->kid = (void *)(state->cp - 1); + state->progLength += 3; + break; + } + break; + case '[': + state->result = NewRENode(state, REOP_CLASS); + if (!state->result) + return JS_FALSE; + termStart = state->cp; + state->result->u.ucclass.startIndex = termStart - state->cpbegin; + while (JS_TRUE) { + if (state->cp == state->cpend) { + js_ReportCompileErrorNumber(state->context, state->tokenStream, + NULL, JSREPORT_ERROR, + JSMSG_UNTERM_CLASS, termStart); + return JS_FALSE; + } + if (*state->cp == '\\') + state->cp++; + else { + if (*state->cp == ']') { + state->result->u.ucclass.kidlen = state->cp - termStart; + break; + } + } + state->cp++; + } + foundCachedCopy = JS_FALSE; + for (i = 0; i < CLASS_CACHE_SIZE; i++) { + if (state->classCache[i].start) { + if (state->classCache[i].length == state->result->u.ucclass.kidlen) { + foundCachedCopy = JS_TRUE; + for (n = 0; n < state->classCache[i].length; n++) { + if (state->classCache[i].start[n] != termStart[n]) { + foundCachedCopy = JS_FALSE; + break; + } + } + if (foundCachedCopy) { + state->result->u.ucclass.index = state->classCache[i].index; + break; + } + } + } + else { + state->classCache[i].start = termStart; + state->classCache[i].length = state->result->u.ucclass.kidlen; + state->classCache[i].index = state->classCount; + break; + } + } + if (!foundCachedCopy) + state->result->u.ucclass.index = state->classCount++; + /* + * Call calculateBitmapSize now as we want any errors it finds + * to be reported during the parse phase, not at execution. + */ + if (!calculateBitmapSize(state, state->result, termStart, state->cp++)) + return JS_FALSE; + state->progLength += 3; /* CLASS, */ + break; + + case '.': + state->result = NewRENode(state, REOP_DOT); + goto doSimple; + case '*': + case '+': + case '?': + js_ReportCompileErrorNumber(state->context, state->tokenStream, + NULL, JSREPORT_ERROR, + JSMSG_BAD_QUANTIFIER, state->cp - 1); + return JS_FALSE; +#if 0 + case '{': /* balance '}' */ + /* Treat left-curly in a non-quantifier context as an error only + * if it's followed immediately by a decimal digit. + * This is an Perl extension. + */ + if ((state->cp != state->cpend) && JS7_ISDEC(*state->cp)) { + js_ReportCompileErrorNumber(state->context, state->tokenStream, + NULL, JSREPORT_ERROR, + JSMSG_BAD_QUANTIFIER, state->cp - 1); + return JS_FALSE; + } + /* fall thru... */ +#endif + default: + state->result = NewRENode(state, REOP_FLAT); + if (!state->result) + return JS_FALSE; + state->result->u.flat.chr = c; + state->result->u.flat.length = 1; + state->result->kid = (void *)(state->cp - 1); + state->progLength += 3; + break; + } + return parseQuantifier(state); +} + +static JSBool +parseQuantifier(CompilerState *state) +{ + RENode *term; + term = state->result; + if (state->cp < state->cpend) { + switch (*state->cp) { + case '+': + state->result = NewRENode(state, REOP_QUANT); + if (!state->result) + return JS_FALSE; + state->result->u.range.min = 1; + state->result->u.range.max = -1; + /* , ... */ + state->progLength += 4; + goto quantifier; + case '*': + state->result = NewRENode(state, REOP_QUANT); + if (!state->result) + return JS_FALSE; + state->result->u.range.min = 0; + state->result->u.range.max = -1; + /* , ... */ + state->progLength += 4; + goto quantifier; + case '?': + state->result = NewRENode(state, REOP_QUANT); + if (!state->result) + return JS_FALSE; + state->result->u.range.min = 0; + state->result->u.range.max = 1; + /* , ... */ + state->progLength += 4; + goto quantifier; + case '{': /* balance '}' */ + { + intN err; + intN min = 0; + intN max = -1; + jschar c; + const jschar *errp = state->cp++; + + c = *state->cp; + if (JS7_ISDEC(c)) { + ++state->cp; + min = getDecimalValue(c, state); + c = *state->cp; + + if ((min + 1) >> 16) { + err = JSMSG_MIN_TOO_BIG; + goto quantError; + } + if (c == ',') { + c = *++state->cp; + if (JS7_ISDEC(c)) { + ++state->cp; + max = getDecimalValue(c, state); + c = *state->cp; + if ((max + 1) >> 16) { + err = JSMSG_MAX_TOO_BIG; + goto quantError; + } + if (min > max) { + err = JSMSG_OUT_OF_ORDER; + goto quantError; + } + } + } + else { + max = min; + } + if (c == '}') { + state->result = NewRENode(state, REOP_QUANT); + if (!state->result) + return JS_FALSE; + state->result->u.range.min = min; + state->result->u.range.max = max; + /* QUANT, , , ... */ + state->progLength += 8; + goto quantifier; + } + } + state->cp = errp; + return JS_TRUE; +quantError: + js_ReportCompileErrorNumber(state->context, + state->tokenStream, + NULL, JSREPORT_ERROR, + err, errp); + return JS_FALSE; + } + } + } + return JS_TRUE; + +quantifier: + ++state->treeDepth; + ++state->cp; + state->result->kid = term; + if ((state->cp < state->cpend) && (*state->cp == '?')) { + ++state->cp; + state->result->u.range.greedy = JS_FALSE; + } + else + state->result->u.range.greedy = JS_TRUE; + return JS_TRUE; +} + +#define CHECK_OFFSET(diff) (JS_ASSERT(((diff) >= -32768) && ((diff) <= 32767))) +#define SET_OFFSET(pc,off) ((pc)[0] = JUMP_OFFSET_HI(off), \ + (pc)[1] = JUMP_OFFSET_LO(off)) +#define GET_OFFSET(pc) ((int16)(((pc)[0] << 8) | (pc)[1])) +#define OFFSET_LEN (2) +#define GET_ARG(pc) GET_OFFSET(pc) +#define SET_ARG(pc,arg) SET_OFFSET(pc,arg) +#define ARG_LEN OFFSET_LEN + +/* + * Recursively generate bytecode for the tree rooted at t. Iteratively. + */ + +typedef struct { + RENode *nextAlt; + jsbytecode *nextAltFixup, *nextTermFixup, *endTermFixup; + RENode *continueNode; + REOp continueOp; +} EmitStateStackEntry; + +static jsbytecode * +emitREBytecode(CompilerState *state, JSRegExp *re, intN treeDepth, + jsbytecode *pc, RENode *t) +{ + ptrdiff_t diff; + RECharSet *charSet; + EmitStateStackEntry *emitStateSP, *emitStateStack = NULL; + REOp op; + + if (treeDepth) { + emitStateStack = + (EmitStateStackEntry *)JS_malloc(state->context, + sizeof(EmitStateStackEntry) + * treeDepth); + if (!emitStateStack) + return NULL; + } + emitStateSP = emitStateStack; + op = t->op; + + while (JS_TRUE) { + *pc++ = op; + switch (op) { + case REOP_EMPTY: + --pc; + break; + + case REOP_ALTPREREQ2: + case REOP_ALTPREREQ: + JS_ASSERT(emitStateSP); + emitStateSP->endTermFixup = pc; + pc += OFFSET_LEN; + SET_ARG(pc, t->u.altprereq.ch1); + pc += ARG_LEN; + SET_ARG(pc, t->u.altprereq.ch2); + pc += ARG_LEN; + + emitStateSP->nextAltFixup = pc; /* address of next alternate */ + pc += OFFSET_LEN; + + emitStateSP->continueNode = t; + emitStateSP->continueOp = REOP_JUMP; + ++emitStateSP; + JS_ASSERT((emitStateSP - emitStateStack) <= treeDepth); + t = (RENode *)(t->kid); + op = t->op; + continue; + + case REOP_JUMP: + emitStateSP->nextTermFixup = pc; /* address of following term */ + pc += OFFSET_LEN; + diff = pc - emitStateSP->nextAltFixup; + CHECK_OFFSET(diff); + SET_OFFSET(emitStateSP->nextAltFixup, diff); + emitStateSP->continueOp = REOP_ENDALT; + ++emitStateSP; + JS_ASSERT((emitStateSP - emitStateStack) <= treeDepth); + t = (RENode *)(t->u.kid2); + op = t->op; + continue; + + case REOP_ENDALT: + diff = pc - emitStateSP->nextTermFixup; + CHECK_OFFSET(diff); + SET_OFFSET(emitStateSP->nextTermFixup, diff); + if (t->op != REOP_ALT) { + diff = pc - emitStateSP->endTermFixup; + CHECK_OFFSET(diff); + SET_OFFSET(emitStateSP->endTermFixup, diff); + } + break; + + case REOP_ALT: + JS_ASSERT(emitStateSP); + emitStateSP->nextAltFixup = pc; /* address of pointer to next alternate */ + pc += OFFSET_LEN; + emitStateSP->continueNode = t; + emitStateSP->continueOp = REOP_JUMP; + ++emitStateSP; + JS_ASSERT((emitStateSP - emitStateStack) <= treeDepth); + t = (RENode *)(t->kid); + op = t->op; + continue; + + case REOP_FLAT: + /* + * Consecutize FLAT's if possible. + */ + if (t->kid) { + while (t->next && (t->next->op == REOP_FLAT) + && (((jschar*)(t->kid) + t->u.flat.length) + == (jschar*)(t->next->kid))) { + t->u.flat.length += t->next->u.flat.length; + t->next = t->next->next; + } + } + if (t->kid && (t->u.flat.length > 1)) { + if (state->flags & JSREG_FOLD) + pc[-1] = REOP_FLATi; + else + pc[-1] = REOP_FLAT; + SET_ARG(pc, (jschar *)(t->kid) - state->cpbegin); + pc += ARG_LEN; + SET_ARG(pc, t->u.flat.length); + pc += ARG_LEN; + } + else { + if (t->u.flat.chr < 256) { + if (state->flags & JSREG_FOLD) + pc[-1] = REOP_FLAT1i; + else + pc[-1] = REOP_FLAT1; + *pc++ = (jsbytecode)(t->u.flat.chr); + } + else { + if (state->flags & JSREG_FOLD) + pc[-1] = REOP_UCFLAT1i; + else + pc[-1] = REOP_UCFLAT1; + SET_ARG(pc, t->u.flat.chr); + pc += ARG_LEN; + } + } + break; + + case REOP_LPAREN: + JS_ASSERT(emitStateSP); + SET_ARG(pc, t->u.parenIndex); + pc += ARG_LEN; + emitStateSP->continueNode = t; + emitStateSP->continueOp = REOP_RPAREN; + ++emitStateSP; + JS_ASSERT((emitStateSP - emitStateStack) <= treeDepth); + t = (RENode *)(t->kid); + op = t->op; + continue; + case REOP_RPAREN: + SET_ARG(pc, t->u.parenIndex); + pc += ARG_LEN; + break; + + case REOP_BACKREF: + SET_ARG(pc, t->u.parenIndex); + pc += ARG_LEN; + break; + case REOP_ASSERT: + JS_ASSERT(emitStateSP); + emitStateSP->nextTermFixup = pc; + pc += OFFSET_LEN; + emitStateSP->continueNode = t; + emitStateSP->continueOp = REOP_ASSERTTEST; + ++emitStateSP; + JS_ASSERT((emitStateSP - emitStateStack) <= treeDepth); + t = (RENode *)(t->kid); + op = t->op; + continue; + case REOP_ASSERTTEST: + case REOP_ASSERTNOTTEST: + diff = pc - emitStateSP->nextTermFixup; + CHECK_OFFSET(diff); + SET_OFFSET(emitStateSP->nextTermFixup, diff); + break; + case REOP_ASSERT_NOT: + JS_ASSERT(emitStateSP); + emitStateSP->nextTermFixup = pc; + pc += OFFSET_LEN; + emitStateSP->continueNode = t; + emitStateSP->continueOp = REOP_ASSERTNOTTEST; + ++emitStateSP; + JS_ASSERT((emitStateSP - emitStateStack) <= treeDepth); + t = (RENode *)(t->kid); + op = t->op; + continue; + case REOP_QUANT: + JS_ASSERT(emitStateSP); + if ((t->u.range.min == 0) && (t->u.range.max == (uint16)(-1))) + pc[-1] = (t->u.range.greedy) ? REOP_STAR : REOP_MINIMALSTAR; + else + if ((t->u.range.min == 0) && (t->u.range.max == 1)) + pc[-1] = (t->u.range.greedy) ? REOP_OPT : REOP_MINIMALOPT; + else + if ((t->u.range.min == 1) && (t->u.range.max == (uint16)(-1))) + pc[-1] = (t->u.range.greedy) ? REOP_PLUS : REOP_MINIMALPLUS; + else { + if (!t->u.range.greedy) pc[-1] = REOP_MINIMALQUANT; + SET_ARG(pc, t->u.range.min); + pc += ARG_LEN; + SET_ARG(pc, t->u.range.max); + pc += ARG_LEN; + } + emitStateSP->nextTermFixup = pc; + pc += OFFSET_LEN; + emitStateSP->continueNode = t; + emitStateSP->continueOp = REOP_ENDCHILD; + ++emitStateSP; + JS_ASSERT((emitStateSP - emitStateStack) <= treeDepth); + t = (RENode *)(t->kid); + op = t->op; + continue; + case REOP_ENDCHILD: + diff = pc - emitStateSP->nextTermFixup; + CHECK_OFFSET(diff); + SET_OFFSET(emitStateSP->nextTermFixup, diff); + break; + case REOP_CLASS: + if (!t->u.ucclass.sense) + pc[-1] = REOP_NCLASS; + SET_ARG(pc, t->u.ucclass.index); + pc += ARG_LEN; + charSet = &re->classList[t->u.ucclass.index]; + charSet->converted = JS_FALSE; + charSet->length = t->u.ucclass.bmsize; + charSet->u.src.startIndex = t->u.ucclass.startIndex; + charSet->u.src.length = t->u.ucclass.kidlen; + charSet->sense = t->u.ucclass.sense; + break; + default: + break; + } + t = t->next; + if (t == NULL) { + if (emitStateSP == emitStateStack) + break; + --emitStateSP; + t = emitStateSP->continueNode; + op = emitStateSP->continueOp; + } + else + op = t->op; + } + if (emitStateStack) + JS_free(state->context, emitStateStack); + return pc; +} + + +JSRegExp * +js_NewRegExp(JSContext *cx, JSTokenStream *ts, + JSString *str, uintN flags, JSBool flat) +{ + JSRegExp *re; + void *mark; + CompilerState state; + size_t resize; + jsbytecode *endPC; + uint32 i; + size_t len; + + re = NULL; + mark = JS_ARENA_MARK(&cx->tempPool); + + state.context = cx; + state.tokenStream = ts; + state.cpbegin = state.cp = JSSTRING_CHARS(str); + state.cpend = state.cp + JSSTRING_LENGTH(str); + state.flags = flags; + state.parenCount = 0; + state.classCount = 0; + state.progLength = 0; + state.treeDepth = 0; + for (i = 0; i < CLASS_CACHE_SIZE; i++) + state.classCache[i].start = NULL; + + len = JSSTRING_LENGTH(str); + + if (len != 0 && flat) { + state.result = NewRENode(&state, REOP_FLAT); + state.result->u.flat.chr = *state.cpbegin; + state.result->u.flat.length = JSSTRING_LENGTH(str); + state.result->kid = (void *)(state.cpbegin); + state.progLength += 5; + } + else { + if (!parseRegExp(&state)) + goto out; + } + resize = sizeof *re + state.progLength + 1; + re = (JSRegExp *) JS_malloc(cx, JS_ROUNDUP(resize, sizeof(jsword))); + if (!re) + goto out; + + re->classCount = state.classCount; + if (state.classCount) { + re->classList = (RECharSet *)JS_malloc(cx, sizeof(RECharSet) + * state.classCount); + if (!re->classList) + goto out; + } + else + re->classList = NULL; + endPC = emitREBytecode(&state, re, state.treeDepth, re->program, state.result); + if (!endPC) { + re = NULL; + goto out; + } + *endPC++ = REOP_END; + JS_ASSERT(endPC <= (re->program + (state.progLength + 1))); + + re->nrefs = 1; + re->parenCount = state.parenCount; + re->flags = flags; + re->source = str; + +out: + JS_ARENA_RELEASE(&cx->tempPool, mark); + return re; +} + +JSRegExp * +js_NewRegExpOpt(JSContext *cx, JSTokenStream *ts, + JSString *str, JSString *opt, JSBool flat) +{ + uintN flags; + jschar *s; + size_t i, n; + char charBuf[2]; + + flags = 0; + if (opt) { + s = JSSTRING_CHARS(opt); + for (i = 0, n = JSSTRING_LENGTH(opt); i < n; i++) { + switch (s[i]) { + case 'g': + flags |= JSREG_GLOB; + break; + case 'i': + flags |= JSREG_FOLD; + break; + case 'm': + flags |= JSREG_MULTILINE; + break; + default: + charBuf[0] = (char)s[i]; + charBuf[1] = '\0'; + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_BAD_FLAG, charBuf); + return NULL; + } + } + } + return js_NewRegExp(cx, ts, str, flags, flat); +} + + +#define HOLD_REGEXP(cx, re) JS_ATOMIC_INCREMENT(&(re)->nrefs) +#define DROP_REGEXP(cx, re) js_DestroyRegExp(cx, re) + +/* + * Save the current state of the match - the position in the input + * text as well as the position in the bytecode. The state of any + * parent expressions is also saved (preceding state). + * Contents of parenCount parentheses from parenIndex are also saved. + */ +static REBackTrackData * +pushBackTrackState(REGlobalData *gData, REOp op, + jsbytecode *target, REMatchState *x, const jschar *cp, + intN parenIndex, intN parenCount) +{ + intN i; + REBackTrackData *result + = (REBackTrackData *)((char *)(gData->backTrackSP) + gData->cursz); + + size_t sz = sizeof(REBackTrackData) + + gData->stateStackTop * sizeof(REProgState) + + parenCount * sizeof(RECapture); + + + if (((char *)result + sz) + > (char *)gData->backTrackStack + gData->maxBackTrack) { + ptrdiff_t offset = (char *)result - (char *)gData->backTrackStack; + gData->backTrackStack + = (REBackTrackData *)JS_ArenaGrow(&gData->pool, + gData->backTrackStack, + gData->maxBackTrack, + gData->maxBackTrack); + gData->maxBackTrack <<= 1; + if (!gData->backTrackStack) + return NULL; + result = (REBackTrackData *)((char *)gData->backTrackStack + offset); + } + gData->backTrackSP = result; + result->sz = gData->cursz; + gData->cursz = sz; + + result->backtrack_op = op; + result->backtrack_pc = target; + result->cp = cp; + result->parenCount = parenCount; + + result->precedingStateTop = gData->stateStackTop; + JS_ASSERT(gData->stateStackTop); + memcpy(result + 1, gData->stateStack, + sizeof(REProgState) * result->precedingStateTop); + + if (parenCount != -1) { + result->parenIndex = parenIndex; + memcpy((char *)(result + 1) + + sizeof(REProgState) * result->precedingStateTop, + &x->parens[parenIndex], + sizeof(RECapture) * parenCount); + for (i = 0; i < parenCount; i++) + x->parens[parenIndex + i].index = -1; + } + + return result; +} + + +/* + * Consecutive literal characters. + */ +#if 0 +static REMatchState * +flatNMatcher(REGlobalData *gData, REMatchState *x, jschar *matchChars, + intN length) +{ + intN i; + if ((x->cp + length) > gData->cpend) + return NULL; + for (i = 0; i < length; i++) { + if (matchChars[i] != x->cp[i]) + return NULL; + } + x->cp += length; + return x; +} +#endif + +static REMatchState * +flatNIMatcher(REGlobalData *gData, REMatchState *x, jschar *matchChars, + intN length) +{ + intN i; + if ((x->cp + length) > gData->cpend) + return NULL; + for (i = 0; i < length; i++) { + if (upcase(matchChars[i]) != upcase(x->cp[i])) + return NULL; + } + x->cp += length; + return x; +} + +/* + * 1. Evaluate DecimalEscape to obtain an EscapeValue E. + * 2. If E is not a character then go to step 6. + * 3. Let ch be E's character. + * 4. Let A be a one-element RECharSet containing the character ch. + * 5. Call CharacterSetMatcher(A, false) and return its Matcher result. + * 6. E must be an integer. Let n be that integer. + * 7. If n=0 or n>NCapturingParens then throw a SyntaxError exception. + * 8. Return an internal Matcher closure that takes two arguments, a State x + * and a Continuation c, and performs the following: + * 1. Let cap be x's captures internal array. + * 2. Let s be cap[n]. + * 3. If s is undefined, then call c(x) and return its result. + * 4. Let e be x's endIndex. + * 5. Let len be s's length. + * 6. Let f be e+len. + * 7. If f>InputLength, return failure. + * 8. If there exists an integer i between 0 (inclusive) and len (exclusive) + * such that Canonicalize(s[i]) is not the same character as + * Canonicalize(Input [e+i]), then return failure. + * 9. Let y be the State (f, cap). + * 10. Call c(y) and return its result. + */ +static REMatchState * +backrefMatcher(REGlobalData *gData, REMatchState *x, uintN parenIndex) +{ + uintN len; + uintN i; + const jschar *parenContent; + RECapture *s = &x->parens[parenIndex]; + if (s->index == -1) + return x; + + len = s->length; + if ((x->cp + len) > gData->cpend) + return NULL; + + parenContent = &gData->cpbegin[s->index]; + if (gData->regexp->flags & JSREG_FOLD) { + for (i = 0; i < len; i++) { + if (upcase(parenContent[i]) != upcase(x->cp[i])) + return NULL; + } + } + else { + for (i = 0; i < len; i++) { + if (parenContent[i] != x->cp[i]) + return NULL; + } + } + x->cp += len; + return x; +} + + +/* Add a single character to the RECharSet */ +static void +addCharacterToCharSet(RECharSet *cs, jschar c) +{ + uintN byteIndex = (uintN)(c / 8); + JS_ASSERT(c <= cs->length); + cs->u.bits[byteIndex] |= 1 << (c & 0x7); +} + + +/* Add a character range, c1 to c2 (inclusive) to the RECharSet */ +static void +addCharacterRangeToCharSet(RECharSet *cs, jschar c1, jschar c2) +{ + uintN i; + + uintN byteIndex1 = (uintN)(c1 / 8); + uintN byteIndex2 = (uintN)(c2 / 8); + + JS_ASSERT((c2 <= cs->length) && (c1 <= c2)); + + c1 &= 0x7; + c2 &= 0x7; + + if (byteIndex1 == byteIndex2) + cs->u.bits[byteIndex1] |= ((uint8)(0xFF) >> (7 - (c2 - c1))) << c1; + else { + cs->u.bits[byteIndex1] |= 0xFF << c1; + for (i = byteIndex1 + 1; i < byteIndex2; i++) + cs->u.bits[i] = 0xFF; + cs->u.bits[byteIndex2] |= (uint8)(0xFF) >> (7 - c2); + } +} + +/* Compile the source of the class into a RECharSet */ +static JSBool +processCharSet(REGlobalData *gData, RECharSet *charSet) +{ + const jschar *src = JSSTRING_CHARS(gData->regexp->source) + + charSet->u.src.startIndex; + const jschar *end = src + charSet->u.src.length; + + jschar rangeStart, thisCh; + uintN byteLength; + jschar c; + uintN n; + intN nDigits; + intN i; + JSBool inRange = JS_FALSE; + + JS_ASSERT(!charSet->converted); + charSet->converted = JS_TRUE; + + byteLength = (charSet->length / 8) + 1; + charSet->u.bits = (uint8 *)JS_malloc(gData->cx, byteLength); + if (!charSet->u.bits) + return JS_FALSE; + memset(charSet->u.bits, 0, byteLength); + + if (src == end) + return JS_TRUE; + + if (*src == '^') { + JS_ASSERT(charSet->sense == JS_FALSE); + ++src; + } + else + JS_ASSERT(charSet->sense == JS_TRUE); + + + while (src != end) { + switch (*src) { + case '\\': + ++src; + c = *src++; + switch (c) { + case 'b': + thisCh = 0x8; + break; + case 'f': + thisCh = 0xC; + break; + case 'n': + thisCh = 0xA; + break; + case 'r': + thisCh = 0xD; + break; + case 't': + thisCh = 0x9; + break; + case 'v': + thisCh = 0xB; + break; + case 'c': + if (((src + 1) < end) && JS_ISWORD(src[1])) + thisCh = (jschar)(*src++ & 0x1F); + else { + --src; + thisCh = '\\'; + } + break; + case 'x': + nDigits = 2; + goto lexHex; + case 'u': + nDigits = 4; +lexHex: + n = 0; + for (i = 0; (i < nDigits) && (src < end); i++) { + uintN digit; + c = *src++; + if (!isASCIIHexDigit(c, &digit)) { + /* + * Back off to accepting the original '\' + * as a literal + */ + src -= (i + 1); + n = '\\'; + break; + } + n = (n << 4) | digit; + } + thisCh = (jschar)(n); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + /* + * This is a non-ECMA extension - decimal escapes (in this + * case, octal!) are supposed to be an error inside class + * ranges, but supported here for backwards compatibility. + * + */ + n = JS7_UNDEC(c); + c = *src; + if ('0' <= c && c <= '7') { + src++; + n = 8 * n + JS7_UNDEC(c); + c = *src; + if ('0' <= c && c <= '7') { + src++; + i = 8 * n + JS7_UNDEC(c); + if (i <= 0377) + n = i; + else + src--; + } + } + thisCh = (jschar)(n); + break; + + case 'd': + addCharacterRangeToCharSet(charSet, '0', '9'); + continue; /* don't need range processing */ + case 'D': + addCharacterRangeToCharSet(charSet, 0, '0' - 1); + addCharacterRangeToCharSet(charSet, (jschar)('9' + 1), + (jschar)(charSet->length)); + continue; + case 's': + for (i = (intN)(charSet->length); i >= 0; i--) + if (JS_ISSPACE(i)) + addCharacterToCharSet(charSet, (jschar)(i)); + continue; + case 'S': + for (i = (intN)(charSet->length); i >= 0; i--) + if (!JS_ISSPACE(i)) + addCharacterToCharSet(charSet, (jschar)(i)); + continue; + case 'w': + for (i = (intN)(charSet->length); i >= 0; i--) + if (JS_ISWORD(i)) + addCharacterToCharSet(charSet, (jschar)(i)); + continue; + case 'W': + for (i = (intN)(charSet->length); i >= 0; i--) + if (!JS_ISWORD(i)) + addCharacterToCharSet(charSet, (jschar)(i)); + continue; + default: + thisCh = c; + break; + + } + break; + + default: + thisCh = *src++; + break; + + } + if (inRange) { + if (gData->regexp->flags & JSREG_FOLD) { + addCharacterRangeToCharSet(charSet, upcase(rangeStart), + upcase(thisCh)); + addCharacterRangeToCharSet(charSet, downcase(rangeStart), + downcase(thisCh)); + } else { + addCharacterRangeToCharSet(charSet, rangeStart, thisCh); + } + inRange = JS_FALSE; + } + else { + if (gData->regexp->flags & JSREG_FOLD) { + addCharacterToCharSet(charSet, upcase(thisCh)); + addCharacterToCharSet(charSet, downcase(thisCh)); + } else { + addCharacterToCharSet(charSet, thisCh); + } + if (src < (end - 1)) { + if (*src == '-') { + ++src; + inRange = JS_TRUE; + rangeStart = thisCh; + } + } + } + } + return JS_TRUE; +} + +void +js_DestroyRegExp(JSContext *cx, JSRegExp *re) +{ + uintN i; + if (JS_ATOMIC_DECREMENT(&re->nrefs) == 0) { + if (re->classList) { + for (i = 0; i < re->classCount; i++) { + if (re->classList[i].converted) + JS_free(cx, re->classList[i].u.bits); + re->classList[i].u.bits = NULL; + } + JS_free(cx, re->classList); + } + JS_free(cx, re); + } +} + +static JSBool +reallocStateStack(REGlobalData *gData) +{ + size_t sz = sizeof(REProgState) * gData->maxStateStack; + gData->maxStateStack <<= 1; + gData->stateStack + = (REProgState *)JS_ArenaGrow(&gData->pool, gData->stateStack, sz, sz); + if (!gData->stateStack) { + gData->ok = JS_FALSE; + return JS_FALSE; + } + return JS_TRUE; +} + +/* +* Apply the current op against the given input to see if +* it's going to match or fail. Return false if we don't +* get a match, true if we do and update the state of the +* input and pc if the update flag is true. +*/ +static REMatchState *simpleMatch(REGlobalData *gData, REMatchState *x, + REOp op, jsbytecode **startpc, JSBool update) +{ + REMatchState *result = NULL; + jschar matchCh; + intN parenIndex; + intN offset, length, index; + jsbytecode *pc = *startpc; /* pc has already been incremented past op */ + jschar *source; + const jschar *startcp = x->cp; + jschar ch; + RECharSet *charSet; + + + switch (op) { + default: + JS_ASSERT(JS_FALSE); + case REOP_BOL: + if (x->cp != gData->cpbegin) { + if (gData->cx->regExpStatics.multiline || + (gData->regexp->flags & JSREG_MULTILINE)) { + if (!RE_IS_LINE_TERM(x->cp[-1])) + break; + } + else + break; + } + result = x; + break; + case REOP_EOL: + if (x->cp != gData->cpend) { + if (gData->cx->regExpStatics.multiline || + (gData->regexp->flags & JSREG_MULTILINE)) { + if (!RE_IS_LINE_TERM(*x->cp)) + break; + } + else + break; + } + result = x; + break; + case REOP_WBDRY: + if ((x->cp == gData->cpbegin || !JS_ISWORD(x->cp[-1])) + ^ !((x->cp != gData->cpend) && JS_ISWORD(*x->cp))) + result = x; + break; + case REOP_WNONBDRY: + if ((x->cp == gData->cpbegin || !JS_ISWORD(x->cp[-1])) + ^ ((x->cp != gData->cpend) && JS_ISWORD(*x->cp))) + result = x; + break; + case REOP_DOT: + if (x->cp != gData->cpend && !RE_IS_LINE_TERM(*x->cp)) { + result = x; + result->cp++; + } + break; + case REOP_DIGIT: + if (x->cp != gData->cpend && JS_ISDIGIT(*x->cp)) { + result = x; + result->cp++; + } + break; + case REOP_NONDIGIT: + if (x->cp != gData->cpend && !JS_ISDIGIT(*x->cp)) { + result = x; + result->cp++; + } + break; + case REOP_ALNUM: + if (x->cp != gData->cpend && JS_ISWORD(*x->cp)) { + result = x; + result->cp++; + } + break; + case REOP_NONALNUM: + if (x->cp != gData->cpend && !JS_ISWORD(*x->cp)) { + result = x; + result->cp++; + } + break; + case REOP_SPACE: + if (x->cp != gData->cpend && JS_ISSPACE(*x->cp)) { + result = x; + result->cp++; + } + break; + case REOP_NONSPACE: + if (x->cp != gData->cpend && !JS_ISSPACE(*x->cp)) { + result = x; + result->cp++; + } + break; + case REOP_BACKREF: + parenIndex = GET_ARG(pc); + pc += ARG_LEN; + result = backrefMatcher(gData, x, parenIndex); + break; + case REOP_FLAT: + offset = GET_ARG(pc); + pc += ARG_LEN; + length = GET_ARG(pc); + pc += ARG_LEN; + source = JSSTRING_CHARS(gData->regexp->source) + offset; + if ((x->cp + length) <= gData->cpend) { + for (index = 0; index < length; index++) { + if (source[index] != x->cp[index]) + return NULL; + } + x->cp += length; + result = x; + } + break; + case REOP_FLAT1: + matchCh = *pc++; + if ((x->cp != gData->cpend) && (*x->cp == matchCh)) { + result = x; + result->cp++; + } + break; + case REOP_FLATi: + offset = GET_ARG(pc); + pc += ARG_LEN; + length = GET_ARG(pc); + pc += ARG_LEN; + source = JSSTRING_CHARS(gData->regexp->source); + result = flatNIMatcher(gData, x, source + offset, length); + break; + case REOP_FLAT1i: + matchCh = *pc++; + if ((x->cp != gData->cpend) && (upcase(*x->cp) == upcase(matchCh))) { + result = x; + result->cp++; + } + break; + case REOP_UCFLAT1: + matchCh = GET_ARG(pc); + pc += ARG_LEN; + if ((x->cp != gData->cpend) && (*x->cp == matchCh)) { + result = x; + result->cp++; + } + break; + case REOP_UCFLAT1i: + matchCh = GET_ARG(pc); + pc += ARG_LEN; + if ((x->cp != gData->cpend) && (upcase(*x->cp) == upcase(matchCh))) { + result = x; + result->cp++; + } + break; + case REOP_CLASS: + index = GET_ARG(pc); + pc += ARG_LEN; + if (x->cp != gData->cpend) { + charSet = &gData->regexp->classList[index]; + JS_ASSERT(charSet->converted); + ch = *x->cp; + index = ch / 8; + if ((charSet->length != 0) && + ( (ch <= charSet->length) + && ((charSet->u.bits[index] & (1 << (ch & 0x7))) != 0) )) { + result = x; + result->cp++; + } + } + break; + case REOP_NCLASS: + index = GET_ARG(pc); + pc += ARG_LEN; + if (x->cp != gData->cpend) { + charSet = &gData->regexp->classList[index]; + JS_ASSERT(charSet->converted); + ch = *x->cp; + index = ch / 8; + if ((charSet->length == 0) || + ( (ch > charSet->length) + || ((charSet->u.bits[index] & (1 << (ch & 0x7))) == 0) )) { + result = x; + result->cp++; + } + } + break; + } + if (result != NULL) { + if (update) + *startpc = pc; + else + x->cp = startcp; + return result; + } + x->cp = startcp; + return NULL; +} + +static REMatchState * +executeREBytecode(REGlobalData *gData, REMatchState *x) +{ + REMatchState *result; + REBackTrackData *backTrackData; + intN offset; + jsbytecode *nextpc; + REOp nextop; + RECapture *cap; + REProgState *curState; + const jschar *startcp; + uintN parenIndex, k; + uintN parenSoFar = 0; + + jschar matchCh1, matchCh2; + RECharSet *charSet; + + JSBool anchor; + jsbytecode *pc = gData->regexp->program; + REOp op = (REOp)(*pc++); + + /* + * If the first node is a simple match, step the index into + * the string until that match is made, or fail if it can't be + * found at all. + */ + if (REOP_IS_SIMPLE(op)) { + anchor = JS_FALSE; + while (x->cp <= gData->cpend) { + nextpc = pc; /* reset back to start each time */ + result = simpleMatch(gData, x, op, &nextpc, JS_TRUE); + if (result) { + anchor = JS_TRUE; + x = result; + pc = nextpc; /* accept skip to next opcode */ + op = (REOp)(*pc++); + break; + } + else { + gData->skipped++; + x->cp++; + } + } + if (!anchor) + return NULL; + } + + while (JS_TRUE) { + if (REOP_IS_SIMPLE(op)) + result = simpleMatch(gData, x, op, &pc, JS_TRUE); + else { + curState = &gData->stateStack[gData->stateStackTop]; + switch (op) { + case REOP_EMPTY: + result = x; + break; + + case REOP_ALTPREREQ2: + nextpc = pc + GET_OFFSET(pc); /* start of next op */ + pc += ARG_LEN; + matchCh2 = GET_ARG(pc); + pc += ARG_LEN; + k = GET_ARG(pc); + pc += ARG_LEN; + + if (x->cp != gData->cpend) { + if (*x->cp == matchCh2) + goto doAlt; + + charSet = &gData->regexp->classList[k]; + if (!charSet->converted) + if (!processCharSet(gData, charSet)) + return NULL; + matchCh1 = *x->cp; + k = matchCh1 / 8; + if ((charSet->length == 0 || + matchCh1 > charSet->length || + (charSet->u.bits[k] & (1 << (matchCh1 & 0x7))) == 0) + ^ charSet->sense) { + goto doAlt; + } + } + result = NULL; + break; + + case REOP_ALTPREREQ: + nextpc = pc + GET_OFFSET(pc); /* start of next op */ + pc += ARG_LEN; + matchCh1 = GET_ARG(pc); + pc += ARG_LEN; + matchCh2 = GET_ARG(pc); + pc += ARG_LEN; + if ((x->cp == gData->cpend) + || ((*x->cp != matchCh1) && (*x->cp != matchCh2))) { + result = NULL; + break; + } + /* else false thru... */ + + case REOP_ALT: +doAlt: + nextpc = pc + GET_OFFSET(pc); /* start of next alternate */ + pc += ARG_LEN; /* start of this alternate */ + curState->parenSoFar = parenSoFar; + ++gData->stateStackTop; + if (gData->stateStackTop == gData->maxStateStack) + if (!reallocStateStack(gData)) + return NULL; + op = (REOp)(*pc++); + startcp = x->cp; + if (REOP_IS_SIMPLE(op)) { + if (!simpleMatch(gData, x, op, &pc, JS_TRUE)) { + op = (REOp)(*nextpc++); + pc = nextpc; + continue; + } + else { /* accept the match and move on */ + result = x; + op = (REOp)(*pc++); + } + } + nextop = (REOp)(*nextpc++); + if (!pushBackTrackState(gData, nextop, nextpc, x, startcp, 0, 0)) + return NULL; + continue; + + /* + * Occurs at (succesful) end of REOP_ALT, + */ + case REOP_JUMP: + --gData->stateStackTop; + offset = GET_OFFSET(pc); + pc += offset; + op = (REOp)(*pc++); + continue; + + /* + * Occurs at last (succesful) end of REOP_ALT, + */ + case REOP_ENDALT: + --gData->stateStackTop; + op = (REOp)(*pc++); + continue; + + case REOP_LPAREN: + parenIndex = GET_ARG(pc); + if ((parenIndex + 1) > parenSoFar) + parenSoFar = parenIndex + 1; + pc += ARG_LEN; + x->parens[parenIndex].index = x->cp - gData->cpbegin; + x->parens[parenIndex].length = 0; + op = (REOp)(*pc++); + continue; + case REOP_RPAREN: + parenIndex = GET_ARG(pc); + pc += ARG_LEN; + cap = &x->parens[parenIndex]; + cap->length = x->cp - (gData->cpbegin + cap->index); + op = (REOp)(*pc++); + continue; + + case REOP_ASSERT: + nextpc = pc + GET_OFFSET(pc); /* start of term after ASSERT */ + pc += ARG_LEN; /* start of ASSERT child */ + op = (REOp)(*pc++); + if (REOP_IS_SIMPLE(op) + && !simpleMatch(gData, x, op, &pc, JS_FALSE)) { + result = NULL; + break; + } + else { + curState->u.assertion.top + = (char *)gData->backTrackSP + - (char *)gData->backTrackStack; + curState->u.assertion.sz = gData->cursz; + curState->index = x->cp - gData->cpbegin; + curState->parenSoFar = parenSoFar; + ++gData->stateStackTop; + if (gData->stateStackTop == gData->maxStateStack) + if (!reallocStateStack(gData)) + return NULL; + if (!pushBackTrackState(gData, REOP_ASSERTTEST, + nextpc, x, x->cp, 0, 0)) + return NULL; + } + continue; + case REOP_ASSERT_NOT: + nextpc = pc + GET_OFFSET(pc); + pc += ARG_LEN; + op = (REOp)(*pc++); + if (REOP_IS_SIMPLE(op) + /* Note - fail to fail! */ + && simpleMatch(gData, x, op, &pc, JS_FALSE)) { + result = NULL; + break; + } + else { + curState->u.assertion.top + = (char *)gData->backTrackSP + - (char *)gData->backTrackStack; + curState->u.assertion.sz = gData->cursz; + curState->index = x->cp - gData->cpbegin; + curState->parenSoFar = parenSoFar; + ++gData->stateStackTop; + if (gData->stateStackTop == gData->maxStateStack) + if (!reallocStateStack(gData)) + return NULL; + if (!pushBackTrackState(gData, REOP_ASSERTNOTTEST, + nextpc, x, x->cp, 0, 0)) + return NULL; + } + continue; + case REOP_ASSERTTEST: + --gData->stateStackTop; + --curState; + x->cp = gData->cpbegin + curState->index; + gData->backTrackSP + = (REBackTrackData *)((char *)gData->backTrackStack + + curState->u.assertion.top); + gData->cursz = curState->u.assertion.sz; + if (result != NULL) + result = x; + break; + case REOP_ASSERTNOTTEST: + --gData->stateStackTop; + --curState; + x->cp = gData->cpbegin + curState->index; + gData->backTrackSP + = (REBackTrackData *)((char *)gData->backTrackStack + + curState->u.assertion.top); + gData->cursz = curState->u.assertion.sz; + if (result == NULL) + result = x; + else + result = NULL; + break; + + case REOP_END: + if (x != NULL) + return x; + break; + + case REOP_STAR: + curState->u.quantifier.min = 0; + curState->u.quantifier.max = -1; + goto quantcommon; + case REOP_PLUS: + curState->u.quantifier.min = 1; + curState->u.quantifier.max = -1; + goto quantcommon; + case REOP_OPT: + curState->u.quantifier.min = 0; + curState->u.quantifier.max = 1; + goto quantcommon; + case REOP_QUANT: + curState->u.quantifier.min = GET_ARG(pc); + pc += ARG_LEN; + curState->u.quantifier.max = GET_ARG(pc); + pc += ARG_LEN; +quantcommon: + if (curState->u.quantifier.max == 0) { + pc = pc + GET_OFFSET(pc); + op = (REOp)(*pc++); + result = x; + continue; + } + /* Step over */ + nextpc = pc + ARG_LEN; + op = (REOp)(*nextpc++); + startcp = x->cp; + if (REOP_IS_SIMPLE(op)) { + if (!simpleMatch(gData, x, op, &nextpc, JS_TRUE)) { + if (curState->u.quantifier.min == 0) + result = x; + else + result = NULL; + pc = pc + GET_OFFSET(pc); + break; + } + else { + op = (REOp)(*nextpc++); + result = x; + } + } + curState->index = startcp - gData->cpbegin; + curState->continue_op = REOP_REPEAT; + curState->continue_pc = pc; + curState->parenSoFar = parenSoFar; + ++gData->stateStackTop; + if (gData->stateStackTop == gData->maxStateStack) + if (!reallocStateStack(gData)) + return NULL; + if (curState->u.quantifier.min == 0) + if (!pushBackTrackState(gData, REOP_REPEAT, + pc, x, startcp, 0, 0)) + return NULL; + pc = nextpc; + continue; + + case REOP_ENDCHILD: /* marks the end of a quantifier child */ + pc = curState[-1].continue_pc; + op = curState[-1].continue_op; + continue; + + case REOP_REPEAT: + --curState; +repeatAgain: + --gData->stateStackTop; + if (result == NULL) { + /* + * There's been a failure, see if we have enough children. + */ + if (curState->u.quantifier.min == 0) { + result = x; + goto repeatDone; + } + break; + } + else { + if ((curState->u.quantifier.min == 0) + && (x->cp == gData->cpbegin + curState->index)) { + /* matched an empty string, that'll get us nowhere */ + result = NULL; + break; + } + if (curState->u.quantifier.min != 0) + curState->u.quantifier.min--; + if (curState->u.quantifier.max != (uint16)(-1)) + curState->u.quantifier.max--; + if (curState->u.quantifier.max == 0) { + result = x; + goto repeatDone; + } + nextpc = pc + ARG_LEN; + nextop = (REOp)(*nextpc); + startcp = x->cp; + if (REOP_IS_SIMPLE(nextop)) { + nextpc++; + if (!simpleMatch(gData, x, nextop, &nextpc, JS_TRUE)) { + if (curState->u.quantifier.min == 0) { + result = x; + goto repeatDone; + } + else + result = NULL; + break; + } + result = x; + } + curState->index = startcp - gData->cpbegin; + ++gData->stateStackTop; + if (gData->stateStackTop == gData->maxStateStack) + if (!reallocStateStack(gData)) + return NULL; + if (curState->u.quantifier.min == 0) + if (!pushBackTrackState(gData, REOP_REPEAT, + pc, x, startcp, + curState->parenSoFar, + parenSoFar + - curState->parenSoFar)) + return NULL; + if (*nextpc == REOP_ENDCHILD) + goto repeatAgain; + pc = nextpc; + op = (REOp)(*pc++); + parenSoFar = curState->parenSoFar; + } + continue; +repeatDone: + pc = pc + GET_OFFSET(pc); + break; + + + case REOP_MINIMALSTAR: + curState->u.quantifier.min = 0; + curState->u.quantifier.max = -1; + goto minimalquantcommon; + case REOP_MINIMALPLUS: + curState->u.quantifier.min = 1; + curState->u.quantifier.max = -1; + goto minimalquantcommon; + case REOP_MINIMALOPT: + curState->u.quantifier.min = 0; + curState->u.quantifier.max = 1; + goto minimalquantcommon; + case REOP_MINIMALQUANT: + curState->u.quantifier.min = GET_ARG(pc); + pc += ARG_LEN; + curState->u.quantifier.max = GET_ARG(pc); + pc += ARG_LEN; +minimalquantcommon: + curState->index = x->cp - gData->cpbegin; + curState->parenSoFar = parenSoFar; + ++gData->stateStackTop; + if (gData->stateStackTop == gData->maxStateStack) + if (!reallocStateStack(gData)) + return NULL; + if (curState->u.quantifier.min != 0) { + curState->continue_op = REOP_MINIMALREPEAT; + curState->continue_pc = pc; + /* step over */ + pc += ARG_LEN; + op = (REOp)(*pc++); + } + else { + if (!pushBackTrackState(gData, REOP_MINIMALREPEAT, + pc, x, x->cp, 0, 0)) + return NULL; + --gData->stateStackTop; + pc = pc + GET_OFFSET(pc); + op = (REOp)(*pc++); + } + continue; + + case REOP_MINIMALREPEAT: + --gData->stateStackTop; + --curState; + + if (result == NULL) { + /* + * Non-greedy failure - try to consume another child. + */ + if ((curState->u.quantifier.max == (uint16)(-1)) + || (curState->u.quantifier.max > 0)) { + curState->index = x->cp - gData->cpbegin; + curState->continue_op = REOP_MINIMALREPEAT; + curState->continue_pc = pc; + pc += ARG_LEN; + for (k = curState->parenSoFar; k < parenSoFar; k++) + x->parens[k].index = -1; + ++gData->stateStackTop; + if (gData->stateStackTop == gData->maxStateStack) + if (!reallocStateStack(gData)) + return NULL; + op = (REOp)(*pc++); + continue; + } + else { + /* Don't need to adjust pc since we're going to pop. */ + break; + } + } + else { + if ((curState->u.quantifier.min == 0) + && (x->cp == gData->cpbegin + curState->index)) { + /* Matched an empty string, that'll get us nowhere. */ + result = NULL; + break; + } + if (curState->u.quantifier.min != 0) + curState->u.quantifier.min--; + if (curState->u.quantifier.max != (uint16)(-1)) + curState->u.quantifier.max--; + if (curState->u.quantifier.min != 0) { + curState->continue_op = REOP_MINIMALREPEAT; + curState->continue_pc = pc; + pc += ARG_LEN; + for (k = curState->parenSoFar; k < parenSoFar; k++) + x->parens[k].index = -1; + curState->index = x->cp - gData->cpbegin; + ++gData->stateStackTop; + if (gData->stateStackTop == gData->maxStateStack) + if (!reallocStateStack(gData)) + return NULL; + op = (REOp)(*pc++); + continue; + } + else { + curState->index = x->cp - gData->cpbegin; + curState->parenSoFar = parenSoFar; + ++gData->stateStackTop; + if (gData->stateStackTop == gData->maxStateStack) + if (!reallocStateStack(gData)) + return NULL; + if (!pushBackTrackState(gData, REOP_MINIMALREPEAT, + pc, x, x->cp, + curState->parenSoFar, + parenSoFar + - curState->parenSoFar)) + return NULL; + --gData->stateStackTop; + pc = pc + GET_OFFSET(pc); + op = (REOp)(*pc++); + continue; + } + } + + default: + JS_ASSERT(JS_FALSE); + result = NULL; + } + } + /* + * If the match failed and there's a backtrack option, take it. + * Otherwise this is a complete and utter failure. + */ + if (result == NULL) { + if (gData->cursz > 0) { + backTrackData = gData->backTrackSP; + gData->cursz = backTrackData->sz; + gData->backTrackSP + = (REBackTrackData *)((char *)backTrackData + - backTrackData->sz); + x->cp = backTrackData->cp; + pc = backTrackData->backtrack_pc; + op = backTrackData->backtrack_op; + gData->stateStackTop = backTrackData->precedingStateTop; + JS_ASSERT(gData->stateStackTop); + + memcpy(gData->stateStack, backTrackData + 1, + sizeof(REProgState) * backTrackData->precedingStateTop); + curState = &gData->stateStack[gData->stateStackTop - 1]; + + if (backTrackData->parenCount) { + memcpy(&x->parens[backTrackData->parenIndex], + (char *)(backTrackData + 1) + sizeof(REProgState) * backTrackData->precedingStateTop, + sizeof(RECapture) * backTrackData->parenCount); + parenSoFar = backTrackData->parenIndex + backTrackData->parenCount; + } + else { + for (k = curState->parenSoFar; k < parenSoFar; k++) + x->parens[k].index = -1; + parenSoFar = curState->parenSoFar; + } + continue; + } + else + return NULL; + } + else + x = result; + + /* + * Continue with the expression. + */ + op = (REOp)*pc++; + } + return NULL; +} + +static REMatchState * +MatchRegExp(REGlobalData *gData, REMatchState *x) +{ + REMatchState *result; + const jschar *cp = x->cp; + const jschar *cp2; + uintN j; + + /* + * Have to include the position beyond the last character + * in order to detect end-of-input/line condition. + */ + for (cp2 = cp; cp2 <= gData->cpend; cp2++) { + gData->skipped = cp2 - cp; + x->cp = cp2; + for (j = 0; j < gData->regexp->parenCount; j++) + x->parens[j].index = -1; + result = executeREBytecode(gData, x); + if (!gData->ok || result) + return result; + gData->backTrackSP = gData->backTrackStack; + gData->cursz = 0; + gData->stateStackTop = 0; + cp2 = cp + gData->skipped; + } + return NULL; +} + + +static REMatchState * +initMatch(JSContext *cx, REGlobalData *gData, JSRegExp *re) +{ + REMatchState *result; + uintN i; + + gData->maxBackTrack = INITIAL_BACKTRACK; + JS_ARENA_ALLOCATE_CAST(gData->backTrackStack, REBackTrackData *, + &gData->pool, + INITIAL_BACKTRACK); + if (!gData->backTrackStack) + return NULL; + gData->backTrackSP = gData->backTrackStack; + gData->cursz = 0; + + + gData->maxStateStack = INITIAL_STATESTACK; + JS_ARENA_ALLOCATE_CAST(gData->stateStack, REProgState *, + &gData->pool, + sizeof(REProgState) * INITIAL_STATESTACK); + if (!gData->stateStack) + return NULL; + gData->stateStackTop = 0; + + gData->cx = cx; + gData->regexp = re; + gData->ok = JS_TRUE; + + JS_ARENA_ALLOCATE_CAST(result, REMatchState *, + &gData->pool, + sizeof(REMatchState) + + (re->parenCount - 1) * sizeof(RECapture)); + if (!result) + return NULL; + + for (i = 0; i < re->classCount; i++) + if (!re->classList[i].converted) + if (!processCharSet(gData, &re->classList[i])) + return NULL; + + return result; +} + +JSBool +js_ExecuteRegExp(JSContext *cx, JSRegExp *re, JSString *str, size_t *indexp, + JSBool test, jsval *rval) +{ + REGlobalData gData; + REMatchState *x, *result; + + const jschar *cp, *ep; + size_t i, length, start; + JSSubString *morepar; + JSBool ok; + JSRegExpStatics *res; + ptrdiff_t matchlen; + uintN num, morenum; + JSString *parstr, *matchstr; + JSObject *obj; + + RECapture *parsub; + + /* + * It's safe to load from cp because JSStrings have a zero at the end, + * and we never let cp get beyond cpend. + */ + start = *indexp; + length = JSSTRING_LENGTH(str); + if (start > length) + start = length; + cp = JSSTRING_CHARS(str); + gData.cpbegin = cp; + gData.cpend = cp + length; + cp += start; + gData.start = start; + gData.skipped = 0; + + JS_InitArenaPool(&gData.pool, "RegExpPool", 8096, 4); + x = initMatch(cx, &gData, re); + if (!x) + return JS_FALSE; + x->cp = cp; + + /* + * Call the recursive matcher to do the real work. Return null on mismatch + * whether testing or not. On match, return an extended Array object. + */ + result = MatchRegExp(&gData, x); + if (!(ok = gData.ok)) goto out; + if (!result) { + *rval = JSVAL_NULL; + goto out; + } + cp = result->cp; + i = PTRDIFF(cp, gData.cpbegin, jschar); + *indexp = i; + matchlen = i - (start + gData.skipped); + ep = cp; + cp -= matchlen; + + if (test) { + /* + * Testing for a match and updating cx->regExpStatics: don't allocate + * an array object, do return true. + */ + *rval = JSVAL_TRUE; + + /* Avoid warning. (gcc doesn't detect that obj is needed iff !test); */ + obj = NULL; + } else { + /* + * The array returned on match has element 0 bound to the matched + * string, elements 1 through state.parenCount bound to the paren + * matches, an index property telling the length of the left context, + * and an input property referring to the input string. + */ + obj = js_NewArrayObject(cx, 0, NULL); + if (!obj) { + ok = JS_FALSE; + goto out; + } + *rval = OBJECT_TO_JSVAL(obj); + +#define DEFVAL(val, id) { \ + ok = js_DefineProperty(cx, obj, id, val, \ + JS_PropertyStub, JS_PropertyStub, \ + JSPROP_ENUMERATE, NULL); \ + if (!ok) { \ + cx->newborn[GCX_OBJECT] = NULL; \ + cx->newborn[GCX_STRING] = NULL; \ + goto out; \ + } \ +} + + matchstr = js_NewStringCopyN(cx, cp, matchlen, 0); + if (!matchstr) { + cx->newborn[GCX_OBJECT] = NULL; + ok = JS_FALSE; + goto out; + } + DEFVAL(STRING_TO_JSVAL(matchstr), INT_TO_JSVAL(0)); + } + + res = &cx->regExpStatics; + res->input = str; + res->parenCount = re->parenCount; + if (re->parenCount == 0) + res->lastParen = js_EmptySubString; + else { + for (num = 0; num < re->parenCount; num++) { + parsub = &result->parens[num]; + if (num < 9) { + if (parsub->index == -1) { + res->parens[num].chars = NULL; + res->parens[num].length = 0; + } + else { + res->parens[num].chars = gData.cpbegin + parsub->index; + res->parens[num].length = parsub->length; + } + } else { + morenum = num - 9; + morepar = res->moreParens; + if (!morepar) { + res->moreLength = 10; + morepar = (JSSubString*) JS_malloc(cx, + 10 * sizeof(JSSubString)); + } else if (morenum >= res->moreLength) { + res->moreLength += 10; + morepar = (JSSubString*) JS_realloc(cx, morepar, + res->moreLength * sizeof(JSSubString)); + } + if (!morepar) { + cx->newborn[GCX_OBJECT] = NULL; + cx->newborn[GCX_STRING] = NULL; + ok = JS_FALSE; + goto out; + } + res->moreParens = morepar; + if (parsub->index == -1) { + morepar[morenum].chars = NULL; + morepar[morenum].length = 0; + } + else { + morepar[morenum].chars = gData.cpbegin + parsub->index; + morepar[morenum].length = parsub->length; + } + } + if (test) + continue; + if (parsub->index == -1) + ok = js_DefineProperty(cx, obj, INT_TO_JSVAL(num + 1), + JSVAL_VOID, NULL, NULL, + JSPROP_ENUMERATE, NULL); + else { + parstr = js_NewStringCopyN(cx, gData.cpbegin + parsub->index, + parsub->length, 0); + if (!parstr) { + cx->newborn[GCX_OBJECT] = NULL; + cx->newborn[GCX_STRING] = NULL; + ok = JS_FALSE; + goto out; + } + ok = js_DefineProperty(cx, obj, INT_TO_JSVAL(num + 1), + STRING_TO_JSVAL(parstr), NULL, NULL, + JSPROP_ENUMERATE, NULL); + } + if (!ok) { + cx->newborn[GCX_OBJECT] = NULL; + cx->newborn[GCX_STRING] = NULL; + goto out; + } + } + if (parsub->index == -1) { + res->lastParen.chars = NULL; + res->lastParen.length = 0; + } + else { + res->lastParen.chars = gData.cpbegin + parsub->index; + res->lastParen.length = parsub->length; + } + } + + if (!test) { + /* + * Define the index and input properties last for better for/in loop + * order (so they come after the elements). + */ + DEFVAL(INT_TO_JSVAL(start + gData.skipped), + (jsid)cx->runtime->atomState.indexAtom); + DEFVAL(STRING_TO_JSVAL(str), + (jsid)cx->runtime->atomState.inputAtom); + } + +#undef DEFVAL + + res->lastMatch.chars = cp; + res->lastMatch.length = matchlen; + if (cx->version == JSVERSION_1_2) { + /* + * JS1.2 emulated Perl4.0.1.8 (patch level 36) for global regexps used + * in scalar contexts, and unintentionally for the string.match "list" + * psuedo-context. On "hi there bye", the following would result: + * + * Language while(/ /g){print("$`");} s/ /$`/g + * perl4.036 "hi", "there" "hihitherehi therebye" + * perl5 "hi", "hi there" "hihitherehi therebye" + * js1.2 "hi", "there" "hihitheretherebye" + */ + res->leftContext.chars = JSSTRING_CHARS(str) + start; + res->leftContext.length = gData.skipped; + } else { + /* + * For JS1.3 and ECMAv2, emulate Perl5 exactly: + * + * js1.3 "hi", "hi there" "hihitherehi therebye" + */ + res->leftContext.chars = JSSTRING_CHARS(str); + res->leftContext.length = start + gData.skipped; + } + res->rightContext.chars = ep; + res->rightContext.length = gData.cpend - ep; + +out: + JS_FinishArenaPool(&gData.pool); + return ok; +} + +/************************************************************************/ + +enum regexp_tinyid { + REGEXP_SOURCE = -1, + REGEXP_GLOBAL = -2, + REGEXP_IGNORE_CASE = -3, + REGEXP_LAST_INDEX = -4, + REGEXP_MULTILINE = -5 +}; + +#define REGEXP_PROP_ATTRS (JSPROP_PERMANENT|JSPROP_SHARED) + +static JSPropertySpec regexp_props[] = { + {"source", REGEXP_SOURCE, REGEXP_PROP_ATTRS | JSPROP_READONLY,0,0}, + {"global", REGEXP_GLOBAL, REGEXP_PROP_ATTRS | JSPROP_READONLY,0,0}, + {"ignoreCase", REGEXP_IGNORE_CASE, REGEXP_PROP_ATTRS | JSPROP_READONLY,0,0}, + {"lastIndex", REGEXP_LAST_INDEX, REGEXP_PROP_ATTRS,0,0}, + {"multiline", REGEXP_MULTILINE, REGEXP_PROP_ATTRS | JSPROP_READONLY,0,0}, + {0,0,0,0,0} +}; + +static JSBool +regexp_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + jsint slot; + JSRegExp *re; + + if (!JSVAL_IS_INT(id)) + return JS_TRUE; + slot = JSVAL_TO_INT(id); + if (slot == REGEXP_LAST_INDEX) + return JS_GetReservedSlot(cx, obj, 0, vp); + + JS_LOCK_OBJ(cx, obj); + re = (JSRegExp *) JS_GetInstancePrivate(cx, obj, &js_RegExpClass, NULL); + if (re) { + switch (slot) { + case REGEXP_SOURCE: + *vp = STRING_TO_JSVAL(re->source); + break; + case REGEXP_GLOBAL: + *vp = BOOLEAN_TO_JSVAL((re->flags & JSREG_GLOB) != 0); + break; + case REGEXP_IGNORE_CASE: + *vp = BOOLEAN_TO_JSVAL((re->flags & JSREG_FOLD) != 0); + break; + case REGEXP_MULTILINE: + *vp = BOOLEAN_TO_JSVAL((re->flags & JSREG_MULTILINE) != 0); + break; + } + } + JS_UNLOCK_OBJ(cx, obj); + return JS_TRUE; +} + +static JSBool +regexp_setProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + JSBool ok; + jsint slot; + jsdouble lastIndex; + + ok = JS_TRUE; + if (!JSVAL_IS_INT(id)) + return ok; + slot = JSVAL_TO_INT(id); + if (slot == REGEXP_LAST_INDEX) { + if (!js_ValueToNumber(cx, *vp, &lastIndex)) + return JS_FALSE; + lastIndex = js_DoubleToInteger(lastIndex); + ok = js_NewNumberValue(cx, lastIndex, vp) && + JS_SetReservedSlot(cx, obj, 0, *vp); + } + return ok; +} + +/* + * RegExp class static properties and their Perl counterparts: + * + * RegExp.input $_ + * RegExp.multiline $* + * RegExp.lastMatch $& + * RegExp.lastParen $+ + * RegExp.leftContext $` + * RegExp.rightContext $' + */ +enum regexp_static_tinyid { + REGEXP_STATIC_INPUT = -1, + REGEXP_STATIC_MULTILINE = -2, + REGEXP_STATIC_LAST_MATCH = -3, + REGEXP_STATIC_LAST_PAREN = -4, + REGEXP_STATIC_LEFT_CONTEXT = -5, + REGEXP_STATIC_RIGHT_CONTEXT = -6 +}; + +JSBool +js_InitRegExpStatics(JSContext *cx, JSRegExpStatics *res) +{ + JS_ClearRegExpStatics(cx); + return js_AddRoot(cx, &res->input, "res->input"); +} + +void +js_FreeRegExpStatics(JSContext *cx, JSRegExpStatics *res) +{ + if (res->moreParens) { + JS_free(cx, res->moreParens); + res->moreParens = NULL; + } + js_RemoveRoot(cx->runtime, &res->input); +} + +static JSBool +regexp_static_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + jsint slot; + JSRegExpStatics *res; + JSString *str; + JSSubString *sub; + + res = &cx->regExpStatics; + if (!JSVAL_IS_INT(id)) + return JS_TRUE; + slot = JSVAL_TO_INT(id); + switch (slot) { + case REGEXP_STATIC_INPUT: + *vp = res->input ? STRING_TO_JSVAL(res->input) + : JS_GetEmptyStringValue(cx); + return JS_TRUE; + case REGEXP_STATIC_MULTILINE: + *vp = BOOLEAN_TO_JSVAL(res->multiline); + return JS_TRUE; + case REGEXP_STATIC_LAST_MATCH: + sub = &res->lastMatch; + break; + case REGEXP_STATIC_LAST_PAREN: + sub = &res->lastParen; + break; + case REGEXP_STATIC_LEFT_CONTEXT: + sub = &res->leftContext; + break; + case REGEXP_STATIC_RIGHT_CONTEXT: + sub = &res->rightContext; + break; + default: + sub = REGEXP_PAREN_SUBSTRING(res, slot); + break; + } + str = js_NewStringCopyN(cx, sub->chars, sub->length, 0); + if (!str) + return JS_FALSE; + *vp = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +static JSBool +regexp_static_setProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + JSRegExpStatics *res; + + if (!JSVAL_IS_INT(id)) + return JS_TRUE; + res = &cx->regExpStatics; + /* XXX use if-else rather than switch to keep MSVC1.52 from crashing */ + if (JSVAL_TO_INT(id) == REGEXP_STATIC_INPUT) { + if (!JSVAL_IS_STRING(*vp) && + !JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp)) { + return JS_FALSE; + } + res->input = JSVAL_TO_STRING(*vp); + } else if (JSVAL_TO_INT(id) == REGEXP_STATIC_MULTILINE) { + if (!JSVAL_IS_BOOLEAN(*vp) && + !JS_ConvertValue(cx, *vp, JSTYPE_BOOLEAN, vp)) { + return JS_FALSE; + } + res->multiline = JSVAL_TO_BOOLEAN(*vp); + } + return JS_TRUE; +} + +static JSPropertySpec regexp_static_props[] = { + {"input", + REGEXP_STATIC_INPUT, + JSPROP_ENUMERATE|JSPROP_SHARED, + regexp_static_getProperty, regexp_static_setProperty}, + {"multiline", + REGEXP_STATIC_MULTILINE, + JSPROP_ENUMERATE|JSPROP_SHARED, + regexp_static_getProperty, regexp_static_setProperty}, + {"lastMatch", + REGEXP_STATIC_LAST_MATCH, + JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, + regexp_static_getProperty, regexp_static_getProperty}, + {"lastParen", + REGEXP_STATIC_LAST_PAREN, + JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, + regexp_static_getProperty, regexp_static_getProperty}, + {"leftContext", + REGEXP_STATIC_LEFT_CONTEXT, + JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, + regexp_static_getProperty, regexp_static_getProperty}, + {"rightContext", + REGEXP_STATIC_RIGHT_CONTEXT, + JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, + regexp_static_getProperty, regexp_static_getProperty}, + + /* XXX should have block scope and local $1, etc. */ + {"$1", 0, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, + regexp_static_getProperty, regexp_static_getProperty}, + {"$2", 1, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, + regexp_static_getProperty, regexp_static_getProperty}, + {"$3", 2, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, + regexp_static_getProperty, regexp_static_getProperty}, + {"$4", 3, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, + regexp_static_getProperty, regexp_static_getProperty}, + {"$5", 4, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, + regexp_static_getProperty, regexp_static_getProperty}, + {"$6", 5, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, + regexp_static_getProperty, regexp_static_getProperty}, + {"$7", 6, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, + regexp_static_getProperty, regexp_static_getProperty}, + {"$8", 7, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, + regexp_static_getProperty, regexp_static_getProperty}, + {"$9", 8, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED, + regexp_static_getProperty, regexp_static_getProperty}, + + {0,0,0,0,0} +}; + +static void +regexp_finalize(JSContext *cx, JSObject *obj) +{ + JSRegExp *re; + + re = (JSRegExp *) JS_GetPrivate(cx, obj); + if (!re) + return; + js_DestroyRegExp(cx, re); +} + +/* Forward static prototype. */ +static JSBool +regexp_exec(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval); + +static JSBool +regexp_call(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + return regexp_exec(cx, JSVAL_TO_OBJECT(argv[-2]), argc, argv, rval); +} + +#if JS_HAS_XDR + +#include "jsxdrapi.h" + +static JSBool +regexp_xdrObject(JSXDRState *xdr, JSObject **objp) +{ + JSRegExp *re; + JSString *source; + uint8 flags; + + if (xdr->mode == JSXDR_ENCODE) { + re = (JSRegExp *) JS_GetPrivate(xdr->cx, *objp); + if (!re) + return JS_FALSE; + source = re->source; + flags = (uint8) re->flags; + } + if (!JS_XDRString(xdr, &source) || + !JS_XDRUint8(xdr, &flags)) { + return JS_FALSE; + } + if (xdr->mode == JSXDR_DECODE) { + *objp = js_NewObject(xdr->cx, &js_RegExpClass, NULL, NULL); + if (!*objp) + return JS_FALSE; + re = js_NewRegExp(xdr->cx, NULL, source, flags, JS_FALSE); + if (!re) + return JS_FALSE; + if (!JS_SetPrivate(xdr->cx, *objp, re) || + !js_SetLastIndex(xdr->cx, *objp, 0)) { + js_DestroyRegExp(xdr->cx, re); + return JS_FALSE; + } + } + return JS_TRUE; +} + +#else /* !JS_HAS_XDR */ + +#define regexp_xdrObject NULL + +#endif /* !JS_HAS_XDR */ + +static uint32 +regexp_mark(JSContext *cx, JSObject *obj, void *arg) +{ + JSRegExp *re = (JSRegExp *) JS_GetPrivate(cx, obj); + if (re) + JS_MarkGCThing(cx, re->source, "source", arg); + return 0; +} + +JSClass js_RegExpClass = { + js_RegExp_str, + JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(1), + JS_PropertyStub, JS_PropertyStub, regexp_getProperty, regexp_setProperty, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, regexp_finalize, + NULL, NULL, regexp_call, NULL, + regexp_xdrObject, NULL, regexp_mark, 0 +}; + +static const jschar empty_regexp_ucstr[] = {'(', '?', ':', ')', 0}; + +static JSBool +regexp_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSRegExp *re; + const jschar *source; + jschar *chars; + size_t length, nflags; + uintN flags; + JSString *str; + + if (!JS_InstanceOf(cx, obj, &js_RegExpClass, argv)) + return JS_FALSE; + JS_LOCK_OBJ(cx, obj); + re = (JSRegExp *) JS_GetPrivate(cx, obj); + if (!re) { + JS_UNLOCK_OBJ(cx, obj); + *rval = STRING_TO_JSVAL(cx->runtime->emptyString); + return JS_TRUE; + } + + source = JSSTRING_CHARS(re->source); + length = JSSTRING_LENGTH(re->source); + if (length == 0) { + source = empty_regexp_ucstr; + length = sizeof(empty_regexp_ucstr) / sizeof(jschar) - 1; + } + length += 2; + nflags = 0; + for (flags = re->flags; flags != 0; flags &= flags - 1) + nflags++; + chars = (jschar*) JS_malloc(cx, (length + nflags + 1) * sizeof(jschar)); + if (!chars) { + JS_UNLOCK_OBJ(cx, obj); + return JS_FALSE; + } + + chars[0] = '/'; + js_strncpy(&chars[1], source, length - 2); + chars[length-1] = '/'; + if (nflags) { + if (re->flags & JSREG_GLOB) + chars[length++] = 'g'; + if (re->flags & JSREG_FOLD) + chars[length++] = 'i'; + if (re->flags & JSREG_MULTILINE) + chars[length++] = 'm'; + } + JS_UNLOCK_OBJ(cx, obj); + chars[length] = 0; + + str = js_NewString(cx, chars, length, 0); + if (!str) { + JS_free(cx, chars); + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +static JSBool +regexp_compile(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSString *opt, *str; + JSRegExp *oldre, *re; + JSBool ok; + JSObject *obj2; + + if (!JS_InstanceOf(cx, obj, &js_RegExpClass, argv)) + return JS_FALSE; + opt = NULL; + if (argc == 0) { + str = cx->runtime->emptyString; + } else { + if (JSVAL_IS_OBJECT(argv[0])) { + /* + * If we get passed in a RegExp object we construct a new + * RegExp that is a duplicate of it by re-compiling the + * original source code. ECMA requires that it be an error + * here if the flags are specified. (We must use the flags + * from the original RegExp also). + */ + obj2 = JSVAL_TO_OBJECT(argv[0]); + if (obj2 && OBJ_GET_CLASS(cx, obj2) == &js_RegExpClass) { + if (argc >= 2 && !JSVAL_IS_VOID(argv[1])) { /* 'flags' passed */ + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_NEWREGEXP_FLAGGED); + return JS_FALSE; + } + JS_LOCK_OBJ(cx, obj2); + re = (JSRegExp *) JS_GetPrivate(cx, obj2); + if (!re) { + JS_UNLOCK_OBJ(cx, obj2); + return JS_FALSE; + } + re = js_NewRegExp(cx, NULL, re->source, re->flags, JS_FALSE); + JS_UNLOCK_OBJ(cx, obj2); + goto madeit; + } + } + str = js_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + argv[0] = STRING_TO_JSVAL(str); + if (argc > 1) { + if (JSVAL_IS_VOID(argv[1])) { + opt = NULL; + } else { + opt = js_ValueToString(cx, argv[1]); + if (!opt) + return JS_FALSE; + argv[1] = STRING_TO_JSVAL(opt); + } + } + } + re = js_NewRegExpOpt(cx, NULL, str, opt, JS_FALSE); +madeit: + if (!re) + return JS_FALSE; + JS_LOCK_OBJ(cx, obj); + oldre = (JSRegExp *) JS_GetPrivate(cx, obj); + ok = JS_SetPrivate(cx, obj, re) && js_SetLastIndex(cx, obj, 0); + JS_UNLOCK_OBJ(cx, obj); + if (!ok) { + js_DestroyRegExp(cx, re); + } else { + if (oldre) + js_DestroyRegExp(cx, oldre); + *rval = OBJECT_TO_JSVAL(obj); + } + return ok; +} + +static JSBool +regexp_exec_sub(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + JSBool test, jsval *rval) +{ + JSBool ok; + JSRegExp *re; + jsdouble lastIndex; + JSString *str; + size_t i; + + ok = JS_InstanceOf(cx, obj, &js_RegExpClass, argv); + if (!ok) + return JS_FALSE; + JS_LOCK_OBJ(cx, obj); + re = (JSRegExp *) JS_GetPrivate(cx, obj); + if (!re) { + JS_UNLOCK_OBJ(cx, obj); + return JS_TRUE; + } + + /* NB: we must reach out: after this paragraph, in order to drop re. */ + HOLD_REGEXP(cx, re); + if (re->flags & JSREG_GLOB) { + ok = js_GetLastIndex(cx, obj, &lastIndex); + } else { + lastIndex = 0; + } + JS_UNLOCK_OBJ(cx, obj); + if (!ok) + goto out; + + /* Now that obj is unlocked, it's safe to (potentially) grab the GC lock. */ + if (argc == 0) { + str = cx->regExpStatics.input; + if (!str) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_NO_INPUT, + JS_GetStringBytes(re->source), + (re->flags & JSREG_GLOB) ? "g" : "", + (re->flags & JSREG_FOLD) ? "i" : "", + (re->flags & JSREG_MULTILINE) ? "m" : ""); + ok = JS_FALSE; + goto out; + } + } else { + str = js_ValueToString(cx, argv[0]); + if (!str) + goto out; + argv[0] = STRING_TO_JSVAL(str); + } + + if (lastIndex < 0 || JSSTRING_LENGTH(str) < lastIndex) { + ok = js_SetLastIndex(cx, obj, 0); + *rval = JSVAL_NULL; + } else { + i = (size_t) lastIndex; + ok = js_ExecuteRegExp(cx, re, str, &i, test, rval); + if (ok && (re->flags & JSREG_GLOB)) + ok = js_SetLastIndex(cx, obj, (*rval == JSVAL_NULL) ? 0 : i); + } + +out: + DROP_REGEXP(cx, re); + return ok; +} + +static JSBool +regexp_exec(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + return regexp_exec_sub(cx, obj, argc, argv, JS_FALSE, rval); +} + +static JSBool +regexp_test(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + if (!regexp_exec_sub(cx, obj, argc, argv, JS_TRUE, rval)) + return JS_FALSE; + if (*rval != JSVAL_TRUE) + *rval = JSVAL_FALSE; + return JS_TRUE; +} + +static JSFunctionSpec regexp_methods[] = { +#if JS_HAS_TOSOURCE + {js_toSource_str, regexp_toString, 0,0,0}, +#endif + {js_toString_str, regexp_toString, 0,0,0}, + {"compile", regexp_compile, 1,0,0}, + {"exec", regexp_exec, 0,0,0}, + {"test", regexp_test, 0,0,0}, + {0,0,0,0,0} +}; + +static JSBool +RegExp(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + if (!(cx->fp->flags & JSFRAME_CONSTRUCTING)) { + /* + * If first arg is regexp and no flags are given, just return the arg. + * (regexp_compile detects the regexp + flags case and throws a + * TypeError.) See 10.15.3.1. + */ + if ((argc < 2 || JSVAL_IS_VOID(argv[1])) && JSVAL_IS_OBJECT(argv[0]) && + OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(argv[0])) == &js_RegExpClass) { + *rval = argv[0]; + return JS_TRUE; + } + + /* Otherwise, replace obj with a new RegExp object. */ + obj = js_NewObject(cx, &js_RegExpClass, NULL, NULL); + if (!obj) + return JS_FALSE; + } + return regexp_compile(cx, obj, argc, argv, rval); +} + +JSObject * +js_InitRegExpClass(JSContext *cx, JSObject *obj) +{ + JSObject *proto, *ctor; + jsval rval; + + proto = JS_InitClass(cx, obj, NULL, &js_RegExpClass, RegExp, 1, + regexp_props, regexp_methods, + regexp_static_props, NULL); + + if (!proto || !(ctor = JS_GetConstructor(cx, proto))) + return NULL; + if (!JS_AliasProperty(cx, ctor, "input", "$_") || + !JS_AliasProperty(cx, ctor, "multiline", "$*") || + !JS_AliasProperty(cx, ctor, "lastMatch", "$&") || + !JS_AliasProperty(cx, ctor, "lastParen", "$+") || + !JS_AliasProperty(cx, ctor, "leftContext", "$`") || + !JS_AliasProperty(cx, ctor, "rightContext", "$'")) { + goto bad; + } + + /* Give RegExp.prototype private data so it matches the empty string. */ + if (!regexp_compile(cx, proto, 0, NULL, &rval)) + goto bad; + return proto; + +bad: + JS_DeleteProperty(cx, obj, js_RegExpClass.name); + return NULL; +} + +JSObject * +js_NewRegExpObject(JSContext *cx, JSTokenStream *ts, + jschar *chars, size_t length, uintN flags) +{ + JSString *str; + JSObject *obj; + JSRegExp *re; + + str = js_NewStringCopyN(cx, chars, length, 0); + if (!str) + return NULL; + re = js_NewRegExp(cx, ts, str, flags, JS_FALSE); + if (!re) + return NULL; + obj = js_NewObject(cx, &js_RegExpClass, NULL, NULL); + if (!obj || !JS_SetPrivate(cx, obj, re) || !js_SetLastIndex(cx, obj, 0)) { + js_DestroyRegExp(cx, re); + return NULL; + } + return obj; +} + +JSObject * +js_CloneRegExpObject(JSContext *cx, JSObject *obj, JSObject *parent) +{ + JSObject *clone; + JSRegExp *re; + + JS_ASSERT(OBJ_GET_CLASS(cx, obj) == &js_RegExpClass); + clone = js_NewObject(cx, &js_RegExpClass, NULL, parent); + if (!clone) + return NULL; + re = JS_GetPrivate(cx, obj); + if (!JS_SetPrivate(cx, clone, re) || !js_SetLastIndex(cx, clone, 0)) { + cx->newborn[GCX_OBJECT] = NULL; + return NULL; + } + HOLD_REGEXP(cx, re); + return clone; +} + +JSBool +js_GetLastIndex(JSContext *cx, JSObject *obj, jsdouble *lastIndex) +{ + jsval v; + + return JS_GetReservedSlot(cx, obj, 0, &v) && + js_ValueToNumber(cx, v, lastIndex); +} + +JSBool +js_SetLastIndex(JSContext *cx, JSObject *obj, jsdouble lastIndex) +{ + jsval v; + + return js_NewNumberValue(cx, lastIndex, &v) && + JS_SetReservedSlot(cx, obj, 0, v); +} + +#endif /* JS_HAS_REGEXPS */ diff --git a/src/extension/script/js/jsregexp.h b/src/extension/script/js/jsregexp.h new file mode 100644 index 000000000..0485c2860 --- /dev/null +++ b/src/extension/script/js/jsregexp.h @@ -0,0 +1,168 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsregexp_h___ +#define jsregexp_h___ +/* + * JS regular expression interface. + */ +#include +#include "jspubtd.h" +#include "jsstr.h" + +#ifdef JS_THREADSAFE +#include "jsdhash.h" +#endif + +struct JSRegExpStatics { + JSString *input; /* input string to match (perl $_, GC root) */ + JSBool multiline; /* whether input contains newlines (perl $*) */ + uintN parenCount; /* number of valid elements in parens[] */ + uintN moreLength; /* number of allocated elements in moreParens */ + JSSubString parens[9]; /* last set of parens matched (perl $1, $2) */ + JSSubString *moreParens; /* null or realloc'd vector for $10, etc. */ + JSSubString lastMatch; /* last string matched (perl $&) */ + JSSubString lastParen; /* last paren matched (perl $+) */ + JSSubString leftContext; /* input to left of last match (perl $`) */ + JSSubString rightContext; /* input to right of last match (perl $') */ +}; + +/* + * This struct holds a bitmap representation of a class from a regexp. + * There's a list of these referenced by the classList field in the JSRegExp + * struct below. The initial state has startIndex set to the offset in the + * original regexp source of the beginning of the class contents. The first + * use of the class converts the source representation into a bitmap. + * + */ +typedef struct RECharSet { + JSBool converted; + JSBool sense; + uint16 length; + union { + uint8 *bits; + struct { + uint16 startIndex; + uint16 length; + } src; + } u; +} RECharSet; + +/* + * This macro is safe because moreParens is guaranteed to be allocated and big + * enough to hold parenCount, or else be null when parenCount is 0. + */ +#define REGEXP_PAREN_SUBSTRING(res, num) \ + (((jsuint)(num) < (jsuint)(res)->parenCount) \ + ? ((jsuint)(num) < 9) \ + ? &(res)->parens[num] \ + : &(res)->moreParens[(num) - 9] \ + : &js_EmptySubString) + +typedef struct RENode RENode; + +struct JSRegExp { + jsrefcount nrefs; /* reference count */ + uint32 parenCount:24, /* number of parenthesized submatches */ + flags:8; /* flags, see jsapi.h's JSREG_* defines */ + uint32 classCount; /* count [...] bitmaps */ + RECharSet *classList; /* list of [...] bitmaps */ + JSString *source; /* locked source string, sans // */ + jsbytecode program[1]; /* regular expression bytecode */ +}; + +extern JSRegExp * +js_NewRegExp(JSContext *cx, JSTokenStream *ts, + JSString *str, uintN flags, JSBool flat); + +extern JSRegExp * +js_NewRegExpOpt(JSContext *cx, JSTokenStream *ts, + JSString *str, JSString *opt, JSBool flat); + +extern void +js_DestroyRegExp(JSContext *cx, JSRegExp *re); + +/* + * Execute re on input str at *indexp, returning null in *rval on mismatch. + * On match, return true if test is true, otherwise return an array object. + * Update *indexp and cx->regExpStatics always on match. + */ +extern JSBool +js_ExecuteRegExp(JSContext *cx, JSRegExp *re, JSString *str, size_t *indexp, + JSBool test, jsval *rval); + +/* + * These two add and remove GC roots, respectively, so their calls must be + * well-ordered. + */ +extern JSBool +js_InitRegExpStatics(JSContext *cx, JSRegExpStatics *res); + +extern void +js_FreeRegExpStatics(JSContext *cx, JSRegExpStatics *res); + +#define JSVAL_IS_REGEXP(cx, v) \ + (JSVAL_IS_OBJECT(v) && JSVAL_TO_OBJECT(v) && \ + OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(v)) == &js_RegExpClass) + +extern JSClass js_RegExpClass; + +extern JSObject * +js_InitRegExpClass(JSContext *cx, JSObject *obj); + +/* + * Create a new RegExp object. + */ +extern JSObject * +js_NewRegExpObject(JSContext *cx, JSTokenStream *ts, + jschar *chars, size_t length, uintN flags); + +extern JSBool +js_XDRRegExp(JSXDRState *xdr, JSObject **objp); + +extern JSObject * +js_CloneRegExpObject(JSContext *cx, JSObject *obj, JSObject *parent); + +extern JSBool +js_GetLastIndex(JSContext *cx, JSObject *obj, jsdouble *lastIndex); + +extern JSBool +js_SetLastIndex(JSContext *cx, JSObject *obj, jsdouble lastIndex); + +#endif /* jsregexp_h___ */ diff --git a/src/extension/script/js/jsscan.c b/src/extension/script/js/jsscan.c new file mode 100644 index 000000000..e0bc5cd92 --- /dev/null +++ b/src/extension/script/js/jsscan.c @@ -0,0 +1,1315 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS lexical scanner. + */ +#include "jsstddef.h" +#include /* first to avoid trouble on some systems */ +#include +#include +#include +#ifdef HAVE_MEMORY_H +#include +#endif +#include +#include +#include +#include "jstypes.h" +#include "jsarena.h" /* Added by JSIFY */ +#include "jsutil.h" /* Added by JSIFY */ +#include "jsdtoa.h" +#include "jsprf.h" +#include "jsapi.h" +#include "jsatom.h" +#include "jscntxt.h" +#include "jsconfig.h" +#include "jsemit.h" +#include "jsexn.h" +#include "jsnum.h" +#include "jsopcode.h" +#include "jsregexp.h" +#include "jsscan.h" + +#define RESERVE_JAVA_KEYWORDS +#define RESERVE_ECMA_KEYWORDS + +static struct keyword { + const char *name; + JSTokenType tokentype; /* JSTokenType */ + JSOp op; /* JSOp */ + JSVersion version; /* JSVersion */ +} keywords[] = { + {"break", TOK_BREAK, JSOP_NOP, JSVERSION_DEFAULT}, + {"case", TOK_CASE, JSOP_NOP, JSVERSION_DEFAULT}, + {"continue", TOK_CONTINUE, JSOP_NOP, JSVERSION_DEFAULT}, + {"default", TOK_DEFAULT, JSOP_NOP, JSVERSION_DEFAULT}, + {js_delete_str, TOK_DELETE, JSOP_NOP, JSVERSION_DEFAULT}, + {"do", TOK_DO, JSOP_NOP, JSVERSION_DEFAULT}, + {"else", TOK_ELSE, JSOP_NOP, JSVERSION_DEFAULT}, + {"export", TOK_EXPORT, JSOP_NOP, JSVERSION_1_2}, + {js_false_str, TOK_PRIMARY, JSOP_FALSE, JSVERSION_DEFAULT}, + {"for", TOK_FOR, JSOP_NOP, JSVERSION_DEFAULT}, + {js_function_str, TOK_FUNCTION, JSOP_NOP, JSVERSION_DEFAULT}, + {"if", TOK_IF, JSOP_NOP, JSVERSION_DEFAULT}, + {js_in_str, TOK_IN, JSOP_IN, JSVERSION_DEFAULT}, + {js_new_str, TOK_NEW, JSOP_NEW, JSVERSION_DEFAULT}, + {js_null_str, TOK_PRIMARY, JSOP_NULL, JSVERSION_DEFAULT}, + {"return", TOK_RETURN, JSOP_NOP, JSVERSION_DEFAULT}, + {"switch", TOK_SWITCH, JSOP_NOP, JSVERSION_DEFAULT}, + {js_this_str, TOK_PRIMARY, JSOP_THIS, JSVERSION_DEFAULT}, + {js_true_str, TOK_PRIMARY, JSOP_TRUE, JSVERSION_DEFAULT}, + {js_typeof_str, TOK_UNARYOP, JSOP_TYPEOF,JSVERSION_DEFAULT}, + {"var", TOK_VAR, JSOP_DEFVAR,JSVERSION_DEFAULT}, + {js_void_str, TOK_UNARYOP, JSOP_VOID, JSVERSION_DEFAULT}, + {"while", TOK_WHILE, JSOP_NOP, JSVERSION_DEFAULT}, + {"with", TOK_WITH, JSOP_NOP, JSVERSION_DEFAULT}, + +#if JS_HAS_CONST + {js_const_str, TOK_VAR, JSOP_DEFCONST,JSVERSION_DEFAULT}, +#else + {js_const_str, TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, +#endif + +#if JS_HAS_EXCEPTIONS + {"try", TOK_TRY, JSOP_NOP, JSVERSION_DEFAULT}, + {"catch", TOK_CATCH, JSOP_NOP, JSVERSION_DEFAULT}, + {"finally", TOK_FINALLY, JSOP_NOP, JSVERSION_DEFAULT}, + {"throw", TOK_THROW, JSOP_NOP, JSVERSION_DEFAULT}, +#else + {"try", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"catch", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"finally", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"throw", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, +#endif + +#if JS_HAS_INSTANCEOF + {js_instanceof_str, TOK_INSTANCEOF, JSOP_INSTANCEOF,JSVERSION_1_4}, +#else + {js_instanceof_str, TOK_RESERVED, JSOP_NOP, JSVERSION_1_4}, +#endif + +#ifdef RESERVE_JAVA_KEYWORDS + {"abstract", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"boolean", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"byte", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"char", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"class", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"double", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"extends", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"final", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"float", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"goto", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"implements", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"import", TOK_IMPORT, JSOP_NOP, JSVERSION_DEFAULT}, + {"int", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"interface", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"long", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"native", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"package", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"private", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"protected", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"public", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"short", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"static", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"super", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"synchronized", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"throws", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"transient", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, + {"volatile", TOK_RESERVED, JSOP_NOP, JSVERSION_DEFAULT}, +#endif + +#ifdef RESERVE_ECMA_KEYWORDS + {"enum", TOK_RESERVED, JSOP_NOP, JSVERSION_1_3}, +#endif + +#if JS_HAS_DEBUGGER_KEYWORD + {"debugger", TOK_DEBUGGER, JSOP_NOP, JSVERSION_1_3}, +#elif defined(RESERVE_ECMA_KEYWORDS) + {"debugger", TOK_RESERVED, JSOP_NOP, JSVERSION_1_3}, +#endif + {0, TOK_EOF, JSOP_NOP, JSVERSION_DEFAULT} +}; + +JSBool +js_InitScanner(JSContext *cx) +{ + struct keyword *kw; + JSAtom *atom; + + for (kw = keywords; kw->name; kw++) { + atom = js_Atomize(cx, kw->name, strlen(kw->name), ATOM_PINNED); + if (!atom) + return JS_FALSE; + ATOM_SET_KEYWORD(atom, kw); + } + return JS_TRUE; +} + +JS_FRIEND_API(void) +js_MapKeywords(void (*mapfun)(const char *)) +{ + struct keyword *kw; + + for (kw = keywords; kw->name; kw++) + mapfun(kw->name); +} + +JSTokenStream * +js_NewTokenStream(JSContext *cx, const jschar *base, size_t length, + const char *filename, uintN lineno, + JSPrincipals *principals) +{ + JSTokenStream *ts; + + ts = js_NewBufferTokenStream(cx, base, length); + if (!ts) + return NULL; + ts->filename = filename; + ts->lineno = lineno; + if (principals) + JSPRINCIPALS_HOLD(cx, principals); + ts->principals = principals; + return ts; +} + +JS_FRIEND_API(JSTokenStream *) +js_NewBufferTokenStream(JSContext *cx, const jschar *base, size_t length) +{ + size_t nb; + JSTokenStream *ts; + + nb = sizeof(JSTokenStream) + JS_LINE_LIMIT * sizeof(jschar); + JS_ARENA_ALLOCATE_CAST(ts, JSTokenStream *, &cx->tempPool, nb); + if (!ts) { + JS_ReportOutOfMemory(cx); + return NULL; + } + memset(ts, 0, nb); + ts->lineno = 1; + ts->linebuf.base = ts->linebuf.limit = ts->linebuf.ptr = (jschar *)(ts + 1); + ts->userbuf.base = (jschar *)base; + ts->userbuf.limit = (jschar *)base + length; + ts->userbuf.ptr = (jschar *)base; + ts->listener = cx->runtime->sourceHandler; + ts->listenerData = cx->runtime->sourceHandlerData; + return ts; +} + +JS_FRIEND_API(JSTokenStream *) +js_NewFileTokenStream(JSContext *cx, const char *filename, FILE *defaultfp) +{ + jschar *base; + JSTokenStream *ts; + FILE *file; + + JS_ARENA_ALLOCATE_CAST(base, jschar *, &cx->tempPool, + JS_LINE_LIMIT * sizeof(jschar)); + if (!base) + return NULL; + ts = js_NewBufferTokenStream(cx, base, JS_LINE_LIMIT); + if (!ts) + return NULL; + if (!filename || strcmp(filename, "-") == 0) { + file = defaultfp; + } else { + file = fopen(filename, "r"); + if (!file) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_OPEN, + filename, "No such file or directory"); + return NULL; + } + } + ts->userbuf.ptr = ts->userbuf.limit; + ts->file = file; + ts->filename = filename; + return ts; +} + +JS_FRIEND_API(JSBool) +js_CloseTokenStream(JSContext *cx, JSTokenStream *ts) +{ + if (ts->principals) + JSPRINCIPALS_DROP(cx, ts->principals); + return !ts->file || fclose(ts->file) == 0; +} + +static int +my_fgets(char *buf, int size, FILE *file) +{ + int n, i, c; + JSBool crflag; + + n = size - 1; + if (n < 0) + return -1; + + crflag = JS_FALSE; + for (i = 0; i < n && (c = getc(file)) != EOF; i++) { + buf[i] = c; + if (c == '\n') { /* any \n ends a line */ + i++; /* keep the \n; we know there is room for \0 */ + break; + } + if (crflag) { /* \r not followed by \n ends line at the \r */ + ungetc(c, file); + break; /* and overwrite c in buf with \0 */ + } + crflag = (c == '\r'); + } + + buf[i] = '\0'; + return i; +} + +static int32 +GetChar(JSTokenStream *ts) +{ + int32 c; + ptrdiff_t i, j, len, olen; + JSBool crflag; + char cbuf[JS_LINE_LIMIT]; + jschar *ubuf, *nl; + + if (ts->ungetpos != 0) { + c = ts->ungetbuf[--ts->ungetpos]; + } else { + do { + if (ts->linebuf.ptr == ts->linebuf.limit) { + len = PTRDIFF(ts->userbuf.limit, ts->userbuf.ptr, jschar); + if (len <= 0) { + if (!ts->file) { + ts->flags |= TSF_EOF; + return EOF; + } + + /* Fill ts->userbuf so that \r and \r\n convert to \n. */ + crflag = (ts->flags & TSF_CRFLAG) != 0; + len = my_fgets(cbuf, JS_LINE_LIMIT - crflag, ts->file); + if (len <= 0) { + ts->flags |= TSF_EOF; + return EOF; + } + olen = len; + ubuf = ts->userbuf.base; + i = 0; + if (crflag) { + ts->flags &= ~TSF_CRFLAG; + if (cbuf[0] != '\n') { + ubuf[i++] = '\n'; + len++; + ts->linepos--; + } + } + for (j = 0; i < len; i++, j++) + ubuf[i] = (jschar) (unsigned char) cbuf[j]; + ts->userbuf.limit = ubuf + len; + ts->userbuf.ptr = ubuf; + } + if (ts->listener) { + ts->listener(ts->filename, ts->lineno, ts->userbuf.ptr, len, + &ts->listenerTSData, ts->listenerData); + } + + /* + * Any one of \n, \r, or \r\n ends a line (longest match wins). + * Also allow the Unicode line and paragraph separators. + */ + for (nl = ts->userbuf.ptr; nl < ts->userbuf.limit; nl++) { + /* + * Try to prevent value-testing on most characters by + * filtering out characters that aren't 000x or 202x. + */ + if ((*nl & 0xDFD0) == 0) { + if (*nl == '\n') + break; + if (*nl == '\r') { + if (nl + 1 < ts->userbuf.limit && nl[1] == '\n') + nl++; + break; + } + if (*nl == LINE_SEPARATOR || *nl == PARA_SEPARATOR) + break; + } + } + + /* + * If there was a line terminator, copy thru it into linebuf. + * Else copy JS_LINE_LIMIT-1 bytes into linebuf. + */ + if (nl < ts->userbuf.limit) + len = PTRDIFF(nl, ts->userbuf.ptr, jschar) + 1; + if (len >= JS_LINE_LIMIT) + len = JS_LINE_LIMIT - 1; + js_strncpy(ts->linebuf.base, ts->userbuf.ptr, len); + ts->userbuf.ptr += len; + olen = len; + + /* + * Make sure linebuf contains \n for EOL (don't do this in + * userbuf because the user's string might be readonly). + */ + if (nl < ts->userbuf.limit) { + if (*nl == '\r') { + if (ts->linebuf.base[len-1] == '\r') { + /* + * Does the line segment end in \r? We must check + * for a \n at the front of the next segment before + * storing a \n into linebuf. This case matters + * only when we're reading from a file. + */ + if (nl + 1 == ts->userbuf.limit && ts->file) { + len--; + ts->flags |= TSF_CRFLAG; /* clear NLFLAG? */ + if (len == 0) { + /* + * This can happen when a segment ends in + * \r\r. Start over. ptr == limit in this + * case, so we'll fall into buffer-filling + * code. + */ + return GetChar(ts); + } + } else { + ts->linebuf.base[len-1] = '\n'; + } + } + } else if (*nl == '\n') { + if (nl > ts->userbuf.base && + nl[-1] == '\r' && + ts->linebuf.base[len-2] == '\r') { + len--; + JS_ASSERT(ts->linebuf.base[len] == '\n'); + ts->linebuf.base[len-1] = '\n'; + } + } else if (*nl == LINE_SEPARATOR || *nl == PARA_SEPARATOR) { + ts->linebuf.base[len-1] = '\n'; + } + } + + /* Reset linebuf based on adjusted segment length. */ + ts->linebuf.limit = ts->linebuf.base + len; + ts->linebuf.ptr = ts->linebuf.base; + + /* Update position of linebuf within physical userbuf line. */ + if (!(ts->flags & TSF_NLFLAG)) + ts->linepos += ts->linelen; + else + ts->linepos = 0; + if (ts->linebuf.limit[-1] == '\n') + ts->flags |= TSF_NLFLAG; + else + ts->flags &= ~TSF_NLFLAG; + + /* Update linelen from original segment length. */ + ts->linelen = olen; + } + c = *ts->linebuf.ptr++; + } while (JS_ISFORMAT(c)); + } + if (c == '\n') + ts->lineno++; + return c; +} + +static void +UngetChar(JSTokenStream *ts, int32 c) +{ + if (c == EOF) + return; + JS_ASSERT(ts->ungetpos < sizeof ts->ungetbuf / sizeof ts->ungetbuf[0]); + if (c == '\n') + ts->lineno--; + ts->ungetbuf[ts->ungetpos++] = (jschar)c; +} + +static int32 +PeekChar(JSTokenStream *ts) +{ + int32 c; + + c = GetChar(ts); + UngetChar(ts, c); + return c; +} + +static JSBool +PeekChars(JSTokenStream *ts, intN n, jschar *cp) +{ + intN i, j; + int32 c; + + for (i = 0; i < n; i++) { + c = GetChar(ts); + if (c == EOF) + break; + cp[i] = (jschar)c; + } + for (j = i - 1; j >= 0; j--) + UngetChar(ts, cp[j]); + return i == n; +} + +static void +SkipChars(JSTokenStream *ts, intN n) +{ + while (--n >= 0) + GetChar(ts); +} + +static JSBool +MatchChar(JSTokenStream *ts, int32 expect) +{ + int32 c; + + c = GetChar(ts); + if (c == expect) + return JS_TRUE; + UngetChar(ts, c); + return JS_FALSE; +} + +JSBool +js_ReportCompileErrorNumber(JSContext *cx, JSTokenStream *ts, + JSCodeGenerator *cg, uintN flags, + const uintN errorNumber, ...) +{ + va_list ap; + JSErrorReporter onError; + JSErrorReport report; + jschar *tokenptr; + JSString *linestr = NULL; + char *message; + JSBool warning; + + if ((flags & JSREPORT_STRICT) && !JS_HAS_STRICT_OPTION(cx)) + return JS_TRUE; + + memset(&report, 0, sizeof (struct JSErrorReport)); + report.flags = flags; + report.errorNumber = errorNumber; + message = NULL; + + va_start(ap, errorNumber); + if (!js_ExpandErrorArguments(cx, js_GetErrorMessage, NULL, + errorNumber, &message, &report, &warning, + JS_TRUE, ap)) { + return JS_FALSE; + } + va_end(ap); + + js_AddRoot(cx, &linestr, "error line buffer"); + + JS_ASSERT(!ts || ts->linebuf.limit < ts->linebuf.base + JS_LINE_LIMIT); + onError = cx->errorReporter; + if (onError) { + /* + * We are typically called with non-null ts and null cg from jsparse.c. + * We can be called with null ts from the regexp compilation functions. + * The code generator (jsemit.c) may pass null ts and non-null cg. + */ + if (ts) { + report.filename = ts->filename; + report.lineno = ts->lineno; + linestr = js_NewStringCopyN(cx, ts->linebuf.base, + ts->linebuf.limit - ts->linebuf.base, + 0); + report.linebuf = linestr + ? JS_GetStringBytes(linestr) + : NULL; + tokenptr = + ts->tokens[(ts->cursor + ts->lookahead) & NTOKENS_MASK].ptr; + report.tokenptr = linestr + ? report.linebuf + (tokenptr - ts->linebuf.base) + : NULL; + report.uclinebuf = linestr + ? JS_GetStringChars(linestr) + : NULL; + report.uctokenptr = linestr + ? report.uclinebuf + (tokenptr - ts->linebuf.base) + : NULL; + } else if (cg) { + report.filename = cg->filename; + report.lineno = CG_CURRENT_LINE(cg); + } + +#if JS_HAS_ERROR_EXCEPTIONS + /* + * If there's a runtime exception type associated with this error + * number, set that as the pending exception. For errors occuring at + * compile time, this is very likely to be a JSEXN_SYNTAXERR. + * + * If an exception is thrown but not caught, the JSREPORT_EXCEPTION + * flag will be set in report.flags. Proper behavior for an error + * reporter is to ignore a report with this flag for all but top-level + * compilation errors. The exception will remain pending, and so long + * as the non-top-level "load", "eval", or "compile" native function + * returns false, the top-level reporter will eventually receive the + * uncaught exception report. + * + * XXX it'd probably be best if there was only one call to this + * function, but there seem to be two error reporter call points. + */ + + /* + * Only try to raise an exception if there isn't one already set - + * otherwise the exception will describe only the last compile error, + * which is likely spurious. + */ + if (!(ts && (ts->flags & TSF_ERROR))) + if (js_ErrorToException(cx, message, &report)) + onError = NULL; + + /* + * Suppress any compiletime errors that don't occur at the top level. + * This may still fail, as interplevel may be zero in contexts where we + * don't really want to call the error reporter, as when js is called + * by other code which could catch the error. + */ + if (cx->interpLevel != 0) + onError = NULL; +#endif + if (cx->runtime->debugErrorHook && onError) { + JSDebugErrorHook hook = cx->runtime->debugErrorHook; + /* test local in case debugErrorHook changed on another thread */ + if (hook && !hook(cx, message, &report, + cx->runtime->debugErrorHookData)) { + onError = NULL; + } + } + if (onError) + (*onError)(cx, message, &report); + } + if (message) + JS_free(cx, message); + if (report.messageArgs) { + int i = 0; + while (report.messageArgs[i]) + JS_free(cx, (void *)report.messageArgs[i++]); + JS_free(cx, (void *)report.messageArgs); + } + if (report.ucmessage) + JS_free(cx, (void *)report.ucmessage); + + js_RemoveRoot(cx->runtime, &linestr); + + if (ts && !JSREPORT_IS_WARNING(flags)) { + /* Set the error flag to suppress spurious reports. */ + ts->flags |= TSF_ERROR; + } + return warning; +} + +JSTokenType +js_PeekToken(JSContext *cx, JSTokenStream *ts) +{ + JSTokenType tt; + + if (ts->lookahead != 0) { + tt = ts->tokens[(ts->cursor + ts->lookahead) & NTOKENS_MASK].type; + } else { + tt = js_GetToken(cx, ts); + js_UngetToken(ts); + } + return tt; +} + +JSTokenType +js_PeekTokenSameLine(JSContext *cx, JSTokenStream *ts) +{ + JSTokenType tt; + + JS_ASSERT(ts->lookahead == 0 || + ON_CURRENT_LINE(ts, CURRENT_TOKEN(ts).pos)); + ts->flags |= TSF_NEWLINES; + tt = js_PeekToken(cx, ts); + ts->flags &= ~TSF_NEWLINES; + return tt; +} + +#define TBMIN 64 + +static JSBool +GrowTokenBuf(JSContext *cx, JSTokenBuf *tb) +{ + jschar *base; + ptrdiff_t offset, length; + size_t tbsize; + JSArenaPool *pool; + + base = tb->base; + offset = PTRDIFF(tb->ptr, base, jschar); + pool = &cx->tempPool; + if (!base) { + tbsize = TBMIN * sizeof(jschar); + length = TBMIN; + JS_ARENA_ALLOCATE_CAST(base, jschar *, pool, tbsize); + } else { + length = PTRDIFF(tb->limit, base, jschar); + tbsize = length * sizeof(jschar); + length <<= 1; + JS_ARENA_GROW_CAST(base, jschar *, pool, tbsize, tbsize); + } + if (!base) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + tb->base = base; + tb->limit = base + length; + tb->ptr = base + offset; + return JS_TRUE; +} + +static JSBool +AddToTokenBuf(JSContext *cx, JSTokenBuf *tb, jschar c) +{ + if (tb->ptr == tb->limit && !GrowTokenBuf(cx, tb)) + return JS_FALSE; + *tb->ptr++ = c; + return JS_TRUE; +} + +/* + * We have encountered a '\': check for a Unicode escape sequence after it, + * returning the character code value if we found a Unicode escape sequence. + * Otherwise, non-destructively return the original '\'. + */ +static int32 +GetUnicodeEscape(JSTokenStream *ts) +{ + jschar cp[5]; + int32 c; + + if (PeekChars(ts, 5, cp) && cp[0] == 'u' && + JS7_ISHEX(cp[1]) && JS7_ISHEX(cp[2]) && + JS7_ISHEX(cp[3]) && JS7_ISHEX(cp[4])) + { + c = (((((JS7_UNHEX(cp[1]) << 4) + + JS7_UNHEX(cp[2])) << 4) + + JS7_UNHEX(cp[3])) << 4) + + JS7_UNHEX(cp[4]); + SkipChars(ts, 5); + return c; + } + return '\\'; +} + +JSTokenType +js_GetToken(JSContext *cx, JSTokenStream *ts) +{ + JSTokenType tt; + JSToken *tp; + int32 c; + JSAtom *atom; + JSBool hadUnicodeEscape; + +#define INIT_TOKENBUF(tb) ((tb)->ptr = (tb)->base) +#define FINISH_TOKENBUF(tb) if (!AddToTokenBuf(cx, tb, 0)) RETURN(TOK_ERROR) +#define TOKEN_LENGTH(tb) ((tb)->ptr - (tb)->base - 1) +#define RETURN(tt) { if (tt == TOK_ERROR) ts->flags |= TSF_ERROR; \ + tp->pos.end.index = ts->linepos + \ + (ts->linebuf.ptr - ts->linebuf.base) - \ + ts->ungetpos; \ + return (tp->type = tt); } + + /* If there was a fatal error, keep returning TOK_ERROR. */ + if (ts->flags & TSF_ERROR) + return TOK_ERROR; + + /* Check for a pushed-back token resulting from mismatching lookahead. */ + while (ts->lookahead != 0) { + ts->lookahead--; + ts->cursor = (ts->cursor + 1) & NTOKENS_MASK; + tt = CURRENT_TOKEN(ts).type; + if (tt != TOK_EOL || (ts->flags & TSF_NEWLINES)) + return tt; + } + +retry: + do { + c = GetChar(ts); + if (c == '\n') { + ts->flags &= ~TSF_DIRTYLINE; + if (ts->flags & TSF_NEWLINES) + break; + } + } while (JS_ISSPACE(c)); + + ts->cursor = (ts->cursor + 1) & NTOKENS_MASK; + tp = &CURRENT_TOKEN(ts); + tp->ptr = ts->linebuf.ptr - 1; + tp->pos.begin.index = ts->linepos + (tp->ptr - ts->linebuf.base); + tp->pos.begin.lineno = tp->pos.end.lineno = (uint16)ts->lineno; + + if (c == EOF) + RETURN(TOK_EOF); + if (c != '-' && c != '\n') + ts->flags |= TSF_DIRTYLINE; + + hadUnicodeEscape = JS_FALSE; + if (JS_ISIDENT_START(c) || + (c == '\\' && + (c = GetUnicodeEscape(ts), + hadUnicodeEscape = JS_ISIDENT_START(c)))) { + INIT_TOKENBUF(&ts->tokenbuf); + for (;;) { + if (!AddToTokenBuf(cx, &ts->tokenbuf, (jschar)c)) + RETURN(TOK_ERROR); + c = GetChar(ts); + if (c == '\\') { + c = GetUnicodeEscape(ts); + if (!JS_ISIDENT(c)) + break; + hadUnicodeEscape = JS_TRUE; + } else { + if (!JS_ISIDENT(c)) + break; + } + } + UngetChar(ts, c); + FINISH_TOKENBUF(&ts->tokenbuf); + + atom = js_AtomizeChars(cx, + ts->tokenbuf.base, + TOKEN_LENGTH(&ts->tokenbuf), + 0); + if (!atom) + RETURN(TOK_ERROR); + if (!hadUnicodeEscape && ATOM_KEYWORD(atom)) { + struct keyword *kw = ATOM_KEYWORD(atom); + + if (JSVERSION_IS_ECMA(cx->version) || kw->version <= cx->version) { + tp->t_op = (JSOp) kw->op; + RETURN(kw->tokentype); + } + } + tp->t_op = JSOP_NAME; + tp->t_atom = atom; + RETURN(TOK_NAME); + } + + if (JS7_ISDEC(c) || (c == '.' && JS7_ISDEC(PeekChar(ts)))) { + jsint radix; + const jschar *endptr; + jsdouble dval; + + radix = 10; + INIT_TOKENBUF(&ts->tokenbuf); + + if (c == '0') { + if (!AddToTokenBuf(cx, &ts->tokenbuf, (jschar)c)) + RETURN(TOK_ERROR); + c = GetChar(ts); + if (JS_TOLOWER(c) == 'x') { + if (!AddToTokenBuf(cx, &ts->tokenbuf, (jschar)c)) + RETURN(TOK_ERROR); + c = GetChar(ts); + radix = 16; + } else if (JS7_ISDEC(c)) { + radix = 8; + } + } + + while (JS7_ISHEX(c)) { + if (radix < 16) { + if (JS7_ISLET(c)) + break; + + /* + * We permit 08 and 09 as decimal numbers, which makes our + * behaviour a superset of the ECMA numeric grammar. We might + * not always be so permissive, so we warn about it. + */ + if (radix == 8 && c >= '8') { + if (!js_ReportCompileErrorNumber(cx, ts, NULL, + JSREPORT_WARNING, + JSMSG_BAD_OCTAL, + c == '8' ? "08" : "09")) { + RETURN(TOK_ERROR); + } + radix = 10; + } + } + if (!AddToTokenBuf(cx, &ts->tokenbuf, (jschar)c)) + RETURN(TOK_ERROR); + c = GetChar(ts); + } + + if (radix == 10 && (c == '.' || JS_TOLOWER(c) == 'e')) { + if (c == '.') { + do { + if (!AddToTokenBuf(cx, &ts->tokenbuf, (jschar)c)) + RETURN(TOK_ERROR); + c = GetChar(ts); + } while (JS7_ISDEC(c)); + } + if (JS_TOLOWER(c) == 'e') { + if (!AddToTokenBuf(cx, &ts->tokenbuf, (jschar)c)) + RETURN(TOK_ERROR); + c = GetChar(ts); + if (c == '+' || c == '-') { + if (!AddToTokenBuf(cx, &ts->tokenbuf, (jschar)c)) + RETURN(TOK_ERROR); + c = GetChar(ts); + } + if (!JS7_ISDEC(c)) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_MISSING_EXPONENT); + RETURN(TOK_ERROR); + } + do { + if (!AddToTokenBuf(cx, &ts->tokenbuf, (jschar)c)) + RETURN(TOK_ERROR); + c = GetChar(ts); + } while (JS7_ISDEC(c)); + } + } + + UngetChar(ts, c); + FINISH_TOKENBUF(&ts->tokenbuf); + + if (radix == 10) { + if (!js_strtod(cx, ts->tokenbuf.base, &endptr, &dval)) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_OUT_OF_MEMORY); + RETURN(TOK_ERROR); + } + } else { + if (!js_strtointeger(cx, ts->tokenbuf.base, &endptr, radix, &dval)) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_OUT_OF_MEMORY); + RETURN(TOK_ERROR); + } + } + tp->t_dval = dval; + RETURN(TOK_NUMBER); + } + + if (c == '"' || c == '\'') { + int32 val, qc = c; + + INIT_TOKENBUF(&ts->tokenbuf); + while ((c = GetChar(ts)) != qc) { + if (c == '\n' || c == EOF) { + UngetChar(ts, c); + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_UNTERMINATED_STRING); + RETURN(TOK_ERROR); + } + if (c == '\\') { + switch (c = GetChar(ts)) { + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'v': c = '\v'; break; + + default: + if ('0' <= c && c < '8') { + val = JS7_UNDEC(c); + c = PeekChar(ts); + if ('0' <= c && c < '8') { + val = 8 * val + JS7_UNDEC(c); + GetChar(ts); + c = PeekChar(ts); + if ('0' <= c && c < '8') { + int32 save = val; + val = 8 * val + JS7_UNDEC(c); + if (val <= 0377) + GetChar(ts); + else + val = save; + } + } + c = (jschar)val; + } else if (c == 'u') { + jschar cp[4]; + if (PeekChars(ts, 4, cp) && + JS7_ISHEX(cp[0]) && JS7_ISHEX(cp[1]) && + JS7_ISHEX(cp[2]) && JS7_ISHEX(cp[3])) { + c = (((((JS7_UNHEX(cp[0]) << 4) + + JS7_UNHEX(cp[1])) << 4) + + JS7_UNHEX(cp[2])) << 4) + + JS7_UNHEX(cp[3]); + SkipChars(ts, 4); + } + } else if (c == 'x') { + jschar cp[2]; + if (PeekChars(ts, 2, cp) && + JS7_ISHEX(cp[0]) && JS7_ISHEX(cp[1])) { + c = (JS7_UNHEX(cp[0]) << 4) + JS7_UNHEX(cp[1]); + SkipChars(ts, 2); + } + } else if (c == '\n' && JSVERSION_IS_ECMA(cx->version)) { + /* ECMA follows C by removing escaped newlines. */ + continue; + } + break; + } + } + if (!AddToTokenBuf(cx, &ts->tokenbuf, (jschar)c)) + RETURN(TOK_ERROR); + } + FINISH_TOKENBUF(&ts->tokenbuf); + atom = js_AtomizeChars(cx, + ts->tokenbuf.base, + TOKEN_LENGTH(&ts->tokenbuf), + 0); + if (!atom) + RETURN(TOK_ERROR); + tp->pos.end.lineno = (uint16)ts->lineno; + tp->t_op = JSOP_STRING; + tp->t_atom = atom; + RETURN(TOK_STRING); + } + + switch (c) { + case '\n': + c = TOK_EOL; + break; + + case ';': c = TOK_SEMI; break; + case '.': c = TOK_DOT; break; + case '[': c = TOK_LB; break; + case ']': c = TOK_RB; break; + case '{': c = TOK_LC; break; + case '}': c = TOK_RC; break; + case '(': c = TOK_LP; break; + case ')': c = TOK_RP; break; + case ',': c = TOK_COMMA; break; + case '?': c = TOK_HOOK; break; + + case ':': + /* + * Default so compiler can modify to JSOP_GETTER if 'p getter: v' in an + * object initializer, likewise for setter. + */ + tp->t_op = JSOP_NOP; + c = TOK_COLON; + break; + + case '|': + if (MatchChar(ts, c)) { + c = TOK_OR; + } else if (MatchChar(ts, '=')) { + tp->t_op = JSOP_BITOR; + c = TOK_ASSIGN; + } else { + c = TOK_BITOR; + } + break; + + case '^': + if (MatchChar(ts, '=')) { + tp->t_op = JSOP_BITXOR; + c = TOK_ASSIGN; + } else { + c = TOK_BITXOR; + } + break; + + case '&': + if (MatchChar(ts, c)) { + c = TOK_AND; + } else if (MatchChar(ts, '=')) { + tp->t_op = JSOP_BITAND; + c = TOK_ASSIGN; + } else { + c = TOK_BITAND; + } + break; + + case '=': + if (MatchChar(ts, c)) { +#if JS_HAS_TRIPLE_EQOPS + tp->t_op = MatchChar(ts, c) ? JSOP_NEW_EQ : (JSOp)cx->jsop_eq; +#else + tp->t_op = cx->jsop_eq; +#endif + c = TOK_EQOP; + } else { + tp->t_op = JSOP_NOP; + c = TOK_ASSIGN; + } + break; + + case '!': + if (MatchChar(ts, '=')) { +#if JS_HAS_TRIPLE_EQOPS + tp->t_op = MatchChar(ts, '=') ? JSOP_NEW_NE : (JSOp)cx->jsop_ne; +#else + tp->t_op = cx->jsop_ne; +#endif + c = TOK_EQOP; + } else { + tp->t_op = JSOP_NOT; + c = TOK_UNARYOP; + } + break; + + case '<': + /* NB: treat HTML begin-comment as comment-till-end-of-line */ + if (MatchChar(ts, '!')) { + if (MatchChar(ts, '-')) { + if (MatchChar(ts, '-')) + goto skipline; + UngetChar(ts, '-'); + } + UngetChar(ts, '!'); + } + if (MatchChar(ts, c)) { + tp->t_op = JSOP_LSH; + c = MatchChar(ts, '=') ? TOK_ASSIGN : TOK_SHOP; + } else { + tp->t_op = MatchChar(ts, '=') ? JSOP_LE : JSOP_LT; + c = TOK_RELOP; + } + break; + + case '>': + if (MatchChar(ts, c)) { + tp->t_op = MatchChar(ts, c) ? JSOP_URSH : JSOP_RSH; + c = MatchChar(ts, '=') ? TOK_ASSIGN : TOK_SHOP; + } else { + tp->t_op = MatchChar(ts, '=') ? JSOP_GE : JSOP_GT; + c = TOK_RELOP; + } + break; + + case '*': + tp->t_op = JSOP_MUL; + c = MatchChar(ts, '=') ? TOK_ASSIGN : TOK_STAR; + break; + + case '/': + if (MatchChar(ts, '/')) { +skipline: + while ((c = GetChar(ts)) != EOF && c != '\n') + /* skip to end of line */; + UngetChar(ts, c); + goto retry; + } + if (MatchChar(ts, '*')) { + while ((c = GetChar(ts)) != EOF && + !(c == '*' && MatchChar(ts, '/'))) { + /* Ignore all characters until comment close. */ + } + if (c == EOF) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_UNTERMINATED_COMMENT); + RETURN(TOK_ERROR); + } + goto retry; + } + +#if JS_HAS_REGEXPS + if (ts->flags & TSF_REGEXP) { + JSObject *obj; + uintN flags; + + INIT_TOKENBUF(&ts->tokenbuf); + while ((c = GetChar(ts)) != '/') { + if (c == '\n' || c == EOF) { + UngetChar(ts, c); + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_UNTERMINATED_REGEXP); + RETURN(TOK_ERROR); + } + if (c == '\\') { + if (!AddToTokenBuf(cx, &ts->tokenbuf, (jschar)c)) + RETURN(TOK_ERROR); + c = GetChar(ts); + } + if (!AddToTokenBuf(cx, &ts->tokenbuf, (jschar)c)) + RETURN(TOK_ERROR); + } + FINISH_TOKENBUF(&ts->tokenbuf); + for (flags = 0; ; ) { + if (MatchChar(ts, 'g')) + flags |= JSREG_GLOB; + else if (MatchChar(ts, 'i')) + flags |= JSREG_FOLD; + else if (MatchChar(ts, 'm')) + flags |= JSREG_MULTILINE; + else + break; + } + c = PeekChar(ts); + if (JS7_ISLET(c)) { + tp->ptr = ts->linebuf.ptr - 1; + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_BAD_REGEXP_FLAG); + (void) GetChar(ts); + RETURN(TOK_ERROR); + } + obj = js_NewRegExpObject(cx, ts, + ts->tokenbuf.base, + TOKEN_LENGTH(&ts->tokenbuf), + flags); + if (!obj) + RETURN(TOK_ERROR); + atom = js_AtomizeObject(cx, obj, 0); + if (!atom) + RETURN(TOK_ERROR); + tp->t_op = JSOP_OBJECT; + tp->t_atom = atom; + RETURN(TOK_OBJECT); + } +#endif /* JS_HAS_REGEXPS */ + + tp->t_op = JSOP_DIV; + c = MatchChar(ts, '=') ? TOK_ASSIGN : TOK_DIVOP; + break; + + case '%': + tp->t_op = JSOP_MOD; + c = MatchChar(ts, '=') ? TOK_ASSIGN : TOK_DIVOP; + break; + + case '~': + tp->t_op = JSOP_BITNOT; + c = TOK_UNARYOP; + break; + + case '+': + if (MatchChar(ts, '=')) { + tp->t_op = JSOP_ADD; + c = TOK_ASSIGN; + } else if (MatchChar(ts, c)) { + c = TOK_INC; + } else { + tp->t_op = JSOP_POS; + c = TOK_PLUS; + } + break; + + case '-': + if (MatchChar(ts, '=')) { + tp->t_op = JSOP_SUB; + c = TOK_ASSIGN; + } else if (MatchChar(ts, c)) { + if (PeekChar(ts) == '>' && !(ts->flags & TSF_DIRTYLINE)) + goto skipline; + c = TOK_DEC; + } else { + tp->t_op = JSOP_NEG; + c = TOK_MINUS; + } + ts->flags |= TSF_DIRTYLINE; + break; + +#if JS_HAS_SHARP_VARS + case '#': + { + uint32 n; + + c = GetChar(ts); + if (!JS7_ISDEC(c)) { + UngetChar(ts, c); + goto badchar; + } + n = (uint32)JS7_UNDEC(c); + for (;;) { + c = GetChar(ts); + if (!JS7_ISDEC(c)) + break; + n = 10 * n + JS7_UNDEC(c); + if (n >= ATOM_INDEX_LIMIT) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_SHARPVAR_TOO_BIG); + RETURN(TOK_ERROR); + } + } + tp->t_dval = (jsdouble) n; + if (JS_HAS_STRICT_OPTION(cx) && + (c == '=' || c == '#')) { + char buf[20]; + JS_snprintf(buf, sizeof buf, "#%u%c", n, c); + if (!js_ReportCompileErrorNumber(cx, ts, NULL, + JSREPORT_WARNING | + JSREPORT_STRICT, + JSMSG_DEPRECATED_USAGE, + buf)) { + RETURN(TOK_ERROR); + } + } + if (c == '=') + RETURN(TOK_DEFSHARP); + if (c == '#') + RETURN(TOK_USESHARP); + goto badchar; + } + + badchar: +#endif /* JS_HAS_SHARP_VARS */ + + default: + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_ILLEGAL_CHARACTER); + RETURN(TOK_ERROR); + } + + JS_ASSERT(c < TOK_LIMIT); + RETURN((JSTokenType)c); + +#undef INIT_TOKENBUF +#undef FINISH_TOKENBUF +#undef TOKEN_LENGTH +#undef RETURN +} + +void +js_UngetToken(JSTokenStream *ts) +{ + JS_ASSERT(ts->lookahead < NTOKENS_MASK); + if (ts->flags & TSF_ERROR) + return; + ts->lookahead++; + ts->cursor = (ts->cursor - 1) & NTOKENS_MASK; +} + +JSBool +js_MatchToken(JSContext *cx, JSTokenStream *ts, JSTokenType tt) +{ + if (js_GetToken(cx, ts) == tt) + return JS_TRUE; + js_UngetToken(ts); + return JS_FALSE; +} diff --git a/src/extension/script/js/jsscan.h b/src/extension/script/js/jsscan.h new file mode 100644 index 000000000..c61eff926 --- /dev/null +++ b/src/extension/script/js/jsscan.h @@ -0,0 +1,264 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsscan_h___ +#define jsscan_h___ +/* + * JS lexical scanner interface. + */ +#include +#include +#include "jsopcode.h" +#include "jsprvtd.h" +#include "jspubtd.h" + +JS_BEGIN_EXTERN_C + +typedef enum JSTokenType { + TOK_ERROR = -1, /* well-known as the only code < EOF */ + TOK_EOF = 0, /* end of file */ + TOK_EOL = 1, /* end of line */ + TOK_SEMI = 2, /* semicolon */ + TOK_COMMA = 3, /* comma operator */ + TOK_ASSIGN = 4, /* assignment ops (= += -= etc.) */ + TOK_HOOK = 5, TOK_COLON = 6, /* conditional (?:) */ + TOK_OR = 7, /* logical or (||) */ + TOK_AND = 8, /* logical and (&&) */ + TOK_BITOR = 9, /* bitwise-or (|) */ + TOK_BITXOR = 10, /* bitwise-xor (^) */ + TOK_BITAND = 11, /* bitwise-and (&) */ + TOK_EQOP = 12, /* equality ops (== !=) */ + TOK_RELOP = 13, /* relational ops (< <= > >=) */ + TOK_SHOP = 14, /* shift ops (<< >> >>>) */ + TOK_PLUS = 15, /* plus */ + TOK_MINUS = 16, /* minus */ + TOK_STAR = 17, TOK_DIVOP = 18, /* multiply/divide ops (* / %) */ + TOK_UNARYOP = 19, /* unary prefix operator */ + TOK_INC = 20, TOK_DEC = 21, /* increment/decrement (++ --) */ + TOK_DOT = 22, /* member operator (.) */ + TOK_LB = 23, TOK_RB = 24, /* left and right brackets */ + TOK_LC = 25, TOK_RC = 26, /* left and right curlies (braces) */ + TOK_LP = 27, TOK_RP = 28, /* left and right parentheses */ + TOK_NAME = 29, /* identifier */ + TOK_NUMBER = 30, /* numeric constant */ + TOK_STRING = 31, /* string constant */ + TOK_OBJECT = 32, /* RegExp or other object constant */ + TOK_PRIMARY = 33, /* true, false, null, this, super */ + TOK_FUNCTION = 34, /* function keyword */ + TOK_EXPORT = 35, /* export keyword */ + TOK_IMPORT = 36, /* import keyword */ + TOK_IF = 37, /* if keyword */ + TOK_ELSE = 38, /* else keyword */ + TOK_SWITCH = 39, /* switch keyword */ + TOK_CASE = 40, /* case keyword */ + TOK_DEFAULT = 41, /* default keyword */ + TOK_WHILE = 42, /* while keyword */ + TOK_DO = 43, /* do keyword */ + TOK_FOR = 44, /* for keyword */ + TOK_BREAK = 45, /* break keyword */ + TOK_CONTINUE = 46, /* continue keyword */ + TOK_IN = 47, /* in keyword */ + TOK_VAR = 48, /* var keyword */ + TOK_WITH = 49, /* with keyword */ + TOK_RETURN = 50, /* return keyword */ + TOK_NEW = 51, /* new keyword */ + TOK_DELETE = 52, /* delete keyword */ + TOK_DEFSHARP = 53, /* #n= for object/array initializers */ + TOK_USESHARP = 54, /* #n# for object/array initializers */ + TOK_TRY = 55, /* try keyword */ + TOK_CATCH = 56, /* catch keyword */ + TOK_FINALLY = 57, /* finally keyword */ + TOK_THROW = 58, /* throw keyword */ + TOK_INSTANCEOF = 59, /* instanceof keyword */ + TOK_DEBUGGER = 60, /* debugger keyword */ + TOK_RESERVED, /* reserved keywords */ + TOK_LIMIT /* domain size */ +} JSTokenType; + +#define IS_PRIMARY_TOKEN(tt) \ + ((uintN)((tt) - TOK_NAME) <= (uintN)(TOK_PRIMARY - TOK_NAME)) + +struct JSTokenPtr { + uint16 index; /* index of char in physical line */ + uint16 lineno; /* physical line number */ +}; + +struct JSTokenPos { + JSTokenPtr begin; /* first character and line of token */ + JSTokenPtr end; /* index 1 past last char, last line */ +}; + +struct JSToken { + JSTokenType type; /* char value or above enumerator */ + JSTokenPos pos; /* token position in file */ + jschar *ptr; /* beginning of token in line buffer */ + union { + struct { + JSOp op; /* operator, for minimal parser */ + JSAtom *atom; /* atom table entry */ + } s; + jsdouble dval; /* floating point number */ + } u; +}; + +#define t_op u.s.op +#define t_atom u.s.atom +#define t_dval u.dval + +typedef struct JSTokenBuf { + jschar *base; /* base of line or stream buffer */ + jschar *limit; /* limit for quick bounds check */ + jschar *ptr; /* next char to get, or slot to use */ +} JSTokenBuf; + +#define JS_LINE_LIMIT 256 /* logical line buffer size limit -- + physical line length is unlimited */ +#define NTOKENS 4 /* 1 current + 2 lookahead, rounded */ +#define NTOKENS_MASK (NTOKENS-1) /* to power of 2 to avoid divmod by 3 */ + +struct JSTokenStream { + JSToken tokens[NTOKENS];/* circular token buffer */ + uintN cursor; /* index of last parsed token */ + uintN lookahead; /* count of lookahead tokens */ + uintN lineno; /* current line number */ + uintN ungetpos; /* next free char slot in ungetbuf */ + jschar ungetbuf[6]; /* at most 6, for \uXXXX lookahead */ + uintN flags; /* flags -- see below */ + ptrdiff_t linelen; /* physical linebuf segment length */ + ptrdiff_t linepos; /* linebuf offset in physical line */ + JSTokenBuf linebuf; /* line buffer for diagnostics */ + JSTokenBuf userbuf; /* user input buffer if !file */ + JSTokenBuf tokenbuf; /* current token string buffer */ + const char *filename; /* input filename or null */ + FILE *file; /* stdio stream if reading from file */ + JSPrincipals *principals; /* principals associated with source */ + JSSourceHandler listener; /* callback for source; eg debugger */ + void *listenerData; /* listener 'this' data */ + void *listenerTSData;/* listener data for this TokenStream */ +}; + +#define CURRENT_TOKEN(ts) ((ts)->tokens[(ts)->cursor]) +#define ON_CURRENT_LINE(ts,pos) ((uint16)(ts)->lineno == (pos).end.lineno) + +/* JSTokenStream flags */ +#define TSF_ERROR 0x01 /* fatal error while compiling */ +#define TSF_EOF 0x02 /* hit end of file */ +#define TSF_NEWLINES 0x04 /* tokenize newlines */ +#define TSF_REGEXP 0x08 /* looking for a regular expression */ +#define TSF_NLFLAG 0x20 /* last linebuf ended with \n */ +#define TSF_CRFLAG 0x40 /* linebuf would have ended with \r */ +#define TSF_DIRTYLINE 0x80 /* stuff other than whitespace since start of line */ + +/* Unicode separators that are treated as line terminators, in addition to \n, \r */ +#define LINE_SEPARATOR 0x2028 +#define PARA_SEPARATOR 0x2029 + +/* + * Create a new token stream, either from an input buffer or from a file. + * Return null on file-open or memory-allocation failure. + * + * NB: All of js_New{,Buffer,File}TokenStream() return a pointer to transient + * memory in the current context's temp pool. This memory is deallocated via + * JS_ARENA_RELEASE() after parsing is finished. + */ +extern JSTokenStream * +js_NewTokenStream(JSContext *cx, const jschar *base, size_t length, + const char *filename, uintN lineno, JSPrincipals *principals); + +extern JS_FRIEND_API(JSTokenStream *) +js_NewBufferTokenStream(JSContext *cx, const jschar *base, size_t length); + +extern JS_FRIEND_API(JSTokenStream *) +js_NewFileTokenStream(JSContext *cx, const char *filename, FILE *defaultfp); + +extern JS_FRIEND_API(JSBool) +js_CloseTokenStream(JSContext *cx, JSTokenStream *ts); + +/* + * Initialize the scanner, installing JS keywords into cx's global scope. + */ +extern JSBool +js_InitScanner(JSContext *cx); + +/* + * Friend-exported API entry point to call a mapping function on each reserved + * identifier in the scanner's keyword table. + */ +extern JS_FRIEND_API(void) +js_MapKeywords(void (*mapfun)(const char *)); + +/* + * Report a compile-time error by its number, using ts or cg to show context. + * Return true for a warning, false for an error. + */ +extern JSBool +js_ReportCompileErrorNumber(JSContext *cx, JSTokenStream *ts, + JSCodeGenerator *cg, uintN flags, + const uintN errorNumber, ...); + +/* + * Look ahead one token and return its type. + */ +extern JSTokenType +js_PeekToken(JSContext *cx, JSTokenStream *ts); + +extern JSTokenType +js_PeekTokenSameLine(JSContext *cx, JSTokenStream *ts); + +/* + * Get the next token from ts. + */ +extern JSTokenType +js_GetToken(JSContext *cx, JSTokenStream *ts); + +/* + * Push back the last scanned token onto ts. + */ +extern void +js_UngetToken(JSTokenStream *ts); + +/* + * Get the next token from ts if its type is tt. + */ +extern JSBool +js_MatchToken(JSContext *cx, JSTokenStream *ts, JSTokenType tt); + +JS_END_EXTERN_C + +#endif /* jsscan_h___ */ diff --git a/src/extension/script/js/jsscope.c b/src/extension/script/js/jsscope.c new file mode 100644 index 000000000..20243e9b7 --- /dev/null +++ b/src/extension/script/js/jsscope.c @@ -0,0 +1,1581 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS symbol tables. + */ +#include "jsstddef.h" +#include +#include +#include "jstypes.h" +#include "jsarena.h" +#include "jsbit.h" +#include "jsclist.h" +#include "jsdhash.h" +#include "jsutil.h" /* Added by JSIFY */ +#include "jsapi.h" +#include "jsatom.h" +#include "jscntxt.h" +#include "jsdbgapi.h" +#include "jslock.h" +#include "jsnum.h" +#include "jsscope.h" +#include "jsstr.h" + +JSScope * +js_GetMutableScope(JSContext *cx, JSObject *obj) +{ + JSScope *scope, *newscope; + + scope = OBJ_SCOPE(obj); + JS_ASSERT(JS_IS_SCOPE_LOCKED(cx, scope)); + if (scope->object == obj) + return scope; + newscope = js_NewScope(cx, 0, scope->map.ops, LOCKED_OBJ_GET_CLASS(obj), + obj); + if (!newscope) + return NULL; + JS_LOCK_SCOPE(cx, newscope); + obj->map = js_HoldObjectMap(cx, &newscope->map); + scope = (JSScope *) js_DropObjectMap(cx, &scope->map, obj); + JS_TRANSFER_SCOPE_LOCK(cx, scope, newscope); + return newscope; +} + +/* + * JSScope uses multiplicative hashing, _a la_ jsdhash.[ch], but specialized + * to minimize footprint. But if a scope has fewer than SCOPE_HASH_THRESHOLD + * entries, we use linear search and avoid allocating scope->table. + */ +#define SCOPE_HASH_THRESHOLD 6 +#define MIN_SCOPE_SIZE_LOG2 4 +#define MIN_SCOPE_SIZE JS_BIT(MIN_SCOPE_SIZE_LOG2) +#define SCOPE_TABLE_NBYTES(n) ((n) * sizeof(JSScopeProperty *)) + +static void +InitMinimalScope(JSScope *scope) +{ + scope->hashShift = JS_DHASH_BITS - MIN_SCOPE_SIZE_LOG2; + scope->entryCount = scope->removedCount = 0; + scope->table = NULL; + scope->lastProp = NULL; +} + +static JSBool +CreateScopeTable(JSScope *scope) +{ + int sizeLog2; + JSScopeProperty *sprop, **spp; + + JS_ASSERT(!scope->table); + JS_ASSERT(scope->lastProp); + + if (scope->entryCount > SCOPE_HASH_THRESHOLD) { + /* + * Ouch: calloc failed at least once already -- let's try again, + * overallocating to hold at least twice the current population. + */ + sizeLog2 = JS_CeilingLog2(2 * scope->entryCount); + scope->hashShift = JS_DHASH_BITS - sizeLog2; + } else { + JS_ASSERT(scope->hashShift == JS_DHASH_BITS - MIN_SCOPE_SIZE_LOG2); + sizeLog2 = MIN_SCOPE_SIZE_LOG2; + } + + scope->table = (JSScopeProperty **) + calloc(JS_BIT(sizeLog2), sizeof(JSScopeProperty *)); + if (!scope->table) + return JS_FALSE; + + scope->hashShift = JS_DHASH_BITS - sizeLog2; + for (sprop = scope->lastProp; sprop; sprop = sprop->parent) { + spp = js_SearchScope(scope, sprop->id, JS_TRUE); + SPROP_STORE_PRESERVING_COLLISION(spp, sprop); + } + return JS_TRUE; +} + +JSScope * +js_NewScope(JSContext *cx, jsrefcount nrefs, JSObjectOps *ops, JSClass *clasp, + JSObject *obj) +{ + JSScope *scope; + + scope = (JSScope *) JS_malloc(cx, sizeof(JSScope)); + if (!scope) + return NULL; + + js_InitObjectMap(&scope->map, nrefs, ops, clasp); + scope->object = obj; + scope->flags = 0; + InitMinimalScope(scope); + +#ifdef JS_THREADSAFE + scope->ownercx = cx; + memset(&scope->lock, 0, sizeof scope->lock); + + /* + * Set u.link = NULL, not u.count = 0, in case the target architecture's + * null pointer has a non-zero integer representation. + */ + scope->u.link = NULL; + +#ifdef DEBUG + scope->file[0] = scope->file[1] = scope->file[2] = scope->file[3] = NULL; + scope->line[0] = scope->line[1] = scope->line[2] = scope->line[3] = 0; +#endif +#endif + + JS_RUNTIME_METER(cx->runtime, liveScopes); + JS_RUNTIME_METER(cx->runtime, totalScopes); + return scope; +} + +#ifdef DEBUG_SCOPE_COUNT +extern void +js_unlog_scope(JSScope *scope); +#endif + +void +js_DestroyScope(JSContext *cx, JSScope *scope) +{ +#ifdef DEBUG_SCOPE_COUNT + js_unlog_scope(scope); +#endif + +#ifdef JS_THREADSAFE + /* Scope must be single-threaded at this point, so set scope->ownercx. */ + JS_ASSERT(scope->u.count == 0); + scope->ownercx = cx; + js_FinishLock(&scope->lock); +#endif + if (scope->table) + JS_free(cx, scope->table); + +#ifdef DEBUG + JS_LOCK_RUNTIME_VOID(cx->runtime, + cx->runtime->liveScopeProps -= scope->entryCount); +#endif + JS_RUNTIME_UNMETER(cx->runtime, liveScopes); + JS_free(cx, scope); +} + +#ifdef DEBUG_brendan +typedef struct JSScopeStats { + jsrefcount searches; + jsrefcount steps; + jsrefcount hits; + jsrefcount misses; + jsrefcount stepHits; + jsrefcount stepMisses; + jsrefcount adds; + jsrefcount redundantAdds; + jsrefcount addFailures; + jsrefcount changeFailures; + jsrefcount compresses; + jsrefcount grows; + jsrefcount removes; + jsrefcount removeFrees; + jsrefcount uselessRemoves; + jsrefcount shrinks; +} JSScopeStats; + +JS_FRIEND_DATA(JSScopeStats) js_scope_stats; + +# define METER(x) JS_ATOMIC_INCREMENT(&js_scope_stats.x) +#else +# define METER(x) /* nothing */ +#endif + +/* + * Double hashing needs the second hash code to be relatively prime to table + * size, so we simply make hash2 odd. The inputs to multiplicative hash are + * the golden ratio, expressed as a fixed-point 32 bit fraction, and the int + * property index or named property's atom number (observe that most objects + * have either no indexed properties, or almost all indexed and a few names, + * so collisions between index and atom number are unlikely). + */ +#define SCOPE_HASH0(id) (HASH_ID(id) * JS_GOLDEN_RATIO) +#define SCOPE_HASH1(hash0,shift) ((hash0) >> (shift)) +#define SCOPE_HASH2(hash0,log2,shift) ((((hash0) << (log2)) >> (shift)) | 1) + +JS_FRIEND_API(JSScopeProperty **) +js_SearchScope(JSScope *scope, jsid id, JSBool adding) +{ + JSHashNumber hash0, hash1, hash2; + int hashShift, sizeLog2; + JSScopeProperty *stored, *sprop, **spp, **firstRemoved; + uint32 sizeMask; + + METER(searches); + if (!scope->table) { + /* Not enough properties to justify hashing: search from lastProp. */ + JS_ASSERT(!SCOPE_HAD_MIDDLE_DELETE(scope)); + for (spp = &scope->lastProp; (sprop = *spp); spp = &sprop->parent) { + if (sprop->id == id) { + METER(hits); + return spp; + } + } + METER(misses); + return spp; + } + + /* Compute the primary hash address. */ + hash0 = SCOPE_HASH0(id); + hashShift = scope->hashShift; + hash1 = SCOPE_HASH1(hash0, hashShift); + spp = scope->table + hash1; + + /* Miss: return space for a new entry. */ + stored = *spp; + if (SPROP_IS_FREE(stored)) { + METER(misses); + return spp; + } + + /* Hit: return entry. */ + sprop = SPROP_CLEAR_COLLISION(stored); + if (sprop && sprop->id == id) { + METER(hits); + return spp; + } + + /* Collision: double hash. */ + sizeLog2 = JS_DHASH_BITS - hashShift; + hash2 = SCOPE_HASH2(hash0, sizeLog2, hashShift); + sizeMask = JS_BITMASK(sizeLog2); + + /* Save the first removed entry pointer so we can recycle it if adding. */ + if (SPROP_IS_REMOVED(stored)) { + firstRemoved = spp; + } else { + firstRemoved = NULL; + if (adding && !SPROP_HAD_COLLISION(stored)) + SPROP_FLAG_COLLISION(spp, sprop); + } + + for (;;) { + METER(steps); + hash1 -= hash2; + hash1 &= sizeMask; + spp = scope->table + hash1; + + stored = *spp; + if (SPROP_IS_FREE(stored)) { + METER(stepMisses); + return (adding && firstRemoved) ? firstRemoved : spp; + } + + sprop = SPROP_CLEAR_COLLISION(stored); + if (sprop && sprop->id == id) { + METER(stepHits); + return spp; + } + + if (SPROP_IS_REMOVED(stored)) { + if (!firstRemoved) + firstRemoved = spp; + } else { + if (adding && !SPROP_HAD_COLLISION(stored)) + SPROP_FLAG_COLLISION(spp, sprop); + } + } + + /* NOTREACHED */ + return NULL; +} + +static JSBool +ChangeScope(JSContext *cx, JSScope *scope, int change) +{ + int oldlog2, newlog2; + uint32 oldsize, newsize, nbytes; + JSScopeProperty **table, **oldtable, **spp, **oldspp, *sprop; + + /* Grow, shrink, or compress by changing scope->table. */ + oldlog2 = JS_DHASH_BITS - scope->hashShift; + newlog2 = oldlog2 + change; + oldsize = JS_BIT(oldlog2); + newsize = JS_BIT(newlog2); + nbytes = SCOPE_TABLE_NBYTES(newsize); + table = (JSScopeProperty **) calloc(nbytes, 1); + if (!table) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + + /* Now that we have a new table allocated, update scope members. */ + scope->hashShift = JS_DHASH_BITS - newlog2; + scope->removedCount = 0; + oldtable = scope->table; + scope->table = table; + + /* Copy only live entries, leaving removed and free ones behind. */ + for (oldspp = oldtable; oldsize != 0; oldspp++) { + sprop = SPROP_FETCH(oldspp); + if (sprop) { + spp = js_SearchScope(scope, sprop->id, JS_TRUE); + JS_ASSERT(SPROP_IS_FREE(*spp)); + *spp = sprop; + } + oldsize--; + } + + /* Finally, free the old table storage. */ + JS_free(cx, oldtable); + return JS_TRUE; +} + +/* + * Take care to exclude the mark and duplicate bits, in case we're called from + * the GC, or we are searching for a property that has not yet been flagged as + * a duplicate when making a duplicate formal parameter. + */ +#define SPROP_FLAGS_NOT_MATCHED (SPROP_MARK | SPROP_IS_DUPLICATE) + +JS_STATIC_DLL_CALLBACK(JSDHashNumber) +js_HashScopeProperty(JSDHashTable *table, const void *key) +{ + const JSScopeProperty *sprop = (const JSScopeProperty *)key; + JSDHashNumber hash; + JSPropertyOp gsop; + + /* Accumulate from least to most random so the low bits are most random. */ + hash = 0; + gsop = sprop->getter; + if (gsop) + hash = (hash >> (JS_DHASH_BITS - 4)) ^ (hash << 4) ^ (jsword)gsop; + gsop = sprop->setter; + if (gsop) + hash = (hash >> (JS_DHASH_BITS - 4)) ^ (hash << 4) ^ (jsword)gsop; + + hash = (hash >> (JS_DHASH_BITS - 4)) ^ (hash << 4) + ^ (sprop->flags & ~SPROP_FLAGS_NOT_MATCHED); + + hash = (hash >> (JS_DHASH_BITS - 4)) ^ (hash << 4) ^ sprop->attrs; + hash = (hash >> (JS_DHASH_BITS - 4)) ^ (hash << 4) ^ sprop->shortid; + hash = (hash >> (JS_DHASH_BITS - 4)) ^ (hash << 4) ^ sprop->slot; + hash = (hash >> (JS_DHASH_BITS - 4)) ^ (hash << 4) ^ sprop->id; + return hash; +} + +#define SPROP_MATCH(sprop, child) \ + SPROP_MATCH_PARAMS(sprop, (child)->id, (child)->getter, (child)->setter, \ + (child)->slot, (child)->attrs, (child)->flags, \ + (child)->shortid) + +#define SPROP_MATCH_PARAMS(sprop, aid, agetter, asetter, aslot, aattrs, \ + aflags, ashortid) \ + ((sprop)->id == (aid) && \ + SPROP_MATCH_PARAMS_AFTER_ID(sprop, agetter, asetter, aslot, aattrs, \ + aflags, ashortid)) + +#define SPROP_MATCH_PARAMS_AFTER_ID(sprop, agetter, asetter, aslot, aattrs, \ + aflags, ashortid) \ + ((sprop)->getter == (agetter) && \ + (sprop)->setter == (asetter) && \ + (sprop)->slot == (aslot) && \ + (sprop)->attrs == (aattrs) && \ + (((sprop)->flags ^ (aflags)) & ~SPROP_FLAGS_NOT_MATCHED) == 0 && \ + (sprop)->shortid == (ashortid)) + +JS_STATIC_DLL_CALLBACK(JSBool) +js_MatchScopeProperty(JSDHashTable *table, + const JSDHashEntryHdr *hdr, + const void *key) +{ + const JSPropertyTreeEntry *entry = (const JSPropertyTreeEntry *)hdr; + const JSScopeProperty *sprop = entry->child; + const JSScopeProperty *kprop = (const JSScopeProperty *)key; + + return SPROP_MATCH(sprop, kprop); +} + +static const JSDHashTableOps PropertyTreeHashOps = { + JS_DHashAllocTable, + JS_DHashFreeTable, + JS_DHashGetKeyStub, + js_HashScopeProperty, + js_MatchScopeProperty, + JS_DHashMoveEntryStub, + JS_DHashClearEntryStub, + JS_DHashFinalizeStub, + NULL +}; + +/* + * A property tree node on rt->propertyFreeList overlays the following prefix + * struct on JSScopeProperty. + */ +typedef struct FreeNode { + jsid id; + JSScopeProperty *next; + JSScopeProperty **prevp; +} FreeNode; + +#define FREENODE(sprop) ((FreeNode *) (sprop)) + +#define FREENODE_INSERT(list, sprop) \ + JS_BEGIN_MACRO \ + FREENODE(sprop)->next = (list); \ + FREENODE(sprop)->prevp = &(list); \ + if (list) \ + FREENODE(list)->prevp = &FREENODE(sprop)->next; \ + (list) = (sprop); \ + JS_END_MACRO + +#define FREENODE_REMOVE(sprop) \ + JS_BEGIN_MACRO \ + *FREENODE(sprop)->prevp = FREENODE(sprop)->next; \ + if (FREENODE(sprop)->next) \ + FREENODE(FREENODE(sprop)->next)->prevp = FREENODE(sprop)->prevp; \ + JS_END_MACRO + +/* NB: Called with the runtime lock held. */ +static JSScopeProperty * +NewScopeProperty(JSRuntime *rt) +{ + JSScopeProperty *sprop; + + sprop = rt->propertyFreeList; + if (sprop) { + FREENODE_REMOVE(sprop); + } else { + JS_ARENA_ALLOCATE_CAST(sprop, JSScopeProperty *, + &rt->propertyArenaPool, + sizeof(JSScopeProperty)); + if (!sprop) + return NULL; + } + + JS_RUNTIME_METER(rt, livePropTreeNodes); + JS_RUNTIME_METER(rt, totalPropTreeNodes); + return sprop; +} + +#define CHUNKY_KIDS_TAG ((jsuword)1) +#define KIDS_IS_CHUNKY(kids) ((jsuword)(kids) & CHUNKY_KIDS_TAG) +#define KIDS_TO_CHUNK(kids) ((PropTreeKidsChunk *) \ + ((jsuword)(kids) & ~CHUNKY_KIDS_TAG)) +#define CHUNK_TO_KIDS(chunk) ((JSScopeProperty *) \ + ((jsuword)(chunk) | CHUNKY_KIDS_TAG)) +#define MAX_KIDS_PER_CHUNK 10 + +typedef struct PropTreeKidsChunk PropTreeKidsChunk; + +struct PropTreeKidsChunk { + JSScopeProperty *kids[MAX_KIDS_PER_CHUNK]; + PropTreeKidsChunk *next; +}; + +static PropTreeKidsChunk * +NewPropTreeKidsChunk(JSRuntime *rt) +{ + PropTreeKidsChunk *chunk; + + chunk = calloc(1, sizeof *chunk); + if (!chunk) + return NULL; + JS_ASSERT(((jsuword)chunk & CHUNKY_KIDS_TAG) == 0); + JS_RUNTIME_METER(rt, propTreeKidsChunks); + return chunk; +} + +static void +DestroyPropTreeKidsChunk(JSRuntime *rt, PropTreeKidsChunk *chunk) +{ + JS_RUNTIME_UNMETER(rt, propTreeKidsChunks); + free(chunk); +} + +/* NB: Called with the runtime lock held. */ +static JSBool +InsertPropertyTreeChild(JSRuntime *rt, JSScopeProperty *parent, + JSScopeProperty *child) +{ + JSPropertyTreeEntry *entry; + JSScopeProperty **childp, *kids, *sprop; + PropTreeKidsChunk *chunk, **chunkp; + uintN i; + + JS_ASSERT(!parent || child->parent != parent); + + if (!parent) { + entry = (JSPropertyTreeEntry *) + JS_DHashTableOperate(&rt->propertyTreeHash, child, JS_DHASH_ADD); + if (!entry) + return JS_FALSE; + childp = &entry->child; + sprop = *childp; + if (!sprop) { + *childp = child; + } else { + /* + * A "Duplicate child" case. + * + * We can't do away with child, as at least one live scope entry + * still points at it. What's more, that scope's lastProp chains + * through an ancestor line to reach child, and js_Enumerate and + * others count on this linkage. We must leave child out of the + * hash table, and not require it to be there when we eventually + * GC it (see RemovePropertyTreeChild, below). + * + * It is necessary to leave the duplicate child out of the hash + * table to preserve entry uniqueness. It is safe to leave the + * child out of the hash table (unlike the duplicate child cases + * below), because the child's parent link will be null, which + * can't dangle. + */ + JS_ASSERT(sprop != child && SPROP_MATCH(sprop, child)); + JS_RUNTIME_METER(rt, duplicatePropTreeNodes); + } + } else { + childp = &parent->kids; + kids = *childp; + if (kids) { + if (KIDS_IS_CHUNKY(kids)) { + chunk = KIDS_TO_CHUNK(kids); + do { + for (i = 0; i < MAX_KIDS_PER_CHUNK; i++) { + childp = &chunk->kids[i]; + sprop = *childp; + if (!sprop) + goto insert; + + JS_ASSERT(sprop != child); + if (SPROP_MATCH(sprop, child)) { + /* + * Duplicate child, see comment above. In this + * case, we must let the duplicate be inserted at + * this level in the tree, so we keep iterating, + * looking for an empty slot in which to insert. + */ + JS_ASSERT(sprop != child); + JS_RUNTIME_METER(rt, duplicatePropTreeNodes); + } + } + chunkp = &chunk->next; + } while ((chunk = *chunkp) != NULL); + + chunk = NewPropTreeKidsChunk(rt); + if (!chunk) + return JS_FALSE; + *chunkp = chunk; + childp = &chunk->kids[0]; + } else { + sprop = kids; + JS_ASSERT(sprop != child); + if (SPROP_MATCH(sprop, child)) { + /* + * Duplicate child, see comment above. Once again, we + * must let duplicates created by deletion pile up in a + * kids-chunk-list, in order to find them when sweeping + * and thereby avoid dangling parent pointers. + */ + JS_RUNTIME_METER(rt, duplicatePropTreeNodes); + } + + chunk = NewPropTreeKidsChunk(rt); + if (!chunk) + return JS_FALSE; + parent->kids = CHUNK_TO_KIDS(chunk); + chunk->kids[0] = sprop; + childp = &chunk->kids[1]; + } + } + insert: + *childp = child; + } + + child->parent = parent; + return JS_TRUE; +} + +/* NB: Called with the runtime lock held. */ +static void +RemovePropertyTreeChild(JSRuntime *rt, JSScopeProperty *child) +{ + JSPropertyTreeEntry *entry; + JSScopeProperty *parent, *kids, *kid; + PropTreeKidsChunk *list, *chunk, **chunkp, *lastChunk; + uintN i, j; + + parent = child->parent; + if (!parent) { + /* + * Don't remove child if it is not in rt->propertyTreeHash, but only + * matches a root child in the table that has compatible members. See + * the "Duplicate child" comments in InsertPropertyTreeChild, above. + */ + entry = (JSPropertyTreeEntry *) + JS_DHashTableOperate(&rt->propertyTreeHash, child, JS_DHASH_LOOKUP); + + if (entry->child == child) + JS_DHashTableRawRemove(&rt->propertyTreeHash, &entry->hdr); + } else { + kids = parent->kids; + if (KIDS_IS_CHUNKY(kids)) { + list = chunk = KIDS_TO_CHUNK(kids); + chunkp = &list; + + do { + for (i = 0; i < MAX_KIDS_PER_CHUNK; i++) { + if (chunk->kids[i] == child) { + lastChunk = chunk; + if (!lastChunk->next) { + j = i + 1; + } else { + j = 0; + do { + chunkp = &lastChunk->next; + lastChunk = *chunkp; + } while (lastChunk->next); + } + for (; j < MAX_KIDS_PER_CHUNK; j++) { + if (!lastChunk->kids[j]) + break; + } + --j; + if (chunk != lastChunk || j > i) + chunk->kids[i] = lastChunk->kids[j]; + lastChunk->kids[j] = NULL; + if (j == 0) { + *chunkp = NULL; + if (!list) + parent->kids = NULL; + DestroyPropTreeKidsChunk(rt, lastChunk); + } + return; + } + } + + chunkp = &chunk->next; + } while ((chunk = *chunkp) != NULL); + } else { + kid = kids; + if (kid == child) + parent->kids = NULL; + } + } +} + +/* + * Called *without* the runtime lock held, this function acquires that lock + * only when inserting a new child. Thus there may be races to find or add + * a node that result in duplicates. We expect such races to be rare! + */ +static JSScopeProperty * +GetPropertyTreeChild(JSContext *cx, JSScopeProperty *parent, + JSScopeProperty *child) +{ + JSRuntime *rt; + JSPropertyTreeEntry *entry; + JSScopeProperty *sprop; + PropTreeKidsChunk *chunk; + uintN i; + + rt = cx->runtime; + if (!parent) { + JS_LOCK_RUNTIME(rt); + + entry = (JSPropertyTreeEntry *) + JS_DHashTableOperate(&rt->propertyTreeHash, child, JS_DHASH_ADD); + if (!entry) + goto out_of_memory; + + sprop = entry->child; + if (sprop) + goto out; + } else { + /* + * Because chunks are appended at the end and never deleted except by + * the GC, we can search without taking the runtime lock. We may miss + * a matching sprop added by another thread, and make a duplicate one, + * but that is an unlikely, therefore small, cost. The property tree + * has extremely low fan-out below its root in popular embeddings with + * real-world workloads. + * + * If workload changes so as to increase fan-out significantly below + * the property tree root, we'll want to add another tag bit stored in + * parent->kids that indicates a JSDHashTable pointer. + */ + entry = NULL; + sprop = parent->kids; + if (sprop) { + if (KIDS_IS_CHUNKY(sprop)) { + chunk = KIDS_TO_CHUNK(sprop); + do { + for (i = 0; i < MAX_KIDS_PER_CHUNK; i++) { + sprop = chunk->kids[i]; + if (!sprop) + goto not_found; + + if (SPROP_MATCH(sprop, child)) + return sprop; + } + } while ((chunk = chunk->next) != NULL); + } else { + if (SPROP_MATCH(sprop, child)) + return sprop; + } + } + + not_found: + JS_LOCK_RUNTIME(rt); + } + + sprop = NewScopeProperty(rt); + if (!sprop) + goto out_of_memory; + + sprop->id = child->id; + sprop->getter = child->getter; + sprop->setter = child->setter; + sprop->slot = child->slot; + sprop->attrs = child->attrs; + sprop->flags = child->flags; + sprop->shortid = child->shortid; + sprop->parent = sprop->kids = NULL; + if (!parent) { + entry->child = sprop; + } else { + if (!InsertPropertyTreeChild(rt, parent, sprop)) + goto out_of_memory; + } + +out: + JS_UNLOCK_RUNTIME(rt); + return sprop; + +out_of_memory: + JS_UNLOCK_RUNTIME(rt); + JS_ReportOutOfMemory(cx); + return NULL; +} + +#ifdef DEBUG_notbrendan +#define CHECK_ANCESTOR_LINE(scope, sparse) \ + JS_BEGIN_MACRO \ + if ((scope)->table) CheckAncestorLine(scope, sparse); \ + JS_END_MACRO + +static void +CheckAncestorLine(JSScope *scope, JSBool sparse) +{ + uint32 size; + JSScopeProperty **spp, **start, **end, *ancestorLine, *sprop, *aprop; + uint32 entryCount, ancestorCount; + + ancestorLine = SCOPE_LAST_PROP(scope); + if (ancestorLine) + JS_ASSERT(SCOPE_HAS_PROPERTY(scope, ancestorLine)); + + entryCount = 0; + size = SCOPE_CAPACITY(scope); + start = scope->table; + for (spp = start, end = start + size; spp < end; spp++) { + sprop = SPROP_FETCH(spp); + if (sprop) { + entryCount++; + for (aprop = ancestorLine; aprop; aprop = aprop->parent) { + if (aprop == sprop) + break; + } + JS_ASSERT(aprop); + } + } + JS_ASSERT(entryCount == scope->entryCount); + + ancestorCount = 0; + for (sprop = ancestorLine; sprop; sprop = sprop->parent) { + if (SCOPE_HAD_MIDDLE_DELETE(scope) && + !SCOPE_HAS_PROPERTY(scope, sprop)) { + JS_ASSERT(sparse || (sprop->flags & SPROP_IS_DUPLICATE)); + continue; + } + ancestorCount++; + } + JS_ASSERT(ancestorCount == scope->entryCount); +} +#else +#define CHECK_ANCESTOR_LINE(scope, sparse) /* nothing */ +#endif + +static void +ReportReadOnlyScope(JSContext *cx, JSScope *scope) +{ + JSString *str; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(scope->object)); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_READ_ONLY, + str + ? JS_GetStringBytes(str) + : LOCKED_OBJ_GET_CLASS(scope->object)->name); +} + +JSScopeProperty * +js_AddScopeProperty(JSContext *cx, JSScope *scope, jsid id, + JSPropertyOp getter, JSPropertyOp setter, uint32 slot, + uintN attrs, uintN flags, intN shortid) +{ + JSScopeProperty **spp, *sprop, *overwriting, **spvec, **spp2, child; + uint32 size, splen, i; + int change; + + JS_ASSERT(JS_IS_SCOPE_LOCKED(cx, scope)); + CHECK_ANCESTOR_LINE(scope, JS_TRUE); + + /* + * You can't add properties to a sealed scope. But note well that you can + * change property attributes in a sealed scope, even though that replaces + * a JSScopeProperty * in the scope's hash table -- but no id is added, so + * the scope remains sealed. + */ + if (SCOPE_IS_SEALED(scope)) { + ReportReadOnlyScope(cx, scope); + return NULL; + } + + /* + * Normalize stub getter and setter values for faster is-stub testing in + * the SPROP_CALL_[GS]ETTER macros. + */ + if (getter == JS_PropertyStub) + getter = NULL; + if (setter == JS_PropertyStub) + setter = NULL; + + /* + * Search for id in order to claim its entry, allocating a property tree + * node if one doesn't already exist for our parameters. + */ + spp = js_SearchScope(scope, id, JS_TRUE); + sprop = overwriting = SPROP_FETCH(spp); + if (!sprop) { + /* Check whether we need to grow, if the load factor is >= .75. */ + size = SCOPE_CAPACITY(scope); + if (scope->entryCount + scope->removedCount >= size - (size >> 2)) { + if (scope->removedCount >= size >> 2) { + METER(compresses); + change = 0; + } else { + METER(grows); + change = 1; + } + if (!ChangeScope(cx, scope, change) && + scope->entryCount + scope->removedCount == size - 1) { + METER(addFailures); + return NULL; + } + spp = js_SearchScope(scope, id, JS_TRUE); + JS_ASSERT(!SPROP_FETCH(spp)); + } + } else { + /* Property exists: js_SearchScope must have returned a valid entry. */ + JS_ASSERT(!SPROP_IS_REMOVED(*spp)); + + /* + * If all property members match, this is a redundant add and we can + * return early. If the caller wants to allocate a slot, but doesn't + * care which slot, copy sprop->slot into slot so we can match sprop, + * if all other members match. + */ + if (!(attrs & JSPROP_SHARED) && + slot == SPROP_INVALID_SLOT && + SPROP_HAS_VALID_SLOT(sprop, scope)) { + slot = sprop->slot; + } + if (SPROP_MATCH_PARAMS_AFTER_ID(sprop, getter, setter, slot, attrs, + flags, shortid)) { + METER(redundantAdds); + return sprop; + } + + /* + * Duplicate formal parameters require us to leave the old property + * on the ancestor line, so the decompiler can find it, even though + * its entry in scope->table is overwritten to point at a new property + * descending from the old one. The SPROP_IS_DUPLICATE flag helps us + * cope with the consequent disparity between ancestor line height and + * scope->entryCount. + */ + if (flags & SPROP_IS_DUPLICATE) { + sprop->flags |= SPROP_IS_DUPLICATE; + } else { + /* + * If we are clearing sprop to force an existing property to be + * overwritten (apart from a duplicate formal parameter), we must + * unlink it from the ancestor line at scope->lastProp, lazily if + * sprop is not lastProp. And we must remove the entry at *spp, + * precisely so the lazy "middle delete" fixup code further below + * won't find sprop in scope->table, in spite of sprop being on + * the ancestor line. + * + * When we finally succeed in finding or creating a new sprop + * and storing its pointer at *spp, we'll use the |overwriting| + * local saved when we first looked up id to decide whether we're + * indeed creating a new entry, or merely overwriting an existing + * property. + */ + if (sprop == SCOPE_LAST_PROP(scope)) { + do { + SCOPE_REMOVE_LAST_PROP(scope); + if (!SCOPE_HAD_MIDDLE_DELETE(scope)) + break; + sprop = SCOPE_LAST_PROP(scope); + } while (sprop && !SCOPE_HAS_PROPERTY(scope, sprop)); + } else if (!SCOPE_HAD_MIDDLE_DELETE(scope)) { + /* + * If we have no hash table yet, we need one now. The middle + * delete code is simple-minded that way! + */ + if (!scope->table) { + if (!CreateScopeTable(scope)) { + JS_ReportOutOfMemory(cx); + return NULL; + } + spp = js_SearchScope(scope, id, JS_TRUE); + sprop = overwriting = SPROP_FETCH(spp); + } + SCOPE_SET_MIDDLE_DELETE(scope); + } + } + + /* + * If we fail later on trying to find or create a new sprop, we will + * goto fail_overwrite and restore *spp from |overwriting|. Note that + * we don't bother to keep scope->removedCount in sync, because we'll + * fix up *spp and scope->entryCount shortly, no matter how control + * flow returns from this function. + */ + if (scope->table) + SPROP_STORE_PRESERVING_COLLISION(spp, NULL); + scope->entryCount--; + CHECK_ANCESTOR_LINE(scope, JS_TRUE); + sprop = NULL; + } + + if (!sprop) { + /* + * If properties were deleted from the middle of the list starting at + * scope->lastProp, we may need to fork the property tree and squeeze + * all deleted properties out of scope's ancestor line. Otherwise we + * risk adding a node with the same id as a "middle" node, violating + * the rule that properties along an ancestor line have distinct ids + * (unless flagged SPROP_IS_DUPLICATE). + */ + if (SCOPE_HAD_MIDDLE_DELETE(scope)) { + JS_ASSERT(scope->table); + CHECK_ANCESTOR_LINE(scope, JS_TRUE); + + splen = scope->entryCount; + if (splen == 0) { + JS_ASSERT(scope->lastProp == NULL); + } else { + /* + * Enumerate live entries in scope->table using a temporary + * vector, by walking the (possibly sparse, due to deletions) + * ancestor line from scope->lastProp. + */ + spvec = (JSScopeProperty **) + JS_malloc(cx, SCOPE_TABLE_NBYTES(splen)); + if (!spvec) + goto fail_overwrite; + i = splen; + sprop = SCOPE_LAST_PROP(scope); + JS_ASSERT(sprop); + do { + /* + * NB: test SCOPE_GET_PROPERTY, not SCOPE_HAS_PROPERTY -- + * the latter insists that sprop->id maps to sprop, while + * the former simply tests whether sprop->id is bound in + * scope. We must allow for duplicate formal parameters + * along the ancestor line, and fork them as needed. + */ + if (!SCOPE_GET_PROPERTY(scope, sprop->id)) + continue; + + JS_ASSERT(sprop != overwriting); + if (i == 0) { + /* + * If our original splen estimate, scope->entryCount, + * is less than the ancestor line height, there must + * be duplicate formal parameters in this (function + * object) scope. Count remaining ancestors in order + * to realloc spvec. + */ + JSScopeProperty *tmp = sprop; + do { + if (SCOPE_GET_PROPERTY(scope, tmp->id)) + i++; + } while ((tmp = tmp->parent) != NULL); + spp2 = (JSScopeProperty **) + JS_realloc(cx, spvec, SCOPE_TABLE_NBYTES(splen+i)); + if (!spp2) { + JS_free(cx, spvec); + goto fail_overwrite; + } + + spvec = spp2; + memmove(spvec + i, spvec, SCOPE_TABLE_NBYTES(splen)); + splen += i; + } + + spvec[--i] = sprop; + } while ((sprop = sprop->parent) != NULL); + JS_ASSERT(i == 0); + + /* + * Now loop forward through spvec, forking the property tree + * whenever we see a "parent gap" due to deletions from scope. + * NB: sprop is null on first entry to the loop body. + */ + do { + if (spvec[i]->parent == sprop) { + sprop = spvec[i]; + } else { + sprop = GetPropertyTreeChild(cx, sprop, spvec[i]); + if (!sprop) { + JS_free(cx, spvec); + goto fail_overwrite; + } + + spp2 = js_SearchScope(scope, sprop->id, JS_FALSE); + JS_ASSERT(SPROP_FETCH(spp2) == spvec[i]); + SPROP_STORE_PRESERVING_COLLISION(spp2, sprop); + } + } while (++i < splen); + JS_free(cx, spvec); + + /* + * Now sprop points to the last property in scope, where the + * ancestor line from sprop to the root is dense w.r.t. scope: + * it contains no nodes not mapped by scope->table, apart from + * any stinking ECMA-mandated duplicate formal parameters. + */ + scope->lastProp = sprop; + CHECK_ANCESTOR_LINE(scope, JS_FALSE); + JS_RUNTIME_METER(cx->runtime, middleDeleteFixups); + } + + SCOPE_CLR_MIDDLE_DELETE(scope); + } + + /* + * Aliases share another property's slot, passed in the |slot| param. + * Shared properties have no slot. Unshared properties that do not + * alias another property's slot get one here, but may lose it due to + * a JS_ClearScope call. + */ + if (!(flags & SPROP_IS_ALIAS)) { + if (attrs & JSPROP_SHARED) { + slot = SPROP_INVALID_SLOT; + } else { + /* + * We may have set slot from a nearly-matching sprop, above. + * If so, we're overwriting that nearly-matching sprop, so we + * can reuse its slot -- we don't need to allocate a new one. + * Callers should therefore pass SPROP_INVALID_SLOT for all + * non-alias, unshared property adds. + */ + if (slot != SPROP_INVALID_SLOT) + JS_ASSERT(overwriting); + else if (!js_AllocSlot(cx, scope->object, &slot)) + goto fail_overwrite; + } + } + + /* + * Check for a watchpoint on a deleted property; if one exists, change + * setter to js_watch_set. + * XXXbe this could get expensive with lots of watchpoints... + */ + if (!JS_CLIST_IS_EMPTY(&cx->runtime->watchPointList) && + js_FindWatchPoint(cx->runtime, scope, id)) { + setter = js_WrapWatchedSetter(cx, id, attrs, setter); + if (!setter) + goto fail_overwrite; + } + + /* Find or create a property tree node labeled by our arguments. */ + child.id = id; + child.getter = getter; + child.setter = setter; + child.slot = slot; + child.attrs = attrs; + child.flags = flags; + child.shortid = shortid; + sprop = GetPropertyTreeChild(cx, scope->lastProp, &child); + if (!sprop) + goto fail_overwrite; + + /* Store the tree node pointer in the table entry for id. */ + if (scope->table) + SPROP_STORE_PRESERVING_COLLISION(spp, sprop); + scope->entryCount++; + scope->lastProp = sprop; + CHECK_ANCESTOR_LINE(scope, JS_FALSE); + if (!overwriting) { + JS_RUNTIME_METER(cx->runtime, liveScopeProps); + JS_RUNTIME_METER(cx->runtime, totalScopeProps); + } + + /* + * If we reach the hashing threshold, try to allocate scope->table. + * If we can't (a rare event, preceded by swapping to death on most + * modern OSes), stick with linear search rather than whining about + * this little set-back. Therefore we must test !scope->table and + * scope->entryCount >= SCOPE_HASH_THRESHOLD, not merely whether the + * entry count just reached the threshold. + */ + if (!scope->table && scope->entryCount >= SCOPE_HASH_THRESHOLD) + (void) CreateScopeTable(scope); + } + + METER(adds); + return sprop; + +fail_overwrite: + if (overwriting) { + /* + * We may or may not have forked overwriting out of scope's ancestor + * line, so we must check (the alternative is to set a flag above, but + * that hurts the common, non-error case). If we did fork overwriting + * out, we'll add it back at scope->lastProp. This means enumeration + * order can change due to a failure to overwrite an id. + * XXXbe very minor incompatibility + */ + for (sprop = SCOPE_LAST_PROP(scope); ; sprop = sprop->parent) { + if (!sprop) { + sprop = SCOPE_LAST_PROP(scope); + if (overwriting->parent == sprop) { + scope->lastProp = overwriting; + } else { + sprop = GetPropertyTreeChild(cx, sprop, overwriting); + if (sprop) { + JS_ASSERT(sprop != overwriting); + scope->lastProp = sprop; + } + overwriting = sprop; + } + break; + } + if (sprop == overwriting) + break; + } + if (overwriting) { + if (scope->table) + SPROP_STORE_PRESERVING_COLLISION(spp, overwriting); + scope->entryCount++; + } + CHECK_ANCESTOR_LINE(scope, JS_TRUE); + } + METER(addFailures); + return NULL; +} + +JSScopeProperty * +js_ChangeScopePropertyAttrs(JSContext *cx, JSScope *scope, + JSScopeProperty *sprop, uintN attrs, uintN mask, + JSPropertyOp getter, JSPropertyOp setter) +{ + JSScopeProperty child, *newsprop, **spp; + + CHECK_ANCESTOR_LINE(scope, JS_TRUE); + + /* Allow only shared (slot-less) => unshared (slot-full) transition. */ + attrs |= sprop->attrs & mask; + JS_ASSERT(!((attrs ^ sprop->attrs) & JSPROP_SHARED) || + !(attrs & JSPROP_SHARED)); + if (getter == JS_PropertyStub) + getter = NULL; + if (setter == JS_PropertyStub) + setter = NULL; + if (sprop->attrs == attrs && + sprop->getter == getter && + sprop->setter == setter) { + return sprop; + } + + child.id = sprop->id; + child.getter = getter; + child.setter = setter; + child.slot = sprop->slot; + child.attrs = attrs; + child.flags = sprop->flags; + child.shortid = sprop->shortid; + + if (SCOPE_LAST_PROP(scope) == sprop) { + /* + * Optimize the case where the last property added to scope is changed + * to have a different attrs, getter, or setter. In the last property + * case, we need not fork the property tree. But since we do not call + * js_AddScopeProperty, we may need to allocate a new slot directly. + */ + if ((sprop->attrs & JSPROP_SHARED) && !(attrs & JSPROP_SHARED)) { + JS_ASSERT(child.slot == SPROP_INVALID_SLOT); + if (!js_AllocSlot(cx, scope->object, &child.slot)) + return NULL; + } + + newsprop = GetPropertyTreeChild(cx, sprop->parent, &child); + if (newsprop) { + spp = js_SearchScope(scope, sprop->id, JS_FALSE); + JS_ASSERT(SPROP_FETCH(spp) == sprop); + + if (scope->table) + SPROP_STORE_PRESERVING_COLLISION(spp, newsprop); + scope->lastProp = newsprop; + CHECK_ANCESTOR_LINE(scope, JS_TRUE); + } + } else { + /* + * Let js_AddScopeProperty handle this |overwriting| case, including + * the conservation of sprop->slot (if it's valid). We must not call + * js_RemoveScopeProperty here, it will free a valid sprop->slot and + * js_AddScopeProperty won't re-allocate it. + */ + newsprop = js_AddScopeProperty(cx, scope, child.id, + child.getter, child.setter, child.slot, + child.attrs, child.flags, child.shortid); + } + +#ifdef DEBUG_brendan + if (!newsprop) + METER(changeFailures); +#endif + return newsprop; +} + +JSBool +js_RemoveScopeProperty(JSContext *cx, JSScope *scope, jsid id) +{ + JSScopeProperty **spp, *stored, *sprop; + uint32 size; + + JS_ASSERT(JS_IS_SCOPE_LOCKED(cx, scope)); + CHECK_ANCESTOR_LINE(scope, JS_TRUE); + if (SCOPE_IS_SEALED(scope)) { + ReportReadOnlyScope(cx, scope); + return JS_FALSE; + } + METER(removes); + + spp = js_SearchScope(scope, id, JS_FALSE); + stored = *spp; + sprop = SPROP_CLEAR_COLLISION(stored); + if (!sprop) { + METER(uselessRemoves); + return JS_TRUE; + } + + /* Convert from a list to a hash so we can handle "middle deletes". */ + if (!scope->table && sprop != scope->lastProp) { + if (!CreateScopeTable(scope)) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + spp = js_SearchScope(scope, id, JS_FALSE); + stored = *spp; + sprop = SPROP_CLEAR_COLLISION(stored); + } + + /* First, if sprop is unshared and not cleared, free its slot number. */ + if (SPROP_HAS_VALID_SLOT(sprop, scope)) + js_FreeSlot(cx, scope->object, sprop->slot); + + /* Next, remove id by setting its entry to a removed or free sentinel. */ + if (SPROP_HAD_COLLISION(stored)) { + JS_ASSERT(scope->table); + *spp = SPROP_REMOVED; + scope->removedCount++; + } else { + METER(removeFrees); + if (scope->table) + *spp = NULL; + } + scope->entryCount--; + JS_RUNTIME_UNMETER(cx->runtime, liveScopeProps); + + /* Update scope->lastProp directly, or set its deferred update flag. */ + if (sprop == SCOPE_LAST_PROP(scope)) { + do { + SCOPE_REMOVE_LAST_PROP(scope); + if (!SCOPE_HAD_MIDDLE_DELETE(scope)) + break; + sprop = SCOPE_LAST_PROP(scope); + } while (sprop && !SCOPE_HAS_PROPERTY(scope, sprop)); + } else if (!SCOPE_HAD_MIDDLE_DELETE(scope)) { + SCOPE_SET_MIDDLE_DELETE(scope); + } + CHECK_ANCESTOR_LINE(scope, JS_TRUE); + + /* Last, consider shrinking scope's table if its load factor is <= .25. */ + size = SCOPE_CAPACITY(scope); + if (size > MIN_SCOPE_SIZE && scope->entryCount <= size >> 2) { + METER(shrinks); + (void) ChangeScope(cx, scope, -1); + } + + return JS_TRUE; +} + +void +js_ClearScope(JSContext *cx, JSScope *scope) +{ + CHECK_ANCESTOR_LINE(scope, JS_TRUE); +#ifdef DEBUG + JS_LOCK_RUNTIME_VOID(cx->runtime, + cx->runtime->liveScopeProps -= scope->entryCount); +#endif + + if (scope->table) + free(scope->table); + SCOPE_CLR_MIDDLE_DELETE(scope); + InitMinimalScope(scope); +} + +#ifdef DEBUG_brendan + +#include +#include + +uint32 js_nkids_max; +uint32 js_nkids_sum; +double js_nkids_sqsum; +uint32 js_nkids_hist[11]; + +static void +MeterKidCount(uintN nkids) +{ + if (nkids) { + js_nkids_sum += nkids; + js_nkids_sqsum += (double)nkids * nkids; + if (nkids > js_nkids_max) + js_nkids_max = nkids; + } + js_nkids_hist[JS_MIN(nkids, 10)]++; +} + +static void +MeterPropertyTree(JSScopeProperty *node) +{ + uintN i, nkids; + JSScopeProperty *kids, *kid; + PropTreeKidsChunk *chunk; + + nkids = 0; + kids = node->kids; + if (kids) { + if (KIDS_IS_CHUNKY(kids)) { + for (chunk = KIDS_TO_CHUNK(kids); chunk; chunk = chunk->next) { + for (i = 0; i < MAX_KIDS_PER_CHUNK; i++) { + kid = chunk->kids[i]; + if (!kid) + break; + MeterPropertyTree(kid); + nkids++; + } + } + } else { + MeterPropertyTree(kids); + nkids = 1; + } + } + + MeterKidCount(nkids); +} + +JS_STATIC_DLL_CALLBACK(JSDHashOperator) +js_MeterPropertyTree(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 number, + void *arg) +{ + JSPropertyTreeEntry *entry = (JSPropertyTreeEntry *)hdr; + + MeterPropertyTree(entry->child); + return JS_DHASH_NEXT; +} + +#endif /* DEBUG_brendan */ + +void +js_SweepScopeProperties(JSRuntime *rt) +{ + JSArena **ap, *a; + JSScopeProperty *limit, *sprop, *parent, *kids, *kid; + uintN liveCount; + PropTreeKidsChunk *chunk, *nextChunk; + uintN i; + +#ifdef DEBUG_brendan + uint32 livePropCapacity = 0, totalLiveCount = 0; + static FILE *logfp; + if (!logfp) + logfp = fopen("/tmp/proptree.stats", "a"); + + MeterKidCount(rt->propertyTreeHash.entryCount); + JS_DHashTableEnumerate(&rt->propertyTreeHash, js_MeterPropertyTree, NULL); + + { + double mean = 0., var = 0., sigma = 0.; + double nodesum = rt->livePropTreeNodes; + double kidsum = js_nkids_sum; + if (nodesum > 0 && kidsum >= 0) { + mean = kidsum / nodesum; + var = nodesum * js_nkids_sqsum - kidsum * kidsum; + if (var < 0.0 || nodesum <= 1) + var = 0.0; + else + var /= nodesum * (nodesum - 1); + + /* Windows says sqrt(0.0) is "-1.#J" (?!) so we must test. */ + sigma = (var != 0.) ? sqrt(var) : 0.; + } + + fprintf(logfp, + "props %u nodes %g beta %g meankids %g sigma %g max %u", + rt->liveScopeProps, nodesum, nodesum / rt->liveScopeProps, + mean, sigma, js_nkids_max); + } + + fprintf(logfp, " histogram %u %u %u %u %u %u %u %u %u %u %u", + js_nkids_hist[0], js_nkids_hist[1], + js_nkids_hist[2], js_nkids_hist[3], + js_nkids_hist[4], js_nkids_hist[5], + js_nkids_hist[6], js_nkids_hist[7], + js_nkids_hist[8], js_nkids_hist[9], + js_nkids_hist[10]); + js_nkids_sum = js_nkids_max = 0; + js_nkids_sqsum = 0; + memset(js_nkids_hist, 0, sizeof js_nkids_hist); +#endif + + ap = &rt->propertyArenaPool.first.next; + while ((a = *ap) != NULL) { + limit = (JSScopeProperty *) a->avail; + liveCount = 0; + for (sprop = (JSScopeProperty *) a->base; sprop < limit; sprop++) { + /* If the id is null, sprop is already on the freelist. */ + if (sprop->id == JSVAL_NULL) + continue; + + /* If the mark bit is set, sprop is alive, so we skip it. */ + if (sprop->flags & SPROP_MARK) { + sprop->flags &= ~SPROP_MARK; + liveCount++; + continue; + } + + /* Ok, sprop is garbage to collect: unlink it from its parent. */ + RemovePropertyTreeChild(rt, sprop); + + /* Take care to reparent all sprop's kids to their grandparent. */ + kids = sprop->kids; + if (kids) { + sprop->kids = NULL; + parent = sprop->parent; + if (KIDS_IS_CHUNKY(kids)) { + chunk = KIDS_TO_CHUNK(kids); + do { + for (i = 0; i < MAX_KIDS_PER_CHUNK; i++) { + kid = chunk->kids[i]; + if (!kid) + break; + JS_ASSERT(kid->parent == sprop); + InsertPropertyTreeChild(rt, parent, kid); + } + nextChunk = chunk->next; + DestroyPropTreeKidsChunk(rt, chunk); + } while ((chunk = nextChunk) != NULL); + } else { + kid = kids; + InsertPropertyTreeChild(rt, parent, kid); + } + } + + /* Clear id so we know (above) that sprop is on the freelist. */ + sprop->id = JSVAL_NULL; + FREENODE_INSERT(rt->propertyFreeList, sprop); + JS_RUNTIME_UNMETER(rt, livePropTreeNodes); + } + + /* If a contains no live properties, return it to the malloc heap. */ + if (liveCount == 0) { + for (sprop = (JSScopeProperty *) a->base; sprop < limit; sprop++) + FREENODE_REMOVE(sprop); + JS_ARENA_DESTROY(&rt->propertyArenaPool, a, ap); + } else { +#ifdef DEBUG_brendan + livePropCapacity += limit - (JSScopeProperty *) a->base; + totalLiveCount += liveCount; +#endif + ap = &a->next; + } + } + +#ifdef DEBUG_brendan + fprintf(logfp, " arenautil %g%%\n", + (totalLiveCount * 100.) / livePropCapacity); + fflush(logfp); +#endif +} + +JSBool +js_InitPropertyTree(JSRuntime *rt) +{ + if (!JS_DHashTableInit(&rt->propertyTreeHash, &PropertyTreeHashOps, NULL, + sizeof(JSPropertyTreeEntry), JS_DHASH_MIN_SIZE)) { + rt->propertyTreeHash.ops = NULL; + return JS_FALSE; + } + JS_InitArenaPool(&rt->propertyArenaPool, "properties", + 256 * sizeof(JSScopeProperty), sizeof(void *)); + return JS_TRUE; +} + +void +js_FinishPropertyTree(JSRuntime *rt) +{ + if (rt->propertyTreeHash.ops) { + JS_DHashTableFinish(&rt->propertyTreeHash); + rt->propertyTreeHash.ops = NULL; + } + JS_FinishArenaPool(&rt->propertyArenaPool); +} diff --git a/src/extension/script/js/jsscope.h b/src/extension/script/js/jsscope.h new file mode 100644 index 000000000..4f66441b4 --- /dev/null +++ b/src/extension/script/js/jsscope.h @@ -0,0 +1,386 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsscope_h___ +#define jsscope_h___ +/* + * JS symbol tables. + */ +#include "jstypes.h" +#include "jsobj.h" +#include "jsprvtd.h" +#include "jspubtd.h" + +#ifdef JS_THREADSAFE +# include "jslock.h" +#endif + +/* + * Given P independent, non-unique properties each of size S words mapped by + * all scopes in a runtime, construct a property tree of N nodes each of size + * S+L words (L for tree linkage). A nominal L value is 2 for leftmost-child + * and right-sibling links. We hope that the N < P by enough that the space + * overhead of L, and the overhead of scope entries pointing at property tree + * nodes, is worth it. + * + * The tree construction goes as follows. If any empty scope in the runtime + * has a property X added to it, find or create a node under the tree root + * labeled X, and set scope->lastProp to point at that node. If any non-empty + * scope whose most recently added property is labeled Y has another property + * labeled Z added, find or create a node for Z under the node that was added + * for Y, and set scope->lastProp to point at that node. + * + * A property is labeled by its members' values: id, getter, setter, slot, + * attributes, tiny or short id, and a field telling for..in order. Note that + * labels are not unique in the tree, but they are unique among a node's kids + * (barring rare and benign multi-threaded race condition outcomes, see below) + * and along any ancestor line from the tree root to a given leaf node (except + * for the hard case of duplicate formal parameters to a function). + * + * Thus the root of the tree represents all empty scopes, and the first ply + * of the tree represents all scopes containing one property, etc. Each node + * in the tree can stand for any number of scopes having the same ordered set + * of properties, where that node was the last added to the scope. (We need + * not store the root of the tree as a node, and do not -- all we need are + * links to its kids.) + * + * Sidebar on for..in loop order: ECMA requires no particular order, but this + * implementation has promised and delivered property definition order, and + * compatibility is king. We could use an order number per property, which + * would require a sort in js_Enumerate, and an entry order generation number + * per scope. An order number beats a list, which should be doubly-linked for + * O(1) delete. An even better scheme is to use a parent link in the property + * tree, so that the ancestor line can be iterated from scope->lastProp when + * filling in a JSIdArray from back to front. This parent link also helps the + * GC to sweep properties iteratively. + * + * What if a property Y is deleted from a scope? If Y is the last property in + * the scope, we simply adjust the scope's lastProp member after we remove the + * scope's hash-table entry pointing at that property node. The parent link + * mentioned in the for..in sidebar above makes this adjustment O(1). But if + * Y comes between X and Z in the scope, then we might have to "fork" the tree + * at X, leaving X->Y->Z in case other scopes have those properties added in + * that order; and to finish the fork, we'd add a node labeled Z with the path + * X->Z, if it doesn't exist. This could lead to lots of extra nodes, and to + * O(n^2) growth when deleting lots of properties. + * + * Rather, for O(1) growth all around, we should share the path X->Y->Z among + * scopes having those three properties added in that order, and among scopes + * having only X->Z where Y was deleted. All such scopes have a lastProp that + * points to the Z child of Y. But a scope in which Y was deleted does not + * have a table entry for Y, and when iterating that scope by traversing the + * ancestor line from Z, we will have to test for a table entry for each node, + * skipping nodes that lack entries. + * + * What if we add Y again? X->Y->Z->Y is wrong and we'll enumerate Y twice. + * Therefore we must fork in such a case, if not earlier. Because delete is + * "bursty", we should not fork eagerly. Delaying a fork till we are at risk + * of adding Y after it was deleted already requires a flag in the JSScope, to + * wit, SCOPE_MIDDLE_DELETE. + * + * What about thread safety? If the property tree operations done by requests + * are find-node and insert-node, then the only hazard is duplicate insertion. + * This is harmless except for minor bloat. When all requests have ended or + * been suspended, the GC is free to sweep the tree after marking all nodes + * reachable from scopes, performing remove-node operations as needed. Note + * also that the stable storage of the property nodes during active requests + * permits the property cache (see jsinterp.h) to dereference JSScopeProperty + * weak references safely. + * + * Is the property tree worth it compared to property storage in each table's + * entries? To decide, we must find the relation <> between the words used + * with a property tree and the words required without a tree. + * + * Model all scopes as one super-scope of capacity T entries (T a power of 2). + * Let alpha be the load factor of this double hash-table. With the property + * tree, each entry in the table is a word-sized pointer to a node that can be + * shared by many scopes. But all such pointers are overhead compared to the + * situation without the property tree, where the table stores property nodes + * directly, as entries each of size S words. With the property tree, we need + * L=2 extra words per node for siblings and kids pointers. Without the tree, + * (1-alpha)*S*T words are wasted on free or removed sentinel-entries required + * by double hashing. + * + * Therefore, + * + * (property tree) <> (no property tree) + * N*(S+L) + T <> S*T + * N*(S+L) + T <> P*S + (1-alpha)*S*T + * N*(S+L) + alpha*T + (1-alpha)*T <> P*S + (1-alpha)*S*T + * + * Note that P is alpha*T by definition, so + * + * N*(S+L) + P + (1-alpha)*T <> P*S + (1-alpha)*S*T + * N*(S+L) <> P*S - P + (1-alpha)*S*T - (1-alpha)*T + * N*(S+L) <> (P + (1-alpha)*T) * (S-1) + * N*(S+L) <> (P + (1-alpha)*P/alpha) * (S-1) + * N*(S+L) <> P * (1/alpha) * (S-1) + * + * Let N = P*beta for a compression ratio beta, beta <= 1: + * + * P*beta*(S+L) <> P * (1/alpha) * (S-1) + * beta*(S+L) <> (S-1)/alpha + * beta <> (S-1)/((S+L)*alpha) + * + * For S = 6 (32-bit architectures) and L = 2, the property tree wins iff + * + * beta < 5/(8*alpha) + * + * We ensure that alpha <= .75, so the property tree wins if beta < .83_. An + * average beta from recent Mozilla browser startups was around .6. + * + * Can we reduce L? Observe that the property tree degenerates into a list of + * lists if at most one property Y follows X in all scopes. In or near such a + * case, we waste a word on the right-sibling link outside of the root ply of + * the tree. Note also that the root ply tends to be large, so O(n^2) growth + * searching it is likely, indicating the need for hashing (but with increased + * thread safety costs). + * + * If only K out of N nodes in the property tree have more than one child, we + * could eliminate the sibling link and overlay a children list or hash-table + * pointer on the leftmost-child link (which would then be either null or an + * only-child link; the overlay could be tagged in the low bit of the pointer, + * or flagged elsewhere in the property tree node, although such a flag must + * not be considered when comparing node labels during tree search). + * + * For such a system, L = 1 + (K * averageChildrenTableSize) / N instead of 2. + * If K << N, L approaches 1 and the property tree wins if beta < .95. + * + * We observe that fan-out below the root ply of the property tree appears to + * have extremely low degree (see the MeterPropertyTree code that histograms + * child-counts in jsscope.c), so instead of a hash-table we use a linked list + * of child node pointer arrays ("kid chunks"). The details are isolated in + * jsscope.c; others must treat JSScopeProperty.kids as opaque. We leave it + * strongly typed for debug-ability of the common (null or one-kid) cases. + * + * One final twist (can you stand it?): the mean number of entries per scope + * in Mozilla is < 5, with a large standard deviation (~8). Instead of always + * allocating scope->table, we leave it null while initializing all the other + * scope members as if it were non-null and minimal-length. Until a property + * is added that crosses the threshold of 6 or more entries for hashing, or + * until a "middle delete" occurs, we use linear search from scope->lastProp + * to find a given id, and save on the space overhead of a hash table. + */ + +struct JSScope { + JSObjectMap map; /* base class state */ + JSObject *object; /* object that owns this scope */ + uint16 flags; /* flags, see below */ + int16 hashShift; /* multiplicative hash shift */ + uint32 entryCount; /* number of entries in table */ + uint32 removedCount; /* removed entry sentinels in table */ + JSScopeProperty **table; /* table of ptrs to shared tree nodes */ + JSScopeProperty *lastProp; /* pointer to last property added */ +#ifdef JS_THREADSAFE + JSContext *ownercx; /* creating context, NULL if shared */ + JSThinLock lock; /* binary semaphore protecting scope */ + union { /* union lockful and lock-free state: */ + jsrefcount count; /* lock entry count for reentrancy */ + JSScope *link; /* next link in rt->scopeSharingTodo */ + } u; +#ifdef DEBUG + const char *file[4]; /* file where lock was (re-)taken */ + unsigned int line[4]; /* line where lock was (re-)taken */ +#endif +#endif +}; + +#define OBJ_SCOPE(obj) ((JSScope *)(obj)->map) + +/* By definition, hashShift = JS_DHASH_BITS - log2(capacity). */ +#define SCOPE_CAPACITY(scope) JS_BIT(JS_DHASH_BITS-(scope)->hashShift) + +/* Scope flags and some macros to hide them from other files than jsscope.c. */ +#define SCOPE_MIDDLE_DELETE 0x0001 +#define SCOPE_SEALED 0x0002 + +#define SCOPE_HAD_MIDDLE_DELETE(scope) ((scope)->flags & SCOPE_MIDDLE_DELETE) +#define SCOPE_SET_MIDDLE_DELETE(scope) ((scope)->flags |= SCOPE_MIDDLE_DELETE) +#define SCOPE_CLR_MIDDLE_DELETE(scope) ((scope)->flags &= ~SCOPE_MIDDLE_DELETE) + +#define SCOPE_IS_SEALED(scope) ((scope)->flags & SCOPE_SEALED) +#define SCOPE_SET_SEALED(scope) ((scope)->flags |= SCOPE_SEALED) +#if 0 +/* + * Don't define this, it can't be done safely because JS_LOCK_OBJ will avoid + * taking the lock if the object owns its scope and the scope is sealed. + */ +#define SCOPE_CLR_SEALED(scope) ((scope)->flags &= ~SCOPE_SEALED) +#endif + +/* + * A little information hiding for scope->lastProp, in case it ever becomes + * a tagged pointer again. + */ +#define SCOPE_LAST_PROP(scope) ((scope)->lastProp) +#define SCOPE_REMOVE_LAST_PROP(scope) ((scope)->lastProp = \ + (scope)->lastProp->parent) + +struct JSScopeProperty { + jsid id; /* int-tagged jsval/untagged JSAtom* */ + JSPropertyOp getter; /* getter and setter hooks or objects */ + JSPropertyOp setter; + uint32 slot; /* index in obj->slots vector */ + uint8 attrs; /* attributes, see jsapi.h JSPROP_* */ + uint8 flags; /* flags, see below for defines */ + int16 shortid; /* tinyid, or local arg/var index */ + JSScopeProperty *parent; /* parent node, reverse for..in order */ + JSScopeProperty *kids; /* null, single child, or a tagged ptr + to many-kids data structure */ +}; + +/* JSScopeProperty pointer tag bit indicating a collision. */ +#define SPROP_COLLISION ((jsuword)1) +#define SPROP_REMOVED ((JSScopeProperty *) SPROP_COLLISION) + +/* Macros to get and set sprop pointer values and collision flags. */ +#define SPROP_IS_FREE(sprop) ((sprop) == NULL) +#define SPROP_IS_REMOVED(sprop) ((sprop) == SPROP_REMOVED) +#define SPROP_IS_LIVE(sprop) ((sprop) > SPROP_REMOVED) +#define SPROP_FLAG_COLLISION(spp,sprop) (*(spp) = (JSScopeProperty *) \ + ((jsuword)(sprop) | SPROP_COLLISION)) +#define SPROP_HAD_COLLISION(sprop) ((jsuword)(sprop) & SPROP_COLLISION) +#define SPROP_FETCH(spp) SPROP_CLEAR_COLLISION(*(spp)) + +#define SPROP_CLEAR_COLLISION(sprop) \ + ((JSScopeProperty *) ((jsuword)(sprop) & ~SPROP_COLLISION)) + +#define SPROP_STORE_PRESERVING_COLLISION(spp, sprop) \ + (*(spp) = (JSScopeProperty *) ((jsuword)(sprop) \ + | SPROP_HAD_COLLISION(*(spp)))) + +/* Bits stored in sprop->flags. */ +#define SPROP_MARK 0x01 +#define SPROP_IS_DUPLICATE 0x02 +#define SPROP_IS_ALIAS 0x04 +#define SPROP_HAS_SHORTID 0x08 + +/* + * If SPROP_HAS_SHORTID is set in sprop->flags, we use sprop->shortid rather + * than id when calling sprop's getter or setter. + */ +#define SPROP_USERID(sprop) \ + (((sprop)->flags & SPROP_HAS_SHORTID) ? INT_TO_JSVAL((sprop)->shortid) \ + : ID_TO_VALUE((sprop)->id)) + +#define SPROP_INVALID_SLOT 0xffffffff + +#define SPROP_HAS_VALID_SLOT(sprop, scope) \ + ((sprop)->slot < (scope)->map.freeslot) + +#define SPROP_CALL_GETTER(cx,sprop,getter,obj,obj2,vp) \ + (!(getter) || \ + (getter)(cx, OBJ_THIS_OBJECT(cx,obj), SPROP_USERID(sprop), vp)) +#define SPROP_CALL_SETTER(cx,sprop,setter,obj,obj2,vp) \ + (!(setter) || \ + (setter)(cx, OBJ_THIS_OBJECT(cx,obj), SPROP_USERID(sprop), vp)) + +#define SPROP_GET(cx,sprop,obj,obj2,vp) \ + (((sprop)->attrs & JSPROP_GETTER) \ + ? js_InternalGetOrSet(cx, obj, (sprop)->id, \ + OBJECT_TO_JSVAL((sprop)->getter), JSACC_READ, \ + 0, 0, vp) \ + : SPROP_CALL_GETTER(cx, sprop, (sprop)->getter, obj, obj2, vp)) + +#define SPROP_SET(cx,sprop,obj,obj2,vp) \ + (((sprop)->attrs & JSPROP_SETTER) \ + ? js_InternalGetOrSet(cx, obj, (sprop)->id, \ + OBJECT_TO_JSVAL((sprop)->setter), JSACC_WRITE, \ + 1, vp, vp) \ + : ((sprop)->attrs & JSPROP_GETTER) \ + ? (JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, \ + JSMSG_GETTER_ONLY, NULL), JS_FALSE) \ + : SPROP_CALL_SETTER(cx, sprop, (sprop)->setter, obj, obj2, vp)) + +/* Macro for common expression to test for shared permanent attributes. */ +#define SPROP_IS_SHARED_PERMANENT(sprop) \ + ((~(sprop)->attrs & (JSPROP_SHARED | JSPROP_PERMANENT)) == 0) + +extern JSScope * +js_GetMutableScope(JSContext *cx, JSObject *obj); + +extern JSScope * +js_NewScope(JSContext *cx, jsrefcount nrefs, JSObjectOps *ops, JSClass *clasp, + JSObject *obj); + +extern void +js_DestroyScope(JSContext *cx, JSScope *scope); + +#define ID_TO_VALUE(id) (((id) & JSVAL_INT) ? id : ATOM_KEY((JSAtom *)(id))) +#define HASH_ID(id) (((id) & JSVAL_INT) \ + ? (jsatomid) JSVAL_TO_INT(id) \ + : ((JSAtom *)id)->number) + +extern JS_FRIEND_API(JSScopeProperty **) +js_SearchScope(JSScope *scope, jsid id, JSBool adding); + +#define SCOPE_GET_PROPERTY(scope, id) \ + SPROP_FETCH(js_SearchScope(scope, id, JS_FALSE)) + +#define SCOPE_HAS_PROPERTY(scope, sprop) \ + (SCOPE_GET_PROPERTY(scope, (sprop)->id) == (sprop)) + +extern JSScopeProperty * +js_AddScopeProperty(JSContext *cx, JSScope *scope, jsid id, + JSPropertyOp getter, JSPropertyOp setter, uint32 slot, + uintN attrs, uintN flags, intN shortid); + +extern JSScopeProperty * +js_ChangeScopePropertyAttrs(JSContext *cx, JSScope *scope, + JSScopeProperty *sprop, uintN attrs, uintN mask, + JSPropertyOp getter, JSPropertyOp setter); + +extern JSBool +js_RemoveScopeProperty(JSContext *cx, JSScope *scope, jsid id); + +extern void +js_ClearScope(JSContext *cx, JSScope *scope); + +#define MARK_SCOPE_PROPERTY(sprop) ((sprop)->flags |= SPROP_MARK) + +extern void +js_SweepScopeProperties(JSRuntime *rt); + +extern JSBool +js_InitPropertyTree(JSRuntime *rt); + +extern void +js_FinishPropertyTree(JSRuntime *rt); + +#endif /* jsscope_h___ */ diff --git a/src/extension/script/js/jsscript.c b/src/extension/script/js/jsscript.c new file mode 100644 index 000000000..504cbbd6d --- /dev/null +++ b/src/extension/script/js/jsscript.c @@ -0,0 +1,1287 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS script operations. + */ +#include "jsstddef.h" +#include +#include "jstypes.h" +#include "jsutil.h" /* Added by JSIFY */ +#include "jsprf.h" +#include "jsapi.h" +#include "jsatom.h" +#include "jscntxt.h" +#include "jsconfig.h" +#include "jsdbgapi.h" +#include "jsemit.h" +#include "jsfun.h" +#include "jsinterp.h" +#include "jslock.h" +#include "jsnum.h" +#include "jsopcode.h" +#include "jsscript.h" +#if JS_HAS_XDR +#include "jsxdrapi.h" +#endif + +#if JS_HAS_SCRIPT_OBJECT + +#if JS_HAS_TOSOURCE +static JSBool +script_toSource(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSScript *script; + size_t i, j, k, n; + char buf[16]; + jschar *s, *t; + uint32 indent; + JSString *str; + + if (!JS_InstanceOf(cx, obj, &js_ScriptClass, argv)) + return JS_FALSE; + script = (JSScript *) JS_GetPrivate(cx, obj); + + /* Let n count the source string length, j the "front porch" length. */ + j = JS_snprintf(buf, sizeof buf, "(new %s(", js_ScriptClass.name); + n = j + 2; + if (!script) { + /* Let k count the constructor argument string length. */ + k = 0; + s = NULL; /* quell GCC overwarning */ + } else { + indent = 0; + if (argc && !js_ValueToECMAUint32(cx, argv[0], &indent)) + return JS_FALSE; + str = JS_DecompileScript(cx, script, "Script.prototype.toSource", + (uintN)indent); + if (!str) + return JS_FALSE; + str = js_QuoteString(cx, str, '\''); + if (!str) + return JS_FALSE; + s = JSSTRING_CHARS(str); + k = JSSTRING_LENGTH(str); + n += k; + } + + /* Allocate the source string and copy into it. */ + t = (jschar *) JS_malloc(cx, (n + 1) * sizeof(jschar)); + if (!t) + return JS_FALSE; + for (i = 0; i < j; i++) + t[i] = buf[i]; + for (j = 0; j < k; i++, j++) + t[i] = s[j]; + t[i++] = ')'; + t[i++] = ')'; + t[i] = 0; + + /* Create and return a JS string for t. */ + str = JS_NewUCString(cx, t, n); + if (!str) { + JS_free(cx, t); + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} +#endif /* JS_HAS_TOSOURCE */ + +static JSBool +script_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSScript *script; + uint32 indent; + JSString *str; + + if (!JS_InstanceOf(cx, obj, &js_ScriptClass, argv)) + return JS_FALSE; + script = (JSScript *) JS_GetPrivate(cx, obj); + if (!script) { + *rval = STRING_TO_JSVAL(cx->runtime->emptyString); + return JS_TRUE; + } + + indent = 0; + if (argc && !js_ValueToECMAUint32(cx, argv[0], &indent)) + return JS_FALSE; + str = JS_DecompileScript(cx, script, "Script.prototype.toString", + (uintN)indent); + if (!str) + return JS_FALSE; + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +static JSBool +script_compile(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSScript *oldscript, *script; + JSString *str; + JSStackFrame *fp, *caller; + JSObject *scopeobj; + const char *file; + uintN line; + JSPrincipals *principals; + + /* Make sure obj is a Script object. */ + if (!JS_InstanceOf(cx, obj, &js_ScriptClass, argv)) + return JS_FALSE; + + /* If no args, leave private undefined and return early. */ + if (argc == 0) + goto out; + + /* Otherwise, the first arg is the script source to compile. */ + str = js_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + + /* Compile using the caller's scope chain, which js_Invoke passes to fp. */ + fp = cx->fp; + caller = JS_GetScriptedCaller(cx, fp); + JS_ASSERT(!caller || fp->scopeChain == caller->scopeChain); + + scopeobj = NULL; + if (argc >= 2) { + if (!js_ValueToObject(cx, argv[1], &scopeobj)) + return JS_FALSE; + argv[1] = OBJECT_TO_JSVAL(scopeobj); + } + if (caller) { + if (!scopeobj) + scopeobj = caller->scopeChain; + + file = caller->script->filename; + line = js_PCToLineNumber(cx, caller->script, caller->pc); + principals = JS_EvalFramePrincipals(cx, fp, caller); + } else { + file = NULL; + line = 0; + principals = NULL; + } + + /* XXXbe set only for the compiler, which does not currently test it */ + fp->flags |= JSFRAME_EVAL; + + /* Compile the new script using the caller's scope chain, a la eval(). */ + script = JS_CompileUCScriptForPrincipals(cx, scopeobj, principals, + JSSTRING_CHARS(str), + JSSTRING_LENGTH(str), + file, line); + if (!script) + return JS_FALSE; + + /* Swap script for obj's old script, if any. */ + oldscript = (JSScript *) JS_GetPrivate(cx, obj); + if (!JS_SetPrivate(cx, obj, script)) { + js_DestroyScript(cx, script); + return JS_FALSE; + } + if (oldscript) + js_DestroyScript(cx, oldscript); + + script->object = obj; +out: + /* Return the object. */ + *rval = OBJECT_TO_JSVAL(obj); + return JS_TRUE; +} + +static JSBool +script_exec(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSScript *script; + JSObject *scopeobj, *parent; + JSStackFrame *fp, *caller; + + if (!JS_InstanceOf(cx, obj, &js_ScriptClass, argv)) + return JS_FALSE; + script = (JSScript *) JS_GetPrivate(cx, obj); + if (!script) + return JS_TRUE; + + scopeobj = NULL; + if (argc) { + if (!js_ValueToObject(cx, argv[0], &scopeobj)) + return JS_FALSE; + argv[0] = OBJECT_TO_JSVAL(scopeobj); + } + + /* + * Emulate eval() by using caller's this, var object, sharp array, etc., + * all propagated by js_Execute via a non-null fourth (down) argument to + * js_Execute. If there is no scripted caller, js_Execute uses its second + * (chain) argument to set the exec frame's varobj, thisp, and scopeChain. + * + * Unlike eval, which the compiler detects, Script.prototype.exec may be + * called from a lightweight function, or even from native code (in which + * case fp->varobj and fp->scopeChain are null). If exec is called from + * a lightweight function, we will need to get a Call object representing + * its frame, to act as the var object and scope chain head. + */ + fp = cx->fp; + caller = JS_GetScriptedCaller(cx, fp); + if (caller && !caller->varobj) { + /* Called from a lightweight function. */ + JS_ASSERT(caller->fun && !(caller->fun->flags & JSFUN_HEAVYWEIGHT)); + + /* Scope chain links from Call object to callee's parent. */ + parent = OBJ_GET_PARENT(cx, JSVAL_TO_OBJECT(caller->argv[-2])); + if (!js_GetCallObject(cx, caller, parent)) + return JS_FALSE; + } + + if (!scopeobj) { + /* No scope object passed in: try to use the caller's scope chain. */ + if (caller) { + /* + * Load caller->scopeChain after the conditional js_GetCallObject + * call above, which resets scopeChain as well as varobj. + */ + scopeobj = caller->scopeChain; + } else { + /* + * Called from native code, so we don't know what scope object to + * use. We could use parent (see above), but Script.prototype.exec + * might be a shared/sealed "superglobal" method. A more general + * approach would use cx->globalObject, which will be the same as + * exec.__parent__ in the non-superglobal case. In the superglobal + * case it's the right object: the global, not the superglobal. + */ + scopeobj = cx->globalObject; + } + } + + return js_Execute(cx, scopeobj, script, caller, JSFRAME_EVAL, rval); +} + +#if JS_HAS_XDR + +static JSBool +XDRAtomListElement(JSXDRState *xdr, JSAtomListElement *ale) +{ + jsval value; + jsatomid index; + + if (xdr->mode == JSXDR_ENCODE) + value = ATOM_KEY(ALE_ATOM(ale)); + + index = ALE_INDEX(ale); + if (!JS_XDRUint32(xdr, &index)) + return JS_FALSE; + ALE_SET_INDEX(ale, index); + + if (!JS_XDRValue(xdr, &value)) + return JS_FALSE; + + if (xdr->mode == JSXDR_DECODE) { + if (!ALE_SET_ATOM(ale, js_AtomizeValue(xdr->cx, value, 0))) + return JS_FALSE; + } + return JS_TRUE; +} + +static JSBool +XDRAtomMap(JSXDRState *xdr, JSAtomMap *map) +{ + uint32 length; + uintN i; + JSBool ok; + + if (xdr->mode == JSXDR_ENCODE) + length = map->length; + + if (!JS_XDRUint32(xdr, &length)) + return JS_FALSE; + + if (xdr->mode == JSXDR_DECODE) { + JSContext *cx; + void *mark; + JSAtomList al; + JSAtomListElement *ale; + + cx = xdr->cx; + mark = JS_ARENA_MARK(&cx->tempPool); + ATOM_LIST_INIT(&al); + for (i = 0; i < length; i++) { + JS_ARENA_ALLOCATE_TYPE(ale, JSAtomListElement, &cx->tempPool); + if (!ale || + !XDRAtomListElement(xdr, ale)) { + if (!ale) + JS_ReportOutOfMemory(cx); + JS_ARENA_RELEASE(&cx->tempPool, mark); + return JS_FALSE; + } + ALE_SET_NEXT(ale, al.list); + al.count++; + al.list = ale; + } + ok = js_InitAtomMap(cx, map, &al); + JS_ARENA_RELEASE(&cx->tempPool, mark); + return ok; + } + + if (xdr->mode == JSXDR_ENCODE) { + JSAtomListElement ale; + + for (i = 0; i < map->length; i++) { + ALE_SET_ATOM(&ale, map->vector[i]); + ALE_SET_INDEX(&ale, i); + if (!XDRAtomListElement(xdr, &ale)) + return JS_FALSE; + } + } + return JS_TRUE; +} + +JSBool +js_XDRScript(JSXDRState *xdr, JSScript **scriptp, JSBool *hasMagic) +{ + JSContext *cx; + JSScript *script, *newscript; + uint32 length, lineno, depth, magic, nsrcnotes, ntrynotes; + uint32 prologLength, version; + JSBool filenameWasSaved; + jssrcnote *notes, *sn; + + cx = xdr->cx; + script = *scriptp; + nsrcnotes = ntrynotes = 0; + filenameWasSaved = JS_FALSE; + notes = NULL; + + /* + * Encode prologLength and version after script->length (_2 or greater), + * but decode both new (>= _2) and old, prolog&version-free (_1) scripts. + * Version _3 supports principals serialization. Version _4 reorders the + * nsrcnotes and ntrynotes fields to come before everything except magic, + * length, prologLength, and version, so that srcnote and trynote storage + * can be allocated as part of the JSScript (along with bytecode storage). + */ + if (xdr->mode == JSXDR_ENCODE) + magic = JSXDR_MAGIC_SCRIPT_CURRENT; + if (!JS_XDRUint32(xdr, &magic)) + return JS_FALSE; + if (magic != JSXDR_MAGIC_SCRIPT_4 && + magic != JSXDR_MAGIC_SCRIPT_3 && + magic != JSXDR_MAGIC_SCRIPT_2 && + magic != JSXDR_MAGIC_SCRIPT_1) { + if (!hasMagic) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_SCRIPT_MAGIC); + return JS_FALSE; + } + *hasMagic = JS_FALSE; + return JS_TRUE; + } + if (hasMagic) + *hasMagic = JS_TRUE; + + if (xdr->mode == JSXDR_ENCODE) { + length = script->length; + prologLength = PTRDIFF(script->main, script->code, jsbytecode); + version = (int32)script->version; + lineno = (uint32)script->lineno; + depth = (uint32)script->depth; + + /* Count the srcnotes, keeping notes pointing at the first one. */ + notes = SCRIPT_NOTES(script); + for (sn = notes; !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) + continue; + nsrcnotes = PTRDIFF(sn, notes, jssrcnote); + nsrcnotes++; /* room for the terminator */ + + /* Count the trynotes. */ + if (script->trynotes) { + while (script->trynotes[ntrynotes].catchStart) + ntrynotes++; + ntrynotes++; /* room for the end marker */ + } + } + + if (!JS_XDRUint32(xdr, &length)) + return JS_FALSE; + if (magic >= JSXDR_MAGIC_SCRIPT_2) { + if (!JS_XDRUint32(xdr, &prologLength)) + return JS_FALSE; + if (!JS_XDRUint32(xdr, &version)) + return JS_FALSE; + + /* To fuse allocations, we need srcnote and trynote counts early. */ + if (magic >= JSXDR_MAGIC_SCRIPT_4) { + if (!JS_XDRUint32(xdr, &nsrcnotes)) + return JS_FALSE; + if (!JS_XDRUint32(xdr, &ntrynotes)) + return JS_FALSE; + } + } + + if (xdr->mode == JSXDR_DECODE) { + script = js_NewScript(cx, length, nsrcnotes, ntrynotes); + if (!script) + return JS_FALSE; + if (magic >= JSXDR_MAGIC_SCRIPT_2) { + script->main += prologLength; + script->version = (JSVersion) version; + + /* If we know nsrcnotes, we allocated space for notes in script. */ + if (magic >= JSXDR_MAGIC_SCRIPT_4) + notes = SCRIPT_NOTES(script); + } + *scriptp = script; + } + + /* + * Control hereafter must goto error on failure, in order for the DECODE + * case to destroy script and conditionally free notes, which if non-null + * in the (DECODE and version < _4) case must point at a temporary vector + * allocated just below. + */ + if (!JS_XDRBytes(xdr, (char *)script->code, length * sizeof(jsbytecode)) || + !XDRAtomMap(xdr, &script->atomMap)) { + goto error; + } + + if (magic < JSXDR_MAGIC_SCRIPT_4) { + if (!JS_XDRUint32(xdr, &nsrcnotes)) + goto error; + if (xdr->mode == JSXDR_DECODE) { + notes = (jssrcnote *) JS_malloc(cx, nsrcnotes * sizeof(jssrcnote)); + if (!notes) + goto error; + } + } + + if (!JS_XDRBytes(xdr, (char *)notes, nsrcnotes * sizeof(jssrcnote)) || + !JS_XDRCStringOrNull(xdr, (char **)&script->filename) || + !JS_XDRUint32(xdr, &lineno) || + !JS_XDRUint32(xdr, &depth) || + (magic < JSXDR_MAGIC_SCRIPT_4 && !JS_XDRUint32(xdr, &ntrynotes))) { + goto error; + } + + /* Script principals transcoding support comes with versions >= _3. */ + if (magic >= JSXDR_MAGIC_SCRIPT_3) { + JSPrincipals *principals; + uint32 encodeable; + + if (xdr->mode == JSXDR_ENCODE) { + principals = script->principals; + encodeable = (cx->runtime->principalsTranscoder != NULL); + if (!JS_XDRUint32(xdr, &encodeable)) + goto error; + if (encodeable && + !cx->runtime->principalsTranscoder(xdr, &principals)) { + goto error; + } + } else { + if (!JS_XDRUint32(xdr, &encodeable)) + goto error; + if (encodeable) { + if (!cx->runtime->principalsTranscoder) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_CANT_DECODE_PRINCIPALS); + goto error; + } + if (!cx->runtime->principalsTranscoder(xdr, &principals)) + goto error; + script->principals = principals; + } + } + } + + if (xdr->mode == JSXDR_DECODE) { + const char *filename = script->filename; + if (filename) { + filename = js_SaveScriptFilename(cx, filename); + if (!filename) + goto error; + JS_free(cx, (void *) script->filename); + script->filename = filename; + filenameWasSaved = JS_TRUE; + } + script->lineno = (uintN)lineno; + script->depth = (uintN)depth; + + if (magic < JSXDR_MAGIC_SCRIPT_4) { + /* + * Argh, we have to reallocate script, copy notes into the extra + * space after the bytecodes, and free the temporary notes vector. + * First, add enough slop to nsrcnotes so we can align the address + * after the srcnotes of the first trynote. + */ + uint32 osrcnotes = nsrcnotes; + + if (ntrynotes) + nsrcnotes += JSTRYNOTE_ALIGNMASK; + newscript = (JSScript *) JS_realloc(cx, script, + sizeof(JSScript) + + length * sizeof(jsbytecode) + + nsrcnotes * sizeof(jssrcnote) + + ntrynotes * sizeof(JSTryNote)); + if (!newscript) + goto error; + + *scriptp = script = newscript; + script->code = (jsbytecode *)(script + 1); + script->main = script->code + prologLength; + memcpy(script->code + length, notes, osrcnotes * sizeof(jssrcnote)); + JS_free(cx, (void *) notes); + notes = NULL; + if (ntrynotes) { + script->trynotes = (JSTryNote *) + ((jsword)(SCRIPT_NOTES(script) + nsrcnotes) & + ~(jsword)JSTRYNOTE_ALIGNMASK); + } + } + } + + while (ntrynotes) { + JSTryNote *tn = &script->trynotes[--ntrynotes]; + uint32 start = (uint32) tn->start, + catchLength = (uint32) tn->length, + catchStart = (uint32) tn->catchStart; + + if (!JS_XDRUint32(xdr, &start) || + !JS_XDRUint32(xdr, &catchLength) || + !JS_XDRUint32(xdr, &catchStart)) { + goto error; + } + tn->start = (ptrdiff_t) start; + tn->length = (ptrdiff_t) catchLength; + tn->catchStart = (ptrdiff_t) catchStart; + } + return JS_TRUE; + + error: + if (xdr->mode == JSXDR_DECODE) { + if (script->filename && !filenameWasSaved) { + JS_free(cx, (void *) script->filename); + script->filename = NULL; + } + if (notes && magic < JSXDR_MAGIC_SCRIPT_4) + JS_free(cx, (void *) notes); + js_DestroyScript(cx, script); + *scriptp = NULL; + } + return JS_FALSE; +} + +#if JS_HAS_XDR_FREEZE_THAW +/* + * These cannot be exposed to web content, and chrome does not need them, so + * we take them out of the Mozilla client altogether. Fortunately, there is + * no way to serialize a native function (see fun_xdrObject in jsfun.c). + */ + +static JSBool +script_freeze(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSXDRState *xdr; + JSScript *script; + JSBool ok, hasMagic; + uint32 len; + void *buf; + JSString *str; + + if (!JS_InstanceOf(cx, obj, &js_ScriptClass, argv)) + return JS_FALSE; + script = (JSScript *) JS_GetPrivate(cx, obj); + if (!script) + return JS_TRUE; + + /* create new XDR */ + xdr = JS_XDRNewMem(cx, JSXDR_ENCODE); + if (!xdr) + return JS_FALSE; + + /* write */ + ok = js_XDRScript(xdr, &script, &hasMagic); + if (!ok) + goto out; + if (!hasMagic) { + *rval = JSVAL_VOID; + goto out; + } + + buf = JS_XDRMemGetData(xdr, &len); + if (!buf) { + ok = JS_FALSE; + goto out; + } + + JS_ASSERT((jsword)buf % sizeof(jschar) == 0); + len /= sizeof(jschar); + str = JS_NewUCStringCopyN(cx, (jschar *)buf, len); + if (!str) { + ok = JS_FALSE; + goto out; + } + +#if IS_BIG_ENDIAN + { + jschar *chars; + uint32 i; + + /* Swap bytes in Unichars to keep frozen strings machine-independent. */ + chars = JS_GetStringChars(str); + for (i = 0; i < len; i++) + chars[i] = JSXDR_SWAB16(chars[i]); + } +#endif + *rval = STRING_TO_JSVAL(str); + +out: + JS_XDRDestroy(xdr); + return ok; +} + +static JSBool +script_thaw(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSXDRState *xdr; + JSString *str; + void *buf; + uint32 len; + JSScript *script, *oldscript; + JSBool ok, hasMagic; + + if (!JS_InstanceOf(cx, obj, &js_ScriptClass, argv)) + return JS_FALSE; + + if (argc == 0) + return JS_TRUE; + str = js_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + + /* create new XDR */ + xdr = JS_XDRNewMem(cx, JSXDR_DECODE); + if (!xdr) + return JS_FALSE; + + buf = JS_GetStringChars(str); + len = JS_GetStringLength(str); +#if IS_BIG_ENDIAN + { + jschar *from, *to; + uint32 i; + + /* Swap bytes in Unichars to keep frozen strings machine-independent. */ + from = (jschar *)buf; + to = (jschar *) JS_malloc(cx, len * sizeof(jschar)); + if (!to) { + JS_XDRDestroy(xdr); + return JS_FALSE; + } + for (i = 0; i < len; i++) + to[i] = JSXDR_SWAB16(from[i]); + buf = (char *)to; + } +#endif + len *= sizeof(jschar); + JS_XDRMemSetData(xdr, buf, len); + + /* XXXbe should magic mismatch be error, or false return value? */ + ok = js_XDRScript(xdr, &script, &hasMagic); + if (!ok) + goto out; + if (!hasMagic) { + *rval = JSVAL_FALSE; + goto out; + } + + /* Swap script for obj's old script, if any. */ + oldscript = (JSScript *) JS_GetPrivate(cx, obj); + ok = JS_SetPrivate(cx, obj, script); + if (!ok) { + JS_free(cx, script); + goto out; + } + if (oldscript) + js_DestroyScript(cx, oldscript); + + script->object = obj; + js_CallNewScriptHook(cx, script, NULL); + +out: + /* + * We reset the buffer to be NULL so that it doesn't free the chars + * memory owned by str (argv[0]). + */ + JS_XDRMemSetData(xdr, NULL, 0); + JS_XDRDestroy(xdr); +#if IS_BIG_ENDIAN + JS_free(cx, buf); +#endif + *rval = JSVAL_TRUE; + return ok; +} + +static const char js_thaw_str[] = "thaw"; + +#endif /* JS_HAS_XDR_FREEZE_THAW */ +#endif /* JS_HAS_XDR */ + +static JSFunctionSpec script_methods[] = { +#if JS_HAS_TOSOURCE + {js_toSource_str, script_toSource, 0,0,0}, +#endif + {js_toString_str, script_toString, 0,0,0}, + {"compile", script_compile, 2,0,0}, + {"exec", script_exec, 1,0,0}, +#if JS_HAS_XDR_FREEZE_THAW + {"freeze", script_freeze, 0,0,0}, + {js_thaw_str, script_thaw, 1,0,0}, +#endif /* JS_HAS_XDR_FREEZE_THAW */ + {0,0,0,0,0} +}; + +#endif /* JS_HAS_SCRIPT_OBJECT */ + +static void +script_finalize(JSContext *cx, JSObject *obj) +{ + JSScript *script; + + script = (JSScript *) JS_GetPrivate(cx, obj); + if (script) + js_DestroyScript(cx, script); +} + +static JSBool +script_call(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ +#if JS_HAS_SCRIPT_OBJECT + return script_exec(cx, JSVAL_TO_OBJECT(argv[-2]), argc, argv, rval); +#else + return JS_FALSE; +#endif +} + +static uint32 +script_mark(JSContext *cx, JSObject *obj, void *arg) +{ + JSScript *script; + + script = (JSScript *) JS_GetPrivate(cx, obj); + if (script) + js_MarkScript(cx, script, arg); + return 0; +} + +JS_FRIEND_DATA(JSClass) js_ScriptClass = { + js_Script_str, + JSCLASS_HAS_PRIVATE, + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, script_finalize, + NULL, NULL, script_call, NULL,/*XXXbe xdr*/ + NULL, NULL, script_mark, 0 +}; + +#if JS_HAS_SCRIPT_OBJECT + +static JSBool +Script(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + /* If not constructing, replace obj with a new Script object. */ + if (!(cx->fp->flags & JSFRAME_CONSTRUCTING)) { + obj = js_NewObject(cx, &js_ScriptClass, NULL, NULL); + if (!obj) + return JS_FALSE; + } + return script_compile(cx, obj, argc, argv, rval); +} + +#if JS_HAS_XDR_FREEZE_THAW + +static JSBool +script_static_thaw(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + obj = js_NewObject(cx, &js_ScriptClass, NULL, NULL); + if (!obj) + return JS_FALSE; + if (!script_thaw(cx, obj, argc, argv, rval)) + return JS_FALSE; + *rval = OBJECT_TO_JSVAL(obj); + return JS_TRUE; +} + +static JSFunctionSpec script_static_methods[] = { + {js_thaw_str, script_static_thaw, 1,0,0}, + {0,0,0,0,0} +}; + +#else /* !JS_HAS_XDR_FREEZE_THAW */ + +#define script_static_methods NULL + +#endif /* !JS_HAS_XDR_FREEZE_THAW */ + +JSObject * +js_InitScriptClass(JSContext *cx, JSObject *obj) +{ + return JS_InitClass(cx, obj, NULL, &js_ScriptClass, Script, 1, + NULL, script_methods, NULL, script_static_methods); +} + +#endif /* JS_HAS_SCRIPT_OBJECT */ + +/* + * Shared script filename management. + */ +static JSHashTable *script_filename_table; +#ifdef JS_THREADSAFE +static JSLock *script_filename_table_lock; +#endif + +JS_STATIC_DLL_CALLBACK(int) +js_compare_strings(const void *k1, const void *k2) +{ + return strcmp(k1, k2) == 0; +} + +/* Shared with jsatom.c to save code space. */ +extern void * JS_DLL_CALLBACK +js_alloc_table_space(void *priv, size_t size); + +extern void JS_DLL_CALLBACK +js_free_table_space(void *priv, void *item); + +/* NB: This struct overlays JSHashEntry -- see jshash.h, do not reorganize. */ +typedef struct ScriptFilenameEntry { + JSHashEntry *next; /* hash chain linkage */ + JSHashNumber keyHash; /* key hash function result */ + const void *key; /* ptr to filename, below */ + JSPackedBool mark; /* mark flag, for GC */ + char filename[3]; /* two or more bytes, NUL-terminated */ +} ScriptFilenameEntry; + +JS_STATIC_DLL_CALLBACK(JSHashEntry *) +js_alloc_entry(void *priv, const void *key) +{ + size_t nbytes = offsetof(ScriptFilenameEntry, filename) + strlen(key) + 1; + + return (JSHashEntry *) malloc(JS_MAX(nbytes, sizeof(JSHashEntry))); +} + +JS_STATIC_DLL_CALLBACK(void) +js_free_entry(void *priv, JSHashEntry *he, uintN flag) +{ + if (flag != HT_FREE_ENTRY) + return; + free(he); +} + +static JSHashAllocOps table_alloc_ops = { + js_alloc_table_space, js_free_table_space, + js_alloc_entry, js_free_entry +}; + +JSBool +js_InitScriptGlobals() +{ +#ifdef JS_THREADSAFE + /* Must come through here once in primordial thread to init safely! */ + if (!script_filename_table_lock) { + script_filename_table_lock = JS_NEW_LOCK(); + if (!script_filename_table_lock) + return JS_FALSE; + } +#endif + if (!script_filename_table) { + JS_ACQUIRE_LOCK(script_filename_table_lock); + if (!script_filename_table) { + script_filename_table = + JS_NewHashTable(16, JS_HashString, js_compare_strings, NULL, + &table_alloc_ops, NULL); + } + JS_RELEASE_LOCK(script_filename_table_lock); + if (!script_filename_table) + return JS_FALSE; + } + return JS_TRUE; +} + +void +js_FreeScriptGlobals() +{ + if (script_filename_table) { + JS_HashTableDestroy(script_filename_table); + script_filename_table = NULL; + } +#ifdef JS_THREADSAFE + if (script_filename_table_lock) { + JS_DESTROY_LOCK(script_filename_table_lock); + script_filename_table_lock = NULL; + } +#endif +} + +#ifdef DEBUG_brendan +size_t sft_savings = 0; +#endif + +const char * +js_SaveScriptFilename(JSContext *cx, const char *filename) +{ + JSHashTable *table; + JSHashNumber hash; + JSHashEntry **hep; + ScriptFilenameEntry *sfe; + + JS_ACQUIRE_LOCK(script_filename_table_lock); + table = script_filename_table; + hash = JS_HashString(filename); + hep = JS_HashTableRawLookup(table, hash, filename); + sfe = (ScriptFilenameEntry *) *hep; +#ifdef DEBUG_brendan + if (sfe) + sft_savings += strlen(sfe->filename); +#endif + if (!sfe) { + sfe = (ScriptFilenameEntry *) + JS_HashTableRawAdd(table, hep, hash, filename, NULL); + if (sfe) { + sfe->key = strcpy(sfe->filename, filename); + JS_ASSERT(!sfe->mark); + } + } + JS_RELEASE_LOCK(script_filename_table_lock); + if (!sfe) { + JS_ReportOutOfMemory(cx); + return NULL; + } + return sfe->filename; +} + +void +js_MarkScriptFilename(const char *filename) +{ + ScriptFilenameEntry *sfe; + + /* + * Back up from filename by its offset within its hash table entry. + * The sfe->key member, redundant given sfe->filename but required by + * the old jshash.c code, here gives us a useful sanity check. This + * assertion will very likely botch if someone tries to mark a string + * that wasn't allocated as an sfe->filename. + */ + sfe = (ScriptFilenameEntry *) + (filename - offsetof(ScriptFilenameEntry, filename)); + JS_ASSERT(sfe->key == sfe->filename); + sfe->mark = JS_TRUE; +} + +JS_STATIC_DLL_CALLBACK(intN) +js_script_filename_sweeper(JSHashEntry *he, intN i, void *arg) +{ + ScriptFilenameEntry *sfe = (ScriptFilenameEntry *) he; + + if (!sfe->mark) + return HT_ENUMERATE_REMOVE; + sfe->mark = JS_FALSE; + return HT_ENUMERATE_NEXT; +} + +void +js_SweepScriptFilenames(JSRuntime *rt) +{ + JS_HashTableEnumerateEntries(script_filename_table, + js_script_filename_sweeper, + rt); +#ifdef DEBUG_brendan + printf("script filename table savings so far: %u\n", sft_savings); +#endif +} + +JSScript * +js_NewScript(JSContext *cx, uint32 length, uint32 nsrcnotes, uint32 ntrynotes) +{ + JSScript *script; + + /* Round up source note count to align script->trynotes for its type. */ + if (ntrynotes) + nsrcnotes += JSTRYNOTE_ALIGNMASK; + script = (JSScript *) JS_malloc(cx, + sizeof(JSScript) + + length * sizeof(jsbytecode) + + nsrcnotes * sizeof(jssrcnote) + + ntrynotes * sizeof(JSTryNote)); + if (!script) + return NULL; + memset(script, 0, sizeof(JSScript)); + script->code = script->main = (jsbytecode *)(script + 1); + script->length = length; + script->version = cx->version; + if (ntrynotes) { + script->trynotes = (JSTryNote *) + ((jsword)(SCRIPT_NOTES(script) + nsrcnotes) & + ~(jsword)JSTRYNOTE_ALIGNMASK); + } + return script; +} + +JS_FRIEND_API(JSScript *) +js_NewScriptFromCG(JSContext *cx, JSCodeGenerator *cg, JSFunction *fun) +{ + uint32 mainLength, prologLength, nsrcnotes, ntrynotes; + JSScript *script; + const char *filename; + + mainLength = CG_OFFSET(cg); + prologLength = CG_PROLOG_OFFSET(cg); + CG_COUNT_FINAL_SRCNOTES(cg, nsrcnotes); + CG_COUNT_FINAL_TRYNOTES(cg, ntrynotes); + script = js_NewScript(cx, prologLength + mainLength, nsrcnotes, ntrynotes); + if (!script) + return NULL; + + /* Now that we have script, error control flow must go to label bad. */ + script->main += prologLength; + memcpy(script->code, CG_PROLOG_BASE(cg), prologLength * sizeof(jsbytecode)); + memcpy(script->main, CG_BASE(cg), mainLength * sizeof(jsbytecode)); + if (!js_InitAtomMap(cx, &script->atomMap, &cg->atomList)) + goto bad; + + filename = cg->filename; + if (filename) { + script->filename = js_SaveScriptFilename(cx, filename); + if (!script->filename) + goto bad; + } + script->lineno = cg->firstLine; + script->depth = cg->maxStackDepth; + if (cg->principals) { + script->principals = cg->principals; + JSPRINCIPALS_HOLD(cx, script->principals); + } + + if (!js_FinishTakingSrcNotes(cx, cg, SCRIPT_NOTES(script))) + goto bad; + if (script->trynotes) + js_FinishTakingTryNotes(cx, cg, script->trynotes); + + /* Tell the debugger about this compiled script. */ + js_CallNewScriptHook(cx, script, fun); + return script; + +bad: + js_DestroyScript(cx, script); + return NULL; +} + +JS_FRIEND_API(void) +js_CallNewScriptHook(JSContext *cx, JSScript *script, JSFunction *fun) +{ + JSRuntime *rt; + JSNewScriptHook hook; + + rt = cx->runtime; + hook = rt->newScriptHook; + if (hook) { + /* + * We use a dummy stack frame to protect the script from a GC caused + * by debugger-hook execution. + * + * XXX We really need a way to manage local roots and such more + * XXX automatically, at which point we can remove this one-off hack + * XXX and others within the engine. See bug 40757 for discussion. + */ + JSStackFrame dummy; + + memset(&dummy, 0, sizeof dummy); + dummy.down = cx->fp; + dummy.script = script; + cx->fp = &dummy; + + hook(cx, script->filename, script->lineno, script, fun, + rt->newScriptHookData); + + cx->fp = dummy.down; + } +} + +void +js_DestroyScript(JSContext *cx, JSScript *script) +{ + JSRuntime *rt; + JSDestroyScriptHook hook; + + rt = cx->runtime; + hook = rt->destroyScriptHook; + if (hook) + hook(cx, script, rt->destroyScriptHookData); + + JS_ClearScriptTraps(cx, script); + js_FreeAtomMap(cx, &script->atomMap); + if (script->principals) + JSPRINCIPALS_DROP(cx, script->principals); + JS_free(cx, script); +} + +void +js_MarkScript(JSContext *cx, JSScript *script, void *arg) +{ + JSAtomMap *map; + uintN i, length; + JSAtom **vector; + + map = &script->atomMap; + length = map->length; + vector = map->vector; + for (i = 0; i < length; i++) + GC_MARK_ATOM(cx, vector[i], arg); + + if (script->filename) + js_MarkScriptFilename(script->filename); +} + +jssrcnote * +js_GetSrcNote(JSScript *script, jsbytecode *pc) +{ + jssrcnote *sn; + ptrdiff_t offset, target; + + target = PTRDIFF(pc, script->code, jsbytecode); + if ((uint32)target >= script->length) + return NULL; + offset = 0; + for (sn = SCRIPT_NOTES(script); !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) { + offset += SN_DELTA(sn); + if (offset == target && SN_IS_GETTABLE(sn)) + return sn; + } + return NULL; +} + +uintN +js_PCToLineNumber(JSContext *cx, JSScript *script, jsbytecode *pc) +{ + JSAtom *atom; + JSFunction *fun; + uintN lineno; + ptrdiff_t offset, target; + jssrcnote *sn; + JSSrcNoteType type; + + /* + * Special case: function definition needs no line number note because + * the function's script contains its starting line number. + */ + if (*pc == JSOP_DEFFUN) { + atom = GET_ATOM(cx, script, pc); + fun = (JSFunction *) JS_GetPrivate(cx, ATOM_TO_OBJECT(atom)); + return fun->script->lineno; + } + + /* + * General case: walk through source notes accumulating their deltas, + * keeping track of line-number notes, until we pass the note for pc's + * offset within script->code. + */ + lineno = script->lineno; + offset = 0; + target = PTRDIFF(pc, script->code, jsbytecode); + for (sn = SCRIPT_NOTES(script); !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) { + offset += SN_DELTA(sn); + type = (JSSrcNoteType) SN_TYPE(sn); + if (type == SRC_SETLINE) { + if (offset <= target) + lineno = (uintN) js_GetSrcNoteOffset(sn, 0); + } else if (type == SRC_NEWLINE) { + if (offset <= target) + lineno++; + } + if (offset > target) + break; + } + return lineno; +} + +jsbytecode * +js_LineNumberToPC(JSScript *script, uintN target) +{ + ptrdiff_t offset; + uintN lineno; + jssrcnote *sn; + JSSrcNoteType type; + + offset = 0; + lineno = script->lineno; + for (sn = SCRIPT_NOTES(script); !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) { + if (lineno >= target) + break; + offset += SN_DELTA(sn); + type = (JSSrcNoteType) SN_TYPE(sn); + if (type == SRC_SETLINE) { + lineno = (uintN) js_GetSrcNoteOffset(sn, 0); + } else if (type == SRC_NEWLINE) { + lineno++; + } + } + return script->code + offset; +} + +uintN +js_GetScriptLineExtent(JSScript *script) +{ + uintN lineno; + jssrcnote *sn; + JSSrcNoteType type; + + lineno = script->lineno; + for (sn = SCRIPT_NOTES(script); !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) { + type = (JSSrcNoteType) SN_TYPE(sn); + if (type == SRC_SETLINE) { + lineno = (uintN) js_GetSrcNoteOffset(sn, 0); + } else if (type == SRC_NEWLINE) { + lineno++; + } + } + return 1 + lineno - script->lineno; +} diff --git a/src/extension/script/js/jsscript.h b/src/extension/script/js/jsscript.h new file mode 100644 index 000000000..ffcc40de7 --- /dev/null +++ b/src/extension/script/js/jsscript.h @@ -0,0 +1,178 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsscript_h___ +#define jsscript_h___ +/* + * JS script descriptor. + */ +#include "jsatom.h" +#include "jsprvtd.h" + +JS_BEGIN_EXTERN_C + +/* + * Exception handling runtime information. + * + * All fields except length are code offsets, relative to the beginning of + * the script. If script->trynotes is not null, it points to a vector of + * these structs terminated by one with catchStart == 0. + */ +struct JSTryNote { + ptrdiff_t start; /* start of try statement */ + ptrdiff_t length; /* count of try statement bytecodes */ + ptrdiff_t catchStart; /* start of catch block (0 if end) */ +}; + +#define JSTRYNOTE_GRAIN sizeof(ptrdiff_t) +#define JSTRYNOTE_ALIGNMASK (JSTRYNOTE_GRAIN - 1) + +struct JSScript { + jsbytecode *code; /* bytecodes and their immediate operands */ + uint32 length; /* length of code vector */ + jsbytecode *main; /* main entry point, after predef'ing prolog */ + JSVersion version; /* JS version under which script was compiled */ + JSAtomMap atomMap; /* maps immediate index to literal struct */ + const char *filename; /* source filename or null */ + uintN lineno; /* base line number of script */ + uintN depth; /* maximum stack depth in slots */ + JSTryNote *trynotes; /* exception table for this script */ + JSPrincipals *principals; /* principals for this script */ + JSObject *object; /* optional Script-class object wrapper */ +}; + +/* No need to store script->notes now that it is allocated right after code. */ +#define SCRIPT_NOTES(script) ((jssrcnote*)((script)->code+(script)->length)) + +#define SCRIPT_FIND_CATCH_START(script, pc, catchpc) \ + JS_BEGIN_MACRO \ + JSTryNote *tn_ = (script)->trynotes; \ + jsbytecode *catchpc_ = NULL; \ + if (tn_) { \ + ptrdiff_t off_ = PTRDIFF(pc, (script)->main, jsbytecode); \ + if (off_ >= 0) { \ + while ((jsuword)(off_ - tn_->start) >= (jsuword)tn_->length) \ + ++tn_; \ + if (tn_->catchStart) \ + catchpc_ = (script)->main + tn_->catchStart; \ + } \ + } \ + catchpc = catchpc_; \ + JS_END_MACRO + +extern JS_FRIEND_DATA(JSClass) js_ScriptClass; + +extern JSObject * +js_InitScriptClass(JSContext *cx, JSObject *obj); + +extern JSBool +js_InitScriptGlobals(); + +extern void +js_FreeScriptGlobals(); + +extern const char * +js_SaveScriptFilename(JSContext *cx, const char *filename); + +extern void +js_MarkScriptFilename(const char *filename); + +extern void +js_SweepScriptFilenames(JSRuntime *rt); + +/* + * Two successively less primitive ways to make a new JSScript. The first + * does *not* call a non-null cx->runtime->newScriptHook -- only the second, + * js_NewScriptFromCG, calls this optional debugger hook. + * + * The js_NewScript function can't know whether the script it creates belongs + * to a function, or is top-level or eval code, but the debugger wants access + * to the newly made script's function, if any -- so callers of js_NewScript + * are responsible for notifying the debugger after successfully creating any + * kind (function or other) of new JSScript. + */ +extern JSScript * +js_NewScript(JSContext *cx, uint32 length, uint32 snlength, uint32 tnlength); + +extern JS_FRIEND_API(JSScript *) +js_NewScriptFromCG(JSContext *cx, JSCodeGenerator *cg, JSFunction *fun); + +/* + * New-script-hook calling is factored from js_NewScriptFromCG so that it + * and callers of js_XDRScript can share this code. In the case of callers + * of js_XDRScript, the hook should be invoked only after successful decode + * of any owning function (the fun parameter) or script object (null fun). + */ +extern JS_FRIEND_API(void) +js_CallNewScriptHook(JSContext *cx, JSScript *script, JSFunction *fun); + +extern void +js_DestroyScript(JSContext *cx, JSScript *script); + +extern void +js_MarkScript(JSContext *cx, JSScript *script, void *arg); + +extern jssrcnote * +js_GetSrcNote(JSScript *script, jsbytecode *pc); + +/* XXX need cx to lock function objects declared by prolog bytecodes. */ +extern uintN +js_PCToLineNumber(JSContext *cx, JSScript *script, jsbytecode *pc); + +extern jsbytecode * +js_LineNumberToPC(JSScript *script, uintN lineno); + +extern uintN +js_GetScriptLineExtent(JSScript *script); + +/* + * If magic is non-null, js_XDRScript succeeds on magic number mismatch but + * returns false in *magic; it reflects a match via a true *magic out param. + * If magic is null, js_XDRScript returns false on bad magic number errors, + * which it reports. + * + * NB: callers must call js_CallNewScriptHook after successful JSXDR_DECODE + * and subsequent set-up of owning function or script object, if any. + */ +extern JSBool +js_XDRScript(JSXDRState *xdr, JSScript **scriptp, JSBool *magic); + +JS_END_EXTERN_C + +#endif /* jsscript_h___ */ diff --git a/src/extension/script/js/jsshell.msg b/src/extension/script/js/jsshell.msg new file mode 100644 index 000000000..4b811ac01 --- /dev/null +++ b/src/extension/script/js/jsshell.msg @@ -0,0 +1,50 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + Error messages for JSShell. See js.msg for format. +*/ + +MSG_DEF(JSSMSG_NOT_AN_ERROR, 0, 0, JSEXN_NONE, "") +MSG_DEF(JSSMSG_CANT_OPEN, 1, 2, JSEXN_NONE, "can't open {0}: {1}") +MSG_DEF(JSSMSG_TRAP_USAGE, 2, 0, JSEXN_NONE, "usage: trap [fun] [pc] expr") +MSG_DEF(JSSMSG_LINE2PC_USAGE, 3, 0, JSEXN_NONE, "usage: line2pc [fun] line") +MSG_DEF(JSSMSG_FILE_SCRIPTS_ONLY, 4, 0, JSEXN_NONE, "only works on JS scripts read from files") +MSG_DEF(JSSMSG_UNEXPECTED_EOF, 5, 1, JSEXN_NONE, "unexpected EOF in {0}") +MSG_DEF(JSSMSG_DOEXP_USAGE, 6, 0, JSEXN_NONE, "usage: doexp obj id") diff --git a/src/extension/script/js/jsstddef.h b/src/extension/script/js/jsstddef.h new file mode 100644 index 000000000..0d87b0c0c --- /dev/null +++ b/src/extension/script/js/jsstddef.h @@ -0,0 +1,83 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * stddef inclusion here to first declare ptrdif as a signed long instead of a + * signed int. + */ + +#ifdef _WINDOWS +# ifndef XP_WIN +# define XP_WIN +# endif +#if defined(_WIN32) || defined(WIN32) +# ifndef XP_WIN32 +# define XP_WIN32 +# endif +#else +# ifndef XP_WIN16 +# define XP_WIN16 +# endif +#endif +#endif + +#ifdef XP_WIN16 +#ifndef _PTRDIFF_T_DEFINED +typedef long ptrdiff_t; + +/* + * The Win16 compiler treats pointer differences as 16-bit signed values. + * This macro allows us to treat them as 17-bit signed values, stored in + * a 32-bit type. + */ +#define PTRDIFF(p1, p2, type) \ + ((((unsigned long)(p1)) - ((unsigned long)(p2))) / sizeof(type)) + +#define _PTRDIFF_T_DEFINED +#endif /*_PTRDIFF_T_DEFINED*/ +#else /*WIN16*/ + +#define PTRDIFF(p1, p2, type) \ + ((p1) - (p2)) + +#endif + +#include + + diff --git a/src/extension/script/js/jsstr.c b/src/extension/script/js/jsstr.c new file mode 100644 index 000000000..e143ab8df --- /dev/null +++ b/src/extension/script/js/jsstr.c @@ -0,0 +1,4502 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS string type implementation. + * + * In order to avoid unnecessary js_LockGCThing/js_UnlockGCThing calls, these + * native methods store strings (possibly newborn) converted from their 'this' + * parameter and arguments on the stack: 'this' conversions at argv[-1], arg + * conversions at their index (argv[0], argv[1]). This is a legitimate method + * of rooting things that might lose their newborn root due to subsequent GC + * allocations in the same native method. + */ +#include "jsstddef.h" +#include +#include +#include "jstypes.h" +#include "jsutil.h" /* Added by JSIFY */ +#include "jshash.h" /* Added by JSIFY */ +#include "jsprf.h" +#include "jsapi.h" +#include "jsarray.h" +#include "jsatom.h" +#include "jsbool.h" +#include "jscntxt.h" +#include "jsconfig.h" +#include "jsgc.h" +#include "jsinterp.h" +#include "jslock.h" +#include "jsnum.h" +#include "jsobj.h" +#include "jsopcode.h" +#include "jsregexp.h" +#include "jsstr.h" + +#if JS_HAS_REPLACE_LAMBDA +#include "jsinterp.h" +#endif + +#define JSSTRDEP_RECURSION_LIMIT 100 + +size_t +js_MinimizeDependentStrings(JSString *str, int level, JSString **basep) +{ + JSString *base; + size_t start, length; + + JS_ASSERT(JSSTRING_IS_DEPENDENT(str)); + base = JSSTRDEP_BASE(str); + start = JSSTRDEP_START(str); + if (JSSTRING_IS_DEPENDENT(base)) { + if (level < JSSTRDEP_RECURSION_LIMIT) { + start += js_MinimizeDependentStrings(base, level + 1, &base); + } else { + do { + start += JSSTRDEP_START(base); + base = JSSTRDEP_BASE(base); + } while (JSSTRING_IS_DEPENDENT(base)); + } + if (start == 0) { + JS_ASSERT(JSSTRING_IS_PREFIX(str)); + JSPREFIX_SET_BASE(str, base); + } else if (start <= JSSTRDEP_START_MASK) { + length = JSSTRDEP_LENGTH(str); + JSSTRDEP_SET_START_AND_LENGTH(str, start, length); + JSSTRDEP_SET_BASE(str, base); + } + } + *basep = base; + return start; +} + +jschar * +js_GetDependentStringChars(JSString *str) +{ + size_t start; + JSString *base; + + start = js_MinimizeDependentStrings(str, 0, &base); + JS_ASSERT(!JSSTRING_IS_DEPENDENT(base)); + JS_ASSERT(start < base->length); + return base->chars + start; +} + +jschar * +js_GetStringChars(JSString *str) +{ + if (JSSTRING_IS_DEPENDENT(str) && !js_UndependString(NULL, str)) + return NULL; + + *js_GetGCThingFlags(str) &= ~GCF_MUTABLE; + return str->chars; +} + +JSString * +js_ConcatStrings(JSContext *cx, JSString *left, JSString *right) +{ + size_t rn, ln, lrdist, n; + jschar *rs, *ls, *s; + JSDependentString *ldep; /* non-null if left should become dependent */ + JSString *str; + + if (JSSTRING_IS_DEPENDENT(right)) { + rn = JSSTRDEP_LENGTH(right); + rs = JSSTRDEP_CHARS(right); + } else { + rn = right->length; + rs = right->chars; + } + if (rn == 0) + return left; + + if (JSSTRING_IS_DEPENDENT(left) || + !(*js_GetGCThingFlags(left) & GCF_MUTABLE)) { + /* We must copy if left does not own a buffer to realloc. */ + ln = JSSTRING_LENGTH(left); + if (ln == 0) + return right; + ls = JSSTRING_CHARS(left); + s = (jschar *) JS_malloc(cx, (ln + rn + 1) * sizeof(jschar)); + if (!s) + return NULL; + js_strncpy(s, ls, ln); + ldep = NULL; + } else { + /* We can realloc left's space and make it depend on our result. */ + ln = left->length; + if (ln == 0) + return right; + ls = left->chars; + s = (jschar *) JS_realloc(cx, ls, (ln + rn + 1) * sizeof(jschar)); + if (!s) + return NULL; + + /* Take care: right could depend on left! */ + lrdist = (size_t)(rs - ls); + if (lrdist < ln) + rs = s + lrdist; + left->chars = ls = s; + ldep = JSSTRDEP(left); + } + + js_strncpy(s + ln, rs, rn); + n = ln + rn; + s[n] = 0; + str = js_NewString(cx, s, n, GCF_MUTABLE); + if (!str) { + /* Out of memory: clean up any space we (re-)allocated. */ + if (!ldep) { + JS_free(cx, s); + } else { + s = JS_realloc(cx, ls, (ln + 1) * sizeof(jschar)); + if (s) + left->chars = s; + } + } else { + /* Morph left into a dependent prefix if we realloc'd its buffer. */ + if (ldep) { + JSPREFIX_SET_LENGTH(ldep, ln); + JSPREFIX_SET_BASE(ldep, str); +#ifdef DEBUG + { + JSRuntime *rt = cx->runtime; + JS_RUNTIME_METER(rt, liveDependentStrings); + JS_RUNTIME_METER(rt, totalDependentStrings); + JS_LOCK_RUNTIME_VOID(rt, + (rt->strdepLengthSum += (double)ln, + rt->strdepLengthSquaredSum += (double)ln * (double)ln)); + } +#endif + } + } + + return str; +} + +/* + * May be called with null cx by js_GetStringChars, above; and by the jslock.c + * MAKE_STRING_IMMUTABLE file-local macro. + */ +const jschar * +js_UndependString(JSContext *cx, JSString *str) +{ + size_t n, size; + jschar *s; + + if (JSSTRING_IS_DEPENDENT(str)) { + n = JSSTRDEP_LENGTH(str); + size = (n + 1) * sizeof(jschar); + s = (jschar *) (cx ? JS_malloc(cx, size) : malloc(size)); + if (!s) + return NULL; + + js_strncpy(s, JSSTRDEP_CHARS(str), n); + s[n] = 0; + str->length = n; + str->chars = s; + +#ifdef DEBUG + if (cx) { + JSRuntime *rt = cx->runtime; + JS_RUNTIME_UNMETER(rt, liveDependentStrings); + JS_RUNTIME_UNMETER(rt, totalDependentStrings); + JS_LOCK_RUNTIME_VOID(rt, + (rt->strdepLengthSum -= (double)n, + rt->strdepLengthSquaredSum -= (double)n * (double)n)); + } +#endif + } + + return str->chars; +} + +/* + * Forward declarations for URI encode/decode and helper routines + */ +static JSBool +str_decodeURI(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval); + +static JSBool +str_decodeURI_Component(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval); + +static JSBool +str_encodeURI(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval); + +static JSBool +str_encodeURI_Component(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval); + +static int +OneUcs4ToUtf8Char(uint8 *utf8Buffer, uint32 ucs4Char); + +static uint32 +Utf8ToOneUcs4Char(const uint8 *utf8Buffer, int utf8Length); + +/* + * Contributions from the String class to the set of methods defined for the + * global object. escape and unescape used to be defined in the Mocha library, + * but as ECMA decided to spec them, they've been moved to the core engine + * and made ECMA-compliant. (Incomplete escapes are interpreted as literal + * characters by unescape.) + */ + +/* + * Stuff to emulate the old libmocha escape, which took a second argument + * giving the type of escape to perform. Retained for compatibility, and + * copied here to avoid reliance on net.h, mkparse.c/NET_EscapeBytes. + */ + +#define URL_XALPHAS ((uint8) 1) +#define URL_XPALPHAS ((uint8) 2) +#define URL_PATH ((uint8) 4) + +static const uint8 urlCharType[256] = +/* Bit 0 xalpha -- the alphas + * Bit 1 xpalpha -- as xalpha but + * converts spaces to plus and plus to %20 + * Bit 2 ... path -- as xalphas but doesn't escape '/' + */ + /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0x */ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 1x */ + 0,0,0,0,0,0,0,0,0,0,7,4,0,7,7,4, /* 2x !"#$%&'()*+,-./ */ + 7,7,7,7,7,7,7,7,7,7,0,0,0,0,0,0, /* 3x 0123456789:;<=>? */ + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, /* 4x @ABCDEFGHIJKLMNO */ + 7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,7, /* 5X PQRSTUVWXYZ[\]^_ */ + 0,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, /* 6x `abcdefghijklmno */ + 7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,0, /* 7X pqrstuvwxyz{\}~ DEL */ + 0, }; + +/* This matches the ECMA escape set when mask is 7 (default.) */ + +#define IS_OK(C, mask) (urlCharType[((uint8) (C))] & (mask)) + +/* See ECMA-262 15.1.2.4. */ +JSBool +js_str_escape(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str; + size_t i, ni, length, newlength; + const jschar *chars; + jschar *newchars; + jschar ch; + jsint mask; + jsdouble d; + const char digits[] = {'0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + + mask = URL_XALPHAS | URL_XPALPHAS | URL_PATH; + if (argc > 1) { + if (!js_ValueToNumber(cx, argv[1], &d)) + return JS_FALSE; + if (!JSDOUBLE_IS_FINITE(d) || + (mask = (jsint)d) != d || + mask & ~(URL_XALPHAS | URL_XPALPHAS | URL_PATH)) + { + char numBuf[12]; + JS_snprintf(numBuf, sizeof numBuf, "%lx", (unsigned long) mask); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_STRING_MASK, numBuf); + return JS_FALSE; + } + } + + str = js_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + argv[0] = STRING_TO_JSVAL(str); + + chars = JSSTRING_CHARS(str); + length = newlength = JSSTRING_LENGTH(str); + + /* Take a first pass and see how big the result string will need to be. */ + for (i = 0; i < length; i++) { + if ((ch = chars[i]) < 128 && IS_OK(ch, mask)) + continue; + if (ch < 256) { + if (mask == URL_XPALPHAS && ch == ' ') + continue; /* The character will be encoded as '+' */ + newlength += 2; /* The character will be encoded as %XX */ + } else { + newlength += 5; /* The character will be encoded as %uXXXX */ + } + } + + newchars = (jschar *) JS_malloc(cx, (newlength + 1) * sizeof(jschar)); + if (!newchars) + return JS_FALSE; + for (i = 0, ni = 0; i < length; i++) { + if ((ch = chars[i]) < 128 && IS_OK(ch, mask)) { + newchars[ni++] = ch; + } else if (ch < 256) { + if (mask == URL_XPALPHAS && ch == ' ') { + newchars[ni++] = '+'; /* convert spaces to pluses */ + } else { + newchars[ni++] = '%'; + newchars[ni++] = digits[ch >> 4]; + newchars[ni++] = digits[ch & 0xF]; + } + } else { + newchars[ni++] = '%'; + newchars[ni++] = 'u'; + newchars[ni++] = digits[ch >> 12]; + newchars[ni++] = digits[(ch & 0xF00) >> 8]; + newchars[ni++] = digits[(ch & 0xF0) >> 4]; + newchars[ni++] = digits[ch & 0xF]; + } + } + JS_ASSERT(ni == newlength); + newchars[newlength] = 0; + + str = js_NewString(cx, newchars, newlength, 0); + if (!str) { + JS_free(cx, newchars); + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} +#undef IS_OK + +/* See ECMA-262 15.1.2.5 */ +static JSBool +str_unescape(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str; + size_t i, ni, length; + const jschar *chars; + jschar *newchars; + jschar ch; + + str = js_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + argv[0] = STRING_TO_JSVAL(str); + + chars = JSSTRING_CHARS(str); + length = JSSTRING_LENGTH(str); + + /* Don't bother allocating less space for the new string. */ + newchars = (jschar *) JS_malloc(cx, (length + 1) * sizeof(jschar)); + if (!newchars) + return JS_FALSE; + ni = i = 0; + while (i < length) { + ch = chars[i++]; + if (ch == '%') { + if (i + 1 < length && + JS7_ISHEX(chars[i]) && JS7_ISHEX(chars[i + 1])) + { + ch = JS7_UNHEX(chars[i]) * 16 + JS7_UNHEX(chars[i + 1]); + i += 2; + } else if (i + 4 < length && chars[i] == 'u' && + JS7_ISHEX(chars[i + 1]) && JS7_ISHEX(chars[i + 2]) && + JS7_ISHEX(chars[i + 3]) && JS7_ISHEX(chars[i + 4])) + { + ch = (((((JS7_UNHEX(chars[i + 1]) << 4) + + JS7_UNHEX(chars[i + 2])) << 4) + + JS7_UNHEX(chars[i + 3])) << 4) + + JS7_UNHEX(chars[i + 4]); + i += 5; + } + } + newchars[ni++] = ch; + } + newchars[ni] = 0; + + str = js_NewString(cx, newchars, ni, 0); + if (!str) { + JS_free(cx, newchars); + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +#if JS_HAS_UNEVAL +static JSBool +str_uneval(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str; + + str = js_ValueToSource(cx, argv[0]); + if (!str) + return JS_FALSE; + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} +#endif + +const char js_escape_str[] = "escape"; +const char js_unescape_str[] = "unescape"; +#if JS_HAS_UNEVAL +const char js_uneval_str[] = "uneval"; +#endif +const char js_decodeURI_str[] = "decodeURI"; +const char js_encodeURI_str[] = "encodeURI"; +const char js_decodeURIComponent_str[] = "decodeURIComponent"; +const char js_encodeURIComponent_str[] = "encodeURIComponent"; + +static JSFunctionSpec string_functions[] = { + {js_escape_str, js_str_escape, 1,0,0}, + {js_unescape_str, str_unescape, 1,0,0}, +#if JS_HAS_UNEVAL + {js_uneval_str, str_uneval, 1,0,0}, +#endif + {js_decodeURI_str, str_decodeURI, 1,0,0}, + {js_encodeURI_str, str_encodeURI, 1,0,0}, + {js_decodeURIComponent_str, str_decodeURI_Component, 1,0,0}, + {js_encodeURIComponent_str, str_encodeURI_Component, 1,0,0}, + + {0,0,0,0,0} +}; + +jschar js_empty_ucstr[] = {0}; +JSSubString js_EmptySubString = {0, js_empty_ucstr}; + +enum string_tinyid { + STRING_LENGTH = -1 +}; + +static JSPropertySpec string_props[] = { + {js_length_str, STRING_LENGTH, + JSPROP_READONLY|JSPROP_PERMANENT|JSPROP_SHARED, 0,0}, + {0,0,0,0,0} +}; + +static JSBool +str_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + JSString *str; + jsint slot; + + if (!JSVAL_IS_INT(id)) + return JS_TRUE; + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + slot = JSVAL_TO_INT(id); + if (slot == STRING_LENGTH) + *vp = INT_TO_JSVAL((jsint) JSSTRING_LENGTH(str)); + return JS_TRUE; +} + +#define STRING_ELEMENT_ATTRS (JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT) + +static JSBool +str_enumerate(JSContext *cx, JSObject *obj) +{ + JSString *str, *str1; + size_t i, length; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + length = JSSTRING_LENGTH(str); + for (i = 0; i < length; i++) { + str1 = js_NewDependentString(cx, str, i, 1, 0); + if (!str1) + return JS_FALSE; + if (!OBJ_DEFINE_PROPERTY(cx, obj, INT_TO_JSVAL(i), + STRING_TO_JSVAL(str1), NULL, NULL, + STRING_ELEMENT_ATTRS, NULL)) { + return JS_FALSE; + } + } + return JS_TRUE; +} + +static JSBool +str_resolve(JSContext *cx, JSObject *obj, jsval id) +{ + JSString *str, *str1; + jsint slot; + + if (!JSVAL_IS_INT(id)) + return JS_TRUE; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + slot = JSVAL_TO_INT(id); + if ((size_t)slot < JSSTRING_LENGTH(str)) { + str1 = js_NewDependentString(cx, str, (size_t)slot, 1, 0); + if (!str1) + return JS_FALSE; + if (!OBJ_DEFINE_PROPERTY(cx, obj, INT_TO_JSVAL(slot), + STRING_TO_JSVAL(str1), NULL, NULL, + STRING_ELEMENT_ATTRS, NULL)) { + return JS_FALSE; + } + } + return JS_TRUE; +} + +static JSClass string_class = { + js_String_str, + JSCLASS_HAS_PRIVATE, + JS_PropertyStub, JS_PropertyStub, str_getProperty, JS_PropertyStub, + str_enumerate, str_resolve, JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS +}; + +#if JS_HAS_TOSOURCE + +/* + * String.prototype.quote is generic (as are most string methods), unlike + * toSource, toString, and valueOf. + */ +static JSBool +str_quote(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + str = js_QuoteString(cx, str, '"'); + if (!str) + return JS_FALSE; + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +static JSBool +str_toSource(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsval v; + JSString *str; + size_t i, j, k, n; + char buf[16]; + jschar *s, *t; + + if (!JS_InstanceOf(cx, obj, &string_class, argv)) + return JS_FALSE; + v = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE); + if (!JSVAL_IS_STRING(v)) + return js_obj_toSource(cx, obj, argc, argv, rval); + str = js_QuoteString(cx, JSVAL_TO_STRING(v), '"'); + if (!str) + return JS_FALSE; + j = JS_snprintf(buf, sizeof buf, "(new %s(", string_class.name); + s = JSSTRING_CHARS(str); + k = JSSTRING_LENGTH(str); + n = j + k + 2; + t = (jschar *) JS_malloc(cx, (n + 1) * sizeof(jschar)); + if (!t) + return JS_FALSE; + for (i = 0; i < j; i++) + t[i] = buf[i]; + for (j = 0; j < k; i++, j++) + t[i] = s[j]; + t[i++] = ')'; + t[i++] = ')'; + t[i] = 0; + str = js_NewString(cx, t, n, 0); + if (!str) { + JS_free(cx, t); + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +#endif /* JS_HAS_TOSOURCE */ + +static JSBool +str_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + jsval v; + + if (!JS_InstanceOf(cx, obj, &string_class, argv)) + return JS_FALSE; + v = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE); + if (!JSVAL_IS_STRING(v)) + return js_obj_toString(cx, obj, argc, argv, rval); + *rval = v; + return JS_TRUE; +} + +static JSBool +str_valueOf(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + if (!JS_InstanceOf(cx, obj, &string_class, argv)) + return JS_FALSE; + *rval = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE); + return JS_TRUE; +} + +/* + * Java-like string native methods. + */ +static JSBool +str_substring(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSString *str; + jsdouble d; + jsdouble length, begin, end; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + argv[-1] = STRING_TO_JSVAL(str); + + if (argc != 0) { + if (!js_ValueToNumber(cx, argv[0], &d)) + return JS_FALSE; + length = JSSTRING_LENGTH(str); + begin = js_DoubleToInteger(d); + if (begin < 0) + begin = 0; + else if (begin > length) + begin = length; + + if (argc == 1) { + end = length; + } else { + if (!js_ValueToNumber(cx, argv[1], &d)) + return JS_FALSE; + end = js_DoubleToInteger(d); + if (end < 0) + end = 0; + else if (end > length) + end = length; + if (end < begin) { + if (cx->version != JSVERSION_1_2) { + /* XXX emulate old JDK1.0 java.lang.String.substring. */ + jsdouble tmp = begin; + begin = end; + end = tmp; + } else { + end = begin; + } + } + } + + str = js_NewDependentString(cx, str, (size_t)begin, + (size_t)(end - begin), 0); + if (!str) + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +static JSBool +str_toLowerCase(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSString *str; + size_t i, n; + jschar *s, *news; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + n = JSSTRING_LENGTH(str); + news = (jschar *) JS_malloc(cx, (n + 1) * sizeof(jschar)); + if (!news) + return JS_FALSE; + s = JSSTRING_CHARS(str); + for (i = 0; i < n; i++) + news[i] = JS_TOLOWER(s[i]); + news[n] = 0; + str = js_NewString(cx, news, n, 0); + if (!str) { + JS_free(cx, news); + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +static JSBool +str_toLocaleLowerCase(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSString *str; + + /* + * Forcefully ignore the first (or any) argument and return toLowerCase(), + * ECMA has reserved that argument, presumably for defining the locale. + */ + if (cx->localeCallbacks && cx->localeCallbacks->localeToLowerCase) { + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + return cx->localeCallbacks->localeToLowerCase(cx, str, rval); + } + return str_toLowerCase(cx, obj, 0, argv, rval); +} + +static JSBool +str_toUpperCase(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSString *str; + size_t i, n; + jschar *s, *news; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + n = JSSTRING_LENGTH(str); + news = (jschar *) JS_malloc(cx, (n + 1) * sizeof(jschar)); + if (!news) + return JS_FALSE; + s = JSSTRING_CHARS(str); + for (i = 0; i < n; i++) + news[i] = JS_TOUPPER(s[i]); + news[n] = 0; + str = js_NewString(cx, news, n, 0); + if (!str) { + JS_free(cx, news); + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +static JSBool +str_toLocaleUpperCase(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSString *str; + + /* + * Forcefully ignore the first (or any) argument and return toUpperCase(), + * ECMA has reserved that argument, presumbaly for defining the locale. + */ + if (cx->localeCallbacks && cx->localeCallbacks->localeToUpperCase) { + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + return cx->localeCallbacks->localeToUpperCase(cx, str, rval); + } + return str_toUpperCase(cx, obj, 0, argv, rval); +} + +static JSBool +str_localeCompare(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSString *str, *thatStr; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + argv[-1] = STRING_TO_JSVAL(str); + + if (argc == 0) { + *rval = JSVAL_ZERO; + } else { + thatStr = js_ValueToString(cx, argv[0]); + if (!thatStr) + return JS_FALSE; + if (cx->localeCallbacks && cx->localeCallbacks->localeCompare) + return cx->localeCallbacks->localeCompare(cx, str, thatStr, rval); + *rval = INT_TO_JSVAL(js_CompareStrings(str, thatStr)); + } + return JS_TRUE; +} + +static JSBool +str_charAt(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str; + jsdouble d; + size_t index; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + argv[-1] = STRING_TO_JSVAL(str); + + if (argc == 0) { + d = 0.0; + } else { + if (!js_ValueToNumber(cx, argv[0], &d)) + return JS_FALSE; + d = js_DoubleToInteger(d); + } + + if (d < 0 || JSSTRING_LENGTH(str) <= d) { + *rval = JS_GetEmptyStringValue(cx); + } else { + index = (size_t)d; + str = js_NewDependentString(cx, str, index, 1, 0); + if (!str) + return JS_FALSE; + *rval = STRING_TO_JSVAL(str); + } + return JS_TRUE; +} + +static JSBool +str_charCodeAt(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSString *str; + jsdouble d; + size_t index; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + argv[-1] = STRING_TO_JSVAL(str); + + if (argc == 0) { + d = 0.0; + } else { + if (!js_ValueToNumber(cx, argv[0], &d)) + return JS_FALSE; + d = js_DoubleToInteger(d); + } + + if (d < 0 || JSSTRING_LENGTH(str) <= d) { + *rval = JS_GetNaNValue(cx); + } else { + index = (size_t)d; + *rval = INT_TO_JSVAL((jsint) JSSTRING_CHARS(str)[index]); + } + return JS_TRUE; +} + +jsint +js_BoyerMooreHorspool(const jschar *text, jsint textlen, + const jschar *pat, jsint patlen, + jsint start) +{ + jsint i, j, k, m; + uint8 skip[BMH_CHARSET_SIZE]; + jschar c; + + JS_ASSERT(0 < patlen && patlen <= BMH_PATLEN_MAX); + for (i = 0; i < BMH_CHARSET_SIZE; i++) + skip[i] = (uint8)patlen; + m = patlen - 1; + for (i = 0; i < m; i++) { + c = pat[i]; + if (c >= BMH_CHARSET_SIZE) + return BMH_BAD_PATTERN; + skip[c] = (uint8)(m - i); + } + for (k = start + m; + k < textlen; + k += ((c = text[k]) >= BMH_CHARSET_SIZE) ? patlen : skip[c]) { + for (i = k, j = m; ; i--, j--) { + if (j < 0) + return i + 1; + if (text[i] != pat[j]) + break; + } + } + return -1; +} + +static JSBool +str_indexOf(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str, *str2; + jsint i, j, index, textlen, patlen; + const jschar *text, *pat; + jsdouble d; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + argv[-1] = STRING_TO_JSVAL(str); + text = JSSTRING_CHARS(str); + textlen = (jsint) JSSTRING_LENGTH(str); + + str2 = js_ValueToString(cx, argv[0]); + if (!str2) + return JS_FALSE; + argv[0] = STRING_TO_JSVAL(str2); + pat = JSSTRING_CHARS(str2); + patlen = (jsint) JSSTRING_LENGTH(str2); + + if (argc > 1) { + if (!js_ValueToNumber(cx, argv[1], &d)) + return JS_FALSE; + d = js_DoubleToInteger(d); + if (d < 0) + i = 0; + else if (d > textlen) + i = textlen; + else + i = (jsint)d; + } else { + i = 0; + } + if (patlen == 0) { + *rval = INT_TO_JSVAL(i); + return JS_TRUE; + } + + /* XXX tune the BMH threshold (512) */ + if ((jsuint)(patlen - 2) <= BMH_PATLEN_MAX - 2 && textlen >= 512) { + index = js_BoyerMooreHorspool(text, textlen, pat, patlen, i); + if (index != BMH_BAD_PATTERN) + goto out; + } + + index = -1; + j = 0; + while (i + j < textlen) { + if (text[i + j] == pat[j]) { + if (++j == patlen) { + index = i; + break; + } + } else { + i++; + j = 0; + } + } + +out: + *rval = INT_TO_JSVAL(index); + return JS_TRUE; +} + +static JSBool +str_lastIndexOf(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSString *str, *str2; + const jschar *text, *pat; + jsint i, j, textlen, patlen; + jsdouble d; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + argv[-1] = STRING_TO_JSVAL(str); + text = JSSTRING_CHARS(str); + textlen = (jsint) JSSTRING_LENGTH(str); + + str2 = js_ValueToString(cx, argv[0]); + if (!str2) + return JS_FALSE; + argv[0] = STRING_TO_JSVAL(str2); + pat = JSSTRING_CHARS(str2); + patlen = (jsint) JSSTRING_LENGTH(str2); + + if (argc > 1) { + if (!js_ValueToNumber(cx, argv[1], &d)) + return JS_FALSE; + if (JSDOUBLE_IS_NaN(d)) { + i = textlen; + } else { + d = js_DoubleToInteger(d); + if (d < 0) + i = 0; + else if (d > textlen - patlen) + i = textlen - patlen; + else + i = (jsint)d; + } + } else { + i = textlen; + } + + if (patlen == 0) { + *rval = INT_TO_JSVAL(i); + return JS_TRUE; + } + + j = 0; + while (i >= 0) { + /* Don't assume that text is NUL-terminated: it could be dependent. */ + if (i + j < textlen && text[i + j] == pat[j]) { + if (++j == patlen) + break; + } else { + i--; + j = 0; + } + } + *rval = INT_TO_JSVAL(i); + return JS_TRUE; +} + +/* + * Perl-inspired string functions. + */ +#if JS_HAS_REGEXPS +typedef struct GlobData { + uintN flags; /* inout: mode and flag bits, see below */ + uintN optarg; /* in: index of optional flags argument */ + JSString *str; /* out: 'this' parameter object as string */ + JSRegExp *regexp; /* out: regexp parameter object private data */ +} GlobData; + +/* + * Mode and flag bit definitions for match_or_replace's GlobData.flags field. + */ +#define MODE_MATCH 0x00 /* in: return match array on success */ +#define MODE_REPLACE 0x01 /* in: match and replace */ +#define MODE_SEARCH 0x02 /* in: search only, return match index or -1 */ +#define GET_MODE(f) ((f) & 0x03) +#define FORCE_FLAT 0x04 /* in: force flat (non-regexp) string match */ +#define KEEP_REGEXP 0x08 /* inout: keep GlobData.regexp alive for caller + of match_or_replace; if set on input + but clear on output, regexp ownership + does not pass to caller */ +#define GLOBAL_REGEXP 0x10 /* out: regexp had the 'g' flag */ + +static JSBool +match_or_replace(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + JSBool (*glob)(JSContext *cx, jsint count, GlobData *data), + GlobData *data, jsval *rval) +{ + JSString *str, *src, *opt; + JSObject *reobj; + JSRegExp *re; + size_t index, length; + JSBool ok, test; + jsint count; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + argv[-1] = STRING_TO_JSVAL(str); + data->str = str; + + if (JSVAL_IS_REGEXP(cx, argv[0])) { + reobj = JSVAL_TO_OBJECT(argv[0]); + re = (JSRegExp *) JS_GetPrivate(cx, reobj); + } else { + src = js_ValueToString(cx, argv[0]); + if (!src) + return JS_FALSE; + if (data->optarg < argc) { + argv[0] = STRING_TO_JSVAL(src); + opt = js_ValueToString(cx, argv[data->optarg]); + if (!opt) + return JS_FALSE; + } else { + opt = NULL; + } + re = js_NewRegExpOpt(cx, NULL, src, opt, + (data->flags & FORCE_FLAT) != 0); + if (!re) + return JS_FALSE; + reobj = NULL; + } + data->regexp = re; + + if (re->flags & JSREG_GLOB) + data->flags |= GLOBAL_REGEXP; + index = 0; + if (GET_MODE(data->flags) == MODE_SEARCH) { + ok = js_ExecuteRegExp(cx, re, str, &index, JS_TRUE, rval); + if (ok) { + *rval = (*rval == JSVAL_TRUE) + ? INT_TO_JSVAL(cx->regExpStatics.leftContext.length) + : INT_TO_JSVAL(-1); + } + } else if (data->flags & GLOBAL_REGEXP) { + if (reobj) { + /* Set the lastIndex property's reserved slot to 0. */ + ok = js_SetLastIndex(cx, reobj, 0); + if (!ok) + return JS_FALSE; + } else { + ok = JS_TRUE; + } + length = JSSTRING_LENGTH(str); + for (count = 0; index <= length; count++) { + ok = js_ExecuteRegExp(cx, re, str, &index, JS_TRUE, rval); + if (!ok || *rval != JSVAL_TRUE) + break; + ok = glob(cx, count, data); + if (!ok) + break; + if (cx->regExpStatics.lastMatch.length == 0) { + if (index == length) + break; + index++; + } + } + } else { + if (GET_MODE(data->flags) == MODE_REPLACE) { + test = JS_TRUE; + } else { + /* + * MODE_MATCH implies str_match is being called from a script or a + * scripted function. If the caller cares only about testing null + * vs. non-null return value, optimize away the array object that + * would normally be returned in *rval. + */ + JS_ASSERT(*cx->fp->down->pc == JSOP_CALL || + *cx->fp->down->pc == JSOP_NEW); + JS_ASSERT(js_CodeSpec[*cx->fp->down->pc].length == 3); + switch (cx->fp->down->pc[3]) { + case JSOP_POP: + case JSOP_IFEQ: + case JSOP_IFNE: + case JSOP_IFEQX: + case JSOP_IFNEX: + test = JS_TRUE; + break; + default: + test = JS_FALSE; + break; + } + } + ok = js_ExecuteRegExp(cx, re, str, &index, test, rval); + } + + if (reobj) { + /* Tell our caller that it doesn't need to destroy data->regexp. */ + data->flags &= ~KEEP_REGEXP; + } else if (!(data->flags & KEEP_REGEXP)) { + /* Caller didn't want to keep data->regexp, so null and destroy it. */ + data->regexp = NULL; + js_DestroyRegExp(cx, re); + } + return ok; +} + +typedef struct MatchData { + GlobData base; + jsval *arrayval; /* NB: local root pointer */ +} MatchData; + +static JSBool +match_glob(JSContext *cx, jsint count, GlobData *data) +{ + MatchData *mdata; + JSObject *arrayobj; + JSSubString *matchsub; + JSString *matchstr; + jsval v; + + mdata = (MatchData *)data; + arrayobj = JSVAL_TO_OBJECT(*mdata->arrayval); + if (!arrayobj) { + arrayobj = js_ConstructObject(cx, &js_ArrayClass, NULL, NULL, 0, NULL); + if (!arrayobj) + return JS_FALSE; + *mdata->arrayval = OBJECT_TO_JSVAL(arrayobj); + } + matchsub = &cx->regExpStatics.lastMatch; + matchstr = js_NewStringCopyN(cx, matchsub->chars, matchsub->length, 0); + if (!matchstr) + return JS_FALSE; + v = STRING_TO_JSVAL(matchstr); + return js_SetProperty(cx, arrayobj, INT_TO_JSVAL(count), &v); +} + +static JSBool +str_match(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + MatchData mdata; + JSBool ok; + + mdata.base.flags = MODE_MATCH; + mdata.base.optarg = 1; + mdata.arrayval = &argv[2]; + *mdata.arrayval = JSVAL_NULL; + ok = match_or_replace(cx, obj, argc, argv, match_glob, &mdata.base, rval); + if (ok && !JSVAL_IS_NULL(*mdata.arrayval)) + *rval = *mdata.arrayval; + return ok; +} + +static JSBool +str_search(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + GlobData data; + + data.flags = MODE_SEARCH; + data.optarg = 1; + return match_or_replace(cx, obj, argc, argv, NULL, &data, rval); +} + +typedef struct ReplaceData { + GlobData base; /* base struct state */ + JSObject *lambda; /* replacement function object or null */ + JSString *repstr; /* replacement string */ + jschar *dollar; /* null or pointer to first $ in repstr */ + jschar *dollarEnd; /* limit pointer for js_strchr_limit */ + jschar *chars; /* result chars, null initially */ + size_t length; /* result length, 0 initially */ + jsint index; /* index in result of next replacement */ + jsint leftIndex; /* left context index in base.str->chars */ + JSSubString dollarStr; /* for "$$" interpret_dollar result */ +} ReplaceData; + +static JSSubString * +interpret_dollar(JSContext *cx, jschar *dp, ReplaceData *rdata, size_t *skip) +{ + JSRegExpStatics *res; + jschar dc, *cp; + uintN num, tmp; + JSString *str; + + JS_ASSERT(*dp == '$'); + + /* + * Allow a real backslash (literal "\\" before "$1") to escape "$1", e.g. + * Do this only for versions strictly less than ECMAv3. + */ + if (cx->version != JSVERSION_DEFAULT && cx->version <= JSVERSION_1_4) { + if (dp > JSSTRING_CHARS(rdata->repstr) && dp[-1] == '\\') + return NULL; + } + + /* Interpret all Perl match-induced dollar variables. */ + res = &cx->regExpStatics; + dc = dp[1]; + if (JS7_ISDEC(dc)) { + if (cx->version != JSVERSION_DEFAULT && cx->version <= JSVERSION_1_4) { + if (dc == '0') + return NULL; + + /* Check for overflow to avoid gobbling arbitrary decimal digits. */ + num = 0; + cp = dp; + while ((dc = *++cp) != 0 && JS7_ISDEC(dc)) { + tmp = 10 * num + JS7_UNDEC(dc); + if (tmp < num) + break; + num = tmp; + } + } else { /* ECMA 3, 1-9 or 01-99 */ + num = JS7_UNDEC(dc); + if (num > res->parenCount) + return NULL; + cp = dp + 2; + dc = *cp; + if ((dc != 0) && JS7_ISDEC(dc)) { + tmp = 10 * num + JS7_UNDEC(dc); + if (tmp <= res->parenCount) { + cp++; + num = tmp; + } + } + if (num == 0) + return NULL; + } + /* Adjust num from 1 $n-origin to 0 array-index-origin. */ + num--; + *skip = cp - dp; + return REGEXP_PAREN_SUBSTRING(res, num); + } + + *skip = 2; + switch (dc) { + case '$': + rdata->dollarStr.chars = dp; + rdata->dollarStr.length = 1; + return &rdata->dollarStr; + case '&': + return &res->lastMatch; + case '+': + return &res->lastParen; + case '`': + if (cx->version == JSVERSION_1_2) { + /* + * JS1.2 imitated the Perl4 bug where left context at each step + * in an iterative use of a global regexp started from last match, + * not from the start of the target string. But Perl4 does start + * $` at the beginning of the target string when it is used in a + * substitution, so we emulate that special case here. + */ + str = rdata->base.str; + res->leftContext.chars = JSSTRING_CHARS(str); + res->leftContext.length = res->lastMatch.chars + - JSSTRING_CHARS(str); + } + return &res->leftContext; + case '\'': + return &res->rightContext; + } + return NULL; +} + +static JSBool +find_replen(JSContext *cx, ReplaceData *rdata, size_t *sizep) +{ + JSString *repstr; + size_t replen, skip; + jschar *dp, *ep; + JSSubString *sub; +#if JS_HAS_REPLACE_LAMBDA + JSObject *lambda; + + lambda = rdata->lambda; + if (lambda) { + uintN argc, i, j, m, n, p; + jsval *sp, *oldsp, rval; + void *mark; + JSStackFrame *fp; + JSBool ok; + + /* + * Save the rightContext from the current regexp, since it + * gets stuck at the end of the replacement string and may + * be clobbered by a RegExp usage in the lambda function. + */ + JSSubString saveRightContext = cx->regExpStatics.rightContext; + + /* + * In the lambda case, not only do we find the replacement string's + * length, we compute repstr and return it via rdata for use within + * do_replace. The lambda is called with arguments ($&, $1, $2, ..., + * index, input), i.e., all the properties of a regexp match array. + * For $&, etc., we must create string jsvals from cx->regExpStatics. + * We grab up stack space to keep the newborn strings GC-rooted. + */ + p = rdata->base.regexp->parenCount; + argc = 1 + p + 2; + sp = js_AllocStack(cx, 2 + argc, &mark); + if (!sp) + return JS_FALSE; + + /* Push lambda and its 'this' parameter. */ + *sp++ = OBJECT_TO_JSVAL(lambda); + *sp++ = OBJECT_TO_JSVAL(OBJ_GET_PARENT(cx, lambda)); + +#define PUSH_REGEXP_STATIC(sub) \ + JS_BEGIN_MACRO \ + JSString *str = js_NewStringCopyN(cx, \ + cx->regExpStatics.sub.chars, \ + cx->regExpStatics.sub.length, \ + 0); \ + if (!str) { \ + ok = JS_FALSE; \ + goto lambda_out; \ + } \ + *sp++ = STRING_TO_JSVAL(str); \ + JS_END_MACRO + + /* Push $&, $1, $2, ... */ + PUSH_REGEXP_STATIC(lastMatch); + i = 0; + m = cx->regExpStatics.parenCount; + n = JS_MIN(m, 9); + for (j = 0; i < n; i++, j++) + PUSH_REGEXP_STATIC(parens[j]); + for (j = 0; i < m; i++, j++) + PUSH_REGEXP_STATIC(moreParens[j]); + +#undef PUSH_REGEXP_STATIC + + /* Make sure to push undefined for any unmatched parens. */ + for (; i < p; i++) + *sp++ = JSVAL_VOID; + + /* Push match index and input string. */ + *sp++ = INT_TO_JSVAL((jsint)cx->regExpStatics.leftContext.length); + *sp++ = STRING_TO_JSVAL(rdata->base.str); + + /* Lift current frame to include the args and do the call. */ + fp = cx->fp; + oldsp = fp->sp; + fp->sp = sp; + ok = js_Invoke(cx, argc, JSINVOKE_INTERNAL); + rval = fp->sp[-1]; + fp->sp = oldsp; + + if (ok) { + /* + * NB: we count on the newborn string root to hold any string + * created by this js_ValueToString that would otherwise be GC- + * able, until we use rdata->repstr in do_replace. + */ + repstr = js_ValueToString(cx, rval); + if (!repstr) { + ok = JS_FALSE; + } else { + rdata->repstr = repstr; + *sizep = JSSTRING_LENGTH(repstr); + } + } + + lambda_out: + js_FreeStack(cx, mark); + cx->regExpStatics.rightContext = saveRightContext; + return ok; + } +#endif /* JS_HAS_REPLACE_LAMBDA */ + + repstr = rdata->repstr; + replen = JSSTRING_LENGTH(repstr); + for (dp = rdata->dollar, ep = rdata->dollarEnd; dp; + dp = js_strchr_limit(dp, '$', ep)) { + sub = interpret_dollar(cx, dp, rdata, &skip); + if (sub) { + replen += sub->length - skip; + dp += skip; + } + else + dp++; + } + *sizep = replen; + return JS_TRUE; +} + +static void +do_replace(JSContext *cx, ReplaceData *rdata, jschar *chars) +{ + JSString *repstr; + jschar *bp, *cp, *dp, *ep; + size_t len, skip; + JSSubString *sub; + + repstr = rdata->repstr; + bp = cp = JSSTRING_CHARS(repstr); + for (dp = rdata->dollar, ep = rdata->dollarEnd; dp; + dp = js_strchr_limit(dp, '$', ep)) { + len = dp - cp; + js_strncpy(chars, cp, len); + chars += len; + cp = dp; + sub = interpret_dollar(cx, dp, rdata, &skip); + if (sub) { + len = sub->length; + js_strncpy(chars, sub->chars, len); + chars += len; + cp += skip; + dp += skip; + } else { + dp++; + } + } + js_strncpy(chars, cp, JSSTRING_LENGTH(repstr) - (cp - bp)); +} + +static JSBool +replace_glob(JSContext *cx, jsint count, GlobData *data) +{ + ReplaceData *rdata; + JSString *str; + size_t leftoff, leftlen, replen, growth; + const jschar *left; + jschar *chars; + + rdata = (ReplaceData *)data; + str = data->str; + leftoff = rdata->leftIndex; + left = JSSTRING_CHARS(str) + leftoff; + leftlen = cx->regExpStatics.lastMatch.chars - left; + rdata->leftIndex = cx->regExpStatics.lastMatch.chars - JSSTRING_CHARS(str); + rdata->leftIndex += cx->regExpStatics.lastMatch.length; + if (!find_replen(cx, rdata, &replen)) + return JS_FALSE; + growth = leftlen + replen; + chars = (jschar *) + (rdata->chars + ? JS_realloc(cx, rdata->chars, (rdata->length + growth + 1) + * sizeof(jschar)) + : JS_malloc(cx, (growth + 1) * sizeof(jschar))); + if (!chars) { + JS_free(cx, rdata->chars); + rdata->chars = NULL; + return JS_FALSE; + } + rdata->chars = chars; + rdata->length += growth; + chars += rdata->index; + rdata->index += growth; + js_strncpy(chars, left, leftlen); + chars += leftlen; + do_replace(cx, rdata, chars); + return JS_TRUE; +} + +static JSBool +str_replace(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSObject *lambda; + JSString *repstr, *str; + ReplaceData rdata; + JSBool ok; + jschar *chars; + size_t leftlen, rightlen, length; + +#if JS_HAS_REPLACE_LAMBDA + if (JS_TypeOfValue(cx, argv[1]) == JSTYPE_FUNCTION) { + lambda = JSVAL_TO_OBJECT(argv[1]); + repstr = NULL; + } else +#endif + { + if (!JS_ConvertValue(cx, argv[1], JSTYPE_STRING, &argv[1])) + return JS_FALSE; + repstr = JSVAL_TO_STRING(argv[1]); + lambda = NULL; + } + + /* + * For ECMA Edition 3, the first argument is to be converted to a string + * to match in a "flat" sense (without regular expression metachars having + * special meanings) UNLESS the first arg is a RegExp object. + */ + rdata.base.flags = MODE_REPLACE | KEEP_REGEXP; + if (cx->version == JSVERSION_DEFAULT || cx->version > JSVERSION_1_4) + rdata.base.flags |= FORCE_FLAT; + rdata.base.optarg = 2; + + rdata.lambda = lambda; + rdata.repstr = repstr; + if (repstr) { + rdata.dollarEnd = JSSTRING_CHARS(repstr) + JSSTRING_LENGTH(repstr); + rdata.dollar = js_strchr_limit(JSSTRING_CHARS(repstr), '$', + rdata.dollarEnd); + } else { + rdata.dollar = rdata.dollarEnd = NULL; + } + rdata.chars = NULL; + rdata.length = 0; + rdata.index = 0; + rdata.leftIndex = 0; + + ok = match_or_replace(cx, obj, argc, argv, replace_glob, &rdata.base, rval); + if (!ok) + return JS_FALSE; + + if (!rdata.chars) { + if ((rdata.base.flags & GLOBAL_REGEXP) || *rval != JSVAL_TRUE) { + /* Didn't match even once. */ + *rval = STRING_TO_JSVAL(rdata.base.str); + goto out; + } + leftlen = cx->regExpStatics.leftContext.length; + ok = find_replen(cx, &rdata, &length); + if (!ok) + goto out; + length += leftlen; + chars = (jschar *) JS_malloc(cx, (length + 1) * sizeof(jschar)); + if (!chars) { + ok = JS_FALSE; + goto out; + } + js_strncpy(chars, cx->regExpStatics.leftContext.chars, leftlen); + do_replace(cx, &rdata, chars + leftlen); + rdata.chars = chars; + rdata.length = length; + } + + rightlen = cx->regExpStatics.rightContext.length; + length = rdata.length + rightlen; + chars = (jschar *) + JS_realloc(cx, rdata.chars, (length + 1) * sizeof(jschar)); + if (!chars) { + JS_free(cx, rdata.chars); + ok = JS_FALSE; + goto out; + } + js_strncpy(chars + rdata.length, cx->regExpStatics.rightContext.chars, + rightlen); + chars[length] = 0; + + str = js_NewString(cx, chars, length, 0); + if (!str) { + JS_free(cx, chars); + ok = JS_FALSE; + goto out; + } + *rval = STRING_TO_JSVAL(str); + +out: + /* If KEEP_REGEXP is still set, it's our job to destroy regexp now. */ + if (rdata.base.flags & KEEP_REGEXP) + js_DestroyRegExp(cx, rdata.base.regexp); + return ok; +} +#endif /* JS_HAS_REGEXPS */ + +/* + * Subroutine used by str_split to find the next split point in str, starting + * at offset *ip and looking either for the separator substring given by sep, + * or for the next re match. In the re case, return the matched separator in + * *sep, and the possibly updated offset in *ip. + * + * Return -2 on error, -1 on end of string, >= 0 for a valid index of the next + * separator occurrence if found, or str->length if no separator is found. + */ +static jsint +find_split(JSContext *cx, JSString *str, JSRegExp *re, jsint *ip, + JSSubString *sep) +{ + jsint i, j, k; + jschar *chars; + size_t length; + + /* + * Stop if past end of string. If at end of string, we will compare the + * null char stored there (by js_NewString*) to sep->chars[j] in the while + * loop at the end of this function, so that + * + * "ab,".split(',') => ["ab", ""] + * + * and the resulting array converts back to the string "ab," for symmetry. + * However, we ape Perl and do this only if there is a sufficiently large + * limit argument (see str_split). + */ + i = *ip; + if ((size_t)i > JSSTRING_LENGTH(str)) + return -1; + + /* + * Perl4 special case for str.split(' '), only if the user has selected + * JavaScript1.2 explicitly. Split on whitespace, and skip leading w/s. + * Strange but true, apparently modeled after awk. + * + * NB: we set sep->length to the length of the w/s run, so we must test + * sep->chars[1] == 0 to make sure sep is just one space. + */ + chars = JSSTRING_CHARS(str); + length = JSSTRING_LENGTH(str); + if (cx->version == JSVERSION_1_2 && + !re && *sep->chars == ' ' && sep->chars[1] == 0) { + + /* Skip leading whitespace if at front of str. */ + if (i == 0) { + while (JS_ISSPACE(chars[i])) + i++; + *ip = i; + } + + /* Don't delimit whitespace at end of string. */ + if ((size_t)i == length) + return -1; + + /* Skip over the non-whitespace chars. */ + while ((size_t)i < length && !JS_ISSPACE(chars[i])) + i++; + + /* Now skip the next run of whitespace. */ + j = i; + while ((size_t)j < length && JS_ISSPACE(chars[j])) + j++; + + /* Update sep->length to count delimiter chars. */ + sep->length = (size_t)(j - i); + return i; + } + +#if JS_HAS_REGEXPS + /* + * Match a regular expression against the separator at or above index i. + * Call js_ExecuteRegExp with true for the test argument. On successful + * match, get the separator from cx->regExpStatics.lastMatch. + */ + if (re) { + size_t index; + jsval rval; + + again: + /* JS1.2 deviated from Perl by never matching at end of string. */ + index = (size_t)i; + if (!js_ExecuteRegExp(cx, re, str, &index, JS_TRUE, &rval)) + return -2; + if (rval != JSVAL_TRUE) { + /* Mismatch: ensure our caller advances i past end of string. */ + sep->length = 1; + return length; + } + i = (jsint)index; + *sep = cx->regExpStatics.lastMatch; + if (sep->length == 0) { + /* + * Empty string match: never split on an empty match at the start + * of a find_split cycle. Same rule as for an empty global match + * in match_or_replace. + */ + if (i == *ip) { + /* + * "Bump-along" to avoid sticking at an empty match, but don't + * bump past end of string -- our caller must do that by adding + * sep->length to our return value. + */ + if ((size_t)i == length) { + if (cx->version == JSVERSION_1_2) { + sep->length = 1; + return i; + } + return -1; + } + i++; + goto again; + } + } + JS_ASSERT((size_t)i >= sep->length); + return i - sep->length; + } +#endif /* JS_HAS_REGEXPS */ + + /* + * Deviate from ECMA by never splitting an empty string by any separator + * string into a non-empty array (an array of length 1 that contains the + * empty string). + */ + if (!JSVERSION_IS_ECMA(cx->version) && length == 0) + return -1; + + /* + * Special case: if sep is the empty string, split str into one character + * substrings. Let our caller worry about whether to split once at end of + * string into an empty substring. + * + * For 1.2 compatibility, at the end of the string, we return the length as + * the result, and set the separator length to 1 -- this allows the caller + * to include an additional null string at the end of the substring list. + */ + if (sep->length == 0) { + if (cx->version == JSVERSION_1_2) { + if ((size_t)i == length) { + sep->length = 1; + return i; + } + return i + 1; + } + return ((size_t)i == length) ? -1 : i + 1; + } + + /* + * Now that we know sep is non-empty, search starting at i in str for an + * occurrence of all of sep's chars. If we find them, return the index of + * the first separator char. Otherwise, return length. + */ + j = 0; + while ((size_t)(k = i + j) < length) { + if (chars[k] == sep->chars[j]) { + if ((size_t)++j == sep->length) + return i; + } else { + i++; + j = 0; + } + } + return k; +} + +static JSBool +str_split(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str, *sub; + JSObject *arrayobj; + jsval v; + JSBool ok, limited; + JSRegExp *re; + JSSubString *sep, tmp; + jsdouble d; + jsint i, j; + uint32 len, limit; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + argv[-1] = STRING_TO_JSVAL(str); + + arrayobj = js_ConstructObject(cx, &js_ArrayClass, NULL, NULL, 0, NULL); + if (!arrayobj) + return JS_FALSE; + *rval = OBJECT_TO_JSVAL(arrayobj); + + if (argc == 0) { + v = STRING_TO_JSVAL(str); + ok = JS_SetElement(cx, arrayobj, 0, &v); + } else { +#if JS_HAS_REGEXPS + if (JSVAL_IS_REGEXP(cx, argv[0])) { + re = (JSRegExp *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(argv[0])); + sep = &tmp; + + /* Set a magic value so we can detect a successful re match. */ + sep->chars = NULL; + } else +#endif + { + JSString *str2 = js_ValueToString(cx, argv[0]); + if (!str2) + return JS_FALSE; + argv[0] = STRING_TO_JSVAL(str2); + + /* + * Point sep at a local copy of str2's header because find_split + * will modify sep->length. + */ + tmp.length = JSSTRING_LENGTH(str2); + tmp.chars = JSSTRING_CHARS(str2); + sep = &tmp; + re = NULL; + } + + /* Use the second argument as the split limit, if given. */ + limited = (argc > 1) && !JSVAL_IS_VOID(argv[1]); + limit = 0; /* Avoid warning. */ + if (limited) { + if (!js_ValueToNumber(cx, argv[1], &d)) + return JS_FALSE; + + /* Clamp limit between 0 and 1 + string length. */ + if (!js_DoubleToECMAUint32(cx, d, &limit)) + return JS_FALSE; + if (limit > JSSTRING_LENGTH(str)) + limit = 1 + JSSTRING_LENGTH(str); + } + + len = i = 0; + while ((j = find_split(cx, str, re, &i, sep)) >= 0) { + if (limited && len >= limit) + break; + sub = js_NewDependentString(cx, str, i, (size_t)(j - i), 0); + if (!sub) + return JS_FALSE; + v = STRING_TO_JSVAL(sub); + if (!JS_SetElement(cx, arrayobj, len, &v)) + return JS_FALSE; + len++; +#if JS_HAS_REGEXPS + /* + * Imitate perl's feature of including parenthesized substrings + * that matched part of the delimiter in the new array, after the + * split substring that was delimited. + */ + if (re && sep->chars) { + uintN num; + JSSubString *parsub; + + for (num = 0; num < cx->regExpStatics.parenCount; num++) { + if (limited && len >= limit) + break; + parsub = REGEXP_PAREN_SUBSTRING(&cx->regExpStatics, num); + sub = js_NewStringCopyN(cx, parsub->chars, parsub->length, + 0); + if (!sub) + return JS_FALSE; + v = STRING_TO_JSVAL(sub); + if (!JS_SetElement(cx, arrayobj, len, &v)) + return JS_FALSE; + len++; + } + sep->chars = NULL; + } +#endif + i = j + sep->length; + if (!JSVERSION_IS_ECMA(cx->version)) { + /* + * Deviate from ECMA to imitate Perl, which omits a final + * split unless a limit argument is given and big enough. + */ + if (!limited && (size_t)i == JSSTRING_LENGTH(str)) + break; + } + } + ok = (j != -2); + } + return ok; +} + +#if JS_HAS_PERL_SUBSTR +static JSBool +str_substr(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str; + jsdouble d; + jsdouble length, begin, end; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + + if (argc != 0) { + if (!js_ValueToNumber(cx, argv[0], &d)) + return JS_FALSE; + length = JSSTRING_LENGTH(str); + begin = js_DoubleToInteger(d); + if (begin < 0) { + begin += length; + if (begin < 0) + begin = 0; + } else if (begin > length) { + begin = length; + } + + if (argc == 1) { + end = length; + } else { + if (!js_ValueToNumber(cx, argv[1], &d)) + return JS_FALSE; + end = js_DoubleToInteger(d); + if (end < 0) + end = 0; + end += begin; + if (end > length) + end = length; + } + + str = js_NewDependentString(cx, str, (size_t)begin, + (size_t)(end - begin), 0); + if (!str) + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} +#endif /* JS_HAS_PERL_SUBSTR */ + +#if JS_HAS_SEQUENCE_OPS +/* + * Python-esque sequence operations. + */ +static JSBool +str_concat(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str, *str2; + uintN i; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + argv[-1] = STRING_TO_JSVAL(str); + + for (i = 0; i < argc; i++) { + str2 = js_ValueToString(cx, argv[i]); + if (!str2) + return JS_FALSE; + argv[i] = STRING_TO_JSVAL(str2); + + str = js_ConcatStrings(cx, str, str2); + if (!str) + return JS_FALSE; + } + + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +static JSBool +str_slice(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str; + jsdouble d; + jsdouble length, begin, end; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + argv[-1] = STRING_TO_JSVAL(str); + + if (argc != 0) { + if (!js_ValueToNumber(cx, argv[0], &d)) + return JS_FALSE; + length = JSSTRING_LENGTH(str); + begin = js_DoubleToInteger(d); + if (begin < 0) { + begin += length; + if (begin < 0) + begin = 0; + } else if (begin > length) { + begin = length; + } + + if (argc == 1) { + end = length; + } else { + if (!js_ValueToNumber(cx, argv[1], &d)) + return JS_FALSE; + end = js_DoubleToInteger(d); + if (end < 0) { + end += length; + if (end < 0) + end = 0; + } else if (end > length) { + end = length; + } + if (end < begin) + end = begin; + } + + str = js_NewDependentString(cx, str, (size_t)begin, + (size_t)(end - begin), 0); + if (!str) + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} +#endif /* JS_HAS_SEQUENCE_OPS */ + +#if JS_HAS_STR_HTML_HELPERS +/* + * HTML composition aids. + */ +static JSBool +tagify(JSContext *cx, JSObject *obj, jsval *argv, + const char *begin, const jschar *param, const char *end, + jsval *rval) +{ + JSString *str; + jschar *tagbuf; + size_t beglen, endlen, parlen, taglen; + size_t i, j; + + str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); + if (!str) + return JS_FALSE; + argv[-1] = STRING_TO_JSVAL(str); + + if (!end) + end = begin; + + beglen = strlen(begin); + taglen = 1 + beglen + 1; /* '' */ + parlen = 0; /* Avoid warning. */ + if (param) { + parlen = js_strlen(param); + taglen += 2 + parlen + 1; /* '="param"' */ + } + endlen = strlen(end); + taglen += JSSTRING_LENGTH(str) + 2 + endlen + 1; /* 'str' */ + + tagbuf = (jschar *) JS_malloc(cx, (taglen + 1) * sizeof(jschar)); + if (!tagbuf) + return JS_FALSE; + + j = 0; + tagbuf[j++] = '<'; + for (i = 0; i < beglen; i++) + tagbuf[j++] = (jschar)begin[i]; + if (param) { + tagbuf[j++] = '='; + tagbuf[j++] = '"'; + js_strncpy(&tagbuf[j], param, parlen); + j += parlen; + tagbuf[j++] = '"'; + } + tagbuf[j++] = '>'; + js_strncpy(&tagbuf[j], JSSTRING_CHARS(str), JSSTRING_LENGTH(str)); + j += JSSTRING_LENGTH(str); + tagbuf[j++] = '<'; + tagbuf[j++] = '/'; + for (i = 0; i < endlen; i++) + tagbuf[j++] = (jschar)end[i]; + tagbuf[j++] = '>'; + JS_ASSERT(j == taglen); + tagbuf[j] = 0; + + str = js_NewString(cx, tagbuf, taglen, 0); + if (!str) { + free((char *)tagbuf); + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +static JSBool +tagify_value(JSContext *cx, JSObject *obj, jsval *argv, + const char *begin, const char *end, + jsval *rval) +{ + JSString *param; + + param = js_ValueToString(cx, argv[0]); + if (!param) + return JS_FALSE; + argv[0] = STRING_TO_JSVAL(param); + return tagify(cx, obj, argv, begin, JSSTRING_CHARS(param), end, rval); +} + +static JSBool +str_bold(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + return tagify(cx, obj, argv, "b", NULL, NULL, rval); +} + +static JSBool +str_italics(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + return tagify(cx, obj, argv, "i", NULL, NULL, rval); +} + +static JSBool +str_fixed(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + return tagify(cx, obj, argv, "tt", NULL, NULL, rval); +} + +static JSBool +str_fontsize(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + return tagify_value(cx, obj, argv, "font size", "font", rval); +} + +static JSBool +str_fontcolor(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + return tagify_value(cx, obj, argv, "font color", "font", rval); +} + +static JSBool +str_link(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + return tagify_value(cx, obj, argv, "a href", "a", rval); +} + +static JSBool +str_anchor(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + return tagify_value(cx, obj, argv, "a name", "a", rval); +} + +static JSBool +str_strike(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + return tagify(cx, obj, argv, "strike", NULL, NULL, rval); +} + +static JSBool +str_small(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + return tagify(cx, obj, argv, "small", NULL, NULL, rval); +} + +static JSBool +str_big(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + return tagify(cx, obj, argv, "big", NULL, NULL, rval); +} + +static JSBool +str_blink(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + return tagify(cx, obj, argv, "blink", NULL, NULL, rval); +} + +static JSBool +str_sup(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + return tagify(cx, obj, argv, "sup", NULL, NULL, rval); +} + +static JSBool +str_sub(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + return tagify(cx, obj, argv, "sub", NULL, NULL, rval); +} +#endif /* JS_HAS_STR_HTML_HELPERS */ + +static JSFunctionSpec string_methods[] = { +#if JS_HAS_TOSOURCE + {"quote", str_quote, 0,0,0}, + {js_toSource_str, str_toSource, 0,0,0}, +#endif + + /* Java-like methods. */ + {js_toString_str, str_toString, 0,0,0}, + {js_valueOf_str, str_valueOf, 0,0,0}, + {"substring", str_substring, 2,0,0}, + {"toLowerCase", str_toLowerCase, 0,0,0}, + {"toUpperCase", str_toUpperCase, 0,0,0}, + {"charAt", str_charAt, 1,0,0}, + {"charCodeAt", str_charCodeAt, 1,0,0}, + {"indexOf", str_indexOf, 1,0,0}, + {"lastIndexOf", str_lastIndexOf, 1,0,0}, + {"toLocaleLowerCase", str_toLocaleLowerCase, 0,0,0}, + {"toLocaleUpperCase", str_toLocaleUpperCase, 0,0,0}, + {"localeCompare", str_localeCompare, 1,0,0}, + + /* Perl-ish methods (search is actually Python-esque). */ +#if JS_HAS_REGEXPS + {"match", str_match, 1,0,2}, + {"search", str_search, 1,0,0}, + {"replace", str_replace, 2,0,0}, + {"split", str_split, 2,0,0}, +#endif +#if JS_HAS_PERL_SUBSTR + {"substr", str_substr, 2,0,0}, +#endif + + /* Python-esque sequence methods. */ +#if JS_HAS_SEQUENCE_OPS + {"concat", str_concat, 0,0,0}, + {"slice", str_slice, 0,0,0}, +#endif + + /* HTML string methods. */ +#if JS_HAS_STR_HTML_HELPERS + {"bold", str_bold, 0,0,0}, + {"italics", str_italics, 0,0,0}, + {"fixed", str_fixed, 0,0,0}, + {"fontsize", str_fontsize, 1,0,0}, + {"fontcolor", str_fontcolor, 1,0,0}, + {"link", str_link, 1,0,0}, + {"anchor", str_anchor, 1,0,0}, + {"strike", str_strike, 0,0,0}, + {"small", str_small, 0,0,0}, + {"big", str_big, 0,0,0}, + {"blink", str_blink, 0,0,0}, + {"sup", str_sup, 0,0,0}, + {"sub", str_sub, 0,0,0}, +#endif + + {0,0,0,0,0} +}; + +static JSBool +String(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str; + + if (argc > 0) { + str = js_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + } else { + str = cx->runtime->emptyString; + } + if (!(cx->fp->flags & JSFRAME_CONSTRUCTING)) { + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; + } + OBJ_SET_SLOT(cx, obj, JSSLOT_PRIVATE, STRING_TO_JSVAL(str)); + return JS_TRUE; +} + +static JSBool +str_fromCharCode(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + jschar *chars; + uintN i; + uint16 code; + JSString *str; + + chars = (jschar *) JS_malloc(cx, (argc + 1) * sizeof(jschar)); + if (!chars) + return JS_FALSE; + for (i = 0; i < argc; i++) { + if (!js_ValueToUint16(cx, argv[i], &code)) { + JS_free(cx, chars); + return JS_FALSE; + } + chars[i] = (jschar)code; + } + chars[i] = 0; + str = js_NewString(cx, chars, argc, 0); + if (!str) { + JS_free(cx, chars); + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +static JSFunctionSpec string_static_methods[] = { + {"fromCharCode", str_fromCharCode, 1,0,0}, + {0,0,0,0,0} +}; + +static JSHashTable *deflated_string_cache; +#ifdef DEBUG +static uint32 deflated_string_cache_bytes; +#endif +#ifdef JS_THREADSAFE +static JSLock *deflated_string_cache_lock; +#endif + +JSBool +js_InitStringGlobals(void) +{ +#ifdef JS_THREADSAFE + /* Must come through here once in primordial thread to init safely! */ + if (!deflated_string_cache_lock) { + deflated_string_cache_lock = JS_NEW_LOCK(); + if (!deflated_string_cache_lock) + return JS_FALSE; + } +#endif + return JS_TRUE; +} + +void +js_FreeStringGlobals() +{ + if (deflated_string_cache) { + JS_HashTableDestroy(deflated_string_cache); + deflated_string_cache = NULL; + } +#ifdef JS_THREADSAFE + if (deflated_string_cache_lock) { + JS_DESTROY_LOCK(deflated_string_cache_lock); + deflated_string_cache_lock = NULL; + } +#endif +} + +JSBool +js_InitRuntimeStringState(JSContext *cx) +{ + JSRuntime *rt; + JSString *empty; + + rt = cx->runtime; + JS_ASSERT(!rt->emptyString); + + /* Make a permanently locked empty string. */ + empty = js_NewStringCopyN(cx, js_empty_ucstr, 0, GCF_LOCK); + if (!empty) + return JS_FALSE; + + /* Atomize it for scripts that use '' + x to convert x to string. */ + if (!js_AtomizeString(cx, empty, ATOM_PINNED)) + return JS_FALSE; + + rt->emptyString = empty; + return JS_TRUE; +} + +void +js_FinishRuntimeStringState(JSContext *cx) +{ + JSRuntime *rt = cx->runtime; + + js_UnlockGCThingRT(rt, rt->emptyString); + rt->emptyString = NULL; +} + +JSObject * +js_InitStringClass(JSContext *cx, JSObject *obj) +{ + JSObject *proto; + + /* Define the escape, unescape functions in the global object. */ + if (!JS_DefineFunctions(cx, obj, string_functions)) + return NULL; + + proto = JS_InitClass(cx, obj, NULL, &string_class, String, 1, + string_props, string_methods, + NULL, string_static_methods); + if (!proto) + return NULL; + OBJ_SET_SLOT(cx, proto, JSSLOT_PRIVATE, + STRING_TO_JSVAL(cx->runtime->emptyString)); + return proto; +} + +JSString * +js_NewString(JSContext *cx, jschar *chars, size_t length, uintN gcflag) +{ + JSString *str; + + if (length > JSSTRING_LENGTH_MASK) { + JS_ReportOutOfMemory(cx); + return NULL; + } + + str = (JSString *) js_AllocGCThing(cx, gcflag | GCX_STRING); + if (!str) + return NULL; + str->length = length; + str->chars = chars; +#ifdef DEBUG + { + JSRuntime *rt = cx->runtime; + JS_RUNTIME_METER(rt, liveStrings); + JS_RUNTIME_METER(rt, totalStrings); + JS_LOCK_RUNTIME_VOID(rt, + (rt->lengthSum += (double)length, + rt->lengthSquaredSum += (double)length * (double)length)); + } +#endif + return str; +} + +JSString * +js_NewDependentString(JSContext *cx, JSString *base, size_t start, + size_t length, uintN gcflag) +{ + JSDependentString *ds; + + if (length == 0) + return cx->runtime->emptyString; + + if (start > JSSTRDEP_START_MASK || + (start != 0 && length > JSSTRDEP_LENGTH_MASK)) { + return js_NewStringCopyN(cx, JSSTRING_CHARS(base) + start, length, + gcflag); + } + + ds = (JSDependentString *) js_AllocGCThing(cx, gcflag | GCX_MUTABLE_STRING); + if (!ds) + return NULL; + if (start == 0) { + JSPREFIX_SET_LENGTH(ds, length); + JSPREFIX_SET_BASE(ds, base); + } else { + JSSTRDEP_SET_START_AND_LENGTH(ds, start, length); + JSSTRDEP_SET_BASE(ds, base); + } +#ifdef DEBUG + { + JSRuntime *rt = cx->runtime; + JS_RUNTIME_METER(rt, liveDependentStrings); + JS_RUNTIME_METER(rt, totalDependentStrings); + JS_RUNTIME_METER(rt, liveStrings); + JS_RUNTIME_METER(rt, totalStrings); + JS_LOCK_RUNTIME_VOID(rt, + (rt->strdepLengthSum += (double)length, + rt->strdepLengthSquaredSum += (double)length * (double)length)); + JS_LOCK_RUNTIME_VOID(rt, + (rt->lengthSum += (double)length, + rt->lengthSquaredSum += (double)length * (double)length)); + } +#endif + return (JSString *)ds; +} + +#ifdef DEBUG +#include + +void printJSStringStats(JSRuntime *rt) { + double mean = 0., var = 0., sigma = 0.; + jsrefcount count = rt->totalStrings; + if (count > 0 && rt->lengthSum >= 0) { + mean = rt->lengthSum / count; + var = count * rt->lengthSquaredSum - rt->lengthSum * rt->lengthSum; + if (var < 0.0 || count <= 1) + var = 0.0; + else + var /= count * (count - 1); + + /* Windows says sqrt(0.0) is "-1.#J" (?!) so we must test. */ + sigma = (var != 0.) ? sqrt(var) : 0.; + } + fprintf(stderr, "%lu total strings, mean length %g (sigma %g)\n", + (unsigned long)count, mean, sigma); + + mean = var = sigma = 0.; + count = rt->totalDependentStrings; + if (count > 0 && rt->strdepLengthSum >= 0) { + mean = rt->strdepLengthSum / count; + var = count * rt->strdepLengthSquaredSum + - rt->strdepLengthSum * rt->strdepLengthSum; + if (var < 0.0 || count <= 1) + var = 0.0; + else + var /= count * (count - 1); + + /* Windows says sqrt(0.0) is "-1.#J" (?!) so we must test. */ + sigma = (var != 0.) ? sqrt(var) : 0.; + } + fprintf(stderr, "%lu total dependent strings, mean length %g (sigma %g)\n", + (unsigned long)count, mean, sigma); +} +#endif + +JSString * +js_NewStringCopyN(JSContext *cx, const jschar *s, size_t n, uintN gcflag) +{ + jschar *news; + JSString *str; + + news = (jschar *)JS_malloc(cx, (n + 1) * sizeof(jschar)); + if (!news) + return NULL; + js_strncpy(news, s, n); + news[n] = 0; + str = js_NewString(cx, news, n, gcflag); + if (!str) + JS_free(cx, news); + return str; +} + +JSString * +js_NewStringCopyZ(JSContext *cx, const jschar *s, uintN gcflag) +{ + size_t n, m; + jschar *news; + JSString *str; + + n = js_strlen(s); + m = (n + 1) * sizeof(jschar); + news = (jschar *) JS_malloc(cx, m); + if (!news) + return NULL; + memcpy(news, s, m); + str = js_NewString(cx, news, n, gcflag); + if (!str) + JS_free(cx, news); + return str; +} + +JS_STATIC_DLL_CALLBACK(JSHashNumber) +js_hash_string_pointer(const void *key) +{ + return (JSHashNumber)key >> JSVAL_TAGBITS; +} + +void +js_PurgeDeflatedStringCache(JSString *str) +{ + JSHashNumber hash; + JSHashEntry *he, **hep; + + if (!deflated_string_cache) + return; + + hash = js_hash_string_pointer(str); + JS_ACQUIRE_LOCK(deflated_string_cache_lock); + hep = JS_HashTableRawLookup(deflated_string_cache, hash, str); + he = *hep; + if (he) { +#ifdef DEBUG + deflated_string_cache_bytes -= JSSTRING_LENGTH(str); +#endif + free(he->value); + JS_HashTableRawRemove(deflated_string_cache, hep, he); + } + JS_RELEASE_LOCK(deflated_string_cache_lock); +} + +void +js_FinalizeString(JSContext *cx, JSString *str) +{ + js_FinalizeStringRT(cx->runtime, str); +} + +void +js_FinalizeStringRT(JSRuntime *rt, JSString *str) +{ + JSBool valid; + + JS_RUNTIME_UNMETER(rt, liveStrings); + if (JSSTRING_IS_DEPENDENT(str)) { + /* If JSSTRFLAG_DEPENDENT is set, this string must be valid. */ + JS_ASSERT(JSSTRDEP_BASE(str)); + JS_RUNTIME_UNMETER(rt, liveDependentStrings); + valid = JS_TRUE; + } else { + /* A stillborn string has null chars, so is not valid. */ + valid = (str->chars != NULL); + if (valid) + free(str->chars); + } + if (valid) { + js_PurgeDeflatedStringCache(str); + str->chars = NULL; + } + str->length = 0; +} + +JSObject * +js_StringToObject(JSContext *cx, JSString *str) +{ + JSObject *obj; + + obj = js_NewObject(cx, &string_class, NULL, NULL); + if (!obj) + return NULL; + OBJ_SET_SLOT(cx, obj, JSSLOT_PRIVATE, STRING_TO_JSVAL(str)); + return obj; +} + +JSString * +js_ValueToString(JSContext *cx, jsval v) +{ + JSObject *obj; + JSString *str; + + if (JSVAL_IS_OBJECT(v)) { + obj = JSVAL_TO_OBJECT(v); + if (!obj) + return ATOM_TO_STRING(cx->runtime->atomState.nullAtom); + if (!OBJ_DEFAULT_VALUE(cx, obj, JSTYPE_STRING, &v)) + return NULL; + } + if (JSVAL_IS_STRING(v)) { + str = JSVAL_TO_STRING(v); + } else if (JSVAL_IS_INT(v)) { + str = js_NumberToString(cx, JSVAL_TO_INT(v)); + } else if (JSVAL_IS_DOUBLE(v)) { + str = js_NumberToString(cx, *JSVAL_TO_DOUBLE(v)); + } else if (JSVAL_IS_BOOLEAN(v)) { + str = js_BooleanToString(cx, JSVAL_TO_BOOLEAN(v)); + } else { + str = ATOM_TO_STRING(cx->runtime->atomState.typeAtoms[JSTYPE_VOID]); + } + return str; +} + +JSString * +js_ValueToSource(JSContext *cx, jsval v) +{ + if (JSVAL_IS_STRING(v)) + return js_QuoteString(cx, JSVAL_TO_STRING(v), '"'); + if (JSVAL_IS_PRIMITIVE(v)) { + /* Special case to preserve negative zero, _contra_ toString. */ + if (JSVAL_IS_DOUBLE(v) && JSDOUBLE_IS_NEGZERO(*JSVAL_TO_DOUBLE(v))) { + /* NB: _ucNstr rather than _ucstr to indicate non-terminated. */ + static const jschar js_negzero_ucNstr[] = {'-', '0'}; + + return js_NewStringCopyN(cx, js_negzero_ucNstr, 2, 0); + } + } else { + if (!js_TryMethod(cx, JSVAL_TO_OBJECT(v), + cx->runtime->atomState.toSourceAtom, + 0, NULL, &v)) { + return NULL; + } + } + return js_ValueToString(cx, v); +} + +JSHashNumber +js_HashString(JSString *str) +{ + JSHashNumber h; + const jschar *s; + size_t n; + + h = 0; + for (s = JSSTRING_CHARS(str), n = JSSTRING_LENGTH(str); n; s++, n--) + h = (h >> (JS_HASH_BITS - 4)) ^ (h << 4) ^ *s; + return h; +} + +intN +js_CompareStrings(JSString *str1, JSString *str2) +{ + size_t l1, l2, n, i; + const jschar *s1, *s2; + intN cmp; + + l1 = JSSTRING_LENGTH(str1), l2 = JSSTRING_LENGTH(str2); + s1 = JSSTRING_CHARS(str1), s2 = JSSTRING_CHARS(str2); + n = JS_MIN(l1, l2); + for (i = 0; i < n; i++) { + cmp = s1[i] - s2[i]; + if (cmp != 0) + return cmp; + } + return (intN)(l1 - l2); +} + +size_t +js_strlen(const jschar *s) +{ + const jschar *t; + + for (t = s; *t != 0; t++) + continue; + return (size_t)(t - s); +} + +jschar * +js_strchr(const jschar *s, jschar c) +{ + while (*s != 0) { + if (*s == c) + return (jschar *)s; + s++; + } + return NULL; +} + +jschar * +js_strchr_limit(const jschar *s, jschar c, const jschar *limit) +{ + while (s < limit) { + if (*s == c) + return (jschar *)s; + s++; + } + return NULL; +} + +const jschar * +js_SkipWhiteSpace(const jschar *s) +{ + /* JS_ISSPACE is false on a null. */ + while (JS_ISSPACE(*s)) + s++; + return s; +} + +#define INFLATE_STRING_BODY \ + for (i = 0; i < length; i++) \ + chars[i] = (unsigned char) bytes[i]; \ + chars[i] = 0; + +void +js_InflateStringToBuffer(jschar *chars, const char *bytes, size_t length) +{ + size_t i; + + INFLATE_STRING_BODY +} + +jschar * +js_InflateString(JSContext *cx, const char *bytes, size_t length) +{ + jschar *chars; + size_t i; + + chars = (jschar *) JS_malloc(cx, (length + 1) * sizeof(jschar)); + if (!chars) + return NULL; + + INFLATE_STRING_BODY + + return chars; +} + +/* + * May be called with null cx by js_GetStringBytes, see below. + */ +char * +js_DeflateString(JSContext *cx, const jschar *chars, size_t length) +{ + size_t i, size; + char *bytes; + + size = (length + 1) * sizeof(char); + bytes = (char *) (cx ? JS_malloc(cx, size) : malloc(size)); + if (!bytes) + return NULL; + for (i = 0; i < length; i++) + bytes[i] = (char) chars[i]; + bytes[i] = 0; + return bytes; +} + +static JSHashTable * +GetDeflatedStringCache(void) +{ + JSHashTable *cache; + + cache = deflated_string_cache; + if (!cache) { + cache = JS_NewHashTable(8, js_hash_string_pointer, + JS_CompareValues, JS_CompareValues, + NULL, NULL); + deflated_string_cache = cache; + } + return cache; +} + +JSBool +js_SetStringBytes(JSString *str, char *bytes, size_t length) +{ + JSHashTable *cache; + JSBool ok; + JSHashNumber hash; + JSHashEntry **hep; + + JS_ACQUIRE_LOCK(deflated_string_cache_lock); + + cache = GetDeflatedStringCache(); + if (!cache) { + ok = JS_FALSE; + } else { + hash = js_hash_string_pointer(str); + hep = JS_HashTableRawLookup(cache, hash, str); + JS_ASSERT(*hep == NULL); + ok = JS_HashTableRawAdd(cache, hep, hash, str, bytes) != NULL; +#ifdef DEBUG + if (ok) + deflated_string_cache_bytes += length; +#endif + } + + JS_RELEASE_LOCK(deflated_string_cache_lock); + return ok; +} + +char * +js_GetStringBytes(JSString *str) +{ + JSHashTable *cache; + char *bytes; + JSHashNumber hash; + JSHashEntry *he, **hep; + + JS_ACQUIRE_LOCK(deflated_string_cache_lock); + + cache = GetDeflatedStringCache(); + if (!cache) { + bytes = NULL; + } else { + hash = js_hash_string_pointer(str); + hep = JS_HashTableRawLookup(cache, hash, str); + he = *hep; + if (he) { + bytes = (char *) he->value; + + /* Try to catch failure to JS_ShutDown between runtime epochs. */ + JS_ASSERT((*bytes == '\0' && JSSTRING_LENGTH(str) == 0) || + *bytes == (char) JSSTRING_CHARS(str)[0]); + } else { + bytes = js_DeflateString(NULL, JSSTRING_CHARS(str), + JSSTRING_LENGTH(str)); + if (bytes) { + if (JS_HashTableRawAdd(cache, hep, hash, str, bytes)) { +#ifdef DEBUG + deflated_string_cache_bytes += JSSTRING_LENGTH(str); +#endif + } else { + free(bytes); + bytes = NULL; + } + } + } + } + + JS_RELEASE_LOCK(deflated_string_cache_lock); + return bytes; +} + +/* + * From java.lang.Character.java: + * + * The character properties are currently encoded into 32 bits in the + * following manner: + * + * 10 bits signed offset used for converting case + * 1 bit if 1, adding the signed offset converts the character to + * lowercase + * 1 bit if 1, subtracting the signed offset converts the character to + * uppercase + * 1 bit if 1, character has a titlecase equivalent (possibly itself) + * 3 bits 0 may not be part of an identifier + * 1 ignorable control; may continue a Unicode identifier or JS + * identifier + * 2 may continue a JS identifier but not a Unicode identifier + * (unused) + * 3 may continue a Unicode identifier or JS identifier + * 4 is a JS whitespace character + * 5 may start or continue a JS identifier; + * may continue but not start a Unicode identifier (_) + * 6 may start or continue a JS identifier but not a Unicode + * identifier ($) + * 7 may start or continue a Unicode identifier or JS identifier + * Thus: + * 5, 6, 7 may start a JS identifier + * 1, 2, 3, 5, 6, 7 may continue a JS identifier + * 7 may start a Unicode identifier + * 1, 3, 5, 7 may continue a Unicode identifier + * 1 is ignorable within an identifier + * 4 is JS whitespace + * 2 bits 0 this character has no numeric property + * 1 adding the digit offset to the character code and then + * masking with 0x1F will produce the desired numeric value + * 2 this character has a "strange" numeric value + * 3 a JS supradecimal digit: adding the digit offset to the + * character code, then masking with 0x1F, then adding 10 + * will produce the desired numeric value + * 5 bits digit offset + * 4 bits reserved for future use + * 5 bits character type + */ + +/* The X table has 1024 entries for a total of 1024 bytes. */ + +const uint8 js_X[] = { + 0, 1, 2, 3, 4, 5, 6, 7, /* 0x0000 */ + 8, 9, 10, 11, 12, 13, 14, 15, /* 0x0200 */ + 16, 17, 18, 19, 20, 21, 22, 23, /* 0x0400 */ + 24, 25, 26, 27, 28, 28, 28, 28, /* 0x0600 */ + 28, 28, 28, 28, 29, 30, 31, 32, /* 0x0800 */ + 33, 34, 35, 36, 37, 38, 39, 40, /* 0x0A00 */ + 41, 42, 43, 44, 45, 46, 28, 28, /* 0x0C00 */ + 47, 48, 49, 50, 51, 52, 53, 28, /* 0x0E00 */ + 28, 28, 54, 55, 56, 57, 58, 59, /* 0x1000 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x1200 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x1400 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x1600 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x1800 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x1A00 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x1C00 */ + 60, 60, 61, 62, 63, 64, 65, 66, /* 0x1E00 */ + 67, 68, 69, 70, 71, 72, 73, 74, /* 0x2000 */ + 75, 75, 75, 76, 77, 78, 28, 28, /* 0x2200 */ + 79, 80, 81, 82, 83, 83, 84, 85, /* 0x2400 */ + 86, 85, 28, 28, 87, 88, 89, 28, /* 0x2600 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x2800 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x2A00 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x2C00 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x2E00 */ + 90, 91, 92, 93, 94, 56, 95, 28, /* 0x3000 */ + 96, 97, 98, 99, 83, 100, 83, 101, /* 0x3200 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x3400 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x3600 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x3800 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x3A00 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x3C00 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x3E00 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x4000 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x4200 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x4400 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x4600 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x4800 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x4A00 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0x4C00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x4E00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x5000 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x5200 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x5400 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x5600 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x5800 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x5A00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x5C00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x5E00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x6000 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x6200 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x6400 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x6600 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x6800 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x6A00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x6C00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x6E00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x7000 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x7200 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x7400 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x7600 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x7800 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x7A00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x7C00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x7E00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x8000 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x8200 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x8400 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x8600 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x8800 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x8A00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x8C00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x8E00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x9000 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x9200 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x9400 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x9600 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x9800 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x9A00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0x9C00 */ + 56, 56, 56, 56, 56, 56, 102, 28, /* 0x9E00 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0xA000 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0xA200 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0xA400 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0xA600 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0xA800 */ + 28, 28, 28, 28, 28, 28, 28, 28, /* 0xAA00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xAC00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xAE00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xB000 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xB200 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xB400 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xB600 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xB800 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xBA00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xBC00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xBE00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xC000 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xC200 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xC400 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xC600 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xC800 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xCA00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xCC00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xCE00 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xD000 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xD200 */ + 56, 56, 56, 56, 56, 56, 56, 56, /* 0xD400 */ + 56, 56, 56, 56, 56, 56, 103, 28, /* 0xD600 */ +104, 104, 104, 104, 104, 104, 104, 104, /* 0xD800 */ +104, 104, 104, 104, 104, 104, 104, 104, /* 0xDA00 */ +104, 104, 104, 104, 104, 104, 104, 104, /* 0xDC00 */ +104, 104, 104, 104, 104, 104, 104, 104, /* 0xDE00 */ +105, 105, 105, 105, 105, 105, 105, 105, /* 0xE000 */ +105, 105, 105, 105, 105, 105, 105, 105, /* 0xE200 */ +105, 105, 105, 105, 105, 105, 105, 105, /* 0xE400 */ +105, 105, 105, 105, 105, 105, 105, 105, /* 0xE600 */ +105, 105, 105, 105, 105, 105, 105, 105, /* 0xE800 */ +105, 105, 105, 105, 105, 105, 105, 105, /* 0xEA00 */ +105, 105, 105, 105, 105, 105, 105, 105, /* 0xEC00 */ +105, 105, 105, 105, 105, 105, 105, 105, /* 0xEE00 */ +105, 105, 105, 105, 105, 105, 105, 105, /* 0xF000 */ +105, 105, 105, 105, 105, 105, 105, 105, /* 0xF200 */ +105, 105, 105, 105, 105, 105, 105, 105, /* 0xF400 */ +105, 105, 105, 105, 105, 105, 105, 105, /* 0xF600 */ +105, 105, 105, 105, 56, 56, 56, 56, /* 0xF800 */ +106, 28, 28, 28, 107, 108, 109, 110, /* 0xFA00 */ + 56, 56, 56, 56, 111, 112, 113, 114, /* 0xFC00 */ +115, 116, 56, 117, 118, 119, 120, 121 /* 0xFE00 */ +}; + +/* The Y table has 7808 entries for a total of 7808 bytes. */ + +const uint8 js_Y[] = { + 0, 0, 0, 0, 0, 0, 0, 0, /* 0 */ + 0, 1, 1, 1, 1, 1, 0, 0, /* 0 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0 */ + 2, 3, 3, 3, 4, 3, 3, 3, /* 0 */ + 5, 6, 3, 7, 3, 8, 3, 3, /* 0 */ + 9, 9, 9, 9, 9, 9, 9, 9, /* 0 */ + 9, 9, 3, 3, 7, 7, 7, 3, /* 0 */ + 3, 10, 10, 10, 10, 10, 10, 10, /* 1 */ + 10, 10, 10, 10, 10, 10, 10, 10, /* 1 */ + 10, 10, 10, 10, 10, 10, 10, 10, /* 1 */ + 10, 10, 10, 5, 3, 6, 11, 12, /* 1 */ + 11, 13, 13, 13, 13, 13, 13, 13, /* 1 */ + 13, 13, 13, 13, 13, 13, 13, 13, /* 1 */ + 13, 13, 13, 13, 13, 13, 13, 13, /* 1 */ + 13, 13, 13, 5, 7, 6, 7, 0, /* 1 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 2 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 2 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 2 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 2 */ + 2, 3, 4, 4, 4, 4, 15, 15, /* 2 */ + 11, 15, 16, 5, 7, 8, 15, 11, /* 2 */ + 15, 7, 17, 17, 11, 16, 15, 3, /* 2 */ + 11, 18, 16, 6, 19, 19, 19, 3, /* 2 */ + 20, 20, 20, 20, 20, 20, 20, 20, /* 3 */ + 20, 20, 20, 20, 20, 20, 20, 20, /* 3 */ + 20, 20, 20, 20, 20, 20, 20, 7, /* 3 */ + 20, 20, 20, 20, 20, 20, 20, 16, /* 3 */ + 21, 21, 21, 21, 21, 21, 21, 21, /* 3 */ + 21, 21, 21, 21, 21, 21, 21, 21, /* 3 */ + 21, 21, 21, 21, 21, 21, 21, 7, /* 3 */ + 21, 21, 21, 21, 21, 21, 21, 22, /* 3 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 4 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 4 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 4 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 4 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 4 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 4 */ + 25, 26, 23, 24, 23, 24, 23, 24, /* 4 */ + 16, 23, 24, 23, 24, 23, 24, 23, /* 4 */ + 24, 23, 24, 23, 24, 23, 24, 23, /* 5 */ + 24, 16, 23, 24, 23, 24, 23, 24, /* 5 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 5 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 5 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 5 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 5 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 5 */ + 27, 23, 24, 23, 24, 23, 24, 28, /* 5 */ + 16, 29, 23, 24, 23, 24, 30, 23, /* 6 */ + 24, 31, 31, 23, 24, 16, 32, 32, /* 6 */ + 33, 23, 24, 31, 34, 16, 35, 36, /* 6 */ + 23, 24, 16, 16, 35, 37, 16, 38, /* 6 */ + 23, 24, 23, 24, 23, 24, 38, 23, /* 6 */ + 24, 39, 40, 16, 23, 24, 39, 23, /* 6 */ + 24, 41, 41, 23, 24, 23, 24, 42, /* 6 */ + 23, 24, 16, 40, 23, 24, 40, 40, /* 6 */ + 40, 40, 40, 40, 43, 44, 45, 43, /* 7 */ + 44, 45, 43, 44, 45, 23, 24, 23, /* 7 */ + 24, 23, 24, 23, 24, 23, 24, 23, /* 7 */ + 24, 23, 24, 23, 24, 16, 23, 24, /* 7 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 7 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 7 */ + 16, 43, 44, 45, 23, 24, 46, 46, /* 7 */ + 46, 46, 23, 24, 23, 24, 23, 24, /* 7 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 8 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 8 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 8 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 8 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 8 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 8 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 8 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 8 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 9 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 9 */ + 16, 16, 16, 47, 48, 16, 49, 49, /* 9 */ + 50, 50, 16, 51, 16, 16, 16, 16, /* 9 */ + 49, 16, 16, 52, 16, 16, 16, 16, /* 9 */ + 53, 54, 16, 16, 16, 16, 16, 54, /* 9 */ + 16, 16, 55, 16, 16, 16, 16, 16, /* 9 */ + 16, 16, 16, 16, 16, 16, 16, 16, /* 9 */ + 16, 16, 16, 56, 16, 16, 16, 16, /* 10 */ + 56, 16, 57, 57, 16, 16, 16, 16, /* 10 */ + 16, 16, 58, 16, 16, 16, 16, 16, /* 10 */ + 16, 16, 16, 16, 16, 16, 16, 16, /* 10 */ + 16, 16, 16, 16, 16, 16, 16, 16, /* 10 */ + 16, 46, 46, 46, 46, 46, 46, 46, /* 10 */ + 59, 59, 59, 59, 59, 59, 59, 59, /* 10 */ + 59, 11, 11, 59, 59, 59, 59, 59, /* 10 */ + 59, 59, 11, 11, 11, 11, 11, 11, /* 11 */ + 11, 11, 11, 11, 11, 11, 11, 11, /* 11 */ + 59, 59, 11, 11, 11, 11, 11, 11, /* 11 */ + 11, 11, 11, 11, 11, 11, 11, 46, /* 11 */ + 59, 59, 59, 59, 59, 11, 11, 11, /* 11 */ + 11, 11, 46, 46, 46, 46, 46, 46, /* 11 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 11 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 11 */ + 60, 60, 60, 60, 60, 60, 60, 60, /* 12 */ + 60, 60, 60, 60, 60, 60, 60, 60, /* 12 */ + 60, 60, 60, 60, 60, 60, 60, 60, /* 12 */ + 60, 60, 60, 60, 60, 60, 60, 60, /* 12 */ + 60, 60, 60, 60, 60, 60, 60, 60, /* 12 */ + 60, 60, 60, 60, 60, 60, 60, 60, /* 12 */ + 60, 60, 60, 60, 60, 60, 60, 60, /* 12 */ + 60, 60, 60, 60, 60, 60, 60, 60, /* 12 */ + 60, 60, 60, 60, 60, 60, 46, 46, /* 13 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 13 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 13 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 13 */ + 60, 60, 46, 46, 46, 46, 46, 46, /* 13 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 13 */ + 46, 46, 46, 46, 3, 3, 46, 46, /* 13 */ + 46, 46, 59, 46, 46, 46, 3, 46, /* 13 */ + 46, 46, 46, 46, 11, 11, 61, 3, /* 14 */ + 62, 62, 62, 46, 63, 46, 64, 64, /* 14 */ + 16, 20, 20, 20, 20, 20, 20, 20, /* 14 */ + 20, 20, 20, 20, 20, 20, 20, 20, /* 14 */ + 20, 20, 46, 20, 20, 20, 20, 20, /* 14 */ + 20, 20, 20, 20, 65, 66, 66, 66, /* 14 */ + 16, 21, 21, 21, 21, 21, 21, 21, /* 14 */ + 21, 21, 21, 21, 21, 21, 21, 21, /* 14 */ + 21, 21, 16, 21, 21, 21, 21, 21, /* 15 */ + 21, 21, 21, 21, 67, 68, 68, 46, /* 15 */ + 69, 70, 38, 38, 38, 71, 72, 46, /* 15 */ + 46, 46, 38, 46, 38, 46, 38, 46, /* 15 */ + 38, 46, 23, 24, 23, 24, 23, 24, /* 15 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 15 */ + 73, 74, 16, 40, 46, 46, 46, 46, /* 15 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 15 */ + 46, 75, 75, 75, 75, 75, 75, 75, /* 16 */ + 75, 75, 75, 75, 75, 46, 75, 75, /* 16 */ + 20, 20, 20, 20, 20, 20, 20, 20, /* 16 */ + 20, 20, 20, 20, 20, 20, 20, 20, /* 16 */ + 20, 20, 20, 20, 20, 20, 20, 20, /* 16 */ + 20, 20, 20, 20, 20, 20, 20, 20, /* 16 */ + 21, 21, 21, 21, 21, 21, 21, 21, /* 16 */ + 21, 21, 21, 21, 21, 21, 21, 21, /* 16 */ + 21, 21, 21, 21, 21, 21, 21, 21, /* 17 */ + 21, 21, 21, 21, 21, 21, 21, 21, /* 17 */ + 46, 74, 74, 74, 74, 74, 74, 74, /* 17 */ + 74, 74, 74, 74, 74, 46, 74, 74, /* 17 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 17 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 17 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 17 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 17 */ + 23, 24, 15, 60, 60, 60, 60, 46, /* 18 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 18 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 18 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 18 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 18 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 18 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 18 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 18 */ + 40, 23, 24, 23, 24, 46, 46, 23, /* 19 */ + 24, 46, 46, 23, 24, 46, 46, 46, /* 19 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 19 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 19 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 19 */ + 23, 24, 23, 24, 46, 46, 23, 24, /* 19 */ + 23, 24, 23, 24, 23, 24, 46, 46, /* 19 */ + 23, 24, 46, 46, 46, 46, 46, 46, /* 19 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 20 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 20 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 20 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 20 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 20 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 20 */ + 46, 76, 76, 76, 76, 76, 76, 76, /* 20 */ + 76, 76, 76, 76, 76, 76, 76, 76, /* 20 */ + 76, 76, 76, 76, 76, 76, 76, 76, /* 21 */ + 76, 76, 76, 76, 76, 76, 76, 76, /* 21 */ + 76, 76, 76, 76, 76, 76, 76, 46, /* 21 */ + 46, 59, 3, 3, 3, 3, 3, 3, /* 21 */ + 46, 77, 77, 77, 77, 77, 77, 77, /* 21 */ + 77, 77, 77, 77, 77, 77, 77, 77, /* 21 */ + 77, 77, 77, 77, 77, 77, 77, 77, /* 21 */ + 77, 77, 77, 77, 77, 77, 77, 77, /* 21 */ + 77, 77, 77, 77, 77, 77, 77, 16, /* 22 */ + 46, 3, 46, 46, 46, 46, 46, 46, /* 22 */ + 46, 60, 60, 60, 60, 60, 60, 60, /* 22 */ + 60, 60, 60, 60, 60, 60, 60, 60, /* 22 */ + 60, 60, 46, 60, 60, 60, 60, 60, /* 22 */ + 60, 60, 60, 60, 60, 60, 60, 60, /* 22 */ + 60, 60, 60, 60, 60, 60, 60, 60, /* 22 */ + 60, 60, 46, 60, 60, 60, 3, 60, /* 22 */ + 3, 60, 60, 3, 60, 46, 46, 46, /* 23 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 23 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 23 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 23 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 23 */ + 40, 40, 40, 46, 46, 46, 46, 46, /* 23 */ + 40, 40, 40, 3, 3, 46, 46, 46, /* 23 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 23 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 24 */ + 46, 46, 46, 46, 3, 46, 46, 46, /* 24 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 24 */ + 46, 46, 46, 3, 46, 46, 46, 3, /* 24 */ + 46, 40, 40, 40, 40, 40, 40, 40, /* 24 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 24 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 24 */ + 40, 40, 40, 46, 46, 46, 46, 46, /* 24 */ + 59, 40, 40, 40, 40, 40, 40, 40, /* 25 */ + 40, 40, 40, 60, 60, 60, 60, 60, /* 25 */ + 60, 60, 60, 46, 46, 46, 46, 46, /* 25 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 25 */ + 78, 78, 78, 78, 78, 78, 78, 78, /* 25 */ + 78, 78, 3, 3, 3, 3, 46, 46, /* 25 */ + 60, 40, 40, 40, 40, 40, 40, 40, /* 25 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 25 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 26 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 26 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 26 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 26 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 26 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 26 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 26 */ + 46, 46, 40, 40, 40, 40, 40, 46, /* 26 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 27 */ + 40, 40, 40, 40, 40, 40, 40, 46, /* 27 */ + 40, 40, 40, 40, 3, 40, 60, 60, /* 27 */ + 60, 60, 60, 60, 60, 79, 79, 60, /* 27 */ + 60, 60, 60, 60, 60, 59, 59, 60, /* 27 */ + 60, 15, 60, 60, 60, 60, 46, 46, /* 27 */ + 9, 9, 9, 9, 9, 9, 9, 9, /* 27 */ + 9, 9, 46, 46, 46, 46, 46, 46, /* 27 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 28 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 28 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 28 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 28 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 28 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 28 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 28 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 28 */ + 46, 60, 60, 80, 46, 40, 40, 40, /* 29 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 29 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 29 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 29 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 29 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 29 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 29 */ + 40, 40, 46, 46, 60, 40, 80, 80, /* 29 */ + 80, 60, 60, 60, 60, 60, 60, 60, /* 30 */ + 60, 80, 80, 80, 80, 60, 46, 46, /* 30 */ + 15, 60, 60, 60, 60, 46, 46, 46, /* 30 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 30 */ + 40, 40, 60, 60, 3, 3, 81, 81, /* 30 */ + 81, 81, 81, 81, 81, 81, 81, 81, /* 30 */ + 3, 46, 46, 46, 46, 46, 46, 46, /* 30 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 30 */ + 46, 60, 80, 80, 46, 40, 40, 40, /* 31 */ + 40, 40, 40, 40, 40, 46, 46, 40, /* 31 */ + 40, 46, 46, 40, 40, 40, 40, 40, /* 31 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 31 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 31 */ + 40, 46, 40, 40, 40, 40, 40, 40, /* 31 */ + 40, 46, 40, 46, 46, 46, 40, 40, /* 31 */ + 40, 40, 46, 46, 60, 46, 80, 80, /* 31 */ + 80, 60, 60, 60, 60, 46, 46, 80, /* 32 */ + 80, 46, 46, 80, 80, 60, 46, 46, /* 32 */ + 46, 46, 46, 46, 46, 46, 46, 80, /* 32 */ + 46, 46, 46, 46, 40, 40, 46, 40, /* 32 */ + 40, 40, 60, 60, 46, 46, 81, 81, /* 32 */ + 81, 81, 81, 81, 81, 81, 81, 81, /* 32 */ + 40, 40, 4, 4, 82, 82, 82, 82, /* 32 */ + 19, 83, 15, 46, 46, 46, 46, 46, /* 32 */ + 46, 46, 60, 46, 46, 40, 40, 40, /* 33 */ + 40, 40, 40, 46, 46, 46, 46, 40, /* 33 */ + 40, 46, 46, 40, 40, 40, 40, 40, /* 33 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 33 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 33 */ + 40, 46, 40, 40, 40, 40, 40, 40, /* 33 */ + 40, 46, 40, 40, 46, 40, 40, 46, /* 33 */ + 40, 40, 46, 46, 60, 46, 80, 80, /* 33 */ + 80, 60, 60, 46, 46, 46, 46, 60, /* 34 */ + 60, 46, 46, 60, 60, 60, 46, 46, /* 34 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 34 */ + 46, 40, 40, 40, 40, 46, 40, 46, /* 34 */ + 46, 46, 46, 46, 46, 46, 81, 81, /* 34 */ + 81, 81, 81, 81, 81, 81, 81, 81, /* 34 */ + 60, 60, 40, 40, 40, 46, 46, 46, /* 34 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 34 */ + 46, 60, 60, 80, 46, 40, 40, 40, /* 35 */ + 40, 40, 40, 40, 46, 40, 46, 40, /* 35 */ + 40, 40, 46, 40, 40, 40, 40, 40, /* 35 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 35 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 35 */ + 40, 46, 40, 40, 40, 40, 40, 40, /* 35 */ + 40, 46, 40, 40, 46, 40, 40, 40, /* 35 */ + 40, 40, 46, 46, 60, 40, 80, 80, /* 35 */ + 80, 60, 60, 60, 60, 60, 46, 60, /* 36 */ + 60, 80, 46, 80, 80, 60, 46, 46, /* 36 */ + 15, 46, 46, 46, 46, 46, 46, 46, /* 36 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 36 */ + 40, 46, 46, 46, 46, 46, 81, 81, /* 36 */ + 81, 81, 81, 81, 81, 81, 81, 81, /* 36 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 36 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 36 */ + 46, 60, 80, 80, 46, 40, 40, 40, /* 37 */ + 40, 40, 40, 40, 40, 46, 46, 40, /* 37 */ + 40, 46, 46, 40, 40, 40, 40, 40, /* 37 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 37 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 37 */ + 40, 46, 40, 40, 40, 40, 40, 40, /* 37 */ + 40, 46, 40, 40, 46, 46, 40, 40, /* 37 */ + 40, 40, 46, 46, 60, 40, 80, 60, /* 37 */ + 80, 60, 60, 60, 46, 46, 46, 80, /* 38 */ + 80, 46, 46, 80, 80, 60, 46, 46, /* 38 */ + 46, 46, 46, 46, 46, 46, 60, 80, /* 38 */ + 46, 46, 46, 46, 40, 40, 46, 40, /* 38 */ + 40, 40, 46, 46, 46, 46, 81, 81, /* 38 */ + 81, 81, 81, 81, 81, 81, 81, 81, /* 38 */ + 15, 46, 46, 46, 46, 46, 46, 46, /* 38 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 38 */ + 46, 46, 60, 80, 46, 40, 40, 40, /* 39 */ + 40, 40, 40, 46, 46, 46, 40, 40, /* 39 */ + 40, 46, 40, 40, 40, 40, 46, 46, /* 39 */ + 46, 40, 40, 46, 40, 46, 40, 40, /* 39 */ + 46, 46, 46, 40, 40, 46, 46, 46, /* 39 */ + 40, 40, 40, 46, 46, 46, 40, 40, /* 39 */ + 40, 40, 40, 40, 40, 40, 46, 40, /* 39 */ + 40, 40, 46, 46, 46, 46, 80, 80, /* 39 */ + 60, 80, 80, 46, 46, 46, 80, 80, /* 40 */ + 80, 46, 80, 80, 80, 60, 46, 46, /* 40 */ + 46, 46, 46, 46, 46, 46, 46, 80, /* 40 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 40 */ + 46, 46, 46, 46, 46, 46, 46, 81, /* 40 */ + 81, 81, 81, 81, 81, 81, 81, 81, /* 40 */ + 84, 19, 19, 46, 46, 46, 46, 46, /* 40 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 40 */ + 46, 80, 80, 80, 46, 40, 40, 40, /* 41 */ + 40, 40, 40, 40, 40, 46, 40, 40, /* 41 */ + 40, 46, 40, 40, 40, 40, 40, 40, /* 41 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 41 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 41 */ + 40, 46, 40, 40, 40, 40, 40, 40, /* 41 */ + 40, 40, 40, 40, 46, 40, 40, 40, /* 41 */ + 40, 40, 46, 46, 46, 46, 60, 60, /* 41 */ + 60, 80, 80, 80, 80, 46, 60, 60, /* 42 */ + 60, 46, 60, 60, 60, 60, 46, 46, /* 42 */ + 46, 46, 46, 46, 46, 60, 60, 46, /* 42 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 42 */ + 40, 40, 46, 46, 46, 46, 81, 81, /* 42 */ + 81, 81, 81, 81, 81, 81, 81, 81, /* 42 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 42 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 42 */ + 46, 46, 80, 80, 46, 40, 40, 40, /* 43 */ + 40, 40, 40, 40, 40, 46, 40, 40, /* 43 */ + 40, 46, 40, 40, 40, 40, 40, 40, /* 43 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 43 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 43 */ + 40, 46, 40, 40, 40, 40, 40, 40, /* 43 */ + 40, 40, 40, 40, 46, 40, 40, 40, /* 43 */ + 40, 40, 46, 46, 46, 46, 80, 60, /* 43 */ + 80, 80, 80, 80, 80, 46, 60, 80, /* 44 */ + 80, 46, 80, 80, 60, 60, 46, 46, /* 44 */ + 46, 46, 46, 46, 46, 80, 80, 46, /* 44 */ + 46, 46, 46, 46, 46, 46, 40, 46, /* 44 */ + 40, 40, 46, 46, 46, 46, 81, 81, /* 44 */ + 81, 81, 81, 81, 81, 81, 81, 81, /* 44 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 44 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 44 */ + 46, 46, 80, 80, 46, 40, 40, 40, /* 45 */ + 40, 40, 40, 40, 40, 46, 40, 40, /* 45 */ + 40, 46, 40, 40, 40, 40, 40, 40, /* 45 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 45 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 45 */ + 40, 46, 40, 40, 40, 40, 40, 40, /* 45 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 45 */ + 40, 40, 46, 46, 46, 46, 80, 80, /* 45 */ + 80, 60, 60, 60, 46, 46, 80, 80, /* 46 */ + 80, 46, 80, 80, 80, 60, 46, 46, /* 46 */ + 46, 46, 46, 46, 46, 46, 46, 80, /* 46 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 46 */ + 40, 40, 46, 46, 46, 46, 81, 81, /* 46 */ + 81, 81, 81, 81, 81, 81, 81, 81, /* 46 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 46 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 46 */ + 46, 40, 40, 40, 40, 40, 40, 40, /* 47 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 47 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 47 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 47 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 47 */ + 40, 40, 40, 40, 40, 40, 40, 3, /* 47 */ + 40, 60, 40, 40, 60, 60, 60, 60, /* 47 */ + 60, 60, 60, 46, 46, 46, 46, 4, /* 47 */ + 40, 40, 40, 40, 40, 40, 59, 60, /* 48 */ + 60, 60, 60, 60, 60, 60, 60, 15, /* 48 */ + 9, 9, 9, 9, 9, 9, 9, 9, /* 48 */ + 9, 9, 3, 3, 46, 46, 46, 46, /* 48 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 48 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 48 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 48 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 48 */ + 46, 40, 40, 46, 40, 46, 46, 40, /* 49 */ + 40, 46, 40, 46, 46, 40, 46, 46, /* 49 */ + 46, 46, 46, 46, 40, 40, 40, 40, /* 49 */ + 46, 40, 40, 40, 40, 40, 40, 40, /* 49 */ + 46, 40, 40, 40, 46, 40, 46, 40, /* 49 */ + 46, 46, 40, 40, 46, 40, 40, 3, /* 49 */ + 40, 60, 40, 40, 60, 60, 60, 60, /* 49 */ + 60, 60, 46, 60, 60, 40, 46, 46, /* 49 */ + 40, 40, 40, 40, 40, 46, 59, 46, /* 50 */ + 60, 60, 60, 60, 60, 60, 46, 46, /* 50 */ + 9, 9, 9, 9, 9, 9, 9, 9, /* 50 */ + 9, 9, 46, 46, 40, 40, 46, 46, /* 50 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 50 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 50 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 50 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 50 */ + 15, 15, 15, 15, 3, 3, 3, 3, /* 51 */ + 3, 3, 3, 3, 3, 3, 3, 3, /* 51 */ + 3, 3, 3, 15, 15, 15, 15, 15, /* 51 */ + 60, 60, 15, 15, 15, 15, 15, 15, /* 51 */ + 78, 78, 78, 78, 78, 78, 78, 78, /* 51 */ + 78, 78, 85, 85, 85, 85, 85, 85, /* 51 */ + 85, 85, 85, 85, 15, 60, 15, 60, /* 51 */ + 15, 60, 5, 6, 5, 6, 80, 80, /* 51 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 52 */ + 46, 40, 40, 40, 40, 40, 40, 40, /* 52 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 52 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 52 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 52 */ + 40, 40, 46, 46, 46, 46, 46, 46, /* 52 */ + 46, 60, 60, 60, 60, 60, 60, 60, /* 52 */ + 60, 60, 60, 60, 60, 60, 60, 80, /* 52 */ + 60, 60, 60, 60, 60, 3, 60, 60, /* 53 */ + 60, 60, 60, 60, 46, 46, 46, 46, /* 53 */ + 60, 60, 60, 60, 60, 60, 46, 60, /* 53 */ + 46, 60, 60, 60, 60, 60, 60, 60, /* 53 */ + 60, 60, 60, 60, 60, 60, 60, 60, /* 53 */ + 60, 60, 60, 60, 60, 60, 46, 46, /* 53 */ + 46, 60, 60, 60, 60, 60, 60, 60, /* 53 */ + 46, 60, 46, 46, 46, 46, 46, 46, /* 53 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 54 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 54 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 54 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 54 */ + 76, 76, 76, 76, 76, 76, 76, 76, /* 54 */ + 76, 76, 76, 76, 76, 76, 76, 76, /* 54 */ + 76, 76, 76, 76, 76, 76, 76, 76, /* 54 */ + 76, 76, 76, 76, 76, 76, 76, 76, /* 54 */ + 76, 76, 76, 76, 76, 76, 46, 46, /* 55 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 55 */ + 16, 16, 16, 16, 16, 16, 16, 16, /* 55 */ + 16, 16, 16, 16, 16, 16, 16, 16, /* 55 */ + 16, 16, 16, 16, 16, 16, 16, 16, /* 55 */ + 16, 16, 16, 16, 16, 16, 16, 16, /* 55 */ + 16, 16, 16, 16, 16, 16, 16, 46, /* 55 */ + 46, 46, 46, 3, 46, 46, 46, 46, /* 55 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 56 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 56 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 56 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 56 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 56 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 56 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 56 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 56 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 57 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 57 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 57 */ + 40, 40, 46, 46, 46, 46, 46, 40, /* 57 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 57 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 57 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 57 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 57 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 58 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 58 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 58 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 58 */ + 40, 40, 40, 46, 46, 46, 46, 46, /* 58 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 58 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 58 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 58 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 59 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 59 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 59 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 59 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 59 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 59 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 59 */ + 40, 40, 46, 46, 46, 46, 46, 46, /* 59 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 60 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 60 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 60 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 60 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 60 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 60 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 60 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 60 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 61 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 61 */ + 23, 24, 23, 24, 23, 24, 16, 16, /* 61 */ + 16, 16, 16, 16, 46, 46, 46, 46, /* 61 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 61 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 61 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 61 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 61 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 62 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 62 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 62 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 62 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 62 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 62 */ + 23, 24, 23, 24, 23, 24, 23, 24, /* 62 */ + 23, 24, 46, 46, 46, 46, 46, 46, /* 62 */ + 86, 86, 86, 86, 86, 86, 86, 86, /* 63 */ + 87, 87, 87, 87, 87, 87, 87, 87, /* 63 */ + 86, 86, 86, 86, 86, 86, 46, 46, /* 63 */ + 87, 87, 87, 87, 87, 87, 46, 46, /* 63 */ + 86, 86, 86, 86, 86, 86, 86, 86, /* 63 */ + 87, 87, 87, 87, 87, 87, 87, 87, /* 63 */ + 86, 86, 86, 86, 86, 86, 86, 86, /* 63 */ + 87, 87, 87, 87, 87, 87, 87, 87, /* 63 */ + 86, 86, 86, 86, 86, 86, 46, 46, /* 64 */ + 87, 87, 87, 87, 87, 87, 46, 46, /* 64 */ + 16, 86, 16, 86, 16, 86, 16, 86, /* 64 */ + 46, 87, 46, 87, 46, 87, 46, 87, /* 64 */ + 86, 86, 86, 86, 86, 86, 86, 86, /* 64 */ + 87, 87, 87, 87, 87, 87, 87, 87, /* 64 */ + 88, 88, 89, 89, 89, 89, 90, 90, /* 64 */ + 91, 91, 92, 92, 93, 93, 46, 46, /* 64 */ + 86, 86, 86, 86, 86, 86, 86, 86, /* 65 */ + 87, 87, 87, 87, 87, 87, 87, 87, /* 65 */ + 86, 86, 86, 86, 86, 86, 86, 86, /* 65 */ + 87, 87, 87, 87, 87, 87, 87, 87, /* 65 */ + 86, 86, 86, 86, 86, 86, 86, 86, /* 65 */ + 87, 87, 87, 87, 87, 87, 87, 87, /* 65 */ + 86, 86, 16, 94, 16, 46, 16, 16, /* 65 */ + 87, 87, 95, 95, 96, 11, 38, 11, /* 65 */ + 11, 11, 16, 94, 16, 46, 16, 16, /* 66 */ + 97, 97, 97, 97, 96, 11, 11, 11, /* 66 */ + 86, 86, 16, 16, 46, 46, 16, 16, /* 66 */ + 87, 87, 98, 98, 46, 11, 11, 11, /* 66 */ + 86, 86, 16, 16, 16, 99, 16, 16, /* 66 */ + 87, 87, 100, 100, 101, 11, 11, 11, /* 66 */ + 46, 46, 16, 94, 16, 46, 16, 16, /* 66 */ +102, 102, 103, 103, 96, 11, 11, 46, /* 66 */ + 2, 2, 2, 2, 2, 2, 2, 2, /* 67 */ + 2, 2, 2, 2, 104, 104, 104, 104, /* 67 */ + 8, 8, 8, 8, 8, 8, 3, 3, /* 67 */ + 5, 6, 5, 5, 5, 6, 5, 5, /* 67 */ + 3, 3, 3, 3, 3, 3, 3, 3, /* 67 */ +105, 106, 104, 104, 104, 104, 104, 46, /* 67 */ + 3, 3, 3, 3, 3, 3, 3, 3, /* 67 */ + 3, 5, 6, 3, 3, 3, 3, 12, /* 67 */ + 12, 3, 3, 3, 7, 5, 6, 46, /* 68 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 68 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 68 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 68 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 68 */ + 46, 46, 104, 104, 104, 104, 104, 104, /* 68 */ + 17, 46, 46, 46, 17, 17, 17, 17, /* 68 */ + 17, 17, 7, 7, 7, 5, 6, 16, /* 68 */ +107, 107, 107, 107, 107, 107, 107, 107, /* 69 */ +107, 107, 7, 7, 7, 5, 6, 46, /* 69 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 69 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 69 */ + 4, 4, 4, 4, 4, 4, 4, 4, /* 69 */ + 4, 4, 4, 4, 46, 46, 46, 46, /* 69 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 69 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 69 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 70 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 70 */ + 60, 60, 60, 60, 60, 60, 60, 60, /* 70 */ + 60, 60, 60, 60, 60, 79, 79, 79, /* 70 */ + 79, 60, 46, 46, 46, 46, 46, 46, /* 70 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 70 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 70 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 70 */ + 15, 15, 38, 15, 15, 15, 15, 38, /* 71 */ + 15, 15, 16, 38, 38, 38, 16, 16, /* 71 */ + 38, 38, 38, 16, 15, 38, 15, 15, /* 71 */ + 38, 38, 38, 38, 38, 38, 15, 15, /* 71 */ + 15, 15, 15, 15, 38, 15, 38, 15, /* 71 */ + 38, 15, 38, 38, 38, 38, 16, 16, /* 71 */ + 38, 38, 15, 38, 16, 40, 40, 40, /* 71 */ + 40, 46, 46, 46, 46, 46, 46, 46, /* 71 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 72 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 72 */ + 46, 46, 46, 19, 19, 19, 19, 19, /* 72 */ + 19, 19, 19, 19, 19, 19, 19, 108, /* 72 */ +109, 109, 109, 109, 109, 109, 109, 109, /* 72 */ +109, 109, 109, 109, 110, 110, 110, 110, /* 72 */ +111, 111, 111, 111, 111, 111, 111, 111, /* 72 */ +111, 111, 111, 111, 112, 112, 112, 112, /* 72 */ +113, 113, 113, 46, 46, 46, 46, 46, /* 73 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 73 */ + 7, 7, 7, 7, 7, 15, 15, 15, /* 73 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 73 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 73 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 73 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 73 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 73 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 74 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 74 */ + 15, 15, 7, 15, 7, 15, 15, 15, /* 74 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 74 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 74 */ + 15, 15, 15, 46, 46, 46, 46, 46, /* 74 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 74 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 74 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 75 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 75 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 75 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 75 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 75 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 75 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 75 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 75 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 76 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 76 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 76 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 76 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 76 */ + 7, 7, 7, 7, 7, 7, 7, 7, /* 76 */ + 7, 7, 46, 46, 46, 46, 46, 46, /* 76 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 76 */ + 15, 46, 15, 15, 15, 15, 15, 15, /* 77 */ + 7, 7, 7, 7, 15, 15, 15, 15, /* 77 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 77 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 77 */ + 7, 7, 15, 15, 15, 15, 15, 15, /* 77 */ + 15, 5, 6, 15, 15, 15, 15, 15, /* 77 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 77 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 77 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 78 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 78 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 78 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 78 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 78 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 78 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 78 */ + 15, 15, 15, 46, 46, 46, 46, 46, /* 78 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 79 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 79 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 79 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 79 */ + 15, 15, 15, 15, 15, 46, 46, 46, /* 79 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 79 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 79 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 79 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 80 */ + 15, 15, 15, 46, 46, 46, 46, 46, /* 80 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 80 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 80 */ +114, 114, 114, 114, 114, 114, 114, 114, /* 80 */ +114, 114, 114, 114, 114, 114, 114, 114, /* 80 */ +114, 114, 114, 114, 82, 82, 82, 82, /* 80 */ + 82, 82, 82, 82, 82, 82, 82, 82, /* 80 */ + 82, 82, 82, 82, 82, 82, 82, 82, /* 81 */ +115, 115, 115, 115, 115, 115, 115, 115, /* 81 */ +115, 115, 115, 115, 115, 115, 115, 115, /* 81 */ +115, 115, 115, 115, 15, 15, 15, 15, /* 81 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 81 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 81 */ + 15, 15, 15, 15, 15, 15, 116, 116, /* 81 */ +116, 116, 116, 116, 116, 116, 116, 116, /* 81 */ +116, 116, 116, 116, 116, 116, 116, 116, /* 82 */ +116, 116, 116, 116, 116, 116, 116, 116, /* 82 */ +117, 117, 117, 117, 117, 117, 117, 117, /* 82 */ +117, 117, 117, 117, 117, 117, 117, 117, /* 82 */ +117, 117, 117, 117, 117, 117, 117, 117, /* 82 */ +117, 117, 118, 46, 46, 46, 46, 46, /* 82 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 82 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 82 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 83 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 83 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 83 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 83 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 83 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 83 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 83 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 83 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 84 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 84 */ + 15, 15, 15, 15, 15, 15, 46, 46, /* 84 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 84 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 84 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 84 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 84 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 84 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 85 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 85 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 85 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 85 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 85 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 85 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 85 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 85 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 86 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 86 */ + 15, 15, 15, 15, 46, 46, 46, 46, /* 86 */ + 46, 46, 15, 15, 15, 15, 15, 15, /* 86 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 86 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 86 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 86 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 86 */ + 46, 15, 15, 15, 15, 46, 15, 15, /* 87 */ + 15, 15, 46, 46, 15, 15, 15, 15, /* 87 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 87 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 87 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 87 */ + 46, 15, 15, 15, 15, 15, 15, 15, /* 87 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 87 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 87 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 88 */ + 15, 15, 15, 15, 46, 15, 46, 15, /* 88 */ + 15, 15, 15, 46, 46, 46, 15, 46, /* 88 */ + 15, 15, 15, 15, 15, 15, 15, 46, /* 88 */ + 46, 15, 15, 15, 15, 15, 15, 15, /* 88 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 88 */ + 46, 46, 46, 46, 46, 46, 119, 119, /* 88 */ +119, 119, 119, 119, 119, 119, 119, 119, /* 88 */ +114, 114, 114, 114, 114, 114, 114, 114, /* 89 */ +114, 114, 83, 83, 83, 83, 83, 83, /* 89 */ + 83, 83, 83, 83, 15, 46, 46, 46, /* 89 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 89 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 89 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 89 */ + 46, 15, 15, 15, 15, 15, 15, 15, /* 89 */ + 15, 15, 15, 15, 15, 15, 15, 46, /* 89 */ + 2, 3, 3, 3, 15, 59, 3, 120, /* 90 */ + 5, 6, 5, 6, 5, 6, 5, 6, /* 90 */ + 5, 6, 15, 15, 5, 6, 5, 6, /* 90 */ + 5, 6, 5, 6, 8, 5, 6, 5, /* 90 */ + 15, 121, 121, 121, 121, 121, 121, 121, /* 90 */ +121, 121, 60, 60, 60, 60, 60, 60, /* 90 */ + 8, 59, 59, 59, 59, 59, 15, 15, /* 90 */ + 46, 46, 46, 46, 46, 46, 46, 15, /* 90 */ + 46, 40, 40, 40, 40, 40, 40, 40, /* 91 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 91 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 91 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 91 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 91 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 91 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 91 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 91 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 92 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 92 */ + 40, 40, 40, 40, 40, 46, 46, 46, /* 92 */ + 46, 60, 60, 59, 59, 59, 59, 46, /* 92 */ + 46, 40, 40, 40, 40, 40, 40, 40, /* 92 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 92 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 92 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 92 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 93 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 93 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 93 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 93 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 93 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 93 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 93 */ + 40, 40, 40, 3, 59, 59, 59, 46, /* 93 */ + 46, 46, 46, 46, 46, 40, 40, 40, /* 94 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 94 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 94 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 94 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 94 */ + 40, 40, 40, 40, 40, 46, 46, 46, /* 94 */ + 46, 40, 40, 40, 40, 40, 40, 40, /* 94 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 94 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 95 */ + 40, 40, 40, 40, 40, 40, 40, 46, /* 95 */ + 15, 15, 85, 85, 85, 85, 15, 15, /* 95 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 95 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 95 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 95 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 95 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 95 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 96 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 96 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 96 */ + 15, 15, 15, 15, 15, 46, 46, 46, /* 96 */ + 85, 85, 85, 85, 85, 85, 85, 85, /* 96 */ + 85, 85, 15, 15, 15, 15, 15, 15, /* 96 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 96 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 96 */ + 15, 15, 15, 15, 46, 46, 46, 46, /* 97 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 97 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 97 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 97 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 97 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 97 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 97 */ + 15, 15, 15, 15, 46, 46, 46, 15, /* 97 */ +114, 114, 114, 114, 114, 114, 114, 114, /* 98 */ +114, 114, 15, 15, 15, 15, 15, 15, /* 98 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 98 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 98 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 98 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 98 */ + 15, 46, 46, 46, 46, 46, 46, 46, /* 98 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 98 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 99 */ + 15, 15, 15, 15, 46, 46, 46, 46, /* 99 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 99 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 99 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 99 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 99 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 99 */ + 15, 15, 15, 15, 15, 15, 15, 46, /* 99 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 100 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 100 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 100 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 100 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 100 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 100 */ + 15, 15, 15, 15, 15, 15, 15, 46, /* 100 */ + 46, 46, 46, 15, 15, 15, 15, 15, /* 100 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 101 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 101 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 101 */ + 15, 15, 15, 15, 15, 15, 46, 46, /* 101 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 101 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 101 */ + 15, 15, 15, 15, 15, 15, 15, 15, /* 101 */ + 15, 15, 15, 15, 15, 15, 15, 46, /* 101 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 102 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 102 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 102 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 102 */ + 40, 40, 40, 40, 40, 40, 46, 46, /* 102 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 102 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 102 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 102 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 103 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 103 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 103 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 103 */ + 40, 40, 40, 40, 46, 46, 46, 46, /* 103 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 103 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 103 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 103 */ +122, 122, 122, 122, 122, 122, 122, 122, /* 104 */ +122, 122, 122, 122, 122, 122, 122, 122, /* 104 */ +122, 122, 122, 122, 122, 122, 122, 122, /* 104 */ +122, 122, 122, 122, 122, 122, 122, 122, /* 104 */ +122, 122, 122, 122, 122, 122, 122, 122, /* 104 */ +122, 122, 122, 122, 122, 122, 122, 122, /* 104 */ +122, 122, 122, 122, 122, 122, 122, 122, /* 104 */ +122, 122, 122, 122, 122, 122, 122, 122, /* 104 */ +123, 123, 123, 123, 123, 123, 123, 123, /* 105 */ +123, 123, 123, 123, 123, 123, 123, 123, /* 105 */ +123, 123, 123, 123, 123, 123, 123, 123, /* 105 */ +123, 123, 123, 123, 123, 123, 123, 123, /* 105 */ +123, 123, 123, 123, 123, 123, 123, 123, /* 105 */ +123, 123, 123, 123, 123, 123, 123, 123, /* 105 */ +123, 123, 123, 123, 123, 123, 123, 123, /* 105 */ +123, 123, 123, 123, 123, 123, 123, 123, /* 105 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 106 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 106 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 106 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 106 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 106 */ + 40, 40, 40, 40, 40, 40, 46, 46, /* 106 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 106 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 106 */ + 16, 16, 16, 16, 16, 16, 16, 46, /* 107 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 107 */ + 46, 46, 46, 16, 16, 16, 16, 16, /* 107 */ + 46, 46, 46, 46, 46, 46, 60, 40, /* 107 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 107 */ + 40, 7, 40, 40, 40, 40, 40, 40, /* 107 */ + 40, 40, 40, 40, 40, 40, 40, 46, /* 107 */ + 40, 40, 40, 40, 40, 46, 40, 46, /* 107 */ + 40, 40, 46, 40, 40, 46, 40, 40, /* 108 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 108 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 108 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 108 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 108 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 108 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 108 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 108 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 109 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 109 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 109 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 109 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 109 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 109 */ + 40, 40, 46, 46, 46, 46, 46, 46, /* 109 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 109 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 110 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 110 */ + 46, 46, 46, 40, 40, 40, 40, 40, /* 110 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 110 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 110 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 110 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 110 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 110 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 111 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 111 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 111 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 111 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 111 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 111 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 111 */ + 40, 40, 40, 40, 40, 40, 5, 6, /* 111 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 112 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 112 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 112 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 112 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 112 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 112 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 112 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 112 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 113 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 113 */ + 46, 46, 40, 40, 40, 40, 40, 40, /* 113 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 113 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 113 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 113 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 113 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 113 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 114 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 114 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 114 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 114 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 114 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 114 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 114 */ + 40, 40, 40, 40, 46, 46, 46, 46, /* 114 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 115 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 115 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 115 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 115 */ + 60, 60, 60, 60, 46, 46, 46, 46, /* 115 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 115 */ + 3, 8, 8, 12, 12, 5, 6, 5, /* 115 */ + 6, 5, 6, 5, 6, 5, 6, 5, /* 115 */ + 6, 5, 6, 5, 6, 46, 46, 46, /* 116 */ + 46, 3, 3, 3, 3, 12, 12, 12, /* 116 */ + 3, 3, 3, 46, 3, 3, 3, 3, /* 116 */ + 8, 5, 6, 5, 6, 5, 6, 3, /* 116 */ + 3, 3, 7, 8, 7, 7, 7, 46, /* 116 */ + 3, 4, 3, 3, 46, 46, 46, 46, /* 116 */ + 40, 40, 40, 46, 40, 46, 40, 40, /* 116 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 116 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 117 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 117 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 117 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 117 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 117 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 117 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 117 */ + 40, 40, 40, 40, 40, 46, 46, 104, /* 117 */ + 46, 3, 3, 3, 4, 3, 3, 3, /* 118 */ + 5, 6, 3, 7, 3, 8, 3, 3, /* 118 */ + 9, 9, 9, 9, 9, 9, 9, 9, /* 118 */ + 9, 9, 3, 3, 7, 7, 7, 3, /* 118 */ + 3, 10, 10, 10, 10, 10, 10, 10, /* 118 */ + 10, 10, 10, 10, 10, 10, 10, 10, /* 118 */ + 10, 10, 10, 10, 10, 10, 10, 10, /* 118 */ + 10, 10, 10, 5, 3, 6, 11, 12, /* 118 */ + 11, 13, 13, 13, 13, 13, 13, 13, /* 119 */ + 13, 13, 13, 13, 13, 13, 13, 13, /* 119 */ + 13, 13, 13, 13, 13, 13, 13, 13, /* 119 */ + 13, 13, 13, 5, 7, 6, 7, 46, /* 119 */ + 46, 3, 5, 6, 3, 3, 40, 40, /* 119 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 119 */ + 59, 40, 40, 40, 40, 40, 40, 40, /* 119 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 119 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 120 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 120 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 120 */ + 40, 40, 40, 40, 40, 40, 59, 59, /* 120 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 120 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 120 */ + 40, 40, 40, 40, 40, 40, 40, 40, /* 120 */ + 40, 40, 40, 40, 40, 40, 40, 46, /* 120 */ + 46, 46, 40, 40, 40, 40, 40, 40, /* 121 */ + 46, 46, 40, 40, 40, 40, 40, 40, /* 121 */ + 46, 46, 40, 40, 40, 40, 40, 40, /* 121 */ + 46, 46, 40, 40, 40, 46, 46, 46, /* 121 */ + 4, 4, 7, 11, 15, 4, 4, 46, /* 121 */ + 7, 7, 7, 7, 7, 15, 15, 46, /* 121 */ + 46, 46, 46, 46, 46, 46, 46, 46, /* 121 */ + 46, 46, 46, 46, 46, 15, 46, 46 /* 121 */ +}; + +/* The A table has 124 entries for a total of 496 bytes. */ + +const uint32 js_A[] = { +0x0001000F, /* 0 Cc, ignorable */ +0x0004000F, /* 1 Cc, whitespace */ +0x0004000C, /* 2 Zs, whitespace */ +0x00000018, /* 3 Po */ +0x0006001A, /* 4 Sc, currency */ +0x00000015, /* 5 Ps */ +0x00000016, /* 6 Pe */ +0x00000019, /* 7 Sm */ +0x00000014, /* 8 Pd */ +0x00036009, /* 9 Nd, identifier part, decimal 16 */ +0x0827FE01, /* 10 Lu, hasLower (add 32), identifier start, supradecimal 31 */ +0x0000001B, /* 11 Sk */ +0x00050017, /* 12 Pc, underscore */ +0x0817FE02, /* 13 Ll, hasUpper (subtract 32), identifier start, supradecimal 31 */ +0x0000000C, /* 14 Zs */ +0x0000001C, /* 15 So */ +0x00070002, /* 16 Ll, identifier start */ +0x0000600B, /* 17 No, decimal 16 */ +0x0000500B, /* 18 No, decimal 8 */ +0x0000800B, /* 19 No, strange */ +0x08270001, /* 20 Lu, hasLower (add 32), identifier start */ +0x08170002, /* 21 Ll, hasUpper (subtract 32), identifier start */ +0xE1D70002, /* 22 Ll, hasUpper (subtract -121), identifier start */ +0x00670001, /* 23 Lu, hasLower (add 1), identifier start */ +0x00570002, /* 24 Ll, hasUpper (subtract 1), identifier start */ +0xCE670001, /* 25 Lu, hasLower (add -199), identifier start */ +0x3A170002, /* 26 Ll, hasUpper (subtract 232), identifier start */ +0xE1E70001, /* 27 Lu, hasLower (add -121), identifier start */ +0x4B170002, /* 28 Ll, hasUpper (subtract 300), identifier start */ +0x34A70001, /* 29 Lu, hasLower (add 210), identifier start */ +0x33A70001, /* 30 Lu, hasLower (add 206), identifier start */ +0x33670001, /* 31 Lu, hasLower (add 205), identifier start */ +0x32A70001, /* 32 Lu, hasLower (add 202), identifier start */ +0x32E70001, /* 33 Lu, hasLower (add 203), identifier start */ +0x33E70001, /* 34 Lu, hasLower (add 207), identifier start */ +0x34E70001, /* 35 Lu, hasLower (add 211), identifier start */ +0x34670001, /* 36 Lu, hasLower (add 209), identifier start */ +0x35670001, /* 37 Lu, hasLower (add 213), identifier start */ +0x00070001, /* 38 Lu, identifier start */ +0x36A70001, /* 39 Lu, hasLower (add 218), identifier start */ +0x00070005, /* 40 Lo, identifier start */ +0x36670001, /* 41 Lu, hasLower (add 217), identifier start */ +0x36E70001, /* 42 Lu, hasLower (add 219), identifier start */ +0x00AF0001, /* 43 Lu, hasLower (add 2), hasTitle, identifier start */ +0x007F0003, /* 44 Lt, hasUpper (subtract 1), hasLower (add 1), hasTitle, identifier start */ +0x009F0002, /* 45 Ll, hasUpper (subtract 2), hasTitle, identifier start */ +0x00000000, /* 46 unassigned */ +0x34970002, /* 47 Ll, hasUpper (subtract 210), identifier start */ +0x33970002, /* 48 Ll, hasUpper (subtract 206), identifier start */ +0x33570002, /* 49 Ll, hasUpper (subtract 205), identifier start */ +0x32970002, /* 50 Ll, hasUpper (subtract 202), identifier start */ +0x32D70002, /* 51 Ll, hasUpper (subtract 203), identifier start */ +0x33D70002, /* 52 Ll, hasUpper (subtract 207), identifier start */ +0x34570002, /* 53 Ll, hasUpper (subtract 209), identifier start */ +0x34D70002, /* 54 Ll, hasUpper (subtract 211), identifier start */ +0x35570002, /* 55 Ll, hasUpper (subtract 213), identifier start */ +0x36970002, /* 56 Ll, hasUpper (subtract 218), identifier start */ +0x36570002, /* 57 Ll, hasUpper (subtract 217), identifier start */ +0x36D70002, /* 58 Ll, hasUpper (subtract 219), identifier start */ +0x00070004, /* 59 Lm, identifier start */ +0x00030006, /* 60 Mn, identifier part */ +0x09A70001, /* 61 Lu, hasLower (add 38), identifier start */ +0x09670001, /* 62 Lu, hasLower (add 37), identifier start */ +0x10270001, /* 63 Lu, hasLower (add 64), identifier start */ +0x0FE70001, /* 64 Lu, hasLower (add 63), identifier start */ +0x09970002, /* 65 Ll, hasUpper (subtract 38), identifier start */ +0x09570002, /* 66 Ll, hasUpper (subtract 37), identifier start */ +0x10170002, /* 67 Ll, hasUpper (subtract 64), identifier start */ +0x0FD70002, /* 68 Ll, hasUpper (subtract 63), identifier start */ +0x0F970002, /* 69 Ll, hasUpper (subtract 62), identifier start */ +0x0E570002, /* 70 Ll, hasUpper (subtract 57), identifier start */ +0x0BD70002, /* 71 Ll, hasUpper (subtract 47), identifier start */ +0x0D970002, /* 72 Ll, hasUpper (subtract 54), identifier start */ +0x15970002, /* 73 Ll, hasUpper (subtract 86), identifier start */ +0x14170002, /* 74 Ll, hasUpper (subtract 80), identifier start */ +0x14270001, /* 75 Lu, hasLower (add 80), identifier start */ +0x0C270001, /* 76 Lu, hasLower (add 48), identifier start */ +0x0C170002, /* 77 Ll, hasUpper (subtract 48), identifier start */ +0x00034009, /* 78 Nd, identifier part, decimal 0 */ +0x00000007, /* 79 Me */ +0x00030008, /* 80 Mc, identifier part */ +0x00037409, /* 81 Nd, identifier part, decimal 26 */ +0x00005A0B, /* 82 No, decimal 13 */ +0x00006E0B, /* 83 No, decimal 23 */ +0x0000740B, /* 84 No, decimal 26 */ +0x0000000B, /* 85 No */ +0xFE170002, /* 86 Ll, hasUpper (subtract -8), identifier start */ +0xFE270001, /* 87 Lu, hasLower (add -8), identifier start */ +0xED970002, /* 88 Ll, hasUpper (subtract -74), identifier start */ +0xEA970002, /* 89 Ll, hasUpper (subtract -86), identifier start */ +0xE7170002, /* 90 Ll, hasUpper (subtract -100), identifier start */ +0xE0170002, /* 91 Ll, hasUpper (subtract -128), identifier start */ +0xE4170002, /* 92 Ll, hasUpper (subtract -112), identifier start */ +0xE0970002, /* 93 Ll, hasUpper (subtract -126), identifier start */ +0xFDD70002, /* 94 Ll, hasUpper (subtract -9), identifier start */ +0xEDA70001, /* 95 Lu, hasLower (add -74), identifier start */ +0xFDE70001, /* 96 Lu, hasLower (add -9), identifier start */ +0xEAA70001, /* 97 Lu, hasLower (add -86), identifier start */ +0xE7270001, /* 98 Lu, hasLower (add -100), identifier start */ +0xFE570002, /* 99 Ll, hasUpper (subtract -7), identifier start */ +0xE4270001, /* 100 Lu, hasLower (add -112), identifier start */ +0xFE670001, /* 101 Lu, hasLower (add -7), identifier start */ +0xE0270001, /* 102 Lu, hasLower (add -128), identifier start */ +0xE0A70001, /* 103 Lu, hasLower (add -126), identifier start */ +0x00010010, /* 104 Cf, ignorable */ +0x0004000D, /* 105 Zl, whitespace */ +0x0004000E, /* 106 Zp, whitespace */ +0x0000400B, /* 107 No, decimal 0 */ +0x0000440B, /* 108 No, decimal 2 */ +0x0427420A, /* 109 Nl, hasLower (add 16), identifier start, decimal 1 */ +0x0427800A, /* 110 Nl, hasLower (add 16), identifier start, strange */ +0x0417620A, /* 111 Nl, hasUpper (subtract 16), identifier start, decimal 17 */ +0x0417800A, /* 112 Nl, hasUpper (subtract 16), identifier start, strange */ +0x0007800A, /* 113 Nl, identifier start, strange */ +0x0000420B, /* 114 No, decimal 1 */ +0x0000720B, /* 115 No, decimal 25 */ +0x06A0001C, /* 116 So, hasLower (add 26) */ +0x0690001C, /* 117 So, hasUpper (subtract 26) */ +0x00006C0B, /* 118 No, decimal 22 */ +0x0000560B, /* 119 No, decimal 11 */ +0x0007720A, /* 120 Nl, identifier start, decimal 25 */ +0x0007400A, /* 121 Nl, identifier start, decimal 0 */ +0x00000013, /* 122 Cs */ +0x00000012 /* 123 Co */ +}; + +const jschar js_uriReservedPlusPound_ucstr[] = + {';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '#', 0}; +const jschar js_uriUnescaped_ucstr[] = + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '-', '_', '.', '!', '~', '*', '\'', '(', ')', 0}; + +#define URI_CHUNK 64U + +/* Concatenate jschars onto an unshared/newborn JSString. */ +static JSBool +AddCharsToURI(JSContext *cx, JSString *str, const jschar *chars, size_t length) +{ + size_t total; + + JS_ASSERT(!JSSTRING_IS_DEPENDENT(str)); + total = str->length + length + 1; + if (!str->chars || + JS_HOWMANY(total, URI_CHUNK) > JS_HOWMANY(str->length + 1, URI_CHUNK)) { + total = JS_ROUNDUP(total, URI_CHUNK); + str->chars = JS_realloc(cx, str->chars, total * sizeof(jschar)); + if (!str->chars) + return JS_FALSE; + } + js_strncpy(str->chars + str->length, chars, length); + str->length += length; + str->chars[str->length] = 0; + return JS_TRUE; +} + +/* + * ECMA 3, 15.1.3 URI Handling Function Properties + * + * The following are implementations of the algorithms + * given in the ECMA specification for the hidden functions + * 'Encode' and 'Decode'. + */ +static JSBool +Encode(JSContext *cx, JSString *str, const jschar *unescapedSet, + const jschar *unescapedSet2, jsval *rval) +{ + size_t length, j, k, L; + jschar *chars, C, C2; + uint32 V; + uint8 utf8buf[6]; + jschar hexBuf[4]; + static const char HexDigits[] = "0123456789ABCDEF"; /* NB: uppercase */ + JSString *R; + + R = js_NewString(cx, NULL, 0, 0); + if (!R) + return JS_FALSE; + + hexBuf[0] = '%'; + hexBuf[3] = 0; + chars = JSSTRING_CHARS(str); + length = JSSTRING_LENGTH(str); + for (k = 0; k < length; k++) { + C = chars[k]; + if (js_strchr(unescapedSet, C) || + (unescapedSet2 && js_strchr(unescapedSet2, C))) { + if (!AddCharsToURI(cx, R, &C, 1)) + return JS_FALSE; + } else { + if ((C >= 0xDC00) && (C <= 0xDFFF)) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_URI, NULL); + return JS_FALSE; + } + if (C < 0xD800 || C > 0xDBFF) { + V = C; + } else { + k++; + if (k == length) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_URI, NULL); + return JS_FALSE; + } + C2 = chars[k]; + if ((C2 < 0xDC00) || (C2 > 0xDFFF)) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_URI, NULL); + return JS_FALSE; + } + V = ((C - 0xD800) << 10) + (C2 - 0xDC00) + 0x10000; + } + L = OneUcs4ToUtf8Char(utf8buf, V); + for (j = 0; j < L; j++) { + hexBuf[1] = HexDigits[utf8buf[j] >> 4]; + hexBuf[2] = HexDigits[utf8buf[j] & 0xf]; + if (!AddCharsToURI(cx, R, hexBuf, 3)) + return JS_FALSE; + } + } + } + + /* + * Shrinking realloc can fail (e.g., with a BSD-style allocator), but we + * don't worry about that case here. Worst case, R hangs onto URI_CHUNK-1 + * more jschars than it needs. + */ + chars = (jschar *) JS_realloc(cx, R->chars, (R->length+1) * sizeof(jschar)); + if (chars) + R->chars = chars; + *rval = STRING_TO_JSVAL(R); + return JS_TRUE; +} + +static JSBool +Decode(JSContext *cx, JSString *str, const jschar *reservedSet, jsval *rval) +{ + size_t length, start, k; + jschar *chars, C, H; + uint32 V; + jsuint B; + uint8 octets[6]; + JSString *R; + intN j, n; + + R = js_NewString(cx, NULL, 0, 0); + if (!R) + return JS_FALSE; + + chars = JSSTRING_CHARS(str); + length = JSSTRING_LENGTH(str); + for (k = 0; k < length; k++) { + C = chars[k]; + if (C == '%') { + start = k; + if ((k + 2) >= length) + goto bad; + if (!JS7_ISHEX(chars[k+1]) || !JS7_ISHEX(chars[k+2])) + goto bad; + B = JS7_UNHEX(chars[k+1]) * 16 + JS7_UNHEX(chars[k+2]); + k += 2; + if (!(B & 0x80)) { + C = (jschar)B; + } else { + n = 1; + while (B & (0x80 >> n)) + n++; + if (n == 1 || n > 6) + goto bad; + octets[0] = (uint8)B; + if (k + 3 * (n - 1) >= length) + goto bad; + for (j = 1; j < n; j++) { + k++; + if (chars[k] != '%') + goto bad; + if (!JS7_ISHEX(chars[k+1]) || !JS7_ISHEX(chars[k+2])) + goto bad; + B = JS7_UNHEX(chars[k+1]) * 16 + JS7_UNHEX(chars[k+2]); + if ((B & 0xC0) != 0x80) + goto bad; + k += 2; + octets[j] = (char)B; + } + V = Utf8ToOneUcs4Char(octets, n); + if (V >= 0x10000) { + V -= 0x10000; + if (V > 0xFFFFF) + goto bad; + C = (jschar)((V & 0x3FF) + 0xDC00); + H = (jschar)((V >> 10) + 0xD800); + if (!AddCharsToURI(cx, R, &H, 1)) + return JS_FALSE; + } else { + C = (jschar)V; + } + } + if (js_strchr(reservedSet, C)) { + if (!AddCharsToURI(cx, R, &chars[start], (k - start + 1))) + return JS_FALSE; + } else { + if (!AddCharsToURI(cx, R, &C, 1)) + return JS_FALSE; + } + } else { + if (!AddCharsToURI(cx, R, &C, 1)) + return JS_FALSE; + } + } + + /* + * Shrinking realloc can fail (e.g., with a BSD-style allocator), but we + * don't worry about that case here. Worst case, R hangs onto URI_CHUNK-1 + * more jschars than it needs. + */ + chars = (jschar *) JS_realloc(cx, R->chars, (R->length+1) * sizeof(jschar)); + if (chars) + R->chars = chars; + *rval = STRING_TO_JSVAL(R); + return JS_TRUE; + +bad: + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_URI); + return JS_FALSE; +} + +static JSBool +str_decodeURI(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSString *str; + + str = js_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + return Decode(cx, str, js_uriReservedPlusPound_ucstr, rval); +} + +static JSBool +str_decodeURI_Component(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSString *str; + + str = js_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + return Decode(cx, str, js_empty_ucstr, rval); +} + +static JSBool +str_encodeURI(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSString *str; + + str = js_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + return Encode(cx, str, js_uriReservedPlusPound_ucstr, js_uriUnescaped_ucstr, + rval); +} + +static JSBool +str_encodeURI_Component(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSString *str; + + str = js_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + return Encode(cx, str, js_uriUnescaped_ucstr, NULL, rval); +} + +/* + * Convert one UCS-4 char and write it into a UTF-8 buffer, which must be at + * least 6 bytes long. Return the number of UTF-8 bytes of data written. + */ +static int +OneUcs4ToUtf8Char(uint8 *utf8Buffer, uint32 ucs4Char) +{ + int utf8Length = 1; + + JS_ASSERT(ucs4Char <= 0x7FFFFFFF); + if (ucs4Char < 0x80) { + *utf8Buffer = (uint8)ucs4Char; + } else { + int i; + uint32 a = ucs4Char >> 11; + utf8Length = 2; + while (a) { + a >>= 5; + utf8Length++; + } + i = utf8Length; + while (--i) { + utf8Buffer[i] = (uint8)((ucs4Char & 0x3F) | 0x80); + ucs4Char >>= 6; + } + *utf8Buffer = (uint8)(0x100 - (1 << (8-utf8Length)) + ucs4Char); + } + return utf8Length; +} + +/* + * Convert a utf8 character sequence into a UCS-4 character and return that + * character. It is assumed that the caller already checked that the sequence + * is valid. + */ +static uint32 +Utf8ToOneUcs4Char(const uint8 *utf8Buffer, int utf8Length) +{ + uint32 ucs4Char; + uint32 minucs4Char; + /* from Unicode 3.1, non-shortest form is illegal */ + static const uint32 minucs4Table[] = { + 0x00000080, 0x00000800, 0x0001000, 0x0020000, 0x0400000 + }; + + JS_ASSERT(utf8Length >= 1 && utf8Length <= 6); + if (utf8Length == 1) { + ucs4Char = *utf8Buffer; + JS_ASSERT(!(ucs4Char & 0x80)); + } else { + JS_ASSERT((*utf8Buffer & (0x100 - (1 << (7-utf8Length)))) == + (0x100 - (1 << (8-utf8Length)))); + ucs4Char = *utf8Buffer++ & ((1<<(7-utf8Length))-1); + minucs4Char = minucs4Table[utf8Length-2]; + while (--utf8Length) { + JS_ASSERT((*utf8Buffer & 0xC0) == 0x80); + ucs4Char = ucs4Char<<6 | (*utf8Buffer++ & 0x3F); + } + if (ucs4Char < minucs4Char || + ucs4Char == 0xFFFE || ucs4Char == 0xFFFF) { + ucs4Char = 0xFFFD; + } + } + return ucs4Char; +} diff --git a/src/extension/script/js/jsstr.h b/src/extension/script/js/jsstr.h new file mode 100644 index 000000000..94ebe97b9 --- /dev/null +++ b/src/extension/script/js/jsstr.h @@ -0,0 +1,439 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsstr_h___ +#define jsstr_h___ +/* + * JS string type implementation. + * + * A JS string is a counted array of unicode characters. To support handoff + * of API client memory, the chars are allocated separately from the length, + * necessitating a pointer after the count, to form a separately allocated + * string descriptor. String descriptors are GC'ed, while their chars are + * allocated from the malloc heap. + * + * When a string is treated as an object (by following it with . or []), the + * runtime wraps it with a JSObject whose valueOf method returns the unwrapped + * string descriptor. + */ +#include +#include "jspubtd.h" +#include "jsprvtd.h" +#include "jshash.h" + +JS_BEGIN_EXTERN_C + +/* + * The original GC-thing "string" type, a flat character string owned by its + * GC-thing descriptor. The chars member points to a vector having byte size + * (length + 1) * sizeof(jschar), terminated at index length by a zero jschar. + * The terminator is purely a backstop, in case the chars pointer flows out to + * native code that requires \u0000 termination. + * + * NB: Always use the JSSTRING_LENGTH and JSSTRING_CHARS accessor macros, + * unless you guard str->member uses with !JSSTRING_IS_DEPENDENT(str). + */ +struct JSString { + size_t length; + jschar *chars; +}; + +/* + * Overlay structure for a string that depends on another string's characters. + * Distinguished by the JSSTRFLAG_DEPENDENT bit being set in length. The base + * member may point to another dependent string if JSSTRING_CHARS has not been + * called yet. The length chars in a dependent string are stored starting at + * base->chars + start, and are not necessarily zero-terminated. If start is + * 0, it is not stored, length is a full size_t (minus the JSSTRFLAG_* bits in + * the high two positions), and the JSSTRFLAG_PREFIX flag is set. + */ +struct JSDependentString { + size_t length; + JSString *base; +}; + +/* Definitions for flags stored in the high order bits of JSString.length. */ +#define JSSTRFLAG_BITS 2 +#define JSSTRFLAG_SHIFT(flg) ((size_t)(flg) << JSSTRING_LENGTH_BITS) +#define JSSTRFLAG_MASK JSSTRFLAG_SHIFT(JS_BITMASK(JSSTRFLAG_BITS)) +#define JSSTRFLAG_DEPENDENT JSSTRFLAG_SHIFT(1) +#define JSSTRFLAG_PREFIX JSSTRFLAG_SHIFT(2) + +/* Universal JSString type inquiry and accessor macros. */ +#define JSSTRING_BIT(n) ((size_t)1 << (n)) +#define JSSTRING_BITMASK(n) (JSSTRING_BIT(n) - 1) +#define JSSTRING_HAS_FLAG(str,flg) ((str)->length & (flg)) +#define JSSTRING_IS_DEPENDENT(str) JSSTRING_HAS_FLAG(str, JSSTRFLAG_DEPENDENT) +#define JSSTRING_IS_PREFIX(str) JSSTRING_HAS_FLAG(str, JSSTRFLAG_PREFIX) +#define JSSTRING_CHARS(str) (JSSTRING_IS_DEPENDENT(str) \ + ? JSSTRDEP_CHARS(str) \ + : (str)->chars) +#define JSSTRING_LENGTH(str) (JSSTRING_IS_DEPENDENT(str) \ + ? JSSTRDEP_LENGTH(str) \ + : (str)->length) +#define JSSTRING_LENGTH_BITS (sizeof(size_t) * JS_BITS_PER_BYTE \ + - JSSTRFLAG_BITS) +#define JSSTRING_LENGTH_MASK JSSTRING_BITMASK(JSSTRING_LENGTH_BITS) + +/* Specific JSDependentString shift/mask accessor and mutator macros. */ +#define JSSTRDEP_START_BITS (JSSTRING_LENGTH_BITS-JSSTRDEP_LENGTH_BITS) +#define JSSTRDEP_START_SHIFT JSSTRDEP_LENGTH_BITS +#define JSSTRDEP_START_MASK JSSTRING_BITMASK(JSSTRDEP_START_BITS) +#define JSSTRDEP_LENGTH_BITS (JSSTRING_LENGTH_BITS / 2) +#define JSSTRDEP_LENGTH_MASK JSSTRING_BITMASK(JSSTRDEP_LENGTH_BITS) + +#define JSSTRDEP(str) ((JSDependentString *)(str)) +#define JSSTRDEP_START(str) (JSSTRING_IS_PREFIX(str) ? 0 \ + : ((JSSTRDEP(str)->length \ + >> JSSTRDEP_START_SHIFT) \ + & JSSTRDEP_START_MASK)) +#define JSSTRDEP_LENGTH(str) (JSSTRDEP(str)->length \ + & (JSSTRING_IS_PREFIX(str) \ + ? JSSTRING_LENGTH_MASK \ + : JSSTRDEP_LENGTH_MASK)) + +#define JSSTRDEP_SET_START_AND_LENGTH(str,off,len) \ + (JSSTRDEP(str)->length = JSSTRFLAG_DEPENDENT \ + | ((off) << JSSTRDEP_START_SHIFT) \ + | (len)) +#define JSPREFIX_SET_LENGTH(str,len) \ + (JSSTRDEP(str)->length = JSSTRFLAG_DEPENDENT | JSSTRFLAG_PREFIX | (len)) + +#define JSSTRDEP_BASE(str) (JSSTRDEP(str)->base) +#define JSSTRDEP_SET_BASE(str,bstr) (JSSTRDEP(str)->base = (bstr)) +#define JSPREFIX_BASE(str) JSSTRDEP_BASE(str) +#define JSPREFIX_SET_BASE(str,bstr) JSSTRDEP_SET_BASE(str,bstr) + +#define JSSTRDEP_CHARS(str) \ + (JSSTRING_IS_DEPENDENT(JSSTRDEP_BASE(str)) \ + ? js_GetDependentStringChars(str) \ + : JSSTRDEP_BASE(str)->chars + JSSTRDEP_START(str)) + +extern size_t +js_MinimizeDependentStrings(JSString *str, int level, JSString **basep); + +extern jschar * +js_GetDependentStringChars(JSString *str); + +extern jschar * +js_GetStringChars(JSString *str); + +extern JSString * +js_ConcatStrings(JSContext *cx, JSString *left, JSString *right); + +extern const jschar * +js_UndependString(JSContext *cx, JSString *str); + +struct JSSubString { + size_t length; + const jschar *chars; +}; + +extern jschar js_empty_ucstr[]; +extern JSSubString js_EmptySubString; + +/* Unicode character attribute lookup tables. */ +extern const uint8 js_X[]; +extern const uint8 js_Y[]; +extern const uint32 js_A[]; + +/* Enumerated Unicode general category types. */ +typedef enum JSCharType { + JSCT_UNASSIGNED = 0, + JSCT_UPPERCASE_LETTER = 1, + JSCT_LOWERCASE_LETTER = 2, + JSCT_TITLECASE_LETTER = 3, + JSCT_MODIFIER_LETTER = 4, + JSCT_OTHER_LETTER = 5, + JSCT_NON_SPACING_MARK = 6, + JSCT_ENCLOSING_MARK = 7, + JSCT_COMBINING_SPACING_MARK = 8, + JSCT_DECIMAL_DIGIT_NUMBER = 9, + JSCT_LETTER_NUMBER = 10, + JSCT_OTHER_NUMBER = 11, + JSCT_SPACE_SEPARATOR = 12, + JSCT_LINE_SEPARATOR = 13, + JSCT_PARAGRAPH_SEPARATOR = 14, + JSCT_CONTROL = 15, + JSCT_FORMAT = 16, + JSCT_PRIVATE_USE = 18, + JSCT_SURROGATE = 19, + JSCT_DASH_PUNCTUATION = 20, + JSCT_START_PUNCTUATION = 21, + JSCT_END_PUNCTUATION = 22, + JSCT_CONNECTOR_PUNCTUATION = 23, + JSCT_OTHER_PUNCTUATION = 24, + JSCT_MATH_SYMBOL = 25, + JSCT_CURRENCY_SYMBOL = 26, + JSCT_MODIFIER_SYMBOL = 27, + JSCT_OTHER_SYMBOL = 28 +} JSCharType; + +/* Character classifying and mapping macros, based on java.lang.Character. */ +#define JS_CCODE(c) (js_A[js_Y[(js_X[(uint16)(c)>>6]<<6)|((c)&0x3F)]]) +#define JS_CTYPE(c) (JS_CCODE(c) & 0x1F) + +#define JS_ISALPHA(c) ((((1 << JSCT_UPPERCASE_LETTER) | \ + (1 << JSCT_LOWERCASE_LETTER) | \ + (1 << JSCT_TITLECASE_LETTER) | \ + (1 << JSCT_MODIFIER_LETTER) | \ + (1 << JSCT_OTHER_LETTER)) \ + >> JS_CTYPE(c)) & 1) + +#define JS_ISALNUM(c) ((((1 << JSCT_UPPERCASE_LETTER) | \ + (1 << JSCT_LOWERCASE_LETTER) | \ + (1 << JSCT_TITLECASE_LETTER) | \ + (1 << JSCT_MODIFIER_LETTER) | \ + (1 << JSCT_OTHER_LETTER) | \ + (1 << JSCT_DECIMAL_DIGIT_NUMBER)) \ + >> JS_CTYPE(c)) & 1) + +/* A unicode letter, suitable for use in an identifier. */ +#define JS_ISUC_LETTER(c) ((((1 << JSCT_UPPERCASE_LETTER) | \ + (1 << JSCT_LOWERCASE_LETTER) | \ + (1 << JSCT_TITLECASE_LETTER) | \ + (1 << JSCT_MODIFIER_LETTER) | \ + (1 << JSCT_OTHER_LETTER) | \ + (1 << JSCT_LETTER_NUMBER)) \ + >> JS_CTYPE(c)) & 1) + +/* +* 'IdentifierPart' from ECMA grammar, is Unicode letter or +* combining mark or digit or connector punctuation. +*/ +#define JS_ISID_PART(c) ((((1 << JSCT_UPPERCASE_LETTER) | \ + (1 << JSCT_LOWERCASE_LETTER) | \ + (1 << JSCT_TITLECASE_LETTER) | \ + (1 << JSCT_MODIFIER_LETTER) | \ + (1 << JSCT_OTHER_LETTER) | \ + (1 << JSCT_LETTER_NUMBER) | \ + (1 << JSCT_NON_SPACING_MARK) | \ + (1 << JSCT_COMBINING_SPACING_MARK) | \ + (1 << JSCT_DECIMAL_DIGIT_NUMBER) | \ + (1 << JSCT_CONNECTOR_PUNCTUATION)) \ + >> JS_CTYPE(c)) & 1) + +/* Unicode control-format characters, ignored in input */ +#define JS_ISFORMAT(c) (((1 << JSCT_FORMAT) >> JS_CTYPE(c)) & 1) + +#define JS_ISWORD(c) (JS_ISALNUM(c) || (c) == '_') + +/* XXXbe unify on A/X/Y tbls, avoid ctype.h? */ +#define JS_ISIDENT_START(c) (JS_ISUC_LETTER(c) || (c) == '_' || (c) == '$') +#define JS_ISIDENT(c) (JS_ISID_PART(c) || (c) == '_' || (c) == '$') + +#define JS_ISDIGIT(c) (JS_CTYPE(c) == JSCT_DECIMAL_DIGIT_NUMBER) + +/* XXXbe fs, etc. ? */ +#define JS_ISSPACE(c) ((JS_CCODE(c) & 0x00070000) == 0x00040000) +#define JS_ISPRINT(c) ((c) < 128 && isprint(c)) + +#define JS_ISUPPER(c) (JS_CTYPE(c) == JSCT_UPPERCASE_LETTER) +#define JS_ISLOWER(c) (JS_CTYPE(c) == JSCT_LOWERCASE_LETTER) + +#define JS_TOUPPER(c) ((JS_CCODE(c) & 0x00100000) ? (c) - ((int32)JS_CCODE(c) >> 22) : (c)) +#define JS_TOLOWER(c) ((JS_CCODE(c) & 0x00200000) ? (c) + ((int32)JS_CCODE(c) >> 22) : (c)) + +#define JS_TOCTRL(c) ((c) ^ 64) /* XXX unsafe! requires uppercase c */ + +/* Shorthands for ASCII (7-bit) decimal and hex conversion. */ +#define JS7_ISDEC(c) ((c) < 128 && isdigit(c)) +#define JS7_UNDEC(c) ((c) - '0') +#define JS7_ISHEX(c) ((c) < 128 && isxdigit(c)) +#define JS7_UNHEX(c) (uintN)(isdigit(c) ? (c) - '0' : 10 + tolower(c) - 'a') +#define JS7_ISLET(c) ((c) < 128 && isalpha(c)) + +/* Initialize truly global state associated with JS strings. */ +extern JSBool +js_InitStringGlobals(void); + +extern void +js_FreeStringGlobals(void); + +extern void +js_PurgeDeflatedStringCache(JSString *str); + +/* Initialize per-runtime string state for the first context in the runtime. */ +extern JSBool +js_InitRuntimeStringState(JSContext *cx); + +extern void +js_FinishRuntimeStringState(JSContext *cx); + +/* Initialize the String class, returning its prototype object. */ +extern JSObject * +js_InitStringClass(JSContext *cx, JSObject *obj); + +extern const char js_escape_str[]; +extern const char js_unescape_str[]; +extern const char js_uneval_str[]; +extern const char js_decodeURI_str[]; +extern const char js_encodeURI_str[]; +extern const char js_decodeURIComponent_str[]; +extern const char js_encodeURIComponent_str[]; + +/* GC-allocate a string descriptor for the given malloc-allocated chars. */ +extern JSString * +js_NewString(JSContext *cx, jschar *chars, size_t length, uintN gcflag); + +extern JSString * +js_NewDependentString(JSContext *cx, JSString *base, size_t start, + size_t length, uintN gcflag); + +/* Copy a counted string and GC-allocate a descriptor for it. */ +extern JSString * +js_NewStringCopyN(JSContext *cx, const jschar *s, size_t n, uintN gcflag); + +/* Copy a C string and GC-allocate a descriptor for it. */ +extern JSString * +js_NewStringCopyZ(JSContext *cx, const jschar *s, uintN gcflag); + +/* Free the chars held by str when it is finalized by the GC. */ +extern void +js_FinalizeString(JSContext *cx, JSString *str); + +extern void +js_FinalizeStringRT(JSRuntime *rt, JSString *str); + +/* Wrap a string value in a String object. */ +extern JSObject * +js_StringToObject(JSContext *cx, JSString *str); + +/* + * Convert a value to a string, returning null after reporting an error, + * otherwise returning a new string reference. + */ +extern JSString * +js_ValueToString(JSContext *cx, jsval v); + +/* + * Convert a value to its source expression, returning null after reporting + * an error, otherwise returning a new string reference. + */ +extern JSString * +js_ValueToSource(JSContext *cx, jsval v); + +#ifdef HT_ENUMERATE_NEXT /* XXX don't require jshash.h */ +/* + * Compute a hash function from str. + */ +extern JSHashNumber +js_HashString(JSString *str); +#endif + +/* + * Return less than, equal to, or greater than zero depending on whether + * str1 is less than, equal to, or greater than str2. + */ +extern intN +js_CompareStrings(JSString *str1, JSString *str2); + +/* + * Boyer-Moore-Horspool superlinear search for pat:patlen in text:textlen. + * The patlen argument must be positive and no greater than BMH_PATLEN_MAX. + * The start argument tells where in text to begin the search. + * + * Return the index of pat in text, or -1 if not found. + */ +#define BMH_CHARSET_SIZE 256 /* ISO-Latin-1 */ +#define BMH_PATLEN_MAX 255 /* skip table element is uint8 */ + +#define BMH_BAD_PATTERN (-2) /* return value if pat is not ISO-Latin-1 */ + +extern jsint +js_BoyerMooreHorspool(const jschar *text, jsint textlen, + const jschar *pat, jsint patlen, + jsint start); + +extern size_t +js_strlen(const jschar *s); + +extern jschar * +js_strchr(const jschar *s, jschar c); + +extern jschar * +js_strchr_limit(const jschar *s, jschar c, const jschar *limit); + +#define js_strncpy(t, s, n) memcpy((t), (s), (n) * sizeof(jschar)) + +/* + * Return s advanced past any Unicode white space characters. + */ +extern const jschar * +js_SkipWhiteSpace(const jschar *s); + +/* + * Inflate bytes to JS chars and vice versa. Report out of memory via cx + * and return null on error, otherwise return the jschar or byte vector that + * was JS_malloc'ed. + */ +extern jschar * +js_InflateString(JSContext *cx, const char *bytes, size_t length); + +extern char * +js_DeflateString(JSContext *cx, const jschar *chars, size_t length); + +/* + * Inflate bytes to JS chars into a buffer. + * 'chars' must be large enough for 'length'+1 jschars. + */ +extern void +js_InflateStringToBuffer(jschar *chars, const char *bytes, size_t length); + +/* + * Associate bytes with str in the deflated string cache, returning true on + * successful association, false on out of memory. + */ +extern JSBool +js_SetStringBytes(JSString *str, char *bytes, size_t length); + +/* + * Find or create a deflated string cache entry for str that contains its + * characters chopped from Unicode code points into bytes. + */ +extern char * +js_GetStringBytes(JSString *str); + +JSBool +js_str_escape(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval); + +JS_END_EXTERN_C + +#endif /* jsstr_h___ */ diff --git a/src/extension/script/js/jstypes.h b/src/extension/script/js/jstypes.h new file mode 100644 index 000000000..1709c6461 --- /dev/null +++ b/src/extension/script/js/jstypes.h @@ -0,0 +1,388 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * IBM Corp. + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* +** File: jstypes.h +** Description: Definitions of NSPR's basic types +** +** Prototypes and macros used to make up for deficiencies in ANSI environments +** that we have found. +** +** Since we do not wrap and all the other standard headers, authors +** of portable code will not know in general that they need these definitions. +** Instead of requiring these authors to find the dependent uses in their code +** and take the following steps only in those C files, we take steps once here +** for all C files. +**/ + +#ifndef jstypes_h___ +#define jstypes_h___ + +#include + +/*********************************************************************** +** MACROS: JS_EXTERN_API +** JS_EXPORT_API +** DESCRIPTION: +** These are only for externally visible routines and globals. For +** internal routines, just use "extern" for type checking and that +** will not export internal cross-file or forward-declared symbols. +** Define a macro for declaring procedures return types. We use this to +** deal with windoze specific type hackery for DLL definitions. Use +** JS_EXTERN_API when the prototype for the method is declared. Use +** JS_EXPORT_API for the implementation of the method. +** +** Example: +** in dowhim.h +** JS_EXTERN_API( void ) DoWhatIMean( void ); +** in dowhim.c +** JS_EXPORT_API( void ) DoWhatIMean( void ) { return; } +** +** +***********************************************************************/ +#ifdef WIN32 +/* These also work for __MWERKS__ */ +#define JS_EXTERN_API(__type) extern __declspec(dllexport) __type +#define JS_EXPORT_API(__type) __declspec(dllexport) __type +#define JS_EXTERN_DATA(__type) extern __declspec(dllexport) __type +#define JS_EXPORT_DATA(__type) __declspec(dllexport) __type + +#define JS_DLL_CALLBACK +#define JS_STATIC_DLL_CALLBACK(__x) static __x + +#elif defined(WIN16) + +#ifdef _WINDLL +#define JS_EXTERN_API(__type) extern __type _cdecl _export _loadds +#define JS_EXPORT_API(__type) __type _cdecl _export _loadds +#define JS_EXTERN_DATA(__type) extern __type _export +#define JS_EXPORT_DATA(__type) __type _export + +#define JS_DLL_CALLBACK __cdecl __loadds +#define JS_STATIC_DLL_CALLBACK(__x) static __x CALLBACK + +#else /* this must be .EXE */ +#define JS_EXTERN_API(__type) extern __type _cdecl _export +#define JS_EXPORT_API(__type) __type _cdecl _export +#define JS_EXTERN_DATA(__type) extern __type _export +#define JS_EXPORT_DATA(__type) __type _export + +#define JS_DLL_CALLBACK __cdecl __loadds +#define JS_STATIC_DLL_CALLBACK(__x) __x JS_DLL_CALLBACK +#endif /* _WINDLL */ + +#elif defined(XP_MAC) +#define JS_EXTERN_API(__type) extern __declspec(export) __type +#define JS_EXPORT_API(__type) __declspec(export) __type +#define JS_EXTERN_DATA(__type) extern __declspec(export) __type +#define JS_EXPORT_DATA(__type) __declspec(export) __type + +#define JS_DLL_CALLBACK +#define JS_STATIC_DLL_CALLBACK(__x) static __x + +#else /* Unix */ + +#define JS_EXTERN_API(__type) extern __type +#define JS_EXPORT_API(__type) __type +#define JS_EXTERN_DATA(__type) extern __type +#define JS_EXPORT_DATA(__type) __type + +#define JS_DLL_CALLBACK +#define JS_STATIC_DLL_CALLBACK(__x) static __x + +#endif + +#ifdef _WIN32 +# if defined(__MWERKS__) || defined(__GNUC__) +# define JS_IMPORT_API(__x) __x +# else +# define JS_IMPORT_API(__x) __declspec(dllimport) __x +# endif +#else +# define JS_IMPORT_API(__x) JS_EXPORT_API (__x) +#endif + +#if defined(_WIN32) && !defined(__MWERKS__) &&!defined(__GNUC__) +# define JS_IMPORT_DATA(__x) __declspec(dllimport) __x +#else +# define JS_IMPORT_DATA(__x) __x +#endif + +/* + * The linkage of JS API functions differs depending on whether the file is + * used within the JS library or not. Any source file within the JS + * interpreter should define EXPORT_JS_API whereas any client of the library + * should not. + */ +#ifdef EXPORT_JS_API +#define JS_PUBLIC_API(t) JS_EXPORT_API(t) +#define JS_PUBLIC_DATA(t) JS_EXPORT_DATA(t) +#else +#define JS_PUBLIC_API(t) JS_IMPORT_API(t) +#define JS_PUBLIC_DATA(t) JS_IMPORT_DATA(t) +#endif + +#define JS_FRIEND_API(t) JS_PUBLIC_API(t) +#define JS_FRIEND_DATA(t) JS_PUBLIC_DATA(t) + +#ifdef _WIN32 +# define JS_INLINE __inline +#elif defined(__GNUC__) +# define JS_INLINE +#else +# define JS_INLINE +#endif + +/*********************************************************************** +** MACROS: JS_BEGIN_MACRO +** JS_END_MACRO +** DESCRIPTION: +** Macro body brackets so that macros with compound statement definitions +** behave syntactically more like functions when called. +***********************************************************************/ +#define JS_BEGIN_MACRO do { +#define JS_END_MACRO } while (0) + +/*********************************************************************** +** MACROS: JS_BEGIN_EXTERN_C +** JS_END_EXTERN_C +** DESCRIPTION: +** Macro shorthands for conditional C++ extern block delimiters. +***********************************************************************/ +#ifdef __cplusplus +#define JS_BEGIN_EXTERN_C extern "C" { +#define JS_END_EXTERN_C } +#else +#define JS_BEGIN_EXTERN_C +#define JS_END_EXTERN_C +#endif + +/*********************************************************************** +** MACROS: JS_BIT +** JS_BITMASK +** DESCRIPTION: +** Bit masking macros. XXX n must be <= 31 to be portable +***********************************************************************/ +#define JS_BIT(n) ((JSUint32)1 << (n)) +#define JS_BITMASK(n) (JS_BIT(n) - 1) + +/*********************************************************************** +** MACROS: JS_HOWMANY +** JS_ROUNDUP +** JS_MIN +** JS_MAX +** DESCRIPTION: +** Commonly used macros for operations on compatible types. +***********************************************************************/ +#define JS_HOWMANY(x,y) (((x)+(y)-1)/(y)) +#define JS_ROUNDUP(x,y) (JS_HOWMANY(x,y)*(y)) +#define JS_MIN(x,y) ((x)<(y)?(x):(y)) +#define JS_MAX(x,y) ((x)>(y)?(x):(y)) + +#if (defined(XP_MAC) || defined(XP_WIN)) && !defined(CROSS_COMPILE) +# include "jscpucfg.h" /* Use standard Mac or Windows configuration */ +#elif defined(XP_UNIX) || defined(XP_BEOS) || defined(XP_OS2) || defined(CROSS_COMPILE) +# include "jsautocfg.h" /* Use auto-detected configuration */ +# include "jsosdep.h" /* ...and platform-specific flags */ +#else +# error "Must define one of XP_BEOS, XP_MAC, XP_OS2, XP_WIN or XP_UNIX" +#endif + +JS_BEGIN_EXTERN_C + +/************************************************************************ +** TYPES: JSUint8 +** JSInt8 +** DESCRIPTION: +** The int8 types are known to be 8 bits each. There is no type that +** is equivalent to a plain "char". +************************************************************************/ +#if JS_BYTES_PER_BYTE == 1 +typedef unsigned char JSUint8; +typedef signed char JSInt8; +#else +#error No suitable type for JSInt8/JSUint8 +#endif + +/************************************************************************ +** TYPES: JSUint16 +** JSInt16 +** DESCRIPTION: +** The int16 types are known to be 16 bits each. +************************************************************************/ +#if JS_BYTES_PER_SHORT == 2 +typedef unsigned short JSUint16; +typedef short JSInt16; +#else +#error No suitable type for JSInt16/JSUint16 +#endif + +/************************************************************************ +** TYPES: JSUint32 +** JSInt32 +** DESCRIPTION: +** The int32 types are known to be 32 bits each. +************************************************************************/ +#if JS_BYTES_PER_INT == 4 +typedef unsigned int JSUint32; +typedef int JSInt32; +#define JS_INT32(x) x +#define JS_UINT32(x) x ## U +#elif JS_BYTES_PER_LONG == 4 +typedef unsigned long JSUint32; +typedef long JSInt32; +#define JS_INT32(x) x ## L +#define JS_UINT32(x) x ## UL +#else +#error No suitable type for JSInt32/JSUint32 +#endif + +/************************************************************************ +** TYPES: JSUint64 +** JSInt64 +** DESCRIPTION: +** The int64 types are known to be 64 bits each. Care must be used when +** declaring variables of type JSUint64 or JSInt64. Different hardware +** architectures and even different compilers have varying support for +** 64 bit values. The only guaranteed portability requires the use of +** the JSLL_ macros (see jslong.h). +************************************************************************/ +#ifdef JS_HAVE_LONG_LONG +#if JS_BYTES_PER_LONG == 8 +typedef long JSInt64; +typedef unsigned long JSUint64; +#elif defined(WIN16) +typedef __int64 JSInt64; +typedef unsigned __int64 JSUint64; +#elif defined(WIN32) && !defined(__GNUC__) +typedef __int64 JSInt64; +typedef unsigned __int64 JSUint64; +#else +typedef long long JSInt64; +typedef unsigned long long JSUint64; +#endif /* JS_BYTES_PER_LONG == 8 */ +#else /* !JS_HAVE_LONG_LONG */ +typedef struct { +#ifdef IS_LITTLE_ENDIAN + JSUint32 lo, hi; +#else + JSUint32 hi, lo; +#endif +} JSInt64; +typedef JSInt64 JSUint64; +#endif /* !JS_HAVE_LONG_LONG */ + +/************************************************************************ +** TYPES: JSUintn +** JSIntn +** DESCRIPTION: +** The JSIntn types are most appropriate for automatic variables. They are +** guaranteed to be at least 16 bits, though various architectures may +** define them to be wider (e.g., 32 or even 64 bits). These types are +** never valid for fields of a structure. +************************************************************************/ +#if JS_BYTES_PER_INT >= 2 +typedef int JSIntn; +typedef unsigned int JSUintn; +#else +#error 'sizeof(int)' not sufficient for platform use +#endif + +/************************************************************************ +** TYPES: JSFloat64 +** DESCRIPTION: +** NSPR's floating point type is always 64 bits. +************************************************************************/ +typedef double JSFloat64; + +/************************************************************************ +** TYPES: JSSize +** DESCRIPTION: +** A type for representing the size of objects. +************************************************************************/ +typedef size_t JSSize; + +/************************************************************************ +** TYPES: JSPtrDiff +** DESCRIPTION: +** A type for pointer difference. Variables of this type are suitable +** for storing a pointer or pointer sutraction. +************************************************************************/ +typedef ptrdiff_t JSPtrdiff; + +/************************************************************************ +** TYPES: JSUptrdiff +** DESCRIPTION: +** A type for pointer difference. Variables of this type are suitable +** for storing a pointer or pointer sutraction. +************************************************************************/ +typedef unsigned long JSUptrdiff; + +/************************************************************************ +** TYPES: JSBool +** DESCRIPTION: +** Use JSBool for variables and parameter types. Use JS_FALSE and JS_TRUE +** for clarity of target type in assignments and actual arguments. Use +** 'if (bool)', 'while (!bool)', '(bool) ? x : y' etc., to test booleans +** just as you would C int-valued conditions. +************************************************************************/ +typedef JSIntn JSBool; +#define JS_TRUE (JSIntn)1 +#define JS_FALSE (JSIntn)0 + +/************************************************************************ +** TYPES: JSPackedBool +** DESCRIPTION: +** Use JSPackedBool within structs where bitfields are not desireable +** but minimum and consistant overhead matters. +************************************************************************/ +typedef JSUint8 JSPackedBool; + +/* +** A JSWord is an integer that is the same size as a void* +*/ +typedef long JSWord; +typedef unsigned long JSUword; + +#include "jsotypes.h" + +JS_END_EXTERN_C + +#endif /* jstypes_h___ */ + diff --git a/src/extension/script/js/jsutil.c b/src/extension/script/js/jsutil.c new file mode 100644 index 000000000..f64718ad4 --- /dev/null +++ b/src/extension/script/js/jsutil.c @@ -0,0 +1,157 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * IBM Corp. + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * PR assertion checker. + */ +#include "jsstddef.h" +#include +#include +#include "jstypes.h" +#include "jsutil.h" + +#ifdef WIN32 +# include +#endif + +#ifdef XP_MAC +# include +# include +# include "jsprf.h" +#endif + +#ifdef XP_MAC +/* + * PStrFromCStr converts the source C string to a destination + * pascal string as it copies. The dest string will + * be truncated to fit into an Str255 if necessary. + * If the C String pointer is NULL, the pascal string's length is + * set to zero. + */ +static void PStrFromCStr(const char* src, Str255 dst) +{ + short length = 0; + + /* handle case of overlapping strings */ + if ( (void*)src == (void*)dst ) + { + unsigned char* curdst = &dst[1]; + unsigned char thisChar; + + thisChar = *(const unsigned char*)src++; + while ( thisChar != '\0' ) + { + unsigned char nextChar; + + /* + * Use nextChar so we don't overwrite what we + * are about to read + */ + nextChar = *(const unsigned char*)src++; + *curdst++ = thisChar; + thisChar = nextChar; + + if ( ++length >= 255 ) + break; + } + } + else if ( src != NULL ) + { + unsigned char* curdst = &dst[1]; + /* count down so test it loop is faster */ + short overflow = 255; + register char temp; + + /* + * Can't do the K&R C thing of while (*s++ = *t++) + * because it will copy trailing zero which might + * overrun pascal buffer. Instead we use a temp variable. + */ + while ( (temp = *src++) != 0 ) + { + *(char*)curdst++ = temp; + + if ( --overflow <= 0 ) + break; + } + length = 255 - overflow; + } + dst[0] = length; +} + +static void jsdebugstr(const char *debuggerMsg) +{ + Str255 pStr; + + PStrFromCStr(debuggerMsg, pStr); + DebugStr(pStr); +} + +static void dprintf(const char *format, ...) +{ + va_list ap; + char *buffer; + + va_start(ap, format); + buffer = (char *)JS_vsmprintf(format, ap); + va_end(ap); + + jsdebugstr(buffer); + JS_DELETE(buffer); +} +#endif /* XP_MAC */ + +JS_PUBLIC_API(void) JS_Assert(const char *s, const char *file, JSIntn ln) +{ +#ifdef XP_MAC + dprintf("Assertion failure: %s, at %s:%d\n", s, file, ln); +#else + fprintf(stderr, "Assertion failure: %s, at %s:%d\n", s, file, ln); +#endif +#if defined(WIN32) + DebugBreak(); +#endif +#if defined(XP_OS2) + asm("int $3"); +#endif +#ifndef XP_MAC + abort(); +#endif +} diff --git a/src/extension/script/js/jsutil.h b/src/extension/script/js/jsutil.h new file mode 100644 index 000000000..d4edb919c --- /dev/null +++ b/src/extension/script/js/jsutil.h @@ -0,0 +1,106 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * PR assertion checker. + */ + +#ifndef jsutil_h___ +#define jsutil_h___ + +JS_BEGIN_EXTERN_C + +/*********************************************************************** +** FUNCTION: JS_MALLOC() +** DESCRIPTION: +** JS_NEW() allocates an untyped item of size _size from the heap. +** INPUTS: _size: size in bytes of item to be allocated +** OUTPUTS: untyped pointer to the node allocated +** RETURN: pointer to node or error returned from malloc(). +***********************************************************************/ +#define JS_MALLOC(_bytes) (malloc((_bytes))) + +/*********************************************************************** +** FUNCTION: JS_DELETE() +** DESCRIPTION: +** JS_DELETE() unallocates an object previosly allocated via JS_NEW() +** or JS_NEWZAP() to the heap. +** INPUTS: pointer to previously allocated object +** OUTPUTS: the referenced object is returned to the heap +** RETURN: void +***********************************************************************/ +#define JS_DELETE(_ptr) { free(_ptr); (_ptr) = NULL; } + +/*********************************************************************** +** FUNCTION: JS_NEW() +** DESCRIPTION: +** JS_NEW() allocates an item of type _struct from the heap. +** INPUTS: _struct: a data type +** OUTPUTS: pointer to _struct +** RETURN: pointer to _struct or error returns from malloc(). +***********************************************************************/ +#define JS_NEW(_struct) ((_struct *) JS_MALLOC(sizeof(_struct))) + +#ifdef DEBUG + +extern JS_PUBLIC_API(void) +JS_Assert(const char *s, const char *file, JSIntn ln); +#define JS_ASSERT(_expr) \ + ((_expr)?((void)0):JS_Assert(# _expr,__FILE__,__LINE__)) + +#define JS_NOT_REACHED(_reasonStr) \ + JS_Assert(_reasonStr,__FILE__,__LINE__) + +#else + +#define JS_ASSERT(expr) ((void) 0) +#define JS_NOT_REACHED(reasonStr) + +#endif /* defined(DEBUG) */ + +/* +** Abort the process in a non-graceful manner. This will cause a core file, +** call to the debugger or other moral equivalent as well as causing the +** entire process to stop. +*/ +extern JS_PUBLIC_API(void) JS_Abort(void); + +JS_END_EXTERN_C + +#endif /* jsutil_h___ */ diff --git a/src/extension/script/js/jsxdrapi.c b/src/extension/script/js/jsxdrapi.c new file mode 100644 index 000000000..1b092924d --- /dev/null +++ b/src/extension/script/js/jsxdrapi.c @@ -0,0 +1,690 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +#include "jsstddef.h" +#include "jsconfig.h" + +#if JS_HAS_XDR + +#include +#include "jstypes.h" +#include "jsutil.h" /* Added by JSIFY */ +#include "jsdhash.h" +#include "jsprf.h" +#include "jsapi.h" +#include "jscntxt.h" +#include "jsobj.h" /* js_XDRObject */ +#include "jsscript.h" /* js_XDRScript */ +#include "jsstr.h" +#include "jsxdrapi.h" + +#ifdef DEBUG +#define DBG(x) x +#else +#define DBG(x) ((void)0) +#endif + +typedef struct JSXDRMemState { + JSXDRState state; + char *base; + uint32 count; + uint32 limit; +} JSXDRMemState; + +#define MEM_BLOCK 8192 +#define MEM_PRIV(xdr) ((JSXDRMemState *)(xdr)) + +#define MEM_BASE(xdr) (MEM_PRIV(xdr)->base) +#define MEM_COUNT(xdr) (MEM_PRIV(xdr)->count) +#define MEM_LIMIT(xdr) (MEM_PRIV(xdr)->limit) + +#define MEM_LEFT(xdr, bytes) \ + JS_BEGIN_MACRO \ + if ((xdr)->mode == JSXDR_DECODE && \ + MEM_COUNT(xdr) + bytes > MEM_LIMIT(xdr)) { \ + JS_ReportErrorNumber((xdr)->cx, js_GetErrorMessage, NULL, \ + JSMSG_END_OF_DATA); \ + return 0; \ + } \ + JS_END_MACRO + +#define MEM_NEED(xdr, bytes) \ + JS_BEGIN_MACRO \ + if ((xdr)->mode == JSXDR_ENCODE) { \ + if (MEM_LIMIT(xdr) && \ + MEM_COUNT(xdr) + bytes > MEM_LIMIT(xdr)) { \ + uint32 limit_ = JS_ROUNDUP(MEM_COUNT(xdr) + bytes, MEM_BLOCK);\ + void *data_ = JS_realloc((xdr)->cx, MEM_BASE(xdr), limit_); \ + if (!data_) \ + return 0; \ + MEM_BASE(xdr) = data_; \ + MEM_LIMIT(xdr) = limit_; \ + } \ + } else { \ + MEM_LEFT(xdr, bytes); \ + } \ + JS_END_MACRO + +#define MEM_DATA(xdr) ((void *)(MEM_BASE(xdr) + MEM_COUNT(xdr))) +#define MEM_INCR(xdr,bytes) (MEM_COUNT(xdr) += (bytes)) + +static JSBool +mem_get32(JSXDRState *xdr, uint32 *lp) +{ + MEM_LEFT(xdr, 4); + *lp = *(uint32 *)MEM_DATA(xdr); + MEM_INCR(xdr, 4); + return JS_TRUE; +} + +static JSBool +mem_set32(JSXDRState *xdr, uint32 *lp) +{ + MEM_NEED(xdr, 4); + *(uint32 *)MEM_DATA(xdr) = *lp; + MEM_INCR(xdr, 4); + return JS_TRUE; +} + +static JSBool +mem_getbytes(JSXDRState *xdr, char *bytes, uint32 len) +{ + MEM_LEFT(xdr, len); + memcpy(bytes, MEM_DATA(xdr), len); + MEM_INCR(xdr, len); + return JS_TRUE; +} + +static JSBool +mem_setbytes(JSXDRState *xdr, char *bytes, uint32 len) +{ + MEM_NEED(xdr, len); + memcpy(MEM_DATA(xdr), bytes, len); + MEM_INCR(xdr, len); + return JS_TRUE; +} + +static void * +mem_raw(JSXDRState *xdr, uint32 len) +{ + void *data; + if (xdr->mode == JSXDR_ENCODE) { + MEM_NEED(xdr, len); + } else if (xdr->mode == JSXDR_DECODE) { + MEM_LEFT(xdr, len); + } + data = MEM_DATA(xdr); + MEM_INCR(xdr, len); + return data; +} + +static JSBool +mem_seek(JSXDRState *xdr, int32 offset, JSXDRWhence whence) +{ + switch (whence) { + case JSXDR_SEEK_CUR: + if ((int32)MEM_COUNT(xdr) + offset < 0) { + JS_ReportErrorNumber(xdr->cx, js_GetErrorMessage, NULL, + JSMSG_SEEK_BEYOND_START); + return JS_FALSE; + } + if (offset > 0) + MEM_NEED(xdr, offset); + MEM_COUNT(xdr) += offset; + return JS_TRUE; + case JSXDR_SEEK_SET: + if (offset < 0) { + JS_ReportErrorNumber(xdr->cx, js_GetErrorMessage, NULL, + JSMSG_SEEK_BEYOND_START); + return JS_FALSE; + } + if (xdr->mode == JSXDR_ENCODE) { + if ((uint32)offset > MEM_COUNT(xdr)) + MEM_NEED(xdr, offset - MEM_COUNT(xdr)); + MEM_COUNT(xdr) = offset; + } else { + if ((uint32)offset > MEM_LIMIT(xdr)) { + JS_ReportErrorNumber(xdr->cx, js_GetErrorMessage, NULL, + JSMSG_SEEK_BEYOND_END); + return JS_FALSE; + } + MEM_COUNT(xdr) = offset; + } + return JS_TRUE; + case JSXDR_SEEK_END: + if (offset >= 0 || + xdr->mode == JSXDR_ENCODE || + (int32)MEM_LIMIT(xdr) + offset < 0) { + JS_ReportErrorNumber(xdr->cx, js_GetErrorMessage, NULL, + JSMSG_END_SEEK); + return JS_FALSE; + } + MEM_COUNT(xdr) = MEM_LIMIT(xdr) + offset; + return JS_TRUE; + default: { + char numBuf[12]; + JS_snprintf(numBuf, sizeof numBuf, "%d", whence); + JS_ReportErrorNumber(xdr->cx, js_GetErrorMessage, NULL, + JSMSG_WHITHER_WHENCE, numBuf); + return JS_FALSE; + } + } +} + +static uint32 +mem_tell(JSXDRState *xdr) +{ + return MEM_COUNT(xdr); +} + +static void +mem_finalize(JSXDRState *xdr) +{ + JS_free(xdr->cx, MEM_BASE(xdr)); +} + +static JSXDROps xdrmem_ops = { + mem_get32, mem_set32, mem_getbytes, mem_setbytes, + mem_raw, mem_seek, mem_tell, mem_finalize +}; + +JS_PUBLIC_API(void) +JS_XDRInitBase(JSXDRState *xdr, JSXDRMode mode, JSContext *cx) +{ + xdr->mode = mode; + xdr->cx = cx; + xdr->registry = NULL; + xdr->numclasses = xdr->maxclasses = 0; + xdr->reghash = NULL; + xdr->userdata = NULL; +} + +JS_PUBLIC_API(JSXDRState *) +JS_XDRNewMem(JSContext *cx, JSXDRMode mode) +{ + JSXDRState *xdr = (JSXDRState *) JS_malloc(cx, sizeof(JSXDRMemState)); + if (!xdr) + return NULL; + JS_XDRInitBase(xdr, mode, cx); + if (mode == JSXDR_ENCODE) { + if (!(MEM_BASE(xdr) = JS_malloc(cx, MEM_BLOCK))) { + JS_free(cx, xdr); + return NULL; + } + } else { + /* XXXbe ok, so better not deref MEM_BASE(xdr) if not ENCODE */ + MEM_BASE(xdr) = NULL; + } + xdr->ops = &xdrmem_ops; + MEM_COUNT(xdr) = 0; + MEM_LIMIT(xdr) = MEM_BLOCK; + return xdr; +} + +JS_PUBLIC_API(void *) +JS_XDRMemGetData(JSXDRState *xdr, uint32 *lp) +{ + if (xdr->ops != &xdrmem_ops) + return NULL; + *lp = MEM_COUNT(xdr); + return MEM_BASE(xdr); +} + +JS_PUBLIC_API(void) +JS_XDRMemSetData(JSXDRState *xdr, void *data, uint32 len) +{ + if (xdr->ops != &xdrmem_ops) + return; + MEM_LIMIT(xdr) = len; + MEM_BASE(xdr) = data; + MEM_COUNT(xdr) = 0; +} + +JS_PUBLIC_API(uint32) +JS_XDRMemDataLeft(JSXDRState *xdr) +{ + if (xdr->ops != &xdrmem_ops) + return 0; + return MEM_LIMIT(xdr) - MEM_COUNT(xdr); +} + +JS_PUBLIC_API(void) +JS_XDRMemResetData(JSXDRState *xdr) +{ + if (xdr->ops != &xdrmem_ops) + return; + MEM_COUNT(xdr) = 0; +} + +JS_PUBLIC_API(void) +JS_XDRDestroy(JSXDRState *xdr) +{ + JSContext *cx = xdr->cx; + xdr->ops->finalize(xdr); + if (xdr->registry) { + JS_free(cx, xdr->registry); + if (xdr->reghash) + JS_DHashTableDestroy(xdr->reghash); + } + JS_free(cx, xdr); +} + +JS_PUBLIC_API(JSBool) +JS_XDRUint8(JSXDRState *xdr, uint8 *b) +{ + uint32 l = *b; + if (!JS_XDRUint32(xdr, &l)) + return JS_FALSE; + *b = (uint8) l; + return JS_TRUE; +} + +JS_PUBLIC_API(JSBool) +JS_XDRUint16(JSXDRState *xdr, uint16 *s) +{ + uint32 l = *s; + if (!JS_XDRUint32(xdr, &l)) + return JS_FALSE; + *s = (uint16) l; + return JS_TRUE; +} + +JS_PUBLIC_API(JSBool) +JS_XDRUint32(JSXDRState *xdr, uint32 *lp) +{ + JSBool ok = JS_TRUE; + if (xdr->mode == JSXDR_ENCODE) { + uint32 xl = JSXDR_SWAB32(*lp); + ok = xdr->ops->set32(xdr, &xl); + } else if (xdr->mode == JSXDR_DECODE) { + ok = xdr->ops->get32(xdr, lp); + *lp = JSXDR_SWAB32(*lp); + } + return ok; +} + +JS_PUBLIC_API(JSBool) +JS_XDRBytes(JSXDRState *xdr, char *bytes, uint32 len) +{ + uint32 padlen; + static char padbuf[JSXDR_ALIGN-1]; + + if (xdr->mode == JSXDR_ENCODE) { + if (!xdr->ops->setbytes(xdr, bytes, len)) + return JS_FALSE; + } else { + if (!xdr->ops->getbytes(xdr, bytes, len)) + return JS_FALSE; + } + len = xdr->ops->tell(xdr); + if (len % JSXDR_ALIGN) { + padlen = JSXDR_ALIGN - (len % JSXDR_ALIGN); + if (xdr->mode == JSXDR_ENCODE) { + if (!xdr->ops->setbytes(xdr, padbuf, padlen)) + return JS_FALSE; + } else { + if (!xdr->ops->seek(xdr, padlen, JSXDR_SEEK_CUR)) + return JS_FALSE; + } + } + return JS_TRUE; +} + +/** + * Convert between a C string and the XDR representation: + * leading 32-bit count, then counted vector of chars, + * then possibly \0 padding to multiple of 4. + */ +JS_PUBLIC_API(JSBool) +JS_XDRCString(JSXDRState *xdr, char **sp) +{ + uint32 len; + + if (xdr->mode == JSXDR_ENCODE) + len = strlen(*sp); + JS_XDRUint32(xdr, &len); + if (xdr->mode == JSXDR_DECODE) { + if (!(*sp = (char *) JS_malloc(xdr->cx, len + 1))) + return JS_FALSE; + } + if (!JS_XDRBytes(xdr, *sp, len)) { + if (xdr->mode == JSXDR_DECODE) + JS_free(xdr->cx, *sp); + return JS_FALSE; + } + if (xdr->mode == JSXDR_DECODE) { + (*sp)[len] = '\0'; + } else if (xdr->mode == JSXDR_FREE) { + JS_free(xdr->cx, *sp); + *sp = NULL; + } + return JS_TRUE; +} + +JS_PUBLIC_API(JSBool) +JS_XDRCStringOrNull(JSXDRState *xdr, char **sp) +{ + uint32 null = (*sp == NULL); + if (!JS_XDRUint32(xdr, &null)) + return JS_FALSE; + if (null) { + *sp = NULL; + return JS_TRUE; + } + return JS_XDRCString(xdr, sp); +} + +/* + * Convert between a JS (Unicode) string and the XDR representation. + */ +JS_PUBLIC_API(JSBool) +JS_XDRString(JSXDRState *xdr, JSString **strp) +{ + uint32 i, len, padlen, nbytes; + jschar *chars = NULL, *raw; + + if (xdr->mode == JSXDR_ENCODE) + len = JSSTRING_LENGTH(*strp); + if (!JS_XDRUint32(xdr, &len)) + return JS_FALSE; + nbytes = len * sizeof(jschar); + + if (xdr->mode == JSXDR_DECODE) { + if (!(chars = (jschar *) JS_malloc(xdr->cx, nbytes + sizeof(jschar)))) + return JS_FALSE; + } else { + chars = JSSTRING_CHARS(*strp); + } + + padlen = nbytes % JSXDR_ALIGN; + if (padlen) { + padlen = JSXDR_ALIGN - padlen; + nbytes += padlen; + } + if (!(raw = (jschar *) xdr->ops->raw(xdr, nbytes))) + goto bad; + if (xdr->mode == JSXDR_ENCODE) { + for (i = 0; i < len; i++) + raw[i] = JSXDR_SWAB16(chars[i]); + if (padlen) + memset((char *)raw + nbytes - padlen, 0, padlen); + } else if (xdr->mode == JSXDR_DECODE) { + for (i = 0; i < len; i++) + chars[i] = JSXDR_SWAB16(raw[i]); + chars[len] = 0; + + if (!(*strp = JS_NewUCString(xdr->cx, chars, len))) + goto bad; + } + return JS_TRUE; + +bad: + if (xdr->mode == JSXDR_DECODE) + JS_free(xdr->cx, chars); + return JS_FALSE; +} + +JS_PUBLIC_API(JSBool) +JS_XDRStringOrNull(JSXDRState *xdr, JSString **strp) +{ + uint32 null = (*strp == NULL); + if (!JS_XDRUint32(xdr, &null)) + return JS_FALSE; + if (null) { + *strp = NULL; + return JS_TRUE; + } + return JS_XDRString(xdr, strp); +} + +JS_PUBLIC_API(JSBool) +JS_XDRDouble(JSXDRState *xdr, jsdouble **dp) +{ + jsdouble d; + if (xdr->mode == JSXDR_ENCODE) + d = **dp; +#if IS_BIG_ENDIAN + if (!JS_XDRUint32(xdr, (uint32 *)&d + 1) || + !JS_XDRUint32(xdr, (uint32 *)&d)) +#else + if (!JS_XDRUint32(xdr, (uint32 *)&d) || + !JS_XDRUint32(xdr, (uint32 *)&d + 1)) +#endif + return JS_FALSE; + if (xdr->mode == JSXDR_DECODE) { + *dp = JS_NewDouble(xdr->cx, d); + if (!*dp) + return JS_FALSE; + } + return JS_TRUE; +} + +/* These are magic pseudo-tags: see jsapi.h, near the top, for real tags. */ +#define JSVAL_XDRNULL 0x8 +#define JSVAL_XDRVOID 0xA + +JS_PUBLIC_API(JSBool) +JS_XDRValue(JSXDRState *xdr, jsval *vp) +{ + uint32 type; + + if (xdr->mode == JSXDR_ENCODE) { + if (JSVAL_IS_NULL(*vp)) + type = JSVAL_XDRNULL; + else if (JSVAL_IS_VOID(*vp)) + type = JSVAL_XDRVOID; + else + type = JSVAL_TAG(*vp); + } + if (!JS_XDRUint32(xdr, &type)) + return JS_FALSE; + + switch (type) { + case JSVAL_XDRNULL: + *vp = JSVAL_NULL; + break; + case JSVAL_XDRVOID: + *vp = JSVAL_VOID; + break; + case JSVAL_STRING: { + JSString *str; + if (xdr->mode == JSXDR_ENCODE) + str = JSVAL_TO_STRING(*vp); + if (!JS_XDRString(xdr, &str)) + return JS_FALSE; + if (xdr->mode == JSXDR_DECODE) + *vp = STRING_TO_JSVAL(str); + break; + } + case JSVAL_DOUBLE: { + jsdouble *dp; + if (xdr->mode == JSXDR_ENCODE) + dp = JSVAL_TO_DOUBLE(*vp); + if (!JS_XDRDouble(xdr, &dp)) + return JS_FALSE; + if (xdr->mode == JSXDR_DECODE) + *vp = DOUBLE_TO_JSVAL(dp); + break; + } + case JSVAL_OBJECT: { + JSObject *obj; + if (xdr->mode == JSXDR_ENCODE) + obj = JSVAL_TO_OBJECT(*vp); + if (!js_XDRObject(xdr, &obj)) + return JS_FALSE; + if (xdr->mode == JSXDR_DECODE) + *vp = OBJECT_TO_JSVAL(obj); + break; + } + case JSVAL_BOOLEAN: { + uint32 b; + if (xdr->mode == JSXDR_ENCODE) + b = (uint32) JSVAL_TO_BOOLEAN(*vp); + if (!JS_XDRUint32(xdr, &b)) + return JS_FALSE; + if (xdr->mode == JSXDR_DECODE) + *vp = BOOLEAN_TO_JSVAL((JSBool) b); + break; + } + default: { + uint32 i; + + JS_ASSERT(type & JSVAL_INT); + if (xdr->mode == JSXDR_ENCODE) + i = (uint32) JSVAL_TO_INT(*vp); + if (!JS_XDRUint32(xdr, &i)) + return JS_FALSE; + if (xdr->mode == JSXDR_DECODE) + *vp = INT_TO_JSVAL((int32) i); + break; + } + } + return JS_TRUE; +} + +JS_PUBLIC_API(JSBool) +JS_XDRScript(JSXDRState *xdr, JSScript **scriptp) +{ + if (!js_XDRScript(xdr, scriptp, NULL)) + return JS_FALSE; + if (xdr->mode == JSXDR_DECODE) + js_CallNewScriptHook(xdr->cx, *scriptp, NULL); + return JS_TRUE; +} + +#define CLASS_REGISTRY_MIN 8 +#define CLASS_INDEX_TO_ID(i) ((i)+1) +#define CLASS_ID_TO_INDEX(id) ((id)-1) + +typedef struct JSRegHashEntry { + JSDHashEntryHdr hdr; + const char *name; + uint32 index; +} JSRegHashEntry; + +JS_PUBLIC_API(JSBool) +JS_XDRRegisterClass(JSXDRState *xdr, JSClass *clasp, uint32 *idp) +{ + uintN numclasses, maxclasses; + JSClass **registry; + + numclasses = xdr->numclasses; + maxclasses = xdr->maxclasses; + if (numclasses == maxclasses) { + maxclasses = (maxclasses == 0) ? CLASS_REGISTRY_MIN : maxclasses << 1; + registry = (JSClass **) + JS_realloc(xdr->cx, xdr->registry, maxclasses * sizeof(JSClass *)); + if (!registry) + return JS_FALSE; + xdr->registry = registry; + xdr->maxclasses = maxclasses; + } else { + JS_ASSERT(numclasses && numclasses < maxclasses); + registry = xdr->registry; + } + + registry[numclasses] = clasp; + if (xdr->reghash) { + JSRegHashEntry *entry = (JSRegHashEntry *) + JS_DHashTableOperate(xdr->reghash, clasp->name, JS_DHASH_ADD); + if (!entry) { + JS_ReportOutOfMemory(xdr->cx); + return JS_FALSE; + } + entry->name = clasp->name; + entry->index = numclasses; + } + *idp = CLASS_INDEX_TO_ID(numclasses); + xdr->numclasses = ++numclasses; + return JS_TRUE; +} + +JS_PUBLIC_API(uint32) +JS_XDRFindClassIdByName(JSXDRState *xdr, const char *name) +{ + uintN i, numclasses; + + numclasses = xdr->numclasses; + if (numclasses >= 10) { + JSRegHashEntry *entry; + + /* Bootstrap reghash from registry on first overpopulated Find. */ + if (!xdr->reghash) { + xdr->reghash = JS_NewDHashTable(JS_DHashGetStubOps(), NULL, + sizeof(JSRegHashEntry), + numclasses); + if (xdr->reghash) { + for (i = 0; i < numclasses; i++) { + JSClass *clasp = xdr->registry[i]; + entry = (JSRegHashEntry *) + JS_DHashTableOperate(xdr->reghash, clasp->name, + JS_DHASH_ADD); + entry->name = clasp->name; + entry->index = i; + } + } + } + + /* If we managed to create reghash, use it for O(1) Find. */ + if (xdr->reghash) { + entry = (JSRegHashEntry *) + JS_DHashTableOperate(xdr->reghash, name, JS_DHASH_LOOKUP); + if (JS_DHASH_ENTRY_IS_BUSY(&entry->hdr)) + return CLASS_INDEX_TO_ID(entry->index); + } + } + + /* Only a few classes, or we couldn't malloc reghash: use linear search. */ + for (i = 0; i < numclasses; i++) { + if (!strcmp(name, xdr->registry[i]->name)) + return CLASS_INDEX_TO_ID(i); + } + return 0; +} + +JS_PUBLIC_API(JSClass *) +JS_XDRFindClassById(JSXDRState *xdr, uint32 id) +{ + uintN i = CLASS_ID_TO_INDEX(id); + + if (i >= xdr->numclasses) + return NULL; + return xdr->registry[i]; +} + +#endif /* JS_HAS_XDR */ diff --git a/src/extension/script/js/jsxdrapi.h b/src/extension/script/js/jsxdrapi.h new file mode 100644 index 000000000..874a62eeb --- /dev/null +++ b/src/extension/script/js/jsxdrapi.h @@ -0,0 +1,193 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsxdrapi_h___ +#define jsxdrapi_h___ + +/* + * JS external data representation interface API. + * + * The XDR system is comprised of three major parts: + * + * - the state serialization/deserialization APIs, which allow consumers + * of the API to serialize JS runtime state (script bytecodes, atom maps, + * object graphs, etc.) for later restoration. These portions + * are implemented in various appropriate files, such as jsscript.c + * for the script portions and jsobj.c for object state. + * - the callback APIs through which the runtime requests an opaque + * representation of a native object, and through which the runtime + * constructs a live native object from an opaque representation. These + * portions are the responsibility of the native object implementor. + * - utility functions for en/decoding of primitive types, such as + * JSStrings. This portion is implemented in jsxdrapi.c. + * + * Spiritually guided by Sun's XDR, where appropriate. + */ + +#include "jspubtd.h" +#include "jsprvtd.h" + +JS_BEGIN_EXTERN_C + +/* We use little-endian byteorder for all encoded data */ + +#if defined IS_LITTLE_ENDIAN +#define JSXDR_SWAB32(x) x +#define JSXDR_SWAB16(x) x +#elif defined IS_BIG_ENDIAN +#define JSXDR_SWAB32(x) (((uint32)(x) >> 24) | \ + (((uint32)(x) >> 8) & 0xff00) | \ + (((uint32)(x) << 8) & 0xff0000) | \ + ((uint32)(x) << 24)) +#define JSXDR_SWAB16(x) (((uint16)(x) >> 8) | ((uint16)(x) << 8)) +#else +#error "unknown byte order" +#endif + +#define JSXDR_ALIGN 4 + +typedef enum JSXDRMode { + JSXDR_ENCODE, + JSXDR_DECODE, + JSXDR_FREE +} JSXDRMode; + +typedef enum JSXDRWhence { + JSXDR_SEEK_SET, + JSXDR_SEEK_CUR, + JSXDR_SEEK_END +} JSXDRWhence; + +typedef struct JSXDROps { + JSBool (*get32)(JSXDRState *, uint32 *); + JSBool (*set32)(JSXDRState *, uint32 *); + JSBool (*getbytes)(JSXDRState *, char *, uint32); + JSBool (*setbytes)(JSXDRState *, char *, uint32); + void * (*raw)(JSXDRState *, uint32); + JSBool (*seek)(JSXDRState *, int32, JSXDRWhence); + uint32 (*tell)(JSXDRState *); + void (*finalize)(JSXDRState *); +} JSXDROps; + +struct JSXDRState { + JSXDRMode mode; + JSXDROps *ops; + JSContext *cx; + JSClass **registry; + uintN numclasses; + uintN maxclasses; + void *reghash; + void *userdata; +}; + +extern JS_PUBLIC_API(void) +JS_XDRInitBase(JSXDRState *xdr, JSXDRMode mode, JSContext *cx); + +extern JS_PUBLIC_API(JSXDRState *) +JS_XDRNewMem(JSContext *cx, JSXDRMode mode); + +extern JS_PUBLIC_API(void *) +JS_XDRMemGetData(JSXDRState *xdr, uint32 *lp); + +extern JS_PUBLIC_API(void) +JS_XDRMemSetData(JSXDRState *xdr, void *data, uint32 len); + +extern JS_PUBLIC_API(uint32) +JS_XDRMemDataLeft(JSXDRState *xdr); + +extern JS_PUBLIC_API(void) +JS_XDRMemResetData(JSXDRState *xdr); + +extern JS_PUBLIC_API(void) +JS_XDRDestroy(JSXDRState *xdr); + +extern JS_PUBLIC_API(JSBool) +JS_XDRUint8(JSXDRState *xdr, uint8 *b); + +extern JS_PUBLIC_API(JSBool) +JS_XDRUint16(JSXDRState *xdr, uint16 *s); + +extern JS_PUBLIC_API(JSBool) +JS_XDRUint32(JSXDRState *xdr, uint32 *lp); + +extern JS_PUBLIC_API(JSBool) +JS_XDRBytes(JSXDRState *xdr, char *bytes, uint32 len); + +extern JS_PUBLIC_API(JSBool) +JS_XDRCString(JSXDRState *xdr, char **sp); + +extern JS_PUBLIC_API(JSBool) +JS_XDRCStringOrNull(JSXDRState *xdr, char **sp); + +extern JS_PUBLIC_API(JSBool) +JS_XDRString(JSXDRState *xdr, JSString **strp); + +extern JS_PUBLIC_API(JSBool) +JS_XDRStringOrNull(JSXDRState *xdr, JSString **strp); + +extern JS_PUBLIC_API(JSBool) +JS_XDRDouble(JSXDRState *xdr, jsdouble **dp); + +extern JS_PUBLIC_API(JSBool) +JS_XDRValue(JSXDRState *xdr, jsval *vp); + +extern JS_PUBLIC_API(JSBool) +JS_XDRScript(JSXDRState *xdr, JSScript **scriptp); + +extern JS_PUBLIC_API(JSBool) +JS_XDRRegisterClass(JSXDRState *xdr, JSClass *clasp, uint32 *lp); + +extern JS_PUBLIC_API(uint32) +JS_XDRFindClassIdByName(JSXDRState *xdr, const char *name); + +extern JS_PUBLIC_API(JSClass *) +JS_XDRFindClassById(JSXDRState *xdr, uint32 id); + +/* + * Magic numbers. + */ +#define JSXDR_MAGIC_SCRIPT_1 0xdead0001 +#define JSXDR_MAGIC_SCRIPT_2 0xdead0002 +#define JSXDR_MAGIC_SCRIPT_3 0xdead0003 +#define JSXDR_MAGIC_SCRIPT_4 0xdead0004 +#define JSXDR_MAGIC_SCRIPT_CURRENT JSXDR_MAGIC_SCRIPT_4 + +JS_END_EXTERN_C + +#endif /* ! jsxdrapi_h___ */ diff --git a/src/extension/script/js/prmjtime.c b/src/extension/script/js/prmjtime.c new file mode 100644 index 000000000..0a53f76bf --- /dev/null +++ b/src/extension/script/js/prmjtime.c @@ -0,0 +1,646 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * PR time code. + */ +#include "jsstddef.h" +#ifdef SOLARIS +#define _REENTRANT 1 +#endif +#include +#include +#include "jstypes.h" +#include "jsutil.h" + +#include "jsprf.h" +#include "prmjtime.h" + +#define PRMJ_DO_MILLISECONDS 1 + +#ifdef XP_OS2 +#include +#endif +#ifdef XP_WIN +#include +#include +#endif + +#ifdef XP_MAC +#include +#include +#include +#include +#include +#include +#include +#if !TARGET_CARBON +#include +#endif +#endif + +#if defined(XP_UNIX) || defined(XP_BEOS) + +#ifdef _SVID_GETTOD /* Defined only on Solaris, see Solaris */ +extern int gettimeofday(struct timeval *tv); +#endif + +#include + +#endif /* XP_UNIX */ + +#ifdef XP_MAC +static uint64 dstLocalBaseMicroseconds; +static unsigned long gJanuaryFirst1970Seconds; + +static void MacintoshInitializeTime(void) +{ + uint64 upTime; + unsigned long currentLocalTimeSeconds, + startupTimeSeconds; + uint64 startupTimeMicroSeconds; + uint32 upTimeSeconds; + uint64 oneMillion, upTimeSecondsLong, microSecondsToSeconds; + DateTimeRec firstSecondOfUnixTime; + + /* + * Figure out in local time what time the machine started up. This information can be added to + * upTime to figure out the current local time as well as GMT. + */ + + Microseconds((UnsignedWide*)&upTime); + + GetDateTime(¤tLocalTimeSeconds); + + JSLL_I2L(microSecondsToSeconds, PRMJ_USEC_PER_SEC); + JSLL_DIV(upTimeSecondsLong, upTime, microSecondsToSeconds); + JSLL_L2I(upTimeSeconds, upTimeSecondsLong); + + startupTimeSeconds = currentLocalTimeSeconds - upTimeSeconds; + + /* Make sure that we normalize the macintosh base seconds to the unix base of January 1, 1970. + */ + + firstSecondOfUnixTime.year = 1970; + firstSecondOfUnixTime.month = 1; + firstSecondOfUnixTime.day = 1; + firstSecondOfUnixTime.hour = 0; + firstSecondOfUnixTime.minute = 0; + firstSecondOfUnixTime.second = 0; + firstSecondOfUnixTime.dayOfWeek = 0; + + DateToSeconds(&firstSecondOfUnixTime, &gJanuaryFirst1970Seconds); + + startupTimeSeconds -= gJanuaryFirst1970Seconds; + + /* Now convert the startup time into a wide so that we can figure out GMT and DST. + */ + + JSLL_I2L(startupTimeMicroSeconds, startupTimeSeconds); + JSLL_I2L(oneMillion, PRMJ_USEC_PER_SEC); + JSLL_MUL(dstLocalBaseMicroseconds, oneMillion, startupTimeMicroSeconds); +} + +static SleepQRec gSleepQEntry = { NULL, sleepQType, NULL, 0 }; +static JSBool gSleepQEntryInstalled = JS_FALSE; + +static pascal long MySleepQProc(long message, SleepQRecPtr sleepQ) +{ + /* just woke up from sleeping, so must recompute dstLocalBaseMicroseconds. */ + if (message == kSleepWakeUp) + MacintoshInitializeTime(); + return 0; +} + +/* Because serial port and SLIP conflict with ReadXPram calls, + * we cache the call here + */ + +static void MyReadLocation(MachineLocation * loc) +{ + static MachineLocation storedLoc; /* InsideMac, OSUtilities, page 4-20 */ + static JSBool didReadLocation = JS_FALSE; + if (!didReadLocation) + { + MacintoshInitializeTime(); + ReadLocation(&storedLoc); + /* install a sleep queue routine, so that when the machine wakes up, time can be recomputed. */ + if (&SleepQInstall != (void*)kUnresolvedCFragSymbolAddress +#if !TARGET_CARBON + && NGetTrapAddress(0xA28A, OSTrap) != NGetTrapAddress(_Unimplemented, ToolTrap) +#endif + ) { + if ((gSleepQEntry.sleepQProc = NewSleepQUPP(MySleepQProc)) != NULL) { + SleepQInstall(&gSleepQEntry); + gSleepQEntryInstalled = JS_TRUE; + } + } + didReadLocation = JS_TRUE; + } + *loc = storedLoc; +} + + +#ifndef XP_MACOSX + +/* CFM library init and terminate routines. We'll use the terminate routine + to clean up the sleep Q entry. On Mach-O, the sleep Q entry gets cleaned + up for us, so nothing to do there. +*/ + +extern pascal OSErr __NSInitialize(const CFragInitBlock* initBlock); +extern pascal void __NSTerminate(); + +pascal OSErr __JSInitialize(const CFragInitBlock* initBlock); +pascal void __JSTerminate(void); + +pascal OSErr __JSInitialize(const CFragInitBlock* initBlock) +{ + return __NSInitialize(initBlock); +} + +pascal void __JSTerminate() +{ + /* clean up the sleepQ entry */ + if (gSleepQEntryInstalled) + SleepQRemove(&gSleepQEntry); + + __NSTerminate(); +} +#endif /* XP_MACOSX */ + +#endif /* XP_MAC */ + +#define IS_LEAP(year) \ + (year != 0 && ((((year & 0x3) == 0) && \ + ((year - ((year/100) * 100)) != 0)) || \ + (year - ((year/400) * 400)) == 0)) + +#define PRMJ_HOUR_SECONDS 3600L +#define PRMJ_DAY_SECONDS (24L * PRMJ_HOUR_SECONDS) +#define PRMJ_YEAR_SECONDS (PRMJ_DAY_SECONDS * 365L) +#define PRMJ_MAX_UNIX_TIMET 2145859200L /*time_t value equiv. to 12/31/2037 */ +/* function prototypes */ +static void PRMJ_basetime(JSInt64 tsecs, PRMJTime *prtm); +/* + * get the difference in seconds between this time zone and UTC (GMT) + */ +JSInt32 +PRMJ_LocalGMTDifference() +{ +#if defined(XP_UNIX) || defined(XP_WIN) || defined(XP_OS2) || defined(XP_BEOS) + struct tm ltime; + + /* get the difference between this time zone and GMT */ + memset((char *)<ime,0,sizeof(ltime)); + ltime.tm_mday = 2; + ltime.tm_year = 70; +#ifdef SUNOS4 + ltime.tm_zone = 0; + ltime.tm_gmtoff = 0; + return timelocal(<ime) - (24 * 3600); +#else + return mktime(<ime) - (24L * 3600L); +#endif +#endif +#if defined(XP_MAC) + static JSInt32 zone = -1L; + MachineLocation machineLocation; + JSInt32 gmtOffsetSeconds; + + /* difference has been set no need to recalculate */ + if (zone != -1) + return zone; + + /* Get the information about the local machine, including + * its GMT offset and its daylight savings time info. + * Convert each into wides that we can add to + * startupTimeMicroSeconds. + */ + + MyReadLocation(&machineLocation); + + /* Mask off top eight bits of gmtDelta, sign extend lower three. */ + gmtOffsetSeconds = (machineLocation.u.gmtDelta << 8); + gmtOffsetSeconds >>= 8; + + /* Backout OS adjustment for DST, to give consistent GMT offset. */ + if (machineLocation.u.dlsDelta != 0) + gmtOffsetSeconds -= PRMJ_HOUR_SECONDS; + return (zone = -gmtOffsetSeconds); +#endif +} + +/* Constants for GMT offset from 1970 */ +#define G1970GMTMICROHI 0x00dcdcad /* micro secs to 1970 hi */ +#define G1970GMTMICROLOW 0x8b3fa000 /* micro secs to 1970 low */ + +#define G2037GMTMICROHI 0x00e45fab /* micro secs to 2037 high */ +#define G2037GMTMICROLOW 0x7a238000 /* micro secs to 2037 low */ + +/* Convert from base time to extended time */ +static JSInt64 +PRMJ_ToExtendedTime(JSInt32 base_time) +{ + JSInt64 exttime; + JSInt64 g1970GMTMicroSeconds; + JSInt64 low; + JSInt32 diff; + JSInt64 tmp; + JSInt64 tmp1; + + diff = PRMJ_LocalGMTDifference(); + JSLL_UI2L(tmp, PRMJ_USEC_PER_SEC); + JSLL_I2L(tmp1,diff); + JSLL_MUL(tmp,tmp,tmp1); + + JSLL_UI2L(g1970GMTMicroSeconds,G1970GMTMICROHI); + JSLL_UI2L(low,G1970GMTMICROLOW); +#ifndef JS_HAVE_LONG_LONG + JSLL_SHL(g1970GMTMicroSeconds,g1970GMTMicroSeconds,16); + JSLL_SHL(g1970GMTMicroSeconds,g1970GMTMicroSeconds,16); +#else + JSLL_SHL(g1970GMTMicroSeconds,g1970GMTMicroSeconds,32); +#endif + JSLL_ADD(g1970GMTMicroSeconds,g1970GMTMicroSeconds,low); + + JSLL_I2L(exttime,base_time); + JSLL_ADD(exttime,exttime,g1970GMTMicroSeconds); + JSLL_SUB(exttime,exttime,tmp); + return exttime; +} + +JSInt64 +PRMJ_Now(void) +{ +#ifdef XP_OS2 + JSInt64 s, us, ms2us, s2us; + struct timeb b; +#endif +#ifdef XP_WIN + JSInt64 s, us, + win2un = JSLL_INIT(0x19DB1DE, 0xD53E8000), + ten = JSLL_INIT(0, 10); + FILETIME time, midnight; +#endif +#if defined(XP_UNIX) || defined(XP_BEOS) + struct timeval tv; + JSInt64 s, us, s2us; +#endif /* XP_UNIX */ +#ifdef XP_MAC + JSUint64 upTime; + JSInt64 localTime; + JSInt64 gmtOffset; + JSInt64 dstOffset; + JSInt32 gmtDiff; + JSInt64 s2us; +#endif /* XP_MAC */ + +#ifdef XP_OS2 + ftime(&b); + JSLL_UI2L(ms2us, PRMJ_USEC_PER_MSEC); + JSLL_UI2L(s2us, PRMJ_USEC_PER_SEC); + JSLL_UI2L(s, b.time); + JSLL_UI2L(us, b.millitm); + JSLL_MUL(us, us, ms2us); + JSLL_MUL(s, s, s2us); + JSLL_ADD(s, s, us); + return s; +#endif +#ifdef XP_WIN + /* The windows epoch is around 1600. The unix epoch is around 1970. + win2un is the difference (in windows time units which are 10 times + more precise than the JS time unit) */ + GetSystemTimeAsFileTime(&time); + /* Win9x gets confused at midnight + http://support.microsoft.com/default.aspx?scid=KB;en-us;q224423 + So if the low part (precision <8mins) is 0 then we get the time + again. */ + if (!time.dwLowDateTime) { + GetSystemTimeAsFileTime(&midnight); + time.dwHighDateTime = midnight.dwHighDateTime; + } + JSLL_UI2L(s, time.dwHighDateTime); + JSLL_UI2L(us, time.dwLowDateTime); + JSLL_SHL(s, s, 32); + JSLL_ADD(s, s, us); + JSLL_SUB(s, s, win2un); + JSLL_DIV(s, s, ten); + return s; +#endif + +#if defined(XP_UNIX) || defined(XP_BEOS) +#ifdef _SVID_GETTOD /* Defined only on Solaris, see Solaris */ + gettimeofday(&tv); +#else + gettimeofday(&tv, 0); +#endif /* _SVID_GETTOD */ + JSLL_UI2L(s2us, PRMJ_USEC_PER_SEC); + JSLL_UI2L(s, tv.tv_sec); + JSLL_UI2L(us, tv.tv_usec); + JSLL_MUL(s, s, s2us); + JSLL_ADD(s, s, us); + return s; +#endif /* XP_UNIX */ +#ifdef XP_MAC + JSLL_UI2L(localTime,0); + gmtDiff = PRMJ_LocalGMTDifference(); + JSLL_I2L(gmtOffset,gmtDiff); + JSLL_UI2L(s2us, PRMJ_USEC_PER_SEC); + JSLL_MUL(gmtOffset,gmtOffset,s2us); + + /* don't adjust for DST since it sets ctime and gmtime off on the MAC */ + Microseconds((UnsignedWide*)&upTime); + JSLL_ADD(localTime,localTime,gmtOffset); + JSLL_ADD(localTime,localTime, dstLocalBaseMicroseconds); + JSLL_ADD(localTime,localTime, upTime); + + dstOffset = PRMJ_DSTOffset(localTime); + JSLL_SUB(localTime,localTime,dstOffset); + + return *((JSUint64 *)&localTime); +#endif /* XP_MAC */ +} + +/* Get the DST timezone offset for the time passed in */ +JSInt64 +PRMJ_DSTOffset(JSInt64 local_time) +{ + JSInt64 us2s; +#ifdef XP_MAC + /* + * Convert the local time passed in to Macintosh epoch seconds. Use UTC utilities to convert + * to UTC time, then compare difference with our GMT offset. If they are the same, then + * DST must not be in effect for the input date/time. + */ + UInt32 macLocalSeconds = (local_time / PRMJ_USEC_PER_SEC) + gJanuaryFirst1970Seconds, utcSeconds; + ConvertLocalTimeToUTC(macLocalSeconds, &utcSeconds); + if ((utcSeconds - macLocalSeconds) == PRMJ_LocalGMTDifference()) + return 0; + else { + JSInt64 dlsOffset; + JSLL_UI2L(us2s, PRMJ_USEC_PER_SEC); + JSLL_UI2L(dlsOffset, PRMJ_HOUR_SECONDS); + JSLL_MUL(dlsOffset, dlsOffset, us2s); + return dlsOffset; + } +#else + time_t local; + JSInt32 diff; + JSInt64 maxtimet; + struct tm tm; + PRMJTime prtm; +#ifndef HAVE_LOCALTIME_R + struct tm *ptm; +#endif + + + JSLL_UI2L(us2s, PRMJ_USEC_PER_SEC); + JSLL_DIV(local_time, local_time, us2s); + + /* get the maximum of time_t value */ + JSLL_UI2L(maxtimet,PRMJ_MAX_UNIX_TIMET); + + if(JSLL_CMP(local_time,>,maxtimet)){ + JSLL_UI2L(local_time,PRMJ_MAX_UNIX_TIMET); + } else if(!JSLL_GE_ZERO(local_time)){ + /*go ahead a day to make localtime work (does not work with 0) */ + JSLL_UI2L(local_time,PRMJ_DAY_SECONDS); + } + JSLL_L2UI(local,local_time); + PRMJ_basetime(local_time,&prtm); +#ifndef HAVE_LOCALTIME_R + ptm = localtime(&local); + if(!ptm){ + return JSLL_ZERO; + } + tm = *ptm; +#else + localtime_r(&local,&tm); /* get dst information */ +#endif + + diff = ((tm.tm_hour - prtm.tm_hour) * PRMJ_HOUR_SECONDS) + + ((tm.tm_min - prtm.tm_min) * 60); + + if(diff < 0){ + diff += PRMJ_DAY_SECONDS; + } + + JSLL_UI2L(local_time,diff); + + JSLL_MUL(local_time,local_time,us2s); + + return(local_time); +#endif +} + +/* Format a time value into a buffer. Same semantics as strftime() */ +size_t +PRMJ_FormatTime(char *buf, int buflen, char *fmt, PRMJTime *prtm) +{ +#if defined(XP_UNIX) || defined(XP_WIN) || defined(XP_OS2) || defined(XP_MAC) || defined(XP_BEOS) + struct tm a; + + /* Zero out the tm struct. Linux, SunOS 4 struct tm has extra members int + * tm_gmtoff, char *tm_zone; when tm_zone is garbage, strftime gets + * confused and dumps core. NSPR20 prtime.c attempts to fill these in by + * calling mktime on the partially filled struct, but this doesn't seem to + * work as well; the result string has "can't get timezone" for ECMA-valid + * years. Might still make sense to use this, but find the range of years + * for which valid tz information exists, and map (per ECMA hint) from the + * given year into that range. + + * N.B. This hasn't been tested with anything that actually _uses_ + * tm_gmtoff; zero might be the wrong thing to set it to if you really need + * to format a time. This fix is for jsdate.c, which only uses + * JS_FormatTime to get a string representing the time zone. */ + memset(&a, 0, sizeof(struct tm)); + + a.tm_sec = prtm->tm_sec; + a.tm_min = prtm->tm_min; + a.tm_hour = prtm->tm_hour; + a.tm_mday = prtm->tm_mday; + a.tm_mon = prtm->tm_mon; + a.tm_wday = prtm->tm_wday; + a.tm_year = prtm->tm_year - 1900; + a.tm_yday = prtm->tm_yday; + a.tm_isdst = prtm->tm_isdst; + + /* Even with the above, SunOS 4 seems to detonate if tm_zone and tm_gmtoff + * are null. This doesn't quite work, though - the timezone is off by + * tzoff + dst. (And mktime seems to return -1 for the exact dst + * changeover time.) + + */ + +#if defined(SUNOS4) + if (mktime(&a) == -1) { + /* Seems to fail whenever the requested date is outside of the 32-bit + * UNIX epoch. We could proceed at this point (setting a.tm_zone to + * "") but then strftime returns a string with a 2-digit field of + * garbage for the year. So we return 0 and hope jsdate.c + * will fall back on toString. + */ + return 0; + } +#endif + + return strftime(buf, buflen, fmt, &a); +#endif +} + +/* table for number of days in a month */ +static int mtab[] = { + /* jan, feb,mar,apr,may,jun */ + 31,28,31,30,31,30, + /* july,aug,sep,oct,nov,dec */ + 31,31,30,31,30,31 +}; + +/* + * basic time calculation functionality for localtime and gmtime + * setups up prtm argument with correct values based upon input number + * of seconds. + */ +static void +PRMJ_basetime(JSInt64 tsecs, PRMJTime *prtm) +{ + /* convert tsecs back to year,month,day,hour,secs */ + JSInt32 year = 0; + JSInt32 month = 0; + JSInt32 yday = 0; + JSInt32 mday = 0; + JSInt32 wday = 6; /* start on a Sunday */ + JSInt32 days = 0; + JSInt32 seconds = 0; + JSInt32 minutes = 0; + JSInt32 hours = 0; + JSInt32 isleap = 0; + JSInt64 result; + JSInt64 result1; + JSInt64 result2; + JSInt64 base; + + JSLL_UI2L(result,0); + JSLL_UI2L(result1,0); + JSLL_UI2L(result2,0); + + /* get the base time via UTC */ + base = PRMJ_ToExtendedTime(0); + JSLL_UI2L(result, PRMJ_USEC_PER_SEC); + JSLL_DIV(base,base,result); + JSLL_ADD(tsecs,tsecs,base); + + JSLL_UI2L(result, PRMJ_YEAR_SECONDS); + JSLL_UI2L(result1,PRMJ_DAY_SECONDS); + JSLL_ADD(result2,result,result1); + + /* get the year */ + while ((isleap == 0) ? !JSLL_CMP(tsecs,<,result) : !JSLL_CMP(tsecs,<,result2)) { + /* subtract a year from tsecs */ + JSLL_SUB(tsecs,tsecs,result); + days += 365; + /* is it a leap year ? */ + if(IS_LEAP(year)){ + JSLL_SUB(tsecs,tsecs,result1); + days++; + } + year++; + isleap = IS_LEAP(year); + } + + JSLL_UI2L(result1,PRMJ_DAY_SECONDS); + + JSLL_DIV(result,tsecs,result1); + JSLL_L2I(mday,result); + + /* let's find the month */ + while(((month == 1 && isleap) ? + (mday >= mtab[month] + 1) : + (mday >= mtab[month]))){ + yday += mtab[month]; + days += mtab[month]; + + mday -= mtab[month]; + + /* it's a Feb, check if this is a leap year */ + if(month == 1 && isleap != 0){ + yday++; + days++; + mday--; + } + month++; + } + + /* now adjust tsecs */ + JSLL_MUL(result,result,result1); + JSLL_SUB(tsecs,tsecs,result); + + mday++; /* day of month always start with 1 */ + days += mday; + wday = (days + wday) % 7; + + yday += mday; + + /* get the hours */ + JSLL_UI2L(result1,PRMJ_HOUR_SECONDS); + JSLL_DIV(result,tsecs,result1); + JSLL_L2I(hours,result); + JSLL_MUL(result,result,result1); + JSLL_SUB(tsecs,tsecs,result); + + /* get minutes */ + JSLL_UI2L(result1,60); + JSLL_DIV(result,tsecs,result1); + JSLL_L2I(minutes,result); + JSLL_MUL(result,result,result1); + JSLL_SUB(tsecs,tsecs,result); + + JSLL_L2I(seconds,tsecs); + + prtm->tm_usec = 0L; + prtm->tm_sec = (JSInt8)seconds; + prtm->tm_min = (JSInt8)minutes; + prtm->tm_hour = (JSInt8)hours; + prtm->tm_mday = (JSInt8)mday; + prtm->tm_mon = (JSInt8)month; + prtm->tm_wday = (JSInt8)wday; + prtm->tm_year = (JSInt16)year; + prtm->tm_yday = (JSInt16)yday; +} diff --git a/src/extension/script/js/prmjtime.h b/src/extension/script/js/prmjtime.h new file mode 100644 index 000000000..6a94a11b1 --- /dev/null +++ b/src/extension/script/js/prmjtime.h @@ -0,0 +1,95 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef prmjtime_h___ +#define prmjtime_h___ +/* + * PR date stuff for mocha and java. Placed here temporarily not to break + * Navigator and localize changes to mocha. + */ +#include +#include "jslong.h" +#ifdef MOZILLA_CLIENT +#include "jscompat.h" +#endif + +JS_BEGIN_EXTERN_C + +typedef struct PRMJTime PRMJTime; + +/* + * Broken down form of 64 bit time value. + */ +struct PRMJTime { + JSInt32 tm_usec; /* microseconds of second (0-999999) */ + JSInt8 tm_sec; /* seconds of minute (0-59) */ + JSInt8 tm_min; /* minutes of hour (0-59) */ + JSInt8 tm_hour; /* hour of day (0-23) */ + JSInt8 tm_mday; /* day of month (1-31) */ + JSInt8 tm_mon; /* month of year (0-11) */ + JSInt8 tm_wday; /* 0=sunday, 1=monday, ... */ + JSInt16 tm_year; /* absolute year, AD */ + JSInt16 tm_yday; /* day of year (0 to 365) */ + JSInt8 tm_isdst; /* non-zero if DST in effect */ +}; + +/* Some handy constants */ +#define PRMJ_USEC_PER_SEC 1000000L +#define PRMJ_USEC_PER_MSEC 1000L + +/* Return the current local time in micro-seconds */ +extern JSInt64 +PRMJ_Now(void); + +/* get the difference between this time zone and gmt timezone in seconds */ +extern JSInt32 +PRMJ_LocalGMTDifference(void); + +/* Format a time value into a buffer. Same semantics as strftime() */ +extern size_t +PRMJ_FormatTime(char *buf, int buflen, char *fmt, PRMJTime *tm); + +/* Get the DST offset for the local time passed in */ +extern JSInt64 +PRMJ_DSTOffset(JSInt64 local_time); + +JS_END_EXTERN_C + +#endif /* prmjtime_h___ */ + diff --git a/src/extension/script/js/resource.h b/src/extension/script/js/resource.h new file mode 100644 index 000000000..9301810e4 --- /dev/null +++ b/src/extension/script/js/resource.h @@ -0,0 +1,15 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by js3240.rc +// + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/extension/script/makefile.in b/src/extension/script/makefile.in new file mode 100644 index 000000000..7ebfd5217 --- /dev/null +++ b/src/extension/script/makefile.in @@ -0,0 +1,17 @@ +# Convenience stub makefile to call the real Makefile. + +@SET_MAKE@ + +# Explicit so that it's the default rule. +all: + cd ../.. && $(MAKE) extension/script/all + +clean %.a %.o: + cd ../.. && $(MAKE) extension/script/$@ + +.PHONY: all clean + +OBJEXT = @OBJEXT@ + +.SUFFIXES: +.SUFFIXES: .a .$(OBJEXT) diff --git a/src/extension/script/quotefile.pl b/src/extension/script/quotefile.pl new file mode 100644 index 000000000..9eb769a25 --- /dev/null +++ b/src/extension/script/quotefile.pl @@ -0,0 +1,82 @@ +#!/usr/bin/perl +############################################################################ +# +# Quote all of the lines of a text file, so that it can be loaded +# into C/C++ +# +############################################################################ + +# +# main - top level code +# + + +if ( $#ARGV != 1 ) { # parse command line args + print "usage: perl quotefile.pl infile outfile\n\n"; + exit 1; +} + +$inName = $ARGV[0]; +$outName = $ARGV[1]; + +print "#######################################################\n"; +print "## Quoting $inName to $outName\n"; +print "#######################################################\n"; + +&doQuoteFile(); #Do your magic! + +print "#######################################################\n"; +print "## DONE\n"; +print "#######################################################\n"; + +exit 0; + + + +############################################################################ +# +# +# +# +############################################################################ +sub doQuoteFile +{ + my $line; #current line from input file + my $datestr; #Current date + local(*INFILE); + local(*OUTFILE); + + $datestr = gmtime(); + + if ( -r $inName ) + { + open INFILE, $inName or + die "$inName: $!"; + open OUTFILE, ">$outName" or + die "$outName: $!"; + print OUTFILE "\n"; + print OUTFILE "/* ###################################################\n"; + print OUTFILE "## This file generated by quotefile.pl from\n"; + print OUTFILE "## $inName on $datestr\n"; + print OUTFILE "## DO NOT EDIT\n"; + print OUTFILE "################################################### */\n"; + print OUTFILE "\n"; + print OUTFILE "static char *inkscape_module_script =\n"; + while () + { + $line = $_; + #Escape existing quotes + $line =~ s/\"/\\"/g; + #Add outer quotes + $line =~ s/^/\"/; + $line =~ s/$/\\n\"/; + + print OUTFILE $line + } + close INFILE; + print OUTFILE "\"\";\n"; + close OUTFILE; + } + +} + diff --git a/src/extension/script/runme.py b/src/extension/script/runme.py new file mode 100644 index 000000000..706676064 --- /dev/null +++ b/src/extension/script/runme.py @@ -0,0 +1,8 @@ + +import inkscape_py + +inkscape = inkscape_py.getInkscape() +desktop = inkscape.getDesktop() +document = desktop.getDocument() +document.hello() + diff --git a/src/extension/script/wrap_swig_module.sh b/src/extension/script/wrap_swig_module.sh new file mode 100644 index 000000000..8c6236f2d --- /dev/null +++ b/src/extension/script/wrap_swig_module.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +inf=$1 +outf=$2 +datestr=`date` + +echo "" > ${outf} +echo "/* ###################################################" >> ${outf} +echo "## This file generated by wrap_swig_module.sh from" >> ${outf} +echo "## ${inf} on ${datestr}" >> ${outf} +echo "## DO NOT EDIT" >> ${outf} +echo "################################################### */" >> ${outf} +echo "" >> ${outf} + +echo "static char *inkscape_module_script =" >> ${outf} +echo "\"\n\"" >> ${outf} + +cat ${inf} | \ +sed -e 's/\"/\\"/g' -e 's/^/\"/g' -e 's/$/\\n\"/g' >> ${outf} + +echo "\"\n\";" >> ${outf} + + + + + + diff --git a/src/extension/system.cpp b/src/extension/system.cpp new file mode 100644 index 000000000..34855cd4e --- /dev/null +++ b/src/extension/system.cpp @@ -0,0 +1,476 @@ +/* + * This is file is kind of the junk file. Basically everything that + * didn't fit in one of the other well defined areas, well, it's now + * here. Which is good in someways, but this file really needs some + * definition. Hopefully that will come ASAP. + * + * Authors: + * Ted Gould + * + * Copyright (C) 2002-2004 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +#include "db.h" +#include "input.h" +#include "output.h" +#include "effect.h" +#include "print.h" +#include "implementation/script.h" +/* #include "implementation/plugin.h" */ + +namespace Inkscape { +namespace Extension { + +static void open_internal(Inkscape::Extension::Extension *in_plug, gpointer in_data); +static void save_internal(Inkscape::Extension::Extension *in_plug, gpointer in_data); +static Extension *build_from_reprdoc(Inkscape::XML::Document *doc, Implementation::Implementation *in_imp); + +/** + * \return A new document created from the filename passed in + * \brief This is a generic function to use the open function of + * a module (including Autodetect) + * \param key Identifier of which module to use + * \param filename The file that should be opened + * + * First things first, are we looking at an autodetection? Well if that's the case then the module + * needs to be found, and that is done with a database lookup through the module DB. The foreach + * function is called, with the parameter being a gpointer array. It contains both the filename + * (to find its extension) and where to write the module when it is found. + * + * If there is no autodetection, then the module database is queried with the key given. + * + * If everything is cool at this point, the module is loaded, and there is possibility for + * preferences. If there is a function, then it is executed to get the dialog to be displayed. + * After it is finished the function continues. + * + * Lastly, the open function is called in the module itself. + */ +SPDocument * +open(Extension *key, gchar const *filename) +{ + Input *imod = NULL; + if (key == NULL) { + gpointer parray[2]; + parray[0] = (gpointer)filename; + parray[1] = (gpointer)&imod; + db.foreach(open_internal, (gpointer)&parray); + } else { + imod = dynamic_cast(key); + } + + bool last_chance_svg = false; + if (key == NULL && imod == NULL) { + last_chance_svg = true; + imod = dynamic_cast(db.get(SP_MODULE_KEY_INPUT_SVG)); + } + + if (imod == NULL) { + throw Input::no_extension_found(); + } + + imod->set_state(Extension::STATE_LOADED); + + if (!imod->loaded()) { + throw Input::open_failed(); + } + + if (!imod->prefs(filename)) + return NULL; + + SPDocument *doc = imod->open(filename); + if (!doc) { + return NULL; + } + + if (last_chance_svg) { + /* We can't call sp_ui_error_dialog because we may be + running from the console, in which case calling sp_ui + routines will cause a segfault. See bug 1000350 - bryce */ + // sp_ui_error_dialog(_("Format autodetect failed. The file is being opened as SVG.")); + g_warning(_("Format autodetect failed. The file is being opened as SVG.")); + } + + /* This kinda overkill as most of these are already set, but I want + to make sure for this release -- TJG */ + Inkscape::XML::Node *repr = sp_document_repr_root(doc); + gboolean saved = sp_document_get_undo_sensitive(doc); + sp_document_set_undo_sensitive(doc, FALSE); + repr->setAttribute("sodipodi:modified", NULL); + sp_document_set_undo_sensitive(doc, saved); + + sp_document_set_uri(doc, filename); + + return doc; +} + +/** + * \return none + * \brief This is the function that searches each module to see + * if it matches the filename for autodetection. + * \param in_plug The module to be tested + * \param in_data An array of pointers containing the filename, and + * the place to put a successfully found module. + * + * Basically this function only looks at input modules as it is part of the open function. If the + * module is an input module, it then starts to take it apart, and the data that is passed in. + * Because the data being passed in is in such a weird format, there are a few casts to make it + * easier to use. While it looks like a lot of local variables, they'll all get removed by the + * compiler. + * + * First thing that is checked is if the filename is shorter than the extension itself. There is + * no way for a match in that case. If it's long enough then there is a string compare of the end + * of the filename (for the length of the extension), and the extension itself. If this passes + * then the pointer passed in is set to the current module. + */ +static void +open_internal(Extension *in_plug, gpointer in_data) +{ + if (!in_plug->deactivated() && dynamic_cast(in_plug)) { + gpointer *parray = (gpointer *)in_data; + gchar const *filename = (gchar const *)parray[0]; + Input **pimod = (Input **)parray[1]; + + // skip all the rest if we already found a function to open it + // since they're ordered by preference now. + if (!*pimod) { + gchar const *ext = dynamic_cast(in_plug)->get_extension(); + + gchar *filenamelower = g_utf8_strdown(filename, -1); + gchar *extensionlower = g_utf8_strdown(ext, -1); + + if (g_str_has_suffix(filenamelower, extensionlower)) { + *pimod = dynamic_cast(in_plug); + } + + g_free(filenamelower); + g_free(extensionlower); + } + } + + return; +} + +/** + * \return None + * \brief This is a generic function to use the save function of + * a module (including Autodetect) + * \param key Identifier of which module to use + * \param doc The document to be saved + * \param filename The file that the document should be saved to + * \param official (optional) whether to set :output_module and :modified in the + * document; is true for normal save, false for temporary saves + * + * First things first, are we looking at an autodetection? Well if that's the case then the module + * needs to be found, and that is done with a database lookup through the module DB. The foreach + * function is called, with the parameter being a gpointer array. It contains both the filename + * (to find its extension) and where to write the module when it is found. + * + * If there is no autodetection the module database is queried with the key given. + * + * If everything is cool at this point, the module is loaded, and there is possibility for + * preferences. If there is a function, then it is executed to get the dialog to be displayed. + * After it is finished the function continues. + * + * Lastly, the save function is called in the module itself. + */ +void +save(Extension *key, SPDocument *doc, gchar const *filename, bool setextension, bool check_overwrite, bool official) +{ + Output *omod; + if (key == NULL) { + gpointer parray[2]; + parray[0] = (gpointer)filename; + parray[1] = (gpointer)&omod; + omod = NULL; + db.foreach(save_internal, (gpointer)&parray); + + /* This is a nasty hack, but it is required to ensure that + autodetect will always save with the Inkscape extensions + if they are available. */ + if (omod != NULL && !strcmp(omod->get_id(), SP_MODULE_KEY_OUTPUT_SVG)) { + omod = dynamic_cast(db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE)); + } + /* If autodetect fails, save as Inkscape SVG */ + if (omod == NULL) { + omod = dynamic_cast(db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE)); + } + } else { + omod = dynamic_cast(key); + } + + if (!dynamic_cast(omod)) { + g_warning("Unable to find output module to handle file: %s\n", filename); + throw Output::no_extension_found(); + return; + } + + omod->set_state(Extension::STATE_LOADED); + if (!omod->loaded()) { + throw Output::save_failed(); + } + + if (!omod->prefs()) + return; + + gchar *fileName = NULL; + if (setextension) { + gchar *lowerfile = g_utf8_strdown(filename, -1); + gchar *lowerext = g_utf8_strdown(omod->get_extension(), -1); + + if (!g_str_has_suffix(lowerfile, lowerext)) { + fileName = g_strdup_printf("%s%s", filename, omod->get_extension()); + } + + g_free(lowerfile); + g_free(lowerext); + } + + if (fileName == NULL) { + fileName = g_strdup(filename); + } + + if (check_overwrite && !sp_ui_overwrite_file(fileName)) { + g_free(fileName); + throw Output::no_overwrite(); + } + + if (official) { + sp_document_set_uri(doc, fileName); + } + + omod->save(doc, fileName); + + g_free(fileName); + return; +} + +/** + * \return none + * \brief This is the function that searches each module to see + * if it matches the filename for autodetection. + * \param in_plug The module to be tested + * \param in_data An array of pointers containing the filename, and + * the place to put a successfully found module. + * + * Basically this function only looks at output modules as it is part of the open function. If the + * module is an output module, it then starts to take it apart, and the data that is passed in. + * Because the data being passed in is in such a weird format, there are a few casts to make it + * easier to use. While it looks like a lot of local variables, they'll all get removed by the + * compiler. + * + * First thing that is checked is if the filename is shorter than the extension itself. There is + * no way for a match in that case. If it's long enough then there is a string compare of the end + * of the filename (for the length of the extension), and the extension itself. If this passes + * then the pointer passed in is set to the current module. + */ +static void +save_internal(Extension *in_plug, gpointer in_data) +{ + if (!in_plug->deactivated() && dynamic_cast(in_plug)) { + gpointer *parray = (gpointer *)in_data; + gchar const *filename = (gchar const *)parray[0]; + Output **pomod = (Output **)parray[1]; + + // skip all the rest if we already found someone to save it + // since they're ordered by preference now. + if (!*pomod) { + gchar const *ext = dynamic_cast(in_plug)->get_extension(); + + gchar *filenamelower = g_utf8_strdown(filename, -1); + gchar *extensionlower = g_utf8_strdown(ext, -1); + + if (g_str_has_suffix(filenamelower, extensionlower)) { + *pomod = dynamic_cast(in_plug); + } + + g_free(filenamelower); + g_free(extensionlower); + } + } + + return; +} + +Print * +get_print(gchar const *key) +{ + return dynamic_cast(db.get(key)); +} + +/** + * \return The built module + * \brief Creates a module from a Inkscape::XML::Document describing the module + * \param doc The XML description of the module + * + * This function basically has two segments. The first is that it goes through the Repr tree + * provided, and determines what kind of of module this is, and what kind of implementation to use. + * All of these are then stored in two enums that are defined in this function. This makes it + * easier to add additional types (which will happen in the future, I'm sure). + * + * Second, there is case statements for these enums. The first one is the type of module. This is + * the one where the module is actually created. After that, then the implementation is applied to + * get the load and unload functions. If there is no implementation then these are not set. This + * case could apply to modules that are built in (like the SVG load/save functions). + */ +static Extension * +build_from_reprdoc(Inkscape::XML::Document *doc, Implementation::Implementation *in_imp) +{ + enum { + MODULE_EXTENSION, + /* MODULE_PLUGIN, */ + MODULE_UNKNOWN_IMP + } module_implementation_type = MODULE_UNKNOWN_IMP; + enum { + MODULE_INPUT, + MODULE_OUTPUT, + MODULE_FILTER, + MODULE_PRINT, + MODULE_UNKNOWN_FUNC + } module_functional_type = MODULE_UNKNOWN_FUNC; + + g_return_val_if_fail(doc != NULL, NULL); + + Inkscape::XML::Node *repr = sp_repr_document_root(doc); + + /* sp_repr_print(repr); */ + + if (strcmp(repr->name(), "inkscape-extension")) { + g_warning("Extension definition started with <%s> instead of . Extension will not be created.\n", repr->name()); + return NULL; + } + + Inkscape::XML::Node *child_repr = sp_repr_children(repr); + while (child_repr != NULL) { + char const *element_name = child_repr->name(); + /* printf("Child: %s\n", child_repr->name()); */ + if (!strcmp(element_name, "input")) { + module_functional_type = MODULE_INPUT; + } else if (!strcmp(element_name, "output")) { + module_functional_type = MODULE_OUTPUT; + } else if (!strcmp(element_name, "effect")) { + module_functional_type = MODULE_FILTER; + } else if (!strcmp(element_name, "print")) { + module_functional_type = MODULE_PRINT; + } else if (!strcmp(element_name, "script")) { + module_implementation_type = MODULE_EXTENSION; +#if 0 + } else if (!strcmp(element_name, "plugin")) { + module_implementation_type = MODULE_PLUGIN; +#endif + } + + //Inkscape::XML::Node *old_repr = child_repr; + child_repr = sp_repr_next(child_repr); + //Inkscape::GC::release(old_repr); + } + + Implementation::Implementation *imp; + if (in_imp == NULL) { + switch (module_implementation_type) { + case MODULE_EXTENSION: { + Implementation::Script *script = new Implementation::Script(); + imp = static_cast(script); + break; + } +#if 0 + case MODULE_PLUGIN: { + Implementation::Plugin *plugin = new Implementation::Plugin(); + imp = static_cast(plugin); + break; + } +#endif + default: { + imp = NULL; + break; + } + } + } else { + imp = in_imp; + } + + Extension *module = NULL; + switch (module_functional_type) { + case MODULE_INPUT: { + module = new Input(repr, imp); + break; + } + case MODULE_OUTPUT: { + module = new Output(repr, imp); + break; + } + case MODULE_FILTER: { + module = new Effect(repr, imp); + break; + } + case MODULE_PRINT: { + module = new Print(repr, imp); + break; + } + default: { + break; + } + } + + return module; +} + +/** + * \return The module created + * \brief This function creates a module from a filename of an + * XML description. + * \param filename The file holding the XML description of the module. + * + * This function calls build_from_reprdoc with using sp_repr_read_file to create the reprdoc. + */ +Extension * +build_from_file(gchar const *filename) +{ + /* TODO: Need to define namespace here, need to write the + DTD in general for this stuff */ + Inkscape::XML::Document *doc = sp_repr_read_file(filename, NULL); + Extension *ext = build_from_reprdoc(doc, NULL); + Inkscape::GC::release(doc); + if (ext == NULL) + g_warning("Unable to create extension from definition file %s.\n", filename); + return ext; +} + +/** + * \return The module created + * \brief This function creates a module from a buffer holding an + * XML description. + * \param buffer The buffer holding the XML description of the module. + * + * This function calls build_from_reprdoc with using sp_repr_read_mem to create the reprdoc. It + * finds the length of the buffer using strlen. + */ +Extension * +build_from_mem(gchar const *buffer, Implementation::Implementation *in_imp) +{ + Inkscape::XML::Document *doc = sp_repr_read_mem(buffer, strlen(buffer), NULL); + Extension *ext = build_from_reprdoc(doc, in_imp); + Inkscape::GC::release(doc); + return ext; +} + + +} } /* namespace Inkscape::Extension */ + +/* + 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/extension/system.h b/src/extension/system.h new file mode 100644 index 000000000..6c23b2f0d --- /dev/null +++ b/src/extension/system.h @@ -0,0 +1,44 @@ +/* + * This is file is kind of the junk file. Basically everything that + * didn't fit in one of the other well defined areas, well, it's now + * here. Which is good in someways, but this file really needs some + * definition. Hopefully that will come ASAP. + * + * Authors: + * Ted Gould + * + * Copyright (C) 2002-2004 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef INKSCAPE_EXTENSION_SYSTEM_H__ +#define INKSCAPE_EXTENSION_SYSTEM_H__ + +#include "document.h" +#include "extension/extension.h" + +namespace Inkscape { +namespace Extension { + +SPDocument *open(Extension *key, gchar const *filename); +void save(Extension *key, SPDocument *doc, gchar const *filename, + bool setextension, bool check_overwrite, bool official); +Print *get_print(gchar const *key); +Extension *build_from_file(gchar const *filename); +Extension *build_from_mem(gchar const *buffer, Implementation::Implementation *in_imp); + +} } /* namespace Inkscape::Extension */ + +#endif /* INKSCAPE_EXTENSION_SYSTEM_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 : diff --git a/src/extension/timer.cpp b/src/extension/timer.cpp new file mode 100644 index 000000000..0e7a6ad11 --- /dev/null +++ b/src/extension/timer.cpp @@ -0,0 +1,214 @@ +/* + * Here is where the extensions can get timed on when they load and + * unload. All of the timing is done in here. + * + * Authors: + * Ted Gould + * + * Copyright (C) 2004 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + + +#include "extension.h" +#include "timer.h" + +namespace Inkscape { +namespace Extension { + +#define TIMER_SCALE_VALUE 20 + +ExpirationTimer * ExpirationTimer::timer_list = NULL; +ExpirationTimer * ExpirationTimer::idle_start = NULL; +long ExpirationTimer::timeout = 240; +bool ExpirationTimer::timer_started = false; + +/** \brief Create a new expiration timer + \param in_extension Which extension this timer is related to + + This function creates the timer, and sets the time to the current + time, plus what ever the current timeout is. Also, if this is + the first timer extension, the timer is kicked off. This function + also sets up teh circularly linked list of all the timers. +*/ +ExpirationTimer::ExpirationTimer (Extension * in_extension) +{ + locked = false; + extension = in_extension; + + /* Fix Me! */ + if (timer_list == NULL) { + next = this; + timer_list = this; + } else { + next = timer_list->next; + timer_list->next = this; + } + + expiration.assign_current_time(); + expiration += timeout; + + if (!timer_started) { + Glib::signal_timeout().connect(sigc::ptr_fun(&timer_func), timeout * 1000 / TIMER_SCALE_VALUE); + timer_started = true; + } + + return; +} + +/** \brief Deletes a \c ExpirationTimer + + The most complex thing that this function does is remove the timer + from the circularly linked list. If this is the only entry in the + list that is easy, otherwise all the entries must be found, and this + one removed from the list. +*/ +ExpirationTimer::~ExpirationTimer(void) +{ + if (this != next) { + /* This will remove this entry from the circularly linked + list. */ + ExpirationTimer * prev; + for (prev = timer_list; + prev->next != this; + prev = prev->next); + prev->next = next; + + if (idle_start == this) + idle_start = next; + + /* This may occur more than you think, just because the guy + doing most of the deletions is the idle function, who tracks + where it is looking using the \c timer_list variable. */ + if (timer_list == this) + timer_list = next; + } else { + /* If we're the only entry in the list, the list needs to go + to NULL */ + timer_list = NULL; + idle_start = NULL; + } + + return; +} + +/** \brief Touches the timer to extend the length before it expires + + Basically it adds more time to the timer. One thing that is kinda + tricky is that it adds half the time remaining back into the timer. + This allows for some extensions that are used regularly to having + extended expiration times. So, in the end, they stay loaded longer. + Extensions that are only used once will expire at a standard rate + set by \c timeout. +*/ +void +ExpirationTimer::touch (void) +{ + Glib::TimeVal current; + current.assign_current_time(); + + long time_left = (long)(expiration.as_double() - current.as_double()); + if (time_left < 0) time_left = 0; + time_left /= 2; + + expiration = current + timeout + time_left; + return; +} + +/** \brief Check to see if the timer has expired + + Checks the time against the current time. +*/ +bool +ExpirationTimer::expired (void) const +{ + if (locked) return false; + + Glib::TimeVal current; + current.assign_current_time(); + return expiration < current; +} + +// int idle_cnt = 0; + +/** \brief This function goes in the idle loop to find expired extensions + \return Whether the function should be requeued or not + + This function first insures that there is a timer list, and then checks + to see if the one on the top of the list has expired. If it has + expired it unloads the module. By unloading the module, the timer + gets deleted (happens in the unload function). If the list is + no empty, the function returns that it should be dequeued and sets + the \c timer_started variable so that the timer will be reissued when + a timer is added. If there is entries left, but the next one is + where this function started, then the timer is set up. The timer + will then re-add the idle loop function when it runs. +*/ +bool +ExpirationTimer::idle_func (void) +{ + // std::cout << "Idle func pass: " << idle_cnt++ << " timer list: " << timer_list << std::endl; + + /* see if this is the last */ + if (timer_list == NULL) { + timer_started = false; + return false; + } + + /* evalutate current */ + if (timer_list->expired()) { + timer_list->extension->set_state(Extension::STATE_UNLOADED); + } + + /* see if this is the last */ + if (timer_list == NULL) { + timer_started = false; + return false; + } + + if (timer_list->next == idle_start) { + /* if so, set up the timer and return FALSE */ + /* Note: This may cause one to be missed on the evaluation if + the one before it expires and it is last in the list. + While this could be taken care of, it isn't worth the + complexity for this lazy removal that we're doing. It + should get picked up next time */ + Glib::signal_timeout().connect(sigc::ptr_fun(&timer_func), timeout * 1000 / TIMER_SCALE_VALUE); + return false; + } + + /* If nothing else, continue on */ + timer_list = timer_list->next; + return true; +} + +/** \brief A timer function to set up the idle function + \return Always false -- to disable the timer + + This function sets up the idle loop when it runs. The idle loop is + the one that unloads all the extensions. +*/ +bool +ExpirationTimer::timer_func (void) +{ + // std::cout << "Timer func" << std::endl; + idle_start = timer_list; + // idle_cnt = 0; + Glib::signal_idle().connect(sigc::ptr_fun(&idle_func)); + return false; +} + +}; }; /* namespace Inkscape, Extension */ + +/* + 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/extension/timer.h b/src/extension/timer.h new file mode 100644 index 000000000..b9649e899 --- /dev/null +++ b/src/extension/timer.h @@ -0,0 +1,72 @@ +/* + * Here is where the extensions can get timed on when they load and + * unload. All of the timing is done in here. + * + * Authors: + * Ted Gould + * + * Copyright (C) 2004 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef INKSCAPE_EXTENSION_TIMER_H__ +#define INKSCAPE_EXTENSION_TIMER_H__ + +#include +#include +#include "extension-forward.h" + +namespace Inkscape { +namespace Extension { + +class ExpirationTimer { + /** \brief Circularly linked list of all timers */ + static ExpirationTimer * timer_list; + /** \brief Which timer was on top when we started the idle loop */ + static ExpirationTimer * idle_start; + /** \brief What the current timeout is */ + static long timeout; + /** \brief Has the timer been started? */ + static bool timer_started; + + /** \brief Is this extension locked from being unloaded? */ + bool locked; + /** \brief Next entry in the list */ + ExpirationTimer * next; + /** \brief When this timer expires */ + Glib::TimeVal expiration; + /** \brief What extension this function relates to */ + Extension * extension; + + bool expired(void) const; + + static bool idle_func (void); + static bool timer_func (void); + +public: + ExpirationTimer(Extension * in_extension); + ~ExpirationTimer(void); + + void touch (void); + void lock (void) { locked = true; }; + void unlock (void) { locked = false; }; + + /** \brief Set the timeout variable */ + static void set_timeout (long in_seconds) { timeout = in_seconds; }; +}; + +}; }; /* namespace Inkscape, Extension */ + +#endif /* INKSCAPE_EXTENSION_TIMER_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 : diff --git a/src/extract-uri-test.cpp b/src/extract-uri-test.cpp new file mode 100644 index 000000000..9bf44baca --- /dev/null +++ b/src/extract-uri-test.cpp @@ -0,0 +1,56 @@ +#include +#include "extract-uri.h" +#include +#include + +struct Case { + char const *input; + char const *exp; +}; + +static void test_extract_uri_case(Case const &c) +{ + char * const p = extract_uri(c.input); + UTEST_TEST(c.input) { + UTEST_ASSERT( ( p == NULL ) == ( c.exp == NULL ) ); + if (p) { + UTEST_ASSERT( strcmp(p, c.exp) == 0 ); + } + } + g_free(p); +} + +int main(int argc, char *argv[]) +{ + utest_start("extract_uri"); + + Case const cases[] = { + { "url(#foo)", "#foo" }, + { "url foo ", "foo" }, + { "url", NULL }, + { "url ", NULL }, + { "url()", NULL }, + { "url ( ) ", NULL }, + { "url foo bar ", "foo bar" } + }; + + for(unsigned i = 0; i < G_N_ELEMENTS(cases); ++i) { + Case const &c = cases[i]; + test_extract_uri_case(c); + } + + return ( utest_end() + ? EXIT_SUCCESS + : EXIT_FAILURE ); +} + +/* + 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 : diff --git a/src/extract-uri.cpp b/src/extract-uri.cpp new file mode 100644 index 000000000..dd6549c8f --- /dev/null +++ b/src/extract-uri.cpp @@ -0,0 +1,40 @@ +#include +#include + +// FIXME: kill this ugliness when we have a proper CSS parser +gchar *extract_uri(gchar const *s) +{ + gchar const *sb = s; + g_assert( strncmp(sb, "url", 3) == 0 ); + sb += 3; + + while ( ( *sb == ' ' ) || + ( *sb == '(' ) ) + { + sb++; + } + + gchar const *se = sb + strlen(sb); + while ( ( se[-1] == ' ' ) || + ( se[-1] == ')' ) ) + { + se--; + } + + if ( sb < se ) { + return g_strndup(sb, se - sb); + } else { + return NULL; + } +} + +/* + 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 : diff --git a/src/extract-uri.h b/src/extract-uri.h new file mode 100644 index 000000000..10def3904 --- /dev/null +++ b/src/extract-uri.h @@ -0,0 +1,20 @@ +#ifndef SEEN_EXTRACT_URI_H +#define SEEN_EXTRACT_URI_H + +#include + +gchar *extract_uri(gchar const *s); + + +#endif /* !SEEN_EXTRACT_URI_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:encoding=utf-8:textwidth=99 : diff --git a/src/file.cpp b/src/file.cpp new file mode 100644 index 000000000..acc6e101a --- /dev/null +++ b/src/file.cpp @@ -0,0 +1,1337 @@ +#define __SP_FILE_C__ + +/* + * File/Print operations + * + * Authors: + * Lauris Kaplinski + * Chema Celorio + * bulia byak + * + * Copyright (C) 1999-2005 Authors + * Copyright (C) 2001-2002 Ximian, Inc. + * Copyright (C) 2004 David Turner + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +/** + * Note: This file needs to be cleaned up extensively. + * What it probably needs is to have one .h file for + * the API, and two or more .cpp files for the implementations. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include "document-private.h" +#include "selection-chemistry.h" +#include "ui/view/view-widget.h" +#include "dir-util.h" +#include "helper/png-write.h" +#include "dialogs/export.h" +#include +#include "inkscape.h" +#include "desktop.h" +#include "selection.h" +#include "interface.h" +#include "style.h" +#include "print.h" +#include "file.h" +#include "message-stack.h" +#include "dialogs/filedialog.h" +#include "prefs-utils.h" +#include "path-prefix.h" + +#include "sp-namedview.h" +#include "desktop-handles.h" + +#include "extension/db.h" +#include "extension/input.h" +#include "extension/output.h" +/* #include "extension/menu.h" */ +#include "extension/system.h" + +#include "io/sys.h" +#include "application/application.h" +#include "application/editor.h" +#include "inkscape.h" +#include "uri.h" + +#ifdef WITH_INKBOARD +#include "jabber_whiteboard/session-manager.h" +#endif + +/** + * 'Current' paths. Used to remember which directory + * had the last file accessed. + * Static globals are evil. This will be gone soon + * as C++ification continues + */ +static gchar *import_path = NULL; + +//#define INK_DUMP_FILENAME_CONV 1 +#undef INK_DUMP_FILENAME_CONV + +//#define INK_DUMP_FOPEN 1 +#undef INK_DUMP_FOPEN + +void dump_str(gchar const *str, gchar const *prefix); +void dump_ustr(Glib::ustring const &ustr); + + +/*###################### +## N E W +######################*/ + +/** + * Create a blank document and add it to the desktop + */ +SPDesktop* +sp_file_new(gchar const *templ) +{ + SPDocument *doc = sp_document_new(templ, TRUE, true); + g_return_val_if_fail(doc != NULL, NULL); + + SPDesktop *dt; + if (Inkscape::NSApplication::Application::getNewGui()) + { + dt = Inkscape::NSApplication::Editor::createDesktop (doc); + } else { + SPViewWidget *dtw = sp_desktop_widget_new(sp_document_namedview(doc, NULL)); + g_return_val_if_fail(dtw != NULL, NULL); + sp_document_unref(doc); + + sp_create_window(dtw, TRUE); + dt = static_cast(dtw->view); + sp_namedview_window_from_document(dt); + } + return dt; +} + +SPDesktop* +sp_file_new_default() +{ + std::list sources; + sources.push_back( profile_path("templates") ); // first try user's local dir + sources.push_back( g_strdup(INKSCAPE_TEMPLATESDIR) ); // then the system templates dir + + while (!sources.empty()) { + gchar *dirname = sources.front(); + if ( Inkscape::IO::file_test( dirname, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) ) ) { + + // TRANSLATORS: default.svg is localizable - this is the name of the default document + // template. This way you can localize the default pagesize, translate the name of + // the default layer, etc. If you wish to localize this file, please create a + // localized share/templates/default.xx.svg file, where xx is your language code. + char *default_template = g_build_filename(dirname, _("default.svg"), NULL); + if (Inkscape::IO::file_test(default_template, G_FILE_TEST_IS_REGULAR)) { + return sp_file_new(default_template); + } + } + g_free(dirname); + sources.pop_front(); + } + + return sp_file_new(NULL); +} + + +/*###################### +## D E L E T E +######################*/ + +/** + * Perform document closures preceding an exit() + */ +void +sp_file_exit() +{ + sp_ui_close_all(); + // no need to call inkscape_exit here; last document being closed will take care of that +} + + +/*###################### +## O P E N +######################*/ + +/** + * Open a file, add the document to the desktop + * + * \param replace_empty if true, and the current desktop is empty, this document + * will replace the empty one. + */ +bool +sp_file_open(gchar const *uri, Inkscape::Extension::Extension *key, bool add_to_recent, bool replace_empty) +{ + SPDocument *doc; + try { + doc = Inkscape::Extension::open(key, uri); + } catch (Inkscape::Extension::Input::no_extension_found &e) { + doc = NULL; + } catch (Inkscape::Extension::Input::open_failed &e) { + doc = NULL; + } + + if (doc) { + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + SPDocument *existing = desktop ? SP_DT_DOCUMENT(desktop) : NULL; + + if (existing && existing->virgin && replace_empty) { + // If the current desktop is empty, open the document there + desktop->change_document(doc); + } else { + if (!Inkscape::NSApplication::Application::getNewGui()) { + // create a whole new desktop and window + SPViewWidget *dtw = sp_desktop_widget_new(sp_document_namedview(doc, NULL)); + sp_create_window(dtw, TRUE); + desktop = static_cast(dtw->view); + } else { + desktop = Inkscape::NSApplication::Editor::createDesktop (doc); + } + } + + doc->virgin = FALSE; + // everyone who cares now has a reference, get rid of ours + sp_document_unref(doc); + // resize the window to match the document properties + // (this may be redundant for new windows... if so, move to the "virgin" + // section above) +#ifdef WITH_INKBOARD + desktop->whiteboard_session_manager()->setDesktop(desktop); +#endif + sp_namedview_window_from_document(desktop); + + if (add_to_recent) { + prefs_set_recent_file(SP_DOCUMENT_URI(doc), SP_DOCUMENT_NAME(doc)); + } + + return TRUE; + } else { + gchar *safeUri = Inkscape::IO::sanitizeString(uri); + gchar *text = g_strdup_printf(_("Failed to load the requested file %s"), safeUri); + sp_ui_error_dialog(text); + g_free(text); + g_free(safeUri); + return FALSE; + } +} + +/** + * Handle prompting user for "do you want to revert"? Revert on "OK" + */ +void +sp_file_revert_dialog() +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + g_assert(desktop != NULL); + + SPDocument *doc = SP_DT_DOCUMENT(desktop); + g_assert(doc != NULL); + + Inkscape::XML::Node *repr = sp_document_repr_root(doc); + g_assert(repr != NULL); + + gchar const *uri = doc->uri; + if (!uri) { + desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Document not saved yet. Cannot revert.")); + return; + } + + bool do_revert = true; + if (repr->attribute("sodipodi:modified") != NULL) { + gchar *text = g_strdup_printf(_("Changes will be lost! Are you sure you want to reload document %s?"), uri); + + bool response = desktop->warnDialog (text); + g_free(text); + + if (!response) { + do_revert = false; + } + } + + bool reverted; + if (do_revert) { + // Allow overwriting of current document. + doc->virgin = TRUE; + reverted = sp_file_open(uri,NULL); + } else { + reverted = false; + } + + if (reverted) { + desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Document reverted.")); + } else { + desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Document not reverted.")); + } +} + +void dump_str(gchar const *str, gchar const *prefix) +{ + Glib::ustring tmp; + tmp = prefix; + tmp += " ["; + size_t const total = strlen(str); + for (unsigned i = 0; i < total; i++) { + gchar *const tmp2 = g_strdup_printf(" %02x", (0x0ff & str[i])); + tmp += tmp2; + g_free(tmp2); + } + + tmp += "]"; + g_message(tmp.c_str()); +} + +void dump_ustr(Glib::ustring const &ustr) +{ + char const *cstr = ustr.c_str(); + char const *data = ustr.data(); + Glib::ustring::size_type const byteLen = ustr.bytes(); + Glib::ustring::size_type const dataLen = ustr.length(); + Glib::ustring::size_type const cstrLen = strlen(cstr); + + g_message(" size: %lu\n length: %lu\n bytes: %lu\n clen: %lu", + gulong(ustr.size()), gulong(dataLen), gulong(byteLen), gulong(cstrLen) ); + g_message( " ASCII? %s", (ustr.is_ascii() ? "yes":"no") ); + g_message( " UTF-8? %s", (ustr.validate() ? "yes":"no") ); + + try { + Glib::ustring tmp; + for (Glib::ustring::size_type i = 0; i < ustr.bytes(); i++) { + tmp = " "; + if (i < dataLen) { + Glib::ustring::value_type val = ustr.at(i); + gchar* tmp2 = g_strdup_printf( (((val & 0xff00) == 0) ? " %02x" : "%04x"), val ); + tmp += tmp2; + g_free( tmp2 ); + } else { + tmp += " "; + } + + if (i < byteLen) { + int val = (0x0ff & data[i]); + gchar *tmp2 = g_strdup_printf(" %02x", val); + tmp += tmp2; + g_free( tmp2 ); + if ( val > 32 && val < 127 ) { + tmp2 = g_strdup_printf( " '%c'", (gchar)val ); + tmp += tmp2; + g_free( tmp2 ); + } else { + tmp += " . "; + } + } else { + tmp += " "; + } + + if ( i < cstrLen ) { + int val = (0x0ff & cstr[i]); + gchar* tmp2 = g_strdup_printf(" %02x", val); + tmp += tmp2; + g_free(tmp2); + if ( val > 32 && val < 127 ) { + tmp2 = g_strdup_printf(" '%c'", (gchar) val); + tmp += tmp2; + g_free( tmp2 ); + } else { + tmp += " . "; + } + } else { + tmp += " "; + } + + g_message( tmp.c_str() ); + } + } catch (...) { + g_message("XXXXXXXXXXXXXXXXXX Exception" ); + } + g_message("---------------"); +} + +static Inkscape::UI::Dialogs::FileOpenDialog *openDialogInstance = NULL; + +/** + * Display an file Open selector. Open a document if OK is pressed. + * Can select single or multiple files for opening. + */ +void +sp_file_open_dialog(gpointer object, gpointer data) +{ + gchar *open_path2 = NULL; + + gchar *open_path = g_strdup(prefs_get_string_attribute("dialogs.open", "path")); + if (open_path != NULL && open_path[0] == '\0') { + g_free(open_path); + open_path = NULL; + } + if (open_path && !Inkscape::IO::file_test(open_path, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) { + g_free(open_path); + open_path = NULL; + } + if (open_path == NULL) + open_path = g_strconcat(g_get_home_dir(), G_DIR_SEPARATOR_S, NULL); + + if (!openDialogInstance) { + openDialogInstance = + Inkscape::UI::Dialogs::FileOpenDialog::create( + (char const *)open_path, + Inkscape::UI::Dialogs::SVG_TYPES, + (char const *)_("Select file to open")); + } + bool const success = openDialogInstance->show(); + gchar *fileName = ( success + ? g_strdup(openDialogInstance->getFilename()) + : NULL ); + Inkscape::Extension::Extension *selection = + openDialogInstance->getSelectionType(); + g_free(open_path); + + if (!success) return; + + // Code to check & open iff multiple files. + Glib::SListHandle flist=openDialogInstance->getFilenames(); + GSList *list=flist.data(); + + if(g_slist_length(list)>1) + { + gchar *fileName=NULL; + + while(list!=NULL) + { + +#ifdef INK_DUMP_FILENAME_CONV + g_message(" FileName: %s",(const char *)list->data); +#endif + + fileName=(gchar *)g_strdup((gchar *)list->data); + + if (fileName && !g_file_test(fileName,G_FILE_TEST_IS_DIR)) { + gsize bytesRead = 0; + gsize bytesWritten = 0; + GError *error = NULL; +#ifdef INK_DUMP_FILENAME_CONV + dump_str( fileName, "A file pre is " ); +#endif + gchar *newFileName = g_filename_to_utf8(fileName, + -1, + &bytesRead, + &bytesWritten, + &error); + if ( newFileName != NULL ) { + g_free(fileName); + fileName = newFileName; +#ifdef INK_DUMP_FILENAME_CONV + dump_str( fileName, "A file post is " ); +#endif + } else { + // TODO: bulia, please look over + g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" ); + } + +#ifdef INK_DUMP_FILENAME_CONV + g_message("Opening File %s\n",fileName); +#endif + + sp_file_open(fileName, selection); + g_free(fileName); + } + else + { + g_message("Cannot Open Directory %s\n",fileName); + } + + list=list->next; + } + + return; + } + + + if (fileName) { + gsize bytesRead = 0; + gsize bytesWritten = 0; + GError *error = NULL; +#ifdef INK_DUMP_FILENAME_CONV + dump_str( fileName, "A file pre is " ); +#endif + gchar *newFileName = g_filename_to_utf8(fileName, + -1, + &bytesRead, + &bytesWritten, + &error); + if ( newFileName != NULL ) { + g_free(fileName); + fileName = newFileName; +#ifdef INK_DUMP_FILENAME_CONV + dump_str( fileName, "A file post is " ); +#endif + } else { + // TODO: bulia, please look over + g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" ); + } + + + if ( !g_utf8_validate(fileName, -1, NULL) ) { + // TODO: bulia, please look over + g_warning( "INPUT FILENAME IS NOT UTF-8" ); + } + + + open_path = g_dirname(fileName); + open_path2 = g_strconcat(open_path, G_DIR_SEPARATOR_S, NULL); + prefs_set_string_attribute("dialogs.open", "path", open_path2); + g_free(open_path); + g_free(open_path2); + + sp_file_open(fileName, selection); + g_free(fileName); + } + + return; +} + + +/*###################### +## V A C U U M +######################*/ + +/** + * Remove unreferenced defs from the defs section of the document. + */ + + +void +sp_file_vacuum() +{ + SPDocument *doc = SP_ACTIVE_DOCUMENT; + + unsigned int diff = vacuum_document (doc); + + sp_document_done(doc); + + SPDesktop *dt = SP_ACTIVE_DESKTOP; + if (diff > 0) { + dt->messageStack()->flashF(Inkscape::NORMAL_MESSAGE, + ngettext("Removed %i unused definition in <defs>.", + "Removed %i unused definitions in <defs>.", + diff), + diff); + } else { + dt->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("No unused definitions in <defs>.")); + } +} + + + +/*###################### +## S A V E +######################*/ + +/** + * This 'save' function called by the others below + */ +static bool +file_save(SPDocument *doc, gchar const *uri, Inkscape::Extension::Extension *key, bool saveas) +{ + if (!doc || !uri) //Safety check + return FALSE; + + try { + Inkscape::Extension::save(key, doc, uri, + saveas && prefs_get_int_attribute("dialogs.save_as", "append_extension", 1), + saveas, TRUE); // save officially, with inkscape: attributes set + } catch (Inkscape::Extension::Output::no_extension_found &e) { + gchar *safeUri = Inkscape::IO::sanitizeString(uri); + gchar *text = g_strdup_printf(_("No Inkscape extension found to save document (%s). This may have been caused by an unknown filename extension."), safeUri); + SP_ACTIVE_DESKTOP->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Document not saved.")); + sp_ui_error_dialog(text); + g_free(text); + g_free(safeUri); + return FALSE; + } catch (Inkscape::Extension::Output::save_failed &e) { + gchar *safeUri = Inkscape::IO::sanitizeString(uri); + gchar *text = g_strdup_printf(_("File %s could not be saved."), safeUri); + SP_ACTIVE_DESKTOP->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Document not saved.")); + sp_ui_error_dialog(text); + g_free(text); + g_free(safeUri); + return FALSE; + } catch (Inkscape::Extension::Output::no_overwrite &e) { + return sp_file_save_dialog(doc); + } + + SP_ACTIVE_DESKTOP->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Document saved.")); + return TRUE; +} + +static Inkscape::UI::Dialogs::FileSaveDialog *saveDialogInstance = NULL; + +/** + * Display a SaveAs dialog. Save the document if OK pressed. + */ +gboolean +sp_file_save_dialog(SPDocument *doc) +{ + Inkscape::XML::Node *repr = sp_document_repr_root(doc); + gchar const *default_extension = NULL; + gchar *save_loc; + Inkscape::Extension::Output *extension; + gchar *save_path = NULL; + + default_extension = repr->attribute("inkscape:output_extension"); + if (default_extension == NULL) { + default_extension = prefs_get_string_attribute("dialogs.save_as", "default"); + } + //g_warning("%s: extension name: '%s'", __FUNCTION__, default_extension); + + if (doc->uri == NULL) { + int i = 1; + char const *filename_extension; + char *temp_filename; + + extension = dynamic_cast(Inkscape::Extension::db.get(default_extension)); + //g_warning("%s: extension ptr: 0x%x", __FUNCTION__, (unsigned int)extension); + if (extension == NULL) { + filename_extension = ".svg"; + } else { + filename_extension = extension->get_extension(); + } + + save_path = g_strdup(prefs_get_string_attribute("dialogs.save_as", "path")); + if (save_path != NULL && save_path[0] == '\0') { + g_free(save_path); + save_path = NULL; + } + if (save_path && !Inkscape::IO::file_test(save_path, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) { + g_free(save_path); + save_path = NULL; + } + if (save_path == NULL) + save_path = g_strdup(g_get_home_dir()); + temp_filename = g_strdup_printf(_("drawing%s"), filename_extension); + save_loc = g_build_filename(save_path, temp_filename, NULL); + g_free(temp_filename); + + while (Inkscape::IO::file_test(save_loc, G_FILE_TEST_EXISTS)) { + g_free(save_loc); + temp_filename = g_strdup_printf(_("drawing-%d%s"), i++, filename_extension); + save_loc = g_build_filename(save_path, temp_filename, NULL); + g_free(temp_filename); + } + } else { + save_loc = g_path_get_dirname(doc->uri); /* \todo should use a getter */ + } + + { // convert save_loc from utf-8 to locale + // is this needed any more, now that everything is handled in + // Inkscape::IO? + gsize bytesRead = 0; + gsize bytesWritten = 0; + GError* error = NULL; +#ifdef INK_DUMP_FILENAME_CONV + dump_str( save_loc, "B file pre is " ); +#endif + gchar* save_loc_local = g_filename_from_utf8( save_loc, -1, &bytesRead, &bytesWritten, &error); + + if ( save_loc_local != NULL ) { + g_free(save_loc); + save_loc = save_loc_local; +#ifdef INK_DUMP_FILENAME_CONV + dump_str( save_loc, "B file post is " ); +#endif + } else { + //g_warning( "Error converting save filename stored in the file to locale encoding."); + } + } + + if (!saveDialogInstance) { + saveDialogInstance = + Inkscape::UI::Dialogs::FileSaveDialog::create( + (char const *) save_loc, + Inkscape::UI::Dialogs::SVG_TYPES, + (char const *) _("Select file to save to"), + default_extension + ); + } // FIXME: else (i.e. if reshowing an already shown dialog) save_loc is not used, it thus always displays the previously opened dir + bool success = saveDialogInstance->show(); + char *fileName = ( success + ? g_strdup(saveDialogInstance->getFilename()) + : NULL ); + Inkscape::Extension::Extension *selectionType = + saveDialogInstance->getSelectionType(); + g_free(save_loc); + g_free(save_path); + if (!success) { + return success; + } + + if (fileName && *fileName) { + gsize bytesRead = 0; + gsize bytesWritten = 0; + GError *error = NULL; +#ifdef INK_DUMP_FILENAME_CONV + dump_str( fileName, "C file pre is " ); +#endif + gchar *newFileName = g_filename_to_utf8(fileName, + -1, + &bytesRead, + &bytesWritten, + &error); + if ( newFileName != NULL ) { + g_free(fileName); + fileName = newFileName; +#ifdef INK_DUMP_FILENAME_CONV + dump_str( fileName, "C file post is " ); +#endif + } else { + g_warning( "Error converting save filename to UTF-8." ); + } + + if (!g_utf8_validate(fileName, -1, NULL)) { + // TODO: bulia, please look over + g_warning( "The filename is not UTF-8." ); + } + + success = file_save(doc, fileName, selectionType, TRUE); + + if (success) { + prefs_set_recent_file(SP_DOCUMENT_URI(doc), SP_DOCUMENT_NAME(doc)); + } + + save_path = g_dirname(fileName); + prefs_set_string_attribute("dialogs.save_as", "path", save_path); + g_free(save_path); + + g_free(fileName); + return success; + } else { + return FALSE; + } +} + + +/** + * Save a document, displaying a SaveAs dialog if necessary. + */ +gboolean +sp_file_save_document(SPDocument *doc) +{ + gboolean success = TRUE; + + Inkscape::XML::Node *repr = sp_document_repr_root(doc); + + gchar const *fn = repr->attribute("sodipodi:modified"); + if (fn != NULL) { + if (doc->uri == NULL + || repr->attribute("inkscape:output_extension") == NULL) + { + return sp_file_save_dialog(doc); + } else { + fn = g_strdup(doc->uri); + gchar const *ext = repr->attribute("inkscape:output_extension"); + success = file_save(doc, fn, Inkscape::Extension::db.get(ext), FALSE); + g_free((void *) fn); + } + } else { + SP_ACTIVE_DESKTOP->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No changes need to be saved.")); + success = TRUE; + } + + return success; +} + + +/** + * Save a document. + */ +bool +sp_file_save(gpointer object, gpointer data) +{ + if (!SP_ACTIVE_DOCUMENT) + return false; + sp_namedview_document_from_window(SP_ACTIVE_DESKTOP); + return sp_file_save_document(SP_ACTIVE_DOCUMENT); +} + + +/** + * Save a document, always displaying the SaveAs dialog. + */ +bool +sp_file_save_as(gpointer object, gpointer data) +{ + if (!SP_ACTIVE_DOCUMENT) + return false; + sp_namedview_document_from_window(SP_ACTIVE_DESKTOP); + return sp_file_save_dialog(SP_ACTIVE_DOCUMENT); +} + + + + +/*###################### +## I M P O R T +######################*/ + +/** + * Import a resource. Called by sp_file_import() + */ +void +file_import(SPDocument *in_doc, gchar const *uri, Inkscape::Extension::Extension *key) +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + + //DEBUG_MESSAGE( fileImport, "file_import( in_doc:%p uri:[%s], key:%p", in_doc, uri, key ); + SPDocument *doc; + try { + doc = Inkscape::Extension::open(key, uri); + } catch (Inkscape::Extension::Input::no_extension_found &e) { + doc = NULL; + } catch (Inkscape::Extension::Input::open_failed &e) { + doc = NULL; + } + + if (doc != NULL) { + // the import extension has passed us a document, now we need to embed it into our document + if ( 0 ) { +// const gchar *docbase = (sp_repr_document_root( sp_repr_document( repr ))->attribute("sodipodi:docbase" ); + g_message(" settings uri [%s]", doc->uri ); + g_message(" base [%s]", doc->base ); + g_message(" name [%s]", doc->name ); + Inkscape::IO::fixupHrefs( doc, doc->base, TRUE ); + g_message(" mid-fixup"); + Inkscape::IO::fixupHrefs( doc, in_doc->base, TRUE ); + } + + // move imported defs to our document's defs + SPObject *in_defs = SP_DOCUMENT_DEFS(in_doc); + SPObject *defs = SP_DOCUMENT_DEFS(doc); + Inkscape::XML::Node *last_def = SP_OBJECT_REPR(in_defs)->lastChild(); + for (SPObject *child = sp_object_first_child(defs); + child != NULL; child = SP_OBJECT_NEXT(child)) + { + // FIXME: in case of id conflict, newly added thing will be re-ided and thus likely break a reference to it from imported stuff + SP_OBJECT_REPR(in_defs)->addChild(SP_OBJECT_REPR(child)->duplicate(), last_def); + } + + guint items_count = 0; + for (SPObject *child = sp_object_first_child(SP_DOCUMENT_ROOT(doc)); + child != NULL; child = SP_OBJECT_NEXT(child)) { + if (SP_IS_ITEM(child)) + items_count ++; + } + SPCSSAttr *style = sp_css_attr_from_object (SP_DOCUMENT_ROOT (doc)); + + SPObject *new_obj = NULL; + + if ((style && style->firstChild()) || items_count > 1) { + // create group + Inkscape::XML::Node *newgroup = sp_repr_new("svg:g"); + sp_repr_css_set (newgroup, style, "style"); + + for (SPObject *child = sp_object_first_child(SP_DOCUMENT_ROOT(doc)); child != NULL; child = SP_OBJECT_NEXT(child) ) { + if (SP_IS_ITEM(child)) { + Inkscape::XML::Node *newchild = SP_OBJECT_REPR(child)->duplicate(); + + // convert layers to groups; FIXME: add "preserve layers" mode where each layer + // from impot is copied to the same-named layer in host + newchild->setAttribute("inkscape:groupmode", NULL); + + newgroup->appendChild(newchild); + } + } + + if (desktop) { + // Add it to the current layer + new_obj = desktop->currentLayer()->appendChildRepr(newgroup); + } else { + // There's no desktop (command line run?) + // FIXME: For such cases we need a document:: method to return the current layer + new_obj = SP_DOCUMENT_ROOT(in_doc)->appendChildRepr(newgroup); + } + + Inkscape::GC::release(newgroup); + } else { + // just add one item + for (SPObject *child = sp_object_first_child(SP_DOCUMENT_ROOT(doc)); child != NULL; child = SP_OBJECT_NEXT(child) ) { + if (SP_IS_ITEM(child)) { + Inkscape::XML::Node *newitem = SP_OBJECT_REPR(child)->duplicate(); + newitem->setAttribute("inkscape:groupmode", NULL); + + if (desktop) { + // Add it to the current layer + new_obj = desktop->currentLayer()->appendChildRepr(newitem); + } else { + // There's no desktop (command line run?) + // FIXME: For such cases we need a document:: method to return the current layer + new_obj = SP_DOCUMENT_ROOT(in_doc)->appendChildRepr(newitem); + } + + } + } + } + + if (style) sp_repr_css_attr_unref (style); + + // select and move the imported item + if (new_obj && SP_IS_ITEM(new_obj)) { + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + selection->set(SP_ITEM(new_obj)); + + // To move the imported object, we must temporarily set the "transform pattern with + // object" option. + { + int const saved_pref = prefs_get_int_attribute("options.transform", "pattern", 1); + prefs_set_int_attribute("options.transform", "pattern", 1); + sp_document_ensure_up_to_date(SP_DT_DOCUMENT(desktop)); + NR::Point m( desktop->point() - selection->bounds().midpoint() ); + sp_selection_move_relative(selection, m); + prefs_set_int_attribute("options.transform", "pattern", saved_pref); + } + } + + sp_document_unref(doc); + sp_document_done(in_doc); + + } else { + gchar *text = g_strdup_printf(_("Failed to load the requested file %s"), uri); + sp_ui_error_dialog(text); + g_free(text); + } + + return; +} + + +static Inkscape::UI::Dialogs::FileOpenDialog *importDialogInstance = NULL; + +/** + * Display an Open dialog, import a resource if OK pressed. + */ +void +sp_file_import(GtkWidget *widget) +{ + SPDocument *doc = SP_ACTIVE_DOCUMENT; + if (!doc) + return; + + if (!importDialogInstance) { + importDialogInstance = + Inkscape::UI::Dialogs::FileOpenDialog::create( + (char const *)import_path, + Inkscape::UI::Dialogs::IMPORT_TYPES, + (char const *)_("Select file to import")); + } + bool success = importDialogInstance->show(); + char *fileName = ( success + ? g_strdup(importDialogInstance->getFilename()) + : NULL ); + Inkscape::Extension::Extension *selection = + importDialogInstance->getSelectionType(); + + if (!success) return; + if (fileName) { + gsize bytesRead = 0; + gsize bytesWritten = 0; + GError *error = NULL; +#ifdef INK_DUMP_FILENAME_CONV + dump_str( fileName, "D file pre is " ); +#endif + gchar *newFileName = g_filename_to_utf8( fileName, + -1, + &bytesRead, + &bytesWritten, + &error); + if ( newFileName != NULL ) { + g_free(fileName); + fileName = newFileName; +#ifdef INK_DUMP_FILENAME_CONV + dump_str( fileName, "D file post is " ); +#endif + } else { + // TODO: bulia, please look over + g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" ); + } + + + if (!g_utf8_validate(fileName, -1, NULL)) { + // TODO: bulia, please look over + g_warning( "INPUT FILENAME IS NOT UTF-8" ); + } + + g_free(import_path); + import_path = g_dirname(fileName); + if (import_path) import_path = g_strconcat(import_path, G_DIR_SEPARATOR_S, NULL); + + file_import(doc, fileName, selection); + g_free(fileName); + } + + return; +} + + + +/*###################### +## E X P O R T +######################*/ + +/** + * + */ +void +sp_file_export_dialog(void *widget) +{ + sp_export_dialog(); +} + +#include +#include + +struct SPEBP { + int width, height, sheight; + guchar r, g, b, a; + NRArenaItem *root; // the root arena item to show; it is assumed that all unneeded items are hidden + guchar *px; + unsigned (*status)(float, void *); + void *data; +}; + + +/** + * + */ +static int +sp_export_get_rows(guchar const **rows, int row, int num_rows, void *data) +{ + struct SPEBP *ebp = (struct SPEBP *) data; + + if (ebp->status) { + if (!ebp->status((float) row / ebp->height, ebp->data)) return 0; + } + + num_rows = MIN(num_rows, ebp->sheight); + num_rows = MIN(num_rows, ebp->height - row); + + /* Set area of interest */ + NRRectL bbox; + bbox.x0 = 0; + bbox.y0 = row; + bbox.x1 = ebp->width; + bbox.y1 = row + num_rows; + /* Update to renderable state */ + NRGC gc(NULL); + nr_matrix_set_identity(&gc.transform); + + nr_arena_item_invoke_update(ebp->root, &bbox, &gc, NR_ARENA_ITEM_STATE_ALL, NR_ARENA_ITEM_STATE_NONE); + + NRPixBlock pb; + nr_pixblock_setup_extern(&pb, NR_PIXBLOCK_MODE_R8G8B8A8N, + bbox.x0, bbox.y0, bbox.x1, bbox.y1, + ebp->px, 4 * ebp->width, FALSE, FALSE); + + for (int r = 0; r < num_rows; r++) { + guchar *p = NR_PIXBLOCK_PX(&pb) + r * pb.rs; + for (int c = 0; c < ebp->width; c++) { + *p++ = ebp->r; + *p++ = ebp->g; + *p++ = ebp->b; + *p++ = ebp->a; + } + } + + /* Render */ + nr_arena_item_invoke_render(ebp->root, &bbox, &pb, 0); + + for (int r = 0; r < num_rows; r++) { + rows[r] = NR_PIXBLOCK_PX(&pb) + r * pb.rs; + } + + nr_pixblock_release(&pb); + + return num_rows; +} + +/** +Hide all items which are not listed in list, recursively, skipping groups and defs +*/ +void +hide_other_items_recursively(SPObject *o, GSList *list, unsigned dkey) +{ + if (SP_IS_ITEM(o) + && !SP_IS_DEFS(o) + && !SP_IS_ROOT(o) + && !SP_IS_GROUP(o) + && !g_slist_find(list, o)) + { + sp_item_invoke_hide(SP_ITEM(o), dkey); + } + + // recurse + if (!g_slist_find(list, o)) { + for (SPObject *child = sp_object_first_child(o) ; child != NULL; child = SP_OBJECT_NEXT(child) ) { + hide_other_items_recursively(child, list, dkey); + } + } +} + + +/** + * Render the SVG drawing onto a PNG raster image, then save to + * a file. Returns TRUE if succeeded in writing the file, + * FALSE otherwise. + */ +int +sp_export_png_file(SPDocument *doc, gchar const *filename, + double x0, double y0, double x1, double y1, + unsigned width, unsigned height, + unsigned long bgcolor, + unsigned (*status)(float, void *), + void *data, bool force_overwrite, + GSList *items_only) +{ + int write_status = TRUE; + g_return_val_if_fail(doc != NULL, FALSE); + g_return_val_if_fail(filename != NULL, FALSE); + g_return_val_if_fail(width >= 1, FALSE); + g_return_val_if_fail(height >= 1, FALSE); + + if (!force_overwrite && !sp_ui_overwrite_file(filename)) { + return FALSE; + } + + sp_document_ensure_up_to_date(doc); + + /* Go to document coordinates */ + gdouble t = y0; + y0 = sp_document_height(doc) - y1; + y1 = sp_document_height(doc) - t; + + /* + * 1) a[0] * x0 + a[2] * y1 + a[4] = 0.0 + * 2) a[1] * x0 + a[3] * y1 + a[5] = 0.0 + * 3) a[0] * x1 + a[2] * y1 + a[4] = width + * 4) a[1] * x0 + a[3] * y0 + a[5] = height + * 5) a[1] = 0.0; + * 6) a[2] = 0.0; + * + * (1,3) a[0] * x1 - a[0] * x0 = width + * a[0] = width / (x1 - x0) + * (2,4) a[3] * y0 - a[3] * y1 = height + * a[3] = height / (y0 - y1) + * (1) a[4] = -a[0] * x0 + * (2) a[5] = -a[3] * y1 + */ + + NRMatrix affine; + affine.c[0] = width / (x1 - x0); + affine.c[1] = 0.0; + affine.c[2] = 0.0; + affine.c[3] = height / (y1 - y0); + affine.c[4] = -affine.c[0] * x0; + affine.c[5] = -affine.c[3] * y0; + + //SP_PRINT_MATRIX("SVG2PNG", &affine); + + struct SPEBP ebp; + ebp.width = width; + ebp.height = height; + ebp.r = NR_RGBA32_R(bgcolor); + ebp.g = NR_RGBA32_G(bgcolor); + ebp.b = NR_RGBA32_B(bgcolor); + ebp.a = NR_RGBA32_A(bgcolor); + + /* Create new arena */ + NRArena *arena = NRArena::create(); + unsigned dkey = sp_item_display_key_new(1); + + /* Create ArenaItems and set transform */ + ebp.root = sp_item_invoke_show(SP_ITEM(sp_document_root(doc)), arena, dkey, SP_ITEM_SHOW_DISPLAY); + nr_arena_item_set_transform(NR_ARENA_ITEM(ebp.root), NR::Matrix(&affine)); + + // We show all and then hide all items we don't want, instead of showing only requested items, + // because that would not work if the shown item references something in defs + if (items_only) { + hide_other_items_recursively(sp_document_root(doc), items_only, dkey); + } + + ebp.status = status; + ebp.data = data; + + if ((width < 256) || ((width * height) < 32768)) { + ebp.px = nr_pixelstore_64K_new(FALSE, 0); + ebp.sheight = 65536 / (4 * width); + write_status = sp_png_write_rgba_striped(filename, width, height, sp_export_get_rows, &ebp); + nr_pixelstore_64K_free(ebp.px); + } else { + ebp.px = nr_new(guchar, 4 * 64 * width); + ebp.sheight = 64; + write_status = sp_png_write_rgba_striped(filename, width, height, sp_export_get_rows, &ebp); + nr_free(ebp.px); + } + + // Hide items + sp_item_invoke_hide(SP_ITEM(sp_document_root(doc)), dkey); + + /* Free Arena and ArenaItem */ + nr_arena_item_unref(ebp.root); + nr_object_unref((NRObject *) arena); + return write_status; +} + + +/*###################### +## P R I N T +######################*/ + + +/** + * Print the current document, if any. + */ +void +sp_file_print() +{ + SPDocument *doc = SP_ACTIVE_DOCUMENT; + if (doc) + sp_print_document(doc, FALSE); +} + + +/** + * Print the current document, if any. Do not use + * the machine's print drivers. + */ +void +sp_file_print_direct() +{ + SPDocument *doc = SP_ACTIVE_DOCUMENT; + if (doc) + sp_print_document(doc, TRUE); +} + + +/** + * Display what the drawing would look like, if + * printed. + */ +void +sp_file_print_preview(gpointer object, gpointer data) +{ + + SPDocument *doc = SP_ACTIVE_DOCUMENT; + if (doc) + sp_print_preview_document(doc); + +} + +void Inkscape::IO::fixupHrefs( SPDocument *doc, const gchar *base, gboolean spns ) +{ + //g_message("Inkscape::IO::fixupHrefs( , [%s], )", base ); + + if ( 0 ) { + gchar const* things[] = { + "data:foo,bar", + "http://www.google.com/image.png", + "ftp://ssd.com/doo", + "/foo/dee/bar.svg", + "foo.svg", + "file:/foo/dee/bar.svg", + "file:///foo/dee/bar.svg", + "file:foo.svg", + "/foo/bar\xe1\x84\x92.svg", + "file:///foo/bar\xe1\x84\x92.svg", + "file:///foo/bar%e1%84%92.svg", + "/foo/bar%e1%84%92.svg", + "bar\xe1\x84\x92.svg", + "bar%e1%84%92.svg", + NULL + }; + g_message("+------"); + for ( int i = 0; things[i]; i++ ) + { + try + { + URI uri(things[i]); + gboolean isAbs = g_path_is_absolute( things[i] ); + gchar *str = uri.toString(); + g_message( "abs:%d isRel:%d scheme:[%s] path:[%s][%s] uri[%s] / [%s]", (int)isAbs, + (int)uri.isRelative(), + uri.getScheme(), + uri.getPath(), + uri.getOpaque(), + things[i], + str ); + g_free(str); + } + catch ( MalformedURIException err ) + { + dump_str( things[i], "MalformedURIException" ); + xmlChar *redo = xmlURIEscape((xmlChar const *)things[i]); + g_message(" gone from [%s] to [%s]", things[i], redo ); + if ( redo == NULL ) + { + URI again = URI::fromUtf8( things[i] ); + gboolean isAbs = g_path_is_absolute( things[i] ); + gchar *str = again.toString(); + g_message( "abs:%d isRel:%d scheme:[%s] path:[%s][%s] uri[%s] / [%s]", (int)isAbs, + (int)again.isRelative(), + again.getScheme(), + again.getPath(), + again.getOpaque(), + things[i], + str ); + g_free(str); + g_message(" ----"); + } + } + } + g_message("+------"); + } + + GSList const *images = sp_document_get_resource_list(doc, "image"); + for (GSList const *l = images; l != NULL; l = l->next) { + Inkscape::XML::Node *ir = SP_OBJECT_REPR(l->data); + + const gchar *href = ir->attribute("xlink:href"); + + // First try to figure out an absolute path to the asset + //g_message("image href [%s]", href ); + if (spns && !g_path_is_absolute(href)) { + const gchar *absref = ir->attribute("sodipodi:absref"); + const gchar *base_href = g_build_filename(base, href, NULL); + //g_message(" absr [%s]", absref ); + + if ( absref && Inkscape::IO::file_test(absref, G_FILE_TEST_EXISTS) && !Inkscape::IO::file_test(base_href, G_FILE_TEST_EXISTS)) + { + // only switch over if the absref is valid while href is not + href = absref; + //g_message(" copied absref to href"); + } + } + + // Once we have an absolute path, convert it relative to the new location + if (href && g_path_is_absolute(href)) { + const gchar *relname = sp_relative_path_from_path(href, base); + //g_message(" setting to [%s]", relname ); + ir->setAttribute("xlink:href", relname); + } +// TODO next refinement is to make the first choice keeping the relative path as-is if +// based on the new location it gives us a valid file. + } +} + + +/* + 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/file.h b/src/file.h new file mode 100644 index 000000000..af053f027 --- /dev/null +++ b/src/file.h @@ -0,0 +1,171 @@ +#ifndef __SP_FILE_H__ +#define __SP_FILE_H__ + +/* + * File/Print operations + * + * Authors: + * Lauris Kaplinski + * Chema Celorio + * + * Copyright (C) 1999-2002 Authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +#include "forward.h" +#include +#include + +/*###################### +## N E W +######################*/ + +/** + * Creates a new Inkscape document and window. + * Return value is a pointer to the newly created desktop. + */ +SPDesktop* sp_file_new (const gchar *templ); +SPDesktop* sp_file_new_default (void); + +/*###################### +## D E L E T E +######################*/ + +/** + * Close the document/view + */ +void sp_file_exit (void); + +/*###################### +## O P E N +######################*/ + +/** + * Opens a new file and window from the given URI + */ +bool sp_file_open( + const gchar *uri, Inkscape::Extension::Extension *key, + bool add_to_recent = true, bool replace_empty = true + ); + +/** + * Displays a file open dialog. Calls sp_file_open on + * an OK. + */ +void sp_file_open_dialog (gpointer object, gpointer data); + +/** + * Reverts file to disk-copy on "YES" + */ +void sp_file_revert_dialog (); + +/*###################### +## S A V E +######################*/ + +/** + * + */ +bool sp_file_save (gpointer object, gpointer data); + +/** + * Saves the given document. Displays a file select dialog + * to choose the new name. + */ +bool sp_file_save_as (gpointer object, gpointer data); + +/** + * Saves the given document. Displays a file select dialog + * if needed. + */ +gboolean sp_file_save_document (SPDocument *document); + +/* Do the saveas dialog with a document as the parameter */ +gboolean sp_file_save_dialog (SPDocument *doc); + + +/*###################### +## I M P O R T +######################*/ + +/** + * Displays a file selector dialog, to allow the + * user to import data into the current document. + */ +void sp_file_import (GtkWidget * widget); + +/** + * Imports a resource + */ +void file_import(SPDocument *in_doc, gchar const *uri, Inkscape::Extension::Extension *key); + +/*###################### +## E X P O R T +######################*/ + +/** + * Displays a "Save as" dialog for the user, with an + * additional type selection, to allow the user to export + * the a document as a given type. + */ +void sp_file_export_dialog (void *widget); + +/** + * Export the given document as a Portable Network Graphics (PNG) + * file. Returns FALSE if an error was encountered while writing + * the file, TRUE otherwise. + */ +int sp_export_png_file (SPDocument *doc, const gchar *filename, + double x0, double y0, double x1, double y1, + unsigned int width, unsigned int height, + unsigned long bgcolor, + unsigned int (*status) (float, void *), void *data, bool force_overwrite = false, GSList *items_only = NULL); + + +/*###################### +## P R I N T +######################*/ + +/* These functions are redundant now, but +would be useful as instance methods +*/ + +/** + * + */ +void sp_file_print (void); + +/** + * + */ +void sp_file_print_direct (void); + +/** + * + */ +void sp_file_print_preview (gpointer object, gpointer data); + +/*##################### +## U T I L I T Y +#####################*/ + +/** + * clean unused defs out of file + */ +void sp_file_vacuum (); + + +namespace Inkscape { +namespace IO { + +void fixupHrefs( SPDocument *doc, const gchar *uri, gboolean spns ); + +} +} + + +#endif diff --git a/src/fill-or-stroke.h b/src/fill-or-stroke.h new file mode 100644 index 000000000..d0b174dca --- /dev/null +++ b/src/fill-or-stroke.h @@ -0,0 +1,9 @@ +#ifndef SEEN_FILL_OR_STROKE_H +#define SEEN_FILL_OR_STROKE_H + +/** \file Definition of the FillOrStroke enum. */ + +/** \post STROKE == 0, FILL != 0. */ +enum FillOrStroke { STROKE = 0, FILL = 1 }; + +#endif /* !SEEN_FILL_OR_STROKE_H */ diff --git a/src/fixes.cpp b/src/fixes.cpp new file mode 100644 index 000000000..72498c448 --- /dev/null +++ b/src/fixes.cpp @@ -0,0 +1,193 @@ +/* + * + * This is the header file to include to fix any broken definitions or funcs + * + * $Id$ + * + * 2004 Kees Cook + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if defined(g_ascii_strtod) +/* + * until 2004-04-22, g_ascii_strtod could not handle having a locale-based + * decimal separator immediately following the number ("5,4" would + * parse to "5,4" instead of "5.0" in fr_FR) + * + * This is the corrected function, lifted from 1.107 gstrfuncs.c in glib + */ +extern "C" { +#include +#include +#include +#include +#include + +gdouble +fixed_g_ascii_strtod (const gchar *nptr, + gchar **endptr) +{ + gchar *fail_pos; + gdouble val; + struct lconv *locale_data; + const char *decimal_point; + int decimal_point_len; + const char *p, *decimal_point_pos; + const char *end = NULL; /* Silence gcc */ + + g_return_val_if_fail (nptr != NULL, 0); + + fail_pos = NULL; + + locale_data = localeconv (); + decimal_point = locale_data->decimal_point; + decimal_point_len = strlen (decimal_point); + + g_assert (decimal_point_len != 0); + + decimal_point_pos = NULL; + if (decimal_point[0] != '.' || + decimal_point[1] != 0) + { + p = nptr; + /* Skip leading space */ + while (g_ascii_isspace (*p)) + p++; + + /* Skip leading optional sign */ + if (*p == '+' || *p == '-') + p++; + + if (p[0] == '0' && + (p[1] == 'x' || p[1] == 'X')) + { + p += 2; + /* HEX - find the (optional) decimal point */ + + while (g_ascii_isxdigit (*p)) + p++; + + if (*p == '.') + { + decimal_point_pos = p++; + + while (g_ascii_isxdigit (*p)) + p++; + + if (*p == 'p' || *p == 'P') + p++; + if (*p == '+' || *p == '-') + p++; + while (g_ascii_isdigit (*p)) + p++; + } + } + else + { + while (g_ascii_isdigit (*p)) + p++; + + if (*p == '.') + { + decimal_point_pos = p++; + + while (g_ascii_isdigit (*p)) + p++; + + if (*p == 'e' || *p == 'E') + p++; + if (*p == '+' || *p == '-') + p++; + while (g_ascii_isdigit (*p)) + p++; + } + } + /* For the other cases, we need not convert the decimal point */ + end = p; + } + + /* Set errno to zero, so that we can distinguish zero results + and underflows */ + errno = 0; + + if (decimal_point_pos) + { + char *copy, *c; + + /* We need to convert the '.' to the locale specific decimal point */ + copy = (char*)g_malloc (end - nptr + 1 + decimal_point_len); + + c = copy; + memcpy (c, nptr, decimal_point_pos - nptr); + c += decimal_point_pos - nptr; + memcpy (c, decimal_point, decimal_point_len); + c += decimal_point_len; + memcpy (c, decimal_point_pos + 1, end - (decimal_point_pos + 1)); + c += end - (decimal_point_pos + 1); + *c = 0; + + val = strtod (copy, &fail_pos); + + if (fail_pos) + { + if (fail_pos - copy > decimal_point_pos - nptr) + fail_pos = (char *)nptr + (fail_pos - copy) - (decimal_point_len - 1); + else + fail_pos = (char *)nptr + (fail_pos - copy); + } + + g_free (copy); + + } + else if (decimal_point[0] != '.' || + decimal_point[1] != 0) + { + char *copy; + + copy = (char*)g_malloc (end - (char *)nptr + 1); + memcpy (copy, nptr, end - nptr); + *(copy + (end - (char *)nptr)) = 0; + + val = strtod (copy, &fail_pos); + + if (fail_pos) + { + fail_pos = (char *)nptr + (fail_pos - copy); + } + + g_free (copy); + } + else + { + val = strtod (nptr, &fail_pos); + } + + if (endptr) + *endptr = fail_pos; + + return val; +} +} + +#endif /* BROKEN_G_ASCII_STRTOD */ + + diff --git a/src/fontsize-expansion.cpp b/src/fontsize-expansion.cpp new file mode 100644 index 000000000..50169c3c2 --- /dev/null +++ b/src/fontsize-expansion.cpp @@ -0,0 +1,30 @@ +#include "libnr/nr-matrix-ops.h" + +/** + * Returns the distance from the transformed baseline to the + * transformation of a point at (0, 1); or 0 if \a m has no inverse. + */ +double +fontsize_expansion(NR::Matrix const &m) +{ + double const denom(hypot(m[0], m[1])); + if (!(denom > 1e-100)) { + return 0.; + } + double const numer(m.descrim2()); + if (!(numer > 1e-100)) { + return 0.; + } + return numer / denom; +} + +/* + 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 : diff --git a/src/fontsize-expansion.h b/src/fontsize-expansion.h new file mode 100644 index 000000000..2477cd49c --- /dev/null +++ b/src/fontsize-expansion.h @@ -0,0 +1,9 @@ +#ifndef INKSCAPE_FONTSIZE_EXPANSION_H +#define INKSCAPE_FONTSIZE_EXPANSION_H + +#include "libnr/nr-forward.h" + +double fontsize_expansion(NR::Matrix const &m); + + +#endif /* !INKSCAPE_FONTSIZE_EXPANSION_H */ diff --git a/src/forward.h b/src/forward.h new file mode 100644 index 000000000..ef05ea026 --- /dev/null +++ b/src/forward.h @@ -0,0 +1,208 @@ +#ifndef __FORWARD_H__ +#define __FORWARD_H__ + +/* + * Forward declarations of most used objects + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +/* Generic containers */ + +namespace Inkscape { +struct Application; +struct ApplicationClass; +} + +/* Editing window */ + +class SPDesktop; +class SPDesktopClass; + +class SPDesktopWidget; +class SPDesktopWidgetClass; + +GType sp_desktop_get_type (); + +class SPEventContext; +class SPEventContextClass; + +#define SP_TYPE_EVENT_CONTEXT (sp_event_context_get_type ()) +#define SP_EVENT_CONTEXT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), SP_TYPE_EVENT_CONTEXT, SPEventContext)) +#define SP_IS_EVENT_CONTEXT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), SP_TYPE_EVENT_CONTEXT)) + +GType sp_event_context_get_type (); + +/* Document tree */ + +class SPDocument; +class SPDocumentClass; + +#define SP_TYPE_DOCUMENT (sp_document_get_type ()) +#define SP_DOCUMENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_DOCUMENT, SPDocument)) +#define SP_IS_DOCUMENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_DOCUMENT)) + +GType sp_document_get_type (); + +/* Objects */ + +class SPObject; +class SPObjectClass; + +#define SP_TYPE_OBJECT (sp_object_get_type ()) +#define SP_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_OBJECT, SPObject)) +#define SP_IS_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_OBJECT)) + +GType sp_object_get_type (); + +class SPItem; +class SPItemClass; + +#define SP_TYPE_ITEM (sp_item_get_type ()) +#define SP_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_ITEM, SPItem)) +#define SP_IS_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_ITEM)) + +GType sp_item_get_type (); + +class SPGroup; +class SPGroupClass; + +class SPDefs; +class SPDefsClass; + +class SPRoot; +class SPRootClass; + +class SPNamedView; +class SPNamedViewClass; + +class SPGuide; +class SPGuideClass; + +class SPObjectGroup; +class SPObjectGroupClass; + +struct SPMarker; +struct SPMarkerClass; +class SPMarkerReference; + +class SPPath; +class SPPathClass; + +class SPShape; +class SPShapeClass; + +class SPPolygon; +class SPPolygonClass; + +class SPEllipse; +class SPEllipseClass; + +class SPCircle; +class SPCircleClass; + +class SPArc; +class SPArcClass; + +class SPChars; +class SPCharsClass; + +class SPText; +class SPTextClass; + +class SPTSpan; +class SPTSpanClass; + +class SPString; +class SPStringClass; + +class SPPaintServer; +class SPPaintServerClass; + +class SPStop; +class SPStopClass; + +class SPGradient; +class SPGradientClass; +class SPGradientReference; + +class SPLinearGradient; +class SPLinearGradientClass; + +class SPRadialGradient; +class SPRadialGradientClass; + +class SPPattern; + +class SPClipPath; +class SPClipPathClass; +class SPClipPathReference; + +class SPMaskReference; + +class SPAvoidRef; + +class SPAnchor; +class SPAnchorClass; + +/* Misc */ + +class ColorRGBA; + +class SPColorSpace; +class SPColor; + +class SPStyle; + +class SPEvent; + +class SPPrintContext; + +namespace Inkscape { +namespace UI { +namespace View { +class View; +}; +}; +}; + +class SPViewWidget; +class SPViewWidgetClass; + +class StopOnTrue; + +namespace Inkscape { +class URI; +class URIReference; +} + +struct box_solution; + + +/* verbs */ + +typedef int sp_verb_t; +namespace Inkscape { + class Verb; +} + +#endif /* !__FORWARD_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 : diff --git a/src/gc-alloc.h b/src/gc-alloc.h new file mode 100644 index 000000000..83811c0b3 --- /dev/null +++ b/src/gc-alloc.h @@ -0,0 +1,89 @@ +/* + * Inkscape::GC::Alloc - GC-aware STL allocator + * + * Copyright 2004 MenTaLguY + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * See the file COPYING for details. + * + */ + +#ifndef SEEN_INKSCAPE_GC_ALLOC_H +#define SEEN_INKSCAPE_GC_ALLOC_H + +#include +#include "gc-core.h" + +namespace Inkscape { + +namespace GC { + +template +class Alloc { +public: + typedef T value_type; + typedef T *pointer; + typedef T const *const_pointer; + typedef T &reference; + typedef T const &const_reference; + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + + template + struct rebind { typedef Alloc other; }; + + Alloc() {} + template Alloc(Alloc const &) {} + + pointer address(reference r) { return &r; } + const_pointer address(const_reference r) { return &r; } + + size_type max_size() const { + return std::numeric_limits::max() / sizeof(T); + } + + pointer allocate(size_type count, void const * =NULL) { + return static_cast(::operator new(count * sizeof(T), SCANNED, collect)); + } + + void construct(pointer p, const_reference value) { + new (static_cast(p)) T(value); + } + void destroy(pointer p) { p->~T(); } + + void deallocate(pointer p, size_type) { ::operator delete(p, GC); } +}; + +// allocators with the same collection policy are interchangable + +template +bool operator==(Alloc const &, Alloc const &) { + return collect1 == collect2; +} + +template +bool operator!=(Alloc const &, Alloc const &) { + return collect1 != collect2; +} + +} + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/gc-anchored.cpp b/src/gc-anchored.cpp new file mode 100644 index 000000000..02dff76d4 --- /dev/null +++ b/src/gc-anchored.cpp @@ -0,0 +1,39 @@ +/* + * Inkscape::GC::Anchored - base class for anchored GC-managed objects + * + * Authors: + * MenTaLguY + * + * Copyright (C) 2004 MenTaLguY + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "gc-anchored.h" + +namespace Inkscape { + +namespace GC { + +Anchored::Anchor *Anchored::_new_anchor() const { + return new Anchor(this); +} + +void Anchored::_free_anchor(Anchored::Anchor *anchor) const { + delete anchor; +} + +} + +} + +/* + 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 : diff --git a/src/gc-anchored.h b/src/gc-anchored.h new file mode 100644 index 000000000..f9d609074 --- /dev/null +++ b/src/gc-anchored.h @@ -0,0 +1,185 @@ +/** \file + * Inkscape::GC::Anchored - base class for anchored GC-managed objects + * + * Authors: + * MenTaLguY + * + * Copyright (C) 2004 MenTaLguY + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SEEN_INKSCAPE_GC_ANCHORED_H +#define SEEN_INKSCAPE_GC_ANCHORED_H + +#include +#include "gc-managed.h" + +namespace Inkscape { + +namespace GC { + +/** + * A base class for anchored objects. + * + * Objects are managed by our mark-and-sweep collector, but are anchored + * against garbage collection so long as their reference count is nonzero. + * + * Object and member destructors will not be called on destruction + * unless a subclass also inherits from Inkscape::GC::Finalized. + * + * New instances of anchored objects should be created using the C++ new + * operator. Under normal circumstances they should not be created on + * the stack. + * + * A newly created anchored object begins with a refcount of one, and + * will not be collected unless the refcount is zero. + * + * NOTE: If you create an object yourself, it is already anchored for + * you. You do not need to anchor it a second time. + * + * Note that a cycle involving an anchored object (with nonzero refcount) + * cannot be collected. To avoid this, don't increment refcounts for + * pointers between two garbage-collected objects. + * + * @see Inkscape::GC::Managed + * @see Inkscape::GC::Finalized + * @see Inkscape::GC::anchor + * @see Inkscape::GC::release + */ + +class Anchored { +public: + void anchor() const { + if (!_anchor) { + _anchor = _new_anchor(); + } + _anchor->refcount++; + } + + void release() const { + if (!--_anchor->refcount) { + _free_anchor(_anchor); + _anchor = NULL; + } + } + +protected: + Anchored() : _anchor(NULL) { anchor(); } // initial refcount of one + +private: + struct Anchor : public Managed { + Anchor() : refcount(0) {} + Anchor(Anchored const *obj) : refcount(0) { + base = Core::base(const_cast(obj)); + } + int refcount; + void *base; + }; + + mutable Anchor *_anchor; + + Anchor *_new_anchor() const; + void _free_anchor(Anchor *anchor) const; + + Anchored(Anchored const &); // no copy + void operator=(Anchored const &); // no assign +}; + +/** + * @brief Increments the reference count of a anchored object. + * + * This function template generates functions which take + * a reference to a anchored object of a given type, increment + * that object's reference count, and return a reference to the + * object of the same type as the function's parameter. + * + * @param m a reference to a anchored object + * + * @return the reference to the object + */ +template +static R &anchor(R &r) { + static_cast(const_cast(r)).anchor(); + return r; +} + +/** + * @brief Increments the reference count of a anchored object. + * + * This function template generates functions which take + * a pointer to a anchored object of a given type, increment + * that object's reference count, and return a pointer to the + * object of the same type as the function's parameter. + * + * @param m a pointer to anchored object + * + * @return the pointer to the object + */ +template +static R *anchor(R *r) { + static_cast(const_cast(r))->anchor(); + return r; +} + +/** + * @brief Decrements the reference count of a anchored object. + * + * This function template generates functions which take + * a reference to a anchored object of a given type, increment + * that object's reference count, and return a reference to the + * object of the same type as the function's parameter. + * + * The return value is safe to use since the object, even if + * its refcount has reached zero, will not actually be collected + * until there are no references to it in local variables or + * parameters. + * + * @param m a reference to a anchored object + * + * @return the reference to the object + */ +template +static R &release(R &r) { + static_cast(const_cast(r)).release(); + return r; +} + +/** + * @brief Decrements the reference count of a anchored object. + * + * This function template generates functions which take + * a pointer to a anchored object of a given type, increment + * that object's reference count, and return a pointer to the + * object of the same type as the function's parameter. + * + * The return value is safe to use since the object, even if + * its refcount has reached zero, will not actually be collected + * until there are no references to it in local variables or + * parameters. + * + * @param m a pointer to a anchored object + * + * @return the pointer to the object + */ +template +static R *release(R *r) { + static_cast(const_cast(r))->release(); + return r; +} + +} + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/gc-core.h b/src/gc-core.h new file mode 100644 index 000000000..cec617d42 --- /dev/null +++ b/src/gc-core.h @@ -0,0 +1,212 @@ +/* + * Inkscape::GC - Wrapper for Boehm GC + * + * Authors: + * MenTaLguY + * + * Copyright (C) 2004 MenTaLguY + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SEEN_INKSCAPE_GC_CORE_H +#define SEEN_INKSCAPE_GC_CORE_H + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#ifdef HAVE_GC_GC_H +# include +#else +# include +#endif +#include + +namespace Inkscape { +namespace GC { + +enum ScanPolicy { + SCANNED, + ATOMIC +}; + +enum CollectionPolicy { + AUTO, + MANUAL +}; + +enum Delete { + GC +}; + +typedef void (*CleanupFunc)(void *mem, void *data); + +struct Ops { + void (*do_init)(); + void *(*malloc)(std::size_t size); + void *(*malloc_atomic)(std::size_t size); + void *(*malloc_uncollectable)(std::size_t size); + void *(*malloc_atomic_uncollectable)(std::size_t size); + void *(*base)(void *ptr); + void (*register_finalizer_ignore_self)(void *base, + CleanupFunc func, void *data, + CleanupFunc *old_func, + void **old_data); + int (*general_register_disappearing_link)(void **p_ptr, + void *base); + int (*unregister_disappearing_link)(void **p_ptr); + std::size_t (*get_heap_size)(); + std::size_t (*get_free_bytes)(); + void (*gcollect)(); + void (*enable)(); + void (*disable)(); + void (*free)(void *ptr); +}; + +struct Core { +public: + static void init(); + static inline void *malloc(std::size_t size) { + return _ops.malloc(size); + } + static inline void *malloc_atomic(std::size_t size) { + return _ops.malloc_atomic(size); + } + static inline void *malloc_uncollectable(std::size_t size) { + return _ops.malloc_uncollectable(size); + } + static inline void *malloc_atomic_uncollectable(std::size_t size) { + return _ops.malloc_atomic_uncollectable(size); + } + static inline void *base(void *ptr) { + return _ops.base(ptr); + } + static inline void register_finalizer_ignore_self(void *base, + CleanupFunc func, + void *data, + CleanupFunc *old_func, + void **old_data) + { + return _ops.register_finalizer_ignore_self(base, func, data, + old_func, old_data); + } + static inline int general_register_disappearing_link(void **p_ptr, + void *base) + { + return _ops.general_register_disappearing_link(p_ptr, base); + } + static inline int unregister_disappearing_link(void **p_ptr) { + return _ops.unregister_disappearing_link(p_ptr); + } + static inline std::size_t get_heap_size() { + return _ops.get_heap_size(); + } + static inline std::size_t get_free_bytes() { + return _ops.get_free_bytes(); + } + static inline void gcollect() { + _ops.gcollect(); + } + static inline void enable() { + _ops.enable(); + } + static inline void disable() { + _ops.disable(); + } + static inline void free(void *ptr) { + return _ops.free(ptr); + } +private: + static Ops _ops; +}; + +inline void init() { + Core::init(); +} + +} +} + +inline void *operator new(std::size_t size, + Inkscape::GC::ScanPolicy scan, + Inkscape::GC::CollectionPolicy collect, + Inkscape::GC::CleanupFunc cleanup=NULL, + void *data=NULL) +throw(std::bad_alloc) +{ + using namespace Inkscape::GC; + + void *mem; + if ( collect == AUTO ) { + if ( scan == SCANNED ) { + mem = Core::malloc(size); + } else { + mem = Core::malloc_atomic(size); + } + } else { + if ( scan == SCANNED ) { + mem = Core::malloc_uncollectable(size); + } else { + mem = Core::malloc_atomic_uncollectable(size); + } + } + if (!mem) { + throw std::bad_alloc(); + } + if (cleanup) { + Core::register_finalizer_ignore_self(mem, cleanup, data, NULL, NULL); + } + return mem; +} + +inline void *operator new(std::size_t size, + Inkscape::GC::ScanPolicy scan, + Inkscape::GC::CleanupFunc cleanup=NULL, + void *data=NULL) +throw(std::bad_alloc) +{ + return operator new(size, scan, Inkscape::GC::AUTO, cleanup, data); +} + +inline void *operator new[](std::size_t size, + Inkscape::GC::ScanPolicy scan, + Inkscape::GC::CollectionPolicy collect, + Inkscape::GC::CleanupFunc cleanup=NULL, + void *data=NULL) +throw(std::bad_alloc) +{ + return operator new(size, scan, collect, cleanup, data); +} + +inline void *operator new[](std::size_t size, + Inkscape::GC::ScanPolicy scan, + Inkscape::GC::CleanupFunc cleanup=NULL, + void *data=NULL) +throw(std::bad_alloc) +{ + return operator new[](size, scan, Inkscape::GC::AUTO, cleanup, data); +} + +inline void operator delete(void *mem, Inkscape::GC::Delete) { + Inkscape::GC::Core::free(mem); +} + +inline void operator delete[](void *mem, Inkscape::GC::Delete) { + operator delete(mem, Inkscape::GC::GC); +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/gc-finalized.h b/src/gc-finalized.h new file mode 100644 index 000000000..a31155653 --- /dev/null +++ b/src/gc-finalized.h @@ -0,0 +1,155 @@ +/* + * Inkscape::GC::Finalized - mixin for GC-managed objects with non-trivial + * destructors + * + * Copyright 2004 MenTaLguY + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * See the file COPYING for details. + * + */ + +#ifndef SEEN_INKSCAPE_GC_FINALIZED_H +#define SEEN_INKSCAPE_GC_FINALIZED_H + +#include +#include "gc-core.h" + +namespace Inkscape { + +namespace GC { + +/* @brief a mix-in ensuring that a object's destructor will get called before + * the garbage collector destroys it + * + * Normally, the garbage collector does not call destructors before destroying + * an object. On construction, this "mix-in" will register a finalizer + * function to call destructors before derived objects are destroyed. + * + * This works pretty well, with the following caveats: + * + * 1. The garbage collector uses strictly topologically-ordered + * finalization; if objects with finalizers reference each other + * directly or indirectly, the collector will refuse to finalize (and + * therefor free) them. You'll see a warning on the console if this + * happens. + * + * The best way to limit this effect is to only make "leaf" objects + * (i.e. those that don't point to other finalizaable objects) + * finalizable, or if the object also derives from GC::Managed<>, + * use GC::Managed<>::clearOnceInaccessible to register those links + * to be cleared once the object is made inacecssible (and before it's + * finalized). + * + * In a tree structure that has parent links and finalized nodes, + * you will almost always want to do this with the parent links + * if you can't avoid having them. + * + * @see Inkscape::GC::Managed<>::clearOnceInaccessible + * @see Inkscape::GC::Managed<>::cancelClearOnceInacessible + * + * 2. Because there is no guarantee when the collector will destroy + * objects, there is no guarantee when the destructor will get called. + * + * It may not get called until the very end of the program, or never. + * + * 3. If allocated in arrays, only the first object in the array will + * have its destructor called, unless you make other arrangements by + * registering your own finalizer instead. + * + * 4. Similarly, making multiple GC::Finalized-derived objects members + * of a non-finalized but garbage-collected object generally won't + * work unless you take care of registering finalizers yourself. + * + * [n.b., by "member", I mean an actual by-value-member of a type that + * derives from GC::Finalized, not simply a member that's a pointer or a + * reference to such a type] + * + */ +class Finalized { +public: + Finalized() { + void *base=Core::base(this); + if (base) { // only if we are managed by the collector + CleanupFunc old_cleanup; + void *old_data; + + // the finalization callback needs to know the value of 'this' + // to call the destructor, but registering a real pointer to + // ourselves would pin us forever and prevent us from being + // finalized; instead we use an offset-from-base-address + + Core::register_finalizer_ignore_self(base, _invoke_dtor, + _offset(base, this), + &old_cleanup, &old_data); + + if (old_cleanup) { + // If there was already a finalizer registered for our + // base address, there are two main possibilities: + // + // 1. one of our members is also a GC::Finalized and had + // already registered a finalizer -- keep ours, since + // it will call that member's destructor, too + // + // 2. someone else registered a finalizer and we will have + // to trust that they will call the destructor -- put + // the existing finalizer back + // + // It's also possible that a member's constructor was called + // after ours (e.g. via placement new). Don't do that. + + if ( old_cleanup != _invoke_dtor ) { + Core::register_finalizer_ignore_self(base, + old_cleanup, old_data, + NULL, NULL); + } + } + } + } + + virtual ~Finalized() { + // make sure the destructor won't get invoked twice + Core::register_finalizer_ignore_self(Core::base(this), + NULL, NULL, NULL, NULL); + } + +private: + /// invoke the destructor for an object given a base and offset pair + static void _invoke_dtor(void *base, void *offset) { + _unoffset(base, offset)->~Finalized(); + } + + /// turn 'this' pointer into an offset-from-base-address (stored as void *) + static void *_offset(void *base, Finalized *self) { + return reinterpret_cast( + reinterpret_cast(self) - reinterpret_cast(base) + ); + } + /// reconstitute 'this' given an offset-from-base-address in a void * + static Finalized *_unoffset(void *base, void *offset) { + return reinterpret_cast( + reinterpret_cast(base) + + reinterpret_cast(offset) + ); + } +}; + +} + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/gc-managed.h b/src/gc-managed.h new file mode 100644 index 000000000..fc13b2513 --- /dev/null +++ b/src/gc-managed.h @@ -0,0 +1,84 @@ +/** \file + * Inkscape::GC::Managed - base class for GC-managed objects + * + * Copyright 2004 MenTaLguY + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * See the file COPYING for details. + * + */ + +#ifndef SEEN_INKSCAPE_GC_MANAGED_H +#define SEEN_INKSCAPE_GC_MANAGED_H + +#include "gc-core.h" + +namespace Inkscape { + +namespace GC { + +/** @brief A base class for objects for whom the normal new and delete + * operators should use the garbage-collected allocator + */ +template +class Managed { +public: + /** @brief Registers a pointer to be cleared when this object becomes + * inaccessible. + */ + template + void clearOnceInaccessible(T **p_ptr) { + Core::general_register_disappearing_link( + reinterpret_cast(p_ptr), Core::base(this) + ); + } + + /** @brief Cancels the registration of a pointer, so it will not be + * cleared when this object becomes inacessible. + */ + template + void cancelClearOnceInaccessible(T **p_ptr) { + Core::unregister_disappearing_link( + reinterpret_cast(p_ptr) + ); + } + + void *operator new(std::size_t size, + ScanPolicy scan=default_scan, + CollectionPolicy collect=default_collect) + throw (std::bad_alloc) + { + return ::operator new(size, scan, collect); + } + + void *operator new[](std::size_t size, + ScanPolicy scan=default_scan, + CollectionPolicy collect=default_collect) + throw (std::bad_alloc) + { + return ::operator new[](size, scan, collect); + } + + void operator delete(void *p) { return ::operator delete(p, GC); } +}; + +} + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/gc.cpp b/src/gc.cpp new file mode 100644 index 000000000..7333b4641 --- /dev/null +++ b/src/gc.cpp @@ -0,0 +1,279 @@ +/* + * Inkscape::GC - Wrapper for Boehm GC + * + * Authors: + * MenTaLguY + * + * Copyright (C) 2004 MenTaLguY + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "gc-core.h" +#include +#include + +namespace Inkscape { +namespace GC { + +namespace { + +void display_warning(char *msg, GC_word arg) { + g_warning(msg, arg); +} + +void do_init() { + GC_no_dls = 1; + GC_all_interior_pointers = 1; + GC_finalize_on_demand = 0; + + GC_INIT(); + + GC_set_warn_proc(&display_warning); +} + +void *debug_malloc(std::size_t size) { + return GC_debug_malloc(size, GC_EXTRAS); +} + +void *debug_malloc_atomic(std::size_t size) { + return GC_debug_malloc_atomic(size, GC_EXTRAS); +} + +void *debug_malloc_uncollectable(std::size_t size) { + return GC_debug_malloc_uncollectable(size, GC_EXTRAS); +} + +void *debug_malloc_atomic_uncollectable(std::size_t size) { + return GC_debug_malloc_uncollectable(size, GC_EXTRAS); +} + +std::ptrdiff_t compute_debug_base_fixup() { + char *base=reinterpret_cast(GC_debug_malloc(1, GC_EXTRAS)); + char *real_base=reinterpret_cast(GC_base(base)); + GC_debug_free(base); + return base - real_base; +} + +inline std::ptrdiff_t const &debug_base_fixup() { + static std::ptrdiff_t fixup=compute_debug_base_fixup(); + return fixup; +} + +void *debug_base(void *ptr) { + char *base=reinterpret_cast(GC_base(ptr)); + return base + debug_base_fixup(); +} + +int debug_general_register_disappearing_link(void **p_ptr, void *base) { + char *real_base=reinterpret_cast(base) - debug_base_fixup(); + return GC_general_register_disappearing_link(p_ptr, real_base); +} + +void dummy_do_init() {} + +void *dummy_base(void *) { return NULL; } + +void dummy_register_finalizer(void *, CleanupFunc, void *, + CleanupFunc *old_func, void **old_data) +{ + if (old_func) { + *old_func = NULL; + } + if (old_data) { + *old_data = NULL; + } +} + +int dummy_general_register_disappearing_link(void **, void *) { return false; } + +int dummy_unregister_disappearing_link(void **link) { return false; } + +std::size_t dummy_get_heap_size() { return 0; } + +std::size_t dummy_get_free_bytes() { return 0; } + +void dummy_gcollect() {} + +void dummy_enable() {} + +void dummy_disable() {} + +Ops enabled_ops = { + &do_init, + &GC_malloc, + &GC_malloc_atomic, + &GC_malloc_uncollectable, + &GC_malloc_atomic_uncollectable, + &GC_base, + &GC_register_finalizer_ignore_self, + &GC_general_register_disappearing_link, + &GC_unregister_disappearing_link, + &GC_get_heap_size, + &GC_get_free_bytes, + &GC_gcollect, + &GC_enable, + &GC_disable, + &GC_free +}; + +Ops debug_ops = { + &do_init, + &debug_malloc, + &debug_malloc_atomic, + &debug_malloc_uncollectable, + &debug_malloc_atomic_uncollectable, + &debug_base, + &GC_debug_register_finalizer_ignore_self, + &debug_general_register_disappearing_link, + &GC_unregister_disappearing_link, + &GC_get_heap_size, + &GC_get_free_bytes, + &GC_gcollect, + &GC_enable, + &GC_disable, + &GC_debug_free +}; + +Ops disabled_ops = { + &dummy_do_init, + &std::malloc, + &std::malloc, + &std::malloc, + &std::malloc, + &dummy_base, + &dummy_register_finalizer, + &dummy_general_register_disappearing_link, + &dummy_unregister_disappearing_link, + &dummy_get_heap_size, + &dummy_get_free_bytes, + &dummy_gcollect, + &dummy_enable, + &dummy_disable, + &std::free +}; + +class InvalidGCModeError : public std::runtime_error { +public: + InvalidGCModeError(const char *mode) + : runtime_error(std::string("Unknown GC mode \"") + mode + "\"") + {} +}; + +Ops const &get_ops() throw (InvalidGCModeError) { + char *mode_string=std::getenv("_INKSCAPE_GC"); + if (mode_string) { + if (!std::strcmp(mode_string, "enable")) { + return enabled_ops; + } else if (!std::strcmp(mode_string, "debug")) { + return debug_ops; + } else if (!std::strcmp(mode_string, "disable")) { + return disabled_ops; + } else { + throw InvalidGCModeError(mode_string); + } + } else { + return enabled_ops; + } +} + +void die_because_not_initialized() { + g_error("Attempt to use GC allocator before call to Inkscape::GC::init()"); +} + +void *stub_malloc(std::size_t) { + die_because_not_initialized(); + return NULL; +} + +void *stub_base(void *) { + die_because_not_initialized(); + return NULL; +} + +void stub_register_finalizer_ignore_self(void *, CleanupFunc, void *, + CleanupFunc *, void **) +{ + die_because_not_initialized(); +} + +int stub_general_register_disappearing_link(void **, void *) { + die_because_not_initialized(); + return 0; +} + +int stub_unregister_disappearing_link(void **) { + die_because_not_initialized(); + return 0; +} + +std::size_t stub_get_heap_size() { + die_because_not_initialized(); + return 0; +} + +std::size_t stub_get_free_bytes() { + die_because_not_initialized(); + return 0; +} + +void stub_gcollect() { + die_because_not_initialized(); +} + +void stub_enable() { + die_because_not_initialized(); +} + +void stub_disable() { + die_because_not_initialized(); +} + +void stub_free(void *) { + die_because_not_initialized(); +} + +} + +Ops Core::_ops = { + NULL, + &stub_malloc, + &stub_malloc, + &stub_malloc, + &stub_malloc, + &stub_base, + &stub_register_finalizer_ignore_self, + &stub_general_register_disappearing_link, + &stub_unregister_disappearing_link, + &stub_get_heap_size, + &stub_get_free_bytes, + &stub_gcollect, + &stub_enable, + &stub_disable, + &stub_free +}; + +void Core::init() { + try { + _ops = get_ops(); + } catch (InvalidGCModeError &e) { + g_warning("%s; enabling normal collection", e.what()); + _ops = enabled_ops; + } + + _ops.do_init(); +} + +} +} + +/* + 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 : diff --git a/src/geom.cpp b/src/geom.cpp new file mode 100644 index 000000000..2749d6d7d --- /dev/null +++ b/src/geom.cpp @@ -0,0 +1,172 @@ +/** + * \file src/geom.cpp + * \brief Various geometrical calculations. + */ + +#ifdef HAVE_CONFIG_H +# include +#endif +#include "geom.h" +#include + +/** + * Finds the intersection of the two (infinite) lines + * defined by the points p such that dot(n0, p) == d0 and dot(n1, p) == d1. + * + * If the two lines intersect, then \a result becomes their point of + * intersection; otherwise, \a result remains unchanged. + * + * This function finds the intersection of the two lines (infinite) + * defined by n0.X = d0 and x1.X = d1. The algorithm is as follows: + * To compute the intersection point use kramer's rule: + * \verbatim + * convert lines to form + * ax + by = c + * dx + ey = f + * + * ( + * e.g. a = (x2 - x1), b = (y2 - y1), c = (x2 - x1)*x1 + (y2 - y1)*y1 + * ) + * + * In our case we use: + * a = n0.x d = n1.x + * b = n0.y e = n1.y + * c = d0 f = d1 + * + * so: + * + * adx + bdy = cd + * adx + aey = af + * + * bdy - aey = cd - af + * (bd - ae)y = cd - af + * + * y = (cd - af)/(bd - ae) + * + * repeat for x and you get: + * + * x = (fb - ce)/(bd - ae) \endverbatim + * + * If the denominator (bd-ae) is 0 then the lines are parallel, if the + * numerators are then 0 then the lines coincide. + * + * \todo Why not use existing but outcommented code below + * (HAVE_NEW_INTERSECTOR_CODE)? + */ +IntersectorKind intersector_line_intersection(NR::Point const &n0, double const d0, + NR::Point const &n1, double const d1, + NR::Point &result) +{ + double denominator = dot(NR::rot90(n0), n1); + double X = n1[NR::Y] * d0 - + n0[NR::Y] * d1; + /* X = (-d1, d0) dot (n0[Y], n1[Y]) */ + + if (denominator == 0) { + if ( X == 0 ) { + return COINCIDENT; + } else { + return PARALLEL; + } + } + + double Y = n0[NR::X] * d1 - + n1[NR::X] * d0; + + result = NR::Point(X, Y) / denominator; + + return INTERSECTS; +} + + + + +/* + New code which we are not yet using +*/ +#ifdef HAVE_NEW_INTERSECTOR_CODE + + +/* ccw exists as a building block */ +static int +sp_intersector_ccw(const NR::Point p0, const NR::Point p1, const NR::Point p2) +/* Determine which way a set of three points winds. */ +{ + NR::Point d1 = p1 - p0; + NR::Point d2 = p2 - p0; +/* compare slopes but avoid division operation */ + double c = dot(NR::rot90(d1), d2); + if(c > 0) + return +1; // ccw - do these match def'n in header? + if(c < 0) + return -1; // cw + + /* Colinear [or NaN]. Decide the order. */ + if ( ( d1[0] * d2[0] < 0 ) || + ( d1[1] * d2[1] < 0 ) ) { + return -1; // p2 < p0 < p1 + } else if ( dot(d1,d1) < dot(d2,d2) ) { + return +1; // p0 <= p1 < p2 + } else { + return 0; // p0 <= p2 <= p1 + } +} + +/** Determine whether two line segments intersect. This doesn't find + the point of intersection, use the line_intersect function above, + or the segment_intersection interface below. + + \pre neither segment is zero-length; i.e. p00 != p01 and p10 != p11. + */ +static bool +sp_intersector_segment_intersectp(NR::Point const &p00, NR::Point const &p01, + NR::Point const &p10, NR::Point const &p11) +{ + g_return_val_if_fail(p00 != p01, false); + g_return_val_if_fail(p10 != p11, false); + + /* true iff ( (the p1 segment straddles the p0 infinite line) + * and (the p0 segment straddles the p1 infinite line) ). */ + return ((sp_intersector_ccw(p00,p01, p10) + *sp_intersector_ccw(p00, p01, p11)) <=0 ) + && + ((sp_intersector_ccw(p10,p11, p00) + *sp_intersector_ccw(p10, p11, p01)) <=0 ); +} + + +/** Determine whether \& where two line segments intersect. + + If the two segments don't intersect, then \a result remains unchanged. + + \pre neither segment is zero-length; i.e. p00 != p01 and p10 != p11. +**/ +static sp_intersector_kind +sp_intersector_segment_intersect(NR::Point const &p00, NR::Point const &p01, + NR::Point const &p10, NR::Point const &p11, + NR::Point &result) +{ + if(sp_intersector_segment_intersectp(p00, p01, p10, p11)) { + NR::Point n0 = (p00 - p01).ccw(); + double d0 = dot(n0,p00); + + NR::Point n1 = (p10 - p11).ccw(); + double d1 = dot(n1,p10); + return sp_intersector_line_intersection(n0, d0, n1, d1, result); + } else { + return no_intersection; + } +} + +#endif /* end yet-unused HAVE_NEW_INTERSECTOR_CODE code */ + +/* + 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 : diff --git a/src/geom.h b/src/geom.h new file mode 100644 index 000000000..d00a4e37c --- /dev/null +++ b/src/geom.h @@ -0,0 +1,30 @@ +/** + * \file geom.h + * \brief Various geometrical calculations + * + * Authors: + * Nathan Hurst + * + * Copyright (C) 1999-2002 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "libnr/nr-forward.h" + +enum IntersectorKind { + INTERSECTS = 0, + PARALLEL, + COINCIDENT, + NO_INTERSECTION +}; + +/* Define here various primatives, such as line, line segment, circle, bezier path etc. */ + + + +/* intersectors */ + +IntersectorKind intersector_line_intersection(NR::Point const &n0, double const d0, + NR::Point const &n1, double const d1, + NR::Point &result); diff --git a/src/gnuc-attribute.h b/src/gnuc-attribute.h new file mode 100644 index 000000000..4b3e57632 --- /dev/null +++ b/src/gnuc-attribute.h @@ -0,0 +1,11 @@ +/** + * Define _gnuc_attribute(blah) as synonym for __attribute__(blah) on gcc, + * or as nothing on other compilers. + */ +#ifndef _gnuc_attribute +# ifdef __GNUC__ +# define _gnuc_attribute(_attr) __attribute__(_attr) +# else +# define _gnuc_attribute(_attr) +# endif +#endif diff --git a/src/gradient-chemistry.cpp b/src/gradient-chemistry.cpp new file mode 100644 index 000000000..aa77ea0ad --- /dev/null +++ b/src/gradient-chemistry.cpp @@ -0,0 +1,1133 @@ +#define __SP_GRADIENT_CHEMISTRY_C__ + +/* + * Various utility methods for gradients + * + * Authors: + * Lauris Kaplinski + * bulia byak + * + * Copyright (C) 2001-2005 authors + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + +#include "style.h" +#include "document-private.h" +#include "desktop-style.h" + +#include "sp-gradient-reference.h" +#include "sp-linear-gradient.h" +#include "sp-radial-gradient.h" +#include "sp-stop.h" +#include "widgets/gradient-vector.h" + +#include "sp-text.h" +#include "sp-tspan.h" +#include +#include "xml/repr.h" +#include "svg/svg.h" + + +// Terminology: +// +// "vector" is a gradient that has stops but not position coords. It can be referenced by one or +// more privates. Objects should not refer to it directly. It has no radial/linear distinction. +// +// "private" is a gradient that has no stops but has position coords (e.g. center, radius etc for a +// radial). It references a vector for the actual colors. Each private is only used by one +// object. It is either linear or radial. + +static void sp_gradient_repr_set_link(Inkscape::XML::Node *repr, SPGradient *gr); +static void sp_item_repr_set_style_gradient(Inkscape::XML::Node *repr, gchar const *property, + SPGradient *gr, bool recursive); + +SPGradient * +sp_gradient_ensure_vector_normalized(SPGradient *gr) +{ + g_return_val_if_fail(gr != NULL, NULL); + g_return_val_if_fail(SP_IS_GRADIENT(gr), NULL); + + /* If we are already normalized vector, just return */ + if (gr->state == SP_GRADIENT_STATE_VECTOR) return gr; + /* Fail, if we have wrong state set */ + if (gr->state != SP_GRADIENT_STATE_UNKNOWN) { + g_warning("file %s: line %d: Cannot normalize private gradient to vector (%s)", __FILE__, __LINE__, SP_OBJECT_ID(gr)); + return NULL; + } + + /* First make sure we have vector directly defined (i.e. gr has its own stops) */ + if (!gr->has_stops) { + /* We do not have stops ourselves, so flatten stops as well */ + sp_gradient_ensure_vector(gr); + g_assert(gr->vector.built); + // this adds stops from gr->vector as children to gr + sp_gradient_repr_write_vector (gr); + } + + /* If gr hrefs some other gradient, remove the href */ + if (gr->ref->getObject()) { + /* We are hrefing someone, so require flattening */ + SP_OBJECT(gr)->updateRepr(((SPObject *) gr)->repr, SP_OBJECT_WRITE_EXT | SP_OBJECT_WRITE_ALL); + sp_gradient_repr_set_link(SP_OBJECT_REPR(gr), NULL); + } + + /* Everything is OK, set state flag */ + gr->state = SP_GRADIENT_STATE_VECTOR; + return gr; +} + +/** + * Creates new private gradient for the given vector + */ + +static SPGradient * +sp_gradient_get_private_normalized(SPDocument *document, SPGradient *vector, SPGradientType type) +{ + g_return_val_if_fail(document != NULL, NULL); + g_return_val_if_fail(vector != NULL, NULL); + g_return_val_if_fail(SP_IS_GRADIENT(vector), NULL); + g_return_val_if_fail(SP_GRADIENT_HAS_STOPS(vector), NULL); + + SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document); + + // create a new private gradient of the requested type + Inkscape::XML::Node *repr; + if (type == SP_GRADIENT_TYPE_LINEAR) { + repr = sp_repr_new("svg:linearGradient"); + } else { + repr = sp_repr_new("svg:radialGradient"); + } + + // privates are garbage-collectable + repr->setAttribute("inkscape:collect", "always"); + + // link to vector + sp_gradient_repr_set_link(repr, vector); + + /* Append the new private gradient to defs */ + SP_OBJECT_REPR(defs)->appendChild(repr); + Inkscape::GC::release(repr); + + // get corresponding object + SPGradient *gr = (SPGradient *) document->getObjectByRepr(repr); + g_assert(gr != NULL); + g_assert(SP_IS_GRADIENT(gr)); + + return gr; +} + +/** +Count how many times gr is used by the styles of o and its descendants +*/ +guint +count_gradient_hrefs(SPObject *o, SPGradient *gr) +{ + if (!o) + return 1; + + guint i = 0; + + SPStyle *style = SP_OBJECT_STYLE(o); + if (style + && style->fill.type == SP_PAINT_TYPE_PAINTSERVER + && SP_IS_GRADIENT(SP_STYLE_FILL_SERVER(style)) + && SP_GRADIENT(SP_STYLE_FILL_SERVER(style)) == gr) + { + i ++; + } + if (style + && style->stroke.type == SP_PAINT_TYPE_PAINTSERVER + && SP_IS_GRADIENT(SP_STYLE_STROKE_SERVER(style)) + && SP_GRADIENT(SP_STYLE_STROKE_SERVER(style)) == gr) + { + i ++; + } + + for (SPObject *child = sp_object_first_child(o); + child != NULL; child = SP_OBJECT_NEXT(child)) { + i += count_gradient_hrefs(child, gr); + } + + return i; +} + + +/** + * If gr has other users, create a new private; also check if gr links to vector, relink if not + */ +SPGradient * +sp_gradient_fork_private_if_necessary(SPGradient *gr, SPGradient *vector, + SPGradientType type, SPObject *o) +{ + g_return_val_if_fail(gr != NULL, NULL); + g_return_val_if_fail(SP_IS_GRADIENT(gr), NULL); + + // Orphaned gradient, no vector with stops at the end of the line; this used to be an assert + // but i think we should not abort on this - maybe just write a validity warning into some sort + // of log + if (!vector || !SP_GRADIENT_HAS_STOPS(vector)) + return (gr); + + // user is the object that uses this gradient; normally it's item but for tspans, we + // check its ancestor text so that tspans don't get different gradients from their + // texts. + SPObject *user = o; + while (SP_IS_TSPAN(user)) { + user = SP_OBJECT_PARENT(user); + } + + // Check the number of uses of the gradient within this object; + // if we are private and there are no other users, + if (SP_OBJECT_HREFCOUNT(gr) <= count_gradient_hrefs(user, gr)) { + // check vector + if ( gr != vector && gr->ref->getObject() != vector ) { + /* our href is not the vector, and vector is different from gr; relink */ + sp_gradient_repr_set_link(SP_OBJECT_REPR(gr), vector); + } + return gr; + } + + SPDocument *doc = SP_OBJECT_DOCUMENT(gr); + SPObject *defs = SP_DOCUMENT_DEFS(doc); + + if ((gr->has_stops) || + (gr->state != SP_GRADIENT_STATE_UNKNOWN) || + (SP_OBJECT_PARENT(gr) != SP_OBJECT(defs)) || + (SP_OBJECT_HREFCOUNT(gr) > 1)) { + // we have to clone a fresh new private gradient for the given vector + + // create an empty one + SPGradient *gr_new = sp_gradient_get_private_normalized(doc, vector, type); + + // copy all the attributes to it + Inkscape::XML::Node *repr_new = SP_OBJECT_REPR(gr_new); + Inkscape::XML::Node *repr = SP_OBJECT_REPR(gr); + repr_new->setAttribute("gradientUnits", repr->attribute("gradientUnits")); + repr_new->setAttribute("gradientTransform", repr->attribute("gradientTransform")); + repr_new->setAttribute("spreadMethod", repr->attribute("spreadMethod")); + if (SP_IS_RADIALGRADIENT(gr)) { + repr_new->setAttribute("cx", repr->attribute("cx")); + repr_new->setAttribute("cy", repr->attribute("cy")); + repr_new->setAttribute("fx", repr->attribute("fx")); + repr_new->setAttribute("fy", repr->attribute("fy")); + repr_new->setAttribute("r", repr->attribute("r")); + } else { + repr_new->setAttribute("x1", repr->attribute("x1")); + repr_new->setAttribute("y1", repr->attribute("y1")); + repr_new->setAttribute("x2", repr->attribute("x2")); + repr_new->setAttribute("y2", repr->attribute("y2")); + } + + return gr_new; + } else { + return gr; + } +} + +SPGradient * +sp_gradient_fork_vector_if_necessary (SPGradient *gr) +{ + if (SP_OBJECT_HREFCOUNT(gr) > 1) { + SPDocument *document = SP_OBJECT_DOCUMENT(gr); + + Inkscape::XML::Node *repr = SP_OBJECT_REPR (gr)->duplicate(); + SP_OBJECT_REPR (SP_DOCUMENT_DEFS (document))->addChild(repr, NULL); + SPGradient *gr_new = (SPGradient *) document->getObjectByRepr(repr); + gr_new = sp_gradient_ensure_vector_normalized (gr_new); + Inkscape::GC::release(repr); + return gr_new; + } + return gr; +} + +/** + * Convert an item's gradient to userspace _without_ preserving coords, setting them to defaults + * instead. No forking or reapplying is done because this is only called for newly created privates. + * @return The new gradient. + */ +SPGradient * +sp_gradient_reset_to_userspace (SPGradient *gr, SPItem *item) +{ + Inkscape::XML::Node *repr = SP_OBJECT_REPR(gr); + + // calculate the bbox of the item + sp_document_ensure_up_to_date(SP_OBJECT_DOCUMENT(item)); + NR::Rect const bbox = item->invokeBbox(NR::identity()); // we need "true" bbox without item_i2d_affine + + NR::Coord const width = bbox.dimensions()[NR::X]; + NR::Coord const height = bbox.dimensions()[NR::Y]; + g_assert(width > 0 && height > 0); + + NR::Point const center = bbox.midpoint(); + + if (SP_IS_RADIALGRADIENT(gr)) { + sp_repr_set_svg_double(repr, "cx", center[NR::X]); + sp_repr_set_svg_double(repr, "cy", center[NR::Y]); + sp_repr_set_svg_double(repr, "fx", center[NR::X]); + sp_repr_set_svg_double(repr, "fy", center[NR::Y]); + sp_repr_set_svg_double(repr, "r", width/2); + + // we want it to be elliptic, not circular + NR::Matrix squeeze = NR::Matrix (NR::translate (-center)) * + NR::Matrix (NR::scale(1, height/width)) * + NR::Matrix (NR::translate (center)); + + gr->gradientTransform = squeeze; + { + gchar c[256]; + if (sp_svg_transform_write(c, 256, gr->gradientTransform)) { + SP_OBJECT_REPR(gr)->setAttribute("gradientTransform", c); + } else { + SP_OBJECT_REPR(gr)->setAttribute("gradientTransform", NULL); + } + } + } else { + sp_repr_set_svg_double(repr, "x1", (center - NR::Point(width/2, 0))[NR::X]); + sp_repr_set_svg_double(repr, "y1", (center - NR::Point(width/2, 0))[NR::Y]); + sp_repr_set_svg_double(repr, "x2", (center + NR::Point(width/2, 0))[NR::X]); + sp_repr_set_svg_double(repr, "y2", (center + NR::Point(width/2, 0))[NR::Y]); + } + + // set the gradientUnits + repr->setAttribute("gradientUnits", "userSpaceOnUse"); + + return gr; +} + +/** + * Convert an item's gradient to userspace if necessary, also fork it if necessary. + * @return The new gradient. + */ +SPGradient * +sp_gradient_convert_to_userspace(SPGradient *gr, SPItem *item, gchar const *property) +{ + g_return_val_if_fail(SP_IS_GRADIENT(gr), NULL); + + // First, fork it if it is shared + gr = sp_gradient_fork_private_if_necessary(gr, sp_gradient_get_vector(gr, FALSE), + SP_IS_RADIALGRADIENT(gr) ? SP_GRADIENT_TYPE_RADIAL : SP_GRADIENT_TYPE_LINEAR, SP_OBJECT(item)); + + if (gr->units == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) { + + Inkscape::XML::Node *repr = SP_OBJECT_REPR(gr); + + // calculate the bbox of the item + sp_document_ensure_up_to_date(SP_OBJECT_DOCUMENT(item)); + NR::Rect const bbox = item->invokeBbox(NR::identity()); // we need "true" bbox without item_i2d_affine + NR::Matrix bbox2user(bbox.dimensions()[NR::X], 0, + 0, bbox.dimensions()[NR::Y], + bbox.min()[NR::X], bbox.min()[NR::Y]); + + /* skew is the additional transform, defined by the proportions of the item, that we need + * to apply to the gradient in order to work around this weird bit from SVG 1.1 + * (http://www.w3.org/TR/SVG11/pservers.html#LinearGradients): + * + * When gradientUnits="objectBoundingBox" and gradientTransform is the identity + * matrix, the stripes of the linear gradient are perpendicular to the gradient + * vector in object bounding box space (i.e., the abstract coordinate system where + * (0,0) is at the top/left of the object bounding box and (1,1) is at the + * bottom/right of the object bounding box). When the object's bounding box is not + * square, the stripes that are conceptually perpendicular to the gradient vector + * within object bounding box space will render non-perpendicular relative to the + * gradient vector in user space due to application of the non-uniform scaling + * transformation from bounding box space to user space. + */ + NR::Matrix skew = bbox2user; + double exp = skew.expansion(); + skew[0] /= exp; + skew[1] /= exp; + skew[2] /= exp; + skew[3] /= exp; + skew[4] = 0; + skew[5] = 0; + + // apply skew to the gradient + gr->gradientTransform = skew; + { + gchar c[256]; + if (sp_svg_transform_write(c, 256, gr->gradientTransform)) { + SP_OBJECT_REPR(gr)->setAttribute("gradientTransform", c); + } else { + SP_OBJECT_REPR(gr)->setAttribute("gradientTransform", NULL); + } + } + + // Matrix to convert points to userspace coords; postmultiply by inverse of skew so + // as to cancel it out when it's applied to the gradient during rendering + NR::Matrix point_convert = bbox2user * skew.inverse(); + + if (SP_IS_RADIALGRADIENT(gr)) { + SPRadialGradient *rg = SP_RADIALGRADIENT(gr); + + // original points in the bbox coords + NR::Point c_b = NR::Point(rg->cx.computed, rg->cy.computed); + NR::Point f_b = NR::Point(rg->fx.computed, rg->fy.computed); + double r_b = rg->r.computed; + + // converted points in userspace coords + NR::Point c_u = c_b * point_convert; + NR::Point f_u = f_b * point_convert; + double r_u = r_b * point_convert.expansion(); + + sp_repr_set_svg_double(repr, "cx", c_u[NR::X]); + sp_repr_set_svg_double(repr, "cy", c_u[NR::Y]); + sp_repr_set_svg_double(repr, "fx", f_u[NR::X]); + sp_repr_set_svg_double(repr, "fy", f_u[NR::Y]); + sp_repr_set_svg_double(repr, "r", r_u); + + } else { + SPLinearGradient *lg = SP_LINEARGRADIENT(gr); + + NR::Point p1_b = NR::Point(lg->x1.computed, lg->y1.computed); + NR::Point p2_b = NR::Point(lg->x2.computed, lg->y2.computed); + + NR::Point p1_u = p1_b * point_convert; + NR::Point p2_u = p2_b * point_convert; + + sp_repr_set_svg_double(repr, "x1", p1_u[NR::X]); + sp_repr_set_svg_double(repr, "y1", p1_u[NR::Y]); + sp_repr_set_svg_double(repr, "x2", p2_u[NR::X]); + sp_repr_set_svg_double(repr, "y2", p2_u[NR::Y]); + } + + // set the gradientUnits + repr->setAttribute("gradientUnits", "userSpaceOnUse"); + } + + // apply the gradient to the item (may be necessary if we forked it); not recursive + // generally because grouped items will be taken care of later (we're being called + // from sp_item_adjust_paint_recursive); however text and all its children should all + // refer to one gradient, hence the recursive call for text (because we can't/don't + // want to access tspans and set gradients on them separately) + if (SP_IS_TEXT(item)) + sp_item_repr_set_style_gradient(SP_OBJECT_REPR(item), property, gr, true); + else + sp_item_repr_set_style_gradient(SP_OBJECT_REPR(item), property, gr, false); + + return gr; +} + +void +sp_gradient_transform_multiply(SPGradient *gradient, NR::Matrix postmul, bool set) +{ + if (set) { + gradient->gradientTransform = postmul; + } else { + gradient->gradientTransform *= postmul; // fixme: get gradient transform by climbing to hrefs? + } + gradient->gradientTransform_set = TRUE; + + gchar c[256]; + if (sp_svg_transform_write(c, 256, gradient->gradientTransform)) { + SP_OBJECT_REPR(gradient)->setAttribute("gradientTransform", c); + } else { + SP_OBJECT_REPR(gradient)->setAttribute("gradientTransform", NULL); + } +} + +SPGradient * +sp_item_gradient (SPItem *item, bool fill_or_stroke) +{ + SPStyle *style = SP_OBJECT_STYLE (item); + SPGradient *gradient = NULL; + + if (fill_or_stroke) { + if (style && (style->fill.type == SP_PAINT_TYPE_PAINTSERVER)) { + SPObject *server = SP_OBJECT_STYLE_FILL_SERVER(item); + if (SP_IS_GRADIENT (server)) { + gradient = SP_GRADIENT (server); + } + } + } else { + if (style && (style->stroke.type == SP_PAINT_TYPE_PAINTSERVER)) { + SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER(item); + if (SP_IS_GRADIENT (server)) { + gradient = SP_GRADIENT (server); + } + } + } + + return gradient; +} + + +SPStop* +sp_first_stop(SPGradient *gradient) +{ + for (SPObject *ochild = sp_object_first_child(gradient); ochild != NULL; ochild = SP_OBJECT_NEXT(ochild)) { + if (SP_IS_STOP (ochild)) + return SP_STOP(ochild); + } + return NULL; +} + +SPStop* +sp_prev_stop(SPStop *stop, SPGradient *gradient) +{ + if (sp_object_first_child(SP_OBJECT(gradient)) == SP_OBJECT(stop)) + return NULL; + SPObject *found = NULL; + for ( SPObject *ochild = sp_object_first_child(SP_OBJECT(gradient)) ; ochild != NULL ; ochild = SP_OBJECT_NEXT(ochild) ) { + if (SP_IS_STOP (ochild)) { + found = ochild; + } + if (SP_OBJECT_NEXT(ochild) == SP_OBJECT(stop) || SP_OBJECT(ochild) == SP_OBJECT(stop)) { + break; + } + } + return SP_STOP(found); +} + +SPStop* +sp_next_stop(SPStop *stop) +{ + for (SPObject *ochild = SP_OBJECT_NEXT(stop); ochild != NULL; ochild = SP_OBJECT_NEXT(ochild)) { + if (SP_IS_STOP (ochild)) + return SP_STOP(ochild); + } + return NULL; +} + +SPStop* +sp_last_stop(SPGradient *gradient) +{ + for (SPStop *stop = sp_first_stop (gradient); stop != NULL; stop = sp_next_stop (stop)) { + if (sp_next_stop (stop) == NULL) + return stop; + } + return NULL; +} + + +void +sp_item_gradient_edit_stop (SPItem *item, guint point_num, bool fill_or_stroke) +{ + SPGradient *gradient = sp_item_gradient (item, fill_or_stroke); + + if (!gradient || !SP_IS_GRADIENT(gradient)) + return; + + SPGradient *vector = sp_gradient_get_vector (gradient, false); + switch (point_num) { + case POINT_LG_P1: + case POINT_RG_CENTER: + case POINT_RG_FOCUS: + { + GtkWidget *dialog = sp_gradient_vector_editor_new (vector, sp_first_stop (vector)); + gtk_widget_show (dialog); + } + break; + + case POINT_LG_P2: + case POINT_RG_R1: + case POINT_RG_R2: + { + GtkWidget *dialog = sp_gradient_vector_editor_new (vector, sp_last_stop (vector)); + gtk_widget_show (dialog); + } + break; + default: + break; + } +} + +guint32 +sp_item_gradient_stop_query_style (SPItem *item, guint point_num, bool fill_or_stroke) +{ + SPGradient *gradient = sp_item_gradient (item, fill_or_stroke); + + if (!gradient || !SP_IS_GRADIENT(gradient)) + return 0; + + SPGradient *vector = sp_gradient_get_vector (gradient, false); + + if (!vector) // orphan! + return 0; // what else to do? + + switch (point_num) { + case POINT_LG_P1: + case POINT_RG_CENTER: + case POINT_RG_FOCUS: + { + SPStop *first = sp_first_stop (vector); + if (first) { + return sp_stop_get_rgba32(first); + } + } + break; + + case POINT_LG_P2: + case POINT_RG_R1: + case POINT_RG_R2: + { + SPStop *last = sp_last_stop (vector); + if (last) { + return sp_stop_get_rgba32(last); + } + } + break; + default: + break; + } + return 0; +} + +void +sp_item_gradient_stop_set_style (SPItem *item, guint point_num, bool fill_or_stroke, SPCSSAttr *stop) +{ + SPGradient *gradient = sp_item_gradient (item, fill_or_stroke); + + if (!gradient || !SP_IS_GRADIENT(gradient)) + return; + + SPGradient *vector = sp_gradient_get_vector (gradient, false); + + if (!vector) // orphan! + return; + + vector = sp_gradient_fork_vector_if_necessary (vector); + if ( gradient != vector && gradient->ref->getObject() != vector ) { + sp_gradient_repr_set_link(SP_OBJECT_REPR(gradient), vector); + } + + switch (point_num) { + case POINT_LG_P1: + case POINT_RG_CENTER: + case POINT_RG_FOCUS: + { + SPStop *first = sp_first_stop (vector); + if (first) { + sp_repr_css_change (SP_OBJECT_REPR (first), stop, "style"); + } + } + break; + + case POINT_LG_P2: + case POINT_RG_R1: + case POINT_RG_R2: + { + SPStop *last = sp_last_stop (vector); + if (last) { + sp_repr_css_change (SP_OBJECT_REPR (last), stop, "style"); + } + } + break; + default: + break; + } +} + +void +sp_item_gradient_reverse_vector (SPItem *item, bool fill_or_stroke) +{ + SPGradient *gradient = sp_item_gradient (item, fill_or_stroke); + if (!gradient || !SP_IS_GRADIENT(gradient)) + return; + + SPGradient *vector = sp_gradient_get_vector (gradient, false); + if (!vector) // orphan! + return; + + vector = sp_gradient_fork_vector_if_necessary (vector); + if ( gradient != vector && gradient->ref->getObject() != vector ) { + sp_gradient_repr_set_link(SP_OBJECT_REPR(gradient), vector); + } + + GSList *child_reprs = NULL; + GSList *child_objects = NULL; + std::vector offsets; + for (SPObject *child = sp_object_first_child(vector); + child != NULL; child = SP_OBJECT_NEXT(child)) { + child_reprs = g_slist_prepend (child_reprs, SP_OBJECT_REPR(child)); + child_objects = g_slist_prepend (child_objects, child); + offsets.push_back(sp_repr_get_double_attribute(SP_OBJECT_REPR(child), "offset", 0)); + } + + GSList *child_copies = NULL; + for (GSList *i = child_reprs; i != NULL; i = i->next) { + Inkscape::XML::Node *repr = (Inkscape::XML::Node *) i->data; + child_copies = g_slist_append (child_copies, repr->duplicate()); + } + + + for (GSList *i = child_objects; i != NULL; i = i->next) { + SPObject *child = SP_OBJECT (i->data); + child->deleteObject(); + } + + std::vector::iterator iter = offsets.end() - 1; + for (GSList *i = child_copies; i != NULL; i = i->next) { + Inkscape::XML::Node *copy = (Inkscape::XML::Node *) i->data; + vector->appendChildRepr(copy); + sp_repr_set_svg_double (copy, "offset", 1 - *iter); + iter --; + Inkscape::GC::release(copy); + } + + g_slist_free (child_reprs); + g_slist_free (child_copies); + g_slist_free (child_objects); +} + + + +/** +Set the position of point point_num of the gradient applied to item (either fill_or_stroke) to +p_w (in desktop coordinates). Write_repr if you want the change to become permanent. +*/ +void +sp_item_gradient_set_coords (SPItem *item, guint point_num, NR::Point p_w, bool fill_or_stroke, bool write_repr, bool scale) +{ + SPGradient *gradient = sp_item_gradient (item, fill_or_stroke); + + if (!gradient || !SP_IS_GRADIENT(gradient)) + return; + + gradient = sp_gradient_convert_to_userspace (gradient, item, fill_or_stroke? "fill" : "stroke"); + + NR::Matrix i2d = sp_item_i2d_affine (item); + NR::Point p = p_w * i2d.inverse(); + p *= (gradient->gradientTransform).inverse(); + // now p is in gradient's original coordinates + + Inkscape::XML::Node *repr = SP_OBJECT_REPR(gradient); + + if (SP_IS_LINEARGRADIENT(gradient)) { + SPLinearGradient *lg = SP_LINEARGRADIENT(gradient); + switch (point_num) { + case POINT_LG_P1: + if (scale) { + lg->x2.computed += (lg->x1.computed - p[NR::X]); + lg->y2.computed += (lg->y1.computed - p[NR::Y]); + } + lg->x1.computed = p[NR::X]; + lg->y1.computed = p[NR::Y]; + if (write_repr) { + if (scale) { + sp_repr_set_svg_double(repr, "x2", lg->x2.computed); + sp_repr_set_svg_double(repr, "y2", lg->y2.computed); + } + sp_repr_set_svg_double(repr, "x1", lg->x1.computed); + sp_repr_set_svg_double(repr, "y1", lg->y1.computed); + } else { + SP_OBJECT (gradient)->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case POINT_LG_P2: + if (scale) { + lg->x1.computed += (lg->x2.computed - p[NR::X]); + lg->y1.computed += (lg->y2.computed - p[NR::Y]); + } + lg->x2.computed = p[NR::X]; + lg->y2.computed = p[NR::Y]; + if (write_repr) { + if (scale) { + sp_repr_set_svg_double(repr, "x1", lg->x1.computed); + sp_repr_set_svg_double(repr, "y1", lg->y1.computed); + } + sp_repr_set_svg_double(repr, "x2", lg->x2.computed); + sp_repr_set_svg_double(repr, "y2", lg->y2.computed); + } else { + SP_OBJECT (gradient)->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + default: + break; + } + } else if (SP_IS_RADIALGRADIENT(gradient)) { + + SPRadialGradient *rg = SP_RADIALGRADIENT(gradient); + NR::Point c (rg->cx.computed, rg->cy.computed); + NR::Point c_w = c * gradient->gradientTransform * i2d; // now in desktop coords + if ((point_num == POINT_RG_R1 || point_num == POINT_RG_R2) && NR::L2 (p_w - c_w) < 1e-3) { + // prevent setting a radius too close to the center + return; + } + NR::Matrix new_transform; + bool transform_set = false; + + switch (point_num) { + case POINT_RG_CENTER: + rg->fx.computed = p[NR::X] + (rg->fx.computed - rg->cx.computed); + rg->fy.computed = p[NR::Y] + (rg->fy.computed - rg->cy.computed); + rg->cx.computed = p[NR::X]; + rg->cy.computed = p[NR::Y]; + if (write_repr) { + sp_repr_set_svg_double(repr, "fx", rg->fx.computed); + sp_repr_set_svg_double(repr, "fy", rg->fy.computed); + sp_repr_set_svg_double(repr, "cx", rg->cx.computed); + sp_repr_set_svg_double(repr, "cy", rg->cy.computed); + } else { + SP_OBJECT (gradient)->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case POINT_RG_FOCUS: + rg->fx.computed = p[NR::X]; + rg->fy.computed = p[NR::Y]; + if (write_repr) { + sp_repr_set_svg_double(repr, "fx", rg->fx.computed); + sp_repr_set_svg_double(repr, "fy", rg->fy.computed); + } else { + SP_OBJECT (gradient)->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case POINT_RG_R1: + { + NR::Point r1_w = (c + NR::Point(rg->r.computed, 0)) * gradient->gradientTransform * i2d; + double r1_angle = NR::atan2(r1_w - c_w); + double move_angle = NR::atan2(p_w - c_w) - r1_angle; + double move_stretch = NR::L2(p_w - c_w) / NR::L2(r1_w - c_w); + + NR::Matrix move = NR::Matrix (NR::translate (-c_w)) * + NR::Matrix (NR::rotate(-r1_angle)) * + NR::Matrix (NR::scale(move_stretch, scale? move_stretch : 1)) * + NR::Matrix (NR::rotate(r1_angle)) * + NR::Matrix (NR::rotate(move_angle)) * + NR::Matrix (NR::translate (c_w)); + + new_transform = gradient->gradientTransform * i2d * move * i2d.inverse(); + transform_set = true; + + break; + } + case POINT_RG_R2: + { + NR::Point r2_w = (c + NR::Point(0, -rg->r.computed)) * gradient->gradientTransform * i2d; + double r2_angle = NR::atan2(r2_w - c_w); + double move_angle = NR::atan2(p_w - c_w) - r2_angle; + double move_stretch = NR::L2(p_w - c_w) / NR::L2(r2_w - c_w); + + NR::Matrix move = NR::Matrix (NR::translate (-c_w)) * + NR::Matrix (NR::rotate(-r2_angle)) * + NR::Matrix (NR::scale(move_stretch, scale? move_stretch : 1)) * + NR::Matrix (NR::rotate(r2_angle)) * + NR::Matrix (NR::rotate(move_angle)) * + NR::Matrix (NR::translate (c_w)); + + new_transform = gradient->gradientTransform * i2d * move * i2d.inverse(); + transform_set = true; + + break; + } + } + + if (transform_set) { + gradient->gradientTransform = new_transform; + gradient->gradientTransform_set = TRUE; + if (write_repr) { + gchar s[256]; + if (sp_svg_transform_write(s, 256, gradient->gradientTransform)) { + SP_OBJECT_REPR(gradient)->setAttribute("gradientTransform", s); + } else { + SP_OBJECT_REPR(gradient)->setAttribute("gradientTransform", NULL); + } + } else { + SP_OBJECT (gradient)->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + } + } +} + +SPGradient * +sp_item_gradient_get_vector (SPItem *item, bool fill_or_stroke) +{ + SPGradient *gradient = sp_item_gradient (item, fill_or_stroke); + + if (gradient) + return sp_gradient_get_vector (gradient, false); + return NULL; +} + +SPGradientSpread +sp_item_gradient_get_spread (SPItem *item, bool fill_or_stroke) +{ + SPGradient *gradient = sp_item_gradient (item, fill_or_stroke); + + if (gradient) + return sp_gradient_get_spread (gradient); + return SP_GRADIENT_SPREAD_PAD; +} + + +/** +Returns the position of point point_num of the gradient applied to item (either fill_or_stroke), +in desktop coordinates. +*/ + +NR::Point +sp_item_gradient_get_coords (SPItem *item, guint point_num, bool fill_or_stroke) +{ + SPGradient *gradient = sp_item_gradient (item, fill_or_stroke); + + NR::Point p (0, 0); + + if (!gradient) + return p; + + if (SP_IS_LINEARGRADIENT(gradient)) { + SPLinearGradient *lg = SP_LINEARGRADIENT(gradient); + switch (point_num) { + case POINT_LG_P1: + p = NR::Point (lg->x1.computed, lg->y1.computed); + break; + case POINT_LG_P2: + p = NR::Point (lg->x2.computed, lg->y2.computed); + break; + } + } else if (SP_IS_RADIALGRADIENT(gradient)) { + SPRadialGradient *rg = SP_RADIALGRADIENT(gradient); + switch (point_num) { + case POINT_RG_CENTER: + p = NR::Point (rg->cx.computed, rg->cy.computed); + break; + case POINT_RG_FOCUS: + p = NR::Point (rg->fx.computed, rg->fy.computed); + break; + case POINT_RG_R1: + p = NR::Point (rg->cx.computed + rg->r.computed, rg->cy.computed); + break; + case POINT_RG_R2: + p = NR::Point (rg->cx.computed, rg->cy.computed - rg->r.computed); + break; + } + } + + if (SP_GRADIENT(gradient)->units == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) { + sp_document_ensure_up_to_date(SP_OBJECT_DOCUMENT(item)); + NR::Rect const bbox = item->invokeBbox(NR::identity()); // we need "true" bbox without item_i2d_affine + p *= NR::Matrix(bbox.dimensions()[NR::X], 0, + 0, bbox.dimensions()[NR::Y], + bbox.min()[NR::X], bbox.min()[NR::Y]); + } + p *= NR::Matrix(gradient->gradientTransform) * sp_item_i2d_affine(item); + return p; +} + + +/** + * Sets item fill or stroke to the gradient of the specified type with given vector, creating + * new private gradient, if needed. + * gr has to be a normalized vector. + */ + +SPGradient * +sp_item_set_gradient(SPItem *item, SPGradient *gr, SPGradientType type, bool is_fill) +{ + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(SP_IS_ITEM(item), NULL); + g_return_val_if_fail(gr != NULL, NULL); + g_return_val_if_fail(SP_IS_GRADIENT(gr), NULL); + g_return_val_if_fail(gr->state == SP_GRADIENT_STATE_VECTOR, NULL); + + SPStyle *style = SP_OBJECT_STYLE(item); + g_assert(style != NULL); + + guint style_type = is_fill? style->fill.type : style->stroke.type; + SPPaintServer *ps = NULL; + if (style_type == SP_PAINT_TYPE_PAINTSERVER) + ps = is_fill? SP_STYLE_FILL_SERVER(style) : SP_STYLE_STROKE_SERVER(style); + + if (ps + && ( (type == SP_GRADIENT_TYPE_LINEAR && SP_IS_LINEARGRADIENT(ps)) || + (type == SP_GRADIENT_TYPE_RADIAL && SP_IS_RADIALGRADIENT(ps)) ) ) + { + + /* Current fill style is the gradient of the required type */ + SPGradient *current = SP_GRADIENT(ps); + + //g_print("hrefcount %d count %d\n", SP_OBJECT_HREFCOUNT(ig), count_gradient_hrefs(SP_OBJECT(item), ig)); + + if (SP_OBJECT_HREFCOUNT(current) == 1 || + SP_OBJECT_HREFCOUNT(current) == count_gradient_hrefs(SP_OBJECT(item), current)) { + + // current is private and it's either used once, or all its uses are by children of item; + // so just change its href to vector + + if ( current != gr && sp_gradient_get_vector(current, false) != gr ) { + /* href is not the vector */ + sp_gradient_repr_set_link(SP_OBJECT_REPR(current), gr); + } + SP_OBJECT(item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + return current; + + } else { + + // the gradient is not private, or it is shared with someone else; + // normalize it (this includes creating new private if necessary) + SPGradient *normalized = sp_gradient_fork_private_if_necessary(current, gr, type, item); + + g_return_val_if_fail(normalized != NULL, NULL); + + if (normalized != current) { + + /* We have to change object style here; recursive because this is used from + * fill&stroke and must work for groups etc. */ + sp_item_repr_set_style_gradient(SP_OBJECT_REPR(item), is_fill? "fill" : "stroke", normalized, true); + } + SP_OBJECT(item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + return normalized; + } + + } else { + /* Current fill style is not a gradient or wrong type, so construct everything */ + SPGradient *constructed = sp_gradient_get_private_normalized(SP_OBJECT_DOCUMENT(item), gr, type); + constructed = sp_gradient_reset_to_userspace(constructed, item); + sp_item_repr_set_style_gradient(SP_OBJECT_REPR(item), + ( is_fill ? "fill" : "stroke" ), + constructed, true); + SP_OBJECT(item)->requestDisplayUpdate(( SP_OBJECT_MODIFIED_FLAG | + SP_OBJECT_STYLE_MODIFIED_FLAG )); + return constructed; + } +} + +static void +sp_gradient_repr_set_link(Inkscape::XML::Node *repr, SPGradient *link) +{ + g_return_if_fail(repr != NULL); + g_return_if_fail(link != NULL); + g_return_if_fail(SP_IS_GRADIENT(link)); + + gchar *ref; + if (link) { + gchar const *id = SP_OBJECT_ID(link); + size_t const len = strlen(id); + ref = (gchar*) alloca(len + 2); + *ref = '#'; + memcpy(ref + 1, id, len + 1); + } else { + ref = NULL; + } + + repr->setAttribute("xlink:href", ref); +} + +static void +sp_item_repr_set_style_gradient(Inkscape::XML::Node *repr, gchar const *property, + SPGradient *gr, bool recursive) +{ + g_return_if_fail(repr != NULL); + g_return_if_fail(gr != NULL); + g_return_if_fail(SP_IS_GRADIENT(gr)); + + gchar *val = g_strdup_printf("url(#%s)", SP_OBJECT_ID(gr)); + + SPCSSAttr *css = sp_repr_css_attr_new(); + sp_repr_css_set_property(css, property, val); + g_free(val); + if (recursive) { + sp_repr_css_change_recursive(repr, css, "style"); + } else { + sp_repr_css_change(repr, css, "style"); + } + sp_repr_css_attr_unref(css); +} + +/* + * Get default normalized gradient vector of document, create if there is none + */ + +SPGradient * +sp_document_default_gradient_vector(SPDocument *document, guint32 color) +{ + SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document); + + Inkscape::XML::Node *repr = sp_repr_new("svg:linearGradient"); + + repr->setAttribute("inkscape:collect", "always"); + // set here, but removed when it's edited in the gradient editor + // to further reduce clutter, we could + // (1) here, search gradients by color and return what is found without duplication + // (2) in fill & stroke, show only one copy of each gradient in list + + Inkscape::XML::Node *stop = sp_repr_new("svg:stop"); + + gchar b[64]; + sp_svg_write_color(b, 64, color); + + { + gchar *t = g_strdup_printf("stop-color:%s;stop-opacity:1;", b); + stop->setAttribute("style", t); + g_free(t); + } + + stop->setAttribute("offset", "0"); + + repr->appendChild(stop); + Inkscape::GC::release(stop); + + stop = sp_repr_new("svg:stop"); + + { + gchar *t = g_strdup_printf("stop-color:%s;stop-opacity:0;", b); + stop->setAttribute("style", t); + g_free(t); + } + + stop->setAttribute("offset", "1"); + + repr->appendChild(stop); + Inkscape::GC::release(stop); + + SP_OBJECT_REPR(defs)->addChild(repr, NULL); + Inkscape::GC::release(repr); + + /* fixme: This does not look like nice */ + SPGradient *gr; + gr = (SPGradient *) document->getObjectByRepr(repr); + g_assert(gr != NULL); + g_assert(SP_IS_GRADIENT(gr)); + /* fixme: Maybe add extra sanity check here */ + gr->state = SP_GRADIENT_STATE_VECTOR; + + return gr; +} + +/** +Return the preferred vector for \a o, made from (in order of preference) its current vector, +current fill or stroke color, or from desktop style if \a o is NULL or doesn't have style. +*/ +SPGradient * +sp_gradient_vector_for_object(SPDocument *const doc, SPDesktop *const desktop, + SPObject *const o, bool const is_fill) +{ + guint32 rgba = 0; + if (o == NULL || SP_OBJECT_STYLE(o) == NULL) { + rgba = sp_desktop_get_color(desktop, is_fill); + } else { + // take the color of the object + SPStyle const &style = *SP_OBJECT_STYLE(o); + SPIPaint const &paint = ( is_fill + ? style.fill + : style.stroke ); + if (paint.type == SP_PAINT_TYPE_COLOR) { + rgba = sp_color_get_rgba32_ualpha(&paint.value.color, 0xff); + } else if (paint.type == SP_PAINT_TYPE_PAINTSERVER) { + SPObject *server = is_fill? SP_OBJECT_STYLE_FILL_SERVER(o) : SP_OBJECT_STYLE_STROKE_SERVER(o); + if (SP_IS_GRADIENT (server)) { + return sp_gradient_get_vector(SP_GRADIENT (server), TRUE); + } else { + rgba = sp_desktop_get_color(desktop, is_fill); + } + } else { + // if o doesn't use flat color, then take current color of the desktop. + rgba = sp_desktop_get_color(desktop, is_fill); + } + } + + return sp_document_default_gradient_vector(doc, rgba); +} + +/* + 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 : diff --git a/src/gradient-chemistry.h b/src/gradient-chemistry.h new file mode 100644 index 000000000..b649a22ec --- /dev/null +++ b/src/gradient-chemistry.h @@ -0,0 +1,82 @@ +#ifndef __SP_GRADIENT_CHEMISTRY_H__ +#define __SP_GRADIENT_CHEMISTRY_H__ + +/* + * Various utility methods for gradients + * + * Author: + * Lauris Kaplinski + * bulia byak + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "forward.h" +#include "sp-gradient.h" + +/* + * Either normalizes given gradient to vector, or returns fresh normalized + * vector - in latter case, original gradient is flattened and stops cleared + * No transrefing - i.e. caller shouldn't hold reference to original and + * does not get one to new automatically (doc holds ref of every object anyways) + */ + +SPGradient *sp_gradient_ensure_vector_normalized (SPGradient *gradient); + + +/* + * Sets item fill/stroke to lineargradient with given vector, creating + * new private gradient, if needed + * gr has to be normalized vector + */ + +SPGradient *sp_item_set_gradient (SPItem *item, SPGradient *gr, SPGradientType type, bool is_fill); + +/* + * Get default normalized gradient vector of document, create if there is none + */ + +SPGradient *sp_document_default_gradient_vector (SPDocument *document, guint32 color = 0); +SPGradient *sp_gradient_vector_for_object (SPDocument *doc, SPDesktop *desktop, SPObject *o, bool is_fill); + +void sp_object_ensure_fill_gradient_normalized (SPObject *object); +void sp_object_ensure_stroke_gradient_normalized (SPObject *object); + +SPGradient *sp_gradient_convert_to_userspace (SPGradient *gr, SPItem *item, const gchar *property); +SPGradient *sp_gradient_reset_to_userspace (SPGradient *gr, SPItem *item); + +SPGradient *sp_gradient_fork_vector_if_necessary (SPGradient *gr); + +SPStop* sp_first_stop(SPGradient *gradient); +SPStop* sp_last_stop(SPGradient *gradient); +SPStop* sp_prev_stop(SPStop *stop, SPGradient *gradient); +SPStop* sp_next_stop(SPStop *stop); + +void sp_gradient_transform_multiply (SPGradient *gradient, NR::Matrix postmul, bool set); + +void sp_item_gradient_set_coords (SPItem *item, guint point_num, NR::Point p_desk, bool fill_or_stroke, bool write_repr, bool scale); +NR::Point sp_item_gradient_get_coords (SPItem *item, guint point_num, bool fill_or_stroke); +SPGradient *sp_item_gradient_get_vector (SPItem *item, bool fill_or_stroke); +SPGradientSpread sp_item_gradient_get_spread (SPItem *item, bool fill_or_stroke); + +struct SPCSSAttr; +void sp_item_gradient_stop_set_style (SPItem *item, guint point_num, bool fill_or_stroke, SPCSSAttr *stop); +guint32 sp_item_gradient_stop_query_style (SPItem *item, guint point_num, bool fill_or_stroke); +void sp_item_gradient_edit_stop (SPItem *item, guint point_num, bool fill_or_stroke); +void sp_item_gradient_reverse_vector (SPItem *item, bool fill_or_stroke); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/gradient-context.cpp b/src/gradient-context.cpp new file mode 100644 index 000000000..84bed562d --- /dev/null +++ b/src/gradient-context.cpp @@ -0,0 +1,468 @@ +#define __SP_GRADIENT_CONTEXT_C__ + +/* + * Gradient drawing and editing tool + * + * Authors: + * bulia byak + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + + +#include + +#include "macros.h" +#include "document.h" +#include "selection.h" +#include "desktop.h" +#include "desktop-handles.h" +#include "message-context.h" +#include "message-stack.h" +#include "pixmaps/cursor-gradient.xpm" +#include "gradient-context.h" +#include +#include "prefs-utils.h" +#include "gradient-drag.h" +#include "gradient-chemistry.h" +#include "xml/repr.h" +#include "sp-item.h" + +static void sp_gradient_context_class_init(SPGradientContextClass *klass); +static void sp_gradient_context_init(SPGradientContext *gr_context); +static void sp_gradient_context_dispose(GObject *object); + +static void sp_gradient_context_setup(SPEventContext *ec); + +static gint sp_gradient_context_root_handler(SPEventContext *event_context, GdkEvent *event); + +static void sp_gradient_drag(SPGradientContext &rc, NR::Point const pt, guint state, guint32 etime); + +static SPEventContextClass *parent_class; + + +GtkType sp_gradient_context_get_type() +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPGradientContextClass), + NULL, NULL, + (GClassInitFunc) sp_gradient_context_class_init, + NULL, NULL, + sizeof(SPGradientContext), + 4, + (GInstanceInitFunc) sp_gradient_context_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPGradientContext", &info, (GTypeFlags) 0); + } + return type; +} + +static void sp_gradient_context_class_init(SPGradientContextClass *klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + SPEventContextClass *event_context_class = (SPEventContextClass *) klass; + + parent_class = (SPEventContextClass *) g_type_class_peek_parent(klass); + + object_class->dispose = sp_gradient_context_dispose; + + event_context_class->setup = sp_gradient_context_setup; + event_context_class->root_handler = sp_gradient_context_root_handler; +} + +static void sp_gradient_context_init(SPGradientContext *gr_context) +{ + SPEventContext *event_context = SP_EVENT_CONTEXT(gr_context); + + event_context->cursor_shape = cursor_gradient_xpm; + event_context->hot_x = 4; + event_context->hot_y = 4; + event_context->xp = 0; + event_context->yp = 0; + event_context->tolerance = 0; + event_context->within_tolerance = false; + event_context->item_to_select = NULL; +} + +static void sp_gradient_context_dispose(GObject *object) +{ + SPGradientContext *rc = SP_GRADIENT_CONTEXT(object); + SPEventContext *ec = SP_EVENT_CONTEXT(object); + + ec->enableGrDrag(false); + + if (rc->_message_context) { + delete rc->_message_context; + } + + G_OBJECT_CLASS(parent_class)->dispose(object); +} + +static void sp_gradient_context_setup(SPEventContext *ec) +{ + SPGradientContext *rc = SP_GRADIENT_CONTEXT(ec); + + if (((SPEventContextClass *) parent_class)->setup) { + ((SPEventContextClass *) parent_class)->setup(ec); + } + + if (prefs_get_int_attribute("tools.gradient", "selcue", 1) != 0) { + ec->enableSelectionCue(); + } + + ec->enableGrDrag(); + + rc->_message_context = new Inkscape::MessageContext(SP_DT_MSGSTACK(ec->desktop)); +} + +static gint sp_gradient_context_root_handler(SPEventContext *event_context, GdkEvent *event) +{ + static bool dragging; + + SPDesktop *desktop = event_context->desktop; + Inkscape::Selection *selection = SP_DT_SELECTION (desktop); + + SPGradientContext *rc = SP_GRADIENT_CONTEXT(event_context); + + event_context->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100); + double const nudge = prefs_get_double_attribute_limited("options.nudgedistance", "value", 2, 0, 1000); // in px + + GrDrag *drag = event_context->_grdrag; + g_assert (drag); + + gint ret = FALSE; + switch (event->type) { + case GDK_2BUTTON_PRESS: + if ( event->button.button == 1 ) { + for (GSList const* i = selection->itemList(); i != NULL; i = i->next) { + SPItem *item = SP_ITEM(i->data); + SPGradientType new_type = (SPGradientType) prefs_get_int_attribute ("tools.gradient", "newgradient", SP_GRADIENT_TYPE_LINEAR); + guint new_fill = prefs_get_int_attribute ("tools.gradient", "newfillorstroke", 1); + + SPGradient *vector = sp_gradient_vector_for_object(SP_DT_DOCUMENT(desktop), desktop, SP_OBJECT (item), new_fill); + + SPGradient *priv = sp_item_set_gradient(item, vector, new_type, new_fill); + sp_gradient_reset_to_userspace(priv, item); + } + + sp_document_done (SP_DT_DOCUMENT (desktop)); + + ret = TRUE; + } + break; + case GDK_BUTTON_PRESS: + if ( event->button.button == 1 ) { + NR::Point const button_w(event->button.x, event->button.y); + + // save drag origin + event_context->xp = (gint) button_w[NR::X]; + event_context->yp = (gint) button_w[NR::Y]; + event_context->within_tolerance = true; + + // remember clicked item, disregarding groups, honoring Alt; do nothing with Crtl to + // enable Ctrl+doubleclick of exactly the selected item(s) + if (!(event->button.state & GDK_CONTROL_MASK)) + event_context->item_to_select = sp_event_context_find_item (desktop, button_w, event->button.state & GDK_MOD1_MASK, TRUE); + + dragging = true; + /* Position center */ + NR::Point const button_dt = desktop->w2d(button_w); + /* Snap center to nearest magnetic point */ + + rc->origin = button_dt; + + ret = TRUE; + } + break; + case GDK_MOTION_NOTIFY: + if ( dragging + && ( event->motion.state & GDK_BUTTON1_MASK ) ) + { + if ( event_context->within_tolerance + && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance ) + && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) { + break; // do not drag if we're within tolerance from origin + } + // Once the user has moved farther than tolerance from the original location + // (indicating they intend to draw, not click), then always process the + // motion notify coordinates as given (no snapping back to origin) + event_context->within_tolerance = false; + + NR::Point const motion_w(event->motion.x, + event->motion.y); + NR::Point const motion_dt = event_context->desktop->w2d(motion_w); + + sp_gradient_drag(*rc, motion_dt, event->motion.state, event->motion.time); + + ret = TRUE; + } + break; + case GDK_BUTTON_RELEASE: + event_context->xp = event_context->yp = 0; + if ( event->button.button == 1 ) { + dragging = false; + + // unless clicked with Ctrl (to enable Ctrl+doubleclick) + if (event->button.state & GDK_CONTROL_MASK) { + ret = TRUE; + break; + } + + if (!event_context->within_tolerance) { + // we've been dragging, do nothing (grdrag handles that) + } else if (event_context->item_to_select) { + // no dragging, select clicked item if any + if (event->button.state & GDK_SHIFT_MASK) { + selection->toggle(event_context->item_to_select); + } else { + selection->set(event_context->item_to_select); + } + } else { + // click in an empty space; do the same as Esc + if (drag->selected) { + drag->setSelected (NULL); + } else { + selection->clear(); + } + } + + event_context->item_to_select = NULL; + ret = TRUE; + } + break; + case GDK_KEY_PRESS: + switch (get_group0_keyval (&event->key)) { + case GDK_Alt_L: + case GDK_Alt_R: + case GDK_Control_L: + case GDK_Control_R: + case GDK_Shift_L: + case GDK_Shift_R: + case GDK_Meta_L: // Meta is when you press Shift+Alt (at least on my machine) + case GDK_Meta_R: + sp_event_show_modifier_tip (event_context->defaultMessageContext(), event, + _("Ctrl: snap gradient angle"), + _("Shift: draw gradient around the starting point"), + NULL); + break; + + case GDK_x: + case GDK_X: + if (MOD__ALT_ONLY) { + desktop->setToolboxFocusTo ("altx-grad"); + ret = TRUE; + } + break; + + case GDK_Escape: + if (drag->selected) { + drag->setSelected (NULL); + } else { + selection->clear(); + } + ret = TRUE; + //TODO: make dragging escapable by Esc + break; + + case GDK_Tab: // Tab - cycle selection forward + if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) { + drag->select_next(); + ret = TRUE; + } + break; + case GDK_ISO_Left_Tab: // Shift Tab - cycle selection backward + if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) { + drag->select_prev(); + ret = TRUE; + } + break; + + case GDK_Left: // move handle left + case GDK_KP_Left: + case GDK_KP_4: + if (!MOD__CTRL) { // not ctrl + if (MOD__ALT) { // alt + if (MOD__SHIFT) drag->selected_move_screen(-10, 0); // shift + else drag->selected_move_screen(-1, 0); // no shift + } + else { // no alt + if (MOD__SHIFT) drag->selected_move(-10*nudge, 0); // shift + else drag->selected_move(-nudge, 0); // no shift + } + ret = TRUE; + } + break; + case GDK_Up: // move handle up + case GDK_KP_Up: + case GDK_KP_8: + if (!MOD__CTRL) { // not ctrl + if (MOD__ALT) { // alt + if (MOD__SHIFT) drag->selected_move_screen(0, 10); // shift + else drag->selected_move_screen(0, 1); // no shift + } + else { // no alt + if (MOD__SHIFT) drag->selected_move(0, 10*nudge); // shift + else drag->selected_move(0, nudge); // no shift + } + ret = TRUE; + } + break; + case GDK_Right: // move handle right + case GDK_KP_Right: + case GDK_KP_6: + if (!MOD__CTRL) { // not ctrl + if (MOD__ALT) { // alt + if (MOD__SHIFT) drag->selected_move_screen(10, 0); // shift + else drag->selected_move_screen(1, 0); // no shift + } + else { // no alt + if (MOD__SHIFT) drag->selected_move(10*nudge, 0); // shift + else drag->selected_move(nudge, 0); // no shift + } + ret = TRUE; + } + break; + case GDK_Down: // move handle down + case GDK_KP_Down: + case GDK_KP_2: + if (!MOD__CTRL) { // not ctrl + if (MOD__ALT) { // alt + if (MOD__SHIFT) drag->selected_move_screen(0, -10); // shift + else drag->selected_move_screen(0, -1); // no shift + } + else { // no alt + if (MOD__SHIFT) drag->selected_move(0, -10*nudge); // shift + else drag->selected_move(0, -nudge); // no shift + } + ret = TRUE; + } + break; + case GDK_r: + case GDK_R: + if (MOD__SHIFT_ONLY) { + // First try selected dragger + if (drag && drag->selected) { + drag->selected_reverse_vector(); + } else { // If no drag or no dragger selected, act on selection (both fill and stroke gradients) + for (GSList const* i = selection->itemList(); i != NULL; i = i->next) { + sp_item_gradient_reverse_vector (SP_ITEM(i->data), true); + sp_item_gradient_reverse_vector (SP_ITEM(i->data), false); + } + } + // we did an undoable action + sp_document_done (SP_DT_DOCUMENT (desktop)); + ret = TRUE; + } + break; + default: + break; + } + break; + case GDK_KEY_RELEASE: + switch (get_group0_keyval (&event->key)) { + case GDK_Alt_L: + case GDK_Alt_R: + case GDK_Control_L: + case GDK_Control_R: + case GDK_Shift_L: + case GDK_Shift_R: + case GDK_Meta_L: // Meta is when you press Shift+Alt + case GDK_Meta_R: + event_context->defaultMessageContext()->clear(); + break; + default: + break; + } + break; + default: + break; + } + + if (!ret) { + if (((SPEventContextClass *) parent_class)->root_handler) { + ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event); + } + } + + return ret; +} + +static void sp_gradient_drag(SPGradientContext &rc, NR::Point const pt, guint state, guint32 etime) +{ + SPDesktop *desktop = SP_EVENT_CONTEXT(&rc)->desktop; + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + SPDocument *document = SP_DT_DOCUMENT(desktop); + SPEventContext *ec = SP_EVENT_CONTEXT(&rc); + + if (!selection->isEmpty()) { + int type = prefs_get_int_attribute ("tools.gradient", "newgradient", 1); + int fill_or_stroke = prefs_get_int_attribute ("tools.gradient", "newfillorstroke", 1); + + SPGradient *vector; + if (ec->item_to_select) { + vector = sp_gradient_vector_for_object(document, desktop, ec->item_to_select, fill_or_stroke); + } else { + vector = sp_gradient_vector_for_object(document, desktop, SP_ITEM(selection->itemList()->data), fill_or_stroke); + } + + // HACK: reset fill-opacity - that 0.75 is annoying; BUT remove this when we have an opacity slider for all tabs + SPCSSAttr *css = sp_repr_css_attr_new(); + sp_repr_css_set_property(css, "fill-opacity", "1.0"); + + for (GSList const *i = selection->itemList(); i != NULL; i = i->next) { + + //FIXME: see above + sp_repr_css_change_recursive(SP_OBJECT_REPR(i->data), css, "style"); + + sp_item_set_gradient(SP_ITEM(i->data), vector, (SPGradientType) type, fill_or_stroke); + + if (type == SP_GRADIENT_TYPE_LINEAR) { + sp_item_gradient_set_coords (SP_ITEM(i->data), POINT_LG_P1, rc.origin, fill_or_stroke, true, false); + sp_item_gradient_set_coords (SP_ITEM(i->data), POINT_LG_P2, pt, fill_or_stroke, true, false); + } else if (type == SP_GRADIENT_TYPE_RADIAL) { + sp_item_gradient_set_coords (SP_ITEM(i->data), POINT_RG_CENTER, rc.origin, fill_or_stroke, true, false); + sp_item_gradient_set_coords (SP_ITEM(i->data), POINT_RG_R1, pt, fill_or_stroke, true, false); + } + SP_OBJECT (i->data)->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + if (ec->_grdrag) { + ec->_grdrag->updateDraggers(); + // prevent regenerating draggers by selection modified signal, which sometimes + // comes too late and thus destroys the knot which we will now grab: + ec->_grdrag->local_change = true; + // give the grab out-of-bounds values of xp/yp because we're already dragging + // and therefore are already out of tolerance + ec->_grdrag->grabKnot (SP_ITEM(selection->itemList()->data), + type == SP_GRADIENT_TYPE_LINEAR? POINT_LG_P2 : POINT_RG_R1, + fill_or_stroke, 99999, 99999, etime); + } + // We did an undoable action, but sp_document_done will be called by the knot when released + + // status text; we do not track coords because this branch is run once, not all the time + // during drag + rc._message_context->setF(Inkscape::NORMAL_MESSAGE, _("Gradient for %d objects; with Ctrl to snap angle"), g_slist_length((GSList *) selection->itemList())); + } else { + SP_DT_MSGSTACK(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select objects on which to create gradient.")); + } +} + + +/* + 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 : diff --git a/src/gradient-context.h b/src/gradient-context.h new file mode 100644 index 000000000..3eb7910f8 --- /dev/null +++ b/src/gradient-context.h @@ -0,0 +1,56 @@ +#ifndef __SP_GRADIENT_CONTEXT_H__ +#define __SP_GRADIENT_CONTEXT_H__ + +/* + * Gradient drawing and editing tool + * + * Authors: + * bulia byak + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL + */ + +#include +#include "event-context.h" +#include "libnr/nr-point.h" +struct SPKnotHolder; + +#define SP_TYPE_GRADIENT_CONTEXT (sp_gradient_context_get_type()) +#define SP_GRADIENT_CONTEXT(obj) (GTK_CHECK_CAST((obj), SP_TYPE_GRADIENT_CONTEXT, SPGradientContext)) +#define SP_GRADIENT_CONTEXT_CLASS(klass) (GTK_CHECK_CLASS_CAST((klass), SP_TYPE_GRADIENT_CONTEXT, SPGradientContextClass)) +#define SP_IS_GRADIENT_CONTEXT(obj) (GTK_CHECK_TYPE((obj), SP_TYPE_GRADIENT_CONTEXT)) +#define SP_IS_GRADIENT_CONTEXT_CLASS(klass) (GTK_CHECK_CLASS_TYPE((klass), SP_TYPE_GRADIENT_CONTEXT)) + +class SPGradientContext; +class SPGradientContextClass; + +struct SPGradientContext : public SPEventContext { + + NR::Point origin; + + Inkscape::MessageContext *_message_context; +}; + +struct SPGradientContextClass { + SPEventContextClass parent_class; +}; + +/* Standard Gtk function */ + +GtkType sp_gradient_context_get_type(); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/gradient-drag.cpp b/src/gradient-drag.cpp new file mode 100644 index 000000000..ffc1b7fc6 --- /dev/null +++ b/src/gradient-drag.cpp @@ -0,0 +1,1108 @@ +#define __GRADIENT_DRAG_C__ + +/* + * On-canvas gradient dragging + * + * Authors: + * bulia byak + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +#include "desktop-handles.h" +#include "selection.h" +#include "desktop.h" +#include "desktop-style.h" +#include "document.h" +#include "display/sp-ctrlline.h" + +#include "xml/repr.h" + +#include "prefs-utils.h" +#include "sp-item.h" +#include "style.h" +#include "knot.h" +#include "sp-linear-gradient.h" +#include "sp-radial-gradient.h" +#include "gradient-chemistry.h" +#include "gradient-drag.h" + +#define GR_KNOT_COLOR_NORMAL 0xffffff00 +#define GR_KNOT_COLOR_SELECTED 0x0000ff00 + +#define GR_LINE_COLOR_FILL 0x0000ff7f +#define GR_LINE_COLOR_STROKE 0x9999007f + +// screen pixels between knots when they snap: +#define SNAP_DIST 5 + +// absolute distance between gradient points for them to become a single dragger when the drag is created: +#define MERGE_DIST 0.1 + +// knot shapes corresponding to GrPoint enum +SPKnotShapeType gr_knot_shapes [] = { + SP_KNOT_SHAPE_SQUARE, //POINT_LG_P1 + SP_KNOT_SHAPE_SQUARE, + SP_KNOT_SHAPE_DIAMOND, + SP_KNOT_SHAPE_CIRCLE, + SP_KNOT_SHAPE_CIRCLE, + SP_KNOT_SHAPE_CROSS // POINT_RG_FOCUS +}; + +const gchar *gr_knot_descr [] = { + N_("Linear gradient start"), //POINT_LG_P1 + N_("Linear gradient end"), + N_("Radial gradient center"), + N_("Radial gradient radius"), + N_("Radial gradient radius"), + N_("Radial gradient focus") // POINT_RG_FOCUS +}; + +static void +gr_drag_sel_changed(Inkscape::Selection *selection, gpointer data) +{ + GrDrag *drag = (GrDrag *) data; + drag->updateDraggers (); + drag->updateLines (); + drag->updateLevels (); +} + +static void +gr_drag_sel_modified (Inkscape::Selection *selection, guint flags, gpointer data) +{ + GrDrag *drag = (GrDrag *) data; + if (drag->local_change) { + drag->local_change = false; + } else { + drag->updateDraggers (); + } + drag->updateLines (); + drag->updateLevels (); +} + +/** +When a _query_style_signal is received, check that \a property requests fill/stroke (otherwise +skip), and fill the \a style with the averaged color of all draggables of the selected dragger, if +any. +*/ +int +gr_drag_style_query (SPStyle *style, int property, gpointer data) +{ + GrDrag *drag = (GrDrag *) data; + + if (property != QUERY_STYLE_PROPERTY_FILL && property != QUERY_STYLE_PROPERTY_STROKE) { + return QUERY_STYLE_NOTHING; + } + + if (!drag->selected) { + return QUERY_STYLE_NOTHING; + } else { + int ret = QUERY_STYLE_NOTHING; + + float cf[4]; + cf[0] = cf[1] = cf[2] = cf[3] = 0; + + int count = 0; + + for (GSList const* i = drag->selected->draggables; i != NULL; i = i->next) { // for all draggables of dragger + GrDraggable *draggable = (GrDraggable *) i->data; + + if (ret == QUERY_STYLE_NOTHING) { + ret = QUERY_STYLE_SINGLE; + } else if (ret == QUERY_STYLE_SINGLE) { + ret = QUERY_STYLE_MULTIPLE_AVERAGED; + } + + guint32 c = sp_item_gradient_stop_query_style (draggable->item, draggable->point_num, draggable->fill_or_stroke); + cf[0] += SP_RGBA32_R_F (c); + cf[1] += SP_RGBA32_G_F (c); + cf[2] += SP_RGBA32_B_F (c); + cf[3] += SP_RGBA32_A_F (c); + + count ++; + } + + if (count) { + cf[0] /= count; + cf[1] /= count; + cf[2] /= count; + cf[3] /= count; + + // set both fill and stroke with our stop-color and stop-opacity + sp_color_set_rgb_float((SPColor *) &style->fill.value.color, cf[0], cf[1], cf[2]); + style->fill.set = TRUE; + style->fill.type = SP_PAINT_TYPE_COLOR; + sp_color_set_rgb_float((SPColor *) &style->stroke.value.color, cf[0], cf[1], cf[2]); + style->stroke.set = TRUE; + style->stroke.type = SP_PAINT_TYPE_COLOR; + + style->fill_opacity.value = SP_SCALE24_FROM_FLOAT (cf[3]); + style->fill_opacity.set = TRUE; + style->stroke_opacity.value = SP_SCALE24_FROM_FLOAT (cf[3]); + style->stroke_opacity.set = TRUE; + } + + return ret; + } +} + +bool +gr_drag_style_set (const SPCSSAttr *css, gpointer data) +{ + GrDrag *drag = (GrDrag *) data; + + if (!drag->selected) + return false; + + SPCSSAttr *stop = sp_repr_css_attr_new (); + + // See if the css contains interesting properties, and if so, translate them into the format + // acceptable for gradient stops + + // any of color properties, in order of increasing priority: + if (css->attribute("flood-color")) + sp_repr_css_set_property (stop, "stop-color", css->attribute("flood-color")); + + if (css->attribute("lighting-color")) + sp_repr_css_set_property (stop, "stop-color", css->attribute("lighting-color")); + + if (css->attribute("color")) + sp_repr_css_set_property (stop, "stop-color", css->attribute("color")); + + if (css->attribute("stroke") && strcmp(css->attribute("stroke"), "none")) + sp_repr_css_set_property (stop, "stop-color", css->attribute("stroke")); + + if (css->attribute("fill") && strcmp(css->attribute("fill"), "none")) + sp_repr_css_set_property (stop, "stop-color", css->attribute("fill")); + + if (css->attribute("stop-color")) + sp_repr_css_set_property (stop, "stop-color", css->attribute("stop-color")); + + // any of opacity properties, in order of increasing priority: + if (css->attribute("flood-opacity")) + sp_repr_css_set_property (stop, "stop-opacity", css->attribute("flood-color")); + + if (css->attribute("opacity")) // TODO: multiply + sp_repr_css_set_property (stop, "stop-opacity", css->attribute("color")); + + if (css->attribute("stroke-opacity")) // TODO: multiply + sp_repr_css_set_property (stop, "stop-opacity", css->attribute("stroke-opacity")); + + if (css->attribute("fill-opacity")) // TODO: multiply + sp_repr_css_set_property (stop, "stop-opacity", css->attribute("fill-opacity")); + + if ((css->attribute("fill") && !strcmp(css->attribute("fill"), "none")) || + (css->attribute("stroke") && !strcmp(css->attribute("stroke"), "none"))) + sp_repr_css_set_property (stop, "stop-opacity", "0"); // if set to none, don't change color, set opacity to 0 + + if (css->attribute("stop-opacity")) + sp_repr_css_set_property (stop, "stop-opacity", css->attribute("stop-opacity")); + + if (!stop->attributeList()) { // nothing for us here, pass it on + sp_repr_css_attr_unref(stop); + return false; + } + + for (GSList const* i = drag->selected->draggables; i != NULL; i = i->next) { // for all draggables of dragger + GrDraggable *draggable = (GrDraggable *) i->data; + + drag->local_change = true; + sp_item_gradient_stop_set_style (draggable->item, draggable->point_num, draggable->fill_or_stroke, stop); + } + + //sp_repr_css_print(stop); + sp_repr_css_attr_unref(stop); + return true; +} + +GrDrag::GrDrag(SPDesktop *desktop) { + + this->desktop = desktop; + + this->selection = SP_DT_SELECTION(desktop); + + this->draggers = NULL; + this->lines = NULL; + this->selected = NULL; + + this->hor_levels.clear(); + this->vert_levels.clear(); + + this->local_change = false; + + this->sel_changed_connection = this->selection->connectChanged( + sigc::bind ( + sigc::ptr_fun(&gr_drag_sel_changed), + (gpointer)this ) + + ); + this->sel_modified_connection = this->selection->connectModified( + sigc::bind( + sigc::ptr_fun(&gr_drag_sel_modified), + (gpointer)this ) + ); + + this->style_set_connection = this->desktop->connectSetStyle( + sigc::bind( + sigc::ptr_fun(&gr_drag_style_set), + (gpointer)this ) + ); + + this->style_query_connection = this->desktop->connectQueryStyle( + sigc::bind( + sigc::ptr_fun(&gr_drag_style_query), + (gpointer)this ) + ); + + this->updateDraggers (); + this->updateLines (); + this->updateLevels (); + + if (desktop->gr_item) { + this->setSelected (getDraggerFor (desktop->gr_item, desktop->gr_point_num, desktop->gr_fill_or_stroke)); + } +} + +GrDrag::~GrDrag() +{ + this->sel_changed_connection.disconnect(); + this->sel_modified_connection.disconnect(); + this->style_set_connection.disconnect(); + this->style_query_connection.disconnect(); + + if (this->selected) { + GrDraggable *draggable = (GrDraggable *) this->selected->draggables->data; + desktop->gr_item = draggable->item; + desktop->gr_point_num = draggable->point_num; + desktop->gr_fill_or_stroke = draggable->fill_or_stroke; + } else { + desktop->gr_item = NULL; + desktop->gr_point_num = 0; + desktop->gr_fill_or_stroke = true; + } + + for (GList *l = this->draggers; l != NULL; l = l->next) { + delete ((GrDragger *) l->data); + } + g_list_free (this->draggers); + this->draggers = NULL; + this->selected = NULL; + + for (GSList *l = this->lines; l != NULL; l = l->next) { + gtk_object_destroy( GTK_OBJECT (l->data)); + } + g_slist_free (this->lines); + this->lines = NULL; +} + +GrDraggable::GrDraggable (SPItem *item, guint point_num, bool fill_or_stroke) +{ + this->item = item; + this->point_num = point_num; + this->fill_or_stroke = fill_or_stroke; + + g_object_ref (G_OBJECT (this->item)); +} + +GrDraggable::~GrDraggable () +{ + g_object_unref (G_OBJECT (this->item)); +} + +NR::Point * +get_snap_vector (NR::Point p, NR::Point o, double snap, double initial) +{ + double r = NR::L2 (p - o); + if (r < 1e-3) + return NULL; + double angle = NR::atan2 (p - o); + // snap angle to snaps increments, starting from initial: + double a_snapped = initial + floor((angle - initial)/snap + 0.5) * snap; + // calculate the new position and subtract p to get the vector: + return new NR::Point (o + r * NR::Point(cos(a_snapped), sin(a_snapped)) - p); +} + +static void +gr_knot_moved_handler(SPKnot *knot, NR::Point const *ppointer, guint state, gpointer data) +{ + GrDragger *dragger = (GrDragger *) data; + + NR::Point p = *ppointer; + + // FIXME: take from prefs + double snap_dist = SNAP_DIST / dragger->parent->desktop->current_zoom(); + + if (state & GDK_SHIFT_MASK) { + // with Shift; unsnap if we carry more than one draggable + if (dragger->draggables && dragger->draggables->next) { + // create a new dragger + GrDragger *dr_new = new GrDragger (dragger->parent, dragger->point, NULL); + dragger->parent->draggers = g_list_prepend (dragger->parent->draggers, dr_new); + // relink to it all but the first draggable in the list + for (GSList const* i = dragger->draggables->next; i != NULL; i = i->next) { + GrDraggable *draggable = (GrDraggable *) i->data; + dr_new->addDraggable (draggable); + } + dr_new->updateKnotShape(); + g_slist_free (dragger->draggables->next); + dragger->draggables->next = NULL; + dragger->updateKnotShape(); + dragger->updateTip(); + } + } else if (!(state & GDK_CONTROL_MASK)) { + // without Shift or Ctrl; see if we need to snap to another dragger + for (GList *di = dragger->parent->draggers; di != NULL; di = di->next) { + GrDragger *d_new = (GrDragger *) di->data; + if (dragger->mayMerge(d_new) && NR::L2 (d_new->point - p) < snap_dist) { + + // Merge draggers: + for (GSList const* i = dragger->draggables; i != NULL; i = i->next) { // for all draggables of dragger + GrDraggable *draggable = (GrDraggable *) i->data; + // copy draggable to d_new: + GrDraggable *da_new = new GrDraggable (draggable->item, draggable->point_num, draggable->fill_or_stroke); + d_new->addDraggable (da_new); + } + + // unlink and delete this dragger + dragger->parent->draggers = g_list_remove (dragger->parent->draggers, dragger); + delete dragger; + + // update the new merged dragger + d_new->fireDraggables(true, false, true); + d_new->parent->updateLines(); + d_new->parent->setSelected (d_new); + d_new->updateKnotShape (); + d_new->updateTip (); + d_new->updateDependencies(true); + sp_document_done (SP_DT_DOCUMENT (d_new->parent->desktop)); + return; + } + } + } + + if (!((state & GDK_SHIFT_MASK) || ((state & GDK_CONTROL_MASK) && (state & GDK_MOD1_MASK)))) { + // See if we need to snap to any of the levels + for (guint i = 0; i < dragger->parent->hor_levels.size(); i++) { + if (fabs(p[NR::Y] - dragger->parent->hor_levels[i]) < snap_dist) { + p[NR::Y] = dragger->parent->hor_levels[i]; + sp_knot_moveto (knot, &p); + } + } + for (guint i = 0; i < dragger->parent->vert_levels.size(); i++) { + if (fabs(p[NR::X] - dragger->parent->vert_levels[i]) < snap_dist) { + p[NR::X] = dragger->parent->vert_levels[i]; + sp_knot_moveto (knot, &p); + } + } + } + + if (state & GDK_CONTROL_MASK) { + unsigned snaps = abs(prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12)); + /* 0 means no snapping. */ + + // This list will store snap vectors from all draggables of dragger + GSList *snap_vectors = NULL; + + for (GSList const* i = dragger->draggables; i != NULL; i = i->next) { + GrDraggable *draggable = (GrDraggable *) i->data; + + NR::Point *dr_snap = NULL; + + if (draggable->point_num == POINT_LG_P1 || draggable->point_num == POINT_LG_P2) { + for (GList *di = dragger->parent->draggers; di != NULL; di = di->next) { + GrDragger *d_new = (GrDragger *) di->data; + if (d_new == dragger) + continue; + if (d_new->isA (draggable->item, + draggable->point_num == POINT_LG_P1? POINT_LG_P2 : POINT_LG_P1, + draggable->fill_or_stroke)) { + // found the other end of the linear gradient; + if (state & GDK_SHIFT_MASK) { + // moving linear around center + NR::Point center = NR::Point (0.5*(d_new->point + dragger->point)); + dr_snap = ¢er; + } else { + // moving linear around the other end + dr_snap = &d_new->point; + } + } + } + } else if (draggable->point_num == POINT_RG_R1 || draggable->point_num == POINT_RG_R2 || draggable->point_num == POINT_RG_FOCUS) { + for (GList *di = dragger->parent->draggers; di != NULL; di = di->next) { + GrDragger *d_new = (GrDragger *) di->data; + if (d_new == dragger) + continue; + if (d_new->isA (draggable->item, + POINT_RG_CENTER, + draggable->fill_or_stroke)) { + // found the center of the radial gradient; + dr_snap = &(d_new->point); + } + } + } else if (draggable->point_num == POINT_RG_CENTER) { + // radial center snaps to hor/vert relative to its original position + dr_snap = &(dragger->point_original); + } + + NR::Point *snap_vector = NULL; + if (dr_snap) { + if (state & GDK_MOD1_MASK) { + // with Alt, snap to the original angle and its perpendiculars + snap_vector = get_snap_vector (p, *dr_snap, M_PI/2, NR::atan2 (dragger->point_original - *dr_snap)); + } else { + // with Ctrl, snap to M_PI/snaps + snap_vector = get_snap_vector (p, *dr_snap, M_PI/snaps, 0); + } + } + if (snap_vector) { + snap_vectors = g_slist_prepend (snap_vectors, snap_vector); + } + } + + // Move by the smallest of snap vectors: + NR::Point move(9999, 9999); + for (GSList const *i = snap_vectors; i != NULL; i = i->next) { + NR::Point *snap_vector = (NR::Point *) i->data; + if (NR::L2(*snap_vector) < NR::L2(move)) + move = *snap_vector; + } + if (move[NR::X] < 9999) { + p += move; + sp_knot_moveto (knot, &p); + } + } + + dragger->point = p; + + if ((state & GDK_CONTROL_MASK) && (state & GDK_SHIFT_MASK)) { + dragger->fireDraggables (false, true); + } else { + dragger->fireDraggables (false); + } + + dragger->updateDependencies(false); +} + +/** +Called when the mouse releases a dragger knot; changes gradient writing to repr, updates other draggers if needed +*/ +static void +gr_knot_ungrabbed_handler (SPKnot *knot, unsigned int state, gpointer data) +{ + GrDragger *dragger = (GrDragger *) data; + + dragger->point_original = dragger->point = knot->pos; + + if ((state & GDK_CONTROL_MASK) && (state & GDK_SHIFT_MASK)) { + dragger->fireDraggables (true, true); + } else { + dragger->fireDraggables (true); + } + + // make this dragger selected + dragger->parent->setSelected (dragger); + + dragger->updateDependencies(true); + + // we did an undoable action + sp_document_done (SP_DT_DOCUMENT (dragger->parent->desktop)); +} + +/** +Called when a dragger knot is clicked; selects the dragger +*/ +static void +gr_knot_clicked_handler(SPKnot *knot, guint state, gpointer data) +{ + GrDragger *dragger = (GrDragger *) data; + + dragger->point_original = dragger->point; + + dragger->parent->setSelected (dragger); +} + +/** +Called when a dragger knot is doubleclicked; opens gradient editor with the stop from the first draggable +*/ +static void +gr_knot_doubleclicked_handler (SPKnot *knot, guint state, gpointer data) +{ + GrDragger *dragger = (GrDragger *) data; + + dragger->point_original = dragger->point; + + if (dragger->draggables == NULL) + return; + + GrDraggable *draggable = (GrDraggable *) dragger->draggables->data; + sp_item_gradient_edit_stop (draggable->item, draggable->point_num, draggable->fill_or_stroke); +} + +/** +Act upon all draggables of the dragger, setting them to the dragger's point +*/ +void +GrDragger::fireDraggables (bool write_repr, bool scale_radial, bool merging_focus) +{ + for (GSList const* i = this->draggables; i != NULL; i = i->next) { + GrDraggable *draggable = (GrDraggable *) i->data; + + // set local_change flag so that selection_changed callback does not regenerate draggers + this->parent->local_change = true; + + // change gradient, optionally writing to repr; prevent focus from moving if it's snapped + // to the center, unless it's the first update upon merge when we must snap it to the point + if (merging_focus || + !(draggable->point_num == POINT_RG_FOCUS && this->isA(draggable->item, POINT_RG_CENTER, draggable->fill_or_stroke))) + sp_item_gradient_set_coords (draggable->item, draggable->point_num, this->point, draggable->fill_or_stroke, write_repr, scale_radial); + } +} + +/** +Checks if the dragger has a draggable with this point_num + */ +bool +GrDragger::isA (guint point_num) +{ + for (GSList const* i = this->draggables; i != NULL; i = i->next) { + GrDraggable *draggable = (GrDraggable *) i->data; + if (draggable->point_num == point_num) { + return true; + } + } + return false; +} + +/** +Checks if the dragger has a draggable with this item, point_num, fill_or_stroke + */ +bool +GrDragger::isA (SPItem *item, guint point_num, bool fill_or_stroke) +{ + for (GSList const* i = this->draggables; i != NULL; i = i->next) { + GrDraggable *draggable = (GrDraggable *) i->data; + if (draggable->point_num == point_num && draggable->item == item && draggable->fill_or_stroke == fill_or_stroke) { + return true; + } + } + return false; +} + +bool +GrDraggable::mayMerge (GrDraggable *da2) +{ + if ((this->item == da2->item) && (this->fill_or_stroke == da2->fill_or_stroke)) { + // we must not merge the points of the same gradient! + if (!((this->point_num == POINT_RG_FOCUS && da2->point_num == POINT_RG_CENTER) || + (this->point_num == POINT_RG_CENTER && da2->point_num == POINT_RG_FOCUS))) { + // except that we can snap center and focus together + return false; + } + } + return true; +} + +bool +GrDragger::mayMerge (GrDragger *other) +{ + if (this == other) + return false; + + for (GSList const* i = this->draggables; i != NULL; i = i->next) { // for all draggables of this + GrDraggable *da1 = (GrDraggable *) i->data; + for (GSList const* j = other->draggables; j != NULL; j = j->next) { // for all draggables of other + GrDraggable *da2 = (GrDraggable *) j->data; + if (!da1->mayMerge(da2)) + return false; + } + } + return true; +} + +bool +GrDragger::mayMerge (GrDraggable *da2) +{ + for (GSList const* i = this->draggables; i != NULL; i = i->next) { // for all draggables of this + GrDraggable *da1 = (GrDraggable *) i->data; + if (!da1->mayMerge(da2)) + return false; + } + return true; +} + +/** +Updates the statusbar tip of the dragger knot, based on its draggables + */ +void +GrDragger::updateTip () +{ + if (this->knot && this->knot->tip) { + g_free (this->knot->tip); + this->knot->tip = NULL; + } + + if (g_slist_length (this->draggables) == 1) { + GrDraggable *draggable = (GrDraggable *) this->draggables->data; + char *item_desc = sp_item_description(draggable->item); + this->knot->tip = g_strdup_printf (_("%s for: %s%s; drag with Ctrl to snap angle, with Ctrl+Alt to preserve angle, with Ctrl+Shift to scale around center"), + _(gr_knot_descr[draggable->point_num]), + item_desc, + draggable->fill_or_stroke == false ? _(" (stroke)") : ""); + g_free(item_desc); + } else if (g_slist_length (draggables) == 2 && isA (POINT_RG_CENTER) && isA (POINT_RG_FOCUS)) { + this->knot->tip = g_strdup_printf (_("Radial gradient center and focus; drag with Shift to separate focus")); + } else { + this->knot->tip = g_strdup_printf (_("Gradient point shared by %d gradients; drag with Shift to separate"), + g_slist_length (this->draggables)); + } +} + +/** +Adds a draggable to the dragger + */ +void +GrDragger::updateKnotShape () +{ + if (!draggables) + return; + GrDraggable *last = (GrDraggable *) g_slist_last(draggables)->data; + g_object_set (G_OBJECT (this->knot->item), "shape", gr_knot_shapes[last->point_num], NULL); +} + +/** +Adds a draggable to the dragger + */ +void +GrDragger::addDraggable (GrDraggable *draggable) +{ + this->draggables = g_slist_prepend (this->draggables, draggable); + + this->updateTip(); +} + + +/** +Moves this dragger to the point of the given draggable, acting upon all other draggables + */ +void +GrDragger::moveThisToDraggable (SPItem *item, guint point_num, bool fill_or_stroke, bool write_repr) +{ + this->point = sp_item_gradient_get_coords (item, point_num, fill_or_stroke); + this->point_original = this->point; + + sp_knot_moveto (this->knot, &(this->point)); + + for (GSList const* i = this->draggables; i != NULL; i = i->next) { + GrDraggable *da = (GrDraggable *) i->data; + if (da->item == item && da->point_num == point_num && da->fill_or_stroke == fill_or_stroke) { + continue; + } + sp_item_gradient_set_coords (da->item, da->point_num, this->point, da->fill_or_stroke, write_repr, false); + } + // FIXME: here we should also call this->updateDependencies(write_repr); to propagate updating, but how to prevent loops? +} + + +/** +Moves all draggables that depend on this one + */ +void +GrDragger::updateDependencies (bool write_repr) +{ + for (GSList const* i = this->draggables; i != NULL; i = i->next) { + GrDraggable *draggable = (GrDraggable *) i->data; + switch (draggable->point_num) { + case POINT_LG_P1: + // the other point is dependent only when dragging with ctrl+shift + this->moveOtherToDraggable (draggable->item, POINT_LG_P2, draggable->fill_or_stroke, write_repr); + break; + case POINT_LG_P2: + this->moveOtherToDraggable (draggable->item, POINT_LG_P1, draggable->fill_or_stroke, write_repr); + break; + case POINT_RG_R2: + this->moveOtherToDraggable (draggable->item, POINT_RG_R1, draggable->fill_or_stroke, write_repr); + this->moveOtherToDraggable (draggable->item, POINT_RG_FOCUS, draggable->fill_or_stroke, write_repr); + break; + case POINT_RG_R1: + this->moveOtherToDraggable (draggable->item, POINT_RG_R2, draggable->fill_or_stroke, write_repr); + this->moveOtherToDraggable (draggable->item, POINT_RG_FOCUS, draggable->fill_or_stroke, write_repr); + break; + case POINT_RG_CENTER: + this->moveOtherToDraggable (draggable->item, POINT_RG_R1, draggable->fill_or_stroke, write_repr); + this->moveOtherToDraggable (draggable->item, POINT_RG_R2, draggable->fill_or_stroke, write_repr); + this->moveOtherToDraggable (draggable->item, POINT_RG_FOCUS, draggable->fill_or_stroke, write_repr); + break; + case POINT_RG_FOCUS: + // nothing can depend on that + break; + default: + break; + } + } +} + + + +GrDragger::GrDragger (GrDrag *parent, NR::Point p, GrDraggable *draggable) +{ + this->draggables = NULL; + + this->parent = parent; + + this->point = p; + this->point_original = p; + + // create the knot + this->knot = sp_knot_new (parent->desktop, NULL); + g_object_set (G_OBJECT (this->knot->item), "mode", SP_KNOT_MODE_XOR, NULL); + this->knot->fill [SP_KNOT_STATE_NORMAL] = GR_KNOT_COLOR_NORMAL; + this->knot->stroke [SP_KNOT_STATE_NORMAL] = 0x000000ff; + this->knot->stroke [SP_KNOT_STATE_DRAGGING] = 0x000000ff; + this->knot->stroke [SP_KNOT_STATE_MOUSEOVER] = 0x000000ff; + g_object_set (G_OBJECT (this->knot->item), "stroke_color", 0x000000ff, NULL); + + // move knot to the given point + sp_knot_set_position (this->knot, &p, SP_KNOT_STATE_NORMAL); + sp_knot_show (this->knot); + + // connect knot's signals + this->handler_id = g_signal_connect (G_OBJECT (this->knot), "moved", G_CALLBACK (gr_knot_moved_handler), this); + g_signal_connect (G_OBJECT (this->knot), "clicked", G_CALLBACK (gr_knot_clicked_handler), this); + g_signal_connect (G_OBJECT (this->knot), "doubleclicked", G_CALLBACK (gr_knot_doubleclicked_handler), this); + g_signal_connect (G_OBJECT (this->knot), "ungrabbed", G_CALLBACK (gr_knot_ungrabbed_handler), this); + + // add the initial draggable + if (draggable) + this->addDraggable (draggable); + updateKnotShape(); +} + +GrDragger::~GrDragger () +{ + // unselect if it was selected + if (this->parent->selected == this) + this->parent->setSelected (NULL); + + /* unref should call destroy */ + g_object_unref (G_OBJECT (this->knot)); + + // delete all draggables + for (GSList const* i = this->draggables; i != NULL; i = i->next) { + delete ((GrDraggable *) i->data); + } + g_slist_free (this->draggables); + this->draggables = NULL; +} + +/** +Select the dragger which has the given draggable. +*/ +GrDragger * +GrDrag::getDraggerFor (SPItem *item, guint point_num, bool fill_or_stroke) +{ + for (GList const* i = this->draggers; i != NULL; i = i->next) { + GrDragger *dragger = (GrDragger *) i->data; + for (GSList const* j = dragger->draggables; j != NULL; j = j->next) { + GrDraggable *da2 = (GrDraggable *) j->data; + if (da2->item == item && da2->point_num == point_num && da2->fill_or_stroke == fill_or_stroke) { + return (dragger); + } + } + } + return NULL; +} + + +void +GrDragger::moveOtherToDraggable (SPItem *item, guint point_num, bool fill_or_stroke, bool write_repr) +{ + GrDragger *d = this->parent->getDraggerFor (item, point_num, fill_or_stroke); + if (d && d != this) { + d->moveThisToDraggable (item, point_num, fill_or_stroke, write_repr); + } +} + + +/** +Set selected dragger +*/ +void +GrDrag::setSelected (GrDragger *dragger) +{ + if (this->selected) { + this->selected->knot->fill [SP_KNOT_STATE_NORMAL] = GR_KNOT_COLOR_NORMAL; + g_object_set (G_OBJECT (this->selected->knot->item), "fill_color", GR_KNOT_COLOR_NORMAL, NULL); + } + if (dragger) { + dragger->knot->fill [SP_KNOT_STATE_NORMAL] = GR_KNOT_COLOR_SELECTED; + g_object_set (G_OBJECT (dragger->knot->item), "fill_color", GR_KNOT_COLOR_SELECTED, NULL); + } + this->selected = dragger; + + this->desktop->emitToolSubselectionChanged((gpointer) dragger); +} + +/** +Create a line from p1 to p2 and add it to the lines list + */ +void +GrDrag::addLine (NR::Point p1, NR::Point p2, guint32 rgba) +{ + SPCanvasItem *line = sp_canvas_item_new(SP_DT_CONTROLS(this->desktop), + SP_TYPE_CTRLLINE, NULL); + sp_ctrlline_set_coords(SP_CTRLLINE(line), p1, p2); + if (rgba != GR_LINE_COLOR_FILL) // fill is the default, so don't set color for it to speed up redraw + sp_ctrlline_set_rgba32 (SP_CTRLLINE(line), rgba); + sp_canvas_item_show (line); + this->lines = g_slist_append (this->lines, line); +} + +/** +If there already exists a dragger within MERGE_DIST of p, add the draggable to it; otherwise create +new dragger and add it to draggers list + */ +void +GrDrag::addDragger (GrDraggable *draggable) +{ + NR::Point p = sp_item_gradient_get_coords (draggable->item, draggable->point_num, draggable->fill_or_stroke); + + for (GList *i = this->draggers; i != NULL; i = i->next) { + GrDragger *dragger = (GrDragger *) i->data; + if (dragger->mayMerge (draggable) && NR::L2 (dragger->point - p) < MERGE_DIST) { + // distance is small, merge this draggable into dragger, no need to create new dragger + dragger->addDraggable (draggable); + dragger->updateKnotShape(); + return; + } + } + + GrDragger *new_dragger = new GrDragger(this, p, draggable); + this->draggers = g_list_prepend (this->draggers, new_dragger); +} + +/** +Add draggers for the radial gradient rg on item +*/ +void +GrDrag::addDraggersRadial (SPRadialGradient *rg, SPItem *item, bool fill_or_stroke) +{ + addDragger (new GrDraggable (item, POINT_RG_CENTER, fill_or_stroke)); + addDragger (new GrDraggable (item, POINT_RG_FOCUS, fill_or_stroke)); + addDragger (new GrDraggable (item, POINT_RG_R1, fill_or_stroke)); + addDragger (new GrDraggable (item, POINT_RG_R2, fill_or_stroke)); +} + +/** +Add draggers for the linear gradient lg on item +*/ +void +GrDrag::addDraggersLinear (SPLinearGradient *lg, SPItem *item, bool fill_or_stroke) +{ + addDragger (new GrDraggable (item, POINT_LG_P1, fill_or_stroke)); + addDragger (new GrDraggable (item, POINT_LG_P2, fill_or_stroke)); +} + +/** +Artificially grab the knot of the dragger with this draggable; used by the gradient context +*/ +void +GrDrag::grabKnot (SPItem *item, guint point_num, bool fill_or_stroke, gint x, gint y, guint32 etime) +{ + GrDragger *dragger = getDraggerFor (item, point_num, fill_or_stroke); + if (dragger) { + sp_knot_start_dragging (dragger->knot, dragger->point, x, y, etime); + } +} + +/** +Regenerates the draggers list from the current selection; is called when selection is changed or +modified, also when a radial dragger needs to update positions of other draggers in the gradient +*/ +void +GrDrag::updateDraggers () +{ + // delete old draggers and deselect + for (GList const* i = this->draggers; i != NULL; i = i->next) { + delete ((GrDragger *) i->data); + } + g_list_free (this->draggers); + this->draggers = NULL; + this->selected = NULL; + + g_return_if_fail (this->selection != NULL); + + for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) { + + SPItem *item = SP_ITEM(i->data); + SPStyle *style = SP_OBJECT_STYLE (item); + + if (style && (style->fill.type == SP_PAINT_TYPE_PAINTSERVER)) { + SPObject *server = SP_OBJECT_STYLE_FILL_SERVER (item); + if (SP_IS_LINEARGRADIENT (server)) { + addDraggersLinear (SP_LINEARGRADIENT (server), item, true); + } else if (SP_IS_RADIALGRADIENT (server)) { + addDraggersRadial (SP_RADIALGRADIENT (server), item, true); + } + } + + if (style && (style->stroke.type == SP_PAINT_TYPE_PAINTSERVER)) { + SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER (item); + if (SP_IS_LINEARGRADIENT (server)) { + addDraggersLinear (SP_LINEARGRADIENT (server), item, false); + } else if (SP_IS_RADIALGRADIENT (server)) { + addDraggersRadial (SP_RADIALGRADIENT (server), item, false); + } + } + + + } +} + +/** +Regenerates the lines list from the current selection; is called on each move of a dragger, so that +lines are always in sync with the actual gradient +*/ +void +GrDrag::updateLines () +{ + // delete old lines + for (GSList const *i = this->lines; i != NULL; i = i->next) { + gtk_object_destroy( GTK_OBJECT (i->data)); + } + g_slist_free (this->lines); + this->lines = NULL; + + g_return_if_fail (this->selection != NULL); + + for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) { + + SPItem *item = SP_ITEM(i->data); + + SPStyle *style = SP_OBJECT_STYLE (item); + + if (style && (style->fill.type == SP_PAINT_TYPE_PAINTSERVER)) { + SPObject *server = SP_OBJECT_STYLE_FILL_SERVER (item); + if (SP_IS_LINEARGRADIENT (server)) { + this->addLine (sp_item_gradient_get_coords (item, POINT_LG_P1, true), sp_item_gradient_get_coords (item, POINT_LG_P2, true), GR_LINE_COLOR_FILL); + } else if (SP_IS_RADIALGRADIENT (server)) { + NR::Point center = sp_item_gradient_get_coords (item, POINT_RG_CENTER, true); + this->addLine (center, sp_item_gradient_get_coords (item, POINT_RG_R1, true), GR_LINE_COLOR_FILL); + this->addLine (center, sp_item_gradient_get_coords (item, POINT_RG_R2, true), GR_LINE_COLOR_FILL); + } + } + + if (style && (style->stroke.type == SP_PAINT_TYPE_PAINTSERVER)) { + SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER (item); + if (SP_IS_LINEARGRADIENT (server)) { + this->addLine (sp_item_gradient_get_coords (item, POINT_LG_P1, false), sp_item_gradient_get_coords (item, POINT_LG_P2, false), GR_LINE_COLOR_STROKE); + } else if (SP_IS_RADIALGRADIENT (server)) { + NR::Point center = sp_item_gradient_get_coords (item, POINT_RG_CENTER, false); + this->addLine (center, sp_item_gradient_get_coords (item, POINT_RG_R1, false), GR_LINE_COLOR_STROKE); + this->addLine (center, sp_item_gradient_get_coords (item, POINT_RG_R2, false), GR_LINE_COLOR_STROKE); + } + } + } +} + +/** +Regenerates the levels list from the current selection +*/ +void +GrDrag::updateLevels () +{ + hor_levels.clear(); + vert_levels.clear(); + + g_return_if_fail (this->selection != NULL); + + for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) { + SPItem *item = SP_ITEM(i->data); + NR::Rect rect = sp_item_bbox_desktop (item); + // Remember the edges of the bbox and the center axis + hor_levels.push_back(rect.min()[NR::Y]); + hor_levels.push_back(rect.max()[NR::Y]); + hor_levels.push_back(0.5 * (rect.min()[NR::Y] + rect.max()[NR::Y])); + vert_levels.push_back(rect.min()[NR::X]); + vert_levels.push_back(rect.max()[NR::X]); + vert_levels.push_back(0.5 * (rect.min()[NR::X] + rect.max()[NR::X])); + } +} + +void +GrDrag::selected_reverse_vector () +{ + if (selected == NULL) + return; + + for (GSList const* i = selected->draggables; i != NULL; i = i->next) { + GrDraggable *draggable = (GrDraggable *) i->data; + + sp_item_gradient_reverse_vector (draggable->item, draggable->fill_or_stroke); + } +} + +void +GrDrag::selected_move (double x, double y) +{ + if (selected == NULL) + return; + + selected->point += NR::Point (x, y); + selected->point_original = selected->point; + sp_knot_moveto (selected->knot, &(selected->point)); + + selected->fireDraggables (true); + + selected->updateDependencies(true); + + // we did an undoable action + sp_document_done (SP_DT_DOCUMENT (desktop)); +} + +void +GrDrag::selected_move_screen (double x, double y) +{ + gdouble zoom = desktop->current_zoom(); + gdouble zx = x / zoom; + gdouble zy = y / zoom; + + selected_move (zx, zy); +} + +void +GrDrag::select_next () +{ + if (selected == NULL || g_list_find(draggers, selected)->next == NULL) { + if (draggers) + setSelected ((GrDragger *) draggers->data); + } else { + setSelected ((GrDragger *) g_list_find(draggers, selected)->next->data); + } +} + +void +GrDrag::select_prev () +{ + if (selected == NULL || g_list_find(draggers, selected)->prev == NULL) { + if (draggers) + setSelected ((GrDragger *) g_list_last (draggers)->data); + } else { + setSelected ((GrDragger *) g_list_find(draggers, selected)->prev->data); + } +} + + +/* + 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/gradient-drag.h b/src/gradient-drag.h new file mode 100644 index 000000000..365da9b92 --- /dev/null +++ b/src/gradient-drag.h @@ -0,0 +1,143 @@ +#ifndef __GRADIENT_DRAG_H__ +#define __GRADIENT_DRAG_H__ + +/* + * On-canvas gradient dragging + * + * Authors: + * bulia byak + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#include + +#include +#include + +struct SPItem; +struct SPKnot; +namespace NR { +class Point; +} + +/** +This class represents a single draggable point of a gradient. It remembers the item +which has the gradient, whether it's fill or stroke, and the point number (from the +GrPoint enum). +*/ +struct GrDraggable { + GrDraggable(SPItem *item, guint point_num, bool fill_or_stroke); + ~GrDraggable(); + + SPItem *item; + guint point_num; + bool fill_or_stroke; + + bool mayMerge (GrDraggable *da2); + + inline int equals (GrDraggable *other) { + return ((item == other->item) && (point_num == other->point_num) && (fill_or_stroke == other->fill_or_stroke)); + } +}; + +struct GrDrag; + +/** +This class holds together a visible on-canvas knot and a list of draggables that need to +be moved when the knot moves. Normally there's one draggable in the list, but there may +be more when draggers are snapped together. +*/ +struct GrDragger { + GrDragger (GrDrag *parent, NR::Point p, GrDraggable *draggable); + ~GrDragger(); + + GrDrag *parent; + + SPKnot *knot; + + // position of the knot, desktop coords + NR::Point point; + // position of the knot before it began to drag; updated when released + NR::Point point_original; + + /** Connection to \a knot's "moved" signal, for blocking it (unused?). */ + guint handler_id; + + GSList *draggables; + + void addDraggable(GrDraggable *draggable); + + void updateKnotShape(); + void updateTip(); + + void moveThisToDraggable (SPItem *item, guint point_num, bool fill_or_stroke, bool write_repr); + void moveOtherToDraggable (SPItem *item, guint point_num, bool fill_or_stroke, bool write_repr); + void updateDependencies (bool write_repr); + + bool mayMerge (GrDragger *other); + bool mayMerge (GrDraggable *da2); + + bool isA (guint point_num); + bool isA (SPItem *item, guint point_num, bool fill_or_stroke); + + void fireDraggables (bool write_repr, bool scale_radial = false, bool merging_focus = false); +}; + +/** +This is the root class of the gradient dragging machinery. It holds lists of GrDraggers +and of lines (simple canvas items). It also remembers one of the draggers as selected. +*/ +struct GrDrag { + GrDrag(SPDesktop *desktop); + ~GrDrag(); + + void addLine (NR::Point p1, NR::Point p2, guint32 rgba); + + void addDragger (GrDraggable *draggable); + + void addDraggersRadial (SPRadialGradient *rg, SPItem *item, bool fill_or_stroke); + void addDraggersLinear (SPLinearGradient *lg, SPItem *item, bool fill_or_stroke); + + GrDragger *selected; + void setSelected (GrDragger *dragger); + + GrDragger *getDraggerFor (SPItem *item, guint point_num, bool fill_or_stroke); + + void grabKnot (SPItem *item, guint point_num, bool fill_or_stroke, gint x, gint y, guint32 etime); + + bool local_change; + + SPDesktop *desktop; + Inkscape::Selection *selection; + sigc::connection sel_changed_connection; + sigc::connection sel_modified_connection; + + sigc::connection style_set_connection; + sigc::connection style_query_connection; + + // lists of edges of selection bboxes, to snap draggers to + std::vector hor_levels; + std::vector vert_levels; + + GList *draggers; + GSList *lines; + + void updateDraggers (); + void updateLines (); + void updateLevels (); + + void selected_move (double x, double y); + void selected_move_screen (double x, double y); + + void select_next (); + void select_prev (); + + void selected_reverse_vector (); +}; + +#endif diff --git a/src/grid-snapper.cpp b/src/grid-snapper.cpp new file mode 100644 index 000000000..61bfa30d4 --- /dev/null +++ b/src/grid-snapper.cpp @@ -0,0 +1,78 @@ +/** + * \file grid-snapper.cpp + * \brief Snapping things to grids. + * + * Authors: + * Lauris Kaplinski + * Frank Felfe + * Carl Hetherington + * + * Copyright (C) 1999-2002 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-namedview.h" +#include "display/canvas-grid.h" + +/** + * \return x rounded to the nearest multiple of c1 plus c0. + * + * \note + * If c1==0 (and c0 is finite), then returns +/-inf. This makes grid spacing of zero + * mean "ignore the grid in this dimention". We're currently discussing "good" semantics + * for guide/grid snapping. + */ + +/* FIXME: move this somewhere else, perhaps */ +static double round_to_nearest_multiple_plus(double x, double const c1, double const c0) +{ + return floor((x - c0) / c1 + .5) * c1 + c0; +} + +Inkscape::GridSnapper::GridSnapper(SPNamedView const *nv, NR::Coord const d) : LineSnapper(nv, d) +{ + +} + +Inkscape::LineSnapper::LineList Inkscape::GridSnapper::_getSnapLines(NR::Point const &p) const +{ + LineList s; + + SPCGrid *griditem = NULL; + for (GSList *l = _named_view->gridviews; l != NULL; l = l->next) { + // FIXME : this is a hack since there is only one view for now + // but when we'll handle multiple views, snapping should + // must be rethought and maybe only the current view + // should give back it's SHOWN lines to snap to + // For now, the last SPCGrid in _named_view->gridviews will be used. + griditem = SP_CGRID(l->data); + } + + g_assert(griditem != NULL); + + for (unsigned int i = 0; i < 2; ++i) { + + /* This is to make sure we snap to only visible grid lines */ + double const scale = griditem->scaled[i] ? griditem->empspacing : 1; + + NR::Coord const rounded = round_to_nearest_multiple_plus(p[i], + _named_view->gridspacing[i] * scale, + _named_view->gridorigin[i]); + + s.push_back(std::make_pair(NR::Dim2(i), rounded)); + } + + return s; +} + +/* + 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/grid-snapper.h b/src/grid-snapper.h new file mode 100644 index 000000000..247823ac7 --- /dev/null +++ b/src/grid-snapper.h @@ -0,0 +1,46 @@ +#ifndef SEEN_GRID_SNAPPER_H +#define SEEN_GRID_SNAPPER_H + +/** + * \file grid-snapper.h + * \brief Snapping things to grids. + * + * Authors: + * Lauris Kaplinski + * Frank Felfe + * Carl Hetherington + * + * Copyright (C) 1999-2002 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "line-snapper.h" + +namespace Inkscape +{ + +/// Snap to grid +class GridSnapper : public LineSnapper +{ +public: + GridSnapper(SPNamedView const *nv, NR::Coord const d); + +private: + LineList _getSnapLines(NR::Point const &p) const; +}; + +} + +#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 : diff --git a/src/guide-snapper.cpp b/src/guide-snapper.cpp new file mode 100644 index 000000000..e247c0451 --- /dev/null +++ b/src/guide-snapper.cpp @@ -0,0 +1,52 @@ +/** + * \file guide-snapper.cpp + * \brief Snapping things to guides. + * + * Authors: + * Lauris Kaplinski + * Frank Felfe + * Carl Hetherington + * + * Copyright (C) 1999-2002 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "libnr/nr-values.h" +#include "libnr/nr-point-fns.h" +#include "sp-namedview.h" +#include "sp-guide.h" + +Inkscape::GuideSnapper::GuideSnapper(SPNamedView const *nv, NR::Coord const d) : LineSnapper(nv, d) +{ + +} + +Inkscape::GuideSnapper::LineList Inkscape::GuideSnapper::_getSnapLines(NR::Point const &p) const +{ + LineList s; + + for (GSList const *l = _named_view->guides; l != NULL; l = l->next) { + SPGuide const *g = SP_GUIDE(l->data); + + /* We assume here that guides are horizontal or vertical */ + if (g->normal == component_vectors[NR::X]) { + s.push_back(std::make_pair(NR::X, g->position)); + } else { + s.push_back(std::make_pair(NR::Y, g->position)); + } + } + + return s; +} + +/* + 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/guide-snapper.h b/src/guide-snapper.h new file mode 100644 index 000000000..f4c7b2de7 --- /dev/null +++ b/src/guide-snapper.h @@ -0,0 +1,52 @@ +#ifndef SEEN_GUIDE_SNAPPER_H +#define SEEN_GUIDE_SNAPPER_H + +/** + * \file guide-snapper.h + * \brief Snapping things to guides. + * + * Authors: + * Lauris Kaplinski + * Frank Felfe + * Carl Hetherington + * + * Copyright (C) 1999-2002 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "libnr/nr-forward.h" +#include "libnr/nr-coord.h" +#include "snapper.h" + +struct SPNamedView; + +namespace Inkscape +{ + +/// Snap to guides +class GuideSnapper : public LineSnapper +{ +public: + GuideSnapper(SPNamedView const *nv, NR::Coord const d); + +private: + LineList _getSnapLines(NR::Point const &p) const; +}; + +} + +#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 : diff --git a/src/help.cpp b/src/help.cpp new file mode 100644 index 000000000..919a200b0 --- /dev/null +++ b/src/help.cpp @@ -0,0 +1,59 @@ +#define __SP_HELP_C__ + +/* + * Help/About window + * + * Authors: + * Lauris Kaplinski + * bulia byak + * + * Copyright (C) 1999-2005 authors + * Copyright (C) 2000-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include "path-prefix.h" + +#include "help.h" +#include "file.h" + +#include "ui/dialog/aboutbox.h" + +void +sp_help_about (void) +{ + Inkscape::UI::Dialog::AboutBox::show_about(); +} + +void +sp_help_open_tutorial(GtkMenuItem *, gpointer data) +{ + gchar const *name = static_cast(data); + gchar *c = g_build_filename(INKSCAPE_TUTORIALSDIR, name, NULL); + sp_file_open(c, NULL, false, false); + g_free(c); +} + +void +sp_help_open_screen(gchar const *name) +{ + gchar *c = g_build_filename(INKSCAPE_SCREENSDIR, name, NULL); + sp_file_open(c, NULL, false, false); + g_free(c); +} + + +/* + 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/help.h b/src/help.h new file mode 100644 index 000000000..34569e840 --- /dev/null +++ b/src/help.h @@ -0,0 +1,35 @@ +#ifndef SEEN_HELP_H +#define SEEN_HELP_H + +/** + * Help/About window + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2003 authors + * Copyright (C) 2000-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include + +void sp_help_about(void); +void sp_help_open_tutorial(GtkMenuItem *menuitem, gpointer data); +void sp_help_open_screen(gchar const *name); + + +#endif /* !SEEN_HELP_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:encoding=utf-8:textwidth=99 : diff --git a/src/helper/.cvsignore b/src/helper/.cvsignore new file mode 100644 index 000000000..4a5454d13 --- /dev/null +++ b/src/helper/.cvsignore @@ -0,0 +1,9 @@ +Makefile +Makefile.in +.deps +.libs +sp-marshal.cpp +sp-marshal.h +makefile +.dirstamp +*-test diff --git a/src/helper/HACKING b/src/helper/HACKING new file mode 100644 index 000000000..2e3ddcbcd --- /dev/null +++ b/src/helper/HACKING @@ -0,0 +1,35 @@ +* +* Unsystematized and temporary helper things +* + +1. libspchelp + +Here you find: + +Useful add-ons to gnome-canvas, such as: + gnome_canvas_item_i2p_affine + +Canvas items useful for editing: +Ctrl - a small rectangle, which do no scale when parent is scaled +CtrlRect - 'rubberband' box +CtrlLine - simple straight line +Probably some of these will be rendered without libart at all - we do not +need antialias for rubberband. + +Yet to implement: +Guide - simple guideline. +FastSVP, FastVpath - not decided yet, but for node editing it seems to be +useful to have special rendering mode - fast stroking of single pixel wide +lines without antialias. + +2. libgt1 + +This is complete placeholder. All type1 parsing code is already included +in gnome-print development version and as soon as it will be more widely +available, I remove this. + +These programs are written mainly by Adobe and Raph Levien, probably others. +I modified gt1-parset1.c to force it to display most of iso-8859-1 characters. + +Lauris Kaplinski + diff --git a/src/helper/Makefile_insert b/src/helper/Makefile_insert new file mode 100644 index 000000000..9f061c090 --- /dev/null +++ b/src/helper/Makefile_insert @@ -0,0 +1,51 @@ +## Makefile.am fragment sourced by src/Makefile.am. + +# +# Miscellaneous unsystematized and temporary helper utilities +# +# libspchelp - canvas utilities, specific canvas items +# + +helper/all: helper/libspchelp.a + +helper/clean: + rm -f helper/libspchelp.a $(helper_libspchelp_a_OBJECTS) + +helper/unit-menu.$(OBJEXT): helper/sp-marshal.h + +helper_libspchelp_a_SOURCES = \ + helper/action.cpp \ + helper/action.h \ + helper/gnome-utils.cpp \ + helper/gnome-utils.h \ + helper/helper-forward.h \ + helper/png-write.cpp \ + helper/png-write.h \ + helper/sp-marshal.cpp \ + helper/sp-marshal.h \ + helper/stlport.h \ + helper/unit-menu.cpp \ + helper/unit-menu.h \ + helper/units.cpp \ + helper/units.h \ + helper/window.cpp \ + helper/window.h \ + helper/stock-items.cpp \ + helper/stock-items.h + + +# TODO: Check that the generated sp-marshal.h is the same as before. +helper/sp-marshal.h: helper/sp-marshal.list + glib-genmarshal --prefix=sp_marshal --header $(srcdir)/helper/sp-marshal.list > helper/tmp.$$$$ \ + && mv helper/tmp.$$$$ helper/sp-marshal.h + +helper/sp-marshal.cpp: helper/sp-marshal.list helper/sp-marshal.h + ( echo '#include "helper/sp-marshal.h"' && \ + glib-genmarshal --prefix=sp_marshal --body $(srcdir)/helper/sp-marshal.list ) \ + > helper/tmp.$$$$ \ + && mv helper/tmp.$$$$ helper/sp-marshal.cpp + +helper/sp-marshal.cpp helper/sp-marshal.h: Makefile + +helper_units_test_SOURCES = helper/units-test.cpp +helper_units_test_LDADD = helper/libspchelp.a -lglib-2.0 diff --git a/src/helper/action.cpp b/src/helper/action.cpp new file mode 100644 index 000000000..9584c5dae --- /dev/null +++ b/src/helper/action.cpp @@ -0,0 +1,228 @@ +#define __SP_ACTION_C__ + +/** \file + * SPAction implementation + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2003 Lauris Kaplinski + * + * This code is in public domain + */ + +#include + + +#include "helper/action.h" + +static void sp_action_class_init (SPActionClass *klass); +static void sp_action_init (SPAction *action); +static void sp_action_finalize (NRObject *object); + +static NRActiveObjectClass *parent_class; + +/** + * Register SPAction class and return its type. + */ +NRType +sp_action_get_type (void) +{ + static unsigned int type = 0; + if (!type) { + type = nr_object_register_type (NR_TYPE_ACTIVE_OBJECT, + "SPAction", + sizeof (SPActionClass), + sizeof (SPAction), + (void (*) (NRObjectClass *)) sp_action_class_init, + (void (*) (NRObject *)) sp_action_init); + } + return type; +} + +/** + * SPAction vtable initialization. + */ +static void +sp_action_class_init (SPActionClass *klass) +{ + NRObjectClass * object_class; + + object_class = (NRObjectClass *) klass; + + parent_class = (NRActiveObjectClass *) (((NRObjectClass *) klass)->parent); + + object_class->finalize = sp_action_finalize; + object_class->cpp_ctor = NRObject::invoke_ctor; +} + +/** + * Callback for SPAction object initialization. + */ +static void +sp_action_init (SPAction *action) +{ + action->sensitive = 0; + action->active = 0; + action->view = NULL; + action->id = action->name = action->tip = NULL; + action->image = NULL; +} + +/** + * Called before SPAction object destruction. + */ +static void +sp_action_finalize (NRObject *object) +{ + SPAction *action; + + action = (SPAction *) object; + + if (action->image) free (action->image); + if (action->tip) free (action->tip); + if (action->name) free (action->name); + if (action->id) free (action->id); + + ((NRObjectClass *) (parent_class))->finalize (object); +} + +/** + * Create new SPAction object and set its properties. + */ +SPAction * +sp_action_new(Inkscape::UI::View::View *view, + const gchar *id, + const gchar *name, + const gchar *tip, + const gchar *image, + Inkscape::Verb * verb) +{ + SPAction *action = (SPAction *)nr_object_new(SP_TYPE_ACTION); + + action->view = view; + action->sensitive = TRUE; + if (id) action->id = strdup (id); + if (name) action->name = strdup (name); + if (tip) action->tip = strdup (tip); + if (image) action->image = strdup (image); + action->verb = verb; + + return action; +} + +/** + \return None + \brief Executes an action + \param action The action to be executed + \param data Data that is passed into the action. This depends + on the situation that the action is used in. + + This function implements the 'action' in SPActions. It first validates + its parameters, making sure it got an action passed in. Then it + turns that action into its parent class of NRActiveObject. The + NRActiveObject allows for listeners to be attached to it. This + function goes through those listeners and calls them with the + vector that was attached to the listener. +*/ +void +sp_action_perform (SPAction *action, void * data) +{ + NRActiveObject *aobject; + + nr_return_if_fail (action != NULL); + nr_return_if_fail (SP_IS_ACTION (action)); + + aobject = NR_ACTIVE_OBJECT(action); + if (aobject->callbacks) { + unsigned int i; + for (i = 0; i < aobject->callbacks->length; i++) { + NRObjectListener *listener; + SPActionEventVector *avector; + + listener = &aobject->callbacks->listeners[i]; + avector = (SPActionEventVector *) listener->vector; + + if ((listener->size >= sizeof (SPActionEventVector)) && avector != NULL && avector->perform != NULL) { + avector->perform (action, listener->data, data); + } + } + } +} + +/** + * Change activation in all actions that can be taken with the action. + */ +void +sp_action_set_active (SPAction *action, unsigned int active) +{ + nr_return_if_fail (action != NULL); + nr_return_if_fail (SP_IS_ACTION (action)); + + if (active != action->active) { + NRActiveObject *aobject; + action->active = active; + aobject = (NRActiveObject *) action; + if (aobject->callbacks) { + unsigned int i; + for (i = 0; i < aobject->callbacks->length; i++) { + NRObjectListener *listener; + SPActionEventVector *avector; + listener = aobject->callbacks->listeners + i; + avector = (SPActionEventVector *) listener->vector; + if ((listener->size >= sizeof (SPActionEventVector)) && avector->set_active) { + avector->set_active (action, active, listener->data); + } + } + } + } +} + +/** + * Change sensitivity in all actions that can be taken with the action. + */ +void +sp_action_set_sensitive (SPAction *action, unsigned int sensitive) +{ + nr_return_if_fail (action != NULL); + nr_return_if_fail (SP_IS_ACTION (action)); + + if (sensitive != action->sensitive) { + NRActiveObject *aobject; + action->sensitive = sensitive; + aobject = (NRActiveObject *) action; + if (aobject->callbacks) { + unsigned int i; + for (i = 0; i < aobject->callbacks->length; i++) { + NRObjectListener *listener; + SPActionEventVector *avector; + listener = aobject->callbacks->listeners + i; + avector = (SPActionEventVector *) listener->vector; + if ((listener->size >= sizeof (SPActionEventVector)) && avector->set_sensitive) { + avector->set_sensitive (action, sensitive, listener->data); + } + } + } + } +} + +/** + * Return View associated with the action. + */ +Inkscape::UI::View::View * +sp_action_get_view (SPAction *action) +{ + g_return_val_if_fail (SP_IS_ACTION (action), NULL); + return action->view; +} + +/* + 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/helper/action.h b/src/helper/action.h new file mode 100644 index 000000000..1e3646439 --- /dev/null +++ b/src/helper/action.h @@ -0,0 +1,91 @@ +#ifndef __SP_ACTION_H__ +#define __SP_ACTION_H__ + +/** \file + * Inkscape UI action implementation + */ + +/* + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2003 Lauris Kaplinski + * + * This code is in public domain + */ + +/** A macro to get the GType for actions */ +#define SP_TYPE_ACTION (sp_action_get_type()) +/** A macro to cast and check the cast of changing an object to an action */ +#define SP_ACTION(o) (NR_CHECK_INSTANCE_CAST((o), SP_TYPE_ACTION, SPAction)) +/** A macro to check whether or not something is an action */ +#define SP_IS_ACTION(o) (NR_CHECK_INSTANCE_TYPE((o), SP_TYPE_ACTION)) + +#include "helper/helper-forward.h" +#include "libnr/nr-object.h" +#include "forward.h" + +//class Inkscape::UI::View::View; + +namespace Inkscape { +class Verb; +} + + +/** This is a structure that is used to hold all the possible + actions that can be taken with an action. These are the + function pointers available. */ +struct SPActionEventVector { + NRObjectEventVector object_vector; /**< Parent class */ + void (* perform)(SPAction *action, void *ldata, void *pdata); /**< Actually do the action of the event. Called by sp_perform_action */ + void (* set_active)(SPAction *action, unsigned active, void *data); /**< Callback for activation change */ + void (* set_sensitive)(SPAction *action, unsigned sensitive, void *data); /**< Callback for a change in sensitivity */ + void (* set_shortcut)(SPAction *action, unsigned shortcut, void *data); /**< Callback for setting the shortcut for this function */ +}; + +/** All the data that is required to be an action. This + structure identifies the action and has the data to + create menus and toolbars for the action */ +struct SPAction : public NRActiveObject { + unsigned sensitive : 1; /**< Value to track whether the action is sensitive */ + unsigned active : 1; /**< Value to track whether the action is active */ + Inkscape::UI::View::View *view; /**< The View to which this action is attached */ + gchar *id; /**< The identifier for the action */ + gchar *name; /**< Full text name of the action */ + gchar *tip; /**< A tooltip to describe the action */ + gchar *image; /**< An image to visually identify the action */ + Inkscape::Verb *verb; /**< The verb that produced this action */ +}; + +/** The action class is the same as its parent. */ +struct SPActionClass { + NRActiveObjectClass parent_class; /**< Parent Class */ +}; + +NRType sp_action_get_type(); + +SPAction *sp_action_new(Inkscape::UI::View::View *view, + gchar const *id, + gchar const *name, + gchar const *tip, + gchar const *image, + Inkscape::Verb *verb); + +void sp_action_perform(SPAction *action, void *data); +void sp_action_set_active(SPAction *action, unsigned active); +void sp_action_set_sensitive(SPAction *action, unsigned sensitive); +Inkscape::UI::View::View *sp_action_get_view(SPAction *action); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/helper/gnome-utils.cpp b/src/helper/gnome-utils.cpp new file mode 100644 index 000000000..aa70dd1a2 --- /dev/null +++ b/src/helper/gnome-utils.cpp @@ -0,0 +1,108 @@ +#define __GNOME_UTILS_C__ + +/* + * Helpers + * + * Author: + * Mitsuru Oka + * Lauris Kaplinski + * + * Copyright (C) 2002 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#include + +/** + * gnome_uri_list_extract_uris: + * @uri_list: an uri-list in the standard format. + * + * Returns a GList containing strings allocated with g_malloc + * that have been splitted from @uri-list. + */ +GList* +gnome_uri_list_extract_uris (const gchar* uri_list) +{ + const gchar *p, *q; + gchar *retval; + GList *result = NULL; + + g_return_val_if_fail (uri_list != NULL, NULL); + + p = uri_list; + + /* We don't actually try to validate the URI according to RFC + * 2396, or even check for allowed characters - we just ignore + * comments and trim whitespace off the ends. We also + * allow LF delimination as well as the specified CRLF. + */ + while (p) { + if (*p != '#') { + while (isspace(*p)) + p++; + + q = p; + while (*q && (*q != '\n') && (*q != '\r')) + q++; + + if (q > p) { + q--; + while (q > p && isspace(*q)) + q--; + + retval = (gchar*)g_malloc (q - p + 2); + strncpy (retval, p, q - p + 1); + retval[q - p + 1] = '\0'; + + result = g_list_prepend (result, retval); + } + } + p = strchr (p, '\n'); + if (p) + p++; + } + + return g_list_reverse (result); +} + +/** + * gnome_uri_list_extract_filenames: + * @uri_list: an uri-list in the standard format + * + * Returns a GList containing strings allocated with g_malloc + * that contain the filenames in the uri-list. + * + * Note that unlike gnome_uri_list_extract_uris() function, this + * will discard any non-file uri from the result value. + */ +GList* +gnome_uri_list_extract_filenames (const gchar* uri_list) +{ + GList *tmp_list, *node, *result; + + g_return_val_if_fail (uri_list != NULL, NULL); + + result = gnome_uri_list_extract_uris (uri_list); + + tmp_list = result; + while (tmp_list) { + gchar *s = (gchar*)tmp_list->data; + + node = tmp_list; + tmp_list = tmp_list->next; + + if (!strncmp (s, "file:", 5)) { + node->data = g_filename_from_uri (s, NULL, NULL); + /* not sure if this fallback is useful at all */ + if (!node->data) node->data = g_strdup (s+5); + } else { + result = g_list_remove_link(result, node); + g_list_free_1 (node); + } + g_free (s); + } + return result; +} diff --git a/src/helper/gnome-utils.h b/src/helper/gnome-utils.h new file mode 100644 index 000000000..0a28c95a9 --- /dev/null +++ b/src/helper/gnome-utils.h @@ -0,0 +1,36 @@ +/* + * GNOME Utils - Migration helper + * + * Author: + * GNOME Developer + * Mitsuru Oka + * Lauris Kaplinski + * + * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc. + * + * Released under GNU GPL + */ + + +#ifndef __GNOME_UTILS_H__ +#define __GNOME_UTILS_H__ + +#include +#include + +GList *gnome_uri_list_extract_uris(gchar const *uri_list); + +GList *gnome_uri_list_extract_filenames(gchar const *uri_list); + +#endif /* __GNOME_UTILS_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:encoding=utf-8:textwidth=99 : diff --git a/src/helper/helper-forward.h b/src/helper/helper-forward.h new file mode 100644 index 000000000..7cb0cddea --- /dev/null +++ b/src/helper/helper-forward.h @@ -0,0 +1,35 @@ +#ifndef __HELPER_FORWARD_H__ +#define __HELPER_FORWARD_H__ + +/* + * Forward declarations + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + +struct SPAction; +struct SPActionClass; +struct SPActionEventVector; + +struct SPUnit; +struct SPUnitSelector; +struct SPUnitSelectorClass; + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/helper/makefile.in b/src/helper/makefile.in new file mode 100644 index 000000000..edb3951aa --- /dev/null +++ b/src/helper/makefile.in @@ -0,0 +1,17 @@ +# Convenience stub makefile to call the real Makefile. + +@SET_MAKE@ + +# Explicit so that it's the default rule. +all: + cd .. && $(MAKE) helper/all + +clean %.a %.o: + cd .. && $(MAKE) helper/$@ + +.PHONY: all clean + +OBJEXT = @OBJEXT@ + +.SUFFIXES: +.SUFFIXES: .a .$(OBJEXT) diff --git a/src/helper/png-write.cpp b/src/helper/png-write.cpp new file mode 100644 index 000000000..21eca1efa --- /dev/null +++ b/src/helper/png-write.cpp @@ -0,0 +1,205 @@ +#define __SP_PNG_WRITE_C__ + +/* + * PNG file format utilities + * + * Authors: + * Lauris Kaplinski + * Whoever wrote this example in libpng documentation + * + * Copyright (C) 1999-2002 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include "png-write.h" +#include "io/sys.h" + +/* This is an example of how to use libpng to read and write PNG files. + * The file libpng.txt is much more verbose then this. If you have not + * read it, do so first. This was designed to be a starting point of an + * implementation. This is not officially part of libpng, and therefore + * does not require a copyright notice. + * + * This file does not currently compile, because it is missing certain + * parts, like allocating memory to hold an image. You will have to + * supply these parts to get it to compile. For an example of a minimal + * working PNG reader/writer, see pngtest.c, included in this distribution. + */ + +/* write a png file */ + +typedef struct SPPNGBD { + const guchar *px; + int rowstride; +} SPPNGBD; + +static int +sp_png_get_block_stripe (const guchar **rows, int row, int num_rows, void *data) +{ + SPPNGBD *bd = (SPPNGBD *) data; + + for (int r = 0; r < num_rows; r++) { + rows[r] = bd->px + (row + r) * bd->rowstride; + } + + return num_rows; +} + +int +sp_png_write_rgba (const gchar *filename, const guchar *px, int width, int height, int rowstride) +{ + SPPNGBD bd; + + bd.px = px; + bd.rowstride = rowstride; + + return sp_png_write_rgba_striped (filename, width, height, sp_png_get_block_stripe, &bd); +} + +int +sp_png_write_rgba_striped (const gchar *filename, int width, int height, + int (* get_rows) (const guchar **rows, int row, int num_rows, void *data), + void *data) +{ + FILE *fp; + png_structp png_ptr; + png_infop info_ptr; + png_color_8 sig_bit; + png_text text_ptr[3]; + png_uint_32 r; + + g_return_val_if_fail (filename != NULL, FALSE); + + /* open the file */ + + Inkscape::IO::dump_fopen_call(filename, "M"); + fp = Inkscape::IO::fopen_utf8name(filename, "wb"); + g_return_val_if_fail (fp != NULL, FALSE); + + /* Create and initialize the png_struct with the desired error handler + * functions. If you want to use the default stderr and longjump method, + * you can supply NULL for the last three parameters. We also check that + * the library version is compatible with the one used at compile time, + * in case we are using dynamically linked libraries. REQUIRED. + */ + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + + if (png_ptr == NULL) { + fclose(fp); + return FALSE; + } + + /* Allocate/initialize the image information data. REQUIRED */ + info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) { + fclose(fp); + png_destroy_write_struct(&png_ptr, NULL); + return FALSE; + } + + /* Set error handling. REQUIRED if you aren't supplying your own + * error hadnling functions in the png_create_write_struct() call. + */ + if (setjmp(png_ptr->jmpbuf)) { + /* If we get here, we had a problem reading the file */ + fclose(fp); + png_destroy_write_struct(&png_ptr, &info_ptr); + return FALSE; + } + + /* set up the output control if you are using standard C streams */ + png_init_io(png_ptr, fp); + + /* Set the image information here. Width and height are up to 2^31, + * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on + * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY, + * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB, + * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or + * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST + * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED + */ + png_set_IHDR(png_ptr, info_ptr, + width, + height, + 8, /* bit_depth */ + PNG_COLOR_TYPE_RGB_ALPHA, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, + PNG_FILTER_TYPE_BASE); + + /* otherwise, if we are dealing with a color image then */ + sig_bit.red = 8; + sig_bit.green = 8; + sig_bit.blue = 8; + /* if the image has an alpha channel then */ + sig_bit.alpha = 8; + png_set_sBIT(png_ptr, info_ptr, &sig_bit); + + /* Made by Inkscape comment */ + text_ptr[0].key = "Software"; + text_ptr[0].text = "www.inkscape.org"; + text_ptr[0].compression = PNG_TEXT_COMPRESSION_NONE; + png_set_text(png_ptr, info_ptr, text_ptr, 1); + + /* other optional chunks like cHRM, bKGD, tRNS, tIME, oFFs, pHYs, */ + /* note that if sRGB is present the cHRM chunk must be ignored + * on read and must be written in accordance with the sRGB profile */ + + /* Write the file header information. REQUIRED */ + png_write_info(png_ptr, info_ptr); + + /* Once we write out the header, the compression type on the text + * chunks gets changed to PNG_TEXT_COMPRESSION_NONE_WR or + * PNG_TEXT_COMPRESSION_zTXt_WR, so it doesn't get written out again + * at the end. + */ + + /* set up the transformations you want. Note that these are + * all optional. Only call them if you want them. + */ + + /* --- CUT --- */ + + /* The easiest way to write the image (you may have a different memory + * layout, however, so choose what fits your needs best). You need to + * use the first method if you aren't handling interlacing yourself. + */ + + r = 0; + while (r < static_cast< png_uint_32 > (height) ) { + png_bytep row_pointers[64]; + int h, n; + + h = MIN (height - r, 64); + n = get_rows ((const unsigned char **) row_pointers, r, h, data); + if (!n) break; + png_write_rows (png_ptr, row_pointers, n); + r += n; + } + + /* You can write optional chunks like tEXt, zTXt, and tIME at the end + * as well. + */ + + /* It is REQUIRED to call this to finish writing the rest of the file */ + png_write_end(png_ptr, info_ptr); + + /* if you allocated any text comments, free them here */ + + /* clean up after the write, and free any memory allocated */ + png_destroy_write_struct(&png_ptr, &info_ptr); + + /* close the file */ + fclose(fp); + + /* that's it */ + return TRUE; +} + diff --git a/src/helper/png-write.h b/src/helper/png-write.h new file mode 100644 index 000000000..2d2c16544 --- /dev/null +++ b/src/helper/png-write.h @@ -0,0 +1,23 @@ +#ifndef __SP_PNG_WRITE_H__ +#define __SP_PNG_WRITE_H__ + +/* + * PNG file format utilities + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +int sp_png_write_rgba(gchar const *filename, guchar const *px, int width, int height, int rowstride); + +int sp_png_write_rgba_striped(gchar const *filename, int width, int height, + int (* get_rows) (guchar const **rows, int row, int num_rows, void *data), + void *data); + +#endif diff --git a/src/helper/sp-marshal.cpp.mingw b/src/helper/sp-marshal.cpp.mingw new file mode 100644 index 000000000..8a7e7648c --- /dev/null +++ b/src/helper/sp-marshal.cpp.mingw @@ -0,0 +1,520 @@ +#include "helper/sp-marshal.h" + +#include + + +#ifdef G_ENABLE_DEBUG +#define g_marshal_value_peek_boolean(v) g_value_get_boolean (v) +#define g_marshal_value_peek_char(v) g_value_get_char (v) +#define g_marshal_value_peek_uchar(v) g_value_get_uchar (v) +#define g_marshal_value_peek_int(v) g_value_get_int (v) +#define g_marshal_value_peek_uint(v) g_value_get_uint (v) +#define g_marshal_value_peek_long(v) g_value_get_long (v) +#define g_marshal_value_peek_ulong(v) g_value_get_ulong (v) +#define g_marshal_value_peek_int64(v) g_value_get_int64 (v) +#define g_marshal_value_peek_uint64(v) g_value_get_uint64 (v) +#define g_marshal_value_peek_enum(v) g_value_get_enum (v) +#define g_marshal_value_peek_flags(v) g_value_get_flags (v) +#define g_marshal_value_peek_float(v) g_value_get_float (v) +#define g_marshal_value_peek_double(v) g_value_get_double (v) +#define g_marshal_value_peek_string(v) (char*) g_value_get_string (v) +#define g_marshal_value_peek_param(v) g_value_get_param (v) +#define g_marshal_value_peek_boxed(v) g_value_get_boxed (v) +#define g_marshal_value_peek_pointer(v) g_value_get_pointer (v) +#define g_marshal_value_peek_object(v) g_value_get_object (v) +#else /* !G_ENABLE_DEBUG */ +/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API. + * Do not access GValues directly in your code. Instead, use the + * g_value_get_*() functions + */ +#define g_marshal_value_peek_boolean(v) (v)->data[0].v_int +#define g_marshal_value_peek_char(v) (v)->data[0].v_int +#define g_marshal_value_peek_uchar(v) (v)->data[0].v_uint +#define g_marshal_value_peek_int(v) (v)->data[0].v_int +#define g_marshal_value_peek_uint(v) (v)->data[0].v_uint +#define g_marshal_value_peek_long(v) (v)->data[0].v_long +#define g_marshal_value_peek_ulong(v) (v)->data[0].v_ulong +#define g_marshal_value_peek_int64(v) (v)->data[0].v_int64 +#define g_marshal_value_peek_uint64(v) (v)->data[0].v_uint64 +#define g_marshal_value_peek_enum(v) (v)->data[0].v_int +#define g_marshal_value_peek_flags(v) (v)->data[0].v_uint +#define g_marshal_value_peek_float(v) (v)->data[0].v_float +#define g_marshal_value_peek_double(v) (v)->data[0].v_double +#define g_marshal_value_peek_string(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_param(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_boxed(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_pointer(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_object(v) (v)->data[0].v_pointer +#endif /* !G_ENABLE_DEBUG */ + + +/* NONE:NONE (sp-marshal.list:2) */ + +/* NONE:UINT (sp-marshal.list:3) */ + +/* NONE:POINTER (sp-marshal.list:4) */ + +/* NONE:POINTER,BOOLEAN (sp-marshal.list:5) */ +void +sp_marshal_VOID__POINTER_BOOLEAN (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__POINTER_BOOLEAN) (gpointer data1, + gpointer arg_1, + gboolean arg_2, + gpointer data2); + register GMarshalFunc_VOID__POINTER_BOOLEAN callback; + register GCClosure *cc = (GCClosure*) closure; + register gpointer data1, data2; + + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_VOID__POINTER_BOOLEAN) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + g_marshal_value_peek_pointer (param_values + 1), + g_marshal_value_peek_boolean (param_values + 2), + data2); +} + +/* NONE:POINTER,UINT (sp-marshal.list:6) */ +void +sp_marshal_VOID__POINTER_UINT (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__POINTER_UINT) (gpointer data1, + gpointer arg_1, + guint arg_2, + gpointer data2); + register GMarshalFunc_VOID__POINTER_UINT callback; + register GCClosure *cc = (GCClosure*) closure; + register gpointer data1, data2; + + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_VOID__POINTER_UINT) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + g_marshal_value_peek_pointer (param_values + 1), + g_marshal_value_peek_uint (param_values + 2), + data2); +} + +/* NONE:POINTER,DOUBLE (sp-marshal.list:7) */ +void +sp_marshal_VOID__POINTER_DOUBLE (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__POINTER_DOUBLE) (gpointer data1, + gpointer arg_1, + gdouble arg_2, + gpointer data2); + register GMarshalFunc_VOID__POINTER_DOUBLE callback; + register GCClosure *cc = (GCClosure*) closure; + register gpointer data1, data2; + + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_VOID__POINTER_DOUBLE) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + g_marshal_value_peek_pointer (param_values + 1), + g_marshal_value_peek_double (param_values + 2), + data2); +} + +/* NONE:DOUBLE,DOUBLE (sp-marshal.list:8) */ +void +sp_marshal_VOID__DOUBLE_DOUBLE (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__DOUBLE_DOUBLE) (gpointer data1, + gdouble arg_1, + gdouble arg_2, + gpointer data2); + register GMarshalFunc_VOID__DOUBLE_DOUBLE callback; + register GCClosure *cc = (GCClosure*) closure; + register gpointer data1, data2; + + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_VOID__DOUBLE_DOUBLE) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + g_marshal_value_peek_double (param_values + 1), + g_marshal_value_peek_double (param_values + 2), + data2); +} + +/* NONE:STRING,BOOL (sp-marshal.list:9) */ +void +sp_marshal_VOID__STRING_BOOLEAN (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__STRING_BOOLEAN) (gpointer data1, + gpointer arg_1, + gboolean arg_2, + gpointer data2); + register GMarshalFunc_VOID__STRING_BOOLEAN callback; + register GCClosure *cc = (GCClosure*) closure; + register gpointer data1, data2; + + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_VOID__STRING_BOOLEAN) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + g_marshal_value_peek_string (param_values + 1), + g_marshal_value_peek_boolean (param_values + 2), + data2); +} + +/* BOOLEAN:NONE (sp-marshal.list:10) */ +void +sp_marshal_BOOLEAN__VOID (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data) +{ + typedef gboolean (*GMarshalFunc_BOOLEAN__VOID) (gpointer data1, + gpointer data2); + register GMarshalFunc_BOOLEAN__VOID callback; + register GCClosure *cc = (GCClosure*) closure; + register gpointer data1, data2; + gboolean v_return; + + g_return_if_fail (return_value != NULL); + g_return_if_fail (n_param_values == 1); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_BOOLEAN__VOID) (marshal_data ? marshal_data : cc->callback); + + v_return = callback (data1, + data2); + + g_value_set_boolean (return_value, v_return); +} + +/* BOOLEAN:UINT (sp-marshal.list:11) */ +void +sp_marshal_BOOLEAN__UINT (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data) +{ + typedef gboolean (*GMarshalFunc_BOOLEAN__UINT) (gpointer data1, + guint arg_1, + gpointer data2); + register GMarshalFunc_BOOLEAN__UINT callback; + register GCClosure *cc = (GCClosure*) closure; + register gpointer data1, data2; + gboolean v_return; + + g_return_if_fail (return_value != NULL); + g_return_if_fail (n_param_values == 2); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_BOOLEAN__UINT) (marshal_data ? marshal_data : cc->callback); + + v_return = callback (data1, + g_marshal_value_peek_uint (param_values + 1), + data2); + + g_value_set_boolean (return_value, v_return); +} + +/* BOOLEAN:POINTER (sp-marshal.list:12) */ +void +sp_marshal_BOOLEAN__POINTER (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data) +{ + typedef gboolean (*GMarshalFunc_BOOLEAN__POINTER) (gpointer data1, + gpointer arg_1, + gpointer data2); + register GMarshalFunc_BOOLEAN__POINTER callback; + register GCClosure *cc = (GCClosure*) closure; + register gpointer data1, data2; + gboolean v_return; + + g_return_if_fail (return_value != NULL); + g_return_if_fail (n_param_values == 2); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_BOOLEAN__POINTER) (marshal_data ? marshal_data : cc->callback); + + v_return = callback (data1, + g_marshal_value_peek_pointer (param_values + 1), + data2); + + g_value_set_boolean (return_value, v_return); +} + +/* BOOLEAN:POINTER,UINT (sp-marshal.list:13) */ +void +sp_marshal_BOOLEAN__POINTER_UINT (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data) +{ + typedef gboolean (*GMarshalFunc_BOOLEAN__POINTER_UINT) (gpointer data1, + gpointer arg_1, + guint arg_2, + gpointer data2); + register GMarshalFunc_BOOLEAN__POINTER_UINT callback; + register GCClosure *cc = (GCClosure*) closure; + register gpointer data1, data2; + gboolean v_return; + + g_return_if_fail (return_value != NULL); + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_BOOLEAN__POINTER_UINT) (marshal_data ? marshal_data : cc->callback); + + v_return = callback (data1, + g_marshal_value_peek_pointer (param_values + 1), + g_marshal_value_peek_uint (param_values + 2), + data2); + + g_value_set_boolean (return_value, v_return); +} + +/* BOOLEAN:POINTER,POINTER (sp-marshal.list:14) */ +void +sp_marshal_BOOLEAN__POINTER_POINTER (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data) +{ + typedef gboolean (*GMarshalFunc_BOOLEAN__POINTER_POINTER) (gpointer data1, + gpointer arg_1, + gpointer arg_2, + gpointer data2); + register GMarshalFunc_BOOLEAN__POINTER_POINTER callback; + register GCClosure *cc = (GCClosure*) closure; + register gpointer data1, data2; + gboolean v_return; + + g_return_if_fail (return_value != NULL); + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_BOOLEAN__POINTER_POINTER) (marshal_data ? marshal_data : cc->callback); + + v_return = callback (data1, + g_marshal_value_peek_pointer (param_values + 1), + g_marshal_value_peek_pointer (param_values + 2), + data2); + + g_value_set_boolean (return_value, v_return); +} + +/* INT:POINTER,POINTER (sp-marshal.list:15) */ +void +sp_marshal_INT__POINTER_POINTER (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data) +{ + typedef gint (*GMarshalFunc_INT__POINTER_POINTER) (gpointer data1, + gpointer arg_1, + gpointer arg_2, + gpointer data2); + register GMarshalFunc_INT__POINTER_POINTER callback; + register GCClosure *cc = (GCClosure*) closure; + register gpointer data1, data2; + gint v_return; + + g_return_if_fail (return_value != NULL); + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_INT__POINTER_POINTER) (marshal_data ? marshal_data : cc->callback); + + v_return = callback (data1, + g_marshal_value_peek_pointer (param_values + 1), + g_marshal_value_peek_pointer (param_values + 2), + data2); + + g_value_set_int (return_value, v_return); +} + +/* DOUBLE:POINTER,UINT (sp-marshal.list:16) */ +void +sp_marshal_DOUBLE__POINTER_UINT (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data) +{ + typedef gdouble (*GMarshalFunc_DOUBLE__POINTER_UINT) (gpointer data1, + gpointer arg_1, + guint arg_2, + gpointer data2); + register GMarshalFunc_DOUBLE__POINTER_UINT callback; + register GCClosure *cc = (GCClosure*) closure; + register gpointer data1, data2; + gdouble v_return; + + g_return_if_fail (return_value != NULL); + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_DOUBLE__POINTER_UINT) (marshal_data ? marshal_data : cc->callback); + + v_return = callback (data1, + g_marshal_value_peek_pointer (param_values + 1), + g_marshal_value_peek_uint (param_values + 2), + data2); + + g_value_set_double (return_value, v_return); +} + diff --git a/src/helper/sp-marshal.h.mingw b/src/helper/sp-marshal.h.mingw new file mode 100644 index 000000000..9b573c523 --- /dev/null +++ b/src/helper/sp-marshal.h.mingw @@ -0,0 +1,125 @@ + +#ifndef __sp_marshal_MARSHAL_H__ +#define __sp_marshal_MARSHAL_H__ + +#include + +G_BEGIN_DECLS + +/* NONE:NONE (sp-marshal.list:2) */ +#define sp_marshal_VOID__VOID g_cclosure_marshal_VOID__VOID +#define sp_marshal_NONE__NONE sp_marshal_VOID__VOID + +/* NONE:UINT (sp-marshal.list:3) */ +#define sp_marshal_VOID__UINT g_cclosure_marshal_VOID__UINT +#define sp_marshal_NONE__UINT sp_marshal_VOID__UINT + +/* NONE:POINTER (sp-marshal.list:4) */ +#define sp_marshal_VOID__POINTER g_cclosure_marshal_VOID__POINTER +#define sp_marshal_NONE__POINTER sp_marshal_VOID__POINTER + +/* NONE:POINTER,BOOLEAN (sp-marshal.list:5) */ +extern void sp_marshal_VOID__POINTER_BOOLEAN (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); +#define sp_marshal_NONE__POINTER_BOOLEAN sp_marshal_VOID__POINTER_BOOLEAN + +/* NONE:POINTER,UINT (sp-marshal.list:6) */ +extern void sp_marshal_VOID__POINTER_UINT (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); +#define sp_marshal_NONE__POINTER_UINT sp_marshal_VOID__POINTER_UINT + +/* NONE:POINTER,DOUBLE (sp-marshal.list:7) */ +extern void sp_marshal_VOID__POINTER_DOUBLE (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); +#define sp_marshal_NONE__POINTER_DOUBLE sp_marshal_VOID__POINTER_DOUBLE + +/* NONE:DOUBLE,DOUBLE (sp-marshal.list:8) */ +extern void sp_marshal_VOID__DOUBLE_DOUBLE (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); +#define sp_marshal_NONE__DOUBLE_DOUBLE sp_marshal_VOID__DOUBLE_DOUBLE + +/* NONE:STRING,BOOL (sp-marshal.list:9) */ +extern void sp_marshal_VOID__STRING_BOOLEAN (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); +#define sp_marshal_NONE__STRING_BOOL sp_marshal_VOID__STRING_BOOLEAN + +/* BOOLEAN:NONE (sp-marshal.list:10) */ +extern void sp_marshal_BOOLEAN__VOID (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); +#define sp_marshal_BOOLEAN__NONE sp_marshal_BOOLEAN__VOID + +/* BOOLEAN:UINT (sp-marshal.list:11) */ +extern void sp_marshal_BOOLEAN__UINT (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + +/* BOOLEAN:POINTER (sp-marshal.list:12) */ +extern void sp_marshal_BOOLEAN__POINTER (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + +/* BOOLEAN:POINTER,UINT (sp-marshal.list:13) */ +extern void sp_marshal_BOOLEAN__POINTER_UINT (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + +/* BOOLEAN:POINTER,POINTER (sp-marshal.list:14) */ +extern void sp_marshal_BOOLEAN__POINTER_POINTER (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + +/* INT:POINTER,POINTER (sp-marshal.list:15) */ +extern void sp_marshal_INT__POINTER_POINTER (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + +/* DOUBLE:POINTER,UINT (sp-marshal.list:16) */ +extern void sp_marshal_DOUBLE__POINTER_UINT (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + +G_END_DECLS + +#endif /* __sp_marshal_MARSHAL_H__ */ diff --git a/src/helper/sp-marshal.list b/src/helper/sp-marshal.list new file mode 100644 index 000000000..15ddb1ec4 --- /dev/null +++ b/src/helper/sp-marshal.list @@ -0,0 +1,16 @@ +# marshallers for sodipodi +NONE:NONE +NONE:UINT +NONE:POINTER +NONE:POINTER,BOOLEAN +NONE:POINTER,UINT +NONE:POINTER,DOUBLE +NONE:DOUBLE,DOUBLE +NONE:STRING,BOOL +BOOLEAN:NONE +BOOLEAN:UINT +BOOLEAN:POINTER +BOOLEAN:POINTER,UINT +BOOLEAN:POINTER,POINTER +INT:POINTER,POINTER +DOUBLE:POINTER,UINT diff --git a/src/helper/stlport.h b/src/helper/stlport.h new file mode 100644 index 000000000..c9389e814 --- /dev/null +++ b/src/helper/stlport.h @@ -0,0 +1,26 @@ +#ifndef __STL_PORT_H__ +#define __STK_PORT_H__ + + +#include +#include +#include + +template +class StlConv { +public : + static void slist(std::list &stlList, const GSList *slist) { + for (const GSList *l = slist; l != NULL; l = l->next) { + T item = reinterpret_cast(l->data); + stlList.push_back(item); + } + } + static void list(std::list &stlList, const GList *list) { + for (const GList *l = list; l != NULL; l = l->next) { + T item = reinterpret_cast(l->data); + stlList.push_back(item); + } + } +}; + +#endif diff --git a/src/helper/stock-items.cpp b/src/helper/stock-items.cpp new file mode 100644 index 000000000..fe2026043 --- /dev/null +++ b/src/helper/stock-items.cpp @@ -0,0 +1,267 @@ +#define __INK_STOCK_ITEMS__ + +/* + * Stock-items + * + * Stock Item management code + * + * Authors: + * John Cliff + * + * Copyright 2004 John Cliff + * + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define noSP_SS_VERBOSE + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include "path-prefix.h" + + +#include +#include "sp-gradient-fns.h" +#include "document-private.h" +#include "sp-pattern.h" +#include "sp-marker.h" +#include "desktop-handles.h" +#include "inkscape.h" + +#include "io/sys.h" + + + + +static SPObject *sp_gradient_load_from_svg(gchar const *name, SPDocument *current_doc); +static SPObject *sp_marker_load_from_svg(gchar const *name, SPDocument *current_doc); +static SPObject *sp_gradient_load_from_svg(gchar const *name, SPDocument *current_doc); + + +// FIXME: these should be merged with the icon loading code so they +// can share a common file/doc cache. This function should just +// take the dir to look in, and the file to check for, and cache +// against that, rather than the existing copy/paste code seen here. + +static SPObject * sp_marker_load_from_svg(gchar const *name, SPDocument *current_doc) +{ + static SPDocument *doc = NULL; + static unsigned int edoc = FALSE; + if (!current_doc) { + return NULL; + } + /* Try to load from document */ + if (!edoc && !doc) { + gchar *markers = g_build_filename(INKSCAPE_MARKERSDIR, "/markers.svg", NULL); + if (Inkscape::IO::file_test(markers, G_FILE_TEST_IS_REGULAR)) { + doc = sp_document_new(markers, FALSE); + } + g_free(markers); + if (doc) { + sp_document_ensure_up_to_date(doc); + } else { + edoc = TRUE; + } + } + if (!edoc && doc) { + /* Get the marker we want */ + SPObject *object = doc->getObjectById(name); + if (object && SP_IS_MARKER(object)) { + SPDefs *defs= (SPDefs *) SP_DOCUMENT_DEFS(current_doc); + Inkscape::XML::Node *mark_repr = SP_OBJECT_REPR(object)->duplicate(); + SP_OBJECT_REPR(defs)->addChild(mark_repr, NULL); + SPObject *cloned_item = current_doc->getObjectByRepr(mark_repr); + Inkscape::GC::release(mark_repr); + return cloned_item; + } + } + return NULL; +} + + +static SPObject * +sp_pattern_load_from_svg(gchar const *name, SPDocument *current_doc) +{ + static SPDocument *doc = NULL; + static unsigned int edoc = FALSE; + if (!current_doc) { + return NULL; + } + /* Try to load from document */ + if (!edoc && !doc) { + gchar *patterns = g_build_filename(INKSCAPE_PATTERNSDIR, "/patterns.svg", NULL); + if (Inkscape::IO::file_test(patterns, G_FILE_TEST_IS_REGULAR)) { + doc = sp_document_new(patterns, FALSE); + } + g_free(patterns); + if (doc) { + sp_document_ensure_up_to_date(doc); + } else { + edoc = TRUE; + } + } + if (!edoc && doc) { + /* Get the pattern we want */ + SPObject *object = doc->getObjectById(name); + if (object && SP_IS_PATTERN(object)) { + SPDefs *defs= (SPDefs *) SP_DOCUMENT_DEFS(current_doc); + Inkscape::XML::Node *pat_repr = SP_OBJECT_REPR(object)->duplicate(); + SP_OBJECT_REPR(defs)->addChild(pat_repr, NULL); + Inkscape::GC::release(pat_repr); + return object; + } + } + return NULL; +} + + +static SPObject * +sp_gradient_load_from_svg(gchar const *name, SPDocument *current_doc) +{ + static SPDocument *doc = NULL; + static unsigned int edoc = FALSE; + if (!current_doc) { + return NULL; + } + /* Try to load from document */ + if (!edoc && !doc) { + gchar *gradients = g_build_filename(INKSCAPE_GRADIENTSDIR, "/gradients.svg", NULL); + if (Inkscape::IO::file_test(gradients, G_FILE_TEST_IS_REGULAR)) { + doc = sp_document_new(gradients, FALSE); + } + g_free(gradients); + if (doc) { + sp_document_ensure_up_to_date(doc); + } else { + edoc = TRUE; + } + } + if (!edoc && doc) { + /* Get the gradient we want */ + SPObject *object = doc->getObjectById(name); + if (object && SP_IS_GRADIENT(object)) { + SPDefs *defs= (SPDefs *) SP_DOCUMENT_DEFS(current_doc); + Inkscape::XML::Node *pat_repr = SP_OBJECT_REPR(object)->duplicate(); + SP_OBJECT_REPR(defs)->addChild(pat_repr, NULL); + Inkscape::GC::release(pat_repr); + return object; + } + } + return NULL; +} + +// get_stock_item returns a pointer to an instance of the desired stock object in the current doc +// if necessary it will import the object. Copes with name clashes through use of the inkscape:stockid property +// This should be set to be the same as the id in the libary file. + +SPObject *get_stock_item(gchar const *urn) +{ + g_assert(urn != NULL); + + /* check its an inkscape URN */ + if (!strncmp (urn, "urn:inkscape:", 13)) { + + gchar const *e = urn + 13; + int a = 0; + gchar * name = g_strdup(e); + gchar *name_p = name; + while (*name_p != ':' && *name_p != '\0'){ + name_p++; + a++; + } + + if (*name_p ==':') { + name_p++; + } + + gchar * base = g_strndup(e, a); + + SPDesktop *desktop = inkscape_active_desktop(); + SPDocument *doc = SP_DT_DOCUMENT(desktop); + SPDefs *defs= (SPDefs *) SP_DOCUMENT_DEFS(doc); + + SPObject *object = NULL; + if (!strcmp(base, "marker")) { + for (SPObject *child = sp_object_first_child(SP_OBJECT(defs)); + child != NULL; + child = SP_OBJECT_NEXT(child)) + { + if (SP_OBJECT_REPR(child)->attribute("inkscape:stockid") && + !strcmp(name_p, SP_OBJECT_REPR(child)->attribute("inkscape:stockid")) && + SP_IS_MARKER(child)) + { + object = child; + } + } + + } + else if (!strcmp(base,"pattern")) { + for (SPObject *child = sp_object_first_child(SP_OBJECT(defs)) ; + child != NULL; + child = SP_OBJECT_NEXT(child) ) + { + if (SP_OBJECT_REPR(child)->attribute("inkscape:stockid") && + !strcmp(name_p, SP_OBJECT_REPR(child)->attribute("inkscape:stockid")) && + SP_IS_PATTERN(child)) + { + object = child; + } + } + + } + else if (!strcmp(base,"gradient")) { + for (SPObject *child = sp_object_first_child(SP_OBJECT(defs)); + child != NULL; + child = SP_OBJECT_NEXT(child)) + { + if (SP_OBJECT_REPR(child)->attribute("inkscape:stockid") && + !strcmp(name_p, SP_OBJECT_REPR(child)->attribute("inkscape:stockid")) && + SP_IS_GRADIENT(child)) + { + object = child; + } + } + + } + + if (object == NULL) { + + if (!strcmp(base, "marker")) { + object = sp_marker_load_from_svg(name_p, doc); + } + else if (!strcmp(base, "pattern")) { + object = sp_pattern_load_from_svg(name_p, doc); + } + else if (!strcmp(base, "gradient")) { + object = sp_gradient_load_from_svg(name_p, doc); + } + } + + g_free(base); + g_free(name); + + return object; + } + + else { + + SPDesktop *desktop = inkscape_active_desktop(); + SPDocument *doc = SP_DT_DOCUMENT(desktop); + SPObject *object = doc->getObjectById(urn); + + return object; + } +} + +/* + 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/helper/stock-items.h b/src/helper/stock-items.h new file mode 100644 index 000000000..ddad55415 --- /dev/null +++ b/src/helper/stock-items.h @@ -0,0 +1,20 @@ +#define __INK_STOCK_ITEMS__ + +/* + * Stock-items + * + * Stock Item management code + * + * Authors: + * John Cliff + * + * Copyright 2004 John Cliff + * + */ + +#include + +#include + +SPObject *get_stock_item(gchar const *urn); + diff --git a/src/helper/unit-menu.cpp b/src/helper/unit-menu.cpp new file mode 100644 index 000000000..34a2b6344 --- /dev/null +++ b/src/helper/unit-menu.cpp @@ -0,0 +1,372 @@ +#define __SP_UNIT_MENU_C__ + +/* + * Unit selector with autupdate capability + * + * Authors: + * Lauris Kaplinski + * bulia byak + * + * Copyright (C) 2000-2002 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define noUNIT_SELECTOR_VERBOSE + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include +#include +#include +#include +#include "helper/sp-marshal.h" +#include "helper/units.h" +#include "unit-menu.h" +#include "widgets/spw-utilities.h" + +struct SPUnitSelector { + GtkHBox box; + + GtkWidget *menu; + + guint bases; + GSList *units; + SPUnit const *unit; + gdouble ctmscale; + guint plural : 1; + guint abbr : 1; + + guint update : 1; + + GSList *adjustments; +}; + +struct SPUnitSelectorClass { + GtkHBoxClass parent_class; + + gboolean (* set_unit)(SPUnitSelector *us, SPUnit const *old, SPUnit const *new_unit); +}; + +enum {SET_UNIT, LAST_SIGNAL}; + +static void sp_unit_selector_class_init(SPUnitSelectorClass *klass); +static void sp_unit_selector_init(SPUnitSelector *selector); +static void sp_unit_selector_finalize(GObject *object); + +static GtkHBoxClass *unit_selector_parent_class; +static guint signals[LAST_SIGNAL] = {0}; + +GtkType +sp_unit_selector_get_type(void) +{ + static GtkType type = 0; + if (!type) { + static GtkTypeInfo const info = { + "SPUnitSelector", + sizeof(SPUnitSelector), + sizeof(SPUnitSelectorClass), + (GtkClassInitFunc) sp_unit_selector_class_init, + (GtkObjectInitFunc) sp_unit_selector_init, + NULL, NULL, NULL + }; + type = gtk_type_unique(GTK_TYPE_HBOX, &info); + } + return type; +} + +static void +sp_unit_selector_class_init(SPUnitSelectorClass *klass) +{ + GObjectClass *object_class; + GtkWidgetClass *widget_class; + + object_class = G_OBJECT_CLASS(klass); + widget_class = GTK_WIDGET_CLASS(klass); + + unit_selector_parent_class = (GtkHBoxClass*)gtk_type_class(GTK_TYPE_HBOX); + + signals[SET_UNIT] = g_signal_new("set_unit", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(SPUnitSelectorClass, set_unit), + NULL, NULL, + sp_marshal_BOOLEAN__POINTER_POINTER, + G_TYPE_BOOLEAN, 2, + G_TYPE_POINTER, G_TYPE_POINTER); + + object_class->finalize = sp_unit_selector_finalize; +} + +static void +sp_unit_selector_init(SPUnitSelector *us) +{ + us->ctmscale = 1.0; + us->abbr = FALSE; + us->plural = TRUE; + + us->menu = gtk_option_menu_new(); + + gtk_widget_show(us->menu); + gtk_box_pack_start(GTK_BOX(us), us->menu, TRUE, TRUE, 0); +} + +static void +sp_unit_selector_finalize(GObject *object) +{ + SPUnitSelector *selector = SP_UNIT_SELECTOR(object); + + if (selector->menu) { + selector->menu = NULL; + } + + while (selector->adjustments) { + gtk_object_unref(GTK_OBJECT(selector->adjustments->data)); + selector->adjustments = g_slist_remove(selector->adjustments, selector->adjustments->data); + } + + if (selector->units) { + sp_unit_free_list(selector->units); + } + + selector->unit = NULL; + + G_OBJECT_CLASS(unit_selector_parent_class)->finalize(object); +} + +GtkWidget * +sp_unit_selector_new(guint bases) +{ + SPUnitSelector *us = (SPUnitSelector*)gtk_type_new(SP_TYPE_UNIT_SELECTOR); + + sp_unit_selector_set_bases(us, bases); + + return (GtkWidget *) us; +} + +void +sp_unit_selector_setsize(GtkWidget *us, guint w, guint h) +{ + gtk_widget_set_size_request(((SPUnitSelector *) us)->menu, w, h); +} + +SPUnit const * +sp_unit_selector_get_unit(SPUnitSelector const *us) +{ + g_return_val_if_fail(us != NULL, NULL); + g_return_val_if_fail(SP_IS_UNIT_SELECTOR(us), NULL); + + return us->unit; +} + +static void +spus_unit_activate(GtkWidget *widget, SPUnitSelector *us) +{ + SPUnit const *unit = (SPUnit const *) gtk_object_get_data(GTK_OBJECT(widget), "unit"); + g_return_if_fail(unit != NULL); + +#ifdef UNIT_SELECTOR_VERBOSE + g_print("Old unit %s new unit %s\n", us->unit->name, unit->name); +#endif + + SPUnit const *old = us->unit; + us->unit = unit; + + us->update = TRUE; + + gboolean consumed = FALSE; + g_signal_emit(G_OBJECT(us), signals[SET_UNIT], 0, old, unit, &consumed); + + if ( !consumed + && ( unit->base == old->base + || ( unit->base == SP_UNIT_ABSOLUTE && old->base == SP_UNIT_DEVICE ) + || ( old->base == SP_UNIT_ABSOLUTE && unit->base == SP_UNIT_DEVICE ) ) ) { + // Either the same base, or absolute<->device: + /* Recalculate adjustments. */ + for (GSList *l = us->adjustments; l != NULL; l = g_slist_next(l)) { + GtkAdjustment *adj = GTK_ADJUSTMENT(l->data); + gdouble val = adj->value; +#ifdef UNIT_SELECTOR_VERBOSE + g_print("Old val %g ... ", val); +#endif + val = sp_convert_distance_full(val, *old, *unit); +#ifdef UNIT_SELECTOR_VERBOSE + g_print("new val %g\n", val); +#endif + adj->value = val; + } + /* need to separate the value changing from the notification + * or else the unit changes can break the calculations */ + for (GSList *l = us->adjustments; l != NULL; l = g_slist_next(l)) { + gtk_adjustment_value_changed(GTK_ADJUSTMENT(l->data)); + } + } else if (!consumed && unit->base != old->base) { + /* when the base changes, signal all the adjustments to get them + * to recalculate */ + for (GSList *l = us->adjustments; l != NULL; l = g_slist_next(l)) { + gtk_signal_emit_by_name(GTK_OBJECT(l->data), "value_changed"); + } + } + + us->update = FALSE; +} + +static void +spus_rebuild_menu(SPUnitSelector *us) +{ + if (GTK_OPTION_MENU(us->menu)->menu) { + gtk_option_menu_remove_menu(GTK_OPTION_MENU(us->menu)); + } + + GtkWidget *m = gtk_menu_new(); + + gtk_widget_show(m); + + gint pos = 0; + gint p = 0; + for (GSList *l = us->units; l != NULL; l = l->next) { + SPUnit const *u = (SPUnit*)l->data; + + // use only abbreviations in the menu + // i = gtk_menu_item_new_with_label((us->abbr) ? (us->plural) ? u->abbr_plural : u->abbr : (us->plural) ? u->plural : u->name); + GtkWidget *i = gtk_menu_item_new_with_label( u->abbr ); + + gtk_object_set_data(GTK_OBJECT(i), "unit", (gpointer) u); + gtk_signal_connect(GTK_OBJECT(i), "activate", GTK_SIGNAL_FUNC(spus_unit_activate), us); + + sp_set_font_size_smaller (i); + + gtk_widget_show(i); + + gtk_menu_shell_append(GTK_MENU_SHELL(m), i); + if (u == us->unit) pos = p; + p += 1; + } + + gtk_option_menu_set_menu(GTK_OPTION_MENU(us->menu), m); + + gtk_option_menu_set_history(GTK_OPTION_MENU(us->menu), pos); +} + +void +sp_unit_selector_set_bases(SPUnitSelector *us, guint bases) +{ + g_return_if_fail(us != NULL); + g_return_if_fail(SP_IS_UNIT_SELECTOR(us)); + + if (bases == us->bases) return; + + GSList *units = sp_unit_get_list(bases); + g_return_if_fail(units != NULL); + sp_unit_free_list(us->units); + us->units = units; + us->unit = (SPUnit*)units->data; + + spus_rebuild_menu(us); +} + +void +sp_unit_selector_add_unit(SPUnitSelector *us, SPUnit const *unit, int position) +{ + if (!g_slist_find(us->units, (gpointer) unit)) { + us->units = g_slist_insert(us->units, (gpointer) unit, position); + + spus_rebuild_menu(us); + } +} + +void +sp_unit_selector_set_unit(SPUnitSelector *us, SPUnit const *unit) +{ + g_return_if_fail(us != NULL); + g_return_if_fail(SP_IS_UNIT_SELECTOR(us)); + + if (unit == NULL) { + return; // silently return, by default a newly created selector uses pt + } + if (unit == us->unit) { + return; + } + + gint const pos = g_slist_index(us->units, (gpointer) unit); + g_return_if_fail(pos >= 0); + + gtk_option_menu_set_history(GTK_OPTION_MENU(us->menu), pos); + + SPUnit const *old = us->unit; + us->unit = unit; + + /* Recalculate adjustments */ + for (GSList *l = us->adjustments; l != NULL; l = l->next) { + GtkAdjustment *adj = GTK_ADJUSTMENT(l->data); + gdouble const val = sp_convert_distance_full(adj->value, *old, *unit); + gtk_adjustment_set_value(adj, val); + } +} + +void +sp_unit_selector_add_adjustment(SPUnitSelector *us, GtkAdjustment *adj) +{ + g_return_if_fail(us != NULL); + g_return_if_fail(SP_IS_UNIT_SELECTOR(us)); + g_return_if_fail(adj != NULL); + g_return_if_fail(GTK_IS_ADJUSTMENT(adj)); + + g_return_if_fail(!g_slist_find(us->adjustments, adj)); + + gtk_object_ref(GTK_OBJECT(adj)); + us->adjustments = g_slist_prepend(us->adjustments, adj); +} + +void +sp_unit_selector_remove_adjustment(SPUnitSelector *us, GtkAdjustment *adj) +{ + g_return_if_fail(us != NULL); + g_return_if_fail(SP_IS_UNIT_SELECTOR(us)); + g_return_if_fail(adj != NULL); + g_return_if_fail(GTK_IS_ADJUSTMENT(adj)); + + g_return_if_fail(g_slist_find(us->adjustments, adj)); + + us->adjustments = g_slist_remove(us->adjustments, adj); + gtk_object_unref(GTK_OBJECT(adj)); +} + +gboolean +sp_unit_selector_update_test(SPUnitSelector const *selector) +{ + g_return_val_if_fail(selector != NULL, FALSE); + g_return_val_if_fail(SP_IS_UNIT_SELECTOR(selector), FALSE); + + return selector->update; +} + +double +sp_unit_selector_get_value_in_pixels(SPUnitSelector const *selector, GtkAdjustment *adj) +{ + g_return_val_if_fail(selector != NULL, adj->value); + g_return_val_if_fail(SP_IS_UNIT_SELECTOR(selector), adj->value); + + return sp_units_get_pixels(adj->value, *(selector->unit)); +} + +void +sp_unit_selector_set_value_in_pixels(SPUnitSelector *selector, GtkAdjustment *adj, double value) +{ + g_return_if_fail(selector != NULL); + g_return_if_fail(SP_IS_UNIT_SELECTOR(selector)); + + gtk_adjustment_set_value(adj, sp_pixels_get_units(value, *(selector->unit))); +} + +/* + 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 : diff --git a/src/helper/unit-menu.h b/src/helper/unit-menu.h new file mode 100644 index 000000000..bf5bb260e --- /dev/null +++ b/src/helper/unit-menu.h @@ -0,0 +1,59 @@ +#ifndef __SP_UNIT_MENU_H__ +#define __SP_UNIT_MENU_H__ + +/* + * SPUnitMenu + * + * Generic (and quite unintelligent) grid item for gnome canvas + * + * Copyright (C) Lauris Kaplinski 2000 + * + */ + +#include +#include + +#include + + +/* Unit selector Widget */ + +#define SP_TYPE_UNIT_SELECTOR (sp_unit_selector_get_type()) +#define SP_UNIT_SELECTOR(o) (GTK_CHECK_CAST((o), SP_TYPE_UNIT_SELECTOR, SPUnitSelector)) +#define SP_UNIT_SELECTOR_CLASS(k) (GTK_CHECK_CLASS_CAST((k), SP_TYPE_UNIT_SELECTOR, SPUnitSelectorClass)) +#define SP_IS_UNIT_SELECTOR(o) (GTK_CHECK_TYPE((o), SP_TYPE_UNIT_SELECTOR)) +#define SP_IS_UNIT_SELECTOR_CLASS(k) (GTK_CHECK_CLASS_TYPE((k), SP_TYPE_UNIT_SELECTOR)) + +GType sp_unit_selector_get_type(void); + +GtkWidget *sp_unit_selector_new(guint bases); +void sp_unit_selector_setsize(GtkWidget *us, guint w, guint h); + +SPUnit const *sp_unit_selector_get_unit(SPUnitSelector const *selector); + +void sp_unit_selector_set_bases(SPUnitSelector *selector, guint bases); +void sp_unit_selector_add_unit(SPUnitSelector *selector, SPUnit const *unit, int position); + +void sp_unit_selector_set_unit(SPUnitSelector *selector, SPUnit const *unit); +void sp_unit_selector_add_adjustment(SPUnitSelector *selector, GtkAdjustment *adjustment); +void sp_unit_selector_remove_adjustment(SPUnitSelector *selector, GtkAdjustment *adjustment); + +gboolean sp_unit_selector_update_test(SPUnitSelector const *selector); + +double sp_unit_selector_get_value_in_pixels(SPUnitSelector const *selector, GtkAdjustment *adj); +void sp_unit_selector_set_value_in_pixels(SPUnitSelector *selector, GtkAdjustment *adj, double value); + + + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/helper/units-test.cpp b/src/helper/units-test.cpp new file mode 100644 index 000000000..1a983fece --- /dev/null +++ b/src/helper/units-test.cpp @@ -0,0 +1,115 @@ +#ifdef HAVE_CONFIG_H +# include +#endif +#include + +#include +#include +#include + + +/* N.B. Wrongly returns false if both near 0. (Not a problem for current users.) */ +static bool +approx_equal(double const x, double const y) +{ + return fabs(x / y - 1) < 1e-15; +} + +static double +sp_units_get_points(double const x, SPUnit const &unit) +{ + SPUnit const &pt_unit = sp_unit_get_by_id(SP_UNIT_PT); + double const px = sp_units_get_pixels(x, unit); + return sp_pixels_get_units(px, pt_unit); +} + +static double +sp_points_get_units(double const pts, SPUnit const &unit) +{ + SPUnit const &pt_unit = sp_unit_get_by_id(SP_UNIT_PT); + double const px = sp_units_get_pixels(pts, pt_unit); + return sp_pixels_get_units(px, unit); +} + +static bool +test_conversions() +{ + utest_start("sp_units_get_pixels, sp_pixels_get_units"); + + struct Case { double x; char const *abbr; double pts; } const tests[] = { + { 1.0, "pt", 1.0 }, + { 5.0, "pt", 5.0 }, + { 1.0, "in", 72.0 }, + { 2.0, "in", 144.0 }, + { 254., "mm", 720.0 }, + { 254., "cm", 7200. }, + { 254., "m", 720000. }, + { 1.5, "mm", (15 * 72. / 254) } + }; + for (unsigned i = 0; i < G_N_ELEMENTS(tests); ++i) { + char name[80]; + Case const &c = tests[i]; + SPUnit const &unit = *sp_unit_get_by_abbreviation(N_(c.abbr)); + + double const calc_pts = sp_units_get_points(c.x, unit); + snprintf(name, sizeof(name), "%.1f %s -> %.1f pt", c.x, c.abbr, c.pts); + UTEST_TEST(name) { + UTEST_ASSERT(approx_equal(calc_pts, c.pts)); + } + + double const calc_x = sp_points_get_units(c.pts, unit); + snprintf(name, sizeof(name), "%.1f pt -> %.1f %s", c.pts, c.x, c.abbr); + UTEST_TEST(name) { + UTEST_ASSERT(approx_equal(calc_x, c.x)); + } + + double tmp = c.x; + bool const converted_to_pts = sp_convert_distance(&tmp, &unit, SP_PS_UNIT); + snprintf(name, sizeof(name), "convert %.1f %s -> %.1f pt", c.x, c.abbr, c.pts); + UTEST_TEST(name) { + UTEST_ASSERT(converted_to_pts); + UTEST_ASSERT(approx_equal(tmp, c.pts)); + } + + tmp = c.pts; + bool const converted_from_pts = sp_convert_distance(&tmp, SP_PS_UNIT, &unit); + snprintf(name, sizeof(name), "convert %.1f pt -> %.1f %s", c.pts, c.x, c.abbr); + UTEST_TEST(name) { + UTEST_ASSERT(converted_from_pts); + UTEST_ASSERT(approx_equal(tmp, c.x)); + } + } + return utest_end(); +} + +static bool +test_unit_table() +{ + utest_start("unit table"); + UTEST_TEST("sp_units_table_sane") { + UTEST_ASSERT(sp_units_table_sane()); + } + return utest_end(); +} + +int +main(int argc, char *argv[]) +{ + int const ret = ( ( test_conversions() + && test_unit_table() ) + ? EXIT_SUCCESS + : EXIT_FAILURE ); + return ret; +} + + +/* + 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/helper/units.cpp b/src/helper/units.cpp new file mode 100644 index 000000000..448f60302 --- /dev/null +++ b/src/helper/units.cpp @@ -0,0 +1,260 @@ +#define __SP_PAPER_C__ + +/* + * SPUnit + * + * Ported from libgnomeprint + * + * Authors: + * Dirk Luetjens + * Yves Arrouye + * Lauris Kaplinski + * bulia byak + * + * Copyright 1999-2001 Ximian, Inc. and authors + * + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "helper/units.h" +#include +#include "unit-constants.h" +#include "svg/svg-length.h" + +/* todo: use some fancy unit program */ + +/* The order determines the order of the list returned by sp_unit_get_list. + * (It can also affect string lookups if there are any duplicates in the + * current locale... hopefully none.) If you re-order this list, then you must + * also re-order the SPUnitId enum values accordingly. Run `make check' (which + * calls sp_unit_table_sane) to ensure that the two are in sync. + */ +SPUnit const sp_units[] = { + {SP_UNIT_SCALE, SP_UNIT_DIMENSIONLESS, 1.0, NONE, SVGLength::NONE, N_("Unit"), "", N_("Units"), ""}, + {SP_UNIT_PT, SP_UNIT_ABSOLUTE, PX_PER_PT, SP_PT, SVGLength::PT, N_("Point"), N_("pt"), N_("Points"), N_("Pt")}, + {SP_UNIT_PX, SP_UNIT_DEVICE, PX_PER_PX, SP_PX, SVGLength::PX, N_("Pixel"), N_("px"), N_("Pixels"), N_("Px")}, + /* You can add new elements from this point forward */ + {SP_UNIT_PERCENT, SP_UNIT_DIMENSIONLESS, 0.01, NONE, SVGLength::PERCENT, N_("Percent"), N_("%"), N_("Percents"), N_("%")}, + {SP_UNIT_MM, SP_UNIT_ABSOLUTE, PX_PER_MM, SP_MM, SVGLength::MM, N_("Millimeter"), N_("mm"), N_("Millimeters"), N_("mm")}, + {SP_UNIT_CM, SP_UNIT_ABSOLUTE, PX_PER_CM, SP_CM, SVGLength::CM, N_("Centimeter"), N_("cm"), N_("Centimeters"), N_("cm")}, + {SP_UNIT_M, SP_UNIT_ABSOLUTE, PX_PER_M, SP_M, SVGLength::NONE, N_("Meter"), N_("m"), N_("Meters"), N_("m")}, // no svg_unit + {SP_UNIT_IN, SP_UNIT_ABSOLUTE, PX_PER_IN, SP_IN, SVGLength::INCH, N_("Inch"), N_("in"), N_("Inches"), N_("in")}, + /* Volatiles do not have default, so there are none here */ + // TRANSLATORS: for info, see http://www.w3.org/TR/REC-CSS2/syndata.html#length-units + {SP_UNIT_EM, SP_UNIT_VOLATILE, 1.0, NONE, SVGLength::EM, N_("Em square"), N_("em"), N_("Em squares"), N_("em")}, + // TRANSLATORS: for info, see http://www.w3.org/TR/REC-CSS2/syndata.html#length-units + {SP_UNIT_EX, SP_UNIT_VOLATILE, 1.0, NONE, SVGLength::EX, N_("Ex square"), N_("ex"), N_("Ex squares"), N_("ex")}, +}; + +#define sp_num_units G_N_ELEMENTS(sp_units) + +SPUnit const * +sp_unit_get_by_abbreviation(gchar const *abbreviation) +{ + g_return_val_if_fail(abbreviation != NULL, NULL); + + for (unsigned i = 0 ; i < sp_num_units ; i++) { + if (!g_strcasecmp(abbreviation, sp_units[i].abbr)) return &sp_units[i]; + if (!g_strcasecmp(abbreviation, sp_units[i].abbr_plural)) return &sp_units[i]; + } + + return NULL; +} + +gchar const * +sp_unit_get_abbreviation(SPUnit const *unit) +{ + g_return_val_if_fail(unit != NULL, NULL); + + return unit->abbr; +} + +gchar const * +sp_unit_get_plural (SPUnit const *unit) +{ + g_return_val_if_fail(unit != NULL, NULL); + + return unit->plural; +} + +SPMetric +sp_unit_get_metric(SPUnit const *unit) +{ + g_return_val_if_fail(unit != NULL, NONE); + + return unit->metric; +} + +guint +sp_unit_get_svg_unit(SPUnit const *unit) +{ + g_return_val_if_fail(unit != NULL, NONE); + + return unit->svg_unit; +} + +GSList * +sp_unit_get_list(guint bases) +{ + g_return_val_if_fail((bases & ~SP_UNITS_ALL) == 0, NULL); + + GSList *units = NULL; + for (unsigned i = sp_num_units ; i--; ) { + if (bases & sp_units[i].base) { + units = g_slist_prepend(units, (gpointer) &sp_units[i]); + } + } + + return units; +} + +void +sp_unit_free_list(GSList *units) +{ + g_slist_free(units); +} + +/* These are pure utility */ +/* Return TRUE if conversion is possible */ +gboolean +sp_convert_distance(gdouble *distance, SPUnit const *from, SPUnit const *to) +{ + g_return_val_if_fail(distance != NULL, FALSE); + g_return_val_if_fail(from != NULL, FALSE); + g_return_val_if_fail(to != NULL, FALSE); + + if (from == to) return TRUE; + if ((from->base == SP_UNIT_DIMENSIONLESS) || (to->base == SP_UNIT_DIMENSIONLESS)) { + *distance = *distance * from->unittobase / to->unittobase; + return TRUE; + } + if ((from->base == SP_UNIT_VOLATILE) || (to->base == SP_UNIT_VOLATILE)) return FALSE; + + if ((from->base == to->base) + || (from->base == SP_UNIT_DEVICE) && (to->base == SP_UNIT_ABSOLUTE) + || (from->base == SP_UNIT_ABSOLUTE) && (to->base == SP_UNIT_DEVICE)) + { + *distance = *distance * from->unittobase / to->unittobase; + return TRUE; + } + + return FALSE; +} + +/** @param devicetransform for device units. */ +/* TODO: Remove the ctmscale parameter given that we no longer have SP_UNIT_USERSPACE. */ +gdouble +sp_convert_distance_full(gdouble const from_dist, SPUnit const &from, SPUnit const &to) +{ + if (&from == &to) { + return from_dist; + } + if (from.base == to.base) { + gdouble ret = from_dist; + bool const succ = sp_convert_distance(&ret, &from, &to); + g_assert(succ); + return ret; + } + if ((from.base == SP_UNIT_DIMENSIONLESS) + || (to.base == SP_UNIT_DIMENSIONLESS)) + { + return from_dist * from.unittobase / to.unittobase; + } + g_return_val_if_fail(((from.base != SP_UNIT_VOLATILE) + && (to.base != SP_UNIT_VOLATILE)), + from_dist); + + gdouble absolute; + switch (from.base) { + case SP_UNIT_ABSOLUTE: + case SP_UNIT_DEVICE: + absolute = from_dist * from.unittobase; + break; + default: + g_warning("file %s: line %d: Illegal unit (base 0x%x)", __FILE__, __LINE__, from.base); + return from_dist; + } + + gdouble ret; + switch (to.base) { + default: + g_warning("file %s: line %d: Illegal unit (base 0x%x)", __FILE__, __LINE__, to.base); + /* FALL-THROUGH */ + case SP_UNIT_ABSOLUTE: + case SP_UNIT_DEVICE: + ret = absolute / to.unittobase; + break; + } + + return ret; +} + +/* Some more convenience */ + +gdouble +sp_units_get_pixels(gdouble const units, SPUnit const &unit) +{ + if (unit.base == SP_UNIT_ABSOLUTE || unit.base == SP_UNIT_DEVICE) { + return units * unit.unittobase; + } else { + g_warning("Different unit bases: No exact unit conversion available"); + return units * unit.unittobase; + } +} + +gdouble +sp_pixels_get_units(gdouble const pixels, SPUnit const &unit) +{ + if (unit.base == SP_UNIT_ABSOLUTE || unit.base == SP_UNIT_DEVICE) { + return pixels / unit.unittobase; + } else { + g_warning("Different unit bases: No exact unit conversion available"); + return pixels / unit.unittobase; + } +} + +bool +sp_units_table_sane() +{ + for (unsigned i = 0; i < G_N_ELEMENTS(sp_units); ++i) { + if (unsigned(sp_units[i].unit_id) != i) { + return false; + } + } + return true; +} + +/** Converts angle (in deg) to compass display */ +double +angle_to_compass(double angle) +{ + double ret = 90 - angle; + if (ret < 0) + ret = 360 + ret; + return ret; +} + +/** Converts angle (in deg) to compass display */ +double +angle_from_compass(double angle) +{ + double ret = 90 - angle; + if (ret > 180) + ret = ret - 180; + return ret; +} + + +/* + 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 : diff --git a/src/helper/units.h b/src/helper/units.h new file mode 100644 index 000000000..3acb65828 --- /dev/null +++ b/src/helper/units.h @@ -0,0 +1,146 @@ +#ifndef __SP_UNIT_H__ +#define __SP_UNIT_H__ + +/* + * SPUnit + * + * Ported from libgnomeprint + * + * Authors: + * Dirk Luetjens + * Yves Arrouye + * Lauris Kaplinski + * + * Copyright 1999-2001 Ximian, Inc. and authors + * + */ + +#include +#include +#include +#include "sp-metric.h" + + +/* + * Units and conversion methods used by libgnomeprint. + * + * You need those for certain config keys (like paper size), if you are + * interested in using these (look at gnome-print-config.h for discussion, + * why you may NOT be interested in paper size). + * + * Unit bases define set of mutually unrelated measuring systems (numbers, + * paper, screen and dimesionless user coordinates). Still, you can convert + * between those, specifying scaling factors explicitly. + * + * Paper (i.e. output) coordinates are taken as absolute real world units. + * It has some justification, because screen unit (pixel) size changes, + * if you change screen resolution, while you cannot change output on paper + * as easily (unless you have thermally contracting paper, of course). + * + */ + +struct SPUnit; +struct SPDistance; + +/* + * The base linear ("absolute") unit is 1/72th of an inch, i.e. the base unit of postscript. + */ + +/* + * Unit bases + */ +enum SPUnitBase { + SP_UNIT_DIMENSIONLESS = (1 << 0), /* For percentages and like */ + SP_UNIT_ABSOLUTE = (1 << 1), /* Real world distances - i.e. mm, cm... */ + SP_UNIT_DEVICE = (1 << 2), /* Pixels in the SVG/CSS sense. */ + SP_UNIT_VOLATILE = (1 << 3) /* em and ex */ +}; + +/* + * Units: indexes into sp_units. + */ +enum SPUnitId { + SP_UNIT_SCALE, // 1.0 == 100% + SP_UNIT_PT, // Postscript points: exactly 72 per inch + SP_UNIT_PX, // "Pixels" in the CSS sense; though Inkscape assumes a constant 90 per inch. + SP_UNIT_PERCENT, /* Note: In Inkscape this often means "relative to current value" (for + users to edit a value), rather than the SVG/CSS use of percentages. */ + SP_UNIT_MM, // millimetres + SP_UNIT_CM, // centimetres + SP_UNIT_M, // metres + SP_UNIT_IN, // inches + SP_UNIT_EM, // font-size of relevant font + SP_UNIT_EX, // x-height of relevant font + sp_max_unit_id = SP_UNIT_EX // For bounds-checking in sp_unit_get_by_id. +}; + +/* + * Notice, that for correct menus etc. you have to use + * ngettext method family yourself. For that reason we + * do not provide translations in unit names. + * I also do not know, whether to allow user-created units, + * because this would certainly confuse textdomain. + */ + +struct SPUnit { + SPUnitId unit_id; /* used as sanity check */ + SPUnitBase base; + gdouble unittobase; /* how many base units in this unit */ + SPMetric metric; // the corresponding SPMetric from sp-metrics.h + guint svg_unit; // the corresponding SVGLengthUnit + + /* When using, you must call "gettext" on them so they're translated */ + gchar const *name; + gchar const *abbr; + gchar const *plural; + gchar const *abbr_plural; +}; + +const SPUnit *sp_unit_get_by_abbreviation (const gchar *abbreviation); +/* When using, you must call "gettext" on them so they're translated */ +const gchar *sp_unit_get_abbreviation (const SPUnit *unit); +gchar const *sp_unit_get_plural (SPUnit const *unit); + +SPMetric sp_unit_get_metric(SPUnit const *unit); +guint sp_unit_get_svg_unit(SPUnit const *unit); + +extern SPUnit const sp_units[]; + +inline SPUnit const & +sp_unit_get_by_id(SPUnitId const id) +{ + /* inline because the compiler should optimize away the g_return_val_if_fail test in the + usual case that the argument value is known at compile-time, leaving just + "return sp_units[constant]". */ + unsigned const ix = unsigned(id); + g_return_val_if_fail(ix <= sp_max_unit_id, sp_units[SP_UNIT_PX]); + return sp_units[ix]; +} + +#define SP_PS_UNIT (&sp_unit_get_by_id(SP_UNIT_PT)) + + +/** Used solely by units-test.cpp. */ +bool sp_units_table_sane(); + +#define SP_UNITS_ALL (SP_UNIT_DIMENSIONLESS | SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE | SP_UNIT_VOLATILE) + +GSList *sp_unit_get_list (guint bases); +void sp_unit_free_list (GSList *units); + +/* These are pure utility */ +/* Return TRUE if conversion is possible, FALSE if unit bases differ */ +gboolean sp_convert_distance (gdouble *distance, const SPUnit *from, const SPUnit *to); + +/* If either one is NULL, transconverting to/from that base fails */ +/* Generic conversion between volatile units would be useless anyways */ +gdouble sp_convert_distance_full(gdouble const from_dist, SPUnit const &from, SPUnit const &to); + +/* Some more convenience */ +gdouble sp_units_get_pixels(gdouble const units, SPUnit const &unit); +gdouble sp_pixels_get_units(gdouble const pixels, SPUnit const &unit); + +double angle_to_compass(double angle); +double angle_from_compass(double angle); + +#endif diff --git a/src/helper/window.cpp b/src/helper/window.cpp new file mode 100644 index 000000000..346bd19f1 --- /dev/null +++ b/src/helper/window.cpp @@ -0,0 +1,47 @@ +#define __SP_WINDOW_C__ + +/* + * Generic window implementation + * + * Author: + * Lauris Kaplinski + * + * This code is in public domain + */ + +#ifdef HAVE_CONFIG_H +# include +#endif +#include + +#include "inkscape.h" +#include "shortcuts.h" +#include "desktop.h" +#include "event-context.h" + +static gboolean +sp_window_key_press (GtkWidget *widget, GdkEventKey *event) +{ + unsigned int shortcut; + shortcut = get_group0_keyval (event) | + ( event->state & GDK_SHIFT_MASK ? + SP_SHORTCUT_SHIFT_MASK : 0 ) | + ( event->state & GDK_CONTROL_MASK ? + SP_SHORTCUT_CONTROL_MASK : 0 ) | + ( event->state & GDK_MOD1_MASK ? + SP_SHORTCUT_ALT_MASK : 0 ); + return sp_shortcut_invoke (shortcut, SP_ACTIVE_DESKTOP); +} + +GtkWidget * +sp_window_new (const gchar *title, unsigned int resizeable) +{ + GtkWidget *window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title ((GtkWindow *) window, title); + gtk_window_set_resizable ((GtkWindow *) window, resizeable); + g_signal_connect_after ((GObject *) window, "key_press_event", (GCallback) sp_window_key_press, NULL); + + return window; +} + + diff --git a/src/helper/window.h b/src/helper/window.h new file mode 100644 index 000000000..764cb0413 --- /dev/null +++ b/src/helper/window.h @@ -0,0 +1,28 @@ +#ifndef __SP_WINDOW_H__ +#define __SP_WINDOW_H__ + +/* + * Generic window implementation + * + * Author: + * Lauris Kaplinski + * + * This code is in public domain + */ + +#include + +GtkWidget *sp_window_new (const gchar *title, unsigned int resizeable); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/inkjar/.cvsignore b/src/inkjar/.cvsignore new file mode 100644 index 000000000..e8014d011 --- /dev/null +++ b/src/inkjar/.cvsignore @@ -0,0 +1,5 @@ +Makefile +Makefile.in +.deps +makefile +.dirstamp diff --git a/src/inkjar/Makefile_insert b/src/inkjar/Makefile_insert new file mode 100644 index 000000000..0376015fa --- /dev/null +++ b/src/inkjar/Makefile_insert @@ -0,0 +1,10 @@ +## Makefile.am fragment sourced by src/Makefile.am. + +inkjar/all: inkjar/libinkjar.a + +inkjar/clean: + rm -f inkjar/libinkjar.a $(inkjar_libinkjar_OBJECTS) + +inkjar_libinkjar_a_SOURCES = \ + inkjar/jar.cpp \ + inkjar/jar.h diff --git a/src/inkjar/jar.cpp b/src/inkjar/jar.cpp new file mode 100644 index 000000000..655f11cbb --- /dev/null +++ b/src/inkjar/jar.cpp @@ -0,0 +1,541 @@ +/* + * Copyright (C) 1999, 2000 Bryan Burns + * Copyright (C) 2004 Johan Ceuppens + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +/* + * TODO/FIXME: + * - configure #ifdefs should be enabled + * - move to cstdlib instead of stdlib.h etc. + * - remove exit functions + * - move to clean C++ code + * - windowsify + * - remove a few g_free/g_mallocs + * - unseekable files + * - move to LGPL by rewriting macros + * - crcs for compressed files + * - put in eof + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + + +//#ifdef STDC_HEADERS +//#endif + +//#ifdef HAVE_UNISTD_H +//#endif + +//#ifdef HAVE_SYS_PARAM_H +//#else +//#define MAXPATHLEN 1024 +//#endif + +//#ifdef HAVE_DIRENT_H +//#endif + +//#ifdef HAVE_FCNTL_H +#include +//#endif + + +#include + +#include "jar.h" + +#include +#ifdef WORDS_BIGENDIAN + +#define L2BI(l) ((l & 0xff000000) >> 24) | \ +((l & 0x00ff0000) >> 8) | \ +((l & 0x0000ff00) << 8) | \ +((l & 0x000000ff) << 24); + +#define L2BS(l) ((l & 0xff00) >> 8) | ((l & 0x00ff) << 8); + +#endif + +namespace Inkjar { + +JarFile::JarFile(gchar const*new_filename) +{ + _filename = strdup(new_filename); + _last_filename = NULL; + fd = -1; +} + +//fixme: the following should probably just return a const gchar* and not +// use strdup +gchar *JarFile::get_last_filename() const +{ + return (_last_filename != NULL ? strdup(_last_filename) : NULL); +} + +JarFile::~JarFile() +{ + if (_filename != NULL) + g_free(_filename); + if (_last_filename != NULL) + g_free(_last_filename); +} + +bool JarFile::init_inflation() +{ + memset(&_zs, 0, sizeof(z_stream)); + + _zs.zalloc = Z_NULL; + _zs.zfree = Z_NULL; + _zs.opaque = Z_NULL; + + if(inflateInit2(&_zs, -15) != Z_OK) { + fprintf(stderr,"error initializing inflation!\n"); + return false; + } + + return true; +} + +bool JarFile::open() +{ + if ((fd = ::open(_filename, O_RDONLY)) < 0) { + fprintf(stderr, "open failed.\n"); + return false; + } + if (!init_inflation()) + return false; + + return true; +} + +bool JarFile::close() +{ + if (fd >= 0 && !::close(fd)) { + inflateEnd(&_zs); + return true; + } + return false; +} + +bool JarFile::read_signature() +{ + guint8 *bytes = (guint8 *)g_malloc(sizeof(guint8) * 4); + if (!read(bytes, 4)) { + g_free(bytes); + return false; + } + + guint32 signature = UNPACK_UB4(bytes, 0); + g_free(bytes); + +#ifdef DEBUG + std::printf("signature is %x\n", signature); +#endif + + if (signature == 0x08074b50) { + //skip data descriptor + bytes = (guint8 *)malloc(sizeof(guint8) * 12); + if (!read(bytes, 12)) { + g_free(bytes); + return false; + } + } else if (signature == 0x02014b50 || signature == 0x04034b50) { + return true; + } else { + return false; + } + return false; +} + +guint32 JarFile::get_crc(guint8 *bytes, guint16 flags) +{ + guint32 crc = 0; + //no data descriptor + if (!(flags & 0x0008)) { + crc = UNPACK_UB4(bytes, LOC_CRC); + +#ifdef DEBUG + std::printf("CRC from file is %x\n", crc); +#endif + } + + return crc; +} + +guint8 *JarFile::read_filename(guint16 filename_length) +{ + guint8 *filename = (guint8 *)g_malloc(sizeof(guint8) + * (filename_length+1)); + if (!read(filename, filename_length)) { + g_free(filename); + return NULL; + } + filename[filename_length] = '\0'; + +#ifdef DEBUG + std::printf("Filename is %s\n", filename); +#endif + + return filename; +} + +bool JarFile::check_compression_method(guint16 method, guint16 flags) +{ + return !(method != 8 && flags & 0x0008); +} + +GByteArray *JarFile::get_next_file_contents() +{ + guint8 *bytes; + GByteArray *gba = g_byte_array_new(); + + read_signature(); + + //get compressed size + bytes = (guint8 *)g_malloc(sizeof(guint8) * 30); + if (!read(bytes+4, 26)) { + g_free(bytes); + return NULL; + } + guint32 compressed_size = UNPACK_UB4(bytes, LOC_CSIZE); + guint16 filename_length = UNPACK_UB2(bytes, LOC_FNLEN); + guint16 eflen = UNPACK_UB2(bytes, LOC_EFLEN); + guint16 flags = UNPACK_UB2(bytes, LOC_EXTRA); + guint16 method = UNPACK_UB2(bytes, LOC_COMP); + + if (filename_length == 0) { + g_byte_array_free(gba, TRUE); + if (_last_filename != NULL) + g_free(_last_filename); + _last_filename = NULL; + return NULL; + } + + +#ifdef DEBUG + std::printf("Compressed size is %u\n", compressed_size); + std::printf("Filename length is %hu\n", filename_length); + std::printf("Extra field length is %hu\n", eflen); + std::printf("Flags are %#hx\n", flags); + std::printf("Compression method is %#hx\n", method); +#endif + + guint32 crc = get_crc(bytes, flags); + + gchar *filename = (gchar *)read_filename(filename_length); + g_free(bytes); + + if (filename == NULL) + return NULL; + + if (_last_filename != NULL) + g_free(_last_filename); + _last_filename = filename; + + //check if this is a directory and skip + + char *c_ptr; + if ((c_ptr = std::strrchr(filename, '/')) != NULL) { + if (*(++c_ptr) == '\0') { + return NULL; + } + } + + if (!check_compression_method(method, flags)) { + std::fprintf(stderr, "error in jar file\n"); + return NULL; + } + + if (method == 8 || flags & 0x0008) { + unsigned int file_length = 0;//uncompressed file length + lseek(fd, eflen, SEEK_CUR); + guint8 *file_data = get_compressed_file(compressed_size, file_length, + crc, flags); + if (file_data == NULL) { + g_byte_array_free(gba, FALSE); + return NULL; + } + g_byte_array_append(gba, file_data, file_length); + } else if (method == 0) { + guint8 *file_data = get_uncompressed_file(compressed_size, crc, + eflen, flags); + + if (file_data == NULL) { + g_byte_array_free(gba, TRUE); + return NULL; + } + g_byte_array_append(gba, file_data, compressed_size); + } else { + lseek(fd, compressed_size+eflen, SEEK_CUR); + g_byte_array_free(gba, FALSE); + return NULL; + } + + + return gba; +} + +guint8 *JarFile::get_uncompressed_file(guint32 compressed_size, guint32 crc, + guint16 eflen, guint16 flags) +{ + GByteArray *gba = g_byte_array_new(); + unsigned int out_a = 0; + unsigned int in_a = compressed_size; + guint8 *bytes; + guint32 crc2 = 0; + + crc2 = crc32(crc2, NULL, 0); + + bytes = (guint8 *)g_malloc(sizeof(guint8) * RDSZ); + while(out_a < compressed_size){ + unsigned int nbytes = (in_a > RDSZ ? RDSZ : in_a); + + if (!(nbytes = read(bytes, nbytes))) { + g_free(bytes); + return NULL; + } + + crc2 = crc32(crc2, (Bytef*)bytes, nbytes); + + g_byte_array_append (gba, bytes, nbytes); + out_a += nbytes; + in_a -= nbytes; + +#ifdef DEBUG + std::printf("%d bytes written\n", out_a); +#endif + } + lseek(fd, eflen, SEEK_CUR); + g_free(bytes); + + if (!check_crc(crc, crc2, flags)) { + bytes = gba->data; + g_byte_array_free(gba, FALSE);//FALSE argument does not free actual data + return NULL; + } + + return bytes; +} + +int JarFile::read(guint8 *buf, int count) +{ + int nbytes; + if ((nbytes = ::read(fd, buf, count)) != count) { + fprintf(stderr, "read error\n"); + exit(1); + return 0; + } + return nbytes; +} + +/* FIXME: this could probably use ZlibBuffer */ +guint8 *JarFile::get_compressed_file(guint32 compressed_size, + unsigned int& file_length, + guint32 oldcrc, guint16 flags) +{ + if (compressed_size == 0) + return NULL; + + guint8 in_buffer[RDSZ]; + guint8 out_buffer[RDSZ]; + int nbytes; + unsigned int leftover_in = compressed_size; + GByteArray *gba = g_byte_array_new(); + + _zs.avail_in = 0; + guint32 crc = crc32(0, Z_NULL, 0); + + do { + + if (!_zs.avail_in) { + + if ((nbytes = ::read(fd, in_buffer, + (leftover_in < RDSZ ? leftover_in : RDSZ))) + < 0) { + fprintf(stderr, "jarfile read error"); + } + _zs.avail_in = nbytes; + _zs.next_in = in_buffer; + crc = crc32(crc, in_buffer, _zs.avail_in); + leftover_in -= RDSZ; + } + _zs.next_out = out_buffer; + _zs.avail_out = RDSZ; + + int ret = inflate(&_zs, Z_NO_FLUSH); + if (RDSZ != _zs.avail_out) { + unsigned int tmp_len = RDSZ - _zs.avail_out; + guint8 *tmp_bytes = (guint8 *)g_malloc(sizeof(guint8) + * tmp_len); + memcpy(tmp_bytes, out_buffer, tmp_len); + g_byte_array_append(gba, tmp_bytes, tmp_len); + } + + if (ret == Z_STREAM_END) { + break; + } + if (ret != Z_OK) + std::printf("decompression error %d\n", ret); + } while (_zs.total_in < compressed_size); + + file_length = _zs.total_out; +#ifdef DEBUG + std::printf("done inflating\n"); + std::printf("%d bytes left over\n", _zs.avail_in); + std::printf("CRC is %x\n", crc); +#endif + + guint8 *ret_bytes; + if (check_crc(oldcrc, crc, flags) && gba->len > 0) + ret_bytes = gba->data; + else + ret_bytes = NULL; + g_byte_array_free(gba, FALSE); + + inflateReset(&_zs); + return ret_bytes; +} + +bool JarFile::check_crc(guint32 oldcrc, guint32 crc, guint16 flags) +{ + //fixme: does not work yet + + if(flags & 0x0008) { + guint8 *bytes = (guint8 *)g_malloc(sizeof(guint8) * 16); + if (!read(bytes, 16)) { + g_free(bytes); + return false; + } + + guint32 signature = UNPACK_UB4(bytes, 0); + g_free(bytes); + if(signature != 0x08074b50) { + fprintf(stderr, "missing data descriptor!\n"); + } + + crc = UNPACK_UB4(bytes, 4); + + } + if (oldcrc != crc) { +#ifdef DEBUG + std::fprintf(stderr, "Error! CRCs do not match! Got %x, expected %x\n", + oldcrc, crc); +#endif + } + return true; +} + +JarFile::JarFile(JarFile const& rhs) +{ + *this = rhs; +} + +JarFile& JarFile::operator=(JarFile const& rhs) +{ + if (this == &rhs) + return *this; + + _zs = rhs._zs;//fixme + if (_filename == NULL) + _filename = NULL; + else + _filename = strdup(rhs._filename); + if (_last_filename == NULL) + _last_filename = NULL; + else + _last_filename = strdup(rhs._last_filename); + fd = rhs.fd; + + return *this; +} + + +///////////////////////// +// JarFileReader // +///////////////////////// + +GByteArray *JarFileReader::get_next_file() +{ + if (_state == CLOSED) { + _jarfile.open(); + _state = OPEN; + } + + return _jarfile.get_next_file_contents(); +} + +JarFileReader& JarFileReader::operator=(JarFileReader const& rhs) +{ + if (&rhs == this) + return *this; + + _jarfile = rhs._jarfile; + _state = rhs._state; + + return *this; +} + +/* + * If the filename gets reset, a jarfile object gets generated again, + * ready to be opened for reading. + */ +void JarFileReader::set_filename(gchar const *new_filename) +{ + _jarfile.close(); + _jarfile = JarFile(new_filename); +} + +void JarFileReader::set_jarfile(JarFile const& new_jarfile) +{ + _jarfile = new_jarfile; +} + +JarFileReader::JarFileReader(JarFileReader const& rhs) +{ + *this = rhs; +} + +} // namespace Inkjar + + +#if 0 //testing code +#include "jar.h" +/* + * This program writes all the files from a jarfile to stdout and inflates + * where needed. + */ +int main(int argc, char *argv[]) +{ + gchar *filename; + if (argc < 2) { + filename = "./ide.jar\0"; + } else { + filename = argv[1]; + } + + Inkjar::JarFileReader jar_file_reader(filename); + + for (;;) { + GByteArray *gba = jar_file_reader.get_next_file(); + if (gba == NULL) { + char *c_ptr; + gchar *last_filename = jar_file_reader.get_last_filename(); + if (last_filename == NULL) + break; + if ((c_ptr = std::strrchr(last_filename, '/')) != NULL) { + if (*(++c_ptr) == '\0') { + g_free(last_filename); + continue; + } + } + } else if (gba->len > 0) + ::write(1, gba->data, gba->len); + else + break; + } + return 0; +} +#endif diff --git a/src/inkjar/jar.h b/src/inkjar/jar.h new file mode 100644 index 000000000..2340a74c7 --- /dev/null +++ b/src/inkjar/jar.h @@ -0,0 +1,151 @@ +#ifndef __INKJAR_JAR_H_ +#define __INKJAR_JAR_H_ +/* + * Copyright (C) 1999 Bryan Burns + * Copyright (C) 2004 Johan Ceuppens + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#include +#include + +namespace Inkjar { + +unsigned const RDSZ = 4096; + +//#define DEBUG 1 //uncommment for debug messages + +enum JarFileReaderState {CLOSED, OPEN}; + +//fixme: The following will be removed +typedef uint8_t ub1; +typedef uint16_t ub2; +typedef uint32_t ub4; + +#define LOC_EXTRA 6 /* extra bytes */ +#define LOC_COMP 8 /* compression method */ +#define LOC_MODTIME 10 /* last modification time */ +#define LOC_MODDATE 12 /* last modification date */ +#define LOC_CRC 14 /* CRC */ +#define LOC_CSIZE 18 /* compressed size */ +#define LOC_USIZE 22 /* uncompressed size */ +#define LOC_FNLEN 26 /* filename length */ +#define LOC_EFLEN 28 /* extra-field length */ + +#define CEN_COMP 10 /* compression method */ +#define CEN_MODTIME 12 +#define CEN_MODDATE 14 +#define CEN_CRC 16 +#define CEN_CSIZE 20 +#define CEN_USIZE 24 +#define CEN_FNLEN 28 +#define CEN_EFLEN 30 +#define CEN_COMLEN 32 +#define CEN_OFFSET 42 + + +/* macros */ +#define PACK_UB4(d, o, v) d[o] = (ub1)((v) & 0x000000ff); \ + d[o + 1] = (ub1)(((v) & 0x0000ff00) >> 8); \ + d[o + 2] = (ub1)(((v) & 0x00ff0000) >> 16); \ + d[o + 3] = (ub1)(((v) & 0xff000000) >> 24) + +#define PACK_UB2(d, o, v) d[o] = (ub1)((v) & 0x00ff); \ + d[o + 1] = (ub1)(((v) & 0xff00) >> 8) + +#define UNPACK_UB4(s, o) (ub4)s[o] + (((ub4)s[o + 1]) << 8) +\ + (((ub4)s[o + 2]) << 16) + (((ub4)s[o + 3]) << 24) + +#define UNPACK_UB2(s, o) (ub2)s[o] + (((ub2)s[o + 1]) << 8) + + + +/* + * JarFile: + * + * This is a wrapper class for canonical jarfile functions like reading, + * writing, seeking etc. JarFile is a dumb class with no state information. + * + * All memory allocations are done with g_malloc. + */ + +class JarFile { +public: + + JarFile() : fd(-1), _filename(NULL), _last_filename(NULL) {} + virtual ~JarFile(); + JarFile(gchar const *new_filename); + + GByteArray *get_next_file_contents(); + gchar *get_last_filename() const; + bool open(); + bool close(); + int read(guint8 *buf, int count); + + JarFile(JarFile const &rhs); + JarFile &operator=(JarFile const &rhs); + +private: + + int fd; + gchar *_filename; + z_stream _zs; + gchar *_last_filename; + + bool init_inflation(); + bool read_signature(); + guint32 get_crc(guint8 *bytes, guint16 flags); + guint8 *read_filename(guint16 filename_length); + bool check_compression_method(guint16 method, guint16 flags); + bool check_crc(guint32 oldcrc, guint32 crc, guint16 flags); + guint8 *get_compressed_file(guint32 compressed_size, + unsigned int &file_length, + guint32 oldcrc, guint16 flags); + guint8 *get_uncompressed_file(guint32 compressed_szie, guint32 crc, + guint16 eflen, guint16 flags); +}; // class JarFile + + +/* + * JarFileReader: + * + * This provides some smarter functions for operating on a jarfile object + * It should be able to grep for files or return the contents of a specific + * file. + */ + +class JarFileReader { +public: + + JarFileReader(gchar const *new_filename) + : _state(CLOSED), _jarfile(new_filename) {} + JarFileReader() : _state(CLOSED) {} + virtual ~JarFileReader() { if (_state == OPEN) _jarfile.close(); } + //fixme return types are incorrect + GByteArray *get_next_file();//fixme clean up return type + void set_filename(gchar const *new_filename); + void set_jarfile(JarFile const &new_jarfile); + gchar *get_last_filename() const { return _jarfile.get_last_filename(); }; + JarFileReader(JarFileReader const &rhs); + JarFileReader &operator=(JarFileReader const &rhs); +private: + JarFileReaderState _state; + JarFile _jarfile; + +}; // class JarFileReader + +} // namespace Inkjar +#endif // header guard + +/* + 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/inkjar/makefile.in b/src/inkjar/makefile.in new file mode 100644 index 000000000..0ccb68b59 --- /dev/null +++ b/src/inkjar/makefile.in @@ -0,0 +1,17 @@ +# Convenience stub makefile to call the real Makefile. + +@SET_MAKE@ + +# Explicit so that it's the default rule. +all: + cd .. && $(MAKE) inkjar/all + +clean %.a %.o: + cd .. && $(MAKE) inkjar/$@ + +.PHONY: all clean + +OBJEXT = @OBJEXT@ + +.SUFFIXES: +.SUFFIXES: .a .$(OBJEXT) diff --git a/src/inkscape-private.h b/src/inkscape-private.h new file mode 100644 index 000000000..e6e6a44ea --- /dev/null +++ b/src/inkscape-private.h @@ -0,0 +1,62 @@ +#ifndef __INKSCAPE_PRIVATE_H__ +#define __INKSCAPE_PRIVATE_H__ + +/* + * Some forward declarations + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define SP_TYPE_INKSCAPE (inkscape_get_type ()) +#define SP_INKSCAPE(obj) (GTK_CHECK_CAST ((obj), SP_TYPE_INKSCAPE, Inkscape)) +#define SP_INKSCAPE_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), SP_TYPE_INKSCAPE, InkscapeClass)) +#define SP_IS_INKSCAPE(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_INKSCAPE)) +#define SP_IS_INKSCAPE_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), SP_TYPE_INKSCAPE)) + +#include "forward.h" +#include "inkscape.h" + +namespace Inkscape { class Selection; } + +GType inkscape_get_type (void); + +void inkscape_ref (void); +void inkscape_unref (void); + +/* + * These are meant solely for desktop, document etc. implementations + */ + +void inkscape_selection_modified (Inkscape::Selection *selection, guint flags); +void inkscape_selection_changed (Inkscape::Selection * selection); +void inkscape_selection_set (Inkscape::Selection * selection); +void inkscape_eventcontext_set (SPEventContext * eventcontext); +void inkscape_add_desktop (SPDesktop * desktop); +void inkscape_remove_desktop (SPDesktop * desktop); +void inkscape_activate_desktop (SPDesktop * desktop); +void inkscape_reactivate_desktop (SPDesktop * desktop); +void inkscape_add_document (SPDocument *document); +void inkscape_remove_document (SPDocument *document); + +void inkscape_set_color (SPColor *color, float opacity); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/inkscape-stock.cpp b/src/inkscape-stock.cpp new file mode 100644 index 000000000..16bf883d0 --- /dev/null +++ b/src/inkscape-stock.cpp @@ -0,0 +1,51 @@ +/* + * @file inkscape-stock.h GTK+ Stock resources + * + * Authors: + * Robert Crosbie + * + * Copyright (C) 1999-2002 Authors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "gtk/gtkiconfactory.h" + + +void +inkscape_gtk_stock_init() { + static bool stock_initialized = false; + + if (stock_initialized) + return; + + GtkIconFactory *icon_factory = gtk_icon_factory_new(); + /* todo: Should we simply remove this file now that we're no longer + * calling gtk_icon_factory_add here? */ + gtk_icon_factory_add_default(icon_factory); + + stock_initialized = true; +} + +/* + 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 : diff --git a/src/inkscape-stock.h b/src/inkscape-stock.h new file mode 100644 index 000000000..0a18b7284 --- /dev/null +++ b/src/inkscape-stock.h @@ -0,0 +1,150 @@ +/* + * @file inkscape-stock.h GTK+ Stock resources + * + * Authors: + * Robert Crosbie + * + * Copyright (C) 1999-2002 Authors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _INKSCAPE_STOCK_H_ +#define _INKSCAPE_STOCK_H_ + +/**************************************************************************/ +/** @name Inkscape Stock images */ +/**************************************************************************/ +/*@{*/ + +/*stroke style*/ +//#define INKSCAPE_STOCK_ABOUT "inkscape-about" +#define INKSCAPE_STOCK_JOIN_MITER "join_miter" +#define INKSCAPE_STOCK_JOIN_ROUND "join_round" +#define INKSCAPE_STOCK_JOIN_BEVEL "join_bevel" +#define INKSCAPE_STOCK_CAP_BUTT "cap_butt" +#define INKSCAPE_STOCK_CAP_ROUND "cap_round" +#define INKSCAPE_STOCK_CAP_SQUARE "cap_square" +//#define INKSCAPE_STOCK_START_MARKER "start_marker" +//#define INKSCAPE_STOCK_MID_MARKER "mid_marker" +//#define INKSCAPE_STOCK_END_MARKER "end_marker" +//#define INKSCAPE_STOCK_MARKER_NONE "-none" +//#define INKSCAPE_STOCK_MARKER_FILLED_ARROW "-filled_arrow" +//#define INKSCAPE_STOCK_MARKER_HOLLOW_ARROW "-hollow_arrow" +//#define INKSCAPE_STOCK_MARKER_QTY (3) + +/*object properties*/ +#define INKSCAPE_STOCK_ROTATE_LEFT "transform_rotate" +#define INKSCAPE_STOCK_SCALE_HOR "transform_scale_hor" +#define INKSCAPE_STOCK_SCALE_VER "transform_scale_ver" +#define INKSCAPE_STOCK_ARROWS_HOR "arrows_hor" +#define INKSCAPE_STOCK_ARROWS_VER "arrows_ver" +//#define INKSCAPE_STOCK_DIMENSION_HOR "dimension_hor" +//#define INKSCAPE_STOCK_DIMENSION_VER "dimension_ver" + +/*text editing*/ +#define INKSCAPE_STOCK_WRITING_MODE_LR "writing_mode_lr" +#define INKSCAPE_STOCK_WRITING_MODE_TB "writing_mode_tb" +#define INKSCAPE_STOCK_TEXT_LETTER_SPACING "text_letter_spacing" +#define INKSCAPE_STOCK_TEXT_LINE_SPACING "text_line_spacing" +#define INKSCAPE_STOCK_TEXT_HORZ_KERN "text_horz_kern" +#define INKSCAPE_STOCK_TEXT_VERT_KERN "text_vert_kern" +#define INKSCAPE_STOCK_TEXT_ROTATION "text_rotation" +#define INKSCAPE_STOCK_TEXT_REMOVE_KERNS "text_remove_kerns" + +/*xml-tree*/ +#define INKSCAPE_STOCK_ADD_XML_ELEMENT_NODE "add_xml_element_node" +#define INKSCAPE_STOCK_ADD_XML_TEXT_NODE "add_xml_text_node" +#define INKSCAPE_STOCK_DUPLICATE_XML_NODE "duplicate_xml_node" +#define INKSCAPE_STOCK_DELETE_XML_NODE "delete_xml_node" +#define INKSCAPE_STOCK_DELETE_XML_ATTRIBUTE "delete_xml_attribute" +#define INKSCAPE_STOCK_SET "set" + +/*paint-selector*/ +#define INKSCAPE_STOCK_FILL_NONE "fill_none" +#define INKSCAPE_STOCK_FILL_SOLID "fill_solid" +#define INKSCAPE_STOCK_FILL_GRADIENT "fill_gradient" +#define INKSCAPE_STOCK_FILL_RADIAL "fill_radial" +#define INKSCAPE_STOCK_FILL_PATTERN "fill_pattern" +#define INKSCAPE_STOCK_FILL_UNSET "fill_unset" +#define INKSCAPE_STOCK_FILL_FRACTAL "fill_fractal" + +//#define INKSCAPE_STOCK_GUIDE_DIALOG "guide_dialog" + +//#define INKSCAPE_STOCK_EDIT_DUPLICATE "edit_duplicate" + +//#define INKSCAPE_STOCK_SELECTION_TOP "selection_top" +//#define INKSCAPE_STOCK_SELECTION_BOT "selection_bot" +//#define INKSCAPE_STOCK_SELECTION_UP "selection_up" +//#define INKSCAPE_STOCK_SELECTION_DOWN "selection_down" +//#define INKSCAPE_STOCK_SELECTION_GROUP "selection_group" +//#define INKSCAPE_STOCK_SELECTION_UNGROUP "selection_ungroup" +//#define INKSCAPE_STOCK_SELECTION_COMBINE "selection_combine" +//#define INKSCAPE_STOCK_SELECTION_BREAK "selection_break" + +//#define INKSCAPE_STOCK_OBJECT_ROTATE "object_rotate" +//#define INKSCAPE_STOCK_OBJECT_RESET "object_reset" +//#define INKSCAPE_STOCK_OBJECT_TOCURVE "object_tocurve" + +//#define INKSCAPE_STOCK_DRAW_SELECT "draw_select" +//#define INKSCAPE_STOCK_DRAW_NODE "draw_node" +//#define INKSCAPE_STOCK_DRAW_RECT "draw_rect" +//#define INKSCAPE_STOCK_DRAW_ARC "draw_arc" +//#define INKSCAPE_STOCK_DRAW_STAR "draw_star" +//#define INKSCAPE_STOCK_DRAW_SPIRAL "draw_spiral" +//#define INKSCAPE_STOCK_DRAW_FREEHAND "draw_freehand" +//#define INKSCAPE_STOCK_DRAW_PEN "draw_pen" +//#define INKSCAPE_STOCK_DRAW_DYNAHAND "draw_dynahand" +//#define INKSCAPE_STOCK_DRAW_TEXT "draw_text" +//#define INKSCAPE_STOCK_DRAW_ZOOM "draw_zoom" +//#define INKSCAPE_STOCK_DRAW_DROPPER "draw_dropper" + +//#define INKSCAPE_STOCK_ZOOM_IN "zoom_in" +//#define INKSCAPE_STOCK_ZOOM_OUT "zoom_out" +//#define INKSCAPE_STOCK_TOGGLE_GRID "toggle_grid" +//#define INKSCAPE_STOCK_TOGGLE_GUIDES "toggle_guides" +//#define INKSCAPE_STOCK_ZOOM_PAGE "zoom_page" +//#define INKSCAPE_STOCK_ZOOM_DRAW "zoom_draw" +//#define INKSCAPE_STOCK_ZOOM_SELECT "zoom_select" + +//#define INKSCAPE_STOCK_OBJECT_LAYOUT "object_layout" +//#define INKSCAPE_STOCK_OBJECT_TRANS "object_trans" +//#define INKSCAPE_STOCK_OBJECT_ALIGN "object_align" +//#define INKSCAPE_STOCK_OBJECT_FONT "object_font" + +#define INKSCAPE_STOCK_PROPERTIES_FILL_PAGE "properties_fill" +#define INKSCAPE_STOCK_PROPERTIES_STROKE_PAINT_PAGE "properties_stroke_paint" +#define INKSCAPE_STOCK_PROPERTIES_STROKE_PAGE "properties_stroke" + +/** + * Sets up the inkscape stock repository. + */ + +void inkscape_gtk_stock_init(void); + +/*@}*/ +#endif /* _INKSCAPE_STOCK_H_ */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=c++:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : + diff --git a/src/inkscape.cpp b/src/inkscape.cpp new file mode 100644 index 000000000..9c0d93173 --- /dev/null +++ b/src/inkscape.cpp @@ -0,0 +1,1413 @@ +#define __INKSCAPE_C__ + +/* + * Interface to main application + * + * Authors: + * Lauris Kaplinski + * bulia byak + * + * Copyright (C) 1999-2005 authors + * g++ port Copyright (C) 2003 Nathan Hurst + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + + +#include "debug/simple-event.h" +#include "debug/event-tracker.h" + +#ifndef WIN32 +# define HAS_PROC_SELF_EXE //to get path of executable +#else + +// For now to get at is_os_wide(). +# include "extension/internal/win32.h" +using Inkscape::Extension::Internal::PrintWin32; + +#define _WIN32_IE 0x0400 +//#define HAS_SHGetSpecialFolderPath +#define HAS_SHGetSpecialFolderLocation +#define HAS_GetModuleFileName +# include +#endif + +#include + +#include +#include + +#include +#include "helper/sp-marshal.h" +#include "dialogs/debugdialog.h" +#include "application/application.h" +#include "application/editor.h" +#include "preferences.h" + + +#include "document.h" +#include "desktop.h" +#include "desktop-handles.h" +#include "selection.h" +#include "event-context.h" +#include "inkscape-private.h" +#include "prefs-utils.h" +#include "xml/repr.h" +#include "io/sys.h" + +#include "extension/init.h" + +static Inkscape::Application *inkscape = NULL; + +/* Backbones of configuration xml data */ +#include "menus-skeleton.h" + +enum { + MODIFY_SELECTION, // global: one of selections modified + CHANGE_SELECTION, // global: one of selections changed + CHANGE_SUBSELECTION, // global: one of subselections (text selection, gradient handle, etc) changed + SET_SELECTION, // global: one of selections set + SET_EVENTCONTEXT, // tool switched + ACTIVATE_DESKTOP, // some desktop got focus + DEACTIVATE_DESKTOP, // some desktop lost focus + SHUTDOWN_SIGNAL, // inkscape is quitting + DIALOGS_HIDE, // user pressed F12 + DIALOGS_UNHIDE, // user pressed F12 + EXTERNAL_CHANGE, // a document was changed by some external means (undo or XML editor); this + // may not be reflected by a selection change and thus needs a separate signal + LAST_SIGNAL +}; + +#define DESKTOP_IS_ACTIVE(d) ((d) == inkscape->desktops->data) + + +/*################################ +# FORWARD DECLARATIONS +################################*/ + +gboolean inkscape_app_use_gui( Inkscape::Application const * app ); + +static void inkscape_class_init (Inkscape::ApplicationClass *klass); +static void inkscape_init (SPObject *object); +static void inkscape_dispose (GObject *object); + +static void inkscape_activate_desktop_private (Inkscape::Application *inkscape, SPDesktop *desktop); +static void inkscape_deactivate_desktop_private (Inkscape::Application *inkscape, SPDesktop *desktop); + +static bool inkscape_init_config (Inkscape::XML::Document *doc, const gchar *config_name, const gchar *skeleton, + unsigned int skel_size, + const gchar *e_mkdir, + const gchar *e_notdir, + const gchar *e_ccf, + const gchar *e_cwf, + const gchar *warn); + +struct Inkscape::Application { + GObject object; + Inkscape::XML::Document *menus; + GSList *documents; + GSList *desktops; + gchar *argv0; + gboolean dialogs_toggle; + gboolean use_gui; // may want to consider a virtual function + // for overriding things like the warning dlg's +}; + +struct Inkscape::ApplicationClass { + GObjectClass object_class; + + /* Signals */ + void (* change_selection) (Inkscape::Application * inkscape, Inkscape::Selection * selection); + void (* change_subselection) (Inkscape::Application * inkscape, SPDesktop *desktop); + void (* modify_selection) (Inkscape::Application * inkscape, Inkscape::Selection * selection, guint flags); + void (* set_selection) (Inkscape::Application * inkscape, Inkscape::Selection * selection); + void (* set_eventcontext) (Inkscape::Application * inkscape, SPEventContext * eventcontext); + void (* activate_desktop) (Inkscape::Application * inkscape, SPDesktop * desktop); + void (* deactivate_desktop) (Inkscape::Application * inkscape, SPDesktop * desktop); + void (* destroy_document) (Inkscape::Application *inkscape, SPDocument *doc); + void (* color_set) (Inkscape::Application *inkscape, SPColor *color, double opacity); + void (* shut_down) (Inkscape::Application *inkscape); + void (* dialogs_hide) (Inkscape::Application *inkscape); + void (* dialogs_unhide) (Inkscape::Application *inkscape); + void (* external_change) (Inkscape::Application *inkscape); +}; + +static GObjectClass * parent_class; +static guint inkscape_signals[LAST_SIGNAL] = {0}; + +static void (* segv_handler) (int) = NULL; + +#ifdef WIN32 +#define INKSCAPE_PROFILE_DIR "Inkscape" +#else +#define INKSCAPE_PROFILE_DIR ".inkscape" +#endif + +#define MENUS_FILE "menus.xml" + + +/** + * Retrieves the GType for the Inkscape Application object. + */ +GType +inkscape_get_type (void) +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof (Inkscape::ApplicationClass), + NULL, NULL, + (GClassInitFunc) inkscape_class_init, + NULL, NULL, + sizeof (Inkscape::Application), + 4, + (GInstanceInitFunc) inkscape_init, + NULL + }; + type = g_type_register_static (G_TYPE_OBJECT, "Inkscape_Application", &info, (GTypeFlags)0); + } + return type; +} + + +/** + * Initializes the inkscape class, registering all of its signal handlers + * and virtual functions + */ +static void +inkscape_class_init (Inkscape::ApplicationClass * klass) +{ + GObjectClass * object_class; + + object_class = (GObjectClass *) klass; + + parent_class = (GObjectClass *)g_type_class_peek_parent (klass); + + inkscape_signals[MODIFY_SELECTION] = g_signal_new ("modify_selection", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (Inkscape::ApplicationClass, modify_selection), + NULL, NULL, + sp_marshal_NONE__POINTER_UINT, + G_TYPE_NONE, 2, + G_TYPE_POINTER, G_TYPE_UINT); + inkscape_signals[CHANGE_SELECTION] = g_signal_new ("change_selection", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (Inkscape::ApplicationClass, change_selection), + NULL, NULL, + sp_marshal_NONE__POINTER, + G_TYPE_NONE, 1, + G_TYPE_POINTER); + inkscape_signals[CHANGE_SUBSELECTION] = g_signal_new ("change_subselection", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (Inkscape::ApplicationClass, change_subselection), + NULL, NULL, + sp_marshal_NONE__POINTER, + G_TYPE_NONE, 1, + G_TYPE_POINTER); + inkscape_signals[SET_SELECTION] = g_signal_new ("set_selection", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (Inkscape::ApplicationClass, set_selection), + NULL, NULL, + sp_marshal_NONE__POINTER, + G_TYPE_NONE, 1, + G_TYPE_POINTER); + inkscape_signals[SET_EVENTCONTEXT] = g_signal_new ("set_eventcontext", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (Inkscape::ApplicationClass, set_eventcontext), + NULL, NULL, + sp_marshal_NONE__POINTER, + G_TYPE_NONE, 1, + G_TYPE_POINTER); + inkscape_signals[ACTIVATE_DESKTOP] = g_signal_new ("activate_desktop", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (Inkscape::ApplicationClass, activate_desktop), + NULL, NULL, + sp_marshal_NONE__POINTER, + G_TYPE_NONE, 1, + G_TYPE_POINTER); + inkscape_signals[DEACTIVATE_DESKTOP] = g_signal_new ("deactivate_desktop", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (Inkscape::ApplicationClass, deactivate_desktop), + NULL, NULL, + sp_marshal_NONE__POINTER, + G_TYPE_NONE, 1, + G_TYPE_POINTER); + inkscape_signals[SHUTDOWN_SIGNAL] = g_signal_new ("shut_down", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (Inkscape::ApplicationClass, shut_down), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + inkscape_signals[DIALOGS_HIDE] = g_signal_new ("dialogs_hide", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (Inkscape::ApplicationClass, dialogs_hide), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + inkscape_signals[DIALOGS_UNHIDE] = g_signal_new ("dialogs_unhide", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (Inkscape::ApplicationClass, dialogs_unhide), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + inkscape_signals[EXTERNAL_CHANGE] = g_signal_new ("external_change", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (Inkscape::ApplicationClass, external_change), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + object_class->dispose = inkscape_dispose; + + klass->activate_desktop = inkscape_activate_desktop_private; + klass->deactivate_desktop = inkscape_deactivate_desktop_private; +} + + +static void +inkscape_init (SPObject * object) +{ + if (!inkscape) { + inkscape = (Inkscape::Application *) object; + } else { + g_assert_not_reached (); + } + + inkscape->menus = sp_repr_read_mem (_(menus_skeleton), MENUS_SKELETON_SIZE, NULL); + + inkscape->documents = NULL; + inkscape->desktops = NULL; + + inkscape->dialogs_toggle = TRUE; +} + + +static void +inkscape_dispose (GObject *object) +{ + Inkscape::Application *inkscape = (Inkscape::Application *) object; + + while (inkscape->documents) { + // we don't otherwise unref, so why here? + sp_document_unref((SPDocument *)inkscape->documents->data); + } + + g_assert (!inkscape->desktops); + + Inkscape::Preferences::save(); + + if (inkscape->menus) { + /* fixme: This is not the best place */ + Inkscape::GC::release(inkscape->menus); + inkscape->menus = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); + + gtk_main_quit (); +} + + +void +inkscape_ref (void) +{ + if (inkscape) + g_object_ref (G_OBJECT (inkscape)); +} + + +void +inkscape_unref (void) +{ + if (inkscape) + g_object_unref (G_OBJECT (inkscape)); +} + + +static void +inkscape_activate_desktop_private (Inkscape::Application *inkscape, SPDesktop *desktop) +{ + desktop->set_active (true); +} + + +static void +inkscape_deactivate_desktop_private (Inkscape::Application *inkscape, SPDesktop *desktop) +{ + desktop->set_active (false); +} + + +/* fixme: This is EVIL, and belongs to main after all */ + +#define SP_INDENT 8 + + +static void +inkscape_segv_handler (int signum) +{ + using Inkscape::Debug::SimpleEvent; + using Inkscape::Debug::EventTracker; + using Inkscape::Debug::Logger; + + static gint recursion = FALSE; + + /* let any SIGABRTs seen from within this handler dump core */ + signal(SIGABRT, SIG_DFL); + + /* Kill loops */ + if (recursion) { + abort (); + } + recursion = TRUE; + + EventTracker > tracker("crash"); + tracker.set >("emergency-save"); + + fprintf(stderr, "\nEmergency save activated!\n"); + + time_t sptime = time (NULL); + struct tm *sptm = localtime (&sptime); + gchar sptstr[256]; + strftime (sptstr, 256, "%Y_%m_%d_%H_%M_%S", sptm); + + gint count = 0; + GSList *savednames = NULL; + GSList *failednames = NULL; + for (GSList *l = inkscape->documents; l != NULL; l = l->next) { + SPDocument *doc; + Inkscape::XML::Node *repr; + doc = (SPDocument *) l->data; + repr = sp_document_repr_root (doc); + if (repr->attribute("sodipodi:modified")) { + const gchar *docname, *d0, *d; + gchar n[64], c[1024]; + FILE *file; + + /* originally, the document name was retrieved from + * the sodipod:docname attribute */ + docname = doc->name; + if (docname) { + /* fixme: Quick hack to remove emergency file suffix */ + d0 = strrchr ((char*)docname, '.'); + if (d0 && (d0 > docname)) { + d0 = strrchr ((char*)(d0 - 1), '.'); + if (d0 && (d0 > docname)) { + d = d0; + while (isdigit (*d) || (*d == '.') || (*d == '_')) d += 1; + if (*d) { + memcpy (n, docname, MIN (d0 - docname - 1, 64)); + n[63] = '\0'; + docname = n; + } + } + } + } + + if (!docname || !*docname) docname = "emergency"; + // try saving to the profile location + g_snprintf (c, 1024, "%.256s.%s.%d", docname, sptstr, count); + gchar * location = homedir_path(c); + Inkscape::IO::dump_fopen_call(location, "E"); + file = Inkscape::IO::fopen_utf8name(location, "w"); + g_free(location); + if (!file) { + // try saving to /tmp + g_snprintf (c, 1024, "/tmp/inkscape-%.256s.%s.%d", docname, sptstr, count); + Inkscape::IO::dump_fopen_call(c, "G"); + file = Inkscape::IO::fopen_utf8name(c, "w"); + } + if (!file) { + // try saving to the current directory + g_snprintf (c, 1024, "inkscape-%.256s.%s.%d", docname, sptstr, count); + Inkscape::IO::dump_fopen_call(c, "F"); + file = Inkscape::IO::fopen_utf8name(c, "w"); + } + if (file) { + sp_repr_save_stream (sp_repr_document (repr), file, SP_SVG_NS_URI); + savednames = g_slist_prepend (savednames, g_strdup (c)); + fclose (file); + } else { + docname = repr->attribute("sodipodi:docname"); + failednames = g_slist_prepend (failednames, (docname) ? g_strdup (docname) : g_strdup (_("Untitled document"))); + } + count++; + } + } + + savednames = g_slist_reverse (savednames); + failednames = g_slist_reverse (failednames); + if (savednames) { + fprintf (stderr, "\nEmergency save document locations:\n"); + for (GSList *l = savednames; l != NULL; l = l->next) { + fprintf (stderr, " %s\n", (gchar *) l->data); + } + } + if (failednames) { + fprintf (stderr, "\nFailed to do emergency save for documents:\n"); + for (GSList *l = failednames; l != NULL; l = l->next) { + fprintf (stderr, " %s\n", (gchar *) l->data); + } + } + + Inkscape::Preferences::save(); + + fprintf (stderr, "Emergency save completed. Inkscape will close now.\n"); + fprintf (stderr, "If you can reproduce this crash, please file a bug at www.inkscape.org\n"); + fprintf (stderr, "with a detailed description of the steps leading to the crash, so we can fix it.\n"); + + /* Show nice dialog box */ + + char const *istr = N_("Inkscape encountered an internal error and will close now.\n"); + char const *sstr = N_("Automatic backups of unsaved documents were done to the following locations:\n"); + char const *fstr = N_("Automatic backup of the following documents failed:\n"); + gint nllen = strlen ("\n"); + gint len = strlen (istr) + strlen (sstr) + strlen (fstr); + for (GSList *l = savednames; l != NULL; l = l->next) { + len = len + SP_INDENT + strlen ((gchar *) l->data) + nllen; + } + for (GSList *l = failednames; l != NULL; l = l->next) { + len = len + SP_INDENT + strlen ((gchar *) l->data) + nllen; + } + len += 1; + gchar *b = g_new (gchar, len); + gint pos = 0; + len = strlen (istr); + memcpy (b + pos, istr, len); + pos += len; + if (savednames) { + len = strlen (sstr); + memcpy (b + pos, sstr, len); + pos += len; + for (GSList *l = savednames; l != NULL; l = l->next) { + memset (b + pos, ' ', SP_INDENT); + pos += SP_INDENT; + len = strlen ((gchar *) l->data); + memcpy (b + pos, l->data, len); + pos += len; + memcpy (b + pos, "\n", nllen); + pos += nllen; + } + } + if (failednames) { + len = strlen (fstr); + memcpy (b + pos, fstr, len); + pos += len; + for (GSList *l = failednames; l != NULL; l = l->next) { + memset (b + pos, ' ', SP_INDENT); + pos += SP_INDENT; + len = strlen ((gchar *) l->data); + memcpy (b + pos, l->data, len); + pos += len; + memcpy (b + pos, "\n", nllen); + pos += nllen; + } + } + *(b + pos) = '\0'; + + if ( inkscape_get_instance() && inkscape_app_use_gui( inkscape_get_instance() ) ) { + GtkWidget *msgbox = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", b); + gtk_dialog_run (GTK_DIALOG (msgbox)); + gtk_widget_destroy (msgbox); + } + else + { + g_message( "Error: %s", b ); + } + g_free (b); + + tracker.clear(); + Logger::shutdown(); + + (* segv_handler) (signum); +} + + + +void +inkscape_application_init (const gchar *argv0, gboolean use_gui) +{ + inkscape = (Inkscape::Application *)g_object_new (SP_TYPE_INKSCAPE, NULL); + /* fixme: load application defaults */ + + segv_handler = signal (SIGSEGV, inkscape_segv_handler); + signal (SIGFPE, inkscape_segv_handler); + signal (SIGILL, inkscape_segv_handler); +#ifndef WIN32 + signal (SIGBUS, inkscape_segv_handler); +#endif + signal (SIGABRT, inkscape_segv_handler); + + inkscape->use_gui = use_gui; + inkscape->argv0 = g_strdup(argv0); + + /* Attempt to load the preferences, and set the save_preferences flag to TRUE + if we could, or FALSE if we couldn't */ + Inkscape::Preferences::load(); + inkscape_load_menus(inkscape); + + /* DebugDialog redirection. On Linux, default to OFF, on Win32, default to ON */ +#ifdef WIN32 +#define DEFAULT_LOG_REDIRECT true +#else +#define DEFAULT_LOG_REDIRECT false +#endif + + if (prefs_get_int_attribute("dialogs.debug", "redirect", DEFAULT_LOG_REDIRECT)) + { + Inkscape::UI::Dialogs::DebugDialog::getInstance()->captureLogMessages(); + } + + /* Initialize the extensions */ + Inkscape::Extension::init(); + + return; +} + +/** + * Returns the current Inkscape::Application global object + */ +Inkscape::Application * +inkscape_get_instance() +{ + return inkscape; +} + +gboolean inkscape_app_use_gui( Inkscape::Application const * app ) +{ + return app->use_gui; +} + +/** + * Preference management + * We use '.' as separator + * + * Returns TRUE if the config file was successfully loaded, FALSE if not. + */ +bool +inkscape_load_config (const gchar *filename, Inkscape::XML::Document *config, const gchar *skeleton, + unsigned int skel_size, const gchar *e_notreg, const gchar *e_notxml, + const gchar *e_notsp, const gchar *warn) +{ + gchar *fn = profile_path(filename); + if (!Inkscape::IO::file_test(fn, G_FILE_TEST_EXISTS)) { + bool result; + /* No such file */ + result = inkscape_init_config (config, filename, skeleton, + skel_size, + _("Cannot create directory %s.\n%s"), + _("%s is not a valid directory.\n%s"), + _("Cannot create file %s.\n%s"), + _("Cannot write file %s.\n%s"), + _("Although Inkscape will run, it will use default settings,\n" + "and any changes made in preferences will not be saved.")); + g_free (fn); + return result; + } + + if (!Inkscape::IO::file_test(fn, G_FILE_TEST_IS_REGULAR)) { + /* Not a regular file */ + gchar *safeFn = Inkscape::IO::sanitizeString(fn); + GtkWidget *w = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, e_notreg, safeFn, warn); + gtk_dialog_run (GTK_DIALOG (w)); + gtk_widget_destroy (w); + g_free(safeFn); + g_free (fn); + return false; + } + + Inkscape::XML::Document *doc = sp_repr_read_file (fn, NULL); + if (doc == NULL) { + /* Not an valid xml file */ + gchar *safeFn = Inkscape::IO::sanitizeString(fn); + GtkWidget *w = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, e_notxml, safeFn, warn); + gtk_dialog_run (GTK_DIALOG (w)); + gtk_widget_destroy (w); + g_free(safeFn); + g_free (fn); + return false; + } + + Inkscape::XML::Node *root = sp_repr_document_root (doc); + if (strcmp (root->name(), "inkscape")) { + gchar *safeFn = Inkscape::IO::sanitizeString(fn); + GtkWidget *w = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, e_notsp, safeFn, warn); + gtk_dialog_run (GTK_DIALOG (w)); + gtk_widget_destroy (w); + Inkscape::GC::release(doc); + g_free(safeFn); + g_free (fn); + return false; + } + + /** \todo this is a hack, need to figure out how to get + * a reasonable merge working with the menus.xml file */ + if (skel_size == MENUS_SKELETON_SIZE) { + if (INKSCAPE) + INKSCAPE->menus = doc; + doc = config; + } else { + config->root()->mergeFrom(doc->root(), "id"); + } + + Inkscape::GC::release(doc); + g_free (fn); + return true; +} + +/** + * Menus management + * + */ +bool +inkscape_load_menus (Inkscape::Application *inkscape) +{ + gchar *fn = profile_path(MENUS_FILE); + bool retval = false; + if (Inkscape::IO::file_test(fn, G_FILE_TEST_EXISTS)) { + retval = inkscape_load_config (MENUS_FILE, + inkscape->menus, + menus_skeleton, + MENUS_SKELETON_SIZE, + _("%s is not a regular file.\n%s"), + _("%s not a valid XML file, or\n" + "you don't have read permissions on it.\n%s"), + _("%s is not a valid menus file.\n%s"), + _("Inkscape will run with default menus.\n" + "New menus will not be saved.")); + } else { + INKSCAPE->menus = sp_repr_read_mem(menus_skeleton, MENUS_SKELETON_SIZE, NULL); + if (INKSCAPE->menus != NULL) + retval = true; + } + g_free(fn); + return retval; +} + +/** + * We use '.' as separator + * \param inkscape Unused + */ +Inkscape::XML::Node * +inkscape_get_repr (Inkscape::Application *inkscape, const gchar *key) +{ + if (key == NULL) { + return NULL; + } + + Inkscape::XML::Node *repr = sp_repr_document_root (Inkscape::Preferences::get()); + g_assert (!(strcmp (repr->name(), "inkscape"))); + + gchar const *s = key; + while ((s) && (*s)) { + + /* Find next name */ + gchar const *e = strchr (s, '.'); + guint len; + if (e) { + len = e++ - s; + } else { + len = strlen (s); + } + + Inkscape::XML::Node* child; + for (child = repr->firstChild(); child != NULL; child = child->next()) { + gchar const *id = child->attribute("id"); + if ((id) && (strlen (id) == len) && (!strncmp (id, s, len))) + { + break; + } + } + if (child == NULL) { + return NULL; + } + + repr = child; + s = e; + } + return repr; +} + + + +void +inkscape_selection_modified (Inkscape::Selection *selection, guint flags) +{ + if (Inkscape::NSApplication::Application::getNewGui()) { + Inkscape::NSApplication::Editor::selectionModified (selection, flags); + return; + } + g_return_if_fail (selection != NULL); + + if (DESKTOP_IS_ACTIVE (selection->desktop())) { + g_signal_emit (G_OBJECT (inkscape), inkscape_signals[MODIFY_SELECTION], 0, selection, flags); + } +} + + +void +inkscape_selection_changed (Inkscape::Selection * selection) +{ + if (Inkscape::NSApplication::Application::getNewGui()) { + Inkscape::NSApplication::Editor::selectionChanged (selection); + return; + } + g_return_if_fail (selection != NULL); + + if (DESKTOP_IS_ACTIVE (selection->desktop())) { + g_signal_emit (G_OBJECT (inkscape), inkscape_signals[CHANGE_SELECTION], 0, selection); + } +} + +void +inkscape_subselection_changed (SPDesktop *desktop) +{ + if (Inkscape::NSApplication::Application::getNewGui()) { + Inkscape::NSApplication::Editor::subSelectionChanged (desktop); + return; + } + g_return_if_fail (desktop != NULL); + + if (DESKTOP_IS_ACTIVE (desktop)) { + g_signal_emit (G_OBJECT (inkscape), inkscape_signals[CHANGE_SUBSELECTION], 0, desktop); + } +} + + +void +inkscape_selection_set (Inkscape::Selection * selection) +{ + if (Inkscape::NSApplication::Application::getNewGui()) { + Inkscape::NSApplication::Editor::selectionSet (selection); + return; + } + g_return_if_fail (selection != NULL); + + if (DESKTOP_IS_ACTIVE (selection->desktop())) { + g_signal_emit (G_OBJECT (inkscape), inkscape_signals[SET_SELECTION], 0, selection); + g_signal_emit (G_OBJECT (inkscape), inkscape_signals[CHANGE_SELECTION], 0, selection); + } +} + + +void +inkscape_eventcontext_set (SPEventContext * eventcontext) +{ + if (Inkscape::NSApplication::Application::getNewGui()) { + Inkscape::NSApplication::Editor::eventContextSet (eventcontext); + return; + } + g_return_if_fail (eventcontext != NULL); + g_return_if_fail (SP_IS_EVENT_CONTEXT (eventcontext)); + + if (DESKTOP_IS_ACTIVE (eventcontext->desktop)) { + g_signal_emit (G_OBJECT (inkscape), inkscape_signals[SET_EVENTCONTEXT], 0, eventcontext); + } +} + + +void +inkscape_add_desktop (SPDesktop * desktop) +{ + g_return_if_fail (desktop != NULL); + + if (Inkscape::NSApplication::Application::getNewGui()) + { + Inkscape::NSApplication::Editor::addDesktop (desktop); + return; + } + g_return_if_fail (inkscape != NULL); + + g_assert (!g_slist_find (inkscape->desktops, desktop)); + + inkscape->desktops = g_slist_append (inkscape->desktops, desktop); + + if (DESKTOP_IS_ACTIVE (desktop)) { + g_signal_emit (G_OBJECT (inkscape), inkscape_signals[ACTIVATE_DESKTOP], 0, desktop); + g_signal_emit (G_OBJECT (inkscape), inkscape_signals[SET_EVENTCONTEXT], 0, SP_DT_EVENTCONTEXT (desktop)); + g_signal_emit (G_OBJECT (inkscape), inkscape_signals[SET_SELECTION], 0, SP_DT_SELECTION (desktop)); + g_signal_emit (G_OBJECT (inkscape), inkscape_signals[CHANGE_SELECTION], 0, SP_DT_SELECTION (desktop)); + } +} + + + +void +inkscape_remove_desktop (SPDesktop * desktop) +{ + g_return_if_fail (desktop != NULL); + if (Inkscape::NSApplication::Application::getNewGui()) + { + Inkscape::NSApplication::Editor::removeDesktop (desktop); + return; + } + g_return_if_fail (inkscape != NULL); + + g_assert (g_slist_find (inkscape->desktops, desktop)); + + if (DESKTOP_IS_ACTIVE (desktop)) { + g_signal_emit (G_OBJECT (inkscape), inkscape_signals[DEACTIVATE_DESKTOP], 0, desktop); + if (inkscape->desktops->next != NULL) { + SPDesktop * new_desktop = (SPDesktop *) inkscape->desktops->next->data; + inkscape->desktops = g_slist_remove (inkscape->desktops, new_desktop); + inkscape->desktops = g_slist_prepend (inkscape->desktops, new_desktop); + g_signal_emit (G_OBJECT (inkscape), inkscape_signals[ACTIVATE_DESKTOP], 0, new_desktop); + g_signal_emit (G_OBJECT (inkscape), inkscape_signals[SET_EVENTCONTEXT], 0, SP_DT_EVENTCONTEXT (new_desktop)); + g_signal_emit (G_OBJECT (inkscape), inkscape_signals[SET_SELECTION], 0, SP_DT_SELECTION (new_desktop)); + g_signal_emit (G_OBJECT (inkscape), inkscape_signals[CHANGE_SELECTION], 0, SP_DT_SELECTION (new_desktop)); + } else { + g_signal_emit (G_OBJECT (inkscape), inkscape_signals[SET_EVENTCONTEXT], 0, NULL); + if (SP_DT_SELECTION(desktop)) + SP_DT_SELECTION(desktop)->clear(); + } + } + + inkscape->desktops = g_slist_remove (inkscape->desktops, desktop); + + // if this was the last desktop, shut down the program + if (inkscape->desktops == NULL) { + inkscape_exit (inkscape); + } +} + + + +void +inkscape_activate_desktop (SPDesktop * desktop) +{ + g_return_if_fail (desktop != NULL); + if (Inkscape::NSApplication::Application::getNewGui()) + { + Inkscape::NSApplication::Editor::activateDesktop (desktop); + return; + } + g_return_if_fail (inkscape != NULL); + + if (DESKTOP_IS_ACTIVE (desktop)) { + return; + } + + g_assert (g_slist_find (inkscape->desktops, desktop)); + + SPDesktop *current = (SPDesktop *) inkscape->desktops->data; + + g_signal_emit (G_OBJECT (inkscape), inkscape_signals[DEACTIVATE_DESKTOP], 0, current); + + inkscape->desktops = g_slist_remove (inkscape->desktops, desktop); + inkscape->desktops = g_slist_prepend (inkscape->desktops, desktop); + + g_signal_emit (G_OBJECT (inkscape), inkscape_signals[ACTIVATE_DESKTOP], 0, desktop); + g_signal_emit (G_OBJECT (inkscape), inkscape_signals[SET_EVENTCONTEXT], 0, SP_DT_EVENTCONTEXT (desktop)); + g_signal_emit (G_OBJECT (inkscape), inkscape_signals[SET_SELECTION], 0, SP_DT_SELECTION (desktop)); + g_signal_emit (G_OBJECT (inkscape), inkscape_signals[CHANGE_SELECTION], 0, SP_DT_SELECTION (desktop)); +} + + +/** + * Resends ACTIVATE_DESKTOP for current desktop; needed when a new desktop has got its window that dialogs will transientize to + */ +void +inkscape_reactivate_desktop (SPDesktop * desktop) +{ + g_return_if_fail (desktop != NULL); + if (Inkscape::NSApplication::Application::getNewGui()) + { + Inkscape::NSApplication::Editor::reactivateDesktop (desktop); + return; + } + g_return_if_fail (inkscape != NULL); + + if (DESKTOP_IS_ACTIVE (desktop)) + g_signal_emit (G_OBJECT (inkscape), inkscape_signals[ACTIVATE_DESKTOP], 0, desktop); +} + + + +SPDesktop * +inkscape_find_desktop_by_dkey (unsigned int dkey) +{ + for (GSList *r = inkscape->desktops; r; r = r->next) { + if (((SPDesktop *) r->data)->dkey == dkey) + return ((SPDesktop *) r->data); + } + return NULL; +} + + + + +unsigned int +inkscape_maximum_dkey() +{ + unsigned int dkey = 0; + + for (GSList *r = inkscape->desktops; r; r = r->next) { + if (((SPDesktop *) r->data)->dkey > dkey) + dkey = ((SPDesktop *) r->data)->dkey; + } + + return dkey; +} + + + +SPDesktop * +inkscape_next_desktop () +{ + SPDesktop *d = NULL; + unsigned int dkey_current = ((SPDesktop *) inkscape->desktops->data)->dkey; + + if (dkey_current < inkscape_maximum_dkey()) { + // find next existing + for (unsigned int i = dkey_current + 1; i <= inkscape_maximum_dkey(); i++) { + d = inkscape_find_desktop_by_dkey (i); + if (d) { + break; + } + } + } else { + // find first existing + for (unsigned int i = 0; i <= inkscape_maximum_dkey(); i++) { + d = inkscape_find_desktop_by_dkey (i); + if (d) { + break; + } + } + } + + g_assert (d); + + return d; +} + + + +SPDesktop * +inkscape_prev_desktop () +{ + SPDesktop *d = NULL; + unsigned int dkey_current = ((SPDesktop *) inkscape->desktops->data)->dkey; + + if (dkey_current > 0) { + // find prev existing + for (signed int i = dkey_current - 1; i >= 0; i--) { + d = inkscape_find_desktop_by_dkey (i); + if (d) { + break; + } + } + } + if (!d) { + // find last existing + d = inkscape_find_desktop_by_dkey (inkscape_maximum_dkey()); + } + + g_assert (d); + + return d; +} + + + +void +inkscape_switch_desktops_next () +{ + inkscape_next_desktop()->presentWindow(); +} + + + +void +inkscape_switch_desktops_prev () +{ + inkscape_prev_desktop()->presentWindow(); +} + + + +void +inkscape_dialogs_hide () +{ + if (Inkscape::NSApplication::Application::getNewGui()) + Inkscape::NSApplication::Editor::hideDialogs(); + else + { + g_signal_emit (G_OBJECT (inkscape), inkscape_signals[DIALOGS_HIDE], 0); + inkscape->dialogs_toggle = FALSE; + } +} + + + +void +inkscape_dialogs_unhide () +{ + if (Inkscape::NSApplication::Application::getNewGui()) + Inkscape::NSApplication::Editor::unhideDialogs(); + else + { + g_signal_emit (G_OBJECT (inkscape), inkscape_signals[DIALOGS_UNHIDE], 0); + inkscape->dialogs_toggle = TRUE; + } +} + + + +void +inkscape_dialogs_toggle () +{ + if (inkscape->dialogs_toggle) { + inkscape_dialogs_hide (); + } else { + inkscape_dialogs_unhide (); + } +} + +void +inkscape_external_change () +{ + g_return_if_fail (inkscape != NULL); + + g_signal_emit (G_OBJECT (inkscape), inkscape_signals[EXTERNAL_CHANGE], 0); +} + +/** + * fixme: These need probably signals too + */ +void +inkscape_add_document (SPDocument *document) +{ + g_return_if_fail (document != NULL); + + if (!Inkscape::NSApplication::Application::getNewGui()) + { + g_assert (!g_slist_find (inkscape->documents, document)); + inkscape->documents = g_slist_append (inkscape->documents, document); + } + else + { + Inkscape::NSApplication::Editor::addDocument (document); + } +} + + + +void +inkscape_remove_document (SPDocument *document) +{ + g_return_if_fail (document != NULL); + + if (!Inkscape::NSApplication::Application::getNewGui()) + { + g_assert (g_slist_find (inkscape->documents, document)); + inkscape->documents = g_slist_remove (inkscape->documents, document); + } + else + { + Inkscape::NSApplication::Editor::removeDocument (document); + } + + return; +} + +SPDesktop * +inkscape_active_desktop (void) +{ + if (Inkscape::NSApplication::Application::getNewGui()) + return Inkscape::NSApplication::Editor::getActiveDesktop(); + + if (inkscape->desktops == NULL) { + return NULL; + } + + return (SPDesktop *) inkscape->desktops->data; +} + +SPDocument * +inkscape_active_document (void) +{ + if (Inkscape::NSApplication::Application::getNewGui()) + return Inkscape::NSApplication::Editor::getActiveDocument(); + + if (SP_ACTIVE_DESKTOP) { + return SP_DT_DOCUMENT (SP_ACTIVE_DESKTOP); + } + + return NULL; +} + +bool inkscape_is_sole_desktop_for_document(SPDesktop const &desktop) { + SPDocument const* document = desktop.doc(); + if (!document) { + return false; + } + for ( GSList *iter = inkscape->desktops ; iter ; iter = iter->next ) { + SPDesktop *other_desktop=(SPDesktop *)iter->data; + SPDocument *other_document=other_desktop->doc(); + if ( other_document == document && other_desktop != &desktop ) { + return false; + } + } + return true; +} + +SPEventContext * +inkscape_active_event_context (void) +{ + if (SP_ACTIVE_DESKTOP) { + return SP_DT_EVENTCONTEXT (SP_ACTIVE_DESKTOP); + } + + return NULL; +} + + + +/*##################### +# HELPERS +#####################*/ + +static bool +inkscape_init_config (Inkscape::XML::Document *doc, const gchar *config_name, const gchar *skeleton, + unsigned int skel_size, + const gchar *e_mkdir, + const gchar *e_notdir, + const gchar *e_ccf, + const gchar *e_cwf, + const gchar *warn) +{ + gchar *dn = profile_path(NULL); + bool use_gui = (Inkscape::NSApplication::Application::getNewGui())? Inkscape::NSApplication::Application::getUseGui() : inkscape->use_gui; + if (!Inkscape::IO::file_test(dn, G_FILE_TEST_EXISTS)) { + if (Inkscape::IO::mkdir_utf8name(dn)) + { + if (use_gui) { + // Cannot create directory + gchar *safeDn = Inkscape::IO::sanitizeString(dn); + GtkWidget *w = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, e_mkdir, safeDn, warn); + gtk_dialog_run (GTK_DIALOG (w)); + gtk_widget_destroy (w); + g_free(safeDn); + g_free (dn); + return false; + } else { + g_warning(e_mkdir, dn, warn); + g_free (dn); + return false; + } + } + } else if (!Inkscape::IO::file_test(dn, G_FILE_TEST_IS_DIR)) { + if (use_gui) { + // Not a directory + gchar *safeDn = Inkscape::IO::sanitizeString(dn); + GtkWidget *w = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, e_notdir, safeDn, warn); + gtk_dialog_run (GTK_DIALOG (w)); + gtk_widget_destroy (w); + g_free( safeDn ); + g_free (dn); + return false; + } else { + g_warning(e_notdir, dn, warn); + g_free(dn); + return false; + } + } + g_free (dn); + + gchar *fn = profile_path(config_name); + + Inkscape::IO::dump_fopen_call(fn, "H"); + FILE *fh = Inkscape::IO::fopen_utf8name(fn, "w"); + if (!fh) { + if (use_gui) { + /* Cannot create file */ + gchar *safeFn = Inkscape::IO::sanitizeString(fn); + GtkWidget *w = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, e_ccf, safeFn, warn); + gtk_dialog_run (GTK_DIALOG (w)); + gtk_widget_destroy (w); + g_free(safeFn); + g_free (fn); + return false; + } else { + g_warning(e_ccf, fn, warn); + g_free(fn); + return false; + } + } + if ( fwrite(skeleton, 1, skel_size, fh) != skel_size ) { + if (use_gui) { + /* Cannot create file */ + gchar *safeFn = Inkscape::IO::sanitizeString(fn); + GtkWidget *w = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, e_cwf, safeFn, warn); + gtk_dialog_run (GTK_DIALOG (w)); + gtk_widget_destroy (w); + g_free(safeFn); + g_free (fn); + fclose(fh); + return false; + } else { + g_warning(e_cwf, fn, warn); + g_free(fn); + fclose(fh); + return false; + } + } + + g_free(fn); + fclose(fh); + return true; +} + +void +inkscape_refresh_display (Inkscape::Application *inkscape) +{ + for (GSList *l = inkscape->desktops; l != NULL; l = l->next) { + (static_cast(l->data))->requestRedraw(); + } +} + + +/** + * Handler for Inkscape's Exit verb. This emits the shutdown signal, + * saves the preferences if appropriate, and quits. + */ +void +inkscape_exit (Inkscape::Application *inkscape) +{ + g_assert (INKSCAPE); + + //emit shutdown signal so that dialogs could remember layout + g_signal_emit (G_OBJECT (INKSCAPE), inkscape_signals[SHUTDOWN_SIGNAL], 0); + + Inkscape::Preferences::save(); + gtk_main_quit (); +} + +gchar * +homedir_path(const char *filename) +{ + static const gchar *homedir = NULL; + if (!homedir) { + homedir = g_get_home_dir(); + gchar* utf8Path = g_filename_to_utf8( homedir, -1, NULL, NULL, NULL ); + if ( utf8Path ) + { + homedir = utf8Path; + if (!g_utf8_validate(homedir, -1, NULL)) { + g_warning( "g_get_home_dir() post A IS NOT UTF-8" ); + } + } + } + if (!homedir) { + gchar * path = g_path_get_dirname(INKSCAPE->argv0); + gchar* utf8Path = g_filename_to_utf8( path, -1, NULL, NULL, NULL ); + g_free(path); + if ( utf8Path ) + { + homedir = utf8Path; + if (!g_utf8_validate(homedir, -1, NULL)) { + g_warning( "g_get_home_dir() post B IS NOT UTF-8" ); + } + } + } + return g_build_filename(homedir, filename, NULL); +} + + +/** + * Get, or guess, or decide the location where the preferences.xml + * file should be located. + */ +gchar * +profile_path(const char *filename) +{ + static const gchar *prefdir = NULL; + if (!prefdir) { +#ifdef HAS_SHGetSpecialFolderLocation + // prefer c:\Documents and Settings\UserName\Application Data\ to + // c:\Documents and Settings\userName\; + if (!prefdir) { + ITEMIDLIST *pidl = 0; + if ( SHGetSpecialFolderLocation( NULL, CSIDL_APPDATA, &pidl ) == NOERROR ) { + gchar * utf8Path = NULL; + + if ( PrintWin32::is_os_wide() ) { + wchar_t pathBuf[MAX_PATH+1]; + g_assert(sizeof(wchar_t) == sizeof(gunichar2)); + + if ( SHGetPathFromIDListW( pidl, pathBuf ) ) { + utf8Path = g_utf16_to_utf8( (gunichar2*)(&pathBuf[0]), -1, NULL, NULL, NULL ); + } + } else { + char pathBuf[MAX_PATH+1]; + + if ( SHGetPathFromIDListA( pidl, pathBuf ) ) { + utf8Path = g_filename_to_utf8( pathBuf, -1, NULL, NULL, NULL ); + } + } + + if ( utf8Path ) { + if (!g_utf8_validate(utf8Path, -1, NULL)) { + g_warning( "SHGetPathFromIDList%c() resulted in invalid UTF-8", (PrintWin32::is_os_wide() ? 'W' : 'A') ); + g_free( utf8Path ); + utf8Path = 0; + } else { + prefdir = utf8Path; + } + } + + + /* not compiling yet... + + // Remember to free the list pointer + IMalloc * imalloc = 0; + if ( SHGetMalloc(&imalloc) == NOERROR) { + imalloc->lpVtbl->Free( imalloc, pidl ); + imalloc->lpVtbl->Release( imalloc ); + } + */ + } + } +#endif + if (!prefdir) { + prefdir = homedir_path(NULL); + } + } + return g_build_filename(prefdir, INKSCAPE_PROFILE_DIR, filename, NULL); +} + +Inkscape::XML::Node * +inkscape_get_menus (Inkscape::Application * inkscape) +{ + Inkscape::XML::Node *repr = sp_repr_document_root (inkscape->menus); + g_assert (!(strcmp (repr->name(), "inkscape"))); + return repr->firstChild(); +} + + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/inkscape.h b/src/inkscape.h new file mode 100644 index 000000000..9907f320f --- /dev/null +++ b/src/inkscape.h @@ -0,0 +1,86 @@ +#ifndef __INKSCAPE_H__ +#define __INKSCAPE_H__ + +/* + * Interface to main application + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2003 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "forward.h" + +namespace Inkscape { +namespace XML { +class Node; +class Document; +} +} + + +#define INKSCAPE inkscape_get_instance() + +void inkscape_application_init (const gchar *argv0, gboolean use_gui); + +bool inkscape_load_config (const gchar *filename, Inkscape::XML::Document *config, const gchar *skeleton, unsigned int skel_size, const gchar *e_notreg, const gchar *e_notxml, const gchar *e_notsp, const gchar *warn); +Inkscape::XML::Node *inkscape_get_repr (Inkscape::Application *inkscape, const gchar *key); + +/* Menus */ +bool inkscape_load_menus (Inkscape::Application * inkscape); +bool inkscape_save_menus (Inkscape::Application * inkscape); +Inkscape::XML::Node *inkscape_get_menus (Inkscape::Application * inkscape); + +Inkscape::Application *inkscape_get_instance(); + +#define SP_ACTIVE_EVENTCONTEXT inkscape_active_event_context () +SPEventContext * inkscape_active_event_context (void); + +#define SP_ACTIVE_DOCUMENT inkscape_active_document () +SPDocument * inkscape_active_document (void); + +#define SP_ACTIVE_DESKTOP inkscape_active_desktop () +SPDesktop * inkscape_active_desktop (void); + +bool inkscape_is_sole_desktop_for_document(SPDesktop const &desktop); + +gchar *homedir_path(const char *filename); +gchar *profile_path(const char *filename); + +void inkscape_switch_desktops_next (); +void inkscape_switch_desktops_prev (); + +void inkscape_dialogs_hide (); +void inkscape_dialogs_unhide (); +void inkscape_dialogs_toggle (); + +void inkscape_external_change (); +void inkscape_subselection_changed (SPDesktop *desktop); + +/* + * fixme: This has to be rethought + */ + +void inkscape_refresh_display (Inkscape::Application *inkscape); + +/* + * fixme: This also + */ + +void inkscape_exit (Inkscape::Application *inkscape); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/inkscape.rc b/src/inkscape.rc new file mode 100644 index 000000000..25dde0b28 --- /dev/null +++ b/src/inkscape.rc @@ -0,0 +1,3 @@ + +APPLICATION_ICON ICON DISCARDABLE "../inkscape32-16.ico" + diff --git a/src/inkscape_version.h.mingw b/src/inkscape_version.h.mingw new file mode 100644 index 000000000..c0f2937ab --- /dev/null +++ b/src/inkscape_version.h.mingw @@ -0,0 +1 @@ +#define INKSCAPE_VERSION "0.43+devel" diff --git a/src/inkview.cpp b/src/inkview.cpp new file mode 100644 index 000000000..bd1ef6e31 --- /dev/null +++ b/src/inkview.cpp @@ -0,0 +1,491 @@ +#define __SPSVGVIEW_C__ + +/* + * Inkscape - an ambitious vector drawing program + * + * Authors: + * Lauris Kaplinski + * Frank Felfe + * Davide Puricelli + * Mitsuru Oka + * Masatake YAMATO + * F.J.Franklin + * Michael Meeks + * Chema Celorio + * Pawel Palucha + * ... and various people who have worked with various projects + * + * Copyright (C) 1999-2002 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Inkscape authors: + * Johan Ceuppens + * + * Copyright (C) 2004 Inkscape authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "gc-core.h" +#include "preferences.h" + +#include +#include "document.h" +#include "svg-view.h" +#include "svg-view-widget.h" + +#ifdef WITH_INKJAR +#include "inkjar/jar.h" +#endif + +#include "inkscape-private.h" + +Inkscape::Application *inkscape; + +#include + +#ifndef HAVE_BIND_TEXTDOMAIN_CODESET +#define bind_textdomain_codeset(p,c) +#endif + +struct SPSlideShow { + char **slides; + int size; + int length; + int current; + SPDocument *doc; + GtkWidget *view; + GtkWindow *window; + bool fullscreen; +}; + +static GtkWidget *sp_svgview_control_show (struct SPSlideShow *ss); +static void sp_svgview_show_next (struct SPSlideShow *ss); +static void sp_svgview_show_prev (struct SPSlideShow *ss); +static void sp_svgview_goto_first (struct SPSlideShow *ss); +static void sp_svgview_goto_last (struct SPSlideShow *ss); + +static int sp_svgview_show_next_cb (GtkWidget *widget, void *data); +static int sp_svgview_show_prev_cb (GtkWidget *widget, void *data); +static int sp_svgview_goto_first_cb (GtkWidget *widget, void *data); +static int sp_svgview_goto_last_cb (GtkWidget *widget, void *data); +#ifdef WITH_INKJAR +static bool is_jar(char const *filename); +#endif +static void usage(); + +static GtkWidget *ctrlwin = NULL; + +/// Dummy functions to keep linker happy +int sp_main_gui (int, char const**) { return 0; } +int sp_main_console (int, char const**) { return 0; } + +static int +sp_svgview_main_delete (GtkWidget *widget, GdkEvent *event, struct SPSlideShow *ss) +{ + gtk_main_quit (); + return FALSE; +} + +static int +sp_svgview_main_key_press (GtkWidget *widget, GdkEventKey *event, struct SPSlideShow *ss) +{ + switch (event->keyval) { + case GDK_Up: + sp_svgview_goto_first(ss); + break; + case GDK_Down: + sp_svgview_goto_last(ss); + break; + case GDK_F11: +#ifdef HAVE_GTK_WINDOW_FULLSCREEN + if (ss->fullscreen) { + gtk_window_unfullscreen ((GtkWindow *) widget); + ss->fullscreen = false; + } else { + gtk_window_fullscreen ((GtkWindow *) widget); + ss->fullscreen = true; + } +#else + std::cout<<"Your GTK+ does not support fullscreen mode. Upgrade to 2.2."<doc)); + return FALSE; +} + +int +main (int argc, const char **argv) +{ + if (argc == 1) { + usage(); + } + + struct SPSlideShow ss; + + GtkWidget *w; + int i; + + bindtextdomain (GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + LIBXML_TEST_VERSION + + Inkscape::GC::init(); + Inkscape::Preferences::loadSkeleton(); + + gtk_init (&argc, (char ***) &argv); + +#ifdef lalaWITH_MODULES + g_warning ("Have to autoinit modules (lauris)"); + sp_modulesys_init(); +#endif /* WITH_MODULES */ + + /* We must set LC_NUMERIC to default, or otherwise */ + /* we'll end with localised SVG files :-( */ + + setlocale (LC_NUMERIC, "C"); + + ss.size = 32; + ss.length = 0; + ss.current = 0; + ss.slides = nr_new (char *, ss.size); + ss.current = 0; + ss.doc = NULL; + ss.view = NULL; + ss.fullscreen = false; + + inkscape = (Inkscape::Application *)g_object_new (SP_TYPE_INKSCAPE, NULL); + Inkscape::Preferences::load(); + + for (i = 1; i < argc; i++) { + struct stat st; + if (stat (argv[i], &st) + || !S_ISREG (st.st_mode) + || (st.st_size < 64)) { + fprintf(stderr, "could not open file %s\n", argv[i]); + } else { + +#ifdef WITH_INKJAR + if (is_jar(argv[i])) { + Inkjar::JarFileReader jar_file_reader(argv[i]); + for (;;) { + GByteArray *gba = jar_file_reader.get_next_file(); + if (gba == NULL) { + char *c_ptr; + gchar *last_filename = jar_file_reader.get_last_filename(); + if (last_filename == NULL) + break; + if ((c_ptr = std::strrchr(last_filename, '/')) != NULL) { + if (*(++c_ptr) == '\0') { + g_free(last_filename); + continue; + } + } + } else if (gba->len > 0) { + //::write(1, gba->data, gba->len); + /* Append to list */ + if (ss.length >= ss.size) { + /* Expand */ + ss.size <<= 1; + ss.slides = nr_renew (ss.slides, char *, ss.size); + } + + ss.doc = sp_document_new_from_mem ((const gchar *)gba->data, + gba->len, + TRUE); + gchar *last_filename = jar_file_reader.get_last_filename(); + if (ss.doc) { + ss.slides[ss.length++] = strdup (last_filename); + sp_document_set_uri (ss.doc, strdup(last_filename)); + } + g_byte_array_free(gba, TRUE); + g_free(last_filename); + } else + break; + } + } else { +#endif /* WITH_INKJAR */ + /* Append to list */ + if (ss.length >= ss.size) { + /* Expand */ + ss.size <<= 1; + ss.slides = nr_renew (ss.slides, char *, ss.size); + + } + + ss.slides[ss.length++] = strdup (argv[i]); + ss.doc = sp_document_new (ss.slides[ss.current], TRUE, false); + + if (!ss.doc && ++ss.current >= ss.length) { + /* No loadable documents */ + return 1; + } +#ifdef WITH_INKJAR + } +#endif + } + } + + if(!ss.doc) + return 1; /* none of the slides loadable */ + + w = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW (w), SP_DOCUMENT_NAME (ss.doc)); + gtk_window_set_default_size (GTK_WINDOW (w), + MIN ((int)sp_document_width (ss.doc), (int)gdk_screen_width () - 64), + MIN ((int)sp_document_height (ss.doc), (int)gdk_screen_height () - 64)); + gtk_window_set_policy (GTK_WINDOW (w), TRUE, TRUE, FALSE); + + g_signal_connect (G_OBJECT (w), "delete_event", (GCallback) sp_svgview_main_delete, &ss); + g_signal_connect (G_OBJECT (w), "key_press_event", (GCallback) sp_svgview_main_key_press, &ss); + + ss.view = sp_svg_view_widget_new (ss.doc); + sp_svg_view_widget_set_resize (SP_SVG_VIEW_WIDGET (ss.view), FALSE, sp_document_width (ss.doc), sp_document_height (ss.doc)); + sp_document_ensure_up_to_date (ss.doc); + sp_document_unref (ss.doc); + gtk_widget_show (ss.view); + gtk_container_add (GTK_CONTAINER (w), ss.view); + + gtk_widget_show (w); + + gtk_main (); + + return 0; +} + +static int +sp_svgview_ctrlwin_delete (GtkWidget *widget, GdkEvent *event, void *data) +{ + ctrlwin = NULL; + return FALSE; +} + +static GtkWidget * +sp_svgview_control_show (struct SPSlideShow *ss) +{ + if (!ctrlwin) { + GtkWidget *t, *b; + ctrlwin = gtk_window_new (GTK_WINDOW_TOPLEVEL); + g_signal_connect (G_OBJECT (ctrlwin), "delete_event", (GCallback) sp_svgview_ctrlwin_delete, NULL); + t = gtk_table_new (1, 4, TRUE); + gtk_container_add ((GtkContainer *) ctrlwin, t); + b = gtk_button_new_from_stock (GTK_STOCK_GOTO_FIRST); + gtk_table_attach ((GtkTable *) t, b, 0, 1, 0, 1, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + 0, 0); + g_signal_connect ((GObject *) b, "clicked", (GCallback) sp_svgview_goto_first_cb, ss); + b = gtk_button_new_from_stock (GTK_STOCK_GO_BACK); + gtk_table_attach ((GtkTable *) t, b, 1, 2, 0, 1, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + 0, 0); + g_signal_connect (G_OBJECT(b), "clicked", (GCallback) sp_svgview_show_prev_cb, ss); + b = gtk_button_new_from_stock (GTK_STOCK_GO_FORWARD); + gtk_table_attach ((GtkTable *) t, b, 2, 3, 0, 1, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + 0, 0); + g_signal_connect (G_OBJECT(b), "clicked", (GCallback) sp_svgview_show_next_cb, ss); + b = gtk_button_new_from_stock (GTK_STOCK_GOTO_LAST); + gtk_table_attach ((GtkTable *) t, b, 3, 4, 0, 1, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + 0, 0); + g_signal_connect (G_OBJECT(b), "clicked", (GCallback) sp_svgview_goto_last_cb, ss); + gtk_widget_show_all (ctrlwin); + } else { + gtk_window_present ((GtkWindow *) ctrlwin); + } + + return NULL; +} + +static int +sp_svgview_show_next_cb (GtkWidget *widget, void *data) +{ + sp_svgview_show_next(static_cast(data)); + return FALSE; +} + +static int +sp_svgview_show_prev_cb (GtkWidget *widget, void *data) +{ + sp_svgview_show_prev(static_cast(data)); + return FALSE; +} + +static int +sp_svgview_goto_first_cb (GtkWidget *widget, void *data) +{ + sp_svgview_goto_first(static_cast(data)); + return FALSE; +} + +static int +sp_svgview_goto_last_cb (GtkWidget *widget, void *data) +{ + sp_svgview_goto_last(static_cast(data)); + return FALSE; +} + +static void +sp_svgview_show_next (struct SPSlideShow *ss) +{ + SPDocument *doc; + int current; + doc = NULL; + current = ss->current; + while (!doc && (current < ss->length - 1)) { + doc = sp_document_new (ss->slides[++current], TRUE, false); + } + if (doc) { + reinterpret_cast(SP_VIEW_WIDGET_VIEW (ss->view))->setDocument (doc); + sp_document_ensure_up_to_date (doc); + ss->doc = doc; + ss->current = current; + } +} + +static void +sp_svgview_show_prev (struct SPSlideShow *ss) +{ + SPDocument *doc; + int current; + doc = NULL; + current = ss->current; + while (!doc && (current > 0)) { + doc = sp_document_new (ss->slides[--current], TRUE, false); + } + if (doc) { + reinterpret_cast(SP_VIEW_WIDGET_VIEW (ss->view))->setDocument (doc); + sp_document_ensure_up_to_date (doc); + ss->doc = doc; + ss->current = current; + } +} + +static void +sp_svgview_goto_first (struct SPSlideShow *ss) +{ + SPDocument *doc = NULL; + int current = 0; + for ( ; !doc && (current < ss->length); current++) { + doc = sp_document_new (ss->slides[current], TRUE, false); + } + if (doc) { + reinterpret_cast(SP_VIEW_WIDGET_VIEW (ss->view))->setDocument (doc); + sp_document_ensure_up_to_date (doc); + ss->doc = doc; + ss->current = current; + } +} + +static void +sp_svgview_goto_last (struct SPSlideShow *ss) +{ + SPDocument *doc = NULL; + int current = ss->length - 1; + for ( ; !doc && (current >= 0); current--) { + doc = sp_document_new (ss->slides[current], TRUE, false); + } + if (doc) { + reinterpret_cast(SP_VIEW_WIDGET_VIEW (ss->view))->setDocument (doc); + sp_document_ensure_up_to_date (doc); + ss->doc = doc; + ss->current = current; + } +} + +#ifdef WITH_INKJAR +static bool +is_jar(char const *filename) +{ + /* fixme: Check MIME type or something. /usr/share/misc/file/magic suggests that checking for + initial string "PK\003\004" in content should suffice. */ + size_t const filename_len = strlen(filename); + if (filename_len < 5) { + return false; + } + char const *extension = filename + filename_len - 4; + return ((memcmp(extension, ".jar", 4) == 0) || + (memcmp(extension, ".sxw", 4) == 0) ); +} +#endif /* WITH_INKJAR */ + +static void usage() +{ + fprintf(stderr, + "Usage: inkview [FILES ...]\n" + "\twhere FILES are SVG (.svg or .svgz)" +#ifdef WITH_INKJAR + "or archives of SVGs (.sxw, .jar)" +#endif + "\n"); + exit(1); +} + +#ifdef XXX +/* TODO !!! make this temporary stub unnecessary */ +Inkscape::Application *inkscape_get_instance() { return NULL; } +void inkscape_ref (void) {} +void inkscape_unref (void) {} +void inkscape_add_document (SPDocument *document) {} +void inkscape_remove_document (SPDocument *document) {} +Inkscape::XML::Node *inkscape_get_repr (Inkscape::Application *inkscape, const gchar *key) {return NULL;} +#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 : diff --git a/src/interface.cpp b/src/interface.cpp new file mode 100644 index 000000000..69ed8818b --- /dev/null +++ b/src/interface.cpp @@ -0,0 +1,1197 @@ +#define __SP_INTERFACE_C__ + +/** + * Main UI stuff + * + * Authors: + * Lauris Kaplinski + * Frank Felfe + * bulia byak + * + * Copyright (C) 1999-2005 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * Copyright (C) 2004 David Turner + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include "inkscape-private.h" +#include "extension/effect.h" +#include "widgets/icon.h" +#include "prefs-utils.h" +#include "path-prefix.h" + +#include "shortcuts.h" + +#include "document.h" +#include "desktop-handles.h" +#include "file.h" +#include "interface.h" +#include "desktop.h" +#include "object-ui.h" +#include "selection.h" +#include "selection-chemistry.h" +#include "svg-view-widget.h" +#include "widgets/desktop-widget.h" +#include "sp-item-group.h" +#include "sp-namedview.h" + +#include "helper/action.h" +#include "helper/gnome-utils.h" +#include "helper/window.h" + +#include "io/sys.h" +#include "io/stringstream.h" +#include "io/base64stream.h" + +#include "dialogs/dialog-events.h" + +#include "message-context.h" + + +using Inkscape::IO::StringOutputStream; +using Inkscape::IO::Base64OutputStream; + +/* forward declaration */ +static gint sp_ui_delete(GtkWidget *widget, GdkEvent *event, Inkscape::UI::View::View *view); + +/* Drag and Drop */ +typedef enum { + URI_LIST, + SVG_XML_DATA, + SVG_DATA, + PNG_DATA, + JPEG_DATA, + IMAGE_DATA +} ui_drop_target_info; + +static GtkTargetEntry ui_drop_target_entries [] = { + {"text/uri-list", 0, URI_LIST}, + {"image/svg+xml", 0, SVG_XML_DATA}, + {"image/svg", 0, SVG_DATA}, + {"image/png", 0, PNG_DATA}, + {"image/jpeg", 0, JPEG_DATA}, +}; + +static GtkTargetEntry *completeDropTargets = 0; +static int completeDropTargetsCount = 0; + +#define ENTRIES_SIZE(n) sizeof(n)/sizeof(n[0]) +static guint nui_drop_target_entries = ENTRIES_SIZE(ui_drop_target_entries); +static void sp_ui_import_files(gchar *buffer); +static void sp_ui_import_one_file(char const *filename); +static void sp_ui_import_one_file_with_check(gpointer filename, gpointer unused); +static void sp_ui_drag_data_received(GtkWidget *widget, + GdkDragContext *drag_context, + gint x, gint y, + GtkSelectionData *data, + guint info, + guint event_time, + gpointer user_data); +static void sp_ui_menu_item_set_sensitive(SPAction *action, + unsigned int sensitive, + void *data); + +SPActionEventVector menu_item_event_vector = { + {NULL}, + NULL, + NULL, /* set_active */ + sp_ui_menu_item_set_sensitive, /* set_sensitive */ + NULL /* set_shortcut */ +}; + +void +sp_create_window(SPViewWidget *vw, gboolean editable) +{ + GtkWidget *w, *hb; + + g_return_if_fail(vw != NULL); + g_return_if_fail(SP_IS_VIEW_WIDGET(vw)); + + w = sp_window_new("", TRUE); + + if (editable) { + g_object_set_data(G_OBJECT(vw), "window", w); + reinterpret_cast(vw)->window = + static_cast((void*)w); + } + + hb = gtk_hbox_new(FALSE, 0); + gtk_widget_show(hb); + gtk_container_add(GTK_CONTAINER(w), hb); + g_object_set_data(G_OBJECT(w), "hbox", hb); + + /* fixme: */ + if (editable) { + gtk_window_set_default_size((GtkWindow *) w, 640, 480); + g_object_set_data(G_OBJECT(w), "desktop", SP_DESKTOP_WIDGET(vw)->desktop); + g_object_set_data(G_OBJECT(w), "desktopwidget", vw); + g_signal_connect(G_OBJECT(w), "delete_event", G_CALLBACK(sp_ui_delete), vw->view); + g_signal_connect(G_OBJECT(w), "focus_in_event", G_CALLBACK(sp_desktop_widget_set_focus), vw); + } else { + gtk_window_set_policy(GTK_WINDOW(w), TRUE, TRUE, TRUE); + } + + gtk_box_pack_end(GTK_BOX(hb), GTK_WIDGET(vw), TRUE, TRUE, 0); + gtk_widget_show(GTK_WIDGET(vw)); + + + if ( completeDropTargets == 0 || completeDropTargetsCount == 0 ) + { + std::vector types; + + GSList *list = gdk_pixbuf_get_formats(); + while ( list ) { + int i = 0; + GdkPixbufFormat *one = (GdkPixbufFormat*)list->data; + gchar** typesXX = gdk_pixbuf_format_get_mime_types(one); + for ( i = 0; typesXX[i]; i++ ) { + types.push_back(g_strdup(typesXX[i])); + } + g_strfreev(typesXX); + + list = g_slist_next(list); + } + completeDropTargetsCount = nui_drop_target_entries + types.size(); + completeDropTargets = new GtkTargetEntry[completeDropTargetsCount]; + for ( int i = 0; i < (int)nui_drop_target_entries; i++ ) { + completeDropTargets[i] = ui_drop_target_entries[i]; + } + int pos = nui_drop_target_entries; + + for (std::vector::iterator it = types.begin() ; it != types.end() ; it++) { + completeDropTargets[pos].target = *it; + completeDropTargets[pos].flags = 0; + completeDropTargets[pos].info = IMAGE_DATA; + pos++; + } + } + + gtk_drag_dest_set(w, + GTK_DEST_DEFAULT_ALL, + completeDropTargets, + completeDropTargetsCount, + GdkDragAction(GDK_ACTION_COPY | GDK_ACTION_MOVE)); + g_signal_connect(G_OBJECT(w), + "drag_data_received", + G_CALLBACK(sp_ui_drag_data_received), + NULL); + gtk_widget_show(w); + + // needed because the first ACTIVATE_DESKTOP was sent when there was no window yet + inkscape_reactivate_desktop(SP_DESKTOP_WIDGET(vw)->desktop); +} + +void +sp_ui_new_view() +{ + SPDocument *document; + SPViewWidget *dtw; + + document = SP_ACTIVE_DOCUMENT; + if (!document) return; + + dtw = sp_desktop_widget_new(sp_document_namedview(document, NULL)); + g_return_if_fail(dtw != NULL); + + sp_create_window(dtw, TRUE); + sp_namedview_window_from_document(static_cast(dtw->view)); +} + +/* TODO: not yet working */ +/* To be re-enabled (by adding to menu) once it works. */ +void +sp_ui_new_view_preview() +{ + SPDocument *document; + SPViewWidget *dtw; + + document = SP_ACTIVE_DOCUMENT; + if (!document) return; + + dtw = (SPViewWidget *) sp_svg_view_widget_new(document); + g_return_if_fail(dtw != NULL); + sp_svg_view_widget_set_resize(SP_SVG_VIEW_WIDGET(dtw), TRUE, 400.0, 400.0); + + sp_create_window(dtw, FALSE); +} + +/** + * \param widget unused + */ +void +sp_ui_close_view(GtkWidget *widget) +{ + if (SP_ACTIVE_DESKTOP == NULL) { + return; + } + if ((SP_ACTIVE_DESKTOP)->shutdown()) { + return; + } + SP_ACTIVE_DESKTOP->destroyWidget(); +} + + +/** + * sp_ui_close_all + * + * This function is called to exit the program, and iterates through all + * open document view windows, attempting to close each in turn. If the + * view has unsaved information, the user will be prompted to save, + * discard, or cancel. + * + * Returns FALSE if the user cancels the close_all operation, TRUE + * otherwise. + */ +unsigned int +sp_ui_close_all(void) +{ + /* Iterate through all the windows, destroying each in the order they + become active */ + while (SP_ACTIVE_DESKTOP) { + if ((SP_ACTIVE_DESKTOP)->shutdown()) { + /* The user cancelled the operation, so end doing the close */ + return FALSE; + } + SP_ACTIVE_DESKTOP->destroyWidget(); + } + + return TRUE; +} + +static gint +sp_ui_delete(GtkWidget *widget, GdkEvent *event, Inkscape::UI::View::View *view) +{ + return view->shutdown(); +} + +/* + * Some day when the right-click menus are ready to start working + * smarter with the verbs, we'll need to change this NULL being + * sent to sp_action_perform to something useful, or set some kind + * of global "right-clicked position" variable for actions to + * investigate when they're called. + */ +static void +sp_ui_menu_activate(void *object, SPAction *action) +{ + sp_action_perform(action, NULL); +} + +static void +sp_ui_menu_select_action(void *object, SPAction *action) +{ + action->view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, action->tip); +} + +static void +sp_ui_menu_deselect_action(void *object, SPAction *action) +{ + action->view->tipsMessageContext()->clear(); +} + +static void +sp_ui_menu_select(gpointer object, gpointer tip) +{ + Inkscape::UI::View::View *view = static_cast (g_object_get_data(G_OBJECT(object), "view")); + view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, (gchar *)tip); +} + +static void +sp_ui_menu_deselect(gpointer object) +{ + Inkscape::UI::View::View *view = static_cast (g_object_get_data(G_OBJECT(object), "view")); + view->tipsMessageContext()->clear(); +} + +/** + * sp_ui_menuitem_add_icon + * + * Creates and attaches a scaled icon to the given menu item. + * + */ +void +sp_ui_menuitem_add_icon( GtkWidget *item, gchar *icon_name ) +{ + GtkWidget *icon; + + icon = sp_icon_new( GTK_ICON_SIZE_MENU, icon_name ); + gtk_widget_show(icon); + gtk_image_menu_item_set_image((GtkImageMenuItem *) item, icon); +} // end of sp_ui_menu_add_icon + +/** + * sp_ui_menu_append_item + * + * Appends a UI item with specific info for Inkscape/Sodipodi. + * + */ +static GtkWidget * +sp_ui_menu_append_item( GtkMenu *menu, gchar const *stock, + gchar const *label, gchar const *tip, Inkscape::UI::View::View *view, GCallback callback, + gpointer data, gboolean with_mnemonic = TRUE ) +{ + GtkWidget *item; + + if (stock) { + item = gtk_image_menu_item_new_from_stock(stock, NULL); + } else if (label) { + item = (with_mnemonic) + ? gtk_image_menu_item_new_with_mnemonic(label) : + gtk_image_menu_item_new_with_label(label); + } else { + item = gtk_separator_menu_item_new(); + } + + gtk_widget_show(item); + + if (callback) { + g_signal_connect(G_OBJECT(item), "activate", callback, data); + } + + if (tip && view) { + g_object_set_data(G_OBJECT(item), "view", (gpointer) view); + g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) tip ); + g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL); + } + + gtk_menu_append(GTK_MENU(menu), item); + + return item; + +} // end of sp_ui_menu_append_item() + +/** +\brief a wrapper around gdk_keyval_name producing (when possible) characters, not names + */ +static gchar const * +sp_key_name(guint keyval) +{ + /* TODO: Compare with the definition of gtk_accel_label_refetch in gtk/gtkaccellabel.c (or + simply use GtkAccelLabel as the TODO comment in sp_ui_shortcut_string suggests). */ + gchar const *n = gdk_keyval_name(gdk_keyval_to_upper(keyval)); + + if (!strcmp(n, "asciicircum")) return "^"; + else if (!strcmp(n, "parenleft" )) return "("; + else if (!strcmp(n, "parenright" )) return ")"; + else if (!strcmp(n, "plus" )) return "+"; + else if (!strcmp(n, "minus" )) return "-"; + else if (!strcmp(n, "asterisk" )) return "*"; + else if (!strcmp(n, "KP_Multiply")) return "*"; + else if (!strcmp(n, "Delete" )) return "Del"; + else if (!strcmp(n, "Page_Up" )) return "PgUp"; + else if (!strcmp(n, "Page_Down" )) return "PgDn"; + else if (!strcmp(n, "grave" )) return "`"; + else if (!strcmp(n, "numbersign" )) return "#"; + else if (!strcmp(n, "bar" )) return "|"; + else if (!strcmp(n, "slash" )) return "/"; + else if (!strcmp(n, "exclam" )) return "!"; + else return n; +} + + +/** + * \param shortcut A GDK keyval OR'd with SP_SHORTCUT_blah_MASK values. + * \param c Points to a buffer at least 256 bytes long. + */ +void +sp_ui_shortcut_string(unsigned const shortcut, gchar *const c) +{ + /* TODO: This function shouldn't exist. Our callers should use GtkAccelLabel instead of + * a generic GtkLabel containing this string, and should call gtk_widget_add_accelerator. + * Will probably need to change sp_shortcut_invoke callers. + * + * The existing gtk_label_new_with_mnemonic call can be replaced with + * g_object_new(GTK_TYPE_ACCEL_LABEL, NULL) followed by + * gtk_label_set_text_with_mnemonic(lbl, str). + */ + static GtkAccelLabelClass const &accel_lbl_cls + = *(GtkAccelLabelClass const *) g_type_class_peek_static(GTK_TYPE_ACCEL_LABEL); + + struct { unsigned test; char const *name; } const modifier_tbl[] = { + { SP_SHORTCUT_SHIFT_MASK, accel_lbl_cls.mod_name_shift }, + { SP_SHORTCUT_CONTROL_MASK, accel_lbl_cls.mod_name_control }, + { SP_SHORTCUT_ALT_MASK, accel_lbl_cls.mod_name_alt } + }; + + gchar *p = c; + gchar *end = p + 256; + + for (unsigned i = 0; i < G_N_ELEMENTS(modifier_tbl); ++i) { + if ((shortcut & modifier_tbl[i].test) + && (p < end)) + { + p += g_snprintf(p, end - p, "%s%s", + modifier_tbl[i].name, + accel_lbl_cls.mod_separator); + } + } + if (p < end) { + p += g_snprintf(p, end - p, "%s", sp_key_name(shortcut & 0xffffff)); + } + end[-1] = '\0'; // snprintf doesn't guarantee to nul-terminate the string. +} + +void +sp_ui_dialog_title_string(Inkscape::Verb *verb, gchar *c) +{ + SPAction *action; + unsigned int shortcut; + gchar *s; + gchar key[256]; + gchar *atitle; + + action = verb->get_action(NULL); + if (!action) + return; + + atitle = sp_action_get_title(action); + + s = g_stpcpy(c, atitle); + + g_free(atitle); + + shortcut = sp_shortcut_get_primary(verb); + if (shortcut) { + s = g_stpcpy(s, " ("); + sp_ui_shortcut_string(shortcut, key); + s = g_stpcpy(s, key); + s = g_stpcpy(s, ")"); + } +} + + +/** + * sp_ui_menu_append_item_from_verb + * + * Appends a custom menu UI from a verb. + * + */ + +static GtkWidget * +sp_ui_menu_append_item_from_verb(GtkMenu *menu, Inkscape::Verb *verb, Inkscape::UI::View::View *view, bool radio = false, GSList *group = NULL) +{ + SPAction *action; + GtkWidget *item; + + if (verb->get_code() == SP_VERB_NONE) { + + item = gtk_separator_menu_item_new(); + + } else { + unsigned int shortcut; + + action = verb->get_action(view); + + if (!action) return NULL; + + shortcut = sp_shortcut_get_primary(verb); + if (shortcut) { + gchar c[256]; + sp_ui_shortcut_string(shortcut, c); + GtkWidget *const hb = gtk_hbox_new(FALSE, 16); + GtkWidget *const name_lbl = gtk_label_new(""); + gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name); + gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5); + gtk_box_pack_start((GtkBox *) hb, name_lbl, TRUE, TRUE, 0); + GtkWidget *const accel_lbl = gtk_label_new(c); + gtk_misc_set_alignment((GtkMisc *) accel_lbl, 1.0, 0.5); + gtk_box_pack_end((GtkBox *) hb, accel_lbl, FALSE, FALSE, 0); + gtk_widget_show_all(hb); + if (radio) { + item = gtk_radio_menu_item_new (group); + } else { + item = gtk_image_menu_item_new(); + } + gtk_container_add((GtkContainer *) item, hb); + } else { + if (radio) { + item = gtk_radio_menu_item_new (group); + } else { + item = gtk_image_menu_item_new (); + } + GtkWidget *const name_lbl = gtk_label_new(""); + gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name); + gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5); + gtk_container_add((GtkContainer *) item, name_lbl); + } + + nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item); + if (!action->sensitive) { + gtk_widget_set_sensitive(item, FALSE); + } + + if (action->image) { + sp_ui_menuitem_add_icon(item, action->image); + } + gtk_widget_set_events(item, GDK_KEY_PRESS_MASK); + g_signal_connect( G_OBJECT(item), "activate", + G_CALLBACK(sp_ui_menu_activate), action ); + + g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select_action), action ); + g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect_action), action ); + } + + gtk_widget_show(item); + gtk_menu_append(GTK_MENU(menu), item); + + return item; + +} // end of sp_ui_menu_append_item_from_verb + + +static void +checkitem_toggled(GtkCheckMenuItem *menuitem, gpointer user_data) +{ + gchar const *pref = (gchar const *) user_data; + Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view"); + + gchar const *pref_path; + if (reinterpret_cast(view)->is_fullscreen) + pref_path = g_strconcat("fullscreen.", pref, NULL); + else + pref_path = g_strconcat("window.", pref, NULL); + + gboolean checked = gtk_check_menu_item_get_active(menuitem); + prefs_set_int_attribute(pref_path, "state", checked); + + reinterpret_cast(view)->layoutWidget(); +} + +static gboolean +checkitem_update(GtkWidget *widget, GdkEventExpose *event, gpointer user_data) +{ + GtkCheckMenuItem *menuitem=GTK_CHECK_MENU_ITEM(widget); + + gchar const *pref = (gchar const *) user_data; + Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view"); + + gchar const *pref_path; + if (static_cast(view)->is_fullscreen) + pref_path = g_strconcat("fullscreen.", pref, NULL); + else + pref_path = g_strconcat("window.", pref, NULL); + + gint ison = prefs_get_int_attribute_limited(pref_path, "state", 1, 0, 1); + + g_signal_handlers_block_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data); + gtk_check_menu_item_set_active(menuitem, ison); + g_signal_handlers_unblock_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data); + + return FALSE; +} + + +void +sp_ui_menu_append_check_item_from_verb(GtkMenu *menu, Inkscape::UI::View::View *view, gchar const *label, gchar const *tip, gchar const *pref, + void (*callback_toggle)(GtkCheckMenuItem *, gpointer user_data), + gboolean (*callback_update)(GtkWidget *widget, GdkEventExpose *event, gpointer user_data), + Inkscape::Verb *verb) +{ + GtkWidget *item; + + unsigned int shortcut = 0; + SPAction *action = NULL; + + if (verb) { + shortcut = sp_shortcut_get_primary(verb); + action = verb->get_action(view); + } + + if (verb && shortcut) { + gchar c[256]; + sp_ui_shortcut_string(shortcut, c); + + GtkWidget *hb = gtk_hbox_new(FALSE, 16); + + { + GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label); + gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5); + gtk_box_pack_start((GtkBox *) hb, l, TRUE, TRUE, 0); + } + + { + GtkWidget *l = gtk_label_new(c); + gtk_misc_set_alignment((GtkMisc *) l, 1.0, 0.5); + gtk_box_pack_end((GtkBox *) hb, l, FALSE, FALSE, 0); + } + + gtk_widget_show_all(hb); + + item = gtk_check_menu_item_new(); + gtk_container_add((GtkContainer *) item, hb); + } else { + GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label); + gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5); + item = gtk_check_menu_item_new(); + gtk_container_add((GtkContainer *) item, l); + } +#if 0 + nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item); + if (!action->sensitive) { + gtk_widget_set_sensitive(item, FALSE); + } +#endif + gtk_widget_show(item); + + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + + g_object_set_data(G_OBJECT(item), "view", (gpointer) view); + + g_signal_connect( G_OBJECT(item), "toggled", (GCallback) callback_toggle, (void *) pref); + g_signal_connect( G_OBJECT(item), "expose_event", (GCallback) callback_update, (void *) pref); + + g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) (action ? action->tip : tip)); + g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL); +} + +static void +sp_recent_open(GtkWidget *widget, gchar const *uri) +{ + sp_file_open(uri, NULL); +} + +static void +sp_file_new_from_template(GtkWidget *widget, gchar const *uri) +{ + sp_file_new(uri); +} + +void +sp_menu_append_new_templates(GtkWidget *menu, Inkscape::UI::View::View *view) +{ + std::list sources; + sources.push_back( profile_path("templates") ); // first try user's local dir + sources.push_back( g_strdup(INKSCAPE_TEMPLATESDIR) ); // then the system templates dir + + // Use this loop to iterate through a list of possible document locations. + while (!sources.empty()) { + gchar *dirname = sources.front(); + + if ( Inkscape::IO::file_test( dirname, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) ) ) { + GError *err = 0; + GDir *dir = g_dir_open(dirname, 0, &err); + + if (dir) { + for (gchar const *file = g_dir_read_name(dir); file != NULL; file = g_dir_read_name(dir)) { + if (!g_str_has_suffix(file, ".svg")) + continue; // skip non-svg files + + gchar *basename = g_path_get_basename(file); + if (g_str_has_suffix(basename, ".svg") && g_str_has_prefix(basename, "default.")) + continue; // skip default.*.svg (i.e. default.svg and translations) - it's in the menu already + + gchar const *filepath = g_build_filename(dirname, file, NULL); + gchar *dupfile = g_strndup(file, strlen(file) - 4); + gchar *filename = g_filename_to_utf8(dupfile, -1, NULL, NULL, NULL); + g_free(dupfile); + GtkWidget *item = gtk_menu_item_new_with_label(filename); + g_free(filename); + + gtk_widget_show(item); + // how does "filepath" ever get freed? + g_signal_connect(G_OBJECT(item), + "activate", + G_CALLBACK(sp_file_new_from_template), + (gpointer) filepath); + + if (view) { + // set null tip for now; later use a description from the template file + g_object_set_data(G_OBJECT(item), "view", (gpointer) view); + g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) NULL ); + g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL); + } + + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + } + g_dir_close(dir); + } + } + + // toss the dirname + g_free(dirname); + sources.pop_front(); + } +} + +void +sp_menu_append_recent_documents(GtkWidget *menu, Inkscape::UI::View::View* /* view */) +{ + gchar const **recent = prefs_get_recent_files(); + if (recent) { + int i; + + for (i = 0; recent[i] != NULL; i += 2) { + gchar const *uri = recent[i]; + gchar const *name = recent[i + 1]; + + GtkWidget *item = gtk_menu_item_new_with_label(name); + gtk_widget_show(item); + g_signal_connect(G_OBJECT(item), + "activate", + G_CALLBACK(sp_recent_open), + (gpointer)uri); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + } + + g_free(recent); + } else { + GtkWidget *item = gtk_menu_item_new_with_label(_("None")); + gtk_widget_show(item); + gtk_widget_set_sensitive(item, FALSE); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + } +} + +void +sp_ui_checkboxes_menus(GtkMenu *m, Inkscape::UI::View::View *view) +{ + //sp_ui_menu_append_check_item_from_verb(m, view, _("_Menu"), _("Show or hide the menu bar"), "menu", + // checkitem_toggled, checkitem_update, 0); + sp_ui_menu_append_check_item_from_verb(m, view, _("Commands Bar"), _("Show or hide the Commands bar (under the menu)"), "commands", + checkitem_toggled, checkitem_update, 0); + sp_ui_menu_append_check_item_from_verb(m, view, _("Tool Controls"), _("Show or hide the Tool Controls panel"), "toppanel", + checkitem_toggled, checkitem_update, 0); + sp_ui_menu_append_check_item_from_verb(m, view, _("_Toolbox"), _("Show or hide the main toolbox (on the left)"), "toolbox", + checkitem_toggled, checkitem_update, 0); + sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "rulers", + checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_RULERS)); + sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "scrollbars", + checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_SCROLLBARS)); + sp_ui_menu_append_check_item_from_verb(m, view, _("_Statusbar"), _("Show or hide the statusbar (at the bottom of the window)"), "statusbar", + checkitem_toggled, checkitem_update, 0); + sp_ui_menu_append_check_item_from_verb(m, view, _("_Panels"), _("Show or hide the panels"), "panels", + checkitem_toggled, checkitem_update, 0); +} + +/** \brief This function turns XML into a menu + \param menus This is the XML that defines the menu + \param menu Menu to be added to + \param view The View that this menu is being built for + + This function is realitively simple as it just goes through the XML + and parses the individual elements. In the case of a submenu, it + just calls itself recursively. Because it is only reasonable to have + a couple of submenus, it is unlikely this will go more than two or + three times. + + In the case of an unreconginzed verb, a menu item is made to identify + the verb that is missing, and display that. The menu item is also made + insensitive. +*/ +void +sp_ui_build_dyn_menus(Inkscape::XML::Node *menus, GtkWidget *menu, Inkscape::UI::View::View *view) +{ + if (menus == NULL) return; + if (menu == NULL) return; + GSList *group = NULL; + + for (Inkscape::XML::Node *menu_pntr = menus; + menu_pntr != NULL; + menu_pntr = menu_pntr->next()) { + if (!strcmp(menu_pntr->name(), "submenu")) { + if (!strcmp(menu_pntr->attribute("name"), "Effects") && !prefs_get_int_attribute("extensions", "show-effects-menu", 0)) { + continue; + } + GtkWidget *mitem = gtk_menu_item_new_with_mnemonic(_(menu_pntr->attribute("name"))); + GtkWidget *submenu = gtk_menu_new(); + sp_ui_build_dyn_menus(menu_pntr->firstChild(), submenu, view); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(mitem), GTK_WIDGET(submenu)); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem); + continue; + } + if (!strcmp(menu_pntr->name(), "verb")) { + gchar const *verb_name = menu_pntr->attribute("verb-id"); + Inkscape::Verb *verb = Inkscape::Verb::getbyid(verb_name); + + if (verb != NULL) { + if (menu_pntr->attribute("radio") != NULL) { + GtkWidget *item = sp_ui_menu_append_item_from_verb (GTK_MENU(menu), verb, view, true, group); + group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item)); + if (menu_pntr->attribute("default") != NULL) { + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE); + } + } else { + sp_ui_menu_append_item_from_verb(GTK_MENU(menu), verb, view); + group = NULL; + } + } else { + gchar string[120]; + g_snprintf(string, 120, _("Verb \"%s\" Unknown"), verb_name); + string[119] = '\0'; /* may not be terminated */ + GtkWidget *item = gtk_menu_item_new_with_label(string); + gtk_widget_set_sensitive(item, false); + gtk_widget_show(item); + gtk_menu_append(GTK_MENU(menu), item); + } + continue; + } + if (!strcmp(menu_pntr->name(), "separator") + // This was spelt wrong in the original version + // and so this is for backward compatibility. It can + // probably be dropped after the 0.44 release. + || !strcmp(menu_pntr->name(), "seperator")) { + GtkWidget *item = gtk_separator_menu_item_new(); + gtk_widget_show(item); + gtk_menu_append(GTK_MENU(menu), item); + continue; + } + if (!strcmp(menu_pntr->name(), "template-list")) { + sp_menu_append_new_templates(menu, view); + continue; + } + if (!strcmp(menu_pntr->name(), "recent-file-list")) { + sp_menu_append_recent_documents(menu, view); + continue; + } + if (!strcmp(menu_pntr->name(), "objects-checkboxes")) { + sp_ui_checkboxes_menus(GTK_MENU(menu), view); + continue; + } + } +} + +/** \brief Build the main tool bar + \param view View to build the bar for + + Currently the main tool bar is built as a dynamic XML menu using + \c sp_ui_build_dyn_menus. This function builds the bar, and then + pass it to get items attached to it. +*/ +GtkWidget * +sp_ui_main_menubar(Inkscape::UI::View::View *view) +{ + GtkWidget *mbar = gtk_menu_bar_new(); + + sp_ui_build_dyn_menus(inkscape_get_menus(INKSCAPE), mbar, view); + + return mbar; +} + +static void leave_group(GtkMenuItem *, SPDesktop *desktop) { + desktop->setCurrentLayer(SP_OBJECT_PARENT(desktop->currentLayer())); +} + +static void enter_group(GtkMenuItem *mi, SPDesktop *desktop) { + desktop->setCurrentLayer(reinterpret_cast(g_object_get_data(G_OBJECT(mi), "group"))); + SP_DT_SELECTION(desktop)->clear(); +} + +GtkWidget * +sp_ui_context_menu(Inkscape::UI::View::View *view, SPItem *item) +{ + GtkWidget *m; + SPDesktop *dt; + + dt = static_cast(view); + + m = gtk_menu_new(); + + /* Undo and Redo */ + sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_UNDO), view); + sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_REDO), view); + + /* Separator */ + sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL); + + sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_CUT), view); + sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_COPY), view); + sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_PASTE), view); + + /* Separator */ + sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL); + + sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DUPLICATE), view); + sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DELETE), view); + + /* Item menu */ + if (item) { + sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL); + sp_object_menu((SPObject *) item, dt, GTK_MENU(m)); + } + + /* layer menu */ + SPGroup *group=NULL; + if (item) { + if (SP_IS_GROUP(item)) { + group = SP_GROUP(item); + } else if ( item != dt->currentRoot() && SP_IS_GROUP(SP_OBJECT_PARENT(item)) ) { + group = SP_GROUP(SP_OBJECT_PARENT(item)); + } + } + + if (( group && group != dt->currentLayer() ) || + ( dt->currentLayer() != dt->currentRoot() ) ) { + sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL); + } + + if ( group && group != dt->currentLayer() ) { + /* TRANSLATORS: #%s is the id of the group e.g. , not a number. */ + gchar *label=g_strdup_printf(_("Enter group #%s"), SP_OBJECT_ID(group)); + GtkWidget *w = gtk_menu_item_new_with_label(label); + g_free(label); + g_object_set_data(G_OBJECT(w), "group", group); + g_signal_connect(G_OBJECT(w), "activate", GCallback(enter_group), dt); + gtk_widget_show(w); + gtk_menu_shell_append(GTK_MENU_SHELL(m), w); + } + + if ( dt->currentLayer() != dt->currentRoot() ) { + if ( SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) { + GtkWidget *w = gtk_menu_item_new_with_label(_("Go to parent")); + g_signal_connect(G_OBJECT(w), "activate", GCallback(leave_group), dt); + gtk_widget_show(w); + gtk_menu_shell_append(GTK_MENU_SHELL(m), w); + + } + } + + return m; +} + +/* Drag and Drop */ +void +sp_ui_drag_data_received(GtkWidget *widget, + GdkDragContext *drag_context, + gint x, gint y, + GtkSelectionData *data, + guint info, + guint event_time, + gpointer user_data) +{ + switch (info) { + case SVG_DATA: + case SVG_XML_DATA: { + gchar *svgdata = (gchar *)data->data; + + SPDocument *doc = SP_ACTIVE_DOCUMENT; + + Inkscape::XML::Document *rnewdoc = sp_repr_read_mem(svgdata, data->length, SP_SVG_NS_URI); + + if (rnewdoc == NULL) { + sp_ui_error_dialog(_("Could not parse SVG data")); + return; + } + + Inkscape::XML::Node *repr = sp_repr_document_root(rnewdoc); + gchar const *style = repr->attribute("style"); + + Inkscape::XML::Node *newgroup = sp_repr_new("svg:g"); + newgroup->setAttribute("style", style); + + for (Inkscape::XML::Node *child = repr->firstChild(); child != NULL; child = child->next()) { + Inkscape::XML::Node *newchild = child->duplicate(); + newgroup->appendChild(newchild); + } + + Inkscape::GC::release(rnewdoc); + + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + // Add it to the current layer + + // Greg's edits to add intelligent positioning of svg drops + SPObject *new_obj = NULL; + new_obj = desktop->currentLayer()->appendChildRepr(newgroup); + + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + selection->set(SP_ITEM(new_obj)); + // To move the imported object, we must temporarily set the "transform pattern with + // object" option. + { + int const saved_pref = prefs_get_int_attribute("options.transform", "pattern", 1); + prefs_set_int_attribute("options.transform", "pattern", 1); + sp_document_ensure_up_to_date(SP_DT_DOCUMENT(desktop)); + NR::Point m( desktop->point() - selection->bounds().midpoint() ); + sp_selection_move_relative(selection, m); + prefs_set_int_attribute("options.transform", "pattern", saved_pref); + } + + Inkscape::GC::release(newgroup); + sp_document_done(doc); + break; + } + + case URI_LIST: { + gchar *uri = (gchar *)data->data; + sp_ui_import_files(uri); + break; + } + + case PNG_DATA: + case JPEG_DATA: + case IMAGE_DATA: { + char tmp[1024]; + + StringOutputStream outs; + Base64OutputStream b64out(outs); + b64out.setColumnWidth(0); + + SPDocument *doc = SP_ACTIVE_DOCUMENT; + + Inkscape::XML::Node *newImage = sp_repr_new("svg:image"); + + for ( int i = 0; i < data->length; i++ ) { + b64out.put( data->data[i] ); + } + b64out.close(); + + + Glib::ustring str = outs.getString(); + + snprintf( tmp, sizeof(tmp), "data:%s;base64,", gdk_atom_name(data->type) ); + str.insert( 0, tmp ); + newImage->setAttribute("xlink:href", str.c_str()); + + GError *error = NULL; + GdkPixbufLoader *loader = gdk_pixbuf_loader_new_with_mime_type( gdk_atom_name(data->type), &error ); + if ( loader ) { + error = NULL; + if ( gdk_pixbuf_loader_write( loader, data->data, data->length, &error) ) { + GdkPixbuf *pbuf = gdk_pixbuf_loader_get_pixbuf(loader); + if ( pbuf ) { + int width = gdk_pixbuf_get_width(pbuf); + int height = gdk_pixbuf_get_height(pbuf); + snprintf( tmp, sizeof(tmp), "%d", width ); + newImage->setAttribute("width", tmp); + + snprintf( tmp, sizeof(tmp), "%d", height ); + newImage->setAttribute("height", tmp); + } + } + } + + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + + // Add it to the current layer + desktop->currentLayer()->appendChildRepr(newImage); + + Inkscape::GC::release(newImage); + sp_document_done( doc ); + break; + } + } +} + +static void +sp_ui_import_files(gchar *buffer) +{ + GList *list = gnome_uri_list_extract_filenames(buffer); + if (!list) + return; + g_list_foreach(list, sp_ui_import_one_file_with_check, NULL); + g_list_foreach(list, (GFunc) g_free, NULL); + g_list_free(list); +} + +static void +sp_ui_import_one_file_with_check(gpointer filename, gpointer unused) +{ + if (filename) { + if (strlen((char const *)filename) > 2) + sp_ui_import_one_file((char const *)filename); + } +} + +static void +sp_ui_import_one_file(char const *filename) +{ + SPDocument *doc = SP_ACTIVE_DOCUMENT; + if (!doc) return; + + if (filename == NULL) return; + + // Pass off to common implementation + // TODO might need to get the proper type of Inkscape::Extension::Extension + file_import( doc, filename, NULL ); +} + +void +sp_ui_error_dialog(gchar const *message) +{ + GtkWidget *dlg; + gchar *safeMsg = Inkscape::IO::sanitizeString(message); + + dlg = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, safeMsg); + sp_transientize(dlg); + gtk_window_set_resizable(GTK_WINDOW(dlg), FALSE); + gtk_dialog_run(GTK_DIALOG(dlg)); + gtk_widget_destroy(dlg); + g_free(safeMsg); +} + +bool +sp_ui_overwrite_file(gchar const *filename) +{ + bool return_value = FALSE; + GtkWidget *dialog; + GtkWidget *hbox; + GtkWidget *boxdata; + gchar *title; + gchar *text; + + if (Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS)) { + + title = g_strdup_printf(_("Overwrite %s"), filename); + dialog = gtk_dialog_new_with_buttons(title, + NULL, + (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), + GTK_STOCK_NO, + GTK_RESPONSE_NO, + GTK_STOCK_YES, + GTK_RESPONSE_YES, + NULL); + gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_YES); + + sp_transientize(dialog); + gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE); + + hbox = gtk_hbox_new(FALSE, 5); + boxdata = gtk_image_new_from_stock(GTK_STOCK_DIALOG_QUESTION, GTK_ICON_SIZE_DIALOG); + gtk_widget_show(boxdata); + gtk_box_pack_start(GTK_BOX(hbox), boxdata, TRUE, TRUE, 5); + text = g_strdup_printf(_("The file %s already exists. Do you want to overwrite that file with the current document?"), filename); + boxdata = gtk_label_new(text); + gtk_label_set_line_wrap(GTK_LABEL(boxdata), TRUE); + gtk_widget_show(boxdata); + gtk_box_pack_start(GTK_BOX(hbox), boxdata, FALSE, FALSE, 5); + gtk_widget_show(hbox); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox, TRUE, TRUE, 5); + + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES) { + return_value = TRUE; + } else { + return_value = FALSE; + } + + gtk_widget_destroy(dialog); + g_free(title); + g_free(text); + } else { + return_value = TRUE; + } + + return return_value; +} + +static void +sp_ui_menu_item_set_sensitive(SPAction *action, unsigned int sensitive, void *data) +{ + return gtk_widget_set_sensitive(GTK_WIDGET(data), sensitive); +} + +/* + 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 : diff --git a/src/interface.h b/src/interface.h new file mode 100644 index 000000000..099fdd277 --- /dev/null +++ b/src/interface.h @@ -0,0 +1,85 @@ +#ifndef __SP_INTERFACE_H__ +#define __SP_INTERFACE_H__ + +/* + * Main UI stuff + * + * Authors: + * Lauris Kaplinski + * Frank Felfe + * + * Copyright (C) 1999-2002 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +#include "forward.h" + + +/** + * Create a new document window. + */ +void sp_create_window (SPViewWidget *vw, gboolean editable); + +/** + * + */ +void sp_ui_close_view (GtkWidget *widget); + +/** + * + */ +void sp_ui_new_view (void); +void sp_ui_new_view_preview (void); + +/** + * + */ +unsigned int sp_ui_close_all (void); + +/** + * + */ +GtkWidget *sp_ui_main_menubar (Inkscape::UI::View::View *view); + +/** + * + */ +GtkWidget *sp_ui_context_menu (Inkscape::UI::View::View *v, SPItem *item); + + +/** + * + */ +void sp_menu_append_recent_documents (GtkWidget *menu); + + +/** + * + */ +void sp_ui_dialog_title_string (Inkscape::Verb * verb, gchar* c); + + +/** + * + */ +void sp_ui_error_dialog (const gchar * message); +bool sp_ui_overwrite_file (const gchar * filename); + +void sp_ui_shortcut_string (unsigned int shortcut, gchar* 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:encoding=utf-8:textwidth=99 : diff --git a/src/io/.cvsignore b/src/io/.cvsignore new file mode 100644 index 000000000..a437e318b --- /dev/null +++ b/src/io/.cvsignore @@ -0,0 +1,5 @@ +.deps +.dirstamp +.libs +makefile +streamtest diff --git a/src/io/Makefile.tst b/src/io/Makefile.tst new file mode 100644 index 000000000..1d6789fd2 --- /dev/null +++ b/src/io/Makefile.tst @@ -0,0 +1,47 @@ +############################################## +# +# Test makefile for InkscapeStreams +# +############################################## + + +CC = gcc +CXX = g++ + + +INC = -I. -I.. + +XSLT_CFLAGS = `pkg-config --cflags libxslt` +XSLT_LIBS = `pkg-config --libs libxslt` + +GLIBMM_CFLAGS = `pkg-config --cflags glibmm-2.4` +GLIBMM_LIBS = `pkg-config --libs glibmm-2.4` + +CFLAGS = -g $(GLIBMM_CFLAGS) $(XSLT_CFLAGS) +LIBS = $(GLIBMM_LIBS) $(XSLT_LIBS) ../uri.o -lz + +OBJ = \ +inkscapestream.o \ +base64stream.o \ +gzipstream.o \ +stringstream.o \ +uristream.o \ +xsltstream.o \ +ftos.o + +all: streamtest + +streamtest: inkscapestream.h libstream.a streamtest.o + $(CXX) -o streamtest streamtest.o libstream.a $(LIBS) + +libstream.a: $(OBJ) + ar crv libstream.a $(OBJ) + + +.cpp.o: + $(CXX) $(CFLAGS) $(INC) -c -o $@ $< + +clean: + -$(RM) *.o *.a + -$(RM) streamtest + diff --git a/src/io/Makefile_insert b/src/io/Makefile_insert new file mode 100644 index 000000000..57978d678 --- /dev/null +++ b/src/io/Makefile_insert @@ -0,0 +1,29 @@ +## Makefile.am fragment sourced by src/Makefile.am. + +io/all: io/libio.a + +io/clean: + rm -f io/libio.a $(io_libio_a_OBJECTS) + +io_libio_a_SOURCES = \ + io/base64stream.h \ + io/base64stream.cpp \ + io/ftos.cpp \ + io/ftos.h \ + io/gzipstream.cpp \ + io/gzipstream.h \ + io/inkscapestream.cpp \ + io/inkscapestream.h \ + io/simple-sax.cpp \ + io/simple-sax.h \ + io/stringstream.cpp \ + io/stringstream.h \ + io/sys.h \ + io/sys.cpp \ + io/uristream.cpp \ + io/uristream.h \ + io/xsltstream.cpp \ + io/xsltstream.h + +#io_streamtest_SOURCES = io/streamtest.cpp +#io_streamtest_LDADD = $(all_libs) diff --git a/src/io/base64stream.cpp b/src/io/base64stream.cpp new file mode 100644 index 000000000..e9f8fe2a2 --- /dev/null +++ b/src/io/base64stream.cpp @@ -0,0 +1,315 @@ +/** + * Base64-enabled input and output streams + * + * This class allows easy encoding and decoding + * of Base64 data with a stream interface, hiding + * the implementation from the user. + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2004 Inkscape.org + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "base64stream.h" + + + +namespace Inkscape +{ +namespace IO +{ + +//######################################################################### +//# B A S E 6 4 I N P U T S T R E A M +//######################################################################### + +static int base64decode[] = +{ +/*00*/ -1, -1, -1, -1, -1, -1, -1, -1, +/*08*/ -1, -1, -1, -1, -1, -1, -1, -1, +/*10*/ -1, -1, -1, -1, -1, -1, -1, -1, +/*18*/ -1, -1, -1, -1, -1, -1, -1, -1, +/*20*/ -1, -1, -1, -1, -1, -1, -1, -1, +/*28*/ -1, -1, -1, 62, -1, -1, -1, 63, +/*30*/ 52, 53, 54, 55, 56, 57, 58, 59, +/*38*/ 60, 61, -1, -1, -1, -1, -1, -1, +/*40*/ -1, 0, 1, 2, 3, 4, 5, 6, +/*48*/ 7, 8, 9, 10, 11, 12, 13, 14, +/*50*/ 15, 16, 17, 18, 19, 20, 21, 22, +/*58*/ 23, 24, 25, -1, -1, -1, -1, -1, +/*60*/ -1, 26, 27, 28, 29, 30, 31, 32, +/*68*/ 33, 34, 35, 36, 37, 38, 39, 40, +/*70*/ 41, 42, 43, 44, 45, 46, 47, 48, +/*78*/ 49, 50, 51, -1, -1, -1, -1, -1 +}; + + +/** + * + */ +Base64InputStream::Base64InputStream(InputStream &sourceStream) + : BasicInputStream(sourceStream) +{ + outCount = 0; + padCount = 0; + closed = false; + done = false; +} + +/** + * + */ +Base64InputStream::~Base64InputStream() +{ + close(); +} + +/** + * Returns the number of bytes that can be read (or skipped over) from + * this input stream without blocking by the next caller of a method for + * this input stream. + */ +int Base64InputStream::available() +{ + if (closed ) + return 0; + int len = source.available() * 2 / 3; + return len; +} + + +/** + * Closes this input stream and releases any system resources + * associated with the stream. + */ +void Base64InputStream::close() +{ + if (closed) + return; + source.close(); + closed = true; +} + +/** + * Reads the next byte of data from the input stream. -1 if EOF + */ +int Base64InputStream::get() +{ + if (closed) + return -1; + + if (outCount - padCount > 0) + { + return outBytes[3-(outCount--)]; + } + + if (done) + return -1; + + int inBytes[4]; + int inCount = 0; + while (inCount < 4) + { + int ch = source.get(); + if (ch < 0) + { + while (inCount < 4) //pad if needed + { + inBytes[inCount++] = 0; + padCount++; + } + done = true; + break; + } + if (isspace(ch)) //ascii whitespace + { + //nothing + } + else if (ch == '=') //padding + { + inBytes[inCount++] = 0; + padCount++; + } + else + { + int byteVal = base64decode[ch & 0x7f]; + //printf("char:%c %d\n", ch, byteVal); + if (byteVal < 0) + { + //Bad lookup value + } + inBytes[inCount++] = byteVal; + } + } + + outBytes[0] = ((inBytes[0]<<2) & 0xfc) | ((inBytes[1]>>4) & 0x03); + outBytes[1] = ((inBytes[1]<<4) & 0xf0) | ((inBytes[2]>>2) & 0x0f); + outBytes[2] = ((inBytes[2]<<6) & 0xc0) | ((inBytes[3] ) & 0x3f); + + outCount = 3; + + //try again + if (outCount - padCount > 0) + { + return outBytes[3-(outCount--)]; + } + + return -1; + +} + + +//######################################################################### +//# B A S E 6 4 O U T P U T S T R E A M +//######################################################################### + +static char *base64encode = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/** + * + */ +Base64OutputStream::Base64OutputStream(OutputStream &destinationStream) + : BasicOutputStream(destinationStream) +{ + column = 0; + columnWidth = 72; + outBuf = 0L; + bitCount = 0; +} + +/** + * + */ +Base64OutputStream::~Base64OutputStream() +{ + close(); +} + +/** + * Closes this output stream and releases any system resources + * associated with this stream. + */ +void Base64OutputStream::close() +{ + if (closed) + return; + + //get any last bytes (1 or 2) out of the buffer + if (bitCount == 16) + { + outBuf <<= 2; //pad to make 18 bits + + int indx = (int)((outBuf & 0x0003f000L) >> 12); + int obyte = (int)base64encode[indx & 63]; + putCh(obyte); + + indx = (int)((outBuf & 0x00000fc0L) >> 6); + obyte = (int)base64encode[indx & 63]; + putCh(obyte); + + indx = (int)((outBuf & 0x0000003fL) ); + obyte = (int)base64encode[indx & 63]; + putCh(obyte); + + putCh('='); + } + else if (bitCount == 8) + { + outBuf <<= 4; //pad to make 12 bits + + int indx = (int)((outBuf & 0x00000fc0L) >> 6); + int obyte = (int)base64encode[indx & 63]; + putCh(obyte); + + indx = (int)((outBuf & 0x0000003fL) ); + obyte = (int)base64encode[indx & 63]; + putCh(obyte); + + putCh('='); + putCh('='); + } + + if (columnWidth > 0) //if <=0, no newlines + destination.put('\n'); + + destination.close(); + closed = true; +} + +/** + * Flushes this output stream and forces any buffered output + * bytes to be written out. + */ +void Base64OutputStream::flush() +{ + if (closed) + return; + //dont flush here. do it on close() + destination.flush(); +} + +/** + * Private. Put a char to the output stream, checking for line length + */ +void Base64OutputStream::putCh(int ch) +{ + destination.put(ch); + column++; + if (columnWidth > 0 && column >= columnWidth) + { + destination.put('\n'); + column = 0; + } +} + + +/** + * Writes the specified byte to this output stream. + */ +void Base64OutputStream::put(int ch) +{ + if (closed) + { + //probably throw an exception here + return; + } + + outBuf <<= 8; + outBuf |= (ch & 0xff); + bitCount += 8; + if (bitCount >= 24) + { + int indx = (int)((outBuf & 0x00fc0000L) >> 18); + int obyte = (int)base64encode[indx & 63]; + putCh(obyte); + + indx = (int)((outBuf & 0x0003f000L) >> 12); + obyte = (int)base64encode[indx & 63]; + putCh(obyte); + + indx = (int)((outBuf & 0x00000fc0L) >> 6); + obyte = (int)base64encode[indx & 63]; + putCh(obyte); + + indx = (int)((outBuf & 0x0000003fL) ); + obyte = (int)base64encode[indx & 63]; + putCh(obyte); + + bitCount = 0; + outBuf = 0L; + } +} + + + +} // namespace IO +} // namespace Inkscape + + +//######################################################################### +//# E N D O F F I L E +//######################################################################### diff --git a/src/io/base64stream.h b/src/io/base64stream.h new file mode 100644 index 000000000..7bfe73e5f --- /dev/null +++ b/src/io/base64stream.h @@ -0,0 +1,122 @@ +#ifndef __INKSCAPE_IO_BASE64STREAM_H__ +#define __INKSCAPE_IO_BASE64STREAM_H__ + +/** + * Base64-enabled input and output streams + * + * This class allows easy encoding and decoding + * of Base64 data with a stream interface, hiding + * the implementation from the user. + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2004 Inkscape.org + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + +#include "inkscapestream.h" + + + +namespace Inkscape +{ +namespace IO +{ + +//######################################################################### +//# B A S E 6 4 I N P U T S T R E A M +//######################################################################### + +/** + * This class is for decoding a Base-64 encoded InputStream source + * + */ +class Base64InputStream : public BasicInputStream +{ + +public: + + Base64InputStream(InputStream &sourceStream); + + virtual ~Base64InputStream(); + + virtual int available(); + + virtual void close(); + + virtual int get(); + +private: + + int outBytes[3]; + + int outCount; + + int padCount; + + bool done; + +}; // class Base64InputStream + + + + +//######################################################################### +//# B A S E 6 4 O U T P U T S T R E A M +//######################################################################### + +/** + * This class is for Base-64 encoding data going to the + * destination OutputStream + * + */ +class Base64OutputStream : public BasicOutputStream +{ + +public: + + Base64OutputStream(OutputStream &destinationStream); + + virtual ~Base64OutputStream(); + + virtual void close(); + + virtual void flush(); + + virtual void put(int ch); + + /** + * Sets the maximum line length for base64 output. If + * set to <=0, then there will be no line breaks; + */ + virtual void setColumnWidth(int val) + { columnWidth = val; } + +private: + + void putCh(int ch); + + int column; + + int columnWidth; + + unsigned long outBuf; + + int bitCount; + +}; // class Base64OutputStream + + + + + + + +} // namespace IO +} // namespace Inkscape + + +#endif /* __INKSCAPE_IO_BASE64STREAM_H__ */ diff --git a/src/io/crystalegg.xml b/src/io/crystalegg.xml new file mode 100644 index 000000000..a94220c28 --- /dev/null +++ b/src/io/crystalegg.xml @@ -0,0 +1,769 @@ + + + + + + The Crystal Egg + H. G.Wells + + + + + + + There was, until a year ago, a little and very grimy-looking shop +near Seven Dials over which, in weather-worn yellow lettering, the name +of "C. Cave, Naturalist and Dealer in Antiquities," was inscribed. The +contents of its window were curiously variegated. They comprised some +elephant tusks and an imperfect set of chessmen, beads and weapons, a +box of eyes, two skulls of tigers and one human, several moth-eaten +stuffed monkeys (one holding a lamp), an old-fashioned cabinet, a +flyblown ostrich egg or so, some fishing-tackle, and an extraordinarily +dirty, empty glass fish tank. There was also, at the moment the story +begins, a mass of crystal, worked into the shape of an egg and +brilliantly polished. And at that two people, who stood outside the +window, were looking, one of them a tall, thin clergyman, the other a +black-bearded young man of dusky complexion and unobtrusive costume. The +dusky young man spoke with eager gestulation, and seemed anxious for his +companion to purchase the article. + + + + While they were there, Mr. Cave came into his shop, his beard still +wagging with the bread and butter of his tea. When he saw these men and +the object of their regard, his countenance fell. He glanced guiltily +over his shoulder, and softly shut the door. He was a little old man, +with pale face and peculiar watery blue eyes; his hair was a dirty grey, +and he wore a shabby blue frock-coat, an ancient silk hat, and carpet +slippers very much down at heel. He remained watching the two men as +they talked. The clergyman went deep into his trouser pocket, examined a +handful of money, and showed his teeth in an agreeable smile. Mr. Cave +seemed still more depressed when they came into the shop. + + + + The clergyman, without any ceremony, asked the price of the crystal +egg. Mr. Cave glanced nervously towards the door leading into the +parlour, and said five pounds. The clergyman protested that the price +was high, to his companion as well as to Mr. Cave -- it was, indeed, +very much more than Mr. Cave had intended to ask, when he had stocked +the article -- and an attempt at bargaining ensued. Mr. Cave stepped to +the shop-door, and held it open. "Five pounds is my price," he said, as +though he wished to save himself the trouble of unprofitable discussion. +As he did so, the upper portion of a woman's face appeared above the +blind in the glass upper panel of the door leading into the parlour, and +stared curiously at the two customers. "Five pounds is my price," said +Mr. Cave, with a quiver in his voice. + + + + The swarthy young man had so far remained a spectator, watching Cave +keenly. Now he spoke. "Give him five pounds," he said. The clergyman +glanced at him to see if he were in earnest, and, when he looked at Mr. +Cave again, he saw that the latter's face was white. "It's a lot of +money," said the clergyman, and, diving into his pocket, began counting +his resources. He had little more than thirty shillings, and he appealed +to his companion, with whom he seemed to be on terms of considerable +intimacy. This gave Mr. Cave an opportunity of collecting his thoughts, +and he began to explain in an agitated manner that the crystal was not, +as a matter of fact, entirely free for sale. His two customers were +naturally surprised at this, and inquired why he had not thought of that +before he began to bargain. Mr. Cave became confused, but he stuck to +his story, that the crystal was not in the market that afternoon, that a +probable purchaser of it had already appeared. The two, treating this as +an attempt to raise the price still further, made as if they would leave +the shop. But at this point the parlour door opened, and the owner of +the dark fringe and the little eyes appeared. + + + + She was a coarse-featured, corpulent woman, younger and very much +larger than Mr. Cave; she walked heavily, and her face was flushed. +"That crystal is for sale, she said. "And five pounds is a good enough +price for it. I can't think what you're about, Cave, not to take the +gentleman's offer!" + + + + Mr. Cave, greatly perturbed by the irruption, looked angrily at her +over the rims of his spectacles, and, without excessive assurance, +asserted his right to manage his business in his own way. An altercation +began. The two customers watched the scene with interest and some +amusement, occasionally assisting Mrs. Cave with suggestions. Mr. Cave, +hard driven, persisted in a confused and impossible story of an enquiry +for the crystal that morning, and his agitation became painful. But he +stuck to his point with extraordinary persistence. +It was the young Oriental who ended this curious controversy. He +proposed that they should call again in the course of two days -- so as +to give the alleged enquirer a fair chance. "And then we must insist," +said the clergyman. "Five pounds." Mrs. Cave took it on herself to +apologise for her husband, explaining that he was sometimes "a little +odd," and as the two customers left, the couple prepared for a free +discussion of the incident in all its bearings. + + + + Mrs. Cave talked to her husband with singular directness. The poor +little man, quivering with emotion, muddled himself between his stories, +maintaining on the one hand that he had another customer in view, and on +the other asserting that the crystal was honestly worth ten guineas. +"Why did you ask five pounds?" said his wife. "Do let me manage my +business my own way!" said Mr. Cave. + + + + Mr. Cave had living with him a step-daughter and a step-son, and at +supper that night the transaction was re-discussed. None of them had a +high opinion of Mr. Cave's business methods, and this action seemed a +culminating folly. + + + + "It's my opinion he's refused that crystal before," said the +step-son, a loose-limbed lout of eighteen. + + + + "But Five Pounds!" said the step-daughter, an argumentative young +woman of six-and-twenty. + + + + Mr. Cave's answers were wretched; he could only mumble weak +assertions that he knew his own business best. They drove him from his +half-eaten supper into the shop, to close it for the night, his ears +aflame and tears of vexation behind his spectacles. "Why had he left the +crystal in the window so long? The folly of it!" That was the trouble +closest in his mind. For a time he could see no way of evading sale. + + + + After supper his step-daughter and step-son smartened themselves up +and went out and his wife retired upstairs to reflect upon the business +aspects of the crystal, over a little sugar and lemon and so forth in +hot water. Mr. Cave went into the shop, and stayed there until late, +ostensibly to make ornamental rockeries for gold-fish cases but really +for a private purpose that will be better explained later. The next day +Mrs. Cave found that the crystal had been removed from the window, and +was lying behind some second-hand books on angling. She replaced it in a +conspicuous position. But she did not argue further about it, as a +nervous headache disinclined her from debate. Mr. Cave was always +disinclined. The day passed disagreeably. Mr. Cave was, if anything, +more absent-minded than usual, and uncommonly irritable withal. In the +afternoon, when his wife was taking her customary sleep, he removed the +crystal from the window again. + + + + The next day Mr. Cave had to deliver a consignment of dog-fish at one +of the hospital schools, where they were needed for dissection. In his +absence Mrs. Cave's mind reverted to the topic of the crystal, and the +methods of expenditure suitable to a windfall of five pounds. She had +already devised some very agreeable expedients, among others a dress of +green silk for herself and a trip to Richmond, when a jangling of the +front door bell summoned her into the shop. The customer was an +examination coach who came to complain of the non-delivery of certain +frogs asked for the previous day. Mrs. Cave did not approve of this +particular branch of Mr. Cave's business, and the gentleman, who had +called in a somewhat aggressive mood, retired after a brief exchange of +words -- entirely civil so far as he was concerned. Mrs. Cave's eye then +naturally turned to the window; for the sight of the crystal was an +assurance of the five pounds and of her dreams. What was her surprise to +find it gone! + + + + She went to the place behind the locker on the counter, where she had +discovered it the day before. It was not there; and she immediately +began an eager search about the shop. + + + + When Mr. Cave returned from his business with the dog-fish, about a +quarter to two in the afternoon, he found the shop in some confusion, +and his wife, extremely exasperated and on her knees behind the counter, +routing among his taxidermic material. Her face came up hot and angry +over the counter, as the jangling bell announced his return, and she +forthwith accused him of "hiding it." + + + + "Hid what?" asked Mr. Cave. + + + + "The crystal!" + + + + At that Mr. Cave, apparently much surprised, rushed to the window. +"Isn't it here?" he said. "Great Heavens! what has become of it?" + + + + Just then, Mr. Cave's step-son re-entered the shop from the inner +room -- he had come home a minute or so before Mr. Cave -- and he was +blaspheming freely. He was apprenticed to a second-hand furniture dealer +down the road, but he had his meals at home, and he was naturally +annoyed to find no dinner ready. + + + + But, when he heard of the loss of the crystal, he forgot his meal, +and his anger was diverted from his mother to his step-father. Their +first idea, of course, was that he had hidden it. But Mr. Cave stoutly +denied all knowledge of its fate -- freely offering his bedabbled +affidavit in the matter -- and at last was worked up to the point of +accusing, first, his wife and then his step-son of having taken it with +a view to a private sale. So began an exceedingly acrimonious and +emotional discussion, which ended for Mrs. Cave in a peculiar nervous +condition midway between hysterics and amuck, and caused the step-son to +be half-an-hour late at the furniture establishment in the afternoon. +Mr. Cave took refuge from his wife's emotions in the shop. + + + + In the evening the matter was resumed, with less passion and in a +judicial spirit, under the presidency of the step-daughter. The supper +passed unhappily and culminated in a painful scene. Mr. Cave gave way at +last to extreme exasperation, and went out banging the front door +violently. The rest of the family, having discussed him with the freedom +his absence warranted, hunted the house from garret to cellar, hoping to +light upon the crystal. + + + + The next day the two customers called again. They were received by +Mrs. Cave almost in tears. It transpired that no one could imagine all +that she had stood from Cave at various times in her married pilgrimage. +. . . She also gave a garbled account of the disappearance. The +clergyman and the Oriental laughed silently at one another, and said it +was very extraordinary. As Mrs. Cave seemed disposed to give them the +complete history of her life they made to leave the shop. Thereupon Mrs. +Cave, still clinging to hope, asked for the clergyman's address, so +that, if she could get anything out of Cave, she might communicate it. +The address was duly given, but apparently was afterwards mislaid. Mrs. +Cave can remember nothing about it. + + + + In the evening of that day, the Caves seem to have exhausted their +emotions, and Mr. Cave, who had been out in the afternoon, supped in a +gloomy isolation that contrasted pleasantly with the impassioned +controversy of the previous days. For some time matters were very badly +strained in the Cave household, but neither crystal nor customer +reappeared. + + + + Now, without mincing the matter, we must admit that Mr. Cave was a +liar. He knew perfectly well where the crystal was. It was in the rooms +of Mr. Jacoby Wace, Assistant Demonstrator at St. Catherine's Hospital, +Westbourne Street. It stood on the sideboard partially covered by a +black velvet cloth, and beside a decanter of American whisky. It is from +Mr. Wace, indeed, that the particulars upon which this narrative is +based were derived. Cave had taken off the thing to the hospital hidden +in the dog-fish sack, and there had pressed the young investigator to +keep it for him. Mr. Wace was a little dubious at first. His +relationship to Cave was peculiar. He had a taste for singular +characters, and he had more than once invited the old man to smoke and +drink in his rooms, and to unfold his rather amusing views of life in +general and of his wife in particular. Mr. Wace had encountered Mrs. +Cave, too, on occasions when Mr. Cave was not at home to attend to him. +He knew the constant interference to which Cave was subjected, and +having weighed the story judicially, he decided to give the crystal a +refuge. Mr. Cave promised to explain the reasons for his remarkable +affection for the crystal more fully +on a later occasion, but he spoke distinctly of seeing visions therein. +He called on Mr. Wace the same evening. + + + + He told a complicated story. The crystal he said had come into his +possession with other oddments at the forced sale of another curiosity +dealer's effects, and not knowing what its value might be, he had +ticketed it at ten shillings. It had hung upon his hands at that price +for some months, and he was thinking of "reducing the figure," when he +made a singular discovery. + + + + At that time his health was very bad -- and it must be borne in mind +that, throughout all this experience, his physical condition was one of +ebb -- and he was in considerable distress by reason of the negligence, +the positive ill-treatment even, he received from his wife and +step-children. His wife was vain, extravagant, unfeeling and had a +growing taste for private drinking; his step-daughter was mean and +over-reaching; and his step-son had conceived a violent dislike for him, +and lost no chance of showing it. The requirements of his business +pressed heavily upon him, and Mr. Wace does not think that he was +altogether free from occasional intemperance. He had begun life in a +comfortable position, he was a man of fair education, and he suffered, +for weeks at a stretch, from melancholia and insomnia. Afraid to disturb +his family, he would slip quietly from his wife's side, when his +thoughts became intolerable, and wander about the house. And about three +o'clock one morning, late in August, chance directed him into the shop. + + + + The dirty little place was impenetrably black except in one spot, +where he perceived an unusual glow of light. Approaching this, he +discovered it to be the crystal egg, which was standing on the corner of +the counter towards the window. A thin ray smote through a crack in the +shutters, impinged upon the object, and seemed as it were to fill its +entire interior. + + + + It occurred to Mr. Cave that this was not in accordance with the laws +of optics as he had known them in his younger days. He could understand +the rays being refracted by the crystal and coming to a focus in its +interior, but this diffusion jarred with his physical conceptions. He +approached the crystal nearly, peering into it and round it, with a +transient revival of the scientific curiosity that in his youth had +determined his choice of a calling. He was surprised to find the light +not steady, but writhing within the substance of the egg, as though that +object was a hollow sphere of some luminous vapour. In moving about to +get different points of view, he suddenly found that he had come between +it and the ray, and that the crystal none the less remained luminous. +Greatly astonished, he lifted it out of the light ray and carried it to +the darkest part of the shop. It remained +bright for some four or five minutes, when it slowly faded and went out. +He placed it in the thin streak of daylight, and its luminousness was +almost immediately restored. + + + + So far, at least, Mr. Wace was able to verify the remarkable story of +Mr. Cave. He has himself repeatedly held this crystal in a ray of light +(which had to be of a less diameter than one millimetre). And in a +perfect darkness, such as could be produced by velvet wrapping, the +crystal did undoubtedly appear very faintly phosphorescent. It would +seem, however, that the luminousness was of some exceptional sort, and +not equally visible to all eyes; for Mr. Harbinger -- whose name will be +familiar to the scientific reader in connection with the Pasteur +Institute -- was quite unable to see any light whatever. And Mr. Wace's +own capacity for its appreciation was out of comparison inferior to that +of Mr. Cave's. Even with Mr. Cave the power varied very considerably: +his vision was most vivid during states of extreme weakness and fatigue. + + + + Now, from the outset this light in the crystal exercised a curious +fascination upon Mr. Cave. And it says more for his loneliness of soul +than a volume of pathetic writing could do, that he told no human being +of his curious observations. He seems to have been living in such an +atmosphere of petty spite that to admit the existence of a pleasure +would have been to risk the loss of it. He found that as the dawn +advanced, and the amount of diffused light increased, the crystal became +to all appearance non-luminous. And for some time he was unable to see +anything in it, except at night-time, in dark corners of the shop. + + + + But the use of an old velvet cloth, which he used as a background for +a collection of minerals, occurred to him, and by doubling this, and +putting it over his head and hands, he was able to get a sight of the +luminous movement within the crystal even in the day-time. He was very +cautious lest he should be thus discovered by his wife, and he practised +this occupation only in the afternoons, while she was asleep upstairs, +and then circumspectly in a hollow under the counter. And one day, +turning the crystal about in his hands, he saw something. It came and +went like a flash, but it gave him the impression that the object had +for a moment opened to him the view of a wide and spacious and strange +country; and, turning it about, he did, just as the light faded, see the +same vision again. + + + + Now, it would be tedious and unnecessary to state all the phases of +Mr. Cave's discovery from this point. Suffice that the effect was this: +the crystal, being peered into at an angle of about 137 degrees from the +direction of the illuminating ray, gave a clear and consistent picture +of a wide and peculiar country-side. It was not dream-like at +all: it produced a definite impression of reality, and the better the +light the more real and solid it seemed. It was a moving picture: that +is to say, certain objects moved in it, but slowly in an orderly manner +like real things, and, according as the direction of the lighting and +vision changed, the picture changed also. It must, indeed, have been +like looking through an oval glass at a view, and turning the glass +about to get at different aspects. + + + + Mr. Cave's statements, Mr. Wace assures me, were extremely +circumstantial, and entirely free from any of that emotional quality +that taints hallucinatory impressions. But it must be remembered that +all the efforts of Mr. Wace to see any similar clarity in the faint +opalescence of the crystal were wholly unsuccessful, try as he would. +The difference in intensity of the impressions received by the two men +was very great, and it is quite conceivable that what was a view to Mr. +Cave was a mere blurred nebulosity to Mr. Wace. + + + + The view, as Mr. Cave described it, was invariably of an extensive +plain, and he seemed always to be looking at it from a considerable +height, as if from a tower or a mast. To the east and to the west the +plain was bounded at a remote distance by vast reddish cliffs, which +reminded him of those he had seen in some picture; but what the picture +was Mr. Wace was unable to ascertain. These cliffs passed north and +south -- he could tell the points of the compass by the stars that were +visible of a night -- receding in an almost illimitable perspective and +fading into the mists of the distance before they met. He was nearer the +eastern set of cliffs, on the occasion of his first vision the sun was +rising over them, and black against the sunlight and pale against their +shadow appeared a multitude of soaring forms that Mr. Cave regarded as +birds. A vast range of buildings spread below him; he seemed to be +looking down upon them; and, as they approached the blurred and +refracted edge of the picture, they became indistinct. There were also +trees curious in shape, and in colouring, a deep mossy green and an +exquisite grey, beside a wide and shining canal. And something great and +brilliantly coloured flew across the picture. But the first time Mr. +Cave saw these pictures he saw only in flashes, his hands shook, his +head moved, the vision came and went, and grew foggy and indistinct. And +at first he had the greatest difficulty in finding the picture again +once the direction of it was lost. + + + + His next clear vision, which came about a week after the first, the +interval having yielded nothing but tantalising glimpses and some useful +experience, showed him the view down the length of the valley. The view +was different, but he had a curious persuasion, which his subsequent +observations abundantly confirmed, that he was regarding this strange +world from exactly the same spot, although he was looking +in a different direction. The long facade of the great building, whose +roof he had looked down upon before, was now receding in perspective. He +recognised the roof. In the front of the facade was a terrace of massive +proportions and extraordinary length, and down the middle of the +terrace, at certain intervals, stood huge but very graceful masts, +bearing small shiny objects which reflected the setting sun. The import +of these small objects did not occur to Mr. Cave until some time after, +as he was describing the scene to Mr. Wace. The terrace overhung a +thicket of the most luxuriant and graceful vegetation, and beyond this +was a wide grassy lawn on which certain broad creatures, in form like +beetles but enormously larger, reposed. Beyond this again was a richly +decorated causeway of pinkish stone; and beyond that, and lined with +dense red weeds, and passing up the valley exactly parallel with the +distant cliffs, was a broad and mirror-like expanse of water. The air +seemed full of squadrons of great birds, manoeuvring in stately curves; +and across the river was a multitude of splendid buildings, richly +coloured and glittering with metallic tracery and facets, among a forest +of moss-like and lichenous trees. And suddenly something flapped +repeatedly across the vision, like the fluttering of a jewelled fan or +the beating of a wing, and a face, or rather the upper part of a face +with very large eyes, came as it were close to his own and as if on the +other side of the crystal. Mr. Cave was so startled and so impressed by +the absolute reality of these eyes, that he drew his head back from the +crystal to look behind it. He had become so absorbed in watching that he +was quite surprised to find himself in the cool darkness of his little +shop, with its familiar odour of methyl, mustiness, and decay. And, as +he blinked about him, the glowing crystal faded, and went out. + + + + Such were the first general impressions of Mr. Cave. The story is +curiously direct and circumstantial. From the outset, when the valley +first flashed momentarily on his senses, his imagination was strangely +affected, and, as he began to appreciate the details of the scene he +saw, his wonder rose to the point of a passion. He went about his +business listless and distraught, thinking only of the time when he +should be able to return to his watching. And then a few weeks after his +first sight of the valley came the two customers, the stress and +excitement of their offer, and the narrow escape of the crystal from +sale, as I have already told. + + + + Now, while the thing was Mr. Cave's secret, it remained a mere +wonder, a thing to creep to covertly and peep at, as a child might peep +upon a forbidden garden. But Mr. Wace has, for a young scientific +investigator, a particularly lucid and consecutive habit of mind. +himself, by seeing the phosphorescence with his own eyes, that there +really was a certain evidence for Mr. Cave's statements, he proceeded to +develop the matter systematically. Mr. Cave was only too eager to come +and feast his eyes on this wonderland he saw, and he came every night +from half-past eight until half-past ten, and sometimes, in Mr. Wace's +absence, during the day. On Sunday afternoons, also, he came. From the +outset Mr. Wace made copious notes, and it was due to his scientific +method that the relation between the direction from which the initiating +ray entered the crystal and the orientation of the picture were proved. +And, by covering the crystal in a box perforated only with a small +aperture to admit the exciting ray, and by substituting black holland +for his buff blinds, he greatly improved the conditions of the +observations; so that in a little while they were able to survey the +valley in any direction they desired. + + + + So having cleared the way, we may give a brief account of this +visionary world within the crystal. The things were in all cases seen by +Mr. Cave, and the method of working was invariably for him to watch the +crystal and report what he saw, while Mr. Wace (who as a science student +had learnt the trick of writing in the dark) wrote a brief note of his +report. When the crystal faded, it was put into its box in the proper +position and the electric light turned on. Mr. Wace asked questions, and +suggested observations to clear up difficult points. Nothing, indeed, +could have been less visionary and more matter-of-fact. + + + + The attention of Mr. Cave had been speedily directed to the bird-like +creatures he had seen so abundantly present in each of his earlier +visions. His first impression was soon corrected, and he considered for +a time that they might represent a diurnal species of bat. Then he +thought, grotesquely enough, that they might be cherubs. Their heads +were round, and curiously human, and it was the eyes of one of them that +had so startled him on his second observation. They had broad, silvery +wings, not feathered, but glistening almost as brilliantly as new-killed +fish and with the same subtle play of colour, and these wings were not +built on the plan of a bird-wing or bat, Mr. Wace learned, but supported +by curved ribs radiating from the body. (A sort of butterfly wing with +curved ribs seems best to express their appearance.) The body was small, +but fitted with two bunches of prehensile organs, like long tentacles, +immediately under the mouth. Incredible as it appeared to Mr. Wace, the +persuasion at last became irresistible, that it was these creatures +which owned the great quasi-human buildings and the magnificent garden +that made the broad valley so splendid. And Mr. Cave perceived that the +buildings, with other peculiarities, had no doors, but that the great +circular windows, which +opened freely, gave the creatures egress and entrance. They would alight +upon their tentacles, fold their wings to a smallness almost rod-like, +and hop into the interior. But among them was a multitude of +smaller-winged creatures, like great dragon-flies and moths and flying +beetles, and across the greensward brilliantly-coloured gigantic +ground-beetles crawled lazily to and fro. Moreover, on the causeways and +terraces, large-headed creatures similar to the greater winged flies, +but wingless, were visible, hopping busily upon their hand-like tangle +of tentacles. + + + + Allusion has already been made to the glittering objects upon masts +that stood upon the terrace of the nearer building. It dawned upon Mr. +Cave, after regarding one of these masts very fixedly on one +particularly vivid day, that the glittering object there was a crystal +exactly like that into which he peered. And a still more careful +scrutiny convinced him that each one in a vista of nearly twenty carried +a similar object. + + + + Occasionally one of the large flying creatures would flutter up to +one, and, folding its wings and coiling a number of its tentacles about +the mast, would regard the crystal fixedly for a space, -- sometimes for +as long as fifteen minutes. And a series of observations, made at the +suggestion of Mr. Wace, convinced both watchers that, so far as this +visionary world was concerned, the crystal into which they peered +actually stood at the summit of the end-most mast on the terrace, and +that on one occasion at least one of these inhabitants of this other +world had looked into Mr. Cave's face while he was making these +observations. + + + + So much for the essential facts of this very singular story. Unless +we dismiss it all as the ingenious fabrication of Mr. Wace, we have to +believe one of two things: either that Mr. Cave's crystal was in two +worlds at once, and that, while it was carried about in one, it remained +stationary in the other, which seems altogether absurd; or else that it +had some peculiar relation of sympathy with another and exactly similar +crystal in this other world, so that what was seen in the interior of +the one in this world, was, under suitable conditions, visible to an +observer in the corresponding crystal in the other world; and vice +versa. At present, indeed, we do not know of any way in which two +crystals could so come en rapport, but nowadays we know enough to +understand that the thing is not altogether impossible. This view of the +crystals as en rapport was the supposition that occurred to Mr. Wace, +and to me at least it seems extremely plausible. . . . + + + + And where was this other world? On this, also, the alert intelligence +of Mr. Wace speedily threw light. After sunset, the sky darkened +rapidly -- there was a very brief twilight interval indeed -- and the +stars shone out. They were recognisably the same as those we see, +arranged in the same constellations. Mr. Cave recognised the Bear, the +Pleiades, Aldebaran, and Sirius: so that the other world must be +somewhere in the solar system, and, at the utmost, only a few hundreds +of millions of miles from our own. Following up this clue, Mr. Wace +learned that the midnight sky was a darker blue even than our midwinter +sky, and that the sun seemed a little smaller. And there were two small +moons! "like our moon but smaller, and quite differently marked" one of +which moved so rapidly that its motion was clearly visible as one +regarded it. These moons were never high in the sky, but vanished as +they rose: that is, every time they revolved they were eclipsed because +they were so near their primary planet. And all this answers quite +completely, although. Mr. Cave did not know it, to what must be the +condition of things on Mars. + + + + Indeed, it seems an exceedingly plausible conclusion that peering +into this crystal Mr. Cave did actually see the planet Mars and its +inhabitants. And, if that be the case, then the evening star that shone +so brilliantly in the sky of that distant vision, was neither more nor +less than our own familiar earth. + + + + For a time the Martians -- if they were Martians -- do not seem to +have known of Mr. Cave's inspection. Once or twice one would come to +peer, and go away very shortly to some other mast, as though the vision +was unsatisfactory. During this time Mr. Cave was able to watch the +proceedings of these winged people without being disturbed by their +attentions, and, although his report is necessarily vague and +fragmentary, it is nevertheless very suggestive. Imagine the impression +of humanity a Martian observer would get who, after a difficult process +of preparation and with considerable fatigue to the eyes, was able to +peer at London from the steeple of St. Martin's Church for stretches, at +longest, of four minutes at a time. Mr. Cave was unable to ascertain if +the winged Martians were the same as the Martians who hopped about the +causeways and terraces, and if the latter could put on wings at will. He +several times saw certain clumsy bipeds, dimly suggestive of apes, white +and partially translucent, feeding among certain of the lichenous trees, +and once some of these fled before one of the hopping, round-headed +Martians. The latter caught one in its tentacles, and then the picture +faded suddenly and left Mr. Cave most tantalisingly in the dark. On +another occasion a vast thing, that Mr. Cave thought at first was some +gigantic insect, appeared advancing along the causeway beside the canal +with extraordinary rapidity. As this drew nearer Mr. Cave perceived that +it was a mechanism of shining +metals and of extraordinary complexity. And then, when he looked again, +it had passed out of sight. + + + + After a time Mr. Wace aspired to attract the attention of the +Martians, and the next time that the strange eyes of one of them +appeared close to the crystal Mr. Cave cried out and sprang away, and +they immediately turned on the light and began to gesticulate in a +manner suggestive of signalling. But when at last Mr. Cave examined the +crystal again the Martian had departed. + + + + Thus far these observations had progressed in early November, and +then Mr. Cave, feeling that the suspicions of his family about the +crystal were allayed, began to take it to and fro with him in order +that, as occasion arose in the daytime or night, he might comfort +himself with what was fast becoming the most real thing in his existence. + + + + In December Mr. Wace's work in connection with a forthcoming +examination became heavy, the sittings were reluctantly suspended for a +week, and for ten or eleven days -- he is not quite sure which -- he saw +nothing of Cave. He then grew anxious to resume these investigations, +and, the stress of his seasonal labours being abated, he went down to +Seven Dials. At the corner he noticed a shutter before a bird fancier's +window, and then another at a cobbler's. Mr. Cave's shop was closed. + + + + He rapped and the door was opened by the step-son in black. He at +once called Mrs. Cave, who was, Mr. Wace could not but observe, in cheap +but ample widow's weeds of the most imposing pattern. Without any very +great surprise Mr. Wace learnt that Cave was dead and already buried. +She was in tears, and her voice was a little thick. She had just +returned from Highgate. Her mind seemed occupied with her own prospects +and the honourable details of the obsequies, but Mr. Wace was at last +able to learn the particulars of Cave's death. He had been found dead in +his shop in the early morning, the day after his last visit to Mr. Wace, +and the crystal had been clasped in his stone-cold hands. His face was +smiling, said Mrs. Cave, and the velvet cloth from the minerals lay on +the floor at his feet. He must have been dead five or six hours when he +was found. + + + + This came as a great shock to Wace, and he began to reproach ho,self +bitterly for having neglected the plain symptoms of the old man's +ill-health. But his chief thought was of the crystal. He approached that +topic in a gingerly manner, because he knew Mrs. Cave's peculiarities. +He was dumbfoundered to learn that it was sold. + + + + Mrs. Cave's first impulse, directly Cave's body had been taken +upstairs, had been to write to the mad clergyman who had offered five +pounds for the crystal, informing him of its recovery; but after a +violent hunt in which her daughter joined her, they were convinced + + + +of the loss of his address. As they were without the means required to +mourn and bury Cave in the elaborate style the dignity of an old Seven +Dials inhabitant demands, they had appealed to a friendly +fellow-tradesman in Great Portland Street. He had very kindly taken over +a portion of the stock at a valuation. The valuation was his own and the +crystal egg was included in one of the lots. Mr. Wace, after a few +suitable consolatory observations, a little offhandedly proffered +perhaps, hurried at once to Great Portland Street. But there he learned +that the crystal egg had already been sold to a tall, dark man in grey. +And there the material facts in this curious, and to me at least very +suggestive, story come abruptly to an end. The Great Portland Street +dealer did not know who the tall dark man in grey was, nor had he +observed him with sufficient attention to describe him minutely. He did +not even know which way this person had gone after leaving the shop. For +a time Mr. Wace remained in the shop, trying the dealer's patience with +hopeless questions, venting his own exasperation. And at last, realising +abruptly that the whole thing had passed out of his hands, had vanished +like a vision of the night, he returned to his own rooms, a little +astonished to find the notes he had made still tangible and visible upon +his untidy table. + + + + His annoyance and disappointment were naturally very great. He made a +second call (equally ineffectual) upon the Great Portland Street dealer, +and he resorted to advertisements in such periodicals as were likely to +come into the hands of a bric-a-brac collector. He also wrote letters to +The Daily Chronicle and Nature, but both those periodicals, suspecting a +hoax, asked him to reconsider his action before they printed, and he was +advised that such a strange story, unfortunately so bare of supporting +evidence, might imperil his reputation as an investigator. Moreover, the +calls of his proper work were urgent. So that after a month or so, save +for an occasional reminder to certain dealers, he had reluctantly to +abandon the quest for the crystal egg, and from that day to this it +remains undiscovered. Occasionally, however, he tells me, and I can +quite believe him, he has bursts of zeal, in which he abandons his more +urgent occupation and resumes the search. + + + + Whether or not it will remain lost for ever, with the material and +origin of it, are things equally speculative at the present time. If the +present purchaser is a collector, one would have expected the enquiries +of Mr. Wace to have readied him through the dealers. He has been able to +discover Mr. Cave's clergyman and "Oriental" -- no other than the Rev. +James Parker and the young Prince of Bosso-Kuni in Java. I am obliged to +them for certain particulars. The object of the Prince was simply +curiosity -- and extravagance. He was so eager to buy, +because Cave was so oddly reluctant to sell. It is just as possible that +the buyer in the second instance was simply a casual purchaser and not a +collector at all, and the crystal egg, for all I know, may at the +present moment be within a mile of me, decorating a drawing-room or +serving as a paper-weight -- its remarkable functions all unknown. +Indeed, it is partly with the idea of such a possibility that I have +thrown this narrative into a form that will give it a chance of being +read by the ordinary consumer of fiction. + + + + My own ideas in the matter are practically identical with those of +Mr. Wace. I believe the crystal on the mast in Mars and the crystal egg +of Mr. Cave's to be in some physical, but at present quite inexplicable, +way en rapport, and we both believe further that the terrestrial crystal +must have been -- possibly at some remote date -- sent hither from that +planet, in order to give the Martians a near view of our affairs. +Possibly the fellows to the crystals in the other masts are also on our +globe. No theory of hallucination suffices for the facts. + + + + + + diff --git a/src/io/doc2html.xsl b/src/io/doc2html.xsl new file mode 100644 index 000000000..f8734a1c2 --- /dev/null +++ b/src/io/doc2html.xsl @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + +

+ +

+ + + +

+ + + +

+
+ + + +
+ +
+ + +

+ +

+
+ + diff --git a/src/io/ftos.cpp b/src/io/ftos.cpp new file mode 100644 index 000000000..47f0dc232 --- /dev/null +++ b/src/io/ftos.cpp @@ -0,0 +1,482 @@ +/* ////////////////////////////////////////////////////////////////////// +// ftos.cc +// +// Copyright (c) 1996-2003 Bryce W. Harrington [bryce at osdl dot org] +// +//----------------------------------------------------------------------- +// License: This code may be used by anyone for any purpose +// so long as the copyright notices and this license +// statement remains attached. +//----------------------------------------------------------------------- +// +// string ftos(double val[, char mode[, int sigfig[, int precision[, int options]]]]) +// +// DESCRIPTION +// This routine is intended to replace the typical use of sprintf for +// converting floating point numbers into strings. +// +// To one-up sprintf, an additional mode was created - 'h' mode - +// which produces numbers in 'engineering notation' - exponents are +// always shown in multiples of 3. To non-engineers this mode is +// probably irrelevant, but for engineers (and scientists) it is SOP. +// +// One other new feature is an option to use 'x10^' instead of the +// conventional 'E' for exponental notation. This is entirely for +// aesthetics since numbers in the 'x10^' form cannot be used as +// inputs for most programs. +// +// For most cases, the routine can simply be used with the defaults +// and acceptable results will be produced. No fill zeros or trailing +// zeros are shown, and exponential notation is only used for numbers +// greater than 1e6 or less than 1e-3. +// +// The one area where sprintf may surpass this routine is in width control. +// No provisions are made in this routine to restrict a number to a +// certain number of digits (thus allowing the number to be constrained +// to an 8 space column, for instance.) Along with this, it does not +// support pre-padding a number with zeros (e.g., '5' -> '0005') and will +// not post-pad a number with spaces (i.e., allow left-justification.) +// +// If width control is this important, then the user will probably want to +// use the stdio routines, which really is well suited for outputting +// columns of data with a brief amount of code. +// +// PARAMETERS +// val - number to be converted +// mode - can be one of four possible values. Default is 'g' +// +// e: Produces numbers in scientific notation. One digit +// is shown on the left side of the decimal, the rest +// on the right, and the exponential is always shown. +// EXAMPLE: 1.04e-4 +// +// f: Produces numbers with fixed format. Number is shown +// exact, with no exponent. +// EXAMPLE: 0.000104 +// +// g: If val is greater than 1e6 or less than 1e-3 it will +// be shown in 'e' format, otherwise 'f' format will be +// used. +// +// h: Produces numbers in engineering format. Result is +// identical to 'f' format for numbers between 1 and +// 1e3, otherwise, the number is shown such that it +// always begins with a nonzero digit on the left side +// (unless number equals zero), and the exponential is +// a multiple of 3. +// EXAMPLE: 104e-6 +// +// If the mode is expressed as a capital letter (e.g., 'F') +// then the exponential part of the number will also be +// capitalized (e.g., '1E6' or '1X10^6'.) +// +// sigfig - the number of significant figures. These are the digits +// that are "retained". For example, the following numbers +// all have four sigfigs: +// 1234 12.34 0.0001234 1.234e-10 +// the last digit shown will be rounded in the standard +// manner (down if the next digit is less than 5, up otherwise.) +// +// precision - the number of digits to show to the right of the decimal. +// For example, all of the following numbers have precisions +// of 2: +// 1234.00 12.34 0.00 1.23e-10 123.40e-12 +// +// options - several options are allowed to control the look of the +// output. +// +// FORCE_DECIMAL - require the decimal point to be shown for +// numbers that do not have any fractional digits (or that +// have a precision set to zero) +// EXAMPLE: 1.e6 +// FORCE_EXP_ZERO - pad the 10's zero in exponent if necessary +// EXAMPLE: 1e06 +// FORCE_HUNDRED_EXP_ZERO - pad the 100's zero in exponent if +// necessary. Also pads 10's zero in exponent if necessary. +// EXAMPLE: 1e006 +// FORCE_EXP_PLUS - show the '+' in the exponent if exponent +// is used. +// EXAMPLE: 1e+6 +// FORCE_EXP - force the output to display the exponent +// EXAMPLE: 0e0 +// FORCE_X10 - use x10^ instead of E +// EXAMPLE: 1x10^6 +// FORCE_PLUS - force output of the '+' for positive numbers +// EXAMPLE: +1e6 +// +// Options can be combined using the usual OR method. For +// example, +// +// ftos(123.456, 'f', -1, -1, FORCE_PLUS | FORCE_X10 | FORCE_EXP) +// +// gives "+123.456x10^0" +// +// RETURN VALUE +// The string representation of the number is returned from the routine. +// The ANSI C++ Standard "string" class was used for several important +// reasons. First, because the string class manages it's own space, the +// ftos routine does not need to concern itself with writing to unallocated +// areas of memory or with handling memory reallocation internally. Second, +// it allows return of an object, not a pointer to an object; this may not +// be as efficient, but it is cleaner and safer than the alternative. Third, +// the routine's return value can be directly assigned to a variable, i.e. +// string var = ftos(3.1415); +// which makes code much easier to comprehend and modify. +// +// Internally, the ftos routine uses fairly typical string operators (=, +=, +// +, etc.) which pretty much any other flavor of string class will define as +// well. Thus if one does not have access to the ANSI C++ Standard string +// class, the user can substitute another with little difficulty. (If the +// alternate class is not named "string" then redefine "string" to whatever +// you wish to use. For example, +// #define string CString +// +// November 1996 - Bryce Harrington +// Created ftoa and ftos +// +// December 1996 - Bryce Harrington +// Added engineering notation mode, added sigfig capability, added +// significant debug code, added options, thoroughly debugged and +// tested the code. +// +// +// June 1999 - Bryce Harrington +// Modified to run on Linux for WorldForge +// +// March 2003 - Bryce Harrington +// Removed DTAG() macros - use of fprintf(stderr,...) instead +// Broke out round/itos/ftos into separate files +// Removed curses bits +// +/////////////////////////////////////////////////////////////////////// */ + + +// This is the routine used for converting a floating point into a string +// This may be included in stdlib.h on some systems and may conflict. +// Let me know your system & etc. so I can properly #ifdef this, but +// try commenting the following four lines out if you run into conflicts. +// extern "C" { +// char* +// ecvt (double val, size_t ndigit, int *decpt, int *sign); +// } + +using namespace std; + +#ifndef HAS_ECVT +#include +#endif + + +#include "ftos.h" + + +// This routine counts from the end of a string like '10229000' to find the index +// of the first non-'0' character (5 would be returned for the above number.) +int countDigs(char *p) +{ + int length =0; + while (*(p+length)!='\0') length++; // Count total length + while (length>0 && *(p+length-1)=='0') length--; // Scan backwards for a non-'0' + return length; +} + +// This routine determines how many digits make up the left hand +// side of the number if the abs value of the number is greater than 1, or the +// digits that make up the right hand side if the abs value of the number +// is between 0 and 1. Returns 1 if v==0. Return value is positive for numbers +// greater than or equal to 1, negative for numbers less than 0.1, and zero for +// numbers between 0.1 and 1. +int countLhsDigits(double v) +{ + if (v<0) v = -v; // Take abs value + else if (v==0) return 1; // Special case if v==0 + + int n=0; + for (; v<0.1; v*=10) // Count digits on right hand side (l.t. 0.1) + { n--; } + for (; v>=1; v/=10) // Count digits on left hand side (g.e. 1.0) + { n++; } + return n; +} + +// This is the routine that does the work of converting the number into a string. +string ftos(double val, char mode, int sigfig, int precision, int options) +{ + // Parse the options to a more usable form + // These options allow the user to control some of the ornaments on the + // number that is output. By default they are all false. Turning them + // on helps to "fix" the format of the number so it lines up in columns + // better. + // - require the decimal point to be shown for numbers that do not have + // any fractional digits (or that have a precision set to zero + bool forceDecimal = (options & FORCE_DECIMAL); + // - show the 10's and 100's zero in exponent + bool forceExpZero = (options & FORCE_EXP_ZERO); + bool forceHundredExpZero = (options & FORCE_HUNDRED_EXP_ZERO); + // - show the '+' in the exponent if exponent is used + bool forceExpPlus = (options & FORCE_EXP_PLUS); + // - force the output to display the exponent + bool forceExponent = (options & FORCE_EXP); + // - use x10^ instead of E + bool forcex10 = (options & FORCE_X10); + // - force output of the '+' for positive numbers + bool forcePlus = (options & FORCE_PLUS); + +#ifdef DEBUG + fprintf(stderr, "Options: "); + fprintf(stderr, " %4s = %s ", "x10", (forcex10 ? "on" : "off" )); + fprintf(stderr, " %4s = %s ", ".", (forceDecimal ? "on" : "off" )); + fprintf(stderr, " %4s = %s ", "e0", (forceExpZero ? "on" : "off" )); + fprintf(stderr, " %4s = %s ", "e00", (forceHundredExpZero ? "on" : "off" )); + fprintf(stderr, " %4s = %s ", "e+", (forceExpPlus ? "on" : "off" )); + fprintf(stderr, " %4s = %s ", "e", (forceExponent ? "on" : "off" )); + fprintf(stderr, " %4s = %s \n", "+#", (forcePlus ? "on" : "off" )); +#endif + + // - exponent usage + bool useExponent = false; + + // Determine the case for the 'e' (if used) + char E = (forcex10)? 'x' : 'e'; + if (g_ascii_isupper(mode)) { + E = g_ascii_toupper(E); + mode = g_ascii_tolower(mode); + } + + // Determine how many decimals we're interested in + int L = countLhsDigits(val); + +#ifdef DEBUG + fprintf(stderr, "*** L is %s\n", itos(L).c_str()); +#endif + + int count = 0; + if (sigfig==0) // bad input - don't want any sigfigs??!! + return ""; + else if (precision>=0) { // Use fixed number of decimal places + count = precision; + if (mode == 'e') count += 1; + else if (mode == 'f') count += L; + else if (mode == 'g') count += (L>6 || L<-3)? 1 : L; + else if (mode == 'h') count += (L>0)? ((L-1)%3+1) : (L%3+3); + if (sigfig>0) count = (sigfig > count)? count : sigfig; // Use sigfig # if it means more decimal places + } + else if (sigfig>0) // Just use sigfigs + count = sigfig; + else // prec < 0 and sigfig < 0 + count = 10; +#ifdef DEBUG + fprintf(stderr, "*** count is %s\n", itos(count).c_str()); +#endif + + // Get number's string rep, sign, and exponent + int sign = 0; + int decimal=0; + +#ifdef HAS_ECVT + char *p = ecvt(val, count, &decimal, &sign); +#else + char *p = (char *) g_strdup_printf("%.0f", val); + // asprintf(&p, "%.0f", val); +#endif + +#ifdef DEBUG + fprintf(stderr, "*** string rep is %s\n", p); + fprintf(stderr, "*** decimal is %s\n", itos(decimal).c_str()); + fprintf(stderr, "*** sign is %s\n", itos(sign).c_str()); +#endif + + // Count the number of relevant digits in the resultant number + int dig = countDigs(p); + if (dig < sigfig) dig = sigfig; + +#ifdef DEBUG + fprintf(stderr, "*** digs is %s\n", itos(dig).c_str()); +#endif + + // Determine number of digits to put on left side of the decimal point + int lhs=0; + // For 'g' mode, decide whether to use 'e' or 'f' format. + if (mode=='g') mode = (decimal>6 || decimal<-3)? 'e' : 'f'; + switch (mode) { + case 'e': + lhs = 1; // only need one char on left side + useExponent = true; // force exponent use + break; + + case 'f': + lhs = (decimal<1)? 1 : decimal; + // use one char on left for num < 1, + // otherwise, use the number of decimal places. + useExponent = false; // don't want exponent for 'f' format + break; + + case 'h': + if (val==0.0) // special case for if value is zero exactly. + lhs = 0; // this prevents code from returning '000.0' + else + lhs = (decimal<=0)? (decimal)%3 + 3 : (decimal-1)%3+1; + useExponent = !(lhs==decimal); // only use exponent if we really need it + break; + + default: + return "**bad mode**"; + } + +#ifdef DEBUG + fprintf(stderr, "*** lhs is %s\n", itos(lhs).c_str()); +#endif + + // Figure out the number of digits to show in the right hand side + int rhs=0; + if (precision>=0) + rhs = precision; + else if (val == 0.0) + rhs = 0; + else if (useExponent || decimal>0) + rhs = dig-lhs; + else + rhs = dig-decimal; + + // can't use a negative rhs value, so turn it to zero if that is the case + if (rhs<0) rhs = 0; + +#ifdef DEBUG + fprintf(stderr, "*** rhs is", itos(rhs).c_str()); +#endif + + // Determine the exponent + int exponent = decimal - lhs; + if (val==0.0) exponent=0; // prevent zero from getting an exponent +#ifdef DEBUG + fprintf(stderr, "*** exponent is %s\n", itos(exponent).c_str()); +#endif + + string ascii; + + // output the sign + if (sign) ascii += "-"; + else if (forcePlus) ascii += "+"; + + // output the left hand side + if (!useExponent && decimal<=0) // if fraction, put the 0 out front + ascii += '0'; + else // is either exponential or >= 1, so write the lhs + for (; lhs>0; lhs--) + ascii += (*p)? *p++ : int('0'); // now fill in the numbers before decimal + +#ifdef DEBUG + fprintf(stderr, "*** ascii + sign + lhs is %s\n", ascii.c_str()); +#endif + + // output the decimal point + if (forceDecimal || rhs>0) + ascii += '.'; + + // output the right hand side + if (!useExponent && rhs>0) // first fill in zeros after dp and before numbers + while (decimal++ <0 && rhs-->0) + ascii += '0'; + for (; rhs>0 ; rhs--) // now fill in the numbers after decimal + ascii += (*p)? *p++ : int('0'); + +#ifdef DEBUG + fprintf(stderr, "*** ascii + . + rhs is %s\n", ascii.c_str()); +#endif + + if (forceExponent || useExponent) // output the entire exponent if required + { + ascii += E; // output the E or X + if (forcex10) ascii += "10^"; // if using 'x10^' format, output the '10^' part + + // output the exponent's sign + if (exponent < 0) { // Negative exponent + exponent = -exponent; // make exponent positive if needed + ascii += '-'; // output negative sign + } + else if (forceExpPlus) // We only want the '+' if it is asked for explicitly + ascii += '+'; + + // output the exponent + if (forceHundredExpZero || exponent >= 100) + ascii += ( (exponent/100) % 10 + '0' ); + if (forceHundredExpZero || forceExpZero || exponent >= 10) + ascii += ( (exponent/10) % 10 + '0' ); + ascii += ( exponent % 10 + '0' ); + +#ifdef DEBUG + fprintf(stderr, "*** ascii + exp is %s\n", ascii.c_str()); +#endif + } + +#ifdef DEBUG + fprintf(stderr, "*** End of ftos with ascii = ", ascii.c_str()); +#endif + /* finally, we can return */ + return ascii; +} + +#ifdef TESTFTOS + +int main() +{ + cout << "Normal (g): " << endl; + cout << "1.0 = " << ftos(1.0) << endl; + cout << "42 = " << ftos(42) << endl; + cout << "3.141 = " << ftos(3.141) << endl; + cout << "0.01 = " << ftos(0.01) << endl; + cout << "1.0e7 = " << ftos(1.0e7) << endl; + cout << endl; + + cout << "Scientific (e): " << endl; + cout << "1.0 = " << ftos(1.0, 'e') << endl; + cout << "42 = " << ftos(42, 'e') << endl; + cout << "3.141 = " << ftos(3.141, 'e') << endl; + cout << "0.01 = " << ftos(0.01, 'e') << endl; + cout << "1.0e7 = " << ftos(1.0e7, 'e') << endl; + cout << endl; + + cout << "Fixed (f): " << endl; + cout << "1.0 = " << ftos(1.0, 'f') << endl; + cout << "42 = " << ftos(42, 'f') << endl; + cout << "3.141 = " << ftos(3.141, 'f') << endl; + cout << "0.01 = " << ftos(0.01, 'f') << endl; + cout << "1.0e7 = " << ftos(1.0e7, 'f') << endl; + cout << endl; + + cout << "Engineering (h): " << endl; + cout << "1.0 = " << ftos(1.0, 'h') << endl; + cout << "42 = " << ftos(42, 'h') << endl; + cout << "3.141 = " << ftos(3.141, 'h') << endl; + cout << "0.01 = " << ftos(0.01, 'h') << endl; + cout << "1.0e7 = " << ftos(1.0e7, 'h') << endl; + cout << endl; + + cout << "Sigfigs: " << endl; + cout << "2 sf = " << ftos(1234, 'g', 2) << " " + << ftos(12.34, 'g', 2) << " " + << ftos(0, 'g', 2) << " " + << ftos(123.4e-11, 'g', 2) << endl; + cout << "4 sf = " << ftos(1234, 'g', 4) << " " + << ftos(12.34, 'g', 4) << " " + << ftos(0, 'g', 4) << " " + << ftos(123.4e-11, 'g', 4) << endl; + cout << "8 sf = " << ftos(1234, 'g', 8) << " " + << ftos(12.34, 'g', 8) << " " + << ftos(0, 'g', 8) << " " + << ftos(123.4e-11, 'g', 8) << endl; + cout << endl; + + cout << "x10 mode: " << endl; + cout << "1234 = " << ftos(1234, 'e', 4, -1, FORCE_X10 | FORCE_EXP) << endl; + cout << "1.01e10 = " << ftos(1.01e10, 'h', -1, -1, FORCE_X10 | FORCE_EXP) << endl; + cout << endl; + + cout << "itos tests..." << endl; + cout << "42 = " << itos(42) << endl; + cout << endl; + + return 0; +} + +#endif // TESTFTOS diff --git a/src/io/ftos.h b/src/io/ftos.h new file mode 100644 index 000000000..888def639 --- /dev/null +++ b/src/io/ftos.h @@ -0,0 +1,54 @@ +///////////////////////////////////////////////////////////////////////// +// ftos.h +// +// Copyright (c) 1996 Bryce W. Harrington - bryce@neptune.net +// +//----------------------------------------------------------------------- +// License: This code may be used by anyone for any purpose +// so long as the copyright notices and this license +// statement remains attached. +//----------------------------------------------------------------------- +// Description of file contents +// 1993 - Bryce Harrington +// Created initial ftoa routine +// +// October 1996 - Bryce Harrington +// Created itos from code taken from Kernighan & Ritchie +// _The C Programming Language_ 2nd edition +// +// November 1996 - Bryce Harrington +// Created new ftoa and ftos +// +// July 1999 - Bryce Harrington +// Ported to Linux for use in WorldForge +// +// January 2000 - Karsten Laux klaux@rhrk.uni-kl.de +// added ultos - convering ulong to string +// +///////////////////////////////////////////////////////////////////////// +#ifndef ftos_h +#define ftos_h + +#include + +// ftos routine +const int FORCE_X10 = (1 << 0); +const int FORCE_DECIMAL = (1 << 1); +const int FORCE_EXP_ZERO = (1 << 2); +const int FORCE_HUNDRED_EXP_ZERO = (1 << 3); +const int FORCE_EXP_PLUS = (1 << 4); +const int FORCE_EXP = (1 << 5); +const int FORCE_PLUS = (1 << 6); + +/// +std::string ftos(double val, char mode='g', int sigfig=-1, int precision=-1, int options=0); +/// +std::string itos(int n); +/// +std::string ultos(unsigned long n); +/// +double rround(double x); // use rounding rule -> x to nearest int. +/// +double rround(double x, int k); // round to the kth place + +#endif // ftos_h diff --git a/src/io/gzipstream.cpp b/src/io/gzipstream.cpp new file mode 100644 index 000000000..02309ecdd --- /dev/null +++ b/src/io/gzipstream.cpp @@ -0,0 +1,458 @@ +/** + * Zlib-enabled input and output streams + * + * This is a thin wrapper of libz calls, in order + * to provide a simple interface to our developers + * for gzip input and output. + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2004 Inkscape.org + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "gzipstream.h" + + +namespace Inkscape +{ +namespace IO +{ + +//######################################################################### +//# G Z I P I N P U T S T R E A M +//######################################################################### + +#define OUT_SIZE 4000 + +/** + * + */ +GzipInputStream::GzipInputStream(InputStream &sourceStream) + : BasicInputStream(sourceStream), + loaded(false), + totalIn(0), + totalOut(0), + outputBuf(NULL), + crc(0), + srcCrc(0), + srcSiz(0), + srcConsumed(0), + srcLen(0), + outputBufPos(0), + outputBufLen(0) +{ + memset( &d_stream, 0, sizeof(d_stream) ); +} + +/** + * + */ +GzipInputStream::~GzipInputStream() +{ + close(); + if ( srcBuf ) { + free(srcBuf); + srcBuf = 0; + } + if ( outputBuf ) { + free(outputBuf); + outputBuf = 0; + } +} + +/** + * Returns the number of bytes that can be read (or skipped over) from + * this input stream without blocking by the next caller of a method for + * this input stream. + */ +int GzipInputStream::available() +{ + if (closed || !outputBuf) + return 0; + return outputBufLen - outputBufPos; +} + + +/** + * Closes this input stream and releases any system resources + * associated with the stream. + */ +void GzipInputStream::close() +{ + if (closed) + return; + + int zerr = inflateEnd(&d_stream); + if (zerr != Z_OK) { + printf("inflateEnd: Some kind of problem: %d\n", zerr); + } + + if ( srcBuf ) { + free(srcBuf); + srcBuf = 0; + } + if ( outputBuf ) { + free(outputBuf); + outputBuf = 0; + } + closed = true; +} + +/** + * Reads the next byte of data from the input stream. -1 if EOF + */ +int GzipInputStream::get() +{ + int ch = -1; + if (closed) { + // leave return value -1 + } + else if (!loaded && !load()) { + closed=true; + } else { + loaded = true; + + int zerr = Z_OK; + if ( outputBufPos >= outputBufLen ) { + // time to read more, if we can + zerr = fetchMore(); + } + + if ( outputBufPos < outputBufLen ) { + ch = (int)outputBuf[outputBufPos++]; + } + } + + return ch; +} + +#define FTEXT 0x01 +#define FHCRC 0x02 +#define FEXTRA 0x04 +#define FNAME 0x08 +#define FCOMMENT 0x10 + +bool GzipInputStream::load() +{ + crc = crc32(0L, Z_NULL, 0); + + std::vector inputBuf; + while (true) + { + int ch = source.get(); + if (ch<0) + break; + inputBuf.push_back((Byte)(ch & 0xff)); + } + long inputBufLen = inputBuf.size(); + + if (inputBufLen < 19) //header + tail + 1 + { + return false; + } + + srcLen = inputBuf.size(); + srcBuf = (Bytef *)malloc(srcLen * sizeof(Byte)); + if (!srcBuf) { + return false; + } + + outputBuf = (unsigned char *)malloc(OUT_SIZE); + if ( !outputBuf ) { + free(srcBuf); + srcBuf = 0; + return false; + } + outputBufLen = 0; // Not filled in yet + + std::vector::iterator iter; + Bytef *p = srcBuf; + for (iter=inputBuf.begin() ; iter != inputBuf.end() ; iter++) + *p++ = *iter; + + int headerLen = 10; + + //Magic + int val = (int)srcBuf[0]; + //printf("val:%x\n", val); + val = (int)srcBuf[1]; + //printf("val:%x\n", val); + + //Method + val = (int)srcBuf[2]; + //printf("val:%x\n", val); + + //flags + int flags = (int)srcBuf[3]; + + //time + val = (int)srcBuf[4]; + val = (int)srcBuf[5]; + val = (int)srcBuf[6]; + val = (int)srcBuf[7]; + + //xflags + val = (int)srcBuf[8]; + //OS + val = (int)srcBuf[9]; + + int cur = 10; +// if ( flags & FEXTRA ) { +// headerLen += 2; +// int xlen = +// TODO deal with optional header parts +// } + if ( flags & FNAME ) { + while ( srcBuf[cur] ) + { + cur++; + headerLen++; + } + headerLen++; + } + + + srcCrc = ((0x0ff & srcBuf[srcLen - 5]) << 24) + | ((0x0ff & srcBuf[srcLen - 6]) << 16) + | ((0x0ff & srcBuf[srcLen - 7]) << 8) + | ((0x0ff & srcBuf[srcLen - 8]) << 0); + //printf("srcCrc:%lx\n", srcCrc); + + srcSiz = ((0x0ff & srcBuf[srcLen - 1]) << 24) + | ((0x0ff & srcBuf[srcLen - 2]) << 16) + | ((0x0ff & srcBuf[srcLen - 3]) << 8) + | ((0x0ff & srcBuf[srcLen - 4]) << 0); + //printf("srcSiz:%lx/%ld\n", srcSiz, srcSiz); + + if ( srcSiz <= 0 ) { + return false; + } + + //outputBufLen = srcSiz + srcSiz/100 + 14; + + unsigned char *data = srcBuf + headerLen; + unsigned long dataLen = srcLen - (headerLen + 8); + //printf("%x %x\n", data[0], data[dataLen-1]); + + d_stream.zalloc = (alloc_func)0; + d_stream.zfree = (free_func)0; + d_stream.opaque = (voidpf)0; + d_stream.next_in = data; + d_stream.avail_in = dataLen; + d_stream.next_out = outputBuf; + d_stream.avail_out = OUT_SIZE; + + int zerr = inflateInit2(&d_stream, -MAX_WBITS); + if ( zerr == Z_OK ) + { + zerr = fetchMore(); + } else { + printf("inflateInit2: Some kind of problem: %d\n", zerr); + } + + + return (zerr == Z_OK) || (zerr == Z_STREAM_END); +} + + +int GzipInputStream::fetchMore() +{ + int zerr = Z_OK; + + // TODO assumes we aren't called till the buffer is empty + d_stream.next_out = outputBuf; + d_stream.avail_out = OUT_SIZE; + outputBufLen = 0; + outputBufPos = 0; + + zerr = inflate( &d_stream, Z_SYNC_FLUSH ); + if ( zerr == Z_OK || zerr == Z_STREAM_END ) { + outputBufLen = OUT_SIZE - d_stream.avail_out; + if ( outputBufLen ) { + crc = crc32(crc, (const Bytef *)outputBuf, outputBufLen); + } + //printf("crc:%lx\n", crc); +// } else if ( zerr != Z_STREAM_END ) { +// // TODO check to be sure this won't happen for partial end reads +// printf("inflate: Some kind of problem: %d\n", zerr); + } + + return zerr; +} + +//######################################################################### +//# G Z I P O U T P U T S T R E A M +//######################################################################### + +/** + * + */ +GzipOutputStream::GzipOutputStream(OutputStream &destinationStream) + : BasicOutputStream(destinationStream) +{ + + totalIn = 0; + totalOut = 0; + crc = crc32(0L, Z_NULL, 0); + + //Gzip header + destination.put(0x1f); + destination.put(0x8b); + + //Say it is compressed + destination.put(Z_DEFLATED); + + //flags + destination.put(0); + + //time + destination.put(0); + destination.put(0); + destination.put(0); + destination.put(0); + + //xflags + destination.put(0); + + //OS code - from zutil.h + //destination.put(OS_CODE); + //apparently, we should not explicitly include zutil.h + destination.put(0); + +} + +/** + * + */ +GzipOutputStream::~GzipOutputStream() +{ + close(); +} + +/** + * Closes this output stream and releases any system resources + * associated with this stream. + */ +void GzipOutputStream::close() +{ + if (closed) + return; + + flush(); + + //# Send the CRC + uLong outlong = crc; + for (int n = 0; n < 4; n++) + { + destination.put((int)(outlong & 0xff)); + outlong >>= 8; + } + //# send the file length + outlong = totalIn & 0xffffffffL; + for (int n = 0; n < 4; n++) + { + destination.put((int)(outlong & 0xff)); + outlong >>= 8; + } + + destination.close(); + closed = true; +} + +/** + * Flushes this output stream and forces any buffered output + * bytes to be written out. + */ +void GzipOutputStream::flush() +{ + if (closed || inputBuf.size()<1) + return; + + uLong srclen = inputBuf.size(); + Bytef *srcbuf = (Bytef *)malloc(srclen * sizeof(Byte)); + if (!srcbuf) + { + return; + } + + uLong destlen = srclen; + Bytef *destbuf = (Bytef *)malloc((destlen + (srclen/100) + 13) * sizeof(Byte)); + if (!destbuf) + { + free(srcbuf); + return; + } + + std::vector::iterator iter; + Bytef *p = srcbuf; + for (iter=inputBuf.begin() ; iter != inputBuf.end() ; iter++) + *p++ = *iter; + + crc = crc32(crc, (const Bytef *)srcbuf, srclen); + + int zerr = compress(destbuf, (uLongf *)&destlen, srcbuf, srclen); + if (zerr != Z_OK) + { + printf("Some kind of problem\n"); + } + + totalOut += destlen; + //skip the redundant zlib header and checksum + for (uLong i=2; i + * + * Copyright (C) 2004 Inkscape.org + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + +#include "inkscapestream.h" +#include + +namespace Inkscape +{ +namespace IO +{ + +//######################################################################### +//# G Z I P I N P U T S T R E A M +//######################################################################### + +/** + * This class is for deflating a gzip-compressed InputStream source + * + */ +class GzipInputStream : public BasicInputStream +{ + +public: + + GzipInputStream(InputStream &sourceStream); + + virtual ~GzipInputStream(); + + virtual int available(); + + virtual void close(); + + virtual int get(); + +private: + + bool load(); + int fetchMore(); + + bool loaded; + + long totalIn; + long totalOut; + + unsigned char *outputBuf; + unsigned char *srcBuf; + + unsigned long crc; + unsigned long srcCrc; + unsigned long srcSiz; + unsigned long srcConsumed; + unsigned long srcLen; + long outputBufPos; + long outputBufLen; + + z_stream d_stream; +}; // class GzipInputStream + + + + +//######################################################################### +//# G Z I P O U T P U T S T R E A M +//######################################################################### + +/** + * This class is for gzip-compressing data going to the + * destination OutputStream + * + */ +class GzipOutputStream : public BasicOutputStream +{ + +public: + + GzipOutputStream(OutputStream &destinationStream); + + virtual ~GzipOutputStream(); + + virtual void close(); + + virtual void flush(); + + virtual void put(int ch); + +private: + + std::vector inputBuf; + + long totalIn; + long totalOut; + unsigned long crc; + +}; // class GzipOutputStream + + + + + + + +} // namespace IO +} // namespace Inkscape + + +#endif /* __INKSCAPE_IO_GZIPSTREAM_H__ */ diff --git a/src/io/inkscapestream.cpp b/src/io/inkscapestream.cpp new file mode 100644 index 000000000..e527101f6 --- /dev/null +++ b/src/io/inkscapestream.cpp @@ -0,0 +1,842 @@ +/** + * Our base input/output stream classes. These are is directly + * inherited from iostreams, and includes any extra + * functionality that we might need. + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2004 Inkscape.org + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + +#include "inkscapestream.h" + +namespace Inkscape +{ +namespace IO +{ + +//######################################################################### +//# U T I L I T Y +//######################################################################### + +void pipeStream(InputStream &source, OutputStream &dest) +{ + for (;;) + { + int ch = source.get(); + if (ch<0) + break; + dest.put(ch); + } + dest.flush(); +} + +//######################################################################### +//# B A S I C I N P U T S T R E A M +//######################################################################### + + +/** + * + */ +BasicInputStream::BasicInputStream(InputStream &sourceStream) + : source(sourceStream) +{ + closed = false; +} + +/** + * Returns the number of bytes that can be read (or skipped over) from + * this input stream without blocking by the next caller of a method for + * this input stream. + */ +int BasicInputStream::available() +{ + if (closed) + return 0; + return source.available(); +} + + +/** + * Closes this input stream and releases any system resources + * associated with the stream. + */ +void BasicInputStream::close() +{ + if (closed) + return; + source.close(); + closed = true; +} + +/** + * Reads the next byte of data from the input stream. -1 if EOF + */ +int BasicInputStream::get() +{ + if (closed) + return -1; + return source.get(); +} + + + +//######################################################################### +//# B A S I C O U T P U T S T R E A M +//######################################################################### + +/** + * + */ +BasicOutputStream::BasicOutputStream(OutputStream &destinationStream) + : destination(destinationStream) +{ + closed = false; +} + +/** + * Closes this output stream and releases any system resources + * associated with this stream. + */ +void BasicOutputStream::close() +{ + if (closed) + return; + destination.close(); + closed = true; +} + +/** + * Flushes this output stream and forces any buffered output + * bytes to be written out. + */ +void BasicOutputStream::flush() +{ + if (closed) + return; + destination.flush(); +} + +/** + * Writes the specified byte to this output stream. + */ +void BasicOutputStream::put(int ch) +{ + if (closed) + return; + destination.put(ch); +} + + + +//######################################################################### +//# B A S I C R E A D E R +//######################################################################### + + +/** + * + */ +BasicReader::BasicReader(Reader &sourceReader) +{ + source = &sourceReader; +} + +/** + * Returns the number of bytes that can be read (or skipped over) from + * this reader without blocking by the next caller of a method for + * this reader. + */ +int BasicReader::available() +{ + if (source) + return source->available(); + else + return 0; +} + + +/** + * Closes this reader and releases any system resources + * associated with the reader. + */ +void BasicReader::close() +{ + if (source) + source->close(); +} + +/** + * Reads the next byte of data from the reader. + */ +gunichar BasicReader::get() +{ + if (source) + return source->get(); + else + return (gunichar)-1; +} + + + + + + +/** + * Reads a line of data from the reader. + */ +Glib::ustring BasicReader::readLine() +{ + Glib::ustring str; + while (available() > 0) + { + gunichar ch = get(); + if (ch == '\n') + break; + str.push_back(ch); + } + return str; +} + +/** + * Reads a line of data from the reader. + */ +Glib::ustring BasicReader::readWord() +{ + Glib::ustring str; + while (available() > 0) + { + gunichar ch = get(); + if (!g_unichar_isprint(ch)) + break; + str.push_back(ch); + } + return str; +} + + +static bool getLong(Glib::ustring &str, long *val) +{ + const char *begin = str.raw().c_str(); + char *end; + long ival = strtol(begin, &end, 10); + if (str == end) + return false; + *val = ival; + return true; +} + +static bool getULong(Glib::ustring &str, unsigned long *val) +{ + const char *begin = str.raw().c_str(); + char *end; + unsigned long ival = strtoul(begin, &end, 10); + if (str == end) + return false; + *val = ival; + return true; +} + +static bool getDouble(Glib::ustring &str, double *val) +{ + const char *begin = str.raw().c_str(); + char *end; + double ival = strtod(begin, &end); + if (str == end) + return false; + *val = ival; + return true; +} + + + + + + + +/** + * + */ +const Reader &BasicReader::readBool (bool& val ) +{ + Glib::ustring buf = readWord(); + if (buf == "true") + val = true; + else + val = false; + return *this; +} + +/** + * + */ +const Reader &BasicReader::readShort (short& val ) +{ + Glib::ustring buf = readWord(); + long ival; + if (getLong(buf, &ival)) + val = (short) ival; + return *this; +} + +/** + * + */ +const Reader &BasicReader::readUnsignedShort (unsigned short& val ) +{ + Glib::ustring buf = readWord(); + unsigned long ival; + if (getULong(buf, &ival)) + val = (unsigned short) ival; + return *this; +} + +/** + * + */ +const Reader &BasicReader::readInt (int& val ) +{ + Glib::ustring buf = readWord(); + long ival; + if (getLong(buf, &ival)) + val = (int) ival; + return *this; +} + +/** + * + */ +const Reader &BasicReader::readUnsignedInt (unsigned int& val ) +{ + Glib::ustring buf = readWord(); + unsigned long ival; + if (getULong(buf, &ival)) + val = (unsigned int) ival; + return *this; +} + +/** + * + */ +const Reader &BasicReader::readLong (long& val ) +{ + Glib::ustring buf = readWord(); + long ival; + if (getLong(buf, &ival)) + val = ival; + return *this; +} + +/** + * + */ +const Reader &BasicReader::readUnsignedLong (unsigned long& val ) +{ + Glib::ustring buf = readWord(); + unsigned long ival; + if (getULong(buf, &ival)) + val = ival; + return *this; +} + +/** + * + */ +const Reader &BasicReader::readFloat (float& val ) +{ + Glib::ustring buf = readWord(); + double ival; + if (getDouble(buf, &ival)) + val = (float)ival; + return *this; +} + +/** + * + */ +const Reader &BasicReader::readDouble (double& val ) +{ + Glib::ustring buf = readWord(); + double ival; + if (getDouble(buf, &ival)) + val = ival; + return *this; +} + + + +//######################################################################### +//# I N P U T S T R E A M R E A D E R +//######################################################################### + + +InputStreamReader::InputStreamReader(InputStream &inputStreamSource) + : inputStream(inputStreamSource) +{ +} + + + +/** + * Close the underlying OutputStream + */ +void InputStreamReader::close() +{ + inputStream.close(); +} + +/** + * Flush the underlying OutputStream + */ +int InputStreamReader::available() +{ + return inputStream.available(); +} + +/** + * Overloaded to receive its bytes from an InputStream + * rather than a Reader + */ +gunichar InputStreamReader::get() +{ + //Do we need conversions here? + gunichar ch = (gunichar)inputStream.get(); + return ch; +} + + + +//######################################################################### +//# S T D R E A D E R +//######################################################################### + + +/** + * + */ +StdReader::StdReader() +{ + inputStream = new StdInputStream(); +} + +/** + * + */ +StdReader::~StdReader() +{ + delete inputStream; +} + + + +/** + * Close the underlying OutputStream + */ +void StdReader::close() +{ + inputStream->close(); +} + +/** + * Flush the underlying OutputStream + */ +int StdReader::available() +{ + return inputStream->available(); +} + +/** + * Overloaded to receive its bytes from an InputStream + * rather than a Reader + */ +gunichar StdReader::get() +{ + //Do we need conversions here? + gunichar ch = (gunichar)inputStream->get(); + return ch; +} + + + + + +//######################################################################### +//# B A S I C W R I T E R +//######################################################################### + +/** + * + */ +BasicWriter::BasicWriter(Writer &destinationWriter) +{ + destination = &destinationWriter; +} + +/** + * Closes this writer and releases any system resources + * associated with this writer. + */ +void BasicWriter::close() +{ + if (destination) + destination->close(); +} + +/** + * Flushes this output stream and forces any buffered output + * bytes to be written out. + */ +void BasicWriter::flush() +{ + if (destination) + destination->flush(); +} + +/** + * Writes the specified byte to this output writer. + */ +void BasicWriter::put(gunichar ch) +{ + if (destination) + destination->put(ch); +} + +/** + * Provide printf()-like formatting + */ +Writer &BasicWriter::printf(char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + gchar *buf = g_strdup_vprintf(fmt, args); + va_end(args); + if (buf) { + writeString(buf); + g_free(buf); + } + return *this; +} +/** + * Writes the specified character to this output writer. + */ +Writer &BasicWriter::writeChar(char ch) +{ + gunichar uch = ch; + put(uch); + return *this; +} + + +/** + * Writes the specified unicode string to this output writer. + */ +Writer &BasicWriter::writeUString(Glib::ustring &str) +{ + for (int i=0; i< (int)str.size(); i++) + put(str[i]); + return *this; +} + +/** + * Writes the specified standard string to this output writer. + */ +Writer &BasicWriter::writeStdString(std::string &str) +{ + Glib::ustring tmp(str); + writeUString(tmp); + return *this; +} + +/** + * Writes the specified character string to this output writer. + */ +Writer &BasicWriter::writeString(const char *str) +{ + Glib::ustring tmp; + if (str) + tmp = str; + else + tmp = "null"; + writeUString(tmp); + return *this; +} + + + + +/** + * + */ +Writer &BasicWriter::writeBool (bool val ) +{ + if (val) + writeString("true"); + else + writeString("false"); + return *this; +} + + +/** + * + */ +Writer &BasicWriter::writeShort (short val ) +{ + gchar *buf = g_strdup_printf("%d", val); + if (buf) { + writeString(buf); + g_free(buf); + } + return *this; +} + + + +/** + * + */ +Writer &BasicWriter::writeUnsignedShort (unsigned short val ) +{ + gchar *buf = g_strdup_printf("%u", val); + if (buf) { + writeString(buf); + g_free(buf); + } + return *this; +} + +/** + * + */ +Writer &BasicWriter::writeInt (int val) +{ + gchar *buf = g_strdup_printf("%d", val); + if (buf) { + writeString(buf); + g_free(buf); + } + return *this; +} + +/** + * + */ +Writer &BasicWriter::writeUnsignedInt (unsigned int val) +{ + gchar *buf = g_strdup_printf("%u", val); + if (buf) { + writeString(buf); + g_free(buf); + } + return *this; +} + +/** + * + */ +Writer &BasicWriter::writeLong (long val) +{ + gchar *buf = g_strdup_printf("%ld", val); + if (buf) { + writeString(buf); + g_free(buf); + } + return *this; +} + +/** + * + */ +Writer &BasicWriter::writeUnsignedLong(unsigned long val) +{ + gchar *buf = g_strdup_printf("%lu", val); + if (buf) { + writeString(buf); + g_free(buf); + } + return *this; +} + +/** + * + */ +Writer &BasicWriter::writeFloat(float val) +{ +#if 1 + gchar *buf = g_strdup_printf("%8.3f", val); + if (buf) { + writeString(buf); + g_free(buf); + } +#else + std::string tmp = ftos(val, 'g', 8, 3, 0); + writeStdString(tmp); +#endif + return *this; +} + +/** + * + */ +Writer &BasicWriter::writeDouble(double val) +{ +#if 1 + gchar *buf = g_strdup_printf("%8.3f", val); + if (buf) { + writeString(buf); + g_free(buf); + } +#else + std::string tmp = ftos(val, 'g', 8, 3, 0); + writeStdString(tmp); +#endif + return *this; +} + + +Writer& operator<< (Writer &writer, char val) + { return writer.writeChar(val); } + +Writer& operator<< (Writer &writer, Glib::ustring &val) + { return writer.writeUString(val); } + +Writer& operator<< (Writer &writer, std::string &val) + { return writer.writeStdString(val); } + +Writer& operator<< (Writer &writer, char *val) + { return writer.writeString(val); } + +Writer& operator<< (Writer &writer, bool val) + { return writer.writeBool(val); } + +Writer& operator<< (Writer &writer, short val) + { return writer.writeShort(val); } + +Writer& operator<< (Writer &writer, unsigned short val) + { return writer.writeUnsignedShort(val); } + +Writer& operator<< (Writer &writer, int val) + { return writer.writeInt(val); } + +Writer& operator<< (Writer &writer, unsigned int val) + { return writer.writeUnsignedInt(val); } + +Writer& operator<< (Writer &writer, long val) + { return writer.writeLong(val); } + +Writer& operator<< (Writer &writer, unsigned long val) + { return writer.writeUnsignedLong(val); } + +Writer& operator<< (Writer &writer, float val) + { return writer.writeFloat(val); } + +Writer& operator<< (Writer &writer, double val) + { return writer.writeDouble(val); } + + + +//######################################################################### +//# O U T P U T S T R E A M W R I T E R +//######################################################################### + + +OutputStreamWriter::OutputStreamWriter(OutputStream &outputStreamDest) + : outputStream(outputStreamDest) +{ +} + + + +/** + * Close the underlying OutputStream + */ +void OutputStreamWriter::close() +{ + flush(); + outputStream.close(); +} + +/** + * Flush the underlying OutputStream + */ +void OutputStreamWriter::flush() +{ + outputStream.flush(); +} + +/** + * Overloaded to redirect the output chars from the next Writer + * in the chain to an OutputStream instead. + */ +void OutputStreamWriter::put(gunichar ch) +{ + //Do we need conversions here? + int intCh = (int) ch; + outputStream.put(intCh); +} + +//######################################################################### +//# S T D W R I T E R +//######################################################################### + + +/** + * + */ +StdWriter::StdWriter() +{ + outputStream = new StdOutputStream(); +} + + +/** + * + */ +StdWriter::~StdWriter() +{ + delete outputStream; +} + + + +/** + * Close the underlying OutputStream + */ +void StdWriter::close() +{ + flush(); + outputStream->close(); +} + +/** + * Flush the underlying OutputStream + */ +void StdWriter::flush() +{ + outputStream->flush(); +} + +/** + * Overloaded to redirect the output chars from the next Writer + * in the chain to an OutputStream instead. + */ +void StdWriter::put(gunichar ch) +{ + //Do we need conversions here? + int intCh = (int) ch; + outputStream->put(intCh); +} + + +} // namespace IO +} // namespace Inkscape + + +//######################################################################### +//# E N D O F F I L E +//######################################################################### diff --git a/src/io/inkscapestream.h b/src/io/inkscapestream.h new file mode 100644 index 000000000..ad213dad9 --- /dev/null +++ b/src/io/inkscapestream.h @@ -0,0 +1,669 @@ +#ifndef __INKSCAPE_IO_INKSCAPESTREAM_H__ +#define __INKSCAPE_IO_INKSCAPESTREAM_H__ +/** + * Our base basic stream classes. + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2004 Inkscape.org + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + +#include + +namespace Inkscape +{ +namespace IO +{ + +class StreamException : public std::exception +{ +public: + + StreamException(const char *theReason) throw() + { reason = theReason; } + StreamException(Glib::ustring &theReason) throw() + { reason = theReason; } + ~StreamException() throw() + { } + char const *what() const throw() + { return reason.c_str(); } + +private: + Glib::ustring reason; + +}; + +//######################################################################### +//# I N P U T S T R E A M +//######################################################################### + +/** + * This interface is the base of all input stream classes. Users who wish + * to make an InputStream that is part of a chain should inherit from + * BasicInputStream. Inherit from this class to make a source endpoint, + * such as a URI or buffer. + * + */ +class InputStream +{ + +public: + + /** + * Constructor. + */ + InputStream() {} + + /** + * Destructor + */ + virtual ~InputStream() {} + + /** + * Return the number of bytes that are currently available + * to be read + */ + virtual int available() = 0; + + /** + * Do whatever it takes to 'close' this input stream + * The most likely implementation of this method will be + * for endpoints that use a resource for their data. + */ + virtual void close() = 0; + + /** + * Read one byte from this input stream. This is a blocking + * call. If no data is currently available, this call will + * not return until it exists. If the user does not want + * their code to block, then the usual solution is: + * if (available() > 0) + * myChar = get(); + * This call returns -1 on end-of-file. + */ + virtual int get() = 0; + +}; // class InputStream + + + + +/** + * This is the class that most users should inherit, to provide + * their own streams. + * + */ +class BasicInputStream : public InputStream +{ + +public: + + BasicInputStream(InputStream &sourceStream); + + virtual ~BasicInputStream() {} + + virtual int available(); + + virtual void close(); + + virtual int get(); + +protected: + + bool closed; + + InputStream &source; + +private: + + +}; // class BasicInputStream + + + +/** + * Convenience class for reading from standard input + */ +class StdInputStream : public InputStream +{ +public: + + int available() + { return 0; } + + void close() + { /* do nothing */ } + + int get() + { return getchar(); } + +}; + + + + + + +//######################################################################### +//# O U T P U T S T R E A M +//######################################################################### + +/** + * This interface is the base of all input stream classes. Users who wish + * to make an OutputStream that is part of a chain should inherit from + * BasicOutputStream. Inherit from this class to make a destination endpoint, + * such as a URI or buffer. + */ +class OutputStream +{ + +public: + + /** + * Constructor. + */ + OutputStream() {} + + /** + * Destructor + */ + virtual ~OutputStream() {} + + /** + * This call should + * 1. flush itself + * 2. close itself + * 3. close the destination stream + */ + virtual void close() = 0; + + /** + * This call should push any pending data it might have to + * the destination stream. It should NOT call flush() on + * the destination stream. + */ + virtual void flush() = 0; + + /** + * Send one byte to the destination stream. + */ + virtual void put(int ch) = 0; + + +}; // class OutputStream + + +/** + * This is the class that most users should inherit, to provide + * their own output streams. + */ +class BasicOutputStream : public OutputStream +{ + +public: + + BasicOutputStream(OutputStream &destinationStream); + + virtual ~BasicOutputStream() {} + + virtual void close(); + + virtual void flush(); + + virtual void put(int ch); + +protected: + + bool closed; + + OutputStream &destination; + + +}; // class BasicOutputStream + + + +/** + * Convenience class for writing to standard output + */ +class StdOutputStream : public OutputStream +{ +public: + + void close() + { } + + void flush() + { } + + void put(int ch) + { putchar(ch); } + +}; + + + + +//######################################################################### +//# R E A D E R +//######################################################################### + + +/** + * This interface and its descendants are for unicode character-oriented input + * + */ +class Reader +{ + +public: + + /** + * Constructor. + */ + Reader() {} + + /** + * Destructor + */ + virtual ~Reader() {} + + + virtual int available() = 0; + + virtual void close() = 0; + + virtual gunichar get() = 0; + + virtual Glib::ustring readLine() = 0; + + virtual Glib::ustring readWord() = 0; + + /* Input formatting */ + virtual const Reader& readBool (bool& val ) = 0; + virtual const Reader& operator>> (bool& val ) = 0; + + virtual const Reader& readShort (short &val) = 0; + virtual const Reader& operator>> (short &val) = 0; + + virtual const Reader& readUnsignedShort (unsigned short &val) = 0; + virtual const Reader& operator>> (unsigned short &val) = 0; + + virtual const Reader& readInt (int &val) = 0; + virtual const Reader& operator>> (int &val) = 0; + + virtual const Reader& readUnsignedInt (unsigned int &val) = 0; + virtual const Reader& operator>> (unsigned int &val) = 0; + + virtual const Reader& readLong (long &val) = 0; + virtual const Reader& operator>> (long &val) = 0; + + virtual const Reader& readUnsignedLong (unsigned long &val) = 0; + virtual const Reader& operator>> (unsigned long &val) = 0; + + virtual const Reader& readFloat (float &val) = 0; + virtual const Reader& operator>> (float &val) = 0; + + virtual const Reader& readDouble (double &val) = 0; + virtual const Reader& operator>> (double &val) = 0; + +}; // interface Reader + + + +/** + * This class and its descendants are for unicode character-oriented input + * + */ +class BasicReader : public Reader +{ + +public: + + BasicReader(Reader &sourceStream); + + virtual ~BasicReader() {} + + virtual int available(); + + virtual void close(); + + virtual gunichar get(); + + virtual Glib::ustring readLine(); + + virtual Glib::ustring readWord(); + + /* Input formatting */ + virtual const Reader& readBool (bool& val ); + virtual const Reader& operator>> (bool& val ) + { return readBool(val); } + + virtual const Reader& readShort (short &val); + virtual const Reader& operator>> (short &val) + { return readShort(val); } + + virtual const Reader& readUnsignedShort (unsigned short &val); + virtual const Reader& operator>> (unsigned short &val) + { return readUnsignedShort(val); } + + virtual const Reader& readInt (int &val); + virtual const Reader& operator>> (int &val) + { return readInt(val); } + + virtual const Reader& readUnsignedInt (unsigned int &val); + virtual const Reader& operator>> (unsigned int &val) + { return readUnsignedInt(val); } + + virtual const Reader& readLong (long &val); + virtual const Reader& operator>> (long &val) + { return readLong(val); } + + virtual const Reader& readUnsignedLong (unsigned long &val); + virtual const Reader& operator>> (unsigned long &val) + { return readUnsignedLong(val); } + + virtual const Reader& readFloat (float &val); + virtual const Reader& operator>> (float &val) + { return readFloat(val); } + + virtual const Reader& readDouble (double &val); + virtual const Reader& operator>> (double &val) + { return readDouble(val); } + + +protected: + + Reader *source; + + BasicReader() + { source = NULL; } + +private: + +}; // class BasicReader + + + +/** + * Class for placing a Reader on an open InputStream + * + */ +class InputStreamReader : public BasicReader +{ +public: + + InputStreamReader(InputStream &inputStreamSource); + + /*Overload these 3 for your implementation*/ + virtual int available(); + + virtual void close(); + + virtual gunichar get(); + + +private: + + InputStream &inputStream; + + +}; + +/** + * Convenience class for reading formatted from standard input + * + */ +class StdReader : public BasicReader +{ +public: + + StdReader(); + + ~StdReader(); + + /*Overload these 3 for your implementation*/ + virtual int available(); + + virtual void close(); + + virtual gunichar get(); + + +private: + + InputStream *inputStream; + + +}; + + + + + +//######################################################################### +//# W R I T E R +//######################################################################### + +/** + * This interface and its descendants are for unicode character-oriented output + * + */ +class Writer +{ + +public: + + /** + * Constructor. + */ + Writer() {} + + /** + * Destructor + */ + virtual ~Writer() {} + + virtual void close() = 0; + + virtual void flush() = 0; + + virtual void put(gunichar ch) = 0; + + /* Formatted output */ + virtual Writer& printf(char *fmt, ...) = 0; + + virtual Writer& writeChar(char val) = 0; + + virtual Writer& writeUString(Glib::ustring &val) = 0; + + virtual Writer& writeStdString(std::string &val) = 0; + + virtual Writer& writeString(const char *str) = 0; + + virtual Writer& writeBool (bool val ) = 0; + + virtual Writer& writeShort (short val ) = 0; + + virtual Writer& writeUnsignedShort (unsigned short val ) = 0; + + virtual Writer& writeInt (int val ) = 0; + + virtual Writer& writeUnsignedInt (unsigned int val ) = 0; + + virtual Writer& writeLong (long val ) = 0; + + virtual Writer& writeUnsignedLong (unsigned long val ) = 0; + + virtual Writer& writeFloat (float val ) = 0; + + virtual Writer& writeDouble (double val ) = 0; + + + +}; // interface Writer + + +/** + * This class and its descendants are for unicode character-oriented output + * + */ +class BasicWriter : public Writer +{ + +public: + + BasicWriter(Writer &destinationWriter); + + virtual ~BasicWriter() {} + + /*Overload these 3 for your implementation*/ + virtual void close(); + + virtual void flush(); + + virtual void put(gunichar ch); + + + + /* Formatted output */ + virtual Writer &printf(char *fmt, ...); + + virtual Writer& writeChar(char val); + + virtual Writer& writeUString(Glib::ustring &val); + + virtual Writer& writeStdString(std::string &val); + + virtual Writer& writeString(const char *str); + + virtual Writer& writeBool (bool val ); + + virtual Writer& writeShort (short val ); + + virtual Writer& writeUnsignedShort (unsigned short val ); + + virtual Writer& writeInt (int val ); + + virtual Writer& writeUnsignedInt (unsigned int val ); + + virtual Writer& writeLong (long val ); + + virtual Writer& writeUnsignedLong (unsigned long val ); + + virtual Writer& writeFloat (float val ); + + virtual Writer& writeDouble (double val ); + + +protected: + + Writer *destination; + + BasicWriter() + { destination = NULL; } + +private: + +}; // class BasicWriter + + + +Writer& operator<< (Writer &writer, char val); + +Writer& operator<< (Writer &writer, Glib::ustring &val); + +Writer& operator<< (Writer &writer, std::string &val); + +Writer& operator<< (Writer &writer, char *val); + +Writer& operator<< (Writer &writer, bool val); + +Writer& operator<< (Writer &writer, short val); + +Writer& operator<< (Writer &writer, unsigned short val); + +Writer& operator<< (Writer &writer, int val); + +Writer& operator<< (Writer &writer, unsigned int val); + +Writer& operator<< (Writer &writer, long val); + +Writer& operator<< (Writer &writer, unsigned long val); + +Writer& operator<< (Writer &writer, float val); + +Writer& operator<< (Writer &writer, double val); + + + + +/** + * Class for placing a Writer on an open OutputStream + * + */ +class OutputStreamWriter : public BasicWriter +{ +public: + + OutputStreamWriter(OutputStream &outputStreamDest); + + /*Overload these 3 for your implementation*/ + virtual void close(); + + virtual void flush(); + + virtual void put(gunichar ch); + + +private: + + OutputStream &outputStream; + + +}; + + +/** + * Convenience class for writing to standard output + */ +class StdWriter : public BasicWriter +{ +public: + StdWriter(); + + ~StdWriter(); + + + virtual void close(); + + + virtual void flush(); + + + virtual void put(gunichar ch); + + +private: + + OutputStream *outputStream; + +}; + +//######################################################################### +//# U T I L I T Y +//######################################################################### + +void pipeStream(InputStream &source, OutputStream &dest); + + + +} // namespace IO +} // namespace Inkscape + + +#endif /* __INKSCAPE_IO_INKSCAPESTREAM_H__ */ diff --git a/src/io/makefile.in b/src/io/makefile.in new file mode 100644 index 000000000..61ee7437f --- /dev/null +++ b/src/io/makefile.in @@ -0,0 +1,17 @@ +# Convenience stub makefile to call the real Makefile. + +@SET_MAKE@ + +# Explicit so that it's the default rule. +all: + cd .. && $(MAKE) io/all + +clean %.a %.o: + cd .. && $(MAKE) io/$@ + +.PHONY: all clean + +OBJEXT = @OBJEXT@ + +.SUFFIXES: +.SUFFIXES: .a .$(OBJEXT) diff --git a/src/io/simple-sax.cpp b/src/io/simple-sax.cpp new file mode 100644 index 000000000..2dd78b451 --- /dev/null +++ b/src/io/simple-sax.cpp @@ -0,0 +1,1493 @@ +/* + * SimpleSAX + * + * Authors: + * Jon A. Cruz + * + * Copyright (C) 2004 AUTHORS + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + +#include "simple-sax.h" + +namespace Inkscape { +namespace IO { + +SaxHandler::SaxHandler() +{ + memset( &sax, 0, sizeof(sax) ); + sax.startDocument = startDocument; + sax.endDocument = endDocument; + sax.startElement = startElement; + sax.endElement = endElement; + sax.characters = characters; +} + +SaxHandler::~SaxHandler() +{ +} + + +static int xmlErrorVals[] = { + XML_ERR_OK, + XML_ERR_INTERNAL_ERROR, + XML_ERR_NO_MEMORY, + XML_ERR_DOCUMENT_START, + XML_ERR_DOCUMENT_EMPTY, + XML_ERR_DOCUMENT_END, + XML_ERR_INVALID_HEX_CHARREF, + XML_ERR_INVALID_DEC_CHARREF, + XML_ERR_INVALID_CHARREF, + XML_ERR_INVALID_CHAR, + XML_ERR_CHARREF_AT_EOF, + XML_ERR_CHARREF_IN_PROLOG, + XML_ERR_CHARREF_IN_EPILOG, + XML_ERR_CHARREF_IN_DTD, + XML_ERR_ENTITYREF_AT_EOF, + XML_ERR_ENTITYREF_IN_PROLOG, + XML_ERR_ENTITYREF_IN_EPILOG, + XML_ERR_ENTITYREF_IN_DTD, + XML_ERR_PEREF_AT_EOF, + XML_ERR_PEREF_IN_PROLOG, + XML_ERR_PEREF_IN_EPILOG, + XML_ERR_PEREF_IN_INT_SUBSET, + XML_ERR_ENTITYREF_NO_NAME, + XML_ERR_ENTITYREF_SEMICOL_MISSING, + XML_ERR_PEREF_NO_NAME, + XML_ERR_PEREF_SEMICOL_MISSING, + XML_ERR_UNDECLARED_ENTITY, + XML_WAR_UNDECLARED_ENTITY, + XML_ERR_UNPARSED_ENTITY, + XML_ERR_ENTITY_IS_EXTERNAL, + XML_ERR_ENTITY_IS_PARAMETER, + XML_ERR_UNKNOWN_ENCODING, + XML_ERR_UNSUPPORTED_ENCODING, + XML_ERR_STRING_NOT_STARTED, + XML_ERR_STRING_NOT_CLOSED, + XML_ERR_NS_DECL_ERROR, + XML_ERR_ENTITY_NOT_STARTED, + XML_ERR_ENTITY_NOT_FINISHED, + XML_ERR_LT_IN_ATTRIBUTE, + XML_ERR_ATTRIBUTE_NOT_STARTED, + XML_ERR_ATTRIBUTE_NOT_FINISHED, + XML_ERR_ATTRIBUTE_WITHOUT_VALUE, + XML_ERR_ATTRIBUTE_REDEFINED, + XML_ERR_LITERAL_NOT_STARTED, + XML_ERR_LITERAL_NOT_FINISHED, + XML_ERR_COMMENT_NOT_FINISHED, + XML_ERR_PI_NOT_STARTED, + XML_ERR_PI_NOT_FINISHED, + XML_ERR_NOTATION_NOT_STARTED, + XML_ERR_NOTATION_NOT_FINISHED, + XML_ERR_ATTLIST_NOT_STARTED, + XML_ERR_ATTLIST_NOT_FINISHED, + XML_ERR_MIXED_NOT_STARTED, + XML_ERR_MIXED_NOT_FINISHED, + XML_ERR_ELEMCONTENT_NOT_STARTED, + XML_ERR_ELEMCONTENT_NOT_FINISHED, + XML_ERR_XMLDECL_NOT_STARTED, + XML_ERR_XMLDECL_NOT_FINISHED, + XML_ERR_CONDSEC_NOT_STARTED, + XML_ERR_CONDSEC_NOT_FINISHED, + XML_ERR_EXT_SUBSET_NOT_FINISHED, + XML_ERR_DOCTYPE_NOT_FINISHED, + XML_ERR_MISPLACED_CDATA_END, + XML_ERR_CDATA_NOT_FINISHED, + XML_ERR_RESERVED_XML_NAME, + XML_ERR_SPACE_REQUIRED, + XML_ERR_SEPARATOR_REQUIRED, + XML_ERR_NMTOKEN_REQUIRED, + XML_ERR_NAME_REQUIRED, + XML_ERR_PCDATA_REQUIRED, + XML_ERR_URI_REQUIRED, + XML_ERR_PUBID_REQUIRED, + XML_ERR_LT_REQUIRED, + XML_ERR_GT_REQUIRED, + XML_ERR_LTSLASH_REQUIRED, + XML_ERR_EQUAL_REQUIRED, + XML_ERR_TAG_NAME_MISMATCH, + XML_ERR_TAG_NOT_FINISHED, + XML_ERR_STANDALONE_VALUE, + XML_ERR_ENCODING_NAME, + XML_ERR_HYPHEN_IN_COMMENT, + XML_ERR_INVALID_ENCODING, + XML_ERR_EXT_ENTITY_STANDALONE, + XML_ERR_CONDSEC_INVALID, + XML_ERR_VALUE_REQUIRED, + XML_ERR_NOT_WELL_BALANCED, + XML_ERR_EXTRA_CONTENT, + XML_ERR_ENTITY_CHAR_ERROR, + XML_ERR_ENTITY_PE_INTERNAL, + XML_ERR_ENTITY_LOOP, + XML_ERR_ENTITY_BOUNDARY, + XML_ERR_INVALID_URI, + XML_ERR_URI_FRAGMENT, + XML_WAR_CATALOG_PI, + XML_ERR_NO_DTD, + XML_ERR_CONDSEC_INVALID_KEYWORD, + XML_ERR_VERSION_MISSING, + XML_WAR_UNKNOWN_VERSION, + XML_WAR_LANG_VALUE, + XML_WAR_NS_URI, + XML_WAR_NS_URI_RELATIVE, + XML_ERR_MISSING_ENCODING, + XML_NS_ERR_XML_NAMESPACE, + XML_NS_ERR_UNDEFINED_NAMESPACE, + XML_NS_ERR_QNAME, + XML_NS_ERR_ATTRIBUTE_REDEFINED, + XML_DTD_ATTRIBUTE_DEFAULT, + XML_DTD_ATTRIBUTE_REDEFINED, + XML_DTD_ATTRIBUTE_VALUE, + XML_DTD_CONTENT_ERROR, + XML_DTD_CONTENT_MODEL, + XML_DTD_CONTENT_NOT_DETERMINIST, + XML_DTD_DIFFERENT_PREFIX, + XML_DTD_ELEM_DEFAULT_NAMESPACE, + XML_DTD_ELEM_NAMESPACE, + XML_DTD_ELEM_REDEFINED, + XML_DTD_EMPTY_NOTATION, + XML_DTD_ENTITY_TYPE, + XML_DTD_ID_FIXED, + XML_DTD_ID_REDEFINED, + XML_DTD_ID_SUBSET, + XML_DTD_INVALID_CHILD, + XML_DTD_INVALID_DEFAULT, + XML_DTD_LOAD_ERROR, + XML_DTD_MISSING_ATTRIBUTE, + XML_DTD_MIXED_CORRUPT, + XML_DTD_MULTIPLE_ID, + XML_DTD_NO_DOC, + XML_DTD_NO_DTD, + XML_DTD_NO_ELEM_NAME, + XML_DTD_NO_PREFIX, + XML_DTD_NO_ROOT, + XML_DTD_NOTATION_REDEFINED, + XML_DTD_NOTATION_VALUE, + XML_DTD_NOT_EMPTY, + XML_DTD_NOT_PCDATA, + XML_DTD_NOT_STANDALONE, + XML_DTD_ROOT_NAME, + XML_DTD_STANDALONE_WHITE_SPACE, + XML_DTD_UNKNOWN_ATTRIBUTE, + XML_DTD_UNKNOWN_ELEM, + XML_DTD_UNKNOWN_ENTITY, + XML_DTD_UNKNOWN_ID, + XML_DTD_UNKNOWN_NOTATION, + XML_DTD_STANDALONE_DEFAULTED, + XML_DTD_XMLID_VALUE, + XML_DTD_XMLID_TYPE, + XML_HTML_STRUCURE_ERROR, + XML_HTML_UNKNOWN_TAG, + XML_RNGP_ANYNAME_ATTR_ANCESTOR, + XML_RNGP_ATTR_CONFLICT, + XML_RNGP_ATTRIBUTE_CHILDREN, + XML_RNGP_ATTRIBUTE_CONTENT, + XML_RNGP_ATTRIBUTE_EMPTY, + XML_RNGP_ATTRIBUTE_NOOP, + XML_RNGP_CHOICE_CONTENT, + XML_RNGP_CHOICE_EMPTY, + XML_RNGP_CREATE_FAILURE, + XML_RNGP_DATA_CONTENT, + XML_RNGP_DEF_CHOICE_AND_INTERLEAVE, + XML_RNGP_DEFINE_CREATE_FAILED, + XML_RNGP_DEFINE_EMPTY, + XML_RNGP_DEFINE_MISSING, + XML_RNGP_DEFINE_NAME_MISSING, + XML_RNGP_ELEM_CONTENT_EMPTY, + XML_RNGP_ELEM_CONTENT_ERROR, + XML_RNGP_ELEMENT_EMPTY, + XML_RNGP_ELEMENT_CONTENT, + XML_RNGP_ELEMENT_NAME, + XML_RNGP_ELEMENT_NO_CONTENT, + XML_RNGP_ELEM_TEXT_CONFLICT, + XML_RNGP_EMPTY, + XML_RNGP_EMPTY_CONSTRUCT, + XML_RNGP_EMPTY_CONTENT, + XML_RNGP_EMPTY_NOT_EMPTY, + XML_RNGP_ERROR_TYPE_LIB, + XML_RNGP_EXCEPT_EMPTY, + XML_RNGP_EXCEPT_MISSING, + XML_RNGP_EXCEPT_MULTIPLE, + XML_RNGP_EXCEPT_NO_CONTENT, + XML_RNGP_EXTERNALREF_EMTPY, + XML_RNGP_EXTERNAL_REF_FAILURE, + XML_RNGP_EXTERNALREF_RECURSE, + XML_RNGP_FORBIDDEN_ATTRIBUTE, + XML_RNGP_FOREIGN_ELEMENT, + XML_RNGP_GRAMMAR_CONTENT, + XML_RNGP_GRAMMAR_EMPTY, + XML_RNGP_GRAMMAR_MISSING, + XML_RNGP_GRAMMAR_NO_START, + XML_RNGP_GROUP_ATTR_CONFLICT, + XML_RNGP_HREF_ERROR, + XML_RNGP_INCLUDE_EMPTY, + XML_RNGP_INCLUDE_FAILURE, + XML_RNGP_INCLUDE_RECURSE, + XML_RNGP_INTERLEAVE_ADD, + XML_RNGP_INTERLEAVE_CREATE_FAILED, + XML_RNGP_INTERLEAVE_EMPTY, + XML_RNGP_INTERLEAVE_NO_CONTENT, + XML_RNGP_INVALID_DEFINE_NAME, + XML_RNGP_INVALID_URI, + XML_RNGP_INVALID_VALUE, + XML_RNGP_MISSING_HREF, + XML_RNGP_NAME_MISSING, + XML_RNGP_NEED_COMBINE, + XML_RNGP_NOTALLOWED_NOT_EMPTY, + XML_RNGP_NSNAME_ATTR_ANCESTOR, + XML_RNGP_NSNAME_NO_NS, + XML_RNGP_PARAM_FORBIDDEN, + XML_RNGP_PARAM_NAME_MISSING, + XML_RNGP_PARENTREF_CREATE_FAILED, + XML_RNGP_PARENTREF_NAME_INVALID, + XML_RNGP_PARENTREF_NO_NAME, + XML_RNGP_PARENTREF_NO_PARENT, + XML_RNGP_PARENTREF_NOT_EMPTY, + XML_RNGP_PARSE_ERROR, + XML_RNGP_PAT_ANYNAME_EXCEPT_ANYNAME, + XML_RNGP_PAT_ATTR_ATTR, + XML_RNGP_PAT_ATTR_ELEM, + XML_RNGP_PAT_DATA_EXCEPT_ATTR, + XML_RNGP_PAT_DATA_EXCEPT_ELEM, + XML_RNGP_PAT_DATA_EXCEPT_EMPTY, + XML_RNGP_PAT_DATA_EXCEPT_GROUP, + XML_RNGP_PAT_DATA_EXCEPT_INTERLEAVE, + XML_RNGP_PAT_DATA_EXCEPT_LIST, + XML_RNGP_PAT_DATA_EXCEPT_ONEMORE, + XML_RNGP_PAT_DATA_EXCEPT_REF, + XML_RNGP_PAT_DATA_EXCEPT_TEXT, + XML_RNGP_PAT_LIST_ATTR, + XML_RNGP_PAT_LIST_ELEM, + XML_RNGP_PAT_LIST_INTERLEAVE, + XML_RNGP_PAT_LIST_LIST, + XML_RNGP_PAT_LIST_REF, + XML_RNGP_PAT_LIST_TEXT, + XML_RNGP_PAT_NSNAME_EXCEPT_ANYNAME, + XML_RNGP_PAT_NSNAME_EXCEPT_NSNAME, + XML_RNGP_PAT_ONEMORE_GROUP_ATTR, + XML_RNGP_PAT_ONEMORE_INTERLEAVE_ATTR, + XML_RNGP_PAT_START_ATTR, + XML_RNGP_PAT_START_DATA, + XML_RNGP_PAT_START_EMPTY, + XML_RNGP_PAT_START_GROUP, + XML_RNGP_PAT_START_INTERLEAVE, + XML_RNGP_PAT_START_LIST, + XML_RNGP_PAT_START_ONEMORE, + XML_RNGP_PAT_START_TEXT, + XML_RNGP_PAT_START_VALUE, + XML_RNGP_PREFIX_UNDEFINED, + XML_RNGP_REF_CREATE_FAILED, + XML_RNGP_REF_CYCLE, + XML_RNGP_REF_NAME_INVALID, + XML_RNGP_REF_NO_DEF, + XML_RNGP_REF_NO_NAME, + XML_RNGP_REF_NOT_EMPTY, + XML_RNGP_START_CHOICE_AND_INTERLEAVE, + XML_RNGP_START_CONTENT, + XML_RNGP_START_EMPTY, + XML_RNGP_START_MISSING, + XML_RNGP_TEXT_EXPECTED, + XML_RNGP_TEXT_HAS_CHILD, + XML_RNGP_TYPE_MISSING, + XML_RNGP_TYPE_NOT_FOUND, + XML_RNGP_TYPE_VALUE, + XML_RNGP_UNKNOWN_ATTRIBUTE, + XML_RNGP_UNKNOWN_COMBINE, + XML_RNGP_UNKNOWN_CONSTRUCT, + XML_RNGP_UNKNOWN_TYPE_LIB, + XML_RNGP_URI_FRAGMENT, + XML_RNGP_URI_NOT_ABSOLUTE, + XML_RNGP_VALUE_EMPTY, + XML_RNGP_VALUE_NO_CONTENT, + XML_RNGP_XMLNS_NAME, + XML_RNGP_XML_NS, + XML_XPATH_EXPRESSION_OK, + XML_XPATH_NUMBER_ERROR, + XML_XPATH_UNFINISHED_LITERAL_ERROR, + XML_XPATH_START_LITERAL_ERROR, + XML_XPATH_VARIABLE_REF_ERROR, + XML_XPATH_UNDEF_VARIABLE_ERROR, + XML_XPATH_INVALID_PREDICATE_ERROR, + XML_XPATH_EXPR_ERROR, + XML_XPATH_UNCLOSED_ERROR, + XML_XPATH_UNKNOWN_FUNC_ERROR, + XML_XPATH_INVALID_OPERAND, + XML_XPATH_INVALID_TYPE, + XML_XPATH_INVALID_ARITY, + XML_XPATH_INVALID_CTXT_SIZE, + XML_XPATH_INVALID_CTXT_POSITION, + XML_XPATH_MEMORY_ERROR, + XML_XPTR_SYNTAX_ERROR, + XML_XPTR_RESOURCE_ERROR, + XML_XPTR_SUB_RESOURCE_ERROR, + XML_XPATH_UNDEF_PREFIX_ERROR, + XML_XPATH_ENCODING_ERROR, + XML_XPATH_INVALID_CHAR_ERROR, + XML_TREE_INVALID_HEX, + XML_TREE_INVALID_DEC, + XML_TREE_UNTERMINATED_ENTITY, + XML_SAVE_NOT_UTF8, + XML_SAVE_CHAR_INVALID, + XML_SAVE_NO_DOCTYPE, + XML_SAVE_UNKNOWN_ENCODING, + XML_REGEXP_COMPILE_ERROR, + XML_IO_UNKNOWN, + XML_IO_EACCES, + XML_IO_EAGAIN, + XML_IO_EBADF, + XML_IO_EBADMSG, + XML_IO_EBUSY, + XML_IO_ECANCELED, + XML_IO_ECHILD, + XML_IO_EDEADLK, + XML_IO_EDOM, + XML_IO_EEXIST, + XML_IO_EFAULT, + XML_IO_EFBIG, + XML_IO_EINPROGRESS, + XML_IO_EINTR, + XML_IO_EINVAL, + XML_IO_EIO, + XML_IO_EISDIR, + XML_IO_EMFILE, + XML_IO_EMLINK, + XML_IO_EMSGSIZE, + XML_IO_ENAMETOOLONG, + XML_IO_ENFILE, + XML_IO_ENODEV, + XML_IO_ENOENT, + XML_IO_ENOEXEC, + XML_IO_ENOLCK, + XML_IO_ENOMEM, + XML_IO_ENOSPC, + XML_IO_ENOSYS, + XML_IO_ENOTDIR, + XML_IO_ENOTEMPTY, + XML_IO_ENOTSUP, + XML_IO_ENOTTY, + XML_IO_ENXIO, + XML_IO_EPERM, + XML_IO_EPIPE, + XML_IO_ERANGE, + XML_IO_EROFS, + XML_IO_ESPIPE, + XML_IO_ESRCH, + XML_IO_ETIMEDOUT, + XML_IO_EXDEV, + XML_IO_NETWORK_ATTEMPT, + XML_IO_ENCODER, + XML_IO_FLUSH, + XML_IO_WRITE, + XML_IO_NO_INPUT, + XML_IO_BUFFER_FULL, + XML_IO_LOAD_ERROR, + XML_IO_ENOTSOCK, + XML_IO_EISCONN, + XML_IO_ECONNREFUSED, + XML_IO_ENETUNREACH, + XML_IO_EADDRINUSE, + XML_IO_EALREADY, + XML_IO_EAFNOSUPPORT, + XML_XINCLUDE_RECURSION, + XML_XINCLUDE_PARSE_VALUE, + XML_XINCLUDE_ENTITY_DEF_MISMATCH, + XML_XINCLUDE_NO_HREF, + XML_XINCLUDE_NO_FALLBACK, + XML_XINCLUDE_HREF_URI, + XML_XINCLUDE_TEXT_FRAGMENT, + XML_XINCLUDE_TEXT_DOCUMENT, + XML_XINCLUDE_INVALID_CHAR, + XML_XINCLUDE_BUILD_FAILED, + XML_XINCLUDE_UNKNOWN_ENCODING, + XML_XINCLUDE_MULTIPLE_ROOT, + XML_XINCLUDE_XPTR_FAILED, + XML_XINCLUDE_XPTR_RESULT, + XML_XINCLUDE_INCLUDE_IN_INCLUDE, + XML_XINCLUDE_FALLBACKS_IN_INCLUDE, + XML_XINCLUDE_FALLBACK_NOT_IN_INCLUDE, + XML_XINCLUDE_DEPRECATED_NS, + XML_XINCLUDE_FRAGMENT_ID, + XML_CATALOG_MISSING_ATTR, + XML_CATALOG_ENTRY_BROKEN, + XML_CATALOG_PREFER_VALUE, + XML_CATALOG_NOT_CATALOG, + XML_CATALOG_RECURSION, + XML_SCHEMAP_PREFIX_UNDEFINED, + XML_SCHEMAP_ATTRFORMDEFAULT_VALUE, + XML_SCHEMAP_ATTRGRP_NONAME_NOREF, + XML_SCHEMAP_ATTR_NONAME_NOREF, + XML_SCHEMAP_COMPLEXTYPE_NONAME_NOREF, + XML_SCHEMAP_ELEMFORMDEFAULT_VALUE, + XML_SCHEMAP_ELEM_NONAME_NOREF, + XML_SCHEMAP_EXTENSION_NO_BASE, + XML_SCHEMAP_FACET_NO_VALUE, + XML_SCHEMAP_FAILED_BUILD_IMPORT, + XML_SCHEMAP_GROUP_NONAME_NOREF, + XML_SCHEMAP_IMPORT_NAMESPACE_NOT_URI, + XML_SCHEMAP_IMPORT_REDEFINE_NSNAME, + XML_SCHEMAP_IMPORT_SCHEMA_NOT_URI, + XML_SCHEMAP_INVALID_BOOLEAN, + XML_SCHEMAP_INVALID_ENUM, + XML_SCHEMAP_INVALID_FACET, + XML_SCHEMAP_INVALID_FACET_VALUE, + XML_SCHEMAP_INVALID_MAXOCCURS, + XML_SCHEMAP_INVALID_MINOCCURS, + XML_SCHEMAP_INVALID_REF_AND_SUBTYPE, + XML_SCHEMAP_INVALID_WHITE_SPACE, + XML_SCHEMAP_NOATTR_NOREF, + XML_SCHEMAP_NOTATION_NO_NAME, + XML_SCHEMAP_NOTYPE_NOREF, + XML_SCHEMAP_REF_AND_SUBTYPE, + XML_SCHEMAP_RESTRICTION_NONAME_NOREF, + XML_SCHEMAP_SIMPLETYPE_NONAME, + XML_SCHEMAP_TYPE_AND_SUBTYPE, + XML_SCHEMAP_UNKNOWN_ALL_CHILD, + XML_SCHEMAP_UNKNOWN_ANYATTRIBUTE_CHILD, + XML_SCHEMAP_UNKNOWN_ATTR_CHILD, + XML_SCHEMAP_UNKNOWN_ATTRGRP_CHILD, + XML_SCHEMAP_UNKNOWN_ATTRIBUTE_GROUP, + XML_SCHEMAP_UNKNOWN_BASE_TYPE, + XML_SCHEMAP_UNKNOWN_CHOICE_CHILD, + XML_SCHEMAP_UNKNOWN_COMPLEXCONTENT_CHILD, + XML_SCHEMAP_UNKNOWN_COMPLEXTYPE_CHILD, + XML_SCHEMAP_UNKNOWN_ELEM_CHILD, + XML_SCHEMAP_UNKNOWN_EXTENSION_CHILD, + XML_SCHEMAP_UNKNOWN_FACET_CHILD, + XML_SCHEMAP_UNKNOWN_FACET_TYPE, + XML_SCHEMAP_UNKNOWN_GROUP_CHILD, + XML_SCHEMAP_UNKNOWN_IMPORT_CHILD, + XML_SCHEMAP_UNKNOWN_LIST_CHILD, + XML_SCHEMAP_UNKNOWN_NOTATION_CHILD, + XML_SCHEMAP_UNKNOWN_PROCESSCONTENT_CHILD, + XML_SCHEMAP_UNKNOWN_REF, + XML_SCHEMAP_UNKNOWN_RESTRICTION_CHILD, + XML_SCHEMAP_UNKNOWN_SCHEMAS_CHILD, + XML_SCHEMAP_UNKNOWN_SEQUENCE_CHILD, + XML_SCHEMAP_UNKNOWN_SIMPLECONTENT_CHILD, + XML_SCHEMAP_UNKNOWN_SIMPLETYPE_CHILD, + XML_SCHEMAP_UNKNOWN_TYPE, + XML_SCHEMAP_UNKNOWN_UNION_CHILD, + XML_SCHEMAP_ELEM_DEFAULT_FIXED, + XML_SCHEMAP_REGEXP_INVALID, + XML_SCHEMAP_FAILED_LOAD, + XML_SCHEMAP_NOTHING_TO_PARSE, + XML_SCHEMAP_NOROOT, + XML_SCHEMAP_REDEFINED_GROUP, + XML_SCHEMAP_REDEFINED_TYPE, + XML_SCHEMAP_REDEFINED_ELEMENT, + XML_SCHEMAP_REDEFINED_ATTRGROUP, + XML_SCHEMAP_REDEFINED_ATTR, + XML_SCHEMAP_REDEFINED_NOTATION, + XML_SCHEMAP_FAILED_PARSE, + XML_SCHEMAP_UNKNOWN_PREFIX, + XML_SCHEMAP_DEF_AND_PREFIX, + XML_SCHEMAP_UNKNOWN_INCLUDE_CHILD, + XML_SCHEMAP_INCLUDE_SCHEMA_NOT_URI, + XML_SCHEMAP_INCLUDE_SCHEMA_NO_URI, + XML_SCHEMAP_NOT_SCHEMA, + XML_SCHEMAP_UNKNOWN_MEMBER_TYPE, + XML_SCHEMAP_INVALID_ATTR_USE, + XML_SCHEMAP_RECURSIVE, + XML_SCHEMAP_SUPERNUMEROUS_LIST_ITEM_TYPE, + XML_SCHEMAP_INVALID_ATTR_COMBINATION, + XML_SCHEMAP_INVALID_ATTR_INLINE_COMBINATION, + XML_SCHEMAP_MISSING_SIMPLETYPE_CHILD, + XML_SCHEMAP_INVALID_ATTR_NAME, + XML_SCHEMAP_REF_AND_CONTENT, + XML_SCHEMAP_CT_PROPS_CORRECT_1, + XML_SCHEMAP_CT_PROPS_CORRECT_2, + XML_SCHEMAP_CT_PROPS_CORRECT_3, + XML_SCHEMAP_CT_PROPS_CORRECT_4, + XML_SCHEMAP_CT_PROPS_CORRECT_5, + XML_SCHEMAP_DERIVATION_OK_RESTRICTION_1, + XML_SCHEMAP_DERIVATION_OK_RESTRICTION_2_1_1, + XML_SCHEMAP_DERIVATION_OK_RESTRICTION_2_1_2, + XML_SCHEMAP_DERIVATION_OK_RESTRICTION_2_2, + XML_SCHEMAP_DERIVATION_OK_RESTRICTION_3, + XML_SCHEMAP_WILDCARD_INVALID_NS_MEMBER, + XML_SCHEMAP_INTERSECTION_NOT_EXPRESSIBLE, + XML_SCHEMAP_UNION_NOT_EXPRESSIBLE, + XML_SCHEMAP_SRC_IMPORT_3_1, + XML_SCHEMAP_SRC_IMPORT_3_2, + XML_SCHEMAP_DERIVATION_OK_RESTRICTION_4_1, + XML_SCHEMAP_DERIVATION_OK_RESTRICTION_4_2, + XML_SCHEMAP_DERIVATION_OK_RESTRICTION_4_3, + XML_SCHEMAP_COS_CT_EXTENDS_1_3, + XML_SCHEMAV_NOROOT, + XML_SCHEMAV_UNDECLAREDELEM, + XML_SCHEMAV_NOTTOPLEVEL, + XML_SCHEMAV_MISSING, + XML_SCHEMAV_WRONGELEM, + XML_SCHEMAV_NOTYPE, + XML_SCHEMAV_NOROLLBACK, + XML_SCHEMAV_ISABSTRACT, + XML_SCHEMAV_NOTEMPTY, + XML_SCHEMAV_ELEMCONT, + XML_SCHEMAV_HAVEDEFAULT, + XML_SCHEMAV_NOTNILLABLE, + XML_SCHEMAV_EXTRACONTENT, + XML_SCHEMAV_INVALIDATTR, + XML_SCHEMAV_INVALIDELEM, + XML_SCHEMAV_NOTDETERMINIST, + XML_SCHEMAV_CONSTRUCT, + XML_SCHEMAV_INTERNAL, + XML_SCHEMAV_NOTSIMPLE, + XML_SCHEMAV_ATTRUNKNOWN, + XML_SCHEMAV_ATTRINVALID, + XML_SCHEMAV_VALUE, + XML_SCHEMAV_FACET, + XML_SCHEMAV_CVC_DATATYPE_VALID_1_2_1, + XML_SCHEMAV_CVC_DATATYPE_VALID_1_2_2, + XML_SCHEMAV_CVC_DATATYPE_VALID_1_2_3, + XML_SCHEMAV_CVC_TYPE_3_1_1, + XML_SCHEMAV_CVC_TYPE_3_1_2, + XML_SCHEMAV_CVC_FACET_VALID, + XML_SCHEMAV_CVC_LENGTH_VALID, + XML_SCHEMAV_CVC_MINLENGTH_VALID, + XML_SCHEMAV_CVC_MAXLENGTH_VALID, + XML_SCHEMAV_CVC_MININCLUSIVE_VALID, + XML_SCHEMAV_CVC_MAXINCLUSIVE_VALID, + XML_SCHEMAV_CVC_MINEXCLUSIVE_VALID, + XML_SCHEMAV_CVC_MAXEXCLUSIVE_VALID, + XML_SCHEMAV_CVC_TOTALDIGITS_VALID, + XML_SCHEMAV_CVC_FRACTIONDIGITS_VALID, + XML_SCHEMAV_CVC_PATTERN_VALID, + XML_SCHEMAV_CVC_ENUMERATION_VALID, + XML_SCHEMAV_CVC_COMPLEX_TYPE_2_1, + XML_SCHEMAV_CVC_COMPLEX_TYPE_2_2, + XML_SCHEMAV_CVC_COMPLEX_TYPE_2_3, + XML_SCHEMAV_CVC_COMPLEX_TYPE_2_4, + +#if defined XML_SCHEMAV_CVC_ELT_1 + XML_SCHEMAV_CVC_ELT_1, + XML_SCHEMAV_CVC_ELT_2, + XML_SCHEMAV_CVC_ELT_3_1, + XML_SCHEMAV_CVC_ELT_3_2_1, + XML_SCHEMAV_CVC_ELT_3_2_2, + XML_SCHEMAV_CVC_ELT_4_1, + XML_SCHEMAV_CVC_ELT_4_2, + XML_SCHEMAV_CVC_ELT_4_3, + XML_SCHEMAV_CVC_ELT_5_1_1, + XML_SCHEMAV_CVC_ELT_5_1_2, + XML_SCHEMAV_CVC_ELT_5_2_1, + XML_SCHEMAV_CVC_ELT_5_2_2_1, + XML_SCHEMAV_CVC_ELT_5_2_2_2_1, + XML_SCHEMAV_CVC_ELT_5_2_2_2_2, + XML_SCHEMAV_CVC_ELT_6, + XML_SCHEMAV_CVC_ELT_7, + XML_SCHEMAV_CVC_ATTRIBUTE_1, + XML_SCHEMAV_CVC_ATTRIBUTE_2, + XML_SCHEMAV_CVC_ATTRIBUTE_3, + XML_SCHEMAV_CVC_ATTRIBUTE_4, + XML_SCHEMAV_CVC_COMPLEX_TYPE_3_1, + XML_SCHEMAV_CVC_COMPLEX_TYPE_3_2_1, + XML_SCHEMAV_CVC_COMPLEX_TYPE_3_2_2, + XML_SCHEMAV_CVC_COMPLEX_TYPE_4, + XML_SCHEMAV_CVC_COMPLEX_TYPE_5_1, + XML_SCHEMAV_CVC_COMPLEX_TYPE_5_2, + XML_SCHEMAV_ELEMENT_CONTENT, + XML_SCHEMAV_DOCUMENT_ELEMENT_MISSING, +#endif + +#if defined XML_SCHEMAV_CVC_COMPLEX_TYPE_1 + XML_SCHEMAV_CVC_COMPLEX_TYPE_1, + XML_SCHEMAV_CVC_AU, + XML_SCHEMAV_CVC_TYPE_1, + XML_SCHEMAV_CVC_TYPE_2, +#endif + + XML_XPTR_UNKNOWN_SCHEME, + XML_XPTR_CHILDSEQ_START, + XML_XPTR_EVAL_FAILED, + XML_XPTR_EXTRA_OBJECTS, + XML_C14N_CREATE_CTXT, + XML_C14N_REQUIRES_UTF8, + XML_C14N_CREATE_STACK, + XML_C14N_INVALID_NODE, + XML_FTP_PASV_ANSWER, + XML_FTP_EPSV_ANSWER, + XML_FTP_ACCNT, + XML_HTTP_URL_SYNTAX, + XML_HTTP_USE_IP, + XML_HTTP_UNKNOWN_HOST, + XML_SCHEMAP_SRC_SIMPLE_TYPE_1, + XML_SCHEMAP_SRC_SIMPLE_TYPE_2, + XML_SCHEMAP_SRC_SIMPLE_TYPE_3, + XML_SCHEMAP_SRC_SIMPLE_TYPE_4, + XML_SCHEMAP_SRC_RESOLVE, + XML_SCHEMAP_SRC_RESTRICTION_BASE_OR_SIMPLETYPE, + XML_SCHEMAP_SRC_LIST_ITEMTYPE_OR_SIMPLETYPE, + XML_SCHEMAP_SRC_UNION_MEMBERTYPES_OR_SIMPLETYPES, + XML_SCHEMAP_ST_PROPS_CORRECT_1, + XML_SCHEMAP_ST_PROPS_CORRECT_2, + XML_SCHEMAP_ST_PROPS_CORRECT_3, + XML_SCHEMAP_COS_ST_RESTRICTS_1_1, + XML_SCHEMAP_COS_ST_RESTRICTS_1_2, + XML_SCHEMAP_COS_ST_RESTRICTS_1_3_1, + XML_SCHEMAP_COS_ST_RESTRICTS_1_3_2, + XML_SCHEMAP_COS_ST_RESTRICTS_2_1, + XML_SCHEMAP_COS_ST_RESTRICTS_2_3_1_1, + XML_SCHEMAP_COS_ST_RESTRICTS_2_3_1_2, + XML_SCHEMAP_COS_ST_RESTRICTS_2_3_2_1, + XML_SCHEMAP_COS_ST_RESTRICTS_2_3_2_2, + XML_SCHEMAP_COS_ST_RESTRICTS_2_3_2_3, + XML_SCHEMAP_COS_ST_RESTRICTS_2_3_2_4, + XML_SCHEMAP_COS_ST_RESTRICTS_2_3_2_5, + XML_SCHEMAP_COS_ST_RESTRICTS_3_1, + XML_SCHEMAP_COS_ST_RESTRICTS_3_3_1, + XML_SCHEMAP_COS_ST_RESTRICTS_3_3_1_2, + XML_SCHEMAP_COS_ST_RESTRICTS_3_3_2_2, + XML_SCHEMAP_COS_ST_RESTRICTS_3_3_2_1, + XML_SCHEMAP_COS_ST_RESTRICTS_3_3_2_3, + XML_SCHEMAP_COS_ST_RESTRICTS_3_3_2_4, + XML_SCHEMAP_COS_ST_RESTRICTS_3_3_2_5, + XML_SCHEMAP_COS_ST_DERIVED_OK_2_1, + XML_SCHEMAP_COS_ST_DERIVED_OK_2_2, + XML_SCHEMAP_S4S_ELEM_NOT_ALLOWED, + XML_SCHEMAP_S4S_ELEM_MISSING, + XML_SCHEMAP_S4S_ATTR_NOT_ALLOWED, + XML_SCHEMAP_S4S_ATTR_MISSING, + +#if defined XML_SCHEMAP_S4S_ATTR_INVALID_VALUE + XML_SCHEMAP_S4S_ATTR_INVALID_VALUE, + XML_SCHEMAP_SRC_ELEMENT_1, + XML_SCHEMAP_SRC_ELEMENT_2_1, + XML_SCHEMAP_SRC_ELEMENT_2_2, + XML_SCHEMAP_SRC_ELEMENT_3, + XML_SCHEMAP_P_PROPS_CORRECT_1, + XML_SCHEMAP_P_PROPS_CORRECT_2_1, + XML_SCHEMAP_P_PROPS_CORRECT_2_2, + XML_SCHEMAP_E_PROPS_CORRECT_2, + XML_SCHEMAP_E_PROPS_CORRECT_3, + XML_SCHEMAP_E_PROPS_CORRECT_4, + XML_SCHEMAP_E_PROPS_CORRECT_5, + XML_SCHEMAP_E_PROPS_CORRECT_6, + XML_SCHEMAP_SRC_INCLUDE, + XML_SCHEMAP_SRC_ATTRIBUTE_1, + XML_SCHEMAP_SRC_ATTRIBUTE_2, + XML_SCHEMAP_SRC_ATTRIBUTE_3_1, + XML_SCHEMAP_SRC_ATTRIBUTE_3_2, + XML_SCHEMAP_SRC_ATTRIBUTE_4, + XML_SCHEMAP_NO_XMLNS, + XML_SCHEMAP_NO_XSI, + XML_SCHEMAP_COS_VALID_DEFAULT_1, + XML_SCHEMAP_COS_VALID_DEFAULT_2_1, + XML_SCHEMAP_COS_VALID_DEFAULT_2_2_1, + XML_SCHEMAP_COS_VALID_DEFAULT_2_2_2, + XML_SCHEMAP_CVC_SIMPLE_TYPE, + XML_SCHEMAP_COS_CT_EXTENDS_1_1, + XML_SCHEMAP_SRC_IMPORT_1_1, + XML_SCHEMAP_SRC_IMPORT_1_2, + XML_SCHEMAP_SRC_IMPORT_2, + XML_SCHEMAP_SRC_IMPORT_2_1, + XML_SCHEMAP_SRC_IMPORT_2_2, +#endif + +#if defined XML_SCHEMAP_INTERNAL + XML_SCHEMAP_INTERNAL, + XML_SCHEMAP_NOT_DETERMINISTIC, +#endif + +#if defined XML_SCHEMAP_SRC_ATTRIBUTE_GROUP_1 + XML_SCHEMAP_SRC_ATTRIBUTE_GROUP_1, + XML_SCHEMAP_SRC_ATTRIBUTE_GROUP_2, + XML_SCHEMAP_SRC_ATTRIBUTE_GROUP_3, + XML_SCHEMAP_MG_PROPS_CORRECT_1, + XML_SCHEMAP_MG_PROPS_CORRECT_2, + XML_SCHEMAP_SRC_CT_1, + XML_SCHEMAP_DERIVATION_OK_RESTRICTION_2_1_3, + XML_SCHEMAP_AU_PROPS_CORRECT_2, + XML_SCHEMAP_A_PROPS_CORRECT_2, +#endif + -1 +}; + + +static const char* xmlErrorStrs[] = { + "ERR_OK", + "ERR_INTERNAL_ERROR", + "ERR_NO_MEMORY", + "ERR_DOCUMENT_START", + "ERR_DOCUMENT_EMPTY", + "ERR_DOCUMENT_END", + "ERR_INVALID_HEX_CHARREF", + "ERR_INVALID_DEC_CHARREF", + "ERR_INVALID_CHARREF", + "ERR_INVALID_CHAR", + "ERR_CHARREF_AT_EOF", + "ERR_CHARREF_IN_PROLOG", + "ERR_CHARREF_IN_EPILOG", + "ERR_CHARREF_IN_DTD", + "ERR_ENTITYREF_AT_EOF", + "ERR_ENTITYREF_IN_PROLOG", + "ERR_ENTITYREF_IN_EPILOG", + "ERR_ENTITYREF_IN_DTD", + "ERR_PEREF_AT_EOF", + "ERR_PEREF_IN_PROLOG", + "ERR_PEREF_IN_EPILOG", + "ERR_PEREF_IN_INT_SUBSET", + "ERR_ENTITYREF_NO_NAME", + "ERR_ENTITYREF_SEMICOL_MISSING", + "ERR_PEREF_NO_NAME", + "ERR_PEREF_SEMICOL_MISSING", + "ERR_UNDECLARED_ENTITY", + "WAR_UNDECLARED_ENTITY", + "ERR_UNPARSED_ENTITY", + "ERR_ENTITY_IS_EXTERNAL", + "ERR_ENTITY_IS_PARAMETER", + "ERR_UNKNOWN_ENCODING", + "ERR_UNSUPPORTED_ENCODING", + "ERR_STRING_NOT_STARTED", + "ERR_STRING_NOT_CLOSED", + "ERR_NS_DECL_ERROR", + "ERR_ENTITY_NOT_STARTED", + "ERR_ENTITY_NOT_FINISHED", + "ERR_LT_IN_ATTRIBUTE", + "ERR_ATTRIBUTE_NOT_STARTED", + "ERR_ATTRIBUTE_NOT_FINISHED", + "ERR_ATTRIBUTE_WITHOUT_VALUE", + "ERR_ATTRIBUTE_REDEFINED", + "ERR_LITERAL_NOT_STARTED", + "ERR_LITERAL_NOT_FINISHED", + "ERR_COMMENT_NOT_FINISHED", + "ERR_PI_NOT_STARTED", + "ERR_PI_NOT_FINISHED", + "ERR_NOTATION_NOT_STARTED", + "ERR_NOTATION_NOT_FINISHED", + "ERR_ATTLIST_NOT_STARTED", + "ERR_ATTLIST_NOT_FINISHED", + "ERR_MIXED_NOT_STARTED", + "ERR_MIXED_NOT_FINISHED", + "ERR_ELEMCONTENT_NOT_STARTED", + "ERR_ELEMCONTENT_NOT_FINISHED", + "ERR_XMLDECL_NOT_STARTED", + "ERR_XMLDECL_NOT_FINISHED", + "ERR_CONDSEC_NOT_STARTED", + "ERR_CONDSEC_NOT_FINISHED", + "ERR_EXT_SUBSET_NOT_FINISHED", + "ERR_DOCTYPE_NOT_FINISHED", + "ERR_MISPLACED_CDATA_END", + "ERR_CDATA_NOT_FINISHED", + "ERR_RESERVED_XML_NAME", + "ERR_SPACE_REQUIRED", + "ERR_SEPARATOR_REQUIRED", + "ERR_NMTOKEN_REQUIRED", + "ERR_NAME_REQUIRED", + "ERR_PCDATA_REQUIRED", + "ERR_URI_REQUIRED", + "ERR_PUBID_REQUIRED", + "ERR_LT_REQUIRED", + "ERR_GT_REQUIRED", + "ERR_LTSLASH_REQUIRED", + "ERR_EQUAL_REQUIRED", + "ERR_TAG_NAME_MISMATCH", + "ERR_TAG_NOT_FINISHED", + "ERR_STANDALONE_VALUE", + "ERR_ENCODING_NAME", + "ERR_HYPHEN_IN_COMMENT", + "ERR_INVALID_ENCODING", + "ERR_EXT_ENTITY_STANDALONE", + "ERR_CONDSEC_INVALID", + "ERR_VALUE_REQUIRED", + "ERR_NOT_WELL_BALANCED", + "ERR_EXTRA_CONTENT", + "ERR_ENTITY_CHAR_ERROR", + "ERR_ENTITY_PE_INTERNAL", + "ERR_ENTITY_LOOP", + "ERR_ENTITY_BOUNDARY", + "ERR_INVALID_URI", + "ERR_URI_FRAGMENT", + "WAR_CATALOG_PI", + "ERR_NO_DTD", + "ERR_CONDSEC_INVALID_KEYWORD", + "ERR_VERSION_MISSING", + "WAR_UNKNOWN_VERSION", + "WAR_LANG_VALUE", + "WAR_NS_URI", + "WAR_NS_URI_RELATIVE", + "ERR_MISSING_ENCODING", + "NS_ERR_XML_NAMESPACE", + "NS_ERR_UNDEFINED_NAMESPACE", + "NS_ERR_QNAME", + "NS_ERR_ATTRIBUTE_REDEFINED", + "DTD_ATTRIBUTE_DEFAULT", + "DTD_ATTRIBUTE_REDEFINED", + "DTD_ATTRIBUTE_VALUE", + "DTD_CONTENT_ERROR", + "DTD_CONTENT_MODEL", + "DTD_CONTENT_NOT_DETERMINIST", + "DTD_DIFFERENT_PREFIX", + "DTD_ELEM_DEFAULT_NAMESPACE", + "DTD_ELEM_NAMESPACE", + "DTD_ELEM_REDEFINED", + "DTD_EMPTY_NOTATION", + "DTD_ENTITY_TYPE", + "DTD_ID_FIXED", + "DTD_ID_REDEFINED", + "DTD_ID_SUBSET", + "DTD_INVALID_CHILD", + "DTD_INVALID_DEFAULT", + "DTD_LOAD_ERROR", + "DTD_MISSING_ATTRIBUTE", + "DTD_MIXED_CORRUPT", + "DTD_MULTIPLE_ID", + "DTD_NO_DOC", + "DTD_NO_DTD", + "DTD_NO_ELEM_NAME", + "DTD_NO_PREFIX", + "DTD_NO_ROOT", + "DTD_NOTATION_REDEFINED", + "DTD_NOTATION_VALUE", + "DTD_NOT_EMPTY", + "DTD_NOT_PCDATA", + "DTD_NOT_STANDALONE", + "DTD_ROOT_NAME", + "DTD_STANDALONE_WHITE_SPACE", + "DTD_UNKNOWN_ATTRIBUTE", + "DTD_UNKNOWN_ELEM", + "DTD_UNKNOWN_ENTITY", + "DTD_UNKNOWN_ID", + "DTD_UNKNOWN_NOTATION", + "DTD_STANDALONE_DEFAULTED", + "DTD_XMLID_VALUE", + "DTD_XMLID_TYPE", + "HTML_STRUCURE_ERROR", + "HTML_UNKNOWN_TAG", + "RNGP_ANYNAME_ATTR_ANCESTOR", + "RNGP_ATTR_CONFLICT", + "RNGP_ATTRIBUTE_CHILDREN", + "RNGP_ATTRIBUTE_CONTENT", + "RNGP_ATTRIBUTE_EMPTY", + "RNGP_ATTRIBUTE_NOOP", + "RNGP_CHOICE_CONTENT", + "RNGP_CHOICE_EMPTY", + "RNGP_CREATE_FAILURE", + "RNGP_DATA_CONTENT", + "RNGP_DEF_CHOICE_AND_INTERLEAVE", + "RNGP_DEFINE_CREATE_FAILED", + "RNGP_DEFINE_EMPTY", + "RNGP_DEFINE_MISSING", + "RNGP_DEFINE_NAME_MISSING", + "RNGP_ELEM_CONTENT_EMPTY", + "RNGP_ELEM_CONTENT_ERROR", + "RNGP_ELEMENT_EMPTY", + "RNGP_ELEMENT_CONTENT", + "RNGP_ELEMENT_NAME", + "RNGP_ELEMENT_NO_CONTENT", + "RNGP_ELEM_TEXT_CONFLICT", + "RNGP_EMPTY", + "RNGP_EMPTY_CONSTRUCT", + "RNGP_EMPTY_CONTENT", + "RNGP_EMPTY_NOT_EMPTY", + "RNGP_ERROR_TYPE_LIB", + "RNGP_EXCEPT_EMPTY", + "RNGP_EXCEPT_MISSING", + "RNGP_EXCEPT_MULTIPLE", + "RNGP_EXCEPT_NO_CONTENT", + "RNGP_EXTERNALREF_EMTPY", + "RNGP_EXTERNAL_REF_FAILURE", + "RNGP_EXTERNALREF_RECURSE", + "RNGP_FORBIDDEN_ATTRIBUTE", + "RNGP_FOREIGN_ELEMENT", + "RNGP_GRAMMAR_CONTENT", + "RNGP_GRAMMAR_EMPTY", + "RNGP_GRAMMAR_MISSING", + "RNGP_GRAMMAR_NO_START", + "RNGP_GROUP_ATTR_CONFLICT", + "RNGP_HREF_ERROR", + "RNGP_INCLUDE_EMPTY", + "RNGP_INCLUDE_FAILURE", + "RNGP_INCLUDE_RECURSE", + "RNGP_INTERLEAVE_ADD", + "RNGP_INTERLEAVE_CREATE_FAILED", + "RNGP_INTERLEAVE_EMPTY", + "RNGP_INTERLEAVE_NO_CONTENT", + "RNGP_INVALID_DEFINE_NAME", + "RNGP_INVALID_URI", + "RNGP_INVALID_VALUE", + "RNGP_MISSING_HREF", + "RNGP_NAME_MISSING", + "RNGP_NEED_COMBINE", + "RNGP_NOTALLOWED_NOT_EMPTY", + "RNGP_NSNAME_ATTR_ANCESTOR", + "RNGP_NSNAME_NO_NS", + "RNGP_PARAM_FORBIDDEN", + "RNGP_PARAM_NAME_MISSING", + "RNGP_PARENTREF_CREATE_FAILED", + "RNGP_PARENTREF_NAME_INVALID", + "RNGP_PARENTREF_NO_NAME", + "RNGP_PARENTREF_NO_PARENT", + "RNGP_PARENTREF_NOT_EMPTY", + "RNGP_PARSE_ERROR", + "RNGP_PAT_ANYNAME_EXCEPT_ANYNAME", + "RNGP_PAT_ATTR_ATTR", + "RNGP_PAT_ATTR_ELEM", + "RNGP_PAT_DATA_EXCEPT_ATTR", + "RNGP_PAT_DATA_EXCEPT_ELEM", + "RNGP_PAT_DATA_EXCEPT_EMPTY", + "RNGP_PAT_DATA_EXCEPT_GROUP", + "RNGP_PAT_DATA_EXCEPT_INTERLEAVE", + "RNGP_PAT_DATA_EXCEPT_LIST", + "RNGP_PAT_DATA_EXCEPT_ONEMORE", + "RNGP_PAT_DATA_EXCEPT_REF", + "RNGP_PAT_DATA_EXCEPT_TEXT", + "RNGP_PAT_LIST_ATTR", + "RNGP_PAT_LIST_ELEM", + "RNGP_PAT_LIST_INTERLEAVE", + "RNGP_PAT_LIST_LIST", + "RNGP_PAT_LIST_REF", + "RNGP_PAT_LIST_TEXT", + "RNGP_PAT_NSNAME_EXCEPT_ANYNAME", + "RNGP_PAT_NSNAME_EXCEPT_NSNAME", + "RNGP_PAT_ONEMORE_GROUP_ATTR", + "RNGP_PAT_ONEMORE_INTERLEAVE_ATTR", + "RNGP_PAT_START_ATTR", + "RNGP_PAT_START_DATA", + "RNGP_PAT_START_EMPTY", + "RNGP_PAT_START_GROUP", + "RNGP_PAT_START_INTERLEAVE", + "RNGP_PAT_START_LIST", + "RNGP_PAT_START_ONEMORE", + "RNGP_PAT_START_TEXT", + "RNGP_PAT_START_VALUE", + "RNGP_PREFIX_UNDEFINED", + "RNGP_REF_CREATE_FAILED", + "RNGP_REF_CYCLE", + "RNGP_REF_NAME_INVALID", + "RNGP_REF_NO_DEF", + "RNGP_REF_NO_NAME", + "RNGP_REF_NOT_EMPTY", + "RNGP_START_CHOICE_AND_INTERLEAVE", + "RNGP_START_CONTENT", + "RNGP_START_EMPTY", + "RNGP_START_MISSING", + "RNGP_TEXT_EXPECTED", + "RNGP_TEXT_HAS_CHILD", + "RNGP_TYPE_MISSING", + "RNGP_TYPE_NOT_FOUND", + "RNGP_TYPE_VALUE", + "RNGP_UNKNOWN_ATTRIBUTE", + "RNGP_UNKNOWN_COMBINE", + "RNGP_UNKNOWN_CONSTRUCT", + "RNGP_UNKNOWN_TYPE_LIB", + "RNGP_URI_FRAGMENT", + "RNGP_URI_NOT_ABSOLUTE", + "RNGP_VALUE_EMPTY", + "RNGP_VALUE_NO_CONTENT", + "RNGP_XMLNS_NAME", + "RNGP_XML_NS", + "XPATH_EXPRESSION_OK", + "XPATH_NUMBER_ERROR", + "XPATH_UNFINISHED_LITERAL_ERROR", + "XPATH_START_LITERAL_ERROR", + "XPATH_VARIABLE_REF_ERROR", + "XPATH_UNDEF_VARIABLE_ERROR", + "XPATH_INVALID_PREDICATE_ERROR", + "XPATH_EXPR_ERROR", + "XPATH_UNCLOSED_ERROR", + "XPATH_UNKNOWN_FUNC_ERROR", + "XPATH_INVALID_OPERAND", + "XPATH_INVALID_TYPE", + "XPATH_INVALID_ARITY", + "XPATH_INVALID_CTXT_SIZE", + "XPATH_INVALID_CTXT_POSITION", + "XPATH_MEMORY_ERROR", + "XPTR_SYNTAX_ERROR", + "XPTR_RESOURCE_ERROR", + "XPTR_SUB_RESOURCE_ERROR", + "XPATH_UNDEF_PREFIX_ERROR", + "XPATH_ENCODING_ERROR", + "XPATH_INVALID_CHAR_ERROR", + "TREE_INVALID_HEX", + "TREE_INVALID_DEC", + "TREE_UNTERMINATED_ENTITY", + "SAVE_NOT_UTF8", + "SAVE_CHAR_INVALID", + "SAVE_NO_DOCTYPE", + "SAVE_UNKNOWN_ENCODING", + "REGEXP_COMPILE_ERROR", + "IO_UNKNOWN", + "IO_EACCES", + "IO_EAGAIN", + "IO_EBADF", + "IO_EBADMSG", + "IO_EBUSY", + "IO_ECANCELED", + "IO_ECHILD", + "IO_EDEADLK", + "IO_EDOM", + "IO_EEXIST", + "IO_EFAULT", + "IO_EFBIG", + "IO_EINPROGRESS", + "IO_EINTR", + "IO_EINVAL", + "IO_EIO", + "IO_EISDIR", + "IO_EMFILE", + "IO_EMLINK", + "IO_EMSGSIZE", + "IO_ENAMETOOLONG", + "IO_ENFILE", + "IO_ENODEV", + "IO_ENOENT", + "IO_ENOEXEC", + "IO_ENOLCK", + "IO_ENOMEM", + "IO_ENOSPC", + "IO_ENOSYS", + "IO_ENOTDIR", + "IO_ENOTEMPTY", + "IO_ENOTSUP", + "IO_ENOTTY", + "IO_ENXIO", + "IO_EPERM", + "IO_EPIPE", + "IO_ERANGE", + "IO_EROFS", + "IO_ESPIPE", + "IO_ESRCH", + "IO_ETIMEDOUT", + "IO_EXDEV", + "IO_NETWORK_ATTEMPT", + "IO_ENCODER", + "IO_FLUSH", + "IO_WRITE", + "IO_NO_INPUT", + "IO_BUFFER_FULL", + "IO_LOAD_ERROR", + "IO_ENOTSOCK", + "IO_EISCONN", + "IO_ECONNREFUSED", + "IO_ENETUNREACH", + "IO_EADDRINUSE", + "IO_EALREADY", + "IO_EAFNOSUPPORT", + "XINCLUDE_RECURSION", + "XINCLUDE_PARSE_VALUE", + "XINCLUDE_ENTITY_DEF_MISMATCH", + "XINCLUDE_NO_HREF", + "XINCLUDE_NO_FALLBACK", + "XINCLUDE_HREF_URI", + "XINCLUDE_TEXT_FRAGMENT", + "XINCLUDE_TEXT_DOCUMENT", + "XINCLUDE_INVALID_CHAR", + "XINCLUDE_BUILD_FAILED", + "XINCLUDE_UNKNOWN_ENCODING", + "XINCLUDE_MULTIPLE_ROOT", + "XINCLUDE_XPTR_FAILED", + "XINCLUDE_XPTR_RESULT", + "XINCLUDE_INCLUDE_IN_INCLUDE", + "XINCLUDE_FALLBACKS_IN_INCLUDE", + "XINCLUDE_FALLBACK_NOT_IN_INCLUDE", + "XINCLUDE_DEPRECATED_NS", + "XINCLUDE_FRAGMENT_ID", + "CATALOG_MISSING_ATTR", + "CATALOG_ENTRY_BROKEN", + "CATALOG_PREFER_VALUE", + "CATALOG_NOT_CATALOG", + "CATALOG_RECURSION", + "SCHEMAP_PREFIX_UNDEFINED", + "SCHEMAP_ATTRFORMDEFAULT_VALUE", + "SCHEMAP_ATTRGRP_NONAME_NOREF", + "SCHEMAP_ATTR_NONAME_NOREF", + "SCHEMAP_COMPLEXTYPE_NONAME_NOREF", + "SCHEMAP_ELEMFORMDEFAULT_VALUE", + "SCHEMAP_ELEM_NONAME_NOREF", + "SCHEMAP_EXTENSION_NO_BASE", + "SCHEMAP_FACET_NO_VALUE", + "SCHEMAP_FAILED_BUILD_IMPORT", + "SCHEMAP_GROUP_NONAME_NOREF", + "SCHEMAP_IMPORT_NAMESPACE_NOT_URI", + "SCHEMAP_IMPORT_REDEFINE_NSNAME", + "SCHEMAP_IMPORT_SCHEMA_NOT_URI", + "SCHEMAP_INVALID_BOOLEAN", + "SCHEMAP_INVALID_ENUM", + "SCHEMAP_INVALID_FACET", + "SCHEMAP_INVALID_FACET_VALUE", + "SCHEMAP_INVALID_MAXOCCURS", + "SCHEMAP_INVALID_MINOCCURS", + "SCHEMAP_INVALID_REF_AND_SUBTYPE", + "SCHEMAP_INVALID_WHITE_SPACE", + "SCHEMAP_NOATTR_NOREF", + "SCHEMAP_NOTATION_NO_NAME", + "SCHEMAP_NOTYPE_NOREF", + "SCHEMAP_REF_AND_SUBTYPE", + "SCHEMAP_RESTRICTION_NONAME_NOREF", + "SCHEMAP_SIMPLETYPE_NONAME", + "SCHEMAP_TYPE_AND_SUBTYPE", + "SCHEMAP_UNKNOWN_ALL_CHILD", + "SCHEMAP_UNKNOWN_ANYATTRIBUTE_CHILD", + "SCHEMAP_UNKNOWN_ATTR_CHILD", + "SCHEMAP_UNKNOWN_ATTRGRP_CHILD", + "SCHEMAP_UNKNOWN_ATTRIBUTE_GROUP", + "SCHEMAP_UNKNOWN_BASE_TYPE", + "SCHEMAP_UNKNOWN_CHOICE_CHILD", + "SCHEMAP_UNKNOWN_COMPLEXCONTENT_CHILD", + "SCHEMAP_UNKNOWN_COMPLEXTYPE_CHILD", + "SCHEMAP_UNKNOWN_ELEM_CHILD", + "SCHEMAP_UNKNOWN_EXTENSION_CHILD", + "SCHEMAP_UNKNOWN_FACET_CHILD", + "SCHEMAP_UNKNOWN_FACET_TYPE", + "SCHEMAP_UNKNOWN_GROUP_CHILD", + "SCHEMAP_UNKNOWN_IMPORT_CHILD", + "SCHEMAP_UNKNOWN_LIST_CHILD", + "SCHEMAP_UNKNOWN_NOTATION_CHILD", + "SCHEMAP_UNKNOWN_PROCESSCONTENT_CHILD", + "SCHEMAP_UNKNOWN_REF", + "SCHEMAP_UNKNOWN_RESTRICTION_CHILD", + "SCHEMAP_UNKNOWN_SCHEMAS_CHILD", + "SCHEMAP_UNKNOWN_SEQUENCE_CHILD", + "SCHEMAP_UNKNOWN_SIMPLECONTENT_CHILD", + "SCHEMAP_UNKNOWN_SIMPLETYPE_CHILD", + "SCHEMAP_UNKNOWN_TYPE", + "SCHEMAP_UNKNOWN_UNION_CHILD", + "SCHEMAP_ELEM_DEFAULT_FIXED", + "SCHEMAP_REGEXP_INVALID", + "SCHEMAP_FAILED_LOAD", + "SCHEMAP_NOTHING_TO_PARSE", + "SCHEMAP_NOROOT", + "SCHEMAP_REDEFINED_GROUP", + "SCHEMAP_REDEFINED_TYPE", + "SCHEMAP_REDEFINED_ELEMENT", + "SCHEMAP_REDEFINED_ATTRGROUP", + "SCHEMAP_REDEFINED_ATTR", + "SCHEMAP_REDEFINED_NOTATION", + "SCHEMAP_FAILED_PARSE", + "SCHEMAP_UNKNOWN_PREFIX", + "SCHEMAP_DEF_AND_PREFIX", + "SCHEMAP_UNKNOWN_INCLUDE_CHILD", + "SCHEMAP_INCLUDE_SCHEMA_NOT_URI", + "SCHEMAP_INCLUDE_SCHEMA_NO_URI", + "SCHEMAP_NOT_SCHEMA", + "SCHEMAP_UNKNOWN_MEMBER_TYPE", + "SCHEMAP_INVALID_ATTR_USE", + "SCHEMAP_RECURSIVE", + "SCHEMAP_SUPERNUMEROUS_LIST_ITEM_TYPE", + "SCHEMAP_INVALID_ATTR_COMBINATION", + "SCHEMAP_INVALID_ATTR_INLINE_COMBINATION", + "SCHEMAP_MISSING_SIMPLETYPE_CHILD", + "SCHEMAP_INVALID_ATTR_NAME", + "SCHEMAP_REF_AND_CONTENT", + "SCHEMAP_CT_PROPS_CORRECT_1", + "SCHEMAP_CT_PROPS_CORRECT_2", + "SCHEMAP_CT_PROPS_CORRECT_3", + "SCHEMAP_CT_PROPS_CORRECT_4", + "SCHEMAP_CT_PROPS_CORRECT_5", + "SCHEMAP_DERIVATION_OK_RESTRICTION_1", + "SCHEMAP_DERIVATION_OK_RESTRICTION_2_1_1", + "SCHEMAP_DERIVATION_OK_RESTRICTION_2_1_2", + "SCHEMAP_DERIVATION_OK_RESTRICTION_2_2", + "SCHEMAP_DERIVATION_OK_RESTRICTION_3", + "SCHEMAP_WILDCARD_INVALID_NS_MEMBER", + "SCHEMAP_INTERSECTION_NOT_EXPRESSIBLE", + "SCHEMAP_UNION_NOT_EXPRESSIBLE", + "SCHEMAP_SRC_IMPORT_3_1", + "SCHEMAP_SRC_IMPORT_3_2", + "SCHEMAP_DERIVATION_OK_RESTRICTION_4_1", + "SCHEMAP_DERIVATION_OK_RESTRICTION_4_2", + "SCHEMAP_DERIVATION_OK_RESTRICTION_4_3", + "SCHEMAP_COS_CT_EXTENDS_1_3", + "SCHEMAV_NOROOT", + "SCHEMAV_UNDECLAREDELEM", + "SCHEMAV_NOTTOPLEVEL", + "SCHEMAV_MISSING", + "SCHEMAV_WRONGELEM", + "SCHEMAV_NOTYPE", + "SCHEMAV_NOROLLBACK", + "SCHEMAV_ISABSTRACT", + "SCHEMAV_NOTEMPTY", + "SCHEMAV_ELEMCONT", + "SCHEMAV_HAVEDEFAULT", + "SCHEMAV_NOTNILLABLE", + "SCHEMAV_EXTRACONTENT", + "SCHEMAV_INVALIDATTR", + "SCHEMAV_INVALIDELEM", + "SCHEMAV_NOTDETERMINIST", + "SCHEMAV_CONSTRUCT", + "SCHEMAV_INTERNAL", + "SCHEMAV_NOTSIMPLE", + "SCHEMAV_ATTRUNKNOWN", + "SCHEMAV_ATTRINVALID", + "SCHEMAV_VALUE", + "SCHEMAV_FACET", + "SCHEMAV_CVC_DATATYPE_VALID_1_2_1", + "SCHEMAV_CVC_DATATYPE_VALID_1_2_2", + "SCHEMAV_CVC_DATATYPE_VALID_1_2_3", + "SCHEMAV_CVC_TYPE_3_1_1", + "SCHEMAV_CVC_TYPE_3_1_2", + "SCHEMAV_CVC_FACET_VALID", + "SCHEMAV_CVC_LENGTH_VALID", + "SCHEMAV_CVC_MINLENGTH_VALID", + "SCHEMAV_CVC_MAXLENGTH_VALID", + "SCHEMAV_CVC_MININCLUSIVE_VALID", + "SCHEMAV_CVC_MAXINCLUSIVE_VALID", + "SCHEMAV_CVC_MINEXCLUSIVE_VALID", + "SCHEMAV_CVC_MAXEXCLUSIVE_VALID", + "SCHEMAV_CVC_TOTALDIGITS_VALID", + "SCHEMAV_CVC_FRACTIONDIGITS_VALID", + "SCHEMAV_CVC_PATTERN_VALID", + "SCHEMAV_CVC_ENUMERATION_VALID", + "SCHEMAV_CVC_COMPLEX_TYPE_2_1", + "SCHEMAV_CVC_COMPLEX_TYPE_2_2", + "SCHEMAV_CVC_COMPLEX_TYPE_2_3", + "SCHEMAV_CVC_COMPLEX_TYPE_2_4", + +#if defined XML_SCHEMAV_CVC_ELT_1 + "SCHEMAV_CVC_ELT_1", + "SCHEMAV_CVC_ELT_2", + "SCHEMAV_CVC_ELT_3_1", + "SCHEMAV_CVC_ELT_3_2_1", + "SCHEMAV_CVC_ELT_3_2_2", + "SCHEMAV_CVC_ELT_4_1", + "SCHEMAV_CVC_ELT_4_2", + "SCHEMAV_CVC_ELT_4_3", + "SCHEMAV_CVC_ELT_5_1_1", + "SCHEMAV_CVC_ELT_5_1_2", + "SCHEMAV_CVC_ELT_5_2_1", + "SCHEMAV_CVC_ELT_5_2_2_1", + "SCHEMAV_CVC_ELT_5_2_2_2_1", + "SCHEMAV_CVC_ELT_5_2_2_2_2", + "SCHEMAV_CVC_ELT_6", + "SCHEMAV_CVC_ELT_7", + "SCHEMAV_CVC_ATTRIBUTE_1", + "SCHEMAV_CVC_ATTRIBUTE_2", + "SCHEMAV_CVC_ATTRIBUTE_3", + "SCHEMAV_CVC_ATTRIBUTE_4", + "SCHEMAV_CVC_COMPLEX_TYPE_3_1", + "SCHEMAV_CVC_COMPLEX_TYPE_3_2_1", + "SCHEMAV_CVC_COMPLEX_TYPE_3_2_2", + "SCHEMAV_CVC_COMPLEX_TYPE_4", + "SCHEMAV_CVC_COMPLEX_TYPE_5_1", + "SCHEMAV_CVC_COMPLEX_TYPE_5_2", + "SCHEMAV_ELEMENT_CONTENT", + "SCHEMAV_DOCUMENT_ELEMENT_MISSING", +#endif + +#if defined XML_SCHEMAV_CVC_COMPLEX_TYPE_1 + "SCHEMAV_CVC_COMPLEX_TYPE_1", + "SCHEMAV_CVC_AU", + "SCHEMAV_CVC_TYPE_1", + "SCHEMAV_CVC_TYPE_2", +#endif + + "XPTR_UNKNOWN_SCHEME", + "XPTR_CHILDSEQ_START", + "XPTR_EVAL_FAILED", + "XPTR_EXTRA_OBJECTS", + "C14N_CREATE_CTXT", + "C14N_REQUIRES_UTF8", + "C14N_CREATE_STACK", + "C14N_INVALID_NODE", + "FTP_PASV_ANSWER", + "FTP_EPSV_ANSWER", + "FTP_ACCNT", + "HTTP_URL_SYNTAX", + "HTTP_USE_IP", + "HTTP_UNKNOWN_HOST", + "SCHEMAP_SRC_SIMPLE_TYPE_1", + "SCHEMAP_SRC_SIMPLE_TYPE_2", + "SCHEMAP_SRC_SIMPLE_TYPE_3", + "SCHEMAP_SRC_SIMPLE_TYPE_4", + "SCHEMAP_SRC_RESOLVE", + "SCHEMAP_SRC_RESTRICTION_BASE_OR_SIMPLETYPE", + "SCHEMAP_SRC_LIST_ITEMTYPE_OR_SIMPLETYPE", + "SCHEMAP_SRC_UNION_MEMBERTYPES_OR_SIMPLETYPES", + "SCHEMAP_ST_PROPS_CORRECT_1", + "SCHEMAP_ST_PROPS_CORRECT_2", + "SCHEMAP_ST_PROPS_CORRECT_3", + "SCHEMAP_COS_ST_RESTRICTS_1_1", + "SCHEMAP_COS_ST_RESTRICTS_1_2", + "SCHEMAP_COS_ST_RESTRICTS_1_3_1", + "SCHEMAP_COS_ST_RESTRICTS_1_3_2", + "SCHEMAP_COS_ST_RESTRICTS_2_1", + "SCHEMAP_COS_ST_RESTRICTS_2_3_1_1", + "SCHEMAP_COS_ST_RESTRICTS_2_3_1_2", + "SCHEMAP_COS_ST_RESTRICTS_2_3_2_1", + "SCHEMAP_COS_ST_RESTRICTS_2_3_2_2", + "SCHEMAP_COS_ST_RESTRICTS_2_3_2_3", + "SCHEMAP_COS_ST_RESTRICTS_2_3_2_4", + "SCHEMAP_COS_ST_RESTRICTS_2_3_2_5", + "SCHEMAP_COS_ST_RESTRICTS_3_1", + "SCHEMAP_COS_ST_RESTRICTS_3_3_1", + "SCHEMAP_COS_ST_RESTRICTS_3_3_1_2", + "SCHEMAP_COS_ST_RESTRICTS_3_3_2_2", + "SCHEMAP_COS_ST_RESTRICTS_3_3_2_1", + "SCHEMAP_COS_ST_RESTRICTS_3_3_2_3", + "SCHEMAP_COS_ST_RESTRICTS_3_3_2_4", + "SCHEMAP_COS_ST_RESTRICTS_3_3_2_5", + "SCHEMAP_COS_ST_DERIVED_OK_2_1", + "SCHEMAP_COS_ST_DERIVED_OK_2_2", + "SCHEMAP_S4S_ELEM_NOT_ALLOWED", + "SCHEMAP_S4S_ELEM_MISSING", + "SCHEMAP_S4S_ATTR_NOT_ALLOWED", + "SCHEMAP_S4S_ATTR_MISSING", + +#if defined XML_SCHEMAP_S4S_ATTR_INVALID_VALUE + "SCHEMAP_S4S_ATTR_INVALID_VALUE", + "SCHEMAP_SRC_ELEMENT_1", + "SCHEMAP_SRC_ELEMENT_2_1", + "SCHEMAP_SRC_ELEMENT_2_2", + "SCHEMAP_SRC_ELEMENT_3", + "SCHEMAP_P_PROPS_CORRECT_1", + "SCHEMAP_P_PROPS_CORRECT_2_1", + "SCHEMAP_P_PROPS_CORRECT_2_2", + "SCHEMAP_E_PROPS_CORRECT_2", + "SCHEMAP_E_PROPS_CORRECT_3", + "SCHEMAP_E_PROPS_CORRECT_4", + "SCHEMAP_E_PROPS_CORRECT_5", + "SCHEMAP_E_PROPS_CORRECT_6", + "SCHEMAP_SRC_INCLUDE", + "SCHEMAP_SRC_ATTRIBUTE_1", + "SCHEMAP_SRC_ATTRIBUTE_2", + "SCHEMAP_SRC_ATTRIBUTE_3_1", + "SCHEMAP_SRC_ATTRIBUTE_3_2", + "SCHEMAP_SRC_ATTRIBUTE_4", + "SCHEMAP_NO_XMLNS", + "SCHEMAP_NO_XSI", + "SCHEMAP_COS_VALID_DEFAULT_1", + "SCHEMAP_COS_VALID_DEFAULT_2_1", + "SCHEMAP_COS_VALID_DEFAULT_2_2_1", + "SCHEMAP_COS_VALID_DEFAULT_2_2_2", + "SCHEMAP_CVC_SIMPLE_TYPE", + "SCHEMAP_COS_CT_EXTENDS_1_1", + "SCHEMAP_SRC_IMPORT_1_1", + "SCHEMAP_SRC_IMPORT_1_2", + "SCHEMAP_SRC_IMPORT_2", + "SCHEMAP_SRC_IMPORT_2_1", + "SCHEMAP_SRC_IMPORT_2_2", +#endif + +#if defined XML_SCHEMAP_INTERNAL + "SCHEMAP_INTERNAL", + "SCHEMAP_NOT_DETERMINISTIC", +#endif + +#if defined XML_SCHEMAP_SRC_ATTRIBUTE_GROUP_1 + "SCHEMAP_SRC_ATTRIBUTE_GROUP_1", + "SCHEMAP_SRC_ATTRIBUTE_GROUP_2", + "SCHEMAP_SRC_ATTRIBUTE_GROUP_3", + "SCHEMAP_MG_PROPS_CORRECT_1", + "SCHEMAP_MG_PROPS_CORRECT_2", + "SCHEMAP_SRC_CT_1", + "SCHEMAP_DERIVATION_OK_RESTRICTION_2_1_3", + "SCHEMAP_AU_PROPS_CORRECT_2", + "SCHEMAP_A_PROPS_CORRECT_2", +#endif + NULL +}; + +const char* SaxHandler::errToStr( int errVal ) +{ + const char* str = NULL; + int index = 0; + while ( errVal != xmlErrorVals[index] && xmlErrorVals[index] >= 0 ) + { + index++; + } + + if ( errVal == xmlErrorVals[index] ) + { + str = xmlErrorStrs[index]; + } + + return str; +} + + +int SaxHandler::parseMemory( const char* buffer, int size ) +{ + int result = xmlSAXUserParseMemory( &sax, + this, + buffer, + size ); + return result; +} + +int SaxHandler::parseFile( const char* filename ) +{ + int result = xmlSAXUserParseFile( &sax, + this, + filename ); + return result; +} + +void SaxHandler::startDocument(void *user_data) +{ + SaxHandler* self = reinterpret_cast(user_data); + self->_startDocument(); +} + +void SaxHandler::endDocument(void *user_data) +{ + SaxHandler* self = reinterpret_cast(user_data); + self->_endDocument(); +} +void SaxHandler::startElement(void *user_data, + const xmlChar *name, + const xmlChar **attrs) +{ + SaxHandler* self = reinterpret_cast(user_data); + self->_startElement(name, attrs); +} +void SaxHandler::endElement(void *user_data, + const xmlChar *name) +{ + SaxHandler* self = reinterpret_cast(user_data); + self->_endElement(name); +} +void SaxHandler::characters(void *user_data, + const xmlChar *ch, + int len) +{ + SaxHandler* self = reinterpret_cast(user_data); + self->_characters(ch, len); +} + + + + + + + +FlatSaxHandler::FlatSaxHandler() + : SaxHandler() +{ +} + +FlatSaxHandler::~FlatSaxHandler() +{ +} + +void FlatSaxHandler::_startElement(const xmlChar *name, const xmlChar **attrs) +{ + data.clear(); +} + +void FlatSaxHandler::_endElement(const xmlChar *name) +{ + //g_message("<%s>%s", name, data.c_str(), name); + data.clear(); +} + +void FlatSaxHandler::_characters(const xmlChar *ch, int len) +{ + data.append((const char*)ch, len); +} + + +} // namespace IO +} // namespace Inkscape + +/* + 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 : diff --git a/src/io/simple-sax.h b/src/io/simple-sax.h new file mode 100644 index 000000000..7de816a14 --- /dev/null +++ b/src/io/simple-sax.h @@ -0,0 +1,97 @@ +#ifndef SEEN_SIMPLE_SAX_H +#define SEEN_SIMPLE_SAX_H + +/* + * SimpleSAX + * + * Authors: + * Jon A. Cruz + * + * Copyright (C) 2004 AUTHORS + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include + +namespace Inkscape { +namespace IO +{ + +class SaxHandler +{ +public: + SaxHandler(); + virtual ~SaxHandler(); + + int parseMemory( const char* buffer, int size ); + int parseFile( const char* filename ); + + static const char* errToStr( int errVal ); + +protected: + virtual void _startDocument() {} + virtual void _endDocument() {} + virtual void _startElement(const xmlChar *name, const xmlChar **attrs) {} + virtual void _endElement(const xmlChar *name) {} + virtual void _characters(const xmlChar *ch, int len) {} + +private: + static void startDocument(void *user_data); + static void endDocument(void *user_data); + static void startElement(void *user_data, + const xmlChar *name, + const xmlChar **attrs); + static void endElement(void *user_data, + const xmlChar *name); + static void characters(void * user_data, + const xmlChar *ch, + int len); + + // Disable: + SaxHandler(SaxHandler const &); + SaxHandler &operator=(SaxHandler const &); + + xmlSAXHandler sax; +}; + + + +class FlatSaxHandler : public SaxHandler +{ +public: + FlatSaxHandler(); + virtual ~FlatSaxHandler(); + +protected: + virtual void _startElement(const xmlChar *name, const xmlChar **attrs); + virtual void _endElement(const xmlChar *name); + virtual void _characters(const xmlChar *ch, int len); + + Glib::ustring data; + +private: + // Disable: + FlatSaxHandler(FlatSaxHandler const &); + FlatSaxHandler &operator=(FlatSaxHandler const &); +}; + + + +} // namespace IO +} // namespace Inkscape + + +/* + 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 : + +#endif // SEEN_SIMPLE_SAX_H diff --git a/src/io/streamtest.cpp b/src/io/streamtest.cpp new file mode 100644 index 000000000..b25ef43f0 --- /dev/null +++ b/src/io/streamtest.cpp @@ -0,0 +1,244 @@ + + +#include +#include // realpath +#include // mkdtemp, realpath +#include // chdir +#include // strlen, strncpy, strrchr + +#include "inkscapestream.h" +#include "base64stream.h" +#include "gzipstream.h" +#include "stringstream.h" +#include "uristream.h" +#include "xsltstream.h" + +// quick way to pass the name of the executable into the test +char myself[PATH_MAX]; + +// names and path storage for other tests +char *xmlname = "crystalegg.xml"; +char xmlpath[PATH_MAX]; +char *xslname = "doc2html.xsl"; +char xslpath[PATH_MAX]; + +bool testUriStream() +{ + printf("######### UriStream copy ############\n"); + Inkscape::URI inUri(myself); + Inkscape::IO::UriInputStream ins(inUri); + Inkscape::URI outUri("streamtest.copy"); + Inkscape::IO::UriOutputStream outs(outUri); + + pipeStream(ins, outs); + + ins.close(); + outs.close(); + + return true; +} + +bool testWriter() +{ + printf("######### OutputStreamWriter ############\n"); + Inkscape::IO::StdOutputStream outs; + Inkscape::IO::OutputStreamWriter writer(outs); + + writer << "Hello, world! " << 123.45 << " times\n"; + + writer.printf("There are %f quick brown foxes in %d states\n", 123.45, 88); + + return true; +} + +bool testStdWriter() +{ + printf("######### StdWriter ############\n"); + Inkscape::IO::StdWriter writer; + + writer << "Hello, world! " << 123.45 << " times\n"; + + writer.printf("There are %f quick brown foxes in %d states\n", 123.45, 88); + + return true; +} + +bool testBase64() +{ + printf("######### Base64 Out ############\n"); + Inkscape::URI plainInUri(xmlpath); + Inkscape::IO::UriInputStream ins1(plainInUri); + + Inkscape::URI b64OutUri("crystalegg.xml.b64"); + Inkscape::IO::UriOutputStream outs1(b64OutUri); + Inkscape::IO::Base64OutputStream b64Outs(outs1); + + pipeStream(ins1, b64Outs); + + ins1.close(); + b64Outs.close(); + + printf("######### Base64 In ############\n"); + Inkscape::URI b64InUri("crystalegg.xml.b64"); + Inkscape::IO::UriInputStream ins2(b64InUri); + Inkscape::IO::Base64InputStream b64Ins(ins2); + + Inkscape::URI plainOutUri("crystalegg.xml.b64dec"); + Inkscape::IO::UriOutputStream outs2(plainOutUri); + + pipeStream(b64Ins, outs2); + + outs2.close(); + b64Ins.close(); + + return true; +} + +bool testXslt() +{ + printf("######### XSLT Sheet ############\n"); + Inkscape::URI xsltSheetUri(xslpath); + Inkscape::IO::UriInputStream xsltSheetIns(xsltSheetUri); + Inkscape::IO::XsltStyleSheet stylesheet(xsltSheetIns); + xsltSheetIns.close(); + + Inkscape::URI sourceUri(xmlpath); + Inkscape::IO::UriInputStream xmlIns(sourceUri); + + printf("######### XSLT Input ############\n"); + Inkscape::URI destUri("test.html"); + Inkscape::IO::UriOutputStream xmlOuts(destUri); + + Inkscape::IO::XsltInputStream xsltIns(xmlIns, stylesheet); + pipeStream(xsltIns, xmlOuts); + xsltIns.close(); + xmlOuts.close(); + + + printf("######### XSLT Output ############\n"); + + Inkscape::IO::UriInputStream xmlIns2(sourceUri); + + Inkscape::URI destUri2("test2.html"); + Inkscape::IO::UriOutputStream xmlOuts2(destUri2); + + Inkscape::IO::XsltOutputStream xsltOuts(xmlOuts2, stylesheet); + pipeStream(xmlIns2, xsltOuts); + xmlIns2.close(); + xsltOuts.close(); + + return true; +} + +bool testGzip() +{ + + printf("######### Gzip Output ############\n"); + Inkscape::URI gzUri("test.gz"); + Inkscape::URI sourceUri(xmlpath); + Inkscape::IO::UriInputStream sourceIns(sourceUri); + Inkscape::IO::UriOutputStream gzOuts(gzUri); + + Inkscape::IO::GzipOutputStream gzipOuts(gzOuts); + pipeStream(sourceIns, gzipOuts); + sourceIns.close(); + gzipOuts.close(); + + printf("######### Gzip Input ############\n"); + + Inkscape::IO::UriInputStream gzIns(gzUri); + Inkscape::URI destUri("crystalegg2.xml"); + Inkscape::IO::UriOutputStream destOuts(destUri); + + Inkscape::IO::GzipInputStream gzipIns(gzIns); + pipeStream(gzipIns, destOuts); + gzipIns.close(); + destOuts.close(); + + return true; +} + +bool doTest() +{ + if (!testUriStream()) + { + return false; + } + if (!testWriter()) + { + return false; + } + if (!testStdWriter()) + { + return false; + } + if (!testBase64()) + { + return false; + } + if (!testXslt()) + { + return false; + } + if (!testGzip()) + { + return false; + } + return true; +} + +void path_init(char *path, char *name) +{ + if (strlen(name)>PATH_MAX-strlen(myself)) + { + printf("merging paths would be too long\n"); + exit(1); + } + strncpy(path,myself,PATH_MAX); + char * ptr = strrchr(path,'/'); + if (!ptr) + { + printf("path '%s' is missing any slashes\n",path); + exit(1); + } + strncpy(ptr+1,name,strlen(name)+1); + printf("'%s'\n",path); +} + + +int main(int argc, char **argv) +{ + if (!realpath(argv[0],myself)) + { + perror("realpath"); + return 1; + } + path_init(xmlpath,xmlname); + path_init(xslpath,xslname); + + // create temp files somewhere else instead of current dir + // TODO: clean them up too + char * testpath = strdup("/tmp/streamtest-XXXXXX"); + testpath = mkdtemp(testpath); + if (!testpath) + { + perror("mkdtemp"); + return 1; + } + if (chdir(testpath)) + { + perror("chdir"); + return 1; + } + + if (!doTest()) + { + printf("#### Test failed\n"); + return 1; + } + else + { + printf("##### Test succeeded\n"); + } + return 0; +} diff --git a/src/io/stringstream.cpp b/src/io/stringstream.cpp new file mode 100644 index 000000000..45fb6fe30 --- /dev/null +++ b/src/io/stringstream.cpp @@ -0,0 +1,128 @@ +/** + * Our base String stream classes. We implement these to + * be based on Glib::ustring + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2004 Inkscape.org + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + +#include "stringstream.h" + +namespace Inkscape +{ +namespace IO +{ + + +//######################################################################### +//# S T R I N G I N P U T S T R E A M +//######################################################################### + + +/** + * + */ +StringInputStream::StringInputStream(Glib::ustring &sourceString) + : buffer(sourceString) +{ + position = 0; +} + +/** + * + */ +StringInputStream::~StringInputStream() +{ + +} + +/** + * Returns the number of bytes that can be read (or skipped over) from + * this input stream without blocking by the next caller of a method for + * this input stream. + */ +int StringInputStream::available() +{ + return buffer.size() - position; +} + + +/** + * Closes this input stream and releases any system resources + * associated with the stream. + */ +void StringInputStream::close() +{ +} + +/** + * Reads the next byte of data from the input stream. -1 if EOF + */ +int StringInputStream::get() +{ + if (position >= (int)buffer.size()) + return -1; + int ch = (int) buffer[position++]; + return ch; +} + + + + +//######################################################################### +//# S T R I N G O U T P U T S T R E A M +//######################################################################### + +/** + * + */ +StringOutputStream::StringOutputStream() +{ +} + +/** + * + */ +StringOutputStream::~StringOutputStream() +{ +} + +/** + * Closes this output stream and releases any system resources + * associated with this stream. + */ +void StringOutputStream::close() +{ +} + +/** + * Flushes this output stream and forces any buffered output + * bytes to be written out. + */ +void StringOutputStream::flush() +{ + //nothing to do +} + +/** + * Writes the specified byte to this output stream. + */ +void StringOutputStream::put(int ch) +{ + gunichar uch = (gunichar)ch; + buffer.push_back(uch); +} + + +} // namespace IO +} // namespace Inkscape + + +//######################################################################### +//# E N D O F F I L E +//######################################################################### diff --git a/src/io/stringstream.h b/src/io/stringstream.h new file mode 100644 index 000000000..4a05d88a9 --- /dev/null +++ b/src/io/stringstream.h @@ -0,0 +1,96 @@ +#ifndef __INKSCAPE_IO_STRINGSTREAM_H__ +#define __INKSCAPE_IO_STRINGSTREAM_H__ + +#include + +#include "inkscapestream.h" + + +namespace Inkscape +{ +namespace IO +{ + + +//######################################################################### +//# S T R I N G I N P U T S T R E A M +//######################################################################### + +/** + * This class is for reading character from a Glib::ustring + * + */ +class StringInputStream : public InputStream +{ + +public: + + StringInputStream(Glib::ustring &sourceString); + + virtual ~StringInputStream(); + + virtual int available(); + + virtual void close(); + + virtual int get(); + +private: + + Glib::ustring &buffer; + + long position; + +}; // class StringInputStream + + + + +//######################################################################### +//# S T R I N G O U T P U T S T R E A M +//######################################################################### + +/** + * This class is for sending a stream to a Glib::ustring + * + */ +class StringOutputStream : public OutputStream +{ + +public: + + StringOutputStream(); + + virtual ~StringOutputStream(); + + virtual void close(); + + virtual void flush(); + + virtual void put(int ch); + + virtual Glib::ustring &getString() + { return buffer; } + + virtual void clear() + { buffer = ""; } + +private: + + Glib::ustring buffer; + + +}; // class StringOutputStream + + + + + + + +} // namespace IO +} // namespace Inkscape + + + +#endif /* __INKSCAPE_IO_STRINGSTREAM_H__ */ diff --git a/src/io/sys.cpp b/src/io/sys.cpp new file mode 100644 index 000000000..89afe9fb4 --- /dev/null +++ b/src/io/sys.cpp @@ -0,0 +1,300 @@ + +/* + * System abstraction utility routines + * + * Authors: + * Jon A. Cruz + * + * Copyright (C) 2004-2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#if GLIB_CHECK_VERSION(2,6,0) + #include +#endif +#include +#include + +#include "prefs-utils.h" +#include "sys.h" + +#ifdef WIN32 + +// For now to get at is_os_wide(). +#include "extension/internal/win32.h" +using Inkscape::Extension::Internal::PrintWin32; + +#endif + +//#define INK_DUMP_FILENAME_CONV 1 +#undef INK_DUMP_FILENAME_CONV + +//#define INK_DUMP_FOPEN 1 +#undef INK_DUMP_FOPEN + +void dump_str(gchar const *str, gchar const *prefix); +void dump_ustr(Glib::ustring const &ustr); + +extern guint update_in_progress; + + +#define DEBUG_MESSAGE(key, ...) \ +{\ + gint dump = prefs_get_int_attribute_limited("options.bulia", #key, 0, 0, 1);\ + gint dumpD = prefs_get_int_attribute_limited("options.bulia", #key"D", 0, 0, 1);\ + gint dumpD2 = prefs_get_int_attribute_limited("options.bulia", #key"D2", 0, 0, 1);\ + dumpD &= ( (update_in_progress == 0) || dumpD2 );\ + if ( dump )\ + {\ + g_message( __VA_ARGS__ );\ +\ + }\ + if ( dumpD )\ + {\ + GtkWidget *dialog = gtk_message_dialog_new(NULL,\ + GTK_DIALOG_DESTROY_WITH_PARENT, \ + GTK_MESSAGE_INFO, \ + GTK_BUTTONS_OK, \ + __VA_ARGS__ \ + );\ + g_signal_connect_swapped(dialog, "response",\ + G_CALLBACK(gtk_widget_destroy), \ + dialog); \ + gtk_widget_show_all( dialog );\ + }\ +} + + + + +void Inkscape::IO::dump_fopen_call( char const *utf8name, char const *id ) +{ +#ifdef INK_DUMP_FOPEN + Glib::ustring str; + for ( int i = 0; utf8name[i]; i++ ) + { + if ( utf8name[i] == '\\' ) + { + str += "\\\\"; + } + else if ( (utf8name[i] >= 0x20) && ((0x0ff & utf8name[i]) <= 0x7f) ) + { + str += utf8name[i]; + } + else + { + gchar tmp[32]; + g_snprintf( tmp, sizeof(tmp), "\\x%02x", (0x0ff & utf8name[i]) ); + str += tmp; + } + } + g_message( "fopen call %s for [%s]", id, str.data() ); +#endif +} + +FILE *Inkscape::IO::fopen_utf8name( char const *utf8name, char const *mode ) +{ + static gint counter = 0; + FILE* fp = NULL; + + DEBUG_MESSAGE( dumpOne, "entering fopen_utf8name( '%s', '%s' )[%d]", utf8name, mode, (counter++) ); + +#ifndef WIN32 + DEBUG_MESSAGE( dumpOne, " STEP 0 ( '%s', '%s' )[%d]", utf8name, mode, (counter++) ); + gchar *filename = g_filename_from_utf8( utf8name, -1, NULL, NULL, NULL ); + if ( filename ) + { + DEBUG_MESSAGE( dumpOne, " STEP 1 ( '%s', '%s' )[%d]", utf8name, mode, (counter++) ); + fp = std::fopen(filename, mode); + DEBUG_MESSAGE( dumpOne, " STEP 2 ( '%s', '%s' )[%d]", utf8name, mode, (counter++) ); + g_free(filename); + DEBUG_MESSAGE( dumpOne, " STEP 3 ( '%s', '%s' )[%d]", utf8name, mode, (counter++) ); + filename = 0; + } +#else + Glib::ustring how( mode ); + how.append("b"); + DEBUG_MESSAGE( dumpOne, " calling is_os_wide() ( '%s', '%s' )[%d]", utf8name, mode, (counter++) ); + + fp = g_fopen(utf8name, how.c_str()); +#endif + + DEBUG_MESSAGE( dumpOne, "leaving fopen_utf8name( '%s', '%s' )[%d]", utf8name, mode, (counter++) ); + + return fp; +} + + +int Inkscape::IO::mkdir_utf8name( char const *utf8name ) +{ + static gint counter = 0; + int retval = -1; + + DEBUG_MESSAGE( dumpMk, "entering mkdir_utf8name( '%s' )[%d]", utf8name, (counter++) ); + +#ifndef WIN32 + DEBUG_MESSAGE( dumpMk, " STEP 0 ( '%s' )[%d]", utf8name, (counter++) ); + gchar *filename = g_filename_from_utf8( utf8name, -1, NULL, NULL, NULL ); + if ( filename ) + { + DEBUG_MESSAGE( dumpMk, " STEP 1 ( '%s' )[%d]", utf8name, (counter++) ); + retval = ::mkdir(filename, S_IRWXU | S_IRGRP | S_IXGRP); + DEBUG_MESSAGE( dumpMk, " STEP 2 ( '%s' )[%d]", utf8name, (counter++) ); + g_free(filename); + DEBUG_MESSAGE( dumpMk, " STEP 3 ( '%s' )[%d]", utf8name, (counter++) ); + filename = 0; + } +#else + DEBUG_MESSAGE( dumpMk, " calling is_os_wide() ( '%s' )[%d]", utf8name, (counter++) ); + + // Mode should be ingnored inside of glib on the way in + retval = g_mkdir( utf8name, 0 ); +#endif + + DEBUG_MESSAGE( dumpMk, "leaving mkdir_utf8name( '%s' )[%d]", utf8name, (counter++) ); + + return retval; +} + +bool Inkscape::IO::file_test( char const *utf8name, GFileTest test ) +{ + bool exists = false; + + if ( utf8name ) { + gchar *filename = NULL; + if (utf8name && !g_utf8_validate(utf8name, -1, NULL)) { + /* FIXME: Trying to guess whether or not a filename is already in utf8 is unreliable. + If any callers pass non-utf8 data (e.g. using g_get_home_dir), then change caller to + use simple g_file_test. Then add g_return_val_if_fail(g_utf_validate(...), false) + to beginning of this function. */ + filename = g_strdup(utf8name); + // Looks like g_get_home_dir isn't safe. + //g_warning("invalid UTF-8 detected internally. HUNT IT DOWN AND KILL IT!!!"); + } else { + filename = g_filename_from_utf8 ( utf8name, -1, NULL, NULL, NULL ); + } + if ( filename ) { + exists = g_file_test (filename, test); + g_free(filename); + filename = NULL; + } else { + g_warning( "Unable to convert filename in IO:file_test" ); + } + } + + return exists; +} + +/** Wrapper around g_dir_open, but taking a utf8name as first argument. */ +GDir * +Inkscape::IO::dir_open(gchar const *const utf8name, guint const flags, GError **const error) +{ + gchar *const opsys_name = g_filename_from_utf8(utf8name, -1, NULL, NULL, error); + if (opsys_name) { + GDir *ret = g_dir_open(opsys_name, flags, error); + g_free(opsys_name); + return ret; + } else { + return NULL; + } +} + +/** + * Like g_dir_read_name, but returns a utf8name (which must be freed, unlike g_dir_read_name). + * + * N.B. Skips over any dir entries that fail to convert to utf8. + */ +gchar * +Inkscape::IO::dir_read_utf8name(GDir *dir) +{ + for (;;) { + gchar const *const opsys_name = g_dir_read_name(dir); + if (!opsys_name) { + return NULL; + } + gchar *utf8_name = g_filename_to_utf8(opsys_name, -1, NULL, NULL, NULL); + if (utf8_name) { + return utf8_name; + } + } +} + + +gchar* Inkscape::IO::locale_to_utf8_fallback( const gchar *opsysstring, + gssize len, + gsize *bytes_read, + gsize *bytes_written, + GError **error ) +{ + gchar *result = NULL; + if ( opsysstring ) { + gchar *newFileName = g_locale_to_utf8( opsysstring, len, bytes_read, bytes_written, error ); + if ( newFileName ) { + if ( !g_utf8_validate(newFileName, -1, NULL) ) { + g_warning( "input filename did not yield UTF-8" ); + g_free( newFileName ); + } else { + result = newFileName; + } + newFileName = 0; + } else if ( g_utf8_validate(opsysstring, -1, NULL) ) { + // This *might* be a case that we want + // g_warning( "input failed filename->utf8, fell back to original" ); + // TODO handle cases when len >= 0 + result = g_strdup( opsysstring ); + } else { + gchar const *charset = 0; + g_get_charset(&charset); + g_warning( "input filename conversion failed for file with locale charset '%s'", charset ); + } + } + return result; +} + + +gchar* Inkscape::IO::sanitizeString( gchar const * str ) +{ + gchar *result = NULL; + if ( str ) { + if ( g_utf8_validate(str, -1, NULL) ) { + result = g_strdup(str); + } else { + guchar scratch[8]; + Glib::ustring buf; + guchar const *ptr = (guchar const*)str; + while ( *ptr ) + { + if ( *ptr == '\\' ) + { + buf.append("\\\\"); + } else if ( *ptr < 0x80 ) { + buf += (char)(*ptr); + } else { + g_snprintf((gchar*)scratch, sizeof(scratch), "\\x%02x", *ptr); + buf.append((const char*)scratch); + } + ptr++; + } + result = g_strdup(buf.c_str()); + } + } + return result; +} + +/* + 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/io/sys.h b/src/io/sys.h new file mode 100644 index 000000000..29c0ad96a --- /dev/null +++ b/src/io/sys.h @@ -0,0 +1,51 @@ +#ifndef SEEN_SYS_H +#define SEEN_SYS_H + +/* + * System abstraction utility routines + * + * Authors: + * Jon A. Cruz + * + * Copyright (C) 2004-2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#include +#include + +/*##################### +## U T I L I T Y +#####################*/ + +namespace Inkscape { +namespace IO { + +void dump_fopen_call( char const *utf8name, char const *id ); + +FILE *fopen_utf8name( char const *utf8name, char const *mode ); + +int mkdir_utf8name( char const *utf8name ); + +bool file_test( char const *utf8name, GFileTest test ); + +GDir *dir_open(gchar const *utf8name, guint flags, GError **error); + +gchar *dir_read_utf8name(GDir *dir); + +gchar* locale_to_utf8_fallback( const gchar *opsysstring, + gssize len, + gsize *bytes_read, + gsize *bytes_written, + GError **error ); + +gchar* sanitizeString( gchar const * str ); + +} +} + + +#endif // SEEN_SYS_H diff --git a/src/io/uristream.cpp b/src/io/uristream.cpp new file mode 100644 index 000000000..e06498d52 --- /dev/null +++ b/src/io/uristream.cpp @@ -0,0 +1,499 @@ +/** + * Our base String stream classes. We implement these to + * be based on Glib::ustring + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2004 Inkscape.org + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + +#include "uristream.h" +#include "sys.h" + +#ifdef WIN32 +// For now to get at is_os_wide(). +# include "extension/internal/win32.h" +using Inkscape::Extension::Internal::PrintWin32; +#endif + + +namespace Inkscape +{ +namespace IO +{ + +/* + * URI scheme types + */ +#define SCHEME_NONE 0 +#define SCHEME_FILE 1 +#define SCHEME_DATA 2 + +/* + * A temporary modification of Jon Cruz's portable fopen(). + * Simplified a bit, since we will always use binary +*/ + +#define FILE_READ 1 +#define FILE_WRITE 2 + +static FILE *fopen_utf8name( char const *utf8name, int mode ) +{ + FILE *fp = NULL; + if (!utf8name) + { + return NULL; + } + if (mode!=FILE_READ && mode!=FILE_WRITE) + { + return NULL; + } + +#ifndef WIN32 + gchar *filename = g_filename_from_utf8( utf8name, -1, NULL, NULL, NULL ); + if ( filename ) { + if (mode == FILE_READ) + fp = std::fopen(filename, "rb"); + else + fp = std::fopen(filename, "wb"); + g_free(filename); + } +#else + if ( PrintWin32::is_os_wide() ) { + gunichar2 *wideName = g_utf8_to_utf16( utf8name, -1, NULL, NULL, NULL ); + if ( wideName ) { + if (mode == FILE_READ) + fp = _wfopen( (wchar_t*)wideName, L"rb" ); + else + fp = _wfopen( (wchar_t*)wideName, L"wb" ); + g_free( wideName ); + } else { + gchar *safe = Inkscape::IO::sanitizeString(utf8name); + g_message("Unable to convert filename from UTF-8 to UTF-16 [%s]", safe); + g_free(safe); + } + } else { + gchar *filename = g_filename_from_utf8( utf8name, -1, NULL, NULL, NULL ); + if ( filename ) { + if (mode == FILE_READ) + fp = std::fopen(filename, "rb"); + else + fp = std::fopen(filename, "wb"); + g_free(filename); + } + } +#endif + + return fp; +} + + + +//######################################################################### +//# U R I I N P U T S T R E A M / R E A D E R +//######################################################################### + + +/** + * + */ +UriInputStream::UriInputStream(Inkscape::URI &source) + throw (StreamException): uri(source) +{ + //get information from uri + char *schemestr = (char *) uri.getScheme(); + scheme = SCHEME_FILE; + if (!schemestr || strncmp("file", schemestr, 4)==0) + scheme = SCHEME_FILE; + else if (strncmp("data", schemestr, 4)==0) + scheme = SCHEME_DATA; + //printf("in schemestr:'%s' scheme:'%d'\n", schemestr, scheme); + char *cpath = NULL; + + switch (scheme) { + + case SCHEME_FILE: + cpath = (char *) uri.toNativeFilename(); + //printf("in cpath:'%s'\n", cpath); + inf = fopen_utf8name(cpath, FILE_READ); + //inf = fopen(cpath, "rb"); + g_free(cpath); + if (!inf) { + Glib::ustring err = "UriInputStream cannot open file "; + err += cpath; + throw StreamException(err); + } + break; + + case SCHEME_DATA: + data = (unsigned char *) uri.getPath(); + //printf("in data:'%s'\n", data); + dataPos = 0; + dataLen = strlen((const char *)data); + break; + + } + closed = false; +} + +/** + * + */ +UriInputStream::UriInputStream(FILE *source, Inkscape::URI &uri) + throw (StreamException): inf(source), + uri(uri) +{ + scheme = SCHEME_FILE; + if (!inf) { + Glib::ustring err = "UriInputStream passed NULL"; + throw StreamException(err); + } + closed = false; +} + +/** + * + */ +UriInputStream::~UriInputStream() throw(StreamException) +{ + close(); +} + +/** + * Returns the number of bytes that can be read (or skipped over) from + * this input stream without blocking by the next caller of a method for + * this input stream. + */ +int UriInputStream::available() throw(StreamException) +{ + return 0; +} + + +/** + * Closes this input stream and releases any system resources + * associated with the stream. + */ +void UriInputStream::close() throw(StreamException) +{ + if (closed) + return; + + switch (scheme) { + + case SCHEME_FILE: + if (!inf) + return; + fflush(inf); + fclose(inf); + inf=NULL; + break; + + case SCHEME_DATA: + //do nothing + break; + + }//switch + + closed = true; +} + +/** + * Reads the next byte of data from the input stream. -1 if EOF + */ +int UriInputStream::get() throw(StreamException) +{ + int retVal = -1; + if (!closed) + { + switch (scheme) { + + case SCHEME_FILE: + if (!inf || feof(inf)) + { + retVal = -1; + } + else + { + retVal = fgetc(inf); + } + break; + + case SCHEME_DATA: + if (dataPos >= dataLen) + { + retVal = -1; + } + else + { + retVal = data[dataPos++]; + } + break; + }//switch + } + return retVal; +} + + + + + + +/** + * + */ +UriReader::UriReader(Inkscape::URI &uri) + throw (StreamException) +{ + inputStream = new UriInputStream(uri); +} + +/** + * + */ +UriReader::~UriReader() throw (StreamException) +{ + delete inputStream; +} + +/** + * + */ +int UriReader::available() throw(StreamException) +{ + return inputStream->available(); +} + +/** + * + */ +void UriReader::close() throw(StreamException) +{ + inputStream->close(); +} + +/** + * + */ +gunichar UriReader::get() throw(StreamException) +{ + gunichar ch = (gunichar)inputStream->get(); + return ch; +} + + +//######################################################################### +//# U R I O U T P U T S T R E A M / W R I T E R +//######################################################################### + +/** + * Temporary kludge + */ +UriOutputStream::UriOutputStream(FILE* fp, Inkscape::URI &destination) + throw (StreamException): closed(false), + ownsFile(false), + outf(fp), + uri(destination), + scheme(SCHEME_FILE) +{ + if (!outf) { + Glib::ustring err = "UriOutputStream given null file "; + throw StreamException(err); + } +} + +/** + * + */ +UriOutputStream::UriOutputStream(Inkscape::URI &destination) + throw (StreamException): closed(false), + ownsFile(true), + outf(NULL), + uri(destination), + scheme(SCHEME_FILE) +{ + //get information from uri + char *schemestr = (char *) uri.getScheme(); + if (!schemestr || strncmp("file", schemestr, 4)==0) + scheme = SCHEME_FILE; + else if (strncmp("data", schemestr, 4)==0) + scheme = SCHEME_DATA; + //printf("out schemestr:'%s' scheme:'%d'\n", schemestr, scheme); + char *cpath = NULL; + + switch (scheme) { + + case SCHEME_FILE: + cpath = (char *) uri.toNativeFilename(); + //printf("out path:'%s'\n", cpath); + outf = fopen_utf8name(cpath, FILE_WRITE); + //outf = fopen(cpath, "wb"); + g_free(cpath); + if (!outf) { + Glib::ustring err = "UriOutputStream cannot open file "; + err += cpath; + throw StreamException(err); + } + break; + + case SCHEME_DATA: + data = "data:"; + break; + + }//switch +} + + +/** + * + */ +UriOutputStream::~UriOutputStream() throw(StreamException) +{ + close(); +} + +/** + * Closes this output stream and releases any system resources + * associated with this stream. + */ +void UriOutputStream::close() throw(StreamException) +{ + if (closed) + return; + + switch (scheme) { + + case SCHEME_FILE: + if (!outf) + return; + fflush(outf); + if ( ownsFile ) + fclose(outf); + outf=NULL; + break; + + case SCHEME_DATA: + uri = URI(data.raw().c_str()); + break; + + }//switch + + closed = true; +} + +/** + * Flushes this output stream and forces any buffered output + * bytes to be written out. + */ +void UriOutputStream::flush() throw(StreamException) +{ + if (closed) + return; + + switch (scheme) { + + case SCHEME_FILE: + if (!outf) + return; + fflush(outf); + break; + + case SCHEME_DATA: + //nothing + break; + + }//switch + +} + +/** + * Writes the specified byte to this output stream. + */ +void UriOutputStream::put(int ch) throw(StreamException) +{ + if (closed) + return; + + unsigned char uch; + gunichar gch; + + switch (scheme) { + + case SCHEME_FILE: + if (!outf) + return; + uch = (unsigned char)(ch & 0xff); + fputc(uch, outf); + //fwrite(uch, 1, 1, outf); + break; + + case SCHEME_DATA: + gch = (gunichar) ch; + data.push_back(gch); + break; + + }//switch + +} + + + + + +/** + * + */ +UriWriter::UriWriter(Inkscape::URI &uri) + throw (StreamException) +{ + outputStream = new UriOutputStream(uri); +} + +/** + * + */ +UriWriter::~UriWriter() throw (StreamException) +{ + delete outputStream; +} + +/** + * + */ +void UriWriter::close() throw(StreamException) +{ + outputStream->close(); +} + +/** + * + */ +void UriWriter::flush() throw(StreamException) +{ + outputStream->flush(); +} + +/** + * + */ +void UriWriter::put(gunichar ch) throw(StreamException) +{ + int ich = (int)ch; + outputStream->put(ich); +} + + + + + +} // namespace IO +} // namespace Inkscape + + +//######################################################################### +//# E N D O F F I L E +//######################################################################### diff --git a/src/io/uristream.h b/src/io/uristream.h new file mode 100644 index 000000000..d62065976 --- /dev/null +++ b/src/io/uristream.h @@ -0,0 +1,173 @@ +#ifndef __INKSCAPE_IO_URISTREAM_H__ +#define __INKSCAPE_IO_URISTREAM_H__ +/** + * This should be the only way that we provide sources/sinks + * to any input/output stream. + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2004 Inkscape.org + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + +#include + +#include "inkscapestream.h" + + +namespace Inkscape +{ +namespace IO +{ + +//######################################################################### +//# U R I I N P U T S T R E A M / R E A D E R +//######################################################################### + +/** + * This class is for receiving a stream of data from a resource + * defined in a URI + */ +class UriInputStream : public InputStream +{ + +public: + UriInputStream(FILE *source, Inkscape::URI &uri) throw(StreamException); + + UriInputStream(Inkscape::URI &source) throw(StreamException); + + virtual ~UriInputStream() throw(StreamException); + + virtual int available() throw(StreamException); + + virtual void close() throw(StreamException); + + virtual int get() throw(StreamException); + +private: + + bool closed; + + FILE *inf; //for file: uris + unsigned char *data; //for data: uris + int dataPos; // current read position in data field + int dataLen; // length of data buffer + + Inkscape::URI &uri; + + int scheme; + +}; // class UriInputStream + + + + +/** + * This class is for receiving a stream of formatted data from a resource + * defined in a URI + */ +class UriReader : public BasicReader +{ + +public: + + UriReader(Inkscape::URI &source) throw(StreamException); + + virtual ~UriReader() throw(StreamException); + + virtual int available() throw(StreamException); + + virtual void close() throw(StreamException); + + virtual gunichar get() throw(StreamException); + +private: + + UriInputStream *inputStream; + +}; // class UriReader + + + +//######################################################################### +//# U R I O U T P U T S T R E A M / W R I T E R +//######################################################################### + +/** + * This class is for sending a stream to a destination resource + * defined in a URI + * + */ +class UriOutputStream : public OutputStream +{ + +public: + + UriOutputStream(FILE *fp, Inkscape::URI &destination) throw(StreamException); + + UriOutputStream(Inkscape::URI &destination) throw(StreamException); + + virtual ~UriOutputStream() throw(StreamException); + + virtual void close() throw(StreamException); + + virtual void flush() throw(StreamException); + + virtual void put(int ch) throw(StreamException); + +private: + + bool closed; + bool ownsFile; + + FILE *outf; //for file: uris + Glib::ustring data; //for data: uris + + Inkscape::URI &uri; + + int scheme; + +}; // class UriOutputStream + + + + + +/** + * This class is for sending a stream of formatted data to a resource + * defined in a URI + */ +class UriWriter : public BasicWriter +{ + +public: + + UriWriter(Inkscape::URI &source) throw(StreamException); + + virtual ~UriWriter() throw(StreamException); + + virtual void close() throw(StreamException); + + virtual void flush() throw(StreamException); + + virtual void put(gunichar ch) throw(StreamException); + +private: + + UriOutputStream *outputStream; + +}; // class UriReader + + + + + + +} // namespace IO +} // namespace Inkscape + + +#endif /* __INKSCAPE_IO_URISTREAM_H__ */ diff --git a/src/io/xsltstream.cpp b/src/io/xsltstream.cpp new file mode 100644 index 000000000..71619a51e --- /dev/null +++ b/src/io/xsltstream.cpp @@ -0,0 +1,220 @@ +/** + * XSL Transforming input and output classes + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2004 Inkscape.org + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + +#include "xsltstream.h" +#include "stringstream.h" +#include + + + + +namespace Inkscape +{ +namespace IO +{ + +//######################################################################### +//# X S L T S T Y L E S H E E T +//######################################################################### +/** + * + */ +XsltStyleSheet::XsltStyleSheet(InputStream &xsltSource) throw (StreamException) +{ + StringOutputStream outs; + pipeStream(xsltSource, outs); + std::string strBuf = outs.getString().raw(); + xmlDocPtr doc = xmlParseMemory(strBuf.c_str(), strBuf.size()); + stylesheet = xsltParseStylesheetDoc(doc); + //xmlFreeDoc(doc); +} + +/** + * + */ +XsltStyleSheet::~XsltStyleSheet() +{ + xsltFreeStylesheet(stylesheet); +} + + + +//######################################################################### +//# X S L T I N P U T S T R E A M +//######################################################################### + + +/** + * + */ +XsltInputStream::XsltInputStream(InputStream &xmlSource, XsltStyleSheet &sheet) + throw (StreamException) + : BasicInputStream(xmlSource), stylesheet(sheet) +{ + //Load the data + StringOutputStream outs; + pipeStream(source, outs); + std::string strBuf = outs.getString().raw(); + + //Do the processing + const char *params[1]; + params[0] = NULL; + xmlDocPtr srcDoc = xmlParseMemory(strBuf.c_str(), strBuf.size()); + xmlDocPtr resDoc = xsltApplyStylesheet(stylesheet.stylesheet, srcDoc, params); + xmlDocDumpFormatMemory(resDoc, &outbuf, &outsize, 1); + outpos = 0; + + //Free our mem + xmlFreeDoc(resDoc); + xmlFreeDoc(srcDoc); +} + +/** + * + */ +XsltInputStream::~XsltInputStream() throw (StreamException) +{ + xmlFree(outbuf); +} + +/** + * Returns the number of bytes that can be read (or skipped over) from + * this input stream without blocking by the next caller of a method for + * this input stream. + */ +int XsltInputStream::available() throw (StreamException) +{ + return outsize - outpos; +} + + +/** + * Closes this input stream and releases any system resources + * associated with the stream. + */ +void XsltInputStream::close() throw (StreamException) +{ + closed = true; +} + +/** + * Reads the next byte of data from the input stream. -1 if EOF + */ +int XsltInputStream::get() throw (StreamException) +{ + if (closed) + return -1; + if (outpos >= outsize) + return -1; + int ch = (int) outbuf[outpos++]; + return ch; +} + + + + + + +//######################################################################### +//# X S L T O U T P U T S T R E A M +//######################################################################### + +/** + * + */ +XsltOutputStream::XsltOutputStream(OutputStream &dest, XsltStyleSheet &sheet) + throw (StreamException) + : BasicOutputStream(dest), stylesheet(sheet) +{ + flushed = false; +} + +/** + * + */ +XsltOutputStream::~XsltOutputStream() throw (StreamException) +{ + //do not automatically close +} + +/** + * Closes this output stream and releases any system resources + * associated with this stream. + */ +void XsltOutputStream::close() throw (StreamException) +{ + flush(); + destination.close(); +} + +/** + * Flushes this output stream and forces any buffered output + * bytes to be written out. + */ +void XsltOutputStream::flush() throw (StreamException) +{ + if (flushed) + { + destination.flush(); + return; + } + + //Do the processing + xmlChar *resbuf; + int resSize; + const char *params[1]; + params[0] = NULL; + xmlDocPtr srcDoc = xmlParseMemory(outbuf.raw().c_str(), outbuf.size()); + xmlDocPtr resDoc = xsltApplyStylesheet(stylesheet.stylesheet, srcDoc, params); + xmlDocDumpFormatMemory(resDoc, &resbuf, &resSize, 1); + /* + xmlErrorPtr err = xmlGetLastError(); + if (err) + { + throw StreamException(err->message); + } + */ + + for (int i=0 ; i + * + * Copyright (C) 2004 Inkscape.org + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + +#include "inkscapestream.h" + +#include +#include + + +namespace Inkscape +{ +namespace IO +{ + +//######################################################################### +//# X S L T S T Y L E S H E E T +//######################################################################### +/** + * This is a container for reusing a loaded stylesheet + */ +class XsltStyleSheet +{ + +public: + + XsltStyleSheet(InputStream &source) throw (StreamException); + + ~XsltStyleSheet(); + + xsltStylesheetPtr stylesheet; + + +}; // class XsltStyleSheet + + +//######################################################################### +//# X S L T I N P U T S T R E A M +//######################################################################### + +/** + * This class is for transforming stream input by a given stylesheet + */ +class XsltInputStream : public BasicInputStream +{ + +public: + + XsltInputStream(InputStream &xmlSource, XsltStyleSheet &stylesheet) + throw (StreamException); + + virtual ~XsltInputStream() throw (StreamException); + + virtual int available() throw (StreamException); + + virtual void close() throw (StreamException); + + virtual int get() throw (StreamException); + + +private: + + XsltStyleSheet &stylesheet; + + xmlChar *outbuf; + int outsize; + int outpos; + +}; // class UriInputStream + + + + +//######################################################################### +//# X S L T O U T P U T S T R E A M +//######################################################################### + +/** + * This class is for transforming stream output by a given stylesheet + */ +class XsltOutputStream : public BasicOutputStream +{ + +public: + + XsltOutputStream(OutputStream &destination, XsltStyleSheet &stylesheet) + throw (StreamException); + + virtual ~XsltOutputStream() throw (StreamException); + + virtual void close() throw (StreamException); + + virtual void flush() throw (StreamException); + + virtual void put(int ch) throw (StreamException); + +private: + + XsltStyleSheet &stylesheet; + + Glib::ustring outbuf; + + bool flushed; + +}; // class UriOutputStream + + + +} // namespace IO +} // namespace Inkscape + + +#endif /* __INKSCAPE_IO_XSLTSTREAM_H__ */ diff --git a/src/isnan.h b/src/isnan.h new file mode 100644 index 000000000..cfae92fc7 --- /dev/null +++ b/src/isnan.h @@ -0,0 +1,57 @@ +#ifndef __ISNAN_H__ +#define __ISNAN_H__ + +/* + * Temporary fix for various misdefinitions of isnan(). + * isnan() is becoming undef'd in some .h files. + * #include this last in your .cpp file to get it right. + * + * The problem is that isnan and isfinite are part of C99 but aren't part of + * the C++ standard (which predates C99). + * + * Authors: + * Inkscape groupies and obsessive-compulsives + * + * Copyright (C) 2004 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + * + * 2005 modification hereby placed in public domain. Probably supercedes the 2004 copyright + * for the code itself. + */ + +#include +/* You might try changing the above to if you have problems. + * Whether you use math.h or cmath, you may need to edit the .cpp file + * and/or other .h files to use the same header file. + */ + +#if defined(__isnan) +# define isNaN(_a) (__isnan(_a)) /* MacOSX/Darwin definition < 10.4 */ +#elif defined(WIN32) || defined(_isnan) +# define isNaN(_a) (_isnan(_a)) /* Win32 definition */ +#elif defined(isnan) || defined(__FreeBSD__) +# define isNaN(_a) (isnan(_a)) /* GNU definition */ +#else +# define isNaN(_a) (std::isnan(_a)) +#endif +/* If the above doesn't work, then try (a != a). + * Also, please report a bug as per http://www.inkscape.org/report_bugs.php, + * giving information about what platform and compiler version you're using. + */ + + +#if defined(__isfinite) +# define isFinite(_a) (__isfinite(_a)) /* MacOSX/Darwin definition < 10.4 */ +#elif defined(isfinite) +# define isFinite(_a) (isfinite(_a)) +#else +# define isFinite(_a) (std::isfinite(_a)) +#endif +/* If the above doesn't work, then try (finite(_a) && !isNaN(_a)) or (!isNaN((_a) - (_a))). + * Also, please report a bug as per http://www.inkscape.org/report_bugs.php, + * giving information about what platform and compiler version you're using. + */ + + +#endif /* __ISNAN_H__ */ diff --git a/src/jabber_whiteboard/.cvsignore b/src/jabber_whiteboard/.cvsignore new file mode 100644 index 000000000..ec96903b9 --- /dev/null +++ b/src/jabber_whiteboard/.cvsignore @@ -0,0 +1,2 @@ +.deps +.dirstamp diff --git a/src/jabber_whiteboard/Makefile_insert b/src/jabber_whiteboard/Makefile_insert new file mode 100644 index 000000000..7e120c002 --- /dev/null +++ b/src/jabber_whiteboard/Makefile_insert @@ -0,0 +1,77 @@ +## Makefile.am fragment sourced by src/Makefile.am. +# +# Jabber whiteboard communication and Inkscape listener components +# Author: David Yip + +jabber_whiteboard/all: jabber_whiteboard/libjabber_whiteboard.a + +jabber_whiteboard/clean: + rm -f jabber_whiteboard/libjabber_whiteboard.a $(jabber_whiteboard_libjabber_whiteboard_a_OBJECTS) + +jabber_whiteboard_SOURCES = \ + jabber_whiteboard/buddy-list-manager.cpp \ + jabber_whiteboard/buddy-list-manager.h \ + jabber_whiteboard/callbacks.cpp \ + jabber_whiteboard/callbacks.h \ + jabber_whiteboard/chat-handler.cpp \ + jabber_whiteboard/chat-handler.h \ + jabber_whiteboard/connection-establishment.cpp \ + jabber_whiteboard/defines.h \ + jabber_whiteboard/deserializer.cpp \ + jabber_whiteboard/deserializer.h \ + jabber_whiteboard/error-codes.h \ + jabber_whiteboard/internal-constants.cpp \ + jabber_whiteboard/internal-constants.h \ + jabber_whiteboard/invitation-confirm-dialog.cpp \ + jabber_whiteboard/invitation-confirm-dialog.h \ + jabber_whiteboard/jabber-handlers.cpp \ + jabber_whiteboard/jabber-handlers.h \ + jabber_whiteboard/message-aggregator.cpp \ + jabber_whiteboard/message-aggregator.h \ + jabber_whiteboard/message-contexts.cpp \ + jabber_whiteboard/message-contexts.h \ + jabber_whiteboard/message-handler.cpp \ + jabber_whiteboard/message-handler.h \ + jabber_whiteboard/message-node.h \ + jabber_whiteboard/message-processors.cpp \ + jabber_whiteboard/message-processors.h \ + jabber_whiteboard/message-queue.cpp \ + jabber_whiteboard/message-queue.h \ + jabber_whiteboard/message-tags.cpp \ + jabber_whiteboard/message-tags.h \ + jabber_whiteboard/message-utilities.cpp \ + jabber_whiteboard/message-utilities.h \ + jabber_whiteboard/node-tracker.cpp \ + jabber_whiteboard/node-tracker-event-tracker.cpp \ + jabber_whiteboard/node-tracker-event-tracker.h \ + jabber_whiteboard/node-tracker.h \ + jabber_whiteboard/node-tracker-observer.h \ + jabber_whiteboard/node-utilities.cpp \ + jabber_whiteboard/node-utilities.h \ + jabber_whiteboard/pedrodom.cpp \ + jabber_whiteboard/pedrodom.h \ + jabber_whiteboard/pedroxmpp.cpp \ + jabber_whiteboard/pedroxmpp.h \ + jabber_whiteboard/serializer.cpp \ + jabber_whiteboard/serializer.h \ + jabber_whiteboard/session-file.cpp \ + jabber_whiteboard/session-file.h \ + jabber_whiteboard/session-file-player.cpp \ + jabber_whiteboard/session-file-player.h \ + jabber_whiteboard/session-file-selector.cpp \ + jabber_whiteboard/session-file-selector.h \ + jabber_whiteboard/session-manager.cpp \ + jabber_whiteboard/session-manager.h \ + jabber_whiteboard/tracker-node.h \ + jabber_whiteboard/typedefs.h \ + jabber_whiteboard/undo-stack-observer.cpp \ + jabber_whiteboard/undo-stack-observer.h + + +if WITH_INKBOARD +temp_whiteboard_files = $(jabber_whiteboard_SOURCES) +endif + +jabber_whiteboard_libjabber_whiteboard_a_SOURCES = \ + jabber_whiteboard/empty.cpp \ + $(temp_whiteboard_files) diff --git a/src/jabber_whiteboard/buddy-list-manager.cpp b/src/jabber_whiteboard/buddy-list-manager.cpp new file mode 100644 index 000000000..4cce2308c --- /dev/null +++ b/src/jabber_whiteboard/buddy-list-manager.cpp @@ -0,0 +1,84 @@ +/** + * Whiteboard session manager + * Buddy list management facility + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + +#include +#include + +#include "jabber_whiteboard/typedefs.h" +#include "jabber_whiteboard/buddy-list-manager.h" + +namespace Inkscape { + +namespace Whiteboard { + +BuddyListManager::BuddyListManager() { } +BuddyListManager::~BuddyListManager() { } + +void +BuddyListManager::addInsertListener(BuddyListListener listener) +{ + this->_sig_insert.connect(listener); +} + +void +BuddyListManager::addEraseListener(BuddyListListener listener) +{ + this->_sig_erase.connect(listener); +} + +void +BuddyListManager::insert(std::string& jid) +{ + this->_bl.insert(jid); + this->_sig_insert.emit(jid); +} + +void +BuddyListManager::erase(std::string& jid) +{ + this->_bl.erase(jid); + this->_sig_erase.emit(jid); +} + +BuddyList::iterator +BuddyListManager::begin() +{ + return this->_bl.begin(); +} + +BuddyList::iterator +BuddyListManager::end() +{ + return this->_bl.end(); +} + +BuddyList& +BuddyListManager::getList() +{ + return this->_bl; +} + +} + +} + +/* + 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 : diff --git a/src/jabber_whiteboard/buddy-list-manager.h b/src/jabber_whiteboard/buddy-list-manager.h new file mode 100644 index 000000000..fcd0bb688 --- /dev/null +++ b/src/jabber_whiteboard/buddy-list-manager.h @@ -0,0 +1,59 @@ +/** + * Whiteboard session manager + * Buddy list management facility + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __WHITEBOARD_BUDDYLIST_MANAGER_H__ +#define __WHITEBOARD_BUDDYLIST_MANAGER_H__ + +#include +#include +#include "jabber_whiteboard/typedefs.h" + +namespace Inkscape { + +namespace Whiteboard { + +class BuddyListManager { +public: + BuddyListManager(); + ~BuddyListManager(); + void insert(std::string& jid); + void erase(std::string& jid); + BuddyList::iterator begin(); + BuddyList::iterator end(); + + void addInsertListener(BuddyListListener listener); + void addEraseListener(BuddyListListener listener); + + BuddyList& getList(); +private: + BuddyList _bl; + BuddyListSignal _sig_insert; + BuddyListSignal _sig_erase; + +}; + +} + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/jabber_whiteboard/callbacks.cpp b/src/jabber_whiteboard/callbacks.cpp new file mode 100644 index 000000000..8216f33c6 --- /dev/null +++ b/src/jabber_whiteboard/callbacks.cpp @@ -0,0 +1,206 @@ +/** + * Whiteboard session manager + * Message dispatch devices and timeout triggers + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +extern "C" { +#include +} + +#include + +#include "message-stack.h" +#include "document.h" +#include "desktop-handles.h" + +#include "jabber_whiteboard/undo-stack-observer.h" +#include "jabber_whiteboard/jabber-handlers.h" +#include "jabber_whiteboard/defines.h" +#include "jabber_whiteboard/typedefs.h" +#include "jabber_whiteboard/session-manager.h" +#include "jabber_whiteboard/message-queue.h" +#include "jabber_whiteboard/message-handler.h" +#include "jabber_whiteboard/message-node.h" +#include "jabber_whiteboard/callbacks.h" + +namespace Inkscape { + +namespace Whiteboard { + +Callbacks::Callbacks(SessionManager* sm) : _sm(sm) +{ + this->_sd = this->_sm->session_data; +} + +Callbacks::~Callbacks() +{ +} + +bool +Callbacks::dispatchSendQueue() +{ + // If we're not in a whiteboard session, don't dispatch anything + if (!(this->_sd->status[IN_WHITEBOARD])) { + return false; + } + + // If the connection is not open, inform the user that an error has occurred + // and stop the queue + LmConnectionState state = lm_connection_get_state(this->_sd->connection); + + if (state != LM_CONNECTION_STATE_OPEN && state != LM_CONNECTION_STATE_AUTHENTICATED) { + SP_DT_MSGSTACK(this->_sm->desktop())->flash(Inkscape::INFORMATION_MESSAGE, _("Jabber connection lost.")); + return false; + } + + // If there's nothing to send, don't do anything + if (this->_sd->send_queue->empty()) { + return true; + } + + // otherwise, send out the first change + MessageNode* first = this->_sd->send_queue->first(); + + SP_DT_MSGSTACK(this->_sm->desktop())->flashF(Inkscape::NORMAL_MESSAGE, + ngettext("Sending message; %u message remaining in send queue.", + "Sending message; %u messages remaining in send queue.", + this->_sd->send_queue->size()), + this->_sd->send_queue->size()); + + if (this->_sd->send_queue->empty()) { + SP_DT_MSGSTACK(this->_sm->desktop())->flash(Inkscape::NORMAL_MESSAGE, _("Receive queue empty.")); + } + + switch (first->type()) { + case CHANGE_REPEATABLE: + case CHANGE_NOT_REPEATABLE: + case CHANGE_COMMIT: + case DOCUMENT_BEGIN: + case DOCUMENT_END: + this->_sm->sendMessage(first->type(), first->sequence(), first->message(), first->recipient().c_str(), first->chatroom()); + break; + default: + g_warning("MessageNode with unknown change type found in send queue; discarding message. This may lead to desynchronization!"); + break; + } + + this->_sd->send_queue->popFront(); + + return true; +} + +bool +Callbacks::dispatchReceiveQueue() +{ + CommitsQueue& rcq = this->_sd->recipients_committed_queue; + // See if we have any commits submitted. + if (!rcq.empty()) { + // Pick the first one off the queue. + ReceivedCommitEvent& committer = rcq.front(); + + // Find the commit event sender's receive queue. + ReceiveMessageQueue* rmq = this->_sd->receive_queues[committer]; + + if (rmq != NULL) { + if (!rmq->empty()) { + // Get the first message off the sender's receive queue. + MessageNode* msg = rmq->first(); + + // There are a few message change types that demand special processing; + // handle them here. + // + // TODO: clean this up. This should be a simple dispatching routine, + // and should not be performing operations like it's doing right now. + // These really should go into connection-establishment.cpp + // (although that should only happen after SessionManager itself + // is cleaned up). + switch(msg->type()) { + case CHANGE_COMMIT: + rcq.pop_front(); + break; + case DOCUMENT_BEGIN: + if (this->_sm->session_data->status[WAITING_TO_SYNC_TO_CHAT]) { + this->_sm->session_data->status.set(WAITING_TO_SYNC_TO_CHAT, 0); + this->_sm->session_data->status.set(SYNCHRONIZING_WITH_CHAT, 1); + } + break; + case DOCUMENT_END: + rcq.pop_front(); + if (this->_sm->session_data->status[SYNCHRONIZING_WITH_CHAT]) { + this->_sm->sendMessage(CONNECTED_SIGNAL, 0, "", this->_sm->session_data->recipient, true); + this->_sm->session_data->status.set(SYNCHRONIZING_WITH_CHAT, 0); + this->_sm->session_data->status.set(IN_CHATROOM, 1); + } else { + this->_sm->sendMessage(CONNECTED_SIGNAL, 0, "", msg->sender().c_str(), false); + } + break; + case CHANGE_REPEATABLE: + case CHANGE_NOT_REPEATABLE: + default: + break; + } + + + // Pass the message to the received change handler. + this->_sm->receiveChange(msg->message()); + SP_DT_MSGSTACK(this->_sm->desktop())->flashF(Inkscape::NORMAL_MESSAGE, + ngettext("Receiving change; %u change left to process.", + "Receiving change; %u changes left to process.", + rmq->size()), + rmq->size()); + + + // Register this message as the latest message received from this + // sender. + rmq->setLatestProcessedPacket(msg->sequence()); + + // Pop this message off the receive queue. + rmq->popFront(); + return true; + } else { + // This really shouldn't happen. + // If we have a commit request from a valid sender, there should + // be something in the receive queue to process. However, + // if a client is buggy or has managed to trick us into accepting + // a commit, we should handle the event gracefully. + g_warning("Processing commit, but no changes to commit were found; ignoring commit event."); + + // Remove this sender from the commit list. If they want to commit + // later, they can. + rcq.pop_front(); + return true; + } + } else { + // If the receive queue returned is NULL, then we don't know about + // this sender. Remove the sender from the commit list. + g_warning("Attempting to process commit from unknown sender; ignoring."); + rcq.pop_front(); + return true; + } + } else { + return true; + } +} + +} + +} + + +/* + 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 : diff --git a/src/jabber_whiteboard/callbacks.h b/src/jabber_whiteboard/callbacks.h new file mode 100644 index 000000000..7a41a4010 --- /dev/null +++ b/src/jabber_whiteboard/callbacks.h @@ -0,0 +1,79 @@ +/** + * Whiteboard session manager + * Message dispatch devices and timeout triggers + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __WHITEBOARD_CALLBACKS_H__ +#define __WHITEBOARD_CALLBACKS_H__ + +#include + +namespace Inkscape { + +namespace Whiteboard { + +class SessionManager; +class SessionData; + +/** + * Callback methods used in timers to dispatch MessageNodes from message queues. + */ +class Callbacks { +public: + /** + * Constructor. + * + * \param sm The SessionManager to associate with. + */ + Callbacks(SessionManager* sm); + ~Callbacks(); + + /** + * Dispatch a message from the send queue to the associated SessionManager object. + * + * The SessionManager object handles the task of actually sending out a Jabber message. + * + * \see Inkscape::Whiteboard::SessionManager::sendMessage + * \return Whether or not this callback should be called again by the timer routine. + */ + bool dispatchSendQueue(); + + /** + * Dispatch a message from the receive queue to the associated SessionManager object. + * + * The SessionManager object handles the task of actually processing a Jabber message. + * + * \see Inkscape::Whiteboard::SessionManager::receiveChange + * \return Whether or not this callback should be called again by the timer routine. + */ + bool dispatchReceiveQueue(); + +private: + SessionManager* _sm; + SessionData* _sd; +}; + + +} + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/jabber_whiteboard/chat-handler.cpp b/src/jabber_whiteboard/chat-handler.cpp new file mode 100644 index 000000000..bf7a2a31e --- /dev/null +++ b/src/jabber_whiteboard/chat-handler.cpp @@ -0,0 +1,249 @@ +/** + * Whiteboard session manager + * Chatroom message handler + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +//#include + +#include "message-stack.h" +#include "desktop-handles.h" +#include "document.h" + +#include "util/ucompose.hpp" + +#include "xml/node.h" + +#include "jabber_whiteboard/typedefs.h" +#include "jabber_whiteboard/message-utilities.h" +#include "jabber_whiteboard/message-queue.h" +#include "jabber_whiteboard/jabber-handlers.h" +#include "jabber_whiteboard/defines.h" +#include "jabber_whiteboard/session-manager.h" +#include "jabber_whiteboard/node-tracker.h" +#include "jabber_whiteboard/chat-handler.h" +#include "jabber_whiteboard/error-codes.h" + + +namespace Inkscape { + +namespace Whiteboard { + +ChatMessageHandler::ChatMessageHandler(SessionManager* sm) : _sm(sm) +{ + +} + +ChatMessageHandler::~ChatMessageHandler() +{ + +} + +LmHandlerResult +ChatMessageHandler::parse(LmMessage* message) +{ + // Retrieve the message type + LmMessageType mtype = lm_message_get_type(message); + + // Retrieve root node of message + LmMessageNode* root = lm_message_get_node(message); + if (root == NULL) { + g_warning("Received a chat message with NULL root node; discarding."); + return LM_HANDLER_RESULT_REMOVE_MESSAGE; + } + + LmMessageSubType msubtype; + + + msubtype = lm_message_get_sub_type(message); + + switch (mtype) { + case LM_MESSAGE_TYPE_MESSAGE: + switch(msubtype) { + case LM_MESSAGE_SUB_TYPE_ERROR: + { + LmMessageNode* error = lm_message_node_get_child(root, "error"); + if (error != NULL) { + this->_handleError(lm_message_node_get_attribute(error, "code")); + } + break; + } + case LM_MESSAGE_SUB_TYPE_GROUPCHAT: + { + // FIXME: We should be checking to see if we're in a room in the presence stanzas as indicated in + // but current versions of mu-conference + // don't broadcast presence in the correct order. + // + // Therefore we need to use some sort of hacked-up method to make this work. We listen for + // the sentinel value in a groupchat message -- currently it is '[username] INKBOARD-JOINED' -- + // and begin processing that way. + LmMessageNode* body = lm_message_node_get_child(root, "body"); + if (body != NULL) { + gchar const* val = lm_message_node_get_value(body); + if (strcmp(val, String::ucompose("%1 INKBOARD-JOINED", this->_sm->session_data->chat_handle).c_str()) == 0) { + return this->_finishConnection(); break; + } else { + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + break; + } + } else { + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + break; + } + } + + default: + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + break; + } + break; + case LM_MESSAGE_TYPE_PRESENCE: + // Retrieve the subtype. + switch (msubtype) { + case LM_MESSAGE_SUB_TYPE_ERROR: + { + g_warning("Could not connect to chatroom"); + // Extract error type + LmMessageNode* error = lm_message_node_get_child(root, "error"); + if (error != NULL) { + this->_handleError(lm_message_node_get_attribute(error, "code")); + } + // Reset status bits + this->_sm->session_data->status.set(CONNECTING_TO_CHAT, 0); + this->_sm->session_data->recipient = ""; + this->_sm->session_data->chat_handle = ""; + + return LM_HANDLER_RESULT_REMOVE_MESSAGE; + break; + } + + case LM_MESSAGE_SUB_TYPE_AVAILABLE: + { + // Extract the handle + // (see JEP-0045, section 6.3.3 - ) + Glib::ustring sender = lm_message_node_get_attribute(root, MESSAGE_FROM); + Glib::ustring chatter = sender.substr(sender.find_last_of('/') + 1, sender.length()); + if (chatter != this->_sm->session_data->chat_handle) { + this->_sm->session_data->chatters.insert(g_strdup(chatter.data())); + // Make a receive queue for this chatter + this->_sm->session_data->receive_queues[sender.raw()] = new ReceiveMessageQueue(this->_sm); + + } else { + // If the presence message is from ourselves, then we know that we + // have successfully entered the chatroom _and_ have received the entire room roster, + // and can therefore decide whether we need to synchronize with the rest of the room. + // (see JEP-0045, section 6.3.3 - ) + } + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + } + + case LM_MESSAGE_SUB_TYPE_UNAVAILABLE: + { + Glib::ustring sender = lm_message_node_get_attribute(root, MESSAGE_FROM); + Glib::ustring chatter = sender.substr(sender.find_last_of('/') + 1, sender.length()); + this->_sm->session_data->chatters.erase(chatter.data()); + + // Delete the message queue used by this sender + this->_sm->session_data->receive_queues.erase(sender.raw()); + + SP_DT_MSGSTACK(this->_sm->desktop())->flashF(Inkscape::INFORMATION_MESSAGE, _("%s has left the chatroom."), sender.c_str()); + } + + default: + // no idea what this message is; discard it + return LM_HANDLER_RESULT_REMOVE_MESSAGE; + break; + } + break; + default: + break; + } + + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +LmHandlerResult +ChatMessageHandler::_finishConnection() +{ + if (this->_sm->session_data->status[CONNECTING_TO_CHAT]) { + this->_sm->session_data->status.set(CONNECTING_TO_CHAT, 0); + + if (this->_sm->session_data->chatters.empty()) { + // We are the only one in the chatroom, so there is no + // need for synchronization + this->_sm->session_data->status.set(IN_CHATROOM, 1); + + // Populate node tracker + KeyToNodeMap newids; + NodeToKeyMap newnodes; + NewChildObjectMessageList newchildren; + + XML::Node* root = this->_sm->document()->rroot; + + this->_sm->setupInkscapeInterface(); + this->_sm->setupCommitListener(); + + for ( Inkscape::XML::Node *child = root->firstChild() ; child != NULL ; child = child->next() ) { + MessageUtilities::newObjectMessage(NULL, newids, newnodes, newchildren, this->_sm->node_tracker(), child, true); + } + + this->_sm->node_tracker()->put(newids, newnodes); + // this->_sm->node_tracker()->dump(); + + } else { + this->_sm->session_data->status.set(WAITING_TO_SYNC_TO_CHAT, 1); + // Send synchronization request to chatroom + this->_sm->sendMessage(CHATROOM_SYNCHRONIZE_REQUEST, 0, "", this->_sm->session_data->recipient, true); + } + } + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +void +ChatMessageHandler::_handleError(char const* errcode) +{ +// try { + unsigned int code = atoi(errcode); + +// unsigned int code = boost::lexical_cast< unsigned int >(errcode); + + Glib::ustring buf; + switch (code) { + case ErrorCodes::CHAT_HANDLE_IN_USE: + buf = String::ucompose(_("Nickname %1 is already in use. Please choose a different nickname."), this->_sm->session_data->chat_handle); + this->_sm->connectionError(buf); + break; + case ErrorCodes::SERVER_CONNECT_FAILED: + buf = _("An error was encountered while attempting to connect to the server."); + this->_sm->connectionError(buf); + break; + default: + break; + } +// } catch (boost::bad_lexical_cast&) { + +// } +} + +} + +} + +/* + 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 : diff --git a/src/jabber_whiteboard/chat-handler.h b/src/jabber_whiteboard/chat-handler.h new file mode 100644 index 000000000..f87c8142d --- /dev/null +++ b/src/jabber_whiteboard/chat-handler.h @@ -0,0 +1,63 @@ +/** + * Whiteboard session manager + * Chatroom message handler + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __WHITEBOARD_CHAT_HANDLER_H__ +#define __WHITEBOARD_CHAT_HANDLER_H__ + +extern "C" { +#include +} + +namespace Inkscape { + +namespace Whiteboard { + +class SessionManager; + + +// TODO: find some way to better integrate this with the rest of the message +// handling framework (i.e. message-processors.cpp, message-handler.cpp, +// message-contexts.cpp) +class ChatMessageHandler { +public: + ChatMessageHandler(SessionManager* sm); + ~ChatMessageHandler(); + + LmHandlerResult parse(LmMessage* message); + +private: + LmHandlerResult _finishConnection(); + void _handleError(char const* errcode); + + SessionManager* _sm; + + // noncopyable, nonassignable + ChatMessageHandler(ChatMessageHandler&); + void operator=(ChatMessageHandler&); +}; + +} + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/jabber_whiteboard/connection-establishment.cpp b/src/jabber_whiteboard/connection-establishment.cpp new file mode 100644 index 000000000..9382227fb --- /dev/null +++ b/src/jabber_whiteboard/connection-establishment.cpp @@ -0,0 +1,307 @@ +/** + * Whiteboard session manager + * Methods for establishing connections and notifying the user of events + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "util/ucompose.hpp" +#include +#include +#include + +#include "desktop.h" +#include "file.h" +#include "document.h" + +#include "xml/repr.h" + +#include "jabber_whiteboard/defines.h" +#include "jabber_whiteboard/typedefs.h" +#include "jabber_whiteboard/node-tracker.h" +#include "jabber_whiteboard/chat-handler.h" +#include "jabber_whiteboard/message-queue.h" +#include "jabber_whiteboard/session-manager.h" +#include "jabber_whiteboard/invitation-confirm-dialog.h" + +namespace Inkscape { + +namespace Whiteboard { + +void +SessionManager::sendRequestToUser(std::string const& recipientJID) +{ + /* + Glib::ustring doccopy; + if (document != NULL) { + doccopy = *document; + } + */ + this->session_data->status.set(WAITING_FOR_INVITE_RESPONSE, 1); + this->sendMessage(CONNECT_REQUEST_USER, 0, "", recipientJID.c_str(), false); +} + +void +SessionManager::sendRequestToChatroom(Glib::ustring const& server, Glib::ustring const& chatroom, Glib::ustring const& handle, Glib::ustring const& password) +{ + // We do not yet use the Basic MUC Protocol for connection establishment etc + // . The protocol we use is the + // old GroupChat system; extension to MUC is TODO + + // Compose room@service/nick string for "to" field + Glib::ustring dest = String::ucompose("%1@%2/%3", chatroom, server, handle); + LmMessage* presence_req = lm_message_new(dest.data(), LM_MESSAGE_TYPE_PRESENCE); + + // Add 'from' attribute + LmMessageNode* preq_root = lm_message_get_node(presence_req); + + lm_message_node_set_attribute(preq_root, "from", lm_connection_get_jid(this->session_data->connection)); + + // Add + // (Not anymore: we don't speak it! -- yipdw) + LmMessageNode* xmlns_node = lm_message_node_add_child(preq_root, "x", ""); + lm_message_node_set_attribute(xmlns_node, "xmlns", "http://jabber.org/protocol/muc/"); + + // If a password was supplied, add it to xmlns_node + if (password != NULL) { + lm_message_node_add_child(xmlns_node, "password", password.c_str()); + } + + // Create chat message handler and node tracker + if (!this->_myChatHandler) { + this->_myChatHandler = new ChatMessageHandler(this); + } + + // Flag ourselves as connecting to a chatroom (but not yet connected) + this->session_data->status.set(CONNECTING_TO_CHAT, 1); + // Send the message + GError *error = NULL; + if (!lm_connection_send(this->session_data->connection, presence_req, &error)) { + g_error("Presence message could not be sent to %s: %s", dest.data(), error->message); + this->session_data->status.set(CONNECTING_TO_CHAT, 0); + } + + this->session_data->chat_handle = handle; + this->session_data->chat_server = server; + this->session_data->chat_name = chatroom; + + this->setRecipient(String::ucompose("%1@%2", chatroom, server).data()); + + lm_message_unref(presence_req); +} + +void +SessionManager::sendConnectRequestResponse(char const* recipientJID, gboolean accepted_request) +{ + if (accepted_request == TRUE) { + this->setRecipient(recipientJID); + this->session_data->status.set(IN_WHITEBOARD, 1); + } + + this->sendMessage(CONNECT_REQUEST_RESPONSE_USER, accepted_request, "", recipientJID, false); +} + +// When this method is invoked, it means that the user has received an invitation from another peer +// to engage in a whiteboard session (i.e. 1:1 communication). The user may accept or reject this invitation. +void +SessionManager::receiveConnectRequest(gchar const* requesterJID) +{ + int x, y; + Gdk::ModifierType mt; + Gdk::Display::get_default()->get_pointer(x, y, mt); + + if (mt) { + // Attach a polling timeout + this->_notify_incoming_request = Glib::signal_timeout().connect(sigc::bind< 0 >(sigc::mem_fun(*this, &SessionManager::_pollReceiveConnectRequest), requesterJID), 50); + return; + } + + if (this->session_data->status[IN_WHITEBOARD]) { + this->sendMessage(ALREADY_IN_SESSION, 0, "", requesterJID, false); + } + + // Check to see if the user made any modifications to this document. If so, + // we want to give them the option of (1) letting us clear their document or (2) + // opening a new, blank document for the whiteboard session. + Glib::ustring primary = "" + String::ucompose(_("%1 has invited you to a whiteboard session."), requesterJID) + "\n\n"; + Glib::ustring title = String::ucompose(_("Incoming whiteboard invitation from %1"), requesterJID); + + if (sp_document_repr_root(this->_myDoc)->attribute("sodipodi:modified") == NULL) { + primary += String::ucompose(_("Do you wish to accept %1's whiteboard session invitation?"), requesterJID); + } else { + primary += String::ucompose(_("Would you like to accept %1's invitation in a new document window?\nAccepting the invitation in your current window will discard unsaved changes."), requesterJID); + } + + // Construct confirmation dialog + InvitationConfirmDialog dialog(primary); + + dialog.add_button(_("Accept invitation"), ACCEPT_INVITATION); + dialog.add_button(_("Decline invitation"), DECLINE_INVITATION); + dialog.add_button(_("Accept invitation in new document window"), ACCEPT_INVITATION_IN_NEW_WINDOW); + + bool undecided = true; + InvitationResponses resp = static_cast< InvitationResponses >(dialog.run()); + + while(undecided) { + if (resp == ACCEPT_INVITATION) { + undecided = false; + this->clearDocument(); + + // Create a receive queue for the initiator of this request + this->session_data->receive_queues[requesterJID] = new ReceiveMessageQueue(this); + + this->setupInkscapeInterface(); + if (dialog.useSessionFile()) { + this->session_data->sessionFile = dialog.getSessionFilePath(); + this->_tryToStartLog(); + } + this->sendConnectRequestResponse(requesterJID, TRUE); + + } else if (resp == ACCEPT_INVITATION_IN_NEW_WINDOW) { + SPDesktop* newdesktop = sp_file_new_default(); + if (newdesktop != NULL) { + undecided = false; + + // Swap desktops around + + // Destroy the new desktop's session manager and add this one in + delete newdesktop->_whiteboard_session_manager; + newdesktop->_whiteboard_session_manager = this; + + // Assign a new session manager to our old desktop + this->_myDesktop->_whiteboard_session_manager = new SessionManager(this->_myDesktop); + + // Reset our desktop and document pointers + this->setDesktop(newdesktop); + + // Prepare document and send acceptance notification + this->session_data->receive_queues[requesterJID] = new ReceiveMessageQueue(this); + this->clearDocument(); + this->setupInkscapeInterface(); + if (dialog.useSessionFile()) { + this->session_data->sessionFile = dialog.getSessionFilePath(); + this->_tryToStartLog(); + } + this->sendConnectRequestResponse(requesterJID, TRUE); + + } else { + // We could not create a new desktop; ask the user if she or he wants to + // replace the current document and accept the invitation, or reject the invitation. + // TRANSLATORS: %1 is a userid here + Glib::ustring msg = "" + String::ucompose(_("A new document window could not be opened for a whiteboard session with %1"), requesterJID) + ".\n\nWould you like to accept the whiteboard connection in the active document or refuse the invitation?"; + InvitationConfirmDialog replace_dialog(msg); + dialog.add_button(_("Accept invitation"), ACCEPT_INVITATION); + dialog.add_button(_("Decline invitation"), DECLINE_INVITATION); + resp = static_cast< InvitationResponses >(dialog.run()); + } + } else { + undecided = false; + this->sendMessage(CONNECT_REQUEST_REFUSED_BY_PEER, 0, "", requesterJID, false); + } + } +} + +// When this method is invoked, it means that the other peer +// has accepted our request. +void +SessionManager::receiveConnectRequestResponse(InvitationResponses response, std::string& sender) +{ + this->session_data->status.set(WAITING_FOR_INVITE_RESPONSE, 0); + + switch(response) { + case ACCEPT_INVITATION: + { + + // Create a receive queue for the other peer. + this->session_data->receive_queues[sender] = new ReceiveMessageQueue(this); + + KeyToNodeMap newids; + NodeToKeyMap newnodes; + this->_myTracker = new XMLNodeTracker(this); + this->setupInkscapeInterface(); + this->_tryToStartLog(); + this->resendDocument(this->session_data->recipient, newids, newnodes); + this->_myTracker->put(newids, newnodes); +// this->_myTracker->dump(); + this->setupCommitListener(); + break; + } + + case DECLINE_INVITATION: + { + // TRANSLATORS: %1 is the peer whom refused our invitation. + Glib::ustring primary = String::ucompose(_("The user %1 has refused your whiteboard invitation.\n\n"), sender); + + // TRANSLATORS: %1 is the peer whom refused our invitation, %2 is our Jabber identity. + Glib::ustring secondary = String::ucompose(_("You are still connected to a Jabber server as %2, and may send an invitation to %1 again, or you may send an invitation to a different user."), sender, lm_connection_get_jid(this->session_data->connection)); + + Gtk::MessageDialog dialog(primary + secondary, true, Gtk::MESSAGE_INFO, Gtk::BUTTONS_CLOSE, false); + dialog.run(); + break; + + } + + case PEER_ALREADY_IN_SESSION: + { + // TRANSLATORS: %1 is the peer whom we tried to contact, but is already in a whiteboard session. + Glib::ustring primary = String::ucompose(_("The user %1 is already in a whiteboard session.\n\n"), sender); + + // TRANSLATORS: %1 is the peer whom we tried to contact, but is already in a whiteboard session. + Glib::ustring secondary = String::ucompose(_("You are still connected to a Jabber server as %1, and may send an invitation to a different user."), sender); + Gtk::MessageDialog dialog(primary + secondary, true, Gtk::MESSAGE_INFO, Gtk::BUTTONS_CLOSE, false); + dialog.run(); + + break; + } + default: + break; + } +} + +void +SessionManager::receiveConnectRequestResponseChat(gchar const* recipient) +{ + // When responding to connection request responses in chatrooms, + // the responding user is already established in the whiteboard session. + // Therefore we do not need to perform any setup of observers or dispatchers; the requesting user + // will do that. + + KeyToNodeMap newids; + NodeToKeyMap newnodes; + this->resendDocument(recipient, newids, newnodes); +} + +bool +SessionManager::_pollReceiveConnectRequest(Glib::ustring const recipientJID) +{ + int x, y; + Gdk::ModifierType mt; + Gdk::Display::get_default()->get_pointer(x, y, mt); + + if (mt) { + return true; + } else { + this->receiveConnectRequest(recipientJID.c_str()); + return false; + } +} + +} + +} + +/* + 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 : diff --git a/src/jabber_whiteboard/defines.h b/src/jabber_whiteboard/defines.h new file mode 100644 index 000000000..3fc3c0e45 --- /dev/null +++ b/src/jabber_whiteboard/defines.h @@ -0,0 +1,98 @@ +/** + * Whiteboard session manager + * Definitions + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __WHITEBOARD_DEFINES_H__ +#define __WHITEBOARD_DEFINES_H__ + +#include "jabber_whiteboard/message-tags.h" +#include "jabber_whiteboard/internal-constants.h" + +namespace Inkscape { + +namespace Whiteboard { + +// message types +// explicitly numbered to aid protocol description later on +enum MessageType { + // image and internal data + CHANGE_NOT_REPEATABLE = 0, + CHANGE_REPEATABLE = 1, + DUMMY_CHANGE = 2, + CHANGE_COMMIT = 3, + DOCUMENT_BEGIN = 4, + DOCUMENT_END = 5, + // 1-1 connections + CONNECT_REQUEST_USER = 6, + CONNECT_REQUEST_RESPONSE_USER = 7, + // chat connections + CONNECT_REQUEST_RESPONSE_CHAT = 8, + // chatroom document synchronization + CHATROOM_SYNCHRONIZE_REQUEST = 9, + CHATROOM_SYNCHRONIZE_RESPONSE = 10, + // requests + DOCUMENT_SENDER_REQUEST = 11, + DOCUMENT_SENDER_REQUEST_RESPONSE = 12, + DOCUMENT_REQUEST = 13, + // notifications + CONNECTED_SIGNAL = 14, + DISCONNECTED_FROM_USER_SIGNAL = 15, + // error responses + CONNECT_REQUEST_REFUSED_BY_PEER = 16, + UNSUPPORTED_PROTOCOL_VERSION = 17, + ALREADY_IN_SESSION = 18, + + // error cases, i.e. garbled messages or bad clients. These should + // never actually be transmitted + UNKNOWN = 21 +}; + +// Responses to whiteboard invitations +enum InvitationResponses { + ACCEPT_INVITATION, + ACCEPT_INVITATION_IN_NEW_WINDOW, + DECLINE_INVITATION, + PEER_ALREADY_IN_SESSION +}; + +// Message handler modes +enum HandlerMode { + DEFAULT, + PRESENCE, + ERROR +}; + +// Actions to pass to the node tracker when we modify a node in +// the document tree upon event serialization +enum NodeTrackerAction { + NODE_ADD, + NODE_REMOVE, + NODE_UNKNOWN +}; + +} + +} + + + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/jabber_whiteboard/deserializer.cpp b/src/jabber_whiteboard/deserializer.cpp new file mode 100644 index 000000000..40047051e --- /dev/null +++ b/src/jabber_whiteboard/deserializer.cpp @@ -0,0 +1,417 @@ +/** + * Inkboard message -> XML::Event* deserializer + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "xml/log-builder.h" +#include "xml/event.h" +#include "xml/node.h" +#include "xml/repr.h" + +#include "util/shared-c-string-ptr.h" + +#include "gc-anchored.h" + +#include "jabber_whiteboard/message-utilities.h" +#include "jabber_whiteboard/typedefs.h" +#include "jabber_whiteboard/node-utilities.h" +#include "jabber_whiteboard/node-tracker.h" +#include "jabber_whiteboard/deserializer.h" +#include + +namespace Inkscape { + +namespace Whiteboard { + +void +Deserializer::deserializeEventAdd(Glib::ustring const& msg) +{ + // 1. Extract required attributes: parent, child, node name, and node type. + // If any of these cannot be found, return. + std::string parent, child, prev; + Glib::ustring name, type; + + struct Node buf; + + buf.tag = MESSAGE_PARENT; + if (!MessageUtilities::findTag(buf, msg)) { + return; + } + parent = buf.data.c_str(); + + buf.tag = MESSAGE_CHILD; + if (!MessageUtilities::findTag(buf, msg)) { + return; + } + child = buf.data.c_str(); + + KeyToNodeMap::iterator i = this->_newnodes.find(child); + if (i != this->_newnodes.end()) { + if (this->_node_action_tracker.getAction(i->second) == NODE_ADD) { + return; + } + } + + buf.tag = MESSAGE_NAME; + if (!MessageUtilities::findTag(buf, msg)) { + return; + } else { + name = buf.data; + } + + buf.tag = MESSAGE_NODETYPE; + if (!MessageUtilities::findTag(buf, msg)) { + return; + } else { + type = buf.data; + } + + // 2. Extract optional attributes: the node previous to the new child node. + buf.tag = MESSAGE_REF; + if (MessageUtilities::findTag(buf, msg)) { + prev = buf.data.c_str(); + } + + // 3. Check if the received child node is a special node. If it is, and we are already + // tracking it, then we should not add the node. + if (this->_xnt->isSpecialNode(name)) { + if (this->_xnt->isTracking(this->_xnt->getSpecialNodeKeyFromName(name))) { + return; + } + } + + // 4. Look up the parent node. If we cannot find it, return. + XML::Node* parentRepr = this->_getNodeByID(parent); + if (parentRepr == NULL) { + g_warning("Cannot find parent node identified by %s", parent.c_str()); + return; + } + + // 5. Look up the node previous to the child, if it exists. + // If we cannot find it, we may be in a change conflict situation. + // In that case, just append the node. + XML::Node* prevRepr = NULL; + if (!prev.empty()) { + prevRepr = this->_getNodeByID(prev); + if (prevRepr == NULL) { + g_warning("Prev node %s could not be found; appending incoming node. Document may not be synchronized.", prev.c_str()); + prevRepr = parentRepr->lastChild(); + } + } + + if (prevRepr) { + if (prevRepr->parent() != parentRepr) { + if (this->_parent_child_map[prevRepr] != parentRepr) { + g_warning("ref mismatch on node %s: child=%s ref=%p parent=%p ref->parent()=%p", prev.c_str(), child.c_str(), prevRepr, parentRepr, prevRepr->parent()); + g_warning("parent_child_map[%p (%s)] = %p (%s)", prevRepr, this->_xnt->get(*prevRepr).c_str(), this->_parent_child_map[prevRepr], this->_xnt->get(*(this->_parent_child_map[prevRepr])).c_str()); + return; + } + } + } + + XML::Node* childRepr = NULL; + + // 6. Create the child node. + + switch (NodeUtilities::stringToNodeType(type)) { + case XML::TEXT_NODE: + buf.tag = MESSAGE_CONTENT; + if (!MessageUtilities::findTag(buf, msg)) { + childRepr = sp_repr_new_text(""); + } else { + childRepr = sp_repr_new_text(buf.data.c_str()); + } + break; + case XML::DOCUMENT_NODE: + // TODO + case XML::COMMENT_NODE: + buf.tag = MESSAGE_CONTENT; + if (!MessageUtilities::findTag(buf, msg)) { + childRepr = sp_repr_new_comment(""); + } else { + childRepr = sp_repr_new_comment(buf.data.c_str()); + } + break; + case XML::ELEMENT_NODE: + default: + childRepr = sp_repr_new(name.data()); + break; + } + + + this->_actions.push_back(SerializedEventNodeAction(KeyNodePair(child, childRepr), NODE_ADD)); + this->_newnodes[child] = childRepr; + this->_newkeys[childRepr] = child; + + + // 8. Deserialize the event. + this->_builder.addChild(*parentRepr, *childRepr, prevRepr); + this->_parent_child_map.erase(childRepr); + this->_parent_child_map[childRepr] = parentRepr; + this->_addOneEvent(this->_builder.detach()); + Inkscape::GC::release(childRepr); +} + +void +Deserializer::deserializeEventDel(Glib::ustring const& msg) +{ + // 1. Extract required attributes: parent, child. If we do not know these, + // return. + std::string parent, child, prev; + + struct Node buf; + + buf.tag = MESSAGE_PARENT; + if (!MessageUtilities::findTag(buf, msg)) { + return; + } + parent = buf.data.c_str(); + + buf.tag = MESSAGE_CHILD; + if (!MessageUtilities::findTag(buf, msg)) { + return; + } + child = buf.data.c_str(); + + KeyToNodeMap::iterator i = this->_newnodes.find(child); + if (i != this->_newnodes.end()) { + if (this->_node_action_tracker.getAction(i->second) == NODE_REMOVE) { + return; + } + } + + // 2. Extract optional attributes: previous node. + buf.tag = MESSAGE_REF; + if (MessageUtilities::findTag(buf, msg)) { + prev = buf.data.c_str(); + } + + // 3. Retrieve nodes. If we cannot find all nodes involved, return. + XML::Node* parentRepr = this->_getNodeByID(parent); + XML::Node* childRepr = this->_getNodeByID(child); + XML::Node* prevRepr = NULL; + if (!prev.empty()) { + prevRepr = this->_getNodeByID(prev); + if (prevRepr == NULL) { + return; + } + } + + // 4. Deserialize the event. + if (parentRepr && childRepr) { + if (childRepr->parent() == parentRepr || this->_parent_child_map[childRepr] == parentRepr) { +// this->_actions.push_back(SerializedEventNodeAction(KeyNodePair(child, childRepr), NODE_REMOVE)); + this->_builder.removeChild(*parentRepr, *childRepr, prevRepr); + this->_parent_child_map.erase(childRepr); + this->_addOneEvent(this->_builder.detach()); + + // 5. Mark the removed node and all its children for removal from the tracker. + this->_recursiveMarkForRemoval(childRepr); + } else { + g_warning("child->parent() == parent mismatch on child=%s (%p), parent=%s: parent=%p child->parent()=%p", child.c_str(), childRepr, parent.c_str(), parentRepr, childRepr->parent()); + g_warning("parent_child_map[%p] = %p", childRepr, this->_parent_child_map[childRepr]); + } + } else { + g_warning("Missing parentRepr and childRepr: parent=%p, child=%p", parentRepr, childRepr); + } +} + +void +Deserializer::deserializeEventChgOrder(Glib::ustring const& msg) +{ + // 1. Extract required attributes: node ID, parent ID, new previous node ID. + // If we do not know these, return. + std::string id, parentid, oldprevid, newprevid; + Node buf; + + buf.tag = MESSAGE_ID; + if (MessageUtilities::findTag(buf, msg)) { + id = buf.data.raw(); + } else { + return; + } + + buf.tag = MESSAGE_PARENT; + if (MessageUtilities::findTag(buf, msg)) { + parentid = buf.data.raw(); + } else { + return; + } + + // 2. Extract optional attributes: old previous node, new previous node. + buf.tag = MESSAGE_OLDVAL; + if (MessageUtilities::findTag(buf, msg)) { + oldprevid = buf.data.raw(); + } + + buf.tag = MESSAGE_NEWVAL; + if (MessageUtilities::findTag(buf, msg)) { + newprevid = buf.data.raw(); + } else { + return; + } + + // 3. Find the identified nodes. If we do not know about the parent and child, return. + XML::Node* node = this->_getNodeByID(id); + XML::Node* parent = this->_getNodeByID(parentid); + XML::Node* oldprev = NULL; + XML::Node* newprev = NULL; + if (!oldprevid.empty()) { + oldprev = this->_getNodeByID(oldprevid); + } + + if (!newprevid.empty()) { + newprev = this->_getNodeByID(newprevid); + } + + if (parent && node) { + // 4. Deserialize the event. + this->_builder.setChildOrder(*parent, *node, oldprev, newprev); + this->_addOneEvent(this->_builder.detach()); + } else { + return; + } +} + +void +Deserializer::deserializeEventChgContent(Glib::ustring const& msg) +{ + // 1. Extract required attributes: node ID. If we do not know these, return. + std::string id; + Util::SharedCStringPtr oldval, newval; + Node buf; + + buf.tag = MESSAGE_ID; + if (!MessageUtilities::findTag(buf, msg)) { + return; + } else { + id = buf.data.raw(); + } + + // 2. Extract optional attributes: old value, new value. + buf.tag = MESSAGE_OLDVAL; + if (MessageUtilities::findTag(buf, msg)) { + oldval = Util::SharedCStringPtr::copy(buf.data.c_str()); + } else { + oldval = Util::SharedCStringPtr::copy(""); + } + + buf.tag = MESSAGE_NEWVAL; + if (MessageUtilities::findTag(buf, msg)) { + newval = Util::SharedCStringPtr::copy(buf.data.c_str()); + } else { + newval = Util::SharedCStringPtr::copy(""); + } + + // 3. Find the node identified by the ID. If we cannot find it, return. + XML::Node* node = this->_getNodeByID(id); + if (node == NULL) { + return; + } + + // 4. Deserialize the event. + this->_builder.setContent(*node, oldval, newval); + this->_addOneEvent(this->_builder.detach()); +} + +void +Deserializer::deserializeEventChgAttr(Glib::ustring const& msg) +{ + // 1. Extract required attributes: node ID, attribute key. If we do not know these, + // return. + + struct Node buf; + buf.tag = MESSAGE_ID; + if (!MessageUtilities::findTag(buf, msg)) { + return; + } + + std::string id = buf.data.data(); + + buf.tag = MESSAGE_KEY; + if (!MessageUtilities::findTag(buf, msg)) { + return; + } + + Glib::ustring key = buf.data; + + // 2. Extract optional attributes: new value. If we do not find it in the message, + // assume there is no new value. + buf.tag = MESSAGE_NEWVAL; + Util::SharedCStringPtr newval; + if (MessageUtilities::findTag(buf, msg)) { + newval = Util::SharedCStringPtr::copy(buf.data.c_str()); + } else { + newval = Util::SharedCStringPtr::copy(""); + } + + // 3. Extract optional attributes: old value. If we do not find it in the message, + // assume that there is no old value. + buf.tag = MESSAGE_OLDVAL; + Util::SharedCStringPtr oldval; + if (MessageUtilities::findTag(buf, msg)) { + oldval = Util::SharedCStringPtr::copy(buf.data.c_str()); + } else { + oldval = Util::SharedCStringPtr::copy(""); + } + + // 4. Look up this node in the local node database and external tracker. + // If it cannot be found, return. + XML::Node* node = this->_getNodeByID(id); + if (node == NULL) { + g_warning("Could not find node %s on which to change attribute", id.c_str()); + return; + } + + // 5. If this node is in the actions queue and is marked as "new", we need to apply + // _all_ received attributes to it _before_ adding it to the document tree. + if (this->_newnodes.find(id) != this->_newnodes.end()) { + node->setAttribute(key.c_str(), newval.cString()); + } + + // 6. Deserialize the event. + this->_builder.setAttribute(*node, g_quark_from_string(key.c_str()), oldval, newval); + this->_updated.insert(node); + this->_addOneEvent(this->_builder.detach()); +} + +void +Deserializer::_recursiveMarkForRemoval(XML::Node* node) +{ + if (node != NULL) { + NodeToKeyMap::iterator i = this->_newkeys.find(node); + if (i == this->_newkeys.end()) { + std::string id = this->_xnt->get(*node); + if (!id.empty()) { + this->_actions.push_back(SerializedEventNodeAction(KeyNodePair(id, node), NODE_REMOVE)); + } + } else { + this->_actions.push_back(SerializedEventNodeAction(KeyNodePair((*i).second, node), NODE_REMOVE)); + } + + for (XML::Node* child = node->firstChild(); child; child = child->next()) { + this->_recursiveMarkForRemoval(child); + } + } +} + +} + +} + +/* + 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 : diff --git a/src/jabber_whiteboard/deserializer.h b/src/jabber_whiteboard/deserializer.h new file mode 100644 index 000000000..715ca4c66 --- /dev/null +++ b/src/jabber_whiteboard/deserializer.h @@ -0,0 +1,285 @@ +/** + * Inkboard message -> XML::Event* deserializer + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __WHITEBOARD_MESSAGE_DESERIALIZER_H__ +#define __WHITEBOARD_MESSAGE_DESERIALIZER_H__ + +#include "xml/log-builder.h" +#include "xml/event.h" + +#include "jabber_whiteboard/node-tracker-event-tracker.h" +#include "jabber_whiteboard/node-tracker.h" +#include "jabber_whiteboard/typedefs.h" + +#include +#include +#include + +namespace Inkscape { + +namespace Whiteboard { + +/** + * A stateful XML::Event deserializer. + * + * The Deserializer class is meant to deserialize XML::Events serialized by + * Inkscape::Whiteboard::Serializer or a serializer that serializes + * XML::Events into the same format. + * + * Usage is as follows: + *
    + *
  1. For each serialized event called, call the appropriate deserialization method.
  2. + *
  3. Detach the deserialized event.
  4. + *
+ * + * The deserializer does not actually modify any aspect of the document or node-tracking systems. + * Methods are provided to provide the information necessary to perform the modifications outside + * of the deserializer. + */ +class Deserializer { +public: + /** + * Constructor. + * + * \param xnt The XMLNodeTracker that a Deserializer should use for retrieving + * XML::Nodes based on string keys. + */ + Deserializer(XMLNodeTracker* xnt) : _xnt(xnt) + { + this->clearEventLog(); + } + + ~Deserializer() { } + + /** + * Deserialize a node add event. + * + * \see XML::EventAdd + * \param msg The message that describes the event. + */ + void deserializeEventAdd(Glib::ustring const& msg); + + /** + * Deserialize a node remove event. + * + * \see XML::EventDel + * \param msg The message that describes the event. + */ + void deserializeEventDel(Glib::ustring const& msg); + + /** + * Deserialize a node order change event. + * + * \see XML::EventChgOrder + * \param msg The message that describes the event. + */ + void deserializeEventChgOrder(Glib::ustring const& msg); + + /** + * Deserialize a node content change event. + * + * \see XML::EventChgContent + * \param msg The message that describes the event. + */ + void deserializeEventChgContent(Glib::ustring const& msg); + + /** + * Deserialize a node attribute change event. + * + * \see XML::EventChgAttr + * \param msg The message that describes the event. + */ + void deserializeEventChgAttr(Glib::ustring const& msg); + + /** + * Retrieve the deserialized event log. + * This method does not clear the internal event log kept by the deserializer. + * To do that, use detachEventLog. + * + * \return The deserialized event log. + */ + XML::Event* getEventLog() + { + return this->_log; + } + + /** + * Retrieve the deserialized event log and clear the internal event log kept by the deserializer. + * + * \return The deserialized event log. + */ + XML::Event* detachEventLog() + { + XML::Event* ret = this->_log; + this->clearEventLog(); + return ret; + } + + /** + * Clear the internal event log. + */ + void clearEventLog() + { + g_log(NULL, G_LOG_LEVEL_DEBUG, "Clearing event log"); + this->_log = NULL; + } + + /** + * Retrieve a list of node entry actions (add node entry, remove node entry) + * that need to be performed on the XMLNodeTracker. + * + * Because this method returns a reference to a list, it is not safe for use + * across multiple invocations of this Deserializer. + * + * \return A reference to a list of node entry actions generated while deserializing. + */ + KeyToNodeActionList& getNodeTrackerActions() + { + return this->_actions; + } + + /** + * Retrieve a list of node entry actions (add node entry, remove node entry) + * that need to be performed on the XMLNodeTracker. + * + * \return A list of node entry actions generated while deserializing. + */ + KeyToNodeActionList getNodeTrackerActionsCopy() + { + return this->_actions; + } + + /** + * Retrieve a set of nodes for which an EventChgAttr was deserialized. + * + * For some actions (i.e. text tool) it is necessary to call updateRepr() on + * the updated nodes. This method provides the information required to perform + * that action. + * + * Because this method returns a reference to a set, it is not safe for use + * across multiple invocations of this Deserializer. + * + * \return A reference to a set of nodes for which an EventChgAttr was deserialized. + */ + AttributesUpdatedSet& getUpdatedAttributeNodeSet() + { + return this->_updated; + } + + /** + * Retrieve a set of nodes for which an EventChgAttr was deserialized. + * + * For some actions (i.e. text tool) it is necessary to call updateRepr() on + * the updated nodes. This method provides the information required to perform + * that action. + * + * \return A set of nodes for which an EventChgAttr was deserialized. + */ + AttributesUpdatedSet getUpdatedAttributeNodeSetCopy() + { + return this->_updated; + } + + /** + * Clear all internal node buffers. + */ + void clearNodeBuffers() + { + g_log(NULL, G_LOG_LEVEL_DEBUG, "Clearing deserializer node buffers"); + this->_newnodes.clear(); + this->_actions.clear(); + this->_newkeys.clear(); + this->_parent_child_map.clear(); + this->_updated.clear(); + } + + /** + * Clear all internal state. + */ + void reset() + { + this->clearEventLog(); + this->clearNodeBuffers(); + } + +private: + XML::Node* _getNodeByID(std::string const& id) + { + KeyToNodeMap::iterator i = this->_newnodes.find(id); + if (i != this->_newnodes.end()) { + return const_cast< XML::Node* >(i->second); + } else { + if (this->_xnt->isTracking(id)) { + return this->_xnt->get(id); + } else { + return NULL; + } + } + } + + void _addOneEvent(XML::Event* event) + { + if (this->_log == NULL) { + this->_log = event; + } else { + event->next = this->_log; + this->_log = event; + } + } + + void _recursiveMarkForRemoval(XML::Node* node); + + // internal states with accessors: + + // node tracker actions (add node, remove node) + KeyToNodeActionList _actions; + + // nodes that have had their attributes updated + AttributesUpdatedSet _updated; + + // the deserialized event log + XML::Event* _log; + + + // for internal use: + + // These maps only store information on a single node. That's fine, though; + // all we care about is the ability to do key <-> node association. The NodeTrackerEventTracker + // and KeyToNodeActionList keep track of the actual actions we need to perform + // on the node tracker. + NodeToKeyMap _newkeys; + KeyToNodeMap _newnodes; + NodeTrackerEventTracker _node_action_tracker; + + typedef std::map< XML::Node*, XML::Node* > _pc_map_type; + _pc_map_type _parent_child_map; + + XMLNodeTracker* _xnt; + + XML::LogBuilder _builder; +}; + +} + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/jabber_whiteboard/empty.cpp b/src/jabber_whiteboard/empty.cpp new file mode 100644 index 000000000..0fed24e45 --- /dev/null +++ b/src/jabber_whiteboard/empty.cpp @@ -0,0 +1,14 @@ +/// Some archivers don't seem to like creating an archive without being +/// given any members to archive. This file acts as a placeholder + + +/* + 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 : diff --git a/src/jabber_whiteboard/error-codes.h b/src/jabber_whiteboard/error-codes.h new file mode 100644 index 000000000..e5c6268fa --- /dev/null +++ b/src/jabber_whiteboard/error-codes.h @@ -0,0 +1,41 @@ +/** + * Whiteboard session manager + * Error codes + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __WHITEBOARD_JABBER_ERROR_CODES_H__ +#define __WHITEBOARD_JABBER_ERROR_CODES_H__ + +namespace Inkscape { + +namespace Whiteboard { + +namespace ErrorCodes { + +static unsigned int const SERVER_CONNECT_FAILED = 502; +static unsigned int const CHAT_HANDLE_IN_USE = 409; + +} + +} + +} +#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:encoding=utf-8:textwidth=99 : diff --git a/src/jabber_whiteboard/internal-constants.cpp b/src/jabber_whiteboard/internal-constants.cpp new file mode 100644 index 000000000..4956a402c --- /dev/null +++ b/src/jabber_whiteboard/internal-constants.cpp @@ -0,0 +1,76 @@ +/** + * Whiteboard session manager + * Internal constants + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "jabber_whiteboard/internal-constants.h" + +namespace Inkscape { + +namespace Whiteboard { +// Protocol versions +char const* MESSAGE_PROTOCOL_V1 = "1"; +char const* MESSAGE_PROTOCOL_V2 = "2"; +int const HIGHEST_SUPPORTED = 1; + +// Node types (as strings) +char const* NODETYPE_DOCUMENT_STR = "document"; +char const* NODETYPE_ELEMENT_STR = "element"; +char const* NODETYPE_TEXT_STR = "text"; +char const* NODETYPE_COMMENT_STR = "comment"; + +// Number of chars to allocate for type field (in SessionManager::sendMessage) +int const TYPE_FIELD_SIZE = 5; + +// Number of chars to allocate for sequence number field (in SessionManager::sendMessage) +int const SEQNUM_FIELD_SIZE = 70; + +// Designators for certain "special" nodes in the document +// These nodes are "special" because they are generally present in all documents, +// and we generally only want one copy of them +char const* DOCUMENT_ROOT_NODE = "ROOT"; +char const* DOCUMENT_NAMEDVIEW_NODE = "NAMEDVIEW"; + +// Names of these special nodes +char const* DOCUMENT_ROOT_NAME = "svg:svg"; +char const* DOCUMENT_NAMEDVIEW_NAME = "sodipodi:namedview"; + +// Inkboard client states +int const IN_WHITEBOARD = 0; +int const LOGGED_IN = 1; +int const IN_CHATROOM = 2; +int const WAITING_FOR_INVITE_RESPONSE = 3; +int const CONNECTING_TO_CHAT = 4; +int const WAITING_TO_SYNC_TO_CHAT = 5; +int const SYNCHRONIZING_WITH_CHAT = 6; +int const OPEN_FOR_DOC = 7; +int const PLAYING_SESSION_FILE = 8; + +// TODO: make this user-configurable, within sane limits +// ("sane" limits being roughly in the range (10, 100], from personal testing) +// Based on discussions with Ted, it seems that we're going to make the Jabber guys +// accomodate Inkscape, not the other way around... +// Dispatch interval (in milliseconds) +int const SEND_TIMEOUT = 35; +} + +} + + +/* + 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 : diff --git a/src/jabber_whiteboard/internal-constants.h b/src/jabber_whiteboard/internal-constants.h new file mode 100644 index 000000000..5a14737be --- /dev/null +++ b/src/jabber_whiteboard/internal-constants.h @@ -0,0 +1,83 @@ +/** + * Whiteboard session manager + * Internal constants + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __WHITEBOARD_INTERNAL_CONSTANTS_H__ +#define __WHITEBOARD_INTERNAL_CONSTANTS_H__ + +namespace Inkscape { + +namespace Whiteboard { + +// TODO: breaking these up into namespaces would be nice, but it's too much typing +// for now + +// Protocol versions +extern char const* MESSAGE_PROTOCOL_V1; +extern int const HIGHEST_SUPPORTED; + +// Node types (as strings) +extern char const* NODETYPE_DOCUMENT_STR; +extern char const* NODETYPE_ELEMENT_STR; +extern char const* NODETYPE_TEXT_STR; +extern char const* NODETYPE_COMMENT_STR; + +// Number of chars to allocate for type field (in SessionManager::sendMessage) +extern int const TYPE_FIELD_SIZE; + +// Number of chars to allocate for sequence number field (in SessionManager::sendMessage) +extern int const SEQNUM_FIELD_SIZE; + +// Designators for certain "special" nodes in the document +// These nodes are "special" because they are generally present in all documents +extern char const* DOCUMENT_ROOT_NODE; +extern char const* DOCUMENT_NAMEDVIEW_NODE; + +// Names of these special nodes +extern char const* DOCUMENT_ROOT_NAME; +extern char const* DOCUMENT_NAMEDVIEW_NAME; + +// Inkboard client states +extern int const IN_WHITEBOARD; +extern int const LOGGED_IN; +extern int const IN_CHATROOM; +extern int const WAITING_FOR_INVITE_RESPONSE; +extern int const CONNECTING_TO_CHAT; +extern int const WAITING_TO_SYNC_TO_CHAT; +extern int const SYNCHRONIZING_WITH_CHAT; +extern int const OPEN_FOR_DOC; +extern int const PLAYING_SESSION_FILE; + +// update this if any other status flags are added +#define NUM_FLAGS 9 + +// TODO: make this user-configurable, within sane limits +// ("sane" limits being roughly in the range (10, 100], from personal testing) +// Based on discussions with Ted, it seems that we're going to make the Jabber guys +// accomodate Inkscape, not the other way around... +// Dispatch interval (in milliseconds) +extern int const SEND_TIMEOUT; +} + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/jabber_whiteboard/invitation-confirm-dialog.cpp b/src/jabber_whiteboard/invitation-confirm-dialog.cpp new file mode 100644 index 000000000..8590abab8 --- /dev/null +++ b/src/jabber_whiteboard/invitation-confirm-dialog.cpp @@ -0,0 +1,66 @@ +/** + * Whiteboard invitation confirmation dialog -- + * quick subclass of Gtk::MessageDialog + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +#include "invitation-confirm-dialog.h" +#include "session-file-selector.h" + +namespace Inkscape { + +namespace Whiteboard { + +InvitationConfirmDialog::InvitationConfirmDialog(Glib::ustring const& msg) : + Gtk::MessageDialog(msg, true, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_NONE, false), + _usesessionfile(_("_Write session file:"), true) +{ + this->_construct(); + this->get_vbox()->show_all_children(); +} + +InvitationConfirmDialog::~InvitationConfirmDialog() +{ + +} + +Glib::ustring const& +InvitationConfirmDialog::getSessionFilePath() +{ + return this->_sfsbox.getFilename(); +} + +bool +InvitationConfirmDialog::useSessionFile() +{ + return this->_sfsbox.isSelected(); +} + +void +InvitationConfirmDialog::_construct() +{ + this->get_vbox()->pack_end(this->_sfsbox); +} + +} + +} + +/* + 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 : diff --git a/src/jabber_whiteboard/invitation-confirm-dialog.h b/src/jabber_whiteboard/invitation-confirm-dialog.h new file mode 100644 index 000000000..42cea9b56 --- /dev/null +++ b/src/jabber_whiteboard/invitation-confirm-dialog.h @@ -0,0 +1,69 @@ +/** + * Whiteboard invitation confirmation dialog -- + * quick subclass of Gtk::MessageDialog + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __WHITEBOARD_INVITATION_CONFIRM_DIALOG_H__ +#define __WHITEBOARD_INVITATION_CONFIRM_DIALOG_H__ + +#include +#include +#include + +#include "jabber_whiteboard/session-file-selector.h" + + +namespace Inkscape { + +namespace Whiteboard { + +class InvitationConfirmDialog : public Gtk::MessageDialog { +public: + InvitationConfirmDialog(Glib::ustring const& msg); + ~InvitationConfirmDialog(); + + Glib::ustring const& getSessionFilePath(); + bool useSessionFile(); + +private: + static unsigned int const SELECT_FILE = 0; + + void _respCallback(int resp); + + Gtk::HBox _filesel; + + SessionFileSelectorBox _sfsbox; + Gtk::CheckButton _usesessionfile; + Gtk::Entry _sessionfile; + Gtk::Button _getfilepath; + + void _construct(); + Glib::ustring _selectedpath; + + // noncopyable, nonassignable + InvitationConfirmDialog(InvitationConfirmDialog const&); + InvitationConfirmDialog& operator=(InvitationConfirmDialog const&); +}; + +} + +} +#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:encoding=utf-8:textwidth=99 : diff --git a/src/jabber_whiteboard/jabber-handlers.cpp b/src/jabber_whiteboard/jabber-handlers.cpp new file mode 100644 index 000000000..308978025 --- /dev/null +++ b/src/jabber_whiteboard/jabber-handlers.cpp @@ -0,0 +1,65 @@ +/** + * Whiteboard session manager + * C-style Loudmouth callbacks + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "jabber_whiteboard/defines.h" +#include "jabber_whiteboard/jabber-handlers.h" +#include "jabber_whiteboard/message-handler.h" +#include "jabber_whiteboard/session-manager.h" + +namespace Inkscape { + +namespace Whiteboard { + +LmHandlerResult +default_handler(LmMessageHandler* handler, LmConnection* connection, LmMessage* message, gpointer user_data) +{ + MessageHandler* mh = reinterpret_cast< MessageHandler* >(user_data); + return mh->handle(message, DEFAULT); +} + + +LmHandlerResult +presence_handler(LmMessageHandler* handler, LmConnection* connection, LmMessage* message, gpointer user_data) +{ + MessageHandler* mh = reinterpret_cast< MessageHandler* >(user_data); + return mh->handle(message, PRESENCE); +} + + +LmHandlerResult +stream_error_handler(LmMessageHandler* handler, LmConnection* connection, LmMessage* message, gpointer user_data) +{ + MessageHandler* mh = reinterpret_cast< MessageHandler* >(user_data); + return mh->handle(message, ERROR); +} + +LmSSLResponse +ssl_error_handler(LmSSL* ssl, LmSSLStatus status, gpointer user_data) +{ + SessionManager* sm = reinterpret_cast< SessionManager* >(user_data); + return sm->handleSSLError(ssl, status); +} + +} + +} + +/* + 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 : diff --git a/src/jabber_whiteboard/jabber-handlers.h b/src/jabber_whiteboard/jabber-handlers.h new file mode 100644 index 000000000..2eec08c78 --- /dev/null +++ b/src/jabber_whiteboard/jabber-handlers.h @@ -0,0 +1,86 @@ +/** + * Whiteboard session manager + * C-style Loudmouth callbacks + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __WHITEBOARD_LOUDMOUTH_CALLBACKS__ +#define __WHITEBOARD_LOUDMOUTH_CALLBACKS__ + +extern "C" { +#include +} + +#include + +namespace Inkscape { + +namespace Whiteboard { + +/** + * C-style callback for Loudmouth to handle received presence messages. + * + * \param handler The LmMessageHandler handling this event. + * \param connection The LmConnection with which the LmMessageHandler is associated. + * \param message The Jabber message received that triggered this handler. + * \param user_data A pointer to an instance of Inkscape::Whiteboard::MessageHandler, which performs + * the real message processing work. + */ +LmHandlerResult presence_handler(LmMessageHandler* handler, LmConnection* connection, LmMessage* message, gpointer user_data); + +/** + * C-style callback for Loudmouth to handle received messages. + * + * This handler handles messages that are not presence or error messages. + * + * \param handler The LmMessageHandler handling this event. + * \param connection The LmConnection with which the LmMessageHandler is associated. + * \param message The Jabber message received that triggered this handler. + * \param user_data A pointer to an instance of Inkscape::Whiteboard::MessageHandler, which performs + * the real message processing work. + */ +LmHandlerResult default_handler(LmMessageHandler* handler, LmConnection* connection, LmMessage* message, gpointer user_data); + +/** + * C-style callback for Loudmouth to handle received error messages. + * + * \param handler The LmMessageHandler handling this event. + * \param connection The LmConnection with which the LmMessageHandler is associated. + * \param message The Jabber message received that triggered this handler. + * \param user_data A pointer to an instance of Inkscape::Whiteboard::MessageHandler, which performs + * the real message processing work. + */ +LmHandlerResult stream_error_handler(LmMessageHandler* handler, LmConnection* connection, LmMessage* message, gpointer user_data); + +/** + * C-style callback for Loudmouth to handle SSL errors. + * + * \param ssl The SSL data structure used by Loudmouth. + * \param status The error code representing the error that occurred. + * \param user_data A pointer to the SessionManager instance handling associated with the connection attempt that + * threw the SSL error. + */ +LmSSLResponse ssl_error_handler(LmSSL* ssl, LmSSLStatus status, gpointer user_data); + +} + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/jabber_whiteboard/makefile.in b/src/jabber_whiteboard/makefile.in new file mode 100644 index 000000000..839569d0b --- /dev/null +++ b/src/jabber_whiteboard/makefile.in @@ -0,0 +1,17 @@ +# Convenience stub makefile to call the real Makefile. + +@SET_MAKE@ + +# Explicit so that it's the default rule. +all: + cd .. && $(MAKE) jabber_whiteboard/all + +clean %.a %.o: + cd .. && $(MAKE) jabber_whiteboard/$@ + +.PHONY: all clean + +OBJEXT = @OBJEXT@ + +.SUFFIXES: +.SUFFIXES: .a .$(OBJEXT) diff --git a/src/jabber_whiteboard/message-aggregator.cpp b/src/jabber_whiteboard/message-aggregator.cpp new file mode 100644 index 000000000..2077dcc6d --- /dev/null +++ b/src/jabber_whiteboard/message-aggregator.cpp @@ -0,0 +1,64 @@ +/** + * Aggregates individual serialized XML::Events into larger packages + * for more efficient delivery + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include "jabber_whiteboard/message-aggregator.h" + +namespace Inkscape { + +namespace Whiteboard { + +bool +MessageAggregator::addOne(Glib::ustring const& msg, Glib::ustring& buf) +{ + // 1. If msg.bytes() > maximum size and the buffer is clear, + // then we have to send an oversize packet -- + // we won't be able to deliver the message any other way. + // Add it to the buffer and return true. Any further attempt to + // aggregate a message will be handled by condition #2. + if (msg.bytes() > MessageAggregator::MAX_SIZE && buf.empty()) { + buf += msg; + return true; + } + + // 2. If msg.bytes() + buf.bytes() > maximum size, return false. + // The user of this class is responsible for retrieving the aggregated message, + // doing something with it, clearing the buffer, and trying again. + // Otherwise, append the message to the buffer and return true. + if (msg.bytes() + buf.bytes() > MessageAggregator::MAX_SIZE) { + return false; + } else { + buf += msg; + return true; + } +} + +bool +MessageAggregator::addOne(Glib::ustring const& msg) +{ + return this->addOne(msg, this->_buf); +} + +} + +} + +/* + 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 : diff --git a/src/jabber_whiteboard/message-aggregator.h b/src/jabber_whiteboard/message-aggregator.h new file mode 100644 index 000000000..56cd8e3da --- /dev/null +++ b/src/jabber_whiteboard/message-aggregator.h @@ -0,0 +1,135 @@ +/** + * Aggregates individual serialized XML::Events into larger packages + * for more efficient delivery + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __WHITEBOARD_MESSAGE_AGGREGATOR_H__ +#define __WHITEBOARD_MESSAGE_AGGREGATOR_H__ + +#include + +namespace Inkscape { + +namespace Whiteboard { + +/** + * Aggregates individual serialized XML::Events into larger messages for increased + * efficiency. + * + * \see Inkscape::Whiteboard::Serializer + */ +class MessageAggregator { +public: + // TODO: This should be user-configurable; perhaps an option in Inkscape Preferences... + /// Maximum size of aggregates in kilobytes; ULONG_MAX = no limit. + static unsigned int const MAX_SIZE = 16384; + + MessageAggregator() { } + ~MessageAggregator() { } + + /** + * Return the instance of this class. + * + * \return MessageAggregator instance. + */ + static MessageAggregator& instance() + { + static MessageAggregator singleton; + return singleton; + } + + /** + * Adds one message to the aggregate + * using a user-provided buffer. Returns true if more messages can be + * added to the buffer; false otherwise. + * + * \param msg The message to add to the aggregate. + * \param buf The aggregate buffer. + * \return Whether or not more messages can be added to the buffer. + */ + bool addOne(Glib::ustring const& msg, Glib::ustring& buf); + + /** + * Adds one message to the aggregate using the internal buffer. + * Note that since this class is designed to be a singleton class, usage of the internal + * buffer is not thread-safe. Use the above method if this matters to you + * (it currently shouldn't matter, but in future...) + * + * Also note that usage of the internal buffer means that you will have to manually + * clear the internal buffer; use reset() for that. + * + * \param msg The message to add to the aggregate. + * \return Whether or not more messages can be added to the buffer. + */ + bool addOne(Glib::ustring const& msg); + + /** + * Return the aggregate message. + * + * Because this method returns a reference to a string, it is not safe for use + * across multiple invocations of this Deserializer. + * + * \return A reference to the aggregate message. + */ + Glib::ustring const& getAggregate() + { + return this->_buf; + } + + /** + * Return the aggregate message. + * + * \return The aggregate message. + */ + Glib::ustring const getAggregateCopy() + { + return this->_buf; + } + + /** + * Return the aggregate message and clear the internal buffer. + * + * \return The aggregate message. + */ + Glib::ustring const detachAggregate() + { + Glib::ustring ret = this->_buf; + this->_buf.clear(); + return ret; + } + + /** + * Clear the internal buffer. + */ + void reset() + { + this->_buf.clear(); + } + +private: + Glib::ustring _buf; +}; + +} + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/jabber_whiteboard/message-contexts.cpp b/src/jabber_whiteboard/message-contexts.cpp new file mode 100644 index 000000000..1b5ea12fd --- /dev/null +++ b/src/jabber_whiteboard/message-contexts.cpp @@ -0,0 +1,134 @@ +/** + * Whiteboard session manager + * Inkboard message context definitions + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "jabber_whiteboard/defines.h" +#include "jabber_whiteboard/message-contexts.h" + +#include + +namespace Inkscape { + +namespace Whiteboard { + +void +initialize_received_message_contexts(MessageContextMap& mcm) +{ + + // Each Inkboard message has a context of validity according to an Inkboard + // client's state. That is, certain messages should be acknowledged and processed + // in some states, whereas other messages should not. + // For instance, a whiteboard invitation should be acknowledged if an Inkboard + // client is not in a whiteboard session, but should be refused if said client + // _is_ in a whiteboard session. + + // Only the flags that are required to be set must be set; all flags, by default, + // are zero. Explicit definition doesn't hurt, though. + + // The general format of message context creation and registration is + // std::bitset< NUM_FLAGS > m_ + // m_.set( ); + // m_.set( ); + // mcm[ ] = m_; + + + // Begin + + // Special bitsets + std::bitset< NUM_FLAGS > all_contexts; + all_contexts.flip(); + + // Messages: CHANGE_NOT_REPEATABLE, CHANGE_REPEATABLE, DUMMY_CHANGE, CHANGE_COMMIT + std::bitset< NUM_FLAGS > m1; + m1.set(LOGGED_IN); + m1.set(IN_WHITEBOARD); + m1.set(IN_CHATROOM); + m1.set(SYNCHRONIZING_WITH_CHAT); + mcm[CHANGE_NOT_REPEATABLE] = m1; + mcm[CHANGE_REPEATABLE] = m1; + mcm[DUMMY_CHANGE] = m1; + mcm[CHANGE_COMMIT] = m1; + + // Messages: DOCUMENT_BEGIN, DOCUMENT_END + std::bitset< NUM_FLAGS > m4; + m4.set(LOGGED_IN); + m4.set(IN_WHITEBOARD); + m4.set(SYNCHRONIZING_WITH_CHAT); + mcm[DOCUMENT_BEGIN] = m4; + mcm[DOCUMENT_END] = m4; + + // Message: CONNECT_REQUEST_USER + std::bitset< NUM_FLAGS > m5; + m5.set(LOGGED_IN); + mcm[CONNECT_REQUEST_USER] = m5; + + // Message: CONNECT_REQUEST_RESPONSE_USER + std::bitset< NUM_FLAGS > m6; + m6.set(LOGGED_IN); + m6.set(WAITING_FOR_INVITE_RESPONSE); + mcm[CONNECT_REQUEST_RESPONSE_USER] = m6; + + // Message: CHATROOM_SYNCHRONIZE_REQUEST + std::bitset< NUM_FLAGS > m7; + m7.set(LOGGED_IN); + m7.set(IN_CHATROOM); + m7.set(IN_WHITEBOARD); + mcm[CHATROOM_SYNCHRONIZE_REQUEST] = m7; + + // Message: CHATROOM_SYNCHRONIZE_RESPONSE + std::bitset< NUM_FLAGS > m8; + m8.set(LOGGED_IN); + m8.set(WAITING_TO_SYNC_TO_CHAT); + mcm[CHATROOM_SYNCHRONIZE_RESPONSE] = m8; + + // Message: CONNECT_REQUEST_RESPONSE_CHAT + std::bitset< NUM_FLAGS > m9; + m9.set(LOGGED_IN); + m9.set(IN_CHATROOM); + m9.set(IN_WHITEBOARD); + mcm[CONNECT_REQUEST_RESPONSE_CHAT] = m9; + + // Message: CONNECTED_SIGNAL + mcm[CONNECTED_SIGNAL] = all_contexts; + + // Message: DISCONNECTED_FROM_USER_SIGNAL + std::bitset< NUM_FLAGS > m11; + m11.set(LOGGED_IN); + m11.set(IN_WHITEBOARD); + mcm[DISCONNECTED_FROM_USER_SIGNAL] = m11; + + // Messages: CONNECT_REQUEST_REFUSED_BY_PEER, ALREADY_IN_SESSION + std::bitset< NUM_FLAGS > m12; + m12.set(LOGGED_IN); + m12.set(WAITING_FOR_INVITE_RESPONSE); + mcm[CONNECT_REQUEST_REFUSED_BY_PEER] = m12; + mcm[ALREADY_IN_SESSION] = m12; + + // Message: UNSUPPORTED_PROTOCOL_VERSION + std::bitset< NUM_FLAGS > m14; + m14.set(LOGGED_IN); + mcm[UNSUPPORTED_PROTOCOL_VERSION] = m14; +} + +} + +} + +/* + 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 : diff --git a/src/jabber_whiteboard/message-contexts.h b/src/jabber_whiteboard/message-contexts.h new file mode 100644 index 000000000..49653c693 --- /dev/null +++ b/src/jabber_whiteboard/message-contexts.h @@ -0,0 +1,39 @@ +/** + * Whiteboard session manager + * Inkboard message context definitions + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __WHITEBOARD_MESSAGE_CONTEXTS_H__ +#define __WHITEBOARD_MESSAGE_CONTEXTS_H__ + +#include "jabber_whiteboard/typedefs.h" + +namespace Inkscape { + +namespace Whiteboard { + +void initialize_received_message_contexts(MessageContextMap& mcm); + +} + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/jabber_whiteboard/message-handler.cpp b/src/jabber_whiteboard/message-handler.cpp new file mode 100644 index 000000000..cdcef74b8 --- /dev/null +++ b/src/jabber_whiteboard/message-handler.cpp @@ -0,0 +1,456 @@ +/** + * Whiteboard session manager + * Jabber received message handling + * + * Authors: + * David Yip + * Steven Montgomery, Jonas Collaros (original C version) + * + * Copyright (c) 2004-2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +extern "C" { +#include +} + +#include +#include +#include +#include + +#include "jabber_whiteboard/defines.h" +#include "jabber_whiteboard/typedefs.h" +#include "jabber_whiteboard/message-processors.h" +#include "jabber_whiteboard/message-handler.h" +#include "jabber_whiteboard/session-manager.h" +#include "jabber_whiteboard/chat-handler.h" +#include "jabber_whiteboard/buddy-list-manager.h" + +namespace Inkscape { + +namespace Whiteboard { + +bool message_contexts_initialized = false; +MessageContextMap _received_message_contexts; + +MessageHandler::MessageHandler(SessionManager* sm) : _sm(sm) +{ + if (message_contexts_initialized == false) { +// this->_initializeContexts(); + MessageHandler::_initializeContexts(); + } + this->_initializeProcessors(); +} + +MessageHandler::~MessageHandler() +{ + this->_destructProcessors(); +} + +LmHandlerResult +MessageHandler::handle(LmMessage* message, HandlerMode mode) +{ + if (this->_isValidMessage(message)) { + switch(mode) { + case DEFAULT: + return this->_default(message); + case PRESENCE: + return this->_presence(message); + case ERROR: + return this->_error(message); + default: + g_warning("Jabber message handler was asked to process a message of an unhandled type; discarding message."); + return LM_HANDLER_RESULT_REMOVE_MESSAGE; + } + } else { + return LM_HANDLER_RESULT_REMOVE_MESSAGE; + } +} + +bool +MessageHandler::_hasValidReceiveContext(LmMessage* message) +{ + MessageType type = this->_getType(message); + std::bitset< NUM_FLAGS >& status = this->_sm->session_data->status; + + std::string s1 = status.to_string< char, std::char_traits< char >, std::allocator< char > >(); + + + if (type == UNKNOWN) { + // unknown types never have a valid receive context + return false; + } else { + std::bitset< NUM_FLAGS >& recvcontext = _received_message_contexts[type]; + + // TODO: remove this debug block + if ((status & recvcontext).to_ulong() < status.to_ulong()) { + g_warning("Received message in incorrect context (current is not a subset of required); discarding message."); + + std::string s2 = recvcontext.to_string< char, std::char_traits< char >, std::allocator< char > >(); + + + g_warning("current context=%s required context=%s (msgtype %s)", s1.c_str(), s2.c_str(), MessageHandler::ink_type_to_string(type)); + } + + return ((status & recvcontext).to_ulong() >= status.to_ulong()); + } +} + +bool +MessageHandler::_isValidMessage(LmMessage* message) +{ + // Global sanity checks + LmMessageNode* root; + LmMessageNode* protocolver; + LmMessageNode* offline; + LmMessageType mtype; + LmMessageSubType msubtype; + gchar const* tmp; + + Glib::ustring sender; + + + // 0. The message must have a root node. + root = lm_message_get_node(message); + if (root == NULL) { + g_warning("Check 0 failed (message has no root node)"); + return false; + } + + + // 1. The message must be of LM_MESSAGE_TYPE_MESSAGE to continue the sanity checks. + // If it is not, check to see if it is either + // a presence message or an error message. If it is either of these, then automatically + // consider it sane. + // + // FIXME: + // (That is probably a dangerous assumption. We should probably at least validate + // the source for error messages.) + // + // We do not handle IQ stanzas or STREAM messages (yet), and we certainly don't + // handle unknowns. + + mtype = lm_message_get_type(message); + switch(mtype) { + case LM_MESSAGE_TYPE_PRESENCE: + case LM_MESSAGE_TYPE_STREAM_ERROR: + return true; + case LM_MESSAGE_TYPE_IQ: + case LM_MESSAGE_TYPE_STREAM: + case LM_MESSAGE_TYPE_UNKNOWN: + g_warning("Check 1 failed (Loudmouth reported type IQ, STREAM, or UNKNOWN)"); + return false; + case LM_MESSAGE_TYPE_MESSAGE: + break; + } + + // 2. The message must contain the JID of the sender. + tmp = lm_message_node_get_attribute(root, MESSAGE_FROM); + + if (tmp == NULL) { + g_warning("Check 2 failed (no sender attribute present)"); + return false; + } else { + sender = tmp; + } + + // 3. We do not yet handle messages from offline storage, so ensure that this is not + // such a message. + offline = lm_message_node_get_child(root, "x"); + if (offline != NULL) { + if (strcmp(lm_message_node_get_value(offline), "Offline Storage") == 0) { + return false; + } + } + + // 4. If this is a regular chat message... + msubtype = lm_message_get_sub_type(message); + + if (msubtype == LM_MESSAGE_SUB_TYPE_CHAT) { + // 4a. A protocol version node must be present. + protocolver = lm_message_node_get_child(root, MESSAGE_PROTOCOL_VER); + if (protocolver == NULL) { + g_warning("Check 4a failed (no protocol attribute in chat message)"); + return false; + } else { + tmp = lm_message_node_get_value(protocolver); + if (tmp == NULL) { + g_warning("Check 4a failed (no protocol attribute in chat message)"); + return false; + } + } + + // 5a. The protocol version must be supported. + if (atoi(tmp) > HIGHEST_SUPPORTED) { + g_warning("Check 5a failed (received a message with protocol version %s, but version %s is not supported)", tmp, tmp); + return false; + } + + // ...otherwise, if this is a groupchat message, we may not have a protocol version + // (since it may be communication from the Jabber server). In this case, we have a + // different set of sanity checks. + } else if (msubtype == LM_MESSAGE_SUB_TYPE_GROUPCHAT) { + // 4b. + // In a chatroom situation, we need to ensure that we don't process messages that + // originated from us. + int cutoff = sender.find_last_of('/') + 1; + if (sender.substr(cutoff, sender.length()) == this->_sm->session_data->chat_handle) { + return false; + } + // TODO: 6b. If the message is NOT from the Jabber server, then check the protocol version. + } + + // If all tests pass, then the message is at least valid. + // Correct context has not yet been established, however; that is the job of the default handler + // and hasValidReceiveContext. + + return true; +} + +MessageType +MessageHandler::_getType(LmMessage* message) +{ + LmMessageNode* root; + LmMessageNode* typenode; + + root = lm_message_get_node(message); + if (root != NULL) { + typenode = lm_message_node_get_child(root, MESSAGE_TYPE); + if (typenode != NULL) { + return static_cast< MessageType >(atoi(lm_message_node_get_value(typenode))); + } + } + return UNKNOWN; +} + +JabberMessage +MessageHandler::_extractData(LmMessage* message) +{ + + JabberMessage jm(message); + LmMessageNode* root; + LmMessageNode* sequence; + LmMessageNode* body; + gchar const* tmp; + + root = lm_message_get_node(message); + + if (root != NULL) { + sequence = lm_message_node_get_child(root, MESSAGE_SEQNUM); + body = lm_message_node_get_child(root, MESSAGE_BODY); + + jm.sender = lm_message_node_get_attribute(root, MESSAGE_FROM); + + if (sequence) { + tmp = lm_message_node_get_value(sequence); + if (tmp != NULL) { + jm.sequence = atoi(tmp); + } + } + + if (body) { + tmp = lm_message_node_get_value(body); + if (tmp != NULL) { + jm.body = tmp; + } + } + + } else { + jm.sequence = 0; + jm.sender = ""; + jm.body = ""; + } + + return jm; +} + +LmHandlerResult +MessageHandler::_default(LmMessage* message) +{ + std::bitset< NUM_FLAGS >& status = this->_sm->session_data->status; + + // Pass groupchat messages with no Inkboard type off to the chat message handler + if (this->_getType(message) == UNKNOWN) { + if (lm_message_get_sub_type(message) == LM_MESSAGE_SUB_TYPE_GROUPCHAT) { + if (status[IN_CHATROOM] || status[CONNECTING_TO_CHAT]) { + return this->_sm->chat_handler()->parse(message); + } + } else { + return LM_HANDLER_RESULT_REMOVE_MESSAGE; + } + } + + if (this->_hasValidReceiveContext(message)) { + // Extract message data + JabberMessage msg = this->_extractData(message); + MessageType type = this->_getType(message); + + // Call message handler and return instruction value to Loudmouth + + return (*this->_received_message_processors[type])(type, msg); + } else { + g_warning("Default message handler received message in invalid receive context; discarding message."); + return LM_HANDLER_RESULT_REMOVE_MESSAGE; + } +} + +LmHandlerResult +MessageHandler::_presence(LmMessage* message) +{ + LmMessageNode* root; + LmMessageSubType msubtype; + gchar const* tmp; + std::string sender; + + SessionData* sd = this->_sm->session_data; + std::bitset< NUM_FLAGS >& status = this->_sm->session_data->status; + + root = lm_message_get_node(message); + if (root == NULL) { + return LM_HANDLER_RESULT_REMOVE_MESSAGE; + } + + tmp = lm_message_node_get_attribute(root, MESSAGE_FROM); + if (tmp == NULL) { + return LM_HANDLER_RESULT_REMOVE_MESSAGE; + } else { + sender = tmp; + } + + msubtype = lm_message_get_sub_type(message); + if (status[CONNECTING_TO_CHAT] || status[IN_CHATROOM]) { + this->_sm->chat_handler()->parse(message); + } else { + switch(msubtype) { + case LM_MESSAGE_SUB_TYPE_UNAVAILABLE: + // remove buddy from online roster + sd->buddyList.erase(sender); + + // if this buddy is in a 1-1 session with us, we need to exit + // the whiteboard + if (status[IN_WHITEBOARD] && !(status[IN_CHATROOM]) && strcasecmp(sender.c_str(), sd->recipient) == 0) { + status.set(IN_WHITEBOARD, 0); + this->_sm->userDisconnectedFromWhiteboard(sender); + this->_sm->closeSession(); + } + return LM_HANDLER_RESULT_REMOVE_MESSAGE; + + case LM_MESSAGE_SUB_TYPE_AVAILABLE: + // we don't want to insert an entry into a buddy list + // if it's our own presence + if (sender != lm_connection_get_jid(this->_sm->session_data->connection)) { + sd->buddyList.insert(sender); + } + return LM_HANDLER_RESULT_REMOVE_MESSAGE; + default: + break; + } + } + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +LmHandlerResult +MessageHandler::_error(LmMessage* message) +{ + LmMessageNode* root; + LmMessageSubType msubtype; + + root = lm_message_get_node(message); + if (root != NULL) { + msubtype = lm_message_get_sub_type(message); + if (msubtype == LM_MESSAGE_SUB_TYPE_ERROR) { + gchar* error = g_strdup(lm_message_node_get_value(root)); + g_warning(error); + + // TODO: more robust error handling code + this->_sm->disconnectFromDocument(); + this->_sm->disconnectFromServer(); + this->_sm->connectionError(error); + } + } + + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +void +MessageHandler::_initializeContexts() +{ + initialize_received_message_contexts(_received_message_contexts); + message_contexts_initialized = true; +} + +void +MessageHandler::_initializeProcessors() +{ + initialize_received_message_processors(this->_sm, this->_received_message_processors); +} + +void +MessageHandler::_destructProcessors() +{ + destroy_received_message_processors(this->_received_message_processors); +} + + +char const* +MessageHandler::ink_type_to_string(gint ink_type) { + switch(ink_type) { + case Inkscape::Whiteboard::CHANGE_NOT_REPEATABLE: + return "CHANGE_NOT_REPEATABLE"; + case Inkscape::Whiteboard::CHANGE_REPEATABLE: + return "CHANGE_REPEATABLE"; + case Inkscape::Whiteboard::DUMMY_CHANGE: + return "DUMMY_CHANGE"; + case Inkscape::Whiteboard::CHANGE_COMMIT: + return "CHANGE_COMMIT"; + case Inkscape::Whiteboard::CONNECT_REQUEST_USER: + return "CONNECT_REQUEST_USER"; + case Inkscape::Whiteboard::CONNECT_REQUEST_RESPONSE_USER: + return "CONNECT_REQUEST_RESPONSE_USER"; + case Inkscape::Whiteboard::CONNECT_REQUEST_RESPONSE_CHAT: + return "CONNECT_REQUEST_RESPONSE_CHAT"; + case Inkscape::Whiteboard::DOCUMENT_SENDER_REQUEST: + return "DOCUMENT_SENDER_REQUEST"; + case Inkscape::Whiteboard::DOCUMENT_SENDER_REQUEST_RESPONSE: + return "DOCUMENT_SENDER_REQUEST_RESPONSE"; + case Inkscape::Whiteboard::DOCUMENT_REQUEST: + return "DOCUMENT_REQUEST"; + case Inkscape::Whiteboard::DOCUMENT_BEGIN: + return "DOCUMENT_BEGIN"; + case Inkscape::Whiteboard::DOCUMENT_END: + return "DOCUMENT_END"; + case Inkscape::Whiteboard::CONNECTED_SIGNAL: + return "CONNECTED_SIGNAL"; + case Inkscape::Whiteboard::DISCONNECTED_FROM_USER_SIGNAL: + return "DISCONNECTED_FROM_USER_SIGNAL"; + case Inkscape::Whiteboard::CONNECT_REQUEST_REFUSED_BY_PEER: + return "CONNECT_REQUEST_REFUSED_BY_PEER"; + case Inkscape::Whiteboard::UNSUPPORTED_PROTOCOL_VERSION: + return "UNSUPPORTED_PROTOCOL_VERSION"; + case Inkscape::Whiteboard::CHATROOM_SYNCHRONIZE_REQUEST: + return "CHATROOM_SYNCHRONIZE_REQUEST"; + case Inkscape::Whiteboard::CHATROOM_SYNCHRONIZE_RESPONSE: + return "CHATROOM_SYNCHRONIZE_RESPONSE"; + case Inkscape::Whiteboard::ALREADY_IN_SESSION: + return "ALREADY_IN_SESSION"; + default: + return "UNKNOWN"; + } +} + +} + +} + +/* + 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 : diff --git a/src/jabber_whiteboard/message-handler.h b/src/jabber_whiteboard/message-handler.h new file mode 100644 index 000000000..700665f74 --- /dev/null +++ b/src/jabber_whiteboard/message-handler.h @@ -0,0 +1,88 @@ +/** + * Whiteboard session manager + * Jabber message handling + * + * Authors: + * David Yip + * Steven Montgomery, Jonas Collaros (original C version) + * + * Copyright (c) 2004-2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __WHITEBOARD_MESSAGE_HANDLER_H__ +#define __WHITEBOARD_MESSAGE_HANDLER_H__ + +extern "C" { +#include +} + +#include "jabber_whiteboard/defines.h" +#include "jabber_whiteboard/typedefs.h" +#include "jabber_whiteboard/message-contexts.h" + +namespace Inkscape { + +namespace Whiteboard { + +struct JabberMessage; +class SessionManager; + +/** + * Handles received Jabber messages. + */ +class MessageHandler { +public: + MessageHandler(SessionManager* sm); + ~MessageHandler(); + LmHandlerResult handle(LmMessage* message, HandlerMode mode); + + bool _hasValidReceiveContext(LmMessage* message); + + static char const* ink_type_to_string(gint ink_type); + +private: + static void _initializeContexts(); + + void _initializeProcessors(); + void _destructProcessors(); + + // Utilities + bool _isValidMessage(LmMessage* message); + MessageType _getType(LmMessage* message); + struct JabberMessage _extractData(LmMessage* message); + + + // Individual message handlers + LmHandlerResult _default(LmMessage* message); + LmHandlerResult _error(LmMessage* message); + LmHandlerResult _presence(LmMessage* message); + + // Message processors map +// MessageContextMap _received_message_contexts; + MessageProcessorMap _received_message_processors; + + SessionManager* _sm; + + // noncopyable, nonassignable + MessageHandler(MessageHandler const&); + MessageHandler& operator=(MessageHandler const&); +}; + +} + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/jabber_whiteboard/message-node.h b/src/jabber_whiteboard/message-node.h new file mode 100644 index 000000000..5e1b6a674 --- /dev/null +++ b/src/jabber_whiteboard/message-node.h @@ -0,0 +1,134 @@ +/** + * Whiteboard message queue and queue handler functions + * Node for storing messages in message queues + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __WHITEBOARD_MESSAGE_NODE_H__ +#define __WHITEBOARD_MESSAGE_NODE_H__ + +#include +#include + +#include "gc-managed.h" +#include "gc-anchored.h" +#include "gc-finalized.h" + +namespace Inkscape { + +namespace Whiteboard { + +/** + * Encapsulates a document change message received by or sent to an Inkboard client. + * + * Received messages that end up in a MessageNode are of the following types: + *
    + *
  1. CHANGE_REPEATABLE
  2. + *
  3. CHANGE_NOT_REPEATABLE
  4. + *
  5. CHANGE_COMMIT
  6. + *
  7. DOCUMENT_BEGIN
  8. + *
  9. DOCUMENT_END
  10. + *
  11. DUMMY_CHANGE
  12. + *
+ * + * This class is intended for use in MessageQueues, although it could potentially + * see use outside of that context. + * + * \see Inkscape::Whiteboard::MessageQueue + */ +class MessageNode : public GC::Managed<>, public GC::Anchored, public GC::Finalized { +public: + /** + * Constructor. + * + * \param seq The sequence number of the message being encapsulated. + * \param sender The sender of the message. + * \param recip The intended recipient. + * \param message_body The body of the message. + * \param type The type of the message. + * \param chatroom Whether or not this message is to be sent to / was received from a chatroom. + */ + MessageNode(unsigned int seq, std::string sender, std::string recip, Glib::ustring const& message_body, MessageType type, bool document, bool chatroom) : + _seq(seq), _type(type), _message(message_body), _document(document), _chatroom(chatroom) + { + this->_sender = sender; + this->_recipient = recip; + } + + ~MessageNode() + { +// g_log(NULL, G_LOG_LEVEL_DEBUG, "MessageNode destructor"); + /* + if (this->_message) { + delete this->_message; + } + */ + } + + unsigned int sequence() + { + return this->_seq; + } + + MessageType type() + { + return this->_type; + } + + bool chatroom() + { + return this->_chatroom; + } + + bool document() + { + return this->_document; + } + + std::string recipient() + { + return this->_recipient; + } + + std::string sender() + { + return this->_sender; + } + + Glib::ustring const& message() + { + return this->_message; + } + +private: + unsigned int _seq; + std::string _sender; + std::string _recipient; + MessageType _type; + Glib::ustring _message; + bool _document; + bool _chatroom; +}; + +} + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/jabber_whiteboard/message-processors.cpp b/src/jabber_whiteboard/message-processors.cpp new file mode 100644 index 000000000..bd24f7ad8 --- /dev/null +++ b/src/jabber_whiteboard/message-processors.cpp @@ -0,0 +1,330 @@ +/** + * Whiteboard session manager + * Jabber received message processors + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +extern "C" { +#include +} + +#include + +#include "xml/session.h" +#include "xml/document.h" + +#include "desktop-handles.h" +#include "document.h" +#include "message-stack.h" + +#include "jabber_whiteboard/undo-stack-observer.h" +#include "jabber_whiteboard/session-manager.h" +#include "jabber_whiteboard/message-node.h" +#include "jabber_whiteboard/message-queue.h" +#include "jabber_whiteboard/message-processors.h" +#include "jabber_whiteboard/typedefs.h" + +namespace Inkscape { + +namespace Whiteboard { + +// Message processors are here! + +// TODO: Remove unnecessary status checks from processors -- +// we do all of that in MessageHandler::_hasValidReceiveContext + +// ********************************************************************* +// ChangeHandler begin +// ********************************************************************* + +/** + * MessageProcessor for document change and event commit messages. + */ +struct ChangeHandler : public MessageProcessor { +public: + ~ChangeHandler() + { + + } + + ChangeHandler(SessionManager* sm) : MessageProcessor(sm) + { + + } + + LmHandlerResult + operator()(MessageType mode, JabberMessage& p) + { + MessageNode* msgNode; + bool chatroom = this->_sm->session_data->status[IN_CHATROOM]; + + ReceiveMessageQueue* rmq = this->_sm->session_data->receive_queues[p.sender]; + + if (rmq != NULL) { + switch (mode) { + case CHANGE_REPEATABLE: + case CHANGE_NOT_REPEATABLE: + case DOCUMENT_BEGIN: + msgNode = new MessageNode(p.sequence, p.sender, "", p.body, mode, false, chatroom); + rmq->insert(msgNode); + Inkscape::GC::release(msgNode); + break; + case DOCUMENT_END: + this->_sm->session_data->recipients_committed_queue.push_back(p.sender); + msgNode = new MessageNode(p.sequence, p.sender, "", p.body, mode, false, chatroom); + rmq->insert(msgNode); + Inkscape::GC::release(msgNode); + break; + case CHANGE_COMMIT: + this->_sm->session_data->recipients_committed_queue.push_back(p.sender); + msgNode = new MessageNode(p.sequence, p.sender, "", p.body, CHANGE_COMMIT, false, chatroom); + rmq->insert(msgNode); + Inkscape::GC::release(msgNode); + break; + case DUMMY_CHANGE: + default: + break; + } + } else { + g_warning("Received message from unknown sender %s", p.sender.c_str()); + } + + return LM_HANDLER_RESULT_REMOVE_MESSAGE; + } +}; +// ********************************************************************* +// ChangeHandler end +// ********************************************************************* + + +// ********************************************************************* +// ConnectRequestHandler begin +// ********************************************************************* +/** + * MessageProcessor for connection request messages. + */ +struct ConnectRequestHandler : public MessageProcessor { +public: + ~ConnectRequestHandler() + { + + } + + ConnectRequestHandler(SessionManager* sm) : MessageProcessor(sm) + { + + } + + LmHandlerResult + operator()(MessageType mode, JabberMessage& m) + { + std::bitset< NUM_FLAGS >& status = this->_sm->session_data->status; + switch(mode) { + case CONNECT_REQUEST_USER: + this->_sm->receiveConnectRequest(m.sender.c_str()); + break; + case CONNECT_REQUEST_RESPONSE_USER: + if (m.sequence == 0) { + this->_sm->receiveConnectRequestResponse(DECLINE_INVITATION, m.sender); + } else { // FIXME: this has got to be buggy... + this->_sm->setRecipient(m.sender.c_str()); + this->_sm->receiveConnectRequestResponse(ACCEPT_INVITATION, m.sender); + } + break; + case Inkscape::Whiteboard::CONNECTED_SIGNAL: + if (!status[IN_CHATROOM] && !status[CONNECTING_TO_CHAT] && !status[SYNCHRONIZING_WITH_CHAT] && !status[WAITING_TO_SYNC_TO_CHAT]) { + this->_sm->userConnectedToWhiteboard(m.sender.c_str()); + this->_sm->setRecipient(m.sender.c_str()); + } else { + SP_DT_MSGSTACK(this->_sm->desktop())->flashF(Inkscape::INFORMATION_MESSAGE, _("%s has joined the chatroom."), m.sender.c_str()); + } + break; + default: + break; + } + return LM_HANDLER_RESULT_REMOVE_MESSAGE; + } +}; +// ********************************************************************* +// ConnectRequestHandler end +// ********************************************************************* + + + + +// ********************************************************************* +// ConnectErrorHandler begin +// ********************************************************************* +/** + * MessageProcessor for connection error messages. + */ +struct ConnectErrorHandler : public MessageProcessor { +public: + ~ConnectErrorHandler() + { + + } + + ConnectErrorHandler(SessionManager* sm) : MessageProcessor(sm) + { + + } + + LmHandlerResult + operator()(MessageType mode, JabberMessage& m) + { + switch(mode) { + case CONNECT_REQUEST_REFUSED_BY_PEER: + if (this->_sm->session_data->status[WAITING_FOR_INVITE_RESPONSE]) { + this->_sm->receiveConnectRequestResponse(DECLINE_INVITATION, m.sender); + } + break; + case Inkscape::Whiteboard::ALREADY_IN_SESSION: + if (this->_sm->session_data->status[WAITING_FOR_INVITE_RESPONSE]) { + this->_sm->receiveConnectRequestResponse(PEER_ALREADY_IN_SESSION, m.sender); + } + break; + case Inkscape::Whiteboard::DISCONNECTED_FROM_USER_SIGNAL: + if (!this->_sm->session_data->status[IN_CHATROOM]) { + this->_sm->closeSession(); + this->_sm->userDisconnectedFromWhiteboard(m.sender.c_str()); + } + break; + default: + break; + } + return LM_HANDLER_RESULT_REMOVE_MESSAGE; + } +}; +// ********************************************************************* +// ConnectErrorHandler end +// ********************************************************************* + + + + +// ********************************************************************* +// ChatSynchronizeHandler begin +// ********************************************************************* +/** + * MessageProcessor for messages specific to chatroom synchronization. + */ +struct ChatSynchronizeHandler : public MessageProcessor { +public: + ~ChatSynchronizeHandler() + { + + } + + ChatSynchronizeHandler(SessionManager* sm) : MessageProcessor(sm) + { + + } + + LmHandlerResult + operator()(MessageType mode, JabberMessage& m) + { + switch(mode) { + case CONNECT_REQUEST_RESPONSE_CHAT: + this->_sm->receiveConnectRequestResponseChat(m.sender.c_str()); + break; + case CHATROOM_SYNCHRONIZE_REQUEST: + if (this->_sm->session_data->status[IN_CHATROOM] && this->_sm->session_data->status[IN_WHITEBOARD]) { + // Send response. Everyone in the chatroom will do this, + // but the client will accept only one response. + // The response is sent privately to the client + // + this->_sm->sendMessage(CHATROOM_SYNCHRONIZE_RESPONSE, this->_sm->session_data->sequence_number, "", m.sender.c_str(), false); + } + break; + case CHATROOM_SYNCHRONIZE_RESPONSE: + if (m.sequence != 0) { + // Set sequence number + this->_sm->session_data->sequence_number = m.sequence; + + // Set status flags + this->_sm->session_data->status.set(WAITING_TO_SYNC_TO_CHAT, 0); + this->_sm->session_data->status.set(SYNCHRONIZING_WITH_CHAT, 1); + + // Send document synchronization request + this->_sm->clearDocument(); + this->_sm->setupInkscapeInterface(); + this->_sm->sendMessage(CONNECT_REQUEST_RESPONSE_CHAT, m.sequence, "", m.sender.c_str(), false); + } else { + this->_sm->sendMessage(CHATROOM_SYNCHRONIZE_REQUEST, 0, "", this->_sm->session_data->recipient, true); + } + break; + default: + break; + } + return LM_HANDLER_RESULT_REMOVE_MESSAGE; + } +}; +// ********************************************************************* +// ChatSynchronizeHandler end +// ********************************************************************* + + + + +// ********************************************************************* +// Initializer +// ********************************************************************* +void +initialize_received_message_processors(SessionManager* sm, MessageProcessorMap& mpm) +{ + MessageProcessor* ch = new ChangeHandler(sm); + MessageProcessor* crh = new ConnectRequestHandler(sm); + MessageProcessor* ceh = new ConnectErrorHandler(sm); + MessageProcessor* csh = new ChatSynchronizeHandler(sm); + + mpm[CHANGE_REPEATABLE] = ch; + mpm[CHANGE_NOT_REPEATABLE] = ch; + mpm[DUMMY_CHANGE] = ch; + mpm[CHANGE_COMMIT] = ch; + mpm[DOCUMENT_BEGIN] = ch; + mpm[DOCUMENT_END] = ch; + + mpm[CONNECT_REQUEST_USER] = crh; + mpm[CONNECT_REQUEST_RESPONSE_USER] = crh; + mpm[CONNECTED_SIGNAL] = crh; + + mpm[CONNECT_REQUEST_REFUSED_BY_PEER] = ceh; + mpm[ALREADY_IN_SESSION] = ceh; + mpm[DISCONNECTED_FROM_USER_SIGNAL] = ceh; + + mpm[CONNECT_REQUEST_RESPONSE_CHAT] = csh; + mpm[CHATROOM_SYNCHRONIZE_REQUEST] = csh; + mpm[CHATROOM_SYNCHRONIZE_RESPONSE] = csh; +} + +/* + * This function is provided solely for convenience and style. You can, of course, + * delete every MessageProcessor in the map with your own loop. + */ +void +destroy_received_message_processors(MessageProcessorMap& mpm) +{ + mpm.clear(); +} + +} + +} + +/* + 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 : diff --git a/src/jabber_whiteboard/message-processors.h b/src/jabber_whiteboard/message-processors.h new file mode 100644 index 000000000..8b0887444 --- /dev/null +++ b/src/jabber_whiteboard/message-processors.h @@ -0,0 +1,176 @@ +/** + * Whiteboard session manager + * Jabber received message processors + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __WHITEBOARD_MESSAGE_PROCESSORS_H__ +#define __WHITEBOARD_MESSAGE_PROCESSORS_H__ + +#include "jabber_whiteboard/typedefs.h" + +#include "gc-managed.h" +#include "gc-finalized.h" + +namespace Inkscape { + +namespace Whiteboard { + +class SessionManager; + +// Processor forward declarations +struct ChangeHandler; +struct DocumentSignalHandler; +struct ConnectRequestHandler; +struct ConnectErrorHandler; +struct ChatSynchronizeHandler; + +/** + * Encapsulates a pointer to an LmMessage along with additional information, + * such as the message's sequence number, its sender, and its body. + * + * All of the above data members can be extracted directly from the LmMessage; + * they are provided for convenience. + */ +struct JabberMessage { +public: + /** + * Constructor. + * + * The constructor attaches a reference to the LmMessage to prevent Loudmouth from + * freeing the message. + */ + JabberMessage(LmMessage* m) : message(m), sequence(0) + { + lm_message_ref(this->message); + } + + /** + * Destructor. + * + * The destructor deletes a reference to the LmMessage, which, assuming all other + * references have been deleted, will allow Loudmouth to free the LmMessage object. + */ + ~JabberMessage() + { + lm_message_unref(this->message); + } + + + // TODO: Hide, or, better, remove this. There's no real reason why it should be here, + // and it allows for the possibility of reference count-induced memory leaks. + /** + * Pointer to original Loudmouth message object. + */ + LmMessage* message; + + /** + * Sequence number of this message. + */ + unsigned int sequence; + + /** + * The JID of this message's sender. + */ + std::string sender; + + /** + * The body of this message. + */ + Glib::ustring body; + +private: + // noncopyable, nonassignable (for now, anyway...) +// JabberMessage(JabberMessage const&); +// JabberMessage& operator=(JabberMessage const&); +}; + +/** + * A MessageProcessor is a functor that is associated with one or more Inkboard message types. + * When an Inkboard client receives an Inkboard message, it passes it to the appropriate + * MessageProcessor. + */ +struct MessageProcessor : public GC::Managed<>, public GC::Finalized { +public: + virtual ~MessageProcessor() + { + + } + + /** + * Functor action operator. + * + * \param mode The type of the message being processed. + * \param m A reference to the JabberMessage encapsulating the received Jabber message. + */ + virtual LmHandlerResult operator()(MessageType mode, JabberMessage& m) = 0; + + /** + * Constructor. + * + * \param sm The SessionManager with which a MessageProcessor instance is associated. + */ + MessageProcessor(SessionManager* sm) : _sm(sm) { } +protected: + /** + * Pointer to the associated SessionManager object. + */ + SessionManager *_sm; + +private: + // noncopyable, nonassignable + MessageProcessor(MessageProcessor const&); + MessageProcessor& operator=(MessageProcessor const&); +}; + +/* +struct ProcessorShell : public GC::Managed<>, public std::binary_function< MessageType, JabberMessage, LmHandlerResult > { +public: + ProcessorShell(MessageProcessor* mpm) : _mpm(mpm) { } + + LmHandlerResult operator()(MessageType type, JabberMessage msg) + { + return (*this->_mpm)(type, msg); + } +private: + MessageProcessor* _mpm; +}; +*/ + +/** + * Initialize the message -> MessageProcessor map. + * + * \param sm The SessionManager with which all created MessageProcessors should be associated with. + * \param mpm Reference to the MessageProcessorMap to initialize. + */ +void initialize_received_message_processors(SessionManager* sm, MessageProcessorMap& mpm); + +/** + * Clean up the message -> MessageProcessor map. + * + * \param mpm Reference to the MessageProcessorMap to clean up. + */ +void destroy_received_message_processors(MessageProcessorMap& mpm); + +} + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/jabber_whiteboard/message-queue.cpp b/src/jabber_whiteboard/message-queue.cpp new file mode 100644 index 000000000..89c20d66f --- /dev/null +++ b/src/jabber_whiteboard/message-queue.cpp @@ -0,0 +1,138 @@ +/** + * Whiteboard message queue + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +#include "desktop-handles.h" +#include "message-stack.h" + +#include "jabber_whiteboard/session-manager.h" +#include "jabber_whiteboard/message-node.h" +#include "jabber_whiteboard/message-queue.h" + +namespace Inkscape { + +namespace Whiteboard { + +MessageQueue::MessageQueue(SessionManager* sm) : _sm(sm) +{ + +} + +MessageQueue::~MessageQueue() +{ + +} + +MessageNode* +MessageQueue::first() +{ + return this->_queue.front(); +} + +void +MessageQueue::popFront() +{ + this->_queue.pop_front(); +// g_log(NULL, G_LOG_LEVEL_DEBUG, "Removed element, queue size (for %s): %u", lm_connection_get_jid(this->_sm->session_data->connection), this->_queue.size()); +} + +unsigned int +MessageQueue::size() +{ + return this->_queue.size(); +} + +bool +MessageQueue::empty() +{ + return this->_queue.empty(); +} + +void +MessageQueue::clear() +{ + this->_queue.clear(); +} + +ReceiveMessageQueue::ReceiveMessageQueue(SessionManager* sm) : MessageQueue(sm), _latest(0) +{ + +} + +void +ReceiveMessageQueue::insert(MessageNode* msg) +{ + // Check to see if the incoming message has a sequence number + // lower than the sequence number of the latest message processed + // by this message's sender. If it does, drop the message and produce + // a warning. + if (msg->sequence() < this->_latest) { + g_warning("Received late message (message sequence number is %u, but latest processed message had sequence number %u). Discarding message; session may be desynchronized.", msg->sequence(), this->_latest); + return; + } + + // Otherwise, it is safe to insert this message. +// Inkscape::GC::anchor(msg); + this->_queue.push_back(msg); + SP_DT_MSGSTACK(this->_sm->desktop())->flashF(Inkscape::NORMAL_MESSAGE, + ngettext("%u change in receive queue.", + "%u changes in receive queue.", + this->_queue.size()), + this->_queue.size()); +// g_log(NULL, G_LOG_LEVEL_DEBUG, "Receive queue size (for %s): %u", lm_connection_get_jid(this->_sm->session_data->connection), this->_queue.size()); +} + +void +ReceiveMessageQueue::insertDeferred(MessageNode* msg) +{ + this->_deferred.push_back(msg); +} + +void +ReceiveMessageQueue::setLatestProcessedPacket(unsigned int seq) +{ + this->_latest = seq; +} + +SendMessageQueue::SendMessageQueue(SessionManager* sm) : MessageQueue(sm) +{ + +} + +void +SendMessageQueue::insert(MessageNode* msg) +{ +// Inkscape::GC::anchor(msg); + this->_queue.push_back(msg); + SP_DT_MSGSTACK(this->_sm->desktop())->flashF(Inkscape::NORMAL_MESSAGE, + ngettext("%u change in send queue.", + "%u changes in send queue.", + this->_queue.size()), + this->_queue.size()); +// g_log(NULL, G_LOG_LEVEL_DEBUG, "Send queue size (for %s): %u", lm_connection_get_jid(this->_sm->session_data->connection), this->_queue.size()); +} + +} + +} + + +/* + 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 : diff --git a/src/jabber_whiteboard/message-queue.h b/src/jabber_whiteboard/message-queue.h new file mode 100644 index 000000000..e607ed81b --- /dev/null +++ b/src/jabber_whiteboard/message-queue.h @@ -0,0 +1,177 @@ +/** + * Whiteboard message queue + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __WHITEBOARD_MESSAGE_QUEUE_H__ +#define __WHITEBOARD_MESSAGE_QUEUE_H__ + +#include +#include + +#include "gc-alloc.h" + +#include "gc-managed.h" + +#include "util/list-container.h" + +namespace Inkscape { + +namespace Whiteboard { + +class SessionManager; +class MessageNode; + +/// Definition of the basic message node queue +typedef std::list< MessageNode*, GC::Alloc< MessageNode*, GC::MANUAL > > MessageQueueBuffer; + +/** + * MessageQueue interface. + * + * A message queue is used to queue up document change messages for sending and receiving. + * + * Message queues exist to allow us to send/process messages at a given rate rather than + * immediately: this allows us to avoid flooding Jabber servers and clients. + * + * Only one message queue should be created per sender. Message queues store MessageNodes. + * + * \see Inkscape::Whiteboard::MessageNode + */ +class MessageQueue { +public: + /** + * Constructor. + * + * \param sm The SessionManager to associate this MessageQueue with. + */ + MessageQueue(SessionManager *sm); + virtual ~MessageQueue(); + + /** + * Retrieve the MessageNode at the front of the queue. + */ + MessageNode* first(); + + /** + * Remove the element at the front of the queue. + */ + void popFront(); + + /** + * Get the size of the queue. + * + * \return The size of the queue. + */ + unsigned int size(); + + /** + * Returns whether or not the queue is empty. + * + * \return Whether or not the queue is empty. + */ + bool empty(); + + /** + * Clear the queue. + */ + void clear(); + + /** + * The insertion method. The insertion procedure must be defined + * by a subclass. + * + * \param msg The MessageNode to insert. + */ + virtual void insert(MessageNode* msg) = 0; + +protected: + /** + * Implementation of the queue. + */ + MessageQueueBuffer _queue; + + /** + * Pointer to SessionManager. + */ + SessionManager* _sm; +}; + + +/** + * MessageQueue subclass designed to queue up received messages. + * Received messages are dispatched for processing on a periodic basis by a timeout. + * + * \see Inkscape::Whiteboard::Callbacks::dispatchReceiveQueue + */ +class ReceiveMessageQueue : public MessageQueue, public GC::Managed<> { +public: + ReceiveMessageQueue(SessionManager* sm); + + /** + * Insert a message into the queue. + * Late messages (out-of-sequence messages) will be discarded. + * + * \param msg The message node to insert. + */ + void insert(MessageNode* msg); + + /** + * Insert a message into the deferred queue. + * The deferred message queue is used for messages that are not discarded, + * but cannot yet be processed due to missing dependencies. + * + * \param msg The message node to insert. + */ + void insertDeferred(MessageNode* msg); + + /** + * Update the latest processed packet count for this message queue. + * + * \param seq The sequence number of the latest processed packet. + */ + void setLatestProcessedPacket(unsigned int seq); +private: + MessageQueueBuffer _deferred; + unsigned int _latest; +}; + +/** + * MessageQueue subclass designed to queue up messages for sending. + * Messages in this queue are dispatched on a periodic basis by a timeout. + * + * \see Inkscape::Whiteboard::Callbacks::dispatchSendQueue + */ +class SendMessageQueue : public MessageQueue { +public: + SendMessageQueue(SessionManager* sm); + + /** + * Insert a message into the queue. + * + * \param msg The message node to insert. + */ + void insert(MessageNode* msg); +}; + +} + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/jabber_whiteboard/message-tags.cpp b/src/jabber_whiteboard/message-tags.cpp new file mode 100644 index 000000000..a46c27356 --- /dev/null +++ b/src/jabber_whiteboard/message-tags.cpp @@ -0,0 +1,67 @@ +/** + * Whiteboard session manager + * Message tags + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "jabber_whiteboard/message-tags.h" + +namespace Inkscape { + +namespace Whiteboard { + +const char* MESSAGE_CHANGE = "inkboard:change"; +const char* MESSAGE_NEWOBJ = "inkboard:new"; +const char* MESSAGE_DELETE = "inkboard:delete"; +const char* MESSAGE_DOCUMENT = "inkboard:document"; +const char* MESSAGE_NODECONTENT = "inkboard:node-content"; +const char* MESSAGE_ORDERCHANGE = "inkboard:order-change"; +const char* MESSAGE_COMMIT = "inkboard:commit"; +const char* MESSAGE_UNDO = "inkboard:undo"; +const char* MESSAGE_REDO = "inkboard:redo"; +const char* MESSAGE_DOCBEGIN = "inkboard:document-begin"; +const char* MESSAGE_DOCEND = "inkboard:document-end"; +const char* MESSAGE_OBJKEY = "objid"; +const char* MESSAGE_ID = "id"; +const char* MESSAGE_KEY = "key"; +const char* MESSAGE_OLDVAL = "old"; +const char* MESSAGE_NEWVAL = "new"; +const char* MESSAGE_NAME = "name"; +const char* MESSAGE_ISINTERACTIVE = "interactive"; +const char* MESSAGE_DATA = "data"; +const char* MESSAGE_PARENT = "parent"; +const char* MESSAGE_CHILD = "child"; +const char* MESSAGE_REF = "ref"; +const char* MESSAGE_CONTENT = "content"; +const char* MESSAGE_REPEATABLE = "repeatable"; +const char* MESSAGE_CHATROOM = "chatroom"; + +const char* MESSAGE_TYPE = "inkboard-type"; +const char* MESSAGE_NODETYPE = "node-type"; +const char* MESSAGE_FROM = "from"; +const char* MESSAGE_TO = "to"; +const char* MESSAGE_BODY = "body"; +const char* MESSAGE_QUEUE = "queue"; +const char* MESSAGE_SEQNUM = "sequence-number"; +const char* MESSAGE_PROTOCOL_VER = "inkboard-protocol"; + +} + +} + +/* + 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 : diff --git a/src/jabber_whiteboard/message-tags.h b/src/jabber_whiteboard/message-tags.h new file mode 100644 index 000000000..5cbb8a079 --- /dev/null +++ b/src/jabber_whiteboard/message-tags.h @@ -0,0 +1,139 @@ +/** + * Whiteboard session manager + * Message tags + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __WHITEBOARD_MESSAGE_TAGS_H__ +#define __WHITEBOARD_MESSAGE_TAGS_H__ + +namespace Inkscape { + +namespace Whiteboard { +/** + * + * These message tags are not used in all messages. + * They are confined to messages of type CHANGE_* and DOCUMENT_*; + * they define the tags that are used inside those messages' bodies. + */ +// TODO: breaking these up into namespaces would be nice, but it's too much typing +// for now +// +// TODO: Some of these message tags are obsolete, and should be removed... + +/// Message tag signaling an attribute change on a node. +extern char const* MESSAGE_CHANGE; + +/// Message tag signaling a new node. +extern char const* MESSAGE_NEWOBJ; + +/// Message tag signaling a node to remove. +extern char const* MESSAGE_DELETE; + +/// Message tag signaling the beginning of a document synchronization. +extern char const* MESSAGE_DOCUMENT; + +/// Message tag signaling a change in node content. +extern char const* MESSAGE_NODECONTENT; + +/// Message tag signaling a change in node order. +extern char const* MESSAGE_ORDERCHANGE; + +/// Message tag signaling a commit. +extern char const* MESSAGE_COMMIT; + +/// Message tag signaling an undo. +extern char const* MESSAGE_UNDO; + +/// Message tag signaling a redo. +extern char const* MESSAGE_REDO; + +/// Message tag signaling the beginning of a document synchronization. +extern char const* MESSAGE_DOCBEGIN; + +/// Message tag signaling the end of a document synchronization. +extern char const* MESSAGE_DOCEND; + +/// Message tag used to identify an object's key. +extern char const* MESSAGE_OBJKEY; + +/// Message tag used to identify a node ID. +extern char const* MESSAGE_ID; + +/// Message tag used to identify an attribute key. +extern char const* MESSAGE_KEY; + +/// Message tag used to identify an old value (attribute or content). +extern char const* MESSAGE_OLDVAL; + +/// Message tag used to identify a new value (attribute or content). +extern char const* MESSAGE_NEWVAL; + +/// Message tag used to identify a node name. +extern char const* MESSAGE_NAME; + +extern char const* MESSAGE_ISINTERACTIVE; +extern char const* MESSAGE_DATA; + +/// Message tag used to identify the parent of a node by string key. +extern char const* MESSAGE_PARENT; + +/// Message tag used to identify a child node by string key. +extern char const* MESSAGE_CHILD; + +/// Message tag used to identify the node previous to a child node by string key. +extern char const* MESSAGE_REF; + +/// Message tag used to identify the content in a node. +extern char const* MESSAGE_CONTENT; +extern char const* MESSAGE_REPEATABLE; +extern char const* MESSAGE_CHATROOM; + +/** + * These message tags are used in all messages. + */ + +/// Message tag used to identify the message type. +extern char const* MESSAGE_TYPE; + +/// Message tag used to identify the type of node being operated on. +extern char const* MESSAGE_NODETYPE; + +/// Message tag used to identify the sender. +extern char const* MESSAGE_FROM; + +/// Message tag used to identify the recipient. +extern char const* MESSAGE_TO; + +/// Message tag used to identify the body portion of the message. +extern char const* MESSAGE_BODY; +extern char const* MESSAGE_QUEUE; + +/// Message tag used to identify the sequence number of the message. +extern char const* MESSAGE_SEQNUM; + +/// Message tag used to identify the Inkboard protocol version being used. +extern char const* MESSAGE_PROTOCOL_VER; + +} + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/jabber_whiteboard/message-utilities.cpp b/src/jabber_whiteboard/message-utilities.cpp new file mode 100644 index 000000000..431545ac0 --- /dev/null +++ b/src/jabber_whiteboard/message-utilities.cpp @@ -0,0 +1,452 @@ +/** + * Message generation utilities + * + * Authors: + * David Yip + * Jonas Collaros, Stephen Montgomery + * + * Copyright (c) 2004-2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +#include "util/shared-c-string-ptr.h" +#include "util/list.h" + +#include "xml/node.h" +#include "xml/attribute-record.h" +#include "xml/repr.h" + +#include "jabber_whiteboard/defines.h" +#include "jabber_whiteboard/typedefs.h" +#include "jabber_whiteboard/node-utilities.h" +#include "jabber_whiteboard/message-utilities.h" +#include "jabber_whiteboard/node-tracker.h" + +#include + +namespace Inkscape { + +namespace Whiteboard { + +// This method can be instructed to not build a message string but only collect nodes that _would_ be transmitted +// and subsequently added to the tracker. This can be useful in the case where an Inkboard user is the only one +// in a chatroom and therefore needs to fill out the node tracker, but does not need to build the message string. +// This can be controlled with the only_collect_nodes flag, which will only create pointers to new XML::Nodes +// in the maps referenced by newidsbuf and newnodesbuf. Passing NULL as the message buffer has the same effect. +// +// only_collect_nodes defaults to false because most invocations of this method also use the message string. +void +MessageUtilities::newObjectMessage(Glib::ustring* msgbuf, KeyToNodeMap& newidsbuf, NodeToKeyMap& newnodesbuf, NewChildObjectMessageList& childmsgbuf, XMLNodeTracker* xmt, Inkscape::XML::Node const* node, bool only_collect_nodes, bool collect_children) +{ + // Initialize pointers + std::string id, refid, parentid; + + gchar const* name = NULL; + XML::Node* parent = NULL; + XML::Node* ref = NULL; + + bool only_add_children = false; + + + if (node != NULL) { + parent = sp_repr_parent(node); + if (parent != NULL) { + parentid = NodeUtilities::findNodeID(*parent, xmt, newnodesbuf); + if (parentid.empty()) { + g_warning("Parent %p is not being tracked, creating new ID", parent); + parentid = xmt->generateKey(); + newidsbuf[parentid] = parent; + newnodesbuf[parent] = parentid; + } + + if ( node != parent->firstChild() && parent != NULL ) { + ref = parent->firstChild(); + while (ref->next() != node) { + ref = ref->next(); + } + } + } + + if (ref != NULL) { + refid = NodeUtilities::findNodeID(*ref, xmt, newnodesbuf); + if (refid.empty() && ref != NULL) { + g_warning("Ref %p is not being tracked, creating new ID", ref); + refid = xmt->generateKey(); + newidsbuf[refid] = ref; + newnodesbuf[ref] = refid; + } + } + + name = static_cast< gchar const* >(node->name()); + } + + // Generate an id for this object and append it onto the list, if + // it's not already in the tracker + if (!xmt->isSpecialNode(node->name())) { + if (!xmt->isTracking(*node)) { + id = xmt->generateKey(); + newidsbuf[id] = node; + newnodesbuf[node] = id; + } else { + id = xmt->get(*node); + } + } else { + id = xmt->get(*node); + if (id.empty()) { + g_warning("Node %p (name %s) is a special node, but it could not be found in the node tracker (possible unexpected duplicate?) Generating unique ID anyway.", node, node->name()); + id = xmt->generateKey(); + newidsbuf[id] = node; + newnodesbuf[node] = id; + } + only_add_children = true; + } + + // If we're only adding children (i.e. this is a special node) + // don't process the given node. + if( !only_add_children && !id.empty() && msgbuf != NULL && !only_collect_nodes ) { + // + (*msgbuf) = (*msgbuf) + "<" + MESSAGE_NEWOBJ + ">"; + + // + (*msgbuf) = (*msgbuf) + "<" + MESSAGE_PARENT + ">"; + + if(!parentid.empty()) { + (*msgbuf) += parentid; + } + + // id + (*msgbuf) = (*msgbuf) + ""; + + (*msgbuf) = (*msgbuf) + "<" + MESSAGE_CHILD + ">"; + (*msgbuf) += id; + + (*msgbuf) = (*msgbuf) + ""; + + if(!refid.empty()) { + // refid + (*msgbuf) = (*msgbuf) + "<" + MESSAGE_REF + ">"; + + (*msgbuf) += refid; + + (*msgbuf) = (*msgbuf) + ""; + } + + // *node.type() + (*msgbuf) = (*msgbuf) + "<" + MESSAGE_NODETYPE + ">" + NodeUtilities::nodeTypeToString(*node); + (*msgbuf) = (*msgbuf) + ""; + + if (node->content() != NULL) { + // node->content() + (*msgbuf) = (*msgbuf) + "<" + MESSAGE_CONTENT + ">" + node->content(); + (*msgbuf) = (*msgbuf) + ""; + } + + // name + (*msgbuf) = (*msgbuf) + "<" + MESSAGE_NAME + ">"; + + if( name != NULL ) { + (*msgbuf) += name; + } + + (*msgbuf) = (*msgbuf) + ""; + + // + (*msgbuf) = (*msgbuf) + ""; + } else if (id.empty()) { + // if ID is NULL, then we have a real problem -- we were not able to find a key + // nor generate one. The only thing we can really do here is abort, since we have + // no way to let the other client(s) uniquely identify this object. + /* FIXME: If this indicates a programming bug, then don't request translation with + * _(...): it is most useful in untranslated form so that developers may search for + * it when someone reports it in a bug report (as we want users to do for all bugs, + * as indicated by it being a g_warning string). + * + * Otherwise, if it is not a programming bug but a network error or a bug in the + * remote peer (perhaps running different software) or whatever, then present it in + * an alert box, and avoid use of technical jargon `NULL'. + */ + g_warning(_("ID for new object is NULL even after generation and lookup attempts: the new object will NOT be sent, nor will any of its child objects!")); + return; + } else { + + } + + + if (!only_collect_nodes && msgbuf != NULL && !id.empty()) { + // Collect new object's attributes and append them onto the msgbuf + Inkscape::Util::List attrlist = node->attributeList(); + + for(; attrlist; attrlist++) { + MessageUtilities::objectChangeMessage(msgbuf, xmt, id, g_quark_to_string(attrlist->key), NULL, attrlist->value, false); + } + } + + if (!only_collect_nodes) { + childmsgbuf.push_back(*msgbuf); + } + + if (!id.empty() && collect_children) { + Glib::ustring childbuf; + // Collect any child objects of this new object + for ( Inkscape::XML::Node const *child = node->firstChild(); child != NULL; child = child->next() ) { + childbuf.clear(); + MessageUtilities::newObjectMessage(&childbuf, newidsbuf, newnodesbuf, childmsgbuf, xmt, child, only_collect_nodes); + if (!only_collect_nodes) { + // we're recursing down the tree, so we're picking up child nodes first + // and parents afterwards +// childmsgbuf.push_front(childbuf); + } + + } + } +} + +void +MessageUtilities::objectChangeMessage(ustring* msgbuf, XMLNodeTracker* xmt, std::string const id, gchar const* key, gchar const* oldval, gchar const* newval, bool is_interactive) +{ + // Construct message + + // id + (*msgbuf) = (*msgbuf) + "<" + MESSAGE_CHANGE + ">"; + (*msgbuf) = (*msgbuf) + "<" + MESSAGE_ID + ">"; + (*msgbuf) += id; + (*msgbuf) = (*msgbuf) + ""; + + // key + (*msgbuf) = (*msgbuf) + "<" + MESSAGE_KEY + ">"; + if (key != NULL) { + (*msgbuf) += key; + } + (*msgbuf) = (*msgbuf) + ""; + + // oldval + (*msgbuf) = (*msgbuf) + "<" + MESSAGE_OLDVAL + ">"; + if (oldval != NULL) { + (*msgbuf) += oldval; + } + (*msgbuf) = (*msgbuf) + ""; + + // newval + (*msgbuf) = (*msgbuf) + "<" + MESSAGE_NEWVAL + ">"; + if (newval != NULL) { + (*msgbuf) += newval; + } + (*msgbuf) = (*msgbuf) + ""; + + // is_interactive + (*msgbuf) = (*msgbuf) + "<" + MESSAGE_ISINTERACTIVE + ">"; + if (is_interactive) { + (*msgbuf) += "true"; + } else { + (*msgbuf) += "false"; + } + (*msgbuf) = (*msgbuf) + ""; + + // + (*msgbuf) = (*msgbuf) + ""; +} + +void +MessageUtilities::objectDeleteMessage(Glib::ustring* msgbuf, XMLNodeTracker* xmt, Inkscape::XML::Node const& parent, Inkscape::XML::Node const& child, Inkscape::XML::Node const* prev) +{ + /* + gchar const* parentid = NULL; + gchar const* previd = NULL; + gchar const* childid = NULL; + + childid = child.attribute("id"); + parentid = parent.attribute("id"); + if (prev != NULL) { + previd = prev->attribute("id"); + }*/ + + std::string parentid, previd, childid; + + childid = xmt->get(child); + parentid = xmt->get(parent); + previd = xmt->get(*prev); + + if (!childid.empty()) { + // parentid + (*msgbuf) = (*msgbuf) + "<" + MESSAGE_DELETE + ">" + "<" + MESSAGE_PARENT + ">"; + if (!parentid.empty()) { + (*msgbuf) += parentid; + } + (*msgbuf) = (*msgbuf) + ""; + + // childid + (*msgbuf) = (*msgbuf) + "<" + MESSAGE_CHILD + ">"; + if (!childid.empty()) { + (*msgbuf) += childid; + } + (*msgbuf) = (*msgbuf) + ""; + + // previd + (*msgbuf) = (*msgbuf) + "<" + MESSAGE_REF + ">"; + if (!previd.empty()) { + (*msgbuf) += previd; + } + (*msgbuf) = (*msgbuf) + ""; + + // + (*msgbuf) = (*msgbuf) + ""; + } +} + +void +MessageUtilities::contentChangeMessage(Glib::ustring& msgbuf, std::string const nodeid, Util::SharedCStringPtr old_value, Util::SharedCStringPtr new_value) +{ + if (!nodeid.empty()) { + // + msgbuf = msgbuf + "<" + MESSAGE_NODECONTENT + ">"; + + // nodeid + msgbuf = msgbuf + "<" + MESSAGE_ID + ">"; + msgbuf += nodeid; + msgbuf = msgbuf + ""; + + // old_value + msgbuf = msgbuf + "<" + MESSAGE_OLDVAL + ">"; + msgbuf += old_value.cString(); + msgbuf = msgbuf + ""; + + // new_value + msgbuf = msgbuf + "<" + MESSAGE_NEWVAL + ">"; + msgbuf += new_value.cString(); + msgbuf = msgbuf + ""; + + // + msgbuf = msgbuf + ""; + } +} + +void +MessageUtilities::childOrderChangeMessage(Glib::ustring& msgbuf, std::string const childid, std::string const oldprevid, std::string const newprevid) +{ + if (!childid.empty()) { + // + msgbuf = msgbuf + "<" + MESSAGE_ORDERCHANGE + ">"; + + // nodeid + msgbuf = msgbuf + "<" + MESSAGE_CHILD + ">"; + msgbuf += childid; + msgbuf = msgbuf + ""; + + // oldprevid + /* + msgbuf = msgbuf + "<" + MESSAGE_OLDVAL + ">"; + msgbuf += (*oldprevid); + msgbuf = msgbuf + ""; + */ + + // newprevid + msgbuf = msgbuf + "<" + MESSAGE_NEWVAL + ">"; + msgbuf += newprevid; + msgbuf = msgbuf + ""; + + // + msgbuf = msgbuf + ""; + } +} + + +bool +MessageUtilities::getFirstMessageTag(struct Node& buf, Glib::ustring const& msg) +{ + if (msg.empty()) { + return false; + } + + // See if we have a valid start tag, i.e. < ... >. If we do, + // continue; if not, stop and return NULL. + // + // find_first_of returns ULONG_MAX when it cannot find the first + // instance of the given character. + + Glib::ustring::size_type startDelim = msg.find_first_of('<'); + if (startDelim != ULONG_MAX) { + Glib::ustring::size_type endDelim = msg.find_first_of('>'); + if (endDelim != ULONG_MAX) { + if (endDelim > startDelim) { + buf.tag = msg.substr(startDelim+1, (endDelim-startDelim)-1); + if (buf.tag.find_first_of('/') == ULONG_MAX) { // start tags should not be end tags + + + // construct end tag () + Glib::ustring endTag(buf.tag); + endTag.insert(0, "/"); + + Glib::ustring::size_type endTagLoc = msg.find(endTag, endDelim); + if (endTagLoc != ULONG_MAX) { + buf.data = msg.substr(endDelim+1, ((endTagLoc - 1) - (endDelim + 1))); + buf.next_pos = endTagLoc + endTag.length() + 1; + + return true; + } + } + } + } + } + + return false; +} + +bool +MessageUtilities::findTag(struct Node& buf, Glib::ustring const& msg) +{ + if (msg.empty()) { + return false; + } + + // Read desired tag type out of buffer, and append + // < > to it + + Glib::ustring searchterm("<"); + searchterm += buf.tag; + searchterm + ">"; + + Glib::ustring::size_type tagStart = msg.find(searchterm, 0); + if (tagStart != ULONG_MAX) { + // Find ending tag starting at the point at the end of + // the start tag. + searchterm.insert(1, "/"); + Glib::ustring::size_type tagEnd = msg.find(searchterm, tagStart + searchterm.length()); + if (tagEnd != ULONG_MAX) { + Glib::ustring::size_type start = tagStart + searchterm.length(); + buf.data = msg.substr(start, tagEnd - start); + return true; + } + } + return false; +} + +Glib::ustring +MessageUtilities::makeTagWithContent(Glib::ustring tagname, Glib::ustring content) +{ + Glib::ustring buf; + buf = "<" + tagname + ">"; + buf += content; + buf += ""; + return buf; +} + + +} + +} + + + +/* + 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 : diff --git a/src/jabber_whiteboard/message-utilities.h b/src/jabber_whiteboard/message-utilities.h new file mode 100644 index 000000000..11acb1a95 --- /dev/null +++ b/src/jabber_whiteboard/message-utilities.h @@ -0,0 +1,84 @@ +/** + * Whiteboard session manager + * Message generation utilities + * + * Authors: + * David Yip + * Jonas Collaros, Stephen Montgomery + * + * Copyright (c) 2004-2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __WHITEBOARD_MESSAGE_UTILITIES_H__ +#define __WHITEBOARD_MESSAGE_UTILITIES_H__ + +#include +#include "xml/repr.h" + +#include "xml/node.h" +#include "jabber_whiteboard/typedefs.h" + +using Glib::ustring; + +namespace Inkscape { + +namespace Util { + +class SharedCStringPtr; + +} + +namespace Whiteboard { + +struct Node { + ustring tag; + ustring data; + ustring::size_type next_pos; +}; + +class XMLNodeTracker; + +class MessageUtilities { +public: + // Message generation utilities + static void newObjectMessage(ustring* msgbuf, KeyToNodeMap& newidsbuf, NodeToKeyMap& newnodesbuf, NewChildObjectMessageList& childmsgbuf, XMLNodeTracker* xmt, Inkscape::XML::Node const* node, bool only_collect_nodes = false, bool collect_children = true); + static void objectChangeMessage(ustring* msgbuf, XMLNodeTracker* xmt, std::string const id, gchar const* key, gchar const* oldval, gchar const* newval, bool is_interactive); + static void objectDeleteMessage(ustring* msgbuf, XMLNodeTracker* xmt, Inkscape::XML::Node const& parent, Inkscape::XML::Node const& child, Inkscape::XML::Node const* prev); + static void contentChangeMessage(ustring& msgbuf, std::string const nodeid, Util::SharedCStringPtr old_value, Util::SharedCStringPtr new_value); + static void childOrderChangeMessage(ustring& msgbuf, std::string const childid, std::string const oldprevid, std::string const newprevid); + + // Message parsing utilities + static bool getFirstMessageTag(struct Node& buf, ustring const& msg); + static bool findTag(struct Node& buf, ustring const& msg); + + // Message tag generation utilities + static Glib::ustring makeTagWithContent(Glib::ustring tagname, Glib::ustring content); + +private: + // noncopyable, nonassignable + MessageUtilities(MessageUtilities const&); + MessageUtilities& operator=(MessageUtilities const&); + +}; + +} + +} + + + + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=c++:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : +#endif diff --git a/src/jabber_whiteboard/node-tracker-event-tracker.cpp b/src/jabber_whiteboard/node-tracker-event-tracker.cpp new file mode 100644 index 000000000..da872c5e6 --- /dev/null +++ b/src/jabber_whiteboard/node-tracker-event-tracker.cpp @@ -0,0 +1,56 @@ +/** + * Tracks node add/remove events to an XMLNodeTracker, and eliminates cases such as + * consecutive add/remove. + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "xml/node.h" + +#include "jabber_whiteboard/node-tracker-event-tracker.h" + +namespace Inkscape { + +namespace Whiteboard { + +bool +NodeTrackerEventTracker::tryToTrack(XML::Node* node, NodeTrackerAction action) +{ + // 1. Check if node is being tracked. + NodeActionMap::iterator i = this->_actions.find(node); + if (i != this->_actions.end()) { + // 2a. Check the action. If it is the same as the action we are registering, + // return false. Otherwise, register the action with the actions map + // and return true. + if (i->second == action) { + return false; + } else { + this->_actions[node] = action; + return true; + } + } else { + // 2b. If we aren't tracking this node, insert it with the given action. + this->_actions[node] = action; + return true; + } +} + +} + +} + +/* + 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 : diff --git a/src/jabber_whiteboard/node-tracker-event-tracker.h b/src/jabber_whiteboard/node-tracker-event-tracker.h new file mode 100644 index 000000000..c600592d4 --- /dev/null +++ b/src/jabber_whiteboard/node-tracker-event-tracker.h @@ -0,0 +1,66 @@ +/** + * Tracks node add/remove events to an XMLNodeTracker, and eliminates cases such as + * consecutive add/remove. + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __WHITEBOARD_NODE_TRACKER_EVENT_TRACKER_H__ +#define __WHITEBOARD_NODE_TRACKER_EVENT_TRACKER_H__ + +#include + +#include "jabber_whiteboard/typedefs.h" + +namespace Inkscape { + +namespace Whiteboard { + +typedef std::pair< XML::Node*, std::string > NodeKeyPair; +typedef std::map< XML::Node*, NodeTrackerAction > NodeActionMap; + +class NodeTrackerEventTracker { +public: + NodeTrackerEventTracker() { } + ~NodeTrackerEventTracker() { } + bool tryToTrack(XML::Node* node, NodeTrackerAction action); + + NodeTrackerAction getAction(XML::Node const* node) + { + NodeActionMap::iterator i = this->_actions.find(const_cast< XML::Node* >(node)); + if (i != this->_actions.end()) { + return i->second; + } else { + return NODE_UNKNOWN; + } + } + + void clear() + { + this->_actions.clear(); + } +private: + NodeActionMap _actions; +}; + +} + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/jabber_whiteboard/node-tracker-observer.h b/src/jabber_whiteboard/node-tracker-observer.h new file mode 100644 index 000000000..600500c70 --- /dev/null +++ b/src/jabber_whiteboard/node-tracker-observer.h @@ -0,0 +1,119 @@ +/** + * Convenience base class for XML::NodeObservers that need to extract data + * from an XMLNodeTracker and queue up added or removed nodes for later + * processing + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __WHITEBOARD_NODE_TRACKER_OBSERVER_H__ +#define __WHITEBOARD_NODE_TRACKER_OBSERVER_H__ + +#include "xml/node-observer.h" + +#include "jabber_whiteboard/node-tracker-event-tracker.h" +#include "jabber_whiteboard/node-tracker.h" +#include "jabber_whiteboard/typedefs.h" + +namespace Inkscape { + +namespace XML { + +class Node; + +} + +namespace Whiteboard { + +class NodeTrackerObserver : public XML::NodeObserver { +public: + NodeTrackerObserver(XMLNodeTracker* xnt) : _xnt(xnt) { } + virtual ~NodeTrackerObserver() { } + + // just reinforce the fact that we don't implement any of the + // notification methods here + virtual void notifyChildAdded(XML::Node &node, XML::Node &child, XML::Node *prev)=0; + + virtual void notifyChildRemoved(XML::Node &node, XML::Node &child, XML::Node *prev)=0; + + virtual void notifyChildOrderChanged(XML::Node &node, XML::Node &child, + XML::Node *old_prev, XML::Node *new_prev)=0; + + virtual void notifyContentChanged(XML::Node &node, + Util::SharedCStringPtr old_content, + Util::SharedCStringPtr new_content)=0; + + virtual void notifyAttributeChanged(XML::Node &node, GQuark name, + Util::SharedCStringPtr old_value, + Util::SharedCStringPtr new_value)=0; + + + // ...but we do provide node tracking facilities + KeyToNodeActionList& getNodeTrackerActions() + { + return this->newnodes; + } + + KeyToNodeActionList getNodeTrackerActionsCopy() + { + return this->newnodes; + } + + void clearNodeBuffers() + { + this->newnodes.clear(); + this->newkeys.clear(); + this->actions.clear(); + } + +protected: + std::string _findOrGenerateNodeID(XML::Node& node) + { + NodeToKeyMap::iterator i = newkeys.find(&node); + if (i != newkeys.end()) { +// g_log(NULL, G_LOG_LEVEL_DEBUG, "Found key for %p (local): %s", &node, i->second.c_str()); + return i->second; + } else { + std::string nodeid = this->_xnt->get(node); + if (nodeid.empty()) { +// g_log(NULL, G_LOG_LEVEL_DEBUG, "Generating key for %p", &node); + return this->_xnt->generateKey(); + } else { +// g_log(NULL, G_LOG_LEVEL_DEBUG, "Found key for %p (tracker): %s", &node, nodeid.c_str()); + return nodeid; + } + } + } + + KeyToNodeActionList newnodes; + NodeTrackerEventTracker actions; + NodeToKeyMap newkeys; + XMLNodeTracker* _xnt; + +private: + // noncopyable, nonassignable + NodeTrackerObserver(NodeTrackerObserver const& other); + NodeTrackerObserver& operator=(NodeTrackerObserver const& other); + +}; + +} + +} +#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:encoding=utf-8:textwidth=99 : diff --git a/src/jabber_whiteboard/node-tracker.cpp b/src/jabber_whiteboard/node-tracker.cpp new file mode 100644 index 000000000..07f9069eb --- /dev/null +++ b/src/jabber_whiteboard/node-tracker.cpp @@ -0,0 +1,368 @@ +/** + * Whiteboard session manager + * XML node tracking facility + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-object.h" +#include "sp-item-group.h" +#include "document.h" +#include "document-private.h" + +#include "xml/node.h" + +#include "util/compose.hpp" + +#include "jabber_whiteboard/session-manager.h" +#include "jabber_whiteboard/node-tracker.h" + + +// TODO: remove redundant calls to isTracking(); it's a rather unnecessary +// performance burden. +namespace Inkscape { + +namespace Whiteboard { + +// Lookup tables + +/** + * Keys for special nodes. + * + * A special node is a node that can only appear once in a document. + */ +char const* specialnodekeys[] = { + DOCUMENT_ROOT_NODE, + DOCUMENT_NAMEDVIEW_NODE, +}; + +/** + * Names of special nodes. + * + * A special node is a node that can only appear once in a document. + */ +char const* specialnodenames[] = { + DOCUMENT_ROOT_NAME, + DOCUMENT_NAMEDVIEW_NAME, +}; + +XMLNodeTracker::XMLNodeTracker(SessionManager* sm) : + _rootKey(DOCUMENT_ROOT_NODE), + _namedviewKey(DOCUMENT_NAMEDVIEW_NODE) +{ + this->_sm = sm; + this->_counter = 0; + + // Construct special node maps + this->createSpecialNodeTables(); + this->reset(); +} + +XMLNodeTracker::~XMLNodeTracker() +{ + this->_clear(); +} + +void +XMLNodeTracker::put(std::string key, XML::Node const& node) +{ + this->put(key, const_cast< XML::Node& >(node)); +} + +void +XMLNodeTracker::put(std::string key, XML::Node& node) +{ + KeyToTrackerNodeMap::iterator i = this->_keyToNode.find(key); + if (i != this->_keyToNode.end()) { + this->_keyToNode.erase(i); + } + this->_keyToNode.insert(std::make_pair< std::string, XML::Node* >(key, &node)); + + TrackerNodeToKeyMap::iterator j = this->_nodeToKey.find(&node); + if (j != this->_nodeToKey.end()) { + this->_nodeToKey.erase(j); + } + this->_nodeToKey.insert(std::make_pair< XML::Node*, std::string >(&node, key)); +} + +void +XMLNodeTracker::put(KeyToNodeMap& newids, NodeToKeyMap& newnodes) +{ + // TODO: redo + KeyToNodeMap::iterator i = newids.begin(); + + for(; i != newids.end(); i++) { + this->put((*i).first, *((*i).second)); + } +} + +void +XMLNodeTracker::process(KeyToNodeActionList& actions) +{ + KeyToNodeActionList::iterator i = actions.begin(); + for(; i != actions.end(); i++) { + // Get the action to perform. + SerializedEventNodeAction action = *i; + switch(action.second) { + case NODE_ADD: + this->put(action.first.first, *action.first.second); + break; + case NODE_REMOVE: + // this->remove(const_cast< XML::Node& >(*action.first.second)); + break; + default: + break; + } + } +} + +XML::Node* +XMLNodeTracker::get(std::string& key) +{ + KeyToTrackerNodeMap::iterator i = this->_keyToNode.find(key); + if (i != this->_keyToNode.end()) { + return (*i).second; + } else { + g_warning("Key %s is not being tracked!", key.c_str()); + return NULL; + } +} + +XML::Node* +XMLNodeTracker::get(std::string const& key) +{ + return this->get(const_cast< std::string& >(key)); +} + + +std::string const +XMLNodeTracker::get(XML::Node& node) +{ + TrackerNodeToKeyMap::iterator i = this->_nodeToKey.find(&node); + if (i != this->_nodeToKey.end()) { + return (*i).second; + } else { + return ""; + } +} + +std::string const +XMLNodeTracker::get(XML::Node const& node) +{ + return this->get(const_cast< XML::Node& >(node)); +} + +bool +XMLNodeTracker::isTracking(std::string& key) +{ + return (this->_keyToNode.find(key) != this->_keyToNode.end()); +} + +bool +XMLNodeTracker::isTracking(std::string const& key) +{ + return this->isTracking(const_cast< std::string& >(key)); +} + +bool +XMLNodeTracker::isTracking(XML::Node& node) +{ + return (this->_nodeToKey.find(&node) != this->_nodeToKey.end()); +} + +bool +XMLNodeTracker::isTracking(XML::Node const& node) +{ + return this->isTracking(const_cast< XML::Node& >(node)); +} + +bool +XMLNodeTracker::isRootNode(XML::Node& node) +{ + XML::Node* docroot = sp_document_repr_root(this->_sm->document()); + return (docroot == &node); +} + + +void +XMLNodeTracker::remove(std::string& key) +{ + if (this->isTracking(key)) { + XML::Node* element = this->get(key); + this->_keyToNode.erase(key); + this->_nodeToKey.erase(element); + } +} + +void +XMLNodeTracker::remove(XML::Node& node) +{ + if (this->isTracking(node)) { + std::string const element = this->get(node); + this->_nodeToKey.erase(&node); + this->_keyToNode.erase(element); + } +} + +bool +XMLNodeTracker::isSpecialNode(char const* name) +{ + return (this->_specialnodes.find(name) != this->_specialnodes.end()); +} + +bool +XMLNodeTracker::isSpecialNode(std::string const& name) +{ + return (this->_specialnodes.find(name.data()) != this->_specialnodes.end()); +} + +std::string const +XMLNodeTracker::getSpecialNodeKeyFromName(Glib::ustring const& name) +{ + return this->_specialnodes[name.data()]; +} + +std::string const +XMLNodeTracker::getSpecialNodeKeyFromName(Glib::ustring const* name) +{ + return this->_specialnodes[name->data()]; +} + +std::string +XMLNodeTracker::generateKey(gchar const* JID) +{ + return String::compose("%1;%2", this->_counter++, JID); +} + +std::string +XMLNodeTracker::generateKey() +{ + SessionData* sd = this->_sm->session_data; + std::bitset< NUM_FLAGS >& status = sd->status; + if (status[IN_CHATROOM]) { + // This is not strictly required for chatrooms: chatrooms will + // function just fine with the user-to-user ID scheme. However, + // the user-to-user scheme can lead to loss of anonymity + // in anonymous chat rooms, since it contains the real JID + // of a user. + return String::compose("%1;%2@%3/%4", this->_counter++, sd->chat_name, sd->chat_server, sd->chat_handle); + } else { + return String::compose("%1;%2", this->_counter++, lm_connection_get_jid(sd->connection)); + } +} + +void +XMLNodeTracker::createSpecialNodeTables() +{ + int const sz = sizeof(specialnodekeys) / sizeof(char const*); + for(int i = 0; i < sz; i++) { + this->_specialnodes[specialnodenames[i]] = specialnodekeys[i]; + } +} + + +// rather nasty and crufty debugging function +void +XMLNodeTracker::dump() +{ + g_log(NULL, G_LOG_LEVEL_DEBUG, "XMLNodeTracker dump for %s", lm_connection_get_jid(this->_sm->session_data->connection)); + KeyToTrackerNodeMap::iterator i = this->_keyToNode.begin(); + TrackerNodeToKeyMap::iterator j = this->_nodeToKey.begin(); + std::map< char const*, char const* >::iterator k = this->_specialnodes.begin(); + + + g_log(NULL, G_LOG_LEVEL_DEBUG, "%u entries in keyToNode, %u entries in nodeToKey", this->_keyToNode.size(), this->_nodeToKey.size()); + + if (this->_keyToNode.size() != this->_nodeToKey.size()) { + g_warning("Map sizes do not match!"); + } + + g_log(NULL, G_LOG_LEVEL_DEBUG, "XMLNodeTracker keyToNode dump"); + while(i != this->_keyToNode.end()) { + if (!((*i).first.empty())) { + if ((*i).second != NULL) { + g_log(NULL, G_LOG_LEVEL_DEBUG, "%s\t->\t%p (%s) (%s)", (*i).first.c_str(), (*i).second, (*i).second->name(), (*i).second->content()); + } else { + g_log(NULL, G_LOG_LEVEL_DEBUG, "%s\t->\t(null)", (*i).first.c_str()); + } + } else { + g_log(NULL, G_LOG_LEVEL_DEBUG, "(null)\t->\t%p (%s)", (*i).second, (*i).second->name()); + } + i++; + } + + g_log(NULL, G_LOG_LEVEL_DEBUG, "XMLNodeTracker nodeToKey dump"); + while(j != this->_nodeToKey.end()) { + if (!((*j).second.empty())) { + if ((*j).first) { + g_log(NULL, G_LOG_LEVEL_DEBUG, "%p\t->\t%s (parent %p)", (*j).first, (*j).second.c_str(), (*j).first->parent()); + } else { + g_log(NULL, G_LOG_LEVEL_DEBUG, "(null)\t->\t%s", (*j).second.c_str()); + } + } else { + g_log(NULL, G_LOG_LEVEL_DEBUG, "%p\t->\t(null)", (*j).first); + } + j++; + } + + g_log(NULL, G_LOG_LEVEL_DEBUG, "_specialnodes dump"); + while(k != this->_specialnodes.end()) { + g_log(NULL, G_LOG_LEVEL_DEBUG, "%s\t->\t%s", (*k).first, (*k).second); + k++; + } +} + +void +XMLNodeTracker::reset() +{ + this->_clear(); + + // Find and insert special nodes + // root node + this->put(this->_rootKey, *(sp_document_repr_root(this->_sm->document()))); + + // namedview node + SPObject* namedview = sp_item_group_get_child_by_name((SPGroup *)this->_sm->document()->root, NULL, DOCUMENT_NAMEDVIEW_NAME); + if (!namedview) { + g_warning("namedview node does not exist; it will be created during synchronization"); + } else { + this->put(this->_namedviewKey, *(SP_OBJECT_REPR(namedview))); + } +} + +void +XMLNodeTracker::_clear() +{ + // Remove all keys in both trackers, and delete each key. + this->_keyToNode.clear(); + this->_nodeToKey.clear(); + + /* + TrackerNodeToKeyMap::iterator j = this->_nodeToKey.begin(); + for(; j != this->_nodeToKey.end(); j++) { + this->_nodeToKey.erase(j); + } + */ +} + +} + +} + + + +/* + 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 : diff --git a/src/jabber_whiteboard/node-tracker.h b/src/jabber_whiteboard/node-tracker.h new file mode 100644 index 000000000..9c92b815a --- /dev/null +++ b/src/jabber_whiteboard/node-tracker.h @@ -0,0 +1,300 @@ +/** + * Whiteboard session manager + * XML node tracking facility + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __WHITEBOARD_XML_NODE_TRACKER_H__ +#define __WHITEBOARD_XML_NODE_TRACKER_H__ + +#include "jabber_whiteboard/tracker-node.h" +#include "jabber_whiteboard/typedefs.h" + +#include +#include +#include +#include + +namespace Inkscape { + +namespace Whiteboard { + +class SessionManager; + +/** + * std::less-like functor for C-style strings. + */ +struct strcmpless : public std::binary_function< char const*, char const*, bool > +{ + bool operator()(char const* _x, char const* _y) const + { + return (strcmp(_x, _y) < 0); + } +}; + + +// TODO: This is a pretty heinous mess of methods that accept +// both pointers and references -- a lot of it has to do with +// XML::Node& in the node observer and XML::Node* elsewhere, +// although some of it (like Glib::ustring const& vs. +// Glib::ustring const*) is completely mea culpa. When possible +// it'd be good to thin this class out. + +/** + * XMLNodeTracker generates and watches unique IDs for XML::Nodes for use in + * document event serialization and deserialization. + * + * More specifically, it has three tasks: + *
    + *
  1. Association XML::Nodes with string IDs, and vice versa.
  2. + *
  3. Facilitation of lookup of a string ID or XML::Node given the other key.
  4. + *
  5. Generation of new string IDs for XML::Nodes.
  6. + *
+ * + * XML::Nodes are assigned an ID that follows one of two forms: + *
    + *
  1. unsigned integer;user JID
  2. + *
  3. unsigned integer;chatroom@conference server/handle
  4. + *
+ * + * Form 1 is used in user-to-user sessions; form 2 is used in chatroom sessions. + */ +class XMLNodeTracker { +public: + /** + * Constructor. + * + * \param sm The SessionManager with which an XMLNodeTracker instance is to be associated with. + */ + XMLNodeTracker(SessionManager* sm); + ~XMLNodeTracker(); + + /** + * Insert a (key,node) pair into the tracker. + * + * \param key The key to associate with the node. + * \param node The node to associate with the key. + */ + void put(std::string key, XML::Node& node); + + /** + * Insert a (key,node) pair into the tracker. + * + * \param key The key to associate with the node. + * \param node The node to associate with the key. + */ + void put(std::string key, XML::Node const& node); + + /** + * Insert a range of (key,node) pairs into the tracker. + * + * The size of the two maps must be the same. + * \param newids The keys to associate with the nodes. + * \param newnodes The nodes to associate with the keys. + */ + void put(KeyToNodeMap& newids, NodeToKeyMap& newnodes); + + /** + * Process a list of node actions to add and remove nodes from the tracker. + * + * \param actions The action list to process. + */ + void process(KeyToNodeActionList& actions); + + /** + * Retrieve an XML::Node given a key. + * + * \param key Reference to a string key. + * \return Pointer to an XML::Node, or NULL if no associated node could be found. + */ + XML::Node* get(std::string& key); + + /** + * Retrieve an XML::Node given a key. + * + * \param key Reference to a const string key. + * \return Pointer to an XML::Node, or NULL if no associated node could be found. + */ + XML::Node* get(std::string const& key); + + /** + * Retrieve a string key given a reference to an XML::Node. + * + * \param node Reference to an XML::Node. + * \return The associated string key, or an empty string if no associated key could be found. + */ + std::string const get(XML::Node& node); + + /** + * Retrieve a string key given a reference to an XML::Node. + * + * \param node Reference to a const XML::Node. + * \return The associated string key, or an empty string if no associated key could be found. + */ + std::string const get(XML::Node const& node); + + /** + * Remove an entry from the tracker based on key. + * + * \param The key of the entry to remove. + */ + void remove(std::string& key); + + /** + * Remove an entry from the tracker based on XML::Node. + * + * \param A reference to the XML::Node associated with the entry to remove. + */ + void remove(XML::Node& node); + + /** + * Return whether or not a (key,node) pair is being tracked, given a string key. + * + * \param The key associated with the pair to check. + * \return Whether or not the pair is being tracked. + */ + bool isTracking(std::string& key); + + /** + * Return whether or not a (key,node) pair is being tracked, given a string key. + * + * \param The key associated with the pair to check. + * \return Whether or not the pair is being tracked. + */ + bool isTracking(std::string const& key); + + /** + * Return whether or not a (key,node) pair is being tracked, given a node. + * + * \param The node associated with the pair to check. + * \return Whether or not the pair is being tracked. + */ + bool isTracking(XML::Node& node); + + /** + * Return whether or not a (key,node) pair is being tracked, given a node. + * + * \param The node associated with the pair to check. + * \return Whether or not the pair is being tracked. + */ + bool isTracking(XML::Node const& node); + + /** + * Return whether or not a node identified by a given name is a special node. + * + * \see Inkscape::Whiteboard::specialnodekeys + * \see Inkscape::Whiteboard::specialnodenames + * + * \param The name associated with the node. + * \return Whether or not the node is a special node. + */ + bool isSpecialNode(char const* name); + + /** + * Return whether or not a node identified by a given name is a special node. + * + * \see Inkscape::Whiteboard::specialnodekeys + * \see Inkscape::Whiteboard::specialnodenames + * + * \param The name associated with the node. + * \return Whether or not the node is a special node. + */ + bool isSpecialNode(std::string const& name); + + /** + * Retrieve the key of a special node given the name of a special node. + * + * \see Inkscape::Whiteboard::specialnodekeys + * \see Inkscape::Whiteboard::specialnodenames + * + * \param The name associated with the node. + * \return The key of the special node. + */ + std::string const getSpecialNodeKeyFromName(Glib::ustring const& name); + + /** + * Retrieve the key of a special node given the name of a special node. + * + * \see Inkscape::Whiteboard::specialnodekeys + * \see Inkscape::Whiteboard::specialnodenames + * + * \param The name associated with the node. + * \return The key of the special node. + */ + std::string const getSpecialNodeKeyFromName(Glib::ustring const* name); + + /** + * Returns whether or not the given node is the root node of the SPDocument associated + * with an XMLNodeTracker's SessionManager. + * + * \param Reference to an XML::Node to test. + * \return Whether or not the given node is the document root node. + */ + bool isRootNode(XML::Node& node); + + /** + * Generate a node key given a JID. + * + * \param The JID to use in the key. + * \return A node string key. + */ + std::string generateKey(gchar const* JID); + + /** + * Generate a node key given the JID specified in the SessionData structure associated + * with an XMLNodeTracker's SessionManager. + * + * \return A node string key. + */ + std::string generateKey(); + + // TODO: remove debugging function + void dump(); + void reset(); + +private: + void createSpecialNodeTables(); + void _clear(); + + unsigned int _counter; + SessionManager* _sm; + + // defined in typedefs.h + KeyToTrackerNodeMap _keyToNode; + TrackerNodeToKeyMap _nodeToKey; + + std::map< char const*, char const*, strcmpless > _specialnodes; + + // Keys for special nodes + std::string _rootKey; + std::string _defsKey; + std::string _namedviewKey; + std::string _metadataKey; + + // noncopyable, nonassignable + XMLNodeTracker(XMLNodeTracker const&); + XMLNodeTracker& operator=(XMLNodeTracker const&); +}; + +} + +} + + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/jabber_whiteboard/node-utilities.cpp b/src/jabber_whiteboard/node-utilities.cpp new file mode 100644 index 000000000..35b2772a1 --- /dev/null +++ b/src/jabber_whiteboard/node-utilities.cpp @@ -0,0 +1,119 @@ +/** + * Whiteboard session manager + * XML node manipulation / retrieval utilities + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "util/shared-c-string-ptr.h" +#include "util/list.h" + +#include "xml/node-observer.h" +#include "xml/attribute-record.h" +#include "xml/repr.h" + +#include "jabber_whiteboard/defines.h" +#include "jabber_whiteboard/typedefs.h" +#include "jabber_whiteboard/node-utilities.h" +#include "jabber_whiteboard/node-tracker.h" +//#include "jabber_whiteboard/node-observer.h" + +namespace Inkscape { + +namespace Whiteboard { + +/* +Inkscape::XML::Node* +NodeUtilities::lookupReprByValue(Inkscape::XML::Node* root, gchar const* key, Glib::ustring const* value) +{ + GQuark const quark = g_quark_from_string(key); + if (root == NULL) { + return NULL; + } + + Inkscape::Util::List attrlist = root->attributeList(); + for( ; attrlist ; attrlist++) { + if ((attrlist->key == quark) && (strcmp(attrlist->value, value->data()) == 0)) { + return root; + } + } + Inkscape::XML::Node* result; + for ( Inkscape::XML::Node* child = root->firstChild() ; child != NULL ; child = child->next() ) { + result = NodeUtilities::lookupReprByValue(child, key, value); + if(result != NULL) { + return result; + } + } + + return NULL; +} +*/ + +Glib::ustring const +NodeUtilities::nodeTypeToString(XML::Node const& node) +{ + switch(node.type()) { + case XML::DOCUMENT_NODE: + return NODETYPE_DOCUMENT_STR; + case XML::ELEMENT_NODE: + return NODETYPE_ELEMENT_STR; + case XML::TEXT_NODE: + return NODETYPE_TEXT_STR; + case XML::COMMENT_NODE: + default: + return NODETYPE_COMMENT_STR; + } +} + +XML::NodeType +NodeUtilities::stringToNodeType(Glib::ustring const& type) +{ + if (type == NODETYPE_DOCUMENT_STR) { + return XML::DOCUMENT_NODE; + } else if (type == NODETYPE_ELEMENT_STR) { + return XML::ELEMENT_NODE; + } else if (type == NODETYPE_TEXT_STR) { + return XML::TEXT_NODE; + } else { + return XML::COMMENT_NODE; + } +} + +std::string const +NodeUtilities::findNodeID(XML::Node const& node, XMLNodeTracker* tracker, NodeToKeyMap const& newnodes) +{ +// g_log(NULL, G_LOG_LEVEL_DEBUG, "Attempting to locate id for %p", &node); + NodeToKeyMap::const_iterator result = newnodes.find(&node); + if (result != newnodes.end()) { +// g_log(NULL, G_LOG_LEVEL_DEBUG, "Located id for %p: %s", &node, (*result).second.c_str()); + return (*result).second; + } + + if (tracker->isTracking(node)) { +// g_log(NULL, G_LOG_LEVEL_DEBUG, "Located id for %p (in tracker): %s", &node, tracker->get(node).c_str()); + return tracker->get(node); + } else { +// g_log(NULL, G_LOG_LEVEL_DEBUG, "Failed to locate id"); + 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:encoding=utf-8:textwidth=99 : diff --git a/src/jabber_whiteboard/node-utilities.h b/src/jabber_whiteboard/node-utilities.h new file mode 100644 index 000000000..c74027de2 --- /dev/null +++ b/src/jabber_whiteboard/node-utilities.h @@ -0,0 +1,61 @@ +/** + * Whiteboard session manager + * XML node manipulation / retrieval utilities + * + * Authors: + * David Yip + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __WHITEBOARD_NODE_UTILITIES_H__ +#define __WHITEBOARD_NODE_UTILITIES_H__ + +#include "jabber_whiteboard/typedefs.h" +#include "xml/node.h" +#include + +namespace Inkscape { + +namespace XML { + +class Node; + +} + +namespace Whiteboard { + +class XMLNodeTracker; + +class NodeUtilities { +public: + // Node utilities +// static Inkscape::XML::Node* lookupReprByValue(Inkscape::XML::Node* root, gchar const* key, ustring const* value); + static Glib::ustring const nodeTypeToString(XML::Node const& node); + static XML::NodeType stringToNodeType(Glib::ustring const& type); + + // Node key search utility method + static std::string const findNodeID(XML::Node const& node, XMLNodeTracker* tracker, NodeToKeyMap const& newnodes); + +private: + // noncopyable, nonassignable + NodeUtilities(NodeUtilities const&); + NodeUtilities& operator=(NodeUtilities const&); +}; + +} + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/jabber_whiteboard/pedrodom.cpp b/src/jabber_whiteboard/pedrodom.cpp new file mode 100644 index 000000000..2ed770a1a --- /dev/null +++ b/src/jabber_whiteboard/pedrodom.cpp @@ -0,0 +1,784 @@ +/* + * Implementation of the Pedro mini-DOM parser and tree + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2005 Bob Jamison + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + + +#include +#include +#include +#ifdef HAVE_MALLOC_H +#include +#endif +#include +#include + + +#include "pedrodom.h" + +namespace Pedro +{ + + + +//######################################################################## +//# E L E M E N T +//######################################################################## + +Element *Element::clone() +{ + Element *elem = new Element(name, value); + elem->parent = parent; + elem->attributes = attributes; + elem->namespaces = namespaces; + + std::vector::iterator iter; + for (iter = children.begin(); iter != children.end() ; iter++) + { + elem->addChild((*iter)->clone()); + } + return elem; +} + + +void Element::findElementsRecursive(std::vector&res, const DOMString &name) +{ + if (getName() == name) + { + res.push_back(this); + } + for (unsigned int i=0; ifindElementsRecursive(res, name); +} + +std::vector Element::findElements(const DOMString &name) +{ + std::vector res; + findElementsRecursive(res, name); + return res; +} + +DOMString Element::getAttribute(const DOMString &name) +{ + for (unsigned int i=0 ; ielems = findElements(tagName); + if (elems.size() <1) + return ""; + DOMString res = elems[0]->getAttribute(attrName); + return res; +} + +DOMString Element::getTagValue(const DOMString &tagName) +{ + std::vectorelems = findElements(tagName); + if (elems.size() <1) + return ""; + DOMString res = elems[0]->getValue(); + return res; +} + +void Element::addChild(Element *child) +{ + if (!child) + return; + child->parent = this; + children.push_back(child); +} + + +void Element::addAttribute(const DOMString &name, const DOMString &value) +{ + Attribute attr(name, value); + attributes.push_back(attr); +} + +void Element::addNamespace(const DOMString &prefix, const DOMString &namespaceURI) +{ + Namespace ns(prefix, namespaceURI); + namespaces.push_back(ns); +} + +void Element::writeIndentedRecursive(FILE *f, int indent) +{ + int i; + if (!f) + return; + //Opening tag, and attributes + for (i=0;i\n"); + + //Between the tags + if (value.size() > 0) + { + for (int i=0;iwriteIndentedRecursive(f, indent+2); + + //Closing tag + for (int i=0; i\n", name.c_str()); +} + +void Element::writeIndented(FILE *f) +{ + writeIndentedRecursive(f, 0); +} + +void Element::print() +{ + writeIndented(stdout); +} + + +//######################################################################## +//# P A R S E R +//######################################################################## + + + +typedef struct + { + char *escaped; + char value; + } EntityEntry; + +static EntityEntry entities[] = +{ + { "&" , '&' }, + { "<" , '<' }, + { ">" , '>' }, + { "'", '\'' }, + { """, '"' }, + { NULL , '\0' } +}; + + + +void Parser::getLineAndColumn(long pos, long *lineNr, long *colNr) +{ + long line = 1; + long col = 1; + for (long i=0 ; i= parselen) + return -1; + currentPosition = pos; + int ch = parsebuf[pos]; + //printf("ch:%c\n", ch); + return ch; +} + + + +DOMString Parser::encode(const DOMString &str) +{ + DOMString ret; + for (unsigned int i=0 ; i') + ret.append(">"); + else if (ch == '\'') + ret.append("'"); + else if (ch == '"') + ret.append("""); + else + ret.push_back(ch); + + } + return ret; +} + + +int Parser::match(long p0, const char *text) +{ + int p = p0; + while (*text) + { + if (peek(p) != *text) + return p0; + p++; text++; + } + return p; +} + + + +int Parser::skipwhite(long p) +{ + + while (p p) + { + p = p2; + while (p"); + if (p2 > p) + { + p = p2; + break; + } + p++; + } + } + XMLCh b = peek(p); + if (!isspace(b)) + break; + p++; + } + return p; +} + +/* modify this to allow all chars for an element or attribute name*/ +int Parser::getWord(int p0, DOMString &buf) +{ + int p = p0; + while (p' || b=='=') + break; + buf.push_back(b); + p++; + } + return p; +} + +int Parser::getQuoted(int p0, DOMString &buf, int do_i_parse) +{ + + int p = p0; + if (peek(p) != '"' && peek(p) != '\'') + return p0; + p++; + + while ( pvalue ; ee++) + { + int p2 = match(p, ee->escaped); + if (p2>p) + { + buf.push_back(ee->value); + p = p2; + found = true; + break; + } + } + if (!found) + { + error("unterminated entity"); + return false; + } + } + else + { + buf.push_back(b); + p++; + } + } + return p; +} + +int Parser::parseVersion(int p0) +{ + //printf("### parseVersion: %d\n", p0); + + int p = p0; + + p = skipwhite(p0); + + if (peek(p) != '<') + return p0; + + p++; + if (p>=parselen || peek(p)!='?') + return p0; + + p++; + + DOMString buf; + + while (p=parselen || peek(p)!='<') + return p0; + + p++; + + if (peek(p)!='!' || peek(p+1)=='-') + return p0; + p++; + + DOMString buf; + while (p') + { + p++; + break; + } + buf.push_back(ch); + p++; + } + + //printf("Got doctype:%s\n",buf.c_str()); + return p; +} + +int Parser::parseElement(int p0, Element *par,int depth) +{ + + int p = p0; + + int p2 = p; + + p = skipwhite(p); + + //## Get open tag + XMLCh ch = peek(p); + if (ch!='<') + return p0; + + p++; + + DOMString openTagName; + p = skipwhite(p); + p = getWord(p, openTagName); + //printf("####tag :%s\n", openTagName.c_str()); + p = skipwhite(p); + + //Add element to tree + Element *n = new Element(openTagName); + n->parent = par; + par->addChild(n); + + // Get attributes + if (peek(p) != '>') + { + while (p') + break; + else if (ch=='/' && p') + { + p++; + //printf("quick close\n"); + return p; + } + } + DOMString attrName; + p2 = getWord(p, attrName); + if (p2==p) + break; + //printf("name:%s",buf); + p=p2; + p = skipwhite(p); + ch = peek(p); + //printf("ch:%c\n",ch); + if (ch!='=') + break; + p++; + p = skipwhite(p); + // ch = parsebuf[p]; + // printf("ch:%c\n",ch); + DOMString attrVal; + p2 = getQuoted(p, attrVal, true); + p=p2+1; + //printf("name:'%s' value:'%s'\n",attrName.c_str(),attrVal.c_str()); + char *namestr = (char *)attrName.c_str(); + if (strncmp(namestr, "xmlns:", 6)==0) + n->addNamespace(attrName, attrVal); + else + n->addAttribute(attrName, attrVal); + } + } + + bool cdata = false; + + p++; + // ### Get intervening data ### */ + DOMString data; + while (pp) + { + p = p2; + while (p"); + if (p2 > p) + { + p = p2; + break; + } + p++; + } + } + + ch = peek(p); + //# END TAG + if (ch=='<' && !cdata && peek(p+1)=='/') + { + break; + } + //# CDATA + p2 = match(p, " p) + { + cdata = true; + p = p2; + continue; + } + + //# CHILD ELEMENT + if (ch == '<') + { + p2 = parseElement(p, n, depth+1); + if (p2 == p) + { + /* + printf("problem on element:%s. p2:%d p:%d\n", + openTagName.c_str(), p2, p); + */ + return p0; + } + p = p2; + continue; + } + //# ENTITY + if (ch=='&' && !cdata) + { + bool found = false; + for (EntityEntry *ee = entities ; ee->value ; ee++) + { + int p2 = match(p, ee->escaped); + if (p2>p) + { + data.push_back(ee->value); + p = p2; + found = true; + break; + } + } + if (!found) + { + error("unterminated entity"); + return -1; + } + continue; + } + + //# NONE OF THE ABOVE + data.push_back(ch); + p++; + }/*while*/ + + + n->value = data; + //printf("%d : data:%s\n",p,data.c_str()); + + //## Get close tag + p = skipwhite(p); + ch = peek(p); + if (ch != '<') + { + error("no < for end tag\n"); + return p0; + } + p++; + ch = peek(p); + if (ch != '/') + { + error("no / on end tag"); + return p0; + } + p++; + ch = peek(p); + p = skipwhite(p); + DOMString closeTagName; + p = getWord(p, closeTagName); + if (openTagName != closeTagName) + { + error("Mismatched closing tag. Expected . Got '%S'.", + openTagName.c_str(), closeTagName.c_str()); + return p0; + } + p = skipwhite(p); + if (peek(p) != '>') + { + error("no > on end tag for '%s'", closeTagName.c_str()); + return p0; + } + p++; + // printf("close element:%s\n",closeTagName.c_str()); + p = skipwhite(p); + return p; +} + + + + +Element *Parser::parse(XMLCh *buf,int pos,int len) +{ + parselen = len; + parsebuf = buf; + Element *rootNode = new Element("root"); + pos = parseVersion(pos); + pos = parseDoctype(pos); + pos = parseElement(pos, rootNode, 0); + return rootNode; +} + + +Element *Parser::parse(const char *buf, int pos, int len) +{ + + XMLCh *charbuf = (XMLCh *)malloc((len+1) * sizeof(XMLCh)); + long i = 0; + while (i< len) + { + charbuf[i] = (XMLCh)buf[i]; + i++; + } + charbuf[i] = '\0'; + Element *n = parse(charbuf, 0, len); + free(charbuf); + return n; +} + +Element *Parser::parse(const DOMString &buf) +{ + long len = buf.size(); + XMLCh *charbuf = (XMLCh *)malloc((len+1) * sizeof(XMLCh)); + long i = 0; + while (i< len) + { + charbuf[i] = (XMLCh)buf[i]; + i++; + } + charbuf[i] = '\0'; + Element *n = parse(charbuf, 0, len); + free(charbuf); + return n; +} + +Element *Parser::parseFile(const char *fileName) +{ + + //##### LOAD INTO A CHAR BUF, THEN CONVERT TO XMLCh + if (!fileName) + return NULL; + + FILE *f = fopen(fileName, "rb"); + if (!f) + return NULL; + + struct stat statBuf; + if (fstat(fileno(f),&statBuf)<0) + { + fclose(f); + return NULL; + } + long filelen = statBuf.st_size; + + //printf("length:%d\n",filelen); + XMLCh *charbuf = (XMLCh *)malloc((filelen+1) * sizeof(XMLCh)); + for (XMLCh *p=charbuf ; !feof(f) ; p++) + { + *p = (XMLCh)fgetc(f); + } + fclose(f); + charbuf[filelen] = '\0'; + + + /* + printf("nrbytes:%d\n",wc_count); + printf("buf:%ls\n======\n",charbuf); + */ + Element *n = parse(charbuf, 0, filelen); + free(charbuf); + return n; +} + + + + + + + +}//namespace Pedro + +#if 0 +//######################################################################## +//# T E S T +//######################################################################## + +bool doTest(char *fileName) +{ + Pedro::Parser parser; + + Pedro::Element *elem = parser.parseFile(fileName); + + if (!elem) + { + printf("Parsing failed\n"); + return false; + } + + elem->print(); + + delete elem; + + return true; +} + + + +int main(int argc, char **argv) +{ + if (argc != 2) + { + printf("usage: %s \n", argv[0]); + return 1; + } + + if (!doTest(argv[1])) + return 1; + + return 0; +} + +#endif + +//######################################################################## +//# E N D O F F I L E +//######################################################################## + + diff --git a/src/jabber_whiteboard/pedrodom.h b/src/jabber_whiteboard/pedrodom.h new file mode 100644 index 000000000..cd10f80c0 --- /dev/null +++ b/src/jabber_whiteboard/pedrodom.h @@ -0,0 +1,308 @@ +#ifndef __PEDRODOM_H__ +#define __PEDRODOM_H__ +/* + * API for the Pedro mini-DOM parser and tree + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2005 Bob Jamison + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include + + +namespace Pedro +{ +typedef std::string DOMString; +typedef unsigned int XMLCh; + + +class Namespace +{ +public: + Namespace() + {} + + Namespace(const DOMString &prefixArg, const DOMString &namespaceURIArg) + { + prefix = prefixArg; + namespaceURI = namespaceURIArg; + } + + Namespace(const Namespace &other) + { + prefix = other.prefix; + namespaceURI = other.namespaceURI; + } + + virtual ~Namespace() + {} + + virtual DOMString getPrefix() + { return prefix; } + + virtual DOMString getNamespaceURI() + { return namespaceURI; } + +protected: + + DOMString prefix; + DOMString namespaceURI; + +}; + +class Attribute +{ +public: + Attribute() + {} + + Attribute(const DOMString &nameArg, const DOMString &valueArg) + { + name = nameArg; + value = valueArg; + } + + Attribute(const Attribute &other) + { + name = other.name; + value = other.value; + } + + virtual ~Attribute() + {} + + virtual DOMString getName() + { return name; } + + virtual DOMString getValue() + { return value; } + +protected: + + DOMString name; + DOMString value; + +}; + + +class Element +{ +friend class Parser; + +public: + Element() + { + parent = NULL; + } + + Element(const DOMString &nameArg) + { + parent = NULL; + name = nameArg; + } + + Element(const DOMString &nameArg, const DOMString &valueArg) + { + parent = NULL; + name = nameArg; + value = valueArg; + } + + Element(const Element &other) + { + parent = other.parent; + children = other.children; + attributes = other.attributes; + namespaces = other.namespaces; + name = other.name; + value = other.value; + } + + virtual Element *clone(); + + virtual ~Element() + { + for (unsigned int i=0 ; i getChildren() + { return children; } + + std::vector findElements(const DOMString &name); + + DOMString getAttribute(const DOMString &name); + + DOMString getTagAttribute(const DOMString &tagName, const DOMString &attrName); + + DOMString getTagValue(const DOMString &tagName); + + void addChild(Element *child); + + void addAttribute(const DOMString &name, const DOMString &value); + + void addNamespace(const DOMString &prefix, const DOMString &namespaceURI); + + + /** + * Prettyprint an XML tree to an output stream. Elements are indented + * according to element hierarchy. + * @param f a stream to receive the output + * @param elem the element to output + */ + void writeIndented(FILE *f); + + /** + * Prettyprint an XML tree to standard output. This is the equivalent of + * writeIndented(stdout). + * @param elem the element to output + */ + void print(); + +protected: + + + void findElementsRecursive(std::vector&res, const DOMString &name); + + void writeIndentedRecursive(FILE *f, int indent); + + Element *parent; + + std::vectorchildren; + + std::vector attributes; + std::vector namespaces; + + DOMString name; + DOMString value; + +}; + + + + + +class Parser +{ +public: + Parser() + { init(); } + + virtual ~Parser() + {} + + /** + * Parse XML in a char buffer. + * @param buf a character buffer to parse + * @param pos position to start parsing + * @param len number of chars, from pos, to parse. + * @return a pointer to the root of the XML document; + */ + Element *parse(const char *buf,int pos,int len); + + /** + * Parse XML in a char buffer. + * @param buf a character buffer to parse + * @param pos position to start parsing + * @param len number of chars, from pos, to parse. + * @return a pointer to the root of the XML document; + */ + Element *parse(const DOMString &buf); + + /** + * Parse a named XML file. The file is loaded like a data file; + * the original format is not preserved. + * @param fileName the name of the file to read + * @return a pointer to the root of the XML document; + */ + Element *parseFile(const char *fileName); + + /** + * Utility method to preprocess a string for XML + * output, escaping its entities. + * @param str the string to encode + */ + static DOMString encode(const DOMString &str); + + +private: + + void init() + { + keepGoing = true; + currentNode = NULL; + parselen = 0; + parsebuf = NULL; + currentPosition = 0; + } + + void getLineAndColumn(long pos, long *lineNr, long *colNr); + + void error(char *fmt, ...); + + int peek(long pos); + + int match(long pos, const char *text); + + int skipwhite(long p); + + int getWord(int p0, DOMString &buf); + + int getQuoted(int p0, DOMString &buf, int do_i_parse); + + int parseVersion(int p0); + + int parseDoctype(int p0); + + int parseElement(int p0, Element *par,int depth); + + Element *parse(XMLCh *buf,int pos,int len); + + bool keepGoing; + Element *currentNode; + long parselen; + XMLCh *parsebuf; + DOMString cdatabuf; + long currentPosition; + int colNr; + + +}; + + + +}//namespace Pedro + + +#endif /* __PEDRODOM_H__ */ + +//######################################################################## +//# E N D O F F I L E +//######################################################################## + diff --git a/src/jabber_whiteboard/pedroxmpp.cpp b/src/jabber_whiteboard/pedroxmpp.cpp new file mode 100644 index 000000000..0429a172f --- /dev/null +++ b/src/jabber_whiteboard/pedroxmpp.cpp @@ -0,0 +1,4786 @@ +/* + * Implementation the Pedro mini-XMPP client + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2005 Bob Jamison + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include +#include + +#include + +#include "pedroxmpp.h" +#include "pedrodom.h" + +#ifdef __WIN32__ + +#include + +#else /* UNIX */ + +#include +#include +#include +#include +#include +#include + +#include + +#endif + +#ifdef WITH_SSL +#include +#include +#endif + + +namespace Pedro +{ + +//######################################################################## +//######################################################################## +//### U T I L I T Y +//######################################################################## +//######################################################################## + + +//######################################################################## +//# B A S E 6 4 +//######################################################################## + +//################# +//# ENCODER +//################# + + +static char *base64encode = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/** + * This class is for Base-64 encoding + */ +class Base64Encoder +{ + +public: + + Base64Encoder() + { + reset(); + } + + virtual ~Base64Encoder() + {} + + virtual void reset() + { + outBuf = 0L; + bitCount = 0; + buf = ""; + } + + virtual void append(int ch); + + virtual void append(char *str); + + virtual void append(unsigned char *str, int len); + + virtual void append(const DOMString &str); + + virtual DOMString finish(); + + static DOMString encode(const DOMString &str); + + +private: + + + unsigned long outBuf; + + int bitCount; + + DOMString buf; + +}; + + + +/** + * Writes the specified byte to the output buffer + */ +void Base64Encoder::append(int ch) +{ + outBuf <<= 8; + outBuf |= (ch & 0xff); + bitCount += 8; + if (bitCount >= 24) + { + int indx = (int)((outBuf & 0x00fc0000L) >> 18); + int obyte = (int)base64encode[indx & 63]; + buf.push_back(obyte); + + indx = (int)((outBuf & 0x0003f000L) >> 12); + obyte = (int)base64encode[indx & 63]; + buf.push_back(obyte); + + indx = (int)((outBuf & 0x00000fc0L) >> 6); + obyte = (int)base64encode[indx & 63]; + buf.push_back(obyte); + + indx = (int)((outBuf & 0x0000003fL) ); + obyte = (int)base64encode[indx & 63]; + buf.push_back(obyte); + + bitCount = 0; + outBuf = 0L; + } +} + +/** + * Writes the specified string to the output buffer + */ +void Base64Encoder::append(char *str) +{ + while (*str) + append((int)*str++); +} + +/** + * Writes the specified string to the output buffer + */ +void Base64Encoder::append(unsigned char *str, int len) +{ + while (len>0) + { + append((int)*str++); + len--; + } +} + +/** + * Writes the specified string to the output buffer + */ +void Base64Encoder::append(const DOMString &str) +{ + append((char *)str.c_str()); +} + +/** + * Closes this output stream and releases any system resources + * associated with this stream. + */ +DOMString Base64Encoder::finish() +{ + //get any last bytes (1 or 2) out of the buffer + if (bitCount == 16) + { + outBuf <<= 2; //pad to make 18 bits + + int indx = (int)((outBuf & 0x0003f000L) >> 12); + int obyte = (int)base64encode[indx & 63]; + buf.push_back(obyte); + + indx = (int)((outBuf & 0x00000fc0L) >> 6); + obyte = (int)base64encode[indx & 63]; + buf.push_back(obyte); + + indx = (int)((outBuf & 0x0000003fL) ); + obyte = (int)base64encode[indx & 63]; + buf.push_back(obyte); + + buf.push_back('='); + } + else if (bitCount == 8) + { + outBuf <<= 4; //pad to make 12 bits + + int indx = (int)((outBuf & 0x00000fc0L) >> 6); + int obyte = (int)base64encode[indx & 63]; + buf.push_back(obyte); + + indx = (int)((outBuf & 0x0000003fL) ); + obyte = (int)base64encode[indx & 63]; + buf.push_back(obyte); + + buf.push_back('='); + buf.push_back('='); + } + + DOMString ret = buf; + reset(); + return ret; +} + + +DOMString Base64Encoder::encode(const DOMString &str) +{ + Base64Encoder encoder; + encoder.append(str); + DOMString ret = encoder.finish(); + return ret; +} + + + +//################# +//# DECODER +//################# + +static int base64decode[] = +{ +/*00*/ -1, -1, -1, -1, -1, -1, -1, -1, +/*08*/ -1, -1, -1, -1, -1, -1, -1, -1, +/*10*/ -1, -1, -1, -1, -1, -1, -1, -1, +/*18*/ -1, -1, -1, -1, -1, -1, -1, -1, +/*20*/ -1, -1, -1, -1, -1, -1, -1, -1, +/*28*/ -1, -1, -1, 62, -1, -1, -1, 63, +/*30*/ 52, 53, 54, 55, 56, 57, 58, 59, +/*38*/ 60, 61, -1, -1, -1, -1, -1, -1, +/*40*/ -1, 0, 1, 2, 3, 4, 5, 6, +/*48*/ 7, 8, 9, 10, 11, 12, 13, 14, +/*50*/ 15, 16, 17, 18, 19, 20, 21, 22, +/*58*/ 23, 24, 25, -1, -1, -1, -1, -1, +/*60*/ -1, 26, 27, 28, 29, 30, 31, 32, +/*68*/ 33, 34, 35, 36, 37, 38, 39, 40, +/*70*/ 41, 42, 43, 44, 45, 46, 47, 48, +/*78*/ 49, 50, 51, -1, -1, -1, -1, -1 +}; + +class Base64Decoder +{ +public: + Base64Decoder() + { + reset(); + } + + virtual ~Base64Decoder() + {} + + virtual void reset() + { + inCount = 0; + buf.clear(); + } + + + virtual void append(int ch); + + virtual void append(char *str); + + virtual void append(const DOMString &str); + + std::vector finish(); + + static std::vector decode(const DOMString &str); + + static DOMString decodeToString(const DOMString &str); + +private: + + int inBytes[4]; + int inCount; + std::vector buf; +}; + +/** + * Appends one char to the decoder + */ +void Base64Decoder::append(int ch) +{ + if (isspace(ch)) + return; + else if (ch == '=') //padding + { + inBytes[inCount++] = 0; + } + else + { + int byteVal = base64decode[ch & 0x7f]; + //printf("char:%c %d\n", ch, byteVal); + if (byteVal < 0) + { + //Bad lookup value + } + inBytes[inCount++] = byteVal; + } + + if (inCount >=4 ) + { + unsigned char b0 = ((inBytes[0]<<2) & 0xfc) | ((inBytes[1]>>4) & 0x03); + unsigned char b1 = ((inBytes[1]<<4) & 0xf0) | ((inBytes[2]>>2) & 0x0f); + unsigned char b2 = ((inBytes[2]<<6) & 0xc0) | ((inBytes[3] ) & 0x3f); + buf.push_back(b0); + buf.push_back(b1); + buf.push_back(b2); + inCount = 0; + } + +} + +void Base64Decoder::append(char *str) +{ + while (*str) + append((int)*str++); +} + +void Base64Decoder::append(const DOMString &str) +{ + append((char *)str.c_str()); +} + +std::vector Base64Decoder::finish() +{ + std::vector ret = buf; + reset(); + return ret; +} + +std::vector Base64Decoder::decode(const DOMString &str) +{ + Base64Decoder decoder; + decoder.append(str); + std::vector ret = decoder.finish(); + return ret; +} + +DOMString Base64Decoder::decodeToString(const DOMString &str) +{ + Base64Decoder decoder; + decoder.append(str); + std::vector ret = decoder.finish(); + DOMString buf; + for (unsigned int i=0 ; i>4) & 15 ]); + ret.push_back(sha1hex[ ch & 15 ]); + } + return ret; +} + + +void Sha1::init() +{ + + lenW = 0; + sizeHi = 0; + sizeLo = 0; + + // Initialize H with the magic constants (see FIPS180 for constants) + H[0] = 0x67452301L; + H[1] = 0xefcdab89L; + H[2] = 0x98badcfeL; + H[3] = 0x10325476L; + H[4] = 0xc3d2e1f0L; + + for (int i = 0; i < 80; i++) + W[i] = 0; +} + + +void Sha1::append(unsigned char *dataIn, int len) +{ + // Read the data into W and process blocks as they get full + for (int i = 0; i < len; i++) + { + W[lenW / 4] <<= 8; + W[lenW / 4] |= (unsigned long)dataIn[i]; + if ((++lenW) % 64 == 0) + { + hashblock(); + lenW = 0; + } + sizeLo += 8; + sizeHi += (sizeLo < 8); + } +} + + +void Sha1::finish(unsigned char hashout[20]) +{ + unsigned char pad0x80 = 0x80; + unsigned char pad0x00 = 0x00; + unsigned char padlen[8]; + + // Pad with a binary 1 (e.g. 0x80), then zeroes, then length + padlen[0] = (unsigned char)((sizeHi >> 24) & 255); + padlen[1] = (unsigned char)((sizeHi >> 16) & 255); + padlen[2] = (unsigned char)((sizeHi >> 8) & 255); + padlen[3] = (unsigned char)((sizeHi >> 0) & 255); + padlen[4] = (unsigned char)((sizeLo >> 24) & 255); + padlen[5] = (unsigned char)((sizeLo >> 16) & 255); + padlen[6] = (unsigned char)((sizeLo >> 8) & 255); + padlen[7] = (unsigned char)((sizeLo >> 0) & 255); + + append(&pad0x80, 1); + + while (lenW != 56) + append(&pad0x00, 1); + append(padlen, 8); + + // Output hash + for (int i = 0; i < 20; i++) + { + hashout[i] = (unsigned char)(H[i / 4] >> 24); + H[i / 4] <<= 8; + } + + // Re-initialize the context (also zeroizes contents) + init(); +} + + +#define SHA_ROTL(X,n) ((((X) << (n)) | ((X) >> (32-(n)))) & 0xffffffffL) + +void Sha1::hashblock() +{ + + for (int t = 16; t <= 79; t++) + W[t] = SHA_ROTL(W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16], 1); + + unsigned long A = H[0]; + unsigned long B = H[1]; + unsigned long C = H[2]; + unsigned long D = H[3]; + unsigned long E = H[4]; + + unsigned long TEMP; + + for (int t = 0; t <= 19; t++) + { + TEMP = (SHA_ROTL(A,5) + (((C^D)&B)^D) + + E + W[t] + 0x5a827999L) & 0xffffffffL; + E = D; D = C; C = SHA_ROTL(B, 30); B = A; A = TEMP; + } + for (int t = 20; t <= 39; t++) + { + TEMP = (SHA_ROTL(A,5) + (B^C^D) + + E + W[t] + 0x6ed9eba1L) & 0xffffffffL; + E = D; D = C; C = SHA_ROTL(B, 30); B = A; A = TEMP; + } + for (int t = 40; t <= 59; t++) + { + TEMP = (SHA_ROTL(A,5) + ((B&C)|(D&(B|C))) + + E + W[t] + 0x8f1bbcdcL) & 0xffffffffL; + E = D; D = C; C = SHA_ROTL(B, 30); B = A; A = TEMP; + } + for (int t = 60; t <= 79; t++) + { + TEMP = (SHA_ROTL(A,5) + (B^C^D) + + E + W[t] + 0xca62c1d6L) & 0xffffffffL; + E = D; D = C; C = SHA_ROTL(B, 30); B = A; A = TEMP; + } + + H[0] += A; + H[1] += B; + H[2] += C; + H[3] += D; + H[4] += E; +} + + + + + +//######################################################################## +//# M D 5 +//######################################################################## + +class Md5 +{ +public: + + /** + * + */ + Md5() + { init(); } + + /** + * + */ + virtual ~Md5() + {} + + /** + * Static convenience method. + * @parm digest points to an buffer of 16 unsigned chars + */ + static void hash(unsigned char *dataIn, + unsigned long len, unsigned char *digest); + + static DOMString hashHex(unsigned char *dataIn, unsigned long len); + + /** + * Initialize the context (also zeroizes contents) + */ + virtual void init(); + + /** + * + */ + virtual void append(unsigned char *dataIn, unsigned long len); + + /** + * + */ + virtual void append(const DOMString &str); + + /** + * Finalize and output the hash. + * @parm digest points to an buffer of 16 unsigned chars + */ + virtual void finish(unsigned char *digest); + + + /** + * Same as above , but hex to an output String + */ + virtual DOMString finishHex(); + +private: + + void transform(unsigned long *buf, unsigned long *in); + + unsigned long buf[4]; + unsigned long bits[2]; + unsigned char in[64]; + +}; + + + + +void Md5::hash(unsigned char *dataIn, unsigned long len, unsigned char *digest) +{ + Md5 md5; + md5.append(dataIn, len); + md5.finish(digest); +} + +DOMString Md5::hashHex(unsigned char *dataIn, unsigned long len) +{ + Md5 md5; + md5.append(dataIn, len); + DOMString ret = md5.finishHex(); + return ret; +} + + + +/* + * Note: this code is harmless on little-endian machines. + */ +/* +static void byteReverse(unsigned char *buf, unsigned long longs) +{ + do + { + unsigned long t = (unsigned long) + ((unsigned) buf[3] << 8 | buf[2]) << 16 | + ((unsigned) buf[1] << 8 | buf[0]); + *(unsigned long *) buf = t; + buf += 4; + } while (--longs); +} +*/ + +static void md5_memcpy(void *dest, void *src, int n) +{ + unsigned char *s1 = (unsigned char *)dest; + unsigned char *s2 = (unsigned char *)src; + while (n--) + *s1++ = *s2++; +} + +static void md5_memset(void *dest, char v, int n) +{ + unsigned char *s = (unsigned char *)dest; + while (n--) + *s++ = v; +} + +/** + * Initialize MD5 polynomials and storage + */ +void Md5::init() +{ + buf[0] = 0x67452301; + buf[1] = 0xefcdab89; + buf[2] = 0x98badcfe; + buf[3] = 0x10325476; + + bits[0] = 0; + bits[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void Md5::append(unsigned char *source, unsigned long len) +{ + + // Update bitcount + unsigned long t = bits[0]; + if ((bits[0] = t + ((unsigned long) len << 3)) < t) + bits[1]++;// Carry from low to high + bits[1] += len >> 29; + + //Bytes already in shsInfo->data + t = (t >> 3) & 0x3f; + + + // Handle any leading odd-sized chunks + if (t) + { + unsigned char *p = (unsigned char *) in + t; + t = 64 - t; + if (len < t) + { + md5_memcpy(p, source, len); + return; + } + md5_memcpy(p, source, t); + //byteReverse(in, 16); + transform(buf, (unsigned long *) in); + source += t; + len -= t; + } + + // Process data in 64-byte chunks + while (len >= 64) + { + md5_memcpy(in, source, 64); + //byteReverse(in, 16); + transform(buf, (unsigned long *) in); + source += 64; + len -= 64; + } + + // Handle any remaining bytes of data. + md5_memcpy(in, source, len); +} + +/* + * Update context to reflect the concatenation of another string + */ +void Md5::append(const DOMString &str) +{ + append((unsigned char *)str.c_str(), str.size()); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void Md5::finish(unsigned char *digest) +{ + // Compute number of bytes mod 64 + unsigned int count = (bits[0] >> 3) & 0x3F; + + // Set the first char of padding to 0x80. + // This is safe since there is always at least one byte free + unsigned char *p = in + count; + *p++ = 0x80; + + // Bytes of padding needed to make 64 bytes + count = 64 - 1 - count; + + // Pad out to 56 mod 64 + if (count < 8) + { + // Two lots of padding: Pad the first block to 64 bytes + md5_memset(p, 0, count); + //byteReverse(in, 16); + transform(buf, (unsigned long *) in); + + // Now fill the next block with 56 bytes + md5_memset(in, 0, 56); + } + else + { + // Pad block to 56 bytes + md5_memset(p, 0, count - 8); + } + //byteReverse(in, 14); + + // Append length in bits and transform + ((unsigned long *) in)[14] = bits[0]; + ((unsigned long *) in)[15] = bits[1]; + + transform(buf, (unsigned long *) in); + //byteReverse((unsigned char *) buf, 4); + md5_memcpy(digest, buf, 16); + init(); // Security! ;-) +} + +static char *md5hex = "0123456789abcdef"; + +DOMString Md5::finishHex() +{ + unsigned char hashout[16]; + finish(hashout); + DOMString ret; + for (int i=0 ; i<16 ; i++) + { + unsigned char ch = hashout[i]; + ret.push_back(md5hex[ (ch>>4) & 15 ]); + ret.push_back(md5hex[ ch & 15 ]); + } + return ret; +} + + + +//# The four core functions - F1 is optimized somewhat + +// #define F1(x, y, z) (x & y | ~x & z) +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +// ## This is the central step in the MD5 algorithm. +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + * @parm buf points to an array of 4 unsigned longs + * @parm in points to an array of 16 unsigned longs + */ +void Md5::transform(unsigned long *buf, unsigned long *in) +{ + unsigned long a = buf[0]; + unsigned long b = buf[1]; + unsigned long c = buf[2]; + unsigned long d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[ 0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[ 1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[ 2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[ 3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[ 4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[ 5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[ 6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[ 7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[ 8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[ 9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[ 1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[ 6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[ 0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[ 5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[ 4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[ 9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[ 3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[ 8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[ 2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[ 7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[ 5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[ 8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[ 1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[ 4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[ 7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[ 0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[ 3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[ 6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[ 9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[ 2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[ 0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[ 7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[ 5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[ 3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[ 1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[ 8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[ 6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[ 4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[ 2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[ 9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + + + + + + + +//######################################################################## +//######################################################################## +//### T H R E A D +//######################################################################## +//######################################################################## + + +//######################################################################## +//### T H R E A D +//######################################################################## + +/** + * This is the interface for a delegate class which can + * be run by a Thread. + * Thread thread(runnable); + * thread.start(); + */ +class Runnable +{ +public: + + Runnable() + {} + virtual ~Runnable() + {} + + /** + * The method of a delegate class which can + * be run by a Thread. Thread is completed when this + * method is done. + */ + virtual void run() = 0; + +}; + + + +/** + * A simple wrapper of native threads in a portable class. + * It can be used either to execute its own run() method, or + * delegate to a Runnable class's run() method. + */ +class Thread +{ +public: + + /** + * Create a thread which will execute its own run() method. + */ + Thread() + { runnable = NULL ; started = false; } + + /** + * Create a thread which will run a Runnable class's run() method. + */ + Thread(const Runnable &runner) + { runnable = (Runnable *)&runner; started = false; } + + /** + * This does not kill a spawned thread. + */ + virtual ~Thread() + {} + + /** + * Static method to pause the current thread for a given + * number of milliseconds. + */ + static void sleep(unsigned long millis); + + /** + * This method will be executed if the Thread was created with + * no delegated Runnable class. The thread is completed when + * the method is done. + */ + virtual void run() + {} + + /** + * Starts the thread. + */ + virtual void start(); + + /** + * Calls either this class's run() method, or that of a Runnable. + * A user would normally not call this directly. + */ + virtual void execute() + { + started = true; + if (runnable) + runnable->run(); + else + run(); + } + +private: + + Runnable *runnable; + + bool started; + +}; + + + + + +#ifdef __WIN32__ + + +static DWORD WINAPI WinThreadFunction(LPVOID context) +{ + Thread *thread = (Thread *)context; + thread->execute(); +} + + +void Thread::start() +{ + DWORD dwThreadId; + HANDLE hThread = CreateThread(NULL, 0, WinThreadFunction, + (LPVOID)this, 0, &dwThreadId); + //Make sure the thread is started before 'this' is deallocated + while (!started) + sleep(10); + CloseHandle(hThread); +} + +void Thread::sleep(unsigned long millis) +{ + Sleep(millis); +} + +#else /* UNIX */ + + +void *PthreadThreadFunction(void *context) +{ + Thread *thread = (Thread *)context; + thread->execute(); + return NULL; +} + + +void Thread::start() +{ + pthread_t thread; + + int ret = pthread_create(&thread, NULL, + PthreadThreadFunction, (void *)this); + if (ret != 0) + printf("Thread::start: thread creation failed: %s\n", strerror(ret)); + + //Make sure the thread is started before 'this' is deallocated + while (!started) + sleep(10); + +} + +void Thread::sleep(unsigned long millis) +{ + timespec requested; + requested.tv_sec = millis / 1000; + requested.tv_nsec = (millis % 1000 ) * 1000000L; + nanosleep(&requested, NULL); +} + +#endif + + +//######################################################################## +//######################################################################## +//### S O C K E T +//######################################################################## +//######################################################################## + + + + + +class TcpSocket +{ +public: + + TcpSocket(); + + TcpSocket(const std::string &hostname, int port); + + TcpSocket(const char *hostname, int port); + + TcpSocket(const TcpSocket &other); + + virtual ~TcpSocket(); + + bool isConnected(); + + void enableSSL(bool val); + + bool connect(const std::string &hostname, int portno); + + bool connect(const char *hostname, int portno); + + bool startTls(); + + bool connect(); + + bool disconnect(); + + bool setReceiveTimeout(unsigned long millis); + + long available(); + + bool write(int ch); + + bool write(char *str); + + bool write(const std::string &str); + + int read(); + + std::string readLine(); + +private: + void init(); + + std::string hostname; + int portno; + int sock; + bool connected; + + bool sslEnabled; + + unsigned long receiveTimeout; + +#ifdef WITH_SSL + SSL_CTX *sslContext; + SSL *sslStream; +#endif + +}; + + + +//######################################################################### +//# U T I L I T Y +//######################################################################### + +static void mybzero(void *s, size_t n) +{ + unsigned char *p = (unsigned char *)s; + while (n > 0) + { + *p++ = (unsigned char)0; + n--; + } +} + +static void mybcopy(void *src, void *dest, size_t n) +{ + unsigned char *p = (unsigned char *)dest; + unsigned char *q = (unsigned char *)src; + while (n > 0) + { + *p++ = *q++; + n--; + } +} + + + +//######################################################################### +//# T C P C O N N E C T I O N +//######################################################################### + +TcpSocket::TcpSocket() +{ + init(); +} + + +TcpSocket::TcpSocket(const char *hostnameArg, int port) +{ + init(); + hostname = hostnameArg; + portno = port; +} + +TcpSocket::TcpSocket(const std::string &hostnameArg, int port) +{ + init(); + hostname = hostnameArg; + portno = port; +} + + +#ifdef WITH_SSL + +static void cryptoLockCallback(int mode, int type, const char *file, int line) +{ + //printf("########### LOCK\n"); + static int modes[CRYPTO_NUM_LOCKS]; /* = {0, 0, ... } */ + const char *errstr = NULL; + + int rw = mode & (CRYPTO_READ|CRYPTO_WRITE); + if (!((rw == CRYPTO_READ) || (rw == CRYPTO_WRITE))) + { + errstr = "invalid mode"; + goto err; + } + + if (type < 0 || type >= CRYPTO_NUM_LOCKS) + { + errstr = "type out of bounds"; + goto err; + } + + if (mode & CRYPTO_LOCK) + { + if (modes[type]) + { + errstr = "already locked"; + /* must not happen in a single-threaded program + * (would deadlock) + */ + goto err; + } + + modes[type] = rw; + } + else if (mode & CRYPTO_UNLOCK) + { + if (!modes[type]) + { + errstr = "not locked"; + goto err; + } + + if (modes[type] != rw) + { + errstr = (rw == CRYPTO_READ) ? + "CRYPTO_r_unlock on write lock" : + "CRYPTO_w_unlock on read lock"; + } + + modes[type] = 0; + } + else + { + errstr = "invalid mode"; + goto err; + } + + err: + if (errstr) + { + /* we cannot use bio_err here */ + fprintf(stderr, "openssl (lock_dbg_cb): %s (mode=%d, type=%d) at %s:%d\n", + errstr, mode, type, file, line); + } +} + +static unsigned long cryptoIdCallback() +{ +#ifdef __WIN32__ + unsigned long ret = (unsigned long) GetCurrentThreadId(); +#else + unsigned long ret = (unsigned long) pthread_self(); +#endif + return ret; +} + +#endif + + +TcpSocket::TcpSocket(const TcpSocket &other) +{ + init(); + sock = other.sock; + hostname = other.hostname; + portno = other.portno; +} + +static bool tcp_socket_inited = false; + +void TcpSocket::init() +{ + if (!tcp_socket_inited) + { +#ifdef __WIN32__ + WORD wVersionRequested = MAKEWORD( 2, 2 ); + WSADATA wsaData; + int err = WSAStartup( wVersionRequested, &wsaData ); +#endif +#ifdef WITH_SSL + sslStream = NULL; + sslContext = NULL; + CRYPTO_set_locking_callback(cryptoLockCallback); + CRYPTO_set_id_callback(cryptoIdCallback); + SSL_library_init(); + SSL_load_error_strings(); +#endif + tcp_socket_inited = true; + } + sock = -1; + connected = false; + hostname = ""; + portno = -1; + sslEnabled = false; + receiveTimeout = 0; +} + +TcpSocket::~TcpSocket() +{ + disconnect(); +} + +bool TcpSocket::isConnected() +{ + if (!connected || sock < 0) + return false; + return true; +} + +void TcpSocket::enableSSL(bool val) +{ + sslEnabled = val; +} + + +bool TcpSocket::connect(const char *hostnameArg, int portnoArg) +{ + hostname = hostnameArg; + portno = portnoArg; + return connect(); +} + +bool TcpSocket::connect(const std::string &hostnameArg, int portnoArg) +{ + hostname = hostnameArg; + portno = portnoArg; + return connect(); +} + + + +#ifdef WITH_SSL +/* +static int password_cb(char *buf, int bufLen, int rwflag, void *userdata) +{ + char *password = "password"; + if (bufLen < (int)(strlen(password)+1)) + return 0; + + strcpy(buf,password); + int ret = strlen(password); + return ret; +} + +static void infoCallback(const SSL *ssl, int where, int ret) +{ + switch (where) + { + case SSL_CB_ALERT: + { + printf("## %d SSL ALERT: %s\n", where, SSL_alert_desc_string_long(ret)); + break; + } + default: + { + printf("## %d SSL: %s\n", where, SSL_state_string_long(ssl)); + break; + } + } +} +*/ +#endif + + +bool TcpSocket::startTls() +{ +#ifdef WITH_SSL + sslStream = NULL; + sslContext = NULL; + + //SSL_METHOD *meth = SSLv23_method(); + //SSL_METHOD *meth = SSLv3_client_method(); + SSL_METHOD *meth = TLSv1_client_method(); + sslContext = SSL_CTX_new(meth); + //SSL_CTX_set_info_callback(sslContext, infoCallback); + +#if 0 + char *keyFile = "client.pem"; + char *caList = "root.pem"; + /* Load our keys and certificates*/ + if (!(SSL_CTX_use_certificate_chain_file(sslContext, keyFile))) + { + fprintf(stderr, "Can't read certificate file\n"); + disconnect(); + return false; + } + + SSL_CTX_set_default_passwd_cb(sslContext, password_cb); + + if (!(SSL_CTX_use_PrivateKey_file(sslContext, keyFile, SSL_FILETYPE_PEM))) + { + fprintf(stderr, "Can't read key file\n"); + disconnect(); + return false; + } + + /* Load the CAs we trust*/ + if (!(SSL_CTX_load_verify_locations(sslContext, caList, 0))) + { + fprintf(stderr, "Can't read CA list\n"); + disconnect(); + return false; + } +#endif + + /* Connect the SSL socket */ + sslStream = SSL_new(sslContext); + SSL_set_fd(sslStream, sock); + + if (SSL_connect(sslStream)<=0) + { + fprintf(stderr, "SSL connect error\n"); + disconnect(); + return false; + } + + sslEnabled = true; +#endif /*WITH_SSL*/ + return true; +} + + +bool TcpSocket::connect() +{ + if (hostname.size()<1) + { + printf("open: null hostname\n"); + return false; + } + + if (portno<1) + { + printf("open: bad port number\n"); + return false; + } + + sock = socket(PF_INET, SOCK_STREAM, 0); + if (sock < 0) + { + printf("open: error creating socket\n"); + return false; + } + + char *c_hostname = (char *)hostname.c_str(); + struct hostent *server = gethostbyname(c_hostname); + if (!server) + { + printf("open: could not locate host '%s'\n", c_hostname); + return false; + } + + struct sockaddr_in serv_addr; + mybzero((char *) &serv_addr, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + mybcopy((char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, + server->h_length); + serv_addr.sin_port = htons(portno); + + int ret = ::connect(sock, (const sockaddr *)&serv_addr, sizeof(serv_addr)); + if (ret < 0) + { + printf("open: could not connect to host '%s'\n", c_hostname); + return false; + } + + if (sslEnabled) + { + if (!startTls()) + return false; + } + connected = true; + return true; +} + +bool TcpSocket::disconnect() +{ + bool ret = true; + connected = false; +#ifdef WITH_SSL + if (sslEnabled) + { + if (sslStream) + { + int r = SSL_shutdown(sslStream); + switch(r) + { + case 1: + break; /* Success */ + case 0: + case -1: + default: + //printf("Shutdown failed"); + ret = false; + } + SSL_free(sslStream); + } + if (sslContext) + SSL_CTX_free(sslContext); + } + sslStream = NULL; + sslContext = NULL; +#endif /*WITH_SSL*/ + +#ifdef __WIN32__ + closesocket(sock); +#else + ::close(sock); +#endif + sock = -1; + sslEnabled = false; + + return ret; +} + + + +bool TcpSocket::setReceiveTimeout(unsigned long millis) +{ + receiveTimeout = millis; + return true; +} + +/** + * For normal sockets, return the number of bytes waiting to be received. + * For SSL, just return >0 when something is ready to be read. + */ +long TcpSocket::available() +{ + if (!isConnected()) + return -1; + + long count = 0; +#ifdef __WIN32__ + if (ioctlsocket(sock, FIONREAD, (unsigned long *)&count) != 0) + return -1; +#else + if (ioctl(sock, FIONREAD, &count) != 0) + return -1; +#endif + if (count<=0 && sslEnabled) + { +#ifdef WITH_SSL + return SSL_pending(sslStream); +#endif + } + return count; +} + + + +bool TcpSocket::write(int ch) +{ + if (!isConnected()) + { + printf("write: socket closed\n"); + return false; + } + unsigned char c = (unsigned char)ch; + + if (sslEnabled) + { +#ifdef WITH_SSL + int r = SSL_write(sslStream, &c, 1); + if (r<=0) + { + switch(SSL_get_error(sslStream, r)) + { + default: + printf("SSL write problem"); + return -1; + } + } +#endif + } + else + { + if (send(sock, (const char *)&c, 1, 0) < 0) + //if (send(sock, &c, 1, 0) < 0) + { + printf("write: could not send data\n"); + return false; + } + } + return true; +} + +bool TcpSocket::write(char *str) +{ + if (!isConnected()) + { + printf("write(str): socket closed\n"); + return false; + } + unsigned char *s = (unsigned char *)str; + int len = strlen(str); + + if (sslEnabled) + { +#ifdef WITH_SSL + int r = SSL_write(sslStream, s, len); + if (r<=0) + { + switch(SSL_get_error(sslStream, r)) + { + default: + printf("SSL write problem"); + return -1; + } + } +#endif + } + else + { + if (send(sock, s, len, 0) < 0) + //if (send(sock, &c, 1, 0) < 0) + { + printf("write: could not send data\n"); + return false; + } + } + return true; +} + +bool TcpSocket::write(const std::string &str) +{ + return write((char *)str.c_str()); +} + +int TcpSocket::read() +{ + if (!isConnected()) + return -1; + + //We'll use this loop for timeouts, so that SSL and plain sockets + //will behave the same way + if (receiveTimeout > 0) + { + unsigned long tim = 0; + while (true) + { + int avail = available(); + if (avail > 0) + break; + if (tim >= receiveTimeout) + return -2; + Thread::sleep(20); + tim += 20; + } + } + + //check again + if (!isConnected()) + return -1; + + unsigned char ch; + if (sslEnabled) + { +#ifdef WITH_SSL + if (!sslStream) + return -1; + int r = SSL_read(sslStream, &ch, 1); + unsigned long err = SSL_get_error(sslStream, r); + switch (err) + { + case SSL_ERROR_NONE: + break; + case SSL_ERROR_ZERO_RETURN: + return -1; + case SSL_ERROR_SYSCALL: + printf("SSL read problem(syscall) %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return -1; + default: + printf("SSL read problem %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return -1; + } +#endif + } + else + { + if (recv(sock, (char *)&ch, 1, 0) <= 0) + { + printf("read: could not receive data\n"); + disconnect(); + return -1; + } + } + return (int)ch; +} + +std::string TcpSocket::readLine() +{ + std::string ret; + + while (isConnected()) + { + int ch = read(); + if (ch<0) + return ret; + if (ch=='\r' || ch=='\n') + return ret; + ret.push_back((char)ch); + } + + return ret; +} + + +//######################################################################## +//######################################################################## +//### X M P P +//######################################################################## +//######################################################################## + + + + +//######################################################################## +//# X M P P E V E N T +//######################################################################## + + +XmppEvent::XmppEvent(int type) +{ + eventType = type; + presence = false; + dom = NULL; +} + +XmppEvent::XmppEvent(const XmppEvent &other) +{ + assign(other); +} + +XmppEvent &XmppEvent::operator=(const XmppEvent &other) +{ + assign(other); + return (*this); +} + +XmppEvent::~XmppEvent() +{ + if (dom) + delete dom; +} + +void XmppEvent::assign(const XmppEvent &other) +{ + eventType = other.eventType; + presence = other.presence; + status = other.status; + show = other.show; + to = other.to; + from = other.from; + group = other.group; + data = other.data; + fileName = other.fileName; + fileDesc = other.fileDesc; + fileSize = other.fileSize; + fileHash = other.fileHash; + setDOM(other.dom); +} + +int XmppEvent::getType() const +{ + return eventType; +} + +DOMString XmppEvent::getIqId() const +{ + return iqId; +} + +void XmppEvent::setIqId(const DOMString &val) +{ + iqId = val; +} + +DOMString XmppEvent::getStreamId() const +{ + return streamId; +} + +void XmppEvent::setStreamId(const DOMString &val) +{ + streamId = val; +} + +bool XmppEvent::getPresence() const +{ + return presence; +} + +void XmppEvent::setPresence(bool val) +{ + presence = val; +} + +DOMString XmppEvent::getShow() const +{ + return show; +} + +void XmppEvent::setShow(const DOMString &val) +{ + show = val; +} + +DOMString XmppEvent::getStatus() const +{ + return status; +} + +void XmppEvent::setStatus(const DOMString &val) +{ + status = val; +} + +DOMString XmppEvent::getTo() const +{ + return to; +} + +void XmppEvent::setTo(const DOMString &val) +{ + to = val; +} + +DOMString XmppEvent::getFrom() const +{ + return from; +} + +void XmppEvent::setFrom(const DOMString &val) +{ + from = val; +} + +DOMString XmppEvent::getGroup() const +{ + return group; +} + +void XmppEvent::setGroup(const DOMString &val) +{ + group = val; +} + +DOMString XmppEvent::getData() const +{ + return data; +} + +void XmppEvent::setData(const DOMString &val) +{ + data = val; +} + +DOMString XmppEvent::getFileName() const +{ + return fileName; +} + +void XmppEvent::setFileName(const DOMString &val) +{ + fileName = val; +} + +DOMString XmppEvent::getFileDesc() const +{ + return fileDesc; +} + +void XmppEvent::setFileDesc(const DOMString &val) +{ + fileDesc = val; +} + +long XmppEvent::getFileSize() const +{ + return fileSize; +} + +void XmppEvent::setFileSize(long val) +{ + fileSize = val; +} + +DOMString XmppEvent::getFileHash() const +{ + return fileHash; +} + +void XmppEvent::setFileHash(const DOMString &val) +{ + fileHash = val; +} + +Element *XmppEvent::getDOM() const +{ + return dom; +} + +void XmppEvent::setDOM(const Element *val) +{ + if (!val) + dom = NULL; + else + dom = ((Element *)val)->clone(); +} + + +std::vector XmppEvent::getUserList() const +{ + return userList; +} + +void XmppEvent::setUserList(const std::vector &val) +{ + userList = val; +} + +//######################################################################## +//# X M P P E V E N T T A R G E T +//######################################################################## + +//########################### +//# CONSTRUCTORS +//########################### + +XmppEventTarget::XmppEventTarget() +{ + eventQueueEnabled = false; +} + + +XmppEventTarget::XmppEventTarget(const XmppEventTarget &other) +{ + listeners = other.listeners; + eventQueueEnabled = other.eventQueueEnabled; +} + +XmppEventTarget::~XmppEventTarget() +{ +} + + +//########################### +//# M E S S A G E S +//########################### + +void XmppEventTarget::error(char *fmt, ...) +{ + va_list args; + va_start(args,fmt); + vsnprintf(targetWriteBuf, targetWriteBufLen, fmt, args); + va_end(args) ; + printf("Error:%s\n", targetWriteBuf); + XmppEvent evt(XmppEvent::EVENT_ERROR); + evt.setData(targetWriteBuf); + dispatchXmppEvent(evt); +} + +void XmppEventTarget::status(char *fmt, ...) +{ + va_list args; + va_start(args,fmt); + vsnprintf(targetWriteBuf, targetWriteBufLen, fmt, args); + va_end(args) ; + //printf("Status:%s\n", targetWriteBuf); + XmppEvent evt(XmppEvent::EVENT_STATUS); + evt.setData(targetWriteBuf); + dispatchXmppEvent(evt); +} + + + +//########################### +//# L I S T E N E R S +//########################### + +void XmppEventTarget::dispatchXmppEvent(const XmppEvent &event) +{ + std::vector::iterator iter; + for (iter = listeners.begin(); iter != listeners.end() ; iter++) + (*iter)->processXmppEvent(event); + if (eventQueueEnabled) + eventQueue.push_back(event); +} + +void XmppEventTarget::addXmppEventListener(const XmppEventListener &listener) +{ + XmppEventListener *lsnr = (XmppEventListener *)&listener; + std::vector::iterator iter; + for (iter = listeners.begin(); iter != listeners.end() ; iter++) + if (*iter == lsnr) + return; + listeners.push_back(lsnr); +} + +void XmppEventTarget::removeXmppEventListener(const XmppEventListener &listener) +{ + XmppEventListener *lsnr = (XmppEventListener *)&listener; + std::vector::iterator iter; + for (iter = listeners.begin(); iter != listeners.end() ; iter++) + if (*iter == lsnr) + listeners.erase(iter); +} + +void XmppEventTarget::clearXmppEventListeners() +{ + listeners.clear(); +} + + +//########################### +//# E V E N T Q U E U E +//########################### + +void XmppEventTarget::eventQueueEnable(bool val) +{ + eventQueueEnabled = val; + if (!eventQueueEnabled) + eventQueue.clear(); +} + +int XmppEventTarget::eventQueueAvailable() +{ + return eventQueue.size(); +} + +XmppEvent XmppEventTarget::eventQueuePop() +{ + if (!eventQueueEnabled || eventQueue.size()<1) + { + XmppEvent dummy(XmppEvent::EVENT_NONE); + return dummy; + } + XmppEvent event = *(eventQueue.begin()); + eventQueue.erase(eventQueue.begin()); + return event; +} + + +//######################################################################## +//# X M P P S T R E A M +//######################################################################## + +/** + * + */ +class XmppStream +{ +public: + + /** + * + */ + XmppStream(); + + /** + * + */ + virtual ~XmppStream(); + + /** + * + */ + virtual void reset(); + + /** + * + */ + virtual int getState(); + + /** + * + */ + virtual void setState(int val); + + /** + * + */ + virtual DOMString getStreamId(); + + /** + * + */ + void setStreamId(const DOMString &val); + + /** + * + */ + virtual DOMString getIqId(); + + /** + * + */ + void setIqId(const DOMString &val); + + /** + * + */ + virtual int getSeqNr(); + + /** + * + */ + virtual DOMString getPeerId(); + + /** + * + */ + virtual void setPeerId(const DOMString &val); + + /** + * + */ + int available(); + + /** + * + */ + void receiveData(std::vector &newData); + + /** + * + */ + std::vector read(); + +private: + + + DOMString streamId; + + DOMString iqId; + + DOMString sourceId; + + int state; + + long seqNr; + + std::vector data; +}; + + +/** + * + */ +XmppStream::XmppStream() +{ + reset(); +} + +/** + * + */ +XmppStream::~XmppStream() +{ + reset(); +} + +/** + * + */ +void XmppStream::reset() +{ + state = XmppClient::STREAM_AVAILABLE; + seqNr = 0; + data.clear(); +} + +/** + * + */ +int XmppStream::getState() +{ + return state; +} + +/** + * + */ +void XmppStream::setState(int val) +{ + state = val; +} + +/** + * + */ +DOMString XmppStream::getStreamId() +{ + return streamId; +} + +/** + * + */ +void XmppStream::setStreamId(const DOMString &val) +{ + streamId = val; +} + +/** + * + */ +DOMString XmppStream::getIqId() +{ + return iqId; +} + + +/** + * + */ +void XmppStream::setIqId(const DOMString &val) +{ + iqId = val; +} + +/** + * Source or destination JID + */ +void XmppStream::setPeerId(const DOMString &val) +{ + sourceId = val; +} + +/** + * Source or destination JID + */ +DOMString XmppStream::getPeerId() +{ + return sourceId; +} + +/** + * Stream packet sequence number + */ +int XmppStream::getSeqNr() +{ + seqNr++; + if (seqNr >= 65535) + seqNr = 0; + return seqNr; +} + +/** + * + */ +int XmppStream::available() +{ + return data.size(); +} + +/** + * + */ +void XmppStream::receiveData(std::vector &newData) +{ + std::vector::iterator iter; + for (iter=newData.begin() ; iter!=newData.end() ; iter++) + data.push_back(*iter); +} + +/** + * + */ +std::vector XmppStream::read() +{ + if (state != XmppClient::STREAM_OPEN) + { + std::vectordummy; + return dummy; + } + std::vector ret = data; + data.clear(); + return ret; +} + + + + + + +//######################################################################## +//# X M P P C L I E N T +//######################################################################## +class ReceiverThread : public Runnable +{ +public: + + ReceiverThread(XmppClient &par) : client(par) {} + + virtual ~ReceiverThread() {} + + void run() + { client.receiveAndProcessLoop(); } + +private: + + XmppClient &client; +}; + + +//########################### +//# CONSTRUCTORS +//########################### + +XmppClient::XmppClient() +{ + init(); +} + + +XmppClient::XmppClient(const XmppClient &other) : XmppEventTarget(other) +{ + init(); + assign(other); +} + +void XmppClient::assign(const XmppClient &other) +{ + msgId = other.msgId; + host = other.host; + realm = other.realm; + port = other.port; + username = other.username; + password = other.password; + resource = other.resource; + connected = other.connected; + groupChats = other.groupChats; +} + + +void XmppClient::init() +{ + sock = new TcpSocket(); + msgId = 0; + connected = false; + + for (int i=0 ; i0 ; i--) + if (!isspace(str[i-1])) + break; + int end = i; + if (start>=end) + return ""; + return str.substr(start, end); +} + +//############################################## +//# VARIABLES (ones that need special handling) +//############################################## +/** + * + */ +DOMString XmppClient::getUsername() +{ + return username; +} + +/** + * + */ +void XmppClient::setUsername(const DOMString &val) +{ + int p = strIndex(val, "@"); + if (p > 0) + { + username = val.substr(0, p); + realm = val.substr(p+1, jid.size()-p-1); + } + else + { + realm = host; + username = val; + } +} + +//############################################## +//# CONNECTION +//############################################## + +//####################### +//# RECEIVING +//####################### +DOMString XmppClient::readStanza() +{ + int openCount = 0; + bool inTag = false; + bool slashSeen = false; + bool trivialTag = false; + bool querySeen = false; + bool inQuote = false; + bool textSeen = false; + DOMString buf; + + while (true) + { + int ch = sock->read(); + //printf("%c", ch); fflush(stdout); + if (ch<0) + { + if (ch == -2) //a simple timeout, not an error + { + //Since we are timed out, let's assume that we + //are between chunks of text. Let's reset all states. + //printf("-----#### Timeout\n"); + continue; + } + else + { + keepGoing = false; + if (!sock->isConnected()) + { + disconnect(); + return ""; + } + else + { + error("socket read error"); + disconnect(); + return ""; + } + } + } + buf.push_back(ch); + if (ch == '<') + { + inTag = true; + slashSeen = false; + querySeen = false; + inQuote = false; + textSeen = false; + trivialTag = false; + } + else if (ch == '>') + { + if (!inTag) //unescaped '>' in pcdata? horror + continue; + inTag = false; + if (!trivialTag && !querySeen) + { + if (slashSeen) + openCount--; + else + openCount++; + } + //printf("# openCount:%d t:%d q:%d\n", + // openCount, trivialTag, querySeen); + //check if we are 'balanced', but not a tag + if (openCount <= 0 && !querySeen) + { + break; + } + //we know that this one will be open-ended + if (strIndex(buf, "= 0) + { + buf.append(""); + break; + } + } + else if (ch == '/') + { + if (inTag && !inQuote) + { + slashSeen = true; + if (textSeen) // <--looks like this + trivialTag = true; + } + } + else if (ch == '?') + { + if (inTag && !inQuote) + querySeen = true; + } + else if (ch == '"' || ch == '\'') + { + if (inTag) + inQuote = !inQuote; + } + else + { + if (inTag && !inQuote && !isspace(ch)) + textSeen = true; + } + } + return buf; +} + + + +static bool isGroupChat(Element *root) +{ + if (!root) + return false; + std::vectorelems = root->findElements("x"); + for (unsigned int i=0 ; igetAttribute("xmlns"); + //printf("### XMLNS ### %s\n", xmlns.c_str()); + if (strIndex(xmlns, "http://jabber.org/protocol/muc") >=0 ) + return true; + } + return false; +} + + + + +static bool getGroupIds(const DOMString &jid, + DOMString &groupJid, DOMString &userNick) +{ + int p = strIndex(jid, "/"); + if (p < 0) + { + groupJid = jid; + userNick = ""; + return true; + } + groupJid = jid.substr(0, p); + userNick = jid.substr(p+1, jid.size()-p-1); + return true; +} + + + + +bool XmppClient::processMessage(Element *root) +{ + DOMString from = root->getTagAttribute("message", "from"); + DOMString to = root->getTagAttribute("message", "to"); + DOMString type = root->getTagAttribute("message", "type"); + + //####Check for embedded namespaces here + //# IN BAND BYTESTREAMS + DOMString ibbNamespace = "http://jabber.org/protocol/ibb"; + if (root->getTagAttribute("data", "xmlns") == ibbNamespace) + { + DOMString streamId = root->getTagAttribute("data", "sid"); + if (streamId.size() > 0) + { + for (int i=0 ; igetStreamId().c_str(), streamId.c_str()); + if (ins->getStreamId() == streamId) + { + //# We have a winner + if (ins->getState() != STREAM_OPEN) + { + XmppEvent event(XmppEvent::EVENT_ERROR); + event.setFrom(from); + event.setData("received unrequested stream data"); + dispatchXmppEvent(event); + return true; + } + DOMString data = root->getTagValue("data"); + std::vectorbinData = + Base64Decoder::decode(data); + ins->receiveData(binData); + } + } + } + } + + + //#### NORMAL MESSAGES + DOMString subject = root->getTagValue("subject"); + DOMString body = root->getTagValue("body"); + DOMString thread = root->getTagValue("thread"); + //##rfc 3921, para 2.4. ignore if no recognizable info + if (subject.size() < 1 && body.size()<1 && thread.size()<1) + return true; + + if (type == "groupchat") + { + DOMString fromGid; + DOMString fromNick; + getGroupIds(from, fromGid, fromNick); + //printf("fromGid:%s fromNick:%s\n", + // fromGid.c_str(), fromNick.c_str()); + DOMString toGid; + DOMString toNick; + getGroupIds(to, toGid, toNick); + //printf("toGid:%s toNick:%s\n", + // toGid.c_str(), toNick.c_str()); + + if (fromNick.size() > 0)//normal group message + { + XmppEvent event(XmppEvent::EVENT_MUC_MESSAGE); + event.setGroup(fromGid); + event.setFrom(fromNick); + event.setData(body); + event.setDOM(root); + dispatchXmppEvent(event); + } + else // from the server itself + { + //note the space before, so it doesnt match 'unlocked' + if (strIndex(body, " locked") >= 0) + { + printf("LOCKED!! ;)\n"); + char *fmt = + "" + "" + "" + "\n"; + if (!write(fmt, jid.c_str(), msgId++, fromGid.c_str())) + return false; + } + } + } + else + { + XmppEvent event(XmppEvent::EVENT_MESSAGE); + event.setFrom(from); + event.setData(body); + event.setDOM(root); + dispatchXmppEvent(event); + } + + return true; +} + + + + +bool XmppClient::processPresence(Element *root) +{ + + DOMString from = root->getTagAttribute("presence", "from"); + DOMString to = root->getTagAttribute("presence", "to"); + DOMString presenceStr = root->getTagAttribute("presence", "type"); + bool presence = true; + if (presenceStr == "unavailable") + presence = false; + DOMString status = root->getTagValue("status"); + DOMString show = root->getTagValue("show"); + + if (isGroupChat(root)) + { + DOMString fromGid; + DOMString fromNick; + getGroupIds(from, fromGid, fromNick); + //printf("fromGid:%s fromNick:%s\n", + // fromGid.c_str(), fromNick.c_str()); + DOMString item_jid = root->getTagAttribute("item", "jid"); + if (item_jid == jid) //Me + { + if (presence) + { + groupChatCreate(fromGid); + groupChatUserAdd(fromGid, fromNick); + + XmppEvent event(XmppEvent::EVENT_MUC_JOIN); + event.setGroup(fromGid); + event.setFrom(fromNick); + event.setPresence(presence); + event.setShow(show); + event.setStatus(status); + dispatchXmppEvent(event); + } + else + { + groupChatDelete(fromGid); + groupChatUserDelete(fromGid, fromNick); + + XmppEvent event(XmppEvent::EVENT_MUC_LEAVE); + event.setGroup(fromGid); + event.setFrom(fromNick); + event.setPresence(presence); + event.setShow(show); + event.setStatus(status); + dispatchXmppEvent(event); + } + } + else + { + if (presence) + groupChatUserAdd(fromGid, fromNick); + else + groupChatUserDelete(fromGid, fromNick); + XmppEvent event(XmppEvent::EVENT_MUC_PRESENCE); + event.setGroup(fromGid); + event.setFrom(fromNick); + event.setPresence(presence); + event.setShow(show); + event.setStatus(status); + dispatchXmppEvent(event); + } + } + else + { + XmppEvent event(XmppEvent::EVENT_PRESENCE); + event.setFrom(from); + event.setPresence(presence); + event.setShow(show); + event.setStatus(status); + dispatchXmppEvent(event); + } + + return true; +} + + + +bool XmppClient::processIq(Element *root) +{ + DOMString from = root->getTagAttribute("iq", "from"); + DOMString id = root->getTagAttribute("iq", "id"); + DOMString type = root->getTagAttribute("iq", "type"); + DOMString xmlns = root->getTagAttribute("query", "xmlns"); + + if (id.size()<1) + return true; + + //Group chat + if (strIndex(xmlns, "http://jabber.org/protocol/muc") >=0 ) + { + printf("results of MUC query\n"); + } + //printf("###IQ xmlns:%s\n", xmlns.c_str()); + + //### FILE TRANSFERS + DOMString siNamespace = "http://jabber.org/protocol/si"; + if (root->getTagAttribute("si", "xmlns") == siNamespace) + { + if (type == "set") + { + DOMString streamId = root->getTagAttribute("si", "id"); + DOMString fname = root->getTagAttribute("file", "name"); + DOMString sizeStr = root->getTagAttribute("file", "size"); + DOMString hash = root->getTagAttribute("file", "hash"); + XmppEvent event(XmppEvent::XmppEvent::EVENT_FILE_RECEIVE); + event.setFrom(from); + event.setIqId(id); + event.setStreamId(streamId); + event.setFileName(fname); + event.setFileHash(hash); + event.setFileSize(atol(sizeStr.c_str())); + dispatchXmppEvent(event); + } + else //result + { + printf("Got result\n"); + for (int i=0 ; igetIqId() == id && + from == outf->getPeerId()) + { + if (type == "error") + { + outf->setState(STREAM_ERROR); + error("user '%s' rejected file", from.c_str()); + return true; + } + else if (type == "result") + { + if (outf->getState() == STREAM_OPENING) + { + XmppEvent event(XmppEvent::XmppEvent::EVENT_FILE_ACCEPTED); + event.setFrom(from); + dispatchXmppEvent(event); + outf->setState(STREAM_OPEN); + } + else if (outf->getState() == STREAM_CLOSING) + { + outf->setState(STREAM_CLOSED); + } + return true; + } + } + } + } + return true; + } + + + //### IN-BAND BYTESTREAMS + //### Incoming stream requests + DOMString ibbNamespace = "http://jabber.org/protocol/ibb"; + if (root->getTagAttribute("open", "xmlns") == ibbNamespace) + { + DOMString streamId = root->getTagAttribute("open", "sid"); + XmppEvent event(XmppEvent::XmppEvent::EVENT_STREAM_RECEIVE_INIT); + dispatchXmppEvent(event); + if (streamId.size()>0) + { + for (int i=0 ; igetStreamId() == streamId) + { + ins->setState(STREAM_OPENING); + ins->setIqId(id); + return true; + } + } + } + return true; + } + else if (root->getTagAttribute("close", "xmlns") == ibbNamespace) + { + XmppEvent event(XmppEvent::XmppEvent::EVENT_STREAM_RECEIVE_CLOSE); + dispatchXmppEvent(event); + DOMString streamId = root->getTagAttribute("close", "sid"); + if (streamId.size()>0) + { + for (int i=0 ; igetStreamId() == streamId && + from == ins->getPeerId()) + { + ins->setState(STREAM_CLOSING); + ins->setIqId(id); + return true; + } + } + } + return true; + } + //### Responses to outgoing requests + for (int i=0 ; igetIqId() == id) + { + if (type == "error") + { + outs->setState(STREAM_ERROR); + return true; + } + else if (type == "result") + { + if (outs->getState() == STREAM_OPENING) + { + outs->setState(STREAM_OPEN); + } + else if (outs->getState() == STREAM_CLOSING) + { + outs->setState(STREAM_CLOSED); + } + return true; + } + } + } + + //###Normal Roster stuff + if (root->getTagAttribute("query", "xmlns") == "jabber:iq:roster") + { + roster.clear(); + std::vectorelems = root->findElements("item"); + for (unsigned int i=0 ; igetAttribute("jid"); + DOMString name = item->getAttribute("name"); + DOMString subscription = item->getAttribute("subscription"); + DOMString group = item->getTagValue("group"); + //printf("jid:%s name:%s sub:%s group:%s\n", userJid.c_str(), name.c_str(), + // subscription.c_str(), group.c_str()); + XmppUser user(userJid, name, subscription, group); + roster.push_back(user); + } + XmppEvent event(XmppEvent::XmppEvent::EVENT_ROSTER); + dispatchXmppEvent(event); + } + + return true; +} + + + +bool XmppClient::receiveAndProcess() +{ + if (!keepGoing) + return false; + + Parser parser; + + DOMString recvBuf = readStanza(); + recvBuf = trim(recvBuf); + if (recvBuf.size() < 1) + return true; + + //Ugly hack. Apparently the first char can be dropped on timeouts + //if (recvBuf[0] != '<') + // recvBuf.insert(0, "<"); + + status("RECV: %s", recvBuf.c_str()); + Element *root = parser.parse(recvBuf); + if (!root) + { + printf("Bad elem\n"); + return true; + } + + //#### MESSAGE + std::vectorelems = root->findElements("message"); + if (elems.size()>0) + { + if (!processMessage(root)) + return false; + } + + //#### PRESENCE + elems = root->findElements("presence"); + if (elems.size()>0) + { + if (!processPresence(root)) + return false; + } + + //#### INFO + elems = root->findElements("iq"); + if (elems.size()>0) + { + if (!processIq(root)) + return false; + } + + delete root; + + return true; +} + + +bool XmppClient::receiveAndProcessLoop() +{ + keepGoing = true; + while (true) + { + if (!keepGoing) + { + printf("Abort requested\n"); + break; + } + if (!receiveAndProcess()) + return false; + } + return true; +} + +//####################### +//# SENDING +//####################### + +bool XmppClient::write(char *fmt, ...) +{ + va_list args; + va_start(args,fmt); + vsnprintf((char *)writeBuf, writeBufLen, fmt,args); + va_end(args) ; + status("SEND: %s", writeBuf); + if (!sock->write((char *)writeBuf)) + { + error("Cannot write to socket"); + return false; + } + return true; +} + +//####################### +//# CONNECT +//####################### + +bool XmppClient::checkConnect() +{ + if (!connected) + { + XmppEvent evt(XmppEvent::EVENT_ERROR); + evt.setData("Attempted operation while disconnected"); + dispatchXmppEvent(evt); + return false; + } + return true; +} + +bool XmppClient::iqAuthenticate(const DOMString &streamId) +{ + Parser parser; + + char *fmt = + "" + "%s" + "\n"; + if (!write(fmt, realm.c_str(), msgId++, username.c_str())) + return false; + + DOMString recbuf = readStanza(); + //printf("iq received: '%s'\n", recbuf.c_str()); + Element *elem = parser.parse(recbuf); + //elem->print(); + DOMString iqType = elem->getTagAttribute("iq", "type"); + //printf("##iqType:%s\n", iqType.c_str()); + delete elem; + + if (iqType != "result") + { + error("error:server does not allow login"); + return false; + } + + bool digest = true; + if (digest) + { + //## Digest authentication + DOMString digest = streamId; + digest.append(password); + digest = Sha1::hashHex((unsigned char *)digest.c_str(), digest.size()); + //printf("digest:%s\n", digest.c_str()); + fmt = + "" + "" + "%s" + "%s" + "%s" + "" + "\n"; + if (!write(fmt, msgId++, username.c_str(), + digest.c_str(), resource.c_str())) + return false; + } + else + { + + //## Plaintext authentication + fmt = + "" + "" + "%s" + "%s" + "%s" + "" + "\n"; + if (!write(fmt, msgId++, username.c_str(), + password.c_str(), resource.c_str())) + return false; + } + + recbuf = readStanza(); + //printf("iq received: '%s'\n", recbuf.c_str()); + elem = parser.parse(recbuf); + //elem->print(); + iqType = elem->getTagAttribute("iq", "type"); + //printf("##iqType:%s\n", iqType.c_str()); + delete elem; + + if (iqType != "result") + { + error("server does not allow login"); + return false; + } + + return true; +} + + + +bool XmppClient::saslMd5Authenticate() +{ + Parser parser; + char *fmt = + "\n"; + if (!write(fmt)) + return false; + + DOMString recbuf = readStanza(); + status("challenge received: '%s'", recbuf.c_str()); + Element *elem = parser.parse(recbuf); + //elem->print(); + DOMString b64challenge = elem->getTagValue("challenge"); + delete elem; + + if (b64challenge.size() < 1) + { + error("login: no SASL challenge offered by server"); + return false; + } + DOMString challenge = Base64Decoder::decodeToString(b64challenge); + status("challenge:'%s'", challenge.c_str()); + + unsigned int p1 = challenge.find("nonce=\""); + if (p1 == DOMString::npos) + { + error("login: no SASL nonce sent by server"); + return false; + } + p1 += 7; + unsigned int p2 = challenge.find("\"", p1); + if (p2 == DOMString::npos) + { + error("login: unterminated SASL nonce sent by server"); + return false; + } + DOMString nonce = challenge.substr(p1, p2-p1); + //printf("nonce: '%s'\n", nonce.c_str()); + char idBuf[7]; + snprintf(idBuf, 6, "%dsasl", msgId++); + DOMString cnonce = Sha1::hashHex((unsigned char *)idBuf, 7); + DOMString authzid = username; authzid.append("@"); authzid.append(host); + DOMString digest_uri = "xmpp/"; digest_uri.append(host); + + //## Make A1 + Md5 md5; + md5.append(username); + md5.append(":"); + md5.append(realm); + md5.append(":"); + md5.append(password); + unsigned char a1tmp[16]; + md5.finish(a1tmp); + md5.init(); + md5.append(a1tmp, 16); + md5.append(":"); + md5.append(nonce); + md5.append(":"); + md5.append(cnonce); + md5.append(":"); + md5.append(authzid); + DOMString a1 = md5.finishHex(); + status("##a1:'%s'", a1.c_str()); + + //# Make A2 + md5.init(); + md5.append("AUTHENTICATE:"); + md5.append(digest_uri); + DOMString a2 = md5.finishHex(); + status("##a2:'%s'", a2.c_str()); + + //# Now make the response + md5.init(); + md5.append(a1); + md5.append(":"); + md5.append(nonce); + md5.append(":"); + md5.append("00000001");//nc + md5.append(":"); + md5.append(cnonce); + md5.append(":"); + md5.append("auth");//qop + md5.append(":"); + md5.append(a2); + DOMString response = md5.finishHex(); + + DOMString resp; + resp.append("username=\""); resp.append(username); resp.append("\","); + resp.append("realm=\""); resp.append(realm); resp.append("\","); + resp.append("nonce=\""); resp.append(nonce); resp.append("\","); + resp.append("cnonce=\""); resp.append(cnonce); resp.append("\","); + resp.append("nc=00000001,qop=auth,"); + resp.append("digest-uri=\""); resp.append(digest_uri); resp.append("\"," ); + resp.append("authzid=\""); resp.append(authzid); resp.append("\","); + resp.append("response=\""); resp.append(response); resp.append("\","); + resp.append("charset=utf-8"); + status("sending response:'%s'", resp.c_str()); + resp = Base64Encoder::encode(resp); + status("base64 response:'%s'", resp.c_str()); + fmt = + "%s\n"; + if (!write(fmt, resp.c_str())) + return false; + + recbuf = readStanza(); + status("server says:: '%s'", recbuf.c_str()); + elem = parser.parse(recbuf); + //elem->print(); + b64challenge = elem->getTagValue("challenge"); + delete elem; + + if (b64challenge.size() < 1) + { + error("login: no second SASL challenge offered by server"); + return false; + } + + challenge = Base64Decoder::decodeToString(b64challenge); + status("challenge: '%s'", challenge.c_str()); + p1 = challenge.find("rspauth="); + if (p1 == DOMString::npos) + { + error("login: no SASL respauth sent by server\n"); + return false; + } + + fmt = + "\n"; + if (!write(fmt)) + return false; + + recbuf = readStanza(); + status("server says: '%s", recbuf.c_str()); + elem = parser.parse(recbuf); + //elem->print(); + b64challenge = elem->getTagValue("challenge"); + bool success = (elem->findElements("success").size() > 0); + delete elem; + + return success; +} + +bool XmppClient::saslPlainAuthenticate() +{ + Parser parser; + + DOMString id = username; + //id.append("@"); + //id.append(host); + Base64Encoder encoder; + encoder.append('\0'); + encoder.append(id); + encoder.append('\0'); + encoder.append(password); + DOMString base64Auth = encoder.finish(); + //printf("authbuf:%s\n", base64Auth.c_str()); + + char *fmt = + "%s\n"; + if (!write(fmt, base64Auth.c_str())) + return false; + DOMString recbuf = readStanza(); + status("challenge received: '%s'", recbuf.c_str()); + Element *elem = parser.parse(recbuf); + + bool success = (elem->findElements("success").size() > 0); + delete elem; + + return success; +} + + + +bool XmppClient::saslAuthenticate() +{ + Parser parser; + + DOMString recbuf = readStanza(); + status("RECV: '%s'\n", recbuf.c_str()); + Element *elem = parser.parse(recbuf); + //elem->print(); + + //Check for starttls + bool wantStartTls = false; + if (elem->findElements("starttls").size() > 0) + { + wantStartTls = true; + if (elem->findElements("required").size() > 0) + status("login: STARTTLS required"); + else + status("login: STARTTLS available"); + } + + if (wantStartTls) + { + delete elem; + char *fmt = + "\n"; + if (!write(fmt)) + return false; + recbuf = readStanza(); + status("RECV: '%s'\n", recbuf.c_str()); + elem = parser.parse(recbuf); + if (elem->getTagAttribute("proceed", "xmlns").size()<1) + { + error("Server rejected TLS negotiation"); + disconnect(); + return false; + } + delete elem; + if (!sock->startTls()) + { + error("Could not start TLS"); + disconnect(); + return false; + } + + fmt = + "\n\n"; + if (!write(fmt, realm.c_str())) + return false; + + recbuf = readStanza(); + status("RECVx: '%s'", recbuf.c_str()); + recbuf.append(""); + elem = parser.parse(recbuf); + bool success = + (elem->getTagAttribute("stream:stream", "id").size()>0); + if (!success) + { + error("STARTTLS negotiation failed"); + disconnect(); + return false; + } + delete elem; + recbuf = readStanza(); + status("RECV: '%s'\n", recbuf.c_str()); + elem = parser.parse(recbuf); + } + + //check for sasl authentication mechanisms + std::vector elems = + elem->findElements("mechanism"); + if (elems.size() < 1) + { + error("login: no SASL mechanism offered by server"); + return false; + } + bool md5Found = false; + bool plainFound = false; + for (unsigned int i=0 ; igetValue(); + if (mech == "DIGEST-MD5") + { + status("MD5 authentication offered"); + md5Found = true; + } + else if (mech == "PLAIN") + { + status("PLAIN authentication offered"); + plainFound = true; + } + } + delete elem; + + bool success = false; + if (md5Found) + { + success = saslMd5Authenticate(); + } + else if (plainFound) + { + success = saslPlainAuthenticate(); + } + else + { + error("not able to handle sasl authentication mechanisms"); + return false; + } + + if (success) + status("###### SASL authentication success\n"); + else + error("###### SASL authentication failure\n"); + + return success; +} + + + + + +bool XmppClient::createSession() +{ + + Parser parser; + if (port==443 || port==5223) + sock->enableSSL(true); + if (!sock->connect(host, port)) + { + return false; + } + + char *fmt = + "\n\n"; + if (!write(fmt, realm.c_str())) + return false; + + DOMString recbuf = readStanza(); + //printf("received: '%s'\n", recbuf.c_str()); + recbuf.append(""); + Element *elem = parser.parse(recbuf); + //elem->print(); + bool useSasl = false; + DOMString streamId = elem->getTagAttribute("stream:stream", "id"); + //printf("### StreamID: %s\n", streamId.c_str()); + DOMString streamVersion = elem->getTagAttribute("stream:stream", "version"); + if (streamVersion == "1.0") + useSasl = true; + + if (useSasl) + { + if (!saslAuthenticate()) + return false; + fmt = + "\n\n"; + + if (!write(fmt, realm.c_str())) + return false; + recbuf = readStanza(); + recbuf.append("\n"); + //printf("now server says:: '%s'\n", recbuf.c_str()); + elem = parser.parse(recbuf); + //elem->print(); + delete elem; + + recbuf = readStanza(); + //printf("now server says:: '%s'\n", recbuf.c_str()); + elem = parser.parse(recbuf); + bool hasBind = (elem->findElements("bind").size() > 0); + //elem->print(); + delete elem; + + if (!hasBind) + { + error("no binding provided by server"); + return false; + } + + + } + else // not SASL + { + if (!iqAuthenticate(streamId)) + return false; + } + + + //### Resource binding + fmt = + "" + "" + "%s" + "\n"; + if (!write(fmt, msgId++, resource.c_str())) + return false; + + recbuf = readStanza(); + status("bind result: '%s'", recbuf.c_str()); + elem = parser.parse(recbuf); + //elem->print(); + DOMString bindType = elem->getTagAttribute("iq", "type"); + //printf("##bindType:%s\n", bindType.c_str()); + delete elem; + + if (bindType != "result") + { + error("no binding with server failed"); + return false; + } + + fmt = + "" + "" + "\n"; + if (!write(fmt, msgId++)) + return false; + + recbuf = readStanza(); + status("session received: '%s'", recbuf.c_str()); + elem = parser.parse(recbuf); + //elem->print(); + DOMString sessionType = elem->getTagAttribute("iq", "type"); + //printf("##sessionType:%s\n", sessionType.c_str()); + delete elem; + + if (sessionType != "result") + { + error("no session provided by server"); + return false; + } + + //printf("########## COOL #########\n"); + //Now that we are bound, we have a valid JID + jid = username; + jid.append("@"); + jid.append(realm); + jid.append("/"); + jid.append(resource); + + //We are now done with the synchronous handshaking. Let's go into + //async mode + + fmt = + "\n"; + if (!write(fmt)) + return false; + + fmt = + "\n"; + if (!write(fmt, msgId++)) + return false; + + fmt = + "" + "\n"; + if (!write(fmt, msgId++, realm.c_str())) + return false; + + fmt = + "" + "\n"; + if (!write(fmt, msgId++, realm.c_str())) + return false; + + /* + recbuf = readStanza(); + status("stream received: '%s'", recbuf.c_str()); + elem = parser.parse(recbuf); + //elem->print(); + delete elem; + */ + + //We are now logged in + status("Connected"); + connected = true; + XmppEvent evt(XmppEvent::EVENT_CONNECTED); + dispatchXmppEvent(evt); + //Thread::sleep(1000000); + + sock->setReceiveTimeout(1000); + ReceiverThread runner(*this); + Thread thread(runner); + thread.start(); + + return true; +} + +bool XmppClient::connect() +{ + if (!createSession()) + { + disconnect(); + return false; + } + return true; +} + + +bool XmppClient::connect(DOMString hostArg, int portArg, + DOMString usernameArg, + DOMString passwordArg, + DOMString resourceArg) +{ + host = hostArg; + port = portArg; + password = passwordArg; + resource = resourceArg; + + //parse this one + setUsername(usernameArg); + + bool ret = connect(); + return ret; +} + +bool XmppClient::disconnect() +{ + if (connected) + { + char *fmt = + "\n"; + write(fmt, jid.c_str()); + } + keepGoing = false; + connected = false; + Thread::sleep(3000); //allow receiving thread to quit + sock->disconnect(); + roster.clear(); + groupChatsClear(); + XmppEvent event(XmppEvent::EVENT_DISCONNECTED); + dispatchXmppEvent(event); + return true; +} + +//####################### +//# ROSTER +//####################### + +bool XmppClient::rosterAdd(const DOMString &rosterGroup, + const DOMString &otherJid, + const DOMString &name) +{ + if (!checkConnect()) + return false; + char *fmt = + "" + "" + "%s" + "\n"; + if (!write(fmt, jid.c_str(), msgId++, otherJid.c_str(), + name.c_str(), rosterGroup.c_str())) + { + return false; + } + return true; +} + +bool XmppClient::rosterDelete(const DOMString &otherJid) +{ + if (!checkConnect()) + return false; + char *fmt = + "" + "" + "%s" + "\n"; + if (!write(fmt, jid.c_str(), msgId++, otherJid.c_str())) + { + return false; + } + return true; +} + + +static bool xmppRosterCompare(const XmppUser& p1, const XmppUser& p2) +{ + DOMString s1 = p1.group; + DOMString s2 = p2.group; + for (unsigned int len=0 ; len XmppClient::getRoster() +{ + std::vector ros = roster; + std::sort(ros.begin(), ros.end(), xmppRosterCompare); + return ros; +} + +//####################### +//# CHAT (individual) +//####################### + +bool XmppClient::message(const DOMString &user, const DOMString &subj, + const DOMString &msg) +{ + if (!checkConnect()) + return false; + + DOMString xmlSubj = toXml(subj); + DOMString xmlMsg = toXml(msg); + + if (xmlSubj.size() > 0) + { + char *fmt = + "" + "%s%s\n"; + if (!write(fmt, jid.c_str(), user.c_str(), + xmlSubj.c_str(), xmlMsg.c_str())) + return false; + } + else + { + char *fmt = + "" + "%s\n"; + if (!write(fmt, jid.c_str(), user.c_str(), xmlMsg.c_str())) + return false; + } + return true; +} + + + +bool XmppClient::message(const DOMString &user, const DOMString &msg) +{ + return message(user, "", msg); +} + + + +bool XmppClient::presence(const DOMString &presence) +{ + if (!checkConnect()) + return false; + + DOMString xmlPres = toXml(presence); + + char *fmt = + "%s\n"; + if (!write(fmt, jid.c_str(), xmlPres.c_str())) + return false; + return true; +} + +//####################### +//# GROUP CHAT +//####################### + +bool XmppClient::groupChatCreate(const DOMString &groupJid) +{ + std::vector::iterator iter; + for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++) + { + if ((*iter)->getGroupJid() == groupJid) + { + error("Group chat '%s' already exists", groupJid.c_str()); + return false; + } + } + XmppGroupChat *chat = new XmppGroupChat(groupJid); + groupChats.push_back(chat); + return true; +} + +/** + * + */ +void XmppClient::groupChatDelete(const DOMString &groupJid) +{ + std::vector::iterator iter; + for (iter=groupChats.begin() ; iter!=groupChats.end() ; ) + { + XmppGroupChat *chat = *iter; + if (chat->getGroupJid() == groupJid) + { + iter = groupChats.erase(iter); + delete chat; + } + else + iter++; + } +} + +/** + * + */ +bool XmppClient::groupChatExists(const DOMString &groupJid) +{ + std::vector::iterator iter; + for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++) + if ((*iter)->getGroupJid() == groupJid) + return true; + return false; +} + +/** + * + */ +void XmppClient::groupChatsClear() +{ + std::vector::iterator iter; + for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++) + delete (*iter); + groupChats.clear(); +} + + +/** + * + */ +void XmppClient::groupChatUserAdd(const DOMString &groupJid, + const DOMString &nick) +{ + std::vector::iterator iter; + for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++) + { + if ((*iter)->getGroupJid() == groupJid) + { + (*iter)->userAdd("", nick); + } + } +} + +/** + * + */ +void XmppClient::groupChatUserDelete(const DOMString &groupJid, + const DOMString &nick) +{ + std::vector::iterator iter; + for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++) + { + if ((*iter)->getGroupJid() == groupJid) + { + (*iter)->userDelete("", nick); + } + } +} + +static bool xmppUserCompare(const XmppUser& p1, const XmppUser& p2) +{ + DOMString s1 = p1.nick; + DOMString s2 = p2.nick; + int comp = 0; + for (unsigned int len=0 ; len XmppClient::groupChatGetUserList( + const DOMString &groupJid) +{ + if (!checkConnect()) + { + std::vector dummy; + return dummy; + } + + std::vector::iterator iter; + for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++) + { + if ((*iter)->getGroupJid() == groupJid ) + { + std::vector uList = (*iter)->getUserList(); + std::sort(uList.begin(), uList.end(), xmppUserCompare); + return uList; + } + } + std::vector dummy; + return dummy; +} + +bool XmppClient::groupChatJoin(const DOMString &groupJid, + const DOMString &nick, + const DOMString &pass) +{ + if (!checkConnect()) + return false; + + DOMString user = nick; + if (user.size()<1) + user = username; + + char *fmt = + "" + "\n"; + if (!write(fmt, groupJid.c_str(), user.c_str())) + return false; + return true; +} + + +bool XmppClient::groupChatLeave(const DOMString &groupJid, + const DOMString &nick) +{ + if (!checkConnect()) + return false; + + DOMString user = nick; + if (user.size()<1) + user = username; + + char *fmt = + "" + "\n"; + if (!write(fmt, groupJid.c_str(), user.c_str())) + return false; + return true; +} + + +bool XmppClient::groupChatMessage(const DOMString &groupJid, + const DOMString &msg) +{ + if (!checkConnect()) + { + return false; + } + + DOMString xmlMsg = toXml(msg); + + char *fmt = + "" + "%s\n"; + if (!write(fmt, jid.c_str(), groupJid.c_str(), xmlMsg.c_str())) + return false; + return true; +} + +bool XmppClient::groupChatPrivateMessage(const DOMString &groupJid, + const DOMString &toNick, + const DOMString &msg) +{ + if (!checkConnect()) + return false; + + DOMString xmlMsg = toXml(msg); + + char *fmt = + "" + "%s\n"; + if (!write(fmt, jid.c_str(), groupJid.c_str(), + toNick.c_str(), xmlMsg.c_str())) + return false; + return true; +} + +bool XmppClient::groupChatPresence(const DOMString &groupJid, + const DOMString &myNick, + const DOMString &presence) +{ + if (!checkConnect()) + return false; + + DOMString user = myNick; + if (user.size()<1) + user = username; + + DOMString xmlPresence = toXml(presence); + + char *fmt = + "" + "\n"; + if (!write(fmt, jid.c_str(), groupJid.c_str(), user.c_str(), xmlPresence.c_str())) + return true; + return true; +} + + + +//####################### +//# S T R E A M S +//####################### + + +/** + * + */ +int XmppClient::outputStreamOpen(const DOMString &destId, + const DOMString &streamIdArg) +{ + int i; + for (i=0; igetState() == STREAM_AVAILABLE) + break; + if (i>=outputStreamCount) + { + error("No available output streams"); + return -1; + } + int streamNr = i; + XmppStream *outs = outputStreams[streamNr]; + + outs->setState(STREAM_OPENING); + + char buf[32]; + snprintf(buf, 31, "inband%d", getMsgId()); + DOMString iqId = buf; + + DOMString streamId = streamIdArg; + if (streamId.size()<1) + { + snprintf(buf, 31, "stream%d", getMsgId()); + DOMString streamId = buf; + } + outs->setIqId(iqId); + outs->setStreamId(streamId); + outs->setPeerId(destId); + + char *fmt = + "" + "\n"; + if (!write(fmt, jid.c_str(), + destId.c_str(), iqId.c_str(), + streamId.c_str())) + { + outs->reset(); + return -1; + } + + int state = outs->getState(); + for (int tim=0 ; tim<20 ; tim++) + { + if (state == STREAM_OPEN) + break; + else if (state == STREAM_ERROR) + { + printf("ERROR\n"); + outs->reset(); + return -1; + } + Thread::sleep(1000); + state = outs->getState(); + } + if (state != STREAM_OPEN) + { + printf("TIMEOUT ERROR\n"); + outs->reset(); + return -1; + } + + return streamNr; +} + +/** + * + */ +int XmppClient::outputStreamWrite(int streamNr, + const unsigned char *buf, unsigned long len) +{ + XmppStream *outs = outputStreams[streamNr]; + + unsigned long outLen = 0; + unsigned char *p = (unsigned char *)buf; + + while (outLen < len) + { + unsigned long chunksize = 1024; + if (chunksize + outLen > len) + chunksize = len - outLen; + + Base64Encoder encoder; + encoder.append(p, chunksize); + DOMString b64data = encoder.finish(); + p += chunksize; + outLen += chunksize; + + char *fmt = + "" + "" + "%s" + "" + "" + "" + "" + "" + "\n"; + if (!write(fmt, jid.c_str(), + outs->getPeerId().c_str(), + getMsgId(), + outs->getStreamId().c_str(), + outs->getSeqNr(), + b64data.c_str())) + { + outs->reset(); + return -1; + } + pause(5000); + } + return outLen; +} + +/** + * + */ +int XmppClient::outputStreamClose(int streamNr) +{ + XmppStream *outs = outputStreams[streamNr]; + + char buf[32]; + snprintf(buf, 31, "inband%d", getMsgId()); + DOMString iqId = buf; + outs->setIqId(iqId); + + outs->setState(STREAM_CLOSING); + char *fmt = + "" + "\n"; + if (!write(fmt, jid.c_str(), + outs->getPeerId().c_str(), + iqId.c_str(), + outs->getStreamId().c_str())) + return false; + + int state = outs->getState(); + for (int tim=0 ; tim<20 ; tim++) + { + if (state == STREAM_CLOSED) + break; + else if (state == STREAM_ERROR) + { + printf("ERROR\n"); + outs->reset(); + return -1; + } + Thread::sleep(1000); + state = outs->getState(); + } + if (state != STREAM_CLOSED) + { + printf("TIMEOUT ERROR\n"); + outs->reset(); + return -1; + } + + outs->reset(); + return 1; +} + + +/** + * + */ +int XmppClient::inputStreamOpen(const DOMString &fromJid, const DOMString &streamId, + const DOMString &iqId) +{ + int i; + for (i=0 ; igetState() == STREAM_AVAILABLE) + break; + } + if (i>=inputStreamCount) + { + error("No available input streams"); + return -1; + } + int streamNr = i; + XmppStream *ins = inputStreams[streamNr]; + ins->reset(); + ins->setPeerId(fromJid); + ins->setState(STREAM_CLOSED); + ins->setStreamId(streamId); + + int state = ins->getState(); + for (int tim=0 ; tim<20 ; tim++) + { + if (state == STREAM_OPENING) + break; + else if (state == STREAM_ERROR) + { + printf("ERROR\n"); + ins->reset(); + return -1; + } + Thread::sleep(1000); + state = ins->getState(); + } + if (state != STREAM_OPENING) + { + printf("TIMEOUT ERROR\n"); + ins->reset(); + return -1; + } + char *fmt = + "\n"; + if (!write(fmt, jid.c_str(), fromJid.c_str(), ins->getIqId().c_str())) + { + return -1; + } + + ins->setState(STREAM_OPEN); + return streamNr; +} + +/** + * + */ +int XmppClient::inputStreamAvailable(int streamNr) +{ + XmppStream *ins = inputStreams[streamNr]; + return ins->available(); +} + +/** + * + */ +std::vector XmppClient::inputStreamRead(int streamNr) +{ + XmppStream *ins = inputStreams[streamNr]; + return ins->read(); +} + +/** + * + */ +bool XmppClient::inputStreamClosing(int streamNr) +{ + XmppStream *ins = inputStreams[streamNr]; + if (ins->getState() == STREAM_CLOSING) + return true; + return false; +} + + +/** + * + */ +int XmppClient::inputStreamClose(int streamNr) +{ + int ret=1; + XmppStream *ins = inputStreams[streamNr]; + if (ins->getState() == STREAM_CLOSING) + { + char *fmt = + "\n"; + if (!write(fmt, jid.c_str(), ins->getPeerId().c_str(), + ins->getIqId().c_str())) + { + ret = -1; + } + } + ins->reset(); + return ret; +} + + + + +//####################### +//# FILE TRANSFERS +//####################### + + +/** + * + */ +bool XmppClient::fileSend(const DOMString &destJidArg, + const DOMString &offeredNameArg, + const DOMString &fileNameArg, + const DOMString &descriptionArg) +{ + DOMString destJid = destJidArg; + DOMString offeredName = offeredNameArg; + DOMString fileName = fileNameArg; + DOMString description = descriptionArg; + + int i; + for (i=0; igetState() == STREAM_AVAILABLE) + break; + if (i>=fileSendCount) + { + error("No available file send streams"); + return false; + } + int fileSendNr = i; + XmppStream *outf = fileSends[fileSendNr]; + + outf->setState(STREAM_OPENING); + + struct stat finfo; + if (stat(fileName.c_str(), &finfo)<0) + { + error("Cannot stat file '%s' for sending", fileName.c_str()); + return false; + } + long fileLen = finfo.st_size; + if (!fileLen > 1000000) + { + error("'%s' too large", fileName.c_str()); + return false; + } + if (!S_ISREG(finfo.st_mode)) + { + error("'%s' is not a regular file", fileName.c_str()); + return false; + } + FILE *f = fopen(fileName.c_str(), "rb"); + if (!f) + { + error("cannot open '%s' for sending", fileName.c_str()); + return false; + } + unsigned char *sendBuf = (unsigned char *)malloc(fileLen+1); + if (!sendBuf) + { + error("cannot cannot allocate send buffer for %s", fileName.c_str()); + return false; + } + for (long i=0 ; i=0 && slashPos<=(int)(fileName.size()-1)) + { + offeredName = fileName.substr(slashPos+1, + fileName.size()-slashPos-1); + printf("offeredName:%s\n", offeredName.c_str()); + } + } + + char buf[32]; + snprintf(buf, 31, "file%d", getMsgId()); + DOMString iqId = buf; + outf->setIqId(iqId); + + snprintf(buf, 31, "stream%d", getMsgId()); + DOMString streamId = buf; + //outf->setStreamId(streamId); + + DOMString hash = Md5::hashHex(sendBuf, fileLen); + printf("Hash:%s\n", hash.c_str()); + + outf->setPeerId(destJid); + + char dtgBuf[81]; + struct tm *timeVal = gmtime(&(finfo.st_mtime)); + strftime(dtgBuf, 80, "%Y-%m-%dT%H:%M:%Sz", timeVal); + + char *fmt = + "" + "" + "%s" + "" + "" + "" + //"" + "" + "\n"; + if (!write(fmt, iqId.c_str(), destJid.c_str(), + streamId.c_str(), offeredName.c_str(), fileLen, + hash.c_str(), dtgBuf, description.c_str())) + { + free(sendBuf); + return false; + } + + int state = outf->getState(); + for (int tim=0 ; tim<20 ; tim++) + { + printf("##### waiting for open\n"); + if (state == STREAM_OPEN) + { + outf->reset(); + break; + } + else if (state == STREAM_ERROR) + { + printf("ERROR\n"); + outf->reset(); + return false; + } + Thread::sleep(1000); + state = outf->getState(); + } + if (state != STREAM_OPEN) + { + printf("TIMEOUT ERROR\n"); + outf->reset(); + return false; + } + + //free up this reqource + outf->reset(); + + int streamNr = outputStreamOpen(destJid, streamId); + if (streamNr<0) + { + error("cannot open output stream %s", streamId.c_str()); + outf->reset(); + return false; + } + + int ret = outputStreamWrite(streamNr, sendBuf, fileLen); + + if (ret<0) + { + } + + outputStreamClose(streamNr); + + free(sendBuf); + return true; +} + + +class FileSendThread : public Thread +{ +public: + + FileSendThread(XmppClient &par, + const DOMString &destJidArg, + const DOMString &offeredNameArg, + const DOMString &fileNameArg, + const DOMString &descriptionArg) : client(par) + { + destJid = destJidArg; + offeredName = offeredNameArg; + fileName = fileNameArg; + description = descriptionArg; + } + + virtual ~FileSendThread() {} + + void run() + { + client.fileSend(destJid, offeredName, + fileName, description); + } + +private: + + XmppClient &client; + DOMString destJid; + DOMString offeredName; + DOMString fileName; + DOMString description; +}; + +/** + * + */ +bool XmppClient::fileSendBackground(const DOMString &destJid, + const DOMString &offeredName, + const DOMString &fileName, + const DOMString &description) +{ + FileSendThread thread(*this, destJid, offeredName, + fileName, description); + thread.start(); + return true; +} + + +/** + * + */ +bool XmppClient::fileReceive(const DOMString &fromJid, + const DOMString &iqId, + const DOMString &streamId, + const DOMString &fileName, + long fileSize, + const DOMString &fileHash) +{ + char *fmt = + "" + "" + "" + "" + "" + "" + "http://jabber.org/protocol/ibb" + "\n"; + if (!write(fmt, fromJid.c_str(), iqId.c_str())) + { + return false; + } + + int streamNr = inputStreamOpen(fromJid, streamId, iqId); + if (streamNr < 0) + { + return false; + } + + + Md5 md5; + FILE *f = fopen(fileName.c_str(), "wb"); + if (!f) + { + return false; + } + + while (true) + { + if (inputStreamAvailable(streamNr)<1) + { + if (inputStreamClosing(streamNr)) + break; + pause(100); + continue; + } + std::vector ret = inputStreamRead(streamNr); + std::vector::iterator iter; + for (iter=ret.begin() ; iter!=ret.end() ; iter++) + { + unsigned char ch = *iter; + md5.append(&ch, 1); + fwrite(&ch, 1, 1, f); + } + } + + inputStreamClose(streamNr); + fclose(f); + + DOMString hash = md5.finishHex(); + printf("received file hash:%s\n", hash.c_str()); + + return true; +} + + + +class FileReceiveThread : public Thread +{ +public: + + FileReceiveThread(XmppClient &par, + const DOMString &fromJidArg, + const DOMString &iqIdArg, + const DOMString &streamIdArg, + const DOMString &fileNameArg, + long fileSizeArg, + const DOMString &fileHashArg) : client(par) + { + fromJid = fromJidArg; + iqId = iqIdArg; + streamId = streamIdArg; + fileName = fileNameArg; + fileSize = fileSizeArg; + fileHash = fileHashArg; + } + + virtual ~FileReceiveThread() {} + + void run() + { + client.fileReceive(fromJid, iqId, streamId, + fileName, fileSize, fileHash); + } + +private: + + XmppClient &client; + DOMString fromJid; + DOMString iqId; + DOMString streamId; + DOMString fileName; + long fileSize; + DOMString fileHash; +}; + +/** + * + */ +bool XmppClient::fileReceiveBackground(const DOMString &fromJid, + const DOMString &iqId, + const DOMString &streamId, + const DOMString &fileName, + long fileSize, + const DOMString &fileHash) +{ + FileReceiveThread thread(*this, fromJid, iqId, streamId, + fileName, fileSize, fileHash); + thread.start(); + return true; +} + + + +//######################################################################## +//# X M P P G R O U P C H A T +//######################################################################## + +/** + * + */ +XmppGroupChat::XmppGroupChat(const DOMString &groupJidArg) +{ + groupJid = groupJidArg; +} + +/** + * + */ +XmppGroupChat::XmppGroupChat(const XmppGroupChat &other) +{ + groupJid = other.groupJid; + userList = other.userList; +} + +/** + * + */ +XmppGroupChat::~XmppGroupChat() +{ +} + + +/** + * + */ +DOMString XmppGroupChat::getGroupJid() +{ + return groupJid; +} + + +void XmppGroupChat::userAdd(const DOMString &jid, const DOMString &nick) +{ + std::vector::iterator iter; + for (iter= userList.begin() ; iter!=userList.end() ; iter++) + { + if (iter->nick == nick) + return; + } + XmppUser user(jid, nick); + userList.push_back(user); +} + +void XmppGroupChat::userDelete(const DOMString &jid, const DOMString &nick) +{ + std::vector::iterator iter; + for (iter= userList.begin() ; iter!=userList.end() ; ) + { + if (iter->nick == nick) + iter = userList.erase(iter); + else + iter++; + } +} + +std::vector XmppGroupChat::getUserList() const +{ + return userList; +} + + + + + + +} //namespace Pedro +//######################################################################## +//# E N D O F F I L E +//######################################################################## + + + + + + + + + + + + + + + diff --git a/src/jabber_whiteboard/pedroxmpp.h b/src/jabber_whiteboard/pedroxmpp.h new file mode 100644 index 000000000..574371bf7 --- /dev/null +++ b/src/jabber_whiteboard/pedroxmpp.h @@ -0,0 +1,1023 @@ +#ifndef __XMPP_H__ +#define __XMPP_H__ +/* + * API for the Pedro mini-XMPP client. + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2005 Bob Jamison + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include + +#include + +#include "pedrodom.h" + +namespace Pedro +{ + +typedef std::string DOMString; + + +//######################################################################## +//# X M P P E V E N T +//######################################################################## +class XmppUser +{ +public: + XmppUser() + { + } + XmppUser(const DOMString &jidArg, const DOMString &nickArg) + { + jid = jidArg; + nick = nickArg; + } + XmppUser(const DOMString &jidArg, const DOMString &nickArg, + const DOMString &subscriptionArg, const DOMString &groupArg) + { + jid = jidArg; + nick = nickArg; + subscription = subscriptionArg; + group = groupArg; + } + XmppUser(const XmppUser &other) + { + jid = other.jid; + nick = other.nick; + subscription = other.subscription; + group = other.group; + show = other.show; + } + XmppUser &operator=(const XmppUser &other) + { + jid = other.jid; + nick = other.nick; + subscription = other.subscription; + group = other.group; + show = other.show; + return *this; + } + virtual ~XmppUser() + {} + DOMString jid; + DOMString nick; + DOMString subscription; + DOMString group; + DOMString show; +}; + +class XmppEvent +{ + +public: + +typedef enum + { + EVENT_NONE, + EVENT_STATUS, + EVENT_ERROR, + EVENT_CONNECTED, + EVENT_DISCONNECTED, + EVENT_PRESENCE, + EVENT_ROSTER, + EVENT_MESSAGE, + EVENT_MUC_JOIN, + EVENT_MUC_LEAVE, + EVENT_MUC_PRESENCE, + EVENT_MUC_MESSAGE, + EVENT_STREAM_RECEIVE_INIT, + EVENT_STREAM_RECEIVE, + EVENT_STREAM_RECEIVE_CLOSE, + EVENT_FILE_ACCEPTED, + EVENT_FILE_RECEIVE + } XmppEventType; + + + /** + * + */ + XmppEvent(int type); + + /** + * + */ + XmppEvent(const XmppEvent &other); + + /** + * + */ + virtual XmppEvent &operator=(const XmppEvent &other); + + /** + * + */ + virtual ~XmppEvent(); + + /** + * + */ + virtual void assign(const XmppEvent &other); + + /** + * + */ + virtual int getType() const; + + + /** + * + */ + virtual DOMString getIqId() const; + + + /** + * + */ + virtual void setIqId(const DOMString &val); + + /** + * + */ + virtual DOMString getStreamId() const; + + + /** + * + */ + virtual void setStreamId(const DOMString &val); + + /** + * + */ + virtual bool getPresence() const; + + + /** + * + */ + virtual void setPresence(bool val); + + /** + * + */ + virtual DOMString getShow() const; + + + /** + * + */ + virtual void setShow(const DOMString &val); + + /** + * + */ + virtual DOMString getStatus() const; + + /** + * + */ + virtual void setStatus(const DOMString &val); + + /** + * + */ + virtual DOMString getTo() const; + + /** + * + */ + virtual void setTo(const DOMString &val); + + /** + * + */ + virtual DOMString getFrom() const; + + /** + * + */ + virtual void setFrom(const DOMString &val); + + /** + * + */ + virtual DOMString getGroup() const; + + /** + * + */ + virtual void setGroup(const DOMString &val); + + /** + * + */ + virtual Element *getDOM() const; + + + /** + * + */ + virtual void setDOM(const Element *val); + + /** + * + */ + virtual std::vector getUserList() const; + + /** + * + */ + virtual void setUserList(const std::vector &userList); + + /** + * + */ + virtual DOMString getFileName() const; + + + /** + * + */ + virtual void setFileName(const DOMString &val); + + + /** + * + */ + virtual DOMString getFileDesc() const; + + + /** + * + */ + virtual void setFileDesc(const DOMString &val); + + + /** + * + */ + virtual long getFileSize() const; + + + /** + * + */ + virtual void setFileSize(long val); + + /** + * + */ + virtual DOMString getFileHash() const; + + /** + * + */ + virtual void setFileHash(const DOMString &val); + + /** + * + */ + virtual DOMString getData() const; + + + /** + * + */ + virtual void setData(const DOMString &val); + +private: + + int eventType; + + DOMString iqId; + + DOMString streamId; + + bool presence; + + DOMString show; + + DOMString status; + + DOMString to; + + DOMString from; + + DOMString group; + + DOMString data; + + DOMString fileName; + DOMString fileDesc; + long fileSize; + DOMString fileHash; + + Element *dom; + + std::vectoruserList; + +}; + + +//######################################################################## +//# X M P P E V E N T L I S T E N E R +//######################################################################## + +class XmppEventListener +{ +public: + + /** + * + */ + XmppEventListener() + {} + + /** + * + */ + XmppEventListener(const XmppEventListener &other) + {} + + /** + * + */ + virtual void operator=(const XmppEventListener &other) + {} + + /** + * + */ + virtual ~XmppEventListener() + {} + + /** + * + */ + virtual void processXmppEvent(const XmppEvent &event) + {} + +}; + + + +//######################################################################## +//# X M P P E V E N T T A R G E T +//######################################################################## + +class XmppEventTarget +{ +public: + + /** + * + */ + XmppEventTarget(); + + /** + * + */ + XmppEventTarget(const XmppEventTarget &other); + + /** + * + */ + virtual ~XmppEventTarget(); + + + //########################### + //# M E S S A G E S + //########################### + + + /** + * Send an error message to all subscribers + */ + void error(char *fmt, ...); + + + /** + * Send a status message to all subscribers + */ + void status(char *fmt, ...); + + //########################### + //# LISTENERS + //########################### + + /** + * + */ + virtual void dispatchXmppEvent(const XmppEvent &event); + + /** + * + */ + virtual void addXmppEventListener(const XmppEventListener &listener); + + /** + * + */ + virtual void removeXmppEventListener(const XmppEventListener &listener); + + /** + * + */ + virtual void clearXmppEventListeners(); + + /** + * + */ + void eventQueueEnable(bool val); + + /** + * + */ + int eventQueueAvailable(); + + /** + * + */ + XmppEvent eventQueuePop(); + + +private: + + std::vector listeners; + + std::vector eventQueue; + bool eventQueueEnabled; + + static const int targetWriteBufLen = 2048; + + char targetWriteBuf[targetWriteBufLen]; +}; + + + + + +//######################################################################## +//# X M P P C L I E N T +//######################################################################## + + +class TcpSocket; +class XmppChat; +class XmppGroupChat; +class XmppStream; + +class XmppClient : public XmppEventTarget +{ + +public: + + //########################### + //# CONSTRUCTORS + //########################### + + /** + * + */ + XmppClient(); + + /** + * + */ + XmppClient(const XmppClient &other); + + /** + * + */ + void assign(const XmppClient &other); + + /** + * + */ + virtual ~XmppClient(); + + + //########################### + //# UTILITY + //########################### + + /** + * + */ + virtual bool pause(unsigned long millis); + + //########################### + //# CONNECTION + //########################### + + /** + * + */ + virtual bool connect(); + + /** + * + */ + virtual bool connect(DOMString host, int port, + DOMString userName, + DOMString password, + DOMString resource); + + /** + * + */ + virtual bool disconnect(); + + + /** + * + */ + virtual bool write(char *fmt, ...); + + /** + * + */ + virtual bool isConnected() + { return connected; } + + /** + * + */ + virtual DOMString getHost() + { return host; } + + /** + * + */ + virtual void setHost(const DOMString &val) + { host = val; } + + /** + * + */ + virtual DOMString getRealm() + { return realm; } + + /** + * + */ + virtual void setRealm(const DOMString &val) + { realm = val; } + + /** + * + */ + virtual int getPort() + { return port; } + + /** + * + */ + virtual void setPort(int val) + { port = val; } + + /** + * + */ + virtual DOMString getUsername(); + + /** + * + */ + virtual void setUsername(const DOMString &val); + + /** + * + */ + virtual DOMString getPassword() + { return password; } + + /** + * + */ + virtual void setPassword(const DOMString &val) + { password = val; } + + /** + * + */ + virtual DOMString getResource() + { return resource; } + + /** + * + */ + virtual void setResource(const DOMString &val) + { resource = val; } + + /** + * + */ + virtual DOMString getJid() + { return jid; } + /** + * + */ + virtual int getMsgId() + { return msgId++; } + + /** + * + */ + bool processMessage(Element *root); + + /** + * + */ + bool processPresence(Element *root); + + /** + * + */ + bool processIq(Element *root); + + /** + * + */ + virtual bool receiveAndProcess(); + + /** + * + */ + virtual bool receiveAndProcessLoop(); + + //####################### + //# ROSTER + //####################### + + /** + * + */ + bool rosterAdd(const DOMString &rosterGroup, + const DOMString &otherJid, + const DOMString &name); + + /** + * + */ + bool rosterDelete(const DOMString &otherJid); + + /** + * + */ + std::vector getRoster(); + + + //####################### + //# CHAT (individual) + //####################### + + /** + * + */ + virtual bool message(const DOMString &user, const DOMString &subj, + const DOMString &text); + + /** + * + */ + virtual bool message(const DOMString &user, const DOMString &text); + + /** + * + */ + virtual bool presence(const DOMString &presence); + + //####################### + //# GROUP CHAT + //####################### + + /** + * + */ + virtual bool groupChatCreate(const DOMString &groupJid); + + /** + * + */ + virtual void groupChatDelete(const DOMString &groupJid); + + /** + * + */ + bool groupChatExists(const DOMString &groupJid); + + /** + * + */ + virtual void groupChatsClear(); + + /** + * + */ + virtual void groupChatUserAdd(const DOMString &groupJid, + const DOMString &nick); + + /** + * + */ + virtual void groupChatUserDelete(const DOMString &groupJid, + const DOMString &nick); + + /** + * + */ + virtual std::vector + groupChatGetUserList(const DOMString &groupJid); + + /** + * + */ + virtual bool groupChatJoin(const DOMString &groupJid, + const DOMString &nick, + const DOMString &pass); + + /** + * + */ + virtual bool groupChatLeave(const DOMString &groupJid, + const DOMString &nick); + + /** + * + */ + virtual bool groupChatMessage(const DOMString &groupJid, + const DOMString &msg); + + /** + * + */ + virtual bool groupChatPrivateMessage(const DOMString &groupJid, + const DOMString &toNick, + const DOMString &msg); + + /** + * + */ + virtual bool groupChatPresence(const DOMString &groupJid, + const DOMString &nick, + const DOMString &presence); + + + //####################### + //# STREAMS + //####################### + + typedef enum + { + STREAM_AVAILABLE, + STREAM_OPENING, + STREAM_OPEN, + STREAM_CLOSING, + STREAM_CLOSED, + STREAM_ERROR + } StreamStates; + + /** + * + */ + virtual int outputStreamOpen(const DOMString &jid, + const DOMString &streamId); + + /** + * + */ + virtual int outputStreamWrite(int streamId, + const unsigned char *buf, unsigned long len); + + /** + * + */ + virtual int outputStreamClose(int streamId); + + /** + * + */ + virtual int inputStreamOpen(const DOMString &jid, + const DOMString &streamId, + const DOMString &iqId); + + /** + * + */ + virtual int inputStreamAvailable(int streamId); + + /** + * + */ + virtual std::vector inputStreamRead(int streamId); + + /** + * + */ + virtual bool inputStreamClosing(int streamId); + + /** + * + */ + virtual int inputStreamClose(int streamId); + + + //####################### + //# FILE TRANSFERS + //####################### + + /** + * + */ + virtual bool fileSend(const DOMString &destJid, + const DOMString &offeredName, + const DOMString &fileName, + const DOMString &description); + + /** + * + */ + virtual bool fileSendBackground(const DOMString &destJid, + const DOMString &offeredName, + const DOMString &fileName, + const DOMString &description); + + /** + * + */ + virtual bool fileReceive(const DOMString &fromJid, + const DOMString &iqId, + const DOMString &streamId, + const DOMString &fileName, + long fileSize, + const DOMString &fileHash); + /** + * + */ + virtual bool fileReceiveBackground(const DOMString &fromJid, + const DOMString &iqId, + const DOMString &streamId, + const DOMString &fileName, + long fileSize, + const DOMString &fileHash); + + +private: + + void init(); + + DOMString host; + + /** + * will be same as host, unless username is + * user@realm + */ + DOMString realm; + + int port; + + DOMString username; + + DOMString password; + + DOMString resource; + + DOMString jid; + + int msgId; + + TcpSocket *sock; + + bool connected; + + bool createSession(); + + bool checkConnect(); + + DOMString readStanza(); + + bool saslMd5Authenticate(); + + bool saslPlainAuthenticate(); + + bool saslAuthenticate(); + + bool iqAuthenticate(const DOMString &streamId); + + bool keepGoing; + + static const int writeBufLen = 2048; + + unsigned char writeBuf[writeBufLen]; + + std::vectorgroupChats; + + static const int outputStreamCount = 16; + + XmppStream *outputStreams[outputStreamCount]; + + static const int inputStreamCount = 16; + + XmppStream *inputStreams[inputStreamCount]; + + static const int fileSendCount = 16; + + XmppStream *fileSends[fileSendCount]; + + std::vectorroster; +}; + + + + +//######################################################################## +//# X M P P G R O U P C H A T +//######################################################################## + +/** + * + */ +class XmppGroupChat +{ +public: + + /** + * + */ + XmppGroupChat(const DOMString &groupJid); + + /** + * + */ + XmppGroupChat(const XmppGroupChat &other); + + /** + * + */ + virtual ~XmppGroupChat(); + + /** + * + */ + virtual DOMString getGroupJid(); + + /** + * + */ + virtual void userAdd(const DOMString &jid, const DOMString &nick); + + /** + * + */ + virtual void userDelete(const DOMString &jid, const DOMString &nick); + + /** + * + */ + virtual std::vector getUserList() const; + + +private: + + DOMString groupJid; + + std::vectoruserList; + +}; + + + + + + + + + + +} //namespace Pedro + +#endif /* __XMPP_H__ */ + +//######################################################################## +//# E N D O F F I L E +//######################################################################## + diff --git a/src/jabber_whiteboard/serializer.cpp b/src/jabber_whiteboard/serializer.cpp new file mode 100644 index 000000000..ddba47299 --- /dev/null +++ b/src/jabber_whiteboard/serializer.cpp @@ -0,0 +1,304 @@ +/** + * Inkboard message -> XML::Event* serializer + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "xml/attribute-record.h" + +#include "jabber_whiteboard/serializer.h" + +#include "util/list.h" +#include "util/shared-c-string-ptr.h" + +#include "jabber_whiteboard/message-utilities.h" +#include "jabber_whiteboard/message-tags.h" +#include "jabber_whiteboard/typedefs.h" +#include "jabber_whiteboard/node-tracker.h" +#include "jabber_whiteboard/node-utilities.h" +#include "jabber_whiteboard/node-tracker-observer.h" + +namespace Inkscape { + +namespace Whiteboard { + +void +Serializer::notifyChildAdded(XML::Node& node, XML::Node& child, XML::Node* prev) +{ + // do not recurse upon initial notification + this->_newObjectEventHelper(node, child, prev, false); + this->_nn.insert(&child); +} + +void +Serializer::_newObjectEventHelper(XML::Node& node, XML::Node& child, XML::Node* prev, bool recurse) +{ + // 1. Check if we are tracking the parent node, + // and issue it an ID if we are not. + std::string parentid = this->_findOrGenerateNodeID(node); + + // 2. Check if the child node is a special node. + // Special nodes are nodes that should appear only once in a document. + // If it is, we do not want to generate a new ID for the child; we will use + // the existing ID. Otherwise, we will generate a new ID for it, since we + // have not yet seen it. + std::string childid; + if (this->_xnt->isSpecialNode(child.name())) { + childid = this->_xnt->get(child); + } else { + // If the child id already exists in the new node buffer, then we've already seen it. + if (!this->actions.tryToTrack(&child, NODE_ADD)) { + return; + } else { + childid = this->_xnt->generateKey(); +// childid = this->_findOrGenerateNodeID(child); + } + } + + // 3. Find this node's previous node, and, if it has one, retrieve its ID. + std::string previd; + if (prev) { + previd = this->_findOrGenerateNodeID(*prev); + } + + // 4. Serialize. + Glib::ustring childmsg = MessageUtilities::makeTagWithContent(MESSAGE_CHILD, childid); + Glib::ustring parentmsg = MessageUtilities::makeTagWithContent(MESSAGE_PARENT, parentid); + Glib::ustring namemsg = MessageUtilities::makeTagWithContent(MESSAGE_NAME, child.name()); + Glib::ustring nodetype = MessageUtilities::makeTagWithContent(MESSAGE_NODETYPE, NodeUtilities::nodeTypeToString(child)); + + Glib::ustring prevmsg; + if (!previd.empty()) { + prevmsg = MessageUtilities::makeTagWithContent(MESSAGE_REF, previd); + } + + Glib::ustring buf = MessageUtilities::makeTagWithContent(MESSAGE_NEWOBJ, childmsg + parentmsg + prevmsg + namemsg + nodetype); + + + this->_events.push_back(buf); + + // 5. Add the child node to the new nodes buffers. + this->newnodes.push_back(SerializedEventNodeAction(KeyNodePair(childid, &child), NODE_ADD)); + this->newkeys[&child] = childid; + this->_parent_child_map[&child] = &node; + + // 6. Scan attributes and content. + Inkscape::Util::List attrlist = child.attributeList(); + + for(; attrlist; attrlist++) { + this->notifyAttributeChanged(child, attrlist->key, Util::SharedCStringPtr(), attrlist->value); + } + + if (child.content()) { + this->notifyContentChanged(child, Util::SharedCStringPtr(), Util::SharedCStringPtr::copy(child.content())); + } + + this->_attributes_scanned.insert(childid); + + // 7. Repeat this process for each child of this child. + if (recurse && child.childCount() > 0) { + XML::Node* prev = child.firstChild(); + for(XML::Node* ch = child.firstChild(); ch; ch = ch->next()) { + if (ch == child.firstChild()) { + // No prev node in this case. + this->_newObjectEventHelper(child, *ch, NULL, true); + } else { + this->_newObjectEventHelper(child, *ch, prev, true); + prev = ch; + } + } + } + + return; +} + + +void +Serializer::notifyChildRemoved(XML::Node& node, XML::Node& child, XML::Node* prev) +{ + // 1. Get the ID of the child. + std::string childid; + + _pc_map_type::iterator i = this->_parent_child_map.find(&child); + if (i != this->_parent_child_map.end() && i->second != &node) { + // Don't look in local! Go for the tracker. + childid = this->_xnt->get(child); + } else if (i == this->_parent_child_map.end()) { + childid = this->_findOrGenerateNodeID(child); + } else if (i->second == &node) { + childid = this->_findOrGenerateNodeID(child); + this->_parent_child_map.erase(i); + } else { + childid = this->_findOrGenerateNodeID(child); + } + + // 2. Double-deletes don't make any sense. If we've seen this node already and if it's + // marked for deletion, return. + if (!this->actions.tryToTrack(&child, NODE_REMOVE)) { + return; + } else { + // 2a. Although we do not have to remove all child nodes of this subtree, + // we _do_ have to mark each child node as deleted. + this->_recursiveMarkAsRemoved(child); + } + + // 2. Mark this node as deleted. We don't want to be faced with the possibility of + // generating a new key for this deleted node, so insert it into both maps. + this->newnodes.push_back(SerializedEventNodeAction(KeyNodePair(childid, &child), NODE_REMOVE)); + this->newkeys[&child] = childid; + this->_nn.erase(&child); + std::string parentid = this->_findOrGenerateNodeID(node); + + + // 4. Serialize the event. + this->_attributes_scanned.erase(childid); + Glib::ustring childidmsg = MessageUtilities::makeTagWithContent(MESSAGE_CHILD, childid); + Glib::ustring parentidmsg = MessageUtilities::makeTagWithContent(MESSAGE_PARENT, parentid); + this->_events.push_back(MessageUtilities::makeTagWithContent(MESSAGE_DELETE, childidmsg + parentidmsg)); +} + + +void +Serializer::notifyChildOrderChanged(XML::Node& node, XML::Node& child, XML::Node* old_prev, XML::Node* new_prev) +{ + // 1. Find the ID of the node, or generate it if it does not exist. + std::string nodeid = this->_findOrGenerateNodeID(child); + + // 2. Find the ID of the parent of this node, or generate it if it does not exist. + std::string parentid = this->_findOrGenerateNodeID(*(child.parent())); + + // 3. Get the ID for the new child reference node, or generate it if it does not exist. + std::string newprevid = this->_findOrGenerateNodeID(*new_prev); + + // 4. Get the ID for the old child reference node, or generate it if it does not exist. + std::string oldprevid = this->_findOrGenerateNodeID(*old_prev); + + // 5. Serialize the event. + Glib::ustring nodeidmsg = MessageUtilities::makeTagWithContent(MESSAGE_ID, nodeid); + Glib::ustring parentidmsg = MessageUtilities::makeTagWithContent(MESSAGE_PARENT, parentid); + Glib::ustring oldprevidmsg = MessageUtilities::makeTagWithContent(MESSAGE_OLDVAL, oldprevid); + Glib::ustring newprevidmsg = MessageUtilities::makeTagWithContent(MESSAGE_NEWVAL, newprevid); + + this->_events.push_back(MessageUtilities::makeTagWithContent(MESSAGE_ORDERCHANGE, nodeidmsg + parentidmsg + oldprevidmsg + newprevidmsg)); +} + +void +Serializer::notifyContentChanged(XML::Node& node, Util::SharedCStringPtr old_content, Util::SharedCStringPtr new_content) +{ + // 1. Find the ID of the node, or generate it if it does not exist. + std::string nodeid = this->_findOrGenerateNodeID(node); + + std::string oldvalmsg, newvalmsg; + + // 2. If the old and new content are identical, don't send out this change. + // (identical meaning "same string" or "same string content") + if (old_content == new_content) { + return; + } + + if (old_content.cString() != NULL && new_content.cString() != NULL) { + if (strcmp(old_content.cString(), new_content.cString()) == 0) { + return; + } + } + + // 3. Serialize the event. + if (old_content.cString() != NULL) { + oldvalmsg = MessageUtilities::makeTagWithContent(MESSAGE_OLDVAL, old_content.cString()); + } + + if (new_content.cString() != NULL) { + newvalmsg = MessageUtilities::makeTagWithContent(MESSAGE_NEWVAL, new_content.cString()); + } + + Glib::ustring nodeidmsg = MessageUtilities::makeTagWithContent(MESSAGE_ID, nodeid); + this->_events.push_back(MessageUtilities::makeTagWithContent(MESSAGE_NODECONTENT, nodeidmsg + oldvalmsg + newvalmsg)); +} + +void +Serializer::notifyAttributeChanged(XML::Node& node, GQuark name, Util::SharedCStringPtr old_value, Util::SharedCStringPtr new_value) +{ + // 1. Find the ID of the node that has had an attribute modified, or generate it if it + // does not exist. + std::string nodeid = this->_findOrGenerateNodeID(node); + + // Proceed with 2-4 if the node has not already been scanned by notifyChildAdded. + if (this->_attributes_scanned.find(nodeid) == this->_attributes_scanned.end()) { + // 2. Convert the key to a string. + Glib::ustring key = g_quark_to_string(name); + + // 3. If oldval == newval, don't echo this change. + if (new_value.cString() != NULL && old_value.cString() != NULL) { + if (strcmp(new_value.cString(), old_value.cString()) == 0) { + return; + } + } + + // 4. Serialize the event. + Glib::ustring keymsg = MessageUtilities::makeTagWithContent(MESSAGE_KEY, key); + Glib::ustring oldvalmsg, newvalmsg; + + if (old_value.cString() != NULL) { + oldvalmsg = MessageUtilities::makeTagWithContent(MESSAGE_OLDVAL, old_value.cString()); + } + + if (new_value.cString() != NULL) { + newvalmsg = MessageUtilities::makeTagWithContent(MESSAGE_NEWVAL, new_value.cString()); + } + + Glib::ustring nodeidmsg = MessageUtilities::makeTagWithContent(MESSAGE_ID, nodeid); + + this->_events.push_back(MessageUtilities::makeTagWithContent(MESSAGE_CHANGE, nodeidmsg + keymsg + oldvalmsg + newvalmsg)); + } +} + +void +Serializer::synthesizeChildNodeAddEvents() +{ + _New_nodes_type::iterator i = this->_nn.begin(); + for(; i != this->_nn.end(); ++i) { + XML::Node* parent = *i; + // The root of the subtree defined by parent has already been considered; now, + // recursively look at the rest of the tree. + XML::Node* prev = parent->firstChild(); + for(XML::Node* ch = parent->firstChild(); ch; ch = ch->next()) { + if (ch == parent->firstChild()) { + this->_newObjectEventHelper(*parent, *ch, NULL, true); + } else { + this->_newObjectEventHelper(*parent, *ch, prev, true); + prev = ch; + } + } + } +} + + +void +Serializer::_recursiveMarkAsRemoved(XML::Node& node) +{ + this->actions.tryToTrack(&node, NODE_REMOVE); + + for(XML::Node* ch = node.firstChild(); ch; ch = ch->next()) { + this->_recursiveMarkAsRemoved(*ch); + } +} + +} + +} + +/* + 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 : diff --git a/src/jabber_whiteboard/serializer.h b/src/jabber_whiteboard/serializer.h new file mode 100644 index 000000000..cc0aa97b6 --- /dev/null +++ b/src/jabber_whiteboard/serializer.h @@ -0,0 +1,118 @@ +/** + * Inkboard message -> XML::Event* serializer + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __WHITEBOARD_MESSAGE_SERIALIZER_H__ +#define __WHITEBOARD_MESSAGE_SERIALIZER_H__ + +#include "xml/node-observer.h" + +#include "util/shared-c-string-ptr.h" + +#include "jabber_whiteboard/node-tracker.h" +#include "jabber_whiteboard/typedefs.h" +#include "jabber_whiteboard/node-tracker-observer.h" + +#include + +namespace Inkscape { + +namespace Whiteboard { + +class Serializer : public NodeTrackerObserver { +public: + Serializer(XMLNodeTracker* xnt) : NodeTrackerObserver(xnt) { } + ~Serializer() { } + + void notifyChildAdded(XML::Node &node, XML::Node &child, XML::Node *prev); + + void notifyChildRemoved(XML::Node &node, XML::Node &child, XML::Node *prev); + + void notifyChildOrderChanged(XML::Node &node, XML::Node &child, + XML::Node *old_prev, XML::Node *new_prev); + + void notifyContentChanged(XML::Node &node, + Util::SharedCStringPtr old_content, + Util::SharedCStringPtr new_content); + + void notifyAttributeChanged(XML::Node &node, GQuark name, + Util::SharedCStringPtr old_value, + Util::SharedCStringPtr new_value); + + void synthesizeChildNodeAddEvents(); + + SerializedEventList& getEventList() + { + return this->_events; + } + + SerializedEventList getEventListCopy() + { + return this->_events; + } + + SerializedEventList getAndClearEventList() + { + SerializedEventList ret = this->_events; + this->_events.clear(); + return ret; + } + + void clearEventList() + { + this->_events.clear(); + } + + void clearAttributesScannedBuffer() + { + this->_attributes_scanned.clear(); + } + + // Convenience method for resetting all stateful aspects of the serializer + void reset() + { + g_log(NULL, G_LOG_LEVEL_DEBUG, "Clearing serializer buffers"); + this->clearEventList(); + this->_parent_child_map.clear(); + this->_nn.clear(); + this->clearNodeBuffers(); + this->clearAttributesScannedBuffer(); + } + +private: + typedef std::set< XML::Node* > _New_nodes_type; + typedef std::map< XML::Node*, XML::Node* > _pc_map_type; + + SerializedEventList _events; + AttributesScannedSet _attributes_scanned; + + _New_nodes_type _nn; + _pc_map_type _parent_child_map; + + void _newObjectEventHelper(XML::Node& parent, XML::Node& child, XML::Node* prev, bool recurse); + void _recursiveMarkAsRemoved(XML::Node& node); +}; + +} + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/jabber_whiteboard/session-file-player.cpp b/src/jabber_whiteboard/session-file-player.cpp new file mode 100644 index 000000000..538d0bc91 --- /dev/null +++ b/src/jabber_whiteboard/session-file-player.cpp @@ -0,0 +1,176 @@ +/** + * Whiteboard session file playback mechanism + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + +#include +#include + +#include + +#include "desktop-handles.h" +#include "document.h" + +#include "jabber_whiteboard/defines.h" +#include "jabber_whiteboard/session-file.h" +#include "jabber_whiteboard/session-file-player.h" +#include "jabber_whiteboard/session-manager.h" + +namespace Inkscape { + +namespace Whiteboard { + +SessionFilePlayer::SessionFilePlayer(unsigned int bufsz, SessionManager* sm) +{ + this->_sm = sm; + this->_delay = 100; + this->_playing = false; + this->_curdir = FORWARD; + + this->_current = this->_next = 0; + this->_sf = NULL; + + if (sm->session_file() != NULL) { + this->_sf = sm->session_file(); + } else { + g_warning("Cannot operate on NULL SessionFile."); + } +} + +SessionFilePlayer::~SessionFilePlayer() +{ + +} + +void +SessionFilePlayer::load(SessionFile* sm) +{ + this->stop(); + if (this->_sm != NULL) { + this->_sf = sm; + this->_current = this->_next = 0; + this->_visited.clear(); + } +} + +SessionFile* +SessionFilePlayer::unload() +{ + SessionFile* sf = this->_sf; + this->stop(); + this->_sf = NULL; + return sf; +} + +Glib::ustring const& +SessionFilePlayer::filename() +{ + return this->_sf->filename(); +} + +Glib::ustring const& +SessionFilePlayer::curmsg() +{ + return this->_curmsg; +} + +void +SessionFilePlayer::start() +{ + this->_playback_dispatcher = Glib::signal_timeout().connect(sigc::bind< 0 > (sigc::mem_fun(*(this), &SessionFilePlayer::step), this->_curdir), this->_delay); + this->_playing = true; +} + +void +SessionFilePlayer::stop() +{ + this->_playback_dispatcher.disconnect(); + this->_playing = false; +} + +void +SessionFilePlayer::setDelay(unsigned int delay) +{ + this->_delay = delay; + if (this->_playing) { + this->stop(); + this->start(); + } +} + +void +SessionFilePlayer::setMessageOutputWidget(Glib::RefPtr const& widgetptr) +{ + this->_outputwidget = widgetptr; +} + +bool +SessionFilePlayer::step(unsigned short dir) +{ + switch (dir) { + case FORWARD: { + // Glib::ustring buf; + this->_current = this->_next; + this->_next = this->_sf->nextMessageFrom(this->_current, this->_curmsg); + if (this->_next == this->_current) { + return false; + } else { + this->_visited.push_front(std::make_pair< gint64, gint64 >(this->_current, this->_curmsg.bytes())); + this->_outputMessageToWidget(); + this->_sm->receiveChange(this->_curmsg); + sp_document_done(SP_DT_DOCUMENT(this->_sm->desktop())); + this->_curdir = FORWARD; + return true; + } + break; + } + case BACKWARD: + if (this->_current == 0) { + return false; + } else { + this->_visited.pop_front(); + std::pair< gint64, gint64 > last = this->_visited.front(); + this->_current = last.first; + this->_next = last.first + last.second; + this->_sf->nextMessageFrom(this->_current, this->_curmsg); + this->_outputMessageToWidget(); + sp_document_undo(SP_DT_DOCUMENT(this->_sm->desktop())); + this->_curdir = BACKWARD; + return true; + } + break; + default: + return true; + break; + } +} + +void +SessionFilePlayer::_outputMessageToWidget() +{ + if (this->_outputwidget) { + this->_outputwidget->set_text(this->_curmsg); + } +} + +} + +} + +/* + 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 : diff --git a/src/jabber_whiteboard/session-file-player.h b/src/jabber_whiteboard/session-file-player.h new file mode 100644 index 000000000..aa2e2b4c1 --- /dev/null +++ b/src/jabber_whiteboard/session-file-player.h @@ -0,0 +1,99 @@ +/** + * Whiteboard session file playback mechanism + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __WHITEBOARD_SESSION_FILE_PLAYER_H__ +#define __WHITEBOARD_SESSION_FILE_PLAYER_H__ + +#include +#include +#include +#include +#include + +struct SPDesktop; + +namespace Inkscape { + namespace UI { + namespace Dialog { + class SessionPlaybackDialogImpl; + } + } + namespace Whiteboard { + +class SessionFile; +class SessionManager; + +class SessionFilePlayer { +friend class UI::Dialog::SessionPlaybackDialogImpl; + +public: + SessionFilePlayer(unsigned int bufsz, SessionManager* sm); + ~SessionFilePlayer(); + + void load(SessionFile* sm); + SessionFile* unload(); + + Glib::ustring const& filename(); + Glib::ustring const& curmsg(); + + void start(); + void stop(); + void setDelay(unsigned int delay); + void setMessageOutputWidget(Glib::RefPtr const& widgetptr); + + static unsigned short const BACKWARD = 0; + static unsigned short const FORWARD = 1; + +private: + bool step(unsigned short dir); + void _outputMessageToWidget(); + + SessionManager* _sm; + SessionFile* _sf; + + // Output widget refptr + Glib::RefPtr _outputwidget; + + // Playback signal + sigc::connection _playback_dispatcher; + + // trackers + unsigned int _delay; + unsigned short _curdir; + bool _playing; + Glib::ustring _curmsg; + + // (position, msgsize) + std::list< std::pair< gint64, gint64 > > _visited; + gint64 _current; + gint64 _next; + + // noncopyable, nonassignable + SessionFilePlayer(SessionFilePlayer const&); + void operator=(SessionFilePlayer const&); +}; + +} + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/jabber_whiteboard/session-file-selector.cpp b/src/jabber_whiteboard/session-file-selector.cpp new file mode 100644 index 000000000..85a3a3ec1 --- /dev/null +++ b/src/jabber_whiteboard/session-file-selector.cpp @@ -0,0 +1,90 @@ +/** + * Session file selector widget + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "session-file-selector.h" + +#include +#include +#include + +namespace Inkscape { + +namespace Whiteboard { + +SessionFileSelectorBox::SessionFileSelectorBox() : + _usesessionfile(_("_Write session file:"), true) +{ + this->_construct(); +} + +SessionFileSelectorBox::~SessionFileSelectorBox() +{ + +} + +bool +SessionFileSelectorBox::isSelected() +{ + return this->_usesessionfile.get_active(); +} + +Glib::ustring const& +SessionFileSelectorBox::getFilename() +{ + return this->_filename; +} + +void +SessionFileSelectorBox::_construct() +{ + this->_getfilepath.set_label("..."); + + this->pack_start(this->_usesessionfile); + this->pack_start(this->_sessionfile); + this->pack_end(this->_getfilepath); + + this->_getfilepath.signal_clicked().connect(sigc::mem_fun(*this, &SessionFileSelectorBox::_callback)); +} + +void +SessionFileSelectorBox::_callback() { + Gtk::FileChooserDialog sessionfiledlg(_("Select a location and filename"), Gtk::FILE_CHOOSER_ACTION_SAVE); + sessionfiledlg.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); + sessionfiledlg.add_button(_("Set filename"), Gtk::RESPONSE_OK); + int result = sessionfiledlg.run(); + switch (result) { + case Gtk::RESPONSE_OK: + { + this->_usesessionfile.set_active(); + this->_sessionfile.set_text(sessionfiledlg.get_filename()); + this->_filename = sessionfiledlg.get_filename(); + break; + } + case Gtk::RESPONSE_CANCEL: + default: + break; + } +} + +} + +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ diff --git a/src/jabber_whiteboard/session-file-selector.h b/src/jabber_whiteboard/session-file-selector.h new file mode 100644 index 000000000..fe097e4ab --- /dev/null +++ b/src/jabber_whiteboard/session-file-selector.h @@ -0,0 +1,61 @@ +/** + * Session file selector widget + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __WHITEBOARD_SESSION_FILE_SELECTOR_BOX_H__ +#define __WHITEBOARD_SESSION_FILE_SELECTOR_BOX_H__ + +#include +#include +#include +#include + +namespace Inkscape { + +namespace Whiteboard { + +class SessionFileSelectorBox : public Gtk::HBox { +public: + SessionFileSelectorBox(); + ~SessionFileSelectorBox(); + + bool isSelected(); + Glib::ustring const& getFilename(); + +private: + // Construction + void _construct(); + void _callback(); + + // GTK+ widgets + Gtk::CheckButton _usesessionfile; + Gtk::Entry _sessionfile; + Gtk::Button _getfilepath; + + // Internal state + Glib::ustring _filename; +}; + +} + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/jabber_whiteboard/session-file.cpp b/src/jabber_whiteboard/session-file.cpp new file mode 100644 index 000000000..137e56510 --- /dev/null +++ b/src/jabber_whiteboard/session-file.cpp @@ -0,0 +1,140 @@ +/** + * Whiteboard session file object + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include + +#include "util/list-container.h" + +#include "jabber_whiteboard/message-utilities.h" +#include "jabber_whiteboard/node-utilities.h" +#include "jabber_whiteboard/typedefs.h" + +#include "jabber_whiteboard/session-file.h" + + +namespace Inkscape { + +namespace Whiteboard { + +SessionFile::SessionFile(Glib::ustring const& filename, bool reading, bool compress) : _filename(filename), _compress(compress), _reading(reading) +{ + try { + + if (!reading) { + this->fptr = Glib::IOChannel::create_from_file(filename, "w+"); + } else { + this->fptr = Glib::IOChannel::create_from_file(filename, "r"); + } + this->_ateof = false; + } catch (Glib::FileError) { + throw; + } +} + +SessionFile::~SessionFile() +{ + if (!this->_reading) { + this->commit(); + } + this->close(); +} + +gint64 +SessionFile::nextMessageFrom(gint64 from, Glib::ustring& buf) +{ + try { + Glib::ustring line; + Glib::IOStatus st; + Node part; + + gint64 accum = from; + buf = ""; + this->fptr->seek(accum, Glib::SEEK_TYPE_SET); + + while(part.tag != MESSAGE_COMMIT) { + st = this->fptr->read_line(line); + if (st == Glib::IO_STATUS_EOF) { + break; + } else { + accum += line.bytes(); + this->fptr->seek(accum); + MessageUtilities::getFirstMessageTag(part, line); + buf += line; + line.clear(); + } + } + + if (st == Glib::IO_STATUS_NORMAL) { + // reset eof flag if successful + this->_ateof = false; + return from + buf.bytes(); + } else { + if (st == Glib::IO_STATUS_EOF) { + this->_ateof = true; + } + return from; + } + } catch (Glib::IOChannelError e) { + g_warning("Could not read next message due to I/O error (error: %s)!", e.what().data()); + } catch (Glib::ConvertError e) { + g_warning("Could not read next message due to charset conversion error (error: %s)!", e.what().data()); + } + + return from; +} + +void +SessionFile::addMessage(Glib::ustring const& message) +{ + Glib::ustring msg = message; + this->changes.push_back(msg); +} + +void +SessionFile::commit() +{ + if (!this->_reading) { + SessionQueue::iterator i = changes.begin(); + for(; i != changes.end(); i++) { + try { + fptr->write(*i); + changes.erase(i); + } catch (Glib::IOChannelError e) { + g_warning("Caught I/O exception (error string: %s) while committing change to session file %s. Attempting to commit remaining changes; session file will be inconsistent with whiteboard session history.", e.what().c_str(), this->_filename.c_str()); + } catch (Glib::ConvertError e) { + g_warning("Caught character set conversion error (error string: %s) while committing change to session file %s. Attempting to commit remaining changes; session file will be inconsistent with whiteboard session history.", e.what().c_str(), this->_filename.c_str()); + } + } + fptr->write("\n"); + } +} + +void +SessionFile::close() +{ + fptr->close(true); +} + +} + +} + +/* + 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 : diff --git a/src/jabber_whiteboard/session-file.h b/src/jabber_whiteboard/session-file.h new file mode 100644 index 000000000..926d27af5 --- /dev/null +++ b/src/jabber_whiteboard/session-file.h @@ -0,0 +1,78 @@ +/** + * Whiteboard session file object + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __WHITEBOARD_SESSION_FILE_H__ +#define __WHITEBOARD_SESSION_FILE_H__ + +#include +#include "util/list-container.h" + +struct SPDesktop; + +namespace Inkscape { + +namespace Whiteboard { + +typedef Glib::RefPtr< Glib::IOChannel > SessionFilePtr; +typedef Util::ListContainer< Glib::ustring const > SessionQueue; + +class SessionFile { +public: + SessionFile(Glib::ustring const& filename, bool reading, bool compress); + ~SessionFile(); + + gint64 nextMessageFrom(gint64 from, Glib::ustring& buf); + + void addMessage(Glib::ustring const& message); + void commit(); + void close(); + + Glib::ustring const& filename() + { + return this->_filename; + } + + bool isReadOnly() + { + return this->_reading; + } + + bool eof() + { + return this->_ateof; + } + +private: + SessionQueue changes; + SessionFilePtr fptr; + Glib::ustring _filename; + + bool _ateof; + bool _compress; + bool _reading; +}; + +} + +} +#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:encoding=utf-8:textwidth=99 : diff --git a/src/jabber_whiteboard/session-manager.cpp b/src/jabber_whiteboard/session-manager.cpp new file mode 100644 index 000000000..a52a7a878 --- /dev/null +++ b/src/jabber_whiteboard/session-manager.cpp @@ -0,0 +1,1118 @@ +/** + * Whiteboard session manager + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +/* +#include "inkscape.h" +*/ + +#include + +#include +#include +#include +#include +#include + +#include "gc-anchored.h" + +#include "xml/repr.h" +#include "xml/node-observer.h" + +#include "util/ucompose.hpp" + +#include "message-context.h" +#include "message-stack.h" +#include "desktop-handles.h" +#include "document.h" +#include "document-private.h" +#include "verbs.h" + +#include "jabber_whiteboard/defines.h" +#include "jabber_whiteboard/typedefs.h" +#include "jabber_whiteboard/deserializer.h" +#include "jabber_whiteboard/message-utilities.h" +#include "jabber_whiteboard/message-handler.h" +#include "jabber_whiteboard/node-tracker.h" +#include "jabber_whiteboard/jabber-handlers.h" +#include "jabber_whiteboard/callbacks.h" +#include "jabber_whiteboard/chat-handler.h" +#include "jabber_whiteboard/session-file.h" +#include "jabber_whiteboard/session-file-player.h" +#include "jabber_whiteboard/session-manager.h" +#include "jabber_whiteboard/message-aggregator.h" +#include "jabber_whiteboard/undo-stack-observer.h" +#include "jabber_whiteboard/serializer.h" + +//#include "jabber_whiteboard/pedro/pedroxmpp.h" + +#include "jabber_whiteboard/message-node.h" +#include "jabber_whiteboard/message-queue.h" + +namespace Inkscape { + +namespace Whiteboard { + +#ifdef WIN32 +static bool lm_initialize_called = false; +#endif + +SessionData::SessionData(SessionManager *sm) +{ + this->_sm = sm; + this->recipient = NULL; + this->connection = NULL; + this->ssl = NULL; + this->ignoreFurtherSSLErrors = false; + this->send_queue = new SendMessageQueue(sm); + this->sequence_number = 1; +} + +SessionData::~SessionData() +{ + this->receive_queues.clear(); + + if (this->send_queue) { + delete this->send_queue; + } +} + +SessionManager::SessionManager(::SPDesktop *desktop) +{ + + // Initialize private members to NULL to facilitate deletion in destructor + this->_myDoc = NULL; + this->session_data = NULL; + this->_myCallbacks = NULL; + this->_myTracker = NULL; + this->_myChatHandler = NULL; + this->_mySessionFile = NULL; + this->_mySessionPlayer = NULL; + this->_myMessageHandler = NULL; + this->_myUndoObserver = NULL; + this->_mySerializer = NULL; + this->_myDeserializer = NULL; + + this->setDesktop(desktop); + + if (this->_myDoc == NULL) { + g_error("Initializing SessionManager on null document object!"); + } + + +#ifdef WIN32 + //# lm_initialize() must be called before any network code + if (!lm_initialize_called) { + lm_initialize(); + lm_initialize_called = true; + } +#endif + + this->_setVerbSensitivity(INITIAL); +} + +SessionManager::~SessionManager() +{ + + if (this->session_data) { + if (this->session_data->status[IN_WHITEBOARD]) { + // also calls closeSession + this->disconnectFromDocument(); + } + this->disconnectFromServer(); + + if (this->session_data->status[LOGGED_IN]) { + // TODO: unref message handlers + } + } + + if (this->_mySessionFile) { + delete this->_mySessionPlayer; + delete this->_mySessionFile; + } + + delete this->_myChatHandler; + + + // Deletion of _myTracker is done in closeSession; + // no need to do it here. + + // Deletion is handled separately from session teardown and server disconnection + // because some teardown methods (e.g. closeSession) require access to members that we will + // be deleting. Separating deletion from teardown means that we do not have + // to worry (as much) about proper ordering of the teardown sequence. (We still need + // to ensure that destructors in each object being deleted have access to all the + // members they need, though.) + + // Stop dispatchers + if (this->_myCallbacks) { + this->stopSendQueueDispatch(); + this->stopReceiveQueueDispatch(); + delete this->_myCallbacks; + } + + delete this->_myMessageHandler; + + delete this->session_data; + + Inkscape::GC::release(this->_myDoc); + +} + +void +SessionManager::setDesktop(::SPDesktop* desktop) +{ + this->_myDesktop = desktop; + if (this->_myDoc != NULL) { + Inkscape::GC::release(this->_myDoc); + } + if (SP_DT_DOCUMENT(desktop) != NULL) { + this->_myDoc = SP_DT_DOCUMENT(desktop); + Inkscape::GC::anchor(this->_myDoc); + } +} + +int +SessionManager::connectToServer(Glib::ustring const& server, Glib::ustring const& port, Glib::ustring const& username, Glib::ustring const& pw, bool usessl) +{ + GError* error = NULL; + Glib::ustring jid; + + // JID format is username@server/resource + jid += username + "@" + server + "/" + RESOURCE_NAME; + + LmMessage* m; + LmMessageHandler* mh; + + if (!this->session_data) { + this->session_data = new SessionData(this); + } + + if (!this->_myMessageHandler) { + this->_myMessageHandler = new MessageHandler(this); + } + + // Connect to server + // We need to check to see if this object already exists, because + // the user may be reusing an old connection that failed due to e.g. + // authentication failure. + if (this->session_data->connection) { + lm_connection_close(this->session_data->connection, &error); + lm_connection_unref(this->session_data->connection); + } + + this->session_data->connection = lm_connection_new(server.c_str()); + + lm_connection_set_port(this->session_data->connection, atoi(port.c_str())); + + if (usessl) { + if (lm_ssl_is_supported()) { + this->session_data->ssl = lm_ssl_new(NULL, ssl_error_handler, reinterpret_cast< gpointer >(this), NULL); + + lm_ssl_ref(this->session_data->ssl); + } else { + return SSL_INITIALIZATION_ERROR; + } + lm_connection_set_ssl(this->session_data->connection, this->session_data->ssl); + } + + // Send authorization + lm_connection_set_jid(this->session_data->connection, jid.c_str()); + + // TODO: + // Asynchronous connection and authentication would be nice, + // but it's a huge mess of mixing C callbacks and C++ method calls. + // I've tried to do it and only managed to severely destabilize the Jabber + // server connection routines. + // + // This, of course, is an invitation to anyone more capable than me + // to convert this from synchronous to asynchronous Loudmouth calls. + if (!lm_connection_open_and_block(this->session_data->connection, &error)) { + if (error != NULL) { + g_warning("Failed to open: %s", error->message); + } + return FAILED_TO_CONNECT; + } + + // Authenticate + if (!lm_connection_authenticate_and_block(this->session_data->connection, username.c_str(), pw.c_str(), RESOURCE_NAME, &error)) { + if (error != NULL) { + g_warning("Failed to authenticate: %s", error->message); + } + lm_connection_close(this->session_data->connection, NULL); + lm_connection_unref(this->session_data->connection); + this->session_data->connection = NULL; + return INVALID_AUTH; + } + + // Register message handler for presence messages + mh = lm_message_handler_new((LmHandleMessageFunction)presence_handler, reinterpret_cast< gpointer >(this->_myMessageHandler), NULL); + lm_connection_register_message_handler(this->session_data->connection, mh, LM_MESSAGE_TYPE_PRESENCE, LM_HANDLER_PRIORITY_NORMAL); + + // Register message handler for stream error messages + mh = lm_message_handler_new((LmHandleMessageFunction)stream_error_handler, reinterpret_cast< gpointer >(this->_myMessageHandler), NULL); + lm_connection_register_message_handler(this->session_data->connection, mh, LM_MESSAGE_TYPE_STREAM_ERROR, LM_HANDLER_PRIORITY_NORMAL); + + // Register message handler for chat messages + mh = lm_message_handler_new((LmHandleMessageFunction)default_handler, reinterpret_cast< gpointer >(this->_myMessageHandler), NULL); + lm_connection_register_message_handler(this->session_data->connection, mh, LM_MESSAGE_TYPE_MESSAGE, LM_HANDLER_PRIORITY_NORMAL); + + // Send presence message to server + m = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_PRESENCE, LM_MESSAGE_SUB_TYPE_NOT_SET); + if (!lm_connection_send(this->session_data->connection, m, &error)) { + if (error != NULL) { + g_warning("Presence message could not be sent: %s", error->message); + } + lm_connection_close(this->session_data->connection, NULL); + lm_connection_unref(this->session_data->connection); + this->session_data->connection = NULL; + return FAILED_TO_CONNECT; + } + + this->session_data->status.set(LOGGED_IN, 1); + + this->_myCallbacks = new Callbacks(this); + + lm_message_unref(m); + + this->_setVerbSensitivity(ESTABLISHED_CONNECTION); + + return CONNECT_SUCCESS; +} + +LmSSLResponse +SessionManager::handleSSLError(LmSSL* ssl, LmSSLStatus status) +{ + if (this->session_data->ignoreFurtherSSLErrors) { + return LM_SSL_RESPONSE_CONTINUE; + } + + Glib::ustring msg; + + // TODO: It'd be nice to provide the user with additional information in some cases, + // like fingerprints, hostname, etc. + switch(status) { + case LM_SSL_STATUS_NO_CERT_FOUND: + msg = _("No SSL certificate was found."); + break; + case LM_SSL_STATUS_UNTRUSTED_CERT: + msg = _("The SSL certificate provided by the Jabber server is untrusted."); + break; + case LM_SSL_STATUS_CERT_EXPIRED: + msg = _("The SSL certificate provided by the Jabber server is expired."); + break; + case LM_SSL_STATUS_CERT_NOT_ACTIVATED: + msg = _("The SSL certificate provided by the Jabber server has not been activated."); + break; + case LM_SSL_STATUS_CERT_HOSTNAME_MISMATCH: + msg = _("The SSL certificate provided by the Jabber server contains a hostname that does not match the Jabber server's hostname."); + break; + case LM_SSL_STATUS_CERT_FINGERPRINT_MISMATCH: + msg = _("The SSL certificate provided by the Jabber server contains an invalid fingerprint."); + break; + case LM_SSL_STATUS_GENERIC_ERROR: + msg = _("An unknown error occurred while setting up the SSL connection."); + break; + } + + // TRANSLATORS: %1 is the message that describes the specific error that occurred when + // establishing the SSL connection. + Glib::ustring mainmsg = String::ucompose(_("%1\n\nDo you wish to continue connecting to the Jabber server?"), msg); + + Gtk::MessageDialog dlg(mainmsg, true, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE, false); + dlg.add_button(_("Continue connecting and ignore further errors"), 0); + dlg.add_button(_("Continue connecting, but warn me of further errors"), 1); + dlg.add_button(_("Cancel connection"), 2); + + switch(dlg.run()) { + case 0: + this->session_data->ignoreFurtherSSLErrors = true; + /* FALL-THROUGH */ + case 1: + return LM_SSL_RESPONSE_CONTINUE; + + default: + return LM_SSL_RESPONSE_STOP; + } +} + +void +SessionManager::disconnectFromServer() +{ + if (this->session_data->connection) { + GError* error = NULL; + + LmMessage *m; + this->disconnectFromDocument(); + m = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_PRESENCE, LM_MESSAGE_SUB_TYPE_UNAVAILABLE); + if (!lm_connection_send(this->session_data->connection, m, &error)) { + g_warning("Could not send unavailable presence message: %s", error->message); + } + + lm_message_unref(m); + lm_connection_close(this->session_data->connection, NULL); + lm_connection_unref(this->session_data->connection); + if (this->session_data->ssl) { + lm_ssl_unref(this->session_data->ssl); + } + + this->session_data->connection = NULL; + this->session_data->ssl = NULL; + this->_setVerbSensitivity(INITIAL); + } +} + +void +SessionManager::disconnectFromDocument() +{ + if (this->session_data->status[IN_WHITEBOARD] || !this->session_data->status[IN_CHATROOM]) { + this->sendMessage(DISCONNECTED_FROM_USER_SIGNAL, 0, "", this->session_data->recipient, false); + } + this->closeSession(); + this->_setVerbSensitivity(DISCONNECTED_FROM_SESSION); +} + +void +SessionManager::closeSession() +{ + + if (this->session_data->status[IN_WHITEBOARD]) { + this->session_data->status.set(IN_WHITEBOARD, 0); + this->session_data->receive_queues.clear(); + this->session_data->send_queue->clear(); + this->stopSendQueueDispatch(); + this->stopReceiveQueueDispatch(); + } + + if (this->_myUndoObserver) { + this->_myDoc->removeUndoObserver(*this->_myUndoObserver); + } + + delete this->_myUndoObserver; + delete this->_mySerializer; + delete this->_myDeserializer; + + this->_myUndoObserver = NULL; + this->_mySerializer = NULL; + this->_myDeserializer = NULL; + + if (this->_myTracker) { + delete this->_myTracker; + this->_myTracker = NULL; + } + + + this->setRecipient(NULL); +} + +void +SessionManager::setRecipient(char const* recipientJID) +{ + if (this->session_data->recipient) { + free(const_cast< gchar* >(this->session_data->recipient)); + } + + if (recipientJID == NULL) { + this->session_data->recipient = NULL; + } else { + this->session_data->recipient = g_strdup(recipientJID); + } +} + +void +SessionManager::sendChange(Glib::ustring const& msg, MessageType type, std::string const& recipientJID, bool chatroom) +{ + if (!this->session_data->status[IN_WHITEBOARD]) { + return; + } + + std::string& recipient = const_cast< std::string& >(recipientJID); + if (recipient.empty()) { + recipient = this->session_data->recipient; + } + + + switch (type) { + case DOCUMENT_BEGIN: + case DOCUMENT_END: + case CHANGE_NOT_REPEATABLE: + case CHANGE_REPEATABLE: + case CHANGE_COMMIT: + { + MessageNode *newNode = new MessageNode(this->session_data->sequence_number++, lm_connection_get_jid(this->session_data->connection), recipient, msg, type, false, chatroom); + this->session_data->send_queue->insert(newNode); + Inkscape::GC::release(newNode); + break; + } + default: + g_warning("Cannot insert MessageNode with unknown change type into send queue; discarding message. This may lead to desynchronization!"); + break; + } +} + + +// FIXME: +// This method needs a massive, massive, massive overhaul. +int +SessionManager::sendMessage(MessageType msgtype, unsigned int sequence, Glib::ustring const& msg, char const* recipientJID, bool chatroom) +{ + LmMessage* m; + GError* error = NULL; + char* type, * seq; + + if (recipientJID == NULL || recipientJID == "") { + g_warning("Null recipient JID specified; not sending message."); + return NO_RECIPIENT_JID; + } else { + } + + // create message + m = lm_message_new(recipientJID, LM_MESSAGE_TYPE_MESSAGE); + + // add sender + lm_message_node_set_attribute(m->node, "from", lm_connection_get_jid(this->session_data->connection)); + + // set message subtype according to whether or not this is + // destined for a chatroom + if (chatroom) { + lm_message_node_set_attribute(m->node, "type", "groupchat"); + } else { + lm_message_node_set_attribute(m->node, "type", "chat"); + } + + // set protocol version; + // we are currently fixed at version 1 + lm_message_node_add_child(m->node, MESSAGE_PROTOCOL_VER, MESSAGE_PROTOCOL_V1); + + // add message type + type = (char *)calloc(TYPE_FIELD_SIZE, sizeof(char)); + snprintf(type, TYPE_FIELD_SIZE, "%i", msgtype); + lm_message_node_add_child(m->node, MESSAGE_TYPE, type); + free(type); + + // add message body + if (!msg.empty()) { + lm_message_node_add_child(m->node, MESSAGE_BODY, msg.c_str()); + } else { + } + + // add sequence number + switch(msgtype) { + case CHANGE_REPEATABLE: + case CHANGE_NOT_REPEATABLE: + case DUMMY_CHANGE: + case CHANGE_COMMIT: + case DOCUMENT_BEGIN: + case DOCUMENT_END: + case CONNECT_REQUEST_RESPONSE_CHAT: + case CONNECT_REQUEST_RESPONSE_USER: + case CHATROOM_SYNCHRONIZE_RESPONSE: + seq = (char* )calloc(SEQNUM_FIELD_SIZE, sizeof(char)); + sprintf(seq, "%u", sequence); + lm_message_node_add_child(m->node, MESSAGE_SEQNUM, seq); + free(seq); + break; + + case CONNECT_REQUEST_USER: + case CONNECTED_SIGNAL: + case DISCONNECTED_FROM_USER_SIGNAL: + break; + + // Error messages and synchronization requests do not need a sequence number + case CHATROOM_SYNCHRONIZE_REQUEST: + case CONNECT_REQUEST_REFUSED_BY_PEER: + case UNSUPPORTED_PROTOCOL_VERSION: + case ALREADY_IN_SESSION: + break; + + default: + g_warning("Outgoing message type not recognized; not sending."); + lm_message_unref(m); + return UNKNOWN_OUTGOING_TYPE; + } + + // We want to log messages even if they were not successfully sent, + // since the user may opt to re-synchronize a session using the session + // file record. + if (!msg.empty()) { + this->_log(msg); + this->_commitLog(); + } + + // send message + + if (!lm_connection_send(this->session_data->connection, m, &error)) { + g_warning("Send failed: %s", error->message); + lm_message_unref(m); + return CONNECTION_ERROR; + } + + lm_message_unref(m); + return SEND_SUCCESS; +} + +void +SessionManager::connectionError(Glib::ustring const& errmsg) +{ + Gtk::MessageDialog dlg(errmsg, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_CLOSE); + dlg.run(); +// sp_whiteboard_connect_dialog(const_cast< gchar* >(errmsg)); +} + +void +SessionManager::resendDocument(char const* recipientJID, KeyToNodeMap& newidsbuf, NodeToKeyMap& newnodesbuf) +{ + Glib::ustring docbegin = MessageUtilities::makeTagWithContent(MESSAGE_DOCBEGIN, ""); + this->sendChange(docbegin, DOCUMENT_BEGIN, recipientJID, false); + + Inkscape::XML::Node* root = sp_document_repr_root(this->_myDoc); + + if(root == NULL) { + return; + } + + NewChildObjectMessageList newchildren; + MessageAggregator& agg = MessageAggregator::instance(); + Glib::ustring buf; + + for ( Inkscape::XML::Node *child = root->firstChild() ; child != NULL ; child = child->next() ) { + // TODO: replace with Serializer methods + MessageUtilities::newObjectMessage(&buf, newidsbuf, newnodesbuf, newchildren, this->_myTracker, child); + + NewChildObjectMessageList::iterator j = newchildren.begin(); + Glib::ustring aggbuf; + + for(; j != newchildren.end(); j++) { + if (!agg.addOne(*j, aggbuf)) { + this->sendChange(aggbuf, CHANGE_REPEATABLE, recipientJID, false); + aggbuf.clear(); + agg.addOne(*j, aggbuf); + } + } + + // send remaining changes + if (!aggbuf.empty()) { + this->sendChange(aggbuf, CHANGE_REPEATABLE, recipientJID, false); + aggbuf.clear(); + } + + newchildren.clear(); + buf.clear(); + } + + Glib::ustring commit = MessageUtilities::makeTagWithContent(MESSAGE_COMMIT, ""); + this->sendChange(commit, CHANGE_COMMIT, recipientJID, false); + Glib::ustring docend = MessageUtilities::makeTagWithContent(MESSAGE_DOCEND, ""); + this->sendChange(docend, DOCUMENT_END, recipientJID, false); +} + +void +SessionManager::receiveChange(Glib::ustring const& changemsg) +{ + + struct Node part; + + Glib::ustring msgcopy = changemsg.c_str(); + + + while(MessageUtilities::getFirstMessageTag(part, msgcopy) != false) { + // TODO: + // Yikes. This is ugly. + if (part.tag == MESSAGE_CHANGE) { + this->_myDeserializer->deserializeEventChgAttr(part.data); + msgcopy.erase(0, part.next_pos); + + } else if (part.tag == MESSAGE_NEWOBJ) { + this->_myDeserializer->deserializeEventAdd(part.data); + msgcopy.erase(0, part.next_pos); + + } else if (part.tag == MESSAGE_DELETE) { + this->_myDeserializer->deserializeEventDel(part.data); + msgcopy.erase(0, part.next_pos); + + } else if (part.tag == MESSAGE_DOCUMENT) { + // no special handler, just keep going with the rest of the message + msgcopy.erase(0, part.next_pos); + + } else if (part.tag == MESSAGE_NODECONTENT) { + this->_myDeserializer->deserializeEventChgContent(part.data); + msgcopy.erase(0, part.next_pos); + + } else if (part.tag == MESSAGE_ORDERCHANGE) { + this->_myDeserializer->deserializeEventChgOrder(part.data); + msgcopy.erase(0, part.next_pos); + + } else if (part.tag == MESSAGE_COMMIT) { + // Retrieve the deserialized event log, node actions, and nodes with updated attributes + XML::Event* log = this->_myDeserializer->getEventLog(); + KeyToNodeActionList& node_changes = this->_myDeserializer->getNodeTrackerActions(); + AttributesUpdatedSet& updated = this->_myDeserializer->getUpdatedAttributeNodeSet(); + + // Make document insensitive to undo + gboolean saved = sp_document_get_undo_sensitive(this->_myDoc); + sp_document_set_undo_sensitive(this->_myDoc, FALSE); + + // Replay the log and push it onto the undo stack + sp_repr_replay_log(log); + + // Call updateRepr on changed nodes + // This is required for some tools to function properly, i.e. text tool + // (TODO: we don't need to update _all_ changed nodes, just their parents) + AttributesUpdatedSet::iterator i = updated.begin(); + for(; i != updated.end(); i++) { + SPObject* updated = this->_myDoc->getObjectByRepr(*i); + if (updated) { + updated->updateRepr(); + } + } + + // merge the events generated by updateRepr + sp_repr_coalesce_log(this->_myDoc->priv->partial, log); + this->_myDoc->priv->partial = NULL; + + this->_myDoc->priv->undo = g_slist_prepend(this->_myDoc->priv->undo, log); + + // Restore undo sensitivity + sp_document_set_undo_sensitive(this->_myDoc, saved); + + // Add or delete nodes to/from the tracker + this->_myTracker->process(node_changes); + + // Reset deserializer state + this->_myDeserializer->reset(); + break; + + } else if (part.tag == MESSAGE_UNDO) { + this->_myUndoObserver->lockObserverFromSending(UndoStackObserver::UNDO_EVENT); + sp_document_undo(this->_myDoc); + this->_myUndoObserver->unlockObserverFromSending(UndoStackObserver::UNDO_EVENT); + msgcopy.erase(0, part.next_pos); + + } else if (part.tag == MESSAGE_REDO) { + this->_myUndoObserver->lockObserverFromSending(UndoStackObserver::REDO_EVENT); + sp_document_redo(this->_myDoc); + this->_myUndoObserver->unlockObserverFromSending(UndoStackObserver::REDO_EVENT); + msgcopy.erase(0, part.next_pos); + + } else if (part.tag == MESSAGE_DOCBEGIN) { + msgcopy.erase(0, part.next_pos); + + } else if (part.tag == MESSAGE_DOCEND) { + // Set this to be the new original state of the document + sp_document_done(this->document()); + sp_document_clear_redo(this->document()); + sp_document_clear_undo(this->document()); + this->setupCommitListener(); + msgcopy.erase(0, part.next_pos); + + } else { + msgcopy.erase(0, part.next_pos); + + } + } + + this->_log(changemsg); + + this->_commitLog(); +} + +bool +SessionManager::isPlayingSessionFile() +{ + return this->session_data->status[PLAYING_SESSION_FILE]; +} + +void +SessionManager::loadSessionFile(Glib::ustring filename) +{ + if (!this->session_data || !this->session_data->status[IN_WHITEBOARD]) { + try { + if (this->_mySessionFile) { + delete this->_mySessionFile; + } + this->_mySessionFile = new SessionFile(filename, true, false); + + // Initialize objects needed for session playback + if (this->_mySessionPlayer == NULL) { + this->_mySessionPlayer = new SessionFilePlayer(16, this); + } else { + this->_mySessionPlayer->load(this->_mySessionFile); + } + + if (this->_myTracker == NULL) { + this->_myTracker = new XMLNodeTracker(this); + } else { + this->_myTracker->reset(); + } + + if (!this->session_data) { + this->session_data = new SessionData(this); + } + + if (!this->_myDeserializer) { + this->_myDeserializer = new Deserializer(this->node_tracker()); + } + + if (!this->_myUndoObserver) { + this->setupCommitListener(); + } + + this->session_data->status.set(PLAYING_SESSION_FILE, 1); + + + } catch (Glib::FileError e) { + g_warning("Could not load session file: %s", e.what().data()); + } + } +} + +void +SessionManager::userConnectedToWhiteboard(gchar const* JID) +{ + SP_DT_MSGSTACK(this->_myDesktop)->flashF(Inkscape::INFORMATION_MESSAGE, _("Established whiteboard session with %s."), JID); +} + + +void +SessionManager::userDisconnectedFromWhiteboard(std::string const& JID) +{ + + SP_DT_MSGSTACK(this->_myDesktop)->flashF(Inkscape::INFORMATION_MESSAGE, _("%s has left the whiteboard session."), JID.c_str()); + + // Inform the user + // TRANSLATORS: %1 is the name of the user that disconnected, %2 is the name of the user whom the disconnected user disconnected from. + // This message is not used in a chatroom context. + Glib::ustring primary = String::ucompose(_("The user %1 has left the whiteboard session.\n\n"), JID); + // TRANSLATORS: %1 and %2 are userids + Glib::ustring secondary = String::ucompose(_("You are still connected to a Jabber server as %2, and may establish a new session to %1 or a different user."), JID, lm_connection_get_jid(this->session_data->connection)); + + // TODO: parent this dialog to the active desktop + Gtk::MessageDialog dialog(primary + secondary, true, Gtk::MESSAGE_INFO, Gtk::BUTTONS_CLOSE, false); + /* + dialog.set_message(primary, true); + dialog.set_secondary_text(secondary, true); + */ + dialog.run(); +} + +void +SessionManager::startSendQueueDispatch() +{ + this->_send_queue_dispatcher = Glib::signal_timeout().connect(sigc::mem_fun(*(this->_myCallbacks), &Callbacks::dispatchSendQueue), SEND_TIMEOUT); +} + +void +SessionManager::stopSendQueueDispatch() +{ + if (this->_send_queue_dispatcher) { + this->_send_queue_dispatcher.disconnect(); + } +} + +void +SessionManager::startReceiveQueueDispatch() +{ + this->_receive_queue_dispatcher = Glib::signal_timeout().connect(sigc::mem_fun(*(this->_myCallbacks), &Callbacks::dispatchReceiveQueue), SEND_TIMEOUT); +} + +void +SessionManager::stopReceiveQueueDispatch() +{ + if (this->_receive_queue_dispatcher) { + this->_receive_queue_dispatcher.disconnect(); + } +} + +void +SessionManager::clearDocument() +{ + // clear all layers, definitions, and metadata + XML::Node* rroot = this->_myDoc->rroot; + + // clear definitions + XML::Node* defs = SP_OBJECT_REPR((SPDefs*)SP_DOCUMENT_DEFS(this->_myDoc)); + g_assert(SP_ROOT(this->_myDoc->root)->defs); + + for(XML::Node* child = defs->firstChild(); child; child = child->next()) { + defs->removeChild(child); + } + + // clear layers + for(XML::Node* child = rroot->firstChild(); child; child = child->next()) { + if (strcmp(child->name(), "svg:g") == 0) { + rroot->removeChild(child); + } + } + + // clear metadata + for(XML::Node* child = rroot->firstChild(); child; child = child->next()) { + if (strcmp(child->name(), "svg:metadata") == 0) { + rroot->removeChild(child); + } + } + +// sp_document_done(this->_myDoc); +} + +void +SessionManager::setupInkscapeInterface() +{ + this->session_data->status.set(IN_WHITEBOARD, 1); + this->startSendQueueDispatch(); + this->startReceiveQueueDispatch(); + if (!this->_myTracker) { + this->_myTracker = new XMLNodeTracker(this); + } + + this->_mySerializer = new Serializer(this->node_tracker()); + this->_myDeserializer = new Deserializer(this->node_tracker()); + + // We're in a whiteboard session now, so set verb sensitivity accordingly + this->_setVerbSensitivity(ESTABLISHED_SESSION); +} + +void +SessionManager::setupCommitListener() +{ + this->_myUndoObserver = new Whiteboard::UndoStackObserver(this); + this->_myDoc->addUndoObserver(*this->_myUndoObserver); +} + +::SPDesktop* +SessionManager::desktop() +{ + return this->_myDesktop; +} + +::SPDocument* +SessionManager::document() +{ + return this->_myDoc; +} + +Callbacks* +SessionManager::callbacks() +{ + return this->_myCallbacks; +} + +Whiteboard::UndoStackObserver* +SessionManager::undo_stack_observer() +{ + return this->_myUndoObserver; +} + +Serializer* +SessionManager::serializer() +{ + return this->_mySerializer; +} + +XMLNodeTracker* +SessionManager::node_tracker() +{ + return this->_myTracker; +} + + +ChatMessageHandler* +SessionManager::chat_handler() +{ + return this->_myChatHandler; +} + +SessionFilePlayer* +SessionManager::session_player() +{ + return this->_mySessionPlayer; +} + +SessionFile* +SessionManager::session_file() +{ + return this->_mySessionFile; +} + +void +SessionManager::_log(Glib::ustring const& message) +{ + if (this->_mySessionFile && !this->_mySessionFile->isReadOnly()) { + this->_mySessionFile->addMessage(message); + } +} + +void +SessionManager::_commitLog() +{ + if (this->_mySessionFile && !this->_mySessionFile->isReadOnly()) { + this->_mySessionFile->commit(); + } +} + +void +SessionManager::_closeLog() +{ + if (this->_mySessionFile) { + this->_mySessionFile->close(); + } +} + +void +SessionManager::startLog(Glib::ustring filename) +{ + try { + this->_mySessionFile = new SessionFile(filename, false, false); + } catch (Glib::FileError e) { + g_warning("Caught I/O error %s while attemping to open file %s for session recording.", e.what().c_str(), filename.c_str()); + throw; + } +} + +void +SessionManager::_tryToStartLog() +{ + if (this->session_data) { + if (!this->session_data->sessionFile.empty()) { + bool undecided = true; + while(undecided) { + try { + this->startLog(this->session_data->sessionFile); + undecided = false; + } catch (Glib::FileError e) { + undecided = true; + Glib::ustring msg = String::ucompose(_("Could not open file %1 for session recording.\nThe error encountered was: %2.\n\nYou may select a different location to record the session, or you may opt to not record this session."), this->session_data->sessionFile, e.what()); + Gtk::MessageDialog dlg(msg, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_NONE, false); + dlg.add_button(_("Choose a different location"), 0); + dlg.add_button(_("Skip session recording"), 1); + switch (dlg.run()) { + case 0: + { + Gtk::FileChooserDialog sessionfiledlg(_("Select a location and filename"), Gtk::FILE_CHOOSER_ACTION_SAVE); + sessionfiledlg.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); + sessionfiledlg.add_button(_("Set filename"), Gtk::RESPONSE_OK); + int result = sessionfiledlg.run(); + switch (result) { + case Gtk::RESPONSE_OK: + { + this->session_data->sessionFile = sessionfiledlg.get_filename(); + break; + } + case Gtk::RESPONSE_CANCEL: + default: + undecided = false; + break; + } + break; + } + case 1: + default: + undecided = false; + break; + } + } + } + } + } +} + +void +SessionManager::_setVerbSensitivity(SensitivityMode mode) +{ + return; + + switch (mode) { + case ESTABLISHED_CONNECTION: + // Upon successful connection, we can disconnect from the server. + // We can also start sharing a document with a user or chatroom. + // We cannot, however, connect to a new server without first disconnecting. + Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_CONNECT)->sensitive(this->_myDoc, false); + Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SERVER)->sensitive(this->_myDoc, true); + Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, true); + Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, true); + break; + + case ESTABLISHED_SESSION: + // When we have established a session, we should not permit the user to go and + // establish another session from the same document without first disconnecting. + // + // TODO: Well, actually, we probably _should_, but there's no real reconnection logic just yet. + Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, false); + Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, false); + Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SESSION)->sensitive(this->_myDoc, true); + break; + case DISCONNECTED_FROM_SESSION: + // Upon disconnecting from a session, we can establish a new session and disconnect + // from the server, but we cannot disconnect from a session (since we just left it.) + Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SESSION)->sensitive(this->_myDoc, false); + Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, true); + Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, true); + + case INITIAL: + default: + // Upon construction, there is no active connection, so we cannot do the following: + // (1) disconnect from a session + // (2) disconnect from a server + // (3) share with a user + // (4) share with a chatroom + Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_CONNECT)->sensitive(this->_myDoc, true); + Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, false); + Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, false); + Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SESSION)->sensitive(this->_myDoc, false); + Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SERVER)->sensitive(this->_myDoc, false); + break; + }; +} + +/* +void +SessionManager::Listener::processXmppEvent(Pedro::XmppEvent const& event) +{ + int type = event.getType(); + + switch (type) { + case Pedro::XmppEvent::EVENT_STATUS: + break; + case Pedro::XmppEvent::EVENT_ERROR: + break; + case Pedro::XmppEvent::EVENT_CONNECTED: + break; + case Pedro::XmppEvent::EVENT_DISCONNECTED: + break; + case Pedro::XmppEvent::EVENT_MESSAGE: + break; + case Pedro::XmppEvent::EVENT_PRESENCE: + break; + case Pedro::XmppEvent::EVENT_MUC_MESSAGE: + break; + case Pedro::XmppEvent::EVENT_MUC_JOIN: + break; + case Pedro::XmppEvent::EVENT_MUC_LEAVE: + break; + case Pedro::XmppEvent::EVENT_MUC_PRESENCE: + 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:encoding=utf-8:textwidth=99 : diff --git a/src/jabber_whiteboard/session-manager.h b/src/jabber_whiteboard/session-manager.h new file mode 100644 index 000000000..d52cca3de --- /dev/null +++ b/src/jabber_whiteboard/session-manager.h @@ -0,0 +1,568 @@ +/** + * Whiteboard session manager + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __SESSION_MANAGER_H__ +#define __SESSION_MANAGER_H__ + +#include +#include +#include + +extern "C" { +#include +} + +#include "jabber_whiteboard/typedefs.h" +#include "jabber_whiteboard/defines.h" +#include "jabber_whiteboard/buddy-list-manager.h" + +#include "gc-alloc.h" + +struct SPDesktop; +struct SPDocument; + +namespace Inkscape { +namespace XML { +class Node; +} +} + +namespace Inkscape { + +namespace Whiteboard { + +class ReceiveMessageQueue; +class SendMessageQueue; +class XMLNodeTracker; +class SessionManager; +class MessageHandler; +class ChatMessageHandler; +class Callbacks; +class SessionFile; +class SessionFilePlayer; +class UndoStackObserver; +class Serializer; +class Deserializer; + +/// Jabber resource name +#define RESOURCE_NAME "Inkboard" + +/// connectToServer return values +#define CONNECT_SUCCESS 0 +#define FAILED_TO_CONNECT 1 +#define INVALID_AUTH 2 +#define SSL_INITIALIZATION_ERROR 3 + +/// sendMessage return values +#define SEND_SUCCESS 0 +#define CONNECTION_ERROR 1 +#define UNKNOWN_OUTGOING_TYPE 2 +#define NO_RECIPIENT_JID 3 + +/** + * Structure grouping data items pertinent to a whiteboard session. + * + * SessionData holds all session data for both 1:1 and chatroom conferences. + * Access to members should be controlled by first querying the status bitset + * to see if useful data will actually exist in that member -- i.e. checking + * status[IN_CHATROOM] to see if the chatters set will contain anything. + * It usually won't hurt to do a straight query -- there are very few members + * that remain uninitialized for very long -- but it's a good idea to check. + */ +struct SessionData { +public: + /** + * Constructor. + * + * \param sm The SessionManager with which a SessionData instance should be + * associated with. + */ + SessionData(SessionManager *sm); + + ~SessionData(); + + /** + * The JID of the recipient: either another user JID or the JID of a chatroom. + */ + gchar const* recipient; + + /** + * Pointer to Loudmouth connection structure. + * Used for Loudmouth calls that require it. + */ + LmConnection* connection; + + /** + * SSL information structure for SSL connections. + */ + LmSSL* ssl; + + /** + * Flag indicating whether or not we should ignore further SSL errors for a given session. + */ + bool ignoreFurtherSSLErrors; + + + /** + * A user's handle in a Jabber chatroom. + */ + Glib::ustring chat_handle; + + /** + * Name of the chatroom that a user in a chatroom is connected to. + */ + Glib::ustring chat_name; + + /** + * Name of the conference server. + */ + Glib::ustring chat_server; + + // Message queues + + /** + * Map associating senders to receive queues. + */ + RecipientToReceiveQueueMap receive_queues; + + /** + * Map associating senders to commit events sent by those committers. + */ + CommitsQueue recipients_committed_queue; + + /** + * Pointer to queue for messages to be sent. + */ + SendMessageQueue* send_queue; + + // Message sequence numbers + + /** + * The sequence number of the latest message sent by this client in a given session. + * Used for determining the sequence number of the next message. + */ + unsigned int sequence_number; + + //unsigned int latest_sent_transaction; + //RecipientToLatestTransactionMap latest_processed_transactions; + + + // Status tracking + /** + * Session state and status flags. + */ + std::bitset< NUM_FLAGS > status; + + /** + * Jabber buddy list data. + */ + BuddyListManager buddyList; + + /** + * List of participants in a Jabber chatroom. + */ + ChatterList chatters; + + /** + * Session file filename; blank if no session file is to be + * recorded. + */ + Glib::ustring sessionFile; + +private: + // access to containing class + SessionManager *_sm; + + // noncopyable, nonassignable + SessionData(SessionData const&); + SessionData& operator=(SessionData const&); +}; + + +// TODO: This class is huge. It might be best to refactor it into smaller, +// more coherent chunks. +// +// TODO: convert to pass-by-reference where appropriate. In particular, a lot of the +// string buffers passed to methods in the argument list can be made into references +// appropriately and easily. + +/** + * Session management class for Inkboard. + * + * By "session management", we refer to the management of all events that an Inkboard + * session may need to handle: negotiating a connection to a Jabber server, negotiating + * sessions with users and chatrooms, sending, receiving, and parsing messages, and so + * forth. + * + * SessionManager instances are associated with Inkscape desktop objects on a 1:1 basis. + */ +class SessionManager { +public: + /** + * Constructor. + * + * \param desktop The desktop with which this SessionManager is associated. */ + SessionManager(::SPDesktop *desktop); + ~SessionManager(); + + // Session tracking data + + /** + * Pointer to SessionData structure. + */ + struct SessionData *session_data; + + // Inkscape interface + + /** + * Set the desktop with which this SessionManager is associated. + * + * @param desktop the desktop with which this SessionManager should be associated + */ + void setDesktop(::SPDesktop* desktop); + + // Session management + + /** + * Connect to a Jabber server. + * + * @param server Jabber server URL + * @param username Jabber username + * @param pw password for Jabber account + * @param usessl use SSL for connection + * + * @return CONNECT_SUCCESS if connection successful; FAILED_TO_CONNECT if connection failed or INVALID_AUTH + * if authentication invalid + */ + int connectToServer(Glib::ustring const& server, Glib::ustring const& port, Glib::ustring const& username, Glib::ustring const& pw, bool usessl); + + /** + * Handle an SSL error by prompting the user for feedback, and continuing or aborting the connection + * process based on that feedback. + * + * @param ssl pointer to LmSSL structure + * @param status The error message + * + * @return LM_SSL_RESPONSE_CONTINUE if user wishes to continue establishing the connection or LM_SSL_RESPONSE_STOP if user wishes to abort connection + */ + LmSSLResponse handleSSLError(LmSSL* ssl, LmSSLStatus status); + + /** + * Disconnect from a Jabber server. + * + * This invokes disconnectFromDocument(). + * + * \see Inkscape::Whiteboard::SessionManager::disconnectFromDocument + */ + void disconnectFromServer(); + + /** + * Disconnect from a document session. The connection to the Jabber server is not + * broken, and may be reused to connect to a new document session. + * + */ + void disconnectFromDocument(); + + /** + * Perform session teardown. This method by itself does not disconnect from a document or + * a Jabber server. + * + */ + void closeSession(); + + /** + * Set the recipient for Inkboard messages. + * + * @param recipientJID the recipient's JID + */ + void setRecipient(char const* recipientJID); + + // Message sending utilities + + /** + * Put an Inkboard message into the send queue. + * This method does not actually send anything to an Inkboard client. + * + * \see Inkscape::Whiteboard::SessionManager::sendMessage + * + * + * @param msg the message to send + * @param type the type of message (only CHANGE_* types permitted) + * @param chatroom whether or not this message is destined for a chatroom + */ + void sendChange(Glib::ustring const& msg, MessageType type, std::string const& recipientJID, bool chatroom); + + /** + * Send a message to an Inkboard client. + * + * + * @param msgtype the type of message to send + * @param sequence message sequence number + * @param msg the message to send + * @param recipientJID the JID of the recipient + * @param chatroom whether or not this message is destined for a chatroom + * + * @return SEND_SUCCESS if successful; otherwise: UNKNOWN_OUTGOING_TYPE if msgtype is not recognized, NO_RECIPIENT_JID if recipientJID is NULL or blank, CONNECTION_ERROR if Jabber connection error occurred + */ + int sendMessage(MessageType msgtype, unsigned int sequence, Glib::ustring const& msg, char const* recipientJID, bool chatroom); + + /** + * Inform the user of a connection error via a Gtk::MessageDialog. + * + * @param errmsg message to display + */ + void connectionError(Glib::ustring const& errmsg); + + /** + * Stream the contents of the document with which this SessionManager is associated with to the given recipient. + * + * @param recipientJID the JID of the recipient + * @param newidsbuf buffer to store IDs of new nodes + * @param newnodesbuf buffer to store address of new nodes + */ + void resendDocument(char const* recipientJID, KeyToNodeMap& newidsbuf, NodeToKeyMap& newnodesbuf); + + + /** + * Send a connection request to another Inkboard client. + * + * + * @param recipientJID the JID to connect to + * @param document document message to send + */ + void sendRequestToUser(std::string const& recipientJID); + + /** + * Send a connection request to chatroom. + * + * @param server server to connect to + * @param chatroom name of chatroom + * @param handle chatroom handle to use + * @param password chatroom password; leave NULL if no password + */ + void sendRequestToChatroom(Glib::ustring const& server, Glib::ustring const& chatroom, Glib::ustring const& handle, Glib::ustring const& password); + + /** + * Send a connection request response to a user who requested to connect to us. + * + * @param requesterJID the JID of the user whom sent us the request + * @param accepted_request whether or not we accepted the request + */ + void sendConnectRequestResponse(char const* requesterJID, gboolean accepted_request); + + /** + * Method called when a connection request is received. This method produces a dialog + * that asks the user whether or not s/he would like to accept the request. + * + * + * @param requesterJID the JID of the user whom sent us the request + * @param msg the message associated with this request + */ + void receiveConnectRequest(gchar const* requesterJID); + + /** + * Method called when a response to a connection request is received. + * This method performs any necessary session setup/teardown and user notification + * depending on the response received. + * + * + * @param msg the message associated with this request + * @param response the response code + * @param sender the JID of the user whom responded to our request + */ + void receiveConnectRequestResponse(InvitationResponses response, std::string& sender); + + /** + * Method called when a document synchronization request is received from a new conference + * member in a chatroom. + * + * \param recipient the recipient JID + */ + void receiveConnectRequestResponseChat(gchar const* recipient); + + // Message parsing and passing + + /** + * Processes a group of document change messages. + * + * \param changemsg The change message group to process. + */ + void receiveChange(Glib::ustring const& changemsg); + + // Logging and session file handling + /** + * Start a session log with the given filename. + * + * \param filename Full path to the file that the session log should be written to. + * \throw Glib::FileError Thrown if an exception is thrown during session file creation. + */ + void startLog(Glib::ustring filename); + + /** + * Load a session file for playback. + * + * \param filename Full path to the session file that is to be loaded. + */ + void loadSessionFile(Glib::ustring filename); + + /** + * Returns whether or not the session is in session file playback mode. + * + * \return Whether or not the session is in session file playback mode. + */ + bool isPlayingSessionFile(); + + // User event notification + + /** + * Method to notify the user that a whiteboard session to another user has been successfully + * established. + * + * \param JID The JID with whom the user established a session. + */ + void userConnectedToWhiteboard(gchar const* JID); + + /** + * Method to notify the user that the other user in a user-to-user whiteboard session + * has disconnected. + * + * \param JID The JID of the user who left the whiteboard session. + */ + void userDisconnectedFromWhiteboard(std::string const& JID); + + // Queue dispatching and UI setup + + /** + * Start the send queue for this session. + */ + void startSendQueueDispatch(); + + /** + * Stop the send queue for this session. + */ + void stopSendQueueDispatch(); + + /** + * Start the receive queue for this session. + */ + void startReceiveQueueDispatch(); + + /** + * Stop the receive queue for this session. + */ + void stopReceiveQueueDispatch(); + + /** + * Clear all layers, definitions, and metadata from the document with which a + * SessionManager instance is associated. + * + * Documents are cleared to assist synchronization between two clients + * or a client and a chatroom. + */ + void clearDocument(); + + /** + * Set up objects for handling actions generated by the user interacting with + * Inkscape. This includes marking the active session as being in a whiteboard session, + * starting send and receive queues, and creating an event serializer and deserializer. + * + * \see Inkscape::Whiteboard::SendMessageQueue + * \see Inkscape::Whiteboard::ReceiveMessageQueue + * \see Inkscape::Whiteboard::Serializer + * \see Inkscape::Whiteboard::Deserializer + */ + void setupInkscapeInterface(); + + /** + * Reset whiteboard verbs to INITIAL state. + */ + void setInitialVerbSensitivity() { + this->_setVerbSensitivity(INITIAL); + } + + /** + * Set up the event commit listener. + * + * The event commit listener watches for events that are committed to the document's undo log, + * serializes those events, and then adds them to the message send queue. + * + * \see Inkscape::Whiteboard::SendMessageQueue + * \see Inkscape::Whiteboard::UndoStackObserver + */ + void setupCommitListener(); + + // Private object retrieval + ::SPDesktop* desktop(); + ::SPDocument* document(); + Callbacks* callbacks(); + Whiteboard::UndoStackObserver* undo_stack_observer(); + Serializer* serializer(); + XMLNodeTracker* node_tracker(); + Deserializer* deserializer(); + ChatMessageHandler* chat_handler(); + SessionFilePlayer* session_player(); + SessionFile* session_file(); + +private: + // Internal logging methods + void _log(Glib::ustring const& message); + void _commitLog(); + void _closeLog(); + void _tryToStartLog(); + + enum SensitivityMode { + INITIAL, + ESTABLISHED_CONNECTION, + ESTABLISHED_SESSION, + DISCONNECTED_FROM_SESSION + }; + + void _setVerbSensitivity(SensitivityMode mode); + + bool _pollReceiveConnectRequest(Glib::ustring const recipient); + + ::SPDesktop* _myDesktop; + ::SPDocument* _myDoc; + Whiteboard::UndoStackObserver* _myUndoObserver; + XMLNodeTracker* _myTracker; + ChatMessageHandler* _myChatHandler; + Callbacks* _myCallbacks; + SessionFile* _mySessionFile; + SessionFilePlayer* _mySessionPlayer; + MessageHandler* _myMessageHandler; + Serializer* _mySerializer; + Deserializer* _myDeserializer; + + sigc::connection _send_queue_dispatcher; + sigc::connection _receive_queue_dispatcher; + sigc::connection _notify_incoming_request; + + // noncopyable, nonassignable + SessionManager(SessionManager const&); + SessionManager& operator=(SessionManager const&); +}; + +} + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/jabber_whiteboard/tracker-node.h b/src/jabber_whiteboard/tracker-node.h new file mode 100644 index 000000000..bbaf527ce --- /dev/null +++ b/src/jabber_whiteboard/tracker-node.h @@ -0,0 +1,94 @@ +/** + * Whiteboard session manager + * XML node tracking facility + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __WHITEBOARD_TRACKER_NODE_H__ +#define __WHITEBOARD_TRACKER_NODE_H__ + +#include "xml/node.h" + +#include "gc-managed.h" +#include "gc-finalized.h" + +#include +#include + +namespace Inkscape { + +namespace Whiteboard { + +// set _size in TrackerNode private members if you add or delete +// any more listeners +enum ListenerType { + ATTR_CHANGED, + CHILD_ADDED, + CHILD_REMOVED, + CHILD_ORDER_CHANGED, + CONTENT_CHANGED +}; + +struct TrackerNode : public GC::Managed<> { +public: + TrackerNode(XML::Node const* n) : _node(n) + { + } + + ~TrackerNode() + { + } + + void lock(ListenerType listener) + { + if (listener < _size) { + this->_listener_locks.set(listener, true); + } + } + + void unlock(ListenerType listener) + { + if (listener < _size) { + this->_listener_locks.set(listener, false); + } + } + + bool isLocked(ListenerType listener) + { + return (this->_listener_locks[listener]); + } + + XML::Node const* _node; + +private: + // change this if any other flags are added + static unsigned short const _size = 5; + std::bitset< _size > _listener_locks; + + // noncopyable, nonassignable + TrackerNode(TrackerNode const&); + TrackerNode& operator=(TrackerNode const&); +}; + +} + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/jabber_whiteboard/typedefs.h b/src/jabber_whiteboard/typedefs.h new file mode 100644 index 000000000..10adbdaf8 --- /dev/null +++ b/src/jabber_whiteboard/typedefs.h @@ -0,0 +1,159 @@ +/** + * Whiteboard session manager + * Typedef declarations and template specializations + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __WHITEBOARD_TYPEDEFS_H__ +#define __WHITEBOARD_TYPEDEFS_H__ + +extern "C" { +#include +} + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "jabber_whiteboard/defines.h" + +#include "gc-alloc.h" + +namespace Inkscape { + +namespace XML { + +class Node; + +} + +namespace Util { + +template< typename T > +class ListContainer; + +} + +} + +// Various specializations of std::less for XMLNodeTracker maps. +namespace std { +using Inkscape::XML::Node; + +/** + * Specialization of std::less<> for pointers to XML::Nodes.a + * + * \see Inkscape::XML::Node + */ +template<> +struct less< Node* > : public binary_function < Node*, Node*, bool > +{ + bool operator()(Node* _x, Node* _y) const + { + return _x < _y; + } + +}; + +} + +namespace Inkscape { + +namespace Whiteboard { +// I am assuming that std::string (which will not properly represent Unicode data) will +// suffice for associating (integer, Jabber ID) identifiers with nodes. +// We do not need to preserve all semantics handled by Unicode; we just need to have +// the byte representation. std::string is good enough for that. +// +// The reason for this is that comparisons with std::string are much faster than +// comparisons with Glib::ustring (simply because the latter is using significantly +// more complex text-handling algorithms), and we need speed here. We _could_ use +// Glib::ustring::collate_key() here and therefore get the best of both worlds, +// but collation keys are rather big. +// +// XML node tracker maps + +/// Associates node keys to pointers to XML::Nodes. +/// \see Inkscape::Whiteboard::XMLNodeTracker +typedef std::map< std::string, XML::Node*, std::less< std::string >, GC::Alloc< std::pair< const std::string, XML::Node* >, GC::MANUAL > > KeyToTrackerNodeMap; + +/// Associates pointers to XML::Nodes with node keys. +/// \see Inkscape::Whiteboard::XMLNodeTracker +typedef std::map< XML::Node*, std::string, std::less< XML::Node* >, GC::Alloc< std::pair< XML::Node* const, std::string >, GC::MANUAL > > TrackerNodeToKeyMap; + + +// TODO: Clean up these typedefs. I'm sure quite a few of these aren't used anymore; additionally, +// it's probably possible to consolidate a few of these types into one. + +// Temporary storage of new object messages and new nodes in said messages +typedef std::list< Glib::ustring > NewChildObjectMessageList; + +typedef std::pair< std::string, XML::Node* > KeyNodePair; +typedef std::pair< KeyNodePair, NodeTrackerAction > SerializedEventNodeAction; + +typedef std::list< SerializedEventNodeAction > KeyToNodeActionList; + +//typedef std::map< std::string, SerializedEventNodeAction > KeyToNodeActionMap; + +typedef std::set< std::string > AttributesScannedSet; +typedef std::set< XML::Node* > AttributesUpdatedSet; + +typedef std::map< std::string, XML::Node const* > KeyToNodeMap; +typedef std::map< XML::Node const*, std::string > NodeToKeyMap; + +// Buddy list management +typedef std::set< std::string > BuddyList; +typedef sigc::signal< void, std::string const& > BuddyListSignal; +typedef sigc::slot< void, std::string const& > BuddyListListener; + +// Chatroom list participants +typedef std::set< char const* > ChatterList; + +// Message context verification and processing +struct MessageProcessor; +class ReceiveMessageQueue; + +typedef std::map< MessageType, std::bitset< NUM_FLAGS > > MessageContextMap; +typedef std::map< MessageType, MessageProcessor*, std::less< MessageType >, GC::Alloc< std::pair< const MessageType, MessageProcessor* >, GC::MANUAL > > MessageProcessorMap; + +typedef std::map< std::string, ReceiveMessageQueue*, std::less< std::string >, GC::Alloc< std::pair< const std::string, ReceiveMessageQueue* >, GC::MANUAL > > RecipientToReceiveQueueMap; +typedef std::map< std::string, unsigned int > ReceipientToLatestTransactionMap; + +typedef std::string ReceivedCommitEvent; +typedef std::list< ReceivedCommitEvent > CommitsQueue; + +// Message serialization +typedef std::list< Glib::ustring > SerializedEventList; + +// Error handling -- someday +// TODO: finish and integrate this +//typedef boost::function< LmHandlerResult (unsigned int code) > ErrorHandlerFunctor; +//typedef std::map< unsigned int, ErrorHandlerFunctor > ErrorHandlerFunctorMap; +} + +} + + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/jabber_whiteboard/undo-stack-observer.cpp b/src/jabber_whiteboard/undo-stack-observer.cpp new file mode 100644 index 000000000..7750dc73c --- /dev/null +++ b/src/jabber_whiteboard/undo-stack-observer.cpp @@ -0,0 +1,181 @@ +/** + * Undo / redo / undo log commit listener + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "document.h" +#include "document-private.h" + +#include "xml/event.h" +#include "xml/event-fns.h" +#include "undo-stack-observer.h" + +#include "util/list.h" +#include "util/reverse-list.h" + +#include "jabber_whiteboard/defines.h" +#include "jabber_whiteboard/session-manager.h" +#include "jabber_whiteboard/node-tracker.h" +#include "jabber_whiteboard/serializer.h" +#include "jabber_whiteboard/message-utilities.h" +#include "jabber_whiteboard/message-aggregator.h" +#include "jabber_whiteboard/message-tags.h" + + +#include + +namespace Inkscape { + +namespace Whiteboard { + +UndoStackObserver::UndoStackObserver(SessionManager* sm) : _sm(sm), _undoSendEventLocks(0), _redoSendEventLocks(0), _undoCommitSendEventLocks(0) { +} + +UndoStackObserver::~UndoStackObserver() { } + +void +UndoStackObserver::notifyUndoEvent(XML::Event* log) +{ + if (this->_undoSendEventLocks == 0) { + bool chatroom = this->_sm->session_data->status.test(IN_CHATROOM); + Glib::ustring commit = MessageUtilities::makeTagWithContent(MESSAGE_UNDO, ""); + this->_sm->sendChange(commit, CHANGE_COMMIT, "", chatroom); + } + + // Retrieve and process added/deleted nodes in the undo log + // TODO: re-enable; right now it doesn't work because we can't recover the names + // of deleted nodes (although perhaps creating a subclass of XML::Event that stored + // names of serialized nodes along with all other Event information would fix this) + /* + KeyToNodeActionMap node_actions = this->_action_observer.getNodeActionMap(); + this->_sm->node_tracker()->process(node_actions); + this->_action_observer.clearNodeBuffers(); + */ + +} + +void +UndoStackObserver::notifyRedoEvent(XML::Event* log) +{ + if (this->_redoSendEventLocks == 0) { + bool chatroom = this->_sm->session_data->status.test(IN_CHATROOM); + Glib::ustring commit = MessageUtilities::makeTagWithContent(MESSAGE_REDO, ""); + this->_sm->sendChange(commit, CHANGE_COMMIT, "", chatroom); + } + + // Retrieve and process added/deleted nodes in the redo log + /* + KeyToNodeActionMap node_actions = this->_action_observer.getNodeActionMap(); + this->_sm->node_tracker()->process(node_actions); + this->_action_observer.clearNodeBuffers(); + */ +} + +void +UndoStackObserver::notifyUndoCommitEvent(XML::Event* log) +{ + if (this->_undoCommitSendEventLocks == 0) { + this->_doAction(log); + } +} + +void +UndoStackObserver::lockObserverFromSending(ObserverType type) +{ + switch (type) { + case UNDO_EVENT: + ++this->_undoSendEventLocks; + break; + case REDO_EVENT: + ++this->_redoSendEventLocks; + break; + case UNDO_COMMIT_EVENT: + ++this->_undoCommitSendEventLocks; + break; + default: + break; + } +} + +void +UndoStackObserver::unlockObserverFromSending(ObserverType type) +{ + switch(type) { + case UNDO_EVENT: + if (this->_undoSendEventLocks) { + --this->_undoSendEventLocks; + } + break; + case REDO_EVENT: + if (this->_redoSendEventLocks) { + --this->_redoSendEventLocks; + } + break; + case UNDO_COMMIT_EVENT: + if (this->_undoCommitSendEventLocks) { + --this->_undoCommitSendEventLocks; + } + break; + default: + break; + } +} + +void +UndoStackObserver::_doAction(XML::Event* log) +{ + if (this->_sm->serializer()) { + bool chatroom = this->_sm->session_data->status.test(IN_CHATROOM); + XML::replay_log_to_observer(log, *this->_sm->serializer()); + + this->_sm->serializer()->synthesizeChildNodeAddEvents(); + + SerializedEventList& events = this->_sm->serializer()->getEventList(); + + SerializedEventList::iterator i = events.begin(); + MessageAggregator& agg = MessageAggregator::instance(); + Glib::ustring msgbuf; + + while(i != events.end()) { + while(agg.addOne(*i++, msgbuf)) { + if (i == events.end()) { + break; + } + } + + if (i != events.end()) { + i--; + } + + this->_sm->sendChange(msgbuf, CHANGE_REPEATABLE, "", chatroom); + msgbuf.clear(); + } + + KeyToNodeActionList& node_actions = this->_sm->serializer()->getNodeTrackerActions(); + this->_sm->node_tracker()->process(node_actions); + this->_sm->serializer()->reset(); + Glib::ustring commit = MessageUtilities::makeTagWithContent(MESSAGE_COMMIT, ""); + this->_sm->sendChange(commit, CHANGE_COMMIT, "", chatroom); + } +} + +} + +} + +/* + 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 : diff --git a/src/jabber_whiteboard/undo-stack-observer.h b/src/jabber_whiteboard/undo-stack-observer.h new file mode 100644 index 000000000..3b3884780 --- /dev/null +++ b/src/jabber_whiteboard/undo-stack-observer.h @@ -0,0 +1,75 @@ +/** + * Undo / redo / undo log commit listener + * + * Authors: + * David Yip + * + * Copyright (c) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __WHITEBOARD_UNDO_COMMIT_OBSERVER_H__ +#define __WHITEBOARD_UNDO_COMMIT_OBSERVER_H__ + +#include +#include "../undo-stack-observer.h" +#include "jabber_whiteboard/typedefs.h" + +namespace Inkscape { + +namespace Whiteboard { + +class SessionManager; + +/** + * Inkboard implementation of Inkscape::UndoStackObserver. + */ +class UndoStackObserver : public Inkscape::UndoStackObserver { +public: + enum ObserverType { + UNDO_EVENT, + REDO_EVENT, + UNDO_COMMIT_EVENT + }; + + UndoStackObserver(SessionManager* sm); + ~UndoStackObserver(); + void notifyUndoEvent(XML::Event* log); + void notifyRedoEvent(XML::Event* log); + void notifyUndoCommitEvent(XML::Event* log); + + void lockObserverFromSending(ObserverType type); + void unlockObserverFromSending(ObserverType type); + +private: + SessionManager* _sm; + + // common action handler + void _doAction(XML::Event* log); + + // noncopyable, nonassignable + UndoStackObserver(UndoStackObserver const& other); + UndoStackObserver& operator=(UndoStackObserver const& other); + + unsigned int _undoSendEventLocks; + unsigned int _redoSendEventLocks; + unsigned int _undoCommitSendEventLocks; +}; + +} + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/knot-enums.h b/src/knot-enums.h new file mode 100644 index 000000000..708d3e19b --- /dev/null +++ b/src/knot-enums.h @@ -0,0 +1,58 @@ +#ifndef SEEN_KNOT_ENUMS_H +#define SEEN_KNOT_ENUMS_H + +/** \file + * Some enums used by SPKnot and by related types \& functions. + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +typedef enum { + SP_KNOT_SHAPE_SQUARE, + SP_KNOT_SHAPE_DIAMOND, + SP_KNOT_SHAPE_CIRCLE, + SP_KNOT_SHAPE_CROSS, + SP_KNOT_SHAPE_BITMAP, + SP_KNOT_SHAPE_IMAGE +} SPKnotShapeType; + +typedef enum { + SP_KNOT_MODE_COLOR, + SP_KNOT_MODE_XOR +} SPKnotModeType; + +typedef enum { + SP_KNOT_STATE_NORMAL, + SP_KNOT_STATE_MOUSEOVER, + SP_KNOT_STATE_DRAGGING, + SP_KNOT_STATE_HIDDEN +} SPKnotStateType; + +#define SP_KNOT_VISIBLE_STATES 3 + +enum { + SP_KNOT_VISIBLE = 1 << 0, + SP_KNOT_MOUSEOVER = 1 << 1, + SP_KNOT_DRAGGING = 1 << 2, + SP_KNOT_GRABBED = 1 << 3 +}; + + +#endif /* !SEEN_KNOT_ENUMS_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:encoding=utf-8:textwidth=99 : diff --git a/src/knot-holder-entity.h b/src/knot-holder-entity.h new file mode 100644 index 000000000..1e9001e67 --- /dev/null +++ b/src/knot-holder-entity.h @@ -0,0 +1,62 @@ +#ifndef SEEN_KNOT_HOLDER_ENTITY_H +#define SEEN_KNOT_HOLDER_ENTITY_H + +/** \file + * SPKnotHolderEntity definition. + * + * Authors: + * Mitsuru Oka + * + * Copyright (C) 1999-2001 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2001 Mitsuru Oka + * Copyright (C) 2004 Monash University + * + * Released under GNU GPL + */ + +#include + +struct SPItem; +struct SPKnot; +namespace NR { +class Point; +} + +/// SPKnotHolderEntity definition. +struct SPKnotHolderEntity { + SPKnot *knot; + + /** Connection to \a knot's "moved" signal. */ + guint handler_id; + + /** + * Called solely from knot_moved_handler. + * + * \param p Requested position of the knot, in item coordinates + * \param origin Position where the knot started being dragged + * \param state GTK event state (for keyboard modifiers) + */ + void (* knot_set) (SPItem *item, NR::Point const &p, NR::Point const &origin, guint state); + + /** + * Returns the position of the knot representation, in item coordinates. + */ + NR::Point (* knot_get) (SPItem *item); + + void (* knot_click) (SPItem *item, guint state); +}; + + +#endif /* !SEEN_KNOT_HOLDER_ENTITY_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:encoding=utf-8:textwidth=99 : diff --git a/src/knot.cpp b/src/knot.cpp new file mode 100644 index 000000000..af567e6e4 --- /dev/null +++ b/src/knot.cpp @@ -0,0 +1,926 @@ +#define __SP_KNOT_C__ + +/** \file + * SPKnot implementation + * + * Authors: + * Lauris Kaplinski + * bulia byak + * + * Copyright (C) 1999-2005 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include +#endif +#include +#include +#include "helper/sp-marshal.h" +#include "display/sodipodi-ctrl.h" +#include "desktop.h" +#include "desktop-handles.h" +#include "knot.h" +#include "document.h" +#include "prefs-utils.h" +#include "message-stack.h" +#include "message-context.h" +#include "event-context.h" + + +#define KNOT_EVENT_MASK (GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | \ + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | \ + GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK) + +static bool nograb = false; + +static bool grabbed = FALSE; +static bool moved = FALSE; + +static gint xp = 0, yp = 0; // where drag started +static gint tolerance = 0; +static bool within_tolerance = false; + +static bool transform_escaped = false; // true iff resize or rotate was cancelled by esc. + +enum { + PROP_0, + + PROP_SIZE, + PROP_ANCHOR, + PROP_SHAPE, + PROP_MODE, + PROP_FILL, PROP_FILL_MOUSEOVER, PROP_FILL_DRAGGING, + PROP_STROKE, PROP_STROKE_MOUSEOVER, PROP_STROKE_DRAGGING, + PROP_IMAGE, PROP_IMAGE_MOUSEOVER, PROP_IMAGE_DRAGGING, + PROP_CURSOR, PROP_CURSOR_MOUSEOVER, PROP_CURSOR_DRAGGING, + PROP_PIXBUF, + PROP_TIP, + + PROP_LAST +}; + +enum { + EVENT, + CLICKED, + DOUBLECLICKED, + GRABBED, + UNGRABBED, + MOVED, + REQUEST, + DISTANCE, + LAST_SIGNAL +}; + +static void sp_knot_class_init(SPKnotClass *klass); +static void sp_knot_init(SPKnot *knot); +static void sp_knot_dispose(GObject *object); +static void sp_knot_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); +static void sp_knot_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); + +static int sp_knot_handler(SPCanvasItem *item, GdkEvent *event, SPKnot *knot); +static void sp_knot_set_flag(SPKnot *knot, guint flag, bool set); +static void sp_knot_update_ctrl(SPKnot *knot); +static void sp_knot_set_ctrl_state(SPKnot *knot); + +static GObjectClass *parent_class; +static guint knot_signals[LAST_SIGNAL] = { 0 }; + +/** + * Registers SPKnot class and returns its type number. + */ +GType sp_knot_get_type() +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPKnotClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_knot_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (SPKnot), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_knot_init, + NULL + }; + type = g_type_register_static (G_TYPE_OBJECT, "SPKnot", &info, (GTypeFlags) 0); + } + return type; +} + +/** + * SPKnot vtable initialization. + */ +static void sp_knot_class_init(SPKnotClass *klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + + parent_class = (GObjectClass*) g_type_class_peek_parent(klass); + + object_class->dispose = sp_knot_dispose; + object_class->set_property = sp_knot_set_property; + object_class->get_property = sp_knot_get_property; + + /* Huh :) */ + + g_object_class_install_property(object_class, + PROP_SIZE, + g_param_spec_uint("size", "Size", "", + 0, + 0xffffffff, + 0xff000000, + (GParamFlags) G_PARAM_READWRITE)); + g_object_class_install_property(object_class, + PROP_ANCHOR, + g_param_spec_enum("anchor", "Anchor", "", + GTK_TYPE_ANCHOR_TYPE, + GTK_ANCHOR_CENTER, + (GParamFlags) G_PARAM_READWRITE)); + g_object_class_install_property(object_class, + PROP_SHAPE, + g_param_spec_int("shape", "Shape", "", + SP_KNOT_SHAPE_SQUARE, + SP_KNOT_SHAPE_IMAGE, + SP_KNOT_SHAPE_SQUARE, + (GParamFlags) G_PARAM_READWRITE)); + + g_object_class_install_property(object_class, + PROP_MODE, + g_param_spec_int("mode", "Mode", "", + SP_KNOT_MODE_COLOR, + SP_KNOT_MODE_XOR, + SP_KNOT_MODE_XOR, + (GParamFlags) G_PARAM_READWRITE)); + + g_object_class_install_property(object_class, + PROP_FILL, + g_param_spec_uint("fill", "Fill", "", + 0, + 0xffffffff, + 0xff000000, + (GParamFlags) G_PARAM_READWRITE)); + + g_object_class_install_property(object_class, + PROP_FILL_MOUSEOVER, + g_param_spec_uint("fill_mouseover", "Fill mouse over", "", + 0, + 0xffffffff, + 0xff000000, + (GParamFlags) G_PARAM_READWRITE)); + + g_object_class_install_property(object_class, + PROP_FILL_DRAGGING, + g_param_spec_uint("fill_dragging", "Fill dragging", "", + 0, + 0xffffffff, + 0xff000000, + (GParamFlags) G_PARAM_READWRITE)); + + g_object_class_install_property(object_class, + PROP_STROKE, + g_param_spec_uint("stroke", "Stroke", "", + 0, + 0xffffffff, + 0x01000000, + (GParamFlags) G_PARAM_READWRITE)); + + g_object_class_install_property(object_class, + PROP_STROKE_MOUSEOVER, + g_param_spec_uint("stroke_mouseover", "Stroke mouseover", "", + 0, + 0xffffffff, + 0x01000000, + (GParamFlags) G_PARAM_READWRITE)); + + g_object_class_install_property(object_class, + PROP_STROKE_DRAGGING, + g_param_spec_uint("stroke_dragging", "Stroke dragging", "", + 0, + 0xffffffff, + 0x01000000, + (GParamFlags) G_PARAM_READWRITE)); + + g_object_class_install_property(object_class, + PROP_IMAGE, + g_param_spec_pointer("image", "Image", "", + (GParamFlags) G_PARAM_READWRITE)); + + g_object_class_install_property(object_class, + PROP_IMAGE_MOUSEOVER, + g_param_spec_pointer("image_mouseover", "Image mouseover", "", + (GParamFlags) G_PARAM_READWRITE)); + + g_object_class_install_property(object_class, + PROP_IMAGE_DRAGGING, + g_param_spec_pointer("image_dragging", "Image dragging", "", + (GParamFlags) G_PARAM_READWRITE)); + + g_object_class_install_property(object_class, + PROP_CURSOR, + g_param_spec_boxed("cursor", "Cursor", "", + GDK_TYPE_CURSOR, + (GParamFlags) G_PARAM_READWRITE)); + + g_object_class_install_property(object_class, + PROP_CURSOR_MOUSEOVER, + g_param_spec_boxed("cursor_mouseover", "Cursor mouseover", "", + GDK_TYPE_CURSOR, + (GParamFlags) G_PARAM_READWRITE)); + + g_object_class_install_property(object_class, + PROP_CURSOR_DRAGGING, + g_param_spec_boxed("cursor_dragging", "Cursor dragging", "", + GDK_TYPE_CURSOR, + (GParamFlags) G_PARAM_READWRITE)); + + g_object_class_install_property(object_class, + PROP_PIXBUF, + g_param_spec_pointer("pixbuf", "Pixbuf", "", + (GParamFlags) G_PARAM_READWRITE)); + + g_object_class_install_property(object_class, + PROP_TIP, + g_param_spec_pointer("tip", "Tip", "", + (GParamFlags) G_PARAM_READWRITE)); + + knot_signals[EVENT] = g_signal_new("event", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(SPKnotClass, event), + NULL, NULL, + sp_marshal_BOOLEAN__POINTER, + G_TYPE_BOOLEAN, 1, + GDK_TYPE_EVENT); + + knot_signals[CLICKED] = g_signal_new("clicked", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET(SPKnotClass, clicked), + NULL, NULL, + sp_marshal_NONE__UINT, + G_TYPE_NONE, 1, + G_TYPE_UINT); + + knot_signals[DOUBLECLICKED] = g_signal_new("doubleclicked", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET(SPKnotClass, doubleclicked), + NULL, NULL, + sp_marshal_NONE__UINT, + G_TYPE_NONE, 1, + G_TYPE_UINT); + + knot_signals[GRABBED] = g_signal_new("grabbed", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET(SPKnotClass, grabbed), + NULL, NULL, + sp_marshal_NONE__UINT, + G_TYPE_NONE, 1, + G_TYPE_UINT); + + knot_signals[UNGRABBED] = g_signal_new("ungrabbed", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET(SPKnotClass, ungrabbed), + NULL, NULL, + sp_marshal_NONE__UINT, + G_TYPE_NONE, 1, + G_TYPE_UINT); + + knot_signals[MOVED] = g_signal_new("moved", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET(SPKnotClass, moved), + NULL, NULL, + sp_marshal_NONE__POINTER_UINT, + G_TYPE_NONE, 2, + G_TYPE_POINTER, G_TYPE_UINT); + + knot_signals[REQUEST] = g_signal_new("request", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(SPKnotClass, request), + NULL, NULL, + sp_marshal_BOOLEAN__POINTER_UINT, + G_TYPE_BOOLEAN, 2, + G_TYPE_POINTER, G_TYPE_UINT); + + knot_signals[DISTANCE] = g_signal_new("distance", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(SPKnotClass, distance), + NULL, NULL, + sp_marshal_DOUBLE__POINTER_UINT, + G_TYPE_DOUBLE, 2, + G_TYPE_POINTER, G_TYPE_UINT); + + const gchar *nograbenv = getenv("INKSCAPE_NO_GRAB"); + nograb = (nograbenv && *nograbenv && (*nograbenv != '0')); +} + +/** + * Callback for SPKnot initialization. + */ +static void sp_knot_init(SPKnot *knot) +{ + knot->desktop = NULL; + knot->item = NULL; + knot->flags = 0; + + knot->size = 8; + knot->pos = NR::Point(0, 0); + knot->grabbed_rel_pos = NR::Point(0, 0); + knot->anchor = GTK_ANCHOR_CENTER; + knot->shape = SP_KNOT_SHAPE_SQUARE; + knot->mode = SP_KNOT_MODE_XOR; + knot->tip = NULL; + + knot->fill[SP_KNOT_STATE_NORMAL] = 0xffffff00; + knot->fill[SP_KNOT_STATE_MOUSEOVER] = 0xff0000ff; + knot->fill[SP_KNOT_STATE_DRAGGING] = 0x0000ffff; + + knot->stroke[SP_KNOT_STATE_NORMAL] = 0x01000000; + knot->stroke[SP_KNOT_STATE_MOUSEOVER] = 0x01000000; + knot->stroke[SP_KNOT_STATE_DRAGGING] = 0x01000000; + + knot->image[SP_KNOT_STATE_NORMAL] = NULL; + knot->image[SP_KNOT_STATE_MOUSEOVER] = NULL; + knot->image[SP_KNOT_STATE_DRAGGING] = NULL; + + knot->cursor[SP_KNOT_STATE_NORMAL] = NULL; + knot->cursor[SP_KNOT_STATE_MOUSEOVER] = NULL; + knot->cursor[SP_KNOT_STATE_DRAGGING] = NULL; + + knot->saved_cursor = NULL; + knot->pixbuf = NULL; +} + +/** + * Called before SPKnot destruction. + */ +static void sp_knot_dispose(GObject *object) +{ + SPKnot *knot = (SPKnot *) object; + + if ((knot->flags & SP_KNOT_GRABBED) && gdk_pointer_is_grabbed ()) { + // This happens e.g. when deleting a node in node tool while dragging it + gdk_pointer_ungrab (GDK_CURRENT_TIME); + } + + if (knot->item) { + gtk_object_destroy (GTK_OBJECT (knot->item)); + knot->item = NULL; + } + + for (gint i = 0; i < SP_KNOT_VISIBLE_STATES; i++) { + if (knot->cursor[i]) { + gdk_cursor_unref(knot->cursor[i]); + knot->cursor[i] = NULL; + } + } + + if (knot->tip) { + g_free(knot->tip); + knot->tip = NULL; + } + + if (((GObjectClass *) (parent_class))->dispose) { + (* ((GObjectClass *) (parent_class))->dispose) (object); + } +} + +/** + * Callback to set property. + */ +static void sp_knot_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + GdkCursor *cursor; + + SPKnot *knot = SP_KNOT(object); + + switch (prop_id) { + case PROP_SIZE: + knot->size = g_value_get_uint(value); + break; + case PROP_ANCHOR: + knot->anchor = (GtkAnchorType) g_value_get_enum(value); + break; + case PROP_SHAPE: + knot->shape = (SPKnotShapeType) g_value_get_int(value); + break; + case PROP_MODE: + knot->mode = (SPKnotModeType) g_value_get_int(value); + break; + case PROP_FILL: + knot->fill[SP_KNOT_STATE_NORMAL] = + knot->fill[SP_KNOT_STATE_MOUSEOVER] = + knot->fill[SP_KNOT_STATE_DRAGGING] = g_value_get_uint(value); + break; + case PROP_FILL_MOUSEOVER: + knot->fill[SP_KNOT_STATE_MOUSEOVER] = + knot->fill[SP_KNOT_STATE_DRAGGING] = g_value_get_uint(value); + break; + case PROP_FILL_DRAGGING: + knot->fill[SP_KNOT_STATE_DRAGGING] = g_value_get_uint(value); + break; + case PROP_STROKE: + knot->stroke[SP_KNOT_STATE_NORMAL] = + knot->stroke[SP_KNOT_STATE_MOUSEOVER] = + knot->stroke[SP_KNOT_STATE_DRAGGING] = g_value_get_uint(value); + break; + case PROP_STROKE_MOUSEOVER: + knot->stroke[SP_KNOT_STATE_MOUSEOVER] = + knot->stroke[SP_KNOT_STATE_DRAGGING] = g_value_get_uint(value); + break; + case PROP_STROKE_DRAGGING: + knot->stroke[SP_KNOT_STATE_DRAGGING] = g_value_get_uint(value); + break; + case PROP_IMAGE: + knot->image[SP_KNOT_STATE_NORMAL] = + knot->image[SP_KNOT_STATE_MOUSEOVER] = + knot->image[SP_KNOT_STATE_DRAGGING] = (guchar*) g_value_get_pointer(value); + break; + case PROP_IMAGE_MOUSEOVER: + knot->image[SP_KNOT_STATE_MOUSEOVER] = (guchar*) g_value_get_pointer(value); + break; + case PROP_IMAGE_DRAGGING: + knot->image[SP_KNOT_STATE_DRAGGING] = (guchar*) g_value_get_pointer(value); + break; + case PROP_CURSOR: + cursor = (GdkCursor*) g_value_get_boxed(value); + for (gint i = 0; i < SP_KNOT_VISIBLE_STATES; i++) { + if (knot->cursor[i]) { + gdk_cursor_unref(knot->cursor[i]); + } + knot->cursor[i] = cursor; + if (cursor) { + gdk_cursor_ref(cursor); + } + } + break; + case PROP_CURSOR_MOUSEOVER: + cursor = (GdkCursor*) g_value_get_boxed(value); + if (knot->cursor[SP_KNOT_STATE_MOUSEOVER]) { + gdk_cursor_unref(knot->cursor[SP_KNOT_STATE_MOUSEOVER]); + } + knot->cursor[SP_KNOT_STATE_MOUSEOVER] = cursor; + if (cursor) { + gdk_cursor_ref(cursor); + } + break; + case PROP_CURSOR_DRAGGING: + cursor = (GdkCursor*) g_value_get_boxed(value); + if (knot->cursor[SP_KNOT_STATE_DRAGGING]) { + gdk_cursor_unref(knot->cursor[SP_KNOT_STATE_DRAGGING]); + } + knot->cursor[SP_KNOT_STATE_DRAGGING] = cursor; + if (cursor) { + gdk_cursor_ref(cursor); + } + break; + case PROP_PIXBUF: + knot->pixbuf = g_value_get_pointer(value); + break; + case PROP_TIP: + knot->tip = g_strdup((const gchar *) g_value_get_pointer(value)); + break; + default: + g_assert_not_reached(); + break; + } + + sp_knot_update_ctrl(knot); +} + +/// Not reached. +static void sp_knot_get_property(GObject *, guint, GValue *, GParamSpec *) +{ + g_assert_not_reached(); +} + +/** + * Update knot for dragging and tell canvas an item was grabbed. + */ +void sp_knot_start_dragging(SPKnot *knot, NR::Point p, gint x, gint y, guint32 etime) +{ + // save drag origin + xp = x; + yp = y; + within_tolerance = true; + + knot->grabbed_rel_pos = p - knot->pos; + knot->drag_origin = knot->pos; + if (!nograb) { + sp_canvas_item_grab(knot->item, + KNOT_EVENT_MASK, + knot->cursor[SP_KNOT_STATE_DRAGGING], + etime); + } + sp_knot_set_flag(knot, SP_KNOT_GRABBED, TRUE); + grabbed = TRUE; +} + +/** + * Called to handle events on knots. + */ +static int sp_knot_handler(SPCanvasItem *item, GdkEvent *event, SPKnot *knot) +{ + g_assert(knot != NULL); + g_assert(SP_IS_KNOT(knot)); + + tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100); + + gboolean consumed = FALSE; + + /* Run client universal event handler, if present */ + + g_signal_emit(G_OBJECT(knot), knot_signals[EVENT], 0, event, &consumed); + + if (consumed) { + return TRUE; + } + + switch (event->type) { + case GDK_2BUTTON_PRESS: + if (event->button.button == 1) { + g_signal_emit(G_OBJECT(knot), knot_signals[DOUBLECLICKED], 0, event->button.state); + + grabbed = FALSE; + moved = FALSE; + consumed = TRUE; + } + break; + case GDK_BUTTON_PRESS: + if (event->button.button == 1) { + NR::Point const p = knot->desktop->w2d(NR::Point(event->button.x, event->button.y)); + sp_knot_start_dragging(knot, p, (gint) event->button.x, (gint) event->button.y, event->button.time); + consumed = TRUE; + } + break; + case GDK_BUTTON_RELEASE: + if (event->button.button == 1) { + if (transform_escaped) { + transform_escaped = false; + consumed = TRUE; + } else { + sp_knot_set_flag(knot, SP_KNOT_GRABBED, FALSE); + if (!nograb) { + sp_canvas_item_ungrab(knot->item, event->button.time); + } + if (moved) { + sp_knot_set_flag(knot, + SP_KNOT_DRAGGING, + FALSE); + g_signal_emit(G_OBJECT (knot), + knot_signals[UNGRABBED], 0, + event->button.state); + } else { + g_signal_emit(G_OBJECT (knot), + knot_signals[CLICKED], 0, + event->button.state); + } + grabbed = FALSE; + moved = FALSE; + consumed = TRUE; + } + } + break; + case GDK_MOTION_NOTIFY: + if (grabbed) { + consumed = TRUE; + + if ( within_tolerance + && ( abs( (gint) event->motion.x - xp ) < tolerance ) + && ( abs( (gint) event->motion.y - yp ) < tolerance ) ) { + break; // do not drag if we're within tolerance from origin + } + + // Once the user has moved farther than tolerance from the original location + // (indicating they intend to move the object, not click), then always process the + // motion notify coordinates as given (no snapping back to origin) + within_tolerance = false; + + if (!moved) { + g_signal_emit(G_OBJECT (knot), + knot_signals[GRABBED], 0, + event->motion.state); + sp_knot_set_flag(knot, + SP_KNOT_DRAGGING, + TRUE); + } + NR::Point const motion_w(event->motion.x, event->motion.y); + NR::Point const motion_dt = knot->desktop->w2d(motion_w); + NR::Point p = motion_dt - knot->grabbed_rel_pos; + sp_knot_request_position (knot, &p, event->motion.state); + knot->desktop->scroll_to_point (&motion_dt); + moved = TRUE; + } + break; + case GDK_ENTER_NOTIFY: + sp_knot_set_flag(knot, SP_KNOT_MOUSEOVER, TRUE); + sp_knot_set_flag(knot, SP_KNOT_GRABBED, FALSE); + + if (knot->tip) { + knot->desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, knot->tip); + } + + grabbed = FALSE; + moved = FALSE; + consumed = TRUE; + break; + case GDK_LEAVE_NOTIFY: + sp_knot_set_flag(knot, SP_KNOT_MOUSEOVER, FALSE); + sp_knot_set_flag(knot, SP_KNOT_GRABBED, FALSE); + + if (knot->tip) { + knot->desktop->event_context->defaultMessageContext()->clear(); + } + + grabbed = FALSE; + moved = FALSE; + + consumed = TRUE; + break; + case GDK_KEY_PRESS: // keybindings for knot + switch (get_group0_keyval(&event->key)) { + case GDK_Escape: + sp_knot_set_flag(knot, SP_KNOT_GRABBED, FALSE); + if (!nograb) { + sp_canvas_item_ungrab(knot->item, event->button.time); + } + if (moved) { + sp_knot_set_flag(knot, + SP_KNOT_DRAGGING, + FALSE); + g_signal_emit(G_OBJECT(knot), + knot_signals[UNGRABBED], 0, + event->button.state); + sp_document_undo(SP_DT_DOCUMENT(knot->desktop)); + knot->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Node or handle drag canceled.")); + transform_escaped = true; + consumed = TRUE; + } + grabbed = FALSE; + moved = FALSE; + break; + default: + consumed = FALSE; + break; + } + break; + default: + break; + } + + + return consumed; +} + +/** + * Return new knot object. + */ +SPKnot *sp_knot_new(SPDesktop *desktop, const gchar *tip) +{ + g_return_val_if_fail(desktop != NULL, NULL); + + SPKnot * knot = (SPKnot*) g_object_new(SP_TYPE_KNOT, 0); + + knot->desktop = desktop; + knot->flags = SP_KNOT_VISIBLE; + if (tip) { + knot->tip = g_strdup (tip); + } + + knot->item = sp_canvas_item_new(SP_DT_CONTROLS (desktop), + SP_TYPE_CTRL, + "anchor", GTK_ANCHOR_CENTER, + "size", 8.0, + "filled", TRUE, + "fill_color", 0xffffff00, + "stroked", TRUE, + "stroke_color", 0x01000000, + "mode", SP_KNOT_MODE_XOR, + NULL); + + gtk_signal_connect(GTK_OBJECT(knot->item), "event", + GTK_SIGNAL_FUNC(sp_knot_handler), knot); + + return knot; +} + +/** + * Show knot on its canvas. + */ +void sp_knot_show(SPKnot *knot) +{ + g_return_if_fail(knot != NULL); + g_return_if_fail(SP_IS_KNOT (knot)); + + sp_knot_set_flag(knot, SP_KNOT_VISIBLE, TRUE); +} + +/** + * Hide knot on its canvas. + */ +void sp_knot_hide(SPKnot *knot) +{ + g_return_if_fail(knot != NULL); + g_return_if_fail(SP_IS_KNOT(knot)); + + sp_knot_set_flag(knot, SP_KNOT_VISIBLE, FALSE); +} + +/** + * Request or set new position for knot. + */ +void sp_knot_request_position(SPKnot *knot, NR::Point *p, guint state) +{ + g_return_if_fail(knot != NULL); + g_return_if_fail(SP_IS_KNOT(knot)); + + gboolean done = FALSE; + + g_signal_emit(G_OBJECT (knot), + knot_signals[REQUEST], 0, + p, + state, + &done); + + /* If user did not complete, we simply move knot to new position */ + + if (!done) { + sp_knot_set_position (knot, p, state); + } +} + +/** + * Return distance of point to knot's position; unused. + */ +gdouble sp_knot_distance(SPKnot * knot, NR::Point *p, guint state) +{ + g_return_val_if_fail(knot != NULL, 1e18); + g_return_val_if_fail(SP_IS_KNOT(knot), 1e18); + + gdouble distance = NR::L2(*p - knot->pos); + + g_signal_emit(G_OBJECT(knot), + knot_signals[DISTANCE], 0, + p, + state, + &distance); + + return distance; +} + +/** + * Move knot to new position. + */ +void sp_knot_set_position(SPKnot *knot, NR::Point *p, guint state) +{ + g_return_if_fail(knot != NULL); + g_return_if_fail(SP_IS_KNOT (knot)); + + knot->pos = *p; + + if (knot->item) { + SP_CTRL(knot->item)->moveto (*p); + } + + g_signal_emit(G_OBJECT (knot), + knot_signals[MOVED], 0, + p, + state); +} + +/** + * Move knot to new position, without emitting a MOVED signal. + */ +void sp_knot_moveto(SPKnot *knot, NR::Point *p) +{ + g_return_if_fail(knot != NULL); + g_return_if_fail(SP_IS_KNOT(knot)); + + knot->pos = *p; + + if (knot->item) { + SP_CTRL(knot->item)->moveto (*p); + } +} + +/** + * Returns position of knot. + */ +NR::Point sp_knot_position(SPKnot const *knot) +{ + g_assert(knot != NULL); + g_assert(SP_IS_KNOT (knot)); + + return knot->pos; +} + +/** + * Set flag in knot, with side effects. + */ +static void sp_knot_set_flag(SPKnot *knot, guint flag, bool set) +{ + g_assert(knot != NULL); + g_assert(SP_IS_KNOT(knot)); + + if (set) { + knot->flags |= flag; + } else { + knot->flags &= ~flag; + } + + switch (flag) { + case SP_KNOT_VISIBLE: + if (set) { + sp_canvas_item_show(knot->item); + } else { + sp_canvas_item_hide(knot->item); + } + break; + case SP_KNOT_MOUSEOVER: + case SP_KNOT_DRAGGING: + sp_knot_set_ctrl_state(knot); + break; + case SP_KNOT_GRABBED: + break; + default: + g_assert_not_reached(); + break; + } +} + +/** + * Update knot's pixbuf and set its control state. + */ +static void sp_knot_update_ctrl(SPKnot *knot) +{ + if (!knot->item) { + return; + } + + gtk_object_set(GTK_OBJECT(knot->item), "shape", knot->shape, NULL); + gtk_object_set(GTK_OBJECT(knot->item), "mode", knot->mode, NULL); + gtk_object_set(GTK_OBJECT(knot->item), "size", (gdouble) knot->size, NULL); + gtk_object_set(GTK_OBJECT(knot->item), "anchor", knot->anchor, NULL); + if (knot->pixbuf) { + gtk_object_set(GTK_OBJECT (knot->item), "pixbuf", knot->pixbuf, NULL); + } + + sp_knot_set_ctrl_state(knot); +} + +/** + * Set knot control state (dragging/mouseover/normal). + */ +static void sp_knot_set_ctrl_state(SPKnot *knot) +{ + if (knot->flags & SP_KNOT_DRAGGING) { + gtk_object_set(GTK_OBJECT (knot->item), + "fill_color", + knot->fill[SP_KNOT_STATE_DRAGGING], + NULL); + gtk_object_set(GTK_OBJECT (knot->item), + "stroke_color", + knot->stroke[SP_KNOT_STATE_DRAGGING], + NULL); + } else if (knot->flags & SP_KNOT_MOUSEOVER) { + gtk_object_set(GTK_OBJECT(knot->item), + "fill_color", + knot->fill[SP_KNOT_STATE_MOUSEOVER], + NULL); + gtk_object_set(GTK_OBJECT(knot->item), + "stroke_color", + knot->stroke[SP_KNOT_STATE_MOUSEOVER], + NULL); + } else { + gtk_object_set(GTK_OBJECT(knot->item), + "fill_color", + knot->fill[SP_KNOT_STATE_NORMAL], + NULL); + gtk_object_set(GTK_OBJECT(knot->item), + "stroke_color", + knot->stroke[SP_KNOT_STATE_NORMAL], + NULL); + } +} + + + +/* + 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/knot.h b/src/knot.h new file mode 100644 index 000000000..02c0f19ad --- /dev/null +++ b/src/knot.h @@ -0,0 +1,127 @@ +#ifndef __SP_KNOT_H__ +#define __SP_KNOT_H__ + +/** \file + * Declarations for SPKnot: Desktop-bound visual control object. + */ +/* + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#include "display/display-forward.h" +#include "forward.h" +#include +#include "knot-enums.h" + +class SPKnot; +class SPKnotClass; + +#define SP_TYPE_KNOT (sp_knot_get_type()) +#define SP_KNOT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SP_TYPE_KNOT, SPKnot)) +#define SP_KNOT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SP_TYPE_KNOT, SPKnotClass)) +#define SP_IS_KNOT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SP_TYPE_KNOT)) +#define SP_IS_KNOT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SP_TYPE_KNOT)) + +/** + * Desktop-bound visual control object. + * + * A knot is a draggable object, with callbacks to change something by + * dragging it, visuably represented by a canvas item (mostly square). + */ +struct SPKnot { + GObject object; + SPDesktop *desktop; /**< Desktop we are on. */ + SPCanvasItem *item; /**< Our CanvasItem. */ + guint flags; + + guint size; /**< Always square. */ + NR::Point pos; /**< Our desktop coordinates. */ + NR::Point grabbed_rel_pos; /**< Grabbed relative position. */ + NR::Point drag_origin; /**< Origin of drag. */ + GtkAnchorType anchor; /**< Anchor. */ + + SPKnotShapeType shape; /**< Shape type. */ + SPKnotModeType mode; + + guint32 fill[SP_KNOT_VISIBLE_STATES]; + guint32 stroke[SP_KNOT_VISIBLE_STATES]; + guchar *image[SP_KNOT_VISIBLE_STATES]; + + GdkCursor *cursor[SP_KNOT_VISIBLE_STATES]; + + GdkCursor *saved_cursor; + gpointer pixbuf; + + gchar *tip; +}; + +/// The SPKnot vtable. +struct SPKnotClass { + GObjectClass parent_class; + + gint (* event) (SPKnot *knot, GdkEvent *event); + + /* + * These are unconditional. + */ + + void (* clicked) (SPKnot *knot, guint state); + void (* doubleclicked) (SPKnot *knot, guint state); + void (* grabbed) (SPKnot *knot, guint state); + void (* ungrabbed) (SPKnot *knot, guint state); + void (* moved) (SPKnot *knot, NR::Point *position, guint state); + void (* stamped) (SPKnot *know, guint state); + + /** Request knot to move to absolute position. */ + bool (* request) (SPKnot *knot, NR::Point *pos, guint state); + + /** Find complex distance from knot to point. */ + gdouble (* distance) (SPKnot *knot, NR::Point *pos, guint state); +}; + +GType sp_knot_get_type(); + +SPKnot *sp_knot_new(SPDesktop *desktop, gchar const *tip = NULL); + +#define SP_KNOT_IS_VISIBLE(k) ((k->flags & SP_KNOT_VISIBLE) != 0) +#define SP_KNOT_IS_MOSEOVER(k) ((k->flags & SP_KNOT_MOUSEOVER) != 0) +#define SP_KNOT_IS_DRAGGING(k) ((k->flags & SP_KNOT_DRAGGING) != 0) +#define SP_KNOT_IS_GRABBED(k) ((k->flags & SP_KNOT_GRABBED) != 0) + +void sp_knot_show(SPKnot *knot); +void sp_knot_hide(SPKnot *knot); + +void sp_knot_request_position(SPKnot *knot, NR::Point *pos, guint state); +gdouble sp_knot_distance(SPKnot *knot, NR::Point *p, guint state); + +void sp_knot_start_dragging(SPKnot *knot, NR::Point p, gint x, gint y, guint32 etime); + +/** Moves knot and emits "moved" signal. */ +void sp_knot_set_position(SPKnot *knot, NR::Point *p, guint state); + +/** Moves knot without any signal. */ +void sp_knot_moveto(SPKnot *knot, NR::Point *p); + +NR::Point sp_knot_position(SPKnot const *knot); + + +#endif /* !__SP_KNOT_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:encoding=utf-8:textwidth=99 : diff --git a/src/knotholder.cpp b/src/knotholder.cpp new file mode 100644 index 000000000..70a234260 --- /dev/null +++ b/src/knotholder.cpp @@ -0,0 +1,229 @@ +#define __KNOT_HOLDER_C__ + +/* + * Container for SPKnot visual handles + * + * Authors: + * Mitsuru Oka + * bulia byak + * + * Copyright (C) 2001-2005 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define noKNOT_HOLDER_DEBUG + + +#include "document.h" +#include "sp-shape.h" +#include "knot.h" +#include "knotholder.h" +#include "knot-holder-entity.h" +#include + +class SPDesktop; + +static void knot_clicked_handler (SPKnot *knot, guint state, gpointer data); +static void knot_moved_handler(SPKnot *knot, NR::Point const *p, guint state, gpointer data); +static void knot_ungrabbed_handler (SPKnot *knot, unsigned int state, SPKnotHolder *kh); + +#ifdef KNOT_HOLDER_DEBUG + +static void sp_knot_holder_debug(GtkObject *object, gpointer data) +{ + g_print("sp-knot-holder-debug: [type=%s] [data=%s]\n", gtk_type_name(GTK_OBJECT_TYPE(object)), (const gchar *) data); +} +#endif + +SPKnotHolder *sp_knot_holder_new(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) +{ + Inkscape::XML::Node *repr = SP_OBJECT(item)->repr; + + g_return_val_if_fail(desktop != NULL, NULL); + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(SP_IS_ITEM(item), NULL); + + SPKnotHolder *knot_holder = g_new(SPKnotHolder, 1); + knot_holder->desktop = desktop; + knot_holder->item = item; + g_object_ref(G_OBJECT(item)); + knot_holder->entity = NULL; + + knot_holder->released = relhandler; + + knot_holder->repr = repr; + knot_holder->local_change = FALSE; + +#ifdef KNOT_HOLDER_DEBUG + g_signal_connect(G_OBJECT(desktop), "destroy", sp_knot_holder_debug, (gpointer) "SPKnotHolder::item"); +#endif + + return knot_holder; +} + +void sp_knot_holder_destroy(SPKnotHolder *kh) +{ + if (kh) { + g_object_unref(G_OBJECT(kh->item)); + while (kh->entity) { + SPKnotHolderEntity *e = (SPKnotHolderEntity *) kh->entity->data; + /* unref should call destroy */ + g_object_unref(G_OBJECT(e->knot)); + g_free(e); + kh->entity = g_slist_remove(kh->entity, e); + } + + g_free(kh); + } +} + +void sp_knot_holder_add( + SPKnotHolder *knot_holder, + SPKnotHolderSetFunc knot_set, + SPKnotHolderGetFunc knot_get, + void (* knot_click) (SPItem *item, guint state), + const gchar *tip + ) +{ + sp_knot_holder_add_full(knot_holder, knot_set, knot_get, knot_click, SP_KNOT_SHAPE_DIAMOND, SP_KNOT_MODE_XOR, tip); +} + +void sp_knot_holder_add_full( + SPKnotHolder *knot_holder, + SPKnotHolderSetFunc knot_set, + SPKnotHolderGetFunc knot_get, + void (* knot_click) (SPItem *item, guint state), + SPKnotShapeType shape, + SPKnotModeType mode, + const gchar *tip + ) +{ + g_return_if_fail(knot_holder != NULL); + g_return_if_fail(knot_set != NULL); + g_return_if_fail(knot_get != NULL); + + SPItem *item = SP_ITEM(knot_holder->item); + + /* create new SPKnotHolderEntry */ + SPKnotHolderEntity *e = g_new(SPKnotHolderEntity, 1); + e->knot = sp_knot_new(knot_holder->desktop, tip); + e->knot_set = knot_set; + e->knot_get = knot_get; + if (knot_click) { + e->knot_click = knot_click; + } else { + e->knot_click = NULL; + } + + g_object_set(G_OBJECT (e->knot->item), "shape", shape, NULL); + g_object_set(G_OBJECT (e->knot->item), "mode", mode, NULL); + + // TODO: add a color argument + //e->knot->fill [SP_KNOT_STATE_NORMAL] = 0x00ff0000; + //g_object_set (G_OBJECT (e->knot->item), "fill_color", 0x00ff0000, NULL); + + knot_holder->entity = g_slist_append(knot_holder->entity, e); + + /* Move to current point. */ + NR::Point dp = e->knot_get(item) * sp_item_i2d_affine(item); + sp_knot_set_position(e->knot, &dp, SP_KNOT_STATE_NORMAL); + + e->handler_id = g_signal_connect(G_OBJECT(e->knot), "moved", G_CALLBACK(knot_moved_handler), knot_holder); + g_signal_connect(G_OBJECT(e->knot), "clicked", G_CALLBACK(knot_clicked_handler), knot_holder); + g_signal_connect(G_OBJECT(e->knot), "ungrabbed", G_CALLBACK(knot_ungrabbed_handler), knot_holder); + +#ifdef KNOT_HOLDER_DEBUG + g_signal_connect(ob, "destroy", sp_knot_holder_debug, "SPKnotHolder::knot"); +#endif + sp_knot_show(e->knot); +} + +/** + * \param p In desktop coordinates. + */ + +static void knotholder_update_knots(SPKnotHolder *knot_holder, SPItem *item) +{ + NR::Matrix const i2d(sp_item_i2d_affine(item)); + + for (GSList *el = knot_holder->entity; el; el = el->next) { + SPKnotHolderEntity *e = (SPKnotHolderEntity *) el->data; + GObject *kob = G_OBJECT(e->knot); + + NR::Point dp( e->knot_get(item) * i2d ); + g_signal_handler_block(kob, e->handler_id); + sp_knot_set_position(e->knot, &dp, SP_KNOT_STATE_NORMAL); + g_signal_handler_unblock(kob, e->handler_id); + } +} + +static void knot_clicked_handler(SPKnot *knot, guint state, gpointer data) +{ + SPKnotHolder *knot_holder = (SPKnotHolder *) data; + SPItem *item = SP_ITEM (knot_holder->item); + + for (GSList *el = knot_holder->entity; el; el = el->next) { + SPKnotHolderEntity *e = (SPKnotHolderEntity *) el->data; + if (e->knot == knot) { + if (e->knot_click) { + e->knot_click(item, state); + } + break; + } + } + + if (SP_IS_SHAPE(item)) { + sp_shape_set_shape(SP_SHAPE(item)); + } + + knotholder_update_knots(knot_holder, item); + + // for drag, this is done by ungrabbed_handler, but for click we must do it here + sp_document_done(SP_OBJECT_DOCUMENT(knot_holder->item)); +} + +static void knot_moved_handler(SPKnot *knot, NR::Point const *p, guint state, gpointer data) +{ + SPKnotHolder *knot_holder = (SPKnotHolder *) data; + SPItem *item = SP_ITEM (knot_holder->item); + // this was a local change and the knotholder does not need to be recreated: + knot_holder->local_change = TRUE; + + for (GSList *el = knot_holder->entity; el; el = el->next) { + SPKnotHolderEntity *e = (SPKnotHolderEntity *) el->data; + if (e->knot == knot) { + NR::Point const q = *p / sp_item_i2d_affine(item); + e->knot_set(item, q, e->knot->drag_origin / sp_item_i2d_affine(item), state); + break; + } + } + + if (SP_IS_SHAPE (item)) { + sp_shape_set_shape(SP_SHAPE (item)); + } + + knotholder_update_knots(knot_holder, item); +} + +static void knot_ungrabbed_handler(SPKnot *knot, unsigned int state, SPKnotHolder *kh) +{ + if (kh->released) { + kh->released(kh->item); + } else { + SPObject *object = (SPObject *) kh->item; + object->updateRepr(object->repr, SP_OBJECT_WRITE_EXT); + sp_document_done(SP_OBJECT_DOCUMENT (object)); + } +} + +/* + 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 : diff --git a/src/knotholder.h b/src/knotholder.h new file mode 100644 index 000000000..6980e2fdd --- /dev/null +++ b/src/knotholder.h @@ -0,0 +1,79 @@ +#ifndef __SP_KNOTHOLDER_H__ +#define __SP_KNOTHOLDER_H__ + +/* + * SPKnotHolder - Hold SPKnot list and manage signals + * + * Author: + * Mitsuru Oka + * + * Copyright (C) 1999-2001 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2001 Mitsuru Oka + * + * Released under GNU GPL + * + */ + +#include +#include "knot-enums.h" +#include "forward.h" +#include "libnr/nr-forward.h" + +namespace Inkscape { +namespace XML { +class Node; +} +} + + +typedef void (* SPKnotHolderSetFunc) (SPItem *item, NR::Point const &p, NR::Point const &origin, guint state); +typedef NR::Point (* SPKnotHolderGetFunc) (SPItem *item); +/* fixme: Think how to make callbacks most sensitive (Lauris) */ +typedef void (* SPKnotHolderReleasedFunc) (SPItem *item); + +struct SPKnotHolder { + SPDesktop *desktop; + SPItem *item; + GSList *entity; + + SPKnotHolderReleasedFunc released; + + Inkscape::XML::Node *repr; ///< repr of the item, for setting and releasing listeners. + + gboolean local_change; ///< if true, no need to recreate knotholder if repr was changed. +}; + + +/* fixme: As a temporary solution, if released is NULL knotholder flushes undo itself (Lauris) */ +SPKnotHolder *sp_knot_holder_new(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler); + +void sp_knot_holder_destroy(SPKnotHolder *knots); + +void sp_knot_holder_add(SPKnotHolder *knot_holder, + SPKnotHolderSetFunc knot_set, + SPKnotHolderGetFunc knot_get, + void (* knot_click) (SPItem *item, guint state), + gchar const *tip); + +void sp_knot_holder_add_full(SPKnotHolder *knot_holder, + SPKnotHolderSetFunc knot_set, + SPKnotHolderGetFunc knot_get, + void (* knot_click) (SPItem *item, guint state), + SPKnotShapeType shape, + SPKnotModeType mode, + gchar const *tip); + + +#endif /* !__SP_KNOTHOLDER_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:encoding=utf-8:textwidth=99 : diff --git a/src/layer-fns.cpp b/src/layer-fns.cpp new file mode 100644 index 000000000..fadcd0f1e --- /dev/null +++ b/src/layer-fns.cpp @@ -0,0 +1,186 @@ +/* + * Inkscape::SelectionDescriber - shows messages describing selection + * + * Authors: + * MenTaLguY + * + * Copyright (C) 2004 MenTaLguY + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "document.h" +#include "sp-item-group.h" +#include "xml/repr.h" +#include "algorithms/find-last-if.h" + +namespace Inkscape { + +/** + * Creates a new layer. Advances to the next layer id indicated + * by the string "layerNN", then creates a new group object of + * that id with attribute inkscape:groupmode='layer', and finally + * appends the new group object to \a root after object \a layer. + * + * \pre \a root should be either \a layer or an ancestor of it + */ +SPObject *create_layer(SPObject *root, SPObject *layer) { + SPDocument *document=SP_OBJECT_DOCUMENT(root); + + static int layer_suffix=1; + gchar *id=NULL; + do { + g_free(id); + id = g_strdup_printf("layer%d", layer_suffix++); + } while (document->getObjectById(id)); + + Inkscape::XML::Node *repr=sp_repr_new("svg:g"); + repr->setAttribute("inkscape:groupmode", "layer"); + repr->setAttribute("id", id); + g_free(id); + + if ( root == layer ) { + SP_OBJECT_REPR(root)->appendChild(repr); + } else { + Inkscape::XML::Node *layer_repr=SP_OBJECT_REPR(layer); + sp_repr_parent(layer_repr)->addChild(repr, layer_repr); + } + + return document->getObjectByRepr(repr); +} + +namespace { + +bool is_layer(SPObject &object) { + return SP_IS_GROUP(&object) && + SP_GROUP(&object)->layerMode() == SPGroup::LAYER; +} + +SPObject *next_sibling_layer(SPObject *layer) { + using std::find_if; + + return find_if( + SP_OBJECT_NEXT(layer), NULL, &is_layer + ); +} + +SPObject *previous_sibling_layer(SPObject *layer) { + using Inkscape::Algorithms::find_last_if; + + SPObject *sibling(find_last_if( + SP_OBJECT_PARENT(layer)->firstChild(), layer, &is_layer + )); + + return ( sibling != layer ) ? sibling : NULL; +} + +SPObject *first_descendant_layer(SPObject *layer) { + using std::find_if; + + SPObject *first_descendant=NULL; + while (layer) { + layer = find_if( + layer->firstChild(), NULL, &is_layer + ); + if (layer) { + first_descendant = layer; + } + } + + return first_descendant; +} + +SPObject *last_child_layer(SPObject *layer) { + using Inkscape::Algorithms::find_last_if; + + return find_last_if( + layer->firstChild(), NULL, &is_layer + ); +} + +SPObject *last_elder_layer(SPObject *root, SPObject *layer) { + using Inkscape::Algorithms::find_last_if; + + while ( layer != root ) { + SPObject *sibling(previous_sibling_layer(layer)); + if (sibling) { + return sibling; + } + layer = SP_OBJECT_PARENT(layer); + } + + return NULL; +} + +} + +/** Finds the next layer under \a root, relative to \a layer in + * depth-first order. + * + * @returns NULL if there are no further layers under \a root + */ +SPObject *next_layer(SPObject *root, SPObject *layer) { + using std::find_if; + + g_return_val_if_fail(layer != NULL, NULL); + + SPObject *sibling(next_sibling_layer(layer)); + if (sibling) { + SPObject *descendant(first_descendant_layer(sibling)); + if (descendant) { + return descendant; + } else { + return sibling; + } + } else { + SPObject *parent=SP_OBJECT_PARENT(layer); + if ( parent != root ) { + return parent; + } else { + return NULL; + } + } +} + + +/** Finds the previous layer under \a root, relative to \a layer in + * depth-first order. + * + * @returns NULL if there are no prior layers under \a root. + */ +SPObject *previous_layer(SPObject *root, SPObject *layer) { + using Inkscape::Algorithms::find_last_if; + + g_return_val_if_fail(layer != NULL, NULL); + + SPObject *child(last_child_layer(layer)); + if (child) { + return child; + } else if ( layer != root ) { + SPObject *sibling(previous_sibling_layer(layer)); + if (sibling) { + return sibling; + } else { + return last_elder_layer(root, SP_OBJECT_PARENT(layer)); + } + } + + return NULL; +} + +} + +/* + 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 : diff --git a/src/layer-fns.h b/src/layer-fns.h new file mode 100644 index 000000000..ba11bab6a --- /dev/null +++ b/src/layer-fns.h @@ -0,0 +1,37 @@ +/* + * assorted functions related to layers + * + * Authors: + * MenTaLguY + * + * Copyright (C) 2004 MenTaLguY + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SEEN_INKSCAPE_LAYER_FNS_H +#define SEEN_INKSCAPE_LAYER_FNS_H + +class SPObject; + +namespace Inkscape { + +SPObject *create_layer(SPObject *root, SPObject *layer); + +SPObject *next_layer(SPObject *root, SPObject *layer); + +SPObject *previous_layer(SPObject *root, SPObject *layer); + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/libavoid/.cvsignore b/src/libavoid/.cvsignore new file mode 100644 index 000000000..38efca7bc --- /dev/null +++ b/src/libavoid/.cvsignore @@ -0,0 +1,3 @@ +.deps +.dirstamp +makefile diff --git a/src/libavoid/Makefile_insert b/src/libavoid/Makefile_insert new file mode 100644 index 000000000..146344813 --- /dev/null +++ b/src/libavoid/Makefile_insert @@ -0,0 +1,33 @@ +## Makefile.am fragment sourced by src/Makefile.am. + +libavoid/all: libavoid/libavoid.a + +libavoid/clean: + rm -f libavoid/libavoid.a $(libavoid_libavoid_a_OBJECTS) + +libavoid_libavoid_a_SOURCES = \ + libavoid/connector.cpp \ + libavoid/connector.h \ + libavoid/debug.h \ + libavoid/geometry.cpp \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/graph.cpp \ + libavoid/graph.h \ + libavoid/incremental.cpp \ + libavoid/incremental.h \ + libavoid/makepath.cpp \ + libavoid/makepath.h \ + libavoid/polyutil.cpp \ + libavoid/polyutil.h \ + libavoid/shape.cpp \ + libavoid/shape.h \ + libavoid/static.cpp \ + libavoid/static.h \ + libavoid/timer.cpp \ + libavoid/timer.h \ + libavoid/vertices.cpp \ + libavoid/vertices.h \ + libavoid/visibility.cpp \ + libavoid/visibility.h \ + libavoid/libavoid.h diff --git a/src/libavoid/README b/src/libavoid/README new file mode 100644 index 000000000..ada0e9908 --- /dev/null +++ b/src/libavoid/README @@ -0,0 +1,5 @@ +This directory contains libavoid-0.1. It has been included here since it is +a new library without wide availablity. + +The project page is http://www.sourceforge.net/projects/libavoid/ +The library's maintainer is Michael Wybrow, an Inkscape developer. diff --git a/src/libavoid/connector.cpp b/src/libavoid/connector.cpp new file mode 100644 index 000000000..cde387c5b --- /dev/null +++ b/src/libavoid/connector.cpp @@ -0,0 +1,408 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * Copyright (C) 2004-2005 Michael Wybrow + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ + +#include "libavoid/graph.h" +#include "libavoid/makepath.h" +#include "libavoid/visibility.h" +#include "libavoid/debug.h" + + +namespace Avoid { + + +ConnRefList connRefs; + + +ConnRef::ConnRef(const uint id) + : _id(id) + , _needs_reroute_flag(true) + , _false_path(false) + , _active(false) + , _route_dist(0) + , _srcVert(NULL) + , _dstVert(NULL) + , _initialised(false) + , _callback(NULL) + , _connector(NULL) +{ + // TODO: Store endpoints and details. + _route.pn = 0; + _route.ps = NULL; +} + + +ConnRef::ConnRef(const uint id, const Point& src, const Point& dst) + : _id(id) + , _needs_reroute_flag(true) + , _false_path(false) + , _active(false) + , _route_dist(0) + , _srcVert(NULL) + , _dstVert(NULL) + , _initialised(false) + , _callback(NULL) + , _connector(NULL) +{ + _route.pn = 0; + _route.ps = NULL; + + if (IncludeEndpoints) + { + bool isShape = false; + _srcVert = new VertInf(VertID(id, isShape, 1), src); + _dstVert = new VertInf(VertID(id, isShape, 2), dst); + vertices.addVertex(_srcVert); + vertices.addVertex(_dstVert); + makeActive(); + _initialised = true; + } +} + + +ConnRef::~ConnRef() +{ + freeRoute(); + + if (_srcVert) + { + vertices.removeVertex(_srcVert); + delete _srcVert; + _srcVert = NULL; + } + + if (_dstVert) + { + vertices.removeVertex(_dstVert); + delete _dstVert; + _dstVert = NULL; + } + + if (_active) + { + makeInactive(); + } +} + +void ConnRef::updateEndPoint(const uint type, const Point& point) +{ + assert((type == (uint) VertID::src) || (type == (uint) VertID::tar)); + //assert(IncludeEndpoints); + + VertInf *altered = NULL; + VertInf *partner = NULL; + bool isShape = false; + + if (type == (uint) VertID::src) + { + if (_srcVert) + { + _srcVert->Reset(point); + } + else + { + _srcVert = new VertInf(VertID(_id, isShape, type), point); + vertices.addVertex(_srcVert); + } + + altered = _srcVert; + partner = _dstVert; + } + else // if (type == (uint) VertID::dst) + { + if (_dstVert) + { + _dstVert->Reset(point); + } + else + { + _dstVert = new VertInf(VertID(_id, isShape, type), point); + vertices.addVertex(_dstVert); + } + + altered = _dstVert; + partner = _srcVert; + } + + bool knownNew = false; + vertexVisibility(altered, partner, knownNew, true); +} + + +void ConnRef::makeActive(void) +{ + assert(!_active); + + // Add to connRefs list. + _pos = connRefs.insert(connRefs.begin(), this); + _active = true; +} + + +void ConnRef::makeInactive(void) +{ + assert(_active); + + // Remove from connRefs list. + connRefs.erase(_pos); + _active = false; +} + + +void ConnRef::freeRoute(void) +{ + if (_route.ps) + { + _route.pn = 0; + std::free(_route.ps); + _route.ps = NULL; + } +} + + +PolyLine& ConnRef::route(void) +{ + return _route; +} + + +void ConnRef::calcRouteDist(void) +{ + _route_dist = 0; + for (int i = 1; i < _route.pn; i++) + { + _route_dist += dist(_route.ps[i], _route.ps[i - 1]); + } +} + + +bool ConnRef::needsReroute(void) +{ + return (_false_path || _needs_reroute_flag); +} + + +void ConnRef::moveRoute(const int& diff_x, const int& diff_y) +{ + for (int i = 0; i < _route.pn; i++) + { + _route.ps[i].x += diff_x; + _route.ps[i].y += diff_y; + } +} + + +void ConnRef::lateSetup(const Point& src, const Point& dst) +{ + assert(!_initialised); + + bool isShape = false; + _srcVert = new VertInf(VertID(_id, isShape, 1), src); + _dstVert = new VertInf(VertID(_id, isShape, 2), dst); + vertices.addVertex(_srcVert); + vertices.addVertex(_dstVert); + makeActive(); + _initialised = true; +} + + +VertInf *ConnRef::src(void) +{ + return _srcVert; +} + + +VertInf *ConnRef::dst(void) +{ + return _dstVert; +} + + +bool ConnRef::isInitialised(void) +{ + return _initialised; +} + + +void ConnRef::unInitialise(void) +{ + vertices.removeVertex(_srcVert); + vertices.removeVertex(_dstVert); + makeInactive(); + _initialised = false; +} + + +void ConnRef::removeFromGraph(void) +{ + for (VertInf *iter = _srcVert; iter != NULL; ) + { + VertInf *tmp = iter; + iter = (iter == _srcVert) ? _dstVert : NULL; + + // For each vertex. + EdgeInfList& visList = tmp->visList; + EdgeInfList::iterator finish = visList.end(); + EdgeInfList::iterator edge; + while ((edge = visList.begin()) != finish) + { + // Remove each visibility edge + delete (*edge); + } + + EdgeInfList& invisList = tmp->invisList; + finish = invisList.end(); + while ((edge = invisList.begin()) != finish) + { + // Remove each invisibility edge + delete (*edge); + } + } +} + + +void ConnRef::setCallback(void (*cb)(void *), void *ptr) +{ + _callback = cb; + _connector = ptr; +} + + +void ConnRef::handleInvalid(void) +{ + if (_false_path || _needs_reroute_flag) { + if (_callback) { + _callback(_connector); + } + } +} + + +void ConnRef::makePathInvalid(void) +{ + _needs_reroute_flag = true; +} + + +int ConnRef::generatePath(Point p0, Point p1) +{ + if (!_false_path && !_needs_reroute_flag) { + // This connector is up to date. + return (int) false; + } + + _false_path = false; + _needs_reroute_flag = false; + + VertInf *src = _srcVert; + VertInf *tar = _dstVert; + + if (!IncludeEndpoints) + { + lateSetup(p0, p1); + + // Update as they have just been set by lateSetup. + src = _srcVert; + tar = _dstVert; + + bool knownNew = true; + vertexVisibility(src, tar, knownNew); + vertexVisibility(tar, src, knownNew); + } + + bool *flag = &(_needs_reroute_flag); + + makePath(this, flag); + + bool result = true; + + int pathlen = 1; + for (VertInf *i = tar; i != src; i = i->pathNext) + { + pathlen++; + if (i == NULL) + { + db_printf("Warning: Path not found...\n"); + pathlen = 2; + tar->pathNext = src; + if (InvisibilityGrph) + { + // TODO: Could we know this edge already? + EdgeInf *edge = EdgeInf::existingEdge(src, tar); + assert(edge != NULL); + edge->addCycleBlocker(); + } + result = false; + break; + } + if (pathlen > 100) + { + fprintf(stderr, "ERROR: Should never be here...\n"); + exit(1); + } + } + Point *path = (Point *) malloc(pathlen * sizeof(Point)); + + int j = pathlen - 1; + for (VertInf *i = tar; i != src; i = i->pathNext) + { + if (InvisibilityGrph) + { + // TODO: Again, we could know this edge without searching. + EdgeInf *edge = EdgeInf::existingEdge(i, i->pathNext); + edge->addConn(flag); + } + else + { + _false_path = true; + } + path[j--] = i->point; + } + path[0] = src->point; + + + // Would clear visibility for endpoints here if required. + + PolyLine& output_route = route(); + output_route.pn = pathlen; + output_route.ps = path; + + return (int) result; +} + + +//============================================================================ + + + // It's intended this function is called after shape movement has + // happened to alert connectors that they need to be rerouted. +void callbackAllInvalidConnectors(void) +{ + ConnRefList::iterator fin = connRefs.end(); + for (ConnRefList::iterator i = connRefs.begin(); i != fin; ++i) { + (*i)->handleInvalid(); + } +} + + +} + + diff --git a/src/libavoid/connector.h b/src/libavoid/connector.h new file mode 100644 index 000000000..6abb01d49 --- /dev/null +++ b/src/libavoid/connector.h @@ -0,0 +1,93 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * Copyright (C) 2004-2005 Michael Wybrow + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ + +#ifndef AVOID_CONNECTOR_H +#define AVOID_CONNECTOR_H + +#include "libavoid/geometry.h" +#include "libavoid/shape.h" +#include + + +namespace Avoid { + +typedef unsigned int uint; + +class ConnRef; + +typedef std::list ConnRefList; + + +class ConnRef +{ + public: + ConnRef(const uint id); + ConnRef(const uint id, const Point& src, const Point& dst); + ~ConnRef(); + + PolyLine& route(void); + bool needsReroute(void); + void moveRoute(const int& diff_x, const int& diff_y); + void freeRoute(void); + void calcRouteDist(void); + void updateEndPoint(const uint type, const Point& point); + void makeActive(void); + void makeInactive(void); + void lateSetup(const Point& src, const Point& dst); + VertInf *src(void); + VertInf *dst(void); + void removeFromGraph(void); + bool isInitialised(void); + void unInitialise(void); + void setCallback(void (*cb)(void *), void *ptr); + void handleInvalid(void); + int generatePath(Point p0, Point p1); + void makePathInvalid(void); + + friend void markConnectors(ShapeRef *shape); + + private: + uint _id; + bool _needs_reroute_flag; + bool _false_path; + bool _active; + PolyLine _route; + double _route_dist; + ConnRefList::iterator _pos; + VertInf *_srcVert; + VertInf *_dstVert; + bool _initialised; + void (*_callback)(void *); + void *_connector; +}; + + +extern ConnRefList connRefs; + +extern void callbackAllInvalidConnectors(void); + +} + + +#endif + + diff --git a/src/libavoid/debug.h b/src/libavoid/debug.h new file mode 100644 index 000000000..0b182d442 --- /dev/null +++ b/src/libavoid/debug.h @@ -0,0 +1,61 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * Copyright (C) 2004-2005 Michael Wybrow + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ + +#ifndef AVOID_DEBUG_H +#define AVOID_DEBUG_H + + + +#ifndef NDEBUG + //#define DBPRINTF_DEBUG +#endif + + +#ifdef DBPRINTF_DEBUG + +#include +#include + +#endif + +namespace Avoid { + +#ifdef DBPRINTF_DEBUG +inline void db_printf(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + va_end(ap); +} +#else +inline void db_printf(const char *fmt, ...) +{ +} +#endif + +} + + +#endif + + diff --git a/src/libavoid/geometry.cpp b/src/libavoid/geometry.cpp new file mode 100644 index 000000000..d720693ac --- /dev/null +++ b/src/libavoid/geometry.cpp @@ -0,0 +1,260 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * Copyright (C) 2004-2005 Michael Wybrow + * + * -------------------------------------------------------------------- + * Much of the code in this module is based on code published with + * and/or described in "Computational Geometry in C" (Second Edition), + * Copyright (C) 1998 Joseph O'Rourke + * -------------------------------------------------------------------- + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ + +#include "libavoid/graph.h" +#include "libavoid/polyutil.h" + +#include + +namespace Avoid { + + +// Returns true iff the point c lies on the closed segment ab. +// +// Based on the code of 'Between'. +// +static const bool inBetween(const Point& a, const Point& b, const Point& c) +{ + // We only call this when we know the points are collinear, + // otherwise we should be checking this here. + assert(vecDir(a, b, c) == 0); + + if (a.x != b.x) + { + // not vertical + return (((a.x < c.x) && (c.x < b.x)) || + ((b.x < c.x) && (c.x < a.x))); + } + else + { + return (((a.y < c.y) && (c.y < b.y)) || + ((b.y < c.y) && (c.y < a.y))); + } +} + + +// Returns true if the segment cd intersects the segment ab, blocking +// visibility. +// +// Based on the code of 'IntersectProp' and 'Intersect'. +// +bool segmentIntersect(const Point& a, const Point& b, const Point& c, + const Point& d) +{ + int ab_c = vecDir(a, b, c); + if ((ab_c == 0) && inBetween(a, b, c)) + { + return true; + } + + int ab_d = vecDir(a, b, d); + if ((ab_d == 0) && inBetween(a, b, d)) + { + return true; + } + + // It's ok for either of the points a or b to be on the line cd, + // so we don't have to check the other two cases. + + int cd_a = vecDir(c, d, a); + int cd_b = vecDir(c, d, b); + + // Is an intersection if a and b are on opposite sides of cd, + // and c and d are on opposite sides of the line ab. + // + // Note: this is safe even though the textbook warns about it + // since, unlike them, out vecDir is equivilent to 'AreaSign' + // rather than 'Area2'. + return (((ab_c * ab_d) < 0) && ((cd_a * cd_b) < 0)); +} + + +// Returns true iff the point p in a valid region that can contain +// shortest paths. a0, a1, a2 are ordered vertices of a shape. +// This function may seem 'backwards' to the user due to some of +// the code being reversed due to screen cooridinated being the +// opposite of graph paper coords. +// TODO: Rewrite this after checking whether it works for Inkscape. +// +// Based on the code of 'InCone'. +// +bool inValidRegion(const Point& a0, const Point& a1, const Point& a2, + const Point& b) +{ + int rSide = vecDir(b, a0, a1); + int sSide = vecDir(b, a1, a2); + + bool rOutOn = (rSide >= 0); + bool sOutOn = (sSide >= 0); + + bool rOut = (rSide > 0); + bool sOut = (sSide > 0); + + if (vecDir(a0, a1, a2) > 0) + { + // Concave at a1: + // + // !rO rO + // !sO !sO + // + // +---s--- + // | + // !rO r rO + // sO | sO + // + // + return (IgnoreRegions ? false : (rOutOn && sOutOn)); + } + else + { + // Convex at a1: + // + // !rO rO + // sO sO + // + // ---s---+ + // | + // !rO r rO + // !sO | !sO + // + // + if (IgnoreRegions) + { + return (rOutOn && !sOut) || (!rOut && sOutOn); + } + return (rOutOn || sOutOn); + } +} + + +// Returns the distance between points a and b. +// +double dist(const Point& a, const Point& b) +{ + double xdiff = a.x - b.x; + double ydiff = a.y - b.y; + + return sqrt((xdiff * xdiff) + (ydiff * ydiff)); +} + + +// Returns true iff the point q is inside (or on the edge of) the +// polygon argpoly. +// +// Based on the code of 'InPoly'. +// +bool inPoly(const Polygn& argpoly, const Point& q) +{ + // Numbers of right and left edge/ray crossings. + int Rcross = 0; + int Lcross = 0; + + // Copy the argument polygon + Polygn poly = copyPoly(argpoly); + Point *P = poly.ps; + int n = poly.pn; + + // Shift so that q is the origin. This is done for pedogical clarity. + for (int i = 0; i < n; ++i) + { + P[i].x = P[i].x - q.x; + P[i].y = P[i].y - q.y; + } + + // For each edge e=(i-1,i), see if crosses ray. + for (int i = 0; i < n; ++i) + { + // First see if q=(0,0) is a vertex. + if ((P[i].x == 0) && (P[i].y == 0)) + { + // We count a vertex as inside. + freePoly(poly); + return true; + } + + // point index; i1 = i-1 mod n + int i1 = ( i + n - 1 ) % n; + + // if e "straddles" the x-axis... + // The commented-out statement is logically equivalent to the one + // following. + // if( ((P[i].y > 0) && (P[i1].y <= 0)) || + // ((P[i1].y > 0) && (P[i].y <= 0)) ) + + if ((P[i].y > 0) != (P[i1].y > 0)) + { + // e straddles ray, so compute intersection with ray. + double x = (P[i].x * P[i1].y - P[i1].x * P[i].y) + / (P[i1].y - P[i].y); + + // crosses ray if strictly positive intersection. + if (x > 0) + { + Rcross++; + } + } + + // if e straddles the x-axis when reversed... + // if( ((P[i].y < 0) && (P[i1].y >= 0)) || + // ((P[i1].y < 0) && (P[i].y >= 0)) ) + + if ((P[i].y < 0) != (P[i1].y < 0)) + { + // e straddles ray, so compute intersection with ray. + double x = (P[i].x * P[i1].y - P[i1].x * P[i].y) + / (P[i1].y - P[i].y); + + // crosses ray if strictly positive intersection. + if (x < 0) + { + Lcross++; + } + } + } + freePoly(poly); + + // q on the edge if left and right cross are not the same parity. + if ( (Rcross % 2) != (Lcross % 2) ) + { + // We count the edge as inside. + return true; + } + + // Inside iff an odd number of crossings. + if ((Rcross % 2) == 1) + { + return true; + } + + // Outside. + return false; +} + + +} + diff --git a/src/libavoid/geometry.h b/src/libavoid/geometry.h new file mode 100644 index 000000000..500da6273 --- /dev/null +++ b/src/libavoid/geometry.h @@ -0,0 +1,75 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * Copyright (C) 2004-2005 Michael Wybrow + * + * -------------------------------------------------------------------- + * Much of the code in this module is based on code published with + * and/or described in "Computational Geometry in C" (Second Edition), + * Copyright (C) 1998 Joseph O'Rourke + * -------------------------------------------------------------------- + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ + + +#ifndef _GEOMETRY_H +#define _GEOMETRY_H + +#include "libavoid/geomtypes.h" + +namespace Avoid { + + +extern double dist(const Point& a, const Point& b); +extern bool segmentIntersect(const Point& a, const Point& b, + const Point& c, const Point& d); +extern bool inPoly(const Polygn& poly, const Point& q); +extern bool inValidRegion(const Point& a0, const Point& a1, const Point& a2, + const Point& b); + + +// Direction from vector. +// Looks at the position of point c from the directed segment ab and +// returns the following: +// 1 counterclockwise +// 0 collinear +// -1 clockwise +// +// Based on the code of 'AreaSign'. +// +static inline int vecDir(const Point& a, const Point& b, const Point& c) +{ + double area2 = ((b.x - a.x) * (c.y - a.y)) - + ((c.x - a.x) * (b.y - a.y)); + + if (area2 < -0.001) + { + return -1; + } + else if (area2 > 0.001) + { + return 1; + } + return 0; +} + + +} + + +#endif diff --git a/src/libavoid/geomtypes.h b/src/libavoid/geomtypes.h new file mode 100644 index 000000000..9b682759a --- /dev/null +++ b/src/libavoid/geomtypes.h @@ -0,0 +1,61 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * Copyright (C) 2004-2005 Michael Wybrow + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ + + +#ifndef AVOID_GEOMTYPES_H +#define AVOID_GEOMTYPES_H + + +namespace Avoid +{ + + +typedef struct +{ + double x; + double y; +} Point; + + +typedef Point Vector; + + +typedef struct +{ + int id; + Point *ps; + int pn; +} Polygn; + +typedef Polygn PolyLine; + + +typedef struct +{ + Point a; + Point b; +} Edge; + + +} + +#endif diff --git a/src/libavoid/graph.cpp b/src/libavoid/graph.cpp new file mode 100644 index 000000000..9b1d602be --- /dev/null +++ b/src/libavoid/graph.cpp @@ -0,0 +1,986 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * Copyright (C) 2004-2005 Michael Wybrow + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ + +#include "libavoid/debug.h" +#include "libavoid/graph.h" +#include "libavoid/connector.h" +#include "libavoid/polyutil.h" +#include "libavoid/timer.h" + +#include + +namespace Avoid { + + +static int st_checked_edges = 0; + +EdgeList visGraph; +EdgeList invisGraph; + + +EdgeInf::EdgeInf(VertInf *v1, VertInf *v2) + : lstPrev(NULL) + , lstNext(NULL) + , _added(false) + , _visible(false) + , _v1(v1) + , _v2(v2) + , _dist(-1) +{ + _blockers.clear(); + _conns.clear(); +} + + +EdgeInf::~EdgeInf() +{ + if (_added) + { + makeInactive(); + } +} + + +void EdgeInf::makeActive(void) +{ + assert(_added == false); + + if (_visible) + { + visGraph.addEdge(this); + _pos1 = _v1->visList.insert(_v1->visList.begin(), this); + _v1->visListSize++; + _pos2 = _v2->visList.insert(_v2->visList.begin(), this); + _v2->visListSize++; + } + else // if (invisible) + { + invisGraph.addEdge(this); + _pos1 = _v1->invisList.insert(_v1->invisList.begin(), this); + _v1->invisListSize++; + _pos2 = _v2->invisList.insert(_v2->invisList.begin(), this); + _v2->invisListSize++; + } + _added = true; +} + + +void EdgeInf::makeInactive(void) +{ + assert(_added == true); + + if (_visible) + { + visGraph.removeEdge(this); + _v1->visList.erase(_pos1); + _v1->visListSize--; + _v2->visList.erase(_pos2); + _v2->visListSize--; + } + else // if (invisible) + { + invisGraph.removeEdge(this); + _v1->invisList.erase(_pos1); + _v1->invisListSize--; + _v2->invisList.erase(_pos2); + _v2->invisListSize--; + } + _blockers.clear(); + _conns.clear(); + _added = false; +} + + +double EdgeInf::getDist(void) +{ + return _dist; +} + + +void EdgeInf::setDist(double dist) +{ + //assert(dist != 0); + + if (_added && !_visible) + { + makeInactive(); + } + if (!_added) + { + _visible = true; + makeActive(); + } + _dist = dist; + _blockers.clear(); +} + + +void EdgeInf::alertConns(void) +{ + for (FlagList::iterator i = _conns.begin(); i != _conns.end(); ++i) + { + *(*i) = true; + } + _conns.clear(); +} + + +void EdgeInf::addConn(bool *flag) +{ + _conns.push_back(flag); +} + + +void EdgeInf::addCycleBlocker(void) +{ + // Needs to be in invisibility graph. + addBlocker(-1); +} + + +void EdgeInf::addBlocker(int b) +{ + assert(InvisibilityGrph); + + if (_added && _visible) + { + makeInactive(); + } + if (!_added) + { + _visible = false; + makeActive(); + } + _dist = 0; + _blockers.clear(); + _blockers.push_back(b); +} + + +bool EdgeInf::hasBlocker(int b) +{ + assert(InvisibilityGrph); + + ShapeList::iterator finish = _blockers.end(); + for (ShapeList::iterator it = _blockers.begin(); it != finish; ++it) + { + if ((*it) == -1) + { + alertConns(); + return true; + } + else if ((*it) == b) + { + return true; + } + } + return false; +} + + +pair EdgeInf::ids(void) +{ + return std::make_pair(_v1->id, _v2->id); +} + + +pair EdgeInf::points(void) +{ + return std::make_pair(_v1->point, _v2->point); +} + + +void EdgeInf::db_print(void) +{ + db_printf("Edge("); + _v1->id.db_print(); + db_printf(","); + _v2->id.db_print(); + db_printf(")\n"); +} + + +void EdgeInf::checkVis(void) +{ + if (_added && !_visible) + { + db_printf("\tChecking visibility for existing invisibility edge..." + "\n\t\t"); + db_print(); + } + else if (_added && _visible) + { + db_printf("\tChecking visibility for existing visibility edge..." + "\n\t\t"); + db_print(); + } + + int blocker = 0; + bool cone1 = true; + bool cone2 = true; + + VertInf *i = _v1; + VertInf *j = _v2; + const VertID& iID = i->id; + const VertID& jID = j->id; + const Point& iPoint = i->point; + const Point& jPoint = j->point; + + st_checked_edges++; + + if (iID.isShape) + { + cone1 = inValidRegion(i->shPrev->point, iPoint, i->shNext->point, + jPoint); + } + else + { + ShapeSet& ss = contains[iID]; + + if ((jID.isShape) && (ss.find(jID.objID) != ss.end())) + { + db_printf("1: Edge of bounding shape\n"); + // Don't even check this edge, it should be zero, + // since a point in a shape can't see it's corners + cone1 = false; + } + } + + if (cone1) + { + // If outside the first cone, don't even bother checking. + if (jID.isShape) + { + cone2 = inValidRegion(j->shPrev->point, jPoint, j->shNext->point, + iPoint); + } + else + { + ShapeSet& ss = contains[jID]; + + if ((iID.isShape) && (ss.find(iID.objID) != ss.end())) + { + db_printf("2: Edge of bounding shape\n"); + // Don't even check this edge, it should be zero, + // since a point in a shape can't see it's corners + cone2 = false; + } + } + } + + if (cone1 && cone2 && ((blocker = firstBlocker()) == 0)) + { + + // if i and j see each other, add edge + db_printf("\tSetting visibility edge... \n\t\t"); + db_print(); + + double d = dist(iPoint, jPoint); + + setDist(d); + + } + else if (InvisibilityGrph) + { +#if 0 + db_printf("%d, %d, %d\n", cone1, cone2, blocker); + db_printf("\t(%d, %d)--(%d, %d)\n", (int) iInfo.point.x, + (int) iInfo.point.y, (int) jInfo.point.x, + (int) jInfo.point.y); +#endif + + // if i and j can't see each other, add blank edge + db_printf("\tSetting invisibility edge... \n\t\t"); + db_print(); + addBlocker(blocker); + } +} + + +int EdgeInf::firstBlocker(void) +{ + ShapeSet ss = ShapeSet(); + + Point& pti = _v1->point; + Point& ptj = _v2->point; + VertID& iID = _v1->id; + VertID& jID = _v2->id; + + if (!(iID.isShape)) + { + ss.insert(contains[iID].begin(), contains[iID].end()); + } + if (!(jID.isShape)) + { + ss.insert(contains[jID].begin(), contains[jID].end()); + } + + VertInf *last = vertices.end(); + for (VertInf *k = vertices.shapesBegin(); k != last; ) + { + VertID kID = k->id; + if ((ss.find(kID.objID) != ss.end())) + { + uint shapeID = kID.objID; + db_printf("Endpoint is inside shape %u so ignore shape edges.\n", + kID.objID); + // One of the endpoints is inside this shape so ignore it. + while ((k != last) && (k->id.objID == shapeID)) + { + // And skip the other vertices from this shape. + k = k->lstNext; + } + continue; + } + Point& kPoint = k->point; + Point& kPrevPoint = k->shPrev->point; + + if (segmentIntersect(pti, ptj, kPrevPoint, kPoint)) + { + ss.clear(); + return kID.objID; + } + k = k->lstNext; + } + ss.clear(); + return 0; +} + + +bool EdgeInf::isBetween(VertInf *i, VertInf *j) +{ + if ( ((i == _v1) && (j == _v2)) || + ((i == _v2) && (j == _v1)) ) + { + return true; + } + return false; +} + + +VertInf *EdgeInf::otherVert(VertInf *vert) +{ + assert((vert == _v1) || (vert == _v2)); + + if (vert == _v1) + { + return _v2; + } + return _v1; +} + + +EdgeInf *EdgeInf::checkEdgeVisibility(VertInf *i, VertInf *j, bool knownNew) +{ + EdgeInf *edge = NULL; + + if (knownNew) + { + assert(existingEdge(i, j) == NULL); + edge = new EdgeInf(i, j); + } + else + { + edge = existingEdge(i, j); + if (edge == NULL) + { + edge = new EdgeInf(i, j); + } + } + edge->checkVis(); + if (!(edge->_added) && !InvisibilityGrph) + { + delete edge; + edge = NULL; + } + + return edge; +} + + +EdgeInf *EdgeInf::existingEdge(VertInf *i, VertInf *j) +{ + VertInf *selected = NULL; + + if (i->visListSize <= j->visListSize) + { + selected = i; + } + else + { + selected = j; + } + + EdgeInfList& visList = selected->visList; + EdgeInfList::iterator finish = visList.end(); + for (EdgeInfList::iterator edge = visList.begin(); edge != finish; + ++edge) + { + if ((*edge)->isBetween(i, j)) + { + return (*edge); + } + } + + if (i->invisListSize <= j->invisListSize) + { + selected = i; + } + else + { + selected = j; + } + + EdgeInfList& invisList = selected->invisList; + finish = invisList.end(); + for (EdgeInfList::iterator edge = invisList.begin(); edge != finish; + ++edge) + { + if ((*edge)->isBetween(i, j)) + { + return (*edge); + } + } + + return NULL; +} + + +//=========================================================================== + + +EdgeList::EdgeList() + : _firstEdge(NULL) + , _lastEdge(NULL) + , _count(0) +{ +} + + +void EdgeList::addEdge(EdgeInf *edge) +{ + if (_firstEdge == NULL) + { + assert(_lastEdge == NULL); + + _lastEdge = edge; + _firstEdge = edge; + + edge->lstPrev = NULL; + edge->lstNext = NULL; + } + else + { + assert(_lastEdge != NULL); + + _lastEdge->lstNext = edge; + edge->lstPrev = _lastEdge; + + _lastEdge = edge; + + edge->lstNext = NULL; + } + _count++; +} + + +void EdgeList::removeEdge(EdgeInf *edge) +{ + if (edge->lstPrev) + { + edge->lstPrev->lstNext = edge->lstNext; + } + if (edge->lstNext) + { + edge->lstNext->lstPrev = edge->lstPrev; + } + if (edge == _lastEdge) + { + _lastEdge = edge->lstPrev; + if (edge == _firstEdge) + { + _firstEdge = NULL; + } + } + else if (edge == _firstEdge) + { + _firstEdge = edge->lstNext; + } + + + edge->lstPrev = NULL; + edge->lstNext = NULL; + + _count--; +} + + +EdgeInf *EdgeList::begin(void) +{ + return _firstEdge; +} + + +EdgeInf *EdgeList::end(void) +{ + return NULL; +} + + +// General visibility graph utility functions + + +void newBlockingShape(Polygn *poly, int pid) +{ + // o Check all visibility edges to see if this one shape + // blocks them. + EdgeInf *finish = visGraph.end(); + for (EdgeInf *iter = visGraph.begin(); iter != finish ; ) + { + EdgeInf *tmp = iter; + iter = iter->lstNext; + + if (tmp->getDist() != 0) + { + pair ids(tmp->ids()); + VertID eID1 = ids.first; + VertID eID2 = ids.second; + pair points(tmp->points()); + Point e1 = points.first; + Point e2 = points.second; + bool blocked = false; + + bool ep_in_poly1 = !(eID1.isShape) ? inPoly(*poly, e1) : false; + bool ep_in_poly2 = !(eID2.isShape) ? inPoly(*poly, e2) : false; + if (ep_in_poly1 || ep_in_poly2) + { + // Don't check edges that have a connector endpoint + // and are inside the shape being added. + continue; + } + + for (int pt_i = 0; pt_i < poly->pn; pt_i++) + { + int pt_n = (pt_i == (poly->pn - 1)) ? 0 : pt_i + 1; + if (segmentIntersect(e1, e2, poly->ps[pt_i], poly->ps[pt_n])) + { + blocked = true; + break; + } + } + if (blocked) + { + db_printf("\tRemoving newly blocked edge (by shape %3d)" + "... \n\t\t", pid); + tmp->alertConns(); + tmp->db_print(); + if (InvisibilityGrph) + { + tmp->addBlocker(pid); + } + else + { + delete tmp; + } + } + } + } +} + + +void checkAllBlockedEdges(int pid) +{ + assert(InvisibilityGrph); + + for (EdgeInf *iter = invisGraph.begin(); iter != invisGraph.end() ; ) + { + EdgeInf *tmp = iter; + iter = iter->lstNext; + + if (tmp->hasBlocker(pid)) + { + tmp->checkVis(); + } + } +} + + +void checkAllMissingEdges(void) +{ + assert(!InvisibilityGrph); + + VertInf *first = NULL; + + if (IncludeEndpoints) + { + first = vertices.connsBegin(); + } + else + { + first = vertices.shapesBegin(); + } + + VertInf *pend = vertices.end(); + for (VertInf *i = first; i != pend; i = i->lstNext) + { + VertID iID = i->id; + + // Check remaining, earlier vertices + for (VertInf *j = first ; j != i; j = j->lstNext) + { + VertID jID = j->id; + if (!(iID.isShape) && (iID.objID != jID.objID)) + { + // Don't keep visibility between edges of different conns + continue; + } + + // See if the edge is already there? + bool found = (EdgeInf::existingEdge(i, j) != NULL); + + if (!found) + { + // Didn't already exist, check. + bool knownNew = true; + EdgeInf::checkEdgeVisibility(i, j, knownNew); + } + } + } +} + + +void generateContains(VertInf *pt) +{ + contains[pt->id].clear(); + + ShapeRefList::iterator finish = shapeRefs.end(); + for (ShapeRefList::iterator i = shapeRefs.begin(); i != finish; ++i) + { + Polygn poly = copyPoly(*i); + if (inPoly(poly, pt->point)) + { + contains[pt->id].insert((*i)->id()); + } + freePoly(poly); + } +} + + +void adjustContainsWithAdd(const Polygn& poly, const int p_shape) +{ + for (VertInf *k = vertices.connsBegin(); k != vertices.shapesBegin(); + k = k->lstNext) + { + if (inPoly(poly, k->point)) + { + contains[k->id].insert(p_shape); + } + } +} + + +void adjustContainsWithDel(const int p_shape) +{ + for (VertInf *k = vertices.connsBegin(); k != vertices.shapesBegin(); + k = k->lstNext) + { + contains[k->id].erase(p_shape); + } +} + + +// Maybe this one should be in with the connector stuff, but it may later +// need to operate on a particular section of the visibility graph so it +// may have to stay here. +// +#define MIN(a, b) (((a) <= (b)) ? (a) : (b)) +#define MAX(a, b) (((a) >= (b)) ? (a) : (b)) + +#ifdef SELECTIVE_DEBUG +static double AngleAFromThreeSides(const double a, const double b, + const double c) +{ + // returns angle A, the angle opposite from side a, in radians + return acos((pow(b, 2) + pow(c, 2) - pow(a, 2)) / (2 * b * c)); +} +#endif + +void markConnectors(ShapeRef *shape) +{ + assert(SelectiveReroute); + + ConnRefList::iterator end = connRefs.end(); + for (ConnRefList::iterator it = connRefs.begin(); it != end; ++it) + { + ConnRef *conn = (*it); + + if (conn->_route.pn == 0) + { + // Ignore uninitialised connectors. + continue; + } + + Point start = conn->_route.ps[0]; + Point end = conn->_route.ps[conn->_route.pn - 1]; + + double conndist = conn->_route_dist; + + double estdist; + double e1, e2; + + VertInf *beginV = shape->firstVert(); + VertInf *endV = shape->lastVert()->lstNext; + for (VertInf *i = beginV; i != endV; i = i->lstNext) + { + const Point& p1 = i->point; + const Point& p2 = i->shNext->point; + + double offy; + double a; + double b; + double c; + double d; + + double min; + double max; + + if (p1.y == p2.y) + { + // Standard case + offy = p1.y; + a = start.x; + b = start.y - offy; + c = end.x; + d = end.y - offy; + + min = MIN(p1.x, p2.x); + max = MAX(p1.x, p2.x); + } + else if (p1.x == p2.x) + { + // Other Standard case + offy = p1.x; + a = start.y; + b = start.x - offy; + c = end.y; + d = end.x - offy; + + min = MIN(p1.y, p2.y); + max = MAX(p1.y, p2.y); + } + else + { + // Need to do rotation + Point n_p2 = { p2.x - p1.x, p2.y - p1.y }; + Point n_start = { start.x - p1.x, start.y - p1.y }; + Point n_end = { end.x - p1.x, end.y - p1.y }; + //printf("n_p2: (%.1f, %.1f)\n", n_p2.x, n_p2.y); + //printf("n_start: (%.1f, %.1f)\n", n_start.x, n_start.y); + //printf("n_end: (%.1f, %.1f)\n", n_end.x, n_end.y); + + double theta = 0 - atan2(n_p2.y, n_p2.x); + //printf("theta = %.2f\n", theta * (180 / PI)); + + Point r_p1 = {0, 0}; + Point r_p2 = n_p2; + start = n_start; + end = n_end; + + double cosv = cos(theta); + double sinv = sin(theta); + + r_p2.x = cosv * n_p2.x - sinv * n_p2.y; + r_p2.y = cosv * n_p2.y + sinv * n_p2.x; + start.x = cosv * n_start.x - sinv * n_start.y; + start.y = cosv * n_start.y + sinv * n_start.x; + end.x = cosv * n_end.x - sinv * n_end.y; + end.y = cosv * n_end.y + sinv * n_end.x; + //printf("r_p2: (%.1f, %.1f)\n", r_p2.x, r_p2.y); + //printf("r_start: (%.1f, %.1f)\n", start.x, start.y); + //printf("r_end: (%.1f, %.1f)\n", end.x, end.y); + + if (((int) r_p2.y) != 0) + { + printf("r_p2.y: %f != 0\n", r_p2.y); + abort(); + } + // This might be slightly off. + r_p2.y = 0; + + offy = r_p1.y; + a = start.x; + b = start.y - offy; + c = end.x; + d = end.y - offy; + + min = MIN(r_p1.x, r_p2.x); + max = MAX(r_p1.x, r_p2.x); + + } + + double x; + if ((b + d) == 0) + { + db_printf("WARNING: (b + d) == 0\n"); + d = d * -1; + } + + if ((b == 0) && (d == 0)) + { + db_printf("WARNING: b == d == 0\n"); + if (((a < min) && (c < min)) || + ((a > max) && (c > max))) + { + // It's going to get adjusted. + x = a; + } + else + { + continue; + } + } + else + { + x = ((b*c) + (a*d)) / (b + d); + } + + //printf("%.1f, %.1f, %.1f, %.1f\n", a, b, c, d); + //printf("x = %.1f\n", x); + + // XXX: Use MAX and MIN + x = (x < min) ? min : x; + x = (x > max) ? max : x; + + //printf("x = %.1f\n", x); + + Point xp; + if (p1.x == p2.x) + { + xp.x = offy; + xp.y = x; + } + else + { + xp.x = x; + xp.y = offy; + } + //printf("(%.1f, %.1f)\n", xp.x, xp.y); + + e1 = dist(start, xp); + e2 = dist(xp, end); + estdist = e1 + e2; + + + //printf("is %.1f < %.1f\n", estdist, conndist); + if (estdist < conndist) + { +#ifdef SELECTIVE_DEBUG + //double angle = AngleAFromThreeSides(dist(start, end), + // e1, e2); + printf("[%3d] - Possible better path found (%.1f < %.1f)\n", + conn->_id, estdist, conndist); +#endif + conn->_needs_reroute_flag = true; + break; + } + + } + } +} + + +void printInfo(void) +{ + FILE *fp = stdout; + fprintf(fp, "\nVisibility Graph info:\n"); + fprintf(fp, "----------------------\n"); + + uint currshape = 0; + int st_shapes = 0; + int st_vertices = 0; + int st_endpoints = 0; + int st_valid_shape_visedges = 0; + int st_valid_endpt_visedges = 0; + int st_invalid_visedges = 0; + VertInf *finish = vertices.end(); + for (VertInf *t = vertices.connsBegin(); t != finish; t = t->lstNext) + { + VertID pID = t->id; + + if ((pID.isShape) && (pID.objID != currshape)) + { + currshape = pID.objID; + st_shapes++; + } + if (pID.isShape) + { + st_vertices++; + } + else + { + // The shape 0 ones are temporary and not considered. + st_endpoints++; + } + } + for (EdgeInf *t = visGraph.begin(); t != visGraph.end(); + t = t->lstNext) + { + std::pair idpair = t->ids(); + + if (!(idpair.first.isShape) || !(idpair.second.isShape)) + { + st_valid_endpt_visedges++; + } + else + { + st_valid_shape_visedges++; + } + } + for (EdgeInf *t = invisGraph.begin(); t != invisGraph.end(); + t = t->lstNext) + { + st_invalid_visedges++; + } + fprintf(fp, "Number of shapes: %d\n", st_shapes); + fprintf(fp, "Number of vertices: %d (%d real, %d endpoints)\n", + st_vertices + st_endpoints, st_vertices, st_endpoints); + fprintf(fp, "Number of vis_edges: %d (%d valid [%d normal, %d endpt], " + "%d invalid)\n", st_valid_shape_visedges + st_invalid_visedges + + st_valid_endpt_visedges, st_valid_shape_visedges + + st_valid_endpt_visedges, st_valid_shape_visedges, + st_valid_endpt_visedges, st_invalid_visedges); + fprintf(fp, "----------------------\n"); + fprintf(fp, "checkVisEdge tally: %d\n", st_checked_edges); + fprintf(fp, "----------------------\n"); + + fprintf(fp, "ADDS: "); timers.Print(tmAdd); + fprintf(fp, "DELS: "); timers.Print(tmDel); + fprintf(fp, "MOVS: "); timers.Print(tmMov); + fprintf(fp, "***S: "); timers.Print(tmSev); + fprintf(fp, "PTHS: "); timers.Print(tmPth); + fprintf(fp, "\n"); +} + + +} + + diff --git a/src/libavoid/graph.h b/src/libavoid/graph.h new file mode 100644 index 000000000..d30f394cf --- /dev/null +++ b/src/libavoid/graph.h @@ -0,0 +1,127 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * Copyright (C) 2004-2005 Michael Wybrow + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ + +#ifndef AVOID_GRAPH_H +#define AVOID_GRAPH_H + + +#include +#include +#include +using std::pair; + +#include "libavoid/vertices.h" + + +namespace Avoid { + + +extern bool UseAStarSearch; +extern bool IgnoreRegions; +extern bool SelectiveReroute; +extern bool IncludeEndpoints; +extern bool UseLeesAlgorithm; +extern bool InvisibilityGrph; +extern bool PartialFeedback; + + +typedef std::list ShapeList; +typedef std::list FlagList; + + +class EdgeInf +{ + public: + EdgeInf(VertInf *v1, VertInf *v2); + ~EdgeInf(); + double getDist(void); + void setDist(double dist); + void alertConns(void); + void addConn(bool *flag); + void addCycleBlocker(void); + void addBlocker(int b); + bool hasBlocker(int b); + pair ids(void); + pair points(void); + void db_print(void); + void checkVis(void); + VertInf *otherVert(VertInf *vert); + static EdgeInf *checkEdgeVisibility(VertInf *i, VertInf *j, + bool knownNew = false); + static EdgeInf *existingEdge(VertInf *i, VertInf *j); + + EdgeInf *lstPrev; + EdgeInf *lstNext; + private: + bool _added; + bool _visible; + VertInf *_v1; + VertInf *_v2; + EdgeInfList::iterator _pos1; + EdgeInfList::iterator _pos2; + ShapeList _blockers; + FlagList _conns; + double _dist; + + void makeActive(void); + void makeInactive(void); + int firstBlocker(void); + bool isBetween(VertInf *i, VertInf *j); +}; + + +class EdgeList +{ + public: + EdgeList(); + void addEdge(EdgeInf *edge); + void removeEdge(EdgeInf *edge); + EdgeInf *begin(void); + EdgeInf *end(void); + private: + EdgeInf *_firstEdge; + EdgeInf *_lastEdge; + unsigned int _count; +}; + + +extern EdgeList visGraph; +extern EdgeList invisGraph; + +class ShapeRef; + +extern void newBlockingShape(Polygn *poly, int pid); +extern void checkAllBlockedEdges(int pid); +extern void checkAllMissingEdges(void); +extern void generateContains(VertInf *pt); +extern void adjustContainsWithAdd(const Polygn& poly, const int p_shape); +extern void adjustContainsWithDel(const int p_shape); +extern void markConnectors(ShapeRef *shape); +extern void printInfo(void); + + +} + + +#endif + + diff --git a/src/libavoid/incremental.cpp b/src/libavoid/incremental.cpp new file mode 100644 index 000000000..3830c70ee --- /dev/null +++ b/src/libavoid/incremental.cpp @@ -0,0 +1,139 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * Copyright (C) 2004-2005 Michael Wybrow + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ + +#include "libavoid/connector.h" +#include "libavoid/graph.h" +#include "libavoid/visibility.h" + +namespace Avoid { + + +void addShape(ShapeRef *shape) +{ + uint pid = shape->id(); + Polygn poly = shape->poly(); + + adjustContainsWithAdd(poly, pid); + + // o Check all visibility edges to see if this one shape + // blocks them. + newBlockingShape(&poly, pid); + + // o Calculate visibility for the new vertices. + if (UseLeesAlgorithm) + { + shapeVisSweep(shape); + } + else + { + shapeVis(shape); + } + callbackAllInvalidConnectors(); +} + + +void delShape(ShapeRef *shape) +{ + uint pid = shape->id(); + + // o Remove entries related to this shape's vertices + shape->removeFromGraph(); + + if (SelectiveReroute) + { + markConnectors(shape); + } + + adjustContainsWithDel(pid); + + delete shape; + + // o Check all edges that were blocked by this shape. + if (InvisibilityGrph) + { + checkAllBlockedEdges(pid); + } + else + { + // check all edges not in graph + checkAllMissingEdges(); + } + callbackAllInvalidConnectors(); +} + + +ShapeRef *moveShape(ShapeRef *oldShape, Polygn *newPoly) +{ + uint pid = oldShape->id(); + + // o Remove entries related to this shape's vertices + oldShape->removeFromGraph(); + + if (SelectiveReroute && !(PartialFeedback && PartialTime)) + { + markConnectors(oldShape); + } + + adjustContainsWithDel(pid); + + delete oldShape; + oldShape = NULL; + + adjustContainsWithAdd(*newPoly, pid); + + // o Check all edges that were blocked by this shape. + if (InvisibilityGrph) + { + checkAllBlockedEdges(pid); + } + else + { + // check all edges not in graph + checkAllMissingEdges(); + } + + ShapeRef *newShape = new ShapeRef(pid, *newPoly); + + // o Check all visibility edges to see if this one shape + // blocks them. + if (!(PartialFeedback && PartialTime)) + { + newBlockingShape(newPoly, pid); + } + + // o Calculate visibility for the new vertices. + if (UseLeesAlgorithm) + { + shapeVisSweep(newShape); + } + else + { + shapeVis(newShape); + } + callbackAllInvalidConnectors(); + + return newShape; +} + + +} + diff --git a/src/libavoid/incremental.h b/src/libavoid/incremental.h new file mode 100644 index 000000000..50ef8f7dc --- /dev/null +++ b/src/libavoid/incremental.h @@ -0,0 +1,39 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * Copyright (C) 2004-2005 Michael Wybrow + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ + + +#ifndef AVOID_INCREMENTAL_H +#define AVOID_INCREMENTAL_H + +#include "libavoid/shape.h" + + +namespace Avoid { + +extern void addShape(ShapeRef *shape); +extern void delShape(ShapeRef *shape); +extern ShapeRef *moveShape(ShapeRef *oldShape, Polygn *newPoly); + +} + + +#endif diff --git a/src/libavoid/libavoid.h b/src/libavoid/libavoid.h new file mode 100644 index 000000000..0b6c7a819 --- /dev/null +++ b/src/libavoid/libavoid.h @@ -0,0 +1,40 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * Copyright (C) 2004-2005 Michael Wybrow + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ + +#ifndef AVOID_LIBAVOID_H +#define AVOID_LIBAVOID_H + +#include "libavoid/geomtypes.h" +#include "libavoid/polyutil.h" +#include "libavoid/connector.h" +#include "libavoid/graph.h" +#include "libavoid/debug.h" +#include "libavoid/timer.h" +#include "libavoid/makepath.h" +#include "libavoid/vertices.h" +#include "libavoid/visibility.h" +#include "libavoid/static.h" +#include "libavoid/incremental.h" + +#endif + + diff --git a/src/libavoid/makefile.in b/src/libavoid/makefile.in new file mode 100644 index 000000000..e651e0ef1 --- /dev/null +++ b/src/libavoid/makefile.in @@ -0,0 +1,17 @@ +# Convenience stub makefile to call the real Makefile. + +@SET_MAKE@ + +# Explicit so that it's the default rule. +all: + cd .. && $(MAKE) libavoid/all + +clean %.a %.o: + cd .. && $(MAKE) libavoid/$@ + +.PHONY: all clean + +OBJEXT = @OBJEXT@ + +.SUFFIXES: +.SUFFIXES: .a .$(OBJEXT) diff --git a/src/libavoid/makepath.cpp b/src/libavoid/makepath.cpp new file mode 100644 index 000000000..776ffd307 --- /dev/null +++ b/src/libavoid/makepath.cpp @@ -0,0 +1,462 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * Copyright (C) 2004-2005 Michael Wybrow + * + * -------------------------------------------------------------------- + * The dijkstraPath function is based on code published and described + * in "Algorithms in C" (Second Edition), 1990, by Robert Sedgewick. + * -------------------------------------------------------------------- + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ + +#include "libavoid/connector.h" +#include "libavoid/graph.h" +#include +#include + +namespace Avoid { + + +double segmt_penalty = 0; +double angle_penalty = 0; + + +static double Dot(const Point& l, const Point& r) +{ + return (l.x * r.x) + (l.y * r.y); +} + +static double CrossLength(const Point& l, const Point& r) +{ + return (l.x * r.y) - (l.y * r.x); +} + + +// Return the angle between the two line segments made by the +// points p1--p2 and p2--p3. Return value is in radians. +// +static double angleBetween(const Point& p1, const Point& p2, const Point& p3) +{ + Point v1 = { p1.x - p2.x, p1.y - p2.y }; + Point v2 = { p3.x - p2.x, p3.y - p2.y }; + + return fabs(atan2(CrossLength(v1, v2), Dot(v1, v2))); +} + + +// Given the two points for a new segment of a path (inf2 & inf3) +// as well as the distance between these points (dist), as well as +// possibly the previous point (inf1) [from inf1--inf2], return a +// cost associated with this route. +// +double cost(const double dist, VertInf *inf1, + VertInf *inf2, VertInf *inf3) +{ + double result = dist; + + if (inf2->pathNext != NULL) + { + // This is not the first segment, so there is a bend + // between it and the last one in the existing path. + if ((angle_penalty > 0) || (segmt_penalty > 0)) + { + Point p1 = inf1->point; + Point p2 = inf2->point; + Point p3 = inf3->point; + + double rad = M_PI - angleBetween(p1, p2, p3); + + // Make `rad' between 0--10 then take its log so small + // angles are not penalised as much as large ones. + result += (angle_penalty * log((rad * 10 / M_PI) + 1)); + + // Don't penalise as an extra segment if there is no turn. + if (rad > 0.0005) + { + result += segmt_penalty; + } + } + + } + + return result; +} + + +// Returns the best path from src to tar using the cost function. +// +// The path is worked out via Dijkstra's algorithm, and is encoded via +// pathNext links in each of the VerInfs along the path. +// +// Based on the code of 'matrixpfs'. +// +static void dijkstraPath(VertInf *src, VertInf *tar) +{ + double unseen = (double) INT_MAX; + + // initialize arrays + VertInf *finish = vertices.end(); + for (VertInf *t = vertices.connsBegin(); t != finish; t = t->lstNext) + { + t->pathNext = NULL; + t->pathDist = -unseen; + } + + VertInf *min = src; + while (min != tar) + { + VertInf *k = min; + min = NULL; + + k->pathDist *= -1; + if (k->pathDist == unseen) + { + k->pathDist = 0; + } + + EdgeInfList& visList = k->visList; + EdgeInfList::iterator finish = visList.end(); + for (EdgeInfList::iterator edge = visList.begin(); edge != finish; + ++edge) + { + VertInf *t = (*edge)->otherVert(k); + VertID tID = t->id; + + // Only check shape verticies, or endpoints. + if ((t->pathDist < 0) && + ((tID.objID == src->id.objID) || tID.isShape)) + { + double kt_dist = (*edge)->getDist(); + double priority = k->pathDist + + cost(kt_dist, k->pathNext, k, t); + + if ((kt_dist != 0) && (t->pathDist < -priority)) + { + t->pathDist = -priority; + t->pathNext = k; + } + if ((min == NULL) || (t->pathDist > min->pathDist)) + { + min = t; + } + } + } + EdgeInfList& invisList = k->invisList; + finish = invisList.end(); + for (EdgeInfList::iterator edge = invisList.begin(); edge != finish; + ++edge) + { + VertInf *t = (*edge)->otherVert(k); + VertID tID = t->id; + + // Only check shape verticies, or endpoints. + if ((t->pathDist < 0) && + ((tID.objID == src->id.objID) || tID.isShape > 0)) + { + if ((min == NULL) || (t->pathDist > min->pathDist)) + { + min = t; + } + } + } + } +} + + +class ANode +{ + public: + VertInf* inf; + double g; // Gone + double h; // Heuristic + double f; // Formula f = g + h + VertInf *pp; + + ANode(VertInf *vinf) + : inf(vinf) + , g(0) + , h(0) + , f(0) + , pp(NULL) + { + } + ANode() + : inf(NULL) + , g(0) + , h(0) + , f(0) + , pp(NULL) + { + } +}; + +bool operator<(const ANode &a, const ANode &b) +{ + return a.f < b.f; +} + + +bool operator>(const ANode &a, const ANode &b) +{ + return a.f > b.f; +} + + +// Returns the best path from src to tar using the cost function. +// +// The path is worked out using the aStar algorithm, and is encoded via +// pathNext links in each of the VerInfs along the path. +// +// The aStar STL code is based on public domain code available on the +// internet. +// +static void aStarPath(VertInf *src, VertInf *tar) +{ + std::vector PENDING; // STL Vectors chosen because of rapid + std::vector DONE; // insertions/deletions at back, + ANode Node, BestNode; // Temporary Node and BestNode + bool bNodeFound = false; // Flag if node is found in container + + tar->pathNext = NULL; + + // Create the start node + Node = ANode(src); + Node.g = 0; + Node.h = dist(Node.inf->point, tar->point); + Node.f = Node.g + Node.h; + // Set a null parent, so cost function knows this is the first segment. + Node.pp = NULL; + + // Populate the PENDING container with the first location + PENDING.push_back(Node); + // Create a heap from PENDING for sorting + using std::make_heap; using std::push_heap; using std::pop_heap; + make_heap( PENDING.begin(), PENDING.end() ); + + while (!PENDING.empty()) + { + // Ascending sort based on overloaded operators below + sort_heap(PENDING.begin(), PENDING.end()); + + // Set the Node with lowest f value to BESTNODE + BestNode = PENDING.front(); + + // Pop off the heap. Actually this moves the + // far left value to the far right. The node + // is not actually removed since the pop is to + // the heap and not the container. + pop_heap(PENDING.begin(), PENDING.end()); + + + // Remove node from right (the value we pop_heap'd) + PENDING.pop_back(); + + // Push the BestNode onto DONE + BestNode.inf->pathNext = BestNode.pp; + DONE.push_back(BestNode); + +#if 0 + printf("Considering... "); + BestNode.ID->print(stdout); + printf(" - g: %3.1f h: %3.1f f: %3.1f back: ", BestNode.g, BestNode.h, + BestNode.f); + BestNode.pp.print(stdout); + printf("\n"); +#endif + + // If at destination, break and create path below + if (BestNode.inf == tar) + { + //bPathFound = true; // arrived at destination... + break; + } + + // Check adjacent points in graph + EdgeInfList& visList = BestNode.inf->visList; + EdgeInfList::iterator finish = visList.end(); + for (EdgeInfList::iterator edge = visList.begin(); edge != finish; + ++edge) + { + Node.inf = (*edge)->otherVert(BestNode.inf); + + // Only check shape verticies, or the tar endpoint. + if (!(Node.inf->id.isShape) && (Node.inf != tar)) + { + continue; + } + + double edgeDist = (*edge)->getDist(); + + if (edgeDist == 0) + { + continue; + } + + VertInf *prevInf = BestNode.inf->pathNext; + + Node.g = BestNode.g + cost(edgeDist, prevInf, + BestNode.inf, Node.inf); + + // Calculate the Heuristic. + Node.h = dist(Node.inf->point, tar->point); + + // The A* formula + Node.f = Node.g + Node.h; + // Point parent to last BestNode (pushed onto DONE) + Node.pp = BestNode.inf; + + bNodeFound = false; + + // Check to see if already on PENDING + for (unsigned int i = 0; i < PENDING.size(); i++) + { + if (Node.inf == PENDING.at(i).inf) + { // If already on PENDING + if (Node.g < PENDING.at(i).g) + { + PENDING.at(i).g = Node.g; + PENDING.at(i).f = Node.g + PENDING.at(i).h; + PENDING.at(i).pp = Node.pp; + } + bNodeFound = true; + break; + } + } + if (!bNodeFound ) // If Node NOT found on PENDING + { + // Check to see if already on DONE + for (unsigned int i = 0; i < DONE.size(); i++) + { + if (Node.inf == DONE.at(i).inf) + { + // If on DONE, Which has lower gone? + if (Node.g < DONE.at(i).g) + { + DONE.at(i).g = Node.g; + DONE.at(i).f = Node.g + DONE.at(i).h; + DONE.at(i).pp = Node.pp; + DONE.at(i).inf->pathNext = Node.pp; + } + bNodeFound = true; + break; + } + } + } + + if (!bNodeFound ) // If Node NOT found on PENDING or DONE + { + // Push NewNode onto PENDING + PENDING.push_back(Node); + // Push NewNode onto heap + push_heap( PENDING.begin(), PENDING.end() ); + // Re-Assert heap, or will be short by one + make_heap( PENDING.begin(), PENDING.end() ); + +#if 0 + // Display PENDING and DONE containers (For Debugging) + cout << "PENDING: "; + for (int i = 0; i < PENDING.size(); i++) + { + cout << PENDING.at(i).x << "," << PENDING.at(i).y << ","; + cout << PENDING.at(i).g << "," << PENDING.at(i).h << " "; + } + cout << endl; + cout << "DONE: "; + for (int i = 0; i < DONE.size(); i++) + { + cout << DONE.at(i).x << "," << DONE.at(i).y << ","; + cout << DONE.at(i).g << "," << DONE.at(i).h << " "; + } + cout << endl << endl; + int ch = _getch(); +#endif + } + } + } +} + + +// Returns the best path for the connector referred to by lineRef. +// +// The path encoded in the pathNext links in each of the VerInfs +// backwards along the path, from the tar back to the source. +// +void makePath(ConnRef *lineRef, bool *flag) +{ + VertInf *src = lineRef->src(); + VertInf *tar = lineRef->dst(); + + // TODO: Could be more efficient here. + EdgeInf *directEdge = EdgeInf::existingEdge(src, tar); + if (!IncludeEndpoints && directVis(src, tar)) + { + Point p = src->point; + Point q = tar->point; + + assert(directEdge == NULL); + + directEdge = new EdgeInf(src, tar); + tar->pathNext = src; + directEdge->setDist(dist(p, q)); + directEdge->addConn(flag); + + return; + } + else if (IncludeEndpoints && directEdge && (directEdge->getDist() > 0)) + { + tar->pathNext = src; + directEdge->addConn(flag); + } + else + { + // Mark the path endpoints as not being able to see + // each other. This is true if we are here. + if (!IncludeEndpoints && InvisibilityGrph) + { + directEdge->addBlocker(0); + } + + if (UseAStarSearch) + { + aStarPath(src, tar); + } + else + { + dijkstraPath(src, tar); + } + +#if 0 + PointMap::iterator t; + for (VertInf *t = vertices.connsBegin(); t != vertices.end(); + t = t->lstNext) + { + + t->id.print(); + printf(" -> "); + t->pathNext->id.print(); + printf("\n"); + } +#endif + } +} + + +} + + diff --git a/src/libavoid/makepath.h b/src/libavoid/makepath.h new file mode 100644 index 000000000..5ab21b993 --- /dev/null +++ b/src/libavoid/makepath.h @@ -0,0 +1,42 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * Copyright (C) 2004-2005 Michael Wybrow + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ + +#ifndef AVOID_MAKEPATH_H +#define AVOID_MAKEPATH_H + +#include "libavoid/connector.h" + +namespace Avoid { + + +extern double segmt_penalty; +extern double angle_penalty; + +extern void makePath(ConnRef *lineRef, bool *flag); + + +} + + +#endif + + diff --git a/src/libavoid/polyutil.cpp b/src/libavoid/polyutil.cpp new file mode 100644 index 000000000..6ece50e63 --- /dev/null +++ b/src/libavoid/polyutil.cpp @@ -0,0 +1,86 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * Copyright (C) 2004-2005 Michael Wybrow + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ + + +#include "libavoid/shape.h" + +namespace Avoid { + + +Polygn newPoly(int size) +{ + Polygn newpoly; + + newpoly.pn = size; + newpoly.ps = (Point *) calloc(size, sizeof(Point)); + if (!newpoly.ps) + { + fprintf(stderr, + "Error: Unable to allocate Point array in Avoid::newPoly\n"); + abort(); + } + return newpoly; +} + + +Polygn copyPoly(Polygn poly) +{ + Polygn newpoly = newPoly(poly.pn); + + newpoly.id = poly.id; + for (int i = 0; i < poly.pn; i++) + { + newpoly.ps[i] = poly.ps[i]; + } + return newpoly; +} + + +Polygn copyPoly(ShapeRef *shape) +{ + Polygn poly = shape->poly(); + Polygn newpoly = newPoly(poly.pn); + + newpoly.id = poly.id; + for (int i = 0; i < poly.pn; i++) + { + newpoly.ps[i] = poly.ps[i]; + } + return newpoly; +} + + +void freePoly(Polygn& poly) +{ + std::free(poly.ps); +} + + +void freePtrPoly(Polygn *poly) +{ + std::free(poly->ps); + std::free(poly); +} + + +} + diff --git a/src/libavoid/polyutil.h b/src/libavoid/polyutil.h new file mode 100644 index 000000000..456e190fb --- /dev/null +++ b/src/libavoid/polyutil.h @@ -0,0 +1,41 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * Copyright (C) 2004-2005 Michael Wybrow + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ + +#include "libavoid/geometry.h" +#include "libavoid/shape.h" + +#ifndef AVOID_POLYUTIL_H +#define AVOID_POLYUTIL_H + +namespace Avoid { + + +extern Polygn newPoly(int size); +extern Polygn copyPoly(Polygn); +extern Polygn copyPoly(ShapeRef *shape); +extern void freePoly(Polygn&); +extern void freePtrPoly(Polygn *argpoly); + + +} + +#endif diff --git a/src/libavoid/shape.cpp b/src/libavoid/shape.cpp new file mode 100644 index 000000000..b08e75f3e --- /dev/null +++ b/src/libavoid/shape.cpp @@ -0,0 +1,176 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * Copyright (C) 2004-2005 Michael Wybrow + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ + + +#include "libavoid/graph.h" // For alertConns +#include "libavoid/polyutil.h" + + +namespace Avoid { + + +ShapeRefList shapeRefs; + + +ShapeRef::ShapeRef(uint id, Polygn& ply) + : _id(id) + , _poly(copyPoly(ply)) + , _active(false) + , _firstVert(NULL) + , _lastVert(NULL) +{ + bool isShape = true; + VertID i = VertID(id, isShape, 0); + + VertInf *last = NULL; + VertInf *node = NULL; + for (int pt_i = 0; pt_i < _poly.pn; pt_i++) + { + node = new VertInf(i, _poly.ps[pt_i]); + + if (!_firstVert) + { + _firstVert = node; + } + else + { + node->shPrev = last; + last->shNext = node; + //node->lstPrev = last; + //last->lstNext = node; + } + vertices.addVertex(node); + + last = node; + i++; + // Increase total vertices count ++; + } + _lastVert = node; + + _lastVert->shNext = _firstVert; + _firstVert->shPrev = _lastVert; + + // Increase total shape count ++; + makeActive(); +} + + +ShapeRef::~ShapeRef() +{ + assert(_firstVert != NULL); + + VertInf *it = _firstVert; + do + { + VertInf *tmp = it; + it = it->shNext; + + // XXX: This could possibly be done less + // safely but faster, all at once. + vertices.removeVertex(tmp); + delete tmp; + } + while (it != _firstVert); + _firstVert = _lastVert = NULL; + + freePoly(_poly); + + makeInactive(); +} + + +void ShapeRef::makeActive(void) +{ + assert(!_active); + + // Add to connRefs list. + _pos = shapeRefs.insert(shapeRefs.begin(), this); + _active = true; +} + + +void ShapeRef::makeInactive(void) +{ + assert(_active); + + // Remove from connRefs list. + shapeRefs.erase(_pos); + _active = false; +} + + +VertInf *ShapeRef::firstVert(void) +{ + return _firstVert; +} + + +VertInf *ShapeRef::lastVert(void) +{ + return _lastVert; +} + + +uint ShapeRef::id(void) +{ + return _id; +} + + +Polygn ShapeRef::poly(void) +{ + return _poly; +} + + +void ShapeRef::removeFromGraph(void) +{ + for (VertInf *iter = firstVert(); iter != lastVert()->lstNext; ) + { + VertInf *tmp = iter; + iter = iter->lstNext; + + // For each vertex. + EdgeInfList& visList = tmp->visList; + EdgeInfList::iterator finish = visList.end(); + EdgeInfList::iterator edge; + while ((edge = visList.begin()) != finish) + { + // Remove each visibility edge + (*edge)->alertConns(); + delete (*edge); + } + + EdgeInfList& invisList = tmp->invisList; + finish = invisList.end(); + while ((edge = invisList.begin()) != finish) + { + // Remove each invisibility edge + delete (*edge); + } + } +} + + +} + + diff --git a/src/libavoid/shape.h b/src/libavoid/shape.h new file mode 100644 index 000000000..acdb36983 --- /dev/null +++ b/src/libavoid/shape.h @@ -0,0 +1,73 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * Copyright (C) 2004-2005 Michael Wybrow + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ + +#ifndef AVOID_SHAPE_H +#define AVOID_SHAPE_H + +#include "libavoid/geometry.h" +#include + + +namespace Avoid { + +typedef unsigned int uint; + +class ShapeRef; +class VertInf; + +typedef std::list ShapeRefList; + + +class ShapeRef +{ + public: + ShapeRef(uint id, Polygn& poly); + ~ShapeRef(); + VertInf *firstVert(void); + VertInf *lastVert(void); + uint id(void); + Polygn poly(void); + + void makeActive(void); + void makeInactive(void); + + void removeFromGraph(void); + + private: + uint _id; + Polygn _poly; + bool _active; + ShapeRefList::iterator _pos; + VertInf *_firstVert; + VertInf *_lastVert; +}; + + +extern ShapeRefList shapeRefs; + + +} + + +#endif + + diff --git a/src/libavoid/static.cpp b/src/libavoid/static.cpp new file mode 100644 index 000000000..d424a2e7f --- /dev/null +++ b/src/libavoid/static.cpp @@ -0,0 +1,76 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * Copyright (C) 2004-2005 Michael Wybrow + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ + +#include +#include "libavoid/connector.h" +#include "libavoid/visibility.h" + +namespace Avoid { + + +// This should only be used for the static algorithm. +// +// XXX: If to set up the vis graph for incremental it would need +// the shapeRef ppinters in obs. +// +void CreateVisGraph(Polygn **obs, int n_obs) +{ + for (int poly_i = 0; poly_i < n_obs; poly_i++) + { + uint id = obs[poly_i]->id; + + new ShapeRef(id, *(obs[poly_i])); + } + computeCompleteVis(); +} + + +void DestroyVisGraph(void) +{ + ShapeRefList::iterator sFinish = shapeRefs.end(); + ShapeRefList::iterator sCurr; + + while ((sCurr = shapeRefs.begin()) != sFinish) + { + ShapeRef *shape = (*sCurr); + + shape->removeFromGraph(); + delete shape; + } + + ConnRefList::iterator cFinish = connRefs.end(); + ConnRefList::iterator cCurr; + + while ((cCurr = connRefs.begin())!= cFinish) + { + ConnRef *conn = (*cCurr); + + conn->removeFromGraph(); + conn->unInitialise(); + } + + assert(vertices.connsBegin() == NULL); +} + + +} + diff --git a/src/libavoid/static.h b/src/libavoid/static.h new file mode 100644 index 000000000..c02c6be2f --- /dev/null +++ b/src/libavoid/static.h @@ -0,0 +1,38 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * Copyright (C) 2004-2005 Michael Wybrow + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ + + +#ifndef AVOID_STATIC_H +#define AVOID_STATIC_H + +#include "libavoid/geomtypes.h" + + +namespace Avoid { + +extern void CreateVisGraph(Polygn **obstacles, int n_obstacles); +extern void DestroyVisGraph(void); + +} + + +#endif diff --git a/src/libavoid/timer.cpp b/src/libavoid/timer.cpp new file mode 100644 index 000000000..7e930d011 --- /dev/null +++ b/src/libavoid/timer.cpp @@ -0,0 +1,168 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * Copyright (C) 2004-2005 Michael Wybrow + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ + +#include +#include +#include +using std::abort; +#include + +#include "libavoid/timer.h" + +namespace Avoid { + + +Timer timers = Timer(); + + +Timer::Timer() +{ + Reset(); +} + + +void Timer::Reset(void) +{ + for (int i = 0; i < tmCount; i++) + { + //tTotal[i] = 0; + cTotal[i] = cPath[i] = 0; + cTally[i] = cPathTally[i] = 0; + cMax[i] = cPathMax[i] = 0; + } + running = false; + count = 0; + type = lasttype = tmNon; +} + + +void Timer::Register(const int t, const bool start) +{ + assert(t != tmNon); + + if (type == tmNon) + { + type = t; + } + else + { + type = tmSev; + } + + if (start) + { + Start(); + } +} + +void Timer::Start(void) +{ + if (running) + { + fprintf(stderr, "ERROR: Timer already running in Timer::Start()\n"); + abort(); + } + cStart[type] = clock(); // CPU time + running = true; +} + + +void Timer::Stop(void) +{ + if (!running) + { + fprintf(stderr, "ERROR: Timer not running in Timer::Stop()\n"); + abort(); + } + clock_t cStop = clock(); // CPU time + running = false; + + bigclock_t cDiff; + if (cStop < cStart[type]) + { + // Uh-oh, the clock value has wrapped around. + // + bigclock_t realStop = ((bigclock_t) cStop) + ULONG_MAX + 1; + cDiff = realStop - cStart[type]; + } + else + { + cDiff = cStop - cStart[type]; + } + + if (cDiff > LONG_MAX) + { + fprintf(stderr, "Error: cDiff overflow in Timer:Stop()\n"); + abort(); + } + + if (type == tmPth) + { + cPath[lasttype] += cDiff; + cPathTally[lasttype]++; + if (((clock_t) cDiff) > cPathMax[lasttype]) + { + cPathMax[lasttype] = (clock_t) cDiff; + } + } + else + { + cTotal[type] += cDiff; + cTally[type]++; + if (((clock_t) cDiff) > cMax[type]) + { + cMax[type] = (clock_t) cDiff; + } + lasttype = type; + } + + type = tmNon; +} + + +void Timer::PrintAll(void) +{ + for (int i = 0; i < tmCount; i++) + { + Print(i); + } +} + + +#define toMsec(tot) ((bigclock_t) ((tot) / (((double) CLOCKS_PER_SEC) / 1000))) +#define toAvg(tot, cnt) ((((cnt) > 0) ? ((long double) (tot)) / (cnt) : 0)) + +void Timer::Print(const int t) +{ + bigclock_t avg = toMsec(toAvg(cTotal[t], cTally[t])); + bigclock_t pind = toMsec(toAvg(cPath[t], cPathTally[t])); + bigclock_t pavg = toMsec(toAvg(cPath[t], cTally[t])); + double max = toMsec(cMax[t]); + double pmax = toMsec(cPathMax[t]); + printf("\t%lld %d %lld %.0f %lld %d %lld %.0f %lld\n", + cTotal[t], cTally[t], avg, max, + cPath[t], cPathTally[t], pavg, pmax, pind); +} + + +} + diff --git a/src/libavoid/timer.h b/src/libavoid/timer.h new file mode 100644 index 000000000..9446ecc51 --- /dev/null +++ b/src/libavoid/timer.h @@ -0,0 +1,92 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * Copyright (C) 2004-2005 Michael Wybrow + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ + +#ifndef PROFILE_H +#define PROFILE_H + +#include + +namespace Avoid { + + +#ifdef NOTIMERS + + #define register_timer(t) do {} while(0) + #define regstart_timer(t) do {} while(0) + #define start_timer() do {} while(0) + #define stop_timer() do {} while(0) + +#else + + #define register_timer(t) timers.Register(t) + #define regstart_timer(t) timers.Register(t, timerStart) + #define start_timer() timers.Start() + #define stop_timer() timers.Stop() + +#endif + +typedef unsigned long long int bigclock_t; + +static const int tmCount = 5; + +static const int tmNon = -1; +static const int tmAdd = 0; +static const int tmDel = 1; +static const int tmMov = 2; +static const int tmPth = 3; +static const int tmSev = 4; + + +static const bool timerStart = true; +static const bool timerDelay = false; + + +class Timer +{ + public: + Timer(); + void Register(const int t, const bool start = timerDelay); + void Start(void); + void Stop(void); + void Reset(void); + void Print(const int t); + void PrintAll(void); + + private: + clock_t cStart[tmCount]; + bigclock_t cTotal[tmCount]; + bigclock_t cPath[tmCount]; + int cTally[tmCount]; + int cPathTally[tmCount]; + clock_t cMax[tmCount]; + clock_t cPathMax[tmCount]; + + bool running; + long count; + int type, lasttype; +}; + +extern Timer timers; + +} + +#endif diff --git a/src/libavoid/vertices.cpp b/src/libavoid/vertices.cpp new file mode 100644 index 000000000..7e74509f0 --- /dev/null +++ b/src/libavoid/vertices.cpp @@ -0,0 +1,438 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * Copyright (C) 2004-2005 Michael Wybrow + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ + +#include "libavoid/geometry.h" +#include "libavoid/graph.h" // For alertConns +#include "libavoid/debug.h" + + + +namespace Avoid { + + +ContainsMap contains; + + +VertID::VertID() +{ +} + + +VertID::VertID(unsigned int id, bool s, int n) + : objID(id) + , isShape(s) + , vn(n) +{ +} + + +VertID::VertID(const VertID& other) + : objID(other.objID) + , isShape(other.isShape) + , vn(other.vn) +{ +} + + +VertID& VertID::operator= (const VertID& rhs) +{ + // Gracefully handle self assignment + //if (this == &rhs) return *this; + + objID = rhs.objID; + isShape = rhs.isShape; + vn = rhs.vn; + + return *this; +} + + +bool VertID::operator==(const VertID& rhs) const +{ + if ((objID != rhs.objID) || (vn != rhs.vn)) + { + return false; + } + assert(isShape == rhs.isShape); + return true; +} + + +bool VertID::operator!=(const VertID& rhs) const +{ + if ((objID != rhs.objID) || (vn != rhs.vn)) + { + return true; + } + assert(isShape == rhs.isShape); + return false; +} + + +bool VertID::operator<(const VertID& rhs) const +{ + if ((objID < rhs.objID) || + ((objID == rhs.objID) && (vn < rhs.vn))) + { + return true; + } + return false; +} + + +VertID VertID::operator+(const int& rhs) const +{ + return VertID(objID, isShape, vn + rhs); +} + + +VertID VertID::operator-(const int& rhs) const +{ + return VertID(objID, isShape, vn - rhs); +} + + +VertID& VertID::operator++(int) +{ + vn += 1; + return *this; +} + + +void VertID::print(FILE *file) const +{ + fprintf(file, "[%u,%d]", objID, vn); +} + + +void VertID::db_print(void) const +{ + db_printf("[%u,%d]", objID, vn); +} + + +const int VertID::src = 1; +const int VertID::tar = 2; + + +VertInf::VertInf(const VertID& vid, const Point& vpoint) + : id(vid) + , point(vpoint) + , lstPrev(NULL) + , lstNext(NULL) + , shPrev(NULL) + , shNext(NULL) + , visListSize(0) + , invisListSize(0) + , pathNext(NULL) + , pathDist(0) +{ +} + + +void VertInf::Reset(const Point& vpoint) +{ + point = vpoint; +} + + +void VertInf::removeFromGraph(const bool isConnVert) +{ + if (isConnVert) + { + assert(!(id.isShape)); + } + + VertInf *tmp = this; + + // For each vertex. + EdgeInfList& visList = tmp->visList; + EdgeInfList::iterator finish = visList.end(); + EdgeInfList::iterator edge; + while ((edge = visList.begin()) != finish) + { + // Remove each visibility edge + (*edge)->alertConns(); + delete (*edge); + } + + EdgeInfList& invisList = tmp->invisList; + finish = invisList.end(); + while ((edge = invisList.begin()) != finish) + { + // Remove each invisibility edge + delete (*edge); + } +} + + +bool directVis(VertInf *src, VertInf *dst) +{ + ShapeSet ss = ShapeSet(); + + Point& p = src->point; + Point& q = dst->point; + + VertID& pID = src->id; + VertID& qID = dst->id; + + if (!(pID.isShape)) + { + ss.insert(contains[pID].begin(), contains[pID].end()); + } + if (!(qID.isShape)) + { + ss.insert(contains[qID].begin(), contains[qID].end()); + } + + // The "beginning" should be the first shape vertex, rather + // than an endpoint, which are also stored in "vertices". + VertInf *endVert = vertices.end(); + for (VertInf *k = vertices.shapesBegin(); k != endVert; k = k->lstNext) + { + if ((ss.find(k->id.objID) == ss.end())) + { + if (segmentIntersect(p, q, k->point, k->shNext->point)) + { + return false; + } + } + } + return true; +} + + +VertInfList::VertInfList() + : _firstShapeVert(NULL) + , _firstConnVert(NULL) + , _lastShapeVert(NULL) + , _lastConnVert(NULL) + , _shapeVertices(0) + , _connVertices(0) +{ +} + + +#define checkVertInfListConditions() \ + do { \ + assert((!_firstConnVert && (_connVertices == 0)) || \ + ((_firstConnVert->lstPrev == NULL) && (_connVertices > 0))); \ + assert((!_firstShapeVert && (_shapeVertices == 0)) || \ + ((_firstShapeVert->lstPrev == NULL) && (_shapeVertices > 0))); \ + assert(!_lastShapeVert || (_lastShapeVert->lstNext == NULL)); \ + assert(!_lastConnVert || (_lastConnVert->lstNext == _firstShapeVert)); \ + assert((!_firstConnVert && !_lastConnVert) || \ + (_firstConnVert && _lastConnVert) ); \ + assert((!_firstShapeVert && !_lastShapeVert) || \ + (_firstShapeVert && _lastShapeVert) ); \ + assert(!_firstShapeVert || _firstShapeVert->id.isShape); \ + assert(!_lastShapeVert || _lastShapeVert->id.isShape); \ + assert(!_firstConnVert || !(_firstConnVert->id.isShape)); \ + assert(!_lastConnVert || !(_lastConnVert->id.isShape)); \ + } while(0) + + +void VertInfList::addVertex(VertInf *vert) +{ + checkVertInfListConditions(); + assert(vert->lstPrev == NULL); + assert(vert->lstNext == NULL); + + if (!(vert->id.isShape)) + { + // A Connector vertex + if (_firstConnVert) + { + // Join with previous front + vert->lstNext = _firstConnVert; + _firstConnVert->lstPrev = vert; + + // Make front + _firstConnVert = vert; + } + else + { + // Make front and back + _firstConnVert = vert; + _lastConnVert = vert; + + // Link to front of shapes list + vert->lstNext = _firstShapeVert; + } + _connVertices++; + } + else // if (vert->id.shape > 0) + { + // A Shape vertex + if (_lastShapeVert) + { + // Join with previous back + vert->lstPrev = _lastShapeVert; + _lastShapeVert->lstNext = vert; + + // Make back + _lastShapeVert = vert; + } + else + { + // Make first and last + _firstShapeVert = vert; + _lastShapeVert = vert; + + // Join with conns list + if (_lastConnVert) + { + assert (_lastConnVert->lstNext == NULL); + + _lastConnVert->lstNext = vert; + } + } + _shapeVertices++; + } + checkVertInfListConditions(); +} + + +void VertInfList::removeVertex(VertInf *vert) +{ + // Conditions for correct data structure + checkVertInfListConditions(); + + if (!(vert->id.isShape)) + { + // A Connector vertex + if (vert == _firstConnVert) + { + + if (vert == _lastConnVert) + { + _firstConnVert = NULL; + _lastConnVert = NULL; + } + else + { + // Set new first + _firstConnVert = _firstConnVert->lstNext; + + if (_firstConnVert) + { + // Set previous + _firstConnVert->lstPrev = NULL; + } + } + } + else if (vert == _lastConnVert) + { + // Set new last + _lastConnVert = _lastConnVert->lstPrev; + + // Make last point to shapes list + _lastConnVert->lstNext = _firstShapeVert; + } + else + { + vert->lstNext->lstPrev = vert->lstPrev; + vert->lstPrev->lstNext = vert->lstNext; + } + _connVertices--; + } + else // if (vert->id.shape > 0) + { + // A Shape vertex + if (vert == _lastShapeVert) + { + // Set new last + _lastShapeVert = _lastShapeVert->lstPrev; + + if (vert == _firstShapeVert) + { + _firstShapeVert = NULL; + if (_lastConnVert) + { + _lastConnVert->lstNext = NULL; + } + } + + if (_lastShapeVert) + { + _lastShapeVert->lstNext = NULL; + } + } + else if (vert == _firstShapeVert) + { + // Set new first + _firstShapeVert = _firstShapeVert->lstNext; + + // Correct the last conn vertex + if (_lastConnVert) + { + _lastConnVert->lstNext = _firstShapeVert; + } + + if (_firstShapeVert) + { + _firstShapeVert->lstPrev = NULL; + } + } + else + { + vert->lstNext->lstPrev = vert->lstPrev; + vert->lstPrev->lstNext = vert->lstNext; + } + _shapeVertices--; + } + vert->lstPrev = NULL; + vert->lstNext = NULL; + + checkVertInfListConditions(); +} + + +VertInf *VertInfList::shapesBegin(void) +{ + return _firstShapeVert; +} + + +VertInf *VertInfList::connsBegin(void) +{ + if (_firstConnVert) + { + return _firstConnVert; + } + // No connector vertices + return _firstShapeVert; +} + + +VertInf *VertInfList::end(void) +{ + return NULL; +} + + +VertInfList vertices; + + +} + + diff --git a/src/libavoid/vertices.h b/src/libavoid/vertices.h new file mode 100644 index 000000000..c2ff6977f --- /dev/null +++ b/src/libavoid/vertices.h @@ -0,0 +1,124 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * Copyright (C) 2004-2005 Michael Wybrow + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ + +#ifndef AVOID_VERTICES_H +#define AVOID_VERTICES_H + +#include +#include +#include +#include +#include "libavoid/geomtypes.h" + +namespace Avoid { + +class EdgeInf; + +typedef std::list EdgeInfList; + + +class VertID +{ + public: + unsigned int objID; + bool isShape; + int vn; + + static const int src; + static const int tar; + + VertID(); + VertID(unsigned int id, bool s, int n); + VertID(const VertID& other); + VertID& operator= (const VertID& rhs); + bool operator==(const VertID& rhs) const; + bool operator!=(const VertID& rhs) const; + bool operator<(const VertID& rhs) const; + VertID operator+(const int& rhs) const; + VertID operator-(const int& rhs) const; + VertID& operator++(int); + void print(FILE *file = stdout) const; + void db_print(void) const; +}; + + +class VertInf +{ + public: + VertInf(const VertID& vid, const Point& vpoint); + void Reset(const Point& vpoint); + void removeFromGraph(const bool isConnVert = true); + + VertID id; + Point point; + VertInf *lstPrev; + VertInf *lstNext; + VertInf *shPrev; + VertInf *shNext; + EdgeInfList visList; + unsigned int visListSize; + EdgeInfList invisList; + unsigned int invisListSize; + VertInf *pathNext; + double pathDist; +}; + + +bool directVis(VertInf *src, VertInf *dst); + + +class VertInfList +{ + public: + VertInfList(); + void addVertex(VertInf *vert); + void removeVertex(VertInf *vert); + VertInf *shapesBegin(void); + VertInf *connsBegin(void); + VertInf *end(void); + void stats(void) + { + printf("Conns %d, shapes %d\n", _connVertices, _shapeVertices); + } + private: + VertInf *_firstShapeVert; + VertInf *_firstConnVert; + VertInf *_lastShapeVert; + VertInf *_lastConnVert; + unsigned int _shapeVertices; + unsigned int _connVertices; +}; + + +typedef std::set ShapeSet; +typedef std::map ContainsMap; + +extern ContainsMap contains; +extern VertInfList vertices; + + +} + + +#endif + + diff --git a/src/libavoid/visibility.cpp b/src/libavoid/visibility.cpp new file mode 100644 index 000000000..e13656f5f --- /dev/null +++ b/src/libavoid/visibility.cpp @@ -0,0 +1,653 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * Copyright (C) 2004-2005 Michael Wybrow + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ + + +#include + +#include "libavoid/shape.h" +#include "libavoid/debug.h" +#include "libavoid/visibility.h" +#include "libavoid/graph.h" + +#include + +#ifdef LINEDEBUG + #include "SDL_gfxPrimitives.h" +#endif + +namespace Avoid { + + +bool UseAStarSearch = true; +bool IgnoreRegions = true; +bool SelectiveReroute = true; +bool IncludeEndpoints = true; +bool UseLeesAlgorithm = false; +bool InvisibilityGrph = true; +bool PartialFeedback = false; + +bool PartialTime = false; + + +void computeCompleteVis(void) +{ + VertInf *beginVert = vertices.shapesBegin(); + VertInf *endVert = vertices.end(); + for (VertInf *i = beginVert; i != endVert; i = i->lstNext) + { + db_printf("-- CONSIDERING --\n"); + i->id.db_print(); + + for (VertInf *j = i->lstPrev ; j != NULL; j = j->lstPrev) + { + bool knownNew = true; + EdgeInf::checkEdgeVisibility(i, j, knownNew); + } + } +} + + +void shapeVis(ShapeRef *shape) +{ + if (!InvisibilityGrph) + { + // Clear shape from graph. + shape->removeFromGraph(); + } + + VertInf *shapeBegin = shape->firstVert(); + VertInf *shapeEnd = shape->lastVert()->lstNext; + + VertInf *pointsBegin = NULL; + if (IncludeEndpoints) + { + pointsBegin = vertices.connsBegin(); + } + else + { + pointsBegin = vertices.shapesBegin(); + } + + for (VertInf *curr = shapeBegin; curr != shapeEnd; curr = curr->lstNext) + { + bool knownNew = true; + + db_printf("-- CONSIDERING --\n"); + curr->id.db_print(); + + db_printf("\tFirst Half:\n"); + for (VertInf *j = pointsBegin ; j != curr; j = j->lstNext) + { + EdgeInf::checkEdgeVisibility(curr, j, knownNew); + } + + db_printf("\tSecond Half:\n"); + VertInf *pointsEnd = vertices.end(); + for (VertInf *k = shapeEnd; k != pointsEnd; k = k->lstNext) + { + EdgeInf::checkEdgeVisibility(curr, k, knownNew); + } + } +} + + +void shapeVisSweep(ShapeRef *shape) +{ + if (!InvisibilityGrph) + { + // Clear shape from graph. + shape->removeFromGraph(); + } + + VertInf *startIter = shape->firstVert(); + VertInf *endIter = shape->lastVert()->lstNext; + + for (VertInf *i = startIter; i != endIter; i = i->lstNext) + { + vertexSweep(i); + } +} + + +void vertexVisibility(VertInf *point, VertInf *partner, bool knownNew, + const bool gen_contains) +{ + const VertID& pID = point->id; + + // Make sure we're only doing ptVis for endpoints. + assert(!(pID.isShape)); + + if (!InvisibilityGrph) + { + point->removeFromGraph(); + } + + if (gen_contains && !(pID.isShape)) + { + generateContains(point); + } + + if (UseLeesAlgorithm) + { + vertexSweep(point); + } + else + { + VertInf *shapesEnd = vertices.end(); + for (VertInf *k = vertices.shapesBegin(); k != shapesEnd; + k = k->lstNext) + { + EdgeInf::checkEdgeVisibility(point, k, knownNew); + } + if (IncludeEndpoints && partner) + { + EdgeInf::checkEdgeVisibility(point, partner, knownNew); + } + } +} + + +//============================================================================ +// SWEEP CODE +// + +static VertInf *centerInf; +static Point centerPoint; +static VertID centerID; +static double centerAngle; + +#ifdef LINEDEBUG + SDL_Surface *avoid_screen = NULL; +#endif + + +class PointPair +{ + public: + PointPair(VertInf *inf) + : vInf(inf) + { + double x = vInf->point.x - centerPoint.x; + double y = vInf->point.y - centerPoint.y; + + angle = pos_to_angle(x, y); + } + bool operator==(const PointPair& rhs) const + { + if (vInf->id == rhs.vInf->id) + { + return true; + } + return false; + } + static double pos_to_angle(double x, double y) + { + double ang = atan(y / x); + ang = (ang * 180) / M_PI; + if (x < 0) + { + ang += 180; + } + else if (y < 0) + { + ang += 360; + } + return ang; + } + + VertInf *vInf; + double angle; +}; + + +typedef std::list VertList; + + +class EdgePair +{ + public: + EdgePair(VertInf *v1, VertInf *v2, double d, double a) + : vInf1(v1), vInf2(v2), initdist(d), initangle(a) + { + currdist = initdist; + currangle = initangle; + } + bool operator<(const EdgePair& rhs) const + { + if (initdist == rhs.initdist) + { + // TODO: This is a bit of a hack, should be + // set by the call to the constructor. + return dist(centerPoint, vInf2->point) < + dist(centerPoint, rhs.vInf2->point); + } + return (initdist < rhs.initdist); + } + bool operator==(const EdgePair& rhs) const + { + if (((vInf1->id == rhs.vInf1->id) && + (vInf2->id == rhs.vInf2->id)) || + ((vInf1->id == rhs.vInf2->id) && + (vInf2->id == rhs.vInf1->id))) + { + return true; + } + return false; + } + bool operator!=(const EdgePair& rhs) const + { + if (((vInf1->id == rhs.vInf1->id) && + (vInf2->id == rhs.vInf2->id)) || + ((vInf1->id == rhs.vInf2->id) && + (vInf2->id == rhs.vInf1->id))) + { + return false; + } + return true; + } + void SetObsAng(double a) + { + obsAngle = fmod(initangle - (a - 180), 360); + + //db_printf("SetObsAng: %.2f (from init %.2f, a %.2f)\n", + // obsAngle, initangle, a); + } + + VertInf *vInf1; + VertInf *vInf2; + double initdist; + double initangle; + double currdist; + double currangle; + double obsAngle; +}; + +typedef std::set EdgeSet; + + +static bool ppCompare(PointPair& pp1, PointPair& pp2) +{ + if (pp1.angle == pp2.angle) + { + // If the points are colinear, then order them in increasing + // distance from the point we are sweeping around. + return dist(centerPoint, pp1.vInf->point) < + dist(centerPoint, pp2.vInf->point); + } + return pp1.angle < pp2.angle; +} + + +#define AHEAD 1 +#define BEHIND -1 + +class isBoundingShape +{ + public: + // constructor remembers the value provided + isBoundingShape(ShapeSet& set) + : ss(set) + { } + // the following is an overloading of the function call operator + bool operator () (const PointPair& pp) + { + if (pp.vInf->id.isShape && + (ss.find(pp.vInf->id.objID) != ss.end())) + { + return true; + } + return false; + } + private: + ShapeSet& ss; +}; + + +static bool sweepVisible(EdgeSet& T, VertInf *currInf, VertInf *lastInf, + bool lastVisible, double lastAngle, int *blocker) +{ + + if (!lastInf || (lastAngle != centerAngle)) + { + // Nothing before it on the current ray + EdgeSet::iterator closestIt = T.begin(); + if (closestIt != T.end()) + { + + Point &e1 = (*closestIt).vInf1->point; + Point &e2 = (*closestIt).vInf2->point; + + if (segmentIntersect(centerInf->point, currInf->point, e1, e2)) + { + *blocker = (*closestIt).vInf1->id.objID; + return false; + } + else + { + return true; + } + } + else + { + return true; + } + } + else + { + // There was another point before this on the ray (lastInf) + if (!lastVisible) + { + *blocker = -1; + return false; + } + else + { + // Check if there is an edge in T that blocks the ray + // between lastInf and currInf. + EdgeSet::iterator tfin = T.end(); + for (EdgeSet::iterator l = T.begin(); l != tfin; ++l) + { + Point &e1 = (*l).vInf1->point; + Point &e2 = (*l).vInf2->point; + + if (segmentIntersect(lastInf->point, currInf->point, e1, e2)) + { + *blocker = (*l).vInf1->id.objID; + return false; + } + } + return true; + } + } +} + + +void vertexSweep(VertInf *vert) +{ + VertID& pID = vert->id; + Point& pPoint = vert->point; + + centerInf = vert; + centerID = pID; + centerPoint = pPoint; + Point centerPt = pPoint; + centerAngle = -1; + + // List of shape (and maybe endpt) vertices, except p + // Sort list, around + VertList v; + + // Initialise the vertex list + VertInf *beginVert = vertices.connsBegin(); + VertInf *endVert = vertices.end(); + for (VertInf *inf = beginVert; inf != endVert; inf = inf->lstNext) + { + if (inf->id == centerID) + { + // Don't include the center point + continue; + } + + if (inf->id.isShape) + { + // Add shape vertex + v.push_back(inf); + } + else + { + if (IncludeEndpoints) + { + if (centerID.isShape) + { + // Add endpoint vertex + v.push_back(inf); + } + else + { + // Center is an endpoint, so only include the other + // endpoint from the matching connector. + VertID partnerID = VertID(centerID.objID, false, + (centerID.vn == 1) ? 2 : 1); + if (inf->id == partnerID) + { + v.push_back(inf); + } + } + } + } + } + // TODO: This should be done with a sorted data type and insertion sort. + v.sort(ppCompare); + + EdgeSet e; + ShapeSet& ss = contains[centerID]; + + // And edge to T that intersect the initial ray. + VertInf *last = vertices.end(); + for (VertInf *k = vertices.shapesBegin(); k != last; ) + { + VertID kID = k->id; + if (!(centerID.isShape) && (ss.find(kID.objID) != ss.end())) + { + uint shapeID = kID.objID; + db_printf("Center is inside shape %u so ignore shape edges.\n", + shapeID); + // One of the endpoints is inside this shape so ignore it. + while ((k != last) && (k->id.objID == shapeID)) + { + // And skip the other vertices from this shape. + k = k->lstNext; + } + continue; + } + + VertInf *kPrev = k->shPrev; + if ((centerInf == k) || (centerInf == kPrev)) + { + k = k->lstNext; + continue; + } + + Point xaxis = { DBL_MAX, centerInf->point.y }; + + if (segmentIntersect(centerInf->point, xaxis, kPrev->point, k->point)) + { + double distance; + if (vecDir(centerInf->point, xaxis, kPrev->point) == BEHIND) + { + distance = dist(centerInf->point, kPrev->point); + } + else + { + distance = dist(centerInf->point, k->point); + } + + EdgePair intPair = EdgePair(k, kPrev, distance, 0.0); + e.insert(intPair).first; + } + k = k->lstNext; + } + + // Start the actual sweep. + db_printf("SWEEP: "); centerID.db_print(); db_printf("\n"); + + VertInf *lastInf = NULL; + double lastAngle = 0; + bool lastVisible = false; + int lastBlocker = 0; + + isBoundingShape isBounding(contains[centerID]); + VertList::iterator vfst = v.begin(); + VertList::iterator vfin = v.end(); + for (VertList::iterator t = vfst; t != vfin; ++t) + { + VertInf *currInf = (*t).vInf; + VertID& currID = currInf->id; + Point& currPt = currInf->point; + centerAngle = (*t).angle; + +#ifdef LINEDEBUG + Sint16 ppx = (int) centerPt.x; + Sint16 ppy = (int) centerPt.y; + + Sint16 cx = (int) currPt.x; + Sint16 cy = (int) currPt.y; +#endif + + double currDist = dist(centerPt, currPt); + db_printf("Dist: %.1f.\n", currDist); + + EdgeInf *edge = EdgeInf::existingEdge(centerInf, currInf); + if (edge == NULL) + { + edge = new EdgeInf(centerInf, currInf); + } + // Ignore vertices from bounding shapes, if sweeping round an endpoint. + if (!(centerID.isShape) && isBounding(*t)) + { + if (InvisibilityGrph) + { + // if p and t can't see each other, add blank edge + db_printf("\tSkipping visibility edge... \n\t\t"); + edge->addBlocker(currInf->id.objID); + edge->db_print(); + } + continue; + } + + + bool cone1 = true, cone2 = true; + if (centerID.isShape) + { + cone1 = inValidRegion(centerInf->shPrev->point, centerPoint, + centerInf->shNext->point, currInf->point); + } + if (currInf->id.isShape) + { + cone2 = inValidRegion(currInf->shPrev->point, currInf->point, + currInf->shNext->point, centerPoint); + } + + if (!cone1 || !cone2) + { + lastInf = NULL; + if (InvisibilityGrph) + { + db_printf("\tSetting invisibility edge... \n\t\t"); + edge->addBlocker(0); + edge->db_print(); + } + } + else + { + int blocker = 0; + // Check visibility. + bool currVisible = sweepVisible(e, currInf, + lastInf, lastVisible, lastAngle, &blocker); + if (blocker == -1) + { + blocker = lastBlocker; + } + if (currVisible) + { +#ifdef LINEDEBUG + lineRGBA(avoid_screen, ppx, ppy, cx, cy, 255, 0, 0, 32); +#endif + db_printf("\tSetting visibility edge... \n\t\t"); + edge->setDist(currDist); + edge->db_print(); + } + else if (InvisibilityGrph) + { + db_printf("\tSetting invisibility edge... \n\t\t"); + edge->addBlocker(blocker); + edge->db_print(); + } + + lastVisible = currVisible; + lastInf = currInf; + lastAngle = centerAngle; + lastBlocker = blocker; + } + + if (currID.isShape) + { + // This is a shape edge + Point& prevPt = currInf->shPrev->point; + Point& nextPt = currInf->shNext->point; + + int prevDir = vecDir(centerPt, currPt, prevPt); + EdgePair prevPair = EdgePair(currInf, currInf->shPrev, + currDist, centerAngle); + + EdgeSet::iterator ePtr; + if (prevDir == BEHIND) + { + ePtr = e.find(prevPair); + if (ePtr != e.end()) + { + e.erase(ePtr); + } + } + else if ((prevDir == AHEAD) && (currInf->shPrev != centerInf)) + { + double x = prevPt.x - currPt.x; + double y = prevPt.y - currPt.y; + double angle = PointPair::pos_to_angle(x, y); + prevPair.SetObsAng(angle); + + ePtr = e.insert(prevPair).first; + } + + + int nextDir = vecDir(centerPt, currPt, nextPt); + EdgePair nextPair = EdgePair(currInf, currInf->shNext, + currDist, centerAngle); + + if (nextDir == BEHIND) + { + ePtr = e.find(nextPair); + if (ePtr != e.end()) + { + e.erase(ePtr); + } + } + else if ((nextDir == AHEAD) && (currInf->shNext != centerInf)) + { + double x = nextPt.x - currPt.x; + double y = nextPt.y - currPt.y; + double angle = PointPair::pos_to_angle(x, y); + nextPair.SetObsAng(angle); + + ePtr = e.insert(nextPair).first; + } + } + +#ifdef LINEDEBUG + SDL_Flip(avoid_screen); +#endif + } +} + + +} + diff --git a/src/libavoid/visibility.h b/src/libavoid/visibility.h new file mode 100644 index 000000000..edfc3b16c --- /dev/null +++ b/src/libavoid/visibility.h @@ -0,0 +1,57 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * Copyright (C) 2004-2005 Michael Wybrow + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ + +#ifndef AVOID_VISIBILITY_H +#define AVOID_VISIBILITY_H + +#include "libavoid/vertices.h" + + +//#define LINEDEBUG + +#ifdef LINEDEBUG + #include +#endif + + +namespace Avoid { + +extern bool PartialTime; +#ifdef LINEDEBUG + extern SDL_Surface *avoid_screen; +#endif + + +extern void vertexVisibility(VertInf *point, VertInf *partner, bool knownNew, + const bool gen_contains = false); +extern void vertexSweep(VertInf *point); +extern void computeCompleteVis(void); +extern void shapeVis(ShapeRef *shape); +extern void shapeVisSweep(ShapeRef *shape); + + +} + + +#endif + + diff --git a/src/libcroco/.cvsignore b/src/libcroco/.cvsignore new file mode 100644 index 000000000..38efca7bc --- /dev/null +++ b/src/libcroco/.cvsignore @@ -0,0 +1,3 @@ +.deps +.dirstamp +makefile diff --git a/src/libcroco/Makefile_insert b/src/libcroco/Makefile_insert new file mode 100644 index 000000000..97ed49ee8 --- /dev/null +++ b/src/libcroco/Makefile_insert @@ -0,0 +1,64 @@ +## Makefile.am fragment sourced by src/Makefile.am. + +libcroco/all: libcroco/libcroco.a + +libcroco/clean: + rm -f libcroco/libcroco.a $(libcroco_libcroco_a_OBJECTS) + +libcroco_libcroco_a_SOURCES = \ + libcroco/cr-utils.c \ + libcroco/cr-utils.h \ + libcroco/cr-input.c \ + libcroco/cr-input.h \ + libcroco/cr-enc-handler.c \ + libcroco/cr-enc-handler.h \ + libcroco/cr-num.c \ + libcroco/cr-num.h \ + libcroco/cr-rgb.c \ + libcroco/cr-rgb.h \ + libcroco/cr-token.c \ + libcroco/cr-token.h \ + libcroco/cr-tknzr.c \ + libcroco/cr-tknzr.h \ + libcroco/cr-term.c \ + libcroco/cr-term.h \ + libcroco/cr-attr-sel.c \ + libcroco/cr-attr-sel.h \ + libcroco/cr-pseudo.c \ + libcroco/cr-pseudo.h \ + libcroco/cr-additional-sel.c \ + libcroco/cr-additional-sel.h \ + libcroco/cr-simple-sel.c \ + libcroco/cr-simple-sel.h \ + libcroco/cr-selector.c \ + libcroco/cr-selector.h \ + libcroco/cr-doc-handler.c \ + libcroco/cr-doc-handler.h \ + libcroco/cr-parser.c \ + libcroco/cr-parser.h \ + libcroco/cr-declaration.c \ + libcroco/cr-declaration.h \ + libcroco/cr-statement.c \ + libcroco/cr-statement.h \ + libcroco/cr-stylesheet.c \ + libcroco/cr-stylesheet.h \ + libcroco/cr-cascade.c \ + libcroco/cr-cascade.h \ + libcroco/cr-om-parser.c \ + libcroco/cr-om-parser.h \ + libcroco/cr-style.c \ + libcroco/cr-style.h \ + libcroco/cr-libxml-node-iface.c \ + libcroco/cr-libxml-node-iface.h \ + libcroco/cr-node-iface.h \ + libcroco/cr-sel-eng.c \ + libcroco/cr-sel-eng.h \ + libcroco/cr-fonts.c \ + libcroco/cr-fonts.h \ + libcroco/cr-prop-list.c \ + libcroco/cr-prop-list.h \ + libcroco/cr-parsing-location.c \ + libcroco/cr-parsing-location.h \ + libcroco/cr-string.c \ + libcroco/cr-string.h \ + libcroco/libcroco.h diff --git a/src/libcroco/README b/src/libcroco/README new file mode 100644 index 000000000..74df23145 --- /dev/null +++ b/src/libcroco/README @@ -0,0 +1,11 @@ +This code is derived from libcroco-0.6. We hope that the changes will find +their way into libcroco-0.7 (in some form). + +The main changed file is cr-sel-eng.{c,h}. The cr-sel-eng in libcroco-0.6 is +somewhat experimental: it has a few bugs, and the interface requires that the +XML node storage be libxml2. The version in this directory has a modification +of allowing passing in a "vtable" of operations so that we can use our +preferred representation Inkscape::XML::Node. + +Once libcroco-0.7 is widely available (with the vtable interface or a +functional equivalent), this directory should be removed. diff --git a/src/libcroco/cr-additional-sel.c b/src/libcroco/cr-additional-sel.c new file mode 100644 index 000000000..9f511af06 --- /dev/null +++ b/src/libcroco/cr-additional-sel.c @@ -0,0 +1,468 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli + * See COPYRIGHTS file for copyright information. + * + */ + +#include "cr-additional-sel.h" +#include "string.h" + +/** + *Default constructor of #CRAdditionalSel. + *@return the newly build instance of #CRAdditionalSel. + */ +CRAdditionalSel * +cr_additional_sel_new (void) +{ + CRAdditionalSel *result = NULL; + + result = g_try_malloc (sizeof (CRAdditionalSel)); + + if (result == NULL) { + cr_utils_trace_debug ("Out of memory"); + return NULL; + } + + memset (result, 0, sizeof (CRAdditionalSel)); + + return result; +} + +/** + *Constructor of #CRAdditionalSel. + *@param a_sel_type the type of the newly built instance + *of #CRAdditionalSel. + *@return the newly built instance of #CRAdditionalSel. + */ +CRAdditionalSel * +cr_additional_sel_new_with_type (enum AddSelectorType a_sel_type) +{ + CRAdditionalSel *result = NULL; + + result = cr_additional_sel_new (); + + g_return_val_if_fail (result, NULL); + + result->type = a_sel_type; + + return result; +} + +/** + *Sets a new class name to a + *CLASS additional selector. + *@param a_this the "this pointer" of the current instance + *of #CRAdditionalSel . + *@param a_class_name the new class name to set. + * + */ +void +cr_additional_sel_set_class_name (CRAdditionalSel * a_this, + CRString * a_class_name) +{ + g_return_if_fail (a_this && a_this->type == CLASS_ADD_SELECTOR); + + if (a_this->content.class_name) { + cr_string_destroy (a_this->content.class_name); + } + + a_this->content.class_name = a_class_name; +} + +/** + *Sets a new id name to an + *ID additional selector. + *@param a_this the "this pointer" of the current instance + *of #CRAdditionalSel . + *@param a_id the new id to set. + */ +void +cr_additional_sel_set_id_name (CRAdditionalSel * a_this, CRString * a_id) +{ + g_return_if_fail (a_this && a_this->type == ID_ADD_SELECTOR); + + if (a_this->content.id_name) { + cr_string_destroy (a_this->content.id_name); + } + + a_this->content.id_name = a_id; +} + +/** + *Sets a new pseudo to a + *PSEUDO additional selector. + *@param a_this the "this pointer" of the current instance + *of #CRAdditionalSel . + *@param a_pseudo the new pseudo to set. + */ +void +cr_additional_sel_set_pseudo (CRAdditionalSel * a_this, CRPseudo * a_pseudo) +{ + g_return_if_fail (a_this + && a_this->type == PSEUDO_CLASS_ADD_SELECTOR); + + if (a_this->content.pseudo) { + cr_pseudo_destroy (a_this->content.pseudo); + } + + a_this->content.pseudo = a_pseudo; +} + +/** + *Sets a new instance of #CRAttrSel to + *a ATTRIBUTE additional selector. + *@param a_this the "this pointer" of the current instance + *of #CRAdditionalSel . + *@param a_sel the new instance of #CRAttrSel to set. + */ +void +cr_additional_sel_set_attr_sel (CRAdditionalSel * a_this, CRAttrSel * a_sel) +{ + g_return_if_fail (a_this && a_this->type == ATTRIBUTE_ADD_SELECTOR); + + if (a_this->content.attr_sel) { + cr_attr_sel_destroy (a_this->content.attr_sel); + } + + a_this->content.attr_sel = a_sel; +} + +/** + *Appends a new instance of #CRAdditional to the + *current list of #CRAdditional. + *@param a_this the "this pointer" of the current instance + *of #CRAdditionalSel . + *@param a_sel the new instance to #CRAdditional to append. + *@return the new list of CRAdditionalSel or NULL if an error arises. + */ +CRAdditionalSel * +cr_additional_sel_append (CRAdditionalSel * a_this, CRAdditionalSel * a_sel) +{ + CRAdditionalSel *cur_sel = NULL; + + g_return_val_if_fail (a_sel, NULL); + + if (a_this == NULL) { + return a_sel; + } + + if (a_sel == NULL) + return NULL; + + for (cur_sel = a_this; + cur_sel && cur_sel->next; cur_sel = cur_sel->next) ; + + g_return_val_if_fail (cur_sel != NULL, NULL); + + cur_sel->next = a_sel; + a_sel->prev = cur_sel; + + return a_this; +} + +/** + *Preppends a new instance of #CRAdditional to the + *current list of #CRAdditional. + *@param a_this the "this pointer" of the current instance + *of #CRAdditionalSel . + *@param a_sel the new instance to #CRAdditional to preappend. + *@return the new list of CRAdditionalSel or NULL if an error arises. + */ +CRAdditionalSel * +cr_additional_sel_prepend (CRAdditionalSel * a_this, CRAdditionalSel * a_sel) +{ + g_return_val_if_fail (a_sel, NULL); + + if (a_this == NULL) { + return a_sel; + } + + a_sel->next = a_this; + a_this->prev = a_sel; + + return a_sel; +} + +guchar * +cr_additional_sel_to_string (CRAdditionalSel * a_this) +{ + guchar *result = NULL; + GString *str_buf = NULL; + CRAdditionalSel *cur = NULL; + + g_return_val_if_fail (a_this, NULL); + + str_buf = g_string_new (NULL); + + for (cur = a_this; cur; cur = cur->next) { + switch (cur->type) { + case CLASS_ADD_SELECTOR: + { + guchar *name = NULL; + + if (cur->content.class_name) { + name = g_strndup + (cur->content.class_name->stryng->str, + cur->content.class_name->stryng->len); + + if (name) { + g_string_append_printf + (str_buf, ".%s", + name); + g_free (name); + name = NULL; + } + } + } + break; + + case ID_ADD_SELECTOR: + { + guchar *name = NULL; + + if (cur->content.class_name) { + name = g_strndup + (cur->content.id_name->stryng->str, + cur->content.id_name->stryng->len); + + if (name) { + g_string_append_printf + (str_buf, "#%s", + name); + g_free (name); + name = NULL; + } + } + } + + break; + + case PSEUDO_CLASS_ADD_SELECTOR: + { + if (cur->content.pseudo) { + guchar *tmp_str = NULL; + + tmp_str = cr_pseudo_to_string + (cur->content.pseudo); + if (tmp_str) { + g_string_append_printf + (str_buf, ":%s", + tmp_str); + g_free (tmp_str); + tmp_str = NULL; + } + } + } + break; + + case ATTRIBUTE_ADD_SELECTOR: + if (cur->content.attr_sel) { + guchar *tmp_str = NULL; + + g_string_append_c (str_buf, '['); + tmp_str = cr_attr_sel_to_string + (cur->content.attr_sel); + if (tmp_str) { + g_string_append_printf + (str_buf, "%s]", tmp_str); + g_free (tmp_str); + tmp_str = NULL; + } + } + break; + + default: + break; + } + } + + if (str_buf) { + result = str_buf->str; + g_string_free (str_buf, FALSE); + str_buf = NULL; + } + + return result; +} + +guchar * +cr_additional_sel_one_to_string (CRAdditionalSel *a_this) +{ + guchar *result = NULL; + GString *str_buf = NULL; + + g_return_val_if_fail (a_this, NULL) ; + + str_buf = g_string_new (NULL) ; + + switch (a_this->type) { + case CLASS_ADD_SELECTOR: + { + guchar *name = NULL; + + if (a_this->content.class_name) { + name = g_strndup + (a_this->content.class_name->stryng->str, + a_this->content.class_name->stryng->len); + + if (name) { + g_string_append_printf + (str_buf, ".%s", + name); + g_free (name); + name = NULL; + } + } + } + break; + + case ID_ADD_SELECTOR: + { + guchar *name = NULL; + + if (a_this->content.class_name) { + name = g_strndup + (a_this->content.id_name->stryng->str, + a_this->content.id_name->stryng->len); + + if (name) { + g_string_append_printf + (str_buf, "#%s", + name); + g_free (name); + name = NULL; + } + } + } + + break; + + case PSEUDO_CLASS_ADD_SELECTOR: + { + if (a_this->content.pseudo) { + guchar *tmp_str = NULL; + + tmp_str = cr_pseudo_to_string + (a_this->content.pseudo); + if (tmp_str) { + g_string_append_printf + (str_buf, ":%s", + tmp_str); + g_free (tmp_str); + tmp_str = NULL; + } + } + } + break; + + case ATTRIBUTE_ADD_SELECTOR: + if (a_this->content.attr_sel) { + guchar *tmp_str = NULL; + + g_string_append_printf (str_buf, "["); + tmp_str = cr_attr_sel_to_string + (a_this->content.attr_sel); + if (tmp_str) { + g_string_append_printf + (str_buf, "%s]", tmp_str); + g_free (tmp_str); + tmp_str = NULL; + } + } + break; + + default: + break; + } + + if (str_buf) { + result = str_buf->str; + g_string_free (str_buf, FALSE); + str_buf = NULL; + } + + return result; +} + +/** + *Dumps the current instance of #CRAdditionalSel to a file + *@param a_this the "this pointer" of the current instance of + *#CRAdditionalSel. + *@param a_fp the destination file. + */ +void +cr_additional_sel_dump (CRAdditionalSel * a_this, FILE * a_fp) +{ + guchar *tmp_str = NULL; + + g_return_if_fail (a_fp); + + if (a_this) { + tmp_str = cr_additional_sel_to_string (a_this); + if (tmp_str) { + fprintf (a_fp, "%s", tmp_str); + g_free (tmp_str); + tmp_str = NULL; + } + } +} + +/** + *Destroys an instance of #CRAdditional. + *@param a_this the "this pointer" of the current instance + *of #CRAdditionalSel . + */ +void +cr_additional_sel_destroy (CRAdditionalSel * a_this) +{ + g_return_if_fail (a_this); + + switch (a_this->type) { + case CLASS_ADD_SELECTOR: + cr_string_destroy (a_this->content.class_name); + a_this->content.class_name = NULL; + break; + + case PSEUDO_CLASS_ADD_SELECTOR: + cr_pseudo_destroy (a_this->content.pseudo); + a_this->content.pseudo = NULL; + break; + + case ID_ADD_SELECTOR: + cr_string_destroy (a_this->content.id_name); + a_this->content.id_name = NULL; + break; + + case ATTRIBUTE_ADD_SELECTOR: + cr_attr_sel_destroy (a_this->content.attr_sel); + a_this->content.attr_sel = NULL; + break; + + default: + break; + } + + if (a_this->next) { + cr_additional_sel_destroy (a_this->next); + } + + g_free (a_this); +} diff --git a/src/libcroco/cr-additional-sel.h b/src/libcroco/cr-additional-sel.h new file mode 100644 index 000000000..1ec1aa0ec --- /dev/null +++ b/src/libcroco/cr-additional-sel.h @@ -0,0 +1,112 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli + * See the COPYRIGHTS file for copyright information. + */ + + +/** + *@file + *This file holds the declaration of the + *#CRAddSel class. + */ +#ifndef __CR_ADD_SEL_H__ +#define __CR_ADD_SEL_H__ + +#include +#include +#include "cr-utils.h" +#include "cr-attr-sel.h" +#include "cr-pseudo.h" +#include "cr-additional-sel.h" + +G_BEGIN_DECLS + +enum AddSelectorType +{ + NO_ADD_SELECTOR = 0 , + CLASS_ADD_SELECTOR = 1 , + PSEUDO_CLASS_ADD_SELECTOR = 1 << 1, + ID_ADD_SELECTOR = 1 << 3, + ATTRIBUTE_ADD_SELECTOR = 1 << 4 +} ; + +union CRAdditionalSelectorContent +{ + CRString *class_name ; + CRString *id_name ; + CRPseudo *pseudo ; + CRAttrSel *attr_sel ; +} ; + +typedef struct _CRAdditionalSel CRAdditionalSel ; + +/** + *#CRAdditionalSel abstracts + *an additionnal selector. + *An additional selector is the selector part + *that comes after the combination of type selectors. + *It can be either "a class selector (the .class part), + *a pseudo class selector, an attribute selector + *or an id selector. + */ +struct _CRAdditionalSel +{ + enum AddSelectorType type ; + union CRAdditionalSelectorContent content ; + + CRAdditionalSel * next ; + CRAdditionalSel * prev ; + CRParsingLocation location ; +} ; + +CRAdditionalSel * cr_additional_sel_new (void) ; + +CRAdditionalSel * cr_additional_sel_new_with_type (enum AddSelectorType a_sel_type) ; + +CRAdditionalSel * cr_additional_sel_append (CRAdditionalSel *a_this, + CRAdditionalSel *a_sel) ; + +void cr_additional_sel_set_class_name (CRAdditionalSel *a_this, + CRString *a_class_name) ; + +void cr_additional_sel_set_id_name (CRAdditionalSel *a_this, + CRString *a_id) ; + +void cr_additional_sel_set_pseudo (CRAdditionalSel *a_this, + CRPseudo *a_pseudo) ; + +void cr_additional_sel_set_attr_sel (CRAdditionalSel *a_this, + CRAttrSel *a_sel) ; + +CRAdditionalSel * cr_additional_sel_prepend (CRAdditionalSel *a_this, + CRAdditionalSel *a_sel) ; + +guchar * cr_additional_sel_to_string (CRAdditionalSel *a_this) ; + +guchar * cr_additional_sel_one_to_string (CRAdditionalSel *a_this) ; + +void cr_additional_sel_dump (CRAdditionalSel *a_this, FILE *a_fp) ; + +void cr_additional_sel_destroy (CRAdditionalSel *a_this) ; + +G_END_DECLS + +#endif /*__CR_ADD_SEL_H*/ diff --git a/src/libcroco/cr-attr-sel.c b/src/libcroco/cr-attr-sel.c new file mode 100644 index 000000000..cf048d102 --- /dev/null +++ b/src/libcroco/cr-attr-sel.c @@ -0,0 +1,221 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * See COPYRIGHTS file for copyrights information. + */ + +#include +#include "cr-attr-sel.h" + +/** + *@file + *The class that abstracts an attribute selector. + *Attributes selectors are described in the css2 spec [5.8]. + *There are more generally used in the css2 selectors described in + *css2 spec [5] . + */ + +/** + *The constructor of #CRAttrSel. + *@return the newly allocated instance + *of #CRAttrSel. + */ +CRAttrSel * +cr_attr_sel_new (void) +{ + CRAttrSel *result = NULL; + + result = g_malloc0 (sizeof (CRAttrSel)); + + return result; +} + +/** + *Appends an attribute selector to the current list of + *attribute selectors represented by a_this. + * + *@param a_this the this pointer of the current instance of + *#CRAttrSel. + *@param a_attr_sel selector to append. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_attr_sel_append_attr_sel (CRAttrSel * a_this, CRAttrSel * a_attr_sel) +{ + CRAttrSel *cur_sel = NULL; + + g_return_val_if_fail (a_this && a_attr_sel, + CR_BAD_PARAM_ERROR); + + for (cur_sel = a_this; + cur_sel->next; + cur_sel = cur_sel->next) ; + + cur_sel->next = a_attr_sel; + a_attr_sel->prev = cur_sel; + + return CR_OK; +} + +/** + *Prepends an attribute selector to the list of + *attributes selector represented by a_this. + * + *@param a_this the "this pointer" of the current instance + *of #CRAttrSel. + *@param a_attr_sel the attribute selector to append. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_attr_sel_prepend_attr_sel (CRAttrSel * a_this, + CRAttrSel * a_attr_sel) +{ + g_return_val_if_fail (a_this && a_attr_sel, + CR_BAD_PARAM_ERROR); + + a_attr_sel->next = a_this; + a_this->prev = a_attr_sel; + + return CR_OK; +} + +guchar * +cr_attr_sel_to_string (CRAttrSel * a_this) +{ + CRAttrSel *cur = NULL; + guchar *result = NULL; + GString *str_buf = NULL; + + g_return_val_if_fail (a_this, NULL); + + str_buf = g_string_new (NULL); + + for (cur = a_this; cur; cur = cur->next) { + if (cur->prev) { + g_string_append_c (str_buf, ' '); + } + + if (cur->name) { + guchar *name = NULL; + + name = g_strndup (cur->name->stryng->str, + cur->name->stryng->len); + if (name) { + g_string_append (str_buf, name); + g_free (name); + name = NULL; + } + } + + if (cur->value) { + guchar *value = NULL; + + value = g_strndup (cur->value->stryng->str, + cur->value->stryng->len); + if (value) { + switch (cur->match_way) { + case SET: + break; + + case EQUALS: + g_string_append_c (str_buf, '='); + break; + + case INCLUDES: + g_string_append (str_buf, "~="); + break; + + case DASHMATCH: + g_string_append (str_buf, "|="); + break; + + default: + break; + } + + g_string_append_printf + (str_buf, "\"%s\"", value); + + g_free (value); + value = NULL; + } + } + } + + if (str_buf) { + result = str_buf->str; + g_string_free (str_buf, FALSE); + } + + return result; +} + +/** + *Dumps the current instance of #CRAttrSel to a file. + *@param a_this the "this pointer" of the current instance of + *#CRAttrSel. + *@param a_fp the destination file. + */ +void +cr_attr_sel_dump (CRAttrSel * a_this, FILE * a_fp) +{ + guchar *tmp_str = NULL; + + g_return_if_fail (a_this); + + tmp_str = cr_attr_sel_to_string (a_this); + + if (tmp_str) { + fprintf (a_fp, "%s", tmp_str); + g_free (tmp_str); + tmp_str = NULL; + } +} + +/** + *Destroys the current instance of #CRAttrSel. + *Frees all the fields if they are non null. + *@param a_this the "this pointer" of the current + *instance of #CRAttrSel. + */ +void +cr_attr_sel_destroy (CRAttrSel * a_this) +{ + g_return_if_fail (a_this); + + if (a_this->name) { + cr_string_destroy (a_this->name); + a_this->name = NULL; + } + + if (a_this->value) { + cr_string_destroy (a_this->value); + a_this->value = NULL; + } + + if (a_this->next) { + cr_attr_sel_destroy (a_this->next); + a_this->next = NULL; + } + + if (a_this) { + g_free (a_this); + a_this = NULL; + } +} diff --git a/src/libcroco/cr-attr-sel.h b/src/libcroco/cr-attr-sel.h new file mode 100644 index 000000000..9cc03d358 --- /dev/null +++ b/src/libcroco/cr-attr-sel.h @@ -0,0 +1,74 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli + * See COPYRIGHTS file for copyright information. + */ + +#ifndef __CR_ATTR_SEL_H__ +#define __CR_ATTR_SEL_H__ + +#include +#include +#include "cr-utils.h" +#include "cr-parsing-location.h" +#include "cr-string.h" + +G_BEGIN_DECLS + + +struct _CRAttrSel ; +typedef struct _CRAttrSel CRAttrSel ; + +enum AttrMatchWay +{ + NO_MATCH = 0, + SET, + EQUALS, + INCLUDES, + DASHMATCH +} ; + +struct _CRAttrSel +{ + CRString *name ; + CRString *value ; + enum AttrMatchWay match_way ; + CRAttrSel *next ; + CRAttrSel *prev ; + CRParsingLocation location ; +} ; + +CRAttrSel * cr_attr_sel_new (void) ; + +enum CRStatus cr_attr_sel_append_attr_sel (CRAttrSel * a_this, + CRAttrSel *a_new) ; + +enum CRStatus cr_attr_sel_prepend_attr_sel (CRAttrSel *a_this, + CRAttrSel *a_attr_sel) ; + +guchar * cr_attr_sel_to_string (CRAttrSel *a_this) ; + +void cr_attr_sel_dump (CRAttrSel *a_this, FILE *a_fp) ; + +void cr_attr_sel_destroy (CRAttrSel *a_this) ; + +G_END_DECLS + +#endif /*__CR_ATTR_SEL_H__*/ diff --git a/src/libcroco/cr-cascade.c b/src/libcroco/cr-cascade.c new file mode 100644 index 000000000..51c6df5ac --- /dev/null +++ b/src/libcroco/cr-cascade.c @@ -0,0 +1,197 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * Copyright (C) 2002-2003 Dodji Seketeli + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the + * GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the + * GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +/* + *$Id: cr-cascade.c,v 1.6 2004/03/07 13:22:47 dodji Exp $ + */ + +#include +#include "cr-cascade.h" + +#define PRIVATE(a_this) ((a_this)->priv) + +struct _CRCascadePriv { + /** + *the 3 style sheets of the cascade: + *author, user, and useragent sheet. + *Intended to be addressed by + *sheets[ORIGIN_AUTHOR] or sheets[ORIGIN_USER] + *of sheets[ORIGIN_UA] ; + */ + CRStyleSheet *sheets[3]; + guint ref_count; +}; + +/** + *Constructor of the #CRCascade class. + *Note that all three parameters of this + *method are ref counted and their refcount is increased. + *Their refcount will be decreased at the destruction of + *the instance of #CRCascade. + *So the caller should not call their destructor. The caller + *should call their ref/unref method instead if it wants + *@param a_author_sheet the autor origin style sheet + *@param a_user_sheet the user origin style sheet. + *@param a_ua_sheet the user agent origin style sheet. + *@return the newly built instance of CRCascade or NULL if + *an error arose during constrution. + */ +CRCascade * +cr_cascade_new (CRStyleSheet * a_author_sheet, + CRStyleSheet * a_user_sheet, CRStyleSheet * a_ua_sheet) +{ + CRCascade *result = NULL; + + result = g_try_malloc (sizeof (CRCascade)); + if (!result) { + cr_utils_trace_info ("Out of memory"); + return NULL; + } + memset (result, 0, sizeof (CRCascade)); + + PRIVATE (result) = g_try_malloc (sizeof (CRCascadePriv)); + if (!PRIVATE (result)) { + cr_utils_trace_info ("Out of memory"); + return NULL; + } + memset (PRIVATE (result), 0, sizeof (CRCascadePriv)); + + if (a_author_sheet) { + cr_cascade_set_sheet (result, a_author_sheet, ORIGIN_AUTHOR); + } + if (a_user_sheet) { + cr_cascade_set_sheet (result, a_user_sheet, ORIGIN_USER); + } + if (a_ua_sheet) { + cr_cascade_set_sheet (result, a_ua_sheet, ORIGIN_UA); + } + + return result; +} + +/** + *Gets a given origin sheet. + *Note that the returned stylesheet + *is refcounted so if the caller wants + *to manage its lifecycle, it must use + *cr_stylesheet_ref()/cr_stylesheet_unref() instead + *of the cr_stylesheet_destroy() method. + *@param a_this the current instance of #CRCascade. + *@param a_origin the origin of the style sheet as + *defined in the css2 spec in chapter 6.4. + *@return the style sheet, or NULL if it does not + *exist. + */ +CRStyleSheet * +cr_cascade_get_sheet (CRCascade * a_this, enum CRStyleOrigin a_origin) +{ + g_return_val_if_fail (a_this + && (unsigned)a_origin < NB_ORIGINS, NULL); + + return PRIVATE (a_this)->sheets[a_origin]; +} + +/** + *Sets a stylesheet in the cascade + *@param a_this the current instance of #CRCascade. + *@param a_sheet the stylesheet to set. + *@param a_origin the origin of the stylesheet. + *@return CR_OK upon successfull completion, an error + *code otherwise. + */ +enum CRStatus +cr_cascade_set_sheet (CRCascade * a_this, + CRStyleSheet * a_sheet, enum CRStyleOrigin a_origin) +{ + g_return_val_if_fail (a_this + && a_sheet + && (unsigned)a_origin < NB_ORIGINS, CR_BAD_PARAM_ERROR); + + if (PRIVATE (a_this)->sheets[a_origin]) + cr_stylesheet_unref (PRIVATE (a_this)->sheets[a_origin]); + PRIVATE (a_this)->sheets[a_origin] = a_sheet; + cr_stylesheet_ref (a_sheet); + a_sheet->origin = a_origin; + return CR_OK; +} + +/** + *Increases the reference counter of the current instance + *of #CRCascade. + *@param a_this the current instance of #CRCascade + * + */ +void +cr_cascade_ref (CRCascade * a_this) +{ + g_return_if_fail (a_this && PRIVATE (a_this)); + + PRIVATE (a_this)->ref_count++; +} + +/** + *Decrements the reference counter associated + *to this instance of #CRCascade. If the reference + *counter reaches zero, the instance is destroyed + *using cr_cascade_destroy() + *@param a_this the current instance of + *#CRCascade. + */ +void +cr_cascade_unref (CRCascade * a_this) +{ + g_return_if_fail (a_this && PRIVATE (a_this)); + + if (PRIVATE (a_this)->ref_count) + PRIVATE (a_this)->ref_count--; + if (!PRIVATE (a_this)->ref_count) { + cr_cascade_destroy (a_this); + } +} + +/** + *Destructor of #CRCascade. + */ +void +cr_cascade_destroy (CRCascade * a_this) +{ + g_return_if_fail (a_this); + + if (PRIVATE (a_this)) { + gulong i = 0; + + for (i = 0; PRIVATE (a_this)->sheets && i < NB_ORIGINS; i++) { + if (PRIVATE (a_this)->sheets[i]) { + if (cr_stylesheet_unref + (PRIVATE (a_this)->sheets[i]) + == TRUE) { + PRIVATE (a_this)->sheets[i] = NULL; + } + } + } + g_free (PRIVATE (a_this)); + PRIVATE (a_this) = NULL; + } + g_free (a_this); +} diff --git a/src/libcroco/cr-cascade.h b/src/libcroco/cr-cascade.h new file mode 100644 index 000000000..5e718427d --- /dev/null +++ b/src/libcroco/cr-cascade.h @@ -0,0 +1,74 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the + * GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the + * GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + */ + +/* + *$Id: cr-cascade.h,v 1.6 2004/01/29 22:05:14 dodji Exp $ + */ + +#ifndef __CR_CASCADE_H__ +#define __CR_CASCADE_H__ + +#include "cr-stylesheet.h" + +/** + *@file + *the declaration of the #CRCascade class. + */ + +G_BEGIN_DECLS + + +typedef struct _CRCascadePriv CRCascadePriv ; + +/** + *An abstraction of the "Cascade" defined + *in the css2 spec, chapter 6.4. + */ +typedef struct _CRCascade CRCascade ; + +struct _CRCascade +{ + CRCascadePriv *priv ; +}; + + +CRCascade * cr_cascade_new (CRStyleSheet *a_author_sheet, + CRStyleSheet *a_user_sheet, + CRStyleSheet *a_ua_sheet) ; + +CRStyleSheet * cr_cascade_get_sheet (CRCascade *a_this, + enum CRStyleOrigin a_origin) ; + +enum CRStatus cr_cascade_set_sheet (CRCascade *a_this, + CRStyleSheet *a_sheet, + enum CRStyleOrigin a_origin) ; + +void cr_cascade_ref (CRCascade *a_this) ; + +void cr_cascade_unref (CRCascade *a_this) ; + +void cr_cascade_destroy (CRCascade *a_this) ; + +G_END_DECLS + +#endif /*__CR_CASCADE_H__*/ diff --git a/src/libcroco/cr-declaration.c b/src/libcroco/cr-declaration.c new file mode 100644 index 000000000..a1ff0d292 --- /dev/null +++ b/src/libcroco/cr-declaration.c @@ -0,0 +1,775 @@ +/* -*- Mode: C; indent-tabs-mode: ni; c-basic-offset: 8 -*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli. + * See COPYRIGHTS file for copyright information. + */ + + +#include +#include "cr-declaration.h" +#include "cr-statement.h" +#include "cr-parser.h" + +/** + *@file + *The definition of the #CRDeclaration class. + */ + +/** + *Dumps (serializes) one css declaration to a file. + *@param a_this the current instance of #CRDeclaration. + *@param a_fp the destination file pointer. + *@param a_indent the number of indentation white char. + */ +static void +dump (CRDeclaration * a_this, FILE * a_fp, glong a_indent) +{ + guchar *str = NULL; + + g_return_if_fail (a_this); + + str = cr_declaration_to_string (a_this, a_indent); + if (str) { + fprintf (a_fp, "%s", str); + g_free (str); + str = NULL; + } +} + +/** + *Constructor of #CRDeclaration. + *@param a_property the property string of the declaration + *@param a_value the value expression of the declaration. + *@return the newly built instance of #CRDeclaration, or NULL in + *case of error. + */ +CRDeclaration * +cr_declaration_new (CRStatement * a_statement, + CRString * a_property, CRTerm * a_value) +{ + CRDeclaration *result = NULL; + + g_return_val_if_fail (a_property, NULL); + + if (a_statement) + g_return_val_if_fail (a_statement + && ((a_statement->type == RULESET_STMT) + || (a_statement->type + == AT_FONT_FACE_RULE_STMT) + || (a_statement->type + == AT_PAGE_RULE_STMT)), NULL); + + result = g_try_malloc (sizeof (CRDeclaration)); + if (!result) { + cr_utils_trace_info ("Out of memory"); + return NULL; + } + memset (result, 0, sizeof (CRDeclaration)); + result->property = a_property; + result->value = a_value; + + if (a_value) { + cr_term_ref (a_value); + } + result->parent_statement = a_statement; + return result; +} + +/** + *Parses a text buffer that contains + *a css declaration. + * + *@param a_statement the parent css2 statement of this + *this declaration. Must be non NULL and of type + *RULESET_STMT (must be a ruleset). + *@param a_str the string that contains the statement. + *@param a_enc the encoding of a_str. + *@return the parsed declaration, or NULL in case of error. + */ +CRDeclaration * +cr_declaration_parse_from_buf (CRStatement * a_statement, + const guchar * a_str, enum CREncoding a_enc) +{ + enum CRStatus status = CR_OK; + CRTerm *value = NULL; + CRString *property = NULL; + CRDeclaration *result = NULL; + CRParser *parser = NULL; + gboolean important = FALSE; + + g_return_val_if_fail (a_str, NULL); + if (a_statement) + g_return_val_if_fail (a_statement->type == RULESET_STMT, + NULL); + + parser = cr_parser_new_from_buf ((guchar*)a_str, strlen (a_str), a_enc, FALSE); + g_return_val_if_fail (parser, NULL); + + status = cr_parser_try_to_skip_spaces_and_comments (parser); + if (status != CR_OK) + goto cleanup; + + status = cr_parser_parse_declaration (parser, &property, + &value, &important); + if (status != CR_OK || !property) + goto cleanup; + + result = cr_declaration_new (a_statement, property, value); + if (result) { + property = NULL; + value = NULL; + result->important = important; + } + + cleanup: + + if (parser) { + cr_parser_destroy (parser); + parser = NULL; + } + + if (property) { + cr_string_destroy (property); + property = NULL; + } + + if (value) { + cr_term_destroy (value); + value = NULL; + } + + return result; +} + +/** + *Parses a ';' separated list of properties declaration. + *@param a_str the input buffer that contains the list of declaration to + *parse. + *@param a_enc the encoding of a_str + *@return the parsed list of declaration, NULL if parsing failed. + */ +CRDeclaration * +cr_declaration_parse_list_from_buf (const guchar * a_str, + enum CREncoding a_enc) +{ + + enum CRStatus status = CR_OK; + CRTerm *value = NULL; + CRString *property = NULL; + CRDeclaration *result = NULL, + *cur_decl = NULL; + CRParser *parser = NULL; + CRTknzr *tokenizer = NULL; + gboolean important = FALSE; + + g_return_val_if_fail (a_str, NULL); + + parser = cr_parser_new_from_buf ((guchar*)a_str, strlen (a_str), a_enc, FALSE); + g_return_val_if_fail (parser, NULL); + status = cr_parser_get_tknzr (parser, &tokenizer); + if (status != CR_OK || !tokenizer) { + if (status == CR_OK) + status = CR_ERROR; + goto cleanup; + } + status = cr_parser_try_to_skip_spaces_and_comments (parser); + if (status != CR_OK) + goto cleanup; + + status = cr_parser_parse_declaration (parser, &property, + &value, &important); + if (status != CR_OK || !property) { + if (status != CR_OK) + status = CR_ERROR; + goto cleanup; + } + result = cr_declaration_new (NULL, property, value); + if (result) { + property = NULL; + value = NULL; + result->important = important; + } + /*now, go parse the other declarations */ + for (;;) { + guint32 c = 0; + + cr_parser_try_to_skip_spaces_and_comments (parser); + status = cr_tknzr_peek_char (tokenizer, &c); + if (status != CR_OK) { + if (status == CR_END_OF_INPUT_ERROR) + status = CR_OK; + goto cleanup; + } + if (c == ';') { + status = cr_tknzr_read_char (tokenizer, &c); + } else { + break; + } + important = FALSE; + cr_parser_try_to_skip_spaces_and_comments (parser); + status = cr_parser_parse_declaration (parser, &property, + &value, &important); + if (status != CR_OK || !property) { + if (status == CR_END_OF_INPUT_ERROR) { + status = CR_OK; + } + break; + } + cur_decl = cr_declaration_new (NULL, property, value); + if (cur_decl) { + cur_decl->important = important; + result = cr_declaration_append (result, cur_decl); + property = NULL; + value = NULL; + cur_decl = NULL; + } else { + break; + } + } + + cleanup: + + if (parser) { + cr_parser_destroy (parser); + parser = NULL; + } + + if (property) { + cr_string_destroy (property); + property = NULL; + } + + if (value) { + cr_term_destroy (value); + value = NULL; + } + + if (status != CR_OK && result) { + cr_declaration_destroy (result); + result = NULL; + } + return result; +} + +/** + *Appends a new declaration to the current declarations list. + *@param a_this the current declaration list. + *@param a_new the declaration to append. + *@return the declaration list with a_new appended to it, or NULL + *in case of error. + */ +CRDeclaration * +cr_declaration_append (CRDeclaration * a_this, CRDeclaration * a_new) +{ + CRDeclaration *cur = NULL; + + g_return_val_if_fail (a_new, NULL); + + if (!a_this) + return a_new; + + for (cur = a_this; cur && cur->next; cur = cur->next) ; + + cur->next = a_new; + a_new->prev = cur; + + return a_this; +} + +/** + *Unlinks the declaration from the declaration list. + *@param a_decl the declaration to unlink. + *@return a pointer to the unlinked declaration in + *case of a successfull completion, NULL otherwise. + */ +CRDeclaration * +cr_declaration_unlink (CRDeclaration * a_decl) +{ + CRDeclaration *result = a_decl; + + g_return_val_if_fail (result, NULL); + + /* + *some sanity checks first + */ + if (a_decl->prev) { + g_return_val_if_fail (a_decl->prev->next == a_decl, NULL); + + } + if (a_decl->next) { + g_return_val_if_fail (a_decl->next->prev == a_decl, NULL); + } + + /* + *now, the real unlinking job. + */ + if (a_decl->prev) { + a_decl->prev->next = a_decl->next; + } + if (a_decl->next) { + a_decl->next->prev = a_decl->prev; + } + if (a_decl->parent_statement) { + CRDeclaration **children_decl_ptr = NULL; + + switch (a_decl->parent_statement->type) { + case RULESET_STMT: + if (a_decl->parent_statement->kind.ruleset) { + children_decl_ptr = + &a_decl->parent_statement-> + kind.ruleset->decl_list; + } + + break; + + case AT_FONT_FACE_RULE_STMT: + if (a_decl->parent_statement->kind.font_face_rule) { + children_decl_ptr = + &a_decl->parent_statement-> + kind.font_face_rule->decl_list; + } + break; + case AT_PAGE_RULE_STMT: + if (a_decl->parent_statement->kind.page_rule) { + children_decl_ptr = + &a_decl->parent_statement-> + kind.page_rule->decl_list; + } + + default: + break; + } + if (children_decl_ptr + && *children_decl_ptr && *children_decl_ptr == a_decl) + *children_decl_ptr = (*children_decl_ptr)->next; + } + + a_decl->next = NULL; + a_decl->prev = NULL; + a_decl->parent_statement = NULL; + + return result; +} + +/** + *prepends a declaration to the current declaration list. + *@param a_this the current declaration list. + *@param a_new the declaration to prepend. + *@return the list with a_new prepended or NULL in case of error. + */ +CRDeclaration * +cr_declaration_prepend (CRDeclaration * a_this, CRDeclaration * a_new) +{ + CRDeclaration *cur = NULL; + + g_return_val_if_fail (a_new, NULL); + + if (!a_this) + return a_new; + + a_this->prev = a_new; + a_new->next = a_this; + + for (cur = a_new; cur && cur->prev; cur = cur->prev) ; + + return cur; +} + +/** + *Appends a declaration to the current declaration list. + *@param a_this the current declaration list. + *@param a_prop the property string of the declaration to append. + *@param a_value the value of the declaration to append. + *@return the list with the new property appended to it, or NULL in + *case of an error. + */ +CRDeclaration * +cr_declaration_append2 (CRDeclaration * a_this, + CRString * a_prop, CRTerm * a_value) +{ + CRDeclaration *new_elem = NULL; + + if (a_this) { + new_elem = cr_declaration_new (a_this->parent_statement, + a_prop, a_value); + } else { + new_elem = cr_declaration_new (NULL, a_prop, a_value); + } + + g_return_val_if_fail (new_elem, NULL); + + return cr_declaration_append (a_this, new_elem); +} + +/** + *Dumps a declaration list to a file. + *@param a_this the current instance of #CRDeclaration. + *@param a_fp the destination file. + *@param a_indent the number of indentation white char. + */ +void +cr_declaration_dump (CRDeclaration * a_this, FILE * a_fp, glong a_indent, + gboolean a_one_per_line) +{ + CRDeclaration *cur = NULL; + + g_return_if_fail (a_this); + + for (cur = a_this; cur; cur = cur->next) { + if (cur->prev) { + if (a_one_per_line == TRUE) + fprintf (a_fp, ";\n"); + else + fprintf (a_fp, "; "); + } + dump (cur, a_fp, a_indent); + } +} + +/** + *Dumps the first declaration of the declaration list to a file. + *@param a_this the current instance of #CRDeclaration. + *@param a_fp the destination file. + *@param a_indent the number of indentation white char. + */ +void +cr_declaration_dump_one (CRDeclaration * a_this, FILE * a_fp, glong a_indent) +{ + g_return_if_fail (a_this); + + dump (a_this, a_fp, a_indent); +} + +/** + *Serializes the declaration into a string + *@param a_this the current instance of #CRDeclaration. + *@param a_indent the number of indentation white char + *to put before the actual serialisation. + */ +gchar * +cr_declaration_to_string (CRDeclaration * a_this, gulong a_indent) +{ + GString *stringue = NULL; + + guchar *str = NULL, + *result = NULL; + + g_return_val_if_fail (a_this, NULL); + + stringue = g_string_new (NULL); + + if (a_this->property + && a_this->property->stryng + && a_this->property->stryng->str) { + str = g_strndup (a_this->property->stryng->str, + a_this->property->stryng->len); + if (str) { + cr_utils_dump_n_chars2 (' ', stringue, + a_indent); + g_string_append (stringue, str); + g_free (str); + str = NULL; + } else + goto error; + + if (a_this->value) { + guchar *value_str = NULL; + + value_str = cr_term_to_string (a_this->value); + if (value_str) { + g_string_append_printf (stringue, " : %s", + value_str); + g_free (value_str); + } else + goto error; + } + if (a_this->important == TRUE) { + g_string_append_printf (stringue, " %s", + "!important"); + } + } + if (stringue && stringue->str) { + result = stringue->str; + g_string_free (stringue, FALSE); + } + return result; + + error: + if (stringue) { + g_string_free (stringue, TRUE); + stringue = NULL; + } + if (str) { + g_free (str); + str = NULL; + } + + return result; +} + +/** + *Serializes the declaration list into a string + *@param a_this the current instance of #CRDeclaration. + *@param a_indent the number of indentation white char + *to put before the actual serialisation. + */ +guchar * +cr_declaration_list_to_string (CRDeclaration * a_this, gulong a_indent) +{ + CRDeclaration *cur = NULL; + GString *stringue = NULL; + guchar *str = NULL, + *result = NULL; + + g_return_val_if_fail (a_this, NULL); + + stringue = g_string_new (NULL); + + for (cur = a_this; cur; cur = cur->next) { + str = cr_declaration_to_string (cur, a_indent); + if (str) { + g_string_append_printf (stringue, "%s;", str); + g_free (str); + } else + break; + } + if (stringue && stringue->str) { + result = stringue->str; + g_string_free (stringue, FALSE); + } + + return result; +} + +/** + *Serializes the declaration list into a string + *@param a_this the current instance of #CRDeclaration. + *@param a_indent the number of indentation white char + *to put before the actual serialisation. + */ +guchar * +cr_declaration_list_to_string2 (CRDeclaration * a_this, + gulong a_indent, gboolean a_one_decl_per_line) +{ + CRDeclaration *cur = NULL; + GString *stringue = NULL; + guchar *str = NULL, + *result = NULL; + + g_return_val_if_fail (a_this, NULL); + + stringue = g_string_new (NULL); + + for (cur = a_this; cur; cur = cur->next) { + str = cr_declaration_to_string (cur, a_indent); + if (str) { + if (a_one_decl_per_line == TRUE) { + if (cur->next) + g_string_append_printf (stringue, + "%s;\n", str); + else + g_string_append (stringue, + str); + } else { + if (cur->next) + g_string_append_printf (stringue, + "%s;", str); + else + g_string_append (stringue, + str); + } + g_free (str); + } else + break; + } + if (stringue && stringue->str) { + result = stringue->str; + g_string_free (stringue, FALSE); + } + + return result; +} + +/** + *Return the number of properties in the declaration; + *@param a_this the current instance of #CRDeclaration. + *@return number of properties in the declaration list. + */ +gint +cr_declaration_nr_props (CRDeclaration * a_this) +{ + CRDeclaration *cur = NULL; + int nr = 0; + + g_return_val_if_fail (a_this, -1); + + for (cur = a_this; cur; cur = cur->next) + nr++; + return nr; +} + +/** + *Use an index to get a CRDeclaration from the declaration list. + *@param a_this the current instance of #CRDeclaration. + *@param itemnr the index into the declaration list. + *@return CRDeclaration at position itemnr, if itemnr > number of declarations - 1, + *it will return NULL. + */ +CRDeclaration * +cr_declaration_get_from_list (CRDeclaration * a_this, int itemnr) +{ + CRDeclaration *cur = NULL; + int nr = 0; + + g_return_val_if_fail (a_this, NULL); + + for (cur = a_this; cur; cur = cur->next) + if (nr++ == itemnr) + return cur; + return NULL; +} + +/** + *Use property name to get a CRDeclaration from the declaration list. + *@param a_this the current instance of #CRDeclaration. + *@param a_prop the property name to search for. + *@return CRDeclaration with property name a_prop, or NULL if not found. + */ +CRDeclaration * +cr_declaration_get_by_prop_name (CRDeclaration * a_this, + const guchar * a_prop) +{ + CRDeclaration *cur = NULL; + + g_return_val_if_fail (a_this, NULL); + g_return_val_if_fail (a_prop, NULL); + + for (cur = a_this; cur; cur = cur->next) { + if (cur->property + && cur->property->stryng + && cur->property->stryng->str) { + if (!strcmp (cur->property->stryng->str, + a_prop)) { + return cur; + } + } + } + return NULL; +} + +/** + *Increases the ref count of the current instance of #CRDeclaration. + *@param a_this the current instance of #CRDeclaration. + */ +void +cr_declaration_ref (CRDeclaration * a_this) +{ + g_return_if_fail (a_this); + + a_this->ref_count++; +} + +/** + *Decrements the ref count of the current instance of #CRDeclaration. + *If the ref count reaches zero, the current instance of #CRDeclaration + *if destroyed. + *@param a_this the current instance of #CRDeclaration. + *@return TRUE if the current instance of #CRDeclaration has been destroyed + *(ref count reached zero), FALSE otherwise. + */ +gboolean +cr_declaration_unref (CRDeclaration * a_this) +{ + g_return_val_if_fail (a_this, FALSE); + + if (a_this->ref_count) { + a_this->ref_count--; + } + + if (a_this->ref_count == 0) { + cr_declaration_destroy (a_this); + return TRUE; + } + return FALSE; +} + +/** + *Destructor of the declaration list. + *@param a_this the current instance of #CRDeclaration. + */ +void +cr_declaration_destroy (CRDeclaration * a_this) +{ + CRDeclaration *cur = NULL; + + g_return_if_fail (a_this); + + /* + *Go get the tail of the list. + *Meanwhile, free each property/value pair contained in the list. + */ + for (cur = a_this; cur && cur->next; cur = cur->next) { + if (cur->property) { + cr_string_destroy (cur->property); + cur->property = NULL; + } + + if (cur->value) { + cr_term_destroy (cur->value); + cur->value = NULL; + } + } + + if (cur) { + if (cur->property) { + cr_string_destroy (cur->property); + cur->property = NULL; + } + + if (cur->value) { + cr_term_destroy (cur->value); + cur->value = NULL; + } + } + + /*in case the list contains only one element */ + if (cur && !cur->prev) { + g_free (cur); + return; + } + + /*walk backward the list and free each "next" element */ + for (cur = cur->prev; cur && cur->prev; cur = cur->prev) { + if (cur->next) { + g_free (cur->next); + cur->next = NULL; + } + } + + if (!cur) + return; + + if (cur->next) { + g_free (cur->next); + cur->next = NULL; + } + + g_free (cur); +} diff --git a/src/libcroco/cr-declaration.h b/src/libcroco/cr-declaration.h new file mode 100644 index 000000000..00523956c --- /dev/null +++ b/src/libcroco/cr-declaration.h @@ -0,0 +1,136 @@ +/* -*- Mode: C; indent-tabs-mode: ni; c-basic-offset: 8 -*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * See the COPYRIGHTS file for copyright information. + */ + +#ifndef __CR_DECLARATION_H__ +#define __CR_DECLARATION_H__ + +#include +#include "cr-utils.h" +#include "cr-term.h" +#include "cr-parsing-location.h" + +G_BEGIN_DECLS + +/** + *@file + *The declaration of the #CRDeclaration class. + */ + +/*forward declaration of what is defined in cr-statement.h*/ +typedef struct _CRStatement CRStatement ; + +/** + *The abstraction of a css declaration defined by the + *css2 spec in chapter 4. + *It is actually a chained list of property/value pairs. + */ +typedef struct _CRDeclaration CRDeclaration ; +struct _CRDeclaration +{ + /**The property.*/ + CRString *property ; + + /**The value of the property.*/ + CRTerm *value ; + + /*the ruleset that contains this declaration*/ + CRStatement *parent_statement ; + + /*the next declaration*/ + CRDeclaration *next ; + + /*the previous one declaration*/ + CRDeclaration *prev ; + + /*does the declaration have the important keyword ?*/ + gboolean important ; + + glong ref_count ; + + CRParsingLocation location ; + /*reserved for future usage*/ + gpointer rfu0 ; + gpointer rfu1 ; + gpointer rfu2 ; + gpointer rfu3 ; +} ; + + +CRDeclaration * cr_declaration_new (CRStatement *a_statement, + CRString *a_property, + CRTerm *a_value) ; + + +CRDeclaration * cr_declaration_parse_from_buf (CRStatement *a_statement, + const guchar *a_str, + enum CREncoding a_enc) ; + +CRDeclaration * cr_declaration_parse_list_from_buf (const guchar *a_str, + enum CREncoding a_enc) ; + +CRDeclaration * cr_declaration_append (CRDeclaration *a_this, + CRDeclaration *a_new) ; + +CRDeclaration * cr_declaration_append2 (CRDeclaration *a_this, + CRString *a_prop, + CRTerm *a_value) ; + +CRDeclaration * cr_declaration_prepend (CRDeclaration *a_this, + CRDeclaration *a_new) ; + +CRDeclaration * cr_declaration_unlink (CRDeclaration * a_decl) ; + +void +cr_declaration_dump (CRDeclaration *a_this, + FILE *a_fp, glong a_indent, + gboolean a_one_per_line) ; + +void cr_declaration_dump_one (CRDeclaration *a_this, + FILE *a_fp, glong a_indent) ; + +gint cr_declaration_nr_props (CRDeclaration *a_this) ; + +CRDeclaration * cr_declaration_get_from_list (CRDeclaration *a_this, + int itemnr) ; + +CRDeclaration * cr_declaration_get_by_prop_name (CRDeclaration *a_this, + const guchar *a_str) ; + +gchar * cr_declaration_to_string (CRDeclaration *a_this, + gulong a_indent) ; + +guchar * cr_declaration_list_to_string (CRDeclaration *a_this, + gulong a_indent) ; + +guchar * cr_declaration_list_to_string2 (CRDeclaration *a_this, + gulong a_indent, + gboolean a_one_decl_per_line) ; + +void cr_declaration_ref (CRDeclaration *a_this) ; + +gboolean cr_declaration_unref (CRDeclaration *a_this) ; + +void cr_declaration_destroy (CRDeclaration *a_this) ; + +G_END_DECLS + +#endif /*__CR_DECLARATION_H__*/ diff --git a/src/libcroco/cr-doc-handler.c b/src/libcroco/cr-doc-handler.c new file mode 100644 index 000000000..2780b6a20 --- /dev/null +++ b/src/libcroco/cr-doc-handler.c @@ -0,0 +1,252 @@ +/* -*- Mode: C; indent-tabs-mode: ni; c-basic-offset: 8 -*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * See COPRYRIGHTS file for copyright information. + */ + +#include +#include "cr-doc-handler.h" +#include "cr-parser.h" + +/** + *@file + *The definition of the CRDocHandler class. + *Contains methods to instantiate, destroy, + *and initialyze instances of #CRDocHandler + *to custom values. + */ + +#define PRIVATE(obj) (obj)->priv + +struct _CRDocHandlerPriv { + /** + *This pointer is to hold an application parsing context. + *For example, it used by the Object Model parser to + *store it parsing context. #CRParser does not touch it, but + *#CROMParser does. #CROMParser allocates this pointer at + *the beginning of the css document, and frees it at the end + *of the document. + */ + gpointer context; + + /** + *The place where #CROMParser puts the result of its parsing, if + *any. + */ + gpointer result; + /** + *a pointer to the parser used to parse + *the current document. + */ + CRParser *parser ; +}; + +/** + *Constructor of #CRDocHandler. + *@return the newly built instance of + *#CRDocHandler + */ +CRDocHandler * +cr_doc_handler_new (void) +{ + CRDocHandler *result = NULL; + + result = g_try_malloc (sizeof (CRDocHandler)); + + g_return_val_if_fail (result, NULL); + + memset (result, 0, sizeof (CRDocHandler)); + + result->priv = g_try_malloc (sizeof (CRDocHandlerPriv)); + if (!result->priv) { + cr_utils_trace_info ("Out of memory exception"); + g_free (result); + return NULL; + } + + cr_doc_handler_set_default_sac_handler (result); + + return result; +} + +/** + *Returns the private parsing context. + *The private parsing context is used by libcroco only. + *@param a_this the current instance of #CRDocHandler. + *@param a_ctxt out parameter. The new parsing context. + *@return CR_OK upon successfull completion, an error code otherwise. + *@return the parsing context, or NULL if an error occured. + */ +enum CRStatus +cr_doc_handler_get_ctxt (CRDocHandler * a_this, gpointer * a_ctxt) +{ + g_return_val_if_fail (a_this && a_this->priv, CR_BAD_PARAM_ERROR); + + *a_ctxt = a_this->priv->context; + + return CR_OK; +} + +/** + *Sets the private parsing context. + *This is used by libcroco only. + *@param a_this the current instance of #CRDocHandler + *@param a_ctxt a pointer to the parsing context. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_doc_handler_set_ctxt (CRDocHandler * a_this, gpointer a_ctxt) +{ + g_return_val_if_fail (a_this && a_this->priv, CR_BAD_PARAM_ERROR); + a_this->priv->context = a_ctxt; + return CR_OK; +} + +/** + *Returns the private parsing result. + *The private parsing result is used by libcroco only. + *@param a_this the current instance of #CRDocHandler + *@param a_result out parameter. The returned result. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_doc_handler_get_result (CRDocHandler * a_this, gpointer * a_result) +{ + g_return_val_if_fail (a_this && a_this->priv, CR_BAD_PARAM_ERROR); + + *a_result = a_this->priv->result; + + return CR_OK; +} + +/** + *Sets the private parsing context. + *This is used by libcroco only. + *@param a_this the current instance of #CRDocHandler + *@param a_result the new result. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_doc_handler_set_result (CRDocHandler * a_this, gpointer a_result) +{ + g_return_val_if_fail (a_this && a_this->priv, CR_BAD_PARAM_ERROR); + a_this->priv->result = a_result; + return CR_OK; +} + +/** + *Sets the sac handlers contained in the current + *instance of DocHandler to the default handlers. + *For the time being the default handlers are + *test handlers. This is expected to change in a + *near future, when the libcroco gets a bit debugged. + * + *@param a_this a pointer to the current instance of #CRDocHandler. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_doc_handler_set_default_sac_handler (CRDocHandler * a_this) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + a_this->start_document = NULL; + a_this->end_document = NULL; + a_this->import_style = NULL; + a_this->namespace_declaration = NULL; + a_this->comment = NULL; + a_this->start_selector = NULL; + a_this->end_selector = NULL; + a_this->property = NULL; + a_this->start_font_face = NULL; + a_this->end_font_face = NULL; + a_this->start_media = NULL; + a_this->end_media = NULL; + a_this->start_page = NULL; + a_this->end_page = NULL; + a_this->ignorable_at_rule = NULL; + a_this->error = NULL; + a_this->unrecoverable_error = NULL; + return CR_OK; +} + +/** + *Increases the reference count of the doc handler + *@param a_this the current instance of #CRDocHandler. + */ +void +cr_doc_handler_ref (CRDocHandler * a_this) +{ + g_return_if_fail (a_this); + + a_this->ref_count++; +} + +/** + *Decreases the ref count of the current instance of #CRDocHandler. + *If the ref count reaches '0' then, destroys the instance. + *@param a_this the currrent instance of #CRDocHandler. + *@return TRUE if the instance as been destroyed, FALSE otherwise. + */ +gboolean +cr_doc_handler_unref (CRDocHandler * a_this) +{ + g_return_val_if_fail (a_this, FALSE); + + if (a_this->ref_count > 0) { + a_this->ref_count--; + } + + if (a_this->ref_count == 0) { + cr_doc_handler_destroy (a_this); + return TRUE; + } + return FALSE ; +} + +/** + *The destructor of the #CRDocHandler class. + *@param a_this the instance of #CRDocHandler to + *destroy. + */ +void +cr_doc_handler_destroy (CRDocHandler * a_this) +{ + g_return_if_fail (a_this); + + if (a_this->priv) { + g_free (a_this->priv); + a_this->priv = NULL; + } + g_free (a_this); +} + +/** + *Associates a parser to the current document handler + *@param a_this the current instance of document handler. + *@param a_parser the parser to associate. + */ +void +cr_doc_handler_associate_a_parser (CRDocHandler *a_this, + gpointer a_parser) +{ + g_return_if_fail (a_this && PRIVATE (a_this) + && a_parser) ; + + PRIVATE (a_this)->parser = a_parser ; +} diff --git a/src/libcroco/cr-doc-handler.h b/src/libcroco/cr-doc-handler.h new file mode 100644 index 000000000..704f186f5 --- /dev/null +++ b/src/libcroco/cr-doc-handler.h @@ -0,0 +1,298 @@ +/* -*- Mode: C; indent-tabs-mode: ni; c-basic-offset: 8 -*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * See the COPYRIGHTS file for copyright information. + */ + +#ifndef __CR_DOC_HANDLER_H__ +#define __CR_DOC_HANDLER_H__ + +/** + *@file + *The declaration of the #CRDocumentHandler class. + *This class is actually the parsing events handler. + */ + +#include +#include "cr-utils.h" +#include "cr-input.h" +#include "cr-stylesheet.h" + +G_BEGIN_DECLS + + +typedef struct _CRDocHandler CRDocHandler ; + +struct _CRDocHandlerPriv ; +typedef struct _CRDocHandlerPriv CRDocHandlerPriv ; + + +/** + *The SAC document handler. + *An instance of this class is to + *be passed to a parser. Then, during the parsing + *the parser calls the convenient function pointer + *whenever a particular event (a css construction) occurs. + */ +struct _CRDocHandler +{ + CRDocHandlerPriv *priv ; + + /** + *This pointer is to be used by the application for + *it custom needs. It is there to extend the doc handler. + */ + gpointer app_data ; + + /** + *Is called at the beginning of the parsing of the document. + *@param a_this a pointer to the current instance of + *#CRDocHandler. + */ + void (*start_document) (CRDocHandler *a_this) ; + + /** + *Is called to notify the end of the parsing of the document. + *@param a_this a pointer to the current instance of + *#CRDocHandler. + */ + void (*end_document) (CRDocHandler *a_this) ; + + /** + *Is called to notify an at charset rule. + *@param a_this the document handler. + *@param a_charset the declared charset. + */ + void (*charset) (CRDocHandler *a_this, + CRString *a_charset, + CRParsingLocation *a_charset_sym_location) ; + + /** + *Is called to notify an import statement in + *the stylesheet. + *@param a_this the current instance of #CRDocHandler. + *@param a_media_list a doubly linked list of GString objects. + *Each GString object contains a string which is the + *destination media for style information. + *@param a_uri the uri of the imported style sheet. + *@param a_uri_default_ns the default namespace of URI + *@param a_location the parsing location of the '@import' + *keyword. + *of the imported style sheet. + */ + void (*import_style) (CRDocHandler *a_this, + GList *a_media_list, + CRString *a_uri, + CRString *a_uri_default_ns, + CRParsingLocation *a_location) ; + + void (*import_style_result) (CRDocHandler *a_this, + GList *a_media_list, + CRString *a_uri, + CRString *a_uri_default_ns, + CRStyleSheet *a_sheet) ; + + /** + *Is called to notify a namespace declaration. + *Not used yet. + *@param a_this the current instance of #CRDocHandler. + *@param a_prefix the prefix of the namespace. + *@param a_uri the uri of the namespace. + *@param a_location the location of the "@namespace" keyword. + */ + void (*namespace_declaration) (CRDocHandler *a_this, + CRString *a_prefix, + CRString *a_uri, + CRParsingLocation *a_location) ; + + /** + *Is called to notify a comment. + *@param a_this a pointer to the current instance + *of #CRDocHandler. + *@param a_comment the comment. + */ + void (*comment) (CRDocHandler *a_this, + CRString *a_comment) ; + + /** + *Is called to notify the beginning of a rule + *statement. + *@param a_this the current instance of #CRDocHandler. + *@param a_selector_list the list of selectors that precedes + *the rule declarations. + */ + void (*start_selector) (CRDocHandler * a_this, + CRSelector *a_selector_list) ; + + /** + *Is called to notify the end of a rule statement. + *@param a_this the current instance of #CRDocHandler. + *@param a_selector_list the list of selectors that precedes + *the rule declarations. This pointer is the same as + *the one passed to start_selector() ; + */ + void (*end_selector) (CRDocHandler *a_this, + CRSelector *a_selector_list) ; + + + /** + *Is called to notify a declaration. + *@param a_this a pointer to the current instance + *of #CRDocHandler. + *@param a_name the name of the parsed property. + *@param a_expression a css expression that represents + *the value of the property. A css expression is + *actually a linked list of 'terms'. Each term can + *be linked to other using operators. + * + */ + void (*property) (CRDocHandler *a_this, + CRString *a_name, + CRTerm *a_expression, + gboolean a_is_important) ; + /** + *Is called to notify the start of a font face statement. + *The parser invokes this method at the beginning of every + *font face statement in the style sheet. There will + *be a corresponding end_font_face () event for every + *start_font_face () event. + * + *@param a_this a pointer to the current instance of + *#CRDocHandler. + *@param a_location the parsing location of the "@font-face" + *keyword. + */ + void (*start_font_face) (CRDocHandler *a_this, + CRParsingLocation *a_location) ; + + /** + *Is called to notify the end of a font face statement. + *@param a_this a pointer to the current instance of + *#CRDocHandler. + */ + void (*end_font_face) (CRDocHandler *a_this) ; + + + /** + *Is called to notify the beginning of a media statement. + *The parser will invoke this method at the beginning of + *every media statement in the style sheet. There will be + *a corresponding end_media() event for every start_media() + *event. + *@param a_this a pointer to the current instance of + *#CRDocHandler. + *@param a_media_list a double linked list of + #CRString * objects. + *Each CRString objects is actually a destination media for + *the style information. + */ + void (*start_media) (CRDocHandler *a_this, + GList *a_media_list, + CRParsingLocation *a_location) ; + + /** + *Is called to notify the end of a media statement. + *@param a_this a pointer to the current instance + *of #CRDocHandler. + *@param a_media_list a double linked list of GString * objects. + *Each GString objects is actually a destination media for + *the style information. + */ + void (*end_media) (CRDocHandler *a_this, + GList *a_media_list) ; + + /** + *Is called to notify the beginning of a page statement. + *The parser invokes this function at the beginning of + *every page statement in the style sheet. There will be + *a corresponding end_page() event for every single + *start_page() event. + *@param a_this a pointer to the current instance of + *#CRDocHandler. + *@param a_name the name of the page (if any, null otherwise). + *@param a_pseudo_page the pseudo page (if any, null otherwise). + *@param a_location the parsing location of the "@page" keyword. + */ + void (*start_page) (CRDocHandler *a_this, + CRString *a_name, + CRString *a_pseudo_page, + CRParsingLocation *a_location) ; + + /** + *Is called to notify the end of a page statement. + *@param a_this a pointer to the current instance of + *#CRDocHandler. + *@param a_name the name of the page (if any, null otherwise). + *@parap a_pseudo_page the pseudo page (if any, null otherwise). + */ + void (*end_page) (CRDocHandler *a_this, + CRString *a_name, + CRString *pseudo_page) ; + + /** + *Is Called to notify an unknown at-rule not supported + *by this parser. + */ + void (*ignorable_at_rule) (CRDocHandler *a_this, + CRString *a_name) ; + + /** + *Is called to notify a parsing error. After this error + *the application must ignore the rule being parsed, if + *any. After completion of this callback, + *the parser will then try to resume the parsing, + *ignoring the current error. + */ + void (*error) (CRDocHandler *a_this) ; + + /** + *Is called to notify an unrecoverable parsing error. + *This is the place to put emergency routines that free allocated + *resources. + */ + void (*unrecoverable_error) (CRDocHandler *a_this) ; + + gboolean resolve_import ; + gulong ref_count ; +} ; + +CRDocHandler * cr_doc_handler_new (void) ; + +enum CRStatus cr_doc_handler_set_result (CRDocHandler *a_this, gpointer a_result) ; + +enum CRStatus cr_doc_handler_get_result (CRDocHandler *a_this, gpointer * a_result) ; + +enum CRStatus cr_doc_handler_set_ctxt (CRDocHandler *a_this, gpointer a_ctxt) ; + +enum CRStatus cr_doc_handler_get_ctxt (CRDocHandler *a_this, gpointer * a_ctxt) ; + +enum CRStatus cr_doc_handler_set_default_sac_handler (CRDocHandler *a_this) ; + +void cr_doc_handler_associate_a_parser (CRDocHandler *a_this, + gpointer a_parser) ; + +void cr_doc_handler_ref (CRDocHandler *a_this) ; + +gboolean cr_doc_handler_unref (CRDocHandler *a_this) ; + +void cr_doc_handler_destroy (CRDocHandler *a_this) ; + +G_END_DECLS + +#endif /*__CR_DOC_HANDLER_H__*/ diff --git a/src/libcroco/cr-enc-handler.c b/src/libcroco/cr-enc-handler.c new file mode 100644 index 000000000..509793ab7 --- /dev/null +++ b/src/libcroco/cr-enc-handler.c @@ -0,0 +1,177 @@ +/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ + +/* + * This file is part of The Croco Library + * + * Copyright (C) 2002-2003 Dodji Seketeli + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +/* + *$Id: cr-enc-handler.c,v 1.7 2004/03/07 13:22:47 dodji Exp $ + */ + +/** + *@file + *The definition of the #CREncHandler class. + */ + +#include "cr-enc-handler.h" +#include "cr-utils.h" + +#include + +struct CREncAlias { + const gchar *name; + enum CREncoding encoding; +}; + +static struct CREncAlias gv_default_aliases[] = { + {"UTF-8", CR_UTF_8}, + {"UTF_8", CR_UTF_8}, + {"UTF8", CR_UTF_8}, + {"UTF-16", CR_UTF_16}, + {"UTF_16", CR_UTF_16}, + {"UTF16", CR_UTF_16}, + {"UCS1", CR_UCS_1}, + {"UCS-1", CR_UCS_1}, + {"UCS_1", CR_UCS_1}, + {"ISO-8859-1", CR_UCS_1}, + {"ISO_8859-1", CR_UCS_1}, + {"UCS-1", CR_UCS_1}, + {"UCS_1", CR_UCS_1}, + {"UCS4", CR_UCS_4}, + {"UCS-4", CR_UCS_4}, + {"UCS_4", CR_UCS_4}, + {"ASCII", CR_ASCII}, + {0, 0} +}; + +static CREncHandler gv_default_enc_handlers[] = { + {CR_UCS_1, cr_utils_ucs1_to_utf8, cr_utils_utf8_to_ucs1, + cr_utils_ucs1_str_len_as_utf8, cr_utils_utf8_str_len_as_ucs1}, + + {CR_ISO_8859_1, cr_utils_ucs1_to_utf8, cr_utils_utf8_to_ucs1, + cr_utils_ucs1_str_len_as_utf8, cr_utils_utf8_str_len_as_ucs1}, + + {CR_ASCII, cr_utils_ucs1_to_utf8, cr_utils_utf8_to_ucs1, + cr_utils_ucs1_str_len_as_utf8, cr_utils_utf8_str_len_as_ucs1}, + + {0, NULL, NULL, NULL, NULL} +}; + +/** + *Gets the instance of encoding handler. + *This function implements a singleton pattern. + *@param a_enc the encoding of the Handler. + *@return the instance of #CREncHandler. + */ +CREncHandler * +cr_enc_handler_get_instance (enum CREncoding a_enc) +{ + gulong i = 0; + + for (i = 0; gv_default_enc_handlers[i].encoding; i++) { + if (gv_default_enc_handlers[i].encoding == a_enc) { + return (CREncHandler *) + & gv_default_enc_handlers[i].encoding; + } + } + + return NULL; +} + +/** + *Given an encoding name (called an alias name) + *the function returns the matching encoding type. + *@param a_alias_name the encoding name + *@param a_enc output param. The returned encoding type + *or 0 if the alias is not supported. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_enc_handler_resolve_enc_alias (const guchar * a_alias_name, + enum CREncoding *a_enc) +{ + gulong i = 0; + guchar *alias_name_up = NULL; + enum CRStatus status = CR_ENCODING_NOT_FOUND_ERROR; + + g_return_val_if_fail (a_alias_name != NULL, CR_BAD_PARAM_ERROR); + + alias_name_up = g_strdup (a_alias_name); + g_ascii_strup (alias_name_up, -1); + + for (i = 0; gv_default_aliases[i].name; i++) { + if (!strcmp (gv_default_aliases[i].name, alias_name_up)) { + *a_enc = gv_default_aliases[i].encoding; + status = CR_OK; + break; + } + } + + return status; +} + +/** + *Converts a raw input buffer into an utf8 buffer. + *@param a_this the current instance of #CREncHandler. + *@param a_in the input buffer to convert. + *@param a_in_len in/out parameter. The len of the input + *buffer to convert. After return, contains the number of + *bytes actually consumed. + *@param @a_out output parameter. The converted output buffer. + *Must be freed by the buffer. + *@param a_out_len output parameter. The length of the output buffer. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_enc_handler_convert_input (CREncHandler * a_this, + const guchar * a_in, + gulong * a_in_len, + guchar ** a_out, gulong * a_out_len) +{ + enum CRStatus status = CR_OK; + + g_return_val_if_fail (a_this && a_in && a_in_len && a_out, + CR_BAD_PARAM_ERROR); + + if (a_this->decode_input == NULL) + return CR_OK; + + if (a_this->enc_str_len_as_utf8) { + status = a_this->enc_str_len_as_utf8 (a_in, + &a_in[*a_in_len - 1], + a_out_len); + + g_return_val_if_fail (status == CR_OK, status); + } else { + *a_out_len = *a_in_len; + } + + *a_out = g_malloc0 (*a_out_len); + + status = a_this->decode_input (a_in, a_in_len, *a_out, a_out_len); + + if (status != CR_OK) { + g_free (*a_out); + *a_out = NULL; + } + + g_return_val_if_fail (status == CR_OK, status); + + return CR_OK; +} diff --git a/src/libcroco/cr-enc-handler.h b/src/libcroco/cr-enc-handler.h new file mode 100644 index 000000000..2f5da69f9 --- /dev/null +++ b/src/libcroco/cr-enc-handler.h @@ -0,0 +1,96 @@ +/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ + +/* + * This file is part of The Croco Library + * + * Copyright (C) 2002-2003 Dodji Seketeli + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +/* + *$Id: cr-enc-handler.h,v 1.5 2004/01/24 19:24:01 dodji Exp $ + */ + +/** + *@file: + *The declaration of the #CREncHandler class. + * + */ + +#ifndef __CR_ENC_HANDLER_H__ +#define __CR_ENC_HANDLER_H__ + +#include "cr-utils.h" + +G_BEGIN_DECLS + + +typedef struct _CREncHandler CREncHandler ; + +typedef enum CRStatus (*CREncInputFunc) (const guchar * a_in, + gulong *a_in_len, + guchar *a_out, + gulong *a_out_len) ; + +typedef enum CRStatus (*CREncOutputFunc) (const guchar * a_in, + gulong *a_in_len, + guchar *a_out, + gulong *a_out_len) ; + +typedef enum CRStatus (*CREncInputStrLenAsUtf8Func) +(const guchar *a_in_start, + const guchar *a_in_end, + gulong *a_in_size); + +typedef enum CRStatus (*CREncUtf8StrLenAsOutputFunc) +(const guchar *a_in_start, + const guchar *a_in_end, + gulong *a_in_size) ; + +/** + *This class is responsible of the + *the encoding conversions stuffs in + *libcroco. + */ + +struct _CREncHandler +{ + enum CREncoding encoding ; + CREncInputFunc decode_input ; + CREncInputFunc encode_output ; + CREncInputStrLenAsUtf8Func enc_str_len_as_utf8 ; + CREncUtf8StrLenAsOutputFunc utf8_str_len_as_enc ; +} ; + +CREncHandler * +cr_enc_handler_get_instance (enum CREncoding a_enc) ; + +enum CRStatus +cr_enc_handler_resolve_enc_alias (const guchar *a_en_alias, + enum CREncoding *a_enc) ; +void +cr_enc_handler_destroy (CREncHandler * a_enc_hdlr) ; + +enum CRStatus +cr_enc_handler_convert_input (CREncHandler *a_this, + const guchar *a_in, + gulong *a_in_len, + guchar **a_out, + gulong *a_out_len) ; + +G_END_DECLS + +#endif /*__CR_ENC_HANDLER_H__*/ diff --git a/src/libcroco/cr-fonts.c b/src/libcroco/cr-fonts.c new file mode 100644 index 000000000..8c87aa229 --- /dev/null +++ b/src/libcroco/cr-fonts.c @@ -0,0 +1,761 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of + * the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the + * GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + *See COPYRIGHTS file for copyright information + */ + +#include "cr-fonts.h" +#include + +static enum CRStatus +cr_font_family_to_string_real (CRFontFamily * a_this, + gboolean a_walk_list, GString ** a_string) +{ + guchar *name = NULL; + enum CRStatus result = CR_OK; + + if (!*a_string) { + *a_string = g_string_new (NULL); + g_return_val_if_fail (*a_string, + CR_INSTANCIATION_FAILED_ERROR); + } + + if (!a_this) { + g_string_append (*a_string, "NULL"); + return CR_OK; + } + + switch (a_this->type) { + case FONT_FAMILY_SANS_SERIF: + name = (guchar *) "sans-serif"; + break; + + case FONT_FAMILY_SERIF: + name = (guchar *) "sans-serif"; + break; + + case FONT_FAMILY_CURSIVE: + name = (guchar *) "cursive"; + break; + + case FONT_FAMILY_FANTASY: + name = (guchar *) "fantasy"; + break; + + case FONT_FAMILY_MONOSPACE: + name = (guchar *) "monospace"; + break; + + case FONT_FAMILY_NON_GENERIC: + name = (guchar *) a_this->name; + break; + + default: + name = (guchar *) NULL; + break; + } + + if (name) { + if (a_this->prev) { + g_string_append_printf (*a_string, ", %s", name); + } else { + g_string_append (*a_string, name); + } + } + if (a_walk_list == TRUE && a_this->next) { + result = cr_font_family_to_string_real (a_this->next, + TRUE, a_string); + } + return result; +} + +static const gchar * +cr_predefined_absolute_font_size_to_string (enum CRPredefinedAbsoluteFontSize + a_code) +{ + gchar *str = NULL; + + switch (a_code) { + case FONT_SIZE_XX_SMALL: + str = (gchar *) "xx-small"; + break; + case FONT_SIZE_X_SMALL: + str = (gchar *) "x-small"; + break; + case FONT_SIZE_SMALL: + str = (gchar *) "small"; + break; + case FONT_SIZE_MEDIUM: + str = (gchar *) "medium"; + break; + case FONT_SIZE_LARGE: + str = (gchar *) "large"; + break; + case FONT_SIZE_X_LARGE: + str = (gchar *) "x-large"; + break; + case FONT_SIZE_XX_LARGE: + str = (gchar *) "xx-large"; + break; + default: + str = (gchar *) "unknown absolute font size value"; + } + return str; +} + +static const gchar * +cr_relative_font_size_to_string (enum CRRelativeFontSize a_code) +{ + gchar *str = NULL; + + switch (a_code) { + case FONT_SIZE_LARGER: + str = (gchar *) "larger"; + break; + case FONT_SIZE_SMALLER: + str = (gchar *) "smaller"; + break; + default: + str = (gchar *) "unknown relative font size value"; + break; + } + return str; +} + +CRFontFamily * +cr_font_family_new (enum CRFontFamilyType a_type, guchar * a_name) +{ + CRFontFamily *result = NULL; + + result = g_try_malloc (sizeof (CRFontFamily)); + + if (!result) { + cr_utils_trace_info ("Out of memory"); + return NULL; + } + + memset (result, 0, sizeof (CRFontFamily)); + result->type = a_type; + + cr_font_family_set_name (result, a_name); + + return result; +} + +guchar * +cr_font_family_to_string (CRFontFamily * a_this, + gboolean a_walk_font_family_list) +{ + enum CRStatus status = CR_OK; + guchar *result = NULL; + GString *stringue = NULL; + + if (!a_this) { + result = g_strdup ("NULL"); + g_return_val_if_fail (result, NULL); + return result; + } + status = cr_font_family_to_string_real (a_this, + a_walk_font_family_list, + &stringue); + + if (status == CR_OK && stringue) { + result = stringue->str; + g_string_free (stringue, FALSE); + stringue = NULL; + + } else { + if (stringue) { + g_string_free (stringue, TRUE); + stringue = NULL; + } + } + + return result; +} +enum CRStatus +cr_font_family_set_name (CRFontFamily * a_this, guchar * a_name) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + /* + *only non generic font families can have a name + */ + + if (a_this->type != FONT_FAMILY_NON_GENERIC) { + return CR_BAD_PARAM_ERROR; + } + + if (a_this->name) { + g_free (a_this->name); + a_this->name = NULL; + } + + a_this->name = a_name; + return CR_OK; +} + +CRFontFamily * +cr_font_family_append (CRFontFamily * a_this, + CRFontFamily * a_family_to_append) +{ + CRFontFamily *cur_ff = NULL; + + g_return_val_if_fail (a_family_to_append, NULL); + + if (!a_this) + return a_family_to_append; + + for (cur_ff = a_this; cur_ff && cur_ff->next; cur_ff = cur_ff->next) ; + + cur_ff->next = a_family_to_append; + a_family_to_append->prev = cur_ff; + + return a_this; + +} + +CRFontFamily * +cr_font_family_prepend (CRFontFamily * a_this, + CRFontFamily * a_family_to_prepend) +{ + g_return_val_if_fail (a_this && a_family_to_prepend, NULL); + + if (!a_this) + return a_family_to_prepend; + + a_family_to_prepend->next = a_this; + a_this->prev = a_family_to_prepend; + + return CR_OK; +} + +enum CRStatus +cr_font_family_destroy (CRFontFamily * a_this) +{ + CRFontFamily *cur_ff = NULL; + + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + for (cur_ff = a_this; cur_ff && cur_ff->next; cur_ff = cur_ff->next) ; + + for (; cur_ff; cur_ff = cur_ff->prev) { + if (a_this->name) { + g_free (a_this->name); + a_this->name = NULL; + } + + if (cur_ff->next) { + g_free (cur_ff->next); + + } + + if (cur_ff->prev == NULL) { + g_free (a_this); + } + } + + return CR_OK; +} + +/*************************************************** + *'font-size' manipulation functions definitions + ***************************************************/ + +CRFontSize * +cr_font_size_new (void) +{ + CRFontSize *result = NULL; + + result = g_try_malloc (sizeof (CRFontSize)); + if (!result) { + cr_utils_trace_info ("Out of memory"); + return NULL; + } + memset (result, 0, sizeof (CRFontSize)); + + return result; +} + +enum CRStatus +cr_font_size_clear (CRFontSize * a_this) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + switch (a_this->type) { + case PREDEFINED_ABSOLUTE_FONT_SIZE: + case RELATIVE_FONT_SIZE: + case INHERITED_FONT_SIZE: + memset (a_this, 0, sizeof (CRFontSize)); + break; + + case ABSOLUTE_FONT_SIZE: + memset (a_this, 0, sizeof (CRFontSize)); + break; + + default: + return CR_UNKNOWN_TYPE_ERROR; + } + + return CR_OK; +} + +enum CRStatus +cr_font_size_copy (CRFontSize * a_dst, CRFontSize * a_src) +{ + g_return_val_if_fail (a_dst && a_src, CR_BAD_PARAM_ERROR); + + switch (a_src->type) { + case PREDEFINED_ABSOLUTE_FONT_SIZE: + case RELATIVE_FONT_SIZE: + case INHERITED_FONT_SIZE: + cr_font_size_clear (a_dst); + memcpy (a_dst, a_src, sizeof (CRFontSize)); + break; + + case ABSOLUTE_FONT_SIZE: + cr_font_size_clear (a_dst); + cr_num_copy (&a_dst->value.absolute, + &a_src->value.absolute); + a_dst->type = a_src->type; + break; + + default: + return CR_UNKNOWN_TYPE_ERROR; + } + return CR_OK; +} + +enum CRStatus +cr_font_size_set_predefined_absolute_font_size (CRFontSize *a_this, + enum CRPredefinedAbsoluteFontSize a_predefined) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR) ; + g_return_val_if_fail ((unsigned)a_predefined < NB_FONT_SIZE_TYPE, + CR_BAD_PARAM_ERROR) ; + + a_this->type = PREDEFINED_ABSOLUTE_FONT_SIZE ; + a_this->value.predefined = a_predefined ; + + return CR_OK ; +} + +enum CRStatus +cr_font_size_set_relative_font_size (CRFontSize *a_this, + enum CRRelativeFontSize a_relative) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR) ; + g_return_val_if_fail ((unsigned)a_relative < NB_RELATIVE_FONT_SIZE, + CR_BAD_PARAM_ERROR) ; + + a_this->type = RELATIVE_FONT_SIZE ; + a_this->value.relative = a_relative ; + return CR_OK ; +} + +enum CRStatus +cr_font_size_set_absolute_font_size (CRFontSize *a_this, + enum CRNumType a_num_type, + gdouble a_value) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR) ; + g_return_val_if_fail ((unsigned)a_num_type < NB_NUM_TYPE, + CR_BAD_PARAM_ERROR) ; + + a_this->type = ABSOLUTE_FONT_SIZE ; + cr_num_set (&a_this->value.absolute, + a_value, a_num_type) ; + return CR_OK ; +} + +enum CRStatus +cr_font_size_set_to_inherit (CRFontSize *a_this) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR) ; + + cr_font_size_clear (a_this) ; + a_this->type = INHERITED_FONT_SIZE ; + + return CR_OK ; +} + +gboolean +cr_font_size_is_set_to_inherit (CRFontSize *a_this) +{ + g_return_val_if_fail (a_this, FALSE) ; + + return a_this->type == INHERITED_FONT_SIZE ; +} + +gchar * +cr_font_size_to_string (CRFontSize * a_this) +{ + gchar *str = NULL; + + if (!a_this) { + str = g_strdup ("NULL"); + g_return_val_if_fail (str, NULL); + return str; + } + switch (a_this->type) { + case PREDEFINED_ABSOLUTE_FONT_SIZE: + str = g_strdup (cr_predefined_absolute_font_size_to_string + (a_this->value.predefined)); + break; + case ABSOLUTE_FONT_SIZE: + str = cr_num_to_string (&a_this->value.absolute); + break; + case RELATIVE_FONT_SIZE: + str = g_strdup (cr_relative_font_size_to_string + (a_this->value.relative)); + break; + case INHERITED_FONT_SIZE: + str = g_strdup ("inherit"); + break; + default: + break; + } + return str; +} + +void +cr_font_size_get_smaller_predefined_font_size (enum CRPredefinedAbsoluteFontSize a_font_size, + enum CRPredefinedAbsoluteFontSize *a_smaller_size) +{ + enum CRPredefinedAbsoluteFontSize result = FONT_SIZE_MEDIUM ; + + g_return_if_fail (a_smaller_size) ; + g_return_if_fail ((unsigned)a_font_size < NB_PREDEFINED_ABSOLUTE_FONT_SIZES) ; + + switch (a_font_size) { + case FONT_SIZE_XX_SMALL: + result = FONT_SIZE_XX_SMALL ; + break ; + case FONT_SIZE_X_SMALL: + result = FONT_SIZE_XX_SMALL ; + break ; + case FONT_SIZE_SMALL: + result = FONT_SIZE_X_SMALL; + break ; + case FONT_SIZE_MEDIUM: + result = FONT_SIZE_SMALL; + break ; + case FONT_SIZE_LARGE: + result = FONT_SIZE_MEDIUM; + break ; + case FONT_SIZE_X_LARGE: + result = FONT_SIZE_LARGE; + break ; + case FONT_SIZE_XX_LARGE: + result = FONT_SIZE_XX_LARGE; + break ; + case FONT_SIZE_INHERIT: + cr_utils_trace_info ("can't return a smaller size for FONT_SIZE_INHERIT") ; + result = FONT_SIZE_MEDIUM ; + break ; + default: + cr_utils_trace_info ("Unknown FONT_SIZE") ; + result = FONT_SIZE_MEDIUM ; + break ; + } + *a_smaller_size = result ; +} + + +void +cr_font_size_get_larger_predefined_font_size (enum CRPredefinedAbsoluteFontSize a_font_size, + enum CRPredefinedAbsoluteFontSize *a_larger_size) +{ + enum CRPredefinedAbsoluteFontSize result = FONT_SIZE_MEDIUM ; + + g_return_if_fail (a_larger_size) ; + g_return_if_fail ((unsigned)a_font_size < NB_PREDEFINED_ABSOLUTE_FONT_SIZES) ; + + switch (a_font_size) { + case FONT_SIZE_XX_SMALL: + result = FONT_SIZE_X_SMALL ; + break ; + case FONT_SIZE_X_SMALL: + result = FONT_SIZE_SMALL ; + break ; + case FONT_SIZE_SMALL: + result = FONT_SIZE_MEDIUM; + break ; + case FONT_SIZE_MEDIUM: + result = FONT_SIZE_LARGE; + break ; + case FONT_SIZE_LARGE: + result = FONT_SIZE_X_LARGE; + break ; + case FONT_SIZE_X_LARGE: + result = FONT_SIZE_XX_LARGE ; + break ; + case FONT_SIZE_XX_LARGE: + result = FONT_SIZE_XX_LARGE; + break ; + case FONT_SIZE_INHERIT: + cr_utils_trace_info ("can't return a bigger size for FONT_SIZE_INHERIT") ; + result = FONT_SIZE_MEDIUM ; + break ; + default: + cr_utils_trace_info ("Unknown FONT_SIZE") ; + result = FONT_SIZE_MEDIUM ; + break ; + } + *a_larger_size = result ; +} + +gboolean +cr_font_size_is_predefined_absolute_font_size (enum CRPredefinedAbsoluteFontSize a_font_size) +{ + if ((unsigned)a_font_size < NB_PREDEFINED_ABSOLUTE_FONT_SIZES) { + return TRUE ; + } else { + return FALSE ; + } +} + +gchar * +cr_font_size_adjust_to_string (CRFontSizeAdjust * a_this) +{ + gchar *str = NULL; + + if (!a_this) { + str = g_strdup ("NULL"); + g_return_val_if_fail (str, NULL); + return str; + } + + switch (a_this->type) { + case FONT_SIZE_ADJUST_NONE: + str = g_strdup ("none"); + break; + case FONT_SIZE_ADJUST_NUMBER: + if (a_this->num) + str = cr_num_to_string (a_this->num); + else + str = g_strdup ("unknow font-size-adjust property value"); // Should raise an error no? + break; + case FONT_SIZE_ADJUST_INHERIT: + str = g_strdup ("inherit"); + } + return str; +} + +const gchar * +cr_font_style_to_string (enum CRFontStyle a_code) +{ + gchar *str = NULL; + + switch (a_code) { + case FONT_STYLE_NORMAL: + str = (gchar *) "normal"; + break; + case FONT_STYLE_ITALIC: + str = (gchar *) "italic"; + break; + case FONT_STYLE_OBLIQUE: + str = (gchar *) "oblique"; + break; + case FONT_STYLE_INHERIT: + str = (gchar *) "inherit"; + break; + default: + str = (gchar *) "unknown font style value"; + break; + } + return str; +} + +const gchar * +cr_font_variant_to_string (enum CRFontVariant a_code) +{ + gchar *str = NULL; + + switch (a_code) { + case FONT_VARIANT_NORMAL: + str = (gchar *) "normal"; + break; + case FONT_VARIANT_SMALL_CAPS: + str = (gchar *) "small-caps"; + break; + case FONT_VARIANT_INHERIT: + str = (gchar *) "inherit"; + break; + } + return str; +} + +enum CRFontWeight +cr_font_weight_get_bolder (enum CRFontWeight a_weight) +{ + if (a_weight >= NB_FONT_WEIGHTS) { + return FONT_WEIGHT_900 ; + } else if (a_weight < FONT_WEIGHT_NORMAL) { + return FONT_WEIGHT_NORMAL ; + } else if (a_weight == FONT_WEIGHT_BOLDER + || a_weight == FONT_WEIGHT_BOLDER) { + cr_utils_trace_info ("FONT_WEIGHT_BOLDER or FONT_WEIGHT_LIGHTER should not appear here") ; + return FONT_WEIGHT_NORMAL ; + } else { + return a_weight << 1 ; + } +} + +const gchar * +cr_font_weight_to_string (enum CRFontWeight a_code) +{ + gchar *str = NULL; + + switch (a_code) { + case FONT_WEIGHT_NORMAL: + str = (gchar *) "normal"; + break; + case FONT_WEIGHT_BOLD: + str = (gchar *) "bold"; + break; + case FONT_WEIGHT_BOLDER: + str = (gchar *) "bolder"; + break; + case FONT_WEIGHT_LIGHTER: + str = (gchar *) "lighter"; + break; + case FONT_WEIGHT_100: + str = (gchar *) "100"; + break; + case FONT_WEIGHT_200: + str = (gchar *) "200"; + break; + case FONT_WEIGHT_300: + str = (gchar *) "300"; + break; + case FONT_WEIGHT_400: + str = (gchar *) "400"; + break; + case FONT_WEIGHT_500: + str = (gchar *) "500"; + break; + case FONT_WEIGHT_600: + str = (gchar *) "600"; + break; + case FONT_WEIGHT_700: + str = (gchar *) "700"; + break; + case FONT_WEIGHT_800: + str = (gchar *) "800"; + break; + case FONT_WEIGHT_900: + str = (gchar *) "900"; + break; + case FONT_WEIGHT_INHERIT: + str = (gchar *) "inherit"; + break; + default: + str = (gchar *) "unknown font-weight property value"; + break; + } + return str; +} + +const gchar * +cr_font_stretch_to_string (enum CRFontStretch a_code) +{ + gchar *str = NULL; + + switch (a_code) { + case FONT_STRETCH_NORMAL: + str = (gchar *) "normal"; + break; + case FONT_STRETCH_WIDER: + str = (gchar *) "wider"; + break; + case FONT_STRETCH_NARROWER: + str = (gchar *) "narrower"; + break; + case FONT_STRETCH_ULTRA_CONDENSED: + str = (gchar *) "ultra-condensed"; + break; + case FONT_STRETCH_EXTRA_CONDENSED: + str = (gchar *) "extra-condensed"; + break; + case FONT_STRETCH_CONDENSED: + str = (gchar *) "condensed"; + break; + case FONT_STRETCH_SEMI_CONDENSED: + str = (gchar *) "semi-condensed"; + break; + case FONT_STRETCH_SEMI_EXPANDED: + str = (gchar *) "semi-expanded"; + break; + case FONT_STRETCH_EXPANDED: + str = (gchar *) "expanded"; + break; + case FONT_STRETCH_EXTRA_EXPANDED: + str = (gchar *) "extra-expaned"; + break; + case FONT_STRETCH_ULTRA_EXPANDED: + str = (gchar *) "ultra-expanded"; + break; + case FONT_STRETCH_INHERIT: + str = (gchar *) "inherit"; + break; + } + return str; +} + +void +cr_font_size_destroy (CRFontSize * a_font_size) +{ + g_return_if_fail (a_font_size); + + g_free (a_font_size) ; +} + +/******************************************************* + *'font-size-adjust' manipulation function definition + *******************************************************/ + +CRFontSizeAdjust * +cr_font_size_adjust_new (void) +{ + CRFontSizeAdjust *result = NULL; + + result = g_try_malloc (sizeof (CRFontSizeAdjust)); + if (!result) { + cr_utils_trace_info ("Out of memory"); + return NULL; + } + memset (result, 0, sizeof (CRFontSizeAdjust)); + + return result; +} + +void +cr_font_size_adjust_destroy (CRFontSizeAdjust * a_this) +{ + g_return_if_fail (a_this); + + if (a_this->type == FONT_SIZE_ADJUST_NUMBER && a_this->num) { + cr_num_destroy (a_this->num); + a_this->num = NULL; + } +} diff --git a/src/libcroco/cr-fonts.h b/src/libcroco/cr-fonts.h new file mode 100644 index 000000000..45446180b --- /dev/null +++ b/src/libcroco/cr-fonts.h @@ -0,0 +1,314 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of + * the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the + * GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli + * See COPYRIGHTS file for copyright information. + */ + +#ifndef __CR_FONTS_H__ +#define __CR_FONTS_H__ +#endif + +#include "cr-utils.h" +#include "cr-num.h" + +/** + *@file + *Various type declarations about font selection related + *properties. + */ +G_BEGIN_DECLS + + +enum CRFontFamilyType +{ + FONT_FAMILY_SANS_SERIF, + FONT_FAMILY_SERIF, + FONT_FAMILY_CURSIVE, + FONT_FAMILY_FANTASY, + FONT_FAMILY_MONOSPACE, + FONT_FAMILY_NON_GENERIC, + FONT_FAMILY_INHERIT, + /**/ + NB_FONT_FAMILIE_TYPES +} ; + +typedef struct _CRFontFamily CRFontFamily ; + +struct _CRFontFamily +{ + enum CRFontFamilyType type ; + + /* + *The name of the font family, in case + *it is non generic. + *Is set only if the type is FONT_FAMILY_NON_GENERIC. + */ + guchar *name ; + + CRFontFamily *next ; + CRFontFamily *prev ; +} ; + + +/** + *The different types + *of absolute font size. + *This is used by the 'font-size' + *property defined in css2 spec + *in chapter 15.2.4 . + *These values a indexes of + *table of size so please, do not + *change their definition order unless + *you know what you are doing. + */ +enum CRPredefinedAbsoluteFontSize +{ + FONT_SIZE_XX_SMALL=0, + FONT_SIZE_X_SMALL, + FONT_SIZE_SMALL, + FONT_SIZE_MEDIUM, + FONT_SIZE_LARGE, + FONT_SIZE_X_LARGE, + FONT_SIZE_XX_LARGE, + FONT_SIZE_INHERIT, + NB_PREDEFINED_ABSOLUTE_FONT_SIZES +} ; + +/** + *The different types + *of relative font size. + *This is used by the 'font-size' + *property defined in css2 spec + *in chapter 15.2.4 . + *These values a indexes of + *table of size so please, do not + *change their definition order unless + *you know what you are doing. + */ +enum CRRelativeFontSize +{ + FONT_SIZE_LARGER, + FONT_SIZE_SMALLER, + NB_RELATIVE_FONT_SIZE +} ; + +/** + *The type of font-size property. + *Used to define the type of #CRFontSize . + *See css2 spec chapter 15.2.4 to understand. + */ +enum CRFontSizeType { + /** + *If the type of #CRFontSize is + *PREDEFINED_ABSOLUTE_FONT_SIZE, + *the CRFontSize::value.predefined_absolute + *field will be defined. + */ + PREDEFINED_ABSOLUTE_FONT_SIZE, + + /** + *If the type of #CRFontSize is + *ABSOLUTE_FONT_SIZE, + *the CRFontSize::value.absolute + *field will be defined. + */ + ABSOLUTE_FONT_SIZE, + + /** + *If the type of #CRFontSize is + *RELATIVE_FONT_SIZE, + *the CRFontSize::value.relative + *field will be defined. + */ + RELATIVE_FONT_SIZE, + + /** + *If the type of #CRFontSize is + *INHERITED_FONT_SIZE, + *the None of the field of the CRFontSize::value enum + *will be defined. + */ + INHERITED_FONT_SIZE, + + NB_FONT_SIZE_TYPE +} ; + +typedef struct _CRFontSize CRFontSize ; +struct _CRFontSize { + enum CRFontSizeType type ; + union { + enum CRPredefinedAbsoluteFontSize predefined ; + enum CRRelativeFontSize relative ; + CRNum absolute ; + } value; +} ; + +enum CRFontSizeAdjustType +{ + FONT_SIZE_ADJUST_NONE = 0, + FONT_SIZE_ADJUST_NUMBER, + FONT_SIZE_ADJUST_INHERIT +} ; +typedef struct _CRFontSizeAdjust CRFontSizeAdjust ; +struct _CRFontSizeAdjust +{ + enum CRFontSizeAdjustType type ; + CRNum *num ; +} ; + +enum CRFontStyle +{ + FONT_STYLE_NORMAL=0, + FONT_STYLE_ITALIC, + FONT_STYLE_OBLIQUE, + FONT_STYLE_INHERIT +} ; + +enum CRFontVariant +{ + FONT_VARIANT_NORMAL=0, + FONT_VARIANT_SMALL_CAPS, + FONT_VARIANT_INHERIT +} ; + +enum CRFontWeight +{ + FONT_WEIGHT_NORMAL = 1, + FONT_WEIGHT_BOLD = 1<<1, + FONT_WEIGHT_BOLDER = 1<<2, + FONT_WEIGHT_LIGHTER = 1<<3, + FONT_WEIGHT_100 = 1<<4, + FONT_WEIGHT_200 = 1<<5, + FONT_WEIGHT_300 = 1<<6, + FONT_WEIGHT_400 = 1<<7, + FONT_WEIGHT_500 = 1<<8, + FONT_WEIGHT_600 = 1<<9, + FONT_WEIGHT_700 = 1<<10, + FONT_WEIGHT_800 = 1<<11, + FONT_WEIGHT_900 = 1<<12, + FONT_WEIGHT_INHERIT = 1<<13, + NB_FONT_WEIGHTS +} ; + +enum CRFontStretch +{ + FONT_STRETCH_NORMAL=0, + FONT_STRETCH_WIDER, + FONT_STRETCH_NARROWER, + FONT_STRETCH_ULTRA_CONDENSED, + FONT_STRETCH_EXTRA_CONDENSED, + FONT_STRETCH_CONDENSED, + FONT_STRETCH_SEMI_CONDENSED, + FONT_STRETCH_SEMI_EXPANDED, + FONT_STRETCH_EXPANDED, + FONT_STRETCH_EXTRA_EXPANDED, + FONT_STRETCH_ULTRA_EXPANDED, + FONT_STRETCH_INHERIT +} ; + +/************************************** + *'font-family' manipulation functions + ***************************************/ +CRFontFamily * +cr_font_family_new (enum CRFontFamilyType a_type, guchar *a_name) ; + +CRFontFamily * +cr_font_family_append (CRFontFamily *a_this, + CRFontFamily *a_family_to_append) ; + +guchar * +cr_font_family_to_string (CRFontFamily *a_this, + gboolean a_walk_font_family_list) ; + +CRFontFamily * +cr_font_family_prepend (CRFontFamily *a_this, + CRFontFamily *a_family_to_prepend); + +enum CRStatus +cr_font_family_destroy (CRFontFamily *a_this) ; + +enum CRStatus +cr_font_family_set_name (CRFontFamily *a_this, guchar *a_name) ; + + +/************************************ + *'font-size' manipulation functions + ***********************************/ + +CRFontSize * cr_font_size_new (void) ; + +enum CRStatus cr_font_size_clear (CRFontSize *a_this) ; + +enum CRStatus cr_font_size_copy (CRFontSize *a_dst, + CRFontSize *a_src) ; +enum CRStatus cr_font_size_set_predefined_absolute_font_size (CRFontSize *a_this, + enum CRPredefinedAbsoluteFontSize a_predefined) ; +enum CRStatus cr_font_size_set_relative_font_size (CRFontSize *a_this, + enum CRRelativeFontSize a_relative) ; + +enum CRStatus cr_font_size_set_absolute_font_size (CRFontSize *a_this, + enum CRNumType a_num_type, + gdouble a_value) ; + +enum CRStatus cr_font_size_set_to_inherit (CRFontSize *a_this) ; + +gboolean cr_font_size_is_set_to_inherit (CRFontSize *a_this) ; + +gchar* cr_font_size_to_string (CRFontSize *a_this) ; + +void cr_font_size_destroy (CRFontSize *a_font_size) ; + +/******************************************************* + *'font-size-adjust' manipulation function declarations + *******************************************************/ + +CRFontSizeAdjust * cr_font_size_adjust_new (void) ; + +gchar * cr_font_size_adjust_to_string (CRFontSizeAdjust *a_this) ; + +void cr_font_size_adjust_destroy (CRFontSizeAdjust *a_this) ; + +void +cr_font_size_get_smaller_predefined_font_size (enum CRPredefinedAbsoluteFontSize a_font_size, + enum CRPredefinedAbsoluteFontSize *a_smaller_size) ; +void +cr_font_size_get_larger_predefined_font_size (enum CRPredefinedAbsoluteFontSize a_font_size, + enum CRPredefinedAbsoluteFontSize *a_larger_size) ; + +gboolean +cr_font_size_is_predefined_absolute_font_size (enum CRPredefinedAbsoluteFontSize a_font_size) ; + +/*********************************** + *various other font related functions + ***********************************/ +const gchar * cr_font_style_to_string (enum CRFontStyle a_code) ; + +const gchar * cr_font_weight_to_string (enum CRFontWeight a_code) ; + +enum CRFontWeight +cr_font_weight_get_bolder (enum CRFontWeight a_weight) ; + +const gchar * cr_font_variant_to_string (enum CRFontVariant a_code) ; + +const gchar * cr_font_stretch_to_string (enum CRFontStretch a_code) ; + +G_END_DECLS diff --git a/src/libcroco/cr-input.c b/src/libcroco/cr-input.c new file mode 100644 index 000000000..9485df3c5 --- /dev/null +++ b/src/libcroco/cr-input.c @@ -0,0 +1,1112 @@ +/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli + * See COPYRIGHTS file for copyright information. + */ + +#include "stdio.h" +#include +#include "cr-input.h" +#include "cr-enc-handler.h" + +/** + *@file + *The definition of the #CRInput class. + */ + +/******************* + *Private type defs + *******************/ + +/** + *The private attributes of + *the #CRInputPriv class. + */ +struct _CRInputPriv { + /* + *The input buffer + */ + guchar *in_buf; + gulong in_buf_size; + + gulong nb_bytes; + + /* + *The index of the next byte + *to be read. + */ + gulong next_byte_index; + + /* + *The current line number + */ + gulong line; + + /* + *The current col number + */ + gulong col; + + gboolean end_of_line; + gboolean end_of_input; + + /* + *the reference count of this + *instance. + */ + guint ref_count; + gboolean free_in_buf; +}; + +#define PRIVATE(object) (object)->priv + +/*************************** + *private constants + **************************/ +#define CR_INPUT_MEM_CHUNK_SIZE 1024 * 4 + +static CRInput *cr_input_new_real (void); + +static CRInput * +cr_input_new_real (void) +{ + CRInput *result = NULL; + + result = g_try_malloc (sizeof (CRInput)); + if (!result) { + cr_utils_trace_info ("Out of memory"); + return NULL; + } + memset (result, 0, sizeof (CRInput)); + + PRIVATE (result) = g_try_malloc (sizeof (CRInputPriv)); + if (!PRIVATE (result)) { + cr_utils_trace_info ("Out of memory"); + g_free (result); + return NULL; + } + memset (PRIVATE (result), 0, sizeof (CRInputPriv)); + PRIVATE (result)->free_in_buf = TRUE; + return result; +} + +/**************** + *Public methods + ***************/ + +/** + *Creates a new input stream from a memory buffer. + *@param a_buf the memory buffer to create the input stream from. + *The #CRInput keeps this pointer so user should not free it !. + *@param a_len the size of the input buffer. + *@param a_enc the buffer's encoding. + *@param a_free_buf if set to TRUE, this a_buf will be freed + *at the destruction of this instance. If set to false, it is up + *to the caller to free it. + *@return the newly built instance of #CRInput. + */ +CRInput * +cr_input_new_from_buf (guchar * a_buf, + gulong a_len, + enum CREncoding a_enc, + gboolean a_free_buf) +{ + CRInput *result = NULL; + enum CRStatus status = CR_OK; + CREncHandler *enc_handler = NULL; + gulong len = a_len; + + g_return_val_if_fail (a_buf, NULL); + + result = cr_input_new_real (); + g_return_val_if_fail (result, NULL); + + /*transform the encoding in utf8 */ + if (a_enc != CR_UTF_8) { + enc_handler = cr_enc_handler_get_instance (a_enc); + if (!enc_handler) { + goto error; + } + + status = cr_enc_handler_convert_input + (enc_handler, a_buf, &len, + &PRIVATE (result)->in_buf, + &PRIVATE (result)->in_buf_size); + if (status != CR_OK) + goto error; + PRIVATE (result)->free_in_buf = TRUE; + if (a_free_buf == TRUE && a_buf) { + g_free (a_buf) ; + a_buf = NULL ; + } + PRIVATE (result)->nb_bytes = PRIVATE (result)->in_buf_size; + } else { + PRIVATE (result)->in_buf = (guchar *) a_buf; + PRIVATE (result)->in_buf_size = a_len; + PRIVATE (result)->nb_bytes = a_len; + PRIVATE (result)->free_in_buf = a_free_buf; + } + PRIVATE (result)->line = 1; + PRIVATE (result)->col = 0; + return result; + + error: + if (result) { + cr_input_destroy (result); + result = NULL; + } + + return NULL; +} + +/** + *Creates a new input stream from + *a file. + *@param a_file_uri the file to create + *the input stream from. + *@param a_enc the encoding of the file + *to create the input from + *@return the newly created input stream if + *this method could read the file and create it, + *NULL otherwise. + */ + +CRInput * +cr_input_new_from_uri (const gchar * a_file_uri, enum CREncoding a_enc) +{ + CRInput *result = NULL; + enum CRStatus status = CR_OK; + FILE *file_ptr = NULL; + guchar tmp_buf[CR_INPUT_MEM_CHUNK_SIZE] = { 0 }; + gulong nb_read = 0, + len = 0, + buf_size = 0; + gboolean loop = TRUE; + guchar *buf = NULL; + + g_return_val_if_fail (a_file_uri, NULL); + + file_ptr = fopen (a_file_uri, "r"); + + if (file_ptr == NULL) { + +#ifdef CR_DEBUG + cr_utils_trace_debug ("could not open file"); +#endif + g_warning ("Could not open file %s\n", a_file_uri); + + return NULL; + } + + /*load the file */ + while (loop) { + nb_read = fread (tmp_buf, 1 /*read bytes */ , + CR_INPUT_MEM_CHUNK_SIZE /*nb of bytes */ , + file_ptr); + + if (nb_read != CR_INPUT_MEM_CHUNK_SIZE) { + /*we read less chars than we wanted */ + if (feof (file_ptr)) { + /*we reached eof */ + loop = FALSE; + } else { + /*a pb occured !! */ + cr_utils_trace_debug ("an io error occured"); + status = CR_ERROR; + goto cleanup; + } + } + + if (status == CR_OK) { + /*read went well */ + buf = g_realloc (buf, len + CR_INPUT_MEM_CHUNK_SIZE); + memcpy (buf + len, tmp_buf, nb_read); + len += nb_read; + buf_size += CR_INPUT_MEM_CHUNK_SIZE; + } + } + + if (status == CR_OK) { + result = cr_input_new_from_buf (buf, len, a_enc, TRUE); + if (!result) { + goto cleanup; + } + /* + *we should free buf here because it's own by CRInput. + *(see the last parameter of cr_input_new_from_buf(). + */ + buf = NULL ; + } + + cleanup: + if (file_ptr) { + fclose (file_ptr); + file_ptr = NULL; + } + + if (buf) { + g_free (buf); + buf = NULL; + } + + return result; +} + +/** + *The destructor of the #CRInput class. + *@param a_this the current instance of #CRInput. + */ +void +cr_input_destroy (CRInput * a_this) +{ + if (a_this == NULL) + return; + + if (PRIVATE (a_this)) { + if (PRIVATE (a_this)->in_buf && PRIVATE (a_this)->free_in_buf) { + g_free (PRIVATE (a_this)->in_buf); + PRIVATE (a_this)->in_buf = NULL; + } + + g_free (PRIVATE (a_this)); + PRIVATE (a_this) = NULL; + } + + g_free (a_this); +} + +/** + *Increments the reference count of the current + *instance of #CRInput. + *@param a_this the current instance of #CRInput. + */ +void +cr_input_ref (CRInput * a_this) +{ + g_return_if_fail (a_this && PRIVATE (a_this)); + + PRIVATE (a_this)->ref_count++; +} + +/** + *Decrements the reference count of this instance + *of #CRInput. If the reference count goes down to + *zero, this instance is destroyed. + *@param a_this the current instance of #CRInput. + * + */ +gboolean +cr_input_unref (CRInput * a_this) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this), FALSE); + + if (PRIVATE (a_this)->ref_count) { + PRIVATE (a_this)->ref_count--; + } + + if (PRIVATE (a_this)->ref_count == 0) { + cr_input_destroy (a_this); + return TRUE; + } + return FALSE; +} + +/** + *Tests wether the current instance of + *#CRInput has reached its input buffer. + *@param a_this the current instance of #CRInput. + *@param a_end_of_input out parameter. Is set to TRUE if + *the current instance has reached the end of its input buffer, + *FALSE otherwise. + *@param CR_OK upon successful completion, an error code otherwise. + *Note that all the out parameters of this method are valid if + *and only if this method returns CR_OK. + */ +enum CRStatus +cr_input_end_of_input (CRInput * a_this, gboolean * a_end_of_input) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_end_of_input, CR_BAD_PARAM_ERROR); + + *a_end_of_input = (PRIVATE (a_this)->next_byte_index + >= PRIVATE (a_this)->in_buf_size) ? TRUE : FALSE; + + return CR_OK; +} + +/** + *Returns the number of bytes left in the input stream + *before the end. + *@param a_this the current instance of #CRInput. + *@return the number of characters left or -1 in case of error. + */ +glong +cr_input_get_nb_bytes_left (CRInput * a_this) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this), -1); + g_return_val_if_fail (PRIVATE (a_this)->nb_bytes + <= PRIVATE (a_this)->in_buf_size, -1); + g_return_val_if_fail (PRIVATE (a_this)->next_byte_index + <= PRIVATE (a_this)->nb_bytes, -1); + + if (PRIVATE (a_this)->end_of_input) + return 0; + + return PRIVATE (a_this)->nb_bytes - PRIVATE (a_this)->next_byte_index; +} + +/** + *Returns the next byte of the input. + *Update the state of the input so that + *the next invocation of this method returns + *the next coming byte. + * + *@param a_this the current instance of #CRInput. + *@param a_byte out parameter the returned byte. + *@return CR_OK upon successful completion, an error code + *otherwise. All the out parameters of this method are valid if + *and only if this method returns CR_OK. + */ +enum CRStatus +cr_input_read_byte (CRInput * a_this, guchar * a_byte) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_byte, CR_BAD_PARAM_ERROR); + + g_return_val_if_fail (PRIVATE (a_this)->next_byte_index <= + PRIVATE (a_this)->nb_bytes, CR_BAD_PARAM_ERROR); + + if (PRIVATE (a_this)->end_of_input == TRUE) + return CR_END_OF_INPUT_ERROR; + + *a_byte = PRIVATE (a_this)->in_buf[PRIVATE (a_this)->next_byte_index]; + + if (PRIVATE (a_this)->nb_bytes - + PRIVATE (a_this)->next_byte_index < 2) { + PRIVATE (a_this)->end_of_input = TRUE; + } else { + PRIVATE (a_this)->next_byte_index++; + } + + return CR_OK; +} + +/** + *Reads an unicode character from the current instance of + *#CRInput. + *@param a_this the current instance of CRInput. + *@param a_char out parameter. The read character. + *@return CR_OK upon successful completion, an error code + *otherwise. + */ +enum CRStatus +cr_input_read_char (CRInput * a_this, guint32 * a_char) +{ + enum CRStatus status = CR_OK; + gulong consumed = 0, + nb_bytes_left = 0; + + g_return_val_if_fail (a_this && PRIVATE (a_this) && a_char, + CR_BAD_PARAM_ERROR); + + if (PRIVATE (a_this)->end_of_input == TRUE) + return CR_END_OF_INPUT_ERROR; + + nb_bytes_left = cr_input_get_nb_bytes_left (a_this); + + if (nb_bytes_left < 1) { + return CR_END_OF_INPUT_ERROR; + } + + status = cr_utils_read_char_from_utf8_buf + (PRIVATE (a_this)->in_buf + + + PRIVATE (a_this)->next_byte_index, + nb_bytes_left, a_char, &consumed); + + if (status == CR_OK) { + /*update next byte index */ + PRIVATE (a_this)->next_byte_index += consumed; + + /*update line and column number */ + if (PRIVATE (a_this)->end_of_line == TRUE) { + PRIVATE (a_this)->col = 1; + PRIVATE (a_this)->line++; + PRIVATE (a_this)->end_of_line = FALSE; + } else if (*a_char != '\n') { + PRIVATE (a_this)->col++; + } + + if (*a_char == '\n') { + PRIVATE (a_this)->end_of_line = TRUE; + } + + } + + return status; +} + +/** + *Setter of the current line number. + *@param a_this the "this pointer" of the current instance of + *#CRInput. + *@param a_line_num the new line number. + *@return CR_OK upon successful completion, an error code otherwise. + */ +enum CRStatus +cr_input_set_line_num (CRInput * a_this, glong a_line_num) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); + + PRIVATE (a_this)->line = a_line_num; + + return CR_OK; +} + +/** + *Getter of the current line number. + *@param a_this the "this pointer" of the current instance of + *#CRInput. + *@param a_line_num the returned line number. + *@return CR_OK upon successful completion, an error code otherwise. + */ +enum CRStatus +cr_input_get_line_num (CRInput * a_this, glong * a_line_num) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_line_num, CR_BAD_PARAM_ERROR); + + *a_line_num = PRIVATE (a_this)->line; + + return CR_OK; +} + +/** + *Setter of the current column number. + *@param a_this the "this pointer" of the current instance of + *#CRInput. + *@param a_col the new column number. + *@return CR_OK upon successful completion, an error code otherwise. + */ +enum CRStatus +cr_input_set_column_num (CRInput * a_this, glong a_col) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); + + PRIVATE (a_this)->col = a_col; + + return CR_OK; +} + +/** + *Getter of the current column number. + *@param a_this the "this pointer" of the current instance of + *#CRInput. + *@param a_col out parameter + *@return CR_OK upon successful completion, an error code otherwise. + */ +enum CRStatus +cr_input_get_column_num (CRInput * a_this, glong * a_col) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this) && a_col, + CR_BAD_PARAM_ERROR); + + *a_col = PRIVATE (a_this)->col; + + return CR_OK; +} + +/** + *Increments the current line number. + *@param a_this the "this pointer" of the current instance of + *#CRInput. + *@return CR_OK upon successful completion, an error code otherwise. + */ +enum CRStatus +cr_input_increment_line_num (CRInput * a_this, glong a_increment) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); + + PRIVATE (a_this)->line += a_increment; + + return CR_OK; +} + +/** + *Increments the current column number. + *@param a_this the "this pointer" of the current instance of + *#CRInput. + *@return CR_OK upon successful completion, an error code otherwise. + */ +enum CRStatus +cr_input_increment_col_num (CRInput * a_this, glong a_increment) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); + + PRIVATE (a_this)->col += a_increment; + + return CR_OK; +} + +/** + *Consumes the next character of the input stream if + *and only if that character equals a_char. + * + *@param a_this the this pointer. + *@param a_char the character to consume. If set to zero, + *consumes any character. + *@return CR_OK upon successful completion, CR_PARSING_ERROR if + *next char is different from a_char, an other error code otherwise + */ +enum CRStatus +cr_input_consume_char (CRInput * a_this, guint32 a_char) +{ + guint32 c; + enum CRStatus status; + + g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); + + if ((status = cr_input_peek_char (a_this, &c)) != CR_OK) { + return status; + } + + if (c == a_char || a_char == 0) { + status = cr_input_read_char (a_this, &c); + } else { + return CR_PARSING_ERROR; + } + + return status; +} + +/** + *Consumes up to a_nb_char occurences of the next contiguous characters + *which equal a_char. Note that the next character of the input stream + **MUST* equal a_char to trigger the consumption, or else, the error + *code CR_PARSING_ERROR is returned. + *If the number of contiguous characters that equals a_char is less than + *a_nb_char, then this function consumes all the characters it can consume. + * + *@param a_this the this pointer of the current instance of #CRInput. + *@param a_char the character to consume. + *@param a_nb_char in/out parameter. The number of characters to consume. + *If set to a negative value, the function will consume all the occurences + *of a_char found. + *After return, if the return value equals CR_OK, this variable contains + *the number of characters actually consumed. + *@return CR_OK if at least one character has been consumed, an error code + *otherwise. + */ +enum CRStatus +cr_input_consume_chars (CRInput * a_this, guint32 a_char, gulong * a_nb_char) +{ + enum CRStatus status = CR_OK; + gulong nb_consumed = 0; + + g_return_val_if_fail (a_this && PRIVATE (a_this) && a_nb_char, + CR_BAD_PARAM_ERROR); + + g_return_val_if_fail (a_char != 0 || a_nb_char != NULL, + CR_BAD_PARAM_ERROR); + + for (nb_consumed = 0; ((status == CR_OK) + && (*a_nb_char > 0 + && nb_consumed < *a_nb_char)); + nb_consumed++) { + status = cr_input_consume_char (a_this, a_char); + } + + *a_nb_char = nb_consumed; + + if ((nb_consumed > 0) + && ((status == CR_PARSING_ERROR) + || (status == CR_END_OF_INPUT_ERROR))) { + status = CR_OK; + } + + return status; +} + +/** + *Same as cr_input_consume_chars() but this one consumes white + *spaces. + * + *@param a_this the "this pointer" of the current instance of #CRInput. + *@param a_nb_chars in/out parameter. The number of white spaces to + *consume. After return, holds the number of white spaces actually consumed. + *@return CR_OK upon successful completion, an error code otherwise. + */ +enum CRStatus +cr_input_consume_white_spaces (CRInput * a_this, gulong * a_nb_chars) +{ + enum CRStatus status = CR_OK; + guint32 cur_char = 0, + nb_consumed = 0; + + g_return_val_if_fail (a_this && PRIVATE (a_this) && a_nb_chars, + CR_BAD_PARAM_ERROR); + + for (nb_consumed = 0; + ((*a_nb_chars > 0) && (nb_consumed < *a_nb_chars)); + nb_consumed++) { + status = cr_input_peek_char (a_this, &cur_char); + if (status != CR_OK) + break; + + /*if the next char is a white space, consume it ! */ + if (cr_utils_is_white_space (cur_char) == TRUE) { + status = cr_input_read_char (a_this, &cur_char); + if (status != CR_OK) + break; + continue; + } + + break; + + } + + if (nb_consumed && status == CR_END_OF_INPUT_ERROR) { + status = CR_OK; + } + + return status; +} + +/** + *Same as cr_input_read_char() but does not update the + *internal state of the input stream. The next call + *to cr_input_peek_char() or cr_input_read_char() will thus + *return the same character as the current one. + *@param a_this the current instance of #CRInput. + *@param a_char out parameter. The returned character. + *@return CR_OK upon successful completion, an error code + *otherwise. + */ +enum CRStatus +cr_input_peek_char (CRInput * a_this, guint32 * a_char) +{ + enum CRStatus status = CR_OK; + glong consumed = 0, + nb_bytes_left = 0; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_char, CR_BAD_PARAM_ERROR); + + if (PRIVATE (a_this)->next_byte_index >= + PRIVATE (a_this)->in_buf_size) { + return CR_END_OF_INPUT_ERROR; + } + + nb_bytes_left = cr_input_get_nb_bytes_left (a_this); + + if (nb_bytes_left < 1) { + return CR_END_OF_INPUT_ERROR; + } + + status = cr_utils_read_char_from_utf8_buf + (PRIVATE (a_this)->in_buf + + PRIVATE (a_this)->next_byte_index, + nb_bytes_left, a_char, &consumed); + + return status; +} + +/** + *Gets a byte from the input stream, + *starting from the current position in the input stream. + *Unlike cr_input_peek_next_byte() this method + *does not update the state of the current input stream. + *Subsequent calls to cr_input_peek_byte with the same arguments + *will return the same byte. + * + *@param a_this the current instance of #CRInput. + *@param a_origin the origin to consider in the calculation + *of the position of the byte to peek. + *@param a_offset the offset of the byte to peek, starting from + *the origin specified by a_origin. + *@param a_byte out parameter the peeked byte. + *@return CR_OK upon successful completion or, + * + *
    + *
  • CR_BAD_PARAM_ERROR if at least one of the parameters is invalid
  • + *
  • CR_OUT_OF_BOUNDS_ERROR if the indexed byte is out of bounds
  • + *
+ */ +enum CRStatus +cr_input_peek_byte (CRInput * a_this, enum CRSeekPos a_origin, + gulong a_offset, guchar * a_byte) +{ + gulong abs_offset = 0; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_byte, CR_BAD_PARAM_ERROR); + + switch (a_origin) { + + case CR_SEEK_CUR: + abs_offset = PRIVATE (a_this)->next_byte_index - 1 + a_offset; + break; + + case CR_SEEK_BEGIN: + abs_offset = a_offset; + break; + + case CR_SEEK_END: + abs_offset = PRIVATE (a_this)->in_buf_size - 1 - a_offset; + break; + + default: + return CR_BAD_PARAM_ERROR; + } + + if (abs_offset < PRIVATE (a_this)->in_buf_size) { + + *a_byte = PRIVATE (a_this)->in_buf[abs_offset]; + + return CR_OK; + + } else { + return CR_END_OF_INPUT_ERROR; + } +} + +/** + *Same as cr_input_peek_byte() but with a simplified + *interface. + *@param a_this the current byte input stream. + *@param a_offset the offset of the byte to peek, starting + *from the current input position pointer. + *@param a_eof out parameter. Is set to true is we reach end of + *stream. If set to NULL by the caller, this parameter is not taken + *in account. + *@return the read byte or 0 if something bad happened. + */ +guchar +cr_input_peek_byte2 (CRInput * a_this, gulong a_offset, gboolean * a_eof) +{ + guchar result = 0; + enum CRStatus status = CR_ERROR; + + g_return_val_if_fail (a_this && PRIVATE (a_this), 0); + + if (a_eof) + *a_eof = FALSE; + + status = cr_input_peek_byte (a_this, CR_SEEK_CUR, a_offset, &result); + + if ((status == CR_END_OF_INPUT_ERROR) + && a_eof) + *a_eof = TRUE; + + return result; +} + +/** + *Returns the memory address of the byte located at a given offset + *in the input stream. + *@param a_this the current instance of #CRInput. + *@param a_offset the offset of the byte in the input stream starting + *from the beginning of the stream. + *@return the address, otherwise NULL if an error occured. + */ +guchar * +cr_input_get_byte_addr (CRInput * a_this, gulong a_offset) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this), NULL); + + if (a_offset >= PRIVATE (a_this)->nb_bytes) { + return NULL; + } + + return &PRIVATE (a_this)->in_buf[a_offset]; +} + +/** + *Returns the address of the current character pointer. + *@param a_this the current input stream + *@param a_offset out parameter. The returned address. + *@return CR_OK upon successful completion, an error code otherwise. + */ +enum CRStatus +cr_input_get_cur_byte_addr (CRInput * a_this, guchar ** a_offset) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this) && a_offset, + CR_BAD_PARAM_ERROR); + + if (!PRIVATE (a_this)->next_byte_index) { + return CR_START_OF_INPUT_ERROR; + } + + *a_offset = cr_input_get_byte_addr + (a_this, PRIVATE (a_this)->next_byte_index - 1); + + return CR_OK; +} + +/** + *Sets the "current byte index" of the current instance + *of #CRInput. Next call to cr_input_get_byte() will return + *the byte next after the new "current byte index". + * + *@param a_this the current instance of #CRInput. + * + *@param a_origin the origin to consider during the calculation + *of the absolute position of the new "current byte index". + * + *@param a_pos the relative offset of the new "current byte index." + *This offset is relative to the origin a_origin. + * + *@return CR_OK upon successful completion otherwise returns + *
    + *
  • CR_BAD_PARAM_ERROR if at least one of the parameters is not valid
  • + *
  • CR_OUT_BOUNDS_ERROR
  • + *
+ */ +enum CRStatus +cr_input_seek_index (CRInput * a_this, enum CRSeekPos a_origin, gint a_pos) +{ + + glong abs_offset = 0; + + g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); + + switch (a_origin) { + + case CR_SEEK_CUR: + abs_offset = PRIVATE (a_this)->next_byte_index - 1 + a_pos; + break; + + case CR_SEEK_BEGIN: + abs_offset = a_pos; + break; + + case CR_SEEK_END: + abs_offset = PRIVATE (a_this)->in_buf_size - 1 - a_pos; + break; + + default: + return CR_BAD_PARAM_ERROR; + } + + if ((abs_offset > 0) + && (gulong) abs_offset < PRIVATE (a_this)->nb_bytes) { + + /*update the input stream's internal state */ + PRIVATE (a_this)->next_byte_index = abs_offset + 1; + + return CR_OK; + } + + return CR_OUT_OF_BOUNDS_ERROR; +} + +/** + *Gets the position of the "current byte index" which + *is basically the position of the last returned byte in the + *input stream. + * + *@param a_this the current instance of #CRInput. + * + *@param a_pos out parameter. The returned position. + * + *@return CR_OK upon successful completion. Otherwise, + *
    + *
  • CR_BAD_PARAMETER_ERROR if at least one of the arguments is invalid.
  • + *
  • CR_START_OF_INPUT if no call to either cr_input_read_byte() + *or cr_input_seek_index() have been issued before calling + *cr_input_get_cur_pos()
  • + *
+ *Note that the out parameters of this function are valid if and only if this + *function returns CR_OK. + */ +enum CRStatus +cr_input_get_cur_pos (CRInput * a_this, CRInputPos * a_pos) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this) && a_pos, + CR_BAD_PARAM_ERROR); + + a_pos->next_byte_index = PRIVATE (a_this)->next_byte_index; + a_pos->line = PRIVATE (a_this)->line; + a_pos->col = PRIVATE (a_this)->col; + a_pos->end_of_line = PRIVATE (a_this)->end_of_line; + a_pos->end_of_file = PRIVATE (a_this)->end_of_input; + + return CR_OK; +} + +/** + *Gets the current parsing location. + *The Parsing location is a public datastructure that + *represents the current line/column/byte offset/ in the input + *stream. + *@param a_this the current instance of #CRInput + *@param a_loc the set parsing location. + *@return CR_OK upon successful completion, an error + *code otherwise. + */ +enum CRStatus +cr_input_get_parsing_location (CRInput *a_this, + CRParsingLocation *a_loc) +{ + g_return_val_if_fail (a_this + && PRIVATE (a_this) + && a_loc, + CR_BAD_PARAM_ERROR) ; + + a_loc->line = PRIVATE (a_this)->line ; + a_loc->column = PRIVATE (a_this)->col ; + if (PRIVATE (a_this)->next_byte_index) { + a_loc->byte_offset = PRIVATE (a_this)->next_byte_index - 1 ; + } else { + a_loc->byte_offset = PRIVATE (a_this)->next_byte_index ; + } + return CR_OK ; +} + +/** + *Getter of the next byte index. + *It actually returns the index of the + *next byte to be read. + *@param a_this the "this pointer" of the current instance of + *#CRInput + *@param a_index out parameter. The returned index. + *@return CR_OK upon successful completion, an error code + *otherwise. + */ +enum CRStatus +cr_input_get_cur_index (CRInput * a_this, glong * a_index) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_index, CR_BAD_PARAM_ERROR); + + *a_index = PRIVATE (a_this)->next_byte_index; + + return CR_OK; +} + +/** + *Setter of the next byte index. + *It sets the index of the next byte to be read. + *@param a_this the "this pointer" of the current instance + *of #CRInput . + *@param a_index the new index to set. + *@return CR_OK upon successful completion, an error code otherwise. + */ +enum CRStatus +cr_input_set_cur_index (CRInput * a_this, glong a_index) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); + + PRIVATE (a_this)->next_byte_index = a_index; + + return CR_OK; +} + +/** + *Sets the end of file flag. + *@param a_this the current instance of #CRInput. + *@param a_eof the new end of file flag. + *@return CR_OK upon successful completion, an error code otherwise. + */ +enum CRStatus +cr_input_set_end_of_file (CRInput * a_this, gboolean a_eof) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); + + PRIVATE (a_this)->end_of_input = a_eof; + + return CR_OK; +} + +/** + *Gets the end of file flag. + *@param a_this the current instance of #CRInput. + *@param a_eof out parameter the place to put the end of + *file flag. + *@return CR_OK upon successful completion, an error code otherwise. + */ +enum CRStatus +cr_input_get_end_of_file (CRInput * a_this, gboolean * a_eof) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_eof, CR_BAD_PARAM_ERROR); + + *a_eof = PRIVATE (a_this)->end_of_input; + + return CR_OK; +} + +/** + *Sets the end of line flag. + *@param a_this the current instance of #CRInput. + *@param a_eol the new end of line flag. + *@return CR_OK upon successful completion, an error code + *otherwise. + */ +enum CRStatus +cr_input_set_end_of_line (CRInput * a_this, gboolean a_eol) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); + + PRIVATE (a_this)->end_of_line = a_eol; + + return CR_OK; +} + +/** + *Gets the end of line flag of the current input. + *@param a_this the current instance of #CRInput + *@param a_eol out parameter. The place to put + *the returned flag + *@return CR_OK upon successful completion, an error code + *otherwise. + */ +enum CRStatus +cr_input_get_end_of_line (CRInput * a_this, gboolean * a_eol) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_eol, CR_BAD_PARAM_ERROR); + + *a_eol = PRIVATE (a_this)->end_of_line; + + return CR_OK; +} + +/** + *Sets the current position in the input stream. + * + *@param a_this the "this pointer" of the current instance of + *#CRInput. + *@param a_pos the new position. + */ +enum CRStatus +cr_input_set_cur_pos (CRInput * a_this, CRInputPos * a_pos) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this) && a_pos, + CR_BAD_PARAM_ERROR); + + cr_input_set_column_num (a_this, a_pos->col); + cr_input_set_line_num (a_this, a_pos->line); + cr_input_set_cur_index (a_this, a_pos->next_byte_index); + cr_input_set_end_of_line (a_this, a_pos->end_of_line); + cr_input_set_end_of_file (a_this, a_pos->end_of_file); + + return CR_OK; +} diff --git a/src/libcroco/cr-input.h b/src/libcroco/cr-input.h new file mode 100644 index 000000000..976b73fb0 --- /dev/null +++ b/src/libcroco/cr-input.h @@ -0,0 +1,174 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset:8 -*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli + * See the COPYRIGHTS file for copyrights information. + */ + +#ifndef __CR_INPUT_SRC_H__ +#define __CR_INPUT_SRC_H__ + + +#include +#include "cr-utils.h" +#include "cr-parsing-location.h" + +G_BEGIN_DECLS + +/** + *@file + *The libcroco basic input stream class + *declaration file. + */ + +typedef struct _CRInput CRInput ; +typedef struct _CRInputPriv CRInputPriv ; + +/** + *The #CRInput class provides the abstraction of + *an utf8-encoded character stream. + */ +struct _CRInput +{ + CRInputPriv *priv ; +} ; + +typedef struct _CRInputPos CRInputPos ; + +struct _CRInputPos +{ + glong line ; + glong col ; + gboolean end_of_file ; + gboolean end_of_line ; + glong next_byte_index ; +} ; + +CRInput * +cr_input_new_from_buf (guchar *a_buf, gulong a_len, + enum CREncoding a_enc, gboolean a_free_buf) ; +CRInput * +cr_input_new_from_uri (const gchar *a_file_uri, + enum CREncoding a_enc) ; + +void +cr_input_destroy (CRInput *a_this) ; + +void +cr_input_ref (CRInput *a_this) ; + +gboolean +cr_input_unref (CRInput *a_this) ; + +enum CRStatus +cr_input_read_byte (CRInput *a_this, guchar *a_byte) ; + +enum CRStatus +cr_input_read_char (CRInput *a_this, guint32 *a_char) ; + +enum CRStatus +cr_input_consume_chars (CRInput *a_this, guint32 a_char, + gulong *a_nb_char) ; + +enum CRStatus +cr_input_consume_char (CRInput *a_this, guint32 a_char) ; + +enum CRStatus +cr_input_consume_white_spaces (CRInput *a_this, gulong *a_nb_chars) ; + +enum CRStatus +cr_input_peek_byte (CRInput *a_this, enum CRSeekPos a_origin, + gulong a_offset, guchar *a_byte) ; + +guchar +cr_input_peek_byte2 (CRInput *a_this, gulong a_offset, + gboolean *a_eof) ; + +enum CRStatus +cr_input_peek_char (CRInput *a_this, guint32 *a_char) ; + +guchar * +cr_input_get_byte_addr (CRInput *a_this, + gulong a_offset) ; + +enum CRStatus +cr_input_get_cur_byte_addr (CRInput *a_this, guchar ** a_offset) ; + +enum CRStatus +cr_input_seek_index (CRInput *a_this, + enum CRSeekPos a_origin, gint a_pos) ; + +enum CRStatus +cr_input_get_cur_index (CRInput *a_this, glong *a_index) ; + +enum CRStatus +cr_input_set_cur_index (CRInput *a_this, glong a_index) ; + +enum CRStatus +cr_input_get_cur_pos (CRInput *a_this, CRInputPos * a_pos) ; + +enum CRStatus +cr_input_set_cur_pos (CRInput *a_this, CRInputPos *a_pos) ; + +enum CRStatus +cr_input_get_parsing_location (CRInput *a_this, + CRParsingLocation *a_loc) ; + +enum CRStatus +cr_input_get_end_of_line (CRInput *a_this, gboolean *a_eol) ; + +enum CRStatus +cr_input_set_end_of_line (CRInput *a_this, gboolean a_eol) ; + +enum CRStatus +cr_input_get_end_of_file (CRInput *a_this, gboolean *a_eof) ; + +enum CRStatus +cr_input_set_end_of_file (CRInput *a_this, gboolean a_eof) ; + +enum CRStatus +cr_input_set_line_num (CRInput *a_this, glong a_line_num) ; + +enum CRStatus +cr_input_get_line_num (CRInput *a_this, glong *a_line_num) ; + +enum CRStatus +cr_input_set_column_num (CRInput *a_this, glong a_col) ; + +enum CRStatus +cr_input_get_column_num (CRInput *a_this, glong *a_col) ; + +enum CRStatus +cr_input_increment_line_num (CRInput *a_this, + glong a_increment) ; + +enum CRStatus +cr_input_increment_col_num (CRInput *a_this, + glong a_increment) ; + +glong +cr_input_get_nb_bytes_left (CRInput *a_this) ; + +enum CRStatus +cr_input_end_of_input (CRInput *a_this, gboolean *a_end_of_input) ; + +G_END_DECLS + +#endif /*__CR_INPUT_SRC_H__*/ + diff --git a/src/libcroco/cr-libxml-node-iface.c b/src/libcroco/cr-libxml-node-iface.c new file mode 100644 index 000000000..fe785491b --- /dev/null +++ b/src/libcroco/cr-libxml-node-iface.c @@ -0,0 +1,81 @@ +#include +#include +#include "cr-libxml-node-iface.h" + +static CRXMLNodePtr +libxml_getParentNode(CRXMLNodePtr cnode) +{ + xmlNode const *xnode = (xmlNode const *) cnode; + return xnode->parent; +} + +static CRXMLNodePtr +libxml_getFirstChild(CRXMLNodePtr cnode) +{ + xmlNode const *xnode = (xmlNode const *) cnode; + return xnode->children; +} + +static CRXMLNodePtr +libxml_getNextSibling(CRXMLNodePtr cnode) +{ + xmlNode const *xnode = (xmlNode const *) cnode; + return xnode->next; +} + +static CRXMLNodePtr +libxml_getPrevSibling(CRXMLNodePtr cnode) +{ + xmlNode const *xnode = (xmlNode const *) cnode; + return xnode->prev; +} + +static char const * +local_part(char const *const qname) +{ + char const *ret = strrchr(qname, ':'); + if (ret) + return ++ret; + else + return qname; +} + +static char const * +libxml_getLocalName(CRXMLNodePtr cnode) +{ + xmlNode const *xnode = (xmlNode const *) cnode; + return local_part(xnode->name); +} + +static char * +libxml_getProp(CRXMLNodePtr cnode, char const *cprop) +{ + xmlNodePtr xnode = (xmlNodePtr) cnode; + xmlChar const *xprop = (xmlChar const *) cprop; + return xmlGetProp(xnode, xprop); +} + +static gboolean +libxml_isElementNode(CRXMLNodePtr cnode) +{ + xmlNode const *xnode = (xmlNode const *) cnode; + return xnode->type == XML_ELEMENT_NODE; +} + +static void +libxml_freePropVal(void *const cval) +{ + xmlFree(cval); +} + +CRNodeIface const cr_libxml_node_iface = { + libxml_getParentNode, + libxml_getFirstChild, + libxml_getNextSibling, + libxml_getPrevSibling, + libxml_getLocalName, + libxml_getProp, /* fixme: Check whether we want xmlGetNoNsProp instead. */ + + libxml_freePropVal, + libxml_isElementNode +}; diff --git a/src/libcroco/cr-libxml-node-iface.h b/src/libcroco/cr-libxml-node-iface.h new file mode 100644 index 000000000..cb9bf2733 --- /dev/null +++ b/src/libcroco/cr-libxml-node-iface.h @@ -0,0 +1,14 @@ +#ifndef __CR_LIBXML_NODE_IFACE_H__ +#define __CR_LIBXML_NODE_IFACE_H__ + +#include +#include "cr-node-iface.h" + +G_BEGIN_DECLS + +CRNodeIface const cr_libxml_node_iface; + +G_END_DECLS + + +#endif/*__CR_LIBXML_NODE_IFACE_H__*/ diff --git a/src/libcroco/cr-node-iface.h b/src/libcroco/cr-node-iface.h new file mode 100644 index 000000000..9c2d30efe --- /dev/null +++ b/src/libcroco/cr-node-iface.h @@ -0,0 +1,35 @@ +#ifndef __CR_NODE_IFACE_H__ +#define __CR_NODE_IFACE_H__ + +#include +#include + +G_BEGIN_DECLS + +typedef gconstpointer CRXMLNodePtr ; +typedef struct _CRNodeIface CRNodeIface ; + +struct _CRNodeIface { + /* Names based on DOM. */ + CRXMLNodePtr (*getParentNode)(CRXMLNodePtr); + CRXMLNodePtr (*getFirstChild)(CRXMLNodePtr); + CRXMLNodePtr (*getNextSibling)(CRXMLNodePtr); + CRXMLNodePtr (*getPrevSibling)(CRXMLNodePtr); + char const *(*getLocalName)(CRXMLNodePtr); + char *(*getProp)(CRXMLNodePtr, char const *); + + /* Others. */ + void (*freePropVal)(void *); + gboolean (*isElementNode)(CRXMLNodePtr); + +#if 0 + char const *getLang(CRXMLNodePtr); + /* todo: Make it easy to have the default xml rules for lang. Maybe interpret NULL + like this. Or provide a cr_get_xml_lang(CRNodeIface const *, CRXMLNodePtr) function. */ +#endif +}; + +G_END_DECLS + + +#endif/*__CR_NODE_IFACE_H__*/ diff --git a/src/libcroco/cr-num.c b/src/libcroco/cr-num.c new file mode 100644 index 000000000..45a63281b --- /dev/null +++ b/src/libcroco/cr-num.c @@ -0,0 +1,299 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli + * See COPYRIGHTS file for copyrights information. + */ + +/** + *@file + *The definition + *of the #CRNum class. + */ + +#include "cr-num.h" +#include "string.h" + +/** + *The default constructor of + *#CRNum. + *@return the newly built instance of + *#CRNum. + */ +CRNum * +cr_num_new (void) +{ + CRNum *result = NULL; + + result = g_try_malloc (sizeof (CRNum)); + + if (result == NULL) { + cr_utils_trace_info ("Out of memory"); + return NULL; + } + + memset (result, 0, sizeof (CRNum)); + + return result; +} + +/** + *A constructor of #CRNum. + *@param a_is_natural indicates whether the intance of #CRNum is + *a natural number or not. + *@param a_integer_part the integer part of the instance + *of #CRNum + *@param a_decimal_part in case the instance of #CRNum + *natural number (but a decimal one) this parameter + *is the decimal part of the instance of #CRNum. + *@return the newly built instance of #CRNum or + *NULL if an error arises. + */ +CRNum * +cr_num_new_with_val (gdouble a_val, enum CRNumType a_type) +{ + CRNum *result = NULL; + + result = cr_num_new (); + + g_return_val_if_fail (result, NULL); + + result->val = a_val; + result->type = a_type; + + return result; +} + +/** + *Returns the string representation of the + *current instance of #CRNum. + *@param a_this the current instance of #CRNum. + *@return the newly built string representation + *of the current instance of #CRNum. The returned + *string is NULL terminated. The caller *must* + *free the returned string. + */ +guchar * +cr_num_to_string (CRNum * a_this) +{ + gdouble test_val = 0.0; + + guchar *tmp_char1 = NULL, + *tmp_char2 = NULL, + *result = NULL; + + g_return_val_if_fail (a_this, NULL); + + test_val = a_this->val - (glong) a_this->val; + + if (!test_val) { + tmp_char1 = g_strdup_printf ("%ld", (glong) a_this->val); + } else { + /* We can't use g_ascii_dtostr, because that sometimes uses + e notation (which wouldn't be a valid number in CSS). */ + size_t const buflen = 35; /* fairly arbitrary. */ + tmp_char1 = g_malloc (buflen); + g_ascii_formatd (tmp_char1, buflen, "%.17f", a_this->val); + } + + g_return_val_if_fail (tmp_char1, NULL); + + switch (a_this->type) { + case NUM_LENGTH_EM: + tmp_char2 = (guchar *) "em"; + break; + + case NUM_LENGTH_EX: + tmp_char2 = (guchar *) "ex"; + break; + + case NUM_LENGTH_PX: + tmp_char2 = (guchar *) "px"; + break; + + case NUM_LENGTH_IN: + tmp_char2 = (guchar *) "in"; + break; + + case NUM_LENGTH_CM: + tmp_char2 = (guchar *) "cm"; + break; + + case NUM_LENGTH_MM: + tmp_char2 = (guchar *) "mm"; + break; + + case NUM_LENGTH_PT: + tmp_char2 = (guchar *) "pt"; + break; + + case NUM_LENGTH_PC: + tmp_char2 = (guchar *) "pc"; + break; + + case NUM_ANGLE_DEG: + tmp_char2 = (guchar *) "deg"; + break; + + case NUM_ANGLE_RAD: + tmp_char2 = (guchar *) "rad"; + break; + + case NUM_ANGLE_GRAD: + tmp_char2 = (guchar *) "grad"; + break; + + case NUM_TIME_MS: + tmp_char2 = (guchar *) "ms"; + break; + + case NUM_TIME_S: + tmp_char2 = (guchar *) "s"; + break; + + case NUM_FREQ_HZ: + tmp_char2 = (guchar *) "Hz"; + break; + + case NUM_FREQ_KHZ: + tmp_char2 = (guchar *) "KHz"; + break; + + case NUM_PERCENTAGE: + tmp_char2 = (guchar *) "%"; + break; + case NUM_INHERIT: + tmp_char2 = (guchar *) "inherit"; + break ; + case NUM_AUTO: + tmp_char2 = (guchar *) "auto"; + break ; + case NUM_GENERIC: + tmp_char2 = NULL ; + break ; + default: + tmp_char2 = (guchar *) "unknown"; + break; + } + + if (tmp_char2) { + result = g_strconcat (tmp_char1, tmp_char2, NULL); + g_free (tmp_char1); + } else { + result = tmp_char1; + } + + return result; +} + +/** + *Copies an instance of #CRNum. + *@param a_src the instance of #CRNum to copy. + *Must be non NULL. + *@param a_dst the destination of the copy. + *Must be non NULL + *@return CR_OK upon successful completion, an + *error code otherwise. + */ +enum CRStatus +cr_num_copy (CRNum * a_dest, CRNum * a_src) +{ + g_return_val_if_fail (a_dest && a_src, CR_BAD_PARAM_ERROR); + + memcpy (a_dest, a_src, sizeof (CRNum)); + + return CR_OK; +} + +/** + *Duplicates an instance of #CRNum + *@param a_this the instance of #CRNum to duplicate. + *@return the newly created (duplicated) instance of #CRNum. + *Must be freed by cr_num_destroy(). + */ +CRNum * +cr_num_dup (CRNum * a_this) +{ + CRNum *result = NULL; + enum CRStatus status = CR_OK; + + g_return_val_if_fail (a_this, NULL); + + result = cr_num_new (); + g_return_val_if_fail (result, NULL); + + status = cr_num_copy (result, a_this); + g_return_val_if_fail (status == CR_OK, NULL); + + return result; +} + +/** + *Sets an instance of #CRNum. + *@param a_this the current instance of #CRNum to be set. + *@param a_val the new numerical value to be hold by the current + *instance of #CRNum + *@param a_type the new type of #CRNum. + */ +enum CRStatus +cr_num_set (CRNum * a_this, gdouble a_val, enum CRNumType a_type) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + a_this->val = a_val; + a_this->type = a_type; + + return CR_OK; +} + +/** + *Tests if the current instance of #CRNum is a fixed + *length value or not. Typically a fixed length value + *is anything from NUM_LENGTH_EM to NUM_LENGTH_PC. + *See the definition of #CRNumType to see what we mean. + *@return TRUE if the instance of #CRNum is a fixed length number, + *FALSE otherwise. + */ +gboolean +cr_num_is_fixed_length (CRNum * a_this) +{ + gboolean result = FALSE; + + g_return_val_if_fail (a_this, FALSE); + + if (a_this->type >= NUM_LENGTH_EM + && a_this->type <= NUM_LENGTH_PC) { + result = TRUE ; + } + return result ; +} + +/** + *The destructor of #CRNum. + *@param a_this the this pointer of + *the current instance of #CRNum. + */ +void +cr_num_destroy (CRNum * a_this) +{ + g_return_if_fail (a_this); + + g_free (a_this); +} diff --git a/src/libcroco/cr-num.h b/src/libcroco/cr-num.h new file mode 100644 index 000000000..83e9dc7c6 --- /dev/null +++ b/src/libcroco/cr-num.h @@ -0,0 +1,127 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli + * See COPYRIGHTS file for copyright information + */ + + +/** + *@file + *The declaration + *of the #CRNum class. + */ + +#ifndef __CR_NUM_H__ +#define __CR_NUM_H__ + +#include +#include "cr-utils.h" +#include "cr-parsing-location.h" + +G_BEGIN_DECLS + +/** + *@file + *The declaration of the #CRNum class. + * + */ + +/** + *The different types + *of numbers. + *Please, do not modify + *the declaration order of the enum + *members, unless you know + *what you are doing. + */ +enum CRNumType +{ + NUM_AUTO = 0, + NUM_GENERIC, + NUM_LENGTH_EM, + NUM_LENGTH_EX, + NUM_LENGTH_PX, + NUM_LENGTH_IN, + NUM_LENGTH_CM, + NUM_LENGTH_MM, + NUM_LENGTH_PT, + NUM_LENGTH_PC, + NUM_ANGLE_DEG, + NUM_ANGLE_RAD, + NUM_ANGLE_GRAD, + NUM_TIME_MS, + NUM_TIME_S, + NUM_FREQ_HZ, + NUM_FREQ_KHZ, + NUM_PERCENTAGE, + NUM_INHERIT, + NUM_UNKNOWN_TYPE, + NB_NUM_TYPE +} ; + + +/** + *An abstraction of a number (num) + *as defined in the css2 spec. + */ +typedef struct _CRNum CRNum ; + +/** + *An abstraction of a number (num) + *as defined in the css2 spec. + */ +struct _CRNum +{ + enum CRNumType type ; + gdouble val ; + CRParsingLocation location ; +} ; + +CRNum * +cr_num_new (void) ; + +CRNum * +cr_num_new_with_val (gdouble a_val, + enum CRNumType a_type) ; + +CRNum * +cr_num_dup (CRNum *a_this) ; + +guchar * +cr_num_to_string (CRNum *a_this) ; + +enum CRStatus +cr_num_copy (CRNum *a_dest, CRNum *a_src) ; + +enum CRStatus +cr_num_set (CRNum *a_this, gdouble a_val, + enum CRNumType a_type) ; + +gboolean +cr_num_is_fixed_length (CRNum *a_this) ; + +void +cr_num_destroy (CRNum *a_this) ; + + +G_END_DECLS + + +#endif /*__CR_NUM_H__*/ diff --git a/src/libcroco/cr-om-parser.c b/src/libcroco/cr-om-parser.c new file mode 100644 index 000000000..245f2d82f --- /dev/null +++ b/src/libcroco/cr-om-parser.c @@ -0,0 +1,1107 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli + * See COPYRIGHTS file for copyright information. + */ + +#include +#include "cr-utils.h" +#include "cr-om-parser.h" + +#define UNUSED(_param) ((void)(_param)) + +/** + *@file + *The definition of the CSS Object Model Parser. + *This parser uses (and sits) the SAC api of libcroco defined + *in cr-parser.h and cr-doc-handler.h + */ + +struct _CROMParserPriv { + CRParser *parser; +}; + +#define PRIVATE(a_this) ((a_this)->priv) + +/* + *Forward declaration of a type defined later + *in this file. + */ +struct _ParsingContext; +typedef struct _ParsingContext ParsingContext; + +static ParsingContext *new_parsing_context (void); + +static void destroy_context (ParsingContext * a_ctxt); + +static void unrecoverable_error (CRDocHandler * a_this); + +static void error (CRDocHandler * a_this); + +static void property (CRDocHandler * a_this, + CRString * a_name, + CRTerm * a_expression, + gboolean a_important); + +static void end_selector (CRDocHandler * a_this, + CRSelector * a_selector_list); + +static void start_selector (CRDocHandler * a_this, + CRSelector * a_selector_list); + +static void start_font_face (CRDocHandler * a_this, + CRParsingLocation *a_location); + +static void end_font_face (CRDocHandler * a_this); + +static void end_document (CRDocHandler * a_this); + +static void start_document (CRDocHandler * a_this); + +static void charset (CRDocHandler * a_this, + CRString * a_charset, + CRParsingLocation *a_location); + +static void start_page (CRDocHandler * a_this, CRString * a_page, + CRString * a_pseudo_page, + CRParsingLocation *a_location); + +static void end_page (CRDocHandler * a_this, CRString * a_page, + CRString * a_pseudo_page); + +static void start_media (CRDocHandler * a_this, + GList * a_media_list, + CRParsingLocation *a_location); + +static void end_media (CRDocHandler * a_this, + GList * a_media_list); + +static void import_style (CRDocHandler * a_this, + GList * a_media_list, + CRString * a_uri, + CRString * a_uri_default_ns, + CRParsingLocation *a_location); + +struct _ParsingContext { + CRStyleSheet *stylesheet; + CRStatement *cur_stmt; + CRStatement *cur_media_stmt; +}; + +/******************************************** + *Private methods + ********************************************/ + +static ParsingContext * +new_parsing_context (void) +{ + ParsingContext *result = NULL; + + result = g_try_malloc (sizeof (ParsingContext)); + if (!result) { + cr_utils_trace_info ("Out of Memory"); + return NULL; + } + memset (result, 0, sizeof (ParsingContext)); + return result; +} + +static void +destroy_context (ParsingContext * a_ctxt) +{ + g_return_if_fail (a_ctxt); + + if (a_ctxt->stylesheet) { + cr_stylesheet_destroy (a_ctxt->stylesheet); + a_ctxt->stylesheet = NULL; + } + if (a_ctxt->cur_stmt) { + cr_statement_destroy (a_ctxt->cur_stmt); + a_ctxt->cur_stmt = NULL; + } + g_free (a_ctxt); +} + +static enum CRStatus +cr_om_parser_init_default_sac_handler (CROMParser * a_this) +{ + CRDocHandler *sac_handler = NULL; + gboolean free_hdlr_if_error = FALSE; + enum CRStatus status = CR_OK; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && PRIVATE (a_this)->parser, + CR_BAD_PARAM_ERROR); + + status = cr_parser_get_sac_handler (PRIVATE (a_this)->parser, + &sac_handler); + g_return_val_if_fail (status == CR_OK, status); + + if (!sac_handler) { + sac_handler = cr_doc_handler_new (); + free_hdlr_if_error = TRUE; + } + + /* + *initialyze here the sac handler. + */ + sac_handler->start_document = start_document; + sac_handler->end_document = end_document; + sac_handler->start_selector = start_selector; + sac_handler->end_selector = end_selector; + sac_handler->property = property; + sac_handler->start_font_face = start_font_face; + sac_handler->end_font_face = end_font_face; + sac_handler->error = error; + sac_handler->unrecoverable_error = unrecoverable_error; + sac_handler->charset = charset; + sac_handler->start_page = start_page; + sac_handler->end_page = end_page; + sac_handler->start_media = start_media; + sac_handler->end_media = end_media; + sac_handler->import_style = import_style; + + status = cr_parser_set_sac_handler (PRIVATE (a_this)->parser, + sac_handler); + if (status == CR_OK) { + return CR_OK; + } + + if (sac_handler && free_hdlr_if_error == TRUE) { + cr_doc_handler_destroy (sac_handler); + sac_handler = NULL; + } + + return status; + +} + +static void +start_document (CRDocHandler * a_this) +{ + ParsingContext *ctxt = NULL; + CRStyleSheet *stylesheet = NULL; + + g_return_if_fail (a_this); + + ctxt = new_parsing_context (); + g_return_if_fail (ctxt); + + stylesheet = cr_stylesheet_new (NULL); + ctxt->stylesheet = stylesheet; + cr_doc_handler_set_ctxt (a_this, ctxt); +} + +static void +start_font_face (CRDocHandler * a_this, + CRParsingLocation *a_location) +{ + enum CRStatus status = CR_OK; + ParsingContext *ctxt = NULL; + ParsingContext **ctxtptr = NULL; + + UNUSED(a_location); + + g_return_if_fail (a_this); + ctxtptr = &ctxt; + status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr); + g_return_if_fail (status == CR_OK && ctxt); + g_return_if_fail (ctxt->cur_stmt == NULL); + + ctxt->cur_stmt = + cr_statement_new_at_font_face_rule (ctxt->stylesheet, NULL); + + g_return_if_fail (ctxt->cur_stmt); +} + +static void +end_font_face (CRDocHandler * a_this) +{ + enum CRStatus status = CR_OK; + ParsingContext *ctxt = NULL; + ParsingContext **ctxtptr = NULL; + CRStatement *stmts = NULL; + + g_return_if_fail (a_this); + + g_return_if_fail (a_this); + ctxtptr = &ctxt; + status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr); + g_return_if_fail (status == CR_OK && ctxt); + g_return_if_fail + (ctxt->cur_stmt + && ctxt->cur_stmt->type == AT_FONT_FACE_RULE_STMT + && ctxt->stylesheet); + + stmts = cr_statement_append (ctxt->stylesheet->statements, + ctxt->cur_stmt); + if (!stmts) + goto error; + + ctxt->stylesheet->statements = stmts; + stmts = NULL; + ctxt->cur_stmt = NULL; + + return; + + error: + + if (ctxt->cur_stmt) { + cr_statement_destroy (ctxt->cur_stmt); + ctxt->cur_stmt = NULL; + } + + if (!stmts) { + cr_statement_destroy (stmts); + stmts = NULL; + } +} + +static void +end_document (CRDocHandler * a_this) +{ + enum CRStatus status = CR_OK; + ParsingContext *ctxt = NULL; + ParsingContext **ctxtptr = NULL; + + g_return_if_fail (a_this); + ctxtptr = &ctxt; + status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr); + g_return_if_fail (status == CR_OK && ctxt); + + if (!ctxt->stylesheet || ctxt->cur_stmt) + goto error; + + status = cr_doc_handler_set_result (a_this, ctxt->stylesheet); + g_return_if_fail (status == CR_OK); + + ctxt->stylesheet = NULL; + destroy_context (ctxt); + cr_doc_handler_set_ctxt (a_this, NULL); + + return; + + error: + if (ctxt) { + destroy_context (ctxt); + } +} + +static void +charset (CRDocHandler * a_this, CRString * a_charset, + CRParsingLocation *a_location) +{ + enum CRStatus status = CR_OK; + CRStatement *stmt = NULL, + *stmt2 = NULL; + CRString *charset = NULL; + + ParsingContext *ctxt = NULL; + ParsingContext **ctxtptr = NULL; + + UNUSED(a_location); + + g_return_if_fail (a_this); + ctxtptr = &ctxt; + status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr); + g_return_if_fail (status == CR_OK && ctxt); + g_return_if_fail (ctxt->stylesheet); + + charset = cr_string_dup (a_charset) ; + stmt = cr_statement_new_at_charset_rule (ctxt->stylesheet, charset); + g_return_if_fail (stmt); + stmt2 = cr_statement_append (ctxt->stylesheet->statements, stmt); + if (!stmt2) { + if (stmt) { + cr_statement_destroy (stmt); + stmt = NULL; + } + if (charset) { + cr_string_destroy (charset); + } + return; + } + ctxt->stylesheet->statements = stmt2; + stmt2 = NULL; +} + +static void +start_page (CRDocHandler * a_this, + CRString * a_page, + CRString * a_pseudo, + CRParsingLocation *a_location) +{ + enum CRStatus status = CR_OK; + ParsingContext *ctxt = NULL; + ParsingContext **ctxtptr = NULL; + + UNUSED(a_location); + + g_return_if_fail (a_this); + ctxtptr = &ctxt; + status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr); + g_return_if_fail (status == CR_OK && ctxt); + g_return_if_fail (ctxt->cur_stmt == NULL); + + ctxt->cur_stmt = cr_statement_new_at_page_rule + (ctxt->stylesheet, NULL, NULL, NULL); + if (a_page) { + ctxt->cur_stmt->kind.page_rule->name = + cr_string_dup (a_page) ; + + if (!ctxt->cur_stmt->kind.page_rule->name) { + goto error; + } + } + if (a_pseudo) { + ctxt->cur_stmt->kind.page_rule->pseudo = + cr_string_dup (a_pseudo) ; + if (!ctxt->cur_stmt->kind.page_rule->pseudo) { + goto error; + } + } + return; + + error: + if (ctxt->cur_stmt) { + cr_statement_destroy (ctxt->cur_stmt); + ctxt->cur_stmt = NULL; + } +} + +static void +end_page (CRDocHandler * a_this, + CRString * a_page, + CRString * a_pseudo_page) +{ + enum CRStatus status = CR_OK; + ParsingContext *ctxt = NULL; + ParsingContext **ctxtptr = NULL; + CRStatement *stmt = NULL; + + g_return_if_fail (a_this); + ctxtptr = &ctxt; + status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr); + g_return_if_fail (status == CR_OK && ctxt); + g_return_if_fail (ctxt->cur_stmt + && ctxt->cur_stmt->type == AT_PAGE_RULE_STMT + && ctxt->stylesheet); + + stmt = cr_statement_append (ctxt->stylesheet->statements, + ctxt->cur_stmt); + + if (stmt) { + ctxt->stylesheet->statements = stmt; + stmt = NULL; + ctxt->cur_stmt = NULL; + } + + if (ctxt->cur_stmt) { + cr_statement_destroy (ctxt->cur_stmt); + ctxt->cur_stmt = NULL; + } + a_page = NULL; /*keep compiler happy */ + a_pseudo_page = NULL; /*keep compiler happy */ +} + +static void +start_media (CRDocHandler * a_this, + GList * a_media_list, + CRParsingLocation *a_location) +{ + enum CRStatus status = CR_OK; + ParsingContext *ctxt = NULL; + ParsingContext **ctxtptr = NULL; + GList *media_list = NULL; + + UNUSED(a_location); + + g_return_if_fail (a_this); + ctxtptr = &ctxt; + status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr); + g_return_if_fail (status == CR_OK && ctxt); + + g_return_if_fail (ctxt + && ctxt->cur_stmt == NULL + && ctxt->cur_media_stmt == NULL + && ctxt->stylesheet); + if (a_media_list) { + /*duplicate the media_list */ + media_list = cr_utils_dup_glist_of_cr_string + (a_media_list); + } + ctxt->cur_media_stmt = + cr_statement_new_at_media_rule + (ctxt->stylesheet, NULL, media_list); + +} + +static void +end_media (CRDocHandler * a_this, GList * a_media_list) +{ + enum CRStatus status = CR_OK; + ParsingContext *ctxt = NULL; + ParsingContext **ctxtptr = NULL; + CRStatement *stmts = NULL; + + g_return_if_fail (a_this); + ctxtptr = &ctxt; + status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr); + g_return_if_fail (status == CR_OK && ctxt); + g_return_if_fail (ctxt + && ctxt->cur_media_stmt + && ctxt->cur_media_stmt->type == AT_MEDIA_RULE_STMT + && ctxt->stylesheet); + + stmts = cr_statement_append (ctxt->stylesheet->statements, + ctxt->cur_media_stmt); + if (!stmts) { + cr_statement_destroy (ctxt->cur_media_stmt); + ctxt->cur_media_stmt = NULL; + } + + ctxt->stylesheet->statements = stmts; + stmts = NULL; + + ctxt->cur_stmt = NULL ; + ctxt->cur_media_stmt = NULL ; + a_media_list = NULL; +} + +static void +import_style (CRDocHandler * a_this, + GList * a_media_list, + CRString * a_uri, + CRString * a_uri_default_ns, + CRParsingLocation *a_location) +{ + enum CRStatus status = CR_OK; + CRString *uri = NULL; + CRStatement *stmt = NULL, + *stmt2 = NULL; + ParsingContext *ctxt = NULL; + ParsingContext **ctxtptr = NULL; + GList *media_list = NULL ; + + UNUSED(a_location); + + g_return_if_fail (a_this); + ctxtptr = &ctxt; + status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr); + g_return_if_fail (status == CR_OK && ctxt); + g_return_if_fail (ctxt->stylesheet); + + uri = cr_string_dup (a_uri) ; + if (a_media_list) + media_list = cr_utils_dup_glist_of_cr_string (a_media_list) ; + stmt = cr_statement_new_at_import_rule + (ctxt->stylesheet, uri, media_list, NULL); + if (!stmt) + goto error; + + if (ctxt->cur_stmt) { + stmt2 = cr_statement_append (ctxt->cur_stmt, stmt); + if (!stmt2) + goto error; + ctxt->cur_stmt = stmt2; + stmt2 = NULL; + stmt = NULL; + } else { + stmt2 = cr_statement_append (ctxt->stylesheet->statements, + stmt); + if (!stmt2) + goto error; + ctxt->stylesheet->statements = stmt2; + stmt2 = NULL; + stmt = NULL; + } + + return; + + error: + if (uri) { + cr_string_destroy (uri); + } + + if (stmt) { + cr_statement_destroy (stmt); + stmt = NULL; + } + a_uri_default_ns = NULL; /*keep compiler happy */ +} + +static void +start_selector (CRDocHandler * a_this, CRSelector * a_selector_list) +{ + enum CRStatus status = CR_OK ; + ParsingContext *ctxt = NULL; + ParsingContext **ctxtptr = NULL; + + g_return_if_fail (a_this); + ctxtptr = &ctxt; + status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr); + g_return_if_fail (status == CR_OK && ctxt); + if (ctxt->cur_stmt) { + /*hmm, this should be NULL so free it */ + cr_statement_destroy (ctxt->cur_stmt); + ctxt->cur_stmt = NULL; + } + + ctxt->cur_stmt = cr_statement_new_ruleset + (ctxt->stylesheet, a_selector_list, NULL, NULL); +} + +static void +end_selector (CRDocHandler * a_this, CRSelector * a_selector_list) +{ + enum CRStatus status = CR_OK; + ParsingContext *ctxt = NULL; + ParsingContext **ctxtptr = NULL; + + g_return_if_fail (a_this); + ctxtptr = &ctxt; + status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr); + g_return_if_fail (status == CR_OK && ctxt); + g_return_if_fail (ctxt->cur_stmt && ctxt->stylesheet); + + if (ctxt->cur_stmt) { + CRStatement *stmts = NULL; + + if (ctxt->cur_media_stmt) { + CRAtMediaRule *media_rule = NULL; + + media_rule = ctxt->cur_media_stmt->kind.media_rule; + + stmts = cr_statement_append + (media_rule->rulesets, ctxt->cur_stmt); + + if (!stmts) { + cr_utils_trace_info + ("Could not append a new statement"); + cr_statement_destroy (media_rule->rulesets); + ctxt->cur_media_stmt-> + kind.media_rule->rulesets = NULL; + return; + } + media_rule->rulesets = stmts; + ctxt->cur_stmt = NULL; + } else { + stmts = cr_statement_append + (ctxt->stylesheet->statements, + ctxt->cur_stmt); + if (!stmts) { + cr_utils_trace_info + ("Could not append a new statement"); + cr_statement_destroy (ctxt->cur_stmt); + ctxt->cur_stmt = NULL; + return; + } + ctxt->stylesheet->statements = stmts; + ctxt->cur_stmt = NULL; + } + + } + a_selector_list = NULL; /*keep compiler happy */ +} + +static void +property (CRDocHandler * a_this, + CRString * a_name, + CRTerm * a_expression, + gboolean a_important) +{ + enum CRStatus status = CR_OK; + ParsingContext *ctxt = NULL; + ParsingContext **ctxtptr = NULL; + CRDeclaration *decl = NULL, + *decl2 = NULL; + CRString *str = NULL; + + g_return_if_fail (a_this); + ctxtptr = &ctxt; + status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr); + g_return_if_fail (status == CR_OK && ctxt); + + /* + *make sure a current ruleset statement has been allocated + *already. + */ + g_return_if_fail + (ctxt->cur_stmt + && + (ctxt->cur_stmt->type == RULESET_STMT + || ctxt->cur_stmt->type == AT_FONT_FACE_RULE_STMT + || ctxt->cur_stmt->type == AT_PAGE_RULE_STMT)); + + if (a_name) { + str = cr_string_dup (a_name); + g_return_if_fail (str); + } + + /*instanciates a new declaration */ + decl = cr_declaration_new (ctxt->cur_stmt, str, a_expression); + g_return_if_fail (decl); + str = NULL; + decl->important = a_important; + /* + *add the new declaration to the current statement + *being build. + */ + switch (ctxt->cur_stmt->type) { + case RULESET_STMT: + decl2 = cr_declaration_append + (ctxt->cur_stmt->kind.ruleset->decl_list, decl); + if (!decl2) { + cr_declaration_destroy (decl); + cr_utils_trace_info + ("Could not append decl to ruleset"); + goto error; + } + ctxt->cur_stmt->kind.ruleset->decl_list = decl2; + decl = NULL; + decl2 = NULL; + break; + + case AT_FONT_FACE_RULE_STMT: + decl2 = cr_declaration_append + (ctxt->cur_stmt->kind.font_face_rule->decl_list, + decl); + if (!decl2) { + cr_declaration_destroy (decl); + cr_utils_trace_info + ("Could not append decl to ruleset"); + goto error; + } + ctxt->cur_stmt->kind.font_face_rule->decl_list = decl2; + decl = NULL; + decl2 = NULL; + break; + case AT_PAGE_RULE_STMT: + decl2 = cr_declaration_append + (ctxt->cur_stmt->kind.page_rule->decl_list, decl); + if (!decl2) { + cr_declaration_destroy (decl); + cr_utils_trace_info + ("Could not append decl to ruleset"); + goto error; + } + ctxt->cur_stmt->kind.page_rule->decl_list = decl2; + decl = NULL; + decl2 = NULL; + break; + + default: + goto error; + break; + } + + return; + + error: + if (str) { + g_free (str); + str = NULL; + } + + if (decl) { + cr_declaration_destroy (decl); + decl = NULL; + } +} + +static void +error (CRDocHandler * a_this) +{ + enum CRStatus status = CR_OK; + ParsingContext *ctxt = NULL; + ParsingContext **ctxtptr = NULL; + + g_return_if_fail (a_this); + ctxtptr = &ctxt; + status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr); + g_return_if_fail (status == CR_OK && ctxt); + + if (ctxt->cur_stmt) { + cr_statement_destroy (ctxt->cur_stmt); + ctxt->cur_stmt = NULL; + } +} + +static void +unrecoverable_error (CRDocHandler * a_this) +{ + enum CRStatus status = CR_OK; + ParsingContext *ctxt = NULL; + ParsingContext **ctxtptr = NULL; + + ctxtptr = &ctxt; + status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr); + g_return_if_fail (status == CR_OK); + + if (ctxt) { + if (ctxt->stylesheet) { + status = cr_doc_handler_set_result + (a_this, ctxt->stylesheet); + g_return_if_fail (status == CR_OK); + } + g_free (ctxt); + cr_doc_handler_set_ctxt (a_this, NULL); + } +} + +/******************************************** + *Public methods + ********************************************/ + +/** + *Constructor of the CROMParser. + *@param a_input the input stream. + *@return the newly built instance of #CROMParser. + */ +CROMParser * +cr_om_parser_new (CRInput * a_input) +{ + CROMParser *result = NULL; + enum CRStatus status = CR_OK; + + result = g_try_malloc (sizeof (CROMParser)); + + if (!result) { + cr_utils_trace_info ("Out of memory"); + return NULL; + } + + memset (result, 0, sizeof (CROMParser)); + PRIVATE (result) = g_try_malloc (sizeof (CROMParserPriv)); + + if (!PRIVATE (result)) { + cr_utils_trace_info ("Out of memory"); + goto error; + } + + memset (PRIVATE (result), 0, sizeof (CROMParserPriv)); + + PRIVATE (result)->parser = cr_parser_new_from_input (a_input); + + if (!PRIVATE (result)->parser) { + cr_utils_trace_info ("parsing instanciation failed"); + goto error; + } + + status = cr_om_parser_init_default_sac_handler (result); + + if (status != CR_OK) { + goto error; + } + + return result; + + error: + + if (result) { + cr_om_parser_destroy (result); + } + + return NULL; +} + +/** + *Parses the content of an in memory buffer. + *@param a_this the current instance of #CROMParser. + *@param a_buf the in memory buffer to parse. + *@param a_len the length of the in memory buffer in number of bytes. + *@param a_enc the encoding of the in memory buffer. + *@param a_result out parameter the resulting style sheet + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_om_parser_parse_buf (CROMParser * a_this, + const guchar * a_buf, + gulong a_len, + enum CREncoding a_enc, CRStyleSheet ** a_result) +{ + + enum CRStatus status = CR_OK; + + g_return_val_if_fail (a_this && a_result, CR_BAD_PARAM_ERROR); + + if (!PRIVATE (a_this)->parser) { + PRIVATE (a_this)->parser = cr_parser_new (NULL); + } + + status = cr_parser_parse_buf (PRIVATE (a_this)->parser, + a_buf, a_len, a_enc); + + if (status == CR_OK) { + CRStyleSheet *result = NULL; + CRStyleSheet **resultptr = NULL; + CRDocHandler *sac_handler = NULL; + + cr_parser_get_sac_handler (PRIVATE (a_this)->parser, + &sac_handler); + g_return_val_if_fail (sac_handler, CR_ERROR); + resultptr = &result; + status = cr_doc_handler_get_result (sac_handler, + (gpointer *) resultptr); + g_return_val_if_fail (status == CR_OK, status); + + if (result) + *a_result = result; + } + + return status; +} + +/** + *The simpler way to parse an in memory css2 buffer. + *@param a_buf the css2 in memory buffer. + *@param a_len the length of the in memory buffer. + *@param a_enc the encoding of the in memory buffer. + *@param a_result out parameter. The resulting css2 style sheet. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_om_parser_simply_parse_buf (const guchar * a_buf, + gulong a_len, + enum CREncoding a_enc, + CRStyleSheet ** a_result) +{ + CROMParser *parser = NULL; + enum CRStatus status = CR_OK; + + parser = cr_om_parser_new (NULL); + if (!parser) { + cr_utils_trace_info ("Could not create om parser"); + cr_utils_trace_info ("System possibly out of memory"); + return CR_ERROR; + } + + status = cr_om_parser_parse_buf (parser, a_buf, a_len, + a_enc, a_result); + + if (parser) { + cr_om_parser_destroy (parser); + parser = NULL; + } + + return status; +} + +/** + *Parses a css2 stylesheet contained + *in a file. + *@param a_this the current instance of the cssom parser. + *@param a_file_uri the uri of the file. + *(only local file paths are suppported so far) + *@param a_enc the encoding of the file. + *@param a_result out parameter. A pointer + *the build css object model. + *@param CR_OK upon successfull completion, an error code + *otherwise. + */ +enum CRStatus +cr_om_parser_parse_file (CROMParser * a_this, + const guchar * a_file_uri, + enum CREncoding a_enc, CRStyleSheet ** a_result) +{ + enum CRStatus status = CR_OK; + + g_return_val_if_fail (a_this && a_file_uri && a_result, + CR_BAD_PARAM_ERROR); + + if (!PRIVATE (a_this)->parser) { + PRIVATE (a_this)->parser = cr_parser_new_from_file + (a_file_uri, a_enc); + } + + status = cr_parser_parse_file (PRIVATE (a_this)->parser, + a_file_uri, a_enc); + + if (status == CR_OK) { + CRStyleSheet *result = NULL; + CRStyleSheet **resultptr = NULL; + CRDocHandler *sac_handler = NULL; + + cr_parser_get_sac_handler (PRIVATE (a_this)->parser, + &sac_handler); + g_return_val_if_fail (sac_handler, CR_ERROR); + resultptr = &result; + status = cr_doc_handler_get_result + (sac_handler, (gpointer *) resultptr); + g_return_val_if_fail (status == CR_OK, status); + if (result) + *a_result = result; + } + + return status; +} + +/** + *The simpler method to parse a css2 file. + *@param a_file_path the css2 local file path. + *@param a_enc the file encoding. + *@param a_result out parameter. The returned css stylesheet. + *Must be freed by the caller using cr_stylesheet_destroy. + *@return CR_OK upon successfull completion, an error code otherwise. + *Note that this method uses cr_om_parser_parse_file() so both methods + *have the same return values. + */ +enum CRStatus +cr_om_parser_simply_parse_file (const guchar * a_file_path, + enum CREncoding a_enc, + CRStyleSheet ** a_result) +{ + CROMParser *parser = NULL; + enum CRStatus status = CR_OK; + + parser = cr_om_parser_new (NULL); + if (!parser) { + cr_utils_trace_info ("Could not allocate om parser"); + cr_utils_trace_info ("System may be out of memory"); + return CR_ERROR; + } + + status = cr_om_parser_parse_file (parser, a_file_path, + a_enc, a_result); + if (parser) { + cr_om_parser_destroy (parser); + parser = NULL; + } + + return status; +} + +/** + *Parses three sheets located by their paths and build a cascade + *@param a_this the current instance of #CROMParser + *@param a_author_path the path to the author stylesheet + *@param a_user_path the path to the user stylesheet + *@param a_ua_path the path to the User Agent stylesheet + *@param a_result out parameter. The resulting cascade if the parsing + *was okay + *@return CR_OK upon successful completion, an error code otherwise + */ +enum CRStatus +cr_om_parser_parse_paths_to_cascade (CROMParser * a_this, + const guchar * a_author_path, + const guchar * a_user_path, + const guchar * a_ua_path, + enum CREncoding a_encoding, + CRCascade ** a_result) +{ + enum CRStatus status = CR_OK; + + /*0->author sheet, 1->user sheet, 2->UA sheet */ + CRStyleSheet *sheets[3]; + guchar *paths[3]; + CRCascade *result = NULL; + gint i = 0; + + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + memset (sheets, 0, sizeof (sheets)); + paths[0] = (guchar *) a_author_path; + paths[1] = (guchar *) a_user_path; + paths[2] = (guchar *) a_ua_path; + + for (i = 0; i < 3; i++) { + status = cr_om_parser_parse_file (a_this, paths[i], + a_encoding, &sheets[i]); + if (status != CR_OK) { + if (sheets[i]) { + cr_stylesheet_unref (sheets[i]); + sheets[i] = NULL; + } + continue; + } + } + result = cr_cascade_new (sheets[0], sheets[1], sheets[2]); + if (!result) { + for (i = 0; i < 3; i++) { + cr_stylesheet_unref (sheets[i]); + sheets[i] = 0; + } + return CR_ERROR; + } + *a_result = result; + return CR_OK; +} + +/** + *Parses three sheets located by their paths and build a cascade + *@param a_author_path the path to the author stylesheet + *@param a_user_path the path to the user stylesheet + *@param a_ua_path the path to the User Agent stylesheet + *@param a_result out parameter. The resulting cascade if the parsing + *was okay + *@return CR_OK upon successful completion, an error code otherwise + */ +enum CRStatus +cr_om_parser_simply_parse_paths_to_cascade (const guchar * a_author_path, + const guchar * a_user_path, + const guchar * a_ua_path, + enum CREncoding a_encoding, + CRCascade ** a_result) +{ + enum CRStatus status = CR_OK; + CROMParser *parser = NULL; + + parser = cr_om_parser_new (NULL); + if (!parser) { + cr_utils_trace_info ("could not allocated om parser"); + cr_utils_trace_info ("System may be out of memory"); + return CR_ERROR; + } + status = cr_om_parser_parse_paths_to_cascade (parser, + a_author_path, + a_user_path, + a_ua_path, + a_encoding, a_result); + if (parser) { + cr_om_parser_destroy (parser); + parser = NULL; + } + return status; +} + +/** + *Destructor of the #CROMParser. + *@param a_this the current instance of #CROMParser. + */ +void +cr_om_parser_destroy (CROMParser * a_this) +{ + g_return_if_fail (a_this && PRIVATE (a_this)); + + if (PRIVATE (a_this)->parser) { + cr_parser_destroy (PRIVATE (a_this)->parser); + PRIVATE (a_this)->parser = NULL; + } + + if (PRIVATE (a_this)) { + g_free (PRIVATE (a_this)); + PRIVATE (a_this) = NULL; + } + + if (a_this) { + g_free (a_this); + a_this = NULL; + } +} diff --git a/src/libcroco/cr-om-parser.h b/src/libcroco/cr-om-parser.h new file mode 100644 index 000000000..4fc63011a --- /dev/null +++ b/src/libcroco/cr-om-parser.h @@ -0,0 +1,98 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * Copyright (C) 2002-2003 Dodji Seketeli + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the + * GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +/* + *$Id: cr-om-parser.h,v 1.9 2004/01/29 22:05:14 dodji Exp $ + */ + +#ifndef __CR_OM_PARSER_H__ +#define __CR_OM_PARSER_H__ + +#include "cr-parser.h" +#include "cr-cascade.h" + + +/** + *@file + *The definition of the CSS Object Model Parser. + *This parser uses (and sits) the SAC api of libcroco defined + *in cr-parser.h and cr-doc-handler.h + */ + +G_BEGIN_DECLS + +typedef struct _CROMParser CROMParser ; +typedef struct _CROMParserPriv CROMParserPriv ; + +/** + *The Object model parser. + *Can parse a css file and build a css object model. + *This parser uses an instance of #CRParser and defines + *a set of SAC callbacks to build the Object Model. + */ +struct _CROMParser +{ + CROMParserPriv *priv ; +} ; + +CROMParser * cr_om_parser_new (CRInput *a_input) ; + + +enum CRStatus cr_om_parser_simply_parse_file (const guchar *a_file_path, + enum CREncoding a_enc, + CRStyleSheet **a_result) ; + +enum CRStatus cr_om_parser_parse_file (CROMParser *a_this, + const guchar *a_file_uri, + enum CREncoding a_enc, + CRStyleSheet **a_result) ; + +enum CRStatus cr_om_parser_simply_parse_buf (const guchar *a_buf, + gulong a_len, + enum CREncoding a_enc, + CRStyleSheet **a_result) ; + +enum CRStatus cr_om_parser_parse_buf (CROMParser *a_this, + const guchar *a_buf, + gulong a_len, + enum CREncoding a_enc, + CRStyleSheet **a_result) ; + +enum CRStatus cr_om_parser_parse_paths_to_cascade (CROMParser *a_this, + const guchar *a_author_path, + const guchar *a_user_path, + const guchar *a_ua_path, + enum CREncoding a_encoding, + CRCascade ** a_result) ; + +enum CRStatus cr_om_parser_simply_parse_paths_to_cascade (const guchar *a_author_path, + const guchar *a_user_path, + const guchar *a_ua_path, + enum CREncoding a_encoding, + CRCascade ** a_result) ; + +void cr_om_parser_destroy (CROMParser *a_this) ; + +G_END_DECLS + +#endif /*__CR_OM_PARSER_H__*/ diff --git a/src/libcroco/cr-parser.c b/src/libcroco/cr-parser.c new file mode 100644 index 000000000..7e71b927d --- /dev/null +++ b/src/libcroco/cr-parser.c @@ -0,0 +1,4408 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the + * GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli + * See COPYRIGHTS file for copyrights information. + */ + +/** + *@file + *The definition of the #CRParser class. + */ + +#include "string.h" +#include "cr-parser.h" +#include "cr-num.h" +#include "cr-term.h" +#include "cr-simple-sel.h" +#include "cr-attr-sel.h" + +/* + *Random notes: + *CSS core syntax vs CSS level 2 syntax + *===================================== + * + *One must keep in mind + *that css UA must comply with two syntax. + * + *1/the specific syntax that defines the css language + *for a given level of specificatin (e.g css2 syntax + *defined in appendix D.1 of the css2 spec) + * + *2/the core (general) syntax that is there to allow + *UAs to parse style sheets written in levels of CSS that + *didn't exist at the time the UAs were created. + * + *the name of parsing functions (or methods) contained in this file + *follows the following scheme: cr_parser_parse_ (...) ; + *where is the name + *of a production of the css2 language. + *When a given production is + *defined by the css2 level grammar *and* by the + *css core syntax, there will be two functions to parse that production: + *one will parse the production defined by the css2 level grammar and the + *other will parse the production defined by the css core grammar. + *The css2 level grammar related parsing function will be called: + *cr_parser_parse_ (...) ; + *Then css core grammar related parsing function will be called: + *cr_parser_parse__core (...) ; + * + *If a production is defined only by the css core grammar, then + *it will be named: + *cr_parser_parse__core (...) ; + */ + +typedef struct _CRParserError CRParserError; + +/** + *An abstraction of an error reported by by the + *parsing routines. + */ +struct _CRParserError { + guchar *msg; + enum CRStatus status; + glong line; + glong column; + glong byte_num; +}; + +enum CRParserState { + READY_STATE = 0, + TRY_PARSE_CHARSET_STATE, + CHARSET_PARSED_STATE, + TRY_PARSE_IMPORT_STATE, + IMPORT_PARSED_STATE, + TRY_PARSE_RULESET_STATE, + RULESET_PARSED_STATE, + TRY_PARSE_MEDIA_STATE, + MEDIA_PARSED_STATE, + TRY_PARSE_PAGE_STATE, + PAGE_PARSED_STATE, + TRY_PARSE_FONT_FACE_STATE, + FONT_FACE_PARSED_STATE +} ; + +/** + *The private attributes of + *#CRParser. + */ +struct _CRParserPriv { + /** + *The tokenizer + */ + CRTknzr *tknzr; + + /** + *The sac handlers to call + *to notify the parsing of + *the css2 constructions. + */ + CRDocHandler *sac_handler; + + /** + *A stack of errors reported + *by the parsing routines. + *Contains instance of #CRParserError. + *This pointer is the top of the stack. + */ + GList *err_stack; + + enum CRParserState state; + gboolean resolve_import; + gboolean is_case_sensitive; + gboolean use_core_grammar; +}; + +#define PRIVATE(obj) ((obj)->priv) + +#define CHARS_TAB_SIZE 12 + +/** + *return TRUE if the character is a number ([0-9]), FALSE otherwise + *@param a_char the char to test. + */ +#define IS_NUM(a_char) (((a_char) >= '0' && (a_char) <= '9')?TRUE:FALSE) + +/** + *Checks if 'status' equals CR_OK. If not, goto the 'error' label. + * + *@param status the status (of type enum CRStatus) to test. + *@param is_exception if set to FALSE, the final status returned + *by the current function will be CR_PARSING_ERROR. If set to TRUE, the + *current status will be the current value of the 'status' variable. + * + */ +#define CHECK_PARSING_STATUS(status, is_exception) \ +if ((status) != CR_OK) \ +{ \ + if (is_exception == FALSE) \ + { \ + status = CR_PARSING_ERROR ; \ + } \ + goto error ; \ +} + +/** + *same as CHECK_PARSING_STATUS() but this one pushes an error + *on the parser error stack when an error arises. + *@param a_this the current instance of #CRParser . + *@param a_status the status to check. Is of type enum #CRStatus. + *@param a_is_exception in case of error, if is TRUE, the status + *is set to CR_PARSING_ERROR before goto error. If is false, the + *real low level status is kept and will be returned by the + *upper level function that called this macro. Usally,this must + *be set to FALSE. + * + */ +#define CHECK_PARSING_STATUS_ERR(a_this, a_status, a_is_exception,\ + a_err_msg, a_err_status) \ +if ((a_status) != CR_OK) \ +{ \ + if (a_is_exception == FALSE) a_status = CR_PARSING_ERROR ; \ + cr_parser_push_error (a_this, a_err_msg, a_err_status) ; \ + goto error ; \ +} + +/** + *Peeks the next char from the input stream of the current parser + *by invoking cr_tknzr_input_peek_char(). + *invokes CHECK_PARSING_STATUS on the status returned by + *cr_tknzr_peek_char(). + * + *@param a_this the current instance of #CRParser. + *@param a_to_char a pointer to the char where to store the + *char peeked. + */ +#define PEEK_NEXT_CHAR(a_this, a_to_char) \ +{\ +enum CRStatus status ; \ +status = cr_tknzr_peek_char (PRIVATE (a_this)->tknzr, a_to_char) ; \ +CHECK_PARSING_STATUS (status, TRUE) \ +} + +/** + *Reads the next char from the input stream of the current parser. + *In case of error, jumps to the "error:" label located in the + *function where this macro is called. + *@param a_this the curent instance of #CRParser + *@param to_char a pointer to the guint32 char where to store + *the character read. + */ +#define READ_NEXT_CHAR(a_this, a_to_char) \ +status = cr_tknzr_read_char (PRIVATE (a_this)->tknzr, a_to_char) ; \ +CHECK_PARSING_STATUS (status, TRUE) + +/** + *Gets information about the current position in + *the input of the parser. + *In case of failure, this macro returns from the + *calling function and + *returns a status code of type enum #CRStatus. + *@param a_this the current instance of #CRParser. + *@param a_pos out parameter. A pointer to the position + *inside the current parser input. Must + */ +#define RECORD_INITIAL_POS(a_this, a_pos) \ +status = cr_tknzr_get_cur_pos (PRIVATE \ +(a_this)->tknzr, a_pos) ; \ +g_return_val_if_fail (status == CR_OK, status) + +/** + *Gets the address of the current byte inside the + *parser input. + *@param parser the current instance of #CRParser. + *@param addr out parameter a pointer (guchar*) + *to where the address must be put. + */ +#define RECORD_CUR_BYTE_ADDR(a_this, a_addr) \ +status = cr_tknzr_get_cur_byte_addr \ + (PRIVATE (a_this)->tknzr, a_addr) ; \ +CHECK_PARSING_STATUS (status, TRUE) + +/** + *Peeks a byte from the topmost parser input at + *a given offset from the current position. + *If it fails, goto the "error:" label. + * + *@param a_parser the current instance of #CRParser. + *@param a_offset the offset of the byte to peek, the + *current byte having the offset '0'. + *@param a_byte_ptr out parameter a pointer (guchar*) to + *where the peeked char is to be stored. + */ +#define PEEK_BYTE(a_parser, a_offset, a_byte_ptr) \ +status = cr_tknzr_peek_byte (PRIVATE (a_this)->tknzr, \ + a_offset, \ + a_byte_ptr) ; \ +CHECK_PARSING_STATUS (status, TRUE) ; + +#define BYTE(a_parser, a_offset, a_eof) \ +cr_tknzr_peek_byte2 (PRIVATE (a_this)->tknzr, a_offset, a_eof) + +/** + *Reads a byte from the topmost parser input + *steam. + *If it fails, goto the "error" label. + *@param a_this the current instance of #CRParser. + *@param a_byte_ptr the guchar * where to put the read char. + */ +#define READ_NEXT_BYTE(a_this, a_byte_ptr) \ +status = cr_tknzr_read_byte (PRIVATE (a_this)->tknzr, a_byte_ptr) ; \ +CHECK_PARSING_STATUS (status, TRUE) ; + +/** + *Skips a given number of byte in the topmost + *parser input. Don't update line and column number. + *In case of error, jumps to the "error:" label + *of the surrounding function. + *@param a_parser the current instance of #CRParser. + *@param a_nb_bytes the number of bytes to skip. + */ +#define SKIP_BYTES(a_this, a_nb_bytes) \ +status = cr_tknzr_seek_index (PRIVATE (a_this)->tknzr, \ + CR_SEEK_CUR, a_nb_bytes) ; \ +CHECK_PARSING_STATUS (status, TRUE) ; + +/** + *Skip utf8 encoded characters. + *Updates line and column numbers. + *@param a_parser the current instance of #CRParser. + *@param a_nb_chars the number of chars to skip. Must be of + *type glong. + */ +#define SKIP_CHARS(a_parser, a_nb_chars) \ +{ \ +glong nb_chars = a_nb_chars ; \ +status = cr_tknzr_consume_chars \ + (PRIVATE (a_parser)->tknzr,0, &nb_chars) ; \ +CHECK_PARSING_STATUS (status, TRUE) ; \ +} + +/** + *Tests the condition and if it is false, sets + *status to "CR_PARSING_ERROR" and goto the 'error' + *label. + *@param condition the condition to test. + */ +#define ENSURE_PARSING_COND(condition) \ +if (! (condition)) {status = CR_PARSING_ERROR; goto error ;} + +#define ENSURE_PARSING_COND_ERR(a_this, a_condition, \ + a_err_msg, a_err_status) \ +if (! (a_condition)) \ +{ \ + status = CR_PARSING_ERROR; \ + cr_parser_push_error (a_this, a_err_msg, a_err_status) ; \ + goto error ; \ +} + +#define GET_NEXT_TOKEN(a_this, a_token_ptr) \ +status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, \ + a_token_ptr) ; \ +ENSURE_PARSING_COND (status == CR_OK) ; + +#ifdef WITH_UNICODE_ESCAPE_AND_RANGE +static enum CRStatus cr_parser_parse_unicode_escape (CRParser * a_this, + guint32 * a_unicode); +static enum CRStatus cr_parser_parse_escape (CRParser * a_this, + guint32 * a_esc_code); + +static enum CRStatus cr_parser_parse_unicode_range (CRParser * a_this, + CRString ** a_inf, + CRString ** a_sup); +#endif + +static enum CRStatus cr_parser_parse_stylesheet_core (CRParser * a_this); + +static enum CRStatus cr_parser_parse_atrule_core (CRParser * a_this); + +static enum CRStatus cr_parser_parse_ruleset_core (CRParser * a_this); + +static enum CRStatus cr_parser_parse_selector_core (CRParser * a_this); + +static enum CRStatus cr_parser_parse_declaration_core (CRParser * a_this); + +static enum CRStatus cr_parser_parse_any_core (CRParser * a_this); + +static enum CRStatus cr_parser_parse_block_core (CRParser * a_this); + +static enum CRStatus cr_parser_parse_value_core (CRParser * a_this); + +static enum CRStatus cr_parser_parse_string (CRParser * a_this, + CRString ** a_str); + +static enum CRStatus cr_parser_parse_ident (CRParser * a_this, + CRString ** a_str); + +static enum CRStatus cr_parser_parse_uri (CRParser * a_this, + CRString ** a_str); + +static enum CRStatus cr_parser_parse_function (CRParser * a_this, + CRString ** a_func_name, + CRTerm ** a_expr); +static enum CRStatus cr_parser_parse_property (CRParser * a_this, + CRString ** a_property); + +static enum CRStatus cr_parser_parse_attribute_selector (CRParser * a_this, + CRAttrSel ** a_sel); + +static enum CRStatus cr_parser_parse_simple_selector (CRParser * a_this, + CRSimpleSel ** a_sel); + +static enum CRStatus cr_parser_parse_simple_sels (CRParser * a_this, + CRSimpleSel ** a_sel); + +static CRParserError *cr_parser_error_new (const guchar * a_msg, + enum CRStatus); + +static void cr_parser_error_set_msg (CRParserError * a_this, + const guchar * a_msg); + +static void cr_parser_error_dump (CRParserError * a_this); + +static void cr_parser_error_set_status (CRParserError * a_this, + enum CRStatus a_status); + +static void cr_parser_error_set_pos (CRParserError * a_this, + glong a_line, + glong a_column, glong a_byte_num); +static void + cr_parser_error_destroy (CRParserError * a_this); + +static enum CRStatus cr_parser_push_error (CRParser * a_this, + const guchar * a_msg, + enum CRStatus a_status); + +static enum CRStatus cr_parser_dump_err_stack (CRParser * a_this, + gboolean a_clear_errs); +static enum CRStatus + cr_parser_clear_errors (CRParser * a_this); + +/***************************** + *error managemet methods + *****************************/ + +/** + *Constructor of #CRParserError class. + *@param a_msg the brute error message. + *@param a_status the error status. + *@return the newly built instance of #CRParserError. + */ +static CRParserError * +cr_parser_error_new (const guchar * a_msg, enum CRStatus a_status) +{ + CRParserError *result = NULL; + + result = g_try_malloc (sizeof (CRParserError)); + + if (result == NULL) { + cr_utils_trace_info ("Out of memory"); + return NULL; + } + + memset (result, 0, sizeof (CRParserError)); + + cr_parser_error_set_msg (result, a_msg); + cr_parser_error_set_status (result, a_status); + + return result; +} + +/** + *Sets the message associated to this instance of #CRError. + *@param a_this the current instance of #CRParserError. + *@param a_msg the new message. + */ +static void +cr_parser_error_set_msg (CRParserError * a_this, const guchar * a_msg) +{ + g_return_if_fail (a_this); + + if (a_this->msg) { + g_free (a_this->msg); + } + + a_this->msg = g_strdup (a_msg); +} + +/** + *Sets the error status. + *@param a_this the current instance of #CRParserError. + *@param a_status the new error status. + * + */ +static void +cr_parser_error_set_status (CRParserError * a_this, enum CRStatus a_status) +{ + g_return_if_fail (a_this); + + a_this->status = a_status; +} + +/** + *Sets the position of the parser error. + *@param a_this the current instance of #CRParserError. + *@param a_line the line number. + *@param a_column the column number. + *@param a_byte_num the byte number. + */ +static void +cr_parser_error_set_pos (CRParserError * a_this, + glong a_line, glong a_column, glong a_byte_num) +{ + g_return_if_fail (a_this); + + a_this->line = a_line; + a_this->column = a_column; + a_this->byte_num = a_byte_num; +} + +static void +cr_parser_error_dump (CRParserError * a_this) +{ + g_return_if_fail (a_this); + + g_printerr ("parsing error: %ld:%ld:", a_this->line, a_this->column); + + g_printerr ("%s\n", a_this->msg); +} + +/** + *The destructor of #CRParserError. + *@param a_this the current instance of #CRParserError. + */ +static void +cr_parser_error_destroy (CRParserError * a_this) +{ + g_return_if_fail (a_this); + + if (a_this->msg) { + g_free (a_this->msg); + a_this->msg = NULL; + } + + g_free (a_this); +} + +/** + *Pushes an error on the parser error stack. + *@param a_this the current instance of #CRParser. + *@param a_msg the error message. + *@param a_status the error status. + *@return CR_OK upon successful completion, an error code otherwise. + */ +static enum CRStatus +cr_parser_push_error (CRParser * a_this, + const guchar * a_msg, enum CRStatus a_status) +{ + enum CRStatus status = CR_OK; + + CRParserError *error = NULL; + CRInputPos pos; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_msg, CR_BAD_PARAM_ERROR); + + error = cr_parser_error_new (a_msg, a_status); + + g_return_val_if_fail (error, CR_ERROR); + + RECORD_INITIAL_POS (a_this, &pos); + + cr_parser_error_set_pos + (error, pos.line, pos.col, pos.next_byte_index - 1); + + PRIVATE (a_this)->err_stack = + g_list_prepend (PRIVATE (a_this)->err_stack, error); + + if (PRIVATE (a_this)->err_stack == NULL) + goto error; + + return CR_OK; + + error: + + if (error) { + cr_parser_error_destroy (error); + error = NULL; + } + + return status; +} + +/** + *Dumps the error stack using g_printerr. + *@param a_this the current instance of #CRParser. + *@param a_clear_errs whether to clear the error stack + *after the dump or not. + *@return CR_OK upon successfull completion, an error code + *otherwise. + */ +static enum CRStatus +cr_parser_dump_err_stack (CRParser * a_this, gboolean a_clear_errs) +{ + GList *cur = NULL; + + g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); + + if (PRIVATE (a_this)->err_stack == NULL) + return CR_OK; + + for (cur = PRIVATE (a_this)->err_stack; cur; cur = cur->next) { + cr_parser_error_dump ((CRParserError *) cur->data); + } + + if (a_clear_errs == TRUE) { + cr_parser_clear_errors (a_this); + } + + return CR_OK; +} + +/** + *Clears all the errors contained in the parser error stack. + *Frees all the errors, and the stack that contains'em. + *@param a_this the current instance of #CRParser. + */ +static enum CRStatus +cr_parser_clear_errors (CRParser * a_this) +{ + GList *cur = NULL; + + g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); + + for (cur = PRIVATE (a_this)->err_stack; cur; cur = cur->next) { + if (cur->data) { + cr_parser_error_destroy ((CRParserError *) + cur->data); + } + } + + if (PRIVATE (a_this)->err_stack) { + g_list_free (PRIVATE (a_this)->err_stack); + PRIVATE (a_this)->err_stack = NULL; + } + + return CR_OK; +} + +/** + *Same as cr_parser_try_to_skip_spaces() but this one skips + *spaces and comments. + * + *@param a_this the current instance of #CRParser. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_parser_try_to_skip_spaces_and_comments (CRParser * a_this) +{ + enum CRStatus status = CR_ERROR; + CRToken *token = NULL; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && PRIVATE (a_this)->tknzr, CR_BAD_PARAM_ERROR); + do { + if (token) { + cr_token_destroy (token); + token = NULL; + } + + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, + &token); + if (status != CR_OK) + goto error; + } + while ((token != NULL) + && (token->type == COMMENT_TK || token->type == S_TK)); + + cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); + + return status; + + error: + + if (token) { + cr_token_destroy (token); + token = NULL; + } + + return status; +} + +/*************************************** + *End of Parser input handling routines + ***************************************/ + + +/************************************* + *Non trivial terminal productions + *parsing routines + *************************************/ + +/** + *Parses a css stylesheet following the core css grammar. + *This is mainly done for test purposes. + *During the parsing, no callback is called. This is just + *to validate that the stylesheet is well formed according to the + *css core syntax. + *stylesheet : [ CDO | CDC | S | statement ]*; + *@param a_this the current instance of #CRParser. + *@return CR_OK upon successful completion, an error code otherwise. + */ +static enum CRStatus +cr_parser_parse_stylesheet_core (CRParser * a_this) +{ + CRToken *token = NULL; + CRInputPos init_pos; + enum CRStatus status = CR_ERROR; + + g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + continue_parsing: + + if (token) { + cr_token_destroy (token); + token = NULL; + } + + cr_parser_try_to_skip_spaces_and_comments (a_this); + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); + if (status == CR_END_OF_INPUT_ERROR) { + status = CR_OK; + goto done; + } else if (status != CR_OK) { + goto error; + } + + switch (token->type) { + + case CDO_TK: + case CDC_TK: + goto continue_parsing; + break; + default: + status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, + token); + CHECK_PARSING_STATUS (status, TRUE); + token = NULL; + status = cr_parser_parse_statement_core (a_this); + cr_parser_clear_errors (a_this); + if (status == CR_OK) { + goto continue_parsing; + } else if (status == CR_END_OF_INPUT_ERROR) { + goto done; + } else { + goto error; + } + } + + done: + if (token) { + cr_token_destroy (token); + token = NULL; + } + + cr_parser_clear_errors (a_this); + return CR_OK; + + error: + cr_parser_push_error + (a_this, "could not recognize next production", CR_ERROR); + + cr_parser_dump_err_stack (a_this, TRUE); + + if (token) { + cr_token_destroy (token); + token = NULL; + } + + cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); + + return status; +} + +/** + *Parses an at-rule as defined by the css core grammar + *in chapter 4.1 in the css2 spec. + *at-rule : ATKEYWORD S* any* [ block | ';' S* ]; + *@param a_this the current instance of #CRParser. + *@return CR_OK upon successfull completion, an error code + *otherwise. + */ +static enum CRStatus +cr_parser_parse_atrule_core (CRParser * a_this) +{ + CRToken *token = NULL; + CRInputPos init_pos; + enum CRStatus status = CR_ERROR; + + g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, + &token); + ENSURE_PARSING_COND (status == CR_OK + && token + && + (token->type == ATKEYWORD_TK + || token->type == IMPORT_SYM_TK + || token->type == PAGE_SYM_TK + || token->type == MEDIA_SYM_TK + || token->type == FONT_FACE_SYM_TK + || token->type == CHARSET_SYM_TK)); + + cr_token_destroy (token); + token = NULL; + + cr_parser_try_to_skip_spaces_and_comments (a_this); + + do { + status = cr_parser_parse_any_core (a_this); + } while (status == CR_OK); + + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, + &token); + ENSURE_PARSING_COND (status == CR_OK && token); + + if (token->type == CBO_TK) { + cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, + token); + token = NULL; + status = cr_parser_parse_block_core (a_this); + CHECK_PARSING_STATUS (status, + FALSE); + goto done; + } else if (token->type == SEMICOLON_TK) { + goto done; + } else { + status = CR_PARSING_ERROR ; + goto error; + } + + done: + if (token) { + cr_token_destroy (token); + token = NULL; + } + return CR_OK; + + error: + if (token) { + cr_token_destroy (token); + token = NULL; + } + cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, + &init_pos); + return status; +} + +/** + *Parses a ruleset as defined by the css core grammar in chapter + *4.1 of the css2 spec. + *ruleset ::= selector? '{' S* declaration? [ ';' S* declaration? ]* '}' S*; + *@param a_this the current instance of #CRParser. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +static enum CRStatus +cr_parser_parse_ruleset_core (CRParser * a_this) +{ + CRToken *token = NULL; + CRInputPos init_pos; + enum CRStatus status = CR_ERROR; + + g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); + RECORD_INITIAL_POS (a_this, &init_pos); + + status = cr_parser_parse_selector_core (a_this); + + ENSURE_PARSING_COND (status == CR_OK + || status == CR_PARSING_ERROR + || status == CR_END_OF_INPUT_ERROR); + + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); + ENSURE_PARSING_COND (status == CR_OK && token + && token->type == CBO_TK); + cr_token_destroy (token); + token = NULL; + + cr_parser_try_to_skip_spaces_and_comments (a_this); + status = cr_parser_parse_declaration_core (a_this); + + parse_declaration_list: + if (token) { + cr_token_destroy (token); + token = NULL; + } + + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); + ENSURE_PARSING_COND (status == CR_OK && token); + if (token->type == CBC_TK) { + goto done; + } + + ENSURE_PARSING_COND (status == CR_OK + && token && token->type == SEMICOLON_TK); + + cr_token_destroy (token); + token = NULL; + cr_parser_try_to_skip_spaces_and_comments (a_this); + status = cr_parser_parse_declaration_core (a_this); + cr_parser_clear_errors (a_this); + ENSURE_PARSING_COND (status == CR_OK || status == CR_PARSING_ERROR); + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); + ENSURE_PARSING_COND (status == CR_OK && token); + if (token->type == CBC_TK) { + cr_token_destroy (token); + token = NULL; + cr_parser_try_to_skip_spaces_and_comments (a_this); + goto done; + } else { + status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, + token); + token = NULL; + goto parse_declaration_list; + } + + done: + if (token) { + cr_token_destroy (token); + token = NULL; + } + + if (status == CR_OK) { + return CR_OK; + } + + error: + if (token) { + cr_token_destroy (token); + token = NULL; + } + + cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); + + return status; +} + +/** + *Parses a "selector" as specified by the css core + *grammar. + *selector : any+; + *@param a_this the current instance of #CRParser. + *@return CR_OK upon successfull completion, an error code + *otherwise. + */ +static enum CRStatus +cr_parser_parse_selector_core (CRParser * a_this) +{ + CRToken *token = NULL; + CRInputPos init_pos; + enum CRStatus status = CR_ERROR; + + g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + status = cr_parser_parse_any_core (a_this); + CHECK_PARSING_STATUS (status, FALSE); + + do { + status = cr_parser_parse_any_core (a_this); + + } while (status == CR_OK); + + return CR_OK; + + error: + if (token) { + cr_token_destroy (token); + token = NULL; + } + + cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); + + return status; +} + +/** + *Parses a "block" as defined in the css core grammar + *in chapter 4.1 of the css2 spec. + *block ::= '{' S* [ any | block | ATKEYWORD S* | ';' ]* '}' S*; + *@param a_this the current instance of #CRParser. + *FIXME: code this function. + */ +static enum CRStatus +cr_parser_parse_block_core (CRParser * a_this) +{ + CRToken *token = NULL; + CRInputPos init_pos; + enum CRStatus status = CR_ERROR; + + g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); + ENSURE_PARSING_COND (status == CR_OK && token + && token->type == CBO_TK); + + parse_block_content: + + if (token) { + cr_token_destroy (token); + token = NULL; + } + + cr_parser_try_to_skip_spaces_and_comments (a_this); + + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); + ENSURE_PARSING_COND (status == CR_OK && token); + + if (token->type == CBC_TK) { + cr_parser_try_to_skip_spaces_and_comments (a_this); + goto done; + } else if (token->type == SEMICOLON_TK) { + goto parse_block_content; + } else if (token->type == ATKEYWORD_TK) { + cr_parser_try_to_skip_spaces_and_comments (a_this); + goto parse_block_content; + } else if (token->type == CBO_TK) { + cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); + token = NULL; + status = cr_parser_parse_block_core (a_this); + CHECK_PARSING_STATUS (status, FALSE); + goto parse_block_content; + } else { + cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); + token = NULL; + status = cr_parser_parse_any_core (a_this); + CHECK_PARSING_STATUS (status, FALSE); + goto parse_block_content; + } + + done: + if (token) { + cr_token_destroy (token); + token = NULL; + } + + if (status == CR_OK) + return CR_OK; + + error: + if (token) { + cr_token_destroy (token); + token = NULL; + } + + cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); + + return status; +} + +static enum CRStatus +cr_parser_parse_declaration_core (CRParser * a_this) +{ + CRToken *token = NULL; + CRInputPos init_pos; + enum CRStatus status = CR_ERROR; + CRString *prop = NULL; + + g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + status = cr_parser_parse_property (a_this, &prop); + CHECK_PARSING_STATUS (status, FALSE); + cr_parser_clear_errors (a_this); + ENSURE_PARSING_COND (status == CR_OK && prop); + cr_string_destroy (prop); + prop = NULL; + + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); + ENSURE_PARSING_COND (status == CR_OK + && token + && token->type == DELIM_TK + && token->u.unichar == ':'); + cr_token_destroy (token); + token = NULL; + cr_parser_try_to_skip_spaces_and_comments (a_this); + status = cr_parser_parse_value_core (a_this); + CHECK_PARSING_STATUS (status, FALSE); + + return CR_OK; + + error: + + if (prop) { + cr_string_destroy (prop); + prop = NULL; + } + + if (token) { + cr_token_destroy (token); + token = NULL; + } + + cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); + + return status; +} + +/** + *Parses a "value" production as defined by the css core grammar + *in chapter 4.1. + *value ::= [ any | block | ATKEYWORD S* ]+; + *@param a_this the current instance of #CRParser. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +static enum CRStatus +cr_parser_parse_value_core (CRParser * a_this) +{ + CRToken *token = NULL; + CRInputPos init_pos; + enum CRStatus status = CR_ERROR; + glong ref = 0; + + g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); + RECORD_INITIAL_POS (a_this, &init_pos); + + continue_parsing: + + if (token) { + cr_token_destroy (token); + token = NULL; + } + + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); + ENSURE_PARSING_COND (status == CR_OK && token); + + switch (token->type) { + case CBO_TK: + status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, + token); + token = NULL; + status = cr_parser_parse_block_core (a_this); + CHECK_PARSING_STATUS (status, FALSE); + ref++; + goto continue_parsing; + + case ATKEYWORD_TK: + cr_parser_try_to_skip_spaces_and_comments (a_this); + ref++; + goto continue_parsing; + + default: + status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, + token); + token = NULL; + status = cr_parser_parse_any_core (a_this); + if (status == CR_OK) { + ref++; + goto continue_parsing; + } else if (status == CR_PARSING_ERROR) { + status = CR_OK; + goto done; + } else { + goto error; + } + } + + done: + if (token) { + cr_token_destroy (token); + token = NULL; + } + + if (status == CR_OK && ref) + return CR_OK; + error: + if (token) { + cr_token_destroy (token); + token = NULL; + } + + cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); + + return status; +} + +/** + *Parses an "any" as defined by the css core grammar in the + *css2 spec in chapter 4.1. + *any ::= [ IDENT | NUMBER | PERCENTAGE | DIMENSION | STRING + * | DELIM | URI | HASH | UNICODE-RANGE | INCLUDES + * | FUNCTION | DASHMATCH | '(' any* ')' | '[' any* ']' ] S*; + * + *@param a_this the current instance of #CRParser. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +static enum CRStatus +cr_parser_parse_any_core (CRParser * a_this) +{ + CRToken *token1 = NULL, + *token2 = NULL; + CRInputPos init_pos; + enum CRStatus status = CR_ERROR; + + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token1); + + ENSURE_PARSING_COND (status == CR_OK && token1); + + switch (token1->type) { + case IDENT_TK: + case NUMBER_TK: + case RGB_TK: + case PERCENTAGE_TK: + case DIMEN_TK: + case EMS_TK: + case EXS_TK: + case LENGTH_TK: + case ANGLE_TK: + case FREQ_TK: + case TIME_TK: + case STRING_TK: + case DELIM_TK: + case URI_TK: + case HASH_TK: + case UNICODERANGE_TK: + case INCLUDES_TK: + case DASHMATCH_TK: + case S_TK: + case COMMENT_TK: + case IMPORTANT_SYM_TK: + status = CR_OK; + break; + case FUNCTION_TK: + /* + *this case isn't specified by the spec but it + *does happen. So we have to handle it. + *We must consider function with parameters. + *We consider parameter as being an "any*" production. + */ + do { + status = cr_parser_parse_any_core (a_this); + } while (status == CR_OK); + + ENSURE_PARSING_COND (status == CR_PARSING_ERROR); + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, + &token2); + ENSURE_PARSING_COND (status == CR_OK + && token2 && token2->type == PC_TK); + break; + case PO_TK: + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, + &token2); + ENSURE_PARSING_COND (status == CR_OK && token2); + + if (token2->type == PC_TK) { + cr_token_destroy (token2); + token2 = NULL; + goto done; + } else { + status = cr_tknzr_unget_token + (PRIVATE (a_this)->tknzr, token2); + token2 = NULL; + } + + do { + status = cr_parser_parse_any_core (a_this); + } while (status == CR_OK); + + ENSURE_PARSING_COND (status == CR_PARSING_ERROR); + + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, + &token2); + ENSURE_PARSING_COND (status == CR_OK + && token2 && token2->type == PC_TK); + status = CR_OK; + break; + + case BO_TK: + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, + &token2); + ENSURE_PARSING_COND (status == CR_OK && token2); + + if (token2->type == BC_TK) { + cr_token_destroy (token2); + token2 = NULL; + goto done; + } else { + status = cr_tknzr_unget_token + (PRIVATE (a_this)->tknzr, token2); + token2 = NULL; + } + + do { + status = cr_parser_parse_any_core (a_this); + } while (status == CR_OK); + + ENSURE_PARSING_COND (status == CR_PARSING_ERROR); + + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, + &token2); + ENSURE_PARSING_COND (status == CR_OK + && token2 && token2->type == BC_TK); + status = CR_OK; + break; + default: + status = CR_PARSING_ERROR; + goto error; + } + + done: + if (token1) { + cr_token_destroy (token1); + token1 = NULL; + } + + if (token2) { + cr_token_destroy (token2); + token2 = NULL; + } + + return CR_OK; + + error: + + if (token1) { + cr_token_destroy (token1); + token1 = NULL; + } + + if (token2) { + cr_token_destroy (token2); + token2 = NULL; + } + + cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); + return status; +} + +/** + *Parses an attribute selector as defined in the css2 spec in + *appendix D.1: + *attrib ::= '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S* + * [ IDENT | STRING ] S* ]? ']' + * + *@param a_this the "this pointer" of the current instance of + *#CRParser . + *@param a_sel out parameter. The successfully parsed attribute selector. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +static enum CRStatus +cr_parser_parse_attribute_selector (CRParser * a_this, + CRAttrSel ** a_sel) +{ + enum CRStatus status = CR_OK; + CRInputPos init_pos; + CRToken *token = NULL; + CRAttrSel *result = NULL; + CRParsingLocation location = {0,0,0} ; + + g_return_val_if_fail (a_this && a_sel, CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); + ENSURE_PARSING_COND (status == CR_OK && token + && token->type == BO_TK); + cr_parsing_location_copy + (&location, &token->location) ; + cr_token_destroy (token); + token = NULL; + + cr_parser_try_to_skip_spaces_and_comments (a_this); + + result = cr_attr_sel_new (); + if (!result) { + cr_utils_trace_info ("result failed") ; + status = CR_OUT_OF_MEMORY_ERROR ; + goto error ; + } + cr_parsing_location_copy (&result->location, + &location) ; + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); + ENSURE_PARSING_COND (status == CR_OK + && token && token->type == IDENT_TK); + + result->name = token->u.str; + token->u.str = NULL; + cr_token_destroy (token); + token = NULL; + + cr_parser_try_to_skip_spaces_and_comments (a_this); + + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); + ENSURE_PARSING_COND (status == CR_OK && token); + + if (token->type == INCLUDES_TK) { + result->match_way = INCLUDES; + goto parse_right_part; + } else if (token->type == DASHMATCH_TK) { + result->match_way = DASHMATCH; + goto parse_right_part; + } else if (token->type == DELIM_TK && token->u.unichar == '=') { + result->match_way = EQUALS; + goto parse_right_part; + } else if (token->type == BC_TK) { + result->match_way = SET; + goto done; + } + + parse_right_part: + + if (token) { + cr_token_destroy (token); + token = NULL; + } + + cr_parser_try_to_skip_spaces_and_comments (a_this); + + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); + ENSURE_PARSING_COND (status == CR_OK && token); + + if (token->type == IDENT_TK) { + result->value = token->u.str; + token->u.str = NULL; + } else if (token->type == STRING_TK) { + result->value = token->u.str; + token->u.str = NULL; + } else { + status = CR_PARSING_ERROR; + goto error; + } + + if (token) { + cr_token_destroy (token); + token = NULL; + } + + cr_parser_try_to_skip_spaces_and_comments (a_this); + + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); + + ENSURE_PARSING_COND (status == CR_OK && token + && token->type == BC_TK); + done: + if (token) { + cr_token_destroy (token); + token = NULL; + } + + if (*a_sel) { + status = cr_attr_sel_append_attr_sel (*a_sel, result); + CHECK_PARSING_STATUS (status, FALSE); + } else { + *a_sel = result; + } + + cr_parser_clear_errors (a_this); + return CR_OK; + + error: + + if (result) { + cr_attr_sel_destroy (result); + result = NULL; + } + + if (token) { + cr_token_destroy (token); + token = NULL; + } + + cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); + + return status; +} + +/** + *Parses a "property" as specified by the css2 spec at [4.1.1]: + *property : IDENT S*; + * + *@param a_this the "this pointer" of the current instance of #CRParser. + *@param GString a_property out parameter. The parsed property without the + *trailing spaces. If *a_property is NULL, this function allocates a + *new instance of GString and set it content to the parsed property. + *If not, the property is just appended to a_property's previous content. + *In both cases, it is up to the caller to free a_property. + *@return CR_OK upon successfull completion, CR_PARSING_ERROR if the + *next construction was not a "property", or an error code. + */ +static enum CRStatus +cr_parser_parse_property (CRParser * a_this, + CRString ** a_property) +{ + enum CRStatus status = CR_OK; + CRInputPos init_pos; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && PRIVATE (a_this)->tknzr + && a_property, + CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + status = cr_parser_parse_ident (a_this, a_property); + CHECK_PARSING_STATUS (status, TRUE); + + cr_parser_try_to_skip_spaces_and_comments (a_this); + + cr_parser_clear_errors (a_this); + return CR_OK; + + error: + + cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); + + return status; +} + +/** + *Parses a "term" as defined in the css2 spec, appendix D.1: + *term ::= unary_operator? [NUMBER S* | PERCENTAGE S* | LENGTH S* | + *EMS S* | EXS S* | ANGLE S* | TIME S* | FREQ S* | function ] | + *STRING S* | IDENT S* | URI S* | RGB S* | UNICODERANGE S* | hexcolor + * + *TODO: handle parsing of 'RGB' + * + *@param a_term out parameter. The successfully parsed term. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_parser_parse_term (CRParser * a_this, CRTerm ** a_term) +{ + enum CRStatus status = CR_PARSING_ERROR; + CRInputPos init_pos; + CRTerm *result = NULL; + CRTerm *param = NULL; + CRToken *token = NULL; + CRString *func_name = NULL; + CRParsingLocation location = {0,0,0} ; + + g_return_val_if_fail (a_this && a_term, CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + result = cr_term_new (); + + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, + &token); + if (status != CR_OK || !token) + goto error; + + cr_parsing_location_copy (&location, &token->location) ; + if (token->type == DELIM_TK && token->u.unichar == '+') { + result->unary_op = PLUS_UOP; + cr_token_destroy (token) ; + token = NULL ; + cr_parser_try_to_skip_spaces_and_comments (a_this); + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, + &token); + if (status != CR_OK || !token) + goto error; + } else if (token->type == DELIM_TK && token->u.unichar == '-') { + result->unary_op = MINUS_UOP; + cr_token_destroy (token) ; + token = NULL ; + cr_parser_try_to_skip_spaces_and_comments (a_this); + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, + &token); + if (status != CR_OK || !token) + goto error; + } + + if (token->type == EMS_TK + || token->type == EXS_TK + || token->type == LENGTH_TK + || token->type == ANGLE_TK + || token->type == TIME_TK + || token->type == FREQ_TK + || token->type == PERCENTAGE_TK + || token->type == NUMBER_TK) { + status = cr_term_set_number (result, token->u.num); + CHECK_PARSING_STATUS (status, TRUE); + token->u.num = NULL; + status = CR_OK; + } else if (token && token->type == FUNCTION_TK) { + status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, + token); + token = NULL; + status = cr_parser_parse_function (a_this, &func_name, + ¶m); + + if (status == CR_OK) { + status = cr_term_set_function (result, + func_name, + param); + CHECK_PARSING_STATUS (status, TRUE); + } + } else if (token && token->type == STRING_TK) { + status = cr_term_set_string (result, + token->u.str); + CHECK_PARSING_STATUS (status, TRUE); + token->u.str = NULL; + } else if (token && token->type == IDENT_TK) { + status = cr_term_set_ident (result, token->u.str); + CHECK_PARSING_STATUS (status, TRUE); + token->u.str = NULL; + } else if (token && token->type == URI_TK) { + status = cr_term_set_uri (result, token->u.str); + CHECK_PARSING_STATUS (status, TRUE); + token->u.str = NULL; + } else if (token && token->type == RGB_TK) { + status = cr_term_set_rgb (result, token->u.rgb); + CHECK_PARSING_STATUS (status, TRUE); + token->u.rgb = NULL; + } else if (token && token->type == UNICODERANGE_TK) { + result->type = TERM_UNICODERANGE; + status = CR_PARSING_ERROR; + } else if (token && token->type == HASH_TK) { + status = cr_term_set_hash (result, token->u.str); + CHECK_PARSING_STATUS (status, TRUE); + token->u.str = NULL; + } else { + status = CR_PARSING_ERROR; + } + + if (status != CR_OK) { + goto error; + } + cr_parsing_location_copy (&result->location, + &location) ; + *a_term = cr_term_append_term (*a_term, result); + + result = NULL; + + cr_parser_try_to_skip_spaces_and_comments (a_this); + + if (token) { + cr_token_destroy (token); + token = NULL; + } + + cr_parser_clear_errors (a_this); + return CR_OK; + + error: + + if (result) { + cr_term_destroy (result); + result = NULL; + } + + if (token) { + cr_token_destroy (token); + token = NULL; + } + + if (param) { + cr_term_destroy (param); + param = NULL; + } + + if (func_name) { + cr_string_destroy (func_name); + func_name = NULL; + } + + cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); + + return status; +} + +/** + *Parses a "simple_selector" as defined by the css2 spec in appendix D.1 : + *element_name? [ HASH | class | attrib | pseudo ]* S* + *and where pseudo is: + *pseudo ::= ':' [ IDENT | FUNCTION S* IDENT S* ')' ] + * + *@Param a_this the "this pointer" of the current instance of #CRParser. + *@param a_sel out parameter. Is set to the successfully parsed simple + *selector. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +static enum CRStatus +cr_parser_parse_simple_selector (CRParser * a_this, CRSimpleSel ** a_sel) +{ + enum CRStatus status = CR_ERROR; + CRInputPos init_pos; + CRToken *token = NULL; + CRSimpleSel *sel = NULL; + CRAdditionalSel *add_sel_list = NULL; + gboolean found_sel = FALSE; + guint32 cur_char = 0; + + g_return_val_if_fail (a_this && a_sel, CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); + if (status != CR_OK) + goto error; + + sel = cr_simple_sel_new (); + ENSURE_PARSING_COND (sel); + + cr_parsing_location_copy + (&sel->location, + &token->location) ; + + if (token && token->type == DELIM_TK + && token->u.unichar == '*') { + sel->type_mask |= UNIVERSAL_SELECTOR; + sel->name = cr_string_new_from_string ("*"); + found_sel = TRUE; + } else if (token && token->type == IDENT_TK) { + sel->name = token->u.str; + sel->type_mask |= TYPE_SELECTOR; + token->u.str = NULL; + found_sel = TRUE; + } else { + status = cr_tknzr_unget_token + (PRIVATE (a_this)->tknzr, + token); + token = NULL; + } + + if (token) { + cr_token_destroy (token); + token = NULL; + } + + cr_parser_try_to_skip_spaces_and_comments (a_this); + + for (;;) { + if (token) { + cr_token_destroy (token); + token = NULL; + } + + status = cr_tknzr_get_next_token + (PRIVATE (a_this)->tknzr, + &token); + if (status != CR_OK) + goto error; + + if (token && token->type == HASH_TK) { + /*we parsed an attribute id */ + CRAdditionalSel *add_sel = NULL; + + add_sel = cr_additional_sel_new_with_type + (ID_ADD_SELECTOR); + + add_sel->content.id_name = token->u.str; + token->u.str = NULL; + + cr_parsing_location_copy + (&add_sel->location, + &token->location) ; + add_sel_list = + cr_additional_sel_append + (add_sel_list, add_sel); + found_sel = TRUE; + } else if (token && (token->type == DELIM_TK) + && (token->u.unichar == '.')) { + cr_token_destroy (token); + token = NULL; + + status = cr_tknzr_get_next_token + (PRIVATE (a_this)->tknzr, &token); + if (status != CR_OK) + goto error; + + if (token && token->type == IDENT_TK) { + CRAdditionalSel *add_sel = NULL; + + add_sel = cr_additional_sel_new_with_type + (CLASS_ADD_SELECTOR); + + add_sel->content.class_name = token->u.str; + token->u.str = NULL; + + add_sel_list = + cr_additional_sel_append + (add_sel_list, add_sel); + found_sel = TRUE; + + cr_parsing_location_copy + (&add_sel->location, + & token->location) ; + } else { + status = CR_PARSING_ERROR; + goto error; + } + } else if (token && token->type == BO_TK) { + CRAttrSel *attr_sel = NULL; + CRAdditionalSel *add_sel = NULL; + + status = cr_tknzr_unget_token + (PRIVATE (a_this)->tknzr, token); + if (status != CR_OK) + goto error; + token = NULL; + + status = cr_parser_parse_attribute_selector + (a_this, &attr_sel); + CHECK_PARSING_STATUS (status, FALSE); + + add_sel = cr_additional_sel_new_with_type + (ATTRIBUTE_ADD_SELECTOR); + + ENSURE_PARSING_COND (add_sel != NULL); + + add_sel->content.attr_sel = attr_sel; + + add_sel_list = + cr_additional_sel_append + (add_sel_list, add_sel); + found_sel = TRUE; + cr_parsing_location_copy + (&add_sel->location, + &attr_sel->location) ; + } else if (token && (token->type == DELIM_TK) + && (token->u.unichar == ':')) { + CRPseudo *pseudo = NULL; + + /*try to parse a pseudo */ + + if (token) { + cr_token_destroy (token); + token = NULL; + } + + pseudo = cr_pseudo_new (); + + status = cr_tknzr_get_next_token + (PRIVATE (a_this)->tknzr, &token); + ENSURE_PARSING_COND (status == CR_OK && token); + + cr_parsing_location_copy + (&pseudo->location, + &token->location) ; + + if (token->type == IDENT_TK) { + pseudo->type = IDENT_PSEUDO; + pseudo->name = token->u.str; + token->u.str = NULL; + found_sel = TRUE; + } else if (token->type == FUNCTION_TK) { + pseudo->name = token->u.str; + token->u.str = NULL; + cr_parser_try_to_skip_spaces_and_comments + (a_this); + status = cr_parser_parse_ident + (a_this, &pseudo->extra); + + ENSURE_PARSING_COND (status == CR_OK); + READ_NEXT_CHAR (a_this, &cur_char); + ENSURE_PARSING_COND (cur_char == ')'); + pseudo->type = FUNCTION_PSEUDO; + found_sel = TRUE; + } else { + status = CR_PARSING_ERROR; + goto error; + } + + if (status == CR_OK) { + CRAdditionalSel *add_sel = NULL; + + add_sel = cr_additional_sel_new_with_type + (PSEUDO_CLASS_ADD_SELECTOR); + + add_sel->content.pseudo = pseudo; + cr_parsing_location_copy + (&add_sel->location, + &pseudo->location) ; + add_sel_list = + cr_additional_sel_append + (add_sel_list, add_sel); + status = CR_OK; + } + } else { + status = cr_tknzr_unget_token + (PRIVATE (a_this)->tknzr, token); + token = NULL; + break; + } + } + + if (status == CR_OK && found_sel == TRUE) { + cr_parser_try_to_skip_spaces_and_comments (a_this); + + sel->add_sel = add_sel_list; + add_sel_list = NULL; + + if (*a_sel == NULL) { + *a_sel = sel; + } else { + cr_simple_sel_append_simple_sel (*a_sel, sel); + } + + sel = NULL; + + if (token) { + cr_token_destroy (token); + token = NULL; + } + + cr_parser_clear_errors (a_this); + return CR_OK; + } else { + status = CR_PARSING_ERROR; + } + + error: + + if (token) { + cr_token_destroy (token); + token = NULL; + } + + if (add_sel_list) { + cr_additional_sel_destroy (add_sel_list); + add_sel_list = NULL; + } + + if (sel) { + cr_simple_sel_destroy (sel); + sel = NULL; + } + + cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); + + return status; + +} + +/** + *Parses a "selector" as defined by the css2 spec in appendix D.1: + *selector ::= simple_selector [ combinator simple_selector ]* + * + *@param a_this the this pointer of the current instance of #CRParser. + *@param a_start a pointer to the + *first chararcter of the successfully parsed + *string. + *@param a_end a pointer to the last character of the successfully parsed + *string. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +static enum CRStatus +cr_parser_parse_simple_sels (CRParser * a_this, + CRSimpleSel ** a_sel) +{ + enum CRStatus status = CR_ERROR; + CRInputPos init_pos; + CRSimpleSel *sel = NULL; + guint32 cur_char = 0; + + g_return_val_if_fail (a_this + && PRIVATE (a_this) + && a_sel, + CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + status = cr_parser_parse_simple_selector (a_this, &sel); + CHECK_PARSING_STATUS (status, FALSE); + + *a_sel = cr_simple_sel_append_simple_sel (*a_sel, sel); + + for (;;) { + guint32 next_char = 0; + enum Combinator comb = 0; + + sel = NULL; + + PEEK_NEXT_CHAR (a_this, &next_char); + + if (next_char == '+') { + READ_NEXT_CHAR (a_this, &cur_char); + comb = COMB_PLUS; + cr_parser_try_to_skip_spaces_and_comments (a_this); + } else if (next_char == '>') { + READ_NEXT_CHAR (a_this, &cur_char); + comb = COMB_GT; + cr_parser_try_to_skip_spaces_and_comments (a_this); + } else { + comb = COMB_WS; + } + + status = cr_parser_parse_simple_selector (a_this, &sel); + if (status != CR_OK) + break; + + if (comb && sel) { + sel->combinator = comb; + comb = 0; + } + if (sel) { + *a_sel = cr_simple_sel_append_simple_sel (*a_sel, + sel) ; + } + } + cr_parser_clear_errors (a_this); + return CR_OK; + + error: + + cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); + + return status; +} + +/** + *Parses a comma separated list of selectors. + *@param a_this the current instance of #CRParser. + *@param a_selector the parsed list of comma separated + *selectors. + *@return CR_OK upon successful completion, an error + *code otherwise. + */ +static enum CRStatus +cr_parser_parse_selector (CRParser * a_this, + CRSelector ** a_selector) +{ + enum CRStatus status = CR_OK; + CRInputPos init_pos; + guint32 cur_char = 0, + next_char = 0; + CRSimpleSel *simple_sels = NULL; + CRSelector *selector = NULL; + + g_return_val_if_fail (a_this && a_selector, CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + status = cr_parser_parse_simple_sels (a_this, &simple_sels); + CHECK_PARSING_STATUS (status, FALSE); + + if (simple_sels) { + selector = cr_selector_append_simple_sel + (selector, simple_sels); + if (selector) { + cr_parsing_location_copy + (&selector->location, + &simple_sels->location) ; + } + simple_sels = NULL; + } else { + status = CR_PARSING_ERROR ; + goto error ; + } + + status = cr_tknzr_peek_char (PRIVATE (a_this)->tknzr, + &next_char); + if (status != CR_OK) { + if (status == CR_END_OF_INPUT_ERROR) { + status = CR_OK; + goto okay; + } else { + goto error; + } + } + + if (next_char == ',') { + for (;;) { + simple_sels = NULL; + + status = cr_tknzr_peek_char (PRIVATE (a_this)->tknzr, + &next_char); + if (status != CR_OK) { + if (status == CR_END_OF_INPUT_ERROR) { + status = CR_OK; + break; + } else { + goto error; + } + } + + if (next_char != ',') + break; + + /*consume the ',' char */ + READ_NEXT_CHAR (a_this, &cur_char); + + cr_parser_try_to_skip_spaces_and_comments (a_this); + + status = cr_parser_parse_simple_sels + (a_this, &simple_sels); + + CHECK_PARSING_STATUS (status, FALSE); + + if (simple_sels) { + selector = + cr_selector_append_simple_sel + (selector, simple_sels); + + simple_sels = NULL; + } + } + } + + okay: + cr_parser_try_to_skip_spaces_and_comments (a_this); + + if (!*a_selector) { + *a_selector = selector; + } else { + *a_selector = cr_selector_append (*a_selector, selector); + } + + selector = NULL; + return CR_OK; + + error: + + if (simple_sels) { + cr_simple_sel_destroy (simple_sels); + simple_sels = NULL; + } + + if (selector) { + cr_selector_unref (selector); + selector = NULL; + } + + cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); + + return status; +} + +/** + *Parses a "function" as defined in css spec at appendix D.1: + *function ::= FUNCTION S* expr ')' S* + *FUNCTION ::= ident'(' + * + *@param a_this the "this pointer" of the current instance of + *#CRParser. + * + *@param a_func_name out parameter. The parsed function name + *@param a_expr out parameter. The successfully parsed term. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +static enum CRStatus +cr_parser_parse_function (CRParser * a_this, + CRString ** a_func_name, + CRTerm ** a_expr) +{ + CRInputPos init_pos; + enum CRStatus status = CR_OK; + CRToken *token = NULL; + CRTerm *expr = NULL; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_func_name, + CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); + if (status != CR_OK) + goto error; + + if (token && token->type == FUNCTION_TK) { + *a_func_name = token->u.str; + token->u.str = NULL; + } else { + status = CR_PARSING_ERROR; + goto error; + } + cr_token_destroy (token); + token = NULL; + + cr_parser_try_to_skip_spaces_and_comments (a_this) ; + + status = cr_parser_parse_expr (a_this, &expr); + + CHECK_PARSING_STATUS (status, FALSE); + + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); + if (status != CR_OK) + goto error; + + ENSURE_PARSING_COND (token && token->type == PC_TK); + + cr_token_destroy (token); + token = NULL; + + if (expr) { + *a_expr = cr_term_append_term (*a_expr, expr); + expr = NULL; + } + + cr_parser_clear_errors (a_this); + return CR_OK; + + error: + + if (*a_func_name) { + cr_string_destroy (*a_func_name); + *a_func_name = NULL; + } + + if (expr) { + cr_term_destroy (expr); + expr = NULL; + } + + if (token) { + cr_token_destroy (token); + + } + + cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); + + return status; +} + +/** + *Parses an uri as defined by the css spec [4.1.1]: + * URI ::= url\({w}{string}{w}\) + * |url\({w}([!#$%&*-~]|{nonascii}|{escape})*{w}\) + * + *@param a_this the current instance of #CRParser. + *@param a_str the successfully parsed url. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +static enum CRStatus +cr_parser_parse_uri (CRParser * a_this, CRString ** a_str) +{ + + enum CRStatus status = CR_PARSING_ERROR; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && PRIVATE (a_this)->tknzr, CR_BAD_PARAM_ERROR); + + status = cr_tknzr_parse_token (PRIVATE (a_this)->tknzr, + URI_TK, NO_ET, a_str, NULL); + return status; +} + +/** + *Parses a string type as defined in css spec [4.1.1]: + * + *string ::= {string1}|{string2} + *string1 ::= \"([\t !#$%&(-~]|\\{nl}|\'|{nonascii}|{escape})*\" + *string2 ::= \'([\t !#$%&(-~]|\\{nl}|\"|{nonascii}|{escape})*\' + * + *@param a_this the current instance of #CRParser. + *@param a_start out parameter. Upon successfull completion, + *points to the beginning of the string, points to an undefined value + *otherwise. + *@param a_end out parameter. Upon successfull completion, points to + *the beginning of the string, points to an undefined value otherwise. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +static enum CRStatus +cr_parser_parse_string (CRParser * a_this, CRString ** a_str) +{ + enum CRStatus status = CR_OK; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && PRIVATE (a_this)->tknzr + && a_str, CR_BAD_PARAM_ERROR); + + status = cr_tknzr_parse_token (PRIVATE (a_this)->tknzr, + STRING_TK, NO_ET, a_str, NULL); + return status; +} + +/** + *Parses an "ident" as defined in css spec [4.1.1]: + *ident ::= {nmstart}{nmchar}* + * + *@param a_this the currens instance of #CRParser. + * + *@param a_str a pointer to parsed ident. If *a_str is NULL, + *this function allocates a new instance of #CRString. If not, + *the function just appends the parsed string to the one passed. + *In both cases it is up to the caller to free *a_str. + * + *@return CR_OK upon successfull completion, an error code + *otherwise. + */ +static enum CRStatus +cr_parser_parse_ident (CRParser * a_this, CRString ** a_str) +{ + enum CRStatus status = CR_OK; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && PRIVATE (a_this)->tknzr + && a_str, CR_BAD_PARAM_ERROR); + + status = cr_tknzr_parse_token (PRIVATE (a_this)->tknzr, + IDENT_TK, NO_ET, a_str, NULL); + return status; +} + +/** + *the next rule is ignored as well. This seems to be a bug + *Parses a stylesheet as defined in the css2 spec in appendix D.1: + *stylesheet ::= [ CHARSET_SYM S* STRING S* ';' ]? + * [S|CDO|CDC]* [ import [S|CDO|CDC]* ]* + * [ [ ruleset | media | page | font_face ] [S|CDO|CDC]* ]* + * + *TODO: Finish the code of this function. Think about splitting it into + *smaller functions. + * + *@param a_this the "this pointer" of the current instance of #CRParser. + *@param a_start out parameter. A pointer to the first character of + *the successfully parsed string. + *@param a_end out parameter. A pointer to the first character of + *the successfully parsed string. + * + *@return CR_OK upon successfull completion, an error code otherwise. + */ +static enum CRStatus +cr_parser_parse_stylesheet (CRParser * a_this) +{ + enum CRStatus status = CR_OK; + CRInputPos init_pos; + CRToken *token = NULL; + CRString *charset = NULL; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && PRIVATE (a_this)->tknzr, CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + PRIVATE (a_this)->state = READY_STATE; + + if (PRIVATE (a_this)->sac_handler + && PRIVATE (a_this)->sac_handler->start_document) { + PRIVATE (a_this)->sac_handler->start_document + (PRIVATE (a_this)->sac_handler); + } + + parse_charset: + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); + + if (status == CR_END_OF_INPUT_ERROR) + goto done; + CHECK_PARSING_STATUS (status, TRUE); + + if (token && token->type == CHARSET_SYM_TK) { + CRParsingLocation location = {0,0,0} ; + status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, + token); + CHECK_PARSING_STATUS (status, TRUE); + token = NULL; + + status = cr_parser_parse_charset (a_this, + &charset, + &location); + + if (status == CR_OK && charset) { + if (PRIVATE (a_this)->sac_handler + && PRIVATE (a_this)->sac_handler->charset) { + PRIVATE (a_this)->sac_handler->charset + (PRIVATE (a_this)->sac_handler, + charset, &location); + } + } else if (status != CR_END_OF_INPUT_ERROR) { + status = cr_parser_parse_atrule_core (a_this); + CHECK_PARSING_STATUS (status, FALSE); + } + + if (charset) { + cr_string_destroy (charset); + charset = NULL; + } + } else if (token + && (token->type == S_TK + || token->type == COMMENT_TK)) { + status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, + token); + token = NULL; + CHECK_PARSING_STATUS (status, TRUE); + + cr_parser_try_to_skip_spaces_and_comments (a_this); + goto parse_charset ; + } else if (token) { + status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, + token); + token = NULL; + CHECK_PARSING_STATUS (status, TRUE); + } + +/* parse_imports:*/ + do { + if (token) { + cr_token_destroy (token); + token = NULL; + } + cr_parser_try_to_skip_spaces_and_comments (a_this) ; + status = cr_tknzr_get_next_token + (PRIVATE (a_this)->tknzr, &token); + + if (status == CR_END_OF_INPUT_ERROR) + goto done; + CHECK_PARSING_STATUS (status, TRUE); + } while (token + && (token->type == S_TK + || token->type == CDO_TK || token->type == CDC_TK)); + + if (token) { + status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, + token); + token = NULL; + } + + for (;;) { + status = cr_tknzr_get_next_token + (PRIVATE (a_this)->tknzr, &token); + if (status == CR_END_OF_INPUT_ERROR) + goto done; + CHECK_PARSING_STATUS (status, TRUE); + + if (token && token->type == IMPORT_SYM_TK) { + GList *media_list = NULL; + CRString *import_string = NULL; + CRParsingLocation location = {0,0,0} ; + + status = cr_tknzr_unget_token + (PRIVATE (a_this)->tknzr, token); + token = NULL; + CHECK_PARSING_STATUS (status, TRUE); + + status = cr_parser_parse_import (a_this, + &media_list, + &import_string, + &location); + if (status == CR_OK) { + if (import_string + && PRIVATE (a_this)->sac_handler + && PRIVATE (a_this)->sac_handler->import_style) { + PRIVATE (a_this)->sac_handler->import_style + (PRIVATE(a_this)->sac_handler, + media_list, + import_string, + NULL, &location) ; + + if ((PRIVATE (a_this)->sac_handler->resolve_import == TRUE)) { + /* + *TODO: resolve the + *import rule. + */ + } + + if ((PRIVATE (a_this)->sac_handler->import_style_result)) { + PRIVATE (a_this)->sac_handler->import_style_result + (PRIVATE (a_this)->sac_handler, + media_list, import_string, + NULL, NULL); + } + } + } else if (status != CR_END_OF_INPUT_ERROR) { + if (PRIVATE (a_this)->sac_handler + && PRIVATE (a_this)->sac_handler->error) { + PRIVATE (a_this)->sac_handler->error + (PRIVATE (a_this)->sac_handler); + } + status = cr_parser_parse_atrule_core (a_this); + CHECK_PARSING_STATUS (status, TRUE) ; + } else { + goto error ; + } + + /* + *then, after calling the appropriate + *SAC handler, free + *the media_list and import_string. + */ + if (media_list) { + GList *cur = NULL; + + /*free the medium list */ + for (cur = media_list; cur; cur = cur->next) { + if (cur->data) { + cr_string_destroy (cur->data); + } + } + + g_list_free (media_list); + media_list = NULL; + } + + if (import_string) { + cr_string_destroy (import_string); + import_string = NULL; + } + + cr_parser_try_to_skip_spaces_and_comments (a_this); + } else if (token + && (token->type == S_TK + || token->type == CDO_TK + || token->type == CDC_TK)) { + status = cr_tknzr_unget_token + (PRIVATE (a_this)->tknzr, token); + token = NULL; + + do { + if (token) { + cr_token_destroy (token); + token = NULL; + } + + status = cr_tknzr_get_next_token + (PRIVATE (a_this)->tknzr, &token); + + if (status == CR_END_OF_INPUT_ERROR) + goto done; + CHECK_PARSING_STATUS (status, TRUE); + } while (token + && (token->type == S_TK + || token->type == CDO_TK + || token->type == CDC_TK)); + } else { + if (token) { + status = cr_tknzr_unget_token + (PRIVATE (a_this)->tknzr, token); + token = NULL; + } + goto parse_ruleset_and_others; + } + } + + parse_ruleset_and_others: + + cr_parser_try_to_skip_spaces_and_comments (a_this); + + for (;;) { + status = cr_tknzr_get_next_token + (PRIVATE (a_this)->tknzr, &token); + if (status == CR_END_OF_INPUT_ERROR) + goto done; + CHECK_PARSING_STATUS (status, TRUE); + + if (token + && (token->type == S_TK + || token->type == CDO_TK || token->type == CDC_TK)) { + status = cr_tknzr_unget_token + (PRIVATE (a_this)->tknzr, token); + token = NULL; + + do { + if (token) { + cr_token_destroy (token); + token = NULL; + } + + cr_parser_try_to_skip_spaces_and_comments + (a_this); + status = cr_tknzr_get_next_token + (PRIVATE (a_this)->tknzr, &token); + } while (token + && (token->type == S_TK + || token->type == COMMENT_TK + || token->type == CDO_TK + || token->type == CDC_TK)); + if (token) { + cr_tknzr_unget_token + (PRIVATE (a_this)->tknzr, token); + token = NULL; + } + } else if (token + && (token->type == HASH_TK + || (token->type == DELIM_TK + && token->u.unichar == '.') + || (token->type == DELIM_TK + && token->u.unichar == ':') + || (token->type == DELIM_TK + && token->u.unichar == '*') + || (token->type == BO_TK) + || token->type == IDENT_TK)) { + /* + *Try to parse a CSS2 ruleset. + *if the parsing fails, try to parse + *a css core ruleset. + */ + status = cr_tknzr_unget_token + (PRIVATE (a_this)->tknzr, token); + CHECK_PARSING_STATUS (status, TRUE); + token = NULL; + + status = cr_parser_parse_ruleset (a_this); + + if (status == CR_OK) { + continue; + } else { + if (PRIVATE (a_this)->sac_handler + && PRIVATE (a_this)->sac_handler->error) { + PRIVATE (a_this)->sac_handler-> + error + (PRIVATE (a_this)-> + sac_handler); + } + + status = cr_parser_parse_ruleset_core + (a_this); + + if (status == CR_OK) { + continue; + } else { + break; + } + } + } else if (token && token->type == MEDIA_SYM_TK) { + status = cr_tknzr_unget_token + (PRIVATE (a_this)->tknzr, token); + CHECK_PARSING_STATUS (status, TRUE); + token = NULL; + + status = cr_parser_parse_media (a_this); + if (status == CR_OK) { + continue; + } else { + if (PRIVATE (a_this)->sac_handler + && PRIVATE (a_this)->sac_handler->error) { + PRIVATE (a_this)->sac_handler-> + error + (PRIVATE (a_this)-> + sac_handler); + } + + status = cr_parser_parse_atrule_core (a_this); + + if (status == CR_OK) { + continue; + } else { + break; + } + } + + } else if (token && token->type == PAGE_SYM_TK) { + status = cr_tknzr_unget_token + (PRIVATE (a_this)->tknzr, token); + CHECK_PARSING_STATUS (status, TRUE); + token = NULL; + status = cr_parser_parse_page (a_this); + + if (status == CR_OK) { + continue; + } else { + if (PRIVATE (a_this)->sac_handler + && PRIVATE (a_this)->sac_handler->error) { + PRIVATE (a_this)->sac_handler-> + error + (PRIVATE (a_this)-> + sac_handler); + } + + status = cr_parser_parse_atrule_core (a_this); + + if (status == CR_OK) { + continue; + } else { + break; + } + } + } else if (token && token->type == FONT_FACE_SYM_TK) { + status = cr_tknzr_unget_token + (PRIVATE (a_this)->tknzr, token); + CHECK_PARSING_STATUS (status, TRUE); + token = NULL; + status = cr_parser_parse_font_face (a_this); + + if (status == CR_OK) { + continue; + } else { + if (PRIVATE (a_this)->sac_handler + && PRIVATE (a_this)->sac_handler->error) { + PRIVATE (a_this)->sac_handler-> + error + (PRIVATE (a_this)-> + sac_handler); + } + + status = cr_parser_parse_atrule_core (a_this); + + if (status == CR_OK) { + continue; + } else { + break; + } + } + } else { + status = cr_tknzr_unget_token + (PRIVATE (a_this)->tknzr, token); + CHECK_PARSING_STATUS (status, TRUE); + token = NULL; + status = cr_parser_parse_statement_core (a_this); + + if (status == CR_OK) { + continue; + } else { + break; + } + } + } + + done: + if (token) { + cr_token_destroy (token); + token = NULL; + } + + if (status == CR_END_OF_INPUT_ERROR || status == CR_OK) { + + if (PRIVATE (a_this)->sac_handler + && PRIVATE (a_this)->sac_handler->end_document) { + PRIVATE (a_this)->sac_handler->end_document + (PRIVATE (a_this)->sac_handler); + } + + return CR_OK; + } + + cr_parser_push_error + (a_this, "could not recognize next production", CR_ERROR); + + if (PRIVATE (a_this)->sac_handler + && PRIVATE (a_this)->sac_handler->unrecoverable_error) { + PRIVATE (a_this)->sac_handler-> + unrecoverable_error (PRIVATE (a_this)->sac_handler); + } + + cr_parser_dump_err_stack (a_this, TRUE); + + return status; + + error: + + if (token) { + cr_token_destroy (token); + token = NULL; + } + + if (PRIVATE (a_this)->sac_handler + && PRIVATE (a_this)->sac_handler->unrecoverable_error) { + PRIVATE (a_this)->sac_handler-> + unrecoverable_error (PRIVATE (a_this)->sac_handler); + } + + cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); + + return status; +} + +/**************************************** + *Public CRParser Methods + ****************************************/ + +/** + *Creates a new parser to parse data + *coming the input stream given in parameter. + *@param a_input the input stream of the parser. + *Note that the newly created parser will ref + *a_input and unref it when parsing reaches the + *end of the input stream. + *@return the newly created instance of #CRParser, + *or NULL if an error occured. + */ +CRParser * +cr_parser_new (CRTknzr * a_tknzr) +{ + CRParser *result = NULL; + enum CRStatus status = CR_OK; + + result = g_malloc0 (sizeof (CRParser)); + + PRIVATE (result) = g_malloc0 (sizeof (CRParserPriv)); + + if (a_tknzr) { + status = cr_parser_set_tknzr (result, a_tknzr); + } + + g_return_val_if_fail (status == CR_OK, NULL); + + return result; +} + +/** + *Instanciates a new parser from a memory buffer. + *@param a_buf the buffer to parse. + *@param a_len the length of the data in the buffer. + *@param a_enc the encoding of the input buffer a_buf. + *@param a_free_buf if set to TRUE, a_buf will be freed + *during the destruction of the newly built instance + *of #CRParser. If set to FALSE, it is up to the caller to + *eventually free it. + *@return the newly built parser, or NULL if an error arises. + */ +CRParser * +cr_parser_new_from_buf (guchar * a_buf, + gulong a_len, + enum CREncoding a_enc, + gboolean a_free_buf) +{ + CRParser *result = NULL; + CRInput *input = NULL; + + g_return_val_if_fail (a_buf, NULL); + + input = cr_input_new_from_buf (a_buf, a_len, a_enc, a_free_buf); + g_return_val_if_fail (input, NULL); + + result = cr_parser_new_from_input (input); + if (!result) { + cr_input_destroy (input); + input = NULL; + return NULL; + } + return result; +} + +CRParser * +cr_parser_new_from_input (CRInput * a_input) +{ + CRParser *result = NULL; + CRTknzr *tokenizer = NULL; + + if (a_input) { + tokenizer = cr_tknzr_new (a_input); + g_return_val_if_fail (tokenizer, NULL); + } + + result = cr_parser_new (tokenizer); + g_return_val_if_fail (result, NULL); + + return result; +} + +CRParser * +cr_parser_new_from_file (const guchar * a_file_uri, enum CREncoding a_enc) +{ + CRParser *result = NULL; + CRTknzr *tokenizer = NULL; + + tokenizer = cr_tknzr_new_from_uri (a_file_uri, a_enc); + if (!tokenizer) { + cr_utils_trace_info ("Could not open input file"); + return NULL; + } + + result = cr_parser_new (tokenizer); + g_return_val_if_fail (result, NULL); + return result; +} + +/** + *Sets a SAC document handler to the parser. + *@param a_this the "this pointer" of the current instance of #CRParser. + *@param a_handler the handler to set. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_parser_set_sac_handler (CRParser * a_this, CRDocHandler * a_handler) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + if (PRIVATE (a_this)->sac_handler) { + cr_doc_handler_unref (PRIVATE (a_this)->sac_handler); + } + + PRIVATE (a_this)->sac_handler = a_handler; + cr_doc_handler_ref (a_handler); + + return CR_OK; +} + +/** + *Gets the SAC document handler. + *@param a_this the "this pointer" of the current instance of + *#CRParser. + *@param a_handler out parameter. The returned handler. + *@return CR_OK upon successfull completion, an error code + *otherwise. + */ +enum CRStatus +cr_parser_get_sac_handler (CRParser * a_this, CRDocHandler ** a_handler) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + *a_handler = PRIVATE (a_this)->sac_handler; + + return CR_OK; +} + +/** + *Sets the SAC handler associated to the current instance + *of #CRParser to the default SAC handler. + *@param a_this a pointer to the current instance of #CRParser. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_parser_set_default_sac_handler (CRParser * a_this) +{ + CRDocHandler *default_sac_handler = NULL; + enum CRStatus status = CR_ERROR; + + g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); + + default_sac_handler = cr_doc_handler_new (); + + cr_doc_handler_set_default_sac_handler (default_sac_handler); + + status = cr_parser_set_sac_handler (a_this, default_sac_handler); + + if (status != CR_OK) { + cr_doc_handler_destroy (default_sac_handler); + default_sac_handler = NULL; + } + + return status; +} + +enum CRStatus +cr_parser_set_use_core_grammar (CRParser * a_this, + gboolean a_use_core_grammar) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); + + PRIVATE (a_this)->use_core_grammar = a_use_core_grammar; + + return CR_OK; +} + +enum CRStatus +cr_parser_get_use_core_grammar (CRParser * a_this, + gboolean * a_use_core_grammar) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); + + *a_use_core_grammar = PRIVATE (a_this)->use_core_grammar; + + return CR_OK; +} + +/** + *Parses a the given in parameter. + *@param a_this a pointer to the current instance of #CRParser. + *@param a_file_uri the uri to the file to load. For the time being, + *only local files are supported. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_parser_parse_file (CRParser * a_this, + const guchar * a_file_uri, enum CREncoding a_enc) +{ + enum CRStatus status = CR_ERROR; + CRTknzr *tknzr = NULL; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_file_uri, CR_BAD_PARAM_ERROR); + + tknzr = cr_tknzr_new_from_uri (a_file_uri, a_enc); + + g_return_val_if_fail (tknzr != NULL, CR_ERROR); + + status = cr_parser_set_tknzr (a_this, tknzr); + g_return_val_if_fail (status == CR_OK, CR_ERROR); + + status = cr_parser_parse (a_this); + + return status; +} + +/** + *Parses an expression as defined by the css2 spec in appendix + *D.1: + *expr: term [ operator term ]* + */ +enum CRStatus +cr_parser_parse_expr (CRParser * a_this, CRTerm ** a_expr) +{ + enum CRStatus status = CR_ERROR; + CRInputPos init_pos; + CRTerm *expr = NULL, + *expr2 = NULL; + guchar next_byte = 0; + gulong nb_terms = 0; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_expr, CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + status = cr_parser_parse_term (a_this, &expr); + + CHECK_PARSING_STATUS (status, FALSE); + + for (;;) { + guchar operator = 0; + + status = cr_tknzr_peek_byte (PRIVATE (a_this)->tknzr, + 1, &next_byte); + if (status != CR_OK) { + if (status == CR_END_OF_INPUT_ERROR) { + /* + if (!nb_terms) + { + goto error ; + } + */ + status = CR_OK; + break; + } else { + goto error; + } + } + + if (next_byte == '/' || next_byte == ',') { + READ_NEXT_BYTE (a_this, &operator); + } + + cr_parser_try_to_skip_spaces_and_comments (a_this); + + status = cr_parser_parse_term (a_this, &expr2); + + if (status != CR_OK || expr2 == NULL) { + status = CR_OK; + break; + } + + switch (operator) { + case '/': + expr2->the_operator = DIVIDE; + break; + case ',': + expr2->the_operator = COMMA; + + default: + break; + } + + expr = cr_term_append_term (expr, expr2); + expr2 = NULL; + operator = 0; + nb_terms++; + } + + if (status == CR_OK) { + *a_expr = cr_term_append_term (*a_expr, expr); + expr = NULL; + + cr_parser_clear_errors (a_this); + return CR_OK; + } + + error: + + if (expr) { + cr_term_destroy (expr); + expr = NULL; + } + + if (expr2) { + cr_term_destroy (expr2); + expr2 = NULL; + } + + cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); + + return status; +} + +/** + *Parses a declaration priority as defined by + *the css2 grammar in appendix C: + *prio: IMPORTANT_SYM S* + *@param a_this the current instance of #CRParser. + *@param a_prio a string representing the priority. + *Today, only "!important" is returned as only this + *priority is defined by css2. + */ +enum CRStatus +cr_parser_parse_prio (CRParser * a_this, CRString ** a_prio) +{ + enum CRStatus status = CR_ERROR; + CRInputPos init_pos; + CRToken *token = NULL; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_prio + && *a_prio == NULL, CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); + if (status == CR_END_OF_INPUT_ERROR) { + goto error; + } + ENSURE_PARSING_COND (status == CR_OK + && token && token->type == IMPORTANT_SYM_TK); + + cr_parser_try_to_skip_spaces_and_comments (a_this); + *a_prio = cr_string_new_from_string ("!important"); + cr_token_destroy (token); + token = NULL; + return CR_OK; + + error: + if (token) { + cr_token_destroy (token); + token = NULL; + } + cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); + + return status; +} + +/** + *TODO: return the parsed priority, so that + *upper layers can take benefit from it. + *Parses a "declaration" as defined by the css2 spec in appendix D.1: + *declaration ::= [property ':' S* expr prio?]? + * + *@param a_this the "this pointer" of the current instance of #CRParser. + *@param a_property the successfully parsed property. The caller + * *must* free the returned pointer. + *@param a_expr the expression that represents the attribute value. + *The caller *must* free the returned pointer. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_parser_parse_declaration (CRParser * a_this, + CRString ** a_property, + CRTerm ** a_expr, gboolean * a_important) +{ + enum CRStatus status = CR_ERROR; + CRInputPos init_pos; + guint32 cur_char = 0; + CRTerm *expr = NULL; + CRString *prio = NULL; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_property && a_expr + && a_important, CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + status = cr_parser_parse_property (a_this, a_property); + + if (status == CR_END_OF_INPUT_ERROR) + goto error; + + CHECK_PARSING_STATUS_ERR + (a_this, status, FALSE, + "while parsing declaration: next property is malformed", + CR_SYNTAX_ERROR); + + READ_NEXT_CHAR (a_this, &cur_char); + + if (cur_char != ':') { + status = CR_PARSING_ERROR; + cr_parser_push_error + (a_this, + "while parsing declaration: this char must be ':'", + CR_SYNTAX_ERROR); + goto error; + } + + cr_parser_try_to_skip_spaces_and_comments (a_this); + + status = cr_parser_parse_expr (a_this, &expr); + + CHECK_PARSING_STATUS_ERR + (a_this, status, FALSE, + "while parsing declaration: next expression is malformed", + CR_SYNTAX_ERROR); + + cr_parser_try_to_skip_spaces_and_comments (a_this); + status = cr_parser_parse_prio (a_this, &prio); + if (prio) { + cr_string_destroy (prio); + prio = NULL; + *a_important = TRUE; + } else { + *a_important = FALSE; + } + if (*a_expr) { + cr_term_append_term (*a_expr, expr); + expr = NULL; + } else { + *a_expr = expr; + expr = NULL; + } + + cr_parser_clear_errors (a_this); + return CR_OK; + + error: + + if (expr) { + cr_term_destroy (expr); + expr = NULL; + } + + if (*a_property) { + cr_string_destroy (*a_property); + *a_property = NULL; + } + + cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); + + return status; +} + +/** + *Parses a statement as defined by the css core grammar in + *chapter 4.1 of the css2 spec. + *statement : ruleset | at-rule; + *@param a_this the current instance of #CRParser. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_parser_parse_statement_core (CRParser * a_this) +{ + CRToken *token = NULL; + CRInputPos init_pos; + enum CRStatus status = CR_ERROR; + + g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); + + ENSURE_PARSING_COND (status == CR_OK && token); + + switch (token->type) { + case ATKEYWORD_TK: + case IMPORT_SYM_TK: + case PAGE_SYM_TK: + case MEDIA_SYM_TK: + case FONT_FACE_SYM_TK: + case CHARSET_SYM_TK: + cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); + token = NULL; + status = cr_parser_parse_atrule_core (a_this); + CHECK_PARSING_STATUS (status, TRUE); + break; + + default: + cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); + token = NULL; + status = cr_parser_parse_ruleset_core (a_this); + cr_parser_clear_errors (a_this); + CHECK_PARSING_STATUS (status, TRUE); + } + + return CR_OK; + + error: + if (token) { + cr_token_destroy (token); + token = NULL; + } + + cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); + + return status; +} + +/** + *Parses a "ruleset" as defined in the css2 spec at appendix D.1. + *ruleset ::= selector [ ',' S* selector ]* + *'{' S* declaration? [ ';' S* declaration? ]* '}' S*; + * + *This methods calls the the SAC handler on the relevant SAC handler + *callbacks whenever it encounters some specific constructions. + *See the documentation of #CRDocHandler (the SAC handler) to know + *when which SAC handler is called. + *@param a_this the "this pointer" of the current instance of #CRParser. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_parser_parse_ruleset (CRParser * a_this) +{ + enum CRStatus status = CR_OK; + CRInputPos init_pos; + guint32 cur_char = 0, + next_char = 0; + CRString *property = NULL; + CRTerm *expr = NULL; + CRSimpleSel *simple_sels = NULL; + CRSelector *selector = NULL; + gboolean start_selector = FALSE, + is_important = FALSE; + + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + status = cr_parser_parse_selector (a_this, &selector); + CHECK_PARSING_STATUS (status, FALSE); + + READ_NEXT_CHAR (a_this, &cur_char); + + ENSURE_PARSING_COND_ERR + (a_this, cur_char == '{', + "while parsing rulset: current char should be '{'", + CR_SYNTAX_ERROR); + + if (PRIVATE (a_this)->sac_handler + && PRIVATE (a_this)->sac_handler->start_selector) { + /* + *the selector is ref counted so that the parser's user + *can choose to keep it. + */ + if (selector) { + cr_selector_ref (selector); + } + + PRIVATE (a_this)->sac_handler->start_selector + (PRIVATE (a_this)->sac_handler, selector); + start_selector = TRUE; + } + + cr_parser_try_to_skip_spaces_and_comments (a_this); + + PRIVATE (a_this)->state = TRY_PARSE_RULESET_STATE; + + status = cr_parser_parse_declaration (a_this, &property, + &expr, + &is_important); + if (expr) { + cr_term_ref (expr); + } + if (status == CR_OK + && PRIVATE (a_this)->sac_handler + && PRIVATE (a_this)->sac_handler->property) { + PRIVATE (a_this)->sac_handler->property + (PRIVATE (a_this)->sac_handler, property, expr, + is_important); + } + if (status == CR_OK) { + /* + *free the allocated + *'property' and 'term' before parsing + *next declarations. + */ + if (property) { + cr_string_destroy (property); + property = NULL; + } + if (expr) { + cr_term_unref (expr); + expr = NULL; + } + } else {/*status != CR_OK*/ + guint32 c = 0 ; + /* + *test if we have reached '}', which + *would mean that we are parsing an empty ruleset (eg. x{ }) + *In that case, goto end_of_ruleset. + */ + status = cr_tknzr_peek_char (PRIVATE (a_this)->tknzr, &c) ; + if (status == CR_OK && c == '}') { + status = CR_OK ; + goto end_of_ruleset ; + } + } + CHECK_PARSING_STATUS_ERR + (a_this, status, FALSE, + "while parsing ruleset: next construction should be a declaration", + CR_SYNTAX_ERROR); + + for (;;) { + PEEK_NEXT_CHAR (a_this, &next_char); + if (next_char != ';') + break; + + /*consume the ';' char */ + READ_NEXT_CHAR (a_this, &cur_char); + + cr_parser_try_to_skip_spaces_and_comments (a_this); + + status = cr_parser_parse_declaration (a_this, &property, + &expr, &is_important); + + if (expr) { + cr_term_ref (expr); + } + if (status == CR_OK + && PRIVATE (a_this)->sac_handler + && PRIVATE (a_this)->sac_handler->property) { + PRIVATE (a_this)->sac_handler->property + (PRIVATE (a_this)->sac_handler, + property, expr, is_important); + } + if (property) { + cr_string_destroy (property); + property = NULL; + } + if (expr) { + cr_term_unref (expr); + expr = NULL; + } + } + + end_of_ruleset: + cr_parser_try_to_skip_spaces_and_comments (a_this); + READ_NEXT_CHAR (a_this, &cur_char); + ENSURE_PARSING_COND_ERR + (a_this, cur_char == '}', + "while parsing rulset: current char must be a '}'", + CR_SYNTAX_ERROR); + + if (PRIVATE (a_this)->sac_handler + && PRIVATE (a_this)->sac_handler->end_selector) { + PRIVATE (a_this)->sac_handler->end_selector + (PRIVATE (a_this)->sac_handler, selector); + start_selector = FALSE; + } + + if (expr) { + cr_term_unref (expr); + expr = NULL; + } + + if (simple_sels) { + cr_simple_sel_destroy (simple_sels); + simple_sels = NULL; + } + + if (selector) { + cr_selector_unref (selector); + selector = NULL; + } + + cr_parser_clear_errors (a_this); + PRIVATE (a_this)->state = RULESET_PARSED_STATE; + + return CR_OK; + + error: + if (start_selector == TRUE + && PRIVATE (a_this)->sac_handler + && PRIVATE (a_this)->sac_handler->error) { + PRIVATE (a_this)->sac_handler->error + (PRIVATE (a_this)->sac_handler); + } + if (expr) { + cr_term_unref (expr); + expr = NULL; + } + if (simple_sels) { + cr_simple_sel_destroy (simple_sels); + simple_sels = NULL; + } + if (property) { + cr_string_destroy (property); + } + if (selector) { + cr_selector_unref (selector); + selector = NULL; + } + + cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); + + return status; +} + +/** + *Parses an 'import' declaration as defined in the css2 spec + *in appendix D.1: + * + *import ::= + *@import [STRING|URI] S* [ medium [ ',' S* medium]* ]? ';' S* + * + *@param a_this the "this pointer" of the current instance + *of #CRParser. + * + *@param a_medium_list out parameter. A linked list of + *#CRString + *Each CRString is a string that contains + *a 'medium' declaration part of the successfully + *parsed 'import' declaration. + * + *@param a_import_string out parameter. + *A string that contains the 'import + *string". The import string can be either an uri (if it starts with + *the substring "uri(") or a any other css2 string. Note that + * *a_import_string must be initially set to NULL or else, this function + *will return CR_BAD_PARAM_ERROR. + * + *@return CR_OK upon sucessfull completion, an error code otherwise. + */ +enum CRStatus +cr_parser_parse_import (CRParser * a_this, + GList ** a_media_list, + CRString ** a_import_string, + CRParsingLocation *a_location) +{ + enum CRStatus status = CR_OK; + CRInputPos init_pos; + guint32 cur_char = 0, + next_char = 0; + CRString *medium = NULL; + + g_return_val_if_fail (a_this + && a_import_string + && (*a_import_string == NULL), + CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + if (BYTE (a_this, 1, NULL) == '@' + && BYTE (a_this, 2, NULL) == 'i' + && BYTE (a_this, 3, NULL) == 'm' + && BYTE (a_this, 4, NULL) == 'p' + && BYTE (a_this, 5, NULL) == 'o' + && BYTE (a_this, 6, NULL) == 'r' + && BYTE (a_this, 7, NULL) == 't') { + SKIP_CHARS (a_this, 1); + if (a_location) { + cr_parser_get_parsing_location + (a_this, a_location) ; + } + SKIP_CHARS (a_this, 6); + status = CR_OK; + } else { + status = CR_PARSING_ERROR; + goto error; + } + + cr_parser_try_to_skip_spaces_and_comments (a_this); + + PRIVATE (a_this)->state = TRY_PARSE_IMPORT_STATE; + + PEEK_NEXT_CHAR (a_this, &next_char); + + if (next_char == '"' || next_char == '\'') { + status = cr_parser_parse_string (a_this, a_import_string); + + CHECK_PARSING_STATUS (status, FALSE); + } else { + status = cr_parser_parse_uri (a_this, a_import_string); + + CHECK_PARSING_STATUS (status, FALSE); + } + + cr_parser_try_to_skip_spaces_and_comments (a_this); + + status = cr_parser_parse_ident (a_this, &medium); + + if (status == CR_OK && medium) { + *a_media_list = g_list_append (*a_media_list, medium); + medium = NULL; + } + + cr_parser_try_to_skip_spaces_and_comments (a_this); + + for (; status == CR_OK;) { + if ((status = cr_tknzr_peek_char (PRIVATE (a_this)->tknzr, + &next_char)) != CR_OK) { + if (status == CR_END_OF_INPUT_ERROR) { + status = CR_OK; + goto okay; + } + goto error; + } + + if (next_char == ',') { + READ_NEXT_CHAR (a_this, &cur_char); + } else { + break; + } + + cr_parser_try_to_skip_spaces_and_comments (a_this); + + status = cr_parser_parse_ident (a_this, &medium); + + cr_parser_try_to_skip_spaces_and_comments (a_this); + + if ((status == CR_OK) && medium) { + *a_media_list = g_list_append (*a_media_list, medium); + + medium = NULL; + } + + CHECK_PARSING_STATUS (status, FALSE); + cr_parser_try_to_skip_spaces_and_comments (a_this); + } + cr_parser_try_to_skip_spaces_and_comments (a_this); + READ_NEXT_CHAR (a_this, &cur_char); + ENSURE_PARSING_COND (cur_char == ';'); + cr_parser_try_to_skip_spaces_and_comments (a_this); + okay: + cr_parser_clear_errors (a_this); + PRIVATE (a_this)->state = IMPORT_PARSED_STATE; + + return CR_OK; + + error: + + if (*a_media_list) { + GList *cur = NULL; + + /* + *free each element of *a_media_list. + *Note that each element of *a_medium list *must* + *be a GString* or else, the code that is coming next + *will corrupt the memory and lead to hard to debug + *random crashes. + *This is where C++ and its compile time + *type checking mecanism (through STL containers) would + *have prevented us to go through this hassle. + */ + for (cur = *a_media_list; cur; cur = cur->next) { + if (cur->data) { + cr_string_destroy (cur->data); + } + } + + g_list_free (*a_media_list); + *a_media_list = NULL; + } + + if (*a_import_string) { + cr_string_destroy (*a_import_string); + *a_import_string = NULL; + } + + if (medium) { + cr_string_destroy (medium); + medium = NULL; + } + + cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); + + return status; +} + +/** + *Parses a 'media' declaration as specified in the css2 spec at + *appendix D.1: + * + *media ::= @media S* medium [ ',' S* medium ]* '{' S* ruleset* '}' S* + * + *Note that this function calls the required sac handlers during the parsing + *to notify media productions. See #CRDocHandler to know the callback called + *during @media parsing. + *@param a_this the "this pointer" of the current instance of #CRParser. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_parser_parse_media (CRParser * a_this) +{ + enum CRStatus status = CR_OK; + CRInputPos init_pos; + CRToken *token = NULL; + guint32 next_char = 0, + cur_char = 0; + CRString *medium = NULL; + GList *media_list = NULL; + CRParsingLocation location = {0,0,0} ; + + g_return_val_if_fail (a_this + && PRIVATE (a_this), + CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, + &token); + ENSURE_PARSING_COND (status == CR_OK + && token + && token->type == MEDIA_SYM_TK); + cr_parsing_location_copy (&location, &token->location) ; + cr_token_destroy (token); + token = NULL; + + cr_parser_try_to_skip_spaces_and_comments (a_this); + + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); + ENSURE_PARSING_COND (status == CR_OK + && token && token->type == IDENT_TK); + + medium = token->u.str; + token->u.str = NULL; + cr_token_destroy (token); + token = NULL; + + if (medium) { + media_list = g_list_append (media_list, medium); + medium = NULL; + } + + for (; status == CR_OK;) { + cr_parser_try_to_skip_spaces_and_comments (a_this); + PEEK_NEXT_CHAR (a_this, &next_char); + + if (next_char == ',') { + READ_NEXT_CHAR (a_this, &cur_char); + } else { + break; + } + + cr_parser_try_to_skip_spaces_and_comments (a_this); + + status = cr_parser_parse_ident (a_this, &medium); + + CHECK_PARSING_STATUS (status, FALSE); + + if (medium) { + media_list = g_list_append (media_list, medium); + medium = NULL; + } + } + + READ_NEXT_CHAR (a_this, &cur_char); + + ENSURE_PARSING_COND (cur_char == '{'); + + /* + *call the SAC handler api here. + */ + if (PRIVATE (a_this)->sac_handler + && PRIVATE (a_this)->sac_handler->start_media) { + PRIVATE (a_this)->sac_handler->start_media + (PRIVATE (a_this)->sac_handler, media_list, + &location); + } + + cr_parser_try_to_skip_spaces_and_comments (a_this); + + PRIVATE (a_this)->state = TRY_PARSE_MEDIA_STATE; + + for (; status == CR_OK;) { + status = cr_parser_parse_ruleset (a_this); + cr_parser_try_to_skip_spaces_and_comments (a_this); + } + + READ_NEXT_CHAR (a_this, &cur_char); + + ENSURE_PARSING_COND (cur_char == '}'); + + /* + *call the right SAC handler api here. + */ + if (PRIVATE (a_this)->sac_handler + && PRIVATE (a_this)->sac_handler->end_media) { + PRIVATE (a_this)->sac_handler->end_media + (PRIVATE (a_this)->sac_handler, media_list); + } + + cr_parser_try_to_skip_spaces_and_comments (a_this); + + /* + *Then, free the data structures passed to + *the last call to the SAC handler. + */ + if (medium) { + cr_string_destroy (medium); + medium = NULL; + } + + if (media_list) { + GList *cur = NULL; + + for (cur = media_list; cur; cur = cur->next) { + cr_string_destroy (cur->data); + } + + g_list_free (media_list); + media_list = NULL; + } + + cr_parser_clear_errors (a_this); + PRIVATE (a_this)->state = MEDIA_PARSED_STATE; + + return CR_OK; + + error: + + if (token) { + cr_token_destroy (token); + token = NULL; + } + + if (medium) { + cr_string_destroy (medium); + medium = NULL; + } + + if (media_list) { + GList *cur = NULL; + + for (cur = media_list; cur; cur = cur->next) { + cr_string_destroy (cur->data); + } + + g_list_free (media_list); + media_list = NULL; + } + + cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); + + return status; +} + +/** + *Parses '@page' rule as specified in the css2 spec in appendix D.1: + *page ::= PAGE_SYM S* IDENT? pseudo_page? S* + *'{' S* declaration [ ';' S* declaration ]* '}' S* + * + *This function also calls the relevant SAC handlers whenever it + *encounters a construction that must + *be reported to the calling application. + *@param a_this the "this pointer" of the current instance of #CRParser. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_parser_parse_page (CRParser * a_this) +{ + enum CRStatus status = CR_OK; + CRInputPos init_pos; + CRToken *token = NULL; + CRTerm *css_expression = NULL; + CRString *page_selector = NULL, + *page_pseudo_class = NULL, + *property = NULL; + gboolean important = TRUE; + CRParsingLocation location = {0,0,0} ; + + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, + &token) ; + ENSURE_PARSING_COND (status == CR_OK + && token + && token->type == PAGE_SYM_TK); + + cr_parsing_location_copy (&location, &token->location) ; + cr_token_destroy (token); + token = NULL; + + cr_parser_try_to_skip_spaces_and_comments (a_this); + + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); + ENSURE_PARSING_COND (status == CR_OK && token); + + if (token->type == IDENT_TK) { + page_selector = token->u.str; + token->u.str = NULL; + cr_token_destroy (token); + token = NULL; + } else { + cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); + token = NULL; + } + + /* + *try to parse pseudo_page + */ + cr_parser_try_to_skip_spaces_and_comments (a_this); + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); + ENSURE_PARSING_COND (status == CR_OK && token); + + if (token->type == DELIM_TK && token->u.unichar == ':') { + cr_token_destroy (token); + token = NULL; + status = cr_parser_parse_ident (a_this, &page_pseudo_class); + CHECK_PARSING_STATUS (status, FALSE); + } else { + cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token); + token = NULL; + } + + /* + *parse_block + * + */ + cr_parser_try_to_skip_spaces_and_comments (a_this); + + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); + + ENSURE_PARSING_COND (status == CR_OK && token + && token->type == CBO_TK); + + cr_token_destroy (token); + token = NULL; + + /* + *Call the appropriate SAC handler here. + */ + if (PRIVATE (a_this)->sac_handler + && PRIVATE (a_this)->sac_handler->start_page) { + PRIVATE (a_this)->sac_handler->start_page + (PRIVATE (a_this)->sac_handler, + page_selector, page_pseudo_class, + &location); + } + cr_parser_try_to_skip_spaces_and_comments (a_this); + + PRIVATE (a_this)->state = TRY_PARSE_PAGE_STATE; + + status = cr_parser_parse_declaration (a_this, &property, + &css_expression, + &important); + ENSURE_PARSING_COND (status == CR_OK); + + /* + *call the relevant SAC handler here... + */ + if (PRIVATE (a_this)->sac_handler + && PRIVATE (a_this)->sac_handler->property) { + if (css_expression) + cr_term_ref (css_expression); + + PRIVATE (a_this)->sac_handler->property + (PRIVATE (a_this)->sac_handler, + property, css_expression, important); + } + /* + *... and free the data structure passed to that last + *SAC handler. + */ + if (property) { + cr_string_destroy (property); + property = NULL; + } + if (css_expression) { + cr_term_unref (css_expression); + css_expression = NULL; + } + + for (;;) { + /*parse the other ';' separated declarations */ + if (token) { + cr_token_destroy (token); + token = NULL; + } + status = cr_tknzr_get_next_token + (PRIVATE (a_this)->tknzr, &token); + + ENSURE_PARSING_COND (status == CR_OK && token); + + if (token->type != SEMICOLON_TK) { + cr_tknzr_unget_token + (PRIVATE (a_this)->tknzr, + token); + token = NULL ; + break; + } + + cr_token_destroy (token); + token = NULL; + cr_parser_try_to_skip_spaces_and_comments (a_this); + + status = cr_parser_parse_declaration (a_this, &property, + &css_expression, + &important); + if (status != CR_OK) + break ; + + /* + *call the relevant SAC handler here... + */ + if (PRIVATE (a_this)->sac_handler + && PRIVATE (a_this)->sac_handler->property) { + cr_term_ref (css_expression); + PRIVATE (a_this)->sac_handler->property + (PRIVATE (a_this)->sac_handler, + property, css_expression, important); + } + /* + *... and free the data structure passed to that last + *SAC handler. + */ + if (property) { + cr_string_destroy (property); + property = NULL; + } + if (css_expression) { + cr_term_unref (css_expression); + css_expression = NULL; + } + } + cr_parser_try_to_skip_spaces_and_comments + (a_this) ; + if (token) { + cr_token_destroy (token) ; + token = NULL ; + } + + status = cr_tknzr_get_next_token + (PRIVATE (a_this)->tknzr, &token); + ENSURE_PARSING_COND (status == CR_OK + && token + && token->type == CBC_TK) ; + cr_token_destroy (token) ; + token = NULL ; + /* + *call the relevant SAC handler here. + */ + if (PRIVATE (a_this)->sac_handler + && PRIVATE (a_this)->sac_handler->end_page) { + PRIVATE (a_this)->sac_handler->end_page + (PRIVATE (a_this)->sac_handler, + page_selector, page_pseudo_class); + } + + if (page_selector) { + cr_string_destroy (page_selector); + page_selector = NULL; + } + + if (page_pseudo_class) { + cr_string_destroy (page_pseudo_class); + page_pseudo_class = NULL; + } + + cr_parser_try_to_skip_spaces_and_comments (a_this); + + /*here goes the former implem of this function ... */ + + cr_parser_clear_errors (a_this); + PRIVATE (a_this)->state = PAGE_PARSED_STATE; + + return CR_OK; + + error: + if (token) { + cr_token_destroy (token); + token = NULL; + } + if (page_selector) { + cr_string_destroy (page_selector); + page_selector = NULL; + } + if (page_pseudo_class) { + cr_string_destroy (page_pseudo_class); + page_pseudo_class = NULL; + } + if (property) { + cr_string_destroy (property); + property = NULL; + } + if (css_expression) { + cr_term_destroy (css_expression); + css_expression = NULL; + } + cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); + return status; +} + +/** + *Parses a charset declaration as defined implictly by the css2 spec in + *appendix D.1: + *charset ::= CHARSET_SYM S* STRING S* ';' + * + *@param a_this the "this pointer" of the current instance of #CRParser. + *@param a_value out parameter. The actual parsed value of the charset + *declararation. Note that for safety check reasons, *a_value must be + *set to NULL. + *@param a_charset_sym_location the parsing location of + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_parser_parse_charset (CRParser * a_this, CRString ** a_value, + CRParsingLocation *a_charset_sym_location) +{ + enum CRStatus status = CR_OK; + CRInputPos init_pos; + CRToken *token = NULL; + CRString *charset_str = NULL; + + g_return_val_if_fail (a_this && a_value + && (*a_value == NULL), + CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); + + ENSURE_PARSING_COND (status == CR_OK + && token && token->type == CHARSET_SYM_TK); + if (a_charset_sym_location) { + cr_parsing_location_copy (a_charset_sym_location, + &token->location) ; + } + cr_token_destroy (token); + token = NULL; + + PRIVATE (a_this)->state = TRY_PARSE_CHARSET_STATE; + + cr_parser_try_to_skip_spaces_and_comments (a_this); + + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); + ENSURE_PARSING_COND (status == CR_OK + && token && token->type == STRING_TK); + charset_str = token->u.str; + token->u.str = NULL; + cr_token_destroy (token); + token = NULL; + + cr_parser_try_to_skip_spaces_and_comments (a_this); + + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); + + ENSURE_PARSING_COND (status == CR_OK + && token && token->type == SEMICOLON_TK); + cr_token_destroy (token); + token = NULL; + + if (charset_str) { + *a_value = charset_str; + charset_str = NULL; + } + + PRIVATE (a_this)->state = CHARSET_PARSED_STATE; + return CR_OK; + + error: + + if (token) { + cr_token_destroy (token); + token = NULL; + } + + if (*a_value) { + cr_string_destroy (*a_value); + *a_value = NULL; + } + + if (charset_str) { + cr_string_destroy (charset_str); + charset_str = NULL; + } + + cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); + + return status; +} + +/** + *Parses the "@font-face" rule specified in the css1 spec in + *appendix D.1: + * + *font_face ::= FONT_FACE_SYM S* + *'{' S* declaration [ ';' S* declaration ]* '}' S* + * + *This function will call SAC handlers whenever it is necessary. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_parser_parse_font_face (CRParser * a_this) +{ + enum CRStatus status = CR_ERROR; + CRInputPos init_pos; + CRString *property = NULL; + CRTerm *css_expression = NULL; + CRToken *token = NULL; + gboolean important = FALSE; + guint32 next_char = 0, + cur_char = 0; + CRParsingLocation location = {0,0,0} ; + + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token); + ENSURE_PARSING_COND (status == CR_OK + && token + && token->type == FONT_FACE_SYM_TK); + + cr_parser_try_to_skip_spaces_and_comments (a_this); + if (token) { + cr_parsing_location_copy (&location, + &token->location) ; + cr_token_destroy (token); + token = NULL; + } + status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, + &token); + ENSURE_PARSING_COND (status == CR_OK && token + && token->type == CBO_TK); + if (token) { + cr_token_destroy (token); + token = NULL; + } + /* + *here, call the relevant SAC handler. + */ + if (PRIVATE (a_this)->sac_handler + && PRIVATE (a_this)->sac_handler->start_font_face) { + PRIVATE (a_this)->sac_handler->start_font_face + (PRIVATE (a_this)->sac_handler, &location); + } + PRIVATE (a_this)->state = TRY_PARSE_FONT_FACE_STATE; + /* + *and resume the parsing. + */ + cr_parser_try_to_skip_spaces_and_comments (a_this); + status = cr_parser_parse_declaration (a_this, &property, + &css_expression, &important); + if (status == CR_OK) { + /* + *here, call the relevant SAC handler. + */ + cr_term_ref (css_expression); + if (PRIVATE (a_this)->sac_handler && + PRIVATE (a_this)->sac_handler->property) { + PRIVATE (a_this)->sac_handler->property + (PRIVATE (a_this)->sac_handler, + property, css_expression, important); + } + ENSURE_PARSING_COND (css_expression && property); + } + /*free the data structures allocated during last parsing. */ + if (property) { + cr_string_destroy (property); + property = NULL; + } + if (css_expression) { + cr_term_unref (css_expression); + css_expression = NULL; + } + for (;;) { + PEEK_NEXT_CHAR (a_this, &next_char); + if (next_char == ';') { + READ_NEXT_CHAR (a_this, &cur_char); + } else { + break; + } + cr_parser_try_to_skip_spaces_and_comments (a_this); + status = cr_parser_parse_declaration (a_this, + &property, + &css_expression, + &important); + if (status != CR_OK) + break; + /* + *here, call the relevant SAC handler. + */ + cr_term_ref (css_expression); + if (PRIVATE (a_this)->sac_handler->property) { + PRIVATE (a_this)->sac_handler->property + (PRIVATE (a_this)->sac_handler, + property, css_expression, important); + } + /* + *Then, free the data structures allocated during + *last parsing. + */ + if (property) { + cr_string_destroy (property); + property = NULL; + } + if (css_expression) { + cr_term_unref (css_expression); + css_expression = NULL; + } + } + cr_parser_try_to_skip_spaces_and_comments (a_this); + READ_NEXT_CHAR (a_this, &cur_char); + ENSURE_PARSING_COND (cur_char == '}'); + /* + *here, call the relevant SAC handler. + */ + if (PRIVATE (a_this)->sac_handler->end_font_face) { + PRIVATE (a_this)->sac_handler->end_font_face + (PRIVATE (a_this)->sac_handler); + } + cr_parser_try_to_skip_spaces_and_comments (a_this); + + if (token) { + cr_token_destroy (token); + token = NULL; + } + cr_parser_clear_errors (a_this); + PRIVATE (a_this)->state = FONT_FACE_PARSED_STATE; + return CR_OK; + + error: + if (token) { + cr_token_destroy (token); + token = NULL; + } + if (property) { + cr_string_destroy (property); + property = NULL; + } + if (css_expression) { + cr_term_destroy (css_expression); + css_expression = NULL; + } + cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos); + return status; +} + +/** + *Parses the data that comes from the + *input previously associated to the current instance of + *#CRParser. + *@param a_this the current instance of #CRParser. + *@return CR_OK ; + */ +enum CRStatus +cr_parser_parse (CRParser * a_this) +{ + enum CRStatus status = CR_ERROR; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && PRIVATE (a_this)->tknzr, CR_BAD_PARAM_ERROR); + + if (PRIVATE (a_this)->use_core_grammar == FALSE) { + status = cr_parser_parse_stylesheet (a_this); + } else { + status = cr_parser_parse_stylesheet_core (a_this); + } + + return status; +} + +enum CRStatus +cr_parser_set_tknzr (CRParser * a_this, CRTknzr * a_tknzr) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); + + if (PRIVATE (a_this)->tknzr) { + cr_tknzr_unref (PRIVATE (a_this)->tknzr); + } + + PRIVATE (a_this)->tknzr = a_tknzr; + + if (a_tknzr) + cr_tknzr_ref (a_tknzr); + + return CR_OK; +} + +/** + *Getter of the parser's underlying tokenizer + *@param a_this the current instance of #CRParser + *@param a_tknzr out parameter. The returned tokenizer + *@return CR_OK upon succesful completion, an error code + *otherwise + */ +enum CRStatus +cr_parser_get_tknzr (CRParser * a_this, CRTknzr ** a_tknzr) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_tknzr, CR_BAD_PARAM_ERROR); + + *a_tknzr = PRIVATE (a_this)->tknzr; + return CR_OK; +} + +/** + *Gets the current parsing location. + *@param a_this the current instance of #CRParser + *@param a_loc the parsing location to get. + *@return CR_OK upon succesful completion, an error code + *otherwise. + */ +enum CRStatus +cr_parser_get_parsing_location (CRParser *a_this, + CRParsingLocation *a_loc) +{ + g_return_val_if_fail (a_this + && PRIVATE (a_this) + && a_loc, CR_BAD_PARAM_ERROR) ; + + return cr_tknzr_get_parsing_location + (PRIVATE (a_this)->tknzr, a_loc) ; +} + +/** + *Parses a stylesheet from a buffer + *@param a_this the current instance of #CRparser + *@param a_buf the input buffer + *@param a_len the length of the input buffer + *@param a_enc the encoding of the buffer + *@return CR_OK upon successful completion, an error code otherwise. + */ +enum CRStatus +cr_parser_parse_buf (CRParser * a_this, + const guchar * a_buf, + gulong a_len, enum CREncoding a_enc) +{ + enum CRStatus status = CR_ERROR; + CRTknzr *tknzr = NULL; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_buf, CR_BAD_PARAM_ERROR); + + tknzr = cr_tknzr_new_from_buf ((guchar*)a_buf, a_len, a_enc, FALSE); + + g_return_val_if_fail (tknzr != NULL, CR_ERROR); + + status = cr_parser_set_tknzr (a_this, tknzr); + g_return_val_if_fail (status == CR_OK, CR_ERROR); + + status = cr_parser_parse (a_this); + + return status; +} + +/** + *Destroys the current instance + *of #CRParser. + *@param a_this the current instance of #CRParser to + *destroy. + */ +void +cr_parser_destroy (CRParser * a_this) +{ + g_return_if_fail (a_this && PRIVATE (a_this)); + + if (PRIVATE (a_this)->tknzr) { + if (cr_tknzr_unref (PRIVATE (a_this)->tknzr) == TRUE) + PRIVATE (a_this)->tknzr = NULL; + } + + if (PRIVATE (a_this)->sac_handler) { + cr_doc_handler_unref (PRIVATE (a_this)->sac_handler); + PRIVATE (a_this)->sac_handler = NULL; + } + + if (PRIVATE (a_this)->err_stack) { + cr_parser_clear_errors (a_this); + PRIVATE (a_this)->err_stack = NULL; + } + + if (PRIVATE (a_this)) { + g_free (PRIVATE (a_this)); + PRIVATE (a_this) = NULL; + } + + if (a_this) { + g_free (a_this); + a_this = NULL; /*useless. Just for the sake of coherence */ + } +} diff --git a/src/libcroco/cr-parser.h b/src/libcroco/cr-parser.h new file mode 100644 index 000000000..1534afb86 --- /dev/null +++ b/src/libcroco/cr-parser.h @@ -0,0 +1,128 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli + * See COPYRIGHTS file for copyrights information. + */ + +#ifndef __CR_PARSER_H__ +#define __CR_PARSER_H__ + +#include +#include "cr-input.h" +#include "cr-tknzr.h" +#include "cr-utils.h" +#include "cr-doc-handler.h" + +G_BEGIN_DECLS + +/** + *@file + *The declaration file + *of the #CRParser class. + */ +typedef struct _CRParser CRParser ; +typedef struct _CRParserPriv CRParserPriv ; + + +/** + *The implementation of + *the SAC parser. + *The Class is opaque + *and must be manipulated through + *the provided methods. + */ +struct _CRParser { + CRParserPriv *priv ; +} ; + + +CRParser * cr_parser_new (CRTknzr *a_tknzr) ; + +CRParser * cr_parser_new_from_buf (guchar *a_buf, gulong a_len, + enum CREncoding a_enc, + gboolean a_free_buf) ; + +CRParser * cr_parser_new_from_file (const guchar *a_file_uri, + enum CREncoding a_enc) ; + +CRParser * cr_parser_new_from_input (CRInput *a_input) ; + +enum CRStatus cr_parser_set_tknzr (CRParser *a_this, CRTknzr *a_tknzr) ; + +enum CRStatus cr_parser_get_tknzr (CRParser *a_this, CRTknzr **a_tknzr) ; + +enum CRStatus cr_parser_get_parsing_location (CRParser *a_this, CRParsingLocation *a_loc) ; + +enum CRStatus cr_parser_try_to_skip_spaces_and_comments (CRParser *a_this) ; + + +enum CRStatus cr_parser_set_sac_handler (CRParser *a_this, + CRDocHandler *a_handler) ; + +enum CRStatus cr_parser_get_sac_handler (CRParser *a_this, + CRDocHandler **a_handler) ; + +enum CRStatus cr_parser_set_use_core_grammar (CRParser *a_this, + gboolean a_use_core_grammar) ; +enum CRStatus cr_parser_get_use_core_grammar (CRParser *a_this, + gboolean *a_use_core_grammar) ; + +enum CRStatus cr_parser_parse (CRParser *a_this) ; + +enum CRStatus cr_parser_parse_file (CRParser *a_this, + const guchar *a_file_uri, + enum CREncoding a_enc) ; + +enum CRStatus cr_parser_parse_buf (CRParser *a_this, const guchar *a_buf, + gulong a_len, enum CREncoding a_enc) ; + +enum CRStatus cr_parser_set_default_sac_handler (CRParser *a_this) ; + +enum CRStatus cr_parser_parse_term (CRParser *a_this, CRTerm **a_term) ; + +enum CRStatus cr_parser_parse_expr (CRParser *a_this, CRTerm **a_expr) ; + +enum CRStatus cr_parser_parse_prio (CRParser *a_this, CRString **a_prio) ; + +enum CRStatus cr_parser_parse_declaration (CRParser *a_this, CRString **a_property, + CRTerm **a_expr, gboolean *a_important) ; + +enum CRStatus cr_parser_parse_statement_core (CRParser *a_this) ; + +enum CRStatus cr_parser_parse_ruleset (CRParser *a_this) ; + +enum CRStatus cr_parser_parse_import (CRParser *a_this, GList ** a_media_list, + CRString **a_import_string, + CRParsingLocation *a_location) ; + +enum CRStatus cr_parser_parse_media (CRParser *a_this) ; + +enum CRStatus cr_parser_parse_page (CRParser *a_this) ; + +enum CRStatus cr_parser_parse_charset (CRParser *a_this, CRString **a_value, + CRParsingLocation *a_charset_sym_location) ; + +enum CRStatus cr_parser_parse_font_face (CRParser *a_this) ; + +void cr_parser_destroy (CRParser *a_this) ; + +G_END_DECLS + +#endif /*__CR_PARSER_H__*/ diff --git a/src/libcroco/cr-parsing-location.c b/src/libcroco/cr-parsing-location.c new file mode 100644 index 000000000..81b05f7c3 --- /dev/null +++ b/src/libcroco/cr-parsing-location.c @@ -0,0 +1,153 @@ +/* -*- Mode: C; indent-tabs-mode: ni; c-basic-offset: 8 -*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli. + * See the COPYRIGHTS file for copyright information. + */ + +#include +#include "cr-parsing-location.h" + +/** + *@file + *Definition of the #CRparsingLocation class. + */ + + +/** + *Instanciates a new parsing location. + *@return the newly instanciated #CRParsingLocation. + *Must be freed by cr_parsing_location_destroy() + */ +CRParsingLocation * +cr_parsing_location_new (void) +{ + CRParsingLocation * result = NULL ; + + result = g_try_malloc (sizeof (CRParsingLocation)) ; + if (!result) { + cr_utils_trace_info ("Out of memory error") ; + return NULL ; + } + cr_parsing_location_init (result) ; + return result ; +} + +/** + *Initializes the an instance of #CRparsingLocation. + *@param a_this the current instance of #CRParsingLocation. + *@return CR_OK upon + */ +enum CRStatus +cr_parsing_location_init (CRParsingLocation *a_this) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR) ; + + memset (a_this, 0, sizeof (CRParsingLocation)) ; + return CR_OK ; +} + +/** + *Copies an instance of CRParsingLocation into another one. + *@param a_to the destination of the copy. + *Must be allocated by the caller. + *@param a_from the source of the copy. + *@return CR_OK upon succesful completion, an error code + *otherwise. + */ +enum CRStatus +cr_parsing_location_copy (CRParsingLocation *a_to, + CRParsingLocation *a_from) +{ + g_return_val_if_fail (a_to && a_from, CR_BAD_PARAM_ERROR) ; + + memcpy (a_to, a_from, sizeof (CRParsingLocation)) ; + return CR_OK ; +} + +/** + *@param a_this the current instance of #CRParsingLocation. + *@param a_mask a bitmap that defines which parts of the + *parsing location are to be serialized (line, column or byte offset) + *@return the serialized string or NULL in case of an error. + */ +gchar * +cr_parsing_location_to_string (CRParsingLocation *a_this, + enum CRParsingLocationSerialisationMask a_mask) +{ + GString *result = NULL ; + gchar *str = NULL ; + + g_return_val_if_fail (a_this, NULL) ; + + if (!a_mask) { + a_mask = DUMP_LINE | DUMP_COLUMN | DUMP_BYTE_OFFSET ; + } + result =g_string_new (NULL) ; + if (!result) + return NULL ; + if (a_mask & DUMP_LINE) { + g_string_append_printf (result, "line:%d ", + a_this->line) ; + } + if (a_mask & DUMP_COLUMN) { + g_string_append_printf (result, "column:%d ", + a_this->column) ; + } + if (a_mask & DUMP_BYTE_OFFSET) { + g_string_append_printf (result, "byte offset:%d ", + a_this->byte_offset) ; + } + if (result->len) { + str = result->str ; + g_string_free (result, FALSE) ; + } else { + g_string_free (result, TRUE) ; + } + return str ; +} + +void +cr_parsing_location_dump (CRParsingLocation *a_this, + enum CRParsingLocationSerialisationMask a_mask, + FILE *a_fp) +{ + gchar *str = NULL ; + + g_return_if_fail (a_this && a_fp) ; + str = cr_parsing_location_to_string (a_this, a_mask) ; + if (str) { + fprintf (a_fp, "%s", str) ; + g_free (str) ; + str = NULL ; + } +} + +/** + *Destroys the current instance of #CRParsingLocation + *@param a_this the current instance of #CRParsingLocation. Must + *have been allocated with cr_parsing_location_new(). + */ +void +cr_parsing_location_destroy (CRParsingLocation *a_this) +{ + g_return_if_fail (a_this) ; + g_free (a_this) ; +} + diff --git a/src/libcroco/cr-parsing-location.h b/src/libcroco/cr-parsing-location.h new file mode 100644 index 000000000..877c0507f --- /dev/null +++ b/src/libcroco/cr-parsing-location.h @@ -0,0 +1,70 @@ +/* -*- Mode: C; indent-tabs-mode: ni; c-basic-offset: 8 -*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli. + * See the COPYRIGHTS file for copyright information. + */ + +#ifndef __CR_PARSING_LOCATION_H__ +#define __CR_PARSING_LOCATION_H__ + +#include "cr-utils.h" + +G_BEGIN_DECLS + +/** + *@file + *The declaration of the CRParsingLocation + *object. This object keeps track of line/column/byte offset/ + *at which the parsing of a given CSS construction appears. + */ + +typedef struct _CRParsingLocation CRParsingLocation; +struct _CRParsingLocation { + guint line ; + guint column ; + guint byte_offset ; +} ; + + +enum CRParsingLocationSerialisationMask { + DUMP_LINE = 1, + DUMP_COLUMN = 1 << 1, + DUMP_BYTE_OFFSET = 1 << 2 +} ; + +CRParsingLocation * cr_parsing_location_new (void) ; + +enum CRStatus cr_parsing_location_init (CRParsingLocation *a_this) ; + +enum CRStatus cr_parsing_location_copy (CRParsingLocation *a_to, + CRParsingLocation *a_from) ; + +gchar * cr_parsing_location_to_string (CRParsingLocation *a_this, + enum CRParsingLocationSerialisationMask a_mask) ; +void cr_parsing_location_dump (CRParsingLocation *a_this, + enum CRParsingLocationSerialisationMask a_mask, + FILE *a_fp) ; + +void cr_parsing_location_destroy (CRParsingLocation *a_this) ; + + + +G_END_DECLS +#endif diff --git a/src/libcroco/cr-prop-list.c b/src/libcroco/cr-prop-list.c new file mode 100644 index 000000000..4c56b9cd3 --- /dev/null +++ b/src/libcroco/cr-prop-list.c @@ -0,0 +1,360 @@ +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli + * See COPYRIGHTS file for copyrights information. + */ + +#include +#include "cr-prop-list.h" + +#define PRIVATE(a_obj) (a_obj)->priv + +struct _CRPropListPriv { + CRString *prop; + CRDeclaration *decl; + CRPropList *next; + CRPropList *prev; +}; + +static CRPropList *cr_prop_list_allocate (void); + +/** + *Default allocator of CRPropList + *@return the newly allocated CRPropList or NULL + *if an error arises. + */ +static CRPropList * +cr_prop_list_allocate (void) +{ + CRPropList *result = NULL; + + result = g_try_malloc (sizeof (CRPropList)); + if (!result) { + cr_utils_trace_info ("could not allocate CRPropList"); + return NULL; + } + memset (result, 0, sizeof (CRPropList)); + PRIVATE (result) = g_try_malloc (sizeof (CRPropListPriv)); + if (!result) { + cr_utils_trace_info ("could not allocate CRPropListPriv"); + g_free (result); + return NULL; + } + memset (PRIVATE (result), 0, sizeof (CRPropListPriv)); + return result; +} + +/**************** + *public methods + ***************/ + +/** + *Appends a property list to the current one. + *@param a_this the current instance of #CRPropList + *@param a_to_append the property list to append + *@return the resulting prop list, or NULL if an error + *occured + */ +CRPropList * +cr_prop_list_append (CRPropList * a_this, CRPropList * a_to_append) +{ + CRPropList *cur = NULL; + + g_return_val_if_fail (a_to_append, NULL); + + if (!a_this) + return a_to_append; + + /*go fetch the last element of the list */ + for (cur = a_this; + cur && PRIVATE (cur) && PRIVATE (cur)->next; + cur = PRIVATE (cur)->next) ; + g_return_val_if_fail (cur, NULL); + PRIVATE (cur)->next = a_to_append; + PRIVATE (a_to_append)->prev = cur; + return a_this; +} + +/** + *Appends a pair of prop/declaration to + *the current prop list. + *@param a_this the current instance of #CRPropList + *@param a_prop the property to consider + *@param a_decl the declaration to consider + *@return the resulting property list, or NULL in case + *of an error. + */ +CRPropList * +cr_prop_list_append2 (CRPropList * a_this, + CRString * a_prop, + CRDeclaration * a_decl) +{ + CRPropList *list = NULL, + *result = NULL; + + g_return_val_if_fail (a_prop && a_decl, NULL); + + list = cr_prop_list_allocate (); + g_return_val_if_fail (list && PRIVATE (list), NULL); + + PRIVATE (list)->prop = a_prop; + PRIVATE (list)->decl = a_decl; + + result = cr_prop_list_append (a_this, list); + return result; +} + +/** + *Prepends a list to the current list + *@param a_this the current instance of #CRPropList + *@param the new list to prepend. + */ +CRPropList * +cr_prop_list_prepend (CRPropList * a_this, CRPropList * a_to_prepend) +{ + CRPropList *cur = NULL; + + g_return_val_if_fail (a_to_prepend, NULL); + + if (!a_this) + return a_to_prepend; + + for (cur = a_to_prepend; cur && PRIVATE (cur)->next; + cur = PRIVATE (cur)->next) ; + g_return_val_if_fail (cur, NULL); + PRIVATE (cur)->next = a_this; + PRIVATE (a_this)->prev = cur; + return a_to_prepend; +} + +/** + *Prepends a list to the current list + *@param a_this the current instance of #CRPropList + *@param the new list to prepend. + */ +CRPropList * +cr_prop_list_prepend2 (CRPropList * a_this, + CRString * a_prop, CRDeclaration * a_decl) +{ + CRPropList *list = NULL, + *result = NULL; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_prop && a_decl, NULL); + + list = cr_prop_list_allocate (); + g_return_val_if_fail (list, NULL); + PRIVATE (list)->prop = a_prop; + PRIVATE (list)->decl = a_decl; + result = cr_prop_list_prepend (a_this, list); + return result; +} + +/** + *Sets the property of a CRPropList + *@param a_this the current instance of #CRPropList + *@param a_prop the property to set + */ +enum CRStatus +cr_prop_list_set_prop (CRPropList * a_this, CRString * a_prop) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_prop, CR_BAD_PARAM_ERROR); + + PRIVATE (a_this)->prop = a_prop; + return CR_OK; +} + +/** + *Getter of the property associated to the current instance + *of #CRPropList + *@param a_this the current instance of #CRPropList + *@param a_prop out parameter. The returned property + *@return CR_OK upon successful completion, an error code + *otherwise. + */ +enum CRStatus +cr_prop_list_get_prop (CRPropList * a_this, CRString ** a_prop) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_prop, CR_BAD_PARAM_ERROR); + + *a_prop = PRIVATE (a_this)->prop; + return CR_OK; +} + +enum CRStatus +cr_prop_list_set_decl (CRPropList * a_this, CRDeclaration * a_decl) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_decl, CR_BAD_PARAM_ERROR); + + PRIVATE (a_this)->decl = a_decl; + return CR_OK; +} + +enum CRStatus +cr_prop_list_get_decl (CRPropList * a_this, CRDeclaration ** a_decl) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_decl, CR_BAD_PARAM_ERROR); + + *a_decl = PRIVATE (a_this)->decl; + return CR_OK; +} + +/** + *Lookup a given property/declaration pair + *@param a_this the current instance of #CRPropList + *@param a_prop the property to lookup + *@param a_prop_list out parameter. The property/declaration + *pair found (if and only if the function returned code if CR_OK) + *@return CR_OK if a prop/decl pair has been found, + *CR_VALUE_NOT_FOUND_ERROR if not, or an error code if something + *bad happens. + */ +enum CRStatus +cr_prop_list_lookup_prop (CRPropList * a_this, + CRString * a_prop, CRPropList ** a_pair) +{ + CRPropList *cur = NULL; + + g_return_val_if_fail (a_prop && a_pair, CR_BAD_PARAM_ERROR); + + if (!a_this) + return CR_VALUE_NOT_FOUND_ERROR; + + g_return_val_if_fail (PRIVATE (a_this), CR_BAD_PARAM_ERROR); + + for (cur = a_this; cur; cur = PRIVATE (cur)->next) { + if (PRIVATE (cur)->prop + && PRIVATE (cur)->prop->stryng + && PRIVATE (cur)->prop->stryng->str + && a_prop->stryng + && a_prop->stryng->str + && !strcmp (PRIVATE (cur)->prop->stryng->str, + a_prop->stryng->str)) + break; + } + + if (cur) { + *a_pair = cur; + return CR_OK; + } + + return CR_VALUE_NOT_FOUND_ERROR; +} + +/** + *Gets the next prop/decl pair in the list + *@param a_this the current instance of CRPropList + *@param the next prop/decl pair, or NULL if we + *reached the end of the list. + *@return the next prop/declaration pair of the list, + *or NULL if we reached end of list (or if an error occurs) + */ +CRPropList * +cr_prop_list_get_next (CRPropList * a_this) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this), NULL); + + return PRIVATE (a_this)->next; +} + +/** + *Gets the previous prop/decl pair in the list + *@param a_this the current instance of CRPropList + *@param the previous prop/decl pair, or NULL if we + *reached the end of the list. + *@return the previous prop/declaration pair of the list, + *or NULL if we reached end of list (or if an error occurs) + */ +CRPropList * +cr_prop_list_get_prev (CRPropList * a_this) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this), NULL); + + return PRIVATE (a_this)->prev; +} + +/** + *Unlinks a prop/decl pair from the list + *@param a_this the current list of prop/decl pairs + *@param a_pair the prop/decl pair to unlink. + *@return the new list or NULL in case of an error. + */ +CRPropList * +cr_prop_list_unlink (CRPropList * a_this, CRPropList * a_pair) +{ + CRPropList *prev = NULL, + *next = NULL; + + g_return_val_if_fail (a_this && PRIVATE (a_this) && a_pair, NULL); + + /*some sanity checks */ + if (PRIVATE (a_pair)->next) { + next = PRIVATE (a_pair)->next; + g_return_val_if_fail (PRIVATE (next), NULL); + g_return_val_if_fail (PRIVATE (next)->prev == a_pair, NULL); + } + if (PRIVATE (a_pair)->prev) { + prev = PRIVATE (a_pair)->prev; + g_return_val_if_fail (PRIVATE (prev), NULL); + g_return_val_if_fail (PRIVATE (prev)->next == a_pair, NULL); + } + if (prev) { + PRIVATE (prev)->next = next; + } + if (next) { + PRIVATE (next)->prev = prev; + } + PRIVATE (a_pair)->prev = PRIVATE (a_pair)->next = NULL; + if (a_this == a_pair) { + if (next) + return next; + return NULL; + } + return a_this; +} + +void +cr_prop_list_destroy (CRPropList * a_this) +{ + CRPropList *tail = NULL, + *cur = NULL; + + g_return_if_fail (a_this && PRIVATE (a_this)); + + for (tail = a_this; + tail && PRIVATE (tail) && PRIVATE (tail)->next; + tail = cr_prop_list_get_next (tail)) ; + g_return_if_fail (tail); + + cur = tail; + + while (cur) { + tail = PRIVATE (cur)->prev; + if (tail && PRIVATE (tail)) + PRIVATE (tail)->next = NULL; + PRIVATE (cur)->prev = NULL; + g_free (PRIVATE (cur)); + PRIVATE (cur) = NULL; + g_free (cur); + cur = tail; + } +} diff --git a/src/libcroco/cr-prop-list.h b/src/libcroco/cr-prop-list.h new file mode 100644 index 000000000..a003be260 --- /dev/null +++ b/src/libcroco/cr-prop-list.h @@ -0,0 +1,80 @@ +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli + * See COPYRIGHTS file for copyrights information. + */ + +#ifndef __CR_PROP_LIST_H__ +#define __CR_PROP_LIST_H__ + +#include "cr-utils.h" +#include "cr-declaration.h" +#include "cr-string.h" + +G_BEGIN_DECLS + +typedef struct _CRPropList CRPropList ; +typedef struct _CRPropListPriv CRPropListPriv ; + +struct _CRPropList +{ + CRPropListPriv * priv; +} ; + +CRPropList * cr_prop_list_append (CRPropList *a_this, + CRPropList *a_to_append) ; + +CRPropList * cr_prop_list_append2 (CRPropList *a_this, + CRString *a_prop, + CRDeclaration *a_decl) ; + +CRPropList * cr_prop_list_prepend (CRPropList *a_this, + CRPropList *a_to_append) ; + +CRPropList * cr_prop_list_prepend2 (CRPropList *a_this, + CRString *a_prop, + CRDeclaration *a_decl) ; + +enum CRStatus cr_prop_list_set_prop (CRPropList *a_this, + CRString *a_prop) ; + +enum CRStatus cr_prop_list_get_prop (CRPropList *a_this, + CRString **a_prop) ; + +enum CRStatus cr_prop_list_lookup_prop (CRPropList *a_this, + CRString *a_prop, + CRPropList**a_pair) ; + +CRPropList * cr_prop_list_get_next (CRPropList *a_this) ; + +CRPropList * cr_prop_list_get_prev (CRPropList *a_this) ; + +enum CRStatus cr_prop_list_set_decl (CRPropList *a_this, + CRDeclaration *a_decl); + +enum CRStatus cr_prop_list_get_decl (CRPropList *a_this, + CRDeclaration **a_decl) ; + +CRPropList * cr_prop_list_unlink (CRPropList *a_this, + CRPropList *a_pair) ; + +void cr_prop_list_destroy (CRPropList *a_this) ; + +G_END_DECLS + +#endif /*__CR_PROP_LIST_H__*/ diff --git a/src/libcroco/cr-pseudo.c b/src/libcroco/cr-pseudo.c new file mode 100644 index 000000000..8e036a30f --- /dev/null +++ b/src/libcroco/cr-pseudo.c @@ -0,0 +1,153 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli + * See COPYRIGHTS file for copyright information. + */ + +#include "cr-pseudo.h" + +/** + *@file + *The definition of the #CRPseudo class. + */ + +/** + *Constructor of the #CRPseudo class. + *@return the newly build instance. + */ +CRPseudo * +cr_pseudo_new (void) +{ + CRPseudo *result = NULL; + + result = g_malloc0 (sizeof (CRPseudo)); + + return result; +} + +guchar * +cr_pseudo_to_string (CRPseudo * a_this) +{ + guchar *result = NULL; + GString *str_buf = NULL; + + g_return_val_if_fail (a_this, NULL); + + str_buf = g_string_new (NULL); + + if (a_this->type == IDENT_PSEUDO) { + guchar *name = NULL; + + if (a_this->name == NULL) { + goto error; + } + + name = g_strndup (a_this->name->stryng->str, + a_this->name->stryng->len); + + if (name) { + g_string_append (str_buf, name); + g_free (name); + name = NULL; + } + } else if (a_this->type == FUNCTION_PSEUDO) { + guchar *name = NULL, + *arg = NULL; + + if (a_this->name == NULL) + goto error; + + name = g_strndup (a_this->name->stryng->str, + a_this->name->stryng->len); + + if (a_this->extra) { + arg = g_strndup (a_this->extra->stryng->str, + a_this->extra->stryng->len); + } + + if (name) { + g_string_append_printf (str_buf, "%s(", name); + g_free (name); + name = NULL; + + if (arg) { + g_string_append (str_buf, arg); + g_free (arg); + arg = NULL; + } + + g_string_append_c (str_buf, ')'); + } + } + + if (str_buf) { + result = str_buf->str; + g_string_free (str_buf, FALSE); + str_buf = NULL; + } + + return result; + + error: + g_string_free (str_buf, TRUE); + return NULL; +} + +/** + *Dumps the pseudo to a file. + *@param a_this the current instance of pseudo + *@param a_fp the destination file pointer. + */ +void +cr_pseudo_dump (CRPseudo * a_this, FILE * a_fp) +{ + guchar *tmp_str = NULL; + + if (a_this) { + tmp_str = cr_pseudo_to_string (a_this); + if (tmp_str) { + fprintf (a_fp, "%s", tmp_str); + g_free (tmp_str); + tmp_str = NULL; + } + } +} + +/** + *destructor of the #CRPseudo class. + *@param a_this the current instance to destroy. + */ +void +cr_pseudo_destroy (CRPseudo * a_this) +{ + g_return_if_fail (a_this); + + if (a_this->name) { + cr_string_destroy (a_this->name); + a_this->name = NULL; + } + + if (a_this->extra) { + cr_string_destroy (a_this->extra); + a_this->extra = NULL; + } + + g_free (a_this); +} diff --git a/src/libcroco/cr-pseudo.h b/src/libcroco/cr-pseudo.h new file mode 100644 index 000000000..6de6c9e21 --- /dev/null +++ b/src/libcroco/cr-pseudo.h @@ -0,0 +1,64 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * See COPYRIGHTS file for copyright information + */ + +#ifndef __CR_PSEUDO_H__ +#define __CR_PSEUDO_H__ + +#include +#include +#include "cr-attr-sel.h" +#include "cr-parsing-location.h" + +G_BEGIN_DECLS + +enum CRPseudoType +{ + IDENT_PSEUDO = 0, + FUNCTION_PSEUDO +} ; + +typedef struct _CRPseudo CRPseudo ; + +/** + *The CRPseudo Class. + *Abstract a "pseudo" as defined by the css2 spec + *in appendix D.1 . + */ +struct _CRPseudo +{ + enum CRPseudoType type ; + CRString *name ; + CRString *extra ; + CRParsingLocation location ; +} ; + +CRPseudo * cr_pseudo_new (void) ; + +guchar * cr_pseudo_to_string (CRPseudo *a_this) ; + +void cr_pseudo_dump (CRPseudo *a_this, FILE *a_fp) ; + +void cr_pseudo_destroy (CRPseudo *a_this) ; + +G_END_DECLS + +#endif /*__CR_PSEUDO_H__*/ diff --git a/src/libcroco/cr-rgb.c b/src/libcroco/cr-rgb.c new file mode 100644 index 000000000..4bda1968e --- /dev/null +++ b/src/libcroco/cr-rgb.c @@ -0,0 +1,621 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli + * See COPYRIGHTS file for copyrights information. + */ + + +#include +#include +#include "cr-rgb.h" +#include "cr-term.h" +#include "cr-parser.h" + +static CRRgb gv_standard_colors[] = { + {"aliceblue", 240, 248, 255, FALSE, FALSE, FALSE, {0,0,0}}, + {"antiquewhite", 250, 235, 215, FALSE, FALSE, FALSE, {0,0,0}}, + {"aqua", 0, 255, 255, FALSE, FALSE, FALSE, {0,0,0}}, + {"aquamarine", 127, 255, 212, FALSE, FALSE, FALSE, {0,0,0}}, + {"azure", 240, 255, 255, FALSE, FALSE, FALSE, {0,0,0}}, + {"beige", 245, 245, 220, FALSE, FALSE, FALSE, {0,0,0}}, + {"bisque", 255, 228, 196, FALSE, FALSE, FALSE, {0,0,0}}, + {"black", 0, 0, 0, FALSE, FALSE, FALSE, {0,0,0}}, + {"blanchedalmond", 255, 235, 205, FALSE, FALSE, FALSE, {0,0,0}}, + {"blue", 0, 0, 255, FALSE, FALSE, FALSE, {0,0,0}}, + {"blueviolet", 138, 43, 226, FALSE, FALSE, FALSE, {0,0,0}}, + {"brown", 165, 42, 42, FALSE, FALSE, FALSE, {0,0,0}}, + {"burlywood", 222, 184, 135, FALSE, FALSE, FALSE, {0,0,0}}, + {"cadetblue", 95, 158, 160, FALSE, FALSE, FALSE, {0,0,0}}, + {"chartreuse", 127, 255, 0, FALSE, FALSE, FALSE, {0,0,0}}, + {"chocolate", 210, 105, 30, FALSE, FALSE, FALSE, {0,0,0}}, + {"coral", 255, 127, 80, FALSE, FALSE, FALSE, {0,0,0}}, + {"cornflowerblue", 100, 149, 237, FALSE, FALSE, FALSE, {0,0,0}}, + {"cornsilk", 255, 248, 220, FALSE, FALSE, FALSE, {0,0,0}}, + {"crimson", 220, 20, 60, FALSE, FALSE, FALSE, {0,0,0}}, + {"cyan", 0, 255, 255, FALSE, FALSE, FALSE, {0,0,0}}, + {"darkblue", 0, 0, 139, FALSE, FALSE, FALSE, {0,0,0}}, + {"darkcyan", 0, 139, 139, FALSE, FALSE, FALSE, {0,0,0}}, + {"darkgoldenrod", 184, 134, 11, FALSE, FALSE, FALSE, {0,0,0}}, + {"darkgray", 169, 169, 169, FALSE, FALSE, FALSE, {0,0,0}}, + {"darkgreen", 0, 100, 0, FALSE, FALSE, FALSE, {0,0,0}}, + {"darkgrey", 169, 169, 169, FALSE, FALSE, FALSE, {0,0,0}}, + {"darkkhaki", 189, 183, 107, FALSE, FALSE, FALSE, {0,0,0}}, + {"darkmagenta", 139, 0, 139, FALSE, FALSE, FALSE, {0,0,0}}, + {"darkolivegreen", 85, 107, 47, FALSE, FALSE, FALSE, {0,0,0}}, + {"darkorange", 255, 140, 0, FALSE, FALSE, FALSE, {0,0,0}}, + {"darkorchid", 153, 50, 204, FALSE, FALSE, FALSE, {0,0,0}}, + {"darkred", 139, 0, 0, FALSE, FALSE, FALSE, {0,0,0}}, + {"darksalmon", 233, 150, 122, FALSE, FALSE, FALSE, {0,0,0}}, + {"darkseagreen", 143, 188, 143, FALSE, FALSE, FALSE, {0,0,0}}, + {"darkslateblue", 72, 61, 139, FALSE, FALSE, FALSE, {0,0,0}}, + {"darkslategray", 47, 79, 79, FALSE, FALSE, FALSE, {0,0,0}}, + {"darkslategrey", 47, 79, 79, FALSE, FALSE, FALSE, {0,0,0}}, + {"darkturquoise", 0, 206, 209, FALSE, FALSE, FALSE, {0,0,0}}, + {"darkviolet", 148, 0, 211, FALSE, FALSE, FALSE, {0,0,0}}, + {"deeppink", 255, 20, 147, FALSE, FALSE, FALSE, {0,0,0}}, + {"deepskyblue", 0, 191, 255, FALSE, FALSE, FALSE, {0,0,0}}, + {"dimgray", 105, 105, 105, FALSE, FALSE, FALSE, {0,0,0}}, + {"dimgrey", 105, 105, 105, FALSE, FALSE, FALSE, {0,0,0}}, + {"dodgerblue", 30, 144, 255, FALSE, FALSE, FALSE, {0,0,0}}, + {"firebrick", 178, 34, 34, FALSE, FALSE, FALSE, {0,0,0}}, + {"floralwhite", 255, 250, 240, FALSE, FALSE, FALSE, {0,0,0}}, + {"forestgreen", 34, 139, 34, FALSE, FALSE, FALSE, {0,0,0}}, + {"fuchsia", 255, 0, 255, FALSE, FALSE, FALSE, {0,0,0}}, + {"gainsboro", 220, 220, 220, FALSE, FALSE, FALSE, {0,0,0}}, + {"ghostwhite", 248, 248, 255, FALSE, FALSE, FALSE, {0,0,0}}, + {"gold", 255, 215, 0, FALSE, FALSE, FALSE, {0,0,0}}, + {"goldenrod", 218, 165, 32, FALSE, FALSE, FALSE, {0,0,0}}, + {"gray", 128, 128, 128, FALSE, FALSE, FALSE, {0,0,0}}, + {"grey", 128, 128, 128, FALSE, FALSE, FALSE, {0,0,0}}, + {"green", 0, 128, 0, FALSE, FALSE, FALSE, {0,0,0}}, + {"greenyellow", 173, 255, 47, FALSE, FALSE, FALSE, {0,0,0}}, + {"honeydew", 240, 255, 240, FALSE, FALSE, FALSE, {0,0,0}}, + {"hotpink", 255, 105, 180, FALSE, FALSE, FALSE, {0,0,0}}, + {"indianred", 205, 92, 92, FALSE, FALSE, FALSE, {0,0,0}}, + {"indigo", 75, 0, 130, FALSE, FALSE, FALSE, {0,0,0}}, + {"ivory", 255, 255, 240, FALSE, FALSE, FALSE, {0,0,0}}, + {"khaki", 240, 230, 140, FALSE, FALSE, FALSE, {0,0,0}}, + {"lavender", 230, 230, 250, FALSE, FALSE, FALSE, {0,0,0}}, + {"lavenderblush", 255, 240, 245, FALSE, FALSE, FALSE, {0,0,0}}, + {"lawngreen", 124, 252, 0, FALSE, FALSE, FALSE, {0,0,0}}, + {"lemonchiffon", 255, 250, 205, FALSE, FALSE, FALSE, {0,0,0}}, + {"lightblue", 173, 216, 230, FALSE, FALSE, FALSE, {0,0,0}}, + {"lightcoral", 240, 128, 128, FALSE, FALSE, FALSE, {0,0,0}}, + {"lightcyan", 224, 255, 255, FALSE, FALSE, FALSE, {0,0,0}}, + {"lightgoldenrodyellow", 250, 250, 210, FALSE, FALSE, FALSE, {0,0,0}}, + {"lightgray", 211, 211, 211, FALSE, FALSE, FALSE, {0,0,0}}, + {"lightgreen", 144, 238, 144, FALSE, FALSE, FALSE, {0,0,0}}, + {"lightgrey", 211, 211, 211, FALSE, FALSE, FALSE, {0,0,0}}, + {"lightpink", 255, 182, 193, FALSE, FALSE, FALSE, {0,0,0}}, + {"lightsalmon", 255, 160, 122, FALSE, FALSE, FALSE, {0,0,0}}, + {"lightseagreen", 32, 178, 170, FALSE, FALSE, FALSE, {0,0,0}}, + {"lightskyblue", 135, 206, 250, FALSE, FALSE, FALSE, {0,0,0}}, + {"lightslategray", 119, 136, 153, FALSE, FALSE, FALSE, {0,0,0}}, + {"lightslategrey", 119, 136, 153, FALSE, FALSE, FALSE, {0,0,0}}, + {"lightsteelblue", 176, 196, 222, FALSE, FALSE, FALSE, {0,0,0}}, + {"lightyellow", 255, 255, 224, FALSE, FALSE, FALSE, {0,0,0}}, + {"lime", 0, 255, 0, FALSE, FALSE, FALSE, {0,0,0}}, + {"limegreen", 50, 205, 50, FALSE, FALSE, FALSE, {0,0,0}}, + {"linen", 250, 240, 230, FALSE, FALSE, FALSE, {0,0,0}}, + {"magenta", 255, 0, 255, FALSE, FALSE, FALSE, {0,0,0}}, + {"maroon", 128, 0, 0, FALSE, FALSE, FALSE, {0,0,0}}, + {"mediumaquamarine", 102, 205, 170, FALSE, FALSE, FALSE, {0,0,0}}, + {"mediumblue", 0, 0, 205, FALSE, FALSE, FALSE, {0,0,0}}, + {"mediumorchid", 186, 85, 211, FALSE, FALSE, FALSE, {0,0,0}}, + {"mediumpurple", 147, 112, 219, FALSE, FALSE, FALSE, {0,0,0}}, + {"mediumseagreen", 60, 179, 113, FALSE, FALSE, FALSE, {0,0,0}}, + {"mediumslateblue", 123, 104, 238, FALSE, FALSE, FALSE, {0,0,0}}, + {"mediumspringgreen", 0, 250, 154, FALSE, FALSE, FALSE, {0,0,0}}, + {"mediumturquoise", 72, 209, 204, FALSE, FALSE, FALSE, {0,0,0}}, + {"mediumvioletred", 199, 21, 133, FALSE, FALSE, FALSE, {0,0,0}}, + {"midnightblue", 25, 25, 112, FALSE, FALSE, FALSE, {0,0,0}}, + {"mintcream", 245, 255, 250, FALSE, FALSE, FALSE, {0,0,0}}, + {"mistyrose", 255, 228, 225, FALSE, FALSE, FALSE, {0,0,0}}, + {"moccasin", 255, 228, 181, FALSE, FALSE, FALSE, {0,0,0}}, + {"navajowhite", 255, 222, 173, FALSE, FALSE, FALSE, {0,0,0}}, + {"navy", 0, 0, 128, FALSE, FALSE, FALSE, {0,0,0}}, + {"oldlace", 253, 245, 230, FALSE, FALSE, FALSE, {0,0,0}}, + {"olive", 128, 128, 0, FALSE, FALSE, FALSE, {0,0,0}}, + {"olivedrab", 107, 142, 35, FALSE, FALSE, FALSE, {0,0,0}}, + {"orange", 255, 165, 0, FALSE, FALSE, FALSE, {0,0,0}}, + {"orangered", 255, 69, 0, FALSE, FALSE, FALSE, {0,0,0}}, + {"orchid", 218, 112, 214, FALSE, FALSE, FALSE, {0,0,0}}, + {"palegoldenrod", 238, 232, 170, FALSE, FALSE, FALSE, {0,0,0}}, + {"palegreen", 152, 251, 152, FALSE, FALSE, FALSE, {0,0,0}}, + {"paleturquoise", 175, 238, 238, FALSE, FALSE, FALSE, {0,0,0}}, + {"palevioletred", 219, 112, 147, FALSE, FALSE, FALSE, {0,0,0}}, + {"papayawhip", 255, 239, 213, FALSE, FALSE, FALSE, {0,0,0}}, + {"peachpuff", 255, 218, 185, FALSE, FALSE, FALSE, {0,0,0}}, + {"peru", 205, 133, 63, FALSE, FALSE, FALSE, {0,0,0}}, + {"pink", 255, 192, 203, FALSE, FALSE, FALSE, {0,0,0}}, + {"plum", 221, 160, 221, FALSE, FALSE, FALSE, {0,0,0}}, + {"powderblue", 176, 224, 230, FALSE, FALSE, FALSE, {0,0,0}}, + {"purple", 128, 0, 128, FALSE, FALSE, FALSE, {0,0,0}}, + {"red", 255, 0, 0, FALSE, FALSE, FALSE, {0,0,0}}, + {"rosybrown", 188, 143, 143, FALSE, FALSE, FALSE, {0,0,0}}, + {"royalblue", 65, 105, 225, FALSE, FALSE, FALSE, {0,0,0}}, + {"saddlebrown", 139, 69, 19, FALSE, FALSE, FALSE, {0,0,0}}, + {"salmon", 250, 128, 114, FALSE, FALSE, FALSE, {0,0,0}}, + {"sandybrown", 244, 164, 96, FALSE, FALSE, FALSE, {0,0,0}}, + {"seagreen", 46, 139, 87, FALSE, FALSE, FALSE, {0,0,0}}, + {"seashell", 255, 245, 238, FALSE, FALSE, FALSE, {0,0,0}}, + {"sienna", 160, 82, 45, FALSE, FALSE, FALSE, {0,0,0}}, + {"silver", 192, 192, 192, FALSE, FALSE, FALSE, {0,0,0}}, + {"skyblue", 135, 206, 235, FALSE, FALSE, FALSE, {0,0,0}}, + {"slateblue", 106, 90, 205, FALSE, FALSE, FALSE, {0,0,0}}, + {"slategray", 112, 128, 144, FALSE, FALSE, FALSE, {0,0,0}}, + {"slategrey", 112, 128, 144, FALSE, FALSE, FALSE, {0,0,0}}, + {"snow", 255, 250, 250, FALSE, FALSE, FALSE, {0,0,0}}, + {"springgreen", 0, 255, 127, FALSE, FALSE, FALSE, {0,0,0}}, + {"steelblue", 70, 130, 180, FALSE, FALSE, FALSE, {0,0,0}}, + {"tan", 210, 180, 140, FALSE, FALSE, FALSE, {0,0,0}}, + {"teal", 0, 128, 128, FALSE, FALSE, FALSE, {0,0,0}}, + {"thistle", 216, 191, 216, FALSE, FALSE, FALSE, {0,0,0}}, + {"tomato", 255, 99, 71, FALSE, FALSE, FALSE, {0,0,0}}, + {"turquoise", 64, 224, 208, FALSE, FALSE, FALSE, {0,0,0}}, + {"violet", 238, 130, 238, FALSE, FALSE, FALSE, {0,0,0}}, + {"wheat", 245, 222, 179, FALSE, FALSE, FALSE, {0,0,0}}, + {"white", 255, 255, 255, FALSE, FALSE, FALSE, {0,0,0}}, + {"whitesmoke", 245, 245, 245, FALSE, FALSE, FALSE, {0,0,0}}, + {"yellow", 255, 255, 0, FALSE, FALSE, FALSE, {0,0,0}}, + {"yellowgreen", 154, 205, 50, FALSE, FALSE, FALSE, {0,0,0}}, + {"transparent", 255, 255, 255, FALSE, FALSE, TRUE, {0,0,0}} +}; + +/** + *The default constructor of #CRRgb. + *@return the newly built instance of #CRRgb + */ +CRRgb * +cr_rgb_new (void) +{ + CRRgb *result = NULL; + + result = g_try_malloc (sizeof (CRRgb)); + + if (result == NULL) { + cr_utils_trace_info ("No more memory"); + return NULL; + } + + memset (result, 0, sizeof (CRRgb)); + + return result; +} + +/** + *A constructor of #CRRgb. + *@param a_red the red component of the color. + *@param a_green the green component of the color. + *@param a_blue the blue component of the color. + *@param a_unit the unit of the rgb values. + *(either percentage or integer values) + *@return the newly built instance of #CRRgb. + */ +CRRgb * +cr_rgb_new_with_vals (gulong a_red, gulong a_green, + gulong a_blue, gboolean a_is_percentage) +{ + CRRgb *result = NULL; + + result = cr_rgb_new (); + + g_return_val_if_fail (result, NULL); + + result->red = a_red; + result->green = a_green; + result->blue = a_blue; + result->is_percentage = a_is_percentage; + + return result; +} + +/** + *Serializes the rgb into a zero terminated string. + *@param a_this the instance of #CRRgb to serialize. + *@return the zero terminated string containing the serialized + *rgb. MUST BE FREED by the caller using g_free(). + */ +guchar * +cr_rgb_to_string (CRRgb * a_this) +{ + guchar *result = NULL; + GString *str_buf = NULL; + + str_buf = g_string_new (NULL); + g_return_val_if_fail (str_buf, NULL); + + if (a_this->is_percentage == 1) { + g_string_append_printf (str_buf, "%ld", a_this->red); + + g_string_append (str_buf, "%, "); + + g_string_append_printf (str_buf, "%ld", a_this->green); + g_string_append (str_buf, "%, "); + + g_string_append_printf (str_buf, "%ld", a_this->blue); + g_string_append_c (str_buf, '%'); + } else { + g_string_append_printf (str_buf, "%ld", a_this->red); + g_string_append (str_buf, ", "); + + g_string_append_printf (str_buf, "%ld", a_this->green); + g_string_append (str_buf, ", "); + + g_string_append_printf (str_buf, "%ld", a_this->blue); + } + + if (str_buf) { + result = str_buf->str; + g_string_free (str_buf, FALSE); + } + + return result; +} + +/** + *Dumps the current instance of #CRRgb + *to a file. + *@param a_this the "this pointer" of + *the current instance of #CRRgb. + *@param a_fp the destination file pointer. + */ +void +cr_rgb_dump (CRRgb * a_this, FILE * a_fp) +{ + guchar *str = NULL; + + g_return_if_fail (a_this); + + str = cr_rgb_to_string (a_this); + + if (str) { + fprintf (a_fp, "%s", str); + g_free (str); + str = NULL; + } +} + +/** + *If the rgb values are expressed in percentage, + *compute their real value. + *@param a_this the current instance of #CRRgb + *@return + */ +enum CRStatus +cr_rgb_compute_from_percentage (CRRgb * a_this) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + if (a_this->is_percentage == FALSE) + return CR_OK; + a_this->red = a_this->red * 255 / 100; + a_this->green = a_this->green * 255 / 100; + a_this->blue = a_this->blue * 255 / 100; + a_this->is_percentage = FALSE; + return CR_OK; +} + +/** + *Sets rgb values to the RGB. + *@param a_this the current instance of #CRRgb. + *@param a_red the red value. + *@param a_green the green value. + *@param a_blue the blue value. + *@return CR_OK upon successful completion, an error code + *otherwise. + */ +enum CRStatus +cr_rgb_set (CRRgb * a_this, gulong a_red, + gulong a_green, gulong a_blue, gboolean a_is_percentage) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + if (a_is_percentage != FALSE) { + g_return_val_if_fail (a_red <= 100 + && a_green <= 100 + && a_blue <= 100, CR_BAD_PARAM_ERROR); + } + + a_this->is_percentage = a_is_percentage; + + a_this->red = a_red; + a_this->green = a_green; + a_this->blue = a_blue; + a_this->inherit = FALSE ; + a_this->is_transparent = FALSE ; + return CR_OK; +} + +/** + *sets the value of the rgb to inherit. + *Look at the css spec from chapter 6.1 to 6.2 to understand + *the meaning of "inherit". + *@param a_this the current instance of #CRRgb + * + */ +enum CRStatus +cr_rgb_set_to_inherit (CRRgb *a_this, gboolean a_inherit) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR) ; + + a_this->inherit = a_inherit ; + + return CR_OK ; +} + +gboolean +cr_rgb_is_set_to_inherit (CRRgb *a_this) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR) ; + + return a_this->inherit ; +} + +/** + *Tests if the the rgb is set to the + *value "transparent" or not. + *@param a_this the current instance of + *#CRRgb + *@return TRUE if the rgb has been set to + *transparent, FALSE otherwise. + */ +gboolean +cr_rgb_is_set_to_transparent (CRRgb *a_this) +{ + g_return_val_if_fail (a_this, FALSE) ; + return a_this->is_transparent ; +} + + +/** + *Sets the rgb to the "transparent" value (or not) + *@param a_this the current instance of #CRRgb + *@param a_is_transparent set to transparent or not. + */ +enum CRStatus +cr_rgb_set_to_transparent (CRRgb *a_this, + gboolean a_is_transparent) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR) ; + a_this->is_transparent = a_is_transparent ; + return CR_OK ; +} + +/** + *Sets the rgb from an other one. + *@param a_this the current instance of #CRRgb. + *@param a_rgb the rgb to "copy" + *@return CR_OK upon successful completion, an error code otherwise. + */ +enum CRStatus +cr_rgb_set_from_rgb (CRRgb * a_this, CRRgb * a_rgb) +{ + g_return_val_if_fail (a_this && a_rgb, CR_BAD_PARAM_ERROR); + + cr_rgb_copy (a_this, a_rgb) ; + + return CR_OK; +} + +enum CRStatus +cr_rgb_set_from_name (CRRgb * a_this, const guchar * a_color_name) +{ + gulong i = 0; + enum CRStatus status = CR_OK; + + g_return_val_if_fail (a_this && a_color_name, CR_BAD_PARAM_ERROR); + + for (i = 0; i < sizeof (gv_standard_colors); i++) { + if (!strcmp (a_color_name, gv_standard_colors[i].name)) { + cr_rgb_set_from_rgb (a_this, &gv_standard_colors[i]); + break; + } + } + + if (i < sizeof (gv_standard_colors)) + status = CR_OK; + else + status = CR_UNKNOWN_TYPE_ERROR; + + return status; +} + +enum CRStatus +cr_rgb_set_from_hex_str (CRRgb * a_this, const guchar * a_hex) +{ + enum CRStatus status = CR_OK; + gulong i = 0; + guchar colors[3] = { 0 }; + + g_return_val_if_fail (a_this && a_hex, CR_BAD_PARAM_ERROR); + + if (strlen (a_hex) == 3) { + for (i = 0; i < 3; i++) { + if (a_hex[i] >= '0' && a_hex[i] <= '9') { + colors[i] = a_hex[i] - '0'; + colors[i] = (colors[i] << 4) | colors[i]; + } else if (a_hex[i] >= 'a' && a_hex[i] <= 'z') { + colors[i] = 10 + a_hex[i] - 'a'; + colors[i] = (colors[i] << 4) | colors[i]; + } else if (a_hex[i] >= 'A' && a_hex[i] <= 'Z') { + colors[i] = 10 + a_hex[i] - 'A'; + colors[i] = (colors[i] << 4) | colors[i]; + } else { + status = CR_UNKNOWN_TYPE_ERROR; + } + } + } else if (strlen (a_hex) == 6) { + for (i = 0; i < 6; i++) { + if (a_hex[i] >= '0' && a_hex[i] <= '9') { + colors[i / 2] <<= 4; + colors[i / 2] |= a_hex[i] - '0'; + status = CR_OK; + } else if (a_hex[i] >= 'a' && a_hex[i] <= 'z') { + colors[i / 2] <<= 4; + colors[i / 2] |= 10 + a_hex[i] - 'a'; + status = CR_OK; + } else if (a_hex[i] >= 'A' && a_hex[i] <= 'Z') { + colors[i / 2] <<= 4; + colors[i / 2] |= 10 + a_hex[i] - 'A'; + status = CR_OK; + } else { + status = CR_UNKNOWN_TYPE_ERROR; + } + } + } else { + status = CR_UNKNOWN_TYPE_ERROR; + } + + if (status == CR_OK) { + status = cr_rgb_set (a_this, colors[0], + colors[1], colors[2], FALSE); + cr_rgb_set_to_transparent (a_this, FALSE) ; + } + return status; +} + +/** + *Set the rgb from a terminal symbol + *@param a_this the instance of #CRRgb to set + *@param a_value the terminal from which to set + */ +enum CRStatus +cr_rgb_set_from_term (CRRgb *a_this, const struct _CRTerm *a_value) +{ + enum CRStatus status = CR_OK ; + g_return_val_if_fail (a_this && a_value, + CR_BAD_PARAM_ERROR) ; + + switch(a_value->type) { + case TERM_RGB: + if (a_value->content.rgb) { + cr_rgb_set_from_rgb + (a_this, a_value->content.rgb) ; + } + break ; + case TERM_IDENT: + if (a_value->content.str + && a_value->content.str->stryng + && a_value->content.str->stryng->str) { + if (!strncmp ("inherit", + a_value->content.str->stryng->str, + sizeof ("inherit")-1)) { + a_this->inherit = TRUE; + a_this->is_transparent = FALSE ; + } else { + status = cr_rgb_set_from_name + (a_this, + a_value->content.str->stryng->str) ; + } + } else { + cr_utils_trace_info + ("a_value has NULL string value") ; + } + break ; + case TERM_HASH: + if (a_value->content.str + && a_value->content.str->stryng + && a_value->content.str->stryng->str) { + status = cr_rgb_set_from_hex_str + (a_this, + a_value->content.str->stryng->str) ; + } else { + cr_utils_trace_info + ("a_value has NULL string value") ; + } + break ; + default: + status = CR_UNKNOWN_TYPE_ERROR ; + } + return status ; +} + +enum CRStatus +cr_rgb_copy (CRRgb *a_dest, CRRgb*a_src) +{ + g_return_val_if_fail (a_dest && a_src, + CR_BAD_PARAM_ERROR) ; + + memcpy (a_dest, a_src, sizeof (CRRgb)) ; + return CR_OK ; +} + +/** + *Destructor of #CRRgb. + *@param a_this the "this pointer" of the + *current instance of #CRRgb. + */ +void +cr_rgb_destroy (CRRgb * a_this) +{ + g_return_if_fail (a_this); + g_free (a_this); +} + +/** + *Parses a text buffer that contains a rgb color + * + *@param a_str a string that contains a color description + *@param a_enc the encoding of a_str + *@return the parsed color, or NULL in case of error + */ +CRRgb *cr_rgb_parse_from_buf (const guchar *a_str, + enum CREncoding a_enc) +{ + enum CRStatus status = CR_OK ; + CRTerm *value = NULL ; + CRParser * parser = NULL; + CRRgb *result = NULL; + + g_return_val_if_fail (a_str, NULL); + + parser = cr_parser_new_from_buf ((guchar*)a_str, strlen (a_str), + a_enc, FALSE) ; + + g_return_val_if_fail (parser, NULL); + + status = cr_parser_try_to_skip_spaces_and_comments (parser) ; + if (status != CR_OK) + goto cleanup; + + status = cr_parser_parse_term (parser, &value); + if (status != CR_OK) + goto cleanup; + + result = cr_rgb_new (); + if (!result) + goto cleanup; + + status = cr_rgb_set_from_term (result, value); + +cleanup: + if (parser) { + cr_parser_destroy (parser); + parser = NULL; + } + if (value) { + cr_term_destroy(value); + value = NULL; + } + return result ; +} + + + diff --git a/src/libcroco/cr-rgb.h b/src/libcroco/cr-rgb.h new file mode 100644 index 000000000..f6b4e8aa1 --- /dev/null +++ b/src/libcroco/cr-rgb.h @@ -0,0 +1,94 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * see COPYRIGHTS file for copyright information. + */ + +#ifndef __CR_RGB_H__ +#define __CR_RGB_H__ + +#include +#include +#include "cr-utils.h" +#include "cr-parsing-location.h" + +G_BEGIN_DECLS + + +typedef struct _CRRgb CRRgb ; +struct _CRRgb +{ + /* + *the unit of the rgb. + *Either NO_UNIT (integer) or + *UNIT_PERCENTAGE (percentage). + */ + const guchar *name ; + glong red ; + glong green ; + glong blue ; + gboolean is_percentage ; + gboolean inherit ; + gboolean is_transparent ; + CRParsingLocation location ; +} ; + +CRRgb * cr_rgb_new (void) ; + +CRRgb * cr_rgb_new_with_vals (gulong a_red, gulong a_green, + gulong a_blue, gboolean a_is_percentage) ; + +CRRgb *cr_rgb_parse_from_buf(const guchar *a_str, + enum CREncoding a_enc); + +enum CRStatus cr_rgb_compute_from_percentage (CRRgb *a_this) ; + +enum CRStatus cr_rgb_set (CRRgb *a_this, gulong a_red, + gulong a_green, gulong a_blue, + gboolean a_is_percentage) ; + +enum CRStatus cr_rgb_copy (CRRgb *a_dest, CRRgb*a_src) ; + +enum CRStatus cr_rgb_set_to_inherit (CRRgb *a_this, gboolean a_inherit) ; + +gboolean cr_rgb_is_set_to_inherit (CRRgb *a_this) ; + +gboolean cr_rgb_is_set_to_transparent (CRRgb *a_this) ; + +enum CRStatus cr_rgb_set_to_transparent (CRRgb *a_this, + gboolean a_is_transparent) ; +enum CRStatus cr_rgb_set_from_rgb (CRRgb *a_this, CRRgb *a_rgb) ; + +enum CRStatus cr_rgb_set_from_name (CRRgb *a_this, const guchar *a_color_name) ; + +enum CRStatus cr_rgb_set_from_hex_str (CRRgb *a_this, const guchar * a_hex_value) ; + +struct _CRTerm; + +enum CRStatus cr_rgb_set_from_term (CRRgb *a_this, const struct _CRTerm *a_value); + +guchar * cr_rgb_to_string (CRRgb *a_this) ; + +void cr_rgb_dump (CRRgb *a_this, FILE *a_fp) ; + +void cr_rgb_destroy (CRRgb *a_this) ; + +G_END_DECLS + +#endif /*__CR_RGB_H__*/ diff --git a/src/libcroco/cr-sel-eng.c b/src/libcroco/cr-sel-eng.c new file mode 100644 index 000000000..f010efea0 --- /dev/null +++ b/src/libcroco/cr-sel-eng.c @@ -0,0 +1,1541 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser + * General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * See COPYRIGHTS file for copyright informations. + */ + +#include +#include "cr-sel-eng.h" +#include "cr-node-iface.h" + +/** + *@file: + *The definition of the #CRSelEng class. + *The #CRSelEng is actually the "Selection Engine" + *class. This is highly experimental for at the moment and + *its api is very likely to change in a near future. + */ + +#define PRIVATE(a_this) (a_this)->priv + +struct CRPseudoClassSelHandlerEntry { + char *name; + enum CRPseudoType type; + CRPseudoClassSelectorHandler handler; +}; + +struct _CRSelEngPriv { + /*not used yet */ + gboolean case_sensitive; + + CRNodeIface const *node_iface; + CRStyleSheet *sheet; + /** + *where to store the next statement + *to be visited so that we can remember + *it from one method call to another. + */ + CRStatement *cur_stmt; + GList *pcs_handlers; + gint pcs_handlers_size; +} ; + +static gboolean class_add_sel_matches_node (CRAdditionalSel * a_add_sel, + CRNodeIface const * a_node_iface, CRXMLNodePtr a_node); + +static gboolean id_add_sel_matches_node (CRAdditionalSel * a_add_sel, + CRNodeIface const * a_node_iface, CRXMLNodePtr a_node); + +static gboolean attr_add_sel_matches_node (CRAdditionalSel * a_add_sel, + CRNodeIface const * a_node_iface, CRXMLNodePtr a_node); + +static enum CRStatus sel_matches_node_real (CRSelEng * a_this, + CRSimpleSel * a_sel, + CRXMLNodePtr a_node, + gboolean * a_result, + gboolean a_eval_sel_list_from_end, + gboolean a_recurse); + +static enum CRStatus cr_sel_eng_get_matched_rulesets_real (CRSelEng * a_this, + CRStyleSheet * + a_stylesheet, + CRXMLNodePtr a_node, + CRStatement ** + a_rulesets, + gulong * a_len); + +static enum CRStatus put_css_properties_in_props_list (CRPropList ** a_props, + CRStatement * + a_ruleset); + +static gboolean pseudo_class_add_sel_matches_node (CRSelEng * a_this, + CRAdditionalSel * + a_add_sel, + CRXMLNodePtr a_node); + +static gboolean lang_pseudo_class_handler (CRSelEng * a_this, + CRAdditionalSel * a_sel, + CRXMLNodePtr a_node); + +static gboolean first_child_pseudo_class_handler (CRSelEng * a_this, + CRAdditionalSel * a_sel, + CRXMLNodePtr a_node); + +static CRXMLNodePtr get_next_element_node (CRNodeIface const * a_node_iface, CRXMLNodePtr a_node); + +static CRXMLNodePtr get_next_child_element_node (CRNodeIface const * a_node_iface, CRXMLNodePtr a_node); + +static CRXMLNodePtr get_prev_element_node (CRNodeIface const * a_node_iface, CRXMLNodePtr a_node); + +static CRXMLNodePtr get_next_parent_element_node (CRNodeIface const * a_node_iface, CRXMLNodePtr a_node); + +void +cr_sel_eng_set_node_iface (CRSelEng *const a_this, CRNodeIface const *const a_node_iface) +{ + /* Allow NULL: the caller may be just ensuring that the previous node_iface + value doesn't get used until next cr_sel_eng_set_node_iface call. */ + PRIVATE(a_this)->node_iface = a_node_iface; +} + +static gboolean +lang_pseudo_class_handler (CRSelEng *const a_this, + CRAdditionalSel * a_sel, CRXMLNodePtr a_node) +{ + CRNodeIface const *node_iface; + CRXMLNodePtr node = a_node; + gboolean result = FALSE; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_sel && a_sel->content.pseudo + && a_sel->content.pseudo + && a_sel->content.pseudo->name + && a_sel->content.pseudo->name->stryng + && a_node, FALSE); + + node_iface = PRIVATE(a_this)->node_iface; + + if (strncmp (a_sel->content.pseudo->name->stryng->str, + "lang", 4) + || !a_sel->content.pseudo->type == FUNCTION_PSEUDO) { + cr_utils_trace_info ("This handler is for :lang only"); + return FALSE; + } + /*lang code should exist and be at least of length 2 */ + if (!a_sel->content.pseudo->extra + || !a_sel->content.pseudo->extra->stryng + || a_sel->content.pseudo->extra->stryng->len < 2) + return FALSE; + for (; node; node = get_next_parent_element_node (node_iface, node)) { + char *val = node_iface->getProp (node, "lang"); + if (val) { + if (!strncmp (val, + a_sel->content.pseudo->extra->stryng->str, + a_sel->content.pseudo->extra->stryng->len)) { + result = TRUE; + break; + } + node_iface->freePropVal (val); + val = NULL; + } + } + + return result; +} + +static gboolean +first_child_pseudo_class_handler (CRSelEng *const a_this, + CRAdditionalSel * a_sel, CRXMLNodePtr const a_node) +{ + CRNodeIface const *node_iface = NULL; + CRXMLNodePtr node = NULL, parent = NULL; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_sel && a_sel->content.pseudo + && a_sel->content.pseudo + && a_sel->content.pseudo->name + && a_sel->content.pseudo->name->stryng + && a_node, FALSE); + + if (strcmp (a_sel->content.pseudo->name->stryng->str, + "first-child") + || !a_sel->content.pseudo->type == IDENT_PSEUDO) { + cr_utils_trace_info ("This handler is for :first-child only"); + return FALSE; + } + node_iface = PRIVATE(a_this)->node_iface; + parent = node_iface->getParentNode (a_node); + if (!parent) + return FALSE; + node = get_next_child_element_node (node_iface, parent); + return (node == a_node); +} + +static gboolean +pseudo_class_add_sel_matches_node (CRSelEng * a_this, + CRAdditionalSel * a_add_sel, + CRXMLNodePtr a_node) +{ + enum CRStatus status = CR_OK; + CRPseudoClassSelectorHandler handler = NULL; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_add_sel + && a_add_sel->content.pseudo + && a_add_sel->content.pseudo->name + && a_add_sel->content.pseudo->name->stryng + && a_add_sel->content.pseudo->name->stryng->str + && a_node, FALSE); + + status = cr_sel_eng_get_pseudo_class_selector_handler + (a_this, a_add_sel->content.pseudo->name->stryng->str, + a_add_sel->content.pseudo->type, &handler); + if (status != CR_OK || !handler) + return FALSE; + + return handler (a_this, a_add_sel, a_node); +} + +/** + *@param a_add_sel the class additional selector to consider. + *@param a_node the xml node to consider. + *@return TRUE if the class additional selector matches + *the xml node given in argument, FALSE otherwise. + */ +static gboolean +class_add_sel_matches_node (CRAdditionalSel * a_add_sel, + CRNodeIface const * a_node_iface, CRXMLNodePtr a_node) +{ + gboolean result = FALSE; + char *klass = NULL; + + g_return_val_if_fail (a_add_sel + && a_add_sel->type == CLASS_ADD_SELECTOR + && a_add_sel->content.class_name + && a_add_sel->content.class_name->stryng + && a_add_sel->content.class_name->stryng->str + && a_node, FALSE); + + klass = a_node_iface->getProp (a_node, "class"); + if (klass) { + char const *cur; + for (cur = klass; cur && *cur; cur++) { + while (cur && *cur + && cr_utils_is_white_space (*cur) + == TRUE) + cur++; + + if (!strncmp (cur, + a_add_sel->content.class_name->stryng->str, + a_add_sel->content.class_name->stryng->len)) { + cur += a_add_sel->content.class_name->stryng->len; + if ((cur && !*cur) + || cr_utils_is_white_space (*cur) == TRUE) + result = TRUE; + } + if (cur && !*cur) + break ; + } + a_node_iface->freePropVal (klass); + klass = NULL; + } + return result; + +} + +/** + *@return TRUE if the additional attribute selector matches + *the current xml node given in argument, FALSE otherwise. + *@param a_add_sel the additional attribute selector to consider. + *@param a_node the xml node to consider. + */ +static gboolean +id_add_sel_matches_node (CRAdditionalSel * a_add_sel, + CRNodeIface const * a_node_iface, CRXMLNodePtr a_node) +{ + gboolean result = FALSE; + char *id = NULL; + + g_return_val_if_fail (a_add_sel + && a_add_sel->type == ID_ADD_SELECTOR + && a_add_sel->content.id_name + && a_add_sel->content.id_name->stryng + && a_add_sel->content.id_name->stryng->str + && a_node, FALSE); + g_return_val_if_fail (a_add_sel + && a_add_sel->type == ID_ADD_SELECTOR + && a_node, FALSE); + + id = a_node_iface->getProp (a_node, "id"); + if (id) { + if (!strncmp (id, a_add_sel->content.id_name->stryng->str, + a_add_sel->content.id_name->stryng->len)) { + result = TRUE; + } + a_node_iface->freePropVal (id); + id = NULL; + } + return result; +} + +/** + *Returns TRUE if the instance of #CRAdditional selector matches + *the node given in parameter, FALSE otherwise. + *@param a_add_sel the additional selector to evaluate. + *@param a_node the xml node against whitch the selector is to + *be evaluated + *return TRUE if the additional selector matches the current xml node + *FALSE otherwise. + */ +static gboolean +attr_add_sel_matches_node (CRAdditionalSel * a_add_sel, + CRNodeIface const * a_node_iface, CRXMLNodePtr a_node) +{ + CRAttrSel *cur_sel = NULL; + + g_return_val_if_fail (a_add_sel + && a_add_sel->type == ATTRIBUTE_ADD_SELECTOR + && a_node, FALSE); + + for (cur_sel = a_add_sel->content.attr_sel; + cur_sel; cur_sel = cur_sel->next) { + if (!cur_sel->name + || !cur_sel->name->stryng + || !cur_sel->name->stryng->str) + return FALSE; + + char *const value = a_node_iface->getProp (a_node, cur_sel->name->stryng->str); + if (!value) + goto free_and_return_false; + + switch (cur_sel->match_way) { + case SET: + break; + + case EQUALS: + if (!cur_sel->value + || !cur_sel->value->stryng + || !cur_sel->value->stryng->str) { + goto free_and_return_false; + } + if (strcmp + (value, + cur_sel->value->stryng->str)) { + goto free_and_return_false; + } + break; + + case INCLUDES: + { + char const *ptr1 = NULL, + *ptr2 = NULL, + *cur = NULL; + gboolean found = FALSE; + + /* + *here, make sure value is a space + *separated list of "words", where one + *value is exactly cur_sel->value->str + */ + for (cur = value; *cur; cur++) { + /* + *set ptr1 to the first non white space + *char addr. + */ + while (cr_utils_is_white_space (*cur) + && *cur) + cur++; + if (!*cur) + break; + ptr1 = cur; + + /* + *set ptr2 to the end the word. + */ + while (!cr_utils_is_white_space (*cur) + && *cur) + cur++; + cur--; + ptr2 = cur; + + if (!strncmp + (ptr1, + cur_sel->value->stryng->str, + ptr2 - ptr1 + 1)) { + found = TRUE; + break; + } + ptr1 = ptr2 = NULL; + } + + if (!found) { + goto free_and_return_false; + } + } + break; + + case DASHMATCH: + { + char const *ptr1 = NULL, + *ptr2 = NULL, + *cur = NULL; + gboolean found = FALSE; + + /* + *here, make sure value is an hyphen + *separated list of "words", each of which + *starting with "cur_sel->value->str" + */ + for (cur = value; *cur; cur++) { + if (*cur == '-') + cur++; + ptr1 = cur; + + while (*cur != '-' && *cur) + cur++; + cur--; + ptr2 = cur; + + if (g_strstr_len + (ptr1, ptr2 - ptr1 + 1, + cur_sel->value->stryng->str) + == ptr1) { + found = TRUE; + break; + } + } + + if (!found) { + goto free_and_return_false; + } + } + break; + default: + goto free_and_return_false; + } + + a_node_iface->freePropVal (value); + continue; + + free_and_return_false: + a_node_iface->freePropVal (value); + return FALSE; + } + + return TRUE; +} + +/** + *Evaluates if a given additional selector matches an xml node. + *@param a_add_sel the additional selector to consider. + *@param a_node the xml node to consider. + *@return TRUE is a_add_sel matches a_node, FALSE otherwise. + */ +static gboolean +additional_selector_matches_node (CRSelEng * a_this, + CRAdditionalSel * a_add_sel, + CRXMLNodePtr a_node) +{ + CRAdditionalSel *cur_add_sel = NULL, *tail = NULL ; + gboolean evaluated = FALSE ; + + for (tail = a_add_sel ; + tail && tail->next; + tail = tail->next) ; + + g_return_val_if_fail (tail, FALSE) ; + + for (cur_add_sel = tail ; + cur_add_sel ; + cur_add_sel = cur_add_sel->prev) { + + evaluated = TRUE ; + if (cur_add_sel->type == NO_ADD_SELECTOR) { + return FALSE; + } + + if (cur_add_sel->type == CLASS_ADD_SELECTOR + && cur_add_sel->content.class_name + && cur_add_sel->content.class_name->stryng + && cur_add_sel->content.class_name->stryng->str) { + if (!class_add_sel_matches_node (cur_add_sel, + PRIVATE(a_this)->node_iface, + a_node)) { + return FALSE; + } + continue ; + } else if (cur_add_sel->type == ID_ADD_SELECTOR + && cur_add_sel->content.id_name + && cur_add_sel->content.id_name->stryng + && cur_add_sel->content.id_name->stryng->str) { + if (!id_add_sel_matches_node (cur_add_sel, + PRIVATE(a_this)->node_iface, + a_node)) { + return FALSE; + } + continue ; + } else if (cur_add_sel->type == ATTRIBUTE_ADD_SELECTOR + && cur_add_sel->content.attr_sel) { + /* + *here, call a function that does the match + *against an attribute additionnal selector + *and an xml node. + */ + if (!attr_add_sel_matches_node (cur_add_sel, + PRIVATE(a_this)->node_iface, + a_node)) { + return FALSE; + } + continue ; + } else if (cur_add_sel->type == PSEUDO_CLASS_ADD_SELECTOR + && cur_add_sel->content.pseudo) { + if (pseudo_class_add_sel_matches_node + (a_this, cur_add_sel, a_node)) { + return TRUE; + } + return FALSE; + } + } + if (evaluated == TRUE) + return TRUE; + return FALSE ; +} + +static CRXMLNodePtr +get_next_element_node (CRNodeIface const * a_node_iface, CRXMLNodePtr a_node) +{ + CRXMLNodePtr cur_node = a_node; + + g_return_val_if_fail (a_node, NULL); + + do { + cur_node = a_node_iface->getNextSibling (cur_node); + } while (cur_node && !a_node_iface->isElementNode(cur_node)); + return cur_node; +} + +/* TODO: Consider renaming this to get_first_child_element_node. + (cf get_first_parent_element_node, which does getParent until element node + rather than getNextSibling). */ +static CRXMLNodePtr +get_next_child_element_node (CRNodeIface const * a_node_iface, CRXMLNodePtr a_node) +{ + CRXMLNodePtr cur_node = NULL; + + g_return_val_if_fail (a_node, NULL); + + cur_node = a_node_iface->getFirstChild (a_node); + if (!cur_node) + return cur_node; + if (a_node_iface->isElementNode (cur_node)) + return cur_node; + return get_next_element_node (a_node_iface, cur_node); +} + +static CRXMLNodePtr +get_prev_element_node (CRNodeIface const * a_node_iface, CRXMLNodePtr a_node) +{ + CRXMLNodePtr cur_node = a_node; + + g_return_val_if_fail (a_node, NULL); + + do { + cur_node = a_node_iface->getPrevSibling (cur_node); + } while (cur_node && !a_node_iface->isElementNode(cur_node)); + return cur_node; +} + +static CRXMLNodePtr +get_next_parent_element_node (CRNodeIface const * a_node_iface, CRXMLNodePtr a_node) +{ + CRXMLNodePtr cur_node = a_node; + + g_return_val_if_fail (a_node, NULL); + + do { + cur_node = a_node_iface->getParentNode (cur_node); + } while (cur_node && !a_node_iface->isElementNode (cur_node)); + return cur_node; +} + +/** + *Evaluate a selector (a simple selectors list) and says + *if it matches the xml node given in parameter. + *The algorithm used here is the following: + *Walk the combinator separated list of simple selectors backward, starting + *from the end of the list. For each simple selector, looks if + *if matches the current node. + * + *@param a_this the selection engine. + *@param a_sel the simple selection list. + *@param a_node the xml node. + *@param a_result out parameter. Set to true if the + *selector matches the xml node, FALSE otherwise. + *@param a_recurse if set to TRUE, the function will walk to + *the next simple selector (after the evaluation of the current one) + *and recursively evaluate it. Must be usually set to TRUE unless you + *know what you are doing. + */ +static enum CRStatus +sel_matches_node_real (CRSelEng * a_this, CRSimpleSel * a_sel, + CRXMLNodePtr a_node, gboolean * a_result, + gboolean a_eval_sel_list_from_end, + gboolean a_recurse) +{ + CRSimpleSel *cur_sel = NULL; + CRXMLNodePtr cur_node = NULL; + CRNodeIface const *node_iface = NULL; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_this && a_node + && a_result, CR_BAD_PARAM_ERROR); + + node_iface = PRIVATE(a_this)->node_iface; + *a_result = FALSE; + + if (!node_iface->isElementNode(a_node)) + return CR_OK; + + if (a_eval_sel_list_from_end == TRUE) { + /*go and get the last simple selector of the list */ + for (cur_sel = a_sel; + cur_sel && cur_sel->next; cur_sel = cur_sel->next) ; + } else { + cur_sel = a_sel; + } + + for (cur_node = a_node; cur_sel; cur_sel = cur_sel->prev) { + if (((cur_sel->type_mask & TYPE_SELECTOR) + && (cur_sel->name + && cur_sel->name->stryng + && cur_sel->name->stryng->str) + && (!strcmp (cur_sel->name->stryng->str, + node_iface->getLocalName(cur_node)))) + || (cur_sel->type_mask & UNIVERSAL_SELECTOR)) { + /* + *this simple selector + *matches the current xml node + *Let's see if the preceding + *simple selectors also match + *their xml node counterpart. + */ + if (cur_sel->add_sel) { + if (additional_selector_matches_node (a_this, cur_sel->add_sel, + cur_node) == TRUE) { + goto walk_a_step_in_expr; + } else { + goto done; + } + } else { + goto walk_a_step_in_expr; + } + } + if (!(cur_sel->type_mask & TYPE_SELECTOR) + && !(cur_sel->type_mask & UNIVERSAL_SELECTOR)) { + if (!cur_sel->add_sel) { + goto done; + } + if (additional_selector_matches_node + (a_this, cur_sel->add_sel, cur_node) + == TRUE) { + goto walk_a_step_in_expr; + } else { + goto done; + } + } else { + goto done ; + } + + walk_a_step_in_expr: + if (a_recurse == FALSE) { + *a_result = TRUE; + goto done; + } + + /* + *here, depending on the combinator of cur_sel + *choose the axis of the xml tree traversal + *and walk one step in the xml tree. + */ + if (!cur_sel->prev) + break; + + switch (cur_sel->combinator) { + case NO_COMBINATOR: + break; + + case COMB_WS: /*descendant selector */ + { + CRXMLNodePtr n = NULL; + enum CRStatus status = CR_OK; + gboolean matches = FALSE; + + /* + *walk the xml tree upward looking for a parent + *node that matches the preceding selector. + */ + for (n = node_iface->getParentNode (cur_node); + n; + n = node_iface->getParentNode (n)) { + status = sel_matches_node_real + (a_this, cur_sel->prev, + n, &matches, FALSE, TRUE); + + if (status != CR_OK) + goto done; + + if (matches == TRUE) { + cur_node = n ; + break; + } + } + + if (!n) { + /* + *didn't find any ancestor that matches + *the previous simple selector. + */ + goto done; + } + /* + *in this case, the preceding simple sel + *will have been interpreted twice, which + *is a cpu and mem waste ... I need to find + *another way to do this. Anyway, this is + *my first attempt to write this function and + *I am a bit clueless. + */ + break; + } + + case COMB_PLUS: + cur_node = get_prev_element_node (node_iface, cur_node); + if (!cur_node) + goto done; + break; + + case COMB_GT: + cur_node = get_next_parent_element_node (node_iface, cur_node); + if (!cur_node) + goto done; + break; + + default: + goto done; + } + continue; + } + + /* + *if we reached this point, it means the selector matches + *the xml node. + */ + *a_result = TRUE; + + done: + return CR_OK; +} + + +/** + *Returns array of the ruleset statements that matches the + *given xml node. + *The engine keeps in memory the last statement he + *visited during the match. So, the next call + *to this function will eventually return a rulesets list starting + *from the last ruleset statement visited during the previous call. + *The enable users to get matching rulesets in an incremental way. + *Note that for each statement returned, + *the engine calculates the specificity of the selector + *that matched the xml node and stores it in the "specifity" field + *of the statement structure. + * + *@param a_sel_eng the current selection engine + *@param a_node the xml node for which the request + *is being made. + *@param a_sel_list the list of selectors to perform the search in. + *@param a_rulesets in/out parameter. A pointer to the + *returned array of rulesets statements that match the xml node + *given in parameter. The caller allocates the array before calling this + *function. + *@param a_len in/out parameter the length (in sizeof (#CRStatement*)) + *of the returned array. + *(the length of a_rulesets, more precisely). + *The caller must set it to the length of a_ruleset prior to calling this + *function. In return, the function sets it to the length + *(in sizeof (#CRStatement)) of the actually returned CRStatement array. + *@return CR_OUTPUT_TOO_SHORT_ERROR if found more rulesets than the size + *of the a_rulesets array. In this case, the first *a_len rulesets found + *are put in a_rulesets, and a further call will return the following + *ruleset(s) following the same principle. + *@return CR_OK if all the rulesets found have been returned. In this + *case, *a_len is set to the actual number of ruleset found. + *@return CR_BAD_PARAM_ERROR in case any of the given parameter are + *bad (e.g null pointer). + *@return CR_ERROR if any other error occured. + */ +static enum CRStatus +cr_sel_eng_get_matched_rulesets_real (CRSelEng * a_this, + CRStyleSheet * a_stylesheet, + CRXMLNodePtr a_node, + CRStatement ** a_rulesets, + gulong * a_len) +{ + CRStatement *cur_stmt = NULL; + CRSelector *sel_list = NULL, + *cur_sel = NULL; + gboolean matches = FALSE; + enum CRStatus status = CR_OK; + gulong i = 0; + + g_return_val_if_fail (a_this + && a_stylesheet + && a_node && a_rulesets, CR_BAD_PARAM_ERROR); + + if (!a_stylesheet->statements) { + *a_rulesets = NULL; + *a_len = 0; + return CR_OK; + } + + /* + *if this stylesheet is "new one" + *let's remember it for subsequent calls. + */ + if (PRIVATE (a_this)->sheet != a_stylesheet) { + PRIVATE (a_this)->sheet = a_stylesheet; + PRIVATE (a_this)->cur_stmt = a_stylesheet->statements; + } + + /* + *walk through the list of statements and, + *get the selectors list inside the statements that + *contain some, and try to match our xml node in these + *selectors lists. + */ + for (cur_stmt = PRIVATE (a_this)->cur_stmt, i = 0; + (PRIVATE (a_this)->cur_stmt = cur_stmt); + cur_stmt = cur_stmt->next) { + /* + *initialyze the selector list in which we will + *really perform the search. + */ + sel_list = NULL; + + /* + *get the the damn selector list in + *which we have to look + */ + switch (cur_stmt->type) { + case RULESET_STMT: + if (cur_stmt->kind.ruleset + && cur_stmt->kind.ruleset->sel_list) { + sel_list = cur_stmt->kind.ruleset->sel_list; + } + break; + + case AT_MEDIA_RULE_STMT: + if (cur_stmt->kind.media_rule + && cur_stmt->kind.media_rule->rulesets + && cur_stmt->kind.media_rule->rulesets-> + kind.ruleset + && cur_stmt->kind.media_rule->rulesets-> + kind.ruleset->sel_list) { + sel_list = + cur_stmt->kind.media_rule-> + rulesets->kind.ruleset->sel_list; + } + break; + + case AT_IMPORT_RULE_STMT: + /* + *some recursivity may be needed here. + *I don't like this :( + */ + break; + default: + break; + } + + if (!sel_list) + continue; + + /* + *now, we have a comma separated selector list to look in. + *let's walk it and try to match the xml_node + *on each item of the list. + */ + for (cur_sel = sel_list; cur_sel; cur_sel = cur_sel->next) { + if (!cur_sel->simple_sel) + continue; + + status = cr_sel_eng_matches_node + (a_this, cur_sel->simple_sel, + a_node, &matches); + + if (status == CR_OK && matches == TRUE) { + /* + *bingo!!! we found one ruleset that + *matches that fucking node. + *lets put it in the out array. + */ + + if (i < *a_len) { + a_rulesets[i] = cur_stmt; + i++; + + /* + *For the cascade computing algorithm + *(which is gonna take place later) + *we must compute the specificity + *(css2 spec chap 6.4.1) of the selector + *that matched the current xml node + *and store it in the css2 statement + *(statement == ruleset here). + */ + status = cr_simple_sel_compute_specificity (cur_sel->simple_sel); + + g_return_val_if_fail (status == CR_OK, + CR_ERROR); + cur_stmt->specificity = + cur_sel->simple_sel-> + specificity; + } else + { + *a_len = i; + return CR_OUTPUT_TOO_SHORT_ERROR; + } + } + } + } + + /* + *if we reached this point, it means + *we reached the end of stylesheet. + *no need to store any info about the stylesheet + *anymore. + */ + g_return_val_if_fail (!PRIVATE (a_this)->cur_stmt, CR_ERROR); + PRIVATE (a_this)->sheet = NULL; + *a_len = i; + return CR_OK; +} + +static enum CRStatus +put_css_properties_in_props_list (CRPropList ** a_props, CRStatement * a_stmt) +{ + CRPropList *props = NULL, + *pair = NULL, + *tmp_props = NULL; + CRDeclaration *cur_decl = NULL; + + g_return_val_if_fail (a_props && a_stmt + && a_stmt->type == RULESET_STMT + && a_stmt->kind.ruleset, CR_BAD_PARAM_ERROR); + + props = *a_props; + + for (cur_decl = a_stmt->kind.ruleset->decl_list; + cur_decl; cur_decl = cur_decl->next) { + CRDeclaration *decl; + + decl = NULL; + pair = NULL; + + if (!cur_decl->property + || !cur_decl->property->stryng + || !cur_decl->property->stryng->str) + continue; + /* + *First, test if the property is not + *already present in our properties list + *If yes, apply the cascading rules to + *compute the precedence. If not, insert + *the property into the list + */ + cr_prop_list_lookup_prop (props, + cur_decl->property, + &pair); + + if (!pair) { + tmp_props = cr_prop_list_append2 + (props, cur_decl->property, cur_decl); + if (tmp_props) { + props = tmp_props; + tmp_props = NULL; + } + continue; + } + + /* + *A property with the same name already exists. + *We must apply here + *some cascading rules + *to compute the precedence. + */ + cr_prop_list_get_decl (pair, &decl); + g_return_val_if_fail (decl, CR_ERROR); + + /* + *first, look at the origin. + *6.4.1 says: + *"for normal declarations, + *author style sheets override user + *style sheets which override + *the default style sheet." + */ + if (decl->parent_statement + && decl->parent_statement->parent_sheet + && (decl->parent_statement->parent_sheet->origin + < a_stmt->parent_sheet->origin)) { + /* + *if the already selected declaration + *is marked as being !important the current + *declaration must not overide it + *(unless the already selected declaration + *has an UA origin) + */ + if (decl->important == TRUE + && decl->parent_statement->parent_sheet->origin + != ORIGIN_UA) { + continue; + } + tmp_props = cr_prop_list_unlink (props, pair); + if (props) { + cr_prop_list_destroy (pair); + } + props = tmp_props; + tmp_props = NULL; + props = cr_prop_list_append2 + (props, cur_decl->property, cur_decl); + + continue; + } else if (decl->parent_statement + && decl->parent_statement->parent_sheet + && (decl->parent_statement-> + parent_sheet->origin + > a_stmt->parent_sheet->origin)) { + cr_utils_trace_info + ("We should not reach this line\n"); + continue; + } + + /* + *A property with the same + *name and the same origin already exists. + *shit. This is lasting longer than expected ... + *Luckily, the spec says in 6.4.1: + *"more specific selectors will override + *more general ones" + *and + *"if two rules have the same weight, + *origin and specificity, + *the later specified wins" + */ + if (a_stmt->specificity + >= decl->parent_statement->specificity) { + if (decl->important == TRUE) + continue; + props = cr_prop_list_unlink (props, pair); + if (pair) { + cr_prop_list_destroy (pair); + pair = NULL; + } + props = cr_prop_list_append2 (props, + cur_decl->property, + cur_decl); + } + } + /*TODO: this may leak. Check this out */ + *a_props = props; + + return CR_OK; +} + +static void +set_style_from_props (CRStyle * a_style, CRPropList * a_props) +{ + CRPropList *cur = NULL; + CRDeclaration *decl = NULL; + + for (cur = a_props; cur; cur = cr_prop_list_get_next (cur)) { + cr_prop_list_get_decl (cur, &decl); + cr_style_set_style_from_decl (a_style, decl); + decl = NULL; + } +} + +/**************************************** + *PUBLIC METHODS + ****************************************/ + +/** + *Creates a new instance of #CRSelEng. + *@return the newly built instance of #CRSelEng of + *NULL if an error occurs. + */ +CRSelEng * +cr_sel_eng_new (void) +{ + CRSelEng *result = NULL; + + result = (CRSelEng *) g_try_malloc (sizeof (CRSelEng)); + if (!result) { + cr_utils_trace_info ("Out of memory"); + return NULL; + } + memset (result, 0, sizeof (CRSelEng)); + + PRIVATE (result) = (CRSelEngPriv *) g_try_malloc (sizeof (CRSelEngPriv)); + if (!PRIVATE (result)) { + cr_utils_trace_info ("Out of memory"); + g_free (result); + return NULL; + } + memset (PRIVATE (result), 0, sizeof (CRSelEngPriv)); + cr_sel_eng_register_pseudo_class_sel_handler + (result, "first-child", + IDENT_PSEUDO, /*(CRPseudoClassSelectorHandler)*/ + first_child_pseudo_class_handler); + cr_sel_eng_register_pseudo_class_sel_handler + (result, "lang", + FUNCTION_PSEUDO, /*(CRPseudoClassSelectorHandler)*/ + lang_pseudo_class_handler); + + return result; +} + +/** + *Adds a new handler entry in the handlers entry table. + *@param a_this the current instance of #CRSelEng + *@param a_pseudo_class_sel_name the name of the pseudo class selector. + *@param a_pseudo_class_type the type of the pseudo class selector. + *@param a_handler the actual handler or callback to be called during + *the selector evaluation process. + *@return CR_OK, upon successful completion, an error code otherwise. + */ +enum CRStatus +cr_sel_eng_register_pseudo_class_sel_handler (CRSelEng * a_this, + char * a_name, + enum CRPseudoType a_type, + CRPseudoClassSelectorHandler + a_handler) +{ + struct CRPseudoClassSelHandlerEntry *handler_entry = NULL; + GList *list = NULL; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_handler && a_name, CR_BAD_PARAM_ERROR); + + handler_entry = (struct CRPseudoClassSelHandlerEntry *) g_try_malloc + (sizeof (struct CRPseudoClassSelHandlerEntry)); + if (!handler_entry) { + return CR_OUT_OF_MEMORY_ERROR; + } + memset (handler_entry, 0, + sizeof (struct CRPseudoClassSelHandlerEntry)); + handler_entry->name = g_strdup (a_name); + handler_entry->type = a_type; + handler_entry->handler = a_handler; + list = g_list_append (PRIVATE (a_this)->pcs_handlers, handler_entry); + if (!list) { + return CR_OUT_OF_MEMORY_ERROR; + } + PRIVATE (a_this)->pcs_handlers = list; + return CR_OK; +} + +enum CRStatus +cr_sel_eng_unregister_pseudo_class_sel_handler (CRSelEng * a_this, + char * a_name, + enum CRPseudoType a_type) +{ + GList *elem = NULL, + *deleted_elem = NULL; + gboolean found = FALSE; + struct CRPseudoClassSelHandlerEntry *entry = NULL; + + g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); + + for (elem = PRIVATE (a_this)->pcs_handlers; + elem; elem = g_list_next (elem)) { + entry = (struct CRPseudoClassSelHandlerEntry *) elem->data; + if (!strcmp (entry->name, a_name) + && entry->type == a_type) { + found = TRUE; + break; + } + } + if (found == FALSE) + return CR_PSEUDO_CLASS_SEL_HANDLER_NOT_FOUND_ERROR; + PRIVATE (a_this)->pcs_handlers = g_list_delete_link + (PRIVATE (a_this)->pcs_handlers, elem); + entry = (struct CRPseudoClassSelHandlerEntry *) elem->data; + if (entry->name) { + g_free (entry->name); + entry->name = NULL; + } + g_free (elem); + g_list_free (deleted_elem); + + return CR_OK; +} + +/** + *Unregisters all the pseudo class sel handlers + *and frees all the associated allocated datastructures. + *@param a_this the current instance of #CRSelEng . + *@return CR_OK upon succesful completion, an error code + *otherwise. + */ +enum CRStatus +cr_sel_eng_unregister_all_pseudo_class_sel_handlers (CRSelEng * a_this) +{ + GList *elem = NULL; + struct CRPseudoClassSelHandlerEntry *entry = NULL; + + g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); + + if (!PRIVATE (a_this)->pcs_handlers) + return CR_OK; + for (elem = PRIVATE (a_this)->pcs_handlers; + elem; elem = g_list_next (elem)) { + entry = (struct CRPseudoClassSelHandlerEntry *) elem->data; + if (!entry) + continue; + if (entry->name) { + g_free (entry->name); + entry->name = NULL; + } + g_free (entry); + elem->data = NULL; + } + g_list_free (PRIVATE (a_this)->pcs_handlers); + PRIVATE (a_this)->pcs_handlers = NULL; + return CR_OK; +} + +enum CRStatus +cr_sel_eng_get_pseudo_class_selector_handler (CRSelEng * a_this, + char * a_name, + enum CRPseudoType a_type, + CRPseudoClassSelectorHandler * + a_handler) +{ + GList *elem = NULL; + struct CRPseudoClassSelHandlerEntry *entry = NULL; + gboolean found = FALSE; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_name, CR_BAD_PARAM_ERROR); + + for (elem = PRIVATE (a_this)->pcs_handlers; + elem; elem = g_list_next (elem)) { + entry = (struct CRPseudoClassSelHandlerEntry *) elem->data; + if (!strcmp (a_name, entry->name) + && entry->type == a_type) { + found = TRUE; + break; + } + } + + if (found == FALSE) + return CR_PSEUDO_CLASS_SEL_HANDLER_NOT_FOUND_ERROR; + *a_handler = entry->handler; + return CR_OK; +} + +/** + *Evaluates a chained list of simple selectors (known as a css2 selector). + *Says wheter if this selector matches the xml node given in parameter or + *not. + *@param a_this the selection engine. + *@param a_sel the simple selector against which the xml node + *is going to be matched. + *@param a_node the node against which the selector is going to be matched. + *@param a_result out parameter. The result of the match. Is set to + *TRUE if the selector matches the node, FALSE otherwise. This value + *is considered if and only if this functions returns CR_OK. + *@return the CR_OK if the selection ran correctly, an error code otherwise. + */ +enum CRStatus +cr_sel_eng_matches_node (CRSelEng * a_this, CRSimpleSel * a_sel, + CRXMLNodePtr a_node, gboolean * a_result) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_this && a_node + && a_result, CR_BAD_PARAM_ERROR); + + if (!PRIVATE(a_this)->node_iface->isElementNode (a_node)) { + *a_result = FALSE; + return CR_OK; + } + + return sel_matches_node_real (a_this, a_sel, + a_node, a_result, + TRUE, TRUE); +} + +/** + *Returns an array of pointers to selectors that matches + *the xml node given in parameter. + * + *@param a_this the current instance of the selection engine. + *@param a_sheet the stylesheet that holds the selectors. + *@param a_node the xml node to consider during the walk thru + *the stylesheet. + *@param a_rulesets out parameter. A pointer to an array of + *rulesets statement pointers. *a_rulesets is allocated by + *this function and must be freed by the caller. However, the caller + *must not alter the rulesets statements pointer because they + *point to statements that are still in the css stylesheet. + *@param a_len the length of *a_ruleset. + *@return CR_OK upon sucessfull completion, an error code otherwise. + */ +enum CRStatus +cr_sel_eng_get_matched_rulesets (CRSelEng * a_this, + CRStyleSheet * a_sheet, + CRXMLNodePtr a_node, + CRStatement *** a_rulesets, gulong * a_len) +{ + CRStatement **stmts_tab = NULL; + enum CRStatus status = CR_OK; + gulong tab_size = 0, + tab_len = 0, + index = 0; + gushort stmts_chunck_size = 8; + + g_return_val_if_fail (a_this + && a_sheet + && a_node + && a_rulesets && *a_rulesets == NULL + && a_len, CR_BAD_PARAM_ERROR); + + stmts_tab = (CRStatement **) g_try_malloc (stmts_chunck_size * sizeof (CRStatement *)); + + if (!stmts_tab) { + cr_utils_trace_info ("Out of memory"); + status = CR_ERROR; + goto error; + } + memset (stmts_tab, 0, stmts_chunck_size * sizeof (CRStatement *)); + + tab_size = stmts_chunck_size; + tab_len = tab_size; + + while ((status = cr_sel_eng_get_matched_rulesets_real + (a_this, a_sheet, a_node, stmts_tab + index, &tab_len)) + == CR_OUTPUT_TOO_SHORT_ERROR) { + stmts_tab = (CRStatement **) g_try_realloc (stmts_tab, + (tab_size + stmts_chunck_size) + * sizeof (CRStatement *)); + if (!stmts_tab) { + cr_utils_trace_info ("Out of memory"); + status = CR_ERROR; + goto error; + } + tab_size += stmts_chunck_size; + index += tab_len; + tab_len = tab_size - index; + } + + tab_len = tab_size - stmts_chunck_size + tab_len; + *a_rulesets = stmts_tab; + *a_len = tab_len; + + return CR_OK; + + error: + + if (stmts_tab) { + g_free (stmts_tab); + stmts_tab = NULL; + + } + + *a_len = 0; + return status; +} + + +enum CRStatus +cr_sel_eng_get_matched_properties_from_cascade (CRSelEng * a_this, + CRCascade * a_cascade, + CRXMLNodePtr a_node, + CRPropList ** a_props) +{ + CRStatement **stmts_tab = NULL; + enum CRStatus status = CR_OK; + gulong tab_size = 0, + tab_len = 0, + i = 0, + index = 0; + enum CRStyleOrigin origin; + gushort stmts_chunck_size = 8; + CRStyleSheet *sheet = NULL; + + g_return_val_if_fail (a_this + && a_cascade + && a_node && a_props, CR_BAD_PARAM_ERROR); + + for (origin = ORIGIN_UA; origin < NB_ORIGINS; origin = (enum CRStyleOrigin) (origin + 1)) { + sheet = cr_cascade_get_sheet (a_cascade, origin); + if (!sheet) + continue; + if (tab_size - index < 1) { + stmts_tab = (CRStatement **) g_try_realloc + (stmts_tab, (tab_size + stmts_chunck_size) + * sizeof (CRStatement *)); + if (!stmts_tab) { + cr_utils_trace_info ("Out of memory"); + status = CR_ERROR; + goto cleanup; + } + tab_size += stmts_chunck_size; + /* + *compute the max size left for + *cr_sel_eng_get_matched_rulesets_real()'s output tab + */ + tab_len = tab_size - index; + } + while ((status = cr_sel_eng_get_matched_rulesets_real + (a_this, sheet, a_node, stmts_tab + index, &tab_len)) + == CR_OUTPUT_TOO_SHORT_ERROR) { + stmts_tab = (CRStatement **) g_try_realloc + (stmts_tab, (tab_size + stmts_chunck_size) + * sizeof (CRStatement *)); + if (!stmts_tab) { + cr_utils_trace_info ("Out of memory"); + status = CR_ERROR; + goto cleanup; + } + tab_size += stmts_chunck_size; + index += tab_len; + /* + *compute the max size left for + *cr_sel_eng_get_matched_rulesets_real()'s output tab + */ + tab_len = tab_size - index; + } + if (status != CR_OK) { + cr_utils_trace_info ("Error while running " + "selector engine"); + goto cleanup; + } + index += tab_len; + tab_len = tab_size - index; + } + + /* + *TODO, walk down the stmts_tab and build the + *property_name/declaration hashtable. + *Make sure one can walk from the declaration to + *the stylesheet. + */ + for (i = 0; i < index; i++) { + CRStatement *stmt = stmts_tab[i]; + + if (!stmt) + continue; + switch (stmt->type) { + case RULESET_STMT: + if (!stmt->parent_sheet) + continue; + status = put_css_properties_in_props_list + (a_props, stmt); + break; + default: + break; + } + + } + status = CR_OK ; + cleanup: + if (stmts_tab) { + g_free (stmts_tab); + stmts_tab = NULL; + } + + return status; +} + +enum CRStatus +cr_sel_eng_get_matched_style (CRSelEng * a_this, + CRCascade * a_cascade, + CRXMLNodePtr a_node, + CRStyle * a_parent_style, + CRStyle ** a_style, + gboolean a_set_props_to_initial_values) +{ + enum CRStatus status = CR_OK; + + CRPropList *props = NULL; + + g_return_val_if_fail (a_this && a_cascade + && a_node && a_style, CR_BAD_PARAM_ERROR); + + status = cr_sel_eng_get_matched_properties_from_cascade + (a_this, a_cascade, a_node, &props); + + g_return_val_if_fail (status == CR_OK, status); + if (props) { + if (!*a_style) { + *a_style = cr_style_new (a_set_props_to_initial_values) ; + g_return_val_if_fail (*a_style, CR_ERROR); + } else { + if (a_set_props_to_initial_values == TRUE) { + cr_style_set_props_to_initial_values (*a_style) ; + } else { + cr_style_set_props_to_default_values (*a_style); + } + } + (*a_style)->parent_style = a_parent_style; + + set_style_from_props (*a_style, props); + if (props) { + cr_prop_list_destroy (props); + props = NULL; + } + } + return CR_OK; +} + +/** + *The destructor of #CRSelEng + *@param a_this the current instance of the selection engine. + */ +void +cr_sel_eng_destroy (CRSelEng * a_this) +{ + g_return_if_fail (a_this); + + if (!PRIVATE (a_this)) + goto end ; + if (PRIVATE (a_this)->pcs_handlers) { + cr_sel_eng_unregister_all_pseudo_class_sel_handlers + (a_this) ; + PRIVATE (a_this)->pcs_handlers = NULL ; + } + g_free (PRIVATE (a_this)); + PRIVATE (a_this) = NULL; + end: + if (a_this) { + g_free (a_this); + } +} diff --git a/src/libcroco/cr-sel-eng.h b/src/libcroco/cr-sel-eng.h new file mode 100644 index 000000000..af6c84398 --- /dev/null +++ b/src/libcroco/cr-sel-eng.h @@ -0,0 +1,112 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli + * See COPYRIGHTS file for copyrights information. + */ + +#ifndef __CR_SEL_ENG_H__ +#define __CR_SEL_ENG_H__ + +#include "cr-utils.h" +#include "cr-stylesheet.h" +#include "cr-cascade.h" +#include "cr-style.h" +#include "cr-prop-list.h" +#include "cr-node-iface.h" + + + +/** + *@file: + *The declaration of the #CRSelEng class. + *The #CRSelEng is actually the "Selection Engine" + *class. + */ + +G_BEGIN_DECLS + +typedef struct _CRSelEng CRSelEng ; +typedef struct _CRSelEngPriv CRSelEngPriv ; + +/** + *The Selection engine class. + *The main service provided by this class, is + *the ability to interpret a libcroco implementation + *of css2 selectors, and given an xml node, say if + *the selector matches the node or not. + */ +struct _CRSelEng +{ + CRSelEngPriv *priv ; +} ; + +void cr_sel_eng_set_node_iface(CRSelEng *a_this, CRNodeIface const *); + +typedef gboolean (*CRPseudoClassSelectorHandler) (CRSelEng* a_this, + CRAdditionalSel *a_add_sel, + CRXMLNodePtr a_node) ; +CRSelEng * cr_sel_eng_new (void) ; + +enum CRStatus cr_sel_eng_register_pseudo_class_sel_handler (CRSelEng *a_this, + char *a_pseudo_class_sel_name, + enum CRPseudoType a_pseudo_class_type, + CRPseudoClassSelectorHandler a_handler) ; + +enum CRStatus cr_sel_eng_unregister_pseudo_class_sel_handler (CRSelEng *a_this, + char *a_pseudo_class_sel_name, + enum CRPseudoType a_pseudo_class_type) ; + +enum CRStatus cr_sel_eng_unregister_all_pseudo_class_sel_handlers (CRSelEng *a_this) ; + +enum CRStatus cr_sel_eng_get_pseudo_class_selector_handler (CRSelEng *a_this, + char *a_pseudo_class_sel_name, + enum CRPseudoType a_pseudo_class_type, + CRPseudoClassSelectorHandler *a_handler) ; + +enum CRStatus cr_sel_eng_matches_node (CRSelEng *a_this, + CRSimpleSel *a_sel, + CRXMLNodePtr a_node, + gboolean *a_result) ; + +enum CRStatus cr_sel_eng_get_matched_rulesets (CRSelEng *a_this, + CRStyleSheet *a_sheet, + CRXMLNodePtr a_node, + CRStatement ***a_rulesets, + gulong *a_len) ; + +enum CRStatus +cr_sel_eng_get_matched_properties_from_cascade (CRSelEng *a_this, + CRCascade *a_cascade, + CRXMLNodePtr a_node, + CRPropList **a_props) ; + +enum CRStatus cr_sel_eng_get_matched_style (CRSelEng *a_this, + CRCascade *a_cascade, + CRXMLNodePtr a_node, + CRStyle *a_parent_style, + CRStyle **a_style, + gboolean a_set_props_to_initial_values) ; + +void cr_sel_eng_destroy (CRSelEng *a_this) ; + +G_END_DECLS + + +#endif/*__CR_SEL_ENG_H__*/ diff --git a/src/libcroco/cr-selector.c b/src/libcroco/cr-selector.c new file mode 100644 index 000000000..5f7316a00 --- /dev/null +++ b/src/libcroco/cr-selector.c @@ -0,0 +1,277 @@ +/* -*- Mode: C; indent-tabs-mode: ni; c-basic-offset: 8 -*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * See COPYRIGHTS file for copyright information. + */ + +#include +#include "cr-selector.h" +#include "cr-parser.h" + +/** + *Creates a new instance of #CRSelector. + *@param a_simple_sel the initial simple selector list + *of the current instance of #CRSelector. + *@return the newly built instance of #CRSelector, or + *NULL in case of failure. + */ +CRSelector * +cr_selector_new (CRSimpleSel * a_simple_sel) +{ + CRSelector *result = NULL; + + result = g_try_malloc (sizeof (CRSelector)); + if (!result) { + cr_utils_trace_info ("Out of memory"); + return NULL; + } + memset (result, 0, sizeof (CRSelector)); + result->simple_sel = a_simple_sel; + return result; +} + +CRSelector * +cr_selector_parse_from_buf (const guchar * a_char_buf, enum CREncoding a_enc) +{ + CRParser *parser = NULL; + + g_return_val_if_fail (a_char_buf, NULL); + + parser = cr_parser_new_from_buf ((guchar*)a_char_buf, strlen (a_char_buf), + a_enc, FALSE); + g_return_val_if_fail (parser, NULL); + + return NULL; +} + +/** + *Appends a new instance of #CRSelector to the current selector list. + *@param a_this the current instance of #CRSelector. + *@param a_new the instance of #CRSelector to be appended. + *@return the new list. + */ +CRSelector * +cr_selector_append (CRSelector * a_this, CRSelector * a_new) +{ + CRSelector *cur = NULL; + + if (!a_this) { + return a_new; + } + + /*walk forward the list headed by a_this to get the list tail */ + for (cur = a_this; cur && cur->next; cur = cur->next) ; + + cur->next = a_new; + a_new->prev = cur; + + return a_this; +} + +/** + *Prepends an element to the #CRSelector list. + *@param a_this the current instance of #CRSelector list. + *@param a_new the instance of #CRSelector. + *@return the new list. + */ +CRSelector * +cr_selector_prepend (CRSelector * a_this, CRSelector * a_new) +{ + CRSelector *cur = NULL; + + a_new->next = a_this; + a_this->prev = a_new; + + for (cur = a_new; cur && cur->prev; cur = cur->prev) ; + + return cur; +} + +/** + *append a simple selector to the current #CRSelector list. + *@param a_this the current instance of #CRSelector. + *@param a_simple_sel the simple selector to append. + *@return the new list or NULL in case of failure. + */ +CRSelector * +cr_selector_append_simple_sel (CRSelector * a_this, + CRSimpleSel * a_simple_sel) +{ + CRSelector *selector = NULL; + + selector = cr_selector_new (a_simple_sel); + g_return_val_if_fail (selector, NULL); + + return cr_selector_append (a_this, selector); +} + +guchar * +cr_selector_to_string (CRSelector * a_this) +{ + guchar *result = NULL; + GString *str_buf = NULL; + + str_buf = g_string_new (NULL); + g_return_val_if_fail (str_buf, NULL); + + if (a_this) { + CRSelector *cur = NULL; + + for (cur = a_this; cur; cur = cur->next) { + if (cur->simple_sel) { + guchar *tmp_str = NULL; + + tmp_str = cr_simple_sel_to_string + (cur->simple_sel); + + if (tmp_str) { + if (cur->prev) + g_string_append (str_buf, + ", "); + + g_string_append (str_buf, tmp_str); + + g_free (tmp_str); + tmp_str = NULL; + } + } + } + } + + if (str_buf) { + result = str_buf->str; + g_string_free (str_buf, FALSE); + str_buf = NULL; + } + + return result; +} + +/** + *Serializes the current instance of #CRSelector to a file. + *@param a_this the current instance of #CRSelector. + *@param a_fp the destination file. + */ +void +cr_selector_dump (CRSelector * a_this, FILE * a_fp) +{ + guchar *tmp_buf = NULL; + + if (a_this) { + tmp_buf = cr_selector_to_string (a_this); + if (tmp_buf) { + fprintf (a_fp, "%s", tmp_buf); + g_free (tmp_buf); + tmp_buf = NULL; + } + } +} + +/** + *Increments the ref count of the current instance + *of #CRSelector. + *@param a_this the current instance of #CRSelector. + */ +void +cr_selector_ref (CRSelector * a_this) +{ + g_return_if_fail (a_this); + + a_this->ref_count++; +} + +/** + *Decrements the ref count of the current instance of + *#CRSelector. + *If the ref count reaches zero, the current instance of + *#CRSelector is destroyed. + *@param a_this the current instance of #CRSelector. + *@return TRUE if this function destroyed the current instance + *of #CRSelector, FALSE otherwise. + */ +gboolean +cr_selector_unref (CRSelector * a_this) +{ + g_return_val_if_fail (a_this, FALSE); + + if (a_this->ref_count) { + a_this->ref_count--; + } + + if (a_this->ref_count == 0) { + cr_selector_destroy (a_this); + return TRUE; + } + + return FALSE; +} + +/** + *Destroys the selector list. + *@param a_this the current instance of #CRSelector. + */ +void +cr_selector_destroy (CRSelector * a_this) +{ + CRSelector *cur = NULL; + + g_return_if_fail (a_this); + + /* + *go and get the list tail. In the same time, free + *all the simple selectors contained in the list. + */ + for (cur = a_this; cur && cur->next; cur = cur->next) { + if (cur->simple_sel) { + cr_simple_sel_destroy (cur->simple_sel); + cur->simple_sel = NULL; + } + } + + if (cur) { + if (cur->simple_sel) { + cr_simple_sel_destroy (cur->simple_sel); + cur->simple_sel = NULL; + } + } + + /*in case the list has only one element */ + if (cur && !cur->prev) { + g_free (cur); + return; + } + + /*walk backward the list and free each "next element" */ + for (cur = cur->prev; cur && cur->prev; cur = cur->prev) { + if (cur->next) { + g_free (cur->next); + cur->next = NULL; + } + } + + if (!cur) + return; + + if (cur->next) { + g_free (cur->next); + cur->next = NULL; + } + + g_free (cur); +} diff --git a/src/libcroco/cr-selector.h b/src/libcroco/cr-selector.h new file mode 100644 index 000000000..6bf769733 --- /dev/null +++ b/src/libcroco/cr-selector.h @@ -0,0 +1,95 @@ +/* -*- Mode: C; indent-tabs-mode: ni; c-basic-offset: 8 -*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * + * Author: Dodji Seketeli + * See COPYRIGHTS file for copyright information. + */ + +#ifndef __CR_SELECTOR_H__ +#define __CR_SELECTOR_H__ + +#include +#include "cr-utils.h" +#include "cr-simple-sel.h" +#include "cr-parsing-location.h" + +/** + *@file + *The declaration file of the #CRSelector file. + */ + +G_BEGIN_DECLS + +typedef struct _CRSelector CRSelector ; + +/** + *Abstracts a CSS2 selector as defined in the right part + *of the 'ruleset" production in the appendix D.1 of the + *css2 spec. + *It is actually the abstraction of a comma separated list + *of simple selectors list. + *In a css2 file, a selector is a list of simple selectors + *separated by a comma. + *e.g: sel0, sel1, sel2 ... + *Each seln is a simple selector + */ +struct _CRSelector +{ + /** + *A Selection expression. + *It is a list of basic selectors. + *Each basic selector can be either an element + *selector, an id selector, a class selector, an + *attribute selector, an universal selector etc ... + */ + CRSimpleSel *simple_sel ; + + /**The next selector list element*/ + CRSelector *next ; + CRSelector *prev ; + CRParsingLocation location ; + glong ref_count ; +}; + +CRSelector* cr_selector_new (CRSimpleSel *a_sel_expr) ; + +CRSelector * cr_selector_parse_from_buf (const guchar * a_char_buf, + enum CREncoding a_enc) ; + +CRSelector* cr_selector_append (CRSelector *a_this, CRSelector *a_new) ; + +CRSelector* cr_selector_append_simple_sel (CRSelector *a_this, + CRSimpleSel *a_simple_sel) ; + +CRSelector* cr_selector_prepend (CRSelector *a_this, CRSelector *a_new) ; + +guchar * cr_selector_to_string (CRSelector *a_this) ; + +void cr_selector_dump (CRSelector *a_this, FILE *a_fp) ; + +void cr_selector_ref (CRSelector *a_this) ; + +gboolean cr_selector_unref (CRSelector *a_this) ; + +void cr_selector_destroy (CRSelector *a_this) ; + +G_END_DECLS + +#endif /*__CR_SELECTOR_H__*/ diff --git a/src/libcroco/cr-simple-sel.c b/src/libcroco/cr-simple-sel.c new file mode 100644 index 000000000..1b941dab5 --- /dev/null +++ b/src/libcroco/cr-simple-sel.c @@ -0,0 +1,308 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli + * See COPYRIGHTS file for copyright information. + */ + +#include +#include +#include "cr-simple-sel.h" + +/** + *The constructor of #CRSimpleSel. + * + *@return the new instance of #CRSimpleSel. + */ +CRSimpleSel * +cr_simple_sel_new (void) +{ + CRSimpleSel *result = NULL; + + result = g_try_malloc (sizeof (CRSimpleSel)); + if (!result) { + cr_utils_trace_info ("Out of memory"); + return NULL; + } + memset (result, 0, sizeof (CRSimpleSel)); + + return result; +} + +/** + *Appends a simpe selector to the current list of simple selector. + * + *@param a_this the this pointer of the current instance of #CRSimpleSel. + *@param a_sel the simple selector to append. + *@return the new list upon successfull completion, an error code otherwise. + */ +CRSimpleSel * +cr_simple_sel_append_simple_sel (CRSimpleSel * a_this, CRSimpleSel * a_sel) +{ + CRSimpleSel *cur = NULL; + + g_return_val_if_fail (a_sel, NULL); + + if (a_this == NULL) + return a_sel; + + for (cur = a_this; cur->next; cur = cur->next) ; + + cur->next = a_sel; + a_sel->prev = cur; + + return a_this; +} + +/** + *Prepends a simple selector to the current list of simple selectors. + *@param a_this the this pointer of the current instance of #CRSimpleSel. + *@param a_sel the simple selector to prepend. + *@return the new list upon successfull completion, an error code otherwise. + */ +CRSimpleSel * +cr_simple_sel_prepend_simple_sel (CRSimpleSel * a_this, CRSimpleSel * a_sel) +{ + g_return_val_if_fail (a_sel, NULL); + + if (a_this == NULL) + return a_sel; + + a_sel->next = a_this; + a_this->prev = a_sel; + + return a_sel; +} + +guchar * +cr_simple_sel_to_string (CRSimpleSel * a_this) +{ + GString *str_buf = NULL; + guchar *result = NULL; + + CRSimpleSel *cur = NULL; + + g_return_val_if_fail (a_this, NULL); + + str_buf = g_string_new (NULL); + for (cur = a_this; cur; cur = cur->next) { + if (cur->name) { + guchar *str = g_strndup (cur->name->stryng->str, + cur->name->stryng->len); + + if (str) { + switch (cur->combinator) { + case COMB_WS: + g_string_append (str_buf, " "); + break; + + case COMB_PLUS: + g_string_append (str_buf, "+"); + break; + + case COMB_GT: + g_string_append (str_buf, ">"); + break; + + default: + break; + } + + g_string_append (str_buf, str); + g_free (str); + str = NULL; + } + } + + if (cur->add_sel) { + guchar *tmp_str = NULL; + + tmp_str = cr_additional_sel_to_string (cur->add_sel); + if (tmp_str) { + g_string_append (str_buf, tmp_str); + g_free (tmp_str); + tmp_str = NULL; + } + } + } + + if (str_buf) { + result = str_buf->str; + g_string_free (str_buf, FALSE); + str_buf = NULL; + } + + return result; +} + + +guchar * +cr_simple_sel_one_to_string (CRSimpleSel * a_this) +{ + GString *str_buf = NULL; + guchar *result = NULL; + + g_return_val_if_fail (a_this, NULL); + + str_buf = g_string_new (NULL); + if (a_this->name) { + guchar *str = g_strndup (a_this->name->stryng->str, + a_this->name->stryng->len); + + if (str) { + g_string_append_printf (str_buf, "%s", str); + g_free (str); + str = NULL; + } + } + + if (a_this->add_sel) { + guchar *tmp_str = NULL; + + tmp_str = cr_additional_sel_to_string (a_this->add_sel); + if (tmp_str) { + g_string_append_printf + (str_buf, "%s", tmp_str); + g_free (tmp_str); + tmp_str = NULL; + } + } + + if (str_buf) { + result = str_buf->str; + g_string_free (str_buf, FALSE); + str_buf = NULL; + } + + return result; +} + +/** + *Dumps the selector to a file. + *TODO: add the support of unicode in the dump. + * + *@param a_this the current instance of #CRSimpleSel. + *@param a_fp the destination file pointer. + *@return CR_OK upon successfull completion, an error code + *otherwise. + */ +enum CRStatus +cr_simple_sel_dump (CRSimpleSel * a_this, FILE * a_fp) +{ + guchar *tmp_str = NULL; + + g_return_val_if_fail (a_fp, CR_BAD_PARAM_ERROR); + + if (a_this) { + tmp_str = cr_simple_sel_to_string (a_this); + if (tmp_str) { + fprintf (a_fp, "%s", tmp_str); + g_free (tmp_str); + tmp_str = NULL; + } + } + + return CR_OK; +} + +/** + *Computes the selector (combinator separated list of simple selectors) + *as defined in the css2 spec in chapter 6.4.3 + *@param a_this the current instance of #CRSimpleSel + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_simple_sel_compute_specificity (CRSimpleSel * a_this) +{ + CRAdditionalSel *cur_add_sel = NULL; + CRSimpleSel *cur_sel = NULL; + gulong a = 0, + b = 0, + c = 0; + + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + for (cur_sel = a_this; cur_sel; cur_sel = cur_sel->next) { + if (cur_sel->type_mask | TYPE_SELECTOR) { + c++; /*hmmh, is this a new language ? */ + } else if (!cur_sel->name + || !cur_sel->name->stryng + || !cur_sel->name->stryng->str) { + if (cur_sel->add_sel->type == + PSEUDO_CLASS_ADD_SELECTOR) { + /* + *this is a pseudo element, and + *the spec says, "ignore pseudo elements". + */ + continue; + } + } + + for (cur_add_sel = cur_sel->add_sel; + cur_add_sel; cur_add_sel = cur_add_sel->next) { + switch (cur_add_sel->type) { + case ID_ADD_SELECTOR: + a++; + break; + + case NO_ADD_SELECTOR: + continue; + + default: + b++; + break; + } + } + } + + /*we suppose a, b and c have 1 to 3 digits */ + a_this->specificity = a * 1000000 + b * 1000 + c; + + return CR_OK; +} + +/** + *The destructor of the current instance of + *#CRSimpleSel. + *@param a_this the this pointer of the current instance of #CRSimpleSel. + * + */ +void +cr_simple_sel_destroy (CRSimpleSel * a_this) +{ + g_return_if_fail (a_this); + + if (a_this->name) { + cr_string_destroy (a_this->name); + a_this->name = NULL; + } + + if (a_this->add_sel) { + cr_additional_sel_destroy (a_this->add_sel); + a_this->add_sel = NULL; + } + + if (a_this->next) { + cr_simple_sel_destroy (a_this->next); + } + + if (a_this) { + g_free (a_this); + } +} diff --git a/src/libcroco/cr-simple-sel.h b/src/libcroco/cr-simple-sel.h new file mode 100644 index 000000000..29033aff5 --- /dev/null +++ b/src/libcroco/cr-simple-sel.h @@ -0,0 +1,130 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli + * See COPYRIGHTS file for copyright information. + */ + + +#ifndef __CR_SEL_H__ +#define __CR_SEL_H__ + +#include +#include +#include "cr-additional-sel.h" +#include "cr-parsing-location.h" + +G_BEGIN_DECLS + +/** + *@file + *the declaration of the #CRSimpleSel class. + * + */ +enum Combinator +{ + NO_COMBINATOR, + COMB_WS,/*whitesape*/ + COMB_PLUS, + COMB_GT/*greater than*/ +} ; + +enum SimpleSelectorType +{ + NO_SELECTOR_TYPE = 0, + UNIVERSAL_SELECTOR = 1, + TYPE_SELECTOR = 1 << 1 +} ; + +typedef struct _CRSimpleSel CRSimpleSel ; + +/** + *The abstraction of a css2 simple selection list + *as defined by the right part of the "selector" production in the + *appendix D.1 of the css2 spec. + *It is basically a list of simple selector, each + *simple selector being separated by a combinator. + * + *In the libcroco's implementation, each simple selector + *is made of at most two parts: + * + *1/An element name or 'type selector' (which can hold a '*' and + *then been called 'universal selector') + * + *2/An additional selector that "specializes" the preceding type or + *universal selector. The additionnal selector can be either + *an id selector, or a class selector, or an attribute selector. + */ +struct _CRSimpleSel +{ + enum SimpleSelectorType type_mask ; + gboolean is_case_sentive ; + CRString * name ; + /** + *The combinator that separates + *this simple selector from the previous + *one. + */ + enum Combinator combinator ; + + /** + *The additional selector list of the + *current simple selector. + *An additional selector may + *be a class selector, an id selector, + *or an attribute selector. + *Note that this field is a linked list. + */ + CRAdditionalSel *add_sel ; + + /* + *the specificity as specified by + *chapter 6.4.3 of the spec. + */ + gulong specificity ; + + CRSimpleSel *next ; + CRSimpleSel *prev ; + CRParsingLocation location ; +} ; + +CRSimpleSel * cr_simple_sel_new (void) ; + +CRSimpleSel * cr_simple_sel_append_simple_sel (CRSimpleSel *a_this, + CRSimpleSel *a_sel) ; + +CRSimpleSel * cr_simple_sel_prepend_simple_sel (CRSimpleSel *a_this, + CRSimpleSel *a_sel) ; + +guchar * cr_simple_sel_to_string (CRSimpleSel *a_this) ; + +guchar * cr_simple_sel_one_to_string (CRSimpleSel * a_this) ; + +enum CRStatus cr_simple_sel_dump (CRSimpleSel *a_this, FILE *a_fp) ; + +enum CRStatus cr_simple_sel_dump_attr_sel_list (CRSimpleSel *a_this) ; + +enum CRStatus cr_simple_sel_compute_specificity (CRSimpleSel *a_this) ; + +void cr_simple_sel_destroy (CRSimpleSel *a_this) ; + +G_END_DECLS + + +#endif /*__CR_SIMPLE_SEL_H__*/ diff --git a/src/libcroco/cr-statement.c b/src/libcroco/cr-statement.c new file mode 100644 index 000000000..c00da1546 --- /dev/null +++ b/src/libcroco/cr-statement.c @@ -0,0 +1,2608 @@ +/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli. + * See COPYRIGHTS files for copyrights information. + */ + +#include +#include "cr-statement.h" +#include "cr-parser.h" + +#define UNUSED(_param) ((void)(_param)) + +/** + *@file + *Definition of the #CRStatement class. + */ + +#define DECLARATION_INDENT_NB 2 + +static void cr_statement_clear (CRStatement * a_this); + +static void +parse_font_face_start_font_face_cb (CRDocHandler * a_this, + CRParsingLocation * a_location) +{ + CRStatement *stmt = NULL; + enum CRStatus status = CR_OK; + + UNUSED(a_location); + + stmt = cr_statement_new_at_font_face_rule (NULL, NULL); + g_return_if_fail (stmt); + + status = cr_doc_handler_set_ctxt (a_this, stmt); + g_return_if_fail (status == CR_OK); +} + +static void +parse_font_face_unrecoverable_error_cb (CRDocHandler * a_this) +{ + CRStatement *stmt = NULL; + CRStatement **stmtptr = NULL; + enum CRStatus status = CR_OK; + + g_return_if_fail (a_this); + + stmtptr = &stmt; + status = cr_doc_handler_get_ctxt (a_this, (gpointer *) stmtptr); + if (status != CR_OK) { + cr_utils_trace_info ("Couldn't get parsing context. " + "This may lead to some memory leaks."); + return; + } + if (stmt) { + cr_statement_destroy (stmt); + cr_doc_handler_set_ctxt (a_this, NULL); + return; + } +} + +static void +parse_font_face_property_cb (CRDocHandler * a_this, + CRString * a_name, + CRTerm * a_value, gboolean a_important) +{ + enum CRStatus status = CR_OK; + CRString *name = NULL; + CRDeclaration *decl = NULL; + CRStatement *stmt = NULL; + CRStatement **stmtptr = NULL; + + UNUSED(a_important); + + g_return_if_fail (a_this && a_name); + + stmtptr = &stmt; + status = cr_doc_handler_get_ctxt (a_this, (gpointer *) stmtptr); + g_return_if_fail (status == CR_OK && stmt); + g_return_if_fail (stmt->type == AT_FONT_FACE_RULE_STMT); + + name = cr_string_dup (a_name) ; + g_return_if_fail (name); + decl = cr_declaration_new (stmt, name, a_value); + if (!decl) { + cr_utils_trace_info ("cr_declaration_new () failed."); + goto error; + } + name = NULL; + + stmt->kind.font_face_rule->decl_list = + cr_declaration_append (stmt->kind.font_face_rule->decl_list, + decl); + if (!stmt->kind.font_face_rule->decl_list) + goto error; + decl = NULL; + + error: + if (decl) { + cr_declaration_unref (decl); + decl = NULL; + } + if (name) { + cr_string_destroy (name); + name = NULL; + } +} + +static void +parse_font_face_end_font_face_cb (CRDocHandler * a_this) +{ + CRStatement *result = NULL; + CRStatement **resultptr = NULL; + enum CRStatus status = CR_OK; + + g_return_if_fail (a_this); + + resultptr = &result; + status = cr_doc_handler_get_ctxt (a_this, (gpointer *) resultptr); + g_return_if_fail (status == CR_OK && result); + g_return_if_fail (result->type == AT_FONT_FACE_RULE_STMT); + + status = cr_doc_handler_set_result (a_this, result); + g_return_if_fail (status == CR_OK); +} + +static void +parse_page_start_page_cb (CRDocHandler * a_this, + CRString * a_name, + CRString * a_pseudo_page, + CRParsingLocation * a_location) +{ + CRStatement *stmt = NULL; + enum CRStatus status = CR_OK; + CRString *page_name = NULL, *pseudo_name = NULL ; + + UNUSED(a_location); + + if (a_name) + page_name = cr_string_dup (a_name) ; + if (a_pseudo_page) + pseudo_name = cr_string_dup (a_pseudo_page) ; + + stmt = cr_statement_new_at_page_rule (NULL, NULL, + page_name, + pseudo_name); + page_name = NULL ; + pseudo_name = NULL ; + g_return_if_fail (stmt); + status = cr_doc_handler_set_ctxt (a_this, stmt); + g_return_if_fail (status == CR_OK); +} + +static void +parse_page_unrecoverable_error_cb (CRDocHandler * a_this) +{ + CRStatement *stmt = NULL; + CRStatement **stmtptr = NULL; + enum CRStatus status = CR_OK; + + g_return_if_fail (a_this); + + stmtptr = &stmt; + status = cr_doc_handler_get_ctxt (a_this, (gpointer *) stmtptr); + if (status != CR_OK) { + cr_utils_trace_info ("Couldn't get parsing context. " + "This may lead to some memory leaks."); + return; + } + if (stmt) { + cr_statement_destroy (stmt); + stmt = NULL; + cr_doc_handler_set_ctxt (a_this, NULL); + } +} + +static void +parse_page_property_cb (CRDocHandler * a_this, + CRString * a_name, + CRTerm * a_expression, gboolean a_important) +{ + CRString *name = NULL; + CRStatement *stmt = NULL; + CRStatement **stmtptr = NULL; + CRDeclaration *decl = NULL; + enum CRStatus status = CR_OK; + + stmtptr = &stmt; + status = cr_doc_handler_get_ctxt (a_this, (gpointer *) stmtptr); + g_return_if_fail (status == CR_OK && stmt->type == AT_PAGE_RULE_STMT); + + name = cr_string_dup (a_name); + g_return_if_fail (name); + + decl = cr_declaration_new (stmt, name, a_expression); + g_return_if_fail (decl); + decl->important = a_important; + stmt->kind.page_rule->decl_list = + cr_declaration_append (stmt->kind.page_rule->decl_list, decl); + g_return_if_fail (stmt->kind.page_rule->decl_list); +} + +static void +parse_page_end_page_cb (CRDocHandler * a_this, + CRString * a_name, + CRString * a_pseudo_page) +{ + enum CRStatus status = CR_OK; + CRStatement *stmt = NULL; + CRStatement **stmtptr = NULL; + + UNUSED(a_name); + UNUSED(a_pseudo_page); + + stmtptr = &stmt; + status = cr_doc_handler_get_ctxt (a_this, (gpointer *) stmtptr); + g_return_if_fail (status == CR_OK && stmt); + g_return_if_fail (stmt->type == AT_PAGE_RULE_STMT); + + status = cr_doc_handler_set_result (a_this, stmt); + g_return_if_fail (status == CR_OK); +} + +static void +parse_at_media_start_media_cb (CRDocHandler * a_this, + GList * a_media_list, + CRParsingLocation * a_location) +{ + enum CRStatus status = CR_OK; + CRStatement *at_media = NULL; + GList *media_list = NULL; + + UNUSED(a_location); + + g_return_if_fail (a_this && a_this->priv); + + if (a_media_list) { + /*duplicate media list */ + media_list = cr_utils_dup_glist_of_cr_string + (a_media_list); + } + + g_return_if_fail (media_list); + + /*make sure cr_statement_new_at_media_rule works in this case. */ + at_media = cr_statement_new_at_media_rule (NULL, NULL, media_list); + + status = cr_doc_handler_set_ctxt (a_this, at_media); + g_return_if_fail (status == CR_OK); + status = cr_doc_handler_set_result (a_this, at_media); + g_return_if_fail (status == CR_OK); +} + +static void +parse_at_media_unrecoverable_error_cb (CRDocHandler * a_this) +{ + enum CRStatus status = CR_OK; + CRStatement *stmt = NULL; + CRStatement **stmtptr = NULL; + + g_return_if_fail (a_this); + + stmtptr = &stmt; + status = cr_doc_handler_get_result (a_this, (gpointer *) stmtptr); + if (status != CR_OK) { + cr_utils_trace_info ("Couldn't get parsing context. " + "This may lead to some memory leaks."); + return; + } + if (stmt) { + cr_statement_destroy (stmt); + stmt = NULL; + cr_doc_handler_set_ctxt (a_this, NULL); + cr_doc_handler_set_result (a_this, NULL); + } +} + +static void +parse_at_media_start_selector_cb (CRDocHandler * a_this, + CRSelector * a_sellist) +{ + enum CRStatus status = CR_OK; + CRStatement *at_media = NULL; + CRStatement **at_media_ptr = NULL; + CRStatement *ruleset = NULL; + + g_return_if_fail (a_this && a_this->priv && a_sellist); + + at_media_ptr = &at_media; + status = cr_doc_handler_get_ctxt (a_this, (gpointer *) at_media_ptr); + g_return_if_fail (status == CR_OK && at_media); + g_return_if_fail (at_media->type == AT_MEDIA_RULE_STMT); + ruleset = cr_statement_new_ruleset (NULL, a_sellist, NULL, at_media); + g_return_if_fail (ruleset); + status = cr_doc_handler_set_ctxt (a_this, ruleset); + g_return_if_fail (status == CR_OK); +} + +static void +parse_at_media_property_cb (CRDocHandler * a_this, + CRString * a_name, CRTerm * a_value, + gboolean a_important) +{ + enum CRStatus status = CR_OK; + + /* + *the current ruleset stmt, child of the + *current at-media being parsed. + */ + CRStatement *stmt = NULL; + CRStatement **stmtptr = NULL; + CRDeclaration *decl = NULL; + CRString *name = NULL; + + g_return_if_fail (a_this && a_name); + + name = cr_string_dup (a_name) ; + g_return_if_fail (name); + + stmtptr = &stmt; + status = cr_doc_handler_get_ctxt (a_this, + (gpointer *) stmtptr); + g_return_if_fail (status == CR_OK && stmt); + g_return_if_fail (stmt->type == RULESET_STMT); + + decl = cr_declaration_new (stmt, name, a_value); + g_return_if_fail (decl); + decl->important = a_important; + status = cr_statement_ruleset_append_decl (stmt, decl); + g_return_if_fail (status == CR_OK); +} + +static void +parse_at_media_end_selector_cb (CRDocHandler * a_this, + CRSelector * a_sellist) +{ + enum CRStatus status = CR_OK; + + /* + *the current ruleset stmt, child of the + *current at-media being parsed. + */ + CRStatement *stmt = NULL; + CRStatement **stmtptr = NULL; + + g_return_if_fail (a_this && a_sellist); + + stmtptr = &stmt; + status = cr_doc_handler_get_ctxt (a_this, (gpointer *) stmtptr); + g_return_if_fail (status == CR_OK && stmt + && stmt->type == RULESET_STMT); + g_return_if_fail (stmt->kind.ruleset->parent_media_rule); + + status = cr_doc_handler_set_ctxt + (a_this, stmt->kind.ruleset->parent_media_rule); + g_return_if_fail (status == CR_OK); +} + +static void +parse_at_media_end_media_cb (CRDocHandler * a_this, + GList * a_media_list) +{ + enum CRStatus status = CR_OK; + CRStatement *at_media = NULL; + CRStatement **at_media_ptr = NULL; + + UNUSED(a_media_list); + + g_return_if_fail (a_this && a_this->priv); + + at_media_ptr = &at_media; + status = cr_doc_handler_get_ctxt (a_this, + (gpointer *) at_media_ptr); + g_return_if_fail (status == CR_OK && at_media); + status = cr_doc_handler_set_result (a_this, at_media); +} + +static void +parse_ruleset_start_selector_cb (CRDocHandler * a_this, + CRSelector * a_sellist) +{ + CRStatement *ruleset = NULL; + + g_return_if_fail (a_this && a_this->priv && a_sellist); + + ruleset = cr_statement_new_ruleset (NULL, a_sellist, NULL, NULL); + g_return_if_fail (ruleset); + + cr_doc_handler_set_result (a_this, ruleset); +} + +static void +parse_ruleset_unrecoverable_error_cb (CRDocHandler * a_this) +{ + CRStatement *stmt = NULL; + CRStatement **stmtptr = NULL; + enum CRStatus status = CR_OK; + + stmtptr = &stmt; + status = cr_doc_handler_get_result (a_this, (gpointer *) stmtptr); + if (status != CR_OK) { + cr_utils_trace_info ("Couldn't get parsing context. " + "This may lead to some memory leaks."); + return; + } + if (stmt) { + cr_statement_destroy (stmt); + stmt = NULL; + cr_doc_handler_set_result (a_this, NULL); + } +} + +static void +parse_ruleset_property_cb (CRDocHandler * a_this, + CRString * a_name, + CRTerm * a_value, gboolean a_important) +{ + enum CRStatus status = CR_OK; + CRStatement *ruleset = NULL; + CRStatement **rulesetptr = NULL; + CRDeclaration *decl = NULL; + CRString *stringue = NULL; + + g_return_if_fail (a_this && a_this->priv && a_name); + + stringue = cr_string_dup (a_name); + g_return_if_fail (stringue); + + rulesetptr = &ruleset; + status = cr_doc_handler_get_result (a_this, (gpointer *) rulesetptr); + g_return_if_fail (status == CR_OK + && ruleset + && ruleset->type == RULESET_STMT); + + decl = cr_declaration_new (ruleset, stringue, a_value); + g_return_if_fail (decl); + decl->important = a_important; + status = cr_statement_ruleset_append_decl (ruleset, decl); + g_return_if_fail (status == CR_OK); +} + +static void +parse_ruleset_end_selector_cb (CRDocHandler * a_this, + CRSelector * a_sellist) +{ + CRStatement *result = NULL; + CRStatement **resultptr = NULL; + enum CRStatus status = CR_OK; + + g_return_if_fail (a_this && a_sellist); + + resultptr = &result; + status = cr_doc_handler_get_result (a_this, (gpointer *) resultptr); + + g_return_if_fail (status == CR_OK + && result + && result->type == RULESET_STMT); +} + +static void +cr_statement_clear (CRStatement * a_this) +{ + g_return_if_fail (a_this); + + switch (a_this->type) { + case AT_RULE_STMT: + break; + case RULESET_STMT: + if (!a_this->kind.ruleset) + return; + if (a_this->kind.ruleset->sel_list) { + cr_selector_unref (a_this->kind.ruleset->sel_list); + a_this->kind.ruleset->sel_list = NULL; + } + if (a_this->kind.ruleset->decl_list) { + cr_declaration_destroy + (a_this->kind.ruleset->decl_list); + a_this->kind.ruleset->decl_list = NULL; + } + g_free (a_this->kind.ruleset); + a_this->kind.ruleset = NULL; + break; + + case AT_IMPORT_RULE_STMT: + if (!a_this->kind.import_rule) + return; + if (a_this->kind.import_rule->url) { + cr_string_destroy + (a_this->kind.import_rule->url) ; + a_this->kind.import_rule->url = NULL; + } + g_free (a_this->kind.import_rule); + a_this->kind.import_rule = NULL; + break; + + case AT_MEDIA_RULE_STMT: + if (!a_this->kind.media_rule) + return; + if (a_this->kind.media_rule->rulesets) { + cr_statement_destroy + (a_this->kind.media_rule->rulesets); + a_this->kind.media_rule->rulesets = NULL; + } + if (a_this->kind.media_rule->media_list) { + GList *cur = NULL; + + for (cur = a_this->kind.media_rule->media_list; + cur; cur = cur->next) { + if (cur->data) { + cr_string_destroy ((CRString *) cur->data); + cur->data = NULL; + } + + } + g_list_free (a_this->kind.media_rule->media_list); + a_this->kind.media_rule->media_list = NULL; + } + g_free (a_this->kind.media_rule); + a_this->kind.media_rule = NULL; + break; + + case AT_PAGE_RULE_STMT: + if (!a_this->kind.page_rule) + return; + + if (a_this->kind.page_rule->decl_list) { + cr_declaration_destroy + (a_this->kind.page_rule->decl_list); + a_this->kind.page_rule->decl_list = NULL; + } + if (a_this->kind.page_rule->name) { + cr_string_destroy + (a_this->kind.page_rule->name); + a_this->kind.page_rule->name = NULL; + } + if (a_this->kind.page_rule->pseudo) { + cr_string_destroy + (a_this->kind.page_rule->pseudo); + a_this->kind.page_rule->pseudo = NULL; + } + g_free (a_this->kind.page_rule); + a_this->kind.page_rule = NULL; + break; + + case AT_CHARSET_RULE_STMT: + if (!a_this->kind.charset_rule) + return; + + if (a_this->kind.charset_rule->charset) { + cr_string_destroy + (a_this->kind.charset_rule->charset); + a_this->kind.charset_rule->charset = NULL; + } + g_free (a_this->kind.charset_rule); + a_this->kind.charset_rule = NULL; + break; + + case AT_FONT_FACE_RULE_STMT: + if (!a_this->kind.font_face_rule) + return; + + if (a_this->kind.font_face_rule->decl_list) { + cr_declaration_unref + (a_this->kind.font_face_rule->decl_list); + a_this->kind.font_face_rule->decl_list = NULL; + } + g_free (a_this->kind.font_face_rule); + a_this->kind.font_face_rule = NULL; + break; + + default: + break; + } +} + +/** + *Serializes the ruleset statement into a string + *@param a_this the current instance of #CRStatement + *@param a_indent the number of whitespace to use for indentation + *@return the newly allocated serialised string. Must be freed + *by the caller, using g_free(). + */ +static gchar * +cr_statement_ruleset_to_string (CRStatement * a_this, glong a_indent) +{ + GString *stringue = NULL; + gchar *tmp_str = NULL, + *result = NULL; + + g_return_val_if_fail (a_this && a_this->type == RULESET_STMT, NULL); + + stringue = g_string_new (NULL); + + if (a_this->kind.ruleset->sel_list) { + if (a_indent) + cr_utils_dump_n_chars2 (' ', stringue, a_indent); + + tmp_str = + cr_selector_to_string (a_this->kind.ruleset-> + sel_list); + if (tmp_str) { + g_string_append (stringue, tmp_str); + g_free (tmp_str); + tmp_str = NULL; + } + } + g_string_append (stringue, " {\n"); + if (a_this->kind.ruleset->decl_list) { + tmp_str = cr_declaration_list_to_string2 + (a_this->kind.ruleset->decl_list, + a_indent + DECLARATION_INDENT_NB, TRUE); + if (tmp_str) { + g_string_append (stringue, tmp_str); + g_free (tmp_str); + tmp_str = NULL; + } + g_string_append (stringue, "\n"); + cr_utils_dump_n_chars2 (' ', stringue, a_indent); + } + g_string_append (stringue, "}"); + result = stringue->str; + + if (stringue) { + g_string_free (stringue, FALSE); + stringue = NULL; + } + if (tmp_str) { + g_free (tmp_str); + tmp_str = NULL; + } + return result; +} + + +/** + *Serializes a font face rule statement into a string. + *@param a_this the current instance of #CRStatement to consider + *It must be a font face rule statement. + *@param a_indent the number of white spaces of indentation. + *@return the serialized string. Must be deallocated by the caller + *using g_free(). + */ +static gchar * +cr_statement_font_face_rule_to_string (CRStatement * a_this, + glong a_indent) +{ + gchar *result = NULL, *tmp_str = NULL ; + GString *stringue = NULL ; + + g_return_val_if_fail (a_this + && a_this->type == AT_FONT_FACE_RULE_STMT, + NULL); + + if (a_this->kind.font_face_rule->decl_list) { + stringue = g_string_new (NULL) ; + g_return_val_if_fail (stringue, NULL) ; + if (a_indent) + cr_utils_dump_n_chars2 (' ', stringue, + a_indent); + g_string_append (stringue, "@font-face {\n"); + tmp_str = cr_declaration_list_to_string2 + (a_this->kind.font_face_rule->decl_list, + a_indent + DECLARATION_INDENT_NB, TRUE) ; + if (tmp_str) { + g_string_append (stringue, + tmp_str) ; + g_free (tmp_str) ; + tmp_str = NULL ; + } + g_string_append (stringue, "\n}"); + } + if (stringue) { + result = stringue->str ; + g_string_free (stringue, FALSE) ; + stringue = NULL ; + } + return result ; +} + + +/** + *Serialises an @charset statement into a string. + *@param a_this the statement to serialize. + *@return the serialized charset statement. Must be + *freed by the caller using g_free(). + */ +static gchar * +cr_statement_charset_to_string (CRStatement *a_this, + gulong a_indent) +{ + gchar *str = NULL ; + GString *stringue = NULL ; + + g_return_val_if_fail (a_this + && a_this->type == AT_CHARSET_RULE_STMT, + NULL) ; + + if (a_this->kind.charset_rule + && a_this->kind.charset_rule->charset + && a_this->kind.charset_rule->charset->stryng + && a_this->kind.charset_rule->charset->stryng->str) { + str = g_strndup (a_this->kind.charset_rule->charset->stryng->str, + a_this->kind.charset_rule->charset->stryng->len); + g_return_val_if_fail (str, NULL); + stringue = g_string_new (NULL) ; + g_return_val_if_fail (stringue, NULL) ; + cr_utils_dump_n_chars2 (' ', stringue, a_indent); + g_string_append_printf (stringue, + "@charset \"%s\" ;", str); + if (str) { + g_free (str); + str = NULL; + } + } + if (stringue) { + str = stringue->str ; + g_string_free (stringue, FALSE) ; + } + return str ; +} + + +/** + *Serialises the at page rule statement into a string + *@param a_this the current instance of #CRStatement. Must + *be an "@page" rule statement. + *@return the serialized string. Must be freed by the caller + */ +static gchar * +cr_statement_at_page_rule_to_string (CRStatement *a_this, + gulong a_indent) +{ + GString *stringue = NULL; + gchar *result = NULL ; + + stringue = g_string_new (NULL) ; + + cr_utils_dump_n_chars2 (' ', stringue, a_indent) ; + g_string_append (stringue, "@page"); + if (a_this->kind.page_rule->name + && a_this->kind.page_rule->name->stryng) { + g_string_append_printf + (stringue, " %s", + a_this->kind.page_rule->name->stryng->str) ; + } else { + g_string_append (stringue, " "); + } + if (a_this->kind.page_rule->pseudo + && a_this->kind.page_rule->pseudo->stryng) { + g_string_append_printf + (stringue, " :%s", + a_this->kind.page_rule->pseudo->stryng->str) ; + } + if (a_this->kind.page_rule->decl_list) { + gchar *str = NULL ; + g_string_append (stringue, " {\n"); + str = cr_declaration_list_to_string2 + (a_this->kind.page_rule->decl_list, + a_indent + DECLARATION_INDENT_NB, TRUE) ; + if (str) { + g_string_append (stringue, str) ; + g_free (str) ; + str = NULL ; + } + g_string_append (stringue, "\n}\n"); + } + result = stringue->str ; + g_string_free (stringue, FALSE) ; + stringue = NULL ; + return result ; +} + + +/** + *Serializes an @media statement. + *@param a_this the current instance of #CRStatement + *@param a_indent the number of spaces of indentation. + *@return the serialized @media statement. Must be freed + *by the caller using g_free(). + */ +static gchar * +cr_statement_media_rule_to_string (CRStatement *a_this, + gulong a_indent) +{ + gchar *str = NULL ; + GString *stringue = NULL ; + GList *cur = NULL; + + g_return_val_if_fail (a_this->type == AT_MEDIA_RULE_STMT, + NULL); + + if (a_this->kind.media_rule) { + stringue = g_string_new (NULL) ; + cr_utils_dump_n_chars2 (' ', stringue, a_indent); + g_string_append (stringue, "@media"); + + for (cur = a_this->kind.media_rule->media_list; cur; + cur = cur->next) { + if (cur->data) { + guchar *str = cr_string_dup2 + ((CRString *) cur->data); + + if (str) { + if (cur->prev) { + g_string_append + (stringue, + ","); + } + g_string_append_printf + (stringue, + " %s", str); + g_free (str); + str = NULL; + } + } + } + g_string_append (stringue, " {\n"); + str = cr_statement_list_to_string + (a_this->kind.media_rule->rulesets, + a_indent + DECLARATION_INDENT_NB) ; + if (str) { + g_string_append (stringue, str) ; + g_free (str) ; + str = NULL ; + } + g_string_append (stringue, "\n}"); + } + if (stringue) { + str = stringue->str ; + g_string_free (stringue, FALSE) ; + } + return str ; +} + + +static gchar * +cr_statement_import_rule_to_string (CRStatement *a_this, + gulong a_indent) +{ + GString *stringue = NULL ; + guchar *str = NULL; + + g_return_val_if_fail (a_this + && a_this->type == AT_IMPORT_RULE_STMT + && a_this->kind.import_rule, + NULL) ; + + if (a_this->kind.import_rule->url + && a_this->kind.import_rule->url->stryng) { + stringue = g_string_new (NULL) ; + g_return_val_if_fail (stringue, NULL) ; + str = g_strndup (a_this->kind.import_rule->url->stryng->str, + a_this->kind.import_rule->url->stryng->len); + cr_utils_dump_n_chars2 (' ', stringue, a_indent); + if (str) { + g_string_append_printf (stringue, + "@import url(\"%s\")", + str); + g_free (str); + str = NULL ; + } else /*there is no url, so no import rule, get out! */ + return NULL; + + if (a_this->kind.import_rule->media_list) { + GList *cur = NULL; + + for (cur = a_this->kind.import_rule->media_list; + cur; cur = cur->next) { + if (cur->data) { + CRString *crstr = cur->data; + + if (cur->prev) { + g_string_append + (stringue, ", "); + } + if (crstr + && crstr->stryng + && crstr->stryng->str) { + g_string_append_len + (stringue, + crstr->stryng->str, + crstr->stryng->len) ; + } + } + } + } + g_string_append (stringue, " ;"); + } + if (stringue) { + str = stringue->str ; + g_string_free (stringue, FALSE) ; + stringue = NULL ; + } + return str ; +} + + +/******************* + *public functions + ******************/ + +/** + *Tries to parse a buffer and says whether if the content of the buffer + *is a css statement as defined by the "Core CSS Grammar" (chapter 4 of the + *css spec) or not. + *@param a_buf the buffer to parse. + *@param a_encoding the character encoding of a_buf. + *@return TRUE if the buffer parses against the core grammar, false otherwise. + */ +gboolean +cr_statement_does_buf_parses_against_core (const guchar * a_buf, + enum CREncoding a_encoding) +{ + CRParser *parser = NULL; + enum CRStatus status = CR_OK; + gboolean result = FALSE; + + parser = cr_parser_new_from_buf ((guchar*)a_buf, strlen (a_buf), + a_encoding, FALSE); + g_return_val_if_fail (parser, FALSE); + + status = cr_parser_set_use_core_grammar (parser, TRUE); + if (status != CR_OK) { + goto cleanup; + } + + status = cr_parser_parse_statement_core (parser); + if (status == CR_OK) { + result = TRUE; + } + + cleanup: + if (parser) { + cr_parser_destroy (parser); + } + + return result; +} + +/** + *Parses a buffer that contains a css statement and returns + *an instance of #CRStatement in case of successfull parsing. + *TODO: at support of "@import" rules. + *@param a_buf the buffer to parse. + *@param a_encoding the character encoding of a_buf. + *@return the newly built instance of #CRStatement in case + *of successfull parsing, NULL otherwise. + */ +CRStatement * +cr_statement_parse_from_buf (const guchar * a_buf, enum CREncoding a_encoding) +{ + CRStatement *result = NULL; + + /* + *The strategy of this function is "brute force". + *It tries to parse all the types of #CRStatement it knows about. + *I could do this a smarter way but I don't have the time now. + *I think I will revisit this when time of performances and + *pull based incremental parsing comes. + */ + + result = cr_statement_ruleset_parse_from_buf (a_buf, a_encoding); + if (!result) { + result = cr_statement_at_charset_rule_parse_from_buf + (a_buf, a_encoding); + } else { + goto out; + } + + if (!result) { + result = cr_statement_at_media_rule_parse_from_buf + (a_buf, a_encoding); + } else { + goto out; + } + + if (!result) { + result = cr_statement_at_charset_rule_parse_from_buf + (a_buf, a_encoding); + } else { + goto out; + } + + if (!result) { + result = cr_statement_font_face_rule_parse_from_buf + (a_buf, a_encoding); + + } else { + goto out; + } + + if (!result) { + result = cr_statement_at_page_rule_parse_from_buf + (a_buf, a_encoding); + } else { + goto out; + } + + if (!result) { + result = cr_statement_at_import_rule_parse_from_buf + (a_buf, a_encoding); + } else { + goto out; + } + + out: + return result; +} + +/** + *Parses a buffer that contains a ruleset statement and instanciates + *a #CRStatement of type RULESET_STMT. + *@param a_buf the buffer to parse. + *@param a_enc the character encoding of a_buf. + *@param the newly built instance of #CRStatement in case of successful parsing, + *NULL otherwise. + */ +CRStatement * +cr_statement_ruleset_parse_from_buf (const guchar * a_buf, + enum CREncoding a_enc) +{ + enum CRStatus status = CR_OK; + CRStatement *result = NULL; + CRStatement **resultptr = NULL; + CRParser *parser = NULL; + CRDocHandler *sac_handler = NULL; + + g_return_val_if_fail (a_buf, NULL); + + parser = cr_parser_new_from_buf ((guchar*)a_buf, strlen (a_buf), + a_enc, FALSE); + + g_return_val_if_fail (parser, NULL); + + sac_handler = cr_doc_handler_new (); + g_return_val_if_fail (sac_handler, NULL); + + sac_handler->start_selector = parse_ruleset_start_selector_cb; + sac_handler->end_selector = parse_ruleset_end_selector_cb; + sac_handler->property = parse_ruleset_property_cb; + sac_handler->unrecoverable_error = + parse_ruleset_unrecoverable_error_cb; + + cr_parser_set_sac_handler (parser, sac_handler); + cr_parser_try_to_skip_spaces_and_comments (parser); + status = cr_parser_parse_ruleset (parser); + if (status != CR_OK) { + goto cleanup; + } + + resultptr = &result; + status = cr_doc_handler_get_result (sac_handler, + (gpointer *) resultptr); + if (!((status == CR_OK) && result)) { + if (result) { + cr_statement_destroy (result); + result = NULL; + } + } + + cleanup: + if (parser) { + cr_parser_destroy (parser); + parser = NULL; + sac_handler = NULL ; + } + if (sac_handler) { + cr_doc_handler_unref (sac_handler); + sac_handler = NULL; + } + return result; +} + +/** + *Creates a new instance of #CRStatement of type + *#CRRulSet. + *@param a_sel_list the list of #CRSimpleSel (selectors) + *the rule applies to. + *@param a_decl_list the list of instances of #CRDeclaration + *that composes the ruleset. + *@param a_media_types a list of instances of GString that + *describe the media list this ruleset applies to. + *@return the new instance of #CRStatement or NULL if something + *went wrong. + */ +CRStatement * +cr_statement_new_ruleset (CRStyleSheet * a_sheet, + CRSelector * a_sel_list, + CRDeclaration * a_decl_list, + CRStatement * a_parent_media_rule) +{ + CRStatement *result = NULL; + + g_return_val_if_fail (a_sel_list, NULL); + + if (a_parent_media_rule) { + g_return_val_if_fail + (a_parent_media_rule->type == AT_MEDIA_RULE_STMT, + NULL); + g_return_val_if_fail (a_parent_media_rule->kind.media_rule, + NULL); + } + + result = g_try_malloc (sizeof (CRStatement)); + + if (!result) { + cr_utils_trace_info ("Out of memory"); + return NULL; + } + + memset (result, 0, sizeof (CRStatement)); + result->type = RULESET_STMT; + result->kind.ruleset = g_try_malloc (sizeof (CRRuleSet)); + + if (!result->kind.ruleset) { + cr_utils_trace_info ("Out of memory"); + if (result) + g_free (result); + return NULL; + } + + memset (result->kind.ruleset, 0, sizeof (CRRuleSet)); + result->kind.ruleset->sel_list = a_sel_list; + if (a_sel_list) + cr_selector_ref (a_sel_list); + result->kind.ruleset->decl_list = a_decl_list; + + if (a_parent_media_rule) { + result->kind.ruleset->parent_media_rule = a_parent_media_rule; + a_parent_media_rule->kind.media_rule->rulesets = + cr_statement_append + (a_parent_media_rule->kind.media_rule->rulesets, + result); + } + + cr_statement_set_parent_sheet (result, a_sheet); + + return result; +} + +/** + *Parses a buffer that contains an "@media" declaration + *and builds an @media css statement. + *@param a_buf the input to parse. + *@param a_enc the encoding of the buffer. + *@return the @media statement, or NULL if the buffer could not + *be successfully parsed. + */ +CRStatement * +cr_statement_at_media_rule_parse_from_buf (const guchar * a_buf, + enum CREncoding a_enc) +{ + CRParser *parser = NULL; + CRStatement *result = NULL; + CRStatement **resultptr = NULL; + CRDocHandler *sac_handler = NULL; + enum CRStatus status = CR_OK; + + parser = cr_parser_new_from_buf ((guchar*)a_buf, strlen (a_buf), + a_enc, FALSE); + if (!parser) { + cr_utils_trace_info ("Instanciation of the parser failed"); + goto cleanup; + } + + sac_handler = cr_doc_handler_new (); + if (!sac_handler) { + cr_utils_trace_info + ("Instanciation of the sac handler failed"); + goto cleanup; + } + + sac_handler->start_media = parse_at_media_start_media_cb; + sac_handler->start_selector = parse_at_media_start_selector_cb; + sac_handler->property = parse_at_media_property_cb; + sac_handler->end_selector = parse_at_media_end_selector_cb; + sac_handler->end_media = parse_at_media_end_media_cb; + sac_handler->unrecoverable_error = + parse_at_media_unrecoverable_error_cb; + + status = cr_parser_set_sac_handler (parser, sac_handler); + if (status != CR_OK) + goto cleanup; + + status = cr_parser_try_to_skip_spaces_and_comments (parser); + if (status != CR_OK) + goto cleanup; + + status = cr_parser_parse_media (parser); + if (status != CR_OK) + goto cleanup; + + resultptr = &result; + status = cr_doc_handler_get_result (sac_handler, + (gpointer *) resultptr); + if (status != CR_OK) + goto cleanup; + + cleanup: + + if (parser) { + cr_parser_destroy (parser); + parser = NULL; + sac_handler = NULL ; + } + if (sac_handler) { + cr_doc_handler_unref (sac_handler); + sac_handler = NULL; + } + + return result; +} + +/** + *Instanciates an instance of #CRStatement of type + *AT_MEDIA_RULE_STMT (@media ruleset). + *@param a_ruleset the ruleset statements contained + *in the @media rule. + *@param a_media, the media string list. A list of GString pointers. + */ +CRStatement * +cr_statement_new_at_media_rule (CRStyleSheet * a_sheet, + CRStatement * a_rulesets, GList * a_media) +{ + CRStatement *result = NULL, + *cur = NULL; + + if (a_rulesets) + g_return_val_if_fail (a_rulesets->type == RULESET_STMT, NULL); + + result = g_try_malloc (sizeof (CRStatement)); + + if (!result) { + cr_utils_trace_info ("Out of memory"); + return NULL; + } + + memset (result, 0, sizeof (CRStatement)); + result->type = AT_MEDIA_RULE_STMT; + + result->kind.media_rule = g_try_malloc (sizeof (CRAtMediaRule)); + if (!result->kind.media_rule) { + cr_utils_trace_info ("Out of memory"); + g_free (result); + return NULL; + } + memset (result->kind.media_rule, 0, sizeof (CRAtMediaRule)); + result->kind.media_rule->rulesets = a_rulesets; + for (cur = a_rulesets; cur; cur = cur->next) { + if (cur->type != RULESET_STMT || !cur->kind.ruleset) { + cr_utils_trace_info ("Bad parameter a_rulesets. " + "It should be a list of " + "correct ruleset statement only !"); + goto error; + } + cur->kind.ruleset->parent_media_rule = result; + } + + result->kind.media_rule->media_list = a_media; + if (a_sheet) { + cr_statement_set_parent_sheet (result, a_sheet); + } + + return result; + + error: + return NULL; +} + +/** + *Creates a new instance of #CRStatment of type + *#CRAtImportRule. + *@param a_url the url to connect to the get the file + *to be imported. + *@param a_sheet the imported parsed stylesheet. + *@return the newly built instance of #CRStatement. + */ +CRStatement * +cr_statement_new_at_import_rule (CRStyleSheet * a_container_sheet, + CRString * a_url, + GList * a_media_list, + CRStyleSheet * a_imported_sheet) +{ + CRStatement *result = NULL; + + result = g_try_malloc (sizeof (CRStatement)); + + if (!result) { + cr_utils_trace_info ("Out of memory"); + return NULL; + } + + memset (result, 0, sizeof (CRStatement)); + result->type = AT_IMPORT_RULE_STMT; + + result->kind.import_rule = g_try_malloc (sizeof (CRAtImportRule)); + + if (!result->kind.import_rule) { + cr_utils_trace_info ("Out of memory"); + g_free (result); + return NULL; + } + + memset (result->kind.import_rule, 0, sizeof (CRAtImportRule)); + result->kind.import_rule->url = a_url; + result->kind.import_rule->media_list = a_media_list; + result->kind.import_rule->sheet = a_imported_sheet; + if (a_container_sheet) + cr_statement_set_parent_sheet (result, a_container_sheet); + + return result; +} + +/** + *Parses a buffer that contains an "@import" rule and + *instanciate a #CRStatement of type AT_IMPORT_RULE_STMT + *@param a_buf the buffer to parse. + *@param a_encoding the encoding of a_buf. + *@return the newly built instance of #CRStatement in case of + *a successfull parsing, NULL otherwise. + */ +CRStatement * +cr_statement_at_import_rule_parse_from_buf (const guchar * a_buf, + enum CREncoding a_encoding) +{ + enum CRStatus status = CR_OK; + CRParser *parser = NULL; + CRStatement *result = NULL; + GList *media_list = NULL; + CRString *import_string = NULL; + CRParsingLocation location = {0,0,0} ; + + parser = cr_parser_new_from_buf ((guchar*)a_buf, strlen (a_buf), + a_encoding, FALSE); + if (!parser) { + cr_utils_trace_info ("Instanciation of parser failed."); + goto cleanup; + } + + status = cr_parser_try_to_skip_spaces_and_comments (parser); + if (status != CR_OK) + goto cleanup; + + status = cr_parser_parse_import (parser, + &media_list, + &import_string, + &location); + if (status != CR_OK || !import_string) + goto cleanup; + + result = cr_statement_new_at_import_rule (NULL, import_string, + media_list, NULL); + if (result) { + cr_parsing_location_copy (&result->location, + &location) ; + import_string = NULL; + media_list = NULL; + } + + cleanup: + if (parser) { + cr_parser_destroy (parser); + parser = NULL; + } + if (media_list) { + GList *cur = NULL; + + for (cur = media_list; media_list; + media_list = g_list_next (media_list)) { + if (media_list->data) { + cr_string_destroy ((CRString*)media_list->data); + media_list->data = NULL; + } + } + g_list_free (media_list); + media_list = NULL; + } + if (import_string) { + cr_string_destroy (import_string); + import_string = NULL; + } + + return result; +} + +/** + *Creates a new instance of #CRStatement of type + *#CRAtPageRule. + *@param a_decl_list a list of instances of #CRDeclarations + *which is actually the list of declarations that applies to + *this page rule. + *@param a_selector the page rule selector. + *@return the newly built instance of #CRStatement or NULL + *in case of error. + */ +CRStatement * +cr_statement_new_at_page_rule (CRStyleSheet * a_sheet, + CRDeclaration * a_decl_list, + CRString * a_name, CRString * a_pseudo) +{ + CRStatement *result = NULL; + + result = g_try_malloc (sizeof (CRStatement)); + + if (!result) { + cr_utils_trace_info ("Out of memory"); + return NULL; + } + + memset (result, 0, sizeof (CRStatement)); + result->type = AT_PAGE_RULE_STMT; + + result->kind.page_rule = g_try_malloc (sizeof (CRAtPageRule)); + + if (!result->kind.page_rule) { + cr_utils_trace_info ("Out of memory"); + g_free (result); + return NULL; + } + + memset (result->kind.page_rule, 0, sizeof (CRAtPageRule)); + if (a_decl_list) { + result->kind.page_rule->decl_list = a_decl_list; + cr_declaration_ref (a_decl_list); + } + result->kind.page_rule->name = a_name; + result->kind.page_rule->pseudo = a_pseudo; + if (a_sheet) + cr_statement_set_parent_sheet (result, a_sheet); + + return result; +} + +/** + *Parses a buffer that contains an "@page" production and, + *if the parsing succeeds, builds the page statement. + *@param a_buf the character buffer to parse. + *@param a_encoding the character encoding of a_buf. + *@return the newly built at page statement in case of successfull parsing, + *NULL otherwise. + */ +CRStatement * +cr_statement_at_page_rule_parse_from_buf (const guchar * a_buf, + enum CREncoding a_encoding) +{ + enum CRStatus status = CR_OK; + CRParser *parser = NULL; + CRDocHandler *sac_handler = NULL; + CRStatement *result = NULL; + CRStatement **resultptr = NULL; + + g_return_val_if_fail (a_buf, NULL); + + parser = cr_parser_new_from_buf ((guchar*)a_buf, strlen (a_buf), + a_encoding, FALSE); + if (!parser) { + cr_utils_trace_info ("Instanciation of the parser failed."); + goto cleanup; + } + + sac_handler = cr_doc_handler_new (); + if (!sac_handler) { + cr_utils_trace_info + ("Instanciation of the sac handler failed."); + goto cleanup; + } + + sac_handler->start_page = parse_page_start_page_cb; + sac_handler->property = parse_page_property_cb; + sac_handler->end_page = parse_page_end_page_cb; + sac_handler->unrecoverable_error = parse_page_unrecoverable_error_cb; + + status = cr_parser_set_sac_handler (parser, sac_handler); + if (status != CR_OK) + goto cleanup; + + /*Now, invoke the parser to parse the "@page production" */ + cr_parser_try_to_skip_spaces_and_comments (parser); + if (status != CR_OK) + goto cleanup; + status = cr_parser_parse_page (parser); + if (status != CR_OK) + goto cleanup; + + resultptr = &result; + status = cr_doc_handler_get_result (sac_handler, + (gpointer *) resultptr); + + cleanup: + + if (parser) { + cr_parser_destroy (parser); + parser = NULL; + sac_handler = NULL ; + } + if (sac_handler) { + cr_doc_handler_unref (sac_handler); + sac_handler = NULL; + } + return result; +} + +/** + *Creates a new instance of #CRStatement of type + *#CRAtCharsetRule. + *@param a_charset the string representing the charset. + *Note that the newly built instance of #CRStatement becomes + *the owner of a_charset. The caller must not free a_charset !!!. + *@return the newly built instance of #CRStatement or NULL + *if an error arises. + */ +CRStatement * +cr_statement_new_at_charset_rule (CRStyleSheet * a_sheet, + CRString * a_charset) +{ + CRStatement *result = NULL; + + g_return_val_if_fail (a_charset, NULL); + + result = g_try_malloc (sizeof (CRStatement)); + + if (!result) { + cr_utils_trace_info ("Out of memory"); + return NULL; + } + + memset (result, 0, sizeof (CRStatement)); + result->type = AT_CHARSET_RULE_STMT; + + result->kind.charset_rule = g_try_malloc (sizeof (CRAtCharsetRule)); + + if (!result->kind.charset_rule) { + cr_utils_trace_info ("Out of memory"); + g_free (result); + return NULL; + } + memset (result->kind.charset_rule, 0, sizeof (CRAtCharsetRule)); + result->kind.charset_rule->charset = a_charset; + cr_statement_set_parent_sheet (result, a_sheet); + + return result; +} + +/** + *Parses a buffer that contains an '@charset' rule and + *creates an instance of #CRStatement of type AT_CHARSET_RULE_STMT. + *@param a_buf the buffer to parse. + *@param the character encoding of the buffer. + *@return the newly built instance of #CRStatement. + */ +CRStatement * +cr_statement_at_charset_rule_parse_from_buf (const guchar * a_buf, + enum CREncoding a_encoding) +{ + enum CRStatus status = CR_OK; + CRParser *parser = NULL; + CRStatement *result = NULL; + CRString *charset = NULL; + + g_return_val_if_fail (a_buf, NULL); + + parser = cr_parser_new_from_buf ((guchar*)a_buf, strlen (a_buf), + a_encoding, FALSE); + if (!parser) { + cr_utils_trace_info ("Instanciation of the parser failed."); + goto cleanup; + } + + /*Now, invoke the parser to parse the "@charset production" */ + cr_parser_try_to_skip_spaces_and_comments (parser); + if (status != CR_OK) + goto cleanup; + status = cr_parser_parse_charset (parser, &charset, NULL); + if (status != CR_OK || !charset) + goto cleanup; + + result = cr_statement_new_at_charset_rule (NULL, charset); + if (result) + charset = NULL; + + cleanup: + + if (parser) { + cr_parser_destroy (parser); + parser = NULL; + } + if (charset) { + cr_string_destroy (charset); + } + + return result; +} + +/** + *Creates an instance of #CRStatement of type #CRAtFontFaceRule. + *@param a_font_decls a list of instances of #CRDeclaration. Each declaration + *is actually a font declaration. + *@return the newly built instance of #CRStatement. + */ +CRStatement * +cr_statement_new_at_font_face_rule (CRStyleSheet * a_sheet, + CRDeclaration * a_font_decls) +{ + CRStatement *result = NULL; + + result = g_try_malloc (sizeof (CRStatement)); + + if (!result) { + cr_utils_trace_info ("Out of memory"); + return NULL; + } + memset (result, 0, sizeof (CRStatement)); + result->type = AT_FONT_FACE_RULE_STMT; + + result->kind.font_face_rule = g_try_malloc + (sizeof (CRAtFontFaceRule)); + + if (!result->kind.font_face_rule) { + cr_utils_trace_info ("Out of memory"); + g_free (result); + return NULL; + } + memset (result->kind.font_face_rule, 0, sizeof (CRAtFontFaceRule)); + + result->kind.font_face_rule->decl_list = a_font_decls; + if (a_sheet) + cr_statement_set_parent_sheet (result, a_sheet); + + return result; +} + +/** + *Parses a buffer that contains an "@font-face" rule and builds + *an instance of #CRStatement of type AT_FONT_FACE_RULE_STMT out of it. + *@param a_buf the buffer to parse. + *@param a_encoding the character encoding of a_buf. + *@return the newly built instance of #CRStatement in case of successufull + *parsing, NULL otherwise. + */ +CRStatement * +cr_statement_font_face_rule_parse_from_buf (const guchar * a_buf, + enum CREncoding a_encoding) +{ + CRStatement *result = NULL; + CRStatement **resultptr = NULL; + CRParser *parser = NULL; + CRDocHandler *sac_handler = NULL; + enum CRStatus status = CR_OK; + + parser = cr_parser_new_from_buf ((guchar*)a_buf, strlen (a_buf), + a_encoding, FALSE); + if (!parser) + goto cleanup; + + sac_handler = cr_doc_handler_new (); + if (!sac_handler) + goto cleanup; + + /* + *set sac callbacks here + */ + sac_handler->start_font_face = parse_font_face_start_font_face_cb; + sac_handler->property = parse_font_face_property_cb; + sac_handler->end_font_face = parse_font_face_end_font_face_cb; + sac_handler->unrecoverable_error = + parse_font_face_unrecoverable_error_cb; + + status = cr_parser_set_sac_handler (parser, sac_handler); + if (status != CR_OK) + goto cleanup; + + /* + *cleanup spaces of comment that may be there before the real + *"@font-face" thing. + */ + status = cr_parser_try_to_skip_spaces_and_comments (parser); + if (status != CR_OK) + goto cleanup; + + status = cr_parser_parse_font_face (parser); + if (status != CR_OK) + goto cleanup; + + resultptr = &result; + status = cr_doc_handler_get_result (sac_handler, + (gpointer *) resultptr); + if (status != CR_OK || !result) + goto cleanup; + + cleanup: + if (parser) { + cr_parser_destroy (parser); + parser = NULL; + sac_handler = NULL ; + } + if (sac_handler) { + cr_doc_handler_unref (sac_handler); + sac_handler = NULL; + } + return result; +} + +/** + *Sets the container stylesheet. + *@param a_this the current instance of #CRStatement. + *@param a_sheet the sheet that contains the current statement. + *@return CR_OK upon successfull completion, an errror code otherwise. + */ +enum CRStatus +cr_statement_set_parent_sheet (CRStatement * a_this, CRStyleSheet * a_sheet) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + a_this->parent_sheet = a_sheet; + return CR_OK; +} + +/** + *Gets the sheets that contains the current statement. + *@param a_this the current #CRStatement. + *@param a_sheet out parameter. A pointer to the sheets that + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_statement_get_parent_sheet (CRStatement * a_this, CRStyleSheet ** a_sheet) +{ + g_return_val_if_fail (a_this && a_sheet, CR_BAD_PARAM_ERROR); + *a_sheet = a_this->parent_sheet; + return CR_OK; +} + +/** + *Appends a new statement to the statement list. + *@param a_this the current instance of the statement list. + *@param a_this a_new the new instance of #CRStatement to append. + *@return the new list statement list, or NULL in cas of failure. + */ +CRStatement * +cr_statement_append (CRStatement * a_this, CRStatement * a_new) +{ + CRStatement *cur = NULL; + + g_return_val_if_fail (a_new, NULL); + + if (!a_this) { + return a_new; + } + + /*walk forward in the current list to find the tail list element */ + for (cur = a_this; cur && cur->next; cur = cur->next) ; + + cur->next = a_new; + a_new->prev = cur; + + return a_this; +} + +/** + *Prepends the an instance of #CRStatement to + *the current statement list. + *@param a_this the current instance of #CRStatement. + *@param a_new the new statement to prepend. + *@return the new list with the new statement prepended, + *or NULL in case of an error. + */ +CRStatement * +cr_statement_prepend (CRStatement * a_this, CRStatement * a_new) +{ + CRStatement *cur = NULL; + + g_return_val_if_fail (a_new, NULL); + + if (!a_this) + return a_new; + + a_new->next = a_this; + a_this->prev = a_new; + + /*walk backward in the prepended list to find the head list element */ + for (cur = a_new; cur && cur->prev; cur = cur->prev) ; + + return cur; +} + +/** + *Unlinks a statement from the statements list. + *@param a_this the current statements list. + *@param a_to_unlink the statement to unlink from the list. + *@return the new list where a_to_unlink has been unlinked + *from, or NULL in case of error. + */ +CRStatement * +cr_statement_unlink (CRStatement * a_stmt) +{ + CRStatement *result = a_stmt; + + g_return_val_if_fail (result, NULL); + + /** + *Some sanity checks first + */ + if (a_stmt->next) { + g_return_val_if_fail (a_stmt->next->prev == a_stmt, NULL); + } + if (a_stmt->prev) { + g_return_val_if_fail (a_stmt->prev->next == a_stmt, NULL); + } + + /** + *Now, the real unlinking job. + */ + if (a_stmt->next) { + a_stmt->next->prev = a_stmt->prev; + } + if (a_stmt->prev) { + a_stmt->prev->next = a_stmt->next; + } + + if (a_stmt->parent_sheet + && a_stmt->parent_sheet->statements == a_stmt) { + a_stmt->parent_sheet->statements = + a_stmt->parent_sheet->statements->next; + } + + a_stmt->next = NULL; + a_stmt->prev = NULL; + a_stmt->parent_sheet = NULL; + + return result; +} + +/** + *Return the number of rules in the statement list; + *@param a_this the current instance of #CRStatement. + *@return number of rules in the statement list. + */ +gint +cr_statement_nr_rules (CRStatement * a_this) +{ + CRStatement *cur = NULL; + int nr = 0; + + g_return_val_if_fail (a_this, -1); + + for (cur = a_this; cur; cur = cur->next) + nr++; + return nr; +} + +/** + *Use an index to get a CRStatement from the statement list. + *@param a_this the current instance of #CRStatement. + *@param itemnr the index into the statement list. + *@return CRStatement at position itemnr, if itemnr > number of statements - 1, + *it will return NULL. + */ +CRStatement * +cr_statement_get_from_list (CRStatement * a_this, int itemnr) +{ + CRStatement *cur = NULL; + int nr = 0; + + g_return_val_if_fail (a_this, NULL); + + for (cur = a_this; cur; cur = cur->next) + if (nr++ == itemnr) + return cur; + return NULL; +} + +/** + *Sets a selector list to a ruleset statement. + *@param a_this the current ruleset statement. + *@param a_sel_list the selector list to set. Note + *that this function increments the ref count of a_sel_list. + *The sel list will be destroyed at the destruction of the + *current instance of #CRStatement. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_statement_ruleset_set_sel_list (CRStatement * a_this, + CRSelector * a_sel_list) +{ + g_return_val_if_fail (a_this && a_this->type == RULESET_STMT, + CR_BAD_PARAM_ERROR); + + if (a_this->kind.ruleset->sel_list) + cr_selector_unref (a_this->kind.ruleset->sel_list); + + a_this->kind.ruleset->sel_list = a_sel_list; + + if (a_sel_list) + cr_selector_ref (a_sel_list); + + return CR_OK; +} + +/** + *Gets a pointer to the list of declaration contained + *in the ruleset statement. + *@param a_this the current instance of #CRStatement. + *@a_decl_list out parameter. A pointer to the the returned + *list of declaration. Must not be NULL. + *@return CR_OK upon successfull completion, an error code if something + *bad happened. + */ +enum CRStatus +cr_statement_ruleset_get_declarations (CRStatement * a_this, + CRDeclaration ** a_decl_list) +{ + g_return_val_if_fail (a_this + && a_this->type == RULESET_STMT + && a_this->kind.ruleset + && a_decl_list, CR_BAD_PARAM_ERROR); + + *a_decl_list = a_this->kind.ruleset->decl_list; + + return CR_OK; +} + +/** + *Gets a pointer to the selector list contained in + *the current ruleset statement. + *@param a_this the current ruleset statement. + *@param a_list out parameter. The returned selector list, + *if and only if the function returned CR_OK. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_statement_ruleset_get_sel_list (CRStatement * a_this, CRSelector ** a_list) +{ + g_return_val_if_fail (a_this && a_this->type == RULESET_STMT + && a_this->kind.ruleset, CR_BAD_PARAM_ERROR); + + *a_list = a_this->kind.ruleset->sel_list; + + return CR_OK; +} + +/** + *Sets a declaration list to the current ruleset statement. + *@param a_this the current ruleset statement. + *@param a_list the declaration list to be added to the current + *ruleset statement. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_statement_ruleset_set_decl_list (CRStatement * a_this, + CRDeclaration * a_list) +{ + g_return_val_if_fail (a_this && a_this->type == RULESET_STMT + && a_this->kind.ruleset, CR_BAD_PARAM_ERROR); + + if (a_this->kind.ruleset->decl_list == a_list) + return CR_OK; + + if (a_this->kind.ruleset->sel_list) { + cr_declaration_destroy (a_this->kind.ruleset->decl_list); + } + + a_this->kind.ruleset->sel_list = NULL; + + return CR_OK; +} + +/** + *Appends a declaration to the current ruleset statement. + *@param a_this the current statement. + *@param a_prop the property of the declaration. + *@param a_value the value of the declaration. + *@return CR_OK uppon successfull completion, an error code + *otherwise. + */ +enum CRStatus +cr_statement_ruleset_append_decl2 (CRStatement * a_this, + CRString * a_prop, + CRTerm * a_value) +{ + CRDeclaration *new_decls = NULL; + + g_return_val_if_fail (a_this && a_this->type == RULESET_STMT + && a_this->kind.ruleset, CR_BAD_PARAM_ERROR); + + new_decls = cr_declaration_append2 + (a_this->kind.ruleset->decl_list, + a_prop, a_value); + g_return_val_if_fail (new_decls, CR_ERROR); + a_this->kind.ruleset->decl_list = new_decls; + + return CR_OK; +} + +/** + *Appends a declaration to the current statement. + *@param a_this the current statement. + *@param a_declaration the declaration to append. + *@return CR_OK upon sucessfull completion, an error code + *otherwise. + */ +enum CRStatus +cr_statement_ruleset_append_decl (CRStatement * a_this, + CRDeclaration * a_decl) +{ + CRDeclaration *new_decls = NULL; + + g_return_val_if_fail (a_this && a_this->type == RULESET_STMT + && a_this->kind.ruleset, CR_BAD_PARAM_ERROR); + + new_decls = cr_declaration_append + (a_this->kind.ruleset->decl_list, a_decl); + g_return_val_if_fail (new_decls, CR_ERROR); + a_this->kind.ruleset->decl_list = new_decls; + + return CR_OK; +} + +/** + *Sets a stylesheet to the current @import rule. + *@param a_this the current @import rule. + *@param a_sheet the stylesheet. The stylesheet is owned + *by the current instance of #CRStatement, that is, the + *stylesheet will be destroyed when the current instance + *of #CRStatement will be destroyed. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_statement_at_import_rule_set_imported_sheet (CRStatement * a_this, + CRStyleSheet * a_sheet) +{ + g_return_val_if_fail (a_this + && a_this->type == AT_IMPORT_RULE_STMT + && a_this->kind.import_rule, + CR_BAD_PARAM_ERROR); + + a_this->kind.import_rule->sheet = a_sheet; + + return CR_OK; +} + +/** + *Gets the stylesheet contained by the @import rule statement. + *@param a_this the current @import rule statement. + *@param a_sheet out parameter. The returned stylesheet if and + *only if the function returns CR_OK. + *@return CR_OK upon sucessfull completion, an error code otherwise. + */ +enum CRStatus +cr_statement_at_import_rule_get_imported_sheet (CRStatement * a_this, + CRStyleSheet ** a_sheet) +{ + g_return_val_if_fail (a_this + && a_this->type == AT_IMPORT_RULE_STMT + && a_this->kind.import_rule, + CR_BAD_PARAM_ERROR); + *a_sheet = a_this->kind.import_rule->sheet; + return CR_OK; + +} + +/** + *Sets an url to the current @import rule statement. + *@param a_this the current @import rule statement. + *@param a_url the url to set. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_statement_at_import_rule_set_url (CRStatement * a_this, + CRString * a_url) +{ + g_return_val_if_fail (a_this + && a_this->type == AT_IMPORT_RULE_STMT + && a_this->kind.import_rule, + CR_BAD_PARAM_ERROR); + + if (a_this->kind.import_rule->url) { + cr_string_destroy (a_this->kind.import_rule->url); + } + + a_this->kind.import_rule->url = a_url; + + return CR_OK; +} + +/** + *Gets the url of the @import rule statement. + *@param the current @import rule statement. + *@param a_url out parameter. The returned url if + *and only if the function returned CR_OK. + */ +enum CRStatus +cr_statement_at_import_rule_get_url (CRStatement * a_this, + CRString ** a_url) +{ + g_return_val_if_fail (a_this + && a_this->type == AT_IMPORT_RULE_STMT + && a_this->kind.import_rule, + CR_BAD_PARAM_ERROR); + + *a_url = a_this->kind.import_rule->url; + + return CR_OK; +} + +/** + *Return the number of rules in the media rule; + *@param a_this the current instance of #CRStatement. + *@return number of rules in the media rule. + */ +int +cr_statement_at_media_nr_rules (CRStatement * a_this) +{ + g_return_val_if_fail (a_this + && a_this->type == AT_MEDIA_RULE_STMT + && a_this->kind.media_rule, CR_BAD_PARAM_ERROR); + + return cr_statement_nr_rules (a_this->kind.media_rule->rulesets); +} + +/** + *Use an index to get a CRStatement from the media rule list of rules. + *@param a_this the current instance of #CRStatement. + *@param itemnr the index into the media rule list of rules. + *@return CRStatement at position itemnr, if itemnr > number of rules - 1, + *it will return NULL. + */ +CRStatement * +cr_statement_at_media_get_from_list (CRStatement * a_this, int itemnr) +{ + g_return_val_if_fail (a_this + && a_this->type == AT_MEDIA_RULE_STMT + && a_this->kind.media_rule, NULL); + + return cr_statement_get_from_list (a_this->kind.media_rule->rulesets, + itemnr); +} + +/** + *Sets a declaration list to the current @page rule statement. + *@param a_this the current @page rule statement. + *@param a_decl_list the declaration list to add. Will be freed + *by the current instance of #CRStatement when it is destroyed. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_statement_at_page_rule_set_declarations (CRStatement * a_this, + CRDeclaration * a_decl_list) +{ + g_return_val_if_fail (a_this + && a_this->type == AT_PAGE_RULE_STMT + && a_this->kind.page_rule, CR_BAD_PARAM_ERROR); + + if (a_this->kind.page_rule->decl_list) { + cr_declaration_unref (a_this->kind.page_rule->decl_list); + } + + a_this->kind.page_rule->decl_list = a_decl_list; + + if (a_decl_list) { + cr_declaration_ref (a_decl_list); + } + + return CR_OK; +} + +/** + *Gets the declaration list associated to the current @page rule + *statement. + *@param a_this the current @page rule statement. + *@param a_decl_list out parameter. The returned declaration list. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_statement_at_page_rule_get_declarations (CRStatement * a_this, + CRDeclaration ** a_decl_list) +{ + g_return_val_if_fail (a_this + && a_this->type == AT_PAGE_RULE_STMT + && a_this->kind.page_rule, CR_BAD_PARAM_ERROR); + + *a_decl_list = a_this->kind.page_rule->decl_list; + + return CR_OK; +} + +/** + *Sets the charset of the current @charset rule statement. + *@param a_this the current @charset rule statement. + *@param a_charset the charset to set. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_statement_at_charset_rule_set_charset (CRStatement * a_this, + CRString * a_charset) +{ + g_return_val_if_fail (a_this + && a_this->type == AT_CHARSET_RULE_STMT + && a_this->kind.charset_rule, + CR_BAD_PARAM_ERROR); + + if (a_this->kind.charset_rule->charset) { + cr_string_destroy (a_this->kind.charset_rule->charset); + } + a_this->kind.charset_rule->charset = a_charset; + return CR_OK; +} + +/** + *Gets the charset string associated to the current + *@charset rule statement. + *@param a_this the current @charset rule statement. + *@param a_charset out parameter. The returned charset string if + *and only if the function returned CR_OK. + */ +enum CRStatus +cr_statement_at_charset_rule_get_charset (CRStatement * a_this, + CRString ** a_charset) +{ + g_return_val_if_fail (a_this + && a_this->type == AT_CHARSET_RULE_STMT + && a_this->kind.charset_rule, + CR_BAD_PARAM_ERROR); + + *a_charset = a_this->kind.charset_rule->charset; + + return CR_OK; +} + +/** + *Sets a declaration list to the current @font-face rule statement. + *@param a_this the current @font-face rule statement. + *@param a_decls the declarations list to set. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_statement_at_font_face_rule_set_decls (CRStatement * a_this, + CRDeclaration * a_decls) +{ + g_return_val_if_fail (a_this + && a_this->type == AT_FONT_FACE_RULE_STMT + && a_this->kind.font_face_rule, + CR_BAD_PARAM_ERROR); + + if (a_this->kind.font_face_rule->decl_list) { + cr_declaration_unref (a_this->kind.font_face_rule->decl_list); + } + + a_this->kind.font_face_rule->decl_list = a_decls; + cr_declaration_ref (a_decls); + + return CR_OK; +} + +/** + *Gets the declaration list associated to the current instance + *of @font-face rule statement. + *@param a_this the current @font-face rule statement. + *@param a_decls out parameter. The returned declaration list if + *and only if this function returns CR_OK. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_statement_at_font_face_rule_get_decls (CRStatement * a_this, + CRDeclaration ** a_decls) +{ + g_return_val_if_fail (a_this + && a_this->type == AT_FONT_FACE_RULE_STMT + && a_this->kind.font_face_rule, + CR_BAD_PARAM_ERROR); + + *a_decls = a_this->kind.font_face_rule->decl_list; + + return CR_OK; +} + +/** + *Adds a declaration to the current @font-face rule + *statement. + *@param a_this the current @font-face rule statement. + *@param a_prop the property of the declaration. + *@param a_value the value of the declaration. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_statement_at_font_face_rule_add_decl (CRStatement * a_this, + CRString * a_prop, CRTerm * a_value) +{ + CRDeclaration *decls = NULL; + + g_return_val_if_fail (a_this + && a_this->type == AT_FONT_FACE_RULE_STMT + && a_this->kind.font_face_rule, + CR_BAD_PARAM_ERROR); + + decls = cr_declaration_append2 + (a_this->kind.font_face_rule->decl_list, + a_prop, a_value); + + g_return_val_if_fail (decls, CR_ERROR); + + if (a_this->kind.font_face_rule->decl_list == NULL) + cr_declaration_ref (decls); + + a_this->kind.font_face_rule->decl_list = decls; + + return CR_OK; +} + +/** + *Serializes a css statement into a string + *@param a_this the current statement to serialize + *@param a_indent the number of white space of indentation. + *@return the serialized statement. Must be freed by the caller + *using g_free(). + */ +gchar * +cr_statement_to_string (CRStatement * a_this, gulong a_indent) +{ + gchar *str = NULL ; + + if (!a_this) + return NULL; + + switch (a_this->type) { + case RULESET_STMT: + str = cr_statement_ruleset_to_string + (a_this, a_indent); + break; + + case AT_FONT_FACE_RULE_STMT: + str = cr_statement_font_face_rule_to_string + (a_this, a_indent) ; + break; + + case AT_CHARSET_RULE_STMT: + str = cr_statement_charset_to_string + (a_this, a_indent); + break; + + case AT_PAGE_RULE_STMT: + str = cr_statement_at_page_rule_to_string + (a_this, a_indent); + break; + + case AT_MEDIA_RULE_STMT: + str = cr_statement_media_rule_to_string + (a_this, a_indent); + break; + + case AT_IMPORT_RULE_STMT: + str = cr_statement_import_rule_to_string + (a_this, a_indent); + break; + + default: + cr_utils_trace_info ("Statement unrecognized"); + break; + } + return str ; +} + +gchar* +cr_statement_list_to_string (CRStatement *a_this, gulong a_indent) +{ + CRStatement *cur_stmt = NULL ; + GString *stringue = NULL ; + gchar *str = NULL ; + + g_return_val_if_fail (a_this, NULL) ; + + stringue = g_string_new (NULL) ; + if (!stringue) { + cr_utils_trace_info ("Out of memory") ; + return NULL ; + } + for (cur_stmt = a_this ; cur_stmt; + cur_stmt = cur_stmt->next) { + str = cr_statement_to_string (cur_stmt, a_indent) ; + if (str) { + if (!cur_stmt->prev) { + g_string_append (stringue, str) ; + } else { + g_string_append_printf + (stringue, "\n%s", str) ; + } + g_free (str) ; + str = NULL ; + } + } + str = stringue->str ; + g_string_free (stringue, FALSE) ; + return str ; +} + +/** + *Dumps the css2 statement to a file. + *@param a_this the current css2 statement. + *@param a_fp the destination file pointer. + *@param a_indent the number of white space indentation characters. + */ +void +cr_statement_dump (CRStatement * a_this, FILE * a_fp, gulong a_indent) +{ + gchar *str = NULL ; + + if (!a_this) + return; + + str = cr_statement_to_string (a_this, a_indent) ; + if (str) { + fprintf (a_fp, "%s",str) ; + g_free (str) ; + str = NULL ; + } +} + +/** + *Dumps a ruleset statement to a file. + *@param a_this the current instance of #CRStatement. + *@param a_fp the destination file pointer. + *@param a_indent the number of indentation white spaces to add. + */ +void +cr_statement_dump_ruleset (CRStatement * a_this, FILE * a_fp, glong a_indent) +{ + guchar *str = NULL; + + g_return_if_fail (a_fp && a_this); + str = cr_statement_ruleset_to_string (a_this, a_indent); + if (str) { + fprintf (a_fp, str); + g_free (str); + str = NULL; + } +} + +/** + *Dumps a font face rule statement to a file. + *@param a_this the current instance of font face rule statement. + *@param a_fp the destination file pointer. + *@param a_indent the number of white space indentation. + */ +void +cr_statement_dump_font_face_rule (CRStatement * a_this, FILE * a_fp, + glong a_indent) +{ + gchar *str = NULL ; + g_return_if_fail (a_this + && a_this->type == AT_FONT_FACE_RULE_STMT); + + str = cr_statement_font_face_rule_to_string (a_this, + a_indent) ; + if (str) { + fprintf (a_fp, "%s", str) ; + g_free (str) ; + str = NULL ; + } +} + +/** + *Dumps an @charset rule statement to a file. + *@param a_this the current instance of the @charset rule statement. + *@param a_fp the destination file pointer. + *@param a_indent the number of indentation white spaces. + */ +void +cr_statement_dump_charset (CRStatement * a_this, FILE * a_fp, gulong a_indent) +{ + guchar *str = NULL; + + g_return_if_fail (a_this && a_this->type == AT_CHARSET_RULE_STMT); + + str = cr_statement_charset_to_string (a_this, + a_indent) ; + if (str) { + fprintf (a_fp, str) ; + g_free (str) ; + str = NULL ; + } +} + + +/** + *Dumps an @page rule statement on stdout. + *@param a_this the statement to dump on stdout. + *@param a_fp the destination file pointer. + *@param a_indent the number of indentation white spaces. + */ +void +cr_statement_dump_page (CRStatement * a_this, FILE * a_fp, gulong a_indent) +{ + guchar *str = NULL; + + g_return_if_fail (a_this + && a_this->type == AT_PAGE_RULE_STMT + && a_this->kind.page_rule); + + str = cr_statement_at_page_rule_to_string (a_this, a_indent) ; + if (str) { + fprintf (a_fp, str); + g_free (str) ; + str = NULL ; + } +} + + +/** + *Dumps an @media rule statement to a file. + *@param a_this the statement to dump. + *@param a_fp the destination file pointer + *@param a_indent the number of white spaces indentation. + */ +void +cr_statement_dump_media_rule (CRStatement * a_this, + FILE * a_fp, + gulong a_indent) +{ + gchar *str = NULL ; + g_return_if_fail (a_this->type == AT_MEDIA_RULE_STMT); + + str = cr_statement_media_rule_to_string (a_this, a_indent) ; + if (str) { + fprintf (a_fp, str) ; + g_free (str) ; + str = NULL ; + } +} + +/** + *Dumps an @import rule statement to a file. + *@param a_fp the destination file pointer. + *@param a_indent the number of white space indentations. + */ +void +cr_statement_dump_import_rule (CRStatement * a_this, FILE * a_fp, + gulong a_indent) +{ + gchar *str = NULL ; + g_return_if_fail (a_this + && a_this->type == AT_IMPORT_RULE_STMT + && a_fp + && a_this->kind.import_rule); + + str = cr_statement_import_rule_to_string (a_this, a_indent) ; + if (str) { + fprintf (a_fp, str) ; + g_free (str) ; + str = NULL ; + } +} + +/** + *Destructor of #CRStatement. + */ +void +cr_statement_destroy (CRStatement * a_this) +{ + CRStatement *cur = NULL; + + g_return_if_fail (a_this); + + /*go get the tail of the list */ + for (cur = a_this; cur && cur->next; cur = cur->next) { + cr_statement_clear (cur); + } + + if (cur) + cr_statement_clear (cur); + + if (cur->prev == NULL) { + g_free (a_this); + return; + } + + /*walk backward and free next element */ + for (cur = cur->prev; cur && cur->prev; cur = cur->prev) { + if (cur->next) { + g_free (cur->next); + cur->next = NULL; + } + } + + if (!cur) + return; + + /*free the one remaining list */ + if (cur->next) { + g_free (cur->next); + cur->next = NULL; + } + + g_free (cur); + cur = NULL; +} diff --git a/src/libcroco/cr-statement.h b/src/libcroco/cr-statement.h new file mode 100644 index 000000000..5639ab730 --- /dev/null +++ b/src/libcroco/cr-statement.h @@ -0,0 +1,440 @@ +/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli + * See COPYRIGHTS file for copyright information. + */ + +#include +#include "cr-utils.h" +#include "cr-term.h" +#include "cr-selector.h" +#include "cr-declaration.h" + +#ifndef __CR_STATEMENT_H__ +#define __CR_STATEMENT_H__ + +G_BEGIN_DECLS + +/** + *@file + *Declaration of the #CRStatement class. + */ + +/* + *forward declaration of CRStyleSheet which is defined in + *cr-stylesheet.h + */ + +struct _CRStatement ; + +/* + *typedef struct _CRStatement CRStatement ; + *this is forward declared in + *cr-declaration.h already. + */ + +struct _CRAtMediaRule ; +typedef struct _CRAtMediaRule CRAtMediaRule ; + +typedef struct _CRRuleSet CRRuleSet ; + +/** + *The abstraction of a css ruleset. + *A ruleset is made of a list of selectors, + *followed by a list of declarations. + */ +struct _CRRuleSet +{ + /**A list of instances of #CRSimpeSel*/ + CRSelector *sel_list ; + + /**A list of instances of #CRDeclaration*/ + CRDeclaration *decl_list ; + + /** + *The parent media rule, or NULL if + *no parent media rule exists. + */ + CRStatement *parent_media_rule ; +} ; + +/* + *a forward declaration of CRStylesheet. + *CRStylesheet is actually declared in + *cr-stylesheet.h + */ +struct _CRStyleSheet ; +typedef struct _CRStyleSheet CRStyleSheet; + + +/**The @import rule abstraction.*/ +typedef struct _CRAtImportRule CRAtImportRule ; +struct _CRAtImportRule +{ + /**the url of the import rule*/ + CRString *url ; + + GList *media_list ; + + /** + *the stylesheet fetched from the url, if any. + *this is not "owned" by #CRAtImportRule which means + *it is not destroyed by the destructor of #CRAtImportRule. + */ + CRStyleSheet * sheet; +}; + + +/**abstraction of an @media rule*/ +struct _CRAtMediaRule +{ + GList *media_list ; + CRStatement *rulesets ; +} ; + + +typedef struct _CRAtPageRule CRAtPageRule ; +/**The @page rule abstraction*/ +struct _CRAtPageRule +{ + /**a list of instances of #CRDeclaration*/ + CRDeclaration *decl_list ; + + /**page selector. Is a pseudo selector*/ + CRString *name ; + CRString *pseudo ; +} ; + +/**The @charset rule abstraction*/ +typedef struct _CRAtCharsetRule CRAtCharsetRule ; +struct _CRAtCharsetRule +{ + CRString * charset ; +}; + +/**The abstaction of the @font-face rule.*/ +typedef struct _CRAtFontFaceRule CRAtFontFaceRule ; +struct _CRAtFontFaceRule +{ + /*a list of instanaces of #CRDeclaration*/ + CRDeclaration *decl_list ; +} ; + + +/** + *The possible types of css2 statements. + */ +enum CRStatementType +{ + /** + *A generic css at-rule + *each unknown at-rule will + *be of this type. + */ + + /**A css at-rule*/ + AT_RULE_STMT = 0, + + /*A css ruleset*/ + RULESET_STMT, + + /**A css2 import rule*/ + AT_IMPORT_RULE_STMT, + + /**A css2 media rule*/ + AT_MEDIA_RULE_STMT, + + /**A css2 page rule*/ + AT_PAGE_RULE_STMT, + + /**A css2 charset rule*/ + AT_CHARSET_RULE_STMT, + + /**A css2 font face rule*/ + AT_FONT_FACE_RULE_STMT +} ; + + +/** + *The abstraction of css statement as defined + *in the chapter 4 and appendix D.1 of the css2 spec. + *A statement is actually a double chained list of + *statements.A statement can be a ruleset, an @import + *rule, an @page rule etc ... + */ +struct _CRStatement +{ + /** + *The type of the statement. + */ + enum CRStatementType type ; + + union + { + CRRuleSet *ruleset ; + CRAtImportRule *import_rule ; + CRAtMediaRule *media_rule ; + CRAtPageRule *page_rule ; + CRAtCharsetRule *charset_rule ; + CRAtFontFaceRule *font_face_rule ; + } kind ; + + /* + *the specificity of the selector + *that matched this statement. + *This is only used by the cascading + *order determination algorithm. + */ + gulong specificity ; + + /* + *the style sheet that contains + *this css statement. + */ + CRStyleSheet *parent_sheet ; + CRStatement *next ; + CRStatement *prev ; + + CRParsingLocation location ; + + /** + *a custom pointer useable by + *applications that use libcroco. + *libcroco itself will never modify + *this pointer. + */ + gpointer app_data ; + + /** + *a custom pointer used + *by the upper layers of libcroco. + *application should never use this + *pointer. + */ + gpointer croco_data ; + +} ; + + +gboolean +cr_statement_does_buf_parses_against_core (const guchar *a_buf, + enum CREncoding a_encoding) ; +CRStatement * +cr_statement_parse_from_buf (const guchar *a_buf, + enum CREncoding a_encoding) ; +CRStatement* +cr_statement_new_ruleset (CRStyleSheet *a_sheet, + CRSelector *a_sel_list, + CRDeclaration *a_decl_list, + CRStatement *a_media_rule) ; +CRStatement * +cr_statement_ruleset_parse_from_buf (const guchar * a_buf, + enum CREncoding a_enc) ; + +CRStatement* +cr_statement_new_at_import_rule (CRStyleSheet *a_container_sheet, + CRString *a_url, + GList *a_media_list, + CRStyleSheet *a_imported_sheet) ; + +CRStatement * +cr_statement_at_import_rule_parse_from_buf (const guchar * a_buf, + enum CREncoding a_encoding) ; + +CRStatement * +cr_statement_new_at_media_rule (CRStyleSheet *a_sheet, + CRStatement *a_ruleset, + GList *a_media) ; +CRStatement * +cr_statement_at_media_rule_parse_from_buf (const guchar *a_buf, + enum CREncoding a_enc) ; + +CRStatement * +cr_statement_new_at_charset_rule (CRStyleSheet *a_sheet, + CRString *a_charset) ; +CRStatement * +cr_statement_at_charset_rule_parse_from_buf (const guchar *a_buf, + enum CREncoding a_encoding); + + +CRStatement * +cr_statement_new_at_font_face_rule (CRStyleSheet *a_sheet, + CRDeclaration *a_font_decls) ; +CRStatement * +cr_statement_font_face_rule_parse_from_buf (const guchar *a_buf, + enum CREncoding a_encoding) ; + +CRStatement * +cr_statement_new_at_page_rule (CRStyleSheet *a_sheet, + CRDeclaration *a_decl_list, + CRString *a_name, + CRString *a_pseudo) ; +CRStatement * +cr_statement_at_page_rule_parse_from_buf (const guchar *a_buf, + enum CREncoding a_encoding) ; + +enum CRStatus +cr_statement_set_parent_sheet (CRStatement *a_this, + CRStyleSheet *a_sheet) ; + +enum CRStatus +cr_statement_get_parent_sheet (CRStatement *a_this, + CRStyleSheet **a_sheet) ; + +CRStatement * +cr_statement_append (CRStatement *a_this, + CRStatement *a_new) ; + +CRStatement* +cr_statement_prepend (CRStatement *a_this, + CRStatement *a_new) ; + +CRStatement * +cr_statement_unlink (CRStatement *a_stmt) ; + +enum CRStatus +cr_statement_ruleset_set_sel_list (CRStatement *a_this, + CRSelector *a_sel_list) ; + +enum CRStatus +cr_statement_ruleset_get_sel_list (CRStatement *a_this, + CRSelector **a_list) ; + +enum CRStatus +cr_statement_ruleset_set_decl_list (CRStatement *a_this, + CRDeclaration *a_list) ; + +enum CRStatus +cr_statement_ruleset_get_declarations (CRStatement *a_this, + CRDeclaration **a_decl_list) ; + +enum CRStatus +cr_statement_ruleset_append_decl2 (CRStatement *a_this, + CRString *a_prop, CRTerm *a_value) ; + +enum CRStatus +cr_statement_ruleset_append_decl (CRStatement *a_this, + CRDeclaration *a_decl) ; + +enum CRStatus +cr_statement_at_import_rule_set_imported_sheet (CRStatement *a_this, + CRStyleSheet *a_sheet) ; + +enum CRStatus +cr_statement_at_import_rule_get_imported_sheet (CRStatement *a_this, + CRStyleSheet **a_sheet) ; + +enum CRStatus +cr_statement_at_import_rule_set_url (CRStatement *a_this, + CRString *a_url) ; + +enum CRStatus +cr_statement_at_import_rule_get_url (CRStatement *a_this, + CRString **a_url) ; + +gint +cr_statement_at_media_nr_rules (CRStatement *a_this) ; + +CRStatement * +cr_statement_at_media_get_from_list (CRStatement *a_this, int itemnr) ; + +enum CRStatus +cr_statement_at_page_rule_set_sel (CRStatement *a_this, + CRSelector *a_sel) ; + +enum CRStatus +cr_statement_at_page_rule_get_sel (CRStatement *a_this, + CRSelector **a_sel) ; + +enum CRStatus +cr_statement_at_page_rule_set_declarations (CRStatement *a_this, + CRDeclaration *a_decl_list) ; + +enum CRStatus +cr_statement_at_page_rule_get_declarations (CRStatement *a_this, + CRDeclaration **a_decl_list) ; + +enum CRStatus +cr_statement_at_charset_rule_set_charset (CRStatement *a_this, + CRString *a_charset) ; + +enum CRStatus +cr_statement_at_charset_rule_get_charset (CRStatement *a_this, + CRString **a_charset) ; + +enum CRStatus +cr_statement_at_font_face_rule_set_decls (CRStatement *a_this, + CRDeclaration *a_decls) ; + +enum CRStatus +cr_statement_at_font_face_rule_get_decls (CRStatement *a_this, + CRDeclaration **a_decls) ; + +enum CRStatus +cr_statement_at_font_face_rule_add_decl (CRStatement *a_this, + CRString *a_prop, + CRTerm *a_value) ; + +gchar * +cr_statement_to_string (CRStatement * a_this, gulong a_indent) ; + +gchar* +cr_statement_list_to_string (CRStatement *a_this, gulong a_indent) ; + +void +cr_statement_dump (CRStatement *a_this, FILE *a_fp, gulong a_indent) ; + +void +cr_statement_dump_ruleset (CRStatement * a_this, FILE * a_fp, + glong a_indent) ; + +void +cr_statement_dump_font_face_rule (CRStatement * a_this, + FILE * a_fp, + glong a_indent) ; + +void +cr_statement_dump_page (CRStatement * a_this, FILE * a_fp, + gulong a_indent) ; + + +void +cr_statement_dump_media_rule (CRStatement * a_this, + FILE * a_fp, + gulong a_indent) ; + +void +cr_statement_dump_import_rule (CRStatement * a_this, FILE * a_fp, + gulong a_indent) ; +void +cr_statement_dump_charset (CRStatement * a_this, FILE * a_fp, + gulong a_indent) ; +gint +cr_statement_nr_rules (CRStatement *a_this) ; + +CRStatement * +cr_statement_get_from_list (CRStatement *a_this, int itemnr) ; + +void +cr_statement_destroy (CRStatement *a_this) ; + +G_END_DECLS + +#endif /*__CR_STATEMENT_H__*/ diff --git a/src/libcroco/cr-string.c b/src/libcroco/cr-string.c new file mode 100644 index 000000000..2529b72be --- /dev/null +++ b/src/libcroco/cr-string.c @@ -0,0 +1,168 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli. + * See COPYRIGHTS file for copyright information. + */ + +#include +#include "cr-string.h" + +/** + *Instanciates a #CRString + *@return the newly instanciated #CRString + *Must be freed with cr_string_destroy(). + */ +CRString * +cr_string_new (void) +{ + CRString *result = NULL ; + + result = g_try_malloc (sizeof (CRString)) ; + if (!result) { + cr_utils_trace_info ("Out of memory") ; + return NULL ; + } + memset (result, 0, sizeof (CRString)) ; + result->stryng = g_string_new (NULL) ; + return result ; +} + +/** + *Instanciate a string and initialise it to + *a_string. + *@param a_string the initial string + *@return the newly instanciated string. + */ +CRString * +cr_string_new_from_string (const gchar * a_string) +{ + CRString *result = NULL ; + + result = cr_string_new () ; + if (!result) { + cr_utils_trace_info ("Out of memory") ; + return NULL ; + } + if (a_string) + g_string_append (result->stryng, a_string) ; + return result ; +} + +/** + *Instanciates a #CRString from an instance of GString. + *@param a_string the input string that will be copied into + *the newly instanciated #CRString + *@return the newly instanciated #CRString. + */ +CRString * +cr_string_new_from_gstring (GString *a_string) +{ + CRString *result = NULL ; + + result = cr_string_new () ; + if (!result) { + cr_utils_trace_info ("Out of memory") ; + return NULL ; + } + if (a_string) { + result->stryng = g_string_new_len + (a_string->str, a_string->len) ; + } else { + result->stryng = g_string_new (NULL) ; + } + return result ; +} + +CRString * +cr_string_dup (CRString *a_this) +{ + CRString *result = NULL ; + g_return_val_if_fail (a_this, NULL) ; + + result = cr_string_new_from_gstring (a_this->stryng) ; + if (!result) { + cr_utils_trace_info ("Out of memory") ; + return NULL ; + } + cr_parsing_location_copy (&result->location, + &a_this->location) ; + return result ; +} + +gchar * +cr_string_dup2 (CRString *a_this) +{ + gchar *result = NULL ; + + g_return_val_if_fail (a_this, NULL) ; + + if (a_this + && a_this->stryng + && a_this->stryng->str) { + result = g_strndup (a_this->stryng->str, + a_this->stryng->len) ; + } + return result ; +} + +/** + *Returns a pointer to the internal raw NULL terminated string + *of the current instance of #CRString. + *@param a_this the current instance of #CRString + */ +const gchar * +cr_string_peek_raw_str (CRString *a_this) +{ + g_return_val_if_fail (a_this, NULL) ; + + if (a_this->stryng && a_this->stryng->str) + return a_this->stryng->str ; + return NULL ; +} + +/** + *Returns the length of the internal raw NULL terminated + *string of the current instance of #CRString. + *@param a_this the current instance of #CRString. + *@return the len of the internal raw NULL termninated string, + *of -1 if no length can be returned. + */ +gint +cr_string_peek_raw_str_len (CRString *a_this) +{ + g_return_val_if_fail (a_this && a_this->stryng, + -1) ; + return a_this->stryng->len ; +} + +/** + *@param a_this the #CRString to destroy. + */ +void +cr_string_destroy (CRString *a_this) +{ + g_return_if_fail (a_this) ; + + if (a_this->stryng) { + g_string_free (a_this->stryng, TRUE) ; + a_this->stryng = NULL ; + } + g_free (a_this) ; +} diff --git a/src/libcroco/cr-string.h b/src/libcroco/cr-string.h new file mode 100644 index 000000000..256453451 --- /dev/null +++ b/src/libcroco/cr-string.h @@ -0,0 +1,76 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * See COPYRIGHTS file for copyright information. + */ + +/** + *@file + *Declaration file of the #CRString class. + */ + +#ifndef __CR_STRING_H__ +#define __CR_STRING_H__ + +#include +#include "cr-utils.h" +#include "cr-parsing-location.h" + +G_BEGIN_DECLS + +typedef struct _CRString CRString ; + +/** + *This is a ship implementation of string based on GString. + *Actually, the aim of CRString is to store the parsing location + *(line,column,byte offset) at which a given string has been parsed + *in the input CSS. + *So this class has a gstring field of type GString that users can + *freely manipulate, and also a CRParginLocation type where the + *parsing location is store. If you don't want to deal with parsing + *location stuffs, then use GString instead. If we were in C++ for example, + *CRString would just inherit GString and just add accessors to + *the CRParsingLocation data ... but we are not and we still have + *to provide the parsing location information. + */ +struct _CRString { + /** + *The GString where all the string + *operation happen. + */ + GString *stryng ; + /** + *The parsing location storage area. + */ + CRParsingLocation location ; +} ; + +CRString * cr_string_new (void) ; + +CRString *cr_string_new_from_string (const gchar * a_string) ; +CRString * cr_string_new_from_gstring (GString *a_string) ; +CRString *cr_string_dup (CRString *a_this) ; +gchar *cr_string_dup2 (CRString *a_this) ; +const gchar *cr_string_peek_raw_str (CRString *a_this) ; +gint cr_string_peek_raw_str_len (CRString *a_this) ; +void cr_string_destroy (CRString *a_this) ; + +G_END_DECLS + +#endif diff --git a/src/libcroco/cr-style.c b/src/libcroco/cr-style.c new file mode 100644 index 000000000..789d68f42 --- /dev/null +++ b/src/libcroco/cr-style.c @@ -0,0 +1,2851 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of + * the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the + * GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli. + * see COPYRIGTHS file for copyright information + */ + +#include +#include "cr-style.h" + +/** + *@file + *The definition of the #CRStyle class. + */ + +/** + *A property ID. + *Each supported css property has an ID which is + *an entry into a property "population" jump table. + *each entry of the property population jump table + *contains code to tranform the literal form of + *a property value into a strongly typed value. + */ +enum CRPropertyID { + PROP_ID_NOT_KNOWN = 0, + PROP_ID_PADDING_TOP, + PROP_ID_PADDING_RIGHT, + PROP_ID_PADDING_BOTTOM, + PROP_ID_PADDING_LEFT, + PROP_ID_PADDING, + PROP_ID_BORDER_TOP_WIDTH, + PROP_ID_BORDER_RIGHT_WIDTH, + PROP_ID_BORDER_BOTTOM_WIDTH, + PROP_ID_BORDER_LEFT_WIDTH, + PROP_ID_BORDER_WIDTH, + PROP_ID_BORDER_TOP_STYLE, + PROP_ID_BORDER_RIGHT_STYLE, + PROP_ID_BORDER_BOTTOM_STYLE, + PROP_ID_BORDER_LEFT_STYLE, + PROP_ID_BORDER_STYLE, + PROP_ID_BORDER_TOP_COLOR, + PROP_ID_BORDER_RIGHT_COLOR, + PROP_ID_BORDER_BOTTOM_COLOR, + PROP_ID_BORDER_LEFT_COLOR, + PROP_ID_BORDER_TOP, + PROP_ID_BORDER_RIGHT, + PROP_ID_BORDER_BOTTOM, + PROP_ID_BORDER_LEFT, + PROP_ID_BORDER, + PROP_ID_MARGIN_TOP, + PROP_ID_MARGIN_RIGHT, + PROP_ID_MARGIN_BOTTOM, + PROP_ID_MARGIN_LEFT, + PROP_ID_MARGIN, + PROP_ID_DISPLAY, + PROP_ID_POSITION, + PROP_ID_TOP, + PROP_ID_RIGHT, + PROP_ID_BOTTOM, + PROP_ID_LEFT, + PROP_ID_FLOAT, + PROP_ID_WIDTH, + PROP_ID_COLOR, + PROP_ID_BACKGROUND_COLOR, + PROP_ID_FONT_FAMILY, + PROP_ID_FONT_SIZE, + PROP_ID_FONT_STYLE, + PROP_ID_FONT_WEIGHT, + PROP_ID_WHITE_SPACE, + /*should be the last one. */ + NB_PROP_IDS +}; + +typedef struct _CRPropertyDesc CRPropertyDesc; + +struct _CRPropertyDesc { + const guchar *name; + enum CRPropertyID prop_id; +}; + +static CRPropertyDesc gv_prop_table[] = { + {"padding-top", PROP_ID_PADDING_TOP}, + {"padding-right", PROP_ID_PADDING_RIGHT}, + {"padding-bottom", PROP_ID_PADDING_BOTTOM}, + {"padding-left", PROP_ID_PADDING_LEFT}, + {"padding", PROP_ID_PADDING}, + {"border-top-width", PROP_ID_BORDER_TOP_WIDTH}, + {"border-right-width", PROP_ID_BORDER_RIGHT_WIDTH}, + {"border-bottom-width", PROP_ID_BORDER_BOTTOM_WIDTH}, + {"border-left-width", PROP_ID_BORDER_LEFT_WIDTH}, + {"border-width", PROP_ID_BORDER_WIDTH}, + {"border-top-style", PROP_ID_BORDER_TOP_STYLE}, + {"border-right-style", PROP_ID_BORDER_RIGHT_STYLE}, + {"border-bottom-style", PROP_ID_BORDER_BOTTOM_STYLE}, + {"border-left-style", PROP_ID_BORDER_LEFT_STYLE}, + {"border-style", PROP_ID_BORDER_STYLE}, + {"border-top", PROP_ID_BORDER_TOP}, + {"border-right", PROP_ID_BORDER_RIGHT}, + {"border-bottom", PROP_ID_BORDER_BOTTOM}, + {"border-left", PROP_ID_BORDER_LEFT}, + {"border", PROP_ID_BORDER}, + {"margin-top", PROP_ID_MARGIN_TOP}, + {"margin-right", PROP_ID_MARGIN_RIGHT}, + {"margin-bottom", PROP_ID_MARGIN_BOTTOM}, + {"margin-left", PROP_ID_MARGIN_LEFT}, + {"margin", PROP_ID_MARGIN}, + {"display", PROP_ID_DISPLAY}, + {"position", PROP_ID_POSITION}, + {"top", PROP_ID_TOP}, + {"right", PROP_ID_RIGHT}, + {"bottom", PROP_ID_BOTTOM}, + {"left", PROP_ID_LEFT}, + {"float", PROP_ID_FLOAT}, + {"width", PROP_ID_WIDTH}, + {"color", PROP_ID_COLOR}, + {"border-top-color", PROP_ID_BORDER_TOP_COLOR}, + {"border-right-color", PROP_ID_BORDER_RIGHT_COLOR}, + {"border-bottom-color", PROP_ID_BORDER_BOTTOM_COLOR}, + {"border-left-color", PROP_ID_BORDER_LEFT_COLOR}, + {"background-color", PROP_ID_BACKGROUND_COLOR}, + {"font-family", PROP_ID_FONT_FAMILY}, + {"font-size", PROP_ID_FONT_SIZE}, + {"font-style", PROP_ID_FONT_STYLE}, + {"font-weight", PROP_ID_FONT_WEIGHT}, + {"white-space", PROP_ID_WHITE_SPACE}, + /*must be the last one */ + {NULL, 0} +}; + +/** + *A the key/value pair of this hash table + *are: + *key => name of the the css propertie found in gv_prop_table + *value => matching property id found in gv_prop_table. + *So this hash table is here just to retrieval of a property id + *from a property name. + */ +static GHashTable *gv_prop_hash = NULL; + +/** + *incremented by each new instance of #CRStyle + *and decremented at the it destroy time. + *When this reaches zero, gv_prop_hash is destroyed. + */ +static gulong gv_prop_hash_ref_count = 0; + +struct CRNumPropEnumDumpInfo { + enum CRNumProp code; + const gchar *str; +}; + +static struct CRNumPropEnumDumpInfo gv_num_props_dump_infos[] = { + {NUM_PROP_TOP, "top"}, + {NUM_PROP_RIGHT, "right"}, + {NUM_PROP_BOTTOM, "bottom"}, + {NUM_PROP_LEFT, "left"}, + {NUM_PROP_PADDING_TOP, "padding-top"}, + {NUM_PROP_PADDING_RIGHT, "padding-right"}, + {NUM_PROP_PADDING_BOTTOM, "padding-bottom"}, + {NUM_PROP_PADDING_LEFT, "padding-left"}, + {NUM_PROP_BORDER_TOP, "border-top"}, + {NUM_PROP_BORDER_RIGHT, "border-right"}, + {NUM_PROP_BORDER_BOTTOM, "border-bottom"}, + {NUM_PROP_BORDER_LEFT, "border-left"}, + {NUM_PROP_MARGIN_TOP, "margin-top"}, + {NUM_PROP_MARGIN_RIGHT, "margin-right"}, + {NUM_PROP_MARGIN_BOTTOM, "margin-bottom"}, + {NUM_PROP_MARGIN_LEFT, "margin-left"}, + {NUM_PROP_WIDTH, "width"}, + {0, NULL} +}; + +struct CRRgbPropEnumDumpInfo { + enum CRRgbProp code; + const gchar *str; +}; + +static struct CRRgbPropEnumDumpInfo gv_rgb_props_dump_infos[] = { + {RGB_PROP_BORDER_TOP_COLOR, "border-top-color"}, + {RGB_PROP_BORDER_RIGHT_COLOR, "border-right-color"}, + {RGB_PROP_BORDER_BOTTOM_COLOR, "bottom-color"}, + {RGB_PROP_BORDER_LEFT_COLOR, "left-color"}, + {RGB_PROP_COLOR, "color"}, + {RGB_PROP_BACKGROUND_COLOR, "background-color"}, + {0, NULL} +}; + +struct CRBorderStylePropEnumDumpInfo { + enum CRBorderStyleProp code; + const gchar *str; + +}; + +static struct CRBorderStylePropEnumDumpInfo gv_border_style_props_dump_infos[] + = { + {BORDER_STYLE_PROP_TOP, "border-style-top"}, + {BORDER_STYLE_PROP_RIGHT, "border-style-right"}, + {BORDER_STYLE_PROP_BOTTOM, "boder-style-bottom"}, + {BORDER_STYLE_PROP_LEFT, "border-style-left"}, + {0, NULL} +}; + +static enum CRStatus + cr_style_init_properties (void); + +enum CRDirection { + DIR_TOP = 0, + DIR_RIGHT, + DIR_BOTTOM, + DIR_LEFT, + + /*must be the last one */ + NB_DIRS +}; + +static const gchar *num_prop_code_to_string (enum CRNumProp a_code); + +static const gchar *rgb_prop_code_to_string (enum CRRgbProp a_code); + +static const gchar *border_style_prop_code_to_string (enum CRBorderStyleProp + a_code); + +static enum CRStatus +set_prop_padding_x_from_value (CRStyle * a_style, + CRTerm * a_value, enum CRDirection a_dir); + +static enum CRStatus +set_prop_border_x_width_from_value (CRStyle * a_style, + CRTerm * a_value, + enum CRDirection a_dir); +static enum CRStatus +set_prop_border_width_from_value (CRStyle *a_style, + CRTerm *a_value) ; + +static enum CRStatus +set_prop_border_x_style_from_value (CRStyle * a_style, + CRTerm * a_value, + enum CRDirection a_dir); +static enum CRStatus +set_prop_border_style_from_value (CRStyle *a_style, + CRTerm *a_value) ; + +static enum CRStatus +set_prop_margin_x_from_value (CRStyle * a_style, CRTerm * a_value, + enum CRDirection a_dir); + +static enum CRStatus +set_prop_display_from_value (CRStyle * a_style, CRTerm * a_value); + +static enum CRStatus +set_prop_position_from_value (CRStyle * a_style, CRTerm * a_value); + +static enum CRStatus +set_prop_x_from_value (CRStyle * a_style, CRTerm * a_value, + enum CRDirection a_dir); + +static enum CRStatus +set_prop_float (CRStyle * a_style, CRTerm * a_value); + +static enum CRStatus +set_prop_width (CRStyle * a_style, CRTerm * a_value); + +static enum CRStatus +set_prop_color (CRStyle * a_style, CRTerm * a_value); + +static enum CRStatus +set_prop_background_color (CRStyle * a_style, CRTerm * a_value); + +static enum CRStatus +set_prop_border_x_color_from_value (CRStyle * a_style, CRTerm * a_value, + enum CRDirection a_dir); + +static enum CRStatus +set_prop_border_x_from_value (CRStyle * a_style, CRTerm * a_value, + enum CRDirection a_dir); + +static enum CRStatus +set_prop_border_from_value (CRStyle * a_style, CRTerm * a_value); + +static enum CRStatus +set_prop_padding_from_value (CRStyle * a_style, CRTerm * a_value); + +static enum CRStatus +set_prop_margin_from_value (CRStyle * a_style, CRTerm * a_value); + +static enum CRStatus +set_prop_font_family_from_value (CRStyle * a_style, CRTerm * a_value); + +static enum CRStatus +init_style_font_size_field (CRStyle * a_style); + +static enum CRStatus +set_prop_font_size_from_value (CRStyle * a_style, CRTerm * a_value); + +static enum CRStatus +set_prop_font_style_from_value (CRStyle * a_style, CRTerm * a_value); + +static enum CRStatus +set_prop_font_weight_from_value (CRStyle * a_style, CRTerm * a_value); + +static const gchar * +num_prop_code_to_string (enum CRNumProp a_code) +{ + unsigned const len = sizeof (gv_num_props_dump_infos) / + sizeof (struct CRNumPropEnumDumpInfo); + if (a_code >= len) { + cr_utils_trace_info ("A field has been added " + "to 'enum CRNumProp' and no matching" + " entry has been " + "added to gv_num_prop_dump_infos table.\n" + "Please add the missing matching entry"); + return NULL; + } + if (gv_num_props_dump_infos[a_code].code != a_code) { + cr_utils_trace_info ("mismatch between the order of fields in" + " 'enum CRNumProp' and " + "the order of entries in " + "the gv_num_prop_dump_infos table"); + return NULL; + } + return gv_num_props_dump_infos[a_code].str; +} + +static const gchar * +rgb_prop_code_to_string (enum CRRgbProp a_code) +{ + unsigned const len = sizeof (gv_rgb_props_dump_infos) / + sizeof (struct CRRgbPropEnumDumpInfo); + + if (a_code >= len) { + cr_utils_trace_info ("A field has been added " + "to 'enum CRRgbProp' and no matching" + " entry has been " + "added to gv_rgb_prop_dump_infos table.\n" + "Please add the missing matching entry"); + return NULL; + } + if (gv_rgb_props_dump_infos[a_code].code != a_code) { + cr_utils_trace_info ("mismatch between the order of fields in" + " 'enum CRRgbProp' and " + "the order of entries in " + "the gv_rgb_props_dump_infos table"); + return NULL; + } + return gv_rgb_props_dump_infos[a_code].str; +} + +static const gchar * +border_style_prop_code_to_string (enum CRBorderStyleProp a_code) +{ + unsigned const len = sizeof (gv_border_style_props_dump_infos) / + sizeof (struct CRBorderStylePropEnumDumpInfo); + + if (a_code >= len) { + cr_utils_trace_info ("A field has been added " + "to 'enum CRBorderStyleProp' and no matching" + " entry has been " + "added to gv_border_style_prop_dump_infos table.\n" + "Please add the missing matching entry"); + return NULL; + } + if (gv_border_style_props_dump_infos[a_code].code != a_code) { + cr_utils_trace_info ("mismatch between the order of fields in" + " 'enum CRBorderStyleProp' and " + "the order of entries in " + "the gv_border_style_props_dump_infos table"); + return NULL; + } + return gv_border_style_props_dump_infos[a_code].str; +} + +static enum CRStatus +cr_style_init_properties (void) +{ + + if (!gv_prop_hash) { + gulong i = 0; + + gv_prop_hash = g_hash_table_new (g_str_hash, g_str_equal); + if (!gv_prop_hash) { + cr_utils_trace_info ("Out of memory"); + return CR_ERROR; + } + + /*load gv_prop_hash from gv_prop_table */ + for (i = 0; gv_prop_table[i].name; i++) { + g_hash_table_insert + (gv_prop_hash, + (gpointer) gv_prop_table[i].name, + GINT_TO_POINTER (gv_prop_table[i].prop_id)); + } + } + + return CR_OK; +} + +static enum CRPropertyID +cr_style_get_prop_id (const guchar * a_prop) +{ + gpointer *raw_id = NULL; + + if (!gv_prop_hash) { + cr_style_init_properties (); + } + + raw_id = g_hash_table_lookup (gv_prop_hash, a_prop); + if (!raw_id) { + return PROP_ID_NOT_KNOWN; + } + return GPOINTER_TO_INT (raw_id); +} + +static enum CRStatus +set_prop_padding_x_from_value (CRStyle * a_style, + CRTerm * a_value, enum CRDirection a_dir) +{ + enum CRStatus status = CR_OK; + CRNum *num_val = NULL; + + g_return_val_if_fail (a_style && a_value, CR_BAD_PARAM_ERROR); + + if (a_value->type != TERM_NUMBER && a_value->type != TERM_IDENT) + return CR_BAD_PARAM_ERROR; + + switch (a_dir) { + case DIR_TOP: + num_val = &a_style->num_props[NUM_PROP_PADDING_TOP].sv; + break; + + case DIR_RIGHT: + num_val = &a_style->num_props[NUM_PROP_PADDING_RIGHT].sv; + break; + + case DIR_BOTTOM: + num_val = &a_style->num_props[NUM_PROP_PADDING_BOTTOM].sv; + break; + + case DIR_LEFT: + num_val = &a_style->num_props[NUM_PROP_PADDING_LEFT].sv; + break; + + default: + return CR_BAD_PARAM_ERROR; + } + + if (a_value->type == TERM_IDENT) { + if (a_value->content.str + && a_value->content.str->stryng + && a_value->content.str->stryng->str + && !strncmp ((guchar *) "inherit", + a_value->content.str->stryng->str, + sizeof ("inherit")-1)) { + status = cr_num_set (num_val, 0.0, NUM_INHERIT); + return CR_OK; + } else + return CR_UNKNOWN_TYPE_ERROR; + } + + g_return_val_if_fail (a_value->type == TERM_NUMBER + && a_value->content.num, CR_UNKNOWN_TYPE_ERROR); + + switch (a_value->content.num->type) { + case NUM_LENGTH_EM: + case NUM_LENGTH_EX: + case NUM_LENGTH_PX: + case NUM_LENGTH_IN: + case NUM_LENGTH_CM: + case NUM_LENGTH_MM: + case NUM_LENGTH_PT: + case NUM_LENGTH_PC: + case NUM_PERCENTAGE: + status = cr_num_copy (num_val, a_value->content.num); + break; + default: + status = CR_UNKNOWN_TYPE_ERROR; + break; + } + + return status; +} + +static enum CRStatus +set_prop_border_x_width_from_value (CRStyle * a_style, + CRTerm * a_value, + enum CRDirection a_dir) +{ + enum CRStatus status = CR_OK; + CRNum *num_val = NULL; + + g_return_val_if_fail (a_value && a_style, CR_BAD_PARAM_ERROR); + + switch (a_dir) { + case DIR_TOP: + num_val = &a_style->num_props[NUM_PROP_BORDER_TOP].sv; + break; + + case DIR_RIGHT: + num_val = &a_style->num_props[NUM_PROP_BORDER_RIGHT].sv; + break; + + case DIR_BOTTOM: + num_val = &a_style->num_props[NUM_PROP_BORDER_BOTTOM].sv; + break; + + case DIR_LEFT: + num_val = &a_style->num_props[NUM_PROP_BORDER_LEFT].sv; + break; + + default: + return CR_BAD_PARAM_ERROR; + break; + } + + if (a_value->type == TERM_IDENT) { + if (a_value->content.str + && a_value->content.str->stryng + && a_value->content.str->stryng->str) { + if (!strncmp ("thin", + a_value->content.str->stryng->str, + sizeof ("thin")-1)) { + cr_num_set (num_val, BORDER_THIN, + NUM_LENGTH_PX); + } else if (!strncmp + ("medium", + a_value->content.str->stryng->str, + sizeof ("medium")-1)) { + cr_num_set (num_val, BORDER_MEDIUM, + NUM_LENGTH_PX); + } else if (!strncmp ("thick", + a_value->content.str->stryng->str, + sizeof ("thick")-1)) { + cr_num_set (num_val, BORDER_THICK, + NUM_LENGTH_PX); + } else { + return CR_UNKNOWN_TYPE_ERROR; + } + } + } else if (a_value->type == TERM_NUMBER) { + if (a_value->content.num) { + cr_num_copy (num_val, a_value->content.num); + } + } else if (a_value->type != TERM_NUMBER + || a_value->content.num == NULL) { + return CR_UNKNOWN_TYPE_ERROR; + } + + return status; +} + +static enum CRStatus +set_prop_border_width_from_value (CRStyle *a_style, + CRTerm *a_value) +{ + CRTerm *cur_term = NULL ; + enum CRDirection direction = DIR_TOP ; + + g_return_val_if_fail (a_style && a_value, + CR_BAD_PARAM_ERROR) ; + cur_term = a_value ; + + if (!cur_term) + return CR_ERROR ; + + for (direction = DIR_TOP ; + direction < NB_DIRS ; direction ++) { + set_prop_border_x_width_from_value (a_style, + cur_term, + direction) ; + } + + cur_term = cur_term->next ; + if (!cur_term) + return CR_OK ; + set_prop_border_x_width_from_value (a_style, cur_term, + DIR_RIGHT) ; + set_prop_border_x_width_from_value (a_style, cur_term, + DIR_LEFT) ; + + cur_term = cur_term->next ; + if (!cur_term) + return CR_OK ; + set_prop_border_x_width_from_value (a_style, cur_term, + DIR_BOTTOM) ; + + cur_term = cur_term->next ; + if (!cur_term) + return CR_OK ; + set_prop_border_x_width_from_value (a_style, cur_term, + DIR_LEFT) ; + + return CR_OK ; +} + +static enum CRStatus +set_prop_border_x_style_from_value (CRStyle * a_style, + CRTerm * a_value, enum CRDirection a_dir) +{ + enum CRStatus status = CR_OK; + enum CRBorderStyle *border_style_ptr = NULL; + + g_return_val_if_fail (a_style && a_value, CR_BAD_PARAM_ERROR); + + switch (a_dir) { + case DIR_TOP: + border_style_ptr = &a_style-> + border_style_props[BORDER_STYLE_PROP_TOP]; + break; + + case DIR_RIGHT: + border_style_ptr = + &a_style->border_style_props[BORDER_STYLE_PROP_RIGHT]; + break; + + case DIR_BOTTOM: + border_style_ptr = &a_style-> + border_style_props[BORDER_STYLE_PROP_BOTTOM]; + break; + + case DIR_LEFT: + border_style_ptr = &a_style-> + border_style_props[BORDER_STYLE_PROP_LEFT]; + break; + + default: + break; + } + + if (a_value->type != TERM_IDENT || !a_value->content.str) { + return CR_UNKNOWN_TYPE_ERROR; + } + + if (!strncmp ("none", + a_value->content.str->stryng->str, + sizeof ("none")-1)) { + *border_style_ptr = BORDER_STYLE_NONE; + } else if (!strncmp ("hidden", + a_value->content.str->stryng->str, + sizeof ("hidden")-1)) { + *border_style_ptr = BORDER_STYLE_HIDDEN; + } else if (!strncmp ("dotted", + a_value->content.str->stryng->str, + sizeof ("dotted")-1)) { + *border_style_ptr = BORDER_STYLE_DOTTED; + } else if (!strncmp ("dashed", + a_value->content.str->stryng->str, sizeof ("dashed")-1)) { + *border_style_ptr = BORDER_STYLE_DASHED; + } else if (!strncmp ("solid", + a_value->content.str->stryng->str, sizeof ("solid")-1)) { + *border_style_ptr = BORDER_STYLE_SOLID; + } else if (!strncmp ("double", + a_value->content.str->stryng->str, sizeof ("double")-1)) { + *border_style_ptr = BORDER_STYLE_DOUBLE; + } else if (!strncmp ("groove", + a_value->content.str->stryng->str, sizeof ("groove")-1)) { + *border_style_ptr = BORDER_STYLE_GROOVE; + } else if (!strncmp ("ridge", + a_value->content.str->stryng->str, + sizeof ("ridge")-1)) { + *border_style_ptr = BORDER_STYLE_RIDGE; + } else if (!strncmp ("inset", + a_value->content.str->stryng->str, + sizeof ("inset")-1)) { + *border_style_ptr = BORDER_STYLE_INSET; + } else if (!strncmp ("outset", + a_value->content.str->stryng->str, + sizeof ("outset")-1)) { + *border_style_ptr = BORDER_STYLE_OUTSET; + } else if (!strncmp ("inherit", + a_value->content.str->stryng->str, + sizeof ("inherit")-1)) { + *border_style_ptr = BORDER_STYLE_INHERIT; + } else { + status = CR_UNKNOWN_TYPE_ERROR; + } + + return status; +} + +static enum CRStatus +set_prop_border_style_from_value (CRStyle *a_style, + CRTerm *a_value) +{ + CRTerm *cur_term = NULL ; + enum CRDirection direction = DIR_TOP ; + + g_return_val_if_fail (a_style && a_value, + CR_BAD_PARAM_ERROR) ; + + cur_term = a_value ; + if (!cur_term || cur_term->type != TERM_IDENT) { + return CR_ERROR ; + } + + for (direction = DIR_TOP ; + direction < NB_DIRS ; + direction ++) { + set_prop_border_x_style_from_value (a_style, + cur_term, + direction) ; + } + + cur_term = cur_term->next ; + if (!cur_term || cur_term->type != TERM_IDENT) { + return CR_OK ; + } + + set_prop_border_x_style_from_value (a_style, cur_term, + DIR_RIGHT) ; + set_prop_border_x_style_from_value (a_style, cur_term, + DIR_LEFT) ; + + cur_term = cur_term->next ; + if (!cur_term || cur_term->type != TERM_IDENT) { + return CR_OK ; + } + set_prop_border_x_style_from_value (a_style, cur_term, + DIR_BOTTOM) ; + + cur_term = cur_term->next ; + if (!cur_term || cur_term->type != TERM_IDENT) { + return CR_OK ; + } + set_prop_border_x_style_from_value (a_style, cur_term, + DIR_LEFT) ; + return CR_OK ; +} + +static enum CRStatus +set_prop_margin_x_from_value (CRStyle * a_style, CRTerm * a_value, + enum CRDirection a_dir) +{ + enum CRStatus status = CR_OK; + CRNum *num_val = NULL; + + g_return_val_if_fail (a_style && a_value, CR_BAD_PARAM_ERROR); + + switch (a_dir) { + case DIR_TOP: + num_val = &a_style->num_props[NUM_PROP_MARGIN_TOP].sv; + break; + + case DIR_RIGHT: + num_val = &a_style->num_props[NUM_PROP_MARGIN_RIGHT].sv; + break; + + case DIR_BOTTOM: + num_val = &a_style->num_props[NUM_PROP_MARGIN_BOTTOM].sv; + break; + + case DIR_LEFT: + num_val = &a_style->num_props[NUM_PROP_MARGIN_LEFT].sv; + break; + + default: + break; + } + + switch (a_value->type) { + case TERM_IDENT: + if (a_value->content.str + && a_value->content.str->stryng + && a_value->content.str->stryng->str + && !strcmp (a_value->content.str->stryng->str, + "inherit")) { + status = cr_num_set (num_val, 0.0, NUM_INHERIT); + } else if (a_value->content.str + && a_value->content.str->stryng + && !strcmp (a_value->content.str->stryng->str, + "auto")) { + status = cr_num_set (num_val, 0.0, NUM_AUTO); + } else { + status = CR_UNKNOWN_TYPE_ERROR; + } + break ; + + case TERM_NUMBER: + status = cr_num_copy (num_val, a_value->content.num); + break; + + default: + status = CR_UNKNOWN_TYPE_ERROR; + break; + } + + return status; +} + +struct CRPropDisplayValPair { + const guchar *prop_name; + enum CRDisplayType type; +}; + +static enum CRStatus +set_prop_display_from_value (CRStyle * a_style, CRTerm * a_value) +{ + static const struct CRPropDisplayValPair disp_vals_map[] = { + {"none", DISPLAY_NONE}, + {"inline", DISPLAY_INLINE}, + {"block", DISPLAY_BLOCK}, + {"run-in", DISPLAY_RUN_IN}, + {"compact", DISPLAY_COMPACT}, + {"marker", DISPLAY_MARKER}, + {"table", DISPLAY_TABLE}, + {"inline-table", DISPLAY_INLINE_TABLE}, + {"table-row-group", DISPLAY_TABLE_ROW_GROUP}, + {"table-header-group", DISPLAY_TABLE_HEADER_GROUP}, + {"table-footer-group", DISPLAY_TABLE_FOOTER_GROUP}, + {"table-row", DISPLAY_TABLE_ROW}, + {"table-column-group", DISPLAY_TABLE_COLUMN_GROUP}, + {"table-column", DISPLAY_TABLE_COLUMN}, + {"table-cell", DISPLAY_TABLE_CELL}, + {"table-caption", DISPLAY_TABLE_CAPTION}, + {"inherit", DISPLAY_INHERIT}, + {NULL, DISPLAY_NONE} + }; + + g_return_val_if_fail (a_style && a_value, CR_BAD_PARAM_ERROR); + + switch (a_value->type) { + case TERM_IDENT: + { + int i = 0; + + if (!a_value->content.str + || !a_value->content.str->stryng + || !a_value->content.str->stryng->str) + break; + + for (i = 0; disp_vals_map[i].prop_name; i++) { + if (!strncmp + (disp_vals_map[i].prop_name, + a_value->content.str->stryng->str, + strlen (disp_vals_map[i].prop_name))) { + a_style->display = + disp_vals_map[i].type; + break; + } + } + } + break; + + default: + break; + } + + return CR_OK; +} + +struct CRPropPositionValPair { + const guchar *name; + enum CRPositionType type; +}; + +static enum CRStatus +set_prop_position_from_value (CRStyle * a_style, CRTerm * a_value) +{ + enum CRStatus status = CR_UNKNOWN_PROP_VAL_ERROR; + static const struct CRPropPositionValPair position_vals_map[] = { + {"static", POSITION_STATIC}, + {"relative", POSITION_RELATIVE}, + {"absolute", POSITION_ABSOLUTE}, + {"fixed", POSITION_FIXED}, + {"inherit", POSITION_INHERIT}, + {NULL, POSITION_STATIC} + /*must alwas be the last one */ + }; + + g_return_val_if_fail (a_value, CR_BAD_PARAM_ERROR); + + switch (a_value->type) { + case TERM_IDENT: + { + int i = 0; + + if (!a_value->content.str + || !a_value->content.str->stryng + || !a_value->content.str->stryng->str) + break; + + for (i = 0; position_vals_map[i].name; i++) { + if (!strncmp (position_vals_map[i].name, + a_value->content.str->stryng->str, + strlen (position_vals_map[i]. + name))) { + a_style->position = + position_vals_map[i].type; + status = CR_OK; + break; + } + } + } + break; + + default: + break; + } + + return CR_OK; +} + +static enum CRStatus +set_prop_x_from_value (CRStyle * a_style, CRTerm * a_value, + enum CRDirection a_dir) +{ + CRNum *box_offset = NULL; + + g_return_val_if_fail (a_style && a_value, CR_BAD_PARAM_ERROR); + + if (!(a_value->type == TERM_NUMBER) + && !(a_value->type == TERM_IDENT)) { + return CR_UNKNOWN_PROP_VAL_ERROR; + } + + switch (a_dir) { + case DIR_TOP: + box_offset = &a_style->num_props[NUM_PROP_TOP].sv; + break; + + case DIR_RIGHT: + box_offset = &a_style->num_props[NUM_PROP_RIGHT].sv; + break; + + case DIR_BOTTOM: + box_offset = &a_style->num_props[NUM_PROP_BOTTOM].sv; + break; + case DIR_LEFT: + box_offset = &a_style->num_props[NUM_PROP_LEFT].sv; + break; + + default: + break; + } + + box_offset->type = NUM_AUTO; + + if (a_value->type == TERM_NUMBER && a_value->content.num) { + cr_num_copy (box_offset, a_value->content.num); + } else if (a_value->type == TERM_IDENT + && a_value->content.str + && a_value->content.str->stryng + && a_value->content.str->stryng->str) { + if (!strncmp ("inherit", + a_value->content.str->stryng->str, + sizeof ("inherit")-1)) { + cr_num_set (box_offset, 0.0, NUM_INHERIT); + } else if (!strncmp ("auto", + a_value->content.str->stryng->str, + sizeof ("auto")-1)) { + box_offset->type = NUM_AUTO; + } + } + + return CR_OK; +} + +static enum CRStatus +set_prop_float (CRStyle * a_style, CRTerm * a_value) +{ + g_return_val_if_fail (a_style && a_value, + CR_BAD_PARAM_ERROR); + + /*the default float type as specified by the css2 spec */ + a_style->float_type = FLOAT_NONE; + + if (a_value->type != TERM_IDENT + || !a_value->content.str + || !a_value->content.str->stryng + || !a_value->content.str->stryng->str) { + /*unknow type, the float type is set to it's default value */ + return CR_OK; + } + + if (!strncmp ("none", + a_value->content.str->stryng->str, + sizeof ("none")-1)) { + a_style->float_type = FLOAT_NONE; + } else if (!strncmp ("left", + a_value->content.str->stryng->str, + sizeof ("left")-1)) { + a_style->float_type = FLOAT_LEFT; + } else if (!strncmp ("right", + a_value->content.str->stryng->str, + sizeof ("right")-1)) { + a_style->float_type = FLOAT_RIGHT; + } else if (!strncmp ("inherit", + a_value->content.str->stryng->str, + sizeof ("inherit")-1)) { + a_style->float_type = FLOAT_INHERIT; + } + return CR_OK; +} + +static enum CRStatus +set_prop_width (CRStyle * a_style, CRTerm * a_value) +{ + CRNum *width = NULL; + g_return_val_if_fail (a_style + && a_value, + CR_BAD_PARAM_ERROR); + + width = &a_style->num_props[NUM_PROP_WIDTH].sv; + cr_num_set (width, 0.0, NUM_AUTO); + + if (a_value->type == TERM_IDENT) { + if (a_value->content.str + && a_value->content.str->stryng + && a_value->content.str->stryng->str) { + if (!strncmp ("auto", + a_value->content.str->stryng->str, + sizeof ("auto")-1)) { + cr_num_set (width, 0.0, NUM_AUTO); + } else if (!strncmp ("inherit", + a_value->content.str->stryng->str, + sizeof ("inherit")-1)) { + cr_num_set (width, 0.0, NUM_INHERIT); + } + } + } else if (a_value->type == TERM_NUMBER) { + if (a_value->content.num) { + cr_num_copy (&a_style->num_props[NUM_PROP_WIDTH].sv, + a_value->content.num); + } + } + return CR_OK; +} + +static enum CRStatus +set_prop_color (CRStyle * a_style, CRTerm * a_value) +{ + enum CRStatus status = CR_OK; + CRRgb *a_rgb = &a_style->rgb_props[RGB_PROP_COLOR].sv; + + g_return_val_if_fail (a_style + && a_value, CR_BAD_PARAM_ERROR); + + status = cr_rgb_set_from_term (a_rgb, a_value); + + return status; +} + +static enum CRStatus +set_prop_background_color (CRStyle * a_style, CRTerm * a_value) +{ + enum CRStatus status = CR_OK; + CRRgb *rgb = &a_style->rgb_props[RGB_PROP_BACKGROUND_COLOR].sv; + + g_return_val_if_fail (a_style && a_value, CR_BAD_PARAM_ERROR); + + status = cr_rgb_set_from_term (rgb, a_value); + return status; +} + +/** + *Sets border-top-color, border-right-color, + *border-bottom-color or border-left-color properties + *in the style structure. The value is taken from a + *css2 term of type IDENT or RGB. + *@param a_style the style structure to set. + *@param a_value the css2 term to take the color information from. + *@param a_dir the direction (TOP, LEFT, RIGHT, or BOTTOM). + *@return CR_OK upon successfull completion, an error code otherwise. + */ +static enum CRStatus +set_prop_border_x_color_from_value (CRStyle * a_style, CRTerm * a_value, + enum CRDirection a_dir) +{ + CRRgb *rgb_color = NULL; + enum CRStatus status = CR_OK; + + g_return_val_if_fail (a_style && a_value, CR_BAD_PARAM_ERROR); + + switch (a_dir) { + case DIR_TOP: + rgb_color = &a_style->rgb_props[RGB_PROP_BORDER_TOP_COLOR].sv; + break; + + case DIR_RIGHT: + rgb_color = + &a_style->rgb_props[RGB_PROP_BORDER_RIGHT_COLOR].sv; + break; + + case DIR_BOTTOM: + rgb_color = + &a_style->rgb_props[RGB_PROP_BORDER_BOTTOM_COLOR].sv; + break; + + case DIR_LEFT: + rgb_color = + &a_style->rgb_props[RGB_PROP_BORDER_LEFT_COLOR].sv; + break; + + default: + cr_utils_trace_info ("unknown DIR type"); + return CR_BAD_PARAM_ERROR; + } + + status = CR_UNKNOWN_PROP_VAL_ERROR; + + if (a_value->type == TERM_IDENT) { + if (a_value->content.str + && a_value->content.str->stryng + && a_value->content.str->stryng->str) { + status = cr_rgb_set_from_name + (rgb_color, + a_value->content.str->stryng->str); + + } + if (status != CR_OK) { + cr_rgb_set_from_name (rgb_color, "black"); + } + } else if (a_value->type == TERM_RGB) { + if (a_value->content.rgb) { + status = cr_rgb_set_from_rgb + (rgb_color, a_value->content.rgb); + } + } + return status; +} + +static enum CRStatus +set_prop_border_x_from_value (CRStyle * a_style, CRTerm * a_value, + enum CRDirection a_dir) +{ + CRTerm *cur_term = NULL; + + enum CRStatus status = CR_OK; + + g_return_val_if_fail (a_style && a_value, CR_BAD_PARAM_ERROR); + + for (cur_term = a_value; + cur_term; + cur_term = cur_term->next) { + status = set_prop_border_x_width_from_value (a_style, + cur_term, a_dir); + + if (status != CR_OK) { + status = set_prop_border_x_style_from_value + (a_style, cur_term, a_dir); + } + if (status != CR_OK) { + status = set_prop_border_x_color_from_value + (a_style, cur_term, a_dir); + } + } + return CR_OK; +} + +static enum CRStatus +set_prop_border_from_value (CRStyle * a_style, CRTerm * a_value) +{ + enum CRDirection direction = 0; + + g_return_val_if_fail (a_style && a_value, CR_BAD_PARAM_ERROR); + + for (direction = 0; direction < NB_DIRS; direction++) { + set_prop_border_x_from_value (a_style, + a_value, + direction); + } + + return CR_OK; +} + +static enum CRStatus +set_prop_padding_from_value (CRStyle * a_style, CRTerm * a_value) +{ + CRTerm *cur_term = NULL; + enum CRDirection direction = 0; + enum CRStatus status = CR_OK; + + g_return_val_if_fail (a_style && a_value, CR_BAD_PARAM_ERROR); + + cur_term = a_value; + + /*filter the eventual non NUMBER terms some user can have written here*/ + while (cur_term && cur_term->type != TERM_NUMBER) { + cur_term = cur_term->next; + } + if (!cur_term) + return CR_ERROR ; + + for (direction = 0; direction < NB_DIRS; direction++) { + set_prop_padding_x_from_value (a_style, cur_term, direction); + } + cur_term = cur_term->next; + + /*filter non NUMBER terms that some users can have written here...*/ + while (cur_term && cur_term->type != TERM_NUMBER) { + cur_term = cur_term->next; + } + /*the user can have just written padding: 1px*/ + if (!cur_term) + return CR_OK; + + set_prop_padding_x_from_value (a_style, cur_term, DIR_RIGHT); + set_prop_padding_x_from_value (a_style, cur_term, DIR_LEFT); + + while (cur_term && cur_term->type != TERM_NUMBER) { + cur_term = cur_term->next; + } + if (!cur_term) + return CR_OK; + + set_prop_padding_x_from_value (a_style, cur_term, DIR_BOTTOM); + + while (cur_term && cur_term->type != TERM_NUMBER) { + cur_term = cur_term->next; + } + if (!cur_term) + return CR_OK; + status = set_prop_padding_x_from_value (a_style, cur_term, DIR_LEFT); + return status; +} + +static enum CRStatus +set_prop_margin_from_value (CRStyle * a_style, CRTerm * a_value) +{ + CRTerm *cur_term = NULL; + enum CRDirection direction = 0; + enum CRStatus status = CR_OK; + + g_return_val_if_fail (a_style && a_value, CR_BAD_PARAM_ERROR); + + cur_term = a_value; + + while (cur_term && cur_term->type != TERM_NUMBER) { + cur_term = cur_term->next; + } + + if (!cur_term) + return CR_OK; + + for (direction = 0; direction < NB_DIRS; direction++) { + set_prop_margin_x_from_value (a_style, cur_term, direction); + } + cur_term = cur_term->next; + + while (cur_term && cur_term->type != TERM_NUMBER) { + cur_term = cur_term->next; + } + if (!cur_term) + return CR_OK; + + set_prop_margin_x_from_value (a_style, cur_term, DIR_RIGHT); + set_prop_margin_x_from_value (a_style, cur_term, DIR_LEFT); + + while (cur_term && cur_term->type != TERM_NUMBER) { + cur_term = cur_term->next; + } + if (!cur_term) + return CR_OK; + + set_prop_margin_x_from_value (a_style, cur_term, DIR_BOTTOM); + + while (cur_term && cur_term->type != TERM_NUMBER) { + cur_term = cur_term->next; + } + if (!cur_term) + return CR_OK; + + status = set_prop_margin_x_from_value (a_style, cur_term, DIR_LEFT); + + return status; +} + +static enum CRStatus +set_prop_font_family_from_value (CRStyle * a_style, CRTerm * a_value) +{ + CRTerm *cur_term = NULL; + CRFontFamily *font_family = NULL, + *cur_ff = NULL, + *cur_ff2 = NULL; + + g_return_val_if_fail (a_style && a_value, CR_BAD_PARAM_ERROR); + + if (a_value->type == TERM_IDENT && + a_value->content.str && + a_value->content.str->stryng && + a_value->content.str->stryng->str && + !strcmp ("inherit", a_value->content.str->stryng->str)) + { + font_family = cr_font_family_new (FONT_FAMILY_INHERIT, NULL); + goto out; + } + + for (cur_term = a_value; cur_term; cur_term = cur_term->next) { + switch (cur_term->type) { + case TERM_IDENT: + { + enum CRFontFamilyType font_type; + + if (cur_term->content.str + && cur_term->content.str->stryng + && cur_term->content.str->stryng->str + && !strcmp + (cur_term->content.str->stryng->str, + "sans-serif")) { + font_type = FONT_FAMILY_SANS_SERIF; + } else if (cur_term->content.str + && cur_term->content.str->stryng + && cur_term->content.str->stryng->str + && !strcmp + (cur_term->content.str->stryng->str, + "serif")) { + font_type = FONT_FAMILY_SERIF; + } else if (cur_term->content.str + && cur_term->content.str->stryng + && cur_term->content.str->stryng->str + && !strcmp (cur_term->content.str->stryng->str, + "cursive")) { + font_type = FONT_FAMILY_CURSIVE; + } else if (cur_term->content.str + && cur_term->content.str->stryng + && cur_term->content.str->stryng->str + && !strcmp (cur_term->content.str->stryng->str, + "fantasy")) { + font_type = FONT_FAMILY_FANTASY; + } else if (cur_term->content.str + && cur_term->content.str->stryng + && cur_term->content.str->stryng->str + && !strcmp (cur_term->content.str->stryng->str, + "monospace")) { + font_type = FONT_FAMILY_MONOSPACE; + } else { + /* + *unknown property value. + *ignore it. + */ + continue; + } + + cur_ff = cr_font_family_new (font_type, NULL); + } + break; + + case TERM_STRING: + { + if (cur_term->content.str + && cur_term->content.str->stryng + && cur_term->content.str->stryng->str) { + cur_ff = cr_font_family_new + (FONT_FAMILY_NON_GENERIC, + cur_term->content.str->stryng->str); + } + } + break; + + default: + break; + } + + cur_ff2 = cr_font_family_append (font_family, cur_ff); + if (cur_ff2) { + font_family = cur_ff2; + } + } + + out: + if (font_family) { + if (a_style->font_family) { + cr_font_family_destroy (a_style->font_family); + a_style->font_family = NULL ; + } + a_style->font_family = font_family; + font_family = NULL ; + } + + return CR_OK; +} + +static enum CRStatus +init_style_font_size_field (CRStyle * a_style) +{ + g_return_val_if_fail (a_style, CR_BAD_PARAM_ERROR); + + memset (&a_style->font_size, 0, + sizeof (CRFontSizeVal)) ; + /* + if (!a_style->font_size) { + a_style->font_size = cr_font_size_new (); + if (!a_style->font_size) { + return CR_INSTANCIATION_FAILED_ERROR; + } + } else { + cr_font_size_clear (a_style->font_size); + } + */ + return CR_OK; +} + +static enum CRStatus +set_prop_font_size_from_value (CRStyle * a_style, CRTerm * a_value) +{ + enum CRStatus status = CR_OK; + + g_return_val_if_fail (a_style && a_value, CR_BAD_PARAM_ERROR); + + switch (a_value->type) { + case TERM_IDENT: + if (a_value->content.str + && a_value->content.str->stryng + && a_value->content.str->stryng->str + && !strcmp (a_value->content.str->stryng->str, + "xx-small")) { + status = init_style_font_size_field (a_style); + g_return_val_if_fail (status == CR_OK, status); + + a_style->font_size.sv.type = + PREDEFINED_ABSOLUTE_FONT_SIZE; + a_style->font_size.sv.value.predefined = + FONT_SIZE_XX_SMALL; + + } else if (a_value->content.str + && a_value->content.str->stryng + && a_value->content.str->stryng->str + && !strcmp (a_value->content.str->stryng->str, + "x-small")) { + status = init_style_font_size_field (a_style); + g_return_val_if_fail (status == CR_OK, status); + + a_style->font_size.sv.type = + PREDEFINED_ABSOLUTE_FONT_SIZE; + a_style->font_size.sv.value.predefined = + FONT_SIZE_X_SMALL; + } else if (a_value->content.str + && a_value->content.str->stryng + && a_value->content.str->stryng->str + && !strcmp (a_value->content.str->stryng->str, + "small")) { + status = init_style_font_size_field (a_style); + g_return_val_if_fail (status == CR_OK, status); + + a_style->font_size.sv.type = + PREDEFINED_ABSOLUTE_FONT_SIZE; + a_style->font_size.sv.value.predefined = + FONT_SIZE_SMALL; + } else if (a_value->content.str + && a_value->content.str->stryng + && a_value->content.str->stryng->str + && !strcmp (a_value->content.str->stryng->str, "medium")) { + status = init_style_font_size_field (a_style); + g_return_val_if_fail (status == CR_OK, status); + + a_style->font_size.sv.type = + PREDEFINED_ABSOLUTE_FONT_SIZE; + a_style->font_size.sv.value.predefined = + FONT_SIZE_MEDIUM; + } else if (a_value->content.str + && a_value->content.str->stryng + && a_value->content.str->stryng->str + && !strcmp (a_value->content.str->stryng->str, + "large")) { + status = init_style_font_size_field (a_style); + g_return_val_if_fail (status == CR_OK, status); + + a_style->font_size.sv.type = + PREDEFINED_ABSOLUTE_FONT_SIZE; + a_style->font_size.sv.value.predefined = + FONT_SIZE_LARGE; + } else if (a_value->content.str + && a_value->content.str->stryng + && a_value->content.str->stryng->str + && !strcmp (a_value->content.str->stryng->str, + "x-large")) { + status = init_style_font_size_field (a_style); + g_return_val_if_fail (status == CR_OK, status); + + a_style->font_size.sv.type = + PREDEFINED_ABSOLUTE_FONT_SIZE; + a_style->font_size.sv.value.predefined = + FONT_SIZE_X_LARGE; + } else if (a_value->content.str + && a_value->content.str->stryng + && a_value->content.str->stryng->str + && !strcmp (a_value->content.str->stryng->str, + "xx-large")) { + status = init_style_font_size_field (a_style); + g_return_val_if_fail (status == CR_OK, status); + + a_style->font_size.sv.type = + PREDEFINED_ABSOLUTE_FONT_SIZE; + a_style->font_size.sv.value.predefined = + FONT_SIZE_XX_LARGE; + } else if (a_value->content.str + && a_value->content.str->stryng + && a_value->content.str->stryng->str + && !strcmp (a_value->content.str->stryng->str, + "larger")) { + status = init_style_font_size_field (a_style); + g_return_val_if_fail (status == CR_OK, status); + + a_style->font_size.sv.type = RELATIVE_FONT_SIZE; + a_style->font_size.sv.value.relative = FONT_SIZE_LARGER; + } else if (a_value->content.str + && a_value->content.str->stryng + && a_value->content.str->stryng->str + && !strcmp (a_value->content.str->stryng->str, + "smaller")) { + status = init_style_font_size_field (a_style); + g_return_val_if_fail (status == CR_OK, status); + + a_style->font_size.sv.type = RELATIVE_FONT_SIZE; + a_style->font_size.sv.value.relative = + FONT_SIZE_SMALLER; + } else if (a_value->content.str + && a_value->content.str->stryng + && a_value->content.str->stryng->str + && !strcmp (a_value->content.str->stryng->str, "inherit")) { + status = init_style_font_size_field (a_style); + g_return_val_if_fail (status == CR_OK, status); + a_style->font_size.sv.type = INHERITED_FONT_SIZE; + + } else { + cr_utils_trace_info ("Unknow value of font-size") ; + status = init_style_font_size_field (a_style); + return CR_UNKNOWN_PROP_VAL_ERROR; + } + break; + + case TERM_NUMBER: + if (a_value->content.num) { + status = init_style_font_size_field (a_style); + g_return_val_if_fail (status == CR_OK, status); + + a_style->font_size.sv.type = ABSOLUTE_FONT_SIZE; + cr_num_copy (&a_style->font_size.sv.value.absolute, + a_value->content.num) ; + } + break; + + default: + status = init_style_font_size_field (a_style); + return CR_UNKNOWN_PROP_VAL_ERROR; + } + return CR_OK; +} + +static enum CRStatus +set_prop_font_style_from_value (CRStyle * a_style, CRTerm * a_value) +{ + enum CRStatus status = CR_OK; + + g_return_val_if_fail (a_style && a_value, CR_BAD_PARAM_ERROR); + + switch (a_value->type) { + case TERM_IDENT: + if (a_value->content.str + && a_value->content.str->stryng + && a_value->content.str->stryng->str) { + if (!strcmp (a_value->content.str->stryng->str, "normal")) { + a_style->font_style = FONT_STYLE_NORMAL; + } else if (!strcmp + (a_value->content.str->stryng->str, + "italic")) { + a_style->font_style = FONT_STYLE_ITALIC; + } else if (!strcmp + (a_value->content.str->stryng->str, + "oblique")) { + a_style->font_style = FONT_STYLE_OBLIQUE; + } else if (!strcmp + (a_value->content.str->stryng->str, + "inherit")) { + a_style->font_style = FONT_STYLE_INHERIT; + } else { + status = CR_UNKNOWN_PROP_VAL_ERROR; + } + } + break; + + default: + status = CR_UNKNOWN_PROP_VAL_ERROR; + break; + } + + return status; +} + +static enum CRStatus +set_prop_font_weight_from_value (CRStyle * a_style, CRTerm * a_value) +{ + enum CRStatus status = CR_OK; + + g_return_val_if_fail (a_style && a_value, CR_BAD_PARAM_ERROR); + + switch (a_value->type) { + case TERM_IDENT: + if (a_value->content.str + && a_value->content.str->stryng + && a_value->content.str->stryng->str) { + if (!strcmp (a_value->content.str->stryng->str, + "normal")) { + a_style->font_weight = FONT_WEIGHT_NORMAL; + } else if (!strcmp (a_value->content.str->stryng->str, + "bold")) { + a_style->font_weight = FONT_WEIGHT_BOLD; + } else if (!strcmp (a_value->content.str->stryng->str, + "bolder")) { + a_style->font_weight = FONT_WEIGHT_BOLDER; + } else if (!strcmp (a_value->content.str->stryng->str, + "lighter")) { + a_style->font_weight = FONT_WEIGHT_LIGHTER; + } else if (!strcmp (a_value->content.str->stryng->str, + "inherit")) { + a_style->font_weight = FONT_WEIGHT_INHERIT; + + } else { + status = CR_UNKNOWN_PROP_VAL_ERROR; + } + + } + break; + + case TERM_NUMBER: + if (a_value->content.num + && (a_value->content.num->type == NUM_GENERIC + || a_value->content.num->type == NUM_AUTO)) { + if (a_value->content.num->val <= 150) { + a_style->font_weight = FONT_WEIGHT_100; + } else if (a_value->content.num->val <= 250) { + a_style->font_weight = FONT_WEIGHT_200; + } else if (a_value->content.num->val <= 350) { + a_style->font_weight = FONT_WEIGHT_300; + } else if (a_value->content.num->val <= 450) { + a_style->font_weight = FONT_WEIGHT_400; + } else if (a_value->content.num->val <= 550) { + a_style->font_weight = FONT_WEIGHT_500; + } else if (a_value->content.num->val <= 650) { + a_style->font_weight = FONT_WEIGHT_600; + } else if (a_value->content.num->val <= 750) { + a_style->font_weight = FONT_WEIGHT_700; + } else if (a_value->content.num->val <= 850) { + a_style->font_weight = FONT_WEIGHT_800; + } else { + a_style->font_weight = FONT_WEIGHT_900; + } + } + break; + + default: + status = CR_UNKNOWN_PROP_VAL_ERROR; + break; + } + + return status; +} + +static enum CRStatus +set_prop_white_space_from_value (CRStyle * a_style, CRTerm * a_value) +{ + enum CRStatus status = CR_OK; + + g_return_val_if_fail (a_style && a_value, CR_BAD_PARAM_ERROR); + + switch (a_value->type) { + case TERM_IDENT: + if (a_value->content.str && a_value->content.str->stryng) { + if (!strcmp (a_value->content.str->stryng->str, "normal")) { + a_style->white_space = WHITE_SPACE_NORMAL; + } else if (!strcmp (a_value->content.str->stryng->str, + "pre")) { + a_style->font_weight = WHITE_SPACE_PRE; + } else if (!strcmp (a_value->content.str->stryng->str, + "nowrap")) { + a_style->white_space = WHITE_SPACE_NOWRAP; + } else if (!strcmp (a_value->content.str->stryng->str, + "inherit")) { + a_style->white_space = WHITE_SPACE_INHERIT; + } else { + status = CR_UNKNOWN_PROP_VAL_ERROR; + } + } + break; + default: + status = CR_UNKNOWN_PROP_VAL_ERROR; + break; + } + + return status; +} + +/****************** + *Public methods + ******************/ + +/** + *Default constructor of #CRStyle. + *@param a_set_props_to_initial_values if TRUE, the style properties + *will be set to the default values. Only the style properties of the + *root box should be set to their initial values. + *Otherwise, the style values are set to their default value. + *Read the CSS2 spec, chapters 6.1.1 to 6.2. + */ +CRStyle * +cr_style_new (gboolean a_set_props_to_initial_values) +{ + CRStyle *result = NULL; + + result = g_try_malloc (sizeof (CRStyle)); + if (!result) { + cr_utils_trace_info ("Out of memory"); + return NULL; + } + memset (result, 0, sizeof (CRStyle)); + gv_prop_hash_ref_count++; + + if (a_set_props_to_initial_values == TRUE) { + cr_style_set_props_to_initial_values (result); + } else { + cr_style_set_props_to_default_values (result); + } + + return result; +} + +/** + *Sets the style properties to their default values according to the css2 spec + * i.e inherit if the property is inherited, its initial value otherwise. + *@param a_this the current instance of #CRStyle. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_style_set_props_to_default_values (CRStyle * a_this) +{ + glong i = 0; + + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + for (i = 0; i < NB_NUM_PROPS; i++) + { + switch (i) + { + case NUM_PROP_WIDTH: + case NUM_PROP_TOP: + case NUM_PROP_RIGHT: + case NUM_PROP_BOTTOM: + case NUM_PROP_LEFT: + cr_num_set (&a_this->num_props[i].sv, 0, NUM_AUTO); + break; + + case NUM_PROP_PADDING_TOP: + case NUM_PROP_PADDING_RIGHT: + case NUM_PROP_PADDING_BOTTOM: + case NUM_PROP_PADDING_LEFT: + case NUM_PROP_BORDER_TOP: + case NUM_PROP_BORDER_RIGHT: + case NUM_PROP_BORDER_BOTTOM: + case NUM_PROP_BORDER_LEFT: + case NUM_PROP_MARGIN_TOP: + case NUM_PROP_MARGIN_RIGHT: + case NUM_PROP_MARGIN_BOTTOM: + case NUM_PROP_MARGIN_LEFT: + cr_num_set (&a_this->num_props[i].sv, + 0, NUM_LENGTH_PX); + break; + + default: + cr_utils_trace_info ("Unknown property"); + break; + } + } + + for (i = 0; i < NB_RGB_PROPS; i++) { + + switch (i) { + /*default foreground color is black */ + case RGB_PROP_COLOR: + /* + *REVIEW: color is inherited and the default value is + *ua dependant. + */ + cr_rgb_set_to_inherit (&a_this->rgb_props[i].sv, + TRUE) ; + break; + + /*default background color is white */ + case RGB_PROP_BACKGROUND_COLOR: + /* TODO: the default value should be transparent */ + cr_rgb_set (&a_this->rgb_props[i].sv, + 255, 255, 255, FALSE); + cr_rgb_set_to_transparent (&a_this->rgb_props[i].sv, + TRUE) ; + break; + + default: + /* + *TODO: for BORDER_COLOR the initial value should + * be the same as COLOR + */ + cr_rgb_set (&a_this->rgb_props[i].sv, 0, 0, 0, + FALSE); + break; + } + } + + for (i = 0; i < NB_BORDER_STYLE_PROPS; i++) { + a_this->border_style_props[i] = BORDER_STYLE_NONE; + } + + a_this->display = DISPLAY_INLINE; + a_this->position = POSITION_STATIC; + a_this->float_type = FLOAT_NONE; + a_this->parent_style = NULL; + a_this->font_style = FONT_STYLE_INHERIT; + a_this->font_variant = FONT_VARIANT_INHERIT; + a_this->font_weight = FONT_WEIGHT_INHERIT; + a_this->font_family = NULL; + + cr_font_size_set_to_inherit (&a_this->font_size.sv) ; + cr_font_size_clear (&a_this->font_size.cv) ; + cr_font_size_clear (&a_this->font_size.av) ; + + /* To make the inheritance resolution possible and efficient */ + a_this->inherited_props_resolved = FALSE ; + return CR_OK; +} + +/** + *Sets the style properties to their initial value according to the css2 spec. + *This function should be used to initialize the style of the root element + *of an xml tree. + *Some properties are user agent dependant like font-family, and + *are not initialized, read the spec to make you renderer compliant. + *@param a_this the current instance of #CRStyle. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_style_set_props_to_initial_values (CRStyle *a_this) +{ + glong i = 0; + + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + for (i = 0; i < NB_NUM_PROPS; i++) { + switch (i) { + case NUM_PROP_WIDTH: + cr_num_set (&a_this->num_props[i].sv, 800, + NUM_LENGTH_PX) ; + break ; + case NUM_PROP_TOP: + case NUM_PROP_RIGHT: + case NUM_PROP_BOTTOM: + case NUM_PROP_LEFT: + cr_num_set (&a_this->num_props[i].sv, 0, NUM_AUTO); + break; + + case NUM_PROP_PADDING_TOP: + case NUM_PROP_PADDING_RIGHT: + case NUM_PROP_PADDING_BOTTOM: + case NUM_PROP_PADDING_LEFT: + case NUM_PROP_BORDER_TOP: + case NUM_PROP_BORDER_RIGHT: + case NUM_PROP_BORDER_BOTTOM: + case NUM_PROP_BORDER_LEFT: + case NUM_PROP_MARGIN_TOP: + case NUM_PROP_MARGIN_RIGHT: + case NUM_PROP_MARGIN_BOTTOM: + case NUM_PROP_MARGIN_LEFT: + cr_num_set (&a_this->num_props[i].sv, + 0, NUM_LENGTH_PX); + break; + + default: + cr_utils_trace_info ("Unknown property"); + break; + } + } + + for (i = 0; i < NB_RGB_PROPS; i++) { + + switch (i) { + /*default foreground color is black */ + case RGB_PROP_COLOR: + cr_rgb_set (&a_this->rgb_props[i].sv, 0, 0, 0, FALSE); + break; + + /*default background color is white */ + case RGB_PROP_BACKGROUND_COLOR: + cr_rgb_set (&a_this->rgb_props[i].sv, + 255, 255, 255, FALSE); + cr_rgb_set_to_transparent (&a_this->rgb_props[i].sv, + TRUE) ; + break; + default: + cr_rgb_set (&a_this->rgb_props[i].sv, 0, 0, 0, FALSE); + break; + } + } + + for (i = 0; i < NB_BORDER_STYLE_PROPS; i++) { + a_this->border_style_props[i] = BORDER_STYLE_NONE; + } + + a_this->display = DISPLAY_BLOCK; + a_this->position = POSITION_STATIC; + a_this->float_type = FLOAT_NONE; + a_this->font_style = FONT_STYLE_NORMAL; + a_this->font_variant = FONT_VARIANT_NORMAL; + a_this->font_weight = FONT_WEIGHT_NORMAL; + a_this->font_stretch = FONT_STRETCH_NORMAL; + a_this->white_space = WHITE_SPACE_NORMAL; + cr_font_size_set_predefined_absolute_font_size + (&a_this->font_size.sv, FONT_SIZE_MEDIUM) ; + a_this->inherited_props_resolved = FALSE ; + + return CR_OK; +} + +/** + *Resolves the inherited properties. + *The function sets the "inherited" properties to either the value of + *their parent properties. + *This function is *NOT* recursive. So the inherited properties of + *the parent style must have been resolved prior to calling this function. + *@param a_this the instance where + *@return CR_OK if a root node is found and the propagation is successful, + *an error code otherwise + */ +enum CRStatus +cr_style_resolve_inherited_properties (CRStyle *a_this) +{ + enum CRStatus ret = CR_OK; + glong i = 0; + + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + g_return_val_if_fail (a_this->parent_style, CR_BAD_PARAM_ERROR) ; + + if (a_this->inherited_props_resolved == TRUE) + return CR_OK ; + + for (i=0 ; i < NB_NUM_PROPS ;i++) { + if (a_this->num_props[i].sv.type == NUM_INHERIT) { + cr_num_copy (&a_this->num_props[i].cv, + &a_this->parent_style->num_props[i].cv); + } + } + for (i=0; i < NB_RGB_PROPS; i++) { + if (cr_rgb_is_set_to_inherit (&a_this->rgb_props[i].sv) == TRUE) { + cr_rgb_copy ( + &a_this->rgb_props[i].cv, + &a_this->parent_style->rgb_props[i].cv); + } + } + for (i = 0; i < NB_BORDER_STYLE_PROPS; i++) { + if (a_this->border_style_props[i] == BORDER_STYLE_INHERIT) { + a_this->border_style_props[i] = + a_this->parent_style->border_style_props[i]; + } + } + + if (a_this->display == DISPLAY_INHERIT) { + a_this->display = a_this->parent_style->display; + } + if (a_this->position == POSITION_INHERIT) { + a_this->position = a_this->parent_style->position; + } + if (a_this->float_type == FLOAT_INHERIT) { + a_this->float_type = a_this->parent_style->float_type; + } + if (a_this->font_style == FONT_STYLE_INHERIT) { + a_this->font_style = a_this->parent_style->font_style; + } + if (a_this->font_variant == FONT_VARIANT_INHERIT) { + a_this->font_variant = a_this->parent_style->font_variant; + } + if (a_this->font_weight == FONT_WEIGHT_INHERIT) { + a_this->font_weight = a_this->parent_style->font_weight; + } + if (a_this->font_stretch == FONT_STRETCH_INHERIT) { + a_this->font_stretch = a_this->parent_style->font_stretch; + } + /*NULL is inherit marker for font_famiy*/ + if (a_this->font_family == NULL) { + a_this->font_family = a_this->parent_style->font_family; + } + if (a_this->font_size.sv.type == INHERITED_FONT_SIZE) { + cr_font_size_copy (&a_this->font_size.cv, + &a_this->parent_style->font_size.cv) ; + } + a_this->inherited_props_resolved = TRUE ; + return ret; +} + +/** + *Walks through a css2 property declaration, and populated the + *according field(s) in the #CRStyle structure. + *If the properties or their value(s) are/is not known, + *sets the corresponding field(s) of #CRStyle to its/their default + *value(s) + *@param a_this the instance of #CRStyle to set. + *@param a_decl the declaration from which the #CRStyle fields are set. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_style_set_style_from_decl (CRStyle * a_this, CRDeclaration * a_decl) +{ + CRTerm *value = NULL; + enum CRStatus status = CR_OK; + + enum CRPropertyID prop_id = PROP_ID_NOT_KNOWN; + + g_return_val_if_fail (a_this && a_decl + && a_decl + && a_decl->property + && a_decl->property->stryng + && a_decl->property->stryng->str, + CR_BAD_PARAM_ERROR); + + prop_id = cr_style_get_prop_id + (a_decl->property->stryng->str); + + value = a_decl->value; + switch (prop_id) { + case PROP_ID_PADDING_TOP: + status = set_prop_padding_x_from_value + (a_this, value, DIR_TOP); + break; + + case PROP_ID_PADDING_RIGHT: + status = set_prop_padding_x_from_value + (a_this, value, DIR_RIGHT); + break; + case PROP_ID_PADDING_BOTTOM: + status = set_prop_padding_x_from_value + (a_this, value, DIR_BOTTOM); + break; + + case PROP_ID_PADDING_LEFT: + status = set_prop_padding_x_from_value + (a_this, value, DIR_LEFT); + break; + + case PROP_ID_PADDING: + status = set_prop_padding_from_value (a_this, value) ; + break; + + case PROP_ID_BORDER_TOP_WIDTH: + status = set_prop_border_x_width_from_value (a_this, value, + DIR_TOP); + break; + + case PROP_ID_BORDER_RIGHT_WIDTH: + status = set_prop_border_x_width_from_value (a_this, value, + DIR_RIGHT); + break; + + case PROP_ID_BORDER_BOTTOM_WIDTH: + status = set_prop_border_x_width_from_value (a_this, value, + DIR_BOTTOM); + break; + + case PROP_ID_BORDER_LEFT_WIDTH: + status = set_prop_border_x_width_from_value (a_this, value, + DIR_LEFT); + break; + + case PROP_ID_BORDER_WIDTH: + status = set_prop_border_width_from_value (a_this, value) ; + break ; + + case PROP_ID_BORDER_TOP_STYLE: + status = set_prop_border_x_style_from_value (a_this, value, + DIR_TOP); + break; + + case PROP_ID_BORDER_RIGHT_STYLE: + status = set_prop_border_x_style_from_value (a_this, value, + DIR_RIGHT); + break; + + case PROP_ID_BORDER_BOTTOM_STYLE: + status = set_prop_border_x_style_from_value (a_this, value, + DIR_BOTTOM); + break; + + case PROP_ID_BORDER_LEFT_STYLE: + status = set_prop_border_x_style_from_value (a_this, value, + DIR_LEFT); + break; + + case PROP_ID_BORDER_STYLE: + status = set_prop_border_style_from_value (a_this, value) ; + break ; + + case PROP_ID_BORDER_TOP_COLOR: + status = set_prop_border_x_color_from_value (a_this, value, + DIR_TOP); + break; + + case PROP_ID_BORDER_RIGHT_COLOR: + status = set_prop_border_x_color_from_value (a_this, value, + DIR_RIGHT); + break; + + case PROP_ID_BORDER_BOTTOM_COLOR: + status = set_prop_border_x_color_from_value (a_this, value, + DIR_BOTTOM); + break; + + case PROP_ID_BORDER_LEFT_COLOR: + status = set_prop_border_x_color_from_value (a_this, value, + DIR_BOTTOM); + break; + + case PROP_ID_BORDER_TOP: + status = set_prop_border_x_from_value (a_this, value, + DIR_TOP); + break; + + case PROP_ID_BORDER_RIGHT: + status = set_prop_border_x_from_value (a_this, value, + DIR_RIGHT); + break; + + case PROP_ID_BORDER_BOTTOM: + status = set_prop_border_x_from_value (a_this, value, + DIR_BOTTOM); + break; + + case PROP_ID_BORDER_LEFT: + status = set_prop_border_x_from_value (a_this, value, + DIR_LEFT); + break; + + case PROP_ID_MARGIN_TOP: + status = set_prop_margin_x_from_value (a_this, value, + DIR_TOP); + break; + + case PROP_ID_BORDER: + status = set_prop_border_from_value (a_this, value); + break; + + case PROP_ID_MARGIN_RIGHT: + status = set_prop_margin_x_from_value (a_this, value, + DIR_RIGHT); + break; + + case PROP_ID_MARGIN_BOTTOM: + status = set_prop_margin_x_from_value (a_this, value, + DIR_BOTTOM); + break; + + case PROP_ID_MARGIN_LEFT: + status = set_prop_margin_x_from_value (a_this, value, + DIR_LEFT); + break; + + case PROP_ID_MARGIN: + status = set_prop_margin_from_value (a_this, value); + break; + + case PROP_ID_DISPLAY: + status = set_prop_display_from_value (a_this, value); + break; + + case PROP_ID_POSITION: + status = set_prop_position_from_value (a_this, value); + break; + + case PROP_ID_TOP: + status = set_prop_x_from_value (a_this, value, DIR_TOP); + break; + + case PROP_ID_RIGHT: + status = set_prop_x_from_value (a_this, value, DIR_RIGHT); + break; + + case PROP_ID_BOTTOM: + status = set_prop_x_from_value (a_this, value, DIR_BOTTOM); + break; + + case PROP_ID_LEFT: + status = set_prop_x_from_value (a_this, value, DIR_LEFT); + break; + + case PROP_ID_FLOAT: + status = set_prop_float (a_this, value); + break; + + case PROP_ID_WIDTH: + status = set_prop_width (a_this, value); + break; + + case PROP_ID_COLOR: + status = set_prop_color (a_this, value); + break; + + case PROP_ID_BACKGROUND_COLOR: + status = set_prop_background_color (a_this, value); + break; + + case PROP_ID_FONT_FAMILY: + status = set_prop_font_family_from_value (a_this, value); + break; + + case PROP_ID_FONT_SIZE: + status = set_prop_font_size_from_value (a_this, value); + break; + + case PROP_ID_FONT_STYLE: + status = set_prop_font_style_from_value (a_this, value); + break; + + case PROP_ID_FONT_WEIGHT: + status = set_prop_font_weight_from_value (a_this, value); + break; + + case PROP_ID_WHITE_SPACE: + status = set_prop_white_space_from_value(a_this, value); + break; + + default: + return CR_UNKNOWN_TYPE_ERROR; + + } + + return status; +} + +/** + *Increases the reference count + *of the current instance of #CRStyle. + *@param a_this the current instance of #CRStyle. + *@return CR_OK upon successfull completion, an error code + *otherwise. + */ +enum CRStatus +cr_style_ref (CRStyle * a_this) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + a_this->ref_count++; + return CR_OK; +} + +/** + *Decreases the reference count of + *the current instance of #CRStyle. + *If the reference count reaches 0, the + *instance of #CRStyle is destoyed. + *@param a_this the current instance of #CRStyle. + *@return TRUE if the instance has been destroyed, FALSE + *otherwise. + */ +gboolean +cr_style_unref (CRStyle * a_this) +{ + g_return_val_if_fail (a_this, FALSE); + + if (a_this->ref_count) + a_this->ref_count--; + + if (!a_this->ref_count) { + cr_style_destroy (a_this); + return TRUE; + } + + return FALSE; +} + +/** + *Duplicates the current instance of #CRStyle . + *The newly created instance of #CRStyle must be + *freed using cr_style_destroy (). + *@param a_this the current instance of #CRStyle. + *@return the newly duplicated instance of #CRStyle. + */ +CRStyle * +cr_style_dup (CRStyle * a_this) +{ + CRStyle *result = NULL; + + g_return_val_if_fail (a_this, NULL); + + result = cr_style_new (FALSE); + if (!result) { + cr_utils_trace_info ("Out of memory"); + return NULL; + } + cr_style_copy (result, a_this); + return result; +} + +/** + *Copies a style data structure into another. + *TODO: this is actually broken because it's based + *on memcpy although some data stuctures of CRStyle should + *be properly duplicated. + *@param a_dest the destination style datastructure + *@param a_src the source style datastructure. + *@return CR_OK upon succesfull completion, an error code otherwise + */ +enum CRStatus +cr_style_copy (CRStyle * a_dest, CRStyle * a_src) +{ + g_return_val_if_fail (a_dest && a_src, CR_BAD_PARAM_ERROR); + + memcpy (a_dest, a_src, sizeof (CRStyle)); + return CR_OK; +} + +/** + *dump a CRNumpPropVal in a string. + *@param a_prop_val the numerical property value to dump + *@param a_str the string to dump the numerical propertie into. + *Note that the string value is appended to a_str. + *@param a_nb_indent the number white chars of indentation. + */ +enum CRStatus +cr_style_num_prop_val_to_string (CRNumPropVal * a_prop_val, + GString * a_str, guint a_nb_indent) +{ + enum CRStatus status = CR_OK; + guchar *tmp_str = NULL; + GString *str = NULL; + + g_return_val_if_fail (a_prop_val && a_str, CR_BAD_PARAM_ERROR); + + str = g_string_new (NULL); + cr_utils_dump_n_chars2 (' ', str, a_nb_indent); + g_string_append (str, "NumPropVal {"); + tmp_str = cr_num_to_string (&a_prop_val->sv); + if (!tmp_str) { + status = CR_ERROR; + goto cleanup; + } + g_string_append_printf (str, "sv: %s ", tmp_str); + g_free (tmp_str); + tmp_str = NULL; + + tmp_str = cr_num_to_string (&a_prop_val->cv); + if (!tmp_str) { + status = CR_ERROR; + goto cleanup; + } + g_string_append_printf (str, "cv: %s ", tmp_str); + g_free (tmp_str); + tmp_str = NULL; + + tmp_str = cr_num_to_string (&a_prop_val->av); + if (!tmp_str) { + status = CR_ERROR; + goto cleanup; + } + g_string_append_printf (str, "av: %s ", tmp_str); + g_free (tmp_str); + tmp_str = NULL; + g_string_append (str, "}"); + g_string_append (a_str, str->str); + status = CR_OK; + cleanup: + + if (tmp_str) { + g_free (tmp_str); + tmp_str = NULL; + } + if (str) { + g_string_free (str, TRUE); + } + return status; +} + +enum CRStatus +cr_style_rgb_prop_val_to_string (CRRgbPropVal * a_prop_val, + GString * a_str, guint a_nb_indent) +{ + enum CRStatus status = CR_OK; + guchar *tmp_str = NULL; + GString *str = NULL; + + g_return_val_if_fail (a_prop_val && a_str, CR_BAD_PARAM_ERROR); + + str = g_string_new (NULL); + + cr_utils_dump_n_chars2 (' ', str, a_nb_indent); + g_string_append (str, "RGBPropVal {"); + tmp_str = cr_rgb_to_string (&a_prop_val->sv); + if (!tmp_str) { + status = CR_ERROR; + goto cleanup; + } + g_string_append_printf (str, "sv: %s ", tmp_str); + g_free (tmp_str); + tmp_str = NULL; + tmp_str = cr_rgb_to_string (&a_prop_val->cv); + if (!tmp_str) { + status = CR_ERROR; + goto cleanup; + } + g_string_append_printf (str, "cv: %s ", tmp_str); + g_free (tmp_str); + tmp_str = NULL; + tmp_str = cr_rgb_to_string (&a_prop_val->av); + if (!tmp_str) { + status = CR_ERROR; + goto cleanup; + } + g_string_append_printf (str, "av: %s ", tmp_str); + g_free (tmp_str); + tmp_str = NULL; + + g_string_append (str, "}"); + g_string_append (a_str, str->str); + status = CR_OK; + cleanup: + + if (tmp_str) { + g_free (tmp_str); + tmp_str = NULL; + } + if (str) { + g_string_free (str, TRUE); + } + return status; +} + +enum CRStatus +cr_style_border_style_to_string (enum CRBorderStyle a_prop, + GString * a_str, guint a_nb_indent) +{ + gchar *str = NULL; + + g_return_val_if_fail (a_str, CR_BAD_PARAM_ERROR); + + switch (a_prop) { + case BORDER_STYLE_NONE: + str = (gchar *) "border-style-none"; + break; + case BORDER_STYLE_HIDDEN: + str = (gchar *) "border-style-hidden"; + break; + case BORDER_STYLE_DOTTED: + str = (gchar *) "border-style-dotted"; + break; + case BORDER_STYLE_DASHED: + str = (gchar *) "border-style-dashed"; + break; + case BORDER_STYLE_SOLID: + str = (gchar *) "border-style-solid"; + break; + case BORDER_STYLE_DOUBLE: + str = (gchar *) "border-style-double"; + break; + case BORDER_STYLE_GROOVE: + str = (gchar *) "border-style-groove"; + break; + case BORDER_STYLE_RIDGE: + str = (gchar *) "border-style-ridge"; + break; + case BORDER_STYLE_INSET: + str = (gchar *) "border-style-inset"; + break; + case BORDER_STYLE_OUTSET: + str = (gchar *) "border-style-outset"; + break; + default: + str = (gchar *) "unknown border style"; + break; + } + cr_utils_dump_n_chars2 (' ', a_str, a_nb_indent); + g_string_append (a_str, str); + return CR_OK; +} + +enum CRStatus +cr_style_display_type_to_string (enum CRDisplayType a_code, + GString * a_str, guint a_nb_indent) +{ + gchar *str = NULL; + + g_return_val_if_fail (a_str, CR_BAD_PARAM_ERROR); + + switch (a_code) { + case DISPLAY_NONE: + str = (gchar *) "display-none"; + break; + case DISPLAY_INLINE: + str = (gchar *) "display-inline"; + break; + case DISPLAY_BLOCK: + str = (gchar *) "display-block"; + break; + case DISPLAY_LIST_ITEM: + str = (gchar *) "display-list-item"; + break; + case DISPLAY_RUN_IN: + str = (gchar *) "display-run-in"; + break; + case DISPLAY_COMPACT: + str = (gchar *) "display-compact"; + break; + case DISPLAY_MARKER: + str = (gchar *) "display-marker"; + break; + case DISPLAY_TABLE: + str = (gchar *) "display-table"; + break; + case DISPLAY_INLINE_TABLE: + str = (gchar *) "display-inline-table"; + break; + case DISPLAY_TABLE_ROW_GROUP: + str = (gchar *) "display-table-row-group"; + break; + case DISPLAY_TABLE_HEADER_GROUP: + str = (gchar *) "display-table-header-group"; + break; + case DISPLAY_TABLE_FOOTER_GROUP: + str = (gchar *) "display-table-footer-group"; + break; + case DISPLAY_TABLE_ROW: + str = (gchar *) "display-table-row"; + break; + case DISPLAY_TABLE_COLUMN_GROUP: + str = (gchar *) "display-table-column-group"; + break; + case DISPLAY_TABLE_COLUMN: + str = (gchar *) "display-table-column"; + break; + case DISPLAY_TABLE_CELL: + str = (gchar *) "display-table-cell"; + break; + case DISPLAY_TABLE_CAPTION: + str = (gchar *) "display-table-caption"; + break; + case DISPLAY_INHERIT: + str = (gchar *) "display-inherit"; + break; + default: + str = (gchar *) "unknown display property"; + break; + } + cr_utils_dump_n_chars2 (' ', a_str, a_nb_indent); + g_string_append (a_str, str); + return CR_OK; + +} + +enum CRStatus +cr_style_position_type_to_string (enum CRPositionType a_code, + GString * a_str, guint a_nb_indent) +{ + gchar *str = NULL; + + g_return_val_if_fail (a_str, CR_BAD_PARAM_ERROR); + + switch (a_code) { + case POSITION_STATIC: + str = (gchar *) "position-static"; + break; + case POSITION_RELATIVE: + str = (gchar *) "position-relative"; + break; + case POSITION_ABSOLUTE: + str = (gchar *) "position-absolute"; + break; + case POSITION_FIXED: + str = (gchar *) "position-fixed"; + break; + case POSITION_INHERIT: + str = (gchar *) "position-inherit"; + break; + default: + str = (gchar *) "unknown static property"; + } + cr_utils_dump_n_chars2 (' ', a_str, a_nb_indent); + g_string_append (a_str, str); + return CR_OK; +} + +enum CRStatus +cr_style_float_type_to_string (enum CRFloatType a_code, + GString * a_str, guint a_nb_indent) +{ + gchar *str = NULL; + + g_return_val_if_fail (a_str, CR_BAD_PARAM_ERROR); + + switch (a_code) { + case FLOAT_NONE: + str = (gchar *) "float-none"; + break; + case FLOAT_LEFT: + str = (gchar *) "float-left"; + break; + case FLOAT_RIGHT: + str = (gchar *) "float-right"; + break; + case FLOAT_INHERIT: + str = (gchar *) "float-inherit"; + break; + default: + str = (gchar *) "unknown float property value"; + break; + } + cr_utils_dump_n_chars2 (' ', a_str, a_nb_indent); + g_string_append (a_str, str); + return CR_OK; +} + +enum CRStatus +cr_style_white_space_type_to_string (enum CRWhiteSpaceType a_code, + GString * a_str, guint a_nb_indent) +{ + gchar *str = NULL; + + g_return_val_if_fail (a_str, CR_BAD_PARAM_ERROR); + + switch (a_code) { + case WHITE_SPACE_NORMAL: + str = (gchar *) "normal"; + break; + case WHITE_SPACE_PRE: + str = (gchar *) "pre"; + break; + case WHITE_SPACE_NOWRAP: + str = (gchar *) "nowrap"; + break; + case WHITE_SPACE_INHERIT: + str = (gchar *) "inherited"; + break; + default: + str = (gchar *) "unknow white space property value"; + break; + } + cr_utils_dump_n_chars2 (' ', a_str, a_nb_indent); + g_string_append (a_str, str); + return CR_OK; +} + +/** + *Serializes in instance of #CRStyle into + *a string + *@param a_this the instance of #CRStyle to serialize + *@param a_str the string to serialise the style into. + *if *a_str is NULL, a new GString is instanciated, otherwise + *the style serialisation is appended to the existed *a_str + *@param the number of white space char to use for indentation. + *@return CR_OK upon successful completion, an error code otherwise. + */ +enum CRStatus +cr_style_to_string (CRStyle * a_this, GString ** a_str, guint a_nb_indent) +{ + const gint INTERNAL_INDENT = 2; + gint indent = a_nb_indent + INTERNAL_INDENT; + gchar *tmp_str = NULL; + GString *str = NULL; + gint i = 0; + + g_return_val_if_fail (a_this && a_str, CR_BAD_PARAM_ERROR); + + if (!*a_str) { + str = g_string_new (NULL); + } else { + str = *a_str; + } + cr_utils_dump_n_chars2 (' ', str, a_nb_indent); + g_string_append (str, "style {\n"); + + /*loop over the num_props and to_string() them */ + for (i = NUM_PROP_TOP; i < NB_NUM_PROPS; i++) { + /* + *to_string() the name of the num_prop + *(using num_prop_code_to_string) + *before outputing it value + */ + cr_utils_dump_n_chars2 (' ', str, indent); + tmp_str = (gchar *) num_prop_code_to_string (i); + if (tmp_str) { + g_string_append_printf (str, "%s: ", tmp_str); + } else { + g_string_append (str, "NULL"); + } + tmp_str = NULL; + cr_style_num_prop_val_to_string (&a_this->num_props[i], str, + a_nb_indent + + INTERNAL_INDENT); + g_string_append (str, "\n"); + } + /*loop over the rgb_props and to_string() them all */ + for (i = RGB_PROP_BORDER_TOP_COLOR; i < NB_RGB_PROPS; i++) { + tmp_str = (gchar *) rgb_prop_code_to_string (i); + cr_utils_dump_n_chars2 (' ', str, indent); + if (tmp_str) { + g_string_append_printf (str, "%s: ", tmp_str); + } else { + g_string_append (str, "NULL: "); + } + tmp_str = NULL; + cr_style_rgb_prop_val_to_string (&a_this->rgb_props[i], str, + a_nb_indent + + INTERNAL_INDENT); + g_string_append (str, "\n"); + } + /*loop over the border_style_props and to_string() them */ + for (i = BORDER_STYLE_PROP_TOP; i < NB_BORDER_STYLE_PROPS; i++) { + tmp_str = (gchar *) border_style_prop_code_to_string (i); + cr_utils_dump_n_chars2 (' ', str, indent); + if (tmp_str) { + g_string_append_printf (str, "%s: ", tmp_str); + } else { + g_string_append (str, "NULL: "); + } + tmp_str = NULL; + cr_style_border_style_to_string (a_this-> + border_style_props[i], str, + 0); + g_string_append (str, "\n"); + } + cr_utils_dump_n_chars2 (' ', str, indent); + g_string_append (str, "display: "); + cr_style_display_type_to_string (a_this->display, str, 0); + g_string_append (str, "\n"); + + cr_utils_dump_n_chars2 (' ', str, indent); + g_string_append (str, "position: "); + cr_style_position_type_to_string (a_this->position, str, 0); + g_string_append (str, "\n"); + + cr_utils_dump_n_chars2 (' ', str, indent); + g_string_append (str, "float-type: "); + cr_style_float_type_to_string (a_this->float_type, str, 0); + g_string_append (str, "\n"); + + cr_utils_dump_n_chars2 (' ', str, indent); + g_string_append (str, "white-space: "); + cr_style_white_space_type_to_string (a_this->white_space, str, 0); + g_string_append (str, "\n"); + + cr_utils_dump_n_chars2 (' ', str, indent); + g_string_append (str, "font-family: "); + tmp_str = cr_font_family_to_string (a_this->font_family, TRUE); + if (tmp_str) { + g_string_append (str, tmp_str); + g_free (tmp_str); + tmp_str = NULL; + } else { + g_string_append (str, "NULL"); + } + g_string_append (str, "\n"); + + cr_utils_dump_n_chars2 (' ', str, indent); + tmp_str = cr_font_size_to_string (&a_this->font_size.sv); + if (tmp_str) { + g_string_append_printf (str, "font-size {sv:%s, ", + tmp_str) ; + } else { + g_string_append (str, "font-size {sv:NULL, "); + } + tmp_str = cr_font_size_to_string (&a_this->font_size.cv); + if (tmp_str) { + g_string_append_printf (str, "cv:%s, ", tmp_str); + } else { + g_string_append (str, "cv:NULL, "); + } + tmp_str = cr_font_size_to_string (&a_this->font_size.av); + if (tmp_str) { + g_string_append_printf (str, "av:%s}", tmp_str); + } else { + g_string_append (str, "av:NULL}"); + } + + tmp_str = NULL; + g_string_append (str, "\n"); + + cr_utils_dump_n_chars2 (' ', str, indent); + tmp_str = cr_font_size_adjust_to_string (a_this->font_size_adjust); + if (tmp_str) { + g_string_append_printf (str, "font-size-adjust: %s", tmp_str); + } else { + g_string_append (str, "font-size-adjust: NULL"); + } + tmp_str = NULL; + g_string_append (str, "\n"); + + cr_utils_dump_n_chars2 (' ', str, indent); + tmp_str = (gchar *) cr_font_style_to_string (a_this->font_style); + if (tmp_str) { + g_string_append_printf (str, "font-style: %s", tmp_str); + } else { + g_string_append (str, "font-style: NULL"); + } + tmp_str = NULL; + g_string_append (str, "\n"); + + cr_utils_dump_n_chars2 (' ', str, indent); + tmp_str = (gchar *) cr_font_variant_to_string (a_this->font_variant); + if (tmp_str) { + g_string_append_printf (str, "font-variant: %s", tmp_str); + } else { + g_string_append (str, "font-variant: NULL"); + } + tmp_str = NULL; + g_string_append (str, "\n"); + + cr_utils_dump_n_chars2 (' ', str, indent); + tmp_str = (gchar *) cr_font_weight_to_string (a_this->font_weight); + if (tmp_str) { + g_string_append_printf (str, "font-weight: %s", tmp_str); + } else { + g_string_append (str, "font-weight: NULL"); + } + tmp_str = NULL; + g_string_append (str, "\n"); + + cr_utils_dump_n_chars2 (' ', str, indent); + tmp_str = (gchar *) cr_font_stretch_to_string (a_this->font_stretch); + if (tmp_str) { + g_string_append_printf (str, "font-stretch: %s", tmp_str); + } else { + g_string_append (str, "font-stretch: NULL"); + } + tmp_str = NULL; + g_string_append (str, "\n"); + + + cr_utils_dump_n_chars2 (' ', str, a_nb_indent); + g_string_append (str, "}"); + + return CR_OK; +} + +/** + *Destructor of the #CRStyle class. + *@param a_this the instance to destroy. + */ +void +cr_style_destroy (CRStyle * a_this) +{ + g_return_if_fail (a_this); + + g_free (a_this); +} + diff --git a/src/libcroco/cr-style.h b/src/libcroco/cr-style.h new file mode 100644 index 000000000..9abdef6b2 --- /dev/null +++ b/src/libcroco/cr-style.h @@ -0,0 +1,339 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli. + * See COPYRIGHTS file for copyright information. + */ + +#ifndef __CR_STYLE_H__ +#define __CR_STYLE_H__ + +#include "cr-utils.h" +#include "cr-statement.h" +#include "cr-fonts.h" + +/** + *@file + *The declaration of the #CRStyle class. + */ +G_BEGIN_DECLS + +typedef struct _CRStyle CRStyle ; + +enum CRBorderStyle +{ + BORDER_STYLE_NONE = 0, + BORDER_STYLE_HIDDEN, + BORDER_STYLE_DOTTED, + BORDER_STYLE_DASHED, + BORDER_STYLE_SOLID, + BORDER_STYLE_DOUBLE, + BORDER_STYLE_GROOVE, + BORDER_STYLE_RIDGE, + BORDER_STYLE_INSET, + BORDER_STYLE_OUTSET, + BORDER_STYLE_INHERIT +} ; + +enum CRDisplayType +{ + DISPLAY_NONE, + DISPLAY_INLINE, + DISPLAY_BLOCK, + DISPLAY_LIST_ITEM, + DISPLAY_RUN_IN, + DISPLAY_COMPACT, + DISPLAY_MARKER, + DISPLAY_TABLE, + DISPLAY_INLINE_TABLE, + DISPLAY_TABLE_ROW_GROUP, + DISPLAY_TABLE_HEADER_GROUP, + DISPLAY_TABLE_FOOTER_GROUP, + DISPLAY_TABLE_ROW, + DISPLAY_TABLE_COLUMN_GROUP, + DISPLAY_TABLE_COLUMN, + DISPLAY_TABLE_CELL, + DISPLAY_TABLE_CAPTION, + DISPLAY_INHERIT +} ; + +enum CRPositionType +{ + POSITION_STATIC, + POSITION_RELATIVE, + POSITION_ABSOLUTE, + POSITION_FIXED, + POSITION_INHERIT, +} ; + +enum CRFloatType +{ + FLOAT_NONE, + FLOAT_LEFT, + FLOAT_RIGHT, + FLOAT_INHERIT +} ; + +enum CRWhiteSpaceType +{ + WHITE_SPACE_NORMAL, + WHITE_SPACE_PRE, + WHITE_SPACE_NOWRAP, + WHITE_SPACE_INHERIT +} ; + + +#define BORDER_THIN 2 +#define BORDER_MEDIUM 4 +#define BORDER_THICK 6 + + +/** + *A numerical css property value. + *This data type is actually split in 3 parts: + *1/the specified value + *2/the computed value + *3/the actual value. + *To understand the semantic of these three parts, + *see css2 spec chap 6.1 ("Specified, computed and actual values."). + */ +typedef struct _CRNumPropVal CRNumPropVal ; +struct _CRNumPropVal +{ + /**specified value*/ + CRNum sv ; + /**computed value*/ + CRNum cv ; + /**actual value*/ + CRNum av ; +} ; + +/** + *An rgb css property value. + *This data type is actually split in 3 parts: + *1/the specified value + *2/the computed value + *3/the actual value. + *To understand the semantic of these three parts, + *see css2 spec chap 6.1 ("Specified, computed and actual values."). + */ +typedef struct _CRRgbPropVal CRRgbPropVal ; +struct _CRRgbPropVal +{ + /**specified value*/ + CRRgb sv ; + /**computed value*/ + CRRgb cv ; + /**actual value*/ + CRRgb av ; +} ; + + +enum CRNumProp +{ + NUM_PROP_TOP=0, + NUM_PROP_RIGHT, + NUM_PROP_BOTTOM, + NUM_PROP_LEFT,/*3*/ + + NUM_PROP_PADDING_TOP, + NUM_PROP_PADDING_RIGHT, + NUM_PROP_PADDING_BOTTOM, + NUM_PROP_PADDING_LEFT,/*7*/ + + NUM_PROP_BORDER_TOP, + NUM_PROP_BORDER_RIGHT, + NUM_PROP_BORDER_BOTTOM, + NUM_PROP_BORDER_LEFT,/*11*/ + + NUM_PROP_MARGIN_TOP, + NUM_PROP_MARGIN_RIGHT, + NUM_PROP_MARGIN_BOTTOM, + NUM_PROP_MARGIN_LEFT,/*15*/ + + NUM_PROP_WIDTH, + + /*must be last*/ + NB_NUM_PROPS +} ; + +enum CRRgbProp +{ + RGB_PROP_BORDER_TOP_COLOR = 0, + RGB_PROP_BORDER_RIGHT_COLOR, + RGB_PROP_BORDER_BOTTOM_COLOR, + RGB_PROP_BORDER_LEFT_COLOR, + RGB_PROP_COLOR, + RGB_PROP_BACKGROUND_COLOR, + + /*must be last*/ + NB_RGB_PROPS +} ; + + +enum CRBorderStyleProp +{ + BORDER_STYLE_PROP_TOP = 0, + BORDER_STYLE_PROP_RIGHT, + BORDER_STYLE_PROP_BOTTOM, + BORDER_STYLE_PROP_LEFT, + + /*must be last*/ + NB_BORDER_STYLE_PROPS +} ; + +enum CRBoxOffsetProp +{ + BOX_OFFSET_PROP_TOP = 0, + BOX_OFFSET_PROP_RIGHT, + BOX_OFFSET_PROP_BOTTOM, + BOX_OFFSET_PROP_LEFT, + + /*must be last*/ + NB_BOX_OFFSET_PROPS +} ; + +typedef struct _CRFontSizeVal CRFontSizeVal ; +struct _CRFontSizeVal { + /*specified value*/ + CRFontSize sv ; + /*computed value*/ + CRFontSize cv ; + /*actual value*/ + CRFontSize av ; +} ; + +/** + *The css2 style class. + *Contains computed and actual values + *inferred from the declarations found + *in the stylesheets. + *See css2 spec chapter 6. + */ +struct _CRStyle +{ + /** + *numerical properties. + *the properties are indexed by + *enum #CRNumProp. + */ + CRNumPropVal num_props[NB_NUM_PROPS] ; + + /** + *color properties. + *They are indexed by enum #CRRgbProp . + */ + CRRgbPropVal rgb_props[NB_RGB_PROPS] ; + + /** + *border style properties. + *They are indexed by enum #CRBorderStyleProp . + */ + enum CRBorderStyle border_style_props[NB_BORDER_STYLE_PROPS] ; + + /**box display type*/ + enum CRDisplayType display ; + + /**the positioning scheme*/ + enum CRPositionType position ; + + /**the float property*/ + enum CRFloatType float_type ; + + /* + *the 'font-family' property. + */ + CRFontFamily *font_family ; + + /** + *the 'font-size' property. + */ + CRFontSizeVal font_size ; + CRFontSizeAdjust *font_size_adjust ; + enum CRFontStyle font_style ; + enum CRFontVariant font_variant ; + enum CRFontWeight font_weight ; + enum CRFontStretch font_stretch ; + + /** + * the 'tex' properties + */ + enum CRWhiteSpaceType white_space; + + gboolean inherited_props_resolved ; + CRStyle *parent_style ; + gulong ref_count ; +} ; + +enum CRStatus cr_style_white_space_type_to_string (enum CRWhiteSpaceType a_code, + GString * a_str, guint a_nb_indent) ; + +enum CRStatus cr_style_num_prop_val_to_string (CRNumPropVal *a_prop_val, + GString *a_str, + guint a_nb_indent) ; + +enum CRStatus cr_style_rgb_prop_val_to_string (CRRgbPropVal *a_prop_val, + GString *a_str, + guint a_nb_indent) ; + +enum CRStatus cr_style_border_style_to_string (enum CRBorderStyle a_prop, + GString *a_str, + guint a_nb_indent) ; + +enum CRStatus cr_style_display_type_to_string (enum CRDisplayType a_code, + GString *a_str, + guint a_nb_indent) ; + +enum CRStatus cr_style_position_type_to_string (enum CRPositionType a_code, + GString *a_str, + guint a_nb_indent) ; + +enum CRStatus cr_style_float_type_to_string (enum CRFloatType a_code, + GString *a_str, + guint a_nb_indent) ; + +CRStyle * cr_style_new (gboolean a_set_props_to_initial_values) ; + +enum CRStatus cr_style_set_props_to_default_values (CRStyle *a_this) ; +enum CRStatus cr_style_set_props_to_initial_values (CRStyle *a_this) ; +enum CRStatus cr_style_resolve_inherited_properties (CRStyle *a_this) ; +enum CRStatus cr_style_propagate_from_parent (CRStyle *a_this); + +enum CRStatus cr_style_set_style_from_decl (CRStyle *a_this, + CRDeclaration *a_decl) ; + + +enum CRStatus cr_style_copy (CRStyle *a_dest, CRStyle *a_src) ; + +enum CRStatus cr_style_ref (CRStyle *a_this) ; + +gboolean cr_style_unref (CRStyle *a_this) ; + +void cr_style_destroy (CRStyle *a_this) ; + +CRStyle * cr_style_dup (CRStyle *a_this) ; + +enum CRStatus cr_style_to_string (CRStyle *a_this, + GString **a_str, + guint a_nb_indent) ; + +G_END_DECLS + +#endif /*__CR_STYLE_H__*/ diff --git a/src/libcroco/cr-stylesheet.c b/src/libcroco/cr-stylesheet.c new file mode 100644 index 000000000..9aeb551e4 --- /dev/null +++ b/src/libcroco/cr-stylesheet.c @@ -0,0 +1,178 @@ +/* -*- Mode: C; indent-tabs-mode: ni; c-basic-offset: 8 -*- */ + +/* + * This file is part of The Croco Library + * + * Copyright (C) 2002-2004 Dodji Seketeli + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include "string.h" +#include "cr-stylesheet.h" + +/** + *@file + *The definition of the #CRStyleSheet class + */ + +/** + *Constructor of the #CRStyleSheet class. + *@param the initial list of css statements. + *@return the newly built css2 stylesheet, or NULL in case of error. + */ +CRStyleSheet * +cr_stylesheet_new (CRStatement * a_stmts) +{ + CRStyleSheet *result; + + result = g_try_malloc (sizeof (CRStyleSheet)); + if (!result) { + cr_utils_trace_info ("Out of memory"); + return NULL; + } + + memset (result, 0, sizeof (CRStyleSheet)); + + if (a_stmts) + result->statements = a_stmts; + + return result; +} + +/** + *@param a_this the current instance of #CRStyleSheet + *@return the serialized stylesheet. + */ +gchar * +cr_stylesheet_to_string (CRStyleSheet *a_this) +{ + gchar *str = NULL; + GString *stringue = NULL; + CRStatement *cur_stmt = NULL; + + g_return_val_if_fail (a_this, NULL); + + if (a_this->statements) { + stringue = g_string_new (NULL) ; + g_return_val_if_fail (stringue, NULL) ; + } + for (cur_stmt = a_this->statements; + cur_stmt; cur_stmt = cur_stmt->next) { + if (cur_stmt->prev) { + g_string_append (stringue, "\n\n") ; + } + str = cr_statement_to_string (cur_stmt, 0) ; + if (str) { + g_string_append (stringue, str) ; + g_free (str) ; + str = NULL ; + } + } + if (stringue) { + str = stringue->str ; + g_string_free (stringue, FALSE) ; + stringue = NULL ; + } + return str ; +} + +/** + *Dumps the current css2 stylesheet to a file. + *@param a_this the current instance of #CRStyleSheet. + *@param a_fp the destination file + */ +void +cr_stylesheet_dump (CRStyleSheet * a_this, FILE * a_fp) +{ + gchar *str = NULL ; + + g_return_if_fail (a_this); + + str = cr_stylesheet_to_string (a_this) ; + if (str) { + fprintf (a_fp, "%s", str) ; + g_free (str) ; + str = NULL ; + } +} + +/** + *Return the number of rules in the stylesheet. + *@param a_this the current instance of #CRStyleSheet. + *@return number of rules in the stylesheet. + */ +gint +cr_stylesheet_nr_rules (CRStyleSheet * a_this) +{ + g_return_val_if_fail (a_this, -1); + + return cr_statement_nr_rules (a_this->statements); +} + +/** + *Use an index to get a CRStatement from the rules in a given stylesheet. + *@param a_this the current instance of #CRStatement. + *@param itemnr the index into the rules. + *@return CRStatement at position itemnr, if itemnr > number of rules - 1, + *it will return NULL. + */ +CRStatement * +cr_stylesheet_statement_get_from_list (CRStyleSheet * a_this, int itemnr) +{ + g_return_val_if_fail (a_this, NULL); + + return cr_statement_get_from_list (a_this->statements, itemnr); +} + +void +cr_stylesheet_ref (CRStyleSheet * a_this) +{ + g_return_if_fail (a_this); + + a_this->ref_count++; +} + +gboolean +cr_stylesheet_unref (CRStyleSheet * a_this) +{ + g_return_val_if_fail (a_this, FALSE); + + if (a_this->ref_count) + a_this->ref_count--; + + if (!a_this->ref_count) { + cr_stylesheet_destroy (a_this); + return TRUE; + } + + return FALSE; +} + +/** + *Destructor of the #CRStyleSheet class. + *@param a_this the current instance of the #CRStyleSheet class. + */ +void +cr_stylesheet_destroy (CRStyleSheet * a_this) +{ + g_return_if_fail (a_this); + + if (a_this->statements) { + cr_statement_destroy (a_this->statements); + a_this->statements = NULL; + } + g_free (a_this); +} diff --git a/src/libcroco/cr-stylesheet.h b/src/libcroco/cr-stylesheet.h new file mode 100644 index 000000000..3766a284a --- /dev/null +++ b/src/libcroco/cr-stylesheet.h @@ -0,0 +1,102 @@ +/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * see COPYRIGHTS file for copyright information. + */ + + +#ifndef __CR_STYLESHEET_H__ +#define __CR_STYLESHEET_H__ + +#include "cr-utils.h" +#include "cr-statement.h" + +G_BEGIN_DECLS + +/** + *@file + *The declaration of the #CRStyleSheet class. + */ + + +enum CRStyleOrigin +{ + /*Please don't change the order of + *the values enumerated here ... + *New values should be added at the end, + *just before ORIGIN_END. + */ + ORIGIN_UA = 0, + ORIGIN_USER, + ORIGIN_AUTHOR, + + /*must always be the last one*/ + NB_ORIGINS +} ; + +/** + *An abstraction of a css stylesheet as defined + *by the css2 spec in chapter 4. + */ +struct _CRStyleSheet +{ + /**The css statements list*/ + CRStatement *statements ; + + enum CRStyleOrigin origin ; + + /*the parent import rule, if any.*/ + CRStatement *parent_import_rule ; + + /**custom data used by libcroco*/ + gpointer croco_data ; + + /** + *custom application data pointer + *Can be used by applications. + */ + gpointer app_data ; + + /** + *the reference count of this insance + *Please, don't never ever modify it + *directly. Use cr_stylesheet_ref() + *and cr_stylesheet_unref() instead. + */ + gulong ref_count ; +} ; + +CRStyleSheet * cr_stylesheet_new (CRStatement *a_stmts) ; + +gchar * cr_stylesheet_to_string (CRStyleSheet *a_this) ; +void cr_stylesheet_dump (CRStyleSheet *a_this, FILE *a_fp) ; + +gint cr_stylesheet_nr_rules (CRStyleSheet *a_this) ; + +CRStatement * cr_stylesheet_statement_get_from_list (CRStyleSheet *a_this, int itemnr) ; + +void cr_stylesheet_ref (CRStyleSheet *a_this) ; + +gboolean cr_stylesheet_unref (CRStyleSheet *a_this) ; + +void cr_stylesheet_destroy (CRStyleSheet *a_this) ; + +G_END_DECLS + +#endif /*__CR_STYLESHEET_H__*/ diff --git a/src/libcroco/cr-term.c b/src/libcroco/cr-term.c new file mode 100644 index 000000000..33eed40f9 --- /dev/null +++ b/src/libcroco/cr-term.c @@ -0,0 +1,791 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli + * See COPYRIGHTS file for copyright information. + */ + +#include +#include +#include "cr-term.h" +#include "cr-num.h" +#include "cr-parser.h" + +/** + *@file + *Definition of the #CRTem class. + */ + +static void +cr_term_clear (CRTerm * a_this) +{ + g_return_if_fail (a_this); + + switch (a_this->type) { + case TERM_NUMBER: + if (a_this->content.num) { + cr_num_destroy (a_this->content.num); + a_this->content.num = NULL; + } + break; + + case TERM_FUNCTION: + if (a_this->ext_content.func_param) { + cr_term_destroy (a_this->ext_content.func_param); + a_this->ext_content.func_param = NULL; + } + case TERM_STRING: + case TERM_IDENT: + case TERM_URI: + case TERM_HASH: + if (a_this->content.str) { + cr_string_destroy (a_this->content.str); + a_this->content.str = NULL; + } + break; + + case TERM_RGB: + if (a_this->content.rgb) { + cr_rgb_destroy (a_this->content.rgb); + a_this->content.rgb = NULL; + } + break; + + case TERM_UNICODERANGE: + case TERM_NO_TYPE: + default: + break; + } + + a_this->type = TERM_NO_TYPE; +} + +/** + *Instanciate a #CRTerm. + *@return the newly build instance + *of #CRTerm. + */ +CRTerm * +cr_term_new (void) +{ + CRTerm *result = NULL; + + result = g_try_malloc (sizeof (CRTerm)); + if (!result) { + cr_utils_trace_info ("Out of memory"); + return NULL; + } + memset (result, 0, sizeof (CRTerm)); + return result; +} + +/** + *Parses an expresion as defined by the css2 spec + *and builds the expression as a list of terms. + *@param a_buf the buffer to parse. + *@return a pointer to the first term of the expression or + *NULL if parsing failed. + */ +CRTerm * +cr_term_parse_expression_from_buf (const guchar * a_buf, + enum CREncoding a_encoding) +{ + CRParser *parser = NULL; + CRTerm *result = NULL; + enum CRStatus status = CR_OK; + + g_return_val_if_fail (a_buf, NULL); + + parser = cr_parser_new_from_buf ((guchar*)a_buf, strlen (a_buf), + a_encoding, FALSE); + g_return_val_if_fail (parser, NULL); + + status = cr_parser_try_to_skip_spaces_and_comments (parser); + if (status != CR_OK) { + goto cleanup; + } + status = cr_parser_parse_expr (parser, &result); + if (status != CR_OK) { + if (result) { + cr_term_destroy (result); + result = NULL; + } + } + + cleanup: + if (parser) { + cr_parser_destroy (parser); + parser = NULL; + } + + return result; +} + +enum CRStatus +cr_term_set_number (CRTerm * a_this, CRNum * a_num) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_term_clear (a_this); + + a_this->type = TERM_NUMBER; + a_this->content.num = a_num; + return CR_OK; +} + +enum CRStatus +cr_term_set_function (CRTerm * a_this, CRString * a_func_name, + CRTerm * a_func_param) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_term_clear (a_this); + + a_this->type = TERM_FUNCTION; + a_this->content.str = a_func_name; + a_this->ext_content.func_param = a_func_param; + return CR_OK; +} + +enum CRStatus +cr_term_set_string (CRTerm * a_this, CRString * a_str) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_term_clear (a_this); + + a_this->type = TERM_STRING; + a_this->content.str = a_str; + return CR_OK; +} + +enum CRStatus +cr_term_set_ident (CRTerm * a_this, CRString * a_str) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_term_clear (a_this); + + a_this->type = TERM_IDENT; + a_this->content.str = a_str; + return CR_OK; +} + +enum CRStatus +cr_term_set_uri (CRTerm * a_this, CRString * a_str) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_term_clear (a_this); + + a_this->type = TERM_URI; + a_this->content.str = a_str; + return CR_OK; +} + +enum CRStatus +cr_term_set_rgb (CRTerm * a_this, CRRgb * a_rgb) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_term_clear (a_this); + + a_this->type = TERM_RGB; + a_this->content.rgb = a_rgb; + return CR_OK; +} + +enum CRStatus +cr_term_set_hash (CRTerm * a_this, CRString * a_str) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_term_clear (a_this); + + a_this->type = TERM_HASH; + a_this->content.str = a_str; + return CR_OK; +} + +/** + *Appends a new term to the current list of #CRTerm. + * + *@param a_this the "this pointer" of the current instance + *of #CRTerm . + *@param a_new_term the term to append. + *@return the list of terms with the a_new_term appended to it. + */ +CRTerm * +cr_term_append_term (CRTerm * a_this, CRTerm * a_new_term) +{ + CRTerm *cur = NULL; + + g_return_val_if_fail (a_new_term, NULL); + + if (a_this == NULL) + return a_new_term; + + for (cur = a_this; cur->next; cur = cur->next) ; + + cur->next = a_new_term; + a_new_term->prev = cur; + + return a_this; +} + +/** + *Prepends a term to the list of terms represented by a_this. + * + *@param a_this the "this pointer" of the current instance of + *#CRTerm . + *@param a_new_term the term to prepend. + *@return the head of the new list. + */ +CRTerm * +cr_term_prepend_term (CRTerm * a_this, CRTerm * a_new_term) +{ + g_return_val_if_fail (a_this && a_new_term, NULL); + + a_new_term->next = a_this; + a_this->prev = a_new_term; + + return a_new_term; +} + +/** + *Serializes the expression represented by + *the chained instances of #CRterm. + *@param a_this the current instance of #CRTerm + *@return the zero terminated string containing the serialized + *form of #CRTerm. MUST BE FREED BY THE CALLER using g_free(). + */ +guchar * +cr_term_to_string (CRTerm * a_this) +{ + GString *str_buf = NULL; + CRTerm *cur = NULL; + guchar *result = NULL, + *content = NULL; + + g_return_val_if_fail (a_this, NULL); + + str_buf = g_string_new (NULL); + g_return_val_if_fail (str_buf, NULL); + + for (cur = a_this; cur; cur = cur->next) { + if ((cur->content.str == NULL) + && (cur->content.num == NULL) + && (cur->content.str == NULL) + && (cur->content.rgb == NULL)) + continue; + + switch (cur->the_operator) { + case DIVIDE: + g_string_append (str_buf, " / "); + break; + + case COMMA: + g_string_append (str_buf, ", "); + break; + + case NO_OP: + if (cur->prev) { + g_string_append (str_buf, " "); + } + break; + default: + + break; + } + + switch (cur->unary_op) { + case PLUS_UOP: + g_string_append (str_buf, "+"); + break; + + case MINUS_UOP: + g_string_append (str_buf, "-"); + break; + + default: + break; + } + + switch (cur->type) { + case TERM_NUMBER: + if (cur->content.num) { + content = cr_num_to_string (cur->content.num); + } + + if (content) { + g_string_append (str_buf, content); + g_free (content); + content = NULL; + } + + break; + + case TERM_FUNCTION: + if (cur->content.str) { + content = g_strndup + (cur->content.str->stryng->str, + cur->content.str->stryng->len); + } + + if (content) { + g_string_append_printf (str_buf, "%s(", + content); + + if (cur->ext_content.func_param) { + guchar *tmp_str = NULL; + + tmp_str = cr_term_to_string + (cur-> + ext_content.func_param); + + if (tmp_str) { + g_string_append (str_buf, + tmp_str); + g_free (tmp_str); + tmp_str = NULL; + } + + g_free (content); + content = NULL; + } + g_string_append (str_buf, ")"); + } + + break; + + case TERM_STRING: + if (cur->content.str) { + content = g_strndup + (cur->content.str->stryng->str, + cur->content.str->stryng->len); + } + + if (content) { + g_string_append_printf (str_buf, + "\"%s\"", content); + g_free (content); + content = NULL; + } + break; + + case TERM_IDENT: + if (cur->content.str) { + content = g_strndup + (cur->content.str->stryng->str, + cur->content.str->stryng->len); + } + + if (content) { + g_string_append (str_buf, content); + g_free (content); + content = NULL; + } + break; + + case TERM_URI: + if (cur->content.str) { + content = g_strndup + (cur->content.str->stryng->str, + cur->content.str->stryng->len); + } + + if (content) { + g_string_append_printf + (str_buf, "url(%s)", content); + g_free (content); + content = NULL; + } + break; + + case TERM_RGB: + if (cur->content.rgb) { + guchar *tmp_str = NULL; + + g_string_append (str_buf, "rgb("); + tmp_str = cr_rgb_to_string (cur->content.rgb); + + if (tmp_str) { + g_string_append (str_buf, tmp_str); + g_free (tmp_str); + tmp_str = NULL; + } + g_string_append (str_buf, ")"); + } + + break; + + case TERM_UNICODERANGE: + g_string_append + (str_buf, + "?found unicoderange: dump not supported yet?"); + break; + + case TERM_HASH: + if (cur->content.str) { + content = g_strndup + (cur->content.str->stryng->str, + cur->content.str->stryng->len); + } + + if (content) { + g_string_append_printf (str_buf, + "#%s", content); + g_free (content); + content = NULL; + } + break; + + default: + g_string_append (str_buf, + "Unrecognized Term type"); + break; + } + } + + if (str_buf) { + result = str_buf->str; + g_string_free (str_buf, FALSE); + str_buf = NULL; + } + + return result; +} + +guchar * +cr_term_one_to_string (CRTerm * a_this) +{ + GString *str_buf = NULL; + guchar *result = NULL, + *content = NULL; + + g_return_val_if_fail (a_this, NULL); + + str_buf = g_string_new (NULL); + g_return_val_if_fail (str_buf, NULL); + + if ((a_this->content.str == NULL) + && (a_this->content.num == NULL) + && (a_this->content.str == NULL) + && (a_this->content.rgb == NULL)) + return NULL ; + + switch (a_this->the_operator) { + case DIVIDE: + g_string_append_printf (str_buf, " / "); + break; + + case COMMA: + g_string_append_printf (str_buf, ", "); + break; + + case NO_OP: + if (a_this->prev) { + g_string_append_printf (str_buf, " "); + } + break; + default: + + break; + } + + switch (a_this->unary_op) { + case PLUS_UOP: + g_string_append_printf (str_buf, "+"); + break; + + case MINUS_UOP: + g_string_append_printf (str_buf, "-"); + break; + + default: + break; + } + + switch (a_this->type) { + case TERM_NUMBER: + if (a_this->content.num) { + content = cr_num_to_string (a_this->content.num); + } + + if (content) { + g_string_append (str_buf, content); + g_free (content); + content = NULL; + } + + break; + + case TERM_FUNCTION: + if (a_this->content.str) { + content = g_strndup + (a_this->content.str->stryng->str, + a_this->content.str->stryng->len); + } + + if (content) { + g_string_append_printf (str_buf, "%s(", + content); + + if (a_this->ext_content.func_param) { + guchar *tmp_str = NULL; + + tmp_str = cr_term_to_string + (a_this-> + ext_content.func_param); + + if (tmp_str) { + g_string_append_printf + (str_buf, + "%s", tmp_str); + g_free (tmp_str); + tmp_str = NULL; + } + + g_string_append_printf (str_buf, ")"); + g_free (content); + content = NULL; + } + } + + break; + + case TERM_STRING: + if (a_this->content.str) { + content = g_strndup + (a_this->content.str->stryng->str, + a_this->content.str->stryng->len); + } + + if (content) { + g_string_append_printf (str_buf, + "\"%s\"", content); + g_free (content); + content = NULL; + } + break; + + case TERM_IDENT: + if (a_this->content.str) { + content = g_strndup + (a_this->content.str->stryng->str, + a_this->content.str->stryng->len); + } + + if (content) { + g_string_append (str_buf, content); + g_free (content); + content = NULL; + } + break; + + case TERM_URI: + if (a_this->content.str) { + content = g_strndup + (a_this->content.str->stryng->str, + a_this->content.str->stryng->len); + } + + if (content) { + g_string_append_printf + (str_buf, "url(%s)", content); + g_free (content); + content = NULL; + } + break; + + case TERM_RGB: + if (a_this->content.rgb) { + guchar *tmp_str = NULL; + + g_string_append_printf (str_buf, "rgb("); + tmp_str = cr_rgb_to_string (a_this->content.rgb); + + if (tmp_str) { + g_string_append (str_buf, tmp_str); + g_free (tmp_str); + tmp_str = NULL; + } + g_string_append_printf (str_buf, ")"); + } + + break; + + case TERM_UNICODERANGE: + g_string_append_printf + (str_buf, + "?found unicoderange: dump not supported yet?"); + break; + + case TERM_HASH: + if (a_this->content.str) { + content = g_strndup + (a_this->content.str->stryng->str, + a_this->content.str->stryng->len); + } + + if (content) { + g_string_append_printf (str_buf, + "#%s", content); + g_free (content); + content = NULL; + } + break; + + default: + g_string_append_printf (str_buf, + "%s", + "Unrecognized Term type"); + break; + } + + if (str_buf) { + result = str_buf->str; + g_string_free (str_buf, FALSE); + str_buf = NULL; + } + + return result; +} + +/** + *Dumps the expression (a list of terms connected by operators) + *to a file. + *TODO: finish the dump. The dump of some type of terms have not yet been + *implemented. + *@param a_this the current instance of #CRTerm. + *@param a_fp the destination file pointer. + */ +void +cr_term_dump (CRTerm * a_this, FILE * a_fp) +{ + guchar *content = NULL; + + g_return_if_fail (a_this); + + content = cr_term_to_string (a_this); + + if (content) { + fprintf (a_fp, "%s", content); + g_free (content); + } +} + +/** + *Return the number of terms in the expression. + *@param a_this the current instance of #CRTerm. + *@return number of terms in the expression. + */ +int +cr_term_nr_values (CRTerm *a_this) +{ + CRTerm *cur = NULL ; + int nr = 0; + + g_return_val_if_fail (a_this, -1) ; + + for (cur = a_this ; cur ; cur = cur->next) + nr ++; + return nr; +} + +/** + *Use an index to get a CRTerm from the expression. + *@param a_this the current instance of #CRTerm. + *@param itemnr the index into the expression. + *@return CRTerm at position itemnr, if itemnr > number of terms - 1, + *it will return NULL. + */ +CRTerm * +cr_term_get_from_list (CRTerm *a_this, int itemnr) +{ + CRTerm *cur = NULL ; + int nr = 0; + + g_return_val_if_fail (a_this, NULL) ; + + for (cur = a_this ; cur ; cur = cur->next) + if (nr++ == itemnr) + return cur; + return NULL; +} + +/** + *Increments the reference counter of the current instance + *of #CRTerm.* + *@param a_this the current instance of #CRTerm. + */ +void +cr_term_ref (CRTerm * a_this) +{ + g_return_if_fail (a_this); + + a_this->ref_count++; +} + +/** + *Decrements the ref count of the current instance of + *#CRTerm. If the ref count reaches zero, the instance is + *destroyed. + *@param a_this the current instance of #CRTerm. + *@return TRUE if the current instance has been destroyed, FALSE otherwise. + */ +gboolean +cr_term_unref (CRTerm * a_this) +{ + g_return_val_if_fail (a_this, FALSE); + + if (a_this->ref_count) { + a_this->ref_count--; + } + + if (a_this->ref_count == 0) { + cr_term_destroy (a_this); + return TRUE; + } + + return FALSE; +} + +/** + *The destructor of the the #CRTerm class. + *@param a_this the "this pointer" of the current instance + *of #CRTerm. + */ +void +cr_term_destroy (CRTerm * a_this) +{ + g_return_if_fail (a_this); + + cr_term_clear (a_this); + + if (a_this->next) { + cr_term_destroy (a_this->next); + a_this->next = NULL; + } + + if (a_this) { + g_free (a_this); + } + +} diff --git a/src/libcroco/cr-term.h b/src/libcroco/cr-term.h new file mode 100644 index 000000000..ae8b234c5 --- /dev/null +++ b/src/libcroco/cr-term.h @@ -0,0 +1,190 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli + * See COPYRIGHTS file for copyright information. + */ + +#include +#include +#include "cr-utils.h" +#include "cr-rgb.h" +#include "cr-num.h" +#include "cr-string.h" + +#ifndef __CR_TERM_H__ +#define __CR_TERM_H__ + +G_BEGIN_DECLS + +/** + *@file + *Declaration of the #CRTerm class. + */ + +enum CRTermType +{ + TERM_NO_TYPE = 0, + TERM_NUMBER, + TERM_FUNCTION, + TERM_STRING, + TERM_IDENT, + TERM_URI, + TERM_RGB, + TERM_UNICODERANGE, + TERM_HASH +} ; + + +enum UnaryOperator +{ + NO_UNARY_UOP = 0, + PLUS_UOP, + MINUS_UOP, + EMPTY_UNARY_UOP +} ; + +enum Operator +{ + NO_OP = 0, + DIVIDE, + COMMA +} ; + +struct _CRTerm ; +typedef struct _CRTerm CRTerm ; + +/** + *An abstraction of a css2 term as + *defined in the CSS2 spec in appendix D.1: + *term ::= + *[ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* + *| ANGLE S* | TIME S* | FREQ S* | function ] + * | STRING S* | IDENT S* | URI S* | RGB S* + *| UNICODERANGE S* | hexcolor + */ +struct _CRTerm +{ + /** + *The type of the term. + */ + enum CRTermType type ; + + /** + *The unary operator associated to + *the current term. + */ + enum UnaryOperator unary_op ; + + /** + *The operator associated to the current term. + */ + enum Operator the_operator ; + + + /** + *The content of the term. + *Depending of the type of the term, + *this holds either a number, a percentage ... + */ + union + { + CRNum *num ; + CRString * str ; + CRRgb * rgb ; + } content ; + + /** + *If the term is of type UNICODERANGE, + *this field holds the upper bound of the range. + *if the term is of type FUNCTION, this holds + *an instance of CRTerm that represents + * the expression which is the argument of the function. + */ + union + { + CRTerm *func_param ; + } ext_content ; + + /** + *A spare pointer, just in case. + *Can be used by the application. + */ + gpointer app_data ; + + glong ref_count ; + + /** + *A pointer to the next term, + *just in case this term is part of + *an expression. + */ + CRTerm *next ; + + /** + *A pointer to the previous + *term. + */ + CRTerm *prev ; + CRParsingLocation location ; +} ; + +CRTerm * cr_term_parse_expression_from_buf (const guchar *a_buf, + enum CREncoding a_encoding) ; +CRTerm * cr_term_new (void) ; + +enum CRStatus cr_term_set_number (CRTerm *a_this, CRNum *a_num) ; + +enum CRStatus cr_term_set_function (CRTerm *a_this, + CRString *a_func_name, + CRTerm *a_func_param) ; + +enum CRStatus cr_term_set_string (CRTerm *a_this, CRString *a_str) ; + +enum CRStatus cr_term_set_ident (CRTerm *a_this, CRString *a_str) ; + +enum CRStatus cr_term_set_uri (CRTerm *a_this, CRString *a_str) ; + +enum CRStatus cr_term_set_rgb (CRTerm *a_this, CRRgb *a_rgb) ; + +enum CRStatus cr_term_set_hash (CRTerm *a_this, CRString *a_str) ; + +CRTerm * cr_term_append_term (CRTerm *a_this, CRTerm *a_new_term) ; + +CRTerm * cr_term_prepend_term (CRTerm *a_this, CRTerm *a_new_term) ; + +guchar * cr_term_to_string (CRTerm *a_this) ; + +guchar * cr_term_one_to_string (CRTerm * a_this) ; + +void cr_term_dump (CRTerm *a_this, FILE *a_fp) ; + +int cr_term_nr_values (CRTerm *a_this) ; + +CRTerm * cr_term_get_from_list (CRTerm *a_this, int itemnr) ; + +void cr_term_ref (CRTerm *a_this) ; + +gboolean cr_term_unref (CRTerm *a_this) ; + +void cr_term_destroy (CRTerm * a_term) ; + +G_END_DECLS + +#endif /*__CR_TERM_H__*/ diff --git a/src/libcroco/cr-tknzr.c b/src/libcroco/cr-tknzr.c new file mode 100644 index 000000000..b591770f8 --- /dev/null +++ b/src/libcroco/cr-tknzr.c @@ -0,0 +1,2760 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli + * See the COPYRIGHTS file for copyrights information. + */ + +/** + *@file + *The definition of the #CRTknzr (tokenizer) + *class. + */ + +#include "string.h" +#include "cr-tknzr.h" +#include "cr-doc-handler.h" + +struct _CRTknzrPriv { + /**The parser input stream of bytes*/ + CRInput *input; + + /** + *A cache where tknzr_unget_token() + *puts back the token. tknzr_get_next_token() + *first look in this cache, and if and + *only if it's empty, fetches the next token + *from the input stream. + */ + CRToken *token_cache; + + /** + *The position of the end of the previous token + *or char fetched. + */ + CRInputPos prev_pos; + + CRDocHandler *sac_handler; + + /** + *The reference count of the current instance + *of #CRTknzr. Is manipulated by cr_tknzr_ref() + *and cr_tknzr_unref(). + */ + glong ref_count; +}; + +#define PRIVATE(obj) ((obj)->priv) + +/** + *return TRUE if the character is a number ([0-9]), FALSE otherwise + *@param a_char the char to test. + */ +#define IS_NUM(a_char) (((a_char) >= '0' && (a_char) <= '9')?TRUE:FALSE) + +/** + *Checks if 'status' equals CR_OK. If not, goto the 'error' label. + * + *@param status the status (of type enum CRStatus) to test. + *@param is_exception if set to FALSE, the final status returned the + *current function will be CR_PARSING_ERROR. If set to TRUE, the + *current status will be the current value of the 'status' variable. + * + */ +#define CHECK_PARSING_STATUS(status, is_exception) \ +if ((status) != CR_OK) \ +{ \ + if (is_exception == FALSE) \ + { \ + status = CR_PARSING_ERROR ; \ + } \ + goto error ; \ +} + +/** + *Peeks the next char from the input stream of the current tokenizer. + *invokes CHECK_PARSING_STATUS on the status returned by + *cr_tknzr_input_peek_char(). + * + *@param the current instance of #CRTkzr. + *@param to_char a pointer to the char where to store the + *char peeked. + */ +#define PEEK_NEXT_CHAR(a_tknzr, a_to_char) \ +{\ +status = cr_tknzr_peek_char (a_tknzr, a_to_char) ; \ +CHECK_PARSING_STATUS (status, TRUE) \ +} + +/** + *Reads the next char from the input stream of the current parser. + *In case of error, jumps to the "error:" label located in the + *function where this macro is called. + *@param parser the curent instance of #CRTknzr + *@param to_char a pointer to the guint32 char where to store + *the character read. + */ +#define READ_NEXT_CHAR(a_tknzr, to_char) \ +status = cr_tknzr_read_char (a_tknzr, to_char) ;\ +CHECK_PARSING_STATUS (status, TRUE) + +/** + *Gets information about the current position in + *the input of the parser. + *In case of failure, this macro returns from the + *calling function and + *returns a status code of type enum #CRStatus. + *@param parser the current instance of #CRTknzr. + *@param pos out parameter. A pointer to the position + *inside the current parser input. Must + */ +#define RECORD_INITIAL_POS(a_tknzr, a_pos) \ +status = cr_input_get_cur_pos (PRIVATE \ +(a_tknzr)->input, a_pos) ; \ +g_return_val_if_fail (status == CR_OK, status) + +/** + *Gets the address of the current byte inside the + *parser input. + *@param parser the current instance of #CRTknzr. + *@param addr out parameter a pointer (guchar*) + *to where the address must be put. + */ +#define RECORD_CUR_BYTE_ADDR(a_tknzr, a_addr) \ +status = cr_input_get_cur_byte_addr \ + (PRIVATE (a_tknzr)->input, a_addr) ; \ +CHECK_PARSING_STATUS (status, TRUE) + +/** + *Peeks a byte from the topmost parser input at + *a given offset from the current position. + *If it fails, goto the "error:" label. + * + *@param a_parser the current instance of #CRTknzr. + *@param a_offset the offset of the byte to peek, the + *current byte having the offset '0'. + *@param a_byte_ptr out parameter a pointer (guchar*) to + *where the peeked char is to be stored. + */ +#define PEEK_BYTE(a_tknzr, a_offset, a_byte_ptr) \ +status = cr_tknzr_peek_byte (a_tknzr, \ + a_offset, \ + a_byte_ptr) ; \ +CHECK_PARSING_STATUS (status, TRUE) ; + +#define BYTE(a_input, a_n, a_eof) \ +cr_input_peek_byte2 (a_input, a_n, a_eof) + +/** + *Reads a byte from the topmost parser input + *steam. + *If it fails, goto the "error" label. + *@param a_parser the current instance of #CRTknzr. + *@param a_byte_ptr the guchar * where to put the read char. + */ +#define READ_NEXT_BYTE(a_tknzr, a_byte_ptr) \ +status = \ +cr_input_read_byte (PRIVATE (a_tknzr)->input, a_byte_ptr) ;\ +CHECK_PARSING_STATUS (status, TRUE) ; + +/** + *Skips a given number of byte in the topmost + *parser input. Don't update line and column number. + *In case of error, jumps to the "error:" label + *of the surrounding function. + *@param a_parser the current instance of #CRTknzr. + *@param a_nb_bytes the number of bytes to skip. + */ +#define SKIP_BYTES(a_tknzr, a_nb_bytes) \ +status = cr_input_seek_index (PRIVATE (a_tknzr)->input, \ + CR_SEEK_CUR, a_nb_bytes) ; \ +CHECK_PARSING_STATUS (status, TRUE) ; + +/** + *Skip utf8 encoded characters. + *Updates line and column numbers. + *@param a_parser the current instance of #CRTknzr. + *@param a_nb_chars the number of chars to skip. Must be of + *type glong. + */ +#define SKIP_CHARS(a_tknzr, a_nb_chars) \ +{ \ +glong nb_chars = a_nb_chars ; \ +status = cr_input_consume_chars \ + (PRIVATE (a_tknzr)->input,0, &nb_chars) ; \ +CHECK_PARSING_STATUS (status, TRUE) ; \ +} + +/** + *Tests the condition and if it is false, sets + *status to "CR_PARSING_ERROR" and goto the 'error' + *label. + *@param condition the condition to test. + */ +#define ENSURE_PARSING_COND(condition) \ +if (! (condition)) {status = CR_PARSING_ERROR; goto error ;} + +static enum CRStatus cr_tknzr_parse_nl (CRTknzr * a_this, + guchar ** a_start, + guchar ** a_end, + CRParsingLocation *a_location); + +static enum CRStatus cr_tknzr_parse_w (CRTknzr * a_this, + guchar ** a_start, + guchar ** a_end, + CRParsingLocation *a_location) ; + +static enum CRStatus cr_tknzr_parse_unicode_escape (CRTknzr * a_this, + guint32 * a_unicode, + CRParsingLocation *a_location) ; + +static enum CRStatus cr_tknzr_parse_escape (CRTknzr * a_this, + guint32 * a_esc_code, + CRParsingLocation *a_location); + +static enum CRStatus cr_tknzr_parse_string (CRTknzr * a_this, + CRString ** a_str); + +static enum CRStatus cr_tknzr_parse_comment (CRTknzr * a_this, + CRString ** a_comment); + +static enum CRStatus cr_tknzr_parse_nmstart (CRTknzr * a_this, + guint32 * a_char, + CRParsingLocation *a_location); + +static enum CRStatus cr_tknzr_parse_num (CRTknzr * a_this, + CRNum ** a_num); + +/********************************** + *PRIVATE methods + **********************************/ + +/** + *Parses a "w" as defined by the css spec at [4.1.1]: + * w ::= [ \t\r\n\f]* + * + *@param a_this the current instance of #CRTknzr. + *@param a_start out param. Upon successfull completion, points + *to the beginning of the parsed white space, points to NULL otherwise. + *Can also point to NULL is there is no white space actually. + *@param a_end out param. Upon successfull completion, points + *to the end of the parsed white space, points to NULL otherwise. + *Can also point to NULL is there is no white space actually. + */ +static enum CRStatus +cr_tknzr_parse_w (CRTknzr * a_this, + guchar ** a_start, + guchar ** a_end, + CRParsingLocation *a_location) +{ + guint32 cur_char = 0; + CRInputPos init_pos; + enum CRStatus status = CR_OK; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && PRIVATE (a_this)->input + && a_start && a_end, + CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + *a_start = NULL; + *a_end = NULL; + + READ_NEXT_CHAR (a_this, &cur_char); + + if (cr_utils_is_white_space (cur_char) == FALSE) { + status = CR_PARSING_ERROR; + goto error; + } + if (a_location) { + cr_tknzr_get_parsing_location (a_this, + a_location) ; + } + RECORD_CUR_BYTE_ADDR (a_this, a_start); + *a_end = *a_start; + + for (;;) { + gboolean is_eof = FALSE; + + cr_input_get_end_of_file (PRIVATE (a_this)->input, &is_eof); + if (is_eof) + break; + + status = cr_tknzr_peek_char (a_this, &cur_char); + if (status == CR_END_OF_INPUT_ERROR) { + status = CR_OK; + break; + } else if (status != CR_OK) { + goto error; + } + + if (cr_utils_is_white_space (cur_char) == TRUE) { + READ_NEXT_CHAR (a_this, &cur_char); + RECORD_CUR_BYTE_ADDR (a_this, a_end); + } else { + break; + } + } + + return CR_OK; + + error: + cr_tknzr_set_cur_pos (a_this, &init_pos); + + return status; +} + +/** + *Parses a newline as defined in the css2 spec: + * nl ::= \n|\r\n|\r|\f + * + *@param a_this the "this pointer" of the current instance of #CRTknzr. + *@param a_start a pointer to the first character of the successfully + *parsed string. + *@param a_end a pointer to the last character of the successfully parsed + *string. + *@result CR_OK uppon successfull completion, an error code otherwise. + */ +static enum CRStatus +cr_tknzr_parse_nl (CRTknzr * a_this, + guchar ** a_start, + guchar ** a_end, + CRParsingLocation *a_location) +{ + CRInputPos init_pos; + guchar next_chars[2] = { 0 }; + enum CRStatus status = CR_PARSING_ERROR; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_start && a_end, CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + PEEK_BYTE (a_this, 1, &next_chars[0]); + PEEK_BYTE (a_this, 2, &next_chars[1]); + + if ((next_chars[0] == '\r' && next_chars[1] == '\n')) { + SKIP_BYTES (a_this, 1); + if (a_location) { + cr_tknzr_get_parsing_location + (a_this, a_location) ; + } + SKIP_CHARS (a_this, 1); + + RECORD_CUR_BYTE_ADDR (a_this, a_end); + + status = CR_OK; + } else if (next_chars[0] == '\n' + || next_chars[0] == '\r' || next_chars[0] == '\f') { + SKIP_CHARS (a_this, 1); + if (a_location) { + cr_tknzr_get_parsing_location + (a_this, a_location) ; + } + RECORD_CUR_BYTE_ADDR (a_this, a_start); + *a_end = *a_start; + status = CR_OK; + } else { + status = CR_PARSING_ERROR; + goto error; + } + return CR_OK ; + + error: + cr_tknzr_set_cur_pos (a_this, &init_pos) ; + return status; +} + +/** + *Go ahead in the parser input, skipping all the spaces. + *If the next char if not a white space, this function does nothing. + *In any cases, it stops when it encounters a non white space character. + * + *@param a_this the current instance of #CRTknzr. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +static enum CRStatus +cr_tknzr_try_to_skip_spaces (CRTknzr * a_this) +{ + enum CRStatus status = CR_ERROR; + guint32 cur_char = 0; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && PRIVATE (a_this)->input, CR_BAD_PARAM_ERROR); + + status = cr_input_peek_char (PRIVATE (a_this)->input, &cur_char); + + if (status != CR_OK) { + if (status == CR_END_OF_INPUT_ERROR) + return CR_OK; + return status; + } + + if (cr_utils_is_white_space (cur_char) == TRUE) { + glong nb_chars = -1; /*consume all spaces */ + + status = cr_input_consume_white_spaces + (PRIVATE (a_this)->input, &nb_chars); + } + + return status; +} + +/** + *Parses a "comment" as defined in the css spec at [4.1.1]: + *COMMENT ::= \/\*[^*]*\*+([^/][^*]*\*+)*\/ . + *This complex regexp is just to say that comments start + *with the two chars '/''*' and ends with the two chars '*''/'. + *It also means that comments cannot be nested. + *So based on that, I've just tried to implement the parsing function + *simply and in a straight forward manner. + */ +static enum CRStatus +cr_tknzr_parse_comment (CRTknzr * a_this, + CRString ** a_comment) +{ + enum CRStatus status = CR_OK; + CRInputPos init_pos; + guint32 cur_char = 0, next_char= 0; + CRString *comment = NULL; + CRParsingLocation loc = {0,0,0} ; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && PRIVATE (a_this)->input, + CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + READ_NEXT_CHAR (a_this, &cur_char) ; + ENSURE_PARSING_COND (cur_char == '/'); + cr_tknzr_get_parsing_location (a_this, &loc) ; + + READ_NEXT_CHAR (a_this, &cur_char); + ENSURE_PARSING_COND (cur_char == '*'); + comment = cr_string_new (); + for (;;) { + READ_NEXT_CHAR (a_this, &cur_char); + + /*make sure there are no nested comments */ + if (cur_char == '/') { + READ_NEXT_CHAR (a_this, &cur_char); + ENSURE_PARSING_COND (cur_char != '*'); + g_string_append_c (comment->stryng, '/'); + g_string_append_unichar (comment->stryng, + cur_char); + continue; + } + + /*Detect the end of the comments region */ + if (cur_char == '*') { + PEEK_NEXT_CHAR (a_this, &next_char); + + if (next_char == '/') { + /* + *end of comments region + *Now, call the right SAC callback. + */ + SKIP_CHARS (a_this, 1) ; + status = CR_OK; + break; + } else { + g_string_append_c (comment->stryng, + '*'); + } + } + g_string_append_unichar (comment->stryng, cur_char); + } + + if (status == CR_OK) { + cr_parsing_location_copy (&comment->location, + &loc) ; + *a_comment = comment; + return CR_OK; + } + error: + + if (comment) { + cr_string_destroy (comment); + comment = NULL; + } + + cr_tknzr_set_cur_pos (a_this, &init_pos); + + return status; +} + +/** + *Parses an 'unicode' escape sequence defined + *in css spec at chap 4.1.1: + *unicode ::= \\[0-9a-f]{1,6}[ \n\r\t\f]? + *@param a_this the current instance of #CRTknzr. + *@param a_start out parameter. A pointer to the start + *of the unicode escape sequence. Must *NOT* be deleted by + *the caller. + *@param a_end out parameter. A pointer to the last character + *of the unicode escape sequence. Must *NOT* be deleted by the caller. + *@return CR_OK if parsing succeded, an error code otherwise. + *Error code can be either CR_PARSING_ERROR if the string + *parsed just doesn't + *respect the production or another error if a + *lower level error occured. + */ +static enum CRStatus +cr_tknzr_parse_unicode_escape (CRTknzr * a_this, + guint32 * a_unicode, + CRParsingLocation *a_location) +{ + guint32 cur_char; + CRInputPos init_pos; + glong occur = 0; + guint32 unicode = 0; + guchar *tmp_char_ptr1 = NULL, + *tmp_char_ptr2 = NULL; + enum CRStatus status = CR_OK; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_unicode, CR_BAD_PARAM_ERROR); + + /*first, let's backup the current position pointer */ + RECORD_INITIAL_POS (a_this, &init_pos); + + READ_NEXT_CHAR (a_this, &cur_char); + + if (cur_char != '\\') { + status = CR_PARSING_ERROR; + goto error; + } + if (a_location) { + cr_tknzr_get_parsing_location + (a_this, a_location) ; + } + PEEK_NEXT_CHAR (a_this, &cur_char); + + for (occur = 0, unicode = 0; ((cur_char >= '0' && cur_char <= '9') + || (cur_char >= 'a' && cur_char <= 'f') + || (cur_char >= 'A' && cur_char <= 'F')) + && occur < 6; occur++) { + gint cur_char_val = 0; + + READ_NEXT_CHAR (a_this, &cur_char); + + if ((cur_char >= '0' && cur_char <= '9')) { + cur_char_val = (cur_char - '0'); + } else if ((cur_char >= 'a' && cur_char <= 'f')) { + cur_char_val = 10 + (cur_char - 'a'); + } else if ((cur_char >= 'A' && cur_char <= 'F')) { + cur_char_val = 10 + (cur_char - 'A'); + } + + unicode = unicode * 10 + cur_char_val; + + PEEK_NEXT_CHAR (a_this, &cur_char); + } + + if (occur == 5) { + /* + *the unicode escape is 6 digit length + */ + + /* + *parse one space that may + *appear just after the unicode + *escape. + */ + cr_tknzr_parse_w (a_this, &tmp_char_ptr1, + &tmp_char_ptr2, NULL); + status = CR_OK; + } else { + /* + *The unicode escape is less than + *6 digit length. The character + *that comes right after the escape + *must be a white space. + */ + status = cr_tknzr_parse_w (a_this, &tmp_char_ptr1, + &tmp_char_ptr2, NULL); + } + + if (status == CR_OK) { + *a_unicode = unicode; + return CR_OK; + } + + error: + /* + *restore the initial position pointer backuped at + *the beginning of this function. + */ + cr_tknzr_set_cur_pos (a_this, &init_pos); + + return status; +} + +/** + *parses an escape sequence as defined by the css spec: + *escape ::= {unicode}|\\[ -~\200-\4177777] + *@param a_this the current instance of #CRTknzr . + */ +static enum CRStatus +cr_tknzr_parse_escape (CRTknzr * a_this, guint32 * a_esc_code, + CRParsingLocation *a_location) +{ + enum CRStatus status = CR_OK; + guint32 cur_char = 0; + CRInputPos init_pos; + guchar next_chars[2]; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_esc_code, CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + PEEK_BYTE (a_this, 1, &next_chars[0]); + PEEK_BYTE (a_this, 2, &next_chars[1]); + + if (next_chars[0] != '\\') { + status = CR_PARSING_ERROR; + goto error; + } + + if ((next_chars[1] >= '0' && next_chars[1] <= '9') + || (next_chars[1] >= 'a' && next_chars[1] <= 'f') + || (next_chars[1] >= 'A' && next_chars[1] <= 'F')) { + status = cr_tknzr_parse_unicode_escape (a_this, a_esc_code, + a_location); + } else { + /*consume the '\' char */ + READ_NEXT_CHAR (a_this, &cur_char); + if (a_location) { + cr_tknzr_get_parsing_location (a_this, + a_location) ; + } + /*then read the char after the '\' */ + READ_NEXT_CHAR (a_this, &cur_char); + + if (cur_char != ' ' && (cur_char < 200 || cur_char > 4177777)) { + status = CR_PARSING_ERROR; + goto error; + } + *a_esc_code = cur_char; + + } + if (status == CR_OK) { + return CR_OK; + } + error: + cr_tknzr_set_cur_pos (a_this, &init_pos); + return status; +} + +/** + *Parses a string type as defined in css spec [4.1.1]: + * + *string ::= {string1}|{string2} + *string1 ::= \"([\t !#$%&(-~]|\\{nl}|\'|{nonascii}|{escape})*\" + *string2 ::= \'([\t !#$%&(-~]|\\{nl}|\"|{nonascii}|{escape})*\' + * + *@param a_this the current instance of #CRTknzr. + *@param a_start out parameter. Upon successfull completion, + *points to the beginning of the string, points to an undefined value + *otherwise. + *@param a_end out parameter. Upon successfull completion, points to + *the beginning of the string, points to an undefined value otherwise. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +static enum CRStatus +cr_tknzr_parse_string (CRTknzr * a_this, CRString ** a_str) +{ + guint32 cur_char = 0, + delim = 0; + CRInputPos init_pos; + enum CRStatus status = CR_OK; + CRString *str = NULL; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && PRIVATE (a_this)->input + && a_str, CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + READ_NEXT_CHAR (a_this, &cur_char); + + if (cur_char == '"') + delim = '"'; + else if (cur_char == '\'') + delim = '\''; + else { + status = CR_PARSING_ERROR; + goto error; + } + str = cr_string_new (); + if (str) { + cr_tknzr_get_parsing_location + (a_this, &str->location) ; + } + for (;;) { + guchar next_chars[2] = { 0 }; + + PEEK_BYTE (a_this, 1, &next_chars[0]); + PEEK_BYTE (a_this, 2, &next_chars[1]); + + if (next_chars[0] == '\\') { + guchar *tmp_char_ptr1 = NULL, + *tmp_char_ptr2 = NULL; + guint32 esc_code = 0; + + if (next_chars[1] == '\'' || next_chars[1] == '"') { + g_string_append_unichar (str->stryng, + next_chars[1]); + SKIP_BYTES (a_this, 2); + status = CR_OK; + } else { + status = cr_tknzr_parse_escape + (a_this, &esc_code, NULL); + + if (status == CR_OK) { + g_string_append_unichar + (str->stryng, + esc_code); + } + } + + if (status != CR_OK) { + /* + *consume the '\' char, and try to parse + *a newline. + */ + READ_NEXT_CHAR (a_this, &cur_char); + + status = cr_tknzr_parse_nl + (a_this, &tmp_char_ptr1, + &tmp_char_ptr2, NULL); + } + + CHECK_PARSING_STATUS (status, FALSE); + } else if (strchr ("\t !#$%&", next_chars[0]) + || (next_chars[0] >= '(' && next_chars[0] <= '~')) { + READ_NEXT_CHAR (a_this, &cur_char); + g_string_append_unichar (str->stryng, + cur_char); + status = CR_OK; + } + + else if (cr_utils_is_nonascii (next_chars[0])) { + READ_NEXT_CHAR (a_this, &cur_char); + g_string_append_unichar (str->stryng, cur_char); + } else if (next_chars[0] == delim) { + READ_NEXT_CHAR (a_this, &cur_char); + break; + } else { + status = CR_PARSING_ERROR; + goto error; + } + } + + if (status == CR_OK) { + if (*a_str == NULL) { + *a_str = str; + str = NULL; + } else { + (*a_str)->stryng = g_string_append_len + ((*a_str)->stryng, + str->stryng->str, + str->stryng->len); + cr_string_destroy (str); + } + return CR_OK; + } + + error: + + if (str) { + cr_string_destroy (str) ; + str = NULL; + } + cr_tknzr_set_cur_pos (a_this, &init_pos); + return status; +} + +/** + *Parses the an nmstart as defined by the css2 spec [4.1.1]: + * nmstart [a-zA-Z]|{nonascii}|{escape} + * + *@param a_this the current instance of #CRTknzr. + *@param a_start out param. A pointer to the starting point of + *the token. + *@param a_end out param. A pointer to the ending point of the + *token. + *@param a_char out param. The actual parsed nmchar. + *@return CR_OK upon successfull completion, + *an error code otherwise. + */ +static enum CRStatus +cr_tknzr_parse_nmstart (CRTknzr * a_this, + guint32 * a_char, + CRParsingLocation *a_location) +{ + CRInputPos init_pos; + enum CRStatus status = CR_OK; + guint32 cur_char = 0, + next_char = 0; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && PRIVATE (a_this)->input + && a_char, CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + PEEK_NEXT_CHAR (a_this, &next_char); + + if (next_char == '\\') { + status = cr_tknzr_parse_escape (a_this, a_char, + a_location); + + if (status != CR_OK) + goto error; + + } else if (cr_utils_is_nonascii (next_char) == TRUE + || ((next_char >= 'a') && (next_char <= 'z')) + || ((next_char >= 'A') && (next_char <= 'Z')) + ) { + READ_NEXT_CHAR (a_this, &cur_char); + if (a_location) { + cr_tknzr_get_parsing_location (a_this, + a_location) ; + } + *a_char = cur_char; + status = CR_OK; + } else { + status = CR_PARSING_ERROR; + goto error; + } + + return CR_OK; + + error: + cr_tknzr_set_cur_pos (a_this, &init_pos); + + return status; + +} + +/** + *Parses an nmchar as described in the css spec at + *chap 4.1.1: + *nmchar ::= [a-z0-9-]|{nonascii}|{escape} + * + *Humm, I have added the possibility for nmchar to + *contain upper case letters. + * + *@param a_this the current instance of #CRTknzr. + *@param a_start out param. A pointer to the starting point of + *the token. + *@param a_end out param. A pointer to the ending point of the + *token. + *@param a_char out param. The actual parsed nmchar. + *@return CR_OK upon successfull completion, + *an error code otherwise. + */ +static enum CRStatus +cr_tknzr_parse_nmchar (CRTknzr * a_this, guint32 * a_char, + CRParsingLocation *a_location) +{ + guint32 cur_char = 0, + next_char = 0; + enum CRStatus status = CR_OK; + CRInputPos init_pos; + + g_return_val_if_fail (a_this && PRIVATE (a_this) && a_char, + CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + status = cr_input_peek_char (PRIVATE (a_this)->input, + &next_char) ; + if (status != CR_OK) + goto error; + + if (next_char == '\\') { + status = cr_tknzr_parse_escape (a_this, a_char, + a_location); + + if (status != CR_OK) + goto error; + + } else if (cr_utils_is_nonascii (next_char) == TRUE + || ((next_char >= 'a') && (next_char <= 'z')) + || ((next_char >= 'A') && (next_char <= 'Z')) + || ((next_char >= '0') && (next_char <= '9')) + || (next_char == '-') + || (next_char == '_') /*'_' not allowed by the spec. */ + ) { + READ_NEXT_CHAR (a_this, &cur_char); + *a_char = cur_char; + status = CR_OK; + if (a_location) { + cr_tknzr_get_parsing_location + (a_this, a_location) ; + } + } else { + status = CR_PARSING_ERROR; + goto error; + } + return CR_OK; + + error: + cr_tknzr_set_cur_pos (a_this, &init_pos); + return status; +} + +/** + *Parses an "ident" as defined in css spec [4.1.1]: + *ident ::= {nmstart}{nmchar}* + * + *Actually parses it using the css3 grammar: + *ident ::= -?{nmstart}{nmchar}* + *@param a_this the currens instance of #CRTknzr. + * + *@param a_str a pointer to parsed ident. If *a_str is NULL, + *this function allocates a new instance of CRString. If not, + *the function just appends the parsed string to the one passed. + *In both cases it is up to the caller to free *a_str. + * + *@return CR_OK upon successfull completion, an error code + *otherwise. + */ +static enum CRStatus +cr_tknzr_parse_ident (CRTknzr * a_this, CRString ** a_str) +{ + guint32 tmp_char = 0; + CRString *stringue = NULL ; + CRInputPos init_pos; + enum CRStatus status = CR_OK; + gboolean location_is_set = FALSE ; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && PRIVATE (a_this)->input + && a_str, CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + PEEK_NEXT_CHAR (a_this, &tmp_char) ; + stringue = cr_string_new () ; + g_return_val_if_fail (stringue, + CR_OUT_OF_MEMORY_ERROR) ; + + if (tmp_char == '-') { + READ_NEXT_CHAR (a_this, &tmp_char) ; + cr_tknzr_get_parsing_location + (a_this, &stringue->location) ; + location_is_set = TRUE ; + g_string_append_unichar (stringue->stryng, + tmp_char) ; + } + status = cr_tknzr_parse_nmstart (a_this, &tmp_char, NULL); + if (status != CR_OK) { + status = CR_PARSING_ERROR; + goto end ; + } + if (location_is_set == FALSE) { + cr_tknzr_get_parsing_location + (a_this, &stringue->location) ; + location_is_set = TRUE ; + } + g_string_append_unichar (stringue->stryng, tmp_char); + for (;;) { + status = cr_tknzr_parse_nmchar (a_this, + &tmp_char, + NULL); + if (status != CR_OK) { + status = CR_OK ; + break; + } + g_string_append_unichar (stringue->stryng, tmp_char); + } + if (status == CR_OK) { + if (!*a_str) { + *a_str = stringue ; + + } else { + g_string_append_len ((*a_str)->stryng, + stringue->stryng->str, + stringue->stryng->len) ; + cr_string_destroy (stringue) ; + } + stringue = NULL ; + } + + error: + end: + if (stringue) { + cr_string_destroy (stringue) ; + stringue = NULL ; + } + if (status != CR_OK ) { + cr_tknzr_set_cur_pos (a_this, &init_pos) ; + } + return status ; +} + + +/** + *Parses a "name" as defined by css spec [4.1.1]: + *name ::= {nmchar}+ + * + *@param a_this the current instance of #CRTknzr. + * + *@param a_str out parameter. A pointer to the successfully parsed + *name. If *a_str is set to NULL, this function allocates a new instance + *of CRString. If not, it just appends the parsed name to the passed *a_str. + *In both cases, it is up to the caller to free *a_str. + * + *@return CR_OK upon successfull completion, an error code otherwise. + */ +static enum CRStatus +cr_tknzr_parse_name (CRTknzr * a_this, + CRString ** a_str) +{ + guint32 tmp_char = 0; + CRInputPos init_pos; + enum CRStatus status = CR_OK; + gboolean str_needs_free = FALSE, + is_first_nmchar=TRUE ; + glong i = 0; + CRParsingLocation loc = {0,0,0} ; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && PRIVATE (a_this)->input + && a_str, + CR_BAD_PARAM_ERROR) ; + + RECORD_INITIAL_POS (a_this, &init_pos); + + if (*a_str == NULL) { + *a_str = cr_string_new (); + str_needs_free = TRUE; + } + for (i = 0;; i++) { + if (is_first_nmchar == TRUE) { + status = cr_tknzr_parse_nmchar + (a_this, &tmp_char, + &loc) ; + is_first_nmchar = FALSE ; + } else { + status = cr_tknzr_parse_nmchar + (a_this, &tmp_char, NULL) ; + } + if (status != CR_OK) + break; + g_string_append_unichar ((*a_str)->stryng, + tmp_char); + } + if (i > 0) { + cr_parsing_location_copy + (&(*a_str)->location, &loc) ; + return CR_OK; + } + if (str_needs_free == TRUE && *a_str) { + cr_string_destroy (*a_str); + *a_str = NULL; + } + cr_tknzr_set_cur_pos (a_this, &init_pos); + return CR_PARSING_ERROR; +} + +/** + *Parses a "hash" as defined by the css spec in [4.1.1]: + *HASH ::= #{name} + */ +static enum CRStatus +cr_tknzr_parse_hash (CRTknzr * a_this, CRString ** a_str) +{ + guint32 cur_char = 0; + CRInputPos init_pos; + enum CRStatus status = CR_OK; + gboolean str_needs_free = FALSE; + CRParsingLocation loc = {0,0,0} ; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && PRIVATE (a_this)->input, + CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + READ_NEXT_CHAR (a_this, &cur_char); + if (cur_char != '#') { + status = CR_PARSING_ERROR; + goto error; + } + if (*a_str == NULL) { + *a_str = cr_string_new (); + str_needs_free = TRUE; + } + cr_tknzr_get_parsing_location (a_this, + &loc) ; + status = cr_tknzr_parse_name (a_this, a_str); + cr_parsing_location_copy (&(*a_str)->location, &loc) ; + if (status != CR_OK) { + goto error; + } + return CR_OK; + + error: + if (str_needs_free == TRUE && *a_str) { + cr_string_destroy (*a_str); + *a_str = NULL; + } + + cr_tknzr_set_cur_pos (a_this, &init_pos); + return status; +} + +/** + *Parses an uri as defined by the css spec [4.1.1]: + * URI ::= url\({w}{string}{w}\) + * |url\({w}([!#$%&*-~]|{nonascii}|{escape})*{w}\) + * + *@param a_this the current instance of #CRTknzr. + *@param a_str the successfully parsed url. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +static enum CRStatus +cr_tknzr_parse_uri (CRTknzr * a_this, + CRString ** a_str) +{ + guint32 cur_char = 0; + CRInputPos init_pos; + enum CRStatus status = CR_PARSING_ERROR; + guchar tab[4] = { 0 }, *tmp_ptr1 = NULL, *tmp_ptr2 = NULL; + CRString *str = NULL; + CRParsingLocation location = {0,0,0} ; + + g_return_val_if_fail (a_this + && PRIVATE (a_this) + && PRIVATE (a_this)->input + && a_str, + CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + PEEK_BYTE (a_this, 1, &tab[0]); + PEEK_BYTE (a_this, 2, &tab[1]); + PEEK_BYTE (a_this, 3, &tab[2]); + PEEK_BYTE (a_this, 4, &tab[3]); + + if (tab[0] != 'u' || tab[1] != 'r' || tab[2] != 'l' || tab[3] != '(') { + status = CR_PARSING_ERROR; + goto error; + } + /* + *Here, we want to skip 4 bytes ('u''r''l''('). + *But we also need to keep track of the parsing location + *of the 'u'. So, we skip 1 byte, we record the parsing + *location, then we skip the 3 remaining bytes. + */ + SKIP_CHARS (a_this, 1); + cr_tknzr_get_parsing_location (a_this, &location) ; + SKIP_CHARS (a_this, 3); + cr_tknzr_try_to_skip_spaces (a_this); + status = cr_tknzr_parse_string (a_this, a_str); + + if (status == CR_OK) { + guint32 next_char = 0; + status = cr_tknzr_parse_w (a_this, &tmp_ptr1, + &tmp_ptr2, NULL); + cr_tknzr_try_to_skip_spaces (a_this); + PEEK_NEXT_CHAR (a_this, &next_char); + if (next_char == ')') { + READ_NEXT_CHAR (a_this, &cur_char); + status = CR_OK; + } else { + status = CR_PARSING_ERROR; + } + } + if (status != CR_OK) { + str = cr_string_new (); + for (;;) { + guint32 next_char = 0; + PEEK_NEXT_CHAR (a_this, &next_char); + if (strchr ("!#$%&", next_char) + || (next_char >= '*' && next_char <= '~') + || (cr_utils_is_nonascii (next_char) == TRUE)) { + READ_NEXT_CHAR (a_this, &cur_char); + g_string_append_unichar + (str->stryng, cur_char); + status = CR_OK; + } else { + guint32 esc_code = 0; + status = cr_tknzr_parse_escape + (a_this, &esc_code, NULL); + if (status == CR_OK) { + g_string_append_unichar + (str->stryng, + esc_code); + } else { + status = CR_OK; + break; + } + } + } + cr_tknzr_try_to_skip_spaces (a_this); + READ_NEXT_CHAR (a_this, &cur_char); + if (cur_char == ')') { + status = CR_OK; + } else { + status = CR_PARSING_ERROR; + goto error; + } + if (str) { + if (*a_str == NULL) { + *a_str = str; + str = NULL; + } else { + g_string_append_len + ((*a_str)->stryng, + str->stryng->str, + str->stryng->len); + cr_string_destroy (str); + } + } + } + + cr_parsing_location_copy + (&(*a_str)->location, + &location) ; + return CR_OK ; + error: + if (str) { + cr_string_destroy (str); + str = NULL; + } + cr_tknzr_set_cur_pos (a_this, &init_pos); + return status; +} + +/** + *parses an RGB as defined in the css2 spec. + *rgb: rgb '('S*{num}%?S* ',' {num}#?S*,S*{num}#?S*')' + * + *@param a_this the "this pointer" of the current instance of + *@param a_rgb out parameter the parsed rgb. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +static enum CRStatus +cr_tknzr_parse_rgb (CRTknzr * a_this, CRRgb ** a_rgb) +{ + enum CRStatus status = CR_OK; + CRInputPos init_pos; + CRNum *num = NULL; + guchar next_bytes[3] = { 0 }, cur_byte = 0; + glong red = 0, + green = 0, + blue = 0, + i = 0; + gboolean is_percentage = FALSE; + CRParsingLocation location = {0,0,0} ; + + g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + PEEK_BYTE (a_this, 1, &next_bytes[0]); + PEEK_BYTE (a_this, 2, &next_bytes[1]); + PEEK_BYTE (a_this, 3, &next_bytes[2]); + + if (((next_bytes[0] == 'r') || (next_bytes[0] == 'R')) + && ((next_bytes[1] == 'g') || (next_bytes[1] == 'G')) + && ((next_bytes[2] == 'b') || (next_bytes[2] == 'B'))) { + SKIP_CHARS (a_this, 1); + cr_tknzr_get_parsing_location (a_this, &location) ; + SKIP_CHARS (a_this, 2); + } else { + status = CR_PARSING_ERROR; + goto error; + } + READ_NEXT_BYTE (a_this, &cur_byte); + ENSURE_PARSING_COND (cur_byte == '('); + + cr_tknzr_try_to_skip_spaces (a_this); + status = cr_tknzr_parse_num (a_this, &num); + ENSURE_PARSING_COND ((status == CR_OK) && (num != NULL)); + + red = num->val; + cr_num_destroy (num); + num = NULL; + + PEEK_BYTE (a_this, 1, &next_bytes[0]); + if (next_bytes[0] == '%') { + SKIP_CHARS (a_this, 1); + is_percentage = TRUE; + } + cr_tknzr_try_to_skip_spaces (a_this); + + for (i = 0; i < 2; i++) { + READ_NEXT_BYTE (a_this, &cur_byte); + ENSURE_PARSING_COND (cur_byte == ','); + + cr_tknzr_try_to_skip_spaces (a_this); + status = cr_tknzr_parse_num (a_this, &num); + ENSURE_PARSING_COND ((status == CR_OK) && (num != NULL)); + + PEEK_BYTE (a_this, 1, &next_bytes[0]); + if (next_bytes[0] == '%') { + SKIP_CHARS (a_this, 1); + is_percentage = 1; + } + + if (i == 0) { + green = num->val; + } else if (i == 1) { + blue = num->val; + } + + if (num) { + cr_num_destroy (num); + num = NULL; + } + cr_tknzr_try_to_skip_spaces (a_this); + } + + READ_NEXT_BYTE (a_this, &cur_byte); + if (*a_rgb == NULL) { + *a_rgb = cr_rgb_new_with_vals (red, green, blue, + is_percentage); + + if (*a_rgb == NULL) { + status = CR_ERROR; + goto error; + } + status = CR_OK; + } else { + (*a_rgb)->red = red; + (*a_rgb)->green = green; + (*a_rgb)->blue = blue; + (*a_rgb)->is_percentage = is_percentage; + + status = CR_OK; + } + + if (status == CR_OK) { + if (a_rgb && *a_rgb) { + cr_parsing_location_copy + (&(*a_rgb)->location, + &location) ; + } + return CR_OK; + } + + error: + if (num) { + cr_num_destroy (num); + num = NULL; + } + + cr_tknzr_set_cur_pos (a_this, &init_pos); + return CR_OK; +} + +/** + *Parses a atkeyword as defined by the css spec in [4.1.1]: + *ATKEYWORD ::= @{ident} + * + *@param a_this the "this pointer" of the current instance of + *#CRTknzr. + * + *@param a_str out parameter. The parsed atkeyword. If *a_str is + *set to NULL this function allocates a new instance of CRString and + *sets it to the parsed atkeyword. If not, this function just appends + *the parsed atkeyword to the end of *a_str. In both cases it is up to + *the caller to free *a_str. + * + *@return CR_OK upon successfull completion, an error code otherwise. + */ +static enum CRStatus +cr_tknzr_parse_atkeyword (CRTknzr * a_this, + CRString ** a_str) +{ + guint32 cur_char = 0; + CRInputPos init_pos; + gboolean str_needs_free = FALSE; + enum CRStatus status = CR_OK; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && PRIVATE (a_this)->input + && a_str, CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + + READ_NEXT_CHAR (a_this, &cur_char); + + if (cur_char != '@') { + status = CR_PARSING_ERROR; + goto error; + } + + if (*a_str == NULL) { + *a_str = cr_string_new (); + str_needs_free = TRUE; + } + status = cr_tknzr_parse_ident (a_this, a_str); + if (status != CR_OK) { + goto error; + } + return CR_OK; + error: + + if (str_needs_free == TRUE && *a_str) { + cr_string_destroy (*a_str); + *a_str = NULL; + } + cr_tknzr_set_cur_pos (a_this, &init_pos); + return status; +} + +static enum CRStatus +cr_tknzr_parse_important (CRTknzr * a_this, + CRParsingLocation *a_location) +{ + guint32 cur_char = 0; + CRInputPos init_pos; + enum CRStatus status = CR_OK; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && PRIVATE (a_this)->input, + CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + READ_NEXT_CHAR (a_this, &cur_char); + ENSURE_PARSING_COND (cur_char == '!'); + if (a_location) { + cr_tknzr_get_parsing_location (a_this, + a_location) ; + } + cr_tknzr_try_to_skip_spaces (a_this); + + if (BYTE (PRIVATE (a_this)->input, 1, NULL) == 'i' + && BYTE (PRIVATE (a_this)->input, 2, NULL) == 'm' + && BYTE (PRIVATE (a_this)->input, 3, NULL) == 'p' + && BYTE (PRIVATE (a_this)->input, 4, NULL) == 'o' + && BYTE (PRIVATE (a_this)->input, 5, NULL) == 'r' + && BYTE (PRIVATE (a_this)->input, 6, NULL) == 't' + && BYTE (PRIVATE (a_this)->input, 7, NULL) == 'a' + && BYTE (PRIVATE (a_this)->input, 8, NULL) == 'n' + && BYTE (PRIVATE (a_this)->input, 9, NULL) == 't') { + SKIP_BYTES (a_this, 9); + if (a_location) { + cr_tknzr_get_parsing_location (a_this, + a_location) ; + } + return CR_OK; + } else { + status = CR_PARSING_ERROR; + } + + error: + cr_tknzr_set_cur_pos (a_this, &init_pos); + + return status; +} + +/** + *Parses a num as defined in the css spec [4.1.1]: + *[0-9]+|[0-9]*\.[0-9]+ + *@param a_this the current instance of #CRTknzr. + *@param a_num out parameter. The parsed number. + *@return CR_OK upon successfull completion, + *an error code otherwise. + */ +static enum CRStatus +cr_tknzr_parse_num (CRTknzr * a_this, + CRNum ** a_num) +{ + enum CRStatus status = CR_PARSING_ERROR; + enum CRNumType val_type = NUM_GENERIC; + gboolean parsing_dec, /* true iff seen decimal point. */ + parsed; /* true iff the substring seen so far is a valid CSS + number, i.e. `[0-9]+|[0-9]*\.[0-9]+'. */ + guint32 cur_char = 0, + next_char = 0; + gdouble numerator, denominator = 1; + CRInputPos init_pos; + CRParsingLocation location = {0,0,0} ; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && PRIVATE (a_this)->input, + CR_BAD_PARAM_ERROR); + + RECORD_INITIAL_POS (a_this, &init_pos); + READ_NEXT_CHAR (a_this, &cur_char); + if (IS_NUM (cur_char)) { + numerator = (cur_char - '0'); + parsing_dec = FALSE; + parsed = TRUE; + } else if (cur_char == '.') { + numerator = 0; + parsing_dec = TRUE; + parsed = FALSE; + } else { + status = CR_PARSING_ERROR; + goto error; + } + cr_tknzr_get_parsing_location (a_this, &location) ; + + for (;;) { + status = cr_tknzr_peek_char (a_this, &next_char); + if (status != CR_OK) { + if (status == CR_END_OF_INPUT_ERROR) + status = CR_OK; + break; + } + if (next_char == '.') { + if (parsing_dec) { + status = CR_PARSING_ERROR; + goto error; + } + + READ_NEXT_CHAR (a_this, &cur_char); + parsing_dec = TRUE; + parsed = FALSE; /* In CSS, there must be at least + one digit after `.'. */ + } else if (IS_NUM (next_char)) { + READ_NEXT_CHAR (a_this, &cur_char); + parsed = TRUE; + + numerator = numerator * 10 + (cur_char - '0'); + if (parsing_dec) { + denominator *= 10; + } + } else { + break; + } + } + + if (!parsed) { + status = CR_PARSING_ERROR; + } + + /* + *Now, set the output param values. + */ + if (status == CR_OK) { + gdouble val = numerator / denominator; + if (*a_num == NULL) { + *a_num = cr_num_new_with_val (val, val_type); + + if (*a_num == NULL) { + status = CR_ERROR; + goto error; + } + } else { + (*a_num)->val = val; + (*a_num)->type = val_type; + } + cr_parsing_location_copy (&(*a_num)->location, + &location) ; + return CR_OK; + } + + error: + + cr_tknzr_set_cur_pos (a_this, &init_pos); + + return status; +} + +/********************************************* + *PUBLIC methods + ********************************************/ + +CRTknzr * +cr_tknzr_new (CRInput * a_input) +{ + CRTknzr *result = NULL; + + result = g_try_malloc (sizeof (CRTknzr)); + + if (result == NULL) { + cr_utils_trace_info ("Out of memory"); + return NULL; + } + + memset (result, 0, sizeof (CRTknzr)); + + result->priv = g_try_malloc (sizeof (CRTknzrPriv)); + + if (result->priv == NULL) { + cr_utils_trace_info ("Out of memory"); + + if (result) { + g_free (result); + result = NULL; + } + + return NULL; + } + memset (result->priv, 0, sizeof (CRTknzrPriv)); + if (a_input) + cr_tknzr_set_input (result, a_input); + return result; +} + +CRTknzr * +cr_tknzr_new_from_buf (guchar * a_buf, gulong a_len, + enum CREncoding a_enc, + gboolean a_free_at_destroy) +{ + CRTknzr *result = NULL; + CRInput *input = NULL; + + input = cr_input_new_from_buf (a_buf, a_len, a_enc, + a_free_at_destroy); + + g_return_val_if_fail (input != NULL, NULL); + + result = cr_tknzr_new (input); + + return result; +} + +CRTknzr * +cr_tknzr_new_from_uri (const guchar * a_file_uri, + enum CREncoding a_enc) +{ + CRTknzr *result = NULL; + CRInput *input = NULL; + + input = cr_input_new_from_uri (a_file_uri, a_enc); + g_return_val_if_fail (input != NULL, NULL); + + result = cr_tknzr_new (input); + + return result; +} + +void +cr_tknzr_ref (CRTknzr * a_this) +{ + g_return_if_fail (a_this && PRIVATE (a_this)); + + PRIVATE (a_this)->ref_count++; +} + +gboolean +cr_tknzr_unref (CRTknzr * a_this) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this), FALSE); + + if (PRIVATE (a_this)->ref_count > 0) { + PRIVATE (a_this)->ref_count--; + } + + if (PRIVATE (a_this)->ref_count == 0) { + cr_tknzr_destroy (a_this); + return TRUE; + } + + return FALSE; +} + +enum CRStatus +cr_tknzr_set_input (CRTknzr * a_this, CRInput * a_input) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); + + if (PRIVATE (a_this)->input) { + cr_input_unref (PRIVATE (a_this)->input); + } + + PRIVATE (a_this)->input = a_input; + + cr_input_ref (PRIVATE (a_this)->input); + + return CR_OK; +} + +enum CRStatus +cr_tknzr_get_input (CRTknzr * a_this, CRInput ** a_input) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); + + *a_input = PRIVATE (a_this)->input; + + return CR_OK; +} + +/********************************* + *Tokenizer input handling routines + *********************************/ + +/** + *Reads the next byte from the parser input stream. + *@param a_this the "this pointer" of the current instance of + *#CRParser. + *@param a_byte out parameter the place where to store the byte + *read. + *@return CR_OK upon successfull completion, an error + *code otherwise. + */ +enum CRStatus +cr_tknzr_read_byte (CRTknzr * a_this, guchar * a_byte) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR); + + return cr_input_read_byte (PRIVATE (a_this)->input, a_byte); + +} + +/** + *Reads the next char from the parser input stream. + *@param a_this the current instance of #CRTknzr. + *@param a_char out parameter. The read char. + *@return CR_OK upon successfull completion, an error code + *otherwise. + */ +enum CRStatus +cr_tknzr_read_char (CRTknzr * a_this, guint32 * a_char) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this) + && PRIVATE (a_this)->input + && a_char, CR_BAD_PARAM_ERROR); + + if (PRIVATE (a_this)->token_cache) { + cr_input_set_cur_pos (PRIVATE (a_this)->input, + &PRIVATE (a_this)->prev_pos); + cr_token_destroy (PRIVATE (a_this)->token_cache); + PRIVATE (a_this)->token_cache = NULL; + } + + return cr_input_read_char (PRIVATE (a_this)->input, a_char); +} + +/** + *Peeks a char from the parser input stream. + *To "peek a char" means reads the next char without consuming it. + *Subsequent calls to this function return the same char. + *@param a_this the current instance of #CRTknzr. + *@param a_char out parameter. The peeked char uppon successfull completion. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_tknzr_peek_char (CRTknzr * a_this, guint32 * a_char) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this) + && PRIVATE (a_this)->input + && a_char, CR_BAD_PARAM_ERROR); + + if (PRIVATE (a_this)->token_cache) { + cr_input_set_cur_pos (PRIVATE (a_this)->input, + &PRIVATE (a_this)->prev_pos); + cr_token_destroy (PRIVATE (a_this)->token_cache); + PRIVATE (a_this)->token_cache = NULL; + } + + return cr_input_peek_char (PRIVATE (a_this)->input, a_char); +} + +/** + *Peeks a byte ahead at a given postion in the parser input stream. + *@param a_this the current instance of #CRTknzr. + *@param a_offset the offset of the peeked byte starting from the current + *byte in the parser input stream. + *@param a_byte out parameter. The peeked byte upon + *successfull completion. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_tknzr_peek_byte (CRTknzr * a_this, gulong a_offset, guchar * a_byte) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this) + && PRIVATE (a_this)->input && a_byte, + CR_BAD_PARAM_ERROR); + + if (PRIVATE (a_this)->token_cache) { + cr_input_set_cur_pos (PRIVATE (a_this)->input, + &PRIVATE (a_this)->prev_pos); + cr_token_destroy (PRIVATE (a_this)->token_cache); + PRIVATE (a_this)->token_cache = NULL; + } + + return cr_input_peek_byte (PRIVATE (a_this)->input, + CR_SEEK_CUR, a_offset, a_byte); +} + +/** + *Same as cr_tknzr_peek_byte() but this api returns the byte peeked. + *@param a_this the current instance of #CRTknzr. + *@param a_offset the offset of the peeked byte starting from the current + *byte in the parser input stream. + *@param a_eof out parameter. If not NULL, is set to TRUE if we reached end of + *file, FALE otherwise. If the caller sets it to NULL, this parameter + *is just ignored. + *@return the peeked byte. + */ +guchar +cr_tknzr_peek_byte2 (CRTknzr * a_this, gulong a_offset, gboolean * a_eof) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this) + && PRIVATE (a_this)->input, 0); + + return cr_input_peek_byte2 (PRIVATE (a_this)->input, a_offset, a_eof); +} + +/** + *Gets the number of bytes left in the topmost input stream + *associated to this parser. + *@param a_this the current instance of #CRTknzr + *@return the number of bytes left or -1 in case of error. + */ +glong +cr_tknzr_get_nb_bytes_left (CRTknzr * a_this) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this) + && PRIVATE (a_this)->input, CR_BAD_PARAM_ERROR); + + if (PRIVATE (a_this)->token_cache) { + cr_input_set_cur_pos (PRIVATE (a_this)->input, + &PRIVATE (a_this)->prev_pos); + cr_token_destroy (PRIVATE (a_this)->token_cache); + PRIVATE (a_this)->token_cache = NULL; + } + + return cr_input_get_nb_bytes_left (PRIVATE (a_this)->input); +} + +enum CRStatus +cr_tknzr_get_cur_pos (CRTknzr * a_this, CRInputPos * a_pos) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this) + && PRIVATE (a_this)->input + && a_pos, CR_BAD_PARAM_ERROR); + + if (PRIVATE (a_this)->token_cache) { + cr_input_set_cur_pos (PRIVATE (a_this)->input, + &PRIVATE (a_this)->prev_pos); + cr_token_destroy (PRIVATE (a_this)->token_cache); + PRIVATE (a_this)->token_cache = NULL; + } + + return cr_input_get_cur_pos (PRIVATE (a_this)->input, a_pos); +} + +enum CRStatus +cr_tknzr_get_parsing_location (CRTknzr *a_this, + CRParsingLocation *a_loc) +{ + g_return_val_if_fail (a_this + && PRIVATE (a_this) + && a_loc, + CR_BAD_PARAM_ERROR) ; + + return cr_input_get_parsing_location + (PRIVATE (a_this)->input, a_loc) ; +} + +enum CRStatus +cr_tknzr_get_cur_byte_addr (CRTknzr * a_this, guchar ** a_addr) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this) + && PRIVATE (a_this)->input, CR_BAD_PARAM_ERROR); + if (PRIVATE (a_this)->token_cache) { + cr_input_set_cur_pos (PRIVATE (a_this)->input, + &PRIVATE (a_this)->prev_pos); + cr_token_destroy (PRIVATE (a_this)->token_cache); + PRIVATE (a_this)->token_cache = NULL; + } + + return cr_input_get_cur_byte_addr (PRIVATE (a_this)->input, a_addr); +} + +enum CRStatus +cr_tknzr_seek_index (CRTknzr * a_this, enum CRSeekPos a_origin, gint a_pos) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this) + && PRIVATE (a_this)->input, CR_BAD_PARAM_ERROR); + + if (PRIVATE (a_this)->token_cache) { + cr_input_set_cur_pos (PRIVATE (a_this)->input, + &PRIVATE (a_this)->prev_pos); + cr_token_destroy (PRIVATE (a_this)->token_cache); + PRIVATE (a_this)->token_cache = NULL; + } + + return cr_input_seek_index (PRIVATE (a_this)->input, a_origin, a_pos); +} + +enum CRStatus +cr_tknzr_consume_chars (CRTknzr * a_this, guint32 a_char, glong * a_nb_char) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this) + && PRIVATE (a_this)->input, CR_BAD_PARAM_ERROR); + + if (PRIVATE (a_this)->token_cache) { + cr_input_set_cur_pos (PRIVATE (a_this)->input, + &PRIVATE (a_this)->prev_pos); + cr_token_destroy (PRIVATE (a_this)->token_cache); + PRIVATE (a_this)->token_cache = NULL; + } + + return cr_input_consume_chars (PRIVATE (a_this)->input, + a_char, a_nb_char); +} + +enum CRStatus +cr_tknzr_set_cur_pos (CRTknzr * a_this, CRInputPos * a_pos) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this) + && PRIVATE (a_this)->input, CR_BAD_PARAM_ERROR); + + if (PRIVATE (a_this)->token_cache) { + cr_token_destroy (PRIVATE (a_this)->token_cache); + PRIVATE (a_this)->token_cache = NULL; + } + + return cr_input_set_cur_pos (PRIVATE (a_this)->input, a_pos); +} + +enum CRStatus +cr_tknzr_unget_token (CRTknzr * a_this, CRToken * a_token) +{ + g_return_val_if_fail (a_this && PRIVATE (a_this) + && PRIVATE (a_this)->token_cache == NULL, + CR_BAD_PARAM_ERROR); + + PRIVATE (a_this)->token_cache = a_token; + + return CR_OK; +} + +/** + *Returns the next token of the input stream. + *This method is really central. Each parsing + *method calls it. + *@param a_this the current tokenizer. + *@param a_tk out parameter. The returned token. + *for the sake of mem leak avoidance, *a_tk must + *be NULL. + *@param CR_OK upon successfull completion, an error code + *otherwise. + */ +enum CRStatus +cr_tknzr_get_next_token (CRTknzr * a_this, CRToken ** a_tk) +{ + enum CRStatus status = CR_OK; + CRToken *token = NULL; + CRInputPos init_pos; + guint32 next_char = 0; + guchar next_bytes[4] = { 0 }; + gboolean reached_eof = FALSE; + CRInput *input = NULL; + CRString *str = NULL; + CRRgb *rgb = NULL; + CRParsingLocation location = {0,0,0} ; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && a_tk && *a_tk == NULL + && PRIVATE (a_this)->input, + CR_BAD_PARAM_ERROR); + + if (PRIVATE (a_this)->token_cache) { + *a_tk = PRIVATE (a_this)->token_cache; + PRIVATE (a_this)->token_cache = NULL; + return CR_OK; + } + + RECORD_INITIAL_POS (a_this, &init_pos); + + status = cr_input_get_end_of_file + (PRIVATE (a_this)->input, &reached_eof); + ENSURE_PARSING_COND (status == CR_OK); + + if (reached_eof == TRUE) { + status = CR_END_OF_INPUT_ERROR; + goto error; + } + + input = PRIVATE (a_this)->input; + + PEEK_NEXT_CHAR (a_this, &next_char); + token = cr_token_new (); + ENSURE_PARSING_COND (token); + + switch (next_char) { + case '@': + { + if (BYTE (input, 2, NULL) == 'f' + && BYTE (input, 3, NULL) == 'o' + && BYTE (input, 4, NULL) == 'n' + && BYTE (input, 5, NULL) == 't' + && BYTE (input, 6, NULL) == '-' + && BYTE (input, 7, NULL) == 'f' + && BYTE (input, 8, NULL) == 'a' + && BYTE (input, 9, NULL) == 'c' + && BYTE (input, 10, NULL) == 'e') { + SKIP_CHARS (a_this, 1); + cr_tknzr_get_parsing_location + (a_this, &location) ; + SKIP_CHARS (a_this, 9); + status = cr_token_set_font_face_sym (token); + CHECK_PARSING_STATUS (status, TRUE); + cr_parsing_location_copy (&token->location, + &location) ; + goto done; + } + + if (BYTE (input, 2, NULL) == 'c' + && BYTE (input, 3, NULL) == 'h' + && BYTE (input, 4, NULL) == 'a' + && BYTE (input, 5, NULL) == 'r' + && BYTE (input, 6, NULL) == 's' + && BYTE (input, 7, NULL) == 'e' + && BYTE (input, 8, NULL) == 't') { + SKIP_CHARS (a_this, 1); + cr_tknzr_get_parsing_location + (a_this, &location) ; + SKIP_CHARS (a_this, 7); + status = cr_token_set_charset_sym (token); + CHECK_PARSING_STATUS (status, TRUE); + cr_parsing_location_copy (&token->location, + &location) ; + goto done; + } + + if (BYTE (input, 2, NULL) == 'i' + && BYTE (input, 3, NULL) == 'm' + && BYTE (input, 4, NULL) == 'p' + && BYTE (input, 5, NULL) == 'o' + && BYTE (input, 6, NULL) == 'r' + && BYTE (input, 7, NULL) == 't') { + SKIP_CHARS (a_this, 1); + cr_tknzr_get_parsing_location + (a_this, &location) ; + SKIP_CHARS (a_this, 6); + status = cr_token_set_import_sym (token); + CHECK_PARSING_STATUS (status, TRUE); + cr_parsing_location_copy (&token->location, + &location) ; + goto done; + } + + if (BYTE (input, 2, NULL) == 'm' + && BYTE (input, 3, NULL) == 'e' + && BYTE (input, 4, NULL) == 'd' + && BYTE (input, 5, NULL) == 'i' + && BYTE (input, 6, NULL) == 'a') { + SKIP_CHARS (a_this, 1); + cr_tknzr_get_parsing_location (a_this, + &location) ; + SKIP_CHARS (a_this, 5); + status = cr_token_set_media_sym (token); + CHECK_PARSING_STATUS (status, TRUE); + cr_parsing_location_copy (&token->location, + &location) ; + goto done; + } + + if (BYTE (input, 2, NULL) == 'p' + && BYTE (input, 3, NULL) == 'a' + && BYTE (input, 4, NULL) == 'g' + && BYTE (input, 5, NULL) == 'e') { + SKIP_CHARS (a_this, 1); + cr_tknzr_get_parsing_location (a_this, + &location) ; + SKIP_CHARS (a_this, 4); + status = cr_token_set_page_sym (token); + CHECK_PARSING_STATUS (status, TRUE); + cr_parsing_location_copy (&token->location, + &location) ; + goto done; + } + status = cr_tknzr_parse_atkeyword (a_this, &str); + if (status == CR_OK) { + status = cr_token_set_atkeyword (token, str); + CHECK_PARSING_STATUS (status, TRUE); + if (str) { + cr_parsing_location_copy (&token->location, + &str->location) ; + } + goto done; + } + } + break; + + case 'u': + + if (BYTE (input, 2, NULL) == 'r' + && BYTE (input, 3, NULL) == 'l' + && BYTE (input, 4, NULL) == '(') { + CRString *str = NULL; + + status = cr_tknzr_parse_uri (a_this, &str); + if (status == CR_OK) { + status = cr_token_set_uri (token, str); + CHECK_PARSING_STATUS (status, TRUE); + if (str) { + cr_parsing_location_copy (&token->location, + &str->location) ; + } + goto done; + } + } else { + status = cr_tknzr_parse_ident (a_this, &str); + if (status == CR_OK && str) { + status = cr_token_set_ident (token, str); + CHECK_PARSING_STATUS (status, TRUE); + if (str) { + cr_parsing_location_copy (&token->location, + &str->location) ; + } + goto done; + } + } + break; + + case 'r': + if (BYTE (input, 2, NULL) == 'g' + && BYTE (input, 3, NULL) == 'b' + && BYTE (input, 4, NULL) == '(') { + status = cr_tknzr_parse_rgb (a_this, &rgb); + if (status == CR_OK && rgb) { + status = cr_token_set_rgb (token, rgb); + CHECK_PARSING_STATUS (status, TRUE); + if (rgb) { + cr_parsing_location_copy (&token->location, + &rgb->location) ; + } + rgb = NULL; + goto done; + } + + } else { + status = cr_tknzr_parse_ident (a_this, &str); + if (status == CR_OK) { + status = cr_token_set_ident (token, str); + CHECK_PARSING_STATUS (status, TRUE); + if (str) { + cr_parsing_location_copy (&token->location, + &str->location) ; + } + str = NULL; + goto done; + } + } + break; + + case '<': + if (BYTE (input, 2, NULL) == '-' + && BYTE (input, 3, NULL) == '-') { + SKIP_CHARS (a_this, 1); + cr_tknzr_get_parsing_location (a_this, + &location) ; + SKIP_CHARS (a_this, 2); + status = cr_token_set_cdo (token); + CHECK_PARSING_STATUS (status, TRUE); + cr_parsing_location_copy (&token->location, + &location) ; + goto done; + } + break; + + case '-': + if (BYTE (input, 2, NULL) == '-' + && BYTE (input, 3, NULL) == '>') { + SKIP_CHARS (a_this, 1); + cr_tknzr_get_parsing_location (a_this, + &location) ; + SKIP_CHARS (a_this, 2); + status = cr_token_set_cdc (token); + CHECK_PARSING_STATUS (status, TRUE); + cr_parsing_location_copy (&token->location, + &location) ; + goto done; + } else { + status = cr_tknzr_parse_ident + (a_this, &str); + if (status == CR_OK) { + cr_token_set_ident + (token, str); + if (str) { + cr_parsing_location_copy (&token->location, + &str->location) ; + } + goto done; + } + } + break; + + case '~': + if (BYTE (input, 2, NULL) == '=') { + SKIP_CHARS (a_this, 1); + cr_tknzr_get_parsing_location (a_this, + &location) ; + SKIP_CHARS (a_this, 1); + status = cr_token_set_includes (token); + CHECK_PARSING_STATUS (status, TRUE); + cr_parsing_location_copy (&token->location, + &location) ; + goto done; + } + break; + + case '|': + if (BYTE (input, 2, NULL) == '=') { + SKIP_CHARS (a_this, 1); + cr_tknzr_get_parsing_location (a_this, + &location) ; + SKIP_CHARS (a_this, 1); + status = cr_token_set_dashmatch (token); + CHECK_PARSING_STATUS (status, TRUE); + cr_parsing_location_copy (&token->location, + &location) ; + goto done; + } + break; + + case '/': + if (BYTE (input, 2, NULL) == '*') { + status = cr_tknzr_parse_comment (a_this, &str); + + if (status == CR_OK) { + status = cr_token_set_comment (token, str); + str = NULL; + CHECK_PARSING_STATUS (status, TRUE); + if (str) { + cr_parsing_location_copy (&token->location, + &str->location) ; + } + goto done; + } + } + break ; + + case ';': + SKIP_CHARS (a_this, 1); + cr_tknzr_get_parsing_location (a_this, + &location) ; + status = cr_token_set_semicolon (token); + CHECK_PARSING_STATUS (status, TRUE); + cr_parsing_location_copy (&token->location, + &location) ; + goto done; + + case '{': + SKIP_CHARS (a_this, 1); + cr_tknzr_get_parsing_location (a_this, + &location) ; + status = cr_token_set_cbo (token); + CHECK_PARSING_STATUS (status, TRUE); + cr_tknzr_get_parsing_location (a_this, + &location) ; + goto done; + + case '}': + SKIP_CHARS (a_this, 1); + cr_tknzr_get_parsing_location (a_this, + &location) ; + status = cr_token_set_cbc (token); + CHECK_PARSING_STATUS (status, TRUE); + cr_parsing_location_copy (&token->location, + &location) ; + goto done; + + case '(': + SKIP_CHARS (a_this, 1); + cr_tknzr_get_parsing_location (a_this, + &location) ; + status = cr_token_set_po (token); + CHECK_PARSING_STATUS (status, TRUE); + cr_parsing_location_copy (&token->location, + &location) ; + goto done; + + case ')': + SKIP_CHARS (a_this, 1); + cr_tknzr_get_parsing_location (a_this, + &location) ; + status = cr_token_set_pc (token); + CHECK_PARSING_STATUS (status, TRUE); + cr_parsing_location_copy (&token->location, + &location) ; + goto done; + + case '[': + SKIP_CHARS (a_this, 1); + cr_tknzr_get_parsing_location (a_this, + &location) ; + status = cr_token_set_bo (token); + CHECK_PARSING_STATUS (status, TRUE); + cr_parsing_location_copy (&token->location, + &location) ; + goto done; + + case ']': + SKIP_CHARS (a_this, 1); + cr_tknzr_get_parsing_location (a_this, + &location) ; + status = cr_token_set_bc (token); + CHECK_PARSING_STATUS (status, TRUE); + cr_parsing_location_copy (&token->location, + &location) ; + goto done; + + case ' ': + case '\t': + case '\n': + case '\f': + case '\r': + { + guchar *start = NULL, + *end = NULL; + + status = cr_tknzr_parse_w (a_this, &start, + &end, &location); + if (status == CR_OK) { + status = cr_token_set_s (token); + CHECK_PARSING_STATUS (status, TRUE); + cr_tknzr_get_parsing_location (a_this, + &location) ; + goto done; + } + } + break; + + case '#': + { + status = cr_tknzr_parse_hash (a_this, &str); + if (status == CR_OK && str) { + status = cr_token_set_hash (token, str); + CHECK_PARSING_STATUS (status, TRUE); + if (str) { + cr_parsing_location_copy (&token->location, + &str->location) ; + } + str = NULL; + goto done; + } + } + break; + + case '\'': + case '"': + status = cr_tknzr_parse_string (a_this, &str); + if (status == CR_OK && str) { + status = cr_token_set_string (token, str); + CHECK_PARSING_STATUS (status, TRUE); + if (str) { + cr_parsing_location_copy (&token->location, + &str->location) ; + } + str = NULL; + goto done; + } + break; + + case '!': + status = cr_tknzr_parse_important (a_this, &location); + if (status == CR_OK) { + status = cr_token_set_important_sym (token); + CHECK_PARSING_STATUS (status, TRUE); + cr_parsing_location_copy (&token->location, + &location) ; + goto done; + } + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '.': + { + CRNum *num = NULL; + + status = cr_tknzr_parse_num (a_this, &num); + if (status == CR_OK && num) { + next_bytes[0] = BYTE (input, 1, NULL); + next_bytes[1] = BYTE (input, 2, NULL); + next_bytes[2] = BYTE (input, 3, NULL); + next_bytes[3] = BYTE (input, 3, NULL); + + if (next_bytes[0] == 'e' + && next_bytes[1] == 'm') { + num->type = NUM_LENGTH_EM; + status = cr_token_set_ems (token, + num); + num = NULL; + SKIP_CHARS (a_this, 2); + } else if (next_bytes[0] == 'e' + && next_bytes[1] == 'x') { + num->type = NUM_LENGTH_EX; + status = cr_token_set_exs (token, + num); + num = NULL; + SKIP_CHARS (a_this, 2); + } else if (next_bytes[0] == 'p' + && next_bytes[1] == 'x') { + num->type = NUM_LENGTH_PX; + status = cr_token_set_length + (token, num, LENGTH_PX_ET); + num = NULL; + SKIP_CHARS (a_this, 2); + } else if (next_bytes[0] == 'c' + && next_bytes[1] == 'm') { + num->type = NUM_LENGTH_CM; + status = cr_token_set_length + (token, num, LENGTH_CM_ET); + num = NULL; + SKIP_CHARS (a_this, 2); + } else if (next_bytes[0] == 'm' + && next_bytes[1] == 'm') { + num->type = NUM_LENGTH_MM; + status = cr_token_set_length + (token, num, LENGTH_MM_ET); + num = NULL; + SKIP_CHARS (a_this, 2); + } else if (next_bytes[0] == 'i' + && next_bytes[1] == 'n') { + num->type = NUM_LENGTH_IN; + status = cr_token_set_length + (token, num, LENGTH_IN_ET); + num = NULL; + SKIP_CHARS (a_this, 2); + } else if (next_bytes[0] == 'p' + && next_bytes[1] == 't') { + num->type = NUM_LENGTH_PT; + status = cr_token_set_length + (token, num, LENGTH_PT_ET); + num = NULL; + SKIP_CHARS (a_this, 2); + } else if (next_bytes[0] == 'p' + && next_bytes[1] == 'c') { + num->type = NUM_LENGTH_PC; + status = cr_token_set_length + (token, num, LENGTH_PC_ET); + num = NULL; + SKIP_CHARS (a_this, 2); + } else if (next_bytes[0] == 'd' + && next_bytes[1] == 'e' + && next_bytes[2] == 'g') { + num->type = NUM_ANGLE_DEG; + status = cr_token_set_angle + (token, num, ANGLE_DEG_ET); + num = NULL; + SKIP_CHARS (a_this, 3); + } else if (next_bytes[0] == 'r' + && next_bytes[1] == 'a' + && next_bytes[2] == 'd') { + num->type = NUM_ANGLE_RAD; + status = cr_token_set_angle + (token, num, ANGLE_RAD_ET); + num = NULL; + SKIP_CHARS (a_this, 3); + } else if (next_bytes[0] == 'g' + && next_bytes[1] == 'r' + && next_bytes[2] == 'a' + && next_bytes[3] == 'd') { + num->type = NUM_ANGLE_GRAD; + status = cr_token_set_angle + (token, num, ANGLE_GRAD_ET); + num = NULL; + SKIP_CHARS (a_this, 4); + } else if (next_bytes[0] == 'm' + && next_bytes[1] == 's') { + num->type = NUM_TIME_MS; + status = cr_token_set_time + (token, num, TIME_MS_ET); + num = NULL; + SKIP_CHARS (a_this, 2); + } else if (next_bytes[0] == 's') { + num->type = NUM_TIME_S; + status = cr_token_set_time + (token, num, TIME_S_ET); + num = NULL; + SKIP_CHARS (a_this, 1); + } else if (next_bytes[0] == 'H' + && next_bytes[1] == 'z') { + num->type = NUM_FREQ_HZ; + status = cr_token_set_freq + (token, num, FREQ_HZ_ET); + num = NULL; + SKIP_CHARS (a_this, 2); + } else if (next_bytes[0] == 'k' + && next_bytes[1] == 'H' + && next_bytes[2] == 'z') { + num->type = NUM_FREQ_KHZ; + status = cr_token_set_freq + (token, num, FREQ_KHZ_ET); + num = NULL; + SKIP_CHARS (a_this, 3); + } else if (next_bytes[0] == '%') { + num->type = NUM_PERCENTAGE; + status = cr_token_set_percentage + (token, num); + num = NULL; + SKIP_CHARS (a_this, 1); + } else { + status = cr_tknzr_parse_ident (a_this, + &str); + if (status == CR_OK && str) { + num->type = NUM_UNKNOWN_TYPE; + status = cr_token_set_dimen + (token, num, str); + num = NULL; + CHECK_PARSING_STATUS (status, + TRUE); + str = NULL; + } else { + status = cr_token_set_number + (token, num); + num = NULL; + CHECK_PARSING_STATUS (status, CR_OK); + str = NULL; + } + } + if (token && token->u.num) { + cr_parsing_location_copy (&token->location, + &token->u.num->location) ; + } else { + status = CR_ERROR ; + } + goto done ; + } + } + break; + + default: + /*process the fallback cases here */ + + if (next_char == '\\' + || (cr_utils_is_nonascii (next_bytes[0]) == TRUE) + || ((next_char >= 'a') && (next_char <= 'z')) + || ((next_char >= 'A') && (next_char <= 'Z'))) { + status = cr_tknzr_parse_ident (a_this, &str); + if (status == CR_OK && str) { + guint32 next_c = 0; + + status = cr_input_peek_char + (PRIVATE (a_this)->input, &next_c); + + if (status == CR_OK && next_c == '(') { + + SKIP_CHARS (a_this, 1); + status = cr_token_set_function + (token, str); + CHECK_PARSING_STATUS (status, TRUE); + /*ownership is transfered + *to token by cr_token_set_function. + */ + if (str) { + cr_parsing_location_copy (&token->location, + &str->location) ; + } + str = NULL; + } else { + status = cr_token_set_ident (token, + str); + CHECK_PARSING_STATUS (status, TRUE); + if (str) { + cr_parsing_location_copy (&token->location, + &str->location) ; + } + str = NULL; + } + goto done; + } else { + if (str) { + cr_string_destroy (str); + str = NULL; + } + } + } + break; + } + + READ_NEXT_CHAR (a_this, &next_char); + cr_tknzr_get_parsing_location (a_this, + &location) ; + status = cr_token_set_delim (token, next_char); + CHECK_PARSING_STATUS (status, TRUE); + cr_parsing_location_copy (&token->location, + &location) ; + done: + + if (status == CR_OK && token) { + *a_tk = token; + /* + *store the previous position input stream pos. + */ + memmove (&PRIVATE (a_this)->prev_pos, + &init_pos, sizeof (CRInputPos)); + return CR_OK; + } + + error: + if (token) { + cr_token_destroy (token); + token = NULL; + } + + if (str) { + cr_string_destroy (str); + str = NULL; + } + cr_tknzr_set_cur_pos (a_this, &init_pos); + return status; + +} + +enum CRStatus +cr_tknzr_parse_token (CRTknzr * a_this, enum CRTokenType a_type, + enum CRTokenExtraType a_et, gpointer a_res, + gpointer a_extra_res) +{ + enum CRStatus status = CR_OK; + CRToken *token = NULL; + + g_return_val_if_fail (a_this && PRIVATE (a_this) + && PRIVATE (a_this)->input + && a_res, CR_BAD_PARAM_ERROR); + + status = cr_tknzr_get_next_token (a_this, &token); + if (status != CR_OK) + return status; + if (token == NULL) + return CR_PARSING_ERROR; + + if (token->type == a_type) { + switch (a_type) { + case NO_TK: + case S_TK: + case CDO_TK: + case CDC_TK: + case INCLUDES_TK: + case DASHMATCH_TK: + case IMPORT_SYM_TK: + case PAGE_SYM_TK: + case MEDIA_SYM_TK: + case FONT_FACE_SYM_TK: + case CHARSET_SYM_TK: + case IMPORTANT_SYM_TK: + status = CR_OK; + break; + + case STRING_TK: + case IDENT_TK: + case HASH_TK: + case ATKEYWORD_TK: + case FUNCTION_TK: + case COMMENT_TK: + case URI_TK: + *((CRString **) a_res) = token->u.str; + token->u.str = NULL; + status = CR_OK; + break; + + case EMS_TK: + case EXS_TK: + case PERCENTAGE_TK: + case NUMBER_TK: + *((CRNum **) a_res) = token->u.num; + token->u.num = NULL; + status = CR_OK; + break; + + case LENGTH_TK: + case ANGLE_TK: + case TIME_TK: + case FREQ_TK: + if (token->extra_type == a_et) { + *((CRNum **) a_res) = token->u.num; + token->u.num = NULL; + status = CR_OK; + } + break; + + case DIMEN_TK: + *((CRNum **) a_res) = token->u.num; + if (a_extra_res == NULL) { + status = CR_BAD_PARAM_ERROR; + goto error; + } + + *((CRString **) a_extra_res) = token->dimen; + token->u.num = NULL; + token->dimen = NULL; + status = CR_OK; + break; + + case DELIM_TK: + *((guint32 *) a_res) = token->u.unichar; + status = CR_OK; + break; + + case UNICODERANGE_TK: + default: + status = CR_PARSING_ERROR; + break; + } + + cr_token_destroy (token); + token = NULL; + } else { + cr_tknzr_unget_token (a_this, token); + token = NULL; + status = CR_PARSING_ERROR; + } + + return status; + + error: + + if (token) { + cr_tknzr_unget_token (a_this, token); + token = NULL; + } + + return status; +} + +void +cr_tknzr_destroy (CRTknzr * a_this) +{ + g_return_if_fail (a_this); + + if (PRIVATE (a_this) && PRIVATE (a_this)->input) { + if (cr_input_unref (PRIVATE (a_this)->input) + == TRUE) { + PRIVATE (a_this)->input = NULL; + } + } + + if (PRIVATE (a_this)->token_cache) { + cr_token_destroy (PRIVATE (a_this)->token_cache); + PRIVATE (a_this)->token_cache = NULL; + } + + if (PRIVATE (a_this)) { + g_free (PRIVATE (a_this)); + PRIVATE (a_this) = NULL; + } + + g_free (a_this); +} diff --git a/src/libcroco/cr-tknzr.h b/src/libcroco/cr-tknzr.h new file mode 100644 index 000000000..13985b30e --- /dev/null +++ b/src/libcroco/cr-tknzr.h @@ -0,0 +1,115 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli + * See COPYRIGHTS file for coypyright information. + */ + +/** + *@file + *The declaration of the #CRTknzr (tokenizer) + *class. + */ + +#ifndef __CR_TKNZR_H__ +#define __CR_TKNZR_H__ + +#include "cr-utils.h" +#include "cr-input.h" +#include "cr-token.h" + +G_BEGIN_DECLS + + +typedef struct _CRTknzr CRTknzr ; +typedef struct _CRTknzrPriv CRTknzrPriv ; + +/** + *The tokenizer is the class that knows + *about all the css token. Its main job is + *to return the next token found in the character + *input stream. + */ +struct _CRTknzr +{ + /*the private data of the tokenizer.*/ + CRTknzrPriv *priv ; +} ; + +CRTknzr * cr_tknzr_new (CRInput *a_input) ; + +CRTknzr * cr_tknzr_new_from_uri (const guchar *a_file_uri, + enum CREncoding a_enc) ; + +CRTknzr * cr_tknzr_new_from_buf (guchar *a_buf, gulong a_len, + enum CREncoding a_enc, + gboolean a_free_at_destroy) ; + +gboolean cr_tknzr_unref (CRTknzr *a_this) ; + +void cr_tknzr_ref (CRTknzr *a_this) ; + +enum CRStatus cr_tknzr_read_byte (CRTknzr *a_this, guchar *a_byte) ; + +enum CRStatus cr_tknzr_read_char (CRTknzr *a_this, guint32 *a_char); + +enum CRStatus cr_tknzr_peek_char (CRTknzr *a_this, guint32 *a_char) ; + +enum CRStatus cr_tknzr_peek_byte (CRTknzr *a_this, gulong a_offset, + guchar *a_byte) ; + +guchar cr_tknzr_peek_byte2 (CRTknzr *a_this, gulong a_offset, + gboolean *a_eof) ; + +enum CRStatus cr_tknzr_set_cur_pos (CRTknzr *a_this, CRInputPos *a_pos) ; + +glong cr_tknzr_get_nb_bytes_left (CRTknzr *a_this) ; + +enum CRStatus cr_tknzr_get_cur_pos (CRTknzr *a_this, CRInputPos *a_pos) ; + +enum CRStatus cr_tknzr_get_parsing_location (CRTknzr *a_this, + CRParsingLocation *a_loc) ; + +enum CRStatus cr_tknzr_seek_index (CRTknzr *a_this, + enum CRSeekPos a_origin, + gint a_pos) ; + +enum CRStatus cr_tknzr_get_cur_byte_addr (CRTknzr *a_this, guchar **a_addr) ; + + +enum CRStatus cr_tknzr_consume_chars (CRTknzr *a_this, guint32 a_char, + glong *a_nb_char) ; + +enum CRStatus cr_tknzr_get_next_token (CRTknzr *a_this, CRToken ** a_tk) ; + +enum CRStatus cr_tknzr_unget_token (CRTknzr *a_this, CRToken *a_token) ; + + +enum CRStatus cr_tknzr_parse_token (CRTknzr *a_this, enum CRTokenType a_type, + enum CRTokenExtraType a_et, gpointer a_res, + gpointer a_extra_res) ; +enum CRStatus cr_tknzr_set_input (CRTknzr *a_this, CRInput *a_input) ; + +enum CRStatus cr_tknzr_get_input (CRTknzr *a_this, CRInput **a_input) ; + +void cr_tknzr_destroy (CRTknzr *a_this) ; + +G_END_DECLS + +#endif /*__CR_TKZNR_H__*/ diff --git a/src/libcroco/cr-token.c b/src/libcroco/cr-token.c new file mode 100644 index 000000000..d2bb492ef --- /dev/null +++ b/src/libcroco/cr-token.c @@ -0,0 +1,635 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli + * see COPYRIGHTS file for copyright information. + */ + +/** + *@file + *The definition of the #CRToken class. + *Abstracts a css2 token. + */ +#include +#include "cr-token.h" + +/* + *TODO: write a CRToken::to_string() method. + */ + +/** + *Frees the attributes of the current instance + *of #CRtoken. + *@param a_this the current instance of #CRToken. + */ +static void +cr_token_clear (CRToken * a_this) +{ + g_return_if_fail (a_this); + + switch (a_this->type) { + case S_TK: + case CDO_TK: + case INCLUDES_TK: + case DASHMATCH_TK: + case PAGE_SYM_TK: + case MEDIA_SYM_TK: + case FONT_FACE_SYM_TK: + case CHARSET_SYM_TK: + case IMPORT_SYM_TK: + case IMPORTANT_SYM_TK: + case SEMICOLON_TK: + case NO_TK: + case DELIM_TK: + case CBO_TK: + case CBC_TK: + case BO_TK: + case BC_TK: + break; + + case STRING_TK: + case IDENT_TK: + case HASH_TK: + case URI_TK: + case FUNCTION_TK: + case COMMENT_TK: + case ATKEYWORD_TK: + if (a_this->u.str) { + cr_string_destroy (a_this->u.str); + a_this->u.str = NULL; + } + break; + + case EMS_TK: + case EXS_TK: + case LENGTH_TK: + case ANGLE_TK: + case TIME_TK: + case FREQ_TK: + case PERCENTAGE_TK: + case NUMBER_TK: + case PO_TK: + case PC_TK: + if (a_this->u.num) { + cr_num_destroy (a_this->u.num); + a_this->u.num = NULL; + } + break; + + case DIMEN_TK: + if (a_this->u.num) { + cr_num_destroy (a_this->u.num); + a_this->u.num = NULL; + } + + if (a_this->dimen) { + cr_string_destroy (a_this->dimen); + a_this->dimen = NULL; + } + + break; + + case RGB_TK: + if (a_this->u.rgb) { + cr_rgb_destroy (a_this->u.rgb) ; + a_this->u.rgb = NULL ; + } + break ; + + case UNICODERANGE_TK: + /*not supported yet. */ + break; + + default: + cr_utils_trace_info ("I don't know how to clear this token\n") ; + break; + } + + a_this->type = NO_TK; +} + +/** + *Default constructor of + *the #CRToken class. + *@return the newly built instance of #CRToken. + */ +CRToken * +cr_token_new (void) +{ + CRToken *result = NULL; + + result = g_try_malloc (sizeof (CRToken)); + + if (result == NULL) { + cr_utils_trace_info ("Out of memory"); + return NULL; + } + + memset (result, 0, sizeof (CRToken)); + + return result; +} + +/** + *Sets the type of curren instance of + *#CRToken to 'S_TK' (S in the css2 spec) + *@param a_this the current instance of #CRToken. + *@return CR_OK upon successfull completion, an error + *code otherwise. + */ +enum CRStatus +cr_token_set_s (CRToken * a_this) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + + a_this->type = S_TK; + + return CR_OK; +} + +/** + *Sets the type of the current instance of + *#CRToken to 'CDO_TK' (CDO as said by the css2 spec) + *@param a_this the current instance of #CRToken. + *@return CR_OK upon successfull completion, an error + *code otherwise. + */ +enum CRStatus +cr_token_set_cdo (CRToken * a_this) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + + a_this->type = CDO_TK; + + return CR_OK; +} + +/** + *Sets the type of the current token to + *CDC_TK (CDC as said by the css2 spec). + *@param a_this the current instance of #CRToken. + *@return CR_OK upon successfull completion, an error + *code otherwise. + */ +enum CRStatus +cr_token_set_cdc (CRToken * a_this) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + + a_this->type = CDC_TK; + + return CR_OK; +} + +/** + *Sets the type of the current instance of + *#CRToken to INCLUDES_TK (INCLUDES as said by the css2 spec). + *@param a_this the current instance of #CRToken. + *@return CR_OK upon successfull completion, an error + *code otherwise. + */ +enum CRStatus +cr_token_set_includes (CRToken * a_this) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + + a_this->type = INCLUDES_TK; + + return CR_OK; +} + +/** + *Sets the type of the current instance of + *#CRToken to DASHMATCH_TK (DASHMATCH as said by the css2 spec). + *@param a_this the current instance of #CRToken. + *@return CR_OK upon successfull completion, an error + *code otherwise. + */ +enum CRStatus +cr_token_set_dashmatch (CRToken * a_this) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + + a_this->type = DASHMATCH_TK; + + return CR_OK; +} + +enum CRStatus +cr_token_set_comment (CRToken * a_this, CRString * a_str) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + a_this->type = COMMENT_TK; + a_this->u.str = a_str ; + return CR_OK; +} + +enum CRStatus +cr_token_set_string (CRToken * a_this, CRString * a_str) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + + a_this->type = STRING_TK; + + a_this->u.str = a_str ; + + return CR_OK; +} + +enum CRStatus +cr_token_set_ident (CRToken * a_this, CRString * a_ident) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + a_this->type = IDENT_TK; + a_this->u.str = a_ident; + return CR_OK; +} + + +enum CRStatus +cr_token_set_function (CRToken * a_this, CRString * a_fun_name) +{ + g_return_val_if_fail (a_this, + CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + a_this->type = FUNCTION_TK; + a_this->u.str = a_fun_name; + return CR_OK; +} + +enum CRStatus +cr_token_set_hash (CRToken * a_this, CRString * a_hash) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + a_this->type = HASH_TK; + a_this->u.str = a_hash; + + return CR_OK; +} + +enum CRStatus +cr_token_set_rgb (CRToken * a_this, CRRgb * a_rgb) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + a_this->type = RGB_TK; + a_this->u.rgb = a_rgb; + + return CR_OK; +} + +enum CRStatus +cr_token_set_import_sym (CRToken * a_this) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + + a_this->type = IMPORT_SYM_TK; + + return CR_OK; +} + +enum CRStatus +cr_token_set_page_sym (CRToken * a_this) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + + a_this->type = PAGE_SYM_TK; + + return CR_OK; +} + +enum CRStatus +cr_token_set_media_sym (CRToken * a_this) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + + a_this->type = MEDIA_SYM_TK; + + return CR_OK; +} + +enum CRStatus +cr_token_set_font_face_sym (CRToken * a_this) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + a_this->type = FONT_FACE_SYM_TK; + + return CR_OK; +} + +enum CRStatus +cr_token_set_charset_sym (CRToken * a_this) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + a_this->type = CHARSET_SYM_TK; + + return CR_OK; +} + +enum CRStatus +cr_token_set_atkeyword (CRToken * a_this, CRString * a_atname) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + a_this->type = ATKEYWORD_TK; + a_this->u.str = a_atname; + return CR_OK; +} + +enum CRStatus +cr_token_set_important_sym (CRToken * a_this) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + cr_token_clear (a_this); + a_this->type = IMPORTANT_SYM_TK; + return CR_OK; +} + +enum CRStatus +cr_token_set_ems (CRToken * a_this, CRNum * a_num) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + cr_token_clear (a_this); + a_this->type = EMS_TK; + a_this->u.num = a_num; + return CR_OK; +} + +enum CRStatus +cr_token_set_exs (CRToken * a_this, CRNum * a_num) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + cr_token_clear (a_this); + a_this->type = EXS_TK; + a_this->u.num = a_num; + return CR_OK; +} + +enum CRStatus +cr_token_set_length (CRToken * a_this, CRNum * a_num, + enum CRTokenExtraType a_et) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + + a_this->type = LENGTH_TK; + a_this->extra_type = a_et; + a_this->u.num = a_num; + + return CR_OK; +} + +enum CRStatus +cr_token_set_angle (CRToken * a_this, CRNum * a_num, + enum CRTokenExtraType a_et) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + + a_this->type = ANGLE_TK; + a_this->extra_type = a_et; + a_this->u.num = a_num; + + return CR_OK; +} + +enum CRStatus +cr_token_set_time (CRToken * a_this, CRNum * a_num, + enum CRTokenExtraType a_et) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + + a_this->type = TIME_TK; + a_this->extra_type = a_et; + a_this->u.num = a_num; + + return CR_OK; +} + +enum CRStatus +cr_token_set_freq (CRToken * a_this, CRNum * a_num, + enum CRTokenExtraType a_et) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + + a_this->type = FREQ_TK; + a_this->extra_type = a_et; + a_this->u.num = a_num; + + return CR_OK; +} + +enum CRStatus +cr_token_set_dimen (CRToken * a_this, CRNum * a_num, + CRString * a_dim) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + cr_token_clear (a_this); + a_this->type = DIMEN_TK; + a_this->u.num = a_num; + a_this->dimen = a_dim; + return CR_OK; + +} + +enum CRStatus +cr_token_set_percentage (CRToken * a_this, CRNum * a_num) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + + a_this->type = PERCENTAGE_TK; + a_this->u.num = a_num; + + return CR_OK; +} + +enum CRStatus +cr_token_set_number (CRToken * a_this, CRNum * a_num) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + + a_this->type = NUMBER_TK; + a_this->u.num = a_num; + return CR_OK; +} + +enum CRStatus +cr_token_set_uri (CRToken * a_this, CRString * a_uri) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + + a_this->type = URI_TK; + a_this->u.str = a_uri; + + return CR_OK; +} + +enum CRStatus +cr_token_set_delim (CRToken * a_this, guint32 a_char) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + + a_this->type = DELIM_TK; + a_this->u.unichar = a_char; + + return CR_OK; +} + +enum CRStatus +cr_token_set_semicolon (CRToken * a_this) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + + a_this->type = SEMICOLON_TK; + + return CR_OK; +} + +enum CRStatus +cr_token_set_cbo (CRToken * a_this) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + + a_this->type = CBO_TK; + + return CR_OK; +} + +enum CRStatus +cr_token_set_cbc (CRToken * a_this) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + + a_this->type = CBC_TK; + + return CR_OK; +} + +enum CRStatus +cr_token_set_po (CRToken * a_this) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + + a_this->type = PO_TK; + + return CR_OK; +} + +enum CRStatus +cr_token_set_pc (CRToken * a_this) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + + a_this->type = PC_TK; + + return CR_OK; +} + +enum CRStatus +cr_token_set_bo (CRToken * a_this) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + + a_this->type = BO_TK; + + return CR_OK; +} + +enum CRStatus +cr_token_set_bc (CRToken * a_this) +{ + g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR); + + cr_token_clear (a_this); + + a_this->type = BC_TK; + + return CR_OK; +} + +/** + *The destructor of the #CRToken class. + *@param a_this the current instance of #CRToken. + */ +void +cr_token_destroy (CRToken * a_this) +{ + g_return_if_fail (a_this); + + cr_token_clear (a_this); + + g_free (a_this); +} diff --git a/src/libcroco/cr-token.h b/src/libcroco/cr-token.h new file mode 100644 index 000000000..f1257b7a8 --- /dev/null +++ b/src/libcroco/cr-token.h @@ -0,0 +1,212 @@ +/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli + * See COPYRIGHTS file for copyright information. + */ + +#ifndef __CR_TOKEN_H__ +#define __CR_TOKEN_H__ + +#include "cr-utils.h" +#include "cr-input.h" +#include "cr-num.h" +#include "cr-rgb.h" +#include "cr-string.h" +#include "cr-parsing-location.h" + +G_BEGIN_DECLS + +enum CRTokenType +{ + NO_TK, + S_TK, + CDO_TK, + CDC_TK, + INCLUDES_TK, + DASHMATCH_TK, + COMMENT_TK, + STRING_TK, + IDENT_TK, + HASH_TK, + IMPORT_SYM_TK, + PAGE_SYM_TK, + MEDIA_SYM_TK, + FONT_FACE_SYM_TK, + CHARSET_SYM_TK, + ATKEYWORD_TK, + IMPORTANT_SYM_TK, + EMS_TK, + EXS_TK, + LENGTH_TK, + ANGLE_TK, + TIME_TK, + FREQ_TK, + DIMEN_TK, + PERCENTAGE_TK, + NUMBER_TK, + RGB_TK, + URI_TK, + FUNCTION_TK, + UNICODERANGE_TK, + SEMICOLON_TK, + CBO_TK, /*opening curly bracket*/ + CBC_TK, /*closing curly bracket*/ + PO_TK, /*opening parenthesis*/ + PC_TK, /*closing parenthesis*/ + BO_TK, /*opening bracket*/ + BC_TK, /*closing bracket*/ + DELIM_TK +} ; + +enum CRTokenExtraType +{ + NO_ET = 0, + LENGTH_PX_ET, + LENGTH_CM_ET, + LENGTH_MM_ET, + LENGTH_IN_ET, + LENGTH_PT_ET, + LENGTH_PC_ET, + ANGLE_DEG_ET, + ANGLE_RAD_ET, + ANGLE_GRAD_ET, + TIME_MS_ET, + TIME_S_ET, + FREQ_HZ_ET, + FREQ_KHZ_ET +} ; + +typedef struct _CRToken CRToken ; + +/** + *This class abstracts a css2 token. + */ +struct _CRToken +{ + enum CRTokenType type ; + enum CRTokenExtraType extra_type ; + CRInputPos pos ; + + union + { + CRString *str ; + CRRgb *rgb ; + CRNum *num ; + guint32 unichar ; + } u ; + + CRString * dimen ; + CRParsingLocation location ; +} ; + +CRToken* cr_token_new (void) ; + +enum CRStatus cr_token_set_s (CRToken *a_this) ; + +enum CRStatus cr_token_set_cdo (CRToken *a_this) ; + +enum CRStatus cr_token_set_cdc (CRToken *a_this) ; + +enum CRStatus cr_token_set_includes (CRToken *a_this) ; + +enum CRStatus cr_token_set_dashmatch (CRToken *a_this) ; + +enum CRStatus cr_token_set_comment (CRToken *a_this, CRString *a_str) ; + +enum CRStatus cr_token_set_string (CRToken *a_this, CRString *a_str) ; + +enum CRStatus cr_token_set_ident (CRToken *a_this, CRString * a_ident) ; + +enum CRStatus cr_token_set_hash (CRToken *a_this, CRString *a_hash) ; + +enum CRStatus cr_token_set_rgb (CRToken *a_this, CRRgb *a_rgb) ; + +enum CRStatus cr_token_set_import_sym (CRToken *a_this) ; + +enum CRStatus cr_token_set_page_sym (CRToken *a_this) ; + +enum CRStatus cr_token_set_media_sym (CRToken *a_this) ; + +enum CRStatus cr_token_set_font_face_sym (CRToken *a_this) ; + +enum CRStatus cr_token_set_charset_sym (CRToken *a_this) ; + +enum CRStatus cr_token_set_atkeyword (CRToken *a_this, CRString *a_atname) ; + +enum CRStatus cr_token_set_important_sym (CRToken *a_this) ; + +enum CRStatus cr_token_set_ems (CRToken *a_this, CRNum *a_num) ; + +enum CRStatus cr_token_set_exs (CRToken *a_this, CRNum *a_num) ; + +enum CRStatus cr_token_set_length (CRToken *a_this, CRNum *a_num, + enum CRTokenExtraType a_et) ; + +enum CRStatus cr_token_set_angle (CRToken *a_this, CRNum *a_num, + enum CRTokenExtraType a_et) ; + +enum CRStatus cr_token_set_time (CRToken *a_this, CRNum *a_num, + enum CRTokenExtraType a_et) ; + +enum CRStatus cr_token_set_freq (CRToken *a_this, CRNum *a_num, + enum CRTokenExtraType a_et) ; + +enum CRStatus cr_token_set_dimen (CRToken *a_this, CRNum *a_num, + CRString *a_dim) ; + +enum CRStatus cr_token_set_percentage (CRToken *a_this, CRNum *a_num) ; + +enum CRStatus cr_token_set_number (CRToken *a_this, CRNum *a_num) ; + +enum CRStatus cr_token_set_uri (CRToken *a_this, CRString *a_uri) ; + +enum CRStatus cr_token_set_function (CRToken *a_this, + CRString *a_fun_name) ; + +enum CRStatus cr_token_set_bc (CRToken *a_this) ; + +enum CRStatus cr_token_set_bo (CRToken *a_this) ; + +enum CRStatus cr_token_set_po (CRToken *a_this) ; + +enum CRStatus cr_token_set_pc (CRToken *a_this) ; + +enum CRStatus cr_token_set_cbc (CRToken *a_this) ; + +enum CRStatus cr_token_set_cbo (CRToken *a_this) ; + +enum CRStatus cr_token_set_semicolon (CRToken *a_this) ; + +enum CRStatus cr_token_set_delim (CRToken *a_this, guint32 a_char) ; + + +/* + enum CRStatus + cr_token_set_unicoderange (CRToken *a_this, + CRUnicodeRange *a_range) ; +*/ + +void +cr_token_destroy (CRToken *a_this) ; + + +G_END_DECLS + +#endif /*__CR_TOKEN_H__*/ diff --git a/src/libcroco/cr-utils.c b/src/libcroco/cr-utils.c new file mode 100644 index 000000000..487cf4b95 --- /dev/null +++ b/src/libcroco/cr-utils.c @@ -0,0 +1,1343 @@ +/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli + * See COPYRIGHTS file for copyright information. + */ + +#include "cr-utils.h" +#include "cr-string.h" + +/** + *@file: + *Some misc utility functions used + *in the libcroco. + *Note that troughout this file I will + *refer to the CSS SPECIFICATIONS DOCUMENTATION + *written by the w3c guys. You can find that document + *at http://www.w3.org/TR/REC-CSS2/ . + */ + +/**************************** + *Encoding transformations and + *encoding helpers + ****************************/ + +/* + *Here is the correspondance between the ucs-4 charactere codes + *and there matching utf-8 encoding pattern as dscribed by RFC 2279: + * + *UCS-4 range (hex.) UTF-8 octet sequence (binary) + *------------------ ----------------------------- + *0000 0000-0000 007F 0xxxxxxx + *0000 0080-0000 07FF 110xxxxx 10xxxxxx + *0000 0800-0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx + *0001 0000-001F FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + *0020 0000-03FF FFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + *0400 0000-7FFF FFFF 1111110x 10xxxxxx ... 10xxxxxx + */ + +/** + *Given an utf8 string buffer, calculates + *the length of this string if it was encoded + *in ucs4. + *@param a_in_start a pointer to the begining of + *the input utf8 string. + *@param a_in_end a pointre to the end of the input + *utf8 string (points to the last byte of the buffer) + *@param a_len out parameter the calculated length. + *@return CR_OK upon succesfull completion, an error code + *otherwise. + */ +enum CRStatus +cr_utils_utf8_str_len_as_ucs4 (const guchar * a_in_start, + const guchar * a_in_end, gulong * a_len) +{ + guchar *byte_ptr = NULL; + gint len = 0; + + /* + *to store the final decoded + *unicode char + */ + guint c = 0; + + g_return_val_if_fail (a_in_start && a_in_end && a_len, + CR_BAD_PARAM_ERROR); + *a_len = 0; + + for (byte_ptr = (guchar *) a_in_start; + byte_ptr <= a_in_end; byte_ptr++) { + gint nb_bytes_2_decode = 0; + + if (*byte_ptr <= 0x7F) { + /* + *7 bits long char + *encoded over 1 byte: + * 0xxx xxxx + */ + c = *byte_ptr; + nb_bytes_2_decode = 1; + + } else if ((*byte_ptr & 0xE0) == 0xC0) { + /* + *up to 11 bits long char. + *encoded over 2 bytes: + *110x xxxx 10xx xxxx + */ + c = *byte_ptr & 0x1F; + nb_bytes_2_decode = 2; + + } else if ((*byte_ptr & 0xF0) == 0xE0) { + /* + *up to 16 bit long char + *encoded over 3 bytes: + *1110 xxxx 10xx xxxx 10xx xxxx + */ + c = *byte_ptr & 0x0F; + nb_bytes_2_decode = 3; + + } else if ((*byte_ptr & 0xF8) == 0xF0) { + /* + *up to 21 bits long char + *encoded over 4 bytes: + *1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx + */ + c = *byte_ptr & 0x7; + nb_bytes_2_decode = 4; + + } else if ((*byte_ptr & 0xFC) == 0xF8) { + /* + *up to 26 bits long char + *encoded over 5 bytes. + *1111 10xx 10xx xxxx 10xx xxxx + *10xx xxxx 10xx xxxx + */ + c = *byte_ptr & 3; + nb_bytes_2_decode = 5; + + } else if ((*byte_ptr & 0xFE) == 0xFC) { + /* + *up to 31 bits long char + *encoded over 6 bytes: + *1111 110x 10xx xxxx 10xx xxxx + *10xx xxxx 10xx xxxx 10xx xxxx + */ + c = *byte_ptr & 1; + nb_bytes_2_decode = 6; + + } else { + /* + *BAD ENCODING + */ + return CR_ENCODING_ERROR; + } + + /* + *Go and decode the remaining byte(s) + *(if any) to get the current character. + */ + for (; nb_bytes_2_decode > 1; nb_bytes_2_decode--) { + /*decode the next byte */ + byte_ptr++; + + /*byte pattern must be: 10xx xxxx */ + if ((*byte_ptr & 0xC0) != 0x80) { + return CR_ENCODING_ERROR; + } + + c = (c << 6) | (*byte_ptr & 0x3F); + } + + len++; + } + + *a_len = len; + + return CR_OK; +} + +/** + *Given an ucs4 string, this function + *returns the size (in bytes) this string + *would have occupied if it was encoded in utf-8. + *@param a_in_start a pointer to the beginning of the input + *buffer. + *@param a_in_end a pointer to the end of the input buffer. + *@param a_len out parameter. The computed length. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_utils_ucs4_str_len_as_utf8 (const guint32 * a_in_start, + const guint32 * a_in_end, gulong * a_len) +{ + gint len = 0; + guint32 *char_ptr = NULL; + + g_return_val_if_fail (a_in_start && a_in_end && a_len, + CR_BAD_PARAM_ERROR); + + for (char_ptr = (guint32 *) a_in_start; + char_ptr <= a_in_end; char_ptr++) { + if (*char_ptr <= 0x7F) { + /*the utf-8 char would take 1 byte */ + len += 1; + } else if (*char_ptr <= 0x7FF) { + /*the utf-8 char would take 2 bytes */ + len += 2; + } else if (*char_ptr <= 0xFFFF) { + len += 3; + } else if (*char_ptr <= 0x1FFFFF) { + len += 4; + } else if (*char_ptr <= 0x3FFFFFF) { + len += 5; + } else if (*char_ptr <= 0x7FFFFFFF) { + len += 6; + } + } + + *a_len = len; + return CR_OK; +} + +/** + *Given an ucsA string, this function + *returns the size (in bytes) this string + *would have occupied if it was encoded in utf-8. + *@param a_in_start a pointer to the beginning of the input + *buffer. + *@param a_in_end a pointer to the end of the input buffer. + *@param a_len out parameter. The computed length. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_utils_ucs1_str_len_as_utf8 (const guchar * a_in_start, + const guchar * a_in_end, gulong * a_len) +{ + gint len = 0; + guchar *char_ptr = NULL; + + g_return_val_if_fail (a_in_start && a_in_end && a_len, + CR_BAD_PARAM_ERROR); + + for (char_ptr = (guchar *) a_in_start; + char_ptr <= a_in_end; char_ptr++) { + if (*char_ptr <= 0x7F) { + /*the utf-8 char would take 1 byte */ + len += 1; + } else { + /*the utf-8 char would take 2 bytes */ + len += 2; + } + } + + *a_len = len; + return CR_OK; +} + +/** + *Converts an utf8 buffer into an ucs4 buffer. + * + *@param a_in the input utf8 buffer to convert. + *@param a_in_len in/out parameter. The size of the + *input buffer to convert. After return, this parameter contains + *the actual number of bytes consumed. + *@param a_out the output converted ucs4 buffer. Must be allocated by + *the caller. + *@param a_out_len in/out parameter. The size of the output buffer. + *If this size is actually smaller than the real needed size, the function + *just converts what it can and returns a success status. After return, + *this param points to the actual number of characters decoded. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_utils_utf8_to_ucs4 (const guchar * a_in, + gulong * a_in_len, guint32 * a_out, gulong * a_out_len) +{ + gulong in_len = 0, + out_len = 0, + in_index = 0, + out_index = 0; + enum CRStatus status = CR_OK; + + /* + *to store the final decoded + *unicode char + */ + guint c = 0; + + g_return_val_if_fail (a_in && a_in_len + && a_out && a_out_len, CR_BAD_PARAM_ERROR); + + if (*a_in_len < 1) { + status = CR_OK; + goto end; + } + + in_len = *a_in_len; + out_len = *a_out_len; + + for (in_index = 0, out_index = 0; + (in_index < in_len) && (out_index < out_len); + in_index++, out_index++) { + gint nb_bytes_2_decode = 0; + + if (a_in[in_index] <= 0x7F) { + /* + *7 bits long char + *encoded over 1 byte: + * 0xxx xxxx + */ + c = a_in[in_index]; + nb_bytes_2_decode = 1; + + } else if ((a_in[in_index] & 0xE0) == 0xC0) { + /* + *up to 11 bits long char. + *encoded over 2 bytes: + *110x xxxx 10xx xxxx + */ + c = a_in[in_index] & 0x1F; + nb_bytes_2_decode = 2; + + } else if ((a_in[in_index] & 0xF0) == 0xE0) { + /* + *up to 16 bit long char + *encoded over 3 bytes: + *1110 xxxx 10xx xxxx 10xx xxxx + */ + c = a_in[in_index] & 0x0F; + nb_bytes_2_decode = 3; + + } else if ((a_in[in_index] & 0xF8) == 0xF0) { + /* + *up to 21 bits long char + *encoded over 4 bytes: + *1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx + */ + c = a_in[in_index] & 0x7; + nb_bytes_2_decode = 4; + + } else if ((a_in[in_index] & 0xFC) == 0xF8) { + /* + *up to 26 bits long char + *encoded over 5 bytes. + *1111 10xx 10xx xxxx 10xx xxxx + *10xx xxxx 10xx xxxx + */ + c = a_in[in_index] & 3; + nb_bytes_2_decode = 5; + + } else if ((a_in[in_index] & 0xFE) == 0xFC) { + /* + *up to 31 bits long char + *encoded over 6 bytes: + *1111 110x 10xx xxxx 10xx xxxx + *10xx xxxx 10xx xxxx 10xx xxxx + */ + c = a_in[in_index] & 1; + nb_bytes_2_decode = 6; + + } else { + /*BAD ENCODING */ + goto end; + } + + /* + *Go and decode the remaining byte(s) + *(if any) to get the current character. + */ + for (; nb_bytes_2_decode > 1; nb_bytes_2_decode--) { + /*decode the next byte */ + in_index++; + + /*byte pattern must be: 10xx xxxx */ + if ((a_in[in_index] & 0xC0) != 0x80) { + goto end; + } + + c = (c << 6) | (a_in[in_index] & 0x3F); + } + + /* + *The decoded ucs4 char is now + *in c. + */ + + /************************ + *Some security tests + ***********************/ + + /*be sure c is a char */ + if (c == 0xFFFF || c == 0xFFFE) + goto end; + + /*be sure c is inferior to the max ucs4 char value */ + if (c > 0x10FFFF) + goto end; + + /* + *c must be less than UTF16 "lower surrogate begin" + *or higher than UTF16 "High surrogate end" + */ + if (c >= 0xD800 && c <= 0xDFFF) + goto end; + + /*Avoid characters that equals zero */ + if (c == 0) + goto end; + + a_out[out_index] = c; + } + + end: + *a_out_len = out_index + 1; + *a_in_len = in_index + 1; + + return status; +} + +/** + *Reads a character from an utf8 buffer. + *Actually decode the next character code (unicode character code) + *and returns it. + *@param a_in the starting address of the utf8 buffer. + *@param a_in_len the length of the utf8 buffer. + *@param a_out output parameter. The resulting read char. + *@param a_consumed the number of the bytes consumed to + *decode the returned character code. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_utils_read_char_from_utf8_buf (const guchar * a_in, + gulong a_in_len, + guint32 * a_out, gulong * a_consumed) +{ + gulong in_len = 0, + in_index = 0, + nb_bytes_2_decode = 0; + enum CRStatus status = CR_OK; + + /* + *to store the final decoded + *unicode char + */ + guint32 c = 0; + + g_return_val_if_fail (a_in && a_out && a_out + && a_consumed, CR_BAD_PARAM_ERROR); + + if (a_in_len < 1) { + status = CR_OK; + goto end; + } + + in_len = a_in_len; + + if (*a_in <= 0x7F) { + /* + *7 bits long char + *encoded over 1 byte: + * 0xxx xxxx + */ + c = *a_in; + nb_bytes_2_decode = 1; + + } else if ((*a_in & 0xE0) == 0xC0) { + /* + *up to 11 bits long char. + *encoded over 2 bytes: + *110x xxxx 10xx xxxx + */ + c = *a_in & 0x1F; + nb_bytes_2_decode = 2; + + } else if ((*a_in & 0xF0) == 0xE0) { + /* + *up to 16 bit long char + *encoded over 3 bytes: + *1110 xxxx 10xx xxxx 10xx xxxx + */ + c = *a_in & 0x0F; + nb_bytes_2_decode = 3; + + } else if ((*a_in & 0xF8) == 0xF0) { + /* + *up to 21 bits long char + *encoded over 4 bytes: + *1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx + */ + c = *a_in & 0x7; + nb_bytes_2_decode = 4; + + } else if ((*a_in & 0xFC) == 0xF8) { + /* + *up to 26 bits long char + *encoded over 5 bytes. + *1111 10xx 10xx xxxx 10xx xxxx + *10xx xxxx 10xx xxxx + */ + c = *a_in & 3; + nb_bytes_2_decode = 5; + + } else if ((*a_in & 0xFE) == 0xFC) { + /* + *up to 31 bits long char + *encoded over 6 bytes: + *1111 110x 10xx xxxx 10xx xxxx + *10xx xxxx 10xx xxxx 10xx xxxx + */ + c = *a_in & 1; + nb_bytes_2_decode = 6; + + } else { + /*BAD ENCODING */ + goto end; + } + + if (nb_bytes_2_decode > a_in_len) { + status = CR_END_OF_INPUT_ERROR; + goto end; + } + + /* + *Go and decode the remaining byte(s) + *(if any) to get the current character. + */ + for (in_index = 1; in_index < nb_bytes_2_decode; in_index++) { + /*byte pattern must be: 10xx xxxx */ + if ((a_in[in_index] & 0xC0) != 0x80) { + goto end; + } + + c = (c << 6) | (a_in[in_index] & 0x3F); + } + + /* + *The decoded ucs4 char is now + *in c. + */ + + /************************ + *Some security tests + ***********************/ + + /*be sure c is a char */ + if (c == 0xFFFF || c == 0xFFFE) + goto end; + + /*be sure c is inferior to the max ucs4 char value */ + if (c > 0x10FFFF) + goto end; + + /* + *c must be less than UTF16 "lower surrogate begin" + *or higher than UTF16 "High surrogate end" + */ + if (c >= 0xD800 && c <= 0xDFFF) + goto end; + + /*Avoid characters that equals zero */ + if (c == 0) + goto end; + + *a_out = c; + + end: + *a_consumed = nb_bytes_2_decode; + + return status; +} + +/** + * + */ +enum CRStatus +cr_utils_utf8_str_len_as_ucs1 (const guchar * a_in_start, + const guchar * a_in_end, gulong * a_len) +{ + /* + *Note: this function can be made shorter + *but it considers all the cases of the utf8 encoding + *to ease further extensions ... + */ + + guchar *byte_ptr = NULL; + gint len = 0; + + /* + *to store the final decoded + *unicode char + */ + guint c = 0; + + g_return_val_if_fail (a_in_start && a_in_end && a_len, + CR_BAD_PARAM_ERROR); + *a_len = 0; + + for (byte_ptr = (guchar *) a_in_start; + byte_ptr <= a_in_end; byte_ptr++) { + gint nb_bytes_2_decode = 0; + + if (*byte_ptr <= 0x7F) { + /* + *7 bits long char + *encoded over 1 byte: + * 0xxx xxxx + */ + c = *byte_ptr; + nb_bytes_2_decode = 1; + + } else if ((*byte_ptr & 0xE0) == 0xC0) { + /* + *up to 11 bits long char. + *encoded over 2 bytes: + *110x xxxx 10xx xxxx + */ + c = *byte_ptr & 0x1F; + nb_bytes_2_decode = 2; + + } else if ((*byte_ptr & 0xF0) == 0xE0) { + /* + *up to 16 bit long char + *encoded over 3 bytes: + *1110 xxxx 10xx xxxx 10xx xxxx + */ + c = *byte_ptr & 0x0F; + nb_bytes_2_decode = 3; + + } else if ((*byte_ptr & 0xF8) == 0xF0) { + /* + *up to 21 bits long char + *encoded over 4 bytes: + *1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx + */ + c = *byte_ptr & 0x7; + nb_bytes_2_decode = 4; + + } else if ((*byte_ptr & 0xFC) == 0xF8) { + /* + *up to 26 bits long char + *encoded over 5 bytes. + *1111 10xx 10xx xxxx 10xx xxxx + *10xx xxxx 10xx xxxx + */ + c = *byte_ptr & 3; + nb_bytes_2_decode = 5; + + } else if ((*byte_ptr & 0xFE) == 0xFC) { + /* + *up to 31 bits long char + *encoded over 6 bytes: + *1111 110x 10xx xxxx 10xx xxxx + *10xx xxxx 10xx xxxx 10xx xxxx + */ + c = *byte_ptr & 1; + nb_bytes_2_decode = 6; + + } else { + /* + *BAD ENCODING + */ + return CR_ENCODING_ERROR; + } + + /* + *Go and decode the remaining byte(s) + *(if any) to get the current character. + */ + for (; nb_bytes_2_decode > 1; nb_bytes_2_decode--) { + /*decode the next byte */ + byte_ptr++; + + /*byte pattern must be: 10xx xxxx */ + if ((*byte_ptr & 0xC0) != 0x80) { + return CR_ENCODING_ERROR; + } + + c = (c << 6) | (*byte_ptr & 0x3F); + } + + /* + *The decoded ucs4 char is now + *in c. + */ + + if (c <= 0xFF) { /*Add other conditions to support + *other char sets (ucs2, ucs3, ucs4). + */ + len++; + } else { + /*the char is too long to fit + *into the supposed charset len. + */ + return CR_ENCODING_ERROR; + } + } + + *a_len = len; + + return CR_OK; +} + +/** + *Converts an utf8 string into an ucs4 string. + *@param a_in the input string to convert. + *@param a_in_len in/out parameter. The length of the input + *string. After return, points to the actual number of bytes + *consumed. This can be usefull to debug the input stream in case + *of encoding error. + *@param a_out out parameter. Points to the output string. It is allocated + *by this function and must be freed by the caller. + *@param a_out_len out parameter. The length of the output string. + *@return CR_OK upon successfull completion, an error code otherwise. + * + */ +enum CRStatus +cr_utils_utf8_str_to_ucs4 (const guchar * a_in, + gulong * a_in_len, + guint32 ** a_out, gulong * a_out_len) +{ + enum CRStatus status = CR_OK; + + g_return_val_if_fail (a_in && a_in_len + && a_out && a_out_len, CR_BAD_PARAM_ERROR); + + status = cr_utils_utf8_str_len_as_ucs4 (a_in, + &a_in[*a_in_len - 1], + a_out_len); + + g_return_val_if_fail (status == CR_OK, status); + + *a_out = (guint32 *) g_malloc0 (*a_out_len * sizeof (guint32)); + + status = cr_utils_utf8_to_ucs4 (a_in, a_in_len, *a_out, a_out_len); + + return status; +} + +/** + *Converts an ucs4 buffer into an utf8 buffer. + * + *@param a_in the input ucs4 buffer to convert. + *@param a_in_len in/out parameter. The size of the + *input buffer to convert. After return, this parameter contains + *the actual number of characters consumed. + *@param a_out the output converted utf8 buffer. Must be allocated by + *the caller. + *@param a_out_len in/out parameter. The size of the output buffer. + *If this size is actually smaller than the real needed size, the function + *just converts what it can and returns a success status. After return, + *this param points to the actual number of bytes in the buffer. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_utils_ucs4_to_utf8 (const guint32 * a_in, + gulong * a_in_len, guchar * a_out, gulong * a_out_len) +{ + gulong in_len = 0, + in_index = 0, + out_index = 0; + enum CRStatus status = CR_OK; + + g_return_val_if_fail (a_in && a_in_len && a_out && a_out_len, + CR_BAD_PARAM_ERROR); + + if (*a_in_len < 1) { + status = CR_OK; + goto end; + } + + in_len = *a_in_len; + + for (in_index = 0; in_index < in_len; in_index++) { + /* + *FIXME: return whenever we encounter forbidden char values. + */ + + if (a_in[in_index] <= 0x7F) { + a_out[out_index] = a_in[in_index]; + out_index++; + } else if (a_in[in_index] <= 0x7FF) { + a_out[out_index] = (0xC0 | (a_in[in_index] >> 6)); + a_out[out_index + 1] = + (0x80 | (a_in[in_index] & 0x3F)); + out_index += 2; + } else if (a_in[in_index] <= 0xFFFF) { + a_out[out_index] = (0xE0 | (a_in[in_index] >> 12)); + a_out[out_index + 1] = + (0x80 | ((a_in[in_index] >> 6) & 0x3F)); + a_out[out_index + 2] = + (0x80 | (a_in[in_index] & 0x3F)); + out_index += 3; + } else if (a_in[in_index] <= 0x1FFFFF) { + a_out[out_index] = (0xF0 | (a_in[in_index] >> 18)); + a_out[out_index + 1] + = (0x80 | ((a_in[in_index] >> 12) & 0x3F)); + a_out[out_index + 2] + = (0x80 | ((a_in[in_index] >> 6) & 0x3F)); + a_out[out_index + 3] + = (0x80 | (a_in[in_index] & 0x3F)); + out_index += 4; + } else if (a_in[in_index] <= 0x3FFFFFF) { + a_out[out_index] = (0xF8 | (a_in[in_index] >> 24)); + a_out[out_index + 1] = + (0x80 | (a_in[in_index] >> 18)); + a_out[out_index + 2] + = (0x80 | ((a_in[in_index] >> 12) & 0x3F)); + a_out[out_index + 3] + = (0x80 | ((a_in[in_index] >> 6) & 0x3F)); + a_out[out_index + 4] + = (0x80 | (a_in[in_index] & 0x3F)); + out_index += 5; + } else if (a_in[in_index] <= 0x7FFFFFFF) { + a_out[out_index] = (0xFC | (a_in[in_index] >> 30)); + a_out[out_index + 1] = + (0x80 | (a_in[in_index] >> 24)); + a_out[out_index + 2] + = (0x80 | ((a_in[in_index] >> 18) & 0x3F)); + a_out[out_index + 3] + = (0x80 | ((a_in[in_index] >> 12) & 0x3F)); + a_out[out_index + 4] + = (0x80 | ((a_in[in_index] >> 6) & 0x3F)); + a_out[out_index + 4] + = (0x80 | (a_in[in_index] & 0x3F)); + out_index += 6; + } else { + status = CR_ENCODING_ERROR; + goto end; + } + } /*end for */ + + end: + *a_in_len = in_index + 1; + *a_out_len = out_index + 1; + + return status; +} + +/** + *Converts an ucs4 string into an utf8 string. + *@param a_in the input string to convert. + *@param a_in_len in/out parameter. The length of the input + *string. After return, points to the actual number of characters + *consumed. This can be usefull to debug the input string in case + *of encoding error. + *@param a_out out parameter. Points to the output string. It is allocated + *by this function and must be freed by the caller. + *@param a_out_len out parameter. The length (in bytes) of the output string. + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_utils_ucs4_str_to_utf8 (const guint32 * a_in, + gulong * a_in_len, + guchar ** a_out, gulong * a_out_len) +{ + enum CRStatus status = CR_OK; + + g_return_val_if_fail (a_in && a_in_len && a_out + && a_out_len, CR_BAD_PARAM_ERROR); + + status = cr_utils_ucs4_str_len_as_utf8 (a_in, + &a_in[*a_out_len - 1], + a_out_len); + + g_return_val_if_fail (status == CR_OK, status); + + status = cr_utils_ucs4_to_utf8 (a_in, a_in_len, *a_out, a_out_len); + + return status; +} + +/** + *Converts an ucs1 buffer into an utf8 buffer. + *The caller must know the size of the resulting buffer and + *allocate it prior to calling this function. + * + *@param a_in the input ucs1 buffer. + * + *@param a_in_len in/out parameter. The length of the input buffer. + *After return, points to the number of bytes actually consumed even + *in case of encoding error. + * + *@param a_out out parameter. The output utf8 converted buffer. + * + *@param a_out_len in/out parameter. The size of the output buffer. + *If the output buffer size is shorter than the actual needed size, + *this function just convert what it can. + * + *@return CR_OK upon successfull completion, an error code otherwise. + * + */ +enum CRStatus +cr_utils_ucs1_to_utf8 (const guchar * a_in, + gulong * a_in_len, guchar * a_out, gulong * a_out_len) +{ + gulong out_index = 0, + in_index = 0, + in_len = 0, + out_len = 0; + enum CRStatus status = CR_OK; + + g_return_val_if_fail (a_in && a_in_len + && a_out_len, + CR_BAD_PARAM_ERROR); + + if (*a_in_len == 0) { + *a_out_len = 0 ; + return CR_OK ; + } + g_return_val_if_fail (a_out, CR_BAD_PARAM_ERROR) ; + + if (*a_in_len < 1) { + status = CR_OK; + goto end; + } + + in_len = *a_in_len; + out_len = *a_out_len; + + for (in_index = 0, out_index = 0; + (in_index < in_len) && (out_index < out_len); in_index++) { + /* + *FIXME: return whenever we encounter forbidden char values. + */ + + if (a_in[in_index] <= 0x7F) { + a_out[out_index] = a_in[in_index]; + out_index++; + } else { + a_out[out_index] = (0xC0 | (a_in[in_index] >> 6)); + a_out[out_index + 1] = + (0x80 | (a_in[in_index] & 0x3F)); + out_index += 2; + } + } /*end for */ + + end: + *a_in_len = in_index; + *a_out_len = out_index; + + return CR_OK; +} + +/** + *Converts an ucs1 string into an utf8 string. + *@param a_in_start the beginning of the input string to convert. + *@param a_in_end the end of the input string to convert. + *@param a_out out parameter. The converted string. + *@param a_out out parameter. The length of the converted string. + *@return CR_OK upon successfull completion, an error code otherwise. + * + */ +enum CRStatus +cr_utils_ucs1_str_to_utf8 (const guchar * a_in, + gulong * a_in_len, + guchar ** a_out, gulong * a_out_len) +{ + gulong in_len = 0, + out_len = 0; + enum CRStatus status = CR_OK; + + g_return_val_if_fail (a_in && a_in_len && a_out + && a_out_len, CR_BAD_PARAM_ERROR); + + if (*a_in_len < 1) { + *a_out_len = 0; + *a_out = NULL; + return CR_OK; + } + + status = cr_utils_ucs1_str_len_as_utf8 (a_in, &a_in[*a_in_len - 1], + &out_len); + + g_return_val_if_fail (status == CR_OK, status); + + in_len = *a_in_len; + + *a_out = (guchar *) g_malloc0 (out_len); + + status = cr_utils_ucs1_to_utf8 (a_in, a_in_len, *a_out, &out_len); + + *a_out_len = out_len; + + return status; +} + +/** + *Converts an utf8 buffer into an ucs1 buffer. + *The caller must know the size of the resulting + *converted buffer, and allocated it prior to calling this + *function. + * + *@param a_in the input utf8 buffer to convert. + * + *@param a_in_len in/out parameter. The size of the input utf8 buffer. + *After return, points to the number of bytes consumed + *by the function even in case of encoding error. + * + *@param a_out out parameter. Points to the resulting buffer. + *Must be allocated by the caller. If the size of a_out is shorter + *than its required size, this function converts what it can and return + *a successfull status. + * + *@param a_out_len in/out parameter. The size of the output buffer. + *After return, points to the number of bytes consumed even in case of + *encoding error. + * + *@return CR_OK upon successfull completion, an error code otherwise. + */ +enum CRStatus +cr_utils_utf8_to_ucs1 (const guchar * a_in, + gulong * a_in_len, guchar * a_out, gulong * a_out_len) +{ + gulong in_index = 0, + out_index = 0, + in_len = 0, + out_len = 0; + enum CRStatus status = CR_OK; + + /* + *to store the final decoded + *unicode char + */ + guint32 c = 0; + + g_return_val_if_fail (a_in && a_in_len + && a_out && a_out_len, CR_BAD_PARAM_ERROR); + + if (*a_in_len < 1) { + status = CR_OK; + goto end; + } + + in_len = *a_in_len; + out_len = *a_out_len; + + for (in_index = 0, out_index = 0; + (in_index < in_len) && (out_index < out_len); + in_index++, out_index++) { + gint nb_bytes_2_decode = 0; + + if (a_in[in_index] <= 0x7F) { + /* + *7 bits long char + *encoded over 1 byte: + * 0xxx xxxx + */ + c = a_in[in_index]; + nb_bytes_2_decode = 1; + + } else if ((a_in[in_index] & 0xE0) == 0xC0) { + /* + *up to 11 bits long char. + *encoded over 2 bytes: + *110x xxxx 10xx xxxx + */ + c = a_in[in_index] & 0x1F; + nb_bytes_2_decode = 2; + + } else if ((a_in[in_index] & 0xF0) == 0xE0) { + /* + *up to 16 bit long char + *encoded over 3 bytes: + *1110 xxxx 10xx xxxx 10xx xxxx + */ + c = a_in[in_index] & 0x0F; + nb_bytes_2_decode = 3; + + } else if ((a_in[in_index] & 0xF8) == 0xF0) { + /* + *up to 21 bits long char + *encoded over 4 bytes: + *1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx + */ + c = a_in[in_index] & 0x7; + nb_bytes_2_decode = 4; + + } else if ((a_in[in_index] & 0xFC) == 0xF8) { + /* + *up to 26 bits long char + *encoded over 5 bytes. + *1111 10xx 10xx xxxx 10xx xxxx + *10xx xxxx 10xx xxxx + */ + c = a_in[in_index] & 3; + nb_bytes_2_decode = 5; + + } else if ((a_in[in_index] & 0xFE) == 0xFC) { + /* + *up to 31 bits long char + *encoded over 6 bytes: + *1111 110x 10xx xxxx 10xx xxxx + *10xx xxxx 10xx xxxx 10xx xxxx + */ + c = a_in[in_index] & 1; + nb_bytes_2_decode = 6; + + } else { + /*BAD ENCODING */ + status = CR_ENCODING_ERROR; + goto end; + } + + /* + *Go and decode the remaining byte(s) + *(if any) to get the current character. + */ + if (in_index + nb_bytes_2_decode - 1 >= in_len) { + status = CR_OK; + goto end; + } + + for (; nb_bytes_2_decode > 1; nb_bytes_2_decode--) { + /*decode the next byte */ + in_index++; + + /*byte pattern must be: 10xx xxxx */ + if ((a_in[in_index] & 0xC0) != 0x80) { + status = CR_ENCODING_ERROR; + goto end; + } + + c = (c << 6) | (a_in[in_index] & 0x3F); + } + + /* + *The decoded ucs4 char is now + *in c. + */ + + if (c > 0xFF) { + status = CR_ENCODING_ERROR; + goto end; + } + + a_out[out_index] = c; + } + + end: + *a_out_len = out_index; + *a_in_len = in_index; + + return CR_OK; +} + +/** + *Converts an utf8 buffer into an + *ucs1 buffer. + *@param a_in_start the start of the input buffer. + *@param a_in_end the end of the input buffer. + *@param a_out out parameter. The resulting converted ucs4 buffer. + *Must be freed by the caller. + *@param a_out_len out parameter. The length of the converted buffer. + *@return CR_OK upon successfull completion, an error code otherwise. + *Note that out parameters are valid if and only if this function + *returns CR_OK. + */ +enum CRStatus +cr_utils_utf8_str_to_ucs1 (const guchar * a_in, + gulong * a_in_len, + guchar ** a_out, gulong * a_out_len) +{ + enum CRStatus status = CR_OK; + + g_return_val_if_fail (a_in && a_in_len + && a_out && a_out_len, CR_BAD_PARAM_ERROR); + + if (*a_in_len < 1) { + *a_out_len = 0; + *a_out = NULL; + return CR_OK; + } + + status = cr_utils_utf8_str_len_as_ucs4 (a_in, &a_in[*a_in_len - 1], + a_out_len); + + g_return_val_if_fail (status == CR_OK, status); + + *a_out = (guchar *) g_malloc0 (*a_out_len * sizeof (guint32)); + + status = cr_utils_utf8_to_ucs1 (a_in, a_in_len, *a_out, a_out_len); + return status; +} + +/***************************************** + *CSS basic types identification utilities + *****************************************/ + +/** + *Returns TRUE if a_char is a white space as + *defined in the css spec in chap 4.1.1. + * + *white-space ::= ' '| \t|\r|\n|\f + * + *@param a_char the character to test. + *return TRUE if is a white space, false otherwise. + */ +gboolean +cr_utils_is_white_space (guint32 a_char) +{ + switch (a_char) { + case ' ': + case '\t': + case '\r': + case '\n': + case '\f': + return TRUE; + break; + default: + return FALSE; + } +} + +/** + *Returns true if the character is a newline + *as defined in the css spec in the chap 4.1.1. + * + *nl ::= \n|\r\n|\r|\f + * + *@param a_char the character to test. + *@return TRUE if the character is a newline, FALSE otherwise. + */ +gboolean +cr_utils_is_newline (guint32 a_char) +{ + switch (a_char) { + case '\n': + case '\r': + case '\f': + return TRUE; + break; + default: + return FALSE; + } +} + +/** + *returns TRUE if the char is part of an hexa num char: + *i.e hexa_char ::= [0-9A-F] + */ +gboolean +cr_utils_is_hexa_char (guint32 a_char) +{ + if ((a_char >= '0' && a_char <= '9') + || (a_char >= 'A' && a_char <= 'F')) { + return TRUE; + } + return FALSE; +} + +/** + *Returns true if the character is a nonascii + *character (as defined in the css spec chap 4.1.1): + * + *nonascii ::= [^\0-\177] + * + *@param a_char the character to test. + *@return TRUE if the character is a nonascii char, + *FALSE otherwise. + */ +gboolean +cr_utils_is_nonascii (guint32 a_char) +{ + if (a_char <= 177) { + return FALSE; + } + + return TRUE; +} + +/** + *Dumps a character a_nb times on a file. + *@param a_char the char to dump + *@param a_fp the destination file pointer + *@param a_nb the number of times a_char is to be dumped. + */ +void +cr_utils_dump_n_chars (guchar a_char, FILE * a_fp, glong a_nb) +{ + glong i = 0; + + for (i = 0; i < a_nb; i++) { + fprintf (a_fp, "%c", a_char); + } +} + +void +cr_utils_dump_n_chars2 (guchar a_char, GString * a_string, glong a_nb) +{ + glong i = 0; + + g_return_if_fail (a_string); + + for (i = 0; i < a_nb; i++) { + g_string_append_printf (a_string, "%c", a_char); + } +} + +/** + *Duplicates a list of GString instances. + *@return the duplicated list of GString instances or NULL if + *something bad happened. + *@param a_list_of_strings the list of strings to be duplicated. + */ +GList * +cr_utils_dup_glist_of_string (GList * a_list_of_strings) +{ + GList *cur = NULL, + *result = NULL; + + g_return_val_if_fail (a_list_of_strings, NULL); + + for (cur = a_list_of_strings; cur; cur = cur->next) { + GString *str = NULL; + + str = g_string_new_len (((GString *) cur->data)->str, + ((GString *) cur->data)->len); + if (str) + result = g_list_append (result, str); + } + + return result; +} + +/** + *Duplicate a GList where the GList::data is a CRString. + *@param a_list_of_strings the list to duplicate + *@return the duplicated list, or NULL if something bad + *happened. + */ +GList * +cr_utils_dup_glist_of_cr_string (GList * a_list_of_strings) +{ + GList *cur = NULL, *result = NULL; + + g_return_val_if_fail (a_list_of_strings, NULL); + + for (cur = a_list_of_strings; cur; cur = cur->next) { + CRString *str = NULL; + + str = cr_string_dup ((CRString *) cur->data) ; + if (str) + result = g_list_append (result, str); + } + + return result; +} diff --git a/src/libcroco/cr-utils.h b/src/libcroco/cr-utils.h new file mode 100644 index 000000000..060842aef --- /dev/null +++ b/src/libcroco/cr-utils.h @@ -0,0 +1,249 @@ +/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ + +/* + * This file is part of The Croco Library + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * Author: Dodji Seketeli + * Look at file COPYRIGHTS for copyright information + */ + +#ifndef __CR_DEFS_H__ +#define __CR_DEFS_H__ + +#include +#include +/* + * We're disabling this #include for Inkscape: see comment in libcroco.h. +//#include "libcroco-config.h" + */ + +G_BEGIN_DECLS + +/** + *@file + *The Croco library basic types definitions + *And global definitions. + */ + +/** + *The status type returned + *by the methods of the croco library. + */ +enum CRStatus { + CR_OK, + CR_BAD_PARAM_ERROR, + CR_INSTANCIATION_FAILED_ERROR, + CR_UNKNOWN_TYPE_ERROR, + CR_UNKNOWN_PROP_ERROR, + CR_UNKNOWN_PROP_VAL_ERROR, + CR_UNEXPECTED_POSITION_SCHEME, + CR_START_OF_INPUT_ERROR, + CR_END_OF_INPUT_ERROR, + CR_OUTPUT_TOO_SHORT_ERROR, + CR_INPUT_TOO_SHORT_ERROR, + CR_OUT_OF_BOUNDS_ERROR, + CR_EMPTY_PARSER_INPUT_ERROR, + CR_ENCODING_ERROR, + CR_ENCODING_NOT_FOUND_ERROR, + CR_PARSING_ERROR, + CR_SYNTAX_ERROR, + CR_NO_ROOT_NODE_ERROR, + CR_NO_TOKEN, + CR_OUT_OF_MEMORY_ERROR, + CR_PSEUDO_CLASS_SEL_HANDLER_NOT_FOUND_ERROR, + CR_BAD_PSEUDO_CLASS_SEL_HANDLER_ERROR, + CR_ERROR, + CR_FILE_NOT_FOUND_ERROR, + CR_VALUE_NOT_FOUND_ERROR +} ; + +/** + *Values used by + *cr_input_seek_position() ; + */ +enum CRSeekPos { + CR_SEEK_CUR, + CR_SEEK_BEGIN, + CR_SEEK_END +} ; + +/** + *Encoding values. + */ +enum CREncoding +{ + CR_UCS_4 = 1/*Must be not NULL*/, + CR_UCS_1, + CR_ISO_8859_1, + CR_ASCII, + CR_UTF_8, + CR_UTF_16, + CR_AUTO/*should be the last one*/ +} ; + + + + +#define CROCO_LOG_DOMAIN "LIBCROCO" + +#ifdef __GNUC__ +#define cr_utils_trace(a_log_level, a_msg) \ +g_log (CROCO_LOG_DOMAIN, \ + G_LOG_LEVEL_CRITICAL, \ + "file %s: line %d (%s): %s\n", \ + __FILE__, \ + __LINE__, \ + __PRETTY_FUNCTION__, \ + a_msg) +#else /*__GNUC__*/ + +#define cr_utils_trace(a_log_level, a_msg) \ +g_log (CROCO_LOG_DOMAIN, \ + G_LOG_LEVEL_CRITICAL, \ + "file %s: line %d: %s\n", \ + __FILE__, \ + __LINE__, \ + a_msg) +#endif + +/** + *Traces an info message. + *The file, line and enclosing function + *of the message will be automatically + *added to the message. + *@param a_msg the msg to trace. + */ +#define cr_utils_trace_info(a_msg) \ +cr_utils_trace (G_LOG_LEVEL_INFO, a_msg) + +/** + *Trace a debug message. + *The file, line and enclosing function + *of the message will be automatically + *added to the message. + *@param a_msg the msg to trace. + */ +#define cr_utils_trace_debug(a_msg) \ +cr_utils_trace (G_LOG_LEVEL_DEBUG, a_msg) ; + + +/**************************** + *Encoding transformations and + *encoding helpers + ****************************/ + +enum CRStatus +cr_utils_read_char_from_utf8_buf (const guchar * a_in, gulong a_in_len, + guint32 *a_out, gulong *a_consumed) ; + +enum CRStatus +cr_utils_ucs1_to_utf8 (const guchar *a_in, gulong *a_in_len, + guchar *a_out, gulong *a_out_len) ; + +enum CRStatus +cr_utils_utf8_to_ucs1 (const guchar * a_in, gulong * a_in_len, + guchar *a_out, gulong *a_out_len) ; + +enum CRStatus +cr_utils_ucs4_to_utf8 (const guint32 *a_in, gulong *a_in_len, + guchar *a_out, gulong *a_out_len) ; + +enum CRStatus +cr_utils_utf8_str_len_as_ucs4 (const guchar *a_in_start, + const guchar *a_in_end, + gulong *a_len) ; +enum CRStatus +cr_utils_ucs1_str_len_as_utf8 (const guchar *a_in_start, + const guchar *a_in_end, + gulong *a_len) ; +enum CRStatus +cr_utils_utf8_str_len_as_ucs1 (const guchar *a_in_start, + const guchar *a_in_end, + gulong *a_len) ; +enum CRStatus +cr_utils_ucs4_str_len_as_utf8 (const guint32 *a_in_start, + const guint32 *a_in_end, + gulong *a_len) ; + +enum CRStatus +cr_utils_ucs1_str_to_utf8 (const guchar *a_in_start, + gulong *a_in_len, + guchar **a_out, + gulong *a_len) ; + +enum CRStatus +cr_utils_utf8_str_to_ucs1 (const guchar * a_in_start, + gulong * a_in_len, + guchar **a_out, + gulong *a_out_len) ; + +enum CRStatus +cr_utils_utf8_to_ucs4 (const guchar * a_in, + gulong * a_in_len, + guint32 *a_out, gulong *a_out_len) ; + +enum CRStatus +cr_utils_ucs4_str_to_utf8 (const guint32 *a_in, + gulong *a_in_len, + guchar **a_out, gulong *a_out_len) ; + +enum CRStatus +cr_utils_utf8_str_to_ucs4 (const guchar * a_in, + gulong *a_in_len, + guint32 **a_out, + gulong *a_out_len) ; + + +/***************************************** + *CSS basic types identification utilities + *****************************************/ + +gboolean +cr_utils_is_newline (guint32 a_char) ; + +gboolean +cr_utils_is_white_space (guint32 a_char) ; + +gboolean +cr_utils_is_nonascii (guint32 a_char) ; + +gboolean +cr_utils_is_hexa_char (guint32 a_char) ; + + +/********************************** + *Miscellaneous utility functions + ***********************************/ + +void +cr_utils_dump_n_chars (guchar a_char, + FILE *a_fp, + glong a_nb) ; + +void +cr_utils_dump_n_chars2 (guchar a_char, + GString *a_string, + glong a_nb) ; +GList * +cr_utils_dup_glist_of_string (GList *a_list) ; + +GList * +cr_utils_dup_glist_of_cr_string (GList * a_list_of_strings) ; + +G_END_DECLS + +#endif /*__CR_DEFS_H__*/ diff --git a/src/libcroco/libcroco.h b/src/libcroco/libcroco.h new file mode 100644 index 000000000..579715a6e --- /dev/null +++ b/src/libcroco/libcroco.h @@ -0,0 +1,48 @@ +/* + * This file is part of The Croco Library + * + * Copyright (C) 2002-2003 Dodji Seketeli + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#ifndef __LIBCROCO_H__ +#define __LIBCROCO_H__ + +/* + * We're disabling this for inkscape: none of its macros are actually used anyway, + * so we might as well keep the build process simple. +//#include "libcroco-config.h" + */ + +#include "cr-utils.h" +#include "cr-pseudo.h" +#include "cr-term.h" +#include "cr-attr-sel.h" +#include "cr-simple-sel.h" +#include "cr-selector.h" +#include "cr-enc-handler.h" +#include "cr-doc-handler.h" +#include "cr-input.h" +#include "cr-parser.h" +#include "cr-statement.h" +#include "cr-stylesheet.h" +#include "cr-om-parser.h" +#include "cr-prop-list.h" +#include "cr-sel-eng.h" +#include "cr-style.h" +#include "cr-string.h" + +#endif /*__LIBCROCO_H__*/ diff --git a/src/libcroco/makefile.in b/src/libcroco/makefile.in new file mode 100644 index 000000000..618e88087 --- /dev/null +++ b/src/libcroco/makefile.in @@ -0,0 +1,17 @@ +# Convenience stub makefile to call the real Makefile. + +@SET_MAKE@ + +# Explicit so that it's the default rule. +all: + cd .. && $(MAKE) libcroco/all + +clean %.a %.o: + cd .. && $(MAKE) libcroco/$@ + +.PHONY: all clean + +OBJEXT = @OBJEXT@ + +.SUFFIXES: +.SUFFIXES: .a .$(OBJEXT) diff --git a/src/libnr/.cvsignore b/src/libnr/.cvsignore new file mode 100644 index 000000000..4191391ae --- /dev/null +++ b/src/libnr/.cvsignore @@ -0,0 +1,13 @@ +Makefile +Makefile.in +.deps +.libs +nr_config.h +testnr +gen_nr_config +makefile +.dirstamp +*-test +test-nr +test-nr.cpp +test-nr-main.cpp diff --git a/src/libnr/Makefile_insert b/src/libnr/Makefile_insert new file mode 100644 index 000000000..541432193 --- /dev/null +++ b/src/libnr/Makefile_insert @@ -0,0 +1,167 @@ +## Makefile.am fragment sourced by src/Makefile.am. + +libnr/all: libnr/libnr.a + +libnr/clean: + rm -f libnr/libnr.a libnr/libtest-nr.a $(libnr_libnr_a_OBJECTS) $(libnr_libtest_nr_a_OBJECTS) + +if USE_MMX +libnr_mmx_sources = \ + libnr/have_mmx.S \ + libnr/nr_mmx_R8G8B8A8_P_EMPTY_A8_RGBAP.S \ + libnr/nr_mmx_R8G8B8A8_P_R8G8B8A8_P_A8_RGBAP.S \ + libnr/nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM.S \ + libnr/nr_mmx_R8G8B8_R8G8B8_R8G8B8A8_P.S +endif + +libnr_libnr_a_SOURCES = \ + libnr/in-svg-plane.h \ + libnr/n-art-bpath.h \ + libnr/nr-blit.cpp \ + libnr/nr-blit.h \ + libnr/nr-compose-transform.cpp \ + libnr/nr-compose-transform.h \ + libnr/nr-compose.cpp \ + libnr/nr-compose.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-gradient.cpp \ + libnr/nr-gradient.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-div.cpp \ + libnr/nr-matrix-div.h \ + libnr/nr-matrix-fns.cpp \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix-rotate-ops.cpp \ + libnr/nr-matrix-rotate-ops.h \ + libnr/nr-matrix-scale-ops.cpp \ + libnr/nr-matrix-scale-ops.h \ + libnr/nr-matrix-translate-ops.cpp \ + libnr/nr-matrix-translate-ops.h \ + libnr/nr-matrix.cpp \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.cpp \ + libnr/nr-object.h \ + libnr/nr-path.cpp \ + libnr/nr-path.h \ + libnr/nr-path-code.h \ + libnr/nr-pixblock-line.cpp \ + libnr/nr-pixblock-line.h \ + libnr/nr-pixblock-pattern.cpp \ + libnr/nr-pixblock-pattern.h \ + libnr/nr-pixblock-pixel.cpp \ + libnr/nr-pixblock-pixel.h \ + libnr/nr-pixblock.cpp \ + libnr/nr-pixblock.h \ + libnr/nr-pixops.h \ + libnr/nr-point-fns.cpp \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.cpp \ + libnr/nr-rect-l.h \ + libnr/nr-rect.cpp \ + libnr/nr-rect.h \ + libnr/nr-rect-ops.h \ + libnr/nr-render.h \ + libnr/nr-rotate-fns.cpp \ + libnr/nr-rotate-fns.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate-matrix-ops.cpp \ + libnr/nr-rotate-matrix-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale-matrix-ops.cpp \ + libnr/nr-scale-matrix-ops.h \ + libnr/nr-scale-translate-ops.cpp \ + libnr/nr-scale-translate-ops.h \ + libnr/nr-scale-ops.h \ + libnr/nr-scale.h \ + libnr/nr-svp-private.h \ + libnr/nr-svp-render.cpp \ + libnr/nr-svp-render.h \ + libnr/nr-svp.cpp \ + libnr/nr-svp.h \ + libnr/nr-translate-matrix-ops.cpp \ + libnr/nr-translate-matrix-ops.h \ + libnr/nr-translate-scale-ops.cpp \ + libnr/nr-translate-scale-ops.h \ + libnr/nr-translate-ops.h \ + libnr/nr-translate.h \ + libnr/nr-translate-rotate-ops.cpp \ + libnr/nr-translate-rotate-ops.h \ + libnr/nr-types.cpp \ + libnr/nr-types.h \ + libnr/nr-values.cpp \ + libnr/nr-values.h \ + $(libnr_mmx_sources) + +libnr_testnr_SOURCES = \ + libnr/testnr.cpp + +libnr_testnr_LDADD = \ + libnr/libnr.a \ + -lglib-2.0 + + +libnr/test-nr-main.cpp: libnr/test-nr.cpp + $(top_srcdir)/cxxtest/cxxtestgen.pl --error-printer -root -o libnr/test-nr-main.cpp $(libnr_test_nr_includes) + +libnr/test-nr.cpp: $(libnr_test_nr_includes) + $(top_srcdir)/cxxtest/cxxtestgen.pl -part -o libnr/test-nr.cpp $(libnr_test_nr_includes) + +libnr_test_nr_includes = \ + $(srcdir)/libnr/nr-types-test.h \ + $(srcdir)/libnr/nr-translate-test.h \ + $(srcdir)/libnr/nr-rotate-test.h \ + $(srcdir)/libnr/nr-scale-test.h \ + $(srcdir)/libnr/nr-point-fns-test.h \ + $(srcdir)/libnr/nr-rotate-fns-test.h \ + $(srcdir)/libnr/in-svg-plane-test.h \ + $(srcdir)/libnr/nr-matrix-test.h + +libnr_libtest_nr_a_SOURCES = \ + libnr/test-nr.cpp \ + $(libnr_test_nr_includes) + +libnr_test_nr_SOURCES = \ + libnr/test-nr-main.cpp \ + $(libnr_test_nr_includes) + +libnr_test_nr_LDADD = \ + libnr/libnr.a \ + libnr/libtest-nr.a \ + -lglib-2.0 + +# -L/usr/X11R6/lib +# -lX11 + +libnr_in_svg_plane_test_SOURCES = libnr/in-svg-plane-test.cpp +libnr_in_svg_plane_test_LDADD = libnr/libnr.a -lglib-2.0 + +libnr_nr_types_test_SOURCES = libnr/nr-types-test.cpp +libnr_nr_types_test_LDADD = libnr/libnr.a -lglib-2.0 + +libnr_nr_point_fns_test_SOURCES = libnr/nr-point-fns-test.cpp +libnr_nr_point_fns_test_LDADD = libnr/libnr.a -lglib-2.0 + +libnr_nr_matrix_test_SOURCES = libnr/nr-matrix-test.cpp +libnr_nr_matrix_test_LDADD = libnr/libnr.a -lglib-2.0 + +libnr_nr_rotate_test_SOURCES = libnr/nr-rotate-test.cpp +libnr_nr_rotate_test_LDADD = libnr/libnr.a -lglib-2.0 + +libnr_nr_rotate_fns_test_SOURCES = libnr/nr-rotate-fns-test.cpp +libnr_nr_rotate_fns_test_LDADD = libnr/libnr.a -lglib-2.0 + +libnr_nr_scale_test_SOURCES = libnr/nr-scale-test.cpp +libnr_nr_scale_test_LDADD = libnr/libnr.a -lglib-2.0 + +libnr_nr_translate_test_SOURCES = libnr/nr-translate-test.cpp +libnr_nr_translate_test_LDADD = libnr/libnr.a -lglib-2.0 diff --git a/src/libnr/have_mmx.S b/src/libnr/have_mmx.S new file mode 100644 index 000000000..d6428191e --- /dev/null +++ b/src/libnr/have_mmx.S @@ -0,0 +1,47 @@ + .file "have_mmx.S" + +# Ensure Inkscape is execshield protected + .section .note.GNU-stack + .previous + + .version "01.01" +gcc2_compiled.: +.text + .align 16 +.globl nr_have_mmx + .type nr_have_mmx,@function + +nr_have_mmx: + push %ebx + +# Check if bit 21 in flags word is writeable + + pushfl + popl %eax + movl %eax,%ebx + xorl $0x00200000, %eax + pushl %eax + popfl + pushfl + popl %eax + + cmpl %eax, %ebx + + je .notfound + +# OK, we have CPUID + + movl $1, %eax + cpuid + + test $0x00800000, %edx + jz .notfound + + movl $1, %eax + jmp .out + +.notfound: + movl $0, %eax +.out: + popl %ebx + ret diff --git a/src/libnr/in-svg-plane-test.cpp b/src/libnr/in-svg-plane-test.cpp new file mode 100644 index 000000000..f5620c32b --- /dev/null +++ b/src/libnr/in-svg-plane-test.cpp @@ -0,0 +1,58 @@ +#include +#include + +#include "libnr/in-svg-plane.h" +#include "utest/utest.h" +#include "isnan.h" + +int main(int argc, char *argv[]) +{ + utest_start("in-svg-plane.h"); + + NR::Point const p3n4(3.0, -4.0); + NR::Point const p0(0.0, 0.0); + double const small = pow(2.0, -1070); + double const inf = 1e400; + double const nan = inf - inf; + + NR::Point const small_left(-small, 0.0); + NR::Point const small_n3_4(-3.0 * small, 4.0 * small); + NR::Point const part_nan(3., nan); + + assert(isNaN(nan)); + assert(!isNaN(small)); + + UTEST_TEST("in_svg_plane") { + UTEST_ASSERT(in_svg_plane(p3n4)); + UTEST_ASSERT(in_svg_plane(p0)); + UTEST_ASSERT(in_svg_plane(small_left)); + UTEST_ASSERT(in_svg_plane(small_n3_4)); + UTEST_ASSERT(nan != nan); + UTEST_ASSERT(!in_svg_plane(NR::Point(nan, 3.))); + UTEST_ASSERT(!in_svg_plane(NR::Point(inf, nan))); + UTEST_ASSERT(!in_svg_plane(NR::Point(0., -inf))); + double const xs[] = {inf, -inf, nan, 1., -2., small, -small}; + for (unsigned i = 0; i < G_N_ELEMENTS(xs); ++i) { + for (unsigned j = 0; j < G_N_ELEMENTS(xs); ++j) { + UTEST_ASSERT( in_svg_plane(NR::Point(xs[i], xs[j])) + == (fabs(xs[i]) < inf && + fabs(xs[j]) < inf ) ); + } + } + } + + return ( utest_end() + ? EXIT_SUCCESS + : EXIT_FAILURE ); +} + +/* + 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 : diff --git a/src/libnr/in-svg-plane-test.h b/src/libnr/in-svg-plane-test.h new file mode 100644 index 000000000..6cd29bd6a --- /dev/null +++ b/src/libnr/in-svg-plane-test.h @@ -0,0 +1,81 @@ +#include + +#include +#include + +#include "libnr/in-svg-plane.h" +#include "isnan.h" + +class InSvgPlaneTest : public CxxTest::TestSuite +{ +public: + + InSvgPlaneTest() : + setupValid(true), + p3n4( 3.0, -4.0 ), + p0(0.0, 0.0), + small( pow(2.0, -1070) ), + inf( 1e400 ), + nan( inf - inf ), + small_left( -small, 0.0 ), + small_n3_4( -3.0 * small, 4.0 * small ), + part_nan( 3., nan ) + { + setupValid &= isNaN(nan); + setupValid &= !isNaN(small); + } + virtual ~InSvgPlaneTest() {} + +// createSuite and destroySuite get us per-suite setup and teardown +// without us having to worry about static initialization order, etc. + static InSvgPlaneTest *createSuite() { return new InSvgPlaneTest(); } + static void destroySuite( InSvgPlaneTest *suite ) { delete suite; } + +// Called before each test in this suite + void setUp() + { + TS_ASSERT( setupValid ); + } + + bool setupValid; + NR::Point const p3n4; + NR::Point const p0; + double const small; + double const inf; + double const nan; + NR::Point const small_left; + NR::Point const small_n3_4; + NR::Point const part_nan; + + + void testInSvgPlane(void) + { + TS_ASSERT( in_svg_plane(p3n4) ); + TS_ASSERT( in_svg_plane(p0) ); + TS_ASSERT( in_svg_plane(small_left) ); + TS_ASSERT( in_svg_plane(small_n3_4) ); + TS_ASSERT_DIFFERS( nan, nan ); + TS_ASSERT( !in_svg_plane(NR::Point(nan, 3.)) ); + TS_ASSERT( !in_svg_plane(NR::Point(inf, nan)) ); + TS_ASSERT( !in_svg_plane(NR::Point(0., -inf)) ); + double const xs[] = {inf, -inf, nan, 1., -2., small, -small}; + for (unsigned i = 0; i < G_N_ELEMENTS(xs); ++i) { + for (unsigned j = 0; j < G_N_ELEMENTS(xs); ++j) { + TS_ASSERT_EQUALS( in_svg_plane(NR::Point(xs[i], xs[j])), + (fabs(xs[i]) < inf && + fabs(xs[j]) < inf ) ); + } + } + } +}; + +/* + 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 : diff --git a/src/libnr/in-svg-plane.h b/src/libnr/in-svg-plane.h new file mode 100644 index 000000000..901fc748f --- /dev/null +++ b/src/libnr/in-svg-plane.h @@ -0,0 +1,33 @@ +#ifndef SEEN_LIBNR_IN_SVG_PLANE_H +#define SEEN_LIBNR_IN_SVG_PLANE_H + +#include "libnr/nr-point-fns.h" + + +/** + * Returns true iff the coordinates of \a p are finite, non-NaN, and "small enough". Currently we + * use the magic number 1e18 for determining "small enough", as this number has in the past been + * used in sodipodi code as a sort of "infinity" value. + * + * For SVG Tiny output, we might choose a smaller value corresponding to the range of valid numbers + * in SVG Tiny (which uses fixed-point arithmetic). + */ +inline bool +in_svg_plane(NR::Point const p) +{ + return NR::LInfty(p) < 1e18; +} + + +#endif /* !SEEN_LIBNR_IN_SVG_PLANE_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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/libnr.def b/src/libnr/libnr.def new file mode 100644 index 000000000..d8f224ca9 --- /dev/null +++ b/src/libnr/libnr.def @@ -0,0 +1,89 @@ +EXPORTS + nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM + nr_R8G8B8A8_N_EMPTY_A8_RGBA32 + nr_R8G8B8A8_N_EMPTY_R8G8B8A8_N + nr_R8G8B8A8_N_EMPTY_R8G8B8A8_N_A8 + nr_R8G8B8A8_N_EMPTY_R8G8B8A8_P + nr_R8G8B8A8_N_EMPTY_R8G8B8A8_P_A8 + nr_R8G8B8A8_N_R8G8B8A8_N_A8_RGBA32 + nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_N + nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_N_A8 + nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_N_TRANSFORM + nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_P + nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_P_A8 + nr_R8G8B8A8_P_EMPTY_A8_RGBA32 + nr_R8G8B8A8_P_EMPTY_R8G8B8A8_N + nr_R8G8B8A8_P_EMPTY_R8G8B8A8_N_A8 + nr_R8G8B8A8_P_EMPTY_R8G8B8A8_P + nr_R8G8B8A8_P_EMPTY_R8G8B8A8_P_A8 + nr_R8G8B8A8_P_R8G8B8A8_P_A8_RGBA32 + nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N + nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_A8 + nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_P + nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_P_A8 +; nr_R8G8B8_EMPTY_A8_RGBA32 + nr_R8G8B8_R8G8B8_A8_RGBA32 + nr_R8G8B8_R8G8B8_R8G8B8A8_N + nr_R8G8B8_R8G8B8_R8G8B8A8_P + nr_active_object_add_listener + nr_active_object_get_type + nr_active_object_remove_listener_by_data + nr_blit_pixblock_mask_rgba32 + nr_blit_pixblock_pixblock_alpha + nr_blit_pixblock_pixblock_mask + nr_compose_pixblock_pixblock_pixel + nr_emit_fail_warning + nr_flat_free_list + nr_flat_free_one + nr_flat_insert_sorted + nr_flat_new_full + nr_lgradient_renderer_setup + nr_matrix_invert + nr_matrix_set_rotate + nr_matrix_set_scale + nr_matrix_set_translate + nr_matrix_multiply + nr_object_check_instance_cast + nr_object_check_instance_type + nr_object_delete + nr_object_get_type + nr_object_new + nr_object_ref + nr_object_register_type + nr_object_release + nr_object_setup + nr_object_unref + nr_path_duplicate_transform + nr_path_matrix_bbox_nion + nr_path_matrix_point_bbox_wind_distance + nr_pixblock_draw_line_rgba32 + nr_pixblock_free + nr_pixblock_new + nr_pixblock_release + nr_pixblock_render_gray_noise + nr_pixblock_render_svp_mask_or + nr_pixblock_setup + nr_pixblock_setup_extern + nr_pixblock_setup_fast + nr_pixelstore_16K_free + nr_pixelstore_16K_new + nr_pixelstore_4K_free + nr_pixelstore_4K_new + nr_pixelstore_64K_free + nr_pixelstore_64K_new + nr_rect_d_intersect + nr_rect_d_matrix_transform + nr_rect_d_union + nr_rect_l_intersect + nr_rect_l_union + nr_rgradient_renderer_setup + nr_svp_bbox + nr_svp_free + nr_svp_point_distance + nr_svp_point_wind + nr_type_is_a + nr_vertex_free_list + nr_vertex_free_one + nr_vertex_new + nr_vertex_new_xy + nr_vertex_reverse_list diff --git a/src/libnr/makefile.in b/src/libnr/makefile.in new file mode 100644 index 000000000..8c80244ab --- /dev/null +++ b/src/libnr/makefile.in @@ -0,0 +1,17 @@ +# Convenience stub makefile to call the real Makefile. + +@SET_MAKE@ + +# Explicit so that it's the default rule. +all: + cd .. && $(MAKE) libnr/all + +clean %.a %.o: + cd .. && $(MAKE) libnr/$@ + +.PHONY: all clean + +OBJEXT = @OBJEXT@ + +.SUFFIXES: +.SUFFIXES: .a .$(OBJEXT) diff --git a/src/libnr/n-art-bpath.h b/src/libnr/n-art-bpath.h new file mode 100644 index 000000000..abce499e0 --- /dev/null +++ b/src/libnr/n-art-bpath.h @@ -0,0 +1,61 @@ +#ifndef SEEN_LIBNR_N_ART_BPATH_H +#define SEEN_LIBNR_N_ART_BPATH_H + +/** \file + * NArtBpath: old-style path segment. + */ + +#include "libnr/nr-point.h" +#include "libnr/nr-path-code.h" + +/** + * Old-style path segment. + * + * Arrays of paths segment start with a MOVETO or MOVETO_OPEN segment + * where the former indicates the beginning of a closed subpath. + * \see subpath_from_bpath() + */ +class NArtBpath { +public: + NRPathcode code; ///< Type of segment + double x1, y1; ///< Position of control point in case of NR_CURVETO + double x2, y2; ///< Position of control point in case of NR_CURVETO + double x3, y3; ///< Position of next point + + /// Convert i-th position data pair to Point object + /// \pre 1 <= i <= 3 + NR::Point c(unsigned const i) const { + switch (i) { + case 1: return NR::Point(x1, y1); + case 2: return NR::Point(x2, y2); + case 3: return NR::Point(x3, y3); + default: abort(); + } + } + + /// Set i-th position data pair from Point + /// \pre 1 <= i <= 3 + void setC(unsigned const i, NR::Point const &p) { + using NR::X; using NR::Y; + switch (i) { + case 1: x1 = p[X]; y1 = p[Y]; break; + case 2: x2 = p[X]; y2 = p[Y]; break; + case 3: x3 = p[X]; y3 = p[Y]; break; + default: abort(); + } + } +}; + + +#endif /* !SEEN_LIBNR_N_ART_BPATH_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 : diff --git a/src/libnr/nr-blit.cpp b/src/libnr/nr-blit.cpp new file mode 100644 index 000000000..2a93bc9bd --- /dev/null +++ b/src/libnr/nr-blit.cpp @@ -0,0 +1,300 @@ +#define __NR_BLIT_C__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * + * This code is in public domain + */ + +#include "nr-pixops.h" +#include "nr-compose.h" +#include "nr-blit.h" + +void +nr_blit_pixblock_pixblock_alpha (NRPixBlock *d, NRPixBlock *s, unsigned int alpha) +{ + NRRectL clip; + unsigned char *dpx, *spx; + int dbpp, sbpp; + int w, h; + + if (alpha == 0) return; + if (s->empty) return; + /* fixme: */ + if (s->mode == NR_PIXBLOCK_MODE_A8) return; + /* fixme: */ + if (s->mode == NR_PIXBLOCK_MODE_R8G8B8) return; + + /* + * Possible variants as of now: + * + * 0. SRC EP - DST EP * + * 1. SRC EP - DST EN * + * 2. SRC EP - DST P * + * 3. SRC EP - DST N * + * 4. SRC EN - DST EP * + * 5. SRC EN - DST EN * + * 6. SRC EN - DST P * + * 7. SRC EN - DST N * + * 8. SRC P - DST EP * + * 9. SRC P - DST EN * + * A. SRC P - DST P * + * B. SRC P - DST N * + * C. SRC N - DST EP * + * D. SRC N - DST EN * + * E. SRC N - DST P * + * F. SRC N - DST N * + * + */ + + nr_rect_l_intersect (&clip, &d->area, &s->area); + + if (nr_rect_l_test_empty (&clip)) return; + + /* Pointers */ + dbpp = NR_PIXBLOCK_BPP (d); + dpx = NR_PIXBLOCK_PX (d) + (clip.y0 - d->area.y0) * d->rs + dbpp * (clip.x0 - d->area.x0); + sbpp = NR_PIXBLOCK_BPP (s); + spx = NR_PIXBLOCK_PX (s) + (clip.y0 - s->area.y0) * s->rs + sbpp * (clip.x0 - s->area.x0); + w = clip.x1 - clip.x0; + h = clip.y1 - clip.y0; + + switch (d->mode) { + case NR_PIXBLOCK_MODE_A8: + /* No rendering into alpha at moment */ + break; + case NR_PIXBLOCK_MODE_R8G8B8: + if (s->mode == NR_PIXBLOCK_MODE_R8G8B8A8P) { + nr_R8G8B8_R8G8B8_R8G8B8A8_P (dpx, w, h, d->rs, spx, s->rs, alpha); + } else { + nr_R8G8B8_R8G8B8_R8G8B8A8_N (dpx, w, h, d->rs, spx, s->rs, alpha); + } + break; + case NR_PIXBLOCK_MODE_R8G8B8A8P: + if (d->empty) { + if (s->mode == NR_PIXBLOCK_MODE_R8G8B8A8P) { + /* Case 8 */ + nr_R8G8B8A8_P_EMPTY_R8G8B8A8_P (dpx, w, h, d->rs, spx, s->rs, alpha); + } else { + /* Case C */ + nr_R8G8B8A8_P_EMPTY_R8G8B8A8_N (dpx, w, h, d->rs, spx, s->rs, alpha); + } + } else { + if (s->mode == NR_PIXBLOCK_MODE_R8G8B8A8P) { + /* case A */ + nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_P (dpx, w, h, d->rs, spx, s->rs, alpha); + } else { + /* case E */ + nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N (dpx, w, h, d->rs, spx, s->rs, alpha); + } + } + break; + case NR_PIXBLOCK_MODE_R8G8B8A8N: + if (d->empty) { + if (s->mode == NR_PIXBLOCK_MODE_R8G8B8A8P) { + /* Case 9 */ + nr_R8G8B8A8_N_EMPTY_R8G8B8A8_P (dpx, w, h, d->rs, spx, s->rs, alpha); + } else { + /* Case D */ + nr_R8G8B8A8_N_EMPTY_R8G8B8A8_N (dpx, w, h, d->rs, spx, s->rs, alpha); + } + } else { + if (s->mode == NR_PIXBLOCK_MODE_R8G8B8A8P) { + /* case B */ + nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_P (dpx, w, h, d->rs, spx, s->rs, alpha); + } else { + /* case F */ + nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_N (dpx, w, h, d->rs, spx, s->rs, alpha); + } + } + break; + } +} + +void +nr_blit_pixblock_pixblock_mask (NRPixBlock *d, NRPixBlock *s, NRPixBlock *m) +{ + NRRectL clip; + unsigned char *dpx, *spx, *mpx; + int dbpp, sbpp; + int w, h; + + if (s->empty) return; + /* fixme: */ + if (s->mode == NR_PIXBLOCK_MODE_A8) return; + /* fixme: */ + if (s->mode == NR_PIXBLOCK_MODE_R8G8B8) return; + + /* + * Possible variants as of now: + * + * 0. SRC EP - DST EP * + * 1. SRC EP - DST EN * + * 2. SRC EP - DST P * + * 3. SRC EP - DST N * + * 4. SRC EN - DST EP * + * 5. SRC EN - DST EN * + * 6. SRC EN - DST P * + * 7. SRC EN - DST N * + * 8. SRC P - DST EP * + * 9. SRC P - DST EN * + * A. SRC P - DST P * + * B. SRC P - DST N * + * C. SRC N - DST EP * + * D. SRC N - DST EN * + * E. SRC N - DST P * + * F. SRC N - DST N * + * + */ + + nr_rect_l_intersect (&clip, &d->area, &s->area); + nr_rect_l_intersect (&clip, &clip, &m->area); + + if (nr_rect_l_test_empty (&clip)) return; + + /* Pointers */ + dbpp = NR_PIXBLOCK_BPP (d); + dpx = NR_PIXBLOCK_PX (d) + (clip.y0 - d->area.y0) * d->rs + dbpp * (clip.x0 - d->area.x0); + sbpp = NR_PIXBLOCK_BPP (s); + spx = NR_PIXBLOCK_PX (s) + (clip.y0 - s->area.y0) * s->rs + sbpp * (clip.x0 - s->area.x0); + mpx = NR_PIXBLOCK_PX (m) + (clip.y0 - m->area.y0) * m->rs + 1 * (clip.x0 - m->area.x0); + w = clip.x1 - clip.x0; + h = clip.y1 - clip.y0; + + switch (d->mode) { + case NR_PIXBLOCK_MODE_A8: + /* No rendering into alpha at moment */ + break; + case NR_PIXBLOCK_MODE_R8G8B8: + if (s->mode == NR_PIXBLOCK_MODE_R8G8B8A8P) { + nr_R8G8B8_R8G8B8_R8G8B8A8_P_A8 (dpx, w, h, d->rs, spx, s->rs, mpx, m->rs); + } else { + nr_R8G8B8_R8G8B8_R8G8B8A8_N_A8 (dpx, w, h, d->rs, spx, s->rs, mpx, m->rs); + } + break; + case NR_PIXBLOCK_MODE_R8G8B8A8P: + if (d->empty) { + if (s->mode == NR_PIXBLOCK_MODE_R8G8B8A8P) { + /* Case 8 */ + nr_R8G8B8A8_P_EMPTY_R8G8B8A8_P_A8 (dpx, w, h, d->rs, spx, s->rs, mpx, m->rs); + } else { + /* Case C */ + nr_R8G8B8A8_P_EMPTY_R8G8B8A8_N_A8 (dpx, w, h, d->rs, spx, s->rs, mpx, m->rs); + } + } else { + if (s->mode == NR_PIXBLOCK_MODE_R8G8B8A8P) { + /* case A */ + nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_P_A8 (dpx, w, h, d->rs, spx, s->rs, mpx, m->rs); + } else { + /* case E */ + nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_A8 (dpx, w, h, d->rs, spx, s->rs, mpx, m->rs); + } + } + break; + case NR_PIXBLOCK_MODE_R8G8B8A8N: + if (d->empty) { + if (s->mode == NR_PIXBLOCK_MODE_R8G8B8A8P) { + /* Case 9 */ + nr_R8G8B8A8_N_EMPTY_R8G8B8A8_P_A8 (dpx, w, h, d->rs, spx, s->rs, mpx, m->rs); + } else { + /* Case D */ + nr_R8G8B8A8_N_EMPTY_R8G8B8A8_N_A8 (dpx, w, h, d->rs, spx, s->rs, mpx, m->rs); + } + } else { + if (s->mode == NR_PIXBLOCK_MODE_R8G8B8A8P) { + /* case B */ + nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_P_A8 (dpx, w, h, d->rs, spx, s->rs, mpx, m->rs); + } else { + /* case F */ + nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_N_A8 (dpx, w, h, d->rs, spx, s->rs, mpx, m->rs); + } + } + break; + } +} + +void +nr_blit_pixblock_mask_rgba32 (NRPixBlock *d, NRPixBlock *m, unsigned long rgba) +{ + if (!(rgba & 0xff)) return; + + if (m) { + NRRectL clip; + unsigned char *dpx, *mpx; + int w, h; + + if (m->mode != NR_PIXBLOCK_MODE_A8) return; + + if (!nr_rect_l_test_intersect (&d->area, &m->area)) return; + + nr_rect_l_intersect (&clip, &d->area, &m->area); + + /* Pointers */ + dpx = NR_PIXBLOCK_PX (d) + (clip.y0 - d->area.y0) * d->rs + NR_PIXBLOCK_BPP (d) * (clip.x0 - d->area.x0); + mpx = NR_PIXBLOCK_PX (m) + (clip.y0 - m->area.y0) * m->rs + (clip.x0 - m->area.x0); + w = clip.x1 - clip.x0; + h = clip.y1 - clip.y0; + + if (d->empty) { + if (d->mode == NR_PIXBLOCK_MODE_R8G8B8) { + nr_R8G8B8_R8G8B8_A8_RGBA32 (dpx, w, h, d->rs, mpx, m->rs, rgba); + } else if (d->mode == NR_PIXBLOCK_MODE_R8G8B8A8P) { + nr_R8G8B8A8_P_EMPTY_A8_RGBA32 (dpx, w, h, d->rs, mpx, m->rs, rgba); + } else { + nr_R8G8B8A8_N_EMPTY_A8_RGBA32 (dpx, w, h, d->rs, mpx, m->rs, rgba); + } + d->empty = 0; + } else { + if (d->mode == NR_PIXBLOCK_MODE_R8G8B8) { + nr_R8G8B8_R8G8B8_A8_RGBA32 (dpx, w, h, d->rs, mpx, m->rs, rgba); + } else if (d->mode == NR_PIXBLOCK_MODE_R8G8B8A8P) { + nr_R8G8B8A8_P_R8G8B8A8_P_A8_RGBA32 (dpx, w, h, d->rs, mpx, m->rs, rgba); + } else { + nr_R8G8B8A8_N_R8G8B8A8_N_A8_RGBA32 (dpx, w, h, d->rs, mpx, m->rs, rgba); + } + } + } else { + unsigned int r, g, b, a; + int x, y; + r = NR_RGBA32_R (rgba); + g = NR_RGBA32_G (rgba); + b = NR_RGBA32_B (rgba); + a = NR_RGBA32_A (rgba); + for (y = d->area.y0; y < d->area.y1; y++) { + unsigned char *p; + p = NR_PIXBLOCK_PX (d) + (y - d->area.y0) * d->rs; + for (x = d->area.x0; x < d->area.x1; x++) { + unsigned int da; + switch (d->mode) { + case NR_PIXBLOCK_MODE_R8G8B8: + p[0] = NR_COMPOSEN11 (r, a, p[0]); + p[1] = NR_COMPOSEN11 (g, a, p[1]); + p[2] = NR_COMPOSEN11 (b, a, p[2]); + p += 3; + break; + case NR_PIXBLOCK_MODE_R8G8B8A8P: + p[0] = NR_COMPOSENPP (r, a, p[0], p[3]); + p[1] = NR_COMPOSENPP (g, a, p[1], p[3]); + p[2] = NR_COMPOSENPP (b, a, p[2], p[3]); + p[3] = (65025 - (255 - a) * (255 - p[3]) + 127) / 255; + p += 4; + break; + case NR_PIXBLOCK_MODE_R8G8B8A8N: + da = 65025 - (255 - a) * (255 - p[3]); + p[0] = NR_COMPOSENNN_A7 (r, a, p[0], p[3], da); + p[1] = NR_COMPOSENNN_A7 (g, a, p[1], p[3], da); + p[2] = NR_COMPOSENNN_A7 (b, a, p[2], p[3], da); + p[3] = (da + 127) / 255; + p += 4; + break; + default: + break; + } + } + } + } +} + diff --git a/src/libnr/nr-blit.h b/src/libnr/nr-blit.h new file mode 100644 index 000000000..3221c8187 --- /dev/null +++ b/src/libnr/nr-blit.h @@ -0,0 +1,32 @@ +#ifndef __NR_BLIT_H__ +#define __NR_BLIT_H__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * + * This code is in public domain + */ + +#include + +#define nr_blit_pixblock_pixblock(d,s) nr_blit_pixblock_pixblock_alpha (d, s, 255) + +void nr_blit_pixblock_pixblock_alpha (NRPixBlock *d, NRPixBlock *s, unsigned int alpha); +void nr_blit_pixblock_pixblock_mask (NRPixBlock *d, NRPixBlock *s, NRPixBlock *m); +void nr_blit_pixblock_mask_rgba32 (NRPixBlock *d, NRPixBlock *m, unsigned long rgba32); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-compose-transform.cpp b/src/libnr/nr-compose-transform.cpp new file mode 100644 index 000000000..bb5022a74 --- /dev/null +++ b/src/libnr/nr-compose-transform.cpp @@ -0,0 +1,360 @@ +#define __NR_COMPOSE_TRANSFORM_C__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * + * This code is in public domain + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "nr-pixops.h" +#include "nr-matrix.h" + + +#ifdef WITH_MMX +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ +/* fixme: */ +int nr_have_mmx (void); +void nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_0 (unsigned char *px, int w, int h, int rs, + const unsigned char *spx, int sw, int sh, int srs, + const long *FFd2s, unsigned int alpha); +void nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_n (unsigned char *px, int w, int h, int rs, + const unsigned char *spx, int sw, int sh, int srs, + const long *FFd2s, const long *FF_S, unsigned int alpha, int dbits); +#define NR_PIXOPS_MMX (1 && nr_have_mmx ()) +#ifdef __cplusplus +} +#endif //__cplusplus +#endif + +/* fixme: Implement missing (Lauris) */ +/* fixme: PREMUL colors before calculating average (Lauris) */ + +/* Fixed point precision */ +#define FBITS 12 + +void nr_R8G8B8A8_N_EMPTY_R8G8B8A8_N_TRANSFORM (unsigned char *px, int w, int h, int rs, + const unsigned char *spx, int sw, int sh, int srs, + const NR::Matrix &d2s, unsigned int alpha, int xd, int yd); +void nr_R8G8B8A8_N_EMPTY_R8G8B8A8_P_TRANSFORM (unsigned char *px, int w, int h, int rs, + const unsigned char *spx, int sw, int sh, int srs, + const NR::Matrix &d2s, unsigned int alpha, int xd, int yd); +void nr_R8G8B8A8_P_EMPTY_R8G8B8A8_N_TRANSFORM (unsigned char *px, int w, int h, int rs, + const unsigned char *spx, int sw, int sh, int srs, + const NR::Matrix &d2s, unsigned int alpha, int xd, int yd); +void nr_R8G8B8A8_P_EMPTY_R8G8B8A8_P_TRANSFORM (unsigned char *px, int w, int h, int rs, + const unsigned char *spx, int sw, int sh, int srs, + const NR::Matrix &d2s, unsigned int alpha, int xd, int yd); + +void +nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_N_TRANSFORM (unsigned char *px, int w, int h, int rs, + const unsigned char *spx, int sw, int sh, int srs, + const NR::Matrix &d2s, unsigned int alpha, int xd, int yd) +{ + int xsize, ysize, size, dbits; + long FFs_x_x, FFs_x_y, FFs_y_x, FFs_y_y, FFs__x, FFs__y; + long FFs_x_x_S, FFs_x_y_S, FFs_y_x_S, FFs_y_y_S; + /* Subpixel positions */ + int FF_sx_S[256]; + int FF_sy_S[256]; + unsigned char *d0; + int FFsx0, FFsy0; + int x, y; + + if (alpha == 0) return; + + xsize = (1 << xd); + ysize = (1 << yd); + size = xsize * ysize; + dbits = xd + yd; + + /* Set up fixed point matrix */ + FFs_x_x = (long) (d2s[0] * (1 << FBITS) + 0.5); + FFs_x_y = (long) (d2s[1] * (1 << FBITS) + 0.5); + FFs_y_x = (long) (d2s[2] * (1 << FBITS) + 0.5); + FFs_y_y = (long) (d2s[3] * (1 << FBITS) + 0.5); + FFs__x = (long) (d2s[4] * (1 << FBITS) + 0.5); + FFs__y = (long) (d2s[5] * (1 << FBITS) + 0.5); + + FFs_x_x_S = FFs_x_x >> xd; + FFs_x_y_S = FFs_x_y >> xd; + FFs_y_x_S = FFs_y_x >> yd; + FFs_y_y_S = FFs_y_y >> yd; + + /* Set up subpixel matrix */ + /* fixme: We can calculate that in floating point (Lauris) */ + for (y = 0; y < ysize; y++) { + for (x = 0; x < xsize; x++) { + FF_sx_S[y * xsize + x] = FFs_x_x_S * x + FFs_y_x_S * y; + FF_sy_S[y * xsize + x] = FFs_x_y_S * x + FFs_y_y_S * y; + } + } + + d0 = px; + FFsx0 = FFs__x; + FFsy0 = FFs__y; + + for (y = 0; y < h; y++) { + unsigned char *d; + long FFsx, FFsy; + d = d0; + FFsx = FFsx0; + FFsy = FFsy0; + for (x = 0; x < w; x++) { + unsigned int r, g, b, a; + long sx, sy; + int i; + r = g = b = a = 0; + for (i = 0; i < size; i++) { + sx = (FFsx + FF_sx_S[i]) >> FBITS; + if ((sx >= 0) && (sx < sw)) { + sy = (FFsy + FF_sy_S[i]) >> FBITS; + if ((sy >= 0) && (sy < sh)) { + const unsigned char *s; + unsigned int ca; + s = spx + sy * srs + sx * 4; + ca = NR_PREMUL (s[3], alpha); + r += NR_PREMUL (s[0], ca); + g += NR_PREMUL (s[1], ca); + b += NR_PREMUL (s[2], ca); + a += ca; + } + } + } + a >>= dbits; + if (a != 0) { + r = r >> dbits; + g = g >> dbits; + b = b >> dbits; + if (a == 255) { + /* Transparent BG, premul src */ + d[0] = r; + d[1] = g; + d[2] = b; + d[3] = a; + } else { + unsigned int ca; + /* Full composition */ + ca = 65025 - (255 - a) * (255 - d[3]); + d[0] = NR_COMPOSENNN_A7 (r, a, d[0], d[3], ca); + d[1] = NR_COMPOSENNN_A7 (g, a, d[1], d[3], ca); + d[2] = NR_COMPOSENNN_A7 (b, a, d[2], d[3], ca); + d[3] = (ca + 127) / 255; + } + } + /* Advance pointers */ + FFsx += FFs_x_x; + FFsy += FFs_x_y; + d += 4; + } + FFsx0 += FFs_y_x; + FFsy0 += FFs_y_y; + d0 += rs; + } +} + +void nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_P_TRANSFORM (unsigned char *px, int w, int h, int rs, + const unsigned char *spx, int sw, int sh, int srs, + const NR::Matrix &d2s, unsigned int alpha, int xd, int yd); + +static void +nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_0 (unsigned char *px, int w, int h, int rs, + const unsigned char *spx, int sw, int sh, int srs, + const long *FFd2s, unsigned int alpha) +{ + unsigned char *d0; + int FFsx0, FFsy0; + int x, y; + + d0 = px; + FFsx0 = FFd2s[4]; + FFsy0 = FFd2s[5]; + + for (y = 0; y < h; y++) { + unsigned char *d; + long FFsx, FFsy; + d = d0; + FFsx = FFsx0; + FFsy = FFsy0; + for (x = 0; x < w; x++) { + long sx, sy; + sx = FFsx >> FBITS; + if ((sx >= 0) && (sx < sw)) { + sy = FFsy >> FBITS; + if ((sy >= 0) && (sy < sh)) { + const unsigned char *s; + unsigned int a; + s = spx + sy * srs + sx * 4; + a = NR_PREMUL (s[3], alpha); + if (a != 0) { + if ((a == 255) || (d[3] == 0)) { + /* Transparent BG, premul src */ + d[0] = NR_PREMUL (s[0], a); + d[1] = NR_PREMUL (s[1], a); + d[2] = NR_PREMUL (s[2], a); + d[3] = a; + } else { + d[0] = NR_COMPOSENPP (s[0], a, d[0], d[3]); + d[1] = NR_COMPOSENPP (s[1], a, d[1], d[3]); + d[2] = NR_COMPOSENPP (s[2], a, d[2], d[3]); + d[3] = (65025 - (255 - a) * (255 - d[3]) + 127) / 255; + } + } + } + } + /* Advance pointers */ + FFsx += FFd2s[0]; + FFsy += FFd2s[1]; + d += 4; + } + FFsx0 += FFd2s[2]; + FFsy0 += FFd2s[3]; + d0 += rs; + } +} + +static void +nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_n (unsigned char *px, int w, int h, int rs, + const unsigned char *spx, int sw, int sh, int srs, + const long *FFd2s, const long *FF_S, unsigned int alpha, int dbits) +{ + int size; + unsigned char *d0; + int FFsx0, FFsy0; + int x, y; + + size = (1 << dbits); + unsigned alpha_rounding_fix = size * 255; + unsigned rgb_rounding_fix = size * (255 * 256); + if (alpha > 127) ++alpha; + + d0 = px; + FFsx0 = FFd2s[4]; + FFsy0 = FFd2s[5]; + + for (y = 0; y < h; y++) { + unsigned char *d; + long FFsx, FFsy; + d = d0; + FFsx = FFsx0; + FFsy = FFsy0; + for (x = 0; x < w; x++) { + unsigned int r, g, b, a; + int i; + r = g = b = a = 0; + for (i = 0; i < size; i++) { + long sx, sy; + sx = (FFsx + FF_S[2 * i]) >> FBITS; + if ((sx >= 0) && (sx < sw)) { + sy = (FFsy + FF_S[2 * i + 1]) >> FBITS; + if ((sy >= 0) && (sy < sh)) { + const unsigned char *s; + unsigned int ca; + s = spx + sy * srs + sx * 4; + ca = s[3] * alpha; + r += s[0] * ca; + g += s[1] * ca; + b += s[2] * ca; + a += ca; + } + } + } + a = (a + alpha_rounding_fix) >> (8 + dbits); + if (a != 0) { + r = (r + rgb_rounding_fix) >> (16 + dbits); + g = (g + rgb_rounding_fix) >> (16 + dbits); + b = (b + rgb_rounding_fix) >> (16 + dbits); + if ((a == 255) || (d[3] == 0)) { + /* Transparent BG, premul src */ + d[0] = r; + d[1] = g; + d[2] = b; + d[3] = a; + } else { + d[0] = NR_COMPOSEPPP (r, a, d[0], d[3]); + d[1] = NR_COMPOSEPPP (g, a, d[1], d[3]); + d[2] = NR_COMPOSEPPP (b, a, d[2], d[3]); + d[3] = (65025 - (255 - a) * (255 - d[3]) + 127) / 255; + } + } + /* Advance pointers */ + FFsx += FFd2s[0]; + FFsy += FFd2s[1]; + d += 4; + } + FFsx0 += FFd2s[2]; + FFsy0 += FFd2s[3]; + d0 += rs; + } +} + +void nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM (unsigned char *px, int w, int h, int rs, + const unsigned char *spx, int sw, int sh, int srs, + const NR::Matrix &d2s, unsigned int alpha, int xd, int yd) +{ + int dbits; + long FFd2s[6]; + int i; + + if (alpha == 0) return; + + dbits = xd + yd; + + for (i = 0; i < 6; i++) { + FFd2s[i] = (long) (d2s[i] * (1 << FBITS) + 0.5); + } + + if (dbits == 0) { +#ifdef WITH_MMX + if (NR_PIXOPS_MMX) { + /* WARNING: MMX composer REQUIRES w > 0 and h > 0 */ + nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_0 (px, w, h, rs, spx, sw, sh, srs, FFd2s, alpha); + return; + } +#endif + nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_0 (px, w, h, rs, spx, sw, sh, srs, FFd2s, alpha); + } else { + int xsize, ysize; + long FFs_x_x_S, FFs_x_y_S, FFs_y_x_S, FFs_y_y_S; + long FF_S[2 * 256]; + int x, y; + + xsize = (1 << xd); + ysize = (1 << yd); + + FFs_x_x_S = FFd2s[0] >> xd; + FFs_x_y_S = FFd2s[1] >> xd; + FFs_y_x_S = FFd2s[2] >> yd; + FFs_y_y_S = FFd2s[3] >> yd; + + /* Set up subpixel matrix */ + /* fixme: We can calculate that in floating point (Lauris) */ + for (y = 0; y < ysize; y++) { + for (x = 0; x < xsize; x++) { + FF_S[2 * (y * xsize + x)] = FFs_x_x_S * x + FFs_y_x_S * y; + FF_S[2 * (y * xsize + x) + 1] = FFs_x_y_S * x + FFs_y_y_S * y; + } + } + +#ifdef WITH_MMX + if (NR_PIXOPS_MMX) { + /* WARNING: MMX composer REQUIRES w > 0 and h > 0 */ + nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_n (px, w, h, rs, spx, sw, sh, srs, FFd2s, FF_S, alpha, dbits); + return; + } +#endif + nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_n (px, w, h, rs, spx, sw, sh, srs, FFd2s, FF_S, alpha, dbits); + } +} + +void nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_P_TRANSFORM (unsigned char *px, int w, int h, int rs, + const unsigned char *spx, int sw, int sh, int srs, + const NR::Matrix &d2s, unsigned int alpha, int xd, int yd); diff --git a/src/libnr/nr-compose-transform.h b/src/libnr/nr-compose-transform.h new file mode 100644 index 000000000..7ffb20074 --- /dev/null +++ b/src/libnr/nr-compose-transform.h @@ -0,0 +1,43 @@ +#ifndef __NR_COMPOSE_TRANSFORM_H__ +#define __NR_COMPOSE_TRANSFORM_H__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * + * This code is in public domain + */ + +#include + +/* FINAL DST SRC */ + +void nr_R8G8B8A8_N_EMPTY_R8G8B8A8_N_TRANSFORM (unsigned char *px, int w, int h, int rs, + const unsigned char *spx, int sw, int sh, int srs, + const NR::Matrix &d2s, unsigned int alpha, int xd, int yd); +void nr_R8G8B8A8_N_EMPTY_R8G8B8A8_P_TRANSFORM (unsigned char *px, int w, int h, int rs, + const unsigned char *spx, int sw, int sh, int srs, + const NR::Matrix &d2s, unsigned int alpha, int xd, int yd); +void nr_R8G8B8A8_P_EMPTY_R8G8B8A8_N_TRANSFORM (unsigned char *px, int w, int h, int rs, + const unsigned char *spx, int sw, int sh, int srs, + const NR::Matrix &d2s, unsigned int alpha, int xd, int yd); +void nr_R8G8B8A8_P_EMPTY_R8G8B8A8_P_TRANSFORM (unsigned char *px, int w, int h, int rs, + const unsigned char *spx, int sw, int sh, int srs, + const NR::Matrix &d2s, unsigned int alpha, int xd, int yd); + +void nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_N_TRANSFORM (unsigned char *px, int w, int h, int rs, + const unsigned char *spx, int sw, int sh, int srs, + const NR::Matrix &d2s, unsigned int alpha, int xd, int yd); +void nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_P_TRANSFORM (unsigned char *px, int w, int h, int rs, + const unsigned char *spx, int sw, int sh, int srs, + const NR::Matrix &d2s, unsigned int alpha, int xd, int yd); +void nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM (unsigned char *px, int w, int h, int rs, + const unsigned char *spx, int sw, int sh, int srs, + const NR::Matrix &d2s, unsigned int alpha, int xd, int yd); +void nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_P_TRANSFORM (unsigned char *px, int w, int h, int rs, + const unsigned char *spx, int sw, int sh, int srs, + const NR::Matrix &d2s, unsigned int alpha, int xd, int yd); + +#endif diff --git a/src/libnr/nr-compose.cpp b/src/libnr/nr-compose.cpp new file mode 100644 index 000000000..8eed6e84c --- /dev/null +++ b/src/libnr/nr-compose.cpp @@ -0,0 +1,986 @@ +#define __NR_COMPOSE_C__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * + * This code is in public domain + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include "nr-pixops.h" + +#ifdef WITH_MMX +/* fixme: */ +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ +int nr_have_mmx (void); +void nr_mmx_R8G8B8A8_P_EMPTY_A8_RGBAP (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned char *c); +void nr_mmx_R8G8B8A8_P_R8G8B8A8_P_A8_RGBAP (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned char *c); +void nr_mmx_R8G8B8_R8G8B8_R8G8B8A8_P (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha); +#define NR_PIXOPS_MMX nr_have_mmx () +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif + +void +nr_R8G8B8A8_N_EMPTY_R8G8B8A8_N (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha) +{ + int r, c; + + for (r = 0; r < h; r++) { + if (alpha == 0) { + memset (px, 0x0, 4 * w); + } else if (alpha == 255) { + memcpy (px, spx, 4 * w); + } else { + const unsigned char *s; + unsigned char *d; + d = px; + s = spx; + for (c = 0; c < w; c++) { + *d++ = *s++; + *d++ = *s++; + *d++ = *s++; + *d++ = NR_PREMUL (*s, alpha); + s++; + } + } + px += rs; + spx += srs; + } +} + +void +nr_R8G8B8A8_N_EMPTY_R8G8B8A8_P (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha) +{ + int r, c; + + for (r = 0; r < h; r++) { + if (alpha == 0) { + memset (px, 0x0, 4 * w); + } else { + const unsigned char *s; + unsigned char *d; + s = spx; + d = px; + for (c = 0; c < w; c++) { + unsigned int a; + a = NR_PREMUL (s[3], alpha); + d[0] = s[0]; + d[1] = s[1]; + d[2] = s[2]; + d[3] = a; + d += 4; + s += 4; + } + px += rs; + spx += srs; + } + } +} + +void +nr_R8G8B8A8_P_EMPTY_R8G8B8A8_N (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha) +{ + int r, c; + + for (r = 0; r < h; r++) { + unsigned char *d, *s; + d = (unsigned char *) px; + s = (unsigned char *) spx; + for (c = 0; c < w; c++) { + unsigned int a; + a = (s[3] * alpha + 127) / 255; + d[0] = (s[0] * a + 127) / 255; + d[1] = (s[1] * a + 127) / 255; + d[2] = (s[2] * a + 127) / 255; + d[3] = a; + d += 4; + s += 4; + } + px += rs; + spx += srs; + } +} + +void +nr_R8G8B8A8_P_EMPTY_R8G8B8A8_P (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha) +{ + int r, c; + + for (r = 0; r < h; r++) { + unsigned char *d, *s; + d = (unsigned char *) px; + s = (unsigned char *) spx; + for (c = 0; c < w; c++) { + if (alpha == 255) { + d[0] = s[0]; + d[1] = s[1]; + d[2] = s[2]; + d[3] = s[3]; + } else { + d[0] = NR_PREMUL (s[0], alpha); + d[1] = NR_PREMUL (s[1], alpha); + d[2] = NR_PREMUL (s[2], alpha); + d[3] = NR_PREMUL (s[3], alpha); + } + d += 4; + s += 4; + } + px += rs; + spx += srs; + } +} + +void +nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_N (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha) +{ + int r, c; + + for (r = 0; r < h; r++) { + unsigned char *d, *s; + d = (unsigned char *) px; + s = (unsigned char *) spx; + for (c = 0; c < w; c++) { + unsigned int a; + a = NR_PREMUL (s[3], alpha); + if (a == 0) { + /* Transparent FG, NOP */ + } else if ((a == 255) || (d[3] == 0)) { + /* Full coverage, COPY */ + d[0] = s[0]; + d[1] = s[1]; + d[2] = s[2]; + d[3] = a; + } else { + unsigned int ca; + /* Full composition */ + ca = 65025 - (255 - a) * (255 - d[3]); + d[0] = NR_COMPOSENNN_A7 (s[0], a, d[0], d[3], ca); + d[1] = NR_COMPOSENNN_A7 (s[1], a, d[1], d[3], ca); + d[2] = NR_COMPOSENNN_A7 (s[2], a, d[2], d[3], ca); + d[3] = (ca + 127) / 255; + } + d += 4; + s += 4; + } + px += rs; + spx += srs; + } +} + +void +nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_P (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha) +{ + int r, c; + + for (r = 0; r < h; r++) { + unsigned char *d, *s; + d = (unsigned char *) px; + s = (unsigned char *) spx; + for (c = 0; c < w; c++) { + unsigned int a; + a = NR_PREMUL (s[3], alpha); + if (a == 0) { + /* Transparent FG, NOP */ + } else if ((a == 255) || (d[3] == 0)) { + /* Full coverage, demul src */ + d[0] = (s[0] * 255 + (s[3] >> 1)) / s[3]; + d[1] = (s[1] * 255 + (s[3] >> 1)) / s[3]; + d[2] = (s[2] * 255 + (s[3] >> 1)) / s[3]; + d[3] = a; + } else { + if (alpha == 255) { + unsigned int ca; + /* Full composition */ + ca = 65025 - (255 - s[3]) * (255 - d[3]); + d[0] = NR_COMPOSEPNN_A7 (s[0], s[3], d[0], d[3], ca); + d[1] = NR_COMPOSEPNN_A7 (s[1], s[3], d[1], d[3], ca); + d[2] = NR_COMPOSEPNN_A7 (s[2], s[3], d[2], d[3], ca); + d[3] = (65025 - (255 - s[3]) * (255 - d[3]) + 127) / 255; + } else { + // calculate premultiplied from two premultiplieds: + d[0] = NR_COMPOSEPPP(NR_PREMUL (s[0], alpha), a, NR_PREMUL (d[0], d[3]), 0); // last parameter not used + d[1] = NR_COMPOSEPPP(NR_PREMUL (s[1], alpha), a, NR_PREMUL (d[1], d[3]), 0); + d[2] = NR_COMPOSEPPP(NR_PREMUL (s[2], alpha), a, NR_PREMUL (d[2], d[3]), 0); + // total opacity: + d[3] = (65025 - (255 - a) * (255 - d[3]) + 127) / 255; + // un-premultiply channels: + d[0] = d[0]*255/d[3]; + d[1] = d[1]*255/d[3]; + d[2] = d[2]*255/d[3]; + } + } + d += 4; + s += 4; + } + px += rs; + spx += srs; + } +} + +void +nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha) +{ + int r, c; + + for (r = 0; r < h; r++) { + unsigned char *d, *s; + d = (unsigned char *) px; + s = (unsigned char *) spx; + for (c = 0; c < w; c++) { + unsigned int a; + a = NR_PREMUL (s[3], alpha); + if (a == 0) { + /* Transparent FG, NOP */ + } else if ((a == 255) || (d[3] == 0)) { + /* Transparent BG, premul src */ + d[0] = NR_PREMUL (s[0], a); + d[1] = NR_PREMUL (s[1], a); + d[2] = NR_PREMUL (s[2], a); + d[3] = a; + } else { + d[0] = NR_COMPOSENPP (s[0], a, d[0], d[3]); + d[1] = NR_COMPOSENPP (s[1], a, d[1], d[3]); + d[2] = NR_COMPOSENPP (s[2], a, d[2], d[3]); + d[3] = (65025 - (255 - a) * (255 - d[3]) + 127) / 255; + } + d += 4; + s += 4; + } + px += rs; + spx += srs; + } +} + +void +nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_P (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha) +{ + int r, c; + + for (r = 0; r < h; r++) { + unsigned char *d, *s; + d = (unsigned char *) px; + s = (unsigned char *) spx; + for (c = 0; c < w; c++) { + unsigned int a; + a = NR_PREMUL (s[3], alpha); + if (a == 0) { + /* Transparent FG, NOP */ + } else if ((a == 255) || (d[3] == 0)) { + /* Transparent BG, COPY */ + d[0] = NR_PREMUL (s[0], alpha); + d[1] = NR_PREMUL (s[1], alpha); + d[2] = NR_PREMUL (s[2], alpha); + d[3] = NR_PREMUL (s[3], alpha); + } else { + if (alpha == 255) { + /* Simple */ + d[0] = NR_COMPOSEPPP (s[0], s[3], d[0], d[3]); + d[1] = NR_COMPOSEPPP (s[1], s[3], d[1], d[3]); + d[2] = NR_COMPOSEPPP (s[2], s[3], d[2], d[3]); + d[3] = (65025 - (255 - s[3]) * (255 - d[3]) + 127) / 255; + } else { + unsigned int c; + c = NR_PREMUL (s[0], alpha); + d[0] = NR_COMPOSEPPP (c, a, d[0], d[3]); + c = NR_PREMUL (s[1], alpha); + d[1] = NR_COMPOSEPPP (c, a, d[1], d[3]); + c = NR_PREMUL (s[2], alpha); + d[2] = NR_COMPOSEPPP (c, a, d[2], d[3]); + d[3] = (65025 - (255 - a) * (255 - d[3]) + 127) / 255; + } + } + d += 4; + s += 4; + } + px += rs; + spx += srs; + } +} + +/* Masked operations */ + +void +nr_R8G8B8A8_N_EMPTY_R8G8B8A8_N_A8 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, const unsigned char *mpx, int mrs) +{ + int x, y; + + for (y = 0; y < h; y++) { + unsigned char *d, *s, *m; + d = (unsigned char *) px; + s = (unsigned char *) spx; + m = (unsigned char *) mpx; + for (x = 0; x < w; x++) { + d[0] = s[0]; + d[1] = s[1]; + d[2] = s[2]; + d[3] = (s[3] * m[0] + 127) / 255; + d += 4; + s += 4; + m += 1; + } + px += rs; + spx += srs; + mpx += mrs; + } +} + +void +nr_R8G8B8A8_N_EMPTY_R8G8B8A8_P_A8 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, const unsigned char *mpx, int mrs) +{ + int x, y; + + for (y = 0; y < h; y++) { + unsigned char *d, *s, *m; + d = (unsigned char *) px; + s = (unsigned char *) spx; + m = (unsigned char *) mpx; + for (x = 0; x < w; x++) { + unsigned int a; + a = NR_PREMUL (s[3], m[0]); + if (a == 0) { + d[3] = 0; + } else { + d[0] = (s[0] * 255 + (a >> 1)) / a; + d[1] = (s[1] * 255 + (a >> 1)) / a; + d[2] = (s[2] * 255 + (a >> 1)) / a; + d[3] = a; + } + d += 4; + s += 4; + m += 1; + } + px += rs; + spx += srs; + mpx += mrs; + } +} + +void +nr_R8G8B8A8_P_EMPTY_R8G8B8A8_N_A8 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, const unsigned char *mpx, int mrs) +{ + int r, c; + + for (r = 0; r < h; r++) { + unsigned char *d, *s, *m; + d = (unsigned char *) px; + s = (unsigned char *) spx; + m = (unsigned char *) mpx; + for (c = 0; c < w; c++) { + unsigned int a; + a = NR_PREMUL (s[3], m[0]); + d[0] = NR_PREMUL (s[0], a); + d[1] = NR_PREMUL (s[1], a); + d[2] = NR_PREMUL (s[2], a); + d[3] = a; + d += 4; + s += 4; + m += 1; + } + px += rs; + spx += srs; + mpx += mrs; + } +} + +void +nr_R8G8B8A8_P_EMPTY_R8G8B8A8_P_A8 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, const unsigned char *mpx, int mrs) +{ + int r, c; + + for (r = 0; r < h; r++) { + unsigned char *d, *s, *m; + d = (unsigned char *) px; + s = (unsigned char *) spx; + m = (unsigned char *) mpx; + for (c = 0; c < w; c++) { + if (m[0] == 255) { + d[0] = s[0]; + d[1] = s[1]; + d[2] = s[2]; + d[3] = s[3]; + } else { + d[0] = NR_PREMUL (s[0], m[0]); + d[1] = NR_PREMUL (s[1], m[0]); + d[2] = NR_PREMUL (s[2], m[0]); + d[3] = NR_PREMUL (s[3], m[0]); + } + d += 4; + s += 4; + m += 1; + } + px += rs; + spx += srs; + mpx += mrs; + } +} + +void +nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_N_A8 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, const unsigned char *mpx, int mrs) +{ + int r, c; + + for (r = 0; r < h; r++) { + unsigned char *d, *s, *m; + d = (unsigned char *) px; + s = (unsigned char *) spx; + m = (unsigned char *) mpx; + for (c = 0; c < w; c++) { + unsigned int a; + a = NR_PREMUL (s[3], m[0]); + if (a == 0) { + /* Transparent FG, NOP */ + } else if ((a == 255) || (d[3] == 0)) { + /* Full coverage, COPY */ + d[0] = s[0]; + d[1] = s[1]; + d[2] = s[2]; + d[3] = a; + } else { + unsigned int ca; + /* Full composition */ + ca = 65025 - (255 - a) * (255 - d[3]); + d[0] = NR_COMPOSENNN_A7 (s[0], a, d[0], d[3], ca); + d[1] = NR_COMPOSENNN_A7 (s[1], a, d[1], d[3], ca); + d[2] = NR_COMPOSENNN_A7 (s[2], a, d[2], d[3], ca); + d[3] = (ca + 127) / 255; + } + d += 4; + s += 4; + m += 1; + } + px += rs; + spx += srs; + mpx += mrs; + } +} + +void +nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_P_A8 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, const unsigned char *mpx, int mrs) +{ + int r, c; + + for (r = 0; r < h; r++) { + unsigned char *d, *s, *m; + d = (unsigned char *) px; + s = (unsigned char *) spx; + m = (unsigned char *) mpx; + for (c = 0; c < w; c++) { + unsigned int a; + a = NR_PREMUL (s[3], m[0]); + if (a == 0) { + /* Transparent FG, NOP */ + } else if ((a == 255) || (d[3] == 0)) { + /* Full coverage, demul src */ + d[0] = (s[0] * 255 + (s[3] >> 1)) / s[3]; + d[1] = (s[1] * 255 + (s[3] >> 1)) / s[3]; + d[2] = (s[2] * 255 + (s[3] >> 1)) / s[3]; + d[3] = a; + } else { + if (m[0] == 255) { + unsigned int ca; + /* Full composition */ + ca = 65025 - (255 - s[3]) * (255 - d[3]); + d[0] = NR_COMPOSEPNN_A7 (s[0], s[3], d[0], d[3], ca); + d[1] = NR_COMPOSEPNN_A7 (s[1], s[3], d[1], d[3], ca); + d[2] = NR_COMPOSEPNN_A7 (s[2], s[3], d[2], d[3], ca); + d[3] = (65025 - (255 - s[3]) * (255 - d[3]) + 127) / 255; + } else { + // calculate premultiplied from two premultiplieds: + d[0] = NR_COMPOSEPPP(NR_PREMUL (s[0], m[0]), a, NR_PREMUL (d[0], d[3]), 0); // last parameter not used + d[1] = NR_COMPOSEPPP(NR_PREMUL (s[1], m[0]), a, NR_PREMUL (d[1], d[3]), 0); + d[2] = NR_COMPOSEPPP(NR_PREMUL (s[2], m[0]), a, NR_PREMUL (d[2], d[3]), 0); + // total opacity: + d[3] = (65025 - (255 - a) * (255 - d[3]) + 127) / 255; + // un-premultiply channels: + d[0] = d[0]*255/d[3]; + d[1] = d[1]*255/d[3]; + d[2] = d[2]*255/d[3]; + } + } + d += 4; + s += 4; + m += 1; + } + px += rs; + spx += srs; + mpx += mrs; + } +} + +void +nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_A8 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, const unsigned char *mpx, int mrs) +{ + int r, c; + + for (r = 0; r < h; r++) { + unsigned char *d, *s, *m; + d = (unsigned char *) px; + s = (unsigned char *) spx; + m = (unsigned char *) mpx; + for (c = 0; c < w; c++) { + unsigned int a; + a = NR_PREMUL (s[3], m[0]); + if (a == 0) { + /* Transparent FG, NOP */ + } else if ((a == 255) || (d[3] == 0)) { + /* Transparent BG, premul src */ + d[0] = NR_PREMUL (s[0], a); + d[1] = NR_PREMUL (s[1], a); + d[2] = NR_PREMUL (s[2], a); + d[3] = a; + } else { + d[0] = NR_COMPOSENPP (s[0], a, d[0], d[3]); + d[1] = NR_COMPOSENPP (s[1], a, d[1], d[3]); + d[2] = NR_COMPOSENPP (s[2], a, d[2], d[3]); + d[3] = (65025 - (255 - a) * (255 - d[3]) + 127) / 255; + } + d += 4; + s += 4; + m += 1; + } + px += rs; + spx += srs; + mpx += mrs; + } +} + +void +nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_P_A8 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, const unsigned char *mpx, int mrs) +{ + int r, c; + + for (r = 0; r < h; r++) { + unsigned char *d, *s, *m; + d = (unsigned char *) px; + s = (unsigned char *) spx; + m = (unsigned char *) mpx; + for (c = 0; c < w; c++) { + unsigned int a; + a = NR_PREMUL (s[3], m[0]); + if (a == 0) { + /* Transparent FG, NOP */ + } else if ((a == 255) || (d[3] == 0)) { + /* Transparent BG, COPY */ + d[0] = NR_PREMUL (s[0], m[0]); + d[1] = NR_PREMUL (s[1], m[0]); + d[2] = NR_PREMUL (s[2], m[0]); + d[3] = NR_PREMUL (s[3], m[0]); + } else { + if (m[0] == 255) { + /* Simple */ + d[0] = NR_COMPOSEPPP (s[0], s[3], d[0], d[3]); + d[1] = NR_COMPOSEPPP (s[1], s[3], d[1], d[3]); + d[2] = NR_COMPOSEPPP (s[2], s[3], d[2], d[3]); + d[3] = NR_A7_NORMALIZED(s[3], d[3]); + } else { + unsigned int c; + c = NR_PREMUL (s[0], m[0]); + d[0] = NR_COMPOSEPPP (c, a, d[0], d[3]); + c = NR_PREMUL (s[1], m[0]); + d[1] = NR_COMPOSEPPP (c, a, d[1], d[3]); + c = NR_PREMUL (s[2], m[0]); + d[2] = NR_COMPOSEPPP (c, a, d[2], d[3]); + d[3] = NR_A7_NORMALIZED(a, d[3]); + } + } + d += 4; + s += 4; + m += 1; + } + px += rs; + spx += srs; + mpx += mrs; + } +} + +void +nr_R8G8B8A8_N_EMPTY_A8_RGBA32 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned long rgba) +{ + unsigned int r, g, b, a; + int x, y; + + r = NR_RGBA32_R (rgba); + g = NR_RGBA32_G (rgba); + b = NR_RGBA32_B (rgba); + a = NR_RGBA32_A (rgba); + + if (a == 0) return; + + for (y = 0; y < h; y++) { + unsigned char *d, *s; + d = (unsigned char *) px; + s = (unsigned char *) spx; + for (x = 0; x < w; x++) { + d[0] = r; + d[1] = g; + d[2] = b; + d[3] = NR_PREMUL (s[0], a); + d += 4; + s += 1; + } + px += rs; + spx += srs; + } +} + +void +nr_R8G8B8A8_P_EMPTY_A8_RGBA32 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned long rgba) +{ + unsigned int r, g, b, a; + int x, y; + + r = NR_RGBA32_R (rgba); + g = NR_RGBA32_G (rgba); + b = NR_RGBA32_B (rgba); + a = NR_RGBA32_A (rgba); + + if (a == 0) return; + +#ifdef WITH_MMX + if (NR_PIXOPS_MMX) { + unsigned char c[4]; + c[0] = NR_PREMUL (r, a); + c[1] = NR_PREMUL (g, a); + c[2] = NR_PREMUL (b, a); + c[3] = a; + /* WARNING: MMX composer REQUIRES w > 0 and h > 0 */ + nr_mmx_R8G8B8A8_P_EMPTY_A8_RGBAP (px, w, h, rs, spx, srs, c); + return; + } +#endif + + for (y = 0; y < h; y++) { + unsigned char *d, *s; + d = (unsigned char *) px; + s = (unsigned char *) spx; + for (x = 0; x < w; x++) { + unsigned int ca; + ca = s[0] * a; + d[0] = (r * ca + 32512) / 65025; + d[1] = (g * ca + 32512) / 65025; + d[2] = (b * ca + 32512) / 65025; + d[3] = (ca + 127) / 255; + d += 4; + s += 1; + } + px += rs; + spx += srs; + } +} + +void +nr_R8G8B8_R8G8B8_A8_RGBA32 (unsigned char *px, int w, int h, int rs, const unsigned char *mpx, int mrs, unsigned long rgba) +{ + unsigned int r, g, b, a; + int x, y; + + r = NR_RGBA32_R (rgba); + g = NR_RGBA32_G (rgba); + b = NR_RGBA32_B (rgba); + a = NR_RGBA32_A (rgba); + + if (a == 0) return; + + for (y = 0; y < h; y++) { + unsigned char *d, *m; + d = (unsigned char *) px; + m = (unsigned char *) mpx; + for (x = 0; x < w; x++) { + unsigned int alpha; + alpha = NR_PREMUL (a, m[0]); + d[0] = NR_COMPOSEN11 (r, alpha, d[0]); + d[1] = NR_COMPOSEN11 (g, alpha, d[1]); + d[2] = NR_COMPOSEN11 (b, alpha, d[2]); + d += 3; + m += 1; + } + px += rs; + mpx += mrs; + } +} + +void +nr_R8G8B8A8_N_R8G8B8A8_N_A8_RGBA32 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned long rgba) +{ + unsigned int r, g, b, a; + int x, y; + + r = NR_RGBA32_R (rgba); + g = NR_RGBA32_G (rgba); + b = NR_RGBA32_B (rgba); + a = NR_RGBA32_A (rgba); + + if (a == 0) return; + + for (y = 0; y < h; y++) { + unsigned char *d, *s; + d = (unsigned char *) px; + s = (unsigned char *) spx; + for (x = 0; x < w; x++) { + unsigned int ca; + ca = NR_PREMUL (s[0], a); + if (ca == 0) { + /* Transparent FG, NOP */ + } else if ((ca == 255) || (d[3] == 0)) { + /* Full coverage, COPY */ + d[0] = r; + d[1] = g; + d[2] = b; + d[3] = ca; + } else { + unsigned int da; + /* Full composition */ + da = 65025 - (255 - ca) * (255 - d[3]); + d[0] = NR_COMPOSENNN_A7 (r, ca, d[0], d[3], da); + d[1] = NR_COMPOSENNN_A7 (g, ca, d[1], d[3], da); + d[2] = NR_COMPOSENNN_A7 (b, ca, d[2], d[3], da); + d[3] = (da + 127) / 255; + } + d += 4; + s += 1; + } + px += rs; + spx += srs; + } +} + +void +nr_R8G8B8A8_P_R8G8B8A8_P_A8_RGBA32 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned long rgba) +{ + unsigned int r, g, b, a; + int x, y; + + if (!(rgba & 0xff)) return; + + r = NR_RGBA32_R (rgba); + g = NR_RGBA32_G (rgba); + b = NR_RGBA32_B (rgba); + a = NR_RGBA32_A (rgba); + +#ifdef WITH_MMX + if (NR_PIXOPS_MMX) { + unsigned char c[4]; + c[0] = NR_PREMUL (r, a); + c[1] = NR_PREMUL (g, a); + c[2] = NR_PREMUL (b, a); + c[3] = a; + /* WARNING: MMX composer REQUIRES w > 0 and h > 0 */ + nr_mmx_R8G8B8A8_P_R8G8B8A8_P_A8_RGBAP (px, w, h, rs, spx, srs, c); + return; + } +#endif + + for (y = 0; y < h; y++) { + unsigned char *d, *s; + d = (unsigned char *) px; + s = (unsigned char *) spx; + for (x = 0; x < w; x++) { + unsigned int ca; + ca = NR_PREMUL (s[0], a); + if (ca == 0) { + /* Transparent FG, NOP */ + } else if ((ca == 255) || (d[3] == 0)) { + /* Full coverage, COPY */ + d[0] = NR_PREMUL (r, ca); + d[1] = NR_PREMUL (g, ca); + d[2] = NR_PREMUL (b, ca); + d[3] = ca; + } else { + /* Full composition */ + d[0] = NR_COMPOSENPP (r, ca, d[0], d[3]); + d[1] = NR_COMPOSENPP (g, ca, d[1], d[3]); + d[2] = NR_COMPOSENPP (b, ca, d[2], d[3]); + d[3] = (65025 - (255 - ca) * (255 - d[3]) + 127) / 255; + } + d += 4; + s += 1; + } + px += rs; + spx += srs; + } +} + +/* RGB */ + +void +nr_R8G8B8_R8G8B8_R8G8B8A8_P (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha) +{ + int r, c; + + if (alpha == 0) return; + +#ifdef WITH_MMX + if (NR_PIXOPS_MMX) { + /* WARNING: MMX composer REQUIRES w > 0 and h > 0 */ + nr_mmx_R8G8B8_R8G8B8_R8G8B8A8_P (px, w, h, rs, spx, srs, alpha); + return; + } +#endif + + for (r = 0; r < h; r++) { + const unsigned char *s; + unsigned char *d; + if (alpha == 255) { + d = px; + s = spx; + for (c = 0; c < w; c++) { + if (s[3] == 0) { + /* NOP */ + } else if (s[3] == 255) { + d[0] = s[0]; + d[1] = s[1]; + d[2] = s[2]; + } else { + d[0] = NR_COMPOSEP11 (s[0], s[3], d[0]); + d[1] = NR_COMPOSEP11 (s[1], s[3], d[1]); + d[2] = NR_COMPOSEP11 (s[2], s[3], d[2]); + } + d += 3; + s += 4; + } + } else { + d = px; + s = spx; + for (c = 0; c < w; c++) { + unsigned int a; + a = NR_PREMUL (s[3], alpha); + if (a == 0) { + /* NOP */ + } else { + d[0] = NR_COMPOSEP11 (s[0], a, d[0]); + d[1] = NR_COMPOSEP11 (s[1], a, d[1]); + d[2] = NR_COMPOSEP11 (s[2], a, d[2]); + } + /* a == 255 is impossible, because alpha < 255 */ + d += 3; + s += 4; + } + } + px += rs; + spx += srs; + } +} + +void +nr_R8G8B8_R8G8B8_R8G8B8A8_N (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha) +{ + int r, c; + + for (r = 0; r < h; r++) { + const unsigned char *s; + unsigned char *d; + if (alpha == 0) { + /* NOP */ + } else if (alpha == 255) { + d = px; + s = spx; + for (c = 0; c < w; c++) { + if (s[3] == 0) { + /* NOP */ + } else if (s[3] == 255) { + d[0] = s[0]; + d[1] = s[1]; + d[2] = s[2]; + } else { + d[0] = NR_COMPOSEN11 (s[0], s[3], d[0]); + d[1] = NR_COMPOSEN11 (s[1], s[3], d[1]); + d[2] = NR_COMPOSEN11 (s[2], s[3], d[2]); + } + d += 3; + s += 4; + } + } else { + d = px; + s = spx; + for (c = 0; c < w; c++) { + unsigned int a; + a = NR_PREMUL (s[3], alpha); + if (a == 0) { + /* NOP */ + } else { + d[0] = NR_COMPOSEN11 (s[0], a, d[0]); + d[1] = NR_COMPOSEN11 (s[1], a, d[1]); + d[2] = NR_COMPOSEN11 (s[2], a, d[2]); + } + /* a == 255 is impossible, because alpha < 255 */ + d += 3; + s += 4; + } + } + px += rs; + spx += srs; + } +} + +void +nr_R8G8B8_R8G8B8_R8G8B8A8_P_A8 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, const unsigned char *mpx, int mrs) +{ + int x, y; + + for (y = 0; y < h; y++) { + unsigned char *d, *s, *m; + d = (unsigned char *) px; + s = (unsigned char *) spx; + m = (unsigned char *) mpx; + for (x = 0; x < w; x++) { + unsigned int a; + a = NR_PREMUL (s[3], m[0]); + if (a != 0) { + unsigned int r, g, b; + r = NR_PREMUL (s[0], m[0]); + d[0] = NR_COMPOSEP11 (r, a, d[0]); + g = NR_PREMUL (s[1], m[0]); + d[1] = NR_COMPOSEP11 (g, a, d[1]); + b = NR_PREMUL (s[2], m[0]); + d[2] = NR_COMPOSEP11 (b, a, d[2]); + } + d += 3; + s += 4; + m += 1; + } + px += rs; + spx += srs; + mpx += mrs; + } +} + +void +nr_R8G8B8_R8G8B8_R8G8B8A8_N_A8 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, const unsigned char *mpx, int mrs) +{ + int x, y; + + for (y = 0; y < h; y++) { + unsigned char *d, *s, *m; + d = (unsigned char *) px; + s = (unsigned char *) spx; + m = (unsigned char *) mpx; + for (x = 0; x < w; x++) { + unsigned int a; + a = NR_PREMUL (s[3], m[0]); + if (a != 0) { + d[0] = NR_COMPOSEP11 (s[0], a, d[0]); + d[1] = NR_COMPOSEP11 (s[1], a, d[1]); + d[2] = NR_COMPOSEP11 (s[2], a, d[2]); + } + d += 3; + s += 4; + m += 1; + } + px += rs; + spx += srs; + mpx += mrs; + } +} + + diff --git a/src/libnr/nr-compose.h b/src/libnr/nr-compose.h new file mode 100644 index 000000000..ccdb52cb0 --- /dev/null +++ b/src/libnr/nr-compose.h @@ -0,0 +1,69 @@ +#ifndef __NR_COMPOSE_H__ +#define __NR_COMPOSE_H__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * + * This code is in public domain + */ + +/* FINAL DST SRC */ + +void nr_R8G8B8A8_N_EMPTY_R8G8B8A8_N (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha); +void nr_R8G8B8A8_N_EMPTY_R8G8B8A8_P (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha); +void nr_R8G8B8A8_P_EMPTY_R8G8B8A8_N (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha); +void nr_R8G8B8A8_P_EMPTY_R8G8B8A8_P (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha); + +void nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_N (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha); +void nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_P (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha); +void nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha); +void nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_P (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha); + +/* FINAL DST SRC MASK */ + +void nr_R8G8B8A8_N_EMPTY_R8G8B8A8_N_A8 (unsigned char *px, int w, int h, int rs, + const unsigned char *spx, int srs, + const unsigned char *mpx, int mrs); +void nr_R8G8B8A8_N_EMPTY_R8G8B8A8_P_A8 (unsigned char *px, int w, int h, int rs, + const unsigned char *spx, int srs, + const unsigned char *mpx, int mrs); +void nr_R8G8B8A8_P_EMPTY_R8G8B8A8_N_A8 (unsigned char *px, int w, int h, int rs, + const unsigned char *spx, int srs, + const unsigned char *mpx, int mrs); +void nr_R8G8B8A8_P_EMPTY_R8G8B8A8_P_A8 (unsigned char *px, int w, int h, int rs, + const unsigned char *spx, int srs, + const unsigned char *mpx, int mrs); + +void nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_N_A8 (unsigned char *p, int w, int h, int rs, + const unsigned char *s, int srs, + const unsigned char *m, int mrs); +void nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_P_A8 (unsigned char *p, int w, int h, int rs, + const unsigned char *s, int srs, + const unsigned char *m, int mrs); +void nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_A8 (unsigned char *p, int w, int h, int rs, + const unsigned char *s, int srs, + const unsigned char *m, int mrs); +void nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_P_A8 (unsigned char *p, int w, int h, int rs, + const unsigned char *s, int srs, + const unsigned char *m, int mrs); + +/* FINAL DST MASK COLOR */ + +void nr_R8G8B8A8_N_EMPTY_A8_RGBA32 (unsigned char *px, int w, int h, int rs, const unsigned char *mpx, int mrs, unsigned long rgba); +void nr_R8G8B8A8_P_EMPTY_A8_RGBA32 (unsigned char *px, int w, int h, int rs, const unsigned char *mpx, int mrs, unsigned long rgba); + +void nr_R8G8B8_R8G8B8_A8_RGBA32 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned long rgba); +void nr_R8G8B8A8_N_R8G8B8A8_N_A8_RGBA32 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned long rgba); +void nr_R8G8B8A8_P_R8G8B8A8_P_A8_RGBA32 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned long rgba); + +/* RGB */ + +void nr_R8G8B8_R8G8B8_R8G8B8A8_P (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha); +void nr_R8G8B8_R8G8B8_R8G8B8A8_N (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, unsigned int alpha); +void nr_R8G8B8_R8G8B8_R8G8B8A8_P_A8 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, const unsigned char *mpx, int mrs); +void nr_R8G8B8_R8G8B8_R8G8B8A8_N_A8 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int srs, const unsigned char *mpx, int mrs); + +#endif diff --git a/src/libnr/nr-convex-hull-ops.h b/src/libnr/nr-convex-hull-ops.h new file mode 100644 index 000000000..2e96bf367 --- /dev/null +++ b/src/libnr/nr-convex-hull-ops.h @@ -0,0 +1,29 @@ +#ifndef SEEN_NR_CONVEX_HULL_FNS_H +#define SEEN_NR_CONVEX_HULL_FNS_H + +/* ex:set et ts=4 sw=4: */ + +/* + * A class representing the convex hull of a set of points. + * + * Copyright 2004 MenTaLguY + * + * This code is licensed under the GNU GPL; see COPYING for more information. + */ + +#include +#include + +namespace NR { + +ConvexHull operator*(const Rect &r, const Matrix &m) { + ConvexHull points(r.corner(0)); + for ( unsigned i = 1 ; i < 4 ; i++ ) { + points.add(r.corner(i)); + } + return points; +} + +} /* namespace NR */ + +#endif diff --git a/src/libnr/nr-convex-hull.h b/src/libnr/nr-convex-hull.h new file mode 100644 index 000000000..28cde376d --- /dev/null +++ b/src/libnr/nr-convex-hull.h @@ -0,0 +1,49 @@ +#ifndef SEEN_NR_CONVEX_HULL_H +#define SEEN_NR_CONVEX_HULL_H + +/* ex:set et ts=4 sw=4: */ + +/* + * A class representing the convex hull of a set of points. + * + * Copyright 2004 MenTaLguY + * + * This code is licensed under the GNU GPL; see COPYING for more information. + */ + +#include + +namespace NR { + +class ConvexHull { +public: + explicit ConvexHull(Point const &p) : _bounds(p, p) {} + + Point midpoint() const { + return _bounds.midpoint(); + } + + void add(Point const &p) { + _bounds.expandTo(p); + } + void add(Rect const &p) { + // Note that this is a hack. when convexhull actually works + // you will need to add all four points. + _bounds.expandTo(p.min()); + _bounds.expandTo(p.max()); + } + void add(ConvexHull const &h) { + _bounds.expandTo(h._bounds); + } + + Rect const &bounds() const { + return _bounds; + } + +private: + Rect _bounds; +}; + +} /* namespace NR */ + +#endif diff --git a/src/libnr/nr-coord.h b/src/libnr/nr-coord.h new file mode 100644 index 000000000..668e2b460 --- /dev/null +++ b/src/libnr/nr-coord.h @@ -0,0 +1,29 @@ +#ifndef SEEN_NR_COORD_H +#define SEEN_NR_COORD_H + +namespace NR { + +/** + * A "real" type with sufficient precision for coordinates. + * + * You may safely assume that double (or even float) provides enough precision for storing + * on-canvas points, and hence that double provides enough precision for dot products of + * differences of on-canvas points. + */ +typedef double Coord; + +} /* namespace NR */ + + +#endif /* !SEEN_NR_COORD_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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-dim2.h b/src/libnr/nr-dim2.h new file mode 100644 index 000000000..d06fd4227 --- /dev/null +++ b/src/libnr/nr-dim2.h @@ -0,0 +1,22 @@ +#ifndef SEEN_NR_DIM2_H +#define SEEN_NR_DIM2_H + +namespace NR { + +enum Dim2 { X=0, Y }; + +} /* namespace NR */ + + +#endif /* !SEEN_NR_DIM2_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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-forward.h b/src/libnr/nr-forward.h new file mode 100644 index 000000000..cbc4d8eec --- /dev/null +++ b/src/libnr/nr-forward.h @@ -0,0 +1,42 @@ +#ifndef __NR_FORWARD_H__ +#define __NR_FORWARD_H__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * + * This code is in public domain + */ + +namespace NR { +class Matrix; +class Point; +class Rect; +class rotate; +class scale; +class translate; +} + +class NArtBpath; +struct NRBPath; +struct NRPixBlock; +struct NRMatrix; +struct NRPoint; +struct NRRect; +struct NRRectL; + + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-gradient.cpp b/src/libnr/nr-gradient.cpp new file mode 100644 index 000000000..fa4f9f91f --- /dev/null +++ b/src/libnr/nr-gradient.cpp @@ -0,0 +1,317 @@ +#define __NR_GRADIENT_C__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * + * This code is in public domain + */ + +#include +#include +#include +#include + +#define NRG_MASK (NR_GRADIENT_VECTOR_LENGTH - 1) +#define NRG_2MASK ((long long) ((NR_GRADIENT_VECTOR_LENGTH << 1) - 1)) + +/* Radial */ + +static void nr_rgradient_render_block_symmetric(NRRenderer *r, NRPixBlock *pb, NRPixBlock *m); +static void nr_rgradient_render_block_optimized(NRRenderer *r, NRPixBlock *pb, NRPixBlock *m); +static void nr_rgradient_render_block_end(NRRenderer *r, NRPixBlock *pb, NRPixBlock *m); +static void nr_rgradient_render_generic_symmetric(NRRGradientRenderer *rgr, NRPixBlock *pb); +static void nr_rgradient_render_generic_optimized(NRRGradientRenderer *rgr, NRPixBlock *pb); + +NRRenderer * +nr_rgradient_renderer_setup(NRRGradientRenderer *rgr, + unsigned char const *cv, + unsigned spread, + NRMatrix const *gs2px, + float cx, float cy, + float fx, float fy, + float r) +{ + rgr->vector = cv; + rgr->spread = spread; + + if (r < NR_EPSILON) { + rgr->renderer.render = nr_rgradient_render_block_end; + } else if (NR_DF_TEST_CLOSE(cx, fx, NR_EPSILON) && + NR_DF_TEST_CLOSE(cy, fy, NR_EPSILON)) { + rgr->renderer.render = nr_rgradient_render_block_symmetric; + + nr_matrix_invert(&rgr->px2gs, gs2px); + rgr->px2gs.c[0] *= (NR_GRADIENT_VECTOR_LENGTH / r); + rgr->px2gs.c[1] *= (NR_GRADIENT_VECTOR_LENGTH / r); + rgr->px2gs.c[2] *= (NR_GRADIENT_VECTOR_LENGTH / r); + rgr->px2gs.c[3] *= (NR_GRADIENT_VECTOR_LENGTH / r); + rgr->px2gs.c[4] -= cx; + rgr->px2gs.c[5] -= cy; + rgr->px2gs.c[4] *= (NR_GRADIENT_VECTOR_LENGTH / r); + rgr->px2gs.c[5] *= (NR_GRADIENT_VECTOR_LENGTH / r); + + rgr->cx = 0.0; + rgr->cy = 0.0; + rgr->fx = rgr->cx; + rgr->fy = rgr->cy; + rgr->r = 1.0; + } else { + rgr->renderer.render = nr_rgradient_render_block_optimized; + + NR::Coord const df = hypot(fx - cx, fy - cy); + if (df >= r) { + fx = cx + (fx - cx ) * r / (float) df; + fy = cy + (fy - cy ) * r / (float) df; + } + + NRMatrix n2gs; + n2gs.c[0] = cx - fx; + n2gs.c[1] = cy - fy; + n2gs.c[2] = cy - fy; + n2gs.c[3] = fx - cx; + n2gs.c[4] = fx; + n2gs.c[5] = fy; + + NRMatrix n2px; + nr_matrix_multiply(&n2px, &n2gs, gs2px); + nr_matrix_invert(&rgr->px2gs, &n2px); + + rgr->cx = 1.0; + rgr->cy = 0.0; + rgr->fx = 0.0; + rgr->fy = 0.0; + rgr->r = r / (float) hypot(fx - cx, fy - cy); + rgr->C = 1.0F - rgr->r * rgr->r; + /* INVARIANT: C < 0 */ + rgr->C = MIN(rgr->C, -NR_EPSILON); + } + + return (NRRenderer *) rgr; +} + +static void +nr_rgradient_render_block_symmetric(NRRenderer *r, NRPixBlock *pb, NRPixBlock *m) +{ + NRRGradientRenderer *rgr = (NRRGradientRenderer *) r; + nr_rgradient_render_generic_symmetric(rgr, pb); +} + +static void +nr_rgradient_render_block_optimized(NRRenderer *r, NRPixBlock *pb, NRPixBlock *m) +{ + NRRGradientRenderer *rgr = (NRRGradientRenderer *) r; + nr_rgradient_render_generic_optimized(rgr, pb); +} + +static void +nr_rgradient_render_block_end(NRRenderer *r, NRPixBlock *pb, NRPixBlock *m) +{ + unsigned char const *c = ((NRRGradientRenderer *) r)->vector + 4 * (NR_GRADIENT_VECTOR_LENGTH - 1); + + nr_blit_pixblock_mask_rgba32(pb, m, (c[0] << 24) | (c[1] << 16) | (c[2] << 8) | c[3]); +} + +/* + * The archetype is following + * + * gx gy - pixel coordinates + * Px Py - coordinates, where Fx Fy - gx gy line intersects with circle + * + * (1) (gx - fx) * (Py - fy) = (gy - fy) * (Px - fx) + * (2) (Px - cx) * (Px - cx) + (Py - cy) * (Py - cy) = r * r + * + * (3) Py = (Px - fx) * (gy - fy) / (gx - fx) + fy + * (4) (gy - fy) / (gx - fx) = D + * (5) Py = D * Px - D * fx + fy + * + * (6) D * fx - fy + cy = N + * (7) Px * Px - 2 * Px * cx + cx * cx + (D * Px) * (D * Px) - 2 * (D * Px) * N + N * N = r * r + * (8) (D * D + 1) * (Px * Px) - 2 * (cx + D * N) * Px + cx * cx + N * N = r * r + * + * (9) A = D * D + 1 + * (10) B = -2 * (cx + D * N) + * (11) C = cx * cx + N * N - r * r + * + * (12) Px = (-B +- SQRT(B * B - 4 * A * C)) / 2 * A + */ + +static void +nr_rgradient_render_generic_symmetric(NRRGradientRenderer *rgr, NRPixBlock *pb) +{ + NR::Coord const dx = rgr->px2gs.c[0]; + NR::Coord const dy = rgr->px2gs.c[1]; + + if (pb->mode == NR_PIXBLOCK_MODE_R8G8B8A8P) { + for (int y = pb->area.y0; y < pb->area.y1; y++) { + unsigned char *d = NR_PIXBLOCK_PX(pb) + (y - pb->area.y0) * pb->rs; + NR::Coord gx = rgr->px2gs.c[0] * pb->area.x0 + rgr->px2gs.c[2] * y + rgr->px2gs.c[4]; + NR::Coord gy = rgr->px2gs.c[1] * pb->area.x0 + rgr->px2gs.c[3] * y + rgr->px2gs.c[5]; + for (int x = pb->area.x0; x < pb->area.x1; x++) { + NR::Coord const pos = hypot(gx, gy); + int idx; + if (rgr->spread == NR_GRADIENT_SPREAD_REFLECT) { + idx = (int) ((long long) pos & NRG_2MASK); + if (idx > NRG_MASK) idx = NRG_2MASK - idx; + } else if (rgr->spread == NR_GRADIENT_SPREAD_REPEAT) { + idx = (int) ((long long) pos & NRG_MASK); + } else { + idx = (int) CLAMP(pos, 0, (double) NRG_MASK); + } + unsigned char const *s = rgr->vector + 4 * idx; + d[0] = NR_COMPOSENPP(s[0], s[3], d[0], d[3]); + d[1] = NR_COMPOSENPP(s[1], s[3], d[1], d[3]); + d[2] = NR_COMPOSENPP(s[2], s[3], d[2], d[3]); + d[3] = (255*255 - (255 - s[3]) * (255 - d[3]) + 127) / 255; + d += 4; + gx += dx; + gy += dy; + } + } + } else if (pb->mode == NR_PIXBLOCK_MODE_R8G8B8A8N) { + for (int y = pb->area.y0; y < pb->area.y1; y++) { + unsigned char *d = NR_PIXBLOCK_PX(pb) + (y - pb->area.y0) * pb->rs; + NR::Coord gx = rgr->px2gs.c[0] * pb->area.x0 + rgr->px2gs.c[2] * y + rgr->px2gs.c[4]; + NR::Coord gy = rgr->px2gs.c[1] * pb->area.x0 + rgr->px2gs.c[3] * y + rgr->px2gs.c[5]; + for (int x = pb->area.x0; x < pb->area.x1; x++) { + NR::Coord const pos = hypot(gx, gy); + int idx; + if (rgr->spread == NR_GRADIENT_SPREAD_REFLECT) { + idx = (int) ((long long) pos & NRG_2MASK); + if (idx > NRG_MASK) idx = NRG_2MASK - idx; + } else if (rgr->spread == NR_GRADIENT_SPREAD_REPEAT) { + idx = (int) ((long long) pos & NRG_MASK); + } else { + idx = (int) CLAMP(pos, 0, (double) NRG_MASK); + } + unsigned char const *s = rgr->vector + 4 * idx; + if (s[3] == 255) { + d[0] = s[0]; + d[1] = s[1]; + d[2] = s[2]; + d[3] = 255; + } else if (s[3] != 0) { + unsigned ca = 255*255 - (255 - s[3]) * (255 - d[3]); + d[0] = NR_COMPOSENNN_A7(s[0], s[3], d[0], d[3], ca); + d[1] = NR_COMPOSENNN_A7(s[1], s[3], d[1], d[3], ca); + d[2] = NR_COMPOSENNN_A7(s[2], s[3], d[2], d[3], ca); + d[3] = (ca + 127) / 255; + } + d += 4; + gx += dx; + gy += dy; + } + } + } else { + NRPixBlock spb; + nr_pixblock_setup_extern(&spb, NR_PIXBLOCK_MODE_R8G8B8A8N, 0, 0, NR_GRADIENT_VECTOR_LENGTH, 1, + (unsigned char *) rgr->vector, + 4 * NR_GRADIENT_VECTOR_LENGTH, + 0, 0); + int const bpp = ( pb->mode == NR_PIXBLOCK_MODE_A8 + ? 1 + : pb->mode == NR_PIXBLOCK_MODE_R8G8B8 + ? 3 + : 4 ); + + for (int y = pb->area.y0; y < pb->area.y1; y++) { + unsigned char *d = NR_PIXBLOCK_PX(pb) + (y - pb->area.y0) * pb->rs; + NR::Coord gx = rgr->px2gs.c[0] * pb->area.x0 + rgr->px2gs.c[2] * y + rgr->px2gs.c[4]; + NR::Coord gy = rgr->px2gs.c[1] * pb->area.x0 + rgr->px2gs.c[3] * y + rgr->px2gs.c[5]; + for (int x = pb->area.x0; x < pb->area.x1; x++) { + NR::Coord const pos = hypot(gx, gy); + int idx; + if (rgr->spread == NR_GRADIENT_SPREAD_REFLECT) { + idx = (int) ((long long) pos & NRG_2MASK); + if (idx > NRG_MASK) idx = NRG_2MASK - idx; + } else if (rgr->spread == NR_GRADIENT_SPREAD_REPEAT) { + idx = (int) ((long long) pos & NRG_MASK); + } else { + idx = (int) CLAMP(pos, 0, (double) NRG_MASK); + } + unsigned char const *s = rgr->vector + 4 * idx; + nr_compose_pixblock_pixblock_pixel(pb, d, &spb, s); + d += bpp; + gx += dx; + gy += dy; + } + } + + nr_pixblock_release(&spb); + } +} + +static void +nr_rgradient_render_generic_optimized(NRRGradientRenderer *rgr, NRPixBlock *pb) +{ + int const x0 = pb->area.x0; + int const y0 = pb->area.y0; + int const x1 = pb->area.x1; + int const y1 = pb->area.y1; + int const rs = pb->rs; + + NRPixBlock spb; + nr_pixblock_setup_extern(&spb, NR_PIXBLOCK_MODE_R8G8B8A8N, 0, 0, NR_GRADIENT_VECTOR_LENGTH, 1, + (unsigned char *) rgr->vector, + 4 * NR_GRADIENT_VECTOR_LENGTH, + 0, 0); + int const bpp = ( pb->mode == NR_PIXBLOCK_MODE_A8 + ? 1 + : pb->mode == NR_PIXBLOCK_MODE_R8G8B8 + ? 3 + : 4 ); + + for (int y = y0; y < y1; y++) { + unsigned char *d = NR_PIXBLOCK_PX(pb) + (y - y0) * rs; + NR::Coord gx = rgr->px2gs.c[0] * x0 + rgr->px2gs.c[2] * y + rgr->px2gs.c[4]; + NR::Coord gy = rgr->px2gs.c[1] * x0 + rgr->px2gs.c[3] * y + rgr->px2gs.c[5]; + NR::Coord const dx = rgr->px2gs.c[0]; + NR::Coord const dy = rgr->px2gs.c[1]; + for (int x = x0; x < x1; x++) { + NR::Coord const gx2 = gx * gx; + NR::Coord const gxy2 = gx2 + gy * gy; + NR::Coord const qgx2_4 = gx2 - rgr->C * gxy2; + /* INVARIANT: qgx2_4 >= 0.0 */ + /* qgx2_4 = MAX(qgx2_4, 0.0); */ + NR::Coord const pxgx = gx + sqrt(qgx2_4); + /* We can safely divide by 0 here */ + /* If we are sure pxgx cannot be -0 */ + NR::Coord const pos = gxy2 / pxgx * NR_GRADIENT_VECTOR_LENGTH; + int idx; + if (pos < (1U << 31)) { + if (rgr->spread == NR_GRADIENT_SPREAD_REFLECT) { + idx = (int) ((long long) pos & NRG_2MASK); + if (idx > NRG_MASK) idx = NRG_2MASK - idx; + } else if (rgr->spread == NR_GRADIENT_SPREAD_REPEAT) { + idx = (int) ((long long) pos & NRG_MASK); + } else { + idx = (int) CLAMP(pos, 0, (double) (NR_GRADIENT_VECTOR_LENGTH - 1)); + } + } else { + idx = NR_GRADIENT_VECTOR_LENGTH - 1; + } + unsigned char const *s = rgr->vector + 4 * idx; + nr_compose_pixblock_pixblock_pixel(pb, d, &spb, s); + d += bpp; + + gx += dx; + gy += dy; + } + } + + nr_pixblock_release(&spb); +} + + +/* + 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 : diff --git a/src/libnr/nr-gradient.h b/src/libnr/nr-gradient.h new file mode 100644 index 000000000..f9cb1ba45 --- /dev/null +++ b/src/libnr/nr-gradient.h @@ -0,0 +1,47 @@ +#ifndef __NR_GRADIENT_H__ +#define __NR_GRADIENT_H__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * + * This code is in public domain + */ + +#include +#include + +#define NR_GRADIENT_VECTOR_LENGTH 1024 + +enum { + NR_GRADIENT_SPREAD_PAD, + NR_GRADIENT_SPREAD_REFLECT, + NR_GRADIENT_SPREAD_REPEAT +}; + +/* Radial */ + +struct NRRGradientRenderer { + NRRenderer renderer; + const unsigned char *vector; + unsigned int spread; + NRMatrix px2gs; + float cx, cy; + float fx, fy; + float r; + float C; +}; + +NRRenderer *nr_rgradient_renderer_setup (NRRGradientRenderer *rgr, + const unsigned char *cv, + unsigned int spread, + const NRMatrix *gs2px, + float cx, float cy, + float fx, float fy, + float r); + + + +#endif diff --git a/src/libnr/nr-i-coord.h b/src/libnr/nr-i-coord.h new file mode 100644 index 000000000..f87dea3d5 --- /dev/null +++ b/src/libnr/nr-i-coord.h @@ -0,0 +1,25 @@ +#ifndef SEEN_NR_I_COORD_H +#define SEEN_NR_I_COORD_H + +#include + +namespace NR { + +/** An integer type with sufficient precision for coordinates. */ +typedef gint32 ICoord; + +} /* namespace NR */ + + +#endif /* !SEEN_NR_I_COORD_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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-macros.h b/src/libnr/nr-macros.h new file mode 100644 index 000000000..74a627ae7 --- /dev/null +++ b/src/libnr/nr-macros.h @@ -0,0 +1,71 @@ +#ifndef __NR_MACROS_H__ +#define __NR_MACROS_H__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * + * This code is in public domain + */ + +#include + +#if HAVE_STDLIB_H +#include +#endif +#include + +#define nr_new(t,n) ((t *) malloc ((n) * sizeof (t))) +#define nr_free free +#define nr_renew(p,t,n) ((t *) realloc (p, (n) * sizeof (t))) + +#ifndef TRUE +#define TRUE (!0) +#endif +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef MAX +#define MAX(a,b) (((a) < (b)) ? (b) : (a)) +#endif +#ifndef MIN +#define MIN(a,b) (((a) > (b)) ? (b) : (a)) +#endif + +#ifndef CLAMP +/** Returns v bounded to within [a, b]. If v is NaN then returns a. + * + * \pre \a a \<= \a b. + */ +# define CLAMP(v,a,b) \ + (assert (a <= b), \ + ((v) >= (a)) \ + ? (((v) > (b)) \ + ? (b) \ + : (v)) \ + : (a)) +#endif + +#define NR_DF_TEST_CLOSE(a,b,e) (fabs ((a) - (b)) <= (e)) + +// Todo: move these into nr-matrix.h +#define NR_MATRIX_DF_TEST_TRANSFORM_CLOSE(a,b,e) (NR_DF_TEST_CLOSE ((*(a))[0], (*(b))[0], e) && \ + NR_DF_TEST_CLOSE ((*(a))[1], (*(b))[1], e) && \ + NR_DF_TEST_CLOSE ((*(a))[2], (*(b))[2], e) && \ + NR_DF_TEST_CLOSE ((*(a))[3], (*(b))[3], e)) +#define NR_MATRIX_DF_TEST_TRANSLATE_CLOSE(a,b,e) (NR_DF_TEST_CLOSE ((*(a))[4], (*(b))[4], e) && \ + NR_DF_TEST_CLOSE ((*(a))[5], (*(b))[5], e)) +#define NR_MATRIX_DF_TEST_CLOSE(a,b,e) (NR_MATRIX_DF_TEST_TRANSLATE_CLOSE (a, b, e) && \ + NR_MATRIX_DF_TEST_TRANSFORM_CLOSE (a, b, e)) + +#define NR_RECT_DFLS_TEST_EMPTY(a) (((a)->x0 >= (a)->x1) || ((a)->y0 >= (a)->y1)) +#define NR_RECT_DFLS_TEST_INTERSECT(a,b) (((a)->x0 < (b)->x1) && ((a)->x1 > (b)->x0) && ((a)->y0 < (b)->y1) && ((a)->y1 > (b)->y0)) +#define NR_RECT_DF_POINT_DF_TEST_INSIDE(r,p) (((p)->x >= (r)->x0) && ((p)->x < (r)->x1) && ((p)->y >= (r)->y0) && ((p)->y < (r)->y1)) +#define NR_RECT_LS_POINT_LS_TEST_INSIDE(r,p) (((p)->x >= (r)->x0) && ((p)->x < (r)->x1) && ((p)->y >= (r)->y0) && ((p)->y < (r)->y1)) + +#define NR_MATRIX_D_TO_DOUBLE(m) ((m)->c) +#define NR_MATRIX_D_FROM_DOUBLE(d) ((NRMatrix *) &(d)[0]) + +#endif diff --git a/src/libnr/nr-matrix-div.cpp b/src/libnr/nr-matrix-div.cpp new file mode 100644 index 000000000..d6fb598b8 --- /dev/null +++ b/src/libnr/nr-matrix-div.cpp @@ -0,0 +1,22 @@ +#include "libnr/nr-matrix-ops.h" +#include "libnr/nr-point-matrix-ops.h" + +NR::Point operator/(NR::Point const &p, NR::Matrix const &m) { + return p * m.inverse(); +} + +NR::Matrix operator/(NR::Matrix const &a, NR::Matrix const &b) { + return a * b.inverse(); +} + + +/* + 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/libnr/nr-matrix-div.h b/src/libnr/nr-matrix-div.h new file mode 100644 index 000000000..8669e2ce2 --- /dev/null +++ b/src/libnr/nr-matrix-div.h @@ -0,0 +1,21 @@ +#ifndef SEEN_LIBNR_NR_MATRIX_DIV_H +#define SEEN_LIBNR_NR_MATRIX_DIV_H + +#include "libnr/nr-forward.h" + +NR::Point operator/(NR::Point const &, NR::Matrix const &); + +NR::Matrix operator/(NR::Matrix const &, NR::Matrix const &); + +#endif /* !SEEN_LIBNR_NR_MATRIX_DIV_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 : diff --git a/src/libnr/nr-matrix-fns.cpp b/src/libnr/nr-matrix-fns.cpp new file mode 100644 index 000000000..f392f3213 --- /dev/null +++ b/src/libnr/nr-matrix-fns.cpp @@ -0,0 +1,54 @@ +#include + +namespace NR { + +Matrix elliptic_quadratic_form(Matrix const &m) { + double const od = m[0] * m[1] + m[2] * m[3]; + return Matrix((m[0]*m[0] + m[1]*m[1]), od, + od, (m[2]*m[2] + m[3]*m[3]), + 0, 0); +/* def quadratic_form((a, b), (c, d)): + return ((a*a + c*c), a*c+b*d),(a*c+b*d, (b*b + d*d)) */ +} + +Eigen::Eigen(Matrix const &m) { + double const B = -m[0] - m[3]; + double const C = m[0]*m[3] - m[1]*m[2]; + double const center = -B/2.0; + double const delta = sqrt(B*B-4*C)/2.0; + values = Point(center + delta, center - delta); + for (int i = 0; i < 2; i++) { + vectors[i] = unit_vector(rot90(Point(m[0]-values[i], m[1]))); + } +} + +/** Returns just the scale/rotate/skew part of the matrix without the translation part. */ +Matrix transform(Matrix const &m) { + Matrix const ret(m[0], m[1], + m[2], m[3], + 0, 0); + return ret; +} + +translate get_translation(Matrix const &m) { + return translate(m[4], m[5]); +} + +void matrix_print(const gchar *say, Matrix const &m) +{ + printf ("%s %g %g %g %g %g %g\n", say, m[0], m[1], m[2], m[3], m[4], m[5]); +} + +} // namespace NR + + +/* + 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/libnr/nr-matrix-fns.h b/src/libnr/nr-matrix-fns.h new file mode 100644 index 000000000..208366746 --- /dev/null +++ b/src/libnr/nr-matrix-fns.h @@ -0,0 +1,50 @@ +#ifndef SEEN_NR_MATRIX_FNS_H +#define SEEN_NR_MATRIX_FNS_H + +#include "nr-matrix.h" + +namespace NR { + +/** Given a matrix m such that unit_circle = m*x, this returns the + * quadratic form x*A*x = 1. */ +Matrix elliptic_quadratic_form(Matrix const &m); + +/** Given a matrix (ignoring the translation) this returns the eigen + * values and vectors. */ +class Eigen{ +public: + Point vectors[2]; + Point values; + Eigen(Matrix const &m); +}; + +// Matrix factories +Matrix from_basis(const Point x_basis, const Point y_basis, const Point offset=Point(0,0)); + +Matrix identity(); + +double expansion(Matrix const &m); + +bool transform_equalp(Matrix const &m0, Matrix const &m1, NR::Coord const epsilon); +bool translate_equalp(Matrix const &m0, Matrix const &m1, NR::Coord const epsilon); +bool matrix_equalp(Matrix const &m0, Matrix const &m1, NR::Coord const epsilon); + +Matrix transform(Matrix const &m); +translate get_translation(Matrix const &m); + +void matrix_print(const gchar *say, Matrix const &m); + +} // namespace NR + +#endif /* !SEEN_NR_MATRIX_FNS_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 : diff --git a/src/libnr/nr-matrix-ops.h b/src/libnr/nr-matrix-ops.h new file mode 100644 index 000000000..02fb28d0c --- /dev/null +++ b/src/libnr/nr-matrix-ops.h @@ -0,0 +1,45 @@ +/* operator functions for NR::Matrix. */ +#ifndef SEEN_NR_MATRIX_OPS_H +#define SEEN_NR_MATRIX_OPS_H + +#include + +namespace NR { + +inline bool operator==(Matrix const &a, Matrix const &b) +{ + for(unsigned i = 0; i < 6; ++i) { + if ( a[i] != b[i] ) { + return false; + } + } + return true; +} + +inline bool operator!=(Matrix const &a, Matrix const &b) +{ + return !( a == b ); +} + +Matrix operator*(Matrix const &a, Matrix const &b); + +inline Matrix operator*(Matrix const &a, NRMatrix const &b) +{ + return a * NR::Matrix(b); +} + +} /* namespace NR */ + + +#endif /* !SEEN_NR_MATRIX_OPS_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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-matrix-rotate-ops.cpp b/src/libnr/nr-matrix-rotate-ops.cpp new file mode 100644 index 000000000..625291575 --- /dev/null +++ b/src/libnr/nr-matrix-rotate-ops.cpp @@ -0,0 +1,18 @@ +#include "libnr/nr-matrix-ops.h" + +NR::Matrix operator*(NR::Matrix const &m, NR::rotate const &r) +{ + return m * NR::Matrix(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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-matrix-rotate-ops.h b/src/libnr/nr-matrix-rotate-ops.h new file mode 100644 index 000000000..44d9c8726 --- /dev/null +++ b/src/libnr/nr-matrix-rotate-ops.h @@ -0,0 +1,20 @@ +#ifndef SEEN_LIBNR_NR_MATRIX_ROTATE_OPS_H +#define SEEN_LIBNR_NR_MATRIX_ROTATE_OPS_H + +#include "libnr/nr-forward.h" + +NR::Matrix operator*(NR::Matrix const &m, NR::rotate const &r); + + +#endif /* !SEEN_LIBNR_NR_MATRIX_ROTATE_OPS_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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-matrix-scale-ops.cpp b/src/libnr/nr-matrix-scale-ops.cpp new file mode 100644 index 000000000..90cbaf585 --- /dev/null +++ b/src/libnr/nr-matrix-scale-ops.cpp @@ -0,0 +1,37 @@ +#include "libnr/nr-matrix-ops.h" + +NR::Matrix +operator/(NR::Matrix const &m, NR::scale const &s) +{ + using NR::X; using NR::Y; + NR::Matrix ret(m); + ret[0] /= s[X]; ret[1] /= s[Y]; + ret[2] /= s[X]; ret[3] /= s[Y]; + ret[4] /= s[X]; ret[5] /= s[Y]; + assert_close( ret, m * NR::Matrix(s.inverse()) ); + return ret; +} + +NR::Matrix +operator*(NR::Matrix const &m, NR::scale const &s) +{ + using NR::X; using NR::Y; + NR::Matrix ret(m); + ret[0] *= s[X]; ret[1] *= s[Y]; + ret[2] *= s[X]; ret[3] *= s[Y]; + ret[4] *= s[X]; ret[5] *= s[Y]; + assert_close( ret, m * NR::Matrix(s) ); + return ret; +} + + +/* + 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 : diff --git a/src/libnr/nr-matrix-scale-ops.h b/src/libnr/nr-matrix-scale-ops.h new file mode 100644 index 000000000..dee275182 --- /dev/null +++ b/src/libnr/nr-matrix-scale-ops.h @@ -0,0 +1,14 @@ +#ifndef SEEN_LIBNR_NR_MATRIX_SCALE_OPS_H +#define SEEN_LIBNR_NR_MATRIX_SCALE_OPS_H +/** \file + * Declarations (and definition if inline) of operator blah (NR::Matrix, NR::scale). + */ + +#include "libnr/nr-forward.h" + +NR::Matrix operator/(NR::Matrix const &m, NR::scale const &s); + +NR::Matrix operator*(NR::Matrix const &m, NR::scale const &s); + + +#endif /* !SEEN_LIBNR_NR_MATRIX_SCALE_OPS_H */ diff --git a/src/libnr/nr-matrix-test.cpp b/src/libnr/nr-matrix-test.cpp new file mode 100644 index 000000000..b7f064e48 --- /dev/null +++ b/src/libnr/nr-matrix-test.cpp @@ -0,0 +1,197 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using NR::Matrix; +using NR::X; +using NR::Y; + +inline bool point_equalp(NR::Point const &a, NR::Point const &b) +{ + return ( NR_DF_TEST_CLOSE(a[X], b[X], 1e-5) && + NR_DF_TEST_CLOSE(a[Y], b[Y], 1e-5) ); +} + +int main(int argc, char *argv[]) +{ + int rc = EXIT_SUCCESS; + + Matrix const m_id(NR::identity()); + NR::rotate const r_id(NR::Point(1, 0)); + NR::translate const t_id(0, 0); + + utest_start("Matrix"); + + Matrix const c16(1.0, 2.0, + 3.0, 4.0, + 5.0, 6.0); + UTEST_TEST("basic constructors, operator=") { + Matrix const c16_copy(c16); + Matrix c16_eq(m_id); + c16_eq = c16; + for(unsigned i = 0; i < 6; ++i) { + UTEST_ASSERT( c16[i] == 1.0 + i ); + UTEST_ASSERT( c16[i] == c16_copy[i] ); + UTEST_ASSERT( c16[i] == c16_eq[i] ); + UTEST_ASSERT( m_id[i] == double( i == 0 || i == 3 ) ); + } + } + + UTEST_TEST("scale constructor") { + NR::scale const s(2.0, 3.0); + NR::Matrix const ms(s); + NR::Point const p(5.0, 7.0); + UTEST_ASSERT( p * s == NR::Point(10.0, 21.0) ); + UTEST_ASSERT( p * ms == NR::Point(10.0, 21.0) ); + } + + NR::rotate const r86(NR::Point(.8, .6)); + NR::Matrix const mr86(r86); + UTEST_TEST("rotate constructor") { + NR::Point const p0(1.0, 0.0); + NR::Point const p90(0.0, 1.0); + UTEST_ASSERT( p0 * r86 == NR::Point(.8, .6) ); + UTEST_ASSERT( p0 * mr86 == NR::Point(.8, .6) ); + UTEST_ASSERT( p90 * r86 == NR::Point(-.6, .8) ); + UTEST_ASSERT( p90 * mr86 == NR::Point(-.6, .8) ); + UTEST_ASSERT(matrix_equalp(Matrix( r86 * r86 ), + mr86 * mr86, + 1e-14)); + } + + NR::translate const t23(2.0, 3.0); + UTEST_TEST("translate constructor") { + NR::Matrix const mt23(t23); + NR::Point const b(-2.0, 3.0); + UTEST_ASSERT( b * t23 == b * mt23 ); + } + + NR::scale const s_id(1.0, 1.0); + UTEST_TEST("test_identity") { + UTEST_ASSERT(m_id.test_identity()); + UTEST_ASSERT(Matrix(t_id).test_identity()); + UTEST_ASSERT(!(Matrix(NR::translate(-2, 3)).test_identity())); + UTEST_ASSERT(Matrix(r_id).test_identity()); + NR::rotate const rot180(NR::Point(-1, 0)); + UTEST_ASSERT(!(Matrix(rot180).test_identity())); + UTEST_ASSERT(Matrix(s_id).test_identity()); + UTEST_ASSERT(!(Matrix(NR::scale(1.0, 0.0)).test_identity())); + UTEST_ASSERT(!(Matrix(NR::scale(0.0, 1.0)).test_identity())); + UTEST_ASSERT(!(Matrix(NR::scale(1.0, -1.0)).test_identity())); + UTEST_ASSERT(!(Matrix(NR::scale(-1.0, -1.0)).test_identity())); + } + + UTEST_TEST("inverse") { + UTEST_ASSERT( m_id.inverse() == m_id ); + UTEST_ASSERT( Matrix(t23).inverse() == Matrix(NR::translate(-2.0, -3.0)) ); + NR::scale const s2(-4.0, 2.0); + NR::scale const sp5(-.25, .5); + UTEST_ASSERT( Matrix(s2).inverse() == Matrix(sp5) ); + } + + UTEST_TEST("nr_matrix_invert") { + NRMatrix const nr_m_id(m_id); + Matrix const m_s2(NR::scale(-4.0, 2.0)); + NRMatrix const nr_s2(m_s2); + Matrix const m_sp5(NR::scale(-.25, .5)); + NRMatrix const nr_sp5(m_sp5); + Matrix const m_t23(t23); + NRMatrix const nr_t23(m_t23); + NRMatrix inv; + nr_matrix_invert(&inv, &nr_m_id); + UTEST_ASSERT( Matrix(inv) == m_id ); + nr_matrix_invert(&inv, &nr_t23); + UTEST_ASSERT( Matrix(inv) == Matrix(NR::translate(-2.0, -3.0)) ); + nr_matrix_invert(&inv, &nr_s2); + UTEST_ASSERT( Matrix(inv) == Matrix(nr_sp5) ); + nr_matrix_invert(&inv, &nr_sp5); + UTEST_ASSERT( Matrix(inv) == Matrix(nr_s2) ); + + /* Test that nr_matrix_invert handles src == dest. */ + inv = nr_s2; + nr_matrix_invert(&inv, &inv); + UTEST_ASSERT( Matrix(inv) == Matrix(nr_sp5) ); + inv = nr_t23; + nr_matrix_invert(&inv, &inv); + UTEST_ASSERT( Matrix(inv) == Matrix(NR::translate(-2.0, -3.0)) ); + } + + UTEST_TEST("elliptic quadratic form") { + NR::Matrix const aff(1.0, 1.0, + 0.0, 1.0, + 5.0, 6.0); + NR::Matrix const invaff = aff.inverse(); + UTEST_ASSERT( invaff[1] == -1.0 ); + + NR::Matrix const ef(elliptic_quadratic_form(invaff)); + NR::Matrix const exp_ef(2, -1, + -1, 1, + 0, 0); + UTEST_ASSERT( ef == exp_ef ); + } + + UTEST_TEST("Matrix * rotate") { + NR::Matrix const ma(2.0, -1.0, + 4.0, 4.0, + -0.5, 2.0); + NR::Matrix const a_r86( ma * r86 ); + NR::Matrix const ma1( a_r86 * r86.inverse() ); + UTEST_ASSERT(matrix_equalp(ma1, ma, 1e-12)); + NR::Matrix const exp_a_r86( 2*.8 + -1*-.6, 2*.6 + -1*.8, + 4*.8 + 4*-.6, 4*.6 + 4*.8, + -.5*.8 + 2*-.6, -.5*.6 + 2*.8 ); + UTEST_ASSERT(matrix_equalp(a_r86, exp_a_r86, 1e-12)); + } + + UTEST_TEST("translate*scale, scale*translate") { + NR::translate const t2n4(2, -4); + NR::scale const sn2_8(-2, 8); + NR::Matrix const exp_ts(-2, 0, + 0, 8, + -4, -32); + NR::Matrix const exp_st(-2, 0, + 0, 8, + 2, -4); + UTEST_ASSERT( exp_ts == t2n4 * sn2_8 ); + UTEST_ASSERT( exp_st == sn2_8 * t2n4 ); + } + + UTEST_TEST("Matrix * scale") { + NR::Matrix const ma(2.0, -1.0, + 4.0, 4.0, + -0.5, 2.0); + NR::scale const sn2_8(-2, 8); + NR::Matrix const exp_as(-4, -8, + -8, 32, + 1, 16); + UTEST_ASSERT( ma * sn2_8 == exp_as ); + } + + if (!utest_end()) { + rc = EXIT_FAILURE; + } + + return rc; +} + + +/* + 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 : diff --git a/src/libnr/nr-matrix-test.h b/src/libnr/nr-matrix-test.h new file mode 100644 index 000000000..476852890 --- /dev/null +++ b/src/libnr/nr-matrix-test.h @@ -0,0 +1,221 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using NR::Matrix; +using NR::X; +using NR::Y; + +inline bool point_equalp(NR::Point const &a, NR::Point const &b) +{ + return ( NR_DF_TEST_CLOSE(a[X], b[X], 1e-5) && + NR_DF_TEST_CLOSE(a[Y], b[Y], 1e-5) ); +} + +class NrMatrixTest : public CxxTest::TestSuite +{ +public: + + NrMatrixTest() : + m_id( NR::identity() ), + r_id( NR::Point(1, 0) ), + t_id( 0, 0 ), + c16( 1.0, 2.0, + 3.0, 4.0, + 5.0, 6.0), + r86( NR::Point(.8, .6) ), + mr86( r86 ), + t23( 2.0, 3.0 ), + s_id( 1.0, 1.0 ) + { + } + virtual ~NrMatrixTest() {} + +// createSuite and destroySuite get us per-suite setup and teardown +// without us having to worry about static initialization order, etc. + static NrMatrixTest *createSuite() { return new NrMatrixTest(); } + static void destroySuite( NrMatrixTest *suite ) { delete suite; } + + Matrix const m_id; + NR::rotate const r_id; + NR::translate const t_id; + Matrix const c16; + NR::rotate const r86; + NR::Matrix const mr86; + NR::translate const t23; + NR::scale const s_id; + + + + + void testCtorsAssignmentOp(void) + { + Matrix const c16_copy(c16); + Matrix c16_eq(m_id); + c16_eq = c16; + for(unsigned i = 0; i < 6; ++i) { + TS_ASSERT_EQUALS( c16[i], 1.0 + i ); + TS_ASSERT_EQUALS( c16[i], c16_copy[i] ); + TS_ASSERT_EQUALS( c16[i], c16_eq[i] ); + TS_ASSERT_EQUALS( m_id[i], double( i == 0 || i == 3 ) ); + } + } + + void testScaleCtor(void) + { + NR::scale const s(2.0, 3.0); + NR::Matrix const ms(s); + NR::Point const p(5.0, 7.0); + TS_ASSERT_EQUALS( p * s, NR::Point(10.0, 21.0) ); + TS_ASSERT_EQUALS( p * ms, NR::Point(10.0, 21.0) ); + } + + void testRotateCtor(void) + { + NR::Point const p0(1.0, 0.0); + NR::Point const p90(0.0, 1.0); + TS_ASSERT_EQUALS( p0 * r86, NR::Point(.8, .6) ); + TS_ASSERT_EQUALS( p0 * mr86, NR::Point(.8, .6) ); + TS_ASSERT_EQUALS( p90 * r86, NR::Point(-.6, .8) ); + TS_ASSERT_EQUALS( p90 * mr86, NR::Point(-.6, .8) ); + TS_ASSERT( matrix_equalp(Matrix( r86 * r86 ), + mr86 * mr86, + 1e-14) ); + } + + void testTranslateCtor(void) + { + NR::Matrix const mt23(t23); + NR::Point const b(-2.0, 3.0); + TS_ASSERT_EQUALS( b * t23, b * mt23 ); + } + + void testIdentity(void) + { + TS_ASSERT( m_id.test_identity() ); + TS_ASSERT( Matrix(t_id).test_identity() ); + TS_ASSERT( !(Matrix(NR::translate(-2, 3)).test_identity()) ); + TS_ASSERT( Matrix(r_id).test_identity() ); + NR::rotate const rot180(NR::Point(-1, 0)); + TS_ASSERT( !(Matrix(rot180).test_identity()) ); + TS_ASSERT( Matrix(s_id).test_identity() ); + TS_ASSERT( !(Matrix(NR::scale(1.0, 0.0)).test_identity()) ); + TS_ASSERT( !(Matrix(NR::scale(0.0, 1.0)).test_identity()) ); + TS_ASSERT( !(Matrix(NR::scale(1.0, -1.0)).test_identity()) ); + TS_ASSERT( !(Matrix(NR::scale(-1.0, -1.0)).test_identity()) ); + } + + void testInverse(void) + { + TS_ASSERT_EQUALS( m_id.inverse(), m_id ); + TS_ASSERT_EQUALS( Matrix(t23).inverse(), Matrix(NR::translate(-2.0, -3.0)) ); + NR::scale const s2(-4.0, 2.0); + NR::scale const sp5(-.25, .5); + TS_ASSERT_EQUALS( Matrix(s2).inverse(), Matrix(sp5) ); + } + + void testNrMatrixInvert(void) + { + NRMatrix const nr_m_id(m_id); + Matrix const m_s2(NR::scale(-4.0, 2.0)); + NRMatrix const nr_s2(m_s2); + Matrix const m_sp5(NR::scale(-.25, .5)); + NRMatrix const nr_sp5(m_sp5); + Matrix const m_t23(t23); + NRMatrix const nr_t23(m_t23); + NRMatrix inv; + nr_matrix_invert(&inv, &nr_m_id); + TS_ASSERT_EQUALS( Matrix(inv), m_id ); + nr_matrix_invert(&inv, &nr_t23); + TS_ASSERT_EQUALS( Matrix(inv), Matrix(NR::translate(-2.0, -3.0)) ); + nr_matrix_invert(&inv, &nr_s2); + TS_ASSERT_EQUALS( Matrix(inv), Matrix(nr_sp5) ); + nr_matrix_invert(&inv, &nr_sp5); + TS_ASSERT_EQUALS( Matrix(inv), Matrix(nr_s2) ); + + /* Test that nr_matrix_invert handles src == dest. */ + inv = nr_s2; + nr_matrix_invert(&inv, &inv); + TS_ASSERT_EQUALS( Matrix(inv), Matrix(nr_sp5) ); + inv = nr_t23; + nr_matrix_invert(&inv, &inv); + TS_ASSERT_EQUALS( Matrix(inv), Matrix(NR::translate(-2.0, -3.0)) ); + } + + void testEllipticQuadraticForm(void) + { + NR::Matrix const aff(1.0, 1.0, + 0.0, 1.0, + 5.0, 6.0); + NR::Matrix const invaff = aff.inverse(); + TS_ASSERT_EQUALS( invaff[1], -1.0 ); + + NR::Matrix const ef(elliptic_quadratic_form(invaff)); + NR::Matrix const exp_ef(2, -1, + -1, 1, + 0, 0); + TS_ASSERT_EQUALS( ef, exp_ef ); + } + + void testMatrixStarRotate(void) + { + NR::Matrix const ma(2.0, -1.0, + 4.0, 4.0, + -0.5, 2.0); + NR::Matrix const a_r86( ma * r86 ); + NR::Matrix const ma1( a_r86 * r86.inverse() ); + TS_ASSERT( matrix_equalp(ma1, ma, 1e-12) ); + NR::Matrix const exp_a_r86( 2*.8 + -1*-.6, 2*.6 + -1*.8, + 4*.8 + 4*-.6, 4*.6 + 4*.8, + -.5*.8 + 2*-.6, -.5*.6 + 2*.8 ); + TS_ASSERT( matrix_equalp(a_r86, exp_a_r86, 1e-12) ); + } + + void testTranslateStarScale_ScaleStarTranslate(void) + { + NR::translate const t2n4(2, -4); + NR::scale const sn2_8(-2, 8); + NR::Matrix const exp_ts(-2, 0, + 0, 8, + -4, -32); + NR::Matrix const exp_st(-2, 0, + 0, 8, + 2, -4); + TS_ASSERT_EQUALS( exp_ts, t2n4 * sn2_8 ); + TS_ASSERT_EQUALS( exp_st, sn2_8 * t2n4 ); + } + + void testMatrixStarScale(void) + { + NR::Matrix const ma(2.0, -1.0, + 4.0, 4.0, + -0.5, 2.0); + NR::scale const sn2_8(-2, 8); + NR::Matrix const exp_as(-4, -8, + -8, 32, + 1, 16); + TS_ASSERT_EQUALS( ma * sn2_8, exp_as ); + } +}; + +/* + 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 : diff --git a/src/libnr/nr-matrix-translate-ops.cpp b/src/libnr/nr-matrix-translate-ops.cpp new file mode 100644 index 000000000..0ccdcf9ce --- /dev/null +++ b/src/libnr/nr-matrix-translate-ops.cpp @@ -0,0 +1,26 @@ +#include "libnr/nr-matrix-ops.h" + +namespace NR { + +Matrix +operator*(Matrix const &m, translate const &t) +{ + Matrix ret(m); + ret[4] += t[X]; + ret[5] += t[Y]; + assert_close( ret, m * Matrix(t) ); + return ret; +} + +} // namespace NR + +/* + 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 : diff --git a/src/libnr/nr-matrix-translate-ops.h b/src/libnr/nr-matrix-translate-ops.h new file mode 100644 index 000000000..f51bccaa1 --- /dev/null +++ b/src/libnr/nr-matrix-translate-ops.h @@ -0,0 +1,36 @@ +#ifndef SEEN_LIBNR_NR_MATRIX_TRANSLATE_OPS_H +#define SEEN_LIBNR_NR_MATRIX_TRANSLATE_OPS_H + +/** \file + * Declarations (and definition if inline) of operator + * blah (NR::Matrix, NR::translate). + */ + +#include "libnr/nr-matrix.h" +#include "libnr/nr-translate.h" + +//NR::Matrix operator*(NR::Matrix const &m, NR::translate const &t); + +namespace NR { +Matrix operator*(Matrix const &m, translate const &t); +} + +inline NR::Matrix +operator/(NR::Matrix const &numer, NR::translate const &denom) +{ + return numer * NR::translate(-denom.offset); +} + + +#endif /* !SEEN_LIBNR_NR_MATRIX_TRANSLATE_OPS_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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-matrix.cpp b/src/libnr/nr-matrix.cpp new file mode 100644 index 000000000..24fa2e206 --- /dev/null +++ b/src/libnr/nr-matrix.cpp @@ -0,0 +1,607 @@ +#define __NR_MATRIX_C__ + +/** \file + * Various matrix routines. Currently includes some NR::rotate etc. routines too. + */ + +/* + * Authors: + * Lauris Kaplinski + * + * This code is in public domain + */ + +#include "nr-matrix.h" + + + +/** + * Multiply two NRMatrices together, storing the result in d. + */ +NRMatrix * +nr_matrix_multiply(NRMatrix *d, NRMatrix const *m0, NRMatrix const *m1) +{ + if (m0) { + if (m1) { + NR::Coord d0 = m0->c[0] * m1->c[0] + m0->c[1] * m1->c[2]; + NR::Coord d1 = m0->c[0] * m1->c[1] + m0->c[1] * m1->c[3]; + NR::Coord d2 = m0->c[2] * m1->c[0] + m0->c[3] * m1->c[2]; + NR::Coord d3 = m0->c[2] * m1->c[1] + m0->c[3] * m1->c[3]; + NR::Coord d4 = m0->c[4] * m1->c[0] + m0->c[5] * m1->c[2] + m1->c[4]; + NR::Coord d5 = m0->c[4] * m1->c[1] + m0->c[5] * m1->c[3] + m1->c[5]; + + NR::Coord *dest = d->c; + *dest++ = d0; + *dest++ = d1; + *dest++ = d2; + *dest++ = d3; + *dest++ = d4; + *dest = d5; + } else { + *d = *m0; + } + } else { + if (m1) { + *d = *m1; + } else { + nr_matrix_set_identity(d); + } + } + + return d; +} + + + + +/** + * Store the inverted value of Matrix m in d + */ +NRMatrix * +nr_matrix_invert(NRMatrix *d, NRMatrix const *m) +{ + if (m) { + NR::Coord const det = m->c[0] * m->c[3] - m->c[1] * m->c[2]; + if (!NR_DF_TEST_CLOSE(det, 0.0, NR_EPSILON)) { + + NR::Coord const idet = 1.0 / det; + NR::Coord *dest = d->c; + + /* Cache m->c[0] and m->c[4] in case d == m. */ + NR::Coord const m_c0(m->c[0]); + NR::Coord const m_c4(m->c[4]); + + /*0*/ *dest++ = m->c[3] * idet; + /*1*/ *dest++ = -m->c[1] * idet; + /*2*/ *dest++ = -m->c[2] * idet; + /*3*/ *dest++ = m_c0 * idet; + /*4*/ *dest++ = -m_c4 * d->c[0] - m->c[5] * d->c[2]; + /*5*/ *dest = -m_c4 * d->c[1] - m->c[5] * d->c[3]; + + } else { + nr_matrix_set_identity(d); + } + } else { + nr_matrix_set_identity(d); + } + + return d; +} + + + + + +/** + * Set this matrix to a translation of x and y + */ +NRMatrix * +nr_matrix_set_translate(NRMatrix *m, NR::Coord const x, NR::Coord const y) +{ + NR::Coord *dest = m->c; + + *dest++ = 1.0; //0 + *dest++ = 0.0; //1 + *dest++ = 0.0; //2 + *dest++ = 1.0; //3 + *dest++ = x; //4 + *dest = y; //5 + + return m; +} + + + + + +/** + * Set this matrix to a scaling transform in sx and sy + */ +NRMatrix * +nr_matrix_set_scale(NRMatrix *m, NR::Coord const sx, NR::Coord const sy) +{ + NR::Coord *dest = m->c; + + *dest++ = sx; //0 + *dest++ = 0.0; //1 + *dest++ = 0.0; //2 + *dest++ = sy; //3 + *dest++ = 0.0; //4 + *dest = 0.0; //5 + + return m; +} + + + + + +/** + * Set this matrix to a rotating transform of angle 'theta' radians + */ +NRMatrix * +nr_matrix_set_rotate(NRMatrix *m, NR::Coord const theta) +{ + NR::Coord const s = sin(theta); + NR::Coord const c = cos(theta); + + NR::Coord *dest = m->c; + + *dest++ = c; //0 + *dest++ = s; //1 + *dest++ = -s; //2 + *dest++ = c; //3 + *dest++ = 0.0; //4 + *dest = 0.0; //5 + + return m; +} + + + + + + + + + +/** + * Implement NR functions and methods + */ +namespace NR { + + + + + +/** + * Constructor. Assign to nr if not null, else identity + */ +Matrix::Matrix(NRMatrix const *nr) +{ + if (nr) { + assign(nr->c); + } else { + set_identity(); + } +} + + + + + +/** + * Multiply two matrices together + */ +Matrix operator*(Matrix const &m0, Matrix const &m1) +{ + NR::Coord const d0 = m0[0] * m1[0] + m0[1] * m1[2]; + NR::Coord const d1 = m0[0] * m1[1] + m0[1] * m1[3]; + NR::Coord const d2 = m0[2] * m1[0] + m0[3] * m1[2]; + NR::Coord const d3 = m0[2] * m1[1] + m0[3] * m1[3]; + NR::Coord const d4 = m0[4] * m1[0] + m0[5] * m1[2] + m1[4]; + NR::Coord const d5 = m0[4] * m1[1] + m0[5] * m1[3] + m1[5]; + + Matrix ret( d0, d1, d2, d3, d4, d5 ); + + return ret; +} + + + + + +/** + * Multiply a matrix by another + */ +Matrix &Matrix::operator*=(Matrix const &o) +{ + *this = *this * o; + return *this; +} + + + + + +/** + * Multiply by a scaling matrix + */ +Matrix &Matrix::operator*=(scale const &other) +{ + /* This loop is massive overkill. Let's unroll. + * o _c[] goes from 0..5 + * o other[] alternates between 0 and 1 + */ + /* + * for (unsigned i = 0; i < 3; ++i) { + * for (unsigned j = 0; j < 2; ++j) { + * this->_c[i * 2 + j] *= other[j]; + * } + * } + */ + + NR::Coord const xscale = other[0]; + NR::Coord const yscale = other[1]; + NR::Coord *dest = _c; + + /*i=0 j=0*/ *dest++ *= xscale; + /*i=0 j=1*/ *dest++ *= yscale; + /*i=1 j=0*/ *dest++ *= xscale; + /*i=1 j=1*/ *dest++ *= yscale; + /*i=2 j=0*/ *dest++ *= xscale; + /*i=2 j=1*/ *dest *= yscale; + + return *this; +} + + + + + +/** + * Return the inverse of this matrix. If an inverse is not defined, + * then return the identity matrix. + */ +Matrix Matrix::inverse() const +{ + Matrix d(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); + + NR::Coord const det = _c[0] * _c[3] - _c[1] * _c[2]; + if (!NR_DF_TEST_CLOSE(det, 0.0, NR_EPSILON)) { + + NR::Coord const idet = 1.0 / det; + NR::Coord *dest = d._c; + + /*0*/ *dest++ = _c[3] * idet; + /*1*/ *dest++ = -_c[1] * idet; + /*2*/ *dest++ = -_c[2] * idet; + /*3*/ *dest++ = _c[0] * idet; + /*4*/ *dest++ = -_c[4] * d._c[0] - _c[5] * d._c[2]; + /*5*/ *dest = -_c[4] * d._c[1] - _c[5] * d._c[3]; + + } else { + d.set_identity(); + } + + return d; +} + + + + + +/** + * Set this matrix to Identity + */ +void Matrix::set_identity() +{ + NR::Coord *dest = _c; + + *dest++ = 1.0; //0 + *dest++ = 0.0; //1 + *dest++ = 0.0; //2 + *dest++ = 1.0; //3 + // translation + *dest++ = 0.0; //4 + *dest = 0.0; //5 +} + + + + + +/** + * return an Identity matrix + */ +Matrix identity() +{ + Matrix ret(1.0, 0.0, + 0.0, 1.0, + 0.0, 0.0); + return ret; +} + + + + + +/** + * + */ +Matrix from_basis(Point const x_basis, Point const y_basis, Point const offset) +{ + Matrix const ret(x_basis[X], y_basis[X], + x_basis[Y], y_basis[Y], + offset[X], offset[Y]); + return ret; +} + + + + +/** + * Returns a rotation matrix corresponding by the specified angle (in radians) about the origin. + * + * \see NR::rotate_degrees + * + * Angle direction in Inkscape code: If you use the traditional mathematics convention that y + * increases upwards, then positive angles are anticlockwise as per the mathematics convention. If + * you take the common non-mathematical convention that y increases downwards, then positive angles + * are clockwise, as is common outside of mathematics. + */ +rotate::rotate(NR::Coord const theta) : + vec(cos(theta), + sin(theta)) +{ +} + + + + + +/** + * Return the determinant of the Matrix + */ +NR::Coord Matrix::det() const +{ + return _c[0] * _c[3] - _c[1] * _c[2]; +} + + + + + +/** + * Return the scalar of the descriminant of the Matrix + */ +NR::Coord Matrix::descrim2() const +{ + return fabs(det()); +} + + + + + +/** + * Return the descriminant of the Matrix + */ +NR::Coord Matrix::descrim() const +{ + return sqrt(descrim2()); +} + + + + + +/** + * Assign a matrix to a given coordinate array + */ +Matrix &Matrix::assign(Coord const *array) +{ + assert(array != NULL); + + Coord const *src = array; + Coord *dest = _c; + + *dest++ = *src++; //0 + *dest++ = *src++; //1 + *dest++ = *src++; //2 + *dest++ = *src++; //3 + *dest++ = *src++; //4 + *dest = *src ; //5 + + return *this; +} + + + + + +/** + * Copy this matrix's value to a NRMatrix + */ +NRMatrix *Matrix::copyto(NRMatrix *nrm) const { + + assert(nrm != NULL); + + Coord const *src = _c; + Coord *dest = nrm->c; + + *dest++ = *src++; //0 + *dest++ = *src++; //1 + *dest++ = *src++; //2 + *dest++ = *src++; //3 + *dest++ = *src++; //4 + *dest = *src ; //5 + + return nrm; +} + + + + +/** + * Copy this matrix's values to an array + */ +NR::Coord *Matrix::copyto(NR::Coord *array) const { + + assert(array != NULL); + + Coord const *src = _c; + Coord *dest = array; + + *dest++ = *src++; //0 + *dest++ = *src++; //1 + *dest++ = *src++; //2 + *dest++ = *src++; //3 + *dest++ = *src++; //4 + *dest = *src ; //5 + + return array; +} + + + + + +/** + * + */ +double expansion(Matrix const &m) { + return sqrt(fabs(m.det())); +} + + + + + +/** + * + */ +double Matrix::expansion() const { + return sqrt(fabs(det())); +} + + + + + +/** + * + */ +double Matrix::expansionX() const { + return sqrt(_c[0] * _c[0] + _c[1] * _c[1]); +} + + + + + +/** + * + */ +double Matrix::expansionY() const { + return sqrt(_c[2] * _c[2] + _c[3] * _c[3]); +} + + + + + +/** + * + */ +bool Matrix::is_translation(Coord const eps) const { + return ( fabs(_c[0] - 1.0) < eps && + fabs(_c[3] - 1.0) < eps && + fabs(_c[1]) < eps && + fabs(_c[2]) < eps ); +} + + + + + +/** + * + */ +bool Matrix::test_identity() const { + return NR_MATRIX_DF_TEST_CLOSE(this, &NR_MATRIX_IDENTITY, NR_EPSILON); +} + + + + + +/** + * + */ +bool transform_equalp(Matrix const &m0, Matrix const &m1, NR::Coord const epsilon) { + return NR_MATRIX_DF_TEST_TRANSFORM_CLOSE(&m0, &m1, epsilon); +} + + + + + +/** + * + */ +bool translate_equalp(Matrix const &m0, Matrix const &m1, NR::Coord const epsilon) { + return NR_MATRIX_DF_TEST_TRANSLATE_CLOSE(&m0, &m1, epsilon); +} + + + + + +/** + * + */ +bool matrix_equalp(Matrix const &m0, Matrix const &m1, NR::Coord const epsilon) +{ + return ( NR_MATRIX_DF_TEST_TRANSFORM_CLOSE(&m0, &m1, epsilon) && + NR_MATRIX_DF_TEST_TRANSLATE_CLOSE(&m0, &m1, epsilon) ); +} + + + + + +/** + * A home-made assertion. Stop if the two matrixes are not 'close' to + * each other. + */ +void assert_close(Matrix const &a, Matrix const &b) +{ + if (!matrix_equalp(a, b, 1e-3)) { + fprintf(stderr, + "a = | %g %g |,\tb = | %g %g |\n" + " | %g %g | \t | %g %g |\n" + " | %g %g | \t | %g %g |\n", + a[0], a[1], b[0], b[1], + a[2], a[3], b[2], b[3], + a[4], a[5], b[4], b[5]); + abort(); + } +} + + + +} //namespace NR + + + +/* + 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 : diff --git a/src/libnr/nr-matrix.h b/src/libnr/nr-matrix.h new file mode 100644 index 000000000..47196137e --- /dev/null +++ b/src/libnr/nr-matrix.h @@ -0,0 +1,453 @@ +#ifndef __NR_MATRIX_H__ +#define __NR_MATRIX_H__ + +/** \file + * Definition of NRMatrix and NR::Matrix types. + * + * \note Operator functions (e.g. Matrix * Matrix etc.) are mostly in + * libnr/nr-matrix-ops.h. See end of file for discussion. + * + * Main authors: + * Lauris Kaplinski : + * Original NRMatrix definition and related macros. + * + * Nathan Hurst : + * NR::Matrix class version of the above. + * + * This code is in public domain. + */ + +#include + +#include "libnr/nr-coord.h" +#include "libnr/nr-values.h" +#include +#include +#include + +/// NRMatrix is the obsolete form of NR::Matrix. +/// It consists of six NR::Coord values. +struct NRMatrix { + NR::Coord c[6]; + + NR::Coord &operator[](int i) { return c[i]; } + NR::Coord operator[](int i) const { return c[i]; } +}; + +#define nr_matrix_set_identity(m) (*(m) = NR_MATRIX_IDENTITY) + +#define nr_matrix_test_identity(m,e) (!(m) || NR_MATRIX_DF_TEST_CLOSE(m, &NR_MATRIX_IDENTITY, e)) + +#define nr_matrix_test_equal(m0,m1,e) ((!(m0) && !(m1)) || ((m0) && (m1) && NR_MATRIX_DF_TEST_CLOSE(m0, m1, e))) +#define nr_matrix_test_transform_equal(m0,m1,e) ((!(m0) && !(m1)) || ((m0) && (m1) && NR_MATRIX_DF_TEST_TRANSFORM_CLOSE(m0, m1, e))) +#define nr_matrix_test_translate_equal(m0,m1,e) ((!(m0) && !(m1)) || ((m0) && (m1) && NR_MATRIX_DF_TEST_TRANSLATE_CLOSE(m0, m1, e))) + +NRMatrix *nr_matrix_invert(NRMatrix *d, NRMatrix const *m); + +/* d,m0,m1 needn't be distinct in any of these multiply routines. */ + +NRMatrix *nr_matrix_multiply(NRMatrix *d, NRMatrix const *m0, NRMatrix const *m1); + +NRMatrix *nr_matrix_set_translate(NRMatrix *m, NR::Coord const x, NR::Coord const y); + +NRMatrix *nr_matrix_set_scale(NRMatrix *m, NR::Coord const sx, NR::Coord const sy); + +NRMatrix *nr_matrix_set_rotate(NRMatrix *m, NR::Coord const theta); + +#define NR_MATRIX_DF_TRANSFORM_X(m,x,y) ((*(m))[0] * (x) + (*(m))[2] * (y) + (*(m))[4]) +#define NR_MATRIX_DF_TRANSFORM_Y(m,x,y) ((*(m))[1] * (x) + (*(m))[3] * (y) + (*(m))[5]) + +#define NR_MATRIX_DF_EXPANSION2(m) (fabs((*(m))[0] * (*(m))[3] - (*(m))[1] * (*(m))[2])) +#define NR_MATRIX_DF_EXPANSION(m) (sqrt(NR_MATRIX_DF_EXPANSION2(m))) + +namespace NR { + +/** + * The Matrix class. + * + * For purposes of multiplication, points should be thought of as row vectors + * + * p = ( p[X] p[Y] 1 ) + * + * to be right-multiplied by transformation matrices + * \verbatim + c[] = | c[0] c[1] 0 | + | c[2] c[3] 0 | + | c[4] c[5] 1 | \endverbatim + * + * (so the columns of the matrix correspond to the columns (elements) of the result, + * and the rows of the matrix correspond to columns (elements) of the "input"). + */ +class Matrix { + + + public: + + /** + * Various forms of constructor + */ + + /** + * + */ + explicit Matrix() { } + + + /** + * + */ + Matrix(Matrix const &m) { + + NR::Coord const *src = m._c; + NR::Coord *dest = _c; + + *dest++ = *src++; //0 + *dest++ = *src++; //1 + *dest++ = *src++; //2 + *dest++ = *src++; //3 + *dest++ = *src++; //4 + *dest = *src ; //5 + + } + + + + + /** + * + */ + Matrix(NRMatrix const &m) { + + NR::Coord const *src = m.c; + NR::Coord *dest = _c; + + *dest++ = *src++; //0 + *dest++ = *src++; //1 + *dest++ = *src++; //2 + *dest++ = *src++; //3 + *dest++ = *src++; //4 + *dest = *src ; //5 + + } + + + + + /** + * + */ + Matrix(double c0, double c1, + double c2, double c3, + double c4, double c5) { + + NR::Coord *dest = _c; + + *dest++ = c0; //0 + *dest++ = c1; //1 + *dest++ = c2; //2 + *dest++ = c3; //3 + *dest++ = c4; //4 + *dest = c5; //5 + + } + + + + /** + * + */ + Matrix &operator=(Matrix const &m) { + + NR::Coord const *src = m._c; + NR::Coord *dest = _c; + + *dest++ = *src++; //0 + *dest++ = *src++; //1 + *dest++ = *src++; //2 + *dest++ = *src++; //3 + *dest++ = *src++; //4 + *dest = *src ; //5 + + return *this; + } + + + + + /** + * + */ + explicit Matrix(scale const &sm) { + + NR::Coord *dest = _c; + + *dest++ = sm[X]; //0 + *dest++ = 0.0; //1 + *dest++ = 0.0; //2 + *dest++ = sm[Y]; //3 + *dest++ = 0.0; //4 + *dest = 0.0; //5 + + } + + + + + + + /** + * + */ + explicit Matrix(rotate const &r) { + + NR::Coord *dest = _c; + + *dest++ = r.vec[X]; //0 + *dest++ = r.vec[Y]; //1 + *dest++ = -r.vec[Y]; //2 + *dest++ = r.vec[X]; //3 + *dest++ = 0.0; //4 + *dest = 0.0; //5 + + } + + + + + /** + * + */ + explicit Matrix(translate const &tm) { + + NR::Coord *dest = _c; + + *dest++ = 1.0; //0 + *dest++ = 0.0; //1 + *dest++ = 0.0; //2 + *dest++ = 1.0; //3 + *dest++ = tm[X]; //4 + *dest = tm[Y]; //5 + } + + + + /** + * + */ + Matrix(NRMatrix const *nr); + + + /** + * + */ + bool test_identity() const; + + + /** + * + */ + bool is_translation(Coord const eps = 1e-6) const; + + + /** + * + */ + Matrix inverse() const; + + + /** + * + */ + Matrix &operator*=(Matrix const &other); + + + /** + * + */ + Matrix &operator*=(scale const &other); + + + + /** + * + */ + Matrix &operator*=(translate const &other) { + _c[4] += other[X]; + _c[5] += other[Y]; + return *this; + } + + + + /** + * + */ + inline Coord &operator[](int const i) { + return _c[i]; + } + + + + /** + * + */ + inline Coord operator[](int const i) const { + return _c[i]; + } + + + /** + * + */ + void set_identity(); + + /** + * + */ + Coord det() const; + + + /** + * + */ + Coord descrim2() const; + + + /** + * + */ + Coord descrim() const; + + + /** + * + */ + double expansion() const; + + + /** + * + */ + double expansionX() const; + + + /** + * + */ + double expansionY() const; + + // legacy + + + /** + * + */ + Matrix &assign(Coord const *array); + + + /** + * + */ + NRMatrix *copyto(NRMatrix* nrm) const; + + + /** + * + */ + Coord *copyto(Coord *array) const; + + + + /** + * + */ + operator NRMatrix&() { + g_assert(sizeof(_c) == sizeof(NRMatrix)); + return *reinterpret_cast(_c); + } + + + + /** + * + */ + operator NRMatrix const&() const { + g_assert(sizeof(_c) == sizeof(NRMatrix)); + return *reinterpret_cast(_c); + } + + + + /** + * + */ + operator NRMatrix*() { + g_assert(sizeof(_c) == sizeof(NRMatrix)); + return reinterpret_cast(_c); + } + + + /** + * + */ + operator NRMatrix const*() const { + g_assert(sizeof(_c) == sizeof(NRMatrix)); + return reinterpret_cast(_c); + } + + + private: + + + NR::Coord _c[6]; +}; + +/** A function to print out the Matrix (for debugging) */ +inline std::ostream &operator<< (std::ostream &out_file, const NR::Matrix &m) { + out_file << "A: " << m[0] << " C: " << m[2] << " E: " << m[4] << "\n"; + out_file << "B: " << m[1] << " D: " << m[3] << " F: " << m[5] << "\n"; + return out_file; +} + +extern void assert_close(Matrix const &a, Matrix const &b); + +} /* namespace NR */ + + + + + + + +/** \note + * Discussion of splitting up nr-matrix.h into lots of little files: + * + * Advantages: + * + * - Reducing amount of recompilation necessary when anything changes. + * + * - Hopefully also reducing compilation time by reducing the number of inline + * function definitions encountered by the compiler for a given .o file. + * (No timing comparisons done yet. On systems without much memory available + * for caching, this may be outweighed by additional I/O costs.) + * + * Disadvantages: + * + * - More #include lines necessary per file. If a compile fails due to + * not having all the necessary #include lines, then the developer needs + * to spend some time working out what #include to add. + */ + +#endif /* !__NR_MATRIX_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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-maybe.h b/src/libnr/nr-maybe.h new file mode 100644 index 000000000..6eed01ec1 --- /dev/null +++ b/src/libnr/nr-maybe.h @@ -0,0 +1,140 @@ +#ifndef __NR_MAYBE_H__ +#define __NR_MAYBE_H__ + +/* + * Functionalesque "Maybe" class + * + * Copyright 2004 MenTaLguY + * + * Authors: + * MenTaLguY + * + * This code is licensed under the GNU GPL; see COPYING for more information. + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +namespace NR { + +/** An exception class for run-time type errors */ +template +class IsNot : public std::domain_error { +public: + IsNot() : domain_error(std::string("Is not ") + typeid(T).name()) {} +}; + +/** A type with only one value, which (in principle) is only equal to itself. + * + * Types that may (at runtime) pretend to be Nothing need only provide an + * operator bool operator==(Type, Nothing); the rest of the operator + * definitions will be taken care of automatically. + * + * Such types should also provide a casting operator to Nothing, obviously. + */ +struct Nothing { + bool operator==(Nothing n) { return true; } + bool operator!=(Nothing n) { return false; } + template + bool operator==(T t) { return t == *this; } + template + bool operator!=(T t) { return t != *this; } +}; + +template +bool operator==(T t, Nothing n) { return false; } +template +bool operator!=(T t, Nothing n) { return !( t == n ); } + +template +struct MaybeTraits; + +template +class Maybe { +public: + typedef MaybeTraits traits; + typedef typename traits::storage storage; + typedef typename traits::reference reference; + + Maybe(Nothing n) : _is_nothing(true), _t() {} + + Maybe(const Maybe &m) : _is_nothing(m._is_nothing), _t(m._t) {} + + template + Maybe(const Maybe &m) + : _is_nothing(m._is_nothing), + _t(traits::to_storage(MaybeTraits::from_storage(m._t))) {} + + template + Maybe(T2 t) : _is_nothing(false), _t(traits::to_storage(t)) {} + + reference assume() const throw(IsNot) { + if (_is_nothing) { + throw IsNot(); + } else { + return traits::from_storage(_t); + } + } + + operator reference() const throw(IsNot) { + if (_is_nothing) { + throw IsNot(); + } else { + return traits::from_storage(_t); + } + } + operator Nothing() const throw(IsNot) { + if (!_is_nothing) { + throw IsNot(); + } else { + return Nothing(); + } + } + + bool operator==(Nothing n) { return _is_nothing; } + bool operator==(reference r) { + return traits::from_storage(_t) == r; + } + +private: + bool _is_nothing; + storage _t; +}; + +/* traits classes used by Maybe */ + +template +struct MaybeTraits { + typedef T const storage; + typedef T const &reference; + static reference to_storage(reference t) { return t; } + static reference from_storage(reference t) { return t; } +}; + +template +struct MaybeTraits { + typedef T *storage; + typedef T &reference; + static storage to_storage(reference t) { return &t; } + static reference from_storage(storage t) { return *t; } +}; + +} /* namespace NR */ + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-object.cpp b/src/libnr/nr-object.cpp new file mode 100644 index 000000000..b18685d11 --- /dev/null +++ b/src/libnr/nr-object.cpp @@ -0,0 +1,295 @@ +#define __NR_OBJECT_C__ + +/* + * RGBA display list system for inkscape + * + * Authors: + * Lauris Kaplinski + * MenTaLguY + * + * This code is in public domain + */ + +#include +#include + +#include + +#include "nr-object.h" + +unsigned int nr_emit_fail_warning(const gchar *file, unsigned int line, const gchar *method, const gchar *expr) +{ + fprintf (stderr, "File %s line %d (%s): Assertion %s failed\n", file, line, method, expr); + return 1; +} + +/* NRObject */ + +static NRObjectClass **classes = NULL; +static unsigned int classes_len = 0; +static unsigned int classes_size = 0; + +NRType nr_type_is_a(NRType type, NRType test) +{ + nr_return_val_if_fail(type < classes_len, FALSE); + nr_return_val_if_fail(test < classes_len, FALSE); + + NRObjectClass *c = classes[type]; + + while (c) { + if (c->type == test) { + return TRUE; + } + c = c->parent; + } + + return FALSE; +} + +void const *nr_object_check_instance_cast(void const *ip, NRType tc) +{ + nr_return_val_if_fail(ip != NULL, NULL); + nr_return_val_if_fail(nr_type_is_a(((NRObject const *) ip)->klass->type, tc), ip); + return ip; +} + +unsigned int nr_object_check_instance_type(void const *ip, NRType tc) +{ + if (ip == NULL) { + return FALSE; + } + + return nr_type_is_a(((NRObject const *) ip)->klass->type, tc); +} + +NRType nr_object_register_type(NRType parent, + gchar const *name, + unsigned int csize, + unsigned int isize, + void (* cinit) (NRObjectClass *), + void (* iinit) (NRObject *)) +{ + if (classes_len >= classes_size) { + classes_size += 32; + classes = nr_renew (classes, NRObjectClass *, classes_size); + if (classes_len == 0) { + classes[0] = NULL; + classes_len = 1; + } + } + + NRType const type = classes_len; + classes_len += 1; + + classes[type] = (NRObjectClass*) new char[csize]; + NRObjectClass *c = classes[type]; + + /* FIXME: is this necessary? */ + memset(c, 0, csize); + + if (classes[parent]) { + memcpy(c, classes[parent], classes[parent]->csize); + } + + c->type = type; + c->parent = classes[parent]; + c->name = strdup(name); + c->csize = csize; + c->isize = isize; + c->cinit = cinit; + c->iinit = iinit; + + c->cinit(c); + + return type; +} + +static void nr_object_class_init (NRObjectClass *klass); +static void nr_object_init (NRObject *object); +static void nr_object_finalize (NRObject *object); + +NRType nr_object_get_type() +{ + static NRType type = 0; + + if (!type) { + type = nr_object_register_type (0, + "NRObject", + sizeof (NRObjectClass), + sizeof (NRObject), + (void (*) (NRObjectClass *)) nr_object_class_init, + (void (*) (NRObject *)) nr_object_init); + } + + return type; +} + +static void nr_object_class_init(NRObjectClass *c) +{ + c->finalize = nr_object_finalize; + c->cpp_ctor = NRObject::invoke_ctor; +} + +static void nr_object_init (NRObject *object) +{ +} + +static void nr_object_finalize (NRObject *object) +{ +} + +/* Dynamic lifecycle */ + +static void nr_class_tree_object_invoke_init(NRObjectClass *c, NRObject *object) +{ + if (c->parent) { + nr_class_tree_object_invoke_init(c->parent, object); + } + c->iinit (object); +} + +namespace { + +void finalize_object(void *base, void *) +{ + NRObject *object = reinterpret_cast(base); + object->klass->finalize(object); + object->~NRObject(); +} + +} + +NRObject *NRObject::alloc(NRType type) +{ + nr_return_val_if_fail (type < classes_len, NULL); + + NRObjectClass *c = classes[type]; + + if ( c->parent && c->cpp_ctor == c->parent->cpp_ctor ) { + g_error("Cannot instantiate NRObject class %s which has not registered a C++ constructor\n", c->name); + } + + NRObject *object = reinterpret_cast( + ::operator new(c->isize, Inkscape::GC::SCANNED, Inkscape::GC::AUTO, + &finalize_object, NULL) + ); + memset(object, 0xf0, c->isize); + + object->klass = c; + c->cpp_ctor(object); + nr_class_tree_object_invoke_init (c, object); + + return object; +} + +/* NRActiveObject */ + +static void nr_active_object_class_init(NRActiveObjectClass *c); +static void nr_active_object_init(NRActiveObject *object); +static void nr_active_object_finalize(NRObject *object); + +static NRObjectClass *parent_class; + +NRType nr_active_object_get_type() +{ + static NRType type = 0; + if (!type) { + type = nr_object_register_type (NR_TYPE_OBJECT, + "NRActiveObject", + sizeof (NRActiveObjectClass), + sizeof (NRActiveObject), + (void (*) (NRObjectClass *)) nr_active_object_class_init, + (void (*) (NRObject *)) nr_active_object_init); + } + return type; +} + +static void nr_active_object_class_init(NRActiveObjectClass *c) +{ + NRObjectClass *object_class = (NRObjectClass *) c; + + parent_class = object_class->parent; + + object_class->finalize = nr_active_object_finalize; + object_class->cpp_ctor = NRObject::invoke_ctor; +} + +static void nr_active_object_init(NRActiveObject *object) +{ +} + +static void nr_active_object_finalize(NRObject *object) +{ + NRActiveObject *aobject = (NRActiveObject *) object; + + if (aobject->callbacks) { + for (unsigned int i = 0; i < aobject->callbacks->length; i++) { + NRObjectListener *listener = aobject->callbacks->listeners + i; + if ( listener->vector->dispose ) { + listener->vector->dispose(object, listener->data); + } + } + free (aobject->callbacks); + } + + ((NRObjectClass *) (parent_class))->finalize(object); +} + +void nr_active_object_add_listener(NRActiveObject *object, + const NRObjectEventVector *vector, + unsigned int size, + void *data) +{ + if (!object->callbacks) { + object->callbacks = (NRObjectCallbackBlock*) malloc(sizeof(NRObjectCallbackBlock)); + object->callbacks->size = 1; + object->callbacks->length = 0; + } + + if (object->callbacks->length >= object->callbacks->size) { + int newsize = object->callbacks->size << 1; + object->callbacks = (NRObjectCallbackBlock *) + realloc(object->callbacks, sizeof(NRObjectCallbackBlock) + (newsize - 1) * sizeof (NRObjectListener)); + object->callbacks->size = newsize; + } + + NRObjectListener *listener = object->callbacks->listeners + object->callbacks->length; + listener->vector = vector; + listener->size = size; + listener->data = data; + object->callbacks->length += 1; +} + +void nr_active_object_remove_listener_by_data(NRActiveObject *object, void *data) +{ + if (object->callbacks == NULL) { + return; + } + + for (unsigned i = 0; i < object->callbacks->length; i++) { + NRObjectListener *listener = object->callbacks->listeners + i; + if ( listener->data == data ) { + object->callbacks->length -= 1; + if ( object->callbacks->length < 1 ) { + free(object->callbacks); + object->callbacks = NULL; + } else if ( object->callbacks->length != i ) { + *listener = object->callbacks->listeners[object->callbacks->length]; + } + 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 : diff --git a/src/libnr/nr-object.h b/src/libnr/nr-object.h new file mode 100644 index 000000000..f39814b0d --- /dev/null +++ b/src/libnr/nr-object.h @@ -0,0 +1,157 @@ +#ifndef __NR_OBJECT_H__ +#define __NR_OBJECT_H__ + +/* + * RGBA display list system for inkscape + * + * Authors: + * Lauris Kaplinski + * MenTaLguY + * + * This code is in public domain + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "gc-managed.h" +#include "gc-finalized.h" +#include "gc-anchored.h" + +typedef guint32 NRType; + +struct NRObject; +struct NRObjectClass; + +#define NR_TYPE_OBJECT (nr_object_get_type ()) +#define NR_OBJECT(o) (NR_CHECK_INSTANCE_CAST ((o), NR_TYPE_OBJECT, NRObject)) +#define NR_IS_OBJECT(o) (NR_CHECK_INSTANCE_TYPE ((o), NR_TYPE_OBJECT)) + +#define NR_TYPE_ACTIVE_OBJECT (nr_active_object_get_type ()) +#define NR_ACTIVE_OBJECT(o) (NR_CHECK_INSTANCE_CAST ((o), NR_TYPE_ACTIVE_OBJECT, NRActiveObject)) +#define NR_IS_ACTIVE_OBJECT(o) (NR_CHECK_INSTANCE_TYPE ((o), NR_TYPE_ACTIVE_OBJECT)) + +#define nr_return_if_fail(expr) if (!(expr) && nr_emit_fail_warning (__FILE__, __LINE__, "?", #expr)) return +#define nr_return_val_if_fail(expr,val) if (!(expr) && nr_emit_fail_warning (__FILE__, __LINE__, "?", #expr)) return (val) + +unsigned int nr_emit_fail_warning (const gchar *file, unsigned int line, const gchar *method, const gchar *expr); + +#ifndef NR_DISABLE_CAST_CHECKS +#define NR_CHECK_INSTANCE_CAST(ip, tc, ct) ((ct *) nr_object_check_instance_cast (ip, tc)) +#else +#define NR_CHECK_INSTANCE_CAST(ip, tc, ct) ((ct *) ip) +#endif + +#define NR_CHECK_INSTANCE_TYPE(ip, tc) nr_object_check_instance_type (ip, tc) +#define NR_OBJECT_GET_CLASS(ip) (((NRObject *) ip)->klass) + +NRType nr_type_is_a (NRType type, NRType test); + +void const *nr_object_check_instance_cast(void const *ip, NRType tc); +unsigned int nr_object_check_instance_type(void const *ip, NRType tc); + +NRType nr_object_register_type (NRType parent, + gchar const *name, + unsigned int csize, + unsigned int isize, + void (* cinit) (NRObjectClass *), + void (* iinit) (NRObject *)); + +/* NRObject */ + +class NRObject : public Inkscape::GC::Managed<>, + public Inkscape::GC::Finalized, + public Inkscape::GC::Anchored +{ +public: + NRObjectClass *klass; + + static NRObject *alloc(NRType type); + + template + static void invoke_ctor(NRObject *object) { + new (object) T(); + } + + /* these can go away eventually */ + NRObject *reference() { + return Inkscape::GC::anchor(this); + } + NRObject *unreference() { + Inkscape::GC::release(this); + return NULL; + } + +protected: + NRObject() {} + +private: + NRObject(NRObject const &); // no copy + void operator=(NRObject const &); // no assign + + void *operator new(size_t size, void *placement) { return placement; } +}; + +struct NRObjectClass { + NRType type; + NRObjectClass *parent; + + gchar *name; + unsigned int csize; + unsigned int isize; + void (* cinit) (NRObjectClass *); + void (* iinit) (NRObject *); + void (* finalize) (NRObject *object); + void (*cpp_ctor)(NRObject *object); +}; + +NRType nr_object_get_type (void); + +/* Dynamic lifecycle */ + +inline NRObject *nr_object_new (NRType type) { + return NRObject::alloc(type); +} + +inline NRObject *nr_object_ref (NRObject *object) { + return object->reference(); +} +inline NRObject *nr_object_unref (NRObject *object) { + return object->unreference(); +} + +/* NRActiveObject */ + +struct NRObjectEventVector { + void (* dispose) (NRObject *object, void *data); +}; + +struct NRObjectListener { + const NRObjectEventVector *vector; + unsigned int size; + void *data; +}; + +struct NRObjectCallbackBlock { + unsigned int size; + unsigned int length; + NRObjectListener listeners[1]; +}; + +struct NRActiveObject : public NRObject { + NRActiveObject() : callbacks(NULL) {} + NRObjectCallbackBlock *callbacks; +}; + +struct NRActiveObjectClass : public NRObjectClass { +}; + +NRType nr_active_object_get_type (void); + +void nr_active_object_add_listener (NRActiveObject *object, const NRObjectEventVector *vector, unsigned int size, void *data); +void nr_active_object_remove_listener_by_data (NRActiveObject *object, void *data); + +#endif + diff --git a/src/libnr/nr-path-code.h b/src/libnr/nr-path-code.h new file mode 100644 index 000000000..cc174d73b --- /dev/null +++ b/src/libnr/nr-path-code.h @@ -0,0 +1,28 @@ +#ifndef SEEN_LIBNR_NR_PATH_CODE_H +#define SEEN_LIBNR_NR_PATH_CODE_H + +/** \file + * NRPathcode enum definition + */ + +typedef enum { + NR_MOVETO, ///< Start of closed subpath + NR_MOVETO_OPEN, ///< Start of open subpath + NR_CURVETO, ///< Bezier curve segment + NR_LINETO, ///< Line segment + NR_END ///< End record +} NRPathcode; + + +#endif /* !SEEN_LIBNR_NR_PATH_CODE_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 : diff --git a/src/libnr/nr-path.cpp b/src/libnr/nr-path.cpp new file mode 100644 index 000000000..6ce2f5f99 --- /dev/null +++ b/src/libnr/nr-path.cpp @@ -0,0 +1,461 @@ +#define __NR_PATH_C__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * + * This code is in public domain + */ + +#include "n-art-bpath.h" +#include "nr-rect.h" +#include "nr-path.h" + +static void nr_curve_bbox (NR::Coord x000, NR::Coord y000, NR::Coord x001, NR::Coord y001, NR::Coord x011, NR::Coord y011, NR::Coord x111, NR::Coord y111, NRRect *bbox); + +static void nr_curve_bbox(NR::Point const p000, NR::Point const p001, + NR::Point const p011, NR::Point const p111, + NRRect *bbox); + +NRBPath *nr_path_duplicate_transform(NRBPath *d, NRBPath *s, NRMatrix const *transform) +{ + int i; + + if (!s->path) { + d->path = NULL; + return d; + } + + i = 0; + while (s->path[i].code != NR_END) i += 1; + + d->path = nr_new (NArtBpath, i + 1); + + i = 0; + while (s->path[i].code != NR_END) { + d->path[i].code = s->path[i].code; + if (s->path[i].code == NR_CURVETO) { + d->path[i].x1 = NR_MATRIX_DF_TRANSFORM_X (transform, s->path[i].x1, s->path[i].y1); + d->path[i].y1 = NR_MATRIX_DF_TRANSFORM_Y (transform, s->path[i].x1, s->path[i].y1); + d->path[i].x2 = NR_MATRIX_DF_TRANSFORM_X (transform, s->path[i].x2, s->path[i].y2); + d->path[i].y2 = NR_MATRIX_DF_TRANSFORM_Y (transform, s->path[i].x2, s->path[i].y2); + } + d->path[i].x3 = NR_MATRIX_DF_TRANSFORM_X (transform, s->path[i].x3, s->path[i].y3); + d->path[i].y3 = NR_MATRIX_DF_TRANSFORM_Y (transform, s->path[i].x3, s->path[i].y3); + i += 1; + } + d->path[i].code = NR_END; + + return d; +} + +NRBPath *nr_path_duplicate_transform(NRBPath *d, NRBPath *s, NR::Matrix const transform) { + NRMatrix tr = transform; + return nr_path_duplicate_transform(d, s, &tr); +} + +NArtBpath* nr_artpath_affine(NArtBpath *s, NR::Matrix const &aff) { + NRBPath bp, abp; + bp.path = s; + nr_path_duplicate_transform(&abp, &bp, aff); + return abp.path; +} + +static void +nr_line_wind_distance (NR::Coord x0, NR::Coord y0, NR::Coord x1, NR::Coord y1, NR::Point &pt, int *wind, NR::Coord *best) +{ + NR::Coord Ax, Ay, Bx, By, Dx, Dy, s; + NR::Coord dist2; + + /* Find distance */ + Ax = x0; + Ay = y0; + Bx = x1; + By = y1; + Dx = x1 - x0; + Dy = y1 - y0; + const NR::Coord Px = pt[NR::X]; + const NR::Coord Py = pt[NR::Y]; + + if (best) { + s = ((Px - Ax) * Dx + (Py - Ay) * Dy) / (Dx * Dx + Dy * Dy); + if (s <= 0.0) { + dist2 = (Px - Ax) * (Px - Ax) + (Py - Ay) * (Py - Ay); + } else if (s >= 1.0) { + dist2 = (Px - Bx) * (Px - Bx) + (Py - By) * (Py - By); + } else { + NR::Coord Qx, Qy; + Qx = Ax + s * Dx; + Qy = Ay + s * Dy; + dist2 = (Px - Qx) * (Px - Qx) + (Py - Qy) * (Py - Qy); + } + + if (dist2 < (*best * *best)) *best = sqrt (dist2); + } + + if (wind) { + /* Find wind */ + if ((Ax >= Px) && (Bx >= Px)) return; + if ((Ay >= Py) && (By >= Py)) return; + if ((Ay < Py) && (By < Py)) return; + if (Ay == By) return; + /* Ctach upper y bound */ + if (Ay == Py) { + if (Ax < Px) *wind -= 1; + return; + } else if (By == Py) { + if (Bx < Px) *wind += 1; + return; + } else { + NR::Coord Qx; + /* Have to calculate intersection */ + Qx = Ax + Dx * (Py - Ay) / Dy; + if (Qx < Px) { + *wind += (Dy > 0.0) ? 1 : -1; + } + } + } +} + +static void +nr_curve_bbox_wind_distance (NR::Coord x000, NR::Coord y000, + NR::Coord x001, NR::Coord y001, + NR::Coord x011, NR::Coord y011, + NR::Coord x111, NR::Coord y111, + NR::Point &pt, + NRRect *bbox, int *wind, NR::Coord *best, + NR::Coord tolerance) +{ + NR::Coord x0, y0, x1, y1, len2; + int needdist, needwind, needline; + + const NR::Coord Px = pt[NR::X]; + const NR::Coord Py = pt[NR::Y]; + + needdist = 0; + needwind = 0; + needline = 0; + + if (bbox) nr_curve_bbox (x000, y000, x001, y001, x011, y011, x111, y111, bbox); + + x0 = MIN (x000, x001); + x0 = MIN (x0, x011); + x0 = MIN (x0, x111); + y0 = MIN (y000, y001); + y0 = MIN (y0, y011); + y0 = MIN (y0, y111); + x1 = MAX (x000, x001); + x1 = MAX (x1, x011); + x1 = MAX (x1, x111); + y1 = MAX (y000, y001); + y1 = MAX (y1, y011); + y1 = MAX (y1, y111); + + if (best) { + /* Quicly adjust to endpoints */ + len2 = (x000 - Px) * (x000 - Px) + (y000 - Py) * (y000 - Py); + if (len2 < (*best * *best)) *best = (NR::Coord) sqrt (len2); + len2 = (x111 - Px) * (x111 - Px) + (y111 - Py) * (y111 - Py); + if (len2 < (*best * *best)) *best = (NR::Coord) sqrt (len2); + + if (((x0 - Px) < *best) && ((y0 - Py) < *best) && ((Px - x1) < *best) && ((Py - y1) < *best)) { + /* Point is inside sloppy bbox */ + /* Now we have to decide, whether subdivide */ + /* fixme: (Lauris) */ + if (((y1 - y0) > 5.0) || ((x1 - x0) > 5.0)) { + needdist = 1; + } else { + needline = 1; + } + } + } + if (!needdist && wind) { + if ((y1 >= Py) && (y0 < Py) && (x0 < Px)) { + /* Possible intersection at the left */ + /* Now we have to decide, whether subdivide */ + /* fixme: (Lauris) */ + if (((y1 - y0) > 5.0) || ((x1 - x0) > 5.0)) { + needwind = 1; + } else { + needline = 1; + } + } + } + + if (needdist || needwind) { + NR::Coord x00t, x0tt, xttt, x1tt, x11t, x01t; + NR::Coord y00t, y0tt, yttt, y1tt, y11t, y01t; + NR::Coord s, t; + + t = 0.5; + s = 1 - t; + + x00t = s * x000 + t * x001; + x01t = s * x001 + t * x011; + x11t = s * x011 + t * x111; + x0tt = s * x00t + t * x01t; + x1tt = s * x01t + t * x11t; + xttt = s * x0tt + t * x1tt; + + y00t = s * y000 + t * y001; + y01t = s * y001 + t * y011; + y11t = s * y011 + t * y111; + y0tt = s * y00t + t * y01t; + y1tt = s * y01t + t * y11t; + yttt = s * y0tt + t * y1tt; + + nr_curve_bbox_wind_distance (x000, y000, x00t, y00t, x0tt, y0tt, xttt, yttt, pt, NULL, wind, best, tolerance); + nr_curve_bbox_wind_distance (xttt, yttt, x1tt, y1tt, x11t, y11t, x111, y111, pt, NULL, wind, best, tolerance); + } else if (1 || needline) { + nr_line_wind_distance (x000, y000, x111, y111, pt, wind, best); + } +} + +void +nr_path_matrix_point_bbox_wind_distance (NRBPath *bpath, NR::Matrix const &m, NR::Point &pt, + NRRect *bbox, int *wind, NR::Coord *dist, + NR::Coord tolerance) +{ + NR::Coord x0, y0, x3, y3; + const NArtBpath *p; + + if (!bpath->path) { + if (wind) *wind = 0; + if (dist) *dist = NR_HUGE; + return; + } + + x0 = y0 = 0.0; + x3 = y3 = 0.0; + + for (p = bpath->path; p->code != NR_END; p+= 1) { + switch (p->code) { + case NR_MOVETO_OPEN: + case NR_MOVETO: + x0 = m[0] * p->x3 + m[2] * p->y3 + m[4]; + y0 = m[1] * p->x3 + m[3] * p->y3 + m[5]; + if (bbox) { + bbox->x0 = (NR::Coord) MIN (bbox->x0, x0); + bbox->y0 = (NR::Coord) MIN (bbox->y0, y0); + bbox->x1 = (NR::Coord) MAX (bbox->x1, x0); + bbox->y1 = (NR::Coord) MAX (bbox->y1, y0); + } + break; + case NR_LINETO: + x3 = m[0] * p->x3 + m[2] * p->y3 + m[4]; + y3 = m[1] * p->x3 + m[3] * p->y3 + m[5]; + if (bbox) { + bbox->x0 = (NR::Coord) MIN (bbox->x0, x3); + bbox->y0 = (NR::Coord) MIN (bbox->y0, y3); + bbox->x1 = (NR::Coord) MAX (bbox->x1, x3); + bbox->y1 = (NR::Coord) MAX (bbox->y1, y3); + } + if (dist || wind) { + nr_line_wind_distance (x0, y0, x3, y3, pt, wind, dist); + } + x0 = x3; + y0 = y3; + break; + case NR_CURVETO: + x3 = m[0] * p->x3 + m[2] * p->y3 + m[4]; + y3 = m[1] * p->x3 + m[3] * p->y3 + m[5]; + nr_curve_bbox_wind_distance (x0, y0, + m[0] * p->x1 + m[2] * p->y1 + m[4], + m[1] * p->x1 + m[3] * p->y1 + m[5], + m[0] * p->x2 + m[2] * p->y2 + m[4], + m[1] * p->x2 + m[3] * p->y2 + m[5], + x3, y3, + pt, + bbox, wind, dist, tolerance); + x0 = x3; + y0 = y3; + break; + default: + break; + } + } +} + +static void +nr_curve_bbox(NR::Point const p000, NR::Point const p001, + NR::Point const p011, NR::Point const p111, + NRRect *bbox) +{ + using NR::X; + using NR::Y; + + nr_curve_bbox(p000[X], p000[Y], + p001[X], p001[Y], + p011[X], p011[Y], + p111[X], p111[Y], + bbox); +} + +/* Fast bbox calculation */ +/* Thanks to Nathan Hurst for suggesting it */ + +static void +nr_curve_bbox (NR::Coord x000, NR::Coord y000, NR::Coord x001, NR::Coord y001, NR::Coord x011, NR::Coord y011, NR::Coord x111, NR::Coord y111, NRRect *bbox) +{ + NR::Coord a, b, c, D; + + bbox->x0 = (NR::Coord) MIN (bbox->x0, x111); + bbox->y0 = (NR::Coord) MIN (bbox->y0, y111); + bbox->x1 = (NR::Coord) MAX (bbox->x1, x111); + bbox->y1 = (NR::Coord) MAX (bbox->y1, y111); + + /* + * xttt = s * (s * (s * x000 + t * x001) + t * (s * x001 + t * x011)) + t * (s * (s * x001 + t * x011) + t * (s * x011 + t * x111)) + * xttt = s * (s2 * x000 + s * t * x001 + t * s * x001 + t2 * x011) + t * (s2 * x001 + s * t * x011 + t * s * x011 + t2 * x111) + * xttt = s * (s2 * x000 + 2 * st * x001 + t2 * x011) + t * (s2 * x001 + 2 * st * x011 + t2 * x111) + * xttt = s3 * x000 + 2 * s2t * x001 + st2 * x011 + s2t * x001 + 2st2 * x011 + t3 * x111 + * xttt = s3 * x000 + 3s2t * x001 + 3st2 * x011 + t3 * x111 + * xttt = s3 * x000 + (1 - s) 3s2 * x001 + (1 - s) * (1 - s) * 3s * x011 + (1 - s) * (1 - s) * (1 - s) * x111 + * xttt = s3 * x000 + (3s2 - 3s3) * x001 + (3s - 6s2 + 3s3) * x011 + (1 - 2s + s2 - s + 2s2 - s3) * x111 + * xttt = (x000 - 3 * x001 + 3 * x011 - x111) * s3 + + * ( 3 * x001 - 6 * x011 + 3 * x111) * s2 + + * ( 3 * x011 - 3 * x111) * s + + * ( x111) + * xttt' = (3 * x000 - 9 * x001 + 9 * x011 - 3 * x111) * s2 + + * ( 6 * x001 - 12 * x011 + 6 * x111) * s + + * ( 3 * x011 - 3 * x111) + */ + + a = 3 * x000 - 9 * x001 + 9 * x011 - 3 * x111; + b = 6 * x001 - 12 * x011 + 6 * x111; + c = 3 * x011 - 3 * x111; + + /* + * s = (-b +/- sqrt (b * b - 4 * a * c)) / 2 * a; + */ + if (fabs (a) < NR_EPSILON) { + /* s = -c / b */ + if (fabs (b) > NR_EPSILON) { + double s, t, xttt; + s = -c / b; + if ((s > 0.0) && (s < 1.0)) { + t = 1.0 - s; + xttt = s * s * s * x000 + 3 * s * s * t * x001 + 3 * s * t * t * x011 + t * t * t * x111; + bbox->x0 = (float) MIN (bbox->x0, xttt); + bbox->x1 = (float) MAX (bbox->x1, xttt); + } + } + } else { + /* s = (-b +/- sqrt (b * b - 4 * a * c)) / 2 * a; */ + D = b * b - 4 * a * c; + if (D >= 0.0) { + NR::Coord d, s, t, xttt; + /* Have solution */ + d = sqrt (D); + s = (-b + d) / (2 * a); + if ((s > 0.0) && (s < 1.0)) { + t = 1.0 - s; + xttt = s * s * s * x000 + 3 * s * s * t * x001 + 3 * s * t * t * x011 + t * t * t * x111; + bbox->x0 = (NR::Coord) MIN (bbox->x0, xttt); + bbox->x1 = (NR::Coord) MAX (bbox->x1, xttt); + } + s = (-b - d) / (2 * a); + if ((s > 0.0) && (s < 1.0)) { + t = 1.0 - s; + xttt = s * s * s * x000 + 3 * s * s * t * x001 + 3 * s * t * t * x011 + t * t * t * x111; + bbox->x0 = (NR::Coord) MIN (bbox->x0, xttt); + bbox->x1 = (NR::Coord) MAX (bbox->x1, xttt); + } + } + } + + a = 3 * y000 - 9 * y001 + 9 * y011 - 3 * y111; + b = 6 * y001 - 12 * y011 + 6 * y111; + c = 3 * y011 - 3 * y111; + + if (fabs (a) < NR_EPSILON) { + /* s = -c / b */ + if (fabs (b) > NR_EPSILON) { + double s, t, yttt; + s = -c / b; + if ((s > 0.0) && (s < 1.0)) { + t = 1.0 - s; + yttt = s * s * s * y000 + 3 * s * s * t * y001 + 3 * s * t * t * y011 + t * t * t * y111; + bbox->y0 = (float) MIN (bbox->y0, yttt); + bbox->y1 = (float) MAX (bbox->y1, yttt); + } + } + } else { + /* s = (-b +/- sqrt (b * b - 4 * a * c)) / 2 * a; */ + D = b * b - 4 * a * c; + if (D >= 0.0) { + NR::Coord d, s, t, yttt; + /* Have solution */ + d = sqrt (D); + s = (-b + d) / (2 * a); + if ((s > 0.0) && (s < 1.0)) { + t = 1.0 - s; + yttt = s * s * s * y000 + 3 * s * s * t * y001 + 3 * s * t * t * y011 + t * t * t * y111; + bbox->y0 = (NR::Coord) MIN (bbox->y0, yttt); + bbox->y1 = (NR::Coord) MAX (bbox->y1, yttt); + } + s = (-b - d) / (2 * a); + if ((s > 0.0) && (s < 1.0)) { + t = 1.0 - s; + yttt = s * s * s * y000 + 3 * s * s * t * y001 + 3 * s * t * t * y011 + t * t * t * y111; + bbox->y0 = (NR::Coord) MIN (bbox->y0, yttt); + bbox->y1 = (NR::Coord) MAX (bbox->y1, yttt); + } + } + } +} + +void +nr_path_matrix_bbox_union(NRBPath const *bpath, NR::Matrix const &m, + NRRect *bbox) +{ + using NR::X; + using NR::Y; + + if (!bpath->path) { + return; + } + + NR::Point prev0(0, 0); + for (NArtBpath const *p = bpath->path; p->code != NR_END; ++p) { + switch (p->code) { + case NR_MOVETO_OPEN: + case NR_MOVETO: { + NR::Point const c3(p->x3, + p->y3); + prev0 = c3 * m; + nr_rect_union_pt(bbox, prev0); + break; + } + + case NR_LINETO: { + NR::Point const c3(p->x3, + p->y3); + NR::Point const endPt( c3 * m ); + nr_rect_union_pt(bbox, endPt); + prev0 = endPt; + break; + } + + case NR_CURVETO: { + NR::Point const c1(p->x1, p->y1); + NR::Point const c2(p->x2, p->y2); + NR::Point const c3(p->x3, p->y3); + NR::Point const endPt( c3 * m ); + nr_curve_bbox(prev0, + c1 * m, + c2 * m, + endPt, + bbox); + prev0 = endPt; + break; + } + + default: + break; + } + } +} + diff --git a/src/libnr/nr-path.h b/src/libnr/nr-path.h new file mode 100644 index 000000000..625a0e498 --- /dev/null +++ b/src/libnr/nr-path.h @@ -0,0 +1,62 @@ +#ifndef __NR_PATH_H__ +#define __NR_PATH_H__ + +/* + * NArtBpath: Curve component. Adapted from libart. + */ + +/* + * libart_lgpl/art_bpath.h copyright information: + * + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#include +#include + +NArtBpath* nr_artpath_affine(NArtBpath *s, NR::Matrix const &transform); + +//ArtBpath* nr_artpath_to_art_bpath(NArtBpath *s); // this lives in src/extension/internal/gnome.cpp to avoid requiring libart everywhere + +struct NRBPath { + NArtBpath *path; +}; + +NRBPath *nr_path_duplicate_transform(NRBPath *d, NRBPath *s, NRMatrix const *transform); + +NRBPath *nr_path_duplicate_transform(NRBPath *d, NRBPath *s, NR::Matrix const transform); + +void nr_path_matrix_point_bbox_wind_distance (NRBPath *bpath, NR::Matrix const &m, NR::Point &pt, + NRRect *bbox, int *wind, NR::Coord *dist, + NR::Coord tolerance); + +void nr_path_matrix_bbox_union(NRBPath const *bpath, NR::Matrix const &m, NRRect *bbox); + +#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 : diff --git a/src/libnr/nr-pixblock-line.cpp b/src/libnr/nr-pixblock-line.cpp new file mode 100644 index 000000000..5e025c7eb --- /dev/null +++ b/src/libnr/nr-pixblock-line.cpp @@ -0,0 +1,92 @@ +#define __NR_PIXBLOCK_LINE_C__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * + * This code is in public domain + */ + +#include +#include + +void +nr_pixblock_draw_line_rgba32 (NRPixBlock *d, long x0, long y0, long x1, long y1, short first, unsigned long rgba) +{ + long deltax, deltay, xinc1, xinc2, yinc1, yinc2; + long den, num, numadd, numpixels; + long x, y, curpixel; + /* Pixblock */ + int dbpp; + NRPixBlock spb; + unsigned char *spx; + + if (x1 >= x0) { + deltax = x1 - x0; + xinc1 = 1; + xinc2 = 1; + } else { + deltax = x0 - x1; + xinc1 = -1; + xinc2 = -1; + } + + if (y1 >= y0) { + deltay = y1 - y0; + yinc1 = 1; + yinc2 = 1; + } else { + deltay = y0 - y1; + yinc1 = -1; + yinc2 = -1; + } + + if (deltax >= deltay) { + xinc1 = 0; + yinc2 = 0; + den = deltax; + num = deltax / 2; + numadd = deltay; + numpixels = deltax; + } else { + xinc2 = 0; + yinc1 = 0; + den = deltay; + num = deltay / 2; + numadd = deltax; + numpixels = deltay; + } + + /* We can be quite sure 1x1 pixblock is TINY */ + nr_pixblock_setup_fast (&spb, NR_PIXBLOCK_MODE_R8G8B8A8N, 0, 0, 1, 1, 0); + spb.empty = 0; + spx = NR_PIXBLOCK_PX (&spb); + spx[0] = NR_RGBA32_R (rgba); + spx[1] = NR_RGBA32_G (rgba); + spx[2] = NR_RGBA32_B (rgba); + spx[3] = NR_RGBA32_A (rgba); + + dbpp = NR_PIXBLOCK_BPP (d); + + x = x0; + y = y0; + + for (curpixel = 0; curpixel <= numpixels; curpixel++) { + if ((x >= d->area.x0) && (y >= d->area.y0) && (x < d->area.x1) && (y < d->area.y1)) { + nr_compose_pixblock_pixblock_pixel (d, NR_PIXBLOCK_PX (d) + (y - d->area.y0) * d->rs + (x - d->area.x0) * dbpp, &spb, spx); + } + num += numadd; + if (num >= den) { + num -= den; + x += xinc1; + y += yinc1; + } + x += xinc2; + y += yinc2; + } + + nr_pixblock_release (&spb); +} + diff --git a/src/libnr/nr-pixblock-line.h b/src/libnr/nr-pixblock-line.h new file mode 100644 index 000000000..7fd58a0ab --- /dev/null +++ b/src/libnr/nr-pixblock-line.h @@ -0,0 +1,28 @@ +#ifndef __NR_PIXBLOCK_LINE_H__ +#define __NR_PIXBLOCK_LINE_H__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * + * This code is in public domain + */ + +#include + +void nr_pixblock_draw_line_rgba32 (NRPixBlock *d, long x0, long y0, long x1, long y1, short first, unsigned long rgba); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-pixblock-pattern.cpp b/src/libnr/nr-pixblock-pattern.cpp new file mode 100644 index 000000000..03bd688ba --- /dev/null +++ b/src/libnr/nr-pixblock-pattern.cpp @@ -0,0 +1,126 @@ +#define __NR_PIXBLOCK_PATTERN_C__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * + * This code is in public domain + */ + + +#include "nr-pixops.h" +#include "nr-pixblock-pattern.h" + +#define NR_NOISE_SIZE 1024 + +void +nr_pixblock_render_gray_noise (NRPixBlock *pb, NRPixBlock *mask) +{ + static unsigned char *noise = NULL; + static unsigned int seed = 0; + unsigned int v; + NRRectL clip; + int x, y, bpp; + + if (mask) { + if (mask->empty) return; + nr_rect_l_intersect (&clip, &pb->area, &mask->area); + if (nr_rect_l_test_empty (&clip)) return; + } else { + clip = pb->area; + } + + if (!noise) { + int i; + noise = nr_new (unsigned char, NR_NOISE_SIZE); + for (i = 0; i < NR_NOISE_SIZE; i++) noise[i] = (rand () / (RAND_MAX >> 8)) & 0xff; + } + + bpp = NR_PIXBLOCK_BPP (pb); + + v = (rand () / (RAND_MAX >> 8)) & 0xff; + + if (mask) { + for (y = clip.y0; y < clip.y1; y++) { + unsigned char *d, *m; + d = NR_PIXBLOCK_PX (pb) + (y - pb->area.y0) * pb->rs + (clip.x0 - pb->area.x0) * bpp; + m = NR_PIXBLOCK_PX (mask) + (y - mask->area.y0) * pb->rs + (clip.x0 - mask->area.x0); + for (x = clip.x0; x < clip.x1; x++) { + v = v ^ noise[seed]; + switch (pb->mode) { + case NR_PIXBLOCK_MODE_A8: + d[0] = (65025 - (255 - m[0]) * (255 - d[0]) + 127) / 255; + break; + case NR_PIXBLOCK_MODE_R8G8B8: + d[0] = NR_COMPOSEN11 (v, m[0], d[0]); + d[1] = NR_COMPOSEN11 (v, m[0], d[1]); + d[2] = NR_COMPOSEN11 (v, m[0], d[2]); + break; + case NR_PIXBLOCK_MODE_R8G8B8A8N: + if (m[0] != 0) { + unsigned int ca; + ca = NR_A7 (m[0], d[3]); + d[0] = NR_COMPOSENNN_A7 (v, m[0], d[0], d[3], ca); + d[1] = NR_COMPOSENNN_A7 (v, m[0], d[1], d[3], ca); + d[2] = NR_COMPOSENNN_A7 (v, m[0], d[2], d[3], ca); + d[3] = (ca + 127) / 255; + } + break; + case NR_PIXBLOCK_MODE_R8G8B8A8P: + d[0] = NR_COMPOSENPP (v, m[0], d[0], d[3]); + d[1] = NR_COMPOSENPP (v, m[0], d[1], d[3]); + d[2] = NR_COMPOSENPP (v, m[0], d[2], d[3]); + d[3] = (NR_A7 (d[3], m[0]) + 127) / 255; + break; + default: + break; + } + d += bpp; + m += 1; + if (++seed >= NR_NOISE_SIZE) { + int i; + i = (rand () / (RAND_MAX / NR_NOISE_SIZE)) % NR_NOISE_SIZE; + noise[i] ^= v; + seed = i % (NR_NOISE_SIZE >> 2); + } + } + } + } else { + for (y = clip.y0; y < clip.y1; y++) { + unsigned char *d; + d = NR_PIXBLOCK_PX (pb) + (y - pb->area.y0) * pb->rs + (clip.x0 - pb->area.x0) * bpp; + for (x = clip.x0; x < clip.x1; x++) { + v = v ^ noise[seed]; + switch (pb->mode) { + case NR_PIXBLOCK_MODE_A8: + d[0] = 255; + break; + case NR_PIXBLOCK_MODE_R8G8B8: + d[0] = v; + d[1] = v; + d[2] = v; + break; + case NR_PIXBLOCK_MODE_R8G8B8A8N: + case NR_PIXBLOCK_MODE_R8G8B8A8P: + d[0] = v; + d[1] = v; + d[2] = v; + d[3] = 255; + default: + break; + } + d += bpp; + if (++seed >= NR_NOISE_SIZE) { + int i; + i = (rand () / (RAND_MAX / NR_NOISE_SIZE)) % NR_NOISE_SIZE; + noise[i] ^= v; + seed = i % (NR_NOISE_SIZE >> 2); + } + } + } + } + + pb->empty = 0; +} diff --git a/src/libnr/nr-pixblock-pattern.h b/src/libnr/nr-pixblock-pattern.h new file mode 100644 index 000000000..463a24379 --- /dev/null +++ b/src/libnr/nr-pixblock-pattern.h @@ -0,0 +1,28 @@ +#ifndef __NR_PIXBLOCK_PATTERN_H__ +#define __NR_PIXBLOCK_PATTERN_H__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * + * This code is in public domain + */ + +#include + +void nr_pixblock_render_gray_noise (NRPixBlock *pb, NRPixBlock *mask); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-pixblock-pixel.cpp b/src/libnr/nr-pixblock-pixel.cpp new file mode 100644 index 000000000..c778c0c7f --- /dev/null +++ b/src/libnr/nr-pixblock-pixel.cpp @@ -0,0 +1,230 @@ +#define __NR_PIXBLOCK_PIXEL_C__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * + * This code is in public domain + */ + +#include "nr-pixops.h" +#include "nr-pixblock-pixel.h" + +void +nr_compose_pixblock_pixblock_pixel (NRPixBlock *dpb, unsigned char *d, const NRPixBlock *spb, const unsigned char *s) +{ + if (spb->empty) return; + + if (dpb->empty) { + /* Empty destination */ + switch (dpb->mode) { + case NR_PIXBLOCK_MODE_A8: + switch (spb->mode) { + case NR_PIXBLOCK_MODE_A8: + break; + case NR_PIXBLOCK_MODE_R8G8B8: + d[0] = 255; + break; + case NR_PIXBLOCK_MODE_R8G8B8A8N: + d[0] = s[3]; + break; + case NR_PIXBLOCK_MODE_R8G8B8A8P: + d[0] = s[3]; + break; + default: + break; + } + break; + case NR_PIXBLOCK_MODE_R8G8B8: + switch (spb->mode) { + case NR_PIXBLOCK_MODE_A8: + break; + case NR_PIXBLOCK_MODE_R8G8B8: + d[0] = s[0]; + d[1] = s[1]; + d[2] = s[2]; + break; + case NR_PIXBLOCK_MODE_R8G8B8A8N: + d[0] = NR_COMPOSEN11 (s[0], s[3], 255); + d[1] = NR_COMPOSEN11 (s[1], s[3], 255); + d[2] = NR_COMPOSEN11 (s[2], s[3], 255); + break; + case NR_PIXBLOCK_MODE_R8G8B8A8P: + d[0] = NR_COMPOSEP11 (s[0], s[3], 255); + d[1] = NR_COMPOSEP11 (s[1], s[3], 255); + d[2] = NR_COMPOSEP11 (s[2], s[3], 255); + break; + default: + break; + } + break; + case NR_PIXBLOCK_MODE_R8G8B8A8N: + switch (spb->mode) { + case NR_PIXBLOCK_MODE_A8: + break; + case NR_PIXBLOCK_MODE_R8G8B8: + d[0] = s[0]; + d[1] = s[1]; + d[2] = s[2]; + d[3] = 255; + break; + case NR_PIXBLOCK_MODE_R8G8B8A8N: + d[0] = s[0]; + d[1] = s[1]; + d[2] = s[2]; + d[3] = s[3]; + break; + case NR_PIXBLOCK_MODE_R8G8B8A8P: + if (s[3] == 0) { + d[0] = 255; + d[1] = 255; + d[2] = 255; + } else { + d[0] = (s[0] * 255) / s[3]; + d[1] = (s[1] * 255) / s[3]; + d[2] = (s[2] * 255) / s[3]; + } + d[3] = s[3]; + break; + default: + break; + } + break; + case NR_PIXBLOCK_MODE_R8G8B8A8P: + switch (spb->mode) { + case NR_PIXBLOCK_MODE_A8: + break; + case NR_PIXBLOCK_MODE_R8G8B8: + d[0] = s[0]; + d[1] = s[1]; + d[2] = s[2]; + d[3] = 255; + break; + case NR_PIXBLOCK_MODE_R8G8B8A8N: + d[0] = NR_PREMUL (s[0], s[3]); + d[1] = NR_PREMUL (s[1], s[3]); + d[2] = NR_PREMUL (s[2], s[3]); + d[3] = s[3]; + break; + case NR_PIXBLOCK_MODE_R8G8B8A8P: + d[0] = s[0]; + d[1] = s[1]; + d[2] = s[2]; + d[3] = s[3]; + break; + default: + break; + } + break; + default: + break; + } + } else { + /* Image destination */ + switch (dpb->mode) { + case NR_PIXBLOCK_MODE_A8: + switch (spb->mode) { + case NR_PIXBLOCK_MODE_A8: + break; + case NR_PIXBLOCK_MODE_R8G8B8: + d[0] = 255; + break; + case NR_PIXBLOCK_MODE_R8G8B8A8N: + d[0] = NR_A7_NORMALIZED(s[3],d[0]); + break; + case NR_PIXBLOCK_MODE_R8G8B8A8P: + d[0] = NR_A7_NORMALIZED(s[3],d[0]); + break; + default: + break; + } + break; + case NR_PIXBLOCK_MODE_R8G8B8: + switch (spb->mode) { + case NR_PIXBLOCK_MODE_A8: + break; + case NR_PIXBLOCK_MODE_R8G8B8: + d[0] = s[0]; + d[1] = s[1]; + d[2] = s[2]; + break; + case NR_PIXBLOCK_MODE_R8G8B8A8N: + d[0] = NR_COMPOSEN11 (s[0], s[3], d[0]); + d[1] = NR_COMPOSEN11 (s[1], s[3], d[1]); + d[2] = NR_COMPOSEN11 (s[2], s[3], d[2]); + break; + case NR_PIXBLOCK_MODE_R8G8B8A8P: + d[0] = NR_COMPOSEP11 (s[0], s[3], d[0]); + d[1] = NR_COMPOSEP11 (s[1], s[3], d[1]); + d[2] = NR_COMPOSEP11 (s[2], s[3], d[2]); + break; + default: + break; + } + break; + case NR_PIXBLOCK_MODE_R8G8B8A8N: + switch (spb->mode) { + case NR_PIXBLOCK_MODE_A8: + break; + case NR_PIXBLOCK_MODE_R8G8B8: + d[0] = s[0]; + d[1] = s[1]; + d[2] = s[2]; + break; + case NR_PIXBLOCK_MODE_R8G8B8A8N: + if (s[3] != 0) { + unsigned int ca; + ca = NR_A7 (s[3], d[3]); + d[0] = NR_COMPOSENNN_A7 (s[0], s[3], d[0], d[3], ca); + d[1] = NR_COMPOSENNN_A7 (s[1], s[3], d[1], d[3], ca); + d[2] = NR_COMPOSENNN_A7 (s[2], s[3], d[2], d[3], ca); + d[3] = (ca + 127) / 255; + } + break; + case NR_PIXBLOCK_MODE_R8G8B8A8P: + if (s[3] != 0) { + unsigned int ca; + ca = NR_A7 (s[3], d[3]); + d[0] = NR_COMPOSEPNN_A7 (s[0], s[3], d[0], d[3], ca); + d[1] = NR_COMPOSEPNN_A7 (s[1], s[3], d[0], d[3], ca); + d[2] = NR_COMPOSEPNN_A7 (s[2], s[3], d[0], d[3], ca); + d[3] = (ca + 127) / 255; + } + break; + default: + break; + } + break; + case NR_PIXBLOCK_MODE_R8G8B8A8P: + switch (spb->mode) { + case NR_PIXBLOCK_MODE_A8: + break; + case NR_PIXBLOCK_MODE_R8G8B8: + d[0] = s[0]; + d[1] = s[1]; + d[2] = s[2]; + break; + case NR_PIXBLOCK_MODE_R8G8B8A8N: + d[0] = NR_COMPOSENPP (s[0], s[3], d[0], d[3]); + d[1] = NR_COMPOSENPP (s[1], s[3], d[1], d[3]); + d[2] = NR_COMPOSENPP (s[2], s[3], d[2], d[3]); + d[3] = NR_A7_NORMALIZED(s[3],d[3]); + break; + case NR_PIXBLOCK_MODE_R8G8B8A8P: + d[0] = NR_COMPOSEPPP (s[0], s[3], d[0], d[3]); + d[1] = NR_COMPOSEPPP (s[1], s[3], d[1], d[3]); + d[2] = NR_COMPOSEPPP (s[2], s[3], d[2], d[3]); + d[3] = NR_A7_NORMALIZED(s[3],d[3]); + break; + default: + break; + } + break; + default: + break; + } + } +} + diff --git a/src/libnr/nr-pixblock-pixel.h b/src/libnr/nr-pixblock-pixel.h new file mode 100644 index 000000000..d989f53cf --- /dev/null +++ b/src/libnr/nr-pixblock-pixel.h @@ -0,0 +1,28 @@ +#ifndef __NR_PIXBLOCK_PIXEL_H__ +#define __NR_PIXBLOCK_PIXEL_H__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * + * This code is in public domain + */ + +#include + +void nr_compose_pixblock_pixblock_pixel (NRPixBlock *dpb, unsigned char *d, const NRPixBlock *spb, const unsigned char *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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-pixblock.cpp b/src/libnr/nr-pixblock.cpp new file mode 100644 index 000000000..9b1ff2752 --- /dev/null +++ b/src/libnr/nr-pixblock.cpp @@ -0,0 +1,411 @@ +#define __NR_PIXBLOCK_C__ + +/** \file + * \brief Allocation/Setup of NRPixBlock objects. Pixel store functions. + * + * Authors: + * (C) 1999-2002 Lauris Kaplinski + * + * This code is in the Public Domain + */ + +#include "nr-pixblock.h" + +/// Size of buffer that needs no allocation (default 4). +#define NR_TINY_MAX sizeof (unsigned char *) + +/** + * Pixbuf initialisation using homegrown memory handling ("pixelstore"). + * + * Pixbuf sizes are differentiated into tiny, <4K, <16K, <64K, and more, + * with each type having its own method of memory handling. After allocating + * memory, the buffer is cleared if the clear flag is set. Intended to + * reduce memory fragmentation. + * \param pb Pointer to the pixbuf struct. + * \param mode Indicates grayscale/RGB/RGBA. + * \param clear True if buffer should be cleared. + * \pre x1>=x0 && y1>=y0 && pb!=NULL + */ +void +nr_pixblock_setup_fast (NRPixBlock *pb, NR_PIXBLOCK_MODE mode, int x0, int y0, int x1, int y1, bool clear) +{ + int w, h, bpp; + size_t size; + + w = x1 - x0; + h = y1 - y0; + bpp = (mode == NR_PIXBLOCK_MODE_A8) ? 1 : (mode == NR_PIXBLOCK_MODE_R8G8B8) ? 3 : 4; + + size = bpp * w * h; + + if (size <= NR_TINY_MAX) { + pb->size = NR_PIXBLOCK_SIZE_TINY; + if (clear) memset (pb->data.p, 0x0, size); + } else if (size <= 4096) { + pb->size = NR_PIXBLOCK_SIZE_4K; + pb->data.px = nr_pixelstore_4K_new (clear, 0x0); + } else if (size <= 16384) { + pb->size = NR_PIXBLOCK_SIZE_16K; + pb->data.px = nr_pixelstore_16K_new (clear, 0x0); + } else if (size <= 65536) { + pb->size = NR_PIXBLOCK_SIZE_64K; + pb->data.px = nr_pixelstore_64K_new (clear, 0x0); + } else if (size <= 262144) { + pb->size = NR_PIXBLOCK_SIZE_256K; + pb->data.px = nr_pixelstore_256K_new (clear, 0x0); + } else if (size <= 1048576) { + pb->size = NR_PIXBLOCK_SIZE_1M; + pb->data.px = nr_pixelstore_1M_new (clear, 0x0); + } else { + pb->size = NR_PIXBLOCK_SIZE_BIG; + pb->data.px = nr_new (unsigned char, size); + if (clear) memset (pb->data.px, 0x0, size); + } + + pb->mode = mode; + pb->empty = 1; + pb->area.x0 = x0; + pb->area.y0 = y0; + pb->area.x1 = x1; + pb->area.y1 = y1; + pb->rs = bpp * w; +} + +/** + * Pixbuf initialisation using nr_new. + * + * After allocating memory, the buffer is cleared if the clear flag is set. + * \param pb Pointer to the pixbuf struct. + * \param mode Indicates grayscale/RGB/RGBA. + * \param clear True if buffer should be cleared. + * \pre x1>=x0 && y1>=y0 && pb!=NULL + */ +void +nr_pixblock_setup (NRPixBlock *pb, NR_PIXBLOCK_MODE mode, int x0, int y0, int x1, int y1, bool clear) +{ + int w, h, bpp; + size_t size; + + w = x1 - x0; + h = y1 - y0; + bpp = (mode == NR_PIXBLOCK_MODE_A8) ? 1 : (mode == NR_PIXBLOCK_MODE_R8G8B8) ? 3 : 4; + + size = bpp * w * h; + + if (size <= NR_TINY_MAX) { + pb->size = NR_PIXBLOCK_SIZE_TINY; + if (clear) memset (pb->data.p, 0x0, size); + } else { + pb->size = NR_PIXBLOCK_SIZE_BIG; + pb->data.px = nr_new (unsigned char, size); + if (clear) memset (pb->data.px, 0x0, size); + } + + pb->mode = mode; + pb->empty = 1; + pb->area.x0 = x0; + pb->area.y0 = y0; + pb->area.x1 = x1; + pb->area.y1 = y1; + pb->rs = bpp * w; +} + +/** + * Pixbuf initialisation with preset values. + * + * After copying all parameters into the NRPixBlock struct, the pixel buffer is cleared if the clear flag is set. + * \param pb Pointer to the pixbuf struct. + * \param mode Indicates grayscale/RGB/RGBA. + * \param clear True if buffer should be cleared. + * \pre x1>=x0 && y1>=y0 && pb!=NULL + */ +void +nr_pixblock_setup_extern (NRPixBlock *pb, NR_PIXBLOCK_MODE mode, int x0, int y0, int x1, int y1, unsigned char *px, int rs, bool empty, bool clear) +{ + int w, bpp; + + w = x1 - x0; + bpp = (mode == NR_PIXBLOCK_MODE_A8) ? 1 : (mode == NR_PIXBLOCK_MODE_R8G8B8) ? 3 : 4; + + pb->size = NR_PIXBLOCK_SIZE_STATIC; + pb->mode = mode; + pb->empty = empty; + pb->area.x0 = x0; + pb->area.y0 = y0; + pb->area.x1 = x1; + pb->area.y1 = y1; + pb->data.px = px; + pb->rs = rs; + + g_assert (pb->data.px != NULL); + if (clear) { + if (rs == bpp * w) { + /// \todo How do you recognise if + /// px was an uncleared tiny buffer? + if (pb->data.px) + memset (pb->data.px, 0x0, bpp * (y1 - y0) * w); + } else { + int y; + for (y = y0; y < y1; y++) { + memset (pb->data.px + (y - y0) * rs, 0x0, bpp * w); + } + } + } +} + +/** + * Frees memory taken by pixel data in NRPixBlock. + * \param pb Pointer to pixblock. + * \pre pb and pb->data.px point to valid addresses. + * + * According to pb->size, one of the functions for freeing the pixelstore + * is called. May be called regardless of how pixbuf was set up. + */ +void +nr_pixblock_release (NRPixBlock *pb) +{ + switch (pb->size) { + case NR_PIXBLOCK_SIZE_TINY: + break; + case NR_PIXBLOCK_SIZE_4K: + nr_pixelstore_4K_free (pb->data.px); + break; + case NR_PIXBLOCK_SIZE_16K: + nr_pixelstore_16K_free (pb->data.px); + break; + case NR_PIXBLOCK_SIZE_64K: + nr_pixelstore_64K_free (pb->data.px); + break; + case NR_PIXBLOCK_SIZE_256K: + nr_pixelstore_256K_free (pb->data.px); + break; + case NR_PIXBLOCK_SIZE_1M: + nr_pixelstore_1M_free (pb->data.px); + break; + case NR_PIXBLOCK_SIZE_BIG: + nr_free (pb->data.px); + break; + case NR_PIXBLOCK_SIZE_STATIC: + break; + default: + break; + } +} + +/** + * Allocates NRPixBlock and sets it up. + * + * \return Pointer to fresh pixblock. + * Calls nr_new() and nr_pixblock_setup(). + */ +NRPixBlock * +nr_pixblock_new (NR_PIXBLOCK_MODE mode, int x0, int y0, int x1, int y1, bool clear) +{ + NRPixBlock *pb; + + pb = nr_new (NRPixBlock, 1); + + nr_pixblock_setup (pb, mode, x0, y0, x1, y1, clear); + + return pb; +} + +/** + * Frees all memory taken by pixblock. + * + * \return NULL + */ +NRPixBlock * +nr_pixblock_free (NRPixBlock *pb) +{ + nr_pixblock_release (pb); + + nr_free (pb); + + return NULL; +} + +/* PixelStore operations */ + +#define NR_4K_BLOCK 32 +static unsigned char **nr_4K_px = NULL; +static unsigned int nr_4K_len = 0; +static unsigned int nr_4K_size = 0; + +unsigned char * +nr_pixelstore_4K_new (bool clear, unsigned char val) +{ + unsigned char *px; + + if (nr_4K_len != 0) { + nr_4K_len -= 1; + px = nr_4K_px[nr_4K_len]; + } else { + px = nr_new (unsigned char, 4096); + } + + if (clear) memset (px, val, 4096); + + return px; +} + +void +nr_pixelstore_4K_free (unsigned char *px) +{ + if (nr_4K_len == nr_4K_size) { + nr_4K_size += NR_4K_BLOCK; + nr_4K_px = nr_renew (nr_4K_px, unsigned char *, nr_4K_size); + } + + nr_4K_px[nr_4K_len] = px; + nr_4K_len += 1; +} + +#define NR_16K_BLOCK 32 +static unsigned char **nr_16K_px = NULL; +static unsigned int nr_16K_len = 0; +static unsigned int nr_16K_size = 0; + +unsigned char * +nr_pixelstore_16K_new (bool clear, unsigned char val) +{ + unsigned char *px; + + if (nr_16K_len != 0) { + nr_16K_len -= 1; + px = nr_16K_px[nr_16K_len]; + } else { + px = nr_new (unsigned char, 16384); + } + + if (clear) memset (px, val, 16384); + + return px; +} + +void +nr_pixelstore_16K_free (unsigned char *px) +{ + if (nr_16K_len == nr_16K_size) { + nr_16K_size += NR_16K_BLOCK; + nr_16K_px = nr_renew (nr_16K_px, unsigned char *, nr_16K_size); + } + + nr_16K_px[nr_16K_len] = px; + nr_16K_len += 1; +} + +#define NR_64K_BLOCK 32 +static unsigned char **nr_64K_px = NULL; +static unsigned int nr_64K_len = 0; +static unsigned int nr_64K_size = 0; + +unsigned char * +nr_pixelstore_64K_new (bool clear, unsigned char val) +{ + unsigned char *px; + + if (nr_64K_len != 0) { + nr_64K_len -= 1; + px = nr_64K_px[nr_64K_len]; + } else { + px = nr_new (unsigned char, 65536); + } + + if (clear) memset (px, val, 65536); + + return px; +} + +void +nr_pixelstore_64K_free (unsigned char *px) +{ + if (nr_64K_len == nr_64K_size) { + nr_64K_size += NR_64K_BLOCK; + nr_64K_px = nr_renew (nr_64K_px, unsigned char *, nr_64K_size); + } + + nr_64K_px[nr_64K_len] = px; + nr_64K_len += 1; +} + +#define NR_256K_BLOCK 32 +#define NR_256K 262144 +static unsigned char **nr_256K_px = NULL; +static unsigned int nr_256K_len = 0; +static unsigned int nr_256K_size = 0; + +unsigned char * +nr_pixelstore_256K_new (bool clear, unsigned char val) +{ + unsigned char *px; + + if (nr_256K_len != 0) { + nr_256K_len -= 1; + px = nr_256K_px[nr_256K_len]; + } else { + px = nr_new (unsigned char, NR_256K); + } + + if (clear) memset (px, val, NR_256K); + + return px; +} + +void +nr_pixelstore_256K_free (unsigned char *px) +{ + if (nr_256K_len == nr_256K_size) { + nr_256K_size += NR_256K_BLOCK; + nr_256K_px = nr_renew (nr_256K_px, unsigned char *, nr_256K_size); + } + + nr_256K_px[nr_256K_len] = px; + nr_256K_len += 1; +} + +#define NR_1M_BLOCK 32 +#define NR_1M 1048576 +static unsigned char **nr_1M_px = NULL; +static unsigned int nr_1M_len = 0; +static unsigned int nr_1M_size = 0; + +unsigned char * +nr_pixelstore_1M_new (bool clear, unsigned char val) +{ + unsigned char *px; + + if (nr_1M_len != 0) { + nr_1M_len -= 1; + px = nr_1M_px[nr_1M_len]; + } else { + px = nr_new (unsigned char, NR_1M); + } + + if (clear) memset (px, val, NR_1M); + + return px; +} + +void +nr_pixelstore_1M_free (unsigned char *px) +{ + if (nr_1M_len == nr_1M_size) { + nr_1M_size += NR_1M_BLOCK; + nr_1M_px = nr_renew (nr_1M_px, unsigned char *, nr_1M_size); + } + + nr_1M_px[nr_1M_len] = px; + nr_1M_len += 1; +} + +/* + 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 : diff --git a/src/libnr/nr-pixblock.h b/src/libnr/nr-pixblock.h new file mode 100644 index 000000000..2a13dd921 --- /dev/null +++ b/src/libnr/nr-pixblock.h @@ -0,0 +1,95 @@ +#ifndef __NR_PIXBLOCK_H__ +#define __NR_PIXBLOCK_H__ + +/** \file + * \brief Pixel block structure. Used for low-level rendering. + * + * Authors: + * (C) 1999-2002 Lauris Kaplinski + * (C) 2005 Ralf Stephan (some cleanup) + * + * This code is in the Public Domain. + */ + +#include +#include + +/// Size indicator. Hardcoded to max. 3 bits. +typedef enum { + NR_PIXBLOCK_SIZE_TINY, ///< Fits in (unsigned char *) + NR_PIXBLOCK_SIZE_4K, ///< Pixelstore + NR_PIXBLOCK_SIZE_16K, ///< Pixelstore + NR_PIXBLOCK_SIZE_64K, ///< Pixelstore + NR_PIXBLOCK_SIZE_256K, ///< Pixelstore + NR_PIXBLOCK_SIZE_1M, ///< Pixelstore + NR_PIXBLOCK_SIZE_BIG, ///< Normally allocated + NR_PIXBLOCK_SIZE_STATIC ///< Externally managed +} NR_PIXBLOCK_SIZE; + +/// Mode indicator. Hardcoded to max. 2 bits. +typedef enum { + NR_PIXBLOCK_MODE_A8, ///< Grayscale + NR_PIXBLOCK_MODE_R8G8B8, ///< 8 bit RGB + NR_PIXBLOCK_MODE_R8G8B8A8N, ///< Normal 8 bit RGBA + NR_PIXBLOCK_MODE_R8G8B8A8P ///< Premultiplied 8 bit RGBA +} NR_PIXBLOCK_MODE; + +/// The pixel block struct. +struct NRPixBlock { + NR_PIXBLOCK_SIZE size : 3; ///< Size indicator + NR_PIXBLOCK_MODE mode : 2; ///< Mode indicator + bool empty : 1; ///< Empty flag + unsigned int rs; ///< Size of line in bytes + NRRectL area; + union { + unsigned char *px; ///< Pointer to buffer + unsigned char p[sizeof (unsigned char *)]; ///< Tiny buffer + } data; +}; + +/// Returns number of bytes per pixel (1, 3, or 4). +inline int +NR_PIXBLOCK_BPP (NRPixBlock *pb) +{ + return ((pb->mode == NR_PIXBLOCK_MODE_A8) ? 1 : + (pb->mode == NR_PIXBLOCK_MODE_R8G8B8) ? 3 : 4); +} + +/// Returns pointer to pixel data. +inline unsigned char* +NR_PIXBLOCK_PX (NRPixBlock *pb) +{ + return ((pb->size == NR_PIXBLOCK_SIZE_TINY) ? + pb->data.p : pb->data.px); +} + +void nr_pixblock_setup (NRPixBlock *pb, NR_PIXBLOCK_MODE mode, int x0, int y0, int x1, int y1, bool clear); +void nr_pixblock_setup_fast (NRPixBlock *pb, NR_PIXBLOCK_MODE mode, int x0, int y0, int x1, int y1, bool clear); +void nr_pixblock_setup_extern (NRPixBlock *pb, NR_PIXBLOCK_MODE mode, int x0, int y0, int x1, int y1, unsigned char *px, int rs, bool empty, bool clear); +void nr_pixblock_release (NRPixBlock *pb); + +NRPixBlock *nr_pixblock_new (NR_PIXBLOCK_MODE mode, int x0, int y0, int x1, int y1, bool clear); +NRPixBlock *nr_pixblock_free (NRPixBlock *pb); + +unsigned char *nr_pixelstore_4K_new (bool clear, unsigned char val); +void nr_pixelstore_4K_free (unsigned char *px); +unsigned char *nr_pixelstore_16K_new (bool clear, unsigned char val); +void nr_pixelstore_16K_free (unsigned char *px); +unsigned char *nr_pixelstore_64K_new (bool clear, unsigned char val); +void nr_pixelstore_64K_free (unsigned char *px); +unsigned char *nr_pixelstore_256K_new (bool clear, unsigned char val); +void nr_pixelstore_256K_free (unsigned char *px); +unsigned char *nr_pixelstore_1M_new (bool clear, unsigned char val); +void nr_pixelstore_1M_free (unsigned char *px); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-pixops.h b/src/libnr/nr-pixops.h new file mode 100644 index 000000000..32fc1e1cd --- /dev/null +++ b/src/libnr/nr-pixops.h @@ -0,0 +1,46 @@ +#ifndef __NR_PIXOPS_H__ +#define __NR_PIXOPS_H__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * bulia byak + * + * This code is in public domain + */ + +#define NR_RGBA32_R(v) (unsigned char) (((v) >> 24) & 0xff) +#define NR_RGBA32_G(v) (unsigned char) (((v) >> 16) & 0xff) +#define NR_RGBA32_B(v) (unsigned char) (((v) >> 8) & 0xff) +#define NR_RGBA32_A(v) (unsigned char) ((v) & 0xff) + +#define FAST_DIVIDE_BY_255(v) ((((v) << 8) + (v) + 257) >> 16) +#define NR_PREMUL(c,a) (FAST_DIVIDE_BY_255(((c) * (a) + 127))) +#define NR_PREMUL_SINGLE(c) (FAST_DIVIDE_BY_255((c) + 127)) +#define NR_A7(fa,ba) (65025 - (255 - fa) * (255 - ba)) +#define NR_A7_NORMALIZED(fa,ba) (FAST_DIVIDE_BY_255((65025 - (255 - (fa)) * (255 - (ba))) + 127)) +#define NR_COMPOSENNN_A7(fc,fa,bc,ba,a) (((255 - (fa)) * (bc) * (ba) + (fa) * (fc) * 255 + 127) / a) +#define NR_COMPOSEPNN_A7(fc,fa,bc,ba,a) (((255 - (fa)) * (bc) * (ba) + (fc) * 65025 + 127) / a) +#define NR_COMPOSENNP(fc,fa,bc,ba) (((255 - (fa)) * (bc) * (ba) + (fa) * (fc) * 255 + 32512) / 65025) +#define NR_COMPOSEPNP(fc,fa,bc,ba) (((255 - (fa)) * (bc) * (ba) + (fc) * 65025 + 32512) / 65025) +#define NR_COMPOSENPP(fc,fa,bc,ba) (FAST_DIVIDE_BY_255((255 - (fa)) * (bc) + (fa) * (fc) + 127)) +#define NR_COMPOSEPPP(fc,fa,bc,ba) (FAST_DIVIDE_BY_255((255 - (fa)) * (bc) + (fc) * 255 + 127)) +#define NR_COMPOSEP11(fc,fa,bc) (FAST_DIVIDE_BY_255((255 - (fa)) * (bc) + (fc) * 255 + 127)) +#define NR_COMPOSEN11(fc,fa,bc) (FAST_DIVIDE_BY_255((255 - (fa)) * (bc) + (fc) * (fa) + 127)) + +#define INK_COMPOSE(f,a,b) ( ( ((guchar) b) * ((guchar) (0xff - a)) + ((guchar) ((b ^ ~f) + b/4 - (b>127? 63 : 0))) * ((guchar) a) ) >>8) + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-point-fns-test.cpp b/src/libnr/nr-point-fns-test.cpp new file mode 100644 index 000000000..11181436b --- /dev/null +++ b/src/libnr/nr-point-fns-test.cpp @@ -0,0 +1,107 @@ +#include +#include +#include +#include + +#include "utest/utest.h" +#include "libnr/nr-point-fns.h" +#include "isnan.h" + +using NR::Point; + +int main(int argc, char *argv[]) +{ + utest_start("nr-point-fns"); + + Point const p3n4(3.0, -4.0); + Point const p0(0.0, 0.0); + double const small = pow(2.0, -1070); + double const inf = 1e400; + double const nan = inf - inf; + + Point const small_left(-small, 0.0); + Point const small_n3_4(-3.0 * small, 4.0 * small); + Point const part_nan(3., nan); + Point const inf_left(-inf, 5.0); + + assert(isNaN(nan)); + assert(!isNaN(small)); + + UTEST_TEST("L1") { + UTEST_ASSERT( NR::L1(p0) == 0.0 ); + UTEST_ASSERT( NR::L1(p3n4) == 7.0 ); + UTEST_ASSERT( NR::L1(small_left) == small ); + UTEST_ASSERT( NR::L1(inf_left) == inf ); + UTEST_ASSERT( NR::L1(small_n3_4) == 7.0 * small ); + UTEST_ASSERT(isNaN(NR::L1(part_nan))); + } + + UTEST_TEST("L2") { + UTEST_ASSERT( NR::L2(p0) == 0.0 ); + UTEST_ASSERT( NR::L2(p3n4) == 5.0 ); + UTEST_ASSERT( NR::L2(small_left) == small ); + UTEST_ASSERT( NR::L2(inf_left) == inf ); + UTEST_ASSERT( NR::L2(small_n3_4) == 5.0 * small ); + UTEST_ASSERT(isNaN(NR::L2(part_nan))); + } + + UTEST_TEST("LInfty") { + UTEST_ASSERT( NR::LInfty(p0) == 0.0 ); + UTEST_ASSERT( NR::LInfty(p3n4) == 4.0 ); + UTEST_ASSERT( NR::LInfty(small_left) == small ); + UTEST_ASSERT( NR::LInfty(inf_left) == inf ); + UTEST_ASSERT( NR::LInfty(small_n3_4) == 4.0 * small ); + UTEST_ASSERT(isNaN(NR::LInfty(part_nan))); + } + + UTEST_TEST("is_zero") { + UTEST_ASSERT(NR::is_zero(p0)); + UTEST_ASSERT(!NR::is_zero(p3n4)); + UTEST_ASSERT(!NR::is_zero(small_left)); + UTEST_ASSERT(!NR::is_zero(inf_left)); + UTEST_ASSERT(!NR::is_zero(small_n3_4)); + UTEST_ASSERT(!NR::is_zero(part_nan)); + } + + UTEST_TEST("atan2") { + UTEST_ASSERT( NR::atan2(p3n4) == atan2(-4.0, 3.0) ); + UTEST_ASSERT( NR::atan2(small_left) == atan2(0.0, -1.0) ); + UTEST_ASSERT( NR::atan2(small_n3_4) == atan2(4.0, -3.0) ); + } + + UTEST_TEST("unit_vector") { + UTEST_ASSERT( NR::unit_vector(p3n4) == Point(.6, -0.8) ); + UTEST_ASSERT( NR::unit_vector(small_left) == Point(-1.0, 0.0) ); + UTEST_ASSERT( NR::unit_vector(small_n3_4) == Point(-.6, 0.8) ); + } + + UTEST_TEST("is_unit_vector") { + UTEST_ASSERT(!NR::is_unit_vector(p3n4)); + UTEST_ASSERT(!NR::is_unit_vector(small_left)); + UTEST_ASSERT(!NR::is_unit_vector(small_n3_4)); + UTEST_ASSERT(!NR::is_unit_vector(part_nan)); + UTEST_ASSERT(!NR::is_unit_vector(inf_left)); + UTEST_ASSERT(!NR::is_unit_vector(Point(.5, 0.5))); + UTEST_ASSERT(NR::is_unit_vector(Point(.6, -0.8))); + UTEST_ASSERT(NR::is_unit_vector(Point(-.6, 0.8))); + UTEST_ASSERT(NR::is_unit_vector(Point(-1.0, 0.0))); + UTEST_ASSERT(NR::is_unit_vector(Point(1.0, 0.0))); + UTEST_ASSERT(NR::is_unit_vector(Point(0.0, -1.0))); + UTEST_ASSERT(NR::is_unit_vector(Point(0.0, 1.0))); + } + + return ( utest_end() + ? EXIT_SUCCESS + : EXIT_FAILURE ); +} + +/* + 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 : diff --git a/src/libnr/nr-point-fns-test.h b/src/libnr/nr-point-fns-test.h new file mode 100644 index 000000000..509d9d2fa --- /dev/null +++ b/src/libnr/nr-point-fns-test.h @@ -0,0 +1,141 @@ +// nr-point-fns-test.h +#include + +#include +#include +#include +#include + +#include "libnr/nr-point-fns.h" +#include "isnan.h" + +using NR::Point; + +class NrPointFnsTest : public CxxTest::TestSuite +{ +public: + NrPointFnsTest() : + setupValid(true), + p3n4( 3.0, -4.0 ), + p0( 0.0, 0.0 ), + small( pow( 2.0, -1070 ) ), + inf( 1e400 ), + nan( inf - inf ), + small_left( -small, 0.0 ), + small_n3_4( -3.0 * small, 4.0 * small ), + part_nan( 3., nan ), + inf_left( -inf, 5.0 ) + { + TS_ASSERT( isNaN(nan) ); + TS_ASSERT( !isNaN(small) ); + + setupValid &= isNaN(nan); + setupValid &= !isNaN(small); + } + virtual ~NrPointFnsTest() {} + +// createSuite and destroySuite get us per-suite setup and teardown +// without us having to worry about static initialization order, etc. + static NrPointFnsTest *createSuite() { return new NrPointFnsTest(); } + static void destroySuite( NrPointFnsTest *suite ) { delete suite; } + +// Called before each test in this suite + void setUp() + { + TS_ASSERT( setupValid ); + } + + bool setupValid; + Point const p3n4; + Point const p0; + double const small; + double const inf; + double const nan; + + Point const small_left; + Point const small_n3_4; + Point const part_nan; + Point const inf_left; + + + void testL1(void) + { + TS_ASSERT_EQUALS( NR::L1(p0), 0.0 ); + TS_ASSERT_EQUALS( NR::L1(p3n4), 7.0 ); + TS_ASSERT_EQUALS( NR::L1(small_left), small ); + TS_ASSERT_EQUALS( NR::L1(inf_left), inf ); + TS_ASSERT_EQUALS( NR::L1(small_n3_4), 7.0 * small ); + TS_ASSERT(isNaN(NR::L1(part_nan))); + } + + void testL2(void) + { + TS_ASSERT_EQUALS( NR::L2(p0), 0.0 ); + TS_ASSERT_EQUALS( NR::L2(p3n4), 5.0 ); + TS_ASSERT_EQUALS( NR::L2(small_left), small ); + TS_ASSERT_EQUALS( NR::L2(inf_left), inf ); + TS_ASSERT_EQUALS( NR::L2(small_n3_4), 5.0 * small ); + TS_ASSERT( isNaN(NR::L2(part_nan)) ); + } + + void testLInfty(void) + { + TS_ASSERT_EQUALS( NR::LInfty(p0), 0.0 ); + TS_ASSERT_EQUALS( NR::LInfty(p3n4), 4.0 ); + TS_ASSERT_EQUALS( NR::LInfty(small_left), small ); + TS_ASSERT_EQUALS( NR::LInfty(inf_left), inf ); + TS_ASSERT_EQUALS( NR::LInfty(small_n3_4), 4.0 * small ); + TS_ASSERT( isNaN(NR::LInfty(part_nan)) ); + } + + void testIsZero(void) + { + TS_ASSERT( NR::is_zero(p0) ); + TS_ASSERT( !NR::is_zero(p3n4) ); + TS_ASSERT( !NR::is_zero(small_left) ); + TS_ASSERT( !NR::is_zero(inf_left) ); + TS_ASSERT( !NR::is_zero(small_n3_4) ); + TS_ASSERT( !NR::is_zero(part_nan) ); + } + + void testAtan2(void) + { + TS_ASSERT_EQUALS( NR::atan2(p3n4), atan2(-4.0, 3.0) ); + TS_ASSERT_EQUALS( NR::atan2(small_left), atan2(0.0, -1.0) ); + TS_ASSERT_EQUALS( NR::atan2(small_n3_4), atan2(4.0, -3.0) ); + } + + void testUnitVector(void) + { + TS_ASSERT_EQUALS( NR::unit_vector(p3n4), Point(.6, -0.8) ); + TS_ASSERT_EQUALS( NR::unit_vector(small_left), Point(-1.0, 0.0) ); + TS_ASSERT_EQUALS( NR::unit_vector(small_n3_4), Point(-.6, 0.8) ); + } + + void testIsUnitVector(void) + { + TS_ASSERT( !NR::is_unit_vector(p3n4) ); + TS_ASSERT( !NR::is_unit_vector(small_left) ); + TS_ASSERT( !NR::is_unit_vector(small_n3_4) ); + TS_ASSERT( !NR::is_unit_vector(part_nan) ); + TS_ASSERT( !NR::is_unit_vector(inf_left) ); + TS_ASSERT( !NR::is_unit_vector(Point(.5, 0.5)) ); + TS_ASSERT( NR::is_unit_vector(Point(.6, -0.8)) ); + TS_ASSERT( NR::is_unit_vector(Point(-.6, 0.8)) ); + TS_ASSERT( NR::is_unit_vector(Point(-1.0, 0.0)) ); + TS_ASSERT( NR::is_unit_vector(Point(1.0, 0.0)) ); + TS_ASSERT( NR::is_unit_vector(Point(0.0, -1.0)) ); + TS_ASSERT( NR::is_unit_vector(Point(0.0, 1.0)) ); + } +}; + +/* + 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 : diff --git a/src/libnr/nr-point-fns.cpp b/src/libnr/nr-point-fns.cpp new file mode 100644 index 000000000..a92c2b00b --- /dev/null +++ b/src/libnr/nr-point-fns.cpp @@ -0,0 +1,74 @@ +#include +#include + +using NR::Point; + +/** Compute the L infinity, or maximum, norm of \a p. */ +NR::Coord NR::LInfty(Point const &p) { + NR::Coord const a(fabs(p[0])); + NR::Coord const b(fabs(p[1])); + return ( a < b || isNaN(b) + ? b + : a ); +} + +/** Returns true iff p is a zero vector, i.e.\ Point(0, 0). + * + * (NaN is considered non-zero.) + */ +bool +NR::is_zero(Point const &p) +{ + return ( p[0] == 0 && + p[1] == 0 ); +} + +bool +NR::is_unit_vector(Point const &p) +{ + return fabs(1.0 - L2(p)) <= 1e-4; + /* The tolerance of 1e-4 is somewhat arbitrary. NR::Point::normalize is believed to return + points well within this tolerance. I'm not aware of any callers that want a small + tolerance; most callers would be ok with a tolerance of 0.25. */ +} + +NR::Coord NR::atan2(Point const p) { + return std::atan2(p[NR::Y], p[NR::X]); +} + +/** Returns a version of \a a scaled to be a unit vector (within rounding error). + * + * The current version tries to handle infinite coordinates gracefully, + * but it's not clear that any callers need that. + * + * \pre a != Point(0, 0). + * \pre Neither coordinate is NaN. + * \post L2(ret) very near 1.0. + */ +Point NR::unit_vector(Point const &a) +{ + Point ret(a); + ret.normalize(); + return ret; +} + +NR::Point abs(NR::Point const &b) +{ + NR::Point ret; + for ( int i = 0 ; i < 2 ; i++ ) { + ret[i] = fabs(b[i]); + } + return ret; +} + + +/* + 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 : diff --git a/src/libnr/nr-point-fns.h b/src/libnr/nr-point-fns.h new file mode 100644 index 000000000..9f9a8f9e2 --- /dev/null +++ b/src/libnr/nr-point-fns.h @@ -0,0 +1,104 @@ +#ifndef __NR_POINT_OPS_H__ +#define __NR_POINT_OPS_H__ + +#include +#include +#include + +namespace NR { + +/** Compute the L1 norm, or manhattan distance, of \a p. */ +inline Coord L1(Point const &p) { + Coord d = 0; + for ( int i = 0 ; i < 2 ; i++ ) { + d += fabs(p[i]); + } + return d; +} + +/** Compute the L2, or euclidean, norm of \a p. */ +inline Coord L2(Point const &p) { + return hypot(p[0], p[1]); +} + +extern double LInfty(Point const &p); + +bool is_zero(Point const &p); + +bool is_unit_vector(Point const &p); + +extern double atan2(Point const p); + +inline bool point_equalp(Point const &a, Point const &b, double const eps) +{ + return ( NR_DF_TEST_CLOSE(a[X], b[X], eps) && + NR_DF_TEST_CLOSE(a[Y], b[Y], eps) ); +} + +/** Returns p * NR::rotate_degrees(90), but more efficient. + * + * Angle direction in Inkscape code: If you use the traditional mathematics convention that y + * increases upwards, then positive angles are anticlockwise as per the mathematics convention. If + * you take the common non-mathematical convention that y increases downwards, then positive angles + * are clockwise, as is common outside of mathematics. + * + * There is no rot_neg90 function: use -rot90(p) instead. + */ +inline Point rot90(Point const &p) +{ + return Point(-p[Y], p[X]); +} + +/** Given two points and a parameter t \in [0, 1], return a point + * proportionally from a to b by t. */ +inline Point Lerp(double const t, Point const a, Point const b) +{ + return ( ( 1 - t ) * a + + t * b ); +} + +Point unit_vector(Point const &a); + +inline Coord dot(Point const &a, Point const &b) +{ + Coord ret = 0; + for ( int i = 0 ; i < 2 ; i++ ) { + ret += a[i] * b[i]; + } + return ret; +} + +inline Coord distance (Point const &a, Point const &b) +{ + Coord ret = 0; + for ( int i = 0 ; i < 2 ; i++ ) { + ret += (a[i] - b[i]) * (a[i] - b[i]); + } + return sqrt (ret); +} + +/** Defined as dot(a, b.cw()). */ +inline Coord cross(Point const &a, Point const &b) +{ + Coord ret = 0; + ret -= a[0] * b[1]; + ret += a[1] * b[0]; + return ret; +} + +Point abs(Point const &b); + +} /* namespace NR */ + +#endif /* !__NR_POINT_OPS_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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-point-l.h b/src/libnr/nr-point-l.h new file mode 100644 index 000000000..8ddfd5e6f --- /dev/null +++ b/src/libnr/nr-point-l.h @@ -0,0 +1,95 @@ +#ifndef SEEN_NR_POINT_L_H +#define SEEN_NR_POINT_L_H + +#include +#include +#include + +struct NRPointL { + NR::ICoord x, y; +}; + +namespace NR { + +class IPoint { +public: + IPoint() + { } + + IPoint(ICoord x, ICoord y) { + _pt[X] = x; + _pt[Y] = y; + } + + IPoint(NRPointL const &p) { + _pt[X] = p.x; + _pt[Y] = p.y; + } + + IPoint(IPoint const &p) { + for (unsigned i = 0; i < 2; ++i) { + _pt[i] = p._pt[i]; + } + } + + IPoint &operator=(IPoint const &p) { + for (unsigned i = 0; i < 2; ++i) { + _pt[i] = p._pt[i]; + } + return *this; + } + + operator Point() { + return Point(_pt[X], _pt[Y]); + } + + ICoord operator[](unsigned i) const throw(std::out_of_range) { + if ( i > Y ) { + throw std::out_of_range("index out of range"); + } + return _pt[i]; + } + + ICoord &operator[](unsigned i) throw(std::out_of_range) { + if ( i > Y ) { + throw std::out_of_range("index out of range"); + } + return _pt[i]; + } + + ICoord operator[](Dim2 d) const throw() { return _pt[d]; } + ICoord &operator[](Dim2 d) throw() { return _pt[d]; } + + IPoint &operator+=(IPoint const &o) { + for ( unsigned i = 0 ; i < 2 ; ++i ) { + _pt[i] += o._pt[i]; + } + return *this; + } + + IPoint &operator-=(IPoint const &o) { + for ( unsigned i = 0 ; i < 2 ; ++i ) { + _pt[i] -= o._pt[i]; + } + return *this; + } + +private: + ICoord _pt[2]; +}; + + +} // namespace NR + +#endif /* !SEEN_NR_POINT_L_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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-point-matrix-ops.h b/src/libnr/nr-point-matrix-ops.h new file mode 100644 index 000000000..58e06fc6d --- /dev/null +++ b/src/libnr/nr-point-matrix-ops.h @@ -0,0 +1,47 @@ +/** \file operator functions over (NR::Point, NR::Matrix). */ +#ifndef SEEN_NR_POINT_MATRIX_OPS_H +#define SEEN_NR_POINT_MATRIX_OPS_H + +#include "libnr/nr-point.h" +#include "libnr/nr-matrix.h" + +namespace NR { + +inline Point operator*(Point const &v, Matrix const &m) +{ +#if 1 /* Which code makes it easier to see what's happening? */ + NR::Point const xform_col0(m[0], + m[2]); + NR::Point const xform_col1(m[1], + m[3]); + NR::Point const xlate(m[4], m[5]); + return ( Point(dot(v, xform_col0), + dot(v, xform_col1)) + + xlate ); +#else + return Point(v[X] * m[0] + v[Y] * m[2] + m[4], + v[X] * m[1] + v[Y] * m[3] + m[5]); +#endif +} + +inline Point &Point::operator*=(Matrix const &m) +{ + *this = *this * m; + return *this; +} + +} /* namespace NR */ + + +#endif /* !SEEN_NR_POINT_MATRIX_OPS_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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-point-ops.h b/src/libnr/nr-point-ops.h new file mode 100644 index 000000000..03d61fb15 --- /dev/null +++ b/src/libnr/nr-point-ops.h @@ -0,0 +1,88 @@ +/* operator functions for NR::Point. */ +#ifndef SEEN_NR_POINT_OPS_H +#define SEEN_NR_POINT_OPS_H + +#include + +namespace NR { + +inline Point operator+(Point const &a, Point const &b) +{ + Point ret; + for (int i = 0; i < 2; i++) { + ret[i] = a[i] + b[i]; + } + return ret; +} + +inline Point operator-(Point const &a, Point const &b) +{ + Point ret; + for (int i = 0; i < 2; i++) { + ret[i] = a[i] - b[i]; + } + return ret; +} + +/** This is a rotation (sort of). */ +inline Point operator^(Point const &a, Point const &b) +{ + Point const ret(a[0] * b[0] - a[1] * b[1], + a[1] * b[0] + a[0] * b[1]); + return ret; +} + +inline Point operator-(Point const &a) +{ + Point ret; + for(unsigned i = 0; i < 2; i++) { + ret[i] = -a[i]; + } + return ret; +} + +inline Point operator*(double const s, Point const &b) +{ + Point ret; + for(int i = 0; i < 2; i++) { + ret[i] = s * b[i]; + } + return ret; +} + +inline Point operator/(Point const &b, double const d) +{ + Point ret; + for(int i = 0; i < 2; i++) { + ret[i] = b[i] / d; + } + return ret; +} + + +inline bool operator==(Point const &a, Point const &b) +{ + return ( ( a[X] == b[X] ) && ( a[Y] == b[Y] ) ); +} + +inline bool operator!=(Point const &a, Point const &b) +{ + return ( ( a[X] != b[X] ) || ( a[Y] != b[Y] ) ); +} + + +} /* namespace NR */ + + +#endif /* !SEEN_NR_POINT_OPS_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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-point.h b/src/libnr/nr-point.h new file mode 100644 index 000000000..f67a66800 --- /dev/null +++ b/src/libnr/nr-point.h @@ -0,0 +1,159 @@ +#ifndef SEEN_NR_POINT_H +#define SEEN_NR_POINT_H + +/** \file + * Cartesian point class. + */ + +//#include +//#include +#include +//#include + +#include +#include + +//#include "round.h" +#include "decimal-round.h" + +/// A NRPoint consists of x and y coodinates. +/// \todo +/// This class appears to be obsoleted out in favour of NR::Point. +struct NRPoint { + NR::Coord x, y; +}; + +namespace NR { + +class Matrix; + +/// Cartesian point. +class Point { +public: + inline Point() + { _pt[X] = _pt[Y] = 0; } + + inline Point(Coord x, Coord y) { + _pt[X] = x; + _pt[Y] = y; + } + + inline Point(NRPoint const &p) { + _pt[X] = p.x; + _pt[Y] = p.y; + } + + inline Point(Point const &p) { + for (unsigned i = 0; i < 2; ++i) { + _pt[i] = p._pt[i]; + } + } + + inline Point &operator=(Point const &p) { + for (unsigned i = 0; i < 2; ++i) { + _pt[i] = p._pt[i]; + } + return *this; + } + + inline Coord operator[](unsigned i) const { + return _pt[i]; + } + + inline Coord &operator[](unsigned i) { + return _pt[i]; + } + + Coord operator[](Dim2 d) const throw() { return _pt[d]; } + Coord &operator[](Dim2 d) throw() { return _pt[d]; } + + /** Return a point like this point but rotated -90 degrees. + (If the y axis grows downwards and the x axis grows to the + right, then this is 90 degrees counter-clockwise.) + **/ + Point ccw() const { + return Point(_pt[Y], -_pt[X]); + } + + /** Return a point like this point but rotated +90 degrees. + (If the y axis grows downwards and the x axis grows to the + right, then this is 90 degrees clockwise.) + **/ + Point cw() const { + return Point(-_pt[Y], _pt[X]); + } + + /** + \brief A function to lower the precision of the point + \param places The number of decimal places that should be in + the final number. + */ + inline void round (int places = 0) { + _pt[X] = (Coord)(Inkscape::decimal_round((double)_pt[X], places)); + _pt[Y] = (Coord)(Inkscape::decimal_round((double)_pt[Y], places)); + return; + } + + void normalize(); + + inline Point &operator+=(Point const &o) { + for ( unsigned i = 0 ; i < 2 ; ++i ) { + _pt[i] += o._pt[i]; + } + return *this; + } + + inline Point &operator-=(Point const &o) { + for ( unsigned i = 0 ; i < 2 ; ++i ) { + _pt[i] -= o._pt[i]; + } + return *this; + } + + inline Point &operator/=(double const s) { + for ( unsigned i = 0 ; i < 2 ; ++i ) { + _pt[i] /= s; + } + return *this; + } + + inline Point &operator*=(double const s) { + for ( unsigned i = 0 ; i < 2 ; ++i ) { + _pt[i] *= s; + } + return *this; + } + + Point &operator*=(Matrix const &m); + + inline int operator == (const Point &in_pnt) { + return ((_pt[X] == in_pnt[X]) && (_pt[Y] == in_pnt[Y])); + } + + friend inline std::ostream &operator<< (std::ostream &out_file, const NR::Point &in_pnt); + +private: + Coord _pt[2]; +}; + +/** A function to print out the Point. It just prints out the coords + on the given output stream */ +inline std::ostream &operator<< (std::ostream &out_file, const NR::Point &in_pnt) { + out_file << "X: " << in_pnt[X] << " Y: " << in_pnt[Y]; + return out_file; +} + +} /* namespace NR */ + +#endif /* !SEEN_NR_POINT_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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-rect-l.cpp b/src/libnr/nr-rect-l.cpp new file mode 100644 index 000000000..40db67a1e --- /dev/null +++ b/src/libnr/nr-rect-l.cpp @@ -0,0 +1,22 @@ +#include + +namespace NR { + +IRect::IRect(Rect const &r) : + _min(int(floor(r.min()[X])), int(floor(r.min()[Y]))), + _max(int(ceil(r.min()[X])), int(ceil(r.min()[Y]))) +{ +} + +} + +/* + 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 : diff --git a/src/libnr/nr-rect-l.h b/src/libnr/nr-rect-l.h new file mode 100644 index 000000000..756a537b0 --- /dev/null +++ b/src/libnr/nr-rect-l.h @@ -0,0 +1,131 @@ +#ifndef SEEN_NR_RECT_L_H +#define SEEN_NR_RECT_L_H + +#include + +struct NRRectL { + NR::ICoord x0, y0, x1, y1; +}; + +#include +#include + +namespace NR { + + +class IRect { +public: + IRect(const NRRectL& r) : _min(r.x0, r.y0), _max(r.x1, r.y1) {} + IRect(const IRect& r) : _min(r._min), _max(r._max) {} + IRect(const IPoint &p0, const IPoint &p1); + + /** as not all Rects are representable by IRects this gives the smallest IRect that contains + * r. */ + IRect(const Rect& r); + + operator Rect() { + return Rect(Point(_min), Point(_max)); + } + + const IPoint &min() const { return _min; } + const IPoint &max() const { return _max; } + + /** returns a vector from min to max. */ + IPoint dimensions() const; + + /** does this rectangle have zero area? */ + bool isEmpty() const { + return isEmpty() && isEmpty(); + } + + bool intersects(const IRect &r) const { + return intersects(r) && intersects(r); + } + bool contains(const IRect &r) const { + return contains(r) && contains(r); + } + bool contains(const IPoint &p) const { + return contains(p) && contains(p); + } + + ICoord maxExtent() const { + return MAX(extent(), extent()); + } + + ICoord extent(Dim2 axis) const { + switch (axis) { + case X: return extent(); + case Y: return extent(); + }; + } + + ICoord extent(unsigned i) const throw(std::out_of_range) { + switch (i) { + case 0: return extent(); + case 1: return extent(); + default: throw std::out_of_range("Dimension out of range"); + }; + } + + /** Translates the rectangle by p. */ + void offset(IPoint p); + + /** Makes this rectangle large enough to include the point p. */ + void expandTo(IPoint p); + + /** Makes this rectangle large enough to include the rectangle r. */ + void expandTo(const IRect &r); + + /** Returns the set of points shared by both rectangles. */ + static Maybe intersection(const IRect &a, const IRect &b); + + /** Returns the smallest rectangle that encloses both rectangles. */ + static IRect union_bounds(const IRect &a, const IRect &b); + +private: + IRect() {} + + template + ICoord extent() const { + return _max[axis] - _min[axis]; + } + + template + bool isEmpty() const { + return !( _min[axis] < _max[axis] ); + } + + template + bool intersects(const IRect &r) const { + return _max[axis] >= r._min[axis] && _min[axis] <= r._max[axis]; + } + + template + bool contains(const IRect &r) const { + return contains(r._min) && contains(r._max); + } + + template + bool contains(const IPoint &p) const { + return p[axis] >= _min[axis] && p[axis] <= _max[axis]; + } + + IPoint _min, _max; +}; + + + +} // namespace NR + +#endif /* !SEEN_NR_RECT_L_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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-rect-ops.h b/src/libnr/nr-rect-ops.h new file mode 100644 index 000000000..870091a94 --- /dev/null +++ b/src/libnr/nr-rect-ops.h @@ -0,0 +1,51 @@ +#ifndef SEEN_NR_RECT_OPS_H +#define SEEN_NR_RECT_OPS_H + +/* + * Rect operators + * + * Copyright 2004 MenTaLguY , + * bulia byak + * + * This code is licensed under the GNU GPL; see COPYING for more information. + */ + +#include + +namespace NR { + +inline Rect expand(Rect const &r, double by) { + NR::Point const p(by, by); + return Rect(r.min() + p, r.max() - p); +} + +inline Rect expand(Rect const &r, NR::Point by) { + return Rect(r.min() + by, r.max() - by); +} + +#if 0 +inline ConvexHull operator*(Rect const &r, Matrix const &m) { + /* FIXME: no mention of m. Should probably be made non-inline. */ + ConvexHull points(r.corner(0)); + for ( unsigned i = 1 ; i < 4 ; i++ ) { + points.add(r.corner(i)); + } + return points; +} +#endif + +} /* namespace NR */ + + +#endif /* !SEEN_NR_RECT_OPS_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 : diff --git a/src/libnr/nr-rect.cpp b/src/libnr/nr-rect.cpp new file mode 100644 index 000000000..0a8287bde --- /dev/null +++ b/src/libnr/nr-rect.cpp @@ -0,0 +1,233 @@ +#define __NR_RECT_C__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * + * This code is in public domain + */ + +#include "nr-rect-l.h" + +/** + * \param r0 Rectangle. + * \param r1 Another rectangle. + * \param d Filled in with the intersection of r0 and r1. + * \return d. + */ + +NRRectL *nr_rect_l_intersect(NRRectL *d, const NRRectL *r0, const NRRectL *r1) +{ + NR::ICoord t; + t = std::max(r0->x0, r1->x0); + d->x1 = std::min(r0->x1, r1->x1); + d->x0 = t; + t = std::max(r0->y0, r1->y0); + d->y1 = std::min(r0->y1, r1->y1); + d->y0 = t; + + return d; +} + +NRRect * +nr_rect_d_intersect (NRRect *d, const NRRect *r0, const NRRect *r1) +{ + NR::Coord t; + t = MAX (r0->x0, r1->x0); + d->x1 = MIN (r0->x1, r1->x1); + d->x0 = t; + t = MAX (r0->y0, r1->y0); + d->y1 = MIN (r0->y1, r1->y1); + d->y0 = t; + + return d; +} + +NRRect * +nr_rect_d_union (NRRect *d, const NRRect *r0, const NRRect *r1) +{ + if (NR_RECT_DFLS_TEST_EMPTY (r0)) { + if (NR_RECT_DFLS_TEST_EMPTY (r1)) { + nr_rect_d_set_empty (d); + } else { + *d = *r1; + } + } else { + if (NR_RECT_DFLS_TEST_EMPTY (r1)) { + *d = *r0; + } else { + NR::Coord t; + t = MIN (r0->x0, r1->x0); + d->x1 = MAX (r0->x1, r1->x1); + d->x0 = t; + t = MIN (r0->y0, r1->y0); + d->y1 = MAX (r0->y1, r1->y1); + d->y0 = t; + } + } + return d; +} + +NRRectL * +nr_rect_l_union (NRRectL *d, const NRRectL *r0, const NRRectL *r1) +{ + if (NR_RECT_DFLS_TEST_EMPTY (r0)) { + if (NR_RECT_DFLS_TEST_EMPTY (r1)) { + nr_rect_l_set_empty (d); + } else { + *d = *r1; + } + } else { + if (NR_RECT_DFLS_TEST_EMPTY (r1)) { + *d = *r0; + } else { + NR::ICoord t; + t = MIN (r0->x0, r1->x0); + d->x1 = MAX (r0->x1, r1->x1); + d->x0 = t; + t = MIN (r0->y0, r1->y0); + d->y1 = MAX (r0->y1, r1->y1); + d->y0 = t; + } + } + return d; +} + +NRRect * +nr_rect_union_pt(NRRect *dst, NR::Point const &p) +{ + using NR::X; + using NR::Y; + + return nr_rect_d_union_xy(dst, p[X], p[Y]); +} + +NRRect * +nr_rect_d_union_xy (NRRect *d, NR::Coord x, NR::Coord y) +{ + if ((d->x0 <= d->x1) && (d->y0 <= d->y1)) { + d->x0 = MIN (d->x0, x); + d->y0 = MIN (d->y0, y); + d->x1 = MAX (d->x1, x); + d->y1 = MAX (d->y1, y); + } else { + d->x0 = d->x1 = x; + d->y0 = d->y1 = y; + } + return d; +} + +NRRect * +nr_rect_d_matrix_transform(NRRect *d, NRRect const *const s, NR::Matrix const &m) +{ + using NR::X; + using NR::Y; + + if (nr_rect_d_test_empty(s)) { + nr_rect_d_set_empty(d); + } else { + NR::Point const c00(NR::Point(s->x0, s->y0) * m); + NR::Point const c01(NR::Point(s->x0, s->y1) * m); + NR::Point const c10(NR::Point(s->x1, s->y0) * m); + NR::Point const c11(NR::Point(s->x1, s->y1) * m); + d->x0 = std::min(std::min(c00[X], c01[X]), + std::min(c10[X], c11[X])); + d->y0 = std::min(std::min(c00[Y], c01[Y]), + std::min(c10[Y], c11[Y])); + d->x1 = std::max(std::max(c00[X], c01[X]), + std::max(c10[X], c11[X])); + d->y1 = std::max(std::max(c00[Y], c01[Y]), + std::max(c10[Y], c11[Y])); + } + return d; +} + +NRRect * +nr_rect_d_matrix_transform(NRRect *d, NRRect const *s, NRMatrix const *m) +{ + return nr_rect_d_matrix_transform(d, s, *m); +} + +namespace NR { + +Rect::Rect(const Point &p0, const Point &p1) +: _min(MIN(p0[X], p1[X]), MIN(p0[Y], p1[Y])), + _max(MAX(p0[X], p1[X]), MAX(p0[Y], p1[Y])) {} + +/** returns the four corners of the rectangle in the correct winding order */ +Point Rect::corner(unsigned i) const { + switch (i % 4) { + case 0: + return _min; + case 1: + return Point(_max[X], _min[Y]); + case 2: + return _max; + default: /* i.e. 3 */ + return Point(_min[X], _max[Y]); + } +} + +/** returns the midpoint of this rectangle */ +Point Rect::midpoint() const { + return ( _min + _max ) / 2; +} + +/** returns a vector from topleft to bottom right. */ +Point Rect::dimensions() const { + return _max - _min; +} + +/** Translates the rectangle by p. */ +void Rect::offset(Point p) { + _min += p; + _max += p; +} + +/** Makes this rectangle large enough to include the point p. */ +void Rect::expandTo(Point p) { + for ( int i=0 ; i < 2 ; i++ ) { + _min[i] = MIN(_min[i], p[i]); + _max[i] = MAX(_max[i], p[i]); + } +} + +/** Returns the set of points shared by both rectangles. */ +Maybe Rect::intersection(const Rect &a, const Rect &b) { + Rect r; + for ( int i=0 ; i < 2 ; i++ ) { + r._min[i] = MAX(a._min[i], b._min[i]); + r._max[i] = MIN(a._max[i], b._max[i]); + + if ( r._min[i] > r._max[i] ) { + return Nothing(); + } + } + return r; +} + +/** returns the smallest rectangle containing both rectangles */ +Rect Rect::union_bounds(const Rect &a, const Rect &b) { + Rect r; + for ( int i=0; i < 2 ; i++ ) { + r._min[i] = MIN(a._min[i], b._min[i]); + r._max[i] = MAX(a._max[i], b._max[i]); + } + return r; +} + +} // namespace NR + + +/* + 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/libnr/nr-rect.h b/src/libnr/nr-rect.h new file mode 100644 index 000000000..219fbd1ac --- /dev/null +++ b/src/libnr/nr-rect.h @@ -0,0 +1,255 @@ +#ifndef LIBNR_NR_RECT_H_SEEN +#define LIBNR_NR_RECT_H_SEEN + +/** \file + * Definitions of NRRect and NR::Rect types, and some associated functions \& macros. + */ +/* + * Authors: + * Lauris Kaplinski + * Nathan Hurst + * MenTaLguY + * + * This code is in public domain + */ + + +#include + +#include "libnr/nr-values.h" +#include +#include +#include +#include +#include +#include + +struct NRMatrix; +namespace NR { + struct Matrix; +} + +/* NULL rect is infinite */ + +struct NRRect { + NR::Coord x0, y0, x1, y1; +}; + +inline bool empty(NRRect const &r) +{ + return ( ( r.x0 > r.x1 ) || + ( r.y0 > r.y1 ) ); +} + +#define nr_rect_d_set_empty(r) (*(r) = NR_RECT_EMPTY) +#define nr_rect_l_set_empty(r) (*(r) = NR_RECT_L_EMPTY) + +#define nr_rect_d_test_empty(r) ((r) && NR_RECT_DFLS_TEST_EMPTY(r)) +#define nr_rect_l_test_empty(r) ((r) && NR_RECT_DFLS_TEST_EMPTY(r)) + +#define nr_rect_d_test_intersect(r0,r1) \ + (!nr_rect_d_test_empty(r0) && !nr_rect_d_test_empty(r1) && \ + !((r0) && (r1) && !NR_RECT_DFLS_TEST_INTERSECT(r0, r1))) +#define nr_rect_l_test_intersect(r0,r1) \ + (!nr_rect_l_test_empty(r0) && !nr_rect_l_test_empty(r1) && \ + !((r0) && (r1) && !NR_RECT_DFLS_TEST_INTERSECT(r0, r1))) + +#define nr_rect_d_point_d_test_inside(r,p) ((p) && (!(r) || (!NR_RECT_DF_TEST_EMPTY(r) && NR_RECT_DF_POINT_DF_TEST_INSIDE(r,p)))) + +/* NULL values are OK for r0 and r1, but not for d */ +NRRect *nr_rect_d_intersect(NRRect *d, NRRect const *r0, NRRect const *r1); +NRRectL *nr_rect_l_intersect(NRRectL *d, NRRectL const *r0, NRRectL const *r1); + +NRRect *nr_rect_d_union(NRRect *d, NRRect const *r0, NRRect const *r1); +NRRectL *nr_rect_l_union(NRRectL *d, NRRectL const *r0, NRRectL const *r1); + +NRRect *nr_rect_union_pt(NRRect *dst, NR::Point const &p); +NRRect *nr_rect_d_union_xy(NRRect *d, NR::Coord x, NR::Coord y); +NRRectL *nr_rect_l_union_xy(NRRectL *d, NR::ICoord x, NR::ICoord y); + +NRRect *nr_rect_d_matrix_transform(NRRect *d, NRRect const *s, NR::Matrix const &m); +NRRect *nr_rect_d_matrix_transform(NRRect *d, NRRect const *s, NRMatrix const *m); + +namespace NR { + +/** A rectangle is always aligned to the X and Y axis. This means it + * can be defined using only 4 coordinates, and determining + * intersection is very efficient. The points inside a rectangle are + * min[dim] <= _pt[dim] <= max[dim]. Emptiness, however, is defined + * as having zero area, meaning an empty rectangle may still contain + * points. Infinities are also permitted. */ +class Rect { +public: + Rect(NRRect const &r) : _min(r.x0, r.y0), _max(r.x1, r.y1) {} + Rect(Rect const &r) : _min(r._min), _max(r._max) {} + Rect(Point const &p0, Point const &p1); + + Point const &min() const { return _min; } + Point const &max() const { return _max; } + + /** returns the four corners of the rectangle in order + * (clockwise if +Y is up, anticlockwise if +Y is down) */ + Point corner(unsigned i) const; + + /** returns a vector from min to max. */ + Point dimensions() const; + + /** returns the midpoint of this rect. */ + Point midpoint() const; + + /** does this rectangle have zero area? */ + bool isEmpty() const { + return isEmpty() || isEmpty(); + } + + bool intersects(Rect const &r) const { + return intersects(r) && intersects(r); + } + bool contains(Rect const &r) const { + return contains(r) && contains(r); + } + bool contains(Point const &p) const { + return contains(p) && contains(p); + } + + double area() const { + return extent() * extent(); + } + + double maxExtent() const { + return MAX(extent(), extent()); + } + + double extent(Dim2 const axis) const { + switch (axis) { + case X: return extent(); + case Y: return extent(); + default: g_error("invalid axis value %d", (int) axis); return 0; + }; + } + + double extent(unsigned i) const throw(std::out_of_range) { + switch (i) { + case 0: return extent(); + case 1: return extent(); + default: throw std::out_of_range("Dimension out of range"); + }; + } + + /** + \brief Remove some precision from the Rect + \param places The number of decimal places left in the end + + This function just calls round on the \c _min and \c _max points. + */ + inline void round(int places = 0) { + _min.round(places); + _max.round(places); + return; + } + + /** Translates the rectangle by p. */ + void offset(Point p); + + /** Makes this rectangle large enough to include the point p. */ + void expandTo(Point p); + + /** Makes this rectangle large enough to include the rectangle r. */ + void expandTo(Rect const &r); + + inline void move_left (gdouble by) { + _min[NR::X] += by; + } + inline void move_right (gdouble by) { + _max[NR::X] += by; + } + inline void move_top (gdouble by) { + _min[NR::Y] += by; + } + inline void move_bottom (gdouble by) { + _max[NR::Y] += by; + } + + /** Returns the set of points shared by both rectangles. */ + static Maybe intersection(Rect const &a, Rect const &b); + + /** Returns the smallest rectangle that encloses both rectangles. */ + static Rect union_bounds(Rect const &a, Rect const &b); + + /** Scales the rect by s, with origin at 0, 0 */ + inline Rect operator*(double const s) const { + return Rect(s * min(), s * max()); + } + + /** Transforms the rect by m. Note that it gives correct results only for scales and translates */ + inline Rect operator*(Matrix const m) const { + return Rect(_min * m, _max * m); + } + + inline bool operator==(Rect const &in_rect) { + return ((this->min() == in_rect.min()) && (this->max() == in_rect.max())); + } + + friend inline std::ostream &operator<<(std::ostream &out_file, NR::Rect const &in_rect); + +private: + Rect() {} + + template + double extent() const { + return _max[axis] - _min[axis]; + } + + template + bool isEmpty() const { + return !( _min[axis] < _max[axis] ); + } + + template + bool intersects(Rect const &r) const { + return _max[axis] >= r._min[axis] && _min[axis] <= r._max[axis]; + } + + template + bool contains(Rect const &r) const { + return contains(r._min) && contains(r._max); + } + + template + bool contains(Point const &p) const { + return p[axis] >= _min[axis] && p[axis] <= _max[axis]; + } + + Point _min, _max; + + /* evil, but temporary */ + friend class Maybe; +}; + +/** A function to print out the rectange if sent to an output + stream. */ +inline std::ostream +&operator<<(std::ostream &out_file, NR::Rect const &in_rect) +{ + out_file << "Rectangle:\n"; + out_file << "\tMin Point -> " << in_rect.min() << "\n"; + out_file << "\tMax Point -> " << in_rect.max() << "\n"; + + return out_file; +} + +} /* namespace NR */ + + +#endif /* !LIBNR_NR_RECT_H_SEEN */ + +/* + 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 : diff --git a/src/libnr/nr-render.h b/src/libnr/nr-render.h new file mode 100644 index 000000000..84215b7a3 --- /dev/null +++ b/src/libnr/nr-render.h @@ -0,0 +1,25 @@ +#ifndef __NR_RENDER_H__ +#define __NR_RENDER_H__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * + * This code is in public domain + */ + +#include + +struct NRRenderer; + +typedef void (* NRRenderFunc) (NRRenderer *r, NRPixBlock *pb, NRPixBlock *m); + +struct NRRenderer { + NRRenderFunc render; +}; + +#define nr_render(r,pb,m) ((NRRenderer *) (r))->render ((NRRenderer *) (r), (pb), (m)) + +#endif diff --git a/src/libnr/nr-rotate-fns-test.cpp b/src/libnr/nr-rotate-fns-test.cpp new file mode 100644 index 000000000..45b2450e0 --- /dev/null +++ b/src/libnr/nr-rotate-fns-test.cpp @@ -0,0 +1,43 @@ +#include +#include + +#include +#include + +int main(int argc, char *argv[]) +{ + utest_start("rotate-fns"); + + UTEST_TEST("rotate_degrees") { + double const d[] = { + 0, 90, 180, 270, 360, 45, 45.01, 44.99, 134, 135, 136, 314, 315, 317, 359, 361 + }; + for (unsigned i = 0; i < G_N_ELEMENTS(d); ++i) { + double const degrees = d[i]; + NR::rotate const rot(rotate_degrees(degrees)); + NR::rotate const rot_approx( M_PI * ( degrees / 180. ) ); + UTEST_ASSERT(rotate_equalp(rot, rot_approx, 1e-12)); + + NR::rotate const rot_inv(rotate_degrees(-degrees)); + NR::rotate const rot_compl(rotate_degrees(360 - degrees)); + UTEST_ASSERT(rotate_equalp(rot_inv, rot_compl, 1e-12)); + + UTEST_ASSERT(!rotate_equalp(rot, rotate_degrees(degrees + 1), 1e-5)); + } + } + + return ( utest_end() + ? EXIT_SUCCESS + : EXIT_FAILURE ); +} + +/* + 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/libnr/nr-rotate-fns-test.h b/src/libnr/nr-rotate-fns-test.h new file mode 100644 index 000000000..e3bfe3043 --- /dev/null +++ b/src/libnr/nr-rotate-fns-test.h @@ -0,0 +1,54 @@ +#include + +#include +#include + +#include + +class NrRotateFnsTest : public CxxTest::TestSuite +{ +public: + + NrRotateFnsTest() + { + } + virtual ~NrRotateFnsTest() {} + +// createSuite and destroySuite get us per-suite setup and teardown +// without us having to worry about static initialization order, etc. + static NrRotateFnsTest *createSuite() { return new NrRotateFnsTest(); } + static void destroySuite( NrRotateFnsTest *suite ) { delete suite; } + + + + void testRotateDegrees(void) + { + double const d[] = { + 0, 90, 180, 270, 360, 45, 45.01, 44.99, 134, 135, 136, 314, 315, 317, 359, 361 + }; + for ( unsigned i = 0; i < G_N_ELEMENTS(d); ++i ) { + double const degrees = d[i]; + NR::rotate const rot(rotate_degrees(degrees)); + NR::rotate const rot_approx( M_PI * ( degrees / 180. ) ); + TS_ASSERT( rotate_equalp(rot, rot_approx, 1e-12) ); + + NR::rotate const rot_inv(rotate_degrees(-degrees)); + NR::rotate const rot_compl(rotate_degrees(360 - degrees)); + TS_ASSERT( rotate_equalp(rot_inv, rot_compl, 1e-12) ); + + TS_ASSERT( !rotate_equalp(rot, rotate_degrees(degrees + 1), 1e-5) ); + } + } + +}; + +/* + 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 : diff --git a/src/libnr/nr-rotate-fns.cpp b/src/libnr/nr-rotate-fns.cpp new file mode 100644 index 000000000..f2669c20c --- /dev/null +++ b/src/libnr/nr-rotate-fns.cpp @@ -0,0 +1,66 @@ +/** \file + * Functions to/from NR::rotate. + */ +#include +#include "libnr/nr-rotate-ops.h" +#include "libnr/nr-rotate-fns.h" + +/** + * Returns a rotation matrix corresponding by the specified angle about the origin. + * + * Angle direction in Inkscape code: If you use the traditional mathematics convention that y + * increases upwards, then positive angles are anticlockwise as per the mathematics convention. If + * you take the common non-mathematical convention that y increases downwards, then positive angles + * are clockwise, as is common outside of mathematics. + */ +NR::rotate +rotate_degrees(double degrees) +{ + if (degrees < 0) { + return rotate_degrees(-degrees).inverse(); + } + + double const degrees0 = degrees; + if (degrees >= 360) { + degrees = fmod(degrees, 360); + } + + NR::rotate ret(1., 0.); + + if (degrees >= 180) { + NR::rotate const rot180(-1., 0.); + degrees -= 180; + ret = rot180; + } + + if (degrees >= 90) { + NR::rotate const rot90(0., 1.); + degrees -= 90; + ret *= rot90; + } + + if (degrees == 45) { + NR::rotate const rot45(M_SQRT1_2, M_SQRT1_2); + ret *= rot45; + } else { + double const radians = M_PI * ( degrees / 180 ); + ret *= NR::rotate(cos(radians), sin(radians)); + } + + NR::rotate const raw_ret( M_PI * ( degrees0 / 180 ) ); + g_return_val_if_fail(rotate_equalp(ret, raw_ret, 1e-8), + raw_ret); + return ret; +} + + +/* + 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/libnr/nr-rotate-fns.h b/src/libnr/nr-rotate-fns.h new file mode 100644 index 000000000..bd075114c --- /dev/null +++ b/src/libnr/nr-rotate-fns.h @@ -0,0 +1,29 @@ +#ifndef SEEN_NR_ROTATE_FNS_H +#define SEEN_NR_ROTATE_FNS_H + +/** \file + * Declarations for rotation functions. + */ + +#include +#include + +inline bool rotate_equalp(NR::rotate const &a, NR::rotate const &b, double const eps) +{ + return point_equalp(a.vec, b.vec, eps); +} + +NR::rotate rotate_degrees(double degrees); + +#endif /* !SEEN_NR_ROTATE_FNS_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 : diff --git a/src/libnr/nr-rotate-matrix-ops.cpp b/src/libnr/nr-rotate-matrix-ops.cpp new file mode 100644 index 000000000..dd3851643 --- /dev/null +++ b/src/libnr/nr-rotate-matrix-ops.cpp @@ -0,0 +1,19 @@ +#include + +NR::Matrix +operator*(NR::rotate const &a, NR::Matrix const &b) +{ + return NR::Matrix(a) * b; +} + + +/* + 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 : diff --git a/src/libnr/nr-rotate-matrix-ops.h b/src/libnr/nr-rotate-matrix-ops.h new file mode 100644 index 000000000..d2f0eadba --- /dev/null +++ b/src/libnr/nr-rotate-matrix-ops.h @@ -0,0 +1,21 @@ +#ifndef SEEN_LIBNR_NR_ROTATE_MATRIX_OPS_H +#define SEEN_LIBNR_NR_ROTATE_MATRIX_OPS_H + +#include + + +NR::Matrix operator*(NR::rotate const &a, NR::Matrix const &b); + + +#endif /* !SEEN_LIBNR_NR_ROTATE_MATRIX_OPS_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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-rotate-ops.h b/src/libnr/nr-rotate-ops.h new file mode 100644 index 000000000..4b60b9d0c --- /dev/null +++ b/src/libnr/nr-rotate-ops.h @@ -0,0 +1,43 @@ +#ifndef SEEN_NR_ROTATE_OPS_H +#define SEEN_NR_ROTATE_OPS_H +#include + +namespace NR { + +inline Point operator*(Point const &v, rotate const &r) +{ + return Point(r.vec[X] * v[X] - r.vec[Y] * v[Y], + r.vec[Y] * v[X] + r.vec[X] * v[Y]); +} + +inline rotate operator*(rotate const &a, rotate const &b) +{ + return rotate( a.vec * b ); +} + +inline rotate &rotate::operator*=(rotate const &b) +{ + *this = *this * b; + return *this; +} + +inline rotate operator/(rotate const &numer, rotate const &denom) +{ + return numer * denom.inverse(); +} + +} /* namespace NR */ + + +#endif /* !SEEN_NR_ROTATE_OPS_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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-rotate-test.cpp b/src/libnr/nr-rotate-test.cpp new file mode 100644 index 000000000..63b9d1737 --- /dev/null +++ b/src/libnr/nr-rotate-test.cpp @@ -0,0 +1,89 @@ +#include +#include +#include +#include /* identity, matrix_equalp */ +#include +#include +#include +#include +#include +#include +using NR::X; +using NR::Y; + +int main(int argc, char *argv[]) +{ + utest_start("rotate"); + + NR::Matrix const m_id(NR::identity()); + NR::rotate const r_id(0.0); + NR::rotate const rot234(.234); + UTEST_TEST("constructors, comparisons") { + UTEST_ASSERT( r_id == r_id ); + UTEST_ASSERT( rot234 == rot234 ); + UTEST_ASSERT( rot234 != r_id ); + UTEST_ASSERT( r_id == NR::rotate(NR::Point(1.0, 0.0)) ); + UTEST_ASSERT( NR::Matrix(r_id) == m_id ); + UTEST_ASSERT( NR::Matrix(r_id).test_identity() ); + + UTEST_ASSERT(rotate_equalp(rot234, NR::rotate(NR::Point(cos(.234), sin(.234))), 1e-12)); + } + + UTEST_TEST("operator=") { + NR::rotate rot234_eq(r_id); + rot234_eq = rot234; + UTEST_ASSERT( rot234 == rot234_eq ); + UTEST_ASSERT( rot234_eq != r_id ); + } + + UTEST_TEST("inverse") { + UTEST_ASSERT( r_id.inverse() == r_id ); + UTEST_ASSERT( rot234.inverse() == NR::rotate(-.234) ); + } + + NR::Point const b(-2.0, 3.0); + NR::rotate const rot180(NR::Point(-1.0, 0.0)); + UTEST_TEST("operator*(Point, rotate)") { + UTEST_ASSERT( b * r_id == b ); + UTEST_ASSERT( b * rot180 == -b ); + UTEST_ASSERT( b * rot234 == b * NR::Matrix(rot234) ); + UTEST_ASSERT(point_equalp(b * NR::rotate(M_PI / 2), + NR::rot90(b), + 1e-14)); + UTEST_ASSERT( b * rotate_degrees(90.) == NR::rot90(b) ); + } + + UTEST_TEST("operator*(rotate, rotate)") { + UTEST_ASSERT( r_id * r_id == r_id ); + UTEST_ASSERT( rot180 * rot180 == r_id ); + UTEST_ASSERT( rot234 * r_id == rot234 ); + UTEST_ASSERT( r_id * rot234 == rot234 ); + UTEST_ASSERT(rotate_equalp(rot234 * rot234.inverse(), r_id, 1e-14)); + UTEST_ASSERT(rotate_equalp(rot234.inverse() * rot234, r_id, 1e-14)); + UTEST_ASSERT(rotate_equalp(( NR::rotate(0.25) * NR::rotate(.5) ), + NR::rotate(.75), + 1e-10)); + } + + UTEST_TEST("operator/(rotate, rotate)") { + UTEST_ASSERT( rot234 / r_id == rot234 ); + UTEST_ASSERT( rot234 / rot180 == rot234 * rot180 ); + UTEST_ASSERT(rotate_equalp(rot234 / rot234, r_id, 1e-14)); + UTEST_ASSERT(rotate_equalp(r_id / rot234, rot234.inverse(), 1e-14)); + } + + return ( utest_end() + ? EXIT_SUCCESS + : EXIT_FAILURE ); +} + +/* + 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 : diff --git a/src/libnr/nr-rotate-test.h b/src/libnr/nr-rotate-test.h new file mode 100644 index 000000000..252b25022 --- /dev/null +++ b/src/libnr/nr-rotate-test.h @@ -0,0 +1,112 @@ +#include + +#include +#include +#include +#include /* identity, matrix_equalp */ +#include +#include +#include +#include +using NR::X; +using NR::Y; + + +class NrRotateTest : public CxxTest::TestSuite +{ +public: + + NrRotateTest() : + m_id( NR::identity() ), + r_id( 0.0 ), + rot234( .234 ), + b( -2.0, 3.0 ), + rot180( NR::Point(-1.0, 0.0) ) + { + } + virtual ~NrRotateTest() {} + +// createSuite and destroySuite get us per-suite setup and teardown +// without us having to worry about static initialization order, etc. + static NrRotateTest *createSuite() { return new NrRotateTest(); } + static void destroySuite( NrRotateTest *suite ) { delete suite; } + + NR::Matrix const m_id; + NR::rotate const r_id; + NR::rotate const rot234; + NR::Point const b; + NR::rotate const rot180; + + + + + void testCtorsCompares(void) + { + TS_ASSERT_EQUALS( r_id, r_id ); + TS_ASSERT_EQUALS( rot234, rot234 ); + TS_ASSERT_DIFFERS( rot234, r_id ); + TS_ASSERT_EQUALS( r_id, NR::rotate(NR::Point(1.0, 0.0)) ); + TS_ASSERT_EQUALS( NR::Matrix(r_id), m_id ); + TS_ASSERT( NR::Matrix(r_id).test_identity() ); + + TS_ASSERT(rotate_equalp(rot234, NR::rotate(NR::Point(cos(.234), sin(.234))), 1e-12)); + } + + void testAssignmentOp(void) + { + NR::rotate rot234_eq(r_id); + rot234_eq = rot234; + TS_ASSERT_EQUALS( rot234, rot234_eq ); + TS_ASSERT_DIFFERS( rot234_eq, r_id ); + } + + void testInverse(void) + { + TS_ASSERT_EQUALS( r_id.inverse(), r_id ); + TS_ASSERT_EQUALS( rot234.inverse(), NR::rotate(-.234) ); + } + + void testOpStarPointRotate(void) + { + TS_ASSERT_EQUALS( b * r_id, b ); + TS_ASSERT_EQUALS( b * rot180, -b ); + TS_ASSERT_EQUALS( b * rot234, b * NR::Matrix(rot234) ); + TS_ASSERT(point_equalp(b * NR::rotate(M_PI / 2), + NR::rot90(b), + 1e-14)); + TS_ASSERT_EQUALS( b * rotate_degrees(90.), NR::rot90(b) ); + } + + void testOpStarRotateRotate(void) + { + TS_ASSERT_EQUALS( r_id * r_id, r_id ); + TS_ASSERT_EQUALS( rot180 * rot180, r_id ); + TS_ASSERT_EQUALS( rot234 * r_id, rot234 ); + TS_ASSERT_EQUALS( r_id * rot234, rot234 ); + TS_ASSERT( rotate_equalp(rot234 * rot234.inverse(), r_id, 1e-14) ); + TS_ASSERT( rotate_equalp(rot234.inverse() * rot234, r_id, 1e-14) ); + TS_ASSERT( rotate_equalp(( NR::rotate(0.25) * NR::rotate(.5) ), + NR::rotate(.75), + 1e-10) ); + } + + void testOpDivRotateRotate(void) + { + TS_ASSERT_EQUALS( rot234 / r_id, rot234 ); + TS_ASSERT_EQUALS( rot234 / rot180, rot234 * rot180 ); + TS_ASSERT( rotate_equalp(rot234 / rot234, r_id, 1e-14) ); + TS_ASSERT( rotate_equalp(r_id / rot234, rot234.inverse(), 1e-14) ); + } + +}; + +/* + 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 : diff --git a/src/libnr/nr-rotate.h b/src/libnr/nr-rotate.h new file mode 100644 index 000000000..051372ce6 --- /dev/null +++ b/src/libnr/nr-rotate.h @@ -0,0 +1,66 @@ +#ifndef SEEN_NR_ROTATE_H +#define SEEN_NR_ROTATE_H + +/** \file + * Rotation about the origin. + */ + +#include +#include +#include + +namespace NR { + +/** Notionally an NR::Matrix corresponding to rotation about the origin. + Behaves like NR::Matrix for multiplication. +**/ +class rotate { +public: + Point vec; + +private: + rotate(); + +public: + explicit rotate(Coord theta); + explicit rotate(Point const &p) : vec(p) {} + explicit rotate(Coord const x, Coord const y) : vec(x, y) {} + + bool operator==(rotate const &o) const { + return vec == o.vec; + } + + bool operator!=(rotate const &o) const { + return vec != o.vec; + } + + inline rotate &operator*=(rotate const &b); + /* Defined in nr-rotate-ops.h. */ + + rotate inverse() const { + /** \todo + * In the usual case that vec is a unit vector (within rounding error), + * dividing by len_sq is either a noop or numerically harmful. + * Make a unit_rotate class (or the like) that knows its length is 1. + */ + double const len_sq = dot(vec, vec); + return rotate( Point(vec[X], -vec[Y]) + / len_sq ); + } +}; + +} /* namespace NR */ + + +#endif /* !SEEN_NR_ROTATE_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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-scale-matrix-ops.cpp b/src/libnr/nr-scale-matrix-ops.cpp new file mode 100644 index 000000000..ce5120079 --- /dev/null +++ b/src/libnr/nr-scale-matrix-ops.cpp @@ -0,0 +1,26 @@ +#include "libnr/nr-matrix-ops.h" + +NR::Matrix +operator*(NR::scale const &s, NR::Matrix const &m) +{ + using NR::X; using NR::Y; + NR::Matrix ret(m); + ret[0] *= s[X]; + ret[1] *= s[X]; + ret[2] *= s[Y]; + ret[3] *= s[Y]; + assert_close( ret, NR::Matrix(s) * m ); + return ret; +} + + +/* + 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 : diff --git a/src/libnr/nr-scale-matrix-ops.h b/src/libnr/nr-scale-matrix-ops.h new file mode 100644 index 000000000..bf1b498c9 --- /dev/null +++ b/src/libnr/nr-scale-matrix-ops.h @@ -0,0 +1,13 @@ +#ifndef SEEN_LIBNR_NR_SCALE_MATRIX_OPS_H +#define SEEN_LIBNR_NR_SCALE_MATRIX_OPS_H +/** \file + * Declarations (and definition if inline) of operator + * blah (NR::scale, NR::Matrix). + */ + +#include "libnr/nr-forward.h" + +NR::Matrix operator*(NR::scale const &s, NR::Matrix const &m); + + +#endif /* !SEEN_LIBNR_NR_SCALE_MATRIX_OPS_H */ diff --git a/src/libnr/nr-scale-ops.h b/src/libnr/nr-scale-ops.h new file mode 100644 index 000000000..da1fea64c --- /dev/null +++ b/src/libnr/nr-scale-ops.h @@ -0,0 +1,40 @@ +#ifndef SEEN_NR_SCALE_OPS_H +#define SEEN_NR_SCALE_OPS_H + +#include + +namespace NR { + +inline Point operator*(Point const &p, scale const &s) +{ + return Point(p[X] * s[X], + p[Y] * s[Y]); +} + +inline scale operator*(scale const &a, scale const &b) +{ + return scale(a[X] * b[X], + a[Y] * b[Y]); +} + +inline scale operator/(scale const &numer, scale const &denom) +{ + return scale(numer[X] / denom[X], + numer[Y] / denom[Y]); +} + +} /* namespace NR */ + + +#endif /* !SEEN_NR_SCALE_OPS_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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-scale-test.cpp b/src/libnr/nr-scale-test.cpp new file mode 100644 index 000000000..cc255d02a --- /dev/null +++ b/src/libnr/nr-scale-test.cpp @@ -0,0 +1,71 @@ +#include +#include +#include +using NR::X; +using NR::Y; + +int main(int argc, char *argv[]) +{ + utest_start("NR::scale"); + + NR::scale const sa(1.5, 2.0); + UTEST_TEST("x,y constructor and operator[] const") { + UTEST_ASSERT(sa[X] == 1.5); + UTEST_ASSERT(sa[Y] == 2.0); + UTEST_ASSERT(sa[0u] == 1.5); + UTEST_ASSERT(sa[1u] == 2.0); + } + + NR::Point const b(-2.0, 3.0); + NR::scale const sb(b); + + UTEST_TEST("copy constructor, operator==, operator!=") { + NR::scale const sa_copy(sa); + UTEST_ASSERT( sa == sa_copy ); + UTEST_ASSERT(!( sa != sa_copy )); + UTEST_ASSERT( sa != sb ); + } + + UTEST_TEST("operator=") { + NR::scale sa_eq(sb); + sa_eq = sa; + UTEST_ASSERT( sa == sa_eq ); + } + + UTEST_TEST("point constructor") { + UTEST_ASSERT(sb[X] == b[X]); + UTEST_ASSERT(sb[Y] == b[Y]); + } + + UTEST_TEST("operator*(Point, scale)") { + NR::Point const ab( b * sa ); + UTEST_ASSERT( ab == NR::Point(-3.0, 6.0) ); + } + + UTEST_TEST("operator*(scale, scale)") { + NR::scale const sab( sa * sb ); + UTEST_ASSERT( sab == NR::scale(-3.0, 6.0) ); + } + + UTEST_TEST("operator/(scale, scale)") { + NR::scale const sa_b( sa / sb ); + NR::scale const exp_sa_b(-0.75, 2./3.); + UTEST_ASSERT( sa_b[0] == exp_sa_b[0] ); + UTEST_ASSERT( fabs( sa_b[1] - exp_sa_b[1] ) < 1e-10 ); + } + + return ( utest_end() + ? EXIT_SUCCESS + : EXIT_FAILURE ); +} + +/* + 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 : diff --git a/src/libnr/nr-scale-test.h b/src/libnr/nr-scale-test.h new file mode 100644 index 000000000..dc5e6ef28 --- /dev/null +++ b/src/libnr/nr-scale-test.h @@ -0,0 +1,92 @@ +#include + +#include +#include +using NR::X; +using NR::Y; + +class NrScaleTest : public CxxTest::TestSuite +{ +public: + + NrScaleTest() : + sa( 1.5, 2.0 ), + b( -2.0, 3.0 ), + sb( b ) + { + } + virtual ~NrScaleTest() {} + +// createSuite and destroySuite get us per-suite setup and teardown +// without us having to worry about static initialization order, etc. + static NrScaleTest *createSuite() { return new NrScaleTest(); } + static void destroySuite( NrScaleTest *suite ) { delete suite; } + + NR::scale const sa; + NR::Point const b; + NR::scale const sb; + + + + void testXY_CtorArrayOperator(void) + { + TS_ASSERT_EQUALS( sa[X], 1.5 ); + TS_ASSERT_EQUALS( sa[Y], 2.0 ); + TS_ASSERT_EQUALS( sa[0u], 1.5 ); + TS_ASSERT_EQUALS( sa[1u], 2.0 ); + } + + + void testCopyCtor_AssignmentOp_NotEquals(void) + { + NR::scale const sa_copy(sa); + TS_ASSERT_EQUALS( sa, sa_copy ); + TS_ASSERT(!( sa != sa_copy )); + TS_ASSERT( sa != sb ); + } + + void testAssignmentOp(void) + { + NR::scale sa_eq(sb); + sa_eq = sa; + TS_ASSERT_EQUALS( sa, sa_eq ); + } + + void testPointCtor(void) + { + TS_ASSERT_EQUALS( sb[X], b[X] ); + TS_ASSERT_EQUALS( sb[Y], b[Y] ); + } + + void testOpStarPointScale(void) + { + NR::Point const ab( b * sa ); + TS_ASSERT_EQUALS( ab, NR::Point(-3.0, 6.0) ); + } + + void testOpStarScaleScale(void) + { + NR::scale const sab( sa * sb ); + TS_ASSERT_EQUALS( sab, NR::scale(-3.0, 6.0) ); + } + + void testOpDivScaleScale(void) + { + NR::scale const sa_b( sa / sb ); + NR::scale const exp_sa_b(-0.75, 2./3.); + TS_ASSERT_EQUALS( sa_b[0], exp_sa_b[0] ); +// TS_ASSERT_EQUALS( fabs( sa_b[1] - exp_sa_b[1] ) < 1e-10 ); + TS_ASSERT_DELTA( sa_b[1], exp_sa_b[1], 1e-10 ); + } +}; + +/* + 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 : diff --git a/src/libnr/nr-scale-translate-ops.cpp b/src/libnr/nr-scale-translate-ops.cpp new file mode 100644 index 000000000..911c92e5b --- /dev/null +++ b/src/libnr/nr-scale-translate-ops.cpp @@ -0,0 +1,19 @@ +#include "libnr/nr-matrix-translate-ops.h" + +NR::Matrix +operator*(NR::scale const &s, NR::translate const &t) +{ + return NR::Matrix(s) * t; +} + + +/* + 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 : diff --git a/src/libnr/nr-scale-translate-ops.h b/src/libnr/nr-scale-translate-ops.h new file mode 100644 index 000000000..2f6f23c2c --- /dev/null +++ b/src/libnr/nr-scale-translate-ops.h @@ -0,0 +1,20 @@ +#ifndef SEEN_LIBNR_NR_SCALE_TRANSLATE_OPS_H +#define SEEN_LIBNR_NR_SCALE_TRANSLATE_OPS_H + +#include "libnr/nr-forward.h" + +NR::Matrix operator*(NR::scale const &s, NR::translate const &t); + + +#endif /* !SEEN_LIBNR_NR_SCALE_TRANSLATE_OPS_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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-scale.h b/src/libnr/nr-scale.h new file mode 100644 index 000000000..c9de871de --- /dev/null +++ b/src/libnr/nr-scale.h @@ -0,0 +1,50 @@ +#ifndef SEEN_NR_SCALE_H +#define SEEN_NR_SCALE_H +#include +#include + +namespace NR { + +class scale { +private: + Point _p; + +private: + scale(); + +public: + explicit scale(Point const &p) : _p(p) {} + scale(double const x, double const y) : _p(x, y) {} + explicit scale(double const s) : _p(s, s) {} + inline Coord operator[](Dim2 const d) const { return _p[d]; } + inline Coord operator[](unsigned const d) const { return _p[d]; } + inline Coord &operator[](Dim2 const d) { return _p[d]; } + inline Coord &operator[](unsigned const d) { return _p[d]; } + + bool operator==(scale const &o) const { + return _p == o._p; + } + + bool operator!=(scale const &o) const { + return _p != o._p; + } + scale inverse() const { + return scale(1/_p[0], 1/_p[1]); + } +}; + +} /* namespace NR */ + + +#endif /* !SEEN_NR_SCALE_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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-svp-private.h b/src/libnr/nr-svp-private.h new file mode 100644 index 000000000..dbddcd7ca --- /dev/null +++ b/src/libnr/nr-svp-private.h @@ -0,0 +1,30 @@ +#ifndef __NR_SVP_PRIVATE_H__ +#define __NR_SVP_PRIVATE_H__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * + * This code is in public domain + */ + +#include + +#define NR_QUANT_X 16.0F +#define NR_QUANT_Y 16.0F +#define NR_COORD_X_FROM_ART(v) (floor (NR_QUANT_X * (v) + 0.5F) / NR_QUANT_X) +#define NR_COORD_Y_FROM_ART(v) (floor (NR_QUANT_Y * (v) + 0.5F) / NR_QUANT_Y) +#define NR_COORD_TO_ART(v) (v) + +/* NRVertex */ + +NRVertex *nr_vertex_new (void); +NRVertex *nr_vertex_new_xy (NR::Coord x, NR::Coord y); +void nr_vertex_free_one (NRVertex *v); +void nr_vertex_free_list (NRVertex *v); + +NRVertex *nr_vertex_reverse_list (NRVertex *v); + +#endif diff --git a/src/libnr/nr-svp-render.cpp b/src/libnr/nr-svp-render.cpp new file mode 100644 index 000000000..0c3b391d5 --- /dev/null +++ b/src/libnr/nr-svp-render.cpp @@ -0,0 +1,615 @@ +#define __NR_SVP_RENDER_C__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * + * This code is in public domain + */ + +#define NR_SVPSEG_Y0(s,i) ((s)->points[(s)->segments[i].start].y) +#define NR_SVPSEG_Y1(s,i) ((s)->points[(s)->segments[i].start + (s)->segments[i].length - 1].y) + +#define noNR_VERBOSE + +#include "nr-svp-render.h" + +static void nr_svp_render (NRSVP *svp, unsigned char *px, unsigned int bpp, unsigned int rs, int x0, int y0, int x1, int y1, + void (* run) (unsigned char *px, int len, int c0_24, int s0_24, void *data), void *data); + +static void nr_svp_run_A8_OR (unsigned char *d, int len, int c0_24, int s0_24, void *data); + +/* Renders graymask of svl into buffer */ + +void +nr_pixblock_render_svp_mask_or (NRPixBlock *d, NRSVP *svp) +{ + nr_svp_render (svp, NR_PIXBLOCK_PX (d), 1, d->rs, + d->area.x0, d->area.y0, d->area.x1, d->area.y1, + nr_svp_run_A8_OR, NULL); +} + +static void +nr_svp_run_A8_OR (unsigned char *d, int len, int c0_24, int s0_24, void *data) +{ + if ((c0_24 >= 0xff0000) && (s0_24 == 0x0)) { + /* Simple copy */ + while (len > 0) { + d[0] = 255; + d += 1; + len -= 1; + } + } else { + while (len > 0) { + unsigned int ca, da; + /* Draw */ + ca = c0_24 >> 16; + da = 65025 - (255 - ca) * (255 - d[0]); + d[0] = (da + 127) / 255; + d += 1; + c0_24 += s0_24; + c0_24 = CLAMP (c0_24, 0, 16777216); + len -= 1; + } + } +} + + +struct NRRun { + NRRun *next; + NR::Coord x0, y0, x1, y1; + float step; + float final; + NR::Coord x; + NR::Coord value; +}; + +static NRRun *nr_run_new (NR::Coord x0, NR::Coord y0, NR::Coord x1, NR::Coord y1, int wind); +static NRRun *nr_run_free_one (NRRun *run); +static void nr_run_free_list (NRRun *run); +static NRRun *nr_run_insort (NRRun *start, NRRun *run); + +struct NRSlice { + NRSlice *next; + int wind; + NRPoint *points; + unsigned int current; + unsigned int last; + NR::Coord x; + NR::Coord y; + NR::Coord stepx; +}; + +static NRSlice *nr_slice_new (int wind, NRPoint *points, unsigned int length, NR::Coord y); +static NRSlice *nr_slice_free_one (NRSlice *s); +static void nr_slice_free_list (NRSlice *s); +static NRSlice *nr_slice_insort (NRSlice *start, NRSlice *slice); +static int nr_slice_compare (NRSlice *l, NRSlice *r); + +static void +nr_svp_render (NRSVP *svp, unsigned char *px, unsigned int bpp, unsigned int rs, int iX0, int iY0, int iX1, int iY1, + void (* run) (unsigned char *px, int len, int c0_24, int s0_24, void *data), void *data) +{ + NR::Coord dX0, dY0, dX1, dY1; + NRSlice *slices; + unsigned int sidx; + int ystart; + unsigned char *rowbuffer; + int iy0; + + if (!svp || !svp->length) return; + + /* Find starting pixel row */ + /* g_assert (svl->bbox.y0 == svl->vertex->y); */ + sidx = 0; + while (NR_SVPSEG_IS_FLAT (svp, sidx) && (sidx < svp->length)) sidx += 1; + if (sidx >= svp->length) return; + ystart = (int) floor (NR_SVPSEG_Y0 (svp, sidx)); + if (ystart > iY0) { + if (ystart >= iY1) return; + px += (ystart - iY0) * rs; + iY0 = ystart; + } + + dX0 = iX0; + dY0 = iY0; + dX1 = iX1; + dY1 = iY1; + + /* Construct initial slice list */ + slices = NULL; + while (sidx < svp->length) { + if (!NR_SVPSEG_IS_FLAT (svp, sidx)) { + NRSVPSegment *seg; + /* It is real renderable segment */ + /* Postpone if starts above initial slice */ + if (NR_SVPSEG_Y0 (svp, sidx) > dY0) break; + seg = svp->segments + sidx; + if (seg->wind && (NR_SVPSEG_Y1 (svp, sidx) > dY0)) { + /* We are renderable and cross initial slice */ + NRSlice *newslice; + newslice = nr_slice_new (seg->wind, svp->points + seg->start, seg->length, dY0); + slices = nr_slice_insort (slices, newslice); + } + } + sidx += 1; + } + if (!slices && (sidx >= svp->length)) return; + + /* Row buffer */ + /* fixme: not needed */ + rowbuffer = px; + + /* Main iteration */ + for (iy0 = iY0; iy0 < iY1; iy0 += 1) { + NR::Coord dy0, dy1; + NRSlice *ss, *cs; + NRRun *runs; + int xstart; + float globalval; + unsigned char *d; + int ix0; + + dy0 = iy0; + dy1 = dy0 + 1.0; + + /* Add possible new svls to slice list */ + while (sidx < svp->length) { + if (!NR_SVPSEG_IS_FLAT (svp, sidx)) { + NRSVPSegment *seg; + /* It is real renderable segment */ + /* Postpone if starts above ending slice */ + if (NR_SVPSEG_Y0 (svp, sidx) > dy1) break; + seg = svp->segments + sidx; + if (seg->wind) { + NR::Coord y; + NRSlice *newslice; + /* We are renderable */ + /* fixme: we should use safely nsvl->vertex->y here */ + y = MAX (dy0, NR_SVPSEG_Y0 (svp, sidx)); + newslice = nr_slice_new (seg->wind, svp->points + seg->start, seg->length, y); + slices = nr_slice_insort (slices, newslice); + } + } + sidx += 1; + } + /* Construct runs, stretching slices */ + /* fixme: This step can be optimized by continuing long runs and adding only new ones (Lauris) */ + runs = NULL; + ss = NULL; + cs = slices; + while (cs) { + /* g_assert (cs->y >= y0); */ + /* g_assert (cs->y < (y + 1)); */ + while ((cs->y < dy1) && (cs->current < cs->last)) { + NR::Coord rx0, ry0, rx1, ry1; + NRRun * newrun; + rx0 = cs->x; + ry0 = cs->y; + if (cs->points[cs->current + 1].y > dy1) { + /* The same slice continues */ + rx1 = rx0 + (dy1 - ry0) * cs->stepx; + ry1 = dy1; + cs->x = rx1; + cs->y = ry1; + } else { + /* Subpixel height run */ + cs->current += 1; + rx1 = cs->points[cs->current].x; + ry1 = cs->points[cs->current].y; + cs->x = rx1; + cs->y = ry1; + if (cs->current < cs->last) { + cs->stepx = (cs->points[cs->current + 1].x - rx1) / (cs->points[cs->current + 1].y - ry1); + } + } + newrun = nr_run_new (rx0, ry0, rx1, ry1, cs->wind); + /* fixme: we should use walking forward/backward instead */ + runs = nr_run_insort (runs, newrun); + } + if (cs->current < cs->last) { + ss = cs; + cs = cs->next; + } else { + /* Slice is exhausted */ + cs = nr_slice_free_one (cs); + if (ss) { + ss->next = cs; + } else { + slices = cs; + } + } + } + /* Slices are expanded to next scanline */ + /* Run list is generated */ + /* Globalval is the sum of all finished runs */ + globalval = 0.0; + if ((runs) && (dX0 < runs->x0)) { + /* First run starts right from x0 */ + xstart = (int) floor (runs->x0); + } else { + NRRun *sr, *cr; + /* First run starts left from x0 */ + xstart = iX0; + sr = NULL; + cr = runs; + while ((cr) && (cr->x0 < dX0)) { + if (cr->x1 <= dX0) { + globalval += cr->final; + /* Remove exhausted current run */ + cr = nr_run_free_one (cr); + if (sr) { + sr->next = cr; + } else { + runs = cr; + } + } else { + cr->x = dX0; + cr->value = (dX0 - cr->x0) * cr->step; + sr = cr; + cr = cr->next; + } + } + } + + /* Running buffer */ + d = rowbuffer + bpp * (xstart - iX0); + + for (ix0 = xstart; (runs) && (ix0 < iX1); ix0++) { + NR::Coord dx0, dx1; + int ix1; + NRRun *sr, *cr; + float localval; + unsigned int fill; + float fillstep; + int rx1; + int c24; + + dx0 = ix0; + dx1 = dx0 + 1.0; + ix1 = ix0 + 1; + + /* process runs */ + localval = globalval; + sr = NULL; + cr = runs; + fill = TRUE; + fillstep = 0.0; + rx1 = iX1; + while ((cr) && (cr->x0 < dx1)) { + if (cr->x1 <= dx1) { + /* Run ends here */ + /* No fill */ + fill = FALSE; + /* Continue with final value */ + globalval += cr->final; + /* Add initial trapezoid */ + localval += (float) (0.5 * (cr->x1 - cr->x) * (cr->value + cr->final)); + /* Add final rectangle */ + localval += (float) ((dx1 - cr->x1) * cr->final); + /* Remove exhausted run */ + cr = nr_run_free_one (cr); + if (sr) { + sr->next = cr; + } else { + runs = cr; + } + } else { + /* Run continues through xnext */ + if (fill) { + if (cr->x0 > ix0) { + fill = FALSE; + } else { + rx1 = MIN (rx1, (int) floor (cr->x1)); + fillstep += cr->step; + } + } + localval += (float) ((dx1 - cr->x) * (cr->value + (dx1 - cr->x) * cr->step / 2.0)); + cr->x = dx1; + cr->value = (dx1 - cr->x0) * cr->step; + sr = cr; + cr = cr->next; + } + } + if (fill) { + if (cr) rx1 = MIN (rx1, (int) floor (cr->x0)); + } +#ifdef NR_VERBOSE + if ((localval < -0.01) || (localval > 1.01)) { + printf ("Weird localval %g : gv %g\n", localval, globalval); // localizing ok + } +#endif + localval = CLAMP (localval, 0.0F, 1.0F); + c24 = (int) floor (16777215 * localval + 0.5); + if (fill && (rx1 > ix1)) { + NRRun *r; + int s24; + s24 = (int) floor (16777215 * fillstep + 0.5); + if ((s24 != 0) || (c24 > 65535)) { + run (d, rx1 - ix0, c24, s24, data); + } + /* We have to rewind run positions as well */ + for (r = runs; r && (r->x0 < dx1); r = r->next) { + r->x = rx1; + r->value = (rx1 - r->x0) * r->step; + } + d += bpp * (rx1 - ix0); + ix0 = rx1 - 1; + } else { + run (d, 1, c24, 0, data); + d += bpp; + } + } + nr_run_free_list (runs); + rowbuffer += rs; + } + if (slices) nr_slice_free_list (slices); +} + +/* Slices */ + +#define NR_SLICE_ALLOC_SIZE 32 +static NRSlice *ffslice = NULL; + +static NRSlice * +nr_slice_new (int wind, NRPoint *points, unsigned int length, NR::Coord y) +{ + NRSlice *s; + NRPoint *p; + + /* g_assert (svl); */ + /* g_assert (svl->vertex); */ + /* fixme: not sure, whether correct */ + /* g_assert (y == NR_COORD_SNAP (y)); */ + /* Slices startpoints are included, endpoints excluded */ + /* g_return_val_if_fail (y >= svl->bbox.y0, NULL); */ + /* g_return_val_if_fail (y < svl->bbox.y1, NULL); */ + + s = ffslice; + + if (s == NULL) { + int i; + s = nr_new (NRSlice, NR_SLICE_ALLOC_SIZE); + for (i = 1; i < (NR_SLICE_ALLOC_SIZE - 1); i++) s[i].next = &s[i + 1]; + s[NR_SLICE_ALLOC_SIZE - 1].next = NULL; + ffslice = s + 1; + } else { + ffslice = s->next; + } + + s->next = NULL; + s->wind = wind; + s->points = points; + s->current = 0; + s->last = length - 1; + + while ((s->current < s->last) && (s->points[s->current + 1].y <= y)) s->current += 1; + p = s->points + s->current; + + if (s->points[s->current].y == y) { + s->x = p[0].x; + } else { + s->x = p[0].x + (p[1].x - p[0].x) * (y - p[0].y) / (p[1].y - p[0].y); + } + s->y = y; + s->stepx = (p[1].x - p[0].x) / (p[1].y - p[0].y); + + return s; +} + +static NRSlice * +nr_slice_free_one (NRSlice *slice) +{ + NRSlice *next; + next = slice->next; + slice->next = ffslice; + ffslice = slice; + return next; +} + +static void +nr_slice_free_list (NRSlice *slice) +{ + NRSlice *l; + + if (!slice) return; + + for (l = slice; l->next != NULL; l = l->next); + + l->next = ffslice; + ffslice = slice; +} + +static NRSlice * +nr_slice_insort (NRSlice * start, NRSlice * slice) +{ + NRSlice * s, * l; + + if (!start) return slice; + if (!slice) return start; + + if (nr_slice_compare (slice, start) <= 0) { + slice->next = start; + return slice; + } + + s = start; + for (l = start->next; l != NULL; l = l->next) { + if (nr_slice_compare (slice, l) <= 0) { + slice->next = l; + s->next = slice; + return start; + } + s = l; + } + + slice->next = NULL; + s->next = slice; + + return start; +} + +static int +nr_slice_compare (NRSlice *l, NRSlice *r) +{ + if (l->y == r->y) { + if (l->x < r->x) return -1; + if (l->x > r->x) return 1; + if (l->stepx < r->stepx) return -1; + if (l->stepx > r->stepx) return 1; + } else if (l->y > r->y) { + unsigned int pidx; + NRPoint *p; + NR::Coord x, ldx, rdx; + /* This is bitch - we have to determine r values at l->y */ + pidx = 0; + while ((pidx < r->last) && (r->points[pidx + 1].y <= l->y)) pidx += 1; + /* If v is last vertex, r ends before l starts */ + if (pidx >= r->last) return 1; + p = r->points + pidx; + if (p[0].y == l->y) { + x = p[0].x; + } else { + x = p[0].x + (p[1].x - p[0].x) * (l->y - p[0].y) / (p[1].y - p[0].y); + } + if (l->x < x) return -1; + if (l->x > x) return 1; + ldx = l->stepx * (p[1].y - p[0].y); + rdx = p[1].x - p[0].x; + if (ldx < rdx) return -1; + if (ldx > rdx) return 1; + } else { + unsigned int pidx; + NRPoint *p; + NR::Coord x, ldx, rdx; + /* This is bitch - we have to determine l value at r->y */ + pidx = 0; + while ((pidx < l->last) && (l->points[pidx + 1].y <= r->y)) pidx += 1; + /* If v is last vertex, l ends before r starts */ + if (pidx >= l->last) return 1; + p = l->points + pidx; + if (p[0].y == r->y) { + x = p[0].x; + } else { + x = p[0].x + (p[1].x - p[0].x) * (r->y - p[0].y) / (p[1].y - p[0].y); + } + if (x < r->x) return -1; + if (x > r->x) return 1; + ldx = l->stepx * (p[1].y - p[0].y); + rdx = p[1].x - p[0].x; + if (ldx < rdx) return -1; + if (ldx > rdx) return 1; + } + return 0; +} + +/* + * Memory management stuff follows (remember goals?) + */ + +#define NR_RUN_ALLOC_SIZE 32 +static NRRun *ffrun = NULL; + +static NRRun * +nr_run_new (NR::Coord x0, NR::Coord y0, NR::Coord x1, NR::Coord y1, int wind) +{ + NRRun * r; + + r = ffrun; + + if (r == NULL) { + int i; + r = nr_new (NRRun, NR_RUN_ALLOC_SIZE); + for (i = 1; i < (NR_RUN_ALLOC_SIZE - 1); i++) (r + i)->next = (r + i + 1); + (r + NR_RUN_ALLOC_SIZE - 1)->next = NULL; + ffrun = r + 1; + } else { + ffrun = r->next; + } + + r->next = NULL; + + if (x0 <= x1) { + r->x0 = x0; + r->x1 = x1; + r->y0 = y0; + r->y1 = y1; + r->step = (x0 == x1) ? 0.0F : (float) (wind * (y1 - y0) / (x1 - x0)); + } else { + r->x0 = x1; + r->x1 = x0; + r->y0 = y1; + r->y1 = y0; + r->step = (float) (wind * (y1 - y0) / (x0 - x1)); + } + + r->final = (float) (wind * (y1 - y0)); + + r->value = 0.0; + r->x = r->x0; + + return r; +} + +static NRRun * +nr_run_free_one (NRRun *run) +{ + NRRun *next; + next = run->next; + run->next = ffrun; + ffrun = run; + return next; +} + +static void +nr_run_free_list (NRRun * run) +{ + NRRun * l; + + if (!run) return; + + for (l = run; l->next != NULL; l = l->next); + l->next = ffrun; + ffrun = run; +} + +static NRRun * +nr_run_insort (NRRun * start, NRRun * run) +{ + NRRun * s, * l; + + if (!start) return run; + if (!run) return start; + + if (run->x0 < start->x0) { + run->next = start; + return run; + } + + s = start; + for (l = start->next; l != NULL; l = l->next) { + if (run->x0 < l->x0) { + run->next = l; + s->next = run; + return start; + } + s = l; + } + + s->next = run; + + return start; +} + +/* + 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 : diff --git a/src/libnr/nr-svp-render.h b/src/libnr/nr-svp-render.h new file mode 100644 index 000000000..1638fb286 --- /dev/null +++ b/src/libnr/nr-svp-render.h @@ -0,0 +1,30 @@ +#ifndef __NR_SVP_RENDER_H__ +#define __NR_SVP_RENDER_H__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * + * This code is in public domain + */ + +#include +#include + +/* Renders graymask of svp into buffer */ +void nr_pixblock_render_svp_mask_or (NRPixBlock *d, NRSVP *svp); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-svp.cpp b/src/libnr/nr-svp.cpp new file mode 100644 index 000000000..a1484397a --- /dev/null +++ b/src/libnr/nr-svp.cpp @@ -0,0 +1,180 @@ +#define __NR_SVP_C__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * + * This code is in public domain + */ + +#define noNR_VERBOSE + +#define NR_SVP_LENGTH_MAX 128 + +#ifdef HAVE_CONFIG_H +# include +#endif + +#ifdef HAVE_IEEEFP_H +# include +#endif + +#include "nr-rect.h" +#include "nr-svp-private.h" + +/* Sorted vector paths */ + +void +nr_svp_free (NRSVP *svp) +{ + if (svp->points) nr_free (svp->points); + free (svp); +} + +void +nr_svp_bbox (NRSVP *svp, NRRect *bbox, unsigned int clear) +{ + unsigned int sidx; + float x0, y0, x1, y1; + + x0 = y0 = NR_HUGE; + x1 = y1 = -NR_HUGE; + + for (sidx = 0; sidx < svp->length; sidx++) { + NRSVPSegment *seg; + seg = svp->segments + sidx; + if (seg->length) { + x0 = MIN (x0, seg->x0); + y0 = MIN (y0, svp->points[seg->start].y); + x1 = MAX (x1, seg->x1); + y1 = MAX (y1, svp->points[seg->start + seg->length - 1].y); + } + } + + if ((x1 > x0) && (y1 > y0)) { + if (clear || (bbox->x1 <= bbox->x0) || (bbox->y1 <= bbox->y0)) { + bbox->x0 = x0; + bbox->y0 = y0; + bbox->x1 = x1; + bbox->y1 = y1; + } else { + bbox->x0 = MIN (bbox->x0, x0); + bbox->y0 = MIN (bbox->y0, y0); + bbox->x1 = MAX (bbox->x1, x1); + bbox->y1 = MAX (bbox->y1, y1); + } + } +} + +/* NRVertex */ + +#define NR_VERTEX_ALLOC_SIZE 4096 +static NRVertex *ffvertex = NULL; + +NRVertex * +nr_vertex_new (void) +{ + NRVertex * v; +#ifndef NR_VERTEX_ALLOC + + v = ffvertex; + + if (v == NULL) { + int i; + v = nr_new (NRVertex, NR_VERTEX_ALLOC_SIZE); + for (i = 1; i < (NR_VERTEX_ALLOC_SIZE - 1); i++) v[i].next = &v[i + 1]; + v[NR_VERTEX_ALLOC_SIZE - 1].next = NULL; + ffvertex = v + 1; + } else { + ffvertex = v->next; + } +#else + v = nr_new (NRVertex, 1); +#endif + + v->next = NULL; + + return v; +} + +NRVertex * +nr_vertex_new_xy (NR::Coord x, NR::Coord y) +{ + NRVertex * v; + + if (!finite(x) || !finite(y)) { + g_critical("nr_vertex_new_xy: BUG: Coordinates are not finite"); + x = y = 0; + } else if (!( fabs(x) < 1e17 && fabs(y) < 1e17 )) { + g_critical("nr_vertex_new_xy: Coordinates out of range"); + x = y = 0; + } + + v = nr_vertex_new (); + + v->x = x; + v->y = y; + + return v; +} + +void +nr_vertex_free_one (NRVertex * v) +{ +#ifndef NR_VERTEX_ALLOC + v->next = ffvertex; + ffvertex = v; +#else + nr_free (v); +#endif +} + +void +nr_vertex_free_list (NRVertex * v) +{ +#ifndef NR_VERTEX_ALLOC + NRVertex * l; + for (l = v; l->next != NULL; l = l->next); + l->next = ffvertex; + ffvertex = v; +#else + NRVertex *l, *n; + l = v; + while (l) { + n = l->next; + nr_free (l); + l = n; + } +#endif +} + +NRVertex * +nr_vertex_reverse_list (NRVertex * v) +{ + NRVertex * p; + + p = NULL; + + while (v) { + NRVertex * n; + n = v->next; + v->next = p; + p = v; + v = n; + } + + 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 : diff --git a/src/libnr/nr-svp.h b/src/libnr/nr-svp.h new file mode 100644 index 000000000..ca1521f29 --- /dev/null +++ b/src/libnr/nr-svp.h @@ -0,0 +1,65 @@ +#ifndef __NR_SVP_H__ +#define __NR_SVP_H__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * + * This code is in public domain + */ + +/* Sorted vector paths */ + +#include +#include + +struct NRPoint; + +struct NRSVPSegment { + gint16 wind; + guint16 length; + guint32 start; + float x0, x1; +}; + +struct NRSVPFlat { + gint16 wind; + guint16 length; + float y; + float x0, x1; +}; + +struct NRSVP { + unsigned int length; + NRPoint *points; + NRSVPSegment segments[1]; +}; + +#define NR_SVPSEG_IS_FLAT(s,i) (!(s)->segments[i].length) + +void nr_svp_free (NRSVP *svp); + +void nr_svp_bbox (NRSVP *svp, NRRect *bbox, unsigned int clear); + +/* Sorted vertex lists */ + +struct NRVertex { + NRVertex *next; + NR::Coord x, y; +}; + + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-translate-matrix-ops.cpp b/src/libnr/nr-translate-matrix-ops.cpp new file mode 100644 index 000000000..d03caf4ff --- /dev/null +++ b/src/libnr/nr-translate-matrix-ops.cpp @@ -0,0 +1,27 @@ +#include "libnr/nr-matrix-ops.h" + +namespace NR { + +Matrix +operator*(translate const &t, Matrix const &m) +{ + Matrix ret(m); + ret[4] += m[0] * t[X] + m[2] * t[Y]; + ret[5] += m[1] * t[X] + m[3] * t[Y]; + assert_close( ret, Matrix(t) * m ); + return ret; +} + +} /* namespace NR */ + + +/* + 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 : diff --git a/src/libnr/nr-translate-matrix-ops.h b/src/libnr/nr-translate-matrix-ops.h new file mode 100644 index 000000000..aceb123d1 --- /dev/null +++ b/src/libnr/nr-translate-matrix-ops.h @@ -0,0 +1,22 @@ +#ifndef SEEN_LIBNR_NR_TRANSLATE_MATRIX_OPS_H +#define SEEN_LIBNR_NR_TRANSLATE_MATRIX_OPS_H + +#include "libnr/nr-forward.h" + +namespace NR { +Matrix operator*(translate const &t, Matrix const &m); +} + + +#endif /* !SEEN_LIBNR_NR_TRANSLATE_MATRIX_OPS_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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-translate-ops.h b/src/libnr/nr-translate-ops.h new file mode 100644 index 000000000..14ab6d1ed --- /dev/null +++ b/src/libnr/nr-translate-ops.h @@ -0,0 +1,43 @@ +#ifndef SEEN_NR_TRANSLATE_OPS_H +#define SEEN_NR_TRANSLATE_OPS_H + +#include +#include + +namespace NR { + +inline bool operator==(translate const &a, translate const &b) +{ + return a.offset == b.offset; +} + +inline bool operator!=(translate const &a, translate const &b) +{ + return !( a == b ); +} + +inline translate operator*(translate const &a, translate const &b) +{ + return translate( a.offset + b.offset ); +} + +inline Point operator*(Point const &v, translate const &t) +{ + return t.offset + v; +} + +} /* namespace NR */ + + +#endif /* !SEEN_NR_TRANSLATE_OPS_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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-translate-rotate-ops.cpp b/src/libnr/nr-translate-rotate-ops.cpp new file mode 100644 index 000000000..35f60c10d --- /dev/null +++ b/src/libnr/nr-translate-rotate-ops.cpp @@ -0,0 +1,20 @@ +#include +#include + +NR::Matrix +operator*(NR::translate const &a, NR::rotate const &b) +{ + return NR::Matrix(b) * NR::translate(a.offset * b); +} + + +/* + 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 : diff --git a/src/libnr/nr-translate-rotate-ops.h b/src/libnr/nr-translate-rotate-ops.h new file mode 100644 index 000000000..0716f21cc --- /dev/null +++ b/src/libnr/nr-translate-rotate-ops.h @@ -0,0 +1,21 @@ +#ifndef SEEN_LIBNR_NR_TRANSLATE_ROTATE_OPS_H +#define SEEN_LIBNR_NR_TRANSLATE_ROTATE_OPS_H + +#include + + +NR::Matrix operator*(NR::translate const &a, NR::rotate const &b); + + +#endif /* !SEEN_LIBNR_NR_TRANSLATE_ROTATE_OPS_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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-translate-scale-ops.cpp b/src/libnr/nr-translate-scale-ops.cpp new file mode 100644 index 000000000..b995bbad5 --- /dev/null +++ b/src/libnr/nr-translate-scale-ops.cpp @@ -0,0 +1,25 @@ +#include "libnr/nr-matrix-ops.h" + +NR::Matrix +operator*(NR::translate const &t, NR::scale const &s) +{ + using NR::X; using NR::Y; + + NR::Matrix ret(s); + ret[4] = t[X] * s[X]; + ret[5] = t[Y] * s[Y]; + assert_close( ret, NR::Matrix(t) * NR::Matrix(s) ); + return ret; +} + + +/* + 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 : diff --git a/src/libnr/nr-translate-scale-ops.h b/src/libnr/nr-translate-scale-ops.h new file mode 100644 index 000000000..c72665857 --- /dev/null +++ b/src/libnr/nr-translate-scale-ops.h @@ -0,0 +1,20 @@ +#ifndef SEEN_LIBNR_NR_TRANSLATE_SCALE_OPS_H +#define SEEN_LIBNR_NR_TRANSLATE_SCALE_OPS_H + +#include "libnr/nr-forward.h" + +NR::Matrix operator*(NR::translate const &t, NR::scale const &s); + + +#endif /* !SEEN_LIBNR_NR_TRANSLATE_SCALE_OPS_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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-translate-test.cpp b/src/libnr/nr-translate-test.cpp new file mode 100644 index 000000000..9e1ef1166 --- /dev/null +++ b/src/libnr/nr-translate-test.cpp @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include +#include +#include +#include +using NR::X; +using NR::Y; + + +int main(int argc, char *argv[]) +{ + utest_start("translate"); + + NR::Point const b(-2.0, 3.0); + NR::translate const tb(b); + NR::translate const tc(-3.0, -2.0); + UTEST_TEST("constructors, operator[]") { + UTEST_ASSERT( tc[X] == -3.0 && tc[Y] == -2.0 ); + UTEST_ASSERT( tb[0] == b[X] && tb[1] == b[Y] ); + } + + UTEST_TEST("operator=") { + NR::translate tb_eq(tc); + tb_eq = tb; + UTEST_ASSERT( tb == tb_eq ); + UTEST_ASSERT( tb_eq != tc ); + } + + NR::translate const tbc( tb * tc ); + UTEST_TEST("operator*(translate, translate)") { + UTEST_ASSERT( tbc.offset == NR::Point(-5.0, 1.0) ); + UTEST_ASSERT( tbc.offset == ( tc * tb ).offset ); + UTEST_ASSERT( NR::Matrix(tbc) == NR::Matrix(tb) * NR::Matrix(tc) ); + } + + UTEST_TEST("operator*(Point, translate)") { + UTEST_ASSERT( tbc.offset == b * tc ); + UTEST_ASSERT( b * tc == b * NR::Matrix(tc) ); + } + + NR::translate const t_id(0.0, 0.0); + NR::Matrix const m_id(NR::identity()); + UTEST_TEST("identity") { + UTEST_ASSERT( b * t_id == b ); + UTEST_ASSERT( NR::Matrix(t_id) == m_id ); + } + + return ( utest_end() + ? EXIT_SUCCESS + : EXIT_FAILURE ); +} + +/* + 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 : diff --git a/src/libnr/nr-translate-test.h b/src/libnr/nr-translate-test.h new file mode 100644 index 000000000..20b537a45 --- /dev/null +++ b/src/libnr/nr-translate-test.h @@ -0,0 +1,87 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +using NR::X; +using NR::Y; + +class NrTranslateTest : public CxxTest::TestSuite +{ +public: + + NrTranslateTest() : + b( -2.0, 3.0 ), + tb( b ), + tc( -3.0, -2.0 ), + tbc( tb * tc ), + t_id( 0.0, 0.0 ), + m_id( NR::identity() ) + { + } + virtual ~NrTranslateTest() {} + +// createSuite and destroySuite get us per-suite setup and teardown +// without us having to worry about static initialization order, etc. + static NrTranslateTest *createSuite() { return new NrTranslateTest(); } + static void destroySuite( NrTranslateTest *suite ) { delete suite; } + + NR::Point const b; + NR::translate const tb; + NR::translate const tc; + NR::translate const tbc; + NR::translate const t_id; + NR::Matrix const m_id; + + + void testCtorsArrayOperator(void) + { + TS_ASSERT_EQUALS( tc[X], -3.0 ); + TS_ASSERT_EQUALS( tc[Y], -2.0 ); + + TS_ASSERT_EQUALS( tb[0], b[X] ); + TS_ASSERT_EQUALS( tb[1], b[Y] ); + } + + void testAssignmentOperator(void) + { + NR::translate tb_eq(tc); + tb_eq = tb; + TS_ASSERT_EQUALS( tb, tb_eq ); + TS_ASSERT_DIFFERS( tb_eq, tc ); + } + + void testOpStarTranslateTranslate(void) + { + TS_ASSERT_EQUALS( tbc.offset, NR::Point(-5.0, 1.0) ); + TS_ASSERT_EQUALS( tbc.offset, ( tc * tb ).offset ); + TS_ASSERT_EQUALS( NR::Matrix(tbc), NR::Matrix(tb) * NR::Matrix(tc) ); + } + + void testOpStarPointTranslate(void) + { + TS_ASSERT_EQUALS( tbc.offset, b * tc ); + TS_ASSERT_EQUALS( b * tc, b * NR::Matrix(tc) ); + } + + void testIdentity(void) + { + TS_ASSERT_EQUALS( b * t_id, b ); + TS_ASSERT_EQUALS( NR::Matrix(t_id), m_id ); + } +}; + +/* + 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 : diff --git a/src/libnr/nr-translate.h b/src/libnr/nr-translate.h new file mode 100644 index 000000000..c1ea927e0 --- /dev/null +++ b/src/libnr/nr-translate.h @@ -0,0 +1,34 @@ +#ifndef SEEN_NR_TRANSLATE_H +#define SEEN_NR_TRANSLATE_H + +#include + +namespace NR { + +class translate { +public: + Point offset; +private: + translate(); +public: + explicit translate(Point const &p) : offset(p) {} + explicit translate(Coord const x, Coord const y) : offset(x, y) {} + Coord operator[](Dim2 const dim) const { return offset[dim]; } + Coord operator[](unsigned const dim) const { return offset[dim]; } +}; + +} /* namespace NR */ + + +#endif /* !SEEN_NR_TRANSLATE_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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-types-test.cpp b/src/libnr/nr-types-test.cpp new file mode 100644 index 000000000..87a35fc68 --- /dev/null +++ b/src/libnr/nr-types-test.cpp @@ -0,0 +1,105 @@ +#include "../utest/utest.h" +#include +#include +#include +using NR::Point; +using NR::X; +using NR::Y; + + +int main(int argc, char *argv[]) { + utest_start("Basic NR::Point operations"); + + UTEST_TEST("X,Y values") { + UTEST_ASSERT(X == 0); + UTEST_ASSERT(Y == 1); + } + + NR::Point const a(1.5, 2.0); + UTEST_TEST("x,y constructor and operator[] const") { + UTEST_ASSERT(a[X] == 1.5); + UTEST_ASSERT(a[Y] == 2.0); + } + + NR::Point const b(-2.0, 3.0); + + UTEST_TEST("copy constructor") { + NR::Point a_copy(a); + UTEST_ASSERT(a == a_copy); + UTEST_ASSERT(!(a != a_copy)); + } + + UTEST_TEST("non-const operator[]") { + NR::Point a_copy(a); + a_copy[X] = -2.0; + UTEST_ASSERT(a_copy != a); + UTEST_ASSERT(a_copy != b); + a_copy[Y] = 3.0; + UTEST_ASSERT(a_copy == b); + } + + NR::Point const ab(-0.5, 5.0); + UTEST_TEST("binary +, -") { + UTEST_ASSERT(a != b); + UTEST_ASSERT(a + b == ab); + UTEST_ASSERT(ab - a == b); + UTEST_ASSERT(ab - b == a); + UTEST_ASSERT(ab + a != b); + } + + UTEST_TEST("unary-") { + UTEST_ASSERT(-a == Point(-a[X], -a[Y])); + } + + UTEST_TEST("scale, divide") { + UTEST_ASSERT(-a == -1.0 * a); + UTEST_ASSERT(a + a + a == 3.0 * a); + UTEST_ASSERT(a / .5 == 2.0 * a); + } + + UTEST_TEST("dot") { + UTEST_ASSERT( dot(a, b) == ( a[X] * b[X] + + a[Y] * b[Y] ) ); + UTEST_ASSERT( dot(a, NR::rot90(a)) == 0.0 ); + UTEST_ASSERT( dot(-a, NR::rot90(a)) == 0.0 ); + } + + double const small = pow(2.0, -1070); + + Point const small_left(-small, 0.0); + Point const smallish_3_neg4(3.0 * small, -4.0 * small); + + UTEST_TEST("L1, L2, LInfty norms") { + UTEST_ASSERT(L1(small_left) == small); + UTEST_ASSERT(L2(small_left) == small); + UTEST_ASSERT(LInfty(small_left) == small); + + UTEST_ASSERT(L1(smallish_3_neg4) == 7.0 * small); + UTEST_ASSERT(L2(smallish_3_neg4) == 5.0 * small); + UTEST_ASSERT(LInfty(smallish_3_neg4) == 4.0 * small); + } + + UTEST_TEST("operator+=") { + Point x(a); + x += b; + UTEST_ASSERT(x == ab); + } + + UTEST_TEST("operator/=") { + Point x(a); + x /= .5; + UTEST_ASSERT(x == a + a); + } + + UTEST_TEST("normalize") { + Point x(small_left); + x.normalize(); + UTEST_ASSERT(x == Point(-1.0, 0.0)); + + x = smallish_3_neg4; + x.normalize(); + UTEST_ASSERT(x == Point(0.6, -0.8)); + } + + return utest_end() ? 0 : 1; +} diff --git a/src/libnr/nr-types-test.h b/src/libnr/nr-types-test.h new file mode 100644 index 000000000..d51db1be7 --- /dev/null +++ b/src/libnr/nr-types-test.h @@ -0,0 +1,145 @@ +// nr-types-test.h +#include + +#include "libnr/nr-types.h" +#include "libnr/nr-point-fns.h" +#include +using NR::Point; +using NR::X; +using NR::Y; + +class NrTypesTest : public CxxTest::TestSuite +{ +public: + NrTypesTest() : + a( 1.5, 2.0 ), + b(-2.0, 3.0), + ab(-0.5, 5.0), + small(pow(2.0, -1070)), + small_left(-small, 0.0), + smallish_3_neg4(3.0 * small, -4.0 * small) + {} + virtual ~NrTypesTest() {} + +// createSuite and destroySuite get us per-suite setup and teardown +// without us having to worry about static initialization order, etc. + static NrTypesTest *createSuite() { return new NrTypesTest(); } + static void destroySuite( NrTypesTest *suite ) { delete suite; } + + NR::Point const a; + NR::Point const b; + NR::Point const ab; + double const small; + Point const small_left; + Point const smallish_3_neg4; + + + void testXYValues( void ) + { + TS_ASSERT_EQUALS( X, 0 ); + TS_ASSERT_EQUALS( Y, 1 ); + } + + void testXYCtorAndArrayConst(void) + { + TS_ASSERT_EQUALS( a[X], 1.5 ); + TS_ASSERT_EQUALS( a[Y], 2.0 ); + } + + void testCopyCtor(void) + { + NR::Point a_copy(a); + + TS_ASSERT_EQUALS( a, a_copy ); + TS_ASSERT( !(a != a_copy) ); + } + + void testNonConstArrayOperator(void) + { + NR::Point a_copy(a); + a_copy[X] = -2.0; + TS_ASSERT_DIFFERS( a_copy, a ); + TS_ASSERT_DIFFERS( a_copy, b ); + a_copy[Y] = 3.0; + TS_ASSERT_EQUALS( a_copy, b ); + } + + void testBinaryPlusMinus(void) + { + TS_ASSERT_DIFFERS( a, b ); + TS_ASSERT_EQUALS( a + b, ab ); + TS_ASSERT_EQUALS( ab - a, b ); + TS_ASSERT_EQUALS( ab - b, a ); + TS_ASSERT_DIFFERS( ab + a, b ); + } + + void testUnaryMinus(void) + { + TS_ASSERT_EQUALS( -a, Point(-a[X], -a[Y]) ); + } + + void tetScaleDivide(void) + { + TS_ASSERT_EQUALS( -a, -1.0 * a ); + TS_ASSERT_EQUALS( a + a + a, 3.0 * a ); + TS_ASSERT_EQUALS( a / .5, 2.0 * a ); + } + + void testDot(void) + { + TS_ASSERT_EQUALS( dot(a, b), ( a[X] * b[X] + + a[Y] * b[Y] ) ); + TS_ASSERT_EQUALS( dot(a, NR::rot90(a)), 0.0 ); + TS_ASSERT_EQUALS( dot(-a, NR::rot90(a)), 0.0 ); + } + + void testL1L2LInftyNorms(void) + { + // TODO look at TS_ASSERT_DELTA + + TS_ASSERT_EQUALS( L1(small_left), small ); + TS_ASSERT_EQUALS( L2(small_left), small ); + TS_ASSERT_EQUALS( LInfty(small_left), small ); + + TS_ASSERT_EQUALS( L1(smallish_3_neg4), 7.0 * small ); + TS_ASSERT_EQUALS( L2(smallish_3_neg4), 5.0 * small ); + TS_ASSERT_EQUALS( LInfty(smallish_3_neg4), 4.0 * small ); + } + + void testOperatorPlusEquals(void) + { + Point x(a); + x += b; + TS_ASSERT_EQUALS( x, ab ); + } + + void tetOperatorDivEquals(void) + { + Point x(a); + x /= .5; + TS_ASSERT_EQUALS( x, a + a ); + } + + void testNormalize(void) + { + Point x(small_left); + x.normalize(); + TS_ASSERT_EQUALS( x, Point(-1.0, 0.0) ); + + x = smallish_3_neg4; + x.normalize(); + TS_ASSERT_EQUALS( x, Point(0.6, -0.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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-types.cpp b/src/libnr/nr-types.cpp new file mode 100644 index 000000000..98054a551 --- /dev/null +++ b/src/libnr/nr-types.cpp @@ -0,0 +1,68 @@ +/** \file + * Implements NR::Point::normalize() + */ + +#include + +#include "isnan.h" //temporary fix for isnan() + +/** Scales this vector to make it a unit vector (within rounding error). + * + * The current version tries to handle infinite coordinates gracefully, + * but it's not clear that any callers need that. + * + * \pre *this != Point(0, 0). + * \pre Neither coordinate is NaN. + * \post L2(*this) very near 1.0. + */ +void NR::Point::normalize() { + double len = hypot(_pt[0], _pt[1]); + g_return_if_fail(len != 0); + g_return_if_fail(!isNaN(len)); + static double const inf = 1e400; + if(len != inf) { + *this /= len; + } else { + unsigned n_inf_coords = 0; + /* Delay updating pt in case neither coord is infinite. */ + NR::Point tmp; + for ( unsigned i = 0 ; i < 2 ; ++i ) { + if ( _pt[i] == inf ) { + ++n_inf_coords; + tmp[i] = 1.0; + } else if ( _pt[i] == -inf ) { + ++n_inf_coords; + tmp[i] = -1.0; + } else { + tmp[i] = 0.0; + } + } + switch (n_inf_coords) { + case 0: + /* Can happen if both coords are near +/-DBL_MAX. */ + *this /= 4.0; + len = hypot(_pt[0], _pt[1]); + g_assert(len != inf); + *this /= len; + break; + + case 1: + *this = tmp; + break; + + case 2: + *this = sqrt(0.5) * tmp; + 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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-types.h b/src/libnr/nr-types.h new file mode 100644 index 000000000..4802f5e0c --- /dev/null +++ b/src/libnr/nr-types.h @@ -0,0 +1,47 @@ +#ifndef __NR_TYPES_H__ +#define __NR_TYPES_H__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * Class-ifying NRPoint, Nathan Hurst + * + * This code is in public domain + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace NR { + +class Rect; +class Matrix; + +} /* namespace NR */ + + +#endif /* !__NR_TYPES_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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr-values.cpp b/src/libnr/nr-values.cpp new file mode 100644 index 000000000..bb310cc49 --- /dev/null +++ b/src/libnr/nr-values.cpp @@ -0,0 +1,23 @@ +#define __NR_VALUES_C__ + +#include + + +/* +The following predefined objects are for reference +and comparison. +*/ +NRMatrix NR_MATRIX_IDENTITY = + {{1.0, 0.0, 0.0, 1.0, 0.0, 0.0}}; +NRRect NR_RECT_EMPTY = + {NR_HUGE, NR_HUGE, -NR_HUGE, -NR_HUGE}; +NRRectL NR_RECT_L_EMPTY = + {NR_HUGE_L, NR_HUGE_L, -NR_HUGE_L, -NR_HUGE_L}; +NRRectL NR_RECT_S_EMPTY = + {NR_HUGE_S, NR_HUGE_S, -NR_HUGE_S, -NR_HUGE_S}; + +/** component_vectors[i] is like $e_i$ in common mathematical usage; + or equivalently $I_i$ (where $I$ is the identity matrix). */ +NR::Point const component_vectors[] = {NR::Point(1., 0.), + NR::Point(0., 1.)}; + diff --git a/src/libnr/nr-values.h b/src/libnr/nr-values.h new file mode 100644 index 000000000..7fa00d809 --- /dev/null +++ b/src/libnr/nr-values.h @@ -0,0 +1,45 @@ +#ifndef __NR_VALUES_H__ +#define __NR_VALUES_H__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * + * This code is in public domain + */ + +#include + +#define NR_EPSILON 1e-18 + +#define NR_HUGE 1e18 +#define NR_HUGE_L (0x7fffffff) +#define NR_HUGE_S (0x7fff) + +/* +The following predefined objects are for reference +and comparison. They are defined in nr-values.cpp +*/ +extern NRMatrix NR_MATRIX_IDENTITY; +extern NRRect NR_RECT_EMPTY; +extern NRRectL NR_RECT_L_EMPTY; +extern NRRectL NR_RECT_S_EMPTY; + +/** component_vectors[i] has 1.0 at position i, and 0.0 elsewhere + (i.e. in the other position). */ +extern NR::Point const component_vectors[2]; + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/libnr/nr_config.h.mingw b/src/libnr/nr_config.h.mingw new file mode 100644 index 000000000..6992cc6fc --- /dev/null +++ b/src/libnr/nr_config.h.mingw @@ -0,0 +1,12 @@ +#define NR_SIZEOF_CHAR 1 +#define NR_SIZEOF_SHORT 2 +#define NR_SIZEOF_INT 4 +#define NR_SIZEOF_LONG 4 + +typedef signed char NRByte; +typedef unsigned char NRUByte; +typedef signed short NRShort; +typedef unsigned short NRUShort; +typedef signed int NRLong; +typedef unsigned long NRULong; + diff --git a/src/libnr/nr_config.h.win32 b/src/libnr/nr_config.h.win32 new file mode 100644 index 000000000..e0bfbda3f --- /dev/null +++ b/src/libnr/nr_config.h.win32 @@ -0,0 +1,14 @@ +#define NR_SIZEOF_CHAR 1 +#define NR_SIZEOF_SHORT 2 +#define NR_SIZEOF_INT 4 +#define NR_SIZEOF_LONG 4 + +typedef signed char NRByte; +typedef unsigned char NRUByte; +typedef signed short NRShort; +typedef unsigned short NRUShort; +typedef signed int NRLong; +typedef unsigned long NRULong; + + + diff --git a/src/libnr/nr_mmx_R8G8B8A8_P_EMPTY_A8_RGBAP.S b/src/libnr/nr_mmx_R8G8B8A8_P_EMPTY_A8_RGBAP.S new file mode 100644 index 000000000..db2cbec5a --- /dev/null +++ b/src/libnr/nr_mmx_R8G8B8A8_P_EMPTY_A8_RGBAP.S @@ -0,0 +1,125 @@ + .file "nr-compose.c" + +# Ensure Inkscape is execshield protected + .section .note.GNU-stack + .previous + + .text + .align 2 +.globl nr_mmx_R8G8B8A8_P_EMPTY_A8_RGBAP + .type nr_mmx_R8G8B8A8_P_EMPTY_A8_RGBAP,@function + +/* + * This code is in public domain + * + * c 32(%ebp) + * srs 28(%ebp) + * spx 24(%ebp) + * rs 20(%ebp) + * h 16(%ebp) + * w 12(%ebp) + * px 8(%ebp) + * r -8(%ebp) + * g -12(%ebp) + * b -16(%ebp) + * a -20(%ebp) + * s -24(%ebp) -> %esi + * d -28(%ebp) -> %edi + * x -32(%ebp) -> %ebx + * y -36(%ebp) + * ca -40(%ebp) + * + * mm0 Fg + * mm1 FgA + * mm2 FgPre + * mm3 + * mm4 + * mm5 + * mm6 128 + * mm7 0 + * +*/ + +nr_mmx_R8G8B8A8_P_EMPTY_A8_RGBAP: + pushl %ebp + movl %esp, %ebp + pushl %ebx + subl $36, %esp + pushl %edi + pushl %esi + +/* Load %mm7 with [0 0 0 0] */ + movl $0, %eax + movd %eax, %mm7 + +/* Load %mm6 with [128 128 128 128] */ + movl $0x80808080, %eax + movd %eax, %mm6 + punpcklbw %mm7, %mm6 + +/* FgC -> %mm0 */ + movl 32(%ebp), %eax + movd (%eax), %mm0 + punpcklbw %mm7, %mm0 + +/* for (y = ...) */ + movl 16(%ebp), %ecx +.fory: + +/* d = px */ +/* s = spx */ + movl 8(%ebp), %edi + movl 24(%ebp), %esi + +/* for (x = ...) */ + movl 12(%ebp), %ebx +.forx: + +/* [m m m m] -> %mm1 */ + movzbl (%esi), %eax + testb $0xff, %al + jz .clip + movd %eax, %mm1 + punpcklwd %mm1, %mm1 + punpckldq %mm1, %mm1 + +/* Fg -> mm2 */ + movq %mm0, %mm2 + pmullw %mm1, %mm2 + paddw %mm6, %mm2 + movq %mm2, %mm3 + psrlw $8, %mm3 + paddw %mm3, %mm2 + psrlw $8, %mm2 + +/* Store pixel */ + packuswb %mm2, %mm2 + movd %mm2, (%edi) + +.clip: + addl $4, %edi + incl %esi + + decl %ebx + jnz .forx + + movl 20(%ebp), %eax + addl %eax, 8(%ebp) + movl 28(%ebp), %eax + addl %eax, 24(%ebp) + + decl %ecx + jnz .fory + +.exit: + emms + popl %esi + popl %edi + addl $36, %esp + popl %ebx + popl %ebp + ret + +.Lfe1: + .size nr_mmx_R8G8B8A8_P_EMPTY_A8_RGBAP,.Lfe1-nr_mmx_R8G8B8A8_P_EMPTY_A8_RGBAP + .ident "GCC: (GNU) 3.2" diff --git a/src/libnr/nr_mmx_R8G8B8A8_P_R8G8B8A8_P_A8_RGBAP.S b/src/libnr/nr_mmx_R8G8B8A8_P_R8G8B8A8_P_A8_RGBAP.S new file mode 100644 index 000000000..fe1d9be57 --- /dev/null +++ b/src/libnr/nr_mmx_R8G8B8A8_P_R8G8B8A8_P_A8_RGBAP.S @@ -0,0 +1,231 @@ + .file "nr-compose.c" + +# Ensure Inkscape is execshield protected + .section .note.GNU-stack + .previous + + .text + .align 2 +.globl nr_mmx_R8G8B8A8_P_R8G8B8A8_P_A8_RGBAP + .type nr_mmx_R8G8B8A8_P_R8G8B8A8_P_A8_RGBAP,@function + +/* + * This code is in public domain + * + * c 32(%ebp) + * srs 28(%ebp) + * spx 24(%ebp) + * rs 20(%ebp) + * h 16(%ebp) + * w 12(%ebp) + * px 8(%ebp) + * r -8(%ebp) + * g -12(%ebp) + * b -16(%ebp) + * a -20(%ebp) + * s -24(%ebp) -> %esi + * d -28(%ebp) -> %edi + * x -32(%ebp) -> %ebx + * y -36(%ebp) + * ca -40(%ebp) + * + * mm0 Fg + * mm1 MMMM + * mm2 FgM + * mm3 + * mm4 + * mm5 255 + * mm6 128 + * mm7 0 + * +*/ + +nr_mmx_R8G8B8A8_P_R8G8B8A8_P_A8_RGBAP: + pushl %ebp + movl %esp, %ebp + pushl %ebx + subl $36, %esp + pushl %edi + pushl %esi + +/* Load %mm7 with [0 0 0 0] */ + movl $0, %eax + movd %eax, %mm7 + +/* Load %mm6 with [128 128 128 128] */ + movl $0x80808080, %eax + movd %eax, %mm6 + punpcklbw %mm7, %mm6 + +/* Load %mm5 with [255 255 255 255] */ + movl $0xffffffff, %eax + movd %eax, %mm5 + punpcklbw %mm7, %mm5 + +/* FgC -> %mm0 */ + movl 32(%ebp), %eax + movd (%eax), %mm0 + punpcklbw %mm7, %mm0 + +/* Check full opacity */ + cmpb $0xff, %al + jz .opaque + +/* for (y = ...) */ + movl 16(%ebp), %ecx +.fory: + +/* d = px */ +/* s = spx */ + movl 8(%ebp), %edi + movl 24(%ebp), %esi + +/* for (x = ...) */ + movl 12(%ebp), %ebx +.forx: + +/* [m m m m] -> %mm1 */ + movzbl (%esi), %eax + testb $0xff, %al + jz .clip + movd %eax, %mm1 + punpcklwd %mm1, %mm1 + punpckldq %mm1, %mm1 + +/* Fg -> mm2 */ + movq %mm0, %mm2 + pmullw %mm1, %mm2 + paddw %mm6, %mm2 + movq %mm2, %mm3 + psrlw $8, %mm3 + paddw %mm3, %mm2 + psrlw $8, %mm2 + +/* [255 - FgA] -> mm1 */ + movq %mm2, %mm1 + punpckhwd %mm1, %mm1 + punpckhdq %mm1, %mm1 + pxor %mm5, %mm1 + +/* Bg -> mm3 */ + movd (%edi), %mm3 + punpcklbw %mm7, %mm3 + +/* Fg + ((255 - FgA) * Bg) / 255 */ + pmullw %mm1, %mm3 + paddw %mm6, %mm3 + movq %mm3, %mm4 + psrlw $8, %mm4 + paddw %mm4, %mm3 + psrlw $8, %mm3 + paddw %mm2, %mm3 + +/* Store pixel */ + packuswb %mm3, %mm3 + movd %mm3, (%edi) + +.clip: + addl $4, %edi + incl %esi + + decl %ebx + jnz .forx + + movl 20(%ebp), %eax + addl %eax, 8(%ebp) + movl 28(%ebp), %eax + addl %eax, 24(%ebp) + + decl %ecx + jnz .fory + +.exit: + emms + popl %esi + popl %edi + addl $36, %esp + popl %ebx + popl %ebp + ret + +.opaque: +/* for (y = ...) */ + movl 16(%ebp), %ecx +.o_fory: + +/* d = px */ +/* s = spx */ + movl 8(%ebp), %edi + movl 24(%ebp), %esi + +/* for (x = ...) */ + movl 12(%ebp), %ebx +.o_forx: + +/* [m m m m] -> %mm1 */ + movzbl (%esi), %eax + testb $0xff, %al + jz .o_clip + cmpb $0xff, %al + jz .o_full + movd %eax, %mm1 + punpcklwd %mm1, %mm1 + punpckldq %mm1, %mm1 + +/* Fg -> mm2 */ + movq %mm0, %mm2 + pmullw %mm1, %mm2 + paddw %mm6, %mm2 + movq %mm2, %mm3 + psrlw $8, %mm3 + paddw %mm3, %mm2 + psrlw $8, %mm2 + +/* [255 - FgA] -> mm1 */ + movq %mm2, %mm1 + punpckhwd %mm1, %mm1 + punpckhdq %mm1, %mm1 + pxor %mm5, %mm1 + +/* Bg -> mm3 */ + movd (%edi), %mm3 + punpcklbw %mm7, %mm3 + +/* Fg + ((255 - FgA) * Bg) / 255 */ + pmullw %mm1, %mm3 + paddw %mm6, %mm3 + movq %mm3, %mm4 + psrlw $8, %mm4 + paddw %mm4, %mm3 + psrlw $8, %mm3 + paddw %mm2, %mm3 + + jmp .o_store + +.o_full: + movq %mm0, %mm3 + +.o_store: +/* Store pixel */ + packuswb %mm3, %mm3 + movd %mm3, (%edi) + +.o_clip: + addl $4, %edi + incl %esi + + decl %ebx + jnz .o_forx + + movl 20(%ebp), %eax + addl %eax, 8(%ebp) + movl 28(%ebp), %eax + addl %eax, 24(%ebp) + + decl %ecx + jnz .o_fory + jmp .exit + +.Lfe1: + .size nr_mmx_R8G8B8A8_P_R8G8B8A8_P_A8_RGBAP,.Lfe1-nr_mmx_R8G8B8A8_P_R8G8B8A8_P_A8_RGBAP + .ident "GCC: (GNU) 3.2" diff --git a/src/libnr/nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM.S b/src/libnr/nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM.S new file mode 100644 index 000000000..e30056af2 --- /dev/null +++ b/src/libnr/nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM.S @@ -0,0 +1,414 @@ + .file "nr-compose-transform.c" + +# Ensure Inkscape is execshield protected + .section .note.GNU-stack + .previous + + .text + .align 2 +.globl nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_0 + .type nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_0,@function + +/* + * This code is in public domain + * + */ + +nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_0: + pushl %ebp + movl %esp, %ebp + pushl %ebx + subl $48, %esp + pushl %edi + pushl %esi + +/* Load %mm7 with [0 0 0 0] */ + movl $0, %eax + movd %eax, %mm7 + +/* Load %mm6 with [128 128 128 128] */ + movl $0x80808080, %eax + movd %eax, %mm6 + punpcklbw %mm7, %mm6 + +/* Load %mm5 with [255 255 255 255] */ + movl $0xffffffff, %eax + movd %eax, %mm5 + punpcklbw %mm7, %mm5 + +/* Load %mm0 with [a a a a] */ + movzbl 44(%ebp), %eax + movd %eax, %mm0 + punpcklwd %mm0, %mm0 + punpckldq %mm0, %mm0 + + movl 8(%ebp), %eax + movl %eax, -8(%ebp) + movl 40(%ebp), %eax + addl $16, %eax + movl (%eax), %eax + movl %eax, -12(%ebp) + movl 40(%ebp), %eax + addl $20, %eax + movl (%eax), %eax + movl %eax, -16(%ebp) + movl $0, -24(%ebp) +.L29: + movl -24(%ebp), %eax + cmpl 16(%ebp), %eax + jl .L32 + jmp .L28 +.L32: + movl -8(%ebp), %edi + + movl -12(%ebp), %eax + movl %eax, %esi + movl -16(%ebp), %eax + movl %eax, -36(%ebp) + + movl 12(%ebp), %ebx +.for_x_0: + + movl %esi, %ecx + cmpl $0, %ecx + js .clip_0 + sarl $12, %ecx + cmpl 28(%ebp), %ecx + jge .clip_0 + shll $2, %ecx + + movl -36(%ebp), %eax + cmpl $0, %eax + js .clip_0 + sarl $12, %eax + cmpl 32(%ebp), %eax + jge .clip_0 + imull 36(%ebp), %eax + + addl %ecx, %eax + addl 24(%ebp), %eax + +/* Fg -> %mm1 */ + movl (%eax), %eax + testl $0xff000000, %eax + jz .clip_0 + movd %eax, %mm1 + punpcklbw %mm7, %mm1 + +/* [a a a 255] -> %mm3 */ + shrl $24, %eax + movl $0x10101, %edx + mull %edx + orl $0xff000000, %eax + movd %eax, %mm3 + punpcklbw %mm7, %mm3 + +/* [Fg * a] -> mm1 */ + pmullw %mm3, %mm1 + paddw %mm6, %mm1 + movq %mm1, %mm4 + psrlw $8, %mm4 + paddw %mm4, %mm1 + psrlw $8, %mm1 + +/* Multiply by alpha */ + pmullw %mm0, %mm1 + paddw %mm6, %mm1 + movq %mm1, %mm4 + psrlw $8, %mm4 + paddw %mm4, %mm1 + psrlw $8, %mm1 + +/* [255 - FgA] -> mm2 */ + movq %mm1, %mm2 + punpckhwd %mm2, %mm2 + punpckhdq %mm2, %mm2 + pxor %mm5, %mm2 + +/* Bg -> mm3 */ + movd (%edi), %mm3 + punpcklbw %mm7, %mm3 + +/* Fg + ((255 - FgA) * Bg) / 255 */ + + pmullw %mm2, %mm3 + paddw %mm6, %mm3 + movq %mm3, %mm4 + psrlw $8, %mm4 + paddw %mm4, %mm3 + psrlw $8, %mm3 + paddw %mm1, %mm3 + +/* Store pixel */ + packuswb %mm3, %mm3 + movd %mm3, (%edi) + +.clip_0: +.L37: + movl 40(%ebp), %ecx + movl (%ecx), %edx + addl %edx, %esi + movl 4(%ecx), %edx + addl %edx, -36(%ebp) + + addl $4, %edi + + decl %ebx + jnz .for_x_0 + +.L34: + movl 8(%ecx), %edx + addl %edx, -12(%ebp) + movl 12(%ecx), %edx + addl %edx, -16(%ebp) + + movl 20(%ebp), %edx + leal -8(%ebp), %eax + addl %edx, (%eax) + leal -24(%ebp), %eax + incl (%eax) + jmp .L29 +.L28: + emms + popl %esi + popl %edi + addl $48, %esp + popl %ebx + popl %ebp + ret +.Lfe2: + .size nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_0,.Lfe2-nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_0 + +/* + * + * dbits 52(%ebp) + * alpha 48(%ebp) + * FF_S 44(%ebp) + * + * d -32(%ebp) -> %edi + * i -60(%ebp) -> %esi + * sx -64(%ebp) -> %ebx + * sy -68(%ebp) + * s -72(%ebp) + * + * %mm0 a a a a + * %mm1 FgA + * %mm2 SumFgA + * %mm3 a a a 255 + * %mm4 +*/ + + .align 2 +.globl nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_n + .type nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_n,@function +nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_n: + pushl %ebp + movl %esp, %ebp + pushl %ebx + subl $72, %esp + pushl %edi + pushl %esi + +/* Load %mm7 with [0 0 0 0] */ + movl $0, %eax + movd %eax, %mm7 + +/* Load %mm6 with [128 128 128 128] */ + movl $0x80808080, %eax + movd %eax, %mm6 + punpcklbw %mm7, %mm6 + +/* Load %mm5 with [255 255 255 255] */ + movl $0xffffffff, %eax + movd %eax, %mm5 + punpcklbw %mm7, %mm5 + +/* Load %mm0 with [a a a a] */ + movzbl 48(%ebp), %eax + movd %eax, %mm0 + punpcklwd %mm0, %mm0 + punpckldq %mm0, %mm0 + + movl $1, %eax + movzbl 52(%ebp), %ecx + sall %cl, %eax + movl %eax, -8(%ebp) + movl 8(%ebp), %eax + movl %eax, -12(%ebp) + movl 40(%ebp), %eax + addl $16, %eax + movl (%eax), %eax + movl %eax, -16(%ebp) + movl 40(%ebp), %eax + addl $20, %eax + movl (%eax), %eax + movl %eax, -20(%ebp) + movl $0, -28(%ebp) +.L44: + movl -28(%ebp), %eax + cmpl 16(%ebp), %eax + jl .L47 + jmp .exit_n +.L47: + movl -12(%ebp), %eax + movl %eax, -32(%ebp) + movl -16(%ebp), %eax + movl %eax, -36(%ebp) + movl -20(%ebp), %eax + movl %eax, -40(%ebp) + movl $0, -24(%ebp) +.L48: + movl -24(%ebp), %eax + cmpl 12(%ebp), %eax + jl .L51 + jmp .L49 +.L51: + +/* Zero accumulator */ + movq %mm7, %mm2 + +/* Set i to dptr (size - 1) */ + movl -8(%ebp), %esi + sub $1, %esi + shll $3, %esi + + movl 44(%ebp), %edi + movl -36(%ebp), %ecx + +.for_i_n: + movl (%edi,%esi), %ebx + addl %ecx, %ebx +/* Test negative before shift */ + cmpl $0, %ebx + js .next_i_n + sarl $12, %ebx + cmpl 28(%ebp), %ebx + jge .next_i_n +/* We multiply sx by 4 here */ + shll $2, %ebx + + movl 4(%edi,%esi), %eax + addl -40(%ebp), %eax +/* Test negative before shift */ + cmpl $0, %eax + js .next_i_n + sarl $12, %eax + cmpl 32(%ebp), %eax + jge .next_i_n +/* We multiply sy by srs here */ + imull 36(%ebp), %eax + + addl %ebx, %eax + addl 24(%ebp), %eax + +/* Fg -> %mm1 */ + movl (%eax), %eax + testl $0xff000000, %eax + jz .next_i_n + movd %eax, %mm1 + punpcklbw %mm7, %mm1 + +/* [a a a 255] -> %mm3 */ + shrl $24, %eax + movl $0x10101, %edx + mull %edx + orl $0xff000000, %eax + movd %eax, %mm3 + punpcklbw %mm7, %mm3 + +/* [Fg * a] -> mm1 */ + pmullw %mm3, %mm1 + paddw %mm6, %mm1 + movq %mm1, %mm4 + psrlw $8, %mm4 + paddw %mm4, %mm1 + psrlw $8, %mm1 + +/* Add to accumulator */ + paddw %mm1, %mm2 + +.next_i_n: + subl $8, %esi + jnb .for_i_n + +/* Divide components by sample size */ + movd 52(%ebp), %mm3 + psrlw %mm3, %mm2 + +/* Multiply by alpha */ + pmullw %mm0, %mm2 + paddw %mm6, %mm2 + movq %mm2, %mm4 + psrlw $8, %mm4 + paddw %mm4, %mm2 + psrlw $8, %mm2 + +/* [255 - FgA] -> mm1 */ + movq %mm2, %mm1 + punpckhwd %mm1, %mm1 + punpckhdq %mm1, %mm1 + pxor %mm5, %mm1 + + movl -32(%ebp), %edi +/* Bg -> mm3 */ + movd (%edi), %mm3 + punpcklbw %mm7, %mm3 + +/* Fg + ((255 - FgA) * Bg) / 255 */ + + pmullw %mm1, %mm3 + paddw %mm6, %mm3 + movq %mm3, %mm4 + psrlw $8, %mm4 + paddw %mm4, %mm3 + psrlw $8, %mm3 + paddw %mm2, %mm3 + +/* Store pixel */ + packuswb %mm3, %mm3 + movd %mm3, (%edi) + +.L58: + movl 40(%ebp), %eax + movl (%eax), %edx + leal -36(%ebp), %eax + addl %edx, (%eax) + movl 40(%ebp), %eax + addl $4, %eax + movl (%eax), %edx + leal -40(%ebp), %eax + addl %edx, (%eax) + leal -32(%ebp), %eax + addl $4, (%eax) + leal -24(%ebp), %eax + incl (%eax) + jmp .L48 +.L49: + movl 40(%ebp), %eax + addl $8, %eax + movl (%eax), %edx + leal -16(%ebp), %eax + addl %edx, (%eax) + movl 40(%ebp), %eax + addl $12, %eax + movl (%eax), %edx + leal -20(%ebp), %eax + addl %edx, (%eax) + movl 20(%ebp), %edx + leal -12(%ebp), %eax + addl %edx, (%eax) + leal -28(%ebp), %eax + incl (%eax) + jmp .L44 + +.exit_n: + emms + popl %esi + popl %edi + addl $72, %esp + popl %ebx + popl %ebp + ret +.Lfe3: + .size nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_n,.Lfe3-nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_n + .ident "GCC: (GNU) 3.2" diff --git a/src/libnr/nr_mmx_R8G8B8_R8G8B8_R8G8B8A8_P.S b/src/libnr/nr_mmx_R8G8B8_R8G8B8_R8G8B8A8_P.S new file mode 100644 index 000000000..37261e572 --- /dev/null +++ b/src/libnr/nr_mmx_R8G8B8_R8G8B8_R8G8B8A8_P.S @@ -0,0 +1,227 @@ + .file "nr-compose.c" + +# Ensure Inkscape is execshield protected + .section .note.GNU-stack + .previous + + .text + .align 2 +.globl nr_mmx_R8G8B8_R8G8B8_R8G8B8A8_P + .type nr_mmx_R8G8B8_R8G8B8_R8G8B8A8_P,@function + +/* + * This code is in public domain + * + * alpha 32(%ebp) + * srs 28(%ebp) + * spx 24(%ebp) + * rs 20(%ebp) + * h 16(%ebp) + * w 12(%ebp) + * px 8(%ebp) + * r -8(%ebp) + * g -12(%ebp) + * b -16(%ebp) + * a -20(%ebp) + * s -24(%ebp) -> %esi + * d -28(%ebp) -> %edi + * x -32(%ebp) -> %ebx + * y -36(%ebp) + * ca -40(%ebp) + * + * mm0 A + * mm1 FgA + * mm2 FgPre + * mm3 + * mm4 + * mm5 255 + * mm6 128 + * mm7 0 + * +*/ + +nr_mmx_R8G8B8_R8G8B8_R8G8B8A8_P: + pushl %ebp + movl %esp, %ebp + pushl %ebx + subl $36, %esp + pushl %edi + pushl %esi + +/* Load %mm7 with [0 0 0 0] */ + movl $0, %eax + movd %eax, %mm7 + +/* Load %mm6 with [128 128 128 128] */ + movl $0x80808080, %eax + movd %eax, %mm6 + punpcklbw %mm7, %mm6 + +/* Load %mm5 with [255 255 255 255] */ + movl $0xffffffff, %eax + movd %eax, %mm5 + punpcklbw %mm7, %mm5 + +/* Load %mm0 with [a a a a] */ +/* Check full opacity */ + movzbl 32(%ebp), %eax + cmpb $0xff, %al + jz .opaque + movd %eax, %mm0 + punpcklwd %mm0, %mm0 + punpckldq %mm0, %mm0 + +/* for (y = ...) */ + movl 16(%ebp), %ecx +.fory: + +/* d = px */ +/* s = spx */ + movl 8(%ebp), %edi + movl 24(%ebp), %esi + +/* for (x = ...) */ + movl 12(%ebp), %ebx +.forx: + +/* Fg -> %mm1 */ +/* fixme: Do we have to bother about alignment here? (Lauris) */ + movl (%esi), %eax + testl $0xff000000, %eax + jz .clip + movd %eax, %mm1 + punpcklbw %mm7, %mm1 + +/* [Fg * a] -> mm1 */ + pmullw %mm0, %mm1 + paddw %mm6, %mm1 + movq %mm1, %mm2 + psrlw $8, %mm2 + paddw %mm2, %mm1 + psrlw $8, %mm1 + +/* [255 - FgA] -> mm2 */ + movq %mm1, %mm2 + punpckhwd %mm2, %mm2 + punpckhdq %mm2, %mm2 + pxor %mm5, %mm2 + +/* Bg -> mm3 */ + movd (%edi), %mm3 + punpcklbw %mm7, %mm3 + +/* Fg + ((255 - FgA) * Bg) / 255 */ + pmullw %mm2, %mm3 + paddw %mm6, %mm3 + movq %mm3, %mm4 + psrlw $8, %mm4 + paddw %mm4, %mm3 + psrlw $8, %mm3 + paddw %mm1, %mm3 + +/* Store pixel */ + packuswb %mm3, %mm3 + movd %mm3, %eax + movb %al, 0(%edi) + shrl $8, %eax + movb %al, 1(%edi) + shrl $8, %eax + movb %al, 2(%edi) + +.clip: + addl $3, %edi + addl $4, %esi + + decl %ebx + jnz .forx + + movl 20(%ebp), %eax + addl %eax, 8(%ebp) + movl 28(%ebp), %eax + addl %eax, 24(%ebp) + + decl %ecx + jnz .fory + +.exit: + emms + popl %esi + popl %edi + addl $36, %esp + popl %ebx + popl %ebp + ret + +.opaque: +/* for (y = ...) */ + movl 16(%ebp), %ecx +.o_fory: + +/* d = px */ +/* s = spx */ + movl 8(%ebp), %edi + movl 24(%ebp), %esi + +/* for (x = ...) */ + movl 12(%ebp), %ebx +.o_forx: + +/* Fg -> %mm1 */ +/* fixme: Do we have to bother about alignment here? (Lauris) */ + movl (%esi), %eax + testl $0xff000000, %eax + jz .o_clip + cmpl $0xff000000, %eax + jnb .o_store + movd %eax, %mm1 + punpcklbw %mm7, %mm1 + +/* [255 - FgA] -> mm2 */ + movq %mm1, %mm2 + punpckhwd %mm2, %mm2 + punpckhdq %mm2, %mm2 + pxor %mm5, %mm2 + +/* Bg -> mm3 */ + movd (%edi), %mm3 + punpcklbw %mm7, %mm3 + +/* Fg + ((255 - FgA) * Bg) / 255 */ + pmullw %mm2, %mm3 + paddw %mm6, %mm3 + movq %mm3, %mm4 + psrlw $8, %mm4 + paddw %mm4, %mm3 + psrlw $8, %mm3 + paddw %mm1, %mm3 + +/* Store pixel */ + packuswb %mm3, %mm3 + movd %mm3, %eax +.o_store: + movb %al, 0(%edi) + shrl $8, %eax + movb %al, 1(%edi) + shrl $8, %eax + movb %al, 2(%edi) + +.o_clip: + addl $3, %edi + addl $4, %esi + + decl %ebx + jnz .o_forx + + movl 20(%ebp), %eax + addl %eax, 8(%ebp) + movl 28(%ebp), %eax + addl %eax, 24(%ebp) + + decl %ecx + jnz .o_fory + + jmp .exit + +.Lfe1: + .size nr_mmx_R8G8B8_R8G8B8_R8G8B8A8_P,.Lfe1-nr_mmx_R8G8B8_R8G8B8_R8G8B8A8_P + .ident "GCC: (GNU) 3.2" diff --git a/src/libnr/testnr.cpp b/src/libnr/testnr.cpp new file mode 100644 index 000000000..12dce4c52 --- /dev/null +++ b/src/libnr/testnr.cpp @@ -0,0 +1,92 @@ +#define __TESTNR_C__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * + * This code is in public domain + */ + +#if defined (_WIN32) || defined (__WIN32__) +# include +#include +#endif + + +#include "nr-blit.h" + +static double +get_time (void) +{ + GTimeVal tv; + g_get_current_time (&tv); + return tv.tv_sec + 1e-6 * tv.tv_usec; +} + +static unsigned int +rand_byte (void) +{ + return (int) (256.0 * rand () / (RAND_MAX + 1.0)); +} + +int +main (int argc, const char **argv) +{ + double start, end; + NRPixBlock d, m[16]; + int count, i; + + srand (time (NULL)); + + printf ("Initializing buffers\n"); + + /* Destination */ + nr_pixblock_setup_fast (&d, NR_PIXBLOCK_MODE_R8G8B8A8P, 0, 0, 64, 64, 1); + d.empty = 0; + + /* Masks */ + for (i = 0; i < 16; i++) { + int r, b, c; + nr_pixblock_setup_fast (&m[i], NR_PIXBLOCK_MODE_A8, 0, 0, 64, 64, 0); + for (r = 0; r < 64; r++) { + unsigned int q; + unsigned char *p; + p = NR_PIXBLOCK_PX (&m[i]) + r * m[i].rs; + for (b = 0; b < 8; b++) { + q = rand_byte (); + if (q < 120) { + for (c = 0; c < 8; c++) *p++ = 0; + } else if (q < 240) { + for (c = 0; c < 8; c++) *p++ = 255; + } else { + for (c = 0; c < 8; c++) *p++ = rand_byte (); + } + } + } + m[i].empty = 0; + } + + printf ("Random transparency\n"); + count = 0; + start = end = get_time (); + while ((end - start) < 5.0) { + unsigned char r, g, b, a; + r = rand_byte (); + g = rand_byte (); + b = rand_byte (); + a = rand_byte (); + + for (i = 0; i < 16; i++) { + nr_blit_pixblock_mask_rgba32 (&d, &m[i], (a << 24) | (g << 16) | (b << 8) | a); + count += 1; + } + end = get_time (); + } + printf ("Did %d [64x64] random buffers in %f sec\n", count, end - start); // localizing ok + printf ("%f buffers per second\n", count / (end - start)); // localizing ok + printf ("%f pixels per second\n", count * (64 * 64) / (end - start)); // localizing ok + + return 0; +} diff --git a/src/libnrtype/.cvsignore b/src/libnrtype/.cvsignore new file mode 100644 index 000000000..e8014d011 --- /dev/null +++ b/src/libnrtype/.cvsignore @@ -0,0 +1,5 @@ +Makefile +Makefile.in +.deps +makefile +.dirstamp diff --git a/src/libnrtype/FontFactory.cpp b/src/libnrtype/FontFactory.cpp new file mode 100644 index 000000000..5fc7e0b3b --- /dev/null +++ b/src/libnrtype/FontFactory.cpp @@ -0,0 +1,630 @@ +/* + * FontFactory.cpp + * testICU + * + * Authors: + * fred + * bulia byak + * + */ + +#include "FontFactory.h" +#include + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include // _() + +/* Freetype2 */ +# include + + +// need to avoid using the size field +size_t font_descr_hash::operator()( PangoFontDescription *const &x) const { + int h = 0; + h *= 1128467; + char const *theF = pango_font_description_get_family(x); + h += (theF)?g_str_hash(theF):0; + h *= 1128467; + h += (int)pango_font_description_get_style(x); + h *= 1128467; + h += (int)pango_font_description_get_variant(x); + h *= 1128467; + h += (int)pango_font_description_get_weight(x); + h *= 1128467; + h += (int)pango_font_description_get_stretch(x); + return h; +} +bool font_descr_equal::operator()( PangoFontDescription *const&a, PangoFontDescription *const &b) { + //if ( pango_font_description_equal(a,b) ) return true; + char const *fa = pango_font_description_get_family(a); + char const *fb = pango_font_description_get_family(b); + if ( ( fa && fb == NULL ) || ( fb && fa == NULL ) ) return false; + if ( fa && fb && strcmp(fa,fb) != 0 ) return false; + if ( pango_font_description_get_style(a) != pango_font_description_get_style(b) ) return false; + if ( pango_font_description_get_variant(a) != pango_font_description_get_variant(b) ) return false; + if ( pango_font_description_get_weight(a) != pango_font_description_get_weight(b) ) return false; + if ( pango_font_description_get_stretch(a) != pango_font_description_get_stretch(b) ) return false; + return true; +} + +/////////////////// helper functions + +/** + * A wrapper for strcasestr that also provides an implementation for Win32. + */ +static bool +ink_strstr(char const *haystack, char const *pneedle) +{ + // windows has no strcasestr implementation, so here is ours... + // stolen from nmap + /* FIXME: This is broken for e.g. ink_strstr("aab", "ab"). Report to nmap. + * + * Also, suggest use of g_ascii_todown instead of buffer stuff, and g_ascii_tolower instead + * of tolower. Given that haystack is a font name (i.e. fairly short), it should be ok to + * do g_ascii_strdown on both haystack and pneedle, and do normal strstr. + * + * Rather than fixing in inkscape, consider getting rid of this routine, instead using + * strdown and plain strstr at caller. We have control over the needle values, so we can + * modify the callers rather than calling strdown there. + */ + char buf[512]; + register char const *p; + char *needle, *q, *foundto; + if (!*pneedle) return true; + if (!haystack) return false; + + needle = buf; + p = pneedle; q = needle; + while ((*q++ = tolower(*p++))) + ; + p = haystack - 1; foundto = needle; + while (*++p) { + if (tolower(*p) == *foundto) { + if (!*++foundto) { + /* Yeah, we found it */ + return true; + } + } else foundto = needle; + } + return false; +} + +/** + * Regular fonts are 'Regular', 'Roman', 'Normal', or 'Plain' + */ +// FIXME: make this UTF8, add non-English style names +static bool +is_regular(char const *s) +{ + if (ink_strstr(s, "Regular")) return true; + if (ink_strstr(s, "Roman")) return true; + if (ink_strstr(s, "Normal")) return true; + if (ink_strstr(s, "Plain")) return true; + return false; +} + +/** + * Non-bold fonts are 'Medium' or 'Book' + */ +static bool +is_nonbold(char const *s) +{ + if (ink_strstr(s, "Medium")) return true; + if (ink_strstr(s, "Book")) return true; + return false; +} + +/** + * Italic fonts are 'Italic', 'Oblique', or 'Slanted' + */ +static bool +is_italic(char const *s) +{ + if (ink_strstr(s, "Italic")) return true; + if (ink_strstr(s, "Oblique")) return true; + if (ink_strstr(s, "Slanted")) return true; + return false; +} + +/** + * Bold fonts are 'Bold' + */ +static bool +is_bold(char const *s) +{ + if (ink_strstr(s, "Bold")) return true; + return false; +} + +/** + * Caps fonts are 'Caps' + */ +static bool +is_caps(char const *s) +{ + if (ink_strstr(s, "Caps")) return true; + return false; +} + +#if 0 /* FIXME: These are all unused. Please delete them or use them (presumably in +* style_name_compare). */ +/** + * Monospaced fonts are 'Mono' + */ +static bool +is_mono(char const *s) +{ + if (ink_strstr(s, "Mono")) return true; + return false; +} + +/** + * Rounded fonts are 'Round' + */ +static bool +is_round(char const *s) +{ + if (ink_strstr(s, "Round")) return true; + return false; +} + +/** + * Outline fonts are 'Outline' + */ +static bool +is_outline(char const *s) +{ + if (ink_strstr(s, "Outline")) return true; + return false; +} + +/** + * Swash fonts are 'Swash' + */ +static bool +is_swash(char const *s) +{ + if (ink_strstr(s, "Swash")) return true; + return false; +} +#endif + +/** + * Determines if two style names match. This allows us to match + * based on the type of style rather than simply doing string matching, + * because for instance 'Plain' and 'Normal' mean the same thing. + * + * Q: Shouldn't this include the other tests such as is_outline, etc.? + * Q: Is there a problem with strcasecmp on Win32? Should it use stricmp? + */ +static int +style_name_compare(void const *aa, void const *bb) +{ + char const *a = (char const *) aa; + char const *b = (char const *) bb; + + if (is_regular(a) && !is_regular(b)) return -1; + if (is_regular(b) && !is_regular(a)) return 1; + + if (is_bold(a) && !is_bold(b)) return 1; + if (is_bold(b) && !is_bold(a)) return -1; + + if (is_italic(a) && !is_italic(b)) return 1; + if (is_italic(b) && !is_italic(a)) return -1; + + if (is_nonbold(a) && !is_nonbold(b)) return 1; + if (is_nonbold(b) && !is_nonbold(a)) return -1; + + if (is_caps(a) && !is_caps(b)) return 1; + if (is_caps(b) && !is_caps(a)) return -1; + + return strcasecmp(a, b); +} + +static int +style_record_compare(void const *aa, void const *bb) +{ + NRStyleRecord const *a = (NRStyleRecord const *) aa; + NRStyleRecord const *b = (NRStyleRecord const *) bb; + + return (style_name_compare(a->name, b->name)); +} + +static void font_factory_name_list_destructor(NRNameList *list) +{ + for (unsigned int i = 0; i < list->length; i++) + free(list->names[i]); + if ( list->names ) nr_free(list->names); +} + +static void font_factory_style_list_destructor(NRStyleList *list) +{ + for (unsigned int i = 0; i < list->length; i++) { + free((void *) (list->records)[i].name); + free((void *) (list->records)[i].descr); + } + if ( list->records ) nr_free(list->records); +} + +/** + * On Win32 performs a stricmp(a,b), otherwise does a strcasecmp(a,b) + */ +static int +family_name_compare(void const *a, void const *b) +{ +#ifndef WIN32 + return strcasecmp((*((char const **) a)), (*((char const **) b))); +#else + return stricmp((*((char const **) a)), (*((char const **) b))); +#endif +} + +void noop(...) {} +//#define PANGO_DEBUG g_print +#define PANGO_DEBUG noop + + + +///////////////////// FontFactory +#ifndef USE_PANGO_WIN32 +// the substitute function to tell fontconfig to enforce outline fonts +void FactorySubstituteFunc(FcPattern *pattern,gpointer /*data*/) +{ + FcPatternAddBool(pattern, "FC_OUTLINE",FcTrue); + //char *fam = NULL; + //FcPatternGetString(pattern, "FC_FAMILY",0, &fam); + //printf("subst_f on %s\n",fam); +} +#endif + + +font_factory *font_factory::lUsine = NULL; + +font_factory *font_factory::Default(void) +{ + if ( lUsine == NULL ) lUsine = new font_factory; + return lUsine; +} + +font_factory::font_factory(void) +{ + fontSize = 512; + nbEnt = 0; + maxEnt = 32; + ents = (font_entry*)malloc(maxEnt*sizeof(font_entry)); + +#ifdef USE_PANGO_WIN32 + hScreenDC = pango_win32_get_dc(); + fontServer = pango_win32_font_map_for_display(); + fontContext = pango_win32_get_context(); + pangoFontCache = pango_win32_font_map_get_font_cache(fontServer); +#else + fontServer = pango_ft2_font_map_new(); + pango_ft2_font_map_set_resolution((PangoFT2FontMap*)fontServer, 72, 72); + fontContext = pango_ft2_font_map_create_context((PangoFT2FontMap*)fontServer); + pango_ft2_font_map_set_default_substitute((PangoFT2FontMap*)fontServer,FactorySubstituteFunc,this,NULL); +#endif +} + +font_factory::~font_factory(void) +{ + for (int i = 0;i < nbEnt;i++) ents[i].f->Unref(); + if ( ents ) free(ents); + + g_object_unref(fontServer); +#ifdef USE_PANGO_WIN32 + pango_win32_shutdown_display(); +#else + //pango_ft2_shutdown_display(); +#endif + //g_object_unref(fontContext); +} + +font_instance *font_factory::FaceFromDescr(char const *family, char const *style) +{ + PangoFontDescription *temp_descr = pango_font_description_from_string(style); + pango_font_description_set_family(temp_descr,family); + font_instance *res = Face(temp_descr); + pango_font_description_free(temp_descr); + return res; +} + +font_instance *font_factory::Face(PangoFontDescription *descr, bool canFail) +{ +#ifdef USE_PANGO_WIN32 + // damn Pango fudges the size, so we need to unfudge. See source of pango_win32_font_map_init() + pango_font_description_set_size(descr, (int) (fontSize*PANGO_SCALE*72/GetDeviceCaps(pango_win32_get_dc(),LOGPIXELSY))); // mandatory huge size (hinting workaround) +#else + pango_font_description_set_size(descr, (int) (fontSize*PANGO_SCALE)); // mandatory huge size (hinting workaround) +#endif + + font_instance *res = NULL; + + if ( loadedFaces.find(descr) == loadedFaces.end() ) { + // not yet loaded + PangoFont *nFace = NULL; + + // workaround for bug #1025565. + // fonts without families blow up Pango. + if (pango_font_description_get_family(descr) != NULL) { + nFace = pango_font_map_load_font(fontServer,fontContext,descr); + } + else { + g_warning(_("Ignoring font without family that will crash Pango")); + } + + if ( nFace ) { + // duplicate FcPattern, the hard way + res = new font_instance(); + // store the descr of the font we asked for, since this is the key where we intend to put the font_instance at + // in the hash_map. the descr of the returned pangofont may differ from what was asked, so we don't know (at this + // point) whether loadedFaces[that_descr] is free or not (and overwriting an entry will bring deallocation problems) + res->descr = pango_font_description_copy(descr); + res->daddy = this; + res->InstallFace(nFace); + if ( res->pFont == NULL ) { + // failed to install face -> bitmap font + // printf("face failed\n"); + res->daddy = NULL; + delete res; + res = NULL; + if ( canFail ) { + char *tc = pango_font_description_to_string(descr); + PANGO_DEBUG("falling back from %s to Sans because InstallFace failed\n",tc); + free(tc); + pango_font_description_set_family(descr,"Sans"); + res = Face(descr,false); + } + } else { + loadedFaces[res->descr]=res; + res->Ref(); + AddInCache(res); + } + } else { + // no match + if ( canFail ) { + PANGO_DEBUG("falling back to Sans\n"); + pango_font_description_set_family(descr,"Sans"); + res = Face(descr,false); + } + } + } else { + // already here + res = loadedFaces[descr]; + res->Ref(); + AddInCache(res); + } + res->InitTheFace(); + return res; +} + +font_instance *font_factory::Face(char const *family, int variant, int style, int weight, int stretch, int /*size*/, int /*spacing*/) +{ + PangoFontDescription *temp_descr = pango_font_description_new(); + pango_font_description_set_family(temp_descr,family); + pango_font_description_set_weight(temp_descr,(PangoWeight)weight); + pango_font_description_set_stretch(temp_descr,(PangoStretch)stretch); + pango_font_description_set_style(temp_descr,(PangoStyle)style); + pango_font_description_set_variant(temp_descr,(PangoVariant)variant); + font_instance *res = Face(temp_descr); + pango_font_description_free(temp_descr); + return res; +} + +font_instance *font_factory::Face(char const *family, NRTypePosDef apos) +{ + PangoFontDescription *temp_descr = pango_font_description_new(); + + pango_font_description_set_family(temp_descr, family); + + if ( apos.variant == NR_POS_VARIANT_SMALLCAPS ) { + pango_font_description_set_variant(temp_descr, PANGO_VARIANT_SMALL_CAPS); + } else { + pango_font_description_set_variant(temp_descr, PANGO_VARIANT_NORMAL); + } + + if ( apos.italic ) { + pango_font_description_set_style(temp_descr, PANGO_STYLE_ITALIC); + } else if ( apos.oblique ) { + pango_font_description_set_style(temp_descr, PANGO_STYLE_OBLIQUE); + } else { + pango_font_description_set_style(temp_descr, PANGO_STYLE_NORMAL); + } + + if ( apos.weight <= NR_POS_WEIGHT_ULTRA_LIGHT ) { + pango_font_description_set_weight(temp_descr, PANGO_WEIGHT_ULTRALIGHT); + } else if ( apos.weight <= NR_POS_WEIGHT_LIGHT ) { + pango_font_description_set_weight(temp_descr, PANGO_WEIGHT_LIGHT); + } else if ( apos.weight <= NR_POS_WEIGHT_NORMAL ) { + pango_font_description_set_weight(temp_descr, PANGO_WEIGHT_NORMAL); + } else if ( apos.weight <= NR_POS_WEIGHT_BOLD ) { + pango_font_description_set_weight(temp_descr, PANGO_WEIGHT_BOLD); + } else if ( apos.weight <= NR_POS_WEIGHT_ULTRA_BOLD ) { + pango_font_description_set_weight(temp_descr, PANGO_WEIGHT_ULTRABOLD); + } else { + pango_font_description_set_weight(temp_descr, PANGO_WEIGHT_HEAVY); + } + + if ( apos.stretch <= NR_POS_STRETCH_ULTRA_CONDENSED ) { + pango_font_description_set_stretch(temp_descr, PANGO_STRETCH_EXTRA_CONDENSED); + } else if ( apos.stretch <= NR_POS_STRETCH_CONDENSED ) { + pango_font_description_set_stretch(temp_descr, PANGO_STRETCH_CONDENSED); + } else if ( apos.stretch <= NR_POS_STRETCH_SEMI_CONDENSED ) { + pango_font_description_set_stretch(temp_descr, PANGO_STRETCH_SEMI_CONDENSED); + } else if ( apos.stretch <= NR_POS_WEIGHT_NORMAL ) { + pango_font_description_set_stretch(temp_descr, PANGO_STRETCH_NORMAL); + } else if ( apos.stretch <= NR_POS_STRETCH_SEMI_EXPANDED ) { + pango_font_description_set_stretch(temp_descr, PANGO_STRETCH_SEMI_EXPANDED); + } else if ( apos.stretch <= NR_POS_STRETCH_EXPANDED ) { + pango_font_description_set_stretch(temp_descr, PANGO_STRETCH_EXPANDED); + } else { + pango_font_description_set_stretch(temp_descr, PANGO_STRETCH_EXTRA_EXPANDED); + } + + font_instance *res = Face(temp_descr); + pango_font_description_free(temp_descr); + return res; +} + +void font_factory::UnrefFace(font_instance *who) +{ + if ( who == NULL ) return; + if ( loadedFaces.find(who->descr) == loadedFaces.end() ) { + // not found + char *tc = pango_font_description_to_string(who->descr); + g_warning("unrefFace %p=%s: failed\n",who,tc); + free(tc); + } else { + loadedFaces.erase(loadedFaces.find(who->descr)); + // printf("unrefFace %p: success\n",who); + } +} + +NRNameList *font_factory::Families(NRNameList *flist) +{ + PangoFontFamily** fams = NULL; + int nbFam = 0; + pango_font_map_list_families(fontServer, &fams, &nbFam); + + PANGO_DEBUG("got %d families\n", nbFam); + + flist->length = nbFam; + flist->names = (guchar **)malloc(nbFam*sizeof(guchar*)); + flist->destructor = font_factory_name_list_destructor; + + for (int i = 0;i < nbFam;i++) { +// Note: on Windows, pango_font_family_get_name always returns lowercase name. +// As a result the list of fonts in the dialog is lowercase. +// We could work around by loading the font and taking pango_font_description_get_family from its descr (that gives correct case), +// but this is slow, and it's better to fix Pango instead. + flist->names[i]=(guchar*)strdup(pango_font_family_get_name(fams[i])); + } + + qsort(flist->names, nbFam, sizeof(guchar *), family_name_compare); + + g_free(fams); + + return flist; +} + +NRStyleList *font_factory::Styles(gchar const *family, NRStyleList *slist) +{ + PangoFontFamily *theFam = NULL; + + // search available families + { + PangoFontFamily** fams = NULL; + int nbFam = 0; + pango_font_map_list_families(fontServer, &fams, &nbFam); + + for (int i = 0;i < nbFam;i++) { + char const *fname = pango_font_family_get_name(fams[i]); + if ( fname && strcmp(family,fname) == 0 ) { + theFam = fams[i]; + break; + } + } + + g_free(fams); + } + + // nothing found + if ( theFam == NULL ) { + slist->length = 0; + slist->records = NULL; + slist->destructor = NULL; + return slist; + } + + // search faces in the found family + PangoFontFace** faces = NULL; + int nFaces = 0; + pango_font_family_list_faces(theFam, &faces, &nFaces); + + slist->records = (NRStyleRecord *) malloc(nFaces * sizeof(NRStyleRecord)); + slist->destructor = font_factory_style_list_destructor; + + int nr = 0; + for (int i = 0; i < nFaces; i++) { + + // no unnamed faces + if (pango_font_face_get_face_name(faces[i]) == NULL) + continue; + PangoFontDescription *nd = pango_font_face_describe(faces[i]); + if (nd == NULL) + continue; + char const *descr = pango_font_description_to_string(nd); + if (descr == NULL) { + pango_font_description_free(nd); + continue; + } + + char const *name = g_strdup(pango_font_face_get_face_name(faces[i])); + pango_font_description_free(nd); + + slist->records[nr].name = name; + slist->records[nr].descr = descr; + nr ++; + } + + slist->length = nr; + + qsort(slist->records, slist->length, sizeof(NRStyleRecord), style_record_compare); + /* effic: Consider doing strdown and all the is_italic etc. tests once off and store the + * results in a table, rather than having the sort invoke multiple is_italic tests per + * record. + */ + + g_free(faces); + + return slist; +} + +void font_factory::AddInCache(font_instance *who) +{ + if ( who == NULL ) return; + for (int i = 0;i < nbEnt;i++) ents[i].age *= 0.9; + for (int i = 0;i < nbEnt;i++) { + if ( ents[i].f == who ) { + // printf("present\n"); + ents[i].age += 1.0; + return; + } + } + if ( nbEnt > maxEnt ) { + printf("cache sur-plein?\n"); + return; + } + who->Ref(); + if ( nbEnt == maxEnt ) { + int bi = 0; + double ba = ents[bi].age; + for (int i = 1;i < nbEnt;i++) { + if ( ents[i].age < ba ) { + bi = i; + ba = ents[bi].age; + } + } + ents[bi].f->Unref(); + ents[bi]=ents[--nbEnt]; + } + ents[nbEnt].f = who; + ents[nbEnt].age = 1.0; + nbEnt++; +} + + +/* + 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 : diff --git a/src/libnrtype/FontFactory.h b/src/libnrtype/FontFactory.h new file mode 100644 index 000000000..2b4c6be0f --- /dev/null +++ b/src/libnrtype/FontFactory.h @@ -0,0 +1,112 @@ +/* + * FontFactory.h + * testICU + * + */ + +#ifndef my_font_factory +#define my_font_factory + +#include +#include +#include + +#ifdef HAVE_CONFIG_H +# include +#endif +#ifdef _WIN32 +#define USE_PANGO_WIN32 +#endif + +#include +#include "nr-type-primitives.h" +#include "nr-type-pos-def.h" +#include + +/* Freetype */ +#ifdef USE_PANGO_WIN32 +#include +#else +#include +#include +#endif + +// the font_factory keeps a hashmap of all the loaded font_instances, and uses the PangoFontDescription +// as index (nota: since pango already does that, using the PangoFont could work too) +struct font_descr_hash : public std::unary_function { + size_t operator()(PangoFontDescription *const &x) const; +}; +struct font_descr_equal : public std::binary_function { + bool operator()(PangoFontDescription *const &a, PangoFontDescription *const &b); +}; + +class font_factory { +public: + static font_factory *lUsine; /**< The default font_factory; i cannot think of why we would + * need more than one. + * + * ("l'usine" is french for "the factory".) + */ + + /** A little cache for fonts, so that you don't loose your time looking up fonts in the font list + * each font in the cache is refcounted once (and deref'd when removed from the cache). */ + struct font_entry { + font_instance *f; + double age; + }; + int nbEnt; ///< Number of entries. + int maxEnt; ///< Cache size. + font_entry *ents; + + // Pango data. Backend-specific structures are cast to these opaque types. + PangoFontMap *fontServer; + PangoContext *fontContext; +#ifdef USE_PANGO_WIN32 + PangoWin32FontCache *pangoFontCache; + HDC hScreenDC; +#endif + double fontSize; /**< The huge fontsize used as workaround for hinting. + * Different between freetype and win32. */ + + __gnu_cxx::hash_map loadedFaces; + + font_factory(); + ~font_factory(); + + /// Returns the default font_factory. + static font_factory* Default(); + + // Various functions to get a font_instance from different descriptions. + font_instance* FaceFromDescr(char const *family, char const *style); + font_instance* Face(PangoFontDescription *descr, bool canFail=true); + font_instance* Face(char const *family, + int variant=PANGO_VARIANT_NORMAL, int style=PANGO_STYLE_NORMAL, + int weight=PANGO_WEIGHT_NORMAL, int stretch=PANGO_STRETCH_NORMAL, + int size=10, int spacing=0); + font_instance* Face(char const *family, NRTypePosDef apos); + + /// Semi-private: tells the font_factory taht the font_instance 'who' has died and should be removed from loadedFaces + void UnrefFace(font_instance* who); + + // Queries for the font-selector. + NRNameList* Families(NRNameList *flist); + NRStyleList* Styles(const gchar *family, NRStyleList *slist); + + // internal + void AddInCache(font_instance *who); +}; + + +#endif /* my_font_factory */ + + +/* + 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 : diff --git a/src/libnrtype/FontInstance.cpp b/src/libnrtype/FontInstance.cpp new file mode 100644 index 000000000..d2b19d0f2 --- /dev/null +++ b/src/libnrtype/FontInstance.cpp @@ -0,0 +1,763 @@ +/* + * FontInstance.cpp + * testICU + * + * Authors: + * fred + * bulia byak + * + */ + +#ifdef HAVE_CONFIG_H +# include +#endif +#include +#include +#include + +/* #include */ + +#include + +#include "RasterFont.h" + +/* Freetype 2 */ +# include +# include +# include +# include +# include +# include + + + +size_t font_style_hash::operator()(const font_style &x) const { + int h=0,n; + for (int i=0;i<6;i++) { + n=(int)floor(100*x.transform[i]); + h*=12186; + h+=n; + } + n=(int)floor(100*x.stroke_width); + h*=12186; + h+=n; + n=(x.vertical)?1:0; + h*=12186; + h+=n; + if ( x.stroke_width >= 0.01 ) { + n=x.stroke_cap*10+x.stroke_join+(int)(x.stroke_miter_limit*100); + h*=12186; + h+=n; + if ( x.nbDash > 0 ) { + n=x.nbDash; + h*=12186; + h+=n; + n=(int)floor(100*x.dash_offset); + h*=12186; + h+=n; + for (int i=0;i 0.01 && b.stroke_width <= 0.01 ) return false; + if ( a.stroke_width <= 0.01 && b.stroke_width > 0.01 ) return false; + if ( a.stroke_width <= 0.01 && b.stroke_width <= 0.01 ) return true; + + if ( a.stroke_cap != b.stroke_cap ) return false; + if ( a.stroke_join != b.stroke_join ) return false; + if ( fabs(a.stroke_miter_limit-b.stroke_miter_limit) > 0.01) return false; + if ( a.nbDash != b.nbDash ) return false; + if ( a.nbDash <= 0 ) return true; + if ( fabs(a.dash_offset-b.dash_offset) < 0.01 ) { + for (int i=0;i= 0.01 ) return false; + } + } else { + return false; + } + return true; +} + +#ifndef USE_PANGO_WIN32 +/* + * Outline extraction + */ +typedef struct ft2_to_liv { + Path* theP; + double scale; + NR::Point last; +} ft2_to_liv; + +// outline as returned by freetype -> livarot Path +// see nr-type-ft2.cpp for the freetype -> artBPath on which this code is based +static int ft2_move_to (FT_Vector * to, void * i_user) { + ft2_to_liv* user=(ft2_to_liv*)i_user; + NR::Point p(user->scale*to->x,user->scale*to->y); + // printf("m t=%f %f\n",p[0],p[1]); + user->theP->MoveTo(p); + user->last=p; + return 0; +} + +static int ft2_line_to (FT_Vector * to, void * i_user) +{ + ft2_to_liv* user=(ft2_to_liv*)i_user; + NR::Point p(user->scale*to->x,user->scale*to->y); + // printf("l t=%f %f\n",p[0],p[1]); + user->theP->LineTo(p); + user->last=p; + return 0; +} + +static int ft2_conic_to (FT_Vector * control, FT_Vector * to, void * i_user) +{ + ft2_to_liv* user=(ft2_to_liv*)i_user; + NR::Point p(user->scale*to->x,user->scale*to->y),c(user->scale*control->x,user->scale*control->y); + // printf("b c=%f %f t=%f %f\n",c[0],c[1],p[0],p[1]); + user->theP->BezierTo(p); + user->theP->IntermBezierTo(c); + user->theP->EndBezierTo(); + user->last=p; + return 0; +} + +static int ft2_cubic_to (FT_Vector * control1, FT_Vector * control2, FT_Vector * to, void * i_user) +{ + ft2_to_liv* user=(ft2_to_liv*)i_user; + NR::Point p(user->scale*to->x,user->scale*to->y), + c1(user->scale*control1->x,user->scale*control1->y), + c2(user->scale*control2->x,user->scale*control2->y); + // printf("c c1=%f %f c2=%f %f t=%f %f\n",c1[0],c1[1],c2[0],c2[1],p[0],p[1]); + user->theP->CubicTo(p,3*(c1-user->last),3*(p-c2)); + user->last=p; + return 0; +} +#endif + +/* + * + */ + +font_instance::font_instance(void) +{ + //printf("font instance born\n"); + descr=NULL; + pFont=NULL; + refCount=0; + daddy=NULL; + nbGlyph=maxGlyph=0; + glyphs=NULL; + theFace=NULL; +} + +font_instance::~font_instance(void) +{ + if ( daddy ) daddy->UnrefFace(this); + //printf("font instance death\n"); + if ( pFont ) g_object_unref(pFont); + pFont=NULL; + if ( descr ) pango_font_description_free(descr); + descr=NULL; + // if ( theFace ) FT_Done_Face(theFace); // owned by pFont. don't touch + theFace=NULL; + + for (int i=0;iUnrefFace(this); + daddy=NULL; + delete this; + } +} + +unsigned int font_instance::Name(gchar *str, unsigned int size) +{ + return Attribute("name", str, size); +} + +unsigned int font_instance::Family(gchar *str, unsigned int size) +{ + return Attribute("family", str, size); +} + +unsigned int font_instance::PSName(gchar *str, unsigned int size) +{ + return Attribute("psname", str, size); +} + +unsigned int font_instance::Attribute(const gchar *key, gchar *str, unsigned int size) +{ + if ( descr == NULL ) { + if ( size > 0 ) str[0]=0; + return 0; + } + char* res=NULL; + bool free_res=false; + + if ( strcmp(key,"name") == 0 ) { + PangoFontDescription* td=pango_font_description_copy(descr); + pango_font_description_unset_fields (td, PANGO_FONT_MASK_SIZE); + res=pango_font_description_to_string (td); + pango_font_description_free(td); + free_res=true; + } else if ( strcmp(key,"psname") == 0 ) { +#ifndef USE_PANGO_WIN32 + res = (char *) FT_Get_Postscript_Name (theFace); // that's the main method, seems to always work +#endif + free_res=false; + if (res == NULL) { // a very limited workaround, only bold, italic, and oblique will work + PangoStyle style=pango_font_description_get_style(descr); + bool i = (style == PANGO_STYLE_ITALIC); + bool o = (style == PANGO_STYLE_OBLIQUE); + PangoWeight weight=pango_font_description_get_weight(descr); + bool b = (weight >= PANGO_WEIGHT_BOLD); + + res = g_strdup_printf ("%s%s%s%s", + pango_font_description_get_family(descr), + (b || i || o) ? "-" : "", + (b) ? "Bold" : "", + (i) ? "Italic" : ((o) ? "Oblique" : "") ); + free_res = true; + } + } else if ( strcmp(key,"family") == 0 ) { + res=(char*)pango_font_description_get_family(descr); + free_res=false; + } else if ( strcmp(key,"style") == 0 ) { + PangoStyle v=pango_font_description_get_style(descr); + if ( v == PANGO_STYLE_ITALIC ) { + res="italic"; + } else if ( v == PANGO_STYLE_OBLIQUE ) { + res="oblique"; + } else { + res="normal"; + } + free_res=false; + } else if ( strcmp(key,"weight") == 0 ) { + PangoWeight v=pango_font_description_get_weight(descr); + if ( v <= PANGO_WEIGHT_ULTRALIGHT ) { + res="200"; + } else if ( v <= PANGO_WEIGHT_LIGHT ) { + res="300"; + } else if ( v <= PANGO_WEIGHT_NORMAL ) { + res="normal"; + } else if ( v <= PANGO_WEIGHT_BOLD ) { + res="bold"; + } else { + res="800"; + } + free_res=false; + } else if ( strcmp(key,"stretch") == 0 ) { + PangoStretch v=pango_font_description_get_stretch(descr); + if ( v <= PANGO_STRETCH_EXTRA_CONDENSED ) { + res="extra-condensed"; + } else if ( v <= PANGO_STRETCH_CONDENSED ) { + res="condensed"; + } else if ( v <= PANGO_STRETCH_SEMI_CONDENSED ) { + res="semi-condensed"; + } else if ( v <= PANGO_STRETCH_NORMAL ) { + res="normal"; + } else if ( v <= PANGO_STRETCH_SEMI_EXPANDED ) { + res="semi-expanded"; + } else if ( v <= PANGO_STRETCH_EXPANDED ) { + res="expanded"; + } else { + res="extra-expanded"; + } + free_res=false; + } else if ( strcmp(key,"variant") == 0 ) { + PangoVariant v=pango_font_description_get_variant(descr); + if ( v == PANGO_VARIANT_SMALL_CAPS ) { + res="small-caps"; + } else { + res="normal"; + } + free_res=false; + } else { + res = NULL; + free_res=false; + } + if ( res == NULL ) { + if ( size > 0 ) str[0]=0; + return 0; + } + + if (res) { + unsigned int len=strlen(res); + unsigned int rlen=(size-1 0 ) memcpy(str,res,rlen); + if ( size > 0 ) str[rlen]=0; + } + if (free_res) free(res); + return len; + } + return 0; +} + +void font_instance::InitTheFace() +{ +#ifdef USE_PANGO_WIN32 + if ( !theFace ) { + LOGFONT *lf=pango_win32_font_logfont(pFont); + g_assert(lf != NULL); + theFace=pango_win32_font_cache_load(daddy->pangoFontCache,lf); + g_free(lf); + } + XFORM identity = {1.0, 0.0, 0.0, 1.0, 0.0, 0.0}; + SetWorldTransform(daddy->hScreenDC, &identity); + SetGraphicsMode(daddy->hScreenDC, GM_COMPATIBLE); + SelectObject(daddy->hScreenDC,theFace); +#else + theFace=pango_ft2_font_get_face(pFont); + FT_Select_Charmap(theFace,ft_encoding_unicode) && FT_Select_Charmap(theFace,ft_encoding_symbol); +#endif +} + +void font_instance::FreeTheFace() +{ +#ifdef USE_PANGO_WIN32 + SelectObject(daddy->hScreenDC,GetStockObject(SYSTEM_FONT)); + pango_win32_font_cache_unload(daddy->pangoFontCache,theFace); +#endif + theFace=NULL; +} + +void font_instance::InstallFace(PangoFont* iFace) +{ + if ( !iFace ) + return; + pFont=iFace; + + InitTheFace(); + + if ( pFont && IsOutlineFont() == false ) { + FreeTheFace(); + if ( pFont ) g_object_unref(pFont); + pFont=NULL; + } +} + +bool font_instance::IsOutlineFont(void) +{ + if ( pFont == NULL ) return false; + InitTheFace(); +#ifdef USE_PANGO_WIN32 + TEXTMETRIC tm; + return GetTextMetrics(daddy->hScreenDC,&tm) && tm.tmPitchAndFamily&TMPF_TRUETYPE; +#else + return FT_IS_SCALABLE(theFace); +#endif +} + +int font_instance::MapUnicodeChar(gunichar c) +{ + if ( pFont == NULL ) return 0; +#ifdef USE_PANGO_WIN32 + return pango_win32_font_get_glyph_index(pFont,c); +#else + int res=0; + theFace=pango_ft2_font_get_face(pFont); + if ( c > 0xf0000 ) { + res=CLAMP(c,0xf0000,0x1fffff)-0xf0000; + } else { + res=FT_Get_Char_Index(theFace, c); + } + return res; +#endif +} + + +#ifdef USE_PANGO_WIN32 +static inline NR::Point pointfx_to_nrpoint(const POINTFX &p, double scale) +{ + return NR::Point(*(long*)&p.x / 65536.0 * scale, + *(long*)&p.y / 65536.0 * scale); +} +#endif + +void font_instance::LoadGlyph(int glyph_id) +{ + if ( pFont == NULL ) return; + InitTheFace(); +#ifndef USE_PANGO_WIN32 + if ( theFace->units_per_EM == 0 ) return; // bitmap font +#endif + + if ( id_to_no.find(glyph_id) == id_to_no.end() ) { + if ( nbGlyph >= maxGlyph ) { + maxGlyph=2*nbGlyph+1; + glyphs=(font_glyph*)realloc(glyphs,maxGlyph*sizeof(font_glyph)); + } + font_glyph n_g; + n_g.outline=NULL; + n_g.artbpath=NULL; + n_g.bbox[0]=n_g.bbox[1]=n_g.bbox[2]=n_g.bbox[3]=0; + bool doAdd=false; + +#ifdef USE_PANGO_WIN32 + MAT2 identity = {{0,1},{0,0},{0,0},{0,1}}; + OUTLINETEXTMETRIC otm; + GetOutlineTextMetrics(daddy->hScreenDC, sizeof(otm), &otm); + GLYPHMETRICS metrics; + DWORD bufferSize=GetGlyphOutline (daddy->hScreenDC, glyph_id, GGO_GLYPH_INDEX | GGO_NATIVE | GGO_UNHINTED, &metrics, 0, NULL, &identity); + double scale=1.0/daddy->fontSize; + n_g.h_advance=metrics.gmCellIncX*scale; + n_g.v_advance=otm.otmTextMetrics.tmHeight*scale; + n_g.h_width=metrics.gmBlackBoxX*scale; + n_g.v_width=metrics.gmBlackBoxY*scale; + n_g.outline=NULL; + if ( bufferSize == GDI_ERROR) { + // shit happened + } else if ( bufferSize == 0) { + // character has no visual representation, but is valid (eg whitespace) + doAdd=true; + } else { + std::auto_ptr buffer(new char[bufferSize]); + if ( GetGlyphOutline (daddy->hScreenDC, glyph_id, GGO_GLYPH_INDEX | GGO_NATIVE | GGO_UNHINTED, &metrics, bufferSize, buffer.get(), &identity) <= 0 ) { + // shit happened + } else { + // Platform SDK is rubbish, read KB87115 instead + n_g.outline=new Path; + DWORD polyOffset=0; + while ( polyOffset < bufferSize ) { + TTPOLYGONHEADER const *polyHeader=(TTPOLYGONHEADER const *)(buffer.get()+polyOffset); + if (polyOffset+polyHeader->cb > bufferSize) break; + + if (polyHeader->dwType == TT_POLYGON_TYPE) { + n_g.outline->MoveTo(pointfx_to_nrpoint(polyHeader->pfxStart, scale)); + DWORD curveOffset=polyOffset+sizeof(TTPOLYGONHEADER); + + while ( curveOffset < polyOffset+polyHeader->cb ) { + TTPOLYCURVE const *polyCurve=(TTPOLYCURVE const *)(buffer.get()+curveOffset); + POINTFX const *p=polyCurve->apfx; + POINTFX const *endp=p+polyCurve->cpfx; + + switch (polyCurve->wType) { + case TT_PRIM_LINE: + while ( p != endp ) + n_g.outline->LineTo(pointfx_to_nrpoint(*p++, scale)); + break; + + case TT_PRIM_QSPLINE: + { + g_assert(polyCurve->cpfx >= 2); + endp -= 2; + NR::Point this_mid=pointfx_to_nrpoint(p[0], scale); + while ( p != endp ) { + NR::Point next_mid=pointfx_to_nrpoint(p[1], scale); + n_g.outline->BezierTo((next_mid+this_mid)/2); + n_g.outline->IntermBezierTo(this_mid); + n_g.outline->EndBezierTo(); + ++p; + this_mid=next_mid; + } + n_g.outline->BezierTo(pointfx_to_nrpoint(p[1], scale)); + n_g.outline->IntermBezierTo(this_mid); + n_g.outline->EndBezierTo(); + break; + } + + case 3: // TT_PRIM_CSPLINE + g_assert(polyCurve->cpfx % 3 == 0); + while ( p != endp ) { + n_g.outline->CubicTo(pointfx_to_nrpoint(p[2], scale), pointfx_to_nrpoint(p[0], scale), pointfx_to_nrpoint(p[1], scale)); + p += 3; + } + break; + } + curveOffset += sizeof(TTPOLYCURVE)+sizeof(POINTFX)*(polyCurve->cpfx-1); + } + n_g.outline->Close(); + } + polyOffset += polyHeader->cb; + } + doAdd=true; + } + } +#else + if (FT_Load_Glyph (theFace, glyph_id, FT_LOAD_NO_SCALE | FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP)) { + // shit happened + } else { + if ( FT_HAS_HORIZONTAL(theFace) ) { + n_g.h_advance=((double)theFace->glyph->metrics.horiAdvance)/((double)theFace->units_per_EM); + n_g.h_width=((double)theFace->glyph->metrics.width)/((double)theFace->units_per_EM); + } else { + n_g.h_width=n_g.h_advance=((double)(theFace->bbox.xMax-theFace->bbox.xMin))/((double)theFace->units_per_EM); + } + if ( FT_HAS_VERTICAL(theFace) ) { + n_g.v_advance=((double)theFace->glyph->metrics.vertAdvance)/((double)theFace->units_per_EM); + n_g.v_width=((double)theFace->glyph->metrics.height)/((double)theFace->units_per_EM); + } else { + n_g.v_width=n_g.v_advance=((double)theFace->height)/((double)theFace->units_per_EM); + } + if ( theFace->glyph->format == ft_glyph_format_outline ) { + FT_Outline_Funcs ft2_outline_funcs = { + ft2_move_to, + ft2_line_to, + ft2_conic_to, + ft2_cubic_to, + 0, 0 + }; + n_g.outline=new Path; + ft2_to_liv tData; + tData.theP=n_g.outline; + tData.scale=1.0/((double)theFace->units_per_EM); + tData.last=NR::Point(0,0); + FT_Outline_Decompose (&theFace->glyph->outline, &ft2_outline_funcs, &tData); + } + doAdd=true; + } +#endif + + if ( doAdd ) { + if ( n_g.outline ) { + n_g.outline->FastBBox(n_g.bbox[0],n_g.bbox[1],n_g.bbox[2],n_g.bbox[3]); + n_g.artbpath=n_g.outline->MakeArtBPath(); + } + glyphs[nbGlyph]=n_g; + id_to_no[glyph_id]=nbGlyph; + nbGlyph++; + } + } else { + } +} + +bool font_instance::FontMetrics(double &ascent,double &descent,double &leading) +{ + if ( pFont == NULL ) return false; + InitTheFace(); + if ( theFace == NULL ) return false; +#ifdef USE_PANGO_WIN32 + OUTLINETEXTMETRIC otm; + if ( !GetOutlineTextMetrics(daddy->hScreenDC,sizeof(otm),&otm) ) return false; + double scale=1.0/daddy->fontSize; + ascent=fabs(otm.otmAscent*scale); + descent=fabs(otm.otmDescent*scale); + leading=fabs(otm.otmLineGap*scale); +#else + if ( theFace->units_per_EM == 0 ) return false; // bitmap font + ascent=fabs(((double)theFace->ascender)/((double)theFace->units_per_EM)); + descent=fabs(((double)theFace->descender)/((double)theFace->units_per_EM)); + leading=fabs(((double)theFace->height)/((double)theFace->units_per_EM)); + leading-=ascent+descent; +#endif + return true; +} + +bool font_instance::FontSlope(double &run, double &rise) +{ + run = 0.0; + rise = 1.0; + + if ( pFont == NULL ) return false; + InitTheFace(); + if ( theFace == NULL ) return false; + +#ifdef USE_PANGO_WIN32 + OUTLINETEXTMETRIC otm; + if ( !GetOutlineTextMetrics(daddy->hScreenDC,sizeof(otm),&otm) ) return false; + run=otm.otmsCharSlopeRun; + rise=otm.otmsCharSlopeRise; +#else + if ( theFace->units_per_EM == 0 ) return false; // bitmap font + + TT_HoriHeader *hhea = (TT_HoriHeader*)FT_Get_Sfnt_Table(theFace, ft_sfnt_hhea); + if (hhea == NULL) return false; + run = hhea->caret_Slope_Run; + rise = hhea->caret_Slope_Rise; +#endif + return true; +} + +NR::Rect font_instance::BBox(int glyph_id) +{ + int no=-1; + if ( id_to_no.find(glyph_id) == id_to_no.end() ) { + LoadGlyph(glyph_id); + if ( id_to_no.find(glyph_id) == id_to_no.end() ) { + // didn't load + } else { + no=id_to_no[glyph_id]; + } + } else { + no=id_to_no[glyph_id]; + } + if ( no < 0 ) return NR::Rect(NR::Point(0,0),NR::Point(0,0)); + NR::Point rmin(glyphs[no].bbox[0],glyphs[no].bbox[1]); + NR::Point rmax(glyphs[no].bbox[2],glyphs[no].bbox[3]); + NR::Rect res(rmin,rmax); + return res; +} + +Path* font_instance::Outline(int glyph_id,Path* copyInto) +{ + int no=-1; + if ( id_to_no.find(glyph_id) == id_to_no.end() ) { + LoadGlyph(glyph_id); + if ( id_to_no.find(glyph_id) == id_to_no.end() ) { + // didn't load + } else { + no=id_to_no[glyph_id]; + } + } else { + no=id_to_no[glyph_id]; + } + if ( no < 0 ) return NULL; + Path* src_o=glyphs[no].outline; + if ( copyInto ) { + copyInto->Reset(); + copyInto->Copy(src_o); + return copyInto; + } + return src_o; +} + +void* font_instance::ArtBPath(int glyph_id) +{ + int no=-1; + if ( id_to_no.find(glyph_id) == id_to_no.end() ) { + LoadGlyph(glyph_id); + if ( id_to_no.find(glyph_id) == id_to_no.end() ) { + // didn't load + } else { + no=id_to_no[glyph_id]; + } + } else { + no=id_to_no[glyph_id]; + } + if ( no < 0 ) return NULL; + return glyphs[no].artbpath; +} + +double font_instance::Advance(int glyph_id,bool vertical) +{ + int no=-1; + if ( id_to_no.find(glyph_id) == id_to_no.end() ) { + LoadGlyph(glyph_id); + if ( id_to_no.find(glyph_id) == id_to_no.end() ) { + // didn't load + } else { + no=id_to_no[glyph_id]; + } + } else { + no=id_to_no[glyph_id]; + } + if ( no >= 0 ) { + if ( vertical ) { + return glyphs[no].v_advance; + } else { + return glyphs[no].h_advance; + } + } + return 0; +} + + +raster_font* font_instance::RasterFont(const NR::Matrix &trs,double stroke_width,bool vertical,JoinType stroke_join,ButtType stroke_cap,float miter_limit) +{ + font_style nStyle; + nStyle.transform=trs; + nStyle.vertical=vertical; + nStyle.stroke_width=stroke_width; + nStyle.stroke_cap=stroke_cap; + nStyle.stroke_join=stroke_join; + nStyle.nbDash=0; + nStyle.dash_offset=0; + nStyle.dashes=NULL; + return RasterFont(nStyle); +} + +raster_font* font_instance::RasterFont(const font_style &inStyle) +{ + raster_font *res=NULL; + double *savDashes=NULL; + font_style nStyle=inStyle; + // for some evil reason font_style doesn't have a copy ctor, so the + // stuff that should be done there is done here instead (because the + // raster_font ctor copies nStyle). + if ( nStyle.stroke_width > 0 && nStyle.nbDash > 0 && nStyle.dashes ) { + savDashes=nStyle.dashes; + nStyle.dashes=(double*)malloc(nStyle.nbDash*sizeof(double)); + memcpy(nStyle.dashes,savDashes,nStyle.nbDash*sizeof(double)); + } + if ( loadedStyles.find(nStyle) == loadedStyles.end() ) { + raster_font *nR = new raster_font(nStyle); + nR->Ref(); + nR->daddy=this; + loadedStyles[nStyle]=nR; + res=nR; + if ( res ) Ref(); + } else { + res=loadedStyles[nStyle]; + res->Ref(); + if ( nStyle.dashes ) free(nStyle.dashes); // since they're not taken by a new rasterfont + } + nStyle.dashes=savDashes; + return res; +} + +void font_instance::RemoveRasterFont(raster_font* who) +{ + if ( who == NULL ) return; + if ( loadedStyles.find(who->style) == loadedStyles.end() ) { + //g_print("RemoveRasterFont failed \n"); + // not found + } else { + loadedStyles.erase(loadedStyles.find(who->style)); + //g_print("RemoveRasterFont\n"); + Unref(); + } +} + + + +/* + 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/libnrtype/Layout-TNG-Compute.cpp b/src/libnrtype/Layout-TNG-Compute.cpp new file mode 100755 index 000000000..0fbbb6f0c --- /dev/null +++ b/src/libnrtype/Layout-TNG-Compute.cpp @@ -0,0 +1,1514 @@ +/* + * Inkscape::Text::Layout::Calculator - text layout engine meaty bits + * + * Authors: + * Richard Hughes + * + * Copyright (C) 2005 Richard Hughes + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ +#include "Layout-TNG.h" +#include "style.h" +#include "font-instance.h" +#include "svg/svg-length.h" +#include "sp-object.h" +#include "Layout-TNG-Scanline-Maker.h" + +namespace Inkscape { +namespace Text { + +//#define IFTRACE(_code) _code +//#define TRACE(_args) g_print _args +#define IFTRACE(_code) +#define TRACE(_args) + +// ******* enum conversion tables +static Layout::EnumConversionItem const enum_convert_spstyle_direction_to_pango_direction[] = { + {SP_CSS_WRITING_MODE_LR_TB, PANGO_DIRECTION_LTR}, + {SP_CSS_WRITING_MODE_RL_TB, PANGO_DIRECTION_RTL}, + {SP_CSS_WRITING_MODE_TB_LR, PANGO_DIRECTION_LTR}}; // this is correct + +static Layout::EnumConversionItem const enum_convert_spstyle_direction_to_my_direction[] = { + {SP_CSS_WRITING_MODE_LR_TB, Layout::LEFT_TO_RIGHT}, + {SP_CSS_WRITING_MODE_RL_TB, Layout::RIGHT_TO_LEFT}, + {SP_CSS_WRITING_MODE_TB_LR, Layout::LEFT_TO_RIGHT}}; // this is correct + +/** \brief private to Layout. Does the real work of text flowing. + +This class does a standard greedy paragraph wrapping algorithm. + +Very high-level overview: + +
+foreach(paragraph) {
+  call pango_itemize() (_buildPangoItemizationForPara())
+  break into spans, without dealing with wrapping (_buildSpansForPara())
+  foreach(line in flow shape) {
+    foreach(chunk in flow shape) {   (in _buildChunksInScanRun())
+      // this inner loop in _measureUnbrokenSpan()
+      if the line height changed discard the line and start again
+      keep adding characters until we run out of space in the chunk, then back up to the last word boundary
+      (do sensible things if there is no previous word break)
+    }
+    push all the glyphs, chars, spans, chunks and line to output (not completely trivial because we must draw rtl in character order) (in _outputLine())
+  }
+  push the paragraph (in calculate())
+}
+
+ +...and all of that needs to work vertically too, and with all the little details that make life annoying +*/ +class Layout::Calculator +{ + class SpanPosition; + friend class SpanPosition; + + Layout &_flow; + + ScanlineMaker *_scanline_maker; + + unsigned _current_shape_index; /// index into Layout::_input_wrap_shapes + + PangoContext *_pango_context; + + Direction _block_progression; + + /** for y= attributes in tspan elements et al, we do the adjustment by moving each + glyph individually by this number. The spec means that this is maintained across + paragraphs. */ + double _y_offset; + + /** to stop pango from hinting its output, the font factory creates all fonts very large. + All numbers returned from pango have to be divided by this number \em and divided by + PANGO_SCALE. See font_factory::font_factory(). */ + double _font_factory_size_multiplier; + + /** Temporary storage associated with each item in Layout::_input_stream. */ + struct InputItemInfo { + bool in_sub_flow; + Layout *sub_flow; // this is only set for the first input item in a sub-flow + + InputItemInfo() : in_sub_flow(false), sub_flow(NULL) {} + void free(); + }; + + /** Temporary storage associated with each item returned by the call to + pango_itemize(). */ + struct PangoItemInfo { + PangoItem *item; + font_instance *font; + + PangoItemInfo() : item(NULL), font(NULL) {} + void free(); + }; + + /** These spans have approximately the same definition as that used for + Layout::Span (constant font, direction, etc), except that they are from + before we have located the line breaks, so bear no relation to chunks. + They are guaranteed to be in at most one PangoItem (spans with no text in + them will not have an associated PangoItem), exactly one input source and + will only have one change of x, y, dx, dy or rotate attribute, which will + be at the beginning. An UnbrokenSpan can cross a chunk boundary, c.f. + BrokenSpan. */ + struct UnbrokenSpan { + PangoGlyphString *glyph_string; + int pango_item_index; /// index into _para.pango_items, or -1 if this is style only + unsigned input_index; /// index into Layout::_input_stream + Glib::ustring::const_iterator input_stream_first_character; + double font_size; + LineHeight line_height; + double line_height_multiplier; /// calculated from the font-height css property + unsigned text_bytes; + unsigned char_index_in_para; /// the index of the first character in this span in the paragraph, for looking up char_attributes + SVGLength x, y, dx, dy, rotate; // these are reoriented copies of the attributes. We change span when we encounter one. + + UnbrokenSpan() : glyph_string(NULL) {} + void free() {if (glyph_string) pango_glyph_string_free(glyph_string); glyph_string = NULL;} + }; + + /** a useful little iterator for moving char-by-char across spans. */ + struct UnbrokenSpanPosition { + std::vector::iterator iter_span; + unsigned char_byte; + unsigned char_index; + + void increment(); ///< Step forward by one character. + + inline bool operator== (UnbrokenSpanPosition const &other) const + {return char_byte == other.char_byte && iter_span == other.iter_span;} + inline bool operator!= (UnbrokenSpanPosition const &other) const + {return char_byte != other.char_byte || iter_span != other.iter_span;} + }; + + /** The line breaking algorithm will convert each UnbrokenSpan into one + or more of these. A BrokenSpan will never cross a chunk boundary, c.f. + UnbrokenSpan. */ + struct BrokenSpan { + UnbrokenSpanPosition start; + UnbrokenSpanPosition end; // the end of this will always be the same as the start of the next + unsigned start_glyph_index; + unsigned end_glyph_index; + double width; + unsigned whitespace_count; + bool ends_with_whitespace; + double each_whitespace_width; + void setZero(); + }; + + /** The definition of a chunk used here is the same as that used in Layout. */ + struct ChunkInfo { + std::vector broken_spans; + double scanrun_width; + double text_width; ///< Total width used by the text (excluding justification). + double x; + int whitespace_count; + }; + + /** Used to provide storage for anything that applies to the current + paragraph only. Since we're only processing one paragraph at a time, + there's only one instantiation of this struct, on the stack of + calculate(). */ + struct ParagraphInfo { + unsigned first_input_index; ///< Index into Layout::_input_stream. + Direction direction; + Alignment alignment; + std::vector input_items; + std::vector pango_items; + std::vector char_attributes; ///< For every character in the paragraph. + std::vector unbroken_spans; + + template static void free_sequence(T &seq); + void free(); + }; + +/* *********************************************************************************************************/ +// Initialisation of ParagraphInfo structure + + +#if 0 /* unused */ + void _initialiseInputItems(ParagraphInfo *para) const; +#endif + + void _buildPangoItemizationForPara(ParagraphInfo *para) const; + + static void _computeFontLineHeight(font_instance *font, double font_size, + SPStyle const *style, LineHeight *line_height, + double *line_height_multiplier); + + unsigned _buildSpansForPara(ParagraphInfo *para) const; + +/* *********************************************************************************************************/ +// Per-line functions + + + bool _goToNextWrapShape(); + + bool _findChunksForLine(ParagraphInfo const ¶, UnbrokenSpanPosition *start_span_pos, + std::vector *chunk_info, LineHeight *line_height); + + static inline PangoLogAttr const &_charAttributes(ParagraphInfo const ¶, + UnbrokenSpanPosition const &span_pos) + { + return para.char_attributes[span_pos.iter_span->char_index_in_para + span_pos.char_index]; + } + + bool _buildChunksInScanRun(ParagraphInfo const ¶, + UnbrokenSpanPosition const &start_span_pos, + ScanlineMaker::ScanRun const &scan_run, + std::vector *chunk_info, + LineHeight *line_height) const; + + /** computes the width of a single UnbrokenSpan (pointed to by span->start.iter_span) + and outputs its vital statistics into the other fields of \a span. + Measuring will stop if maximum_width is reached and in that case the + function will return false. In other cases where a line break must be + done immediately the function will also return false. On return + \a last_break_span will contain the vital statistics for the span only + up to the last line breaking change. If there are no line breaking + characters in the span then \a last_break_span will not be altered. + Similarly, \a last_emergency_break_span will contain the vital + statistics for the span up to the last inter-character boundary, + or will be unaltered if there is none. */ + bool _measureUnbrokenSpan(ParagraphInfo const ¶, BrokenSpan *span, BrokenSpan *last_break_span, BrokenSpan *last_emergency_break_span, double maximum_width) const + { + span->setZero(); + + if (span->start.iter_span->dx._set && span->start.char_byte == 0) + span->width += span->start.iter_span->dx.computed; + + if (span->start.iter_span->pango_item_index == -1) { + // if this is a style-only span there's no text in it + // so we don't need to do very much at all + span->end.iter_span++; + return true; + } + + if (_flow._input_stream[span->start.iter_span->input_index]->Type() == CONTROL_CODE) { + InputStreamControlCode const *control_code = static_cast(_flow._input_stream[span->start.iter_span->input_index]); + if (control_code->code == SHAPE_BREAK || control_code->code == PARAGRAPH_BREAK) { + *last_emergency_break_span = *last_break_span = *span; + return false; + } + if (control_code->code == ARBITRARY_GAP) { + if (span->width + control_code->width > maximum_width) + return false; + TRACE(("fitted control code, width = %f\n", control_code->width)); + span->width += control_code->width; + span->end.increment(); + } + return true; + + } + + if (_flow._input_stream[span->start.iter_span->input_index]->Type() != TEXT_SOURCE) + return true; // never happens + + InputStreamTextSource const *text_source = static_cast(_flow._input_stream[span->start.iter_span->input_index]); + + if (_directions_are_orthogonal(_block_progression, text_source->styleGetBlockProgression())) { + // TODO: block-progression altered in the middle + // Measure the precomputed flow from para.input_items + span->end.iter_span++; // for now, skip to the next span + return true; + } + + // a normal span going with a normal block-progression + double font_size_multiplier = span->start.iter_span->font_size / (PANGO_SCALE * _font_factory_size_multiplier); + double soft_hyphen_glyph_width = 0.0; + bool soft_hyphen_in_word = false; + bool is_soft_hyphen = false; + IFTRACE(int char_count = 0); + + // if we're not at the start of the span we need to pre-init glyph_index + span->start_glyph_index = 0; + while (span->start_glyph_index < (unsigned)span->start.iter_span->glyph_string->num_glyphs + && span->start.iter_span->glyph_string->log_clusters[span->start_glyph_index] < (int)span->start.char_byte) + span->start_glyph_index++; + span->end_glyph_index = span->start_glyph_index; + + // go char-by-char summing the width, while keeping track of the previous break point + do { + PangoLogAttr const &char_attributes = _charAttributes(para, span->end); + + if (char_attributes.is_mandatory_break) { + *last_emergency_break_span = *last_break_span = *span; + TRACE(("span %d end of para; width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count)); + return false; + } + + if (char_attributes.is_line_break) { + // a suitable position to break at, record where we are + *last_emergency_break_span = *last_break_span = *span; + if (soft_hyphen_in_word) { + // if there was a previous soft hyphen we're not going to need it any more so we can remove it + span->width -= soft_hyphen_glyph_width; + if (!is_soft_hyphen) + soft_hyphen_in_word = false; + } + } else if (char_attributes.is_char_break) { + *last_emergency_break_span = *span; + } + // todo: break between chars if necessary (ie no word breaks present) when doing rectangular flowing + + // sum the glyph widths, letter spacing and word spacing to get the character width + double char_width = 0.0; + while (span->end_glyph_index < (unsigned)span->end.iter_span->glyph_string->num_glyphs + && span->end.iter_span->glyph_string->log_clusters[span->end_glyph_index] <= (int)span->end.char_byte) { + if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT) + char_width += span->start.iter_span->font_size * para.pango_items[span->end.iter_span->pango_item_index].font->Advance(span->end.iter_span->glyph_string->glyphs[span->end_glyph_index].glyph, true); + else + char_width += font_size_multiplier * span->end.iter_span->glyph_string->glyphs[span->end_glyph_index].geometry.width; + span->end_glyph_index++; + } + if (char_attributes.is_cursor_position) + char_width += text_source->style->letter_spacing.computed; + if (char_attributes.is_white) + char_width += text_source->style->word_spacing.computed; + span->width += char_width; + IFTRACE(char_count++); + + if (char_attributes.is_white) { + span->whitespace_count++; + span->each_whitespace_width = char_width; + } + span->ends_with_whitespace = char_attributes.is_white; + + is_soft_hyphen = (UNICODE_SOFT_HYPHEN == *Glib::ustring::const_iterator(span->end.iter_span->input_stream_first_character.base() + span->end.char_byte)); + if (is_soft_hyphen) + soft_hyphen_glyph_width = char_width; + + span->end.increment(); + + if (span->width > maximum_width && !char_attributes.is_white) { // whitespaces don't matter, we can put as many as we want at eol + TRACE(("span %d exceeded scanrun; width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count)); + return false; + } + + } while (span->end.char_byte != 0); // while we haven't wrapped to the next span + TRACE(("fitted span %d width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count)); + return true; + } + +/* *********************************************************************************************************/ +// Per-line functions (output) + + /** Uses the paragraph alignment and the chunk information to work out + where the actual left of the final chunk must be. Also sets + \a add_to_each_whitespace to be the amount of x to add at each + whitespace character to make full justification work. */ + double _getChunkLeftWithAlignment(ParagraphInfo const ¶, std::vector::const_iterator it_chunk, double *add_to_each_whitespace) const + { + *add_to_each_whitespace = 0.0; + if (_flow._input_wrap_shapes.empty()) { + switch (para.alignment) { + case FULL: + case LEFT: + default: + return it_chunk->x; + case RIGHT: + return it_chunk->x - it_chunk->text_width; + case CENTER: + return it_chunk->x - it_chunk->text_width / 2; + } + } + + switch (para.alignment) { + case FULL: + if (!it_chunk->broken_spans.empty() + && it_chunk->broken_spans.back().end.iter_span != para.unbroken_spans.end()) { // don't justify the last chunk in the para + if (it_chunk->whitespace_count) + *add_to_each_whitespace = (it_chunk->scanrun_width - it_chunk->text_width) / it_chunk->whitespace_count; + //else + //add_to_each_charspace = something + } + return it_chunk->x; + case LEFT: + default: + return it_chunk->x; + case RIGHT: + return it_chunk->x + it_chunk->scanrun_width - it_chunk->text_width; + case CENTER: + return it_chunk->x + (it_chunk->scanrun_width - it_chunk->text_width) / 2; + } + } + + /** Once we've got here we have finished making changes to the line and + are ready to output the final result to #_flow. This method takes its + input parameters and does that. + */ + void _outputLine(ParagraphInfo const ¶, LineHeight const &line_height, std::vector const &chunk_info) + { + if (chunk_info.empty()) { + TRACE(("line too short to fit anything on it, go to next\n")); + return; + } + + // we've finished fiddling about with ascents and descents: create the output + TRACE(("found line fit; creating output\n")); + Layout::Line new_line; + new_line.in_paragraph = _flow._paragraphs.size() - 1; + new_line.baseline_y = _scanline_maker->yCoordinate() + line_height.ascent; + new_line.in_shape = _current_shape_index; + _flow._lines.push_back(new_line); + + for (std::vector::const_iterator it_chunk = chunk_info.begin() ; it_chunk != chunk_info.end() ; it_chunk++) { + + double add_to_each_whitespace; + // add the chunk to the list + Layout::Chunk new_chunk; + new_chunk.in_line = _flow._lines.size() - 1; + new_chunk.left_x = _getChunkLeftWithAlignment(para, it_chunk, &add_to_each_whitespace); + // we may also have y move orders to deal with here (dx, dy and rotate are done per span) + if (!it_chunk->broken_spans.empty() // this one only happens for empty paragraphs + && it_chunk->broken_spans.front().start.char_byte == 0 + && it_chunk->broken_spans.front().start.iter_span->y._set) { + // if this is the start of a line, we should change the baseline rather than each glyph individually + if (_flow._characters.empty() || _flow._characters.back().chunk(&_flow).in_line != _flow._lines.size() - 1) { + new_line.baseline_y = it_chunk->broken_spans.front().start.iter_span->y.computed; + _flow._lines.back().baseline_y = new_line.baseline_y; + _y_offset = 0.0; + _scanline_maker->setNewYCoordinate(new_line.baseline_y - line_height.ascent); + } else + _y_offset = it_chunk->broken_spans.front().start.iter_span->y.computed - new_line.baseline_y; + } + _flow._chunks.push_back(new_chunk); + + double x; + double direction_sign; + Direction previous_direction = para.direction; + double counter_directional_width_remaining = 0.0; + float glyph_rotate = 0.0; + if (para.direction == LEFT_TO_RIGHT) { + direction_sign = +1.0; + x = 0.0; + } else { + direction_sign = -1.0; + if (para.alignment == FULL && !_flow._input_wrap_shapes.empty()) + x = it_chunk->scanrun_width; + else + x = it_chunk->text_width; + } + + for (std::vector::const_iterator it_span = it_chunk->broken_spans.begin() ; it_span != it_chunk->broken_spans.end() ; it_span++) { + // begin adding spans to the list + UnbrokenSpan const &unbroken_span = *it_span->start.iter_span; + + if (it_span->start.char_byte == 0) { + // start of an unbroken span, we might have dx, dy or rotate still to process (x and y are done per chunk) + if (unbroken_span.dx._set) x += unbroken_span.dx.computed; + if (unbroken_span.dy._set) _y_offset += unbroken_span.dy.computed; + if (unbroken_span.rotate._set) glyph_rotate = unbroken_span.rotate.computed * (M_PI/180); + } + + if (_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE + && unbroken_span.pango_item_index == -1) { + // style only, nothing to output + continue; + } + + Layout::Span new_span; + double x_in_span = 0.0; + + new_span.in_chunk = _flow._chunks.size() - 1; + new_span.line_height = unbroken_span.line_height; + new_span.in_input_stream_item = unbroken_span.input_index; + new_span.baseline_shift = _y_offset; + new_span.block_progression = _block_progression; + if (_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE) { + new_span.font = para.pango_items[unbroken_span.pango_item_index].font; + new_span.font->Ref(); + new_span.font_size = unbroken_span.font_size; + new_span.direction = para.pango_items[unbroken_span.pango_item_index].item->analysis.level & 1 ? RIGHT_TO_LEFT : LEFT_TO_RIGHT; + new_span.input_stream_first_character = Glib::ustring::const_iterator(unbroken_span.input_stream_first_character.base() + it_span->start.char_byte); + } else { // a control code + new_span.font = NULL; + new_span.font_size = new_span.line_height.ascent + new_span.line_height.descent; + new_span.direction = para.direction; + } + + if (new_span.direction == para.direction) { + x -= counter_directional_width_remaining; + counter_directional_width_remaining = 0.0; + } else if (new_span.direction != previous_direction) { + // measure width of spans we need to switch round + counter_directional_width_remaining = 0.0; + std::vector::const_iterator it_following_span; + for (it_following_span = it_span ; it_following_span != it_chunk->broken_spans.end() ; it_following_span++) { + Layout::Direction following_span_progression = static_cast(_flow._input_stream[it_following_span->start.iter_span->input_index])->styleGetBlockProgression(); + if (!Layout::_directions_are_orthogonal(following_span_progression, _block_progression)) { + if (it_following_span->start.iter_span->pango_item_index == -1) { // when the span came from a control code + if (new_span.direction != para.direction) break; + } else + if (new_span.direction != (para.pango_items[it_following_span->start.iter_span->pango_item_index].item->analysis.level & 1 ? RIGHT_TO_LEFT : LEFT_TO_RIGHT)) break; + } + counter_directional_width_remaining += direction_sign * (it_following_span->width + it_following_span->whitespace_count * add_to_each_whitespace); + } + x += counter_directional_width_remaining; + counter_directional_width_remaining = 0.0; // we want to go increasingly negative + } + new_span.x_start = x; + + if (_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE) { + // the span is set up, push the glyphs and chars + InputStreamTextSource const *text_source = static_cast(_flow._input_stream[unbroken_span.input_index]); + Glib::ustring::const_iterator iter_source_text = Glib::ustring::const_iterator(unbroken_span.input_stream_first_character.base() + it_span->start.char_byte) ; + unsigned char_index_in_unbroken_span = it_span->start.char_index; + unsigned cluster_start_char_index = _flow._characters.size(); + double font_size_multiplier = new_span.font_size / (PANGO_SCALE * _font_factory_size_multiplier); + + for (unsigned glyph_index = it_span->start_glyph_index ; glyph_index < it_span->end_glyph_index ; glyph_index++) { + unsigned char_byte = iter_source_text.base() - unbroken_span.input_stream_first_character.base(); + if (unbroken_span.glyph_string->glyphs[glyph_index].attr.is_cluster_start) + cluster_start_char_index = _flow._characters.size(); + + if (unbroken_span.glyph_string->log_clusters[glyph_index] < (int)unbroken_span.text_bytes + && *iter_source_text == UNICODE_SOFT_HYPHEN + && it_span + 1 != it_chunk->broken_spans.end() + && glyph_index + 1 != it_span->end_glyph_index) { + // if we're looking at a soft hyphen and it's not the last glyph in the + // chunk we don't draw the glyph but we still need to add to _characters + Layout::Character new_character; + new_character.in_span = _flow._spans.size(); // the span hasn't been added yet, so no -1 + new_character.char_attributes = para.char_attributes[unbroken_span.char_index_in_para + char_index_in_unbroken_span]; + new_character.in_glyph = -1; + _flow._characters.push_back(new_character); + iter_source_text++; + char_index_in_unbroken_span++; + while (glyph_index < (unsigned)unbroken_span.glyph_string->num_glyphs + && unbroken_span.glyph_string->log_clusters[glyph_index] == (int)char_byte) + glyph_index++; + glyph_index--; + continue; + } + + // create the Layout::Glyph + Layout::Glyph new_glyph; + new_glyph.glyph = unbroken_span.glyph_string->glyphs[glyph_index].glyph; + new_glyph.in_character = cluster_start_char_index; + new_glyph.rotation = glyph_rotate; + + /* put something like this back in when we do glyph-rotation-horizontal/vertical + if (new_span.block_progression == LEFT_TO_RIGHT || new_span.block_progression == RIGHT_TO_LEFT) { + new_glyph.x += new_span.line_height.ascent; + new_glyph.y -= unbroken_span.glyph_string->glyphs[glyph_index].geometry.width * font_size_multiplier * 0.5; + new_glyph.width = new_span.line_height.ascent + new_span.line_height.descent; + } else */ + + if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT) { + new_glyph.x = x + unbroken_span.glyph_string->glyphs[glyph_index].geometry.x_offset * font_size_multiplier + new_span.line_height.ascent; + new_glyph.y = _y_offset + (unbroken_span.glyph_string->glyphs[glyph_index].geometry.y_offset - unbroken_span.glyph_string->glyphs[glyph_index].geometry.width * 0.5) * font_size_multiplier; + new_glyph.width = new_span.font_size * para.pango_items[unbroken_span.pango_item_index].font->Advance(unbroken_span.glyph_string->glyphs[glyph_index].glyph, true); + } else { + new_glyph.x = x + unbroken_span.glyph_string->glyphs[glyph_index].geometry.x_offset * font_size_multiplier; + new_glyph.y = _y_offset + unbroken_span.glyph_string->glyphs[glyph_index].geometry.y_offset * font_size_multiplier; + new_glyph.width = unbroken_span.glyph_string->glyphs[glyph_index].geometry.width * font_size_multiplier; + if (new_glyph.width == 0) + new_glyph.width = new_span.font_size * para.pango_items[unbroken_span.pango_item_index].font->Advance(unbroken_span.glyph_string->glyphs[glyph_index].glyph, false); + // for some reason pango returns zero width for invalid glyph characters (those empty boxes), so go to freetype for the info + } + if (new_span.direction == RIGHT_TO_LEFT) { + // pango wanted to give us glyphs in visual order but we refused, so we need to work + // out where the cluster start is ourselves + double cluster_width = 0.0; + for (unsigned rtl_index = glyph_index; rtl_index < it_span->end_glyph_index ; rtl_index++) { + if (unbroken_span.glyph_string->glyphs[rtl_index].attr.is_cluster_start && rtl_index != glyph_index) + break; + if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT) + cluster_width += new_span.font_size * para.pango_items[unbroken_span.pango_item_index].font->Advance(unbroken_span.glyph_string->glyphs[rtl_index].glyph, true); + else + cluster_width += font_size_multiplier * unbroken_span.glyph_string->glyphs[rtl_index].geometry.width; + } + new_glyph.x -= cluster_width; + } + _flow._glyphs.push_back(new_glyph); + + // create the Layout::Character(s) + double advance_width = new_glyph.width; + unsigned end_byte; + if (glyph_index == (unsigned)unbroken_span.glyph_string->num_glyphs - 1) + end_byte = it_span->start.iter_span->text_bytes; + else { + // output chars for the whole cluster that is commenced by this glyph + if (unbroken_span.glyph_string->glyphs[glyph_index].attr.is_cluster_start) { + int next_cluster_glyph_index = glyph_index + 1; + while (next_cluster_glyph_index < unbroken_span.glyph_string->num_glyphs + && !unbroken_span.glyph_string->glyphs[next_cluster_glyph_index].attr.is_cluster_start) + next_cluster_glyph_index++; + if (next_cluster_glyph_index < unbroken_span.glyph_string->num_glyphs) + end_byte = unbroken_span.glyph_string->log_clusters[next_cluster_glyph_index]; + else + end_byte = it_span->start.iter_span->text_bytes; + } else + end_byte = char_byte; // don't output any chars if we're not at the start of a cluster + } + while (char_byte < end_byte) { + Layout::Character new_character; + new_character.in_span = _flow._spans.size(); + new_character.x = x_in_span; + new_character.char_attributes = para.char_attributes[unbroken_span.char_index_in_para + char_index_in_unbroken_span]; + new_character.in_glyph = _flow._glyphs.size() - 1; + _flow._characters.push_back(new_character); + if (new_character.char_attributes.is_white) + advance_width += text_source->style->word_spacing.computed + add_to_each_whitespace; // justification + if (new_character.char_attributes.is_cursor_position) + advance_width += text_source->style->letter_spacing.computed; + iter_source_text++; + char_index_in_unbroken_span++; + char_byte = iter_source_text.base() - unbroken_span.input_stream_first_character.base(); + glyph_rotate = 0.0; + } + + advance_width *= direction_sign; + if (new_span.direction != para.direction) { + counter_directional_width_remaining -= advance_width; + x -= advance_width; + x_in_span -= advance_width; + } else { + x += advance_width; + x_in_span += advance_width; + } + } + } else if (_flow._input_stream[unbroken_span.input_index]->Type() == CONTROL_CODE) { + x += static_cast(_flow._input_stream[unbroken_span.input_index])->width; + } + + new_span.x_end = new_span.x_start + x_in_span; + _flow._spans.push_back(new_span); + previous_direction = new_span.direction; + } + // end adding spans to the list, on to the next chunk... + } + TRACE(("output done\n")); + } + +/* *********************************************************************************************************/ +// Setup and top-level functions + + /** initialises the ScanlineMaker for the first shape in the flow, or + the infinite version if we're not doing wrapping. */ + void _createFirstScanlineMaker() + { + _current_shape_index = 0; + if (_flow._input_wrap_shapes.empty()) { + // create the special no-wrapping infinite scanline maker + double initial_x = 0, initial_y = 0; + InputStreamTextSource const *text_source = static_cast(_flow._input_stream.front()); + if (!text_source->x.empty()) + initial_x = text_source->x.front().computed; + if (!text_source->y.empty()) + initial_y = text_source->y.front().computed; + _scanline_maker = new InfiniteScanlineMaker(initial_x, initial_y, _block_progression); + TRACE((" wrapping disabled\n")); + } + else { + _scanline_maker = new ShapeScanlineMaker(_flow._input_wrap_shapes[_current_shape_index].shape, _block_progression); + TRACE((" begin wrap shape 0\n")); + } + } + +public: + Calculator(Layout *text_flow) + : _flow(*text_flow) {} + + bool calculate(); +}; + +/* fixme: I don't like the fact that InputItemInfo etc. use the default copy constructor and + * operator= (and thus don't involve incrementing reference counts), yet they provide a free method + * that does delete or Unref. + * + * I suggest using the garbage collector to manage deletion. + */ +void Layout::Calculator::InputItemInfo::free() +{ + if (sub_flow) { + delete sub_flow; + sub_flow = NULL; + } +} + +void Layout::Calculator::PangoItemInfo::free() +{ + if (item) { + pango_item_free(item); + item = NULL; + } + if (font) { + font->Unref(); + font = NULL; + } +} + +void Layout::Calculator::UnbrokenSpanPosition::increment() +{ + gchar const *text_base = &*iter_span->input_stream_first_character.base(); + char_byte = g_utf8_next_char(text_base + char_byte) - text_base; + char_index++; + if (char_byte == iter_span->text_bytes) { + iter_span++; + char_index = char_byte = 0; + } +} + +void Layout::Calculator::BrokenSpan::setZero() +{ + end = start; + width = 0.0; + whitespace_count = 0; + end_glyph_index = start_glyph_index = 0; + ends_with_whitespace = false; + each_whitespace_width = 0.0; +} + +template void Layout::Calculator::ParagraphInfo::free_sequence(T &seq) +{ + for (typename T::iterator it(seq.begin()); it != seq.end(); ++it) { + it->free(); + } + seq.clear(); +} + +void Layout::Calculator::ParagraphInfo::free() +{ + free_sequence(input_items); + free_sequence(pango_items); + free_sequence(unbroken_spans); +} + +///** +// * For sections of text with a block-progression different to the rest +// * of the flow, the best thing to do is to detect them in advance and +// * create child TextFlow objects with just the rotated text. In the +// * parent we then effectively use ARBITRARY_GAP fields during the +// * flowing (because we don't allow wrapping when the block-progression +// * changes) and copy the actual text in during the output phase. +// * +// * NB: this code not enabled yet. +// */ +//void Layout::Calculator::_initialiseInputItems(ParagraphInfo *para) const +//{ +// Direction prev_block_progression = _block_progression; +// int run_start_input_index = para->first_input_index; +// +// para->free_sequence(para->input_items); +// for(int input_index = para->first_input_index ; input_index < (int)_flow._input_stream.size() ; input_index++) { +// InputItemInfo input_item; +// +// input_item.in_sub_flow = false; +// input_item.sub_flow = NULL; +// if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) { +// Layout::InputStreamControlCode const *control_code = static_cast(_flow._input_stream[input_index]); +// if ( control_code->code == SHAPE_BREAK +// || control_code->code == PARAGRAPH_BREAK) +// break; // stop at the end of the paragraph +// // all other control codes we'll pick up later +// +// } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE) { +// Layout::InputStreamTextSource *text_source = static_cast(_flow._input_stream[input_index]); +// Direction this_block_progression = text_source->styleGetBlockProgression(); +// if (this_block_progression != prev_block_progression) { +// if (prev_block_progression != _block_progression) { +// // need to back up so that control codes belong outside the block-progression change +// int run_end_input_index = input_index - 1; +// while (run_end_input_index > run_start_input_index +// && _flow._input_stream[run_end_input_index]->Type() != TEXT_SOURCE) +// run_end_input_index--; +// // now create the sub-flow +// input_item.sub_flow = new Layout; +// for (int sub_input_index = run_start_input_index ; sub_input_index <= run_end_input_index ; sub_input_index++) { +// input_item.in_sub_flow = true; +// if (_flow._input_stream[sub_input_index]->Type() == CONTROL_CODE) { +// Layout::InputStreamControlCode const *control_code = static_cast(_flow._input_stream[sub_input_index]); +// input_item.sub_flow->appendControlCode(control_code->code, control_code->source_cookie, control_code->width, control_code->ascent, control_code->descent); +// } else if (_flow._input_stream[sub_input_index]->Type() == TEXT_SOURCE) { +// Layout::InputStreamTextSource *text_source = static_cast(_flow._input_stream[sub_input_index]); +// input_item.sub_flow->appendText(*text_source->text, text_source->style, text_source->source_cookie, NULL, 0, text_source->text_begin, text_source->text_end); +// Layout::InputStreamTextSource *sub_flow_text_source = static_cast(input_item.sub_flow->_input_stream.back()); +// sub_flow_text_source->x = text_source->x; // this is easier than going via optionalattrs for the appendText() call +// sub_flow_text_source->y = text_source->y; // should these actually be allowed anyway? You'll almost never get the results you expect +// sub_flow_text_source->dx = text_source->dx; // (not that it's very clear what you should expect, anyway) +// sub_flow_text_source->dy = text_source->dy; +// sub_flow_text_source->rotate = text_source->rotate; +// } +// } +// input_item.sub_flow->calculateFlow(); +// } +// run_start_input_index = input_index; +// } +// prev_block_progression = this_block_progression; +// } +// para->input_items.push_back(input_item); +// } +//} + +/** + * Take all the text from \a _para.first_input_index to the end of the + * paragraph and stitch it together so that pango_itemize() can be called on + * the whole thing. + * + * Input: para.first_input_index. + * Output: para.direction, para.pango_items, para.char_attributes. + */ +void Layout::Calculator::_buildPangoItemizationForPara(ParagraphInfo *para) const +{ + Glib::ustring para_text; + PangoAttrList *attributes_list; + unsigned input_index; + + para->free_sequence(para->pango_items); + para->char_attributes.clear(); + + TRACE(("itemizing para, first input %d\n", para->first_input_index)); + + attributes_list = pango_attr_list_new(); + for(input_index = para->first_input_index ; input_index < _flow._input_stream.size() ; input_index++) { + if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) { + Layout::InputStreamControlCode const *control_code = static_cast(_flow._input_stream[input_index]); + if ( control_code->code == SHAPE_BREAK + || control_code->code == PARAGRAPH_BREAK) + break; // stop at the end of the paragraph + // all other control codes we'll pick up later + + } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE) { + Layout::InputStreamTextSource *text_source = static_cast(_flow._input_stream[input_index]); + + // create the font_instance + font_instance *font = text_source->styleGetFontInstance(); + if (font == NULL) + continue; // bad news: we'll have to ignore all this text because we know of no font to render it + + PangoAttribute *attribute_font_description = pango_attr_font_desc_new(font->descr); + attribute_font_description->start_index = para_text.bytes(); + para_text.append(&*text_source->text_begin.base(), text_source->text_length); // build the combined text + attribute_font_description->end_index = para_text.bytes(); + pango_attr_list_insert(attributes_list, attribute_font_description); + // ownership of attribute is assumed by the list + } + } + + TRACE(("whole para: \"%s\"\n", para_text.data())); + TRACE(("%d input sources used\n", input_index - para->first_input_index)); + + // do the pango_itemize() + GList *pango_items_glist = NULL; + if (_flow._input_stream[para->first_input_index]->Type() == TEXT_SOURCE) { + Layout::InputStreamTextSource const *text_source = static_cast(_flow._input_stream[para->first_input_index]); + if (text_source->style->direction.set) { + PangoDirection pango_direction = (PangoDirection)_enum_converter(text_source->style->direction.computed, enum_convert_spstyle_direction_to_pango_direction, sizeof(enum_convert_spstyle_direction_to_pango_direction)/sizeof(enum_convert_spstyle_direction_to_pango_direction[0])); + pango_items_glist = pango_itemize_with_base_dir(_pango_context, pango_direction, para_text.data(), 0, para_text.bytes(), attributes_list, NULL); + para->direction = (Layout::Direction)_enum_converter(text_source->style->direction.computed, enum_convert_spstyle_direction_to_my_direction, sizeof(enum_convert_spstyle_direction_to_my_direction)/sizeof(enum_convert_spstyle_direction_to_my_direction[0])); + } + } + if (pango_items_glist == NULL) { // no direction specified, guess it + pango_items_glist = pango_itemize(_pango_context, para_text.data(), 0, para_text.bytes(), attributes_list, NULL); + + // I think according to the css spec this is wrong and we're never allowed to guess the directionality + // of a paragraph. Need to talk to an rtl speaker. + if (pango_items_glist == NULL || pango_items_glist->data == NULL) para->direction = LEFT_TO_RIGHT; + else para->direction = (((PangoItem*)pango_items_glist->data)->analysis.level & 1) ? RIGHT_TO_LEFT : LEFT_TO_RIGHT; + } + pango_attr_list_unref(attributes_list); + + // convert the GList to our vector<> and make the font_instance for each PangoItem at the same time + para->pango_items.reserve(g_list_length(pango_items_glist)); + TRACE(("para itemizes to %d sections\n", g_list_length(pango_items_glist))); + for (GList *current_pango_item = pango_items_glist ; current_pango_item != NULL ; current_pango_item = current_pango_item->next) { + PangoItemInfo new_item; + new_item.item = (PangoItem*)current_pango_item->data; + PangoFontDescription *font_description = pango_font_describe(new_item.item->analysis.font); + new_item.font = (font_factory::Default())->Face(font_description); + pango_font_description_free(font_description); // Face() makes a copy + para->pango_items.push_back(new_item); + } + g_list_free(pango_items_glist); + + // and get the character attributes on everything + para->char_attributes.resize(para_text.length() + 1); + pango_get_log_attrs(para_text.data(), para_text.bytes(), -1, NULL, &*para->char_attributes.begin(), para->char_attributes.size()); + + TRACE(("end para itemize, direction = %d\n", para->direction)); +} + +/** + * Gets the ascent, descent and leading for a font and the alteration that has to be performed + * according to the value specified by the line-height css property. The result of multiplying + * \a line_height by \a line_height_multiplier is the inline box height as specified in css2 + * section 10.8. + */ +void Layout::Calculator::_computeFontLineHeight(font_instance *font, double font_size, + SPStyle const *style, LineHeight *line_height, + double *line_height_multiplier) +{ + if (font == NULL) { + line_height->setZero(); + *line_height_multiplier = 1.0; + } + font->FontMetrics(line_height->ascent, line_height->descent, line_height->leading); + *line_height *= font_size; + + // yet another borked SPStyle member that we're going to have to fix ourselves + for ( ; ; ) { + if (style->line_height.set && !style->line_height.inherit) { + if (style->line_height.normal) + break; + switch (style->line_height.unit) { + case SP_CSS_UNIT_NONE: + *line_height_multiplier = style->line_height.computed * font_size / line_height->total(); + return; + case SP_CSS_UNIT_EX: + *line_height_multiplier = style->line_height.value * 0.5 * font_size / line_height->total(); + // 0.5 is an approximation of the x-height. Fixme. + return; + case SP_CSS_UNIT_EM: + case SP_CSS_UNIT_PERCENT: + *line_height_multiplier = style->line_height.value * font_size / line_height->total(); + return; + default: // absolute values + *line_height_multiplier = style->line_height.computed / line_height->total(); + return; + } + break; + } + if (style->object->parent == NULL) break; + style = style->object->parent->style; + if (style == NULL) break; + } + *line_height_multiplier = LINE_HEIGHT_NORMAL * font_size / line_height->total(); +} + +/** + * Split the paragraph into spans. Also call pango_shape() on them. + * + * Input: para->first_input_index, para->pango_items + * Output: para->spans + * Returns: the index of the beginning of the following paragraph in _flow._input_stream + */ +unsigned Layout::Calculator::_buildSpansForPara(ParagraphInfo *para) const +{ + unsigned pango_item_index = 0; + unsigned char_index_in_para = 0; + unsigned byte_index_in_para = 0; + unsigned input_index; + + TRACE(("build spans\n")); + para->free_sequence(para->unbroken_spans); + + for(input_index = para->first_input_index ; input_index < _flow._input_stream.size() ; input_index++) { + if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) { + Layout::InputStreamControlCode const *control_code = static_cast(_flow._input_stream[input_index]); + if ( control_code->code == SHAPE_BREAK + || control_code->code == PARAGRAPH_BREAK) + break; // stop at the end of the paragraph + else if (control_code->code == ARBITRARY_GAP) { + UnbrokenSpan new_span; + new_span.pango_item_index = -1; + new_span.input_index = input_index; + new_span.line_height.ascent = control_code->ascent; + new_span.line_height.descent = control_code->descent; + new_span.line_height.leading = 0.0; + new_span.text_bytes = 0; + new_span.char_index_in_para = char_index_in_para; + para->unbroken_spans.push_back(new_span); + TRACE(("add gap span %d\n", para->unbroken_spans.size() - 1)); + } + } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE && pango_item_index < para->pango_items.size()) { + Layout::InputStreamTextSource const *text_source = static_cast(_flow._input_stream[input_index]); + unsigned char_index_in_source = 0; + + unsigned span_start_byte_in_source = 0; + // we'll need to make several spans from each text source, based on the rules described about the UnbrokenSpan definition + for ( ; ; ) { + /* we need to change spans at every change of PangoItem, source stream change, + or change in one of the attributes altering position/rotation. */ + + unsigned const pango_item_bytes = ( pango_item_index >= para->pango_items.size() + ? 0 + : ( para->pango_items[pango_item_index].item->offset + + para->pango_items[pango_item_index].item->length + - byte_index_in_para ) ); + unsigned const text_source_bytes = ( text_source->text_end.base() + - text_source->text_begin.base() + - span_start_byte_in_source ); + UnbrokenSpan new_span; + new_span.text_bytes = std::min(text_source_bytes, pango_item_bytes); + new_span.input_stream_first_character = Glib::ustring::const_iterator(text_source->text_begin.base() + span_start_byte_in_source); + new_span.char_index_in_para = char_index_in_para + char_index_in_source; + new_span.input_index = input_index; + + // cut at attribute changes as well + new_span.x._set = false; + new_span.y._set = false; + new_span.dx._set = false; + new_span.dy._set = false; + new_span.rotate._set = false; + if (_block_progression == TOP_TO_BOTTOM || _block_progression == BOTTOM_TO_TOP) { + if (text_source->x.size() > char_index_in_source) new_span.x = text_source->x[char_index_in_source]; + if (text_source->y.size() > char_index_in_source) new_span.y = text_source->y[char_index_in_source]; + if (text_source->dx.size() > char_index_in_source) new_span.dx = text_source->dx[char_index_in_source]; + if (text_source->dy.size() > char_index_in_source) new_span.dy = text_source->dy[char_index_in_source]; + } else { + if (text_source->x.size() > char_index_in_source) new_span.y = text_source->x[char_index_in_source]; + if (text_source->y.size() > char_index_in_source) new_span.x = text_source->y[char_index_in_source]; + if (text_source->dx.size() > char_index_in_source) new_span.dy = text_source->dx[char_index_in_source]; + if (text_source->dy.size() > char_index_in_source) new_span.dx = text_source->dy[char_index_in_source]; + } + if (text_source->rotate.size() > char_index_in_source) new_span.rotate = text_source->rotate[char_index_in_source]; + if (input_index == 0 && para->unbroken_spans.empty() && !new_span.y._set && _flow._input_wrap_shapes.empty()) { + // if we don't set an explicit y some of the automatic wrapping code takes over and moves the text vertically + // so that the top of the letters is at zero, not the baseline + new_span.y = 0.0; + } + Glib::ustring::const_iterator iter_text = new_span.input_stream_first_character; + iter_text++; + for (unsigned i = char_index_in_source + 1 ; ; i++, iter_text++) { + if (iter_text >= text_source->text_end) break; + if (iter_text.base() - new_span.input_stream_first_character.base() >= (int)new_span.text_bytes) break; + if ( i >= text_source->x.size() && i >= text_source->y.size() + && i >= text_source->dx.size() && i >= text_source->dy.size() + && i >= text_source->rotate.size()) break; + if ( (text_source->x.size() > i && text_source->x[i]._set) + || (text_source->y.size() > i && text_source->y[i]._set) + || (text_source->dx.size() > i && text_source->dx[i]._set && text_source->dx[i].computed != 0.0) + || (text_source->dy.size() > i && text_source->dy[i]._set && text_source->dy[i].computed != 0.0) + || (text_source->rotate.size() > i && text_source->rotate[i]._set + && (i == 0 || text_source->rotate[i].computed != text_source->rotate[i - 1].computed))) { + new_span.text_bytes = iter_text.base() - new_span.input_stream_first_character.base(); + break; + } + } + + // now we know the length, do some final calculations and add the UnbrokenSpan to the list + new_span.font_size = text_source->styleComputeFontSize(); + if (new_span.text_bytes) { + int const original_bidi_level = para->pango_items[pango_item_index].item->analysis.level; + para->pango_items[pango_item_index].item->analysis.level = 0; + // pango_shape() will reorder glyphs in rtl sections which messes us up because + // the svg spec requires us to draw glyphs in character order + new_span.glyph_string = pango_glyph_string_new(); + /* Some assertions intended to help diagnose bug #1277746. */ + g_assert( 0 < new_span.text_bytes ); + g_assert( span_start_byte_in_source < text_source->text->bytes() ); + g_assert( span_start_byte_in_source + new_span.text_bytes <= text_source->text->bytes() ); + g_assert( memchr(text_source->text->data() + span_start_byte_in_source, '\0', static_cast(new_span.text_bytes)) + == NULL ); + pango_shape(text_source->text->data() + span_start_byte_in_source, + new_span.text_bytes, + ¶->pango_items[pango_item_index].item->analysis, + new_span.glyph_string); + para->pango_items[pango_item_index].item->analysis.level = original_bidi_level; + new_span.pango_item_index = pango_item_index; + _computeFontLineHeight(para->pango_items[pango_item_index].font, new_span.font_size, text_source->style, &new_span.line_height, &new_span.line_height_multiplier); + // TODO: metrics for vertical text + TRACE(("add text span %d \"%s\"\n", para->unbroken_spans.size(), text_source->text->raw().substr(span_start_byte_in_source, new_span.text_bytes).c_str())); + TRACE((" %d glyphs\n", new_span.glyph_string->num_glyphs)); + } else { + // if there's no text we still need to initialise the styles + new_span.pango_item_index = -1; + font_instance *font = text_source->styleGetFontInstance(); + if (font) { + _computeFontLineHeight(font, new_span.font_size, text_source->style, &new_span.line_height, &new_span.line_height_multiplier); + font->Unref(); + } else { + new_span.line_height.setZero(); + new_span.line_height_multiplier = 1.0; + } + TRACE(("add style init span %d\n", para->unbroken_spans.size())); + } + para->unbroken_spans.push_back(new_span); + + // calculations for moving to the next UnbrokenSpan + byte_index_in_para += new_span.text_bytes; + char_index_in_source += g_utf8_strlen(&*new_span.input_stream_first_character.base(), new_span.text_bytes); + + if (new_span.text_bytes >= pango_item_bytes) { // end of pango item + pango_item_index++; + if (pango_item_index == para->pango_items.size()) break; // end of paragraph + } + if (new_span.text_bytes == text_source_bytes) + break; // end of source + // else attribute changed + span_start_byte_in_source += new_span.text_bytes; + } + char_index_in_para += char_index_in_source; + } + } + TRACE(("end build spans\n")); + return input_index; +} + +/** + * Reinitialises the variables required on completion of one shape and + * moving on to the next. Returns false if there are no more shapes to wrap + * in to. + */ +bool Layout::Calculator::_goToNextWrapShape() +{ + delete _scanline_maker; + _scanline_maker = NULL; + _current_shape_index++; + if (_current_shape_index == _flow._input_wrap_shapes.size()) return false; + _scanline_maker = new ShapeScanlineMaker(_flow._input_wrap_shapes[_current_shape_index].shape, _block_progression); + TRACE(("begin wrap shape %d\n", _current_shape_index)); + return true; +} + +/** + * Given \a para filled in and \a start_span_pos set, keeps trying to + * find somewhere it can fit the next line of text. The process of finding + * the text that fits will involve creating one or more entries in + * \a chunk_info describing the bounds of the fitted text and several + * bits of information that will prove useful when we come to output the + * line to #_flow. Returns with \a start_span_pos set to the end of the + * text that was fitted, \a chunk_info completely filled out and + * \a line_height set to the largest line box on the line. The return + * value is false only if we've run out of shapes to wrap inside (and + * hence couldn't create any chunks). + */ +bool Layout::Calculator::_findChunksForLine(ParagraphInfo const ¶, + UnbrokenSpanPosition *start_span_pos, + std::vector *chunk_info, + LineHeight *line_height) +{ + // init the initial line_height + if (start_span_pos->iter_span == para.unbroken_spans.end()) { + if (_flow._spans.empty()) { + // empty first para: create a font for the sole purpose of measuring it + InputStreamTextSource const *text_source = static_cast(_flow._input_stream.front()); + font_instance *font = text_source->styleGetFontInstance(); + if (font) { + double font_size = text_source->styleComputeFontSize(); + double multiplier; + _computeFontLineHeight(font, font_size, text_source->style, line_height, &multiplier); + font->Unref(); + *line_height *= multiplier; + _scanline_maker->setNewYCoordinate(_scanline_maker->yCoordinate() - line_height->ascent); + } + } + // else empty subsequent para: keep the old line height + } else { + if (_flow._input_wrap_shapes.empty()) { + // if we're not wrapping set the line_height big and negative so we can use negative line height + line_height->ascent = -1.0e10; + line_height->descent = -1.0e10; + line_height->leading = -1.0e10; + } + else + line_height->setZero(); + } + + UnbrokenSpanPosition span_pos; + for( ; ; ) { + std::vector scan_runs; + scan_runs = _scanline_maker->makeScanline(*line_height); + while (scan_runs.empty()) { + if (!_goToNextWrapShape()) return false; // no more shapes to wrap in to + scan_runs = _scanline_maker->makeScanline(*line_height); + } + + TRACE(("finding line fit y=%f, %d scan runs\n", scan_runs.front().y, scan_runs.size())); + chunk_info->clear(); + chunk_info->reserve(scan_runs.size()); + if (para.direction == RIGHT_TO_LEFT) std::reverse(scan_runs.begin(), scan_runs.end()); + unsigned scan_run_index; + span_pos = *start_span_pos; + for (scan_run_index = 0 ; scan_run_index < scan_runs.size() ; scan_run_index++) { + if (!_buildChunksInScanRun(para, span_pos, scan_runs[scan_run_index], chunk_info, line_height)) + break; + if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty()) + span_pos = chunk_info->back().broken_spans.back().end; + } + if (scan_run_index == scan_runs.size()) break; // ie when buildChunksInScanRun() succeeded + } + *start_span_pos = span_pos; + return true; +} + +/** + * Given a scan run and a first character, append one or more chunks to + * the \a chunk_info vector that describe all the spans and other detail + * necessary to output the greatest amount of text that will fit on this scan + * line (greedy line breaking algorithm). Each chunk contains one or more + * BrokenSpan structures that link back to UnbrokenSpan structures that link + * to the text itself. Normally there will be either one or zero (if the + * scanrun is too short to fit any text) chunk added to \a chunk_info by + * each call to this method, but we will add more than one if an x or y + * attribute has been set on a tspan. \a line_height must be set on input, + * and if it needs to be made larger and the #_scanline_maker can't do + * an in-situ resize then it will be set to the required value and the + * method will return false. + */ +bool Layout::Calculator::_buildChunksInScanRun(ParagraphInfo const ¶, + UnbrokenSpanPosition const &start_span_pos, + ScanlineMaker::ScanRun const &scan_run, + std::vector *chunk_info, + LineHeight *line_height) const +{ + ChunkInfo new_chunk; + new_chunk.text_width = 0.0; + new_chunk.whitespace_count = 0; + new_chunk.scanrun_width = scan_run.width(); + new_chunk.x = scan_run.x_start; + + // we haven't done anything yet so the last valid break position is the beginning + BrokenSpan last_span_at_break, last_span_at_emergency_break; + last_span_at_break.start = start_span_pos; + last_span_at_break.setZero(); + last_span_at_emergency_break.start = start_span_pos; + last_span_at_emergency_break.setZero(); + + TRACE(("trying chunk from %f to %g\n", scan_run.x_start, scan_run.x_end)); + BrokenSpan new_span; + new_span.end = start_span_pos; + while (new_span.end.iter_span != para.unbroken_spans.end()) { // this loops once for each UnbrokenSpan + + new_span.start = new_span.end; + + // force a chunk change at x or y attribute change + if ((new_span.start.iter_span->x._set || new_span.start.iter_span->y._set) && new_span.start.char_byte == 0) { + + if (new_span.start.iter_span != start_span_pos.iter_span) + chunk_info->push_back(new_chunk); + + new_chunk.x += new_chunk.text_width; + new_chunk.text_width = 0.0; + new_chunk.whitespace_count = 0; + new_chunk.broken_spans.clear(); + if (new_span.start.iter_span->x._set) new_chunk.x = new_span.start.iter_span->x.computed; + // y doesn't need to be done until output time + } + + // see if this span is too tall to fit on the current line + LineHeight total_height = new_span.start.iter_span->line_height; + total_height *= new_span.start.iter_span->line_height_multiplier; + /* floating point 80-bit/64-bit rounding problems require epsilon. See + discussion http://inkscape.gristle.org/2005-03-16.txt around 22:00 */ + if ( total_height.ascent > line_height->ascent + FLT_EPSILON + || total_height.descent > line_height->descent + FLT_EPSILON + || total_height.leading > line_height->leading + FLT_EPSILON) { + line_height->max(total_height); + if (!_scanline_maker->canExtendCurrentScanline(*line_height)) + return false; + } + + bool span_fitted = _measureUnbrokenSpan(para, &new_span, &last_span_at_break, &last_span_at_emergency_break, new_chunk.scanrun_width - new_chunk.text_width); + + new_chunk.text_width += new_span.width; + new_chunk.whitespace_count += new_span.whitespace_count; + new_chunk.broken_spans.push_back(new_span); // if !span_fitted we'll correct ourselves below + + if (!span_fitted) break; + + if (new_span.end.iter_span == para.unbroken_spans.end()) { + last_span_at_break = new_span; + break; + } + } + + TRACE(("chunk complete, used %f width (%d whitespaces, %d brokenspans)\n", new_chunk.text_width, new_chunk.whitespace_count, new_chunk.broken_spans.size())); + chunk_info->push_back(new_chunk); + + if (scan_run.width() >= 4.0 * line_height->total() && last_span_at_break.end == start_span_pos) { + /* **non-SVG spec bit**: See bug #1191102 + If the user types a very long line with no spaces, the way the spec + is written at the moment means that when the length of the text + exceeds the available width of all remaining areas, the text is + completely hidden. This condition alters that behaviour so that if + the length of the line is greater than four times the line-height + and there are no spaces, it'll be emergency-wrapped at the last + character. One could read the SVG Tiny 1.2 draft as permitting this + sort of behaviour, but it's still a bit dodgy. The hard-coding of + 4x is not nice, either. */ + last_span_at_break = last_span_at_emergency_break; + } + + if (!chunk_info->back().broken_spans.empty() && last_span_at_break.end != chunk_info->back().broken_spans.back().end) { + // need to back out spans until we come to the one with the last break in it + while (!chunk_info->empty() && last_span_at_break.start.iter_span != chunk_info->back().broken_spans.back().start.iter_span) { + chunk_info->back().text_width -= chunk_info->back().broken_spans.back().width; + chunk_info->back().whitespace_count -= chunk_info->back().broken_spans.back().whitespace_count; + chunk_info->back().broken_spans.pop_back(); + if (chunk_info->back().broken_spans.empty()) + chunk_info->pop_back(); + } + if (!chunk_info->empty()) { + chunk_info->back().text_width -= chunk_info->back().broken_spans.back().width; + chunk_info->back().whitespace_count -= chunk_info->back().broken_spans.back().whitespace_count; + if (last_span_at_break.start == last_span_at_break.end) { + chunk_info->back().broken_spans.pop_back(); // last break was at an existing boundary + if (chunk_info->back().broken_spans.empty()) + chunk_info->pop_back(); + } else { + chunk_info->back().broken_spans.back() = last_span_at_break; + chunk_info->back().text_width += last_span_at_break.width; + chunk_info->back().whitespace_count += last_span_at_break.whitespace_count; + } + TRACE(("correction: fitted span %d width = %f\n", last_span_at_break.start.iter_span - para.unbroken_spans.begin(), last_span_at_break.width)); + } + } + + if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty() && chunk_info->back().broken_spans.back().ends_with_whitespace) { + // for justification we need to discard space occupied by the single whitespace at the end of the chunk + chunk_info->back().broken_spans.back().ends_with_whitespace = false; + chunk_info->back().broken_spans.back().width -= chunk_info->back().broken_spans.back().each_whitespace_width; + chunk_info->back().broken_spans.back().whitespace_count--; + chunk_info->back().text_width -= chunk_info->back().broken_spans.back().each_whitespace_width; + chunk_info->back().whitespace_count--; + } + + return true; +} + +/** The management function to start the whole thing off. */ +bool Layout::Calculator::calculate() +{ + if (_flow._input_stream.empty()) + return false; + g_assert(_flow._input_stream.front()->Type() == TEXT_SOURCE); + if (_flow._input_stream.front()->Type() != TEXT_SOURCE) + return false; + + TRACE(("begin calculateFlow()\n")); + + _flow._clearOutputObjects(); + + _pango_context = (font_factory::Default())->fontContext; + _font_factory_size_multiplier = (font_factory::Default())->fontSize; + + _block_progression = _flow._blockProgression(); + _y_offset = 0.0; + _createFirstScanlineMaker(); + + ParagraphInfo para; + LineHeight line_height; // needs to be maintained across paragraphs to be able to deal with blank paras (this is wrong) + for(para.first_input_index = 0 ; para.first_input_index < _flow._input_stream.size() ; ) { + // jump to the next wrap shape if this is a SHAPE_BREAK control code + if (_flow._input_stream[para.first_input_index]->Type() == CONTROL_CODE) { + InputStreamControlCode const *control_code = static_cast(_flow._input_stream[para.first_input_index]); + if (control_code->code == SHAPE_BREAK) { + TRACE(("shape break control code\n")); + if (!_goToNextWrapShape()) break; + continue; + } + } + if (_scanline_maker == NULL) + break; // we're trying to flow past the last wrap shape + + _buildPangoItemizationForPara(¶); + unsigned para_end_input_index = _buildSpansForPara(¶); + + if (_flow._input_stream[para.first_input_index]->Type() == TEXT_SOURCE) + para.alignment = static_cast(_flow._input_stream[para.first_input_index])->styleGetAlignment(para.direction, !_flow._input_wrap_shapes.empty()); + else + para.alignment = para.direction == LEFT_TO_RIGHT ? LEFT : RIGHT; + + TRACE(("para prepared, adding as #%d\n", _flow._paragraphs.size())); + Layout::Paragraph new_paragraph; + new_paragraph.base_direction = para.direction; + new_paragraph.alignment = para.alignment; + _flow._paragraphs.push_back(new_paragraph); + + // start scanning lines + UnbrokenSpanPosition span_pos; + span_pos.iter_span = para.unbroken_spans.begin(); + span_pos.char_byte = 0; + span_pos.char_index = 0; + + do { // for each line in the paragraph + TRACE(("begin line\n")); + std::vector line_chunk_info; + if (!_findChunksForLine(para, &span_pos, &line_chunk_info, &line_height)) + break; // out of shapes to wrap in to + + _outputLine(para, line_height, line_chunk_info); + _scanline_maker->completeLine(); + } while (span_pos.iter_span != para.unbroken_spans.end()); + + TRACE(("para %d end\n\n", _flow._paragraphs.size() - 1)); + if (_scanline_maker != NULL) { + bool is_empty_para = _flow._characters.empty() || _flow._characters.back().line(&_flow).in_paragraph != _flow._paragraphs.size() - 1; + if ((is_empty_para && para_end_input_index + 1 >= _flow._input_stream.size()) + || para_end_input_index + 1 < _flow._input_stream.size()) { + // we need a span just for the para if it's either an empty last para or a break in the middle + Layout::Span new_span; + if (_flow._spans.empty()) { + new_span.font = NULL; + new_span.font_size = line_height.ascent + line_height.descent; + new_span.line_height = line_height; + new_span.x_end = 0.0; + } else { + new_span = _flow._spans.back(); + if (_flow._chunks[new_span.in_chunk].in_line != _flow._lines.size() - 1) + new_span.x_end = 0.0; + } + new_span.in_chunk = _flow._chunks.size() - 1; + if (new_span.font) + new_span.font->Ref(); + new_span.x_start = new_span.x_end; + new_span.baseline_shift = 0.0; + new_span.direction = para.direction; + new_span.block_progression = _block_progression; + if (para_end_input_index == _flow._input_stream.size()) + new_span.in_input_stream_item = _flow._input_stream.size() - 1; + else + new_span.in_input_stream_item = para_end_input_index; + _flow._spans.push_back(new_span); + } + if (para_end_input_index + 1 < _flow._input_stream.size()) { + // we've got to add an invisible character between paragraphs so that we can position iterators + // (and hence cursors) both before and after the paragraph break + Layout::Character new_character; + new_character.in_span = _flow._spans.size() - 1; + new_character.char_attributes.is_line_break = 1; + new_character.char_attributes.is_mandatory_break = 1; + new_character.char_attributes.is_char_break = 1; + new_character.char_attributes.is_white = 1; + new_character.char_attributes.is_cursor_position = 1; + new_character.char_attributes.is_word_start = 0; + new_character.char_attributes.is_word_end = 1; + new_character.char_attributes.is_sentence_start = 0; + new_character.char_attributes.is_sentence_end = 1; + new_character.char_attributes.is_sentence_boundary = 1; + new_character.char_attributes.backspace_deletes_character = 1; + new_character.x = _flow._spans.back().x_end - _flow._spans.back().x_start; + new_character.in_glyph = -1; + _flow._characters.push_back(new_character); + } + } + para.free(); + para.first_input_index = para_end_input_index + 1; + } + + para.free(); + if (_scanline_maker) + delete _scanline_maker; + + return true; +} + +void Layout::_calculateCursorShapeForEmpty() +{ + _empty_cursor_shape.position = NR::Point(0, 0); + _empty_cursor_shape.height = 0.0; + _empty_cursor_shape.rotation = 0.0; + if (_input_stream.empty() || _input_stream.front()->Type() != TEXT_SOURCE) + return; + + InputStreamTextSource const *text_source = static_cast(_input_stream.front()); + + font_instance *font = text_source->styleGetFontInstance(); + double font_size = text_source->styleComputeFontSize(); + double caret_slope_run = 0.0, caret_slope_rise = 1.0; + LineHeight line_height; + if (font) { + const_cast(font)->FontSlope(caret_slope_run, caret_slope_rise); + font->FontMetrics(line_height.ascent, line_height.descent, line_height.leading); + line_height *= font_size; + font->Unref(); + } else { + line_height.ascent = font_size * 0.85; // random guesses + line_height.descent = font_size * 0.15; + line_height.leading = 0.0; + } + double caret_slope = atan2(caret_slope_run, caret_slope_rise); + _empty_cursor_shape.height = font_size / cos(caret_slope); + _empty_cursor_shape.rotation = caret_slope; + + if (_input_wrap_shapes.empty()) { + _empty_cursor_shape.position = NR::Point(text_source->x.empty() || !text_source->x.front()._set ? 0.0 : text_source->x.front().computed, + text_source->y.empty() || !text_source->y.front()._set ? 0.0 : text_source->y.front().computed); + } else { + Direction block_progression = text_source->styleGetBlockProgression(); + ShapeScanlineMaker scanline_maker(_input_wrap_shapes.front().shape, block_progression); + std::vector scan_runs = scanline_maker.makeScanline(line_height); + if (!scan_runs.empty()) { + if (block_progression == LEFT_TO_RIGHT || block_progression == RIGHT_TO_LEFT) + _empty_cursor_shape.position = NR::Point(scan_runs.front().y + font_size, scan_runs.front().x_start); + else + _empty_cursor_shape.position = NR::Point(scan_runs.front().x_start, scan_runs.front().y + font_size); + } + } +} + +bool Layout::calculateFlow() +{ + bool result = Calculator(this).calculate(); + if (_characters.empty()) + _calculateCursorShapeForEmpty(); + return result; +} + +}//namespace Text +}//namespace Inkscape + + +/* + 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 : diff --git a/src/libnrtype/Layout-TNG-Input.cpp b/src/libnrtype/Layout-TNG-Input.cpp new file mode 100755 index 000000000..8b695af04 --- /dev/null +++ b/src/libnrtype/Layout-TNG-Input.cpp @@ -0,0 +1,270 @@ +/* + * Inkscape::Text::Layout - text layout engine input functions + * + * Authors: + * Richard Hughes + * + * Copyright (C) 2005 Richard Hughes + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ +#include "Layout-TNG.h" +#include "style.h" +#include "svg/svg-length.h" +#include "sp-object.h" +#include "FontFactory.h" + +namespace Inkscape { +namespace Text { + +void Layout::_clearInputObjects() +{ + for(std::vector::iterator it = _input_stream.begin() ; it != _input_stream.end() ; it++) + delete *it; + _input_stream.clear(); + _input_wrap_shapes.clear(); +} + +// this function does nothing more than store all its parameters for future reference +void Layout::appendText(Glib::ustring const &text, SPStyle *style, void *source_cookie, OptionalTextTagAttrs const *optional_attributes, unsigned optional_attributes_offset, Glib::ustring::const_iterator text_begin, Glib::ustring::const_iterator text_end) +{ + if (style == NULL) return; + + InputStreamTextSource *new_source = new InputStreamTextSource; + + new_source->source_cookie = source_cookie; + new_source->text = &text; + new_source->text_begin = text_begin; + new_source->text_end = text_end; + new_source->style = style; + sp_style_ref(style); + + new_source->text_length = 0; + for ( ; text_begin != text_end && text_begin != text.end() ; text_begin++) + new_source->text_length++; // save this because calculating the length of a UTF-8 string is expensive + + if (optional_attributes) { + // we need to fill in x and y even if the text is empty so that empty paragraphs can be positioned correctly + _copyInputVector(optional_attributes->x, optional_attributes_offset, &new_source->x, std::max(1, new_source->text_length)); + _copyInputVector(optional_attributes->y, optional_attributes_offset, &new_source->y, std::max(1, new_source->text_length)); + _copyInputVector(optional_attributes->dx, optional_attributes_offset, &new_source->dx, new_source->text_length); + _copyInputVector(optional_attributes->dy, optional_attributes_offset, &new_source->dy, new_source->text_length); + _copyInputVector(optional_attributes->rotate, optional_attributes_offset, &new_source->rotate, new_source->text_length); + } + + _input_stream.push_back(new_source); +} + +void Layout::_copyInputVector(std::vector const &input_vector, unsigned input_offset, std::vector *output_vector, size_t max_length) +{ + output_vector->clear(); + if (input_offset >= input_vector.size()) return; + output_vector->reserve(std::min(max_length, input_vector.size() - input_offset)); + while (input_offset < input_vector.size() && max_length != 0) { + if (!input_vector[input_offset]._set) + break; + output_vector->push_back(input_vector[input_offset]); + input_offset++; + max_length--; + } +} + +// just save what we've been given, really +void Layout::appendControlCode(TextControlCode code, void *source_cookie, double width, double ascent, double descent) +{ + InputStreamControlCode *new_code = new InputStreamControlCode; + + new_code->source_cookie = source_cookie; + new_code->code = code; + new_code->width = width; + new_code->ascent = ascent; + new_code->descent = descent; + + _input_stream.push_back(new_code); +} + +// more saving of the parameters +void Layout::appendWrapShape(Shape const *shape, DisplayAlign display_align) +{ + _input_wrap_shapes.push_back(InputWrapShape()); + _input_wrap_shapes.back().shape = shape; + _input_wrap_shapes.back().display_align = display_align; +} + +int Layout::_enum_converter(int input, EnumConversionItem const *conversion_table, unsigned conversion_table_size) +{ + for (unsigned i = 0 ; i < conversion_table_size ; i++) + if (conversion_table[i].input == input) + return conversion_table[i].output; + return conversion_table[0].output; +} + +// ***** the style format interface +// this doesn't include all accesses to SPStyle, only the ones that are non-trivial + +static const float medium_font_size = 12.0; // more of a default if all else fails than anything else +float Layout::InputStreamTextSource::styleComputeFontSize() const +{ + return style->font_size.computed; + + // in case the computed value's not good enough, here's some manual code held in reserve: + SPStyle const *this_style = style; + float inherit_multiplier = 1.0; + + for ( ; ; ) { + if (this_style->font_size.set && !this_style->font_size.inherit) { + switch (this_style->font_size.type) { + case SP_FONT_SIZE_LITERAL: { + switch(this_style->font_size.value) { // these multipliers are straight out of the CSS spec + case SP_CSS_FONT_SIZE_XX_SMALL: return medium_font_size * inherit_multiplier * (3.0/5.0); + case SP_CSS_FONT_SIZE_X_SMALL: return medium_font_size * inherit_multiplier * (3.0/4.0); + case SP_CSS_FONT_SIZE_SMALL: return medium_font_size * inherit_multiplier * (8.0/9.0); + default: + case SP_CSS_FONT_SIZE_MEDIUM: return medium_font_size * inherit_multiplier; + case SP_CSS_FONT_SIZE_LARGE: return medium_font_size * inherit_multiplier * (6.0/5.0); + case SP_CSS_FONT_SIZE_X_LARGE: return medium_font_size * inherit_multiplier * (3.0/2.0); + case SP_CSS_FONT_SIZE_XX_LARGE: return medium_font_size * inherit_multiplier * 2.0; + case SP_CSS_FONT_SIZE_SMALLER: inherit_multiplier *= 0.84; break; //not exactly according to spec + case SP_CSS_FONT_SIZE_LARGER: inherit_multiplier *= 1.26; break; //not exactly according to spec + } + break; + } + case SP_FONT_SIZE_PERCENTAGE: { // 'em' units should be in here, but aren't. Fix in style.cpp. + inherit_multiplier *= this_style->font_size.value; + break; + } + case SP_FONT_SIZE_LENGTH: { + return this_style->font_size.value * inherit_multiplier; + } + } + } + if (this_style->object->parent == NULL) break; + this_style = this_style->object->parent->style; + if (this_style == NULL) break; + } + return medium_font_size * inherit_multiplier; +} + +static const Layout::EnumConversionItem enum_convert_spstyle_block_progression_to_direction[] = { + {SP_CSS_BLOCK_PROGRESSION_TB, Layout::TOP_TO_BOTTOM}, + {SP_CSS_BLOCK_PROGRESSION_LR, Layout::LEFT_TO_RIGHT}, + {SP_CSS_BLOCK_PROGRESSION_RL, Layout::RIGHT_TO_LEFT}}; + +static const Layout::EnumConversionItem enum_convert_spstyle_writing_mode_to_direction[] = { + {SP_CSS_WRITING_MODE_LR_TB, Layout::TOP_TO_BOTTOM}, + {SP_CSS_WRITING_MODE_RL_TB, Layout::TOP_TO_BOTTOM}, + {SP_CSS_WRITING_MODE_TB_RL, Layout::RIGHT_TO_LEFT}, + {SP_CSS_WRITING_MODE_TB_LR, Layout::LEFT_TO_RIGHT}}; + +Layout::Direction Layout::InputStreamTextSource::styleGetBlockProgression() const +{ + // this function shouldn't be necessary, but since style.cpp doesn't support + // shorthand properties yet, it is. + SPStyle const *this_style = style; + + for ( ; ; ) { + if (this_style->block_progression.set) + return (Layout::Direction)_enum_converter(this_style->block_progression.computed, enum_convert_spstyle_block_progression_to_direction, sizeof(enum_convert_spstyle_block_progression_to_direction)/sizeof(enum_convert_spstyle_block_progression_to_direction[0])); + if (this_style->writing_mode.set) + return (Layout::Direction)_enum_converter(this_style->writing_mode.computed, enum_convert_spstyle_writing_mode_to_direction, sizeof(enum_convert_spstyle_writing_mode_to_direction)/sizeof(enum_convert_spstyle_writing_mode_to_direction[0])); + if (this_style->object->parent == NULL) break; + this_style = this_style->object->parent->style; + if (this_style == NULL) break; + } + return TOP_TO_BOTTOM; + +} + +static Layout::Alignment text_anchor_to_alignment(unsigned anchor, Layout::Direction para_direction) +{ + switch (anchor) { + default: + case SP_CSS_TEXT_ANCHOR_START: return para_direction == Layout::LEFT_TO_RIGHT ? Layout::LEFT : Layout::RIGHT; + case SP_CSS_TEXT_ANCHOR_MIDDLE: return Layout::CENTER; + case SP_CSS_TEXT_ANCHOR_END: return para_direction == Layout::LEFT_TO_RIGHT ? Layout::RIGHT : Layout::LEFT; + } +} + +Layout::Alignment Layout::InputStreamTextSource::styleGetAlignment(Layout::Direction para_direction, bool try_text_align) const +{ + if (!try_text_align) + return text_anchor_to_alignment(style->text_anchor.computed, para_direction); + + // there's no way to tell the difference between text-anchor set higher up the cascade to the default and + // text-anchor never set anywhere in the cascade, so in order to detect which of text-anchor or text-align + // to use we'll have to run up the style tree ourselves. + SPStyle const *this_style = style; + + for ( ; ; ) { + // If both text-align and text-anchor are set at the same level, text-align takes + // precedence because it is the most expressive. + if (this_style->text_align.set) { + switch (style->text_align.computed) { + default: + case SP_CSS_TEXT_ALIGN_START: return para_direction == LEFT_TO_RIGHT ? LEFT : RIGHT; + case SP_CSS_TEXT_ALIGN_END: return para_direction == LEFT_TO_RIGHT ? RIGHT : LEFT; + case SP_CSS_TEXT_ALIGN_LEFT: return LEFT; + case SP_CSS_TEXT_ALIGN_RIGHT: return RIGHT; + case SP_CSS_TEXT_ALIGN_CENTER: return CENTER; + case SP_CSS_TEXT_ALIGN_JUSTIFY: return FULL; + } + } + if (this_style->text_anchor.set) + return text_anchor_to_alignment(this_style->text_anchor.computed, para_direction); + if (this_style->object->parent == NULL) break; + this_style = this_style->object->parent->style; + if (this_style == NULL) break; + } + return para_direction == LEFT_TO_RIGHT ? LEFT : RIGHT; +} + +static const Layout::EnumConversionItem enum_convert_spstyle_style_to_pango_style[] = { + {SP_CSS_FONT_STYLE_NORMAL, PANGO_STYLE_NORMAL}, + {SP_CSS_FONT_STYLE_ITALIC, PANGO_STYLE_ITALIC}, + {SP_CSS_FONT_STYLE_OBLIQUE, PANGO_STYLE_OBLIQUE}}; + +static const Layout::EnumConversionItem enum_convert_spstyle_weight_to_pango_weight[] = { + {SP_CSS_FONT_WEIGHT_NORMAL, PANGO_WEIGHT_NORMAL}, + {SP_CSS_FONT_WEIGHT_100, PANGO_WEIGHT_ULTRALIGHT}, + {SP_CSS_FONT_WEIGHT_200, PANGO_WEIGHT_ULTRALIGHT}, + {SP_CSS_FONT_WEIGHT_300, PANGO_WEIGHT_LIGHT}, + {SP_CSS_FONT_WEIGHT_400, PANGO_WEIGHT_NORMAL}, + {SP_CSS_FONT_WEIGHT_500, PANGO_WEIGHT_SEMIBOLD}, + {SP_CSS_FONT_WEIGHT_600, PANGO_WEIGHT_BOLD}, + {SP_CSS_FONT_WEIGHT_BOLD,PANGO_WEIGHT_BOLD}, + {SP_CSS_FONT_WEIGHT_700, PANGO_WEIGHT_BOLD}, + {SP_CSS_FONT_WEIGHT_800, PANGO_WEIGHT_ULTRABOLD}, + {SP_CSS_FONT_WEIGHT_900, PANGO_WEIGHT_HEAVY}}; + +static const Layout::EnumConversionItem enum_convert_spstyle_stretch_to_pango_stretch[] = { + {SP_CSS_FONT_STRETCH_NORMAL, PANGO_STRETCH_NORMAL}, + {SP_CSS_FONT_STRETCH_ULTRA_CONDENSED, PANGO_STRETCH_ULTRA_CONDENSED}, + {SP_CSS_FONT_STRETCH_EXTRA_CONDENSED, PANGO_STRETCH_EXTRA_CONDENSED}, + {SP_CSS_FONT_STRETCH_CONDENSED, PANGO_STRETCH_CONDENSED}, + {SP_CSS_FONT_STRETCH_SEMI_CONDENSED, PANGO_STRETCH_SEMI_CONDENSED}, + {SP_CSS_FONT_STRETCH_SEMI_EXPANDED, PANGO_STRETCH_SEMI_EXPANDED}, + {SP_CSS_FONT_STRETCH_EXPANDED, PANGO_STRETCH_EXPANDED}, + {SP_CSS_FONT_STRETCH_EXTRA_EXPANDED, PANGO_STRETCH_EXTRA_EXPANDED}, + {SP_CSS_FONT_STRETCH_ULTRA_EXPANDED, PANGO_STRETCH_ULTRA_EXPANDED}}; + +static const Layout::EnumConversionItem enum_convert_spstyle_variant_to_pango_variant[] = { + {SP_CSS_FONT_VARIANT_NORMAL, PANGO_VARIANT_NORMAL}, + {SP_CSS_FONT_VARIANT_SMALL_CAPS, PANGO_VARIANT_SMALL_CAPS}}; + +font_instance *Layout::InputStreamTextSource::styleGetFontInstance() const +{ + if (style->text == NULL) return NULL; + return (font_factory::Default())->Face(style->text->font_family.value, + _enum_converter(style->font_variant.computed, enum_convert_spstyle_variant_to_pango_variant, sizeof(enum_convert_spstyle_variant_to_pango_variant)/sizeof(enum_convert_spstyle_variant_to_pango_variant[0])), + _enum_converter(style->font_style.computed, enum_convert_spstyle_style_to_pango_style, sizeof(enum_convert_spstyle_style_to_pango_style)/sizeof(enum_convert_spstyle_style_to_pango_style[0])), + _enum_converter(style->font_weight.computed, enum_convert_spstyle_weight_to_pango_weight, sizeof(enum_convert_spstyle_weight_to_pango_weight)/sizeof(enum_convert_spstyle_weight_to_pango_weight[0])), + _enum_converter(style->font_stretch.computed, enum_convert_spstyle_stretch_to_pango_stretch, sizeof(enum_convert_spstyle_stretch_to_pango_stretch)/sizeof(enum_convert_spstyle_stretch_to_pango_stretch[0]))); +} + +Layout::InputStreamTextSource::~InputStreamTextSource() +{ + sp_style_unref(style); +} + +}//namespace Text +}//namespace Inkscape diff --git a/src/libnrtype/Layout-TNG-OutIter.cpp b/src/libnrtype/Layout-TNG-OutIter.cpp new file mode 100755 index 000000000..3dd043a68 --- /dev/null +++ b/src/libnrtype/Layout-TNG-OutIter.cpp @@ -0,0 +1,994 @@ +/* + * Inkscape::Text::Layout - text layout engine output functions using iterators + * + * Authors: + * Richard Hughes + * + * Copyright (C) 2005 Richard Hughes + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ +#include "Layout-TNG.h" +#include "livarot/Path.h" +#include "font-instance.h" +#include "svg/svg-length.h" +#include "libnr/nr-matrix-translate-ops.h" +#include "libnr/nr-translate-rotate-ops.h" +#include "style.h" + +namespace Inkscape { +namespace Text { + +Layout::iterator Layout::_cursorXOnLineToIterator(unsigned line_index, double local_x) const +{ + unsigned char_index = _lineToCharacter(line_index); + int best_char_index = -1; + double best_x_difference = DBL_MAX; + + if (char_index == _characters.size()) return end(); + for ( ; char_index < _characters.size() ; char_index++) { + if (_characters[char_index].chunk(this).in_line != line_index) break; + if (_characters[char_index].char_attributes.is_mandatory_break) break; + if (!_characters[char_index].char_attributes.is_cursor_position) continue; + double this_x_difference = fabs(_characters[char_index].x + _characters[char_index].span(this).x_start + _characters[char_index].chunk(this).left_x - local_x); + if (this_x_difference < best_x_difference) { + best_char_index = char_index; + best_x_difference = this_x_difference; + } + } + // also try the very end of a para (not lines though because the space wraps) + if (char_index == _characters.size() || _characters[char_index].char_attributes.is_mandatory_break) { + double this_x_difference; + if (char_index == 0) this_x_difference = fabs(_spans.front().x_end + _chunks.front().left_x - local_x); + else this_x_difference = fabs(_characters[char_index - 1].span(this).x_end + _characters[char_index - 1].chunk(this).left_x - local_x); + if (this_x_difference < best_x_difference) { + best_char_index = char_index; + best_x_difference = this_x_difference; + } + } + if (best_char_index == -1) return iterator(this, char_index); + return iterator(this, best_char_index); +} + +double Layout::_getChunkWidth(unsigned chunk_index) const +{ + double chunk_width = 0.0; + unsigned span_index; + if (chunk_index) { + span_index = _lineToSpan(_chunks[chunk_index].in_line); + for ( ; span_index < _spans.size() && _spans[span_index].in_chunk < chunk_index ; span_index++); + } else + span_index = 0; + for ( ; span_index < _spans.size() && _spans[span_index].in_chunk == chunk_index ; span_index++) + chunk_width = std::max(chunk_width, (double)std::max(_spans[span_index].x_start, _spans[span_index].x_end)); + return chunk_width; +} + +/* getting the cursor position for a mouse click is not as simple as it might +seem. The two major problems are flows set up in multiple columns and large +dy adjustments such that text does not belong to the line it appears to. In +the worst case it's possible to have two characters on top of each other, in +which case the one we pick is arbitrary. + +This is a 3-stage (2 pass) algorithm: +1) search all the spans to see if the point is contained in one, if so take + that. Note that this will collect all clicks from the current UI because + of how the hit detection of nrarena objects works. +2) if that fails, run through all the chunks finding a best guess of the one + the user wanted. This is the one whose y coordinate is nearest, or if + there's a tie, the x. +3) search in that chunk using x-coordinate only to find the position. +*/ +Layout::iterator Layout::getNearestCursorPositionTo(double x, double y) const +{ + if (_lines.empty()) return begin(); + double local_x = x; + double local_y = y; + + if (_path_fitted) { + Path::cut_position position = const_cast(_path_fitted)->PointToCurvilignPosition(NR::Point(x, y)); + local_x = const_cast(_path_fitted)->PositionToLength(position.piece, position.t); + return _cursorXOnLineToIterator(0, local_x + _chunks.front().left_x); + } + + if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) { + local_x = y; + local_y = x; + } + // stage 1: + for (unsigned span_index = 0 ; span_index < _spans.size() ; span_index++) { + double span_left, span_right; + if (_spans[span_index].x_start < _spans[span_index].x_end) { + span_left = _spans[span_index].x_start; + span_right = _spans[span_index].x_end; + } else { + span_left = _spans[span_index].x_end; + span_right = _spans[span_index].x_start; + } + if ( local_x >= _chunks[_spans[span_index].in_chunk].left_x + span_left + && local_x <= _chunks[_spans[span_index].in_chunk].left_x + span_right + && local_y >= _spans[span_index].line(this).baseline_y + _spans[span_index].baseline_shift - _spans[span_index].line_height.ascent + && local_y <= _spans[span_index].line(this).baseline_y + _spans[span_index].baseline_shift + _spans[span_index].line_height.descent) { + return _cursorXOnLineToIterator(_chunks[_spans[span_index].in_chunk].in_line, local_x); + } + } + + // stage 2: + unsigned span_index = 0; + unsigned chunk_index; + int best_chunk_index = -1; + double best_y_range = DBL_MAX; + double best_x_range = DBL_MAX; + for (chunk_index = 0 ; chunk_index < _chunks.size() ; chunk_index++) { + LineHeight line_height = {0.0, 0.0, 0.0}; + double chunk_width = 0.0; + for ( ; span_index < _spans.size() && _spans[span_index].in_chunk == chunk_index ; span_index++) { + line_height.max(_spans[span_index].line_height); + chunk_width = std::max(chunk_width, (double)std::max(_spans[span_index].x_start, _spans[span_index].x_end)); + } + double this_y_range; + if (local_y < _lines[_chunks[chunk_index].in_line].baseline_y - line_height.ascent) + this_y_range = _lines[_chunks[chunk_index].in_line].baseline_y - line_height.ascent - local_y; + else if (local_y > _lines[_chunks[chunk_index].in_line].baseline_y + line_height.descent) + this_y_range = local_y - (_lines[_chunks[chunk_index].in_line].baseline_y + line_height.descent); + else + this_y_range = 0.0; + if (this_y_range <= best_y_range) { + if (this_y_range < best_y_range) best_x_range = DBL_MAX; + double this_x_range; + if (local_x < _chunks[chunk_index].left_x) + this_x_range = _chunks[chunk_index].left_x - local_y; + else if (local_x > _chunks[chunk_index].left_x + chunk_width) + this_x_range = local_x - (_chunks[chunk_index].left_x + chunk_width); + else + this_x_range = 0.0; + if (this_x_range < best_x_range) { + best_y_range = this_y_range; + best_x_range = this_x_range; + best_chunk_index = chunk_index; + } + } + } + + // stage 3: + if (best_chunk_index == -1) return begin(); // never happens + return _cursorXOnLineToIterator(_chunks[best_chunk_index].in_line, local_x); +} + +Layout::iterator Layout::getLetterAt(double x, double y) const +{ + NR::Point point(x, y); + + double rotation; + for (iterator it = begin() ; it != end() ; it.nextCharacter()) { + NR::Rect box = characterBoundingBox(it, &rotation); + // todo: rotation + if (box.contains(point)) return it; + } + return end(); +} + +Layout::iterator Layout::sourceToIterator(void *source_cookie, Glib::ustring::const_iterator text_iterator) const +{ + unsigned source_index; + if (_characters.empty()) return end(); + for (source_index = 0 ; source_index < _input_stream.size() ; source_index++) + if (_input_stream[source_index]->source_cookie == source_cookie) break; + if (source_index == _input_stream.size()) return end(); + + unsigned char_index = _sourceToCharacter(source_index); + + if (_input_stream[source_index]->Type() != TEXT_SOURCE) + return iterator(this, char_index); + + InputStreamTextSource const *text_source = static_cast(_input_stream[source_index]); + if (text_iterator <= text_source->text_begin) return iterator(this, char_index); + if (text_iterator >= text_source->text_end) { + if (source_index == _input_stream.size() - 1) return end(); + return iterator(this, _sourceToCharacter(source_index + 1)); + } + Glib::ustring::const_iterator iter_text = text_source->text_begin; + for ( ; char_index < _characters.size() ; char_index++) { + if (iter_text == text_iterator) + return iterator(this, char_index); + iter_text++; + } + return end(); // never happens +} + +Layout::iterator Layout::sourceToIterator(void *source_cookie) const +{ + return sourceToIterator(source_cookie, Glib::ustring::const_iterator(std::string::const_iterator(NULL))); +} + +NR::Rect Layout::glyphBoundingBox(iterator const &it, double *rotation) const +{ + if (rotation) *rotation = _glyphs[it._glyph_index].rotation; + return _glyphs[it._glyph_index].span(this).font->BBox(_glyphs[it._glyph_index].glyph); +} + +NR::Point Layout::characterAnchorPoint(iterator const &it) const +{ + if (_characters.empty()) + return _empty_cursor_shape.position; + if (it._char_index == _characters.size()) { + return NR::Point(_chunks.back().left_x + _spans.back().x_end, _lines.back().baseline_y + _spans.back().baseline_shift); + } else { + return NR::Point(_characters[it._char_index].chunk(this).left_x + + _spans[_characters[it._char_index].in_span].x_start + + _characters[it._char_index].x, + _characters[it._char_index].line(this).baseline_y + + _characters[it._char_index].span(this).baseline_shift); + } +} + +NR::Point Layout::chunkAnchorPoint(iterator const &it) const +{ + unsigned chunk_index; + + if (_chunks.empty()) + return NR::Point(0.0, 0.0); + + if (_characters.empty()) + chunk_index = 0; + else if (it._char_index == _characters.size()) + chunk_index = _chunks.size() - 1; + else chunk_index = _characters[it._char_index].span(this).in_chunk; + + Alignment alignment = _paragraphs[_lines[_chunks[chunk_index].in_line].in_paragraph].alignment; + if (alignment == LEFT || alignment == FULL) + return NR::Point(_chunks[chunk_index].left_x, _lines[chunk_index].baseline_y); + + double chunk_width = _getChunkWidth(chunk_index); + if (alignment == RIGHT) + return NR::Point(_chunks[chunk_index].left_x + chunk_width, _lines[chunk_index].baseline_y); + //centre + return NR::Point(_chunks[chunk_index].left_x + chunk_width * 0.5, _lines[chunk_index].baseline_y); +} + +NR::Rect Layout::characterBoundingBox(iterator const &it, double *rotation) const +{ + NR::Point top_left, bottom_right; + unsigned char_index = it._char_index; + + if (_path_fitted) { + double cluster_half_width = 0.0; + for (int glyph_index = _characters[char_index].in_glyph ; _glyphs[glyph_index].in_character == char_index ; glyph_index++) + cluster_half_width += _glyphs[glyph_index].width; + cluster_half_width *= 0.5; + + double midpoint_offset = _characters[char_index].span(this).x_start + _characters[char_index].x + cluster_half_width; + int unused = 0; + Path::cut_position *midpoint_otp = const_cast(_path_fitted)->CurvilignToPosition(1, &midpoint_offset, unused); + if (midpoint_offset >= 0.0 && midpoint_otp != NULL && midpoint_otp[0].piece >= 0) { + NR::Point midpoint; + NR::Point tangent; + Span const &span = _characters[char_index].span(this); + double top = span.baseline_shift - span.line_height.ascent; + double bottom = span.baseline_shift + span.line_height.descent; + + const_cast(_path_fitted)->PointAndTangentAt(midpoint_otp[0].piece, midpoint_otp[0].t, midpoint, tangent); + top_left[NR::X] = midpoint[NR::X] - cluster_half_width; + top_left[NR::Y] = midpoint[NR::Y] + top; + bottom_right[NR::X] = midpoint[NR::X] + cluster_half_width; + bottom_right[NR::Y] = midpoint[NR::Y] + bottom; + if (rotation) + *rotation = atan2(tangent[1], tangent[0]); + } + g_free(midpoint_otp); + } else { + if (it._char_index == _characters.size()) { + top_left[NR::X] = bottom_right[NR::X] = _chunks.back().left_x + _spans.back().x_end; + char_index--; + } else { + double span_x = _spans[_characters[it._char_index].in_span].x_start + _characters[it._char_index].chunk(this).left_x; + top_left[NR::X] = span_x + _characters[it._char_index].x; + if (it._char_index + 1 == _characters.size() || _characters[it._char_index + 1].in_span != _characters[it._char_index].in_span) + bottom_right[NR::X] = _spans[_characters[it._char_index].in_span].x_end + _characters[it._char_index].chunk(this).left_x; + else + bottom_right[NR::X] = span_x + _characters[it._char_index + 1].x; + } + + double baseline_y = _characters[char_index].line(this).baseline_y + _characters[char_index].span(this).baseline_shift; + if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) { + double span_height = _spans[_characters[char_index].in_span].line_height.ascent + _spans[_characters[char_index].in_span].line_height.descent; + top_left[NR::Y] = top_left[NR::X]; + top_left[NR::X] = baseline_y - span_height * 0.5; + bottom_right[NR::Y] = bottom_right[NR::X]; + bottom_right[NR::X] = baseline_y + span_height * 0.5; + } else { + top_left[NR::Y] = baseline_y - _spans[_characters[char_index].in_span].line_height.ascent; + bottom_right[NR::Y] = baseline_y + _spans[_characters[char_index].in_span].line_height.descent; + } + + if (rotation) { + if (it._glyph_index == -1) + *rotation = 0.0; + else if (it._glyph_index == (int)_glyphs.size()) + *rotation = _glyphs.back().rotation; + else + *rotation = _glyphs[it._glyph_index].rotation; + } + } + + return NR::Rect(top_left, bottom_right); +} + +std::vector Layout::createSelectionShape(iterator const &it_start, iterator const &it_end, NR::Matrix const &transform) const +{ + std::vector quads; + unsigned char_index; + unsigned end_char_index; + + if (it_start._char_index < it_end._char_index) { + char_index = it_start._char_index; + end_char_index = it_end._char_index; + } else { + char_index = it_end._char_index; + end_char_index = it_start._char_index; + } + for ( ; char_index < end_char_index ; ) { + if (_characters[char_index].in_glyph == -1) { + char_index++; + continue; + } + double char_rotation = _glyphs[_characters[char_index].in_glyph].rotation; + unsigned span_index = _characters[char_index].in_span; + + NR::Point top_left, bottom_right; + if (_path_fitted || char_rotation != 0.0) { + NR::Rect box = characterBoundingBox(iterator(this, char_index), &char_rotation); + top_left = box.min(); + bottom_right = box.max(); + char_index++; + } else { // for straight text we can be faster by combining all the character boxes in a span into one box + double span_x = _spans[span_index].x_start + _spans[span_index].chunk(this).left_x; + top_left[NR::X] = span_x + _characters[char_index].x; + while (char_index < end_char_index && _characters[char_index].in_span == span_index) + char_index++; + if (char_index == _characters.size() || _characters[char_index].in_span != span_index) + bottom_right[NR::X] = _spans[span_index].x_end + _spans[span_index].chunk(this).left_x; + else + bottom_right[NR::X] = span_x + _characters[char_index].x; + + double baseline_y = _spans[span_index].line(this).baseline_y + _spans[span_index].baseline_shift; + if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) { + double span_height = _spans[span_index].line_height.ascent + _spans[span_index].line_height.descent; + top_left[NR::Y] = top_left[NR::X]; + top_left[NR::X] = baseline_y - span_height * 0.5; + bottom_right[NR::Y] = bottom_right[NR::X]; + bottom_right[NR::X] = baseline_y + span_height * 0.5; + } else { + top_left[NR::Y] = baseline_y - _spans[span_index].line_height.ascent; + bottom_right[NR::Y] = baseline_y + _spans[span_index].line_height.descent; + } + } + + NR::Rect char_box(top_left, bottom_right); + if (char_box.extent(NR::X) == 0.0 || char_box.extent(NR::Y) == 0.0) + continue; + NR::Point center_of_rotation((top_left[NR::X] + bottom_right[NR::X]) * 0.5, + top_left[NR::Y] + _spans[span_index].line_height.ascent); + NR::Matrix total_transform = NR::translate(-center_of_rotation) * NR::rotate(char_rotation) * NR::translate(center_of_rotation) * transform; + for(int i = 0; i < 4; i ++) + quads.push_back(char_box.corner(i) * total_transform); + } + return quads; +} + +void Layout::queryCursorShape(iterator const &it, NR::Point *position, double *height, double *rotation) const +{ + if (_characters.empty()) { + *position = _empty_cursor_shape.position; + *height = _empty_cursor_shape.height; + *rotation = _empty_cursor_shape.rotation; + } else { + // we want to cursor to be positioned where the left edge of a character that is about to be typed will be. + // this means x & rotation are the current values but y & height belong to the previous character. + // this isn't quite right because dx attributes will be moved along, but it's good enough + Span const *span; + if (_path_fitted) { + // text on a path + double x; + if (it._char_index >= _characters.size()) { + span = &_spans.back(); + x = span->x_end + _chunks.back().left_x - _chunks[0].left_x; + } else { + span = &_spans[_characters[it._char_index].in_span]; + x = _chunks[span->in_chunk].left_x + span->x_start + _characters[it._char_index].x - _chunks[0].left_x; + if (it._char_index != 0) + span = &_spans[_characters[it._char_index - 1].in_span]; + } + double path_length = const_cast(_path_fitted)->Length(); + double x_on_path = x; + if (x_on_path < 0.0) x_on_path = 0.0; + + int unused = 0; + // as far as I know these functions are const, they're just not marked as such + Path::cut_position *path_parameter_list = const_cast(_path_fitted)->CurvilignToPosition(1, &x_on_path, unused); + Path::cut_position path_parameter; + if (path_parameter_list != NULL && path_parameter_list[0].piece >= 0) + path_parameter = path_parameter_list[0]; + else { + path_parameter.piece = _path_fitted->descr_cmd.size() - 1; + path_parameter.t = 0.9999; // 1.0 will get the wrong tangent + } + g_free(path_parameter_list); + + NR::Point point; + NR::Point tangent; + const_cast(_path_fitted)->PointAndTangentAt(path_parameter.piece, path_parameter.t, point, tangent); + if (x < 0.0) + point += x * tangent; + if (x > path_length ) + point += (x - path_length) * tangent; + *rotation = atan2(tangent); + (*position)[NR::X] = point[NR::X] - tangent[NR::Y] * span->baseline_shift; + (*position)[NR::Y] = point[NR::Y] + tangent[NR::X] * span->baseline_shift; + } else { + // text is not on a path + if (it._char_index >= _characters.size()) { + span = &_spans.back(); + (*position)[NR::X] = _chunks[span->in_chunk].left_x + span->x_end; + *rotation = _glyphs.empty() ? 0.0 : _glyphs.back().rotation; + } else { + span = &_spans[_characters[it._char_index].in_span]; + (*position)[NR::X] = _chunks[span->in_chunk].left_x + span->x_start + _characters[it._char_index].x; + if (it._glyph_index == -1) *rotation = 0.0; + else if(it._glyph_index == 0) *rotation = _glyphs[0].rotation; + else *rotation = _glyphs[it._glyph_index - 1].rotation; + // the first char in a line wants to have the y of the new line, so in that case we don't switch to the previous span + if (it._char_index != 0 && _characters[it._char_index - 1].chunk(this).in_line == _chunks[span->in_chunk].in_line) + span = &_spans[_characters[it._char_index - 1].in_span]; + } + (*position)[NR::Y] = span->line(this).baseline_y + span->baseline_shift; + } + // up to now *position is the baseline point, not the final point which will be the bottom of the descent + if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) { + *height = span->line_height.ascent + span->line_height.descent; + *rotation += M_PI / 2; + std::swap((*position)[NR::X], (*position)[NR::Y]); + (*position)[NR::X] -= sin(*rotation) * *height * 0.5; + (*position)[NR::Y] += cos(*rotation) * *height * 0.5; + } else { + double caret_slope_run = 0.0, caret_slope_rise = 1.0; + if (span->font) + const_cast(span->font)->FontSlope(caret_slope_run, caret_slope_rise); + double caret_slope = atan2(caret_slope_run, caret_slope_rise); + *height = (span->line_height.ascent + span->line_height.descent) / cos(caret_slope); + *rotation += caret_slope; + (*position)[NR::X] -= sin(*rotation) * span->line_height.descent; + (*position)[NR::Y] += cos(*rotation) * span->line_height.descent; + } + } +} + +void Layout::getSourceOfCharacter(iterator const &it, void **source_cookie, Glib::ustring::iterator *text_iterator) const +{ + if (it._char_index == _characters.size()) { + *source_cookie = NULL; + return; + } + InputStreamItem *stream_item = _input_stream[_spans[_characters[it._char_index].in_span].in_input_stream_item]; + *source_cookie = stream_item->source_cookie; + if (text_iterator && stream_item->Type() == TEXT_SOURCE) { + InputStreamTextSource const *text_source = static_cast(stream_item); + Glib::ustring::const_iterator text_iter_const = text_source->text_begin; + unsigned char_index = it._char_index; + unsigned original_input_source_index = _spans[_characters[char_index].in_span].in_input_stream_item; + // confusing algorithm because the iterator goes forwards while the index goes backwards. + // It's just that it's faster doing it that way + while (char_index && _spans[_characters[char_index - 1].in_span].in_input_stream_item == original_input_source_index) { + ++text_iter_const; + char_index--; + } + text_source->text->begin().base() + (text_iter_const.base() - text_source->text->begin().base()); + *text_iterator = Glib::ustring::iterator(std::string::iterator(const_cast(&*text_source->text->begin().base() + (text_iter_const.base() - text_source->text->begin().base())))); + // the caller owns the string, so they're going to want a non-const iterator + } +} + +void Layout::simulateLayoutUsingKerning(iterator const &from, iterator const &to, OptionalTextTagAttrs *result) const +{ + SVGLength zero_length; + zero_length = 0.0; + + result->x.clear(); + result->y.clear(); + result->dx.clear(); + result->dy.clear(); + result->rotate.clear(); + if (to._char_index <= from._char_index) + return; + result->dx.reserve(to._char_index - from._char_index); + result->dy.reserve(to._char_index - from._char_index); + result->rotate.reserve(to._char_index - from._char_index); + for (unsigned char_index = from._char_index ; char_index < to._char_index ; char_index++) { + if (!_characters[char_index].char_attributes.is_char_break) + continue; + if (char_index == 0) + continue; + if (_characters[char_index].chunk(this).in_line != _characters[char_index - 1].chunk(this).in_line) + continue; + + unsigned prev_cluster_char_index; + for (prev_cluster_char_index = char_index - 1 ; + prev_cluster_char_index != 0 && !_characters[prev_cluster_char_index].char_attributes.is_cursor_position ; + prev_cluster_char_index--); + if (_characters[char_index].span(this).in_chunk == _characters[char_index - 1].span(this).in_chunk) { + // dx is zero for the first char in a chunk + // this algorithm works by comparing the summed widths of the glyphs with the observed + // difference in x coordinates of characters, and subtracting the two to produce the x kerning. + double glyphs_width = 0.0; + if (_characters[prev_cluster_char_index].in_glyph != -1) + for (int glyph_index = _characters[prev_cluster_char_index].in_glyph ; glyph_index < _characters[char_index].in_glyph ; glyph_index++) + glyphs_width += _glyphs[glyph_index].width; + if (_characters[char_index].span(this).direction == RIGHT_TO_LEFT) + glyphs_width = -glyphs_width; + + double dx = (_characters[char_index].x + _characters[char_index].span(this).x_start + - _characters[prev_cluster_char_index].x - _characters[prev_cluster_char_index].span(this).x_start) + - glyphs_width; + + + InputStreamItem *input_item = _input_stream[_characters[char_index].span(this).in_input_stream_item]; + if (input_item->Type() == TEXT_SOURCE) { + SPStyle const *style = static_cast(input_item)->style; + if (_characters[char_index].char_attributes.is_white) + dx -= style->word_spacing.computed; + if (_characters[char_index].char_attributes.is_cursor_position) + dx -= style->letter_spacing.computed; + } + + if (fabs(dx) > 0.0001) { + result->dx.resize(char_index - from._char_index + 1, zero_length); + result->dx.back() = dx; + } + } + double dy = _characters[char_index].span(this).baseline_shift - _characters[prev_cluster_char_index].span(this).baseline_shift; + if (fabs(dy) > 0.0001) { + result->dy.resize(char_index - from._char_index + 1, zero_length); + result->dy.back() = dy; + } + if (_characters[char_index].in_glyph != -1 && _glyphs[_characters[char_index].in_glyph].rotation != 0.0) { + result->rotate.resize(char_index - from._char_index + 1, zero_length); + result->rotate.back() = _glyphs[_characters[char_index].in_glyph].rotation; + } + } +} + +#define PREV_START_OF_ITEM(this_func) \ + { \ + _cursor_moving_vertically = false; \ + if (_char_index == 0) return false; \ + _char_index--; \ + return this_func(); \ + } +// end of macro + +#define THIS_START_OF_ITEM(item_getter) \ + { \ + _cursor_moving_vertically = false; \ + if (_char_index == 0) return false; \ + unsigned original_item; \ + if (_char_index == _parent_layout->_characters.size()) { \ + _char_index--; \ + original_item = item_getter; \ + } else { \ + original_item = item_getter; \ + _char_index--; \ + } \ + while (item_getter == original_item) { \ + if (_char_index == 0) { \ + _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \ + return true; \ + } \ + _char_index--; \ + } \ + _char_index++; \ + _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \ + return true; \ + } +// end of macro + +#define NEXT_START_OF_ITEM(item_getter) \ + { \ + _cursor_moving_vertically = false; \ + if (_char_index == _parent_layout->_characters.size()) return false; \ + unsigned original_item = item_getter; \ + for( ; ; ) { \ + _char_index++; \ + if (_char_index == _parent_layout->_characters.size()) { \ + _glyph_index = _parent_layout->_glyphs.size(); \ + return false; \ + } \ + if (item_getter != original_item) break; \ + } \ + _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \ + return true; \ + } +// end of macro + +bool Layout::iterator::prevStartOfSpan() + PREV_START_OF_ITEM(thisStartOfSpan); + +bool Layout::iterator::thisStartOfSpan() + THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].in_span); + +bool Layout::iterator::nextStartOfSpan() + NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].in_span); + + +bool Layout::iterator::prevStartOfChunk() + PREV_START_OF_ITEM(thisStartOfChunk); + +bool Layout::iterator::thisStartOfChunk() + THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_chunk); + +bool Layout::iterator::nextStartOfChunk() + NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_chunk); + + +bool Layout::iterator::prevStartOfLine() + PREV_START_OF_ITEM(thisStartOfLine); + +bool Layout::iterator::thisStartOfLine() + THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].chunk(_parent_layout).in_line); + +bool Layout::iterator::nextStartOfLine() + NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].chunk(_parent_layout).in_line); + + +bool Layout::iterator::prevStartOfShape() + PREV_START_OF_ITEM(thisStartOfShape); + +bool Layout::iterator::thisStartOfShape() + THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_shape); + +bool Layout::iterator::nextStartOfShape() + NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_shape); + + +bool Layout::iterator::prevStartOfParagraph() + PREV_START_OF_ITEM(thisStartOfParagraph); + +bool Layout::iterator::thisStartOfParagraph() + THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_paragraph); + +bool Layout::iterator::nextStartOfParagraph() + NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_paragraph); + + +bool Layout::iterator::prevStartOfSource() + PREV_START_OF_ITEM(thisStartOfSource); + +bool Layout::iterator::thisStartOfSource() + THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_input_stream_item); + +bool Layout::iterator::nextStartOfSource() + NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_input_stream_item); + + +bool Layout::iterator::thisEndOfLine() +{ + if (_char_index == _parent_layout->_characters.size()) return false; + if (nextStartOfLine()) + { + if (_char_index && _parent_layout->_characters[_char_index - 1].char_attributes.is_white) + return prevCursorPosition(); + return true; + } + if (_char_index && _parent_layout->_characters[_char_index - 1].chunk(_parent_layout).in_line != _parent_layout->_lines.size() - 1) + return prevCursorPosition(); // for when the last paragraph is empty + return false; +} + +void Layout::iterator::beginCursorUpDown() +{ + if (_char_index == _parent_layout->_characters.size()) + _x_coordinate = _parent_layout->_chunks.back().left_x + _parent_layout->_spans.back().x_end; + else + _x_coordinate = _parent_layout->_characters[_char_index].x + _parent_layout->_characters[_char_index].span(_parent_layout).x_start + _parent_layout->_characters[_char_index].chunk(_parent_layout).left_x; + _cursor_moving_vertically = true; +} + +bool Layout::iterator::nextLineCursor() +{ + if (!_cursor_moving_vertically) + beginCursorUpDown(); + if (_char_index == _parent_layout->_characters.size()) + return false; + unsigned line_index = _parent_layout->_characters[_char_index].chunk(_parent_layout).in_line; + if (line_index == _parent_layout->_lines.size() - 1) return false; + if (_parent_layout->_lines[line_index + 1].in_shape != _parent_layout->_lines[line_index].in_shape) { + // switching between shapes: adjust the stored x to compensate + _x_coordinate += _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index + 1)].in_chunk].left_x + - _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index)].in_chunk].left_x; + } + _char_index = _parent_layout->_cursorXOnLineToIterator(line_index + 1, _x_coordinate)._char_index; + _glyph_index = _parent_layout->_characters[_char_index].in_glyph; + return true; +} + +bool Layout::iterator::prevLineCursor() +{ + if (!_cursor_moving_vertically) + beginCursorUpDown(); + unsigned line_index; + if (_char_index == _parent_layout->_characters.size()) + line_index = _parent_layout->_lines.size() - 1; + else + line_index = _parent_layout->_characters[_char_index].chunk(_parent_layout).in_line; + if (line_index == 0) return false; + if (_parent_layout->_lines[line_index - 1].in_shape != _parent_layout->_lines[line_index].in_shape) { + // switching between shapes: adjust the stored x to compensate + _x_coordinate += _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index - 1)].in_chunk].left_x + - _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index)].in_chunk].left_x; + } + _char_index = _parent_layout->_cursorXOnLineToIterator(line_index - 1, _x_coordinate)._char_index; + _glyph_index = _parent_layout->_characters[_char_index].in_glyph; + return true; +} + +#define NEXT_WITH_ATTRIBUTE_SET(attr) \ + { \ + _cursor_moving_vertically = false; \ + for ( ; ; ) { \ + if (_char_index + 1 >= _parent_layout->_characters.size()) { \ + _char_index = _parent_layout->_characters.size(); \ + _glyph_index = _parent_layout->_glyphs.size(); \ + return false; \ + } \ + _char_index++; \ + if (_parent_layout->_characters[_char_index].char_attributes.attr) break; \ + } \ + _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \ + return true; \ + } +// end of macro + +#define PREV_WITH_ATTRIBUTE_SET(attr) \ + { \ + _cursor_moving_vertically = false; \ + for ( ; ; ) { \ + if (_char_index == 0) { \ + _glyph_index = 0; \ + return true; \ + } \ + _char_index--; \ + if (_parent_layout->_characters[_char_index].char_attributes.attr) break; \ + } \ + _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \ + return true; \ + } +// end of macro + +bool Layout::iterator::nextCursorPosition() + NEXT_WITH_ATTRIBUTE_SET(is_cursor_position); + +bool Layout::iterator::prevCursorPosition() + PREV_WITH_ATTRIBUTE_SET(is_cursor_position); + +bool Layout::iterator::nextStartOfWord() + NEXT_WITH_ATTRIBUTE_SET(is_word_start); + +bool Layout::iterator::prevStartOfWord() + PREV_WITH_ATTRIBUTE_SET(is_word_start); + +bool Layout::iterator::nextEndOfWord() + NEXT_WITH_ATTRIBUTE_SET(is_word_end); + +bool Layout::iterator::prevEndOfWord() + PREV_WITH_ATTRIBUTE_SET(is_word_end); + +bool Layout::iterator::nextStartOfSentence() + NEXT_WITH_ATTRIBUTE_SET(is_sentence_start); + +bool Layout::iterator::prevStartOfSentence() + PREV_WITH_ATTRIBUTE_SET(is_sentence_start); + +bool Layout::iterator::nextEndOfSentence() + NEXT_WITH_ATTRIBUTE_SET(is_sentence_end); + +bool Layout::iterator::prevEndOfSentence() + PREV_WITH_ATTRIBUTE_SET(is_sentence_end); + +bool Layout::iterator::_cursorLeftOrRightLocalX(Direction direction) +{ + // the only reason this function is so complicated is to enable visual cursor + // movement moving in to or out of counterdirectional runs + if (_parent_layout->_characters.empty()) return false; + unsigned old_span_index; + Direction old_span_direction; + if (_char_index == _parent_layout->_characters.size()) + old_span_index = _parent_layout->_spans.size() - 1; + else + old_span_index = _parent_layout->_characters[_char_index].in_span; + old_span_direction = _parent_layout->_spans[old_span_index].direction; + Direction para_direction = _parent_layout->_spans[old_span_index].paragraph(_parent_layout).base_direction; + + int scan_direction; + unsigned old_char_index = _char_index; + if (old_span_direction != para_direction + && ((_char_index == 0 && direction == para_direction) + || (_char_index == _parent_layout->_characters.size() && direction != para_direction))) { + // the end of the text is actually in the middle because of reordering. Do cleverness + scan_direction = direction == para_direction ? +1 : -1; + } else { + if (direction == old_span_direction) { + if (!nextCursorPosition()) return false; + } else { + if (!prevCursorPosition()) return false; + } + + unsigned new_span_index = _parent_layout->_characters[_char_index].in_span; + if (new_span_index == old_span_index) return true; + if (old_span_direction != _parent_layout->_spans[new_span_index].direction) { + // we must jump to the other end of a counterdirectional run + scan_direction = direction == para_direction ? +1 : -1; + } else if (_parent_layout->_spans[old_span_index].in_chunk != _parent_layout->_spans[new_span_index].in_chunk) { + // we might have to do a weird jump when we would have crossed a chunk/line break + if (_parent_layout->_spans[old_span_index].line(_parent_layout).in_paragraph != _parent_layout->_spans[new_span_index].line(_parent_layout).in_paragraph) + return true; + if (old_span_direction == para_direction) + return true; + scan_direction = direction == para_direction ? +1 : -1; + } else + return true; // same direction, same chunk: no cleverness required + } + + unsigned new_span_index = old_span_index; + for ( ; ; ) { + if (scan_direction > 0) { + if (new_span_index == _parent_layout->_spans.size() - 1) { + if (_parent_layout->_spans[new_span_index].direction == old_span_direction) { + _char_index = old_char_index; + return false; // the visual end is in the logical middle + } + break; + } + new_span_index++; + } else { + if (new_span_index == 0) { + if (_parent_layout->_spans[new_span_index].direction == old_span_direction) { + _char_index = old_char_index; + return false; // the visual end is in the logical middle + } + break; + } + new_span_index--; + } + if (_parent_layout->_spans[new_span_index].direction == para_direction) { + if (para_direction == old_span_direction) + new_span_index -= scan_direction; + break; + } + if (_parent_layout->_spans[new_span_index].in_chunk != _parent_layout->_spans[old_span_index].in_chunk) { + if (_parent_layout->_spans[old_span_index].line(_parent_layout).in_paragraph == _parent_layout->_spans[new_span_index].line(_parent_layout).in_paragraph + && para_direction == old_span_direction) + new_span_index -= scan_direction; + break; + } + } + + // found the correct span, now find the correct character + if (_parent_layout->_spans[old_span_index].line(_parent_layout).in_paragraph != _parent_layout->_spans[new_span_index].line(_parent_layout).in_paragraph) { + if (new_span_index > old_span_index) + _char_index = _parent_layout->_spanToCharacter(new_span_index); + else + _char_index = _parent_layout->_spanToCharacter(new_span_index + 1) - 1; + } else { + if (_parent_layout->_spans[new_span_index].direction != direction) { + if (new_span_index >= _parent_layout->_spans.size() - 1) + _char_index = _parent_layout->_characters.size(); + else + _char_index = _parent_layout->_spanToCharacter(new_span_index + 1) - 1; + } else + _char_index = _parent_layout->_spanToCharacter(new_span_index); + } + if (_char_index == _parent_layout->_characters.size()) { + _glyph_index = _parent_layout->_glyphs.size(); + return false; + } + _glyph_index = _parent_layout->_characters[_char_index].in_glyph; + return _char_index != 0; +} + +bool Layout::iterator::_cursorLeftOrRightLocalXByWord(Direction direction) +{ + bool r; + while ((r = _cursorLeftOrRightLocalX(direction)) + && !_parent_layout->_characters[_char_index].char_attributes.is_word_start); + return r; +} + +bool Layout::iterator::cursorUp() +{ + Direction block_progression = _parent_layout->_blockProgression(); + if(block_progression == TOP_TO_BOTTOM) + return prevLineCursor(); + else if(block_progression == BOTTOM_TO_TOP) + return nextLineCursor(); + else + return _cursorLeftOrRightLocalX(RIGHT_TO_LEFT); +} + +bool Layout::iterator::cursorDown() +{ + Direction block_progression = _parent_layout->_blockProgression(); + if(block_progression == TOP_TO_BOTTOM) + return nextLineCursor(); + else if(block_progression == BOTTOM_TO_TOP) + return prevLineCursor(); + else + return _cursorLeftOrRightLocalX(LEFT_TO_RIGHT); +} + +bool Layout::iterator::cursorLeft() +{ + Direction block_progression = _parent_layout->_blockProgression(); + if(block_progression == LEFT_TO_RIGHT) + return prevLineCursor(); + else if(block_progression == RIGHT_TO_LEFT) + return nextLineCursor(); + else + return _cursorLeftOrRightLocalX(RIGHT_TO_LEFT); +} + +bool Layout::iterator::cursorRight() +{ + Direction block_progression = _parent_layout->_blockProgression(); + if(block_progression == LEFT_TO_RIGHT) + return nextLineCursor(); + else if(block_progression == RIGHT_TO_LEFT) + return prevLineCursor(); + else + return _cursorLeftOrRightLocalX(LEFT_TO_RIGHT); +} + +bool Layout::iterator::cursorUpWithControl() +{ + Direction block_progression = _parent_layout->_blockProgression(); + if(block_progression == TOP_TO_BOTTOM) + return prevStartOfParagraph(); + else if(block_progression == BOTTOM_TO_TOP) + return nextStartOfParagraph(); + else + return _cursorLeftOrRightLocalXByWord(RIGHT_TO_LEFT); +} + +bool Layout::iterator::cursorDownWithControl() +{ + Direction block_progression = _parent_layout->_blockProgression(); + if(block_progression == TOP_TO_BOTTOM) + return nextStartOfParagraph(); + else if(block_progression == BOTTOM_TO_TOP) + return prevStartOfParagraph(); + else + return _cursorLeftOrRightLocalXByWord(LEFT_TO_RIGHT); +} + +bool Layout::iterator::cursorLeftWithControl() +{ + Direction block_progression = _parent_layout->_blockProgression(); + if(block_progression == LEFT_TO_RIGHT) + return prevStartOfParagraph(); + else if(block_progression == RIGHT_TO_LEFT) + return nextStartOfParagraph(); + else + return _cursorLeftOrRightLocalXByWord(RIGHT_TO_LEFT); +} + +bool Layout::iterator::cursorRightWithControl() +{ + Direction block_progression = _parent_layout->_blockProgression(); + if(block_progression == LEFT_TO_RIGHT) + return nextStartOfParagraph(); + else if(block_progression == RIGHT_TO_LEFT) + return prevStartOfParagraph(); + else + return _cursorLeftOrRightLocalXByWord(LEFT_TO_RIGHT); +} + +}//namespace Text +}//namespace Inkscape diff --git a/src/libnrtype/Layout-TNG-Output.cpp b/src/libnrtype/Layout-TNG-Output.cpp new file mode 100755 index 000000000..08eb403db --- /dev/null +++ b/src/libnrtype/Layout-TNG-Output.cpp @@ -0,0 +1,469 @@ +/* + * Inkscape::Text::Layout - text layout engine output functions + * + * Authors: + * Richard Hughes + * + * Copyright (C) 2005 Richard Hughes + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ +#include "Layout-TNG.h" +#include "display/nr-arena-glyphs.h" +#include "style.h" +#include "print.h" +#include "extension/print.h" +#include "livarot/Path.h" +#include "libnr/nr-scale-matrix-ops.h" +#include "font-instance.h" +#include "svg/svg-length.h" + +namespace Inkscape { +namespace Text { + +void Layout::_clearOutputObjects() +{ + _paragraphs.clear(); + _lines.clear(); + _chunks.clear(); + for (std::vector::iterator it_span = _spans.begin() ; it_span != _spans.end() ; it_span++) + if (it_span->font) it_span->font->Unref(); + _spans.clear(); + _characters.clear(); + _glyphs.clear(); + _path_fitted = NULL; +} + +void Layout::LineHeight::max(LineHeight const &other) +{ + if (other.ascent > ascent) ascent = other.ascent; + if (other.descent > descent) descent = other.descent; + if (other.leading > leading) leading = other.leading; +} + +void Layout::_getGlyphTransformMatrix(int glyph_index, NRMatrix *matrix) const +{ + Span const &span = _glyphs[glyph_index].span(this); + double sin_rotation = sin(_glyphs[glyph_index].rotation); + double cos_rotation = cos(_glyphs[glyph_index].rotation); + (*matrix)[0] = span.font_size * cos_rotation; + (*matrix)[1] = span.font_size * sin_rotation; + (*matrix)[2] = span.font_size * sin_rotation; + (*matrix)[3] = -span.font_size * cos_rotation; + if (span.block_progression == LEFT_TO_RIGHT || span.block_progression == RIGHT_TO_LEFT) { + (*matrix)[4] = _lines[_chunks[span.in_chunk].in_line].baseline_y + _glyphs[glyph_index].y; + (*matrix)[5] = _chunks[span.in_chunk].left_x + _glyphs[glyph_index].x; + } else { + (*matrix)[4] = _chunks[span.in_chunk].left_x + _glyphs[glyph_index].x; + (*matrix)[5] = _lines[_chunks[span.in_chunk].in_line].baseline_y + _glyphs[glyph_index].y; + } +} + +void Layout::show(NRArenaGroup *in_arena, NRRect const *paintbox) const +{ + int glyph_index = 0; + for (unsigned span_index = 0 ; span_index < _spans.size() ; span_index++) { + if (_input_stream[_spans[span_index].in_input_stream_item]->Type() != TEXT_SOURCE) continue; + InputStreamTextSource const *text_source = static_cast(_input_stream[_spans[span_index].in_input_stream_item]); + NRArenaGlyphsGroup *nr_group = NRArenaGlyphsGroup::create(in_arena->arena); + nr_arena_item_add_child(in_arena, nr_group, NULL); + nr_arena_item_unref(nr_group); + + nr_arena_glyphs_group_set_style(nr_group, text_source->style); + while (glyph_index < (int)_glyphs.size() && _characters[_glyphs[glyph_index].in_character].in_span == span_index) { + if (_characters[_glyphs[glyph_index].in_character].in_glyph != -1) { + NRMatrix glyph_matrix; + _getGlyphTransformMatrix(glyph_index, &glyph_matrix); + nr_arena_glyphs_group_add_component(nr_group, _spans[span_index].font, _glyphs[glyph_index].glyph, &glyph_matrix); + } + glyph_index++; + } + nr_arena_glyphs_group_set_paintbox(NR_ARENA_GLYPHS_GROUP(nr_group), paintbox); + } + nr_arena_item_request_update(NR_ARENA_ITEM(in_arena), NR_ARENA_ITEM_STATE_ALL, FALSE); +} + +void Layout::getBoundingBox(NRRect *bounding_box, NR::Matrix const &transform) const +{ + for (unsigned glyph_index = 0 ; glyph_index < _glyphs.size() ; glyph_index++) { + if (_characters[_glyphs[glyph_index].in_character].in_glyph == -1) continue; + // this could be faster + NRMatrix glyph_matrix; + _getGlyphTransformMatrix(glyph_index, &glyph_matrix); + NR::Matrix total_transform = glyph_matrix; + total_transform *= transform; + NR::Rect glyph_rect = _glyphs[glyph_index].span(this).font->BBox(_glyphs[glyph_index].glyph); + NR::Point bmi = glyph_rect.min(), bma = glyph_rect.max(); + NR::Point tlp(bmi[0],bmi[1]), trp(bma[0],bmi[1]), blp(bmi[0],bma[1]), brp(bma[0],bma[1]); + tlp *= total_transform; + trp *= total_transform; + blp *= total_transform; + brp *= total_transform; + glyph_rect = NR::Rect(tlp,trp); + glyph_rect.expandTo(blp); + glyph_rect.expandTo(brp); + if ( (glyph_rect.min())[0] < bounding_box->x0 ) bounding_box->x0=(glyph_rect.min())[0]; + if ( (glyph_rect.max())[0] > bounding_box->x1 ) bounding_box->x1=(glyph_rect.max())[0]; + if ( (glyph_rect.min())[1] < bounding_box->y0 ) bounding_box->y0=(glyph_rect.min())[1]; + if ( (glyph_rect.max())[1] > bounding_box->y1 ) bounding_box->y1=(glyph_rect.max())[1]; + } +} + +void Layout::print(SPPrintContext *ctx, + NRRect const *pbox, NRRect const *dbox, NRRect const *bbox, + NRMatrix const &ctm) const +{ + if (_input_stream.empty()) return; + + Direction block_progression = _blockProgression(); + bool text_to_path = ctx->module->textToPath(); + for (unsigned glyph_index = 0 ; glyph_index < _glyphs.size() ; ) { + if (_characters[_glyphs[glyph_index].in_character].in_glyph == -1) { + // invisible glyphs + unsigned same_character = _glyphs[glyph_index].in_character; + while (_glyphs[glyph_index].in_character == same_character) + glyph_index++; + continue; + } + NRMatrix glyph_matrix; + Span const &span = _spans[_characters[_glyphs[glyph_index].in_character].in_span]; + InputStreamTextSource const *text_source = static_cast(_input_stream[span.in_input_stream_item]); + if (text_to_path || _path_fitted) { + NRBPath bpath; + bpath.path = (NArtBpath*)span.font->ArtBPath(_glyphs[glyph_index].glyph); + if (bpath.path) { + NRBPath abp; + _getGlyphTransformMatrix(glyph_index, &glyph_matrix); + abp.path = nr_artpath_affine(bpath.path, glyph_matrix); + if (text_source->style->fill.type != SP_PAINT_TYPE_NONE) + sp_print_fill(ctx, &abp, &ctm, text_source->style, pbox, dbox, bbox); + if (text_source->style->stroke.type != SP_PAINT_TYPE_NONE) + sp_print_stroke(ctx, &abp, &ctm, text_source->style, pbox, dbox, bbox); + nr_free(abp.path); + } + glyph_index++; + } else { + NR::Point g_pos(0,0); // all strings are output at (0,0) because we do the translation using the matrix + glyph_matrix = NR::Matrix(NR::scale(1.0, -1.0) * NR::Matrix(NR::rotate(_glyphs[glyph_index].rotation))); + if (block_progression == LEFT_TO_RIGHT || block_progression == RIGHT_TO_LEFT) { + glyph_matrix.c[4] = span.line(this).baseline_y + span.baseline_shift; + // since we're outputting character codes, not glyphs, we want the character x + glyph_matrix.c[5] = span.chunk(this).left_x + span.x_start + _characters[_glyphs[glyph_index].in_character].x; + } else { + glyph_matrix.c[4] = span.chunk(this).left_x + span.x_start + _characters[_glyphs[glyph_index].in_character].x; + glyph_matrix.c[5] = span.line(this).baseline_y + span.baseline_shift; + } + Glib::ustring::const_iterator span_iter = span.input_stream_first_character; + unsigned char_index = _glyphs[glyph_index].in_character; + unsigned original_span = _characters[char_index].in_span; + while (char_index && _characters[char_index - 1].in_span == original_span) { + char_index--; + span_iter++; + } + + // try to output as many characters as possible in one go by detecting kerning and stopping when we encounter it + Glib::ustring span_string; + double char_x = _characters[_glyphs[glyph_index].in_character].x; + unsigned this_span_index = _characters[_glyphs[glyph_index].in_character].in_span; + do { + span_string += *span_iter; + span_iter++; + + unsigned same_character = _glyphs[glyph_index].in_character; + while (glyph_index < _glyphs.size() && _glyphs[glyph_index].in_character == same_character) { + char_x += _glyphs[glyph_index].width; + glyph_index++; + } + } while (glyph_index < _glyphs.size() + && _path_fitted == NULL + && _characters[_glyphs[glyph_index].in_character].in_span == this_span_index + && fabs(char_x - _characters[_glyphs[glyph_index].in_character].x) < 1e-5); + sp_print_bind(ctx, glyph_matrix, 1.0); + sp_print_text(ctx, span_string.c_str(), g_pos, text_source->style); + sp_print_release(ctx); + } + } +} + +// these functions are for dumpAsText() only. No need to translate +static char const *direction_to_text(Layout::Direction d) +{ + switch (d) { + case Layout::LEFT_TO_RIGHT: return "ltr"; + case Layout::RIGHT_TO_LEFT: return "rtl"; + case Layout::TOP_TO_BOTTOM: return "ttb"; + case Layout::BOTTOM_TO_TOP: return "btt"; + } + return "???"; +} + +static char const *style_to_text(PangoStyle s) +{ + switch (s) { + case PANGO_STYLE_NORMAL: return "upright"; + case PANGO_STYLE_ITALIC: return "italic"; + case PANGO_STYLE_OBLIQUE: return "oblique"; + } + return "???"; +} + +static char const *weight_to_text(PangoWeight w) +{ + switch (w) { + case PANGO_WEIGHT_ULTRALIGHT: return "ultralight"; + case PANGO_WEIGHT_LIGHT : return "light"; + case PANGO_WEIGHT_SEMIBOLD : return "semibold"; + case PANGO_WEIGHT_NORMAL : return "normalweight"; + case PANGO_WEIGHT_BOLD : return "bold"; + case PANGO_WEIGHT_ULTRABOLD : return "ultrabold"; + case PANGO_WEIGHT_HEAVY : return "heavy"; + } + return "???"; +} + +Glib::ustring Layout::dumpAsText() const +{ + Glib::ustring result; + + for (unsigned span_index = 0 ; span_index < _spans.size() ; span_index++) { + char line[256]; + snprintf(line, sizeof(line), "==== span %d\n", span_index); + result += line; + snprintf(line, sizeof(line), " in para %d (direction=%s)\n", _lines[_chunks[_spans[span_index].in_chunk].in_line].in_paragraph, + direction_to_text(_paragraphs[_lines[_chunks[_spans[span_index].in_chunk].in_line].in_paragraph].base_direction)); + result += line; + snprintf(line, sizeof(line), " in source %d (type=%d, cookie=%p)\n", _spans[span_index].in_input_stream_item, + _input_stream[_spans[span_index].in_input_stream_item]->Type(), + _input_stream[_spans[span_index].in_input_stream_item]->source_cookie); + result += line; + snprintf(line, sizeof(line), " in line %d (baseline=%f, shape=%d)\n", _chunks[_spans[span_index].in_chunk].in_line, + _lines[_chunks[_spans[span_index].in_chunk].in_line].baseline_y, + _lines[_chunks[_spans[span_index].in_chunk].in_line].in_shape); + result += line; + snprintf(line, sizeof(line), " in chunk %d (x=%f, baselineshift=%f)\n", _spans[span_index].in_chunk, _chunks[_spans[span_index].in_chunk].left_x, _spans[span_index].baseline_shift); + result += line; + if (_spans[span_index].font) { + snprintf(line, sizeof(line), " font '%s' %f %s %s\n", pango_font_description_get_family(_spans[span_index].font->descr), _spans[span_index].font_size, style_to_text(pango_font_description_get_style(_spans[span_index].font->descr)), weight_to_text(pango_font_description_get_weight(_spans[span_index].font->descr))); + result += line; + } + snprintf(line, sizeof(line), " x_start = %f, x_end = %f\n", _spans[span_index].x_start, _spans[span_index].x_end); + result += line; + snprintf(line, sizeof(line), " line height: ascent %f, descent %f leading %f\n", _spans[span_index].line_height.ascent, _spans[span_index].line_height.descent, _spans[span_index].line_height.leading); + result += line; + snprintf(line, sizeof(line), " direction %s, block-progression %s\n", direction_to_text(_spans[span_index].direction), direction_to_text(_spans[span_index].block_progression)); + result += line; + result += " ** characters:\n"; + Glib::ustring::const_iterator iter_char = _spans[span_index].input_stream_first_character; + // very inefficent code. what the hell, it's only debug stuff. + for (unsigned char_index = 0 ; char_index < _characters.size() ; char_index++) { + if (_characters[char_index].in_span != span_index) continue; + if (_input_stream[_spans[span_index].in_input_stream_item]->Type() != TEXT_SOURCE) { + snprintf(line, sizeof(line), " %d: control x=%f flags=%03x glyph=%d\n", char_index, _characters[char_index].x, *(unsigned*)&_characters[char_index].char_attributes, _characters[char_index].in_glyph); + } else { + snprintf(line, sizeof(line), " %d: '%c' x=%f flags=%03x glyph=%d\n", char_index, *iter_char, _characters[char_index].x, *(unsigned*)&_characters[char_index].char_attributes, _characters[char_index].in_glyph); + iter_char++; + } + result += line; + } + result += " ** glyphs:\n"; + for (unsigned glyph_index = 0 ; glyph_index < _glyphs.size() ; glyph_index++) { + if (_characters[_glyphs[glyph_index].in_character].in_span != span_index) continue; + snprintf(line, sizeof(line), " %d: %d (%f,%f) rot=%f cx=%f char=%d\n", glyph_index, _glyphs[glyph_index].glyph, _glyphs[glyph_index].x, _glyphs[glyph_index].y, _glyphs[glyph_index].rotation, _glyphs[glyph_index].width, _glyphs[glyph_index].in_character); + result += line; + } + result += "\n"; + } + result += "EOT\n"; + return result; +} + +void Layout::fitToPathAlign(SVGLength const &startOffset, Path const &path) +{ + double offset = 0.0; + + if (startOffset._set) { + if (startOffset.unit == SVGLength::PERCENT) + offset = startOffset.computed * const_cast(path).Length(); + else + offset = startOffset.computed; + } + + switch (_paragraphs.front().alignment) { + case CENTER: + offset -= _getChunkWidth(0) * 0.5; + break; + case RIGHT: + offset -= _getChunkWidth(0); + break; + default: + break; + } + + if (_characters.empty()) { + int unused = 0; + Path::cut_position *point_otp = const_cast(path).CurvilignToPosition(1, &offset, unused); + if (offset >= 0.0 && point_otp != NULL && point_otp[0].piece >= 0) { + NR::Point point; + NR::Point tangent; + const_cast(path).PointAndTangentAt(point_otp[0].piece, point_otp[0].t, point, tangent); + _empty_cursor_shape.position = point; + _empty_cursor_shape.rotation = atan2(tangent[NR::Y], tangent[NR::X]); + } + } + + for (unsigned char_index = 0 ; char_index < _characters.size() ; ) { + int next_cluster_glyph_index; + unsigned next_cluster_char_index; + double character_advance; + Span const &span = _characters[char_index].span(this); + + for (next_cluster_char_index = char_index + 1 ; + next_cluster_char_index < _characters.size() && !_characters[next_cluster_char_index].char_attributes.is_cursor_position; + next_cluster_char_index++); + + if (next_cluster_char_index == _characters.size()) { + next_cluster_glyph_index = _glyphs.size(); + character_advance = 0.0; // arbitrary because we're not going to advance + } else { + next_cluster_glyph_index = _characters[next_cluster_char_index].in_glyph; + character_advance = (_glyphs[next_cluster_glyph_index].x + _glyphs[next_cluster_glyph_index].chunk(this).left_x) + - (_glyphs[_characters[char_index].in_glyph].x + span.chunk(this).left_x); + } + + double start_offset = offset + span.x_start + _characters[char_index].x; + double cluster_width = 0.0; + for (int glyph_index = _characters[char_index].in_glyph ; glyph_index < next_cluster_glyph_index ; glyph_index++) + cluster_width += _glyphs[glyph_index].width; + if (span.direction == RIGHT_TO_LEFT) + start_offset -= cluster_width; + double end_offset = start_offset + cluster_width; + + int unused = 0; + double midpoint_offset = (start_offset + end_offset) * 0.5; + // as far as I know these functions are const, they're just not marked as such + Path::cut_position *midpoint_otp = const_cast(path).CurvilignToPosition(1, &midpoint_offset, unused); + if (midpoint_offset >= 0.0 && midpoint_otp != NULL && midpoint_otp[0].piece >= 0) { + NR::Point midpoint; + NR::Point tangent; + + const_cast(path).PointAndTangentAt(midpoint_otp[0].piece, midpoint_otp[0].t, midpoint, tangent); + + if (start_offset >= 0.0 && end_offset >= 0.0) { + Path::cut_position *start_otp = const_cast(path).CurvilignToPosition(1, &start_offset, unused); + if (start_otp != NULL && start_otp[0].piece >= 0) { + Path::cut_position *end_otp = const_cast(path).CurvilignToPosition(1, &end_offset, unused); + if (end_otp != NULL && end_otp[0].piece >= 0) { + bool on_same_subpath = true; + for (size_t i = 0 ; i < path.pts.size() ; i++) { + if (path.pts[i].piece <= start_otp[0].piece) continue; + if (path.pts[i].piece >= end_otp[0].piece) break; + if (path.pts[i].isMoveTo == polyline_moveto) { + on_same_subpath = false; + break; + } + } + if (on_same_subpath) { + // both points were on the same subpath (without this test the angle is very weird) + NR::Point startpoint, endpoint; + const_cast(path).PointAt(start_otp[0].piece, start_otp[0].t, startpoint); + const_cast(path).PointAt(end_otp[0].piece, end_otp[0].t, endpoint); + if (endpoint != startpoint) { + tangent = endpoint - startpoint; + tangent.normalize(); + } else { + tangent = NR::Point (0,0); + } + } + g_free(end_otp); + } + g_free(start_otp); + } + } + + double rotation = atan2(tangent[1], tangent[0]); + for (int glyph_index = _characters[char_index].in_glyph ; glyph_index < next_cluster_glyph_index ; glyph_index++) { + double tangent_shift = -cluster_width * 0.5 + _glyphs[glyph_index].x - (_characters[char_index].x + span.x_start); + double normal_shift = _glyphs[glyph_index].y; + if (span.direction == RIGHT_TO_LEFT) + tangent_shift += cluster_width; + _glyphs[glyph_index].x = midpoint[0] - span.chunk(this).left_x + tangent[0] * tangent_shift - tangent[1] * normal_shift; + _glyphs[glyph_index].y = midpoint[1] - _lines.front().baseline_y + tangent[1] * tangent_shift + tangent[0] * normal_shift; + _glyphs[glyph_index].rotation += rotation; + } + } else { // outside the bounds of the path: hide the glyphs + _characters[char_index].in_glyph = -1; + } + g_free(midpoint_otp); + + char_index = next_cluster_char_index; + } + + for (unsigned span_index = 0 ; span_index < _spans.size() ; span_index++) { + _spans[span_index].x_start += offset; + _spans[span_index].x_end += offset; + } + + _path_fitted = &path; +} + +SPCurve *Layout::convertToCurves(iterator const &from_glyph, iterator const &to_glyph) const +{ + GSList *cc = NULL; + + for (int glyph_index = from_glyph._glyph_index ; glyph_index < to_glyph._glyph_index ; glyph_index++) { + NRMatrix glyph_matrix; + Span const &span = _glyphs[glyph_index].span(this); + _getGlyphTransformMatrix(glyph_index, &glyph_matrix); + + NRBPath bpath; + bpath.path = (NArtBpath*)span.font->ArtBPath(_glyphs[glyph_index].glyph); + if (bpath.path) { + NArtBpath *abp = nr_artpath_affine(bpath.path, glyph_matrix); + SPCurve *c = sp_curve_new_from_bpath(abp); + if (c) cc = g_slist_prepend(cc, c); + } + } + cc = g_slist_reverse(cc); + + SPCurve *curve; + if ( cc ) { + curve = sp_curve_concat(cc); + } else { + curve = sp_curve_new(); + } + + while (cc) { + /* fixme: This is dangerous, as we are mixing art_alloc and g_new */ + sp_curve_unref((SPCurve *) cc->data); + cc = g_slist_remove(cc, cc->data); + } + + return curve; +} + +void Layout::transform(NR::Matrix const &transform) +{ + // this is all massively oversimplified + // I can't actually think of anybody who'll want to use it at the moment, so it'll stay simple + for (unsigned glyph_index = 0 ; glyph_index < _glyphs.size() ; glyph_index++) { + NR::Point point(_glyphs[glyph_index].x, _glyphs[glyph_index].y); + point *= transform; + _glyphs[glyph_index].x = point[0]; + _glyphs[glyph_index].y = point[1]; + } +} + +}//namespace Text +}//namespace Inkscape + + +/* + 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 : diff --git a/src/libnrtype/Layout-TNG-Scanline-Maker.h b/src/libnrtype/Layout-TNG-Scanline-Maker.h new file mode 100755 index 000000000..3865e2c12 --- /dev/null +++ b/src/libnrtype/Layout-TNG-Scanline-Maker.h @@ -0,0 +1,169 @@ +/* + * Inkscape::Text::Layout::ScanlineMaker - text layout engine shape measurers + * + * Authors: + * Richard Hughes + * + * Copyright (C) 2005 Richard Hughes + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ +#ifndef __LAYOUT_TNG_SCANLINE_MAKER_H__ +#define __LAYOUT_TNG_SCANLINE_MAKER_H__ + +#include +#include +#include "libnrtype/Layout-TNG.h" + +class Shape; + +namespace Inkscape { +namespace Text { + +/** \brief private to Layout. Generates lists of chunks within a shape. + +This is the abstract base class for taking a given shape and scanning through +it line-by-line to get the horizontal extents of each chunk for a line of a +given height. There are two specialisations: One for real shapes and one that +turns off wrapping by simulating an infinite shape. In due course there will +be a further specialisation to optimise for the common case where the shape +is a rectangle. +*/ +class Layout::ScanlineMaker +{ +public: + virtual ~ScanlineMaker() {} + + struct ScanRun { + double y; /// that's the top of the scan run, not the baseline + double x_start; // these are not flipped according to the text direction + double x_end; + inline double width() const {return std::abs(x_start - x_end);} + }; + + /** Returns a list of chunks on the current line which can fit text with + the given properties. It is up to the caller to discard any chunks which + are too narrow for its needs. This function may change the y coordinate + between calls if the new height too big to fit in the space remaining in + this shape. Returns an empty vector if there is no space left in the + current shape. */ + virtual std::vector makeScanline(Layout::LineHeight const &line_height) =0; + + /** Indicates that the caller has successfully filled the current line + and hence that the next call to makeScanline() should return lines on + the next lower line. There is no error return, the next call to + makeScanline() will give an error if there is no more space. */ + virtual void completeLine() =0; + + /** Returns the y coordinate of the top of the scanline that will be + returned by the next call to makeScanline(). */ + virtual double yCoordinate() = 0; + + /** Forces an arbitrary change in the stored y coordinate of the object. + The next call to makeScanline() will return runs whose top is at + the new coordinate. */ + virtual void setNewYCoordinate(double new_y) =0; + + /** Tests whether the caller can fit a new line with the given metrics + into exactly the space returned by the previous call to makeScanline(). + This saves the caller from having to discard its wrapping solution and + starting at the beginning of the line again when a larger font is seen. + The metrics given here are considered to be the ones that are being + used now, and hence is the line advance height used by completeLine(). + */ + virtual bool canExtendCurrentScanline(Layout::LineHeight const &line_height) =0; +}; + +/** \brief private to Layout. Generates infinite scanlines for when you don't want wrapping + +This is a 'fake' scanline maker which will always return infinite results, +effectively turning off wrapping. It's a very simple implementation. + +It does have the curious property, however, that the input coordinates are +'real' x and y, but the outputs are rotated according to the +\a block_progression. +*/ +class Layout::InfiniteScanlineMaker : public Layout::ScanlineMaker +{ +public: + InfiniteScanlineMaker(double initial_x, double initial_y, Layout::Direction block_progression); + ~InfiniteScanlineMaker(); + + /** Returns a single infinite run at the current location */ + virtual std::vector makeScanline(Layout::LineHeight const &line_height); + + /** Increments the current y by the current line height */ + virtual void completeLine(); + + virtual double yCoordinate() + {return _y;} + + /** Just changes y */ + virtual void setNewYCoordinate(double new_y); + + /** Always true, but has to save the new height */ + virtual bool canExtendCurrentScanline(Layout::LineHeight const &line_height); + +private: + double _x, _y; + Layout::LineHeight _current_line_height; + bool _negative_block_progression; /// if true, indicates that completeLine() should decrement rather than increment, ie block-progression is either rl or bt +}; + +/** \brief private to Layout. Generates scanlines inside an arbitrary shape + +This is the 'perfect', and hence slowest, implementation of a +Layout::ScanlineMaker, which will return exact bounds for any given +input shape. +*/ +class Layout::ShapeScanlineMaker : public Layout::ScanlineMaker +{ +public: + ShapeScanlineMaker(Shape const *shape, Layout::Direction block_progression); + ~ShapeScanlineMaker(); + + virtual std::vector makeScanline(Layout::LineHeight const &line_height); + + virtual void completeLine(); + + virtual double yCoordinate(); + + virtual void setNewYCoordinate(double new_y); + + /** never true */ + virtual bool canExtendCurrentScanline(Layout::LineHeight const &line_height); +private: + /** To generate scanlines for top-to-bottom text it is easiest if we + simply rotate the given shape by a multiple of 90 degrees. This stores + that. If no rotation was needed we can simply store the pointer we were + given and set shape_needs_freeing appropriately. */ + Shape *_rotated_shape; + + /// see #rotated_shape; + bool _shape_needs_freeing; + + // Shape::BeginRaster() needs floats rather than doubles + float _bounding_box_top, _bounding_box_bottom; + float _y; + float _rasterizer_y; + int _current_rasterization_point; + float _current_line_height; + + bool _negative_block_progression; /// if true, indicates that completeLine() should decrement rather than increment, ie block-progression is either rl or bt +}; + +}//namespace Text +}//namespace Inkscape + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/libnrtype/Layout-TNG-Scanline-Makers.cpp b/src/libnrtype/Layout-TNG-Scanline-Makers.cpp new file mode 100755 index 000000000..73b4dd6fa --- /dev/null +++ b/src/libnrtype/Layout-TNG-Scanline-Makers.cpp @@ -0,0 +1,189 @@ +/* + * Inkscape::Text::Layout::ScanlineMaker - text layout engine shape measurers + * + * Authors: + * Richard Hughes + * + * Copyright (C) 2005 Richard Hughes + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ +#include "Layout-TNG-Scanline-Maker.h" +#include "livarot/Shape.h" +#include "livarot/float-line.h" + +namespace Inkscape { +namespace Text { + +// *********************** infinite version + +Layout::InfiniteScanlineMaker::InfiniteScanlineMaker(double initial_x, double initial_y, Layout::Direction block_progression) +{ + _current_line_height.ascent = 0.0; + _current_line_height.descent = 0.0; + _current_line_height.leading = 0.0; + switch (block_progression) { + case LEFT_TO_RIGHT: + case RIGHT_TO_LEFT: + _x = initial_y; + _y = initial_x; + break; + default: + _x = initial_x; + _y = initial_y; + break; + } + _negative_block_progression = block_progression == RIGHT_TO_LEFT || block_progression == BOTTOM_TO_TOP; + +} + +Layout::InfiniteScanlineMaker::~InfiniteScanlineMaker() +{ +} + +std::vector Layout::InfiniteScanlineMaker::makeScanline(Layout::LineHeight const &line_height) +{ + std::vector runs(1); + runs[0].x_start = _x; + runs[0].x_end = FLT_MAX; // we could use DBL_MAX, but this just seems safer + runs[0].y = _y; + _current_line_height = line_height; + return runs; +} + +void Layout::InfiniteScanlineMaker::completeLine() +{ + if (_negative_block_progression) + _y -= _current_line_height.total(); + else + _y += _current_line_height.total(); + _current_line_height.ascent = 0.0; + _current_line_height.descent = 0.0; + _current_line_height.leading = 0.0; +} + +void Layout::InfiniteScanlineMaker::setNewYCoordinate(double new_y) +{ + _y = new_y; +} + +bool Layout::InfiniteScanlineMaker::canExtendCurrentScanline(Layout::LineHeight const &line_height) +{ + _current_line_height = line_height; + return true; +} + +// *********************** real shapes version + +Layout::ShapeScanlineMaker::ShapeScanlineMaker(Shape const *shape, Layout::Direction block_progression) +{ + if (block_progression == TOP_TO_BOTTOM) { + _rotated_shape = const_cast(shape); + _shape_needs_freeing = false; + } else { + Shape *temp_rotated_shape = new Shape; + _shape_needs_freeing = true; + temp_rotated_shape->Copy(const_cast(shape)); + switch (block_progression) { + case BOTTOM_TO_TOP: temp_rotated_shape->Transform(NR::Matrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0)); break; // reflect about x axis + case LEFT_TO_RIGHT: temp_rotated_shape->Transform(NR::Matrix(0.0, 1.0, 1.0, 0.0, 0.0, 0.0)); break; // reflect about y=x + case RIGHT_TO_LEFT: temp_rotated_shape->Transform(NR::Matrix(0.0, -1.0, 1.0, 0.0, 0.0, 0.0)); break; // reflect about y=-x + default: break; + } + _rotated_shape = new Shape; + _rotated_shape->ConvertToShape(temp_rotated_shape); + delete temp_rotated_shape; + } + _rotated_shape->CalcBBox(true); + _bounding_box_top = _rotated_shape->topY; + _bounding_box_bottom = _rotated_shape->bottomY; + _y = _rasterizer_y = _bounding_box_top; + _current_rasterization_point = 0; + _rotated_shape->BeginRaster(_y, _current_rasterization_point); + _negative_block_progression = block_progression == RIGHT_TO_LEFT || block_progression == BOTTOM_TO_TOP; +} + + +Layout::ShapeScanlineMaker::~ShapeScanlineMaker() +{ + _rotated_shape->EndRaster(); + if (_shape_needs_freeing) + delete _rotated_shape; +} + +std::vector Layout::ShapeScanlineMaker::makeScanline(Layout::LineHeight const &line_height) +{ + FloatLigne line_rasterization; + FloatLigne line_decent_length_runs; + float line_text_height = (float)(line_height.ascent + line_height.descent); + + if (_y > _bounding_box_bottom) + return std::vector(); + + if (_y < _bounding_box_top) + _y = _bounding_box_top; + + if (line_text_height == 0.0) + line_text_height = 0.001; // Scan() doesn't work for zero height so this will have to do + + _current_line_height = (float)line_height.total(); + + // I think what's going on here is that we're moving the top of the scanline to the given position... + _rotated_shape->Scan(_rasterizer_y, _current_rasterization_point, _y, line_text_height); + // ...then actually retreiving the scanline (which alters the first two parameters) + _rotated_shape->Scan(_rasterizer_y, _current_rasterization_point, _y + line_text_height , &line_rasterization, true, line_text_height); + // sanitise the raw rasterisation, which could have weird overlaps + line_rasterization.Flatten(); + // cut out runs that cover less than 90% of the line + line_decent_length_runs.Over(&line_rasterization, 0.9 * line_text_height); + + if (line_decent_length_runs.runs.empty()) + { + if (line_rasterization.runs.empty()) + return std::vector(); // stop the flow + // make up a pointless run: anything that's not an empty vector + std::vector result(1); + result[0].x_start = line_rasterization.runs[0].st; + result[0].x_end = line_rasterization.runs[0].st; + result[0].y = _negative_block_progression ? -_current_line_height - _y : _y; + return result; + } + + // convert the FloatLigne to what we use: vector + std::vector result(line_decent_length_runs.runs.size()); + for (unsigned i = 0 ; i < result.size() ; i++) { + result[i].x_start = line_decent_length_runs.runs[i].st; + result[i].x_end = line_decent_length_runs.runs[i].en; + result[i].y = _negative_block_progression ? -_current_line_height - _y : _y; + } + + return result; +} + +void Layout::ShapeScanlineMaker::completeLine() +{ + _y += _current_line_height; +} + +double Layout::ShapeScanlineMaker::yCoordinate() +{ + if (_negative_block_progression) return -_current_line_height - _y; + return _y; +} + +void Layout::ShapeScanlineMaker::setNewYCoordinate(double new_y) +{ + _y = (float)new_y; + if (_negative_block_progression) _y = -_current_line_height - _y; + // what will happen with the rasteriser if we move off the shape? + // it's not an important question because doesn't have a y attribute +} + +bool Layout::ShapeScanlineMaker::canExtendCurrentScanline(Layout::LineHeight const &line_height) +{ + //we actually could return true if only the leading changed, but that's too much effort for something that rarely happens + return false; +} + +}//namespace Text +}//namespace Inkscape diff --git a/src/libnrtype/Layout-TNG.cpp b/src/libnrtype/Layout-TNG.cpp new file mode 100755 index 000000000..bda0d1697 --- /dev/null +++ b/src/libnrtype/Layout-TNG.cpp @@ -0,0 +1,45 @@ +/* + * Inkscape::Text::Layout - text layout engine misc + * + * Authors: + * Richard Hughes + * + * Copyright (C) 2005 Richard Hughes + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ +#include "Layout-TNG.h" + +namespace Inkscape { +namespace Text { + +const gunichar Layout::UNICODE_SOFT_HYPHEN = 0x00AD; +const double Layout::LINE_HEIGHT_NORMAL = 1.25; + +Layout::Layout() +{ + _path_fitted = NULL; +} + +Layout::~Layout() +{ + clear(); +} + +void Layout::clear() +{ + _clearInputObjects(); + _clearOutputObjects(); +} + +bool Layout::_directions_are_orthogonal(Direction d1, Direction d2) +{ + if (d1 == BOTTOM_TO_TOP) d1 = TOP_TO_BOTTOM; + if (d2 == BOTTOM_TO_TOP) d2 = TOP_TO_BOTTOM; + if (d1 == RIGHT_TO_LEFT) d1 = LEFT_TO_RIGHT; + if (d2 == RIGHT_TO_LEFT) d2 = LEFT_TO_RIGHT; + return d1 != d2; +} + +}//namespace Text +}//namespace Inkscape diff --git a/src/libnrtype/Layout-TNG.h b/src/libnrtype/Layout-TNG.h new file mode 100755 index 000000000..6400ee77b --- /dev/null +++ b/src/libnrtype/Layout-TNG.h @@ -0,0 +1,1029 @@ +/* + * Inkscape::Text::Layout - text layout engine + * + * Authors: + * Richard Hughes + * + * Copyright (C) 2005 Richard Hughes + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ +#ifndef __LAYOUT_TNG_H__ +#define __LAYOUT_TNG_H__ + +#include "libnr/nr-rect.h" +#include "libnr/nr-matrix.h" +#include "libnr/nr-matrix-ops.h" +#include "libnr/nr-rotate-ops.h" +#include +#include +#include + +class SPStyle; +class Shape; +class NRArenaGroup; +class SPPrintContext; +class SVGLength; +class Path; +class SPCurve; +class font_instance; + +namespace Inkscape { +namespace Text { + +/** \brief Generates the layout for either wrapped or non-wrapped text and stores the result + +Use this class for all your text output needs. It takes text with formatting +markup as input and turns that into the glyphs and their necessary positions. +It stores the glyphs internally, but maintains enough information to both +retrieve your own rendering information if you wish and to perform visual +text editing where the output refers back to where it came from. + +Usage: +-# Construct +-# Set the text using appendText() and appendControlCode() +-# If you want text wrapping, call appendWrapShape() a few times +-# Call calculateFlow() +-# You can go several directions from here, but the most interesting + things start with creating a Layout::iterator with begin() or end(). + +Terminology, in descending order of size: +- Flow: Not often used, but when it is it means all the text +- Shape: A Shape object which is used to represent one of the regions inside + which to flow the text. Can overlap with... +- Paragraph: Err...A paragraph. Contains one or more... +- Line: An entire horizontal line with a common baseline. Contains one or + more... +- Chunk: You only get more than one of these when a shape is sufficiently + complex that the text has to flow either side of some obstruction in + the middle. A chunk is the base unit for wrapping. Contains one or more... +- Span: A convenient subset of a chunk with the same font, style, + directionality, block progression and input stream. Fill and outline + need not be constant because that's a later rendering stage. +- This is where it gets weird because a span will contain one or more + elements of both of the following, which can overlap with each other in + any way: + - Character: a single Unicode codepoint from an input stream. Many arabic + characters contain multiple glyphs + - Glyph: a rendering primitive for font engines. A ligature glyph will + represent multiple characters. + +Other terminology: +- Input stream: An object representing a single call to appendText() or + appendControlCode(). +- Control code: Metadata in the text stream to signify items that occupy + real space (unlike style changes) but don't belong in the text string. + Paragraph breaks are in this category. See Layout::TextControlCode. +- SVG1.1: The W3C Recommendation "Scalable Vector Graphics (SVG) 1.1" + http://www.w3.org/TR/SVG11/ +- 'left', 'down', etc: These terms are generally used to mean what they + mean in left-to-right, top-to-bottom text but rotated or reflected for + the current directionality. Thus, the 'width' of a ttb line is actually + its height, and the (internally stored) y coordinate of a glyph is + actually its x coordinate. Confusing to the reader but much simpler in + the code. All public methods use real x and y. + +Comments: +- There's a strong emphasis on international support in this class, but + that's primarily because once you can display all the insane things + required by various languages, simple things like styling text are + almost trivial. +- There are a few places (appendText() is one) where pointers are held to + caller-owned objects and used for quite a long time. This is messy but + is safe for our usage scenario and in many cases the cost of copying the + objects is quite high. +- "Why isn't foo here?": Ask yourself if it's possible to implement foo + externally using iterators. However this may not mean that it doesn't + belong as a member, though. +- I've used floats rather than doubles to store relative distances in some + places (internal only) where it would save significant amounts of memory. + The SVG spec allows you to do this as long as intermediate calculations + are done double. Very very long lines might not finish precisely where + you want, but that's to be expected with any typesetting. Also, + SVGLength only uses floats. +- If you look at the six arrays for holding the output data you'll realise + that there's no O(1) way to drill down from a paragraph to find its + starting glyph. This was a conscious decision to reduce complexity and + to save memory. Drilling down isn't actually that slow because a binary + chop will work nicely. Add this to the realisation that most of the + times you do this will be in response to user actions and hence you only + need to be faster than the user and I think the design makes sense. +- There are a massive number of functions acting on Layout::iterator. A + large number are trivial and will be inline, but is it really necessary + to have all these, especially when some can be implemented by the caller + using the others? +- The separation of methods between Layout and Layout::iterator is a + bit arbitrary, because many methods could go in either. I've used the STL + model where the iterator itself can only move around; the base class is + required to do anything interesting. +- I use Pango internally, not Pangomm. The reason for this is lots of + Pangomm methods take Glib::ustrings as input and then output byte offsets + within the strings. There's simply no way to use byte offsets with + ustrings without some very entertaining reinterpret_cast<>s. The Pangomm + docs seem to be lacking quite a lot of things mentioned in the Pango + docs, too. +*/ +class Layout { +public: + class iterator; + friend class iterator; + class Calculator; + friend class Calculator; + class ScanlineMaker; + class InfiniteScanlineMaker; + class ShapeScanlineMaker; + + Layout(); + virtual ~Layout(); + + /** Used to specify any particular text direction required. Used for + both the 'direction' and 'block-progression' CSS attributes. */ + enum Direction {LEFT_TO_RIGHT, RIGHT_TO_LEFT, TOP_TO_BOTTOM, BOTTOM_TO_TOP}; + + /** Display alignment for shapes. See appendWrapShape(). */ + enum DisplayAlign {DISPLAY_ALIGN_BEFORE, DISPLAY_ALIGN_CENTER, DISPLAY_ALIGN_AFTER}; + + /** The optional attributes which can be applied to a SVG text or + related tag. See appendText(). See SVG1.1 section 10.4 for the + definitions of all these members. See sp_svg_length_list_read() for + the standard way to make these vectors. It is the responsibility of + the caller to deal with the inheritance of these values using its + knowledge of the parse tree. */ + struct OptionalTextTagAttrs { + std::vector x; + std::vector y; + std::vector dx; + std::vector dy; + std::vector rotate; + }; + + /** Control codes which can be embedded in the text to be flowed. See + appendControlCode(). */ + enum TextControlCode { + PARAGRAPH_BREAK, /// forces the flow to move on to the next line + SHAPE_BREAK, /// forces the flow to ignore the remainder of the current shape (from #flow_inside_shapes) and continue at the top of the one after. + ARBITRARY_GAP /// inserts an arbitrarily-sized hole in the flow in line with the current text. + }; + + /** For expressing paragraph alignment. These values are rotated in the + case of vertical text, but are not dependent on whether the paragraph is + rtl or ltr, thus LEFT is always either left or top. */ + enum Alignment {LEFT, CENTER, RIGHT, FULL}; + + /** The CSS spec allows line-height:normal to be whatever the user agent + thinks will look good. This is our value, as a multiple of font-size. */ + static const double LINE_HEIGHT_NORMAL; + + // ************************** describing the stuff to flow ************************* + + /** \name Input + Methods for describing the text you want to flow, its style, and the + shapes to flow in to. + */ + //@{ + + /** Empties everything stored in this class and resets it to its + original state, like when it was created. All iterators on this + object will be invalidated (but can be revalidated using + validateIterator(). */ + void clear(); + + /** Queries whether any calls have been made to appendText() or + appendControlCode() since the object was last cleared. */ + bool inputExists() const + {return !_input_stream.empty();} + + /** adds a new piece of text to the end of the current list of text to + be processed. This method can only add text of a consistent style. + To add lots of different styles, call it lots of times. + \param text The text. \b Note: only a \em pointer is stored. Do not + mess with the text until after you have called + calculateFlow(). + \param style The font style. Layout will hold a reference to this + object for the duration of its ownership, ie until you + call clear() or the class is destroyed. Must not be NULL. + \param source_cookie This pointer is treated as opaque by Layout + but will be passed through the flowing process intact so + that callers can use it to refer to the original object + that generated a particular glyph. See Layout::iterator. + Implementation detail: currently all callers put an + SPString in here. + \param optional_attributes A structure containing additional options + for this text. See OptionalTextTagAttrs. The values are + copied to internal storage before this method returns. + \param optional_attributes_offset It is convenient for callers to be + able to use the same \a optional_attributes structure for + several sequential text fields, in which case the vectors + will need to be offset. This parameter causes the nth + element of all the vectors to be read as if it were the + first. + \param text_begin Used for selecting only a substring of \a text + to process. + \param text_end Used for selecting only a substring of \a text + to process. + */ + void appendText(Glib::ustring const &text, SPStyle *style, void *source_cookie, OptionalTextTagAttrs const *optional_attributes, unsigned optional_attributes_offset, Glib::ustring::const_iterator text_begin, Glib::ustring::const_iterator text_end); + inline void appendText(Glib::ustring const &text, SPStyle *style, void *source_cookie, OptionalTextTagAttrs const *optional_attributes = NULL, unsigned optional_attributes_offset = 0) + {appendText(text, style, source_cookie, optional_attributes, optional_attributes_offset, text.begin(), text.end());} + + /** Control codes are metadata in the text stream to signify items + that occupy real space (unlike style changes) but don't belong in the + text string. See TextControlCode for the types available. + + A control code \em cannot be the first item in the input stream. Use + appendText() with an empty string to set up the paragraph properties. + \param code A member of the TextFlowControlCode enumeration. + \param width The width in pixels that this item occupies. + \param ascent The number of pixels above the text baseline that this + control code occupies. + \param descent The number of pixels below the text baseline that this + control code occupies. + \param source_cookie This pointer is treated as opaque by Layout + but will be passed through the flowing process intact so + that callers can use it to refer to the original object + that generated a particular area. See Layout::iterator. + Implementation detail: currently all callers put an + SPObject in here. + Note that for some control codes (eg tab) the values of the \a width, + \a ascender and \a descender are implied by the surrounding text (and + in the case of tabs, the values set in tab_stops) so the values you pass + here are ignored. + */ + void appendControlCode(TextControlCode code, void *source_cookie, double width = 0.0, double ascent = 0.0, double descent = 0.0); + + /** Stores another shape inside which to flow the text. If this method + is never called then no automatic wrapping is done and lines will + continue to infinity if necessary. Text can be flowed inside multiple + shapes in sequence, like with frames in a DTP package. If the text flows + past the end of the last shape all remaining text is ignored. + + \param shape The Shape to use next in the flow. The storage for this + is managed by the caller, and need only be valid for + the duration of the call to calculateFlow(). + \param display_align The vertical alignment of the text within this + shape. See XSL1.0 section 7.13.4. The behaviour of + settings other than DISPLAY_ALIGN_BEFORE when using + non-rectangular shapes is undefined. + */ + void appendWrapShape(Shape const *shape, DisplayAlign display_align = DISPLAY_ALIGN_BEFORE); + + //@} + + // ************************** doing the actual flowing ************************* + + /** \name Processing + The method to do the actual work of converting text into glyphs. + */ + //@{ + + /** Takes all the stuff you set with the members above here and creates + a load of glyphs for use with the members below here. All iterators on + this object will be invalidated (but can be fixed with validateIterator(). + The implementation just creates a new Layout::Calculator and calls its + Calculator::Calculate() method, so if you want more details on the + internals, go there. + \return false on failure. + */ + bool calculateFlow(); + + //@} + + // ************************** operating on the output glyphs ************************* + + /** \name Output + Methods for reading and interpreting the output glyphs. See also + Layout::iterator. + */ + //@{ + + /** Returns true if there are some glyphs in this object, ie whether + computeFlow() has been called on a non-empty input since the object was + created or the last call to clear(). */ + inline bool outputExists() const + {return !_characters.empty();} + + /** Adds all the output glyphs to \a in_arena using the given \a paintbox. + \param in_arena The arena to add the glyphs group to + \param paintbox The current rendering tile + */ + void show(NRArenaGroup *in_arena, NRRect const *paintbox) const; + + /** Calculates the smallest rectangle completely enclosing all the + glyphs. + \param bounding_box Where to store the box + \param transform The transform to be applied to the entire object + prior to calculating its bounds. + */ + void getBoundingBox(NRRect *bounding_box, NR::Matrix const &transform) const; + + /** Sends all the glyphs to the given print context. + \param ctx I have + \param pbox no idea + \param dbox what these + \param bbox parameters + \param ctm do yet + */ + void print(SPPrintContext *ctx, NRRect const *pbox, NRRect const *dbox, NRRect const *bbox, NRMatrix const &ctm) const; + + /** debug and unit test method. Creates a textual representation of the + contents of this object. The output is designed to be both human-readable + and comprehensible when diffed with a known-good dump. */ + Glib::ustring dumpAsText() const; + + /** Moves all the glyphs in the structure so that the baseline of all + the characters sits neatly along the path specified. If the text has + more than one line the results are undefined. The 'align' means to + use the SVG align method as documented in SVG1.1 section 10.13.2. + NB: njh has suggested that it would be cool if we could flow from + shape to path and back again. This is possible, so this method will be + removed at some point. + A pointer to \a path is retained by the class for use by the cursor + positioning functions. */ + void fitToPathAlign(SVGLength const &startOffset, Path const &path); + + /** Convert the specified range of characters into their bezier + outlines. + */ + SPCurve* convertToCurves(iterator const &from_glyph, iterator const &to_glyph) const; + inline SPCurve* convertToCurves() const; + + /** Apply the given transform to all the output presently stored in + this object. This only transforms the glyph positions, The glyphs + themselves will not be transformed. */ + void transform(NR::Matrix const &transform); + + //@} + + // ********** + + /** \name Output (Iterators) + Methods for operating with the Layout::iterator class. The method + names ending with 'Index' return 0-based offsets of the number of + items since the beginning of the flow. + */ + //@{ + + /** Returns an iterator pointing at the first glyph of the flowed output. + The first glyph is also the first character, line, paragraph, etc. */ + inline iterator begin() const; + + /** Returns an iterator pointing just past the end of the last glyph, + which is also just past the end of the last chunk, span, etc, etc. */ + inline iterator end() const; + + /** Returns an iterator pointing at the given character index. This + index should be related to the result from a prior call to + iteratorToCharIndex(). */ + inline iterator charIndexToIterator(int char_index) const; + + /** Returns the character index from the start of the flow represented + by the given iterator. This number isn't very useful, except for when + editing text it will stay valid across calls to computeFlow() and will + change in predictable ways when characters are added and removed. It's + also useful when transitioning old code. */ + inline int iteratorToCharIndex(iterator const &it) const; + + /** Checks the validity of the given iterator over the current layout. + If it points to a position out of the bounds for this layout it will + be corrected to the nearest valid position. If you pass an iterator + belonging to a different layout it will be converted to one for this + layout. */ + inline void validateIterator(iterator *it) const; + + /** Returns an iterator pointing to the cursor position for a mouse + click at the given coordinates. */ + iterator getNearestCursorPositionTo(double x, double y) const; + inline iterator getNearestCursorPositionTo(NR::Point &point) const; + + /** Returns an iterator pointing to the letter whose bounding box contains + the given coordinates. end() if the point is not over any letter. The + iterator will \em not point at the specific glyph within the character. */ + iterator getLetterAt(double x, double y) const; + inline iterator getLetterAt(NR::Point &point) const; + + /** Returns an iterator pointing to the character in the output which + was created from the given input. If the character at the given byte + offset was removed (soft hyphens, for example) the next character after + it is returned. If no input was added with the given cookie, end() is + returned. If more than one input has the same cookie, the first will + be used regardless of the value of \a text_iterator. If + \a text_iterator is out of bounds, the first or last character belonging + to the given input will be returned accordingly. */ + iterator sourceToIterator(void *source_cookie, Glib::ustring::const_iterator text_iterator) const; + + /** Returns an iterator pointing to the first character in the output + which was created from the given source. If \a source_cookie is invalid, + end() is returned. If more than one input has the same cookie, the + first one will be used. */ + iterator sourceToIterator(void *source_cookie) const; + + // many functions acting on iterators, most of which are obvious + // also most of them don't check that \a it != end(). Be careful. + + /** Returns the bounding box of the given glyph, and its rotation. + The centre of rotation is the horizontal centre of the box at the + text baseline. */ + NR::Rect glyphBoundingBox(iterator const &it, double *rotation) const; + + /** Returns the zero-based line number of the character pointed to by + \a it. */ + inline unsigned lineIndex(iterator const &it) const; + + /** Returns the zero-based number of the shape which contains the + character pointed to by \a it. */ + inline unsigned shapeIndex(iterator const &it) const; + + /** Returns true if the character at \a it is a whitespace, as defined + by Pango. This is not meant to be used for picking out words from the + output, use iterator::nextStartOfWord() and friends instead. */ + inline bool isWhitespace(iterator const &it) const; + + /** Returns the unicode character code of the character pointed to by + \a it. If \a it == end() the result is undefined. */ + inline int characterAt(iterator const &it) const; + + /** Discovers where the character pointed to by \a it came from, by + retrieving the cookie that was passed to the call to appendText() or + appendControlCode() which generated that output. If \a it == end() + then NULL is returned as the cookie. If the character was generated + from a call to appendText() then the optional \a text_iterator + parameter is set to point to the actual character, otherwise + \a text_iterator is unaltered. */ + void getSourceOfCharacter(iterator const &it, void **source_cookie, Glib::ustring::iterator *text_iterator = NULL) const; + + /** For latin text, the left side of the character, on the baseline */ + NR::Point characterAnchorPoint(iterator const &it) const; + + /** This is that value to apply to the x,y attributes of tspan role=line + elements, and hence it takes alignment into account. */ + NR::Point chunkAnchorPoint(iterator const &it) const; + + /** Returns the box extents (not ink extents) of the given character. + The centre of rotation is at the horizontal centre of the box on the + text baseline. */ + NR::Rect characterBoundingBox(iterator const &it, double *rotation = NULL) const; + + /** Basically uses characterBoundingBox() on all the characters from + \a start to \a end and returns the union of these boxes. The return value + is a list of zero or more quadrilaterals specified by a group of four + points for each, thus size() is always a multiple of four. */ + std::vector createSelectionShape(iterator const &it_start, iterator const &it_end, NR::Matrix const &transform) const; + + /** Returns true if \a it points to a character which is a valid cursor + position, as defined by Pango. */ + inline bool isCursorPosition(iterator const &it) const; + + /** Gets the ideal cursor shape for a given iterator. The result is + undefined if \a it is not at a valid cursor position. + \param it The location in the output + \param position The pixel location of the centre of the 'bottom' of + the cursor. + \param height The height in pixels of the surrounding text + \param rotation The angle to draw from \a position. Radians, zero up, + increasing clockwise. + */ + void queryCursorShape(iterator const &it, NR::Point *position, double *height, double *rotation) const; + + /** Returns true if \a it points to a character which is a the start of + a word, as defined by Pango. */ + inline bool isStartOfWord(iterator const &it) const; + + /** Returns true if \a it points to a character which is a the end of + a word, as defined by Pango. */ + inline bool isEndOfWord(iterator const &it) const; + + /** Returns true if \a it points to a character which is a the start of + a sentence, as defined by Pango. */ + inline bool isStartOfSentence(iterator const &it) const; + + /** Returns true if \a it points to a character which is a the end of + a sentence, as defined by Pango. */ + inline bool isEndOfSentence(iterator const &it) const; + + /** Returns the zero-based number of the paragraph containing the + character pointed to by \a it. */ + inline unsigned paragraphIndex(iterator const &it) const; + + /** Returns the actual alignment used for the paragraph containing + the character pointed to by \a it. This means that the CSS 'start' + and 'end' are correctly translated into LEFT or RIGHT according to + the paragraph's directionality. For vertical text, LEFT is top + alignment and RIGHT is bottom. */ + inline Alignment paragraphAlignment(iterator const &it) const; + + /** Returns kerning information which could cause the current output + to be exactly reproduced if the letter and word spacings were zero and + full justification was not used. The x and y arrays are not used, but + they are cleared. The dx applied to the first character in a chunk + will always be zero. If the region between \a from and \a to crosses + a line break then the results may be surprising, and are undefined. + Trailing zeros on the returned arrays will be trimmed. */ + void simulateLayoutUsingKerning(iterator const &from, iterator const &to, OptionalTextTagAttrs *result) const; + + //@} + + /// it's useful for this to be public so that ScanlineMaker can use it + struct LineHeight { + double ascent; + double descent; + double leading; + inline double total() const {return ascent + descent + leading;} + inline void setZero() {ascent = descent = leading = 0.0;} + inline LineHeight& operator*=(double x) {ascent *= x; descent *= x; leading *= x; return *this;} + void max(LineHeight const &other); /// makes this object contain the largest of all three members between this object and other + }; + + /// see _enum_converter() + struct EnumConversionItem { + int input, output; + }; + +private: + /** Erases all the stuff set by the owner as input, ie #_input_stream + and #_input_wrap_shapes. */ + void _clearInputObjects(); + + /** Erases all the stuff output by computeFlow(). Glyphs and things. */ + void _clearOutputObjects(); + + static const gunichar UNICODE_SOFT_HYPHEN; + + // ******************* input flow + + enum InputStreamItemType {TEXT_SOURCE, CONTROL_CODE}; + + class InputStreamItem { + public: + virtual ~InputStreamItem() {} + virtual InputStreamItemType Type() =0; + void *source_cookie; + }; + + /** Represents a text item in the input stream. See #_input_stream. + Most of the members are copies of the values passed to appendText(). */ + class InputStreamTextSource : public InputStreamItem { + public: + virtual InputStreamItemType Type() {return TEXT_SOURCE;} + virtual ~InputStreamTextSource(); + Glib::ustring const *text; /// owned by the caller + Glib::ustring::const_iterator text_begin, text_end; + int text_length; /// in characters, from text_start to text_end only + SPStyle *style; + /** These vectors can (often will) be shorter than the text + in this source, but never longer. */ + std::vector x; + std::vector y; + std::vector dx; + std::vector dy; + std::vector rotate; + + // a few functions for some of the more complicated style accesses + float styleComputeFontSize() const; + font_instance *styleGetFontInstance() const; + Direction styleGetBlockProgression() const; + Alignment styleGetAlignment(Direction para_direction, bool try_text_align) const; + }; + + /** Represents a control code item in the input stream. See + #_input_streams. All the members are copies of the values passed to + appendControlCode(). */ + class InputStreamControlCode : public InputStreamItem { + public: + virtual InputStreamItemType Type() {return CONTROL_CODE;} + TextControlCode code; + double ascent; + double descent; + double width; + }; + + /** This is our internal storage for all the stuff passed to the + appendText() and appendControlCode() functions. */ + std::vector _input_stream; + + /** The parameters to appendText() are allowed to be a little bit + complex. This copies them to be the right length and starting at zero. + We also don't want to write five bits of identical code just with + different variable names. */ + static void _copyInputVector(std::vector const &input_vector, unsigned input_offset, std::vector *output_vector, size_t max_length); + + /** There are a few cases where we have different sets of enums meaning + the same thing, eg Pango font styles vs. SPStyle font styles. These need + converting. */ + static int _enum_converter(int input, EnumConversionItem const *conversion_table, unsigned conversion_table_size); + + /** The overall block-progression of the whole flow. */ + inline Direction _blockProgression() const + {return static_cast(_input_stream.front())->styleGetBlockProgression();} + + /** so that LEFT_TO_RIGHT == RIGHT_TO_LEFT but != TOP_TO_BOTTOM */ + static bool _directions_are_orthogonal(Direction d1, Direction d2); + + /** If the output is empty callers still want to be able to call + queryCursorShape() and get a valid answer so, while #_input_wrap_shapes + can still be considered valid, we need to precompute the cursor shape + for this case. */ + void _calculateCursorShapeForEmpty(); + + struct CursorShape { + NR::Point position; + double height; + double rotation; + } _empty_cursor_shape; + + // ******************* input shapes + + struct InputWrapShape { + Shape const *shape; /// as passed to Layout::appendWrapShape() + DisplayAlign display_align; /// as passed to Layout::appendWrapShape() + }; + std::vector _input_wrap_shapes; + + // ******************* output + + /** as passed to fitToPathAlign() */ + Path const *_path_fitted; + + struct Glyph; + struct Character; + struct Span; + struct Chunk; + struct Line; + struct Paragraph; + + struct Glyph { + int glyph; + unsigned in_character; + float x; /// relative to the start of the chunk + float y; /// relative to the current line's baseline + float rotation; /// absolute, modulo any object transforms, which we don't know about + float width; + inline Span const & span(Layout const *l) const {return l->_spans[l->_characters[in_character].in_span];} + inline Chunk const & chunk(Layout const *l) const {return l->_chunks[l->_spans[l->_characters[in_character].in_span].in_chunk];} + inline Line const & line(Layout const *l) const {return l->_lines[l->_chunks[l->_spans[l->_characters[in_character].in_span].in_chunk].in_line];} + }; + struct Character { + unsigned in_span; + float x; /// relative to the start of the *span* (so we can do block-progression) + PangoLogAttr char_attributes; + int in_glyph; /// will be -1 if this character has no visual representation + inline Span const & span(Layout const *l) const {return l->_spans[in_span];} + inline Chunk const & chunk(Layout const *l) const {return l->_chunks[l->_spans[in_span].in_chunk];} + inline Line const & line(Layout const *l) const {return l->_lines[l->_chunks[l->_spans[in_span].in_chunk].in_line];} + inline Paragraph const & paragraph(Layout const *l) const {return l->_paragraphs[l->_lines[l->_chunks[l->_spans[in_span].in_chunk].in_line].in_paragraph];} + // to get the advance width of a character, subtract the x values if it's in the middle of a span, or use span.x_end if it's at the end + }; + struct Span { + unsigned in_chunk; + font_instance *font; + float font_size; + float x_start; /// relative to the start of the chunk + float x_end; /// relative to the start of the chunk + LineHeight line_height; + double baseline_shift; /// relative to the line's baseline + Direction direction; /// See CSS3 section 3.2. Either rtl or ltr + Direction block_progression; /// See CSS3 section 3.2. The direction in which lines go. + unsigned in_input_stream_item; + Glib::ustring::const_iterator input_stream_first_character; + inline Chunk const & chunk(Layout const *l) const {return l->_chunks[in_chunk];} + inline Line const & line(Layout const *l) const {return l->_lines[l->_chunks[in_chunk].in_line];} + inline Paragraph const & paragraph(Layout const *l) const {return l->_paragraphs[l->_lines[l->_chunks[in_chunk].in_line].in_paragraph];} + }; + struct Chunk { + unsigned in_line; + double left_x; + }; + struct Line { + unsigned in_paragraph; + double baseline_y; + unsigned in_shape; + }; + struct Paragraph { + Direction base_direction; /// can be overridden by child Span objects + Alignment alignment; + }; + std::vector _paragraphs; + std::vector _lines; + std::vector _chunks; + std::vector _spans; + std::vector _characters; + std::vector _glyphs; + + /** gets the overall matrix that transforms the given glyph from local + space to world space. */ + void _getGlyphTransformMatrix(int glyph_index, NRMatrix *matrix) const; + + // loads of functions to drill down the object tree, all of them + // annoyingly similar and all of them requiring predicate functors. + // I'll be buggered if I can find a way to make it work with + // functions or with a templated functor, so macros it is. +#define EMIT_PREDICATE(name, object_type, index_generator) \ + class name { \ + Layout const * const _flow; \ + public: \ + inline name(Layout const *flow) : _flow(flow) {} \ + inline bool operator()(object_type const &object, unsigned index) \ + {return index_generator < index;} \ + } +// end of macro + EMIT_PREDICATE(PredicateLineToSpan, Span, _flow->_chunks[object.in_chunk].in_line); + EMIT_PREDICATE(PredicateLineToCharacter, Character, _flow->_chunks[_flow->_spans[object.in_span].in_chunk].in_line); + EMIT_PREDICATE(PredicateSpanToCharacter, Character, object.in_span); + EMIT_PREDICATE(PredicateSourceToCharacter, Character, _flow->_spans[object.in_span].in_input_stream_item); + + inline unsigned _lineToSpan(unsigned line_index) const + {return std::lower_bound(_spans.begin(), _spans.end(), line_index, PredicateLineToSpan(this)) - _spans.begin();} + inline unsigned _lineToCharacter(unsigned line_index) const + {return std::lower_bound(_characters.begin(), _characters.end(), line_index, PredicateLineToCharacter(this)) - _characters.begin();} + inline unsigned _spanToCharacter(unsigned span_index) const + {return std::lower_bound(_characters.begin(), _characters.end(), span_index, PredicateSpanToCharacter(this)) - _characters.begin();} + inline unsigned _sourceToCharacter(unsigned source_index) const + {return std::lower_bound(_characters.begin(), _characters.end(), source_index, PredicateSourceToCharacter(this)) - _characters.begin();} + + /** given an x coordinate and a line number, returns an iterator + pointing to the closest cursor position on that line to the + coordinate. */ + iterator _cursorXOnLineToIterator(unsigned line_index, double local_x) const; + + /** calculates the width of a chunk, which is the largest x + coordinate (start or end) of the spans contained within it. */ + double _getChunkWidth(unsigned chunk_index) const; +}; + +/** \brief Holds a position within the glyph output of Layout. + +Used to access the output of a Layout, query information and generally +move around in it. See Layout for a glossary of the names of functions. + +I'm not going to document all the methods because most of their names make +their function self-evident. + +A lot of the functions would do the same thing in a naive implementation +for latin-only text, for example nextCharacter(), nextCursorPosition() and +cursorRight(). Generally it's fairly obvious which one you should use in a +given situation, but sometimes you might need to put some thought in to it. + +All the methods return false if the requested action would have caused the +current position to move out of bounds. In this case the position is moved +to either begin() or end(), depending on which direction you were going. + +Note that some characters do not have a glyph representation (eg line +breaks), so if you try using prev/nextGlyph() from one of these you're +heading for a crash. +*/ +class Layout::iterator { +public: + friend class Layout; + // this is just so you can create uninitialised iterators - don't actually try to use one + iterator() : _parent_layout(NULL) {} + // no copy constructor required, the default does what we want + bool operator== (iterator const &other) const + {return _glyph_index == other._glyph_index && _char_index == other._char_index;} + bool operator!= (iterator const &other) const + {return _glyph_index != other._glyph_index || _char_index != other._char_index;} + + /* mustn't compare _glyph_index in these operators because for characters + that don't have glyphs (line breaks, elided soft hyphens, etc), the glyph + index is -1 which makes them not well-ordered. To be honest, interating by + glyphs is not very useful and should be avoided. */ + bool operator< (iterator const &other) const + {return _char_index < other._char_index;} + bool operator<= (iterator const &other) const + {return _char_index <= other._char_index;} + bool operator> (iterator const &other) const + {return _char_index > other._char_index;} + bool operator>= (iterator const &other) const + {return _char_index >= other._char_index;} + + /* **** visual-oriented methods **** */ + + //glyphs + inline bool prevGlyph(); + inline bool nextGlyph(); + + //span + bool prevStartOfSpan(); + bool thisStartOfSpan(); + bool nextStartOfSpan(); + + //chunk + bool prevStartOfChunk(); + bool thisStartOfChunk(); + bool nextStartOfChunk(); + + //line + bool prevStartOfLine(); + bool thisStartOfLine(); + bool nextStartOfLine(); + bool thisEndOfLine(); + + //shape + bool prevStartOfShape(); + bool thisStartOfShape(); + bool nextStartOfShape(); + + /* **** text-oriented methods **** */ + + //characters + inline bool nextCharacter(); + inline bool prevCharacter(); + + bool nextCursorPosition(); + bool prevCursorPosition(); + bool nextLineCursor(); + bool prevLineCursor(); + + //words + bool nextStartOfWord(); + bool prevStartOfWord(); + bool nextEndOfWord(); + bool prevEndOfWord(); + + //sentences + bool nextStartOfSentence(); + bool prevStartOfSentence(); + bool nextEndOfSentence(); + bool prevEndOfSentence(); + + //paragraphs + bool prevStartOfParagraph(); + bool thisStartOfParagraph(); + bool nextStartOfParagraph(); + //no endOfPara methods because that's just the previous char + + //sources + bool prevStartOfSource(); + bool thisStartOfSource(); + bool nextStartOfSource(); + + //logical cursor movement + bool cursorUp(); + bool cursorDown(); + bool cursorLeft(); + bool cursorRight(); + + //logical cursor movement (by word or paragraph) + bool cursorUpWithControl(); + bool cursorDownWithControl(); + bool cursorLeftWithControl(); + bool cursorRightWithControl(); + +private: + Layout const *_parent_layout; + int _glyph_index; /// index into Layout::glyphs, or -1 + unsigned _char_index; /// index into Layout::character + bool _cursor_moving_vertically; + /** for cursor up/down movement we must maintain the x position where + we started so the cursor doesn't 'drift' left or right with the repeated + quantization to character boundaries. */ + double _x_coordinate; + + inline iterator(Layout const *p, unsigned c, int g) + : _parent_layout(p), _glyph_index(g), _char_index(c), _cursor_moving_vertically(false), _x_coordinate(0.0) {} + inline iterator(Layout const *p, unsigned c) + : _parent_layout(p), _glyph_index(p->_characters[c].in_glyph), _char_index(c), _cursor_moving_vertically(false), _x_coordinate(0.0) {} + // no dtor required + void beginCursorUpDown(); /// stores the current x coordinate so that the cursor won't drift. See #_x_coordinate + + /** moves forward or backwards one cursor position according to the + directionality of the current paragraph, but ignoring block progression. + Helper for the cursor*() functions. */ + bool _cursorLeftOrRightLocalX(Direction direction); + + /** moves forward or backwards by until the next character with + is_word_start according to the directionality of the current paragraph, + but ignoring block progression. Helper for the cursor*WithControl() + functions. */ + bool _cursorLeftOrRightLocalXByWord(Direction direction); +}; + +// ************************** inline methods + +inline SPCurve* Layout::convertToCurves() const + {return convertToCurves(begin(), end());} + +inline Layout::iterator Layout::begin() const + {return iterator(this, 0, 0);} + +inline Layout::iterator Layout::end() const + {return iterator(this, _characters.size(), _glyphs.size());} + +inline Layout::iterator Layout::charIndexToIterator(int char_index) const +{ + if (char_index < 0) return begin(); + if (char_index >= (int)_characters.size()) return end(); + return iterator(this, char_index); +} + +inline int Layout::iteratorToCharIndex(Layout::iterator const &it) const + {return it._char_index;} + +inline void Layout::validateIterator(Layout::iterator *it) const +{ + it->_parent_layout = this; + if (it->_char_index >= _characters.size()) { + it->_char_index = _characters.size(); + it->_glyph_index = _glyphs.size(); + } else + it->_glyph_index = _characters[it->_char_index].in_glyph; +} + +inline Layout::iterator Layout::getNearestCursorPositionTo(NR::Point &point) const + {return getNearestCursorPositionTo(point[0], point[1]);} + +inline Layout::iterator Layout::getLetterAt(NR::Point &point) const + {return getLetterAt(point[0], point[1]);} + +inline unsigned Layout::lineIndex(iterator const &it) const + {return it._char_index == _characters.size() ? _lines.size() - 1 : _characters[it._char_index].chunk(this).in_line;} + +inline unsigned Layout::shapeIndex(iterator const &it) const + {return it._char_index == _characters.size() ? _input_wrap_shapes.size() - 1 : _characters[it._char_index].line(this).in_shape;} + +inline bool Layout::isWhitespace(iterator const &it) const + {return it._char_index == _characters.size() || _characters[it._char_index].char_attributes.is_white;} + +inline int Layout::characterAt(iterator const &it) const +{ + void *unused; + Glib::ustring::iterator text_iter; + getSourceOfCharacter(it, &unused, &text_iter); + return *text_iter; +} + +inline bool Layout::isCursorPosition(iterator const &it) const + {return it._char_index == _characters.size() || _characters[it._char_index].char_attributes.is_cursor_position;} + +inline bool Layout::isStartOfWord(iterator const &it) const + {return it._char_index != _characters.size() && _characters[it._char_index].char_attributes.is_word_start;} + +inline bool Layout::isEndOfWord(iterator const &it) const + {return it._char_index == _characters.size() || _characters[it._char_index].char_attributes.is_word_end;} + +inline bool Layout::isStartOfSentence(iterator const &it) const + {return it._char_index != _characters.size() && _characters[it._char_index].char_attributes.is_sentence_start;} + +inline bool Layout::isEndOfSentence(iterator const &it) const + {return it._char_index == _characters.size() || _characters[it._char_index].char_attributes.is_sentence_end;} + +inline unsigned Layout::paragraphIndex(iterator const &it) const + {return it._char_index == _characters.size() ? _paragraphs.size() - 1 : _characters[it._char_index].line(this).in_paragraph;} + +inline Layout::Alignment Layout::paragraphAlignment(iterator const &it) const + {return _paragraphs[paragraphIndex(it)].alignment;} + +inline bool Layout::iterator::nextGlyph() +{ + _cursor_moving_vertically = false; + if (_glyph_index >= (int)_parent_layout->_glyphs.size() - 1) { + if (_glyph_index == (int)_parent_layout->_glyphs.size()) return false; + _char_index = _parent_layout->_characters.size(); + _glyph_index = _parent_layout->_glyphs.size(); + } + else _char_index = _parent_layout->_glyphs[++_glyph_index].in_character; + return true; +} + +inline bool Layout::iterator::prevGlyph() +{ + _cursor_moving_vertically = false; + if (_glyph_index == 0) return false; + _char_index = _parent_layout->_glyphs[--_glyph_index].in_character; + return true; +} + +inline bool Layout::iterator::nextCharacter() +{ + _cursor_moving_vertically = false; + if (_char_index + 1 >= _parent_layout->_characters.size()) { + if (_char_index == _parent_layout->_characters.size()) return false; + _char_index = _parent_layout->_characters.size(); + _glyph_index = _parent_layout->_glyphs.size(); + } + else _glyph_index = _parent_layout->_characters[++_char_index].in_glyph; + return true; +} + +inline bool Layout::iterator::prevCharacter() +{ + _cursor_moving_vertically = false; + if (_char_index == 0) return false; + _glyph_index = _parent_layout->_characters[--_char_index].in_glyph; + return true; +} + +}//namespace Text +}//namespace Inkscape + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/libnrtype/Makefile_insert b/src/libnrtype/Makefile_insert new file mode 100644 index 000000000..218cbad6b --- /dev/null +++ b/src/libnrtype/Makefile_insert @@ -0,0 +1,42 @@ +## Makefile.am fragment sourced by src/Makefile.am. + +libnrtype/all: libnrtype/libnrtype.a + +libnrtype/clean: + rm -f libnrtype/libnrtype.a $(libnrtype_libnrtype_a_OBJECTS) + +libnrtype_libnrtype_a_SOURCES = \ + libnrtype/boundary-type.h \ + libnrtype/font-style-to-pos.cpp \ + libnrtype/font-style-to-pos.h \ + libnrtype/font-glyph.h \ + libnrtype/font-instance.h \ + libnrtype/font-style.h \ + libnrtype/nr-type-pos-def.cpp \ + libnrtype/nr-type-pos-def.h \ + libnrtype/nr-type-primitives.cpp \ + libnrtype/nr-type-primitives.h \ + libnrtype/nrtype-forward.h \ + libnrtype/FontFactory.cpp \ + libnrtype/FontFactory.h \ + libnrtype/FontInstance.cpp \ + libnrtype/one-box.h \ + libnrtype/one-glyph.h \ + libnrtype/one-para.h \ + libnrtype/RasterFont.cpp \ + libnrtype/RasterFont.h \ + libnrtype/raster-glyph.h \ + libnrtype/raster-position.h \ + libnrtype/text-boundary.h \ + libnrtype/TextWrapper.cpp \ + libnrtype/TextWrapper.h \ + libnrtype/Layout-TNG-Compute.cpp \ + libnrtype/Layout-TNG-Input.cpp \ + libnrtype/Layout-TNG-OutIter.cpp \ + libnrtype/Layout-TNG-Output.cpp \ + libnrtype/Layout-TNG-Scanline-Maker.h \ + libnrtype/Layout-TNG-Scanline-Makers.cpp \ + libnrtype/Layout-TNG.cpp \ + libnrtype/Layout-TNG.h + + diff --git a/src/libnrtype/RasterFont.cpp b/src/libnrtype/RasterFont.cpp new file mode 100644 index 000000000..68ecb2e4d --- /dev/null +++ b/src/libnrtype/RasterFont.cpp @@ -0,0 +1,431 @@ +/* + * RasterFont.cpp + * testICU + * + */ + +#ifdef HAVE_CONFIG_H +# include +#endif +#include "RasterFont.h" + +#include +#include +#include +#include +#include +#include +#include +#include + + +static void glyph_run_A8_OR (raster_info &dest,void */*data*/,int st,float vst,int en,float ven); + +void font_style::Apply(Path* src,Shape* dest) { + src->Convert(1); + if ( stroke_width > 0 ) { + if ( nbDash > 0 ) { + double dlen = 0.0; + const float scale = 1/*NR_MATRIX_DF_EXPANSION (&transform)*/; + for (int i = 0; i < nbDash; i++) dlen += dashes[i] * scale; + if (dlen >= 0.01) { + float sc_offset = dash_offset * scale; + float *tdashs=(float*)malloc((nbDash+1)*sizeof(float)); + while ( sc_offset >= dlen ) sc_offset-=dlen; + tdashs[0]=dashes[0] * scale; + for (int i=1;iDashPolyline(0.0,0.0,dlen,nbDash,tdashs,true,sc_offset); + free(tdashs); + } + } + src->Stroke(dest, false, 0.5*stroke_width, stroke_join, stroke_cap, 0.5*stroke_width*stroke_miter_limit); + } else { + src->Fill(dest,0); + } +} + +raster_font::raster_font(font_style const &fstyle) : + daddy(NULL), + refCount(0), + style(fstyle), + glyph_id_to_raster_glyph_no(), + nbBase(0), + maxBase(0), + bases(NULL) +{ + // printf("raster font born\n"); +} + +raster_font::~raster_font(void) +{ +// printf("raster font death\n"); + if ( daddy ) daddy->RemoveRasterFont(this); + daddy=NULL; + if ( style.dashes ) free(style.dashes); + style.dashes=NULL; + for (int i=0;iRemoveRasterFont(this); + daddy=NULL; + delete this; + } +} +void raster_font::Ref(void) +{ + refCount++; +// printf("raster %x ref'd %i\n",this,refCount); +} +raster_glyph* raster_font::GetGlyph(int glyph_id) +{ + raster_glyph *res=NULL; + if ( glyph_id_to_raster_glyph_no.find(glyph_id) == glyph_id_to_raster_glyph_no.end() ) { + LoadRasterGlyph(glyph_id); + if ( glyph_id_to_raster_glyph_no.find(glyph_id) == glyph_id_to_raster_glyph_no.end() ) { // recheck + } else { + res=bases[glyph_id_to_raster_glyph_no[glyph_id]]; + } + } else { + res=bases[glyph_id_to_raster_glyph_no[glyph_id]]; + } + return res; +} +NR::Point raster_font::Advance(int glyph_id) +{ + if ( daddy == NULL ) return NR::Point(0,0); + double a=daddy->Advance(glyph_id,style.vertical); + NR::Point f_a=(style.vertical)?NR::Point(0,a):NR::Point(a,0); + return f_a*style.transform; +} +void raster_font::BBox(int glyph_id,NRRect *area) +{ + area->x0=area->y0=area->x1=area->y1=0; + if ( daddy == NULL ) return; + NR::Rect res=daddy->BBox(glyph_id); + NR::Point bmi=res.min(),bma=res.max(); + NR::Point tlp(bmi[0],bmi[1]),trp(bma[0],bmi[1]),blp(bmi[0],bma[1]),brp(bma[0],bma[1]); + tlp=tlp*style.transform; + trp=trp*style.transform; + blp=blp*style.transform; + brp=brp*style.transform; + res=NR::Rect(tlp,trp); + res.expandTo(blp); + res.expandTo(brp); + area->x0=(res.min())[0]; + area->y0=(res.min())[1]; + area->x1=(res.max())[0]; + area->y1=(res.max())[1]; +} + +void raster_font::LoadRasterGlyph(int glyph_id) +{ + raster_glyph *res=NULL; + if ( glyph_id_to_raster_glyph_no.find(glyph_id) == glyph_id_to_raster_glyph_no.end() ) { + res=new raster_glyph(); + res->daddy=this; + res->glyph_id=glyph_id; + if ( nbBase >= maxBase ) { + maxBase=2*nbBase+1; + bases=(raster_glyph**)realloc(bases,maxBase*sizeof(raster_glyph*)); + } + bases[nbBase]=res; + glyph_id_to_raster_glyph_no[glyph_id]=nbBase; + nbBase++; + } else { + res=bases[glyph_id_to_raster_glyph_no[glyph_id]]; + } + if ( res == NULL ) return; + if ( res->polygon ) return; + if ( res->outline == NULL ) { + if ( daddy == NULL ) return; + Path* outline=daddy->Outline(glyph_id,NULL); + res->outline=new Path; + if ( outline ) { + res->outline->Copy(outline); + } + res->outline->Transform(style.transform); + } + Shape* temp=new Shape; + res->polygon=new Shape; + style.Apply(res->outline,temp); + if ( style.stroke_width > 0 ) { + res->polygon->ConvertToShape(temp,fill_nonZero); + } else { + res->polygon->ConvertToShape(temp,fill_oddEven); + } + delete temp; + + res->SetSubPixelPositionning(4); +} +void raster_font::RemoveRasterGlyph(raster_glyph* who) +{ + if ( who == NULL ) return; + int glyph_id=who->glyph_id; + if ( glyph_id_to_raster_glyph_no.find(glyph_id) == glyph_id_to_raster_glyph_no.end() ) { + int no=glyph_id_to_raster_glyph_no[glyph_id]; + if ( no >= nbBase-1 ) { + } else { + bases[no]=bases[--nbBase]; + glyph_id_to_raster_glyph_no[bases[no]->glyph_id]=no; + } + glyph_id_to_raster_glyph_no.erase(glyph_id_to_raster_glyph_no.find(glyph_id)); + } else { + // not here + } +} + +/*int top,bottom; // baseline is y=0 + int* run_on_line; // array of size (bottom-top+1): run_on_line[i] gives the number of runs on line top+i + int nbRun; + float_ligne_run* runs;*/ + +raster_position::raster_position(void) +{ + top=0; + bottom=-1; + run_on_line=NULL; + nbRun=0; + runs=NULL; +} +raster_position::~raster_position(void) +{ + if ( run_on_line ) free(run_on_line); + if ( runs ) free(runs); +} + +void raster_position::AppendRuns(std::vector const &r,int y) +{ + if ( top > bottom ) { + top=bottom=y; + if ( run_on_line ) free(run_on_line); + run_on_line=(int*)malloc(sizeof(int)); + run_on_line[0]=0; + } else { + if ( y < top ) { + // printf("wtf?\n"); + return; + } else if ( y > bottom ) { + int ob=bottom; + bottom=y; + run_on_line=(int*)realloc(run_on_line,(bottom-top+1)*sizeof(int)); + for (int i=ob+1;i<=bottom;i++) run_on_line[i-top]=0; + } + } + + if ( r.empty() == false) { + run_on_line[y - top] = r.size(); + runs = (float_ligne_run *) realloc(runs, (nbRun + r.size()) * sizeof(float_ligne_run)); + + for (int i = 0; i < int(r.size()); i++) { + runs[nbRun + i] = r[i]; + } + + nbRun += r.size(); + } +} +void raster_position::Blit(float ph,int pv,NRPixBlock &over) +{ + int base_y=top+pv; + int first_y=top+pv,last_y=bottom+pv; + if ( first_y < over.area.y0 ) first_y=over.area.y0; + if ( last_y >= over.area.y1 ) last_y=over.area.y1-1; + if ( first_y > last_y ) return; + IntLigne *theIL=new IntLigne(); + FloatLigne *theI=new FloatLigne(); + + char* mdata=(char*)over.data.px; + if ( over.size == NR_PIXBLOCK_SIZE_TINY ) mdata=(char*)over.data.p; + + for (int y=first_y;y<=last_y;y++) { + int first_r=0,last_r=0; + for (int i=base_y;iReset(); + for (int i=first_r;i<=last_r;i++) theI->AddRun(runs[i].st+ph,runs[i].en+ph,runs[i].vst,runs[i].ven,runs[i].pente); +// for (int i=first_r;i<=last_r;i++) {runs[i].st+=ph;runs[i].en+=ph;} +// theI->nbRun=theI->maxRun=last_r-first_r+1; +// theI->runs=runs+first_r; + + theIL->Copy(theI); + raster_info dest; + dest.startPix=over.area.x0; + dest.endPix=over.area.x1; + dest.sth=over.area.x0; + dest.stv=y; + dest.buffer=((uint32_t*)(mdata+(over.rs*(y-over.area.y0)))); + theIL->Raster(dest,NULL,glyph_run_A8_OR); + +// theI->nbRun=theI->maxRun=0; +// theI->runs=NULL; +// for (int i=first_r;i<=last_r;i++) {runs[i].st-=ph;runs[i].en-=ph;} + } + } + delete theIL; + delete theI; +} + + +/* raster_font* daddy; + int glyph_id; + + Path* outline; + Shape* polygon; + + int nb_sub_pixel; + raster_position* sub_pixel;*/ + +raster_glyph::raster_glyph(void) +{ + daddy=NULL; + glyph_id=0; + outline=NULL; + polygon=NULL; + nb_sub_pixel=0; + sub_pixel=NULL; +} +raster_glyph::~raster_glyph(void) +{ + if ( outline ) delete outline; + if ( polygon ) delete polygon; + if ( sub_pixel ) delete [] sub_pixel; +} + + +void raster_glyph::SetSubPixelPositionning(int nb_pos) +{ + nb_sub_pixel=nb_pos; + if ( nb_sub_pixel <= 0 ) nb_sub_pixel=0; + if ( sub_pixel ) delete [] sub_pixel; + sub_pixel=NULL; + if ( nb_sub_pixel > 0 ) { + sub_pixel=new raster_position[nb_pos]; + if ( polygon ) { + for (int i=0;i= nb_sub_pixel ) return; + if ( sub_pixel[no].top <= sub_pixel[no].bottom ) return; + if ( polygon == NULL ) { + if ( daddy == NULL ) return; + daddy->LoadRasterGlyph(glyph_id); + if ( polygon == NULL ) return; + } + + float sub_delta=((float)no)/((float)nb_sub_pixel); + + polygon->CalcBBox(); + + float l=polygon->leftX,r=polygon->rightX,t=polygon->topY,b=polygon->bottomY; + int il,ir,it,ib; + il=(int)floor(l); + ir=(int)ceil(r); + it=(int)floor(t); + ib=(int)ceil(b); + + // version par FloatLigne + int curPt; + float curY; + polygon->BeginQuickRaster(curY, curPt); + + FloatLigne* theI=new FloatLigne(); + + polygon->DirectQuickScan(curY,curPt,(float)(it-1)+sub_delta,true,1.0); + + for (int y=it-1;yReset(); + polygon->QuickScan(curY,curPt,((float)(y+1))+sub_delta,theI,1.0); + theI->Flatten(); + + sub_pixel[no].AppendRuns(theI->runs, y); + } + polygon->EndQuickRaster(); + delete theI; +} + +void raster_glyph::Blit(const NR::Point &at,NRPixBlock &over) +{ + if ( nb_sub_pixel <= 0 ) return; + int pv=(int)ceil(at[1]); + double dec=4*(ceil(at[1])-at[1]); + int no=(int)floor(dec); + sub_pixel[no].Blit(at[0],pv,over); +} + + + +static void +glyph_run_A8_OR (raster_info &dest,void */*data*/,int st,float vst,int en,float ven) +{ + if ( st >= en ) return; + if ( vst < 0 ) vst=0; + if ( vst > 1 ) vst=1; + if ( ven < 0 ) ven=0; + if ( ven > 1 ) ven=1; + float sv=vst; + float dv=ven-vst; + int len=en-st; + unsigned char* d=(unsigned char*)dest.buffer; + d+=(st-dest.startPix); + if ( fabs(dv) < 0.001 ) { + if ( vst > 0.999 ) { + /* Simple copy */ + while (len > 0) { + d[0] = 255; + d += 1; + len -= 1; + } + } else { + sv*=256; + unsigned int c0_24=(int)sv; + c0_24&=0xFF; + while (len > 0) { + unsigned int da; + /* Draw */ + da = 65025 - (255 - c0_24) * (255 - d[0]); + d[0] = (da + 127) / 255; + d += 1; + len -= 1; + } + } + } else { + if ( en <= st+1 ) { + sv=0.5*(vst+ven); + sv*=256; + unsigned int c0_24=(int)sv; + c0_24&=0xFF; + unsigned int da; + /* Draw */ + da = 65025 - (255 - c0_24) * (255 - d[0]); + d[0] = (da + 127) / 255; + } else { + dv/=len; + sv+=0.5*dv; // correction trapezoidale + sv*=16777216; + dv*=16777216; + int c0_24 = static_cast(CLAMP(sv, 0, 16777216)); + int s0_24 = static_cast(dv); + while (len > 0) { + unsigned int ca, da; + /* Draw */ + ca = c0_24 >> 16; + if ( ca > 255 ) ca=255; + da = 65025 - (255 - ca) * (255 - d[0]); + d[0] = (da + 127) / 255; + d += 1; + c0_24 += s0_24; + c0_24 = CLAMP (c0_24, 0, 16777216); + len -= 1; + } + } + } +} diff --git a/src/libnrtype/RasterFont.h b/src/libnrtype/RasterFont.h new file mode 100644 index 000000000..03665c69a --- /dev/null +++ b/src/libnrtype/RasterFont.h @@ -0,0 +1,66 @@ +/* + * RasterFont.h + * testICU + * + */ + +#ifndef my_raster_font +#define my_raster_font + +#include + +#include +#include +#include + +// one rasterfont is one way to draw a font on the screen +// the way it's drawn is stored in style +class raster_font { +public: + font_instance* daddy; + int refCount; + + font_style style; + + __gnu_cxx::hash_map glyph_id_to_raster_glyph_no; + // an array of glyphs in this rasterfont. + // it's a bit redundant with the one in the daddy font_instance, but these glyphs + // contains the real rasterization data + int nbBase,maxBase; + raster_glyph** bases; + + explicit raster_font(font_style const &fstyle); + ~raster_font(void); + + void Unref(void); + void Ref(void); + + // utility functions + NR::Point Advance(int glyph_id); + void BBox(int glyph_id,NRRect *area); + + // attempts to load a glyph and return a raster_glyph on which you can call Blit + raster_glyph* GetGlyph(int glyph_id); + // utility + void LoadRasterGlyph(int glyph_id); // refreshes outline/polygon if needed + void RemoveRasterGlyph(raster_glyph* who); + +private: + /* Disable the default copy constructor and operator=: they do the wrong thing for refCount. */ + raster_font(raster_font const &); + raster_font &operator=(raster_font const &); +}; + +#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 : diff --git a/src/libnrtype/TextWrapper.cpp b/src/libnrtype/TextWrapper.cpp new file mode 100644 index 000000000..ce8797322 --- /dev/null +++ b/src/libnrtype/TextWrapper.cpp @@ -0,0 +1,937 @@ +/* + * TextWrapper.cpp + * testICU + * + */ + +#ifdef HAVE_CONFIG_H +# include +#endif +#include "TextWrapper.h" + +#include +#include "libnrtype/text-boundary.h" +#include "libnrtype/one-glyph.h" +#include "libnrtype/one-box.h" +#include "libnrtype/one-para.h" + +#include + +text_wrapper::text_wrapper(void) +{ + // voids everything + utf8_text = NULL; + uni32_text = NULL; + glyph_text = NULL; + utf8_length = 0; + uni32_length = 0; + glyph_length = 0; + utf8_codepoint = NULL; + uni32_codepoint = NULL; + default_font = NULL; + bounds = NULL; + nbBound = maxBound = 0; + boxes = NULL; + nbBox = maxBox = 0; + paras = NULL; + nbPara = maxPara = 0; + kern_x = kern_y = NULL; + last_addition = -1; + // inits the pangolayout with default params + font_factory *font_src = font_factory::Default(); + pLayout = pango_layout_new(font_src->fontContext); + pango_layout_set_single_paragraph_mode(pLayout, true); + pango_layout_set_width(pLayout, -1); +} +text_wrapper::~text_wrapper(void) +{ + // frees everything + //printf("delete\n"); + g_object_unref(pLayout); + if ( utf8_text ) free(utf8_text); + if ( uni32_text ) free(uni32_text); + if ( glyph_text ) free(glyph_text); + if ( utf8_codepoint ) free(utf8_codepoint); + if ( uni32_codepoint ) free(uni32_codepoint); + if ( default_font ) default_font->Unref(); + if ( boxes ) free(boxes); + if ( paras ) free(paras); + if ( kern_x ) free(kern_x); + if ( kern_y ) free(kern_y); + for (unsigned i = 0; i < nbBound; i++) { + switch ( bounds[i].type ) { + default: + break; + } + } + if ( bounds ) free(bounds); + default_font = NULL; + +} + +void text_wrapper::SetDefaultFont(font_instance *iFont) +{ + // refcounts the font for our internal uses + if ( iFont ) iFont->Ref(); + if ( default_font ) default_font->Unref(); + default_font = iFont; +} + +void text_wrapper::AppendUTF8(char const *text, int len) +{ + // appends text to what needs to be handled + if ( utf8_length <= 0 ) { + // a first check to prevent the text from containing a leading line return (which + // is probably a bug anyway) + if ( text[0] == '\n' || text[0] == '\r' ) { + /* fixme: Should the below be `0 <= len' ? The existing code looks wrong + * for the case that len==0. + * TODO: Document the meaning of the len parameter. */ + if ( len > 0 ) { + while ( len > 0 && ( *text == '\n' || *text == '\r' ) ) {text++; len--;} + } else { + while ( *text == '\n' || *text == '\r' ) text++; + } + } + } + if ( len == 0 || text == NULL || *text == 0 ) return; + g_return_if_fail(g_utf8_validate(text, len, NULL)); + + // compute the length + int const nlen = ( len < 0 + ? strlen(text) + : len ); + /* effic: Use g_utf8_validate's last param to do this. */ + + // prepare to store the additional text + /* effic: (Not an issue for the sole caller at the time of writing.) This implementation + takes quadratic time if the text is composed of n appends. Use a proper data structure. + STL vector would suffice. */ + utf8_text = (char*)realloc(utf8_text, (utf8_length + nlen + 1) * sizeof(char)); + uni32_codepoint = (int*)realloc(uni32_codepoint, (utf8_length + nlen + 1) * sizeof(int)); + + // copy the source text in the newly lengthened array + memcpy(utf8_text + utf8_length, text, nlen * sizeof(char)); + utf8_length += nlen; + utf8_text[utf8_length] = 0; + // remember where the text ended, before we recompute it, for the dx/dy we'll add after that (if any) + last_addition = uni32_length; + // free old uni32 structures (instead of incrementally putting the text) + if ( uni32_text ) free(uni32_text); + if ( utf8_codepoint ) free(utf8_codepoint); + uni32_text = NULL; + utf8_codepoint = NULL; + uni32_length = 0; + { + // recompute length of uni32 text + char *p = utf8_text; + while ( *p ) { + p = g_utf8_next_char(p); // since we validated the input text, we can use this 'fast' macro + uni32_length++; + } + } + // realloc the arrays + uni32_text = (gunichar*)malloc((uni32_length + 1) * sizeof(gunichar)); + utf8_codepoint = (int*)malloc((uni32_length + 1) * sizeof(int)); + { + // read the utf8 string and compute codepoints positions + char *p = utf8_text; + int i = 0; + int l_o = 0; + while ( *p ) { + // get the new codepoint + uni32_text[i] = g_utf8_get_char(p); + // compute the offset in the utf8_string + unsigned int n_o = (unsigned int)(p - utf8_text); + // record the codepoint's start + utf8_codepoint[i] = n_o; + // record the codepoint's correspondance in the utf8 string + for (unsigned int j = l_o; j < n_o; j++) uni32_codepoint[j] = i - 1; + // and move on + l_o = n_o; + p = g_utf8_next_char(p); + i++; + } + // the termination of the loop + for (int j = l_o; j < utf8_length; j++) uni32_codepoint[j] = uni32_length - 1; + uni32_codepoint[utf8_length] = uni32_length; + uni32_text[uni32_length] = 0; + utf8_codepoint[uni32_length] = utf8_length; + } + // if needed, fill the dx/dy arrays with 0 for the newly created part + // these will be filled by a KernXForLastAddition() right after this function + // note that the SVG spec doesn't require you to give a dx for each codepoint, + // so setting the dx to 0 is mandatory + if ( uni32_length > last_addition ) { + if ( kern_x ) { + kern_x = (double*)realloc(kern_x, (uni32_length + 1) * sizeof(double)); + for (int i = last_addition; i <= uni32_length; i++) kern_x[i] = 0; + } + if ( kern_y ) { + kern_y = (double*)realloc(kern_y, (uni32_length + 1) * sizeof(double)); + for (int i = last_addition; i <= uni32_length; i++) kern_y[i] = 0; + } + } +} + +void text_wrapper::DoLayout(void) +{ + // THE function + // first some sanity checks + if ( default_font == NULL ) return; + if ( uni32_length <= 0 || utf8_length <= 0 ) return; + // prepare the pangolayout object + { + //char *tc = pango_font_description_to_string(default_font->descr); + //printf("layout with %s\n", tc); + //free(tc); + } + pango_layout_set_font_description(pLayout, default_font->descr); + pango_layout_set_text(pLayout, utf8_text, utf8_length); + // reset the glyph string + if ( glyph_text ) free(glyph_text); + glyph_text = NULL; + glyph_length = 0; + + double pango_to_ink = (1.0 / ((double)PANGO_SCALE)); // utility + int max_g = 0; + PangoLayoutIter *pIter = pango_layout_get_iter(pLayout); // and go! + do { + PangoLayoutLine *pLine = pango_layout_iter_get_line(pIter); // no need for unref + int plOffset = pLine->start_index; // start of the line in the uni32_text + PangoRectangle ink_r, log_r; + pango_layout_iter_get_line_extents(pIter, &ink_r, &log_r); + double plY = (1.0 / ((double)PANGO_SCALE)) * ((double)log_r.y); // start position of this line of the layout + double plX = (1.0 / ((double)PANGO_SCALE)) * ((double)log_r.x); + GSList *curR = pLine->runs; // get ready to iterate over the runs of this line + while ( curR ) { + PangoLayoutRun *pRun = (PangoLayoutRun*)curR->data; + int prOffset = pRun->item->offset; // start of the run in the line + if ( pRun ) { + // a run has uniform font/directionality/etc... + int o_g_l = glyph_length; // save the index of the first glyph we'll add + for (int i = 0; i < pRun->glyphs->num_glyphs; i++) { // add glyph sequentially, reading them from the run + // realloc the structures + if ( glyph_length >= max_g ) { + max_g = 2 * glyph_length + 1; + glyph_text = (one_glyph*)realloc(glyph_text, (max_g + 1) * sizeof(one_glyph)); + } + // fill the glyph info + glyph_text[glyph_length].font = pRun->item->analysis.font; + glyph_text[glyph_length].gl = pRun->glyphs->glyphs[i].glyph; + glyph_text[glyph_length].uni_st = plOffset + prOffset + pRun->glyphs->log_clusters[i]; + // depending on the directionality, the last uni32 codepoint for this glyph is the first of the next char + // or the first of the previous + if ( pRun->item->analysis.level == 1 ) { + // rtl + if ( i < pRun->glyphs->num_glyphs - 1 ) { + glyph_text[glyph_length + 1].uni_en = glyph_text[glyph_length].uni_st; + } + glyph_text[glyph_length].uni_dir = 1; + glyph_text[glyph_length + 1].uni_dir = 1; // set the directionality for the next too, so that the last glyph in + // the array has the correct direction + } else { + // ltr + if ( i > 0 ) { + glyph_text[glyph_length - 1].uni_en = glyph_text[glyph_length].uni_st; + } + glyph_text[glyph_length].uni_dir = 0; + glyph_text[glyph_length + 1].uni_dir = 0; + } + // set the position + // the layout is an infinite line + glyph_text[glyph_length].x = plX + pango_to_ink * ((double)pRun->glyphs->glyphs[i].geometry.x_offset); + glyph_text[glyph_length].y = plY + pango_to_ink * ((double)pRun->glyphs->glyphs[i].geometry.y_offset); + // advance to the next glyph + plX += pango_to_ink * ((double)pRun->glyphs->glyphs[i].geometry.width); + // and set the next glyph's position, in case it's the terminating glyph + glyph_text[glyph_length + 1].x = plX; + glyph_text[glyph_length + 1].y = plY; + glyph_length++; + } + // and finish filling the info + // notably, the uni_en of the last char in ltr text and the uni_en of the first in rtl are still not set + if ( pRun->item->analysis.level == 1 ) { + // rtl + if ( glyph_length > o_g_l ) glyph_text[o_g_l].uni_en = plOffset + prOffset + pRun->item->length; + } else { + if ( glyph_length > 0 ) glyph_text[glyph_length - 1].uni_en = plOffset + prOffset + pRun->item->length; + } + // the terminating glyph has glyph_id=0 because it means 'no glyph' + glyph_text[glyph_length].gl = 0; + // and is associated with no text (but you cannot set uni_st=uni_en=0, because the termination + // is expected to be the glyph for the termination of the uni32_text) + glyph_text[glyph_length].uni_st = glyph_text[glyph_length].uni_en = plOffset + prOffset + pRun->item->length; + } + curR = curR->next; + } + } while ( pango_layout_iter_next_line(pIter) ); + pango_layout_iter_free(pIter); + + // grunt work done. now some additional info for layout: computing letters, mostly (one letter = several glyphs sometimes) + PangoLogAttr *pAttrs = NULL; + int nbAttr = 0; + // get the layout attrs, they hold the boundaries pango computed + pango_layout_get_log_attrs(pLayout, &pAttrs, &nbAttr); + // feed to MakeTextBoundaries which knows what to do with these + MakeTextBoundaries(pAttrs, nbAttr); + // the array of boundaries is full, but out-of-order + SortBoundaries(); + // boundary array is ready to be used, call chunktext to fill the *_start fields of the glyphs, and compute + // the boxed version of the text for sp-typeset + ChunkText(); + // get rid of the attributes + if ( pAttrs ) g_free(pAttrs); + + // cleaning up + for (int i = 0; i < glyph_length; i++) { + glyph_text[i].uni_st = uni32_codepoint[glyph_text[i].uni_st]; + glyph_text[i].uni_en = uni32_codepoint[glyph_text[i].uni_en]; + glyph_text[i].x /= 512; // why is this not default_font->daddy->fontsize? + glyph_text[i].y /= 512; + } + if ( glyph_length > 0 ) { + glyph_text[glyph_length].x /= 512; + glyph_text[glyph_length].y /= 512; + } +} + +void text_wrapper::ChunkText(void) +{ + int c_st = -1, c_en = -1; + for (int i = 0; i < glyph_length; i++) { + int g_st = glyph_text[i].uni_st, g_en = glyph_text[i].uni_en; + glyph_text[i].char_start = false; + glyph_text[i].word_start = false; + glyph_text[i].para_start = false; + // boundaries depend on the directionality + // letter boundaries correspond to the glyphs starting one letter when you read them left to right (always) + // because that's the order they are stored into in the glyph_text array + if ( glyph_text[i].uni_dir == 0 ) { + if ( IsBound(bnd_char, g_st, c_st) ) { // check if there is a charcater (=letter in pango speak) at this position + // can be a 'start' boundary or a 'end' boundary, doesn't matter, as long + // as you get from one letter to the next at this position + if ( g_st == bounds[c_st].uni_pos ) glyph_text[i].char_start = true; + } + if ( IsBound(bnd_word, g_st, c_st) ) { + if ( g_st == bounds[c_st].uni_pos ) glyph_text[i].word_start = true; + } + if ( IsBound(bnd_para, g_st, c_st) ) { + if ( g_st == bounds[c_st].uni_pos ) glyph_text[i].para_start = true; + } + } else { + if ( IsBound(bnd_char, g_en, c_en) ) { + if ( g_en == bounds[c_en].uni_pos ) glyph_text[i].char_start = true; + } + if ( IsBound(bnd_word, g_en, c_en) ) { + if ( g_en == bounds[c_en].uni_pos ) glyph_text[i].word_start = true; + } + if ( IsBound(bnd_para, g_en, c_en) ) { + if ( g_en == bounds[c_en].uni_pos ) glyph_text[i].para_start = true; + } + } + } + + if ( glyph_length > 0 ) { + glyph_text[glyph_length].char_start = true; + glyph_text[glyph_length].word_start = true; + glyph_text[glyph_length].para_start = true; + } + { + // doing little boxes + int g_st = -1, g_en = -1; + while ( NextWord(g_st, g_en) ) { + // check uniformity of fonts + if ( g_st < g_en ) { + int n_st = g_st; + int n_en = g_st; + bool first = true; + do { + n_st = n_en; + PangoFont *curPF = glyph_text[n_st].font; + do { + n_en++; + } while ( n_en < g_en && glyph_text[n_en].font == curPF ); + if ( nbBox >= maxBox ) { + maxBox = 2 * nbBox + 1; + boxes = (one_box*)realloc(boxes, maxBox * sizeof(one_box)); + } + boxes[nbBox].g_st = n_st; + boxes[nbBox].g_en = n_en; + boxes[nbBox].word_start = first; + boxes[nbBox].word_end = (n_en >= g_en); + nbBox++; + first = false; + } while ( n_en < g_en ); + } + } + } + { + // doing little paras + int g_st = -1, g_en = -1; + while ( NextPara(g_st, g_en) ) { + int b_st = 0; + while ( b_st < nbBox && boxes[b_st].g_st < g_st ) b_st++; + if ( b_st < nbBox && boxes[b_st].g_st == g_st ) { + int b_en = b_st; + while ( b_en < nbBox && boxes[b_en].g_en < g_en ) b_en++; + if ( b_en < nbBox && boxes[b_en].g_en == g_en ) { + if ( nbPara >= maxPara ) { + maxPara = 2 * nbPara + 1; + paras = (one_para*)realloc(paras, maxPara * sizeof(one_para)); + } + paras[nbPara].b_st = b_st; + paras[nbPara].b_en = b_en; + nbPara++; + } + } + } + } +} + +void text_wrapper::MakeVertical(void) +{ + if ( glyph_length <= 0 ) return; + font_factory *font_src = font_factory::Default(); + + // explanation: when laying out text vertically, you must keep the glyphs of a single letter together + double baseY = glyph_text[0].y; + double lastY = baseY; + int g_st = 0, g_en = 0; + int nbLetter = 0; + PangoFont *curPF = NULL; + font_instance *curF = NULL; + do { + // move to the next letter boundary + g_st = g_en; + do { + g_en++; + } while ( g_en < glyph_length && glyph_text[g_en].char_start == false ); + // got a letter + if ( g_st < g_en && g_en <= glyph_length ) { + // we need to compute the letter's width (in case sometimes we implement the flushleft and flushright) + // and the total height for this letter. for example accents usually have 0 width, so this is not + // stupid + double n_adv = 0; + double minX = glyph_text[g_st].x, maxX = glyph_text[g_st].x; + for (int i = g_st; i < g_en; i++) { + if ( glyph_text[i].font != curPF ) { // font is not the same as the one of the previous glyph + // so we need to update curF + if ( curF ) curF->Unref(); + curF = NULL; + curPF = glyph_text[i].font; + if ( curPF ) { + PangoFontDescription *pfd = pango_font_describe(curPF); + curF = font_src->Face(pfd); + pango_font_description_free(pfd); + } + } + double x = ( curF + ? curF->Advance(glyph_text[i].gl, true) + : 0 ); + if ( x > n_adv ) n_adv = x; + if ( glyph_text[i].x < minX ) minX = glyph_text[i].x; + if ( glyph_text[i].x > maxX ) maxX = glyph_text[i].x; + } + lastY += n_adv; + // and put the glyphs of this letter at their new position + for (int i = g_st; i < g_en; i++) { + glyph_text[i].x -= minX; + glyph_text[i].y += lastY; + } + g_st = g_en; + } + nbLetter++; + } while ( g_st < glyph_length ); + if ( curF ) curF->Unref(); +} + +void text_wrapper::MergeWhiteSpace(void) +{ + if ( glyph_length <= 0 ) return; + // scans the glyphs and shifts them accordingly + double delta_x = 0, delta_y = 0; + bool inWhite = true; + int wpos = 0, rpos = 0; // wpos is the position where we read glyphs, rpos is the position where we write them back + // since we only eat whitespace, wpos <= rpos + for (rpos = 0; rpos < glyph_length; rpos++) { + // copy the glyph at its new position + glyph_text[wpos].gl = glyph_text[rpos].gl; + glyph_text[wpos].uni_st = glyph_text[rpos].uni_st; + glyph_text[wpos].uni_en = glyph_text[rpos].uni_en; + glyph_text[wpos].font = glyph_text[rpos].font; + glyph_text[wpos].x = glyph_text[rpos].x - delta_x; + glyph_text[wpos].y = glyph_text[rpos].y - delta_y; + wpos++; // move the write position + if ( g_unichar_isspace(uni32_text[glyph_text[rpos].uni_st]) ) { + if ( inWhite ) { + // eat me: 2 steps: first add the shift in position to the cumulated shift + delta_x += glyph_text[rpos + 1].x - glyph_text[rpos].x; + delta_y += glyph_text[rpos + 1].y - glyph_text[rpos].y; + // then move the write position back. this way, we'll overwrite the previous whitespace with the new glyph + // since this is only done after the first whitespace, we only keep the first whitespace + wpos--; + } + inWhite = true; + } else { + inWhite = false; + } + } + // and the terminating glyph (we should probably copy the rest of the glyph's info, too) + glyph_text[wpos].x = glyph_text[rpos].x - delta_x; + glyph_text[wpos].y = glyph_text[rpos].y - delta_y; + // sets the new length + glyph_length = wpos; +} + +// utility: computes the number of letters in the layout +int text_wrapper::NbLetter(int g_st, int g_en) +{ + if ( glyph_length <= 0 ) return 0; + if ( g_st < 0 || g_st >= g_en ) { + g_st = 0; + g_en = glyph_length; + } + int nbLetter = 0; + for (int i = g_st; i < g_en; i++) { + if ( glyph_text[i].char_start ) nbLetter++; + } + return nbLetter; +} + +void text_wrapper::AddLetterSpacing(double dx, double dy, int g_st, int g_en) +{ + if ( glyph_length <= 0 ) return; + if ( g_st < 0 || g_st >= g_en ) { + g_st = 0; + g_en = glyph_length; + } + int nbLetter = 0; + + // letterspacing means: add 'dx * (nbLetter - 1)' to the x position + // so we just scan the glyph string + for (int i = g_st; i < g_en; i++) { + if ( i > g_st && glyph_text[i].char_start ) nbLetter++; + glyph_text[i].x += dx * nbLetter; + glyph_text[i].y += dy * nbLetter; + } + if ( glyph_text[g_en].char_start ) nbLetter++; + glyph_text[g_en].x += dx * nbLetter; + glyph_text[g_en].y += dy * nbLetter; +} + +/** @name Movement commands + * Miscellaneous functions for moving about glyphs. + * \a st and \en are start and end glyph indices. + * The three methods differ only in whether they look for .char_start, .word_start or .para_start. + * \return True iff a next character was found. (False iff we've already reached the end.) + */ +//@{ +bool text_wrapper::NextChar(int &st, int &en) const +{ + if ( st < 0 || en < 0 ) {st = 0; en = 0;} + if ( st >= en ) en = st; + if ( st >= glyph_length || en >= glyph_length ) return false; // finished + st = en; + do { + en++; + } while ( en < glyph_length && glyph_text[en].char_start == false ); + return true; +} +bool text_wrapper::NextWord(int &st, int &en) const +{ + if ( st < 0 || en < 0 ) {st = 0; en = 0;} + if ( st >= en ) en = st; + if ( st >= glyph_length || en >= glyph_length ) return false; // finished + st = en; + do { + en++; + } while ( en < glyph_length && glyph_text[en].word_start == false ); + return true; +} +bool text_wrapper::NextPara(int &st, int &en) const +{ + if ( st < 0 || en < 0 ) {st = 0; en = 0;} + if ( st >= en ) en = st; + if ( st >= glyph_length || en >= glyph_length ) return false; // finished + st = en; + do { + en++; + } while ( en < glyph_length && glyph_text[en].para_start == false ); + return true; +} +//@} + +// boundary handling +/** + * Append \a ib to our bounds array. + * \return The index of the new element. + */ +unsigned text_wrapper::AddBoundary(text_boundary const &ib) +{ + if ( nbBound >= maxBound ) { + maxBound = 2 * nbBound + 1; + bounds = (text_boundary*)realloc(bounds, maxBound * sizeof(text_boundary)); + } + unsigned const ix = nbBound++; + bounds[ix] = ib; + return ix; +} + +/** + * Add the start \& end boundaries \a is \& \a ie to bounds. + */ +void text_wrapper::AddTwinBoundaries(text_boundary const &is, text_boundary const &ie) +{ + unsigned const ns = AddBoundary(is); + unsigned const ne = AddBoundary(ie); + bounds[ns].start = true; + bounds[ns].other = ne; + bounds[ne].start = false; + bounds[ne].other = ns; +} + +static int CmpBound(void const *a, void const *b) { + text_boundary const &ta = *reinterpret_cast(a); + text_boundary const &tb = *reinterpret_cast(b); + if ( ta.uni_pos < tb.uni_pos ) return -1; + if ( ta.uni_pos > tb.uni_pos ) return 1; + /* TODO: I'd guess that for a given uni_pos it would be better for the end boundary to precede the start boundary. */ + if ( ta.start && !tb.start ) return -1; + if ( !ta.start && tb.start ) return 1; + return 0; +} +/** + * Sort this.bounds by b.uni_pos, updating the .other index values appropriately. + */ +void text_wrapper::SortBoundaries(void) +{ + /* effic: If this function (including descendents such as the qsort calll) ever takes + * non-negligible time, then we can fairly easily improve it by changing MakeBoundaries add in + * sorted order. It would just have to remember for itself the index of each start boundary + * for updating the .other fields appropriately. + * + * A simpler speedup is just to change qsort to std::sort, which can inline the comparison + * function. + */ + + /* The 'other' field needs to be updated after sorting by qsort, so we build the inverse + * permutation. */ + for (unsigned i = 0; i < nbBound; i++) { + bounds[i].old_ix = i; + } + qsort(bounds, nbBound, sizeof(text_boundary), CmpBound); + unsigned *const old2new = g_new(unsigned, nbBound); + for (unsigned new_ix = 0; new_ix < nbBound; new_ix++) { // compute inverse permutation + old2new[bounds[new_ix].old_ix] = new_ix; + } + for (unsigned i = 0; i < nbBound; i++) { // update 'other' + if ( bounds[i].other < nbBound ) { + bounds[i].other = old2new[bounds[i].other]; + } + } + g_free(old2new); +} +void text_wrapper::MakeTextBoundaries(PangoLogAttr *pAttrs, int nAttr) +{ + if ( pAttrs == NULL || nAttr <= 0 || uni32_length <= 0 ) return; + if ( nAttr > uni32_length + 1 ) nAttr = uni32_length + 1; + int last_c_st = -1; + int last_w_st = -1; + int last_s_st = -1; + int last_p_st = 0; + // reads the text and adds a pair of boundaries each time we encounter a stop + // last_* are used to keep track of the start of new text chunk + for (int i = 0; i <= nAttr; i++) { + text_boundary nbs; + text_boundary nbe; + nbs.uni_pos = i; + nbs.start = true; + nbe.uni_pos = i; + nbe.start = false; + // letters + if ( i == nAttr || pAttrs[i].is_cursor_position ) { + if ( last_c_st >= 0 ) { + nbs.type = nbe.type = bnd_char; + nbs.uni_pos = last_c_st; + nbe.uni_pos = i; + AddTwinBoundaries(nbs, nbe); + } + last_c_st = i; + } + // words + if ( i == nAttr || pAttrs[i].is_word_start ) { + if ( last_w_st >= 0 ) { + nbs.type = nbe.type = bnd_word; + nbs.uni_pos = last_w_st; + nbe.uni_pos = i; + nbs.data.i = nbe.data.i = ( pAttrs[last_w_st].is_white ? 1 : 0 ); + AddTwinBoundaries(nbs, nbe); + } + last_w_st = i; + } + if ( i < nAttr && pAttrs[i].is_word_end ) { + if ( last_w_st >= 0 ) { + nbs.type = nbe.type = bnd_word; + nbs.uni_pos = last_w_st; + nbe.uni_pos = i; + nbs.data.i = nbe.data.i = ( pAttrs[last_w_st].is_white ? 1 : 0 ); + AddTwinBoundaries(nbs, nbe); + } + last_w_st = i; + } + // sentences + if ( i == nAttr || pAttrs[i].is_sentence_boundary ) { + if ( last_s_st >= 0 ) { + nbs.type = nbe.type = bnd_sent; + nbs.uni_pos = last_s_st; + nbe.uni_pos = i; + AddTwinBoundaries(nbs, nbe); + } + last_s_st = i; + } + // paragraphs + if ( uni32_text[i] == '\n' || uni32_text[i] == '\r' || i == nAttr ) { // too simple to be true? + nbs.type = nbe.type = bnd_para; + nbs.uni_pos = last_p_st; + nbe.uni_pos = i + 1; + AddTwinBoundaries(nbs, nbe); + last_p_st = i + 1; + } + } +} + +bool text_wrapper::IsBound(BoundaryType const bnd_type, int g_st, int &c_st) +{ + if ( c_st < 0 ) c_st = 0; + int scan_dir = 0; + while ( unsigned(c_st) < nbBound ) { + if ( bounds[c_st].uni_pos == g_st && bounds[c_st].type == bnd_type ) { + return true; + } + if ( bounds[c_st].uni_pos < g_st ) { + if ( scan_dir < 0 ) break; + c_st++; + scan_dir = 1; + } else if ( bounds[c_st].uni_pos > g_st ) { + if ( scan_dir > 0 ) break; + c_st--; + scan_dir = -1; + } else { + // good pos, wrong type + while ( c_st > 0 && bounds[c_st].uni_pos == g_st ) { + c_st--; + } + if ( bounds[c_st].uni_pos < g_st ) c_st++; + while ( unsigned(c_st) < nbBound && bounds[c_st].uni_pos == g_st ) { + if ( bounds[c_st].type == bnd_type ) { + return true; + } + c_st++; + } + break; + } + } + return false; +} + +/* Unused. Retained only because I haven't asked cyreve (Richard Hughes) whether he intends ever + * to use it. You can probably safely remove it. */ +//bool text_wrapper::Contains(BoundaryType const bnd_type, int g_st, int g_en, int &c_st, int &c_en) +//{ +// if ( c_st < 0 ) c_st = 0; +// bool found = false; +// int scan_dir = 0; +// while ( unsigned(c_st) < nbBound ) { +// if ( bounds[c_st].type == bnd_type ) { +// if ( bounds[c_st].start ) { +// c_en = bounds[c_st].other; +// } else { +// } +// } +// if ( bounds[c_st].type == bnd_type && unsigned(c_en) == bounds[c_st].other ) { +// if ( g_st >= bounds[c_st].uni_pos && g_en <= bounds[c_en].uni_pos ) { +// // character found +// found = true; +// break; +// } +// } +// if ( bounds[c_st].uni_pos < g_st ) { +// if ( scan_dir < 0 ) break; +// c_st++; +// scan_dir = 1; +// } else if ( bounds[c_st].uni_pos > g_st ) { +// if ( scan_dir > 0 ) break; +// c_st--; +// scan_dir = -1; +// } else { +// // good pos, wrong type +// while ( c_st > 0 && bounds[c_st].uni_pos == g_st ) { +// c_st--; +// } +// if ( bounds[c_st].uni_pos < g_st ) c_st++; +// while ( unsigned(c_st) < nbBound && bounds[c_st].uni_pos == g_st ) { +// if ( bounds[c_st].type == bnd_type ) { +// if ( bounds[c_st].start ) { +// c_en = bounds[c_st].other; +// } else { +// } +// } +// if ( bounds[c_st].type == bnd_type && unsigned(c_en) == bounds[c_st].other ) { +// if ( g_st >= bounds[c_st].uni_pos && g_en <= bounds[c_en].uni_pos ) { +// // character found +// return true; +// } +// } +// c_st++; +// } +// +// break; +// } +// } +// return found; +//} + +void text_wrapper::MeasureBoxes(void) +{ + font_factory *f_src = font_factory::Default(); + for (int i = 0; i < nbBox; i++) { + boxes[i].ascent = 0; + boxes[i].descent = 0; + boxes[i].leading = 0; + boxes[i].width = 0; + + PangoFont *curPF = glyph_text[boxes[i].g_st].font; + if ( curPF ) { + PangoFontDescription *pfd = pango_font_describe(curPF); + font_instance *curF = f_src->Face(pfd); + if ( curF ) { + curF->FontMetrics(boxes[i].ascent, boxes[i].descent, boxes[i].leading); + curF->Unref(); + } + pango_font_description_free(pfd); + boxes[i].width = glyph_text[boxes[i].g_en].x - glyph_text[boxes[i].g_st].x; + } + } +} + + +void text_wrapper::KernXForLastAddition(double *i_kern_x, int i_len, double scale) +{ + if ( i_kern_x == NULL || i_len <= 0 || last_addition < 0 || last_addition >= uni32_length || uni32_length <= 0 ) return; + if ( kern_x == NULL ) { + kern_x = (double*)malloc((uni32_length + 1) * sizeof(double)); + for (int i = 0; i <= uni32_length; i++) kern_x[i] = 0; + } + int last_len = uni32_length - last_addition; + if ( i_len > last_len ) i_len = last_len; + for (int i = 0; i < i_len; i++) kern_x[last_addition + i] = i_kern_x[i] * scale; +} + +void text_wrapper::KernYForLastAddition(double *i_kern_y, int i_len, double scale) +{ + if ( i_kern_y == NULL || i_len <= 0 || last_addition < 0 || last_addition >= uni32_length || uni32_length <= 0 ) return; + if ( kern_y == NULL ) { + kern_y = (double*)malloc((uni32_length + 1) * sizeof(double)); + for (int i = 0; i <= uni32_length; i++) kern_y[i] = 0; + } + int last_len = uni32_length - last_addition; + if ( i_len > last_len ) i_len = last_len; + for (int i = 0; i < i_len; i++) kern_y[last_addition + i] = i_kern_y[i] * scale; +} + +void text_wrapper::KernXForLastAddition(GList *i_kern_x, double scale) +{ + if ( i_kern_x == NULL || last_addition < 0 || last_addition >= uni32_length || uni32_length <= 0 ) return; + if ( kern_x == NULL ) { + kern_x = (double*)malloc((uni32_length + 1) * sizeof(double)); + for (int i = 0; i <= uni32_length; i++) kern_x[i] = 0; + } + int last_len = uni32_length - last_addition; + GList *l = i_kern_x; + for (int i = 0; i < last_len && l && l->data; i++, l = l->next) { + kern_x[last_addition + i] = ((SVGLength *) l->data)->computed * scale; + } +} + +void text_wrapper::KernYForLastAddition(GList *i_kern_y, double scale) +{ + if ( i_kern_y == NULL || last_addition < 0 || last_addition >= uni32_length || uni32_length <= 0 ) return; + if ( kern_y == NULL ) { + kern_y = (double*)malloc((uni32_length + 1) * sizeof(double)); + for (int i = 0; i <= uni32_length; i++) kern_y[i] = 0; + } + int last_len = uni32_length - last_addition; + GList *l = i_kern_y; + for (int i = 0; i < last_len && l && l->data; i++, l = l->next) { + kern_y[last_addition + i] = ((SVGLength *) l->data)->computed * scale; + } +} + + +void text_wrapper::AddDxDy(void) +{ + if ( glyph_length <= 0 ) return; + if ( kern_x ) { + double sum = 0; + int l_pos = -1; + for (int i = 0; i < glyph_length; i++) { + int n_pos = glyph_text[i].uni_st; + if ( l_pos < n_pos ) { + for (int j = l_pos + 1; j <= n_pos; j++) sum += kern_x[j]; + } else if ( l_pos > n_pos ) { + for (int j = l_pos; j > n_pos; j--) sum -= kern_x[j]; + } + l_pos = n_pos; + + glyph_text[i].x += sum; + } + { + int n_pos = uni32_length; + if ( l_pos < n_pos ) { + for (int j = l_pos + 1; j <= n_pos; j++) sum += kern_x[j]; + } else if ( l_pos > n_pos ) { + for (int j = l_pos; j > n_pos; j--) sum -= kern_x[j]; + } + l_pos = n_pos; + glyph_text[glyph_length].x += sum; + } + } + if ( kern_y ) { + double sum = 0; + int l_pos = -1; + for (int i = 0; i < glyph_length; i++) { + int n_pos = glyph_text[i].uni_st; + if ( l_pos < n_pos ) { + for (int j = l_pos + 1; j <= n_pos; j++) sum += kern_y[j]; + } else if ( l_pos > n_pos ) { + for (int j = l_pos; j > n_pos; j--) sum -= kern_y[j]; + } + l_pos = n_pos; + + glyph_text[i].y += sum; + } + { + int n_pos = uni32_length; + if ( l_pos < n_pos ) { + for (int j = l_pos + 1; j <= n_pos; j++) sum += kern_y[j]; + } else if ( l_pos > n_pos ) { + for (int j = l_pos; j > n_pos; j--) sum -= kern_y[j]; + } + l_pos = n_pos; + glyph_text[glyph_length].y += sum; + } + } +} + + +/* + 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 : diff --git a/src/libnrtype/TextWrapper.h b/src/libnrtype/TextWrapper.h new file mode 100644 index 000000000..188f04ee1 --- /dev/null +++ b/src/libnrtype/TextWrapper.h @@ -0,0 +1,139 @@ +/* + * TextWrapper.h + * testICU + * + */ + +#ifndef my_text_wrapper +#define my_text_wrapper + +#include +#include +#include + +#include + +#include +#include "libnrtype/boundary-type.h" + +// miscanellous but useful data for a given text: chunking into logical pieces +// pieces include sentence/word, needed for example for the word-spacing property, +// and more important stuff like letter (ie visual letters) + +struct text_boundary; +struct one_glyph; +struct one_box; +struct one_para; + +class text_wrapper { +public: + char *utf8_text; // source text + gunichar *uni32_text; // ucs4 text computed from utf8_text + one_glyph *glyph_text; // glyph string computed for uni32_text + + // maps between the 2 + // These should most definitely be size_t, not int. + // I am quite sure (but not bored enough to actually test it + // on a 500MHz machine with 256MB RAM ) that this will crash + // for text longer than 2GB on architectures where + // sizeof(size_t) != sizeof(int) + int utf8_length; // utf8_text length + int uni32_length; // uni32_text length + int glyph_length; /**< Number of glyph in the glyph_text array. + * The size of the array is (glyph_length+1) in fact; the last glyph is kind of a '0' char. */ + int *uni32_codepoint; // uni32_codepoint[i] is the index in uni32_text corresponding to utf8_text[i] + int *utf8_codepoint; // utf8_codepoint[i] is the index in utf8_text of the beginning of uni32_text[i] + + // layout + font_instance *default_font; // font set as the default font (would need at least one alternate per language) + PangoLayout *pLayout; // private structure + + // kerning additions + int last_addition; // index in uni32_text of the beginning of the text added by the last AppendUTF8 call + double *kern_x; // dx[i] is the dx for the ith unicode char + double *kern_y; + + // boundaries, in an array + unsigned nbBound, maxBound; + text_boundary *bounds; + + // text organization + int nbBox, maxBox; + one_box *boxes; + int nbPara, maxPara; + one_para *paras; + + text_wrapper(void); + ~text_wrapper(void); + + // filling the structure with input data + void SetDefaultFont(font_instance *iFont); + + /** + * Append the specified text to utf8_text and uni32_codepoint. + * + * Note: Despite the name, the current implementation is primarily suited for a single + * call to set the text, rather than repeated calls to AppendUTF8: the implementation is + * Omega(n) in the new total length of the string, rather than just in the length of the + * text being appended. This can probably be addressed fairly easily (see comments in + * code) if this is an issue for new callers. + * + * \pre text is valid UTF-8, or null. + * Formally: text==NULL || g_utf8_validate(text, len, NULL). + * + * \param len Our sole existing caller (widgets/font_selector.cpp) uses len=-1. N.B. The current + * implementation may be buggy for non-negative len, especially for len==0. + */ + void AppendUTF8(char const *text, int len); + + // adds dx or dy for the text added by the last AppendUTF8() call + void KernXForLastAddition(double *i_kern_x, int i_len, double scale = 1.0); + void KernYForLastAddition(double *i_kern_y, int i_len, double scale = 1.0); + void KernXForLastAddition(GList *i_kern_x, double scale = 1.0); + void KernYForLastAddition(GList *i_kern_y, double scale = 1.0); + // compute the layout and stuff + void DoLayout(void); + // semi-private: computes boundaries in the input text + void ChunkText(void); + // utility function to move to the next element + bool NextChar(int &st, int &en) const; + bool NextWord(int &st, int &en) const; + bool NextPara(int &st, int &en) const; + + // post-processing after the initial layout + // for the xml-space property: merges consecutive whitespace, and eats leading whitespace in the text + void MergeWhiteSpace(void); + // makes vertical 'x' and 'y' fields in the glyph_text based on the computed positions + void MakeVertical(void); + // as the names says... + void AddLetterSpacing(double dx, double dy, int g_st = -1, int g_en = -1); + // adds the kerning specified by the KernXForLastAddition call to the layout + void AddDxDy(void); + + // boundary handling +private: + unsigned AddBoundary(text_boundary const &ib); +public: + void AddTwinBoundaries(text_boundary const &is, text_boundary const &ie); + void SortBoundaries(void); + void MakeTextBoundaries(PangoLogAttr *pAttrs, int nAttr); + //bool Contains(BoundaryType type, int g_st, int g_en, int &c_st, int &c_en); + bool IsBound(BoundaryType type, int g_st, int &c_st); + + void MeasureBoxes(void); + int NbLetter(int g_st, int g_en); +}; + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/libnrtype/boundary-type.h b/src/libnrtype/boundary-type.h new file mode 100644 index 000000000..7f8ecea90 --- /dev/null +++ b/src/libnrtype/boundary-type.h @@ -0,0 +1,31 @@ +#ifndef LIBNRTYPE_BOUNDARY_TYPE_H_INKSCAPE +#define LIBNRTYPE_BOUNDARY_TYPE_H_INKSCAPE + +/** \file Definition of the BoundaryType enum. */ + +/** + * The different kinds of semantic boundaries in text; or rather, + * the different things that may be delimited by a text_boundary. + */ +enum BoundaryType { + bnd_none = 0, + bnd_char, + bnd_word, + bnd_sent, /**< Sentence. Not currently used, and pango (1.8) does a bad job of determining + * sentence boundaries anyway. */ + bnd_para +}; + + +#endif /* !LIBNRTYPE_BOUNDARY_TYPE_H_INKSCAPE */ + +/* + 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 : diff --git a/src/libnrtype/font-glyph.h b/src/libnrtype/font-glyph.h new file mode 100644 index 000000000..c79888c12 --- /dev/null +++ b/src/libnrtype/font-glyph.h @@ -0,0 +1,29 @@ +#ifndef SEEN_LIBNRTYPE_FONT_GLYPH_H +#define SEEN_LIBNRTYPE_FONT_GLYPH_H + +#include +#include + +// the info for a glyph in a font. it's totally resolution- and fontsize-independent +struct font_glyph { + double h_advance, h_width; // width != advance because of kerning adjustements + double v_advance, v_width; + double bbox[4]; // bbox of the path (and the artbpath), not the bbox of the glyph + // as the fonts sometimes contain + Path* outline; // outline as a livarot Path + void* artbpath; // outline as a artbpath, for text->curve stuff (should be unified with livarot) +}; + + +#endif /* !SEEN_LIBNRTYPE_FONT_GLYPH_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 : diff --git a/src/libnrtype/font-instance.h b/src/libnrtype/font-instance.h new file mode 100644 index 000000000..5d57ff549 --- /dev/null +++ b/src/libnrtype/font-instance.h @@ -0,0 +1,121 @@ +#ifndef SEEN_LIBNRTYPE_FONT_INSTANCE_H +#define SEEN_LIBNRTYPE_FONT_INSTANCE_H + +#include +#include +#include +#include +#include "FontFactory.h" + +#include +#include +#include +#include + +// the font_instance are the template of several raster_font; they provide metrics and outlines +// that are drawn by the raster_font, so the raster_font needs info relative to the way the +// font need to be drawn. note that fontsize is a scale factor in the transform matrix +// of the style +// the various raster_font in use at a given time are held in a hash_map whose indices are the +// styles, hence the 2 following 'classes' +struct font_style_hash : public std::unary_function { + size_t operator()(font_style const &x) const; +}; + +struct font_style_equal : public std::binary_function { + bool operator()(font_style const &a, font_style const &b); +}; + +class font_instance { +public: + // hashmap to get the raster_font for a given style + __gnu_cxx::hash_map loadedStyles; + // the real source of the font + PangoFont* pFont; + // depending on the rendering backend, different temporary data + + // that's the font's fingerprint; this particular PangoFontDescription gives the entry at which this font_instance + // resides in the font_factory loadedFaces hash_map + PangoFontDescription* descr; + // refcount + int refCount; + // font_factory owning this font_instance + font_factory* daddy; + + // common glyph definitions for all the rasterfonts + __gnu_cxx::hash_map id_to_no; + int nbGlyph, maxGlyph; + font_glyph* glyphs; + + font_instance(void); + ~font_instance(void); + + void Ref(void); + void Unref(void); + + bool IsOutlineFont(void); // utility + void InstallFace(PangoFont* iFace); // utility; should reset the pFont field if loading failed + // in case the PangoFont is a bitmap font, for example. that way, the calling function + // will be able to check the validity of the font before installing it in loadedFaces + void InitTheFace(); + + int MapUnicodeChar(gunichar c); // calls the relevant unicode->glyph index function + void LoadGlyph(int glyph_id); // the main backend-dependent function + // loads the given glyph's info + + // nota: all coordinates returned by these functions are on a [0..1] scale; you need to multiply + // by the fontsize to get the real sizes + Path* Outline(int glyph_id, Path *copyInto=NULL); + // queries the outline of the glyph (in livarot Path form), and copies it into copyInto instead + // of allocating a new Path if copyInto != NULL + void* ArtBPath(int glyph_id); + // returns the artbpath for this glyph. no refcounting needed, it's deallocated when the + // font_instance dies + double Advance(int glyph_id, bool vertical); + // nominal advance of the font. + bool FontMetrics(double &ascent, double &descent, double &leading); + bool FontSlope(double &run, double &rise); + // for generating slanted cursors for oblique fonts + NR::Rect BBox(int glyph_id); + + // creates a rasterfont for the given style + raster_font* RasterFont(NR::Matrix const &trs, double stroke_width, + bool vertical = false, JoinType stroke_join = join_straight, + ButtType stroke_cap = butt_straight, float miter_limit = 4.0); + // the dashes array in iStyle is copied + raster_font* RasterFont(font_style const &iStyle); + // private use: tells the font_instance that the raster_font 'who' has died + void RemoveRasterFont(raster_font *who); + + // attribute queries + unsigned Name(gchar *str, unsigned size); + unsigned PSName(gchar *str, unsigned size); + unsigned Family(gchar *str, unsigned size); + unsigned Attribute(gchar const *key, gchar *str, unsigned size); + +private: + void FreeTheFace(); + +#ifdef USE_PANGO_WIN32 + HFONT theFace; +#else + FT_Face theFace; + // it's a pointer in fact; no worries to ref/unref it, pango does its magic + // as long as pFont is valid, theFace is too +#endif + +}; + + +#endif /* !SEEN_LIBNRTYPE_FONT_INSTANCE_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 : diff --git a/src/libnrtype/font-style-to-pos.cpp b/src/libnrtype/font-style-to-pos.cpp new file mode 100644 index 000000000..02f5ef37c --- /dev/null +++ b/src/libnrtype/font-style-to-pos.cpp @@ -0,0 +1,120 @@ +#include "font-style-to-pos.h" +#include + +/* 'lighter' and 'darker' have to be resolved earlier */ +/** +Given a style struct (CSS representation), sets the corresponding fields in a NRTypePosDef. + */ +NRTypePosDef +font_style_to_pos (SPStyle const &style) +{ + NRTypePosDef ret; + + switch (style.font_weight.computed) { + case SP_CSS_FONT_WEIGHT_100: + ret.weight = NR_POS_WEIGHT_CSS100; + break; + + case SP_CSS_FONT_WEIGHT_200: + ret.weight = NR_POS_WEIGHT_CSS200; + break; + + case SP_CSS_FONT_WEIGHT_300: + ret.weight = NR_POS_WEIGHT_CSS300; + break; + + case SP_CSS_FONT_WEIGHT_400: + case SP_CSS_FONT_WEIGHT_NORMAL: + ret.weight = NR_POS_WEIGHT_CSS400; + break; + + case SP_CSS_FONT_WEIGHT_500: + ret.weight = NR_POS_WEIGHT_CSS500; + break; + + case SP_CSS_FONT_WEIGHT_600: + ret.weight = NR_POS_WEIGHT_CSS600; + break; + + case SP_CSS_FONT_WEIGHT_700: + case SP_CSS_FONT_WEIGHT_BOLD: + ret.weight = NR_POS_WEIGHT_CSS700; + break; + + case SP_CSS_FONT_WEIGHT_800: + ret.weight = NR_POS_WEIGHT_CSS800; + break; + + case SP_CSS_FONT_WEIGHT_900: + ret.weight = NR_POS_WEIGHT_CSS900; + break; + + case SP_CSS_FONT_WEIGHT_LIGHTER: + case SP_CSS_FONT_WEIGHT_BOLDER: + default: + g_warning("Unrecognized font_weight.computed value"); + ret.weight = NR_POS_WEIGHT_NORMAL; + break; + } + + switch (style.font_style.computed) { + case SP_CSS_FONT_STYLE_ITALIC: + ret.italic = 1; + break; + + case SP_CSS_FONT_STYLE_OBLIQUE: + ret.oblique = 1; + break; + + case SP_CSS_FONT_STYLE_NORMAL: + default: + ret.italic = 0; + ret.oblique = 0; + break; + } + + switch (style.font_stretch.computed) { + case SP_CSS_FONT_STRETCH_ULTRA_CONDENSED: + case SP_CSS_FONT_STRETCH_EXTRA_CONDENSED: + ret.stretch = NR_POS_STRETCH_EXTRA_CONDENSED; + break; + + case SP_CSS_FONT_STRETCH_CONDENSED: + case SP_CSS_FONT_STRETCH_NARROWER: + ret.stretch = NR_POS_STRETCH_CONDENSED; + break; + + case SP_CSS_FONT_STRETCH_SEMI_CONDENSED: + ret.stretch = NR_POS_STRETCH_SEMI_CONDENSED; + break; + + case SP_CSS_FONT_STRETCH_SEMI_EXPANDED: + ret.stretch = NR_POS_STRETCH_SEMI_EXPANDED; + break; + + case SP_CSS_FONT_STRETCH_EXPANDED: + case SP_CSS_FONT_STRETCH_WIDER: + ret.stretch = NR_POS_STRETCH_EXPANDED; + break; + + case SP_CSS_FONT_STRETCH_EXTRA_EXPANDED: + case SP_CSS_FONT_STRETCH_ULTRA_EXPANDED: + ret.stretch = NR_POS_STRETCH_EXTRA_EXPANDED; + break; + + default: + ret.stretch = NR_POS_STRETCH_NORMAL; + break; + } + + switch (style.font_variant.computed) { + case SP_CSS_FONT_VARIANT_SMALL_CAPS: + ret.variant = NR_POS_VARIANT_SMALLCAPS; + break; + default: + ret.variant = NR_POS_VARIANT_NORMAL; + break; + } + + return ret; +} diff --git a/src/libnrtype/font-style-to-pos.h b/src/libnrtype/font-style-to-pos.h new file mode 100644 index 000000000..f58fdda3f --- /dev/null +++ b/src/libnrtype/font-style-to-pos.h @@ -0,0 +1,20 @@ +#ifndef __FONT_STYLE_TO_POS_H__ +#define __FONT_STYLE_TO_POS_H__ + +#include /* SPStyle */ +#include + +NRTypePosDef font_style_to_pos (SPStyle const &style); + +#endif /* __FONT_STYLE_TO_POS_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:encoding=utf-8:textwidth=99 : diff --git a/src/libnrtype/font-style.h b/src/libnrtype/font-style.h new file mode 100644 index 000000000..748d2020a --- /dev/null +++ b/src/libnrtype/font-style.h @@ -0,0 +1,38 @@ +#ifndef SEEN_LIBNRTYPE_FONT_STYLE_H +#define SEEN_LIBNRTYPE_FONT_STYLE_H + +#include +#include +#include + +// structure that holds data describing how to render glyphs of a font + +// Different raster styles. +struct font_style { + NR::Matrix transform; // the ctm. contains the font-size + bool vertical; // should be rendered vertically or not? + // good font support would take the glyph alternates for vertical mode, when present + double stroke_width; // if 0, the glyph is filled; otherwise stroked + JoinType stroke_join; + ButtType stroke_cap; + float stroke_miter_limit; + int nbDash; + double dash_offset; + double* dashes; + + void Apply(Path *src, Shape *dst); // utility: applies the style to the path and stores the result in the shape +}; + + +#endif /* !SEEN_LIBNRTYPE_FONT_STYLE_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 : diff --git a/src/libnrtype/libnrtype.def b/src/libnrtype/libnrtype.def new file mode 100644 index 000000000..34051de2d --- /dev/null +++ b/src/libnrtype/libnrtype.def @@ -0,0 +1,59 @@ +EXPORTS + nr_font_generic_free + nr_font_generic_glyph_advance_get + nr_font_generic_glyph_area_get + nr_font_generic_glyph_outline_get + nr_font_generic_glyph_outline_unref + nr_font_generic_new + nr_font_generic_rasterfont_free + nr_font_generic_rasterfont_new + nr_font_glyph_advance_get + nr_font_glyph_area_get + nr_font_glyph_outline_get + nr_font_glyph_outline_unref + nr_font_new_default + nr_font_ref + nr_font_unref + nr_name_list_release + nr_rasterfont_generic_free + nr_rasterfont_generic_glyph_advance_get + nr_rasterfont_generic_glyph_area_get + nr_rasterfont_generic_glyph_mask_render + nr_rasterfont_generic_new + nr_rasterfont_glyph_advance_get + nr_rasterfont_glyph_area_get + nr_rasterfont_glyph_mask_render + nr_rasterfont_new + nr_rasterfont_ref + nr_rasterfont_unref + nr_type_dict_insert + nr_type_dict_lookup + nr_type_dict_new + nr_type_directory_family_list_get + nr_type_directory_forget_face + nr_type_directory_lookup + nr_type_directory_lookup_fuzzy + nr_type_directory_style_list_get + nr_type_empty_build_def + nr_type_ft2_build_def +; nr_type_gnome_build_def +; nr_type_gnome_families_get +; nr_type_gnome_typefaces_get + nr_type_w32_build_def + nr_type_w32_families_get + nr_type_w32_typefaces_get +; nr_type_xft_build_def +; nr_type_xft_families_get +; nr_type_xft_typefaces_get + nr_typeface_attribute_get + nr_typeface_family_name_get + nr_typeface_ft2_get_type + nr_typeface_get_type + nr_typeface_glyph_advance_get + nr_typeface_glyph_outline_get + nr_typeface_glyph_outline_unref +; nr_typeface_gnome_get_type + nr_typeface_lookup_default + nr_typeface_name_get + nr_typeface_new + nr_typeface_w32_get_type diff --git a/src/libnrtype/makefile.in b/src/libnrtype/makefile.in new file mode 100644 index 000000000..fbebd763d --- /dev/null +++ b/src/libnrtype/makefile.in @@ -0,0 +1,17 @@ +# Convenience stub makefile to call the real Makefile. + +@SET_MAKE@ + +# Explicit so that it's the default rule. +all: + cd .. && $(MAKE) libnrtype/all + +clean %.a %.o: + cd .. && $(MAKE) libnrtype/$@ + +.PHONY: all clean + +OBJEXT = @OBJEXT@ + +.SUFFIXES: +.SUFFIXES: .a .$(OBJEXT) diff --git a/src/libnrtype/nr-type-pos-def.cpp b/src/libnrtype/nr-type-pos-def.cpp new file mode 100644 index 000000000..fef04039e --- /dev/null +++ b/src/libnrtype/nr-type-pos-def.cpp @@ -0,0 +1,268 @@ +#include "nr-type-pos-def.h" +#include +#include + +/** + * Given a font name or style name, returns a constant describing its + * apparent style (normal/italic/oblique). +*/ +int +parse_name_for_style (char const *cc) +{ + g_assert ( cc != NULL ); + gchar *c = g_ascii_strdown (cc, -1); + + gint style; + // first dab at i18n... french and german + if (strstr (c, "italic") || strstr (c, "italique") || strstr (c, "kursiv")) { + style = NR_POS_STYLE_ITALIC; + } else if (strstr (c, "oblique")) { + style = NR_POS_STYLE_OBLIQUE; + } else { + style = NR_POS_STYLE_NORMAL; + } + + g_free (c); + return style; +} + + +/** + * Given a font name or style name, returns a constant describing its + * apparent weight. +*/ +int +parse_name_for_weight (char const *cc) +{ + g_assert ( cc != NULL ); + gchar *c = g_ascii_strdown (cc, -1); + + gint weight; + if (strstr (c, "thin")) { + weight = NR_POS_WEIGHT_THIN; + } else if (strstr (c, "extra light")) { + weight = NR_POS_WEIGHT_EXTRA_LIGHT; + } else if (strstr (c, "ultra light")) { + weight = NR_POS_WEIGHT_ULTRA_LIGHT; + } else if (strstr (c, "light")) { + weight = NR_POS_WEIGHT_LIGHT; + } else if (strstr (c, "book")) { + weight = NR_POS_WEIGHT_BOOK; + } else if (strstr (c, "medium")) { + weight = NR_POS_WEIGHT_MEDIUM; + } else if (strstr (c, "semi bold")) { + weight = NR_POS_WEIGHT_SEMIBOLD; + } else if (strstr (c, "semibold")) { + weight = NR_POS_WEIGHT_SEMIBOLD; + } else if (strstr (c, "demi bold")) { + weight = NR_POS_WEIGHT_DEMIBOLD; + } else if (strstr (c, "demibold") || strstr (c, "demi")) { + weight = NR_POS_WEIGHT_DEMIBOLD; + } else if (strstr (c, "ultra bold")) { + weight = NR_POS_WEIGHT_ULTRA_BOLD; + } else if (strstr (c, "extra bold") || strstr (c, "xbold") || strstr (c, "xtrabold")) { + weight = NR_POS_WEIGHT_EXTRA_BOLD; + } else if (strstr (c, "black") || strstr (c, "heavy")) { + weight = NR_POS_WEIGHT_BLACK; + } else if (strstr (c, "bold")) { + /* Must come after the checks for `blah bold'. */ + weight = NR_POS_WEIGHT_BOLD; + } else { + weight = NR_POS_WEIGHT_NORMAL; + } + + g_free (c); + return weight; +} + +/** + * Given a font name or style name, returns a constant describing its + * apparent stretch. +*/ +int +parse_name_for_stretch (char const *cc) +{ + g_assert ( cc != NULL ); + gchar *c = g_ascii_strdown (cc, -1); + + gint stretch; + if (strstr (c, "ultra narrow") || strstr (c, "ultra condensed") || strstr (c, "extra condensed")) { + stretch = NR_POS_STRETCH_EXTRA_CONDENSED; + } else if (strstr (c, "ultra wide") || strstr (c, "ultra expanded") || strstr (c, "ultra extended") || strstr (c, "extra expanded")) { + stretch = NR_POS_STRETCH_EXTRA_EXPANDED; + } else if (strstr (c, "semi condensed") || strstr (c, "semicondensed")) { + stretch = NR_POS_STRETCH_SEMI_CONDENSED; + } else if (strstr (c, "semi extended") || strstr (c, "semiextended")) { + stretch = NR_POS_STRETCH_SEMI_EXPANDED; + } else if (strstr (c, "narrow") || strstr (c, "condensed")) { + stretch = NR_POS_STRETCH_CONDENSED; + } else if (strstr (c, "wide") || strstr (c, "expanded") || strstr (c, "extended")) { + stretch = NR_POS_STRETCH_EXPANDED; + } else { + stretch = NR_POS_STRETCH_NORMAL; + } + + g_free (c); + return stretch; +} + +/** + * Given a font name or style name, returns a constant describing its + * apparent variant (normal/smallcaps). +*/ +int +parse_name_for_variant (char const *cc) +{ + g_assert ( cc != NULL ); + gchar *c = g_ascii_strdown (cc, -1); + + gint variant; + if (strstr (c, "small caps") || strstr (c, "smallcaps") || strstr (c, "caps")) { + variant = NR_POS_VARIANT_SMALLCAPS; + } else { + variant = NR_POS_VARIANT_NORMAL; + } + + g_free (c); + return variant; +} + +/** + * Given a style constant, returns the CSS value for font-style. +*/ +const char * +style_to_css (int style) +{ + switch (style) { + case NR_POS_STYLE_NORMAL: + return "normal"; + break; + case NR_POS_STYLE_ITALIC: + return "italic"; + break; + case NR_POS_STYLE_OBLIQUE: + return "oblique"; + break; + default: + break; + } + return NULL; +} + + +/** + * Given a weight constant, returns the CSS value for font-weight. +*/ +const char * +weight_to_css (int weight) +{ + switch (weight) { + case NR_POS_WEIGHT_THIN: + return "100"; + break; + case NR_POS_WEIGHT_EXTRA_LIGHT: + return "200"; + break; + case NR_POS_WEIGHT_LIGHT: + return "300"; + break; + case NR_POS_WEIGHT_BOOK: + return "normal"; + break; + case NR_POS_WEIGHT_MEDIUM: + return "500"; + break; + case NR_POS_WEIGHT_SEMIBOLD: + return "600"; + break; + case NR_POS_WEIGHT_BOLD: + return "bold"; + break; + case NR_POS_WEIGHT_EXTRA_BOLD: + return "800"; + break; + case NR_POS_WEIGHT_BLACK: + return "900"; + break; + default: + break; + } + return NULL; +} + +/** + * Given a stretch constant, returns the CSS value for font-stretch. +*/ +const char * +stretch_to_css (int stretch) +{ + switch (stretch) { + case NR_POS_STRETCH_EXTRA_CONDENSED: + return "extra-condensed"; + break; + case NR_POS_STRETCH_CONDENSED: + return "condensed"; + break; + case NR_POS_STRETCH_SEMI_CONDENSED: + return "semi-condensed"; + break; + case NR_POS_STRETCH_NORMAL: + return "normal"; + break; + case NR_POS_STRETCH_SEMI_EXPANDED: + return "semi-expanded"; + break; + case NR_POS_STRETCH_EXPANDED: + return "expanded"; + break; + case NR_POS_STRETCH_EXTRA_EXPANDED: + return "extra-expanded"; + break; + default: + break; + } + return NULL; +} + +/** + * Given a variant constant, returns the CSS value for font-variant. +*/ +const char * +variant_to_css (int stretch) +{ + switch (stretch) { + case NR_POS_VARIANT_SMALLCAPS: + return "small-caps"; + break; + case NR_POS_VARIANT_NORMAL: + return "normal"; + break; + default: + break; + } + return NULL; +} + + +/** + * Constructor for NRTypePostDef. Sets the italic, oblique, weight, + * stretch, and variant. + */ +NRTypePosDef::NRTypePosDef(char const *description) { + // we cannot use strcasestr, it's linux only... so we must lowercase the string first + g_assert ( description != NULL ); + gchar *c = g_ascii_strdown (description, -1); + + /* copied from nr-type-directory.cpp:nr_type_calculate_position. */ + + italic = (strstr (c, "italic") != NULL); + oblique = (strstr (c, "oblique") != NULL); + + weight = parse_name_for_weight (c); + + stretch = parse_name_for_stretch (c); + + variant = parse_name_for_variant (c); + + g_free (c); +} diff --git a/src/libnrtype/nr-type-pos-def.h b/src/libnrtype/nr-type-pos-def.h new file mode 100644 index 000000000..ab9f5b45c --- /dev/null +++ b/src/libnrtype/nr-type-pos-def.h @@ -0,0 +1,102 @@ +#ifndef __NR_TYPE_POS_DEF_H__ +#define __NR_TYPE_POS_DEF_H__ + +#define NR_POS_STYLE_NORMAL 0 +#define NR_POS_STYLE_ITALIC 1 +#define NR_POS_STYLE_OBLIQUE 2 + +#define NR_POS_WEIGHT_THIN 32 +#define NR_POS_WEIGHT_EXTRA_LIGHT 64 +#define NR_POS_WEIGHT_ULTRA_LIGHT 64 +#define NR_POS_WEIGHT_LIGHT 96 +#define NR_POS_WEIGHT_BOOK 128 +#define NR_POS_WEIGHT_NORMAL 128 +#define NR_POS_WEIGHT_MEDIUM 144 +#define NR_POS_WEIGHT_SEMIBOLD 160 +#define NR_POS_WEIGHT_DEMIBOLD 160 +#define NR_POS_WEIGHT_BOLD 192 +#define NR_POS_WEIGHT_ULTRA_BOLD 224 +#define NR_POS_WEIGHT_EXTRA_BOLD 224 +#define NR_POS_WEIGHT_BLACK 255 + +#define NR_POS_STRETCH_ULTRA_CONDENSED 48 +#define NR_POS_STRETCH_EXTRA_CONDENSED 48 +#define NR_POS_STRETCH_CONDENSED 88 +#define NR_POS_STRETCH_SEMI_CONDENSED 108 +#define NR_POS_STRETCH_NORMAL 128 +#define NR_POS_STRETCH_SEMI_EXPANDED 148 +#define NR_POS_STRETCH_EXPANDED 168 +#define NR_POS_STRETCH_EXTRA_EXPANDED 228 +#define NR_POS_STRETCH_ULTRA_EXPANDED 228 + +// This is an enumerate, rather than on/off property, +// for I sincerely hope the vocabulary of this property will be +// extended by the W3C in the future to allow for more fancy fonts +#define NR_POS_VARIANT_NORMAL 0 +#define NR_POS_VARIANT_SMALLCAPS 1 + +/* Mapping from CSS weight numbers. + + for i in `seq 9`; do + if [ $i -le 4 ]; then w=$((32 * $i)); + elif [ $i = 5 ]; then w=144; + elif [ $i -lt 9 ]; then w=$((32 * $(($i - 1)))); + else w=255; + fi; + printf '#define NR_POS_WEIGHT_CSS%d00\t\t%3d\n' $i $w; + done + + This calculation approximately matches the old to-and-from-text code, + I don't claim it to be reasonable. ("approximately": some of the old + code wrote strings like "semi" and "heavy" that weren't being parsed + at the other end, and it had CSS100 darker than CSS200.) + */ +#define NR_POS_WEIGHT_CSS100 32 +#define NR_POS_WEIGHT_CSS200 64 +#define NR_POS_WEIGHT_CSS300 96 +#define NR_POS_WEIGHT_CSS400 128 +#define NR_POS_WEIGHT_CSS500 144 +#define NR_POS_WEIGHT_CSS600 160 +#define NR_POS_WEIGHT_CSS700 192 +#define NR_POS_WEIGHT_CSS800 224 +#define NR_POS_WEIGHT_CSS900 255 + + +class NRTypePosDef { +public: + unsigned int italic : 1; + unsigned int oblique : 1; + unsigned int weight : 8; + unsigned int stretch : 8; + unsigned int variant : 8; + /* These can probably be made sensible sizes rather than bitfields; for the moment we'll + keep the old definition. */ + +public: + NRTypePosDef() : + italic(0), + oblique(0), + weight(NR_POS_WEIGHT_NORMAL), + stretch(NR_POS_STRETCH_NORMAL), + variant(NR_POS_VARIANT_NORMAL) + { } + + NRTypePosDef(char const *description); + + bool signature() {return this->italic + + this->oblique * 255 + + this->weight * 255 * 255 + + this->stretch * 255 * 255 * 255 + + this->variant * 255 * 255 * 255 * 255;}; +}; + +int parse_name_for_style (char const *c); +int parse_name_for_weight (char const *c); +int parse_name_for_stretch (char const *c); +int parse_name_for_variant (char const *c); +const char *style_to_css (int style); +const char *weight_to_css (int weight); +const char *stretch_to_css (int stretch); +const char *variant_to_css (int variant); + +#endif /* __NR_TYPE_POS_DEF_H__ */ diff --git a/src/libnrtype/nr-type-primitives.cpp b/src/libnrtype/nr-type-primitives.cpp new file mode 100644 index 000000000..616134383 --- /dev/null +++ b/src/libnrtype/nr-type-primitives.cpp @@ -0,0 +1,167 @@ +#define __NR_TYPE_PRIMITIVES_C__ + +/* + * Typeface and script library + * + * Authors: + * Lauris Kaplinski + * + * This code is in public domain + */ + +/* This should be enough for approximately 10000 fonts */ +#define NR_DICTSIZE 2777 + +#include +#include +#include +#include "nr-type-primitives.h" + +/** + * An entry in a list of key->value pairs + */ +struct NRTDEntry { + NRTDEntry *next; + const gchar *key; + void *val; +}; + +/** + * Type Dictionary, consisting of size number of key-value entries + */ +struct NRTypeDict { + unsigned int size; + NRTDEntry **entries; +}; + +static NRTDEntry *nr_td_entry_new (void); + +/** + * Calls the destructor for each item in list + */ +void +nr_name_list_release (NRNameList *list) +{ + if (list->destructor) { + list->destructor (list); + } +} + +void +nr_style_list_release (NRStyleList *list) +{ + if (list->destructor) { + list->destructor (list); + } +} + +/** + * Creates a new typeface dictionary of size NR_DICTSIZE + * and initalizes all the entries to NULL + */ +NRTypeDict * +nr_type_dict_new (void) +{ + NRTypeDict *td; + int i; + + td = nr_new (NRTypeDict, 1); + + td->size = NR_DICTSIZE; + td->entries = nr_new (NRTDEntry *, td->size); + for (i = 0; i < NR_DICTSIZE; i++) { + td->entries[i] = NULL; + } + + return td; +} + +/** + * Hashes a string and returns the int + */ +static unsigned int +nr_str_hash (const gchar *p) +{ + unsigned int h; + + h = *p; + + if (h != 0) { + for (p += 1; *p; p++) h = (h << 5) - h + *p; + } + + return h; +} + +/** + * Inserts a key/value into a typeface dictionary + */ +void +nr_type_dict_insert (NRTypeDict *td, const gchar *key, void *val) +{ + if (key) { + NRTDEntry *tde; + unsigned int hval; + + hval = nr_str_hash (key) % td->size; + + for (tde = td->entries[hval]; tde; tde = tde->next) { + if (!strcmp (key, tde->key)) { + tde->val = val; + return; + } + } + + tde = nr_td_entry_new (); + tde->next = td->entries[hval]; + tde->key = key; + tde->val = val; + td->entries[hval] = tde; + } +} + +/** + * Looks up the given key from the typeface dictionary + */ +void * +nr_type_dict_lookup (NRTypeDict *td, const gchar *key) +{ + if (key) { + NRTDEntry *tde; + unsigned int hval; + hval = nr_str_hash (key) % td->size; + for (tde = td->entries[hval]; tde; tde = tde->next) { + if (!strcmp (key, tde->key)) return tde->val; + } + } + + return NULL; +} + +#define NR_TDE_BLOCK_SIZE 32 + +static NRTDEntry *nr_tde_free_list; + +/** + * Creates a new TDEntry + */ +static NRTDEntry * +nr_td_entry_new (void) +{ + NRTDEntry *tde; + + if (!nr_tde_free_list) { + int i; + nr_tde_free_list = nr_new (NRTDEntry, NR_TDE_BLOCK_SIZE); + for (i = 0; i < (NR_TDE_BLOCK_SIZE - 1); i++) { + nr_tde_free_list[i].next = nr_tde_free_list + i + 1; + } + nr_tde_free_list[i].next = NULL; + } + + tde = nr_tde_free_list; + nr_tde_free_list = tde->next; + + return tde; +} + diff --git a/src/libnrtype/nr-type-primitives.h b/src/libnrtype/nr-type-primitives.h new file mode 100644 index 000000000..9bb181c4b --- /dev/null +++ b/src/libnrtype/nr-type-primitives.h @@ -0,0 +1,50 @@ +#ifndef __NR_TYPE_PRIMITIVES_H__ +#define __NR_TYPE_PRIMITIVES_H__ + +/* + * Typeface and script library + * + * Authors: + * Lauris Kaplinski + * g++ port: Nathan Hurst + * + * This code is in public domain + */ + +#include + +struct NRNameList; +struct NRStyleList; +struct NRTypeDict; + +typedef void (* NRNameListDestructor) (NRNameList *list); +typedef void (* NRStyleListDestructor) (NRStyleList *list); + +struct NRNameList { + guint length; + guchar **names; + guchar **families; + NRNameListDestructor destructor; +}; + +struct NRStyleRecord { + const char *name; + const char *descr; +}; + +struct NRStyleList { + guint length; + NRStyleRecord *records; + NRStyleListDestructor destructor; +}; + +void nr_name_list_release (NRNameList *list); +void nr_style_list_release (NRStyleList *list); + +NRTypeDict *nr_type_dict_new (void); + +void nr_type_dict_insert (NRTypeDict *td, const gchar *key, void *val); + +void *nr_type_dict_lookup (NRTypeDict *td, const gchar *key); + +#endif diff --git a/src/libnrtype/nrtype-forward.h b/src/libnrtype/nrtype-forward.h new file mode 100644 index 000000000..f3344f2fd --- /dev/null +++ b/src/libnrtype/nrtype-forward.h @@ -0,0 +1,23 @@ +#ifndef SEEN_LIBNRTYPE_NRTYPE_FORWARD_H +#define SEEN_LIBNRTYPE_NRTYPE_FORWARD_H + +class font_factory; +struct font_glyph; +class font_instance; +struct font_style; +class raster_font; +class raster_glyph; +class raster_position; + +#endif /* !SEEN_LIBNRTYPE_NRTYPE_FORWARD_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 : diff --git a/src/libnrtype/one-box.h b/src/libnrtype/one-box.h new file mode 100644 index 000000000..1040e2be9 --- /dev/null +++ b/src/libnrtype/one-box.h @@ -0,0 +1,28 @@ +/** \file Definition of struct one_box. */ + +#ifndef LIBNRTYPE_ONE_BOX_H_INKSCAPE +#define LIBNRTYPE_ONE_BOX_H_INKSCAPE + +// text chunking 2, the comeback +// this time for sp-typeset +struct one_box { + int g_st, g_en; ///< First and last glyph of this word. + double ascent, descent, leading; + double width; + bool word_start, word_end; +}; + + +#endif /* !LIBNRTYPE_ONE_BOX_H_INKSCAPE */ + +/* + 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 : + diff --git a/src/libnrtype/one-glyph.h b/src/libnrtype/one-glyph.h new file mode 100644 index 000000000..667c7743b --- /dev/null +++ b/src/libnrtype/one-glyph.h @@ -0,0 +1,46 @@ +/** \file Definition of struct one_glyph. */ + +/* + * License: May be redistributed with or without modifications under the terms of the Gnu General + * Public License as published by the Free Software Foundation, version 2 or (at your option) any + * later version. + */ + +#ifndef LIBNRTYPE_ONE_GLYPH_H_INKSCAPE +#define LIBNRTYPE_ONE_GLYPH_H_INKSCAPE + +#include + +/** + * Information for a single glyph. + * + * Pango converts the text into glyphs, but scatters the info for a given glyph; here is a + * structure holding what inkscape needs to know. + */ +struct one_glyph { + int gl; ///< glyph_id + double x, y; ///< glyph position in the layout (nominal sizes, in the [0..1] range). + bool char_start; /**< Whether this glyph is the beginning of a letter. (RTL is taken in + * account.) */ + bool word_start; ///< Whether this glyph is the beginning of a word. + bool para_start; ///< Whether this glyph is the beginning of a paragraph (for indentation). + char uni_dir; ///< BiDi orientation of the run containing this glyph. + int uni_st, uni_en; /**< Start and end positions of the text corresponding to this glyph. + * You always have uni_st < uni_en. */ + PangoFont *font; /**< Font this glyph uses. (For bidi text, you need several fonts.) + * When rendering glyphs, check if this font is the one you're using. */ +}; + + +#endif /* !LIBNRTYPE_ONE_GLYPH_H_INKSCAPE */ + +/* + 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 : diff --git a/src/libnrtype/one-para.h b/src/libnrtype/one-para.h new file mode 100644 index 000000000..60e59531f --- /dev/null +++ b/src/libnrtype/one-para.h @@ -0,0 +1,20 @@ +#ifndef LIBNRTYPE_ONE_PARA_H_INKSCAPE +#define LIBNRTYPE_ONE_PARA_H_INKSCAPE + +struct one_para { + int b_st, b_en; +}; + + +#endif /* !LIBNRTYPE_ONE_PARA_H_INKSCAPE */ + +/* + 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 : diff --git a/src/libnrtype/raster-glyph.h b/src/libnrtype/raster-glyph.h new file mode 100644 index 000000000..e39cc4e41 --- /dev/null +++ b/src/libnrtype/raster-glyph.h @@ -0,0 +1,49 @@ +#ifndef SEEN_LIBNRTYPE_RASTER_GLYPH_H +#define SEEN_LIBNRTYPE_RASTER_GLYPH_H + +#include +#include +#include + +// a little utility class that holds data to render a styled glyph +// ie. it's like a polygon. its function is to wrap the subpixel positionning +class raster_glyph { +public: + // raster_font that created me + raster_font* daddy; + // the glyph i am (the style is in daddy) + int glyph_id; + // internal structure: the styled path, and the associated uncrossed polygon + // they could be removed after the raster_position have been computed + Path* outline; // transformed by the matrix in style (may be factorized, but is small) + Shape* polygon; + // subpixel positions + // nb_sub_pixel is set to 4 when the glyph is created (it's hardcoded) + int nb_sub_pixel; + raster_position* sub_pixel; + + raster_glyph(void); + ~raster_glyph(void); + + // utility + void SetSubPixelPositionning(int nb_pos); + void LoadSubPixelPosition(int no); + + // the interesting function: blits the glyph onto over + // over should be a mask, ie a NRPixBlock with one 8bit plane + void Blit(NR::Point const &at, NRPixBlock &over); // alpha only +}; + + +#endif /* !SEEN_LIBNRTYPE_RASTER_GLYPH_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 : diff --git a/src/libnrtype/raster-position.h b/src/libnrtype/raster-position.h new file mode 100644 index 000000000..ef12b93ec --- /dev/null +++ b/src/libnrtype/raster-position.h @@ -0,0 +1,46 @@ +#ifndef SEEN_LIBNRTYPE_RASTER_POSITION_H +#define SEEN_LIBNRTYPE_RASTER_POSITION_H + +#include + +#include +#include +#include + +// one subpixel position +// it's basically a set of trapezoids (=float_ligne_run) representing the black areas of the glyph +// all trapezoids are in the same array, hence the run_on_line array to give the number of +// trapezoids on each line +// trapezoids store the x-positions as float, and are shifted to the x blit position +// so it's "exact" in the x direction and subpixel in the y direction +class raster_position { +public: + int top, bottom; // baseline is y=0 + // top is the first pixel, bottom is the last + int* run_on_line; // array of size (bottom-top+1): run_on_line[i] gives the number of runs on line top+i + int nbRun; + float_ligne_run* runs; + +public: + raster_position(); + ~raster_position(); + + // stuff runs into the structure + void AppendRuns(std::vector const &r, int y); + // blits the trapezoids. + void Blit(float ph, int pv, NRPixBlock &over); +}; + + +#endif /* !SEEN_LIBNRTYPE_RASTER_POSITION_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 : diff --git a/src/libnrtype/text-boundary.h b/src/libnrtype/text-boundary.h new file mode 100644 index 000000000..83a825b55 --- /dev/null +++ b/src/libnrtype/text-boundary.h @@ -0,0 +1,49 @@ +#ifndef TEXT_BOUNDARY_H_INKSCAPE +#define TEXT_BOUNDARY_H_INKSCAPE + +/** \file Definition of text_boundary. */ + +/* + * License: May be redistributed with or without modifications under the terms of the Gnu General + * Public License as published by the Free Software Foundation, version 2 or (at your option) any + * later version. + */ + +#include "libnrtype/boundary-type.h" + + +/** + * A character/word/paragraph boundary in the text, used by TextWrapper. + * + * (Boundaries are paired.) + */ +struct text_boundary { + /** Index of the boundary in the text: first char of the text chunk if 'start' boundary, char + * right after the boundary otherwise. + */ + int uni_pos; + BoundaryType type; ///< Kind of boundary. + bool start; ///< Indicates whether this marks the beginning or end of a chunk. + unsigned other; ///< Index in bounds[] of the corresponding end/beginning boundary. + unsigned old_ix; ///< Temporary storage used solely SortBoundaries. + /// Data for this boundary; usually, one int is enough. + union { + int i; + double f; + void *p; + } data; +}; + + +#endif /* !TEXT_BOUNDARY_H_INKSCAPE */ + +/* + 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 : diff --git a/src/line-snapper.cpp b/src/line-snapper.cpp new file mode 100644 index 000000000..383c1fb96 --- /dev/null +++ b/src/line-snapper.cpp @@ -0,0 +1,80 @@ +#include "libnr/nr-values.h" +#include "libnr/nr-point-fns.h" +#include "geom.h" +#include "line-snapper.h" + +Inkscape::LineSnapper::LineSnapper(SPNamedView const *nv, NR::Coord const d) : Snapper(nv, d) +{ + +} + +Inkscape::SnappedPoint Inkscape::LineSnapper::_doFreeSnap(NR::Point const &p, + std::list const &it) const +{ + /* Snap along x (ie to vertical lines) */ + Inkscape::SnappedPoint const v = _doConstrainedSnap(p, component_vectors[NR::X], it); + /* Snap along y (ie to horizontal lines) */ + Inkscape::SnappedPoint const h = _doConstrainedSnap(p, component_vectors[NR::Y], it); + + /* If we snapped to both, combine the two results. This is so that, for example, + ** we snap nicely to the intersection of two guidelines. + */ + if (v.getDistance() < NR_HUGE && h.getDistance() < NR_HUGE) { + return SnappedPoint(NR::Point(v.getPoint()[NR::X], h.getPoint()[NR::Y]), hypot(v.getDistance(), h.getDistance())); + } + + /* If we snapped to a vertical line, return that */ + if (v.getDistance() < NR_HUGE) { + return v; + } + + /* Otherwise just return any horizontal snap; if we didn't snap to that either + ** we haven't snapped to anything. + */ + return h; +} + +Inkscape::SnappedPoint Inkscape::LineSnapper::_doConstrainedSnap(NR::Point const &p, + NR::Point const &c, + std::list const &it) const +{ + Inkscape::SnappedPoint s = SnappedPoint(p, NR_HUGE); + + NR::Point const v = NR::unit_vector(c); + + /* Get the lines that we will try to snap to */ + const LineList lines = _getSnapLines(p); + + for (LineList::const_iterator i = lines.begin(); i != lines.end(); i++) { + + /* Normal to the line we're trying to snap along */ + NR::Point const n(NR::rot90(v)); + + /* Hence constant term of the line we're trying to snap along */ + NR::Coord const q = dot(n, p); + + /* Try to intersect this line with the target line */ + NR::Point t = p; + IntersectorKind const k = intersector_line_intersection(n, q, component_vectors[i->first], i->second, t); + + if (k == INTERSECTS) { + const NR::Coord dist = L2(t - p); + if (dist < getDistance() && dist < s.getDistance() ) { + s = SnappedPoint(t, dist); + } + } + } + + return s; +} + +/* + 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/line-snapper.h b/src/line-snapper.h new file mode 100644 index 000000000..581466d33 --- /dev/null +++ b/src/line-snapper.h @@ -0,0 +1,44 @@ +#ifndef SEEN_LINE_SNAPPER_H +#define SEEN_LINE_SNAPPER_H + +/** + * \file src/line-snapper.h + * \brief Superclass for snappers to horizontal and vertical lines. + * + * Authors: + * Carl Hetherington + * + * Released under GNU GPL, read the file 'COPYING' for more information. + */ + +#include "snapper.h" + +namespace Inkscape +{ + +class LineSnapper : public Snapper +{ +public: + LineSnapper(SPNamedView const *nv, NR::Coord const d); + +protected: + typedef std::list > LineList; + +private: + SnappedPoint _doFreeSnap(NR::Point const &p, + std::list const &it) const; + + SnappedPoint _doConstrainedSnap(NR::Point const &p, + NR::Point const &c, + std::list const &it) const; + + /** + * \param p Point that we are trying to snap. + * \return List of lines that we should try snapping to. + */ + virtual LineList _getSnapLines(NR::Point const &p) const = 0; +}; + +} + +#endif /* !SEEN_LINE_SNAPPER_H */ diff --git a/src/livarot/.cvsignore b/src/livarot/.cvsignore new file mode 100644 index 000000000..ef34ceff5 --- /dev/null +++ b/src/livarot/.cvsignore @@ -0,0 +1,7 @@ +*~ +.deps +.dirstamp +.libs +Makefile +Makefile.in +makefile diff --git a/src/livarot/AVL.cpp b/src/livarot/AVL.cpp new file mode 100644 index 000000000..5ddbe8070 --- /dev/null +++ b/src/livarot/AVL.cpp @@ -0,0 +1,965 @@ +/* + * AVL.cpp + * nlivarot + * + * Created by fred on Mon Jun 16 2003. + * + */ + +#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] = NULL; + son[i] = NULL; + } + + dad = NULL; + 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] = NULL; + } +} + +AVLTree *AVLTree::Leftmost() +{ + return leafFromDad(NULL, LEFT); +} + +AVLTree *AVLTree::leaf(AVLTree *from, Side s) +{ + if (from == son[1 - s]) { + if (son[s]) { + return son[s]->leafFromDad(this, s); + } + else if (dad) { + return dad->leaf(this, s); + } + } + else if (from == son[s]) { + if (dad) { + return dad->leaf(this, s); + } + } + + return NULL; +} + +AVLTree *AVLTree::leafFromDad(AVLTree *from, Side s) +{ + if (son[s]) { + return son[s]->leafFromDad(this, s); + } + + return this; +} + +int +AVLTree::RestoreBalances (AVLTree * from, AVLTree * &racine) +{ + if (from == NULL) + { + if (dad) + return dad->RestoreBalances (this, racine); + } + else + { + if (balance == 0) + { + if (from == son[LEFT]) + balance = 1; + if (from == son[RIGHT]) + balance = -1; + if (dad) + return dad->RestoreBalances (this, racine); + return avl_no_err; + } + else if (balance > 0) + { + if (from == son[RIGHT]) + { + balance = 0; + return avl_no_err; + } + if (son[LEFT] == NULL) + { +// cout << "mierda\n"; + return avl_bal_err; + } + AVLTree *a = this; + AVLTree *b = son[LEFT]; + AVLTree *e = son[RIGHT]; + AVLTree *c = son[LEFT]->son[LEFT]; + AVLTree *d = son[LEFT]->son[RIGHT]; + if (son[LEFT]->balance > 0) + { + AVLTree *r = dad; + + a->dad = b; + b->son[RIGHT] = a; + a->son[RIGHT] = e; + if (e) + e->dad = a; + a->son[LEFT] = d; + if (d) + d->dad = a; + b->son[LEFT] = c; + if (c) + c->dad = b; + b->dad = r; + if (r) + { + if (r->son[LEFT] == a) + r->son[LEFT] = b; + if (r->son[RIGHT] == a) + r->son[RIGHT] = b; + } + if (racine == a) + racine = b; + + a->balance = 0; + b->balance = 0; + return avl_no_err; + } + else + { + if (son[LEFT]->son[RIGHT] == NULL) + { + // cout << "mierda\n"; + return avl_bal_err; + } + AVLTree *f = son[LEFT]->son[RIGHT]->son[LEFT]; + AVLTree *g = son[LEFT]->son[RIGHT]->son[RIGHT]; + AVLTree *r = dad; + + a->dad = d; + d->son[RIGHT] = a; + b->dad = d; + d->son[LEFT] = b; + a->son[LEFT] = g; + if (g) + g->dad = a; + a->son[RIGHT] = e; + if (e) + e->dad = a; + b->son[LEFT] = c; + if (c) + c->dad = b; + b->son[RIGHT] = f; + if (f) + f->dad = b; + + d->dad = r; + if (r) + { + if (r->son[LEFT] == a) + r->son[LEFT] = d; + if (r->son[RIGHT] == a) + r->son[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 == son[LEFT]) + { + balance = 0; + return avl_no_err; + } + if (son[RIGHT] == NULL) + { +// cout << "mierda\n"; + return avl_bal_err; + } + AVLTree *a = this; + AVLTree *b = son[RIGHT]; + AVLTree *e = son[LEFT]; + AVLTree *c = son[RIGHT]->son[RIGHT]; + AVLTree *d = son[RIGHT]->son[LEFT]; + AVLTree *r = dad; + if (son[RIGHT]->balance < 0) + { + + a->dad = b; + b->son[LEFT] = a; + a->son[LEFT] = e; + if (e) + e->dad = a; + a->son[RIGHT] = d; + if (d) + d->dad = a; + b->son[RIGHT] = c; + if (c) + c->dad = b; + b->dad = r; + if (r) + { + if (r->son[LEFT] == a) + r->son[LEFT] = b; + if (r->son[RIGHT] == a) + r->son[RIGHT] = b; + } + if (racine == a) + racine = b; + a->balance = 0; + b->balance = 0; + return avl_no_err; + } + else + { + if (son[RIGHT]->son[LEFT] == NULL) + { +// cout << "mierda\n"; + return avl_bal_err; + } + AVLTree *f = son[RIGHT]->son[LEFT]->son[RIGHT]; + AVLTree *g = son[RIGHT]->son[LEFT]->son[LEFT]; + + a->dad = d; + d->son[LEFT] = a; + b->dad = d; + d->son[RIGHT] = b; + a->son[RIGHT] = g; + if (g) + g->dad = a; + a->son[LEFT] = e; + if (e) + e->dad = a; + b->son[RIGHT] = c; + if (c) + c->dad = b; + b->son[LEFT] = f; + if (f) + f->dad = b; + + d->dad = r; + if (r) + { + if (r->son[LEFT] == a) + r->son[LEFT] = d; + if (r->son[RIGHT] == a) + r->son[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 (dad) + { + if (this == dad->son[RIGHT]) + return dad->RestoreBalances (1, racine); + if (this == dad->son[LEFT]) + return dad->RestoreBalances (-1, racine); + } + return avl_no_err; + } + else if (diff == 0) + { + } + else if (diff > 0) + { + if (son[LEFT] == NULL) + { +// cout << "un probleme\n"; + return avl_bal_err; + } + AVLTree *r = dad; + AVLTree *a = this; + AVLTree *b = son[RIGHT]; + AVLTree *e = son[LEFT]; + AVLTree *f = e->son[RIGHT]; + AVLTree *g = e->son[LEFT]; + if (e->balance > 0) + { + e->son[RIGHT] = a; + e->son[LEFT] = g; + a->son[RIGHT] = b; + a->son[LEFT] = f; + if (a) + a->dad = e; + if (g) + g->dad = e; + if (b) + b->dad = a; + if (f) + f->dad = a; + e->dad = r; + if (r) + { + if (r->son[LEFT] == a) + r->son[LEFT] = e; + if (r->son[RIGHT] == a) + r->son[RIGHT] = e; + } + if (racine == this) + racine = e; + e->balance = 0; + a->balance = 0; + if (r) + { + if (e == r->son[RIGHT]) + return r->RestoreBalances (1, racine); + if (e == r->son[LEFT]) + return r->RestoreBalances (-1, racine); + } + return avl_no_err; + } + else if (e->balance == 0) + { + e->son[RIGHT] = a; + e->son[LEFT] = g; + a->son[RIGHT] = b; + a->son[LEFT] = f; + if (a) + a->dad = e; + if (g) + g->dad = e; + if (b) + b->dad = a; + if (f) + f->dad = a; + e->dad = r; + if (r) + { + if (r->son[LEFT] == a) + r->son[LEFT] = e; + if (r->son[RIGHT] == a) + r->son[RIGHT] = e; + } + if (racine == this) + racine = e; + e->balance = -1; + a->balance = 1; + return avl_no_err; + } + else if (e->balance < 0) + { + if (son[LEFT]->son[RIGHT] == NULL) + { +// cout << "un probleme\n"; + return avl_bal_err; + } + AVLTree *i = son[LEFT]->son[RIGHT]->son[RIGHT]; + AVLTree *j = son[LEFT]->son[RIGHT]->son[LEFT]; + + f->son[RIGHT] = a; + f->son[LEFT] = e; + a->son[RIGHT] = b; + a->son[LEFT] = i; + e->son[RIGHT] = j; + e->son[LEFT] = g; + if (b) + b->dad = a; + if (i) + i->dad = a; + if (g) + g->dad = e; + if (j) + j->dad = e; + if (a) + a->dad = f; + if (e) + e->dad = f; + f->dad = r; + if (r) + { + if (r->son[LEFT] == a) + r->son[LEFT] = f; + if (r->son[RIGHT] == a) + r->son[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->son[RIGHT]) + return r->RestoreBalances (1, racine); + if (f == r->son[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 (son[RIGHT] == NULL) + { +// cout << "un probleme\n"; + return avl_bal_err; + } + AVLTree *r = dad; + AVLTree *a = this; + AVLTree *b = son[LEFT]; + AVLTree *e = son[RIGHT]; + AVLTree *f = e->son[LEFT]; + AVLTree *g = e->son[RIGHT]; + if (e->balance < 0) + { + e->son[LEFT] = a; + e->son[RIGHT] = g; + a->son[LEFT] = b; + a->son[RIGHT] = f; + if (a) + a->dad = e; + if (g) + g->dad = e; + if (b) + b->dad = a; + if (f) + f->dad = a; + e->dad = r; + if (r) + { + if (r->son[LEFT] == a) + r->son[LEFT] = e; + if (r->son[RIGHT] == a) + r->son[RIGHT] = e; + } + if (racine == this) + racine = e; + e->balance = 0; + a->balance = 0; + if (r) + { + if (e == r->son[RIGHT]) + return r->RestoreBalances (1, racine); + if (e == r->son[LEFT]) + return r->RestoreBalances (-1, racine); + } + return avl_no_err; + } + else if (e->balance == 0) + { + e->son[LEFT] = a; + e->son[RIGHT] = g; + a->son[LEFT] = b; + a->son[RIGHT] = f; + if (a) + a->dad = e; + if (g) + g->dad = e; + if (b) + b->dad = a; + if (f) + f->dad = a; + e->dad = r; + if (r) + { + if (r->son[LEFT] == a) + r->son[LEFT] = e; + if (r->son[RIGHT] == a) + r->son[RIGHT] = e; + } + if (racine == this) + racine = e; + e->balance = 1; + a->balance = -1; + return avl_no_err; + } + else if (e->balance > 0) + { + if (son[RIGHT]->son[LEFT] == NULL) + { +// cout << "un probleme\n"; + return avl_bal_err; + } + AVLTree *i = son[RIGHT]->son[LEFT]->son[LEFT]; + AVLTree *j = son[RIGHT]->son[LEFT]->son[RIGHT]; + + f->son[LEFT] = a; + f->son[RIGHT] = e; + a->son[LEFT] = b; + a->son[RIGHT] = i; + e->son[LEFT] = j; + e->son[RIGHT] = g; + if (b) + b->dad = a; + if (i) + i->dad = a; + if (g) + g->dad = e; + if (j) + j->dad = e; + if (a) + a->dad = f; + if (e) + e->dad = f; + f->dad = r; + if (r) + { + if (r->son[LEFT] == a) + r->son[LEFT] = f; + if (r->son[RIGHT] == a) + r->son[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->son[RIGHT]) + return r->RestoreBalances (1, racine); + if (f == r->son[LEFT]) + return r->RestoreBalances (-1, racine); + } + return avl_no_err; + } + } + else if (diff == 0) + { + } + else if (diff > 0) + { + balance = 0; + if (dad) + { + if (this == dad->son[RIGHT]) + return dad->RestoreBalances (1, racine); + if (this == dad->son[LEFT]) + return dad->RestoreBalances (-1, racine); + } + return avl_no_err; + } + } + return avl_no_err; +} + +/* + * removal + */ +int +AVLTree::Remove (AVLTree * &racine, bool rebalance) +{ + AVLTree *startNode = NULL; + 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] = NULL; + + if (son[LEFT] && son[RIGHT]) + { + AVLTree *newMe = son[LEFT]->leafFromDad(this, RIGHT); + if (newMe == NULL || newMe->son[RIGHT]) + { +// cout << "pas normal\n"; + return avl_rm_err; + } + if (newMe == son[LEFT]) + { + startNode = newMe; + diff = -1; + newMe->son[RIGHT] = son[RIGHT]; + son[RIGHT]->dad = newMe; + newMe->dad = dad; + if (dad) + { + if (dad->son[LEFT] == this) + dad->son[LEFT] = newMe; + if (dad->son[RIGHT] == this) + dad->son[RIGHT] = newMe; + } + } + else + { + AVLTree *oDad = newMe->dad; + startNode = oDad; + diff = 1; + + oDad->son[RIGHT] = newMe->son[LEFT]; + if (newMe->son[LEFT]) + newMe->son[LEFT]->dad = oDad; + + newMe->dad = dad; + newMe->son[LEFT] = son[LEFT]; + newMe->son[RIGHT] = son[RIGHT]; + if (dad) + { + if (dad->son[LEFT] == this) + dad->son[LEFT] = newMe; + if (dad->son[RIGHT] == this) + dad->son[RIGHT] = newMe; + } + if (son[LEFT]) + son[LEFT]->dad = newMe; + if (son[RIGHT]) + son[RIGHT]->dad = newMe; + } + newMe->balance = balance; + if (racine == this) + racine = newMe; + } + else if (son[LEFT]) + { + startNode = dad; + diff = 0; + if (dad) + { + if (this == dad->son[LEFT]) + diff = -1; + if (this == dad->son[RIGHT]) + diff = 1; + } + if (dad) + { + if (dad->son[LEFT] == this) + dad->son[LEFT] = son[LEFT]; + if (dad->son[RIGHT] == this) + dad->son[RIGHT] = son[LEFT]; + } + if (son[LEFT]->dad == this) + son[LEFT]->dad = dad; + if (racine == this) + racine = son[LEFT]; + } + else if (son[RIGHT]) + { + startNode = dad; + diff = 0; + if (dad) + { + if (this == dad->son[LEFT]) + diff = -1; + if (this == dad->son[RIGHT]) + diff = 1; + } + if (dad) + { + if (dad->son[LEFT] == this) + dad->son[LEFT] = son[RIGHT]; + if (dad->son[RIGHT] == this) + dad->son[RIGHT] = son[RIGHT]; + } + if (son[RIGHT]->dad == this) + son[RIGHT]->dad = dad; + if (racine == this) + racine = son[RIGHT]; + } + else + { + startNode = dad; + diff = 0; + if (dad) + { + if (this == dad->son[LEFT]) + diff = -1; + if (this == dad->son[RIGHT]) + diff = 1; + } + if (dad) + { + if (dad->son[LEFT] == this) + dad->son[LEFT] = NULL; + if (dad->son[RIGHT] == this) + dad->son[RIGHT] = NULL; + } + if (racine == this) + racine = NULL; + } + dad = son[RIGHT] = son[LEFT] = NULL; + 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 *) NULL, racine); + return res; +} + +int +AVLTree::Insert (AVLTree * &racine, int insertType, AVLTree * insertL, + AVLTree * insertR) +{ + if (racine == NULL) + { + 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 == NULL || insertR->son[LEFT]) + { +// cout << "ngou?\n"; + return avl_ins_err; + } + insertR->son[LEFT] = this; + dad = insertR; + insertOn(LEFT, insertR); + } + else if (insertType == found_on_right) + { + if (insertL == NULL || insertL->son[RIGHT]) + { +// cout << "ngou?\n"; + return avl_ins_err; + } + insertL->son[RIGHT] = this; + dad = insertL; + insertOn(RIGHT, insertL); + } + else if (insertType == found_between) + { + if (insertR == NULL || insertL == NULL + || (insertR->son[LEFT] != NULL && insertL->son[RIGHT] != NULL)) + { +// cout << "ngou?\n"; + return avl_ins_err; + } + if (insertR->son[LEFT] == NULL) + { + insertR->son[LEFT] = this; + dad = insertR; + } + else if (insertL->son[RIGHT] == NULL) + { + insertL->son[RIGHT] = this; + dad = insertL; + } + insertBetween (insertL, insertR); + } + else if (insertType == found_exact) + { + if (insertL == NULL) + { +// cout << "ngou?\n"; + return avl_ins_err; + } + // et on insere + + if (insertL->son[RIGHT]) + { + insertL = insertL->son[RIGHT]->leafFromDad(insertL, LEFT); + if (insertL->son[LEFT]) + { +// cout << "ngou?\n"; + return avl_ins_err; + } + insertL->son[LEFT] = this; + this->dad = insertL; + insertBetween (insertL->elem[LEFT], insertL); + } + else + { + insertL->son[RIGHT] = this; + dad = 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 (dad) + { + if (dad->son[LEFT] == this) + dad->son[LEFT] = to; + if (dad->son[RIGHT] == this) + dad->son[RIGHT] = to; + } + if (son[RIGHT]) + { + son[RIGHT]->dad = to; + } + if (son[LEFT]) + { + son[LEFT]->dad = to; + } + to->dad = dad; + to->son[RIGHT] = son[RIGHT]; + to->son[LEFT] = son[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:encoding=utf-8:textwidth=99 : diff --git a/src/livarot/AVL.h b/src/livarot/AVL.h new file mode 100644 index 000000000..257c39c72 --- /dev/null +++ b/src/livarot/AVL.h @@ -0,0 +1,95 @@ +/* + * AVL.h + * nlivarot + * + * Created by fred on Mon Jun 16 2003. + * + */ + +#ifndef my_avl +#define my_avl + +#include +#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 *son[2]; + + AVLTree(); + ~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 *dad; + + 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 *leafFromDad(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:encoding=utf-8:textwidth=99 : diff --git a/src/livarot/AlphaLigne.cpp b/src/livarot/AlphaLigne.cpp new file mode 100644 index 000000000..fa128e1a7 --- /dev/null +++ b/src/livarot/AlphaLigne.cpp @@ -0,0 +1,304 @@ +/* + * AlphaLigne.cpp + * nlivarot + * + * Created by fred on Fri Jul 25 2003. + * public domain + * + */ + +#include "AlphaLigne.h" +//#include "Buffer.h" + +#include +#include + +AlphaLigne::AlphaLigne(int iMin,int iMax) +{ + min=iMin; + max=iMax; + if ( max < min+1 ) max=min+1; + steps=NULL; + nbStep=maxStep=0; + before.x=min-1; + before.delta=0; + after.x=max+1; + after.delta=0; +} +AlphaLigne::~AlphaLigne(void) +{ + g_free(steps); + steps=NULL; + nbStep=maxStep=0; +} +void AlphaLigne::Affiche(void) +{ + printf("%i steps\n",nbStep); + for (int i=0;i %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= 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 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= 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 ) { + 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(void) +{ + // 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 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 000000000..b2686f6ba --- /dev/null +++ b/src/livarot/AlphaLigne.h @@ -0,0 +1,90 @@ +/* + * AlphaLigne.h + * nlivarot + * + * Created by fred on Fri Jul 25 2003. + * public domain + * + */ + +#ifndef my_alpha_ligne +#define my_alpha_ligne + +#include +#include +#include +#include +//#include + +#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 +typedef struct alpha_step { + int x; // position + float delta; // increase or decrease in pixel coverage with respect to the coverage of the previous pixel +} alpha_step; + + +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); + ~AlphaLigne(void); + + // empties the line + void Reset(void); + + // 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(void); + + // debug dump of the steps + void Affiche(void); + + // 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 000000000..52870f699 --- /dev/null +++ b/src/livarot/BitLigne.cpp @@ -0,0 +1,172 @@ +/* + * BitLigne.cpp + * nlivarot + * + * Created by fred on Wed Jul 23 2003. + * public domain + * + */ + +#include "BitLigne.h" + +#include +#include + +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(void) +{ + g_free(fullB); + g_free(partB); +} + +void BitLigne::Reset(void) +{ + 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(void) +{ + for (int i=0;i +#include +#include +#include + +#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 + ~BitLigne(void); + + // reset the line to full empty + void Reset(void); + + // 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(void); + +}; + +#endif + + diff --git a/src/livarot/Livarot.h b/src/livarot/Livarot.h new file mode 100644 index 000000000..0218e0127 --- /dev/null +++ b/src/livarot/Livarot.h @@ -0,0 +1,34 @@ +/* + * Livarot.h + * nlivarot + * + * Created by fred on Sun Jul 27 2003. + * + */ + +#include "LivarotDefs.h" + +#include "Shape.h" +#include "Path.h" +#include "Buffer.h" + +#include "Ligne.h" +#include "AlphaLigne.h" +#include "BitLigne.h" + +#include "Bounding.h" +#include "Region.h" + +#include "VoronoiGraph.h" +#include "VoronoiConstr.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:encoding=utf-8:textwidth=99 : diff --git a/src/livarot/LivarotDefs.h b/src/livarot/LivarotDefs.h new file mode 100644 index 000000000..bdb50b563 --- /dev/null +++ b/src/livarot/LivarotDefs.h @@ -0,0 +1,170 @@ +/* + * LivarotDefs.h + * nlivarot + * + * Created by fred on Tue Jun 17 2003. + * + */ + +#ifndef my_defs +#define my_defs +#include + +// 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) +}; + +// return codes for the find function in the AVL tree (private) +enum +{ + not_found = 0, + found_exact = 1, + found_on_left = 2, + found_on_right = 3, + found_between = 4 +}; + +// boolean operation +enum bool_op +{ + bool_op_union, // A OR B + bool_op_inters, // A AND B + bool_op_diff, // A \ B + bool_op_symdiff, // A XOR B + bool_op_cut, // coupure (pleines) + bool_op_slice // coupure (contour) +}; +typedef enum bool_op BooleanOp; + +// 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; + +// stupid version of dashes: in dash x is plain, dash x+1 must be empty, so the gap field is extremely redundant +typedef struct one_dash +{ + bool gap; + double length; +} +one_dash; + +// color definition structures for the rasterizations primitives (not present here) +typedef struct std_color +{ + uint32_t uCol; + uint16_t iColA, iColR, iColG, iColB; + double fColA, fColR, fColG, fColB; + uint32_t iColATab[256]; +} +std_color; + +typedef struct grad_stop +{ + double at; + double ca, cr, cg, cb; + double iSize; +} +grad_stop; + +// linear gradient for filling polygons +typedef struct lin_grad +{ + int type; // 0= gradient appears once + // 1= repeats itself start-end/start-end/start-end... + // 2= repeats itself start-end/end-start/start-end... + double u, v, w; // u*x+v*y+w = position in the gradient (clipped to [0;1]) +// double caa,car,cag,cab; // color at gradient position 0 +// double cba,cbr,cbg,cbb; // color at gradient position 1 + int nbStop; + grad_stop stops[2]; +} +lin_grad; + +// radial gradient (color is funciton of r^2, need to be corrected with a sqrt() to be r) +typedef struct rad_grad +{ + int type; // 0= gradient appears once + // 1= repeats itself start-end/start-end/start-end... + // 2= repeats itself start-end/end-start/start-end... + double mh, mv; // center + double rxx, rxy, ryx, ryy; // 1/radius + int nbStop; + grad_stop stops[2]; +} +rad_grad; + +// functions types for an arbitrary filling shader +typedef void (*InitColorFunc) (int ph, int pv, void *); // init for position ph,pv; the last parameter is a pointer + // on the gen_color structure +typedef void (*NextPixelColorFunc) (void *); // go to next pixel and update the color +typedef void (*NextLigneColorFunc) (void *); // go to next line (the h-coordinate must be the ph passed in + // the InitColorFunc) +typedef void (*GotoPixelColorFunc) (int ph, void *); // move to h-coordinate ph +typedef void (*GotoLigneColorFunc) (int pv, void *); // move to v-coordinate pv (the h-coordinate must be the ph passed + // in the InitColorFunc) + +// an arbitrary shader +typedef struct gen_color +{ + double colA, colR, colG, colB; + InitColorFunc iFunc; + NextPixelColorFunc npFunc; + NextLigneColorFunc nlFunc; + GotoPixelColorFunc gpFunc; + GotoLigneColorFunc glFunc; +} +gen_color; + +// info for a run of pixel to fill +typedef 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 +} raster_info; +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/Makefile_insert b/src/livarot/Makefile_insert new file mode 100644 index 000000000..6982d0a98 --- /dev/null +++ b/src/livarot/Makefile_insert @@ -0,0 +1,45 @@ +## Makefile.am fragment sourced by src/Makefile.am. + +livarot/all: livarot/libvarot.a + +livarot/clean: + rm -f livarot/libvarot.a $(livarot_libvarot_a_OBJECTS) + +livarot_libvarot_a_SOURCES = \ + livarot/AVL.cpp \ + livarot/AVL.h \ + livarot/AlphaLigne.cpp \ + livarot/AlphaLigne.h \ + livarot/BitLigne.cpp \ + livarot/BitLigne.h \ + livarot/float-line.cpp \ + livarot/float-line.h \ + livarot/int-line.cpp \ + livarot/int-line.h \ + livarot/LivarotDefs.h \ + livarot/MyMath.h \ + livarot/MySeg.cpp \ + livarot/MySeg.h \ + livarot/Path.cpp \ + livarot/Path.h \ + livarot/PathConversion.cpp \ + livarot/PathCutting.cpp \ + livarot/PathOutline.cpp \ + livarot/PathSimplify.cpp \ + livarot/PathStroke.cpp \ + livarot/Shape.cpp \ + livarot/Shape.h \ + livarot/ShapeDraw.cpp \ + livarot/ShapeMisc.cpp \ + livarot/ShapeRaster.cpp \ + livarot/ShapeSweep.cpp \ + livarot/sweep-tree-list.cpp \ + livarot/sweep-tree-list.h \ + livarot/sweep-tree.cpp \ + livarot/sweep-tree.h \ + livarot/sweep-event.cpp \ + livarot/sweep-event.h \ + livarot/sweep-event-queue.h \ + livarot/livarot-forward.h \ + livarot/path-description.h \ + livarot/path-description.cpp diff --git a/src/livarot/MyMath.h b/src/livarot/MyMath.h new file mode 100644 index 000000000..27e8089ec --- /dev/null +++ b/src/livarot/MyMath.h @@ -0,0 +1,281 @@ +/* + * MyMath.h + * nlivarot + * + * Created by fred on Wed Jun 18 2003. + * + */ + +#ifndef my_math +#define my_math + +#include +#include +#include +#include +//#include + + +typedef struct vec2 +{ + double x, y; +} vec2; + + +typedef struct mat2 +{ + double xx, xy, yx, yy; +} mat2; + + +typedef struct vec2d +{ + double x, y; +} vec2d; + + +typedef struct mat2d +{ + double xx, xy, yx, yy; +} mat2d; + + +#define RotCCW(a) {\ + double t = (a).x;\ + (a).x = (a).y;\ + (a).y = -t;\ +} + +#define RotCCWTo(a,d) {\ + (d).x = (a).y;\ + (d).y = -(a).x;\ +} + +#define RotCW(a) {\ + double t = (a).x;\ + (a).x = -(a).y;\ + (a).y = t;\ +} + +#define RotCWTo(a,d) {\ + (d).x = -(a).y;\ + (d).y = (a).x;\ +} + +#define Normalize(a) { \ + double _le = (a).x*(a).x+(a).y*(a).y; \ + if ( _le > 0.0001 ) { \ + _le = 1.0 / sqrt(_le); \ + (a).x *= _le; \ + (a).y *= _le; \ + } \ +} + +#define L_VEC_Set(a,u,v) { \ + a.x = u; \ + a.y = v; \ +} + + +#define L_VEC_Length(a,l) { \ + l = sqrt(a.x*a.x+a.y*a.y); \ +} + +#define L_VEC_Add(a,b,r) { \ + r.x = a.x+b.x; \ + r.y = a.y+b.y; \ +} + +#define L_VEC_Sub(a,b,r) { \ + r.x = a.x-b.x; \ + r.y = a.y-b.y; \ +} + +#define L_VEC_Mul(a,b,r) { \ + r.x = a.x*b.x; \ + r.y = a.y*b.y; \ +} + +#define L_VEC_Div(a,b,r) { \ + r.x = a.x/b.x; \ + r.y = a.y/b.y; \ +} + +#define L_VEC_AddMul(a,b,c,r) { \ + r.x = a.x+b.x*c.x; \ + r.y = a.y+b.y*c.y; \ +} + +#define L_VEC_SubMul(a,b,c,r) { \ + r.x = a.x-b.x*c.x; \ + r.y = a.y-b.y*c.y; \ +} + + +#define L_VEC_MulC(a,b,r) { \ + r.x = a.x*(b); \ + r.y = a.y*(b); \ +} + +#define L_VEC_DivC(a,b,r) { \ + r.x = a.x/(b); \ + r.y = a.y/(b); \ +} + +#define L_VEC_AddMulC(a,b,c,r) { \ + r.x = a.x+b.x*c; \ + r.y = a.y+b.y*c; \ +} + +#define L_VEC_SubMulC(a,b,c,r) { \ + r.x = a.x-b.x*c; \ + r.y = a.y-b.y*c; \ +} + +#define L_VEC_Cmp(a,b) ((fabs(a.y-b.y)<0.0000001)? \ + ((fabs(a.x-b.x)<0.0000001)?0:((a.x > b.x)?1:-1)): \ + ((a.y > b.y)?1:-1)) + +#define L_VAL_Cmp(a,b) ((fabs(a-b)<0.0000001)?0:((a>b)?1:-1)) + +#define L_VEC_Normalize(d) { \ + double l=sqrt(d.x*d.x+d.y*d.y); \ + if ( l < 0.00000001 ) { \ + d.x=d.y=0; \ + } else { \ + d.x/=l; \ + d.y/=l; \ + } \ +} + +#define L_VEC_Distance(a,b,d) { \ + double dx = a.x-b.x; \ + double dy = a.y-b.y; \ + d = sqrt(dx*dx + dy*dy); \ +} + +#define L_VEC_Neg(d) { \ + d.x=d.x; d.y=-d.y; \ +} + +#define L_VEC_RotCW(d) { \ + double t=d.x; d.x=d.y; d.y=-t; \ +} \ + +#define L_VEC_RotCCW(d) { \ + double t=d.x; d.x=-d.y; d.y=t; \ +} + +#define L_VAL_Zero(a) ((fabs(a)<0.00000001)?0:((a>0)?1:-1)) + +#define L_VEC_Cross(a,b,r) { \ + r = a.x*b.x+a.y*b.y; \ +} + +#define L_VEC_Dot(a,b,r) { \ + r = a.x*b.y-a.y*b.x; \ +} + + +#define L_MAT(m,a,b) { \ + c[0][0].Set(ica.x); c[0][1].Set(icb.x); c[1][0].Set(ica.y); c[1][1].Set(icb.y); \ +} + +#define L_MAT_Set(m,a00,a10,a01,a11) {m.xx = a00; m.xy = a01; m.yx = a10; m.yy = a11;} + +#define L_MAT_SetC(m,a,b) {m.xx = a.x; m.xy = b.x; m.yx = a.y; m.yy = b.y;} + +#define L_MAT_SetL(m,a,b) {m.xx = a.x; m.xy = a.y;m.yx = b.x; m.yy = b.y;} + +#define L_MAT_Init(m) {m.xx=m.xy=m.yx=m.yy=0;} + +#define L_MAT_Col(m,no,r) { \ + if ( no == 0 ) { \ + r.x = m.xx; \ + r.y = m.yx; \ + } \ + if ( no == 0 ) { \ + r.x = m.xy; \ + r.y = m.yy; \ + } \ +} + +#define L_MAT_Row(m,no,r) { \ + if ( no == 0 ) { \ + r.x = m.xx; \ + r.y = m.xy; \ + } \ + if ( no == 0 ) { \ + r.x = m.yx; \ + r.y = m.yy; \ + } \ +} + +#define L_MAT_Det(m,d) {d=m.xx*m.yy-m.xy*m.yx;} + +#define L_MAT_Neg(m) {m.xx=-m.xx; m.xy=-m.xy; m.yx=-m.yx; m.yy=-m.yy;} + +#define L_MAT_Trs(m) {double t=m.xy; m.xy=m.yx; m.yx=t;} + +#define L_MAT_Inv(m) { \ + double d; \ + L_MAT_Det(m,d); \ + m.yx =- m.yx; \ + m.xy =- m.xy; \ + double t=m.xx;m.xx=m.yy;m.yy=t; \ + double inv_d = 1.0/d; \ + m.xx *= inv_d; \ + m.xy *= inv_d; \ + m.yx *= inv_d; \ + m.yy *= inv_d; \ +} + +#define L_MAT_Cof(m) { \ + m.yx =- m.yx; \ + m.xy =- m.xy; \ + double t=m.xx; m.xx=m.yy; m.yy=t; \ +} + +#define L_MAT_Add(u,v,m) { \ + m.xx=u.xx+v.xx; m.xy=u.xy+v.xy; m.yx=u.yx+v.yx; m.yy=u.yy+v.yy; \ +} + +#define L_MAT_Sub(u,v,m) { \ + m.xx=u.xx-v.xx; m.xy=u.xy-v.xy; m.yx=u.yx-v.yx; m.yy=u.yy-v.yy; \ +} + +#define L_MAT_Mul(u,v,m) { \ + mat2d r; \ + r.xx = u.xx*v.xx+u.xy*v.yx; \ + r.yx = u.yx*v.xx+u.yy*y.yx; \ + r.xy = u.xx*v.xy+u.xy*v.yy; \ + r.yy = u.yx*v.xy+u.yy*v.yy; \ + m=r; \ +} + +#define L_MAT_MulC(u,v,m) { \ + m.xx=u.xx*v; m.xy=u.xy*v; m.yx=u.yx*v; m.yy=u.yy*v; \ +} + +#define L_MAT_DivC(u,v,m) { \ + double iv = 1.0/v; \ + m.xx = u.xx*iv; m.xy=u.xy*iv; m.yx=u.yx*iv; m.yy=u.yy*iv; \ +} + +#define L_MAT_MulV(m,v,r) { \ + vec2d t; \ + t.x = m.xx*v.x+m.xy*v.y; \ + t.y = m.yx*v.x+m.yy*v.y; \ + r=t; \ +} + +#define L_MAT_TMulV(m,v,r) { \ + vec2d t; \ + t.x = m.xx*v.x+m.yx*v.y; \ + t.y = m.xy*v.x+m.yy*v.y; \ + r=t; \ +} + + + +#endif diff --git a/src/livarot/MySeg.cpp b/src/livarot/MySeg.cpp new file mode 100644 index 000000000..4a2d58dfd --- /dev/null +++ b/src/livarot/MySeg.cpp @@ -0,0 +1,867 @@ +/* + * MySeg.cpp + * nlivarot + * + * Created by fred on Wed Nov 12 2003. + * Copyright (c) 2003 __MyCompanyName__. All rights reserved. + * + */ + +#include "MySeg.h" +#include + +void +L_SEG::Distance (L_SEG & is, double &di, int mode) +{ + if (mode == inters_seg_seg) + { + double ms, me, os, oe; + + vec2d en = is.p; + L_VEC_Add (en, is.d, en); + + Distance (is.p, os, inters_seg_pt); + Distance (en, oe, inters_seg_pt); + + en = p; + L_VEC_Add (en, d, en); + + is.Distance (p, ms, inters_orseg_pt); + is.Distance (en, me, inters_orseg_pt); + if (L_VAL_Zero (ms) < 0 || L_VAL_Zero (me) < 0) + { + di = 1000000; + return; + } + if (L_VAL_Cmp (oe, os) < 0) + os = oe; + if (L_VAL_Cmp (me, ms) < 0) + ms = me; + if (L_VAL_Cmp (ms, os) < 0) + os = ms; + di = os; + } + else if (mode == inters_seg_dmd) + { + } + else if (mode == inters_seg_dr) + { + } +} + +void +L_SEG::Distance (vec2d & iv, double &di, int mode) +{ + if (L_VAL_Zero (d.x) == 0 && L_VAL_Zero (d.y) == 0) + { + L_VEC_Distance (p, iv, di); + return; + } + double dd, sqd; + vec2d nd = d; + L_VEC_RotCW (nd); + L_VEC_Cross (nd, nd, dd); + sqd = sqrt (dd); + vec2d diff = iv; + L_VEC_Sub (diff, p, diff); + if (mode == inters_dr_pt) + { + double cp; + L_VEC_Cross (diff, nd, cp); + di = cp / sqd; + if (di < 0) + di = -di; + } + else if (mode == inters_dmd_pt) + { + double cp; + L_VEC_Cross (diff, d, cp); + if (cp < 0) + { + L_VEC_Distance (p, iv, di); + return; + } + L_VEC_Cross (diff, nd, cp); + di = cp / sqd; + if (di < 0) + di = -di; + } + else if (mode == inters_seg_pt) + { + double cp; + L_VEC_Cross (diff, d, cp); + if (cp < 0) + { + L_VEC_Distance (p, iv, di); + return; + } + if (cp > dd) + { + vec2d se = p; + L_VEC_Add (se, d, se); + L_VEC_Distance (se, iv, di); + return; + } + L_VEC_Cross (diff, nd, cp); + di = cp / sqd; + if (di < 0) + di = -di; + } + else if (mode == inters_orseg_pt) + { + double cp; + L_VEC_Cross (diff, d, cp); + if (cp < 0) + { + L_VEC_Distance (p, iv, di); + L_VEC_Dot (diff, d, cp); + if (L_VAL_Zero (cp) < 0) + di = -di; + return; + } + if (cp > dd) + { + vec2d se = p; + L_VEC_Add (se, d, se); + L_VEC_Distance (se, iv, di); + + L_VEC_Dot (diff, d, cp); + if (cp < 0) + di = -di; + return; + } + L_VEC_Cross (diff, nd, cp); + di = cp / sqd; +// if ( diL_VAL_Zero() < 0 ) di.Neg(); + } +} + +int +L_SEG::Intersect (L_SEG & iu, L_SEG & iv, int mode) +{ + double iudd, ivdd; + L_VEC_Cross (iu.d, iu.d, iudd); + L_VEC_Cross (iv.d, iv.d, ivdd); + if (L_VAL_Zero (iudd) <= 0) + return 0; // cas illicite + if (L_VAL_Zero (ivdd) <= 0) + return 0; // cas illicite + + vec2d usvs, uevs, usve, ueve; + L_VEC_Sub (iv.p, iu.p, usvs); + L_VEC_Sub (usvs, iu.d, uevs); + L_VEC_Add (usvs, iv.d, usve); + L_VEC_Sub (usve, iu.d, ueve); + double usvsl, uevsl, usvel, uevel; + L_VEC_Cross (usvs, usvs, usvsl); + L_VEC_Cross (uevs, uevs, uevsl); + L_VEC_Cross (usve, usve, usvel); + L_VEC_Cross (ueve, ueve, uevel); + + double dd; + L_VEC_Cross (iu.d, iv.d, dd); + + if (L_VAL_Zero (usvsl) <= 0) + { + if (mode == inters_dr_dr || mode == inters_dmd_dr + || mode == inters_seg_dr) + { + return inters_colinear + inters_a_st + inters_b_st; + } + else if (mode == inters_dmd_dmd || mode == inters_seg_dmd + || mode == inters_seg_seg) + { + if (L_VAL_Zero (dd) > 0) + { + return inters_colinear + inters_a_st + inters_b_st; + } + else + { + return inters_a_st + inters_b_st; + } + } + return 0; + } + if (L_VAL_Zero (uevsl) <= 0) + { + if (mode == inters_dr_dr || mode == inters_dmd_dr + || mode == inters_seg_dr) + { + return inters_colinear + inters_a_en + inters_b_st; + } + else if (mode == inters_dmd_dmd) + { + return inters_colinear + inters_a_en + inters_b_st; + } + else if (mode == inters_seg_dmd || mode == inters_seg_seg) + { + if (L_VAL_Zero (dd) > 0) + { + return inters_a_en + inters_b_st; + } + else + { + return inters_colinear + inters_a_en + inters_b_st; + } + } + return 0; + } + if (L_VAL_Zero (usvel) <= 0) + { + if (mode == inters_dr_dr || mode == inters_dmd_dr + || mode == inters_seg_dr) + { + return inters_colinear + inters_a_st + inters_b_en; + } + else if (mode == inters_dmd_dmd || mode == inters_seg_dmd) + { + return inters_colinear + inters_a_st + inters_b_en; + } + else if (mode == inters_seg_seg) + { + if (L_VAL_Zero (dd) > 0) + { + return inters_a_st + inters_b_en; + } + else + { + return inters_colinear + inters_a_st + inters_b_en; + } + } + return 0; + } + if (L_VAL_Zero (uevel) <= 0) + { + if (mode == inters_dr_dr || mode == inters_dmd_dr + || mode == inters_seg_dr) + { + return inters_colinear + inters_a_en + inters_b_en; + } + else if (mode == inters_dmd_dmd || mode == inters_seg_dmd) + { + return inters_colinear + inters_a_en + inters_b_en; + } + else if (mode == inters_seg_seg) + { + if (L_VAL_Zero (dd) > 0) + { + return inters_colinear + inters_a_en + inters_b_en; + } + else + { + return inters_a_en + inters_b_en; + } + } + return 0; + } + + // plus d'extremites en commun a partir de ce point + + mat2d m; + L_MAT_SetC (m, iu.d, iv.d); + double det; + L_MAT_Det (m, det); + + if (L_VAL_Zero (det) == 0) + { // ces couillons de vecteurs sont colineaires + vec2d iudp; + iudp.x = iu.d.y; + iudp.y = -iu.d.x; + double dist; + L_VEC_Cross (iudp, usvs, dist); + if (L_VAL_Zero (dist) == 0) + { + if (mode == inters_dr_dr || mode == inters_dmd_dr + || mode == inters_seg_dr) + { + return inters_colinear; + } + else if (mode == inters_dmd_dmd) + { + if (L_VAL_Zero (dd) > 0) + return inters_colinear; + double ts; + L_VEC_Cross (iu.d, usvs, ts); + if (L_VAL_Zero (ts) > 0) + return inters_colinear; + return 0; + } + else if (mode == inters_seg_dmd) + { + if (L_VAL_Zero (dd) > 0) + { + double ts; + L_VEC_Cross (iv.d, uevs, ts); + if (L_VAL_Zero (ts) < 0) + return inters_colinear; + return 0; + } + else + { + double ts; + L_VEC_Cross (iv.d, usvs, ts); + if (L_VAL_Zero (ts) < 0) + return inters_colinear; + return 0; + } + } + else if (mode == inters_seg_seg) + { + double ts, te; + L_VEC_Cross (iu.d, usvs, ts); + L_VEC_Cross (iu.d, uevs, te); + if (L_VAL_Zero (ts) > 0 && L_VAL_Zero (te) < 0) + return inters_colinear; + L_VEC_Cross (iu.d, usve, ts); + L_VEC_Cross (iu.d, ueve, te); + if (L_VAL_Zero (ts) > 0 && L_VAL_Zero (te) < 0) + return inters_colinear; + L_VEC_Cross (iv.d, usvs, ts); + L_VEC_Cross (iv.d, usve, te); + if (L_VAL_Zero (ts) < 0 && L_VAL_Zero (te) > 0) + return inters_colinear; + L_VEC_Cross (iv.d, uevs, ts); + L_VEC_Cross (iv.d, ueve, te); + if (L_VAL_Zero (ts) < 0 && L_VAL_Zero (te) > 0) + return inters_colinear; + return 0; + } + } + else + { + return 0; // paralleles + } + } + + // plus de colinearite ni d'extremites en commun + L_MAT_Inv (m); + vec2d res; + L_MAT_MulV (m, usvs, res); + + if (mode == inters_dr_dr) + { + return inters_a_mi + inters_b_mi; + } + else if (mode == inters_dmd_dr) + { + int i = L_VAL_Zero (res.x); + if (i == 0) + return inters_a_st + inters_b_mi; + if (i > 0) + return inters_a_mi + inters_b_mi; + return 0; + } + else if (mode == inters_seg_dr) + { + int i = L_VAL_Zero (res.x); + int j = L_VAL_Cmp (res.x, 1); + if (i == 0) + return inters_a_st + inters_b_mi; + if (j == 0) + return inters_a_en + inters_b_mi; + if (i > 0 && j < 0) + return inters_a_mi + inters_b_mi; + return 0; + } + else if (mode == inters_dmd_dmd) + { + int i = L_VAL_Zero (res.x); + int j = -(L_VAL_Zero (res.y)); + // nota : i=0 et j=0 a ete elimine au debut + if (i == 0 && j > 0) + return inters_a_st + inters_b_mi; + if (j == 0 && i > 0) + return inters_a_mi + inters_b_st; + if (i > 0 && j > 0) + return inters_a_mi + inters_b_mi; + return 0; + } + else if (mode == inters_seg_dmd) + { + int i = L_VAL_Zero (res.x); + int j = L_VAL_Cmp (res.x, 1); + int k = -(L_VAL_Zero (res.y)); + // nota : i=0 et j=0 a ete elimine au debut + if (i == 0 && k > 0) + return inters_a_st + inters_b_mi; + if (j == 0 && k > 0) + return inters_a_en + inters_b_mi; + if (i > 0 && j < 0 && k == 0) + return inters_a_mi + inters_b_st; + if (i > 0 && j < 0 && k > 0) + return inters_a_mi + inters_b_mi; + return 0; + } + else if (mode == inters_seg_seg) + { + int i = L_VAL_Zero (res.x); + int j = L_VAL_Cmp (res.x, 1); + int k = -(L_VAL_Zero (res.y)); + int l = -(L_VAL_Cmp (res.y, 1)); + // nota : i=0 et j=0 a ete elimine au debut + if (i == 0 && k > 0 && l < 0) + return inters_a_st + inters_b_mi; + if (j == 0 && k > 0 && l < 0) + return inters_a_en + inters_b_mi; + if (k == 0 && i > 0 && j < 0) + return inters_a_mi + inters_b_st; + if (l == 0 && i > 0 && j < 0) + return inters_a_mi + inters_b_en; + if (k > 0 && l < 0 && i > 0 && j < 0) + return inters_a_mi + inters_b_mi; + return 0; + } + + return 0; +} + +int +L_SEG::Intersect (L_SEG & iu, L_SEG & iv, vec2d & at, int mode) +{ + double iudd, ivdd; + L_VEC_Cross (iu.d, iu.d, iudd); + L_VEC_Cross (iv.d, iv.d, ivdd); + if (L_VAL_Zero (iudd) <= 0) + return 0; // cas illicite + if (L_VAL_Zero (ivdd) <= 0) + return 0; // cas illicite + + vec2d usvs, uevs, usve, ueve; + L_VEC_Sub (iv.p, iu.p, usvs); + L_VEC_Sub (usvs, iu.d, uevs); + L_VEC_Add (usvs, iv.d, usve); + L_VEC_Sub (usve, iu.d, ueve); + double usvsl, uevsl, usvel, uevel; + L_VEC_Cross (usvs, usvs, usvsl); + L_VEC_Cross (uevs, uevs, uevsl); + L_VEC_Cross (usve, usve, usvel); + L_VEC_Cross (ueve, ueve, uevel); + + double dd; + L_VEC_Cross (iu.d, iv.d, dd); + + if (L_VAL_Zero (usvsl) <= 0) + { + at = iu.p; + if (mode == inters_dr_dr || mode == inters_dmd_dr + || mode == inters_seg_dr) + { + return inters_colinear + inters_a_st + inters_b_st; + } + else if (mode == inters_dmd_dmd || mode == inters_seg_dmd + || mode == inters_seg_seg) + { + if (L_VAL_Zero (dd) > 0) + { + return inters_colinear + inters_a_st + inters_b_st; + } + else + { + return inters_a_st + inters_b_st; + } + } + return 0; + } + if (L_VAL_Zero (uevsl) <= 0) + { + at = iv.p; + if (mode == inters_dr_dr || mode == inters_dmd_dr + || mode == inters_seg_dr) + { + return inters_colinear + inters_a_en + inters_b_st; + } + else if (mode == inters_dmd_dmd) + { + return inters_colinear + inters_a_en + inters_b_st; + } + else if (mode == inters_seg_dmd || mode == inters_seg_seg) + { + if (L_VAL_Zero (dd) > 0) + { + return inters_a_en + inters_b_st; + } + else + { + return inters_colinear + inters_a_en + inters_b_st; + } + } + return 0; + } + if (L_VAL_Zero (usvel) <= 0) + { + at = iu.p; + if (mode == inters_dr_dr || mode == inters_dmd_dr + || mode == inters_seg_dr) + { + return inters_colinear + inters_a_st + inters_b_en; + } + else if (mode == inters_dmd_dmd || mode == inters_seg_dmd) + { + return inters_colinear + inters_a_st + inters_b_en; + } + else if (mode == inters_seg_seg) + { + if (L_VAL_Zero (dd) > 0) + { + return inters_a_st + inters_b_en; + } + else + { + return inters_colinear + inters_a_st + inters_b_en; + } + } + return 0; + } + if (L_VAL_Zero (uevel) <= 0) + { + at = iu.p; + L_VEC_Add (at, iu.d, at); + if (mode == inters_dr_dr || mode == inters_dmd_dr + || mode == inters_seg_dr) + { + return inters_colinear + inters_a_en + inters_b_en; + } + else if (mode == inters_dmd_dmd || mode == inters_seg_dmd) + { + return inters_colinear + inters_a_en + inters_b_en; + } + else if (mode == inters_seg_seg) + { + if (L_VAL_Zero (dd) > 0) + { + return inters_colinear + inters_a_en + inters_b_en; + } + else + { + return inters_a_en + inters_b_en; + } + } + return 0; + } + + // plus d'extremites en commun a partir de ce point + + mat2d m; + L_MAT_SetC (m, iu.d, iv.d); + double det; + L_MAT_Det (m, det); + + if (L_VAL_Zero (det) == 0) + { // ces couillons de vecteurs sont colineaires + vec2d iudp; + iudp.x = iu.d.y; + iudp.y = -iu.d.x; + double dist; + L_VEC_Cross (iudp, usvs, dist); + if (L_VAL_Zero (dist) == 0) + { + if (mode == inters_dr_dr || mode == inters_dmd_dr + || mode == inters_seg_dr) + { + return inters_colinear; + } + else if (mode == inters_dmd_dmd) + { + if (L_VAL_Zero (dd) > 0) + return inters_colinear; + double ts; + L_VEC_Cross (iu.d, usvs, ts); + if (L_VAL_Zero (ts) > 0) + return inters_colinear; + return 0; + } + else if (mode == inters_seg_dmd) + { + if (L_VAL_Zero (dd) > 0) + { + double ts; + L_VEC_Cross (iv.d, uevs, ts); + if (L_VAL_Zero (ts) < 0) + return inters_colinear; + return 0; + } + else + { + double ts; + L_VEC_Cross (iv.d, usvs, ts); + if (L_VAL_Zero (ts) < 0) + return inters_colinear; + return 0; + } + } + else if (mode == inters_seg_seg) + { + double ts, te; + L_VEC_Cross (iu.d, usvs, ts); + L_VEC_Cross (iu.d, uevs, te); + if (L_VAL_Zero (ts) > 0 && L_VAL_Zero (te) < 0) + return inters_colinear; + L_VEC_Cross (iu.d, usve, ts); + L_VEC_Cross (iu.d, ueve, te); + if (L_VAL_Zero (ts) > 0 && L_VAL_Zero (te) < 0) + return inters_colinear; + L_VEC_Cross (iv.d, usvs, ts); + L_VEC_Cross (iv.d, usve, te); + if (L_VAL_Zero (ts) < 0 && L_VAL_Zero (te) > 0) + return inters_colinear; + L_VEC_Cross (iv.d, uevs, ts); + L_VEC_Cross (iv.d, ueve, te); + if (L_VAL_Zero (ts) < 0 && L_VAL_Zero (te) > 0) + return inters_colinear; + return 0; + } + } + else + { + return 0; // paralleles + } + } + + // plus de colinearite ni d'extremites en commun + L_MAT_Inv (m); + vec2d res; + L_MAT_MulV (m, usvs, res); + + // l'intersection + L_VEC_MulC (iu.d, res.x, at); + L_VEC_Add (at, iu.p, at); + + if (mode == inters_dr_dr) + { + return inters_a_mi + inters_b_mi; + } + else if (mode == inters_dmd_dr) + { + int i = L_VAL_Zero (res.x); + if (i == 0) + return inters_a_st + inters_b_mi; + if (i > 0) + return inters_a_mi + inters_b_mi; + return 0; + } + else if (mode == inters_seg_dr) + { + int i = L_VAL_Zero (res.x); + int j = L_VAL_Cmp (res.x, 1); + if (i == 0) + return inters_a_st + inters_b_mi; + if (j == 0) + return inters_a_en + inters_b_mi; + if (i > 0 && j < 0) + return inters_a_mi + inters_b_mi; + return 0; + } + else if (mode == inters_dmd_dmd) + { + int i = L_VAL_Zero (res.x); + int j = -(L_VAL_Zero (res.y)); + // nota : i=0 et j=0 a ete elimine au debut + if (i == 0 && j > 0) + return inters_a_st + inters_b_mi; + if (j == 0 && i > 0) + return inters_a_mi + inters_b_st; + if (i > 0 && j > 0) + return inters_a_mi + inters_b_mi; + return 0; + } + else if (mode == inters_seg_dmd) + { + int i = L_VAL_Zero (res.x); + int j = L_VAL_Cmp (res.x, 1); + int k = -(L_VAL_Zero (res.y)); + // nota : i=0 et j=0 a ete elimine au debut + if (i == 0 && k > 0) + return inters_a_st + inters_b_mi; + if (j == 0 && k > 0) + return inters_a_en + inters_b_mi; + if (i > 0 && j < 0 && k == 0) + return inters_a_mi + inters_b_st; + if (i > 0 && j < 0 && k > 0) + return inters_a_mi + inters_b_mi; + return 0; + } + else if (mode == inters_seg_seg) + { + int i = L_VAL_Zero (res.x); + int j = L_VAL_Cmp (res.x, 1); + int k = -(L_VAL_Zero (res.y)); // la coordonnée sur iv est inversee + int l = -(L_VAL_Cmp (res.y, -1)); + // nota : i=0 et j=0 a ete elimine au debut + if (i == 0 && k > 0 && l < 0) + return inters_a_st + inters_b_mi; + if (j == 0 && k > 0 && l < 0) + return inters_a_en + inters_b_mi; + if (k == 0 && i > 0 && j < 0) + return inters_a_mi + inters_b_st; + if (l == 0 && i > 0 && j < 0) + return inters_a_mi + inters_b_en; + if (k > 0 && l < 0 && i > 0 && j < 0) + return inters_a_mi + inters_b_mi; + return 0; + } + + return 0; +} + +int +L_SEG::IntersectGeneral (L_SEG & iu, L_SEG & iv, vec2d & at, int mode) +{ + + vec2d usvs; + L_VEC_Sub (iv.p, iu.p, usvs); + + double dd; + L_VEC_Cross (iu.d, iv.d, dd); + + + mat2d m; + L_MAT_SetC (m, iu.d, iv.d); + double det; + L_MAT_Det (m, det); + + if (L_VAL_Zero (det)) + { // ces couillons de vecteurs sont colineaires + return 0; // paralleles + } + + // plus de colinearite ni d'extremites en commun + L_MAT_Inv (m); + vec2d res; + L_MAT_MulV (m, usvs, res); + + // l'intersection + L_VEC_MulC (iu.d, res.x, at); + L_VEC_Add (at, iu.p, at); + + if (mode == inters_dr_dr) + { + return inters_a_mi + inters_b_mi; + } + else if (mode == inters_dmd_dr) + { + int i = L_VAL_Zero (res.x); + if (i == 0) + return inters_a_st + inters_b_mi; + if (i > 0) + return inters_a_mi + inters_b_mi; + return 0; + } + else if (mode == inters_seg_dr) + { + int i = L_VAL_Zero (res.x); + int j = L_VAL_Cmp (res.x, 1); + if (i == 0) + return inters_a_st + inters_b_mi; + if (j == 0) + return inters_a_en + inters_b_mi; + if (i > 0 && j < 0) + return inters_a_mi + inters_b_mi; + return 0; + } + else if (mode == inters_dmd_dmd) + { + int i = L_VAL_Zero (res.x); + int j = -(L_VAL_Zero (res.y)); + // nota : i=0 et j=0 a ete elimine au debut + if (i == 0 && j > 0) + return inters_a_st + inters_b_mi; + if (j == 0 && i > 0) + return inters_a_mi + inters_b_st; + if (i > 0 && j > 0) + return inters_a_mi + inters_b_mi; + return 0; + } + else if (mode == inters_seg_dmd) + { + int i = L_VAL_Zero (res.x); + int j = L_VAL_Cmp (res.x, 1); + int k = -(L_VAL_Zero (res.y)); + // nota : i=0 et j=0 a ete elimine au debut + if (i == 0 && k > 0) + return inters_a_st + inters_b_mi; + if (j == 0 && k > 0) + return inters_a_en + inters_b_mi; + if (i > 0 && j < 0 && k == 0) + return inters_a_mi + inters_b_st; + if (i > 0 && j < 0 && k > 0) + return inters_a_mi + inters_b_mi; + return 0; + } + else if (mode == inters_seg_seg) + { + int i = L_VAL_Zero (res.x); + int j = L_VAL_Cmp (res.x, 1); + int k = -(L_VAL_Zero (res.y)); // la coordonnée sur iv est inversee + int l = -(L_VAL_Cmp (res.y, -1)); + // nota : i=0 et j=0 a ete elimine au debut + if (i == 0 && k > 0 && l < 0) + return inters_a_st + inters_b_mi; + if (j == 0 && k > 0 && l < 0) + return inters_a_en + inters_b_mi; + if (k == 0 && i > 0 && j < 0) + return inters_a_mi + inters_b_st; + if (l == 0 && i > 0 && j < 0) + return inters_a_mi + inters_b_en; + if (k > 0 && l < 0 && i > 0 && j < 0) + return inters_a_mi + inters_b_mi; + return 0; + } + + return 0; +} + +int +L_SEG::Contains (vec2d & pos, int mode) +{ + vec2d sp, ep; + L_VEC_Sub (pos, p, sp); + L_VEC_Sub (sp, d, ep); + double spl, epl; + L_VEC_Cross (sp, sp, spl); + L_VEC_Cross (ep, ep, epl); + if (L_VAL_Zero (spl) == 0) + { + if (mode == inters_dr_pt) + return inters_a_mi; + return inters_a_st; + } + if (L_VAL_Zero (epl) == 0) + { + if (mode == inters_dr_pt || mode == inters_dmd_pt) + return inters_a_mi; + return inters_a_en; + } + + vec2d perp = d; + L_VEC_RotCW (perp); + + double dd, ps; + L_VEC_Cross (d, d, dd); + L_VEC_Cross (perp, sp, ps); + if (L_VAL_Zero (ps) == 0) + { // sur la droite + if (mode == inters_dr_pt) + return inters_a_mi; + L_VEC_Cross (d, sp, ps); + // ps != 0 car le cas est traité avant + if (mode == inters_dmd_pt) + { + if (L_VAL_Zero (ps) > 0) + { + return inters_a_mi; + } + } + else + { + if (L_VAL_Zero (ps) > 0) + { + if (L_VAL_Cmp (ps, dd) < 0) + { + return inters_a_mi; + } + } + } + } + + return 0; +} diff --git a/src/livarot/MySeg.h b/src/livarot/MySeg.h new file mode 100644 index 000000000..8183b7d46 --- /dev/null +++ b/src/livarot/MySeg.h @@ -0,0 +1,147 @@ +/* + * MySeg.h + * nlivarot + * + * Created by fred on Wed Nov 12 2003. + * + */ + +#ifndef my_math_seg +#define my_math_seg + +#include "MyMath.h" + +// codes for the intersections computations +// pt = point +// seg = segment +// dmd = half line +// dr = infinite line +enum +{ + inters_seg_seg, // intersection between 2 segments + inters_seg_dmd, // intersection between one segment (parameter no 1) and one half-line (parameter no 2) + inters_seg_dr, // .... + inters_dmd_dmd, + inters_dmd_dr, + inters_dr_dr, + + inters_seg_pt, // "intersection" between segment and point= "does the segment contain the point?" + inters_dmd_pt, + inters_dr_pt, + + inters_orseg_pt // don't use +}; + +// return codes for the intersection computations; build as a concatenation of +// _a = first parameter +// _b = second parameter +// _st = start of the segment/half-line (lines don't have starts) +// _en = end of thz segment (half-lines and lines don't have ends) +// _mi = inside of the segment/half-line/line +// _colinear = this flag is set if the intersection of the 2 parameter is more than a point +// the first 2 bits of the return code contain the position of the intersection on the first parameter (_st, _mi or _en) +// the next 2 bits of the return code contain the position of the intersection on the second parameter (_st, _mi or _en) +// the 5th bit is set if the parameters are colinear +enum +{ + inters_a_st = 1, + inters_a_mi = 2, + inters_a_en = 3, + inters_b_st = 4, + inters_b_mi = 8, + inters_b_en = 12, + inters_colinear = 16 +}; + + +// +// a class to describe a segment: defined by its startpoint p and its direction d +// if the object is considered as a segment: p+xd, where x ranges from 0 to 1 +// if the object is considered as an half-line, the length of the direction vector doesn't matter: +// p+xd, where x ranges from 0 to +infinity +// if the object is considered as a line: p+xd, where x ranges from -infinity to +infinity +// +class L_SEG +{ +public: + vec2d p, d; + + // constructors + L_SEG (vec2d & st, vec2d & dir):p (st), d (dir) + { + }; // by default, you give one startpoint and one direction + L_SEG (void) + { + }; + ~L_SEG (void) + { + }; + + // assignations + void Set (L_SEG * s) + { + p = s->p; + d = s->d; + }; + void Set (L_SEG & s) + { + p = s.p; + d = s.d; + }; + // 2 specific assignations: + // assignation where you give the startpoint and the direction (like in the constructor): + void SetSD (vec2d & st, vec2d & dir) + { + p = st; + d = dir; + }; + // assignation where you give the startpoint and the endpoint: + void SetSE (vec2d & st, vec2d & en) + { + p = st; + d.x = en.x - st.x; + d.y = en.y - st.y; + }; + + // reverses the segment + void Rev (void) + { + p.x += d.x; + p.y += d.y; + d.x = -d.x; + d.y = -d.y; + }; + // transitibve version: the reversed segment is stored in s + void Rev (L_SEG & s) + { + s.p.x = p.x + d.x; + s.p.y = p.y + d.y; + s.d.x = -d.x; + s.d.y = -d.y; + }; + + // distance of the point iv to the segment/half-line/line + // the mode parameter specifies how the caller instance should be handled: + // inters_seg_pt : segment + // inters_dmd_pt : half-line + // inters_dr_pt : line + void Distance (vec2d & iv, double &d, int mode = inters_dr_pt); + // distance between 2 segments + // mode parameter specifies how the segments have to be treated (just like above) + void Distance (L_SEG & is, double &d, int mode = inters_seg_seg); + + // tests if the segment contains the point pos + // mode is as in the Distance function + int Contains (vec2d & pos, int mode); + + // intersection between 2 lines/half-lines/segments + // mode specifies how the L_SEG instances have to be considered; codes at the beginning of this file + // the "at" parameter stores the intersection point, if it exists and is unique + static int Intersect (L_SEG & iu, L_SEG & iv, int mode); + static int Intersect (L_SEG & iu, L_SEG & iv, vec2d & at, int mode); + // specific version, when you can garantuee the colinearity case won't occur + static int IntersectGeneral (L_SEG & iu, L_SEG & iv, vec2d & at, int mode); +}; + + +#endif diff --git a/src/livarot/Path.cpp b/src/livarot/Path.cpp new file mode 100644 index 000000000..32c100310 --- /dev/null +++ b/src/livarot/Path.cpp @@ -0,0 +1,908 @@ +/* + * Path.cpp + * nlivarot + * + * Created by fred on Tue Jun 17 2003. + * + */ + +#include +#include "Path.h" +#include "livarot/path-description.h" +#include + +/* + * 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 (std::vector::iterator i = descr_cmd.begin(); i != descr_cmd.end(); i++) { + delete *i; + } +} + +// debug function do dump the path contents on stdout +void Path::Affiche() +{ + std::cout << "path: " << descr_cmd.size() << " commands." << std::endl; + for (std::vector::const_iterator i = descr_cmd.begin(); i != descr_cmd.end(); i++) { + (*i)->dump(std::cout); + std::cout << std::endl; + } + + std::cout << std::endl; +} + +void Path::Reset() +{ + for (std::vector::iterator i = descr_cmd.begin(); i != descr_cmd.end(); i++) { + delete *i; + } + + descr_cmd.clear(); + pending_bezier_cmd = -1; + pending_moveto_cmd = -1; + descr_flags = 0; +} + +void Path::Copy(Path * who) +{ + ResetPoints(); + + for (std::vector::iterator i = descr_cmd.begin(); i != descr_cmd.end(); i++) { + delete *i; + } + + descr_cmd.clear(); + + for (std::vector::const_iterator i = who->descr_cmd.begin(); + i != who->descr_cmd.end(); + i++) + { + 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(NR::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(NR::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(NR::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(NR::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(NR::Point const &iPt, NR::Point const &iStD, NR::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(NR::Point const &iPt, NR::Point const &iStD, NR::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(NR::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(NR::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(NR::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(NR::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(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(NR::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(descr_cmd[pending_bezier_cmd]); + nBData->nb++; + return descr_cmd.size() - 1; +} + + +void Path::InsertIntermBezierTo(NR::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(NR::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(NR::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 de la polyligne + */ +void +Path::SetBackData (bool nVal) +{ + if (back == false) { + if (nVal == true && back == false) { + back = true; + ResetPoints(); + } else if (nVal == false && back == true) { + back = false; + ResetPoints(); + } + } else { + if (nVal == true && back == false) { + back = true; + ResetPoints(); + } else if (nVal == false && back == true) { + back = false; + ResetPoints(); + } + } +} + + +void Path::ResetPoints() +{ + pts.clear(); +} + + +int Path::AddPoint(NR::Point const &iPt, bool mvto) +{ + if (back) { + return AddPoint (iPt, -1, 0.0, mvto); + } + + if ( !mvto && pts.empty() == false && pts.back().p == iPt ) { + return -1; + } + + int const n = pts.size(); + pts.push_back(path_lineto(mvto ? polyline_moveto : polyline_lineto, iPt)); + return n; +} + + +int Path::AddPoint(NR::Point const &iPt, int ip, double it, bool mvto) +{ + if (back == false) { + return AddPoint (iPt, mvto); + } + + if ( !mvto && pts.empty() == false && pts.back().p == iPt ) { + return -1; + } + + int const n = pts.size(); + pts.push_back(path_lineto(mvto ? polyline_moveto : polyline_lineto, iPt, ip, it)); + return n; +} + +int Path::AddForcedPoint(NR::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.push_back(path_lineto(polyline_forced, pts[n - 1].p)); + return n; +} + + +int Path::AddForcedPoint(NR::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.push_back(path_lineto(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::const_iterator i = pts.begin(); + l = r = i->p[NR::X]; + t = b = i->p[NR::Y]; + i++; + + for (; i != pts.end(); i++) { + r = std::max(r, i->p[NR::X]); + l = std::min(l, i->p[NR::X]); + b = std::max(b, i->p[NR::Y]); + t = std::min(t, i->p[NR::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, NR::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 = NR::Point(0,0); + return; + } + + PathDescr const *theD = descr_cmd[piece]; + int const typ = theD->getType(); + NR::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(theD); + TangentOnSegAt(at, PrevPoint (piece - 1), *nData, pos, tgt, len); + + } else if (typ == descr_arcto) { + + PathDescrArcTo const *nData = dynamic_cast(theD); + TangentOnArcAt(at,PrevPoint (piece - 1), *nData, pos, tgt, len, rad); + + } else if (typ == descr_cubicto) { + + PathDescrCubicTo const *nData = dynamic_cast(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(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; + NR::Point const bStPt = PrevPoint(bez_st - 1); + if (stB->nb == 1 || k <= 0) { + PathDescrIntermBezierTo *nData = dynamic_cast(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(descr_cmd[bez_st + 1]); + PathDescrIntermBezierTo *nnextI = dynamic_cast(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(descr_cmd[bez_st + k]); + PathDescrIntermBezierTo *prevI = dynamic_cast(descr_cmd[bez_st + k - 1]); + NR::Point stP = 0.5 * ( prevI->p + nextI->p ); + TangentOnBezAt(at, stP, *nextI, *stB, false, pos, tgt, len, rad); + } else { + PathDescrIntermBezierTo *nextI = dynamic_cast(descr_cmd[bez_st + k]); + PathDescrIntermBezierTo *prevI = dynamic_cast(descr_cmd[bez_st + k - 1]); + PathDescrIntermBezierTo *nnextI = dynamic_cast(descr_cmd[bez_st + k + 1]); + NR::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, NR::Point &pos, NR::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 = NR::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(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(theD); + TangentOnSegAt(at, PrevPoint (piece - 1), *nData, pos, tgt, len); + + } else if (typ == descr_arcto) { + + PathDescrArcTo const *nData = dynamic_cast(theD); + TangentOnArcAt (at,PrevPoint (piece - 1), *nData, pos, tgt, len, rad); + + } else if (typ == descr_cubicto) { + + PathDescrCubicTo const *nData = dynamic_cast(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(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; + NR::Point const bStPt(PrevPoint( bez_st - 1 )); + if (stB->nb == 1 || k <= 0) { + PathDescrIntermBezierTo* nData = dynamic_cast(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(descr_cmd[bez_st + 1]); + PathDescrIntermBezierTo *nnextI = dynamic_cast(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(descr_cmd[bez_st + k - 1]); + PathDescrIntermBezierTo *nextI = dynamic_cast(descr_cmd[bez_st + k]); + NR::Point stP = 0.5 * ( prevI->p + nextI->p ); + TangentOnBezAt(at, stP, *nextI, *stB, false, pos, tgt, len, rad); + } else { + PathDescrIntermBezierTo *prevI = dynamic_cast(descr_cmd[bez_st + k - 1]); + PathDescrIntermBezierTo *nextI = dynamic_cast(descr_cmd[bez_st + k]); + PathDescrIntermBezierTo *nnextI = dynamic_cast(descr_cmd[bez_st + k + 1]); + NR::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); + } + } + } +} + +void Path::Transform(const NR::Matrix &trans) +{ + for (std::vector::iterator i = descr_cmd.begin(); i != descr_cmd.end(); i++) { + (*i)->transform(trans); + } +} + +void Path::FastBBox(double &l,double &t,double &r,double &b) +{ + l = t = r = b = 0; + bool empty = true; + NR::Point lastP(0, 0); + + for (int i = 0; i < int(descr_cmd.size()); i++) { + int const typ = descr_cmd[i]->getType(); + switch ( typ ) { + case descr_lineto: + { + PathDescrLineTo *nData = dynamic_cast(descr_cmd[i]); + if ( empty ) { + l = r = nData->p[NR::X]; + t = b = nData->p[NR::Y]; + empty = false; + } else { + if ( nData->p[NR::X] < l ) { + l = nData->p[NR::X]; + } + if ( nData->p[NR::X] > r ) { + r = nData->p[NR::X]; + } + if ( nData->p[NR::Y] < t ) { + t = nData->p[NR::Y]; + } + if ( nData->p[NR::Y] > b ) { + b = nData->p[NR::Y]; + } + } + lastP = nData->p; + } + break; + + case descr_moveto: + { + PathDescrMoveTo *nData = dynamic_cast(descr_cmd[i]); + if ( empty ) { + l = r = nData->p[NR::X]; + t = b = nData->p[NR::Y]; + empty = false; + } else { + if ( nData->p[NR::X] < l ) { + l = nData->p[NR::X]; + } + if ( nData->p[NR::X] > r ) { + r = nData->p[NR::X]; + } + if ( nData->p[NR::Y] < t ) { + t = nData->p[NR::Y]; + } + if ( nData->p[NR::Y] > b ) { + b = nData->p[NR::Y]; + } + } + lastP = nData->p; + } + break; + + case descr_arcto: + { + PathDescrArcTo *nData = dynamic_cast(descr_cmd[i]); + if ( empty ) { + l = r = nData->p[NR::X]; + t = b = nData->p[NR::Y]; + empty = false; + } else { + if ( nData->p[NR::X] < l ) { + l = nData->p[NR::X]; + } + if ( nData->p[NR::X] > r ) { + r = nData->p[NR::X]; + } + if ( nData->p[NR::Y] < t ) { + t = nData->p[NR::Y]; + } + if ( nData->p[NR::Y] > b ) { + b = nData->p[NR::Y]; + } + } + lastP = nData->p; + } + break; + + case descr_cubicto: + { + PathDescrCubicTo *nData = dynamic_cast(descr_cmd[i]); + if ( empty ) { + l = r = nData->p[NR::X]; + t = b = nData->p[NR::Y]; + empty = false; + } else { + if ( nData->p[NR::X] < l ) { + l = nData->p[NR::X]; + } + if ( nData->p[NR::X] > r ) { + r = nData->p[NR::X]; + } + if ( nData->p[NR::Y] < t ) { + t = nData->p[NR::Y]; + } + if ( nData->p[NR::Y] > b ) { + b = nData->p[NR::Y]; + } + } + + NR::Point np = nData->p - nData->end; + if ( np[NR::X] < l ) { + l = np[NR::X]; + } + if ( np[NR::X] > r ) { + r = np[NR::X]; + } + if ( np[NR::Y] < t ) { + t = np[NR::Y]; + } + if ( np[NR::Y] > b ) { + b = np[NR::Y]; + } + + np = lastP + nData->start; + if ( np[NR::X] < l ) { + l = np[NR::X]; + } + if ( np[NR::X] > r ) { + r = np[NR::X]; + } + if ( np[NR::Y] < t ) { + t = np[NR::Y]; + } + if ( np[NR::Y] > b ) { + b = np[NR::Y]; + } + lastP = nData->p; + } + break; + + case descr_bezierto: + { + PathDescrBezierTo *nData = dynamic_cast(descr_cmd[i]); + if ( empty ) { + l = r = nData->p[NR::X]; + t = b = nData->p[NR::Y]; + empty = false; + } else { + if ( nData->p[NR::X] < l ) { + l = nData->p[NR::X]; + } + if ( nData->p[NR::X] > r ) { + r = nData->p[NR::X]; + } + if ( nData->p[NR::Y] < t ) { + t = nData->p[NR::Y]; + } + if ( nData->p[NR::Y] > b ) { + b = nData->p[NR::Y]; + } + } + lastP = nData->p; + } + break; + + case descr_interm_bezier: + { + PathDescrIntermBezierTo *nData = dynamic_cast(descr_cmd[i]); + if ( empty ) { + l = r = nData->p[NR::X]; + t = b = nData->p[NR::Y]; + empty = false; + } else { + if ( nData->p[NR::X] < l ) { + l = nData->p[NR::X]; + } + if ( nData->p[NR::X] > r ) { + r = nData->p[NR::X]; + } + if ( nData->p[NR::Y] < t ) { + t = nData->p[NR::Y]; + } + if ( nData->p[NR::Y] > b ) { + b = nData->p[NR::Y]; + } + } + } + break; + } + } +} + +char *Path::svg_dump_path() const +{ + Inkscape::SVGOStringStream os; + + for (int i = 0; i < int(descr_cmd.size()); i++) { + NR::Point const p = (i == 0) ? NR::Point(0, 0) : PrevPoint(i - 1); + descr_cmd[i]->dumpSVG(os, p); + } + + return g_strdup (os.str().c_str()); +} + +/* + 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 000000000..5b1df6294 --- /dev/null +++ b/src/livarot/Path.h @@ -0,0 +1,387 @@ +/* + * Path.h + * nlivarot + * + * Created by fred on Tue Jun 17 2003. + * + */ + +#ifndef my_path +#define my_path + +#include +#include "LivarotDefs.h" +#include "livarot/livarot-forward.h" +#include "libnr/nr-point.h" + +/* + * 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 +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; + // the path description + std::vector descr_cmd; + + // polyline storage: a series of coordinates (and maybe weights) + // also back data: info on where this polyline's segment comes from, ie wich command in the path description: "piece" + // and what abcissis on the chunk of path for this command: "t" + // t=0 means it's at the start of the command's chunk, t=1 it's at the end + struct path_lineto + { + path_lineto(bool m, NR::Point pp) : isMoveTo(m), p(pp), piece(-1), t(0) {} + path_lineto(bool m, NR::Point pp, int pie, double tt) : isMoveTo(m), p(pp), piece(pie), t(tt) {} + + int isMoveTo; + NR::Point p; + int piece; + double t; + }; + + std::vector pts; + + bool back; + + Path(); + ~Path(); + + // creation of the path description + void Reset(); // reset to the empty description + void Copy (Path * who); + + // the commands... + int ForcePoint(); + int Close(); + int MoveTo ( NR::Point const &ip); + int LineTo ( NR::Point const &ip); + int CubicTo ( NR::Point const &ip, NR::Point const &iStD, NR::Point const &iEnD); + int ArcTo ( NR::Point const &ip, double iRx, double iRy, double angle, bool iLargeArc, bool iClockwise); + int IntermBezierTo ( NR::Point const &ip); // add a quadratic bezier spline control point + int BezierTo ( NR::Point const &ip); // quadratic bezier spline to this point (control points can be added after this) + int TempBezierTo(); // start a quadratic bezier spline (control points can be added after this) + int EndBezierTo(); + int EndBezierTo ( NR::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) + void Convert (double treshhold); + void ConvertEvenLines (double treshhold); // decomposes line segments too, for later recomposition + // same function for use when you want to later recompose the curves from the polyline + void ConvertWithBackData (double treshhold); + + // creation of the polyline (you can tinker with these function if you want) + void SetBackData (bool nVal); // has back data? + void ResetPoints(); // resets to the empty polyline + int AddPoint ( NR::Point const &iPt, bool mvto = false); // add point + int AddPoint ( NR::Point const &iPt, int ip, double it, bool mvto = false); + int AddForcedPoint ( NR::Point const &iPt); // add point + int AddForcedPoint ( NR::Point const &iPt, int ip, double it); + + // 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 + 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 + void Simplify (double treshhold); + + // description 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, NR::Point & pos); + void PointAndTangentAt (int piece, double at, NR::Point & pos, NR::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 NR::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,float *dashs,bool stPlain,float stOffset); + + //utilitaire pour inkscape + void LoadArtBPath(void *iP,NR::Matrix const &tr,bool doTransformation); + void* MakeArtBPath(); + + void Transform(const NR::Matrix &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(NR::Point const &pos) 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; + + private: + // utilitary functions for the path contruction + void CancelBezier (); + void CloseSubpath(); + void InsertMoveTo (NR::Point const &iPt,int at); + void InsertForcePoint (int at); + void InsertLineTo (NR::Point const &iPt,int at); + void InsertArcTo (NR::Point const &ip, double iRx, double iRy, double angle, bool iLargeArc, bool iClockwise,int at); + void InsertCubicTo (NR::Point const &ip, NR::Point const &iStD, NR::Point const &iEnD,int at); + void InsertBezierTo (NR::Point const &iPt,int iNb,int at); + void InsertIntermBezierTo (NR::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 const &orig_pts, float head,float tail,float body,int nbD,float *dashs,bool stPlain,float stOffset); + + // Functions used by the conversion. + // they append points to the polyline + void DoArc ( NR::Point const &iS, NR::Point const &iE, double rx, double ry, + double angle, bool large, bool wise, double tresh); + void RecCubicTo ( NR::Point const &iS, NR::Point const &iSd, NR::Point const &iE, NR::Point const &iEd, double tresh, int lev, + double maxL = -1.0); + void RecBezierTo ( NR::Point const &iPt, NR::Point const &iS, NR::Point const &iE, double treshhold, int lev, double maxL = -1.0); + + void DoArc ( NR::Point const &iS, NR::Point const &iE, double rx, double ry, + double angle, bool large, bool wise, double tresh, int piece); + void RecCubicTo ( NR::Point const &iS, NR::Point const &iSd, NR::Point const &iE, NR::Point const &iEd, double tresh, int lev, + double st, double et, int piece); + void RecBezierTo ( NR::Point const &iPt, NR::Point const &iS, const NR::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 ( NR::Point const &iS, NR::Point const &iE, double rx, double ry, + double angle, bool large, bool wise, double tresh, int piece, + offset_orig & orig); + void RecCubicTo ( NR::Point const &iS, NR::Point const &iSd, NR::Point const &iE, NR::Point const &iEd, double tresh, int lev, + double st, double et, int piece, offset_orig & orig); + void RecBezierTo ( NR::Point const &iPt, NR::Point const &iS, NR::Point const &iE, double treshhold, int lev, double st, double et, + int piece, offset_orig & orig); + + static void ArcAngles ( NR::Point const &iS, NR::Point const &iE, double rx, + double ry, double angle, bool large, bool wise, + double &sang, double &eang); + static void QuadraticPoint (double t, NR::Point &oPt, NR::Point const &iS, NR::Point const &iM, NR::Point const &iE); + static void CubicTangent (double t, NR::Point &oPt, NR::Point const &iS, + NR::Point const &iSd, NR::Point const &iE, + NR::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, NR::Point & lastP, NR::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, NR::Point const &iS, PathDescrLineTo const &fin, + NR::Point &pos, NR::Point &tgt, double &len); + static void TangentOnArcAt(double at, NR::Point const &iS, PathDescrArcTo const &fin, + NR::Point &pos, NR::Point &tgt, double &len, double &rad); + static void TangentOnCubAt (double at, NR::Point const &iS, PathDescrCubicTo const &fin, bool before, + NR::Point &pos, NR::Point &tgt, double &len, double &rad); + static void TangentOnBezAt (double at, NR::Point const &iS, + PathDescrIntermBezierTo & mid, + PathDescrBezierTo & fin, bool before, + NR::Point & pos, NR::Point & tgt, double &len, double &rad); + static void OutlineJoin (Path * dest, NR::Point pos, NR::Point stNor, NR::Point enNor, + double width, JoinType join, double miter); + + static bool IsNulCurve (std::vector const &cmd, int curD, NR::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, NR::Point pos, + NR::Point dir, int &leftNo, int &rightNo); + static void DoJoin (Shape * dest, double width, JoinType join, NR::Point pos, + NR::Point prev, NR::Point next, double miter, double prevL, + double nextL, int *stNo, int *enNo); + static void DoLeftJoin (Shape * dest, double width, JoinType join, NR::Point pos, + NR::Point prev, NR::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, NR::Point pos, + NR::Point prev, NR::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, + NR::Point const &iS, NR::Point const &iE, + NR::Point const &nS, NR::Point const &nE, + NR::Point &origine,float width); + + + void DoSimplify(int off, int N, double treshhold); + bool AttemptSimplify(int off, int N, double treshhold, PathDescrCubicTo &res, int &worstP); + static bool FitCubic(NR::Point const &start, + PathDescrCubicTo &res, + double *Xk, double *Yk, double *Qk, double *tk, int nbPt); + + struct fitting_tables { + int nbPt,maxPt,inPt; + double *Xk; + double *Yk; + double *Qk; + double *tk; + double *lk; + char *fk; + double totLen; + }; + bool AttemptSimplify (fitting_tables &data,double treshhold, PathDescrCubicTo & res,int &worstP); + bool ExtendFit(int off, int N, fitting_tables &data,double treshhold, PathDescrCubicTo & res,int &worstP); + double RaffineTk (NR::Point pt, NR::Point p0, NR::Point p1, NR::Point p2, NR::Point p3, double it); + void FlushPendingAddition(Path* dest,PathDescr *lastAddition,PathDescrCubicTo &lastCubic,int lastAD); +}; +#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:encoding=utf-8:textwidth=99 : diff --git a/src/livarot/PathConversion.cpp b/src/livarot/PathConversion.cpp new file mode 100644 index 000000000..e6f7acb0c --- /dev/null +++ b/src/livarot/PathConversion.cpp @@ -0,0 +1,1575 @@ +/* + * PathConversion.cpp + * nlivarot + * + * Created by fred on Mon Nov 03 2003. + * + */ + +#include "Path.h" +#include "Shape.h" +#include "livarot/path-description.h" + +#include +#include +#include + +/* + * 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 ( descr_flags & descr_adding_bezier ) { + CancelBezier(); + } + + if ( descr_flags & descr_doing_subpath ) { + CloseSubpath(); + } + + SetBackData(true); + ResetPoints(); + if ( descr_cmd.empty() ) { + return; + } + + NR::Point curX; + int curP = 1; + int lastMoveTo = -1; + + // The initial moveto. + { + int const firstTyp = descr_cmd[0]->getType(); + if ( firstTyp == descr_moveto ) { + curX = dynamic_cast(descr_cmd[0])->p; + } else { + curP = 0; + curX[NR::X] = curX[NR::Y] = 0; + } + lastMoveTo = AddPoint(curX, 0, 0.0, true); + } + + // And the rest, one by one. + while ( curP < int(descr_cmd.size()) ) { + + int const nType = descr_cmd[curP]->getType(); + NR::Point nextX; + + switch (nType) { + case descr_forced: { + AddForcedPoint(curX, curP, 1.0); + curP++; + break; + } + + case descr_moveto: { + PathDescrMoveTo *nData = dynamic_cast(descr_cmd[curP]); + nextX = nData->p; + lastMoveTo = AddPoint(nextX, curP, 0.0, true); + // et on avance + curP++; + break; + } + + case descr_close: { + nextX = pts[lastMoveTo].p; + AddPoint(nextX, curP, 1.0, false); + curP++; + break; + } + + case descr_lineto: { + PathDescrLineTo *nData = dynamic_cast(descr_cmd[curP]); + nextX = nData->p; + AddPoint(nextX,curP,1.0,false); + // et on avance + curP++; + break; + } + + case descr_cubicto: { + PathDescrCubicTo *nData = dynamic_cast(descr_cmd[curP]); + nextX = nData->p; + RecCubicTo(curX, nData->start, nextX, nData->end, treshhold, 8, 0.0, 1.0, curP); + AddPoint(nextX, curP, 1.0, false); + // et on avance + curP++; + break; + } + + case descr_arcto: { + PathDescrArcTo *nData = dynamic_cast(descr_cmd[curP]); + nextX = nData->p; + 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(descr_cmd[curP]); + int nbInterm = nBData->nb; + nextX = nBData->p; + + int ip = curP + 1; + PathDescrIntermBezierTo *nData = dynamic_cast(descr_cmd[ip]); + + if ( nbInterm >= 1 ) { + NR::Point bx = curX; + NR::Point cx = curX; + NR::Point dx = curX; + + dx = nData->p; + ip++; + nData = dynamic_cast(descr_cmd[ip]); + + cx = 2 * bx - dx; + + for (int k = 0; k < nbInterm - 1; k++) { + bx = cx; + cx = dx; + + dx = nData->p; + ip++; + nData = dynamic_cast(descr_cmd[ip]); + + NR::Point stx; + stx = (bx + cx) / 2; + if ( k > 0 ) { + AddPoint(stx,curP - 1+k,1.0,false); + } + + { + NR::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; + + NR::Point stx; + stx = (bx + cx) / 2; + + if ( nbInterm > 1 ) { + AddPoint(stx, curP + nbInterm - 2, 1.0, false); + } + + { + NR::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; + } + + NR::Point curX; + int curP = 1; + int lastMoveTo = 0; + + // le moveto + { + int const firstTyp = descr_cmd[0]->getType(); + if ( firstTyp == descr_moveto ) { + curX = dynamic_cast(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(); + NR::Point nextX; + + switch (nType) { + case descr_forced: { + descr_cmd[curP]->associated = AddForcedPoint(curX); + curP++; + break; + } + + case descr_moveto: { + PathDescrMoveTo *nData = dynamic_cast(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; + } + } + curP++; + break; + } + + case descr_lineto: { + PathDescrLineTo *nData = dynamic_cast(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(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(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(descr_cmd[curP]); + int nbInterm = nBData->nb; + nextX = nBData->p; + int curBD = curP; + + curP++; + int ip = curP; + PathDescrIntermBezierTo *nData = dynamic_cast(descr_cmd[ip]); + + if ( nbInterm == 1 ) { + NR::Point const midX = nData->p; + RecBezierTo(midX, curX, nextX, treshhold, 8); + } else if ( nbInterm > 1 ) { + NR::Point bx = curX; + NR::Point cx = curX; + NR::Point dx = curX; + + dx = nData->p; + ip++; + nData = dynamic_cast(descr_cmd[ip]); + + cx = 2 * bx - dx; + + for (int k = 0; k < nbInterm - 1; k++) { + bx = cx; + cx = dx; + + dx = nData->p; + ip++; + nData = dynamic_cast(descr_cmd[ip]); + + NR::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; + } + } + } + + { + NR::Point const mx = (cx + dx) / 2; + RecBezierTo(cx, stx, mx, treshhold, 8); + } + } + + { + bx = cx; + cx = dx; + + dx = nextX; + dx = 2 * dx - cx; + + NR::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; + } + } + + { + NR::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; + } + + NR::Point curX; + int curP = 1; + int lastMoveTo = 0; + + // le moveto + { + int const firstTyp = descr_cmd[0]->getType(); + if ( firstTyp == descr_moveto ) { + curX = dynamic_cast(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(); + NR::Point nextX; + + switch (nType) { + case descr_forced: { + descr_cmd[curP]->associated = AddForcedPoint(curX); + curP++; + break; + } + + case descr_moveto: { + PathDescrMoveTo* nData = dynamic_cast(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; + { + NR::Point nexcur; + nexcur = nextX - curX; + const double segL = NR::L2(nexcur); + if ( segL > treshhold ) { + for (double i = treshhold; i < segL; i += treshhold) { + NR::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; + } + } + + curP++; + break; + } + + case descr_lineto: { + PathDescrLineTo* nData = dynamic_cast(descr_cmd[curP]); + nextX = nData->p; + NR::Point nexcur = nextX - curX; + const double segL = L2(nexcur); + if ( segL > treshhold ) { + for (double i = treshhold; i < segL; i += treshhold) { + NR::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(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(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(descr_cmd[curP]); + int nbInterm = nBData->nb; + nextX = nBData->p; + int curBD = curP; + + curP++; + int ip = curP; + PathDescrIntermBezierTo *nData = dynamic_cast(descr_cmd[ip]); + + if ( nbInterm == 1 ) { + NR::Point const midX = nData->p; + RecBezierTo(midX, curX, nextX, treshhold, 8, 4 * treshhold); + } else if ( nbInterm > 1 ) { + NR::Point bx = curX; + NR::Point cx = curX; + NR::Point dx = curX; + + dx = nData->p; + ip++; + nData = dynamic_cast(descr_cmd[ip]); + + cx = 2 * bx - dx; + + for (int k = 0; k < nbInterm - 1; k++) { + bx = cx; + cx = dx; + dx = nData->p; + + ip++; + nData = dynamic_cast(descr_cmd[ip]); + + NR::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; + } + } + } + + { + NR::Point const mx = (cx + dx) / 2; + RecBezierTo(cx, stx, mx, treshhold, 8, 4 * treshhold); + } + } + + { + bx = cx; + cx = dx; + + dx = nextX; + dx = 2 * dx - cx; + + NR::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; + } + } + + { + NR::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 ( NR::LInfty(curX - nextX) > 0.00001 ) { + curX = nextX; + } + } +} + +const NR::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(descr_cmd[i]); + return nData->p; + } + case descr_lineto: { + PathDescrLineTo *nData = dynamic_cast(descr_cmd[i]); + return nData->p; + } + case descr_arcto: { + PathDescrArcTo *nData = dynamic_cast(descr_cmd[i]); + return nData->p; + } + case descr_cubicto: { + PathDescrCubicTo *nData = dynamic_cast(descr_cmd[i]); + return nData->p; + } + case descr_bezierto: { + PathDescrBezierTo *nData = dynamic_cast(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 NR::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, NR::Point &oPt, + const NR::Point &iS, const NR::Point &iM, const NR::Point &iE) +{ + NR::Point const ax = iE - 2 * iM + iS; + NR::Point const bx = 2 * iM - 2 * iS; + NR::Point const cx = iS; + + oPt = t * t * ax + t * bx + cx; +} +// idem for cubic bezier patch +void Path::CubicTangent(double t, NR::Point &oPt, const NR::Point &iS, const NR::Point &isD, + const NR::Point &iE, const NR::Point &ieD) +{ + NR::Point const ax = ieD - 2 * iE + 2 * iS + isD; + NR::Point const bx = 3 * iE - ieD - 2 * isD - 3 * iS; + NR::Point const cx = isD; + + oPt = 3 * t * t * ax + 2 * t * bx + cx; +} + +// extract interesting info of a SVG arc description +static void ArcAnglesAndCenter(NR::Point const &iS, NR::Point const &iE, + double rx, double ry, double angle, + bool large, bool wise, + double &sang, double &eang, NR::Point &dr); + +void Path::ArcAngles(const NR::Point &iS, const NR::Point &iE, + double rx, double ry, double angle, bool large, bool wise, double &sang, double &eang) +{ + NR::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(NR::Point const &iS, NR::Point const &iE, + double rx, double ry, double angle, + bool large, bool wise, + double &sang, double &eang, NR::Point &dr) +{ + NR::Point se = iE - iS; + NR::Point ca(cos(angle), sin(angle)); + NR::Point cse(dot(se, ca), cross(se, ca)); + cse[0] /= rx; + cse[1] /= ry; + double const lensq = dot(cse,cse); + NR::Point csd = ( ( lensq < 4 + ? sqrt( 1/lensq - .25 ) + : 0.0 ) + * cse.ccw() ); + + NR::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(csd, ca); + dr[1] = cross(csd, ca); + + 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(NR::Point const &iS, NR::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 ) { + return; + // We always add a lineto afterwards, so this is fine. + // [on ajoute toujours un lineto apres, donc c bon] + } + + double sang; + double eang; + NR::Point dr; + ArcAnglesAndCenter(iS, iE, rx, ry, angle, large, wise, sang, eang, dr); + /* 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). */ + + NR::scale const ar(rx, ry); + NR::rotate cb( angle + sang ); + if (wise) { + + double const incr = -0.1; + if ( sang < eang ) { + sang += 2*M_PI; + } + NR::rotate const omega(incr); + for (double b = sang + incr ; b > eang ; b += incr) { + cb = omega * cb; + AddPoint( cb.vec * ar + dr ); + } + + } else { + + double const incr = 0.1; + if ( sang > eang ) { + sang -= 2*M_PI; + } + NR::rotate const omega(incr); + for (double b = sang + incr ; b < eang ; b += incr) { + cb = omega * cb; + AddPoint( cb.vec * ar + dr); + } + } +} + + +void Path::RecCubicTo( NR::Point const &iS, NR::Point const &isD, + NR::Point const &iE, NR::Point const &ieD, + double tresh, int lev, double maxL) +{ + NR::Point se = iE - iS; + const double dC = NR::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 ) { + // presque tt droit -> attention si on nous demande de bien subdiviser les petits segments + if ( maxL > 0 && dC > maxL ) { + if ( lev <= 0 ) { + return; + } + NR::Point m = 0.5 * (iS + iE) + 0.125 * (isD - ieD); + NR::Point md = 0.75 * (iE - iS) - 0.125 * (isD + ieD); + + NR::Point hisD = 0.5 * isD; + NR::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; + } + + { + NR::Point m = 0.5 * (iS + iE) + 0.125 * (isD - ieD); + NR::Point md = 0.75 * (iE - iS) - 0.125 * (isD + ieD); + + NR::Point hisD = 0.5 * isD; + NR::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 NR::Point &iP, + const NR::Point &iS, + const NR::Point &iE, + double tresh, int lev, double maxL) +{ + if ( lev <= 0 ) { + return; + } + + NR::Point ps = iS - iP; + NR::Point pe = iE - iP; + NR::Point se = iE - iS; + double s = fabs(cross(pe, ps)); + if ( s < tresh ) { + const double l = L2(se); + if ( maxL > 0 && l > maxL ) { + const NR::Point m = 0.25 * (iS + iE + 2 * iP); + NR::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 NR::Point m = 0.25 * (iS + iE + 2 * iP); + NR::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(NR::Point const &iS, NR::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 ) { + return; + // We always add a lineto afterwards, so this is fine. + // [on ajoute toujours un lineto apres, donc c bon] + } + + double sang; + double eang; + NR::Point dr; + ArcAnglesAndCenter(iS, iE, rx, ry, angle, large, wise, sang, eang, dr); + /* 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). */ + + NR::scale const ar(rx, ry); + NR::rotate cb(angle + sang); + if (wise) { + + double const incr = -0.1; + if ( sang < eang ) { + sang += 2*M_PI; + } + NR::rotate const omega(incr); + for (double b = sang + incr; b > eang; b += incr) { + cb = omega * cb; + AddPoint(cb.vec * ar + dr, piece, (sang - b) / (sang - eang)); + } + + } else { + + double const incr = 0.1; + if ( sang > eang ) { + sang -= 2 * M_PI; + } + NR::rotate const omega(incr); + for (double b = sang + incr ; b < eang ; b += incr) { + cb = omega * cb; + AddPoint(cb.vec * ar + dr, piece, (b - sang) / (eang - sang)); + } + } +} + +void Path::RecCubicTo(NR::Point const &iS, NR::Point const &isD, + NR::Point const &iE, NR::Point const &ieD, + double tresh, int lev, double st, double et, int piece) +{ + const NR::Point se = iE - iS; + const double dC = NR::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; + } + + NR::Point m = 0.5 * (iS + iE) + 0.125 * (isD - ieD); + NR::Point md = 0.75 * (iE - iS) - 0.125 * (isD + ieD); + double mt = (st + et) / 2; + + NR::Point hisD = 0.5 * isD; + NR::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(NR::Point const &iP, + NR::Point const &iS, + NR::Point const &iE, + double tresh, int lev, double st, double et, int piece) +{ + if ( lev <= 0 ) { + return; + } + + NR::Point ps = iS - iP; + NR::Point pe = iE - iP; + const double s = fabs(cross(pe, ps)); + if ( s < tresh ) { + return; + } + + { + const double mt = (st + et) / 2; + const NR::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(NR::Point const &iS, NR::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; + NR::Point dr; + ArcAnglesAndCenter(iS, iE, rx, ry, angle, large, wise, sang, eang, dr); + /* 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). */ + + NR::scale const ar(rx, ry); + NR::rotate cb(angle + sang); + if (wise) { + + double const incr = -0.1; + if ( sang < eang ) { + sang += 2*M_PI; + } + NR::rotate const omega(incr); + for (double b = sang + incr; b > eang ;b += incr) { + cb = omega * cb; + AddPoint(cb.vec * ar + dr, piece, (sang - b) / (sang - eang)); + } + + } else { + double const incr = 0.1; + if ( sang > eang ) { + sang -= 2*M_PI; + } + NR::rotate const omega(incr); + for (double b = sang + incr ; b < eang ; b += incr) { + cb = omega * cb; + AddPoint(cb.vec * ar + dr, piece, (b - sang) / (eang - sang)); + } + } +} + + +void Path::RecCubicTo(NR::Point const &iS, NR::Point const &isD, + NR::Point const &iE, NR::Point const &ieD, + double tresh, int lev, double st, double et, + int piece, offset_orig &orig) +{ + const NR::Point se = iE - iS; + const double dC = NR::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; + { + NR::Point os_pos; + NR::Point os_tgt; + NR::Point oe_pos; + NR::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); + + + NR::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 NR::Point m = 0.5 * (iS+iE) + 0.125 * (isD - ieD); + const NR::Point md = 0.75 * (iE - iS) - 0.125 * (isD + ieD); + const double mt = (st + et) / 2; + const NR::Point hisD = 0.5 * isD; + const NR::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(NR::Point const &iP, NR::Point const &iS,NR::Point const &iE, + double tresh, int lev, double st, double et, + int piece, offset_orig& orig) +{ + bool doneSub = false; + if ( lev <= 0 ) { + return; + } + + const NR::Point ps = iS - iP; + const NR::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; + { + NR::Point os_pos; + NR::Point os_tgt; + NR::Point oe_pos; + NR::Point oe_tgt; + NR::Point n_tgt; + NR::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; + NR::Point m = 0.25 * (iS + iE + 2 * iP); + NR::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 == NULL ) { + 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 (int i = 0; i < int(pts.size()); i++) { + dest->AddPoint(pts[i].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 ( NR::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 ( NR::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 (int i = 0; i < int(pts.size()); i++) { + dest->AddPoint(pts[i].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 ( NR::LInfty(pts[sbp].p - pts[prp].p) >= 0.00001 ) { + lEdge = dest->AddEdge(first+curP, first+pathEnd); + pathEnd = curP; + if ( NR::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 + for (int i = 0; i < int(pts.size()); i++) { + dest->AddPoint(pts[i].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 ( NR::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 ( NR::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 (int i = 0;i < int(pts.size()); i++) { + dest->AddPoint(pts[i].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 ( NR::LInfty(pts[sbp].p - pts[prp].p) >= 0.00001 ) { + lEdge = dest->AddEdge(first+pathEnd, first+curP); + pathEnd = curP; + if ( NR::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 000000000..2bcc3f244 --- /dev/null +++ b/src/livarot/PathCutting.cpp @@ -0,0 +1,1487 @@ +/* + * PathCutting.cpp + * nlivarot + * + * Created by fred on someday in 2004. + * public domain + * + */ + +#include "Path.h" +#include "livarot/path-description.h" +#include "libnr/n-art-bpath.h" +#include "libnr/nr-point-matrix-ops.h" + +void Path::DashPolyline(float head,float tail,float body,int nbD,float *dashs,bool stPlain,float stOffset) +{ + if ( nbD <= 0 || body <= 0.0001 ) return; // pas de tirets, en fait + + std::vector 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::DashSubPath(int spL, int spP, std::vector const &orig_pts, float head,float tail,float body,int nbD,float *dashs,bool stPlain,float stOffset) +{ + if ( spL <= 0 || spP == -1 ) return; + + double totLength=0; + NR::Point lastP; + lastP = orig_pts[spP].p; + for (int i=1;i 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 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 ) { + NR::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 ) { + NR::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 ) { + NR::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 ) { + NR::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; + } + } +} +#include "../display/canvas-bpath.h" + +void* Path::MakeArtBPath(void) +{ + int nb_cmd=0,max_cmd=0; + NArtBpath* bpath=(NArtBpath*)g_malloc((max_cmd+1)*sizeof(NArtBpath)); + + NR::Point lastP,bezSt,bezEn,lastMP; + int lastM=-1,bezNb=0; + for (int i=0;igetType(); + switch ( typ ) { + case descr_close: + { + if ( lastM >= 0 ) { + bpath[lastM].code=NR_MOVETO; + if ( nb_cmd >= max_cmd ) { + max_cmd=2*nb_cmd+1; + bpath=(NArtBpath*)g_realloc(bpath,(max_cmd+1)*sizeof(NArtBpath)); + } + bpath[nb_cmd].code=NR_LINETO; + bpath[nb_cmd].x3=lastMP[0]; + bpath[nb_cmd].y3=lastMP[1]; + nb_cmd++; + } + lastM=-1; + } + break; + case descr_lineto: + { + PathDescrLineTo *nData = dynamic_cast(descr_cmd[i]); + if ( nb_cmd >= max_cmd ) { + max_cmd=2*nb_cmd+1; + bpath=(NArtBpath*)g_realloc(bpath,(max_cmd+1)*sizeof(NArtBpath)); + } + bpath[nb_cmd].code=NR_LINETO; + bpath[nb_cmd].x3=nData->p[0]; + bpath[nb_cmd].y3=nData->p[1]; + nb_cmd++; + lastP=nData->p; + } + break; + case descr_moveto: + { + PathDescrMoveTo *nData = dynamic_cast(descr_cmd[i]); + if ( nb_cmd >= max_cmd ) { + max_cmd=2*nb_cmd+1; + bpath=(NArtBpath*)g_realloc(bpath,(max_cmd+1)*sizeof(NArtBpath)); + } + bpath[nb_cmd].code=NR_MOVETO_OPEN; + bpath[nb_cmd].x3=nData->p[0]; + bpath[nb_cmd].y3=nData->p[1]; + lastM=nb_cmd; + nb_cmd++; + lastP=lastMP=nData->p; + } + break; + case descr_arcto: + { + PathDescrArcTo *nData = dynamic_cast(descr_cmd[i]); + lastP=nData->p; + } + break; + case descr_cubicto: + { + PathDescrCubicTo *nData = dynamic_cast(descr_cmd[i]); + if ( nb_cmd >= max_cmd ) { + max_cmd=2*nb_cmd+1; + bpath=(NArtBpath*)g_realloc(bpath,(max_cmd+1)*sizeof(NArtBpath)); + } + bpath[nb_cmd].code=NR_CURVETO; + bpath[nb_cmd].x1=lastP[0]+0.333333*nData->start[0]; + bpath[nb_cmd].y1=lastP[1]+0.333333*nData->start[1]; + bpath[nb_cmd].x2=nData->p[0]-0.333333*nData->end[0]; + bpath[nb_cmd].y2=nData->p[1]-0.333333*nData->end[1]; + bpath[nb_cmd].x3=nData->p[0]; + bpath[nb_cmd].y3=nData->p[1]; + nb_cmd++; + lastP=nData->p; + } + break; + case descr_bezierto: + { + PathDescrBezierTo *nData = dynamic_cast(descr_cmd[i]); + if ( nb_cmd >= max_cmd ) { + max_cmd=2*nb_cmd+1; + bpath=(NArtBpath*)g_realloc(bpath,(max_cmd+1)*sizeof(NArtBpath)); + } + if ( nData->nb <= 0 ) { + bpath[nb_cmd].code=NR_LINETO; + bpath[nb_cmd].x3=nData->p[0]; + bpath[nb_cmd].y3=nData->p[1]; + nb_cmd++; + bezNb=0; + } else if ( nData->nb == 1 ){ + PathDescrIntermBezierTo *iData = dynamic_cast(descr_cmd[i+1]); + bpath[nb_cmd].code=NR_CURVETO; + bpath[nb_cmd].x1=0.333333*(lastP[0]+2*iData->p[0]); + bpath[nb_cmd].y1=0.333333*(lastP[1]+2*iData->p[1]); + bpath[nb_cmd].x2=0.333333*(nData->p[0]+2*iData->p[0]); + bpath[nb_cmd].y2=0.333333*(nData->p[1]+2*iData->p[1]); + bpath[nb_cmd].x3=nData->p[0]; + bpath[nb_cmd].y3=nData->p[1]; + nb_cmd++; + 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(descr_cmd[i]); + NR::Point p_m=nData->p,p_s=0.5*(bezSt+p_m),p_e; + if ( bezNb > 1 ) { + PathDescrIntermBezierTo *iData = dynamic_cast(descr_cmd[i+1]); + p_e=0.5*(p_m+iData->p); + } else { + p_e=bezEn; + } + + if ( nb_cmd >= max_cmd ) { + max_cmd=2*nb_cmd+1; + bpath=(NArtBpath*)g_realloc(bpath,(max_cmd+1)*sizeof(NArtBpath)); + } + bpath[nb_cmd].code=NR_CURVETO; + NR::Point cp1=0.333333*(p_s+2*p_m),cp2=0.333333*(2*p_m+p_e); + bpath[nb_cmd].x1=cp1[0]; + bpath[nb_cmd].y1=cp1[1]; + bpath[nb_cmd].x2=cp2[0]; + bpath[nb_cmd].y2=cp2[1]; + bpath[nb_cmd].x3=p_e[0]; + bpath[nb_cmd].y3=p_e[1]; + nb_cmd++; + + bezNb--; + } + } + break; + } + } + bpath[nb_cmd].code=NR_END; + return bpath; +} + +void Path::LoadArtBPath(void *iV,NR::Matrix const &trans,bool doTransformation) +{ + if ( iV == NULL ) return; + NArtBpath *bpath = (NArtBpath*)iV; + + SetBackData (false); + Reset(); + { + int i; + bool closed = false; + NR::Point lastX(0,0); + + for (i = 0; bpath[i].code != NR_END; i++) + { + switch (bpath[i].code) + { + case NR_LINETO: + lastX[0] = bpath[i].x3; + lastX[1] = bpath[i].y3; + if ( doTransformation ) { + lastX*=trans; + } + LineTo (lastX); + break; + + case NR_CURVETO: + { + NR::Point tmp,tms(0,0),tme(0,0),tm1,tm2; + tmp[0]=bpath[i].x3; + tmp[1]=bpath[i].y3; + tm1[0]=bpath[i].x1; + tm1[1]=bpath[i].y1; + tm2[0]=bpath[i].x2; + tm2[1]=bpath[i].y2; + if ( doTransformation ) { + tmp*=trans; + tm1*=trans; + tm2*=trans; + } + tms=3 * (tm1 - lastX); + tme=3 * (tmp - tm2); + CubicTo (tmp,tms,tme); + } + lastX[0] = bpath[i].x3; + lastX[1] = bpath[i].y3; + if ( doTransformation ) { + lastX*=trans; + } + break; + + case NR_MOVETO_OPEN: + case NR_MOVETO: + if (closed) Close (); + closed = (bpath[i].code == NR_MOVETO); + lastX[0] = bpath[i].x3; + lastX[1] = bpath[i].y3; + if ( doTransformation ) { + lastX*=trans; + } + MoveTo (lastX); + break; + default: + break; + } + } + if (closed) Close (); + } +} + + +/** + * \return Length of the lines in the pts vector. + */ + +double Path::Length() +{ + if ( pts.empty() ) { + return 0; + } + + NR::Point lastP = pts[0].p; + + double len = 0; + for (std::vector::const_iterator i = pts.begin(); i != pts.end(); i++) { + + if ( i->isMoveTo != polyline_moveto ) { + len += NR::L2(i->p - lastP); + } + + lastP = i->p; + } + + return len; +} + + +double Path::Surface() +{ + if ( pts.empty() ) { + return 0; + } + + NR::Point lastM = pts[0].p; + NR::Point lastP = lastM; + + double surf = 0; + for (std::vector::const_iterator i = pts.begin(); i != pts.end(); i++) { + + if ( i->isMoveTo == polyline_moveto ) { + surf += NR::cross(lastM - lastP, lastM); + lastP = lastM = i->p; + } else { + surf += NR::cross(i->p - lastP, i->p); + lastP = i->p; + } + + } + + return surf; +} + + +Path** Path::SubPaths(int &outNb,bool killNoSurf) +{ + int nbRes=0; + Path** res=NULL; + Path* curAdd=NULL; + + for (int i=0;igetType(); + 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=NULL; + } + curAdd=new Path; + curAdd->SetBackData(false); + { + PathDescrMoveTo *nData = dynamic_cast(descr_cmd[i]); + curAdd->MoveTo(nData->p); + } + break; + case descr_close: + { + curAdd->Close(); + } + break; + case descr_lineto: + { + PathDescrLineTo *nData = dynamic_cast(descr_cmd[i]); + curAdd->LineTo(nData->p); + } + break; + case descr_cubicto: + { + PathDescrCubicTo *nData = dynamic_cast(descr_cmd[i]); + curAdd->CubicTo(nData->p,nData->start,nData->end); + } + break; + case descr_arcto: + { + PathDescrArcTo *nData = dynamic_cast(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(descr_cmd[i]); + curAdd->BezierTo(nData->p); + } + break; + case descr_interm_bezier: + { + PathDescrIntermBezierTo *nData = dynamic_cast(descr_cmd[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=NULL; + + outNb=nbRes; + return res; +} +Path** Path::SubPathsWithNesting(int &outNb,bool killNoSurf,int nbNest,int* nesting,int* conts) +{ + int nbRes=0; + Path** res=NULL; + Path* curAdd=NULL; + bool increment=false; + + for (int i=0;igetType(); + 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=NULL; + } + Path* hasDad=NULL; + for (int j=0;j= 0 ) { + int dadMvt=conts[nesting[j]]; + for (int k=0;kdescr_cmd.empty() == false && res[k]->descr_cmd[0]->associated == dadMvt ) { + hasDad=res[k]; + break; + } + } + } + if ( conts[j] > i ) break; + } + if ( hasDad ) { + curAdd=hasDad; + increment=true; + } else { + curAdd=new Path; + curAdd->SetBackData(false); + increment=false; + } + PathDescrMoveTo *nData = dynamic_cast(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(descr_cmd[i]); + curAdd->LineTo(nData->p); + } + break; + case descr_cubicto: + { + PathDescrCubicTo *nData = dynamic_cast(descr_cmd[i]); + curAdd->CubicTo(nData->p,nData->start,nData->end); + } + break; + case descr_arcto: + { + PathDescrArcTo *nData = dynamic_cast(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(descr_cmd[i]); + curAdd->BezierTo(nData->p); + } + break; + case descr_interm_bezier: + { + PathDescrIntermBezierTo *nData = dynamic_cast(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=NULL; + + 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() +{ + NR::Point lastSeen(0, 0); + NR::Point lastMove(0, 0); + + { + NR::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(descr_cmd[i]); + d->p = lastPos; + break; + } + case descr_close: + { + PathDescrClose *d = dynamic_cast(descr_cmd[i]); + d->p = lastPos; + break; + } + case descr_moveto: + { + PathDescrMoveTo *d = dynamic_cast(descr_cmd[i]); + lastPos = d->p; + break; + } + case descr_lineto: + { + PathDescrLineTo *d = dynamic_cast(descr_cmd[i]); + lastPos = d->p; + break; + } + case descr_arcto: + { + PathDescrArcTo *d = dynamic_cast(descr_cmd[i]); + lastPos = d->p; + break; + } + case descr_cubicto: + { + PathDescrCubicTo *d = dynamic_cast(descr_cmd[i]); + lastPos = d->p; + break; + } + case descr_bezierto: + { + PathDescrBezierTo *d = dynamic_cast(descr_cmd[i]); + lastPos = d->p; + break; + } + case descr_interm_bezier: + { + PathDescrIntermBezierTo *d = dynamic_cast(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(descr_cmd[i]); + lastMove = lastSeen = nData->p; + hasMoved = true; + } + break; + case descr_close: + { + lastSeen=lastMove; + } + break; + case descr_lineto: + { + PathDescrLineTo *nData = dynamic_cast(descr_cmd[i]); + lastSeen=nData->p; + } + break; + case descr_cubicto: + { + PathDescrCubicTo *nData = dynamic_cast(descr_cmd[i]); + lastSeen=nData->p; + } + break; + case descr_arcto: + { + PathDescrArcTo *nData = dynamic_cast(descr_cmd[i]); + lastSeen=nData->p; + } + break; + case descr_bezierto: + { + PathDescrBezierTo *nData = dynamic_cast(descr_cmd[i]); + lastSeen=nData->p; + } + break; + case descr_interm_bezier: + { + PathDescrIntermBezierTo *nData = dynamic_cast(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 NULL; + } + + qsort(cvAbs, nbCv, sizeof(double), CmpCurv); + + cut_position *res = NULL; + nbCut = 0; + int curCv = 0; + + double len = 0; + double lastT = 0; + int lastPiece = -1; + + NR::Point lastM = pts[0].p; + NR::Point lastP = lastM; + + for (std::vector::const_iterator i = pts.begin(); i != pts.end(); i++) { + + if ( i->isMoveTo == polyline_moveto ) { + + lastP = lastM = i->p; + lastT = i->t; + lastPiece = i->piece; + + } else { + + double const add = NR::L2(i->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 = i->piece; + res[nbCut].t = theta * i->t + (1 - theta) * ( (lastPiece != i->piece) ? 0 : lastT); + nbCut++; + curAdd -= cvAbs[curCv] - curPos; + curPos = cvAbs[curCv]; + curCv++; + } + + len += add; + lastPiece = i->piece; + lastP = i->p; + lastT = i->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 inline static T square(T x) {return x*x;} +Path::cut_position Path::PointToCurvilignPosition(NR::Point const &pos) const +{ + 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) continue; + NR::Point p1, p2, localPos; + double thisRangeSquared; + double t; + + if (pts[i - 1].p == pts[i].p) { + thisRangeSquared = square(pts[i].p[NR::X] - pos[NR::X]) + square(pts[i].p[NR::Y] - pos[NR::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[NR::X] - pts[i].p[NR::X]) < fabs(pts[i - 1].p[NR::Y] - pts[i].p[NR::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[NR::X] - p1[NR::X]) / (p2[NR::Y] - p1[NR::Y]); + double intersection = p1[NR::X] - gradient * p1[NR::Y]; + /* + orthogonalGradient = -1.0 / gradient; // you are going to have numerical problems here. + orthogonalIntersection = localPos[NR::X] - orthogonalGradient * localPos[NR::Y]; + nearestY = (orthogonalIntersection - intersection) / (gradient - orthogonalGradient); + + expand out nearestY fully : + nearestY = (localPos[NR::X] - (-1.0 / gradient) * localPos[NR::Y] - intersection) / (gradient - (-1.0 / gradient)); + + multiply top and bottom by gradient: + nearestY = (localPos[NR::X] * gradient - (-1.0) * localPos[NR::Y] - intersection * gradient) / (gradient * gradient - (-1.0)); + + and simplify to get: + */ + double nearestY = (localPos[NR::X] * gradient + localPos[NR::Y] - intersection * gradient) + / (gradient * gradient + 1.0); + t = (nearestY - p1[NR::Y]) / (p2[NR::Y] - p1[NR::Y]); + if (t <= 0.0) thisRangeSquared = square(p1[NR::X] - localPos[NR::X]) + square(p1[NR::Y] - localPos[NR::Y]); + else if (t >= 1.0) thisRangeSquared = square(p2[NR::X] - localPos[NR::X]) + square(p2[NR::Y] - localPos[NR::Y]); + else thisRangeSquared = square(nearestY * gradient + intersection - localPos[NR::X]) + square(nearestY - localPos[NR::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 += NR::L2((t - pts[i - 1].t) / (pts[i].t - pts[i - 1].t) * (pts[i].p - pts[i - 1].p)); + break; + } + length += NR::L2(pts[i].p - pts[i - 1].p); + } + return length; +} + +void Path::ConvertPositionsToForced(int nbPos, cut_position *poss) +{ + if ( nbPos <= 0 ) { + return; + } + + { + NR::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(descr_cmd[i]); + d->p = lastPos; + break; + } + + case descr_close: + { + delete descr_cmd[i]; + descr_cmd[i] = new PathDescrLineTo(NR::Point(0, 0)); + + int fp = i - 1; + while ( fp >= 0 && (descr_cmd[fp]->getType()) != descr_moveto ) { + fp--; + } + + if ( fp >= 0 ) { + PathDescrMoveTo *oData = dynamic_cast(descr_cmd[fp]); + dynamic_cast(descr_cmd[i])->p = oData->p; + } + } + break; + + case descr_bezierto: + { + PathDescrBezierTo *nData = dynamic_cast(descr_cmd[i]); + NR::Point theP = nData->p; + if ( nData->nb == 0 ) { + lastPos = theP; + } + } + break; + + case descr_moveto: + { + PathDescrMoveTo *d = dynamic_cast(descr_cmd[i]); + lastPos = d->p; + break; + } + case descr_lineto: + { + PathDescrLineTo *d = dynamic_cast(descr_cmd[i]); + lastPos = d->p; + break; + } + case descr_arcto: + { + PathDescrArcTo *d = dynamic_cast(descr_cmd[i]); + lastPos = d->p; + break; + } + case descr_cubicto: + { + PathDescrCubicTo *d = dynamic_cast(descr_cmd[i]); + lastPos = d->p; + break; + } + case descr_interm_bezier: + { + PathDescrIntermBezierTo *d = dynamic_cast(descr_cmd[i]); + lastPos = d->p; + break; + } + default: + break; + } + } + } + + qsort(poss, nbPos, sizeof(cut_position), CmpPosition); + + for (int curP=0;curP= 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 + NR::Point theP; + NR::Point theT; + NR::Point startP; + startP=PrevPoint(cp-1); + if ( typ == descr_cubicto ) { + double len,rad; + NR::Point stD,enD,endP; + { + PathDescrCubicTo *oData = dynamic_cast(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(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(descr_cmd[cp]); + endP=oData->p; + } + + theP=ct*endP+(1-ct)*startP; + + InsertLineTo(endP,cp+1); + InsertForcePoint(cp+1); + { + PathDescrLineTo *nData = dynamic_cast(descr_cmd[cp]); + nData->p=theP; + } + // decalages dans le tableau des positions de coupe + for (int j=curP+1;j(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,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(descr_cmd[cp]); + nData->p=theP; + if ( delta*ct > M_PI ) { + nData->clockwise=true; + } else { + nData->clockwise=false; + } + } + // decalages dans le tableau des positions de coupe + for (int j=curP+1;j= 0 && (descr_cmd[theBDI]->getType()) != descr_bezierto ) theBDI--; + if ( (descr_cmd[theBDI]->getType()) == descr_bezierto ) { + PathDescrBezierTo theBD=*(dynamic_cast(descr_cmd[theBDI])); + if ( cp >= theBDI && cp < theBDI+theBD.nb ) { + if ( theBD.nb == 1 ) { + NR::Point endP=theBD.p; + NR::Point midP; + NR::Point startP; + startP=PrevPoint(theBDI-1); + { + PathDescrIntermBezierTo *nData = dynamic_cast(descr_cmd[theBDI+1]); + midP=nData->p; + } + NR::Point aP=ct*midP+(1-ct)*startP; + NR::Point bP=ct*endP+(1-ct)*midP; + NR::Point knotP=ct*bP+(1-ct)*aP; + + InsertIntermBezierTo(bP,theBDI+2); + InsertBezierTo(knotP,1,theBDI+2); + InsertForcePoint(theBDI+2); + { + PathDescrIntermBezierTo *nData = dynamic_cast(descr_cmd[theBDI+1]); + nData->p=aP; + } + { + PathDescrBezierTo *nData = dynamic_cast(descr_cmd[theBDI]); + nData->p=knotP; + } + // decalages dans le tableau des positions de coupe + for (int j=curP+1;j theBDI ) { + NR::Point pcP,ncP; + { + PathDescrIntermBezierTo *nData = dynamic_cast(descr_cmd[cp]); + pcP=nData->p; + } + { + PathDescrIntermBezierTo *nData = dynamic_cast(descr_cmd[cp+1]); + ncP=nData->p; + } + NR::Point knotP=0.5*(pcP+ncP); + + InsertBezierTo(knotP,theBD.nb-(cp-theBDI),cp+1); + { + PathDescrBezierTo *nData = dynamic_cast(descr_cmd[theBDI]); + nData->nb=cp-theBDI; + } + + // decalages dans le tableau des positions de coupe + for (int j=curP;j(descr_cmd[cp+1]); + pcP=nData->p; + } + { + PathDescrIntermBezierTo *nData = dynamic_cast(descr_cmd[cp+2]); + ncP=nData->p; + } + NR::Point knotP=0.5*(pcP+ncP); + + InsertBezierTo(knotP,theBD.nb-1,cp+2); + { + PathDescrBezierTo *nData = dynamic_cast(descr_cmd[theBDI]); + nData->nb=1; + } + + // decalages dans le tableau des positions de coupe + for (int j=curP;jgetType(); + if ( typ == descr_moveto ) { + NR::Point np; + { + PathDescrMoveTo *nData = dynamic_cast(descr_cmd[i]); + np=nData->p; + } + NR::Point endP; + bool hasClose=false; + int hasForced=-1; + bool doesClose=false; + int j=i+1; + for (;jgetType(); + 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(descr_cmd[j]); + endP=nData->p; + } else if ( ntyp == descr_arcto ) { + PathDescrArcTo *nData = dynamic_cast(descr_cmd[j]); + endP=nData->p; + } else if ( ntyp == descr_cubicto ) { + PathDescrCubicTo *nData = dynamic_cast(descr_cmd[j]); + endP=nData->p; + } else if ( ntyp == descr_bezierto ) { + PathDescrBezierTo *nData = dynamic_cast(descr_cmd[j]); + endP=nData->p; + } else { + } + } + if ( NR::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. + NR::Point nMvtP=PrevPoint(hasForced); + res->MoveTo(nMvtP); + NR::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(descr_cmd[k]); + res->LineTo(nData->p); + nLastP=nData->p; + } else if ( ntyp == descr_arcto ) { + PathDescrArcTo *nData = dynamic_cast(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(descr_cmd[k]); + res->CubicTo(nData->p,nData->start,nData->end); + nLastP=nData->p; + } else if ( ntyp == descr_bezierto ) { + PathDescrBezierTo *nData = dynamic_cast(descr_cmd[k]); + res->BezierTo(nData->p); + nLastP=nData->p; + } else if ( ntyp == descr_interm_bezier ) { + PathDescrIntermBezierTo *nData = dynamic_cast(descr_cmd[k]); + res->IntermBezierTo(nData->p); + } else { + } + } + if ( doesClose == false ) res->LineTo(np); + nLastP=np; + for (int k=i+1;kgetType(); + 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(descr_cmd[k]); + res->LineTo(nData->p); + nLastP=nData->p; + } else if ( ntyp == descr_arcto ) { + PathDescrArcTo *nData = dynamic_cast(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(descr_cmd[k]); + res->CubicTo(nData->p,nData->start,nData->end); + nLastP=nData->p; + } else if ( ntyp == descr_bezierto ) { + PathDescrBezierTo *nData = dynamic_cast(descr_cmd[k]); + res->BezierTo(nData->p); + nLastP=nData->p; + } else if ( ntyp == descr_interm_bezier ) { + PathDescrIntermBezierTo *nData = dynamic_cast(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(descr_cmd[i]); + res->LineTo(nData->p); + lastP=nData->p; + } else if ( typ == descr_arcto ) { + PathDescrArcTo *nData = dynamic_cast(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(descr_cmd[i]); + res->CubicTo(nData->p,nData->start,nData->end); + lastP=nData->p; + } else if ( typ == descr_bezierto ) { + PathDescrBezierTo *nData = dynamic_cast(descr_cmd[i]); + res->BezierTo(nData->p); + lastP=nData->p; + } else if ( typ == descr_interm_bezier ) { + PathDescrIntermBezierTo *nData = dynamic_cast(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:encoding=utf-8:textwidth=99 : diff --git a/src/livarot/PathOutline.cpp b/src/livarot/PathOutline.cpp new file mode 100644 index 000000000..ee656f900 --- /dev/null +++ b/src/livarot/PathOutline.cpp @@ -0,0 +1,1493 @@ +/* + * PathOutline.cpp + * nlivarot + * + * Created by fred on Fri Nov 28 2003. + * + */ + +#include "livarot/Path.h" +#include "livarot/path-description.h" +#include + +/* + * 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 == NULL ) { + return; + } + + dest->Reset(); + dest->SetBackData(false); + + outline_callbacks calls; + NR::Point endButt; + NR::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; + NR::Point curX; + NR::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(descr_cmd[curD]); + nextX = PrevPoint (curD - 1); + NR::Point isD=-nData->start; + NR::Point ieD=-nData->end; + rev->CubicTo (nextX, ieD,isD); + curX = nextX; + curD--; + } else if (typ == descr_arcto) { + PathDescrArcTo* nData = dynamic_cast(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(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); + NR::Point endNor=endButt.ccw(); + if (butt == butt_round) { + dest->ArcTo (endPos+width*endNor, 1.0001 * width, 1.0001 * width, 0.0, true, 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*endNor, 1.0001 * width, 1.0001 * width, 0.0, true, 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 == NULL) return; + dest->Reset (); + dest->SetBackData (false); + + outline_callbacks calls; + NR::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 == NULL) return; + dest->Reset (); + dest->SetBackData (false); + + outline_callbacks calls; + NR::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; + NR::Point curX; + NR::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(descr_cmd[curD]); + nextX = PrevPoint (curD - 1); + NR::Point isD=-nData->start; + NR::Point ieD=-nData->end; + rev->CubicTo (nextX, ieD,isD); + curX = nextX; + curD--; + } else if (typ == descr_arcto) { + PathDescrArcTo* nData = dynamic_cast(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(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, NR::Point &lastP, NR::Point &lastT) +{ + outline_callback_data callsData; + + callsData.orig = this; + callsData.dest = dest; + int curP = 1; + + // le moveto + NR::Point curX; + { + int firstTyp = descr_cmd[off]->getType(); + if ( firstTyp != descr_moveto ) { + curX[0] = curX[1] = 0; + curP = 0; + } else { + PathDescrMoveTo* nData = dynamic_cast(descr_cmd[off]); + curX = nData->p; + } + } + NR::Point curT(0, 0); + + bool doFirst = true; + NR::Point firstP(0, 0); + NR::Point firstT(0, 0); + + // et le reste, 1 par 1 + while (curP < num_pd) + { + int curD = off + curP; + int nType = descr_cmd[curD]->getType(); + NR::Point nextX; + NR::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(descr_cmd[curD]); + nextX = nData->p; + // et on avance + if (doFirst) { + } else { + if (closeIfNeeded) { + if ( NR::LInfty (curX- firstP) < 0.0001 ) { + OutlineJoin (dest, firstP, curT, firstT, width, join, + miter); + 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 + { + NR::Point pos; + pos = curX; + OutlineJoin (dest, pos, curT, stNor, width, join, + miter); + } + dest->LineTo (enPos+width*enNor); + + // jointure + { + NR::Point pos; + pos = firstP; + OutlineJoin (dest, enPos, enNor, firstT, width, join, + miter); + dest->Close (); + } + } + } + } + firstP = nextX; + curP++; + } + else if (nType == descr_close) + { + if (doFirst == false) + { + if (NR::LInfty (curX - firstP) < 0.0001) + { + OutlineJoin (dest, firstP, curT, firstT, width, join, + miter); + 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); + } + + dest->LineTo (enPos+width*enNor); + + // jointure + { + OutlineJoin (dest, enPos, enNor, firstT, width, join, + miter); + dest->Close (); + } + } + } + doFirst = true; + curP++; + } + else if (nType == descr_lineto) + { + PathDescrLineTo* nData = dynamic_cast(descr_cmd[curD]); + nextX = nData->p; + // test de nullité du segment + if (IsNulCurve (descr_cmd, curD, curX)) + { + curP++; + continue; + } + // et on avance + TangentOnSegAt (0.0, curX, *nData, stPos, stTgt, stTle); + TangentOnSegAt (1.0, curX, *nData, 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 + NR::Point pos; + pos = curX; + OutlineJoin (dest, pos, curT, stNor, width, join, miter); + } + + 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(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 + NR::Point pos; + pos = curX; + OutlineJoin (dest, pos, curT, stNor, width, join, miter); + } + + 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(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 + NR::Point pos; + pos = curX; + OutlineJoin (dest, pos, curT, stNor, width, join, miter); + } + + 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(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(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 + NR::Point pos; + pos = curX; + if (stTle > 0) OutlineJoin (dest, pos, curT, stNor, width, join, miter); + } + 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) { + NR::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 + NR::Point pos; + pos = curX; + OutlineJoin (dest, pos, curT, stNor, width, join, miter); + } + + 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) { + NR::Point bx=curX; + NR::Point cx=curX; + NR::Point dx=curX; + + dx = nData->p; + TangentOnBezAt (0.0, curX, *nData, *nBData, false, stPos, stTgt, stTle, stRad); + stNor=stTgt.cw(); + + ip++; + nData = dynamic_cast(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 + NR::Point pos=curX; + OutlineJoin (dest, pos, stTgt, stNor, width, join, miter); + // 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(descr_cmd[ip]); + NR::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; + + NR::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 == false) + { + } + } + +} + +/* + * + * 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 const &cmd, int curD, NR::Point const &curX) +{ + switch(cmd[curD]->getType()) { + case descr_lineto: + { + PathDescrLineTo *nData = dynamic_cast(cmd[curD]); + if (NR::LInfty(nData->p - curX) < 0.00001) { + return true; + } + return false; + } + case descr_cubicto: + { + PathDescrCubicTo *nData = dynamic_cast(cmd[curD]); + NR::Point A = nData->start + nData->end + 2*(curX - nData->p); + NR::Point B = 3*(nData->p - curX) - 2*nData->start - nData->end; + NR::Point C = nData->start; + if (NR::LInfty(A) < 0.0001 + && NR::LInfty(B) < 0.0001 + && NR::LInfty (C) < 0.0001) { + return true; + } + return false; + } + case descr_arcto: + { + PathDescrArcTo* nData = dynamic_cast(cmd[curD]); + if ( NR::LInfty(nData->p - curX) < 0.00001) { + if ((nData->large == false) + || (fabs (nData->rx) < 0.00001 + || fabs (nData->ry) < 0.00001)) { + return true; + } + } + return false; + } + case descr_bezierto: + { + PathDescrBezierTo* nBData = dynamic_cast(cmd[curD]); + if (nBData->nb <= 0) + { + if (NR::LInfty(nBData->p - curX) < 0.00001) { + return true; + } + return false; + } + else if (nBData->nb == 1) + { + if (NR::LInfty(nBData->p - curX) < 0.00001) { + int ip = curD + 1; + PathDescrIntermBezierTo* nData = dynamic_cast(cmd[ip]); + if (NR::LInfty(nData->p - curX) < 0.00001) { + return true; + } + } + return false; + } else if (NR::LInfty(nBData->p - curX) < 0.00001) { + for (int i = 1; i <= nBData->nb; i++) { + int ip = curD + i; + PathDescrIntermBezierTo* nData = dynamic_cast(cmd[ip]); + if (NR::LInfty(nData->p - curX) > 0.00001) { + return false; + } + } + return true; + } + } + default: + return true; + } +} + +// tangents and cuvarture 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, NR::Point const &iS, PathDescrLineTo const &fin, + NR::Point &pos, NR::Point &tgt, double &len) +{ + NR::Point const iE = fin.p; + NR::Point const seg = iE - iS; + double const l = L2(seg); + if (l <= 0.000001) { + pos = iS; + tgt = NR::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 NR::Point &iS, PathDescrArcTo const &fin, + NR::Point &pos, NR::Point &tgt, double &len, double &rad) +{ + NR::Point const iE = fin.p; + double const rx = fin.rx; + double const ry = fin.ry; + double const angle = fin.angle; + 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; + if (l >= 4) + return; + 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 == true) + { + 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 == false) + { + 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; + NR::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; + NR::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; + } +} +void +Path::TangentOnCubAt (double at, NR::Point const &iS, PathDescrCubicTo const &fin, bool before, + NR::Point &pos, NR::Point &tgt, double &len, double &rad) +{ + const NR::Point E = fin.p; + const NR::Point Sd = fin.start; + const NR::Point Ed = fin.end; + + pos = iS; + tgt = NR::Point(0,0); + len = rad = 0; + + const NR::Point A = Sd + Ed - 2*E + 2*iS; + const NR::Point B = 0.5*(Ed - Sd); + const NR::Point C = 0.25*(6*E - 6*iS - Sd - Ed); + const NR::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 NR::Point der = (3 * atb * atb)*A + (2 * atb)*B + C; + const NR::Point dder = (6 * atb)*A + 2*B; + const NR::Point ddder = 6 * A; + + double l = NR::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(ddder,dder)); + tgt = dder / l; + if (before) { + tgt = -tgt; + } + return; + } + len = l; + + rad = -l * (dot(der,der)) / (cross(dder,der)); + + tgt = der / l; +} + +void +Path::TangentOnBezAt (double at, NR::Point const &iS, + PathDescrIntermBezierTo & mid, + PathDescrBezierTo & fin, bool before, NR::Point & pos, + NR::Point & tgt, double &len, double &rad) +{ + pos = iS; + tgt = NR::Point(0,0); + len = rad = 0; + + const NR::Point A = fin.p + iS - 2*mid.p; + const NR::Point B = 2*mid.p - 2 * iS; + const NR::Point C = iS; + + pos = at * at * A + at * B + C; + const NR::Point der = 2 * at * A + B; + const NR::Point dder = 2 * A; + double l = NR::L2(der); + + if (l <= 0.0001) { + l = NR::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(dder,der)); + + tgt = der / l; +} + +void +Path::OutlineJoin (Path * dest, NR::Point pos, NR::Point stNor, NR::Point enNor, double width, + JoinType join, double miter) +{ + const double angSi = cross (enNor,stNor); + const double angCo = dot (stNor, enNor); + // 1/1000 is very big/ugly, but otherwise it stuffs things up a little... + // 1/1000 est tres grossier, mais sinon ca merde tout azimut + if ((width >= 0 && angSi > -0.001) + || (width < 0 && angSi < 0.001)) { + if (angCo > 0.999) { + // straight ahead + // tout droit + } else if (angCo < -0.999) { + // half turn + // demit-tour + dest->LineTo (pos + width*enNor); + } else { + dest->LineTo (pos); + dest->LineTo (pos + width*enNor); + } + } else { + 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 ) { + NR::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 { + NR::Point biNor; + NR::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) { + NR::Point const biss = unit_vector(NR::rot90( stNor - enNor )); + double c2 = NR::dot (biss, enNor); + double l = width / c2; + if ( fabs(l) > miter) { + dest->LineTo (pos + width*enNor); + } else { + dest->LineTo (pos+l*biss); + dest->LineTo (pos+width*enNor); + } + } else { + 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) +{ + NR::Point stPos, miPos, enPos; + NR::Point stTgt, enTgt, miTgt, stNor, enNor, miNor; + double stRad, miRad, enRad; + double stTle, miTle, enTle; + // un cubic + { + PathDescrCubicTo temp(NR::Point(data->x2, data->y2), + NR::Point(data->d.c.dx1, data->d.c.dy1), + NR::Point(data->d.c.dx2, data->d.c.dy2)); + + NR::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; + } + + NR::Point chk; + const NR::Point req = miPos + width * miNor; + { + PathDescrCubicTo temp(enPos + width * enNor, + stGue * stTgt, + enGue * enTgt); + double chTle, chRad; + NR::Point chTgt; + TangentOnCubAt (0.5, stPos+width*stNor, + temp, false, chk, chTgt, chTle, chRad); + } + const NR::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(NR::Point(data->x2, data->y2), 1); + PathDescrIntermBezierTo tempi(NR::Point(data->d.b.mx, data->d.b.my)); + NR::Point stPos, enPos, stTgt, enTgt; + double stRad, enRad, stTle, enTle; + NR::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) +{ + NR::Point stPos, miPos, enPos; + NR::Point stTgt, enTgt, miTgt, stNor, enNor, miNor; + double stRad, miRad, enRad; + double stTle, miTle, enTle; + // un cubic + { + PathDescrArcTo temp(NR::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); + + NR::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; + { + NR::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, 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; + } + + NR::Point chk; + const NR::Point req = miPos + width*miNor; + { + PathDescrCubicTo temp(enPos + width * enNor, stGue * scal * stTgt, enGue * scal * enTgt); + double chTle, chRad; + NR::Point chTgt; + TangentOnCubAt (0.5, stPos+width*stNor, + temp, false, chk, chTgt, chTle, chRad); + } + const NR::Point diff = req - chk; + const double err = (dot(diff,diff)); + if (err <= tol * tol) + { + 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 000000000..44b138263 --- /dev/null +++ b/src/livarot/PathSimplify.cpp @@ -0,0 +1,1397 @@ +/* + * PathSimplify.cpp + * nlivarot + * + * Created by fred on Fri Dec 12 2003. + * + */ + +#include +#include +#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) +{ + if (pts.size() <= 1) { + return; + } + + Reset(); + + int lastM = 0; + while (lastM < int(pts.size())) { + int lastP = lastM + 1; + while (lastP < int(pts.size()) + && (pts[lastP].isMoveTo == polyline_lineto + || pts[lastP].isMoveTo == polyline_forced)) + { + lastP++; + } + + DoSimplify(lastM, lastP - lastM, treshhold); + + lastM = lastP; + } +} + + +// 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 metod +double RecDistanceToCubic(NR::Point const &iS, NR::Point const &isD, + NR::Point const &iE, NR::Point const &ieD, + NR::Point &pt, double current, int lev, double st, double et) +{ + if ( lev <= 0 ) { + return current; + } + + NR::Point const m = 0.5 * (iS + iE) + 0.125 * (isD - ieD); + NR::Point const md = 0.75 * (iE - iS) - 0.125 * (isD + ieD); + double const mt = (st + et) / 2; + + NR::Point const hisD = 0.5 * isD; + NR::Point const hieD = 0.5 * ieD; + + NR::Point const mp = pt - m; + double nle = NR::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; +} + + +double DistanceToCubic(NR::Point const &start, PathDescrCubicTo res, NR::Point &pt) +{ + NR::Point const sp = pt - start; + NR::Point const ep = pt - res.p; + double nle = NR::dot(sp, sp); + double nnle = NR::dot(ep, ep); + if ( nnle < nle ) { + nle = nnle; + } + + NR::Point seg = res.p - start; + nnle = NR::cross(seg, sp); + nnle *= nnle; + nnle /= NR::dot(seg, seg); + if ( nnle < nle ) { + if ( NR::dot(sp,seg) >= 0 ) { + seg = start - res.p; + if ( NR::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 + if (N <= 1) { + return; + } + + int curP = 0; + + fitting_tables data; + data.Xk = data.Yk = data.Qk = NULL; + data.tk = data.lk = NULL; + data.fk = NULL; + data.totLen = 0; + data.nbPt = data.maxPt = data.inPt = 0; + + NR::Point const moveToPt = pts[off].p; + MoveTo(moveToPt); + NR::Point endToPt = moveToPt; + + while (curP < N - 1) { + + int lastP = curP + 1; + int M = 2; + + // remettre a zero + data.inPt = data.nbPt = 0; + + PathDescrCubicTo res(NR::Point(0, 0), NR::Point(0, 0), NR::Point(0, 0)); + bool contains_forced = false; + int step = 64; + + while ( step > 0 ) { + int forced_pt = -1; + int worstP = -1; + + do { + if (pts[off + lastP].isMoveTo == polyline_forced) { + contains_forced = true; + } + forced_pt = lastP; + lastP += step; + M += step; + } while (lastP < N && ExtendFit(off + curP, M, data, + (contains_forced) ? 0.05 * treshhold : treshhold, + res, worstP) ); + if (lastP >= N) { + + lastP -= step; + M -= step; + + } else { + // le dernier a echoue + lastP -= step; + M -= step; + + if ( contains_forced ) { + lastP = forced_pt; + M = lastP - curP + 1; + } + + AttemptSimplify(off + curP, M, treshhold, res, worstP); // ca passe forcement + } + step /= 2; + } + + endToPt = pts[off + lastP].p; + if (M <= 2) { + LineTo(endToPt); + } else { + CubicTo(endToPt, res.start, res.end); + } + + curP = lastP; + } + + if (NR::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 behing 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(NR::Point const &start, PathDescrCubicTo &res, + double *Xk, double *Yk, double *Qk, double *tk, int nbPt) +{ + NR::Point const end = res.p; + + // la matrice tNN + NR::Matrix 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; + } + + NR::Matrix 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 + NR::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]; + } + + NR::Point P = Q * M; + NR::Point cp1; + NR::Point cp2; + cp1[NR::X] = P[NR::X]; + cp2[NR::X] = P[NR::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 = NR::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[NR::Y] = P[NR::X]; + cp2[NR::Y] = P[NR::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 >= 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)); + } + + if ( N > data.inPt ) { + for (int i = data.inPt; i < N; i++) { + data.Xk[i] = pts[off + i].p[NR::X]; + data.Yk[i] = pts[off + i].p[NR::Y]; + data.fk[i] = ( pts[off + i].isMoveTo == polyline_forced ) ? 0x01 : 0x00; + } + data.lk[0] = 0; + data.tk[0] = 0; + + double prevLen = 0; + for (int i = 0; i < data.inPt; i++) { + prevLen += data.lk[i]; + } + data.totLen = prevLen; + + for (int i = ( (data.inPt > 0) ? data.inPt : 1); i < N; i++) { + NR::Point diff; + diff[NR::X] = data.Xk[i] - data.Xk[i - 1]; + diff[NR::Y] = data.Yk[i] - data.Yk[i - 1]; + data.lk[i] = NR::L2(diff); + data.totLen += data.lk[i]; + data.tk[i] = data.totLen; + } + + for (int i = 0; i < data.inPt; i++) { + data.tk[i] *= prevLen; + data.tk[i] /= data.totLen; + } + + for (int i = data.inPt; i < N; i++) { + data.tk[i] /= data.totLen; + } + data.inPt = N; + } + + if ( N < data.nbPt ) { + // on est allŽ trop loin + // faut recalculer les 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; + + 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; + } + + if ( data.totLen < 0.0001 ) { + double worstD = 0; + NR::Point start; + worstP = -1; + start[0] = data.Xk[0]; + start[1] = data.Yk[0]; + for (int i = 1; i < N; i++) { + NR::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; + } + + 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) +{ + NR::Point start,end; + // pour une coordonnee + NR::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++) { + NR::Point nPt; + nPt[NR::X] = data.Xk[i]; + nPt[NR::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; + NR::Point prevAppP; + NR::Point prevP; + double prevDist; + prevP[NR::X] = data.Xk[0]; + prevP[NR::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++) { + NR::Point curAppP; + NR::Point curP; + double curDist; + NR::Point midAppP; + NR::Point midP; + double midDist; + + curAppP[NR::X] = N13(data.tk[i]) * cp1[NR::X] + + N23(data.tk[i]) * cp2[NR::X] + + N03(data.tk[i]) * data.Xk[0] + + N33(data.tk[i]) * data.Xk[data.nbPt - 1]; + + curAppP[NR::Y] = N13(data.tk[i]) * cp1[NR::Y] + + N23(data.tk[i]) * cp2[NR::Y] + + N03(data.tk[i]) * data.Yk[0] + + N33(data.tk[i]) * data.Yk[data.nbPt - 1]; + + curP[NR::X] = data.Xk[i]; + curP[NR::Y] = data.Yk[i]; + double mtk = 0.5 * (data.tk[i] + data.tk[i - 1]); + + midAppP[NR::X] = N13(mtk) * cp1[NR::X] + + N23(mtk) * cp2[NR::X] + + N03(mtk) * data.Xk[0] + + N33(mtk) * data.Xk[data.nbPt - 1]; + + midAppP[NR::Y] = N13(mtk) * cp1[NR::Y] + + N23(mtk) * cp2[NR::Y] + + N03(mtk) * data.Yk[0] + + N33(mtk) * data.Yk[data.nbPt - 1]; + + midP = 0.5 * (curP + prevP); + + NR::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++) { + NR::Point curAppP; + NR::Point curP; + double curDist; + + curAppP[NR::X] = N13(data.tk[i]) * cp1[NR::X] + + N23(data.tk[i]) * cp2[NR::X] + + N03(data.tk[i]) * data.Xk[0] + + N33(data.tk[i]) * data.Xk[data.nbPt - 1]; + + curAppP[NR::Y] = N13(data.tk[i]) * cp1[NR::Y] + + N23(data.tk[i]) * cp2[NR::Y] + + N03(data.tk[i]) * data.Yk[0] + + N33(data.tk[i]) * data.Yk[data.nbPt - 1]; + + curP[NR::X] = data.Xk[i]; + curP[NR::Y] = data.Yk[i]; + + NR::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++) { + NR::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; + NR::Point prevAppP; + NR::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++) { + NR::Point curAppP; + NR::Point curP; + double curDist; + NR::Point midAppP; + NR::Point midP; + double midDist; + + curAppP[NR::X] = N13(data.tk[i]) * cp1[NR::X] + + N23(data.tk[i]) * cp2[NR::X] + + N03(data.tk[i]) * data.Xk[0] + + N33(data.tk[i]) * data.Xk[data.nbPt - 1]; + + curAppP[NR::Y] = N13(data.tk[i]) * cp1[NR::Y] + + N23(data.tk[i]) * cp2[NR::Y] + + N03(data.tk[i]) * data.Yk[0] + + N33(data.tk[i]) * data.Yk[data.nbPt - 1]; + + curP[NR::X] = data.Xk[i]; + curP[NR::Y] = data.Yk[i]; + double mtk = 0.5 * (data.tk[i] + data.tk[i - 1]); + + midAppP[NR::X] = N13(mtk) * cp1[NR::X] + + N23(mtk) * cp2[NR::X] + + N03(mtk) * data.Xk[0] + + N33(mtk) * data.Xk[data.nbPt - 1]; + + midAppP[NR::Y] = N13(mtk) * cp1[NR::Y] + + N23(mtk) * cp2[NR::Y] + + N03(mtk) * data.Yk[0] + + N33(mtk) * data.Yk[data.nbPt - 1]; + + midP = 0.5 * (curP + prevP); + + NR::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++) { + NR::Point curAppP; + NR::Point curP; + double curDist; + + curAppP[NR::X] = N13(data.tk[i]) * cp1[NR::X] + + N23(data.tk[i]) * cp2[NR::X] + + N03(data.tk[i]) * data.Xk[0] + + N33(data.tk[i]) * data.Xk[data.nbPt - 1]; + + curAppP[NR::Y] = N13(data.tk[i]) * cp1[NR::Y] + + N23(data.tk[i]) * cp2[1] + + N03(data.tk[i]) * data.Yk[0] + + N33(data.tk[i]) * data.Yk[data.nbPt - 1]; + + curP[NR::X] = data.Xk[i]; + curP[NR::Y] = data.Yk[i]; + + NR::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) +{ + NR::Point start; + NR::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 + + NR::Point cp1; + NR::Point cp2; + + if (N == 2) { + worstP = 1; + return true; + } + + start = pts[off].p; + cp1 = pts[off + 1].p; + end = pts[off + N - 1].p; + + res.p = end; + res.start[0] = res.start[1] = 0; + res.end[0] = res.end[1] = 0; + 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. + 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; + { + NR::Point prevP = start; + for (int i = 1; i < N; i++) { + Xk[i] = pts[off + i].p[NR::X]; + Yk[i] = pts[off + i].p[NR::Y]; + + if ( pts[off + i].isMoveTo == polyline_forced ) { + fk[i] = 0x01; + } else { + fk[i] = 0; + } + + NR::Point diff(Xk[i] - prevP[NR::X], Yk[i] - prevP[1]); + prevP[0] = Xk[i]; + prevP[1] = Yk[i]; + lk[i] = NR::L2(diff); + tk[i] = tk[i - 1] + lk[i]; + } + } + + if (tk[N - 1] < 0.00001) { + // longueur nulle + 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++) { + NR::Point nPt; + bool isForced = fk[i]; + nPt[0] = Xk[i]; + nPt[1] = 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; + } + } + } + + g_free(tk); + g_free(Qk); + g_free(Xk); + g_free(Yk); + g_free(fk); + g_free(lk); + + return false; + } + + 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) ) { + cp1 = start + res.start / 3; + cp2 = end + res.end / 3; + } else { + // aie, non-inversible + 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++) { + NR::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 + double delta = 0; + { + double worstD = 0; + worstP = -1; + NR::Point prevAppP; + NR::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++) + { + NR::Point curAppP; + NR::Point curP; + double curDist; + NR::Point midAppP; + NR::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); + + NR::Point diff; + diff = curAppP-curP; + curDist = dot(diff,diff); + + diff = midAppP-midP; + midDist = dot(diff,diff); + + delta+=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; + } + delta/=totLen; + } else { +#endif + for (int i = 1; i < N - 1; i++) + { + NR::Point curAppP; + NR::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]; + + NR::Point diff; + diff = curAppP-curP; + curDist = dot(diff,diff); + delta += 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 + } + + if (delta < treshhold * treshhold) + { + // premier jet + res.start = 3.0 * (cp1 - start); + res.end = -3.0 * (cp2 - end); + res.p = end; + + // Refine a little. + for (int i = 1; i < N - 1; i++) + { + NR::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) ) { + } else { + // 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; + { + double worstD = 0; + worstP = -1; + NR::Point prevAppP; + NR::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++) + { + NR::Point curAppP; + NR::Point curP; + double curDist; + NR::Point midAppP; + NR::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); + + NR::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++) + { + NR::Point curAppP; + NR::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]; + + NR::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) + { + return true; + } else { + // nothing better to do + res.start = 3.0 * (cp1 - start); + res.end = -3.0 * (cp2 - end); + } + return true; + } else { + // 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 (NR::Point pt, NR::Point p0, NR::Point p1, NR::Point p2, NR::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[NR::X] - + p0[NR::X] * N03(it) - + p1[NR::X] * N13(it) - + p2[NR::X] * N23(it) - + p3[NR::X] * N33(it); + + double const Bx = (p1[NR::X] - p0[NR::X]) * N02(it) + + (p2[NR::X] - p1[NR::X]) * N12(it) + + (p3[NR::X] - p2[NR::X]) * N22(it); + + double const Cx = (p0[NR::X] - 2 * p1[NR::X] + p2[NR::X]) * N01(it) + + (p3[NR::X] - 2 * p2[NR::X] + p1[NR::X]) * N11(it); + + double const Ay = pt[NR::Y] - + p0[NR::Y] * N03(it) - + p1[NR::Y] * N13(it) - + p2[NR::Y] * N23(it) - + p3[NR::Y] * N33(it); + + double const By = (p1[NR::Y] - p0[NR::Y]) * N02(it) + + (p2[NR::Y] - p1[NR::Y]) * N12(it) + + (p3[NR::Y] - p2[NR::Y]) * N22(it); + + double const Cy = (p0[NR::Y] - 2 * p1[NR::Y] + p2[NR::Y]) * N01(it) + + (p3[NR::Y] - 2 * p2[NR::Y] + p1[NR::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 was to reduce the number of path commands, especially when ooperations 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 tableau, it's no longer worth optimizing + // the rewriting in the same tableau. + // [[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; + NR::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. + */ + PathDescr *lastAddition = new PathDescrMoveTo(NR::Point(0, 0)); + bool containsForced = false; + PathDescrCubicTo pending_cubic(NR::Point(0, 0), NR::Point(0, 0), NR::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,pending_cubic,lastAP); + } + lastAddition = descr_cmd[curP]; + lastAP = curP; + FlushPendingAddition(tempDest, lastAddition, pending_cubic, lastAP); + // Added automatically (too bad about multiple moveto's). + // [fr: (tant pis pour les moveto multiples)] + containsForced = false; + + PathDescrMoveTo *nData = dynamic_cast(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(NR::Point(0, 0), NR::Point(0, 0), NR::Point(0, 0)); + int worstP = -1; + if (AttemptSimplify(lastA, nextA - lastA + 1, (containsForced) ? 0.05 * tresh : tresh, res, worstP)) { + lastAddition = new PathDescrCubicTo(NR::Point(0, 0), + NR::Point(0, 0), + NR::Point(0, 0)); + pending_cubic = res; + lastAP = -1; + } + + FlushPendingAddition(tempDest, lastAddition, pending_cubic, lastAP); + FlushPendingAddition(tempDest, descr_cmd[curP], pending_cubic, curP); + + } else { + FlushPendingAddition(tempDest,descr_cmd[curP],pending_cubic,curP); + } + + containsForced = false; + lastAddition = new PathDescrMoveTo(NR::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(NR::Point(0, 0), NR::Point(0, 0), NR::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 + containsForced = true; + } else { + // on force l'addition + FlushPendingAddition(tempDest, lastAddition, pending_cubic, lastAP); + lastAddition = new PathDescrMoveTo(NR::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(NR::Point(0, 0), NR::Point(0, 0), NR::Point(0, 0)); + int worstP = -1; + if (AttemptSimplify(lastA, nextA - lastA + 1, tresh, res, worstP)) { + lastAddition = new PathDescrCubicTo(NR::Point(0, 0), + NR::Point(0, 0), + NR::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 + FlushPendingAddition(tempDest, lastAddition, pending_cubic, lastAP); + lastAddition = descr_cmd[curP]; + if ( typ == descr_cubicto ) { + pending_cubic = *(dynamic_cast(descr_cmd[curP])); + } + lastAP = curP; + containsForced = false; + } + + } else { + lastA = prevA /*descr_cmd[curP-1]->associated */ ; + lastAddition = descr_cmd[curP]; + if ( typ == descr_cubicto ) { + pending_cubic = *(dynamic_cast(descr_cmd[curP])); + } + lastAP = curP; + containsForced = false; + } + prevA = nextA; + + } else if (typ == descr_bezierto) { + + if (lastAddition->flags != descr_moveto) { + FlushPendingAddition(tempDest, lastAddition, pending_cubic, lastAP); + lastAddition = new PathDescrMoveTo(NR::Point(0, 0)); + } + lastAP = -1; + lastA = descr_cmd[curP]->associated; + lastP = curP; + PathDescrBezierTo *nBData = dynamic_cast(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, 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(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(descr_cmd[lastAP]); + dest->LineTo(nData->p); + } + break; + + case descr_arcto: + if ( lastAP >= 0 ) { + PathDescrArcTo *nData = dynamic_cast(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(descr_cmd[lastAP]); + dest->BezierTo(nData->p); + } + break; + + case descr_interm_bezier: + if ( lastAP >= 0 ) { + PathDescrIntermBezierTo *nData = dynamic_cast(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:encoding=utf-8:textwidth=99 : diff --git a/src/livarot/PathStroke.cpp b/src/livarot/PathStroke.cpp new file mode 100644 index 000000000..c182b93aa --- /dev/null +++ b/src/livarot/PathStroke.cpp @@ -0,0 +1,756 @@ +/* + * PathStroke.cpp + * nlivarot + * + * Created by fred on Tue Jun 17 2003. + * + */ + +#include "Path.h" +#include "Shape.h" +#include + +/* + * 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 +NR::Point StrokeNormalize(const NR::Point value) { + double length = L2(value); + if ( length < 0.0000001 ) { + return NR::Point(0, 0); + } else { + return value/length; + } +} + +// faster version if length is known +NR::Point StrokeNormalize(const NR::Point value, double length) { + if ( length < 0.0000001 ) { + return NR::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 == NULL) { + 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 ) { + NR::Point sbStart = pts[lastM].p; + NR::Point sbEnd = pts[lastP - 1].p; + if ( NR::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 }; + NR::Point dir; + dir[0] = 1; + dir[1] = 0; + NR::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; + } + + NR::Point prevP, nextP; + int prevI, nextI; + int upTo; + + int curI = 0; + NR::Point curP = pts[off].p; + + if (doClose) { + + prevI = N - 1; + while (prevI > 0) { + prevP = pts[off + prevI].p; + NR::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; + NR::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 }; + NR::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 }; + NR::Point prevD = curP - prevP; + NR::Point nextD = nextP - curP; + double prevLe = NR::L2(prevD); + double nextLe = NR::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; + NR::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 = NR::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 = NR::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, NR::Point pos, NR::Point dir, + int &leftNo, int &rightNo) +{ + NR::Point nor; + nor = dir.ccw(); + + if (butt == butt_square) + { + NR::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 NR::Point sx = pos + width * nor; + const NR::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, NR::Point pos, NR::Point prev, + NR::Point next, double miter, double prevL, double nextL, + int *stNo, int *enNo) +{ + NR::Point pnor = prev.ccw(); + NR::Point nnor = next.ccw(); + double angSi = cross(next, prev); + + /* 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 NR::Point sx = pos + width * pnor; + const NR::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 NR::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) { + NR::Point sx = pos - width * pnor; + stNo[RIGHT] = dest->AddPoint(sx); + NR::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 NR::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) { + + NR::Point sx = pos + width * pnor; + stNo[LEFT] = dest->AddPoint(sx); + NR::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, NR::Point pos, + NR::Point prev, NR::Point next, double miter, double prevL, double nextL, + int &leftStNo, int &leftEnNo,int pathID,int pieceID,double tID) +{ + NR::Point pnor=prev.ccw(); + NR::Point nnor=next.ccw(); + double angSi = cross (next, prev); + 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) + { + /* NR::Point biss; + biss.x=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; + 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); + 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 NR::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 (biss, nnor); + double dec = (l - emiter) * c2 / s2; + const NR::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 NR::Point sx = pos + width * pnor; + leftStNo = dest->AddPoint (sx); + const NR::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, NR::Point pos, + NR::Point prev, NR::Point next, double miter, double prevL, + double nextL, int &rightStNo, int &rightEnNo,int pathID,int pieceID,double tID) +{ + const NR::Point pnor=prev.ccw(); + const NR::Point nnor=next.ccw(); + double angSi = cross (next,prev); + 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 NR::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 (biss, nnor); + double dec = (l - emiter) * c2 / s2; + const NR::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 NR::Point sx = pos - width * pnor; + rightStNo = dest->AddPoint (sx); + const NR::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 + { + /* NR::Point biss; + biss=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; + 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); + 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 + NR::Point const &iS, NR::Point const &iE, // start and end point + NR::Point const &nS, NR::Point const &nE, // start and end normal vector + NR::Point &origine, float width) // center and radius of round +{ + //NR::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(nS, nE); + 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); + NR::rotate omega(((sia > 0) ? -lod : lod)); + NR::Point cur = iS - origine; + // StrokeNormalize(cur); + // cur*=width; + int lastNo = sNo; + for (int i = 0; i < nbS; i++) { + cur = cur * omega; + NR::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)) +indent-tabs-mode:nil +fill-column:99 +End: + */ +// vim: filetype=c++:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/livarot/Shape.cpp b/src/livarot/Shape.cpp new file mode 100644 index 000000000..eead99225 --- /dev/null +++ b/src/livarot/Shape.cpp @@ -0,0 +1,2282 @@ +/* + * Shape.cpp + * nlivarot + * + * Created by fred on Thu Jun 12 2003. + * + */ + +#include +#include "Shape.h" +#include "livarot/sweep-event-queue.h" +#include "livarot/sweep-tree-list.h" +#include + +/* + * 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() + : iData(NULL), + sTree(NULL), + sEvts(NULL), + _need_points_sorting(false), + _need_edges_sorting(false), + _has_points_data(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) +{ + leftX = topY = rightX = bottomY = 0; + maxPt = 0; + maxAr = 0; + + type = shape_polygon; +} +Shape::~Shape (void) +{ + maxPt = 0; + maxAr = 0; +} + +void Shape::Affiche(void) +{ + /* + printf("sh=%p nbPt=%i nbAr=%i\n",this,nbPt,nbAr); // localizing ok + for (int i=0;inumberOfPoints(), who->numberOfEdges()); + type = who->type; + _need_points_sorting = who->_need_points_sorting; + _need_edges_sorting = who->_need_edges_sorting; + _has_points_data = 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; + + _pts = who->_pts; + _aretes = who->_aretes; +} + +void +Shape::Reset (int n, int m) +{ + _pts.clear(); + _aretes.clear(); + + type = shape_polygon; + if (n > maxPt) + { + maxPt = n; + if (_has_points_data) + pData.resize(maxPt); + if (_has_voronoi_data) + vorpData.resize(maxPt); + } + if (m > maxAr) + { + maxAr = m; + 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; +} + +int +Shape::AddPoint (const NR::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 = NULL; + pData[n].askForWindingB = -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 (void) +{ + if (_need_points_sorting && hasPoints()) + SortPoints (0, numberOfPoints() - 1); + _need_points_sorting = false; +} + +void +Shape::SortPointsRounded (void) +{ + 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 = NR::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 = NULL; + 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 = NR::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 = NULL; + 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 (void) +{ + if (_need_edges_sorting == false) { + return; + } + _need_edges_sorting = false; + + edge_list *list = (edge_list *) g_malloc(numberOfEdges() * sizeof (edge_list)); + for (int p = 0; p < numberOfPoints(); p++) + { + int const d = getPoint(p).totalDegree(); + if (d > 1) + { + int cb; + cb = getPoint(p).incidentEdge[FIRST]; + int nb = 0; + 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); + } + SortEdgesList (list, 0, nb - 1); + _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 (NR::Point ax, NR::Point bx,bool as,bool bs) +{ + 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; + + 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; + } + } + if (quadA < quadB) + return 1; + if (quadA > quadB) + return -1; + + NR::Point av, bv; + av = ax; + bv = bx; + double si = cross (bv, av); + int tstSi = 0; + if (si > 0.000001) tstSi = 1; + if (si < -0.000001) tstSi = -1; + if ( tstSi == 0 ) { + if ( as == true && bs == false ) return -1; + if ( as == false && bs == true ) return 1; + } + 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; + NR::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 (hasPoints() == false) + { + leftX = rightX = topY = bottomY = 0; + 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]; + } + } + } +} + +// 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 NR::Point px) const +{ + int lr = 0, ll = 0, rr = 0; + + for (int i = 0; i < numberOfEdges(); i++) + { + NR::Point const adir = getEdge(i).dx; + + NR::Point const ast = getPoint(getEdge(i).st).x; + NR::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; + } + + NR::Point const diff = px - ast; + double const cote = cross(diff, adir); + 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() +{ + 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]); + } +} + +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 = NULL; + swsData[i].firstLinkedPoint = -1; + swsData[i].stPt = swsData[i].enPt = -1; + swsData[i].leftRnd = swsData[i].rightRnd = -1; + swsData[i].nextSh = NULL; + swsData[i].nextBo = -1; + swsData[i].curPoint = -1; + swsData[i].doneTo = -1; + } +} + + +void Shape::clearIncidenceData() +{ + g_free(iData); + iData = NULL; + 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, NR::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 = NR::dot(p - s->getPoint(0).x, p - s->getPoint(0).x); + + for (int i = 0; i < s->numberOfPoints(); i++) { + NR::Point const offset( p - s->getPoint(i).x ); + double ndot = NR::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 */ + NR::Point const st(s->getPoint(s->getEdge(i).st).x); // edge start + NR::Point const en(s->getPoint(s->getEdge(i).en).x); // edge end + + NR::Point const d(p - st); // vector between p and edge start + NR::Point const e(en - st); // vector of the edge + double const el = NR::dot(e, e); // edge length + + /* Update bdot if appropriate */ + if ( el > 0.001 ) { + double const npr = NR::dot(d, e); + if ( npr > 0 && npr < el ) { + double const nl = fabs( NR::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, NR::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++) { + NR::Point const offset( p - s->getPoint(i).x ); + double const l1 = NR::L1(offset); + if ( (l1 <= max_l2) || ((l1 <= max_l1) && (NR::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 ) { + NR::Point const st(s->getPoint(s->getEdge(i).st).x); + NR::Point const en(s->getPoint(s->getEdge(i).en).x); + NR::Point const d(p - st); + NR::Point const e(en - st); + double const el = NR::L2(e); + if ( el > 0.001 ) { + NR::Point const e_unit(e / el); + double const npr = NR::dot(d, e_unit); + if ( npr > 0 && npr < el ) { + double const nl = fabs(NR::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 000000000..dad27e17b --- /dev/null +++ b/src/livarot/Shape.h @@ -0,0 +1,562 @@ +/* + * Digraph.h + * nlivarot + * + * Created by fred on Thu Jun 12 2003. + * + */ + +#ifndef my_shape +#define my_shape + +#include +#include +#include +#include +#include +#include + +#include "libnr/nr-point.h" +#include "livarot/livarot-forward.h" +#include "livarot/LivarotDefs.h" + +struct SweepTree; +struct SweepTreeList; +struct SweepEventQueue; + +/* + * 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 IntLigne; +class BitLigne; +class AlphaLigne; + +class Shape +{ +public: + + struct back_data + { + int pathID, pieceID; + double tSt, tEn; + }; + + 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 sTreeChangeType + { + EDGE_INSERTED = 0, + EDGE_REMOVED = 1, + INTERSECTION = 2 + }; + + 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(); + ~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 + void Reset(int n = 0, int m = 0); + // -points: + int AddPoint(const NR::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 + 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 + 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 + 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 + 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 + 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, 5)), -5); + } + + // 2 miscannellous variations on it, to scale to and back the rounding grid + inline static double HalfRound(double x) + { + return ldexp(x, -5); + } + + inline static double IHalfRound(double x) + { + return ldexp(x, 5); + } + + // 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); + + int PtWinding(const NR::Point px) const; // plus rapide + int Winding(const NR::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(NR::Matrix const &tr) + {for(std::vector::iterator it=_pts.begin();it!=_pts.end();it++) it->x*=tr;} + + std::vector ebData; + std::vector vorpData; + std::vector voreData; + + int nbQRas; + int firstQRas; + int lastQRas; + std::vector qrsData; + + std::vector chgts; + 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; + SweepEventQueue *sEvts; + + // bounding box stuff + double leftX, topY, rightX, bottomY; + + // topological information: who links who? + struct dg_point + { + NR::Point x; // position + int dI, dO; // indegree and outdegree + int incidentEdge[2]; // first and last incident edge + int oldDegree; + + int totalDegree() const { return dI + dO; } + }; + + struct dg_arete + { + NR::Point dx; // edge vector + int st, en; // start and end points of the edge + int nextS, prevS; // next and previous edge in the double-linked list at the start point + int nextE, prevE; // next and 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; + + inline int numberOfPoints() const { return _pts.size(); } + inline bool hasPoints() const { return (_pts.empty() == false); } + inline int numberOfEdges() const { return _aretes.size(); } + inline bool hasEdges() const { return (_aretes.empty() == false); } + + inline void needPointsSorting() { _need_points_sorting = true; } + inline void needEdgesSorting() { _need_edges_sorting = true; } + + inline bool hasBackData() const { return _has_back_data; } + + inline dg_point const &getPoint(int n) const { return _pts[n]; } + inline dg_arete const &getEdge(int n) const { return _aretes[n]; } + +private: + + friend class SweepTree; + friend class SweepEvent; + friend class SweepEventQueue; + + // temporary data for the various algorithms + struct edge_data + { + int weight; // weight of the edge (to handle multiple edges) + NR::Point rdx; // rounded edge vector + double length, sqlength, ilength, isqlength; // length^2, length, 1/length^2, 1/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; + }; + + 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; + NR::Point rx; // rounded coordinates of the point + }; + + + struct edge_list + { // temporary array of edges for easier sorting + int no; + bool starting; + NR::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 + void MakePointData(bool nVal); + void MakeEdgeData(bool nVal); + void MakeSweepSrcData(bool nVal); + void MakeSweepDestData(bool nVal); + void MakeRasterData(bool nVal); + void MakeQuickRasterData(bool nVal); + + void SortPoints(int s, int e); + void SortPointsByOldInd(int s, int e); + + // fonctions annexes pour ConvertToShape et Booleen + void ResetSweep(); // allocates sweep structures + void CleanupSweep(); // deallocates them + + // edge sorting function + void SortEdgesList(edge_list *edges, int s, int e); + + void TesteIntersection(SweepTree *t, Side s, bool onlyDiff); // test if there is an intersection + bool TesteIntersection(SweepTree *iL, SweepTree *iR, NR::Point &atx, double &atL, double &atR, bool onlyDiff); + bool TesteIntersection(Shape *iL, Shape *iR, int ilb, int irb, + NR::Point &atx, double &atL, double &atR, + bool onlyDiff); + bool TesteAdjacency(Shape *iL, int ilb, const NR::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); + int AssemblePoints(int st, int en); + void AssembleAretes(FillRule directed = fill_nonZero); + void AddChgt(int lastPointNo, int lastChgtPt, Shape *&shapeHead, + int &edgeHead, sTreeChangeType type, Shape *lS, int lB, Shape *rS, + int rB); + void CheckAdjacencies(int lastPointNo, int lastChgtPt, Shape *shapeHead, int edgeHead); + void CheckEdges(int lastPointNo, int lastChgtPt, Shape *a, Shape *b, BooleanOp mod); + void Avance(int lastPointNo, int lastChgtPt, Shape *iS, int iB, Shape *a, Shape *b, BooleanOp mod); + void DoEdgeTo(Shape *iS, int iB, int iTo, bool direct, bool sens); + void GetWindings(Shape *a, Shape *b = NULL, BooleanOp mod = bool_op_union, bool brutal = false); + + void Validate(); + + int Winding(int nPt) const; + void SortPointsRounded(); + 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); + + 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 NR::Point px, const NR::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 _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; + + std::vector _pts; + std::vector _aretes; + + // the arrays of temporary data + // these ones are dynamically kept at a length of maxPt or maxAr + std::vector eData; + std::vector swsData; + std::vector swdData; + std::vector swrData; + std::vector pData; + + 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 + static int CmpToVert(const NR::Point ax, const NR::Point bx, bool as, bool bs); +}; + +bool directedEulerian(Shape const *s); +double distance(Shape const *s, NR::Point const &p); +bool distanceLessThanOrEqual(Shape const *s, NR::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 000000000..07e46afc7 --- /dev/null +++ b/src/livarot/ShapeDraw.cpp @@ -0,0 +1,103 @@ +/* + * ShapeDraw.cpp + * nlivarot + * + * Created by fred on Mon Jun 16 2003. + * + */ + +#include "Shape.h" +//#include + +// 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,"\n"); + fprintf(outFile,"\n"); + fprintf(outFile,"\n"); + fprintf(outFile," \n"); + fprintf(outFile," \n"); + + if ( doPoint ) { + for (int i=0;i\n",ph,pv); // localizing ok + } + } + if ( pointsNo ) { + for (int i=0;i\n",ph-2,pv+1); // localizing ok + fprintf(outFile,"%i\n",i); + fprintf(outFile," \n"); + } + } + { + for (int i=0;i\n",sh,sv,endh,endv); // localizing ok + } else { + fprintf(outFile," \n",sh,sv,eh,ev); // localizing ok + } + } + } + if ( edgesNo ) { + for (int i=0;i\n",(sh+eh)/2+2,(sv+ev)/2); // localizing ok + fprintf(outFile,"%i\n",i); + fprintf(outFile," \n"); + } + } + + fprintf(outFile,"\n"); + fclose(outFile); + +} diff --git a/src/livarot/ShapeMisc.cpp b/src/livarot/ShapeMisc.cpp new file mode 100644 index 000000000..d9bb665f0 --- /dev/null +++ b/src/livarot/ShapeMisc.cpp @@ -0,0 +1,1228 @@ +/* + * ShapeMisc.cpp + * nlivarot + * + * Created by fred on Sun Jul 20 2003. + * + */ + +#include "livarot/Shape.h" +#include +#include "livarot/Path.h" +#include "livarot/path-description.h" +#include + +/* + * 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) +{ + if (numberOfPoints() <= 1 || numberOfEdges() <= 1) + return; + if (directedEulerian(this) == false) + 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 = 0; + 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 == 0) + 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; + 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; + swdData[curBord].precParc = -1; + swdData[curBord].suivParc = -1; + do + { + int cPt = getEdge(curBord).en; + int nb = curBord; + // printf("de curBord= %d au point %i -> ",curBord,cPt); + // get next edge + 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 != 0 || getEdge(nb).st != cPt); + + if (nb < 0 || nb == curBord) + { + // no next edge: end of this contour, we get back + if (back == false) + dest->Close (); + back = true; + // retour en arriere + curBord = swdData[curBord].precParc; + // printf("retour vers %d\n",curBord); + if (curBord < 0) + break; + } + else + { + // 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; + } + 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); + } + } + } + while (1 /*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) +{ + 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 = 0; + 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 == 0) + 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; + } + } + } + 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; + 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 != 0 || 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 + { + 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); + swdData[curBord].suivParc = -1; + AddContour (dest, nbP, orig, startBord, curBord,splitWhenForced); + startBord=nb; + } + } + 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 (1 /*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=NULL; + contStart=NULL; + 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 = 0; + swdData[i].precParc = swdData[i].suivParc = -1; + } + + int searchInd = 0; + + int lastPtUsed = 0; + do + { + int dadContour=-1; + int startBord = -1; + { + int fi = 0; + for (fi = lastPtUsed; fi < numberOfPoints(); fi++) + { + if (getPoint(fi).incidentEdge[FIRST] >= 0 && swdData[getPoint(fi).incidentEdge[FIRST]].misc == 0) + break; + } + { + int askTo = pData[fi].askForWindingB; + if (askTo < 0 || askTo >= numberOfEdges() ) { + dadContour=-1; + } else { + dadContour = GPOINTER_TO_INT(swdData[askTo].misc); + dadContour-=1; // pour compenser le decalage + } + } + 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 *) (1+nbNest); + //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 != 0 || 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 ( escapePath ) { + nesting[nbNest++]=-1; // contient des bouts de coupure -> a part + } else { + nesting[nbNest++]=dadContour; + } + 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 ( escapePath ) { + nesting[nbNest++]=-1; // contient des bouts de coupure -> a part + } else { + nesting[nbNest++]=dadContour; + } + + swdData[curBord].suivParc = -1; + AddContour (dest, nbP, orig, startBord, curBord,splitWhenForced); + startBord=nb; + } + } + swdData[nb].misc = (void *) (1+nbNest); + swdData[nb].ind = searchInd++; + swdData[nb].precParc = curBord; + swdData[curBord].suivParc = nb; + curBord = nb; + //printf("suite %d\n",curBord); + } + } + while (1 /*swdData[curBord].precParc >= 0 */ ); + // fin du cas non-oriente + } + } + while (lastPtUsed < numberOfPoints()); + + MakePointData (false); + MakeEdgeData (false); + MakeSweepDestData (false); +} + +// 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, has 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) +{ + Reset (0, 0); + MakeBackData(a->_has_back_data); + + if (dec == 0) + { + _pts = a->_pts; + if (numberOfPoints() > maxPt) + { + maxPt = numberOfPoints(); + if (_has_points_data) + pData.resize(maxPt); + } + + _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); + } + + NR::Point stD, seD, enD; + double stL, seL, enL; + stD = a->getEdge(stB).dx; + seD = a->getEdge(i).dx; + enD = a->getEdge(enB).dx; + + stL = sqrt (dot(stD,stD)); + seL = sqrt (dot(seD,seD)); + enL = sqrt (dot(enD,enD)); + MiscNormalize (stD); + MiscNormalize (enD); + MiscNormalize (seD); + + NR::Point ptP; + int stNo, enNo; + ptP = a->getPoint(a->getEdge(i).st).x; + 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, 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, -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 0; +} + +// 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; + + { + dest->MoveTo (getPoint(getEdge(bord).st).x); + } + + while (bord >= 0) + { + int nPiece = ebData[bord].pieceID; + int nPath = ebData[bord].pathID; + + if (nPath < 0 || nPath >= nbP || orig[nPath] == NULL) + { + // segment batard + dest->LineTo (getPoint(getEdge(bord).en).x); + bord = swdData[bord].suivParc; + } + else + { + Path *from = orig[nPath]; + if (nPiece < 0 || nPiece >= int(from->descr_cmd.size())) + { + // segment batard + dest->LineTo (getPoint(getEdge(bord).en).x); + bord = swdData[bord].suivParc; + } + else + { + int nType = from->descr_cmd[nPiece]->getType(); + if (nType == descr_close || nType == descr_moveto + || nType == descr_forced) + { + // devrait pas arriver + dest->LineTo (getPoint(getEdge(bord).en).x); + bord = swdData[bord].suivParc; + } + else if (nType == descr_lineto) + { + 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(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; + } + 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; + NR::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; + NR::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(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, 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(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; + NR::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; + } + NR::Point prevx = from->PrevPoint (nPiece - 1); + + NR::Point sDx, eDx; + { + PathDescrCubicTo *nData = dynamic_cast(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; + NR::Point px = getPoint(getEdge(bord).st).x; + NR::Point nx = getPoint(getEdge(bord).en).x; + int inBezier = -1, nbInterm = -1; + int typ; + typ = from->descr_cmd[nPiece]->getType(); + PathDescrBezierTo *nBData = NULL; + if (typ == descr_bezierto) + { + nBData = dynamic_cast(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(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 != NULL, 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(from->descr_cmd[i+1]); + dest->IntermBezierTo (nData->p); + } + dest->EndBezierTo (); + } + else + { + NR::Point tx; + { + PathDescrIntermBezierTo* psData = dynamic_cast(from->descr_cmd[pe]); + PathDescrIntermBezierTo* pnData = dynamic_cast(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(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) + { + NR::Point tx; + { + PathDescrIntermBezierTo* psData = dynamic_cast(from->descr_cmd[ps+1]); + PathDescrIntermBezierTo* pnData = dynamic_cast(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(from->descr_cmd[i+1]); + dest->IntermBezierTo (nData->p); + } + dest->EndBezierTo (); + } + else + { + NR::Point tx; + { + PathDescrIntermBezierTo* psData = dynamic_cast(from->descr_cmd[ps+1]); + PathDescrIntermBezierTo* pnData = dynamic_cast(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(from->descr_cmd[pe]); + PathDescrIntermBezierTo* pnData = dynamic_cast(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(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(from->descr_cmd[i+1]); + dest->IntermBezierTo (nData->p); + } + dest->EndBezierTo (); + } + else + { + NR::Point tx; + { + PathDescrIntermBezierTo* psData = dynamic_cast(from->descr_cmd[pe+1]); + PathDescrIntermBezierTo* pnData = dynamic_cast(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(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) + { + NR::Point tx; + { + PathDescrIntermBezierTo* psData = dynamic_cast(from->descr_cmd[ps]); + PathDescrIntermBezierTo* pnData = dynamic_cast(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(from->descr_cmd[i]); + dest->IntermBezierTo (nData->p); + } + dest->EndBezierTo (); + } + else + { + NR::Point tx; + { + PathDescrIntermBezierTo* psData = dynamic_cast(from->descr_cmd[ps]); + PathDescrIntermBezierTo* pnData = dynamic_cast(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(from->descr_cmd[pe+1]); + PathDescrIntermBezierTo* pnData = dynamic_cast(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(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 (NR::Point px, NR::Point nx, + Path * dest, int inBezier, int nbInterm, + Path * from, int p, double ts, double te) +{ + PathDescrBezierTo* nBData = dynamic_cast(from->descr_cmd[inBezier]); + NR::Point bstx = from->PrevPoint (inBezier - 1); + NR::Point benx = nBData->p; + + NR::Point mx; + if (p == inBezier) + { + // premier bout + if (nbInterm <= 1) + { + // seul bout de la spline + PathDescrIntermBezierTo *nData = dynamic_cast(from->descr_cmd[inBezier+1]); + mx = nData->p; + } + else + { + // premier bout d'une spline qui en contient plusieurs + PathDescrIntermBezierTo *nData = dynamic_cast(from->descr_cmd[inBezier+1]); + mx = nData->p; + nData = dynamic_cast(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(from->descr_cmd[inBezier+nbInterm]); + mx = nData->p; + nData = dynamic_cast(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(from->descr_cmd[p+1]); + mx = nData->p; + nData = dynamic_cast(from->descr_cmd[p]); + bstx = (nData->p + mx) / 2; + nData = dynamic_cast(from->descr_cmd[p+2]); + benx = (nData->p + mx) / 2; + } + NR::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 000000000..4e762396e --- /dev/null +++ b/src/livarot/ShapeRaster.cpp @@ -0,0 +1,2009 @@ +/* + * 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 +#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 == NULL) { + sTree = new SweepTreeList(numberOfEdges()); + } + if (sEvts == NULL) { + 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 = NULL; + eData[i].rdx=pData[getEdge(i).en].rx - pData[getEdge(i).st].rx; + } +} + + +void Shape::EndRaster() +{ + delete sTree; + sTree = NULL; + delete sEvts; + sEvts = NULL; + + 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 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 == NULL ) { + upNo = -1; + } + } else { + if ( nbUp <= 0 ) { + dnNo = -1; + } + if ( dnNo >= 0 && swrData[dnNo].misc == NULL ) { + 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 = NULL; + 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 = NULL; + 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 = NULL; + + 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(sTree->racine->Leftmost()); + while ( curS ) { + int cb = curS->bord; + AvanceEdge(cb, to, true, step); + curS = static_cast(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 == NULL ) { + 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;iRemove(*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 = NULL; + node->Remove(*sTree, *sEvts, true); + } + } + + for (int i=0;i 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(sTree->racine->Leftmost()); + while ( curS ) { + int cb = curS->bord; + AvanceEdge(cb, to, true, step); + curS = static_cast(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= 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(sTree->racine->Leftmost()); + while ( curS ) { + + int lastGuess = -1; + int cb = curS->bord; + + if ( swrData[cb].sens == false && curS->elem[LEFT] ) { + + int lb = (static_cast(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 (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 == NULL ) { + 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 = NULL; + 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(sTree->racine->Leftmost()); + while ( curS ) { + int cb = curS->bord; + AvanceEdge(cb, to, line, exact, step); + curS = static_cast(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(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(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(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(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 == NULL ) { + 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 = NULL; + 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(sTree->racine->Leftmost()); + while ( curS ) { + int cb = curS->bord; + AvanceEdge(cb, to, line, exact, step); + curS = static_cast(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 = -1; + 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 == NULL ) { + 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 = NULL; + 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(sTree->racine->Leftmost()); + while ( curS ) { + int cb = curS->bord; + AvanceEdge(cb, to, line, exact, step); + curS = static_cast(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 == NULL ) { + 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 = -1; + 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 == NULL ) { + 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 == NULL ) { + 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; + NR::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 ) { + NR::Point dir; + NR::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 i = 0; i < 2; i++) { + int const j = getPoint(P).incidentEdge[i]; + 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 = NULL; +} + + +/* + 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 000000000..3e5a3bcaf --- /dev/null +++ b/src/livarot/ShapeSweep.cpp @@ -0,0 +1,3327 @@ +/* + * ShapeSweep.cpp + * nlivarot + * + * Created by fred on Thu Jun 19 2003. + * + */ + +#include +#include "Shape.h" +#include "livarot/sweep-event-queue.h" +#include "livarot/sweep-tree-list.h" +#include "livarot/sweep-tree.h" + +#include "libnr/nr-matrix.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 (void) +{ + MakePointData (true); + MakeEdgeData (true); + MakeSweepSrcData (true); +} + +void +Shape::CleanupSweep (void) +{ + MakePointData (false); + MakeEdgeData (false); + MakeSweepSrcData (false); +} + +void +Shape::ForceToPolygon (void) +{ + 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); + } + + _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, NULL, 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 (0, 0); + + if (a->numberOfPoints() <= 1 || a->numberOfEdges() <= 1) { + return 0; + } + + if ( directed != fill_justDont && directedEulerian(a) == false ) { + return shape_input_err; + } + + a->ResetSweep(); + + if (sTree == NULL) { + sTree = new SweepTreeList(a->numberOfEdges()); + } + if (sEvts == NULL) { + sEvts = new SweepEventQueue(a->numberOfEdges()); + } + + MakePointData(true); + MakeEdgeData(true); + MakeSweepSrcData(true); + MakeSweepDestData(true); + MakeBackData(a->_has_back_data); + + a->initialisePointData(); + a->initialiseEdgeData(); + + a->SortPointsRounded(); + + chgts.clear(); + + double lastChange = a->pData[0].rx[1] - 1.0; + int lastChgtPt = 0; + int edgeHead = -1; + Shape *shapeHead = NULL; + + clearIncidenceData(); + + int curAPt = 0; + + while (curAPt < a->numberOfPoints() || sEvts->size() > 0) { + NR::Point ptX; + double ptL, ptR; + SweepTree *intersL = NULL; + SweepTree *intersR = NULL; + int nPt = -1; + Shape *ptSh = NULL; + bool isIntersection = false; + if (sEvts->peek(intersL, intersR, ptX, ptL, ptR)) + { + 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 just be pop? */ + sEvts->extract(intersL, intersR, ptX, ptL, ptR); + isIntersection = true; + } + else + { + nPt = curAPt++; + ptSh = a; + ptX = ptSh->pData[nPt].rx; + isIntersection = false; + } + } + else + { + nPt = curAPt++; + ptSh = a; + ptX = ptSh->pData[nPt].rx; + isIntersection = false; + } + + if (isIntersection == false) + { + if (ptSh->getPoint(nPt).dI == 0 && ptSh->getPoint(nPt).dO == 0) + continue; + } + + NR::Point rPtX; + rPtX[0]= Round (ptX[0]); + rPtX[1]= Round (ptX[1]); + int lastPointNo = -1; + 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 (unsigned int i = 0; i < chgts.size(); i++) + { + chgts[i].ptNo = pData[chgts[i].ptNo].newInd; + if (chgts[i].type == 0) + { + if (chgts[i].src->getEdge(chgts[i].bord).st < + chgts[i].src->getEdge(chgts[i].bord).en) + { + chgts[i].src->swsData[chgts[i].bord].stPt = + chgts[i].ptNo; + } + else + { + chgts[i].src->swsData[chgts[i].bord].enPt = + chgts[i].ptNo; + } + } + else if (chgts[i].type == 1) + { + if (chgts[i].src->getEdge(chgts[i].bord).st > + chgts[i].src->getEdge(chgts[i].bord).en) + { + chgts[i].src->swsData[chgts[i].bord].stPt = + chgts[i].ptNo; + } + else + { + chgts[i].src->swsData[chgts[i].bord].enPt = + chgts[i].ptNo; + } + } + } + + CheckAdjacencies (lastI, lastChgtPt, shapeHead, edgeHead); + + CheckEdges (lastI, lastChgtPt, a, NULL, 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; + } + } + + 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 = NULL; + } + + + if (isIntersection) + { +// printf("(%i %i [%i %i]) ",intersL->bord,intersR->bord,intersL->startPoint,intersR->startPoint); + 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, false); + TesteIntersection (intersR, RIGHT, false); + } + 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 == NULL) + { + 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 == NULL) + { + } + else + { + AddChgt (lastPointNo, lastChgtPt, shapeHead, + edgeHead, EDGE_REMOVED, node->src, node->bord, + NULL, -1); + ptSh->swsData[cb].misc = NULL; + + int onLeftB = -1, onRightB = -1; + Shape *onLeftS = NULL; + Shape *onRightS = NULL; + 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; + 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 = NULL; + if (dnNo >= 0) + { + if (upNo >= 0) + { + SweepTree *node = (SweepTree *) ptSh->swsData[upNo].misc; + + AddChgt (lastPointNo, lastChgtPt, shapeHead, edgeHead, EDGE_REMOVED, + node->src, node->bord, NULL, -1); + + ptSh->swsData[upNo].misc = NULL; + + node->RemoveEvents (*sEvts); + node->ConvertTo (ptSh, dnNo, 1, lastPointNo); + ptSh->swsData[dnNo].misc = node; + TesteIntersection (node, RIGHT, false); + TesteIntersection (node, LEFT, false); + insertionNode = node; + + ptSh->swsData[dnNo].curPoint = lastPointNo; + AddChgt (lastPointNo, lastChgtPt, shapeHead, edgeHead, EDGE_INSERTED, + node->src, node->bord, NULL, -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, false); + TesteIntersection (node, LEFT, false); + insertionNode = node; + + ptSh->swsData[dnNo].curPoint = lastPointNo; + AddChgt (lastPointNo, lastChgtPt, shapeHead, edgeHead, EDGE_INSERTED, + node->src, node->bord, NULL, -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->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, false); + TesteIntersection (node, LEFT, false); + + ptSh->swsData[cb].curPoint = lastPointNo; + AddChgt (lastPointNo, lastChgtPt, shapeHead, + edgeHead, EDGE_INSERTED, node->src, node->bord, NULL, + -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; + } + + for (unsigned int i = 0; i < chgts.size(); i++) + { + chgts[i].ptNo = pData[chgts[i].ptNo].newInd; + if (chgts[i].type == 0) + { + if (chgts[i].src->getEdge(chgts[i].bord).st < + chgts[i].src->getEdge(chgts[i].bord).en) + { + chgts[i].src->swsData[chgts[i].bord].stPt = chgts[i].ptNo; + } + else + { + chgts[i].src->swsData[chgts[i].bord].enPt = chgts[i].ptNo; + } + } + else if (chgts[i].type == 1) + { + if (chgts[i].src->getEdge(chgts[i].bord).st > + chgts[i].src->getEdge(chgts[i].bord).en) + { + chgts[i].src->swsData[chgts[i].bord].stPt = chgts[i].ptNo; + } + else + { + chgts[i].src->swsData[chgts[i].bord].enPt = chgts[i].ptNo; + } + } + } + + CheckAdjacencies (lastI, lastChgtPt, shapeHead, edgeHead); + + CheckEdges (lastI, lastChgtPt, a, NULL, 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 = NULL; + } + + 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(); + + AssembleAretes (directed); + +// Plot (98.0, 112.0, 8.0, 400.0, 400.0, true, true, true, true); + + for (int i = 0; i < numberOfPoints(); i++) + { + _pts[i].oldDegree = getPoint(i).totalDegree(); + } +// Validate(); + + _need_edges_sorting = true; + if ( directed == fill_justDont ) { + SortEdges(); + } else { + GetWindings (a); + } +// 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"); +// } + 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;iCleanupSweep (); + _pts.clear(); + _aretes.clear(); + return shape_euler_err; + } + } + 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 == NULL || b == NULL) + 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 == NULL) { + sTree = new SweepTreeList(a->numberOfEdges() + b->numberOfEdges()); + } + if (sEvts == NULL) { + 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 = NULL; + + clearIncidenceData(); + + int curAPt = 0; + int curBPt = 0; + + while (curAPt < a->numberOfPoints() || curBPt < b->numberOfPoints() || sEvts->size() > 0) + { +/* for (int i=0;ibord,sEvts.events[i].rightSweep->bord); // localizing ok + } + // cout << endl; + if ( sTree.racine ) { + SweepTree* ct=static_cast (sTree.racine->Leftmost()); + while ( ct ) { + printf("%i %i [%i\n",ct->bord,ct->startPoint,(ct->src==a)?1:0); + ct=static_cast (ct->elem[RIGHT]); + } + } + printf("\n");*/ + + NR::Point ptX; + double ptL, ptR; + SweepTree *intersL = NULL; + SweepTree *intersR = NULL; + int nPt = -1; + Shape *ptSh = NULL; + 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; + } + + NR::Point rPtX; + rPtX[0]= Round (ptX[0]); + rPtX[1]= Round (ptX[1]); + int lastPointNo = -1; + 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 (unsigned int i = 0; i < chgts.size(); i++) + { + chgts[i].ptNo = pData[chgts[i].ptNo].newInd; + if (chgts[i].type == 0) + { + if (chgts[i].src->getEdge(chgts[i].bord).st < + chgts[i].src->getEdge(chgts[i].bord).en) + { + chgts[i].src->swsData[chgts[i].bord].stPt = + chgts[i].ptNo; + } + else + { + chgts[i].src->swsData[chgts[i].bord].enPt = + chgts[i].ptNo; + } + } + else if (chgts[i].type == 1) + { + if (chgts[i].src->getEdge(chgts[i].bord).st > + chgts[i].src->getEdge(chgts[i].bord).en) + { + chgts[i].src->swsData[chgts[i].bord].stPt = + chgts[i].ptNo; + } + else + { + chgts[i].src->swsData[chgts[i].bord].enPt = + chgts[i].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 = NULL; + } + + + 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 == NULL) + { + 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 == NULL) + { + } + else + { + AddChgt (lastPointNo, lastChgtPt, shapeHead, + edgeHead, EDGE_REMOVED, node->src, node->bord, + NULL, -1); + ptSh->swsData[cb].misc = NULL; + + int onLeftB = -1, onRightB = -1; + Shape *onLeftS = NULL; + Shape *onRightS = NULL; + 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 = NULL; + if (dnNo >= 0) + { + if (upNo >= 0) + { + SweepTree *node = (SweepTree *) ptSh->swsData[upNo].misc; + + AddChgt (lastPointNo, lastChgtPt, shapeHead, edgeHead, EDGE_REMOVED, + node->src, node->bord, NULL, -1); + + ptSh->swsData[upNo].misc = NULL; + + 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, NULL, -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, NULL, -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, NULL, + -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 (unsigned int i = 0; i < chgts.size(); i++) + { + chgts[i].ptNo = pData[chgts[i].ptNo].newInd; + if (chgts[i].type == 0) + { + if (chgts[i].src->getEdge(chgts[i].bord).st < + chgts[i].src->getEdge(chgts[i].bord).en) + { + chgts[i].src->swsData[chgts[i].bord].stPt = chgts[i].ptNo; + } + else + { + chgts[i].src->swsData[chgts[i].bord].enPt = chgts[i].ptNo; + } + } + else if (chgts[i].type == 1) + { + if (chgts[i].src->getEdge(chgts[i].bord).st > + chgts[i].src->getEdge(chgts[i].bord).en) + { + chgts[i].src->swsData[chgts[i].bord].stPt = chgts[i].ptNo; + } + else + { + chgts[i].src->swsData[chgts[i].bord].enPt = chgts[i].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 = NULL; + } + + 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++) + { + swdData[i].leW = swdData[i].leW % 2; + if (swdData[i].leW < 0) + swdData[i].leW = -swdData[i].leW; + swdData[i].riW = swdData[i].riW; + 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= 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 = NULL; + delete sEvts; + sEvts = NULL; + + 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) +{ + SweepTree *tt = static_cast(t->elem[s]); + if (tt == NULL) { + return; + } + + SweepTree *a = (s == LEFT) ? tt : t; + SweepTree *b = (s == LEFT) ? t : tt; + + NR::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, NR::Point &atx, double &atL, double &atR, bool onlyDiff) +{ + int lSt = iL->src->getEdge(iL->bord).st, lEn = iL->src->getEdge(iL->bord).en; + int rSt = iR->src->getEdge(iR->bord).st, rEn = iR->src->getEdge(iR->bord).en; + NR::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 + if (lSt < lEn) + { + } + else + { + int swap = lSt; + lSt = lEn; + lEn = swap; + ldir = -ldir; + } + if (rSt < rEn) + { + } + else + { + int swap = rSt; + rSt = rEn; + rEn = swap; + rdir = -rdir; + } + + 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; + } + } + + double ang = cross (rdir, ldir); +// 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 (iL->src == iR->src && lSt == rSt) + { + if (iL->src == iR->src && lEn == rEn) + return false; // c'est juste un doublon + atx = iL->src->pData[lSt].rx; + atR = atL = -1; + return true; // l'ordre est mauvais + } + 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 + + if (onlyDiff && iL->src == iR->src) + return false; + + // on reprend les vrais points + 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... + { + NR::Point sDiff, eDiff; + double slDot, elDot; + double srDot, erDot; + sDiff = iL->src->pData[lSt].rx - iR->src->pData[rSt].rx; + eDiff = iL->src->pData[lEn].rx - iR->src->pData[rSt].rx; + srDot = cross (sDiff,rdir); + erDot = cross (eDiff,rdir); + sDiff = iR->src->pData[rSt].rx - iL->src->pData[lSt].rx; + eDiff = iR->src->pData[rEn].rx - iL->src->pData[lSt].rx; + slDot = cross (sDiff,ldir); + elDot = cross (eDiff,ldir); + + if ((srDot >= 0 && erDot >= 0) || (srDot <= 0 && erDot <= 0)) + { + if (srDot == 0) + { + 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; + } + } + 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 ((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;*/ + 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) +{ + NR::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 +{ + int askTo = pData[nPt].askForWindingB; + if (askTo < 0 || askTo >= numberOfEdges()) + return 0; + if (getEdge(askTo).st < getEdge(askTo).en) + { + return swdData[askTo].leW; + } + else + { + return swdData[askTo].riW; + } + return 0; +} + +int +Shape::Winding (const NR::Point px) const +{ + int lr = 0, ll = 0, rr = 0; + + for (int i = 0; i < numberOfEdges(); i++) + { + NR::Point adir, diff, ast, aen; + adir = eData[i].rdx; + + ast = pData[getEdge(i).st].rx; + aen = pData[getEdge(i).en].rx; + + int nWeight = eData[i].weight; + + 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; + } + + diff = px - ast; + double cote = cross (diff,adir); + 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; +} + +// 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 == NULL) { + 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 (int i = 0; i < numberOfPoints(); i++) { + if (getPoint(i).totalDegree() == 2) { + int cb, cc; + cb = getPoint(i).incidentEdge[FIRST]; + cc = getPoint(i).incidentEdge[LAST]; + 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 ( directed == fill_justDont ) { + if ( doublon ) { + 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; + } else { + } + if ( 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; + } + _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 = 0; + swdData[i].precParc = swdData[i].suivParc = -1; + } + + // chainage + SortEdges (); + + int searchInd = 0; + + int lastPtUsed = 0; + do + { + int startBord = -1; + int outsideW = 0; + { + int fi = 0; + for (fi = lastPtUsed; fi < numberOfPoints(); fi++) + { + if (getPoint(fi).incidentEdge[FIRST] >= 0 && swdData[getPoint(fi).incidentEdge[FIRST]].misc == 0) + break; + } + lastPtUsed = fi + 1; + if (fi < numberOfPoints()) + { + int bestB = getPoint(fi).incidentEdge[FIRST]; + if (bestB >= 0) + { + startBord = bestB; + if (fi == 0) + { + outsideW = 0; + } + else + { + if (brutal) + { + outsideW = Winding (getPoint(fi).x); + } + else + { + outsideW = Winding (fi); + } + } + 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) + { + // parcours en profondeur pour mettre les leF et riF a leurs valeurs + swdData[startBord].misc = (void *) 1; + swdData[startBord].leW = outsideW; + swdData[startBord].riW = outsideW - eData[startBord].weight; +// if ( doDebug ) printf("part de %d\n",startBord); + int curBord = startBord; + bool curDir = true; + swdData[curBord].precParc = -1; + swdData[curBord].suivParc = -1; + do + { + int cPt; + if (curDir) + cPt = getEdge(curBord).en; + else + cPt = getEdge(curBord).st; + 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; + if (getEdge(nb).en == cPt) + { + outsideW = swdData[nb].riW; + nnb = CyclePrevAt (cPt, nb); + } + else + { + outsideW = swdData[nb].leW; + nnb = CyclePrevAt (cPt, nb); + } + if (nnb == nb) + { + // cul-de-sac + nb = -1; + break; + } + nb = nnb; + } + while (nb >= 0 && nb != curBord && swdData[nb].misc != 0); + if (nb < 0 || nb == curBord) + { + // retour en arriere + int oPt; + if (curDir) + oPt = getEdge(curBord).st; + else + oPt = getEdge(curBord).en; + curBord = swdData[curBord].precParc; +// if ( doDebug ) printf("retour vers %d\n",curBord); + if (curBord < 0) + break; + if (oPt == getEdge(curBord).en) + curDir = true; + else + curDir = false; + } + else + { + swdData[nb].misc = (void *) 1; + swdData[nb].ind = searchInd++; + if (cPt == getEdge(nb).st) + { + swdData[nb].riW = outsideW; + swdData[nb].leW = outsideW + eData[nb].weight; + } + else + { + swdData[nb].leW = outsideW; + swdData[nb].riW = outsideW - eData[nb].weight; + } + swdData[nb].precParc = curBord; + swdData[curBord].suivParc = nb; + curBord = nb; +// if ( doDebug ) printf("suite %d\n",curBord); + if (cPt == getEdge(nb).en) + curDir = false; + else + curDir = true; + } + } + while (1 /*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, + NR::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; + } + + NR::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) + { + double swf = il; + il = ir; + ir = swf; + } + if (it > ib) + { + double swf = it; + it = ib; + ib = swf; + } + 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) + { + double swf = jl; + jl = jr; + jr = swf; + } + if (jt > jb) + { + double swf = jt; + jt = jb; + jb = swf; + } + + if (il > jr || it > jb || ir < jl || ib < jt) + return false; + + // pre-test + { + NR::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 (sDiff,rdir ); + erDot = cross (eDiff,rdir ); + 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 (sDiff,ldir ); + elDot = cross (eDiff,ldir); + 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 + NR::Point usvs; + usvs = irs->pData[rSt].rx - ils->pData[lSt].rx; + + // pas sur de l'ordre des coefs de m + NR::Matrix 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 + NR::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 (sDiff,rdir ); + eDot = cross (eDiff,rdir); + + 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 (sDiff,ldir ); + eDot = cross (eDiff,ldir ); + + 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 NR::Point atx, int nPt, + bool push) +{ + if (nPt == a->swsData[no].stPt || nPt == a->swsData[no].enPt) + return false; + + NR::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 (diff,adir)) * 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 (diff1,adir); + di2 = cross (diff3,adir); + if ((di1 < 0 && di2 > 0) || (di1 > 0 && di2 < 0)) + { + adjacent = true; + } + else + { + di1 = cross ( diff2,adir); + di2 = cross (diff4,adir); + 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 (unsigned int cCh = 0; cCh < chgts.size(); cCh++) + { + int chLeN = chgts[cCh].ptNo; + int chRiN = chgts[cCh].ptNo; + if (chgts[cCh].src) + { + Shape *lS = chgts[cCh].src; + int lB = chgts[cCh].bord; + int lftN = lS->swsData[lB].leftRnd; + int rgtN = lS->swsData[lB].rightRnd; + if (lftN < chLeN) + chLeN = lftN; + if (rgtN > chRiN) + chRiN = rgtN; +// 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; + } + for (int n = rgtN + 1; n < lastPointNo; n++) + { + if (TesteAdjacency (lS, lB, getPoint(n).x, n, false) == + false) + break; + lS->swsData[lB].rightRnd = n; + } + } + if (chgts[cCh].osrc) + { + Shape *rS = chgts[cCh].osrc; + int rB = chgts[cCh].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; + } + } + if (chgts[cCh].lSrc) + { + if (chgts[cCh].lSrc->swsData[chgts[cCh].lBrd].leftRnd < lastChgtPt) + { + Shape *nSrc = chgts[cCh].lSrc; + int nBrd = chgts[cCh].lBrd /*,nNo=chgts[cCh].ptNo */ ; + bool hit; + + do + { + hit = false; + for (int n = chRiN; n >= chLeN; 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; + } + } + 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 (hit) + { + SweepTree *node = + static_cast < SweepTree * >(nSrc->swsData[nBrd].misc); + if (node == NULL) + break; + node = static_cast < SweepTree * >(node->elem[LEFT]); + if (node == NULL) + break; + nSrc = node->src; + nBrd = node->bord; + if (nSrc->swsData[nBrd].leftRnd >= lastChgtPt) + break; + } + } + while (hit); + + } + } + if (chgts[cCh].rSrc) + { + if (chgts[cCh].rSrc->swsData[chgts[cCh].rBrd].leftRnd < lastChgtPt) + { + Shape *nSrc = chgts[cCh].rSrc; + int nBrd = chgts[cCh].rBrd /*,nNo=chgts[cCh].ptNo */ ; + bool hit; + do + { + hit = false; + 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; + } + } + 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 == NULL) + break; + node = static_cast < SweepTree * >(node->elem[RIGHT]); + if (node == NULL) + 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) +{ + sTreeChange c; + c.ptNo = lastPointNo; + c.type = type; + c.src = lS; + c.bord = lB; + c.osrc = rS; + c.obord = rB; + chgts.push_back(c); + const int nCh = chgts.size() - 1; + + /* FIXME: this looks like a cut and paste job */ + + if (lS) { + 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 = NULL; + chgts[nCh].lBrd = -1; + } + + if (lS->swsData[lB].leftRnd < lastChgtPt) { + lS->swsData[lB].leftRnd = lastPointNo; + lS->swsData[lB].nextSh = shapeHead; + lS->swsData[lB].nextBo = edgeHead; + edgeHead = lB; + shapeHead = lS; + } else { + int old = lS->swsData[lB].leftRnd; + if (getPoint(old).x[0] > getPoint(lastPointNo).x[0]) { + lS->swsData[lB].leftRnd = lastPointNo; + } + } + 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; + } + } + + if (rS) { + 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 = NULL; + chgts[nCh].rBrd = -1; + } + + 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 { + 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 = NULL; + chgts[nCh].rBrd = -1; + } + } +} + +// is this a debug function? It's calling localized "printf" ... +void +Shape::Validate (void) +{ + 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++) + { + NR::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 (unsigned int cCh = 0; cCh < chgts.size(); cCh++) + { + if (chgts[cCh].type == 0) + { + Shape *lS = chgts[cCh].src; + int lB = chgts[cCh].bord; + lS->swsData[lB].curPoint = chgts[cCh].ptNo; + } + } + for (unsigned int cCh = 0; cCh < chgts.size(); cCh++) + { +// int chLeN=chgts[cCh].ptNo; +// int chRiN=chgts[cCh].ptNo; + if (chgts[cCh].src) + { + Shape *lS = chgts[cCh].src; + int lB = chgts[cCh].bord; + Avance (lastPointNo, lastChgtPt, lS, lB, a, b, mod); + } + if (chgts[cCh].osrc) + { + Shape *rS = chgts[cCh].osrc; + int rB = chgts[cCh].obord; + Avance (lastPointNo, lastChgtPt, rS, rB, a, b, mod); + } + if (chgts[cCh].lSrc) + { + Shape *nSrc = chgts[cCh].lSrc; + int nBrd = chgts[cCh].lBrd; + while (nSrc->swsData[nBrd].leftRnd >= + 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 == NULL) + break; + node = static_cast < SweepTree * >(node->elem[LEFT]); + if (node == NULL) + break; + nSrc = node->src; + nBrd = node->bord; + } + } + if (chgts[cCh].rSrc) + { + Shape *nSrc = chgts[cCh].rSrc; + int nBrd = chgts[cCh].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 == NULL) + break; + node = static_cast < SweepTree * >(node->elem[RIGHT]); + if (node == NULL) + 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); + bool avoidDiag = false; +// if ( lastChgtPt > 0 && pts[lastChgtPt-1].y+dd == pts[lastChgtPt].y ) avoidDiag=true; + + bool direct = true; + if (lS == b && (mod == bool_op_diff || mod == bool_op_symdiff)) + direct = false; + int lftN = lS->swsData[lB].leftRnd; + int rgtN = lS->swsData[lB].rightRnd; + if (lS->swsData[lB].doneTo < lastChgtPt) + { + int lp = lS->swsData[lB].curPoint; + if (lp >= 0 && getPoint(lp).x[1] + dd == getPoint(lastChgtPt).x[1]) + avoidDiag = true; + if (lS->eData[lB].rdx[1] == 0) + { + // tjs de gauche a droite et pas de diagonale + if (lS->eData[lB].rdx[0] >= 0) + { + for (int p = lftN; p <= rgtN; p++) + { + DoEdgeTo (lS, lB, p, direct, true); + lp = p; + } + } + else + { + for (int p = lftN; p <= rgtN; p++) + { + DoEdgeTo (lS, lB, p, direct, false); + lp = p; + } + } + } + else if (lS->eData[lB].rdx[1] > 0) + { + if (lS->eData[lB].rdx[0] >= 0) + { + + for (int p = lftN; p <= rgtN; p++) + { + 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, true); + DoEdgeTo (lS, lB, lftN, direct, true); + } + else + { + DoEdgeTo (lS, lB, lftN, direct, true); + } + } + else + { + DoEdgeTo (lS, lB, p, direct, true); + } + lp = p; + } + } + else + { + + 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, true); + DoEdgeTo (lS, lB, rgtN, direct, true); + } + else + { + DoEdgeTo (lS, lB, rgtN, direct, true); + } + } + else + { + DoEdgeTo (lS, lB, p, direct, true); + } + lp = p; + } + } + } + else + { + if (lS->eData[lB].rdx[0] >= 0) + { + + 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); + DoEdgeTo (lS, lB, rgtN, direct, false); + } + else + { + DoEdgeTo (lS, lB, rgtN, direct, false); + } + } + else + { + DoEdgeTo (lS, lB, p, direct, false); + } + lp = p; + } + } + else + { + + for (int p = lftN; p <= rgtN; p++) + { + 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; + } + 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; + NR::Point bpx = iS->pData[iS->getEdge(iB).st].rx; + NR::Point bdx = iS->eData[iB].rdx; + NR::Point psx = getPoint(getEdge(ne).st).x; + NR::Point pex = getPoint(getEdge(ne).en).x; + NR::Point psbx=psx-bpx; + NR::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 000000000..afa3b6032 --- /dev/null +++ b/src/livarot/float-line.cpp @@ -0,0 +1,918 @@ +/** + * \file livarot/float-line.cpp + * + * Implementation of coverage with floating-point values. + * + * \author Fred + * + * public domain + * + */ + +#ifdef faster_flatten +# include // std::abs(float) +#endif +#include "livarot/float-line.h" +#include "livarot/int-line.h" + +FloatLigne::FloatLigne() +{ + s_first = s_last = -1; +} + + +FloatLigne::~FloatLigne() +{ + +} + +/// 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) +{ + 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=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 == true ) { + 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 (int i = 0; i < int(bords.size()); i++) { + printf("(%f %f %f %i) ",bords[i].pos,bords[i].val,bords[i].pente,(bords[i].start?1:0)); // localization ok + } + + printf("\n"); + printf("%lu : \n", (long unsigned int) runs.size()); + + for (int i = 0; i < int(runs.size()); i++) { + printf("(%f %f -> %f %f / %f)", + runs[i].st, runs[i].vst, runs[i].en, runs[i].ven, runs[i].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 (int i = 0; i < int(a->runs.size()); i++) { + float_ligne_run runA = a->runs[i]; + 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 (int i = 0; i < int(a->runs.size()); i++) { + float_ligne_run runA = a->runs[i]; + 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 == false, it only leaves the parts that are not entirely under + * tresh. If addIt == 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 (int i = 0; i < int(a->runs.size()); i++) { + float_ligne_run runA = a->runs[i]; + 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 (int i = 0; i < int(a->runs.size()); i++) { + float_ligne_run runA = a->runs[i]; + 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:encoding=utf-8:textwidth=99 : diff --git a/src/livarot/float-line.h b/src/livarot/float-line.h new file mode 100644 index 000000000..ef4f2c398 --- /dev/null +++ b/src/livarot/float-line.h @@ -0,0 +1,136 @@ +#ifndef INKSCAPE_LIVAROT_FLOAT_LINE_H +#define INKSCAPE_LIVAROT_FLOAT_LINE_H + +/** \file + * Coverage with floating-point boundaries. + */ + +#include +#include "livarot/LivarotDefs.h" + +class IntLigne; +class BitLigne; + +/// 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 bords; ///< vector of coverage boundaries + std::vector 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(); + ~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:encoding=utf-8:textwidth=99 : diff --git a/src/livarot/int-line.cpp b/src/livarot/int-line.cpp new file mode 100644 index 000000000..fabe8d01c --- /dev/null +++ b/src/livarot/int-line.cpp @@ -0,0 +1,1066 @@ +/** + * \file livarot/int-line.cpp + * + * Implementation of coverage with integer boundaries. + * + * \author Fred + * + * public domain + * + */ + +#include +#include +#include "livarot/int-line.h" +#include "livarot/float-line.h" +#include "livarot/BitLigne.h" + +IntLigne::IntLigne() +{ + nbBord = maxBord = 0; + bords = NULL; + + nbRun = maxRun = 0; + runs = NULL; + + firstAc = lastAc = -1; +} + + +IntLigne::~IntLigne() +{ + if ( maxBord > 0 ) { + g_free(bords); + nbBord = maxBord = 0; + bords = NULL; + } + if ( maxRun > 0 ) { + g_free(runs); + nbRun = maxRun = 0; + runs = NULL; + } +} + +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 == true ) { + 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 (int i = 0; i < int(a->runs.size()); i++) { + float_ligne_run runA = a->runs[i]; + 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+a->runs[i].vst)*(miStF-a->runs[i].st); + AddRun(curSt,curSt+1,lastSurf,lastSurf); + } else { + lastSurf+=0.5*(msv+a->runs[i].vst)*(miStF-a->runs[i].st); + AddRun(curSt,curSt+1,lastSurf,lastSurf); + } + pixExists=false; + } else { + lastSurf=0.5*(msv+a->runs[i].vst)*(miStF-a->runs[i].st); + AddRun(curSt,curSt+1,lastSurf,lastSurf); + } + } else if ( pixExists ) { + if ( curPos < curSt ) { + AddRun(curPos,curPos+1,lastSurf,lastSurf); + lastSurf=0.5*(a->runs[i].ven+a->runs[i].vst)*(a->runs[i].en-a->runs[i].st); + curPos=curSt; + } else { + lastSurf += 0.5 * (a->runs[i].ven+a->runs[i].vst)*(a->runs[i].en-a->runs[i].st); + } + } else { + lastSurf=0.5*(a->runs[i].ven+a->runs[i].vst)*(a->runs[i].en-a->runs[i].st); + curPos=curSt; + pixExists=true; + } + } else if ( pixExists ) { + if ( curPos < curSt ) { + AddRun(curPos,curPos+1,lastSurf,lastSurf); + lastSurf = 0.5 * (msv+a->runs[i].vst) * (miStF-a->runs[i].st); + AddRun(curSt,curSt+1,lastSurf,lastSurf); + } else { + lastSurf += 0.5 * (msv+a->runs[i].vst) * (miStF-a->runs[i].st); + AddRun(curSt,curSt+1,lastSurf,lastSurf); + } + pixExists=false; + } else { + lastSurf = 0.5 * (msv+a->runs[i].vst) * (miStF-a->runs[i].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+a->runs[i].ven)*(a->runs[i].en-curEnF); + pixExists=true; + curPos=curEn; + } else if ( ! stEx ) { + if ( pixExists ) { + AddRun(curPos,curPos+1,lastSurf,lastSurf); + } + lastSurf=0.5*(mev+a->runs[i].ven)*(a->runs[i].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 000000000..6ed011c04 --- /dev/null +++ b/src/livarot/int-line.h @@ -0,0 +1,107 @@ +#ifndef INKSCAPE_LIVAROT_INT_LINE_H +#define INKSCAPE_LIVAROT_INT_LINE_H + +#include "livarot/livarot-forward.h" +#include "livarot/LivarotDefs.h" + +/** \file + * Coverage with integer boundaries. + */ + +/// 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(); + ~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(p1); + int_ligne_bord const *d2 = reinterpret_cast(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:encoding=utf-8:textwidth=99 : diff --git a/src/livarot/livarot-forward.h b/src/livarot/livarot-forward.h new file mode 100644 index 000000000..9705b18e0 --- /dev/null +++ b/src/livarot/livarot-forward.h @@ -0,0 +1,28 @@ +#ifndef SEEN_LIVAROT_FORWARD_H +#define SEEN_LIVAROT_FORWARD_H + +class Path; +class Shape; +struct float_ligne_run; +class FloatLigne; +class BitLigne; +class PathDescr; +class PathDescrLineTo; +class PathDescrArcTo; +class PathDescrCubicTo; +class PathDescrBezierTo; +class PathDescrIntermBezierTo; + + +#endif /* !SEEN_LIVAROT_FORWARD_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 : diff --git a/src/livarot/makefile.in b/src/livarot/makefile.in new file mode 100644 index 000000000..09b4789b9 --- /dev/null +++ b/src/livarot/makefile.in @@ -0,0 +1,17 @@ +# Convenience stub makefile to call the real Makefile. + +@SET_MAKE@ + +# Explicit so that it's the default rule. +all: + cd .. && $(MAKE) livarot/all + +clean %.a %.o: + cd .. && $(MAKE) livarot/$@ + +.PHONY: all clean + +OBJEXT = @OBJEXT@ + +.SUFFIXES: +.SUFFIXES: .a .$(OBJEXT) diff --git a/src/livarot/path-description.cpp b/src/livarot/path-description.cpp new file mode 100644 index 000000000..96cd81ee6 --- /dev/null +++ b/src/livarot/path-description.cpp @@ -0,0 +1,171 @@ +#include "libnr/nr-point-matrix-ops.h" +#include "livarot/path-description.h" + +PathDescr *PathDescrMoveTo::clone() const +{ + return new PathDescrMoveTo(*this); +} + +void PathDescrMoveTo::dumpSVG(Inkscape::SVGOStringStream& s, NR::Point const &last) const +{ + s << "M " << p[NR::X] << " " << p[NR::Y] << " "; +} + +void PathDescrMoveTo::transform(NR::Matrix const& t) +{ + p = p * t; +} + +void PathDescrMoveTo::dump(std::ostream &s) const +{ + /* localizing ok */ + s << " m " << p[NR::X] << " " << p[NR::Y]; +} + +void PathDescrLineTo::dumpSVG(Inkscape::SVGOStringStream& s, NR::Point const &last) const +{ + s << "L " << p[NR::X] << " " << p[NR::Y] << " "; +} + +PathDescr *PathDescrLineTo::clone() const +{ + return new PathDescrLineTo(*this); +} + +void PathDescrLineTo::transform(NR::Matrix const& t) +{ + p = p * t; +} + +void PathDescrLineTo::dump(std::ostream &s) const +{ + /* localizing ok */ + s << " l " << p[NR::X] << " " << p[NR::Y]; +} + +PathDescr *PathDescrBezierTo::clone() const +{ + return new PathDescrBezierTo(*this); +} + +void PathDescrBezierTo::transform(NR::Matrix const& t) +{ + p = p * t; +} + +void PathDescrBezierTo::dump(std::ostream &s) const +{ + /* localizing ok */ + s << " b " << p[NR::X] << " " << p[NR::Y] << " " << nb; +} + +PathDescr *PathDescrIntermBezierTo::clone() const +{ + return new PathDescrIntermBezierTo(*this); +} + +void PathDescrIntermBezierTo::transform(NR::Matrix const& t) +{ + p = p * t; +} + +void PathDescrIntermBezierTo::dump(std::ostream &s) const +{ + /* localizing ok */ + s << " i " << p[NR::X] << " " << p[NR::Y]; +} + +void PathDescrCubicTo::dumpSVG(Inkscape::SVGOStringStream& s, NR::Point const &last) const +{ + s << "C " + << last[NR::X] + start[0] / 3 << " " + << last[NR::Y] + start[1] / 3 << " " + << p[NR::X] - end[0] / 3 << " " + << p[NR::Y] - end[1] / 3 << " " + << p[NR::X] << " " + << p[NR::Y] << " "; +} + +PathDescr *PathDescrCubicTo::clone() const +{ + return new PathDescrCubicTo(*this); +} + +void PathDescrCubicTo::dump(std::ostream &s) const +{ + /* localizing ok */ + s << " c " + << p[NR::X] << " " << p[NR::Y] << " " + << start[NR::X] << " " << start[NR::Y] << " " + << end[NR::X] << " " << end[NR::Y] << " "; +} + +void PathDescrCubicTo::transform(NR::Matrix const& t) +{ + NR::Matrix tr = t; + tr[4] = tr[5] = 0; + start = start * tr; + end = end * tr; + + p = p * t; +} + +void PathDescrArcTo::dumpSVG(Inkscape::SVGOStringStream& s, NR::Point const &last) const +{ + s << "A " + << rx << " " + << ry << " " + << angle << " " + << (large ? "1" : "0") << " " + << (clockwise ? "0" : "1") << " " + << p[NR::X] << " " + << p[NR::Y] << " "; +} + +PathDescr *PathDescrArcTo::clone() const +{ + return new PathDescrArcTo(*this); +} + +void PathDescrArcTo::transform(NR::Matrix const& t) +{ + p = p * t; +} + +void PathDescrArcTo::dump(std::ostream &s) const +{ + /* localizing ok */ + s << " a " + << p[NR::X] << " " << p[NR::Y] << " " + << rx << " " << ry << " " + << angle << " " + << (clockwise ? 1 : 0) << " " + << (large ? 1 : 0); +} + +PathDescr *PathDescrForced::clone() const +{ + return new PathDescrForced(*this); +} + +void PathDescrClose::dumpSVG(Inkscape::SVGOStringStream& s, NR::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 000000000..0086d86b1 --- /dev/null +++ b/src/livarot/path-description.h @@ -0,0 +1,165 @@ +#ifndef SEEN_INKSCAPE_LIVAROT_PATH_DESCRIPTION_H +#define SEEN_INKSCAPE_LIVAROT_PATH_DESCRIPTION_H + +#include "svg/stringstream.h" +#include "libnr/nr-point.h" + +// path description commands +/* FIXME: these should be unnecessary once the refactoring of the path +** description stuff is finished. +*/ +enum +{ + descr_moveto = 0, // a moveto + descr_lineto = 1, // a (guess what) lineto + descr_cubicto = 2, + descr_bezierto = 3, // "beginning" of a quadratic bezier spline, will contain its endpoint (i know, it's bad...) + descr_arcto = 4, + descr_close = 5, + descr_interm_bezier = 6, // control point of the bezier spline + descr_forced = 7, + + descr_type_mask = 15 // the command no will be stored in a "flags" field, potentially with other info, so we need + // a mask to AND the field and extract the command +}; + +struct PathDescr +{ + PathDescr() : flags(0), associated(-1), tSt(0), tEn(1) {} + PathDescr(int f) : flags(f), associated(-1), tSt(0), tEn(1) {} + virtual ~PathDescr() {} + + int getType() const { return flags & descr_type_mask; } + void setType(int t) { + flags &= ~descr_type_mask; + flags |= t; + } + + virtual void dumpSVG(Inkscape::SVGOStringStream &s, NR::Point const &last) const {} + virtual PathDescr *clone() const = 0; + virtual void transform(NR::Matrix const &t) {} + virtual void dump(std::ostream &s) const {} + + int flags; // most notably contains the path command no + int associated; // index in the polyline of the point that ends the path portion of this command + double tSt; + double tEn; +}; + +struct PathDescrMoveTo : public PathDescr +{ + PathDescrMoveTo(NR::Point const &pp) + : PathDescr(descr_moveto), p(pp) {} + + void dumpSVG(Inkscape::SVGOStringStream &s, NR::Point const &last) const; + PathDescr *clone() const; + void transform(NR::Matrix const &t); + void dump(std::ostream &s) const; + + NR::Point p; +}; + +struct PathDescrLineTo : public PathDescr +{ + PathDescrLineTo(NR::Point const &pp) + : PathDescr(descr_lineto), p(pp) {} + + void dumpSVG(Inkscape::SVGOStringStream &s, NR::Point const &last) const; + PathDescr *clone() const; + void transform(NR::Matrix const &t); + void dump(std::ostream &s) const; + + NR::Point p; +}; + +// quadratic bezier curves: a set of control points, and an endpoint +struct PathDescrBezierTo : public PathDescr +{ + PathDescrBezierTo(NR::Point const &pp, int n) + : PathDescr(descr_bezierto), p(pp), nb(n) {} + + PathDescr *clone() const; + void transform(NR::Matrix const &t); + void dump(std::ostream &s) const; + + NR::Point p; // the endpoint's coordinates + int nb; // number of control points, stored in the next path description commands +}; + +/* FIXME: I don't think this should be necessary */ +struct PathDescrIntermBezierTo : public PathDescr +{ + PathDescrIntermBezierTo() + : PathDescr(descr_interm_bezier) , p(0, 0) {} + PathDescrIntermBezierTo(NR::Point const &pp) + : PathDescr(descr_interm_bezier), p(pp) {} + + PathDescr *clone() const; + void transform(NR::Matrix const &t); + void dump(std::ostream &s) const; + + NR::Point p; // control point coordinates +}; + +// cubic spline curve: 2 tangents and one endpoint +struct PathDescrCubicTo : public PathDescr +{ + PathDescrCubicTo(NR::Point const &pp, NR::Point const &s, NR::Point const& e) + : PathDescr(descr_cubicto), p(pp), start(s), end(e) {} + + void dumpSVG(Inkscape::SVGOStringStream &s, NR::Point const &last) const; + PathDescr *clone() const; + void transform(NR::Matrix const &t); + void dump(std::ostream &s) const; + + NR::Point p; + NR::Point start; + NR::Point end; +}; + +// arc: endpoint, 2 radii and one angle, plus 2 booleans to choose the arc (svg style) +struct PathDescrArcTo : public PathDescr +{ + PathDescrArcTo(NR::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, NR::Point const &last) const; + PathDescr *clone() const; + void transform(NR::Matrix const &t); + void dump(std::ostream &s) const; + + NR::Point p; + double rx; + double ry; + double angle; + bool large; + bool clockwise; +}; + +struct PathDescrForced : public PathDescr +{ + PathDescrForced() : PathDescr(descr_forced), p(0, 0) {} + + PathDescr *clone() const; + + /* FIXME: not sure whether _forced should have a point associated with it; + ** Path::ConvertForcedToMoveTo suggests that maybe it should. + */ + NR::Point p; +}; + +struct PathDescrClose : public PathDescr +{ + PathDescrClose() : PathDescr(descr_close) {} + + void dumpSVG(Inkscape::SVGOStringStream &s, NR::Point const &last) const; + PathDescr *clone() const; + + /* FIXME: not sure whether _forced should have a point associated with it; + ** Path::ConvertForcedToMoveTo suggests that maybe it should. + */ + NR::Point p; +}; + +#endif + diff --git a/src/livarot/sweep-event-queue.h b/src/livarot/sweep-event-queue.h new file mode 100644 index 000000000..b3cdd4c9d --- /dev/null +++ b/src/livarot/sweep-event-queue.h @@ -0,0 +1,54 @@ +#ifndef SEEN_LIVAROT_SWEEP_EVENT_QUEUE_H +#define SEEN_LIVAROT_SWEEP_EVENT_QUEUE_H +/** \file + * A container of intersection events. + */ + +#include +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); + ~SweepEventQueue(); + + int size() const { return nbEvt; } + + /// Look for the topmost intersection in the heap + bool peek(SweepTree * &iLeft, SweepTree * &iRight, NR::Point &oPt, double &itl, double &itr); + /// Extract the topmost intersection from the heap + bool extract(SweepTree * &iLeft, SweepTree * &iRight, NR::Point &oPt, double &itl, double &itr); + /// Add one intersection in the binary heap + SweepEvent *add(SweepTree *iLeft, SweepTree *iRight, NR::Point &iPt, double itl, double itr); + + void remove(SweepEvent *e); + 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:encoding=utf-8:textwidth=99 : diff --git a/src/livarot/sweep-event.cpp b/src/livarot/sweep-event.cpp new file mode 100644 index 000000000..e84048a07 --- /dev/null +++ b/src/livarot/sweep-event.cpp @@ -0,0 +1,275 @@ +#include +#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, NR::Point &px, double itl, double itr) +{ + if (nbEvt > maxEvt) { + return NULL; + } + + int const n = nbEvt++; + events[n].MakeNew (iLeft, iRight, px, itl, itr); + + SweepTree *t[2] = { iLeft, iRight }; + for (int i = 0; i < 2; i++) { + Shape *s = t[i]->src; + Shape::dg_arete const &e = s->getEdge(t[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, NR::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, NR::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; + NR::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 son1 = 2 * curInd + 1; + int const son2 = son1 + 1; + int const no1 = inds[son1]; + int const no2 = inds[son2]; + if (son2 < 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 = son1; + events[no1].ind = curInd; + inds[son1] = to; + inds[curInd] = no1; + curInd = son1; + } else { + events[to].ind = son2; + events[no2].ind = curInd; + inds[son2] = to; + inds[curInd] = no2; + curInd = son2; + } + } else { + if (px[1] > events[no2].posx[1] + || (px[1] == events[no2].posx[1] + && px[0] > events[no2].posx[0])) + { + events[to].ind = son2; + events[no2].ind = curInd; + inds[son2] = to; + inds[curInd] = no2; + curInd = son2; + } 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 = son1; + events[no1].ind = curInd; + inds[son1] = 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 (NULL, NULL, NR::Point(0, 0), 0, 0); +} + +SweepEvent::~SweepEvent() +{ + MakeDelete(); +} + +void SweepEvent::MakeNew(SweepTree *iLeft, SweepTree *iRight, NR::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] = NULL; + sweep[i] = NULL; + } +} + + +/* + 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 : diff --git a/src/livarot/sweep-event.h b/src/livarot/sweep-event.h new file mode 100644 index 000000000..7231dd01d --- /dev/null +++ b/src/livarot/sweep-event.h @@ -0,0 +1,45 @@ +#ifndef INKSCAPE_LIVAROT_SWEEP_EVENT_H +#define INKSCAPE_LIVAROT_SWEEP_EVENT_H +/** \file + * Intersection events. + */ + +#include +class SweepTree; + + +/** One intersection event. */ +class SweepEvent +{ +public: + SweepTree *sweep[2]; ///< Sweep element associated with the left and right edge of the intersection. + + NR::Point posx; ///< Coordinates of the intersection. + double tl, tr; ///< Coordinates of the intersection on the left edge (tl) and on the right edge (tr). + + int ind; ///< Index in the binary heap. + + SweepEvent(); // not used. + ~SweepEvent(); // not used. + + /// Initialize a SweepEvent structure. + void MakeNew (SweepTree * iLeft, SweepTree * iRight, NR::Point const &iPt, + double itl, double itr); + + /// Void a SweepEvent structure. + void MakeDelete (void); +}; + + +#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:encoding=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 000000000..d59728e42 --- /dev/null +++ b/src/livarot/sweep-tree-list.cpp @@ -0,0 +1,47 @@ +#include +#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(NULL) +{ + /* 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 = NULL; +} + + +SweepTree *SweepTreeList::add(Shape *iSrc, int iBord, int iWeight, int iStartPoint, Shape *iDst) +{ + if (nbTree >= maxTree) { + return NULL; + } + + 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:encoding=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 000000000..730b1cc55 --- /dev/null +++ b/src/livarot/sweep-tree-list.h @@ -0,0 +1,37 @@ +#ifndef INKSCAPE_LIVAROT_SWEEP_TREE_LIST_H +#define INKSCAPE_LIVAROT_SWEEP_TREE_LIST_H +/** \file SweepTreeList definition. */ + +class Shape; +class SweepTree; + +/** + * The sweepline: a set of edges intersecting the current sweepline + * stored as an AVL tree. + */ +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. + + SweepTreeList(int s); + ~SweepTreeList(); + + 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:encoding=utf-8:textwidth=99 : diff --git a/src/livarot/sweep-tree.cpp b/src/livarot/sweep-tree.cpp new file mode 100644 index 000000000..0e4567663 --- /dev/null +++ b/src/livarot/sweep-tree.cpp @@ -0,0 +1,558 @@ +#include "libnr/nr-point-fns.h" +#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 = NULL; + bord = -1; + startPoint = -1; + evt[LEFT] = evt[RIGHT] = NULL; + 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] = NULL; + 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] = NULL; + } + evt[i] = NULL; + } + + 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(NR::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 + NR::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(); + + NR::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 + NR::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(nNorm, bNorm); + } else { + y = cross(bNorm, nNorm); + } + if (y == 0) { + y = dot(bNorm, nNorm); + if (y == 0) { + insertL = this; + insertR = static_cast(elem[RIGHT]); + return found_exact; + } + } + } + if (y < 0) { + if (son[LEFT]) { + return (static_cast(son[LEFT]))->Find(px, newOne, + insertL, insertR, + sweepSens); + } else { + insertR = this; + insertL = static_cast(elem[LEFT]); + if (insertL) { + return found_between; + } else { + return found_on_left; + } + } + } else { + if (son[RIGHT]) { + return (static_cast(son[RIGHT]))->Find(px, newOne, + insertL, insertR, + sweepSens); + } else { + insertL = this; + insertR = static_cast(elem[RIGHT]); + if (insertR) { + return found_between; + } else { + return found_on_right; + } + } + } + return not_found; +} + +// only find a point's position +int +SweepTree::Find(NR::Point const &px, SweepTree * &insertL, + SweepTree * &insertR) +{ + NR::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; + } + bNorm=bNorm.ccw(); + + NR::Point diff; + diff = px - bOrig; + + double y = 0; + y = dot(bNorm, diff); + if (y == 0) + { + insertL = this; + insertR = static_cast(elem[RIGHT]); + return found_exact; + } + if (y < 0) + { + if (son[LEFT]) + { + return (static_cast(son[LEFT]))->Find(px, insertL, + insertR); + } + else + { + insertR = this; + insertL = static_cast(elem[LEFT]); + if (insertL) + { + return found_between; + } + else + { + return found_on_left; + } + } + } + else + { + if (son[RIGHT]) + { + return (static_cast(son[RIGHT]))->Find(px, insertL, + insertR); + } + else + { + insertL = this; + insertR = static_cast(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] = NULL; + } +} + +int +SweepTree::Remove(SweepTreeList &list, SweepEventQueue &queue, + bool rebalance) +{ + RemoveEvents(queue); + AVLTree *tempR = static_cast(list.racine); + int err = AVLTree::Remove(tempR, rebalance); + list.racine = static_cast(tempR); + MakeDelete(); + if (list.nbTree <= 1) + { + list.nbTree = 0; + list.racine = NULL; + } + 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 (list.racine == NULL) + { + list.racine = this; + return avl_no_err; + } + SweepTree *insertL = NULL; + SweepTree *insertR = NULL; + int insertion = + list.racine->Find(iDst->getPoint(iAtPoint).x, this, + insertL, insertR, sweepSens); + + if (insertion == found_exact) { + 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); + } + + AVLTree *tempR = static_cast(list.racine); + int err = + AVLTree::Insert(tempR, insertion, static_cast(insertL), + static_cast(insertR), rebalance); + list.racine = static_cast(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 (list.racine == NULL) + { + list.racine = this; + return avl_no_err; + } + + NR::Point fromP; + fromP = src->pData[fromPt].rx; + NR::Point nNorm; + nNorm = src->getEdge(bord).dx; + if (src->getEdge(bord).st > src->getEdge(bord).en) + { + nNorm = -nNorm; + } + if (sweepSens == false) + { + nNorm = -nNorm; + } + + NR::Point bNorm; + bNorm = insNode->src->getEdge(insNode->bord).dx; + if (insNode->src->getEdge(insNode->bord).st > + insNode->src->getEdge(insNode->bord).en) + { + bNorm = -bNorm; + } + + SweepTree *insertL = NULL; + SweepTree *insertR = NULL; + double ang = cross(nNorm, bNorm); + if (ang == 0) + { + insertL = insNode; + insertR = static_cast(insNode->elem[RIGHT]); + } + else if (ang > 0) + { + insertL = insNode; + insertR = static_cast(insNode->elem[RIGHT]); + + while (insertL) + { + if (insertL->src == src) + { + if (insertL->src->getEdge(insertL->bord).st != fromPt + && insertL->src->getEdge(insertL->bord).en != fromPt) + { + break; + } + } + else + { + 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 = insertL->src->getEdge(insertL->bord).dx; + if (insertL->src->getEdge(insertL->bord).st > + insertL->src->getEdge(insertL->bord).en) + { + bNorm = -bNorm; + } + ang = cross(nNorm, bNorm); + if (ang <= 0) + { + break; + } + insertR = insertL; + insertL = static_cast(insertR->elem[LEFT]); + } + } + else if (ang < 0) + { + insertL = insNode; + insertR = static_cast(insNode->elem[RIGHT]); + + while (insertR) + { + if (insertR->src == src) + { + if (insertR->src->getEdge(insertR->bord).st != fromPt + && insertR->src->getEdge(insertR->bord).en != fromPt) + { + 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; + } + } + bNorm = insertR->src->getEdge(insertR->bord).dx; + if (insertR->src->getEdge(insertR->bord).st > + insertR->src->getEdge(insertR->bord).en) + { + bNorm = -bNorm; + } + ang = cross(nNorm, bNorm); + if (ang > 0) + { + break; + } + insertL = insertR; + insertR = static_cast(insertL->elem[RIGHT]); + } + } + + int insertion = found_between; + + if (insertL == NULL) { + insertion = found_on_left; + } + if (insertR == NULL) { + insertion = found_on_right; + } + + if (insertion == found_exact) { + /* FIXME: surely this can never be called? */ + 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); + } + + AVLTree *tempR = static_cast(list.racine); + int err = + AVLTree::Insert(tempR, insertion, static_cast(insertL), + static_cast(insertR), rebalance); + list.racine = static_cast(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; +} + +void +SweepTree::SwapWithRight(SweepTreeList &list, SweepEventQueue &queue) +{ + SweepTree *tL = this; + SweepTree *tR = static_cast(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:encoding=utf-8:textwidth=99 : diff --git a/src/livarot/sweep-tree.h b/src/livarot/sweep-tree.h new file mode 100644 index 000000000..80a1a8273 --- /dev/null +++ b/src/livarot/sweep-tree.h @@ -0,0 +1,82 @@ +#ifndef INKSCAPE_LIVAROT_SWEEP_TREE_H +#define INKSCAPE_LIVAROT_SWEEP_TREE_H + +#include "libnr/nr-point.h" +#include "livarot/AVL.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. + */ +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(); + + // Inits a brand new node. + 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 + void ConvertTo(Shape *iSrc, int iBord, int iWeight, int iStartPoint); + + // Delete the contents of node. + void MakeDelete(); + + // utilites + + // the find function that was missing in the AVLTrree class + // the return values are defined in LivarotDefs.h + int Find(NR::Point const &iPt, SweepTree *newOne, SweepTree *&insertL, + SweepTree *&insertR, bool sweepSens = true); + int Find(NR::Point const &iPt, SweepTree *&insertL, SweepTree *&insertR); + + /// Remove sweepevents attached to this node. + void RemoveEvents(SweepEventQueue &queue); + + 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); + int Insert(SweepTreeList &list, SweepEventQueue &queue, Shape *iDst, + int iAtPoint, bool rebalance = true, bool sweepSens = true); + 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. + void SwapWithRight(SweepTreeList &list, SweepEventQueue &queue); + + void Avance(Shape *dst, int nPt, Shape *a, Shape *b); + + 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:encoding=utf-8:textwidth=99 : diff --git a/src/macros.h b/src/macros.h new file mode 100644 index 000000000..d43dbc692 --- /dev/null +++ b/src/macros.h @@ -0,0 +1,54 @@ +#ifndef __MACROS_H__ +#define __MACROS_H__ + +/* + * Useful macros for inkscape + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL + */ + +#ifdef SP_MACROS_SILENT +#define SP_PRINT_MATRIX(s,m) +#define SP_PRINT_TRANSFORM(s,t) +#define SP_PRINT_DRECT(s,r) +#define SP_PRINT_DRECT_WH(s,r) +#define SP_PRINT_IRECT(s,r) +#define SP_PRINT_IRECT_WH(s,r) +#else +#define SP_PRINT_MATRIX(s,m) g_print("%s (%g %g %g %g %g %g)\n", (s), (m)->c[0], (m)->c[1], (m)->c[2], (m)->c[3], (m)->c[4], (m)->c[5]) +#define SP_PRINT_TRANSFORM(s,t) g_print("%s (%g %g %g %g %g %g)\n", (s), (t)[0], (t)[1], (t)[2], (t)[3], (t)[4], (t)[5]) +#define SP_PRINT_DRECT(s,r) g_print("%s (%g %g %g %g)\n", (s), (r)->x0, (r)->y0, (r)->x1, (r)->y1) +#define SP_PRINT_DRECT_WH(s,r) g_print("%s (%g %g %g %g)\n", (s), (r)->x0, (r)->y0, (r)->x1 - (r)->x0, (r)->y1 - (r)->y0) +#define SP_PRINT_IRECT(s,r) g_print("%s (%d %d %d %d)\n", (s), (r)->x0, (r)->y0, (r)->x1, (r)->y1) +#define SP_PRINT_IRECT_WH(s,r) g_print("%s (%d %d %d %d)\n", (s), (r)->x0, (r)->y0, (r)->x1 - (r)->x0, (r)->y1 - (r)->y0) +#endif + +#define sp_signal_disconnect_by_data(o,d) g_signal_handlers_disconnect_matched(o, G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, d) + +#define sp_round(v,m) (((v) < 0.0) ? ((ceil((v) / (m) - 0.5)) * (m)) : ((floor((v) / (m) + 0.5)) * (m))) + +#endif + +// keyboard modifiers in an event +#define MOD__SHIFT (event->key.state & GDK_SHIFT_MASK) +#define MOD__CTRL (event->key.state & GDK_CONTROL_MASK) +#define MOD__ALT (event->key.state & GDK_MOD1_MASK) +#define MOD__SHIFT_ONLY ((event->key.state & GDK_SHIFT_MASK) && !(event->key.state & GDK_CONTROL_MASK) && !(event->key.state & GDK_MOD1_MASK)) +#define MOD__CTRL_ONLY (!(event->key.state & GDK_SHIFT_MASK) && (event->key.state & GDK_CONTROL_MASK) && !(event->key.state & GDK_MOD1_MASK)) +#define MOD__ALT_ONLY (!(event->key.state & GDK_SHIFT_MASK) && !(event->key.state & GDK_CONTROL_MASK) && (event->key.state & GDK_MOD1_MASK)) + +/* + 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 : diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 000000000..165316cd2 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,1543 @@ +#define __MAIN_C__ + +/** \file + * Inkscape - an ambitious vector drawing program + * + * Authors: + * Lauris Kaplinski + * Frank Felfe + * Davide Puricelli + * Mitsuru Oka + * Masatake YAMATO + * F.J.Franklin + * Michael Meeks + * Chema Celorio + * Pawel Palucha + * Bryce Harrington + * ... and various people who have worked with various projects + * + * Copyright (C) 1999-2004 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +// Putting the following in main.cpp appears a natural choice. + +/** \mainpage The Inkscape Source Code Documentation + * While the standard doxygen documentation can be accessed through the links + * in the header, the following documents are additionally available to the + * interested reader. + * + * \section groups Main directory documentation + * Inkscape's classes and files in the main directory can be grouped into + * the following categories: + * + * - \subpage ObjectTree - inkscape's SVG canvas + * - \subpage Tools - the tools UI + * - \subpage UI - inkscape's user interface + * - \subpage XmlTree - XML backbone of the document + * - \subpage Rendering - rendering and buffering + * - \subpage OtherServices - what doesn't fit in the above + * + * See also the
other directories until doxygen + * allows setting links to those doc files. + * + * \section extlinks Links to external documentation + * + * \subsection liblinks External documentation on libraries used in inkscape + * + * Gtkmm + * atkmm + * gdkmm + * pangomm + * libsigc++ + * GTK+ + * gdk-pixbuf + * GObject + * atk + * pango + * GnomeVFS + * libsigc + * ORBit + * bonobo + * bonobo-activation + * libxslt + * libxml2 + * + * \subsection stdlinks External standards documentation + * + * SVG1.1 + * SVG1.2 + * SVGMobile + * SVGTest + * PNG + * XSLT + * PS + * Gnome-HIG + */ + +/** \page ObjectTree Object Tree Classes and Files + * Inkscape::ObjectHierarchy [\ref object-hierarchy.cpp, \ref object-hierarchy.h] + * - SPObject [\ref sp-object.cpp, \ref sp-object.h, \ref object-edit.cpp, \ref sp-object-repr.cpp] + * - SPDefs [\ref sp-defs.cpp, \ref sp-defs.h] + * - SPFlowline [\ref sp-flowdiv.cpp, \ref sp-flowdiv.h] + * - SPFlowregionbreak [\ref sp-flowdiv.cpp, \ref sp-flowdiv.h] + * - SPGuide [\ref sp-guide.cpp, \ref sp-guide.h] + * - SPItem [\ref sp-item.cpp, \ref sp-item.h, \ref sp-item-notify-moveto.cpp, \ref sp-item-rm-unsatisfied-cns.cpp, \ref sp-item-transform.cpp, \ref sp-item-update-cns.cpp, ] + * - SPFlowdiv [\ref sp-flowdiv.cpp, \ref sp-flowdiv.h] + * - SPFlowpara [\ref sp-flowdiv.cpp, \ref sp-flowdiv.h] + * - SPFlowregion [\ref sp-flowregion.cpp, \ref sp-flowregion.h] + * - SPFlowregionExclude [\ref sp-flowregion.cpp, \ref sp-flowregion.h] + * - SPFlowtext [\ref sp-flowtext.cpp, \ref sp-flowtext.h] + * - SPFlowtspan [\ref sp-flowdiv.cpp, \ref sp-flowdiv.h] + * - SPGroup [\ref sp-item-group.cpp, \ref sp-item-group.h] + * - SPAnchor [\ref sp-anchor.cpp, \ref sp-anchor.h] + * - SPMarker [\ref sp-marker.cpp, \ref sp-marker.h] + * - SPRoot [\ref sp-root.cpp, \ref sp-root.h] + * - SPSymbol [\ref sp-symbol.cpp, \ref sp-symbol.h] + * - SPImage [\ref sp-image.cpp, \ref sp-image.h] + * - SPShape [\ref sp-shape.cpp, \ref sp-shape.h, \ref marker-status.cpp] + * - SPGenericEllipse [\ref sp-ellipse.cpp, \ref sp-ellipse.h] + * - SPArc + * - SPCircle + * - SPEllipse + * - SPLine [\ref sp-line.cpp, \ref sp-line.h] + * - SPOffset [\ref sp-offset.cpp, \ref sp-offset.h] + * - SPPath [\ref sp-path.cpp, \ref sp-path.h, \ref path-chemistry.cpp, \ref nodepath.cpp, \ref nodepath.h, \ref splivarot.cpp] + * - SPPolygon [\ref sp-polygon.cpp, \ref sp-polygon.h] + * - SPStar [\ref sp-star.cpp, \ref sp-star.h] + * - SPPolyLine [\ref sp-polyline.cpp, \ref sp-polyline.h] + * - SPRect [\ref sp-rect.cpp, \ref sp-rect.h] + * - SPSpiral [\ref sp-spiral.cpp, \ref sp-spiral.h] + * - SPText [\ref sp-text.cpp, \ref sp-text.h, \ref text-chemistry.cpp, \ref text-editing.cpp] + * - SPTextPath [\ref sp-tspan.cpp, \ref sp-tspan.h] + * - SPTSpan [\ref sp-tspan.cpp, \ref sp-tspan.h] + * - SPUse [\ref sp-use.cpp, \ref sp-use.h] + * - SPMetadata [\ref sp-metadata.cpp, \ref sp-metadata.h] + * - SPObjectGroup [\ref sp-object-group.cpp, \ref sp-object-group.h] + * - SPClipPath [\ref sp-clippath.cpp, \ref sp-clippath.h] + * - SPMask [\ref sp-mask.cpp, \ref sp-mask.h] + * - SPNamedView [\ref sp-namedview.cpp, \ref sp-namedview.h] + * - SPPaintServer [\ref sp-paint-server.cpp, \ref sp-paint-server.h] + * - SPGradient [\ref sp-gradient.cpp, \ref sp-gradient.h, \ref gradient-chemistry.cpp, \ref sp-gradient-reference.h, \ref sp-gradient-spread.h, \ref sp-gradient-units.h, \ref sp-gradient-vector.h] + * - SPLinearGradient + * - SPRadialGradient + * - SPPattern [\ref sp-pattern.cpp, \ref sp-pattern.h] + * - SPSkeleton [\ref sp-skeleton.cpp, \ref sp-skeleton.h] + * - SPStop [\ref sp-stop.h] + * - SPString [\ref sp-string.cpp, \ref sp-string.h] + * - SPStyleElem [\ref sp-style-elem.cpp, \ref sp-style-elem.h] + * + */ +/** \page Tools Tools Related Classes and Files + * + * SelCue [\ref selcue.cpp, \ref selcue.h, \ref rubberband.cpp] + * Inkscape::Selection [\ref selection.cpp, \ref selection.h, \ref selection-chemistry.cpp] + * SPSelTrans [\ref seltrans.cpp, \ref seltrans.h] + * + * \section Event Context Class Hierarchy + * + *- SPEventContext[\ref event-context.cpp, \ref event-context.h] + * - SPArcContext [\ref arc-context.cpp, \ref arc-context.h] + * - SPDrawContext [\ref draw-context.cpp, \ref draw-context.h] + * - SPPenContext [\ref pen-context.cpp, \ref pen-context.h] + * - SPPencilContext [\ref pencil-context.cpp, \ref pencil-context.h] + * - SPConnectorContext [\ref connector-context.cpp, \ref connector-context.h, \ref sp-conn-end.cpp, \ref sp-conn-end-pair.cpp] + * - SPGradientContext [\ref gradient-context.cpp, \ref gradient-context.h, \ref gradient-drag.cpp, \ref gradient-toolbar.cpp] + * - SPRectContext [\ref rect-context.cpp, \ref rect-context.h] + * - SPSelectContext [\ref select-context.cpp, \ref select-context.h] + * - SPSpiralContext [\ref spiral-context.cpp, \ref spiral-context.h] + * - SPStarContext [\ref star-context.cpp, \ref star-context.h] + * + * SPNodeContext [\ref node-context.cpp, \ref node-context.h] + * + * SPZoomContext [\ref zoom-context.cpp, \ref zoom-context.h] + * + * SPDynaDrawContext [\ref dyna-draw-context.cpp, \ref dyna-draw-context.h] + * + * SPDropperContext [\ref dropper-context.cpp, \ref dropper-context.h] + */ +/** \page UI User Interface Classes and Files + * + * - Inkscape::UI::View::View [\ref ui/view/view.cpp, \ref ui/view/view.h] + * - Inkscape::UI::View::Edit [\ref ui/view/edit.cpp, \ref ui/view/edit.h] + * - SPDesktop [\ref desktop.cpp, \ref desktop-affine.cpp, \ref desktop-events.cpp, \ref desktop-handles.cpp, \ref desktop-style.cpp, \ref desktop.h, \ref desktop-affine.h, \ref desktop-events.h, \ref desktop-handles.h, \ref desktop-style.h] + * - SPSVGView [\ref svg-view.cpp, \ref svg-view.h] + * + * SPDesktopWidget [\ref desktop-widget.h] SPSVGSPViewWidget [\ref svg-view.cpp] + * SPDocument [\ref document.cpp, \ref document.h] + * + * SPDrawAnchor [\ref draw-anchor.cpp, \ref draw-anchor.h] + * SPKnot [\ref knot.cpp, \ref knot.h, \ref knot-enums.h] + * SPKnotHolder [\ref knotholder.cpp, \ref knotholder.h, \ref knot-holder-entity.h] + * + * [\ref layer-fns.cpp, \ref selection-describer.h] + * Inkscape::MessageContext [\ref message-context.h] + * Inkscape::MessageStack [\ref message-stack.h, \ref message.h] + * + * Snapper, GridSnapper, GuideSnapper [\ref snap.cpp, \ref snap.h] + * + * SPGuide [\ref sp-guide.cpp, \ref sp-guide.h, \ref satisfied-guide-cns.cpp, \ref sp-guide-attachment.h, \ref sp-guide-constraint.h] + * + * [\ref help.cpp] [\ref inkscape.cpp] [\ref inkscape-stock.cpp] + * [\ref interface.cpp, \ref memeq.h] [\ref main.cpp, \ref winmain.cpp] + * [\ref menus-skeleton.h, \ref preferences-skeleton.h] + * [\ref object-ui.cpp] [\ref select-toolbar.cpp] [\ref shortcuts.cpp] + * [\ref sp-cursor.cpp] [\ref text-edit.cpp] [\ref toolbox.cpp, \ref ui/widget/toolbox.cpp] + * Inkscape::Verb [\ref verbs.h] + * + */ +/** \page XmlTree CSS/XML Tree Classes and Files + * + * SPStyle [\ref style.cpp, \ref style.h] + * Media [\ref media.cpp, \ref media.h] + * [\ref attributes.cpp, \ref attributes.h] + * + * - Inkscape::URIReference [\ref uri-references.cpp, \ref uri-references.h] + * - SPClipPathReference [\ref sp-clippath.h] + * - SPGradientReference [\ref sp-gradient-reference.h] + * - SPMarkerReference [\ref sp-marker.h] + * - SPMaskReference [\ref sp-mask.h] + * - SPUseReference [\ref sp-use-reference.h] + * - SPUsePath + */ +/** \page Rendering Rendering Related Classes and Files + * + * SPColor [\ref color.cpp, \ref color.h, \ref color-rgba.h] + * [\ref geom.cpp] [\ref isnan.h] [\ref mod360.cpp] + */ +/** \page OtherServices Classes and Files From Other Services + * [\ref inkview.cpp, \ref slideshow.cpp] [\ref sp-animation.cpp] + * + * Inkscape::GC + * + * [\ref sp-metrics.cpp, \ref sp-metrics.h] + * + * [\ref prefs-utils.cpp] [\ref print.cpp] + * + * - Inkscape::GZipBuffer [\ref streams-gzip.h] + * - Inkscape::JarBuffer [\ref streams-jar.h] + * - Inkscape::ZlibBuffer [\ref streams-zlib.h] + * - Inkscape::URIHandle [\ref streams-handles.h] + * - Inkscape::FileHandle + * [\ref dir-util.cpp] [\ref file.cpp] + * Inkscape::URI [\ref uri.h, \ref extract-uri.cpp, \ref uri-references.cpp] + * Inkscape::BadURIException [\ref bad-uri-exception.h] + * + * Inkscape::Whiteboard::UndoStackObserver [\ref undo-stack-observer.cpp, \ref composite-undo-stack-observer.cpp] + * [\ref document-undo.cpp] + * + * {\ref dialogs/} [\ref approx-equal.h] [\ref decimal-round.h] [\ref enums.h] [\ref unit-constants.h] + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include "path-prefix.h" + +#include + +#ifdef HAVE_IEEEFP_H +#include +#endif +#include +#include + +#include +#ifndef POPT_TABLEEND +#define POPT_TABLEEND { NULL, '\0', 0, 0, 0, NULL, NULL } +#endif /* Not def: POPT_TABLEEND */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "gc-core.h" + +#include "macros.h" +#include "file.h" +#include "document.h" +#include "sp-object.h" +#include "interface.h" +#include "print.h" +#include "slideshow.h" +#include "color.h" +#include "sp-item.h" +#include "sp-root.h" +#include "unit-constants.h" + +#include "svg/svg.h" +#include "svg/stringstream.h" + +#include "inkscape-private.h" +#include "inkscape-stock.h" +#include "inkscape_version.h" + +#include "sp-namedview.h" +#include "sp-guide.h" +#include "sp-object-repr.h" +#include "xml/repr.h" + +#include "io/sys.h" + +#include "debug/logger.h" + +#include +#include +#include +#include + +#ifdef WIN32 +//#define REPLACEARGS_ANSI +//#define REPLACEARGS_DEBUG + +#include "registrytool.h" + +#include "extension/internal/win32.h" +using Inkscape::Extension::Internal::PrintWin32; + +#endif // WIN32 + +#include "extension/init.h" + +#include +#include + +#ifndef HAVE_BIND_TEXTDOMAIN_CODESET +#define bind_textdomain_codeset(p,c) +#endif + +#include "application/application.h" + +enum { + SP_ARG_NONE, + SP_ARG_NOGUI, + SP_ARG_GUI, + SP_ARG_FILE, + SP_ARG_PRINT, + SP_ARG_EXPORT_PNG, + SP_ARG_EXPORT_DPI, + SP_ARG_EXPORT_AREA, + SP_ARG_EXPORT_AREA_DRAWING, + SP_ARG_EXPORT_AREA_SNAP, + SP_ARG_EXPORT_WIDTH, + SP_ARG_EXPORT_HEIGHT, + SP_ARG_EXPORT_ID, + SP_ARG_EXPORT_ID_ONLY, + SP_ARG_EXPORT_USE_HINTS, + SP_ARG_EXPORT_BACKGROUND, + SP_ARG_EXPORT_BACKGROUND_OPACITY, + SP_ARG_EXPORT_SVG, + SP_ARG_EXPORT_PS, + SP_ARG_EXPORT_EPS, + SP_ARG_EXPORT_TEXT_TO_PATH, + SP_ARG_EXPORT_BBOX_PAGE, + SP_ARG_EXTENSIONDIR, + SP_ARG_SLIDESHOW, + SP_ARG_QUERY_X, + SP_ARG_QUERY_Y, + SP_ARG_QUERY_WIDTH, + SP_ARG_QUERY_HEIGHT, + SP_ARG_QUERY_ID, + SP_ARG_VERSION, + SP_ARG_NEW_GUI, + SP_ARG_VACUUM_DEFS, + SP_ARG_LAST +}; + +int sp_main_gui(int argc, char const **argv); +int sp_main_console(int argc, char const **argv); +static void sp_do_export_png(SPDocument *doc); +static void do_export_ps(SPDocument* doc, gchar const* uri, char const *mime); +static void do_query_dimension (SPDocument *doc, bool extent, NR::Dim2 const axis, const gchar *id); + + +static gchar *sp_global_printer = NULL; +static gboolean sp_global_slideshow = FALSE; +static gchar *sp_export_png = NULL; +static gchar *sp_export_dpi = NULL; +static gchar *sp_export_area = NULL; +static gboolean sp_export_area_drawing = FALSE; +static gchar *sp_export_width = NULL; +static gchar *sp_export_height = NULL; +static gchar *sp_export_id = NULL; +static gchar *sp_export_background = NULL; +static gchar *sp_export_background_opacity = NULL; +static gboolean sp_export_area_snap = FALSE; +static gboolean sp_export_use_hints = FALSE; +static gboolean sp_export_id_only = FALSE; +static gchar *sp_export_svg = NULL; +static gchar *sp_export_ps = NULL; +static gchar *sp_export_eps = NULL; +static gboolean sp_export_text_to_path = FALSE; +static gboolean sp_export_bbox_page = FALSE; +static gboolean sp_query_x = FALSE; +static gboolean sp_query_y = FALSE; +static gboolean sp_query_width = FALSE; +static gboolean sp_query_height = FALSE; +static gchar *sp_query_id = NULL; +static int sp_new_gui = FALSE; +static gboolean sp_vacuum_defs = FALSE; + +static gchar *sp_export_png_utf8 = NULL; +static gchar *sp_export_svg_utf8 = NULL; +static gchar *sp_global_printer_utf8 = NULL; + +#ifdef WIN32 +static bool replaceArgs( int& argc, char**& argv ); +#endif +static GSList *sp_process_args(poptContext ctx); +struct poptOption options[] = { + {"version", 'V', + POPT_ARG_NONE, NULL, SP_ARG_VERSION, + N_("Print the Inkscape version number"), + NULL}, + + {"without-gui", 'z', + POPT_ARG_NONE, NULL, SP_ARG_NOGUI, + N_("Do not use X server (only process files from console)"), + NULL}, + + {"with-gui", 'g', + POPT_ARG_NONE, NULL, SP_ARG_GUI, + N_("Try to use X server (even if $DISPLAY is not set)"), + NULL}, + + {"file", 'f', + POPT_ARG_STRING, NULL, SP_ARG_FILE, + N_("Open specified document(s) (option string may be excluded)"), + N_("FILENAME")}, + + {"print", 'p', + POPT_ARG_STRING, &sp_global_printer, SP_ARG_PRINT, + N_("Print document(s) to specified output file (use '| program' for pipe)"), + N_("FILENAME")}, + + {"export-png", 'e', + POPT_ARG_STRING, &sp_export_png, SP_ARG_EXPORT_PNG, + N_("Export document to a PNG file"), + N_("FILENAME")}, + + {"export-dpi", 'd', + POPT_ARG_STRING, &sp_export_dpi, SP_ARG_EXPORT_DPI, + N_("The resolution used for exporting SVG into bitmap (default 90)"), + N_("DPI")}, + + {"export-area", 'a', + POPT_ARG_STRING, &sp_export_area, SP_ARG_EXPORT_AREA, + N_("Exported area in SVG user units (default is the canvas; 0,0 is lower-left corner)"), + N_("x0:y0:x1:y1")}, + + {"export-area-drawing", 'D', + POPT_ARG_NONE, &sp_export_area_drawing, SP_ARG_EXPORT_AREA_DRAWING, + N_("Exported area is the entire drawing (not canvas)"), + NULL}, + + {"export-area-snap", 0, + POPT_ARG_NONE, &sp_export_area_snap, SP_ARG_EXPORT_AREA_SNAP, + N_("Snap the bitmap export area outwards to the nearest integer values (in SVG user units)"), + NULL}, + + {"export-width", 'w', + POPT_ARG_STRING, &sp_export_width, SP_ARG_EXPORT_WIDTH, + N_("The width of exported bitmap in pixels (overrides export-dpi)"), + N_("WIDTH")}, + + {"export-height", 'h', + POPT_ARG_STRING, &sp_export_height, SP_ARG_EXPORT_HEIGHT, + N_("The height of exported bitmap in pixels (overrides export-dpi)"), + N_("HEIGHT")}, + + {"export-id", 'i', + POPT_ARG_STRING, &sp_export_id, SP_ARG_EXPORT_ID, + N_("The ID of the object to export (overrides export-area)"), + N_("ID")}, + + {"export-id-only", 'j', + POPT_ARG_NONE, &sp_export_id_only, SP_ARG_EXPORT_ID_ONLY, + // TRANSLATORS: this means: "Only export the object whose id is given in --export-id". + // See "man inkscape" for details. + N_("Export just the object with export-id, hide all others (only with export-id)"), + NULL}, + + {"export-use-hints", 't', + POPT_ARG_NONE, &sp_export_use_hints, SP_ARG_EXPORT_USE_HINTS, + N_("Use stored filename and DPI hints when exporting (only with export-id)"), + NULL}, + + {"export-background", 'b', + POPT_ARG_STRING, &sp_export_background, SP_ARG_EXPORT_BACKGROUND, + N_("Background color of exported bitmap (any SVG-supported color string)"), + N_("COLOR")}, + + {"export-background-opacity", 'y', + POPT_ARG_STRING, &sp_export_background_opacity, SP_ARG_EXPORT_BACKGROUND_OPACITY, + N_("Background opacity of exported bitmap (either 0.0 to 1.0, or 1 to 255)"), + N_("VALUE")}, + + {"export-plain-svg", 'l', + POPT_ARG_STRING, &sp_export_svg, SP_ARG_EXPORT_SVG, + N_("Export document to plain SVG file (no sodipodi or inkscape namespaces)"), + N_("FILENAME")}, + + {"export-ps", 'P', + POPT_ARG_STRING, &sp_export_ps, SP_ARG_EXPORT_PS, + N_("Export document to a PS file"), + N_("FILENAME")}, + + {"export-eps", 'E', + POPT_ARG_STRING, &sp_export_eps, SP_ARG_EXPORT_EPS, + N_("Export document to an EPS file"), + N_("FILENAME")}, + + {"export-text-to-path", 'T', + POPT_ARG_NONE, &sp_export_text_to_path, SP_ARG_EXPORT_TEXT_TO_PATH, + N_("Convert text object to paths on export (EPS)"), + NULL}, + + {"export-bbox-page", 'B', + POPT_ARG_NONE, &sp_export_bbox_page, SP_ARG_EXPORT_BBOX_PAGE, + N_("Export files with the bounding box set to the page size (EPS)"), + NULL}, + + {"query-x", 'X', + POPT_ARG_NONE, &sp_query_x, SP_ARG_QUERY_X, + // TRANSLATORS: "--query-id" is an Inkscape command line option; see "inkscape --help" + N_("Query the X coordinate of the drawing or, if specified, of the object with --query-id"), + NULL}, + + {"query-y", 'Y', + POPT_ARG_NONE, &sp_query_y, SP_ARG_QUERY_Y, + // TRANSLATORS: "--query-id" is an Inkscape command line option; see "inkscape --help" + N_("Query the Y coordinate of the drawing or, if specified, of the object with --query-id"), + NULL}, + + {"query-width", 'W', + POPT_ARG_NONE, &sp_query_width, SP_ARG_QUERY_WIDTH, + // TRANSLATORS: "--query-id" is an Inkscape command line option; see "inkscape --help" + N_("Query the width of the drawing or, if specified, of the object with --query-id"), + NULL}, + + {"query-height", 'H', + POPT_ARG_NONE, &sp_query_height, SP_ARG_QUERY_HEIGHT, + // TRANSLATORS: "--query-id" is an Inkscape command line option; see "inkscape --help" + N_("Query the height of the drawing or, if specified, of the object with --query-id"), + NULL}, + + {"query-id", 'I', + POPT_ARG_STRING, &sp_query_id, SP_ARG_QUERY_ID, + N_("The ID of the object whose dimensions are queried"), + N_("ID")}, + + {"extension-directory", 'x', + POPT_ARG_NONE, NULL, SP_ARG_EXTENSIONDIR, + // TRANSLATORS: this option makes Inkscape print the name (path) of the extension directory + N_("Print out the extension directory and exit"), + NULL}, + + {"slideshow", 's', + POPT_ARG_NONE, &sp_global_slideshow, SP_ARG_SLIDESHOW, + N_("Show given files one-by-one, switch to next on any key/mouse event"), + NULL}, + + {"new-gui", 'G', + POPT_ARG_NONE, &sp_new_gui, SP_ARG_NEW_GUI, + N_("Use the new Gtkmm GUI interface"), + NULL}, + + {"vacuum-defs", 0, + POPT_ARG_NONE, &sp_vacuum_defs, SP_ARG_VACUUM_DEFS, + N_("Remove unused definitions from the defs section(s) of the document"), + NULL}, + + POPT_AUTOHELP POPT_TABLEEND +}; + +static bool needToRecodeParams = true; +gchar* blankParam = ""; + +int +main(int argc, char **argv) +{ +#ifdef HAVE_FPSETMASK + /* This is inherited from Sodipodi code, where it was in #ifdef __FreeBSD__. It's probably + safe to remove: the default mask is already 0 in C99, and in current FreeBSD according to + the fenv man page on www.freebsd.org, and in glibc according to (libc)FP Exceptions. */ + fpsetmask(fpgetmask() & ~(FP_X_DZ | FP_X_INV)); +#endif + +#ifdef ENABLE_NLS +#ifdef WIN32 + RegistryTool rt; + rt.setPathInfo(); + gchar *pathBuf = g_strconcat(g_path_get_dirname(argv[0]), "\\", PACKAGE_LOCALE_DIR, NULL); + bindtextdomain(GETTEXT_PACKAGE, pathBuf); + g_free(pathBuf); +#else +#ifdef ENABLE_BINRELOC + bindtextdomain(GETTEXT_PACKAGE, BR_LOCALEDIR("")); +#else + bindtextdomain(GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR); +#endif +#endif +#endif + + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); + +#ifdef ENABLE_NLS + textdomain(GETTEXT_PACKAGE); +#endif + + LIBXML_TEST_VERSION + + Inkscape::GC::init(); + + Inkscape::Debug::Logger::init(); + + gboolean use_gui; +#ifndef WIN32 + use_gui = (getenv("DISPLAY") != NULL); +#else + /* + Set the current directory to the directory of the + executable. This seems redundant, but is needed for + when inkscape.exe is executed from another directory. + We use relative paths on win32. + HKCR\svgfile\shell\open\command is a good example + */ + /// \todo FIXME BROKEN - non-UTF-8 sneaks in here. + char *homedir = g_path_get_dirname(argv[0]); + SetCurrentDirectory(homedir); + g_free(homedir); + + use_gui = TRUE; +#endif + /* Test whether with/without GUI is forced */ + for (int i = 1; i < argc; i++) { + if (!strcmp(argv[i], "-z") + || !strcmp(argv[i], "--without-gui") + || !strcmp(argv[i], "-p") + || !strncmp(argv[i], "--print", 7) + || !strcmp(argv[i], "-e") + || !strncmp(argv[i], "--export-png", 12) + || !strcmp(argv[i], "-l") + || !strncmp(argv[i], "--export-plain-svg", 12) + || !strcmp(argv[i], "-i") + || !strncmp(argv[i], "--export-area-drawing", 21) + || !strcmp(argv[i], "-D") + || !strncmp(argv[i], "--export-id", 12) + || !strcmp(argv[i], "-P") + || !strncmp(argv[i], "--export-ps", 11) + || !strcmp(argv[i], "-E") + || !strncmp(argv[i], "--export-eps", 12) + || !strcmp(argv[i], "-W") + || !strncmp(argv[i], "--query-width", 13) + || !strcmp(argv[i], "-H") + || !strncmp(argv[i], "--query-height", 14) + || !strcmp(argv[i], "-X") + || !strncmp(argv[i], "--query-x", 13) + || !strcmp(argv[i], "-Y") + || !strncmp(argv[i], "--query-y", 14) + || !strcmp(argv[i], "--vacuum-defs") + ) + { + /* main_console handles any exports -- not the gui */ + use_gui = FALSE; + break; + } else if (!strcmp(argv[i], "-g") || !strcmp(argv[i], "--with-gui")) { + use_gui = TRUE; + break; + } else if (!strcmp(argv[i], "-G") || !strcmp(argv[i], "--new-gui")) { + sp_new_gui = TRUE; + break; + } + } + +#ifdef WIN32 +#ifndef REPLACEARGS_ANSI + if ( PrintWin32::is_os_wide() ) +#endif // REPLACEARGS_ANSI + { + // If the call fails, we'll need to convert charsets + needToRecodeParams = !replaceArgs( argc, argv ); + } +#endif // WIN32 + + /// \todo Should this be a static object (see inkscape.cpp)? + Inkscape::NSApplication::Application app(argc, argv, use_gui, sp_new_gui); + + return app.run(); +} + +void fixupSingleFilename( gchar **orig, gchar **spare ) +{ + if ( orig && *orig && **orig ) { + GError *error = NULL; + gchar *newFileName = Inkscape::IO::locale_to_utf8_fallback(*orig, -1, NULL, NULL, &error); + if ( newFileName ) + { + *orig = newFileName; + if ( spare ) { + *spare = newFileName; + } +// g_message("Set a replacement fixup"); + } + } +} + +GSList *fixupFilenameEncoding( GSList* fl ) +{ + GSList *newFl = NULL; + while ( fl ) { + gchar *fn = static_cast(fl->data); + fl = g_slist_remove( fl, fl->data ); + gchar *newFileName = Inkscape::IO::locale_to_utf8_fallback(fn, -1, NULL, NULL, NULL); + if ( newFileName ) { + + if ( 0 ) + { + gchar *safeFn = Inkscape::IO::sanitizeString(fn); + gchar *safeNewFn = Inkscape::IO::sanitizeString(newFileName); + GtkWidget *w = gtk_message_dialog_new( NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, + "Note: Converted '%s' to '%s'", safeFn, safeNewFn ); + gtk_dialog_run (GTK_DIALOG (w)); + gtk_widget_destroy (w); + g_free(safeNewFn); + g_free(safeFn); + } + + g_free( fn ); + fn = newFileName; + newFileName = 0; + } + else + if ( 0 ) + { + gchar *safeFn = Inkscape::IO::sanitizeString(fn); + GtkWidget *w = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, "Error: Unable to convert '%s'", safeFn ); + gtk_dialog_run (GTK_DIALOG (w)); + gtk_widget_destroy (w); + g_free(safeFn); + } + newFl = g_slist_append( newFl, fn ); + } + return newFl; +} + +int sp_common_main( int argc, char const **argv, GSList **flDest ) +{ + /// \todo fixme: Move these to some centralized location (Lauris) + sp_object_type_register("sodipodi:namedview", SP_TYPE_NAMEDVIEW); + sp_object_type_register("sodipodi:guide", SP_TYPE_GUIDE); + + + // temporarily switch gettext encoding to locale, so that help messages can be output properly + gchar const *charset; + g_get_charset(&charset); + + bind_textdomain_codeset(GETTEXT_PACKAGE, charset); + + poptContext ctx = poptGetContext(NULL, argc, argv, options, 0); + poptSetOtherOptionHelp(ctx, _("[OPTIONS...] [FILE...]\n\nAvailable options:")); + g_return_val_if_fail(ctx != NULL, 1); + + /* Collect own arguments */ + GSList *fl = sp_process_args(ctx); + poptFreeContext(ctx); + + // now switch gettext back to UTF-8 (for GUI) + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); + + // Now let's see if the file list still holds up + if ( needToRecodeParams ) + { + fl = fixupFilenameEncoding( fl ); + } + + // Check the globals for filename-fixup + if ( needToRecodeParams ) + { + fixupSingleFilename( &sp_export_png, &sp_export_png_utf8 ); + fixupSingleFilename( &sp_export_svg, &sp_export_svg_utf8 ); + fixupSingleFilename( &sp_global_printer, &sp_global_printer_utf8 ); + } + else + { + if ( sp_export_png ) + sp_export_png_utf8 = g_strdup( sp_export_png ); + if ( sp_export_svg ) + sp_export_svg_utf8 = g_strdup( sp_export_svg ); + if ( sp_global_printer ) + sp_global_printer_utf8 = g_strdup( sp_global_printer ); + } + + // Return the list if wanted, else free it up. + if ( flDest ) { + *flDest = fl; + fl = 0; + } else { + while ( fl ) { + g_free( fl->data ); + fl = g_slist_remove( fl, fl->data ); + } + } + return 0; +} + +int +sp_main_gui(int argc, char const **argv) +{ + Gtk::Main main_instance (&argc, const_cast(&argv)); + + GSList *fl = NULL; + int retVal = sp_common_main( argc, argv, &fl ); + g_return_val_if_fail(retVal == 0, 1); + + inkscape_gtk_stock_init(); + + /* Set default icon */ + gchar *filename = (gchar *) g_build_filename (INKSCAPE_APPICONDIR, "inkscape.png", NULL); + if (Inkscape::IO::file_test(filename, (GFileTest)(G_FILE_TEST_IS_REGULAR | G_FILE_TEST_IS_SYMLINK))) { + gtk_window_set_default_icon_from_file(filename, NULL); + } + g_free (filename); + filename = 0; + + if (!sp_global_slideshow) { + gboolean create_new = TRUE; + + /// \todo FIXME BROKEN - non-UTF-8 sneaks in here. + inkscape_application_init(argv[0], true); + + while (fl) { + if (sp_file_open((gchar *)fl->data,NULL)) { + create_new=FALSE; + } + fl = g_slist_remove(fl, fl->data); + } + if (create_new) { + sp_file_new_default(); + } + } else { + if (fl) { + GtkWidget *ss; + /// \todo FIXME BROKEN - non-UTF-8 sneaks in here. + inkscape_application_init(argv[0], true); + ss = sp_slideshow_new(fl); + if (ss) gtk_widget_show(ss); + } else { + g_warning ("No slides to display"); + exit(0); + } + } + + main_instance.run(); + +#ifdef WIN32 + //We might not need anything here + //sp_win32_finish(); <-- this is a NOP func +#endif + + return 0; +} + +int +sp_main_console(int argc, char const **argv) +{ + /* We are started in text mode */ + + /* Do this g_type_init(), so that we can use Xft/Freetype2 (Pango) + * in a non-Gtk environment. Used in libnrtype's + * FontInstance.cpp and FontFactory.cpp. + * http://mail.gnome.org/archives/gtk-list/2003-December/msg00063.html + */ + g_type_init(); + char **argv2 = const_cast(argv); + gtk_init_check( &argc, &argv2 ); + //setlocale(LC_ALL, ""); + + GSList *fl = NULL; + int retVal = sp_common_main( argc, argv, &fl ); + g_return_val_if_fail(retVal == 0, 1); + + if (fl == NULL) { + g_print("Nothing to do!\n"); + exit(0); + } + + inkscape_application_init(argv[0], false); + + while (fl) { + SPDocument *doc; + + doc = Inkscape::Extension::open(NULL, (gchar *)fl->data); + if (doc == NULL) { + doc = Inkscape::Extension::open(Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG), (gchar *)fl->data); + } + if (doc == NULL) { + g_warning("Specified document %s cannot be opened (is it valid SVG file?)", (gchar *) fl->data); + } else { + if (sp_vacuum_defs) { + vacuum_document(doc); + } + if (sp_vacuum_defs && !sp_export_svg) { + // save under the name given in the command line + sp_repr_save_file(doc->rdoc, (gchar *)fl->data, SP_SVG_NS_URI); + } + if (sp_global_printer) { + sp_print_document_to_file(doc, sp_global_printer); + } + if (sp_export_png || sp_export_id || sp_export_area_drawing) { + sp_do_export_png(doc); + } + if (sp_export_svg) { + Inkscape::XML::Document *rdoc; + Inkscape::XML::Node *repr; + rdoc = sp_repr_document_new("svg:svg"); + repr = rdoc->root(); + repr = sp_document_root(doc)->updateRepr(repr, SP_OBJECT_WRITE_BUILD); + sp_repr_save_file(repr->document(), sp_export_svg, SP_SVG_NS_URI); + } + if (sp_export_ps) { + do_export_ps(doc, sp_export_ps, "image/x-postscript"); + } + if (sp_export_eps) { + do_export_ps(doc, sp_export_eps, "image/x-e-postscript"); + } + if (sp_query_width || sp_query_height) { + do_query_dimension (doc, true, sp_query_width? NR::X : NR::Y, sp_query_id); + } else if (sp_query_x || sp_query_y) { + do_query_dimension (doc, false, sp_query_x? NR::X : NR::Y, sp_query_id); + } + } + fl = g_slist_remove(fl, fl->data); + } + + inkscape_unref(); + + return 0; +} + +static void +do_query_dimension (SPDocument *doc, bool extent, NR::Dim2 const axis, const gchar *id) +{ + SPObject *o = NULL; + + if (id) { + o = doc->getObjectById(id); + if (o) { + if (!SP_IS_ITEM (o)) { + g_warning("Object with id=\"%s\" is not a visible item. Cannot query dimensions.", id); + return; + } + } else { + g_warning("Object with id=\"%s\" is not found. Cannot query dimensions.", id); + return; + } + } else { + o = SP_DOCUMENT_ROOT(doc); + } + + if (o) { + sp_document_ensure_up_to_date (doc); + NR::Rect area = sp_item_bbox_desktop((SPItem *) o); + + Inkscape::SVGOStringStream os; + if (extent) { + os << area.extent(axis); + } else { + os << area.min()[axis]; + } + g_print ("%s\n", os.str().c_str()); + } +} + + +static void +sp_do_export_png(SPDocument *doc) +{ + const gchar *filename = NULL; + gdouble dpi = 0.0; + + if (sp_export_use_hints && (!sp_export_id && !sp_export_area_drawing)) { + g_warning ("--export-use-hints can only be used with --export-id or --export-area-drawing; ignored."); + } + + GSList *items = NULL; + + NRRect area; + if (sp_export_id || sp_export_area_drawing) { + + SPObject *o = NULL; + if (sp_export_id) { + o = doc->getObjectById(sp_export_id); + } else if (sp_export_area_drawing) { + o = SP_DOCUMENT_ROOT (doc); + } + + if (o) { + if (!SP_IS_ITEM (o)) { + g_warning("Object with id=\"%s\" is not a visible item. Nothing exported.", sp_export_id); + return; + } + if (sp_export_area) { + g_warning ("Object with id=\"%s\" is being exported; --export-area is ignored.", sp_export_id); + } + + items = g_slist_prepend (items, SP_ITEM(o)); + + if (sp_export_id_only) { + g_print("Exporting only object with id=\"%s\"; all other objects hidden\n", sp_export_id); + } + + if (sp_export_use_hints) { + + // retrieve export filename hint + const gchar *fn_hint = SP_OBJECT_REPR(o)->attribute("inkscape:export-filename"); + if (fn_hint) { + if (sp_export_png) { + g_warning ("Using export filename from the command line (--export-png). Filename hint %s is ignored.", fn_hint); + filename = sp_export_png; + } else { + filename = fn_hint; + } + } else { + g_warning ("Export filename hint not found for the object."); + filename = sp_export_png; + } + + // retrieve export dpi hints + const gchar *dpi_hint = SP_OBJECT_REPR(o)->attribute("inkscape:export-xdpi"); // only xdpi, ydpi is always the same now + if (dpi_hint) { + if (sp_export_dpi || sp_export_width || sp_export_height) { + g_warning ("Using bitmap dimensions from the command line (--export-dpi, --export-width, or --export-height). DPI hint %s is ignored.", dpi_hint); + } else { + dpi = atof(dpi_hint); + } + } else { + g_warning ("Export DPI hint not found for the object."); + } + + } + + // write object bbox to area + sp_document_ensure_up_to_date (doc); + sp_item_invoke_bbox((SPItem *) o, &area, sp_item_i2r_affine((SPItem *) o), TRUE); + } else { + g_warning("Object with id=\"%s\" was not found in the document. Nothing exported.", sp_export_id); + return; + } + } else if (sp_export_area) { + /* Try to parse area (given in SVG pixels) */ + if (!sscanf(sp_export_area, "%lg:%lg:%lg:%lg", &area.x0, &area.y0, &area.x1, &area.y1) == 4) { + g_warning("Cannot parse export area '%s'; use 'x0:y0:x1:y1'. Nothing exported.", sp_export_area); + return; + } + if ((area.x0 >= area.x1) || (area.y0 >= area.y1)) { + g_warning("Export area '%s' has negative width or height. Nothing exported.", sp_export_area); + return; + } + } else { + /* Export the whole canvas */ + sp_document_ensure_up_to_date (doc); + area.x0 = SP_ROOT(doc->root)->x.computed; + area.y0 = SP_ROOT(doc->root)->y.computed; + area.x1 = area.x0 + sp_document_width (doc); + area.y1 = area.y0 + sp_document_height (doc); + } + + // set filename and dpi from options, if not yet set from the hints + if (!filename) { + if (!sp_export_png) { + g_warning ("No export filename given and no filename hint. Nothing exported."); + return; + } + filename = sp_export_png; + } + + if (sp_export_dpi && dpi == 0.0) { + dpi = atof(sp_export_dpi); + if ((dpi < 0.1) || (dpi > 10000.0)) { + g_warning("DPI value %s out of range [0.1 - 10000.0]. Nothing exported.", sp_export_dpi); + return; + } + g_print("DPI: %g\n", dpi); + } + + if (sp_export_area_snap) { + area.x0 = std::floor (area.x0); + area.y0 = std::floor (area.y0); + area.x1 = std::ceil (area.x1); + area.y1 = std::ceil (area.y1); + } + + // default dpi + if (dpi == 0.0) + dpi = PX_PER_IN; + + gint width = 0; + gint height = 0; + + if (sp_export_width) { + width = atoi(sp_export_width); + if ((width < 1) || (width > 65536)) { + g_warning("Export width %d out of range (1 - 65536). Nothing exported.", width); + return; + } + dpi = (gdouble) width * PX_PER_IN / (area.x1 - area.x0); + } + + if (sp_export_height) { + height = atoi(sp_export_height); + if ((height < 1) || (height > 65536)) { + g_warning("Export height %d out of range (1 - 65536). Nothing exported.", width); + return; + } + dpi = (gdouble) height * PX_PER_IN / (area.y1 - area.y0); + } + + if (!sp_export_width) { + width = (gint) ((area.x1 - area.x0) * dpi / PX_PER_IN + 0.5); + } + + if (!sp_export_height) { + height = (gint) ((area.y1 - area.y0) * dpi / PX_PER_IN + 0.5); + } + + guint32 bgcolor = 0x00000000; + if (sp_export_background) { + // override the page color + bgcolor = sp_svg_read_color(sp_export_background, 0xffffff00); + bgcolor |= 0xff; // default is no opacity + } else { + // read from namedview + Inkscape::XML::Node *nv = sp_repr_lookup_name (doc->rroot, "sodipodi:namedview"); + if (nv && nv->attribute("pagecolor")) + bgcolor = sp_svg_read_color(nv->attribute("pagecolor"), 0xffffff00); + if (nv && nv->attribute("inkscape:pageopacity")) + bgcolor |= SP_COLOR_F_TO_U(sp_repr_get_double_attribute (nv, "inkscape:pageopacity", 1.0)); + } + + if (sp_export_background_opacity) { + // override opacity + gfloat value; + if (sp_svg_number_read_f (sp_export_background_opacity, &value)) { + if (value > 1.0) { + value = CLAMP (value, 1.0f, 255.0f); + bgcolor &= (guint32) 0xffffff00; + bgcolor |= (guint32) floor(value); + } else { + value = CLAMP (value, 0.0f, 1.0f); + bgcolor &= (guint32) 0xffffff00; + bgcolor |= SP_COLOR_F_TO_U(value); + } + } + } + + g_print("Background RRGGBBAA: %08x\n", bgcolor); + + g_print("Area %g:%g:%g:%g exported to %d x %d pixels (%g dpi)\n", area.x0, area.y0, area.x1, area.y1, width, height, dpi); + + g_print("Bitmap saved as: %s\n", filename); + + if ((width >= 1) && (height >= 1) && (width < 65536) && (height < 65536)) { + sp_export_png_file(doc, filename, area.x0, area.y0, area.x1, area.y1, width, height, bgcolor, NULL, NULL, true, sp_export_id_only ? items : NULL); + } else { + g_warning("Calculated bitmap dimensions %d %d are out of range (1 - 65535). Nothing exported.", width, height); + } + + g_slist_free (items); +} + + +/** + * Perform an export of either PS or EPS. + * + * \param doc Document to export. + * \param uri URI to export to. + * \param mime MIME type to export as. + */ + +static void do_export_ps(SPDocument* doc, gchar const* uri, char const* mime) +{ + /** \todo + * FIXME: I've no idea if this is the `proper' way to do this. + * If anyone feels qualified to say that it is, perhaps they + * could remove this comment. + */ + + Inkscape::Extension::DB::OutputList o; + Inkscape::Extension::db.get_output_list(o); + Inkscape::Extension::DB::OutputList::const_iterator i = o.begin(); + while (i != o.end() && strcmp( (*i)->get_mimetype(), mime ) != 0) { + i++; + } + + if (i == o.end()) + { + g_warning ("Could not find an extension to export this file."); + return; + } + + bool old_text_to_path = false; + bool old_bbox_page = false; + + try { + old_text_to_path = (*i)->get_param_bool("textToPath"); + (*i)->set_param_bool("textToPath", sp_export_text_to_path); + } + catch (...) { + g_warning ("Could not set export-text-to-path option for this export."); + } + + try { + old_bbox_page = (*i)->get_param_bool("pageBoundingBox"); + (*i)->set_param_bool("pageBoundingBox", sp_export_bbox_page); + } + catch (...) { + g_warning ("Could not set export-bbox-page option for this export."); + } + + (*i)->save(doc, uri); + + try { + (*i)->set_param_bool("textToPath", old_text_to_path); + (*i)->set_param_bool("pageBoundingBox", old_bbox_page); + } + catch (...) { + + } +} + +#ifdef WIN32 +bool replaceArgs( int& argc, char**& argv ) +{ + bool worked = false; + +#ifdef REPLACEARGS_DEBUG + MessageBoxA( NULL, "GetCommandLineW() getting called", "GetCommandLineW", MB_OK | MB_ICONINFORMATION ); +#endif // REPLACEARGS_DEBUG + + wchar_t* line = GetCommandLineW(); + if ( line ) + { +#ifdef REPLACEARGS_DEBUG + { + gchar* utf8Line = g_utf16_to_utf8( (gunichar2*)line, -1, NULL, NULL, NULL ); + if ( utf8Line ) + { + gchar *safe = Inkscape::IO::sanitizeString(utf8Line); + { + char tmp[strlen(safe) + 32]; + snprintf( tmp, sizeof(tmp), "GetCommandLineW() = '%s'", safe ); + MessageBoxA( NULL, tmp, "GetCommandLineW", MB_OK | MB_ICONINFORMATION ); + } + } + } +#endif // REPLACEARGS_DEBUG + + int numArgs = 0; + wchar_t** parsed = CommandLineToArgvW( line, &numArgs ); + +#ifdef REPLACEARGS_ANSI +// test code for trying things on Win95/98/ME + if ( !parsed ) + { +#ifdef REPLACEARGS_DEBUG + MessageBoxA( NULL, "Unable to process command-line. Faking it", "CommandLineToArgvW", MB_OK | MB_ICONINFORMATION ); +#endif // REPLACEARGS_DEBUG + int lineLen = wcslen(line) + 1; + wchar_t* lineDup = new wchar_t[lineLen]; + wcsncpy( lineDup, line, lineLen ); + + int pos = 0; + bool inQuotes = false; + bool inWhitespace = true; + std::vector places; + while ( lineDup[pos] ) + { + if ( inQuotes ) + { + if ( lineDup[pos] == L'"' ) + { + inQuotes = false; + } + } + else if ( lineDup[pos] == L'"' ) + { + inQuotes = true; + inWhitespace = false; + places.push_back(pos); + } + else if ( lineDup[pos] == L' ' || lineDup[pos] == L'\t' ) + { + if ( !inWhitespace ) + { + inWhitespace = true; + lineDup[pos] = 0; + } + } + else if ( inWhitespace && (lineDup[pos] != L' ' && lineDup[pos] != L'\t') ) + { + inWhitespace = false; + places.push_back(pos); + } + else + { + // consume + } + pos++; + } +#ifdef REPLACEARGS_DEBUG + { + char tmp[256]; + snprintf( tmp, sizeof(tmp), "Counted %d args", places.size() ); + MessageBoxA( NULL, tmp, "CommandLineToArgvW", MB_OK | MB_ICONINFORMATION ); + } +#endif // REPLACEARGS_DEBUG + + wchar_t** block = new wchar_t*[places.size()]; + int i = 0; + for ( std::vector::iterator it = places.begin(); it != places.end(); it++ ) + { + block[i++] = &lineDup[*it]; + } + parsed = block; + numArgs = places.size(); + } +#endif // REPLACEARGS_ANSI + + if ( parsed ) + { + std::vectorexpandedArgs; + if ( numArgs > 0 ) + { + expandedArgs.push_back( parsed[0] ); + } + + for ( int i1 = 1; i1 < numArgs; i1++ ) + { + bool wildcarded = (wcschr(parsed[i1], L'?') != NULL) || (wcschr(parsed[i1], L'*') != NULL); + wildcarded &= parsed[i1][0] != L'"'; + wildcarded &= parsed[i1][0] != L'-'; + if ( wildcarded ) + { +#ifdef REPLACEARGS_ANSI + WIN32_FIND_DATAA data = {0}; +#else + WIN32_FIND_DATAW data = {0}; +#endif // REPLACEARGS_ANSI + + int baseLen = wcslen(parsed[i1]) + 2; + wchar_t* base = new wchar_t[baseLen]; + wcsncpy( base, parsed[i1], baseLen ); + wchar_t* last = wcsrchr( base, L'\\' ); + if ( last ) + { + last[1] = 0; + } + else + { + base[0] = 0; + } + baseLen = wcslen( base ); + +#ifdef REPLACEARGS_ANSI + char target[MAX_PATH]; + if ( WideCharToMultiByte( CP_ACP, 0, parsed[i1], -1, target, sizeof(target), NULL, NULL) ) + { + HANDLE hf = FindFirstFileA( target, &data ); +#else + HANDLE hf = FindFirstFileW( parsed[i1], &data ); +#endif // REPLACEARGS_ANSI + if ( hf != INVALID_HANDLE_VALUE ) + { + BOOL found = TRUE; + do + { +#ifdef REPLACEARGS_ANSI + int howMany = MultiByteToWideChar( CP_ACP, 0, data.cFileName, -1, NULL, 0 ); + if ( howMany > 0 ) + { + howMany += baseLen; + wchar_t* tmp = new wchar_t[howMany + 1]; + wcsncpy( tmp, base, howMany + 1 ); + MultiByteToWideChar( CP_ACP, 0, data.cFileName, -1, tmp + baseLen, howMany + 1 - baseLen ); + expandedArgs.push_back( tmp ); + found = FindNextFileA( hf, &data ); + } +#else + int howMany = wcslen(data.cFileName) + baseLen; + wchar_t* tmp = new wchar_t[howMany + 1]; + wcsncpy( tmp, base, howMany + 1 ); + wcsncat( tmp, data.cFileName, howMany + 1 ); + expandedArgs.push_back( tmp ); + found = FindNextFileW( hf, &data ); +#endif // REPLACEARGS_ANSI + } while ( found ); + + FindClose( hf ); + } + else + { + expandedArgs.push_back( parsed[i1] ); + } +#ifdef REPLACEARGS_ANSI + } +#endif // REPLACEARGS_ANSI + + delete[] base; + } + else + { + expandedArgs.push_back( parsed[i1] ); + } + } + + { + wchar_t** block = new wchar_t*[expandedArgs.size()]; + int iz = 0; + for ( std::vector::iterator it = expandedArgs.begin(); it != expandedArgs.end(); it++ ) + { + block[iz++] = *it; + } + parsed = block; + numArgs = expandedArgs.size(); + } + + std::vector newArgs; + for ( int i = 0; i < numArgs; i++ ) + { + gchar* replacement = g_utf16_to_utf8( (gunichar2*)parsed[i], -1, NULL, NULL, NULL ); + if ( replacement ) + { +#ifdef REPLACEARGS_DEBUG + gchar *safe2 = Inkscape::IO::sanitizeString(replacement); + + if ( safe2 ) + { + { + char tmp[1024]; + snprintf( tmp, sizeof(tmp), " [%2d] = '%s'", i, safe2 ); + MessageBoxA( NULL, tmp, "GetCommandLineW", MB_OK | MB_ICONINFORMATION ); + } + g_free( safe2 ); + } +#endif // REPLACEARGS_DEBUG + + newArgs.push_back( replacement ); + } + else + { + newArgs.push_back( blankParam ); + } + } + + // Now push our munged params to be the new argv and argc + { + char** block = new char*[newArgs.size()]; + int iz = 0; + for ( std::vector::iterator it = newArgs.begin(); it != newArgs.end(); it++ ) + { + block[iz++] = *it; + } + argv = block; + argc = newArgs.size(); + worked = true; + } + } +#ifdef REPLACEARGS_DEBUG + else + { + MessageBoxA( NULL, "Unable to process command-line", "CommandLineToArgvW", MB_OK | MB_ICONINFORMATION ); + } +#endif // REPLACEARGS_DEBUG + } +#ifdef REPLACEARGS_DEBUG + else + { + { + MessageBoxA( NULL, "Unable to fetch result from GetCommandLineW()", "GetCommandLineW", MB_OK | MB_ICONINFORMATION ); + } + + char* line2 = GetCommandLineA(); + if ( line2 ) + { + gchar *safe = Inkscape::IO::sanitizeString(line2); + { + { + char tmp[strlen(safe) + 32]; + snprintf( tmp, sizeof(tmp), "GetCommandLineA() = '%s'", safe ); + MessageBoxA( NULL, tmp, "GetCommandLineA", MB_OK | MB_ICONINFORMATION ); + } + } + } + else + { + MessageBoxA( NULL, "Unable to fetch result from GetCommandLineA()", "GetCommandLineA", MB_OK | MB_ICONINFORMATION ); + } + } +#endif // REPLACEARGS_DEBUG + + return worked; +} +#endif // WIN32 + +static GSList * +sp_process_args(poptContext ctx) +{ + GSList *fl = NULL; + + gint a; + while ((a = poptGetNextOpt(ctx)) >= 0) { + switch (a) { + case SP_ARG_FILE: { + gchar const *fn = poptGetOptArg(ctx); + if (fn != NULL) { + fl = g_slist_append(fl, g_strdup(fn)); + } + break; + } + case SP_ARG_VERSION: { + printf("Inkscape %s (%s)\n", INKSCAPE_VERSION, __DATE__); + exit(0); + break; + } + case SP_ARG_EXTENSIONDIR: { + printf("%s\n", INKSCAPE_EXTENSIONDIR); + exit(0); + break; + } + default: { + break; + } + } + } + + gchar const ** const args = poptGetArgs(ctx); + if (args != NULL) { + for (unsigned i = 0; args[i] != NULL; i++) { + fl = g_slist_append(fl, g_strdup(args[i])); + } + } + + return fl; +} + + +/* + 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/make.dep b/src/make.dep new file mode 100644 index 000000000..f9cea8860 --- /dev/null +++ b/src/make.dep @@ -0,0 +1,19683 @@ +######################################################## +## File: make.dep +## Purpose: Dependency listing for use by Makefiles +## Generated by mkdep.pl at :Sun Jan 15 07:06:29 2006 +## Do not edit this file! Changes will be lost. +######################################################## + +application/app-prototype.o: \ + application/app-prototype.cpp \ + application/app-prototype.h + +application/application.o: \ + application/application.cpp \ + application/app-prototype.h \ + application/application.h \ + application/editor.h \ + preferences.h + +application/editor.o: \ + application/editor.cpp \ + application/app-prototype.h \ + application/application.h \ + application/editor.h \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + display/display-forward.h \ + document.h \ + enums.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/action.h \ + helper/helper-forward.h \ + io/sys.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + line-snapper.h \ + message.h \ + object-snapper.h \ + path-prefix.h \ + preferences.h \ + prefix.h \ + require-config.h \ + round.h \ + selection.h \ + snapped-point.h \ + snapper.h \ + sp-guide-attachment.h \ + sp-guide.h \ + sp-metric.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object-repr.h \ + sp-object.h \ + traits/reference.h \ + ui/dialog/dialog-manager.h \ + ui/dialog/dialog.h \ + ui/view/edit-widget-interface.h \ + ui/view/edit-widget.h \ + ui/view/view.h \ + ui/widget/button.h \ + ui/widget/handlebox.h \ + ui/widget/ruler.h \ + ui/widget/selected-style.h \ + ui/widget/svg-canvas.h \ + ui/widget/toolbox.h \ + ui/widget/zoom-status.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + widgets/layer-selector.h + +arc-context.o: \ + arc-context.cpp \ + arc-context.h \ + context-fns.h \ + decimal-round.h \ + desktop-handles.h \ + desktop-style.h \ + desktop.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/sp-canvas.h \ + document.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + knot-enums.h \ + knotholder.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + line-snapper.h \ + macros.h \ + message-context.h \ + message.h \ + object-edit.h \ + object-snapper.h \ + prefs-utils.h \ + round.h \ + selection.h \ + snap.h \ + snapped-point.h \ + snapper.h \ + sp-ellipse.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-metric.h \ + sp-metrics.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + sp-shape.h \ + svg/svg-length.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node-event-vector.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +attributes-test.o: \ + attributes-test.cpp \ + attributes.h \ + streq.h + +attributes.o: \ + attributes.cpp \ + attributes.h + +color.o: \ + color.cpp \ + color.h + +composite-undo-stack-observer.o: \ + composite-undo-stack-observer.cpp \ + composite-undo-stack-observer.h \ + debug/event.h \ + undo-stack-observer.h \ + util/shared-c-string-ptr.h + +conn-avoid-ref.o: \ + conn-avoid-ref.cpp \ + conn-avoid-ref.h \ + debug/event-tracker.h \ + debug/event.h \ + debug/logger.h \ + decimal-round.h \ + display/nr-arena-forward.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/incremental.h \ + libavoid/polyutil.h \ + libavoid/shape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-ops.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-item.h \ + sp-object.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list-container.h \ + util/list.h \ + util/shared-c-string-ptr.h \ + version.h \ + xml/attribute-record.h \ + xml/composite-node-observer.h \ + xml/node-event-vector.h \ + xml/node-fns.h \ + xml/node-observer.h \ + xml/node.h \ + xml/repr.h \ + xml/simple-node.cpp \ + xml/simple-node.h \ + xml/sp-css-attr.h \ + xml/transaction-logger.h + +connector-context.o: \ + connector-context.cpp \ + bad-uri-exception.h \ + conn-avoid-ref.h \ + connector-context.h \ + context-fns.h \ + decimal-round.h \ + desktop-affine.h \ + desktop-handles.h \ + desktop-style.h \ + desktop.h \ + display/canvas-bpath.h \ + display/curve.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/sodipodi-ctrl.h \ + display/sp-canvas.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape.h \ + knot-enums.h \ + knot.h \ + libavoid/connector.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libavoid/vertices.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + message-context.h \ + message-stack.h \ + message.h \ + prefs-utils.h \ + round.h \ + selection.h \ + snap.h \ + snapped-point.h \ + snapper.h \ + sp-conn-end-pair.h \ + sp-conn-end.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-path.h \ + sp-shape.h \ + sp-use-reference.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + uri-references.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node-event-vector.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +context-fns.o: \ + context-fns.cpp \ + context-fns.h \ + decimal-round.h \ + desktop-affine.h \ + desktop.h \ + display/nr-arena-forward.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + message-context.h \ + message-stack.h \ + message.h \ + round.h \ + snap.h \ + snapped-point.h \ + snapper.h \ + sp-item.h \ + sp-object.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + version.h + +debug/heap.o: \ + debug/heap.cpp \ + debug/gc-heap.h \ + debug/heap.h \ + debug/sysv-heap.h \ + gc-alloc.h \ + gc-core.h \ + util/shared-c-string-ptr.h + +debug/logger.o: \ + debug/logger.cpp \ + debug/event.h \ + debug/logger.h \ + debug/simple-event.h \ + gc-alloc.h \ + gc-core.h \ + util/shared-c-string-ptr.h + +debug/sysv-heap.o: \ + debug/sysv-heap.cpp \ + debug/heap.h \ + debug/sysv-heap.h \ + util/shared-c-string-ptr.h + +desktop-affine.o: \ + desktop-affine.cpp \ + decimal-round.h \ + desktop.h \ + display/nr-arena-forward.h \ + document.h \ + enums.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + message.h \ + round.h \ + sp-item-group.h \ + sp-item.h \ + sp-object.h \ + sp-root.h \ + svg/svg-length.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + version.h + +desktop-events.o: \ + desktop-events.cpp \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + dialogs/dialog-events.h \ + display/display-forward.h \ + display/guideline.h \ + display/sp-canvas.h \ + document.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + helper/unit-menu.h \ + helper/units.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + line-snapper.h \ + message-context.h \ + message.h \ + object-snapper.h \ + round.h \ + snapped-point.h \ + snapper.h \ + sp-guide-attachment.h \ + sp-guide.h \ + sp-metric.h \ + sp-metrics.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view-widget.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + widgets/desktop-widget.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +desktop-handles.o: \ + desktop-handles.cpp \ + decimal-round.h \ + desktop.h \ + display/sp-canvas.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + message.h \ + round.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h + +desktop-style.o: \ + desktop-style.cpp \ + bad-uri-exception.h \ + color-rgba.h \ + color.h \ + decimal-round.h \ + desktop-style.h \ + desktop.h \ + display/nr-arena-forward.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock.h \ + libnr/nr-pixops.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/Layout-TNG.h \ + libnrtype/font-style-to-pos.h \ + libnrtype/nr-type-pos-def.h \ + message.h \ + prefs-utils.h \ + round.h \ + selection.h \ + sp-flowdiv.h \ + sp-flowregion.h \ + sp-flowtext.h \ + sp-gradient-fns.h \ + sp-gradient-spread.h \ + sp-gradient-units.h \ + sp-gradient-vector.h \ + sp-gradient.h \ + sp-item.h \ + sp-linear-gradient-fns.h \ + sp-linear-gradient.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-paint-server.h \ + sp-pattern.h \ + sp-radial-gradient-fns.h \ + sp-radial-gradient.h \ + sp-string.h \ + sp-text.h \ + sp-textpath.h \ + sp-tspan.h \ + sp-use.h \ + style.h \ + svg/css-ostringstream.h \ + svg/svg-length.h \ + text-tag-attributes.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + uri-references.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +desktop.o: \ + desktop.cpp \ + color.h \ + decimal-round.h \ + desktop-events.h \ + desktop-handles.h \ + desktop.h \ + display/canvas-arena.h \ + display/display-forward.h \ + display/gnome-canvas-acetate.h \ + display/nr-arena-forward.h \ + display/nr-arena-item.h \ + display/nr-arena.h \ + display/sodipodi-ctrlrect.h \ + display/sp-canvas-util.h \ + display/sp-canvas.h \ + document.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + helper/units.h \ + inkscape-private.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-div.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect-ops.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + line-snapper.h \ + macros.h \ + message-context.h \ + message-stack.h \ + message.h \ + object-hierarchy.h \ + object-snapper.h \ + prefs-utils.h \ + round.h \ + select-context.h \ + selection.h \ + snapped-point.h \ + snapper.h \ + sp-item-group.h \ + sp-item.h \ + sp-metric.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + sp-paint-server.h \ + traits/reference.h \ + ui/dialog/dialog-manager.h \ + ui/dialog/dialog.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +dialogs/clonetiler.o: \ + dialogs/clonetiler.cpp \ + application/app-prototype.h \ + application/application.h \ + application/editor.h \ + color.h \ + decimal-round.h \ + desktop-handles.h \ + dialogs/dialog-events.h \ + dialogs/unclump.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/nr-arena-item.h \ + display/nr-arena.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + helper/unit-menu.h \ + helper/units.h \ + helper/window.h \ + inkscape.h \ + interface.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix-translate-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate-ops.h \ + libnr/nr-translate-rotate-ops.h \ + libnr/nr-translate-scale-ops.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + line-snapper.h \ + macros.h \ + message-stack.h \ + message.h \ + object-snapper.h \ + prefs-utils.h \ + require-config.h \ + round.h \ + selection.h \ + snapped-point.h \ + snapper.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-metric.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + sp-paint-server.h \ + sp-use.h \ + style.h \ + svg/svg-length.h \ + traits/reference.h \ + ui/widget/button.h \ + ui/widget/color-picker.h \ + ui/widget/color-preview.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + verbs.h \ + version.h \ + widgets/icon.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +dialogs/debugdialog.o: \ + dialogs/debugdialog.cpp \ + dialogs/debugdialog.h \ + ui/dialog/dialog.h \ + ui/widget/button.h + +dialogs/dialog-events.o: \ + dialogs/dialog-events.cpp \ + decimal-round.h \ + desktop.h \ + dialogs/dialog-events.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape-private.h \ + inkscape.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + macros.h \ + message.h \ + prefs-utils.h \ + round.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h + +dialogs/display-settings.o: \ + dialogs/display-settings.cpp \ + color.h \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + dialogs/dialog-events.h \ + display/display-forward.h \ + document.h \ + enums.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/helper-forward.h \ + helper/window.h \ + inkscape.h \ + interface.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + macros.h \ + message-stack.h \ + message.h \ + prefs-utils.h \ + require-config.h \ + round.h \ + selcue.h \ + selection-chemistry.h \ + selection.h \ + sp-marker-loc.h \ + style.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + ui/widget/button.h \ + ui/widget/style-swatch.h \ + unit-constants.h \ + util/list.h \ + verbs.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +dialogs/eek-preview.o: \ + dialogs/eek-preview.cpp \ + dialogs/eek-preview.h + +dialogs/export.o: \ + dialogs/export.cpp \ + decimal-round.h \ + desktop-handles.h \ + dialogs/dialog-events.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + extension/db.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/output.h \ + file.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + helper/unit-menu.h \ + helper/units.h \ + helper/window.h \ + inkscape-private.h \ + inkscape.h \ + interface.h \ + io/sys.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + line-snapper.h \ + macros.h \ + object-snapper.h \ + prefs-utils.h \ + require-config.h \ + round.h \ + selection.h \ + snapped-point.h \ + snapper.h \ + sp-item.h \ + sp-metric.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + traits/reference.h \ + unit-constants.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + verbs.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +dialogs/extensions.o: \ + dialogs/extensions.cpp \ + dialogs/extensions.h \ + document.h \ + extension/db.h \ + extension/extension-forward.h \ + extension/extension.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + traits/reference.h \ + ui/previewable.h \ + ui/previewfillable.h \ + ui/widget/button.h \ + ui/widget/panel.h \ + util/list.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +dialogs/filedialog.o: \ + dialogs/filedialog.cpp \ + dialogs/dialog-events.h \ + dialogs/filedialog.h \ + dialogs/input.h \ + display/display-forward.h \ + document.h \ + extension/db.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/output.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + prefs-utils.h \ + svg-view-widget.h \ + traits/reference.h \ + ui/stock.h \ + ui/view/view-widget.h \ + util/list.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +dialogs/fill-style.o: \ + dialogs/fill-style.cpp \ + bad-uri-exception.h \ + color.h \ + composite-undo-stack-observer.h \ + decimal-round.h \ + desktop-handles.h \ + desktop-style.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document-private.h \ + document.h \ + enums.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + gradient-chemistry.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + selection.h \ + sp-defs.h \ + sp-gradient-fns.h \ + sp-gradient-spread.h \ + sp-gradient-units.h \ + sp-gradient-vector.h \ + sp-gradient.h \ + sp-item-group.h \ + sp-item.h \ + sp-linear-gradient-fns.h \ + sp-linear-gradient.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-paint-server.h \ + sp-pattern.h \ + sp-radial-gradient-fns.h \ + sp-radial-gradient.h \ + sp-root.h \ + style.h \ + svg/svg-length.h \ + traits/reference.h \ + undo-stack-observer.h \ + uri-references.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + widgets/paint-selector.h \ + widgets/sp-widget.h \ + xml/event-fns.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +dialogs/find.o: \ + dialogs/find.cpp \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + dialogs/dialog-events.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/helper-forward.h \ + helper/window.h \ + inkscape.h \ + interface.h \ + libavoid/connector.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/Layout-TNG.h \ + macros.h \ + message-stack.h \ + message.h \ + prefs-utils.h \ + require-config.h \ + round.h \ + selection-chemistry.h \ + selection.h \ + sp-conn-end-pair.h \ + sp-defs.h \ + sp-ellipse.h \ + sp-flowtext.h \ + sp-image.h \ + sp-item-group.h \ + sp-item.h \ + sp-line.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-offset.h \ + sp-path.h \ + sp-polygon.h \ + sp-polyline.h \ + sp-rect.h \ + sp-shape.h \ + sp-spiral.h \ + sp-star.h \ + sp-string.h \ + sp-text.h \ + sp-tspan.h \ + sp-use.h \ + svg/svg-length.h \ + text-editing.h \ + text-tag-attributes.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + verbs.h \ + version.h \ + widgets/icon.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +dialogs/iconpreview.o: \ + dialogs/iconpreview.cpp \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + dialogs/iconpreview.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/nr-arena.h \ + document.h \ + enums.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + message.h \ + prefs-utils.h \ + round.h \ + selection.h \ + sp-item-group.h \ + sp-item.h \ + sp-object.h \ + sp-paint-server.h \ + sp-root.h \ + svg/svg-length.h \ + traits/reference.h \ + ui/previewable.h \ + ui/previewfillable.h \ + ui/stock.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + ui/widget/button.h \ + ui/widget/panel.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +dialogs/in-dt-coordsys.o: \ + dialogs/in-dt-coordsys.cpp \ + decimal-round.h \ + display/nr-arena-forward.h \ + enums.h \ + forward.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-item-group.h \ + sp-item.h \ + sp-object.h \ + sp-root.h \ + svg/svg-length.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + version.h + +dialogs/input.o: \ + dialogs/input.cpp \ + dialogs/dialog-events.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/helper-forward.h \ + inkscape.h \ + interface.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + macros.h \ + prefs-utils.h \ + require-config.h \ + traits/reference.h \ + util/list.h \ + verbs.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +dialogs/item-properties.o: \ + dialogs/item-properties.cpp \ + decimal-round.h \ + desktop-handles.h \ + dialogs/dialog-events.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/helper-forward.h \ + helper/window.h \ + inkscape.h \ + interface.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + macros.h \ + prefs-utils.h \ + require-config.h \ + round.h \ + selection.h \ + sp-item.h \ + sp-object.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + verbs.h \ + version.h \ + widgets/sp-widget.h + +dialogs/layer-properties.o: \ + dialogs/layer-properties.cpp \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + dialogs/layer-properties.h \ + display/display-forward.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape.h \ + layer-fns.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + message-stack.h \ + message.h \ + round.h \ + selection.h \ + sp-object.h \ + traits/reference.h \ + ui/dialog/dialog.h \ + ui/stock.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h + +dialogs/object-attributes.o: \ + dialogs/object-attributes.cpp \ + decimal-round.h \ + dialogs/sp-attribute-widget.h \ + display/nr-arena-forward.h \ + forward.h \ + helper/window.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + macros.h \ + round.h \ + sp-anchor.h \ + sp-item-group.h \ + sp-item.h \ + sp-object.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + version.h + +dialogs/object-properties.o: \ + dialogs/object-properties.cpp \ + color.h \ + decimal-round.h \ + desktop-handles.h \ + desktop-style.h \ + dialogs/dialog-events.h \ + dialogs/fill-style.h \ + dialogs/stroke-style.h \ + display/canvas-bpath.h \ + display/display-forward.h \ + display/sp-canvas.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/helper-forward.h \ + helper/window.h \ + inkscape-stock.h \ + inkscape.h \ + interface.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + macros.h \ + prefs-utils.h \ + require-config.h \ + round.h \ + sp-marker-loc.h \ + style.h \ + svg/css-ostringstream.h \ + traits/reference.h \ + util/list.h \ + verbs.h \ + widgets/icon.h \ + widgets/sp-widget.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +dialogs/rdf.o: \ + dialogs/rdf.cpp \ + decimal-round.h \ + dialogs/rdf.h \ + display/nr-arena-forward.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-item-group.h \ + sp-item.h \ + sp-object.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +dialogs/sp-attribute-widget.o: \ + dialogs/sp-attribute-widget.cpp \ + dialogs/sp-attribute-widget.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + macros.h \ + sp-object.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +dialogs/stroke-style.o: \ + dialogs/stroke-style.cpp \ + bad-uri-exception.h \ + color.h \ + composite-undo-stack-observer.h \ + decimal-round.h \ + desktop-handles.h \ + desktop-style.h \ + dialogs/dialog-events.h \ + dialogs/stroke-style.h \ + display/canvas-bpath.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/nr-arena-item.h \ + display/nr-arena.h \ + display/sp-canvas.h \ + document-private.h \ + document.h \ + enums.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + gradient-chemistry.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + helper/stock-items.h \ + helper/unit-menu.h \ + helper/units.h \ + inkscape-stock.h \ + inkscape.h \ + io/sys.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/Layout-TNG.h \ + line-snapper.h \ + object-snapper.h \ + path-prefix.h \ + prefix.h \ + require-config.h \ + round.h \ + selection.h \ + snapped-point.h \ + snapper.h \ + sp-defs.h \ + sp-gradient-fns.h \ + sp-gradient-spread.h \ + sp-gradient-units.h \ + sp-gradient-vector.h \ + sp-gradient.h \ + sp-item-group.h \ + sp-item.h \ + sp-linear-gradient-fns.h \ + sp-linear-gradient.h \ + sp-marker-loc.h \ + sp-marker.h \ + sp-metric.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + sp-paint-server.h \ + sp-pattern.h \ + sp-radial-gradient-fns.h \ + sp-radial-gradient.h \ + sp-rect.h \ + sp-root.h \ + sp-shape.h \ + sp-string.h \ + sp-text.h \ + style.h \ + svg/css-ostringstream.h \ + svg/svg-length.h \ + text-tag-attributes.h \ + traits/reference.h \ + undo-stack-observer.h \ + uri-references.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + widgets/dash-selector.h \ + widgets/icon.h \ + widgets/paint-selector.h \ + widgets/sp-widget.h \ + widgets/spw-utilities.h \ + xml/event-fns.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +dialogs/swatches.o: \ + dialogs/swatches.cpp \ + desktop-handles.h \ + desktop-style.h \ + dialogs/eek-preview.h \ + dialogs/swatches.h \ + display/display-forward.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + extension/db.h \ + extension/extension-forward.h \ + extension/extension.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape.h \ + io/sys.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + path-prefix.h \ + prefix.h \ + require-config.h \ + traits/reference.h \ + ui/previewable.h \ + ui/previewfillable.h \ + ui/previewholder.h \ + ui/widget/button.h \ + ui/widget/panel.h \ + util/list.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +dialogs/text-edit.o: \ + dialogs/text-edit.cpp \ + color.h \ + decimal-round.h \ + desktop-handles.h \ + desktop-style.h \ + dialogs/dialog-events.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/helper-forward.h \ + helper/window.h \ + inkscape-stock.h \ + inkscape.h \ + interface.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/FontFactory.h \ + libnrtype/Layout-TNG.h \ + libnrtype/font-instance.h \ + libnrtype/font-style-to-pos.h \ + libnrtype/font-style.h \ + libnrtype/nr-type-pos-def.h \ + libnrtype/nr-type-primitives.h \ + libnrtype/nrtype-forward.h \ + livarot/LivarotDefs.h \ + livarot/livarot-forward.h \ + macros.h \ + prefs-utils.h \ + require-config.h \ + round.h \ + selection.h \ + sp-flowtext.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-string.h \ + sp-text.h \ + style.h \ + svg/css-ostringstream.h \ + svg/svg-length.h \ + text-editing.h \ + text-tag-attributes.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + verbs.h \ + version.h \ + widgets/font-selector.h \ + widgets/icon.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +dialogs/tiledialog.o: \ + dialogs/tiledialog.cpp \ + decimal-round.h \ + desktop-handles.h \ + dialogs/tiledialog.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/helper-forward.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + prefs-utils.h \ + require-config.h \ + round.h \ + selection.h \ + sp-item.h \ + sp-object.h \ + traits/reference.h \ + ui/dialog/dialog.h \ + ui/stock.h \ + ui/widget/button.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + verbs.h \ + version.h \ + widgets/icon.h + +dialogs/unclump.o: \ + dialogs/unclump.cpp \ + decimal-round.h \ + display/nr-arena-forward.h \ + forward.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-item.h \ + sp-object.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + version.h + +dialogs/xml-tree.o: \ + dialogs/xml-tree.cpp \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + dialogs/dialog-events.h \ + dialogs/in-dt-coordsys.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + enums.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/helper-forward.h \ + helper/window.h \ + inkscape-stock.h \ + inkscape.h \ + interface.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/Layout-TNG.h \ + macros.h \ + message-context.h \ + message-stack.h \ + message.h \ + prefs-utils.h \ + require-config.h \ + round.h \ + selection.h \ + shortcuts.h \ + sp-item-group.h \ + sp-item.h \ + sp-object.h \ + sp-root.h \ + sp-string.h \ + sp-tspan.h \ + svg/svg-length.h \ + text-tag-attributes.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + verbs.h \ + version.h \ + widgets/icon.h \ + widgets/sp-xmlview-attr-list.h \ + widgets/sp-xmlview-content.h \ + widgets/sp-xmlview-tree.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +dir-util-test.o: \ + dir-util-test.cpp \ + dir-util.h \ + streq.h + +dir-util.o: \ + dir-util.cpp + +display/bezier-utils.o: \ + display/bezier-utils.cpp \ + decimal-round.h \ + display/bezier-utils.h \ + isnan.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + round.h + +display/canvas-arena.o: \ + display/canvas-arena.cpp \ + decimal-round.h \ + display/canvas-arena.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/nr-arena-group.h \ + display/nr-arena-item.h \ + display/nr-arena.h \ + display/sp-canvas-util.h \ + display/sp-canvas.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/sp-marshal.h \ + libnr/nr-blit.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-object.h \ + sp-paint-server.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + version.h + +display/canvas-bpath.o: \ + display/canvas-bpath.cpp \ + decimal-round.h \ + display/canvas-bpath.h \ + display/curve.h \ + display/display-forward.h \ + display/sp-canvas-util.h \ + display/sp-canvas.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixops.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + livarot/BitLigne.h \ + livarot/LivarotDefs.h \ + livarot/Path.h \ + livarot/int-line.h \ + livarot/livarot-forward.h \ + round.h + +display/canvas-grid.o: \ + display/canvas-grid.cpp \ + decimal-round.h \ + display/canvas-grid.h \ + display/display-forward.h \ + display/sp-canvas-util.h \ + display/sp-canvas.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixops.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +display/curve.o: \ + display/curve.cpp \ + decimal-round.h \ + display/curve.h \ + libnr/n-art-bpath.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-path-code.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate-ops.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +display/gnome-canvas-acetate.o: \ + display/gnome-canvas-acetate.cpp \ + decimal-round.h \ + display/display-forward.h \ + display/gnome-canvas-acetate.h \ + display/sp-canvas.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +display/guideline.o: \ + display/guideline.cpp \ + decimal-round.h \ + display/display-forward.h \ + display/guideline.h \ + display/sp-canvas-util.h \ + display/sp-canvas.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixops.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +display/nr-arena-glyphs.o: \ + display/nr-arena-glyphs.cpp \ + color.h \ + decimal-round.h \ + display/canvas-bpath.h \ + display/curve.h \ + display/nr-arena-forward.h \ + display/nr-arena-glyphs.h \ + display/nr-arena-group.h \ + display/nr-arena-item.h \ + display/nr-arena.h \ + display/sp-canvas.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libnr/nr-blit.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-path.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/FontFactory.h \ + libnrtype/RasterFont.h \ + libnrtype/font-instance.h \ + libnrtype/font-style.h \ + libnrtype/nr-type-pos-def.h \ + libnrtype/nr-type-primitives.h \ + libnrtype/nrtype-forward.h \ + libnrtype/raster-glyph.h \ + livarot/LivarotDefs.h \ + livarot/livarot-forward.h \ + require-config.h \ + round.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-paint-server.h \ + style.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + version.h + +display/nr-arena-group.o: \ + display/nr-arena-group.cpp \ + decimal-round.h \ + display/nr-arena-forward.h \ + display/nr-arena-group.h \ + display/nr-arena-item.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +display/nr-arena-image.o: \ + display/nr-arena-image.cpp \ + decimal-round.h \ + display/nr-arena-forward.h \ + display/nr-arena-image.h \ + display/nr-arena-item.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libnr/nr-compose-transform.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + prefs-utils.h \ + round.h + +display/nr-arena-item.o: \ + display/nr-arena-item.cpp \ + decimal-round.h \ + display/nr-arena-forward.h \ + display/nr-arena-item.h \ + display/nr-arena.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libnr/nr-blit.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-pixblock.h \ + libnr/nr-pixops.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-object.h \ + sp-paint-server.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + version.h + +display/nr-arena-shape.o: \ + display/nr-arena-shape.cpp \ + color.h \ + decimal-round.h \ + display/canvas-bpath.h \ + display/curve.h \ + display/nr-arena-forward.h \ + display/nr-arena-item.h \ + display/nr-arena-shape.h \ + display/nr-arena.h \ + display/sp-canvas.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libnr/n-art-bpath.h \ + libnr/nr-blit.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-path-code.h \ + libnr/nr-path.h \ + libnr/nr-pixblock.h \ + libnr/nr-pixops.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + livarot/LivarotDefs.h \ + livarot/Path.h \ + livarot/float-line.h \ + livarot/int-line.h \ + livarot/livarot-forward.h \ + round.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-paint-server.h \ + style.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + version.h + +display/nr-arena.o: \ + display/nr-arena.cpp \ + decimal-round.h \ + display/nr-arena-forward.h \ + display/nr-arena-item.h \ + display/nr-arena.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libnr/nr-blit.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-object.h \ + sp-paint-server.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + version.h + +display/nr-gradient-gpl.o: \ + display/nr-gradient-gpl.cpp \ + decimal-round.h \ + display/nr-gradient-gpl.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-gradient.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock-pixel.h \ + libnr/nr-pixblock.h \ + libnr/nr-pixops.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-render.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +display/nr-plain-stuff-gdk.o: \ + display/nr-plain-stuff-gdk.cpp \ + decimal-round.h \ + display/nr-plain-stuff-gdk.h \ + display/nr-plain-stuff.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock-pattern.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +display/nr-plain-stuff.o: \ + display/nr-plain-stuff.cpp \ + display/nr-plain-stuff.h \ + libnr/nr-pixops.h + +display/sodipodi-ctrl.o: \ + display/sodipodi-ctrl.cpp \ + decimal-round.h \ + display/display-forward.h \ + display/sodipodi-ctrl.h \ + display/sp-canvas-util.h \ + display/sp-canvas.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +display/sodipodi-ctrlrect.o: \ + display/sodipodi-ctrlrect.cpp \ + decimal-round.h \ + display/display-forward.h \ + display/sodipodi-ctrlrect.h \ + display/sp-canvas-util.h \ + display/sp-canvas.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +display/sp-canvas-util.o: \ + display/sp-canvas-util.cpp \ + decimal-round.h \ + display/sp-canvas-util.h \ + display/sp-canvas.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-div.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixops.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + livarot/BitLigne.h \ + livarot/LivarotDefs.h \ + livarot/int-line.h \ + livarot/livarot-forward.h \ + round.h + +display/sp-canvas.o: \ + display/sp-canvas.cpp \ + decimal-round.h \ + display/display-forward.h \ + display/sp-canvas.h \ + helper/sp-marshal.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +display/sp-ctrlline.o: \ + display/sp-ctrlline.cpp \ + decimal-round.h \ + display/display-forward.h \ + display/sp-canvas-util.h \ + display/sp-canvas.h \ + display/sp-ctrlline.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + livarot/LivarotDefs.h \ + livarot/Path.h \ + livarot/livarot-forward.h \ + round.h + +display/sp-ctrlquadr.o: \ + display/sp-ctrlquadr.cpp \ + decimal-round.h \ + display/display-forward.h \ + display/sp-canvas-util.h \ + display/sp-canvas.h \ + display/sp-ctrlquadr.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + livarot/LivarotDefs.h \ + livarot/Path.h \ + livarot/livarot-forward.h \ + round.h + +document-undo.o: \ + document-undo.cpp \ + composite-undo-stack-observer.h \ + debug/event-tracker.h \ + debug/event.h \ + debug/logger.h \ + debug/simple-event.h \ + decimal-round.h \ + display/nr-arena-forward.h \ + document-private.h \ + document.h \ + enums.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-defs.h \ + sp-item-group.h \ + sp-item.h \ + sp-object.h \ + sp-root.h \ + svg/svg-length.h \ + traits/reference.h \ + undo-stack-observer.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + util/shared-c-string-ptr.h \ + version.h \ + xml/event-fns.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +document.o: \ + document.cpp \ + application/app-prototype.h \ + application/application.h \ + application/editor.h \ + composite-undo-stack-observer.h \ + decimal-round.h \ + dialogs/rdf.h \ + dir-util.h \ + display/nr-arena-forward.h \ + display/nr-arena-item.h \ + document-private.h \ + document.h \ + enums.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/units.h \ + inkscape-private.h \ + inkscape.h \ + inkscape_version.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + prefs-utils.h \ + round.h \ + sp-defs.h \ + sp-item-group.h \ + sp-item.h \ + sp-metric.h \ + sp-object-repr.h \ + sp-object.h \ + sp-root.h \ + svg/svg-length.h \ + traits/reference.h \ + undo-stack-observer.h \ + unit-constants.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/event-fns.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +dom/charclass.o: \ + dom/charclass.cpp \ + dom/charclass.h + +dom/cssparser.o: \ + dom/cssparser.cpp \ + dom/charclass.h \ + dom/css.h \ + dom/cssparser.h \ + dom/dom.h \ + dom/domstring.h \ + dom/stylesheets.h \ + dom/views.h + +dom/domimpl.o: \ + dom/domimpl.cpp \ + dom/dom.h \ + dom/domimpl.h \ + dom/domstring.h + +dom/domstream.o: \ + dom/domstream.cpp \ + dom/dom.h \ + dom/domstream.h \ + dom/domstring.h + +dom/domstring.o: \ + dom/domstring.cpp \ + dom/domstring.h + +dom/js/fdlibm/e_acos.o: \ + dom/js/fdlibm/e_acos.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/e_acosh.o: \ + dom/js/fdlibm/e_acosh.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/e_asin.o: \ + dom/js/fdlibm/e_asin.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/e_atan2.o: \ + dom/js/fdlibm/e_atan2.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/e_atanh.o: \ + dom/js/fdlibm/e_atanh.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/e_cosh.o: \ + dom/js/fdlibm/e_cosh.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/e_exp.o: \ + dom/js/fdlibm/e_exp.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/e_fmod.o: \ + dom/js/fdlibm/e_fmod.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/e_gamma.o: \ + dom/js/fdlibm/e_gamma.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/e_gamma_r.o: \ + dom/js/fdlibm/e_gamma_r.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/e_hypot.o: \ + dom/js/fdlibm/e_hypot.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/e_j0.o: \ + dom/js/fdlibm/e_j0.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/e_j1.o: \ + dom/js/fdlibm/e_j1.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/e_jn.o: \ + dom/js/fdlibm/e_jn.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/e_lgamma.o: \ + dom/js/fdlibm/e_lgamma.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/e_lgamma_r.o: \ + dom/js/fdlibm/e_lgamma_r.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/e_log.o: \ + dom/js/fdlibm/e_log.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/e_log10.o: \ + dom/js/fdlibm/e_log10.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/e_pow.o: \ + dom/js/fdlibm/e_pow.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/e_rem_pio2.o: \ + dom/js/fdlibm/e_rem_pio2.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/e_remainder.o: \ + dom/js/fdlibm/e_remainder.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/e_scalb.o: \ + dom/js/fdlibm/e_scalb.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/e_sinh.o: \ + dom/js/fdlibm/e_sinh.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/e_sqrt.o: \ + dom/js/fdlibm/e_sqrt.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/k_cos.o: \ + dom/js/fdlibm/k_cos.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/k_rem_pio2.o: \ + dom/js/fdlibm/k_rem_pio2.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/k_sin.o: \ + dom/js/fdlibm/k_sin.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/k_standard.o: \ + dom/js/fdlibm/k_standard.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/k_tan.o: \ + dom/js/fdlibm/k_tan.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/s_asinh.o: \ + dom/js/fdlibm/s_asinh.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/s_atan.o: \ + dom/js/fdlibm/s_atan.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/s_cbrt.o: \ + dom/js/fdlibm/s_cbrt.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/s_ceil.o: \ + dom/js/fdlibm/s_ceil.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/s_copysign.o: \ + dom/js/fdlibm/s_copysign.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/s_cos.o: \ + dom/js/fdlibm/s_cos.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/s_erf.o: \ + dom/js/fdlibm/s_erf.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/s_expm1.o: \ + dom/js/fdlibm/s_expm1.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/s_fabs.o: \ + dom/js/fdlibm/s_fabs.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/s_finite.o: \ + dom/js/fdlibm/s_finite.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/s_floor.o: \ + dom/js/fdlibm/s_floor.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/s_frexp.o: \ + dom/js/fdlibm/s_frexp.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/s_ilogb.o: \ + dom/js/fdlibm/s_ilogb.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/s_isnan.o: \ + dom/js/fdlibm/s_isnan.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/s_ldexp.o: \ + dom/js/fdlibm/s_ldexp.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/s_lib_version.o: \ + dom/js/fdlibm/s_lib_version.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/s_log1p.o: \ + dom/js/fdlibm/s_log1p.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/s_logb.o: \ + dom/js/fdlibm/s_logb.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/s_matherr.o: \ + dom/js/fdlibm/s_matherr.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/s_modf.o: \ + dom/js/fdlibm/s_modf.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/s_nextafter.o: \ + dom/js/fdlibm/s_nextafter.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/s_rint.o: \ + dom/js/fdlibm/s_rint.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/s_scalbn.o: \ + dom/js/fdlibm/s_scalbn.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/s_signgam.o: \ + dom/js/fdlibm/s_signgam.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/s_significand.o: \ + dom/js/fdlibm/s_significand.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/s_sin.o: \ + dom/js/fdlibm/s_sin.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/s_tan.o: \ + dom/js/fdlibm/s_tan.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/s_tanh.o: \ + dom/js/fdlibm/s_tanh.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/w_acos.o: \ + dom/js/fdlibm/w_acos.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/w_acosh.o: \ + dom/js/fdlibm/w_acosh.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/w_asin.o: \ + dom/js/fdlibm/w_asin.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/w_atan2.o: \ + dom/js/fdlibm/w_atan2.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/w_atanh.o: \ + dom/js/fdlibm/w_atanh.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/w_cosh.o: \ + dom/js/fdlibm/w_cosh.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/w_exp.o: \ + dom/js/fdlibm/w_exp.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/w_fmod.o: \ + dom/js/fdlibm/w_fmod.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/w_gamma.o: \ + dom/js/fdlibm/w_gamma.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/w_gamma_r.o: \ + dom/js/fdlibm/w_gamma_r.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/w_hypot.o: \ + dom/js/fdlibm/w_hypot.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/w_j0.o: \ + dom/js/fdlibm/w_j0.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/w_j1.o: \ + dom/js/fdlibm/w_j1.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/w_jn.o: \ + dom/js/fdlibm/w_jn.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/w_lgamma.o: \ + dom/js/fdlibm/w_lgamma.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/w_lgamma_r.o: \ + dom/js/fdlibm/w_lgamma_r.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/w_log.o: \ + dom/js/fdlibm/w_log.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/w_log10.o: \ + dom/js/fdlibm/w_log10.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/w_pow.o: \ + dom/js/fdlibm/w_pow.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/w_remainder.o: \ + dom/js/fdlibm/w_remainder.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/w_scalb.o: \ + dom/js/fdlibm/w_scalb.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/w_sinh.o: \ + dom/js/fdlibm/w_sinh.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/fdlibm/w_sqrt.o: \ + dom/js/fdlibm/w_sqrt.c \ + dom/js/fdlibm/fdlibm.h + +dom/js/js.o: \ + dom/js/js.c \ + dom/js/jsapi.h \ + dom/js/jsarena.h \ + dom/js/jsatom.h \ + dom/js/jsautocfg.h \ + dom/js/jsclist.h \ + dom/js/jscntxt.h \ + dom/js/jscompat.h \ + dom/js/jsconfig.h \ + dom/js/jscpucfg.h \ + dom/js/jsdbgapi.h \ + dom/js/jsdhash.h \ + dom/js/jsemit.h \ + dom/js/jsfun.h \ + dom/js/jsgc.h \ + dom/js/jshash.h \ + dom/js/jsinterp.h \ + dom/js/jslock.h \ + dom/js/jslong.h \ + dom/js/jsobj.h \ + dom/js/jsopcode.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsparse.h \ + dom/js/jsprf.h \ + dom/js/jsprvtd.h \ + dom/js/jspubtd.h \ + dom/js/jsregexp.h \ + dom/js/jsscan.h \ + dom/js/jsscope.h \ + dom/js/jsscript.h \ + dom/js/jsstddef.h \ + dom/js/jsstr.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h + +dom/js/jsapi.o: \ + dom/js/jsapi.c \ + dom/js/jsapi.h \ + dom/js/jsarena.h \ + dom/js/jsarray.h \ + dom/js/jsatom.h \ + dom/js/jsautocfg.h \ + dom/js/jsbool.h \ + dom/js/jsclist.h \ + dom/js/jscntxt.h \ + dom/js/jscompat.h \ + dom/js/jsconfig.h \ + dom/js/jscpucfg.h \ + dom/js/jsdate.h \ + dom/js/jsdhash.h \ + dom/js/jsdtoa.h \ + dom/js/jsemit.h \ + dom/js/jsexn.h \ + dom/js/jsfile.h \ + dom/js/jsfun.h \ + dom/js/jsgc.h \ + dom/js/jshash.h \ + dom/js/jsinterp.h \ + dom/js/jslock.h \ + dom/js/jslong.h \ + dom/js/jsmath.h \ + dom/js/jsnum.h \ + dom/js/jsobj.h \ + dom/js/jsopcode.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsparse.h \ + dom/js/jsprf.h \ + dom/js/jsprvtd.h \ + dom/js/jspubtd.h \ + dom/js/jsregexp.h \ + dom/js/jsscan.h \ + dom/js/jsscope.h \ + dom/js/jsscript.h \ + dom/js/jsstddef.h \ + dom/js/jsstr.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h \ + dom/js/prmjtime.h + +dom/js/jsarena.o: \ + dom/js/jsarena.c \ + dom/js/jsarena.h \ + dom/js/jsautocfg.h \ + dom/js/jsbit.h \ + dom/js/jscompat.h \ + dom/js/jscpucfg.h \ + dom/js/jshash.h \ + dom/js/jslock.h \ + dom/js/jslong.h \ + dom/js/jsobj.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsprvtd.h \ + dom/js/jspubtd.h \ + dom/js/jsscope.h \ + dom/js/jsstddef.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h + +dom/js/jsarray.o: \ + dom/js/jsarray.c \ + dom/js/jsapi.h \ + dom/js/jsarena.h \ + dom/js/jsarray.h \ + dom/js/jsatom.h \ + dom/js/jsautocfg.h \ + dom/js/jsclist.h \ + dom/js/jscntxt.h \ + dom/js/jscompat.h \ + dom/js/jsconfig.h \ + dom/js/jscpucfg.h \ + dom/js/jsdhash.h \ + dom/js/jsfun.h \ + dom/js/jsgc.h \ + dom/js/jshash.h \ + dom/js/jsinterp.h \ + dom/js/jslock.h \ + dom/js/jslong.h \ + dom/js/jsnum.h \ + dom/js/jsobj.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsprvtd.h \ + dom/js/jspubtd.h \ + dom/js/jsregexp.h \ + dom/js/jsscope.h \ + dom/js/jsstddef.h \ + dom/js/jsstr.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h + +dom/js/jsatom.o: \ + dom/js/jsatom.c \ + dom/js/jsapi.h \ + dom/js/jsarena.h \ + dom/js/jsatom.h \ + dom/js/jsautocfg.h \ + dom/js/jsclist.h \ + dom/js/jscntxt.h \ + dom/js/jscompat.h \ + dom/js/jsconfig.h \ + dom/js/jscpucfg.h \ + dom/js/jsdhash.h \ + dom/js/jsgc.h \ + dom/js/jshash.h \ + dom/js/jsinterp.h \ + dom/js/jslock.h \ + dom/js/jslong.h \ + dom/js/jsnum.h \ + dom/js/jsobj.h \ + dom/js/jsopcode.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsprf.h \ + dom/js/jsprvtd.h \ + dom/js/jspubtd.h \ + dom/js/jsregexp.h \ + dom/js/jsscope.h \ + dom/js/jsstddef.h \ + dom/js/jsstr.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h + +dom/js/jsbool.o: \ + dom/js/jsbool.c \ + dom/js/jsapi.h \ + dom/js/jsarena.h \ + dom/js/jsatom.h \ + dom/js/jsautocfg.h \ + dom/js/jsbool.h \ + dom/js/jsclist.h \ + dom/js/jscntxt.h \ + dom/js/jscompat.h \ + dom/js/jsconfig.h \ + dom/js/jscpucfg.h \ + dom/js/jsdhash.h \ + dom/js/jsgc.h \ + dom/js/jshash.h \ + dom/js/jsinterp.h \ + dom/js/jslock.h \ + dom/js/jslong.h \ + dom/js/jsnum.h \ + dom/js/jsobj.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsprf.h \ + dom/js/jsprvtd.h \ + dom/js/jspubtd.h \ + dom/js/jsregexp.h \ + dom/js/jsscope.h \ + dom/js/jsstddef.h \ + dom/js/jsstr.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h + +dom/js/jscntxt.o: \ + dom/js/jscntxt.c \ + dom/js/jsapi.h \ + dom/js/jsarena.h \ + dom/js/jsatom.h \ + dom/js/jsautocfg.h \ + dom/js/jsclist.h \ + dom/js/jscntxt.h \ + dom/js/jscompat.h \ + dom/js/jsconfig.h \ + dom/js/jscpucfg.h \ + dom/js/jsdbgapi.h \ + dom/js/jsdhash.h \ + dom/js/jsexn.h \ + dom/js/jsgc.h \ + dom/js/jshash.h \ + dom/js/jsinterp.h \ + dom/js/jslock.h \ + dom/js/jslong.h \ + dom/js/jsnum.h \ + dom/js/jsobj.h \ + dom/js/jsopcode.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsprf.h \ + dom/js/jsprvtd.h \ + dom/js/jspubtd.h \ + dom/js/jsregexp.h \ + dom/js/jsscan.h \ + dom/js/jsscope.h \ + dom/js/jsscript.h \ + dom/js/jsstddef.h \ + dom/js/jsstr.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h + +dom/js/jscpucfg.o: \ + dom/js/jscpucfg.c + +dom/js/jsdate.o: \ + dom/js/jsdate.c \ + dom/js/jsapi.h \ + dom/js/jsarena.h \ + dom/js/jsatom.h \ + dom/js/jsautocfg.h \ + dom/js/jsclist.h \ + dom/js/jscntxt.h \ + dom/js/jscompat.h \ + dom/js/jsconfig.h \ + dom/js/jscpucfg.h \ + dom/js/jsdate.h \ + dom/js/jsdhash.h \ + dom/js/jsdtoa.h \ + dom/js/jsgc.h \ + dom/js/jshash.h \ + dom/js/jsinterp.h \ + dom/js/jslock.h \ + dom/js/jslong.h \ + dom/js/jsnum.h \ + dom/js/jsobj.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsprf.h \ + dom/js/jsprvtd.h \ + dom/js/jspubtd.h \ + dom/js/jsregexp.h \ + dom/js/jsscope.h \ + dom/js/jsstddef.h \ + dom/js/jsstr.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h \ + dom/js/prmjtime.h + +dom/js/jsdbgapi.o: \ + dom/js/jsdbgapi.c \ + dom/js/jsapi.h \ + dom/js/jsarena.h \ + dom/js/jsatom.h \ + dom/js/jsautocfg.h \ + dom/js/jsclist.h \ + dom/js/jscntxt.h \ + dom/js/jscompat.h \ + dom/js/jsconfig.h \ + dom/js/jscpucfg.h \ + dom/js/jsdbgapi.h \ + dom/js/jsdhash.h \ + dom/js/jsemit.h \ + dom/js/jsfun.h \ + dom/js/jsgc.h \ + dom/js/jshash.h \ + dom/js/jsinterp.h \ + dom/js/jslock.h \ + dom/js/jslong.h \ + dom/js/jsobj.h \ + dom/js/jsopcode.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsprvtd.h \ + dom/js/jspubtd.h \ + dom/js/jsregexp.h \ + dom/js/jsscope.h \ + dom/js/jsscript.h \ + dom/js/jsstddef.h \ + dom/js/jsstr.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h + +dom/js/jsdhash.o: \ + dom/js/jsdhash.c \ + dom/js/jsautocfg.h \ + dom/js/jsbit.h \ + dom/js/jscpucfg.h \ + dom/js/jsdhash.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h + +dom/js/jsdtoa.o: \ + dom/js/jsdtoa.c \ + dom/js/jsautocfg.h \ + dom/js/jscompat.h \ + dom/js/jsconfig.h \ + dom/js/jscpucfg.h \ + dom/js/jsdtoa.h \ + dom/js/jslibmath.h \ + dom/js/jslong.h \ + dom/js/jsnum.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsprf.h \ + dom/js/jspubtd.h \ + dom/js/jsstddef.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h + +dom/js/jsemit.o: \ + dom/js/jsemit.c \ + dom/js/jsapi.h \ + dom/js/jsarena.h \ + dom/js/jsatom.h \ + dom/js/jsautocfg.h \ + dom/js/jsbit.h \ + dom/js/jsclist.h \ + dom/js/jscntxt.h \ + dom/js/jscompat.h \ + dom/js/jsconfig.h \ + dom/js/jscpucfg.h \ + dom/js/jsdhash.h \ + dom/js/jsemit.h \ + dom/js/jsfun.h \ + dom/js/jsgc.h \ + dom/js/jshash.h \ + dom/js/jsinterp.h \ + dom/js/jslock.h \ + dom/js/jslong.h \ + dom/js/jsnum.h \ + dom/js/jsobj.h \ + dom/js/jsopcode.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsparse.h \ + dom/js/jsprf.h \ + dom/js/jsprvtd.h \ + dom/js/jspubtd.h \ + dom/js/jsregexp.h \ + dom/js/jsscan.h \ + dom/js/jsscope.h \ + dom/js/jsscript.h \ + dom/js/jsstddef.h \ + dom/js/jsstr.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h \ + ui/dialog/dialog.h \ + ui/dialog/memory.h + +dom/js/jsexn.o: \ + dom/js/jsexn.c \ + dom/js/jsapi.h \ + dom/js/jsarena.h \ + dom/js/jsatom.h \ + dom/js/jsautocfg.h \ + dom/js/jsbit.h \ + dom/js/jsclist.h \ + dom/js/jscntxt.h \ + dom/js/jscompat.h \ + dom/js/jsconfig.h \ + dom/js/jscpucfg.h \ + dom/js/jsdhash.h \ + dom/js/jsexn.h \ + dom/js/jsfun.h \ + dom/js/jsgc.h \ + dom/js/jshash.h \ + dom/js/jsinterp.h \ + dom/js/jslock.h \ + dom/js/jslong.h \ + dom/js/jsnum.h \ + dom/js/jsobj.h \ + dom/js/jsopcode.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsprf.h \ + dom/js/jsprvtd.h \ + dom/js/jspubtd.h \ + dom/js/jsregexp.h \ + dom/js/jsscope.h \ + dom/js/jsscript.h \ + dom/js/jsstddef.h \ + dom/js/jsstr.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h + +dom/js/jsfile.o: \ + dom/js/jsfile.c \ + dom/js/jsapi.h \ + dom/js/jsarena.h \ + dom/js/jsatom.h \ + dom/js/jsautocfg.h \ + dom/js/jsclist.h \ + dom/js/jscntxt.h \ + dom/js/jscompat.h \ + dom/js/jsconfig.h \ + dom/js/jscpucfg.h \ + dom/js/jsdate.h \ + dom/js/jsdbgapi.h \ + dom/js/jsdhash.h \ + dom/js/jsemit.h \ + dom/js/jsfun.h \ + dom/js/jsgc.h \ + dom/js/jshash.h \ + dom/js/jsinterp.h \ + dom/js/jslock.h \ + dom/js/jslong.h \ + dom/js/jsobj.h \ + dom/js/jsopcode.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsparse.h \ + dom/js/jsprvtd.h \ + dom/js/jspubtd.h \ + dom/js/jsregexp.h \ + dom/js/jsscan.h \ + dom/js/jsscope.h \ + dom/js/jsscript.h \ + dom/js/jsstddef.h \ + dom/js/jsstr.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h + +dom/js/jsfun.o: \ + dom/js/jsfun.c \ + dom/js/jsapi.h \ + dom/js/jsarena.h \ + dom/js/jsarray.h \ + dom/js/jsatom.h \ + dom/js/jsautocfg.h \ + dom/js/jsbit.h \ + dom/js/jsclist.h \ + dom/js/jscntxt.h \ + dom/js/jscompat.h \ + dom/js/jsconfig.h \ + dom/js/jscpucfg.h \ + dom/js/jsdbgapi.h \ + dom/js/jsdhash.h \ + dom/js/jsexn.h \ + dom/js/jsfun.h \ + dom/js/jsgc.h \ + dom/js/jshash.h \ + dom/js/jsinterp.h \ + dom/js/jslock.h \ + dom/js/jslong.h \ + dom/js/jsnum.h \ + dom/js/jsobj.h \ + dom/js/jsopcode.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsparse.h \ + dom/js/jsprvtd.h \ + dom/js/jspubtd.h \ + dom/js/jsregexp.h \ + dom/js/jsscan.h \ + dom/js/jsscope.h \ + dom/js/jsscript.h \ + dom/js/jsstddef.h \ + dom/js/jsstr.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h \ + dom/js/jsxdrapi.h + +dom/js/jsgc.o: \ + dom/js/jsgc.c \ + dom/js/jsapi.h \ + dom/js/jsarena.h \ + dom/js/jsatom.h \ + dom/js/jsautocfg.h \ + dom/js/jsclist.h \ + dom/js/jscntxt.h \ + dom/js/jscompat.h \ + dom/js/jsconfig.h \ + dom/js/jscpucfg.h \ + dom/js/jsdbgapi.h \ + dom/js/jsdhash.h \ + dom/js/jsfun.h \ + dom/js/jsgc.h \ + dom/js/jshash.h \ + dom/js/jsinterp.h \ + dom/js/jslock.h \ + dom/js/jslong.h \ + dom/js/jsnum.h \ + dom/js/jsobj.h \ + dom/js/jsopcode.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsprf.h \ + dom/js/jsprvtd.h \ + dom/js/jspubtd.h \ + dom/js/jsregexp.h \ + dom/js/jsscope.h \ + dom/js/jsscript.h \ + dom/js/jsstddef.h \ + dom/js/jsstr.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h + +dom/js/jshash.o: \ + dom/js/jshash.c \ + dom/js/jsautocfg.h \ + dom/js/jsbit.h \ + dom/js/jscompat.h \ + dom/js/jscpucfg.h \ + dom/js/jshash.h \ + dom/js/jslong.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsstddef.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h + +dom/js/jsinterp.o: \ + dom/js/jsinterp.c \ + dom/js/jsapi.h \ + dom/js/jsarena.h \ + dom/js/jsarray.h \ + dom/js/jsatom.h \ + dom/js/jsautocfg.h \ + dom/js/jsbool.h \ + dom/js/jsclist.h \ + dom/js/jscntxt.h \ + dom/js/jscompat.h \ + dom/js/jsconfig.h \ + dom/js/jscpucfg.h \ + dom/js/jsdbgapi.h \ + dom/js/jsdhash.h \ + dom/js/jsfun.h \ + dom/js/jsgc.h \ + dom/js/jshash.h \ + dom/js/jsinterp.h \ + dom/js/jslock.h \ + dom/js/jslong.h \ + dom/js/jsnum.h \ + dom/js/jsobj.h \ + dom/js/jsopcode.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsprf.h \ + dom/js/jsprvtd.h \ + dom/js/jspubtd.h \ + dom/js/jsregexp.h \ + dom/js/jsscope.h \ + dom/js/jsscript.h \ + dom/js/jsstddef.h \ + dom/js/jsstr.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h + +dom/js/jslock.o: \ + dom/js/jslock.c \ + dom/js/jsapi.h \ + dom/js/jsarena.h \ + dom/js/jsatom.h \ + dom/js/jsautocfg.h \ + dom/js/jsbit.h \ + dom/js/jsclist.h \ + dom/js/jscntxt.h \ + dom/js/jscompat.h \ + dom/js/jsconfig.h \ + dom/js/jscpucfg.h \ + dom/js/jsdhash.h \ + dom/js/jsdtoa.h \ + dom/js/jsgc.h \ + dom/js/jshash.h \ + dom/js/jsinterp.h \ + dom/js/jslock.h \ + dom/js/jslong.h \ + dom/js/jsobj.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsprvtd.h \ + dom/js/jspubtd.h \ + dom/js/jsregexp.h \ + dom/js/jsscope.h \ + dom/js/jsstddef.h \ + dom/js/jsstr.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h \ + ui/dialog/dialog.h \ + ui/dialog/memory.h + +dom/js/jslog2.o: \ + dom/js/jslog2.c \ + dom/js/jsautocfg.h \ + dom/js/jsbit.h \ + dom/js/jscpucfg.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsstddef.h \ + dom/js/jstypes.h + +dom/js/jslong.o: \ + dom/js/jslong.c \ + dom/js/jsautocfg.h \ + dom/js/jscpucfg.h \ + dom/js/jslong.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsstddef.h \ + dom/js/jstypes.h + +dom/js/jsmath.o: \ + dom/js/jsmath.c \ + dom/js/jsapi.h \ + dom/js/jsarena.h \ + dom/js/jsatom.h \ + dom/js/jsautocfg.h \ + dom/js/jsclist.h \ + dom/js/jscntxt.h \ + dom/js/jscompat.h \ + dom/js/jsconfig.h \ + dom/js/jscpucfg.h \ + dom/js/jsdhash.h \ + dom/js/jsgc.h \ + dom/js/jshash.h \ + dom/js/jsinterp.h \ + dom/js/jslibmath.h \ + dom/js/jslock.h \ + dom/js/jslong.h \ + dom/js/jsmath.h \ + dom/js/jsnum.h \ + dom/js/jsobj.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsprvtd.h \ + dom/js/jspubtd.h \ + dom/js/jsregexp.h \ + dom/js/jsscope.h \ + dom/js/jsstddef.h \ + dom/js/jsstr.h \ + dom/js/jstypes.h \ + dom/js/prmjtime.h + +dom/js/jsnum.o: \ + dom/js/jsnum.c \ + dom/js/jsapi.h \ + dom/js/jsarena.h \ + dom/js/jsatom.h \ + dom/js/jsautocfg.h \ + dom/js/jsclist.h \ + dom/js/jscntxt.h \ + dom/js/jscompat.h \ + dom/js/jsconfig.h \ + dom/js/jscpucfg.h \ + dom/js/jsdhash.h \ + dom/js/jsdtoa.h \ + dom/js/jsgc.h \ + dom/js/jshash.h \ + dom/js/jsinterp.h \ + dom/js/jslock.h \ + dom/js/jslong.h \ + dom/js/jsnum.h \ + dom/js/jsobj.h \ + dom/js/jsopcode.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsprf.h \ + dom/js/jsprvtd.h \ + dom/js/jspubtd.h \ + dom/js/jsregexp.h \ + dom/js/jsscope.h \ + dom/js/jsstddef.h \ + dom/js/jsstr.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h + +dom/js/jsobj.o: \ + dom/js/jsobj.c \ + dom/js/jsapi.h \ + dom/js/jsarena.h \ + dom/js/jsarray.h \ + dom/js/jsatom.h \ + dom/js/jsautocfg.h \ + dom/js/jsbool.h \ + dom/js/jsclist.h \ + dom/js/jscntxt.h \ + dom/js/jscompat.h \ + dom/js/jsconfig.h \ + dom/js/jscpucfg.h \ + dom/js/jsdbgapi.h \ + dom/js/jsdhash.h \ + dom/js/jsfun.h \ + dom/js/jsgc.h \ + dom/js/jshash.h \ + dom/js/jsinterp.h \ + dom/js/jslock.h \ + dom/js/jslong.h \ + dom/js/jsnum.h \ + dom/js/jsobj.h \ + dom/js/jsopcode.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsprf.h \ + dom/js/jsprvtd.h \ + dom/js/jspubtd.h \ + dom/js/jsregexp.h \ + dom/js/jsscope.h \ + dom/js/jsscript.h \ + dom/js/jsstddef.h \ + dom/js/jsstr.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h \ + dom/js/jsxdrapi.h + +dom/js/jsopcode.o: \ + dom/js/jsopcode.c \ + dom/js/jsapi.h \ + dom/js/jsarena.h \ + dom/js/jsarray.h \ + dom/js/jsatom.h \ + dom/js/jsautocfg.h \ + dom/js/jsclist.h \ + dom/js/jscntxt.h \ + dom/js/jscompat.h \ + dom/js/jsconfig.h \ + dom/js/jscpucfg.h \ + dom/js/jsdbgapi.h \ + dom/js/jsdhash.h \ + dom/js/jsdtoa.h \ + dom/js/jsemit.h \ + dom/js/jsfun.h \ + dom/js/jsgc.h \ + dom/js/jshash.h \ + dom/js/jsinterp.h \ + dom/js/jslock.h \ + dom/js/jslong.h \ + dom/js/jsobj.h \ + dom/js/jsopcode.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsprf.h \ + dom/js/jsprvtd.h \ + dom/js/jspubtd.h \ + dom/js/jsregexp.h \ + dom/js/jsscope.h \ + dom/js/jsscript.h \ + dom/js/jsstddef.h \ + dom/js/jsstr.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h \ + ui/dialog/dialog.h \ + ui/dialog/memory.h + +dom/js/jsparse.o: \ + dom/js/jsparse.c \ + dom/js/jsapi.h \ + dom/js/jsarena.h \ + dom/js/jsatom.h \ + dom/js/jsautocfg.h \ + dom/js/jsclist.h \ + dom/js/jscntxt.h \ + dom/js/jscompat.h \ + dom/js/jsconfig.h \ + dom/js/jscpucfg.h \ + dom/js/jsdhash.h \ + dom/js/jsemit.h \ + dom/js/jsfun.h \ + dom/js/jsgc.h \ + dom/js/jshash.h \ + dom/js/jsinterp.h \ + dom/js/jslock.h \ + dom/js/jslong.h \ + dom/js/jsnum.h \ + dom/js/jsobj.h \ + dom/js/jsopcode.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsparse.h \ + dom/js/jsprvtd.h \ + dom/js/jspubtd.h \ + dom/js/jsregexp.h \ + dom/js/jsscan.h \ + dom/js/jsscope.h \ + dom/js/jsscript.h \ + dom/js/jsstddef.h \ + dom/js/jsstr.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h + +dom/js/jsprf.o: \ + dom/js/jsprf.c \ + dom/js/jsautocfg.h \ + dom/js/jscpucfg.h \ + dom/js/jslong.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsprf.h \ + dom/js/jsstddef.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h + +dom/js/jsregexp.o: \ + dom/js/jsregexp.c \ + dom/js/jsapi.h \ + dom/js/jsarena.h \ + dom/js/jsarray.h \ + dom/js/jsatom.h \ + dom/js/jsautocfg.h \ + dom/js/jsclist.h \ + dom/js/jscntxt.h \ + dom/js/jscompat.h \ + dom/js/jsconfig.h \ + dom/js/jscpucfg.h \ + dom/js/jsdhash.h \ + dom/js/jsfun.h \ + dom/js/jsgc.h \ + dom/js/jshash.h \ + dom/js/jsinterp.h \ + dom/js/jslock.h \ + dom/js/jslong.h \ + dom/js/jsnum.h \ + dom/js/jsobj.h \ + dom/js/jsopcode.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsprvtd.h \ + dom/js/jspubtd.h \ + dom/js/jsregexp.h \ + dom/js/jsscan.h \ + dom/js/jsscope.h \ + dom/js/jsstddef.h \ + dom/js/jsstr.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h \ + dom/js/jsxdrapi.h + +dom/js/jsscan.o: \ + dom/js/jsscan.c \ + dom/js/jsapi.h \ + dom/js/jsarena.h \ + dom/js/jsatom.h \ + dom/js/jsautocfg.h \ + dom/js/jsclist.h \ + dom/js/jscntxt.h \ + dom/js/jscompat.h \ + dom/js/jsconfig.h \ + dom/js/jscpucfg.h \ + dom/js/jsdhash.h \ + dom/js/jsdtoa.h \ + dom/js/jsemit.h \ + dom/js/jsexn.h \ + dom/js/jsgc.h \ + dom/js/jshash.h \ + dom/js/jsinterp.h \ + dom/js/jslock.h \ + dom/js/jslong.h \ + dom/js/jsnum.h \ + dom/js/jsobj.h \ + dom/js/jsopcode.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsprf.h \ + dom/js/jsprvtd.h \ + dom/js/jspubtd.h \ + dom/js/jsregexp.h \ + dom/js/jsscan.h \ + dom/js/jsscope.h \ + dom/js/jsstddef.h \ + dom/js/jsstr.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h \ + ui/dialog/dialog.h \ + ui/dialog/memory.h + +dom/js/jsscope.o: \ + dom/js/jsscope.c \ + dom/js/jsapi.h \ + dom/js/jsarena.h \ + dom/js/jsatom.h \ + dom/js/jsautocfg.h \ + dom/js/jsbit.h \ + dom/js/jsclist.h \ + dom/js/jscntxt.h \ + dom/js/jscompat.h \ + dom/js/jsconfig.h \ + dom/js/jscpucfg.h \ + dom/js/jsdbgapi.h \ + dom/js/jsdhash.h \ + dom/js/jsgc.h \ + dom/js/jshash.h \ + dom/js/jsinterp.h \ + dom/js/jslock.h \ + dom/js/jslong.h \ + dom/js/jsnum.h \ + dom/js/jsobj.h \ + dom/js/jsopcode.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsprvtd.h \ + dom/js/jspubtd.h \ + dom/js/jsregexp.h \ + dom/js/jsscope.h \ + dom/js/jsstddef.h \ + dom/js/jsstr.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h + +dom/js/jsscript.o: \ + dom/js/jsscript.c \ + dom/js/jsapi.h \ + dom/js/jsarena.h \ + dom/js/jsatom.h \ + dom/js/jsautocfg.h \ + dom/js/jsclist.h \ + dom/js/jscntxt.h \ + dom/js/jscompat.h \ + dom/js/jsconfig.h \ + dom/js/jscpucfg.h \ + dom/js/jsdbgapi.h \ + dom/js/jsdhash.h \ + dom/js/jsemit.h \ + dom/js/jsfun.h \ + dom/js/jsgc.h \ + dom/js/jshash.h \ + dom/js/jsinterp.h \ + dom/js/jslock.h \ + dom/js/jslong.h \ + dom/js/jsnum.h \ + dom/js/jsobj.h \ + dom/js/jsopcode.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsprf.h \ + dom/js/jsprvtd.h \ + dom/js/jspubtd.h \ + dom/js/jsregexp.h \ + dom/js/jsscope.h \ + dom/js/jsscript.h \ + dom/js/jsstddef.h \ + dom/js/jsstr.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h \ + dom/js/jsxdrapi.h + +dom/js/jsstr.o: \ + dom/js/jsstr.c \ + dom/js/jsapi.h \ + dom/js/jsarena.h \ + dom/js/jsarray.h \ + dom/js/jsatom.h \ + dom/js/jsautocfg.h \ + dom/js/jsbool.h \ + dom/js/jsclist.h \ + dom/js/jscntxt.h \ + dom/js/jscompat.h \ + dom/js/jsconfig.h \ + dom/js/jscpucfg.h \ + dom/js/jsdhash.h \ + dom/js/jsgc.h \ + dom/js/jshash.h \ + dom/js/jsinterp.h \ + dom/js/jslock.h \ + dom/js/jslong.h \ + dom/js/jsnum.h \ + dom/js/jsobj.h \ + dom/js/jsopcode.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsprf.h \ + dom/js/jsprvtd.h \ + dom/js/jspubtd.h \ + dom/js/jsregexp.h \ + dom/js/jsscope.h \ + dom/js/jsstddef.h \ + dom/js/jsstr.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h + +dom/js/jsutil.o: \ + dom/js/jsutil.c \ + dom/js/jsautocfg.h \ + dom/js/jscpucfg.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsprf.h \ + dom/js/jsstddef.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h + +dom/js/jsxdrapi.o: \ + dom/js/jsxdrapi.c \ + dom/js/jsapi.h \ + dom/js/jsarena.h \ + dom/js/jsatom.h \ + dom/js/jsautocfg.h \ + dom/js/jsclist.h \ + dom/js/jscntxt.h \ + dom/js/jscompat.h \ + dom/js/jsconfig.h \ + dom/js/jscpucfg.h \ + dom/js/jsdhash.h \ + dom/js/jsgc.h \ + dom/js/jshash.h \ + dom/js/jsinterp.h \ + dom/js/jslock.h \ + dom/js/jslong.h \ + dom/js/jsobj.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsprf.h \ + dom/js/jsprvtd.h \ + dom/js/jspubtd.h \ + dom/js/jsregexp.h \ + dom/js/jsscope.h \ + dom/js/jsscript.h \ + dom/js/jsstddef.h \ + dom/js/jsstr.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h \ + dom/js/jsxdrapi.h + +dom/js/prmjtime.o: \ + dom/js/prmjtime.c \ + dom/js/jsautocfg.h \ + dom/js/jscompat.h \ + dom/js/jscpucfg.h \ + dom/js/jslong.h \ + dom/js/jsosdep.h \ + dom/js/jsotypes.h \ + dom/js/jsprf.h \ + dom/js/jsstddef.h \ + dom/js/jstypes.h \ + dom/js/jsutil.h \ + dom/js/prmjtime.h \ + extension/extension-forward.h \ + extension/timer.h + +dom/lsimpl.o: \ + dom/lsimpl.cpp \ + dom/dom.h \ + dom/domimpl.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/events.h \ + dom/ls.h \ + dom/lsimpl.h \ + dom/traversal.h \ + dom/views.h \ + dom/xmlreader.h + +dom/prop-css.o: \ + dom/prop-css.cpp + +dom/prop-css2.o: \ + dom/prop-css2.cpp + +dom/prop-svg.o: \ + dom/prop-svg.cpp + +dom/smilimpl.o: \ + dom/smilimpl.cpp \ + dom/dom.h \ + dom/domimpl.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/smilimpl.h \ + dom/views.h + +dom/stringstream.o: \ + dom/stringstream.cpp \ + dom/dom.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/stringstream.h + +dom/svgimpl.o: \ + dom/svgimpl.cpp \ + dom/css.h \ + dom/dom.h \ + dom/domimpl.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/smilimpl.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgimpl.h \ + dom/svgtypes.h \ + dom/views.h + +dom/svglsimpl.o: \ + dom/svglsimpl.cpp \ + dom/css.h \ + dom/dom.h \ + dom/domimpl.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/events.h \ + dom/ls.h \ + dom/lsimpl.h \ + dom/smil.h \ + dom/smilimpl.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgimpl.h \ + dom/svglsimpl.h \ + dom/svgparser.h \ + dom/svgtypes.h \ + dom/traversal.h \ + dom/views.h \ + dom/xmlreader.h + +dom/svgparser.o: \ + dom/svgparser.cpp \ + dom/charclass.h \ + dom/css.h \ + dom/cssparser.h \ + dom/dom.h \ + dom/domimpl.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/smilimpl.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgimpl.h \ + dom/svgparser.h \ + dom/svgtypes.h \ + dom/views.h + +dom/uri.o: \ + dom/uri.cpp \ + dom/dom.h \ + dom/domstring.h \ + dom/uri.h + +dom/uristream.o: \ + dom/uristream.cpp \ + dom/dom.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/uri.h \ + dom/uristream.h + +dom/uritest.o: \ + dom/uritest.cpp \ + dom/dom.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/uri.h \ + dom/uristream.h + +dom/xmlreader.o: \ + dom/xmlreader.cpp \ + dom/css.h \ + dom/dom.h \ + dom/domimpl.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/smilimpl.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgimpl.h \ + dom/svgtypes.h \ + dom/views.h \ + dom/xmlreader.h + +dom/xpathimpl.o: \ + dom/xpathimpl.cpp \ + dom/dom.h \ + dom/domimpl.h \ + dom/domstring.h \ + dom/xpath.h \ + dom/xpathimpl.h + +dom/xpathparser.o: \ + dom/xpathparser.cpp \ + dom/charclass.h \ + dom/dom.h \ + dom/domstring.h \ + dom/xpathparser.h + +draw-anchor.o: \ + draw-anchor.cpp \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + display/display-forward.h \ + display/sodipodi-ctrl.h \ + display/sp-canvas.h \ + draw-anchor.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + message.h \ + round.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h + +draw-context.o: \ + draw-context.cpp \ + decimal-round.h \ + desktop-affine.h \ + desktop-handles.h \ + desktop-style.h \ + desktop.h \ + display/canvas-bpath.h \ + display/curve.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/sp-canvas.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + draw-anchor.h \ + draw-context.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libavoid/connector.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/n-art-bpath.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-path-code.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + macros.h \ + message-stack.h \ + message.h \ + pen-context.h \ + prefs-utils.h \ + round.h \ + selection-chemistry.h \ + selection.h \ + snap.h \ + snapped-point.h \ + snapper.h \ + sp-conn-end-pair.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-path.h \ + sp-shape.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +dropper-context.o: \ + dropper-context.cpp \ + color-rgba.h \ + color.h \ + decimal-round.h \ + desktop-handles.h \ + desktop-style.h \ + desktop.h \ + display/canvas-arena.h \ + display/canvas-bpath.h \ + display/curve.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/nr-arena-item.h \ + display/sp-canvas.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + dropper-context.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/n-art-bpath.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-path-code.h \ + libnr/nr-pixblock.h \ + libnr/nr-pixops.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale-translate-ops.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + line-snapper.h \ + macros.h \ + message-context.h \ + message.h \ + object-snapper.h \ + prefs-utils.h \ + round.h \ + selection.h \ + snapped-point.h \ + snapper.h \ + sp-metric.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h + +dyna-draw-context.o: \ + dyna-draw-context.cpp \ + context-fns.h \ + decimal-round.h \ + desktop-affine.h \ + desktop-events.h \ + desktop-handles.h \ + desktop-style.h \ + desktop.h \ + display/bezier-utils.h \ + display/canvas-bpath.h \ + display/curve.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/sp-canvas.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + dyna-draw-context.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/n-art-bpath.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-path-code.h \ + libnr/nr-path.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + macros.h \ + message-context.h \ + message.h \ + round.h \ + selection.h \ + sp-item.h \ + sp-object.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +event-context.o: \ + event-context.cpp \ + attributes.h \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + display/display-forward.h \ + display/sp-canvas.h \ + event-context.h \ + extension/extension-forward.h \ + file.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + gradient-drag.h \ + interface.h \ + knot-enums.h \ + knotholder.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + macros.h \ + message-context.h \ + message.h \ + object-edit.h \ + prefs-utils.h \ + round.h \ + rubberband.h \ + selcue.h \ + selection.h \ + shortcuts.h \ + sp-cursor.h \ + tools-switch.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/list.h \ + xml/node-event-vector.h \ + xml/node.h + +extension/db.o: \ + extension/db.cpp \ + dialogs/input.h \ + document.h \ + extension/db.h \ + extension/effect.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/output.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/helper-forward.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + require-config.h \ + traits/reference.h \ + util/list.h \ + verbs.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +extension/dependency.o: \ + extension/dependency.cpp \ + document.h \ + extension/db.h \ + extension/dependency.h \ + extension/extension-forward.h \ + extension/extension.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + path-prefix.h \ + prefix.h \ + require-config.h \ + traits/reference.h \ + util/list.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +extension/effect.o: \ + extension/effect.cpp \ + decimal-round.h \ + document.h \ + extension/effect.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/implementation/implementation.h \ + extension/prefdialog.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/action.h \ + helper/helper-forward.h \ + inkscape-private.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-object.h \ + libnr/nr-point.h \ + message.h \ + require-config.h \ + round.h \ + traits/reference.h \ + ui/dialog/dialog.h \ + ui/view/view.h \ + util/list.h \ + verbs.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +extension/error-file.o: \ + extension/error-file.cpp \ + dialogs/extensions.h \ + document.h \ + extension/error-file.h \ + extension/extension-forward.h \ + extension/extension.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + prefs-utils.h \ + traits/reference.h \ + ui/previewable.h \ + ui/previewfillable.h \ + ui/widget/button.h \ + ui/widget/panel.h \ + util/list.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +extension/extension.o: \ + extension/extension.cpp \ + decimal-round.h \ + document.h \ + extension/db.h \ + extension/dependency.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/implementation/implementation.h \ + extension/parameter.h \ + extension/timer.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-point.h \ + round.h \ + traits/reference.h \ + util/list.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +extension/implementation/implementation.o: \ + extension/implementation/implementation.cpp \ + decimal-round.h \ + dialogs/input.h \ + document.h \ + extension/effect.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/implementation/implementation.h \ + extension/output.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/helper-forward.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-point.h \ + require-config.h \ + round.h \ + traits/reference.h \ + util/list.h \ + verbs.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +extension/implementation/plugin.o: \ + extension/implementation/plugin.cpp \ + decimal-round.h \ + document.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/implementation/implementation.h \ + extension/implementation/plugin-link.h \ + extension/implementation/plugin.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-point.h \ + path-prefix.h \ + prefix.h \ + require-config.h \ + round.h \ + traits/reference.h \ + util/list.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +extension/implementation/script.o: \ + extension/implementation/script.cpp \ + decimal-round.h \ + desktop-handles.h \ + display/display-forward.h \ + document.h \ + extension/db.h \ + extension/effect.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/implementation/implementation.h \ + extension/implementation/script.h \ + extension/system.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + io/sys.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + line-snapper.h \ + message.h \ + object-snapper.h \ + prefs-utils.h \ + require-config.h \ + round.h \ + selection.h \ + snapped-point.h \ + snapper.h \ + sp-metric.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + traits/reference.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/glib-list-iterators.h \ + util/list.h \ + verbs.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +extension/init.o: \ + extension/init.cpp \ + decimal-round.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stringstream.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + extension/db.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/implementation/implementation.h \ + extension/internal/bluredge.h \ + extension/internal/eps-out.h \ + extension/internal/gdkpixbuf-input.h \ + extension/internal/gimpgrad.h \ + extension/internal/gnome.h \ + extension/internal/grid.h \ + extension/internal/latex-pstricks-out.h \ + extension/internal/latex-pstricks.h \ + extension/internal/pov-out.h \ + extension/internal/ps-out.h \ + extension/internal/ps.h \ + extension/internal/svgz.h \ + extension/internal/win32.h \ + extension/system.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape.h \ + io/sys.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-path.h \ + libnr/nr-point.h \ + path-prefix.h \ + prefix.h \ + prefs-utils.h \ + require-config.h \ + round.h \ + traits/reference.h \ + util/list.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +extension/input.o: \ + extension/input.cpp \ + dialogs/dialog-events.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/helper-forward.h \ + inkscape.h \ + interface.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + macros.h \ + prefs-utils.h \ + require-config.h \ + traits/reference.h \ + util/list.h \ + verbs.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +extension/internal/bluredge.o: \ + extension/internal/bluredge.cpp \ + decimal-round.h \ + desktop.h \ + display/nr-arena-forward.h \ + document.h \ + extension/effect.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/implementation/implementation.h \ + extension/internal/bluredge.h \ + extension/system.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/action.h \ + helper/helper-forward.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + message.h \ + path-chemistry.h \ + prefs-utils.h \ + require-config.h \ + round.h \ + selection.h \ + sp-item.h \ + sp-object.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/glib-list-iterators.h \ + util/list.h \ + verbs.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +extension/internal/eps-out.o: \ + extension/internal/eps-out.cpp \ + decimal-round.h \ + display/nr-arena-forward.h \ + document.h \ + extension/db.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/implementation/implementation.h \ + extension/internal/eps-out.h \ + extension/output.h \ + extension/print.h \ + extension/system.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-path.h \ + libnr/nr-point.h \ + round.h \ + traits/reference.h \ + util/list.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +extension/internal/gdkpixbuf-input.o: \ + extension/internal/gdkpixbuf-input.cpp \ + composite-undo-stack-observer.h \ + decimal-round.h \ + dir-util.h \ + display/nr-arena-forward.h \ + document-private.h \ + document.h \ + enums.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/implementation/implementation.h \ + extension/internal/gdkpixbuf-input.h \ + extension/system.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + prefs-utils.h \ + round.h \ + sp-defs.h \ + sp-item-group.h \ + sp-item.h \ + sp-object.h \ + sp-root.h \ + svg/svg-length.h \ + traits/reference.h \ + undo-stack-observer.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/event-fns.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +extension/internal/gimpgrad.o: \ + extension/internal/gimpgrad.cpp \ + color-rgba.h \ + decimal-round.h \ + document.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/implementation/implementation.h \ + extension/internal/gimpgrad.h \ + extension/system.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + io/sys.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-pixops.h \ + libnr/nr-point.h \ + round.h \ + traits/reference.h \ + util/list.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +extension/internal/grid.o: \ + extension/internal/grid.cpp \ + decimal-round.h \ + desktop.h \ + document.h \ + extension/effect.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/implementation/implementation.h \ + extension/internal/grid.h \ + extension/system.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/helper-forward.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + message.h \ + require-config.h \ + round.h \ + selection.h \ + sp-object.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + verbs.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +extension/internal/latex-pstricks-out.o: \ + extension/internal/latex-pstricks-out.cpp \ + decimal-round.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/nr-arena-item.h \ + display/nr-arena.h \ + document.h \ + extension/db.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/implementation/implementation.h \ + extension/internal/latex-pstricks-out.h \ + extension/print.h \ + extension/system.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libavoid/connector.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-path.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-conn-end-pair.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-paint-server.h \ + sp-path.h \ + sp-shape.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +extension/internal/latex-pstricks.o: \ + extension/internal/latex-pstricks.cpp \ + color.h \ + decimal-round.h \ + display/nr-arena-forward.h \ + document.h \ + dom/dom.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/stringstream.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/implementation/implementation.h \ + extension/internal/latex-pstricks.h \ + extension/print.h \ + extension/system.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + io/sys.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/n-art-bpath.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-path-code.h \ + libnr/nr-path.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + style.h \ + traits/reference.h \ + unit-constants.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +extension/internal/pov-out.o: \ + extension/internal/pov-out.cpp \ + color.h \ + decimal-round.h \ + display/curve.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/implementation/implementation.h \ + extension/internal/pov-out.h \ + extension/system.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape.h \ + io/sys.h \ + libavoid/connector.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/n-art-bpath.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-path-code.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-conn-end-pair.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-path.h \ + sp-shape.h \ + style.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +extension/internal/ps-out.o: \ + extension/internal/ps-out.cpp \ + decimal-round.h \ + display/nr-arena-forward.h \ + document.h \ + extension/db.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/implementation/implementation.h \ + extension/internal/ps-out.h \ + extension/output.h \ + extension/print.h \ + extension/system.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-path.h \ + libnr/nr-point.h \ + round.h \ + traits/reference.h \ + util/list.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +extension/internal/ps.o: \ + extension/internal/ps.cpp \ + color.h \ + decimal-round.h \ + display/canvas-bpath.h \ + display/nr-arena-forward.h \ + display/nr-arena-item.h \ + display/sp-canvas.h \ + document.h \ + dom/dom.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/stringstream.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/implementation/implementation.h \ + extension/internal/ps.h \ + extension/print.h \ + extension/system.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + io/sys.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/n-art-bpath.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-path-code.h \ + libnr/nr-path.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/FontFactory.h \ + libnrtype/font-instance.h \ + libnrtype/font-style-to-pos.h \ + libnrtype/font-style.h \ + libnrtype/nr-type-pos-def.h \ + libnrtype/nr-type-primitives.h \ + libnrtype/nrtype-forward.h \ + livarot/LivarotDefs.h \ + livarot/livarot-forward.h \ + require-config.h \ + round.h \ + sp-gradient-fns.h \ + sp-gradient-spread.h \ + sp-gradient-units.h \ + sp-gradient-vector.h \ + sp-gradient.h \ + sp-item.h \ + sp-linear-gradient-fns.h \ + sp-linear-gradient.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-paint-server.h \ + sp-radial-gradient-fns.h \ + sp-radial-gradient.h \ + style.h \ + svg/svg-length.h \ + traits/reference.h \ + unit-constants.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +extension/internal/svg.o: \ + extension/internal/svg.cpp \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/output.h \ + extension/system.h \ + file.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + sp-object.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +extension/internal/svgz.o: \ + extension/internal/svgz.cpp \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/internal/svgz.h \ + extension/system.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + traits/reference.h \ + util/list.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +extension/internal/win32.o: \ + extension/internal/win32.cpp \ + decimal-round.h \ + display/nr-arena-forward.h \ + display/nr-arena-item.h \ + display/nr-arena.h \ + document.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/implementation/implementation.h \ + extension/internal/win32.h \ + extension/print.h \ + extension/system.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-path.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-object.h \ + sp-paint-server.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +extension/output.o: \ + extension/output.cpp \ + decimal-round.h \ + document.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/implementation/implementation.h \ + extension/output.h \ + extension/prefdialog.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-point.h \ + round.h \ + traits/reference.h \ + ui/dialog/dialog.h \ + util/list.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +extension/parameter.o: \ + extension/parameter.cpp \ + document.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/parameter.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + prefs-utils.h \ + traits/reference.h \ + util/list.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +extension/prefdialog.o: \ + extension/prefdialog.cpp \ + dialogs/dialog-events.h \ + extension/prefdialog.h \ + forward.h \ + ui/dialog/dialog.h \ + ui/stock.h + +extension/print.o: \ + extension/print.cpp \ + decimal-round.h \ + display/nr-arena-forward.h \ + document.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/implementation/implementation.h \ + extension/print.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-path.h \ + libnr/nr-point.h \ + round.h \ + traits/reference.h \ + util/list.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +extension/script/InkscapeBinding.o: \ + extension/script/InkscapeBinding.cpp \ + extension/script/InkscapeBinding.h \ + help.h + +extension/script/InkscapeInterpreter.o: \ + extension/script/InkscapeInterpreter.cpp \ + extension/script/InkscapeInterpreter.h + +extension/script/InkscapePerl.o: \ + extension/script/InkscapePerl.cpp \ + extension/script/InkscapeInterpreter.h \ + extension/script/InkscapePerl.h \ + extension/script/inkscape_perl.pm.h + +extension/script/InkscapePython.o: \ + extension/script/InkscapePython.cpp \ + extension/script/InkscapeInterpreter.h \ + extension/script/InkscapePython.h \ + extension/script/inkscape_py.py.h + +extension/script/InkscapeScript.o: \ + extension/script/InkscapeScript.cpp \ + extension/script/InkscapeInterpreter.h \ + extension/script/InkscapePerl.h \ + extension/script/InkscapePython.h \ + extension/script/InkscapeScript.h + +extension/script/inkscape_perl_wrap.o: \ + extension/script/inkscape_perl_wrap.cpp \ + extension/script/InkscapeBinding.h + +extension/script/inkscape_py_wrap.o: \ + extension/script/inkscape_py_wrap.cpp \ + extension/script/InkscapeBinding.h + +extension/system.o: \ + extension/system.cpp \ + decimal-round.h \ + dialogs/input.h \ + display/nr-arena-forward.h \ + document.h \ + extension/db.h \ + extension/effect.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/implementation/implementation.h \ + extension/implementation/script.h \ + extension/output.h \ + extension/print.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/helper-forward.h \ + interface.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-path.h \ + libnr/nr-point.h \ + require-config.h \ + round.h \ + traits/reference.h \ + util/list.h \ + verbs.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +extension/timer.o: \ + extension/timer.cpp \ + document.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/timer.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + traits/reference.h \ + util/list.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +extract-uri.o: \ + extract-uri.cpp + +file.o: \ + file.cpp \ + application/app-prototype.h \ + application/application.h \ + application/editor.h \ + color.h \ + composite-undo-stack-observer.h \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + dialogs/export.h \ + dialogs/filedialog.h \ + dialogs/input.h \ + dir-util.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/nr-arena-item.h \ + display/nr-arena.h \ + document-private.h \ + document.h \ + dom/dom.h \ + dom/domstring.h \ + dom/uri.h \ + enums.h \ + extension/db.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/output.h \ + extension/print.h \ + extension/system.h \ + file.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + helper/png-write.h \ + inkscape.h \ + interface.h \ + io/sys.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-path.h \ + libnr/nr-pixblock.h \ + libnr/nr-pixops.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + line-snapper.h \ + message-stack.h \ + message.h \ + object-snapper.h \ + path-prefix.h \ + prefix.h \ + prefs-utils.h \ + require-config.h \ + round.h \ + selection-chemistry.h \ + selection.h \ + snapped-point.h \ + snapper.h \ + sp-defs.h \ + sp-item-group.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-metric.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + sp-paint-server.h \ + sp-root.h \ + style.h \ + svg/svg-length.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view-widget.h \ + ui/view/view.h \ + undo-stack-observer.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/event-fns.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +fixes.o: \ + fixes.cpp + +fontsize-expansion.o: \ + fontsize-expansion.cpp \ + decimal-round.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +gc-anchored.o: \ + gc-anchored.cpp \ + gc-anchored.h \ + gc-core.h \ + gc-managed.h + +gc.o: \ + gc.cpp \ + gc-core.h + +geom.o: \ + geom.cpp \ + decimal-round.h \ + geom.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + round.h + +gradient-chemistry.o: \ + gradient-chemistry.cpp \ + bad-uri-exception.h \ + color.h \ + composite-undo-stack-observer.h \ + decimal-round.h \ + desktop-style.h \ + display/nr-arena-forward.h \ + document-private.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + enums.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/Layout-TNG.h \ + round.h \ + sp-defs.h \ + sp-gradient-fns.h \ + sp-gradient-reference.h \ + sp-gradient-spread.h \ + sp-gradient-units.h \ + sp-gradient-vector.h \ + sp-gradient.h \ + sp-item-group.h \ + sp-item.h \ + sp-linear-gradient-fns.h \ + sp-linear-gradient.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-paint-server.h \ + sp-radial-gradient-fns.h \ + sp-radial-gradient.h \ + sp-root.h \ + sp-stop-fns.h \ + sp-stop.h \ + sp-string.h \ + sp-text.h \ + sp-tspan.h \ + style.h \ + svg/svg-length.h \ + text-tag-attributes.h \ + traits/reference.h \ + undo-stack-observer.h \ + uri-references.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + widgets/gradient-vector.h \ + xml/event-fns.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +gradient-context.o: \ + gradient-context.cpp \ + color.h \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + gradient-chemistry.h \ + gradient-context.h \ + gradient-drag.h \ + knot-enums.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + macros.h \ + message-context.h \ + message-stack.h \ + message.h \ + prefs-utils.h \ + round.h \ + selection.h \ + sp-gradient-fns.h \ + sp-gradient-spread.h \ + sp-gradient-units.h \ + sp-gradient-vector.h \ + sp-gradient.h \ + sp-item.h \ + sp-object.h \ + sp-paint-server.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +gradient-drag.o: \ + gradient-drag.cpp \ + color.h \ + decimal-round.h \ + desktop-handles.h \ + desktop-style.h \ + desktop.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/sp-canvas.h \ + display/sp-ctrlline.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + gradient-chemistry.h \ + gradient-drag.h \ + knot-enums.h \ + knot.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + message.h \ + prefs-utils.h \ + round.h \ + selection.h \ + sp-gradient-fns.h \ + sp-gradient-spread.h \ + sp-gradient-units.h \ + sp-gradient-vector.h \ + sp-gradient.h \ + sp-item.h \ + sp-linear-gradient-fns.h \ + sp-linear-gradient.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-paint-server.h \ + sp-radial-gradient-fns.h \ + sp-radial-gradient.h \ + style.h \ + svg/svg-length.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +grid-snapper.o: \ + grid-snapper.cpp \ + decimal-round.h \ + display/canvas-grid.h \ + display/sp-canvas.h \ + forward.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + line-snapper.h \ + object-snapper.h \ + round.h \ + snapped-point.h \ + snapper.h \ + sp-metric.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + version.h + +guide-snapper.o: \ + guide-snapper.cpp \ + decimal-round.h \ + display/display-forward.h \ + forward.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-values.h \ + line-snapper.h \ + object-snapper.h \ + round.h \ + snapped-point.h \ + snapper.h \ + sp-guide-attachment.h \ + sp-guide.h \ + sp-metric.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + version.h + +help.o: \ + help.cpp \ + extension/extension-forward.h \ + file.h \ + forward.h \ + help.h \ + path-prefix.h \ + prefix.h \ + require-config.h \ + ui/dialog/aboutbox.h \ + ui/dialog/dialog.h + +helper/action.o: \ + helper/action.cpp \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/action.h \ + helper/helper-forward.h \ + libnr/nr-object.h + +helper/gnome-utils.o: \ + helper/gnome-utils.cpp + +helper/png-write.o: \ + helper/png-write.cpp \ + helper/png-write.h \ + io/sys.h + +helper/sp-marshal.o: \ + helper/sp-marshal.cpp \ + helper/sp-marshal.h + +helper/stock-items.o: \ + helper/stock-items.cpp \ + bad-uri-exception.h \ + composite-undo-stack-observer.h \ + decimal-round.h \ + desktop-handles.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document-private.h \ + document.h \ + enums.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape.h \ + io/sys.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + path-prefix.h \ + prefix.h \ + require-config.h \ + round.h \ + sp-defs.h \ + sp-gradient-fns.h \ + sp-gradient-spread.h \ + sp-gradient-units.h \ + sp-item-group.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-marker.h \ + sp-object.h \ + sp-paint-server.h \ + sp-pattern.h \ + sp-root.h \ + svg/svg-length.h \ + traits/reference.h \ + undo-stack-observer.h \ + uri-references.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/event-fns.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +helper/unit-menu.o: \ + helper/unit-menu.cpp \ + helper/helper-forward.h \ + helper/sp-marshal.h \ + helper/unit-menu.h \ + helper/units.h \ + sp-metric.h \ + widgets/spw-utilities.h + +helper/units.o: \ + helper/units.cpp \ + helper/units.h \ + sp-metric.h \ + svg/svg-length.h \ + unit-constants.h + +helper/window.o: \ + helper/window.cpp \ + decimal-round.h \ + desktop.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + message.h \ + round.h \ + shortcuts.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h + +inkjar/jar.o: \ + inkjar/jar.cpp \ + inkjar/jar.h + +inkscape-stock.o: \ + inkscape-stock.cpp + +inkscape.o: \ + inkscape.cpp \ + application/app-prototype.h \ + application/application.h \ + application/editor.h \ + debug/event-tracker.h \ + debug/event.h \ + debug/logger.h \ + debug/simple-event.h \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + dialogs/debugdialog.h \ + display/display-forward.h \ + document.h \ + event-context.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/implementation/implementation.h \ + extension/init.h \ + extension/internal/win32.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/sp-marshal.h \ + inkscape-private.h \ + inkscape.h \ + inkscape_version.h \ + io/sys.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + menus-skeleton.h \ + message.h \ + preferences.h \ + prefs-utils.h \ + round.h \ + selection.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/list.h \ + util/shared-c-string-ptr.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +interface.o: \ + interface.cpp \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + dialogs/dialog-events.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + dom/dom.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/stringstream.h \ + extension/effect.h \ + extension/extension-forward.h \ + extension/extension.h \ + file.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/action.h \ + helper/gnome-utils.h \ + helper/helper-forward.h \ + helper/window.h \ + inkscape-private.h \ + inkscape.h \ + interface.h \ + io/base64stream.h \ + io/inkscapestream.h \ + io/sys.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + line-snapper.h \ + message-context.h \ + message.h \ + object-snapper.h \ + object-ui.h \ + path-prefix.h \ + prefix.h \ + prefs-utils.h \ + require-config.h \ + round.h \ + selection-chemistry.h \ + selection.h \ + shortcuts.h \ + snapped-point.h \ + snapper.h \ + sp-item-group.h \ + sp-item.h \ + sp-metric.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + svg-view-widget.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view-widget.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + verbs.h \ + version.h \ + widgets/desktop-widget.h \ + widgets/icon.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +io/base64stream.o: \ + io/base64stream.cpp \ + io/base64stream.h \ + io/inkscapestream.h + +io/ftos.o: \ + io/ftos.cpp \ + io/ftos.h + +io/gzipstream.o: \ + io/gzipstream.cpp \ + io/gzipstream.h \ + io/inkscapestream.h + +io/inkscapestream.o: \ + io/inkscapestream.cpp \ + io/inkscapestream.h + +io/simple-sax.o: \ + io/simple-sax.cpp \ + io/simple-sax.h + +io/stringstream.o: \ + io/stringstream.cpp \ + dom/dom.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/stringstream.h + +io/sys.o: \ + io/sys.cpp \ + decimal-round.h \ + document.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/implementation/implementation.h \ + extension/internal/win32.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + io/sys.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-point.h \ + prefs-utils.h \ + round.h \ + traits/reference.h \ + util/list.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +io/uristream.o: \ + io/uristream.cpp \ + dom/dom.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/uri.h \ + dom/uristream.h + +io/xsltstream.o: \ + io/xsltstream.cpp \ + dom/dom.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/stringstream.h \ + io/inkscapestream.h \ + io/xsltstream.h + +knot.o: \ + knot.cpp \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + display/display-forward.h \ + display/sodipodi-ctrl.h \ + display/sp-canvas.h \ + document.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/sp-marshal.h \ + knot-enums.h \ + knot.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + message-context.h \ + message-stack.h \ + message.h \ + prefs-utils.h \ + round.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h + +knotholder.o: \ + knotholder.cpp \ + decimal-round.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + knot-enums.h \ + knot-holder-entity.h \ + knot.h \ + knotholder.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-div.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-shape.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + version.h + +layer-fns.o: \ + layer-fns.cpp \ + algorithms/find-last-if.h \ + decimal-round.h \ + display/nr-arena-forward.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-item-group.h \ + sp-item.h \ + sp-object.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +libavoid/connector.o: \ + libavoid/connector.cpp \ + libavoid/connector.h \ + libavoid/debug.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/graph.h \ + libavoid/makepath.h \ + libavoid/shape.h \ + libavoid/vertices.h \ + libavoid/visibility.h + +libavoid/geometry.o: \ + libavoid/geometry.cpp \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/graph.h \ + libavoid/polyutil.h \ + libavoid/shape.h \ + libavoid/vertices.h + +libavoid/graph.o: \ + libavoid/graph.cpp \ + extension/extension-forward.h \ + extension/timer.h \ + libavoid/connector.h \ + libavoid/debug.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/graph.h \ + libavoid/polyutil.h \ + libavoid/shape.h \ + libavoid/vertices.h + +libavoid/incremental.o: \ + libavoid/incremental.cpp \ + libavoid/connector.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/graph.h \ + libavoid/shape.h \ + libavoid/vertices.h \ + libavoid/visibility.h + +libavoid/makepath.o: \ + libavoid/makepath.cpp \ + libavoid/connector.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/graph.h \ + libavoid/shape.h \ + libavoid/vertices.h + +libavoid/polyutil.o: \ + libavoid/polyutil.cpp \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h + +libavoid/shape.o: \ + libavoid/shape.cpp \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/graph.h \ + libavoid/polyutil.h \ + libavoid/shape.h \ + libavoid/vertices.h + +libavoid/static.o: \ + libavoid/static.cpp \ + libavoid/connector.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libavoid/vertices.h \ + libavoid/visibility.h + +libavoid/timer.o: \ + libavoid/timer.cpp \ + document.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/timer.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + traits/reference.h \ + util/list.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +libavoid/vertices.o: \ + libavoid/vertices.cpp \ + libavoid/debug.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/graph.h \ + libavoid/vertices.h + +libavoid/visibility.o: \ + libavoid/visibility.cpp \ + libavoid/debug.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/graph.h \ + libavoid/shape.h \ + libavoid/vertices.h \ + libavoid/visibility.h + +libcroco/cr-additional-sel.o: \ + libcroco/cr-additional-sel.c \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-string.h \ + libcroco/cr-utils.h + +libcroco/cr-attr-sel.o: \ + libcroco/cr-attr-sel.c \ + libcroco/cr-attr-sel.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-string.h \ + libcroco/cr-utils.h + +libcroco/cr-cascade.o: \ + libcroco/cr-cascade.c \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h + +libcroco/cr-declaration.o: \ + libcroco/cr-declaration.c \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-declaration.h \ + libcroco/cr-doc-handler.h \ + libcroco/cr-input.h \ + libcroco/cr-num.h \ + libcroco/cr-parser.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-tknzr.h \ + libcroco/cr-token.h \ + libcroco/cr-utils.h + +libcroco/cr-doc-handler.o: \ + libcroco/cr-doc-handler.c \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-declaration.h \ + libcroco/cr-doc-handler.h \ + libcroco/cr-input.h \ + libcroco/cr-num.h \ + libcroco/cr-parser.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-tknzr.h \ + libcroco/cr-token.h \ + libcroco/cr-utils.h + +libcroco/cr-enc-handler.o: \ + libcroco/cr-enc-handler.c \ + libcroco/cr-enc-handler.h \ + libcroco/cr-utils.h + +libcroco/cr-fonts.o: \ + libcroco/cr-fonts.c \ + libcroco/cr-fonts.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-utils.h + +libcroco/cr-input.o: \ + libcroco/cr-input.c \ + libcroco/cr-enc-handler.h \ + libcroco/cr-input.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-utils.h + +libcroco/cr-libxml-node-iface.o: \ + libcroco/cr-libxml-node-iface.c \ + libcroco/cr-libxml-node-iface.h \ + libcroco/cr-node-iface.h + +libcroco/cr-num.o: \ + libcroco/cr-num.c \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-utils.h + +libcroco/cr-om-parser.o: \ + libcroco/cr-om-parser.c \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-doc-handler.h \ + libcroco/cr-input.h \ + libcroco/cr-num.h \ + libcroco/cr-om-parser.h \ + libcroco/cr-parser.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-tknzr.h \ + libcroco/cr-token.h \ + libcroco/cr-utils.h + +libcroco/cr-parser.o: \ + libcroco/cr-parser.c \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-declaration.h \ + libcroco/cr-doc-handler.h \ + libcroco/cr-input.h \ + libcroco/cr-num.h \ + libcroco/cr-parser.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-tknzr.h \ + libcroco/cr-token.h \ + libcroco/cr-utils.h + +libcroco/cr-parsing-location.o: \ + libcroco/cr-parsing-location.c \ + libcroco/cr-parsing-location.h \ + libcroco/cr-utils.h + +libcroco/cr-prop-list.o: \ + libcroco/cr-prop-list.c \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-prop-list.h \ + libcroco/cr-rgb.h \ + libcroco/cr-string.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h + +libcroco/cr-pseudo.o: \ + libcroco/cr-pseudo.c \ + libcroco/cr-attr-sel.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-string.h \ + libcroco/cr-utils.h + +libcroco/cr-rgb.o: \ + libcroco/cr-rgb.c \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-declaration.h \ + libcroco/cr-doc-handler.h \ + libcroco/cr-input.h \ + libcroco/cr-num.h \ + libcroco/cr-parser.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-tknzr.h \ + libcroco/cr-token.h \ + libcroco/cr-utils.h + +libcroco/cr-sel-eng.o: \ + libcroco/cr-sel-eng.c \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-fonts.h \ + libcroco/cr-node-iface.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-prop-list.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-sel-eng.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-style.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h + +libcroco/cr-selector.o: \ + libcroco/cr-selector.c \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-declaration.h \ + libcroco/cr-doc-handler.h \ + libcroco/cr-input.h \ + libcroco/cr-num.h \ + libcroco/cr-parser.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-tknzr.h \ + libcroco/cr-token.h \ + libcroco/cr-utils.h + +libcroco/cr-simple-sel.o: \ + libcroco/cr-simple-sel.c \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-string.h \ + libcroco/cr-utils.h + +libcroco/cr-statement.o: \ + libcroco/cr-statement.c \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-declaration.h \ + libcroco/cr-doc-handler.h \ + libcroco/cr-input.h \ + libcroco/cr-num.h \ + libcroco/cr-parser.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-tknzr.h \ + libcroco/cr-token.h \ + libcroco/cr-utils.h + +libcroco/cr-string.o: \ + libcroco/cr-string.c \ + libcroco/cr-parsing-location.h \ + libcroco/cr-string.h \ + libcroco/cr-utils.h + +libcroco/cr-style.o: \ + libcroco/cr-style.c \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-declaration.h \ + libcroco/cr-fonts.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-style.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h + +libcroco/cr-stylesheet.o: \ + libcroco/cr-stylesheet.c \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h + +libcroco/cr-term.o: \ + libcroco/cr-term.c \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-declaration.h \ + libcroco/cr-doc-handler.h \ + libcroco/cr-input.h \ + libcroco/cr-num.h \ + libcroco/cr-parser.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-tknzr.h \ + libcroco/cr-token.h \ + libcroco/cr-utils.h + +libcroco/cr-tknzr.o: \ + libcroco/cr-tknzr.c \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-declaration.h \ + libcroco/cr-doc-handler.h \ + libcroco/cr-input.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-tknzr.h \ + libcroco/cr-token.h \ + libcroco/cr-utils.h + +libcroco/cr-token.o: \ + libcroco/cr-token.c \ + libcroco/cr-input.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-rgb.h \ + libcroco/cr-string.h \ + libcroco/cr-token.h \ + libcroco/cr-utils.h + +libcroco/cr-utils.o: \ + libcroco/cr-utils.c \ + libcroco/cr-parsing-location.h \ + libcroco/cr-string.h \ + libcroco/cr-utils.h + +libnr/nr-blit.o: \ + libnr/nr-blit.cpp \ + decimal-round.h \ + libnr/nr-blit.h \ + libnr/nr-compose.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock.h \ + libnr/nr-pixops.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +libnr/nr-compose-transform.o: \ + libnr/nr-compose-transform.cpp \ + decimal-round.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-pixops.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +libnr/nr-compose.o: \ + libnr/nr-compose.cpp \ + libnr/nr-pixops.h + +libnr/nr-gradient.o: \ + libnr/nr-gradient.cpp \ + decimal-round.h \ + libnr/nr-blit.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-gradient.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock-pixel.h \ + libnr/nr-pixblock.h \ + libnr/nr-pixops.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-render.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +libnr/nr-matrix-div.o: \ + libnr/nr-matrix-div.cpp \ + decimal-round.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +libnr/nr-matrix-fns.o: \ + libnr/nr-matrix-fns.cpp \ + decimal-round.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +libnr/nr-matrix-rotate-ops.o: \ + libnr/nr-matrix-rotate-ops.cpp \ + decimal-round.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +libnr/nr-matrix-scale-ops.o: \ + libnr/nr-matrix-scale-ops.cpp \ + decimal-round.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +libnr/nr-matrix-translate-ops.o: \ + libnr/nr-matrix-translate-ops.cpp \ + decimal-round.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +libnr/nr-matrix.o: \ + libnr/nr-matrix.cpp \ + decimal-round.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +libnr/nr-object.o: \ + libnr/nr-object.cpp \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libnr/nr-macros.h \ + libnr/nr-object.h + +libnr/nr-path.o: \ + libnr/nr-path.cpp \ + decimal-round.h \ + libnr/n-art-bpath.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-path-code.h \ + libnr/nr-path.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +libnr/nr-pixblock-line.o: \ + libnr/nr-pixblock-line.cpp \ + decimal-round.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock-pixel.h \ + libnr/nr-pixblock.h \ + libnr/nr-pixops.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +libnr/nr-pixblock-pattern.o: \ + libnr/nr-pixblock-pattern.cpp \ + decimal-round.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock-pattern.h \ + libnr/nr-pixblock.h \ + libnr/nr-pixops.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +libnr/nr-pixblock-pixel.o: \ + libnr/nr-pixblock-pixel.cpp \ + decimal-round.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock-pixel.h \ + libnr/nr-pixblock.h \ + libnr/nr-pixops.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +libnr/nr-pixblock.o: \ + libnr/nr-pixblock.cpp \ + decimal-round.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +libnr/nr-point-fns.o: \ + libnr/nr-point-fns.cpp \ + decimal-round.h \ + isnan.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-macros.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + round.h + +libnr/nr-rect-l.o: \ + libnr/nr-rect-l.cpp \ + decimal-round.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +libnr/nr-rect.o: \ + libnr/nr-rect.cpp \ + decimal-round.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +libnr/nr-rotate-fns.o: \ + libnr/nr-rotate-fns.cpp \ + decimal-round.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-macros.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rotate-fns.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + round.h + +libnr/nr-rotate-matrix-ops.o: \ + libnr/nr-rotate-matrix-ops.cpp \ + decimal-round.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +libnr/nr-scale-matrix-ops.o: \ + libnr/nr-scale-matrix-ops.cpp \ + decimal-round.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +libnr/nr-scale-translate-ops.o: \ + libnr/nr-scale-translate-ops.cpp \ + decimal-round.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-translate-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +libnr/nr-svp-render.o: \ + libnr/nr-svp-render.cpp \ + decimal-round.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-svp-render.h \ + libnr/nr-svp.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +libnr/nr-svp.o: \ + libnr/nr-svp.cpp \ + decimal-round.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-svp-private.h \ + libnr/nr-svp.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +libnr/nr-translate-matrix-ops.o: \ + libnr/nr-translate-matrix-ops.cpp \ + decimal-round.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +libnr/nr-translate-rotate-ops.o: \ + libnr/nr-translate-rotate-ops.cpp \ + decimal-round.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-translate-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +libnr/nr-translate-scale-ops.o: \ + libnr/nr-translate-scale-ops.cpp \ + decimal-round.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +libnr/nr-types.o: \ + libnr/nr-types.cpp \ + decimal-round.h \ + isnan.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-types.h \ + libnr/nr-values.h \ + round.h + +libnr/nr-values.o: \ + libnr/nr-values.cpp \ + decimal-round.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +libnr/testnr.o: \ + libnr/testnr.cpp \ + decimal-round.h \ + libnr/nr-blit.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +libnrtype/FontFactory.o: \ + libnrtype/FontFactory.cpp \ + decimal-round.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/FontFactory.h \ + libnrtype/font-instance.h \ + libnrtype/font-style.h \ + libnrtype/nr-type-pos-def.h \ + libnrtype/nr-type-primitives.h \ + libnrtype/nrtype-forward.h \ + livarot/LivarotDefs.h \ + livarot/livarot-forward.h \ + require-config.h \ + round.h + +libnrtype/FontInstance.o: \ + libnrtype/FontInstance.cpp \ + decimal-round.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/FontFactory.h \ + libnrtype/RasterFont.h \ + libnrtype/font-glyph.h \ + libnrtype/font-instance.h \ + libnrtype/font-style.h \ + libnrtype/nr-type-pos-def.h \ + libnrtype/nr-type-primitives.h \ + libnrtype/nrtype-forward.h \ + livarot/LivarotDefs.h \ + livarot/Path.h \ + livarot/livarot-forward.h \ + require-config.h \ + round.h + +libnrtype/Layout-TNG-Compute.o: \ + libnrtype/Layout-TNG-Compute.cpp \ + color.h \ + decimal-round.h \ + forward.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/FontFactory.h \ + libnrtype/Layout-TNG-Scanline-Maker.h \ + libnrtype/Layout-TNG.h \ + libnrtype/font-instance.h \ + libnrtype/font-style.h \ + libnrtype/nr-type-pos-def.h \ + libnrtype/nr-type-primitives.h \ + libnrtype/nrtype-forward.h \ + livarot/LivarotDefs.h \ + livarot/livarot-forward.h \ + require-config.h \ + round.h \ + sp-marker-loc.h \ + sp-object.h \ + style.h \ + svg/svg-length.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + version.h + +libnrtype/Layout-TNG-Input.o: \ + libnrtype/Layout-TNG-Input.cpp \ + color.h \ + decimal-round.h \ + forward.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/FontFactory.h \ + libnrtype/Layout-TNG.h \ + libnrtype/nr-type-pos-def.h \ + libnrtype/nr-type-primitives.h \ + libnrtype/nrtype-forward.h \ + round.h \ + sp-marker-loc.h \ + sp-object.h \ + style.h \ + svg/svg-length.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + version.h + +libnrtype/Layout-TNG-OutIter.o: \ + libnrtype/Layout-TNG-OutIter.cpp \ + color.h \ + decimal-round.h \ + forward.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix-translate-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate-rotate-ops.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/FontFactory.h \ + libnrtype/Layout-TNG.h \ + libnrtype/font-instance.h \ + libnrtype/font-style.h \ + libnrtype/nr-type-pos-def.h \ + libnrtype/nr-type-primitives.h \ + libnrtype/nrtype-forward.h \ + livarot/LivarotDefs.h \ + livarot/Path.h \ + livarot/livarot-forward.h \ + require-config.h \ + round.h \ + sp-marker-loc.h \ + style.h \ + svg/svg-length.h + +libnrtype/Layout-TNG-Output.o: \ + libnrtype/Layout-TNG-Output.cpp \ + color.h \ + decimal-round.h \ + display/curve.h \ + display/nr-arena-forward.h \ + display/nr-arena-glyphs.h \ + display/nr-arena-group.h \ + display/nr-arena-item.h \ + document.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/print.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-path.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale-matrix-ops.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/FontFactory.h \ + libnrtype/Layout-TNG.h \ + libnrtype/font-instance.h \ + libnrtype/font-style.h \ + libnrtype/nr-type-pos-def.h \ + libnrtype/nr-type-primitives.h \ + libnrtype/nrtype-forward.h \ + livarot/LivarotDefs.h \ + livarot/Path.h \ + livarot/livarot-forward.h \ + require-config.h \ + round.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-paint-server.h \ + style.h \ + svg/svg-length.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +libnrtype/Layout-TNG-Scanline-Makers.o: \ + libnrtype/Layout-TNG-Scanline-Makers.cpp \ + decimal-round.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/Layout-TNG-Scanline-Maker.h \ + libnrtype/Layout-TNG.h \ + livarot/LivarotDefs.h \ + livarot/float-line.h \ + round.h + +libnrtype/Layout-TNG.o: \ + libnrtype/Layout-TNG.cpp \ + decimal-round.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/Layout-TNG.h \ + round.h + +libnrtype/RasterFont.o: \ + libnrtype/RasterFont.cpp \ + decimal-round.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/FontFactory.h \ + libnrtype/RasterFont.h \ + libnrtype/font-instance.h \ + libnrtype/font-style.h \ + libnrtype/nr-type-pos-def.h \ + libnrtype/nr-type-primitives.h \ + libnrtype/nrtype-forward.h \ + libnrtype/raster-glyph.h \ + libnrtype/raster-position.h \ + livarot/LivarotDefs.h \ + livarot/Path.h \ + livarot/float-line.h \ + livarot/int-line.h \ + livarot/livarot-forward.h \ + require-config.h \ + round.h + +libnrtype/TextWrapper.o: \ + libnrtype/TextWrapper.cpp \ + decimal-round.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/FontFactory.h \ + libnrtype/TextWrapper.h \ + libnrtype/boundary-type.h \ + libnrtype/font-instance.h \ + libnrtype/font-style.h \ + libnrtype/nr-type-pos-def.h \ + libnrtype/nr-type-primitives.h \ + libnrtype/nrtype-forward.h \ + libnrtype/one-box.h \ + libnrtype/one-glyph.h \ + libnrtype/one-para.h \ + libnrtype/text-boundary.h \ + livarot/LivarotDefs.h \ + livarot/livarot-forward.h \ + require-config.h \ + round.h + +libnrtype/font-style-to-pos.o: \ + libnrtype/font-style-to-pos.cpp \ + color.h \ + forward.h \ + libnrtype/font-style-to-pos.h \ + libnrtype/nr-type-pos-def.h \ + sp-marker-loc.h \ + style.h + +libnrtype/nr-type-pos-def.o: \ + libnrtype/nr-type-pos-def.cpp \ + libnrtype/nr-type-pos-def.h + +libnrtype/nr-type-primitives.o: \ + libnrtype/nr-type-primitives.cpp \ + libnr/nr-macros.h \ + libnrtype/nr-type-primitives.h + +line-snapper.o: \ + line-snapper.cpp \ + decimal-round.h \ + geom.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-values.h \ + line-snapper.h \ + round.h \ + snapped-point.h \ + snapper.h + +livarot/AVL.o: \ + livarot/AVL.cpp \ + livarot/AVL.h \ + livarot/LivarotDefs.h + +livarot/AlphaLigne.o: \ + livarot/AlphaLigne.cpp \ + livarot/AlphaLigne.h \ + livarot/LivarotDefs.h + +livarot/BitLigne.o: \ + livarot/BitLigne.cpp \ + livarot/BitLigne.h \ + livarot/LivarotDefs.h + +livarot/MySeg.o: \ + livarot/MySeg.cpp \ + livarot/MyMath.h \ + livarot/MySeg.h + +livarot/Path.o: \ + livarot/Path.cpp \ + decimal-round.h \ + dom/dom.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/stringstream.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + livarot/LivarotDefs.h \ + livarot/Path.h \ + livarot/livarot-forward.h \ + livarot/path-description.h \ + round.h + +livarot/PathConversion.o: \ + livarot/PathConversion.cpp \ + decimal-round.h \ + dom/dom.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/stringstream.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale-ops.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + livarot/LivarotDefs.h \ + livarot/Path.h \ + livarot/livarot-forward.h \ + livarot/path-description.h \ + round.h + +livarot/PathCutting.o: \ + livarot/PathCutting.cpp \ + decimal-round.h \ + display/canvas-bpath.h \ + display/sp-canvas.h \ + dom/dom.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/stringstream.h \ + libnr/n-art-bpath.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-path-code.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + livarot/LivarotDefs.h \ + livarot/Path.h \ + livarot/livarot-forward.h \ + livarot/path-description.h \ + round.h + +livarot/PathOutline.o: \ + livarot/PathOutline.cpp \ + decimal-round.h \ + dom/dom.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/stringstream.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-macros.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + livarot/LivarotDefs.h \ + livarot/Path.h \ + livarot/livarot-forward.h \ + livarot/path-description.h \ + round.h + +livarot/PathSimplify.o: \ + livarot/PathSimplify.cpp \ + decimal-round.h \ + dom/dom.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/stringstream.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + livarot/LivarotDefs.h \ + livarot/Path.h \ + livarot/livarot-forward.h \ + livarot/path-description.h \ + round.h + +livarot/PathStroke.o: \ + livarot/PathStroke.cpp \ + decimal-round.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-macros.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + livarot/LivarotDefs.h \ + livarot/Path.h \ + livarot/livarot-forward.h \ + round.h + +livarot/Shape.o: \ + livarot/Shape.cpp \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/graph.h \ + libavoid/polyutil.h \ + libavoid/shape.h \ + libavoid/vertices.h + +livarot/ShapeDraw.o: \ + livarot/ShapeDraw.cpp \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h + +livarot/ShapeMisc.o: \ + livarot/ShapeMisc.cpp \ + decimal-round.h \ + dom/dom.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/stringstream.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-macros.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + livarot/LivarotDefs.h \ + livarot/Path.h \ + livarot/livarot-forward.h \ + livarot/path-description.h \ + round.h + +livarot/ShapeRaster.o: \ + livarot/ShapeRaster.cpp \ + decimal-round.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + livarot/AVL.h \ + livarot/AlphaLigne.h \ + livarot/BitLigne.h \ + livarot/LivarotDefs.h \ + livarot/float-line.h \ + livarot/sweep-event-queue.h \ + livarot/sweep-tree-list.h \ + livarot/sweep-tree.h \ + round.h + +livarot/ShapeSweep.o: \ + livarot/ShapeSweep.cpp \ + decimal-round.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + livarot/AVL.h \ + livarot/LivarotDefs.h \ + livarot/sweep-event-queue.h \ + livarot/sweep-tree-list.h \ + livarot/sweep-tree.h \ + round.h + +livarot/float-line.o: \ + livarot/float-line.cpp \ + livarot/LivarotDefs.h \ + livarot/float-line.h \ + livarot/int-line.h \ + livarot/livarot-forward.h + +livarot/int-line.o: \ + livarot/int-line.cpp \ + livarot/BitLigne.h \ + livarot/LivarotDefs.h \ + livarot/float-line.h \ + livarot/int-line.h \ + livarot/livarot-forward.h + +livarot/path-description.o: \ + livarot/path-description.cpp \ + decimal-round.h \ + dom/dom.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/stringstream.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + livarot/path-description.h \ + round.h + +livarot/sweep-event.o: \ + livarot/sweep-event.cpp \ + decimal-round.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-point.h \ + livarot/AVL.h \ + livarot/LivarotDefs.h \ + livarot/sweep-event-queue.h \ + livarot/sweep-event.h \ + livarot/sweep-tree.h \ + round.h + +livarot/sweep-tree-list.o: \ + livarot/sweep-tree-list.cpp \ + decimal-round.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-point.h \ + livarot/AVL.h \ + livarot/LivarotDefs.h \ + livarot/sweep-tree-list.h \ + livarot/sweep-tree.h \ + round.h + +livarot/sweep-tree.o: \ + livarot/sweep-tree.cpp \ + decimal-round.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + livarot/AVL.h \ + livarot/LivarotDefs.h \ + livarot/sweep-event-queue.h \ + livarot/sweep-event.h \ + livarot/sweep-tree-list.h \ + livarot/sweep-tree.h \ + round.h + +marker-status.o: \ + marker-status.cpp + +media.o: \ + media.cpp \ + media.h + +message-context.o: \ + message-context.cpp \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + message-context.h \ + message-stack.h \ + message.h + +message-stack.o: \ + message-stack.cpp \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + message-stack.h \ + message.h + +mod360.o: \ + mod360.cpp + +node-context.o: \ + node-context.cpp \ + color.h \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/sp-canvas-util.h \ + display/sp-canvas.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + knot-enums.h \ + knotholder.h \ + libavoid/connector.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-path-code.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + livarot/LivarotDefs.h \ + livarot/Path.h \ + livarot/livarot-forward.h \ + macros.h \ + message-context.h \ + message.h \ + node-context.h \ + nodepath.h \ + object-edit.h \ + path-chemistry.h \ + prefs-utils.h \ + round.h \ + rubberband.h \ + selection.h \ + sp-conn-end-pair.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-path.h \ + sp-shape.h \ + splivarot.h \ + style.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node-event-vector.h \ + xml/node.h + +nodepath.o: \ + nodepath.cpp \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + display/curve.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/sodipodi-ctrl.h \ + display/sp-canvas.h \ + display/sp-ctrlline.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + helper/stlport.h \ + helper/units.h \ + inkscape.h \ + knot-enums.h \ + knot.h \ + libavoid/connector.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/n-art-bpath.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-path-code.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + line-snapper.h \ + livarot/LivarotDefs.h \ + livarot/Path.h \ + livarot/livarot-forward.h \ + message-context.h \ + message-stack.h \ + message.h \ + node-context.h \ + nodepath.h \ + object-snapper.h \ + prefs-utils.h \ + round.h \ + selection-chemistry.h \ + selection.h \ + snap.h \ + snapped-point.h \ + snapper.h \ + sp-conn-end-pair.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-metric.h \ + sp-metrics.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + sp-path.h \ + sp-shape.h \ + splivarot.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +object-edit.o: \ + object-edit.cpp \ + bad-uri-exception.h \ + color.h \ + decimal-round.h \ + desktop-affine.h \ + desktop.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape.h \ + isnan.h \ + knot-enums.h \ + knotholder.h \ + libavoid/connector.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale-ops.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/Layout-TNG.h \ + message.h \ + object-edit.h \ + prefs-utils.h \ + round.h \ + snap.h \ + snapped-point.h \ + snapper.h \ + sp-conn-end-pair.h \ + sp-ellipse.h \ + sp-flowtext.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-offset.h \ + sp-paint-server.h \ + sp-path.h \ + sp-pattern.h \ + sp-polygon.h \ + sp-rect.h \ + sp-shape.h \ + sp-spiral.h \ + sp-star.h \ + style.h \ + svg/svg-length.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + uri-references.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +object-hierarchy.o: \ + object-hierarchy.cpp \ + forward.h \ + object-hierarchy.h \ + sp-object.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + version.h + +object-snapper.o: \ + object-snapper.cpp \ + decimal-round.h \ + desktop.h \ + display/curve.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + inkscape.h \ + libavoid/connector.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/n-art-bpath.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-path-code.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-ops.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + line-snapper.h \ + livarot/LivarotDefs.h \ + livarot/Path.h \ + livarot/livarot-forward.h \ + message.h \ + object-snapper.h \ + round.h \ + snapped-point.h \ + snapper.h \ + sp-conn-end-pair.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-metric.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + sp-path.h \ + sp-shape.h \ + splivarot.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + version.h + +object-ui.o: \ + object-ui.cpp \ + decimal-round.h \ + desktop-handles.h \ + dialogs/item-properties.h \ + dialogs/object-attributes.h \ + dialogs/object-properties.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libavoid/connector.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + object-ui.h \ + round.h \ + selection.h \ + sp-anchor.h \ + sp-conn-end-pair.h \ + sp-image.h \ + sp-item-group.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-path.h \ + sp-shape.h \ + svg/svg-length.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +path-chemistry.o: \ + path-chemistry.cpp \ + color.h \ + decimal-round.h \ + desktop-handles.h \ + display/curve.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape.h \ + libavoid/connector.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-path.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/Layout-TNG.h \ + message-stack.h \ + message.h \ + round.h \ + selection.h \ + sp-conn-end-pair.h \ + sp-flowtext.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-path.h \ + sp-shape.h \ + sp-string.h \ + sp-text.h \ + style.h \ + svg/svg-length.h \ + text-editing.h \ + text-tag-attributes.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +pen-context.o: \ + pen-context.cpp \ + context-fns.h \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + display/canvas-bpath.h \ + display/curve.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/sodipodi-ctrl.h \ + display/sp-canvas.h \ + display/sp-ctrlline.h \ + draw-anchor.h \ + draw-context.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + helper/units.h \ + libavoid/connector.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libnr/n-art-bpath.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-path-code.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + line-snapper.h \ + macros.h \ + message-context.h \ + message-stack.h \ + message.h \ + object-snapper.h \ + pen-context.h \ + prefs-utils.h \ + round.h \ + selection.h \ + snapped-point.h \ + snapper.h \ + sp-conn-end-pair.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-metric.h \ + sp-metrics.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + sp-path.h \ + sp-shape.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h + +pencil-context.o: \ + pencil-context.cpp \ + context-fns.h \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + display/bezier-utils.h \ + display/canvas-bpath.h \ + display/curve.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/sp-canvas.h \ + draw-anchor.h \ + draw-context.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libavoid/connector.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libnr/in-svg-plane.h \ + libnr/n-art-bpath.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-path-code.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + message-context.h \ + message-stack.h \ + message.h \ + modifier-fns.h \ + pencil-context.h \ + prefs-utils.h \ + round.h \ + selection.h \ + snap.h \ + snapped-point.h \ + snapper.h \ + sp-conn-end-pair.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-path.h \ + sp-shape.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h + +preferences.o: \ + preferences.cpp \ + dialogs/input.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape.h \ + inkscape_version.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + preferences-skeleton.h \ + preferences.h \ + traits/reference.h \ + util/list.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +prefix.o: \ + prefix.cpp \ + prefix.h + +prefs-utils.o: \ + prefs-utils.cpp \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + traits/reference.h \ + util/list.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +print.o: \ + print.cpp \ + decimal-round.h \ + display/nr-arena-forward.h \ + document.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/implementation/implementation.h \ + extension/print.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-path.h \ + libnr/nr-point.h \ + round.h \ + traits/reference.h \ + util/list.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +rect-context.o: \ + rect-context.cpp \ + context-fns.h \ + decimal-round.h \ + desktop-handles.h \ + desktop-style.h \ + desktop.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/sp-canvas.h \ + document.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + knot-enums.h \ + knotholder.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + line-snapper.h \ + macros.h \ + message-context.h \ + message.h \ + object-edit.h \ + object-snapper.h \ + prefs-utils.h \ + rect-context.h \ + round.h \ + selection.h \ + snap.h \ + snapped-point.h \ + snapper.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-metric.h \ + sp-metrics.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + sp-rect.h \ + sp-shape.h \ + svg/svg-length.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node-event-vector.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +registrytool.o: \ + registrytool.cpp \ + registrytool.h + +removeoverlap/block.o: \ + removeoverlap/block.cpp \ + removeoverlap/block.h \ + removeoverlap/blocks.h \ + removeoverlap/constraint.h \ + removeoverlap/pairingheap/PairingHeap.cpp \ + removeoverlap/pairingheap/PairingHeap.h \ + removeoverlap/pairingheap/dsexceptions.h \ + removeoverlap/variable.h + +removeoverlap/blocks.o: \ + removeoverlap/blocks.cpp \ + removeoverlap/block.h \ + removeoverlap/blocks.h \ + removeoverlap/constraint.h \ + removeoverlap/variable.h + +removeoverlap/constraint.o: \ + removeoverlap/constraint.cpp \ + removeoverlap/constraint.h \ + removeoverlap/variable.h + +removeoverlap/generate-constraints.o: \ + removeoverlap/generate-constraints.cpp \ + isnan.h \ + removeoverlap/constraint.h \ + removeoverlap/generate-constraints.h \ + removeoverlap/variable.h + +removeoverlap/pairingheap/PairingHeap.o: \ + removeoverlap/pairingheap/PairingHeap.cpp \ + removeoverlap/pairingheap/PairingHeap.cpp \ + removeoverlap/pairingheap/PairingHeap.h \ + removeoverlap/pairingheap/dsexceptions.h + +removeoverlap/remove_rectangle_overlap.o: \ + removeoverlap/remove_rectangle_overlap.cpp \ + removeoverlap/constraint.h \ + removeoverlap/generate-constraints.h \ + removeoverlap/solve_VPSC.h \ + removeoverlap/variable.h + +removeoverlap/removeoverlap.o: \ + removeoverlap/removeoverlap.cpp \ + decimal-round.h \ + display/nr-arena-forward.h \ + forward.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + removeoverlap/generate-constraints.h \ + removeoverlap/remove_rectangle_overlap.h \ + round.h \ + sp-item-transform.h \ + sp-item.h \ + sp-object.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/glib-list-iterators.h \ + version.h + +removeoverlap/solve_VPSC.o: \ + removeoverlap/solve_VPSC.cpp \ + removeoverlap/block.h \ + removeoverlap/blocks.h \ + removeoverlap/constraint.h \ + removeoverlap/solve_VPSC.h \ + removeoverlap/variable.h + +removeoverlap/variable.o: \ + removeoverlap/variable.cpp \ + removeoverlap/block.h \ + removeoverlap/variable.h + +rubberband.o: \ + rubberband.cpp \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + display/display-forward.h \ + display/sodipodi-ctrlrect.h \ + display/sp-canvas.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + message.h \ + round.h \ + rubberband.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h + +satisfied-guide-cns.o: \ + satisfied-guide-cns.cpp \ + approx-equal.h \ + decimal-round.h \ + desktop-handles.h \ + display/display-forward.h \ + forward.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + line-snapper.h \ + object-snapper.h \ + round.h \ + snapped-point.h \ + snapper.h \ + sp-guide-attachment.h \ + sp-guide-constraint.h \ + sp-guide.h \ + sp-metric.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + version.h + +selcue.o: \ + selcue.cpp \ + decimal-round.h \ + desktop-handles.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/sodipodi-ctrl.h \ + display/sodipodi-ctrlrect.h \ + display/sp-canvas-util.h \ + display/sp-canvas.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/Layout-TNG.h \ + prefs-utils.h \ + round.h \ + selcue.h \ + selection.h \ + sp-flowtext.h \ + sp-item.h \ + sp-object.h \ + sp-string.h \ + sp-text.h \ + svg/svg-length.h \ + text-editing.h \ + text-tag-attributes.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h + +select-context.o: \ + select-context.cpp \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/sodipodi-ctrl.h \ + display/sp-canvas.h \ + document.h \ + enums.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + macros.h \ + message-context.h \ + message-stack.h \ + message.h \ + prefs-utils.h \ + round.h \ + rubberband.h \ + selcue.h \ + select-context.h \ + selection-chemistry.h \ + selection-describer.h \ + selection.h \ + seltrans-handles.h \ + seltrans.h \ + sp-cursor.h \ + sp-item-group.h \ + sp-item.h \ + sp-object.h \ + sp-root.h \ + svg/svg-length.h \ + tools-switch.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h + +selection-chemistry.o: \ + selection-chemistry.cpp \ + bad-uri-exception.h \ + color.h \ + composite-undo-stack-observer.h \ + context-fns.h \ + decimal-round.h \ + desktop-handles.h \ + desktop-style.h \ + desktop.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document-private.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + dropper-context.h \ + enums.h \ + event-context.h \ + extension/extension-forward.h \ + file.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + inkscape.h \ + layer-fns.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix-rotate-ops.h \ + libnr/nr-matrix-translate-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-fns.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale-ops.h \ + libnr/nr-scale-translate-ops.h \ + libnr/nr-scale.h \ + libnr/nr-translate-matrix-ops.h \ + libnr/nr-translate-scale-ops.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/Layout-TNG.h \ + line-snapper.h \ + message-stack.h \ + message.h \ + object-snapper.h \ + prefs-utils.h \ + round.h \ + selection-chemistry.h \ + selection.h \ + snapped-point.h \ + snapper.h \ + sp-defs.h \ + sp-flowregion.h \ + sp-flowtext.h \ + sp-gradient-fns.h \ + sp-gradient-reference.h \ + sp-gradient-spread.h \ + sp-gradient-units.h \ + sp-gradient-vector.h \ + sp-gradient.h \ + sp-item-group.h \ + sp-item-transform.h \ + sp-item.h \ + sp-linear-gradient-fns.h \ + sp-marker-loc.h \ + sp-marker.h \ + sp-metric.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + sp-offset.h \ + sp-paint-server.h \ + sp-pattern.h \ + sp-radial-gradient-fns.h \ + sp-root.h \ + sp-shape.h \ + sp-string.h \ + sp-text.h \ + sp-textpath.h \ + sp-tspan.h \ + sp-use.h \ + style.h \ + svg/svg-length.h \ + text-context.h \ + text-editing.h \ + text-tag-attributes.h \ + tools-switch.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + undo-stack-observer.h \ + uri-references.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/event-fns.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +selection-describer.o: \ + selection-describer.cpp \ + decimal-round.h \ + desktop.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/Layout-TNG.h \ + message-context.h \ + message.h \ + round.h \ + selection-describer.h \ + selection.h \ + sp-flowtext.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-offset.h \ + sp-shape.h \ + sp-string.h \ + sp-text.h \ + sp-textpath.h \ + sp-use.h \ + svg/svg-length.h \ + text-tag-attributes.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/quote.h + +selection.o: \ + selection.cpp \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape-private.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + macros.h \ + message.h \ + round.h \ + selection.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-shape.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +seltrans-handles.o: \ + seltrans-handles.cpp \ + decimal-round.h \ + display/sodipodi-ctrl.h \ + display/sp-canvas.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + seltrans-handles.h + +seltrans.o: \ + seltrans.cpp \ + decimal-round.h \ + desktop-handles.h \ + desktop-style.h \ + desktop.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/sodipodi-ctrl.h \ + display/sp-canvas.h \ + display/sp-ctrlline.h \ + document.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + isnan.h \ + knot-enums.h \ + knot.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix-translate-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale-ops.h \ + libnr/nr-scale.h \ + libnr/nr-translate-matrix-ops.h \ + libnr/nr-translate-ops.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + line-snapper.h \ + message-context.h \ + message.h \ + object-snapper.h \ + prefs-utils.h \ + round.h \ + selcue.h \ + select-context.h \ + selection-chemistry.h \ + selection.h \ + seltrans-handles.h \ + seltrans.h \ + snap.h \ + snapped-point.h \ + snapper.h \ + sp-item-transform.h \ + sp-item.h \ + sp-metric.h \ + sp-metrics.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +shortcuts-default-xml.o: \ + shortcuts-default-xml.cpp + +shortcuts.o: \ + shortcuts.cpp \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/action.h \ + helper/helper-forward.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + libnr/nr-object.h \ + require-config.h \ + shortcuts.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + verbs.h \ + xml/node-iterators.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +slideshow.o: \ + slideshow.cpp \ + display/display-forward.h \ + document.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + message.h \ + svg-view-widget.h \ + svg-view.h \ + ui/view/view-widget.h \ + ui/view/view.h + +snap.o: \ + snap.cpp \ + decimal-round.h \ + forward.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-scale-ops.h \ + libnr/nr-scale.h \ + libnr/nr-values.h \ + line-snapper.h \ + object-snapper.h \ + round.h \ + snap.h \ + snapped-point.h \ + snapper.h \ + sp-metric.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + version.h + +snapped-point.o: \ + snapped-point.cpp \ + decimal-round.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-point.h \ + round.h \ + snapped-point.h + +snapper.o: \ + snapper.cpp \ + decimal-round.h \ + forward.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-point.h \ + libnr/nr-values.h \ + line-snapper.h \ + object-snapper.h \ + round.h \ + snapped-point.h \ + snapper.h \ + sp-metric.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + version.h + +sp-anchor.o: \ + sp-anchor.cpp \ + attributes.h \ + decimal-round.h \ + display/nr-arena-forward.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + message.h \ + round.h \ + sp-anchor.h \ + sp-item-group.h \ + sp-item.h \ + sp-object.h \ + traits/reference.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/quote.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-animation.o: \ + sp-animation.cpp \ + forward.h \ + sp-animation.h \ + sp-object.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + version.h + +sp-clippath.o: \ + sp-clippath.cpp \ + attributes.h \ + bad-uri-exception.h \ + decimal-round.h \ + display/nr-arena-forward.h \ + display/nr-arena-group.h \ + display/nr-arena-item.h \ + display/nr-arena.h \ + document.h \ + enums.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-clippath.h \ + sp-item.h \ + sp-object-group.h \ + sp-object.h \ + sp-paint-server.h \ + traits/reference.h \ + uri-references.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-conn-end-pair.o: \ + sp-conn-end-pair.cpp \ + attributes.h \ + bad-uri-exception.h \ + decimal-round.h \ + display/curve.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + dom/dom.h \ + dom/domstring.h \ + dom/uri.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libavoid/connector.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libavoid/vertices.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-conn-end-pair.h \ + sp-conn-end.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-path.h \ + sp-shape.h \ + sp-use-reference.h \ + traits/reference.h \ + uri-references.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-conn-end.o: \ + sp-conn-end.cpp \ + bad-uri-exception.h \ + decimal-round.h \ + display/curve.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + dom/dom.h \ + dom/domstring.h \ + dom/uri.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libavoid/connector.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-div.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-conn-end-pair.h \ + sp-conn-end.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-path.h \ + sp-shape.h \ + sp-use-reference.h \ + traits/reference.h \ + uri-references.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-cursor.o: \ + sp-cursor.cpp \ + sp-cursor.h + +sp-defs.o: \ + sp-defs.cpp \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + sp-defs.h \ + sp-object.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-ellipse.o: \ + sp-ellipse.cpp \ + attributes.h \ + color.h \ + decimal-round.h \ + display/curve.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stringstream.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/n-art-bpath.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-path-code.h \ + libnr/nr-path.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + prefs-utils.h \ + round.h \ + sp-ellipse.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-shape.h \ + style.h \ + svg/svg-length.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-flowdiv.o: \ + sp-flowdiv.cpp \ + decimal-round.h \ + display/nr-arena-forward.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-flowdiv.h \ + sp-item.h \ + sp-object.h \ + sp-string.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-flowregion.o: \ + sp-flowregion.cpp \ + color.h \ + decimal-round.h \ + display/canvas-bpath.h \ + display/curve.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/sp-canvas.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/Layout-TNG.h \ + livarot/LivarotDefs.h \ + livarot/Path.h \ + livarot/livarot-forward.h \ + round.h \ + sp-flowregion.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-shape.h \ + sp-string.h \ + sp-text.h \ + sp-use.h \ + style.h \ + svg/svg-length.h \ + text-tag-attributes.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-flowtext.o: \ + sp-flowtext.cpp \ + attributes.h \ + color.h \ + decimal-round.h \ + desktop-affine.h \ + desktop-handles.h \ + desktop.h \ + display/curve.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/nr-arena-glyphs.h \ + display/nr-arena-group.h \ + display/nr-arena-item.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/Layout-TNG.h \ + libnrtype/nrtype-forward.h \ + message.h \ + round.h \ + selection.h \ + sp-flowdiv.h \ + sp-flowregion.h \ + sp-flowtext.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-paint-server.h \ + sp-rect.h \ + sp-shape.h \ + sp-string.h \ + sp-use.h \ + style.h \ + svg/svg-length.h \ + text-tag-attributes.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-gradient-reference.o: \ + sp-gradient-reference.cpp \ + bad-uri-exception.h \ + forward.h \ + libnr/nr-forward.h \ + sp-gradient-fns.h \ + sp-gradient-reference.h \ + sp-gradient-spread.h \ + sp-gradient-units.h \ + uri-references.h + +sp-gradient.o: \ + sp-gradient.cpp \ + attributes.h \ + bad-uri-exception.h \ + color.h \ + composite-undo-stack-observer.h \ + decimal-round.h \ + display/nr-arena-forward.h \ + display/nr-gradient-gpl.h \ + document-private.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/uri.h \ + dom/views.h \ + enums.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + gradient-chemistry.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-gradient.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-div.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix-scale-ops.h \ + libnr/nr-matrix-translate-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-render.h \ + libnr/nr-rotate.h \ + libnr/nr-scale-translate-ops.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + macros.h \ + round.h \ + sp-defs.h \ + sp-gradient-fns.h \ + sp-gradient-reference.h \ + sp-gradient-spread.h \ + sp-gradient-units.h \ + sp-gradient-vector.h \ + sp-gradient.h \ + sp-item-group.h \ + sp-item.h \ + sp-linear-gradient-fns.h \ + sp-linear-gradient.h \ + sp-object.h \ + sp-paint-server.h \ + sp-radial-gradient-fns.h \ + sp-radial-gradient.h \ + sp-root.h \ + sp-stop-fns.h \ + sp-stop.h \ + streq.h \ + svg/css-ostringstream.h \ + svg/svg-length.h \ + traits/reference.h \ + undo-stack-observer.h \ + uri-references.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/event-fns.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-guide.o: \ + sp-guide.cpp \ + attributes.h \ + decimal-round.h \ + display/display-forward.h \ + display/guideline.h \ + display/nr-arena-forward.h \ + display/sp-canvas.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + remove-last.h \ + round.h \ + sp-guide-attachment.h \ + sp-guide-constraint.h \ + sp-guide.h \ + sp-item-notify-moveto.h \ + sp-item.h \ + sp-object.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-image.o: \ + sp-image.cpp \ + attributes.h \ + decimal-round.h \ + display/nr-arena-forward.h \ + display/nr-arena-image.h \ + display/nr-arena-item.h \ + document.h \ + enums.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/print.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + io/sys.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-path.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-image.h \ + sp-item.h \ + sp-object.h \ + svg/svg-length.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/quote.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-item-group.o: \ + sp-item-group.cpp \ + attributes.h \ + color.h \ + decimal-round.h \ + display/nr-arena-forward.h \ + display/nr-arena-group.h \ + display/nr-arena-item.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + enums.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + prefs-utils.h \ + round.h \ + sp-item-group.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-root.h \ + sp-use.h \ + style.h \ + svg/svg-length.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-item-notify-moveto.o: \ + sp-item-notify-moveto.cpp \ + decimal-round.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + forward.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-translate-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-guide-attachment.h \ + sp-guide.h \ + sp-item-rm-unsatisfied-cns.h \ + sp-item.h \ + sp-object.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + version.h + +sp-item-rm-unsatisfied-cns.o: \ + sp-item-rm-unsatisfied-cns.cpp \ + approx-equal.h \ + decimal-round.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + forward.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + remove-last.h \ + round.h \ + sp-guide-attachment.h \ + sp-guide-constraint.h \ + sp-guide.h \ + sp-item.h \ + sp-object.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + version.h + +sp-item-transform.o: \ + sp-item-transform.cpp \ + decimal-round.h \ + display/nr-arena-forward.h \ + forward.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix-rotate-ops.h \ + libnr/nr-matrix-scale-ops.h \ + libnr/nr-matrix-translate-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-item.h \ + sp-object.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + version.h + +sp-item-update-cns.o: \ + sp-item-update-cns.cpp \ + decimal-round.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + forward.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + satisfied-guide-cns.h \ + sp-guide-attachment.h \ + sp-guide-constraint.h \ + sp-guide.h \ + sp-item.h \ + sp-object.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + version.h + +sp-item.o: \ + sp-item.cpp \ + algorithms/find-last-if.h \ + attributes.h \ + bad-uri-exception.h \ + color.h \ + conn-avoid-ref.h \ + decimal-round.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/nr-arena-item.h \ + display/nr-arena.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/uri.h \ + dom/views.h \ + enums.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/print.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + gradient-chemistry.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-div.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix-scale-ops.h \ + libnr/nr-matrix-translate-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-path.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale-translate-ops.h \ + libnr/nr-scale.h \ + libnr/nr-translate-scale-ops.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/Layout-TNG.h \ + prefs-utils.h \ + round.h \ + sp-clippath.h \ + sp-gradient-fns.h \ + sp-gradient-spread.h \ + sp-gradient-units.h \ + sp-gradient-vector.h \ + sp-gradient.h \ + sp-item-group.h \ + sp-item-rm-unsatisfied-cns.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-mask.h \ + sp-object-group.h \ + sp-object.h \ + sp-paint-server.h \ + sp-pattern.h \ + sp-rect.h \ + sp-root.h \ + sp-shape.h \ + sp-string.h \ + sp-text.h \ + sp-use.h \ + style.h \ + svg/svg-length.h \ + text-tag-attributes.h \ + traits/copy.h \ + traits/list-copy.h \ + traits/reference.h \ + uri-references.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + util/reverse-list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-line.o: \ + sp-line.cpp \ + attributes.h \ + color.h \ + decimal-round.h \ + display/curve.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-item.h \ + sp-line.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-shape.h \ + style.h \ + svg/svg-length.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-marker.o: \ + sp-marker.cpp \ + attributes.h \ + bad-uri-exception.h \ + decimal-round.h \ + display/nr-arena-forward.h \ + display/nr-arena-group.h \ + display/nr-arena-item.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + enums.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-fns.h \ + libnr/nr-rotate.h \ + libnr/nr-scale-matrix-ops.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-item-group.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-marker.h \ + sp-object.h \ + svg/svg-length.h \ + traits/reference.h \ + uri-references.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-mask.o: \ + sp-mask.cpp \ + attributes.h \ + bad-uri-exception.h \ + decimal-round.h \ + display/nr-arena-forward.h \ + display/nr-arena-group.h \ + display/nr-arena-item.h \ + display/nr-arena.h \ + document.h \ + enums.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-item.h \ + sp-mask.h \ + sp-object-group.h \ + sp-object.h \ + sp-paint-server.h \ + traits/reference.h \ + uri-references.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-metadata.o: \ + sp-metadata.cpp \ + decimal-round.h \ + display/nr-arena-forward.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-item-group.h \ + sp-item.h \ + sp-metadata.h \ + sp-object.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node-iterators.h \ + xml/node.h + +sp-metrics.o: \ + sp-metrics.cpp \ + sp-metric.h \ + sp-metrics.h \ + unit-constants.h + +sp-namedview.o: \ + sp-namedview.cpp \ + attributes.h \ + decimal-round.h \ + desktop-events.h \ + desktop-handles.h \ + desktop.h \ + display/canvas-grid.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/sp-canvas.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + helper/units.h \ + isnan.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + line-snapper.h \ + message.h \ + object-snapper.h \ + prefs-utils.h \ + round.h \ + snapped-point.h \ + snapper.h \ + sp-guide-attachment.h \ + sp-guide.h \ + sp-item-group.h \ + sp-item.h \ + sp-metric.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-object-group.o: \ + sp-object-group.cpp \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + sp-object-group.h \ + sp-object.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-object-repr.o: \ + sp-object-repr.cpp \ + bad-uri-exception.h \ + decimal-round.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + enums.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libavoid/connector.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/Layout-TNG.h \ + media.h \ + round.h \ + sp-anchor.h \ + sp-clippath.h \ + sp-conn-end-pair.h \ + sp-defs.h \ + sp-ellipse.h \ + sp-flowdiv.h \ + sp-flowregion.h \ + sp-flowtext.h \ + sp-image.h \ + sp-item-group.h \ + sp-item.h \ + sp-line.h \ + sp-linear-gradient-fns.h \ + sp-marker-loc.h \ + sp-marker.h \ + sp-mask.h \ + sp-metadata.h \ + sp-object-group.h \ + sp-object.h \ + sp-offset.h \ + sp-paint-server.h \ + sp-path.h \ + sp-pattern.h \ + sp-polygon.h \ + sp-polyline.h \ + sp-radial-gradient-fns.h \ + sp-rect.h \ + sp-root.h \ + sp-shape.h \ + sp-spiral.h \ + sp-star.h \ + sp-stop-fns.h \ + sp-string.h \ + sp-style-elem.h \ + sp-symbol.h \ + sp-text.h \ + sp-textpath.h \ + sp-tspan.h \ + sp-use.h \ + svg/svg-length.h \ + text-tag-attributes.h \ + traits/reference.h \ + uri-references.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-object.o: \ + sp-object.cpp \ + algorithms/longest-common-suffix.h \ + attributes.h \ + color.h \ + debug/event-tracker.h \ + debug/event.h \ + debug/logger.h \ + decimal-round.h \ + display/nr-arena-forward.h \ + document.h \ + enums.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/sp-marshal.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-item-group.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object-repr.h \ + sp-object.h \ + sp-root.h \ + streq.h \ + strneq.h \ + style.h \ + svg/svg-length.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + util/shared-c-string-ptr.h \ + version.h \ + xml/node-event-vector.h \ + xml/node-fns.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-offset.o: \ + sp-offset.cpp \ + attributes.h \ + bad-uri-exception.h \ + decimal-round.h \ + display/curve.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/uri.h \ + dom/views.h \ + enums.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/n-art-bpath.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-path-code.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/Layout-TNG.h \ + livarot/LivarotDefs.h \ + livarot/Path.h \ + livarot/livarot-forward.h \ + prefs-utils.h \ + round.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-offset.h \ + sp-shape.h \ + sp-string.h \ + sp-text.h \ + sp-use-reference.h \ + svg/svg-length.h \ + text-tag-attributes.h \ + traits/reference.h \ + uri-references.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-paint-server.o: \ + sp-paint-server.cpp \ + decimal-round.h \ + forward.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock-pattern.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-object.h \ + sp-paint-server.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + version.h + +sp-path.o: \ + sp-path.cpp \ + attributes.h \ + decimal-round.h \ + display/curve.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libavoid/connector.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/n-art-bpath.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-path-code.h \ + libnr/nr-path.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-conn-end-pair.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-path.h \ + sp-shape.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-pattern.o: \ + sp-pattern.cpp \ + attributes.h \ + bad-uri-exception.h \ + composite-undo-stack-observer.h \ + decimal-round.h \ + display/nr-arena-forward.h \ + display/nr-arena-group.h \ + display/nr-arena-item.h \ + display/nr-arena.h \ + document-private.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/uri.h \ + dom/views.h \ + enums.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate-matrix-ops.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + macros.h \ + round.h \ + sp-defs.h \ + sp-item-group.h \ + sp-item.h \ + sp-object.h \ + sp-paint-server.h \ + sp-pattern.h \ + sp-root.h \ + svg/svg-length.h \ + traits/reference.h \ + undo-stack-observer.h \ + uri-references.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/event-fns.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-polygon.o: \ + sp-polygon.cpp \ + attributes.h \ + decimal-round.h \ + display/curve.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + dom/dom.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/stringstream.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/n-art-bpath.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-path-code.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-polygon.h \ + sp-shape.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-polyline.o: \ + sp-polyline.cpp \ + attributes.h \ + decimal-round.h \ + display/curve.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-polyline.h \ + sp-shape.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-rect.o: \ + sp-rect.cpp \ + attributes.h \ + color.h \ + decimal-round.h \ + display/curve.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-div.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-rect.h \ + sp-shape.h \ + style.h \ + svg/svg-length.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-root.o: \ + sp-root.cpp \ + attributes.h \ + decimal-round.h \ + display/nr-arena-forward.h \ + display/nr-arena-group.h \ + display/nr-arena-item.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stringstream.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + enums.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/print.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape_version.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix-translate-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-path.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale-ops.h \ + libnr/nr-scale.h \ + libnr/nr-translate-scale-ops.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-defs.h \ + sp-item-group.h \ + sp-item.h \ + sp-object.h \ + sp-root.h \ + svg/svg-length.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-shape.o: \ + sp-shape.cpp \ + bad-uri-exception.h \ + color.h \ + decimal-round.h \ + display/canvas-bpath.h \ + display/curve.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/nr-arena-item.h \ + display/nr-arena-shape.h \ + display/sp-canvas.h \ + document.h \ + enums.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/print.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + gnuc-attribute.h \ + libavoid/connector.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/n-art-bpath.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix-translate-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-path-code.h \ + libnr/nr-path.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale-matrix-ops.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + macros.h \ + marker-status.h \ + prefs-utils.h \ + round.h \ + sp-conn-end-pair.h \ + sp-item-group.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-marker.h \ + sp-object.h \ + sp-paint-server.h \ + sp-path.h \ + sp-shape.h \ + style.h \ + svg/svg-length.h \ + traits/reference.h \ + uri-references.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-skeleton.o: \ + sp-skeleton.cpp \ + attributes.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + sp-object.h \ + sp-skeleton.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-spiral.o: \ + sp-spiral.cpp \ + attributes.h \ + decimal-round.h \ + display/bezier-utils.h \ + display/curve.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-shape.h \ + sp-spiral.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-star.o: \ + sp-star.cpp \ + attributes.h \ + decimal-round.h \ + display/curve.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-polygon.h \ + sp-shape.h \ + sp-star.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-string.o: \ + sp-string.cpp \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + sp-object.h \ + sp-string.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-style-elem-test.o: \ + sp-style-elem-test.cpp \ + attributes.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape-private.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + media.h \ + sp-object.h \ + sp-style-elem.h \ + streq.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-style-elem.o: \ + sp-style-elem.cpp \ + attributes.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-doc-handler.h \ + libcroco/cr-input.h \ + libcroco/cr-num.h \ + libcroco/cr-parser.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-tknzr.h \ + libcroco/cr-token.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + media.h \ + sp-object.h \ + sp-style-elem.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node-event-vector.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-symbol.o: \ + sp-symbol.cpp \ + attributes.h \ + decimal-round.h \ + display/nr-arena-forward.h \ + display/nr-arena-group.h \ + display/nr-arena-item.h \ + document.h \ + enums.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/print.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-path.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-item-group.h \ + sp-item.h \ + sp-object.h \ + sp-symbol.h \ + svg/svg-length.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-text.o: \ + sp-text.cpp \ + attributes.h \ + color.h \ + decimal-round.h \ + desktop-handles.h \ + display/curve.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/nr-arena-glyphs.h \ + display/nr-arena-group.h \ + display/nr-arena-item.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stringstream.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/FontFactory.h \ + libnrtype/Layout-TNG.h \ + libnrtype/font-instance.h \ + libnrtype/font-style-to-pos.h \ + libnrtype/font-style.h \ + libnrtype/nr-type-pos-def.h \ + libnrtype/nr-type-primitives.h \ + libnrtype/nrtype-forward.h \ + line-snapper.h \ + livarot/LivarotDefs.h \ + livarot/livarot-forward.h \ + mod360.h \ + object-snapper.h \ + require-config.h \ + round.h \ + snapped-point.h \ + snapper.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-metric.h \ + sp-metrics.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + sp-paint-server.h \ + sp-string.h \ + sp-text.h \ + sp-textpath.h \ + sp-tspan.h \ + style.h \ + svg/svg-length.h \ + text-editing.h \ + text-tag-attributes.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/quote.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-tspan.o: \ + sp-tspan.cpp \ + attributes.h \ + bad-uri-exception.h \ + decimal-round.h \ + display/nr-arena-forward.h \ + document.h \ + dom/dom.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/stringstream.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/Layout-TNG.h \ + livarot/LivarotDefs.h \ + livarot/Path.h \ + livarot/livarot-forward.h \ + round.h \ + sp-item.h \ + sp-object.h \ + sp-string.h \ + sp-text.h \ + sp-textpath.h \ + sp-tspan.h \ + sp-use-reference.h \ + svg/svg-length.h \ + text-tag-attributes.h \ + traits/reference.h \ + uri-references.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +sp-use-reference.o: \ + sp-use-reference.cpp \ + bad-uri-exception.h \ + decimal-round.h \ + display/curve.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + dom/dom.h \ + dom/domstring.h \ + dom/uri.h \ + enums.h \ + forward.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/Layout-TNG.h \ + livarot/LivarotDefs.h \ + livarot/Path.h \ + livarot/livarot-forward.h \ + prefs-utils.h \ + round.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-shape.h \ + sp-string.h \ + sp-text.h \ + sp-use-reference.h \ + svg/svg-length.h \ + text-tag-attributes.h \ + traits/reference.h \ + uri-references.h \ + util/forward-pointer-iterator.h \ + version.h + +sp-use.o: \ + sp-use.cpp \ + attributes.h \ + bad-uri-exception.h \ + color.h \ + decimal-round.h \ + display/nr-arena-forward.h \ + display/nr-arena-group.h \ + display/nr-arena-item.h \ + document.h \ + dom/dom.h \ + dom/domstring.h \ + dom/uri.h \ + enums.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix-translate-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + prefs-utils.h \ + round.h \ + sp-flowregion.h \ + sp-item-group.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object-repr.h \ + sp-object.h \ + sp-symbol.h \ + sp-use-reference.h \ + sp-use.h \ + style.h \ + svg/svg-length.h \ + traits/reference.h \ + uri-references.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +spiral-context.o: \ + spiral-context.cpp \ + context-fns.h \ + decimal-round.h \ + desktop-affine.h \ + desktop-handles.h \ + desktop-style.h \ + desktop.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/sp-canvas.h \ + document.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + knot-enums.h \ + knotholder.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + line-snapper.h \ + macros.h \ + message-context.h \ + message.h \ + object-edit.h \ + object-snapper.h \ + prefs-utils.h \ + round.h \ + selection.h \ + snap.h \ + snapped-point.h \ + snapper.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-metric.h \ + sp-metrics.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + sp-shape.h \ + sp-spiral.h \ + spiral-context.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node-event-vector.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +splivarot.o: \ + splivarot.cpp \ + color.h \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + display/canvas-bpath.h \ + display/curve.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/sp-canvas.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape.h \ + libavoid/connector.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/n-art-bpath.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-path-code.h \ + libnr/nr-path.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/Layout-TNG.h \ + livarot/LivarotDefs.h \ + livarot/Path.h \ + livarot/livarot-forward.h \ + message-stack.h \ + message.h \ + prefs-utils.h \ + round.h \ + selection.h \ + sp-conn-end-pair.h \ + sp-item-group.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-path.h \ + sp-shape.h \ + sp-string.h \ + sp-text.h \ + style.h \ + svg/svg-length.h \ + text-tag-attributes.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr-sorting.h \ + xml/repr.h \ + xml/sp-css-attr.h + +star-context.o: \ + star-context.cpp \ + context-fns.h \ + decimal-round.h \ + desktop-affine.h \ + desktop-handles.h \ + desktop-style.h \ + desktop.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/sp-canvas.h \ + document.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + knot-enums.h \ + knotholder.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + line-snapper.h \ + macros.h \ + message-context.h \ + message.h \ + object-edit.h \ + object-snapper.h \ + prefs-utils.h \ + round.h \ + selection.h \ + snap.h \ + snapped-point.h \ + snapper.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-metric.h \ + sp-metrics.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + sp-polygon.h \ + sp-shape.h \ + sp-star.h \ + star-context.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node-event-vector.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +streams-gzip.o: \ + streams-gzip.cpp \ + forward.h \ + streams-gzip.h \ + streams-handles.h \ + streams-zlib.h + +streams-handles.o: \ + streams-handles.cpp \ + dom/dom.h \ + dom/domstring.h \ + dom/uri.h \ + forward.h \ + streams-handles.h + +streams-jar.o: \ + streams-jar.cpp \ + forward.h \ + streams-handles.h \ + streams-jar.h \ + streams-zlib.h + +streams-zlib.o: \ + streams-zlib.cpp \ + forward.h \ + streams-handles.h \ + streams-zlib.h + +style-test.o: \ + style-test.cpp \ + color.h \ + forward.h \ + sp-marker-loc.h \ + streq.h \ + strneq.h \ + style.h + +style.o: \ + style.cpp \ + attributes.h \ + bad-uri-exception.h \ + color.h \ + decimal-round.h \ + display/canvas-bpath.h \ + display/sp-canvas.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + extract-uri.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + gnuc-attribute.h \ + isnan.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-fonts.h \ + libcroco/cr-node-iface.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-prop-list.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-sel-eng.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-style.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + marker-status.h \ + round.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-paint-server.h \ + streq.h \ + strneq.h \ + style.h \ + svg/css-ostringstream.h \ + traits/reference.h \ + unit-constants.h \ + uri-references.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/croco-node-iface.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +svg-view-widget.o: \ + svg-view-widget.cpp \ + decimal-round.h \ + display/canvas-arena.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/nr-arena-item.h \ + display/sp-canvas.h \ + document.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + message.h \ + round.h \ + svg-view-widget.h \ + svg-view.h \ + ui/view/view-widget.h \ + ui/view/view.h + +svg-view.o: \ + svg-view.cpp \ + decimal-round.h \ + display/canvas-arena.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/nr-arena-item.h \ + display/sp-canvas.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + message.h \ + round.h \ + sp-item.h \ + sp-object.h \ + svg-view.h \ + traits/reference.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + version.h + +svg/css-ostringstream.o: \ + svg/css-ostringstream.cpp \ + svg/css-ostringstream.h \ + svg/strip-trailing-zeros.h + +svg/gnome-canvas-bpath-util.o: \ + svg/gnome-canvas-bpath-util.cpp \ + decimal-round.h \ + libnr/n-art-bpath.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-path-code.h \ + libnr/nr-point.h \ + round.h \ + svg/gnome-canvas-bpath-util.h + +svg/itos.o: \ + svg/itos.cpp + +svg/round.o: \ + svg/round.cpp + +svg/stringstream.o: \ + svg/stringstream.cpp \ + dom/dom.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/stringstream.h + +svg/strip-trailing-zeros.o: \ + svg/strip-trailing-zeros.cpp \ + svg/strip-trailing-zeros.h + +svg/svg-affine.o: \ + svg/svg-affine.cpp \ + decimal-round.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix-translate-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rotate-fns.h \ + libnr/nr-rotate-matrix-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale-matrix-ops.h \ + libnr/nr-scale.h \ + libnr/nr-translate-matrix-ops.h \ + libnr/nr-translate-rotate-ops.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +svg/svg-color.o: \ + svg/svg-color.cpp \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h + +svg/svg-length.o: \ + svg/svg-length.cpp \ + dom/css.h \ + dom/dom.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stringstream.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + unit-constants.h + +svg/svg-path.o: \ + svg/svg-path.cpp \ + decimal-round.h \ + dom/dom.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/stringstream.h \ + libnr/n-art-bpath.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-path-code.h \ + libnr/nr-point.h \ + round.h \ + svg/gnome-canvas-bpath-util.h + +text-chemistry.o: \ + text-chemistry.cpp \ + decimal-round.h \ + desktop-handles.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/Layout-TNG.h \ + message-stack.h \ + message.h \ + round.h \ + selection.h \ + sp-flowdiv.h \ + sp-flowregion.h \ + sp-flowtext.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-rect.h \ + sp-shape.h \ + sp-string.h \ + sp-text.h \ + sp-textpath.h \ + svg/svg-length.h \ + text-editing.h \ + text-tag-attributes.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +text-context.o: \ + text-context.cpp \ + color.h \ + context-fns.h \ + decimal-round.h \ + desktop-affine.h \ + desktop-handles.h \ + desktop-style.h \ + desktop.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/sodipodi-ctrlrect.h \ + display/sp-canvas.h \ + display/sp-ctrlline.h \ + display/sp-ctrlquadr.h \ + document.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + knot-enums.h \ + knotholder.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/Layout-TNG.h \ + line-snapper.h \ + macros.h \ + message-context.h \ + message-stack.h \ + message.h \ + object-edit.h \ + object-snapper.h \ + prefs-utils.h \ + round.h \ + rubberband.h \ + selection.h \ + snapped-point.h \ + snapper.h \ + sp-flowtext.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-metric.h \ + sp-metrics.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + sp-string.h \ + sp-text.h \ + style.h \ + svg/svg-length.h \ + text-context.h \ + text-editing.h \ + text-tag-attributes.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node-event-vector.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +text-editing.o: \ + text-editing.cpp \ + color.h \ + decimal-round.h \ + desktop.h \ + display/nr-arena-forward.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/Layout-TNG.h \ + message.h \ + round.h \ + sp-flowdiv.h \ + sp-flowregion.h \ + sp-flowtext.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-string.h \ + sp-text.h \ + sp-textpath.h \ + sp-tspan.h \ + style.h \ + svg/svg-length.h \ + text-editing.h \ + text-tag-attributes.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + unit-constants.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + util/shared-c-string-ptr.h \ + version.h \ + xml/attribute-record.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +tools-switch.o: \ + tools-switch.cpp \ + arc-context.h \ + connector-context.h \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + display/curve.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + draw-context.h \ + dropper-context.h \ + dyna-draw-context.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + gradient-context.h \ + inkscape-private.h \ + inkscape.h \ + libavoid/connector.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-path-code.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/Layout-TNG.h \ + message-context.h \ + message.h \ + node-context.h \ + nodepath.h \ + pen-context.h \ + pencil-context.h \ + rect-context.h \ + round.h \ + select-context.h \ + sp-conn-end-pair.h \ + sp-ellipse.h \ + sp-flowtext.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-path.h \ + sp-polygon.h \ + sp-rect.h \ + sp-shape.h \ + sp-spiral.h \ + sp-star.h \ + sp-string.h \ + sp-text.h \ + spiral-context.h \ + star-context.h \ + svg/svg-length.h \ + text-context.h \ + text-tag-attributes.h \ + tools-switch.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h \ + zoom-context.h + +trace/filterset.o: \ + trace/filterset.cpp \ + trace/filterset.h \ + trace/imagemap-gdk.h \ + trace/imagemap.h + +trace/imagemap-gdk.o: \ + trace/imagemap-gdk.cpp \ + trace/imagemap-gdk.h \ + trace/imagemap.h + +trace/imagemap.o: \ + trace/imagemap.cpp \ + io/sys.h \ + trace/imagemap.h + +trace/potrace/curve.o: \ + trace/potrace/curve.cpp \ + decimal-round.h \ + display/curve.h \ + libnr/n-art-bpath.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-path-code.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate-ops.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h + +trace/potrace/decompose.o: \ + trace/potrace/decompose.cpp \ + decimal-round.h \ + display/curve.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-point.h \ + round.h \ + trace/potrace/bitmap.h \ + trace/potrace/decompose.h \ + trace/potrace/lists.h \ + trace/potrace/potracelib.h \ + trace/potrace/progress.h + +trace/potrace/greymap.o: \ + trace/potrace/greymap.cpp \ + trace/potrace/greymap.h + +trace/potrace/inkscape-potrace.o: \ + trace/potrace/inkscape-potrace.cpp \ + decimal-round.h \ + desktop-handles.h \ + display/curve.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + dom/dom.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/stringstream.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape.h \ + libavoid/connector.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + message-stack.h \ + message.h \ + round.h \ + sp-conn-end-pair.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-path.h \ + sp-shape.h \ + trace/filterset.h \ + trace/imagemap-gdk.h \ + trace/imagemap.h \ + trace/potrace/bitmap.h \ + trace/potrace/inkscape-potrace.h \ + trace/potrace/potracelib.h \ + trace/potrace/progress.h \ + trace/potrace/trace.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + version.h + +trace/potrace/potracelib.o: \ + trace/potrace/potracelib.cpp \ + decimal-round.h \ + display/curve.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-point.h \ + round.h \ + trace/potrace/decompose.h \ + trace/potrace/potracelib.h \ + trace/potrace/progress.h \ + trace/potrace/trace.h + +trace/potrace/render.o: \ + trace/potrace/render.cpp \ + trace/potrace/auxiliary.h \ + trace/potrace/greymap.h \ + trace/potrace/potracelib.h \ + trace/potrace/render.h + +trace/potrace/trace.o: \ + trace/potrace/trace.cpp \ + decimal-round.h \ + display/curve.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-point.h \ + round.h \ + trace/potrace/lists.h \ + trace/potrace/progress.h + +trace/trace.o: \ + trace/trace.cpp \ + decimal-round.h \ + display/curve.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-point.h \ + round.h \ + trace/potrace/lists.h \ + trace/potrace/progress.h + +ui/dialog/aboutbox.o: \ + ui/dialog/aboutbox.cpp \ + decimal-round.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape_version.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/Layout-TNG.h \ + path-prefix.h \ + prefix.h \ + require-config.h \ + round.h \ + sp-item.h \ + sp-object.h \ + sp-string.h \ + sp-text.h \ + svg-view-widget.h \ + svg/svg-length.h \ + text-editing.h \ + text-tag-attributes.h \ + traits/reference.h \ + ui/dialog/aboutbox.h \ + ui/dialog/dialog.h \ + ui/stock.h \ + ui/view/view-widget.h \ + util/forward-pointer-iterator.h \ + version.h + +ui/dialog/align-and-distribute.o: \ + ui/dialog/align-and-distribute.cpp \ + decimal-round.h \ + desktop-handles.h \ + dialogs/unclump.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + enums.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/helper-forward.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-path-code.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/Layout-TNG.h \ + macros.h \ + node-context.h \ + nodepath.h \ + prefs-utils.h \ + removeoverlap/removeoverlap.h \ + require-config.h \ + round.h \ + selection.h \ + sp-flowtext.h \ + sp-item-transform.h \ + sp-item.h \ + sp-object.h \ + sp-string.h \ + sp-text.h \ + svg/svg-length.h \ + text-editing.h \ + text-tag-attributes.h \ + tools-switch.h \ + traits/reference.h \ + ui/dialog/align-and-distribute.h \ + ui/dialog/dialog.h \ + ui/widget/notebook-page.h \ + util/forward-pointer-iterator.h \ + util/glib-list-iterators.h \ + util/list.h \ + verbs.h \ + version.h \ + widgets/icon.h + +ui/dialog/dialog-manager.o: \ + ui/dialog/dialog-manager.cpp \ + application/application.h \ + decimal-round.h \ + dialogs/export.h \ + dialogs/find.h \ + dialogs/tiledialog.h \ + forward.h \ + helper/helper-forward.h \ + helper/unit-menu.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + require-config.h \ + round.h \ + ui/dialog/align-and-distribute.h \ + ui/dialog/dialog-manager.h \ + ui/dialog/dialog.h \ + ui/dialog/document-metadata.h \ + ui/dialog/document-properties.h \ + ui/dialog/extension-editor.h \ + ui/dialog/fill-and-stroke.h \ + ui/dialog/inkscape-preferences.h \ + ui/dialog/layer-editor.h \ + ui/dialog/memory.h \ + ui/dialog/messages.h \ + ui/dialog/scriptdialog.h \ + ui/dialog/session-player.h \ + ui/dialog/text-properties.h \ + ui/dialog/tracedialog.h \ + ui/dialog/transformation.h \ + ui/dialog/whiteboard-connect.h \ + ui/dialog/whiteboard-sharewithchat.h \ + ui/dialog/whiteboard-sharewithuser.h \ + ui/dialog/xml-editor.h \ + ui/widget/button.h \ + ui/widget/icon-widget.h \ + ui/widget/imageicon.h \ + ui/widget/labelled.h \ + ui/widget/licensor.h \ + ui/widget/notebook-page.h \ + ui/widget/page-sizer.h \ + ui/widget/preferences-widget.h \ + ui/widget/registered-widget.h \ + ui/widget/registry.h \ + ui/widget/scalar-unit.h \ + ui/widget/scalar.h \ + ui/widget/tolerance-slider.h \ + verbs.h + +ui/dialog/dialog.o: \ + ui/dialog/dialog.cpp \ + application/app-prototype.h \ + application/application.h \ + application/editor.h \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + dialogs/dialog-events.h \ + display/display-forward.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/helper-forward.h \ + inkscape.h \ + interface.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + message.h \ + prefs-utils.h \ + require-config.h \ + round.h \ + shortcuts.h \ + ui/dialog/dialog-manager.h \ + ui/dialog/dialog.h \ + ui/stock.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + verbs.h + +ui/dialog/document-metadata.o: \ + ui/dialog/document-metadata.cpp \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + dialogs/rdf.h \ + display/display-forward.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + line-snapper.h \ + message.h \ + object-snapper.h \ + require-config.h \ + round.h \ + snapped-point.h \ + snapper.h \ + sp-metric.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + traits/reference.h \ + ui/dialog/dialog.h \ + ui/dialog/document-metadata.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + ui/widget/entity-entry.h \ + ui/widget/licensor.h \ + ui/widget/notebook-page.h \ + ui/widget/registry.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + verbs.h \ + version.h \ + xml/node-event-vector.h \ + xml/node.h + +ui/dialog/document-properties.o: \ + ui/dialog/document-properties.cpp \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + display/display-forward.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + helper/unit-menu.h \ + helper/units.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + line-snapper.h \ + message.h \ + object-snapper.h \ + require-config.h \ + round.h \ + snapped-point.h \ + snapper.h \ + sp-metric.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + traits/reference.h \ + ui/dialog/dialog.h \ + ui/dialog/document-properties.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + ui/widget/button.h \ + ui/widget/color-picker.h \ + ui/widget/color-preview.h \ + ui/widget/labelled.h \ + ui/widget/notebook-page.h \ + ui/widget/page-sizer.h \ + ui/widget/registered-widget.h \ + ui/widget/registry.h \ + ui/widget/scalar-unit.h \ + ui/widget/scalar.h \ + ui/widget/tolerance-slider.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + verbs.h \ + version.h \ + xml/node-event-vector.h \ + xml/node.h + +ui/dialog/export.o: \ + ui/dialog/export.cpp \ + decimal-round.h \ + desktop-handles.h \ + dialogs/dialog-events.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + extension/db.h \ + extension/extension-forward.h \ + extension/extension.h \ + extension/output.h \ + file.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + helper/unit-menu.h \ + helper/units.h \ + helper/window.h \ + inkscape-private.h \ + inkscape.h \ + interface.h \ + io/sys.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + line-snapper.h \ + macros.h \ + object-snapper.h \ + prefs-utils.h \ + require-config.h \ + round.h \ + selection.h \ + snapped-point.h \ + snapper.h \ + sp-item.h \ + sp-metric.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + traits/reference.h \ + unit-constants.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + verbs.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +ui/dialog/extension-editor.o: \ + ui/dialog/extension-editor.cpp \ + forward.h \ + helper/helper-forward.h \ + require-config.h \ + ui/dialog/dialog.h \ + ui/dialog/extension-editor.h \ + verbs.h + +ui/dialog/fill-and-stroke.o: \ + ui/dialog/fill-and-stroke.cpp \ + forward.h \ + helper/helper-forward.h \ + require-config.h \ + ui/dialog/dialog.h \ + ui/dialog/fill-and-stroke.h \ + ui/widget/notebook-page.h \ + verbs.h + +ui/dialog/find.o: \ + ui/dialog/find.cpp \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + dialogs/dialog-events.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/helper-forward.h \ + helper/window.h \ + inkscape.h \ + interface.h \ + libavoid/connector.h \ + libavoid/geometry.h \ + libavoid/geomtypes.h \ + libavoid/shape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/Layout-TNG.h \ + macros.h \ + message-stack.h \ + message.h \ + prefs-utils.h \ + require-config.h \ + round.h \ + selection-chemistry.h \ + selection.h \ + sp-conn-end-pair.h \ + sp-defs.h \ + sp-ellipse.h \ + sp-flowtext.h \ + sp-image.h \ + sp-item-group.h \ + sp-item.h \ + sp-line.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-offset.h \ + sp-path.h \ + sp-polygon.h \ + sp-polyline.h \ + sp-rect.h \ + sp-shape.h \ + sp-spiral.h \ + sp-star.h \ + sp-string.h \ + sp-text.h \ + sp-tspan.h \ + sp-use.h \ + svg/svg-length.h \ + text-editing.h \ + text-tag-attributes.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + verbs.h \ + version.h \ + widgets/icon.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +ui/dialog/inkscape-preferences.o: \ + ui/dialog/inkscape-preferences.cpp \ + color.h \ + decimal-round.h \ + desktop-handles.h \ + display/display-forward.h \ + document.h \ + enums.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/helper-forward.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + message-stack.h \ + message.h \ + prefs-utils.h \ + require-config.h \ + round.h \ + selcue.h \ + selection-chemistry.h \ + selection.h \ + sp-marker-loc.h \ + style.h \ + traits/reference.h \ + ui/dialog/dialog.h \ + ui/dialog/inkscape-preferences.h \ + ui/widget/preferences-widget.h \ + util/list.h \ + verbs.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +ui/dialog/layer-editor.o: \ + ui/dialog/layer-editor.cpp \ + forward.h \ + helper/helper-forward.h \ + require-config.h \ + ui/dialog/dialog.h \ + ui/dialog/layer-editor.h \ + verbs.h + +ui/dialog/memory.o: \ + ui/dialog/memory.cpp \ + debug/heap.h \ + forward.h \ + gc-core.h \ + helper/helper-forward.h \ + require-config.h \ + ui/dialog/dialog.h \ + ui/dialog/memory.h \ + util/shared-c-string-ptr.h \ + verbs.h + +ui/dialog/messages.o: \ + ui/dialog/messages.cpp \ + forward.h \ + helper/helper-forward.h \ + require-config.h \ + ui/dialog/dialog.h \ + ui/dialog/messages.h \ + ui/widget/button.h \ + verbs.h + +ui/dialog/scriptdialog.o: \ + ui/dialog/scriptdialog.cpp \ + extension/script/InkscapeScript.h \ + forward.h \ + helper/helper-forward.h \ + require-config.h \ + ui/dialog/dialog.h \ + ui/dialog/scriptdialog.h \ + verbs.h + +ui/dialog/text-properties.o: \ + ui/dialog/text-properties.cpp \ + forward.h \ + helper/helper-forward.h \ + require-config.h \ + ui/dialog/dialog.h \ + ui/dialog/text-properties.h \ + ui/widget/notebook-page.h \ + verbs.h + +ui/dialog/tracedialog.o: \ + ui/dialog/tracedialog.cpp \ + forward.h \ + helper/helper-forward.h \ + require-config.h \ + trace/imagemap.h \ + trace/potrace/inkscape-potrace.h \ + trace/potrace/potracelib.h \ + trace/potrace/progress.h \ + trace/potrace/trace.h \ + ui/dialog/dialog.h \ + ui/dialog/tracedialog.h \ + ui/stock.h \ + verbs.h + +ui/dialog/transformation.o: \ + ui/dialog/transformation.cpp \ + application/application.h \ + decimal-round.h \ + desktop-handles.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/helper-forward.h \ + helper/unit-menu.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + macros.h \ + prefs-utils.h \ + require-config.h \ + round.h \ + selection-chemistry.h \ + selection.h \ + sp-item-transform.h \ + sp-item.h \ + sp-object.h \ + traits/reference.h \ + ui/dialog/dialog.h \ + ui/dialog/transformation.h \ + ui/stock.h \ + ui/widget/button.h \ + ui/widget/imageicon.h \ + ui/widget/labelled.h \ + ui/widget/notebook-page.h \ + ui/widget/scalar-unit.h \ + ui/widget/scalar.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + verbs.h \ + version.h + +ui/dialog/tree-editor.o: \ + ui/dialog/tree-editor.cpp \ + forward.h \ + helper/helper-forward.h \ + require-config.h \ + ui/dialog/dialog.h \ + ui/dialog/tree-editor.h \ + verbs.h + +ui/dialog/xml-editor.o: \ + ui/dialog/xml-editor.cpp \ + forward.h \ + helper/helper-forward.h \ + require-config.h \ + ui/dialog/dialog.h \ + ui/dialog/xml-editor.h \ + verbs.h + +ui/icons.o: \ + ui/icons.cpp \ + path-prefix.h \ + prefix.h \ + require-config.h \ + ui/stock.h + +ui/previewholder.o: \ + ui/previewholder.cpp \ + ui/previewable.h \ + ui/previewfillable.h \ + ui/previewholder.h + +ui/stock-items.o: \ + ui/stock-items.cpp \ + bad-uri-exception.h \ + composite-undo-stack-observer.h \ + decimal-round.h \ + desktop-handles.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document-private.h \ + document.h \ + enums.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape.h \ + io/sys.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + path-prefix.h \ + prefix.h \ + require-config.h \ + round.h \ + sp-defs.h \ + sp-gradient-fns.h \ + sp-gradient-spread.h \ + sp-gradient-units.h \ + sp-item-group.h \ + sp-item.h \ + sp-marker-loc.h \ + sp-marker.h \ + sp-object.h \ + sp-paint-server.h \ + sp-pattern.h \ + sp-root.h \ + svg/svg-length.h \ + traits/reference.h \ + undo-stack-observer.h \ + uri-references.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/event-fns.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +ui/stock.o: \ + ui/stock.cpp \ + ui/stock.h + +ui/view/desktop-affine.o: \ + ui/view/desktop-affine.cpp \ + decimal-round.h \ + desktop.h \ + display/nr-arena-forward.h \ + document.h \ + enums.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + message.h \ + round.h \ + sp-item-group.h \ + sp-item.h \ + sp-object.h \ + sp-root.h \ + svg/svg-length.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + version.h + +ui/view/desktop-events.o: \ + ui/view/desktop-events.cpp \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + dialogs/dialog-events.h \ + display/display-forward.h \ + display/guideline.h \ + display/sp-canvas.h \ + document.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + helper/unit-menu.h \ + helper/units.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + line-snapper.h \ + message-context.h \ + message.h \ + object-snapper.h \ + round.h \ + snapped-point.h \ + snapper.h \ + sp-guide-attachment.h \ + sp-guide.h \ + sp-metric.h \ + sp-metrics.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view-widget.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + widgets/desktop-widget.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +ui/view/desktop-handles.o: \ + ui/view/desktop-handles.cpp \ + decimal-round.h \ + desktop.h \ + display/sp-canvas.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + message.h \ + round.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h + +ui/view/desktop-style.o: \ + ui/view/desktop-style.cpp \ + bad-uri-exception.h \ + color-rgba.h \ + color.h \ + decimal-round.h \ + desktop-style.h \ + desktop.h \ + display/nr-arena-forward.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock.h \ + libnr/nr-pixops.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/Layout-TNG.h \ + libnrtype/font-style-to-pos.h \ + libnrtype/nr-type-pos-def.h \ + message.h \ + prefs-utils.h \ + round.h \ + selection.h \ + sp-flowdiv.h \ + sp-flowregion.h \ + sp-flowtext.h \ + sp-gradient-fns.h \ + sp-gradient-spread.h \ + sp-gradient-units.h \ + sp-gradient-vector.h \ + sp-gradient.h \ + sp-item.h \ + sp-linear-gradient-fns.h \ + sp-linear-gradient.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-paint-server.h \ + sp-pattern.h \ + sp-radial-gradient-fns.h \ + sp-radial-gradient.h \ + sp-string.h \ + sp-text.h \ + sp-textpath.h \ + sp-tspan.h \ + sp-use.h \ + style.h \ + svg/css-ostringstream.h \ + svg/svg-length.h \ + text-tag-attributes.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + uri-references.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +ui/view/desktop.o: \ + ui/view/desktop.cpp \ + color.h \ + decimal-round.h \ + desktop-events.h \ + desktop-handles.h \ + desktop.h \ + display/canvas-arena.h \ + display/display-forward.h \ + display/gnome-canvas-acetate.h \ + display/nr-arena-forward.h \ + display/nr-arena-item.h \ + display/nr-arena.h \ + display/sodipodi-ctrlrect.h \ + display/sp-canvas-util.h \ + display/sp-canvas.h \ + document.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + helper/units.h \ + inkscape-private.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-div.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect-ops.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + line-snapper.h \ + macros.h \ + message-context.h \ + message-stack.h \ + message.h \ + object-hierarchy.h \ + object-snapper.h \ + prefs-utils.h \ + round.h \ + select-context.h \ + selection.h \ + snapped-point.h \ + snapper.h \ + sp-item-group.h \ + sp-item.h \ + sp-metric.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + sp-paint-server.h \ + traits/reference.h \ + ui/dialog/dialog-manager.h \ + ui/dialog/dialog.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +ui/view/edit-widget.o: \ + ui/view/edit-widget.cpp \ + application/app-prototype.h \ + application/editor.h \ + decimal-round.h \ + desktop.h \ + display/nr-arena-forward.h \ + display/sodipodi-ctrlrect.h \ + display/sp-canvas.h \ + document.h \ + enums.h \ + event-context.h \ + extension/db.h \ + extension/extension-forward.h \ + extension/extension.h \ + file.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/action.h \ + helper/helper-forward.h \ + helper/stock-items.h \ + helper/units.h \ + interface.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + line-snapper.h \ + macros.h \ + message.h \ + object-snapper.h \ + path-prefix.h \ + prefix.h \ + prefs-utils.h \ + require-config.h \ + round.h \ + shortcuts.h \ + snapped-point.h \ + snapper.h \ + sp-item.h \ + sp-metric.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + traits/reference.h \ + ui/dialog/dialog-manager.h \ + ui/dialog/dialog.h \ + ui/dialog/whiteboard-connect.h \ + ui/dialog/whiteboard-sharewithchat.h \ + ui/dialog/whiteboard-sharewithuser.h \ + ui/icons.h \ + ui/stock.h \ + ui/view/edit-widget-interface.h \ + ui/view/edit-widget.h \ + ui/view/view.h \ + ui/widget/button.h \ + ui/widget/handlebox.h \ + ui/widget/ruler.h \ + ui/widget/selected-style.h \ + ui/widget/svg-canvas.h \ + ui/widget/toolbox.h \ + ui/widget/zoom-status.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + verbs.h \ + version.h \ + widgets/layer-selector.h \ + widgets/spw-utilities.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +ui/view/edit.o: \ + ui/view/edit.cpp + +ui/view/view-widget.o: \ + ui/view/view-widget.cpp \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + message.h \ + ui/view/view-widget.h \ + ui/view/view.h + +ui/view/view.o: \ + ui/view/view.cpp \ + decimal-round.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/helper-forward.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-point.h \ + message-context.h \ + message-stack.h \ + message.h \ + require-config.h \ + round.h \ + ui/view/view.h \ + verbs.h + +ui/widget/button.o: \ + ui/widget/button.cpp \ + ui/widget/button.h + +ui/widget/color-picker.o: \ + ui/widget/color-picker.cpp \ + color.h \ + desktop-handles.h \ + display/display-forward.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + ui/dialog/dialog.h \ + ui/widget/button.h \ + ui/widget/color-picker.h \ + ui/widget/color-preview.h \ + widgets/sp-color-notebook.h \ + widgets/sp-color-selector.h + +ui/widget/color-preview.o: \ + ui/widget/color-preview.cpp \ + display/nr-plain-stuff-gdk.h \ + ui/widget/color-preview.h + +ui/widget/combo-text.o: \ + ui/widget/combo-text.cpp \ + ui/widget/combo-text.h + +ui/widget/entity-entry.o: \ + ui/widget/entity-entry.cpp \ + dialogs/rdf.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + ui/widget/entity-entry.h \ + ui/widget/registry.h + +ui/widget/handlebox.o: \ + ui/widget/handlebox.cpp \ + ui/widget/handlebox.h + +ui/widget/icon-widget.o: \ + ui/widget/icon-widget.cpp \ + ui/widget/icon-widget.h + +ui/widget/imageicon.o: \ + ui/widget/imageicon.cpp \ + display/display-forward.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + svg-view-widget.h \ + ui/view/view-widget.h \ + ui/widget/imageicon.h + +ui/widget/labelled.o: \ + ui/widget/labelled.cpp \ + ui/widget/labelled.h \ + widgets/icon.h + +ui/widget/licensor.o: \ + ui/widget/licensor.cpp \ + dialogs/rdf.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + ui/widget/entity-entry.h \ + ui/widget/licensor.h \ + ui/widget/registry.h + +ui/widget/notebook-page.o: \ + ui/widget/notebook-page.cpp \ + ui/widget/notebook-page.h + +ui/widget/page-sizer.o: \ + ui/widget/page-sizer.cpp \ + desktop-handles.h \ + display/display-forward.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/helper-forward.h \ + helper/unit-menu.h \ + helper/units.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + sp-metric.h \ + ui/widget/labelled.h \ + ui/widget/page-sizer.h \ + ui/widget/registered-widget.h \ + ui/widget/registry.h \ + ui/widget/scalar-unit.h \ + ui/widget/scalar.h + +ui/widget/panel.o: \ + ui/widget/panel.cpp \ + prefs-utils.h \ + ui/previewable.h \ + ui/previewfillable.h \ + ui/widget/button.h \ + ui/widget/panel.h + +ui/widget/preferences-widget.o: \ + ui/widget/preferences-widget.cpp \ + color.h \ + decimal-round.h \ + desktop-handles.h \ + display/display-forward.h \ + document.h \ + enums.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/helper-forward.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + message-stack.h \ + message.h \ + prefs-utils.h \ + require-config.h \ + round.h \ + selcue.h \ + selection-chemistry.h \ + selection.h \ + sp-marker-loc.h \ + style.h \ + traits/reference.h \ + ui/widget/preferences-widget.h \ + util/list.h \ + verbs.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +ui/widget/registered-widget.o: \ + ui/widget/registered-widget.cpp \ + decimal-round.h \ + desktop-handles.h \ + display/display-forward.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stringstream.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + helper/unit-menu.h \ + helper/units.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-point.h \ + line-snapper.h \ + object-snapper.h \ + round.h \ + snapped-point.h \ + snapper.h \ + sp-metric.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + traits/reference.h \ + ui/widget/button.h \ + ui/widget/color-picker.h \ + ui/widget/color-preview.h \ + ui/widget/labelled.h \ + ui/widget/registered-widget.h \ + ui/widget/registry.h \ + ui/widget/scalar-unit.h \ + ui/widget/scalar.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +ui/widget/registry.o: \ + ui/widget/registry.cpp \ + ui/widget/registry.h + +ui/widget/ruler.o: \ + ui/widget/ruler.cpp \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + display/display-forward.h \ + display/guideline.h \ + display/sp-canvas.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + helper/units.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + line-snapper.h \ + message.h \ + object-snapper.h \ + round.h \ + snapped-point.h \ + snapper.h \ + sp-metric.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + ui/widget/ruler.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +ui/widget/scalar-unit.o: \ + ui/widget/scalar-unit.cpp \ + helper/helper-forward.h \ + helper/unit-menu.h \ + ui/widget/labelled.h \ + ui/widget/scalar-unit.h \ + ui/widget/scalar.h + +ui/widget/scalar.o: \ + ui/widget/scalar.cpp \ + ui/widget/labelled.h \ + ui/widget/scalar.h + +ui/widget/selected-style.o: \ + ui/widget/selected-style.cpp \ + bad-uri-exception.h \ + color.h \ + decimal-round.h \ + desktop-handles.h \ + desktop-style.h \ + desktop.h \ + dialogs/object-properties.h \ + display/display-forward.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + enums.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/units.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + message.h \ + round.h \ + selection.h \ + sp-linear-gradient-fns.h \ + sp-marker-loc.h \ + sp-metric.h \ + sp-object.h \ + sp-paint-server.h \ + sp-pattern.h \ + sp-radial-gradient-fns.h \ + style.h \ + svg/css-ostringstream.h \ + svg/svg-length.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + ui/widget/button.h \ + ui/widget/color-preview.h \ + ui/widget/selected-style.h \ + uri-references.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + widgets/spinbutton-events.h \ + widgets/spw-utilities.h \ + widgets/widget-sizes.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +ui/widget/style-swatch.o: \ + ui/widget/style-swatch.cpp \ + bad-uri-exception.h \ + color.h \ + decimal-round.h \ + desktop.h \ + document.h \ + enums.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/units.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + message.h \ + round.h \ + sp-linear-gradient-fns.h \ + sp-marker-loc.h \ + sp-metric.h \ + sp-object.h \ + sp-paint-server.h \ + sp-pattern.h \ + sp-radial-gradient-fns.h \ + style.h \ + svg/svg-length.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + ui/widget/button.h \ + ui/widget/color-preview.h \ + ui/widget/style-swatch.h \ + uri-references.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + widgets/spw-utilities.h \ + widgets/widget-sizes.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +ui/widget/svg-canvas.o: \ + ui/widget/svg-canvas.cpp \ + decimal-round.h \ + desktop-events.h \ + desktop.h \ + display/canvas-arena.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/nr-arena-item.h \ + display/sp-canvas.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + message.h \ + round.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + ui/widget/svg-canvas.h + +ui/widget/tolerance-slider.o: \ + ui/widget/tolerance-slider.cpp \ + decimal-round.h \ + desktop-handles.h \ + display/display-forward.h \ + document.h \ + dom/dom.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/stringstream.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-point.h \ + line-snapper.h \ + object-snapper.h \ + round.h \ + snapped-point.h \ + snapper.h \ + sp-metric.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + traits/reference.h \ + ui/widget/registry.h \ + ui/widget/tolerance-slider.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +ui/widget/toolbox.o: \ + ui/widget/toolbox.cpp \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/action.h \ + helper/helper-forward.h \ + libnr/nr-object.h \ + path-prefix.h \ + prefix.h \ + require-config.h \ + ui/widget/handlebox.h \ + ui/widget/toolbox.h + +ui/widget/unit-menu.o: \ + ui/widget/unit-menu.cpp \ + helper/helper-forward.h \ + helper/sp-marshal.h \ + helper/unit-menu.h \ + helper/units.h \ + sp-metric.h \ + widgets/spw-utilities.h + +ui/widget/zoom-status.o: \ + ui/widget/zoom-status.cpp \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + display/display-forward.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + message.h \ + round.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + ui/widget/zoom-status.h \ + widgets/spw-utilities.h + +uri-references.o: \ + uri-references.cpp \ + bad-uri-exception.h \ + document.h \ + dom/dom.h \ + dom/domstring.h \ + dom/uri.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + sp-object.h \ + traits/reference.h \ + uri-references.h \ + util/forward-pointer-iterator.h \ + version.h + +uri.o: \ + uri.cpp \ + dom/dom.h \ + dom/domstring.h \ + dom/uri.h + +util/list-container-test.o: \ + util/list-container-test.cpp \ + gc-core.h \ + gc-managed.h \ + traits/reference.h \ + util/list-container.h \ + util/list.h + +util/shared-c-string-ptr.o: \ + util/shared-c-string-ptr.cpp \ + gc-core.h \ + util/shared-c-string-ptr.h + +util/units.o: \ + util/units.cpp \ + helper/units.h \ + sp-metric.h \ + svg/svg-length.h \ + unit-constants.h + +verbs.o: \ + verbs.cpp \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + dialogs/clonetiler.h \ + dialogs/display-settings.h \ + dialogs/extensions.h \ + dialogs/find.h \ + dialogs/iconpreview.h \ + dialogs/input.h \ + dialogs/item-properties.h \ + dialogs/layer-properties.h \ + dialogs/object-properties.h \ + dialogs/swatches.h \ + dialogs/text-edit.h \ + dialogs/xml-tree.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + event-context.h \ + extension/effect.h \ + extension/extension-forward.h \ + extension/extension.h \ + file.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + help.h \ + helper/action.h \ + helper/helper-forward.h \ + inkscape-private.h \ + inkscape.h \ + interface.h \ + layer-fns.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix-ops.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-path-code.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/Layout-TNG.h \ + line-snapper.h \ + livarot/LivarotDefs.h \ + livarot/Path.h \ + livarot/livarot-forward.h \ + message-stack.h \ + message.h \ + node-context.h \ + nodepath.h \ + object-snapper.h \ + path-chemistry.h \ + prefs-utils.h \ + require-config.h \ + round.h \ + selection-chemistry.h \ + selection.h \ + snapped-point.h \ + snapper.h \ + sp-flowtext.h \ + sp-item.h \ + sp-metric.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + splivarot.h \ + text-chemistry.h \ + tools-switch.h \ + traits/reference.h \ + ui/dialog/dialog-manager.h \ + ui/dialog/dialog.h \ + ui/dialog/whiteboard-connect.h \ + ui/dialog/whiteboard-sharewithchat.h \ + ui/dialog/whiteboard-sharewithuser.h \ + ui/previewable.h \ + ui/previewfillable.h \ + ui/previewholder.h \ + ui/stock.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + ui/widget/button.h \ + ui/widget/panel.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + verbs.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +version.o: \ + version.cpp \ + version.h + +widgets/button.o: \ + widgets/button.cpp \ + ui/widget/button.h + +widgets/dash-selector.o: \ + widgets/dash-selector.cpp \ + color.h \ + dialogs/dialog-events.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + libnr/nr-macros.h \ + sp-marker-loc.h \ + style.h \ + traits/reference.h \ + util/list.h \ + widgets/dash-selector.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +widgets/desktop-widget.o: \ + widgets/desktop-widget.cpp \ + decimal-round.h \ + desktop-events.h \ + desktop-handles.h \ + desktop.h \ + dialogs/swatches.h \ + display/canvas-arena.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + display/nr-arena-item.h \ + display/sp-canvas.h \ + document.h \ + enums.h \ + extension/db.h \ + extension/extension-forward.h \ + extension/extension.h \ + file.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/action.h \ + helper/helper-forward.h \ + helper/units.h \ + inkscape-private.h \ + inkscape.h \ + interface.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + line-snapper.h \ + macros.h \ + message.h \ + object-snapper.h \ + prefs-utils.h \ + round.h \ + snapped-point.h \ + snapper.h \ + sp-item.h \ + sp-metric.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + traits/reference.h \ + ui/previewable.h \ + ui/previewfillable.h \ + ui/previewholder.h \ + ui/view/edit-widget-interface.h \ + ui/view/view-widget.h \ + ui/view/view.h \ + ui/widget/button.h \ + ui/widget/handlebox.h \ + ui/widget/panel.h \ + ui/widget/ruler.h \ + ui/widget/selected-style.h \ + ui/widget/toolbox.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + widgets/desktop-widget.h \ + widgets/layer-selector.h \ + widgets/spinbutton-events.h \ + widgets/spw-utilities.h \ + widgets/widget-sizes.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +widgets/font-selector.o: \ + widgets/font-selector.cpp \ + decimal-round.h \ + display/nr-plain-stuff-gdk.h \ + libnr/nr-blit.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + libnrtype/FontFactory.h \ + libnrtype/RasterFont.h \ + libnrtype/TextWrapper.h \ + libnrtype/boundary-type.h \ + libnrtype/font-instance.h \ + libnrtype/font-style.h \ + libnrtype/nr-type-pos-def.h \ + libnrtype/nr-type-primitives.h \ + libnrtype/nrtype-forward.h \ + libnrtype/one-glyph.h \ + libnrtype/raster-glyph.h \ + livarot/LivarotDefs.h \ + livarot/livarot-forward.h \ + require-config.h \ + round.h \ + widgets/font-selector.h + +widgets/gradient-image.o: \ + widgets/gradient-image.cpp \ + decimal-round.h \ + display/nr-plain-stuff-gdk.h \ + display/nr-plain-stuff.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock-pattern.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + macros.h \ + round.h \ + sp-gradient-fns.h \ + sp-gradient-spread.h \ + sp-gradient-units.h \ + widgets/gradient-image.h + +widgets/gradient-selector.o: \ + widgets/gradient-selector.cpp \ + color.h \ + composite-undo-stack-observer.h \ + decimal-round.h \ + display/nr-arena-forward.h \ + document-private.h \ + document.h \ + enums.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + gradient-chemistry.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-defs.h \ + sp-gradient-fns.h \ + sp-gradient-spread.h \ + sp-gradient-units.h \ + sp-gradient-vector.h \ + sp-gradient.h \ + sp-item-group.h \ + sp-item.h \ + sp-object.h \ + sp-paint-server.h \ + sp-root.h \ + svg/svg-length.h \ + traits/reference.h \ + undo-stack-observer.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + widgets/gradient-selector.h \ + widgets/gradient-vector.h \ + xml/event-fns.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +widgets/gradient-toolbar.o: \ + widgets/gradient-toolbar.cpp \ + color.h \ + composite-undo-stack-observer.h \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document-private.h \ + document.h \ + enums.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + gradient-chemistry.h \ + gradient-context.h \ + gradient-drag.h \ + helper/action.h \ + helper/helper-forward.h \ + knot-enums.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + macros.h \ + message.h \ + prefs-utils.h \ + round.h \ + selection.h \ + sp-defs.h \ + sp-gradient-fns.h \ + sp-gradient-spread.h \ + sp-gradient-units.h \ + sp-gradient-vector.h \ + sp-gradient.h \ + sp-item-group.h \ + sp-item.h \ + sp-linear-gradient-fns.h \ + sp-linear-gradient.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-paint-server.h \ + sp-radial-gradient-fns.h \ + sp-radial-gradient.h \ + sp-root.h \ + style.h \ + svg/svg-length.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + ui/widget/button.h \ + ui/widget/handlebox.h \ + ui/widget/toolbox.h \ + undo-stack-observer.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + widgets/gradient-image.h \ + widgets/gradient-vector.h \ + widgets/spinbutton-events.h \ + widgets/spw-utilities.h \ + widgets/widget-sizes.h \ + xml/event-fns.h + +widgets/gradient-vector.o: \ + widgets/gradient-vector.cpp \ + color.h \ + composite-undo-stack-observer.h \ + decimal-round.h \ + dialogs/dialog-events.h \ + display/nr-arena-forward.h \ + document-private.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + enums.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + gradient-chemistry.h \ + helper/window.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + macros.h \ + prefs-utils.h \ + round.h \ + sp-defs.h \ + sp-gradient-fns.h \ + sp-gradient-spread.h \ + sp-gradient-units.h \ + sp-gradient-vector.h \ + sp-gradient.h \ + sp-item-group.h \ + sp-item.h \ + sp-object.h \ + sp-paint-server.h \ + sp-root.h \ + sp-stop-fns.h \ + sp-stop.h \ + svg/css-ostringstream.h \ + svg/svg-length.h \ + traits/reference.h \ + undo-stack-observer.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + widgets/gradient-image.h \ + widgets/gradient-vector.h \ + widgets/sp-color-notebook.h \ + widgets/sp-color-preview.h \ + widgets/sp-color-selector.h \ + widgets/widget-sizes.h \ + xml/event-fns.h \ + xml/node-event-vector.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +widgets/icon.o: \ + widgets/icon.cpp \ + decimal-round.h \ + display/nr-arena-forward.h \ + display/nr-arena-item.h \ + display/nr-arena.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape.h \ + io/sys.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + path-prefix.h \ + prefix.h \ + prefs-utils.h \ + require-config.h \ + round.h \ + sp-item.h \ + sp-object.h \ + sp-paint-server.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + version.h \ + widgets/icon.h + +widgets/layer-selector.o: \ + widgets/layer-selector.cpp \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + dialogs/layer-properties.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + message.h \ + round.h \ + selection.h \ + sp-item.h \ + sp-object.h \ + traits/copy.h \ + traits/list-copy.h \ + traits/reference.h \ + ui/dialog/dialog.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + util/filter-list.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + util/reverse-list.h \ + version.h \ + widgets/icon.h \ + widgets/layer-selector.h \ + widgets/shrink-wrap-button.h \ + xml/node-event-vector.h \ + xml/node.h + +widgets/paint-selector.o: \ + widgets/paint-selector.cpp \ + bad-uri-exception.h \ + color.h \ + composite-undo-stack-observer.h \ + decimal-round.h \ + desktop-style.h \ + display/nr-arena-forward.h \ + document-private.h \ + document.h \ + dom/css.h \ + dom/dom.h \ + dom/domstring.h \ + dom/events.h \ + dom/smil.h \ + dom/stylesheets.h \ + dom/svg.h \ + dom/svgtypes.h \ + dom/views.h \ + enums.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape-stock.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-pixblock.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-defs.h \ + sp-gradient-fns.h \ + sp-gradient-spread.h \ + sp-gradient-units.h \ + sp-gradient-vector.h \ + sp-gradient.h \ + sp-item-group.h \ + sp-item.h \ + sp-linear-gradient-fns.h \ + sp-marker-loc.h \ + sp-object.h \ + sp-paint-server.h \ + sp-pattern.h \ + sp-radial-gradient-fns.h \ + sp-root.h \ + style.h \ + svg/css-ostringstream.h \ + svg/svg-length.h \ + traits/reference.h \ + undo-stack-observer.h \ + uri-references.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + widgets/gradient-selector.h \ + widgets/icon.h \ + widgets/paint-selector.h \ + widgets/sp-color-notebook.h \ + widgets/sp-color-selector.h \ + widgets/widget-sizes.h \ + xml/event-fns.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +widgets/ruler.o: \ + widgets/ruler.cpp \ + decimal-round.h \ + desktop-handles.h \ + desktop.h \ + display/display-forward.h \ + display/guideline.h \ + display/sp-canvas.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/helper-forward.h \ + helper/units.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-l.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect-l.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + line-snapper.h \ + message.h \ + object-snapper.h \ + round.h \ + snapped-point.h \ + snapper.h \ + sp-metric.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + ui/widget/ruler.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +widgets/select-toolbar.o: \ + widgets/select-toolbar.cpp \ + decimal-round.h \ + desktop-handles.h \ + desktop-style.h \ + desktop.h \ + display/display-forward.h \ + document.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + grid-snapper.h \ + guide-snapper.h \ + helper/action.h \ + helper/helper-forward.h \ + helper/unit-menu.h \ + helper/units.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-object.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + line-snapper.h \ + message-stack.h \ + message.h \ + object-snapper.h \ + prefs-utils.h \ + require-config.h \ + round.h \ + selection-chemistry.h \ + selection.h \ + snapped-point.h \ + snapper.h \ + sp-item-transform.h \ + sp-metric.h \ + sp-namedview.h \ + sp-object-group.h \ + sp-object.h \ + traits/reference.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + ui/widget/button.h \ + ui/widget/handlebox.h \ + ui/widget/toolbox.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + verbs.h \ + version.h \ + widgets/sp-widget.h \ + widgets/spinbutton-events.h \ + widgets/spw-utilities.h \ + widgets/widget-sizes.h + +widgets/shrink-wrap-button.o: \ + widgets/shrink-wrap-button.cpp \ + ui/widget/button.h + +widgets/sp-color-gtkselector.o: \ + widgets/sp-color-gtkselector.cpp \ + color.h \ + widgets/sp-color-gtkselector.h \ + widgets/sp-color-selector.h + +widgets/sp-color-notebook.o: \ + widgets/sp-color-notebook.cpp \ + color.h \ + dialogs/dialog-events.h \ + forward.h \ + prefs-utils.h \ + widgets/sp-color-notebook.h \ + widgets/sp-color-scales.h \ + widgets/sp-color-selector.h \ + widgets/sp-color-slider.h \ + widgets/sp-color-wheel-selector.h \ + widgets/sp-color-wheel.h \ + widgets/spw-utilities.h + +widgets/sp-color-preview.o: \ + widgets/sp-color-preview.cpp \ + display/nr-plain-stuff-gdk.h \ + widgets/sp-color-preview.h + +widgets/sp-color-scales.o: \ + widgets/sp-color-scales.cpp \ + color.h \ + dialogs/dialog-events.h \ + forward.h \ + widgets/sp-color-scales.h \ + widgets/sp-color-selector.h \ + widgets/sp-color-slider.h + +widgets/sp-color-selector.o: \ + widgets/sp-color-selector.cpp \ + color.h \ + widgets/sp-color-selector.h + +widgets/sp-color-slider.o: \ + widgets/sp-color-slider.cpp \ + color.h \ + widgets/sp-color-scales.h \ + widgets/sp-color-selector.h \ + widgets/sp-color-slider.h + +widgets/sp-color-wheel-selector.o: \ + widgets/sp-color-wheel-selector.cpp \ + color.h \ + dialogs/dialog-events.h \ + forward.h \ + widgets/sp-color-scales.h \ + widgets/sp-color-selector.h \ + widgets/sp-color-slider.h \ + widgets/sp-color-wheel-selector.h \ + widgets/sp-color-wheel.h + +widgets/sp-color-wheel.o: \ + widgets/sp-color-wheel.cpp \ + color.h \ + decimal-round.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-macros.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rotate-ops.h \ + libnr/nr-rotate.h \ + round.h \ + widgets/sp-color-wheel.h + +widgets/sp-widget.o: \ + widgets/sp-widget.cpp \ + document.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + macros.h \ + widgets/sp-widget.h + +widgets/sp-xmlview-attr-list.o: \ + widgets/sp-xmlview-attr-list.cpp \ + document.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/sp-marshal.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + traits/reference.h \ + util/list.h \ + widgets/sp-xmlview-attr-list.h \ + xml/node-event-vector.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +widgets/sp-xmlview-content.o: \ + widgets/sp-xmlview-content.cpp \ + composite-undo-stack-observer.h \ + decimal-round.h \ + desktop-handles.h \ + display/display-forward.h \ + display/nr-arena-forward.h \ + document-private.h \ + document.h \ + enums.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + inkscape.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + sp-defs.h \ + sp-item-group.h \ + sp-item.h \ + sp-object.h \ + sp-root.h \ + svg/svg-length.h \ + traits/reference.h \ + undo-stack-observer.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + version.h \ + widgets/sp-xmlview-content.h \ + xml/event-fns.h \ + xml/node-event-vector.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +widgets/sp-xmlview-tree.o: \ + widgets/sp-xmlview-tree.cpp \ + document.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + traits/reference.h \ + util/list.h \ + widgets/sp-xmlview-tree.h \ + xml/node-event-vector.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +widgets/spinbutton-events.o: \ + widgets/spinbutton-events.cpp \ + event-context.h \ + forward.h \ + widgets/sp-widget.h \ + widgets/widget-sizes.h + +widgets/spw-utilities.o: \ + widgets/spw-utilities.cpp \ + decimal-round.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/helper-forward.h \ + helper/unit-menu.h \ + libnr/nr-convex-hull.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + round.h \ + selection.h \ + traits/reference.h \ + util/list.h + +widgets/toolbox.o: \ + widgets/toolbox.cpp \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + helper/action.h \ + helper/helper-forward.h \ + libnr/nr-object.h \ + path-prefix.h \ + prefix.h \ + require-config.h \ + ui/widget/handlebox.h \ + ui/widget/toolbox.h + +xml/composite-node-observer.o: \ + xml/composite-node-observer.cpp \ + algorithms/find-if-before.h \ + debug/event-tracker.h \ + debug/event.h \ + debug/logger.h \ + debug/simple-event.h \ + gc-anchored.h \ + gc-core.h \ + gc-managed.h \ + traits/reference.h \ + util/list-container.h \ + util/list.h \ + util/shared-c-string-ptr.h \ + xml/composite-node-observer.h \ + xml/node-event-vector.h \ + xml/node-observer.h \ + xml/node.h + +xml/croco-node-iface.o: \ + xml/croco-node-iface.cpp \ + gc-anchored.h \ + gc-core.h \ + gc-managed.h \ + libcroco/cr-node-iface.h \ + traits/reference.h \ + util/list.h \ + xml/croco-node-iface.h \ + xml/node.h + +xml/event.o: \ + xml/event.cpp \ + debug/event-tracker.h \ + debug/event.h \ + debug/logger.h \ + debug/simple-event.h \ + document.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + traits/copy.h \ + traits/list-copy.h \ + traits/reference.h \ + util/list.h \ + util/reverse-list.h \ + util/shared-c-string-ptr.h \ + xml/event-fns.h \ + xml/node-observer.h + +xml/log-builder.o: \ + xml/log-builder.cpp \ + debug/event.h \ + gc-core.h \ + gc-managed.h \ + util/shared-c-string-ptr.h \ + xml/event-fns.h \ + xml/log-builder.h \ + xml/node-observer.h + +xml/node-fns.o: \ + xml/node-fns.cpp \ + algorithms/find-if-before.h \ + gc-anchored.h \ + gc-core.h \ + gc-managed.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + xml/node-iterators.h \ + xml/node.h + +xml/quote.o: \ + xml/quote.cpp + +xml/repr-css.o: \ + xml/repr-css.cpp \ + document.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + traits/reference.h \ + util/list-container.h \ + util/list.h \ + util/shared-c-string-ptr.h \ + xml/attribute-record.h \ + xml/composite-node-observer.h \ + xml/node-observer.h \ + xml/node.h \ + xml/repr.h \ + xml/simple-node.h \ + xml/sp-css-attr.h \ + xml/transaction-logger.h + +xml/repr-io.o: \ + xml/repr-io.cpp \ + document.h \ + dom/dom.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/uri.h \ + dom/uristream.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + io/gzipstream.h \ + io/inkscapestream.h \ + io/sys.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + traits/reference.h \ + util/list.h \ + util/shared-c-string-ptr.h \ + xml/attribute-record.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +xml/repr-sorting.o: \ + xml/repr-sorting.cpp \ + algorithms/longest-common-suffix.h \ + document.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + traits/reference.h \ + util/forward-pointer-iterator.h \ + util/list.h \ + xml/node-iterators.h \ + xml/node.h \ + xml/repr.h \ + xml/sp-css-attr.h + +xml/repr-util.o: \ + xml/repr-util.cpp \ + document.h \ + dom/dom.h \ + dom/domstream.h \ + dom/domstring.h \ + dom/stringstream.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + svg/css-ostringstream.h \ + traits/reference.h \ + util/list.h \ + xml/node.h \ + xml/repr-sorting.h \ + xml/repr.h \ + xml/sp-css-attr.h + +xml/repr.o: \ + xml/repr.cpp \ + document.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + traits/reference.h \ + util/list-container.h \ + util/list.h \ + util/shared-c-string-ptr.h \ + xml/attribute-record.h \ + xml/comment-node.h \ + xml/composite-node-observer.h \ + xml/element-node.h \ + xml/node-observer.h \ + xml/node.h \ + xml/repr.h \ + xml/simple-document.h \ + xml/simple-node.h \ + xml/sp-css-attr.h \ + xml/text-node.h \ + xml/transaction-logger.h + +xml/simple-document.o: \ + xml/simple-document.cpp \ + document.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + traits/reference.h \ + util/list-container.h \ + util/list.h \ + util/shared-c-string-ptr.h \ + xml/attribute-record.h \ + xml/composite-node-observer.h \ + xml/log-builder.h \ + xml/node-observer.h \ + xml/node.h \ + xml/session.h \ + xml/simple-document.h \ + xml/simple-node.h \ + xml/simple-session.h \ + xml/transaction-logger.h + +xml/simple-node.o: \ + xml/simple-node.cpp \ + debug/event-tracker.h \ + debug/event.h \ + debug/logger.h \ + document.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libcroco/cr-additional-sel.h \ + libcroco/cr-attr-sel.h \ + libcroco/cr-cascade.h \ + libcroco/cr-declaration.h \ + libcroco/cr-num.h \ + libcroco/cr-parsing-location.h \ + libcroco/cr-pseudo.h \ + libcroco/cr-rgb.h \ + libcroco/cr-selector.h \ + libcroco/cr-simple-sel.h \ + libcroco/cr-statement.h \ + libcroco/cr-string.h \ + libcroco/cr-stylesheet.h \ + libcroco/cr-term.h \ + libcroco/cr-utils.h \ + libnr/nr-forward.h \ + traits/reference.h \ + util/list-container.h \ + util/list.h \ + util/shared-c-string-ptr.h \ + xml/attribute-record.h \ + xml/composite-node-observer.h \ + xml/node-event-vector.h \ + xml/node-fns.h \ + xml/node-observer.h \ + xml/node.h \ + xml/repr.h \ + xml/simple-node.h \ + xml/sp-css-attr.h \ + xml/transaction-logger.h + +xml/simple-session.o: \ + xml/simple-session.cpp \ + gc-anchored.h \ + gc-core.h \ + gc-managed.h \ + traits/reference.h \ + util/list-container.h \ + util/list.h \ + util/shared-c-string-ptr.h \ + xml/attribute-record.h \ + xml/comment-node.h \ + xml/composite-node-observer.h \ + xml/element-node.h \ + xml/event-fns.h \ + xml/log-builder.h \ + xml/node-observer.h \ + xml/node.h \ + xml/session.h \ + xml/simple-node.h \ + xml/simple-session.h \ + xml/text-node.h \ + xml/transaction-logger.h + +zoom-context.o: \ + zoom-context.cpp \ + decimal-round.h \ + desktop.h \ + event-context.h \ + forward.h \ + gc-anchored.h \ + gc-core.h \ + gc-finalized.h \ + gc-managed.h \ + libnr/nr-coord.h \ + libnr/nr-dim2.h \ + libnr/nr-forward.h \ + libnr/nr-i-coord.h \ + libnr/nr-macros.h \ + libnr/nr-matrix-fns.h \ + libnr/nr-matrix.h \ + libnr/nr-maybe.h \ + libnr/nr-point-fns.h \ + libnr/nr-point-matrix-ops.h \ + libnr/nr-point-ops.h \ + libnr/nr-point.h \ + libnr/nr-rect.h \ + libnr/nr-rotate.h \ + libnr/nr-scale.h \ + libnr/nr-translate.h \ + libnr/nr-values.h \ + macros.h \ + message.h \ + prefs-utils.h \ + round.h \ + rubberband.h \ + ui/view/edit-widget-interface.h \ + ui/view/view.h \ + zoom-context.h diff --git a/src/make.exclude b/src/make.exclude new file mode 100644 index 000000000..179863c6e --- /dev/null +++ b/src/make.exclude @@ -0,0 +1,80 @@ +###################################################################### +# File: make.exclude +# +# This is a list of files to exclude from +# building using the Makedep scheme. +# To use, run: +# perl mkfiles.pl +# then +# perl mkdep.pl +# +###################################################################### + + +ast +bonobo + +dialogs/filedialog-win32.cpp +display/testnr.cpp +display/bezier-utils-test.cpp +dom/testdom.cpp +dom/testsvg.cpp +dom/xpathtest.cpp +dom/xpathtests.cpp +extension/api.cpp +extension/dxf2svg +extension/internal/gnome.cpp +extension/script/js +extension/script/bindtest.cpp +extension/script/cpptest.cpp +extension/plugin +extract-uri-test.cpp +helper/units-test.cpp +inkview.cpp +libnr/in-svg-plane-test.cpp +libnr/nr-matrix-test.cpp +libnr/nr-point-fns-test.cpp +libnr/nr-rotate-fns-test.cpp +libnr/nr-rotate-test.cpp +libnr/nr-scale-test.cpp +libnr/nr-translate-test.cpp +libnr/nr-types-test.cpp +livarot/Path-test.cpp +main.cpp +mod360-test.cpp +trace/potrace/potest.cpp +round-test.cpp +sp-gradient-test.cpp +svg/ftos.cpp +utest +widgets/test-widgets.cpp +winmain.cpp +xml/quote-test.cpp +xml/repr-action-test.cpp +io/streamtest.cpp + + +############################################### +# Bob: +# Uncomment these to build Inkboard +# For the moment, DO NOT remove these from cvs +# until those files compile everywhere +############################################### +dialogs/whiteboard-connect-dialog.cpp +dialogs/whiteboard-common-dialog.cpp +dialogs/whiteboard-sharewithchat-dialog.cpp +dialogs/whiteboard-sharewithuser-dialog.cpp +jabber_whiteboard +ui/dialog/session-player.cpp +ui/dialog/whiteboard-connect.cpp +ui/dialog/whiteboard-sharewithchat.cpp +ui/dialog/whiteboard-sharewithuser.cpp + +############################################### +# Various test harnesses in removeoverlap +############################################### +removeoverlap/placement_SolveVPSC.cpp +removeoverlap/placement_SolveVPSC.h +removeoverlap/test.cpp +removeoverlap/remove_rectangle_overlap-test.cpp +removeoverlap/remove_rectangle_overlap-test.h \ No newline at end of file diff --git a/src/make.files b/src/make.files new file mode 100644 index 000000000..8d7a04f56 --- /dev/null +++ b/src/make.files @@ -0,0 +1,1339 @@ +######################################################## +## File: make.files +## Purpose: Used by mkdep.pl +## Generated by mkfiles.pl at :Sun Jan 15 07:06:12 2006 +######################################################## + +algorithms/find-if-before.h +algorithms/find-last-if.h +algorithms/longest-common-suffix.h +application/app-prototype.cpp +application/app-prototype.h +application/application.cpp +application/application.h +application/editor.cpp +application/editor.h +approx-equal.h +arc-context.cpp +arc-context.h +attributes-test.cpp +attributes.cpp +attributes.h +bad-uri-exception.h +color-rgba.h +color.cpp +color.h +composite-undo-stack-observer.cpp +composite-undo-stack-observer.h +conn-avoid-ref.cpp +conn-avoid-ref.h +connector-context.cpp +connector-context.h +context-fns.cpp +context-fns.h +debug/event-tracker.h +debug/event.h +debug/gc-heap.h +debug/heap.cpp +debug/heap.h +debug/logger.cpp +debug/logger.h +debug/simple-event.h +debug/sysv-heap.cpp +debug/sysv-heap.h +decimal-round.h +desktop-affine.cpp +desktop-affine.h +desktop-events.cpp +desktop-events.h +desktop-handles.cpp +desktop-handles.h +desktop-style.cpp +desktop-style.h +desktop.cpp +desktop.h +dialogs/clonetiler.cpp +dialogs/clonetiler.h +dialogs/debugdialog.cpp +dialogs/debugdialog.h +dialogs/dialog-events.cpp +dialogs/dialog-events.h +dialogs/display-settings.cpp +dialogs/display-settings.h +dialogs/eek-preview.cpp +dialogs/eek-preview.h +dialogs/export.cpp +dialogs/export.h +dialogs/extensions.cpp +dialogs/extensions.h +dialogs/filedialog.cpp +dialogs/filedialog.h +dialogs/fill-style.cpp +dialogs/fill-style.h +dialogs/find.cpp +dialogs/find.h +dialogs/iconpreview.cpp +dialogs/iconpreview.h +dialogs/in-dt-coordsys.cpp +dialogs/in-dt-coordsys.h +dialogs/input.cpp +dialogs/input.h +dialogs/item-properties.cpp +dialogs/item-properties.h +dialogs/layer-properties.cpp +dialogs/layer-properties.h +dialogs/object-attributes.cpp +dialogs/object-attributes.h +dialogs/object-properties.cpp +dialogs/object-properties.h +dialogs/rdf.cpp +dialogs/rdf.h +dialogs/sp-attribute-widget.cpp +dialogs/sp-attribute-widget.h +dialogs/stroke-style.cpp +dialogs/stroke-style.h +dialogs/swatches.cpp +dialogs/swatches.h +dialogs/text-edit.cpp +dialogs/text-edit.h +dialogs/tiledialog.cpp +dialogs/tiledialog.h +dialogs/unclump.cpp +dialogs/unclump.h +dialogs/xml-tree.cpp +dialogs/xml-tree.h +dir-util-test.cpp +dir-util.cpp +dir-util.h +display/bezier-utils.cpp +display/bezier-utils.h +display/canvas-arena.cpp +display/canvas-arena.h +display/canvas-bpath.cpp +display/canvas-bpath.h +display/canvas-grid.cpp +display/canvas-grid.h +display/curve.cpp +display/curve.h +display/display-forward.h +display/gnome-canvas-acetate.cpp +display/gnome-canvas-acetate.h +display/guideline.cpp +display/guideline.h +display/nr-arena-forward.h +display/nr-arena-glyphs.cpp +display/nr-arena-glyphs.h +display/nr-arena-group.cpp +display/nr-arena-group.h +display/nr-arena-image.cpp +display/nr-arena-image.h +display/nr-arena-item.cpp +display/nr-arena-item.h +display/nr-arena-shape.cpp +display/nr-arena-shape.h +display/nr-arena.cpp +display/nr-arena.h +display/nr-gradient-gpl.cpp +display/nr-gradient-gpl.h +display/nr-plain-stuff-gdk.cpp +display/nr-plain-stuff-gdk.h +display/nr-plain-stuff.cpp +display/nr-plain-stuff.h +display/sodipodi-ctrl.cpp +display/sodipodi-ctrl.h +display/sodipodi-ctrlrect.cpp +display/sodipodi-ctrlrect.h +display/sp-canvas-util.cpp +display/sp-canvas-util.h +display/sp-canvas.cpp +display/sp-canvas.h +display/sp-ctrlline.cpp +display/sp-ctrlline.h +display/sp-ctrlquadr.cpp +display/sp-ctrlquadr.h +document-private.h +document-undo.cpp +document.cpp +document.h +dom/Makefile.static +dom/charclass.cpp +dom/charclass.h +dom/css.h +dom/cssparser.cpp +dom/cssparser.h +dom/dom.h +dom/domimpl.cpp +dom/domimpl.h +dom/domstream.cpp +dom/domstream.h +dom/domstring.cpp +dom/domstring.h +dom/domstringimpl.h +dom/events.h +dom/js/fdlibm/e_acos.c +dom/js/fdlibm/e_acosh.c +dom/js/fdlibm/e_asin.c +dom/js/fdlibm/e_atan2.c +dom/js/fdlibm/e_atanh.c +dom/js/fdlibm/e_cosh.c +dom/js/fdlibm/e_exp.c +dom/js/fdlibm/e_fmod.c +dom/js/fdlibm/e_gamma.c +dom/js/fdlibm/e_gamma_r.c +dom/js/fdlibm/e_hypot.c +dom/js/fdlibm/e_j0.c +dom/js/fdlibm/e_j1.c +dom/js/fdlibm/e_jn.c +dom/js/fdlibm/e_lgamma.c +dom/js/fdlibm/e_lgamma_r.c +dom/js/fdlibm/e_log.c +dom/js/fdlibm/e_log10.c +dom/js/fdlibm/e_pow.c +dom/js/fdlibm/e_rem_pio2.c +dom/js/fdlibm/e_remainder.c +dom/js/fdlibm/e_scalb.c +dom/js/fdlibm/e_sinh.c +dom/js/fdlibm/e_sqrt.c +dom/js/fdlibm/fdlibm.h +dom/js/fdlibm/k_cos.c +dom/js/fdlibm/k_rem_pio2.c +dom/js/fdlibm/k_sin.c +dom/js/fdlibm/k_standard.c +dom/js/fdlibm/k_tan.c +dom/js/fdlibm/s_asinh.c +dom/js/fdlibm/s_atan.c +dom/js/fdlibm/s_cbrt.c +dom/js/fdlibm/s_ceil.c +dom/js/fdlibm/s_copysign.c +dom/js/fdlibm/s_cos.c +dom/js/fdlibm/s_erf.c +dom/js/fdlibm/s_expm1.c +dom/js/fdlibm/s_fabs.c +dom/js/fdlibm/s_finite.c +dom/js/fdlibm/s_floor.c +dom/js/fdlibm/s_frexp.c +dom/js/fdlibm/s_ilogb.c +dom/js/fdlibm/s_isnan.c +dom/js/fdlibm/s_ldexp.c +dom/js/fdlibm/s_lib_version.c +dom/js/fdlibm/s_log1p.c +dom/js/fdlibm/s_logb.c +dom/js/fdlibm/s_matherr.c +dom/js/fdlibm/s_modf.c +dom/js/fdlibm/s_nextafter.c +dom/js/fdlibm/s_rint.c +dom/js/fdlibm/s_scalbn.c +dom/js/fdlibm/s_signgam.c +dom/js/fdlibm/s_significand.c +dom/js/fdlibm/s_sin.c +dom/js/fdlibm/s_tan.c +dom/js/fdlibm/s_tanh.c +dom/js/fdlibm/w_acos.c +dom/js/fdlibm/w_acosh.c +dom/js/fdlibm/w_asin.c +dom/js/fdlibm/w_atan2.c +dom/js/fdlibm/w_atanh.c +dom/js/fdlibm/w_cosh.c +dom/js/fdlibm/w_exp.c +dom/js/fdlibm/w_fmod.c +dom/js/fdlibm/w_gamma.c +dom/js/fdlibm/w_gamma_r.c +dom/js/fdlibm/w_hypot.c +dom/js/fdlibm/w_j0.c +dom/js/fdlibm/w_j1.c +dom/js/fdlibm/w_jn.c +dom/js/fdlibm/w_lgamma.c +dom/js/fdlibm/w_lgamma_r.c +dom/js/fdlibm/w_log.c +dom/js/fdlibm/w_log10.c +dom/js/fdlibm/w_pow.c +dom/js/fdlibm/w_remainder.c +dom/js/fdlibm/w_scalb.c +dom/js/fdlibm/w_sinh.c +dom/js/fdlibm/w_sqrt.c +dom/js/js.c +dom/js/jsapi.c +dom/js/jsapi.h +dom/js/jsarena.c +dom/js/jsarena.h +dom/js/jsarray.c +dom/js/jsarray.h +dom/js/jsatom.c +dom/js/jsatom.h +dom/js/jsautocfg.h +dom/js/jsbit.h +dom/js/jsbool.c +dom/js/jsbool.h +dom/js/jsclist.h +dom/js/jscntxt.c +dom/js/jscntxt.h +dom/js/jscompat.h +dom/js/jsconfig.h +dom/js/jscpucfg.c +dom/js/jscpucfg.h +dom/js/jsdate.c +dom/js/jsdate.h +dom/js/jsdbgapi.c +dom/js/jsdbgapi.h +dom/js/jsdhash.c +dom/js/jsdhash.h +dom/js/jsdtoa.c +dom/js/jsdtoa.h +dom/js/jsemit.c +dom/js/jsemit.h +dom/js/jsexn.c +dom/js/jsexn.h +dom/js/jsfile.c +dom/js/jsfile.h +dom/js/jsfun.c +dom/js/jsfun.h +dom/js/jsgc.c +dom/js/jsgc.h +dom/js/jshash.c +dom/js/jshash.h +dom/js/jsinterp.c +dom/js/jsinterp.h +dom/js/jslibmath.h +dom/js/jslock.c +dom/js/jslock.h +dom/js/jslog2.c +dom/js/jslong.c +dom/js/jslong.h +dom/js/jsmath.c +dom/js/jsmath.h +dom/js/jsnum.c +dom/js/jsnum.h +dom/js/jsobj.c +dom/js/jsobj.h +dom/js/jsopcode.c +dom/js/jsopcode.h +dom/js/jsosdep.h +dom/js/jsotypes.h +dom/js/jsparse.c +dom/js/jsparse.h +dom/js/jsprf.c +dom/js/jsprf.h +dom/js/jsprvtd.h +dom/js/jspubtd.h +dom/js/jsregexp.c +dom/js/jsregexp.h +dom/js/jsscan.c +dom/js/jsscan.h +dom/js/jsscope.c +dom/js/jsscope.h +dom/js/jsscript.c +dom/js/jsscript.h +dom/js/jsstddef.h +dom/js/jsstr.c +dom/js/jsstr.h +dom/js/jstypes.h +dom/js/jsutil.c +dom/js/jsutil.h +dom/js/jsxdrapi.c +dom/js/jsxdrapi.h +dom/js/prmjtime.c +dom/js/prmjtime.h +dom/js/resource.h +dom/ls.h +dom/lsimpl.cpp +dom/lsimpl.h +dom/phoebedom.h +dom/prop-css.cpp +dom/prop-css2.cpp +dom/prop-svg.cpp +dom/smil.h +dom/smilimpl.cpp +dom/smilimpl.h +dom/stringstream.cpp +dom/stringstream.h +dom/stylesheets.h +dom/svg.h +dom/svgimpl.cpp +dom/svgimpl.h +dom/svglsimpl.cpp +dom/svglsimpl.h +dom/svgparser.cpp +dom/svgparser.h +dom/svgtypes.h +dom/traversal.h +dom/uri.cpp +dom/uri.h +dom/uristream.cpp +dom/uristream.h +dom/uritest.cpp +dom/views.h +dom/xmlreader.cpp +dom/xmlreader.h +dom/xpath.h +dom/xpathimpl.cpp +dom/xpathimpl.h +dom/xpathparser.cpp +dom/xpathparser.h +draw-anchor.cpp +draw-anchor.h +draw-context.cpp +draw-context.h +dropper-context.cpp +dropper-context.h +dyna-draw-context.cpp +dyna-draw-context.h +enums.h +event-context.cpp +event-context.h +extension/db.cpp +extension/db.h +extension/dependency.cpp +extension/dependency.h +extension/effect.cpp +extension/effect.h +extension/error-file.cpp +extension/error-file.h +extension/extension-forward.h +extension/extension.cpp +extension/extension.h +extension/implementation/implementation.cpp +extension/implementation/implementation.h +extension/implementation/plugin-link.h +extension/implementation/plugin.cpp +extension/implementation/plugin.h +extension/implementation/script.cpp +extension/implementation/script.h +extension/init.cpp +extension/init.h +extension/input.cpp +extension/input.h +extension/internal/bluredge.cpp +extension/internal/bluredge.h +extension/internal/eps-out.cpp +extension/internal/eps-out.h +extension/internal/gdkpixbuf-input.cpp +extension/internal/gdkpixbuf-input.h +extension/internal/gimpgrad.cpp +extension/internal/gimpgrad.h +extension/internal/gnome.h +extension/internal/grid.cpp +extension/internal/grid.h +extension/internal/latex-pstricks-out.cpp +extension/internal/latex-pstricks-out.h +extension/internal/latex-pstricks.cpp +extension/internal/latex-pstricks.h +extension/internal/pov-out.cpp +extension/internal/pov-out.h +extension/internal/ps-out.cpp +extension/internal/ps-out.h +extension/internal/ps.cpp +extension/internal/ps.h +extension/internal/svg.cpp +extension/internal/svg.h +extension/internal/svgz.cpp +extension/internal/svgz.h +extension/internal/win32.cpp +extension/internal/win32.h +extension/output.cpp +extension/output.h +extension/parameter.cpp +extension/parameter.h +extension/prefdialog.cpp +extension/prefdialog.h +extension/print.cpp +extension/print.h +extension/script/InkscapeBinding.cpp +extension/script/InkscapeBinding.h +extension/script/InkscapeInterpreter.cpp +extension/script/InkscapeInterpreter.h +extension/script/InkscapePerl.cpp +extension/script/InkscapePerl.h +extension/script/InkscapePython.cpp +extension/script/InkscapePython.h +extension/script/InkscapeScript.cpp +extension/script/InkscapeScript.h +extension/script/inkscape_perl.pm.h +extension/script/inkscape_perl_wrap.cpp +extension/script/inkscape_py.py.h +extension/script/inkscape_py_wrap.cpp +extension/script/wrap_swig_module.sh +extension/system.cpp +extension/system.h +extension/timer.cpp +extension/timer.h +extract-uri.cpp +extract-uri.h +file.cpp +file.h +fill-or-stroke.h +fixes.cpp +fontsize-expansion.cpp +fontsize-expansion.h +forward.h +gc-alloc.h +gc-anchored.cpp +gc-anchored.h +gc-core.h +gc-finalized.h +gc-managed.h +gc.cpp +geom.cpp +geom.h +gnuc-attribute.h +gradient-chemistry.cpp +gradient-chemistry.h +gradient-context.cpp +gradient-context.h +gradient-drag.cpp +gradient-drag.h +grid-snapper.cpp +grid-snapper.h +guide-snapper.cpp +guide-snapper.h +help.cpp +help.h +helper/action.cpp +helper/action.h +helper/gnome-utils.cpp +helper/gnome-utils.h +helper/helper-forward.h +helper/png-write.cpp +helper/png-write.h +helper/sp-marshal.cpp +helper/sp-marshal.h +helper/stlport.h +helper/stock-items.cpp +helper/stock-items.h +helper/unit-menu.cpp +helper/unit-menu.h +helper/units.cpp +helper/units.h +helper/window.cpp +helper/window.h +inkjar/jar.cpp +inkjar/jar.h +inkscape-private.h +inkscape-stock.cpp +inkscape-stock.h +inkscape.cpp +inkscape.h +inkscape.rc +inkscape_version.h +interface.cpp +interface.h +io/base64stream.cpp +io/base64stream.h +io/ftos.cpp +io/ftos.h +io/gzipstream.cpp +io/gzipstream.h +io/inkscapestream.cpp +io/inkscapestream.h +io/simple-sax.cpp +io/simple-sax.h +io/stringstream.cpp +io/stringstream.h +io/sys.cpp +io/sys.h +io/uristream.cpp +io/uristream.h +io/xsltstream.cpp +io/xsltstream.h +isnan.h +knot-enums.h +knot-holder-entity.h +knot.cpp +knot.h +knotholder.cpp +knotholder.h +layer-fns.cpp +layer-fns.h +libavoid/connector.cpp +libavoid/connector.h +libavoid/debug.h +libavoid/geometry.cpp +libavoid/geometry.h +libavoid/geomtypes.h +libavoid/graph.cpp +libavoid/graph.h +libavoid/incremental.cpp +libavoid/incremental.h +libavoid/libavoid.h +libavoid/makepath.cpp +libavoid/makepath.h +libavoid/polyutil.cpp +libavoid/polyutil.h +libavoid/shape.cpp +libavoid/shape.h +libavoid/static.cpp +libavoid/static.h +libavoid/timer.cpp +libavoid/timer.h +libavoid/vertices.cpp +libavoid/vertices.h +libavoid/visibility.cpp +libavoid/visibility.h +libcroco/cr-additional-sel.c +libcroco/cr-additional-sel.h +libcroco/cr-attr-sel.c +libcroco/cr-attr-sel.h +libcroco/cr-cascade.c +libcroco/cr-cascade.h +libcroco/cr-declaration.c +libcroco/cr-declaration.h +libcroco/cr-doc-handler.c +libcroco/cr-doc-handler.h +libcroco/cr-enc-handler.c +libcroco/cr-enc-handler.h +libcroco/cr-fonts.c +libcroco/cr-fonts.h +libcroco/cr-input.c +libcroco/cr-input.h +libcroco/cr-libxml-node-iface.c +libcroco/cr-libxml-node-iface.h +libcroco/cr-node-iface.h +libcroco/cr-num.c +libcroco/cr-num.h +libcroco/cr-om-parser.c +libcroco/cr-om-parser.h +libcroco/cr-parser.c +libcroco/cr-parser.h +libcroco/cr-parsing-location.c +libcroco/cr-parsing-location.h +libcroco/cr-prop-list.c +libcroco/cr-prop-list.h +libcroco/cr-pseudo.c +libcroco/cr-pseudo.h +libcroco/cr-rgb.c +libcroco/cr-rgb.h +libcroco/cr-sel-eng.c +libcroco/cr-sel-eng.h +libcroco/cr-selector.c +libcroco/cr-selector.h +libcroco/cr-simple-sel.c +libcroco/cr-simple-sel.h +libcroco/cr-statement.c +libcroco/cr-statement.h +libcroco/cr-string.c +libcroco/cr-string.h +libcroco/cr-style.c +libcroco/cr-style.h +libcroco/cr-stylesheet.c +libcroco/cr-stylesheet.h +libcroco/cr-term.c +libcroco/cr-term.h +libcroco/cr-tknzr.c +libcroco/cr-tknzr.h +libcroco/cr-token.c +libcroco/cr-token.h +libcroco/cr-utils.c +libcroco/cr-utils.h +libcroco/libcroco.h +libnr/in-svg-plane-test.h +libnr/in-svg-plane.h +libnr/n-art-bpath.h +libnr/nr-blit.cpp +libnr/nr-blit.h +libnr/nr-compose-transform.cpp +libnr/nr-compose-transform.h +libnr/nr-compose.cpp +libnr/nr-compose.h +libnr/nr-convex-hull-ops.h +libnr/nr-convex-hull.h +libnr/nr-coord.h +libnr/nr-dim2.h +libnr/nr-forward.h +libnr/nr-gradient.cpp +libnr/nr-gradient.h +libnr/nr-i-coord.h +libnr/nr-macros.h +libnr/nr-matrix-div.cpp +libnr/nr-matrix-div.h +libnr/nr-matrix-fns.cpp +libnr/nr-matrix-fns.h +libnr/nr-matrix-ops.h +libnr/nr-matrix-rotate-ops.cpp +libnr/nr-matrix-rotate-ops.h +libnr/nr-matrix-scale-ops.cpp +libnr/nr-matrix-scale-ops.h +libnr/nr-matrix-test.h +libnr/nr-matrix-translate-ops.cpp +libnr/nr-matrix-translate-ops.h +libnr/nr-matrix.cpp +libnr/nr-matrix.h +libnr/nr-maybe.h +libnr/nr-object.cpp +libnr/nr-object.h +libnr/nr-path-code.h +libnr/nr-path.cpp +libnr/nr-path.h +libnr/nr-pixblock-line.cpp +libnr/nr-pixblock-line.h +libnr/nr-pixblock-pattern.cpp +libnr/nr-pixblock-pattern.h +libnr/nr-pixblock-pixel.cpp +libnr/nr-pixblock-pixel.h +libnr/nr-pixblock.cpp +libnr/nr-pixblock.h +libnr/nr-pixops.h +libnr/nr-point-fns-test.h +libnr/nr-point-fns.cpp +libnr/nr-point-fns.h +libnr/nr-point-l.h +libnr/nr-point-matrix-ops.h +libnr/nr-point-ops.h +libnr/nr-point.h +libnr/nr-rect-l.cpp +libnr/nr-rect-l.h +libnr/nr-rect-ops.h +libnr/nr-rect.cpp +libnr/nr-rect.h +libnr/nr-render.h +libnr/nr-rotate-fns-test.h +libnr/nr-rotate-fns.cpp +libnr/nr-rotate-fns.h +libnr/nr-rotate-matrix-ops.cpp +libnr/nr-rotate-matrix-ops.h +libnr/nr-rotate-ops.h +libnr/nr-rotate-test.h +libnr/nr-rotate.h +libnr/nr-scale-matrix-ops.cpp +libnr/nr-scale-matrix-ops.h +libnr/nr-scale-ops.h +libnr/nr-scale-test.h +libnr/nr-scale-translate-ops.cpp +libnr/nr-scale-translate-ops.h +libnr/nr-scale.h +libnr/nr-svp-private.h +libnr/nr-svp-render.cpp +libnr/nr-svp-render.h +libnr/nr-svp.cpp +libnr/nr-svp.h +libnr/nr-translate-matrix-ops.cpp +libnr/nr-translate-matrix-ops.h +libnr/nr-translate-ops.h +libnr/nr-translate-rotate-ops.cpp +libnr/nr-translate-rotate-ops.h +libnr/nr-translate-scale-ops.cpp +libnr/nr-translate-scale-ops.h +libnr/nr-translate-test.h +libnr/nr-translate.h +libnr/nr-types-test.h +libnr/nr-types.cpp +libnr/nr-types.h +libnr/nr-values.cpp +libnr/nr-values.h +libnr/testnr.cpp +libnrtype/FontFactory.cpp +libnrtype/FontFactory.h +libnrtype/FontInstance.cpp +libnrtype/Layout-TNG-Compute.cpp +libnrtype/Layout-TNG-Input.cpp +libnrtype/Layout-TNG-OutIter.cpp +libnrtype/Layout-TNG-Output.cpp +libnrtype/Layout-TNG-Scanline-Maker.h +libnrtype/Layout-TNG-Scanline-Makers.cpp +libnrtype/Layout-TNG.cpp +libnrtype/Layout-TNG.h +libnrtype/RasterFont.cpp +libnrtype/RasterFont.h +libnrtype/TextWrapper.cpp +libnrtype/TextWrapper.h +libnrtype/boundary-type.h +libnrtype/font-glyph.h +libnrtype/font-instance.h +libnrtype/font-style-to-pos.cpp +libnrtype/font-style-to-pos.h +libnrtype/font-style.h +libnrtype/nr-type-pos-def.cpp +libnrtype/nr-type-pos-def.h +libnrtype/nr-type-primitives.cpp +libnrtype/nr-type-primitives.h +libnrtype/nrtype-forward.h +libnrtype/one-box.h +libnrtype/one-glyph.h +libnrtype/one-para.h +libnrtype/raster-glyph.h +libnrtype/raster-position.h +libnrtype/text-boundary.h +line-snapper.cpp +line-snapper.h +livarot/AVL.cpp +livarot/AVL.h +livarot/AlphaLigne.cpp +livarot/AlphaLigne.h +livarot/BitLigne.cpp +livarot/BitLigne.h +livarot/Livarot.h +livarot/LivarotDefs.h +livarot/MyMath.h +livarot/MySeg.cpp +livarot/MySeg.h +livarot/Path.cpp +livarot/Path.h +livarot/PathConversion.cpp +livarot/PathCutting.cpp +livarot/PathOutline.cpp +livarot/PathSimplify.cpp +livarot/PathStroke.cpp +livarot/Shape.cpp +livarot/Shape.h +livarot/ShapeDraw.cpp +livarot/ShapeMisc.cpp +livarot/ShapeRaster.cpp +livarot/ShapeSweep.cpp +livarot/float-line.cpp +livarot/float-line.h +livarot/int-line.cpp +livarot/int-line.h +livarot/livarot-forward.h +livarot/path-description.cpp +livarot/path-description.h +livarot/sweep-event-queue.h +livarot/sweep-event.cpp +livarot/sweep-event.h +livarot/sweep-tree-list.cpp +livarot/sweep-tree-list.h +livarot/sweep-tree.cpp +livarot/sweep-tree.h +macros.h +marker-status.cpp +marker-status.h +media.cpp +media.h +memeq.h +menus-skeleton.h +message-context.cpp +message-context.h +message-stack.cpp +message-stack.h +message.h +mod360.cpp +mod360.h +modifier-fns.h +node-context.cpp +node-context.h +nodepath.cpp +nodepath.h +object-edit.cpp +object-edit.h +object-hierarchy.cpp +object-hierarchy.h +object-snapper.cpp +object-snapper.h +object-ui.cpp +object-ui.h +path-chemistry.cpp +path-chemistry.h +path-prefix.h +pen-context.cpp +pen-context.h +pencil-context.cpp +pencil-context.h +preferences-skeleton.h +preferences.cpp +preferences.h +prefix.cpp +prefix.h +prefs-utils.cpp +prefs-utils.h +print.cpp +print.h +rect-context.cpp +rect-context.h +registrytool.cpp +registrytool.h +remove-last.h +removeoverlap/block.cpp +removeoverlap/block.h +removeoverlap/blocks.cpp +removeoverlap/blocks.h +removeoverlap/constraint.cpp +removeoverlap/constraint.h +removeoverlap/generate-constraints.cpp +removeoverlap/generate-constraints.h +removeoverlap/pairingheap/PairingHeap.cpp +removeoverlap/pairingheap/PairingHeap.h +removeoverlap/pairingheap/dsexceptions.h +removeoverlap/remove_rectangle_overlap.cpp +removeoverlap/remove_rectangle_overlap.h +removeoverlap/removeoverlap.cpp +removeoverlap/removeoverlap.h +removeoverlap/solve_VPSC.cpp +removeoverlap/solve_VPSC.h +removeoverlap/variable.cpp +removeoverlap/variable.h +require-config.h +round.h +rubberband.cpp +rubberband.h +satisfied-guide-cns.cpp +satisfied-guide-cns.h +selcue.cpp +selcue.h +select-context.cpp +select-context.h +selection-chemistry.cpp +selection-chemistry.h +selection-describer.cpp +selection-describer.h +selection.cpp +selection.h +seltrans-handles.cpp +seltrans-handles.h +seltrans.cpp +seltrans.h +shortcuts-default-xml.cpp +shortcuts.cpp +shortcuts.h +slideshow.cpp +slideshow.h +snap.cpp +snap.h +snapped-point.cpp +snapped-point.h +snapper.cpp +snapper.h +sp-anchor.cpp +sp-anchor.h +sp-animation.cpp +sp-animation.h +sp-clippath.cpp +sp-clippath.h +sp-conn-end-pair.cpp +sp-conn-end-pair.h +sp-conn-end.cpp +sp-conn-end.h +sp-cursor.cpp +sp-cursor.h +sp-defs.cpp +sp-defs.h +sp-ellipse.cpp +sp-ellipse.h +sp-flowdiv.cpp +sp-flowdiv.h +sp-flowregion.cpp +sp-flowregion.h +sp-flowtext.cpp +sp-flowtext.h +sp-gradient-fns.h +sp-gradient-reference.cpp +sp-gradient-reference.h +sp-gradient-spread.h +sp-gradient-units.h +sp-gradient-vector.h +sp-gradient.cpp +sp-gradient.h +sp-guide-attachment.h +sp-guide-constraint.h +sp-guide.cpp +sp-guide.h +sp-image.cpp +sp-image.h +sp-item-group.cpp +sp-item-group.h +sp-item-notify-moveto.cpp +sp-item-notify-moveto.h +sp-item-rm-unsatisfied-cns.cpp +sp-item-rm-unsatisfied-cns.h +sp-item-transform.cpp +sp-item-transform.h +sp-item-update-cns.cpp +sp-item-update-cns.h +sp-item.cpp +sp-item.h +sp-line.cpp +sp-line.h +sp-linear-gradient-fns.h +sp-linear-gradient.h +sp-marker-loc.h +sp-marker.cpp +sp-marker.h +sp-mask.cpp +sp-mask.h +sp-metadata.cpp +sp-metadata.h +sp-metric.h +sp-metrics.cpp +sp-metrics.h +sp-namedview.cpp +sp-namedview.h +sp-object-group.cpp +sp-object-group.h +sp-object-repr.cpp +sp-object-repr.h +sp-object.cpp +sp-object.h +sp-offset.cpp +sp-offset.h +sp-paint-server.cpp +sp-paint-server.h +sp-path.cpp +sp-path.h +sp-pattern.cpp +sp-pattern.h +sp-polygon.cpp +sp-polygon.h +sp-polyline.cpp +sp-polyline.h +sp-radial-gradient-fns.h +sp-radial-gradient.h +sp-rect.cpp +sp-rect.h +sp-root.cpp +sp-root.h +sp-shape.cpp +sp-shape.h +sp-skeleton.cpp +sp-skeleton.h +sp-spiral.cpp +sp-spiral.h +sp-star.cpp +sp-star.h +sp-stop-fns.h +sp-stop.h +sp-string.cpp +sp-string.h +sp-style-elem-test.cpp +sp-style-elem.cpp +sp-style-elem.h +sp-symbol.cpp +sp-symbol.h +sp-text.cpp +sp-text.h +sp-textpath.h +sp-tspan.cpp +sp-tspan.h +sp-use-reference.cpp +sp-use-reference.h +sp-use.cpp +sp-use.h +spiral-context.cpp +spiral-context.h +splivarot.cpp +splivarot.h +star-context.cpp +star-context.h +streams-gzip.cpp +streams-gzip.h +streams-handles.cpp +streams-handles.h +streams-jar.cpp +streams-jar.h +streams-zlib.cpp +streams-zlib.h +streq.h +strneq.h +style-test.cpp +style.cpp +style.h +svg-profile.h +svg-view-widget.cpp +svg-view-widget.h +svg-view.cpp +svg-view.h +svg/css-ostringstream-test.h +svg/css-ostringstream.cpp +svg/css-ostringstream.h +svg/ftos.h +svg/gnome-canvas-bpath-util.cpp +svg/gnome-canvas-bpath-util.h +svg/itos.cpp +svg/round.cpp +svg/stringstream-test.h +svg/stringstream.cpp +svg/stringstream.h +svg/strip-trailing-zeros.cpp +svg/strip-trailing-zeros.h +svg/svg-affine.cpp +svg/svg-color.cpp +svg/svg-length.cpp +svg/svg-length.h +svg/svg-path.cpp +svg/svg.h +text-chemistry.cpp +text-chemistry.h +text-context.cpp +text-context.h +text-editing.cpp +text-editing.h +text-tag-attributes.h +tools-switch.cpp +tools-switch.h +trace/filterset.cpp +trace/filterset.h +trace/imagemap-gdk.cpp +trace/imagemap-gdk.h +trace/imagemap.cpp +trace/imagemap.h +trace/potrace/auxiliary.h +trace/potrace/bitmap.h +trace/potrace/curve.cpp +trace/potrace/curve.h +trace/potrace/decompose.cpp +trace/potrace/decompose.h +trace/potrace/greymap.cpp +trace/potrace/greymap.h +trace/potrace/inkscape-potrace.cpp +trace/potrace/inkscape-potrace.h +trace/potrace/lists.h +trace/potrace/potracelib.cpp +trace/potrace/potracelib.h +trace/potrace/progress.h +trace/potrace/render.cpp +trace/potrace/render.h +trace/potrace/trace.cpp +trace/potrace/trace.h +trace/trace.cpp +trace/trace.h +traits/copy.h +traits/function.h +traits/list-copy.h +traits/reference.h +ui/dialog/aboutbox.cpp +ui/dialog/aboutbox.h +ui/dialog/align-and-distribute.cpp +ui/dialog/align-and-distribute.h +ui/dialog/dialog-manager.cpp +ui/dialog/dialog-manager.h +ui/dialog/dialog.cpp +ui/dialog/dialog.h +ui/dialog/document-metadata.cpp +ui/dialog/document-metadata.h +ui/dialog/document-properties.cpp +ui/dialog/document-properties.h +ui/dialog/export.cpp +ui/dialog/export.h +ui/dialog/extension-editor.cpp +ui/dialog/extension-editor.h +ui/dialog/fill-and-stroke.cpp +ui/dialog/fill-and-stroke.h +ui/dialog/find.cpp +ui/dialog/find.h +ui/dialog/inkscape-preferences.cpp +ui/dialog/inkscape-preferences.h +ui/dialog/layer-editor.cpp +ui/dialog/layer-editor.h +ui/dialog/memory.cpp +ui/dialog/memory.h +ui/dialog/messages.cpp +ui/dialog/messages.h +ui/dialog/scriptdialog.cpp +ui/dialog/scriptdialog.h +ui/dialog/session-player.h +ui/dialog/text-properties.cpp +ui/dialog/text-properties.h +ui/dialog/tracedialog.cpp +ui/dialog/tracedialog.h +ui/dialog/transformation.cpp +ui/dialog/transformation.h +ui/dialog/tree-editor.cpp +ui/dialog/tree-editor.h +ui/dialog/whiteboard-connect.h +ui/dialog/whiteboard-sharewithchat.h +ui/dialog/whiteboard-sharewithuser.h +ui/dialog/xml-editor.cpp +ui/dialog/xml-editor.h +ui/icons.cpp +ui/icons.h +ui/previewable.h +ui/previewfillable.h +ui/previewholder.cpp +ui/previewholder.h +ui/stock-items.cpp +ui/stock-items.h +ui/stock.cpp +ui/stock.h +ui/view/desktop-affine.cpp +ui/view/desktop-affine.h +ui/view/desktop-events.cpp +ui/view/desktop-events.h +ui/view/desktop-handles.cpp +ui/view/desktop-handles.h +ui/view/desktop-style.cpp +ui/view/desktop-style.h +ui/view/desktop.cpp +ui/view/desktop.h +ui/view/edit-widget-interface.h +ui/view/edit-widget.cpp +ui/view/edit-widget.h +ui/view/edit.cpp +ui/view/edit.h +ui/view/view-widget.cpp +ui/view/view-widget.h +ui/view/view.cpp +ui/view/view.h +ui/widget/button.cpp +ui/widget/button.h +ui/widget/color-picker.cpp +ui/widget/color-picker.h +ui/widget/color-preview.cpp +ui/widget/color-preview.h +ui/widget/combo-text.cpp +ui/widget/combo-text.h +ui/widget/entity-entry.cpp +ui/widget/entity-entry.h +ui/widget/handlebox.cpp +ui/widget/handlebox.h +ui/widget/icon-widget.cpp +ui/widget/icon-widget.h +ui/widget/imageicon.cpp +ui/widget/imageicon.h +ui/widget/labelled.cpp +ui/widget/labelled.h +ui/widget/licensor.cpp +ui/widget/licensor.h +ui/widget/notebook-page.cpp +ui/widget/notebook-page.h +ui/widget/page-sizer.cpp +ui/widget/page-sizer.h +ui/widget/panel.cpp +ui/widget/panel.h +ui/widget/preferences-widget.cpp +ui/widget/preferences-widget.h +ui/widget/registered-widget.cpp +ui/widget/registered-widget.h +ui/widget/registry.cpp +ui/widget/registry.h +ui/widget/ruler.cpp +ui/widget/ruler.h +ui/widget/scalar-unit.cpp +ui/widget/scalar-unit.h +ui/widget/scalar.cpp +ui/widget/scalar.h +ui/widget/selected-style.cpp +ui/widget/selected-style.h +ui/widget/style-swatch.cpp +ui/widget/style-swatch.h +ui/widget/svg-canvas.cpp +ui/widget/svg-canvas.h +ui/widget/tolerance-slider.cpp +ui/widget/tolerance-slider.h +ui/widget/toolbox.cpp +ui/widget/toolbox.h +ui/widget/unit-menu.cpp +ui/widget/unit-menu.h +ui/widget/zoom-status.cpp +ui/widget/zoom-status.h +undo-stack-observer.h +unit-constants.h +uri-references.cpp +uri-references.h +uri.cpp +uri.h +util/compose.hpp +util/filter-list.h +util/forward-pointer-iterator.h +util/glib-list-iterators.h +util/list-container-test.cpp +util/list-container.h +util/list.h +util/map-list.h +util/reverse-list.h +util/shared-c-string-ptr.cpp +util/shared-c-string-ptr.h +util/tuple.h +util/ucompose.hpp +util/units.cpp +util/units.h +verbs.cpp +verbs.h +version.cpp +version.h +widgets/button.cpp +widgets/button.h +widgets/dash-selector.cpp +widgets/dash-selector.h +widgets/desktop-widget.cpp +widgets/desktop-widget.h +widgets/font-selector.cpp +widgets/font-selector.h +widgets/gradient-image.cpp +widgets/gradient-image.h +widgets/gradient-selector.cpp +widgets/gradient-selector.h +widgets/gradient-toolbar.cpp +widgets/gradient-toolbar.h +widgets/gradient-vector.cpp +widgets/gradient-vector.h +widgets/icon.cpp +widgets/icon.h +widgets/layer-selector.cpp +widgets/layer-selector.h +widgets/paint-selector.cpp +widgets/paint-selector.h +widgets/ruler.cpp +widgets/ruler.h +widgets/select-toolbar.cpp +widgets/select-toolbar.h +widgets/shrink-wrap-button.cpp +widgets/shrink-wrap-button.h +widgets/sp-color-gtkselector.cpp +widgets/sp-color-gtkselector.h +widgets/sp-color-notebook.cpp +widgets/sp-color-notebook.h +widgets/sp-color-preview.cpp +widgets/sp-color-preview.h +widgets/sp-color-scales.cpp +widgets/sp-color-scales.h +widgets/sp-color-selector.cpp +widgets/sp-color-selector.h +widgets/sp-color-slider.cpp +widgets/sp-color-slider.h +widgets/sp-color-wheel-selector.cpp +widgets/sp-color-wheel-selector.h +widgets/sp-color-wheel.cpp +widgets/sp-color-wheel.h +widgets/sp-widget.cpp +widgets/sp-widget.h +widgets/sp-xmlview-attr-list.cpp +widgets/sp-xmlview-attr-list.h +widgets/sp-xmlview-content.cpp +widgets/sp-xmlview-content.h +widgets/sp-xmlview-tree.cpp +widgets/sp-xmlview-tree.h +widgets/spinbutton-events.cpp +widgets/spinbutton-events.h +widgets/spw-utilities.cpp +widgets/spw-utilities.h +widgets/toolbox.cpp +widgets/toolbox.h +widgets/widget-sizes.h +xml/attribute-record.h +xml/comment-node.h +xml/composite-node-observer.cpp +xml/composite-node-observer.h +xml/croco-node-iface.cpp +xml/croco-node-iface.h +xml/document.h +xml/element-node.h +xml/event-fns.h +xml/event.cpp +xml/event.h +xml/invalid-operation-exception.h +xml/log-builder.cpp +xml/log-builder.h +xml/node-event-vector.h +xml/node-fns.cpp +xml/node-fns.h +xml/node-iterators.h +xml/node-observer.h +xml/node.h +xml/quote-test.h +xml/quote.cpp +xml/quote.h +xml/repr-action-test.h +xml/repr-css.cpp +xml/repr-io.cpp +xml/repr-sorting.cpp +xml/repr-sorting.h +xml/repr-util.cpp +xml/repr.cpp +xml/repr.h +xml/session.h +xml/simple-document.cpp +xml/simple-document.h +xml/simple-node.cpp +xml/simple-node.h +xml/simple-session.cpp +xml/simple-session.h +xml/sp-css-attr.h +xml/text-node.h +xml/transaction-logger.h +zoom-context.cpp +zoom-context.h diff --git a/src/make.ofiles b/src/make.ofiles new file mode 100644 index 000000000..a20ac2bc0 --- /dev/null +++ b/src/make.ofiles @@ -0,0 +1,1307 @@ +######################################################## +## File: make.ofiles +## Purpose: Object file listing for use by Makefiles +## Generated by mkdep.pl at :Sun Jan 15 07:06:17 2006 +## Do not edit this file! Changes will be lost. +######################################################## + +OBJECTS = \ + application/app-prototype.o \ + application/application.o \ + application/editor.o \ + arc-context.o \ + attributes-test.o \ + attributes.o \ + color.o \ + composite-undo-stack-observer.o \ + conn-avoid-ref.o \ + connector-context.o \ + context-fns.o \ + debug/heap.o \ + debug/logger.o \ + debug/sysv-heap.o \ + desktop-affine.o \ + desktop-events.o \ + desktop-handles.o \ + desktop-style.o \ + desktop.o \ + dialogs/clonetiler.o \ + dialogs/debugdialog.o \ + dialogs/dialog-events.o \ + dialogs/display-settings.o \ + dialogs/eek-preview.o \ + dialogs/export.o \ + dialogs/extensions.o \ + dialogs/filedialog.o \ + dialogs/fill-style.o \ + dialogs/find.o \ + dialogs/iconpreview.o \ + dialogs/in-dt-coordsys.o \ + dialogs/input.o \ + dialogs/item-properties.o \ + dialogs/layer-properties.o \ + dialogs/object-attributes.o \ + dialogs/object-properties.o \ + dialogs/rdf.o \ + dialogs/sp-attribute-widget.o \ + dialogs/stroke-style.o \ + dialogs/swatches.o \ + dialogs/text-edit.o \ + dialogs/tiledialog.o \ + dialogs/unclump.o \ + dialogs/xml-tree.o \ + dir-util-test.o \ + dir-util.o \ + display/bezier-utils.o \ + display/canvas-arena.o \ + display/canvas-bpath.o \ + display/canvas-grid.o \ + display/curve.o \ + display/gnome-canvas-acetate.o \ + display/guideline.o \ + display/nr-arena-glyphs.o \ + display/nr-arena-group.o \ + display/nr-arena-image.o \ + display/nr-arena-item.o \ + display/nr-arena-shape.o \ + display/nr-arena.o \ + display/nr-gradient-gpl.o \ + display/nr-plain-stuff-gdk.o \ + display/nr-plain-stuff.o \ + display/sodipodi-ctrl.o \ + display/sodipodi-ctrlrect.o \ + display/sp-canvas-util.o \ + display/sp-canvas.o \ + display/sp-ctrlline.o \ + display/sp-ctrlquadr.o \ + document-undo.o \ + document.o \ + dom/charclass.o \ + dom/cssparser.o \ + dom/domimpl.o \ + dom/domstream.o \ + dom/domstring.o \ + dom/js/fdlibm/e_acos.o \ + dom/js/fdlibm/e_acosh.o \ + dom/js/fdlibm/e_asin.o \ + dom/js/fdlibm/e_atan2.o \ + dom/js/fdlibm/e_atanh.o \ + dom/js/fdlibm/e_cosh.o \ + dom/js/fdlibm/e_exp.o \ + dom/js/fdlibm/e_fmod.o \ + dom/js/fdlibm/e_gamma.o \ + dom/js/fdlibm/e_gamma_r.o \ + dom/js/fdlibm/e_hypot.o \ + dom/js/fdlibm/e_j0.o \ + dom/js/fdlibm/e_j1.o \ + dom/js/fdlibm/e_jn.o \ + dom/js/fdlibm/e_lgamma.o \ + dom/js/fdlibm/e_lgamma_r.o \ + dom/js/fdlibm/e_log.o \ + dom/js/fdlibm/e_log10.o \ + dom/js/fdlibm/e_pow.o \ + dom/js/fdlibm/e_rem_pio2.o \ + dom/js/fdlibm/e_remainder.o \ + dom/js/fdlibm/e_scalb.o \ + dom/js/fdlibm/e_sinh.o \ + dom/js/fdlibm/e_sqrt.o \ + dom/js/fdlibm/k_cos.o \ + dom/js/fdlibm/k_rem_pio2.o \ + dom/js/fdlibm/k_sin.o \ + dom/js/fdlibm/k_standard.o \ + dom/js/fdlibm/k_tan.o \ + dom/js/fdlibm/s_asinh.o \ + dom/js/fdlibm/s_atan.o \ + dom/js/fdlibm/s_cbrt.o \ + dom/js/fdlibm/s_ceil.o \ + dom/js/fdlibm/s_copysign.o \ + dom/js/fdlibm/s_cos.o \ + dom/js/fdlibm/s_erf.o \ + dom/js/fdlibm/s_expm1.o \ + dom/js/fdlibm/s_fabs.o \ + dom/js/fdlibm/s_finite.o \ + dom/js/fdlibm/s_floor.o \ + dom/js/fdlibm/s_frexp.o \ + dom/js/fdlibm/s_ilogb.o \ + dom/js/fdlibm/s_isnan.o \ + dom/js/fdlibm/s_ldexp.o \ + dom/js/fdlibm/s_lib_version.o \ + dom/js/fdlibm/s_log1p.o \ + dom/js/fdlibm/s_logb.o \ + dom/js/fdlibm/s_matherr.o \ + dom/js/fdlibm/s_modf.o \ + dom/js/fdlibm/s_nextafter.o \ + dom/js/fdlibm/s_rint.o \ + dom/js/fdlibm/s_scalbn.o \ + dom/js/fdlibm/s_signgam.o \ + dom/js/fdlibm/s_significand.o \ + dom/js/fdlibm/s_sin.o \ + dom/js/fdlibm/s_tan.o \ + dom/js/fdlibm/s_tanh.o \ + dom/js/fdlibm/w_acos.o \ + dom/js/fdlibm/w_acosh.o \ + dom/js/fdlibm/w_asin.o \ + dom/js/fdlibm/w_atan2.o \ + dom/js/fdlibm/w_atanh.o \ + dom/js/fdlibm/w_cosh.o \ + dom/js/fdlibm/w_exp.o \ + dom/js/fdlibm/w_fmod.o \ + dom/js/fdlibm/w_gamma.o \ + dom/js/fdlibm/w_gamma_r.o \ + dom/js/fdlibm/w_hypot.o \ + dom/js/fdlibm/w_j0.o \ + dom/js/fdlibm/w_j1.o \ + dom/js/fdlibm/w_jn.o \ + dom/js/fdlibm/w_lgamma.o \ + dom/js/fdlibm/w_lgamma_r.o \ + dom/js/fdlibm/w_log.o \ + dom/js/fdlibm/w_log10.o \ + dom/js/fdlibm/w_pow.o \ + dom/js/fdlibm/w_remainder.o \ + dom/js/fdlibm/w_scalb.o \ + dom/js/fdlibm/w_sinh.o \ + dom/js/fdlibm/w_sqrt.o \ + dom/js/js.o \ + dom/js/jsapi.o \ + dom/js/jsarena.o \ + dom/js/jsarray.o \ + dom/js/jsatom.o \ + dom/js/jsbool.o \ + dom/js/jscntxt.o \ + dom/js/jscpucfg.o \ + dom/js/jsdate.o \ + dom/js/jsdbgapi.o \ + dom/js/jsdhash.o \ + dom/js/jsdtoa.o \ + dom/js/jsemit.o \ + dom/js/jsexn.o \ + dom/js/jsfile.o \ + dom/js/jsfun.o \ + dom/js/jsgc.o \ + dom/js/jshash.o \ + dom/js/jsinterp.o \ + dom/js/jslock.o \ + dom/js/jslog2.o \ + dom/js/jslong.o \ + dom/js/jsmath.o \ + dom/js/jsnum.o \ + dom/js/jsobj.o \ + dom/js/jsopcode.o \ + dom/js/jsparse.o \ + dom/js/jsprf.o \ + dom/js/jsregexp.o \ + dom/js/jsscan.o \ + dom/js/jsscope.o \ + dom/js/jsscript.o \ + dom/js/jsstr.o \ + dom/js/jsutil.o \ + dom/js/jsxdrapi.o \ + dom/js/prmjtime.o \ + dom/lsimpl.o \ + dom/prop-css.o \ + dom/prop-css2.o \ + dom/prop-svg.o \ + dom/smilimpl.o \ + dom/stringstream.o \ + dom/svgimpl.o \ + dom/svglsimpl.o \ + dom/svgparser.o \ + dom/uri.o \ + dom/uristream.o \ + dom/uritest.o \ + dom/xmlreader.o \ + dom/xpathimpl.o \ + dom/xpathparser.o \ + draw-anchor.o \ + draw-context.o \ + dropper-context.o \ + dyna-draw-context.o \ + event-context.o \ + extension/db.o \ + extension/dependency.o \ + extension/effect.o \ + extension/error-file.o \ + extension/extension.o \ + extension/implementation/implementation.o \ + extension/implementation/plugin.o \ + extension/implementation/script.o \ + extension/init.o \ + extension/input.o \ + extension/internal/bluredge.o \ + extension/internal/eps-out.o \ + extension/internal/gdkpixbuf-input.o \ + extension/internal/gimpgrad.o \ + extension/internal/grid.o \ + extension/internal/latex-pstricks-out.o \ + extension/internal/latex-pstricks.o \ + extension/internal/pov-out.o \ + extension/internal/ps-out.o \ + extension/internal/ps.o \ + extension/internal/svg.o \ + extension/internal/svgz.o \ + extension/internal/win32.o \ + extension/output.o \ + extension/parameter.o \ + extension/prefdialog.o \ + extension/print.o \ + extension/script/InkscapeBinding.o \ + extension/script/InkscapeInterpreter.o \ + extension/script/InkscapePerl.o \ + extension/script/InkscapePython.o \ + extension/script/InkscapeScript.o \ + extension/script/inkscape_perl_wrap.o \ + extension/script/inkscape_py_wrap.o \ + extension/system.o \ + extension/timer.o \ + extract-uri.o \ + file.o \ + fixes.o \ + fontsize-expansion.o \ + gc-anchored.o \ + gc.o \ + geom.o \ + gradient-chemistry.o \ + gradient-context.o \ + gradient-drag.o \ + grid-snapper.o \ + guide-snapper.o \ + help.o \ + helper/action.o \ + helper/gnome-utils.o \ + helper/png-write.o \ + helper/sp-marshal.o \ + helper/stock-items.o \ + helper/unit-menu.o \ + helper/units.o \ + helper/window.o \ + inkjar/jar.o \ + inkscape-stock.o \ + inkscape.o \ + interface.o \ + io/base64stream.o \ + io/ftos.o \ + io/gzipstream.o \ + io/inkscapestream.o \ + io/simple-sax.o \ + io/stringstream.o \ + io/sys.o \ + io/uristream.o \ + io/xsltstream.o \ + knot.o \ + knotholder.o \ + layer-fns.o \ + libavoid/connector.o \ + libavoid/geometry.o \ + libavoid/graph.o \ + libavoid/incremental.o \ + libavoid/makepath.o \ + libavoid/polyutil.o \ + libavoid/shape.o \ + libavoid/static.o \ + libavoid/timer.o \ + libavoid/vertices.o \ + libavoid/visibility.o \ + libcroco/cr-additional-sel.o \ + libcroco/cr-attr-sel.o \ + libcroco/cr-cascade.o \ + libcroco/cr-declaration.o \ + libcroco/cr-doc-handler.o \ + libcroco/cr-enc-handler.o \ + libcroco/cr-fonts.o \ + libcroco/cr-input.o \ + libcroco/cr-libxml-node-iface.o \ + libcroco/cr-num.o \ + libcroco/cr-om-parser.o \ + libcroco/cr-parser.o \ + libcroco/cr-parsing-location.o \ + libcroco/cr-prop-list.o \ + libcroco/cr-pseudo.o \ + libcroco/cr-rgb.o \ + libcroco/cr-sel-eng.o \ + libcroco/cr-selector.o \ + libcroco/cr-simple-sel.o \ + libcroco/cr-statement.o \ + libcroco/cr-string.o \ + libcroco/cr-style.o \ + libcroco/cr-stylesheet.o \ + libcroco/cr-term.o \ + libcroco/cr-tknzr.o \ + libcroco/cr-token.o \ + libcroco/cr-utils.o \ + libnr/nr-blit.o \ + libnr/nr-compose-transform.o \ + libnr/nr-compose.o \ + libnr/nr-gradient.o \ + libnr/nr-matrix-div.o \ + libnr/nr-matrix-fns.o \ + libnr/nr-matrix-rotate-ops.o \ + libnr/nr-matrix-scale-ops.o \ + libnr/nr-matrix-translate-ops.o \ + libnr/nr-matrix.o \ + libnr/nr-object.o \ + libnr/nr-path.o \ + libnr/nr-pixblock-line.o \ + libnr/nr-pixblock-pattern.o \ + libnr/nr-pixblock-pixel.o \ + libnr/nr-pixblock.o \ + libnr/nr-point-fns.o \ + libnr/nr-rect-l.o \ + libnr/nr-rect.o \ + libnr/nr-rotate-fns.o \ + libnr/nr-rotate-matrix-ops.o \ + libnr/nr-scale-matrix-ops.o \ + libnr/nr-scale-translate-ops.o \ + libnr/nr-svp-render.o \ + libnr/nr-svp.o \ + libnr/nr-translate-matrix-ops.o \ + libnr/nr-translate-rotate-ops.o \ + libnr/nr-translate-scale-ops.o \ + libnr/nr-types.o \ + libnr/nr-values.o \ + libnr/testnr.o \ + libnrtype/FontFactory.o \ + libnrtype/FontInstance.o \ + libnrtype/Layout-TNG-Compute.o \ + libnrtype/Layout-TNG-Input.o \ + libnrtype/Layout-TNG-OutIter.o \ + libnrtype/Layout-TNG-Output.o \ + libnrtype/Layout-TNG-Scanline-Makers.o \ + libnrtype/Layout-TNG.o \ + libnrtype/RasterFont.o \ + libnrtype/TextWrapper.o \ + libnrtype/font-style-to-pos.o \ + libnrtype/nr-type-pos-def.o \ + libnrtype/nr-type-primitives.o \ + line-snapper.o \ + livarot/AVL.o \ + livarot/AlphaLigne.o \ + livarot/BitLigne.o \ + livarot/MySeg.o \ + livarot/Path.o \ + livarot/PathConversion.o \ + livarot/PathCutting.o \ + livarot/PathOutline.o \ + livarot/PathSimplify.o \ + livarot/PathStroke.o \ + livarot/Shape.o \ + livarot/ShapeDraw.o \ + livarot/ShapeMisc.o \ + livarot/ShapeRaster.o \ + livarot/ShapeSweep.o \ + livarot/float-line.o \ + livarot/int-line.o \ + livarot/path-description.o \ + livarot/sweep-event.o \ + livarot/sweep-tree-list.o \ + livarot/sweep-tree.o \ + marker-status.o \ + media.o \ + message-context.o \ + message-stack.o \ + mod360.o \ + node-context.o \ + nodepath.o \ + object-edit.o \ + object-hierarchy.o \ + object-snapper.o \ + object-ui.o \ + path-chemistry.o \ + pen-context.o \ + pencil-context.o \ + preferences.o \ + prefix.o \ + prefs-utils.o \ + print.o \ + rect-context.o \ + registrytool.o \ + removeoverlap/block.o \ + removeoverlap/blocks.o \ + removeoverlap/constraint.o \ + removeoverlap/generate-constraints.o \ + removeoverlap/pairingheap/PairingHeap.o \ + removeoverlap/remove_rectangle_overlap.o \ + removeoverlap/removeoverlap.o \ + removeoverlap/solve_VPSC.o \ + removeoverlap/variable.o \ + rubberband.o \ + satisfied-guide-cns.o \ + selcue.o \ + select-context.o \ + selection-chemistry.o \ + selection-describer.o \ + selection.o \ + seltrans-handles.o \ + seltrans.o \ + shortcuts-default-xml.o \ + shortcuts.o \ + slideshow.o \ + snap.o \ + snapped-point.o \ + snapper.o \ + sp-anchor.o \ + sp-animation.o \ + sp-clippath.o \ + sp-conn-end-pair.o \ + sp-conn-end.o \ + sp-cursor.o \ + sp-defs.o \ + sp-ellipse.o \ + sp-flowdiv.o \ + sp-flowregion.o \ + sp-flowtext.o \ + sp-gradient-reference.o \ + sp-gradient.o \ + sp-guide.o \ + sp-image.o \ + sp-item-group.o \ + sp-item-notify-moveto.o \ + sp-item-rm-unsatisfied-cns.o \ + sp-item-transform.o \ + sp-item-update-cns.o \ + sp-item.o \ + sp-line.o \ + sp-marker.o \ + sp-mask.o \ + sp-metadata.o \ + sp-metrics.o \ + sp-namedview.o \ + sp-object-group.o \ + sp-object-repr.o \ + sp-object.o \ + sp-offset.o \ + sp-paint-server.o \ + sp-path.o \ + sp-pattern.o \ + sp-polygon.o \ + sp-polyline.o \ + sp-rect.o \ + sp-root.o \ + sp-shape.o \ + sp-skeleton.o \ + sp-spiral.o \ + sp-star.o \ + sp-string.o \ + sp-style-elem-test.o \ + sp-style-elem.o \ + sp-symbol.o \ + sp-text.o \ + sp-tspan.o \ + sp-use-reference.o \ + sp-use.o \ + spiral-context.o \ + splivarot.o \ + star-context.o \ + streams-gzip.o \ + streams-handles.o \ + streams-jar.o \ + streams-zlib.o \ + style-test.o \ + style.o \ + svg-view-widget.o \ + svg-view.o \ + svg/css-ostringstream.o \ + svg/gnome-canvas-bpath-util.o \ + svg/itos.o \ + svg/round.o \ + svg/stringstream.o \ + svg/strip-trailing-zeros.o \ + svg/svg-affine.o \ + svg/svg-color.o \ + svg/svg-length.o \ + svg/svg-path.o \ + text-chemistry.o \ + text-context.o \ + text-editing.o \ + tools-switch.o \ + trace/filterset.o \ + trace/imagemap-gdk.o \ + trace/imagemap.o \ + trace/potrace/curve.o \ + trace/potrace/decompose.o \ + trace/potrace/greymap.o \ + trace/potrace/inkscape-potrace.o \ + trace/potrace/potracelib.o \ + trace/potrace/render.o \ + trace/potrace/trace.o \ + trace/trace.o \ + ui/dialog/aboutbox.o \ + ui/dialog/align-and-distribute.o \ + ui/dialog/dialog-manager.o \ + ui/dialog/dialog.o \ + ui/dialog/document-metadata.o \ + ui/dialog/document-properties.o \ + ui/dialog/export.o \ + ui/dialog/extension-editor.o \ + ui/dialog/fill-and-stroke.o \ + ui/dialog/find.o \ + ui/dialog/inkscape-preferences.o \ + ui/dialog/layer-editor.o \ + ui/dialog/memory.o \ + ui/dialog/messages.o \ + ui/dialog/scriptdialog.o \ + ui/dialog/text-properties.o \ + ui/dialog/tracedialog.o \ + ui/dialog/transformation.o \ + ui/dialog/tree-editor.o \ + ui/dialog/xml-editor.o \ + ui/icons.o \ + ui/previewholder.o \ + ui/stock-items.o \ + ui/stock.o \ + ui/view/desktop-affine.o \ + ui/view/desktop-events.o \ + ui/view/desktop-handles.o \ + ui/view/desktop-style.o \ + ui/view/desktop.o \ + ui/view/edit-widget.o \ + ui/view/edit.o \ + ui/view/view-widget.o \ + ui/view/view.o \ + ui/widget/button.o \ + ui/widget/color-picker.o \ + ui/widget/color-preview.o \ + ui/widget/combo-text.o \ + ui/widget/entity-entry.o \ + ui/widget/handlebox.o \ + ui/widget/icon-widget.o \ + ui/widget/imageicon.o \ + ui/widget/labelled.o \ + ui/widget/licensor.o \ + ui/widget/notebook-page.o \ + ui/widget/page-sizer.o \ + ui/widget/panel.o \ + ui/widget/preferences-widget.o \ + ui/widget/registered-widget.o \ + ui/widget/registry.o \ + ui/widget/ruler.o \ + ui/widget/scalar-unit.o \ + ui/widget/scalar.o \ + ui/widget/selected-style.o \ + ui/widget/style-swatch.o \ + ui/widget/svg-canvas.o \ + ui/widget/tolerance-slider.o \ + ui/widget/toolbox.o \ + ui/widget/unit-menu.o \ + ui/widget/zoom-status.o \ + uri-references.o \ + uri.o \ + util/list-container-test.o \ + util/shared-c-string-ptr.o \ + util/units.o \ + verbs.o \ + version.o \ + widgets/button.o \ + widgets/dash-selector.o \ + widgets/desktop-widget.o \ + widgets/font-selector.o \ + widgets/gradient-image.o \ + widgets/gradient-selector.o \ + widgets/gradient-toolbar.o \ + widgets/gradient-vector.o \ + widgets/icon.o \ + widgets/layer-selector.o \ + widgets/paint-selector.o \ + widgets/ruler.o \ + widgets/select-toolbar.o \ + widgets/shrink-wrap-button.o \ + widgets/sp-color-gtkselector.o \ + widgets/sp-color-notebook.o \ + widgets/sp-color-preview.o \ + widgets/sp-color-scales.o \ + widgets/sp-color-selector.o \ + widgets/sp-color-slider.o \ + widgets/sp-color-wheel-selector.o \ + widgets/sp-color-wheel.o \ + widgets/sp-widget.o \ + widgets/sp-xmlview-attr-list.o \ + widgets/sp-xmlview-content.o \ + widgets/sp-xmlview-tree.o \ + widgets/spinbutton-events.o \ + widgets/spw-utilities.o \ + widgets/toolbox.o \ + xml/composite-node-observer.o \ + xml/croco-node-iface.o \ + xml/event.o \ + xml/log-builder.o \ + xml/node-fns.o \ + xml/quote.o \ + xml/repr-css.o \ + xml/repr-io.o \ + xml/repr-sorting.o \ + xml/repr-util.o \ + xml/repr.o \ + xml/simple-document.o \ + xml/simple-node.o \ + xml/simple-session.o \ + zoom-context.o + +LINTS = \ + application/app-prototype.lint \ + application/application.lint \ + application/editor.lint \ + arc-context.lint \ + attributes-test.lint \ + attributes.lint \ + color.lint \ + composite-undo-stack-observer.lint \ + conn-avoid-ref.lint \ + connector-context.lint \ + context-fns.lint \ + debug/heap.lint \ + debug/logger.lint \ + debug/sysv-heap.lint \ + desktop-affine.lint \ + desktop-events.lint \ + desktop-handles.lint \ + desktop-style.lint \ + desktop.lint \ + dialogs/clonetiler.lint \ + dialogs/debugdialog.lint \ + dialogs/dialog-events.lint \ + dialogs/display-settings.lint \ + dialogs/eek-preview.lint \ + dialogs/export.lint \ + dialogs/extensions.lint \ + dialogs/filedialog.lint \ + dialogs/fill-style.lint \ + dialogs/find.lint \ + dialogs/iconpreview.lint \ + dialogs/in-dt-coordsys.lint \ + dialogs/input.lint \ + dialogs/item-properties.lint \ + dialogs/layer-properties.lint \ + dialogs/object-attributes.lint \ + dialogs/object-properties.lint \ + dialogs/rdf.lint \ + dialogs/sp-attribute-widget.lint \ + dialogs/stroke-style.lint \ + dialogs/swatches.lint \ + dialogs/text-edit.lint \ + dialogs/tiledialog.lint \ + dialogs/unclump.lint \ + dialogs/xml-tree.lint \ + dir-util-test.lint \ + dir-util.lint \ + display/bezier-utils.lint \ + display/canvas-arena.lint \ + display/canvas-bpath.lint \ + display/canvas-grid.lint \ + display/curve.lint \ + display/gnome-canvas-acetate.lint \ + display/guideline.lint \ + display/nr-arena-glyphs.lint \ + display/nr-arena-group.lint \ + display/nr-arena-image.lint \ + display/nr-arena-item.lint \ + display/nr-arena-shape.lint \ + display/nr-arena.lint \ + display/nr-gradient-gpl.lint \ + display/nr-plain-stuff-gdk.lint \ + display/nr-plain-stuff.lint \ + display/sodipodi-ctrl.lint \ + display/sodipodi-ctrlrect.lint \ + display/sp-canvas-util.lint \ + display/sp-canvas.lint \ + display/sp-ctrlline.lint \ + display/sp-ctrlquadr.lint \ + document-undo.lint \ + document.lint \ + dom/charclass.lint \ + dom/cssparser.lint \ + dom/domimpl.lint \ + dom/domstream.lint \ + dom/domstring.lint \ + dom/js/fdlibm/e_acos.lint \ + dom/js/fdlibm/e_acosh.lint \ + dom/js/fdlibm/e_asin.lint \ + dom/js/fdlibm/e_atan2.lint \ + dom/js/fdlibm/e_atanh.lint \ + dom/js/fdlibm/e_cosh.lint \ + dom/js/fdlibm/e_exp.lint \ + dom/js/fdlibm/e_fmod.lint \ + dom/js/fdlibm/e_gamma.lint \ + dom/js/fdlibm/e_gamma_r.lint \ + dom/js/fdlibm/e_hypot.lint \ + dom/js/fdlibm/e_j0.lint \ + dom/js/fdlibm/e_j1.lint \ + dom/js/fdlibm/e_jn.lint \ + dom/js/fdlibm/e_lgamma.lint \ + dom/js/fdlibm/e_lgamma_r.lint \ + dom/js/fdlibm/e_log.lint \ + dom/js/fdlibm/e_log10.lint \ + dom/js/fdlibm/e_pow.lint \ + dom/js/fdlibm/e_rem_pio2.lint \ + dom/js/fdlibm/e_remainder.lint \ + dom/js/fdlibm/e_scalb.lint \ + dom/js/fdlibm/e_sinh.lint \ + dom/js/fdlibm/e_sqrt.lint \ + dom/js/fdlibm/k_cos.lint \ + dom/js/fdlibm/k_rem_pio2.lint \ + dom/js/fdlibm/k_sin.lint \ + dom/js/fdlibm/k_standard.lint \ + dom/js/fdlibm/k_tan.lint \ + dom/js/fdlibm/s_asinh.lint \ + dom/js/fdlibm/s_atan.lint \ + dom/js/fdlibm/s_cbrt.lint \ + dom/js/fdlibm/s_ceil.lint \ + dom/js/fdlibm/s_copysign.lint \ + dom/js/fdlibm/s_cos.lint \ + dom/js/fdlibm/s_erf.lint \ + dom/js/fdlibm/s_expm1.lint \ + dom/js/fdlibm/s_fabs.lint \ + dom/js/fdlibm/s_finite.lint \ + dom/js/fdlibm/s_floor.lint \ + dom/js/fdlibm/s_frexp.lint \ + dom/js/fdlibm/s_ilogb.lint \ + dom/js/fdlibm/s_isnan.lint \ + dom/js/fdlibm/s_ldexp.lint \ + dom/js/fdlibm/s_lib_version.lint \ + dom/js/fdlibm/s_log1p.lint \ + dom/js/fdlibm/s_logb.lint \ + dom/js/fdlibm/s_matherr.lint \ + dom/js/fdlibm/s_modf.lint \ + dom/js/fdlibm/s_nextafter.lint \ + dom/js/fdlibm/s_rint.lint \ + dom/js/fdlibm/s_scalbn.lint \ + dom/js/fdlibm/s_signgam.lint \ + dom/js/fdlibm/s_significand.lint \ + dom/js/fdlibm/s_sin.lint \ + dom/js/fdlibm/s_tan.lint \ + dom/js/fdlibm/s_tanh.lint \ + dom/js/fdlibm/w_acos.lint \ + dom/js/fdlibm/w_acosh.lint \ + dom/js/fdlibm/w_asin.lint \ + dom/js/fdlibm/w_atan2.lint \ + dom/js/fdlibm/w_atanh.lint \ + dom/js/fdlibm/w_cosh.lint \ + dom/js/fdlibm/w_exp.lint \ + dom/js/fdlibm/w_fmod.lint \ + dom/js/fdlibm/w_gamma.lint \ + dom/js/fdlibm/w_gamma_r.lint \ + dom/js/fdlibm/w_hypot.lint \ + dom/js/fdlibm/w_j0.lint \ + dom/js/fdlibm/w_j1.lint \ + dom/js/fdlibm/w_jn.lint \ + dom/js/fdlibm/w_lgamma.lint \ + dom/js/fdlibm/w_lgamma_r.lint \ + dom/js/fdlibm/w_log.lint \ + dom/js/fdlibm/w_log10.lint \ + dom/js/fdlibm/w_pow.lint \ + dom/js/fdlibm/w_remainder.lint \ + dom/js/fdlibm/w_scalb.lint \ + dom/js/fdlibm/w_sinh.lint \ + dom/js/fdlibm/w_sqrt.lint \ + dom/js/js.lint \ + dom/js/jsapi.lint \ + dom/js/jsarena.lint \ + dom/js/jsarray.lint \ + dom/js/jsatom.lint \ + dom/js/jsbool.lint \ + dom/js/jscntxt.lint \ + dom/js/jscpucfg.lint \ + dom/js/jsdate.lint \ + dom/js/jsdbgapi.lint \ + dom/js/jsdhash.lint \ + dom/js/jsdtoa.lint \ + dom/js/jsemit.lint \ + dom/js/jsexn.lint \ + dom/js/jsfile.lint \ + dom/js/jsfun.lint \ + dom/js/jsgc.lint \ + dom/js/jshash.lint \ + dom/js/jsinterp.lint \ + dom/js/jslock.lint \ + dom/js/jslog2.lint \ + dom/js/jslong.lint \ + dom/js/jsmath.lint \ + dom/js/jsnum.lint \ + dom/js/jsobj.lint \ + dom/js/jsopcode.lint \ + dom/js/jsparse.lint \ + dom/js/jsprf.lint \ + dom/js/jsregexp.lint \ + dom/js/jsscan.lint \ + dom/js/jsscope.lint \ + dom/js/jsscript.lint \ + dom/js/jsstr.lint \ + dom/js/jsutil.lint \ + dom/js/jsxdrapi.lint \ + dom/js/prmjtime.lint \ + dom/lsimpl.lint \ + dom/prop-css.lint \ + dom/prop-css2.lint \ + dom/prop-svg.lint \ + dom/smilimpl.lint \ + dom/stringstream.lint \ + dom/svgimpl.lint \ + dom/svglsimpl.lint \ + dom/svgparser.lint \ + dom/uri.lint \ + dom/uristream.lint \ + dom/uritest.lint \ + dom/xmlreader.lint \ + dom/xpathimpl.lint \ + dom/xpathparser.lint \ + draw-anchor.lint \ + draw-context.lint \ + dropper-context.lint \ + dyna-draw-context.lint \ + event-context.lint \ + extension/db.lint \ + extension/dependency.lint \ + extension/effect.lint \ + extension/error-file.lint \ + extension/extension.lint \ + extension/implementation/implementation.lint \ + extension/implementation/plugin.lint \ + extension/implementation/script.lint \ + extension/init.lint \ + extension/input.lint \ + extension/internal/bluredge.lint \ + extension/internal/eps-out.lint \ + extension/internal/gdkpixbuf-input.lint \ + extension/internal/gimpgrad.lint \ + extension/internal/grid.lint \ + extension/internal/latex-pstricks-out.lint \ + extension/internal/latex-pstricks.lint \ + extension/internal/pov-out.lint \ + extension/internal/ps-out.lint \ + extension/internal/ps.lint \ + extension/internal/svg.lint \ + extension/internal/svgz.lint \ + extension/internal/win32.lint \ + extension/output.lint \ + extension/parameter.lint \ + extension/prefdialog.lint \ + extension/print.lint \ + extension/script/InkscapeBinding.lint \ + extension/script/InkscapeInterpreter.lint \ + extension/script/InkscapePerl.lint \ + extension/script/InkscapePython.lint \ + extension/script/InkscapeScript.lint \ + extension/script/inkscape_perl_wrap.lint \ + extension/script/inkscape_py_wrap.lint \ + extension/system.lint \ + extension/timer.lint \ + extract-uri.lint \ + file.lint \ + fixes.lint \ + fontsize-expansion.lint \ + gc-anchored.lint \ + gc.lint \ + geom.lint \ + gradient-chemistry.lint \ + gradient-context.lint \ + gradient-drag.lint \ + grid-snapper.lint \ + guide-snapper.lint \ + help.lint \ + helper/action.lint \ + helper/gnome-utils.lint \ + helper/png-write.lint \ + helper/sp-marshal.lint \ + helper/stock-items.lint \ + helper/unit-menu.lint \ + helper/units.lint \ + helper/window.lint \ + inkjar/jar.lint \ + inkscape-stock.lint \ + inkscape.lint \ + interface.lint \ + io/base64stream.lint \ + io/ftos.lint \ + io/gzipstream.lint \ + io/inkscapestream.lint \ + io/simple-sax.lint \ + io/stringstream.lint \ + io/sys.lint \ + io/uristream.lint \ + io/xsltstream.lint \ + knot.lint \ + knotholder.lint \ + layer-fns.lint \ + libavoid/connector.lint \ + libavoid/geometry.lint \ + libavoid/graph.lint \ + libavoid/incremental.lint \ + libavoid/makepath.lint \ + libavoid/polyutil.lint \ + libavoid/shape.lint \ + libavoid/static.lint \ + libavoid/timer.lint \ + libavoid/vertices.lint \ + libavoid/visibility.lint \ + libcroco/cr-additional-sel.lint \ + libcroco/cr-attr-sel.lint \ + libcroco/cr-cascade.lint \ + libcroco/cr-declaration.lint \ + libcroco/cr-doc-handler.lint \ + libcroco/cr-enc-handler.lint \ + libcroco/cr-fonts.lint \ + libcroco/cr-input.lint \ + libcroco/cr-libxml-node-iface.lint \ + libcroco/cr-num.lint \ + libcroco/cr-om-parser.lint \ + libcroco/cr-parser.lint \ + libcroco/cr-parsing-location.lint \ + libcroco/cr-prop-list.lint \ + libcroco/cr-pseudo.lint \ + libcroco/cr-rgb.lint \ + libcroco/cr-sel-eng.lint \ + libcroco/cr-selector.lint \ + libcroco/cr-simple-sel.lint \ + libcroco/cr-statement.lint \ + libcroco/cr-string.lint \ + libcroco/cr-style.lint \ + libcroco/cr-stylesheet.lint \ + libcroco/cr-term.lint \ + libcroco/cr-tknzr.lint \ + libcroco/cr-token.lint \ + libcroco/cr-utils.lint \ + libnr/nr-blit.lint \ + libnr/nr-compose-transform.lint \ + libnr/nr-compose.lint \ + libnr/nr-gradient.lint \ + libnr/nr-matrix-div.lint \ + libnr/nr-matrix-fns.lint \ + libnr/nr-matrix-rotate-ops.lint \ + libnr/nr-matrix-scale-ops.lint \ + libnr/nr-matrix-translate-ops.lint \ + libnr/nr-matrix.lint \ + libnr/nr-object.lint \ + libnr/nr-path.lint \ + libnr/nr-pixblock-line.lint \ + libnr/nr-pixblock-pattern.lint \ + libnr/nr-pixblock-pixel.lint \ + libnr/nr-pixblock.lint \ + libnr/nr-point-fns.lint \ + libnr/nr-rect-l.lint \ + libnr/nr-rect.lint \ + libnr/nr-rotate-fns.lint \ + libnr/nr-rotate-matrix-ops.lint \ + libnr/nr-scale-matrix-ops.lint \ + libnr/nr-scale-translate-ops.lint \ + libnr/nr-svp-render.lint \ + libnr/nr-svp.lint \ + libnr/nr-translate-matrix-ops.lint \ + libnr/nr-translate-rotate-ops.lint \ + libnr/nr-translate-scale-ops.lint \ + libnr/nr-types.lint \ + libnr/nr-values.lint \ + libnr/testnr.lint \ + libnrtype/FontFactory.lint \ + libnrtype/FontInstance.lint \ + libnrtype/Layout-TNG-Compute.lint \ + libnrtype/Layout-TNG-Input.lint \ + libnrtype/Layout-TNG-OutIter.lint \ + libnrtype/Layout-TNG-Output.lint \ + libnrtype/Layout-TNG-Scanline-Makers.lint \ + libnrtype/Layout-TNG.lint \ + libnrtype/RasterFont.lint \ + libnrtype/TextWrapper.lint \ + libnrtype/font-style-to-pos.lint \ + libnrtype/nr-type-pos-def.lint \ + libnrtype/nr-type-primitives.lint \ + line-snapper.lint \ + livarot/AVL.lint \ + livarot/AlphaLigne.lint \ + livarot/BitLigne.lint \ + livarot/MySeg.lint \ + livarot/Path.lint \ + livarot/PathConversion.lint \ + livarot/PathCutting.lint \ + livarot/PathOutline.lint \ + livarot/PathSimplify.lint \ + livarot/PathStroke.lint \ + livarot/Shape.lint \ + livarot/ShapeDraw.lint \ + livarot/ShapeMisc.lint \ + livarot/ShapeRaster.lint \ + livarot/ShapeSweep.lint \ + livarot/float-line.lint \ + livarot/int-line.lint \ + livarot/path-description.lint \ + livarot/sweep-event.lint \ + livarot/sweep-tree-list.lint \ + livarot/sweep-tree.lint \ + marker-status.lint \ + media.lint \ + message-context.lint \ + message-stack.lint \ + mod360.lint \ + node-context.lint \ + nodepath.lint \ + object-edit.lint \ + object-hierarchy.lint \ + object-snapper.lint \ + object-ui.lint \ + path-chemistry.lint \ + pen-context.lint \ + pencil-context.lint \ + preferences.lint \ + prefix.lint \ + prefs-utils.lint \ + print.lint \ + rect-context.lint \ + registrytool.lint \ + removeoverlap/block.lint \ + removeoverlap/blocks.lint \ + removeoverlap/constraint.lint \ + removeoverlap/generate-constraints.lint \ + removeoverlap/pairingheap/PairingHeap.lint \ + removeoverlap/remove_rectangle_overlap.lint \ + removeoverlap/removeoverlap.lint \ + removeoverlap/solve_VPSC.lint \ + removeoverlap/variable.lint \ + rubberband.lint \ + satisfied-guide-cns.lint \ + selcue.lint \ + select-context.lint \ + selection-chemistry.lint \ + selection-describer.lint \ + selection.lint \ + seltrans-handles.lint \ + seltrans.lint \ + shortcuts-default-xml.lint \ + shortcuts.lint \ + slideshow.lint \ + snap.lint \ + snapped-point.lint \ + snapper.lint \ + sp-anchor.lint \ + sp-animation.lint \ + sp-clippath.lint \ + sp-conn-end-pair.lint \ + sp-conn-end.lint \ + sp-cursor.lint \ + sp-defs.lint \ + sp-ellipse.lint \ + sp-flowdiv.lint \ + sp-flowregion.lint \ + sp-flowtext.lint \ + sp-gradient-reference.lint \ + sp-gradient.lint \ + sp-guide.lint \ + sp-image.lint \ + sp-item-group.lint \ + sp-item-notify-moveto.lint \ + sp-item-rm-unsatisfied-cns.lint \ + sp-item-transform.lint \ + sp-item-update-cns.lint \ + sp-item.lint \ + sp-line.lint \ + sp-marker.lint \ + sp-mask.lint \ + sp-metadata.lint \ + sp-metrics.lint \ + sp-namedview.lint \ + sp-object-group.lint \ + sp-object-repr.lint \ + sp-object.lint \ + sp-offset.lint \ + sp-paint-server.lint \ + sp-path.lint \ + sp-pattern.lint \ + sp-polygon.lint \ + sp-polyline.lint \ + sp-rect.lint \ + sp-root.lint \ + sp-shape.lint \ + sp-skeleton.lint \ + sp-spiral.lint \ + sp-star.lint \ + sp-string.lint \ + sp-style-elem-test.lint \ + sp-style-elem.lint \ + sp-symbol.lint \ + sp-text.lint \ + sp-tspan.lint \ + sp-use-reference.lint \ + sp-use.lint \ + spiral-context.lint \ + splivarot.lint \ + star-context.lint \ + streams-gzip.lint \ + streams-handles.lint \ + streams-jar.lint \ + streams-zlib.lint \ + style-test.lint \ + style.lint \ + svg-view-widget.lint \ + svg-view.lint \ + svg/css-ostringstream.lint \ + svg/gnome-canvas-bpath-util.lint \ + svg/itos.lint \ + svg/round.lint \ + svg/stringstream.lint \ + svg/strip-trailing-zeros.lint \ + svg/svg-affine.lint \ + svg/svg-color.lint \ + svg/svg-length.lint \ + svg/svg-path.lint \ + text-chemistry.lint \ + text-context.lint \ + text-editing.lint \ + tools-switch.lint \ + trace/filterset.lint \ + trace/imagemap-gdk.lint \ + trace/imagemap.lint \ + trace/potrace/curve.lint \ + trace/potrace/decompose.lint \ + trace/potrace/greymap.lint \ + trace/potrace/inkscape-potrace.lint \ + trace/potrace/potracelib.lint \ + trace/potrace/render.lint \ + trace/potrace/trace.lint \ + trace/trace.lint \ + ui/dialog/aboutbox.lint \ + ui/dialog/align-and-distribute.lint \ + ui/dialog/dialog-manager.lint \ + ui/dialog/dialog.lint \ + ui/dialog/document-metadata.lint \ + ui/dialog/document-properties.lint \ + ui/dialog/export.lint \ + ui/dialog/extension-editor.lint \ + ui/dialog/fill-and-stroke.lint \ + ui/dialog/find.lint \ + ui/dialog/inkscape-preferences.lint \ + ui/dialog/layer-editor.lint \ + ui/dialog/memory.lint \ + ui/dialog/messages.lint \ + ui/dialog/scriptdialog.lint \ + ui/dialog/text-properties.lint \ + ui/dialog/tracedialog.lint \ + ui/dialog/transformation.lint \ + ui/dialog/tree-editor.lint \ + ui/dialog/xml-editor.lint \ + ui/icons.lint \ + ui/previewholder.lint \ + ui/stock-items.lint \ + ui/stock.lint \ + ui/view/desktop-affine.lint \ + ui/view/desktop-events.lint \ + ui/view/desktop-handles.lint \ + ui/view/desktop-style.lint \ + ui/view/desktop.lint \ + ui/view/edit-widget.lint \ + ui/view/edit.lint \ + ui/view/view-widget.lint \ + ui/view/view.lint \ + ui/widget/button.lint \ + ui/widget/color-picker.lint \ + ui/widget/color-preview.lint \ + ui/widget/combo-text.lint \ + ui/widget/entity-entry.lint \ + ui/widget/handlebox.lint \ + ui/widget/icon-widget.lint \ + ui/widget/imageicon.lint \ + ui/widget/labelled.lint \ + ui/widget/licensor.lint \ + ui/widget/notebook-page.lint \ + ui/widget/page-sizer.lint \ + ui/widget/panel.lint \ + ui/widget/preferences-widget.lint \ + ui/widget/registered-widget.lint \ + ui/widget/registry.lint \ + ui/widget/ruler.lint \ + ui/widget/scalar-unit.lint \ + ui/widget/scalar.lint \ + ui/widget/selected-style.lint \ + ui/widget/style-swatch.lint \ + ui/widget/svg-canvas.lint \ + ui/widget/tolerance-slider.lint \ + ui/widget/toolbox.lint \ + ui/widget/unit-menu.lint \ + ui/widget/zoom-status.lint \ + uri-references.lint \ + uri.lint \ + util/list-container-test.lint \ + util/shared-c-string-ptr.lint \ + util/units.lint \ + verbs.lint \ + version.lint \ + widgets/button.lint \ + widgets/dash-selector.lint \ + widgets/desktop-widget.lint \ + widgets/font-selector.lint \ + widgets/gradient-image.lint \ + widgets/gradient-selector.lint \ + widgets/gradient-toolbar.lint \ + widgets/gradient-vector.lint \ + widgets/icon.lint \ + widgets/layer-selector.lint \ + widgets/paint-selector.lint \ + widgets/ruler.lint \ + widgets/select-toolbar.lint \ + widgets/shrink-wrap-button.lint \ + widgets/sp-color-gtkselector.lint \ + widgets/sp-color-notebook.lint \ + widgets/sp-color-preview.lint \ + widgets/sp-color-scales.lint \ + widgets/sp-color-selector.lint \ + widgets/sp-color-slider.lint \ + widgets/sp-color-wheel-selector.lint \ + widgets/sp-color-wheel.lint \ + widgets/sp-widget.lint \ + widgets/sp-xmlview-attr-list.lint \ + widgets/sp-xmlview-content.lint \ + widgets/sp-xmlview-tree.lint \ + widgets/spinbutton-events.lint \ + widgets/spw-utilities.lint \ + widgets/toolbox.lint \ + xml/composite-node-observer.lint \ + xml/croco-node-iface.lint \ + xml/event.lint \ + xml/log-builder.lint \ + xml/node-fns.lint \ + xml/quote.lint \ + xml/repr-css.lint \ + xml/repr-io.lint \ + xml/repr-sorting.lint \ + xml/repr-util.lint \ + xml/repr.lint \ + xml/simple-document.lint \ + xml/simple-node.lint \ + xml/simple-session.lint \ + zoom-context.lint + +INCLUDEPATH = \ + -Ialgorithms \ + -Iapplication \ + -Idebug \ + -Idialogs \ + -Idisplay \ + -Idom \ + -Idom/js/fdlibm \ + -Idom/js \ + -Idom \ + -Iextension \ + -Iextension/implementation \ + -Iextension \ + -Iextension/internal \ + -Iextension \ + -Iextension/script \ + -Iextension \ + -Ihelper \ + -Iinkjar \ + -Iio \ + -Ilibavoid \ + -Ilibcroco \ + -Ilibnr \ + -Ilibnrtype \ + -Ilivarot \ + -Iremoveoverlap \ + -Iremoveoverlap/pairingheap \ + -Iremoveoverlap \ + -Isvg \ + -Itrace \ + -Itrace/potrace \ + -Itrace \ + -Itraits \ + -Iui/dialog \ + -Iui \ + -Iui/view \ + -Iui/widget \ + -Iutil \ + -Iwidgets \ + -Ixml/ diff --git a/src/makedef.pl b/src/makedef.pl new file mode 100644 index 000000000..42ae3b08a --- /dev/null +++ b/src/makedef.pl @@ -0,0 +1,61 @@ +#! perl + +&doMakeDef(); +exit(0); + +sub doMakeDef() +{ + print "####### Generating 'inkscape.def' #######\n"; + + my $nmlines = (); #store lines read in hash, to remove dupes + my @lines; #output lines + my $line; #single line of input + my $datestr; #current date + local(*PIPE); #output of the 'nm' command + local(*OUTFILE); #output file - inkscape.def + + open (PIPE, "nm libinkscape.a |"); + while() + { + if ($_ =~ /\./) + { + next; + } + elsif ($_ =~ /T _/) + { + $line = $_; + $line =~ s/.* T _//; + $nmlines->{$line} = 1; + } + elsif ($_ =~ /B _/) + { + $line = $_; + $line =~ s/.* B _//; + $nmlines->{$line} = 1; + } + } + close (PIPE); + + foreach (keys(%{$nmlines})) + { + push @lines, $_; + } + + @lines = sort(@lines); + + $datestr = gmtime(); + open (OUTFILE, ">inkscape.def"); + print OUTFILE ";########################################################\n"; + print OUTFILE ";## File: inkscape.def\n"; + print OUTFILE ";## Purpose: Used by dllwrap to make inkscape.dll\n"; + print OUTFILE ";## Generated by makedef.pl at :$datestr\n"; + print OUTFILE ";########################################################\n\n"; + print OUTFILE "LIBRARY\tinkscape.dll\n"; + print OUTFILE "EXPORTS\n"; + foreach (<@lines>) + { + # print $_, "\n"; + print OUTFILE " ", $_, "\n"; + } + close (OUTFILE); +} diff --git a/src/marker-status.cpp b/src/marker-status.cpp new file mode 100644 index 000000000..d8b5e52ac --- /dev/null +++ b/src/marker-status.cpp @@ -0,0 +1,23 @@ + +void marker_status(char const *format, ...) +{ + /* Don't bother inlining this. Not called often, and eventually all + calls will be removed anyway. */ +#if 0 /* Bryce sets this to 1. */ + va_list args; + va_start(args, format); + g_logv(marker_status_INITIAL_ARGS, format, args); + va_end(args); +#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:encoding=utf-8:textwidth=99 : diff --git a/src/marker-status.h b/src/marker-status.h new file mode 100644 index 000000000..102604985 --- /dev/null +++ b/src/marker-status.h @@ -0,0 +1,19 @@ +#ifndef SEEN_MARKER_DEBUG_H +#define SEEN_MARKER_DEBUG_H + +#include "gnuc-attribute.h" + +void marker_status(char const *format, ...) _gnuc_attribute((format(__printf__, 1, 2))); + +#endif /* !SEEN_MARKER_DEBUG_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:encoding=utf-8:textwidth=99 : diff --git a/src/media.cpp b/src/media.cpp new file mode 100644 index 000000000..8f9dfc18a --- /dev/null +++ b/src/media.cpp @@ -0,0 +1,27 @@ +#include "media.h" + +void +media_clear_all(Media &media) +{ + media.print = false; + media.screen = false; +} + +void +media_set_all(Media &media) +{ + media.print = true; + media.screen = true; +} + + +/* + 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 : diff --git a/src/media.h b/src/media.h new file mode 100644 index 000000000..8ae374aa1 --- /dev/null +++ b/src/media.h @@ -0,0 +1,24 @@ +#ifndef INKSCAPE_MEDIA_H +#define INKSCAPE_MEDIA_H + +class Media { +public: + bool print; + bool screen; +}; + +void media_clear_all(Media &); +void media_set_all(Media &); + +#endif /* !INKSCAPE_MEDIA_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:encoding=utf-8:textwidth=99 : diff --git a/src/memeq.h b/src/memeq.h new file mode 100644 index 000000000..db348d3f5 --- /dev/null +++ b/src/memeq.h @@ -0,0 +1,25 @@ +#ifndef INKSCAPE_MEMEQ_H +#define INKSCAPE_MEMEQ_H + +#include + +/** Convenience/readability wrapper for memcmp(a,b,n)==0. */ +inline bool +memeq(void const *a, void const *b, size_t n) +{ + return std::memcmp(a, b, n) == 0; +} + + +#endif /* !INKSCAPE_MEMEQ_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:encoding=utf-8:textwidth=99 : diff --git a/src/menus-skeleton.h b/src/menus-skeleton.h new file mode 100644 index 000000000..0ecfb7cef --- /dev/null +++ b/src/menus-skeleton.h @@ -0,0 +1,252 @@ +#ifndef SEEN_MENUS_SKELETON_H +#define SEEN_MENUS_SKELETON_H + +#include + +#ifdef __cplusplus +#undef N_ +#define N_(x) x +#endif + +static char const menus_skeleton[] = +"\n" +"\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +/* These are ugly, but what needs to happen here is allowing users + to use the native PS support if they are using another print driver. + This is done through the "Print Direct" command. Which is inserted + here based on if those other drivers are being built. */ +#ifdef WITH_GNOME_PRINT +" \n" +#endif +#ifdef WIN32 +" \n" +#endif +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +#ifdef WITH_INKBOARD +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +#endif +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +//" \n" +" \n" +"\n"; + +#define MENUS_SKELETON_SIZE (sizeof(menus_skeleton) - 1) + + +#endif /* !SEEN_MENUS_SKELETON_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:encoding=utf-8:textwidth=99 : diff --git a/src/message-context.cpp b/src/message-context.cpp new file mode 100644 index 000000000..5055f4102 --- /dev/null +++ b/src/message-context.cpp @@ -0,0 +1,94 @@ +/* + * MessageContext - context for posting status messages + * + * Authors: + * MenTaLguY + * + * Copyright (C) 2004 MenTaLguY + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include "message-context.h" +#include "message-stack.h" + +namespace Inkscape { + +MessageContext::MessageContext(MessageStack *stack) +: _stack(stack), _message_id(0), _flash_message_id(0) +{ + GC::anchor(_stack); +} + +MessageContext::~MessageContext() { + clear(); + GC::release(_stack); + _stack = NULL; +} + +void MessageContext::set(MessageType type, gchar const *message) { + if (_message_id) { + _stack->cancel(_message_id); + } + _message_id = _stack->push(type, message); +} + +void MessageContext::setF(MessageType type, gchar const *format, ...) +{ + va_list args; + va_start(args, format); + setVF(type, format, args); + va_end(args); +} + +void MessageContext::setVF(MessageType type, gchar const *format, va_list args) +{ + gchar *message=g_strdup_vprintf(format, args); + set(type, message); + g_free(message); +} + +void MessageContext::flash(MessageType type, gchar const *message) { + if (_flash_message_id) { + _stack->cancel(_flash_message_id); + } + _flash_message_id = _stack->flash(type, message); +} + +void MessageContext::flashF(MessageType type, gchar const *format, ...) { + va_list args; + va_start(args, format); + flashVF(type, format, args); + va_end(args); +} + +void MessageContext::flashVF(MessageType type, gchar const *format, va_list args) { + gchar *message=g_strdup_vprintf(format, args); + flash(type, message); + g_free(message); +} + +void MessageContext::clear() { + if (_message_id) { + _stack->cancel(_message_id); + _message_id = 0; + } + if (_flash_message_id) { + _stack->cancel(_flash_message_id); + _flash_message_id = 0; + } +} + +} + +/* + 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 : diff --git a/src/message-context.h b/src/message-context.h new file mode 100644 index 000000000..241b38660 --- /dev/null +++ b/src/message-context.h @@ -0,0 +1,118 @@ +/** \file + * A convenience class for working with MessageStacks. + */ + +/* + * Authors: + * MenTaLguY + * + * Copyright (C) 2004 MenTaLguY + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SEEN_INKSCAPE_MESSAGE_CONTEXT_H +#define SEEN_INKSCAPE_MESSAGE_CONTEXT_H + +#include +#include +#include "message.h" + +namespace Inkscape { + +class MessageStack; + +/** A convenience class for working with MessageStacks. + * + * In general, a particular piece of code will only want to display + * one status message at a time. This class takes care of tracking + * a "current" message id in a particular stack for us, and provides + * a convenient means to remove or replace it. + * + * @see Inkscape::MessageStack + */ +class MessageContext { +public: + /** Constructs an Inkscape::MessageContext referencing a particular + * Inkscape::MessageStack, which will be used for our messages + * + * MessageContexts retain references to the MessageStacks they use. + * + * @param stack the Inkscape::MessageStack to use for our messages + */ + MessageContext(MessageStack *stack); + ~MessageContext(); + + /** @brief pushes a message on the stack, replacing our old message + * + * @param type the message type + * @param message the message text + */ + void set(MessageType type, gchar const *message); + + /** @brief pushes a message on the stack using prinf-style formatting, + * and replacing our old message + * + * @param type the message type + * @param format a printf-style formatting string + */ + void setF(MessageType type, gchar const *format, ...); + + /** @brief pushes a message on the stack using printf-style formatting, + * and a stdarg argument list + * + * @param type the message type + * @param format a printf-style formatting string + * @param args printf-style arguments + */ + void setVF(MessageType type, gchar const *format, va_list args); + + /** @brief pushes a message onto the stack for a brief period of time + * without disturbing our "current" message + * + * @param type the message type + * @param message the message text + */ + void flash(MessageType type, gchar const *message); + + /** @brief pushes a message onto the stack for a brief period of time + * using printf-style formatting, without disturbing our current + * message + * + * @param type the message type + * @param format a printf-style formatting string + */ + void flashF(MessageType type, gchar const *format, ...); + + /** @brief pushes a message onto the stack for a brief period of time + * using printf-style formatting and a stdarg argument list; + * it does not disturb our "current" message + * + * @param type the message type + * @param format a printf-style formatting string + * @param args printf-style arguments + */ + void flashVF(MessageType type, gchar const *format, va_list args); + + /** @brief removes our current message from the stack */ + void clear(); + +private: + MessageStack *_stack; ///< the message stack to use + MessageId _message_id; ///< our current message id, or 0 + MessageId _flash_message_id; ///< current flashed message id, or 0 +}; + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/message-stack.cpp b/src/message-stack.cpp new file mode 100644 index 000000000..23ff1435f --- /dev/null +++ b/src/message-stack.cpp @@ -0,0 +1,160 @@ +/* + * MessageStack - context for posting status messages + * + * Authors: + * MenTaLguY + * + * Copyright (C) 2004 MenTaLguY + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include "message-stack.h" + +namespace Inkscape { + +MessageStack::MessageStack() +: _messages(NULL), _next_id(1) +{ +} + +MessageStack::~MessageStack() +{ + while (_messages) { + _messages = _discard(_messages); + } +} + +MessageId MessageStack::push(MessageType type, gchar const *message) { + return _push(type, 0, message); +} + +MessageId MessageStack::pushF(MessageType type, gchar const *format, ...) +{ + va_list args; + va_start(args, format); + MessageId id=pushVF(type, format, args); + va_end(args); + return id; +} + +MessageId MessageStack::pushVF(MessageType type, gchar const *format, va_list args) +{ + MessageId id; + gchar *message=g_strdup_vprintf(format, args); + id = push(type, message); + g_free(message); + return id; +} + +void MessageStack::cancel(MessageId id) { + Message **ref; + for ( ref = &_messages ; *ref ; ref = &(*ref)->next ) { + if ( (*ref)->id == id ) { + *ref = _discard(*ref); + _emitChanged(); + break; + } + } +} + +MessageId MessageStack::flash(MessageType type, gchar const *message) { + switch (type) { + case INFORMATION_MESSAGE: // stay rather long so as to seem permanent, but eventually disappear + return _push(type, 6000 + 80*strlen(message), message); + break; + case ERROR_MESSAGE: // pretty important stuff, but temporary + return _push(type, 4000 + 60*strlen(message), message); + break; + case WARNING_MESSAGE: // a bit less important than error + return _push(type, 2000 + 40*strlen(message), message); + break; + case NORMAL_MESSAGE: // something ephemeral + default: + return _push(type, 1000 + 20*strlen(message), message); + break; + } +} + +MessageId MessageStack::flashF(MessageType type, gchar const *format, ...) { + va_list args; + va_start(args, format); + MessageId id = flashVF(type, format, args); + va_end(args); + return id; +} + +MessageId MessageStack::flashVF(MessageType type, gchar const *format, va_list args) +{ + gchar *message=g_strdup_vprintf(format, args); + MessageId id = flash(type, message); + g_free(message); + return id; +} + +MessageId MessageStack::_push(MessageType type, guint lifetime, gchar const *message) +{ + Message *m=new Message; + MessageId id=_next_id++; + + m->stack = this; + m->id = id; + m->type = type; + m->message = g_strdup(message); + + if (lifetime) { + m->timeout_id = g_timeout_add(lifetime, &MessageStack::_timeout, m); + } else { + m->timeout_id = 0; + } + + m->next = _messages; + _messages = m; + + _emitChanged(); + + return id; +} + +MessageStack::Message *MessageStack::_discard(MessageStack::Message *m) +{ + Message *next=m->next; + if (m->timeout_id) { + g_source_remove(m->timeout_id); + m->timeout_id = 0; + } + g_free(m->message); + m->message = NULL; + m->stack = NULL; + delete m; + return next; +} + +void MessageStack::_emitChanged() { + if (_messages) { + _changed_signal.emit(_messages->type, _messages->message); + } else { + _changed_signal.emit(NORMAL_MESSAGE, NULL); + } +} + +gboolean MessageStack::_timeout(gpointer data) { + Message *m=reinterpret_cast(data); + m->timeout_id = 0; + m->stack->cancel(m->id); + return FALSE; +} + +} + +/* + 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 : diff --git a/src/message-stack.h b/src/message-stack.h new file mode 100644 index 000000000..4741cce1e --- /dev/null +++ b/src/message-stack.h @@ -0,0 +1,177 @@ +/** \file + * Managing current status messages. + */ + +/* + * Authors: + * MenTaLguY + * + * Copyright (C) 2004 MenTaLguY + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SEEN_INKSCAPE_MESSAGE_STACK_H +#define SEEN_INKSCAPE_MESSAGE_STACK_H + +#include +#include +#include +#include "gc-managed.h" +#include "gc-finalized.h" +#include "gc-anchored.h" +#include "message.h" + +namespace Inkscape { + +/** + * A class which holds a stack of displayed messages. + * + * Messages can be pushed onto the top of the stack, and removed + * from any point in the stack by their id. + * + * Messages may also be "flashed", meaning that they will be + * automatically removed from the stack a fixed period of time + * after they are pushed. + * + * "Flashed" warnings and errors will persist longer than normal + * messages. + * + * There is no simple "pop" operation provided, since these + * stacks are intended to be shared by many different clients; + * assuming that the message you pushed is still on top is an + * invalid and unsafe assumption. + */ +class MessageStack : public GC::Managed<>, + public GC::Finalized, + public GC::Anchored +{ +public: + MessageStack(); + ~MessageStack(); + + /** @brief returns the type of message currently at the top of the stack */ + MessageType currentMessageType() { + return _messages ? _messages->type : NORMAL_MESSAGE; + } + /** @brief returns the text of the message currently at the top of + * the stack + */ + gchar const *currentMessage() { + return _messages ? _messages->message : NULL; + } + + /** @brief connects to the "changed" signal which is emitted whenever + * the topmost message on the stack changes. + */ + sigc::connection connectChanged(sigc::slot slot) + { + return _changed_signal.connect(slot); + } + + /** @brief pushes a message onto the stack + * + * @param type the message type + * @param message the message text + * + * @return the id of the pushed message + */ + MessageId push(MessageType type, gchar const *message); + + /** @brief pushes a message onto the stack using printf-like formatting + * + * @param type the message type + * @param format a printf-style format string + * + * @return the id of the pushed message + */ + MessageId pushF(MessageType type, gchar const *format, ...); + + /** @brief pushes a message onto the stack using printf-like formatting, + * using a stdarg argument list + * + * @param type the message type + * @param format a printf-style format string + * @param args the subsequent printf-style arguments + * + * @return the id of the pushed message + */ + MessageId pushVF(MessageType type, gchar const *format, va_list args); + + /** @brief removes a message from the stack, given its id + * + * This method will remove a message from the stack if it has not + * already been removed. It may be removed from any part of the stack. + * + * @param id the message id to remove + */ + void cancel(MessageId id); + + /** @brief temporarily pushes a message onto the stack + * + * @param type the message type + * @param message the message text + * + * @return the id of the pushed message + */ + MessageId flash(MessageType type, gchar const *message); + + /** @brief temporarily pushes a message onto the stack using + * printf-like formatting + * + * @param type the message type + * @param format a printf-style format string + * + * @return the id of the pushed message + */ + MessageId flashF(MessageType type, gchar const *format, ...); + + /** @brief temporarily pushes a message onto the stack using + * printf-like formatting, using a stdarg argument list + * + * @param type the message type + * @param format a printf-style format string + * @param args the printf-style arguments + * + * @return the id of the pushed message + */ + MessageId flashVF(MessageType type, gchar const *format, va_list args); + +private: + struct Message { + Message *next; + MessageStack *stack; + MessageId id; + MessageType type; + gchar *message; + guint timeout_id; + }; + + MessageStack(MessageStack const &); // no copy + void operator=(MessageStack const &); // no assign + + /// pushes a message onto the stack with an optional timeout + MessageId _push(MessageType type, guint lifetime, gchar const *message); + + Message *_discard(Message *m); ///< frees a message struct and returns the next such struct in the list + void _emitChanged(); ///< emits the "changed" signal + static gboolean _timeout(gpointer data); ///< callback to expire flashed messages + + sigc::signal _changed_signal; + Message *_messages; ///< the stack of messages as a linked list + MessageId _next_id; ///< the next message id to assign +}; + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/message.h b/src/message.h new file mode 100644 index 000000000..24a61fea7 --- /dev/null +++ b/src/message.h @@ -0,0 +1,49 @@ +/* + * status-message-related types + * + * Authors: + * MenTaLguY + * + * Copyright (C) 2004 MenTaLguY + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SEEN_INKSCAPE_MESSAGE_H +#define SEEN_INKSCAPE_MESSAGE_H + +namespace Inkscape { + +/** + * A hint about the meaning of a message; is it an ordinary message, + * a message advising the user of some significant or unexpected condition, + * or a message indicating an unambiguous error. + */ +enum MessageType { + NORMAL_MESSAGE, + WARNING_MESSAGE, + ERROR_MESSAGE, + INFORMATION_MESSAGE +}; + +/** + * An integer ID which identifies a displayed message in a particular + * Inkscape::MessageStack + * + * @see Inkscape::MessageStack + */ +typedef unsigned long MessageId; + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/mkdep.pl b/src/mkdep.pl new file mode 100755 index 000000000..146641aa3 --- /dev/null +++ b/src/mkdep.pl @@ -0,0 +1,565 @@ +#!/usr/bin/perl +############################################################################ +# +# Creates make.dep - a list of all dependencies. +# Uses make.files as input; mkfiles.pl should be run before mkdep.pl. +# +############################################################################ + +$obj_ext = "o"; +$linebreak = "\\"; +$dir_sep = "/"; +$nodepend = 0; +$tolower = 1; # lowercase all filenames +$include_path = ""; + +# +# main - top level code +# + +if ( @ARGV ) { # parse command line args + foreach $a ( @ARGV ) { + if ( $a eq "-w" ) { + $warnon = 1; + } + elsif ( ( $a eq "-?" ) || ( $a eq "-h" ) ) { + print "usage: perl mkdep.pl includedirs...\"\n\n" . + "\"includedirs\" is any number of include directories that are located\n". + "outside of your project directory tree.\n\n". + "example:\n perl mkdep.pl s:\\ w:\\include\n"; + exit 1; + } + else { + push @extradirs, $a; + $include_path = $include_path . $a . ";"; + } + } + print "Includepath used: ".join(" ",@extradirs)."\n"; +} + +&make_files; +&make_dep; + +exit 0; + + +# +# make_files: read "make.files", create "make.ofiles" +# + +sub make_files { + + # create output file (now, to check for permissions) + open OFILES, ">make.ofiles" or + die("Can't create make.ofiles"); + + # read file list + #open FILES,"; + #close FILES; + + open FILES, ") + { + $line = $_; + #Trim whitespace from ends + $line =~ s/^\s|\t|\n//; + #Ignore if no text in line + next if ( length($line) < 1 ); + #Ignore if starts with '#' + next if ( $line =~ /^#/ ); + # print $line, "\n"; + push @lines, $line; + } + close FILES; + + # sort out header and source files + @hdr = sort grep(/\.(h|hpp|icc|ia)$/i,@lines); + @src = sort grep(/\.(c|cpp|cc|s)$/i,@lines); + + print STDERR scalar @lines ." files found (".scalar @src." sources and ".scalar @hdr." headers) in \"make.files\".\n"; + + # scan specified include dirs + for ( @extradirs ) { + push @extrafiles, &find_files($_,"\.*",1); + } + # add external includefiles to file list + push @lines, @extrafiles; + + print STDERR scalar @lines . " files found everywhere.\n"; + + # create @inc and %full_path + $last = ""; + for ( @lines ) { + m/^(.*\/)([^\/]*)$/ || m/^()(.*)$/; + $path = $1; + $filepart = $2; + if ( $path ne $last ) { + # store dir path + if (length($path) > 0) { + push (@inc, $path ); + } + $last = $path; + } + + # remove any newlines at end of line + $filepart =~ s/(.*)\n+$/$1/g; + + # store full path for all include files + if ( !defined $full_path{ lc $filepart } ) { + $full_path{lc $filepart} = $path.$filepart; + } + } + + $includes = join (" \\\n\t-I", @inc); + $includes =~ s/\/ \\/ \\/g; # remove trailing slashes + + # save source list for use by make_depend + $sourcelist = join(" ",@src); + + # create object list from source list + $objects = &Objects( $sourcelist ); + + # save object list for use by make_depend + $objectlist = $objects; + + # make a "lints" list for linting + $lints = $objects; + $lints =~ s/\.${obj_ext}/\.lint/g; + $lints =~ s/ / \\\n\t/g; + + # put newline+tab on all files (to make nice lists) + $objects =~ s/ / \\\n\t/g; + + # print file + $datestr = gmtime(); + print OFILES "########################################################\n"; + print OFILES "## File: make.ofiles\n"; + print OFILES "## Purpose: Object file listing for use by Makefiles\n"; + print OFILES "## Generated by mkdep.pl at :$datestr\n"; + print OFILES "## Do not edit this file! Changes will be lost.\n"; + print OFILES "########################################################\n\n"; + print OFILES "OBJECTS = \\\n\t" . $objects . "\n\n"; + print OFILES "LINTS = \\\n\t" . $lints . "\n\n"; + if ( $includes ne "" ) { + print OFILES "INCLUDEPATH = \\\n\t-I" . $includes . "\n"; + } + + close OFILES; +} + + + +# +# Finds files. +# +# Examples: +# find_files("/usr","\.cpp$",1) - finds .cpp files in /usr and below +# find_files("/tmp","^#",0) - finds #* files in /tmp +# + +sub find_files { + my($dir,$match,$descend) = @_; + my($file,$p,@files); + local(*D); + $dir =~ s=\\=/=g; + ($dir eq "") && ($dir = "."); + + if ( opendir(D,$dir) ) { + if ( $dir eq "." ) { + $dir = ""; + } else { + ($dir =~ /\/$/) || ($dir .= "/"); + } + foreach $file ( readdir(D) ) { + + next if ( $file =~ /^\.\.?$/ ); + $p = $dir . $file; +# ($file =~ /$match/i) && (push @files, ($tolower==0 ? $p : lc($p))); + ($file =~ /$match/i) && (push @files, $p ); + if ( $descend && -d $p && ! -l $p ) { + push @files, &find_files($p,$match,$descend); + } + } + closedir(D); + } + return @files; +} + + +# +# strip_project_val(tag) +# +# Strips white space from project value strings. +# + +sub strip_project_val { + my($v) = @_; + $v =~ s/^\s+//; # trim white space + $v =~ s/\s+$//; + return $v; +} + +# +# Objects(files) +# +# Replaces any extension with .o ($obj_ext). +# + +sub Objects { + local($_) = @_; + my(@a); + @a = split(/\s+/,$_); + foreach ( @a ) { + s-\.\w+$-.${obj_ext}-; + } + return join(" ",@a); +} + + +sub make_dep { + + $outfile = "make.dep"; + + print STDERR "Parsing source files...\n"; + + open(DEP,">" . fix_path($outfile)) or + die ("Can't create \"$outfile\""); + + &BuildObj( $objectlist, $sourcelist ); + + $datestr = gmtime(); + print DEP "########################################################\n"; + print DEP "## File: make.dep\n"; + print DEP "## Purpose: Dependency listing for use by Makefiles\n"; + print DEP "## Generated by mkdep.pl at :$datestr\n"; + print DEP "## Do not edit this file! Changes will be lost.\n"; + print DEP "########################################################\n\n"; + print DEP $text; + close DEP; + print STDERR "\n"; + + + if ( $warnon && (keys %missing) ) { + # print out missing files + if ( !open MISSING, ">makemiss.txt" ) { + print "Couldn't create \"makemiss.txt\": $!\n"; + print "Printing on screen.\n"; + *MISSING = *STDOUT; + } + + print MISSING "Missing files: (Note: only the first miss for each file is logged)\n\n"; + printf MISSING "%-32s %s\n","",""; + printf MISSING "%-32s %s\n","------","---------------"; + @missingfiles = sort @missingfiles; + foreach ( @missingfiles ) { + @a = split( ",", $_ ); + printf MISSING "%-32s %s\n",$a[0],$a[1]; + } + close MISSING; + + print STDOUT "(missing files written to \"makemiss.txt\")\n"; + } +} + + +# +# BuildObj(objects,sources) +# +# Builds the object files. +# + +sub uniq (@) { + if ($#_ <= 0) { + return @_; + } + my $prev = shift(@_); + my @ret = ($prev); + while(@_) { + my $curr = shift(@_); + if ($curr ne $prev) { + push @ret, $curr; + $prev = $curr; + } + } + return @ret; +} + +sub BuildObj { + my($obj,$src) = @_; + my(@objv,$srcv,$i,$s,$o,$d,$c,$comp,$cimp); + $text = ""; + @objv = split(/\s+/,$obj); + @srcv = split(/\s+/,$src); + $tot = $#objv; + + # fix dependpath + if ( ! $depend_path_fixed ) { + $depend_path_fixed = 1; + $depend_path = $include_path; + $count = 0; + + while ( $count < 100 ) { + if ( $depend_path =~ s/(\$[\{\(]?\w+[\}\)]?)/035/ ) { + $_ = $1; + s/[\$\{\}\(\)]//g; + $depend_path =~ s/035/$ENV{$_}/g; + } else { + $count = 100; + } + } + @dep_path = &split_path($depend_path); + unshift @dep_path, ""; # current dir first + } + + %missing = (); + # go through file list + for $i ( 0..$#objv ) { + $s = $srcv[$i]; + $o = $objv[$i]; + next if $s eq ""; + + if ( $warnon ) { + print STDERR "\r" . ($i+1) . " missing files: ". scalar keys %missing; + } + else { + print STDERR "\r" . ($i+1); + } + + @incfiles = (); + $d = &make_depend(lc $s); + + for ( @incfiles ) { + push @ifiles, $full_path{$n}; + } + + $text .= $o . ": ${linebreak}\n\t" . $s; + + @incpath = (); + for ( @incfiles ) { + if ( defined $full_path{$_} ) { + push @incpath, $full_path{$_}; + } + } + @incpath = uniq sort @incpath; + for ( @incpath ) { + $text .= " ${linebreak}\n\t" . $_; + } + $text .= "\n\n"; + + # ----------------------------- +# print "\n". $text; +# exit; + } + chop $text; +} + + +# +# build_dep() - Internal for make_depend() +# + +sub build_dep { + my($file) = @_; + my(@i,$a,$n); + $a = ""; + + if ( !defined $depend_dict{$file} ) { + return $a; + } + + @i = split(/ /,$depend_dict{$file}); +# print "INC2: ($file) ".$depend_dict{$file}."\n"; + + for $n ( @i ) { +# if ( (!defined $dep_dict{$n}) && defined($full_path{$n}) ) { + if ( !defined $dep_dict{$n} ) { + $dep_dict{$n} = 1; + push @incfiles, $n; +# $a .= $full_path{$n} . " " . &build_dep($n); + &build_dep($n); + } + } +# print "INCFILES ($file): ".@incfiles."\n"; + return $a; +} + + +# +# make_depend(file) +# +# Returns a list of included files. +# Uses the global $depend_path variable. +# + +sub make_depend { + my($file) = @_; + my($i,$count); + if ( $nodepend ) { + return ""; + } + + @cur_dep_path = @dep_path; + if ( $file =~ /(.*[\/\\])/ ) { + $dep_curdir = $1; + splice( @cur_dep_path, 0, 0, $dep_curdir ); + } else { + $dep_curdir = ""; + } + $dep_file = $file; + &canonical_dep($file); + %dep_dict = (); + + + $i = &build_dep($file); +# chop $i; + + $i =~ s=/=$dir_sep=g unless $is_unix; + $i =~ s=([a-zA-Z]):/=//$1/=g if (defined($gnuwin32) && $gnuwin32); +# @l = sort split(/ /,$i); + @l = split(/ /,$i); + return join(" ${linebreak}\n\t", @l ); +# return $i; # all on one line! +} + +# +# canonical_dep(file) - Internal for make_depend() +# +# Reads the file and all included files recursively. +# %depend_dict associates a file name to a list of included files. +# + +sub canonical_dep { + my($file) = @_; + my(@inc,$i); + push @sfile, $file; + # ----------------------------- +# print "FILE: $file\n"; + @inc = &scan_dep($file); + + if ( @inc ) { + $depend_dict{$file} = join(" ",@inc); + + # recursively scan all files not already scanned + for $i ( @inc ) { + if ( ! defined( $depend_dict{$i} ) ) { + &canonical_dep($i); + + # still nothing defined? + if ( ! defined( $depend_dict{$i} ) ) { + # insert dummy string, so we don't parse the file again + $depend_dict{$i} = ""; + } +# print "CACHE: $i: $depend_dict{$i}\n"; + } + } + } + pop @sfile, $file; +} + +# +# scan_dep(file) - Internal for make_depend() +# +# Returns an array of included files. +# + +sub scan_dep { + my($file) = @_; + my($dir,$path,$found,@allincs,@includes,%incs); + $path = ($file eq $dep_file) ? $file : $dep_curdir . $file; + @includes = (); + +# print STDERR "SCAN_DEP $file\n"; + + # replace backslash with regular slash + $file =~ s-\\-/-g; + + # look in the file list + if ( !defined $full_path{$file} ) { + # file not in list - some special case + + # handle explicit path'ed includes (such as #include "common/bsp821.h") + if ( $file =~ /[\/\\]([^\\\/]+)$/ ) { + $full_path{$file} = $full_path{$1}; + } + } + + $open = open TMP,fix_path( $full_path{$file} ); + + if ( $open ) { + @source = ; + + # find all lines with include as first text in line (no comments allowed) + @allincs = grep(/^\s*[\#\.]\s*include/,@source); + +# print "SCAN_ALLINC: $#allincs include lines found\n"; + + # iterate all include lines + foreach ( @allincs ) { + # parse out filename + if ( !(/\s*[\#\.]\s*include\s+[<\"]([^>\"]*)[>\"]/) || defined($incs{$1}) ) { + next; + } + push(@includes, ($tolower==0 ? $1 : lc($1))); + +# print "SCAN_INC: $1\n"; + + $incs{$1} = "1"; + } + + # ----------------------------- +# print "INC: ".join( ",", @includes)."\n"; + close(TMP); + } + else { + &add_missing( $file ); + } + $/ = "\n"; + return @includes; +} + + +sub add_missing { + my($file) = @_; + @s = @sfile; + pop @s; + push @missingfiles, $file.",".join(" -> ",@s); + $missing{$file} = 1; +} + +# +# split_path(path) +# +# Splits a path containing : (Unix) or ; (MSDOS, NT etc.) separators. +# Returns an array. +# + +sub split_path { + my($p) = @_; + return "" if !defined($p); + $p =~ s=:=;=g if $is_unix; + $p =~ s=[/\\]+=/=g; + $p =~ s=([^/:]);=$1/;=g; + $p =~ s=([^:;/])$=$1/=; + $p =~ s=/=$dir_sep=g unless $is_unix; + return split(/;/,$p); +} + +# +# fix_path(path) +# +# Converts all '\' to '/' if this really seems to be a Unix box. +# + +sub fix_path { + my($p) = @_; +# if ( $really_unix ) { +# $p =~ s-\\-/-g; +# } else { +# $p =~ s-/-\\-g; + $p =~ s-\\-/-g; +# } + return $p; +} diff --git a/src/mkfiles.pl b/src/mkfiles.pl new file mode 100755 index 000000000..e313f5af3 --- /dev/null +++ b/src/mkfiles.pl @@ -0,0 +1,226 @@ +#!/usr/bin/perl +############################################################################ +# +# Creates make.files - a list of all source files +# +############################################################################ + +# +# main - top level code +# + +if ( @ARGV ) { # parse command line args + print "usage: perl mkfiles.pl\n\n"; + exit 1; +} + +$tolower = 0; #Do we want to convert items in make.files to lower case? +$verbose = 1; #Do we want to list the files? + + + +print "###########################\n"; +print "## Generating make.files ##\n"; +print "###########################\n"; + +&doMakeFiles(); #Do your magic! + +print "###########################\n"; +print "## make.files done ##\n"; +print "###########################\n"; + +exit 0; + + + +############################################################################ +# +# Main routine. Read make.exclude, then search the current directory +# recursively for source code items, rejecting those listed in make.exclude +# +############################################################################ +sub doMakeFiles +{ + my $excludes; #list of files to exclude + my @excluded; #list of files that were actually excluded + my @irrelevant; #list of files in make.exclude that were not found + my $line; #current line from make.exclude + my $patterns; #The extensions for which to search + my @files; #Result of find_files() + my @codefiles; #Sorted list of @files + my $datestr; #Current date + local(*MAKE_DOT_EXCLUDE); + local(*MAKE_DOT_FILES); + + if ( -r "make.exclude" ) + { + open MAKE_DOT_EXCLUDE, "make.exclude" or + die "make.exclude: $!"; + while () + { + $line = $_; + #Trim whitespace from ends + $line =~ s/^\s|\t|\n//; + #Ignore if no text in line + next if ( length($line) < 1 ); + #Ignore if starts with '#' + next if ( $line =~ /^#/ ); + # print $line, "\n"; + $excludes->{$line} = 1; + } + close MAKE_DOT_EXCLUDE; + } + + #while ( my ($key, $value) = each(%{$excludes}) ) + # { + # print "$key => $value\n"; + # } + + $patterns = '.c$|.cpp$|.h$|.hpp$'; + + @files = &find_files(".", $patterns, $excludes, \@excluded); + + # Print the list of files excluded + print "###EXCLUDED: ", scalar(@excluded), " files/directories rejected by request\n"; + @excluded = sort(@excluded); + foreach (@excluded) + { + if ($verbose) + { + print " $_\n"; + } + #mark the item in the hash table , so we know it occurred + $excludes->{$_} = 0; + } + + # Count how many files in make.exclude were not used + foreach (keys(%{$excludes})) + { + #if still a 1, then it was not used + if ($excludes->{$_} !=0) + { + push @irrelevant, $_; + } + } + + # Print the list of file in make.exclude, but not used. + if (scalar(@irrelevant)>0) + { + @irrelevant = sort(@irrelevant); + print "###IRRELEVANT: ", scalar(@irrelevant), " entries in make.exclude that were not found\n"; + foreach (@irrelevant) + { + if ($verbose) + { + print " $_\n"; + } + } + } + + @codefiles = sort(@files); + + $datestr = gmtime(); + open MAKE_DOT_FILES, ">make.files" or + die("make.files: $!"); + print MAKE_DOT_FILES "########################################################\n"; + print MAKE_DOT_FILES "## File: make.files\n"; + print MAKE_DOT_FILES "## Purpose: Used by mkdep.pl\n"; + print MAKE_DOT_FILES "## Generated by mkfiles.pl at :$datestr\n"; + print MAKE_DOT_FILES "########################################################\n\n"; + foreach (@codefiles) + { + next if (length($_)<1); + print MAKE_DOT_FILES "$_\n"; + } + close MAKE_DOT_FILES; + +} # doMakeFiles + + + + +############################################################################ +# +# Search the current directory recursively, checking rejecting items from +# make.exclude, or adding those that match the extensions in $patterns. +# @param $dir the current directory being searched +# @param $patterns the file extensions for which we are searching +# @param $excludes a hash of the files which should be rejected +# @param $excluded an array in which to store a record of all files rejected +# +############################################################################ +sub find_files +{ + my($dir, $patterns, $excludes, $excluded) = @_; + my $file; + my $p; + my @files = (); + my @file_result; + local(*DIR); + + + $dir =~ s=\\=/=g; + + # Check for 0-length + if (length($dir)<1) + { + $dir = '.'; + } + + # Process a directory of files + if ( opendir(DIR,$dir) ) + { + # Remove leading . + if ( $dir eq '.' ) + { + $dir = ''; + } + + foreach $file ( readdir(DIR) ) + { + # Don't allow '..' + next if ( $file =~ /^\.\.?$/ ); + next if ( length($file)<1 ); + + if (length($dir)>0) + { + $p = $dir . '/' . $file; + } + else + { + $p = $file; + } + + if ($tolower != 0) + { + $p = lc($p); + } + + #print "File:$p\n"; + + # Check if it is an excluded file + if (defined($excludes->{$p})) + { + #print "EXCLUDED:$p\n"; + push @{$excluded}, $p; + next; + } + # Check for a desired extension + if ($p =~ /$patterns/i) + { + #print "match:$p\n"; + push @files, $p; + next; + } + #We have seen '0' inserted from a null result. This + #should avoid this problem + @file_result = &find_files($p, $patterns, $excludes, $excluded); + if (scalar(@file_result)>0) + { + push @files, @file_result; + } + } + closedir(DIR); + } + return @files; +} diff --git a/src/mod360-test.cpp b/src/mod360-test.cpp new file mode 100644 index 000000000..83f0feba7 --- /dev/null +++ b/src/mod360-test.cpp @@ -0,0 +1,70 @@ +#include +#include "mod360.h" +#include +#include + +/** Note: doesn't distinguish between 0.0 and -0.0. */ +static bool same_double(double const x, double const y) +{ + return ( ( isnan(x) && isnan(y) ) + || ( x == y ) ); +} + +int main(int argc, char **argv) +{ + double const inf = 1e400; + double const nan = inf - inf; + + utest_start("mod360.cpp"); + + UTEST_TEST("same_double") { + double const sd_cases[] = {inf, -inf, nan, 0.0, -1.0, 1.0, 8.0}; + for (unsigned i = 0; i < G_N_ELEMENTS(sd_cases); ++i) { + for (unsigned j = 0; j < G_N_ELEMENTS(sd_cases); ++j) { + UTEST_ASSERT( same_double(sd_cases[i], sd_cases[j]) + == ( i == j ) ); + } + } + } + + UTEST_TEST("mod360") { + struct Case { + double x; + double y; + } const cases[] = { + {0, 0}, + {10, 10}, + {360, 0}, + {361, 1}, + {-1, 359}, + {-359, 1}, + {-360, -0}, + {-361, 359}, + {inf, 0}, + {-inf, 0}, + {nan, 0}, + {720, 0}, + {-721, 359}, + {-1000, 80} + }; + for(unsigned i = 0; i < G_N_ELEMENTS(cases); ++i) { + Case const &c = cases[i]; + UTEST_ASSERT(same_double(mod360(c.x), c.y)); + } + } + + return ( utest_end() + ? EXIT_SUCCESS + : EXIT_FAILURE ); +} + +/* + 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 : diff --git a/src/mod360.cpp b/src/mod360.cpp new file mode 100644 index 000000000..a30aa65a7 --- /dev/null +++ b/src/mod360.cpp @@ -0,0 +1,29 @@ +#include +#include + +/** Returns \a x wrapped around to between 0 and less than 360, + or 0 if \a x isn't finite. +**/ +double mod360(double const x) +{ + double const m = fmod(x, 360.0); + double const ret = ( isnan(m) + ? 0.0 + : ( m < 0 + ? m + 360 + : m ) ); + g_return_val_if_fail(0.0 <= ret && ret < 360.0, + 0.0); + return ret; +} + +/* + 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 : diff --git a/src/mod360.h b/src/mod360.h new file mode 100644 index 000000000..378efab4b --- /dev/null +++ b/src/mod360.h @@ -0,0 +1,17 @@ +#ifndef SEEN_MOD360_H +#define SEEN_MOD360_H + +double mod360(double const x); + +#endif /* !SEEN_MOD360_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:encoding=utf-8:textwidth=99 : diff --git a/src/modifier-fns.h b/src/modifier-fns.h new file mode 100644 index 000000000..a3cab7d20 --- /dev/null +++ b/src/modifier-fns.h @@ -0,0 +1,64 @@ +#ifndef SEEN_MODIFIER_FNS_H +#define SEEN_MODIFIER_FNS_H + +/** \file + * Functions on GdkEventKey.state that test modifier keys. + * + * The MOD__SHIFT macro in macros.h is equivalent to mod_shift(event-\>key.state). + */ + +/* + * Hereby placed in public domain. + */ + +#include +#include + +inline bool +mod_shift(guint const state) +{ + return state & GDK_SHIFT_MASK; +} + +inline bool +mod_ctrl(guint const state) +{ + return state & GDK_CONTROL_MASK; +} + +inline bool +mod_alt(guint const state) +{ + return state & GDK_MOD1_MASK; +} + +inline bool +mod_shift_only(guint const state) +{ + return (state & GDK_SHIFT_MASK) && !(state & GDK_CONTROL_MASK) && !(state & GDK_MOD1_MASK); +} + +inline bool +mod_ctrl_only(guint const state) +{ + return !(state & GDK_SHIFT_MASK) && (state & GDK_CONTROL_MASK) && !(state & GDK_MOD1_MASK); +} + +inline bool +mod_alt_only(guint const state) +{ + return !(state & GDK_SHIFT_MASK) && !(state & GDK_CONTROL_MASK) && (state & GDK_MOD1_MASK); +} + +#endif /* !SEEN_MODIFIER_FNS_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:encoding=utf-8:textwidth=99 : diff --git a/src/node-context.cpp b/src/node-context.cpp new file mode 100644 index 000000000..747fa65b3 --- /dev/null +++ b/src/node-context.cpp @@ -0,0 +1,964 @@ +#define __SP_NODE_CONTEXT_C__ + +/* + * Node editing context + * + * Authors: + * Lauris Kaplinski + * bulia byak + * + * This code is in public domain, except stamping code, + * which is Copyright (C) Masatake Yamato 2002 + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include +#include "macros.h" +#include +#include "display/sp-canvas-util.h" +#include "object-edit.h" +#include "sp-path.h" +#include "path-chemistry.h" +#include "rubberband.h" +#include "desktop.h" +#include "desktop-handles.h" +#include "selection.h" +#include "pixmaps/cursor-node.xpm" +#include "message-context.h" +#include "node-context.h" +#include "pixmaps/cursor-node-d.xpm" +#include "prefs-utils.h" +#include "xml/node-event-vector.h" +#include "style.h" +#include "splivarot.h" + +static void sp_node_context_class_init(SPNodeContextClass *klass); +static void sp_node_context_init(SPNodeContext *node_context); +static void sp_node_context_dispose(GObject *object); + +static void sp_node_context_setup(SPEventContext *ec); +static gint sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event); +static gint sp_node_context_item_handler(SPEventContext *event_context, + SPItem *item, GdkEvent *event); + +static void nodepath_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name, + gchar const *old_value, gchar const *new_value, + bool is_interactive, gpointer data); + +static Inkscape::XML::NodeEventVector nodepath_repr_events = { + NULL, /* child_added */ + NULL, /* child_removed */ + nodepath_event_attr_changed, + NULL, /* content_changed */ + NULL /* order_changed */ +}; + +static SPEventContextClass *parent_class; + +static gchar *undo_label_1 = "dragcurve:1"; +static gchar *undo_label_2 = "dragcurve:2"; +static gchar *undo_label = undo_label_1; + +GType +sp_node_context_get_type() +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPNodeContextClass), + NULL, NULL, + (GClassInitFunc) sp_node_context_class_init, + NULL, NULL, + sizeof(SPNodeContext), + 4, + (GInstanceInitFunc) sp_node_context_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPNodeContext", &info, (GTypeFlags)0); + } + return type; +} + +static void +sp_node_context_class_init(SPNodeContextClass *klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + SPEventContextClass *event_context_class = (SPEventContextClass *) klass; + + parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass); + + object_class->dispose = sp_node_context_dispose; + + event_context_class->setup = sp_node_context_setup; + event_context_class->root_handler = sp_node_context_root_handler; + event_context_class->item_handler = sp_node_context_item_handler; +} + +static void +sp_node_context_init(SPNodeContext *node_context) +{ + SPEventContext *event_context = SP_EVENT_CONTEXT(node_context); + + event_context->cursor_shape = cursor_node_xpm; + event_context->hot_x = 1; + event_context->hot_y = 1; + + node_context->leftalt = FALSE; + node_context->rightalt = FALSE; + node_context->leftctrl = FALSE; + node_context->rightctrl = FALSE; + + new (&node_context->sel_changed_connection) sigc::connection(); +} + +static void +sp_node_context_dispose(GObject *object) +{ + SPNodeContext *nc = SP_NODE_CONTEXT(object); + SPEventContext *ec = SP_EVENT_CONTEXT(object); + + ec->enableGrDrag(false); + + nc->sel_changed_connection.disconnect(); + nc->sel_changed_connection.~connection(); + + Inkscape::XML::Node *repr = NULL; + if (nc->nodepath) { + repr = nc->nodepath->repr; + } + if (!repr && ec->shape_knot_holder) { + repr = ec->shape_knot_holder->repr; + } + + if (repr) { + sp_repr_remove_listener_by_data(repr, ec); + Inkscape::GC::release(repr); + } + + if (nc->nodepath) { + sp_nodepath_destroy(nc->nodepath); + nc->nodepath = NULL; + } + + if (ec->shape_knot_holder) { + sp_knot_holder_destroy(ec->shape_knot_holder); + ec->shape_knot_holder = NULL; + } + + if (nc->_node_message_context) { + delete nc->_node_message_context; + } + + G_OBJECT_CLASS(parent_class)->dispose(object); +} + +static void +sp_node_context_setup(SPEventContext *ec) +{ + SPNodeContext *nc = SP_NODE_CONTEXT(ec); + + if (((SPEventContextClass *) parent_class)->setup) + ((SPEventContextClass *) parent_class)->setup(ec); + + nc->sel_changed_connection.disconnect(); + nc->sel_changed_connection = SP_DT_SELECTION(ec->desktop)->connectChanged(sigc::bind(sigc::ptr_fun(&sp_node_context_selection_changed), (gpointer)nc)); + + Inkscape::Selection *selection = SP_DT_SELECTION(ec->desktop); + SPItem *item = selection->singleItem(); + + nc->nodepath = NULL; + ec->shape_knot_holder = NULL; + + nc->rb_escaped = false; + + nc->cursor_drag = false; + + nc->added_node = false; + + if (item) { + nc->nodepath = sp_nodepath_new(ec->desktop, item); + if ( nc->nodepath) { + //point pack to parent in case nodepath is deleted + nc->nodepath->nodeContext = nc; + } + ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop); + + if (nc->nodepath || ec->shape_knot_holder) { + // setting listener + Inkscape::XML::Node *repr; + if (ec->shape_knot_holder) + repr = ec->shape_knot_holder->repr; + else + repr = SP_OBJECT_REPR(item); + if (repr) { + Inkscape::GC::anchor(repr); + sp_repr_add_listener(repr, &nodepath_repr_events, ec); + sp_repr_synthesize_events(repr, &nodepath_repr_events, ec); + } + } + } + + if (prefs_get_int_attribute("tools.nodes", "selcue", 0) != 0) { + ec->enableSelectionCue(); + } + + if (prefs_get_int_attribute("tools.nodes", "gradientdrag", 0) != 0) { + ec->enableGrDrag(); + } + + nc->_node_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack()); + sp_nodepath_update_statusbar(nc->nodepath); +} + +/** +\brief Callback that processes the "changed" signal on the selection; +destroys old and creates new nodepath and reassigns listeners to the new selected item's repr +*/ +void +sp_node_context_selection_changed(Inkscape::Selection *selection, gpointer data) +{ + SPNodeContext *nc = SP_NODE_CONTEXT(data); + SPEventContext *ec = SP_EVENT_CONTEXT(nc); + + Inkscape::XML::Node *old_repr = NULL; + + if (nc->nodepath) { + old_repr = nc->nodepath->repr; + sp_nodepath_destroy(nc->nodepath); + } + if (ec->shape_knot_holder) { + old_repr = ec->shape_knot_holder->repr; + sp_knot_holder_destroy(ec->shape_knot_holder); + } + + if (old_repr) { // remove old listener + sp_repr_remove_listener_by_data(old_repr, ec); + Inkscape::GC::release(old_repr); + } + + SPItem *item = selection->singleItem(); + + SPDesktop *desktop = selection->desktop(); + nc->nodepath = NULL; + ec->shape_knot_holder = NULL; + if (item) { + nc->nodepath = sp_nodepath_new(desktop, item); + if (nc->nodepath) { + nc->nodepath->nodeContext = nc; + } + ec->shape_knot_holder = sp_item_knot_holder(item, desktop); + + if (nc->nodepath || ec->shape_knot_holder) { + // setting new listener + Inkscape::XML::Node *repr; + if (ec->shape_knot_holder) + repr = ec->shape_knot_holder->repr; + else + repr = SP_OBJECT_REPR(item); + if (repr) { + Inkscape::GC::anchor(repr); + sp_repr_add_listener(repr, &nodepath_repr_events, ec); + sp_repr_synthesize_events(repr, &nodepath_repr_events, ec); + } + } + } + sp_nodepath_update_statusbar(nc->nodepath); +} + +/** +\brief Regenerates nodepath when the item's repr was change outside of node edit +(e.g. by undo, or xml editor, or edited in another view). The item is assumed to be the same +(otherwise sp_node_context_selection_changed() would have been called), so repr and listeners +are not changed. +*/ +void +sp_nodepath_update_from_item(SPNodeContext *nc, SPItem *item) +{ + g_assert(nc); + SPEventContext *ec = ((SPEventContext *) nc); + + SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(SP_EVENT_CONTEXT(nc)); + g_assert(desktop); + + if (nc->nodepath) { + sp_nodepath_destroy(nc->nodepath); + } + + if (ec->shape_knot_holder) { + sp_knot_holder_destroy(ec->shape_knot_holder); + } + + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + item = selection->singleItem(); + + nc->nodepath = NULL; + ec->shape_knot_holder = NULL; + if (item) { + nc->nodepath = sp_nodepath_new(desktop, item); + if (nc->nodepath) { + nc->nodepath->nodeContext = nc; + } + ec->shape_knot_holder = sp_item_knot_holder(item, desktop); + } + sp_nodepath_update_statusbar(nc->nodepath); +} + +/** +\brief Callback that is fired whenever an attribute of the selected item (which we have in the nodepath) changes +*/ +static void +nodepath_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name, + gchar const *old_value, gchar const *new_value, + bool is_interactive, gpointer data) +{ + SPItem *item = NULL; + char const *newd = NULL, *newtypestr = NULL; + gboolean changed = FALSE; + + g_assert(data); + SPNodeContext *nc = ((SPNodeContext *) data); + SPEventContext *ec = ((SPEventContext *) data); + g_assert(nc); + Inkscape::NodePath::Path *np = nc->nodepath; + SPKnotHolder *kh = ec->shape_knot_holder; + + if (np) { + item = SP_ITEM(np->path); + if (!strcmp(name, "d")) { + newd = new_value; + changed = nodepath_repr_d_changed(np, new_value); + } else if (!strcmp(name, "sodipodi:nodetypes")) { + newtypestr = new_value; + changed = nodepath_repr_typestr_changed(np, new_value); + } else { + return; + // With paths, we only need to act if one of the path-affecting attributes has changed. + } + } else if (kh) { + item = SP_ITEM(kh->item); + changed = !(kh->local_change); + kh->local_change = FALSE; + } + if (np && changed) { + GList *saved = NULL; + SPDesktop *desktop = np->desktop; + g_assert(desktop); + Inkscape::Selection *selection = desktop->selection; + g_assert(selection); + + saved = save_nodepath_selection(nc->nodepath); + sp_nodepath_update_from_item(nc, item); + if (nc->nodepath && saved) restore_nodepath_selection(nc->nodepath, saved); + + } else if (kh && changed) { + sp_nodepath_update_from_item(nc, item); + } + + sp_nodepath_update_statusbar(nc->nodepath); +} + +void +sp_node_context_show_modifier_tip(SPEventContext *event_context, GdkEvent *event) +{ + sp_event_show_modifier_tip + (event_context->defaultMessageContext(), event, + _("Ctrl: toggle node type, snap handle angle, move hor/vert; Ctrl+Alt: move along handles"), + _("Shift: toggle node selection, disable snapping, rotate both handles"), + _("Alt: lock handle length; Ctrl+Alt: move along handles")); +} + +bool +sp_node_context_is_over_stroke (SPNodeContext *nc, SPItem *item, NR::Point event_p, bool remember) +{ + SPDesktop *desktop = SP_EVENT_CONTEXT (nc)->desktop; + + //Translate click point into proper coord system + nc->curvepoint_doc = desktop->w2d(event_p); + nc->curvepoint_doc *= sp_item_dt2i_affine(item); + nc->curvepoint_doc *= sp_item_i2doc_affine(item); + + NR::Maybe position = get_nearest_position_on_Path(item, nc->curvepoint_doc); + NR::Point nearest = get_point_on_Path(item, position.assume().piece, position.assume().t); + NR::Point delta = nearest - nc->curvepoint_doc; + + delta = desktop->d2w(delta); + + double stroke_tolerance = + (SP_OBJECT_STYLE (item)->stroke.type != SP_PAINT_TYPE_NONE? + desktop->current_zoom() * + SP_OBJECT_STYLE (item)->stroke_width.computed * + sp_item_i2d_affine (item).expansion() * 0.5 + : 0.0) + + (double) SP_EVENT_CONTEXT(nc)->tolerance; + + bool close = (NR::L2 (delta) < stroke_tolerance); + + if (remember && close) { + nc->curvepoint_event[NR::X] = (gint) event_p [NR::X]; + nc->curvepoint_event[NR::Y] = (gint) event_p [NR::Y]; + nc->hit = true; + nc->grab_t = position.assume().t; + nc->grab_node = sp_nodepath_get_node_by_index(position.assume().piece); + } + + return close; +} + + +static gint +sp_node_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event) +{ + gint ret = FALSE; + + SPDesktop *desktop = event_context->desktop; + Inkscape::Selection *selection = SP_DT_SELECTION (desktop); + + SPNodeContext *nc = SP_NODE_CONTEXT(event_context); + + switch (event->type) { + case GDK_2BUTTON_PRESS: + case GDK_BUTTON_RELEASE: + if (event->button.button == 1) { + if (!nc->drag) { + + // find out clicked item, disregarding groups, honoring Alt + SPItem *item_clicked = sp_event_context_find_item (desktop, + NR::Point(event->button.x, event->button.y), + (event->button.state & GDK_MOD1_MASK) && !(event->button.state & GDK_CONTROL_MASK), TRUE); + // find out if we're over the selected item, disregarding groups + SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(), + NR::Point(event->button.x, event->button.y)); + bool over_stroke = false; + if (item_over && nc->nodepath) { + over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->button.x, event->button.y), false); + } + + if (over_stroke || nc->added_node) { + switch (event->type) { + case GDK_BUTTON_RELEASE: + if (event->button.state & GDK_CONTROL_MASK && event->button.state & GDK_MOD1_MASK) { + //add a node + sp_nodepath_add_node_near_point(item_over, nc->curvepoint_doc); + } else { + if (nc->added_node) { // we just received double click, ignore release + nc->added_node = false; + break; + } + //select the segment + if (event->button.state & GDK_SHIFT_MASK) { + sp_nodepath_select_segment_near_point(item_over, nc->curvepoint_doc, true); + } else { + sp_nodepath_select_segment_near_point(item_over, nc->curvepoint_doc, false); + } + } + break; + case GDK_2BUTTON_PRESS: + //add a node + sp_nodepath_add_node_near_point(item_over, nc->curvepoint_doc); + nc->added_node = true; + break; + default: + break; + } + } else if (event->button.state & GDK_SHIFT_MASK) { + selection->toggle(item_clicked); + } else { + selection->set(item_clicked); + } + + ret = TRUE; + } + break; + } + break; + case GDK_BUTTON_PRESS: + if (event->button.button == 1 && !(event->button.state & GDK_SHIFT_MASK)) { + // save drag origin + event_context->xp = (gint) event->button.x; + event_context->yp = (gint) event->button.y; + event_context->within_tolerance = true; + nc->hit = false; + + if (!nc->drag) { + // find out if we're over the selected item, disregarding groups + SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(), + NR::Point(event->button.x, event->button.y)); + + if (nc->nodepath && selection->single() && item_over) { + + // save drag origin + bool over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->button.x, event->button.y), true); + //only dragging curves + if (over_stroke) { + sp_nodepath_select_segment_near_point(item_over, nc->curvepoint_doc, false); + ret = TRUE; + } else { + break; + } + } else { + break; + } + + ret = TRUE; + } + break; + } + break; + default: + break; + } + + if (!ret) { + if (((SPEventContextClass *) parent_class)->item_handler) + ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event); + } + + return ret; +} + +static gint +sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event) +{ + SPDesktop *desktop = event_context->desktop; + Inkscape::Selection *selection = SP_DT_SELECTION (desktop); + + SPNodeContext *nc = SP_NODE_CONTEXT(event_context); + double const nudge = prefs_get_double_attribute_limited("options.nudgedistance", "value", 2, 0, 1000); // in px + event_context->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100); // read every time, to make prefs changes really live + int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12); + double const offset = prefs_get_double_attribute_limited("options.defaultscale", "value", 2, 0, 1000); + + gint ret = FALSE; + + switch (event->type) { + case GDK_BUTTON_PRESS: + if (event->button.button == 1) { + // save drag origin + event_context->xp = (gint) event->button.x; + event_context->yp = (gint) event->button.y; + event_context->within_tolerance = true; + nc->hit = false; + + NR::Point const button_w(event->button.x, + event->button.y); + NR::Point const button_dt(desktop->w2d(button_w)); + Inkscape::Rubberband::get()->start(desktop, button_dt); + ret = TRUE; + } + break; + case GDK_MOTION_NOTIFY: + if (event->motion.state & GDK_BUTTON1_MASK) { + + if ( event_context->within_tolerance + && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance ) + && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) { + break; // do not drag if we're within tolerance from origin + } + // Once the user has moved farther than tolerance from the original location + // (indicating they intend to move the object, not click), then always process the + // motion notify coordinates as given (no snapping back to origin) + event_context->within_tolerance = false; + + if (nc->nodepath && nc->hit) { + NR::Point const delta_w(event->motion.x - nc->curvepoint_event[NR::X], + event->motion.y - nc->curvepoint_event[NR::Y]); + NR::Point const delta_dt(desktop->w2d(delta_w)); + sp_nodepath_curve_drag (nc->grab_node, nc->grab_t, delta_dt, undo_label); + nc->curvepoint_event[NR::X] = (gint) event->motion.x; + nc->curvepoint_event[NR::Y] = (gint) event->motion.y; + gobble_motion_events(GDK_BUTTON1_MASK); + } else { + NR::Point const motion_w(event->motion.x, + event->motion.y); + NR::Point const motion_dt(desktop->w2d(motion_w)); + Inkscape::Rubberband::get()->move(motion_dt); + } + nc->drag = TRUE; + ret = TRUE; + } else { + if (!nc->nodepath || selection->singleItem() == NULL) { + break; + } + + SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(), + NR::Point(event->motion.x, event->motion.y)); + bool over_stroke = false; + if (item_over && nc->nodepath) { + over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->motion.x, event->motion.y), false); + } + + if (nc->cursor_drag && !over_stroke) { + event_context->cursor_shape = cursor_node_xpm; + event_context->hot_x = 1; + event_context->hot_y = 1; + sp_event_context_update_cursor(event_context); + nc->cursor_drag = false; + } else if (!nc->cursor_drag && over_stroke) { + event_context->cursor_shape = cursor_node_d_xpm; + event_context->hot_x = 1; + event_context->hot_y = 1; + sp_event_context_update_cursor(event_context); + nc->cursor_drag = true; + } + } + break; + case GDK_BUTTON_RELEASE: + event_context->xp = event_context->yp = 0; + if (event->button.button == 1) { + + NR::Maybe b = Inkscape::Rubberband::get()->getRectangle(); + + if (nc->hit && !event_context->within_tolerance) { //drag curve + if (undo_label == undo_label_1) + undo_label = undo_label_2; + else + undo_label = undo_label_1; + } else if (b != NR::Nothing() && !event_context->within_tolerance) { // drag to select + if (nc->nodepath) { + sp_nodepath_select_rect(nc->nodepath, b.assume(), event->button.state & GDK_SHIFT_MASK); + } + } else { + if (!(nc->rb_escaped)) { // unless something was cancelled + if (nc->nodepath && nc->nodepath->selected) + sp_nodepath_deselect(nc->nodepath); + else + SP_DT_SELECTION(desktop)->clear(); + } + } + ret = TRUE; + Inkscape::Rubberband::get()->stop(); + nc->rb_escaped = false; + nc->drag = FALSE; + nc->hit = false; + break; + } + break; + case GDK_KEY_PRESS: + switch (get_group0_keyval(&event->key)) { + case GDK_Insert: + case GDK_KP_Insert: + // with any modifiers + sp_node_selected_add_node(); + ret = TRUE; + break; + case GDK_Delete: + case GDK_KP_Delete: + case GDK_BackSpace: + // with any modifiers + sp_node_selected_delete(); + ret = TRUE; + break; + case GDK_C: + case GDK_c: + if (MOD__SHIFT_ONLY) { + sp_node_selected_set_type(Inkscape::NodePath::NODE_CUSP); + ret = TRUE; + } + break; + case GDK_S: + case GDK_s: + if (MOD__SHIFT_ONLY) { + sp_node_selected_set_type(Inkscape::NodePath::NODE_SMOOTH); + ret = TRUE; + } + break; + case GDK_Y: + case GDK_y: + if (MOD__SHIFT_ONLY) { + sp_node_selected_set_type(Inkscape::NodePath::NODE_SYMM); + ret = TRUE; + } + break; + case GDK_B: + case GDK_b: + if (MOD__SHIFT_ONLY) { + sp_node_selected_break(); + ret = TRUE; + } + break; + case GDK_J: + case GDK_j: + if (MOD__SHIFT_ONLY) { + sp_node_selected_join(); + ret = TRUE; + } + break; + case GDK_D: + case GDK_d: + if (MOD__SHIFT_ONLY) { + sp_node_selected_duplicate(); + ret = TRUE; + } + break; + case GDK_L: + case GDK_l: + if (MOD__SHIFT_ONLY) { + sp_node_selected_set_line_type(NR_LINETO); + ret = TRUE; + } + break; + case GDK_U: + case GDK_u: + if (MOD__SHIFT_ONLY) { + sp_node_selected_set_line_type(NR_CURVETO); + ret = TRUE; + } + break; + case GDK_R: + case GDK_r: + if (MOD__SHIFT_ONLY) { + // FIXME: add top panel button + sp_selected_path_reverse(); + ret = TRUE; + } + break; + case GDK_Left: // move selection left + case GDK_KP_Left: + case GDK_KP_4: + if (!MOD__CTRL) { // not ctrl + if (MOD__ALT) { // alt + if (MOD__SHIFT) sp_node_selected_move_screen(-10, 0); // shift + else sp_node_selected_move_screen(-1, 0); // no shift + } + else { // no alt + if (MOD__SHIFT) sp_node_selected_move(-10*nudge, 0); // shift + else sp_node_selected_move(-nudge, 0); // no shift + } + ret = TRUE; + } + break; + case GDK_Up: // move selection up + case GDK_KP_Up: + case GDK_KP_8: + if (!MOD__CTRL) { // not ctrl + if (MOD__ALT) { // alt + if (MOD__SHIFT) sp_node_selected_move_screen(0, 10); // shift + else sp_node_selected_move_screen(0, 1); // no shift + } + else { // no alt + if (MOD__SHIFT) sp_node_selected_move(0, 10*nudge); // shift + else sp_node_selected_move(0, nudge); // no shift + } + ret = TRUE; + } + break; + case GDK_Right: // move selection right + case GDK_KP_Right: + case GDK_KP_6: + if (!MOD__CTRL) { // not ctrl + if (MOD__ALT) { // alt + if (MOD__SHIFT) sp_node_selected_move_screen(10, 0); // shift + else sp_node_selected_move_screen(1, 0); // no shift + } + else { // no alt + if (MOD__SHIFT) sp_node_selected_move(10*nudge, 0); // shift + else sp_node_selected_move(nudge, 0); // no shift + } + ret = TRUE; + } + break; + case GDK_Down: // move selection down + case GDK_KP_Down: + case GDK_KP_2: + if (!MOD__CTRL) { // not ctrl + if (MOD__ALT) { // alt + if (MOD__SHIFT) sp_node_selected_move_screen(0, -10); // shift + else sp_node_selected_move_screen(0, -1); // no shift + } + else { // no alt + if (MOD__SHIFT) sp_node_selected_move(0, -10*nudge); // shift + else sp_node_selected_move(0, -nudge); // no shift + } + ret = TRUE; + } + break; + case GDK_Tab: // Tab - cycle selection forward + if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) { + sp_nodepath_select_next(nc->nodepath); + ret = TRUE; + } + break; + case GDK_ISO_Left_Tab: // Shift Tab - cycle selection backward + if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) { + sp_nodepath_select_prev(nc->nodepath); + ret = TRUE; + } + break; + case GDK_Escape: + { + NR::Maybe const b = Inkscape::Rubberband::get()->getRectangle(); + if (b != NR::Nothing()) { + Inkscape::Rubberband::get()->stop(); + nc->rb_escaped = true; + } else { + if (nc->nodepath && nc->nodepath->selected) { + sp_nodepath_deselect(nc->nodepath); + } else { + SP_DT_SELECTION(desktop)->clear(); + } + } + ret = TRUE; + break; + } + + case GDK_bracketleft: + if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) { + if (nc->leftctrl) + sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, -1, false); + if (nc->rightctrl) + sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 1, false); + } else if ( MOD__ALT && !MOD__CTRL ) { + if (nc->leftalt && nc->rightalt) + sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 0, true); + else { + if (nc->leftalt) + sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, -1, true); + if (nc->rightalt) + sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 1, true); + } + } else if ( snaps != 0 ) { + sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 0, false); + } + ret = TRUE; + break; + case GDK_bracketright: + if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) { + if (nc->leftctrl) + sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, -1, false); + if (nc->rightctrl) + sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 1, false); + } else if ( MOD__ALT && !MOD__CTRL ) { + if (nc->leftalt && nc->rightalt) + sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 0, true); + else { + if (nc->leftalt) + sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, -1, true); + if (nc->rightalt) + sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 1, true); + } + } else if ( snaps != 0 ) { + sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 0, false); + } + ret = TRUE; + break; + case GDK_less: + case GDK_comma: + if (MOD__CTRL) { + if (nc->leftctrl) + sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, -1); + if (nc->rightctrl) + sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 1); + } else if (MOD__ALT) { + if (nc->leftalt && nc->rightalt) + sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 0); + else { + if (nc->leftalt) + sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, -1); + if (nc->rightalt) + sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 1); + } + } else { + sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 0); + } + ret = TRUE; + break; + case GDK_greater: + case GDK_period: + if (MOD__CTRL) { + if (nc->leftctrl) + sp_nodepath_selected_nodes_scale(nc->nodepath, offset, -1); + if (nc->rightctrl) + sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 1); + } else if (MOD__ALT) { + if (nc->leftalt && nc->rightalt) + sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 0); + else { + if (nc->leftalt) + sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, -1); + if (nc->rightalt) + sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 1); + } + } else { + sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 0); + } + ret = TRUE; + break; + + case GDK_Alt_L: + nc->leftalt = TRUE; + sp_node_context_show_modifier_tip(event_context, event); + break; + case GDK_Alt_R: + nc->rightalt = TRUE; + sp_node_context_show_modifier_tip(event_context, event); + break; + case GDK_Control_L: + nc->leftctrl = TRUE; + sp_node_context_show_modifier_tip(event_context, event); + break; + case GDK_Control_R: + nc->rightctrl = TRUE; + sp_node_context_show_modifier_tip(event_context, event); + break; + case GDK_Shift_L: + case GDK_Shift_R: + case GDK_Meta_L: + case GDK_Meta_R: + sp_node_context_show_modifier_tip(event_context, event); + break; + default: + ret = node_key(event); + break; + } + break; + case GDK_KEY_RELEASE: + switch (get_group0_keyval(&event->key)) { + case GDK_Alt_L: + nc->leftalt = FALSE; + event_context->defaultMessageContext()->clear(); + break; + case GDK_Alt_R: + nc->rightalt = FALSE; + event_context->defaultMessageContext()->clear(); + break; + case GDK_Control_L: + nc->leftctrl = FALSE; + event_context->defaultMessageContext()->clear(); + break; + case GDK_Control_R: + nc->rightctrl = FALSE; + event_context->defaultMessageContext()->clear(); + break; + case GDK_Shift_L: + case GDK_Shift_R: + case GDK_Meta_L: + case GDK_Meta_R: + event_context->defaultMessageContext()->clear(); + break; + } + break; + default: + break; + } + + if (!ret) { + if (((SPEventContextClass *) parent_class)->root_handler) + ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event); + } + + return ret; +} + + +/* + 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 : diff --git a/src/node-context.h b/src/node-context.h new file mode 100644 index 000000000..a9b4beb07 --- /dev/null +++ b/src/node-context.h @@ -0,0 +1,69 @@ +#ifndef __SP_NODE_CONTEXT_H__ +#define __SP_NODE_CONTEXT_H__ + +/* + * Node editing context + * + * Authors: + * Lauris Kaplinski + * bulia byak + * + * This code is in public domain + */ + +#include +#include "event-context.h" +#include "forward.h" +#include "nodepath.h" +struct SPKnotHolder; +namespace Inkscape { class Selection; } + +#define SP_TYPE_NODE_CONTEXT (sp_node_context_get_type ()) +#define SP_NODE_CONTEXT(obj) (GTK_CHECK_CAST ((obj), SP_TYPE_NODE_CONTEXT, SPNodeContext)) +#define SP_NODE_CONTEXT_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), SP_TYPE_NODE_CONTEXT, SPNodeContextClass)) +#define SP_IS_NODE_CONTEXT(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_NODE_CONTEXT)) +#define SP_IS_NODE_CONTEXT_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), SP_TYPE_NODE_CONTEXT)) + +class SPNodeContext; +class SPNodeContextClass; + +struct SPNodeContext { + SPEventContext event_context; + + guint drag : 1; + + Inkscape::NodePath::Path *nodepath; + + gboolean leftalt; + gboolean rightalt; + gboolean leftctrl; + gboolean rightctrl; + + /// If true, rubberband was cancelled by esc, so the next button release should not deselect. + bool rb_escaped; + + sigc::connection sel_changed_connection; + + Inkscape::MessageContext *_node_message_context; + + double grab_t; + Inkscape::NodePath::Node * grab_node; + bool hit; + NR::Point curvepoint_event; // int coords from event + NR::Point curvepoint_doc; // same, in doc coords + bool cursor_drag; + + bool added_node; +}; + +struct SPNodeContextClass { + SPEventContextClass parent_class; +}; + +/* Standard Gtk function */ + +GtkType sp_node_context_get_type (void); + +void sp_node_context_selection_changed (Inkscape::Selection * selection, gpointer data); + +#endif diff --git a/src/nodepath.cpp b/src/nodepath.cpp new file mode 100644 index 000000000..c28ca7b80 --- /dev/null +++ b/src/nodepath.cpp @@ -0,0 +1,3675 @@ +#define __SP_NODEPATH_C__ + +/** \file + * Path handler in node edit mode + * + * Authors: + * Lauris Kaplinski + * bulia byak + * + * This code is in public domain + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include "display/curve.h" +#include "display/sp-ctrlline.h" +#include "display/sodipodi-ctrl.h" +#include +#include "libnr/n-art-bpath.h" +#include "helper/units.h" +#include "knot.h" +#include "inkscape.h" +#include "document.h" +#include "sp-namedview.h" +#include "desktop.h" +#include "desktop-handles.h" +#include "snap.h" +#include "message-stack.h" +#include "message-context.h" +#include "node-context.h" +#include "selection-chemistry.h" +#include "selection.h" +#include "xml/repr.h" +#include "prefs-utils.h" +#include "sp-metrics.h" +#include "sp-path.h" +#include +#include "splivarot.h" +#include "svg/svg.h" + +class NR::Matrix; + +/// \todo +/// evil evil evil. FIXME: conflict of two different Path classes! +/// There is a conflict in the namespace between two classes named Path. +/// #include "sp-flowtext.h" +/// #include "sp-flowregion.h" + +#define SP_TYPE_FLOWREGION (sp_flowregion_get_type ()) +#define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION)) +GType sp_flowregion_get_type (void); +#define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ()) +#define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT)) +GType sp_flowtext_get_type (void); +// end evil workaround + +#include "helper/stlport.h" + + +/// \todo fixme: Implement these via preferences */ + +#define NODE_FILL 0xbfbfbf00 +#define NODE_STROKE 0x000000ff +#define NODE_FILL_HI 0xff000000 +#define NODE_STROKE_HI 0x000000ff +#define NODE_FILL_SEL 0x0000ffff +#define NODE_STROKE_SEL 0x000000ff +#define NODE_FILL_SEL_HI 0xff000000 +#define NODE_STROKE_SEL_HI 0x000000ff +#define KNOT_FILL 0xffffffff +#define KNOT_STROKE 0x000000ff +#define KNOT_FILL_HI 0xff000000 +#define KNOT_STROKE_HI 0x000000ff + +static GMemChunk *nodechunk = NULL; + +/* Creation from object */ + +static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t); +static gchar *parse_nodetypes(gchar const *types, gint length); + +/* Object updating */ + +static void stamp_repr(Inkscape::NodePath::Path *np); +static SPCurve *create_curve(Inkscape::NodePath::Path *np); +static gchar *create_typestr(Inkscape::NodePath::Path *np); + +static void sp_node_ensure_ctrls(Inkscape::NodePath::Node *node); + +static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override); + +static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected); + +/* Control knot placement, if node or other knot is moved */ + +static void sp_node_adjust_knot(Inkscape::NodePath::Node *node, gint which_adjust); +static void sp_node_adjust_knots(Inkscape::NodePath::Node *node); + +/* Knot event handlers */ + +static void node_clicked(SPKnot *knot, guint state, gpointer data); +static void node_grabbed(SPKnot *knot, guint state, gpointer data); +static void node_ungrabbed(SPKnot *knot, guint state, gpointer data); +static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data); +static void node_ctrl_clicked(SPKnot *knot, guint state, gpointer data); +static void node_ctrl_grabbed(SPKnot *knot, guint state, gpointer data); +static void node_ctrl_ungrabbed(SPKnot *knot, guint state, gpointer data); +static gboolean node_ctrl_request(SPKnot *knot, NR::Point *p, guint state, gpointer data); +static void node_ctrl_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data); + +/* Constructors and destructors */ + +static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath); +static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath); +static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp); +static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n); +static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code, + NR::Point *ppos, NR::Point *pos, NR::Point *npos); +static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node); + +/* Helpers */ + +static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which); +static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me); +static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me); + +// active_node indicates mouseover node +static Inkscape::NodePath::Node *active_node = NULL; + +/** + * \brief Creates new nodepath from item + */ +Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPItem *item) +{ + Inkscape::XML::Node *repr = SP_OBJECT(item)->repr; + + /** \todo + * FIXME: remove this. We don't want to edit paths inside flowtext. + * Instead we will build our flowtext with cloned paths, so that the + * real paths are outside the flowtext and thus editable as usual. + */ + if (SP_IS_FLOWTEXT(item)) { + for (SPObject *child = sp_object_first_child(SP_OBJECT(item)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) { + if SP_IS_FLOWREGION(child) { + SPObject *grandchild = sp_object_first_child(SP_OBJECT(child)); + if (grandchild && SP_IS_PATH(grandchild)) { + item = SP_ITEM(grandchild); + break; + } + } + } + } + + if (!SP_IS_PATH(item)) + return NULL; + SPPath *path = SP_PATH(item); + SPCurve *curve = sp_shape_get_curve(SP_SHAPE(path)); + if (curve == NULL) + return NULL; + + NArtBpath *bpath = sp_curve_first_bpath(curve); + gint length = curve->end; + if (length == 0) + return NULL; // prevent crash for one-node paths + + gchar const *nodetypes = repr->attribute("sodipodi:nodetypes"); + gchar *typestr = parse_nodetypes(nodetypes, length); + + //Create new nodepath + Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1); + if (!np) + return NULL; + + // Set defaults + np->desktop = desktop; + np->path = path; + np->subpaths = NULL; + np->selected = NULL; + np->nodeContext = NULL; //Let the context that makes this set it + + // we need to update item's transform from the repr here, + // because they may be out of sync when we respond + // to a change in repr by regenerating nodepath --bb + sp_object_read_attr(SP_OBJECT(item), "transform"); + + np->i2d = sp_item_i2d_affine(SP_ITEM(path)); + np->d2i = np->i2d.inverse(); + np->repr = repr; + + /* Now the bitchy part (lauris) */ + + NArtBpath *b = bpath; + + while (b->code != NR_END) { + b = subpath_from_bpath(np, b, typestr + (b - bpath)); + } + + g_free(typestr); + sp_curve_unref(curve); + + return np; +} + +/** + * Destroys nodepath's subpaths, then itself, also tell context about it. + */ +void sp_nodepath_destroy(Inkscape::NodePath::Path *np) { + + if (!np) //soft fail, like delete + return; + + while (np->subpaths) { + sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data); + } + + //Inform the context that made me, if any, that I am gone. + if (np->nodeContext) + np->nodeContext->nodepath = NULL; + + g_assert(!np->selected); + + np->desktop = NULL; + + g_free(np); +} + + +/** + * Return the node count of a given NodeSubPath. + */ +static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath) +{ + if (!subpath) + return 0; + gint nodeCount = g_list_length(subpath->nodes); + return nodeCount; +} + +/** + * Return the node count of a given NodePath. + */ +static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np) +{ + if (!np) + return 0; + gint nodeCount = 0; + for (GList *item = np->subpaths ; item ; item=item->next) { + Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data; + nodeCount += g_list_length(subpath->nodes); + } + return nodeCount; +} + + +/** + * Clean up a nodepath after editing. + * + * Currently we are deleting trivial subpaths. + */ +static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath) +{ + GList *badSubPaths = NULL; + + //Check all subpaths to be >=2 nodes + for (GList *l = nodepath->subpaths; l ; l=l->next) { + Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data; + if (sp_nodepath_subpath_get_node_count(sp)<2) + badSubPaths = g_list_append(badSubPaths, sp); + } + + //Delete them. This second step is because sp_nodepath_subpath_destroy() + //also removes the subpath from nodepath->subpaths + for (GList *l = badSubPaths; l ; l=l->next) { + Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data; + sp_nodepath_subpath_destroy(sp); + } + + g_list_free(badSubPaths); +} + + + +/** + * \brief Returns true if the argument nodepath and the d attribute in + * its repr do not match. + * + * This may happen if repr was changed in, e.g., XML editor or by undo. + * + * \todo + * UGLY HACK, think how we can eliminate it. + */ +gboolean nodepath_repr_d_changed(Inkscape::NodePath::Path *np, char const *newd) +{ + g_assert(np); + + SPCurve *curve = create_curve(np); + + gchar *svgpath = sp_svg_write_path(curve->bpath); + + char const *attr_d = ( newd + ? newd + : SP_OBJECT(np->path)->repr->attribute("d") ); + + gboolean ret; + if (attr_d && svgpath) + ret = strcmp(attr_d, svgpath); + else + ret = TRUE; + + g_free(svgpath); + sp_curve_unref(curve); + + return ret; +} + +/** + * \brief Returns true if the argument nodepath and the sodipodi:nodetypes + * attribute in its repr do not match. + * + * This may happen if repr was changed in, e.g., the XML editor or by undo. + */ +gboolean nodepath_repr_typestr_changed(Inkscape::NodePath::Path *np, char const *newtypestr) +{ + g_assert(np); + gchar *typestr = create_typestr(np); + char const *attr_typestr = ( newtypestr + ? newtypestr + : SP_OBJECT(np->path)->repr->attribute("sodipodi:nodetypes") ); + gboolean const ret = (attr_typestr && strcmp(attr_typestr, typestr)); + + g_free(typestr); + + return ret; +} + +/** + * Create new nodepath from b, make it subpath of np. + * \param t The node type. + * \todo Fixme: t should be a proper type, rather than gchar + */ +static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t) +{ + NR::Point ppos, pos, npos; + + g_assert((b->code == NR_MOVETO) || (b->code == NR_MOVETO_OPEN)); + + Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np); + bool const closed = (b->code == NR_MOVETO); + + pos = NR::Point(b->x3, b->y3) * np->i2d; + if (b[1].code == NR_CURVETO) { + npos = NR::Point(b[1].x1, b[1].y1) * np->i2d; + } else { + npos = pos; + } + Inkscape::NodePath::Node *n; + n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType) *t, NR_MOVETO, &pos, &pos, &npos); + g_assert(sp->first == n); + g_assert(sp->last == n); + + b++; + t++; + while ((b->code == NR_CURVETO) || (b->code == NR_LINETO)) { + pos = NR::Point(b->x3, b->y3) * np->i2d; + if (b->code == NR_CURVETO) { + ppos = NR::Point(b->x2, b->y2) * np->i2d; + } else { + ppos = pos; + } + if (b[1].code == NR_CURVETO) { + npos = NR::Point(b[1].x1, b[1].y1) * np->i2d; + } else { + npos = pos; + } + n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType)*t, b->code, &ppos, &pos, &npos); + b++; + t++; + } + + if (closed) sp_nodepath_subpath_close(sp); + + return b; +} + +/** + * Convert from sodipodi:nodetypes to new style type string. + */ +static gchar *parse_nodetypes(gchar const *types, gint length) +{ + g_assert(length > 0); + + gchar *typestr = g_new(gchar, length + 1); + + gint pos = 0; + + if (types) { + for (gint i = 0; types[i] && ( i < length ); i++) { + while ((types[i] > '\0') && (types[i] <= ' ')) i++; + if (types[i] != '\0') { + switch (types[i]) { + case 's': + typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH; + break; + case 'z': + typestr[pos++] =Inkscape::NodePath::NODE_SYMM; + break; + case 'c': + typestr[pos++] =Inkscape::NodePath::NODE_CUSP; + break; + default: + typestr[pos++] =Inkscape::NodePath::NODE_NONE; + break; + } + } + } + } + + while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE; + + return typestr; +} + +/** + * Make curve out of path and associate it with it. + */ +static void update_object(Inkscape::NodePath::Path *np) +{ + g_assert(np); + + SPCurve *curve = create_curve(np); + + sp_shape_set_curve(SP_SHAPE(np->path), curve, TRUE); + + sp_curve_unref(curve); +} + +/** + * Update XML path node with data from path object. + */ +static void update_repr_internal(Inkscape::NodePath::Path *np) +{ + g_assert(np); + + Inkscape::XML::Node *repr = SP_OBJECT(np->path)->repr; + + SPCurve *curve = create_curve(np); + gchar *typestr = create_typestr(np); + gchar *svgpath = sp_svg_write_path(curve->bpath); + + repr->setAttribute("d", svgpath); + repr->setAttribute("sodipodi:nodetypes", typestr); + + g_free(svgpath); + g_free(typestr); + sp_curve_unref(curve); +} + +/** + * Update XML path node with data from path object, commit changes forever. + */ +static void update_repr(Inkscape::NodePath::Path *np) +{ + update_repr_internal(np); + sp_document_done(SP_DT_DOCUMENT(np->desktop)); +} + +/** + * Update XML path node with data from path object, commit changes with undo. + */ +static void update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key) +{ + update_repr_internal(np); + sp_document_maybe_done(SP_DT_DOCUMENT(np->desktop), key); +} + +/** + * Make duplicate of path, replace corresponding XML node in tree, commit. + */ +static void stamp_repr(Inkscape::NodePath::Path *np) +{ + g_assert(np); + + Inkscape::XML::Node *old_repr = SP_OBJECT(np->path)->repr; + Inkscape::XML::Node *new_repr = old_repr->duplicate(); + + // remember the position of the item + gint pos = old_repr->position(); + // remember parent + Inkscape::XML::Node *parent = sp_repr_parent(old_repr); + + SPCurve *curve = create_curve(np); + gchar *typestr = create_typestr(np); + + gchar *svgpath = sp_svg_write_path(curve->bpath); + + new_repr->setAttribute("d", svgpath); + new_repr->setAttribute("sodipodi:nodetypes", typestr); + + // add the new repr to the parent + parent->appendChild(new_repr); + // move to the saved position + new_repr->setPosition(pos > 0 ? pos : 0); + + sp_document_done(SP_DT_DOCUMENT(np->desktop)); + + Inkscape::GC::release(new_repr); + g_free(svgpath); + g_free(typestr); + sp_curve_unref(curve); +} + +/** + * Create curve from path. + */ +static SPCurve *create_curve(Inkscape::NodePath::Path *np) +{ + SPCurve *curve = sp_curve_new(); + + for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) { + Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data; + sp_curve_moveto(curve, + sp->first->pos * np->d2i); + Inkscape::NodePath::Node *n = sp->first->n.other; + while (n) { + NR::Point const end_pt = n->pos * np->d2i; + switch (n->code) { + case NR_LINETO: + sp_curve_lineto(curve, end_pt); + break; + case NR_CURVETO: + sp_curve_curveto(curve, + n->p.other->n.pos * np->d2i, + n->p.pos * np->d2i, + end_pt); + break; + default: + g_assert_not_reached(); + break; + } + if (n != sp->last) { + n = n->n.other; + } else { + n = NULL; + } + } + if (sp->closed) { + sp_curve_closepath(curve); + } + } + + return curve; +} + +/** + * Convert path type string to sodipodi:nodetypes style. + */ +static gchar *create_typestr(Inkscape::NodePath::Path *np) +{ + gchar *typestr = g_new(gchar, 32); + gint len = 32; + gint pos = 0; + + for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) { + Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data; + + if (pos >= len) { + typestr = g_renew(gchar, typestr, len + 32); + len += 32; + } + + typestr[pos++] = 'c'; + + Inkscape::NodePath::Node *n; + n = sp->first->n.other; + while (n) { + gchar code; + + switch (n->type) { + case Inkscape::NodePath::NODE_CUSP: + code = 'c'; + break; + case Inkscape::NodePath::NODE_SMOOTH: + code = 's'; + break; + case Inkscape::NodePath::NODE_SYMM: + code = 'z'; + break; + default: + g_assert_not_reached(); + code = '\0'; + break; + } + + if (pos >= len) { + typestr = g_renew(gchar, typestr, len + 32); + len += 32; + } + + typestr[pos++] = code; + + if (n != sp->last) { + n = n->n.other; + } else { + n = NULL; + } + } + } + + if (pos >= len) { + typestr = g_renew(gchar, typestr, len + 1); + len += 1; + } + + typestr[pos++] = '\0'; + + return typestr; +} + +/** + * Returns current path in context. + */ +static Inkscape::NodePath::Path *sp_nodepath_current() +{ + if (!SP_ACTIVE_DESKTOP) { + return NULL; + } + + SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context; + + if (!SP_IS_NODE_CONTEXT(event_context)) { + return NULL; + } + + return SP_NODE_CONTEXT(event_context)->nodepath; +} + + + +/** + \brief Fills node and control positions for three nodes, splitting line + marked by end at distance t. + */ +static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t) +{ + g_assert(new_path != NULL); + g_assert(end != NULL); + + g_assert(end->p.other == new_path); + Inkscape::NodePath::Node *start = new_path->p.other; + g_assert(start); + + if (end->code == NR_LINETO) { + new_path->type =Inkscape::NodePath::NODE_CUSP; + new_path->code = NR_LINETO; + new_path->pos = (t * start->pos + (1 - t) * end->pos); + } else { + new_path->type =Inkscape::NodePath::NODE_SMOOTH; + new_path->code = NR_CURVETO; + gdouble s = 1 - t; + for (int dim = 0; dim < 2; dim++) { + NR::Coord const f000 = start->pos[dim]; + NR::Coord const f001 = start->n.pos[dim]; + NR::Coord const f011 = end->p.pos[dim]; + NR::Coord const f111 = end->pos[dim]; + NR::Coord const f00t = s * f000 + t * f001; + NR::Coord const f01t = s * f001 + t * f011; + NR::Coord const f11t = s * f011 + t * f111; + NR::Coord const f0tt = s * f00t + t * f01t; + NR::Coord const f1tt = s * f01t + t * f11t; + NR::Coord const fttt = s * f0tt + t * f1tt; + start->n.pos[dim] = f00t; + new_path->p.pos[dim] = f0tt; + new_path->pos[dim] = fttt; + new_path->n.pos[dim] = f1tt; + end->p.pos[dim] = f11t; + } + } +} + +/** + * Adds new node on direct line between two nodes, activates handles of all + * three nodes. + */ +static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t) +{ + g_assert(end); + g_assert(end->subpath); + g_assert(g_list_find(end->subpath->nodes, end)); + + Inkscape::NodePath::Node *start = end->p.other; + g_assert( start->n.other == end ); + Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath, + end, + Inkscape::NodePath::NODE_SMOOTH, + (NRPathcode)end->code, + &start->pos, &start->pos, &start->n.pos); + sp_nodepath_line_midpoint(newnode, end, t); + + sp_node_ensure_ctrls(start); + sp_node_ensure_ctrls(newnode); + sp_node_ensure_ctrls(end); + + return newnode; +} + +/** +\brief Break the path at the node: duplicate the argument node, start a new subpath with the duplicate, and copy all nodes after the argument node to it +*/ +static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node) +{ + g_assert(node); + g_assert(node->subpath); + g_assert(g_list_find(node->subpath->nodes, node)); + + Inkscape::NodePath::SubPath *sp = node->subpath; + Inkscape::NodePath::Path *np = sp->nodepath; + + if (sp->closed) { + sp_nodepath_subpath_open(sp, node); + return sp->first; + } else { + // no break for end nodes + if (node == sp->first) return NULL; + if (node == sp->last ) return NULL; + + // create a new subpath + Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np); + + // duplicate the break node as start of the new subpath + Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos); + + while (node->n.other) { // copy the remaining nodes into the new subpath + Inkscape::NodePath::Node *n = node->n.other; + Inkscape::NodePath::Node *nn = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos); + if (n->selected) { + sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection + } + sp_nodepath_node_destroy(n); // remove the point on the original subpath + } + + return newnode; + } +} + +/** + * Duplicate node and connect to neighbours. + */ +static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node) +{ + g_assert(node); + g_assert(node->subpath); + g_assert(g_list_find(node->subpath->nodes, node)); + + Inkscape::NodePath::SubPath *sp = node->subpath; + + NRPathcode code = (NRPathcode) node->code; + if (code == NR_MOVETO) { // if node is the endnode, + node->code = NR_LINETO; // new one is inserted before it, so change that to line + } + + Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos); + + if (!node->n.other || !node->p.other) // if node is an endnode, select it + return node; + else + return newnode; // otherwise select the newly created node +} + +static void sp_node_control_mirror_n_to_p(Inkscape::NodePath::Node *node) +{ + node->p.pos = (node->pos + (node->pos - node->n.pos)); +} + +static void sp_node_control_mirror_p_to_n(Inkscape::NodePath::Node *node) +{ + node->n.pos = (node->pos + (node->pos - node->p.pos)); +} + +/** + * Change line type at node, with side effects on neighbours. + */ +static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code) +{ + g_assert(end); + g_assert(end->subpath); + g_assert(end->p.other); + + if (end->code == static_cast< guint > ( code ) ) + return; + + Inkscape::NodePath::Node *start = end->p.other; + + end->code = code; + + if (code == NR_LINETO) { + if (start->code == NR_LINETO) start->type =Inkscape::NodePath::NODE_CUSP; + if (end->n.other) { + if (end->n.other->code == NR_LINETO) end->type =Inkscape::NodePath::NODE_CUSP; + } + sp_node_adjust_knot(start, -1); + sp_node_adjust_knot(end, 1); + } else { + NR::Point delta = end->pos - start->pos; + start->n.pos = start->pos + delta / 3; + end->p.pos = end->pos - delta / 3; + sp_node_adjust_knot(start, 1); + sp_node_adjust_knot(end, -1); + } + + sp_node_ensure_ctrls(start); + sp_node_ensure_ctrls(end); +} + +/** + * Change node type, and its handles accordingly. + */ +static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeType type) +{ + g_assert(node); + g_assert(node->subpath); + + if (type == static_cast(static_cast< guint >(node->type) ) ) + return node; + + if ((node->p.other != NULL) && (node->n.other != NULL)) { + if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) { + type =Inkscape::NodePath::NODE_CUSP; + } + } + + node->type = type; + + if (node->type == Inkscape::NodePath::NODE_CUSP) { + g_object_set(G_OBJECT(node->knot), "shape", SP_KNOT_SHAPE_DIAMOND, "size", 9, NULL); + } else { + g_object_set(G_OBJECT(node->knot), "shape", SP_KNOT_SHAPE_SQUARE, "size", 7, NULL); + } + + sp_node_adjust_knots(node); + + sp_nodepath_update_statusbar(node->subpath->nodepath); + + return node; +} + +/** + * Same as sp_nodepath_set_node_type(), but also converts, if necessary, + * adjacent segments from lines to curves. +*/ +void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type) +{ + if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) { + if ((node->p.other != NULL) && (node->code == NR_LINETO || node->pos == node->p.pos)) { + // convert adjacent segment BEFORE to curve + node->code = NR_CURVETO; + NR::Point delta; + if (node->n.other != NULL) + delta = node->n.other->pos - node->p.other->pos; + else + delta = node->pos - node->p.other->pos; + node->p.pos = node->pos - delta / 4; + sp_node_ensure_ctrls(node); + } + + if ((node->n.other != NULL) && (node->n.other->code == NR_LINETO || node->pos == node->n.pos)) { + // convert adjacent segment AFTER to curve + node->n.other->code = NR_CURVETO; + NR::Point delta; + if (node->p.other != NULL) + delta = node->p.other->pos - node->n.other->pos; + else + delta = node->pos - node->n.other->pos; + node->n.pos = node->pos - delta / 4; + sp_node_ensure_ctrls(node); + } + } + + sp_nodepath_set_node_type (node, type); +} + +/** + * Move node to point, and adjust its and neighbouring handles. + */ +void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p) +{ + NR::Point delta = p - node->pos; + node->pos = p; + + node->p.pos += delta; + node->n.pos += delta; + + if (node->p.other) { + if (node->code == NR_LINETO) { + sp_node_adjust_knot(node, 1); + sp_node_adjust_knot(node->p.other, -1); + } + } + if (node->n.other) { + if (node->n.other->code == NR_LINETO) { + sp_node_adjust_knot(node, -1); + sp_node_adjust_knot(node->n.other, 1); + } + } + + sp_node_ensure_ctrls(node); +} + +/** + * Call sp_node_moveto() for node selection and handle possible snapping. + */ +static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy, + bool const snap = true) +{ + NR::Coord best[2] = { NR_HUGE, NR_HUGE }; + NR::Point delta(dx, dy); + NR::Point best_pt = delta; + + if (snap) { + for (GList *l = nodepath->selected; l != NULL; l = l->next) { + Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data; + NR::Point p = n->pos + delta; + for (int dim = 0; dim < 2; dim++) { + NR::Coord dist = namedview_dim_snap(nodepath->desktop->namedview, + Inkscape::Snapper::SNAP_POINT, p, + NR::Dim2(dim), nodepath->path); + if (dist < best[dim]) { + best[dim] = dist; + best_pt[dim] = p[dim] - n->pos[dim]; + } + } + } + } + + for (GList *l = nodepath->selected; l != NULL; l = l->next) { + Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data; + sp_node_moveto(n, n->pos + best_pt); + } + + update_object(nodepath); +} + +/** + * Move node selection to point, adjust its and neighbouring handles, + * handle possible snapping, and commit the change with possible undo. + */ +void +sp_node_selected_move(gdouble dx, gdouble dy) +{ + Inkscape::NodePath::Path *nodepath = sp_nodepath_current(); + if (!nodepath) return; + + sp_nodepath_selected_nodes_move(nodepath, dx, dy, false); + + if (dx == 0) { + update_repr_keyed(nodepath, "node:move:vertical"); + } else if (dy == 0) { + update_repr_keyed(nodepath, "node:move:horizontal"); + } else { + update_repr(nodepath); + } +} + +/** + * Move node selection off screen and commit the change. + */ +void +sp_node_selected_move_screen(gdouble dx, gdouble dy) +{ + // borrowed from sp_selection_move_screen in selection-chemistry.c + // we find out the current zoom factor and divide deltas by it + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + + gdouble zoom = desktop->current_zoom(); + gdouble zdx = dx / zoom; + gdouble zdy = dy / zoom; + + Inkscape::NodePath::Path *nodepath = sp_nodepath_current(); + if (!nodepath) return; + + sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false); + + if (dx == 0) { + update_repr_keyed(nodepath, "node:move:vertical"); + } else if (dy == 0) { + update_repr_keyed(nodepath, "node:move:horizontal"); + } else { + update_repr(nodepath); + } +} + +/** + * Ensure knot on side of node is visible/invisible. + */ +static void sp_node_ensure_knot(Inkscape::NodePath::Node *node, gint which, gboolean show_knot) +{ + g_assert(node != NULL); + + Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which); + NRPathcode code = sp_node_path_code_from_side(node, side); + + show_knot = show_knot && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6); + + if (show_knot) { + if (!SP_KNOT_IS_VISIBLE(side->knot)) { + sp_knot_show(side->knot); + } + + sp_knot_set_position(side->knot, &side->pos, 0); + sp_canvas_item_show(side->line); + + } else { + if (SP_KNOT_IS_VISIBLE(side->knot)) { + sp_knot_hide(side->knot); + } + sp_canvas_item_hide(side->line); + } +} + +/** + * Ensure handles on node and neighbours of node are visible if selected. + */ +static void sp_node_ensure_ctrls(Inkscape::NodePath::Node *node) +{ + g_assert(node != NULL); + + if (!SP_KNOT_IS_VISIBLE(node->knot)) { + sp_knot_show(node->knot); + } + + sp_knot_set_position(node->knot, &node->pos, 0); + + gboolean show_knots = node->selected; + if (node->p.other != NULL) { + if (node->p.other->selected) show_knots = TRUE; + } + if (node->n.other != NULL) { + if (node->n.other->selected) show_knots = TRUE; + } + + sp_node_ensure_knot(node, -1, show_knots); + sp_node_ensure_knot(node, 1, show_knots); +} + +/** + * Call sp_node_ensure_ctrls() for all nodes on subpath. + */ +static void sp_nodepath_subpath_ensure_ctrls(Inkscape::NodePath::SubPath *subpath) +{ + g_assert(subpath != NULL); + + for (GList *l = subpath->nodes; l != NULL; l = l->next) { + sp_node_ensure_ctrls((Inkscape::NodePath::Node *) l->data); + } +} + +/** + * Call sp_nodepath_subpath_ensure_ctrls() for all subpaths of nodepath. + */ +static void sp_nodepath_ensure_ctrls(Inkscape::NodePath::Path *nodepath) +{ + g_assert(nodepath != NULL); + + for (GList *l = nodepath->subpaths; l != NULL; l = l->next) { + sp_nodepath_subpath_ensure_ctrls((Inkscape::NodePath::SubPath *) l->data); + } +} + +/** + * Adds all selected nodes in nodepath to list. + */ +void Inkscape::NodePath::Path::selection(std::list &l) +{ + StlConv::list(l, selected); +/// \todo this adds a copying, rework when the selection becomes a stl list +} + +/** + * Align selected nodes on the specified axis. + */ +void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis) +{ + if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected + return; + } + + if ( !nodepath->selected->next ) { // only one node selected + return; + } + Inkscape::NodePath::Node *pNode = reinterpret_cast(nodepath->selected->data); + NR::Point dest(pNode->pos); + for (GList *l = nodepath->selected; l != NULL; l = l->next) { + pNode = reinterpret_cast(l->data); + if (pNode) { + dest[axis] = pNode->pos[axis]; + sp_node_moveto(pNode, dest); + } + } + if (axis == NR::X) { + update_repr_keyed(nodepath, "node:move:vertical"); + } else { + update_repr_keyed(nodepath, "node:move:horizontal"); + } +} + +/// Helper struct. +struct NodeSort +{ + Inkscape::NodePath::Node *_node; + NR::Coord _coord; + /// \todo use vectorof pointers instead of calling copy ctor + NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) : + _node(node), _coord(node->pos[axis]) + {} + +}; + +static bool operator<(NodeSort const &a, NodeSort const &b) +{ + return (a._coord < b._coord); +} + +/** + * Distribute selected nodes on the specified axis. + */ +void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis) +{ + if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected + return; + } + + if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected + return; + } + + Inkscape::NodePath::Node *pNode = reinterpret_cast(nodepath->selected->data); + std::vector sorted; + for (GList *l = nodepath->selected; l != NULL; l = l->next) { + pNode = reinterpret_cast(l->data); + if (pNode) { + NodeSort n(pNode, axis); + sorted.push_back(n); + //dest[axis] = pNode->pos[axis]; + //sp_node_moveto(pNode, dest); + } + } + std::sort(sorted.begin(), sorted.end()); + unsigned int len = sorted.size(); + //overall bboxes span + float dist = (sorted.back()._coord - + sorted.front()._coord); + //new distance between each bbox + float step = (dist) / (len - 1); + float pos = sorted.front()._coord; + for ( std::vector ::iterator it(sorted.begin()); + it < sorted.end(); + it ++ ) + { + NR::Point dest((*it)._node->pos); + dest[axis] = pos; + sp_node_moveto((*it)._node, dest); + pos += step; + } + + if (axis == NR::X) { + update_repr_keyed(nodepath, "node:move:horizontal"); + } else { + update_repr_keyed(nodepath, "node:move:vertical"); + } +} + + +/** + * Call sp_nodepath_line_add_node() for all selected segments. + */ +void +sp_node_selected_add_node(void) +{ + Inkscape::NodePath::Path *nodepath = sp_nodepath_current(); + if (!nodepath) { + return; + } + + GList *nl = NULL; + + for (GList *l = nodepath->selected; l != NULL; l = l->next) { + Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data; + g_assert(t->selected); + if (t->p.other && t->p.other->selected) { + nl = g_list_prepend(nl, t); + } + } + + while (nl) { + Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data; + Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5); + sp_nodepath_node_select(n, TRUE, FALSE); + nl = g_list_remove(nl, t); + } + + /** \todo fixme: adjust ? */ + sp_nodepath_ensure_ctrls(nodepath); + + update_repr(nodepath); + + sp_nodepath_update_statusbar(nodepath); +} + +/** + * Select segment nearest to point + */ +void +sp_nodepath_select_segment_near_point(SPItem * item, NR::Point p, bool toggle) +{ + Inkscape::NodePath::Path *nodepath = sp_nodepath_current(); + if (!nodepath) { + return; + } + + Path::cut_position position = get_nearest_position_on_Path(item, p); + + //find segment to segment + Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece); + + gboolean force = FALSE; + if (!(e->selected && e->p.other->selected)) { + force = TRUE; + } + sp_nodepath_node_select(e, (gboolean) toggle, force); + sp_nodepath_node_select(e->p.other, TRUE, force); + + sp_nodepath_ensure_ctrls(nodepath); + + sp_nodepath_update_statusbar(nodepath); +} + +/** + * Add a node nearest to point + */ +void +sp_nodepath_add_node_near_point(SPItem * item, NR::Point p) +{ + Inkscape::NodePath::Path *nodepath = sp_nodepath_current(); + if (!nodepath) { + return; + } + + Path::cut_position position = get_nearest_position_on_Path(item, p); + + //find segment to split + Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece); + + //don't know why but t seems to flip for lines + if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) { + position.t = 1.0 - position.t; + } + Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t); + sp_nodepath_node_select(n, FALSE, TRUE); + + /* fixme: adjust ? */ + sp_nodepath_ensure_ctrls(nodepath); + + update_repr(nodepath); + + sp_nodepath_update_statusbar(nodepath); +} + +/* + * Adjusts a segment so that t moves by a certain delta for dragging + * converts lines to curves + * + * method and idea borrowed from Simon Budig and the GIMP + * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative() + */ +void +sp_nodepath_curve_drag(Inkscape::NodePath::Node * e, double t, NR::Point delta, char * key) +{ + /* feel good is an arbitrary parameter that distributes the delta between handles + * if t of the drag point is less than 1/6 distance form the endpoint only + * the corresponding hadle is adjusted. This matches the behavior in GIMP + */ + double feel_good; + if (t <= 1.0 / 6.0) + feel_good = 0; + else if (t <= 0.5) + feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2; + else if (t <= 5.0 / 6.0) + feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5; + else + feel_good = 1; + + //if we're dragging a line convert it to a curve + if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) { + sp_nodepath_set_line_type(e, NR_CURVETO); + } + + NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta; + NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta; + e->p.other->n.pos += offsetcoord0; + e->p.pos += offsetcoord1; + + // adjust controls of adjacent segments where necessary + sp_node_adjust_knot(e,1); + sp_node_adjust_knot(e->p.other,-1); + + sp_nodepath_ensure_ctrls(e->subpath->nodepath); + + update_repr_keyed(e->subpath->nodepath, key); + + sp_nodepath_update_statusbar(e->subpath->nodepath); +} + + +/** + * Call sp_nodepath_break() for all selected segments. + */ +void sp_node_selected_break() +{ + Inkscape::NodePath::Path *nodepath = sp_nodepath_current(); + if (!nodepath) return; + + GList *temp = NULL; + for (GList *l = nodepath->selected; l != NULL; l = l->next) { + Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data; + Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n); + if (nn == NULL) continue; // no break, no new node + temp = g_list_prepend(temp, nn); + } + + if (temp) { + sp_nodepath_deselect(nodepath); + } + for (GList *l = temp; l != NULL; l = l->next) { + sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE); + } + + sp_nodepath_ensure_ctrls(nodepath); + + update_repr(nodepath); +} + +/** + * Duplicate the selected node(s). + */ +void sp_node_selected_duplicate() +{ + Inkscape::NodePath::Path *nodepath = sp_nodepath_current(); + if (!nodepath) { + return; + } + + GList *temp = NULL; + for (GList *l = nodepath->selected; l != NULL; l = l->next) { + Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data; + Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n); + if (nn == NULL) continue; // could not duplicate + temp = g_list_prepend(temp, nn); + } + + if (temp) { + sp_nodepath_deselect(nodepath); + } + for (GList *l = temp; l != NULL; l = l->next) { + sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE); + } + + sp_nodepath_ensure_ctrls(nodepath); + + update_repr(nodepath); +} + +/** + * Join two nodes by merging them into one. + */ +void sp_node_selected_join() +{ + Inkscape::NodePath::Path *nodepath = sp_nodepath_current(); + if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses + + if (g_list_length(nodepath->selected) != 2) { + nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have two endnodes selected.")); + return; + } + + Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data; + Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data; + + g_assert(a != b); + g_assert(a->p.other || a->n.other); + g_assert(b->p.other || b->n.other); + + if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) { + nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have two endnodes selected.")); + return; + } + + /* a and b are endpoints */ + + NR::Point c = (a->pos + b->pos) / 2; + + if (a->subpath == b->subpath) { + Inkscape::NodePath::SubPath *sp = a->subpath; + sp_nodepath_subpath_close(sp); + + sp_nodepath_ensure_ctrls(sp->nodepath); + + update_repr(nodepath); + + return; + } + + /* a and b are separate subpaths */ + Inkscape::NodePath::SubPath *sa = a->subpath; + Inkscape::NodePath::SubPath *sb = b->subpath; + NR::Point p; + Inkscape::NodePath::Node *n; + NRPathcode code; + if (a == sa->first) { + p = sa->first->n.pos; + code = (NRPathcode)sa->first->n.other->code; + Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath); + n = sa->last; + sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos); + n = n->p.other; + while (n) { + sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos); + n = n->p.other; + if (n == sa->first) n = NULL; + } + sp_nodepath_subpath_destroy(sa); + sa = t; + } else if (a == sa->last) { + p = sa->last->p.pos; + code = (NRPathcode)sa->last->code; + sp_nodepath_node_destroy(sa->last); + } else { + code = NR_END; + g_assert_not_reached(); + } + + if (b == sb->first) { + sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos); + for (n = sb->first->n.other; n != NULL; n = n->n.other) { + sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos); + } + } else if (b == sb->last) { + sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos); + for (n = sb->last->p.other; n != NULL; n = n->p.other) { + sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos); + } + } else { + g_assert_not_reached(); + } + /* and now destroy sb */ + + sp_nodepath_subpath_destroy(sb); + + sp_nodepath_ensure_ctrls(sa->nodepath); + + update_repr(nodepath); + + sp_nodepath_update_statusbar(nodepath); +} + +/** + * Join two nodes by adding a segment between them. + */ +void sp_node_selected_join_segment() +{ + Inkscape::NodePath::Path *nodepath = sp_nodepath_current(); + if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses + + if (g_list_length(nodepath->selected) != 2) { + nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have two endnodes selected.")); + return; + } + + Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data; + Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data; + + g_assert(a != b); + g_assert(a->p.other || a->n.other); + g_assert(b->p.other || b->n.other); + + if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) { + nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have two endnodes selected.")); + return; + } + + if (a->subpath == b->subpath) { + Inkscape::NodePath::SubPath *sp = a->subpath; + + /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/ + sp->closed = TRUE; + + sp->first->p.other = sp->last; + sp->last->n.other = sp->first; + + sp_node_control_mirror_p_to_n(sp->last); + sp_node_control_mirror_n_to_p(sp->first); + + sp->first->code = sp->last->code; + sp->first = sp->last; + + sp_nodepath_ensure_ctrls(sp->nodepath); + + update_repr(nodepath); + + return; + } + + /* a and b are separate subpaths */ + Inkscape::NodePath::SubPath *sa = a->subpath; + Inkscape::NodePath::SubPath *sb = b->subpath; + + Inkscape::NodePath::Node *n; + NR::Point p; + NRPathcode code; + if (a == sa->first) { + code = (NRPathcode) sa->first->n.other->code; + Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath); + n = sa->last; + sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos); + for (n = n->p.other; n != NULL; n = n->p.other) { + sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos); + } + sp_nodepath_subpath_destroy(sa); + sa = t; + } else if (a == sa->last) { + code = (NRPathcode)sa->last->code; + } else { + code = NR_END; + g_assert_not_reached(); + } + + if (b == sb->first) { + n = sb->first; + sp_node_control_mirror_p_to_n(sa->last); + sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos); + sp_node_control_mirror_n_to_p(sa->last); + for (n = n->n.other; n != NULL; n = n->n.other) { + sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos); + } + } else if (b == sb->last) { + n = sb->last; + sp_node_control_mirror_p_to_n(sa->last); + sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos); + sp_node_control_mirror_n_to_p(sa->last); + for (n = n->p.other; n != NULL; n = n->p.other) { + sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos); + } + } else { + g_assert_not_reached(); + } + /* and now destroy sb */ + + sp_nodepath_subpath_destroy(sb); + + sp_nodepath_ensure_ctrls(sa->nodepath); + + update_repr(nodepath); +} + +/** + * Delete one or more selected nodes. + */ +void sp_node_selected_delete() +{ + Inkscape::NodePath::Path *nodepath = sp_nodepath_current(); + if (!nodepath) return; + if (!nodepath->selected) return; + + /** \todo fixme: do it the right way */ + while (nodepath->selected) { + Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data; + sp_nodepath_node_destroy(node); + } + + + //clean up the nodepath (such as for trivial subpaths) + sp_nodepath_cleanup(nodepath); + + sp_nodepath_ensure_ctrls(nodepath); + + // if the entire nodepath is removed, delete the selected object. + if (nodepath->subpaths == NULL || + sp_nodepath_get_node_count(nodepath) < 2) { + SPDocument *document = SP_DT_DOCUMENT (nodepath->desktop); + sp_nodepath_destroy(nodepath); + sp_selection_delete(); + sp_document_done (document); + return; + } + + update_repr(nodepath); + + sp_nodepath_update_statusbar(nodepath); +} + +/** + * Delete one or more segments between two selected nodes. + * This is the code for 'split'. + */ +void +sp_node_selected_delete_segment(void) +{ + Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive + Inkscape::NodePath::Node *curr, *next; //Iterators + + Inkscape::NodePath::Path *nodepath = sp_nodepath_current(); + if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses + + if (g_list_length(nodepath->selected) != 2) { + nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, + _("Select two non-endpoint nodes on a path between which to delete segments.")); + return; + } + + //Selected nodes, not inclusive + Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data; + Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data; + + if ( ( a==b) || //same node + (a->subpath != b->subpath ) || //not the same path + (!a->p.other || !a->n.other) || //one of a's sides does not have a segment + (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment + { + nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, + _("Select two non-endpoint nodes on a path between which to delete segments.")); + return; + } + + //########################################### + //# BEGIN EDITS + //########################################### + //################################## + //# CLOSED PATH + //################################## + if (a->subpath->closed) { + + + gboolean reversed = FALSE; + + //Since we can go in a circle, we need to find the shorter distance. + // a->b or b->a + start = end = NULL; + int distance = 0; + int minDistance = 0; + for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) { + if (curr==b) { + //printf("a to b:%d\n", distance); + start = a;//go from a to b + end = b; + minDistance = distance; + //printf("A to B :\n"); + break; + } + distance++; + } + + //try again, the other direction + distance = 0; + for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) { + if (curr==a) { + //printf("b to a:%d\n", distance); + if (distance < minDistance) { + start = b; //we go from b to a + end = a; + reversed = TRUE; + //printf("B to A\n"); + } + break; + } + distance++; + } + + + //Copy everything from 'end' to 'start' to a new subpath + Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath); + for (curr=end ; curr ; curr=curr->n.other) { + NRPathcode code = (NRPathcode) curr->code; + if (curr == end) + code = NR_MOVETO; + sp_nodepath_node_new(t, NULL, + (Inkscape::NodePath::NodeType)curr->type, code, + &curr->p.pos, &curr->pos, &curr->n.pos); + if (curr == start) + break; + } + sp_nodepath_subpath_destroy(a->subpath); + + + } + + + + //################################## + //# OPEN PATH + //################################## + else { + + //We need to get the direction of the list between A and B + //Can we walk from a to b? + start = end = NULL; + for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) { + if (curr==b) { + start = a; //did it! we go from a to b + end = b; + //printf("A to B\n"); + break; + } + } + if (!start) {//didn't work? let's try the other direction + for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) { + if (curr==a) { + start = b; //did it! we go from b to a + end = a; + //printf("B to A\n"); + break; + } + } + } + if (!start) { + nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, + _("Cannot find path between nodes.")); + return; + } + + + + //Copy everything after 'end' to a new subpath + Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath); + for (curr=end ; curr ; curr=curr->n.other) { + sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, (NRPathcode)curr->code, + &curr->p.pos, &curr->pos, &curr->n.pos); + } + + //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list + for (curr = start->n.other ; curr ; curr=next) { + next = curr->n.other; + sp_nodepath_node_destroy(curr); + } + + } + //########################################### + //# END EDITS + //########################################### + + //clean up the nodepath (such as for trivial subpaths) + sp_nodepath_cleanup(nodepath); + + sp_nodepath_ensure_ctrls(nodepath); + + update_repr(nodepath); + + // if the entire nodepath is removed, delete the selected object. + if (nodepath->subpaths == NULL || + sp_nodepath_get_node_count(nodepath) < 2) { + sp_nodepath_destroy(nodepath); + sp_selection_delete(); + return; + } + + sp_nodepath_update_statusbar(nodepath); +} + +/** + * Call sp_nodepath_set_line() for all selected segments. + */ +void +sp_node_selected_set_line_type(NRPathcode code) +{ + Inkscape::NodePath::Path *nodepath = sp_nodepath_current(); + if (nodepath == NULL) return; + + for (GList *l = nodepath->selected; l != NULL; l = l->next) { + Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data; + g_assert(n->selected); + if (n->p.other && n->p.other->selected) { + sp_nodepath_set_line_type(n, code); + } + } + + update_repr(nodepath); +} + +/** + * Call sp_nodepath_convert_node_type() for all selected nodes. + */ +void +sp_node_selected_set_type(Inkscape::NodePath::NodeType type) +{ + Inkscape::NodePath::Path *nodepath = sp_nodepath_current(); + if (nodepath == NULL) return; + + for (GList *l = nodepath->selected; l != NULL; l = l->next) { + sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type); + } + + update_repr(nodepath); +} + +/** + * Change select status of node, update its own and neighbour handles. + */ +static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected) +{ + node->selected = selected; + + if (selected) { + g_object_set(G_OBJECT(node->knot), + "fill", NODE_FILL_SEL, + "fill_mouseover", NODE_FILL_SEL_HI, + "stroke", NODE_STROKE_SEL, + "stroke_mouseover", NODE_STROKE_SEL_HI, + "size", (node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9, + NULL); + } else { + g_object_set(G_OBJECT(node->knot), + "fill", NODE_FILL, + "fill_mouseover", NODE_FILL_HI, + "stroke", NODE_STROKE, + "stroke_mouseover", NODE_STROKE_HI, + "size", (node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7, + NULL); + } + + sp_node_ensure_ctrls(node); + if (node->n.other) sp_node_ensure_ctrls(node->n.other); + if (node->p.other) sp_node_ensure_ctrls(node->p.other); +} + +/** +\brief Select a node +\param node The node to select +\param incremental If true, add to selection, otherwise deselect others +\param override If true, always select this node, otherwise toggle selected status +*/ +static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override) +{ + Inkscape::NodePath::Path *nodepath = node->subpath->nodepath; + + if (incremental) { + if (override) { + if (!g_list_find(nodepath->selected, node)) { + nodepath->selected = g_list_append(nodepath->selected, node); + } + sp_node_set_selected(node, TRUE); + } else { // toggle + if (node->selected) { + g_assert(g_list_find(nodepath->selected, node)); + nodepath->selected = g_list_remove(nodepath->selected, node); + } else { + g_assert(!g_list_find(nodepath->selected, node)); + nodepath->selected = g_list_append(nodepath->selected, node); + } + sp_node_set_selected(node, !node->selected); + } + } else { + sp_nodepath_deselect(nodepath); + nodepath->selected = g_list_append(nodepath->selected, node); + sp_node_set_selected(node, TRUE); + } + + sp_nodepath_update_statusbar(nodepath); +} + + +/** +\brief Deselect all nodes in the nodepath +*/ +void +sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath) +{ + if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses + + while (nodepath->selected) { + sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE); + nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data); + } + sp_nodepath_update_statusbar(nodepath); +} + +/** +\brief Select or invert selection of all nodes in the nodepath +*/ +void +sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert) +{ + if (!nodepath) return; + + for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) { + Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data; + for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) { + Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data; + sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE); + } + } +} + +/** + * If nothing selected, does the same as sp_nodepath_select_all(); + * otherwise selects/inverts all nodes in all subpaths that have selected nodes + * (i.e., similar to "select all in layer", with the "selected" subpaths + * being treated as "layers" in the path). + */ +void +sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert) +{ + if (!nodepath) return; + + if (g_list_length (nodepath->selected) == 0) { + sp_nodepath_select_all (nodepath, invert); + return; + } + + GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us + GSList *subpaths = NULL; + + for (GList *l = copy; l != NULL; l = l->next) { + Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data; + Inkscape::NodePath::SubPath *subpath = n->subpath; + if (!g_slist_find (subpaths, subpath)) + subpaths = g_slist_prepend (subpaths, subpath); + } + + for (GSList *sp = subpaths; sp != NULL; sp = sp->next) { + Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data; + for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) { + Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data; + sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE); + } + } + + g_slist_free (subpaths); + g_list_free (copy); +} + +/** + * \brief Select the node after the last selected; if none is selected, + * select the first within path. + */ +void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath) +{ + if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses + + Inkscape::NodePath::Node *last = NULL; + if (nodepath->selected) { + for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) { + Inkscape::NodePath::SubPath *subpath, *subpath_next; + subpath = (Inkscape::NodePath::SubPath *) spl->data; + for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) { + Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data; + if (node->selected) { + if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) { + if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath + if (spl->next) { // there's a next subpath + subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data; + last = subpath_next->first; + } else if (spl->prev) { // there's a previous subpath + last = NULL; // to be set later to the first node of first subpath + } else { + last = node->n.other; + } + } else { + last = node->n.other; + } + } else { + if (node->n.other) { + last = node->n.other; + } else { + if (spl->next) { // there's a next subpath + subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data; + last = subpath_next->first; + } else if (spl->prev) { // there's a previous subpath + last = NULL; // to be set later to the first node of first subpath + } else { + last = (Inkscape::NodePath::Node *) subpath->first; + } + } + } + } + } + } + sp_nodepath_deselect(nodepath); + } + + if (last) { // there's at least one more node after selected + sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE); + } else { // no more nodes, select the first one in first subpath + Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data; + sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE); + } +} + +/** + * \brief Select the node before the first selected; if none is selected, + * select the last within path + */ +void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath) +{ + if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses + + Inkscape::NodePath::Node *last = NULL; + if (nodepath->selected) { + for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) { + Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data; + for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) { + Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data; + if (node->selected) { + if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) { + if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath + if (spl->prev) { // there's a prev subpath + Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data; + last = subpath_prev->last; + } else if (spl->next) { // there's a next subpath + last = NULL; // to be set later to the last node of last subpath + } else { + last = node->p.other; + } + } else { + last = node->p.other; + } + } else { + if (node->p.other) { + last = node->p.other; + } else { + if (spl->prev) { // there's a prev subpath + Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data; + last = subpath_prev->last; + } else if (spl->next) { // there's a next subpath + last = NULL; // to be set later to the last node of last subpath + } else { + last = (Inkscape::NodePath::Node *) subpath->last; + } + } + } + } + } + } + sp_nodepath_deselect(nodepath); + } + + if (last) { // there's at least one more node before selected + sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE); + } else { // no more nodes, select the last one in last subpath + GList *spl = g_list_last(nodepath->subpaths); + Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data; + sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE); + } +} + +/** + * \brief Select all nodes that are within the rectangle. + */ +void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental) +{ + if (!incremental) { + sp_nodepath_deselect(nodepath); + } + + for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) { + Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data; + for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) { + Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data; + + if (b.contains(node->pos)) { + sp_nodepath_node_select(node, TRUE, TRUE); + } + } + } +} + +/** +\brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes +*/ +GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath) +{ + if (!nodepath->selected) { + return NULL; + } + + GList *r = NULL; + guint i = 0; + for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) { + Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data; + for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) { + Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data; + i++; + if (node->selected) { + r = g_list_append(r, GINT_TO_POINTER(i)); + } + } + } + return r; +} + +/** +\brief Restores selection by selecting nodes whose positions are in the list +*/ +void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r) +{ + sp_nodepath_deselect(nodepath); + + guint i = 0; + for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) { + Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data; + for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) { + Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data; + i++; + if (g_list_find(r, GINT_TO_POINTER(i))) { + sp_nodepath_node_select(node, TRUE, TRUE); + } + } + } + +} + +/** +\brief Adjusts control point according to node type and line code. +*/ +static void sp_node_adjust_knot(Inkscape::NodePath::Node *node, gint which_adjust) +{ + double len, otherlen, linelen; + + g_assert(node); + + Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust); + Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me); + + /** \todo fixme: */ + if (me->other == NULL) return; + if (other->other == NULL) return; + + /* I have line */ + + NRPathcode mecode, ocode; + if (which_adjust == 1) { + mecode = (NRPathcode)me->other->code; + ocode = (NRPathcode)node->code; + } else { + mecode = (NRPathcode)node->code; + ocode = (NRPathcode)other->other->code; + } + + if (mecode == NR_LINETO) return; + + /* I am curve */ + + if (other->other == NULL) return; + + /* Other has line */ + + if (node->type == Inkscape::NodePath::NODE_CUSP) return; + + NR::Point delta; + if (ocode == NR_LINETO) { + /* other is lineto, we are either smooth or symm */ + Inkscape::NodePath::Node *othernode = other->other; + len = NR::L2(me->pos - node->pos); + delta = node->pos - othernode->pos; + linelen = NR::L2(delta); + if (linelen < 1e-18) return; + + me->pos = node->pos + (len / linelen)*delta; + sp_knot_set_position(me->knot, &me->pos, 0); + + sp_node_ensure_ctrls(node); + return; + } + + if (node->type == Inkscape::NodePath::NODE_SYMM) { + + me->pos = 2 * node->pos - other->pos; + sp_knot_set_position(me->knot, &me->pos, 0); + + sp_node_ensure_ctrls(node); + return; + } + + /* We are smooth */ + + len = NR::L2(me->pos - node->pos); + delta = other->pos - node->pos; + otherlen = NR::L2(delta); + if (otherlen < 1e-18) return; + + me->pos = node->pos - (len / otherlen) * delta; + sp_knot_set_position(me->knot, &me->pos, 0); + + sp_node_ensure_ctrls(node); +} + +/** + \brief Adjusts control point according to node type and line code + */ +static void sp_node_adjust_knots(Inkscape::NodePath::Node *node) +{ + g_assert(node); + + if (node->type == Inkscape::NodePath::NODE_CUSP) return; + + /* we are either smooth or symm */ + + if (node->p.other == NULL) return; + + if (node->n.other == NULL) return; + + if (node->code == NR_LINETO) { + if (node->n.other->code == NR_LINETO) return; + sp_node_adjust_knot(node, 1); + sp_node_ensure_ctrls(node); + return; + } + + if (node->n.other->code == NR_LINETO) { + if (node->code == NR_LINETO) return; + sp_node_adjust_knot(node, -1); + sp_node_ensure_ctrls(node); + return; + } + + /* both are curves */ + + NR::Point const delta( node->n.pos - node->p.pos ); + + if (node->type == Inkscape::NodePath::NODE_SYMM) { + node->p.pos = node->pos - delta / 2; + node->n.pos = node->pos + delta / 2; + sp_node_ensure_ctrls(node); + return; + } + + /* We are smooth */ + + double plen = NR::L2(node->p.pos - node->pos); + if (plen < 1e-18) return; + double nlen = NR::L2(node->n.pos - node->pos); + if (nlen < 1e-18) return; + node->p.pos = node->pos - (plen / (plen + nlen)) * delta; + node->n.pos = node->pos + (nlen / (plen + nlen)) * delta; + sp_node_ensure_ctrls(node); +} + +/** + * Knot events handler callback. + */ +static gboolean node_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n) +{ + gboolean ret = FALSE; + switch (event->type) { + case GDK_ENTER_NOTIFY: + active_node = n; + break; + case GDK_LEAVE_NOTIFY: + active_node = NULL; + break; + case GDK_KEY_PRESS: + switch (get_group0_keyval (&event->key)) { + case GDK_space: + if (event->key.state & GDK_BUTTON1_MASK) { + Inkscape::NodePath::Path *nodepath = n->subpath->nodepath; + stamp_repr(nodepath); + ret = TRUE; + } + break; + default: + break; + } + break; + default: + break; + } + + return ret; +} + +/** + * Handle keypress on node; directly called. + */ +gboolean node_key(GdkEvent *event) +{ + Inkscape::NodePath::Path *np; + + // there is no way to verify nodes so set active_node to nil when deleting!! + if (active_node == NULL) return FALSE; + + if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) { + gint ret = FALSE; + switch (get_group0_keyval (&event->key)) { + /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts! + case GDK_BackSpace: + np = active_node->subpath->nodepath; + sp_nodepath_node_destroy(active_node); + update_repr(np); + active_node = NULL; + ret = TRUE; + break; + case GDK_c: + sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP); + ret = TRUE; + break; + case GDK_s: + sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH); + ret = TRUE; + break; + case GDK_y: + sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM); + ret = TRUE; + break; + case GDK_b: + sp_nodepath_node_break(active_node); + ret = TRUE; + break; + } + return ret; + } + return FALSE; +} + +/** + * Mouseclick on node callback. + */ +static void node_clicked(SPKnot *knot, guint state, gpointer data) +{ + Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data; + + if (state & GDK_CONTROL_MASK) { + Inkscape::NodePath::Path *nodepath = n->subpath->nodepath; + + if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type + if (n->type == Inkscape::NodePath::NODE_CUSP) { + sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH); + } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) { + sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM); + } else { + sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP); + } + update_repr(nodepath); + sp_nodepath_update_statusbar(nodepath); + + } else { //ctrl+alt+click: delete node + sp_nodepath_node_destroy(n); + //clean up the nodepath (such as for trivial subpaths) + sp_nodepath_cleanup(nodepath); + + // if the entire nodepath is removed, delete the selected object. + if (nodepath->subpaths == NULL || + sp_nodepath_get_node_count(nodepath) < 2) { + SPDocument *document = SP_DT_DOCUMENT (nodepath->desktop); + sp_nodepath_destroy(nodepath); + sp_selection_delete(); + sp_document_done (document); + + } else { + sp_nodepath_ensure_ctrls(nodepath); + update_repr(nodepath); + sp_nodepath_update_statusbar(nodepath); + } + } + + } else { + sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE); + } +} + +/** + * Mouse grabbed node callback. + */ +static void node_grabbed(SPKnot *knot, guint state, gpointer data) +{ + Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data; + + n->origin = knot->pos; + + if (!n->selected) { + sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE); + } +} + +/** + * Mouse ungrabbed node callback. + */ +static void node_ungrabbed(SPKnot *knot, guint state, gpointer data) +{ + Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data; + + n->dragging_out = NULL; + + update_repr(n->subpath->nodepath); +} + +/** + * The point on a line, given by its angle, closest to the given point. + * \param p A point. + * \param a Angle of the line; it is assumed to go through coordinate origin. + * \param closest Pointer to the point struct where the result is stored. + * \todo FIXME: use dot product perhaps? + */ +static void point_line_closest(NR::Point *p, double a, NR::Point *closest) +{ + if (a == HUGE_VAL) { // vertical + *closest = NR::Point(0, (*p)[NR::Y]); + } else { + (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1); + (*closest)[NR::Y] = a * (*closest)[NR::X]; + } +} + +/** + * Distance from the point to a line given by its angle. + * \param p A point. + * \param a Angle of the line; it is assumed to go through coordinate origin. + */ +static double point_line_distance(NR::Point *p, double a) +{ + NR::Point c; + point_line_closest(p, a, &c); + return sqrt(((*p)[NR::X] - c[NR::X])*((*p)[NR::X] - c[NR::X]) + ((*p)[NR::Y] - c[NR::Y])*((*p)[NR::Y] - c[NR::Y])); +} + +/** + * Callback for node "request" signal. + * \todo fixme: This goes to "moved" event? (lauris) + */ +static gboolean +node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data) +{ + double yn, xn, yp, xp; + double an, ap, na, pa; + double d_an, d_ap, d_na, d_pa; + gboolean collinear = FALSE; + NR::Point c; + NR::Point pr; + + Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data; + + // If either (Shift and some handle retracted), or (we're already dragging out a handle) + if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) { + + NR::Point mouse = (*p); + + if (!n->dragging_out) { + // This is the first drag-out event; find out which handle to drag out + double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL); + double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL); + + if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node? + return FALSE; + + Inkscape::NodePath::NodeSide *opposite; + if (appr_p > appr_n) { // closer to p + n->dragging_out = &n->p; + opposite = &n->n; + n->code = NR_CURVETO; + } else if (appr_p < appr_n) { // closer to n + n->dragging_out = &n->n; + opposite = &n->p; + n->n.other->code = NR_CURVETO; + } else { // p and n nodes are the same + if (n->n.pos != n->pos) { // n handle already dragged, drag p + n->dragging_out = &n->p; + opposite = &n->n; + n->code = NR_CURVETO; + } else if (n->p.pos != n->pos) { // p handle already dragged, drag n + n->dragging_out = &n->n; + opposite = &n->p; + n->n.other->code = NR_CURVETO; + } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other + double appr_other_n = (n->n.other ? NR::L2(n->n.other->n.pos - n->pos) - NR::L2(n->n.other->n.pos - (*p)) : -HUGE_VAL); + double appr_other_p = (n->n.other ? NR::L2(n->n.other->p.pos - n->pos) - NR::L2(n->n.other->p.pos - (*p)) : -HUGE_VAL); + if (appr_other_p > appr_other_n) { // closer to other's p handle + n->dragging_out = &n->n; + opposite = &n->p; + n->n.other->code = NR_CURVETO; + } else { // closer to other's n handle + n->dragging_out = &n->p; + opposite = &n->n; + n->code = NR_CURVETO; + } + } + } + + // if there's another handle, make sure the one we drag out starts parallel to it + if (opposite->pos != n->pos) { + mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos); + } + } + + // pass this on to the handle-moved callback + node_ctrl_moved(n->dragging_out->knot, &mouse, state, (gpointer) n); + sp_node_ensure_ctrls(n); + return TRUE; + } + + if (state & GDK_CONTROL_MASK) { // constrained motion + + // calculate relative distances of handles + // n handle: + yn = n->n.pos[NR::Y] - n->pos[NR::Y]; + xn = n->n.pos[NR::X] - n->pos[NR::X]; + // if there's no n handle (straight line), see if we can use the direction to the next point on path + if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) { + if (n->n.other) { // if there is the next point + if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either + yn = n->n.other->pos[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag + xn = n->n.other->pos[NR::X] - n->origin[NR::X]; + } + } + if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi + if (yn < 0) { xn = -xn; yn = -yn; } + + // p handle: + yp = n->p.pos[NR::Y] - n->pos[NR::Y]; + xp = n->p.pos[NR::X] - n->pos[NR::X]; + // if there's no p handle (straight line), see if we can use the direction to the prev point on path + if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) { + if (n->p.other) { + if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6) + yp = n->p.other->pos[NR::Y] - n->origin[NR::Y]; + xp = n->p.other->pos[NR::X] - n->origin[NR::X]; + } + } + if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi + if (yp < 0) { xp = -xp; yp = -yp; } + + if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) { + // sliding on handles, only if at least one of the handles is non-vertical + // (otherwise it's the same as ctrl+drag anyway) + + // calculate angles of the control handles + if (xn == 0) { + if (yn == 0) { // no handle, consider it the continuation of the other one + an = 0; + collinear = TRUE; + } + else an = 0; // vertical; set the angle to horizontal + } else an = yn/xn; + + if (xp == 0) { + if (yp == 0) { // no handle, consider it the continuation of the other one + ap = an; + } + else ap = 0; // vertical; set the angle to horizontal + } else ap = yp/xp; + + if (collinear) an = ap; + + // angles of the perpendiculars; HUGE_VAL means vertical + if (an == 0) na = HUGE_VAL; else na = -1/an; + if (ap == 0) pa = HUGE_VAL; else pa = -1/ap; + + //g_print("an %g ap %g\n", an, ap); + + // mouse point relative to the node's original pos + pr = (*p) - n->origin; + + // distances to the four lines (two handles and two perpendiculars) + d_an = point_line_distance(&pr, an); + d_na = point_line_distance(&pr, na); + d_ap = point_line_distance(&pr, ap); + d_pa = point_line_distance(&pr, pa); + + // find out which line is the closest, save its closest point in c + if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) { + point_line_closest(&pr, an, &c); + } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) { + point_line_closest(&pr, ap, &c); + } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) { + point_line_closest(&pr, na, &c); + } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) { + point_line_closest(&pr, pa, &c); + } + + // move the node to the closest point + sp_nodepath_selected_nodes_move(n->subpath->nodepath, + n->origin[NR::X] + c[NR::X] - n->pos[NR::X], + n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]); + + } else { // constraining to hor/vert + + if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor + sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]); + } else { // snap to vert + sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]); + } + } + } else { // move freely + sp_nodepath_selected_nodes_move(n->subpath->nodepath, + (*p)[NR::X] - n->pos[NR::X], + (*p)[NR::Y] - n->pos[NR::Y], + (state & GDK_SHIFT_MASK) == 0); + } + + n->subpath->nodepath->desktop->scroll_to_point(p); + + return TRUE; +} + +/** + * Node handle clicked callback. + */ +static void node_ctrl_clicked(SPKnot *knot, guint state, gpointer data) +{ + Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data; + + if (state & GDK_CONTROL_MASK) { // "delete" handle + if (n->p.knot == knot) { + n->p.pos = n->pos; + } else if (n->n.knot == knot) { + n->n.pos = n->pos; + } + sp_node_ensure_ctrls(n); + Inkscape::NodePath::Path *nodepath = n->subpath->nodepath; + update_repr(nodepath); + sp_nodepath_update_statusbar(nodepath); + + } else { // just select or add to selection, depending in Shift + sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE); + } +} + +/** + * Node handle grabbed callback. + */ +static void node_ctrl_grabbed(SPKnot *knot, guint state, gpointer data) +{ + Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data; + + if (!n->selected) { + sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE); + } + + // remember the origin of the control + if (n->p.knot == knot) { + n->p.origin = n->p.pos - n->pos; + } else if (n->n.knot == knot) { + n->n.origin = n->n.pos - n->pos; + } else { + g_assert_not_reached(); + } + +} + +/** + * Node handle ungrabbed callback. + */ +static void node_ctrl_ungrabbed(SPKnot *knot, guint state, gpointer data) +{ + Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data; + + // forget origin and set knot position once more (because it can be wrong now due to restrictions) + if (n->p.knot == knot) { + n->p.origin.a = 0; + sp_knot_set_position(knot, &n->p.pos, state); + } else if (n->n.knot == knot) { + n->n.origin.a = 0; + sp_knot_set_position(knot, &n->n.pos, state); + } else { + g_assert_not_reached(); + } + + update_repr(n->subpath->nodepath); +} + +/** + * Node handle "request" signal callback. + */ +static gboolean node_ctrl_request(SPKnot *knot, NR::Point *p, guint state, gpointer data) +{ + Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data; + + Inkscape::NodePath::NodeSide *me, *opposite; + gint which; + if (n->p.knot == knot) { + me = &n->p; + opposite = &n->n; + which = -1; + } else if (n->n.knot == knot) { + me = &n->n; + opposite = &n->p; + which = 1; + } else { + me = opposite = NULL; + which = 0; + g_assert_not_reached(); + } + + NRPathcode const othercode = sp_node_path_code_from_side(n, opposite); + + SnapManager const m(n->subpath->nodepath->desktop->namedview); + + if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) { + /* We are smooth node adjacent with line */ + NR::Point const delta = *p - n->pos; + NR::Coord const len = NR::L2(delta); + Inkscape::NodePath::Node *othernode = opposite->other; + NR::Point const ndelta = n->pos - othernode->pos; + NR::Coord const linelen = NR::L2(ndelta); + if (len > NR_EPSILON && linelen > NR_EPSILON) { + NR::Coord const scal = dot(delta, ndelta) / linelen; + (*p) = n->pos + (scal / linelen) * ndelta; + } + *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, ndelta, NULL).getPoint(); + } else { + *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint(); + } + + sp_node_adjust_knot(n, -which); + + return FALSE; +} + +/** + * Node handle moved callback. + */ +static void node_ctrl_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data) +{ + Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data; + + Inkscape::NodePath::NodeSide *me; + Inkscape::NodePath::NodeSide *other; + if (n->p.knot == knot) { + me = &n->p; + other = &n->n; + } else if (n->n.knot == knot) { + me = &n->n; + other = &n->p; + } else { + me = NULL; + other = NULL; + g_assert_not_reached(); + } + + // calculate radial coordinates of the grabbed control, other control, and the mouse point + Radial rme(me->pos - n->pos); + Radial rother(other->pos - n->pos); + Radial rnew(*p - n->pos); + + if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) { + int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12); + /* 0 interpreted as "no snapping". */ + + // The closest PI/snaps angle, starting from zero. + double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps); + if (me->origin.a == HUGE_VAL) { + // ortho doesn't exist: original control was zero length. + rnew.a = a_snapped; + } else { + /* The closest PI/2 angle, starting from original angle (i.e. snapping to original, + * its opposite and perpendiculars). */ + double const a_ortho = me->origin.a + floor((rnew.a - me->origin.a)/(M_PI/2) + 0.5) * (M_PI/2); + + // Snap to the closest. + rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a) + ? a_snapped + : a_ortho ); + } + } + + if (state & GDK_MOD1_MASK) { + // lock handle length + rnew.r = me->origin.r; + } + + if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK)) + && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) { + // rotate the other handle correspondingly, if both old and new angles exist and are not the same + rother.a += rnew.a - rme.a; + other->pos = NR::Point(rother) + n->pos; + sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos); + sp_knot_set_position(other->knot, &other->pos, 0); + } + + me->pos = NR::Point(rnew) + n->pos; + sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos); + + // this is what sp_knot_set_position does, but without emitting the signal: + // we cannot emit a "moved" signal because we're now processing it + if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos); + + knot->desktop->set_coordinate_status(me->pos); + + update_object(n->subpath->nodepath); + + /* status text */ + SPDesktop *desktop = n->subpath->nodepath->desktop; + if (!desktop) return; + SPEventContext *ec = desktop->event_context; + if (!ec) return; + Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context; + if (!mc) return; + + double degrees = 180 / M_PI * rnew.a; + if (degrees > 180) degrees -= 360; + if (degrees < -180) degrees += 360; + if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0) + degrees = angle_to_compass (degrees); + + GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric()); + + mc->setF(Inkscape::NORMAL_MESSAGE, + _("Node handle: angle %0.2f°, length %s; with Ctrl to snap angle; with Alt to lock length; with Shift to rotate both handles"), degrees, length->str); + + g_string_free(length, TRUE); +} + +/** + * Node handle event callback. + */ +static gboolean node_ctrl_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n) +{ + gboolean ret = FALSE; + switch (event->type) { + case GDK_KEY_PRESS: + switch (get_group0_keyval (&event->key)) { + case GDK_space: + if (event->key.state & GDK_BUTTON1_MASK) { + Inkscape::NodePath::Path *nodepath = n->subpath->nodepath; + stamp_repr(nodepath); + ret = TRUE; + } + break; + default: + break; + } + break; + default: + break; + } + + return ret; +} + +static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle, + Radial &rme, Radial &rother, gboolean const both) +{ + rme.a += angle; + if ( both + || ( n.type == Inkscape::NodePath::NODE_SMOOTH ) + || ( n.type == Inkscape::NodePath::NODE_SYMM ) ) + { + rother.a += angle; + } +} + +static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle, + Radial &rme, Radial &rother, gboolean const both) +{ + gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom(); + + gdouble r; + if ( both + || ( n.type == Inkscape::NodePath::NODE_SMOOTH ) + || ( n.type == Inkscape::NodePath::NODE_SYMM ) ) + { + r = MAX(rme.r, rother.r); + } else { + r = rme.r; + } + + gdouble const weird_angle = atan2(norm_angle, r); +/* Bulia says norm_angle is just the visible distance that the + * object's end must travel on the screen. Left as 'angle' for want of + * a better name.*/ + + rme.a += weird_angle; + if ( both + || ( n.type == Inkscape::NodePath::NODE_SMOOTH ) + || ( n.type == Inkscape::NodePath::NODE_SYMM ) ) + { + rother.a += weird_angle; + } +} + +/** + * Rotate one node. + */ +static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen) +{ + Inkscape::NodePath::NodeSide *me, *other; + bool both = false; + + double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X]; + double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X]; + + if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which" + me = &(n->p); + other = &(n->n); + } else if (!n->p.other) { + me = &(n->n); + other = &(n->p); + } else { + if (which > 0) { // right handle + if (xn > xp) { + me = &(n->n); + other = &(n->p); + } else { + me = &(n->p); + other = &(n->n); + } + } else if (which < 0){ // left handle + if (xn <= xp) { + me = &(n->n); + other = &(n->p); + } else { + me = &(n->p); + other = &(n->n); + } + } else { // both handles + me = &(n->n); + other = &(n->p); + both = true; + } + } + + Radial rme(me->pos - n->pos); + Radial rother(other->pos - n->pos); + + if (screen) { + node_rotate_one_internal_screen (*n, angle, rme, rother, both); + } else { + node_rotate_one_internal (*n, angle, rme, rother, both); + } + + me->pos = n->pos + NR::Point(rme); + + if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) { + other->pos = n->pos + NR::Point(rother); + } + + sp_node_ensure_ctrls(n); +} + +/** + * Rotate selected nodes. + */ +void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen) +{ + if (!nodepath || !nodepath->selected) return; + + if (g_list_length(nodepath->selected) == 1) { + Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data; + node_rotate_one (n, angle, which, screen); + } else { + // rotate as an object: + + Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data; + NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node + for (GList *l = nodepath->selected; l != NULL; l = l->next) { + Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data; + box.expandTo (n->pos); // contain all selected nodes + } + + gdouble rot; + if (screen) { + gdouble const zoom = nodepath->desktop->current_zoom(); + gdouble const zmove = angle / zoom; + gdouble const r = NR::L2(box.max() - box.midpoint()); + rot = atan2(zmove, r); + } else { + rot = angle; + } + + NR::Matrix t = + NR::Matrix (NR::translate(-box.midpoint())) * + NR::Matrix (NR::rotate(rot)) * + NR::Matrix (NR::translate(box.midpoint())); + + for (GList *l = nodepath->selected; l != NULL; l = l->next) { + Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data; + n->pos *= t; + n->n.pos *= t; + n->p.pos *= t; + sp_node_ensure_ctrls(n); + } + } + + update_object(nodepath); + /// \todo fixme: use _keyed + update_repr(nodepath); +} + +/** + * Scale one node. + */ +static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which) +{ + bool both = false; + Inkscape::NodePath::NodeSide *me, *other; + + double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X]; + double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X]; + + if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which" + me = &(n->p); + other = &(n->n); + n->code = NR_CURVETO; + } else if (!n->p.other) { + me = &(n->n); + other = &(n->p); + if (n->n.other) + n->n.other->code = NR_CURVETO; + } else { + if (which > 0) { // right handle + if (xn > xp) { + me = &(n->n); + other = &(n->p); + if (n->n.other) + n->n.other->code = NR_CURVETO; + } else { + me = &(n->p); + other = &(n->n); + n->code = NR_CURVETO; + } + } else if (which < 0){ // left handle + if (xn <= xp) { + me = &(n->n); + other = &(n->p); + if (n->n.other) + n->n.other->code = NR_CURVETO; + } else { + me = &(n->p); + other = &(n->n); + n->code = NR_CURVETO; + } + } else { // both handles + me = &(n->n); + other = &(n->p); + both = true; + n->code = NR_CURVETO; + if (n->n.other) + n->n.other->code = NR_CURVETO; + } + } + + Radial rme(me->pos - n->pos); + Radial rother(other->pos - n->pos); + + rme.r += grow; + if (rme.r < 0) rme.r = 0; + if (rme.a == HUGE_VAL) { + if (me->other) { // if direction is unknown, initialize it towards the next node + Radial rme_next(me->other->pos - n->pos); + rme.a = rme_next.a; + } else { // if there's no next, initialize to 0 + rme.a = 0; + } + } + if (both || n->type == Inkscape::NodePath::NODE_SYMM) { + rother.r += grow; + if (rother.r < 0) rother.r = 0; + if (rother.a == HUGE_VAL) { + rother.a = rme.a + M_PI; + } + } + + me->pos = n->pos + NR::Point(rme); + + if (both || n->type == Inkscape::NodePath::NODE_SYMM) { + other->pos = n->pos + NR::Point(rother); + } + + sp_node_ensure_ctrls(n); +} + +/** + * Scale selected nodes. + */ +void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which) +{ + if (!nodepath || !nodepath->selected) return; + + if (g_list_length(nodepath->selected) == 1) { + // scale handles of the single selected node + Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data; + node_scale_one (n, grow, which); + } else { + // scale nodes as an "object": + + Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data; + NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node + for (GList *l = nodepath->selected; l != NULL; l = l->next) { + Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data; + box.expandTo (n->pos); // contain all selected nodes + } + + double scale = (box.maxExtent() + grow)/box.maxExtent(); + + NR::Matrix t = + NR::Matrix (NR::translate(-box.midpoint())) * + NR::Matrix (NR::scale(scale, scale)) * + NR::Matrix (NR::translate(box.midpoint())); + + for (GList *l = nodepath->selected; l != NULL; l = l->next) { + Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data; + n->pos *= t; + n->n.pos *= t; + n->p.pos *= t; + sp_node_ensure_ctrls(n); + } + } + + update_object(nodepath); + /// \todo fixme: use _keyed + update_repr(nodepath); +} + +void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which) +{ + if (!nodepath) return; + sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which); +} + +/** + * Flip selected nodes horizontally/vertically. + */ +void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis) +{ + if (!nodepath || !nodepath->selected) return; + + if (g_list_length(nodepath->selected) == 1) { + // flip handles of the single selected node + Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data; + double temp = n->p.pos[axis]; + n->p.pos[axis] = n->n.pos[axis]; + n->n.pos[axis] = temp; + sp_node_ensure_ctrls(n); + } else { + // scale nodes as an "object": + + Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data; + NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node + for (GList *l = nodepath->selected; l != NULL; l = l->next) { + Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data; + box.expandTo (n->pos); // contain all selected nodes + } + + NR::Matrix t = + NR::Matrix (NR::translate(-box.midpoint())) * + NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) * + NR::Matrix (NR::translate(box.midpoint())); + + for (GList *l = nodepath->selected; l != NULL; l = l->next) { + Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data; + n->pos *= t; + n->n.pos *= t; + n->p.pos *= t; + sp_node_ensure_ctrls(n); + } + } + + update_object(nodepath); + /// \todo fixme: use _keyed + update_repr(nodepath); +} + +//----------------------------------------------- +/** + * Return new subpath under given nodepath. + */ +static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath) +{ + g_assert(nodepath); + g_assert(nodepath->desktop); + + Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1); + + s->nodepath = nodepath; + s->closed = FALSE; + s->nodes = NULL; + s->first = NULL; + s->last = NULL; + + // do not use prepend here because: + // if you have a path like "subpath_1 subpath_2 ... subpath_k" in the svg, you end up with + // subpath_k -> ... ->subpath_1 in the nodepath structure. thus the i-th node of the svg is not + // the i-th node in the nodepath (only if there are multiple subpaths) + // note that the problem only arise when called from subpath_from_bpath(), since for all the other + // cases, the repr is updated after the call to sp_nodepath_subpath_new() + nodepath->subpaths = g_list_append /*g_list_prepend*/ (nodepath->subpaths, s); + + return s; +} + +/** + * Destroy nodes in subpath, then subpath itself. + */ +static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath) +{ + g_assert(subpath); + g_assert(subpath->nodepath); + g_assert(g_list_find(subpath->nodepath->subpaths, subpath)); + + while (subpath->nodes) { + sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data); + } + + subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath); + + g_free(subpath); +} + +/** + * Link head to tail in subpath. + */ +static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp) +{ + g_assert(!sp->closed); + g_assert(sp->last != sp->first); + g_assert(sp->first->code == NR_MOVETO); + + sp->closed = TRUE; + + //Link the head to the tail + sp->first->p.other = sp->last; + sp->last->n.other = sp->first; + sp->last->n.pos = sp->first->n.pos; + sp->first = sp->last; + + //Remove the extra end node + sp_nodepath_node_destroy(sp->last->n.other); +} + +/** + * Open closed (loopy) subpath at node. + */ +static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n) +{ + g_assert(sp->closed); + g_assert(n->subpath == sp); + g_assert(sp->first == sp->last); + + /* We create new startpoint, current node will become last one */ + + Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, + &n->pos, &n->pos, &n->n.pos); + + + sp->closed = FALSE; + + //Unlink to make a head and tail + sp->first = new_path; + sp->last = n; + n->n.other = NULL; + new_path->p.other = NULL; +} + +/** + * Returns area in triangle given by points; may be negative. + */ +inline double +triangle_area (NR::Point p1, NR::Point p2, NR::Point p3) +{ + return (p1[NR::X]*p2[NR::Y] + p1[NR::Y]*p3[NR::X] + p2[NR::X]*p3[NR::Y] - p2[NR::Y]*p3[NR::X] - p1[NR::Y]*p2[NR::X] - p1[NR::X]*p3[NR::Y]); +} + +/** + * Return new node in subpath with given properties. + * \param pos Position of node. + * \param ppos Handle position in previous direction + * \param npos Handle position in previous direction + */ +Inkscape::NodePath::Node * +sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp, Inkscape::NodePath::Node *next, Inkscape::NodePath::NodeType type, NRPathcode code, NR::Point *ppos, NR::Point *pos, NR::Point *npos) +{ + g_assert(sp); + g_assert(sp->nodepath); + g_assert(sp->nodepath->desktop); + + if (nodechunk == NULL) + nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE); + + Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk); + + n->subpath = sp; + + if (type != Inkscape::NodePath::NODE_NONE) { + // use the type from sodipodi:nodetypes + n->type = type; + } else { + if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) { + // points are (almost) collinear + if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) { + // endnode, or a node with a retracted handle + n->type = Inkscape::NodePath::NODE_CUSP; + } else { + n->type = Inkscape::NodePath::NODE_SMOOTH; + } + } else { + n->type = Inkscape::NodePath::NODE_CUSP; + } + } + + n->code = code; + n->selected = FALSE; + n->pos = *pos; + n->p.pos = *ppos; + n->n.pos = *npos; + + n->dragging_out = NULL; + + Inkscape::NodePath::Node *prev; + if (next) { + g_assert(g_list_find(sp->nodes, next)); + prev = next->p.other; + } else { + prev = sp->last; + } + + if (prev) + prev->n.other = n; + else + sp->first = n; + + if (next) + next->p.other = n; + else + sp->last = n; + + n->p.other = prev; + n->n.other = next; + + n->knot = sp_knot_new(sp->nodepath->desktop); + sp_knot_set_position(n->knot, pos, 0); + g_object_set(G_OBJECT(n->knot), + "anchor", GTK_ANCHOR_CENTER, + "fill", NODE_FILL, + "fill_mouseover", NODE_FILL_HI, + "stroke", NODE_STROKE, + "stroke_mouseover", NODE_STROKE_HI, + "tip", _("Node: drag to edit the path; with Ctrl to snap to horizontal/vertical; with Ctrl+Alt to snap to handles' directions"), + NULL); + if (n->type == Inkscape::NodePath::NODE_CUSP) + g_object_set(G_OBJECT(n->knot), "shape", SP_KNOT_SHAPE_DIAMOND, "size", 9, NULL); + else + g_object_set(G_OBJECT(n->knot), "shape", SP_KNOT_SHAPE_SQUARE, "size", 7, NULL); + + g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n); + g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n); + g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n); + g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n); + g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n); + sp_knot_show(n->knot); + + n->p.knot = sp_knot_new(sp->nodepath->desktop); + sp_knot_set_position(n->p.knot, ppos, 0); + g_object_set(G_OBJECT(n->p.knot), + "shape", SP_KNOT_SHAPE_CIRCLE, + "size", 7, + "anchor", GTK_ANCHOR_CENTER, + "fill", KNOT_FILL, + "fill_mouseover", KNOT_FILL_HI, + "stroke", KNOT_STROKE, + "stroke_mouseover", KNOT_STROKE_HI, + "tip", _("Node handle: drag to shape the curve; with Ctrl to snap angle; with Alt to lock length; with Shift to rotate both handles"), + NULL); + g_signal_connect(G_OBJECT(n->p.knot), "clicked", G_CALLBACK(node_ctrl_clicked), n); + g_signal_connect(G_OBJECT(n->p.knot), "grabbed", G_CALLBACK(node_ctrl_grabbed), n); + g_signal_connect(G_OBJECT(n->p.knot), "ungrabbed", G_CALLBACK(node_ctrl_ungrabbed), n); + g_signal_connect(G_OBJECT(n->p.knot), "request", G_CALLBACK(node_ctrl_request), n); + g_signal_connect(G_OBJECT(n->p.knot), "moved", G_CALLBACK(node_ctrl_moved), n); + g_signal_connect(G_OBJECT(n->p.knot), "event", G_CALLBACK(node_ctrl_event), n); + + sp_knot_hide(n->p.knot); + n->p.line = sp_canvas_item_new(SP_DT_CONTROLS(n->subpath->nodepath->desktop), + SP_TYPE_CTRLLINE, NULL); + sp_canvas_item_hide(n->p.line); + + n->n.knot = sp_knot_new(sp->nodepath->desktop); + sp_knot_set_position(n->n.knot, npos, 0); + g_object_set(G_OBJECT(n->n.knot), + "shape", SP_KNOT_SHAPE_CIRCLE, + "size", 7, + "anchor", GTK_ANCHOR_CENTER, + "fill", KNOT_FILL, + "fill_mouseover", KNOT_FILL_HI, + "stroke", KNOT_STROKE, + "stroke_mouseover", KNOT_STROKE_HI, + "tip", _("Node handle: drag to shape the curve; with Ctrl to snap angle; with Alt to lock length; with Shift to rotate the opposite handle in sync"), + NULL); + g_signal_connect(G_OBJECT(n->n.knot), "clicked", G_CALLBACK(node_ctrl_clicked), n); + g_signal_connect(G_OBJECT(n->n.knot), "grabbed", G_CALLBACK(node_ctrl_grabbed), n); + g_signal_connect(G_OBJECT(n->n.knot), "ungrabbed", G_CALLBACK(node_ctrl_ungrabbed), n); + g_signal_connect(G_OBJECT(n->n.knot), "request", G_CALLBACK(node_ctrl_request), n); + g_signal_connect(G_OBJECT(n->n.knot), "moved", G_CALLBACK(node_ctrl_moved), n); + g_signal_connect(G_OBJECT(n->n.knot), "event", G_CALLBACK(node_ctrl_event), n); + sp_knot_hide(n->n.knot); + n->n.line = sp_canvas_item_new(SP_DT_CONTROLS(n->subpath->nodepath->desktop), + SP_TYPE_CTRLLINE, NULL); + sp_canvas_item_hide(n->n.line); + + sp->nodes = g_list_prepend(sp->nodes, n); + + return n; +} + +/** + * Destroy node and its knots, link neighbors in subpath. + */ +static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node) +{ + g_assert(node); + g_assert(node->subpath); + g_assert(SP_IS_KNOT(node->knot)); + g_assert(SP_IS_KNOT(node->p.knot)); + g_assert(SP_IS_KNOT(node->n.knot)); + g_assert(g_list_find(node->subpath->nodes, node)); + + Inkscape::NodePath::SubPath *sp = node->subpath; + + if (node->selected) { // first, deselect + g_assert(g_list_find(node->subpath->nodepath->selected, node)); + node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node); + } + + node->subpath->nodes = g_list_remove(node->subpath->nodes, node); + + g_object_unref(G_OBJECT(node->knot)); + g_object_unref(G_OBJECT(node->p.knot)); + g_object_unref(G_OBJECT(node->n.knot)); + + gtk_object_destroy(GTK_OBJECT(node->p.line)); + gtk_object_destroy(GTK_OBJECT(node->n.line)); + + if (sp->nodes) { // there are others nodes on the subpath + if (sp->closed) { + if (sp->first == node) { + g_assert(sp->last == node); + sp->first = node->n.other; + sp->last = sp->first; + } + node->p.other->n.other = node->n.other; + node->n.other->p.other = node->p.other; + } else { + if (sp->first == node) { + sp->first = node->n.other; + sp->first->code = NR_MOVETO; + } + if (sp->last == node) sp->last = node->p.other; + if (node->p.other) node->p.other->n.other = node->n.other; + if (node->n.other) node->n.other->p.other = node->p.other; + } + } else { // this was the last node on subpath + sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp); + } + + g_mem_chunk_free(nodechunk, node); +} + +/** + * Returns one of the node's two knots (node sides). + * \param which Indicates which side. + * \return Pointer to previous node side if which==-1, next if which==1. + */ +static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which) +{ + g_assert(node); + + switch (which) { + case -1: + return &node->p; + case 1: + return &node->n; + default: + break; + } + + g_assert_not_reached(); + + return NULL; +} + +/** + * Return knot on other side of node. + */ +static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me) +{ + g_assert(node); + + if (me == &node->p) return &node->n; + if (me == &node->n) return &node->p; + + g_assert_not_reached(); + + return NULL; +} + +/** + * Return NRPathcode on this knot's side of the node. + */ +static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me) +{ + g_assert(node); + + if (me == &node->p) { + if (node->p.other) return (NRPathcode)node->code; + return NR_MOVETO; + } + + if (me == &node->n) { + if (node->n.other) return (NRPathcode)node->n.other->code; + return NR_MOVETO; + } + + g_assert_not_reached(); + + return NR_END; +} + +/** + * Call sp_nodepath_line_add_node() at t on the segment denoted by piece + */ +Inkscape::NodePath::Node * +sp_nodepath_get_node_by_index(int index) +{ + Inkscape::NodePath::Node *e = NULL; + + Inkscape::NodePath::Path *nodepath = sp_nodepath_current(); + if (!nodepath) { + return e; + } + + //find segment + for (GList *l = nodepath->subpaths; l ; l=l->next) { + + Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data; + int n = g_list_length(sp->nodes); + if (sp->closed) { + n++; + } + + //if the piece belongs to this subpath grab it + //otherwise move onto the next subpath + if (index < n) { + e = sp->first; + for (int i = 0; i < index; ++i) { + e = e->n.other; + } + break; + } else { + if (sp->closed) { + index -= (n+1); + } else { + index -= n; + } + } + } + + return e; +} + +/** + * Returns plain text meaning of node type. + */ +static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node) +{ + unsigned retracted = 0; + bool endnode = false; + + for (int which = -1; which <= 1; which += 2) { + Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which); + if (side->other && NR::L2(side->pos - node->pos) < 1e-6) + retracted ++; + if (!side->other) + endnode = true; + } + + if (retracted == 0) { + if (endnode) { + // TRANSLATORS: "end" is an adjective here (NOT a verb) + return _("end node"); + } else { + switch (node->type) { + case Inkscape::NodePath::NODE_CUSP: + // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial + return _("cusp"); + case Inkscape::NodePath::NODE_SMOOTH: + // TRANSLATORS: "smooth" is an adjective here + return _("smooth"); + case Inkscape::NodePath::NODE_SYMM: + return _("symmetric"); + } + } + } else if (retracted == 1) { + if (endnode) { + // TRANSLATORS: "end" is an adjective here (NOT a verb) + return _("end node, handle retracted (drag with Shift to extend)"); + } else { + return _("one handle retracted (drag with Shift to extend)"); + } + } else { + return _("both handles retracted (drag with Shift to extend)"); + } + + return NULL; +} + +/** + * Handles content of statusbar as long as node tool is active. + */ +void +sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath) +{ + gchar const *when_selected = _("Drag nodes or node handles; arrow keys to move nodes"); + gchar const *when_selected_one = _("Drag the node or its handles; arrow keys to move the node"); + + gint total = 0; + gint selected = 0; + SPDesktop *desktop = NULL; + + if (nodepath) { + for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) { + Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data; + total += g_list_length(subpath->nodes); + } + selected = g_list_length(nodepath->selected); + desktop = nodepath->desktop; + } else { + desktop = SP_ACTIVE_DESKTOP; + } + + SPEventContext *ec = desktop->event_context; + if (!ec) return; + Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context; + if (!mc) return; + + if (selected == 0) { + Inkscape::Selection *sel = desktop->selection; + if (!sel || sel->isEmpty()) { + mc->setF(Inkscape::NORMAL_MESSAGE, + _("Select a single object to edit its nodes or handles.")); + } else { + if (nodepath) { + mc->setF(Inkscape::NORMAL_MESSAGE, + ngettext("0 out of %i node selected. Click, Shift+click, or drag around nodes to select.", + "0 out of %i nodes selected. Click, Shift+click, or drag around nodes to select.", + total), + total); + } else { + if (g_slist_length((GSList *)sel->itemList()) == 1) { + mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it.")); + } else { + mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles.")); + } + } + } + } else if (nodepath && selected == 1) { + mc->setF(Inkscape::NORMAL_MESSAGE, + ngettext("%i of %i node selected; %s. %s.", + "%i of %i nodes selected; %s. %s.", + total), + selected, total, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one); + } else { + mc->setF(Inkscape::NORMAL_MESSAGE, + ngettext("%i of %i node selected. %s.", + "%i of %i nodes selected. %s.", + total), + selected, total, when_selected); + } +} + + +/* + 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 : diff --git a/src/nodepath.h b/src/nodepath.h new file mode 100644 index 000000000..84c83be53 --- /dev/null +++ b/src/nodepath.h @@ -0,0 +1,266 @@ +#ifndef __SP_NODEPATH_H__ +#define __SP_NODEPATH_H__ + +/** \file + * Path handler in node edit mode + */ + +/* + * Authors: + * Lauris Kaplinski + * + * This code is in public domain + */ + +//#include "knot.h" +//#include "sp-path.h" +//#include "desktop-handles.h" +#include "libnr/nr-path-code.h" + +#include + +class SPDesktop; +class SPPath; +class SPKnot; + +namespace Inkscape { +namespace XML { +class Node; +} +} + + +/** + * Radial objects are represented by an angle and a distance from + * 0,0. 0,0 is represented by a == big_num. + */ +class Radial{ + public: +/** Radius */ + double r; +/** Amplitude */ + double a; + Radial() {} + // Radial(NR::Point const &p); // Convert a point to radial coordinates + Radial(Radial &p) : r(p.r),a(p.a) {} + // operator NR::Point() const; + +/** + * Construct Radial from NR::Point. + */ +Radial(NR::Point const &p) +{ + r = NR::L2(p); + if (r > 0) { + a = NR::atan2 (p); + } else { + a = HUGE_VAL; //undefined + } +} + +/** + * Cast Radial to cartesian NR::Point. + */ +operator NR::Point() const +{ + if (a == HUGE_VAL) { + return NR::Point(0,0); + } else { + return r*NR::Point(cos(a), sin(a)); + } +} + +}; + +/** defined in node-context.h */ +class SPNodeContext; + +namespace Inkscape { +namespace NodePath { + +/** + * This is a node on a subpath + */ +class Path; + +/** + * This is a subdivision of a NodePath + */ +class SubPath; + +class NodeSide; + +/** + * This is a node (point) along a subpath + */ +class Node; + + +/** + * This is a collection of subpaths which contain nodes + * + * In the following data model. Nodepaths are made up of subpaths which + * are comprised of nodes. + * + * Nodes are linked thus: + * \verbatim + n other + node -----> nodeside ------> node \endverbatim + */ +class Path { + public: +/** Pointer to the current desktop, for reporting purposes */ + SPDesktop * desktop; +/** The parent path of this nodepath */ + SPPath * path; +/** The context which created this nodepath. Important if this nodepath is deleted */ + SPNodeContext * nodeContext; +/** The subpaths which comprise this NodePath */ + GList * subpaths; +/** A list of nodes which are currently selected */ + GList * selected; +/** Transforms (userspace <---> virtual space? someone please describe ) + njh: I'd be guessing that these are item <-> desktop transforms.*/ + NR::Matrix i2d, d2i; +/** The DOM node which describes this NodePath */ + Inkscape::XML::Node *repr; + //STL compliant method to get the selected nodes + void selection(std::list &l); +}; + + +/** + * This is the lowest list item, a simple list of nodes. + */ +class SubPath { + public: +/** The parent of this subpath */ + Path * nodepath; +/** Is this path closed (no endpoints) or not?*/ + gboolean closed; +/** The nodes in this subpath. */ + GList * nodes; +/** The first node of the subpath (does not imply open/closed)*/ + Node * first; +/** The last node of the subpath */ + Node * last; +}; + + + +/** + * What kind of node is this? This is the value for the node->type + * field. NodeType indicates the degree of continuity required for + * the node. I think that the corresponding integer indicates which + * derivate is connected. (Thus 2 means that the node is continuous + * to the second derivative, i.e. has matching endpoints and tangents) + */ +typedef enum { +/** A normal node */ + NODE_NONE, +/** This node non-continuously joins two segments.*/ + NODE_CUSP, +/** This node continuously joins two segments. */ + NODE_SMOOTH, +/** This node is symmetric. */ + NODE_SYMM +} NodeType; + + + +/** + * A NodeSide is a datarecord which may be on either side (n or p) of a node, + * which describes the segment going to the next node. + */ +class NodeSide{ + public: +/** Pointer to the next node, */ + Node * other; +/** Position */ + NR::Point pos; +/** Knots are Inkscape's way of providing draggable points. This + * Knot is the point on the curve representing the control point in a + * bezier curve.*/ + SPKnot * knot; +/** What kind of rendering? */ + SPCanvasItem * line; +/** */ + Radial origin; +}; + +/** + * A node along a NodePath + */ +class Node { + public: +/** The parent subpath of this node */ + SubPath * subpath; +/** Type is selected from NodeType.*/ + guint type : 4; +/** Code refers to which ArtCode is used to represent the segment + * (which segment?).*/ + guint code : 4; +/** Boolean. Am I currently selected or not? */ + guint selected : 1; +/** */ + NR::Point pos; +/** */ + NR::Point origin; +/** Knots are Inkscape's way of providing draggable points. This + * Knot is the point on the curve representing the endpoint.*/ + SPKnot * knot; +/** The NodeSide in the 'next' direction */ + NodeSide n; +/** The NodeSide in the 'previous' direction */ + NodeSide p; + + /** The pointer to the nodeside which we are dragging out with Shift */ + NodeSide *dragging_out; +}; + +} // namespace NodePath +} // namespace Inkscape + +// Do function documentation in nodepath.cpp +Inkscape::NodePath::Path * sp_nodepath_new (SPDesktop * desktop, SPItem * item); +void sp_nodepath_destroy (Inkscape::NodePath::Path * nodepath); +void sp_nodepath_deselect (Inkscape::NodePath::Path *nodepath); +void sp_nodepath_select_all (Inkscape::NodePath::Path *nodepath, bool invert); +void sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert); +void sp_nodepath_select_next (Inkscape::NodePath::Path *nodepath); +void sp_nodepath_select_prev (Inkscape::NodePath::Path *nodepath); +void sp_nodepath_select_rect (Inkscape::NodePath::Path * nodepath, NR::Rect const &b, gboolean incremental); +GList *save_nodepath_selection (Inkscape::NodePath::Path *nodepath); +void restore_nodepath_selection (Inkscape::NodePath::Path *nodepath, GList *r); +gboolean nodepath_repr_d_changed (Inkscape::NodePath::Path * np, const char *newd); +gboolean nodepath_repr_typestr_changed (Inkscape::NodePath::Path * np, const char *newtypestr); +gboolean node_key (GdkEvent * event); +void sp_nodepath_update_statusbar (Inkscape::NodePath::Path *nodepath); +void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis); +void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis); +void sp_nodepath_select_segment_near_point(SPItem * item, NR::Point p, bool toggle); +void sp_nodepath_add_node_near_point(SPItem * item, NR::Point p); +void sp_nodepath_curve_drag(Inkscape::NodePath::Node * e, double t, NR::Point delta, char * key); +Inkscape::NodePath::Node * sp_nodepath_get_node_by_index(int index); +/* possibly private functions */ + +void sp_node_selected_add_node (void); +void sp_node_selected_break (void); +void sp_node_selected_duplicate (void); +void sp_node_selected_join (void); +void sp_node_selected_join_segment (void); +void sp_node_selected_delete (void); +void sp_node_selected_delete_segment (void); +void sp_node_selected_set_type (Inkscape::NodePath::NodeType type); +void sp_node_selected_set_line_type (NRPathcode code); +void sp_node_selected_move (gdouble dx, gdouble dy); +void sp_node_selected_move_screen (gdouble dx, gdouble dy); + +void sp_nodepath_selected_nodes_rotate (Inkscape::NodePath::Path * nodepath, gdouble angle, int which, bool screen); + +void sp_nodepath_selected_nodes_scale (Inkscape::NodePath::Path * nodepath, gdouble grow, int which); +void sp_nodepath_selected_nodes_scale_screen (Inkscape::NodePath::Path * nodepath, gdouble grow, int which); + +void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis); + +#endif diff --git a/src/object-edit.cpp b/src/object-edit.cpp new file mode 100644 index 000000000..3db0f0a21 --- /dev/null +++ b/src/object-edit.cpp @@ -0,0 +1,1074 @@ +#define __SP_OBJECT_EDIT_C__ + +/* + * Node editing extension to objects + * + * Authors: + * Lauris Kaplinski + * Mitsuru Oka + * + * Licensed under GNU GPL + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + + + +#include "sp-item.h" +#include "sp-rect.h" +#include "sp-ellipse.h" +#include "sp-star.h" +#include "sp-spiral.h" +#include "sp-offset.h" +#include "sp-flowtext.h" +#include "prefs-utils.h" +#include "inkscape.h" +#include "snap.h" +#include "desktop-affine.h" +#include +#include "desktop.h" + +#include "sp-pattern.h" +#include "sp-path.h" + +#include + +#include "object-edit.h" + +#include + + +#include "xml/repr.h" + +#include "isnan.h" + +#define sp_round(v,m) (((v) < 0.0) ? ((ceil((v) / (m) - 0.5)) * (m)) : ((floor((v) / (m) + 0.5)) * (m))) + +static SPKnotHolder *sp_rect_knot_holder(SPItem *item, SPDesktop *desktop); +static SPKnotHolder *sp_arc_knot_holder(SPItem *item, SPDesktop *desktop); +static SPKnotHolder *sp_star_knot_holder(SPItem *item, SPDesktop *desktop); +static SPKnotHolder *sp_spiral_knot_holder(SPItem *item, SPDesktop *desktop); +static SPKnotHolder *sp_offset_knot_holder(SPItem *item, SPDesktop *desktop); +static SPKnotHolder *sp_path_knot_holder(SPItem *item, SPDesktop *desktop); +static SPKnotHolder *sp_flowtext_knot_holder(SPItem *item, SPDesktop *desktop); +static void sp_pat_knot_holder(SPItem *item, SPKnotHolder *knot_holder); + +SPKnotHolder * +sp_item_knot_holder(SPItem *item, SPDesktop *desktop) +{ + if (SP_IS_RECT(item)) { + return sp_rect_knot_holder(item, desktop); + } else if (SP_IS_ARC(item)) { + return sp_arc_knot_holder(item, desktop); + } else if (SP_IS_STAR(item)) { + return sp_star_knot_holder(item, desktop); + } else if (SP_IS_SPIRAL(item)) { + return sp_spiral_knot_holder(item, desktop); + } else if (SP_IS_OFFSET(item)) { + return sp_offset_knot_holder(item, desktop); + } else if (SP_IS_PATH(item)) { + return sp_path_knot_holder(item, desktop); + } else if (SP_IS_FLOWTEXT(item) && SP_FLOWTEXT(item)->has_internal_frame()) { + return sp_flowtext_knot_holder(item, desktop); + } + + return NULL; +} + + +/* Pattern manipulation */ + +static gdouble sp_pattern_extract_theta(SPPattern *pat, gdouble scale) +{ + gdouble theta = asin(pat->patternTransform[1] / scale); + if (pat->patternTransform[0] < 0) theta = M_PI - theta ; + return theta; +} + +static gdouble sp_pattern_extract_scale(SPPattern *pat) +{ + gdouble s = pat->patternTransform[1]; + gdouble c = pat->patternTransform[0]; + gdouble xscale = sqrt(c * c + s * s); + return xscale; +} + +static NR::Point sp_pattern_extract_trans(SPPattern const *pat) +{ + return NR::Point(pat->patternTransform[4], pat->patternTransform[5]); +} + +static void +sp_pattern_xy_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state) +{ + SPPattern *pat = SP_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style)); + + NR::Point p_snapped = p; + + if ( state & GDK_CONTROL_MASK ) { + if (fabs((p - origin)[NR::X]) > fabs((p - origin)[NR::Y])) { + p_snapped[NR::Y] = origin[NR::Y]; + } else { + p_snapped[NR::X] = origin[NR::X]; + } + } + + if (state) { + NR::Point const q = p_snapped - sp_pattern_extract_trans(pat); + sp_item_adjust_pattern(item, NR::Matrix(NR::translate(q))); + } + + item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + + +static NR::Point sp_pattern_xy_get(SPItem *item) +{ + SPPattern const *pat = SP_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style)); + return sp_pattern_extract_trans(pat); +} + +static NR::Point sp_pattern_angle_get(SPItem *item) +{ + SPPattern *pat = SP_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style)); + + gdouble x = (pattern_width(pat)*0.5); + gdouble y = 0; + NR::Point delta = NR::Point(x,y); + gdouble scale = sp_pattern_extract_scale(pat); + gdouble theta = sp_pattern_extract_theta(pat, scale); + delta = delta * NR::Matrix(NR::rotate(theta))*NR::Matrix(NR::scale(scale,scale)); + delta = delta + sp_pattern_extract_trans(pat); + return delta; +} + +static void +sp_pattern_angle_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state) +{ + int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12); + + SPPattern *pat = SP_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style)); + + // get the angle from pattern 0,0 to the cursor pos + NR::Point delta = p - sp_pattern_extract_trans(pat); + gdouble theta = atan2(delta); + + if ( state & GDK_CONTROL_MASK ) { + theta = sp_round(theta, M_PI/snaps); + } + + // get the scale from the current transform so we can keep it. + gdouble scl = sp_pattern_extract_scale(pat); + NR::Matrix rot = NR::Matrix(NR::rotate(theta)) * NR::Matrix(NR::scale(scl,scl)); + NR::Point const t = sp_pattern_extract_trans(pat); + rot[4] = t[NR::X]; + rot[5] = t[NR::Y]; + sp_item_adjust_pattern(item, rot, true); + item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +static void +sp_pattern_scale_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state) +{ + SPPattern *pat = SP_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style)); + + // Get the scale from the position of the knotholder, + NR::Point d = p - sp_pattern_extract_trans(pat); + gdouble s = NR::L2(d); + gdouble pat_x = pattern_width(pat) * 0.5; + gdouble pat_y = pattern_height(pat) * 0.5; + gdouble pat_h = hypot(pat_x, pat_y); + gdouble scl = s / pat_h; + + // get angle from current transform, (need get current scale first to calculate angle) + gdouble oldscale = sp_pattern_extract_scale(pat); + gdouble theta = sp_pattern_extract_theta(pat,oldscale); + + NR::Matrix rot = NR::Matrix(NR::rotate(theta)) * NR::Matrix(NR::scale(scl,scl)); + NR::Point const t = sp_pattern_extract_trans(pat); + rot[4] = t[NR::X]; + rot[5] = t[NR::Y]; + sp_item_adjust_pattern(item, rot, true); + item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + + +static NR::Point sp_pattern_scale_get(SPItem *item) +{ + SPPattern *pat = SP_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style)); + + gdouble x = pattern_width(pat)*0.5; + gdouble y = pattern_height(pat)*0.5; + NR::Point delta = NR::Point(x,y); + NR::Matrix a = pat->patternTransform; + a[4] = 0; + a[5] = 0; + delta = delta * a; + delta = delta + sp_pattern_extract_trans(pat); + return delta; +} + +/* SPRect */ + +static NR::Point sp_rect_rx_get(SPItem *item) +{ + SPRect *rect = SP_RECT(item); + + return NR::Point(rect->x.computed + rect->width.computed - rect->rx.computed, rect->y.computed); +} + +static void sp_rect_rx_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state) +{ + SPRect *rect = SP_RECT(item); + + if (state & GDK_CONTROL_MASK) { + gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0; + rect->rx.computed = rect->ry.computed = CLAMP(rect->x.computed + rect->width.computed - p[NR::X], 0.0, temp); + rect->rx._set = rect->ry._set = true; + + } else { + rect->rx.computed = CLAMP(rect->x.computed + rect->width.computed - p[NR::X], 0.0, rect->width.computed / 2.0); + rect->rx._set = true; + } + + ((SPObject*)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + + +static NR::Point sp_rect_ry_get(SPItem *item) +{ + SPRect *rect = SP_RECT(item); + + return NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->ry.computed); +} + +static void sp_rect_ry_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state) +{ + SPRect *rect = SP_RECT(item); + + if (state & GDK_CONTROL_MASK) { + gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0; + rect->rx.computed = rect->ry.computed = CLAMP(p[NR::Y] - rect->y.computed, 0.0, temp); + rect->ry._set = rect->rx._set = true; + } else { + if (!rect->rx._set || rect->rx.computed == 0) { + rect->ry.computed = CLAMP(p[NR::Y] - rect->y.computed, + 0.0, + MIN(rect->height.computed / 2.0, rect->width.computed / 2.0)); + } else { + rect->ry.computed = CLAMP(p[NR::Y] - rect->y.computed, + 0.0, + rect->height.computed / 2.0); + } + + rect->ry._set = true; + } + + ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +/** + * Remove rounding from a rectangle. + */ +static void rect_remove_rounding(SPRect *rect) +{ + SP_OBJECT_REPR(rect)->setAttribute("rx", NULL); + SP_OBJECT_REPR(rect)->setAttribute("ry", NULL); +} + +/** + * Called when the horizontal rounding radius knot is clicked. + */ +static void sp_rect_rx_knot_click(SPItem *item, guint state) +{ + SPRect *rect = SP_RECT(item); + + if (state & GDK_SHIFT_MASK) { + rect_remove_rounding(rect); + } else if (state & GDK_CONTROL_MASK) { + /* Ctrl-click sets the vertical rounding to be the same as the horizontal */ + SP_OBJECT_REPR(rect)->setAttribute("ry", SP_OBJECT_REPR(rect)->attribute("rx")); + } +} + +/** + * Called when the vertical rounding radius knot is clicked. + */ +static void sp_rect_ry_knot_click(SPItem *item, guint state) +{ + SPRect *rect = SP_RECT(item); + + if (state & GDK_SHIFT_MASK) { + rect_remove_rounding(rect); + } else if (state & GDK_CONTROL_MASK) { + /* Ctrl-click sets the vertical rounding to be the same as the horizontal */ + SP_OBJECT_REPR(rect)->setAttribute("rx", SP_OBJECT_REPR(rect)->attribute("ry")); + } +} + +#define SGN(x) ((x)>0?1:((x)<0?-1:0)) + +static void sp_rect_clamp_radii(SPRect *rect) +{ + // clamp rounding radii so that they do not exceed width/height + if (2 * rect->rx.computed > rect->width.computed) { + rect->rx.computed = 0.5 * rect->width.computed; + rect->rx._set = true; + } + if (2 * rect->ry.computed > rect->height.computed) { + rect->ry.computed = 0.5 * rect->height.computed; + rect->ry._set = true; + } +} + +static NR::Point sp_rect_wh_get(SPItem *item) +{ + SPRect *rect = SP_RECT(item); + + return NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed); +} + +static NR::Point rect_snap_knot_position(NR::Point const &p) +{ + SPDesktop const *desktop = inkscape_active_desktop(); + NR::Point s = sp_desktop_dt2root_xy_point(desktop, p); + SnapManager const m(desktop->namedview); + s = m.freeSnap(Inkscape::Snapper::BBOX_POINT | Inkscape::Snapper::SNAP_POINT, s, NULL).getPoint(); + return sp_desktop_root2dt_xy_point(desktop, s); +} + +static void sp_rect_wh_set_internal(SPRect *rect, NR::Point const &p, NR::Point const &origin, guint state) +{ + NR::Point const s = rect_snap_knot_position(p); + + if (state & GDK_CONTROL_MASK) { + // original width/height when drag started + gdouble const w_orig = (origin[NR::X] - rect->x.computed); + gdouble const h_orig = (origin[NR::Y] - rect->y.computed); + + //original ratio + gdouble const ratio = (w_orig / h_orig); + + // mouse displacement since drag started + gdouble const minx = s[NR::X] - origin[NR::X]; + gdouble const miny = s[NR::Y] - origin[NR::Y]; + + if (fabs(minx) > fabs(miny)) { + + // snap to horizontal or diagonal + rect->width.computed = MAX(w_orig + minx, 0); + if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) { + // closer to the diagonal and in same-sign quarters, change both using ratio + rect->height.computed = MAX(h_orig + minx / ratio, 0); + } else { + // closer to the horizontal, change only width, height is h_orig + rect->height.computed = MAX(h_orig, 0); + } + + } else { + // snap to vertical or diagonal + rect->height.computed = MAX(h_orig + miny, 0); + if (miny != 0 && fabs(minx/miny) > 0.5 * ratio && (SGN(minx) == SGN(miny))) { + // closer to the diagonal and in same-sign quarters, change both using ratio + rect->width.computed = MAX(w_orig + miny * ratio, 0); + } else { + // closer to the vertical, change only height, width is w_orig + rect->width.computed = MAX(w_orig, 0); + } + } + + rect->width._set = rect->height._set = true; + + } else { + // move freely + rect->width.computed = MAX(s[NR::X] - rect->x.computed, 0); + rect->height.computed = MAX(s[NR::Y] - rect->y.computed, 0); + rect->width._set = rect->height._set = true; + } + + sp_rect_clamp_radii(rect); + + ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +static void sp_rect_wh_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state) +{ + SPRect *rect = SP_RECT(item); + + sp_rect_wh_set_internal(rect, p, origin, state); +} + +static NR::Point sp_rect_xy_get(SPItem *item) +{ + SPRect *rect = SP_RECT(item); + + return NR::Point(rect->x.computed, rect->y.computed); +} + +static void sp_rect_xy_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state) +{ + SPRect *rect = SP_RECT(item); + + // opposite corner (unmoved) + gdouble opposite_x = (rect->x.computed + rect->width.computed); + gdouble opposite_y = (rect->y.computed + rect->height.computed); + + // original width/height when drag started + gdouble w_orig = opposite_x - origin[NR::X]; + gdouble h_orig = opposite_y - origin[NR::Y]; + + NR::Point const s = rect_snap_knot_position(p); + + // mouse displacement since drag started + gdouble minx = s[NR::X] - origin[NR::X]; + gdouble miny = s[NR::Y] - origin[NR::Y]; + + if (state & GDK_CONTROL_MASK) { + //original ratio + gdouble ratio = (w_orig / h_orig); + + if (fabs(minx) > fabs(miny)) { + + // snap to horizontal or diagonal + rect->x.computed = MIN(s[NR::X], opposite_x); + rect->width.computed = MAX(w_orig - minx, 0); + if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) { + // closer to the diagonal and in same-sign quarters, change both using ratio + rect->y.computed = MIN(origin[NR::Y] + minx / ratio, opposite_y); + rect->height.computed = MAX(h_orig - minx / ratio, 0); + } else { + // closer to the horizontal, change only width, height is h_orig + rect->y.computed = MIN(origin[NR::Y], opposite_y); + rect->height.computed = MAX(h_orig, 0); + } + + } else { + + // snap to vertical or diagonal + rect->y.computed = MIN(s[NR::Y], opposite_y); + rect->height.computed = MAX(h_orig - miny, 0); + if (miny != 0 && fabs(minx/miny) > 0.5 *ratio && (SGN(minx) == SGN(miny))) { + // closer to the diagonal and in same-sign quarters, change both using ratio + rect->x.computed = MIN(origin[NR::X] + miny * ratio, opposite_x); + rect->width.computed = MAX(w_orig - miny * ratio, 0); + } else { + // closer to the vertical, change only height, width is w_orig + rect->x.computed = MIN(origin[NR::X], opposite_x); + rect->width.computed = MAX(w_orig, 0); + } + + } + + rect->width._set = rect->height._set = rect->x._set = rect->y._set = true; + + } else { + // move freely + rect->x.computed = MIN(s[NR::X], opposite_x); + rect->width.computed = MAX(w_orig - minx, 0); + rect->y.computed = MIN(s[NR::Y], opposite_y); + rect->height.computed = MAX(h_orig - miny, 0); + rect->width._set = rect->height._set = rect->x._set = rect->y._set = true; + } + + sp_rect_clamp_radii(rect); + + ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +static SPKnotHolder *sp_rect_knot_holder(SPItem *item, SPDesktop *desktop) +{ + SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, item, NULL); + + sp_knot_holder_add_full( + knot_holder, sp_rect_rx_set, sp_rect_rx_get, sp_rect_rx_knot_click, + SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR, + _("Adjust the horizontal rounding radius; with Ctrl to make the vertical " + "radius the same")); + + sp_knot_holder_add_full( + knot_holder, sp_rect_ry_set, sp_rect_ry_get, sp_rect_ry_knot_click, + SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR, + _("Adjust the vertical rounding radius; with Ctrl to make the horizontal " + "radius the same") + ); + + sp_knot_holder_add_full( + knot_holder, sp_rect_wh_set, sp_rect_wh_get, NULL, + SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR, + _("Adjust the width and height of the rectangle; with Ctrl to lock ratio " + "or stretch in one dimension only") + ); + + sp_knot_holder_add_full( + knot_holder, sp_rect_xy_set, sp_rect_xy_get, NULL, + SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR, + _("Adjust the width and height of the rectangle; with Ctrl to lock ratio " + "or stretch in one dimension only") + ); + + sp_pat_knot_holder(item, knot_holder); + return knot_holder; +} + +/* SPArc */ + +/* + * return values: + * 1 : inside + * 0 : on the curves + * -1 : outside + */ +static gint +sp_genericellipse_side(SPGenericEllipse *ellipse, NR::Point const &p) +{ + gdouble dx = (p[NR::X] - ellipse->cx.computed) / ellipse->rx.computed; + gdouble dy = (p[NR::Y] - ellipse->cy.computed) / ellipse->ry.computed; + + gdouble s = dx * dx + dy * dy; + if (s < 1.0) return 1; + if (s > 1.0) return -1; + return 0; +} + +static void +sp_arc_start_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state) +{ + int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12); + + SPGenericEllipse *ge = SP_GENERICELLIPSE(item); + SPArc *arc = SP_ARC(item); + + ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE; + + NR::Point delta = p - NR::Point(ge->cx.computed, ge->cy.computed); + NR::scale sc(ge->rx.computed, ge->ry.computed); + ge->start = atan2(delta * sc.inverse()); + if ( ( state & GDK_CONTROL_MASK ) + && snaps ) + { + ge->start = sp_round(ge->start, M_PI/snaps); + } + sp_genericellipse_normalize(ge); + ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +static NR::Point sp_arc_start_get(SPItem *item) +{ + SPGenericEllipse *ge = SP_GENERICELLIPSE(item); + SPArc *arc = SP_ARC(item); + + return sp_arc_get_xy(arc, ge->start); +} + +static void +sp_arc_end_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state) +{ + int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12); + + SPGenericEllipse *ge = SP_GENERICELLIPSE(item); + SPArc *arc = SP_ARC(item); + + ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE; + + NR::Point delta = p - NR::Point(ge->cx.computed, ge->cy.computed); + NR::scale sc(ge->rx.computed, ge->ry.computed); + ge->end = atan2(delta * sc.inverse()); + if ( ( state & GDK_CONTROL_MASK ) + && snaps ) + { + ge->end = sp_round(ge->end, M_PI/snaps); + } + sp_genericellipse_normalize(ge); + ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +static NR::Point sp_arc_end_get(SPItem *item) +{ + SPGenericEllipse *ge = SP_GENERICELLIPSE(item); + SPArc *arc = SP_ARC(item); + + return sp_arc_get_xy(arc, ge->end); +} + +static void +sp_arc_startend_click(SPItem *item, guint state) +{ + SPGenericEllipse *ge = SP_GENERICELLIPSE(item); + + if (state & GDK_SHIFT_MASK) { + ge->end = ge->start = 0; + ((SPObject *)ge)->updateRepr(); + } +} + + +static void +sp_arc_rx_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state) +{ + SPGenericEllipse *ge = SP_GENERICELLIPSE(item); + SPArc *arc = SP_ARC(item); + + ge->rx.computed = fabs( ge->cx.computed - p[NR::X] ); + + if ( state & GDK_CONTROL_MASK ) { + ge->ry.computed = ge->rx.computed; + } + + ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +static NR::Point sp_arc_rx_get(SPItem *item) +{ + SPGenericEllipse *ge = SP_GENERICELLIPSE(item); + + return (NR::Point(ge->cx.computed, ge->cy.computed) - NR::Point(ge->rx.computed, 0)); +} + +static void +sp_arc_ry_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state) +{ + SPGenericEllipse *ge = SP_GENERICELLIPSE(item); + SPArc *arc = SP_ARC(item); + + ge->ry.computed = fabs( ge->cy.computed - p[NR::Y] ); + + if ( state & GDK_CONTROL_MASK ) { + ge->rx.computed = ge->ry.computed; + } + + ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +static NR::Point sp_arc_ry_get(SPItem *item) +{ + SPGenericEllipse *ge = SP_GENERICELLIPSE(item); + + return (NR::Point(ge->cx.computed, ge->cy.computed) - NR::Point(0, ge->ry.computed)); +} + +static void +sp_arc_rx_click(SPItem *item, guint state) +{ + SPGenericEllipse *ge = SP_GENERICELLIPSE(item); + + if (state & GDK_CONTROL_MASK) { + ge->ry.computed = ge->rx.computed; + ((SPObject *)ge)->updateRepr(); + } +} + +static void +sp_arc_ry_click(SPItem *item, guint state) +{ + SPGenericEllipse *ge = SP_GENERICELLIPSE(item); + + if (state & GDK_CONTROL_MASK) { + ge->rx.computed = ge->ry.computed; + ((SPObject *)ge)->updateRepr(); + } +} + +static SPKnotHolder * +sp_arc_knot_holder(SPItem *item, SPDesktop *desktop) +{ + SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, item, NULL); + + sp_knot_holder_add_full(knot_holder, sp_arc_rx_set, sp_arc_rx_get, sp_arc_rx_click, + SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR, + _("Adjust ellipse width, with Ctrl to make circle")); + sp_knot_holder_add_full(knot_holder, sp_arc_ry_set, sp_arc_ry_get, sp_arc_ry_click, + SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR, + _("Adjust ellipse height, with Ctrl to make circle")); + sp_knot_holder_add_full(knot_holder, sp_arc_start_set, sp_arc_start_get, sp_arc_startend_click, + SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR, + _("Position the start point of the arc or segment; with Ctrl to snap angle; drag inside the ellipse for arc, outside for segment")); + sp_knot_holder_add_full(knot_holder, sp_arc_end_set, sp_arc_end_get, sp_arc_startend_click, + SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR, + _("Position the end point of the arc or segment; with Ctrl to snap angle; drag inside the ellipse for arc, outside for segment")); + + sp_pat_knot_holder(item, knot_holder); + + return knot_holder; +} + +/* SPStar */ + +static void +sp_star_knot1_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state) +{ + SPStar *star = SP_STAR(item); + + NR::Point d = p - star->center; + + double arg1 = atan2(d); + double darg1 = arg1 - star->arg[0]; + + if (state & GDK_MOD1_MASK) { + star->randomized = darg1/(star->arg[0] - star->arg[1]); + } else if (state & GDK_SHIFT_MASK) { + star->rounded = darg1/(star->arg[0] - star->arg[1]); + } else if (state & GDK_CONTROL_MASK) { + star->r[0] = L2(d); + } else { + star->r[0] = L2(d); + star->arg[0] = arg1; + star->arg[1] += darg1; + } + ((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +static void +sp_star_knot2_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state) +{ + SPStar *star = SP_STAR(item); + if (star->flatsided == false) { + NR::Point d = p - star->center; + + double arg1 = atan2(d); + double darg1 = arg1 - star->arg[1]; + + if (state & GDK_MOD1_MASK) { + star->randomized = darg1/(star->arg[0] - star->arg[1]); + } else if (state & GDK_SHIFT_MASK) { + star->rounded = fabs(darg1/(star->arg[0] - star->arg[1])); + } else if (state & GDK_CONTROL_MASK) { + star->r[1] = L2(d); + star->arg[1] = star->arg[0] + M_PI / star->sides; + } + else { + star->r[1] = L2(d); + star->arg[1] = atan2(d); + } + ((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } +} + +static NR::Point sp_star_knot1_get(SPItem *item) +{ + g_assert(item != NULL); + + SPStar *star = SP_STAR(item); + + return sp_star_get_xy(star, SP_STAR_POINT_KNOT1, 0); + +} + +static NR::Point sp_star_knot2_get(SPItem *item) +{ + g_assert(item != NULL); + + SPStar *star = SP_STAR(item); + + return sp_star_get_xy(star, SP_STAR_POINT_KNOT2, 0); +} + +static void +sp_star_knot_click(SPItem *item, guint state) +{ + SPStar *star = SP_STAR(item); + + if (state & GDK_MOD1_MASK) { + star->randomized = 0; + ((SPObject *)star)->updateRepr(); + } else if (state & GDK_SHIFT_MASK) { + star->rounded = 0; + ((SPObject *)star)->updateRepr(); + } else if (state & GDK_CONTROL_MASK) { + star->arg[1] = star->arg[0] + M_PI / star->sides; + ((SPObject *)star)->updateRepr(); + } +} + +static SPKnotHolder * +sp_star_knot_holder(SPItem *item, SPDesktop *desktop) +{ + /* we don't need to get parent knot_holder */ + SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, item, NULL); + g_assert(item != NULL); + + SPStar *star = SP_STAR(item); + + sp_knot_holder_add(knot_holder, sp_star_knot1_set, sp_star_knot1_get, sp_star_knot_click, + _("Adjust the tip radius of the star or polygon; with Shift to round; with Alt to randomize")); + if (star->flatsided == false) + sp_knot_holder_add(knot_holder, sp_star_knot2_set, sp_star_knot2_get, sp_star_knot_click, + _("Adjust the base radius of the star; with Ctrl to keep star rays radial (no skew); with Shift to round; with Alt to randomize")); + + sp_pat_knot_holder(item, knot_holder); + + return knot_holder; +} + +/* SPSpiral */ + +/* + * set attributes via inner (t=t0) knot point: + * [default] increase/decrease inner point + * [shift] increase/decrease inner and outer arg synchronizely + * [control] constrain inner arg to round per PI/4 + */ +static void +sp_spiral_inner_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state) +{ + int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12); + + SPSpiral *spiral = SP_SPIRAL(item); + + gdouble dx = p[NR::X] - spiral->cx; + gdouble dy = p[NR::Y] - spiral->cy; + + if (state & GDK_MOD1_MASK) { + // adjust divergence by vertical drag, relative to rad + double new_exp = (spiral->rad + dy)/(spiral->rad); + spiral->exp = new_exp > 0? new_exp : 0; + } else { + // roll/unroll from inside + gdouble arg_t0; + sp_spiral_get_polar(spiral, spiral->t0, NULL, &arg_t0); + + gdouble arg_tmp = atan2(dy, dx) - arg_t0; + gdouble arg_t0_new = arg_tmp - floor((arg_tmp+M_PI)/(2.0*M_PI))*2.0*M_PI + arg_t0; + spiral->t0 = (arg_t0_new - spiral->arg) / (2.0*M_PI*spiral->revo); + + /* round inner arg per PI/snaps, if CTRL is pressed */ + if ( ( state & GDK_CONTROL_MASK ) + && ( fabs(spiral->revo) > SP_EPSILON_2 ) + && ( snaps != 0 ) ) { + gdouble arg = 2.0*M_PI*spiral->revo*spiral->t0 + spiral->arg; + spiral->t0 = (sp_round(arg, M_PI/snaps) - spiral->arg)/(2.0*M_PI*spiral->revo); + } + + spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999); + } + + ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +/* + * set attributes via outer (t=1) knot point: + * [default] increase/decrease revolution factor + * [control] constrain inner arg to round per PI/4 + */ +static void +sp_spiral_outer_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state) +{ + int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12); + + SPSpiral *spiral = SP_SPIRAL(item); + + gdouble dx = p[NR::X] - spiral->cx; + gdouble dy = p[NR::Y] - spiral->cy; + + if (state & GDK_SHIFT_MASK) { // rotate without roll/unroll + spiral->arg = atan2(dy, dx) - 2.0*M_PI*spiral->revo; + if (!(state & GDK_MOD1_MASK)) { + // if alt not pressed, change also rad; otherwise it is locked + spiral->rad = MAX(hypot(dx, dy), 0.001); + } + if ( ( state & GDK_CONTROL_MASK ) + && snaps ) { + spiral->arg = sp_round(spiral->arg, M_PI/snaps); + } + } else { // roll/unroll + // arg of the spiral outer end + double arg_1; + sp_spiral_get_polar(spiral, 1, NULL, &arg_1); + + // its fractional part after the whole turns are subtracted + double arg_r = arg_1 - sp_round(arg_1, 2.0*M_PI); + + // arg of the mouse point relative to spiral center + double mouse_angle = atan2(dy, dx); + if (mouse_angle < 0) + mouse_angle += 2*M_PI; + + // snap if ctrl + if ( ( state & GDK_CONTROL_MASK ) && snaps ) { + mouse_angle = sp_round(mouse_angle, M_PI/snaps); + } + + // by how much we want to rotate the outer point + double diff = mouse_angle - arg_r; + if (diff > M_PI) + diff -= 2*M_PI; + else if (diff < -M_PI) + diff += 2*M_PI; + + // calculate the new rad; + // the value of t corresponding to the angle arg_1 + diff: + double t_temp = ((arg_1 + diff) - spiral->arg)/(2*M_PI*spiral->revo); + // the rad at that t: + double rad_new = 0; + if (t_temp > spiral->t0) + sp_spiral_get_polar(spiral, t_temp, &rad_new, NULL); + + // change the revo (converting diff from radians to the number of turns) + spiral->revo += diff/(2*M_PI); + if (spiral->revo < 1e-3) + spiral->revo = 1e-3; + + // if alt not pressed and the values are sane, change the rad + if (!(state & GDK_MOD1_MASK) && rad_new > 1e-3 && rad_new/spiral->rad < 2) { + // adjust t0 too so that the inner point stays unmoved + double r0; + sp_spiral_get_polar(spiral, spiral->t0, &r0, NULL); + spiral->rad = rad_new; + spiral->t0 = pow(r0 / spiral->rad, 1.0/spiral->exp); + } + if (!isFinite(spiral->t0)) spiral->t0 = 0.0; + spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999); + } + + ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +static NR::Point sp_spiral_inner_get(SPItem *item) +{ + SPSpiral *spiral = SP_SPIRAL(item); + + return sp_spiral_get_xy(spiral, spiral->t0); +} + +static NR::Point sp_spiral_outer_get(SPItem *item) +{ + SPSpiral *spiral = SP_SPIRAL(item); + + return sp_spiral_get_xy(spiral, 1.0); +} + +static void +sp_spiral_inner_click(SPItem *item, guint state) +{ + SPSpiral *spiral = SP_SPIRAL(item); + + if (state & GDK_MOD1_MASK) { + spiral->exp = 1; + ((SPObject *)spiral)->updateRepr(); + } else if (state & GDK_SHIFT_MASK) { + spiral->t0 = 0; + ((SPObject *)spiral)->updateRepr(); + } +} + +static SPKnotHolder * +sp_spiral_knot_holder(SPItem *item, SPDesktop *desktop) +{ + SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, item, NULL); + + sp_knot_holder_add(knot_holder, sp_spiral_inner_set, sp_spiral_inner_get, sp_spiral_inner_click, + _("Roll/unroll the spiral from inside; with Ctrl to snap angle; with Alt to converge/diverge")); + sp_knot_holder_add(knot_holder, sp_spiral_outer_set, sp_spiral_outer_get, NULL, + _("Roll/unroll the spiral from outside; with Ctrl to snap angle; with Shift to scale/rotate")); + + sp_pat_knot_holder(item, knot_holder); + + return knot_holder; +} + +/* SPOffset */ + +static void +sp_offset_offset_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state) +{ + SPOffset *offset = SP_OFFSET(item); + + offset->rad = sp_offset_distance_to_original(offset, p); + offset->knot = p; + offset->knotSet = true; + + ((SPObject *)offset)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + + +static NR::Point sp_offset_offset_get(SPItem *item) +{ + SPOffset *offset = SP_OFFSET(item); + + NR::Point np; + sp_offset_top_point(offset,&np); + return np; +} + +static SPKnotHolder * +sp_offset_knot_holder(SPItem *item, SPDesktop *desktop) +{ + SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, item, NULL); + + sp_knot_holder_add(knot_holder, sp_offset_offset_set, sp_offset_offset_get, NULL, + _("Adjust the offset distance")); + + sp_pat_knot_holder(item, knot_holder); + + return knot_holder; +} + +static SPKnotHolder * +sp_path_knot_holder(SPItem *item, SPDesktop *desktop) // FIXME: eliminate, instead make a pattern-drag similar to gradient-drag +{ + if ((SP_OBJECT(item)->style->fill.type == SP_PAINT_TYPE_PAINTSERVER) + && SP_IS_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style))) + { + SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, item, NULL); + + sp_pat_knot_holder(item, knot_holder); + + return knot_holder; + } + return NULL; +} + +static void +sp_pat_knot_holder(SPItem *item, SPKnotHolder *knot_holder) +{ + if ((SP_OBJECT(item)->style->fill.type == SP_PAINT_TYPE_PAINTSERVER) + && SP_IS_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style))) + { + sp_knot_holder_add_full(knot_holder, sp_pattern_xy_set, sp_pattern_xy_get, NULL, SP_KNOT_SHAPE_CROSS, SP_KNOT_MODE_XOR, + // TRANSLATORS: This refers to the pattern that's inside the object + _("Move the pattern fill inside the object")); + sp_knot_holder_add_full(knot_holder, sp_pattern_scale_set, sp_pattern_scale_get, NULL, SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR, + _("Scale the pattern fill uniformly")); + sp_knot_holder_add_full(knot_holder, sp_pattern_angle_set, sp_pattern_angle_get, NULL, SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR, + _("Rotate the pattern fill; with Ctrl to snap angle")); + } +} + +static NR::Point sp_flowtext_corner_get(SPItem *item) +{ + SPRect *rect = SP_RECT(item); + + return NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed); +} + +static void +sp_flowtext_corner_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state) +{ + SPRect *rect = SP_RECT(item); + + sp_rect_wh_set_internal(rect, p, origin, state); +} + +static SPKnotHolder * +sp_flowtext_knot_holder(SPItem *item, SPDesktop *desktop) +{ + SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, SP_FLOWTEXT(item)->get_frame(NULL), NULL); + + sp_knot_holder_add(knot_holder, sp_flowtext_corner_set, sp_flowtext_corner_get, NULL, + _("Drag to resize the flowed text frame")); + + return knot_holder; +} + + +/* + 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 : diff --git a/src/object-edit.h b/src/object-edit.h new file mode 100644 index 000000000..9fdc2365f --- /dev/null +++ b/src/object-edit.h @@ -0,0 +1,29 @@ +#ifndef __SP_OBJECT_EDIT_H__ +#define __SP_OBJECT_EDIT_H__ + +/* + * Node editing extension to objects + * + * Authors: + * Lauris Kaplinski + * Mitsuru Oka + * + * Licensed under GNU GPL + */ + +#include "knotholder.h" + +SPKnotHolder *sp_item_knot_holder (SPItem *item, SPDesktop *desktop); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/object-hierarchy.cpp b/src/object-hierarchy.cpp new file mode 100644 index 000000000..30f13e49a --- /dev/null +++ b/src/object-hierarchy.cpp @@ -0,0 +1,213 @@ +/** \file + * Object hierarchy implementation. + * + * Authors: + * MenTaLguY + * + * Copyright (C) 2004 MenTaLguY + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-object.h" +#include "object-hierarchy.h" + +namespace Inkscape { + +/** + * Create new object hierarchy. + * \param top The first entry if non-NULL. + */ +ObjectHierarchy::ObjectHierarchy(SPObject *top) { + if (top) { + _addBottom(top); + } +} + +ObjectHierarchy::~ObjectHierarchy() { + _clear(); +} + +/** + * Remove all entries. + */ +void ObjectHierarchy::clear() { + _clear(); + _changed_signal.emit(NULL, NULL); +} + +/** + * Trim or expand hierarchy on top such that object becomes top entry. + */ +void ObjectHierarchy::setTop(SPObject *object) { + g_return_if_fail(object != NULL); + + if ( top() == object ) { + return; + } + + if (!top()) { + _addTop(object); + } else if (object->isAncestorOf(top())) { + _addTop(object, top()); + } else if ( object == bottom() || object->isAncestorOf(bottom()) ) { + _trimAbove(object); + } else { + _clear(); + _addTop(object); + } + + _changed_signal.emit(top(), bottom()); +} + +/** + * Add hierarchy from junior's parent to senior to this + * hierarchy's top. + */ +void ObjectHierarchy::_addTop(SPObject *senior, SPObject *junior) { + g_assert(junior != NULL); + g_assert(senior != NULL); + + SPObject *object=SP_OBJECT_PARENT(junior); + do { + _addTop(object); + object = SP_OBJECT_PARENT(object); + } while ( object != senior ); +} + +/** + * Add object to top of hierarchy. + * \pre object!=NULL + */ +void ObjectHierarchy::_addTop(SPObject *object) { + g_assert(object != NULL); + _hierarchy.push_back(_attach(object)); + _added_signal.emit(object); +} + +/** + * Remove all objects above limit from hierarchy. + */ +void ObjectHierarchy::_trimAbove(SPObject *limit) { + while ( !_hierarchy.empty() && _hierarchy.back().object != limit ) { + SPObject *object=_hierarchy.back().object; + + sp_object_ref(object, NULL); + _detach(_hierarchy.back()); + _hierarchy.pop_back(); + _removed_signal.emit(object); + sp_object_unref(object, NULL); + } +} + +/** + * Trim or expand hierarchy at bottom such that object becomes bottom entry. + */ +void ObjectHierarchy::setBottom(SPObject *object) { + g_return_if_fail(object != NULL); + + if ( bottom() == object ) { + return; + } + + if (!top()) { + _addBottom(object); + } else if (bottom()->isAncestorOf(object)) { + _addBottom(bottom(), object); + } else if ( top() == object ) { + _trimBelow(top()); + } else if (top()->isAncestorOf(object)) { + if (object->isAncestorOf(bottom())) { + _trimBelow(object); + } else { // object is a sibling or cousin of bottom() + SPObject *saved_top=top(); + sp_object_ref(saved_top, NULL); + _clear(); + _addBottom(saved_top); + _addBottom(saved_top, object); + sp_object_unref(saved_top, NULL); + } + } else { + _clear(); + _addBottom(object); + } + + _changed_signal.emit(top(), bottom()); +} + +/** + * Remove all objects under given object. + * \param limit If NULL, remove all. + */ +void ObjectHierarchy::_trimBelow(SPObject *limit) { + while ( !_hierarchy.empty() && _hierarchy.front().object != limit ) { + SPObject *object=_hierarchy.front().object; + sp_object_ref(object, NULL); + _detach(_hierarchy.front()); + _hierarchy.pop_front(); + _removed_signal.emit(object); + sp_object_unref(object, NULL); + } +} + +/** + * Add hierarchy from senior to junior to this hierarchy's bottom. + */ +void ObjectHierarchy::_addBottom(SPObject *senior, SPObject *junior) { + g_assert(junior != NULL); + g_assert(senior != NULL); + + if ( junior != senior ) { + _addBottom(senior, SP_OBJECT_PARENT(junior)); + _addBottom(junior); + } +} + +/** + * Add object at bottom of hierarchy. + * \pre object!=NULL + */ +void ObjectHierarchy::_addBottom(SPObject *object) { + g_assert(object != NULL); + _hierarchy.push_front(_attach(object)); + _added_signal.emit(object); +} + +void ObjectHierarchy::_trim_for_release(SPObject *object, ObjectHierarchy *hier) +{ + hier->_trimBelow(object); + g_assert(!hier->_hierarchy.empty()); + g_assert(hier->_hierarchy.front().object == object); + + sp_object_ref(object, NULL); + hier->_detach(hier->_hierarchy.front()); + hier->_hierarchy.pop_front(); + hier->_removed_signal.emit(object); + sp_object_unref(object, NULL); + + hier->_changed_signal.emit(hier->top(), hier->bottom()); +} + +ObjectHierarchy::Record ObjectHierarchy::_attach(SPObject *object) { + sp_object_ref(object, NULL); + gulong id = g_signal_connect(G_OBJECT(object), "release", GCallback(&ObjectHierarchy::_trim_for_release), this); + return Record(object, id); +} + +void ObjectHierarchy::_detach(ObjectHierarchy::Record const &rec) { + g_signal_handler_disconnect(G_OBJECT(rec.object), rec.handler_id); + sp_object_unref(rec.object, NULL); +} + +} + +/* + 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 : diff --git a/src/object-hierarchy.h b/src/object-hierarchy.h new file mode 100644 index 000000000..92da163ea --- /dev/null +++ b/src/object-hierarchy.h @@ -0,0 +1,119 @@ +/** \file + * Inkscape::ObjectHierarchy - tracks a hierarchy of active SPObjects + * + * Authors: + * MenTaLguY + * + * Copyright (C) 2004 MenTaLguY + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SEEN_INKSCAPE_OBJECT_HIERARCHY_H +#define SEEN_INKSCAPE_OBJECT_HIERARCHY_H + +#include +#include +#include +#include +#include + +class SPObject; + +namespace Inkscape { + +/** + * An Inkscape::ObjectHierarchy is useful for situations where one wishes + * to keep a reference to an SPObject, but fall back on one of its ancestors + * when that object is removed. + * + * That cannot be accomplished simply by hooking the "release" signal of the + * SPObject, as by the time that signal is emitted, the object's parent + * field has already been cleared. + * + * There are also some subtle refcounting issues to take into account. + * + * @see SPObject + */ + +class ObjectHierarchy { +public: + ObjectHierarchy(SPObject *top=NULL); + ~ObjectHierarchy(); + + bool contains(SPObject *object); + + sigc::connection connectAdded(const sigc::slot &slot) { + return _added_signal.connect(slot); + } + sigc::connection connectRemoved(const sigc::slot &slot) { + return _removed_signal.connect(slot); + } + sigc::connection connectChanged(const sigc::slot &slot) + { + return _changed_signal.connect(slot); + } + + void clear(); + + SPObject *top() { + return !_hierarchy.empty() ? _hierarchy.back().object : NULL; + } + void setTop(SPObject *object); + + SPObject *bottom() { + return !_hierarchy.empty() ? _hierarchy.front().object : NULL; + } + void setBottom(SPObject *object); + +private: + struct Record { + Record(SPObject *o, gulong id) : object(o), handler_id(id) {} + + SPObject *object; + gulong handler_id; + }; + + ObjectHierarchy(ObjectHierarchy const &); // no copy + void operator=(ObjectHierarchy const &); // no assign + + /// @brief adds objects in range [senior, junior) to the top + void _addTop(SPObject *senior, SPObject *junior); + /// @brief adds one object to the top + void _addTop(SPObject *object); + /// @brief removes all objects above the limit object + void _trimAbove(SPObject *limit); + + /// @brief adds objects in range (senior, junior] to the bottom + void _addBottom(SPObject *senior, SPObject *junior); + /// @brief adds one object to the bottom + void _addBottom(SPObject *object); + /// @brief removes all objects below the limit object + void _trimBelow(SPObject *limit); + + Record _attach(SPObject *object); + void _detach(Record const &record); + + void _clear() { _trimBelow(NULL); } + + static void _trim_for_release(SPObject *released, ObjectHierarchy *hier); + + std::list _hierarchy; + sigc::signal _added_signal; + sigc::signal _removed_signal; + sigc::signal _changed_signal; +}; + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/object-snapper.cpp b/src/object-snapper.cpp new file mode 100644 index 000000000..6552cea96 --- /dev/null +++ b/src/object-snapper.cpp @@ -0,0 +1,176 @@ +/** + * \file object-snapper.cpp + * \brief Snapping things to objects. + * + * Authors: + * Carl Hetherington + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "libnr/n-art-bpath.h" +#include "libnr/nr-rect-ops.h" +#include "document.h" +#include "sp-namedview.h" +#include "sp-path.h" +#include "display/curve.h" +#include "desktop.h" +#include "inkscape.h" +#include "splivarot.h" + + +Inkscape::ObjectSnapper::ObjectSnapper(SPNamedView const *nv, NR::Coord const d) + : Snapper(nv, d), _snap_to_nodes(true), _snap_to_paths(true) +{ + +} + + +/** + * \param p Point we are trying to snap (desktop coordinates) + */ + +void Inkscape::ObjectSnapper::_findCandidates(std::list& c, + SPObject* r, + std::list const &it, + NR::Point const &p) const +{ + for (SPObject* o = r->children; o != NULL; o = o->next) { + if (SP_IS_ITEM(o)) { + + /* See if this item is on the ignore list */ + std::list::const_iterator i = it.begin(); + while (i != it.end() && *i != o) { + i++; + } + + if (i == it.end()) { + /* See if the item is within range */ + NR::Rect const b = NR::expand(sp_item_bbox_desktop(SP_ITEM(o)), -getDistance()); + if (b.contains(p)) { + c.push_back(SP_ITEM(o)); + } + } + } + + _findCandidates(c, o, it, p); + } +} + + +void Inkscape::ObjectSnapper::_snapNodes(Inkscape::SnappedPoint &s, + NR::Point const &p, + std::list const &cand) const +{ + /* FIXME: this seems like a hack. Perhaps Snappers should be + ** in SPDesktop rather than SPNamedView? + */ + SPDesktop const *desktop = SP_ACTIVE_DESKTOP; + + for (std::list::const_iterator i = cand.begin(); i != cand.end(); i++) { + if (SP_IS_SHAPE(*i)) { + + SPShape const *sh = SP_SHAPE(*i); + if (sh->curve) { + + int j = 0; + NR::Matrix const i2doc = sp_item_i2doc_affine(*i); + + while (sh->curve->bpath[j].code != NR_END) { + + /* Get this node in desktop coordinates */ + NArtBpath const &bp = sh->curve->bpath[j]; + NR::Point const n = desktop->doc2dt(bp.c(3) * i2doc); + + /* Try to snap to this node of the path */ + NR::Coord const dist = NR::L2(n - p); + if (dist < getDistance() && dist < s.getDistance()) { + s = SnappedPoint(n, dist); + } + + j++; + } + } + } + } +} + + +void Inkscape::ObjectSnapper::_snapPaths(Inkscape::SnappedPoint &s, + NR::Point const &p, + std::list const &cand) const +{ + /* FIXME: this seems like a hack. Perhaps Snappers should be + ** in SPDesktop rather than SPNamedView? + */ + SPDesktop const *desktop = SP_ACTIVE_DESKTOP; + + NR::Point const p_doc = desktop->dt2doc(p); + + for (std::list::const_iterator i = cand.begin(); i != cand.end(); i++) { + + /* Transform the requested snap point to this item's coordinates */ + NR::Matrix const i2doc = sp_item_i2doc_affine(*i); + NR::Point const p_it = p_doc * i2doc.inverse(); + + /* Look for the nearest position on this SPItem to our snap point */ + NR::Maybe const o = get_nearest_position_on_Path(*i, p_it); + if (o != NR::Nothing() && o.assume().t >= 0 && o.assume().t <= 1) { + + /* Convert the nearest point back to desktop coordinates */ + NR::Point const o_it = get_point_on_Path(*i, o.assume().piece, o.assume().t); + NR::Point const o_dt = desktop->doc2dt(o_it * i2doc); + + NR::Coord const dist = NR::L2(o_dt - p); + if (dist < getDistance() && dist < s.getDistance()) { + s = SnappedPoint(o_dt, dist); + } + } + } + +} + + +Inkscape::SnappedPoint Inkscape::ObjectSnapper::_doFreeSnap(NR::Point const &p, + std::list const &it) const +{ + /* Get a list of all the SPItems that we will try to snap to */ + std::list cand; + _findCandidates(cand, sp_document_root(_named_view->document), it, p); + + SnappedPoint s(p, NR_HUGE); + + if (_snap_to_nodes) { + _snapNodes(s, p, cand); + } + if (_snap_to_paths) { + _snapPaths(s, p, cand); + } + + return s; +} + + + +Inkscape::SnappedPoint Inkscape::ObjectSnapper::_doConstrainedSnap(NR::Point const &p, + NR::Point const &c, + std::list const &it) const +{ + /* FIXME: this needs implementing properly; I think we have to do the + ** intersection of c with the objects. + */ + return _doFreeSnap(p, it); +} + +/* + 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/object-snapper.h b/src/object-snapper.h new file mode 100644 index 000000000..189e96e3e --- /dev/null +++ b/src/object-snapper.h @@ -0,0 +1,68 @@ +#ifndef SEEN_OBJECT_SNAPPER_H +#define SEEN_OBJECT_SNAPPER_H + +/** + * \file object-snapper.h + * \brief Snapping things to objects. + * + * Authors: + * Carl Hetherington + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "snapper.h" + +struct SPNamedView; +struct SPItem; +struct SPObject; + +namespace Inkscape +{ + +class ObjectSnapper : public Snapper +{ +public: + ObjectSnapper(SPNamedView const *nv, NR::Coord const d); + + void setSnapToNodes(bool s) { + _snap_to_nodes = s; + } + + bool getSnapToNodes() const { + return _snap_to_nodes; + } + + void setSnapToPaths(bool s) { + _snap_to_paths = s; + } + + bool getSnapToPaths() const { + return _snap_to_paths; + } + +private: + SnappedPoint _doFreeSnap(NR::Point const &p, + std::list const &it) const; + + SnappedPoint _doConstrainedSnap(NR::Point const &p, + NR::Point const &c, + std::list const &it) const; + + void _findCandidates(std::list& c, + SPObject* r, + std::list const &it, + NR::Point const &p) const; + + void _snapNodes(Inkscape::SnappedPoint &s, NR::Point const &p, std::list const &cand) const; + void _snapPaths(Inkscape::SnappedPoint &s, NR::Point const &p, std::list const &cand) const; + + bool _snap_to_nodes; + bool _snap_to_paths; +}; + +} + +#endif diff --git a/src/object-ui.cpp b/src/object-ui.cpp new file mode 100644 index 000000000..a55a8f500 --- /dev/null +++ b/src/object-ui.cpp @@ -0,0 +1,351 @@ +#define __SP_OBJECT_UI_C__ + +/* + * Unser-interface related object extension + * + * Authors: + * Lauris Kaplinski + * + * This code is in public domain + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "object-ui.h" +#include "xml/repr.h" + +static void sp_object_type_menu(GType type, SPObject *object, SPDesktop *desktop, GtkMenu *menu); + +/* Append object-specific part to context menu */ + +void +sp_object_menu(SPObject *object, SPDesktop *desktop, GtkMenu *menu) +{ + GObjectClass *klass; + klass = G_OBJECT_GET_CLASS(object); + while (G_TYPE_CHECK_CLASS_TYPE((klass), SP_TYPE_OBJECT)) { + GType type; + type = G_TYPE_FROM_CLASS(klass); + sp_object_type_menu(type, object, desktop, menu); + klass = (GObjectClass*)g_type_class_peek_parent(klass); + } +} + +/* Implementation */ + +#include + +#include + +#include "sp-anchor.h" +#include "sp-image.h" + +#include "document.h" +#include "desktop-handles.h" +#include "selection.h" + +#include "dialogs/item-properties.h" +#include "dialogs/object-attributes.h" +#include "dialogs/object-properties.h" + +#include "sp-path.h" + + +static void sp_item_menu(SPObject *object, SPDesktop *desktop, GtkMenu *menu); +static void sp_group_menu(SPObject *object, SPDesktop *desktop, GtkMenu *menu); +static void sp_anchor_menu(SPObject *object, SPDesktop *desktop, GtkMenu *menu); +static void sp_image_menu(SPObject *object, SPDesktop *desktop, GtkMenu *menu); +static void sp_shape_menu(SPObject *object, SPDesktop *desktop, GtkMenu *menu); + +static void +sp_object_type_menu(GType type, SPObject *object, SPDesktop *desktop, GtkMenu *menu) +{ + static GHashTable *t2m = NULL; + void (* handler)(SPObject *object, SPDesktop *desktop, GtkMenu *menu); + if (!t2m) { + t2m = g_hash_table_new(NULL, NULL); + g_hash_table_insert(t2m, GUINT_TO_POINTER(SP_TYPE_ITEM), (void*)sp_item_menu); + g_hash_table_insert(t2m, GUINT_TO_POINTER(SP_TYPE_GROUP), (void*)sp_group_menu); + g_hash_table_insert(t2m, GUINT_TO_POINTER(SP_TYPE_ANCHOR), (void*)sp_anchor_menu); + g_hash_table_insert(t2m, GUINT_TO_POINTER(SP_TYPE_IMAGE), (void*)sp_image_menu); + g_hash_table_insert(t2m, GUINT_TO_POINTER(SP_TYPE_SHAPE), (void*)sp_shape_menu); + } + handler = (void (*)(SPObject*, SPDesktop*, GtkMenu*))g_hash_table_lookup(t2m, GUINT_TO_POINTER(type)); + if (handler) handler(object, desktop, menu); +} + +/* SPItem */ + +static void sp_item_properties(GtkMenuItem *menuitem, SPItem *item); +static void sp_item_select_this(GtkMenuItem *menuitem, SPItem *item); +static void sp_item_create_link(GtkMenuItem *menuitem, SPItem *item); + +/* Generate context menu item section */ + +static void +sp_item_menu(SPObject *object, SPDesktop *desktop, GtkMenu *m) +{ + SPItem *item; + GtkWidget *w; + + item = (SPItem *) object; + + /* Item dialog */ + w = gtk_menu_item_new_with_mnemonic(_("Object _Properties")); + gtk_object_set_data(GTK_OBJECT(w), "desktop", desktop); + gtk_signal_connect(GTK_OBJECT(w), "activate", GTK_SIGNAL_FUNC(sp_item_properties), item); + gtk_widget_show(w); + gtk_menu_append(GTK_MENU(m), w); + /* Separator */ + w = gtk_menu_item_new(); + gtk_widget_show(w); + gtk_menu_append(GTK_MENU(m), w); + /* Select item */ + w = gtk_menu_item_new_with_mnemonic(_("_Select This")); + if (SP_DT_SELECTION(desktop)->includes(item)) { + gtk_widget_set_sensitive(w, FALSE); + } else { + gtk_object_set_data(GTK_OBJECT(w), "desktop", desktop); + gtk_signal_connect(GTK_OBJECT(w), "activate", GTK_SIGNAL_FUNC(sp_item_select_this), item); + } + gtk_widget_show(w); + gtk_menu_append(GTK_MENU(m), w); + /* Create link */ + w = gtk_menu_item_new_with_mnemonic(_("_Create Link")); + gtk_object_set_data(GTK_OBJECT(w), "desktop", desktop); + gtk_signal_connect(GTK_OBJECT(w), "activate", GTK_SIGNAL_FUNC(sp_item_create_link), item); + gtk_widget_set_sensitive(w, !SP_IS_ANCHOR(item)); + gtk_widget_show(w); + gtk_menu_append(GTK_MENU(m), w); +} + +static void +sp_item_properties(GtkMenuItem *menuitem, SPItem *item) +{ + SPDesktop *desktop; + + g_assert(SP_IS_ITEM(item)); + + desktop = (SPDesktop*)gtk_object_get_data(GTK_OBJECT(menuitem), "desktop"); + g_return_if_fail(desktop != NULL); + + SP_DT_SELECTION(desktop)->set(item); + + sp_item_dialog(); +} + +static void +sp_item_select_this(GtkMenuItem *menuitem, SPItem *item) +{ + SPDesktop *desktop; + + g_assert(SP_IS_ITEM(item)); + + desktop = (SPDesktop*)gtk_object_get_data(GTK_OBJECT(menuitem), "desktop"); + g_return_if_fail(desktop != NULL); + + SP_DT_SELECTION(desktop)->set(item); +} + +static void +sp_item_create_link(GtkMenuItem *menuitem, SPItem *item) +{ + g_assert(SP_IS_ITEM(item)); + g_assert(!SP_IS_ANCHOR(item)); + + SPDesktop *desktop = (SPDesktop*)gtk_object_get_data(GTK_OBJECT(menuitem), "desktop"); + g_return_if_fail(desktop != NULL); + + Inkscape::XML::Node *repr = sp_repr_new("svg:a"); + SP_OBJECT_REPR(SP_OBJECT_PARENT(item))->addChild(repr, SP_OBJECT_REPR(item)); + SPObject *object = SP_OBJECT_DOCUMENT(item)->getObjectByRepr(repr); + g_return_if_fail(SP_IS_ANCHOR(object)); + + const char *id = SP_OBJECT_REPR(item)->attribute("id"); + Inkscape::XML::Node *child = SP_OBJECT_REPR(item)->duplicate(); + SP_OBJECT(item)->deleteObject(false); + repr->addChild(child, NULL); + child->setAttribute("id", id); + sp_document_done(SP_OBJECT_DOCUMENT(object)); + + sp_object_attributes_dialog(object, "SPAnchor"); + + SP_DT_SELECTION(desktop)->set(SP_ITEM(object)); +} + +/* SPGroup */ + +static void sp_item_group_ungroup_activate(GtkMenuItem *menuitem, SPGroup *group); + +static void +sp_group_menu(SPObject *object, SPDesktop *desktop, GtkMenu *menu) +{ + SPItem *item=SP_ITEM(object); + GtkWidget *w; + + /* "Ungroup" */ + w = gtk_menu_item_new_with_mnemonic(_("_Ungroup")); + gtk_object_set_data(GTK_OBJECT(w), "desktop", desktop); + gtk_signal_connect(GTK_OBJECT(w), "activate", GTK_SIGNAL_FUNC(sp_item_group_ungroup_activate), item); + gtk_widget_show(w); + gtk_menu_append(GTK_MENU(menu), w); +} + +static void +sp_item_group_ungroup_activate(GtkMenuItem *menuitem, SPGroup *group) +{ + SPDesktop *desktop; + GSList *children; + + g_assert(SP_IS_GROUP(group)); + + desktop = (SPDesktop*)gtk_object_get_data(GTK_OBJECT(menuitem), "desktop"); + g_return_if_fail(desktop != NULL); + + children = NULL; + sp_item_group_ungroup(group, &children); + + SP_DT_SELECTION(desktop)->setList(children); + g_slist_free(children); +} + +/* SPAnchor */ + +static void sp_anchor_link_properties(GtkMenuItem *menuitem, SPAnchor *anchor); +static void sp_anchor_link_follow(GtkMenuItem *menuitem, SPAnchor *anchor); +static void sp_anchor_link_remove(GtkMenuItem *menuitem, SPAnchor *anchor); + +static void +sp_anchor_menu(SPObject *object, SPDesktop *desktop, GtkMenu *m) +{ + SPItem *item; + GtkWidget *w; + + item = (SPItem *) object; + + /* Link dialog */ + w = gtk_menu_item_new_with_mnemonic(_("Link _Properties")); + gtk_object_set_data(GTK_OBJECT(w), "desktop", desktop); + gtk_signal_connect(GTK_OBJECT(w), "activate", GTK_SIGNAL_FUNC(sp_anchor_link_properties), item); + gtk_widget_show(w); + gtk_menu_append(GTK_MENU(m), w); + /* Separator */ + w = gtk_menu_item_new(); + gtk_widget_show(w); + gtk_menu_append(GTK_MENU(m), w); + /* Select item */ + w = gtk_menu_item_new_with_mnemonic(_("_Follow Link")); + gtk_signal_connect(GTK_OBJECT(w), "activate", GTK_SIGNAL_FUNC(sp_anchor_link_follow), item); + gtk_widget_show(w); + gtk_menu_append(GTK_MENU(m), w); + /* Reset transformations */ + w = gtk_menu_item_new_with_mnemonic(_("_Remove Link")); + gtk_object_set_data(GTK_OBJECT(w), "desktop", desktop); + gtk_signal_connect(GTK_OBJECT(w), "activate", GTK_SIGNAL_FUNC(sp_anchor_link_remove), item); + gtk_widget_show(w); + gtk_menu_append(GTK_MENU(m), w); +} + +static void +sp_anchor_link_properties(GtkMenuItem *menuitem, SPAnchor *anchor) +{ + sp_object_attributes_dialog(SP_OBJECT(anchor), "Link"); +} + +static void +sp_anchor_link_follow(GtkMenuItem *menuitem, SPAnchor *anchor) +{ + g_return_if_fail(anchor != NULL); + g_return_if_fail(SP_IS_ANCHOR(anchor)); + + /* shell out to an external browser here */ +} + +static void +sp_anchor_link_remove(GtkMenuItem *menuitem, SPAnchor *anchor) +{ + GSList *children; + + g_return_if_fail(anchor != NULL); + g_return_if_fail(SP_IS_ANCHOR(anchor)); + + children = NULL; + sp_item_group_ungroup(SP_GROUP(anchor), &children); + + g_slist_free(children); +} + +/* Image */ + +static void sp_image_image_properties(GtkMenuItem *menuitem, SPAnchor *anchor); + +static void +sp_image_menu(SPObject *object, SPDesktop *desktop, GtkMenu *m) +{ + SPItem *item; + GtkWidget *w; + + item = (SPItem *) object; + + /* Link dialog */ + w = gtk_menu_item_new_with_mnemonic(_("Image _Properties")); + gtk_object_set_data(GTK_OBJECT(w), "desktop", desktop); + gtk_signal_connect(GTK_OBJECT(w), "activate", GTK_SIGNAL_FUNC(sp_image_image_properties), item); + gtk_widget_show(w); + gtk_menu_append(GTK_MENU(m), w); +} + +static void +sp_image_image_properties(GtkMenuItem *menuitem, SPAnchor *anchor) +{ + sp_object_attributes_dialog(SP_OBJECT(anchor), "Image"); +} + +/* SPShape */ + +static void +sp_shape_fill_settings(GtkMenuItem *menuitem, SPItem *item) +{ + SPDesktop *desktop; + + g_assert(SP_IS_ITEM(item)); + + desktop = (SPDesktop*)gtk_object_get_data(GTK_OBJECT(menuitem), "desktop"); + g_return_if_fail(desktop != NULL); + + if (SP_DT_SELECTION(desktop)->isEmpty()) { + SP_DT_SELECTION(desktop)->set(item); + } + + sp_object_properties_dialog(); +} + +static void +sp_shape_menu(SPObject *object, SPDesktop *desktop, GtkMenu *m) +{ + SPItem *item; + GtkWidget *w; + + item = (SPItem *) object; + + /* Item dialog */ + w = gtk_menu_item_new_with_mnemonic(_("_Fill and Stroke")); + gtk_object_set_data(GTK_OBJECT(w), "desktop", desktop); + gtk_signal_connect(GTK_OBJECT(w), "activate", GTK_SIGNAL_FUNC(sp_shape_fill_settings), item); + gtk_widget_show(w); + gtk_menu_append(GTK_MENU(m), w); +} + + +/* + 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 : diff --git a/src/object-ui.h b/src/object-ui.h new file mode 100644 index 000000000..3624b4d2e --- /dev/null +++ b/src/object-ui.h @@ -0,0 +1,32 @@ +#ifndef __SP_OBJECT_UI_H__ +#define __SP_OBJECT_UI_H__ + +/* + * Unser-interface related object extension + * + * Authors: + * Lauris Kaplinski + * + * This code is in public domain + */ + +#include + +#include "forward.h" + +/* Append object-specific part to context menu */ + +void sp_object_menu (SPObject *object, SPDesktop *desktop, GtkMenu *menu); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/path-chemistry.cpp b/src/path-chemistry.cpp new file mode 100644 index 000000000..6a5d876d6 --- /dev/null +++ b/src/path-chemistry.cpp @@ -0,0 +1,369 @@ +#define __SP_PATH_CHEMISTRY_C__ + +/* + * Here are handlers for modifying selections, specific to paths + * + * Authors: + * Lauris Kaplinski + * bulia byak + * + * Copyright (C) 1999-2004 Authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include +#endif +#include "xml/repr.h" +#include "svg/svg.h" +#include "display/curve.h" +#include +#include "sp-path.h" +#include "sp-text.h" +#include "sp-flowtext.h" +#include "libnr/nr-path.h" +#include "text-editing.h" +#include "style.h" +#include "inkscape.h" +#include "document.h" +#include "message-stack.h" +#include "selection.h" +#include "desktop-handles.h" + +/* Helper functions for sp_selected_path_to_curves */ +static void sp_selected_path_to_curves0 (gboolean do_document_done, guint32 text_grouping_policy); +static Inkscape::XML::Node * sp_selected_item_to_curved_repr(SPItem * item, guint32 text_grouping_policy); +enum { + /* Not used yet. This is the placeholder of Lauris's idea. */ + SP_TOCURVE_INTERACTIVE = 1 << 0, + SP_TOCURVE_GROUPING_BY_WORD = 1 << 1, + SP_TOCURVE_GROUPING_BY_LINE = 1 << 2, + SP_TOCURVE_GROUPING_BY_WHOLE = 1 << 3 +}; + +void +sp_selected_path_combine (void) +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + + Inkscape::Selection *selection = SP_DT_SELECTION (desktop); + GSList *items = (GSList *) selection->itemList(); + + if (g_slist_length (items) < 2) { + SP_DT_MSGSTACK(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select at least two objects to combine.")); + return; + } + + for (GSList *i = items; i != NULL; i = i->next) { + SPItem *item = (SPItem *) i->data; + if (!SP_IS_SHAPE (item) && !SP_IS_TEXT(item)) { + SP_DT_MSGSTACK(desktop)->flash(Inkscape::WARNING_MESSAGE, _("At least one of the objects is not a path, cannot combine.")); + return; + } + } + + Inkscape::XML::Node *parent = SP_OBJECT_REPR ((SPItem *) items->data)->parent(); + for (GSList *i = items; i != NULL; i = i->next) { + if ( SP_OBJECT_REPR ((SPItem *) i->data)->parent() != parent ) { + SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, _("You cannot combine objects from different groups or layers.")); + return; + } + } + + sp_selected_path_to_curves0 (FALSE, 0); + + items = (GSList *) selection->itemList(); + + items = g_slist_copy (items); + + items = g_slist_sort (items, (GCompareFunc) sp_item_repr_compare_position); + + // remember the position of the topmost object + gint topmost = (SP_OBJECT_REPR ((SPItem *) g_slist_last(items)->data))->position(); + + // remember the id of the bottomost object + const char *id = SP_OBJECT_REPR ((SPItem *) items->data)->attribute("id"); + + // FIXME: merge styles of combined objects instead of using the first one's style + gchar *style = g_strdup (SP_OBJECT_REPR ((SPItem *) items->data)->attribute("style")); + + GString *dstring = g_string_new(""); + for (GSList *i = items; i != NULL; i = i->next) { + + SPPath *path = (SPPath *) i->data; + SPCurve *c = sp_shape_get_curve (SP_SHAPE (path)); + + NArtBpath *abp = nr_artpath_affine(c->bpath, SP_ITEM(path)->transform); + sp_curve_unref (c); + gchar *str = sp_svg_write_path (abp); + nr_free (abp); + + dstring = g_string_append(dstring, str); + g_free (str); + + // if this is the bottommost object, + if (!strcmp (SP_OBJECT_REPR (path)->attribute("id"), id)) { + // delete it so that its clones don't get alerted; this object will be restored shortly, with the same id + SP_OBJECT (path)->deleteObject(false); + } else { + // delete the object for real, so that its clones can take appropriate action + SP_OBJECT (path)->deleteObject(); + } + + topmost --; + } + + g_slist_free (items); + + Inkscape::XML::Node *repr = sp_repr_new ("svg:path"); + + // restore id + repr->setAttribute("id", id); + + repr->setAttribute("style", style); + g_free (style); + + repr->setAttribute("d", dstring->str); + g_string_free (dstring, TRUE); + + // add the new group to the group members' common parent + parent->appendChild(repr); + + // move to the position of the topmost, reduced by the number of deleted items + repr->setPosition(topmost > 0 ? topmost + 1 : 0); + + sp_document_done (SP_DT_DOCUMENT (desktop)); + + selection->set(repr); + + Inkscape::GC::release(repr); +} + +void +sp_selected_path_break_apart (void) +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + + Inkscape::Selection *selection = SP_DT_SELECTION (desktop); + + if (selection->isEmpty()) { + SP_DT_MSGSTACK(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select path(s) to break apart.")); + return; + } + + bool did = false; + + for (GSList *items = g_slist_copy((GSList *) selection->itemList()); + items != NULL; + items = items->next) { + + SPItem *item = (SPItem *) items->data; + + if (!SP_IS_PATH (item)) + continue; + + SPPath *path = SP_PATH (item); + + SPCurve *curve = sp_shape_get_curve (SP_SHAPE (path)); + if (curve == NULL) + continue; + + did = true; + + Inkscape::XML::Node *parent = SP_OBJECT_REPR (item)->parent(); + gint pos = SP_OBJECT_REPR (item)->position(); + const char *id = SP_OBJECT_REPR (item)->attribute("id"); + + gchar *style = g_strdup (SP_OBJECT (item)->repr->attribute("style")); + + NArtBpath *abp = nr_artpath_affine (curve->bpath, (SP_ITEM (path))->transform); + + sp_curve_unref (curve); + + // it's going to resurrect as one of the pieces, so we delete without advertisement + SP_OBJECT (item)->deleteObject(false); + + curve = sp_curve_new_from_bpath (abp); + g_assert (curve != NULL); + + GSList *list = sp_curve_split (curve); + + sp_curve_unref (curve); + + for (GSList *l = g_slist_reverse(list); l != NULL; l = l->next) { + curve = (SPCurve *) l->data; + + Inkscape::XML::Node *repr = sp_repr_new ("svg:path"); + repr->setAttribute("style", style); + + gchar *str = sp_svg_write_path (curve->bpath); + repr->setAttribute("d", str); + g_free (str); + + // add the new repr to the parent + parent->appendChild(repr); + + // move to the saved position + repr->setPosition(pos > 0 ? pos : 0); + + // if it's the first one, restore id + if (l == list) + repr->setAttribute("id", id); + + selection->add(repr); + + Inkscape::GC::release(repr); + } + + g_slist_free (list); + g_free (style); + + } + + if (did) { + sp_document_done (SP_DT_DOCUMENT (desktop)); + } else { + SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, _("No path(s) to break apart in the selection.")); + return; + } +} + +/* This function is an entry point from GUI */ +void +sp_selected_path_to_curves (void) +{ + sp_selected_path_to_curves0(TRUE, SP_TOCURVE_INTERACTIVE); +} + +static void +sp_selected_path_to_curves0 (gboolean interactive, guint32 text_grouping_policy) +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + + Inkscape::Selection *selection = SP_DT_SELECTION (desktop); + + if (selection->isEmpty()) { + if (interactive) + SP_DT_MSGSTACK(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select object(s) to convert to path.")); + return; + } + + bool did = false; + + for (GSList *items = g_slist_copy((GSList *) selection->itemList()); + items != NULL; + items = items->next) { + + SPItem *item = SP_ITEM (items->data); + + Inkscape::XML::Node *repr = sp_selected_item_to_curved_repr (item, 0); + if (!repr) + continue; + + did = true; + + // remember the position of the item + gint pos = SP_OBJECT_REPR (item)->position(); + // remember parent + Inkscape::XML::Node *parent = SP_OBJECT_REPR (item)->parent(); + // remember id + const char *id = SP_OBJECT_REPR (item)->attribute("id"); + + selection->remove(item); + + // it's going to resurrect, so we delete without advertisement + SP_OBJECT (item)->deleteObject(false); + + // restore id + repr->setAttribute("id", id); + // add the new repr to the parent + parent->appendChild(repr); + // move to the saved position + repr->setPosition(pos > 0 ? pos : 0); + + selection->add(repr); + Inkscape::GC::release(repr); + } + + if (interactive) { + if (did) { + sp_document_done (SP_DT_DOCUMENT (desktop)); + } else { + SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, _("No objects to convert to path in the selection.")); + return; + } + } +} + +static Inkscape::XML::Node * +sp_selected_item_to_curved_repr(SPItem * item, guint32 text_grouping_policy) +{ + if (!item) + return NULL; + + SPCurve *curve = NULL; + if (SP_IS_SHAPE (item)) { + curve = sp_shape_get_curve (SP_SHAPE (item)); + } else if (SP_IS_TEXT (item) || SP_IS_FLOWTEXT (item)) { + curve = te_get_layout(item)->convertToCurves (); + } + + if (!curve) + return NULL; + + Inkscape::XML::Node *repr = sp_repr_new ("svg:path"); + /* Transformation */ + repr->setAttribute("transform", SP_OBJECT_REPR (item)->attribute("transform")); + /* Style */ + gchar *style_str = sp_style_write_difference (SP_OBJECT_STYLE (item), + SP_OBJECT_STYLE (SP_OBJECT_PARENT (item))); + repr->setAttribute("style", style_str); + g_free (style_str); + + /* Definition */ + gchar *def_str = sp_svg_write_path (curve->bpath); + repr->setAttribute("d", def_str); + g_free (def_str); + sp_curve_unref (curve); + return repr; +} + +void +sp_selected_path_reverse () +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + + Inkscape::Selection *selection = SP_DT_SELECTION (desktop); + GSList *items = (GSList *) selection->itemList(); + + if (g_slist_length (items) == 0) { + SP_DT_MSGSTACK(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select path(s) to reverse.")); + return; + } + + + bool did = false; + for (GSList *i = items; i != NULL; i = i->next) { + + if (!SP_IS_SHAPE (items->data)) + continue; + + did = true; + SPShape *shape = SP_SHAPE (items->data); + + SPCurve *rcurve = sp_curve_reverse (shape->curve); + + char *str = sp_svg_write_path (rcurve->bpath); + SP_OBJECT_REPR (shape)->setAttribute("d", str); + + sp_curve_unref (rcurve); + } + + if (did) { + sp_document_done (SP_DT_DOCUMENT (desktop)); + } else { + SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, _("No paths to reverse in the selection.")); + } +} diff --git a/src/path-chemistry.h b/src/path-chemistry.h new file mode 100644 index 000000000..41187bbe1 --- /dev/null +++ b/src/path-chemistry.h @@ -0,0 +1,35 @@ +#ifndef __PATH_CHEMISTRY_H__ +#define __PATH_CHEMISTRY_H__ + +/* + * Here are handlers for modifying selections, specific to paths + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "forward.h" + +void sp_selected_path_combine (void); +void sp_selected_path_break_apart (void); +void sp_selected_path_to_curves (void); + +void sp_selected_path_reverse (); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/path-prefix.h b/src/path-prefix.h new file mode 100644 index 000000000..89bc6309c --- /dev/null +++ b/src/path-prefix.h @@ -0,0 +1,83 @@ +/* + * Separate the inkscape paths from the prefix code, as that is kind of + * a separate package (binreloc) + * http://autopackage.org/downloads.html + * + * Since the directories set up by autoconf end up in config.h, we can't + * _change_ them, since config.h isn't protected by a set of + * one-time-include directives and is repeatedly re-included by some + * chains of .h files. As a result, nothing should refer to those + * define'd directories, and instead should use only the paths defined here. + * + */ +#ifndef _PATH_PREFIX_H_ +#define _PATH_PREFIX_H_ + +#include "require-config.h" // INKSCAPE_DATADIR +#include "prefix.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#ifdef ENABLE_BINRELOC +# define INKSCAPE_APPICONDIR BR_DATADIR( "/pixmaps" ) +# define INKSCAPE_EXTENSIONDIR BR_DATADIR( "/inkscape/extensions" ) +# define INKSCAPE_GRADIENTSDIR BR_DATADIR( "/inkscape/gradients" ) +# define INKSCAPE_PIXMAPDIR BR_DATADIR( "/inkscape/icons" ) +# define INKSCAPE_MARKERSDIR BR_DATADIR( "/inkscape/markers" ) +# define INKSCAPE_PALETTESDIR BR_DATADIR( "/inkscape/palettes" ) +# define INKSCAPE_PATTERNSDIR BR_DATADIR( "/inkscape/patterns" ) +# define INKSCAPE_SCREENSDIR BR_DATADIR( "/inkscape/screens" ) +# define INKSCAPE_TUTORIALSDIR BR_DATADIR( "/inkscape/tutorials" ) +# define INKSCAPE_PLUGINDIR BR_LIBDIR( "/inkscape/plugins" ) +# define INKSCAPE_TEMPLATESDIR BR_DATADIR( "/inkscape/templates" ) +# define INKSCAPE_UIDIR BR_DATADIR( "/inkscape/ui" ) +#else +# ifdef WIN32 +# define INKSCAPE_APPICONDIR "pixmaps" +# define INKSCAPE_EXTENSIONDIR "share\\extensions" +# define INKSCAPE_GRADIENTSDIR "share\\gradients" +# define INKSCAPE_PIXMAPDIR "share\\icons" +# define INKSCAPE_MARKERSDIR "share\\markers" +# define INKSCAPE_PALETTESDIR "share\\palettes" +# define INKSCAPE_PATTERNSDIR "share\\patterns" +# define INKSCAPE_SCREENSDIR "share\\screens" +# define INKSCAPE_TUTORIALSDIR "share\\tutorials" +# define INKSCAPE_PLUGINDIR "plugins" +# define INKSCAPE_TEMPLATESDIR "share\\templates" +# define INKSCAPE_UIDIR INKSCAPE_DATADIR "\\share\\ui" +# elif defined ENABLE_OSX_APP_LOCATIONS +# define INKSCAPE_APPICONDIR "Contents/Resources/pixmaps" +# define INKSCAPE_EXTENSIONDIR "Contents/Resources/extensions" +# define INKSCAPE_GRADIENTSDIR "Contents/Resources/gradients" +# define INKSCAPE_PIXMAPDIR "Contents/Resources/icons" +# define INKSCAPE_MARKERSDIR "Contents/Resources/markers" +# define INKSCAPE_PALETTESDIR "Contents/Resources/palettes" +# define INKSCAPE_PATTERNSDIR "Contents/Resources/patterns" +# define INKSCAPE_SCREENSDIR "Contents/Resources/screens" +# define INKSCAPE_TUTORIALSDIR "Contents/Resources/tutorials" +# define INKSCAPE_PLUGINDIR "Contents/Resources/plugins" +# define INKSCAPE_TEMPLATESDIR "Contents/Resources/templates" +# define INKSCAPE_UIDIR "Contents/Resources/ui" +# else +# define INKSCAPE_APPICONDIR INKSCAPE_DATADIR "/pixmaps" +# define INKSCAPE_EXTENSIONDIR INKSCAPE_DATADIR "/inkscape/extensions" +# define INKSCAPE_GRADIENTSDIR INKSCAPE_DATADIR "/inkscape/gradients" +# define INKSCAPE_PIXMAPDIR INKSCAPE_DATADIR "/inkscape/icons" +# define INKSCAPE_MARKERSDIR INKSCAPE_DATADIR "/inkscape/markers" +# define INKSCAPE_PALETTESDIR INKSCAPE_DATADIR "/inkscape/palettes" +# define INKSCAPE_PATTERNSDIR INKSCAPE_DATADIR "/inkscape/patterns" +# define INKSCAPE_SCREENSDIR INKSCAPE_DATADIR "/inkscape/screens" +# define INKSCAPE_TUTORIALSDIR INKSCAPE_DATADIR "/inkscape/tutorials" +# define INKSCAPE_PLUGINDIR INKSCAPE_LIBDIR "/inkscape/plugins" +# define INKSCAPE_TEMPLATESDIR INKSCAPE_DATADIR "/inkscape/templates" +# define INKSCAPE_UIDIR INKSCAPE_DATADIR "/inkscape/ui" +# endif +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _PATH_PREFIX_H_ */ diff --git a/src/pen-context.cpp b/src/pen-context.cpp new file mode 100644 index 000000000..95593f25b --- /dev/null +++ b/src/pen-context.cpp @@ -0,0 +1,1084 @@ +/** \file + * Pen event context implementation. + */ + +/* + * Authors: + * Lauris Kaplinski + * bulia byak + * + * Copyright (C) 2000 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2002 Lauris Kaplinski + * Copyright (C) 2004 Monash University + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +#include "pen-context.h" +#include "sp-namedview.h" +#include "sp-metrics.h" +#include "desktop.h" +#include "desktop-handles.h" +#include "selection.h" +#include "draw-anchor.h" +#include "message-stack.h" +#include "message-context.h" +#include "prefs-utils.h" +#include "sp-path.h" + +#include "pixmaps/cursor-pen.xpm" +#include "display/canvas-bpath.h" +#include "display/sp-ctrlline.h" +#include "display/sodipodi-ctrl.h" +#include +#include "libnr/n-art-bpath.h" +#include "helper/units.h" +#include "macros.h" +#include "context-fns.h" + + +static void sp_pen_context_class_init(SPPenContextClass *klass); +static void sp_pen_context_init(SPPenContext *pc); +static void sp_pen_context_dispose(GObject *object); + +static void sp_pen_context_setup(SPEventContext *ec); +static void sp_pen_context_finish(SPEventContext *ec); +static void sp_pen_context_set(SPEventContext *ec, gchar const *key, gchar const *val); +static gint sp_pen_context_root_handler(SPEventContext *ec, GdkEvent *event); + +static void spdc_pen_set_initial_point(SPPenContext *pc, NR::Point const p); +static void spdc_pen_set_subsequent_point(SPPenContext *pc, NR::Point const p, bool statusbar); +static void spdc_pen_set_ctrl(SPPenContext *pc, NR::Point const p, guint state); +static void spdc_pen_finish_segment(SPPenContext *pc, NR::Point p, guint state); + +static void spdc_pen_finish(SPPenContext *pc, gboolean closed); + +static gint pen_handle_button_press(SPPenContext *const pc, GdkEventButton const &bevent); +static gint pen_handle_motion_notify(SPPenContext *const pc, GdkEventMotion const &mevent); +static gint pen_handle_button_release(SPPenContext *const pc, GdkEventButton const &revent); +static gint pen_handle_2button_press(SPPenContext *const pc); +static gint pen_handle_key_press(SPPenContext *const pc, GdkEvent *event); +static void spdc_reset_colors(SPPenContext *pc); + + +static NR::Point pen_drag_origin_w(0, 0); +static bool pen_within_tolerance = false; + +static SPDrawContextClass *pen_parent_class; + +/** + * Register SPPenContext with Gdk and return its type. + */ +GType +sp_pen_context_get_type(void) +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPPenContextClass), + NULL, NULL, + (GClassInitFunc) sp_pen_context_class_init, + NULL, NULL, + sizeof(SPPenContext), + 4, + (GInstanceInitFunc) sp_pen_context_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_DRAW_CONTEXT, "SPPenContext", &info, (GTypeFlags)0); + } + return type; +} + +/** + * Initialize the SPPenContext vtable. + */ +static void +sp_pen_context_class_init(SPPenContextClass *klass) +{ + GObjectClass *object_class; + SPEventContextClass *event_context_class; + + object_class = (GObjectClass *) klass; + event_context_class = (SPEventContextClass *) klass; + + pen_parent_class = (SPDrawContextClass*)g_type_class_peek_parent(klass); + + object_class->dispose = sp_pen_context_dispose; + + event_context_class->setup = sp_pen_context_setup; + event_context_class->finish = sp_pen_context_finish; + event_context_class->set = sp_pen_context_set; + event_context_class->root_handler = sp_pen_context_root_handler; +} + +/** + * Callback to initialize SPPenContext object. + */ +static void +sp_pen_context_init(SPPenContext *pc) +{ + + SPEventContext *event_context = SP_EVENT_CONTEXT(pc); + + event_context->cursor_shape = cursor_pen_xpm; + event_context->hot_x = 4; + event_context->hot_y = 4; + + pc->npoints = 0; + pc->mode = SP_PEN_CONTEXT_MODE_CLICK; + pc->state = SP_PEN_CONTEXT_POINT; + + pc->c0 = NULL; + pc->c1 = NULL; + pc->cl0 = NULL; + pc->cl1 = NULL; +} + +/** + * Callback to destroy the SPPenContext object's members and itself. + */ +static void +sp_pen_context_dispose(GObject *object) +{ + SPPenContext *pc; + + pc = SP_PEN_CONTEXT(object); + + if (pc->c0) { + gtk_object_destroy(GTK_OBJECT(pc->c0)); + pc->c0 = NULL; + } + if (pc->c1) { + gtk_object_destroy(GTK_OBJECT(pc->c1)); + pc->c1 = NULL; + } + if (pc->cl0) { + gtk_object_destroy(GTK_OBJECT(pc->cl0)); + pc->cl0 = NULL; + } + if (pc->cl1) { + gtk_object_destroy(GTK_OBJECT(pc->cl1)); + pc->cl1 = NULL; + } + + G_OBJECT_CLASS(pen_parent_class)->dispose(object); +} + +/** + * Callback to initialize SPPenContext object. + */ +static void +sp_pen_context_setup(SPEventContext *ec) +{ + SPPenContext *pc; + + pc = SP_PEN_CONTEXT(ec); + + if (((SPEventContextClass *) pen_parent_class)->setup) { + ((SPEventContextClass *) pen_parent_class)->setup(ec); + } + + /* Pen indicators */ + pc->c0 = sp_canvas_item_new(SP_DT_CONTROLS(SP_EVENT_CONTEXT_DESKTOP(ec)), SP_TYPE_CTRL, "shape", SP_CTRL_SHAPE_CIRCLE, + "size", 4.0, "filled", 0, "fill_color", 0xff00007f, "stroked", 1, "stroke_color", 0x0000ff7f, NULL); + pc->c1 = sp_canvas_item_new(SP_DT_CONTROLS(SP_EVENT_CONTEXT_DESKTOP(ec)), SP_TYPE_CTRL, "shape", SP_CTRL_SHAPE_CIRCLE, + "size", 4.0, "filled", 0, "fill_color", 0xff00007f, "stroked", 1, "stroke_color", 0x0000ff7f, NULL); + pc->cl0 = sp_canvas_item_new(SP_DT_CONTROLS(SP_EVENT_CONTEXT_DESKTOP(ec)), SP_TYPE_CTRLLINE, NULL); + sp_ctrlline_set_rgba32(SP_CTRLLINE(pc->cl0), 0x0000007f); + pc->cl1 = sp_canvas_item_new(SP_DT_CONTROLS(SP_EVENT_CONTEXT_DESKTOP(ec)), SP_TYPE_CTRLLINE, NULL); + sp_ctrlline_set_rgba32(SP_CTRLLINE(pc->cl1), 0x0000007f); + + sp_canvas_item_hide(pc->c0); + sp_canvas_item_hide(pc->c1); + sp_canvas_item_hide(pc->cl0); + sp_canvas_item_hide(pc->cl1); + + sp_event_context_read(ec, "mode"); + + pc->anchor_statusbar = false; + + if (prefs_get_int_attribute("tools.freehand.pen", "selcue", 0) != 0) { + ec->enableSelectionCue(); + } +} + +/** + * Finalization callback. + */ +static void +sp_pen_context_finish(SPEventContext *ec) +{ + spdc_pen_finish(SP_PEN_CONTEXT(ec), FALSE); + + if (((SPEventContextClass *) pen_parent_class)->finish) { + ((SPEventContextClass *) pen_parent_class)->finish(ec); + } +} + +/** + * Callback that sets key to value in pen context. + */ +static void +sp_pen_context_set(SPEventContext *ec, gchar const *key, gchar const *val) +{ + SPPenContext *pc = SP_PEN_CONTEXT(ec); + + if (!strcmp(key, "mode")) { + if ( val && !strcmp(val, "drag") ) { + pc->mode = SP_PEN_CONTEXT_MODE_DRAG; + } else { + pc->mode = SP_PEN_CONTEXT_MODE_CLICK; + } + } +} + +/** + * Snaps new node relative to the previous node. + */ +static void +spdc_endpoint_snap(SPPenContext const *const pc, NR::Point &p, guint const state) +{ + if (pc->npoints > 0) { + spdc_endpoint_snap_rotation(pc, p, pc->p[0], state); + } + + spdc_endpoint_snap_free(pc, p, state); +} + +/** + * Snaps new node's handle relative to the new node. + */ +static void +spdc_endpoint_snap_handle(SPPenContext const *const pc, NR::Point &p, guint const state) +{ + g_return_if_fail(( pc->npoints == 2 || + pc->npoints == 5 )); + + spdc_endpoint_snap_rotation(pc, p, pc->p[pc->npoints - 2], state); + spdc_endpoint_snap_free(pc, p, state); +} + +/** + * Callback to handle all pen events. + */ +static gint +sp_pen_context_root_handler(SPEventContext *ec, GdkEvent *event) +{ + SPPenContext *const pc = SP_PEN_CONTEXT(ec); + + gint ret = FALSE; + + switch (event->type) { + case GDK_BUTTON_PRESS: + ret = pen_handle_button_press(pc, event->button); + break; + + case GDK_MOTION_NOTIFY: + ret = pen_handle_motion_notify(pc, event->motion); + break; + + case GDK_BUTTON_RELEASE: + ret = pen_handle_button_release(pc, event->button); + break; + + case GDK_2BUTTON_PRESS: + ret = pen_handle_2button_press(pc); + break; + + case GDK_KEY_PRESS: + ret = pen_handle_key_press(pc, event); + break; + + default: + break; + } + + if (!ret) { + gint (*const parent_root_handler)(SPEventContext *, GdkEvent *) + = ((SPEventContextClass *) pen_parent_class)->root_handler; + if (parent_root_handler) { + ret = parent_root_handler(ec, event); + } + } + + return ret; +} + +/** + * Handle mouse button press event. + */ +static gint pen_handle_button_press(SPPenContext *const pc, GdkEventButton const &bevent) +{ + gint ret = FALSE; + if (bevent.button == 1) { + + SPDrawContext * const dc = SP_DRAW_CONTEXT(pc); + SPDesktop * const desktop = SP_EVENT_CONTEXT_DESKTOP(dc); + + if (Inkscape::have_viable_layer(desktop, dc->_message_context) == false) { + return TRUE; + } + + NR::Point const event_w(bevent.x, bevent.y); + pen_drag_origin_w = event_w; + pen_within_tolerance = true; + + /* Test whether we hit any anchor. */ + SPDrawAnchor * const anchor = spdc_test_inside(pc, event_w); + + NR::Point const event_dt(desktop->w2d(event_w)); + switch (pc->mode) { + case SP_PEN_CONTEXT_MODE_CLICK: + /* In click mode we add point on release */ + switch (pc->state) { + case SP_PEN_CONTEXT_POINT: + case SP_PEN_CONTEXT_CONTROL: + case SP_PEN_CONTEXT_CLOSE: + break; + case SP_PEN_CONTEXT_STOP: + /* This is allowed, if we just cancelled curve */ + pc->state = SP_PEN_CONTEXT_POINT; + break; + default: + break; + } + break; + case SP_PEN_CONTEXT_MODE_DRAG: + switch (pc->state) { + case SP_PEN_CONTEXT_STOP: + /* This is allowed, if we just cancelled curve */ + case SP_PEN_CONTEXT_POINT: + if (pc->npoints == 0) { + + /* Set start anchor */ + pc->sa = anchor; + NR::Point p; + if (anchor) { + + /* Adjust point to anchor if needed */ + p = anchor->dp; + desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Continuing selected path")); + + } else { + + // This is the first click of a new curve; deselect item so that + // this curve is not combined with it (unless it is drawn from its + // anchor, which is handled by the sibling branch above) + Inkscape::Selection * const selection = SP_DT_SELECTION(desktop); + if (!(bevent.state & GDK_SHIFT_MASK)) { + + selection->clear(); + desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Creating new path")); + + } else if (selection->singleItem() && SP_IS_PATH(selection->singleItem())) { + + desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Appending to selected path")); + } + + /* Create green anchor */ + p = event_dt; + spdc_endpoint_snap(pc, p, bevent.state); + pc->green_anchor = sp_draw_anchor_new(pc, pc->green_curve, TRUE, p); + } + spdc_pen_set_initial_point(pc, p); + } else { + + /* Set end anchor */ + pc->ea = anchor; + NR::Point p; + if (anchor) { + + p = anchor->dp; + // we hit an anchor, will finish the curve (either with or without closing) + // in release handler + pc->state = SP_PEN_CONTEXT_CLOSE; + + if (pc->green_anchor && pc->green_anchor->active) { + // we clicked on the current curve start, so close it even if + // we drag a handle away from it + dc->green_closed = TRUE; + } + ret = TRUE; + break; + + } else { + + p = event_dt; + spdc_endpoint_snap(pc, p, bevent.state); /* Snap node only if not hitting anchor. */ + spdc_pen_set_subsequent_point(pc, p, true); + } + + } + pc->state = SP_PEN_CONTEXT_CONTROL; + ret = TRUE; + break; + case SP_PEN_CONTEXT_CONTROL: + g_warning("Button down in CONTROL state"); + break; + case SP_PEN_CONTEXT_CLOSE: + g_warning("Button down in CLOSE state"); + break; + default: + break; + } + break; + default: + break; + } + } else if (bevent.button == 3) { + if (pc->npoints != 0) { + spdc_pen_finish(pc, FALSE); + ret = TRUE; + } + } + + return ret; +} + +/** + * Handle motion_notify event. + */ +static gint +pen_handle_motion_notify(SPPenContext *const pc, GdkEventMotion const &mevent) +{ + gint ret = FALSE; + + if (mevent.state & GDK_BUTTON2_MASK || mevent.state & GDK_BUTTON3_MASK) { + // allow middle-button scrolling + return FALSE; + } + + NR::Point const event_w(mevent.x, + mevent.y); + if (pen_within_tolerance) { + gint const tolerance = prefs_get_int_attribute_limited("options.dragtolerance", + "value", 0, 0, 100); + if ( NR::LInfty( event_w - pen_drag_origin_w ) < tolerance ) { + return FALSE; // Do not drag if we're within tolerance from origin. + } + } + // Once the user has moved farther than tolerance from the original location + // (indicating they intend to move the object, not click), then always process the + // motion notify coordinates as given (no snapping back to origin) + pen_within_tolerance = false; + + SPDesktop *const dt = pc->desktop; + if ( ( mevent.state & GDK_BUTTON1_MASK ) && !pc->grab ) { + /* Grab mouse, so release will not pass unnoticed */ + pc->grab = SP_CANVAS_ITEM(dt->acetate); + sp_canvas_item_grab(pc->grab, ( GDK_KEY_PRESS_MASK | GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK ), + NULL, mevent.time); + } + + /* Find desktop coordinates */ + NR::Point p = dt->w2d(event_w); + + /* Test, whether we hit any anchor */ + SPDrawAnchor *anchor = spdc_test_inside(pc, event_w); + + switch (pc->mode) { + case SP_PEN_CONTEXT_MODE_CLICK: + switch (pc->state) { + case SP_PEN_CONTEXT_POINT: + if ( pc->npoints != 0 ) { + /* Only set point, if we are already appending */ + spdc_endpoint_snap(pc, p, mevent.state); + spdc_pen_set_subsequent_point(pc, p, true); + ret = TRUE; + } + break; + case SP_PEN_CONTEXT_CONTROL: + case SP_PEN_CONTEXT_CLOSE: + /* Placing controls is last operation in CLOSE state */ + spdc_endpoint_snap(pc, p, mevent.state); + spdc_pen_set_ctrl(pc, p, mevent.state); + ret = TRUE; + break; + case SP_PEN_CONTEXT_STOP: + /* This is perfectly valid */ + break; + default: + break; + } + break; + case SP_PEN_CONTEXT_MODE_DRAG: + switch (pc->state) { + case SP_PEN_CONTEXT_POINT: + if ( pc->npoints > 0 ) { + /* Only set point, if we are already appending */ + + if (!anchor) { /* Snap node only if not hitting anchor */ + spdc_endpoint_snap(pc, p, mevent.state); + } + + spdc_pen_set_subsequent_point(pc, p, !anchor); + + if (anchor && !pc->anchor_statusbar) { + pc->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Click or click and drag to close and finish the path.")); + pc->anchor_statusbar = true; + } else if (!anchor && pc->anchor_statusbar) { + pc->_message_context->clear(); + pc->anchor_statusbar = false; + } + + ret = TRUE; + } else { + if (anchor && !pc->anchor_statusbar) { + pc->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Click or click and drag to continue the path from this point.")); + pc->anchor_statusbar = true; + } else if (!anchor && pc->anchor_statusbar) { + pc->_message_context->clear(); + pc->anchor_statusbar = false; + } + } + break; + case SP_PEN_CONTEXT_CONTROL: + case SP_PEN_CONTEXT_CLOSE: + /* Placing controls is last operation in CLOSE state */ + + // snap the handle + spdc_endpoint_snap_handle(pc, p, mevent.state); + + spdc_pen_set_ctrl(pc, p, mevent.state); + ret = TRUE; + break; + case SP_PEN_CONTEXT_STOP: + /* This is perfectly valid */ + break; + default: + break; + } + break; + default: + break; + } + return ret; +} + +/** + * Handle mouse button release event. + */ +static gint +pen_handle_button_release(SPPenContext *const pc, GdkEventButton const &revent) +{ + gint ret = FALSE; + if ( revent.button == 1 ) { + + SPDrawContext *dc = SP_DRAW_CONTEXT (pc); + + NR::Point const event_w(revent.x, + revent.y); + /* Find desktop coordinates */ + NR::Point p = pc->desktop->w2d(event_w); + + /* Test whether we hit any anchor. */ + SPDrawAnchor *anchor = spdc_test_inside(pc, event_w); + + switch (pc->mode) { + case SP_PEN_CONTEXT_MODE_CLICK: + switch (pc->state) { + case SP_PEN_CONTEXT_POINT: + if ( pc->npoints == 0 ) { + /* Start new thread only with button release */ + if (anchor) { + p = anchor->dp; + } + pc->sa = anchor; + spdc_pen_set_initial_point(pc, p); + } else { + /* Set end anchor here */ + pc->ea = anchor; + if (anchor) { + p = anchor->dp; + } + } + pc->state = SP_PEN_CONTEXT_CONTROL; + ret = TRUE; + break; + case SP_PEN_CONTEXT_CONTROL: + /* End current segment */ + spdc_endpoint_snap(pc, p, revent.state); + spdc_pen_finish_segment(pc, p, revent.state); + pc->state = SP_PEN_CONTEXT_POINT; + ret = TRUE; + break; + case SP_PEN_CONTEXT_CLOSE: + /* End current segment */ + if (!anchor) { /* Snap node only if not hitting anchor */ + spdc_endpoint_snap(pc, p, revent.state); + } + spdc_pen_finish_segment(pc, p, revent.state); + spdc_pen_finish(pc, TRUE); + pc->state = SP_PEN_CONTEXT_POINT; + ret = TRUE; + break; + case SP_PEN_CONTEXT_STOP: + /* This is allowed, if we just cancelled curve */ + pc->state = SP_PEN_CONTEXT_POINT; + ret = TRUE; + break; + default: + break; + } + break; + case SP_PEN_CONTEXT_MODE_DRAG: + switch (pc->state) { + case SP_PEN_CONTEXT_POINT: + case SP_PEN_CONTEXT_CONTROL: + spdc_endpoint_snap(pc, p, revent.state); + spdc_pen_finish_segment(pc, p, revent.state); + break; + case SP_PEN_CONTEXT_CLOSE: + spdc_endpoint_snap(pc, p, revent.state); + spdc_pen_finish_segment(pc, p, revent.state); + if (pc->green_closed) { + // finishing at the start anchor, close curve + spdc_pen_finish(pc, TRUE); + } else { + // finishing at some other anchor, finish curve but not close + spdc_pen_finish(pc, FALSE); + } + break; + case SP_PEN_CONTEXT_STOP: + /* This is allowed, if we just cancelled curve */ + break; + default: + break; + } + pc->state = SP_PEN_CONTEXT_POINT; + ret = TRUE; + break; + default: + break; + } + + if (pc->grab) { + /* Release grab now */ + sp_canvas_item_ungrab(pc->grab, revent.time); + pc->grab = NULL; + } + + ret = TRUE; + + dc->green_closed = FALSE; + } + + return ret; +} + +static gint +pen_handle_2button_press(SPPenContext *const pc) +{ + gint ret = FALSE; + if (pc->npoints != 0) { + spdc_pen_finish(pc, FALSE); + ret = TRUE; + } + return ret; +} + +void +pen_lastpoint_move (SPPenContext *const pc, gdouble x, gdouble y) +{ + if (pc->npoints != 5) + return; + + // green + NArtBpath *const bpath = sp_curve_last_bpath(pc->green_curve); + if (bpath) { + if (bpath->code == NR_CURVETO) { + bpath->x2 += x; + bpath->y2 += y; + } + bpath->x3 += x; + bpath->y3 += y; + if (pc->green_bpaths && pc->green_bpaths->data) { + // remove old piecewise green canvasitems + while (pc->green_bpaths) { + gtk_object_destroy(GTK_OBJECT(pc->green_bpaths->data)); + pc->green_bpaths = g_slist_remove(pc->green_bpaths, pc->green_bpaths->data); + } + // one canvas bpath for all of green_curve + SPCanvasItem *cshape = sp_canvas_bpath_new(SP_DT_SKETCH(pc->desktop), pc->green_curve); + sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cshape), pc->green_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); + sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(cshape), 0, SP_WIND_RULE_NONZERO); + + pc->green_bpaths = g_slist_prepend(pc->green_bpaths, cshape); + } + } else { + // start anchor too + if (pc->green_anchor) { + pc->green_anchor->dp += NR::Point(x, y); + SP_CTRL(pc->green_anchor->ctrl)->moveto(pc->green_anchor->dp); + } + } + + // red + pc->p[0] += NR::Point(x, y); + pc->p[1] += NR::Point(x, y); + sp_curve_reset(pc->red_curve); + sp_curve_moveto(pc->red_curve, pc->p[0]); + sp_curve_curveto(pc->red_curve, pc->p[1], pc->p[2], pc->p[3]); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->red_curve); + + // handles + if (pc->p[0] != pc->p[1]) { + SP_CTRL(pc->c1)->moveto(pc->p[1]); + sp_ctrlline_set_coords(SP_CTRLLINE(pc->cl1), pc->p[0], pc->p[1]); + } + if (bpath && bpath->code == NR_CURVETO && NR::Point(bpath->x2, bpath->y2) != pc->p[0]) { + SP_CTRL(pc->c0)->moveto(NR::Point(bpath->x2, bpath->y2)); + sp_ctrlline_set_coords(SP_CTRLLINE(pc->cl0), NR::Point(bpath->x2, bpath->y2), pc->p[0]); + } +} + +void +pen_lastpoint_move_screen (SPPenContext *const pc, gdouble x, gdouble y) +{ + pen_lastpoint_move (pc, x / pc->desktop->current_zoom(), y / pc->desktop->current_zoom()); +} + +static gint +pen_handle_key_press(SPPenContext *const pc, GdkEvent *event) +{ + gint ret = FALSE; + gdouble const nudge = prefs_get_double_attribute_limited("options.nudgedistance", "value", 2, 0, 1000); // in px + + switch (get_group0_keyval (&event->key)) { + + case GDK_Left: // move last point left + case GDK_KP_Left: + case GDK_KP_4: + if (!MOD__CTRL) { // not ctrl + if (MOD__ALT) { // alt + if (MOD__SHIFT) pen_lastpoint_move_screen(pc, -10, 0); // shift + else pen_lastpoint_move_screen(pc, -1, 0); // no shift + } + else { // no alt + if (MOD__SHIFT) pen_lastpoint_move(pc, -10*nudge, 0); // shift + else pen_lastpoint_move(pc, -nudge, 0); // no shift + } + ret = TRUE; + } + break; + case GDK_Up: // move last point up + case GDK_KP_Up: + case GDK_KP_8: + if (!MOD__CTRL) { // not ctrl + if (MOD__ALT) { // alt + if (MOD__SHIFT) pen_lastpoint_move_screen(pc, 0, 10); // shift + else pen_lastpoint_move_screen(pc, 0, 1); // no shift + } + else { // no alt + if (MOD__SHIFT) pen_lastpoint_move(pc, 0, 10*nudge); // shift + else pen_lastpoint_move(pc, 0, nudge); // no shift + } + ret = TRUE; + } + break; + case GDK_Right: // move last point right + case GDK_KP_Right: + case GDK_KP_6: + if (!MOD__CTRL) { // not ctrl + if (MOD__ALT) { // alt + if (MOD__SHIFT) pen_lastpoint_move_screen(pc, 10, 0); // shift + else pen_lastpoint_move_screen(pc, 1, 0); // no shift + } + else { // no alt + if (MOD__SHIFT) pen_lastpoint_move(pc, 10*nudge, 0); // shift + else pen_lastpoint_move(pc, nudge, 0); // no shift + } + ret = TRUE; + } + break; + case GDK_Down: // move last point down + case GDK_KP_Down: + case GDK_KP_2: + if (!MOD__CTRL) { // not ctrl + if (MOD__ALT) { // alt + if (MOD__SHIFT) pen_lastpoint_move_screen(pc, 0, -10); // shift + else pen_lastpoint_move_screen(pc, 0, -1); // no shift + } + else { // no alt + if (MOD__SHIFT) pen_lastpoint_move(pc, 0, -10*nudge); // shift + else pen_lastpoint_move(pc, 0, -nudge); // no shift + } + ret = TRUE; + } + break; + + case GDK_Return: + case GDK_KP_Enter: + if (pc->npoints != 0) { + spdc_pen_finish(pc, FALSE); + ret = TRUE; + } + break; + case GDK_Escape: + if (pc->npoints != 0) { + // if drawing, cancel, otherwise pass it up for deselecting + pc->state = SP_PEN_CONTEXT_STOP; + spdc_reset_colors(pc); + sp_canvas_item_hide(pc->c0); + sp_canvas_item_hide(pc->c1); + sp_canvas_item_hide(pc->cl0); + sp_canvas_item_hide(pc->cl1); + ret = TRUE; + } + break; + case GDK_z: + case GDK_Z: + if (MOD__CTRL_ONLY && pc->npoints != 0) { + // if drawing, cancel, otherwise pass it up for undo + pc->state = SP_PEN_CONTEXT_STOP; + spdc_reset_colors(pc); + sp_canvas_item_hide(pc->c0); + sp_canvas_item_hide(pc->c1); + sp_canvas_item_hide(pc->cl0); + sp_canvas_item_hide(pc->cl1); + ret = TRUE; + } + break; + case GDK_BackSpace: + case GDK_Delete: + case GDK_KP_Delete: + if (sp_curve_is_empty(pc->green_curve)) { + /* Same as cancel */ + pc->state = SP_PEN_CONTEXT_STOP; + spdc_reset_colors(pc); + sp_canvas_item_hide(pc->c0); + sp_canvas_item_hide(pc->c1); + sp_canvas_item_hide(pc->cl0); + sp_canvas_item_hide(pc->cl1); + ret = TRUE; + } else { + /* Reset red curve */ + sp_curve_reset(pc->red_curve); + /* Destroy topmost green bpath */ + if (pc->green_bpaths) { + if (pc->green_bpaths->data) + gtk_object_destroy(GTK_OBJECT(pc->green_bpaths->data)); + pc->green_bpaths = g_slist_remove(pc->green_bpaths, pc->green_bpaths->data); + } + /* Get last segment */ + NArtBpath const *const p = SP_CURVE_BPATH(pc->green_curve); + gint const e = SP_CURVE_LENGTH(pc->green_curve); + if ( e < 2 ) { + g_warning("Green curve length is %d", e); + break; + } + pc->p[0] = p[e - 2].c(3); + pc->p[1] = p[e - 1].c(1); + NR::Point const pt(( pc->npoints < 4 + ? p[e - 1].c(3) + : pc->p[3] )); + pc->npoints = 2; + sp_curve_backspace(pc->green_curve); + sp_canvas_item_hide(pc->c0); + sp_canvas_item_hide(pc->c1); + sp_canvas_item_hide(pc->cl0); + sp_canvas_item_hide(pc->cl1); + pc->state = SP_PEN_CONTEXT_POINT; + spdc_pen_set_subsequent_point(pc, pt, true); + ret = TRUE; + } + break; + default: + break; + } + return ret; +} + +static void +spdc_reset_colors(SPPenContext *pc) +{ + /* Red */ + sp_curve_reset(pc->red_curve); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), NULL); + /* Blue */ + sp_curve_reset(pc->blue_curve); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->blue_bpath), NULL); + /* Green */ + while (pc->green_bpaths) { + gtk_object_destroy(GTK_OBJECT(pc->green_bpaths->data)); + pc->green_bpaths = g_slist_remove(pc->green_bpaths, pc->green_bpaths->data); + } + sp_curve_reset(pc->green_curve); + if (pc->green_anchor) { + pc->green_anchor = sp_draw_anchor_destroy(pc->green_anchor); + } + pc->sa = NULL; + pc->ea = NULL; + pc->npoints = 0; + pc->red_curve_is_valid = false; +} + + +static void +spdc_pen_set_initial_point(SPPenContext *const pc, NR::Point const p) +{ + g_assert( pc->npoints == 0 ); + + pc->p[0] = p; + pc->p[1] = p; + pc->npoints = 2; + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), NULL); +} + +static void +spdc_pen_set_subsequent_point(SPPenContext *const pc, NR::Point const p, bool statusbar) +{ + g_assert( pc->npoints != 0 ); + /* todo: Check callers to see whether 2 <= npoints is guaranteed. */ + + pc->p[2] = p; + pc->p[3] = p; + pc->p[4] = p; + pc->npoints = 5; + sp_curve_reset(pc->red_curve); + sp_curve_moveto(pc->red_curve, pc->p[0]); + bool is_curve; + if ( (pc->onlycurves) + || ( pc->p[1] != pc->p[0] ) ) + { + sp_curve_curveto(pc->red_curve, pc->p[1], p, p); + is_curve = true; + } else { + sp_curve_lineto(pc->red_curve, p); + is_curve = false; + } + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->red_curve); + + if (statusbar) { + // status text + SPDesktop *desktop = SP_EVENT_CONTEXT(pc)->desktop; + NR::Point rel = p - pc->p[0]; + GString *dist = SP_PX_TO_METRIC_STRING(NR::L2(rel), desktop->namedview->getDefaultMetric()); + double angle = atan2(rel[NR::Y], rel[NR::X]) * 180 / M_PI; + if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0) + angle = angle_to_compass (angle); + pc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s: angle %3.2f°, distance %s; with Ctrl to snap angle, Enter to finish the path"), is_curve? "Curve segment" : "Line segment", angle, dist->str); + g_string_free(dist, FALSE); + } +} + +static void +spdc_pen_set_ctrl(SPPenContext *const pc, NR::Point const p, guint const state) +{ + sp_canvas_item_show(pc->c1); + sp_canvas_item_show(pc->cl1); + + if ( pc->npoints == 2 ) { + pc->p[1] = p; + sp_canvas_item_hide(pc->c0); + sp_canvas_item_hide(pc->cl0); + SP_CTRL(pc->c1)->moveto(pc->p[1]); + sp_ctrlline_set_coords(SP_CTRLLINE(pc->cl1), pc->p[0], pc->p[1]); + + // status text + SPDesktop *desktop = SP_EVENT_CONTEXT(pc)->desktop; + NR::Point rel = p - pc->p[0]; + GString *dist = SP_PX_TO_METRIC_STRING(NR::L2(rel), desktop->namedview->getDefaultMetric()); + double angle = atan2(rel[NR::Y], rel[NR::X]) * 180 / M_PI; + if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0) + angle = angle_to_compass (angle); + pc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("Curve handle: angle %3.2f°, length %s; with Ctrl to snap angle"), angle, dist->str); + g_string_free(dist, FALSE); + + } else if ( pc->npoints == 5 ) { + pc->p[4] = p; + sp_canvas_item_show(pc->c0); + sp_canvas_item_show(pc->cl0); + bool is_symm = false; + if ( ( ( pc->mode == SP_PEN_CONTEXT_MODE_CLICK ) && ( state & GDK_CONTROL_MASK ) ) || + ( ( pc->mode == SP_PEN_CONTEXT_MODE_DRAG ) && !( state & GDK_SHIFT_MASK ) ) ) { + NR::Point delta = p - pc->p[3]; + pc->p[2] = pc->p[3] - delta; + is_symm = true; + sp_curve_reset(pc->red_curve); + sp_curve_moveto(pc->red_curve, pc->p[0]); + sp_curve_curveto(pc->red_curve, pc->p[1], pc->p[2], pc->p[3]); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->red_curve); + } + SP_CTRL(pc->c0)->moveto(pc->p[2]); + sp_ctrlline_set_coords(SP_CTRLLINE(pc->cl0), pc->p[3], pc->p[2]); + SP_CTRL(pc->c1)->moveto(pc->p[4]); + sp_ctrlline_set_coords(SP_CTRLLINE(pc->cl1), pc->p[3], pc->p[4]); + + // status text + SPDesktop *desktop = SP_EVENT_CONTEXT(pc)->desktop; + NR::Point rel = p - pc->p[3]; + GString *dist = SP_PX_TO_METRIC_STRING(NR::L2(rel), desktop->namedview->getDefaultMetric()); + double angle = atan2(rel[NR::Y], rel[NR::X]) * 180 / M_PI; + if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0) + angle = angle_to_compass (angle); + pc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s: angle %3.2f°, length %s; with Ctrl to snap angle, with Shift to move this handle only"), is_symm? "Curve handle, symmetric" : "Curve handle", angle, dist->str); + g_string_free(dist, FALSE); + + } else { + g_warning("Something bad happened - npoints is %d", pc->npoints); + } +} + +static void +spdc_pen_finish_segment(SPPenContext *const pc, NR::Point const p, guint const state) +{ + if (!sp_curve_empty(pc->red_curve)) { + sp_curve_append_continuous(pc->green_curve, pc->red_curve, 0.0625); + SPCurve *curve = sp_curve_copy(pc->red_curve); + /// \todo fixme: + SPCanvasItem *cshape = sp_canvas_bpath_new(SP_DT_SKETCH(pc->desktop), curve); + sp_curve_unref(curve); + sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cshape), pc->green_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); + + pc->green_bpaths = g_slist_prepend(pc->green_bpaths, cshape); + + pc->p[0] = pc->p[3]; + pc->p[1] = pc->p[4]; + pc->npoints = 2; + + sp_curve_reset(pc->red_curve); + } +} + +static void +spdc_pen_finish(SPPenContext *const pc, gboolean const closed) +{ + SPDesktop *const desktop = pc->desktop; + pc->_message_context->clear(); + desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Finishing pen")); + + sp_curve_reset(pc->red_curve); + spdc_concat_colors_and_flush(pc, closed); + pc->sa = NULL; + pc->ea = NULL; + + pc->npoints = 0; + pc->state = SP_PEN_CONTEXT_POINT; + + sp_canvas_item_hide(pc->c0); + sp_canvas_item_hide(pc->c1); + sp_canvas_item_hide(pc->cl0); + sp_canvas_item_hide(pc->cl1); + + if (pc->green_anchor) { + pc->green_anchor = sp_draw_anchor_destroy(pc->green_anchor); + } +} + + +/* + 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 : diff --git a/src/pen-context.h b/src/pen-context.h new file mode 100644 index 000000000..d0392134b --- /dev/null +++ b/src/pen-context.h @@ -0,0 +1,62 @@ +#ifndef SEEN_PEN_CONTEXT_H +#define SEEN_PEN_CONTEXT_H + +/** \file + * SPPenContext: a context for pen tool events. + */ + +#include "draw-context.h" + + +#define SP_TYPE_PEN_CONTEXT (sp_pen_context_get_type()) +#define SP_PEN_CONTEXT(o) (G_TYPE_CHECK_INSTANCE_CAST((o), SP_TYPE_PEN_CONTEXT, SPPenContext)) +#define SP_PEN_CONTEXT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), SP_TYPE_PEN_CONTEXT, SPPenContextClass)) +#define SP_IS_PEN_CONTEXT(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), SP_TYPE_PEN_CONTEXT)) +#define SP_IS_PEN_CONTEXT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE((k), SP_TYPE_PEN_CONTEXT)) + +enum { + SP_PEN_CONTEXT_POINT, + SP_PEN_CONTEXT_CONTROL, + SP_PEN_CONTEXT_CLOSE, + SP_PEN_CONTEXT_STOP +}; + +enum { + SP_PEN_CONTEXT_MODE_CLICK, + SP_PEN_CONTEXT_MODE_DRAG +}; + +/** + * SPPenContext: a context for pen tool events. + */ +struct SPPenContext : public SPDrawContext { + NR::Point p[5]; + + /** \invar npoints in {0, 2, 5}. */ + gint npoints; + + unsigned int mode : 1; + unsigned int state : 2; + unsigned int onlycurves : 1; + + SPCanvasItem *c0, *c1, *cl0, *cl1; +}; + +/// The SPPenContext vtable (empty). +struct SPPenContextClass : public SPEventContextClass { }; + +GType sp_pen_context_get_type(); + + +#endif /* !SEEN_PEN_CONTEXT_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:encoding=utf-8:textwidth=99 : diff --git a/src/pencil-context.cpp b/src/pencil-context.cpp new file mode 100644 index 000000000..a75b4c561 --- /dev/null +++ b/src/pencil-context.cpp @@ -0,0 +1,591 @@ +/** \file + * Pencil event context implementation. + */ + +/* + * Authors: + * Lauris Kaplinski + * bulia byak + * + * Copyright (C) 2000 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2002 Lauris Kaplinski + * Copyright (C) 2004 Monash University + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +#include "pencil-context.h" +#include "desktop.h" +#include "desktop-handles.h" +#include "selection.h" +#include "draw-anchor.h" +#include "message-stack.h" +#include "message-context.h" +#include "modifier-fns.h" +#include "sp-path.h" +#include "prefs-utils.h" +#include "snap.h" +#include "pixmaps/cursor-pencil.xpm" +#include "display/bezier-utils.h" +#include "display/canvas-bpath.h" +#include +#include "libnr/in-svg-plane.h" +#include "libnr/n-art-bpath.h" +#include "context-fns.h" + +static void sp_pencil_context_class_init(SPPencilContextClass *klass); +static void sp_pencil_context_init(SPPencilContext *pc); +static void sp_pencil_context_setup(SPEventContext *ec); +static void sp_pencil_context_dispose(GObject *object); + +static gint sp_pencil_context_root_handler(SPEventContext *event_context, GdkEvent *event); +static gint pencil_handle_button_press(SPPencilContext *const pc, GdkEventButton const &bevent); +static gint pencil_handle_motion_notify(SPPencilContext *const pc, GdkEventMotion const &mevent); +static gint pencil_handle_button_release(SPPencilContext *const pc, GdkEventButton const &revent); +static gint pencil_handle_key_press(SPPencilContext *const pc, guint const keyval, guint const state); + +static void spdc_set_startpoint(SPPencilContext *pc, NR::Point const p); +static void spdc_set_endpoint(SPPencilContext *pc, NR::Point const p); +static void spdc_finish_endpoint(SPPencilContext *pc); +static void spdc_add_freehand_point(SPPencilContext *pc, NR::Point p, guint state); +static void fit_and_split(SPPencilContext *pc); + + +static SPDrawContextClass *pencil_parent_class; + +/** + * Register SPPencilContext class with Gdk and return its type number. + */ +GType +sp_pencil_context_get_type() +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPPencilContextClass), + NULL, NULL, + (GClassInitFunc) sp_pencil_context_class_init, + NULL, NULL, + sizeof(SPPencilContext), + 4, + (GInstanceInitFunc) sp_pencil_context_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_DRAW_CONTEXT, "SPPencilContext", &info, (GTypeFlags)0); + } + return type; +} + +/** + * Initialize SPPencilContext vtable. + */ +static void +sp_pencil_context_class_init(SPPencilContextClass *klass) +{ + GObjectClass *object_class; + SPEventContextClass *event_context_class; + + object_class = (GObjectClass *) klass; + event_context_class = (SPEventContextClass *) klass; + + pencil_parent_class = (SPDrawContextClass*)g_type_class_peek_parent(klass); + + object_class->dispose = sp_pencil_context_dispose; + + event_context_class->setup = sp_pencil_context_setup; + event_context_class->root_handler = sp_pencil_context_root_handler; +} + +/** + * Callback to initialize SPPencilContext object. + */ +static void +sp_pencil_context_init(SPPencilContext *pc) +{ + SPEventContext *event_context = SP_EVENT_CONTEXT(pc); + + event_context->cursor_shape = cursor_pencil_xpm; + event_context->hot_x = 4; + event_context->hot_y = 4; + + pc->npoints = 0; + pc->state = SP_PENCIL_CONTEXT_IDLE; + pc->req_tangent = NR::Point(0, 0); +} + +/** + * Callback to setup SPPencilContext object. + */ +static void +sp_pencil_context_setup(SPEventContext *ec) +{ + if (prefs_get_int_attribute("tools.freehand.pencil", "selcue", 0) != 0) { + ec->enableSelectionCue(); + } + + if (((SPEventContextClass *) pencil_parent_class)->setup) { + ((SPEventContextClass *) pencil_parent_class)->setup(ec); + } + + SPPencilContext *const pc = SP_PENCIL_CONTEXT(ec); + pc->is_drawing = false; + + pc->anchor_statusbar = false; +} + +static void +sp_pencil_context_dispose(GObject *object) +{ + G_OBJECT_CLASS(pencil_parent_class)->dispose(object); +} + +/** Snaps new node relative to the previous node. */ +static void +spdc_endpoint_snap(SPPencilContext const *pc, NR::Point &p, guint const state) +{ + spdc_endpoint_snap_rotation(pc, p, pc->p[0], state); + spdc_endpoint_snap_free(pc, p, state); +} + +/** + * Callback for handling all pencil context events. + */ +gint +sp_pencil_context_root_handler(SPEventContext *const ec, GdkEvent *event) +{ + SPPencilContext *const pc = SP_PENCIL_CONTEXT(ec); + + gint ret = FALSE; + + switch (event->type) { + case GDK_BUTTON_PRESS: + ret = pencil_handle_button_press(pc, event->button); + break; + + case GDK_MOTION_NOTIFY: + ret = pencil_handle_motion_notify(pc, event->motion); + break; + + case GDK_BUTTON_RELEASE: + ret = pencil_handle_button_release(pc, event->button); + break; + + case GDK_KEY_PRESS: + ret = pencil_handle_key_press(pc, get_group0_keyval (&event->key), event->key.state); + break; + + default: + break; + } + + if (!ret) { + gint (*const parent_root_handler)(SPEventContext *, GdkEvent *) + = ((SPEventContextClass *) pencil_parent_class)->root_handler; + if (parent_root_handler) { + ret = parent_root_handler(ec, event); + } + } + + return ret; +} + +static gint +pencil_handle_button_press(SPPencilContext *const pc, GdkEventButton const &bevent) +{ + gint ret = FALSE; + if ( bevent.button == 1 ) { + + SPDrawContext *dc = SP_DRAW_CONTEXT (pc); + SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(dc); + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + + if (Inkscape::have_viable_layer(desktop, dc->_message_context) == false) { + return TRUE; + } + + NR::Point const button_w(bevent.x, bevent.y); + + /* Find desktop coordinates */ + NR::Point p = pc->desktop->w2d(button_w); + + /* Test whether we hit any anchor. */ + SPDrawAnchor *anchor = spdc_test_inside(pc, button_w); + + switch (pc->state) { + case SP_PENCIL_CONTEXT_ADDLINE: + /* Current segment will be finished with release */ + ret = TRUE; + break; + default: + /* Set first point of sequence */ + if (anchor) { + p = anchor->dp; + desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Continuing selected path")); + } else { + + if (!(bevent.state & GDK_SHIFT_MASK)) { + + // This is the first click of a new curve; deselect item so that + // this curve is not combined with it (unless it is drawn from its + // anchor, which is handled by the sibling branch above) + selection->clear(); + desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Creating new path")); + SnapManager const m(desktop->namedview); + p = m.freeSnap(Inkscape::Snapper::BBOX_POINT | Inkscape::Snapper::SNAP_POINT, p, NULL).getPoint(); + } else if (selection->singleItem() && SP_IS_PATH(selection->singleItem())) { + desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Appending to selected path")); + } + } + pc->sa = anchor; + spdc_set_startpoint(pc, p); + ret = TRUE; + break; + } + + pc->is_drawing = true; + } + return ret; +} + +static gint +pencil_handle_motion_notify(SPPencilContext *const pc, GdkEventMotion const &mevent) +{ + gint ret = FALSE; + SPDesktop *const dt = pc->desktop; + + if (mevent.state & GDK_BUTTON2_MASK || mevent.state & GDK_BUTTON3_MASK) { + // allow middle-button scrolling + return FALSE; + } + + if ( ( mevent.state & GDK_BUTTON1_MASK ) && !pc->grab && pc->is_drawing) { + /* Grab mouse, so release will not pass unnoticed */ + pc->grab = SP_CANVAS_ITEM(dt->acetate); + sp_canvas_item_grab(pc->grab, ( GDK_KEY_PRESS_MASK | GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK ), + NULL, mevent.time); + } + + /* Find desktop coordinates */ + NR::Point p = dt->w2d(NR::Point(mevent.x, mevent.y)); + + /* Test whether we hit any anchor. */ + SPDrawAnchor *anchor = spdc_test_inside(pc, NR::Point(mevent.x, mevent.y)); + + switch (pc->state) { + case SP_PENCIL_CONTEXT_ADDLINE: + /* Set red endpoint */ + if (anchor) { + p = anchor->dp; + } else { + spdc_endpoint_snap(pc, p, mevent.state); + } + spdc_set_endpoint(pc, p); + ret = TRUE; + break; + default: + /* We may be idle or already freehand */ + if ( mevent.state & GDK_BUTTON1_MASK && pc->is_drawing ) { + pc->state = SP_PENCIL_CONTEXT_FREEHAND; + if ( !pc->sa && !pc->green_anchor ) { + /* Create green anchor */ + pc->green_anchor = sp_draw_anchor_new(pc, pc->green_curve, TRUE, pc->p[0]); + } + /** \todo + * fixme: I am not sure whether we want to snap to anchors + * in middle of freehand (Lauris) + */ + if (anchor) { + p = anchor->dp; + } else if ((mevent.state & GDK_SHIFT_MASK) == 0) { + SnapManager const m(dt->namedview); + p = m.freeSnap(Inkscape::Snapper::BBOX_POINT | Inkscape::Snapper::SNAP_POINT, p, NULL).getPoint(); + } + if ( pc->npoints != 0 ) { // buttonpress may have happened before we entered draw context! + spdc_add_freehand_point(pc, p, mevent.state); + ret = TRUE; + } + + if (anchor && !pc->anchor_statusbar) { + pc->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Release here to close and finish the path.")); + pc->anchor_statusbar = true; + } else if (!anchor && pc->anchor_statusbar) { + pc->_message_context->clear(); + pc->anchor_statusbar = false; + } else if (!anchor) { + pc->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Drawing a freehand path")); + } + + } else { + if (anchor && !pc->anchor_statusbar) { + pc->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Drag to continue the path from this point.")); + pc->anchor_statusbar = true; + } else if (!anchor && pc->anchor_statusbar) { + pc->_message_context->clear(); + pc->anchor_statusbar = false; + } + } + break; + } + return ret; +} + +static gint +pencil_handle_button_release(SPPencilContext *const pc, GdkEventButton const &revent) +{ + gint ret = FALSE; + + if ( revent.button == 1 && pc->is_drawing) { + SPDesktop *const dt = pc->desktop; + + pc->is_drawing = false; + + /* Find desktop coordinates */ + NR::Point p = dt->w2d(NR::Point(revent.x, revent.y)); + + /* Test whether we hit any anchor. */ + SPDrawAnchor *anchor = spdc_test_inside(pc, NR::Point(revent.x, + revent.y)); + + switch (pc->state) { + case SP_PENCIL_CONTEXT_IDLE: + /* Releasing button in idle mode means single click */ + /* We have already set up start point/anchor in button_press */ + pc->state = SP_PENCIL_CONTEXT_ADDLINE; + ret = TRUE; + break; + case SP_PENCIL_CONTEXT_ADDLINE: + /* Finish segment now */ + if (anchor) { + p = anchor->dp; + } else { + spdc_endpoint_snap(pc, p, revent.state); + } + pc->ea = anchor; + spdc_set_endpoint(pc, p); + spdc_finish_endpoint(pc); + pc->state = SP_PENCIL_CONTEXT_IDLE; + ret = TRUE; + break; + case SP_PENCIL_CONTEXT_FREEHAND: + /* Finish segment now */ + /// \todo fixme: Clean up what follows (Lauris) + if (anchor) { + p = anchor->dp; + } + pc->ea = anchor; + /* Write curves to object */ + + dt->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Finishing freehand")); + + spdc_concat_colors_and_flush(pc, FALSE); + pc->sa = NULL; + pc->ea = NULL; + if (pc->green_anchor) { + pc->green_anchor = sp_draw_anchor_destroy(pc->green_anchor); + } + pc->state = SP_PENCIL_CONTEXT_IDLE; + ret = TRUE; + break; + default: + break; + } + + if (pc->grab) { + /* Release grab now */ + sp_canvas_item_ungrab(pc->grab, revent.time); + pc->grab = NULL; + } + + pc->grab = NULL; + ret = TRUE; + } + return ret; +} + +static gint +pencil_handle_key_press(SPPencilContext *const pc, guint const keyval, guint const state) +{ + gint ret = FALSE; + switch (keyval) { + case GDK_Up: + case GDK_Down: + case GDK_KP_Up: + case GDK_KP_Down: + // Prevent the zoom field from activation. + if (!mod_ctrl_only(state)) { + ret = TRUE; + } + break; + default: + break; + } + return ret; +} + +/** + * Reset points and set new starting point. + */ +static void +spdc_set_startpoint(SPPencilContext *const pc, NR::Point const p) +{ + pc->npoints = 0; + pc->red_curve_is_valid = false; + if (in_svg_plane(p)) { + pc->p[pc->npoints++] = p; + } +} + +/** + * Change moving endpoint position. + *
    + *
  • Ctrl constrains to moving to H/V direction, snapping in given direction. + *
  • Otherwise we snap freely to whatever attractors are available. + *
+ * + * Number of points is (re)set to 2 always, 2nd point is modified. + * We change RED curve. + */ +static void +spdc_set_endpoint(SPPencilContext *const pc, NR::Point const p) +{ + if (pc->npoints == 0) { + return; + /* May occur if first point wasn't in SVG plane (e.g. weird w2d transform, perhaps from bad + * zoom setting). + */ + } + g_return_if_fail( pc->npoints > 0 ); + + sp_curve_reset(pc->red_curve); + if ( ( p == pc->p[0] ) + || !in_svg_plane(p) ) + { + pc->npoints = 1; + } else { + pc->p[1] = p; + pc->npoints = 2; + + sp_curve_moveto(pc->red_curve, pc->p[0]); + sp_curve_lineto(pc->red_curve, pc->p[1]); + pc->red_curve_is_valid = true; + + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->red_curve); + } +} + +/** + * Finalize addline. + * + * \todo + * fixme: I'd like remove red reset from concat colors (lauris). + * Still not sure, how it will make most sense. + */ +static void +spdc_finish_endpoint(SPPencilContext *const pc) +{ + if ( ( SP_CURVE_LENGTH(pc->red_curve) != 2 ) + || ( SP_CURVE_SEGMENT(pc->red_curve, 0)->c(3) == + SP_CURVE_SEGMENT(pc->red_curve, 1)->c(3) ) ) + { + sp_curve_reset(pc->red_curve); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), NULL); + } else { + /* Write curves to object. */ + spdc_concat_colors_and_flush(pc, FALSE); + pc->sa = NULL; + pc->ea = NULL; + } +} + +static void +spdc_add_freehand_point(SPPencilContext *pc, NR::Point p, guint state) +{ + g_assert( pc->npoints > 0 ); + g_return_if_fail(unsigned(pc->npoints) < G_N_ELEMENTS(pc->p)); + + if ( ( p != pc->p[ pc->npoints - 1 ] ) + && in_svg_plane(p) ) + { + pc->p[pc->npoints++] = p; + fit_and_split(pc); + } +} + +static inline double +square(double const x) +{ + return x * x; +} + +static void +fit_and_split(SPPencilContext *pc) +{ + g_assert( pc->npoints > 1 ); + + double const tolerance_sq = square( NR::expansion(pc->desktop->w2d()) + * prefs_get_double_attribute_limited("tools.freehand.pencil", + "tolerance", 10.0, 1.0, 100.0) ); + + NR::Point b[4]; + g_assert(is_zero(pc->req_tangent) + || is_unit_vector(pc->req_tangent)); + NR::Point const tHatEnd(0, 0); + int const n_segs = sp_bezier_fit_cubic_full(b, NULL, pc->p, pc->npoints, + pc->req_tangent, tHatEnd, tolerance_sq, 1); + if ( n_segs > 0 + && unsigned(pc->npoints) < G_N_ELEMENTS(pc->p) ) + { + /* Fit and draw and reset state */ + sp_curve_reset(pc->red_curve); + sp_curve_moveto(pc->red_curve, b[0]); + sp_curve_curveto(pc->red_curve, b[1], b[2], b[3]); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->red_curve); + pc->red_curve_is_valid = true; + } else { + /* Fit and draw and copy last point */ + + g_assert(!sp_curve_empty(pc->red_curve)); + + /* Set up direction of next curve. */ + { + NArtBpath const &last_seg = *sp_curve_last_bpath(pc->red_curve); + pc->p[0] = last_seg.c(3); + pc->npoints = 1; + g_assert( last_seg.code == NR_CURVETO ); + /* Relevance: validity of last_seg.c(2). */ + NR::Point const req_vec( pc->p[0] - last_seg.c(2) ); + pc->req_tangent = ( ( NR::is_zero(req_vec) || !in_svg_plane(req_vec) ) + ? NR::Point(0, 0) + : NR::unit_vector(req_vec) ); + } + + sp_curve_append_continuous(pc->green_curve, pc->red_curve, 0.0625); + SPCurve *curve = sp_curve_copy(pc->red_curve); + + /// \todo fixme: + SPCanvasItem *cshape = sp_canvas_bpath_new(SP_DT_SKETCH(pc->desktop), curve); + sp_curve_unref(curve); + sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cshape), pc->green_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); + + pc->green_bpaths = g_slist_prepend(pc->green_bpaths, cshape); + + pc->red_curve_is_valid = false; + } +} + + +/* + 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 : diff --git a/src/pencil-context.h b/src/pencil-context.h new file mode 100644 index 000000000..60519c269 --- /dev/null +++ b/src/pencil-context.h @@ -0,0 +1,52 @@ +#ifndef SEEN_PENCIL_CONTEXT_H +#define SEEN_PENCIL_CONTEXT_H + +/** \file + * SPPencilContext: a context for pencil tool events + */ + +#include "draw-context.h" + + +#define SP_TYPE_PENCIL_CONTEXT (sp_pencil_context_get_type()) +#define SP_PENCIL_CONTEXT(o) (G_TYPE_CHECK_INSTANCE_CAST((o), SP_TYPE_PENCIL_CONTEXT, SPPencilContext)) +#define SP_PENCIL_CONTEXT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), SP_TYPE_PENCIL_CONTEXT, SPPencilContextClass)) +#define SP_IS_PENCIL_CONTEXT(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), SP_TYPE_PENCIL_CONTEXT)) +#define SP_IS_PENCIL_CONTEXT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE((k), SP_TYPE_PENCIL_CONTEXT)) + +enum PencilState { + SP_PENCIL_CONTEXT_IDLE, + SP_PENCIL_CONTEXT_ADDLINE, + SP_PENCIL_CONTEXT_FREEHAND +}; + +/** + * SPPencilContext: a context for pencil tool events + */ +struct SPPencilContext : public SPDrawContext { + NR::Point p[16]; + gint npoints; + PencilState state; + NR::Point req_tangent; + + bool is_drawing; +}; + +/// The SPPencilContext vtable (empty). +struct SPPencilContextClass : public SPEventContextClass { }; + +GType sp_pencil_context_get_type(); + + +#endif /* !SEEN_PENCIL_CONTEXT_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:encoding=utf-8:textwidth=99 : diff --git a/src/pixmaps/cursor-arc.xpm b/src/pixmaps/cursor-arc.xpm new file mode 100644 index 000000000..37852a190 --- /dev/null +++ b/src/pixmaps/cursor-arc.xpm @@ -0,0 +1,38 @@ +/* XPM */ +static char * cursor_arc_xpm[] = { +"32 32 3 1", +" c None", +". c #FFFFFF", +"+ c #000000", +" ... ", +" .+. ", +" .+. ", +"....+.... ", +".+++ +++. ", +"....+.... ", +" .+. ", +" .+. ..... ", +" ... .+++.... ", +" .+..+++.. ", +" .+.....+.. ", +" .+......+.. ", +" ..+.......+. ", +" ...++.......+. ", +" ...++.........+. ", +" ...++..........+.. ", +" .++...........+.. ", +" ...++......+++.. ", +" ...++++++.... ", +" ........ ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" "}; diff --git a/src/pixmaps/cursor-arrow.xpm b/src/pixmaps/cursor-arrow.xpm new file mode 100644 index 000000000..e629d83d2 --- /dev/null +++ b/src/pixmaps/cursor-arrow.xpm @@ -0,0 +1,38 @@ +/* XPM */ +static char * cursor_arrow_xpm[] = { +"32 32 3 1", +" c None", +". c #FFFFFF", +"+ c #000000", +" .. ", +".++.. ", +".+ ++.. ", +" .+ ++.. ", +" .+ ++.. ", +" .+ ++. ", +" .+ +. ", +" .+ +. ", +" .+ +. ", +" .+ +. ", +" .+ + +. ", +" .++.+ +. ", +" .. .+ +. ", +" .+ +. ", +" .+ +. ", +" .+ +. ", +" .+ +. ", +" .+ +. ", +" .+. ", +" . ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" "}; diff --git a/src/pixmaps/cursor-calligraphy.xpm b/src/pixmaps/cursor-calligraphy.xpm new file mode 100644 index 000000000..ee0a5b876 --- /dev/null +++ b/src/pixmaps/cursor-calligraphy.xpm @@ -0,0 +1,38 @@ +/* XPM */ +static char * cursor_calligraphy_xpm[] = { +"32 32 3 1", +" c None", +". c #FFFFFF", +"+ c #000000", +" ... ", +" .+. ", +" .+. ", +"....+.... ", +".+++ +++. ", +"....+.... ", +" .+. . ", +" .+. .+. ", +" ... .+. ", +" .+. ", +" .+. ", +" .++. ", +" .++. ", +" .++. ... ", +" .++. ..+++. ", +" .+++. .++++++. ", +" .++++...+++++++. ", +" .++++++++++++++. ", +" .+++++++...++++. ", +" .++++++. .++++. ", +" .+++.. .++. ", +" ... .++. ", +" .++. ", +" .++. ", +" .++. ", +" .+. ", +" .+. ", +" .+. ", +" .+. ", +" . ", +" ", +" "}; diff --git a/src/pixmaps/cursor-connector.xpm b/src/pixmaps/cursor-connector.xpm new file mode 100644 index 000000000..ef35849ed --- /dev/null +++ b/src/pixmaps/cursor-connector.xpm @@ -0,0 +1,39 @@ +/* XPM */ +static char *cursor_connector_xpm[] = { +"32 32 3 1", +" c None", +". c #FFFFFF", +"+ c #000000", +". ", +".. ", +".+. ", +".++. ", +".+++. ", +".++++. ", +".+++++. ", +".++++++. ", +".+++++++. ", +".++++++++. ", +".+++++.... ", +".++.++. ", +".+. .++. ", +".. .++. ", +" .++. ", +" .++. ... ", +" .. .+. ", +" .+. ", +" .+. ", +" ...........+. ", +" .+++++++++++. ", +" .+........... ", +" .+. ", +" .+. ", +" ...+... ", +" .+++++. ", +" .+++. ", +" .+. ", +" . ", +" ", +" ", +" ", +}; diff --git a/src/pixmaps/cursor-dropper.xpm b/src/pixmaps/cursor-dropper.xpm new file mode 100644 index 000000000..ef0184697 --- /dev/null +++ b/src/pixmaps/cursor-dropper.xpm @@ -0,0 +1,38 @@ +/* XPM */ +static char * cursor_dropper_xpm[] = { +"32 32 3 1", +" c None", +". c #FFFFFF", +"+ c #000000", +" . ", +" .+. ", +" .+. ", +" .+. ", +" .+. ", +" .+. ", +" ..... ..... ", +".+++++ +++++. ", +" ..... ..... ", +" .+. ", +" .+. ", +" .+. ", +" .+. .... ", +" .+. .+++. ", +" . .+.++. ", +" .+..++. ", +" .+..++. ", +" .+..++. ", +" .+..++. ", +" .+..++. . ", +" .+..++.+. ", +" .+..++++. ", +" .+.++++. ", +" .++++++.. ", +" .+++++++++. ", +" .+..++++++. ", +" ..+.+++++. ", +" .+.++++. ", +" .+..++. ", +" .+++. ", +" ... ", +" "}; diff --git a/src/pixmaps/cursor-ellipse.xpm b/src/pixmaps/cursor-ellipse.xpm new file mode 100644 index 000000000..bdc175240 --- /dev/null +++ b/src/pixmaps/cursor-ellipse.xpm @@ -0,0 +1,38 @@ +/* XPM */ +static char * cursor_ellipse_xpm[] = { +"32 32 3 1", +" c None", +". c #FFFFFF", +"+ c #000000", +" ... ", +" .+. ", +" .+. ", +"....+.... ", +".+++ +++. ", +"....+.... ", +" .+. ", +" .+. ....... ", +" ... ....+++++.... ", +" ..+++.....+++.. ", +" ..+...........+.. ", +" ..+.............+.. ", +" .+...............+. ", +" .+...............+. ", +" .+...............+. ", +" ..+.............+.. ", +" ..+...........+.. ", +" ..+++.....+++.. ", +" ....+++++.... ", +" ....... ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" "}; diff --git a/src/pixmaps/cursor-gradient.xpm b/src/pixmaps/cursor-gradient.xpm new file mode 100644 index 000000000..b17873f58 --- /dev/null +++ b/src/pixmaps/cursor-gradient.xpm @@ -0,0 +1,38 @@ +/* XPM */ +static char * cursor_gradient_xpm[] = { +"32 32 3 1", +" c None", +". c #FFFFFF", +"+ c #000000", +" ... ", +" .+. ", +" .+. ", +"....+.... ", +".+++ +++. ", +"....+.... ", +" .+. ", +" .+. ..... ", +" ... .+++. ", +" .+.+. ", +" .+++. ", +" .+. ", +" .+. ", +" .+. ", +" .+. ", +" .+++. ", +" .+.+. ", +" .+++. ", +" ..... ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" "}; diff --git a/src/pixmaps/cursor-node-d.xpm b/src/pixmaps/cursor-node-d.xpm new file mode 100644 index 000000000..326b64105 --- /dev/null +++ b/src/pixmaps/cursor-node-d.xpm @@ -0,0 +1,38 @@ +/* XPM */ +static char * cursor_node_d_xpm[] = { +"32 32 3 1", +" g None", +". g #FFFFFF", +"+ g #000000", +" . ", +".+. ", +" .+. ", +" .++. ", +" .++. ", +" .+++. ", +" .+++. ", +" .++++. ", +" .++++. ", +" .+++++. ", +" .+++++. ", +" .++++++. ", +" .+++++. ", +" .+++.. ", +" .+. ++ ++ ", +" . +..+..+ ", +" ++..+..++ ", +" +.+..+..+.+ ", +" +.+..+..+.+ ", +" +.+..+..+.+ ", +" +.........+ ", +" +.........+ ", +" +........+ ", +" +........+ ", +" +......+ ", +" +......+ ", +" +.....+ ", +" +.....+ ", +" +++++++ ", +" ", +" ", +" "}; diff --git a/src/pixmaps/cursor-node-m.xpm b/src/pixmaps/cursor-node-m.xpm new file mode 100644 index 000000000..9b2479a28 --- /dev/null +++ b/src/pixmaps/cursor-node-m.xpm @@ -0,0 +1,38 @@ +/* XPM */ +static char * cursor_node_m_xpm[] = { +"32 32 3 1", +" g None", +". g #FFFFFF", +"+ g #000000", +" . ", +".+. ", +" .+. ", +" .++. ", +" .++. ", +" .+++. ", +" .+++. ", +" .++++. ", +" .++++. ", +" .+++++. ", +" .+++++. ", +" .++++++. ++++ ", +" .+++++. +.+..+ ", +" .+++.. +..+..++ ", +" .+. +..+..+.+ ", +" . + +..+..+..+ ", +" +.++..+..+..+ ", +" +..+..+..+..+ ", +" +..+..+..+..+ ", +" +..+..+..+..+ ", +" +...........+ ", +" +..........+ ", +" +.........+ ", +" +........+ ", +" +.......+ ", +" +......+ ", +" +.....+ ", +" +.....+ ", +" +++++++ ", +" ", +" ", +" "}; diff --git a/src/pixmaps/cursor-node.xpm b/src/pixmaps/cursor-node.xpm new file mode 100644 index 000000000..297dae6dd --- /dev/null +++ b/src/pixmaps/cursor-node.xpm @@ -0,0 +1,38 @@ +/* XPM */ +static char * cursor_node_xpm[] = { +"32 32 3 1", +" c None", +". c #FFFFFF", +"+ c #000000", +" . ", +".+. ", +" .+. ", +" .++. ", +" .++. ", +" .+++. ", +" .+++. ", +" .++++. ", +" .++++. ", +" .+++++. ", +" .+++++. ", +" .++++++. ", +" .+++++. ", +" .+++.. ", +" .+. ", +" . ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" "}; diff --git a/src/pixmaps/cursor-pen.xpm b/src/pixmaps/cursor-pen.xpm new file mode 100644 index 000000000..5bb565c42 --- /dev/null +++ b/src/pixmaps/cursor-pen.xpm @@ -0,0 +1,38 @@ +/* XPM */ +static char * cursor_pen_xpm[] = { +"32 32 3 1", +" c None", +". c #FFFFFF", +"+ c #000000", +" ... ", +" .+. ", +" .+. ", +"....+.... ", +".+++ +++. ", +"....+.... ", +" .+. ", +" .+. ", +" ... .. ", +" .++... ", +" .+.++++.. ", +" .++.+..++.. ", +" .++.+...++. ", +" .+.+.+....+. ", +" .+..+.++..+. ", +" .+..+..+..+. ", +" .+..+...+..+. ", +" .+..+.+...+.. ", +" .+...+.+..+.+. ", +" .++.......+.+. ", +" ..+.....++..+. ", +" .+++.++.+..+. ", +" ...++.+.++. ", +" .++++.++. ", +" .+++++. ", +" .+++. ", +" .+. ", +" . ", +" ", +" ", +" ", +" "}; diff --git a/src/pixmaps/cursor-pencil.xpm b/src/pixmaps/cursor-pencil.xpm new file mode 100644 index 000000000..ff00464f6 --- /dev/null +++ b/src/pixmaps/cursor-pencil.xpm @@ -0,0 +1,38 @@ +/* XPM */ +static char * cursor_pencil_xpm[] = { +"32 32 3 1", +" c None", +". c #FFFFFF", +"+ c #000000", +" ... ", +" .+. ", +" .+. ", +"....+.... ", +".+++ +++. ", +"....+.... ", +" .+. ", +" .+. ", +" ... ", +" .. ", +" .+...... ", +" .++++++. ", +" .++..+.+. ", +" .+..++..+. ", +" .+.+..+..+. ", +" .+++...+..+. ", +" .+..+...+..+. ", +" .+..+...+..+. ", +" .+..+...+..+. ", +" .+..+...+..+. ", +" .+..+...+.++. ", +" .+..+...+..+. ", +" .+..+.+...+. ", +" .+..+...+. ", +" .++...+. ", +" .+..+. ", +" .++. ", +" .. ", +" ", +" ", +" ", +" "}; diff --git a/src/pixmaps/cursor-rect.xpm b/src/pixmaps/cursor-rect.xpm new file mode 100644 index 000000000..508dd4b54 --- /dev/null +++ b/src/pixmaps/cursor-rect.xpm @@ -0,0 +1,38 @@ +/* XPM */ +static char * cursor_rect_xpm[] = { +"32 32 3 1", +" c None", +". c #FFFFFF", +"+ c #000000", +" ... ", +" .+. ", +" .+. ", +"....+.... ", +".+++ +++. ", +"....+.... ", +" .+. ", +" .+. ................. ", +" ... .+++++++++++++++. ", +" .+.............+. ", +" .+.............+. ", +" .+.............+. ", +" .+.............+. ", +" .+.............+. ", +" .+.............+. ", +" .+.............+. ", +" .+.............+. ", +" .+.............+. ", +" .+++++++++++++++. ", +" ................. ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" "}; diff --git a/src/pixmaps/cursor-select-d.xpm b/src/pixmaps/cursor-select-d.xpm new file mode 100644 index 000000000..fb37164e5 --- /dev/null +++ b/src/pixmaps/cursor-select-d.xpm @@ -0,0 +1,38 @@ +/* XPM */ +static char * cursor_select_d_xpm[] = { +"32 32 3 1", +" g None", +". g #FFFFFF", +"+ g #000000", +".. ", +".+. ", +".++. ", +".+++. ", +".++++. ", +".+++++. ", +".++++++. ", +".+++++++. ", +".++++++++. ", +".+++++++++. ", +".+++++.... ", +".++.++. ", +".+. .++. ", +" . .++. ", +" .++. ++ ++ ", +" .++. +..+..+ ", +" .. ++..+..++ ", +" +.+..+..+.+ ", +" +.+..+..+.+ ", +" +.+..+..+.+ ", +" +.........+ ", +" +.........+ ", +" +........+ ", +" +........+ ", +" +......+ ", +" +......+ ", +" +.....+ ", +" +.....+ ", +" +++++++ ", +" ", +" ", +" "}; diff --git a/src/pixmaps/cursor-select-m.xpm b/src/pixmaps/cursor-select-m.xpm new file mode 100644 index 000000000..29b0a5c3a --- /dev/null +++ b/src/pixmaps/cursor-select-m.xpm @@ -0,0 +1,38 @@ +/* XPM */ +static char * cursor_select_m_xpm[] = { +"32 32 3 1", +" g None", +". g #FFFFFF", +"+ g #000000", +".. ", +".+. ", +".++. ", +".+++. ", +".++++. ", +".+++++. ", +".++++++. ", +".+++++++. ", +".++++++++. ", +".+++++++++. ", +".+++++.... ", +".++.++. ++++ ", +".+. .++. +.+..+ ", +" . .++. +..+..++ ", +" .++. +..+..+.+ ", +" .++. + +..+..+..+ ", +" .. +.++..+..+..+ ", +" +..+..+..+..+ ", +" +..+..+..+..+ ", +" +..+..+..+..+ ", +" +...........+ ", +" +..........+ ", +" +.........+ ", +" +........+ ", +" +.......+ ", +" +......+ ", +" +.....+ ", +" +.....+ ", +" +++++++ ", +" ", +" ", +" "}; diff --git a/src/pixmaps/cursor-spiral.xpm b/src/pixmaps/cursor-spiral.xpm new file mode 100644 index 000000000..eefa8be70 --- /dev/null +++ b/src/pixmaps/cursor-spiral.xpm @@ -0,0 +1,38 @@ +/* XPM */ +static char * cursor_spiral_xpm[] = { +"32 32 3 1", +" c None", +". c #FFFFFF", +"+ c #000000", +" ... ", +" .+. ", +" .+. ", +"....+.... ", +".+++ +++. ", +"....+.... ", +" .+. ....... ", +" .+. ...+++++... ", +" ... ..++.....++.. ", +" .+.........+.. ", +" ..+...++++...+. ", +" .+...+....+..+.. ", +" .+..+......+..+. ", +" .+..+......+..+. ", +" .+..+...+..+..+. ", +" ..+..+++..+..+.. ", +" .+......+...+. ", +" ..++...+...+.. ", +" ...+++...+.. ", +" ......+.. ", +" .... ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" "}; diff --git a/src/pixmaps/cursor-star.xpm b/src/pixmaps/cursor-star.xpm new file mode 100644 index 000000000..25ff432a0 --- /dev/null +++ b/src/pixmaps/cursor-star.xpm @@ -0,0 +1,38 @@ +/* XPM */ +static char * cursor_star_xpm[] = { +"32 32 3 1", +" c None", +". c #FFFFFF", +"+ c #000000", +" ... ", +" .+. ", +" .+. ", +"....+.... ", +".+++ +++. ", +"....+.... .. ", +" .+. .++. ", +" .+. .++. ", +" ... .++. ", +" .+..+. ", +" ........+..+........ ", +" .++++++....++++++. ", +" .++..........++. ", +" ..++......++.. ", +" .+......+. ", +" .+......+. ", +" .+...++...+. ", +" .+.++..++.+. ", +" .+.+.. ..+.+. ", +" .++. .++. ", +" .+. .+. ", +" .. .. ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" "}; diff --git a/src/pixmaps/cursor-text-insert.xpm b/src/pixmaps/cursor-text-insert.xpm new file mode 100644 index 000000000..fc7708aea --- /dev/null +++ b/src/pixmaps/cursor-text-insert.xpm @@ -0,0 +1,38 @@ +/* XPM */ +static char * cursor_text_insert_xpm[] = { +"32 32 3 1", +" c None", +". c #FFFFFF", +"+ c #000000", +" ....... ", +" .+++.+++. ", +" ...+... ", +" .+. ", +" .+. ", +" .+. ", +" .+. ", +" .+. ", +" .+. ", +" .+. ", +" .+. ", +" .+. ", +" .+. ", +" .+. ", +" .+. ", +" .+. ", +" .+. ", +" ...+... ", +" .+++.+++. ", +" ....... ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" "}; diff --git a/src/pixmaps/cursor-text.xpm b/src/pixmaps/cursor-text.xpm new file mode 100644 index 000000000..cea25f2bc --- /dev/null +++ b/src/pixmaps/cursor-text.xpm @@ -0,0 +1,38 @@ +/* XPM */ +static char * cursor_text_xpm[] = { +"32 32 3 1", +" c None", +". c #FFFFFF", +"+ c #000000", +" . ", +" .+. ", +" .+. ", +" .+. ", +" .+. ", +" .+. ", +" ..... ..... ", +".+++++ +++++. ", +" ..... ..... ", +" .+. ", +" .+. ", +" .+. .... ", +" .+. .++++. ", +" .+. .++++. ", +" . .++++++. ", +" .++++++. ", +" .++++++. ", +" .+++..+++. ", +" .+++..+++. ", +" .++++++++. ", +" .++++++++++. ", +" .+++....+++. ", +" ... ... ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" "}; diff --git a/src/pixmaps/cursor-zoom-out.xpm b/src/pixmaps/cursor-zoom-out.xpm new file mode 100644 index 000000000..318406ebc --- /dev/null +++ b/src/pixmaps/cursor-zoom-out.xpm @@ -0,0 +1,38 @@ +/* XPM */ +static char * cursor_zoom_out_xpm[] = { +"32 32 3 1", +" c None", +". c #FFFFFF", +"+ c #000000", +" ... ", +" ..+++.. ", +" .++ ++. ", +" .+ +. ", +" .+ +. ", +".+ ..... +. ", +".+ +++++ +. ", +".+ ..... +. ", +" .+ +. ", +" .+ +. ", +" .++ ++.+.. ", +" ..+++..+.++. ", +" ... .+ +. ", +" .+ +. ", +" .+ +. ", +" .+ +. ", +" .+ +. ", +" .+ +. ", +" .++. ", +" .. ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" "}; diff --git a/src/pixmaps/cursor-zoom.xpm b/src/pixmaps/cursor-zoom.xpm new file mode 100644 index 000000000..88c83004d --- /dev/null +++ b/src/pixmaps/cursor-zoom.xpm @@ -0,0 +1,38 @@ +/* XPM */ +static char * cursor_zoom_xpm[] = { +"32 32 3 1", +" c None", +". c #FFFFFF", +"+ c #000000", +" ... ", +" ..+++.. ", +" .++ ++. ", +" .+ +. ", +" .+ .+. +. ", +".+ ..+.. +. ", +".+ +++++ +. ", +".+ ..+.. +. ", +" .+ .+. +. ", +" .+ +. ", +" .++ ++.+.. ", +" ..+++..+.++. ", +" ... .+ +. ", +" .+ +. ", +" .+ +. ", +" .+ +. ", +" .+ +. ", +" .+ +. ", +" .++. ", +" .. ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" "}; diff --git a/src/pixmaps/handles.xpm b/src/pixmaps/handles.xpm new file mode 100644 index 000000000..6ab68e7d3 --- /dev/null +++ b/src/pixmaps/handles.xpm @@ -0,0 +1,260 @@ +/* XPM */ +static char * handle_scale_nw_xpm[] = { +"13 13 3 1", +" c None", +". c #000000", +"+ c #FFFFFF", +" ", +" ....... ", +" .+++.. ", +" .++++. ", +" .+++++. ", +" ..+++++. . ", +" ...+++++... ", +" . .+++++.. ", +" .+++++. ", +" .++++. ", +" ..+++. ", +" ....... ", +" "}; + +/* XPM */ +static char * handle_scale_ne_xpm[] = { +"13 13 3 1", +" c None", +". c #000000", +"+ c #FFFFFF", +" ", +" ....... ", +" ..+++. ", +" .++++. ", +" .+++++. ", +" . .+++++.. ", +" ...+++++... ", +" ..+++++. . ", +" .+++++. ", +" .++++. ", +" .+++.. ", +" ....... ", +" "}; + +/* XPM */ +static char * handle_scale_h_xpm[] = { +"13 13 3 1", +" c None", +". c #000000", +"+ c #FFFFFF", +" ", +" ", +" . . ", +" .. .. ", +" ..+...+.. ", +" ..+++++++.. ", +"..+++++++++..", +" ..+++++++.. ", +" ..+...+.. ", +" .. .. ", +" . . ", +" ", +" "}; + +/* XPM */ +static char * handle_scale_v_xpm[] = { +"13 13 3 1", +" c None", +". c #000000", +"+ c #FFFFFF", +" . ", +" ... ", +" ..+.. ", +" ..+++.. ", +" ..+++++.. ", +" .+++. ", +" .+++. ", +" .+++. ", +" ..+++++.. ", +" ..+++.. ", +" ..+.. ", +" ... ", +" . "}; + +/* XPM */ +static char * handle_rotate_nw_xpm[] = { +"13 13 3 1", +" c None", +". c #000000", +"+ c #FFFFFF", +" . ", +" .. ", +" .... ", +" ...++.. ", +" .++++++..", +" .+++++++. ", +" .++++..+. ", +" .+++. . ", +"...+++. ", +" ..++++. ", +" ..++. ", +" ... ", +" . "}; + +/* XPM */ +static char * handle_rotate_n_xpm[] = { +"13 13 3 1", +" c None", +". c #000000", +"+ c #FFFFFF", +" . . ", +" .. .. ", +" ......... ", +" ..+++++++.. ", +"..+++++++++..", +" ..+++++++.. ", +" ......... ", +" .. .. ", +" . . ", +" ", +" ", +" ", +" "}; + +/* XPM */ +static char * handle_rotate_ne_xpm[] = { +"13 13 3 1", +" c None", +". c #000000", +"+ c #FFFFFF", +" . ", +" .. ", +" .... ", +" ..++... ", +"..++++++. ", +" .+++++++. ", +" .+..++++. ", +" . .+++. ", +" .+++...", +" .++++.. ", +" .++.. ", +" ... ", +" . "}; + +/* XPM */ +static char * handle_rotate_e_xpm[] = { +"13 13 3 1", +" c None", +". c #000000", +"+ c #FFFFFF", +" . ", +" ... ", +" ..+.. ", +" ..+++.. ", +" ...+++...", +" .+++. ", +" .+++. ", +" .+++. ", +" ...+++...", +" ..+++.. ", +" ..+.. ", +" ... ", +" . "}; + +/* XPM */ +static char * handle_rotate_se_xpm[] = { +"13 13 3 1", +" c None", +". c #000000", +"+ c #FFFFFF", +" . ", +" ... ", +" .++.. ", +" .++++.. ", +" .+++...", +" . .+++. ", +" .+..++++. ", +" .+++++++. ", +"..++++++. ", +" ..++... ", +" .... ", +" .. ", +" . "}; + + +/* XPM */ +static char * handle_rotate_s_xpm[] = { +"13 13 3 1", +" c None", +". c #000000", +"+ c #FFFFFF", +" ", +" ", +" ", +" ", +" . . ", +" .. .. ", +" ......... ", +" ..+++++++.. ", +"..+++++++++..", +" ..+++++++.. ", +" ......... ", +" .. .. ", +" . . "}; + +/* XPM */ +static char * handle_rotate_sw_xpm[] = { +"13 13 3 1", +" c None", +". c #000000", +"+ c #FFFFFF", +" . ", +" ... ", +" ..++. ", +" ..++++. ", +"...+++. ", +" .+++. . ", +" .++++..+. ", +" .+++++++. ", +" .++++++..", +" ...++.. ", +" .... ", +" .. ", +" . "}; + +/* XPM */ +static char * handle_rotate_w_xpm[] = { +"13 13 3 1", +" c None", +". c #000000", +"+ c #FFFFFF", +" . ", +" ... ", +" ..+.. ", +" ..+++.. ", +"...+++... ", +" .+++. ", +" .+++. ", +" .+++. ", +"...+++. . ", +" ..+++.. ", +" ..+.. ", +" ... ", +" . "}; + +/* XPM */ +static char * handle_center_xpm[] = { +"13 13 3 1", +" c None", +". c #000000", +"+ c #FFFFFF", +" ", +" . ", +" . ", +" . ", +" ++.++ ", +" ++.++ ", +" ..... ..... ", +" ++.++ ", +" ++.++ ", +" . ", +" . ", +" . ", +" "}; diff --git a/src/plugin.def b/src/plugin.def new file mode 100644 index 000000000..168e1e4a0 --- /dev/null +++ b/src/plugin.def @@ -0,0 +1,7 @@ +;######################################################## +;## File: plugin.def +;## Purpose: Used by dllwrap to make an inkscape plugin +;######################################################## + +EXPORTS + inkscape_plugin_table diff --git a/src/preferences-skeleton.h b/src/preferences-skeleton.h new file mode 100644 index 000000000..688a080fa --- /dev/null +++ b/src/preferences-skeleton.h @@ -0,0 +1,266 @@ +#ifndef SEEN_PREFERENCES_SKELETON_H +#define SEEN_PREFERENCES_SKELETON_H + +#include + +static char const preferences_skeleton[] = +"\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"\n" +" \n" +" \n" +" \n" +"\n" +" \n" +" \n" +" \n" +"\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"\n" +" " +" \n" +"\n" +" \n" +" \n" +"\n" +" \n" +" \n" +"\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"\n" +"\n"; + +#define PREFERENCES_SKELETON_SIZE (sizeof(preferences_skeleton) - 1) + + +#endif /* !SEEN_PREFERENCES_SKELETON_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:encoding=utf-8:textwidth=99 : diff --git a/src/preferences.cpp b/src/preferences.cpp new file mode 100644 index 000000000..05701a2f6 --- /dev/null +++ b/src/preferences.cpp @@ -0,0 +1,92 @@ +/** \file + * \brief Prefs handling implementation + * + * Authors: + * Ralf Stephan + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +#include "preferences-skeleton.h" +#include "xml/repr.h" +#include "dialogs/input.h" +#include "inkscape.h" +#include "preferences.h" + +#define PREFERENCES_FILE "preferences.xml" + +static Inkscape::XML::Document *_preferences; +static bool _save_preferences; + +namespace Inkscape { + +void +Preferences::loadSkeleton() +{ + _preferences = sp_repr_read_mem (preferences_skeleton, PREFERENCES_SKELETON_SIZE, 0); +} + +Inkscape::XML::Document* +Preferences::get() +{ + return _preferences; +} + +/** + * Attempts to load the preferences file indicated by the global PREFERENCES_FILE + * parameter. If it cannot load it, the default preferences_skeleton will be used + * instead. + */ +void +Preferences::load() +{ + /// \todo this still uses old Gtk+ code which should be somewhere else + if (inkscape_load_config (PREFERENCES_FILE, + _preferences, + preferences_skeleton, + PREFERENCES_SKELETON_SIZE, + _("%s is not a regular file.\n%s"), + _("%s not a valid XML file, or\n" + "you don't have read permissions on it.\n%s"), + _("%s is not a valid preferences file.\n%s"), + _("Inkscape will run with default settings.\n" + "New settings will not be saved."))) + { + sp_input_load_from_preferences(); + _save_preferences = true; + } else + _save_preferences = false; +} + +void +Preferences::save() +{ + if (!_preferences || ! _save_preferences) + return; + + gchar *fn = profile_path (PREFERENCES_FILE); + (void) sp_repr_save_file (_preferences, fn); + g_free (fn); +} + + +} // namespace Inkscape + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:75 + End: +*/ +// vim: filetype=c++:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/preferences.h b/src/preferences.h new file mode 100644 index 000000000..4a7659586 --- /dev/null +++ b/src/preferences.h @@ -0,0 +1,44 @@ +/** \file + * \brief Static class where all preferences handling happens. + * + * Authors: + * Ralf Stephan + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_PREFERENCES_H +#define INKSCAPE_PREFERENCES_H + +namespace Inkscape { + namespace XML { + class Document; + } + +class Preferences +{ +public: + static XML::Document *get(); + static void load(); + static void save(); + static void loadSkeleton(); + +private: +}; + +} // namespace Inkscape + +#endif // INKSCAPE_PREFERENCES_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:encoding=utf-8:textwidth=99 : diff --git a/src/prefix.cpp b/src/prefix.cpp new file mode 100644 index 000000000..3c9ec6cae --- /dev/null +++ b/src/prefix.cpp @@ -0,0 +1,427 @@ +/* + * BinReloc - a library for creating relocatable executables + * Written by: Mike Hearn + * Hongli Lai + * http://autopackage.org/ + * + * This source code is public domain. You can relicense this code + * under whatever license you want. + * + * NOTE: if you're using C++ and are getting "undefined reference + * to br_*", try renaming prefix.c to prefix.cpp + */ + +/* WARNING, BEFORE YOU MODIFY PREFIX.C: + * + * If you make changes to any of the functions in prefix.c, you MUST + * change the BR_NAMESPACE macro (in prefix.h). + * This way you can avoid symbol table conflicts with other libraries + * that also happen to use BinReloc. + * + * Example: + * #define BR_NAMESPACE(funcName) foobar_ ## funcName + * --> expands br_locate to foobar_br_locate + */ + +#ifndef _PREFIX_C_ +#define _PREFIX_C_ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + + +/* PLEASE NOTE: We use GThreads now for portability */ +/* @see http://developer.gnome.org/doc/API/2.0/glib/glib-Threads.html */ +#ifndef BR_THREADS + /* Change 1 to 0 if you don't want thread support */ + #define BR_THREADS 1 + #include //for GThreads +#endif /* BR_THREADS */ + +#include +#include +#include +#include +#include "prefix.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#undef NULL +#define NULL ((void *) 0) + +#ifdef __GNUC__ + #define br_return_val_if_fail(expr,val) if (!(expr)) {fprintf (stderr, "** BinReloc (%s): assertion %s failed\n", __PRETTY_FUNCTION__, #expr); return val;} +#else + #define br_return_val_if_fail(expr,val) if (!(expr)) return val +#endif /* __GNUC__ */ + + +#ifdef ENABLE_BINRELOC + +#include +#include +#include +#include + +/** + * br_locate: + * symbol: A symbol that belongs to the app/library you want to locate. + * Returns: A newly allocated string containing the full path of the + * app/library that func belongs to, or NULL on error. This + * string should be freed when not when no longer needed. + * + * Finds out to which application or library symbol belongs, then locate + * the full path of that application or library. + * Note that symbol cannot be a pointer to a function. That will not work. + * + * Example: + * --> main.c + * #include "prefix.h" + * #include "libfoo.h" + * + * int main (int argc, char *argv[]) { + * printf ("Full path of this app: %s\n", br_locate (&argc)); + * libfoo_start (); + * return 0; + * } + * + * --> libfoo.c starts here + * #include "prefix.h" + * + * void libfoo_start () { + * --> "" is a symbol that belongs to libfoo (because it's called + * --> from libfoo_start()); that's why this works. + * printf ("libfoo is located in: %s\n", br_locate ("")); + * } + */ +char * +br_locate (void *symbol) +{ + char line[5000]; + FILE *f; + char *path; + + br_return_val_if_fail (symbol != NULL, NULL); + + f = fopen ("/proc/self/maps", "r"); + if (!f) + return NULL; + + while (!feof (f)) + { + unsigned long start, end; + + if (!fgets (line, sizeof (line), f)) + continue; + if (!strstr (line, " r-xp ") || !strchr (line, '/')) + continue; + + sscanf (line, "%lx-%lx ", &start, &end); + if (symbol >= (void *) start && symbol < (void *) end) + { + char *tmp; + size_t len; + + /* Extract the filename; it is always an absolute path */ + path = strchr (line, '/'); + + /* Get rid of the newline */ + tmp = strrchr (path, '\n'); + if (tmp) *tmp = 0; + + /* Get rid of "(deleted)" */ + len = strlen (path); + if (len > 10 && strcmp (path + len - 10, " (deleted)") == 0) + { + tmp = path + len - 10; + *tmp = 0; + } + + fclose(f); + return strdup (path); + } + } + + fclose (f); + return NULL; +} + + +/** + * br_locate_prefix: + * symbol: A symbol that belongs to the app/library you want to locate. + * Returns: A prefix. This string should be freed when no longer needed. + * + * Locates the full path of the app/library that symbol belongs to, and return + * the prefix of that path, or NULL on error. + * Note that symbol cannot be a pointer to a function. That will not work. + * + * Example: + * --> This application is located in /usr/bin/foo + * br_locate_prefix (&argc); --> returns: "/usr" + */ +char * +br_locate_prefix (void *symbol) +{ + char *path, *prefix; + + br_return_val_if_fail (symbol != NULL, NULL); + + path = br_locate (symbol); + if (!path) return NULL; + + prefix = br_extract_prefix (path); + free (path); + return prefix; +} + + +/** + * br_prepend_prefix: + * symbol: A symbol that belongs to the app/library you want to locate. + * path: The path that you want to prepend the prefix to. + * Returns: The new path, or NULL on error. This string should be freed when no + * longer needed. + * + * Gets the prefix of the app/library that symbol belongs to. Prepend that prefix to path. + * Note that symbol cannot be a pointer to a function. That will not work. + * + * Example: + * --> The application is /usr/bin/foo + * br_prepend_prefix (&argc, "/share/foo/data.png"); --> Returns "/usr/share/foo/data.png" + */ +char * +br_prepend_prefix (void *symbol, char *path) +{ + char *tmp, *newpath; + + br_return_val_if_fail (symbol != NULL, NULL); + br_return_val_if_fail (path != NULL, NULL); + + tmp = br_locate_prefix (symbol); + if (!tmp) return NULL; + + if (strcmp (tmp, "/") == 0) + newpath = strdup (path); + else + newpath = br_strcat (tmp, path); + + /* Get rid of compiler warning ("br_prepend_prefix never used") */ + if (0) br_prepend_prefix (NULL, NULL); + + free (tmp); + return newpath; +} + +#endif /* ENABLE_BINRELOC */ + + +/* Thread stuff for thread safetiness */ +#if BR_THREADS + +GPrivate* br_thread_key = (GPrivate *)NULL; + +/* + We do not need local store init() or fini(), because + g_private_new (g_free) will take care of all of that + for us. Isn't GLib wonderful? +*/ + +#else /* !BR_THREADS */ + +static char *br_last_value = (char*)NULL; + +static void +br_free_last_value () +{ + if (br_last_value) + free (br_last_value); +} + +#endif /* BR_THREADS */ + + +/** + * br_thread_local_store: + * str: A dynamically allocated string. + * Returns: str. This return value must not be freed. + * + * Store str in a thread-local variable and return str. The next + * you run this function, that variable is freed too. + * This function is created so you don't have to worry about freeing + * strings. + * + * Example: + * char *foo; + * foo = thread_local_store (strdup ("hello")); --> foo == "hello" + * foo = thread_local_store (strdup ("world")); --> foo == "world"; "hello" is now freed. + */ +const char * +br_thread_local_store (char *str) +{ + #if BR_THREADS + if (!g_thread_supported ()) + { + g_thread_init ((GThreadFunctions *)NULL); + br_thread_key = g_private_new (g_free); + } + + char *specific = (char *) g_private_get (br_thread_key); + if (specific) + free (specific); + g_private_set (br_thread_key, str); + + #else /* !BR_THREADS */ + static int initialized = 0; + + if (!initialized) + { + atexit (br_free_last_value); + initialized = 1; + } + + if (br_last_value) + free (br_last_value); + br_last_value = str; + #endif /* BR_THREADS */ + + return (const char *) str; +} + + +/** + * br_strcat: + * str1: A string. + * str2: Another string. + * Returns: A newly-allocated string. This string should be freed when no longer needed. + * + * Concatenate str1 and str2 to a newly allocated string. + */ +char * +br_strcat (const char *str1, const char *str2) +{ + char *result; + size_t len1, len2; + + if (!str1) str1 = ""; + if (!str2) str2 = ""; + + len1 = strlen (str1); + len2 = strlen (str2); + + result = (char *) malloc (len1 + len2 + 1); + memcpy (result, str1, len1); + memcpy (result + len1, str2, len2); + result[len1 + len2] = '\0'; + + return result; +} + + +/* Emulates glibc's strndup() */ +static char * +br_strndup (char *str, size_t size) +{ + char *result = (char*)NULL; + size_t len; + + br_return_val_if_fail (str != (char*)NULL, (char*)NULL); + + len = strlen (str); + if (!len) return strdup (""); + if (size > len) size = len; + + result = (char *) calloc (sizeof (char), len + 1); + memcpy (result, str, size); + return result; +} + + +/** + * br_extract_dir: + * path: A path. + * Returns: A directory name. This string should be freed when no longer needed. + * + * Extracts the directory component of path. Similar to g_dirname() or the dirname + * commandline application. + * + * Example: + * br_extract_dir ("/usr/local/foobar"); --> Returns: "/usr/local" + */ +char * +br_extract_dir (const char *path) +{ + char *end, *result; + + br_return_val_if_fail (path != (char*)NULL, (char*)NULL); + + end = strrchr (path, '/'); + if (!end) return strdup ("."); + + while (end > path && *end == '/') + end--; + result = br_strndup ((char *) path, end - path + 1); + if (!*result) + { + free (result); + return strdup ("/"); + } else + return result; +} + + +/** + * br_extract_prefix: + * path: The full path of an executable or library. + * Returns: The prefix, or NULL on error. This string should be freed when no longer needed. + * + * Extracts the prefix from path. This function assumes that your executable + * or library is installed in an LSB-compatible directory structure. + * + * Example: + * br_extract_prefix ("/usr/bin/gnome-panel"); --> Returns "/usr" + * br_extract_prefix ("/usr/local/lib/libfoo.so"); --> Returns "/usr/local" + * br_extract_prefix ("/usr/local/libfoo.so"); --> Returns "/usr" + */ +char * +br_extract_prefix (const char *path) +{ + char *end, *tmp, *result; + + br_return_val_if_fail (path != (char*)NULL, (char*)NULL); + + if (!*path) return strdup ("/"); + end = strrchr (path, '/'); + if (!end) return strdup (path); + + tmp = br_strndup ((char *) path, end - path); + if (!*tmp) + { + free (tmp); + return strdup ("/"); + } + end = strrchr (tmp, '/'); + if (!end) return tmp; + + result = br_strndup (tmp, end - tmp); + free (tmp); + + if (!*result) + { + free (result); + result = strdup ("/"); + } + + return result; +} + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _PREFIX_C */ diff --git a/src/prefix.h b/src/prefix.h new file mode 100644 index 000000000..0406f29f8 --- /dev/null +++ b/src/prefix.h @@ -0,0 +1,122 @@ +/* + * BinReloc - a library for creating relocatable executables + * Written by: Mike Hearn + * Hongli Lai + * http://autopackage.org/ + * + * This source code is public domain. You can relicense this code + * under whatever license you want. + * + * See http://autopackage.org/docs/binreloc/ for + * more information and how to use this. + * + * NOTE: if you're using C++ and are getting "undefined reference + * to br_*", try renaming prefix.c to prefix.cpp + */ + +#ifndef _PREFIX_H_ +#define _PREFIX_H_ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* WARNING, BEFORE YOU MODIFY PREFIX.C: + * + * If you make changes to any of the functions in prefix.c, you MUST + * change the BR_NAMESPACE macro. + * This way you can avoid symbol table conflicts with other libraries + * that also happen to use BinReloc. + * + * Example: + * #define BR_NAMESPACE(funcName) foobar_ ## funcName + * --> expands br_locate to foobar_br_locate + */ +#undef BR_NAMESPACE +#define BR_NAMESPACE(funcName) funcName + + +#ifdef ENABLE_BINRELOC + +#define br_thread_local_store BR_NAMESPACE(br_thread_local_store) +#define br_locate BR_NAMESPACE(br_locate) +#define br_locate_prefix BR_NAMESPACE(br_locate_prefix) +#define br_prepend_prefix BR_NAMESPACE(br_prepend_prefix) + +#ifndef BR_NO_MACROS + /* These are convience macros that replace the ones usually used + in Autoconf/Automake projects */ + #undef SELFPATH + #undef PREFIX + #undef PREFIXDIR + #undef BINDIR + #undef SBINDIR + #undef DATADIR + #undef LIBDIR + #undef LIBEXECDIR + #undef ETCDIR + #undef SYSCONFDIR + #undef CONFDIR + #undef LOCALEDIR + + #define SELFPATH (br_thread_local_store (br_locate ((void *) ""))) + #define PREFIX (br_thread_local_store (br_locate_prefix ((void *) ""))) + #define PREFIXDIR (br_thread_local_store (br_locate_prefix ((void *) ""))) + #define BINDIR (br_thread_local_store (br_prepend_prefix ((void *) "", "/bin"))) + #define SBINDIR (br_thread_local_store (br_prepend_prefix ((void *) "", "/sbin"))) + #define DATADIR (br_thread_local_store (br_prepend_prefix ((void *) "", "/share"))) + #define LIBDIR (br_thread_local_store (br_prepend_prefix ((void *) "", "/lib"))) + #define LIBEXECDIR (br_thread_local_store (br_prepend_prefix ((void *) "", "/libexec"))) + #define ETCDIR (br_thread_local_store (br_prepend_prefix ((void *) "", "/etc"))) + #define SYSCONFDIR (br_thread_local_store (br_prepend_prefix ((void *) "", "/etc"))) + #define CONFDIR (br_thread_local_store (br_prepend_prefix ((void *) "", "/etc"))) + #define LOCALEDIR (br_thread_local_store (br_prepend_prefix ((void *) "", "/share/locale"))) +#endif /* BR_NO_MACROS */ + + +/* The following functions are used internally by BinReloc + and shouldn't be used directly in applications. */ + +const char *br_thread_local_store (char *str); +char *br_locate (void *symbol); +char *br_locate_prefix (void *symbol); +char *br_prepend_prefix (void *symbol, char *path); + + +#endif /* ENABLE_BINRELOC */ + + +/* These macros and functions are not guarded by the ENABLE_BINRELOC + * macro because they are portable. You can use these functions. + */ + +#define br_strcat BR_NAMESPACE(br_strcat) +#define br_extract_dir BR_NAMESPACE(br_extract_dir) +#define br_extract_prefix BR_NAMESPACE(br_extract_prefix) + +#ifndef BR_NO_MACROS + /* Convenience functions for concatenating paths */ + #define BR_SELFPATH(suffix) (br_thread_local_store (br_strcat (SELFPATH, suffix))) + #define BR_PREFIX(suffix) (br_thread_local_store (br_strcat (PREFIX, suffix))) + #define BR_PREFIXDIR(suffix) (br_thread_local_store (br_strcat (BR_PREFIX, suffix))) + #define BR_BINDIR(suffix) (br_thread_local_store (br_strcat (BINDIR, suffix))) + #define BR_SBINDIR(suffix) (br_thread_local_store (br_strcat (SBINDIR, suffix))) + #define BR_DATADIR(suffix) (br_thread_local_store (br_strcat (DATADIR, suffix))) + #define BR_LIBDIR(suffix) (br_thread_local_store (br_strcat (LIBDIR, suffix))) + #define BR_LIBEXECDIR(suffix) (br_thread_local_store (br_strcat (LIBEXECDIR, suffix))) + #define BR_ETCDIR(suffix) (br_thread_local_store (br_strcat (ETCDIR, suffix))) + #define BR_SYSCONFDIR(suffix) (br_thread_local_store (br_strcat (SYSCONFDIR, suffix))) + #define BR_CONFDIR(suffix) (br_thread_local_store (br_strcat (CONFDIR, suffix))) + #define BR_LOCALEDIR(suffix) (br_thread_local_store (br_strcat (LOCALEDIR, suffix))) +#endif + +char *br_strcat (const char *str1, const char *str2); +char *br_extract_dir (const char *path); +char *br_extract_prefix(const char *path); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _PREFIX_H_ */ diff --git a/src/prefs-utils.cpp b/src/prefs-utils.cpp new file mode 100644 index 000000000..54c83c1e9 --- /dev/null +++ b/src/prefs-utils.cpp @@ -0,0 +1,186 @@ +/* + * Utility functions for reading and setting preferences + * + * Authors: + * bulia byak + * + * Copyright (C) 2003 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "inkscape.h" +#include "xml/repr.h" + + +void +prefs_set_int_attribute(gchar const *path, gchar const *attr, gint value) +{ + Inkscape::XML::Node *repr = inkscape_get_repr(INKSCAPE, path); + if (repr) { + sp_repr_set_int(repr, attr, value); + } +} + +gint +prefs_get_int_attribute(gchar const *path, gchar const *attr, gint def) +{ + Inkscape::XML::Node *repr = inkscape_get_repr(INKSCAPE, path); + if (repr) { + return sp_repr_get_int_attribute(repr, attr, def); + } else { + return def; + } +} + +/** +\brief Retrieves an int attribute guarding against screwed-up data; if the value is beyond limits, default is returned +*/ +gint +prefs_get_int_attribute_limited(gchar const *path, gchar const *attr, gint def, gint min, gint max) +{ + Inkscape::XML::Node *repr = inkscape_get_repr(INKSCAPE, path); + if (repr) { + gint const v = sp_repr_get_int_attribute(repr, attr, def); + if (v >= min && v <= max) { + return v; + } else { + return def; + } + } else { + return def; + } +} + +void +prefs_set_double_attribute(gchar const *path, gchar const *attr, double value) +{ + Inkscape::XML::Node *repr = inkscape_get_repr(INKSCAPE, path); + if (repr) { + sp_repr_set_svg_double(repr, attr, value); + } +} + +double +prefs_get_double_attribute(gchar const *path, gchar const *attr, double def) +{ + Inkscape::XML::Node *repr = inkscape_get_repr(INKSCAPE, path); + if (repr) { + return sp_repr_get_double_attribute(repr, attr, def); + } else { + return def; + } +} + +/** +\brief Retrieves an int attribute guarding against screwed-up data; if the value is beyond limits, default is returned +*/ +double +prefs_get_double_attribute_limited(gchar const *path, gchar const *attr, double def, double min, double max) +{ + Inkscape::XML::Node *repr = inkscape_get_repr(INKSCAPE, path); + if (repr) { + double const v = sp_repr_get_double_attribute(repr, attr, def); + if (v >= min && v <= max) { + return v; + } else { + return def; + } + } else { + return def; + } +} + +gchar const * +prefs_get_string_attribute(gchar const *path, gchar const *attr) +{ + Inkscape::XML::Node *repr = inkscape_get_repr(INKSCAPE, path); + if (repr) { + return (char *) repr->attribute(attr); + } + return NULL; +} + +void +prefs_set_string_attribute(gchar const *path, gchar const *attr, gchar const *value) +{ + Inkscape::XML::Node *repr = inkscape_get_repr(INKSCAPE, path); + if (repr) { + repr->setAttribute(attr, value); + } +} + +void +prefs_set_recent_file(gchar const *uri, gchar const *name) +{ + unsigned const max_documents = prefs_get_int_attribute("options.maxrecentdocuments", "value", 20); + + if (uri != NULL) { + Inkscape::XML::Node *recent = inkscape_get_repr(INKSCAPE, "documents.recent"); + if (recent) { + Inkscape::XML::Node *child = sp_repr_lookup_child(recent, "uri", uri); + if (child) { + recent->changeOrder(child, NULL); + } else { + if (recent->childCount() >= max_documents) { + child = recent->firstChild(); + // count to the last + for (unsigned i = 0; i + 2 < max_documents; ++i) { + child = child->next(); + } + // remove all after the last + while (child->next()) { + sp_repr_unparent(child->next()); + } + } + child = sp_repr_new("document"); + child->setAttribute("uri", uri); + recent->addChild(child, NULL); + } + child->setAttribute("name", name); + } + } +} + +gchar const ** +prefs_get_recent_files() +{ + Inkscape::XML::Node *recent = inkscape_get_repr(INKSCAPE, "documents.recent"); + if (recent) { + unsigned const docs = recent->childCount(); + gchar const **datalst = (gchar const **) g_malloc(sizeof(gchar *) * ((docs * 2) + 1)); + + gint i; + Inkscape::XML::Node *child; + for (i = 0, child = recent->firstChild(); + child != NULL; + child = child->next(), i += 2) + { + gchar const *uri = child->attribute("uri"); + gchar const *name = child->attribute("name"); + datalst[i] = uri; + datalst[i + 1] = name; + } + + datalst[i] = NULL; + return datalst; + } + + return NULL; +} + +/* + 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 : diff --git a/src/prefs-utils.h b/src/prefs-utils.h new file mode 100644 index 000000000..3cd2998e5 --- /dev/null +++ b/src/prefs-utils.h @@ -0,0 +1,42 @@ +/* + * Utility functions for reading and setting preferences + * + * Authors: + * bulia byak + * + * Copyright (C) 2003 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SEEN_PREFS_UTILS_H +#define SEEN_PREFS_UTILS_H + +#include + +void prefs_set_int_attribute(gchar const *path, gchar const *attr, gint value); +gint prefs_get_int_attribute(gchar const *path, gchar const *attr, gint def); +gint prefs_get_int_attribute_limited(gchar const *path, gchar const *attr, gint def, gint min, gint max); + +void prefs_set_double_attribute(gchar const *path, gchar const *attr, double value); +double prefs_get_double_attribute(gchar const *path, gchar const *attr, double def); +double prefs_get_double_attribute_limited(gchar const *path, gchar const *attr, double def, double min, double max); + +gchar const *prefs_get_string_attribute(gchar const *path, gchar const *attr); +void prefs_set_string_attribute(gchar const *path, gchar const *attr, gchar const *value); + +void prefs_set_recent_file(const gchar * uri, const gchar * name); +const gchar ** prefs_get_recent_files(void); + +#endif /* !SEEN_PREFS_UTILS_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:encoding=utf-8:textwidth=99 : diff --git a/src/print.cpp b/src/print.cpp new file mode 100644 index 000000000..624021c9a --- /dev/null +++ b/src/print.cpp @@ -0,0 +1,228 @@ +#define __SP_PRINT_C__ + +/** \file + * Frontend to printing + */ +/* + * Author: + * Lauris Kaplinski + * + * This code is in public domain + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + + + +#include "sp-item.h" +#include "extension/print.h" +#include "extension/system.h" +#include "print.h" + +#if 0 +# include + +# ifdef WIN32 +# include +# endif + +# ifdef WITH_GNOME_PRINT +# include +# endif +#endif + +/* Identity typedef */ + +unsigned int sp_print_bind(SPPrintContext *ctx, NR::Matrix const &transform, float opacity) +{ + NRMatrix const ntransform(transform); + return sp_print_bind(ctx, &ntransform, opacity); +} + +unsigned int +sp_print_bind(SPPrintContext *ctx, NRMatrix const *transform, float opacity) +{ + return ctx->module->bind(transform, opacity); +} + +unsigned int +sp_print_release(SPPrintContext *ctx) +{ + return ctx->module->release(); +} + +unsigned int +sp_print_comment(SPPrintContext *ctx, char const *comment) +{ + return ctx->module->comment(comment); +} + +unsigned int +sp_print_fill(SPPrintContext *ctx, NRBPath const *bpath, NRMatrix const *ctm, SPStyle const *style, + NRRect const *pbox, NRRect const *dbox, NRRect const *bbox) +{ + return ctx->module->fill(bpath, ctm, style, pbox, dbox, bbox); +} + +unsigned int +sp_print_stroke(SPPrintContext *ctx, NRBPath const *bpath, NRMatrix const *ctm, SPStyle const *style, + NRRect const *pbox, NRRect const *dbox, NRRect const *bbox) +{ + return ctx->module->stroke(bpath, ctm, style, pbox, dbox, bbox); +} + +unsigned int +sp_print_image_R8G8B8A8_N(SPPrintContext *ctx, + guchar *px, unsigned int w, unsigned int h, unsigned int rs, + NRMatrix const *transform, SPStyle const *style) +{ + return ctx->module->image(px, w, h, rs, transform, style); +} + +unsigned int sp_print_text(SPPrintContext *ctx, char const *text, NR::Point p, + SPStyle const *style) +{ + return ctx->module->text(text, p, style); +} + +#include "display/nr-arena.h" +#include "display/nr-arena-item.h" + +/* UI */ + +void +sp_print_preview_document(SPDocument *doc) +{ + Inkscape::Extension::Print *mod; + unsigned int ret; + + sp_document_ensure_up_to_date(doc); + + mod = Inkscape::Extension::get_print(SP_MODULE_KEY_PRINT_DEFAULT); + + ret = mod->set_preview(); + + if (ret) { + SPPrintContext context; + context.module = mod; + + /* fixme: This has to go into module constructor somehow */ + /* Create new arena */ + mod->base = SP_ITEM(sp_document_root(doc)); + mod->arena = NRArena::create(); + mod->dkey = sp_item_display_key_new(1); + mod->root = sp_item_invoke_show(mod->base, mod->arena, mod->dkey, SP_ITEM_SHOW_DISPLAY); + /* Print document */ + ret = mod->begin(doc); + sp_item_invoke_print(mod->base, &context); + ret = mod->finish(); + /* Release arena */ + sp_item_invoke_hide(mod->base, mod->dkey); + mod->base = NULL; + nr_arena_item_unref(mod->root); + mod->root = NULL; + nr_object_unref((NRObject *) mod->arena); + mod->arena = NULL; + } + + return; +} + +void +sp_print_document(SPDocument *doc, unsigned int direct) +{ + Inkscape::Extension::Print *mod; + unsigned int ret; + + sp_document_ensure_up_to_date(doc); + + if (direct) { + mod = Inkscape::Extension::get_print(SP_MODULE_KEY_PRINT_PS); + } else { + mod = Inkscape::Extension::get_print(SP_MODULE_KEY_PRINT_DEFAULT); + } + + ret = mod->setup(); + + if (ret) { + SPPrintContext context; + context.module = mod; + + /* fixme: This has to go into module constructor somehow */ + /* Create new arena */ + mod->base = SP_ITEM(sp_document_root(doc)); + mod->arena = NRArena::create(); + mod->dkey = sp_item_display_key_new(1); + mod->root = sp_item_invoke_show(mod->base, mod->arena, mod->dkey, SP_ITEM_SHOW_DISPLAY); + /* Print document */ + ret = mod->begin(doc); + sp_item_invoke_print(mod->base, &context); + ret = mod->finish(); + /* Release arena */ + sp_item_invoke_hide(mod->base, mod->dkey); + mod->base = NULL; + nr_arena_item_unref(mod->root); + mod->root = NULL; + nr_object_unref((NRObject *) mod->arena); + mod->arena = NULL; + } + + return; +} + +void +sp_print_document_to_file(SPDocument *doc, gchar const *filename) +{ + Inkscape::Extension::Print *mod; + SPPrintContext context; + gchar const *oldconst; + gchar *oldoutput; + unsigned int ret; + + sp_document_ensure_up_to_date(doc); + + mod = Inkscape::Extension::get_print(SP_MODULE_KEY_PRINT_PS); + oldconst = mod->get_param_string("destination"); + oldoutput = g_strdup(oldconst); + mod->set_param_string("destination", (gchar *)filename); + +/* Start */ + context.module = mod; + /* fixme: This has to go into module constructor somehow */ + /* Create new arena */ + mod->base = SP_ITEM(sp_document_root(doc)); + mod->arena = NRArena::create(); + mod->dkey = sp_item_display_key_new(1); + mod->root = sp_item_invoke_show(mod->base, mod->arena, mod->dkey, SP_ITEM_SHOW_DISPLAY); + /* Print document */ + ret = mod->begin(doc); + sp_item_invoke_print(mod->base, &context); + ret = mod->finish(); + /* Release arena */ + sp_item_invoke_hide(mod->base, mod->dkey); + mod->base = NULL; + nr_arena_item_unref(mod->root); + mod->root = NULL; + nr_object_unref((NRObject *) mod->arena); + mod->arena = NULL; +/* end */ + + mod->set_param_string("destination", oldoutput); + g_free(oldoutput); + + 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:encoding=utf-8:textwidth=99 : diff --git a/src/print.h b/src/print.h new file mode 100644 index 000000000..0ec3fb321 --- /dev/null +++ b/src/print.h @@ -0,0 +1,58 @@ +#ifndef PRINT_H_INKSCAPE +#define PRINT_H_INKSCAPE + +/** \file + * Frontend to printing + */ +/* + * Author: + * Lauris Kaplinski + * + * This code is in public domain + */ + +#include +#include "forward.h" +#include "extension/extension-forward.h" + +struct SPPrintContext { + Inkscape::Extension::Print *module; +}; + +unsigned int sp_print_bind(SPPrintContext *ctx, NR::Matrix const &transform, float opacity); +unsigned int sp_print_bind(SPPrintContext *ctx, NRMatrix const *transform, float opacity); +unsigned int sp_print_release(SPPrintContext *ctx); +unsigned int sp_print_comment(SPPrintContext *ctx, char const *comment); +unsigned int sp_print_fill(SPPrintContext *ctx, NRBPath const *bpath, NRMatrix const *ctm, SPStyle const *style, + NRRect const *pbox, NRRect const *dbox, NRRect const *bbox); +unsigned int sp_print_stroke(SPPrintContext *ctx, NRBPath const *bpath, NRMatrix const *transform, SPStyle const *style, + NRRect const *pbox, NRRect const *dbox, NRRect const *bbox); + +unsigned int sp_print_image_R8G8B8A8_N(SPPrintContext *ctx, + guchar *px, unsigned int w, unsigned int h, unsigned int rs, + NRMatrix const *transform, SPStyle const *style); + +unsigned int sp_print_text(SPPrintContext *ctx, char const *text, NR::Point p, + SPStyle const *style); + +void sp_print_get_param(SPPrintContext *ctx, gchar *name, bool *value); + + +/* UI */ +void sp_print_preview_document(SPDocument *doc); +void sp_print_document(SPDocument *doc, unsigned int direct); +void sp_print_document_to_file(SPDocument *doc, gchar const *filename); + + +#endif /* !PRINT_H_INKSCAPE */ + +/* + 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 : diff --git a/src/proofs b/src/proofs new file mode 100644 index 000000000..a3a22733f --- /dev/null +++ b/src/proofs @@ -0,0 +1,332 @@ +This file contains some loose proofs of a few properties. It's somewhat +ad-hoc. At least it gives an indication of what assert/g_assert calls have +been checked by a developer. If an assertion does trigger, then this file may +help in debugging that assertion failure. + +It's currently ordered by caller. + +(Re-ordering to avoid forward references in proofs might be a good idea, +though this would in some cases require splitting up the proofs for a routine, +e.g. proving preconditions of g called from f, then proving g's postcondition, +then using that to prove something else in f again. Furthermore it may not +even be possible to avoid forward references for recursive/looping code.) + + + +src/pencil-context.cpp:fit_and_split + +Very loose proof of !sp_curve_empty (pc->red_curve) assertion: +fit_and_split is called successively with its input varying only by appending a point. +For the n_segs > 0 && unsigned(pc->npoints) < G_N_ELEMENTS(pc->p) condition to fail, +we must have at least 3 distinct points, which means that a previous call had 2 distinct points, +in which case we'd have filled in pc->red_curve to a non-empty curve. + +Expansion of the above claim of at least 3 distinct points: We know n_segs <= 0 || +unsigned(dc->npoints) >= G_N_ELEMENTS(pc->p) from the negation of the containing `if' condition. +G_N_ELEMENTS(pc->p) is greater than 3 (in int arithmetic), from SPPencilContext::p array definition +in pencil-context.h. npoints grows by no more than one per fit_and_split invocation; we should be +able to establish that dc->npoints == G_N_ELEMENTS(pc->p) if unsigned(dc->npoints) >= +G_N_ELEMENTS(pc->p), in which case 3 <= dc->npoints in int arithmetic. We know that dc->npoints >= +2 from assertion at head of fit_and_split; in which case if n_segs <= 0 then fit_and_split has +failed, which implies that dc->npoints > 2 (since the fitter can always exactly fit 2 points, +i.e. it never fails if npoints == 2; TODO: add sp_bezier_fit_cubic postcondition for this). + + +src/pencil-context.cpp:fit_and_split + +Proof of precondition: The only caller is spdc_add_freehand_point (by +textual search in that file, and staticness). See proof for that +function. + + +src/pencil-context.cpp:spdc_add_freehand_point + +Proof of fit_and_split `pc->npoints > 1' requirement: +It initially unconditionally asserts `pc->npoints > 0'. There are no function calls or modifications +of pc or pc->npoints other than incrementing pc->npoints after that assertion. +We assume that integer overflow doesn't occur during that increment, +so we get pc->npoints > 1. + + +src/pencil-context.cpp:spdc_set_endpoint + +Very loose proof of npoints > 0: Should be preceded by spdc_set_startpoint(pc) according to state +transitions. spdc_set_startpoint sets pc->npoints to 0 (handled at beginning of function) or 1. + + +src/display/bezier-utils.cpp:compute_max_error + +Proof of postcondition: *splitPoint is set only from i, which goes from 1 to less than last. +i isn't written to in the loop body: only uses are indexing built-in arrays d and u +(and RHS of assignment). + + +src/display/bezier-utils.cpp:sp_bezier_fit_cubic_full + +Proof of `nsegs1 != 0' assertion: nsegs1 is const. Have already +returned in the (nsegs1 < 0) case, so !(nsegs1 < 0), i.e. nsegs1 >= 0 +(given that nsegs1 is gint). nsegs1 is set to +sp_bezier_fit_cubic_full(_, _, _, splitPoint + 1, ...). We will show +that sp_bezier_fit_cubic_full ensures len < 2 || ret != 0. splitPoint +> 0 from compute_max_error postcondition combined with error >= +precondition and thus having handled the compute_max_error returning 0 +case: if returned 0 for maxError then maxError <= error * 9.0 would be +true, and we recalculate splitPoint; if the renewed maxError is 0 then +the maxError <= error test will succeed and we return. If we don't +return, then the most recent compute_max_error must have returned +non-zero, which implies (through compute_max_error postcondition) that +splitPoint would have been set s.t. 0 < splitPoint. splitPoint is not +subsequently written to. (It is subsequently passed only to +sp_darray_center_tangent 2nd arg, which is a plain unsigned rather +than reference.) 0 < splitPoint < last guarantees splitPoint + 1 >= +2. (We know splitPoint + 1 won't overflow because last = len - 1 and +len is of the same type as splitPoint.) Passing splitPoint + 1 for +len of the call that sets nsegs1 ensures that nsegs1 is non-zero (from +the len < 2 || ret != 0 property that we show below). QED. + +Proof that len < 2 || (failed no-dups precondition) || ret != 0: All +returns are either -1, 0, 1, or nsegs1 + nsegs2. There are two +literal 0 cases: one conditional on len < 2, and the other for failed +precondition (non-uniqued data). For the nsegs1 + nsegs2 case, we've +already ruled out nsegs1 < 0 (through conditional return) and nsegs2 < +0 (same). The nsegs1 + nsegs2 case occurs only when we recurse; we've +already shown the desired property for non-recursive case. In the +nsegs1 non-recursive case, we have that nsegs1 != 0, which combined +with !(nsegs1 < 0) and !(nsegs2 < 0) implies that nsegs1 + nsegs2 +either overflows or is greater than 0. We should be able to show that +nsegs1 + nsegs2 < len even with exact arithmetic. (Very loose proof: +given that len >= 2 (from earlier conditional return), we can fit len +points using len-1 segments even using straight line segments.) +nsegs1 and nsegs2 are the same type as len, and we've shown that +nsegs1 + nsegs2 in exact arithmetic is >= 0 from each operand being +non-negative, so nsegs1 + nsegs2 doesn't overflow. Thus nsegs1 + +nsegs2 > 0. Thus we have shown for each return point that either the +return value is -1 or > 0 or occurs when len < 2 or in failure of +no-dups precondition. (We should also show that no-dups outer +precondition being met implies it being met for inner instances of +sp_bezier_fit_cubic_full, because we pass a subsequence of the data +array, and don't write to that array.) QED. + +We should also show that the recursion is finite for the inductive +proof to hold. The finiteness comes from inner calls having len > 0 +and len less than that of outer calls (from splitPoint calculation and +0 < splitPoint < last for recursive case and last < len and transitive +property of < for gint). If len < 2 then we don't recurse +(conditional return). + +We should go over this proof to make it clear that there are no +"forward references" other than for recursive case. We could also be +more formal in use of inductive proof (e.g. clearly stating what the +base and inductive premises are; namely the non-recursing and +recursing cases of sp_bezier_fit_cubic_full). + +Better proof sketch that nseg1 + nsegs2 < len: ret < len for each +recursive case other than where len > 0 precondition fails. nsegs1 is +calculated for inner len=splitPoint + 1, nsegs2 for inner len=len - +splitPoint. Assuming exact arithmetic we'll transform that to ret <= +len - 1. Implies that in exact arithmetic, nsegs1 + nsegs2 <= +(splitPoint + 1 - 1) + (len - splitPoint - 1). Simplifying RHS (using +exact arithmetic): nsegs1 + nsegs2 <= len - 1, i.e. nsegs1 + nsegs2 < +len. Presumably we can show that machine arithmetic gets the same +results as exact arithmetic from similar arguments used so far for +showing that overflow doesn't occur. For the recursive case the +return values are either nsegs1 + nsegs2 or -1. + +We should also show that inner preconditions hold, especially the len +> 0 precondition. (For nsegs1 call, we use 0 < splitPoint and that +splitPoint + 1 doesn't overflow. For nsegs2 call, we pass len - +splitPoint; combine with splitPoint < last, last = len - 1, and no +overflow.) We've already sketched a proof for no-dups precondition. +The others are fairly simple. + +For error >= 0: error is const, and we pass it to all recursions. + +For inner max_beziers >= 1: recursions are conditional on outer +1 < max_beziers before setting rec_max_beziers1 to max_beziers - 1, +and passing rec_max_beziers1 as inner max_beziers value, +so we have outer max_beziers >= 2 so inner max_beziers >= 1. +max_beziers and rec_max_beziers1 are both const. + + +src/display/bezier-utils.cpp:sp_darray_right_tangent(Point const[], unsigned) + +Proof of unit_vector precondition that a != Point(0, 0): our unequal precondition. + +Loose (incorrect) proof of unit_vector precondition that neither +coordinate is NaN: our in_svg_plane precondition, and fact that +in_svg_plane returns false if either argument is infinite. HOWEVER, +the unchecked in_svg_plane precondition isn't currently guaranteed, so +we're just relying on the input points never being infinity (which +might occur with strange zoom settings). + + +src/display/bezier-utils.cpp:sp_darray_right_tangent(Point const[], unsigned, double) + +Loose proof of unit_vector precondition that a != Point(0, 0) for first call to unit_vector: + +We've asserted that 0 <= tolerance_sq; combine with tolerance_sq < +distsq and transitivity of <=/< show that 0 < distsq. Definition of +dot should give us that t != 0.0, given that 0.0 * 0.0 == +0.0, and 0 +< +0.0 is false. + +Loose proof for the second unit_vector invocation: distsq != 0 from ?: +condition, which should give us that t != Point(0, 0) in the same way +as in the above proof. + +Proof of sp_darray_right_tangent(Point[], unsigned) preconditions: We +have the same preconditions, and pass the same arguments. d, *d and +len are const. + + + +src/extension/internal/ps.cpp:PrintPS::print_fill_style: + +Proof of the + g_return_if_fail( style->fill.type == SP_PAINT_TYPE_COLOR + || ( style->fill.type == SP_PAINT_TYPE_PAINTSERVER + && SP_IS_GRADIENT(SP_STYLE_FILL_SERVER(style)) ) ) +at beginning of function: + +rgrep print_fill_style reveals no callers in any other files. There are two calls in ps.cpp, both +inside an `if' test of that same condition (with no relevant lines between the test and the call). +Each call uses `style' as its second argument, and `style' in print_fill_style refers to its second +parameter. In both caller & callee, `style' is a const pointer to const, and there is very little +code between the two tests, so the relevant values are very unlikely to change between the two +tests. + + +Proof of + g_assert( style->fill.type == SP_PAINT_TYPE_PAINTSERVER + && SP_IS_GRADIENT(SP_STYLE_FILL_SERVER(style)) ) : + +The g_return_if_fail(style->fill.type == SP_PAINT_TYPE_COLOR + || ( style->fill.type == SP_PAINT_TYPE_PAINTSERVER + && SP_IS_GRADIENT(SP_STYLE_FILL_SERVER(style)) ) ) +call at the beginning of the function, and we're in the `else' branch of a test for +style->fill.type == SP_PAINT_TYPE_COLOR, and style is a const pointer to const, so it's likely that +style->fill and the gradient object have the same values throughout. + + + +src/extensions/internal/ps.cpp:PrintPS::fill: + +Proof of the two assertions + g_assert( style->fill.type == SP_PAINT_TYPE_PAINTSERVER + && SP_IS_GRADIENT(SP_STYLE_FILL_SERVER(style)) ) : + +Each is in the `else' branch of a test for `style->fill.type == SP_PAINT_TYPE_COLOR', +within a test for + ( style->fill.type == SP_PAINT_TYPE_COLOR + || ( style->fill.type == SP_PAINT_TYPE_PAINTSERVER + && SP_IS_GRADIENT(SP_STYLE_FILL_SERVER(style)) ) ). + +`style' is a const pointer to const, so the values are unlikely to have changed between the tests. + + + +src/seltrans.cpp:sp_sel_trans_update_handles + +Proof of requirements of sp_show_handles: + +sp_show_handles requirements: !arg1.empty. + +Before any call to sp_show_handles is a test `if (... || seltrans.empty) { ...; return; }' +(with no `break' etc. call preventing that `return'). +Each subsequent sp_show_handles call uses seltrans as arg1. +seltrans is a reference. There are no calls between that failing seltrans.empty test +and the sp_show_handles calls that pass seltrans. The sole call is sp_remove_handles, +probably doesn't have access to seltrans. + + + +src/seltrans.cpp:sp_show_handles + +Proof of precondition: + +sp_show_handles is static. Searching reveals calls only in sp_sel_trans_update_handles (proof above). + + + +src/sp-spiral.cpp:sp_spiral_fit_and_draw + +Proof of postcondition is_unit_vector(*hat2): + +hat2 is set by sp_spiral_get_tangent unconditionally, which Ensures is_unit_vector(*hat2). +We then negate *hat2, which doesn't affect its length. +We pass it only to sp_bezier_fit_cubic_full, which claims constness of *hat2. + +Proof of unconditionalness: Not inside if/for/while. No previous `return'. + + +src/sp-spiral.cpp:sp_spiral_set_shape + +Loose proof of requirements for sp_spiral_fit_and_draw: + + Proof of dstep > 0: + + SAMPLE_STEP equals .25. + spiral->revo is bounded to [0.05, 20.0] (and non-NaN) by various CLAMP calls. + (TODO: Add precondition, given that those CLAMP calls are outside of this function.) + SAMPLE_SIZE equals 8. + dstep is const and equals SAMPLE_STEP / spiral->revo / (SAMPLE_SIZE - 1), + == 1 / (4 * [.05, 20.0] * 7) + == 1 / [1.4, 560] + dstep in [.0018, .714]. + + Proof of is_unit_vector(hat1): + + Initially guaranteed by sp_spiral_get_tangent Ensures. + For subsequent calls, hat1 is set from negated hat2 as set by sp_spiral_fit_and_draw, + which Ensures is_unit_vector(hat2). + + + +src/style.cpp:sp_css_attr_from_style: + +Proof of sp_style_write_string pre `style != NULL': + +Passes style as style argument. style is const, and has already been checked against NULL. + + +src/style.cpp:sp_css_attr_from_object + +Proof of `flags in {IFSET, ALWAYS} precondition: + + $ grep sp_css_attr_from_object `sed 's,#.*,,' make.files ` + file.cpp: SPCSSAttr *style = sp_css_attr_from_object (SP_DOCUMENT_ROOT (doc)); + selection-chemistry.cpp: SPCSSAttr *css = sp_css_attr_from_object (SP_OBJECT(item), SP_STYLE_FLAG_ALWAYS); + selection-chemistry.cpp: SPCSSAttr *temp = sp_css_attr_from_object (last_element, SP_STYLE_FLAG_IFSET); + style.cpp:sp_css_attr_from_object (SPObject *object, guint flags) + style.h:SPCSSAttr *sp_css_attr_from_object(SPObject *object, guint flags = SP_STYLE_FLAG_IFSET); + + +src/style.cpp:sp_css_attr_from_style + +Proof of precondition `style != NULL': + +Callers are selection-chemistry.cpp and style.cpp: + + $ grep sp_css_attr_from_style `sed 's,#.*,,' make.files ` +selection-chemistry.cpp: SPCSSAttr *css = sp_css_attr_from_style (query, SP_STYLE_FLAG_ALWAYS); + style.cpp:sp_css_attr_from_style (SPStyle const *const style, guint flags) + style.cpp: return sp_css_attr_from_style (style, flags); + style.h:SPCSSAttr *sp_css_attr_from_style (SPStyle const *const style, guint flags); + +selection-chemistry.cpp caller: query is initialized from sp_style_new() +(which guarantees non-NULL), and is const. + +style.cpp caller: preceded by explicit test for NULL: + + $ grep -B2 sp_css_attr_from_style style.cpp|tail -3 + if (style == NULL) + return NULL; + return sp_css_attr_from_style (style, flags); + + + + +# Local Variables: +# mode:indented-text +# fill-column:99 +# End: +# vim: filetype=text:tabstop=8:encoding=utf-8:textwidth=99 : diff --git a/src/rect-context.cpp b/src/rect-context.cpp new file mode 100644 index 000000000..be56047f0 --- /dev/null +++ b/src/rect-context.cpp @@ -0,0 +1,501 @@ +#define __SP_RECT_CONTEXT_C__ + +/* + * Rectangle drawing context + * + * Author: + * Lauris Kaplinski + * bulia byak + * + * Copyright (C) 2000-2005 authors + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "config.h" + +#include + +#include "macros.h" +#include "display/sp-canvas.h" +#include "sp-rect.h" +#include "document.h" +#include "sp-namedview.h" +#include "selection.h" +#include "desktop-handles.h" +#include "snap.h" +#include "desktop.h" +#include "desktop-style.h" +#include "message-context.h" +#include "pixmaps/cursor-rect.xpm" +#include "rect-context.h" +#include "sp-metrics.h" +#include +#include "object-edit.h" +#include "xml/repr.h" +#include "xml/node-event-vector.h" +#include "prefs-utils.h" +#include "context-fns.h" + +static void sp_rect_context_class_init(SPRectContextClass *klass); +static void sp_rect_context_init(SPRectContext *rect_context); +static void sp_rect_context_dispose(GObject *object); + +static void sp_rect_context_setup(SPEventContext *ec); +static void sp_rect_context_set(SPEventContext *ec, gchar const *key, gchar const *val); + +static gint sp_rect_context_root_handler(SPEventContext *event_context, GdkEvent *event); +static gint sp_rect_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event); + +static void sp_rect_drag(SPRectContext &rc, NR::Point const pt, guint state); +static void sp_rect_finish(SPRectContext *rc); + +static SPEventContextClass *parent_class; + + +GtkType sp_rect_context_get_type() +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPRectContextClass), + NULL, NULL, + (GClassInitFunc) sp_rect_context_class_init, + NULL, NULL, + sizeof(SPRectContext), + 4, + (GInstanceInitFunc) sp_rect_context_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPRectContext", &info, (GTypeFlags) 0); + } + return type; +} + +static void sp_rect_context_class_init(SPRectContextClass *klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + SPEventContextClass *event_context_class = (SPEventContextClass *) klass; + + parent_class = (SPEventContextClass *) g_type_class_peek_parent(klass); + + object_class->dispose = sp_rect_context_dispose; + + event_context_class->setup = sp_rect_context_setup; + event_context_class->set = sp_rect_context_set; + event_context_class->root_handler = sp_rect_context_root_handler; + event_context_class->item_handler = sp_rect_context_item_handler; +} + +static void sp_rect_context_init(SPRectContext *rect_context) +{ + SPEventContext *event_context = SP_EVENT_CONTEXT(rect_context); + + event_context->cursor_shape = cursor_rect_xpm; + event_context->hot_x = 4; + event_context->hot_y = 4; + event_context->xp = 0; + event_context->yp = 0; + event_context->tolerance = 0; + event_context->within_tolerance = false; + event_context->item_to_select = NULL; + + event_context->shape_repr = NULL; + event_context->shape_knot_holder = NULL; + + rect_context->item = NULL; + + rect_context->rx = 0.0; + rect_context->ry = 0.0; + + new (&rect_context->sel_changed_connection) sigc::connection(); +} + +static void sp_rect_context_dispose(GObject *object) +{ + SPRectContext *rc = SP_RECT_CONTEXT(object); + SPEventContext *ec = SP_EVENT_CONTEXT(object); + + ec->enableGrDrag(false); + + rc->sel_changed_connection.disconnect(); + rc->sel_changed_connection.~connection(); + + /* fixme: This is necessary because we do not grab */ + if (rc->item) { + sp_rect_finish(rc); + } + + if (ec->shape_knot_holder) { + sp_knot_holder_destroy(ec->shape_knot_holder); + ec->shape_knot_holder = NULL; + } + + if (ec->shape_repr) { // remove old listener + sp_repr_remove_listener_by_data(ec->shape_repr, ec); + Inkscape::GC::release(ec->shape_repr); + ec->shape_repr = 0; + } + + if (rc->_message_context) { + delete rc->_message_context; + } + + G_OBJECT_CLASS(parent_class)->dispose(object); +} + +static Inkscape::XML::NodeEventVector ec_shape_repr_events = { + NULL, /* child_added */ + NULL, /* child_removed */ + ec_shape_event_attr_changed, + NULL, /* content_changed */ + NULL /* order_changed */ +}; + +/** +\brief Callback that processes the "changed" signal on the selection; +destroys old and creates new knotholder +*/ +void sp_rect_context_selection_changed(Inkscape::Selection *selection, gpointer data) +{ + SPRectContext *rc = SP_RECT_CONTEXT(data); + SPEventContext *ec = SP_EVENT_CONTEXT(rc); + + if (ec->shape_knot_holder) { // destroy knotholder + sp_knot_holder_destroy(ec->shape_knot_holder); + ec->shape_knot_holder = NULL; + } + + if (ec->shape_repr) { // remove old listener + sp_repr_remove_listener_by_data(ec->shape_repr, ec); + Inkscape::GC::release(ec->shape_repr); + ec->shape_repr = 0; + } + + SPItem *item = selection->singleItem(); + if (item) { + ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop); + Inkscape::XML::Node *shape_repr = SP_OBJECT_REPR(item); + if (shape_repr) { + ec->shape_repr = shape_repr; + Inkscape::GC::anchor(shape_repr); + sp_repr_add_listener(shape_repr, &ec_shape_repr_events, ec); + sp_repr_synthesize_events(shape_repr, &ec_shape_repr_events, ec); + } + } +} + +static void sp_rect_context_setup(SPEventContext *ec) +{ + SPRectContext *rc = SP_RECT_CONTEXT(ec); + + if (((SPEventContextClass *) parent_class)->setup) { + ((SPEventContextClass *) parent_class)->setup(ec); + } + + SPItem *item = SP_DT_SELECTION(ec->desktop)->singleItem(); + if (item) { + ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop); + Inkscape::XML::Node *shape_repr = SP_OBJECT_REPR(item); + if (shape_repr) { + ec->shape_repr = shape_repr; + Inkscape::GC::anchor(shape_repr); + sp_repr_add_listener(shape_repr, &ec_shape_repr_events, ec); + sp_repr_synthesize_events(shape_repr, &ec_shape_repr_events, ec); + } + } + + rc->sel_changed_connection.disconnect(); + rc->sel_changed_connection = SP_DT_SELECTION(ec->desktop)->connectChanged( + sigc::bind(sigc::ptr_fun(&sp_rect_context_selection_changed), (gpointer)rc) + ); + + sp_event_context_read(ec, "rx"); + sp_event_context_read(ec, "ry"); + + if (prefs_get_int_attribute("tools.shapes", "selcue", 0) != 0) { + ec->enableSelectionCue(); + } + + if (prefs_get_int_attribute("tools.shapes", "gradientdrag", 0) != 0) { + ec->enableGrDrag(); + } + + rc->_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack()); +} + +static void sp_rect_context_set(SPEventContext *ec, gchar const *key, gchar const *val) +{ + SPRectContext *rc = SP_RECT_CONTEXT(ec); + + /* fixme: Proper error handling for non-numeric data. Use a locale-independent function like + * g_ascii_strtod (or a thin wrapper that does the right thing for invalid values inf/nan). */ + if ( strcmp(key, "rx") == 0 ) { + rc->rx = ( val + ? g_ascii_strtod (val, NULL) + : 0.0 ); + } else if ( strcmp(key, "ry") == 0 ) { + rc->ry = ( val + ? g_ascii_strtod (val, NULL) + : 0.0 ); + } +} + +static gint sp_rect_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event) +{ + SPDesktop *desktop = event_context->desktop; + + gint ret = FALSE; + + switch (event->type) { + case GDK_BUTTON_PRESS: + if ( event->button.button == 1 ) { + Inkscape::setup_for_drag_start(desktop, event_context, event); + ret = TRUE; + } + break; + // motion and release are always on root (why?) + default: + break; + } + + if (((SPEventContextClass *) parent_class)->item_handler) { + ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event); + } + + return ret; +} + +static gint sp_rect_context_root_handler(SPEventContext *event_context, GdkEvent *event) +{ + static bool dragging; + + SPDesktop *desktop = event_context->desktop; + Inkscape::Selection *selection = SP_DT_SELECTION (desktop); + + SPRectContext *rc = SP_RECT_CONTEXT(event_context); + + event_context->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100); + + gint ret = FALSE; + switch (event->type) { + case GDK_BUTTON_PRESS: + if ( event->button.button == 1 ) { + NR::Point const button_w(event->button.x, + event->button.y); + + // save drag origin + event_context->xp = (gint) button_w[NR::X]; + event_context->yp = (gint) button_w[NR::Y]; + event_context->within_tolerance = true; + + // remember clicked item, disregarding groups, honoring Alt + event_context->item_to_select = sp_event_context_find_item (desktop, button_w, event->button.state & GDK_MOD1_MASK, TRUE); + + dragging = true; + + /* Position center */ + NR::Point const button_dt(desktop->w2d(button_w)); + + /* Snap center */ + SnapManager const m(desktop->namedview); + rc->center = m.freeSnap(Inkscape::Snapper::SNAP_POINT | Inkscape::Snapper::BBOX_POINT, + button_dt, rc->item).getPoint(); + + sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), + ( GDK_KEY_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK | + GDK_POINTER_MOTION_HINT_MASK | + GDK_BUTTON_PRESS_MASK ), + NULL, event->button.time); + ret = TRUE; + } + break; + case GDK_MOTION_NOTIFY: + if ( dragging + && ( event->motion.state & GDK_BUTTON1_MASK ) ) + { + if ( event_context->within_tolerance + && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance ) + && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) { + break; // do not drag if we're within tolerance from origin + } + // Once the user has moved farther than tolerance from the original location + // (indicating they intend to draw, not click), then always process the + // motion notify coordinates as given (no snapping back to origin) + event_context->within_tolerance = false; + + NR::Point const motion_w(event->motion.x, + event->motion.y); + NR::Point const motion_dt(desktop->w2d(motion_w)); + sp_rect_drag(*rc, motion_dt, event->motion.state); + ret = TRUE; + } + break; + case GDK_BUTTON_RELEASE: + event_context->xp = event_context->yp = 0; + if ( event->button.button == 1 ) { + dragging = false; + + if (!event_context->within_tolerance) { + // we've been dragging, finish the rect + sp_rect_finish(rc); + } else if (event_context->item_to_select) { + // no dragging, select clicked item if any + if (event->button.state & GDK_SHIFT_MASK) { + selection->toggle(event_context->item_to_select); + } else { + selection->set(event_context->item_to_select); + } + } else { + // click in an empty space + selection->clear(); + } + + event_context->item_to_select = NULL; + ret = TRUE; + sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), + event->button.time); + } + break; + case GDK_KEY_PRESS: + switch (get_group0_keyval (&event->key)) { + case GDK_Alt_L: + case GDK_Alt_R: + case GDK_Control_L: + case GDK_Control_R: + case GDK_Shift_L: + case GDK_Shift_R: + case GDK_Meta_L: // Meta is when you press Shift+Alt (at least on my machine) + case GDK_Meta_R: + sp_event_show_modifier_tip (event_context->defaultMessageContext(), event, + _("Ctrl: make square or integer-ratio rect, lock a rounded corner circular"), + _("Shift: draw around the starting point"), + NULL); + break; + case GDK_Up: + case GDK_Down: + case GDK_KP_Up: + case GDK_KP_Down: + // prevent the zoom field from activation + if (!MOD__CTRL_ONLY) + ret = TRUE; + break; + + case GDK_x: + case GDK_X: + if (MOD__ALT_ONLY) { + desktop->setToolboxFocusTo ("altx-rect"); + ret = TRUE; + } + break; + + case GDK_Escape: + SP_DT_SELECTION(desktop)->clear(); + //TODO: make dragging escapable by Esc + default: + break; + } + break; + case GDK_KEY_RELEASE: + switch (get_group0_keyval (&event->key)) { + case GDK_Alt_L: + case GDK_Alt_R: + case GDK_Control_L: + case GDK_Control_R: + case GDK_Shift_L: + case GDK_Shift_R: + case GDK_Meta_L: // Meta is when you press Shift+Alt + case GDK_Meta_R: + event_context->defaultMessageContext()->clear(); + break; + default: + break; + } + break; + default: + break; + } + + if (!ret) { + if (((SPEventContextClass *) parent_class)->root_handler) { + ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event); + } + } + + return ret; +} + +static void sp_rect_drag(SPRectContext &rc, NR::Point const pt, guint state) +{ + SPDesktop *desktop = SP_EVENT_CONTEXT(&rc)->desktop; + + if (!rc.item) { + + if (Inkscape::have_viable_layer(desktop, rc._message_context) == false) { + return; + } + + /* Create object */ + Inkscape::XML::Node *repr = sp_repr_new("svg:rect"); + + /* Set style */ + sp_desktop_apply_style_tool (desktop, repr, "tools.shapes.rect", false); + + rc.item = (SPItem *) desktop->currentLayer()->appendChildRepr(repr); + Inkscape::GC::release(repr); + rc.item->transform = SP_ITEM(desktop->currentRoot())->getRelativeTransform(desktop->currentLayer()); + rc.item->updateRepr(); + } + + NR::Rect const r = Inkscape::snap_rectangular_box(desktop, rc.item, pt, rc.center, state); + + sp_rect_position_set(SP_RECT(rc.item), r.min()[NR::X], r.min()[NR::Y], r.dimensions()[NR::X], r.dimensions()[NR::Y]); + if ( rc.rx != 0.0 ) { + sp_rect_set_rx (SP_RECT(rc.item), TRUE, rc.rx); + } + if ( rc.ry != 0.0 ) { + if (rc.rx == 0.0) + sp_rect_set_ry (SP_RECT(rc.item), TRUE, CLAMP(rc.ry, 0, MIN(r.dimensions()[NR::X], r.dimensions()[NR::Y])/2)); + else + sp_rect_set_ry (SP_RECT(rc.item), TRUE, CLAMP(rc.ry, 0, r.dimensions()[NR::Y])); + } + + // status text + GString *xs = SP_PX_TO_METRIC_STRING(r.dimensions()[NR::X], desktop->namedview->getDefaultMetric()); + GString *ys = SP_PX_TO_METRIC_STRING(r.dimensions()[NR::Y], desktop->namedview->getDefaultMetric()); + rc._message_context->setF(Inkscape::NORMAL_MESSAGE, _("Rectangle: %s × %s; with Ctrl to make square or integer-ratio rectangle; with Shift to draw around the starting point"), xs->str, ys->str); + g_string_free(xs, FALSE); + g_string_free(ys, FALSE); +} + +static void sp_rect_finish(SPRectContext *rc) +{ + rc->_message_context->clear(); + + if ( rc->item != NULL ) { + SPDesktop * dt; + + dt = SP_EVENT_CONTEXT_DESKTOP(rc); + + SP_OBJECT(rc->item)->updateRepr(); + + SP_DT_SELECTION(dt)->set(rc->item); + sp_document_done(SP_DT_DOCUMENT(dt)); + + rc->item = NULL; + } +} + +/* + 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 : diff --git a/src/rect-context.h b/src/rect-context.h new file mode 100644 index 000000000..782102b73 --- /dev/null +++ b/src/rect-context.h @@ -0,0 +1,51 @@ +#ifndef __SP_RECT_CONTEXT_H__ +#define __SP_RECT_CONTEXT_H__ + +/* + * Rectangle drawing context + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2000 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL + */ + +#include +#include "event-context.h" +#include "libnr/nr-point.h" +struct SPKnotHolder; + +#define SP_TYPE_RECT_CONTEXT (sp_rect_context_get_type ()) +#define SP_RECT_CONTEXT(obj) (GTK_CHECK_CAST ((obj), SP_TYPE_RECT_CONTEXT, SPRectContext)) +#define SP_RECT_CONTEXT_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), SP_TYPE_RECT_CONTEXT, SPRectContextClass)) +#define SP_IS_RECT_CONTEXT(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_RECT_CONTEXT)) +#define SP_IS_RECT_CONTEXT_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), SP_TYPE_RECT_CONTEXT)) + +class SPRectContext; +class SPRectContextClass; + +struct SPRectContext : public SPEventContext { + SPItem *item; + NR::Point center; + + gdouble rx; /* roundness radius (x direction) */ + gdouble ry; /* roundness radius (y direction) */ + + sigc::connection sel_changed_connection; + + Inkscape::MessageContext *_message_context; +}; + +struct SPRectContextClass { + SPEventContextClass parent_class; +}; + +/* Standard Gtk function */ + +GtkType sp_rect_context_get_type (void); + +#endif diff --git a/src/registrytool.cpp b/src/registrytool.cpp new file mode 100644 index 000000000..6747a0dd8 --- /dev/null +++ b/src/registrytool.cpp @@ -0,0 +1,211 @@ +/** + * Inkscape Registry Tool + * + * This simple tool is intended for allowing Inkscape to append subdirectories + * to its path. This will allow extensions and other files to be accesses + * without explicit user intervention. + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2005 Bob Jamison + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include + +#include "registrytool.h" + + +typedef struct +{ + HKEY key; + int strlen; + char *str; +} KeyTableEntry; + + + +KeyTableEntry keyTable[] = +{ + { HKEY_CLASSES_ROOT, 18, "HKEY_CLASSES_ROOT\\" }, + { HKEY_CURRENT_CONFIG, 20, "HKEY_CURRENT_CONFIG\\" }, + { HKEY_CURRENT_USER, 18, "HKEY_CURRENT_USER\\" }, + { HKEY_LOCAL_MACHINE, 19, "HKEY_LOCAL_MACHINE\\" }, + { HKEY_USERS, 11, "HKEY_USERS\\" }, + { NULL, 0, NULL } +}; + +bool RegistryTool::setStringValue(const std::string &keyNameArg, + const std::string &valueName, + const std::string &value) +{ + std::string keyName = keyNameArg; + + HKEY rootKey = HKEY_LOCAL_MACHINE; //default root + //Trim out the root key if necessary + for (KeyTableEntry *entry = keyTable; entry->key; entry++) + { + if (keyName.compare(0, entry->strlen, entry->str)==0) + { + rootKey = entry->key; + keyName = keyName.substr(entry->strlen); + } + } + //printf("trimmed string: '%s'\n", keyName.c_str()); + + //Get or create the key + HKEY key; + if (RegCreateKeyEx(rootKey, keyName.c_str(), + 0, NULL, REG_OPTION_NON_VOLATILE, + KEY_WRITE, NULL, &key, NULL)) + { + printf("RegistryTool: Could not create the registry key '%s'\n", keyName.c_str()); + return false; + } + + //Set the value + if (RegSetValueEx(key, valueName.c_str(), + 0, REG_SZ, (LPBYTE) value.c_str(), (DWORD) value.size())) + { + printf("RegistryTool: Could not set the value '%s'\n", value.c_str()); + RegCloseKey(key); + return false; + } + + + RegCloseKey(key); + + return true; +} + +bool RegistryTool::getExeInfo(std::string &fullPath, + std::string &path, + std::string &exeName) +{ + + char buf[MAX_PATH+1]; + if (!GetModuleFileName(NULL, buf, MAX_PATH)) + { + printf("Could not fetch executable file name\n"); + return false; + } + else + { + //printf("Executable file name: '%s'\n", buf); + } + + fullPath = buf; + path = ""; + exeName = ""; + std::string::size_type pos = fullPath.rfind('\\'); + if (pos != fullPath.npos) + { + path = fullPath.substr(0, pos); + exeName = fullPath.substr(pos+1); + } + + return true; +} + + +bool RegistryTool::setPathInfo() +{ + std::string fullPath; + std::string path; + std::string exeName; + + if (!getExeInfo(fullPath, path, exeName)) + return false; + + //printf("full:'%s' path:'%s' exe:'%s'\n", + // fullPath.c_str(), path.c_str(), exeName.c_str()); + + std::string keyName = + "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\"; + keyName.append(exeName); + + std::string valueName = ""; + std::string value = fullPath; + + if (!setStringValue(keyName, valueName, value)) + return false; + + //add our subdirectories + std::string appPath = path; + appPath.append("\\python;"); + appPath.append(path); + appPath.append("\\perl"); + valueName = "Path"; + value = appPath; + + if (!setStringValue(keyName, valueName, value)) + return false; + + return true; +} + + +#ifdef TESTREG + +void testReg() +{ + RegistryTool rt; + char *key = + "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\inkscape.exe"; + char *name = ""; + char *value = "c:\\inkscape\\inkscape.exe"; + if (!rt.setStringValue(key, name, value)) + { + printf("Test failed\n"); + } + else + { + printf("Test succeeded\n"); + } + name = "Path"; + value = "c:\\inkscape\\python"; + if (!rt.setStringValue(key, name, value)) + { + printf("Test failed\n"); + } + else + { + printf("Test succeeded\n"); + } +} + + +void testPath() +{ + RegistryTool rt; + rt.setPathInfo(); +} + + +int main(int argc, char **argv) +{ + //testReg(); + testPath(); + return 0; +} + +#endif /* TESTREG */ + +//######################################################################## +//# E N D O F F I L E +//######################################################################## diff --git a/src/registrytool.h b/src/registrytool.h new file mode 100644 index 000000000..9aa644189 --- /dev/null +++ b/src/registrytool.h @@ -0,0 +1,56 @@ +#ifndef __REGISTRYTOOL_H__ +#define __REGISTRYTOOL_H__ +/** + * Inkscape Registry Tool + * + * This simple tool is intended for allowing Inkscape to append subdirectories + * to its path. This will allow extensions and other files to be accesses + * without explicit user intervention. + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2005 Bob Jamison + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +class RegistryTool +{ +public: + + RegistryTool() + {} + + virtual ~RegistryTool() + {} + + bool setStringValue(const std::string &key, + const std::string &valueName, + const std::string &value); + + bool getExeInfo(std::string &fullPath, + std::string &path, + std::string &exeName); + + bool setPathInfo(); + + +}; + +#endif /* __REGISTRYTOOL_H__ */ + diff --git a/src/remove-last.h b/src/remove-last.h new file mode 100644 index 000000000..4d5df3f54 --- /dev/null +++ b/src/remove-last.h @@ -0,0 +1,31 @@ +#ifndef __REMOVE_LAST_H__ +#define __REMOVE_LAST_H__ + +#include +#include + +template +inline void remove_last(std::vector &seq, T const &elem) +{ + using std::vector; + + typename vector::reverse_iterator i(find(seq.rbegin(), seq.rend(), elem)); + g_assert( i != seq.rend() ); + typename vector::iterator ii(&*i); + seq.erase(ii); +} + + +#endif /* !__REMOVE_LAST_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:encoding=utf-8:textwidth=99 : diff --git a/src/removeoverlap/.cvsignore b/src/removeoverlap/.cvsignore new file mode 100644 index 000000000..e8014d011 --- /dev/null +++ b/src/removeoverlap/.cvsignore @@ -0,0 +1,5 @@ +Makefile +Makefile.in +.deps +makefile +.dirstamp diff --git a/src/removeoverlap/Makefile_insert b/src/removeoverlap/Makefile_insert new file mode 100644 index 000000000..e28304431 --- /dev/null +++ b/src/removeoverlap/Makefile_insert @@ -0,0 +1,31 @@ +## Makefile.am fragment sourced by src/Makefile.am. + +removeoverlap/all: removeoverlap/libremoveoverlap.a + +removeoverlap/clean: + rm -f removeoverlap/libremoveoverlap.a $(removeoverlap_libremoveoverlap_a_OBJECTS) + +removeoverlap_libremoveoverlap_a_SOURCES = \ + removeoverlap/block.cpp \ + removeoverlap/block.h \ + removeoverlap/blocks.cpp \ + removeoverlap/blocks.h \ + removeoverlap/constraint.cpp \ + removeoverlap/constraint.h \ + removeoverlap/generate-constraints.cpp \ + removeoverlap/generate-constraints.h \ + removeoverlap/remove_rectangle_overlap.cpp \ + removeoverlap/remove_rectangle_overlap.h \ + removeoverlap/removeoverlap.cpp \ + removeoverlap/removeoverlap.h \ + removeoverlap/solve_VPSC.cpp \ + removeoverlap/solve_VPSC.h \ + removeoverlap/variable.cpp \ + removeoverlap/variable.h \ + removeoverlap/pairingheap/dsexceptions.h \ + removeoverlap/pairingheap/PairingHeap.cpp \ + removeoverlap/pairingheap/PairingHeap.h + +removeoverlap_remove_rectangle_overlap_test_SOURCES = \ + removeoverlap/remove_rectangle_overlap-test.cpp +removeoverlap_remove_rectangle_overlap_test_LDADD = removeoverlap/libremoveoverlap.a -lglib-2.0 diff --git a/src/removeoverlap/block.cpp b/src/removeoverlap/block.cpp new file mode 100644 index 000000000..799fa5d8e --- /dev/null +++ b/src/removeoverlap/block.cpp @@ -0,0 +1,279 @@ +/** + * \brief Remove overlaps function + * + * Authors: + * Tim Dwyer + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ + + +#include "constraint.h" +#include "block.h" +#include "blocks.h" +#include "pairingheap/PairingHeap.h" +#ifdef RECTANGLE_OVERLAP_LOGGING +using std::ios; +using std::ofstream; +using std::endl; +#endif +using std::vector; + +void Block::addVariable(Variable *v) { + v->block=this; + vars->push_back(v); + weight+=v->weight; + wposn += v->weight * (v->desiredPosition - v->offset); + posn=wposn/weight; +} +Block::Block(Variable *v) { + timeStamp=0; + posn=weight=wposn=0; + in=NULL; + out=NULL; + deleted=false; + vars=new vector; + if(v!=NULL) { + v->offset=0; + addVariable(v); + } +} + +double Block::desiredWeightedPosition() { + double wp = 0; + for (vector::iterator v=vars->begin();v!=vars->end();v++) { + wp += ((*v)->desiredPosition - (*v)->offset) * (*v)->weight; + } + return wp; +} +Block::~Block(void) +{ + delete vars; + delete in; + delete out; +} +void Block::setUpInConstraints() { + setUpConstraintHeap(in,true); +} +void Block::setUpOutConstraints() { + setUpConstraintHeap(out,false); +} +void Block::setUpConstraintHeap(PairingHeap* &h,bool in) { + delete h; + h = new PairingHeap(&compareConstraints); + for (vector::iterator i=vars->begin();i!=vars->end();i++) { + Variable *v=*i; + vector *cs=in?&(v->in):&(v->out); + for (vector::iterator j=cs->begin();j!=cs->end();j++) { + Constraint *c=*j; + c->timeStamp=blockTimeCtr; + if (c->left->block != this && in || c->right->block != this && !in) { + h->insert(c); + } + } + } +} +/** + * Merges b into this block across c. Can be either a + * right merge or a left merge + * @param b block to merge into this + * @param c constraint being merged + * @param distance separation required to satisfy c + */ +void Block::merge(Block *b, Constraint *c, double dist) { + c->active=true; + wposn+=b->wposn-dist*b->weight; + weight+=b->weight; + posn=wposn/weight; + for(vector::iterator i=b->vars->begin();i!=b->vars->end();i++) { + Variable *v=*i; + v->block=this; + v->offset+=dist; + vars->push_back(v); + } +} + +void Block::mergeIn(Block *b) { +#ifdef RECTANGLE_OVERLAP_LOGGING + ofstream f(LOGFILE,ios::app); + f<<" merging constraint heaps... "<findMinInConstraint(); + in->merge(b->in); +} +void Block::mergeOut(Block *b) { + findMinOutConstraint(); + b->findMinOutConstraint(); + out->merge(b->out); +} +Constraint *Block::findMinInConstraint() { + Constraint *v = NULL; + vector outOfDate; + while (!in->isEmpty()) { + v = in->findMin(); + Block *lb=v->left->block; + Block *rb=v->right->block; + // rb may not be this if called between merge and mergeIn +#ifdef RECTANGLE_OVERLAP_LOGGING + ofstream f(LOGFILE,ios::app); + f<<" checking constraint ... "<<*v; + f<<" timestamps: left="<timeStamp<<" right="<timeStamp<<" constraint="<timeStamp<deleteMin(); +#ifdef RECTANGLE_OVERLAP_LOGGING + f<<" ... skipping internal constraint"<timeStamp > rb->timeStamp + && v->timeStamp < lb->timeStamp + || v->timeStamp < rb->timeStamp) { + // block at other end of constraint has been moved since this + in->deleteMin(); + outOfDate.push_back(v); +#ifdef RECTANGLE_OVERLAP_LOGGING + f<<" reinserting out of date constraint"<::iterator i=outOfDate.begin();i!=outOfDate.end();i++) { + v=*i; + v->timeStamp=blockTimeCtr; + in->insert(v); + } + if(in->isEmpty()) { + v=NULL; + } else { + v=in->findMin(); + } + return v; +} +Constraint *Block::findMinOutConstraint() { + if(out->isEmpty()) return NULL; + Constraint *v = out->findMin(); + while (v->left->block == v->right->block) { + out->deleteMin(); + if(out->isEmpty()) return NULL; + v = out->findMin(); + } + return v; +} +void Block::deleteMinInConstraint() { + in->deleteMin(); +} +void Block::deleteMinOutConstraint() { + out->deleteMin(); +} +inline bool Block::canFollowLeft(Constraint *c, Variable *last) { + return c->left->block==this && c->active && last!=c->left; +} +inline bool Block::canFollowRight(Constraint *c, Variable *last) { + return c->right->block==this && c->active && last!=c->right; +} + +// computes the derivative of v and the lagrange multipliers +// of v's out constraints (as the recursive sum of those below. +// Does not backtrack over u. +// also records the constraint with minimum lagrange multiplier +// in min_lm +double Block::compute_dfdv(Variable *v, Variable *u, Constraint *&min_lm) { + double dfdv=v->weight*(v->position() - v->desiredPosition); + for(vector::iterator it=v->out.begin();it!=v->out.end();it++) { + Constraint *c=*it; + if(canFollowRight(c,u)) { + dfdv+=c->lm=compute_dfdv(c->right,v,min_lm); + if(min_lm==NULL||c->lmlm) min_lm=c; + } + } + for(vector::iterator it=v->in.begin();it!=v->in.end();it++) { + Constraint *c=*it; + if(canFollowLeft(c,u)) { + dfdv-=c->lm=-compute_dfdv(c->left,v,min_lm); + if(min_lm==NULL||c->lmlm) min_lm=c; + } + } + return dfdv; +} + +// resets LMs for all active constraints to 0 by +// traversing active constraint tree starting from v, +// not back tracking over u +void Block::reset_active_lm(Variable *v, Variable *u) { + for(vector::iterator it=v->out.begin();it!=v->out.end();it++) { + Constraint *c=*it; + if(canFollowRight(c,u)) { + c->lm=0; + reset_active_lm(c->right,v); + } + } + for(vector::iterator it=v->in.begin();it!=v->in.end();it++) { + Constraint *c=*it; + if(canFollowLeft(c,u)) { + c->lm=0; + reset_active_lm(c->left,v); + } + } +} +/** + * finds the constraint with the minimum lagrange multiplier, that is, the constraint + * that most wants to split + */ +Constraint *Block::findMinLM() { + Constraint *min_lm=NULL; + reset_active_lm(vars->front(),NULL); + compute_dfdv(vars->front(),NULL,min_lm); + return min_lm; +} + +// populates block b by traversing the active constraint tree adding variables as they're +// visited. Starts from variable v and does not backtrack over variable u. +void Block::populateSplitBlock(Block *b, Variable *v, Variable *u) { + b->addVariable(v); + for (vector::iterator c=v->in.begin();c!=v->in.end();c++) { + if (canFollowLeft(*c,u)) + populateSplitBlock(b, (*c)->left, v); + } + for (vector::iterator c=v->out.begin();c!=v->out.end();c++) { + if (canFollowRight(*c,u)) + populateSplitBlock(b, (*c)->right, v); + } +} +/** + * Creates two new blocks, l and r, and splits this block across constraint c, + * placing the left subtree of constraints (and associated variables) into l + * and the right into r + */ +void Block::split(Block *&l, Block *&r, Constraint *c) { + c->active=false; + l=new Block(); + populateSplitBlock(l,c->left,c->right); + r=new Block(); + populateSplitBlock(r,c->right,c->left); +} + +/** + * Computes the cost (squared euclidean distance from desired positions) of the + * current positions for variables in this block + */ +double Block::cost() { + double c = 0; + for (vector::iterator v=vars->begin();v!=vars->end();v++) { + double diff = (*v)->position() - (*v)->desiredPosition; + c += (*v)->weight * diff * diff; + } + return c; +} +ostream& operator <<(ostream &os, const Block &b) +{ + os<<"Block:"; + for(vector::iterator v=b.vars->begin();v!=b.vars->end();v++) { + os<<" "<<**v; + } + return os; +} diff --git a/src/removeoverlap/block.h b/src/removeoverlap/block.h new file mode 100644 index 000000000..7905309bb --- /dev/null +++ b/src/removeoverlap/block.h @@ -0,0 +1,59 @@ +/** + * \brief Remove overlaps function + * + * Authors: + * Tim Dwyer + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ + +#ifndef SEEN_REMOVEOVERLAP_BLOCK_H +#define SEEN_REMOVEOVERLAP_BLOCK_H + +#include +#include +class Variable; +class Constraint; +template class PairingHeap; +class StupidPriorityQueue; + +class Block +{ + friend std::ostream& operator <<(std::ostream &os,const Block &b); +public: + std::vector *vars; + double posn; + double weight; + double wposn; + Block(Variable *v=NULL); + ~Block(void); + Constraint *findMinLM(); + Constraint *findMinInConstraint(); + Constraint *findMinOutConstraint(); + void deleteMinInConstraint(); + void deleteMinOutConstraint(); + double desiredWeightedPosition(); + void merge(Block *b, Constraint *c, double dist); + void mergeIn(Block *b); + void mergeOut(Block *b); + void split(Block *&l, Block *&r, Constraint *c); + void setUpInConstraints(); + void setUpOutConstraints(); + double cost(); + bool deleted; + long timeStamp; + PairingHeap *in; + PairingHeap *out; +private: + void reset_active_lm(Variable *v, Variable *u); + double compute_dfdv(Variable *v, Variable *u, Constraint *&min_lm); + bool canFollowLeft(Constraint *c, Variable *last); + bool canFollowRight(Constraint *c, Variable *last); + void populateSplitBlock(Block *b, Variable *v, Variable *u); + void addVariable(Variable *v); + void setUpConstraintHeap(PairingHeap* &h,bool in); +}; + +#endif // SEEN_REMOVEOVERLAP_BLOCK_H diff --git a/src/removeoverlap/blocks.cpp b/src/removeoverlap/blocks.cpp new file mode 100644 index 000000000..45c479187 --- /dev/null +++ b/src/removeoverlap/blocks.cpp @@ -0,0 +1,190 @@ +/** + * \brief A block structure defined over the variables + * + * A block structure defined over the variables such that each block contains + * 1 or more variables, with the invariant that all constraints inside a block + * are satisfied by keeping the variables fixed relative to one another + * + * Authors: + * Tim Dwyer + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ + +#include "blocks.h" +#include "block.h" +#include "constraint.h" +#ifdef RECTANGLE_OVERLAP_LOGGING +using std::ios; +using std::ofstream; +using std::endl; +#endif +using std::set; +using std::vector; +using std::iterator; +using std::list; +using std::copy; + +long blockTimeCtr; + +Blocks::Blocks(Variable *vs[], const int n) : vs(vs),nvs(n) { + blockTimeCtr=0; + for(int i=0;i::iterator i=begin();i!=end();i++) { + delete *i; + } + clear(); +} + +/** + * returns a list of variables with total ordering determined by the constraint + * DAG + */ +list *Blocks::totalOrder() { + list *order = new list; + for(int i=0;ivisited=false; + } + for(int i=0;iin.size()==0) { + dfsVisit(vs[i],order); + } + } + return order; +} +// Recursive depth first search giving total order by pushing nodes in the DAG +// onto the front of the list when we finish searching them +void Blocks::dfsVisit(Variable *v, list *order) { + v->visited=true; + vector::iterator it=v->out.begin(); + for(;it!=v->out.end();it++) { + Constraint *c=*it; + if(!c->right->visited) { + dfsVisit(c->right, order); + } + } + order->push_front(v); +} +/** + * Processes incoming constraints, most violated to least, merging with the + * neighbouring (left) block until no more violated constraints are found + */ +void Blocks::mergeLeft(Block *r) { +#ifdef RECTANGLE_OVERLAP_LOGGING + ofstream f(LOGFILE,ios::app); + f<<"mergeLeft called on "<<*r<timeStamp=++blockTimeCtr; + r->setUpInConstraints(); + Constraint *c=r->findMinInConstraint(); + while (c != NULL && c->slack()<0) { +#ifdef RECTANGLE_OVERLAP_LOGGING + f<<"mergeLeft on constraint: "<<*c<deleteMinInConstraint(); + Block *l = c->left->block; + if (l->in==NULL) l->setUpInConstraints(); + double dist = c->right->offset - c->left->offset - c->gap; + if (r->vars->size() < l->vars->size()) { + dist=-dist; + std::swap(l, r); + } + r->merge(l, c, dist); + r->mergeIn(l); + r->timeStamp=++blockTimeCtr; + removeBlock(l); + c=r->findMinInConstraint(); + } +#ifdef RECTANGLE_OVERLAP_LOGGING + f<<"merged "<<*r<setUpOutConstraints(); + Constraint *c = l->findMinOutConstraint(); + while (c != NULL && c->slack()<0) { +#ifdef RECTANGLE_OVERLAP_LOGGING + f<<"mergeRight on constraint: "<<*c<deleteMinOutConstraint(); + Block *r = c->right->block; + r->setUpOutConstraints(); + double dist = c->left->offset + c->gap - c->right->offset; + if (l->vars->size() > r->vars->size()) { + dist=-dist; + std::swap(l, r); + } + l->merge(r, c, dist); + l->mergeOut(r); + removeBlock(r); + c=l->findMinOutConstraint(); + } +#ifdef RECTANGLE_OVERLAP_LOGGING + f<<"merged "<<*l<deleted=true; + //erase(doomed); +} +void Blocks::cleanup() { + vector bcopy(size()); + copy(begin(),end(),bcopy.begin()); + for(vector::iterator i=bcopy.begin();i!=bcopy.end();i++) { + Block *b=*i; + if(b->deleted) { + erase(b); + delete b; + } + } +} +/** + * Splits block b across constraint c into two new blocks, l and r (c's left + * and right sides respectively) + */ +void Blocks::split(Block *b, Block *&l, Block *&r, Constraint *c) { + b->split(l,r,c); +#ifdef RECTANGLE_OVERLAP_LOGGING + ofstream f(LOGFILE,ios::app); + f<<"Split left: "<<*l<posn = b->posn; + r->wposn = r->posn * r->weight; + mergeLeft(l); + // r may have been merged! + r = c->right->block; + r->wposn = r->desiredWeightedPosition(); + r->posn = r->wposn / r->weight; + mergeRight(r); + removeBlock(b); + + insert(l); + insert(r); +} +/** + * returns the cost total squared distance of variables from their desired + * positions + */ +double Blocks::cost() { + double c = 0; + for(set::iterator i=begin();i!=end();i++) { + c += (*i)->cost(); + } + return c; +} + diff --git a/src/removeoverlap/blocks.h b/src/removeoverlap/blocks.h new file mode 100644 index 000000000..437af6310 --- /dev/null +++ b/src/removeoverlap/blocks.h @@ -0,0 +1,49 @@ +/** + * \brief Remove overlaps function + * + * Authors: + * Tim Dwyer + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ + +#ifndef SEEN_REMOVEOVERLAP_BLOCKS_H +#define SEEN_REMOVEOVERLAP_BLOCKS_H + +#ifdef RECTANGLE_OVERLAP_LOGGING +#define LOGFILE "cRectangleOverlap.log" +#endif + +#include +#include + +class Block; +class Variable; +class Constraint; +/** + * A block structure defined over the variables such that each block contains + * 1 or more variables, with the invariant that all constraints inside a block + * are satisfied by keeping the variables fixed relative to one another + */ +class Blocks : public std::set +{ +public: + Blocks(Variable *vs[], const int n); + ~Blocks(void); + void mergeLeft(Block *r); + void mergeRight(Block *l); + void split(Block *b, Block *&l, Block *&r, Constraint *c); + std::list *totalOrder(); + void cleanup(); + double cost(); +private: + void dfsVisit(Variable *v, std::list *order); + void removeBlock(Block *doomed); + Variable **vs; + int nvs; +}; + +extern long blockTimeCtr; +#endif // SEEN_REMOVEOVERLAP_BLOCKS_H diff --git a/src/removeoverlap/constraint.cpp b/src/removeoverlap/constraint.cpp new file mode 100644 index 000000000..78c5f03ad --- /dev/null +++ b/src/removeoverlap/constraint.cpp @@ -0,0 +1,29 @@ +/** + * \brief Remove overlaps function + * + * Authors: + * Tim Dwyer + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ + +#include "constraint.h" + +Constraint::Constraint(Variable *left, Variable *right, double gap) +{ + this->left=left; + left->out.push_back(this); + this->right=right; + right->in.push_back(this); + this->gap=gap; + active=false; + visited=false; + timeStamp=0; +} +std::ostream& operator <<(std::ostream &os, const Constraint &c) +{ + os<<*c.left<<"+"< + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ + +#ifndef SEEN_REMOVEOVERLAP_CONSTRAINT_H +#define SEEN_REMOVEOVERLAP_CONSTRAINT_H + +#include +#include "variable.h" + +class Constraint +{ + friend std::ostream& operator <<(std::ostream &os,const Constraint &c); +public: + Variable *left; + Variable *right; + double gap; + double lm; + Constraint(Variable *left, Variable *right, double gap); + ~Constraint(void){}; + inline double Constraint::slack() const { return right->position() - gap - left->position(); } + //inline bool operator<(Constraint const &o) const { return slack() < o.slack(); } + long timeStamp; + bool active; + bool visited; +}; +#include +static inline bool compareConstraints(Constraint *&l, Constraint *&r) { + double sl = l->slack(); + double sr = r->slack(); + if(l->left->block==l->right->block) sl=DBL_MIN; + if(r->left->block==r->right->block) sr=DBL_MIN; + if(sl==sr) { + // arbitrary choice based on id + if(l->left->id==r->left->id) { + if(l->right->idright->id) return true; + return false; + } + if(l->left->idleft->id) return true; + return false; + } + return sl < sr; +} + +#endif // SEEN_REMOVEOVERLAP_CONSTRAINT_H diff --git a/src/removeoverlap/generate-constraints.cpp b/src/removeoverlap/generate-constraints.cpp new file mode 100644 index 000000000..3238a0ca9 --- /dev/null +++ b/src/removeoverlap/generate-constraints.cpp @@ -0,0 +1,302 @@ +/** + * \brief Remove overlaps function + * + * Authors: + * Tim Dwyer + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ + +#include +#include +#include "generate-constraints.h" +#include "constraint.h" + +#include "isnan.h" /* Include last */ + +using std::set; +using std::vector; + +std::ostream& operator <<(std::ostream &os, const Rectangle &r) { + os << "{"< NodeSet; + +struct Node { + Variable *v; + Rectangle *r; + double pos; + Node *firstAbove, *firstBelow; + NodeSet *leftNeighbours, *rightNeighbours; + Node(Variable *v, Rectangle *r, double p) : v(v),r(r),pos(p) { + firstAbove=firstBelow=NULL; + leftNeighbours=rightNeighbours=NULL; + } + ~Node() { + delete leftNeighbours; + delete rightNeighbours; + } + void addLeftNeighbour(Node *u) { + leftNeighbours->insert(u); + } + void addRightNeighbour(Node *u) { + rightNeighbours->insert(u); + } + void setNeighbours(NodeSet *left, NodeSet *right) { + leftNeighbours=left; + rightNeighbours=right; + for(NodeSet::iterator i=left->begin();i!=left->end();i++) { + Node *v=*(i); + v->addRightNeighbour(this); + } + for(NodeSet::iterator i=right->begin();i!=right->end();i++) { + Node *v=*(i); + v->addLeftNeighbour(this); + } + } +}; +bool CmpNodePos::operator() (const Node* u, const Node* v) const { + if (u->pos < v->pos) { + return true; + } + if (v->pos < u->pos) { + return false; + } + if (isNaN(u->pos) != isNaN(v->pos)) { + return isNaN(u->pos); + } + return u < v; + + /* I don't know how important it is to handle NaN correctly + * (e.g. we probably handle it badly in other code anyway, and + * in any case the best we can hope for is to reduce the + * badness of other nodes). + * + * Nevertheless, we try to do the right thing here and in + * event comparison. The issue is that (on platforms with + * ieee floating point comparison) NaN compares neither less + * than nor greater than any other number, yet sort wants a + * well-defined ordering. In particular, we want to ensure + * transitivity of equivalence, which normally wouldn't be + * guaranteed if the "middle" item in the transitivity + * involves a NaN. (NaN is neither less than nor greater than + * other numbers, so tends to be considered as equal to all + * other numbers: even unequal numbers.) + */ +} + +NodeSet* getLeftNeighbours(NodeSet &scanline,Node *v) { + NodeSet *leftv = new NodeSet; + NodeSet::iterator i=scanline.find(v); + while(i--!=scanline.begin()) { + Node *u=*(i); + if(u->r->overlapX(v->r)<=0) { + leftv->insert(u); + return leftv; + } + if(u->r->overlapX(v->r)<=u->r->overlapY(v->r)) { + leftv->insert(u); + } + } + return leftv; +} +NodeSet* getRightNeighbours(NodeSet &scanline,Node *v) { + NodeSet *rightv = new NodeSet; + NodeSet::iterator i=scanline.find(v); + for(i++;i!=scanline.end(); i++) { + Node *u=*(i); + if(u->r->overlapX(v->r)<=0) { + rightv->insert(u); + return rightv; + } + if(u->r->overlapX(v->r)<=u->r->overlapY(v->r)) { + rightv->insert(u); + } + } + return rightv; +} + +typedef enum {Open, Close} EventType; +struct Event { + EventType type; + Node *v; + double pos; + Event(EventType t, Node *v, double p) : type(t),v(v),pos(p) {}; +}; +Event **events; +int compare_events(const void *a, const void *b) { + Event *ea=*(Event**)a; + Event *eb=*(Event**)b; + if(ea->pos > eb->pos) { + return 1; + } else if(ea->pos < eb->pos) { + return -1; + } else if(isNaN(ea->pos) != isNaN(ea->pos)) { + /* See comment in CmpNodePos. */ + return ( isNaN(ea->pos) + ? -1 + : 1 ); + } else if(ea->v->r==ea->v->r) { + // when comparing opening and closing from the same rect + // open must come first + if(ea->type==Open) return -1; + return 1; + } + return 0; +} + +/** + * Prepares variables and constraints in order to apply VPSC horizontally. + * useNeighbourLists determines whether or not a heuristic is used to deciding whether to resolve + * all overlap in the x pass, or leave some overlaps for the y pass. + */ +int generateXConstraints(Rectangle *rs[], double weights[], const int n, Variable **&vars, Constraint **&cs, bool useNeighbourLists) { + events=new Event*[2*n]; + int i,m,ctr=0; + vector constraints; + vars=new Variable*[n]; + for(i=0;igetCentreX(),weights[i]); + Node *v = new Node(vars[i],rs[i],rs[i]->getCentreX()); + events[ctr++]=new Event(Open,v,rs[i]->getMinY()); + events[ctr++]=new Event(Close,v,rs[i]->getMaxY()); + } + qsort((Event*)events, (size_t)2*n, sizeof(Event*), compare_events ); + NodeSet scanline; + for(i=0;i<2*n;i++) { + Event *e=events[i]; + Node *v=e->v; + if(e->type==Open) { + scanline.insert(v); + if(useNeighbourLists) { + v->setNeighbours( + getLeftNeighbours(scanline,v), + getRightNeighbours(scanline,v) + ); + } else { + NodeSet::iterator i=scanline.find(v); + if(i--!=scanline.begin()) { + Node *u=*i; + v->firstAbove=u; + u->firstBelow=v; + } + i=scanline.find(v); + if(++i!=scanline.end()) { + Node *u=*i; + v->firstBelow=u; + u->firstAbove=v; + } + } + } else { + // Close event + int r; + if(useNeighbourLists) { + for(NodeSet::iterator i=v->leftNeighbours->begin(); + i!=v->leftNeighbours->end();i++ + ) { + Node *u=*i; + double sep = (v->r->width()+u->r->width())/2.0; + constraints.push_back(new Constraint(u->v,v->v,sep)); + r=u->rightNeighbours->erase(v); + } + + for(NodeSet::iterator i=v->rightNeighbours->begin(); + i!=v->rightNeighbours->end();i++ + ) { + Node *u=*i; + double sep = (v->r->width()+u->r->width())/2.0; + constraints.push_back(new Constraint(v->v,u->v,sep)); + r=u->leftNeighbours->erase(v); + } + } else { + Node *l=v->firstAbove, *r=v->firstBelow; + if(l!=NULL) { + double sep = (v->r->width()+l->r->width())/2.0; + constraints.push_back(new Constraint(l->v,v->v,sep)); + l->firstBelow=v->firstBelow; + } + if(r!=NULL) { + double sep = (v->r->width()+r->r->width())/2.0; + constraints.push_back(new Constraint(v->v,r->v,sep)); + r->firstAbove=v->firstAbove; + } + } + r=scanline.erase(v); + delete v; + } + delete e; + } + delete [] events; + cs=new Constraint*[m=constraints.size()]; + for(i=0;i constraints; + vars=new Variable*[n]; + for(i=0;igetCentreY(),weights[i]); + Node *v = new Node(vars[i],rs[i],rs[i]->getCentreY()); + events[ctr++]=new Event(Open,v,rs[i]->getMinX()); + events[ctr++]=new Event(Close,v,rs[i]->getMaxX()); + } + qsort((Event*)events, (size_t)2*n, sizeof(Event*), compare_events ); + NodeSet scanline; + for(i=0;i<2*n;i++) { + Event *e=events[i]; + Node *v=e->v; + if(e->type==Open) { + scanline.insert(v); + NodeSet::iterator i=scanline.find(v); + if(i--!=scanline.begin()) { + Node *u=*i; + v->firstAbove=u; + u->firstBelow=v; + } + i=scanline.find(v); + if(++i!=scanline.end()) { + Node *u=*i; + v->firstBelow=u; + u->firstAbove=v; + } + } else { + // Close event + Node *l=v->firstAbove, *r=v->firstBelow; + if(l!=NULL) { + double sep = (v->r->height()+l->r->height())/2.0; + constraints.push_back(new Constraint(l->v,v->v,sep)); + l->firstBelow=v->firstBelow; + } + if(r!=NULL) { + double sep = (v->r->height()+r->r->height())/2.0; + constraints.push_back(new Constraint(v->v,r->v,sep)); + r->firstAbove=v->firstAbove; + } + scanline.erase(v); + delete v; + } + delete e; + } + delete [] events; + cs=new Constraint*[m=constraints.size()]; + for(i=0;i + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ + +#ifndef SEEN_REMOVEOVERLAP_GENERATE_CONSTRAINTS_H +#define SEEN_REMOVEOVERLAP_GENERATE_CONSTRAINTS_H +#include + +class Rectangle { + friend std::ostream& operator <<(std::ostream &os, const Rectangle &r); +public: + static double xBorder,yBorder; + Rectangle(double x, double X, double y, double Y); + double getMaxX() const { return maxX+xBorder; } + double getMaxY() const { return maxY+yBorder; } + double getMinX() const { return minX; } + double getMinY() const { return minY; } + double getMinD(unsigned const d) const { + return ( d == 0 ? getMinX() : getMinY() ); + } + double getMaxD(unsigned const d) const { + return ( d == 0 ? getMaxX() : getMaxY() ); + } + double getCentreX() const { return minX+width()/2.0; } + double getCentreY() const { return minY+height()/2.0; } + double width() const { return getMaxX()-minX; } + double height() const { return getMaxY()-minY; } + static void setXBorder(double x) {xBorder=x;} + static void setYBorder(double y) {yBorder=y;} + void moveCentreX(double x) { + moveMinX(x-width()/2.0); + } + void moveCentreY(double y) { + moveMinY(y-height()/2.0); + } + void moveMinX(double x) { + maxX=x+width()-xBorder; + minX=x; + } + void moveMinY(double y) { + maxY=y+height()-yBorder; + minY=y; + } + inline double overlapX(Rectangle *r) const { + if (getCentreX() <= r->getCentreX() && r->minX < getMaxX()) + return getMaxX() - r->minX; + if (r->getCentreX() <= getCentreX() && minX < r->getMaxX()) + return r->getMaxX() - minX; + return 0; + } + inline double overlapY(Rectangle *r) const { + if (getCentreY() <= r->getCentreY() && r->minY < getMaxY()) + return getMaxY() - r->minY; + if (r->getCentreY() <= getCentreY() && minY < r->getMaxY()) + return r->getMaxY() - minY; + return 0; + } +private: + double minX,maxX,minY,maxY; +}; + + +class Variable; +class Constraint; + +// returns number of constraints generated +int generateXConstraints(Rectangle *rs[], double weights[], const int n, Variable **&vs, Constraint **&cs,bool useNeighbourLists); + +int generateYConstraints(Rectangle *rs[], double weights[], const int n, Variable **&vs, Constraint **&cs); + +#endif // SEEN_REMOVEOVERLAP_GENERATE_CONSTRAINTS_H diff --git a/src/removeoverlap/makefile.in b/src/removeoverlap/makefile.in new file mode 100644 index 000000000..480b25a90 --- /dev/null +++ b/src/removeoverlap/makefile.in @@ -0,0 +1,17 @@ +# Convenience stub makefile to call the real Makefile. + +@SET_MAKE@ + +# Explicit so that it's the default rule. +all: + cd .. && $(MAKE) removeoverlap/all + +clean %.a %.o: + cd .. && $(MAKE) removeoverlap/$@ + +.PHONY: all clean + +OBJEXT = @OBJEXT@ + +.SUFFIXES: +.SUFFIXES: .a .$(OBJEXT) diff --git a/src/removeoverlap/pairingheap/.cvsignore b/src/removeoverlap/pairingheap/.cvsignore new file mode 100644 index 000000000..e8014d011 --- /dev/null +++ b/src/removeoverlap/pairingheap/.cvsignore @@ -0,0 +1,5 @@ +Makefile +Makefile.in +.deps +makefile +.dirstamp diff --git a/src/removeoverlap/pairingheap/PairingHeap.cpp b/src/removeoverlap/pairingheap/PairingHeap.cpp new file mode 100644 index 000000000..9c67f44fa --- /dev/null +++ b/src/removeoverlap/pairingheap/PairingHeap.cpp @@ -0,0 +1,309 @@ +/** + * \brief Pairing heap datastructure implementation + * + * Based on example code in "Data structures and Algorithm Analysis in C++" + * by Mark Allen Weiss, used and released under the GPL by permission + * of the author. + * + * No promises about correctness. Use at your own risk! + * + * Authors: + * Mark Allen Weiss + * Tim Dwyer + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ + +#include + +#include "dsexceptions.h" +#include "PairingHeap.h" + +#ifndef PAIRING_HEAP_CPP +#define PAIRING_HEAP_CPP +using namespace std; +/** +* Construct the pairing heap. +*/ +template +PairingHeap::PairingHeap( bool (*lessThan)(T &lhs, T &rhs) ) +{ + root = NULL; + counter=0; + this->lessThan=lessThan; +} + + +/** +* Copy constructor +*/ +template +PairingHeap::PairingHeap( const PairingHeap & rhs ) +{ + root = NULL; + counter=rhs->size(); + *this = rhs; +} + +/** +* Destroy the leftist heap. +*/ +template +PairingHeap::~PairingHeap( ) +{ + makeEmpty( ); +} + +/** +* Insert item x into the priority queue, maintaining heap order. +* Return a pointer to the node containing the new item. +*/ +template +PairNode * +PairingHeap::insert( const T & x ) +{ + PairNode *newNode = new PairNode( x ); + + if( root == NULL ) + root = newNode; + else + compareAndLink( root, newNode ); + counter++; + return newNode; +} +template +int PairingHeap::size() { + return counter; +} +/** +* Find the smallest item in the priority queue. +* Return the smallest item, or throw Underflow if empty. +*/ +template +const T & PairingHeap::findMin( ) const +{ + if( isEmpty( ) ) + throw Underflow( ); + return root->element; +} +/** + * Remove the smallest item from the priority queue. + * Throws Underflow if empty. + */ +template +void PairingHeap::deleteMin( ) +{ + if( isEmpty( ) ) + throw Underflow( ); + + PairNode *oldRoot = root; + + if( root->leftChild == NULL ) + root = NULL; + else + root = combineSiblings( root->leftChild ); + counter--; + delete oldRoot; +} + +/** +* Test if the priority queue is logically empty. +* Returns true if empty, false otherwise. +*/ +template +bool PairingHeap::isEmpty( ) const +{ + return root == NULL; +} + +/** +* Test if the priority queue is logically full. +* Returns false in this implementation. +*/ +template +bool PairingHeap::isFull( ) const +{ + return false; +} + +/** +* Make the priority queue logically empty. +*/ +template +void PairingHeap::makeEmpty( ) +{ + reclaimMemory( root ); + root = NULL; +} + +/** +* Deep copy. +*/ +template +const PairingHeap & +PairingHeap::operator=( const PairingHeap & rhs ) +{ + if( this != &rhs ) + { + makeEmpty( ); + root = clone( rhs.root ); + } + + return *this; +} + +/** +* Internal method to make the tree empty. +* WARNING: This is prone to running out of stack space. +*/ +template +void PairingHeap::reclaimMemory( PairNode * t ) const +{ + if( t != NULL ) + { + reclaimMemory( t->leftChild ); + reclaimMemory( t->nextSibling ); + delete t; + } +} + +/** +* Change the value of the item stored in the pairing heap. +* Does nothing if newVal is larger than currently stored value. +* p points to a node returned by insert. +* newVal is the new value, which must be smaller +* than the currently stored value. +*/ +template +void PairingHeap::decreaseKey( PairNode *p, + const T & newVal ) +{ + if( p->element < newVal ) + return; // newVal cannot be bigger + p->element = newVal; + if( p != root ) + { + if( p->nextSibling != NULL ) + p->nextSibling->prev = p->prev; + if( p->prev->leftChild == p ) + p->prev->leftChild = p->nextSibling; + else + p->prev->nextSibling = p->nextSibling; + + p->nextSibling = NULL; + compareAndLink( root, p ); + } +} + +/** +* Internal method that is the basic operation to maintain order. +* Links first and second together to satisfy heap order. +* first is root of tree 1, which may not be NULL. +* first->nextSibling MUST be NULL on entry. +* second is root of tree 2, which may be NULL. +* first becomes the result of the tree merge. +*/ +template +void PairingHeap:: +compareAndLink( PairNode * & first, + PairNode *second ) const +{ + if( second == NULL ) + return; + if( lessThan(second->element,first->element) ) + { + // Attach first as leftmost child of second + second->prev = first->prev; + first->prev = second; + first->nextSibling = second->leftChild; + if( first->nextSibling != NULL ) + first->nextSibling->prev = first; + second->leftChild = first; + first = second; + } + else + { + // Attach second as leftmost child of first + second->prev = first; + first->nextSibling = second->nextSibling; + if( first->nextSibling != NULL ) + first->nextSibling->prev = first; + second->nextSibling = first->leftChild; + if( second->nextSibling != NULL ) + second->nextSibling->prev = second; + first->leftChild = second; + } +} + +/** +* Internal method that implements two-pass merging. +* firstSibling the root of the conglomerate; +* assumed not NULL. +*/ +template +PairNode * +PairingHeap::combineSiblings( PairNode *firstSibling ) const +{ + if( firstSibling->nextSibling == NULL ) + return firstSibling; + + // Allocate the array + static vector *> treeArray( 5 ); + + // Store the subtrees in an array + int numSiblings = 0; + for( ; firstSibling != NULL; numSiblings++ ) + { + if( numSiblings == (int)treeArray.size( ) ) + treeArray.resize( numSiblings * 2 ); + treeArray[ numSiblings ] = firstSibling; + firstSibling->prev->nextSibling = NULL; // break links + firstSibling = firstSibling->nextSibling; + } + if( numSiblings == (int)treeArray.size( ) ) + treeArray.resize( numSiblings + 1 ); + treeArray[ numSiblings ] = NULL; + + // Combine subtrees two at a time, going left to right + int i = 0; + for( ; i + 1 < numSiblings; i += 2 ) + compareAndLink( treeArray[ i ], treeArray[ i + 1 ] ); + + int j = i - 2; + + // j has the result of last compareAndLink. + // If an odd number of trees, get the last one. + if( j == numSiblings - 3 ) + compareAndLink( treeArray[ j ], treeArray[ j + 2 ] ); + + // Now go right to left, merging last tree with + // next to last. The result becomes the new last. + for( ; j >= 2; j -= 2 ) + compareAndLink( treeArray[ j - 2 ], treeArray[ j ] ); + return treeArray[ 0 ]; +} + +/** +* Internal method to clone subtree. +* WARNING: This is prone to running out of stack space. +*/ +template +PairNode * +PairingHeap::clone( PairNode * t ) const +{ + if( t == NULL ) + return NULL; + else + { + PairNode *p = new PairNode( t->element ); + if( ( p->leftChild = clone( t->leftChild ) ) != NULL ) + p->leftChild->prev = p; + if( ( p->nextSibling = clone( t->nextSibling ) ) != NULL ) + p->nextSibling->prev = p; + return p; + } +} + +#endif diff --git a/src/removeoverlap/pairingheap/PairingHeap.h b/src/removeoverlap/pairingheap/PairingHeap.h new file mode 100644 index 000000000..586a591a8 --- /dev/null +++ b/src/removeoverlap/pairingheap/PairingHeap.h @@ -0,0 +1,111 @@ +/** + * \brief Pairing heap datastructure implementation + * + * Based on example code in "Data structures and Algorithm Analysis in C++" + * by Mark Allen Weiss, used and released under the GPL by permission + * of the author. + * + * No promises about correctness. Use at your own risk! + * + * Authors: + * Mark Allen Weiss + * Tim Dwyer + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ +#ifndef PAIRING_HEAP_H_ +#define PAIRING_HEAP_H_ +#include +// Pairing heap class +// +// CONSTRUCTION: with no parameters +// +// ******************PUBLIC OPERATIONS********************* +// PairNode & insert( x ) --> Insert x +// deleteMin( minItem ) --> Remove (and optionally return) smallest item +// T findMin( ) --> Return smallest item +// bool isEmpty( ) --> Return true if empty; else false +// bool isFull( ) --> Return true if empty; else false +// void makeEmpty( ) --> Remove all items +// void decreaseKey( PairNode p, newVal ) +// --> Decrease value in node p +// ******************ERRORS******************************** +// Throws Underflow as warranted + + +// Node and forward declaration because g++ does +// not understand nested classes. +template +class PairingHeap; + +template +class PairNode +{ + T element; + PairNode *leftChild; + PairNode *nextSibling; + PairNode *prev; + + PairNode( const T & theElement ) : element( theElement ), + leftChild(NULL), nextSibling(NULL), prev(NULL) { } + friend class PairingHeap; +}; + +template +class Comparator +{ +public: + virtual bool isLessThan(const T &lhs, const T &rhs) const = 0; +}; + +template +class PairingHeap +{ +public: + PairingHeap( bool (*lessThan)(T &lhs, T &rhs) ); + PairingHeap( const PairingHeap & rhs ); + ~PairingHeap( ); + + bool isEmpty( ) const; + bool isFull( ) const; + int size(); + + PairNode *insert( const T & x ); + const T & findMin( ) const; + void deleteMin( ); + void makeEmpty( ); + void decreaseKey( PairNode *p, const T & newVal ); + void merge( PairingHeap *rhs ) + { + PairNode *broot=rhs->getRoot(); + if (root == NULL) { + if(broot != NULL) { + root = broot; + } + } else { + compareAndLink(root, broot); + } + counter+=rhs->size(); + } + + const PairingHeap & operator=( const PairingHeap & rhs ); +protected: + PairNode * getRoot() { + PairNode *r=root; + root=NULL; + return r; + } +private: + PairNode *root; + bool (*lessThan)(T &lhs, T &rhs); + int counter; + void reclaimMemory( PairNode *t ) const; + void compareAndLink( PairNode * & first, PairNode *second ) const; + PairNode * combineSiblings( PairNode *firstSibling ) const; + PairNode * clone( PairNode * t ) const; +}; + +#include "PairingHeap.cpp" +#endif diff --git a/src/removeoverlap/pairingheap/dsexceptions.h b/src/removeoverlap/pairingheap/dsexceptions.h new file mode 100644 index 000000000..bef2c78c5 --- /dev/null +++ b/src/removeoverlap/pairingheap/dsexceptions.h @@ -0,0 +1,9 @@ +#ifndef DSEXCEPTIONS_H_ +#define DSEXCEPTIONS_H_ + +class Underflow { }; +class Overflow { }; +class OutOfMemory { }; +class BadIterator { }; + +#endif diff --git a/src/removeoverlap/placement_SolveVPSC.cpp b/src/removeoverlap/placement_SolveVPSC.cpp new file mode 100755 index 000000000..a9f4344c8 --- /dev/null +++ b/src/removeoverlap/placement_SolveVPSC.cpp @@ -0,0 +1,130 @@ +#include +#include "placement_SolveVPSC.h" +#include +#include "solve_VPSC.h" +#include "variable.h" +#include "constraint.h" +#include "remove_rectangle_overlap.h" +#include "generate-constraints.h" +#include +#include +#define MaxSize 500 + +JNIEXPORT jdouble JNICALL Java_placement_SolveVPSC_solve + (JNIEnv *env, jobject obj, jobjectArray vName, jdoubleArray vWeight, jdoubleArray vDesPos, jintArray cLeft, jintArray cRight, jdoubleArray cGap, jdoubleArray vResult, jint mode) +{ + jsize n = env->GetArrayLength(vWeight); + jsize m = env->GetArrayLength(cLeft); + int i; + double *lvWeight = env->GetDoubleArrayElements(vWeight, 0); + double *lvDesPos = env->GetDoubleArrayElements(vDesPos, 0); + long *lcLeft = env->GetIntArrayElements(cLeft, 0); + long *lcRight = env->GetIntArrayElements(cRight, 0); + double *lcGap = env->GetDoubleArrayElements(cGap, 0); + Variable **vs=new Variable*[n]; + Constraint **cs=new Constraint*[m]; + for (i=0; iGetObjectArrayElement(vName, i); + const char *name = env->GetStringUTFChars(lvName, NULL); + // once upon a time variables had real names, now you'll have to + // track them by number. + vs[i]=new Variable(i,lvDesPos[i],lvWeight[i]); + } + for (i=0; iposition(); + env->SetDoubleArrayRegion(vResult, i,1,&p); + } + for (i=0; iReleaseIntArrayElements(cLeft, lcLeft, 0); + env->ReleaseIntArrayElements(cRight, lcRight, 0); + env->ReleaseDoubleArrayElements(cGap, lcGap, 0); + env->ReleaseDoubleArrayElements(vWeight, lvWeight, 0); + env->ReleaseDoubleArrayElements(vDesPos, lvDesPos, 0); + delete [] vs; + return cost; +} + +static Variable **vs; +static Constraint **cs; +static int m,n; +JNIEXPORT jint JNICALL Java_placement_SolveVPSC_generateXConstraints +(JNIEnv *env, jobject obj, jdoubleArray rMinX, jdoubleArray rMaxX, jdoubleArray rMinY, jdoubleArray rMaxY, jdoubleArray rWeight) { + n = (int)env->GetArrayLength(rWeight); + Rectangle **rs=new Rectangle*[n]; + double *ws = env->GetDoubleArrayElements(rWeight, 0); + double *minX = env->GetDoubleArrayElements(rMinX, 0); + double *maxX = env->GetDoubleArrayElements(rMaxX, 0); + double *minY = env->GetDoubleArrayElements(rMinY, 0); + double *maxY = env->GetDoubleArrayElements(rMaxY, 0); + for(int i=0;iGetArrayLength(rWeight); + Rectangle **rs=new Rectangle*[n]; + double *ws = env->GetDoubleArrayElements(rWeight, 0); + double *minX = env->GetDoubleArrayElements(rMinX, 0); + double *maxX = env->GetDoubleArrayElements(rMaxX, 0); + double *minY = env->GetDoubleArrayElements(rMinY, 0); + double *maxY = env->GetDoubleArrayElements(rMaxY, 0); + for(int i=0;i vmap; + for(int i=0;ileft]; + jint r=vmap[cs[i]->right]; + double g=cs[i]->gap; + env->SetIntArrayRegion(cLeft, i,1,&l); + env->SetIntArrayRegion(cRight, i,1,&r); + env->SetDoubleArrayRegion(cGap, i,1,&g); + } +} +JNIEXPORT void JNICALL Java_placement_SolveVPSC_removeOverlaps +(JNIEnv *env, jobject obj, jdoubleArray rMinX, jdoubleArray rMaxX, jdoubleArray rMinY, jdoubleArray rMaxY) { + //assert(1==2); //break for debugging + n = (int)env->GetArrayLength(rMinX); + Rectangle **rs=new Rectangle*[n]; + double *minX = env->GetDoubleArrayElements(rMinX, 0); + double *maxX = env->GetDoubleArrayElements(rMaxX, 0); + double *minY = env->GetDoubleArrayElements(rMinY, 0); + double *maxY = env->GetDoubleArrayElements(rMaxY, 0); + for(int i=0;igetMinX(); + double y=rs[i]->getMinY(); + env->SetDoubleArrayRegion(rMinX, i,1,&x); + env->SetDoubleArrayRegion(rMinY, i,1,&y); + } + delete [] rs; + env->ReleaseDoubleArrayElements(rMaxX, maxX, 0); + env->ReleaseDoubleArrayElements(rMaxY, maxY, 0); +} \ No newline at end of file diff --git a/src/removeoverlap/placement_SolveVPSC.h b/src/removeoverlap/placement_SolveVPSC.h new file mode 100755 index 000000000..9f1c10cda --- /dev/null +++ b/src/removeoverlap/placement_SolveVPSC.h @@ -0,0 +1,53 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class placement_SolveVPSC */ + +#ifndef _Included_placement_SolveVPSC +#define _Included_placement_SolveVPSC +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: placement_SolveVPSC + * Method: solve + * Signature: ([Ljava/lang/String;[D[D[I[I[D[DI)D + */ +JNIEXPORT jdouble JNICALL Java_placement_SolveVPSC_solve + (JNIEnv *, jobject, jobjectArray, jdoubleArray, jdoubleArray, jintArray, jintArray, jdoubleArray, jdoubleArray, jint); + +/* + * Class: placement_SolveVPSC + * Method: generateXConstraints + * Signature: ([D[D[D[D[D)I + */ +JNIEXPORT jint JNICALL Java_placement_SolveVPSC_generateXConstraints + (JNIEnv *, jobject, jdoubleArray, jdoubleArray, jdoubleArray, jdoubleArray, jdoubleArray); + +/* + * Class: placement_SolveVPSC + * Method: generateYConstraints + * Signature: ([D[D[D[D[D)I + */ +JNIEXPORT jint JNICALL Java_placement_SolveVPSC_generateYConstraints + (JNIEnv *, jobject, jdoubleArray, jdoubleArray, jdoubleArray, jdoubleArray, jdoubleArray); + +/* + * Class: placement_SolveVPSC + * Method: getConstraints + * Signature: ([I[I[D)V + */ +JNIEXPORT void JNICALL Java_placement_SolveVPSC_getConstraints + (JNIEnv *, jobject, jintArray, jintArray, jdoubleArray); + +/* + * Class: placement_SolveVPSC + * Method: removeOverlaps + * Signature: ([D[D[D[D)V + */ +JNIEXPORT void JNICALL Java_placement_SolveVPSC_removeOverlaps + (JNIEnv *, jobject, jdoubleArray, jdoubleArray, jdoubleArray, jdoubleArray); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/removeoverlap/remove_rectangle_overlap-test.cpp b/src/removeoverlap/remove_rectangle_overlap-test.cpp new file mode 100644 index 000000000..9999d027e --- /dev/null +++ b/src/removeoverlap/remove_rectangle_overlap-test.cpp @@ -0,0 +1,308 @@ +#include "removeoverlap/remove_rectangle_overlap.h" +#include // for alarm() +#include // for srand seed and clock(). +#include +#include +#include +#include +#include "removeoverlap/generate-constraints.h" +#include "utest/utest.h" +using std::abs; +using std::rand; + +static bool +possibly_eq(double const a, double const b) +{ + return abs(a - b) < 1e-13; +} + +static bool +possibly_le(double const a, double const b) +{ + return a - b < 1e-13; +} + +static void +show_rects(unsigned const n_rects, double const rect2coords[][4]) +{ + for (unsigned i = 0; i < n_rects; ++i) { + printf("{%g, %g, %g, %g},\n", + rect2coords[i][0], + rect2coords[i][1], + rect2coords[i][2], + rect2coords[i][3]); + } +} + +/** + * Returns the signum of x, but erring towards returning 0 if x is "not too far" from 0. ("Not too + * far from 0" means [-0.9, 0.9] in current version.) + */ +static int +sgn0(double const x) +{ + if (x <= -0.9) { + return -1; + } else if (0.9 <= x) { + return 1; + } else { + return 0; + } +} + +static void +test_case(unsigned const n_rects, double const rect2coords[][4]) +{ + Rectangle **rs = (Rectangle **) g_malloc(sizeof(Rectangle*) * n_rects); + for (unsigned i = 0; i < n_rects; ++i) { + rs[i] = new Rectangle(rect2coords[i][0], + rect2coords[i][1], + rect2coords[i][2], + rect2coords[i][3]); + } + removeRectangleOverlap(rs, n_rects, 0.0, 0.0); + for (unsigned i = 0; i < n_rects; ++i) { + UTEST_ASSERT(possibly_eq(rs[i]->width(), (rect2coords[i][1] - + rect2coords[i][0] ))); + UTEST_ASSERT(possibly_eq(rs[i]->height(), (rect2coords[i][3] - + rect2coords[i][2] ))); + for (unsigned j = 0; j < i; ++j) { + if (!( possibly_le(rs[i]->getMaxX(), rs[j]->getMinX()) || + possibly_le(rs[j]->getMaxX(), rs[i]->getMinX()) || + possibly_le(rs[i]->getMaxY(), rs[j]->getMinY()) || + possibly_le(rs[j]->getMaxY(), rs[i]->getMinY()) )) { + show_rects(n_rects, rect2coords); + char buf[32]; + sprintf(buf, "[%u],[%u] of %u", j, i, n_rects); + utest__fail("Found overlap among ", buf, " rectangles"); + } + } + + /* Optimality test. */ + { + bool found_block[2] = {false, false}; + int const desired_movement[2] = {sgn0(rect2coords[i][0] - rs[i]->getMinX()), + sgn0(rect2coords[i][2] - rs[i]->getMinY())}; + for (unsigned j = 0; j < n_rects; ++j) { + if (j == i) + continue; + for (unsigned d = 0; d < 2; ++d) { + if ( ( desired_movement[d] < 0 + ? abs(rs[j]->getMaxD(d) - rs[i]->getMinD(d)) + : abs(rs[i]->getMaxD(d) - rs[j]->getMinD(d)) ) + < .002 ) { + found_block[d] = true; + } + } + } + + for (unsigned d = 0; d < 2; ++d) { + if ( !found_block[d] + && desired_movement[d] != 0 ) { + show_rects(n_rects, rect2coords); + char buf[32]; + sprintf(buf, "%c in rectangle [%u] of %u", "XY"[d], i, n_rects); + utest__fail("Found clear non-optimality in ", buf, " rectangles"); + } + } + } + } + for (unsigned i = 0; i < n_rects; ++i) { + delete rs[i]; + } + g_free(rs); +} + +int main() +{ + srand(time(NULL)); + + /* Ensure that the program doesn't run for more than 30 seconds. */ + alarm(30); + + utest_start("removeRectangleOverlap(zero gaps)"); + + /* Derived from Bulia's initial test case. This used to crash. */ + UTEST_TEST("eg0") { + double case0[][4] = { + {-180.5, 69.072, 368.071, 629.071}, + {99.5, 297.644, 319.5, 449.071}, + {199.5, 483.358, 450.929, 571.929}, + {168.071, 277.644, 462.357, 623.357}, + {99.5, 99.751, 479.5, 674.786}, + {-111.929, 103.358, 453.786, 611.929}, + {-29.0714, 143.358, 273.786, 557.643}, + {122.357, 269.072, 322.357, 531.929}, + {256.643, 357.644, 396.643, 520.5} + }; + test_case(G_N_ELEMENTS(case0), case0); + } + +#if 0 /* This involves a zero-height rect, so we'll ignore for the moment. */ + UTEST_TEST("eg1") { + double case1[][4] = { + {5, 14, 9, 14}, + {6, 13, 6, 8}, + {11, 12, 5, 5}, + {5, 8, 5, 7}, + {12, 14, 14, 15}, + {12, 14, 1, 14}, + {1, 15, 14, 15}, + {5, 6, 13, 13} + }; + test_case(G_N_ELEMENTS(case1), case1); + } +#endif + + /* The next few examples used to result in overlaps. */ + UTEST_TEST("eg2") { + double case2[][4] = { + {3, 4, 6, 13}, + {0, 1, 0, 5}, + {0, 4, 1, 6}, + {2, 5, 0, 6}, + {0, 10, 9, 13}, + {5, 11, 1, 13}, + {1, 2, 3, 8} + }; + test_case(G_N_ELEMENTS(case2), case2); + } + + UTEST_TEST("eg3") { + double case3[][4] = { + {0, 5, 0, 3}, + {1, 2, 1, 3}, + {3, 7, 4, 7}, + {0, 9, 4, 5}, + {3, 7, 0, 3} + }; + test_case(G_N_ELEMENTS(case3), case3); + } + + UTEST_TEST("eg4") { + double case4[][4] = { + {0, 1, 2, 3}, + {0, 4, 0, 4}, + {1, 6, 0, 4}, + {2, 3, 4, 5}, + {0, 5, 4, 6} + }; + test_case(G_N_ELEMENTS(case4), case4); + } + + UTEST_TEST("eg5") { + double case5[][4] = { + {1, 5, 1, 2}, + {1, 6, 5, 7}, + {6, 8, 1, 2}, + {2, 3, 1, 4}, + {5, 8, 2, 6} + }; + test_case(G_N_ELEMENTS(case5), case5); + } + + /* This one causes overlap in 2005-12-19 04:00 UTC version. */ + UTEST_TEST("olap6") { + double case6[][4] = { + {7, 22, 39, 54}, + {7, 33, 0, 59}, + {3, 26, 16, 56}, + {7, 17, 18, 20}, + {1, 59, 11, 26}, + {19, 20, 13, 49}, + {1, 10, 0, 4}, + {47, 52, 1, 3} + }; + test_case(G_N_ELEMENTS(case6), case6); + } + + /* The next two examples caused loops in the version at 2005-12-07 04:00 UTC. */ + UTEST_TEST("loop0") { + double loop0[][4] = { + {13, 16, 6, 27}, + {0, 6, 0, 12}, + {11, 14, 1, 10}, + {12, 39, 5, 24}, + {14, 34, 4, 7}, + {1, 30, 20, 27}, + {1, 6, 1, 2}, + {19, 28, 10, 24}, + {4, 34, 15, 21}, + {7, 13, 13, 34} + }; + test_case(G_N_ELEMENTS(loop0), loop0); + } + + UTEST_TEST("loop1") { + double loop1[][4] = { + {6, 18, 9, 16}, + {8, 26, 10, 13}, + {3, 10, 0, 14}, + {0, 5, 16, 22}, + {1, 8, 11, 21}, + {1, 5, 0, 13}, + {24, 25, 0, 2} + }; + test_case(G_N_ELEMENTS(loop1), loop1); + } + + UTEST_TEST("loop2") { + double loop2[][4] = { + {16, 22, 9, 16}, + {8, 9, 14, 19}, + {17, 25, 8, 13}, + {10, 26, 26, 29}, + {14, 19, 9, 19}, + {0, 18, 3, 12}, + {7, 8, 14, 22}, + {14, 20, 25, 29} + }; + test_case(G_N_ELEMENTS(loop2), loop2); + } + + /* Random cases of up to 10 rectangles, with small non-neg int coords. */ + for (unsigned n = 0; n <= 10; ++n) { + char buf[64]; + sprintf(buf, "random ints with %u rectangles", n); + UTEST_TEST(buf) { + unsigned const fld_size = 8 * n; + double (*coords)[4] = (double (*)[4]) g_malloc(n * 4 * sizeof(double)); + clock_t const clock_stop = clock() + CLOCKS_PER_SEC; + for (unsigned repeat = (n == 0 ? 1 + : n == 1 ? 36 + : (1 << 16) ); repeat--;) { + for (unsigned i = 0; i < n; ++i) { + for (unsigned d = 0; d < 2; ++d) { + //unsigned const start = rand() % fld_size; + //unsigned const end = start + rand() % (fld_size - start); + unsigned const end = 1 + (rand() % (fld_size - 1)); + unsigned const start = rand() % end; + coords[i][2 * d] = start; + coords[i][2 * d + 1] = end; + } + } + test_case(n, coords); + if (clock() >= clock_stop) { + break; + } + } + g_free(coords); + } + } + + return ( utest_end() + ? EXIT_SUCCESS + : EXIT_FAILURE ); +} + + +/* + 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 : diff --git a/src/removeoverlap/remove_rectangle_overlap.cpp b/src/removeoverlap/remove_rectangle_overlap.cpp new file mode 100755 index 000000000..30dbbaf9e --- /dev/null +++ b/src/removeoverlap/remove_rectangle_overlap.cpp @@ -0,0 +1,109 @@ +#include +#include +#include "generate-constraints.h" +#include "solve_VPSC.h" +#include "variable.h" +#include "constraint.h" +#ifdef RECTANGLE_OVERLAP_LOGGING +using std::ios; +using std::ofstream; +using std::endl; +#endif + +#define EXTRA_GAP 0.0001 + +double Rectangle::xBorder=0; +double Rectangle::yBorder=0; +/** + * Takes an array of n rectangles and moves them as little as possible + * such that rectangles are separated by at least xBorder horizontally + * and yBorder vertically + * + * Works in three passes: + * 1) removes some overlap horizontally + * 2) removes remaining overlap vertically + * 3) a last horizontal pass removes all overlap starting from original + * x-positions - this corrects the case where rectangles were moved + * too much in the first pass. + */ +void removeRectangleOverlap(Rectangle *rs[], int n, double xBorder, double yBorder) { + assert(0 <= n); + try { + // The extra gap avoids numerical imprecision problems + Rectangle::setXBorder(xBorder+EXTRA_GAP); + Rectangle::setYBorder(yBorder+EXTRA_GAP); + double *ws=new double[n]; + for(int i=0;idesiredPosition; + } + VPSC vpsc_x(vs,n,cs,m); +#ifdef RECTANGLE_OVERLAP_LOGGING + ofstream f(LOGFILE,ios::app); + f<<"Calling VPSC: Horizontal pass 1"<moveCentreX(vs[i]->position()); + delete vs[i]; + } + delete [] vs; + for(int i = 0; i < m; ++i) { + delete cs[i]; + } + delete [] cs; + // Removing the extra gap here ensures things that were moved to be adjacent to + // one another above are not considered overlapping + Rectangle::setXBorder(Rectangle::xBorder-EXTRA_GAP); + m=generateYConstraints(rs,ws,n,vs,cs); + VPSC vpsc_y(vs,n,cs,m); +#ifdef RECTANGLE_OVERLAP_LOGGING + f.open(LOGFILE,ios::app); + f<<"Calling VPSC: Vertical pass"<moveCentreY(vs[i]->position()); + rs[i]->moveCentreX(oldX[i]); + delete vs[i]; + } + delete [] vs; + delete [] oldX; + for(int i = 0; i < m; ++i) { + delete cs[i]; + } + delete [] cs; + Rectangle::setYBorder(Rectangle::yBorder-EXTRA_GAP); + m=generateXConstraints(rs,ws,n,vs,cs,false); + VPSC vpsc_x2(vs,n,cs,m); +#ifdef RECTANGLE_OVERLAP_LOGGING + f.open(LOGFILE,ios::app); + f<<"Calling VPSC: Horizontal pass 2"<moveCentreX(vs[i]->position()); + delete vs[i]; + } + delete [] vs; + for(int i = 0; i < m; ++i) { + delete cs[i]; + } + delete [] cs; + delete [] ws; + } catch (char *str) { + std::cerr< + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ + +class Rectangle; + +void removeRectangleOverlap(Rectangle *rs[], int n, double xBorder, double yBorder); + + +#endif /* !REMOVE_RECTANGLE_OVERLAP_H_SEEN */ diff --git a/src/removeoverlap/removeoverlap.cpp b/src/removeoverlap/removeoverlap.cpp new file mode 100644 index 000000000..9d7beb45f --- /dev/null +++ b/src/removeoverlap/removeoverlap.cpp @@ -0,0 +1,80 @@ +/** \file + * Interface between Inkscape code (SPItem) and remove-overlaps function. + */ +/* +* Authors: +* Tim Dwyer +* +* Copyright (C) 2005 Authors +* +* Released under GNU GPL. Read the file 'COPYING' for more information. +*/ +#include "util/glib-list-iterators.h" +#include "sp-item.h" +#include "sp-item-transform.h" +#include "removeoverlap/generate-constraints.h" +#include "removeoverlap/remove_rectangle_overlap.h" + +/** +* Takes a list of inkscape items and moves them as little as possible +* such that rectangular bounding boxes are separated by at least xGap +* horizontally and yGap vertically +*/ +void removeoverlap(GSList const *const items, double const xGap, double const yGap) { + if(!items) { + return; + } + + using Inkscape::Util::GSListConstIterator; + std::list selected; + selected.insert >(selected.end(), items, NULL); + if (selected.empty()) return; + int n=selected.size(); + + //Check 2 or more selected objects + if (n < 2) return; + + Rectangle **rs = new Rectangle*[n]; + int i=0; + + NR::Point const gap(xGap, yGap); + for (std::list::iterator it(selected.begin()); + it != selected.end(); + ++it) + { + using NR::X; using NR::Y; + NR::Rect const item_box(sp_item_bbox_desktop(*it)); + + /* The current algorithm requires widths & heights to be strictly positive. */ + NR::Point min(item_box.min()); + NR::Point max(item_box.max()); + for (unsigned d = 0; d < 2; ++d) { + double const minsize = 1; // arbitrary positive number + if (max[d] - min[d] + gap[d] < minsize) { + double const mid = .5 * (min[d] + max[d]); + min[d] = mid - .5*minsize; + max[d] = mid + .5*minsize; + } else { + min[d] -= .5*gap[d]; + max[d] += .5*gap[d]; + } + } + rs[i++] = new Rectangle(min[X], max[X], + min[Y], max[Y]); + } + removeRectangleOverlap(rs, n, 0.0, 0.0); + i=0; + for (std::list::iterator it(selected.begin()); + it != selected.end(); + ++it) + { + NR::Rect const item_box(sp_item_bbox_desktop(*it)); + Rectangle *r = rs[i++]; + NR::Point const curr(item_box.midpoint()); + NR::Point const dest(r->getCentreX(), + r->getCentreY()); + sp_item_move_rel(*it, NR::translate(dest - curr)); + delete r; + } + delete [] rs; +} diff --git a/src/removeoverlap/removeoverlap.h b/src/removeoverlap/removeoverlap.h new file mode 100644 index 000000000..b904f52f1 --- /dev/null +++ b/src/removeoverlap/removeoverlap.h @@ -0,0 +1,17 @@ +/** + * \brief Remove overlaps function + * + * Authors: + * Tim Dwyer + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ + +#ifndef SEEN_REMOVEOVERLAP_H +#define SEEN_REMOVEOVERLAP_H + +void removeoverlap(GSList const *items, double xGap, double yGap); + +#endif // SEEN_REMOVEOVERLAP_H diff --git a/src/removeoverlap/solve_VPSC.cpp b/src/removeoverlap/solve_VPSC.cpp new file mode 100644 index 000000000..296cc415b --- /dev/null +++ b/src/removeoverlap/solve_VPSC.cpp @@ -0,0 +1,265 @@ +/** + * \brief Remove overlaps function + * + * Authors: + * Tim Dwyer + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ + +#include +#include "constraint.h" +#include "block.h" +#include "blocks.h" +#include "solve_VPSC.h" +#ifdef RECTANGLE_OVERLAP_LOGGING +using std::ios; +using std::ofstream; +using std::endl; +#endif + +using std::list; +using std::set; + +VPSC::VPSC(Variable *vs[], const int n, Constraint *cs[], const int m) : cs(cs), m(m) { + //assert(!constraintGraphIsCyclic(vs,n)); + bs=new Blocks(vs,n); +#ifdef RECTANGLE_OVERLAP_LOGGING + printBlocks(); +#endif +} +VPSC::~VPSC() { + delete bs; +} + +// useful in debugging +void VPSC::printBlocks() { +#ifdef RECTANGLE_OVERLAP_LOGGING + ofstream f(LOGFILE,ios::app); + for(set::iterator i=bs->begin();i!=bs->end();i++) { + Block *b=*i; + f<<" "<<*b< *vs=bs->totalOrder(); + for(list::iterator i=vs->begin();i!=vs->end();i++) { + Variable *v=*i; + if(!v->block->deleted) { + bs->mergeLeft(v->block); + } + } + bs->cleanup(); + for(int i=0;islack()<-0.0000001) { +#ifdef RECTANGLE_OVERLAP_LOGGING + ofstream f(LOGFILE,ios::app); + f<<"Error: Unsatisfied constraint: "<<*cs[i]<slack()>-0.0000001); + throw "Unsatisfied constraint"; + } + } + delete vs; +} + +void VPSC::refine() { + bool solved=false; + // Solve shouldn't loop indefinately + // ... but just to make sure we limit the number of iterations + int maxtries=100; + while(!solved&&maxtries>=0) { + solved=true; + maxtries--; + for(set::const_iterator i=bs->begin();i!=bs->end();i++) { + Block *b=*i; + b->setUpInConstraints(); + b->setUpOutConstraints(); + } + for(set::const_iterator i=bs->begin();i!=bs->end();i++) { + Block *b=*i; + Constraint *c=b->findMinLM(); + if(c!=NULL && c->lm<0) { +#ifdef RECTANGLE_OVERLAP_LOGGING + ofstream f(LOGFILE,ios::app); + f<<"Split on constraint: "<<*c<split(b,l,r,c); + bs->cleanup(); + // split alters the block set so we have to restart + solved=false; + break; + } + } + } + for(int i=0;islack()<-0.0000001) { + assert(cs[i]->slack()>-0.0000001); + throw "Unsatisfied constraint"; + } + } +} +/** + * Calculate the optimal solution. After using satisfy() to produce a + * feasible solution, refine() examines each block to see if further + * refinement is possible by splitting the block. This is done repeatedly + * until no further improvement is possible. + */ +void VPSC::solve() { + satisfy(); + refine(); +} + +/** + * incremental version of solve that should allow refinement after blocks are + * moved. Work in progress. + */ +void VPSC::move_and_split() { + //assert(!blockGraphIsCyclic()); + for(set::const_iterator i=bs->begin();i!=bs->end();i++) { + Block *b=*i; + if(!b->deleted) { + b->wposn = b->desiredWeightedPosition(); + b->posn = b->wposn / b->weight; + Variable *v=b->vars->front(); + bs->mergeLeft(b); + // b may be merged away, so get any new block from one of its members + bs->mergeRight(v->block); + } + } + bs->cleanup(); + // assert(!blockGraphIsCyclic()); + refine(); +} + +#include +using std::map; +using std::vector; +struct node { + set in; + set out; +}; +/* +// useful in debugging - cycles would be BAD +bool VPSC::constraintGraphIsCyclic(Variable *vs[], const int n) { + map varmap; + vector graph; + for(int i=0;i::iterator c=vs[i]->in.begin();c!=vs[i]->in.end();c++) { + Variable *l=(*c)->left; + varmap[vs[i]]->in.insert(varmap[l]); + } + + for(vector::iterator c=vs[i]->out.begin();c!=vs[i]->out.end();c++) { + Variable *r=(*c)->right; + varmap[vs[i]]->out.insert(varmap[r]); + } + } + while(graph.size()>0) { + node *u=NULL; + vector::iterator i=graph.begin(); + for(;i!=graph.end();i++) { + u=*i; + if(u->in.size()==0) { + break; + } + } + if(i==graph.end() && graph.size()>0) { + //cycle found! + return true; + } else { + graph.erase(i); + for(set::iterator j=u->out.begin();j!=u->out.end();j++) { + node *v=*j; + v->in.erase(u); + } + delete u; + } + } + for(unsigned i=0; i bmap; + vector graph; + for(set::const_iterator i=bs->begin();i!=bs->end();i++) { + Block *b=*i; + node *u=new node; + graph.push_back(u); + bmap[b]=u; + } + for(set::const_iterator i=bs->begin();i!=bs->end();i++) { + Block *b=*i; + b->setUpInConstraints(); + Constraint *c=b->findMinInConstraint(); + while(c!=NULL) { + Block *l=c->left->block; + bmap[b]->in.insert(bmap[l]); + b->deleteMinInConstraint(); + c=b->findMinInConstraint(); + } + + b->setUpOutConstraints(); + c=b->findMinOutConstraint(); + while(c!=NULL) { + Block *r=c->right->block; + bmap[b]->out.insert(bmap[r]); + b->deleteMinOutConstraint(); + c=b->findMinOutConstraint(); + } + } + while(graph.size()>0) { + node *u=NULL; + vector::iterator i=graph.begin(); + for(;i!=graph.end();i++) { + u=*i; + if(u->in.size()==0) { + break; + } + } + if(i==graph.end() && graph.size()>0) { + //cycle found! + return true; + } else { + graph.erase(i); + for(set::iterator j=u->out.begin();j!=u->out.end();j++) { + node *v=*j; + v->in.erase(u); + } + delete u; + } + } + for(unsigned i=0; i +* +* Copyright (C) 2005 Authors +* +* Released under GNU GPL. Read the file 'COPYING' for more information. +*/ + +#ifndef SEEN_REMOVEOVERLAP_SOLVE_VPSC_H +#define SEEN_REMOVEOVERLAP_SOLVE_VPSC_H + +class Variable; +class Constraint; +class Blocks; + +/** + * Variable Placement with Separation Constraints problem instance + */ +class VPSC { +public: + void satisfy(); + void solve(); + + void move_and_split(); + VPSC(Variable *vs[], const int n, Constraint *cs[], const int m); + ~VPSC(); +protected: + Blocks *bs; + void refine(); +private: + void printBlocks(); + bool constraintGraphIsCyclic(Variable *vs[], const int n); + bool blockGraphIsCyclic(); + Constraint **cs; + int m; +}; + +#endif // SEEN_REMOVEOVERLAP_SOLVE_VPSC_H diff --git a/src/removeoverlap/variable.cpp b/src/removeoverlap/variable.cpp new file mode 100644 index 000000000..0cf2e28a7 --- /dev/null +++ b/src/removeoverlap/variable.cpp @@ -0,0 +1,20 @@ +/** + * \brief Remove overlaps function + * + * Authors: + * Tim Dwyer + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ +#include "variable.h" +std::ostream& operator <<(std::ostream &os, const Variable &v) { + os << "(" << v.id << "=" << v.position() << ")"; + return os; +} + +#include "block.h" +double Variable::position() const { + return block->posn+offset; +} diff --git a/src/removeoverlap/variable.h b/src/removeoverlap/variable.h new file mode 100644 index 000000000..492e7504a --- /dev/null +++ b/src/removeoverlap/variable.h @@ -0,0 +1,46 @@ +/** + * \brief Remove overlaps function + * + * Authors: + * Tim Dwyer + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ + +#ifndef SEEN_REMOVEOVERLAP_VARIABLE_H +#define SEEN_REMOVEOVERLAP_VARIABLE_H + +#include +#include +class Block; +class Constraint; + +class Variable +{ + friend std::ostream& operator <<(std::ostream &os, const Variable &v); +public: + static const unsigned int _TOSTRINGBUFFSIZE=20; + const int id; // useful in log files + double desiredPosition; + const double weight; + double offset; + Block *block; + bool visited; + std::vector in; + std::vector out; + char *toString(); + inline Variable(const int id, const double desiredPos, const double weight) + : id(id) + , desiredPosition(desiredPos) + , weight(weight) + { + } + double position() const; + ~Variable(void){ + in.clear(); + out.clear(); + } +}; +#endif // SEEN_REMOVEOVERLAP_VARIABLE_H diff --git a/src/require-config.h b/src/require-config.h new file mode 100644 index 000000000..7c7ef0552 --- /dev/null +++ b/src/require-config.h @@ -0,0 +1,3 @@ +#ifndef PACKAGE_TARNAME /* random symbol defined by config.h */ +# error "Must #include config.h (ifdef HAVE_CONFIG_H) before anything else." +#endif diff --git a/src/round-test.cpp b/src/round-test.cpp new file mode 100644 index 000000000..efb4b3e83 --- /dev/null +++ b/src/round-test.cpp @@ -0,0 +1,91 @@ +#include +#include +#include + +#include +#include +#include + +static Case1 const nonneg_round_cases[] = { + { 5.0, 5.0 }, + { 0.0, 0.0 }, + { 5.4, 5.0 }, + { 5.6, 6.0 }, + { 1e-7, 0.0 }, + { 1e7 + .49, 1e7 }, + { 1e7 + .51, 1e7 + 1 }, + { 1e12 + .49, 1e12 }, + { 1e12 + .51, 1e12 + 1 }, + { 1e40, 1e40 } +}; + +static Case1 nonpos_round_cases[G_N_ELEMENTS(nonneg_round_cases)]; + +static void fill_nonpos_round_cases() +{ + assert(G_N_ELEMENTS(nonneg_round_cases) == G_N_ELEMENTS(nonpos_round_cases)); + for(unsigned i = 0; i < G_N_ELEMENTS(nonpos_round_cases); ++i) { + nonpos_round_cases[i].f_arg0 = -nonneg_round_cases[i].f_arg0; + nonpos_round_cases[i].valid_arg0 = -nonneg_round_cases[i].valid_arg0; + } +} + +static bool +test_round() +{ + utest_start("round"); + test_1ary_cases > + ("non-neg round", + Inkscape::round, + G_N_ELEMENTS(nonneg_round_cases), nonneg_round_cases); + + fill_nonpos_round_cases(); + + test_1ary_cases > + ("non-pos round", + Inkscape::round, + G_N_ELEMENTS(nonpos_round_cases), nonpos_round_cases); + +#if 0 + for(unsigned i = 0; i < G_N_ELEMENTS(round_cases); ++i) { + RoundCase const &c = round_cases[i]; + double const got = Inkscape::round(c.unrounded); + UTEST_ASSERT( got == c.exp_rounded ); + + double const neg_got = Inkscape::round(-c.unrounded); + UTEST_ASSERT( neg_got = -c.exp_rounded ); + } +#endif + return utest_end(); +} + +#if 0 +/* Deliberately down here just to ensure that correct behaviour of Inkscape::round doesn't depend + on #including decimal-round.h. (decimal-round.h already #includes round.h, so there's no point + checking the other way around.) */ +#include + +static void +test_decimal_round() +{ +} +#endif + +int main() +{ + int const ret = ( test_round() + ? EXIT_SUCCESS + : EXIT_FAILURE ); + return ret; +} + +/* + 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/round.h b/src/round.h new file mode 100644 index 000000000..16d280566 --- /dev/null +++ b/src/round.h @@ -0,0 +1,32 @@ +#ifndef SEEN_ROUND_H +#define SEEN_ROUND_H + +#include + +namespace Inkscape { + +/** Returns x rounded to the nearest integer. It is unspecified what happens + if x is half way between two integers: we may in future use rint/round + on platforms that have them. If you depend on a particular rounding + behaviour, then please change this documentation accordingly. +**/ +inline double +round(double const x) +{ + return std::floor( x + .5 ); +} + +} + +#endif /* !SEEN_ROUND_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 : diff --git a/src/rubberband.cpp b/src/rubberband.cpp new file mode 100644 index 000000000..d44662c5b --- /dev/null +++ b/src/rubberband.cpp @@ -0,0 +1,82 @@ +#define __RUBBERBAND_C__ + +/** + * \file src/rubberband.cpp + * \brief Rubberbanding selector + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "display/sodipodi-ctrlrect.h" +#include "desktop.h" +#include "desktop-handles.h" +#include "rubberband.h" + +Inkscape::Rubberband *Inkscape::Rubberband::_instance = NULL; + +Inkscape::Rubberband::Rubberband() + : _desktop(NULL), _canvas(NULL) +{ + +} + +void Inkscape::Rubberband::start(SPDesktop *d, NR::Point const &p) +{ + stop(); + _desktop = d; + _start = p; +} + +void Inkscape::Rubberband::stop() +{ + if (_canvas) { + gtk_object_destroy((GtkObject *) _canvas); + _canvas = NULL; + } +} + +void Inkscape::Rubberband::move(NR::Point const &p) +{ + if (_canvas == NULL) { + _canvas = static_cast(sp_canvas_item_new(SP_DT_CONTROLS(_desktop), SP_TYPE_CTRLRECT, NULL)); + } + + _desktop->scroll_to_point(&p); + _end = p; + + _canvas->setRectangle(NR::Rect(_start, _end)); +} + +NR::Maybe Inkscape::Rubberband::getRectangle() const +{ + if (_canvas == NULL) { + return NR::Nothing(); + } + + return NR::Rect(_start, _end); +} + +Inkscape::Rubberband *Inkscape::Rubberband::get() +{ + if (_instance == NULL) { + _instance = new Inkscape::Rubberband; + } + + return _instance; +} + +/* + 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 : diff --git a/src/rubberband.h b/src/rubberband.h new file mode 100644 index 000000000..9dd0b6025 --- /dev/null +++ b/src/rubberband.h @@ -0,0 +1,64 @@ +#ifndef __RUBBERBAND_H__ +#define __RUBBERBAND_H__ + +/** + * \file src/rubberband.h + * \brief Rubberbanding selector + * + * Authors: + * Lauris Kaplinski + * Carl Hetherington + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "forward.h" +#include "libnr/nr-forward.h" +#include "libnr/nr-point.h" +#include "libnr/nr-maybe.h" + +/* fixme: do multidocument safe */ + +class CtrlRect; + +namespace Inkscape +{ + +class Rubberband +{ +public: + + void start(SPDesktop *desktop, NR::Point const &p); + void move(NR::Point const &p); + NR::Maybe getRectangle() const; + void stop(); + + static Rubberband* get(); + +private: + + Rubberband(); + static Rubberband* _instance; + + SPDesktop *_desktop; + NR::Point _start; + NR::Point _end; + CtrlRect *_canvas; +}; + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/satisfied-guide-cns.cpp b/src/satisfied-guide-cns.cpp new file mode 100644 index 000000000..edbf32dbb --- /dev/null +++ b/src/satisfied-guide-cns.cpp @@ -0,0 +1,33 @@ +#include +#include +#include +#include +#include +#include + +void satisfied_guide_cns(SPDesktop const &desktop, + std::vector const &snappoints, + std::vector &cns) +{ + SPNamedView const &nv = *SP_DT_NAMEDVIEW(&desktop); + for (GSList const *l = nv.guides; l != NULL; l = l->next) { + SPGuide &g = *SP_GUIDE(l->data); + for (unsigned int i = 0; i < snappoints.size(); ++i) { + if (approx_equal(dot(g.normal, snappoints[i]), g.position)) { + cns.push_back(SPGuideConstraint(&g, i)); + } + } + } +} + + +/* + 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 : diff --git a/src/satisfied-guide-cns.h b/src/satisfied-guide-cns.h new file mode 100644 index 000000000..93097e021 --- /dev/null +++ b/src/satisfied-guide-cns.h @@ -0,0 +1,25 @@ +#ifndef __SATISFIED_GUIDE_CNS_H__ +#define __SATISFIED_GUIDE_CNS_H__ + +#include +namespace NR { class Point; } +#include +class SPGuideConstraint; + +void satisfied_guide_cns(SPDesktop const &desktop, + std::vector const &snappoints, + std::vector &cns); + + +#endif /* !__SATISFIED_GUIDE_CNS_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:encoding=utf-8:textwidth=99 : diff --git a/src/selcue.cpp b/src/selcue.cpp new file mode 100644 index 000000000..845561d18 --- /dev/null +++ b/src/selcue.cpp @@ -0,0 +1,153 @@ +#define __SELCUE_C__ + +/* + * Helper object for showing selected items + * + * Authors: + * bulia byak + * Carl Hetherington + * + * Copyright (C) 2004 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "desktop-handles.h" +#include "selection.h" +#include "display/sp-canvas-util.h" +#include "display/sodipodi-ctrl.h" +#include "display/sodipodi-ctrlrect.h" +#include "libnrtype/Layout-TNG.h" +#include "text-editing.h" +#include "sp-text.h" +#include "sp-flowtext.h" +#include "prefs-utils.h" +#include "selcue.h" + +Inkscape::SelCue::SelCue(SPDesktop *desktop) + : _desktop(desktop) +{ + _selection = SP_DT_SELECTION(_desktop); + + _sel_changed_connection = _selection->connectChanged( + sigc::hide(sigc::mem_fun(*this, &Inkscape::SelCue::_updateItemBboxes)) + ); + + _sel_modified_connection = _selection->connectModified( + sigc::hide(sigc::hide(sigc::mem_fun(*this, &Inkscape::SelCue::_updateItemBboxes))) + ); + + _updateItemBboxes(); +} + +Inkscape::SelCue::~SelCue() +{ + _sel_changed_connection.disconnect(); + _sel_modified_connection.disconnect(); + + for (std::list::iterator i = _item_bboxes.begin(); i != _item_bboxes.end(); i++) { + gtk_object_destroy(*i); + } + _item_bboxes.clear(); + + for (std::list::iterator i = _text_baselines.begin(); i != _text_baselines.end(); i++) { + gtk_object_destroy(*i); + } + _text_baselines.clear(); +} + +void Inkscape::SelCue::_updateItemBboxes() +{ + for (std::list::iterator i = _item_bboxes.begin(); i != _item_bboxes.end(); i++) { + gtk_object_destroy(*i); + } + _item_bboxes.clear(); + + for (std::list::iterator i = _text_baselines.begin(); i != _text_baselines.end(); i++) { + gtk_object_destroy(*i); + } + _text_baselines.clear(); + + gint mode = prefs_get_int_attribute ("options.selcue", "value", MARK); + if (mode == NONE) { + return; + } + + g_return_if_fail(_selection != NULL); + + for (GSList const *l = _selection->itemList(); l != NULL; l = l->next) { + SPItem *item = (SPItem *) l->data; + + NR::Rect const b = sp_item_bbox_desktop(item); + + SPCanvasItem* box = NULL; + + if (mode == MARK) { + box = sp_canvas_item_new(SP_DT_CONTROLS(_desktop), + SP_TYPE_CTRL, + "mode", SP_CTRL_MODE_XOR, + "shape", SP_CTRL_SHAPE_DIAMOND, + "size", 5.0, + "filled", TRUE, + "fill_color", 0x000000ff, + "stroked", FALSE, + "stroke_color", 0x000000ff, + NULL); + sp_canvas_item_show(box); + SP_CTRL(box)->moveto(NR::Point(b.min()[NR::X], b.max()[NR::Y])); + + sp_canvas_item_move_to_z(box, 0); // just low enough to not get in the way of other draggable knots + + } else if (mode == BBOX) { + box = sp_canvas_item_new( + SP_DT_CONTROLS(_desktop), + SP_TYPE_CTRLRECT, + NULL + ); + + SP_CTRLRECT(box)->setRectangle(b); + SP_CTRLRECT(box)->setColor(0x000000a0, 0, 0); + SP_CTRLRECT(box)->setDashed(true); + + sp_canvas_item_move_to_z(box, 0); + } + + if (box) { + _item_bboxes.push_back(box); + } + + SPCanvasItem* baseline_point = NULL; + if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) { // visualize baseline + Inkscape::Text::Layout const *layout = te_get_layout(item); + if (layout != NULL) { + NR::Point a = layout->characterAnchorPoint(layout->begin()) * sp_item_i2d_affine(item); + baseline_point = sp_canvas_item_new(SP_DT_CONTROLS(_desktop), SP_TYPE_CTRL, + "mode", SP_CTRL_MODE_XOR, + "size", 4.0, + "filled", 0, + "stroked", 1, + "stroke_color", 0x000000ff, + NULL); + + sp_canvas_item_show(baseline_point); + SP_CTRL(baseline_point)->moveto(a); + sp_canvas_item_move_to_z(baseline_point, 0); + } + } + + if (baseline_point) { + _text_baselines.push_back(baseline_point); + } + } +} + +/* + 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/selcue.h b/src/selcue.h new file mode 100644 index 000000000..42ebc9878 --- /dev/null +++ b/src/selcue.h @@ -0,0 +1,64 @@ +#ifndef __SP_SELCUE_H__ +#define __SP_SELCUE_H__ + +/* + * Helper object for showing selected items + * + * Authors: + * bulia byak + * Carl Hetherington + * + * Copyright (C) 2004 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include + +class SPDesktop; +class SPCanvasItem; + +namespace Inkscape { + +class Selection; + +class SelCue +{ +public: + SelCue(SPDesktop *desktop); + ~SelCue(); + + enum Type { + NONE, + MARK, + BBOX + }; + +private: + + void _updateItemBboxes(); + + SPDesktop *_desktop; + Selection *_selection; + sigc::connection _sel_changed_connection; + sigc::connection _sel_modified_connection; + std::list _item_bboxes; + std::list _text_baselines; +}; + +} + +#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 : diff --git a/src/select-context.cpp b/src/select-context.cpp new file mode 100644 index 000000000..bda4012c7 --- /dev/null +++ b/src/select-context.cpp @@ -0,0 +1,823 @@ +#define __SP_SELECT_CONTEXT_C__ + +/* + * Selection and transformation context + * + * Author: + * Lauris Kaplinski + * bulia byak + * + * Copyright (C) 1999-2005 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include +#include "macros.h" +#include "rubberband.h" +#include "document.h" +#include "selection.h" +#include "seltrans-handles.h" +#include "sp-cursor.h" +#include "pixmaps/cursor-select-m.xpm" +#include "pixmaps/cursor-select-d.xpm" +#include "pixmaps/handles.xpm" +#include + +#include "select-context.h" +#include "selection-chemistry.h" +#include "desktop.h" +#include "desktop-handles.h" +#include "sp-root.h" +#include "prefs-utils.h" +#include "tools-switch.h" +#include "message-stack.h" +#include "selection-describer.h" +#include "seltrans.h" + +static void sp_select_context_class_init(SPSelectContextClass *klass); +static void sp_select_context_init(SPSelectContext *select_context); +static void sp_select_context_dispose(GObject *object); + +static void sp_select_context_setup(SPEventContext *ec); +static void sp_select_context_set(SPEventContext *ec, gchar const *key, gchar const *val); +static gint sp_select_context_root_handler(SPEventContext *event_context, GdkEvent *event); +static gint sp_select_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event); + +static SPEventContextClass *parent_class; + +static GdkCursor *CursorSelectMouseover = NULL; +static GdkCursor *CursorSelectDragging = NULL; +GdkPixbuf *handles[13]; + +static gint rb_escaped = 0; // if non-zero, rubberband was canceled by esc, so the next button release should not deselect +static gint drag_escaped = 0; // if non-zero, drag was canceled by esc + +static gint xp = 0, yp = 0; // where drag started +static gint tolerance = 0; +static bool within_tolerance = false; + +GtkType +sp_select_context_get_type(void) +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPSelectContextClass), + NULL, NULL, + (GClassInitFunc) sp_select_context_class_init, + NULL, NULL, + sizeof(SPSelectContext), + 4, + (GInstanceInitFunc) sp_select_context_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPSelectContext", &info, (GTypeFlags)0); + } + return type; +} + +static void +sp_select_context_class_init(SPSelectContextClass *klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + SPEventContextClass *event_context_class = (SPEventContextClass *) klass; + + parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass); + + object_class->dispose = sp_select_context_dispose; + + event_context_class->setup = sp_select_context_setup; + event_context_class->set = sp_select_context_set; + event_context_class->root_handler = sp_select_context_root_handler; + event_context_class->item_handler = sp_select_context_item_handler; + + // cursors in select context + CursorSelectMouseover = sp_cursor_new_from_xpm(cursor_select_m_xpm , 1, 1); + CursorSelectDragging = sp_cursor_new_from_xpm(cursor_select_d_xpm , 1, 1); + // selection handles + handles[0] = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_scale_nw_xpm); + handles[1] = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_scale_ne_xpm); + handles[2] = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_scale_h_xpm); + handles[3] = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_scale_v_xpm); + handles[4] = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_rotate_nw_xpm); + handles[5] = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_rotate_n_xpm); + handles[6] = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_rotate_ne_xpm); + handles[7] = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_rotate_e_xpm); + handles[8] = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_rotate_se_xpm); + handles[9] = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_rotate_s_xpm); + handles[10] = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_rotate_sw_xpm); + handles[11] = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_rotate_w_xpm); + handles[12] = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_center_xpm); + +} + +static void +sp_select_context_init(SPSelectContext *sc) +{ + sc->dragging = FALSE; + sc->moved = FALSE; + sc->button_press_shift = false; + sc->button_press_ctrl = false; + sc->button_press_alt = false; + sc->_seltrans = NULL; + sc->_describer = NULL; +} + +static void +sp_select_context_dispose(GObject *object) +{ + SPSelectContext *sc = SP_SELECT_CONTEXT(object); + SPEventContext * ec = SP_EVENT_CONTEXT (object); + + ec->enableGrDrag(false); + + if (sc->grabbed) { + sp_canvas_item_ungrab(sc->grabbed, GDK_CURRENT_TIME); + sc->grabbed = NULL; + } + + delete sc->_seltrans; + sc->_seltrans = NULL; + delete sc->_describer; + sc->_describer = NULL; + + if (CursorSelectDragging) { + gdk_cursor_unref (CursorSelectDragging); + CursorSelectDragging = NULL; + } + if (CursorSelectMouseover) { + gdk_cursor_unref (CursorSelectMouseover); + CursorSelectMouseover = NULL; + } + + G_OBJECT_CLASS(parent_class)->dispose(object); +} + +static void +sp_select_context_setup(SPEventContext *ec) +{ + SPSelectContext *select_context = SP_SELECT_CONTEXT(ec); + + if (((SPEventContextClass *) parent_class)->setup) { + ((SPEventContextClass *) parent_class)->setup(ec); + } + + SPDesktop *desktop = ec->desktop; + + select_context->_describer = new Inkscape::SelectionDescriber(desktop->selection, desktop->messageStack()); + + select_context->_seltrans = new Inkscape::SelTrans(desktop); + + sp_event_context_read(ec, "show"); + sp_event_context_read(ec, "transform"); + + if (prefs_get_int_attribute("tools.select", "gradientdrag", 0) != 0) { + ec->enableGrDrag(); + } +} + +static void +sp_select_context_set(SPEventContext *ec, gchar const *key, gchar const *val) +{ + SPSelectContext *sc = SP_SELECT_CONTEXT(ec); + + if (!strcmp(key, "show")) { + if (val && !strcmp(val, "outline")) { + sc->_seltrans->setShow(Inkscape::SelTrans::SHOW_OUTLINE); + } else { + sc->_seltrans->setShow(Inkscape::SelTrans::SHOW_CONTENT); + } + } +} + +static bool +sp_select_context_abort(SPEventContext *event_context) +{ + SPDesktop *desktop = event_context->desktop; + SPSelectContext *sc = SP_SELECT_CONTEXT(event_context); + Inkscape::SelTrans *seltrans = sc->_seltrans; + + if (sc->dragging) { + if (sc->moved) { // cancel dragging an object + seltrans->ungrab(); + sc->moved = FALSE; + sc->dragging = FALSE; + drag_escaped = 1; + + if (sc->item) { + // only undo if the item is still valid + if (SP_OBJECT_DOCUMENT( SP_OBJECT(sc->item))) { + sp_document_undo(SP_DT_DOCUMENT(desktop)); + } + + sp_object_unref( SP_OBJECT(sc->item), NULL); + } else if (sc->button_press_ctrl) { + // NOTE: This is a workaround to a bug. + // When the ctrl key is held, sc->item is not defined + // so in this case (only), we skip the object doc check + sp_document_undo(SP_DT_DOCUMENT(desktop)); + } + sc->item = NULL; + + SP_EVENT_CONTEXT(sc)->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Move canceled.")); + return true; + } + } else { + NR::Maybe const b = Inkscape::Rubberband::get()->getRectangle(); + if (b != NR::Nothing()) { + Inkscape::Rubberband::get()->stop(); + rb_escaped = 1; + SP_EVENT_CONTEXT(sc)->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Selection canceled.")); + return true; + } + } + return false; +} + +bool +key_is_a_modifier (guint key) { + return (key == GDK_Alt_L || + key == GDK_Alt_R || + key == GDK_Control_L || + key == GDK_Control_R || + key == GDK_Shift_L || + key == GDK_Shift_R || + key == GDK_Meta_L || // Meta is when you press Shift+Alt (at least on my machine) + key == GDK_Meta_R); +} + +void +sp_select_context_up_one_layer(SPDesktop *desktop) +{ + /* Click in empty place, go up one level -- but don't leave a layer to root. + * + * (Rationale: we don't usually allow users to go to the root, since that + * detracts from the layer metaphor: objects at the root level can in front + * of or behind layers. Whereas it's fine to go to the root if editing + * a document that has no layers (e.g. a non-Inkscape document).) + * + * Once we support editing SVG "islands" (e.g. embedded in an xhtml + * document), we might consider further restricting the below to disallow + * leaving a layer to go to a non-layer. + */ + SPObject *const current_layer = desktop->currentLayer(); + if (current_layer) { + SPObject *const parent = SP_OBJECT_PARENT(current_layer); + if ( parent + && ( SP_OBJECT_PARENT(parent) + || !( SP_IS_GROUP(current_layer) + && ( SPGroup::LAYER + == SP_GROUP(current_layer)->layerMode() ) ) ) ) + { + desktop->setCurrentLayer(parent); + if (SP_IS_GROUP(current_layer) && SPGroup::LAYER != SP_GROUP(current_layer)->layerMode()) + SP_DT_SELECTION(desktop)->set(current_layer); + } + } +} + +static gint +sp_select_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event) +{ + gint ret = FALSE; + + SPDesktop *desktop = event_context->desktop; + SPSelectContext *sc = SP_SELECT_CONTEXT(event_context); + Inkscape::SelTrans *seltrans = sc->_seltrans; + + tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100); + + // make sure we still have valid objects to move around + if (sc->item && SP_OBJECT_DOCUMENT( SP_OBJECT(sc->item))==NULL) { + sp_select_context_abort(event_context); + } + + switch (event->type) { + case GDK_BUTTON_PRESS: + if (event->button.button == 1) { + /* Left mousebutton */ + + // save drag origin + xp = (gint) event->button.x; + yp = (gint) event->button.y; + within_tolerance = true; + + // remember what modifiers were on before button press + sc->button_press_shift = (event->button.state & GDK_SHIFT_MASK) ? true : false; + sc->button_press_ctrl = (event->button.state & GDK_CONTROL_MASK) ? true : false; + sc->button_press_alt = (event->button.state & GDK_MOD1_MASK) ? true : false; + + if (event->button.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) { + // if shift or ctrl was pressed, do not move objects; + // pass the event to root handler which will perform rubberband, shift-click, ctrl-click, ctrl-drag + } else { + sc->dragging = TRUE; + sc->moved = FALSE; + + // remember the clicked item in sc->item: + sc->item = sp_event_context_find_item (desktop, + NR::Point(event->button.x, event->button.y), event->button.state & GDK_MOD1_MASK, FALSE); + sp_object_ref(sc->item, NULL); + + rb_escaped = drag_escaped = 0; + + sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->drawing), + GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK, + NULL, event->button.time); + sc->grabbed = SP_CANVAS_ITEM(desktop->drawing); + + ret = TRUE; + } + } else if (event->button.button == 3) { + // right click; do not eat it so that right-click menu can appear, but cancel dragging & rubberband + sp_select_context_abort(event_context); + } + break; + + case GDK_ENTER_NOTIFY: + { + GdkCursor *cursor = gdk_cursor_new(GDK_FLEUR); + gdk_window_set_cursor(GTK_WIDGET(SP_DT_CANVAS(desktop))->window, cursor); + gdk_cursor_destroy(cursor); + break; + } + + case GDK_LEAVE_NOTIFY: + gdk_window_set_cursor(GTK_WIDGET(SP_DT_CANVAS(desktop))->window, event_context->cursor); + break; + + case GDK_KEY_PRESS: + if (get_group0_keyval (&event->key) == GDK_space) { + if (sc->dragging && sc->grabbed) { + /* stamping mode: show content mode moving */ + seltrans->stamp(); + ret = TRUE; + } + } + break; + + default: + break; + } + + if (!ret) { + if (((SPEventContextClass *) parent_class)->item_handler) + ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event); + } + + return ret; +} + +static gint +sp_select_context_root_handler(SPEventContext *event_context, GdkEvent *event) +{ + SPItem *item = NULL; + SPItem *item_at_point = NULL, *group_at_point = NULL, *item_in_group = NULL; + gint ret = FALSE; + + SPDesktop *desktop = event_context->desktop; + SPSelectContext *sc = SP_SELECT_CONTEXT(event_context); + Inkscape::SelTrans *seltrans = sc->_seltrans; + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + gdouble const nudge = prefs_get_double_attribute_limited("options.nudgedistance", "value", 2, 0, 1000); // in px + gdouble const offset = prefs_get_double_attribute_limited("options.defaultscale", "value", 2, 0, 1000); + tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100); + int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12); + + // make sure we still have valid objects to move around + if (sc->item && SP_OBJECT_DOCUMENT( SP_OBJECT(sc->item))==NULL) { + sp_select_context_abort(event_context); + } + + switch (event->type) { + case GDK_2BUTTON_PRESS: + if (event->button.button == 1) { + if (!selection->isEmpty()) { + SPItem *clicked_item = (SPItem *) selection->itemList()->data; + if (SP_IS_GROUP (clicked_item)) { // enter group + desktop->setCurrentLayer(reinterpret_cast(clicked_item)); + SP_DT_SELECTION(desktop)->clear(); + sc->dragging = false; + } else { // switch tool + tools_switch_by_item (desktop, clicked_item); + } + } else { + sp_select_context_up_one_layer(desktop); + } + ret = TRUE; + } + break; + case GDK_BUTTON_PRESS: + if (event->button.button == 1) { + + // save drag origin + xp = (gint) event->button.x; + yp = (gint) event->button.y; + within_tolerance = true; + + NR::Point const button_pt(event->button.x, event->button.y); + NR::Point const p(desktop->w2d(button_pt)); + Inkscape::Rubberband::get()->start(desktop, p); + sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), + GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK, + NULL, event->button.time); + sc->grabbed = SP_CANVAS_ITEM(desktop->acetate); + + // remember what modifiers were on before button press + sc->button_press_shift = (event->button.state & GDK_SHIFT_MASK) ? true : false; + sc->button_press_ctrl = (event->button.state & GDK_CONTROL_MASK) ? true : false; + sc->button_press_alt = (event->button.state & GDK_MOD1_MASK) ? true : false; + + sc->moved = FALSE; + + rb_escaped = drag_escaped = 0; + + ret = TRUE; + } else if (event->button.button == 3) { + // right click; do not eat it so that right-click menu can appear, but cancel dragging & rubberband + sp_select_context_abort(event_context); + } + break; + + case GDK_MOTION_NOTIFY: + if (event->motion.state & GDK_BUTTON1_MASK) { + NR::Point const motion_pt(event->motion.x, event->motion.y); + NR::Point const p(desktop->w2d(motion_pt)); + + if ( within_tolerance + && ( abs( (gint) event->motion.x - xp ) < tolerance ) + && ( abs( (gint) event->motion.y - yp ) < tolerance ) ) { + break; // do not drag if we're within tolerance from origin + } + // Once the user has moved farther than tolerance from the original location + // (indicating they intend to move the object, not click), then always process the + // motion notify coordinates as given (no snapping back to origin) + within_tolerance = false; + + if (sc->button_press_ctrl || sc->button_press_alt) // if ctrl or alt was pressed and it's not click, we want to drag rather than rubberband + sc->dragging = TRUE; + + if (sc->dragging) { + /* User has dragged fast, so we get events on root (lauris)*/ + // not only that; we will end up here when ctrl-dragging as well + // and also when we started within tolerance, but trespassed tolerance outside of item + Inkscape::Rubberband::get()->stop(); + item_at_point = desktop->item_at_point(NR::Point(event->button.x, event->button.y), FALSE); + if (!item_at_point) // if no item at this point, try at the click point (bug 1012200) + item_at_point = desktop->item_at_point(NR::Point(xp, yp), FALSE); + if (item_at_point || sc->moved || sc->button_press_alt) { + // drag only if starting from an item, or if something is already grabbed, or if alt-dragging + if (!sc->moved) { + item_in_group = desktop->item_at_point(NR::Point(event->button.x, event->button.y), TRUE); + group_at_point = desktop->group_at_point(NR::Point(event->button.x, event->button.y)); + // if neither a group nor an item (possibly in a group) at point are selected, set selection to the item at point + if ((!item_in_group || !selection->includes(item_in_group)) && + (!group_at_point || !selection->includes(group_at_point)) + && !sc->button_press_alt) { + // select what is under cursor + if (!seltrans->isEmpty()) { + seltrans->resetState(); + } + // when simply ctrl-dragging, we don't want to go into groups + if (item_at_point && !selection->includes(item_at_point)) + selection->set(item_at_point); + } // otherwise, do not change selection so that dragging selected-within-group items, as well as alt-dragging, is possible + seltrans->grab(p, -1, -1, FALSE); + sc->moved = TRUE; + } + if (!seltrans->isEmpty()) + seltrans->moveTo(p, event->button.state); + if (desktop->scroll_to_point(&p)) + // unfortunately in complex drawings, gobbling results in losing grab of the object, for some mysterious reason + ; //gobble_motion_events(GDK_BUTTON1_MASK); + ret = TRUE; + } else { + sc->dragging = FALSE; + } + } else { + Inkscape::Rubberband::get()->move(p); + gobble_motion_events(GDK_BUTTON1_MASK); + } + } + break; + case GDK_BUTTON_RELEASE: + xp = yp = 0; + if ((event->button.button == 1) && (sc->grabbed)) { + if (sc->dragging) { + if (sc->moved) { + // item has been moved + seltrans->ungrab(); + sc->moved = FALSE; + } else if (sc->item && !drag_escaped) { + // item has not been moved -> simply a click, do selecting + if (!selection->isEmpty()) { + if (event->button.state & GDK_SHIFT_MASK) { + // with shift, toggle selection + seltrans->resetState(); + selection->toggle(sc->item); + } else { + // without shift, increase state (i.e. toggle scale/rotation handles) + if (selection->includes(sc->item)) { + seltrans->increaseState(); + } else { + seltrans->resetState(); + selection->set(sc->item); + } + } + } else { // simple or shift click, no previous selection + seltrans->resetState(); + selection->set(sc->item); + } + } + sc->dragging = FALSE; + if (sc->item) { + sp_object_unref( SP_OBJECT(sc->item), NULL); + } + sc->item = NULL; + } else { + NR::Maybe const b = Inkscape::Rubberband::get()->getRectangle(); + if (b != NR::Nothing() && !within_tolerance) { + // this was a rubberband drag + Inkscape::Rubberband::get()->stop(); + seltrans->resetState(); + // find out affected items: + GSList *items = sp_document_items_in_box(SP_DT_DOCUMENT(desktop), desktop->dkey, b.assume()); + if (event->button.state & GDK_SHIFT_MASK) { + // with shift, add to selection + selection->addList (items); + } else { + // without shift, simply select anew + selection->setList (items); + } + g_slist_free (items); + } else { // it was just a click, or a too small rubberband + Inkscape::Rubberband::get()->stop(); + if (sc->button_press_shift && !rb_escaped && !drag_escaped) { + // this was a shift-click, select what was clicked upon + + sc->button_press_shift = false; + + if (sc->button_press_ctrl) { + // go into groups, honoring Alt + item = sp_event_context_find_item (desktop, + NR::Point(event->button.x, event->button.y), event->button.state & GDK_MOD1_MASK, TRUE); + sc->button_press_ctrl = FALSE; + } else { + // don't go into groups, honoring Alt + item = sp_event_context_find_item (desktop, + NR::Point(event->button.x, event->button.y), event->button.state & GDK_MOD1_MASK, FALSE); + } + + if (item) { + selection->toggle(item); + item = NULL; + } + + } else if (sc->button_press_ctrl && !rb_escaped && !drag_escaped) { // ctrl-click + + sc->button_press_ctrl = FALSE; + + item = sp_event_context_find_item (desktop, + NR::Point(event->button.x, event->button.y), event->button.state & GDK_MOD1_MASK, TRUE); + + if (item) { + if (selection->includes(item)) { + seltrans->increaseState(); + } else { + seltrans->resetState(); + selection->set(item); + } + item = NULL; + } + + } else { // click without shift, simply deselect, unless with Alt or something was cancelled + if (!selection->isEmpty()) { + if (!(rb_escaped) && !(drag_escaped) && !(event->button.state & GDK_MOD1_MASK)) + selection->clear(); + rb_escaped = 0; + ret = TRUE; + } + } + } + ret = TRUE; + } + if (sc->grabbed) { + sp_canvas_item_ungrab(sc->grabbed, event->button.time); + sc->grabbed = NULL; + } + } + sc->button_press_shift = false; + sc->button_press_ctrl = false; + sc->button_press_alt = false; + break; + + case GDK_KEY_PRESS: // keybindings for select context + + if (!key_is_a_modifier (get_group0_keyval (&event->key))) { + event_context->defaultMessageContext()->clear(); + } else { + sp_event_show_modifier_tip (event_context->defaultMessageContext(), event, + _("Ctrl: select in groups, move hor/vert"), + _("Shift: toggle select, force rubberband, disable snapping"), + _("Alt: select under, move selected")); + break; + } + + switch (get_group0_keyval (&event->key)) { + case GDK_Left: // move selection left + case GDK_KP_Left: + case GDK_KP_4: + if (!MOD__CTRL) { // not ctrl + if (MOD__ALT) { // alt + if (MOD__SHIFT) sp_selection_move_screen(-10, 0); // shift + else sp_selection_move_screen(-1, 0); // no shift + } + else { // no alt + if (MOD__SHIFT) sp_selection_move(-10*nudge, 0); // shift + else sp_selection_move(-nudge, 0); // no shift + } + ret = TRUE; + } + break; + case GDK_Up: // move selection up + case GDK_KP_Up: + case GDK_KP_8: + if (!MOD__CTRL) { // not ctrl + if (MOD__ALT) { // alt + if (MOD__SHIFT) sp_selection_move_screen(0, 10); // shift + else sp_selection_move_screen(0, 1); // no shift + } + else { // no alt + if (MOD__SHIFT) sp_selection_move(0, 10*nudge); // shift + else sp_selection_move(0, nudge); // no shift + } + ret = TRUE; + } + break; + case GDK_Right: // move selection right + case GDK_KP_Right: + case GDK_KP_6: + if (!MOD__CTRL) { // not ctrl + if (MOD__ALT) { // alt + if (MOD__SHIFT) sp_selection_move_screen(10, 0); // shift + else sp_selection_move_screen(1, 0); // no shift + } + else { // no alt + if (MOD__SHIFT) sp_selection_move(10*nudge, 0); // shift + else sp_selection_move(nudge, 0); // no shift + } + ret = TRUE; + } + break; + case GDK_Down: // move selection down + case GDK_KP_Down: + case GDK_KP_2: + if (!MOD__CTRL) { // not ctrl + if (MOD__ALT) { // alt + if (MOD__SHIFT) sp_selection_move_screen(0, -10); // shift + else sp_selection_move_screen(0, -1); // no shift + } + else { // no alt + if (MOD__SHIFT) sp_selection_move(0, -10*nudge); // shift + else sp_selection_move(0, -nudge); // no shift + } + ret = TRUE; + } + break; + case GDK_Escape: + if (!sp_select_context_abort(event_context)) + selection->clear(); + ret = TRUE; + break; + case GDK_a: + case GDK_A: + if (MOD__CTRL_ONLY) { + sp_edit_select_all(); + ret = TRUE; + } + break; + case GDK_Tab: // Tab - cycle selection forward + if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) { + sp_selection_item_next(); + ret = TRUE; + } + break; + case GDK_ISO_Left_Tab: // Shift Tab - cycle selection backward + if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) { + sp_selection_item_prev(); + ret = TRUE; + } + break; + case GDK_space: + /* stamping mode: show outline mode moving */ + /* FIXME: Is next condition ok? (lauris) */ + if (sc->dragging && sc->grabbed) { + seltrans->stamp(); + ret = TRUE; + } + break; + case GDK_x: + case GDK_X: + if (MOD__ALT_ONLY) { + desktop->setToolboxFocusTo ("altx"); + ret = TRUE; + } + break; + case GDK_bracketleft: + if (MOD__ALT) { + sp_selection_rotate_screen(selection, 1); + } else if (MOD__CTRL) { + sp_selection_rotate(selection, 90); + } else if (snaps) { + sp_selection_rotate(selection, 180/snaps); + } + ret = TRUE; + break; + case GDK_bracketright: + if (MOD__ALT) { + sp_selection_rotate_screen(selection, -1); + } else if (MOD__CTRL) { + sp_selection_rotate(selection, -90); + } else if (snaps) { + sp_selection_rotate(selection, -180/snaps); + } + ret = TRUE; + break; + case GDK_less: + case GDK_comma: + if (MOD__ALT) { + sp_selection_scale_screen(selection, -2); + } else if (MOD__CTRL) { + sp_selection_scale_times(selection, 0.5); + } else { + sp_selection_scale(selection, -offset); + } + ret = TRUE; + break; + case GDK_greater: + case GDK_period: + if (MOD__ALT) { + sp_selection_scale_screen(selection, 2); + } else if (MOD__CTRL) { + sp_selection_scale_times(selection, 2); + } else { + sp_selection_scale(selection, offset); + } + ret = TRUE; + break; + case GDK_Return: + if (MOD__CTRL_ONLY) { + if (selection->singleItem()) { + SPItem *clicked_item = selection->singleItem(); + if (SP_IS_GROUP (clicked_item)) { // enter group + desktop->setCurrentLayer(reinterpret_cast(clicked_item)); + SP_DT_SELECTION(desktop)->clear(); + } else { + SP_EVENT_CONTEXT(sc)->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Selected object is not a group. Cannot enter.")); + } + } + ret = TRUE; + } + break; + case GDK_BackSpace: + if (MOD__CTRL_ONLY) { + sp_select_context_up_one_layer(desktop); + ret = TRUE; + } + break; + default: + break; + } + break; + case GDK_KEY_RELEASE: + if (key_is_a_modifier (get_group0_keyval (&event->key))) + event_context->defaultMessageContext()->clear(); + break; + default: + break; + } + + if (!ret) { + if (((SPEventContextClass *) parent_class)->root_handler) + ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event); + } + + return ret; +} + + +/* + 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/select-context.h b/src/select-context.h new file mode 100644 index 000000000..b7e5748a0 --- /dev/null +++ b/src/select-context.h @@ -0,0 +1,52 @@ +#ifndef __SP_SELECT_CONTEXT_H__ +#define __SP_SELECT_CONTEXT_H__ + +/* + * Select tool + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "event-context.h" + +#define SP_TYPE_SELECT_CONTEXT (sp_select_context_get_type ()) +#define SP_SELECT_CONTEXT(obj) (GTK_CHECK_CAST ((obj), SP_TYPE_SELECT_CONTEXT, SPSelectContext)) +#define SP_SELECT_CONTEXT_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), SP_TYPE_SELECT_CONTEXT, SPSelectContextClass)) +#define SP_IS_SELECT_CONTEXT(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_SELECT_CONTEXT)) +#define SP_IS_SELECT_CONTEXT_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), SP_TYPE_SELECT_CONTEXT)) + +class SPSelectContext; +class SPSelectContextClass; + +namespace Inkscape { + class MessageContext; + class SelTrans; + class SelectionDescriber; +} + +struct SPSelectContext : public SPEventContext { + guint dragging : 1; + guint moved : 1; + bool button_press_shift; + bool button_press_ctrl; + bool button_press_alt; + SPItem *item; + SPCanvasItem *grabbed; + Inkscape::SelTrans *_seltrans; + Inkscape::SelectionDescriber *_describer; +}; + +struct SPSelectContextClass { + SPEventContextClass parent_class; +}; + +/* Standard Gtk function */ + +GtkType sp_select_context_get_type (void); + +#endif diff --git a/src/selection-chemistry.cpp b/src/selection-chemistry.cpp new file mode 100644 index 000000000..ee64e87ec --- /dev/null +++ b/src/selection-chemistry.cpp @@ -0,0 +1,2217 @@ +#define __SP_SELECTION_CHEMISTRY_C__ + +/* + * Miscellanous operations on selected items + * + * Authors: + * Lauris Kaplinski + * Frank Felfe + * MenTaLguY + * bulia byak + * + * Copyright (C) 1999-2005 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include "svg/svg.h" +#include "inkscape.h" +#include "desktop.h" +#include "desktop-style.h" +#include "selection.h" +#include "tools-switch.h" +#include "desktop-handles.h" +#include "message-stack.h" +#include "sp-item-transform.h" +#include "sp-marker.h" +#include "sp-use.h" +#include "sp-textpath.h" +#include "sp-tspan.h" +#include "sp-flowtext.h" +#include "sp-flowregion.h" +#include "text-editing.h" +#include "text-context.h" +#include "dropper-context.h" +#include +#include "libnr/nr-matrix-rotate-ops.h" +#include "libnr/nr-matrix-translate-ops.h" +#include "libnr/nr-rotate-fns.h" +#include "libnr/nr-scale-ops.h" +#include "libnr/nr-scale-translate-ops.h" +#include "libnr/nr-translate-matrix-ops.h" +#include "libnr/nr-translate-scale-ops.h" +#include "xml/repr.h" +#include "style.h" +#include "document-private.h" +#include "sp-gradient.h" +#include "sp-gradient-reference.h" +#include "sp-linear-gradient-fns.h" +#include "sp-pattern.h" +#include "sp-radial-gradient-fns.h" +#include "sp-namedview.h" +#include "prefs-utils.h" +#include "sp-offset.h" +#include "file.h" +#include "layer-fns.h" +#include "context-fns.h" +using NR::X; +using NR::Y; + +#include "selection-chemistry.h" + +/* fixme: find a better place */ +GSList *clipboard = NULL; +GSList *defs_clipboard = NULL; +SPCSSAttr *style_clipboard = NULL; + +static void sp_copy_stuff_used_by_item(GSList **defs_clip, SPItem *item, const GSList *items); + +void sp_selection_copy_one (Inkscape::XML::Node *repr, NR::Matrix full_t, GSList **clip) +{ + Inkscape::XML::Node *copy = repr->duplicate(); + + // copy complete inherited style + SPCSSAttr *css = sp_repr_css_attr_inherited(repr, "style"); + sp_repr_css_set(copy, css, "style"); + sp_repr_css_attr_unref(css); + + // write the complete accummulated transform passed to us + // (we're dealing with unattached repr, so we write to its attr instead of using sp_item_set_transform) + gchar affinestr[80]; + if (sp_svg_transform_write(affinestr, 79, full_t)) { + copy->setAttribute("transform", affinestr); + } else { + copy->setAttribute("transform", NULL); + } + + *clip = g_slist_prepend(*clip, copy); +} + +void sp_selection_copy_impl (const GSList *items, GSList **clip, GSList **defs_clip, SPCSSAttr **style_clip) +{ + + // Copy stuff referenced by all items to defs_clip: + if (defs_clip) { + for (GSList *i = (GSList *) items; i != NULL; i = i->next) { + sp_copy_stuff_used_by_item (defs_clip, SP_ITEM (i->data), items); + } + *defs_clip = g_slist_reverse(*defs_clip); + } + + // Store style: + if (style_clip) { + SPItem *item = SP_ITEM (items->data); // take from the first selected item + *style_clip = take_style_from_item (item); + } + + if (clip) { + // Sort items: + GSList *sorted_items = g_slist_copy ((GSList *) items); + sorted_items = g_slist_sort((GSList *) sorted_items, (GCompareFunc) sp_object_compare_position); + + // Copy item reprs: + for (GSList *i = (GSList *) sorted_items; i != NULL; i = i->next) { + sp_selection_copy_one (SP_OBJECT_REPR (i->data), sp_item_i2doc_affine(SP_ITEM (i->data)), clip); + } + + *clip = g_slist_reverse(*clip); + g_slist_free ((GSList *) sorted_items); + } +} + +/** +Add gradients/patterns/markers referenced by copied objects to defs +*/ +void +paste_defs (GSList **defs_clip, SPDocument *doc) +{ + if (!defs_clip) + return; + + for (GSList *gl = *defs_clip; gl != NULL; gl = gl->next) { + SPDefs *defs= (SPDefs *) SP_DOCUMENT_DEFS(doc); + Inkscape::XML::Node *repr = (Inkscape::XML::Node *) gl->data; + gchar const *id = repr->attribute("id"); + if (!id || !doc->getObjectById(id)) { + Inkscape::XML::Node *copy = repr->duplicate(); + SP_OBJECT_REPR(defs)->addChild(copy, NULL); + Inkscape::GC::release(copy); + } + } +} + +GSList *sp_selection_paste_impl (SPDocument *document, SPObject *parent, GSList **clip, GSList **defs_clip) +{ + paste_defs (defs_clip, document); + + GSList *copied = NULL; + // add objects to document + for (GSList *l = *clip; l != NULL; l = l->next) { + Inkscape::XML::Node *repr = (Inkscape::XML::Node *) l->data; + Inkscape::XML::Node *copy = repr->duplicate(); + + // premultiply the item transform by the accumulated parent transform in the paste layer + NR::Matrix local = sp_item_i2doc_affine(SP_ITEM(parent)); + if (!local.test_identity()) { + gchar const *t_str = copy->attribute("transform"); + NR::Matrix item_t (NR::identity()); + if (t_str) + sp_svg_transform_read(t_str, &item_t); + item_t *= local.inverse(); + // (we're dealing with unattached repr, so we write to its attr instead of using sp_item_set_transform) + gchar affinestr[80]; + if (sp_svg_transform_write(affinestr, 79, item_t)) { + copy->setAttribute("transform", affinestr); + } else { + copy->setAttribute("transform", NULL); + } + } + + parent->appendChildRepr(copy); + copied = g_slist_prepend(copied, copy); + Inkscape::GC::release(copy); + } + return copied; +} + +void sp_selection_delete_impl(const GSList *items) +{ + for (const GSList *i = items ; i ; i = i->next ) { + sp_object_ref((SPObject *)i->data, NULL); + } + for (const GSList *i = items; i != NULL; i = i->next) { + SPItem *item = (SPItem *) i->data; + SP_OBJECT(item)->deleteObject(); + sp_object_unref((SPObject *)item, NULL); + } +} + + +void sp_selection_delete() +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop == NULL) { + return; + } + + if (tools_isactive (desktop, TOOLS_TEXT)) + if (sp_text_delete_selection(desktop->event_context)) { + sp_document_done(SP_DT_DOCUMENT(desktop)); + return; + } + + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + + // check if something is selected + if (selection->isEmpty()) { + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Nothing was deleted.")); + return; + } + + const GSList *selected = g_slist_copy(const_cast(selection->itemList())); + selection->clear(); + sp_selection_delete_impl (selected); + g_slist_free ((GSList *) selected); + + /* a tool may have set up private information in it's selection context + * that depends on desktop items. I think the only sane way to deal with + * this currently is to reset the current tool, which will reset it's + * associated selection context. For example: deleting an object + * while moving it around the canvas. + */ + tools_switch ( desktop, tools_active ( desktop ) ); + + sp_document_done(SP_DT_DOCUMENT(desktop)); +} + +/* fixme: sequencing */ +void sp_selection_duplicate() +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop == NULL) + return; + + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + + // check if something is selected + if (selection->isEmpty()) { + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select object(s) to duplicate.")); + return; + } + + GSList *reprs = g_slist_copy((GSList *) selection->reprList()); + + selection->clear(); + + // sorting items from different parents sorts each parent's subset without possibly mixing them, just what we need + reprs = g_slist_sort(reprs, (GCompareFunc) sp_repr_compare_position); + + GSList *newsel = NULL; + + while (reprs) { + Inkscape::XML::Node *parent = ((Inkscape::XML::Node *) reprs->data)->parent(); + Inkscape::XML::Node *copy = ((Inkscape::XML::Node *) reprs->data)->duplicate(); + + parent->appendChild(copy); + + newsel = g_slist_prepend(newsel, copy); + reprs = g_slist_remove(reprs, reprs->data); + Inkscape::GC::release(copy); + } + + sp_document_done(SP_DT_DOCUMENT(desktop)); + + selection->setReprList(newsel); + + g_slist_free(newsel); +} + +void sp_edit_clear_all() +{ + SPDesktop *dt = SP_ACTIVE_DESKTOP; + if (!dt) + return; + + SPDocument *doc = SP_DT_DOCUMENT(dt); + SP_DT_SELECTION(dt)->clear(); + + g_return_if_fail(SP_IS_GROUP(dt->currentLayer())); + GSList *items = sp_item_group_item_list(SP_GROUP(dt->currentLayer())); + + while (items) { + SP_OBJECT (items->data)->deleteObject(); + items = g_slist_remove(items, items->data); + } + + sp_document_done(doc); +} + +GSList * +get_all_items (GSList *list, SPObject *from, SPDesktop *desktop, bool onlyvisible, bool onlysensitive, const GSList *exclude) +{ + for (SPObject *child = sp_object_first_child(SP_OBJECT(from)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) { + if (SP_IS_ITEM(child) && + !desktop->isLayer(SP_ITEM(child)) && + (!onlysensitive || !SP_ITEM(child)->isLocked()) && + (!onlyvisible || !desktop->itemIsHidden(SP_ITEM(child))) && + (!exclude || !g_slist_find ((GSList *) exclude, child)) + ) + { + list = g_slist_prepend (list, SP_ITEM(child)); + } + + if (SP_IS_ITEM(child) && desktop->isLayer(SP_ITEM(child))) { + list = get_all_items (list, child, desktop, onlyvisible, onlysensitive, exclude); + } + } + + return list; +} + +void sp_edit_select_all_full (bool force_all_layers, bool invert) +{ + SPDesktop *dt = SP_ACTIVE_DESKTOP; + if (!dt) + return; + + Inkscape::Selection *selection = SP_DT_SELECTION(dt); + + g_return_if_fail(SP_IS_GROUP(dt->currentLayer())); + + bool inlayer = prefs_get_int_attribute ("options.kbselection", "inlayer", 1); + bool onlyvisible = prefs_get_int_attribute ("options.kbselection", "onlyvisible", 1); + bool onlysensitive = prefs_get_int_attribute ("options.kbselection", "onlysensitive", 1); + + GSList *items = NULL; + + const GSList *exclude = NULL; + if (invert) { + exclude = selection->itemList(); + } + + if (inlayer && !force_all_layers) { + + if ( (onlysensitive && SP_ITEM(dt->currentLayer())->isLocked()) || + (onlyvisible && dt->itemIsHidden(SP_ITEM(dt->currentLayer()))) ) + return; + + GSList *all_items = sp_item_group_item_list(SP_GROUP(dt->currentLayer())); + + for (GSList *i = all_items; i; i = i->next) { + SPItem *item = SP_ITEM (i->data); + + if (item && (!onlysensitive || !item->isLocked())) { + if (!onlyvisible || !dt->itemIsHidden(item)) { + if (!dt->isLayer(item)) { + if (!invert || !g_slist_find ((GSList *) exclude, item)) { + items = g_slist_prepend (items, item); // leave it in the list + } + } + } + } + } + + g_slist_free (all_items); + + } else { + items = get_all_items (NULL, dt->currentRoot(), dt, onlyvisible, onlysensitive, exclude); + } + + selection->setList (items); + + if (items) { + g_slist_free (items); + } +} + +void sp_edit_select_all () +{ + sp_edit_select_all_full (false, false); +} + +void sp_edit_select_all_in_all_layers () +{ + sp_edit_select_all_full (true, false); +} + +void sp_edit_invert () +{ + sp_edit_select_all_full (false, true); +} + +void sp_edit_invert_in_all_layers () +{ + sp_edit_select_all_full (true, true); +} + +void sp_selection_group() +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop == NULL) + return; + + SPDocument *document = SP_DT_DOCUMENT (desktop); + + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + + // Check if something is selected. + if (selection->isEmpty()) { + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select two or more objects to group.")); + return; + } + + GSList const *l = (GSList *) selection->reprList(); + + // Check if at least two objects are selected. + if (l->next == NULL) { + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select at least two objects to group.")); + return; + } + + GSList *p = g_slist_copy((GSList *) l); + + selection->clear(); + + p = g_slist_sort(p, (GCompareFunc) sp_repr_compare_position); + + // Remember the position and parent of the topmost object. + gint topmost = ((Inkscape::XML::Node *) g_slist_last(p)->data)->position(); + Inkscape::XML::Node *topmost_parent = ((Inkscape::XML::Node *) g_slist_last(p)->data)->parent(); + + Inkscape::XML::Node *group = sp_repr_new("svg:g"); + + while (p) { + Inkscape::XML::Node *current = (Inkscape::XML::Node *) p->data; + + if (current->parent() == topmost_parent) { + Inkscape::XML::Node *spnew = current->duplicate(); + sp_repr_unparent(current); + group->appendChild(spnew); + Inkscape::GC::release(spnew); + topmost --; // only reduce count for those items deleted from topmost_parent + } else { // move it to topmost_parent first + GSList *temp_clip = NULL; + + // At this point, current may already have no item, due to its being a clone whose original is already moved away + // So we copy it artificially calculating the transform from its repr->attr("transform") and the parent transform + gchar const *t_str = current->attribute("transform"); + NR::Matrix item_t (NR::identity()); + if (t_str) + sp_svg_transform_read(t_str, &item_t); + item_t *= sp_item_i2doc_affine(SP_ITEM(document->getObjectByRepr(current->parent()))); + //FIXME: when moving both clone and original from a transformed group (either by + //grouping into another parent, or by cut/paste) the transform from the original's + //parent becomes embedded into original itself, and this affects its clones. Fix + //this by remembering the transform diffs we write to each item into an array and + //then, if this is clone, looking up its original in that array and pre-multiplying + //it by the inverse of that original's transform diff. + + sp_selection_copy_one (current, item_t, &temp_clip); + sp_repr_unparent(current); + + // paste into topmost_parent (temporarily) + GSList *copied = sp_selection_paste_impl (document, document->getObjectByRepr(topmost_parent), &temp_clip, NULL); + if (temp_clip) g_slist_free (temp_clip); + if (copied) { // if success, + // take pasted object (now in topmost_parent) + Inkscape::XML::Node *in_topmost = (Inkscape::XML::Node *) copied->data; + // make a copy + Inkscape::XML::Node *spnew = in_topmost->duplicate(); + // remove pasted + sp_repr_unparent(in_topmost); + // put its copy into group + group->appendChild(spnew); + Inkscape::GC::release(spnew); + g_slist_free (copied); + } + } + p = g_slist_remove(p, current); + } + + // Add the new group to the topmost members' parent + topmost_parent->appendChild(group); + + // Move to the position of the topmost, reduced by the number of items deleted from topmost_parent + group->setPosition(topmost + 1); + + sp_document_done(SP_DT_DOCUMENT(desktop)); + + selection->set(group); + Inkscape::GC::release(group); +} + +void sp_selection_ungroup() +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop == NULL) + return; + + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + + if (selection->isEmpty()) { + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select a group to ungroup.")); + return; + } + + GSList *items = g_slist_copy((GSList *) selection->itemList()); + selection->clear(); + + // Get a copy of current selection. + GSList *new_select = NULL; + bool ungrouped = false; + for (GSList *i = items; + i != NULL; + i = i->next) + { + SPItem *group = (SPItem *) i->data; + + // when ungrouping cloned groups with their originals, some objects that were selected may no more exist due to unlinking + if (!SP_IS_OBJECT(group)) { + continue; + } + + /* We do not allow ungrouping etc. (lauris) */ + if (strcmp(SP_OBJECT_REPR(group)->name(), "svg:g") && strcmp(SP_OBJECT_REPR(group)->name(), "svg:switch")) { + // keep the non-group item in the new selection + selection->add(group); + continue; + } + + GSList *children = NULL; + /* This is not strictly required, but is nicer to rely on group ::destroy (lauris) */ + sp_item_group_ungroup(SP_GROUP(group), &children, false); + ungrouped = true; + // Add ungrouped items to the new selection. + new_select = g_slist_concat(new_select, children); + } + + if (new_select) { // Set new selection. + selection->addList(new_select); + g_slist_free(new_select); + } + if (!ungrouped) { + desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("No groups to ungroup in the selection.")); + } + + g_slist_free(items); + + sp_document_done(SP_DT_DOCUMENT(desktop)); +} + +static SPGroup * +sp_item_list_common_parent_group(const GSList *items) +{ + if (!items) { + return NULL; + } + SPObject *parent = SP_OBJECT_PARENT(items->data); + /* Strictly speaking this CAN happen, if user selects from XML editor */ + if (!SP_IS_GROUP(parent)) { + return NULL; + } + for (items = items->next; items; items = items->next) { + if (SP_OBJECT_PARENT(items->data) != parent) { + return NULL; + } + } + + return SP_GROUP(parent); +} + +/** Finds out the minimum common bbox of the selected items + */ +NR::Rect +enclose_items(const GSList *items) +{ + g_assert(items != NULL); + + NR::Rect r = sp_item_bbox_desktop((SPItem *) items->data); + + for (GSList *i = items->next; i; i = i->next) { + r = NR::Rect::union_bounds(r, sp_item_bbox_desktop((SPItem *) i->data)); + } + + return r; +} + +SPObject * +prev_sibling(SPObject *child) +{ + SPObject *parent = SP_OBJECT_PARENT(child); + if (!SP_IS_GROUP(parent)) { + return NULL; + } + for ( SPObject *i = sp_object_first_child(parent) ; i; i = SP_OBJECT_NEXT(i) ) { + if (i->next == child) + return i; + } + return NULL; +} + +void +sp_selection_raise() +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (!desktop) + return; + + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + + GSList const *items = (GSList *) selection->itemList(); + if (!items) { + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select object(s) to raise.")); + return; + } + + SPGroup const *group = sp_item_list_common_parent_group(items); + if (!group) { + desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("You cannot raise/lower objects from different groups or layers.")); + return; + } + + Inkscape::XML::Node *grepr = SP_OBJECT_REPR(group); + + /* construct reverse-ordered list of selected children */ + GSList *rev = g_slist_copy((GSList *) items); + rev = g_slist_sort(rev, (GCompareFunc) sp_item_repr_compare_position); + + // find out the common bbox of the selected items + NR::Rect selected = enclose_items(items); + + // for all objects in the selection (starting from top) + while (rev) { + SPObject *child = SP_OBJECT(rev->data); + // for each selected object, find the next sibling + for (SPObject *newref = child->next; newref; newref = newref->next) { + // if the sibling is an item AND overlaps our selection, + if (SP_IS_ITEM(newref) && selected.intersects(sp_item_bbox_desktop(SP_ITEM(newref)))) { + // AND if it's not one of our selected objects, + if (!g_slist_find((GSList *) items, newref)) { + // move the selected object after that sibling + grepr->changeOrder(SP_OBJECT_REPR(child), SP_OBJECT_REPR(newref)); + } + break; + } + } + rev = g_slist_remove(rev, child); + } + + sp_document_done(SP_DT_DOCUMENT(desktop)); +} + +void sp_selection_raise_to_top() +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop == NULL) + return; + + SPDocument *document = SP_DT_DOCUMENT(desktop); + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + + if (selection->isEmpty()) { + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select object(s) to raise to top.")); + return; + } + + GSList const *items = (GSList *) selection->itemList(); + + SPGroup const *group = sp_item_list_common_parent_group(items); + if (!group) { + desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("You cannot raise/lower objects from different groups or layers.")); + return; + } + + GSList *rl = g_slist_copy((GSList *) selection->reprList()); + rl = g_slist_sort(rl, (GCompareFunc) sp_repr_compare_position); + + for (GSList *l = rl; l != NULL; l = l->next) { + Inkscape::XML::Node *repr = (Inkscape::XML::Node *) l->data; + repr->setPosition(-1); + } + + g_slist_free(rl); + + sp_document_done(document); +} + +void +sp_selection_lower() +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop == NULL) + return; + + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + + GSList const *items = (GSList *) selection->itemList(); + if (!items) { + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select object(s) to lower.")); + return; + } + + SPGroup const *group = sp_item_list_common_parent_group(items); + if (!group) { + desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("You cannot raise/lower objects from different groups or layers.")); + return; + } + + Inkscape::XML::Node *grepr = SP_OBJECT_REPR(group); + + // find out the common bbox of the selected items + NR::Rect selected = enclose_items(items); + + /* construct direct-ordered list of selected children */ + GSList *rev = g_slist_copy((GSList *) items); + rev = g_slist_sort(rev, (GCompareFunc) sp_item_repr_compare_position); + rev = g_slist_reverse(rev); + + // for all objects in the selection (starting from top) + while (rev) { + SPObject *child = SP_OBJECT(rev->data); + // for each selected object, find the prev sibling + for (SPObject *newref = prev_sibling(child); newref; newref = prev_sibling(newref)) { + // if the sibling is an item AND overlaps our selection, + if (SP_IS_ITEM(newref) && selected.intersects(sp_item_bbox_desktop(SP_ITEM(newref)))) { + // AND if it's not one of our selected objects, + if (!g_slist_find((GSList *) items, newref)) { + // move the selected object before that sibling + SPObject *put_after = prev_sibling(newref); + if (put_after) + grepr->changeOrder(SP_OBJECT_REPR(child), SP_OBJECT_REPR(put_after)); + else + SP_OBJECT_REPR(child)->setPosition(0); + } + break; + } + } + rev = g_slist_remove(rev, child); + } + + sp_document_done(SP_DT_DOCUMENT(desktop)); + +} + +void sp_selection_lower_to_bottom() +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop == NULL) + return; + + SPDocument *document = SP_DT_DOCUMENT(desktop); + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + + if (selection->isEmpty()) { + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select object(s) to lower to bottom.")); + return; + } + + GSList const *items = (GSList *) selection->itemList(); + + SPGroup const *group = sp_item_list_common_parent_group(items); + if (!group) { + desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("You cannot raise/lower objects from different groups or layers.")); + return; + } + + GSList *rl; + rl = g_slist_copy((GSList *) selection->reprList()); + rl = g_slist_sort(rl, (GCompareFunc) sp_repr_compare_position); + rl = g_slist_reverse(rl); + + for (GSList *l = rl; l != NULL; l = l->next) { + gint minpos; + SPObject *pp, *pc; + Inkscape::XML::Node *repr = (Inkscape::XML::Node *) l->data; + pp = document->getObjectByRepr(sp_repr_parent(repr)); + minpos = 0; + g_assert(SP_IS_GROUP(pp)); + pc = sp_object_first_child(pp); + while (!SP_IS_ITEM(pc)) { + minpos += 1; + pc = pc->next; + } + repr->setPosition(minpos); + } + + g_slist_free(rl); + + sp_document_done(document); +} + +void +sp_undo(SPDesktop *desktop, SPDocument *doc) +{ + if (!sp_document_undo(SP_DT_DOCUMENT(desktop))) + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Nothing to undo.")); +} + +void +sp_redo(SPDesktop *desktop, SPDocument *doc) +{ + if (!sp_document_redo(SP_DT_DOCUMENT(desktop))) + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Nothing to redo.")); +} + +void sp_selection_cut() +{ + sp_selection_copy(); + sp_selection_delete(); +} + +void sp_copy_gradient (GSList **defs_clip, SPGradient *gradient) +{ + SPGradient *ref = gradient; + + while (ref) { + // climb up the refs, copying each one in the chain + Inkscape::XML::Node *grad_repr = SP_OBJECT_REPR(ref)->duplicate(); + *defs_clip = g_slist_prepend (*defs_clip, grad_repr); + + ref = ref->ref->getObject(); + } +} + +void sp_copy_pattern (GSList **defs_clip, SPPattern *pattern) +{ + SPPattern *ref = pattern; + + while (ref) { + // climb up the refs, copying each one in the chain + Inkscape::XML::Node *pattern_repr = SP_OBJECT_REPR(ref)->duplicate(); + *defs_clip = g_slist_prepend (*defs_clip, pattern_repr); + + // items in the pattern may also use gradients and other patterns, so we need to recurse here as well + for (SPObject *child = sp_object_first_child(SP_OBJECT(ref)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) { + if (!SP_IS_ITEM (child)) + continue; + sp_copy_stuff_used_by_item (defs_clip, (SPItem *) child, NULL); + } + + ref = ref->ref->getObject(); + } +} + +void sp_copy_marker (GSList **defs_clip, SPMarker *marker) +{ + Inkscape::XML::Node *marker_repr = SP_OBJECT_REPR(marker)->duplicate(); + *defs_clip = g_slist_prepend (*defs_clip, marker_repr); +} + + +void sp_copy_textpath_path (GSList **defs_clip, SPTextPath *tp, const GSList *items) +{ + SPItem *path = sp_textpath_get_path_item (tp); + if (!path) + return; + if (items && g_slist_find ((GSList *) items, path)) // do not copy it to defs if it is already in the list of items copied + return; + Inkscape::XML::Node *repr = SP_OBJECT_REPR(path)->duplicate(); + *defs_clip = g_slist_prepend (*defs_clip, repr); +} + +void sp_copy_stuff_used_by_item (GSList **defs_clip, SPItem *item, const GSList *items) +{ + SPStyle *style = SP_OBJECT_STYLE (item); + + if (style && (style->fill.type == SP_PAINT_TYPE_PAINTSERVER)) { + SPObject *server = SP_OBJECT_STYLE_FILL_SERVER(item); + if (SP_IS_LINEARGRADIENT (server) || SP_IS_RADIALGRADIENT (server)) + sp_copy_gradient (defs_clip, SP_GRADIENT(server)); + if (SP_IS_PATTERN (server)) + sp_copy_pattern (defs_clip, SP_PATTERN(server)); + } + + if (style && (style->stroke.type == SP_PAINT_TYPE_PAINTSERVER)) { + SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER(item); + if (SP_IS_LINEARGRADIENT (server) || SP_IS_RADIALGRADIENT (server)) + sp_copy_gradient (defs_clip, SP_GRADIENT(server)); + if (SP_IS_PATTERN (server)) + sp_copy_pattern (defs_clip, SP_PATTERN(server)); + } + + if (SP_IS_SHAPE (item)) { + SPShape *shape = SP_SHAPE (item); + for (int i = 0 ; i < SP_MARKER_LOC_QTY ; i++) { + if (shape->marker[i]) { + sp_copy_marker (defs_clip, SP_MARKER (shape->marker[i])); + } + } + } + + if (SP_IS_TEXT_TEXTPATH (item)) { + sp_copy_textpath_path (defs_clip, SP_TEXTPATH(sp_object_first_child(SP_OBJECT(item))), items); + } + + // recurse + for (SPObject *o = SP_OBJECT(item)->children; o != NULL; o = o->next) { + if (SP_IS_ITEM(o)) + sp_copy_stuff_used_by_item (defs_clip, SP_ITEM (o), items); + } +} + +/** + * \pre item != NULL + */ +SPCSSAttr * +take_style_from_item (SPItem *item) +{ + // write the complete cascaded style, context-free + SPCSSAttr *css = sp_css_attr_from_object (SP_OBJECT(item), SP_STYLE_FLAG_ALWAYS); + if (css == NULL) + return NULL; + + if ((SP_IS_GROUP(item) && SP_OBJECT(item)->children) || + (SP_IS_TEXT (item) && SP_OBJECT(item)->children && SP_OBJECT(item)->children->next == NULL)) { + // if this is a text with exactly one tspan child, merge the style of that tspan as well + // If this is a group, merge the style of its topmost (last) child with style + for (SPObject *last_element = item->lastChild(); last_element != NULL; last_element = SP_OBJECT_PREV (last_element)) { + if (SP_OBJECT_STYLE (last_element) != NULL) { + SPCSSAttr *temp = sp_css_attr_from_object (last_element, SP_STYLE_FLAG_IFSET); + if (temp) { + sp_repr_css_merge (css, temp); + sp_repr_css_attr_unref (temp); + } + break; + } + } + } + if (!(SP_IS_TEXT (item) || SP_IS_TSPAN (item) || SP_IS_STRING (item))) { + // do not copy text properties from non-text objects, it's confusing + css = sp_css_attr_unset_text (css); + } + + // FIXME: also transform gradient/pattern fills, by forking? NO, this must be nondestructive + double ex = NR::expansion(sp_item_i2doc_affine(item)); + if (ex != 1.0) { + css = sp_css_attr_scale (css, ex); + } + + return css; +} + + +void sp_selection_copy() +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop == NULL) + return; + + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + + if (tools_isactive (desktop, TOOLS_DROPPER)) { + sp_dropper_context_copy(desktop->event_context); + return; // copied color under cursor, nothing else to do + } + + // check if something is selected + if (selection->isEmpty()) { + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Nothing was copied.")); + return; + } + + const GSList *items = g_slist_copy ((GSList *) selection->itemList()); + + // 0. Copy text to system clipboard + // FIXME: for non-texts, put serialized XML as text to the clipboard; + //for this sp_repr_write_stream needs to be rewritten with iostream instead of FILE + Glib::ustring text; + if (tools_isactive (desktop, TOOLS_TEXT)) { + text = sp_text_get_selected_text(desktop->event_context); + } + + if (text.empty()) { + guint texts = 0; + for (GSList *i = (GSList *) items; i; i = i->next) { + SPItem *item = SP_ITEM (i->data); + if (SP_IS_TEXT (item) || SP_IS_FLOWTEXT(item)) { + if (texts > 0) // if more than one text object is copied, separate them by spaces + text += " "; + gchar *this_text = sp_te_get_string_multiline (item); + if (this_text) { + text += this_text; + g_free(this_text); + } + texts++; + } + } + } + if (!text.empty()) { + Glib::RefPtr refClipboard = Gtk::Clipboard::get(); + refClipboard->set_text(text); + } + + // clear old defs clipboard + while (defs_clipboard) { + Inkscape::GC::release((Inkscape::XML::Node *) defs_clipboard->data); + defs_clipboard = g_slist_remove (defs_clipboard, defs_clipboard->data); + } + + // clear style clipboard + if (style_clipboard) { + sp_repr_css_attr_unref (style_clipboard); + style_clipboard = NULL; + } + + //clear main clipboard + while (clipboard) { + Inkscape::GC::release((Inkscape::XML::Node *) clipboard->data); + clipboard = g_slist_remove(clipboard, clipboard->data); + } + + sp_selection_copy_impl (items, &clipboard, &defs_clipboard, &style_clipboard); + + if (tools_isactive (desktop, TOOLS_TEXT)) { // take style from cursor/text selection, overwriting the style just set by copy_impl + SPStyle *const query = sp_style_new(); + if (sp_desktop_query_style_all (desktop, query)) { + SPCSSAttr *css = sp_css_attr_from_style (query, SP_STYLE_FLAG_ALWAYS); + if (css != NULL) { + // clear style clipboard + if (style_clipboard) { + sp_repr_css_attr_unref (style_clipboard); + style_clipboard = NULL; + } + //sp_repr_css_print (css); + style_clipboard = css; + } + } + g_free (query); + } + + g_slist_free ((GSList *) items); +} + +void sp_selection_paste(bool in_place) +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + + if (desktop == NULL) { + return; + } + + SPDocument *document = SP_DT_DOCUMENT(desktop); + + if (Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false) { + return; + } + + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + + if (tools_isactive (desktop, TOOLS_TEXT)) { + if (sp_text_paste_inline(desktop->event_context)) + return; // pasted from system clipboard into text, nothing else to do + } + + // check if something is in the clipboard + if (clipboard == NULL) { + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Nothing on the clipboard.")); + return; + } + + GSList *copied = sp_selection_paste_impl(document, desktop->currentLayer(), &clipboard, &defs_clipboard); + // add pasted objects to selection + selection->setReprList((GSList const *) copied); + g_slist_free (copied); + + if (!in_place) { + sp_document_ensure_up_to_date(document); + + NR::Point m( desktop->point() - selection->bounds().midpoint() ); + + /* Snap the offset of the new item(s) to the grid */ + /* FIXME: this gridsnap fiddling is a hack. */ + Inkscape::GridSnapper &s = desktop->namedview->grid_snapper; + gdouble const curr_gridsnap = s.getDistance(); + s.setDistance(NR_HUGE); + m = s.freeSnap(Inkscape::Snapper::SNAP_POINT, m, NULL).getPoint(); + s.setDistance(curr_gridsnap); + sp_selection_move_relative(selection, m); + } + + sp_document_done(document); +} + +void sp_selection_paste_style() +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop == NULL) return; + + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + + // check if something is in the clipboard + if (clipboard == NULL) { + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Nothing on the clipboard.")); + return; + } + + // check if something is selected + if (selection->isEmpty()) { + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select object(s) to paste style to.")); + return; + } + + paste_defs (&defs_clipboard, SP_DT_DOCUMENT(desktop)); + + sp_desktop_set_style (desktop, style_clipboard); + + sp_document_done(SP_DT_DOCUMENT (desktop)); +} + +void sp_selection_to_next_layer () +{ + SPDesktop *dt = SP_ACTIVE_DESKTOP; + + Inkscape::Selection *selection = SP_DT_SELECTION(dt); + + // check if something is selected + if (selection->isEmpty()) { + dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select object(s) to move to the layer above.")); + return; + } + + const GSList *items = g_slist_copy ((GSList *) selection->itemList()); + + SPObject *next=Inkscape::next_layer(dt->currentRoot(), dt->currentLayer()); + if (next) { + GSList *temp_clip = NULL; + sp_selection_copy_impl (items, &temp_clip, NULL, NULL); // we're in the same doc, so no need to copy defs + sp_selection_delete_impl (items); + GSList *copied = sp_selection_paste_impl (SP_DT_DOCUMENT (dt), next, &temp_clip, NULL); + selection->setReprList((GSList const *) copied); + g_slist_free (copied); + if (temp_clip) g_slist_free (temp_clip); + dt->setCurrentLayer(next); + sp_document_done(SP_DT_DOCUMENT (dt)); + } else { + dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No more layers above.")); + } + + g_slist_free ((GSList *) items); +} + +void sp_selection_to_prev_layer () +{ + SPDesktop *dt = SP_ACTIVE_DESKTOP; + + Inkscape::Selection *selection = SP_DT_SELECTION(dt); + + // check if something is selected + if (selection->isEmpty()) { + dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select object(s) to move to the layer below.")); + return; + } + + const GSList *items = g_slist_copy ((GSList *) selection->itemList()); + + SPObject *next=Inkscape::previous_layer(dt->currentRoot(), dt->currentLayer()); + if (next) { + GSList *temp_clip = NULL; + sp_selection_copy_impl (items, &temp_clip, NULL, NULL); // we're in the same doc, so no need to copy defs + sp_selection_delete_impl (items); + GSList *copied = sp_selection_paste_impl (SP_DT_DOCUMENT (dt), next, &temp_clip, NULL); + selection->setReprList((GSList const *) copied); + g_slist_free (copied); + if (temp_clip) g_slist_free (temp_clip); + dt->setCurrentLayer(next); + sp_document_done(SP_DT_DOCUMENT (dt)); + } else { + dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No more layers below.")); + } + + g_slist_free ((GSList *) items); +} + +void sp_selection_apply_affine(Inkscape::Selection *selection, NR::Matrix const &affine, bool set_i2d) +{ + if (selection->isEmpty()) + return; + + for (GSList const *l = selection->itemList(); l != NULL; l = l->next) { + SPItem *item = SP_ITEM(l->data); + +#if 0 /* Re-enable this once persistent guides have a graphical indication. + At the time of writing, this is the only place to re-enable. */ + sp_item_update_cns(*item, selection->desktop()); +#endif + + // we're moving both a clone and its original + bool transform_clone_with_original = (SP_IS_USE(item) && selection->includes( sp_use_get_original (SP_USE(item)) )); + bool transform_textpath_with_path = (SP_IS_TEXT_TEXTPATH(item) && selection->includes( sp_textpath_get_path_item (SP_TEXTPATH(sp_object_first_child(SP_OBJECT(item)))) )); + bool transform_flowtext_with_frame = (SP_IS_FLOWTEXT(item) && selection->includes( SP_FLOWTEXT(item)->get_frame (NULL))); // only the first frame so far + bool transform_offset_with_source = (SP_IS_OFFSET(item) && SP_OFFSET (item)->sourceHref) && selection->includes( sp_offset_get_source (SP_OFFSET(item)) ); + + // "clones are unmoved when original is moved" preference + int compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED); + bool prefs_unmoved = (compensation == SP_CLONE_COMPENSATION_UNMOVED); + bool prefs_parallel = (compensation == SP_CLONE_COMPENSATION_PARALLEL); + + // If this is a clone and it's selected along with its original, do not move it; it will feel the + // transform of its original and respond to it itself. Without this, a clone is doubly + // transformed, very unintuitive. + // Same for textpath if we are also doing ANY transform to its path: do not touch textpath, + // letters cannot be squeezed or rotated anyway, they only refill the changed path. + // Same for linked offset if we are also moving its source: do not move it. + if (transform_textpath_with_path || transform_offset_with_source) { + // restore item->transform field from the repr, in case it was changed by seltrans + sp_object_read_attr (SP_OBJECT (item), "transform"); + + } else if (transform_flowtext_with_frame) { + // apply the inverse of the region's transform to the so that the flow remains + // the same (even though the output itself gets transformed) + for (SPObject *region = item->firstChild() ; region ; region = SP_OBJECT_NEXT(region)) { + if (!SP_IS_FLOWREGION(region) && !SP_IS_FLOWREGIONEXCLUDE(region)) + continue; + for (SPObject *use = region->firstChild() ; use ; use = SP_OBJECT_NEXT(use)) { + if (!SP_IS_USE(use)) continue; + sp_item_write_transform(SP_USE(use), SP_OBJECT_REPR(use), item->transform.inverse(), NULL); + } + } + } else if (transform_clone_with_original) { + // We are transforming a clone along with its original. The below matrix juggling is + // necessary to ensure that they transform as a whole, i.e. the clone's induced + // transform and its move compensation are both cancelled out. + + // restore item->transform field from the repr, in case it was changed by seltrans + sp_object_read_attr (SP_OBJECT (item), "transform"); + + // calculate the matrix we need to apply to the clone to cancel its induced transform from its original + NR::Matrix t = matrix_to_desktop (matrix_from_desktop (affine, item), item); + NR::Matrix t_inv = matrix_to_desktop (matrix_from_desktop (affine.inverse(), item), item); + NR::Matrix result = t_inv * item->transform * t; + + if ((prefs_parallel || prefs_unmoved) && affine.is_translation()) { + // we need to cancel out the move compensation, too + + // find out the clone move, same as in sp_use_move_compensate + NR::Matrix parent = sp_use_get_parent_transform (SP_USE(item)); + NR::Matrix clone_move = parent.inverse() * t * parent; + + if (prefs_parallel) { + NR::Matrix move = result * clone_move * t_inv; + sp_item_write_transform(item, SP_OBJECT_REPR(item), move, &move); + + } else if (prefs_unmoved) { + if (SP_IS_USE(sp_use_get_original(SP_USE(item)))) + clone_move = NR::identity(); + NR::Matrix move = result * clone_move; + sp_item_write_transform(item, SP_OBJECT_REPR(item), move, &move); + } + + } else { + // just apply the result + sp_item_write_transform(item, SP_OBJECT_REPR(item), result, &result); + } + + } else { + if (set_i2d) { + sp_item_set_i2d_affine(item, sp_item_i2d_affine(item) * affine); + } + sp_item_write_transform(item, SP_OBJECT_REPR(item), item->transform, NULL); + } + } +} + +void sp_selection_remove_transform() +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop == NULL) + return; + + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + + GSList const *l = (GSList *) selection->reprList(); + while (l != NULL) { + sp_repr_set_attr((Inkscape::XML::Node*)l->data, "transform", NULL); + l = l->next; + } + + sp_document_done(SP_DT_DOCUMENT(desktop)); +} + +void +sp_selection_scale_absolute(Inkscape::Selection *selection, + double const x0, double const x1, + double const y0, double const y1) +{ + if (selection->isEmpty()) + return; + + NR::Rect const bbox(selection->bounds()); + if (bbox.isEmpty()) { + return; + } + + NR::translate const p2o(-bbox.min()); + + NR::scale const newSize(x1 - x0, + y1 - y0); + NR::scale const scale( newSize / NR::scale(bbox.dimensions()) ); + NR::translate const o2n(x0, y0); + NR::Matrix const final( p2o * scale * o2n ); + + sp_selection_apply_affine(selection, final); +} + + +void sp_selection_scale_relative(Inkscape::Selection *selection, NR::Point const &align, NR::scale const &scale) +{ + if (selection->isEmpty()) + return; + + NR::Rect const bbox(selection->bounds()); + + if (bbox.isEmpty()) { + return; + } + + // FIXME: ARBITRARY LIMIT: don't try to scale above 1 Mpx, it won't display properly and will crash sooner or later anyway + if ( bbox.extent(NR::X) * scale[NR::X] > 1e6 || + bbox.extent(NR::Y) * scale[NR::Y] > 1e6 ) + { + return; + } + + NR::translate const n2d(-align); + NR::translate const d2n(align); + NR::Matrix const final( n2d * scale * d2n ); + sp_selection_apply_affine(selection, final); +} + +void +sp_selection_rotate_relative(Inkscape::Selection *selection, NR::Point const ¢er, gdouble const angle_degrees) +{ + NR::translate const d2n(center); + NR::translate const n2d(-center); + NR::rotate const rotate(rotate_degrees(angle_degrees)); + NR::Matrix const final( NR::Matrix(n2d) * rotate * d2n ); + sp_selection_apply_affine(selection, final); +} + +void +sp_selection_skew_relative(Inkscape::Selection *selection, NR::Point const &align, double dx, double dy) +{ + NR::translate const d2n(align); + NR::translate const n2d(-align); + NR::Matrix const skew(1, dy, + dx, 1, + 0, 0); + NR::Matrix const final( n2d * skew * d2n ); + sp_selection_apply_affine(selection, final); +} + +void sp_selection_move_relative(Inkscape::Selection *selection, NR::Point const &move) +{ + sp_selection_apply_affine(selection, NR::Matrix(NR::translate(move))); +} + +void sp_selection_move_relative(Inkscape::Selection *selection, double dx, double dy) +{ + sp_selection_apply_affine(selection, NR::Matrix(NR::translate(dx, dy))); +} + + +/** + * \brief sp_selection_rotate_90 + * + * This function rotates selected objects 90 degrees clockwise. + * + */ + +void sp_selection_rotate_90_cw() +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + + if (selection->isEmpty()) + return; + + GSList const *l = selection->itemList(); + NR::rotate const rot_neg_90(NR::Point(0, -1)); + for (GSList const *l2 = l ; l2 != NULL ; l2 = l2->next) { + SPItem *item = SP_ITEM(l2->data); + sp_item_rotate_rel(item, rot_neg_90); + } + + sp_document_done(SP_DT_DOCUMENT(desktop)); +} + + +/** + * \brief sp_selection_rotate_90_ccw + * + * This function rotates selected objects 90 degrees counter-clockwise. + * + */ + +void sp_selection_rotate_90_ccw() +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + + if (selection->isEmpty()) + return; + + GSList const *l = selection->itemList(); + NR::rotate const rot_neg_90(NR::Point(0, 1)); + for (GSList const *l2 = l ; l2 != NULL ; l2 = l2->next) { + SPItem *item = SP_ITEM(l2->data); + sp_item_rotate_rel(item, rot_neg_90); + } + + sp_document_done(SP_DT_DOCUMENT(desktop)); +} + +void +sp_selection_rotate(Inkscape::Selection *selection, gdouble const angle_degrees) +{ + if (selection->isEmpty()) + return; + + NR::Point const center(selection->bounds().midpoint()); + + sp_selection_rotate_relative(selection, center, angle_degrees); + + sp_document_maybe_done(SP_DT_DOCUMENT(selection->desktop()), + ( ( angle_degrees > 0 ) + ? "selector:rotate:ccw" + : "selector:rotate:cw" )); +} + +/** +\param angle the angle in "angular pixels", i.e. how many visible pixels must move the outermost point of the rotated object +*/ +void +sp_selection_rotate_screen(Inkscape::Selection *selection, gdouble angle) +{ + if (selection->isEmpty()) + return; + + NR::Rect const bbox(selection->bounds()); + NR::Point const center(bbox.midpoint()); + + gdouble const zoom = selection->desktop()->current_zoom(); + gdouble const zmove = angle / zoom; + gdouble const r = NR::L2(bbox.max() - center); + + gdouble const zangle = 180 * atan2(zmove, r) / M_PI; + + sp_selection_rotate_relative(selection, center, zangle); + + sp_document_maybe_done(SP_DT_DOCUMENT(selection->desktop()), + ( (angle > 0) + ? "selector:rotate:ccw" + : "selector:rotate:cw" )); +} + +void +sp_selection_scale(Inkscape::Selection *selection, gdouble grow) +{ + if (selection->isEmpty()) + return; + + NR::Rect const bbox(selection->bounds()); + NR::Point const center(bbox.midpoint()); + double const max_len = bbox.maxExtent(); + + // you can't scale "do nizhe pola" (below zero) + if ( max_len + grow <= 1e-3 ) { + return; + } + + double const times = 1.0 + grow / max_len; + sp_selection_scale_relative(selection, center, NR::scale(times, times)); + + sp_document_maybe_done(SP_DT_DOCUMENT(selection->desktop()), + ( (grow > 0) + ? "selector:scale:larger" + : "selector:scale:smaller" )); +} + +void +sp_selection_scale_screen(Inkscape::Selection *selection, gdouble grow_pixels) +{ + sp_selection_scale(selection, + grow_pixels / selection->desktop()->current_zoom()); +} + +void +sp_selection_scale_times(Inkscape::Selection *selection, gdouble times) +{ + if (selection->isEmpty()) + return; + + NR::Point const center(selection->bounds().midpoint()); + sp_selection_scale_relative(selection, center, NR::scale(times, times)); + sp_document_done(SP_DT_DOCUMENT(selection->desktop())); +} + +void +sp_selection_move(gdouble dx, gdouble dy) +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + if (selection->isEmpty()) { + return; + } + + sp_selection_move_relative(selection, dx, dy); + + if (dx == 0) { + sp_document_maybe_done(SP_DT_DOCUMENT(desktop), "selector:move:vertical"); + } else if (dy == 0) { + sp_document_maybe_done(SP_DT_DOCUMENT(desktop), "selector:move:horizontal"); + } else { + sp_document_done(SP_DT_DOCUMENT(desktop)); + } +} + +void +sp_selection_move_screen(gdouble dx, gdouble dy) +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + if (selection->isEmpty()) { + return; + } + + // same as sp_selection_move but divide deltas by zoom factor + gdouble const zoom = desktop->current_zoom(); + gdouble const zdx = dx / zoom; + gdouble const zdy = dy / zoom; + sp_selection_move_relative(selection, zdx, zdy); + + if (dx == 0) { + sp_document_maybe_done(SP_DT_DOCUMENT(desktop), "selector:move:vertical"); + } else if (dy == 0) { + sp_document_maybe_done(SP_DT_DOCUMENT(desktop), "selector:move:horizontal"); + } else { + sp_document_done(SP_DT_DOCUMENT(desktop)); + } +} + +namespace { + +template +SPItem *next_item(SPDesktop *desktop, GSList *path, SPObject *root, + bool only_in_viewport, bool inlayer, bool onlyvisible, bool onlysensitive); + +template +SPItem *next_item_from_list(SPDesktop *desktop, GSList const *items, SPObject *root, + bool only_in_viewport, bool inlayer, bool onlyvisible, bool onlysensitive); + +struct Forward { + typedef SPObject *Iterator; + + static Iterator children(SPObject *o) { return sp_object_first_child(o); } + static Iterator siblings_after(SPObject *o) { return SP_OBJECT_NEXT(o); } + static void dispose(Iterator i) {} + + static SPObject *object(Iterator i) { return i; } + static Iterator next(Iterator i) { return SP_OBJECT_NEXT(i); } +}; + +struct Reverse { + typedef GSList *Iterator; + + static Iterator children(SPObject *o) { + return make_list(o->firstChild(), NULL); + } + static Iterator siblings_after(SPObject *o) { + return make_list(SP_OBJECT_PARENT(o)->firstChild(), o); + } + static void dispose(Iterator i) { + g_slist_free(i); + } + + static SPObject *object(Iterator i) { + return reinterpret_cast(i->data); + } + static Iterator next(Iterator i) { return i->next; } + +private: + static GSList *make_list(SPObject *object, SPObject *limit) { + GSList *list=NULL; + while ( object != limit ) { + list = g_slist_prepend(list, object); + object = SP_OBJECT_NEXT(object); + } + return list; + } +}; + +} + +void +sp_selection_item_next(void) +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + g_return_if_fail(desktop != NULL); + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + + bool inlayer = prefs_get_int_attribute ("options.kbselection", "inlayer", 1); + bool onlyvisible = prefs_get_int_attribute ("options.kbselection", "onlyvisible", 1); + bool onlysensitive = prefs_get_int_attribute ("options.kbselection", "onlysensitive", 1); + + SPObject *root; + if (inlayer) { + root = desktop->currentLayer(); + } else { + root = desktop->currentRoot(); + } + + SPItem *item=next_item_from_list(desktop, selection->itemList(), root, SP_CYCLING == SP_CYCLE_VISIBLE, inlayer, onlyvisible, onlysensitive); + + if (item) { + selection->set(item); + if ( SP_CYCLING == SP_CYCLE_FOCUS ) { + scroll_to_show_item(desktop, item); + } + } +} + +void +sp_selection_item_prev(void) +{ + SPDocument *document = SP_ACTIVE_DOCUMENT; + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + g_return_if_fail(document != NULL); + g_return_if_fail(desktop != NULL); + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + + bool inlayer = prefs_get_int_attribute ("options.kbselection", "inlayer", 1); + bool onlyvisible = prefs_get_int_attribute ("options.kbselection", "onlyvisible", 1); + bool onlysensitive = prefs_get_int_attribute ("options.kbselection", "onlysensitive", 1); + + SPObject *root; + if (inlayer) { + root = desktop->currentLayer(); + } else { + root = desktop->currentRoot(); + } + + SPItem *item=next_item_from_list(desktop, selection->itemList(), root, SP_CYCLING == SP_CYCLE_VISIBLE, inlayer, onlyvisible, onlysensitive); + + if (item) { + selection->set(item); + if ( SP_CYCLING == SP_CYCLE_FOCUS ) { + scroll_to_show_item(desktop, item); + } + } +} + +namespace { + +template +SPItem *next_item_from_list(SPDesktop *desktop, GSList const *items, + SPObject *root, bool only_in_viewport, bool inlayer, bool onlyvisible, bool onlysensitive) +{ + SPObject *current=root; + while (items) { + SPItem *item=SP_ITEM(items->data); + if ( root->isAncestorOf(item) && + ( !only_in_viewport || desktop->isWithinViewport(item) ) ) + { + current = item; + break; + } + items = items->next; + } + + GSList *path=NULL; + while ( current != root ) { + path = g_slist_prepend(path, current); + current = SP_OBJECT_PARENT(current); + } + + SPItem *next; + // first, try from the current object + next = next_item(desktop, path, root, only_in_viewport, inlayer, onlyvisible, onlysensitive); + g_slist_free(path); + + if (!next) { // if we ran out of objects, start over at the root + next = next_item(desktop, NULL, root, only_in_viewport, inlayer, onlyvisible, onlysensitive); + } + + return next; +} + +template +SPItem *next_item(SPDesktop *desktop, GSList *path, SPObject *root, + bool only_in_viewport, bool inlayer, bool onlyvisible, bool onlysensitive) +{ + typename D::Iterator children; + typename D::Iterator iter; + + SPItem *found=NULL; + + if (path) { + SPObject *object=reinterpret_cast(path->data); + g_assert(SP_OBJECT_PARENT(object) == root); + if (desktop->isLayer(object)) { + found = next_item(desktop, path->next, object, only_in_viewport, inlayer, onlyvisible, onlysensitive); + } + iter = children = D::siblings_after(object); + } else { + iter = children = D::children(root); + } + + while ( iter && !found ) { + SPObject *object=D::object(iter); + if (desktop->isLayer(object)) { + if (!inlayer) { // recurse into sublayers + found = next_item(desktop, NULL, object, only_in_viewport, inlayer, onlyvisible, onlysensitive); + } + } else if ( SP_IS_ITEM(object) && + ( !only_in_viewport || desktop->isWithinViewport(SP_ITEM(object)) ) && + ( !onlyvisible || !desktop->itemIsHidden(SP_ITEM(object))) && + ( !onlysensitive || !SP_ITEM(object)->isLocked()) && + !desktop->isLayer(SP_ITEM(object)) ) + { + found = SP_ITEM(object); + } + iter = D::next(iter); + } + + D::dispose(children); + + return found; +} + +} + +/** + * If \a item is not entirely visible then adjust visible area to centre on the centre on of + * \a item. + */ +void scroll_to_show_item(SPDesktop *desktop, SPItem *item) +{ + NR::Rect dbox = desktop->get_display_area(); + NR::Rect sbox = sp_item_bbox_desktop(item); + + if (dbox.contains(sbox) == false) { + NR::Point const s_dt = sbox.midpoint(); + NR::Point const s_w = desktop->d2w(s_dt); + NR::Point const d_dt = dbox.midpoint(); + NR::Point const d_w = desktop->d2w(d_dt); + NR::Point const moved_w( d_w - s_w ); + gint const dx = (gint) moved_w[X]; + gint const dy = (gint) moved_w[Y]; + desktop->scroll_world(dx, dy); + } +} + + +void +sp_selection_clone() +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop == NULL) + return; + + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + + // check if something is selected + if (selection->isEmpty()) { + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select an object to clone.")); + return; + } + + // Check if more than one object is selected. + if (g_slist_length((GSList *) selection->itemList()) > 1) { + desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("If you want to clone several objects, group them and clone the group.")); + return; + } + + Inkscape::XML::Node *sel_repr = SP_OBJECT_REPR(selection->singleItem()); + Inkscape::XML::Node *parent = sp_repr_parent(sel_repr); + + Inkscape::XML::Node *clone = sp_repr_new("svg:use"); + clone->setAttribute("x", "0"); + clone->setAttribute("y", "0"); + clone->setAttribute("xlink:href", g_strdup_printf("#%s", sel_repr->attribute("id"))); + + // add the new clone to the top of the original's parent + parent->appendChild(clone); + + sp_document_done(SP_DT_DOCUMENT(desktop)); + + selection->set(clone); + Inkscape::GC::release(clone); +} + +void +sp_selection_unlink() +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (!desktop) + return; + + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + + if (selection->isEmpty()) { + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select a clone to unlink.")); + return; + } + + // Get a copy of current selection. + GSList *new_select = NULL; + bool unlinked = false; + for (GSList *items = g_slist_copy((GSList *) selection->itemList()); + items != NULL; + items = items->next) + { + SPItem *use = (SPItem *) items->data; + + if (!SP_IS_USE(use)) { + // keep the non-yse item in the new selection + new_select = g_slist_prepend(new_select, use); + continue; + } + + SPItem *unlink = sp_use_unlink(SP_USE(use)); + unlinked = true; + // Add ungrouped items to the new selection. + new_select = g_slist_prepend(new_select, unlink); + } + + if (new_select) { // set new selection + selection->clear(); + selection->setList(new_select); + g_slist_free(new_select); + } + if (!unlinked) { + desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("No clones to unlink in the selection.")); + } + + sp_document_done(SP_DT_DOCUMENT(desktop)); +} + +void +sp_select_clone_original() +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop == NULL) + return; + + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + + SPItem *item = selection->singleItem(); + + const gchar *error = _("Select a clone to go to its original. Select a linked offset to go to its source. Select a text on path to go to the path. Select a flowed text to go to its frame."); + + // Check if other than two objects are selected + if (g_slist_length((GSList *) selection->itemList()) != 1 || !item) { + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, error); + return; + } + + SPItem *original = NULL; + if (SP_IS_USE(item)) { + original = sp_use_get_original (SP_USE(item)); + } else if (SP_IS_OFFSET(item) && SP_OFFSET (item)->sourceHref) { + original = sp_offset_get_source (SP_OFFSET(item)); + } else if (SP_IS_TEXT_TEXTPATH(item)) { + original = sp_textpath_get_path_item (SP_TEXTPATH(sp_object_first_child(SP_OBJECT(item)))); + } else if (SP_IS_FLOWTEXT(item)) { + original = SP_FLOWTEXT(item)->get_frame (NULL); // first frame only + } else { // it's an object that we don't know what to do with + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, error); + return; + } + + if (!original) { + desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Cannot find the object to select (orphaned clone, offset, textpath, flowed text?)")); + return; + } + + for (SPObject *o = original; o && !SP_IS_ROOT(o); o = SP_OBJECT_PARENT (o)) { + if (SP_IS_DEFS (o)) { + desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The object you're trying to select is not visible (it is in <defs>)")); + return; + } + } + + if (original) { + selection->clear(); + selection->set(original); + if (SP_CYCLING == SP_CYCLE_FOCUS) { + scroll_to_show_item(desktop, original); + } + } +} + +void +sp_selection_tile(bool apply) +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop == NULL) + return; + + SPDocument *document = SP_DT_DOCUMENT(desktop); + + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + + // check if something is selected + if (selection->isEmpty()) { + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select object(s) to convert to pattern.")); + return; + } + + sp_document_ensure_up_to_date(document); + NR::Rect r = selection->bounds(); + if (r.isEmpty()) { + return; + } + + // calculate the transform to be applied to objects to move them to 0,0 + NR::Point move_p = NR::Point(0, sp_document_height(document)) - (r.min() + NR::Point (0, r.extent(NR::Y))); + move_p[NR::Y] = -move_p[NR::Y]; + NR::Matrix move = NR::Matrix (NR::translate (move_p)); + + GSList *items = g_slist_copy((GSList *) selection->itemList()); + + items = g_slist_sort (items, (GCompareFunc) sp_object_compare_position); + + // bottommost object, after sorting + SPObject *parent = SP_OBJECT_PARENT (items->data); + + // remember the position of the first item + gint pos = SP_OBJECT_REPR (items->data)->position(); + + // create a list of duplicates + GSList *repr_copies = NULL; + for (GSList *i = items; i != NULL; i = i->next) { + Inkscape::XML::Node *dup = (SP_OBJECT_REPR (i->data))->duplicate(); + repr_copies = g_slist_prepend (repr_copies, dup); + } + + NR::Rect bounds(desktop->dt2doc(r.min()), desktop->dt2doc(r.max())); + + if (apply) { + // delete objects so that their clones don't get alerted; this object will be restored shortly + for (GSList *i = items; i != NULL; i = i->next) { + SPObject *item = SP_OBJECT (i->data); + item->deleteObject (false); + } + } + + // Hack: Temporarily set clone compensation to unmoved, so that we can move clone-originals + // without disturbing clones. + // See ActorAlign::on_button_click() in src/ui/dialog/align-and-distribute.cpp + int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED); + prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED); + + const gchar *pat_id = pattern_tile (repr_copies, bounds, document, + NR::Matrix(NR::translate(desktop->dt2doc(NR::Point(r.min()[NR::X], r.max()[NR::Y])))), move); + + // restore compensation setting + prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation); + + if (apply) { + Inkscape::XML::Node *rect = sp_repr_new ("svg:rect"); + rect->setAttribute("style", g_strdup_printf("stroke:none;fill:url(#%s)", pat_id)); + sp_repr_set_svg_double(rect, "width", bounds.extent(NR::X)); + sp_repr_set_svg_double(rect, "height", bounds.extent(NR::Y)); + sp_repr_set_svg_double(rect, "x", bounds.min()[NR::X]); + sp_repr_set_svg_double(rect, "y", bounds.min()[NR::Y]); + + // restore parent and position + SP_OBJECT_REPR (parent)->appendChild(rect); + rect->setPosition(pos > 0 ? pos : 0); + SPItem *rectangle = (SPItem *) SP_DT_DOCUMENT (desktop)->getObjectByRepr(rect); + + Inkscape::GC::release(rect); + + selection->clear(); + selection->set(rectangle); + } + + g_slist_free (items); + + sp_document_done (document); +} + +void +sp_selection_untile() +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop == NULL) + return; + + SPDocument *document = SP_DT_DOCUMENT(desktop); + + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + + // check if something is selected + if (selection->isEmpty()) { + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select an object with pattern fill to extract objects from.")); + return; + } + + GSList *new_select = NULL; + + bool did = false; + + for (GSList *items = g_slist_copy((GSList *) selection->itemList()); + items != NULL; + items = items->next) { + + SPItem *item = (SPItem *) items->data; + + SPStyle *style = SP_OBJECT_STYLE (item); + + if (!style || style->fill.type != SP_PAINT_TYPE_PAINTSERVER) + continue; + + SPObject *server = SP_OBJECT_STYLE_FILL_SERVER(item); + + if (!SP_IS_PATTERN(server)) + continue; + + did = true; + + SPPattern *pattern = pattern_getroot (SP_PATTERN (server)); + + NR::Matrix pat_transform = pattern_patternTransform (SP_PATTERN (server)); + pat_transform *= item->transform; + + for (SPObject *child = sp_object_first_child(SP_OBJECT(pattern)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) { + Inkscape::XML::Node *copy = SP_OBJECT_REPR(child)->duplicate(); + SPItem *i = SP_ITEM (desktop->currentLayer()->appendChildRepr(copy)); + + // FIXME: relink clones to the new canvas objects + // use SPObject::setid when mental finishes it to steal ids of + + // this is needed to make sure the new item has curve (simply requestDisplayUpdate does not work) + sp_document_ensure_up_to_date (document); + + NR::Matrix transform( i->transform * pat_transform ); + sp_item_write_transform(i, SP_OBJECT_REPR(i), transform); + + new_select = g_slist_prepend(new_select, i); + } + + SPCSSAttr *css = sp_repr_css_attr_new (); + sp_repr_css_set_property (css, "fill", "none"); + sp_repr_css_change (SP_OBJECT_REPR (item), css, "style"); + } + + if (!did) { + desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("No pattern fills in the selection.")); + } else { + sp_document_done(SP_DT_DOCUMENT(desktop)); + selection->setList(new_select); + } +} + +void +sp_selection_create_bitmap_copy () +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop == NULL) + return; + + SPDocument *document = SP_DT_DOCUMENT(desktop); + + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + + // check if something is selected + if (selection->isEmpty()) { + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select object(s) to make a bitmap copy.")); + return; + } + + // Get the bounding box of the selection + NRRect bbox; + sp_document_ensure_up_to_date (document); + selection->bounds(&bbox); + if (NR_RECT_DFLS_TEST_EMPTY(&bbox)) { + return; // exceptional situation, so not bother with a translatable error message, just quit quietly + } + + // List of the items to show; all others will be hidden + GSList *items = g_slist_copy ((GSList *) selection->itemList()); + + // Sort items so that the topmost comes last + items = g_slist_sort(items, (GCompareFunc) sp_item_repr_compare_position); + + // Generate a random value from the current time (you may create bitmap from the same object(s) + // multiple times, and this is done so that they don't clash) + GTimeVal cu; + g_get_current_time (&cu); + guint current = (int) (cu.tv_sec * 1000000 + cu.tv_usec) % 1024; + + // Create the filename + gchar *filename = g_strdup_printf ("%s-%s-%u.png", document->name, SP_OBJECT_REPR(items->data)->attribute("id"), current); + // Imagemagick is known not to handle spaces in filenames, so we replace anything but letters, + // digits, and a few other chars, with "_" + filename = g_strcanon (filename, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.=+~$#@^&!?", '_'); + // Build the complete path by adding document->base if set + gchar *filepath = g_build_filename (document->base?document->base:"", filename, NULL); + + //g_print ("%s\n", filepath); + + // Remember parent and z-order of the topmost one + gint pos = SP_OBJECT_REPR(g_slist_last(items)->data)->position(); + SPObject *parent_object = SP_OBJECT_PARENT(g_slist_last(items)->data); + Inkscape::XML::Node *parent = SP_OBJECT_REPR(parent_object); + + // Calculate resolution + double res; + int const prefs_res = prefs_get_int_attribute ("options.createbitmap", "resolution", 0); + if (0 < prefs_res) { + // If it's given explicitly in prefs, take it + res = prefs_res; + } else { + // Otherwise look up minimum bitmap size (default 250 pixels) and calculate resolution from it + int const prefs_min = prefs_get_int_attribute ("options.createbitmap", "minsize", 250); + res = prefs_min / MIN ((bbox.x1 - bbox.x0), (bbox.y1 - bbox.y0)); + } + + // The width and height of the bitmap in pixels + unsigned width = (unsigned) floor ((bbox.x1 - bbox.x0) * res); + unsigned height =(unsigned) floor ((bbox.y1 - bbox.y0) * res); + + // Find out if we have to run a filter + const gchar *run = NULL; + const gchar *filter = prefs_get_string_attribute ("options.createbitmap", "filter"); + if (filter) { + // filter command is given; + // see if we have a parameter to pass to it + const gchar *param1 = prefs_get_string_attribute ("options.createbitmap", "filter_param1"); + if (param1) { + if (param1[strlen(param1) - 1] == '%') { + // if the param string ends with %, interpret it as a percentage of the image's max dimension + gchar p1[256]; + g_ascii_dtostr (p1, 256, ceil (g_ascii_strtod (param1, NULL) * MAX(width, height) / 100)); + // the first param is always the image filename, the second is param1 + run = g_strdup_printf ("%s \"%s\" %s", filter, filepath, p1); + } else { + // otherwise pass the param1 unchanged + run = g_strdup_printf ("%s \"%s\" %s", filter, filepath, param1); + } + } else { + // run without extra parameter + run = g_strdup_printf ("%s \"%s\"", filter, filepath); + } + } + + // Calculate the matrix that will be applied to the image so that it exactly overlaps the source objects + NR::Matrix eek = sp_item_i2d_affine (SP_ITEM(parent_object)); + NR::Matrix t = NR::scale (1/res, -1/res) * NR::translate (bbox.x0, bbox.y1) * eek.inverse(); + + // Do the export + sp_export_png_file(document, filepath, + bbox.x0, bbox.y0, bbox.x1, bbox.y1, + width, height, + (guint32) 0xffffff00, + NULL, NULL, + true, /*bool force_overwrite,*/ + items); + + g_slist_free (items); + + // Run filter, if any + if (run) { + g_print ("Running external filter: %s\n", run); + system (run); + } + + // Import the image back + GdkPixbuf *pb = gdk_pixbuf_new_from_file (filepath, NULL); + if (pb) { + // Create the repr for the image + Inkscape::XML::Node * repr = sp_repr_new ("svg:image"); + repr->setAttribute("xlink:href", filename); + repr->setAttribute("sodipodi:absref", filepath); + sp_repr_set_svg_double(repr, "width", gdk_pixbuf_get_width(pb)); + sp_repr_set_svg_double(repr, "height", gdk_pixbuf_get_height(pb)); + + // Write transform + gchar c[256]; + if (sp_svg_transform_write(c, 256, t)) { + repr->setAttribute("transform", c); + } + + // add the new repr to the parent + parent->appendChild(repr); + + // move to the saved position + repr->setPosition(pos > 0 ? pos + 1 : 1); + + // Set selection to the new image + selection->clear(); + selection->add(repr); + + // Clean up + Inkscape::GC::release(repr); + gdk_pixbuf_unref (pb); + + // Complete undoable transaction + sp_document_done (document); + } + + g_free (filename); + g_free (filepath); +} + + +/* + 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/selection-chemistry.h b/src/selection-chemistry.h new file mode 100644 index 000000000..9fbeaa9ec --- /dev/null +++ b/src/selection-chemistry.h @@ -0,0 +1,108 @@ +#ifndef __SP_SELECTION_CHEMISTRY_H__ +#define __SP_SELECTION_CHEMISTRY_H__ + +/* + * Miscellanous operations on selected items + * + * Authors: + * Lauris Kaplinski + * Frank Felfe + * bulia byak + * + * Copyright (C) 1999-2005 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "forward.h" +#include "libnr/nr-forward.h" + +namespace Inkscape { class Selection; } + +class SPCSSAttr; + +void sp_selection_delete(); +void sp_selection_duplicate(); +void sp_edit_clear_all(); + +void sp_edit_select_all(); +void sp_edit_select_all_in_all_layers (); +void sp_edit_invert (); +void sp_edit_invert_in_all_layers (); + +void sp_selection_clone(); +void sp_selection_unlink(); +void sp_select_clone_original (); + +void sp_selection_tile(bool apply = true); +void sp_selection_untile(); + +void sp_selection_group(); +void sp_selection_ungroup(); + +void sp_selection_raise(); +void sp_selection_raise_to_top(); +void sp_selection_lower(); +void sp_selection_lower_to_bottom(); + +SPCSSAttr *take_style_from_item (SPItem *item); + +void sp_selection_cut(); +void sp_selection_copy(); +void sp_selection_paste(bool in_place); +void sp_selection_paste_style(); + +void sp_selection_to_next_layer (); +void sp_selection_to_prev_layer (); + +void sp_selection_apply_affine(Inkscape::Selection *selection, NR::Matrix const &affine, bool set_i2d = true); +void sp_selection_remove_transform (void); +void sp_selection_scale_absolute (Inkscape::Selection *selection, double x0, double x1, double y0, double y1); +void sp_selection_scale_relative(Inkscape::Selection *selection, NR::Point const &align, NR::scale const &scale); +void sp_selection_rotate_relative (Inkscape::Selection *selection, NR::Point const ¢er, gdouble angle); +void sp_selection_skew_relative (Inkscape::Selection *selection, NR::Point const &align, double dx, double dy); +void sp_selection_move_relative (Inkscape::Selection *selection, NR::Point const &move); +void sp_selection_move_relative (Inkscape::Selection *selection, double dx, double dy); + +void sp_selection_rotate_90_cw (void); +void sp_selection_rotate_90_ccw (void); +void sp_selection_rotate (Inkscape::Selection *selection, gdouble angle); +void sp_selection_rotate_screen (Inkscape::Selection *selection, gdouble angle); + +void sp_selection_scale (Inkscape::Selection *selection, gdouble grow); +void sp_selection_scale_screen (Inkscape::Selection *selection, gdouble grow_pixels); +void sp_selection_scale_times (Inkscape::Selection *selection, gdouble times); + +void sp_selection_move (gdouble dx, gdouble dy); +void sp_selection_move_screen (gdouble dx, gdouble dy); + +void sp_selection_item_next (void); +void sp_selection_item_prev (void); + +void scroll_to_show_item(SPDesktop *desktop, SPItem *item); + +void sp_undo (SPDesktop *desktop, SPDocument *doc); +void sp_redo (SPDesktop *desktop, SPDocument *doc); + +void sp_selection_create_bitmap_copy (); + +/* selection cycling */ + +typedef enum +{ + SP_CYCLE_SIMPLE, + SP_CYCLE_VISIBLE, /* cycle only visible items */ + SP_CYCLE_FOCUS /* readjust visible area to view selected item */ +} SPCycleType; + +/* fixme: This should be moved into preference repr */ +#ifndef __SP_SELECTION_CHEMISTRY_C__ +extern SPCycleType SP_CYCLING; +#else +SPCycleType SP_CYCLING = SP_CYCLE_FOCUS; +#endif + +#endif + + diff --git a/src/selection-describer.cpp b/src/selection-describer.cpp new file mode 100644 index 000000000..9d484b293 --- /dev/null +++ b/src/selection-describer.cpp @@ -0,0 +1,121 @@ +/* + * Inkscape::SelectionDescriber - shows messages describing selection + * + * Authors: + * MenTaLguY + * bulia byak + * + * Copyright (C) 2004 MenTaLguY + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include "xml/quote.h" +#include "selection.h" +#include "selection-describer.h" +#include "desktop.h" +#include "sp-textpath.h" +#include "sp-offset.h" +#include "sp-flowtext.h" +#include "sp-use.h" + +namespace Inkscape { + +SelectionDescriber::SelectionDescriber(Inkscape::Selection *selection, MessageStack *stack) +: _context(stack) +{ + selection->connectChanged(sigc::mem_fun(*this, &SelectionDescriber::_updateMessageFromSelection)); + _updateMessageFromSelection(selection); +} + +void SelectionDescriber::_updateMessageFromSelection(Inkscape::Selection *selection) { + GSList const *items = selection->itemList(); + + char const *when_selected = _("Click selection to toggle scale/rotation handles"); + if (!items) { // no items + _context.set(Inkscape::NORMAL_MESSAGE, _("No objects selected. Click, Shift+click, or drag around objects to select.")); + } else { + SPItem *item = SP_ITEM(items->data); + SPObject *layer = selection->desktop()->layerForObject (SP_OBJECT (item)); + SPObject *root = selection->desktop()->currentRoot(); + gchar *layer_phrase; + if (layer == root) { + layer_phrase = g_strdup(""); // for simplicity + } else { + char const *name, *fmt; + if (layer && layer->label()) { + name = layer->label(); + fmt = _(" in layer %s"); + } else { + name = layer->defaultLabel(); + fmt = _(" in layer %s"); + } + char *quoted_name = xml_quote_strdup(name); + layer_phrase = g_strdup_printf(fmt, quoted_name); + g_free(quoted_name); + } + + if (!items->next) { // one item + char *item_desc = sp_item_description(item); + if (SP_IS_USE(item) || (SP_IS_OFFSET(item) && SP_OFFSET (item)->sourceHref)) { + _context.setF(Inkscape::NORMAL_MESSAGE, "%s%s. %s. %s.", + item_desc, layer_phrase, + _("Use Shift+D to look up original"), when_selected); + } else if (SP_IS_TEXT_TEXTPATH(item)) { + _context.setF(Inkscape::NORMAL_MESSAGE, "%s%s. %s. %s.", + item_desc, layer_phrase, + _("Use Shift+D to look up path"), when_selected); + } else if (SP_IS_FLOWTEXT(item) && !SP_FLOWTEXT(item)->has_internal_frame()) { + _context.setF(Inkscape::NORMAL_MESSAGE, "%s%s. %s. %s.", + item_desc, layer_phrase, + _("Use Shift+D to look up frame"), when_selected); + } else { + _context.setF(Inkscape::NORMAL_MESSAGE, "%s%s. %s.", + item_desc, layer_phrase, when_selected); + } + g_free(item_desc); + } else { // multiple items + int object_count = g_slist_length((GSList *)items); + const gchar *object_count_str = NULL; + object_count_str = g_strdup_printf ( + ngettext("%i object selected", + "%i objects selected", + object_count), + object_count); + + if (selection->numberOfLayers() == 1) { + _context.setF(Inkscape::NORMAL_MESSAGE, _("%s%s. %s."), + object_count_str, layer_phrase, when_selected); + } else { + _context.setF(Inkscape::NORMAL_MESSAGE, + ngettext("%s in %i layer. %s.", + "%s in %i layers. %s.", + selection->numberOfLayers()), + object_count_str, selection->numberOfLayers(), when_selected); + } + + if (object_count_str) + g_free ((gchar *) object_count_str); + } + + g_free(layer_phrase); + } +} + +} + +/* + 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 : diff --git a/src/selection-describer.h b/src/selection-describer.h new file mode 100644 index 000000000..cd892bd14 --- /dev/null +++ b/src/selection-describer.h @@ -0,0 +1,46 @@ +/* + * Inkscape::SelectionDescriber - shows messages describing selection + * + * Authors: + * MenTaLguY + * + * Copyright (C) 2004 MenTaLguY + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SEEN_INKSCAPE_SELECTION_DESCRIPTION_HANDLER_H +#define SEEN_INKSCAPE_SELECTION_DESCRIPTION_HANDLER_H + +#include +#include "message-context.h" + +namespace Inkscape { class Selection; } + +namespace Inkscape { + +class MessageStack; + +class SelectionDescriber : public sigc::trackable { +public: + SelectionDescriber(Inkscape::Selection *selection, MessageStack *stack); + +private: + void _updateMessageFromSelection(Inkscape::Selection *selection); + + MessageContext _context; +}; + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/selection.cpp b/src/selection.cpp new file mode 100644 index 000000000..4c99885eb --- /dev/null +++ b/src/selection.cpp @@ -0,0 +1,450 @@ +/** \file + * Per-desktop selection container + * + * Authors: + * Lauris Kaplinski + * MenTaLguY + * bulia byak + * + * Copyright (C) 2004-2005 MenTaLguY + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include +#endif +#include "macros.h" +#include "inkscape-private.h" +#include "desktop.h" +#include "desktop-handles.h" +#include "document.h" +#include "selection.h" +#include "xml/repr.h" + +#include "sp-shape.h" + + +#define SP_SELECTION_UPDATE_PRIORITY (G_PRIORITY_HIGH_IDLE + 1) + +namespace Inkscape { + +Selection::Selection(SPDesktop *desktop) : + _objs(NULL), + _reprs(NULL), + _items(NULL), + _desktop(desktop), + _flags(0), + _idle(0) +{ + clearOnceInaccessible(&_desktop); +} + +Selection::~Selection() { + _clear(); + _desktop = NULL; + if (_idle) { + g_source_remove(_idle); + _idle = 0; + } +} + +void +Selection::_release(SPObject *obj, Selection *selection) +{ + selection->remove(obj); +} + +/* Handler for selected objects "modified" signal */ + +void +Selection::_schedule_modified(SPObject *obj, guint flags, Selection *selection) +{ + if (!selection->_idle) { + /* Request handling to be run in _idle loop */ + selection->_idle = g_idle_add_full(SP_SELECTION_UPDATE_PRIORITY, GSourceFunc(&Selection::_emit_modified), selection, NULL); + } + + /* Collect all flags */ + selection->_flags |= flags; +} + +gboolean +Selection::_emit_modified(Selection *selection) +{ + /* force new handler to be created if requested before we return */ + selection->_idle = 0; + guint flags = selection->_flags; + selection->_flags = 0; + + selection->_emitModified(flags); + + /* drop this handler */ + return FALSE; +} + +void Selection::_emitModified(guint flags) { + inkscape_selection_modified(this, flags); + _modified_signal.emit(this, flags); +} + +void Selection::_emitChanged() { + inkscape_selection_changed(this); + _changed_signal.emit(this); +} + +void Selection::_invalidateCachedLists() { + g_slist_free(_items); + _items = NULL; + + g_slist_free(_reprs); + _reprs = NULL; +} + +void Selection::_clear() { + _invalidateCachedLists(); + while (_objs) { + SPObject *obj=reinterpret_cast(_objs->data); + sp_signal_disconnect_by_data(obj, this); + _objs = g_slist_remove(_objs, obj); + } +} + +bool Selection::includes(SPObject *obj) const { + if (obj == NULL) + return FALSE; + + g_return_val_if_fail(SP_IS_OBJECT(obj), FALSE); + + return ( g_slist_find(_objs, obj) != NULL ); +} + +void Selection::add(SPObject *obj) { + g_return_if_fail(obj != NULL); + g_return_if_fail(SP_IS_OBJECT(obj)); + + if (includes(obj)) { + return; + } + + _invalidateCachedLists(); + _add(obj); + _emitChanged(); +} + +void Selection::_add(SPObject *obj) { + // unselect any of the item's ancestors and descendants which may be selected + // (to prevent double-selection) + _removeObjectDescendants(obj); + _removeObjectAncestors(obj); + + _objs = g_slist_prepend(_objs, obj); + g_signal_connect(G_OBJECT(obj), "release", + G_CALLBACK(&Selection::_release), this); + g_signal_connect(G_OBJECT(obj), "modified", + G_CALLBACK(&Selection::_schedule_modified), this); + + /* + if (!SP_IS_SHAPE(obj)) { + printf("This is not a shape\n"); + } + */ +} + +void Selection::set(SPObject *object) { + _clear(); + add(object); +} + +void Selection::toggle(SPObject *obj) { + if (includes (obj)) { + remove (obj); + } else { + add(obj); + } +} + +void Selection::remove(SPObject *obj) { + g_return_if_fail(obj != NULL); + g_return_if_fail(SP_IS_OBJECT(obj)); + g_return_if_fail(includes(obj)); + + _invalidateCachedLists(); + _remove(obj); + _emitChanged(); +} + +void Selection::_remove(SPObject *obj) { + sp_signal_disconnect_by_data(obj, this); + _objs = g_slist_remove(_objs, obj); +} + +void Selection::setList(GSList const *list) { + _clear(); + + if ( list != NULL ) { + for ( GSList const *iter = list ; iter != NULL ; iter = iter->next ) { + _add(reinterpret_cast(iter->data)); + } + } + + _emitChanged(); +} + +void Selection::addList(GSList const *list) { + + if (list == NULL) + return; + + _invalidateCachedLists(); + + for ( GSList const *iter = list ; iter != NULL ; iter = iter->next ) { + SPObject *obj = reinterpret_cast(iter->data); + if (includes(obj)) { + continue; + } + _add (obj); + } + + _emitChanged(); +} + +void Selection::setReprList(GSList const *list) { + _clear(); + + for ( GSList const *iter = list ; iter != NULL ; iter = iter->next ) { + SPObject *obj=_objectForXMLNode(reinterpret_cast(iter->data)); + if (obj) { + _add(obj); + } + } + + _emitChanged(); +} + +void Selection::clear() { + _clear(); + _emitChanged(); +} + +GSList const *Selection::list() { + return _objs; +} + +GSList const *Selection::itemList() { + if (_items) { + return _items; + } + + for ( GSList const *iter=_objs ; iter != NULL ; iter = iter->next ) { + SPObject *obj=reinterpret_cast(iter->data); + if (SP_IS_ITEM(obj)) { + _items = g_slist_prepend(_items, SP_ITEM(obj)); + } + } + _items = g_slist_reverse(_items); + + return _items; +} + +GSList const *Selection::reprList() { + if (_reprs) { return _reprs; } + + for ( GSList const *iter=itemList() ; iter != NULL ; iter = iter->next ) { + SPObject *obj=reinterpret_cast(iter->data); + _reprs = g_slist_prepend(_reprs, SP_OBJECT_REPR(obj)); + } + _reprs = g_slist_reverse(_reprs); + + return _reprs; +} + +SPObject *Selection::single() { + if ( _objs != NULL && _objs->next == NULL ) { + return reinterpret_cast(_objs->data); + } else { + return NULL; + } +} + +SPItem *Selection::singleItem() { + GSList const *items=itemList(); + if ( items != NULL && items->next == NULL ) { + return reinterpret_cast(items->data); + } else { + return NULL; + } +} + +Inkscape::XML::Node *Selection::singleRepr() { + SPObject *obj=single(); + return obj ? SP_OBJECT_REPR(obj) : NULL; +} + +NRRect *Selection::bounds(NRRect *bbox) const +{ + g_return_val_if_fail (bbox != NULL, NULL); + NR::Rect const b = bounds(); + bbox->x0 = b.min()[NR::X]; + bbox->y0 = b.min()[NR::Y]; + bbox->x1 = b.max()[NR::X]; + bbox->x1 = b.max()[NR::Y]; + return bbox; +} + +NR::Rect Selection::bounds() const +{ + GSList const *items = const_cast(this)->itemList(); + if (!items) { + return NR::Rect(NR::Point(0, 0), NR::Point(0, 0)); + } + + GSList const *i = items; + NR::Rect bbox = sp_item_bbox_desktop(SP_ITEM(i->data)); + + while (i != NULL) { + bbox = NR::Rect::union_bounds(bbox, sp_item_bbox_desktop(SP_ITEM(i->data))); + i = i->next; + } + + return bbox; +} + +NRRect *Selection::boundsInDocument(NRRect *bbox) const { + g_return_val_if_fail (bbox != NULL, NULL); + + GSList const *items=const_cast(this)->itemList(); + if (!items) { + bbox->x0 = bbox->y0 = bbox->x1 = bbox->y1 = 0.0; + return bbox; + } + + bbox->x0 = bbox->y0 = 1e18; + bbox->x1 = bbox->y1 = -1e18; + + for ( GSList const *iter=items ; iter != NULL ; iter = iter->next ) { + SPItem *item=SP_ITEM(iter->data); + NR::Matrix const i2doc(sp_item_i2doc_affine(item)); + sp_item_invoke_bbox(item, bbox, i2doc, FALSE); + } + + return bbox; +} + +NR::Rect Selection::boundsInDocument() const { + NRRect r; + return NR::Rect(*boundsInDocument(&r)); +} + +/** + * Compute the list of points in the selection that are to be considered for snapping. + */ +std::vector Selection::getSnapPoints() const { + GSList const *items = const_cast(this)->itemList(); + std::vector p; + for (GSList const *iter = items; iter != NULL; iter = iter->next) { + sp_item_snappoints(SP_ITEM(iter->data), SnapPointsIter(p)); + } + + return p; +} + +std::vector Selection::getSnapPointsConvexHull() const { + GSList const *items = const_cast(this)->itemList(); + std::vector p; + for (GSList const *iter = items; iter != NULL; iter = iter->next) { + sp_item_snappoints(SP_ITEM(iter->data), SnapPointsIter(p)); + } + + std::vector::iterator i; + NR::ConvexHull cvh(*(p.begin())); + for (i = p.begin(); i != p.end(); i++) { + // these are the points we get back + cvh.add(*i); + } + + NR::Rect rHull = cvh.bounds(); + std::vector pHull(4); + pHull[0] = rHull.corner(0); + pHull[1] = rHull.corner(1); + pHull[2] = rHull.corner(2); + pHull[3] = rHull.corner(3); + + return pHull; +} + +std::vector Selection::getBBoxPoints() const { + GSList const *items = const_cast(this)->itemList(); + std::vector p; + for (GSList const *iter = items; iter != NULL; iter = iter->next) { + NR::Rect b = sp_item_bbox_desktop(SP_ITEM(iter->data)); + p.push_back(b.min()); + p.push_back(b.max()); + } + + return p; +} + +void Selection::_removeObjectDescendants(SPObject *obj) { + GSList *iter, *next; + for ( iter = _objs ; iter ; iter = next ) { + next = iter->next; + SPObject *sel_obj=reinterpret_cast(iter->data); + SPObject *parent=SP_OBJECT_PARENT(sel_obj); + while (parent) { + if ( parent == obj ) { + _remove(sel_obj); + break; + } + parent = SP_OBJECT_PARENT(parent); + } + } +} + +void Selection::_removeObjectAncestors(SPObject *obj) { + SPObject *parent=SP_OBJECT_PARENT(obj); + while (parent) { + if (includes(parent)) { + _remove(parent); + } + parent = SP_OBJECT_PARENT(parent); + } +} + +SPObject *Selection::_objectForXMLNode(Inkscape::XML::Node *repr) const { + g_return_val_if_fail(repr != NULL, NULL); + gchar const *id = repr->attribute("id"); + g_return_val_if_fail(id != NULL, NULL); + SPObject *object=SP_DT_DOCUMENT(_desktop)->getObjectById(id); + g_return_val_if_fail(object != NULL, NULL); + return object; +} + +guint Selection::numberOfLayers() { + GSList const *items = const_cast(this)->itemList(); + GSList *layers = NULL; + for (GSList const *iter = items; iter != NULL; iter = iter->next) { + SPObject *layer = desktop()->layerForObject(SP_OBJECT(iter->data)); + if (g_slist_find (layers, layer) == NULL) { + layers = g_slist_prepend (layers, layer); + } + } + guint ret = g_slist_length (layers); + g_slist_free (layers); + return ret; +} + +} + +/* + 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 : diff --git a/src/selection.h b/src/selection.h new file mode 100644 index 000000000..48d1112a8 --- /dev/null +++ b/src/selection.h @@ -0,0 +1,347 @@ +#ifndef SEEN_INKSCAPE_SELECTION_H +#define SEEN_INKSCAPE_SELECTION_H + +/** \file + * Inkscape::Selection: per-desktop selection container + * + * Authors: + * Lauris Kaplinski + * MenTaLguY + * bulia byak + * + * Copyright (C) 2004-2005 MenTaLguY + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include + +#include "libnr/nr-rect.h" +#include "libnr/nr-convex-hull.h" +#include "forward.h" +#include "gc-managed.h" +#include "gc-finalized.h" +#include "gc-anchored.h" +#include "util/list.h" + +class SPItem; + +namespace Inkscape { +namespace XML { +class Node; +} +} + +namespace Inkscape { + +/** + * @brief The set of selected SPObjects for a given desktop. + * + * This class represents the set of selected SPItems for a given + * SPDesktop. + * + * An SPObject and its parent cannot be simultaneously selected; + * selecting an SPObjects has the side-effect of unselecting any of + * its children which might have been selected. + * + * This is a per-desktop object that keeps the list of selected objects + * at the given desktop. Both SPItem and SPRepr lists can be retrieved + * from the selection. Many actions operate on the selection, so it is + * widely used throughout the code. + * It also implements its own asynchronous notification signals that + * UI elements can listen to. + */ +class Selection : public Inkscape::GC::Managed<>, + public Inkscape::GC::Finalized, + public Inkscape::GC::Anchored +{ +public: + /** + * Constructs an selection object, bound to a particular + * SPDesktop + * + * @param desktop the desktop in question + */ + Selection(SPDesktop *desktop); + ~Selection(); + + /** + * @brief Returns the desktop the seoection is bound to + * + * @return the desktop the selection is bound to + */ + SPDesktop *desktop() { return _desktop; } + + /** + * @brief Add an SPObject to the set of selected objects + * + * @param obj the SPObject to add + */ + void add(SPObject *obj); + + /** + * @brief Add an XML node's SPObject to the set of selected objects + * + * @param the xml node of the item to add + */ + void add(XML::Node *repr) { add(_objectForXMLNode(repr)); } + + /** + * @brief Set the selection to a single specific object + * + * @param obj the object to select + */ + void set(SPObject *obj); + + /** + * @brief Set the selection to an XML node's SPObject + * + * @param repr the xml node of the item to select + */ + void set(XML::Node *repr) { set(_objectForXMLNode(repr)); } + + /** + * @brief Removes an item from the set of selected objects + * + * It is ok to call this method for an unselected item. + * + * @param item the item to unselect + */ + void remove(SPObject *obj); + + /** + * @brief Removes an item if selected, adds otherwise + * + * @param item the item to unselect + */ + void toggle(SPObject *obj); + + /** + * @brief Removes an item from the set of selected objects + * + * It is ok to call this method for an unselected item. + * + * @param repr the xml node of the item to remove + */ + void remove(XML::Node *repr) { remove(_objectForXMLNode(repr)); } + + /** + * @brief Selects exactly the specified objects + * + * @param objs the objects to select + */ + void setList(GSList const *objs); + + /** + * @brief Adds the specified objects to selection, without deselecting first + * + * @param objs the objects to select + */ + void addList(GSList const *objs); + + /** + * @brief Clears the selection and selects the specified objects + * + * @param repr a list of xml nodes for the items to select + */ + void setReprList(GSList const *reprs); + + /** \brief Add items from an STL iterator range to the selection + * \param from the begin iterator + * \param to the end iterator + */ + template + void add(InputIterator from, InputIterator to) { + _invalidateCachedLists(); + while ( from != to ) { + _add(*from); + ++from; + } + _emitChanged(); + } + + /** + * @brief Unselects all selected objects. + */ + void clear(); + + /** + * @brief Returns true if no items are selected + */ + bool isEmpty() const { return _objs == NULL; } + + /** + * @brief Returns true if the given object is selected + */ + bool includes(SPObject *obj) const; + + /** + * @brief Returns true if the given item is selected + */ + bool includes(XML::Node *repr) const { + return includes(_objectForXMLNode(repr)); + } + + /** + * @brief Returns a single selected object + * + * @return NULL unless exactly one object is selected + */ + SPObject *single(); + + /** + * @brief Returns a single selected item + * + * @return NULL unless exactly one object is selected + */ + SPItem *singleItem(); + + /** + * @brief Returns a single selected object's xml node + * + * @return NULL unless exactly one object is selected + */ + XML::Node *singleRepr(); + + /** @brief Returns the list of selected objects */ + GSList const *list(); + /** @brief Returns the list of selected SPItems */ + GSList const *itemList(); + /** @brief Returns a list of the xml nodes of all selected objects */ + /// \todo only returns reprs of SPItems currently; need a separate + /// method for that + GSList const *reprList(); + + /** @brief Returns the number of layers in which there are selected objects */ + guint numberOfLayers(); + + /** @brief Returns the bounding rectangle of the selection */ + NRRect *bounds(NRRect *dest) const; + /** @brief Returns the bounding rectangle of the selection */ + NR::Rect bounds() const; + + /** + * @brief Returns the bounding rectangle of the selection + * + * \todo how is this different from bounds()? + */ + NRRect *boundsInDocument(NRRect *dest) const; + + /** + * @brief Returns the bounding rectangle of the selection + * + * \todo how is this different from bounds()? + */ + NR::Rect boundsInDocument() const; + + /** + * @brief Gets the selection's snap points. + * @return Selection's snap points + */ + std::vector getSnapPoints() const; + + /** + * @brief Gets the snap points of a selection that form a convex hull. + * @return Selection's convex hull points + */ + std::vector getSnapPointsConvexHull() const; + + /** + * @return A vector containing the top-left and bottom-right + * corners of each selected object's bounding box. + */ + std::vector getBBoxPoints() const; + + /** + * @brief Connects a slot to be notified of selection changes + * + * This method connects the given slot such that it will + * be called upon any change in the set of selected objects. + * + * @param slot the slot to connect + * + * @return the resulting connection + */ + sigc::connection connectChanged(sigc::slot const &slot) { + return _changed_signal.connect(slot); + } + + /** + * @brief Connects a slot to be notified of selected + * object modifications + * + * This method connects the given slot such that it will + * receive notifications whenever any selected item is + * modified. + * + * @param slot the slot to connect + * + * @return the resulting connection + * + */ + sigc::connection connectModified(sigc::slot const &slot) + { + return _modified_signal.connect(slot); + } + +private: + /** @brief no copy */ + Selection(Selection const &); + /** @brief no assign */ + void operator=(Selection const &); + + /** @brief Issues modification notification signals */ + static gboolean _emit_modified(Selection *selection); + /** @brief Schedules an item modification signal to be sent */ + static void _schedule_modified(SPObject *obj, guint flags, Selection *selection); + /** @brief Releases a selected object that is being removed */ + static void _release(SPObject *obj, Selection *selection); + + /** @brief Issues modified selection signal */ + void _emitModified(guint flags); + /** @brief Issues changed selection signal */ + void _emitChanged(); + + void _invalidateCachedLists(); + + /** @brief unselect all descendants of the given item */ + void _removeObjectDescendants(SPObject *obj); + /** @brief unselect all ancestors of the given item */ + void _removeObjectAncestors(SPObject *obj); + /** @brief clears the selection (without issuing a notification) */ + void _clear(); + /** @brief adds an object (without issuing a notification) */ + void _add(SPObject *obj); + /** @brief removes an object (without issuing a notification) */ + void _remove(SPObject *obj); + /** @brief returns the SPObject corresponding to an xml node (if any) */ + SPObject *_objectForXMLNode(XML::Node *repr) const; + + mutable GSList *_objs; + mutable GSList *_reprs; + mutable GSList *_items; + + SPDesktop *_desktop; + guint _flags; + guint _idle; + + sigc::signal _changed_signal; + sigc::signal _modified_signal; +}; + +} + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/seltrans-handles.cpp b/src/seltrans-handles.cpp new file mode 100644 index 000000000..95b680c5e --- /dev/null +++ b/src/seltrans-handles.cpp @@ -0,0 +1,42 @@ +#define SP_SELTRANS_HANDLES_C + +#include "seltrans-handles.h" + + +SPSelTransHandle const handles_scale[] = { +//anchor cursor control action request x y + {GTK_ANCHOR_SE, GDK_TOP_LEFT_CORNER, 0, sp_sel_trans_scale, sp_sel_trans_scale_request, 0, 1}, + {GTK_ANCHOR_S, GDK_TOP_SIDE, 3, sp_sel_trans_stretch, sp_sel_trans_stretch_request, 0.5, 1}, + {GTK_ANCHOR_SW, GDK_TOP_RIGHT_CORNER, 1, sp_sel_trans_scale, sp_sel_trans_scale_request, 1, 1}, + {GTK_ANCHOR_W, GDK_RIGHT_SIDE, 2, sp_sel_trans_stretch, sp_sel_trans_stretch_request, 1, 0.5}, + {GTK_ANCHOR_NW, GDK_BOTTOM_RIGHT_CORNER, 0, sp_sel_trans_scale, sp_sel_trans_scale_request, 1, 0}, + {GTK_ANCHOR_N, GDK_BOTTOM_SIDE, 3, sp_sel_trans_stretch, sp_sel_trans_stretch_request, 0.5, 0}, + {GTK_ANCHOR_NE, GDK_BOTTOM_LEFT_CORNER, 1, sp_sel_trans_scale, sp_sel_trans_scale_request, 0, 0}, + {GTK_ANCHOR_E, GDK_LEFT_SIDE, 2, sp_sel_trans_stretch, sp_sel_trans_stretch_request, 0, 0.5} +}; + +SPSelTransHandle const handles_rotate[] = { + {GTK_ANCHOR_SE, GDK_EXCHANGE, 4, sp_sel_trans_rotate, sp_sel_trans_rotate_request, 0, 1}, + {GTK_ANCHOR_S, GDK_SB_H_DOUBLE_ARROW, 5, sp_sel_trans_skew, sp_sel_trans_skew_request, 0.5, 1}, + {GTK_ANCHOR_SW, GDK_EXCHANGE, 6, sp_sel_trans_rotate, sp_sel_trans_rotate_request, 1, 1}, + {GTK_ANCHOR_W, GDK_SB_V_DOUBLE_ARROW, 7, sp_sel_trans_skew, sp_sel_trans_skew_request, 1, 0.5}, + {GTK_ANCHOR_NW, GDK_EXCHANGE, 8, sp_sel_trans_rotate, sp_sel_trans_rotate_request, 1, 0}, + {GTK_ANCHOR_N, GDK_SB_H_DOUBLE_ARROW, 9, sp_sel_trans_skew, sp_sel_trans_skew_request, 0.5, 0}, + {GTK_ANCHOR_NE, GDK_EXCHANGE, 10, sp_sel_trans_rotate, sp_sel_trans_rotate_request, 0, 0}, + {GTK_ANCHOR_E, GDK_SB_V_DOUBLE_ARROW, 11, sp_sel_trans_skew, sp_sel_trans_skew_request, 0, 0.5} +}; + +SPSelTransHandle const handle_center = + {GTK_ANCHOR_CENTER, GDK_CROSSHAIR, 12, sp_sel_trans_center, sp_sel_trans_center_request, 0.5, 0.5}; + + +/* + 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 : diff --git a/src/seltrans-handles.h b/src/seltrans-handles.h new file mode 100644 index 000000000..0afa552ea --- /dev/null +++ b/src/seltrans-handles.h @@ -0,0 +1,57 @@ +#ifndef __SP_SELTRANS_HANDLES_H__ +#define __SP_SELTRANS_HANDLES_H__ + +/* + * Seltrans knots + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "display/sodipodi-ctrl.h" + +namespace Inkscape +{ + class SelTrans; +} + +class SPSelTransHandle; + +// request handlers +gboolean sp_sel_trans_scale_request(Inkscape::SelTrans *seltrans, + SPSelTransHandle const &handle, NR::Point &p, guint state); +gboolean sp_sel_trans_stretch_request(Inkscape::SelTrans *seltrans, + SPSelTransHandle const &handle, NR::Point &p, guint state); +gboolean sp_sel_trans_skew_request(Inkscape::SelTrans *seltrans, + SPSelTransHandle const &handle, NR::Point &p, guint state); +gboolean sp_sel_trans_rotate_request(Inkscape::SelTrans *seltrans, + SPSelTransHandle const &handle, NR::Point &p, guint state); +gboolean sp_sel_trans_center_request(Inkscape::SelTrans *seltrans, + SPSelTransHandle const &handle, NR::Point &p, guint state); + +// action handlers +void sp_sel_trans_scale(Inkscape::SelTrans *seltrans, SPSelTransHandle const &handle, NR::Point &p, guint state); +void sp_sel_trans_stretch(Inkscape::SelTrans *seltrans, SPSelTransHandle const &handle, NR::Point &p, guint state); +void sp_sel_trans_skew(Inkscape::SelTrans *seltrans, SPSelTransHandle const &handle, NR::Point &p, guint state); +void sp_sel_trans_rotate(Inkscape::SelTrans *seltrans, SPSelTransHandle const &handle, NR::Point &p, guint state); +void sp_sel_trans_center(Inkscape::SelTrans *seltrans, SPSelTransHandle const &handle, NR::Point &p, guint state); + +struct SPSelTransHandle { + GtkAnchorType anchor; + GdkCursorType cursor; + guint control; + void (* action) (Inkscape::SelTrans *seltrans, SPSelTransHandle const &handle, NR::Point &p, guint state); + gboolean (* request) (Inkscape::SelTrans *seltrans, SPSelTransHandle const &handle, NR::Point &p, guint state); + gdouble x, y; +}; + +extern SPSelTransHandle const handles_scale[8]; +extern SPSelTransHandle const handles_rotate[8]; +extern SPSelTransHandle const handle_center; + +#endif + diff --git a/src/seltrans.cpp b/src/seltrans.cpp new file mode 100644 index 000000000..ca65b5d76 --- /dev/null +++ b/src/seltrans.cpp @@ -0,0 +1,1332 @@ +#define __SELTRANS_C__ + +/* + * Helper object for transforming selected items + * + * Author: + * Lauris Kaplinski + * bulia byak + * Carl Hetherington + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include "document.h" +#include "sp-namedview.h" +#include "desktop.h" +#include "desktop-handles.h" +#include "desktop-style.h" +#include "knot.h" +#include "snap.h" +#include "selection.h" +#include "select-context.h" +#include "sp-item.h" +#include "sp-item-transform.h" +#include "seltrans-handles.h" +#include "seltrans.h" +#include "selection-chemistry.h" +#include "sp-metrics.h" +#include +#include "display/sp-ctrlline.h" +#include "prefs-utils.h" +#include "xml/repr.h" + +#include "isnan.h" //temp fix. make sure included last + +static void sp_remove_handles(SPKnot *knot[], gint num); + +static void sp_sel_trans_handle_grab(SPKnot *knot, guint state, gpointer data); +static void sp_sel_trans_handle_ungrab(SPKnot *knot, guint state, gpointer data); +static void sp_sel_trans_handle_new_event(SPKnot *knot, NR::Point *position, guint32 state, gpointer data); +static gboolean sp_sel_trans_handle_request(SPKnot *knot, NR::Point *p, guint state, gboolean *data); + +extern GdkPixbuf *handles[]; + +static gboolean sp_seltrans_handle_event(SPKnot *knot, GdkEvent *event, gpointer) +{ + switch (event->type) { + case GDK_MOTION_NOTIFY: + break; + case GDK_KEY_PRESS: + if (get_group0_keyval (&event->key) == GDK_space) { + /* stamping mode: both mode(show content and outline) operation with knot */ + if (!SP_KNOT_IS_GRABBED(knot)) { + return FALSE; + } + SPDesktop *desktop = knot->desktop; + Inkscape::SelTrans *seltrans = SP_SELECT_CONTEXT(desktop->event_context)->_seltrans; + seltrans->stamp(); + return TRUE; + } + break; + default: + break; + } + + return FALSE; +} + +Inkscape::SelTrans::SelTrans(SPDesktop *desktop) : + _desktop(desktop), + _selcue(desktop), + _state(STATE_SCALE), + _show(SHOW_CONTENT), + _grabbed(false), + _show_handles(true), + _box(NR::Point(0,0), NR::Point(0,0)), + _chandle(NULL), + _stamp_cache(NULL), + _message_context(desktop->messageStack()) +{ + g_return_if_fail(desktop != NULL); + + for (int i = 0; i < 8; i++) { + _shandle[i] = NULL; + _rhandle[i] = NULL; + } + + _updateVolatileState(); + + _center = _box.midpoint(); + + _updateHandles(); + + _selection = SP_DT_SELECTION(desktop); + + _norm = sp_canvas_item_new(SP_DT_CONTROLS(desktop), + SP_TYPE_CTRL, + "anchor", GTK_ANCHOR_CENTER, + "mode", SP_CTRL_MODE_COLOR, + "shape", SP_CTRL_SHAPE_BITMAP, + "size", 13.0, + "filled", TRUE, + "fill_color", 0x00000000, + "stroked", TRUE, + "stroke_color", 0x000000a0, + "pixbuf", handles[12], + NULL); + + _grip = sp_canvas_item_new(SP_DT_CONTROLS(desktop), + SP_TYPE_CTRL, + "anchor", GTK_ANCHOR_CENTER, + "mode", SP_CTRL_MODE_XOR, + "shape", SP_CTRL_SHAPE_CROSS, + "size", 7.0, + "filled", TRUE, + "fill_color", 0xffffff7f, + "stroked", TRUE, + "stroke_color", 0xffffffff, + "pixbuf", handles[12], + NULL); + + sp_canvas_item_hide(_grip); + sp_canvas_item_hide(_norm); + + for (int i = 0; i < 4; i++) { + _l[i] = sp_canvas_item_new(SP_DT_CONTROLS(desktop), SP_TYPE_CTRLLINE, NULL); + sp_canvas_item_hide(_l[i]); + } + + _sel_changed_connection = _selection->connectChanged( + sigc::mem_fun(*this, &Inkscape::SelTrans::_selChanged) + ); + + _sel_modified_connection = _selection->connectModified( + sigc::mem_fun(*this, &Inkscape::SelTrans::_selModified) + ); +} + +Inkscape::SelTrans::~SelTrans() +{ + _sel_changed_connection.disconnect(); + _sel_modified_connection.disconnect(); + + for (unsigned int i = 0; i < 8; i++) { + if (_shandle[i]) { + g_object_unref(G_OBJECT(_shandle[i])); + _shandle[i] = NULL; + } + if (_rhandle[i]) { + g_object_unref(G_OBJECT(_rhandle[i])); + _rhandle[i] = NULL; + } + } + if (_chandle) { + g_object_unref(G_OBJECT(_chandle)); + _chandle = NULL; + } + + if (_norm) { + gtk_object_destroy(GTK_OBJECT(_norm)); + _norm = NULL; + } + if (_grip) { + gtk_object_destroy(GTK_OBJECT(_grip)); + _grip = NULL; + } + for (int i = 0; i < 4; i++) { + if (_l[i]) { + gtk_object_destroy(GTK_OBJECT(_l[i])); + _l[i] = NULL; + } + } + + for (unsigned i = 0; i < _items.size(); i++) { + sp_object_unref(SP_OBJECT(_items[i].first), NULL); + } + + _items.clear(); +} + +void Inkscape::SelTrans::resetState() +{ + _state = STATE_SCALE; +} + +void Inkscape::SelTrans::increaseState() +{ + if (_state == STATE_SCALE) { + _state = STATE_ROTATE; + } else { + _state = STATE_SCALE; + } + + _updateHandles(); +} + +void Inkscape::SelTrans::setCenter(NR::Point const &p) +{ + _center = p; + _updateHandles(); +} + +void Inkscape::SelTrans::grab(NR::Point const &p, gdouble x, gdouble y, bool show_handles) +{ + Inkscape::Selection *selection = SP_DT_SELECTION(_desktop); + + g_return_if_fail(!_grabbed); + + _grabbed = true; + _show_handles = show_handles; + _updateVolatileState(); + + _changed = false; + + if (_empty) { + return; + } + + for (GSList const *l = selection->itemList(); l; l = l->next) { + SPItem *it = (SPItem*)sp_object_ref(SP_OBJECT(l->data), NULL); + _items.push_back(std::pair(it, sp_item_i2d_affine(it))); + } + + _current.set_identity(); + + _point = p; + + _snap_points = selection->getSnapPoints(); + _bbox_points = selection->getBBoxPoints(); + + gchar const *scale_origin = prefs_get_string_attribute("tools.select", "scale_origin"); + bool const origin_on_bbox = (scale_origin == NULL || !strcmp(scale_origin, "bbox")); + NR::Rect op_box = _box; + if (origin_on_bbox == false && _snap_points.empty() == false) { + std::vector::iterator i = _snap_points.begin(); + op_box = NR::Rect(*i, *i); + i++; + while (i != _snap_points.end()) { + op_box.expandTo(*i); + i++; + } + } + + _opposite = ( op_box.min() + ( op_box.dimensions() * NR::scale(1-x, 1-y) ) ); + + if ((x != -1) && (y != -1)) { + sp_canvas_item_show(_norm); + sp_canvas_item_show(_grip); + } + + if (_show == SHOW_OUTLINE) { + for (int i = 0; i < 4; i++) + sp_canvas_item_show(_l[i]); + } + + + _updateHandles(); + g_return_if_fail(_stamp_cache == NULL); +} + +void Inkscape::SelTrans::transform(NR::Matrix const &rel_affine, NR::Point const &norm) +{ + g_return_if_fail(_grabbed); + g_return_if_fail(!_empty); + + NR::Matrix const affine( NR::translate(-norm) * rel_affine * NR::translate(norm) ); + + if (_show == SHOW_CONTENT) { + // update the content + for (unsigned i = 0; i < _items.size(); i++) { + SPItem &item = *_items[i].first; + NR::Matrix const &prev_transform = _items[i].second; + sp_item_set_i2d_affine(&item, prev_transform * affine); + } + } else { + NR::Point p[4]; + /* update the outline */ + for (unsigned i = 0 ; i < 4 ; i++) { + p[i] = _box.corner(i) * affine; + } + for (unsigned i = 0 ; i < 4 ; i++) { + sp_ctrlline_set_coords(SP_CTRLLINE(_l[i]), p[i], p[(i+1)%4]); + } + } + + _current = affine; + _changed = true; + _updateHandles(); +} + +void Inkscape::SelTrans::_centreTrans(Inkscape::XML::Node *current) const +{ + for ( Inkscape::XML::Node *child = sp_repr_children(current) ; child ; child = sp_repr_next(child) ) { + _centreTrans(child); + } + double const cx = sp_repr_get_double_attribute(current, "inkscape:c_rx", 9999999); + double const cy = sp_repr_get_double_attribute(current, "inkscape:c_ry", 9999999); + if (cx != 9999999) { + NR::Point object_centre = NR::Point(cx, cy) * _current; + sp_repr_set_svg_double(current, "inkscape:c_rx", object_centre[NR::X]); + sp_repr_set_svg_double(current, "inkscape:c_ry", object_centre[NR::Y]); + } + } + + + + +void Inkscape::SelTrans::ungrab() +{ + g_return_if_fail(_grabbed); + + Inkscape::Selection *selection = SP_DT_SELECTION(_desktop); + + bool updh = true; + if (!_empty && _changed) { + sp_selection_apply_affine(selection, _current, (_show == SHOW_OUTLINE)? true : false); + _center *= _current; + sp_document_done(SP_DT_DOCUMENT(_desktop)); + updh = false; + } + + for (unsigned i = 0; i < _items.size(); i++) + { + Inkscape::XML::Node *current = SP_OBJECT_REPR(_items[i].first); + if (current != NULL) { + _centreTrans(current); + } + + sp_object_unref(SP_OBJECT(_items[i].first), NULL); + } + _items.clear(); + + _grabbed = false; + _show_handles = true; + + sp_canvas_item_hide(_norm); + sp_canvas_item_hide(_grip); + + if (_show == SHOW_OUTLINE) { + for (int i = 0; i < 4; i++) + sp_canvas_item_hide(_l[i]); + } + + _updateVolatileState(); + if (updh) { + _updateHandles(); + } + if (_stamp_cache) { + g_slist_free(_stamp_cache); + _stamp_cache = NULL; + } + + _message_context.clear(); +} + +/* fixme: This is really bad, as we compare positions for each stamp (Lauris) */ +/* fixme: IMHO the best way to keep sort cache would be to implement timestamping at last */ + +void Inkscape::SelTrans::stamp() +{ + Inkscape::Selection *selection = SP_DT_SELECTION(_desktop); + + /* stamping mode */ + if (!_empty) { + GSList *l; + if (_stamp_cache) { + l = _stamp_cache; + } else { + /* Build cache */ + l = g_slist_copy((GSList *) selection->itemList()); + l = g_slist_sort(l, (GCompareFunc) sp_object_compare_position); + _stamp_cache = l; + } + + while (l) { + SPItem *original_item = SP_ITEM(l->data); + Inkscape::XML::Node *original_repr = SP_OBJECT_REPR(original_item); + + // remember the position of the item + gint pos = original_repr->position(); + // remember parent + Inkscape::XML::Node *parent = sp_repr_parent(original_repr); + + Inkscape::XML::Node *copy_repr = original_repr->duplicate(); + + // add the new repr to the parent + parent->appendChild(copy_repr); + // move to the saved position + copy_repr->setPosition(pos > 0 ? pos : 0); + + SPItem *copy_item = (SPItem *) SP_DT_DOCUMENT(_desktop)->getObjectByRepr(copy_repr); + + NR::Matrix const *new_affine; + if (_show == SHOW_OUTLINE) { + NR::Matrix const i2d(sp_item_i2d_affine(original_item)); + NR::Matrix const i2dnew( i2d * _current ); + sp_item_set_i2d_affine(copy_item, i2dnew); + new_affine = ©_item->transform; + } else { + new_affine = &original_item->transform; + } + + sp_item_write_transform(copy_item, copy_repr, *new_affine); + + Inkscape::GC::release(copy_repr); + l = l->next; + } + sp_document_done(SP_DT_DOCUMENT(_desktop)); + } +} + +void Inkscape::SelTrans::_updateHandles() +{ + if ( !_show_handles || _empty ) + { + sp_remove_handles(_shandle, 8); + sp_remove_handles(_rhandle, 8); + sp_remove_handles(&_chandle, 1); + return; + } + + // center handle + if ( _chandle == NULL ) { + _chandle = sp_knot_new(_desktop); + g_object_set(G_OBJECT(_chandle), + "anchor", handle_center.anchor, + "shape", SP_CTRL_SHAPE_BITMAP, + "size", 13, + "mode", SP_CTRL_MODE_XOR, + "fill", 0x00000000, + "fill_mouseover", 0x00000000, + "stroke", 0x000000ff, + "stroke_mouseover", 0xff0000b0, + "pixbuf", handles[handle_center.control], + "tip", _("Center of rotation and skewing: drag to reposition; scaling with Shift also uses this center"), + NULL); + g_signal_connect(G_OBJECT(_chandle), "request", + G_CALLBACK(sp_sel_trans_handle_request), (gpointer) &handle_center); + g_signal_connect(G_OBJECT(_chandle), "moved", + G_CALLBACK(sp_sel_trans_handle_new_event), (gpointer) &handle_center); + g_signal_connect(G_OBJECT(_chandle), "grabbed", + G_CALLBACK(sp_sel_trans_handle_grab), (gpointer) &handle_center); + g_signal_connect(G_OBJECT(_chandle), "ungrabbed", + G_CALLBACK(sp_sel_trans_handle_ungrab), (gpointer) &handle_center); + } + + sp_remove_handles(&_chandle, 1); + if ( _state == STATE_SCALE ) { + sp_remove_handles(_rhandle, 8); + _showHandles(_shandle, handles_scale, 8, + _("Squeeze or stretch selection; with Ctrl to scale uniformly; with Shift to scale around rotation center"), + _("Scale selection; with Ctrl to scale uniformly; with Shift to scale around rotation center")); + } else { + sp_remove_handles(_shandle, 8); + _showHandles(_rhandle, handles_rotate, 8, + _("Skew selection; with Ctrl to snap angle; with Shift to skew around the opposite side"), + _("Rotate selection; with Ctrl to snap angle; with Shift to rotate around the opposite corner")); + } + if ( _state == STATE_SCALE ) { + sp_knot_hide(_chandle); + } else { + Inkscape::Selection *selection = _desktop->selection; + Inkscape::XML::Node *current = selection->singleRepr(); + if (current != NULL && sp_repr_get_double_attribute(current, "inkscape:c_rx", 99999999) != 99999999) { + double cx = sp_repr_get_double_attribute(current, "inkscape:c_rx", _center[NR::X]); + double cy = sp_repr_get_double_attribute(current, "inkscape:c_ry", _center[NR::Y]); + _center = NR::Point(cx, cy); + } + sp_knot_show(_chandle); + sp_knot_moveto(_chandle, &_center); + } +} + +void Inkscape::SelTrans::_updateVolatileState() +{ + Inkscape::Selection *selection = SP_DT_SELECTION(_desktop); + _empty = selection->isEmpty(); + + if (_empty) { + return; + } + + _box = selection->bounds(); + if (_box.isEmpty()) { + _empty = true; + return; + } + + _strokewidth = stroke_average_width (selection->itemList()); + + _current.set_identity(); +} + +static void sp_remove_handles(SPKnot *knot[], gint num) +{ + for (int i = 0; i < num; i++) { + if (knot[i] != NULL) { + sp_knot_hide(knot[i]); + } + } +} + +void Inkscape::SelTrans::_showHandles(SPKnot *knot[], SPSelTransHandle const handle[], gint num, + gchar const *even_tip, gchar const *odd_tip) +{ + g_return_if_fail( !_empty ); + + for (int i = 0; i < num; i++) { + if (knot[i] == NULL) { + knot[i] = sp_knot_new(_desktop); + g_object_set(G_OBJECT(knot[i]), + "anchor", handle[i].anchor, + "shape", SP_CTRL_SHAPE_BITMAP, + "size", 13, + "mode", SP_KNOT_MODE_XOR, + "fill", 0x000000ff, // inversion + "fill_mouseover", 0x00ff6600, // green + "stroke", 0x000000ff, // inversion + "stroke_mouseover", 0x000000ff, // inversion + "pixbuf", handles[handle[i].control], + "tip", i % 2 ? even_tip : odd_tip, + NULL); + + g_signal_connect(G_OBJECT(knot[i]), "request", + G_CALLBACK(sp_sel_trans_handle_request), (gpointer) &handle[i]); + g_signal_connect(G_OBJECT(knot[i]), "moved", + G_CALLBACK(sp_sel_trans_handle_new_event), (gpointer) &handle[i]); + g_signal_connect(G_OBJECT(knot[i]), "grabbed", + G_CALLBACK(sp_sel_trans_handle_grab), (gpointer) &handle[i]); + g_signal_connect(G_OBJECT(knot[i]), "ungrabbed", + G_CALLBACK(sp_sel_trans_handle_ungrab), (gpointer) &handle[i]); + g_signal_connect(G_OBJECT(knot[i]), "event", G_CALLBACK(sp_seltrans_handle_event), (gpointer) &handle[i]); + } + sp_knot_show(knot[i]); + + NR::Point const handle_pt(handle[i].x, handle[i].y); + NR::Point p( _box.min() + + ( _box.dimensions() + * NR::scale(handle_pt) ) ); + + sp_knot_moveto(knot[i], &p); + } +} + +static void sp_sel_trans_handle_grab(SPKnot *knot, guint state, gpointer data) +{ + SP_SELECT_CONTEXT(knot->desktop->event_context)->_seltrans->handleGrab( + knot, state, *(SPSelTransHandle const *) data + ); +} + +static void sp_sel_trans_handle_ungrab(SPKnot *knot, guint state, gpointer data) +{ + SP_SELECT_CONTEXT(knot->desktop->event_context)->_seltrans->ungrab(); +} + +static void sp_sel_trans_handle_new_event(SPKnot *knot, NR::Point *position, guint state, gpointer data) +{ + SP_SELECT_CONTEXT(knot->desktop->event_context)->_seltrans->handleNewEvent( + knot, position, state, *(SPSelTransHandle const *) data + ); +} + +static gboolean sp_sel_trans_handle_request(SPKnot *knot, NR::Point *position, guint state, gboolean *data) +{ + return SP_SELECT_CONTEXT(knot->desktop->event_context)->_seltrans->handleRequest( + knot, position, state, *(SPSelTransHandle const *) data + ); +} + +void Inkscape::SelTrans::handleGrab(SPKnot *knot, guint state, SPSelTransHandle const &handle) +{ + switch (handle.anchor) { + case GTK_ANCHOR_CENTER: + g_object_set(G_OBJECT(_grip), + "shape", SP_CTRL_SHAPE_BITMAP, + "size", 13.0, + NULL); + sp_canvas_item_show(_grip); + break; + default: + g_object_set(G_OBJECT(_grip), + "shape", SP_CTRL_SHAPE_CROSS, + "size", 7.0, + NULL); + sp_canvas_item_show(_norm); + sp_canvas_item_show(_grip); + + break; + } + + grab(sp_knot_position(knot), handle.x, handle.y, FALSE); +} + + +void Inkscape::SelTrans::handleNewEvent(SPKnot *knot, NR::Point *position, guint state, SPSelTransHandle const &handle) +{ + if (!SP_KNOT_IS_GRABBED(knot)) { + return; + } + + // in case items have been unhooked from the document, don't + // try to continue processing events for them. + for (unsigned int i = 0; i < _items.size(); i++) { + if (!SP_OBJECT_DOCUMENT(SP_OBJECT(_items[i].first)) ) { + return; + } + } + + handle.action(this, handle, *position, state); +} + + +gboolean Inkscape::SelTrans::handleRequest(SPKnot *knot, NR::Point *position, guint state, SPSelTransHandle const &handle) +{ + if (!SP_KNOT_IS_GRABBED(knot)) { + return TRUE; + } + + knot->desktop->set_coordinate_status(*position); + knot->desktop->setPosition(*position); + + + if (state & GDK_MOD1_MASK) { + *position = _point + ( *position - _point ) / 10; + } + + if (!(state & GDK_SHIFT_MASK) == !(_state == STATE_ROTATE)) { + _origin = _opposite; + } else { + _origin = _center; + } + if (handle.request(this, handle, *position, state)) { + sp_knot_set_position(knot, position, state); + SP_CTRL(_grip)->moveto(*position); + SP_CTRL(_norm)->moveto(_origin); + } + + return TRUE; +} + + +void Inkscape::SelTrans::_selChanged(Inkscape::Selection *selection) +{ + if (!_grabbed) { + _updateVolatileState(); + _center = _box.midpoint(); + _updateHandles(); + } +} + +void Inkscape::SelTrans::_selModified(Inkscape::Selection *selection, guint flags) +{ + if (!_grabbed) { + _updateVolatileState(); + + if ( + (flags != (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG)) && + (flags != SP_OBJECT_PARENT_MODIFIED_FLAG) && + (flags != SP_OBJECT_CHILD_MODIFIED_FLAG) && + !_changed) { + // Only reset center if object itself is modified (not style, parent or child), + // and this is not a local change by seltrans + // (still annoyingly recenters on keyboard transforms, fixme) + _center = _box.midpoint(); + } + + // reset internal flag + _changed = false; + + _updateHandles(); + } +} + +/* + * handlers for handle move-request + */ + +/** Returns -1 or 1 according to the sign of x. Returns 1 for 0 and NaN. */ +static double sign(double const x) +{ + return ( x < 0 + ? -1 + : 1 ); +} + +gboolean sp_sel_trans_scale_request(Inkscape::SelTrans *seltrans, + SPSelTransHandle const &, NR::Point &pt, guint state) +{ + return seltrans->scaleRequest(pt, state); +} + +gboolean sp_sel_trans_stretch_request(Inkscape::SelTrans *seltrans, + SPSelTransHandle const &handle, NR::Point &pt, guint state) +{ + return seltrans->stretchRequest(handle, pt, state); +} + +gboolean sp_sel_trans_skew_request(Inkscape::SelTrans *seltrans, + SPSelTransHandle const &handle, NR::Point &pt, guint state) +{ + return seltrans->skewRequest(handle, pt, state); +} + +gboolean sp_sel_trans_rotate_request(Inkscape::SelTrans *seltrans, + SPSelTransHandle const &, NR::Point &pt, guint state) +{ + return seltrans->rotateRequest(pt, state); +} + +gboolean sp_sel_trans_center_request(Inkscape::SelTrans *seltrans, + SPSelTransHandle const &, NR::Point &pt, guint state) +{ + return seltrans->centerRequest(pt, state); +} + +gboolean Inkscape::SelTrans::scaleRequest(NR::Point &pt, guint state) +{ + using NR::X; + using NR::Y; + + NR::Point d = _point - _origin; + NR::scale s(0, 0); + + /* Work out the new scale factors `s' */ + for ( unsigned int i = 0 ; i < 2 ; i++ ) { + if ( fabs(d[i]) > 0.001 ) { + s[i] = ( pt[i] - _origin[i] ) / d[i]; + if ( fabs(s[i]) < 1e-9 ) { + s[i] = 1e-9; + } + } + } + + /* Get a STL list of the selected items. + ** FIXME: this should probably be done by Inkscape::Selection. + */ + std::list it; + for (GSList const *i = _selection->itemList(); i != NULL; i = i->next) { + it.push_back(reinterpret_cast(i->data)); + } + + if ((state & GDK_CONTROL_MASK) || _desktop->isToolboxButtonActive ("lock")) { + /* Scale is locked to a 1:1 aspect ratio, so that s[X] must be made to equal s[Y] */ + + NR::Dim2 locked_dim; + + /* Lock aspect ratio, using the smaller of the x and y factors */ + if (fabs(s[NR::X]) > fabs(s[NR::Y])) { + s[NR::X] = fabs(s[NR::Y]) * sign(s[NR::X]); + locked_dim = NR::X; + } else { + s[NR::Y] = fabs(s[NR::X]) * sign(s[NR::Y]); + locked_dim = NR::Y; + } + + /* Snap the scale factor */ + std::pair bb = namedview_vector_snap_list(_desktop->namedview, + Snapper::BBOX_POINT, _bbox_points, + _origin, s, it); + std::pair sn = namedview_vector_snap_list(_desktop->namedview, + Snapper::SNAP_POINT, _snap_points, + _origin, s, it); + + double bd = bb.second ? fabs(bb.first - s[locked_dim]) : NR_HUGE; + double sd = sn.second ? fabs(sn.first - s[locked_dim]) : NR_HUGE; + double r = (bd < sd) ? bb.first : sn.first; + + for ( unsigned int i = 0 ; i < 2 ; i++ ) { + s[i] = r * sign(s[i]); + } + + } else { + /* Scale aspect ratio is unlocked */ + for ( unsigned int i = 0 ; i < 2 ; i++ ) { + std::pair bb = namedview_dim_snap_list_scale(_desktop->namedview, + Snapper::BBOX_POINT, _bbox_points, + _origin, s[i], NR::Dim2(i), it); + std::pair sn = namedview_dim_snap_list_scale(_desktop->namedview, + Snapper::SNAP_POINT, _snap_points, + _origin, s[i], NR::Dim2(i), it); + + /* Pick the snap that puts us closest to the original scale */ + NR::Coord bd = bb.second ? fabs(bb.first - s[i]) : NR_HUGE; + NR::Coord sd = sn.second ? fabs(sn.first - s[i]) : NR_HUGE; + s[i] = (bd < sd) ? bb.first : sn.first; + } + } + + /* Update the knot position */ + pt = ( _point - _origin ) * s + _origin; + + /* Status text */ + _message_context.setF(Inkscape::NORMAL_MESSAGE, + _("Scale: %0.2f%% x %0.2f%%; with Ctrl to lock ratio"), + 100 * s[NR::X], 100 * s[NR::Y]); + + return TRUE; +} + +gboolean Inkscape::SelTrans::stretchRequest(SPSelTransHandle const &handle, NR::Point &pt, guint state) +{ + using NR::X; + using NR::Y; + + NR::Dim2 axis, perp; + + switch (handle.cursor) { + case GDK_TOP_SIDE: + case GDK_BOTTOM_SIDE: + axis = NR::Y; + perp = NR::X; + break; + case GDK_LEFT_SIDE: + case GDK_RIGHT_SIDE: + axis = NR::X; + perp = NR::Y; + break; + default: + g_assert_not_reached(); + return TRUE; + }; + + if ( fabs( _point[axis] - _origin[axis] ) < 1e-15 ) { + return FALSE; + } + + NR::scale s(1, 1); + s[axis] = ( ( pt[axis] - _origin[axis] ) + / ( _point[axis] - _origin[axis] ) ); + if ( fabs(s[axis]) < 1e-15 ) { + s[axis] = 1e-15; + } + + /* Get a STL list of the selected items. + ** FIXME: this should probably be done by Inkscape::Selection. + */ + std::list it; + for (GSList const *i = _selection->itemList(); i != NULL; i = i->next) { + it.push_back(reinterpret_cast(i->data)); + } + + if ( state & GDK_CONTROL_MASK ) { + s[perp] = fabs(s[axis]); + + std::pair sn = namedview_vector_snap_list(_desktop->namedview, + Snapper::BBOX_POINT, + _bbox_points, _origin, s, it); + std::pair bb = namedview_vector_snap_list(_desktop->namedview, + Snapper::SNAP_POINT, + _snap_points, _origin, s, it); + + double bd = bb.second ? fabs(bb.first - s[axis]) : NR_HUGE; + double sd = sn.second ? fabs(sn.first - s[axis]) : NR_HUGE; + double ratio = (bd < sd) ? bb.first : sn.first; + + s[axis] = fabs(ratio) * sign(s[axis]); + s[perp] = fabs(s[axis]); + } else { + std::pair bb = namedview_dim_snap_list_scale(_desktop->namedview, Snapper::BBOX_POINT, + _bbox_points, _origin, + s[axis], axis, it); + std::pair sn = namedview_dim_snap_list_scale(_desktop->namedview, Snapper::SNAP_POINT, + _snap_points, _origin, + s[axis], axis, it); + + /* Pick the snap that puts us closest to the original scale */ + NR::Coord bd = bb.second ? fabs(bb.first - s[axis]) : NR_HUGE; + NR::Coord sd = sn.second ? fabs(sn.first - s[axis]) : NR_HUGE; + s[axis] = (bd < sd) ? bb.first : sn.first; + } + + pt = ( _point - _origin ) * NR::scale(s) + _origin; + if (isNaN(pt[X] + pt[Y])) { + g_warning("point=(%g, %g), norm=(%g, %g), s=(%g, %g)\n", + _point[X], _point[Y], _origin[X], _origin[Y], s[X], s[Y]); + } + + // status text + _message_context.setF(Inkscape::NORMAL_MESSAGE, + _("Scale: %0.2f%% x %0.2f%%; with Ctrl to lock ratio"), + 100 * s[NR::X], 100 * s[NR::Y]); + + return TRUE; +} + +gboolean Inkscape::SelTrans::skewRequest(SPSelTransHandle const &handle, NR::Point &pt, guint state) +{ + using NR::X; + using NR::Y; + + if (handle.cursor != GDK_SB_V_DOUBLE_ARROW && handle.cursor != GDK_SB_H_DOUBLE_ARROW) { + return FALSE; + } + + NR::Dim2 dim_a; + NR::Dim2 dim_b; + if (handle.cursor == GDK_SB_V_DOUBLE_ARROW) { + dim_a = X; + dim_b = Y; + } else { + dim_a = Y; + dim_b = X; + } + + double skew[2]; + double s[2] = { 1.0, 1.0 }; + + if (fabs(_point[dim_a] - _origin[dim_a]) < NR_EPSILON) { + return FALSE; + } + + skew[dim_a] = ( pt[dim_b] - _point[dim_b] ) / ( _point[dim_a] - _origin[dim_a] ); + + s[dim_a] = ( pt[dim_a] - _origin[dim_a] ) / ( _point[dim_a] - _origin[dim_a] ); + + if ( fabs(s[dim_a]) < 1 ) { + s[dim_a] = sign(s[dim_a]); + } else { + s[dim_a] = floor( s[dim_a] + 0.5 ); + } + + double radians = atan(skew[dim_a] / s[dim_a]); + + if (state & GDK_CONTROL_MASK) { + + int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12); + + if (snaps) { + double sections = floor( radians * snaps / M_PI + .5 ); + if (fabs(sections) >= snaps / 2) sections = sign(sections) * (snaps / 2 - 1); + radians = ( M_PI / snaps ) * sections; + } + skew[dim_a] = tan(radians) * s[dim_a]; + } else { + skew[dim_a] = namedview_dim_snap_list_skew(_desktop->namedview, + Snapper::SNAP_POINT, _snap_points, + _origin, skew[dim_a], dim_b); + } + + pt[dim_b] = ( _point[dim_a] - _origin[dim_a] ) * skew[dim_a] + _point[dim_b]; + pt[dim_a] = ( _point[dim_a] - _origin[dim_a] ) * s[dim_a] + _origin[dim_a]; + + /* status text */ + double degrees = 180 / M_PI * radians; + if (degrees > 180) degrees -= 360; + if (degrees < -180) degrees += 360; + + _message_context.setF(Inkscape::NORMAL_MESSAGE, + // TRANSLATORS: don't modify the first ";" + // (it will NOT be displayed as ";" - only the second one will be) + _("Skew: %0.2f°; with Ctrl to snap angle"), + degrees); + + return TRUE; +} + +gboolean Inkscape::SelTrans::rotateRequest(NR::Point &pt, guint state) +{ + int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12); + + // rotate affine in rotate + NR::Point const d1 = _point - _origin; + NR::Point const d2 = pt - _origin; + + NR::Coord const h1 = NR::L2(d1); + if (h1 < 1e-15) return FALSE; + NR::Point q1 = d1 / h1; + NR::Coord const h2 = NR::L2(d2); + if (fabs(h2) < 1e-15) return FALSE; + NR::Point q2 = d2 / h2; + + double radians; + if (state & GDK_CONTROL_MASK) { + /* Have to restrict movement. */ + double cos_t = NR::dot(q1, q2); + double sin_t = NR::dot(NR::rot90(q1), q2); + radians = atan2(sin_t, cos_t); + if (snaps) { + radians = ( M_PI / snaps ) * floor( radians * snaps / M_PI + .5 ); + } + q1 = NR::Point(1, 0); + q2 = NR::Point(cos(radians), sin(radians)); + } else { + radians = atan2(NR::dot(NR::rot90(d1), d2), + NR::dot(d1, d2)); + } + + NR::rotate const r1(q1); + NR::rotate const r2(q2); + pt = _point * NR::translate(-_origin) * ( r2 / r1 ) * NR::translate(_origin); + + /* status text */ + double degrees = 180 / M_PI * radians; + if (degrees > 180) degrees -= 360; + if (degrees < -180) degrees += 360; + + _message_context.setF(Inkscape::NORMAL_MESSAGE, + // TRANSLATORS: don't modify the first ";" + // (it will NOT be displayed as ";" - only the second one will be) + _("Rotate: %0.2f°; with Ctrl to snap angle"), degrees); + + return TRUE; +} + +gboolean Inkscape::SelTrans::centerRequest(NR::Point &pt, guint state) +{ + using NR::X; + using NR::Y; + + SnapManager const m(_desktop->namedview); + pt = m.freeSnap(Snapper::SNAP_POINT, pt, NULL).getPoint(); + + if (state & GDK_CONTROL_MASK) { + if ( fabs(_point[X] - pt[X]) > fabs(_point[Y] - pt[Y]) ) { + pt[Y] = _point[Y]; + } else { + pt[X] = _point[X]; + } + } + + Inkscape::Selection *selection = _desktop->selection; + Inkscape::XML::Node *current = selection->singleRepr(); + if (current != NULL){ + sp_repr_set_svg_double(current, "inkscape:c_rx", pt[X]); + sp_repr_set_svg_double(current, "inkscape:c_ry", pt[Y]); + + } + + if (!(state & GDK_SHIFT_MASK)) { + // screen pixels to snap center to bbox +#define SNAP_DIST 5 + // FIXME: take from prefs + double snap_dist = SNAP_DIST / _desktop->current_zoom(); + + for (int i = 0; i < 2; i++) { + + if (fabs(pt[i] - _box.min()[i]) < snap_dist) { + pt[i] = _box.min()[i]; + } + if (fabs(pt[i] - _box.midpoint()[i]) < snap_dist) { + pt[i] = _box.midpoint()[i]; + } + if (fabs(pt[i] - _box.max()[i]) < snap_dist) { + pt[i] = _box.max()[i]; + } + } + } + + // status text + GString *xs = SP_PX_TO_METRIC_STRING(pt[X], _desktop->namedview->getDefaultMetric()); + GString *ys = SP_PX_TO_METRIC_STRING(pt[Y], _desktop->namedview->getDefaultMetric()); + _message_context.setF(Inkscape::NORMAL_MESSAGE, _("Move center to %s, %s"), xs->str, ys->str); + g_string_free(xs, FALSE); + g_string_free(ys, FALSE); + + return TRUE; +} + +/* + * handlers for handle movement + * + */ + +void sp_sel_trans_stretch(Inkscape::SelTrans *seltrans, SPSelTransHandle const &handle, NR::Point &pt, guint state) +{ + seltrans->stretch(handle, pt, state); +} + +void sp_sel_trans_scale(Inkscape::SelTrans *seltrans, SPSelTransHandle const &, NR::Point &pt, guint state) +{ + seltrans->scale(pt, state); +} + +void sp_sel_trans_skew(Inkscape::SelTrans *seltrans, SPSelTransHandle const &handle, NR::Point &pt, guint state) +{ + seltrans->skew(handle, pt, state); +} + +void sp_sel_trans_rotate(Inkscape::SelTrans *seltrans, SPSelTransHandle const &, NR::Point &pt, guint state) +{ + seltrans->rotate(pt, state); +} + +void Inkscape::SelTrans::stretch(SPSelTransHandle const &handle, NR::Point &pt, guint state) +{ + using NR::X; + using NR::Y; + + NR::Dim2 dim; + switch (handle.cursor) { + case GDK_LEFT_SIDE: + case GDK_RIGHT_SIDE: + dim = X; + break; + case GDK_TOP_SIDE: + case GDK_BOTTOM_SIDE: + dim = Y; + break; + default: + g_assert_not_reached(); + abort(); + break; + } + + NR::Point const scale_origin(_origin); + double const offset = _point[dim] - scale_origin[dim]; + if (!( fabs(offset) >= 1e-15 )) { + return; + } + NR::scale s(1, 1); + s[dim] = ( pt[dim] - scale_origin[dim] ) / offset; + if (isNaN(s[dim])) { + g_warning("s[dim]=%g, pt[dim]=%g, scale_origin[dim]=%g, point[dim]=%g\n", + s[dim], pt[dim], scale_origin[dim], _point[dim]); + } + if (!( fabs(s[dim]) >= 1e-15 )) { + s[dim] = 1e-15; + } + if (state & GDK_CONTROL_MASK) { + /* Preserve aspect ratio, but never flip in the dimension not being edited. */ + s[!dim] = fabs(s[dim]); + } + + NR::Rect new_bbox = _box * (NR::translate(-scale_origin) * NR::Matrix(s) * NR::translate(scale_origin)); + + int transform_stroke = prefs_get_int_attribute ("options.transform", "stroke", 1); + NR::Matrix scaler = get_scale_transform_with_stroke (_box, _strokewidth, transform_stroke, + new_bbox.min()[NR::X], new_bbox.min()[NR::Y], new_bbox.max()[NR::X], new_bbox.max()[NR::Y]); + + transform(scaler, NR::Point(0, 0)); // we have already accounted for origin, so pass 0,0 +} + +void Inkscape::SelTrans::scale(NR::Point &pt, guint state) +{ + NR::Point const offset = _point - _origin; + + NR::scale s (1, 1); + for (int i = NR::X; i <= NR::Y; i++) { + if (fabs(offset[i]) > 1e-9) + s[i] = (pt[i] - _origin[i]) / offset[i]; + if (fabs(s[i]) < 1e-9) + s[i] = 1e-9; + } + NR::Rect new_bbox = _box * (NR::translate(-_origin) * NR::Matrix(s) * NR::translate(_origin)); + + int transform_stroke = prefs_get_int_attribute ("options.transform", "stroke", 1); + NR::Matrix scaler = get_scale_transform_with_stroke (_box, _strokewidth, transform_stroke, + new_bbox.min()[NR::X], new_bbox.min()[NR::Y], new_bbox.max()[NR::X], new_bbox.max()[NR::Y]); + + transform(scaler, NR::Point(0, 0)); // we have already accounted for origin, so pass 0,0 +} + +void Inkscape::SelTrans::skew(SPSelTransHandle const &handle, NR::Point &pt, guint state) +{ + NR::Point const offset = _point - _origin; + + unsigned dim; + switch (handle.cursor) { + case GDK_SB_H_DOUBLE_ARROW: + dim = NR::Y; + break; + case GDK_SB_V_DOUBLE_ARROW: + dim = NR::X; + break; + default: + g_assert_not_reached(); + abort(); + break; + } + if (fabs(offset[dim]) < 1e-15) { + return; + } + NR::Matrix skew = NR::identity(); + skew[2*dim + dim] = (pt[dim] - _origin[dim]) / offset[dim]; + skew[2*dim + (1-dim)] = (pt[1-dim] - _point[1-dim]) / offset[dim]; + skew[2*(1-dim) + (dim)] = 0; + skew[2*(1-dim) + (1-dim)] = 1; + + for (int i = 0; i < 2; i++) { + if (fabs(skew[3*i]) < 1e-15) { + skew[3*i] = 1e-15; + } + } + transform(skew, _origin); +} + +void Inkscape::SelTrans::rotate(NR::Point &pt, guint state) +{ + NR::Point const offset = _point - _origin; + + NR::Coord const h1 = NR::L2(offset); + if (h1 < 1e-15) { + return; + } + NR::Point const q1 = offset / h1; + NR::Coord const h2 = NR::L2( pt - _origin ); + if (h2 < 1e-15) { + return; + } + NR::Point const q2 = (pt - _origin) / h2; + NR::rotate const r1(q1); + NR::rotate const r2(q2); + + NR::Matrix rotate( r2 / r1 ); + transform(rotate, _origin); +} + +void sp_sel_trans_center(Inkscape::SelTrans *seltrans, SPSelTransHandle const &, NR::Point &pt, guint state) +{ + seltrans->setCenter(pt); +} + + +void Inkscape::SelTrans::moveTo(NR::Point const &xy, guint state) +{ + SnapManager const m(_desktop->namedview); + + /* The amount that we've moved by during this drag */ + NR::Point dxy = xy - _point; + + /* Get a STL list of the selected items. + ** FIXME: this should probably be done by Inkscape::Selection. + */ + std::list it; + for (GSList const *i = _selection->itemList(); i != NULL; i = i->next) { + it.push_back(reinterpret_cast(i->data)); + } + + bool const alt = (state & GDK_MOD1_MASK); + bool const control = (state & GDK_CONTROL_MASK); + bool const shift = (state & GDK_SHIFT_MASK); + + if (alt) { + + /* Alt pressed means keep offset: snap the moved distance to the grid. + ** FIXME: this will snap to more than just the grid, nowadays. + */ + + dxy = m.freeSnap(Snapper::SNAP_POINT, dxy, NULL).getPoint(); + + } else if (!shift) { + + /* We're snapping to things, possibly with a constraint to horizontal or + ** vertical movement. Obtain a list of possible translations and then + ** pick the smallest. + */ + + /* This will be our list of possible translations */ + std::list > s; + + if (control) { + + /* Snap to things, and also constrain to horizontal or vertical movement */ + + for (unsigned int dim = 0; dim < 2; dim++) { + s.push_back(m.constrainedSnapTranslation(Inkscape::Snapper::BBOX_POINT, + _bbox_points, + component_vectors[dim], it, dxy)); + s.push_back(m.constrainedSnapTranslation(Inkscape::Snapper::SNAP_POINT, + _snap_points, + component_vectors[dim], it, dxy)); + } + + } else { + + /* Snap to things with no constraint */ + + s.push_back(m.freeSnapTranslation(Inkscape::Snapper::BBOX_POINT, + _bbox_points, it, dxy)); + s.push_back(m.freeSnapTranslation(Inkscape::Snapper::SNAP_POINT, + _snap_points, it, dxy)); + } + + /* Pick one */ + NR::Coord best = NR_HUGE; + for (std::list >::const_iterator i = s.begin(); i != s.end(); i++) { + if (i->second) { + NR::Coord const m = NR::L2(i->first); + if (m < best) { + best = m; + dxy = i->first; + } + } + } + } + + if (control) { + /* Ensure that the horizontal and vertical constraint has been applied */ + if (fabs(dxy[NR::X]) > fabs(dxy[NR::Y])) { + dxy[NR::Y] = 0; + } else { + dxy[NR::X] = 0; + } + } + + NR::Matrix const move((NR::translate(dxy))); + NR::Point const norm(0, 0); + transform(move, norm); + + // status text + GString *xs = SP_PX_TO_METRIC_STRING(dxy[NR::X], _desktop->namedview->getDefaultMetric()); + GString *ys = SP_PX_TO_METRIC_STRING(dxy[NR::Y], _desktop->namedview->getDefaultMetric()); + _message_context.setF(Inkscape::NORMAL_MESSAGE, _("Move by %s, %s; with Ctrl to restrict to horizontal/vertical; with Shift to disable snapping"), xs->str, ys->str); + g_string_free(xs, TRUE); + g_string_free(ys, TRUE); +} + + +/* + 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/seltrans.h b/src/seltrans.h new file mode 100644 index 000000000..30be58eeb --- /dev/null +++ b/src/seltrans.h @@ -0,0 +1,151 @@ +#ifndef __SELTRANS_H__ +#define __SELTRANS_H__ + +/* + * Helper object for transforming selected items + * + * Author: + * Lauris Kaplinski + * Carl Hetherington + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#include +#include +#include "forward.h" +#include "selcue.h" +#include "message-context.h" +#include + +struct SPKnot; +class SPDesktop; +class SPCanvasItem; +class SPSelTransHandle; + +namespace Inkscape +{ + +namespace XML +{ + class Node; +} + +class SelTrans +{ +public: + SelTrans(SPDesktop *desktop); + ~SelTrans(); + + Inkscape::MessageContext &messageContext() { + return _message_context; + } + + void increaseState(); + void resetState(); + void setCenter(NR::Point const &p); + void grab(NR::Point const &p, gdouble x, gdouble y, bool show_handles); + void transform(NR::Matrix const &rel_affine, NR::Point const &norm); + void ungrab(); + void stamp(); + void moveTo(NR::Point const &xy, guint state); + void stretch(SPSelTransHandle const &handle, NR::Point &pt, guint state); + void scale(NR::Point &pt, guint state); + void skew(SPSelTransHandle const &handle, NR::Point &pt, guint state); + void rotate(NR::Point &pt, guint state); + gboolean scaleRequest(NR::Point &pt, guint state); + gboolean stretchRequest(SPSelTransHandle const &handle, NR::Point &pt, guint state); + gboolean skewRequest(SPSelTransHandle const &handle, NR::Point &pt, guint state); + gboolean rotateRequest(NR::Point &pt, guint state); + gboolean centerRequest(NR::Point &pt, guint state); + + gboolean handleRequest(SPKnot *knot, NR::Point *position, guint state, SPSelTransHandle const &handle); + void handleGrab(SPKnot *knot, guint state, SPSelTransHandle const &handle); + void handleNewEvent(SPKnot *knot, NR::Point *position, guint state, SPSelTransHandle const &handle); + + enum Show + { + SHOW_CONTENT, + SHOW_OUTLINE + }; + + void setShow(Show s) { + _show = s; + } + bool isEmpty() { + return _empty; + } + +private: + void _updateHandles(); + void _updateVolatileState(); + void _selChanged(Inkscape::Selection *selection); + void _selModified(Inkscape::Selection *selection, guint flags); + void _showHandles(SPKnot *knot[], SPSelTransHandle const handle[], gint num, + gchar const *even_tip, gchar const *odd_tip); + void _centreTrans(Inkscape::XML::Node *current) const; + + enum State { + STATE_SCALE, + STATE_ROTATE + }; + + SPDesktop *_desktop; + + std::vector > _items; + + std::vector _snap_points; + std::vector _bbox_points; + + Inkscape::SelCue _selcue; + + Inkscape::Selection *_selection; + State _state; + Show _show; + + bool _grabbed; + bool _show_handles; + bool _empty; + bool _changed; + + NR::Rect _box; + gdouble _strokewidth; + NR::Matrix _current; + NR::Point _opposite; ///< opposite point to where a scale is taking place + NR::Point _center; + SPKnot *_shandle[8]; + SPKnot *_rhandle[8]; + SPKnot *_chandle; + SPCanvasItem *_norm; + SPCanvasItem *_grip; + SPCanvasItem *_l[4]; + guint _sel_changed_id; + guint _sel_modified_id; + GSList *_stamp_cache; + + NR::Point _origin; ///< position of origin for transforms + NR::Point _point; ///< original position of the knot being used for the current transform + Inkscape::MessageContext _message_context; + SigC::Connection _sel_changed_connection; + SigC::Connection _sel_modified_connection; +}; + +} + +#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 : diff --git a/src/shortcuts-default-xml.cpp b/src/shortcuts-default-xml.cpp new file mode 100644 index 000000000..e91f435e2 --- /dev/null +++ b/src/shortcuts-default-xml.cpp @@ -0,0 +1,243 @@ +extern char const shortcuts_default_xml[]= +"\n" +"\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +""; diff --git a/src/shortcuts.cpp b/src/shortcuts.cpp new file mode 100644 index 000000000..047928071 --- /dev/null +++ b/src/shortcuts.cpp @@ -0,0 +1,195 @@ +#define __SP_SHORTCUTS_C__ + +/** \file + * Keyboard shortcut processing. + */ +/* + * Authors: + * Lauris Kaplinski + * MenTaLguY + * bulia byak + * Peter Moulder + * + * Copyright (C) 2005 Monash University + * Copyright (C) 2005 MenTaLguY + * + * You may redistribute and/or modify this file under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include "helper/action.h" +#include "shortcuts.h" +#include "verbs.h" +#include "xml/node-iterators.h" +#include "xml/repr.h" + +using namespace Inkscape; + +static void sp_shortcut_set(unsigned int const shortcut, Inkscape::Verb *const verb, bool const is_primary); + +static void set_shortcuts_xml(XML::Document const *doc); + +/* Returns true if action was performed */ + +bool +sp_shortcut_invoke(unsigned int shortcut, Inkscape::UI::View::View *view) +{ + Inkscape::Verb *verb = sp_shortcut_get_verb(shortcut); + if (verb) { + SPAction *action = verb->get_action(view); + if (action) { + sp_action_perform(action, NULL); + return true; + } + } + return false; +} + +static GHashTable *verbs = NULL; +static GHashTable *primary_shortcuts = NULL; + +extern char const shortcuts_default_xml[]; + +static void +sp_shortcut_init() +{ + verbs = g_hash_table_new(NULL, NULL); + primary_shortcuts = g_hash_table_new(NULL, NULL); + + XML::Document *shortcuts=sp_repr_read_mem(shortcuts_default_xml, strlen(shortcuts_default_xml), NULL); + if (shortcuts) { + set_shortcuts_xml(shortcuts); + GC::release(shortcuts); + } else { + g_error("Unable to parse default shortcuts"); + } +} + +static void set_shortcuts_xml(XML::Document const *doc) { + XML::Node const *root=doc->root(); + g_return_if_fail(!strcmp(root->name(), "keybindings")); + XML::NodeConstSiblingIterator iter=root->firstChild(); + for ( ; iter ; ++iter ) { + bool is_primary; + + if (!strcmp(iter->name(), "primary")) { + is_primary = true; + } else if (!strcmp(iter->name(), "secondary")) { + is_primary = false; + } else { + g_warning("Unknown key binding type %s", iter->name()); + continue; + } + + gchar const *verb_name=iter->attribute("verb"); + if (!verb_name) { + g_warning("Missing verb name for shortcut"); + continue; + } + + gchar const *keyval_name=iter->attribute("keyval"); + if (!keyval_name) { + g_warning("Missing keyval for %s", verb_name); + continue; + } + guint keyval=gdk_keyval_from_name(keyval_name); + if (keyval == GDK_VoidSymbol) { + g_warning("Unknown keyval %s for %s", keyval_name, verb_name); + continue; + } + + guint modifiers=0; + + gchar const *modifiers_string=iter->attribute("modifiers"); + if (modifiers_string) { + gchar const *iter=modifiers_string; + while (*iter) { + size_t length=strcspn(iter, ","); + gchar *mod=g_strndup(iter, length); + if (!strcmp(mod, "control")) { + modifiers |= SP_SHORTCUT_CONTROL_MASK; + } else if (!strcmp(mod, "shift")) { + modifiers |= SP_SHORTCUT_SHIFT_MASK; + } else if (!strcmp(mod, "alt")) { + modifiers |= SP_SHORTCUT_ALT_MASK; + } else { + g_warning("Unknown modifier %s for %s", mod, verb_name); + } + g_free(mod); + iter += length; + if (*iter) iter++; + } + } + + sp_shortcut_set(keyval | modifiers, + Inkscape::Verb::getbyid(verb_name), + is_primary); + } +} + +/** + * Adds a keyboard shortcut for the given verb. + * (Removes any existing binding for the given shortcut, including appropriately + * adjusting sp_shortcut_get_primary if necessary.) + * + * \param is_primary True iff this is the shortcut to be written in menu items or buttons. + * + * \post sp_shortcut_get_verb(shortcut) == verb. + * \post !is_primary or sp_shortcut_get_primary(verb) == shortcut. + */ +static void +sp_shortcut_set(unsigned int const shortcut, Inkscape::Verb *const verb, bool const is_primary) +{ + if (!verbs) sp_shortcut_init(); + + Inkscape::Verb *old_verb = (Inkscape::Verb *)(g_hash_table_lookup(verbs, GINT_TO_POINTER(shortcut))); + g_hash_table_insert(verbs, GINT_TO_POINTER(shortcut), (gpointer)(verb)); + + /* Maintain the invariant that sp_shortcut_get_primary(v) returns either 0 or a valid shortcut for v. */ + if (old_verb && old_verb != verb) { + unsigned int const old_primary = (unsigned int)GPOINTER_TO_INT(g_hash_table_lookup(primary_shortcuts, (gpointer)old_verb)); + + if (old_primary == shortcut) { + g_hash_table_insert(primary_shortcuts, (gpointer)old_verb, GINT_TO_POINTER(0)); + } + } + + if (is_primary) { + g_hash_table_insert(primary_shortcuts, (gpointer)(verb), GINT_TO_POINTER(shortcut)); + } +} + +Inkscape::Verb * +sp_shortcut_get_verb(unsigned int shortcut) +{ + if (!verbs) sp_shortcut_init(); + return (Inkscape::Verb *)(g_hash_table_lookup(verbs, GINT_TO_POINTER(shortcut))); +} + +unsigned int +sp_shortcut_get_primary(Inkscape::Verb *verb) +{ + if (!primary_shortcuts) sp_shortcut_init(); + return (unsigned int)GPOINTER_TO_INT(g_hash_table_lookup(primary_shortcuts, + (gpointer)(verb))); +} + + +/* + 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 : diff --git a/src/shortcuts.h b/src/shortcuts.h new file mode 100644 index 000000000..8ce0f7685 --- /dev/null +++ b/src/shortcuts.h @@ -0,0 +1,39 @@ +#ifndef __SP_SHORTCUTS_H__ +#define __SP_SHORTCUTS_H__ + +/* + * Keyboard shortcut processing + * + * Author: + * Lauris Kaplinski + * + * This code is in public domain + */ + +#include + + +/* We define high-bit mask for packing into single int */ + +#define SP_SHORTCUT_SHIFT_MASK (1 << 24) +#define SP_SHORTCUT_CONTROL_MASK (1 << 25) +#define SP_SHORTCUT_ALT_MASK (1 << 26) + +/* Returns true if action was performed */ +bool sp_shortcut_invoke (unsigned int shortcut, Inkscape::UI::View::View *view); + +Inkscape::Verb * sp_shortcut_get_verb (unsigned int shortcut); +unsigned int sp_shortcut_get_primary (Inkscape::Verb * verb); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/slideshow.cpp b/src/slideshow.cpp new file mode 100644 index 000000000..d2bb39790 --- /dev/null +++ b/src/slideshow.cpp @@ -0,0 +1,105 @@ +#define __SLIDESHOW_C__ + +/* + * Help/About window + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2003 authors + * Copyright (C) 2000-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include +#include "document.h" +#include "svg-view-widget.h" +#include "svg-view.h" + +static gint +sp_slideshow_event (SPViewWidget *vw, GdkEvent *event, GtkWidget *window) +{ + GSList *slides; + const gchar *fname, *nname; + int idx; + + slides = (GSList*)g_object_get_data (G_OBJECT (window), "slides"); + fname = (const gchar*)g_object_get_data (G_OBJECT (window), "current"); + idx = g_slist_index (slides, fname); + + switch (event->type) { + case GDK_KEY_PRESS: + switch (event->key.keyval) { + case GDK_BackSpace: + case GDK_Delete: + case GDK_Left: + idx -= 1; + break; + case GDK_Escape: + gtk_widget_destroy (window); + return TRUE; + break; + default: + idx += 1; + break; + } + break; + case GDK_BUTTON_PRESS: + idx += 1; + break; + default: + break; + } + + nname = (const gchar*)g_slist_nth_data (slides, idx); + g_print ("Old %s new %s\n", fname, nname); + + if (nname && (nname != fname)) { + SPDocument *doc; + g_print ("Trying to load\n"); + doc = sp_document_new (nname, TRUE); + if (doc) { + reinterpret_cast(SP_VIEW_WIDGET_VIEW (vw))->setDocument (doc); + sp_document_unref (doc); + } + g_object_set_data (G_OBJECT (window), "current", (gpointer) nname); + } + + return TRUE; +} + +GtkWidget * +sp_slideshow_new (const GSList *files) +{ + SPDocument *doc; + GtkWidget *w, *v; + + doc = sp_document_new ((const gchar*)files->data, TRUE); + g_return_val_if_fail (doc != NULL, NULL); + + w = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW (w), _("Inkscape slideshow")); + gtk_window_set_default_size (GTK_WINDOW (w), 480, 360); + gtk_window_set_policy (GTK_WINDOW (w), TRUE, TRUE, FALSE); + + v = sp_svg_view_widget_new (doc); + sp_svg_view_widget_set_resize (SP_SVG_VIEW_WIDGET (v), FALSE, sp_document_width (doc), sp_document_height (doc)); + sp_document_unref (doc); + gtk_widget_show (v); + gtk_container_add (GTK_CONTAINER (w), v); + + g_object_set_data (G_OBJECT (w), "slides", (gpointer) files); + g_object_set_data (G_OBJECT (w), "current", files->data); + + g_signal_connect (G_OBJECT (v), "event", G_CALLBACK (sp_slideshow_event), w); + + return w; +} diff --git a/src/slideshow.h b/src/slideshow.h new file mode 100644 index 000000000..0aa236bcc --- /dev/null +++ b/src/slideshow.h @@ -0,0 +1,31 @@ +#ifndef __SP_SLIDESHOW_H__ +#define __SP_SLIDESHOW_H__ + +/* + * Slideshow/About window + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2003 authors + * Copyright (C) 2000-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +GtkWidget *sp_slideshow_new (const GSList *files); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/snap.cpp b/src/snap.cpp new file mode 100644 index 000000000..b14549db3 --- /dev/null +++ b/src/snap.cpp @@ -0,0 +1,406 @@ +#define __SP_DESKTOP_SNAP_C__ + +/** + * \file snap.cpp + * + * \brief Various snapping methods + * + * Authors: + * Lauris Kaplinski + * Frank Felfe + * Carl Hetherington + * + * Copyright (C) 1999-2002 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-namedview.h" +#include "snap.h" +#include +#include +#include + + +/** + * \return true if one of the snappers will try to snap something. + */ +bool SnapManager::willSnapSomething() const +{ + SPNamedView::SnapperList s = namedview->getSnappers(); + SPNamedView::SnapperList::const_iterator i = s.begin(); + while (i != s.end() && (*i)->willSnapSomething() == false) { + i++; + } + + return (i != s.end()); +} + + +/* FIXME: lots of cut-and-paste here. This needs some +** functor voodoo to cut it all down a bit. +*/ + +Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t, + NR::Point const &p, + SPItem const *it) const + +{ + std::list lit; + lit.push_back(it); + return freeSnap(t, p, lit); +} + + +Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::Snapper::PointType t, + NR::Point const &p, + std::list const &it) const +{ + Inkscape::SnappedPoint r(p, NR_HUGE); + + SPNamedView::SnapperList snappers = namedview->getSnappers(); + for (SPNamedView::SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) { + Inkscape::SnappedPoint const s = (*i)->freeSnap(t, p, it); + if (s.getDistance() < r.getDistance()) { + r = s; + } + } + + return r; +} + + +Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::Snapper::PointType t, + NR::Point const &p, + NR::Point const &c, + SPItem const *it) const +{ + std::list lit; + lit.push_back(it); + return constrainedSnap(t, p, c, lit); +} + + +Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::Snapper::PointType t, + NR::Point const &p, + NR::Point const &c, + std::list const &it) const +{ + Inkscape::SnappedPoint r(p, NR_HUGE); + + SPNamedView::SnapperList snappers = namedview->getSnappers(); + for (SPNamedView::SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) { + Inkscape::SnappedPoint const s = (*i)->constrainedSnap(t, p, c, it); + if (s.getDistance() < r.getDistance()) { + r = s; + } + } + + return r; +} + + +std::pair SnapManager::freeSnapTranslation(Inkscape::Snapper::PointType t, + std::vector const &p, + std::list const &it, + NR::Point const &tr) const +{ + if (willSnapSomething() == false) { + return std::make_pair(tr, false); + } + + NR::Point best_translation = tr; + NR::Coord best_distance = NR_HUGE; + + for (std::vector::const_iterator i = p.begin(); i != p.end(); i++) { + /* Translated version of this point */ + NR::Point const q = *i + tr; + /* Snap it */ + Inkscape::SnappedPoint s = freeSnap(t, q, it); + if (s.getDistance() < NR_HUGE) { + /* Resulting translation */ + NR::Point const r = s.getPoint() - *i; + NR::Coord const d = NR::L2(r); + if (d < best_distance) { + best_distance = d; + best_translation = r; + } + } + } + + return std::make_pair(best_translation, best_distance < NR_HUGE); +} + + + +std::pair SnapManager::constrainedSnapTranslation(Inkscape::Snapper::PointType t, + std::vector const &p, + NR::Point const &c, + std::list const &it, + NR::Point const &tr) const +{ + if (willSnapSomething() == false) { + return std::make_pair(tr, false); + } + + NR::Point best_translation = tr; + NR::Coord best_distance = NR_HUGE; + + for (std::vector::const_iterator i = p.begin(); i != p.end(); i++) { + /* Translated version of this point */ + NR::Point const q = *i + tr; + /* Snap it */ + Inkscape::SnappedPoint s = constrainedSnap(t, q, c, it); + if (s.getDistance() < NR_HUGE) { + /* Resulting translation */ + NR::Point const r = s.getPoint() - *i; + NR::Coord const d = NR::L2(r); + if (d < best_distance) { + best_distance = d; + best_translation = r; + } + } + } + + return std::make_pair(best_translation, best_distance < NR_HUGE); +} + + + + + + +/// Minimal distance to norm before point is considered for snap. +static const double MIN_DIST_NORM = 1.0; + +/** + * Try to snap \a req in one dimension. + * + * \param nv NamedView to use. + * \param req Point to snap; updated to the snapped point if a snap occurred. + * \param dim Dimension to snap in. + * \return Distance to the snap point along the \a dim axis, or \c NR_HUGE + * if no snap occurred. + */ +NR::Coord namedview_dim_snap(SPNamedView const *nv, Inkscape::Snapper::PointType t, NR::Point &req, + NR::Dim2 const dim, SPItem const *it) +{ + return namedview_vector_snap(nv, t, req, component_vectors[dim], it); +} + +NR::Coord namedview_dim_snap(SPNamedView const *nv, Inkscape::Snapper::PointType t, NR::Point &req, + NR::Dim2 const dim, std::list const &it) +{ + return namedview_vector_snap(nv, t, req, component_vectors[dim], it); +} + + +NR::Coord namedview_vector_snap(SPNamedView const *nv, Inkscape::Snapper::PointType t, + NR::Point &req, NR::Point const &d, + SPItem const *it) +{ + std::list lit; + lit.push_back(it); + return namedview_vector_snap(nv, t, req, d, lit); +} + +/** + * Look for snap point along the line described by the point \a req + * and the direction vector \a d. + * Modifies req to the snap point, if one is found. + * \return The distance from \a req to the snap point along the vector \a d, + * or \c NR_HUGE if no snap point was found. + * + * \pre d ≠ (0, 0). + */ +NR::Coord namedview_vector_snap(SPNamedView const *nv, Inkscape::Snapper::PointType t, + NR::Point &req, NR::Point const &d, + std::list const &it) +{ + g_assert(nv != NULL); + g_assert(SP_IS_NAMEDVIEW(nv)); + + SPNamedView::SnapperList snappers = nv->getSnappers(); + + NR::Coord best = NR_HUGE; + for (SPNamedView::SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) { + Inkscape::SnappedPoint const s = (*i)->constrainedSnap(t, req, d, it); + if (s.getDistance() < best) { + req = s.getPoint(); + best = s.getDistance(); + } + } + + return best; +} + + +/* + * functions for lists of points + * + * All functions take a list of NR::Point and parameter indicating the proposed transformation. + * They return the updated transformation parameter. + */ + +/** + * Snap list of points in one dimension. + * \return Coordinate difference. + */ +std::pair namedview_dim_snap_list(SPNamedView const *nv, Inkscape::Snapper::PointType t, + const std::vector &p, + NR::Coord const dx, NR::Dim2 const dim, + std::list const &it + ) +{ + NR::Coord dist = NR_HUGE; + NR::Coord xdist = dx; + + SnapManager const m(nv); + + if (m.willSnapSomething()) { + for (std::vector::const_iterator i = p.begin(); i != p.end(); i++) { + NR::Point q = *i; + NR::Coord const pre = q[dim]; + q[dim] += dx; + NR::Coord const d = namedview_dim_snap(nv, t, q, dim, it); + if (d < dist) { + xdist = q[dim] - pre; + dist = d; + } + } + } + + return std::make_pair(xdist, dist < NR_HUGE); +} + +/** + * Snap list of points in two dimensions. + */ +std::pair namedview_vector_snap_list(SPNamedView const *nv, Inkscape::Snapper::PointType t, + const std::vector &p, NR::Point const &norm, + NR::scale const &s, std::list const &it) +{ + using NR::X; + using NR::Y; + + SnapManager const m(nv); + + if (m.willSnapSomething() == false) { + return std::make_pair(s[X], false); + } + + NR::Coord dist = NR_HUGE; + double ratio = fabs(s[X]); + for (std::vector::const_iterator i = p.begin(); i != p.end(); i++) { + NR::Point const &q = *i; + NR::Point check = ( q - norm ) * s + norm; + if (NR::LInfty( q - norm ) > MIN_DIST_NORM) { + NR::Coord d = namedview_vector_snap(nv, t, check, check - norm, it); + if (d < dist) { + dist = d; + NR::Dim2 const dominant = ( ( fabs( q[X] - norm[X] ) > + fabs( q[Y] - norm[Y] ) ) + ? X + : Y ); + ratio = ( ( check[dominant] - norm[dominant] ) + / ( q[dominant] - norm[dominant] ) ); + } + } + } + + return std::make_pair(ratio, dist < NR_HUGE); +} + + +/** + * Try to snap points in \a p after they have been scaled by \a sx with respect to + * the origin \a norm. The best snap is the one that changes the scale least. + * + * \return Pair containing snapped scale and a flag which is true if a snap was made. + */ +std::pair namedview_dim_snap_list_scale(SPNamedView const *nv, Inkscape::Snapper::PointType t, + const std::vector &p, NR::Point const &norm, + double const sx, NR::Dim2 dim, + std::list const &it) +{ + SnapManager const m(nv); + if (m.willSnapSomething() == false) { + return std::make_pair(sx, false); + } + + g_assert(dim < 2); + + NR::Coord dist = NR_HUGE; + double scale = sx; + + for (std::vector::const_iterator i = p.begin(); i != p.end(); i++) { + NR::Point q = *i; + NR::Point check = q; + + /* Scaled version of the point we are looking at */ + check[dim] = (sx * (q - norm) + norm)[dim]; + + if (fabs (q[dim] - norm[dim]) > MIN_DIST_NORM) { + /* Snap this point */ + const NR::Coord d = namedview_dim_snap (nv, t, check, dim, it); + /* Work out the resulting scale factor */ + double snapped_scale = (check[dim] - norm[dim]) / (q[dim] - norm[dim]); + + if (dist == NR_HUGE || fabs(snapped_scale - sx) < fabs(scale - sx)) { + /* This is either the first point, or the snapped scale + ** is the closest yet to the original. + */ + scale = snapped_scale; + dist = d; + } + } + } + + return std::make_pair(scale, dist < NR_HUGE); +} + +/** + * Try to snap points after they have been skewed. + */ +double namedview_dim_snap_list_skew(SPNamedView const *nv, Inkscape::Snapper::PointType t, + const std::vector &p, NR::Point const &norm, + double const sx, NR::Dim2 const dim) +{ + SnapManager const m(nv); + + if (m.willSnapSomething() == false) { + return sx; + } + + g_assert(dim < 2); + + gdouble dist = NR_HUGE; + gdouble skew = sx; + + for (std::vector::const_iterator i = p.begin(); i != p.end(); i++) { + NR::Point q = *i; + NR::Point check = q; + // apply shear + check[dim] += sx * (q[!dim] - norm[!dim]); + if (fabs (q[!dim] - norm[!dim]) > MIN_DIST_NORM) { + const gdouble d = namedview_dim_snap (nv, t, check, dim, NULL); + if (d < fabs (dist)) { + dist = d; + skew = (check[dim] - q[dim]) / (q[!dim] - norm[!dim]); + } + } + } + + return skew; +} + +/* + 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 : diff --git a/src/snap.h b/src/snap.h new file mode 100644 index 000000000..6b94fb4c2 --- /dev/null +++ b/src/snap.h @@ -0,0 +1,114 @@ +#ifndef SEEN_SNAP_H +#define SEEN_SNAP_H + +/** + * \file snap.h + * \brief Various snapping methods. + * + * Authors: + * Lauris Kaplinski + * Frank Felfe + * Carl Hetherington + * + * Copyright (C) 2000-2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +#include +#include +#include +#include "snapper.h" + +class SPNamedView; + +class SnapManager +{ +public: + SnapManager(SPNamedView const *v) : namedview(v) {} + + bool willSnapSomething() const; + + Inkscape::SnappedPoint freeSnap(Inkscape::Snapper::PointType t, + NR::Point const &p, + SPItem const *it) const; + + Inkscape::SnappedPoint freeSnap(Inkscape::Snapper::PointType t, + NR::Point const &p, + std::list const &it) const; + + Inkscape::SnappedPoint constrainedSnap(Inkscape::Snapper::PointType t, + NR::Point const &p, + NR::Point const &c, + SPItem const *it) const; + + Inkscape::SnappedPoint constrainedSnap(Inkscape::Snapper::PointType t, + NR::Point const &p, + NR::Point const &c, + std::list const &it) const; + + std::pair freeSnapTranslation(Inkscape::Snapper::PointType t, + std::vector const &p, + std::list const &it, + NR::Point const &tr) const; + + std::pair constrainedSnapTranslation(Inkscape::Snapper::PointType t, + std::vector const &p, + NR::Point const &c, + std::list const &it, + NR::Point const &tr) const; + +private: + SPNamedView const *namedview; +}; + + +/* Single point methods */ +NR::Coord namedview_vector_snap(SPNamedView const *nv, Inkscape::Snapper::PointType t, NR::Point &req, + NR::Point const &d, std::list const &it); +NR::Coord namedview_vector_snap(SPNamedView const *nv, Inkscape::Snapper::PointType t, NR::Point &req, + NR::Point const &d, SPItem const *it); +NR::Coord namedview_dim_snap(SPNamedView const *nv, Inkscape::Snapper::PointType t, NR::Point& req, + NR::Dim2 const dim, SPItem const *it); +NR::Coord namedview_dim_snap(SPNamedView const *nv, Inkscape::Snapper::PointType t, NR::Point& req, + NR::Dim2 const dim, std::list const &it); + +/* List of points methods */ + +std::pair namedview_vector_snap_list(SPNamedView const *nv, + Inkscape::Snapper::PointType t, const std::vector &p, + NR::Point const &norm, NR::scale const &s, + std::list const &it + ); + +std::pair namedview_dim_snap_list(SPNamedView const *nv, + Inkscape::Snapper::PointType t, const std::vector &p, + double const dx, NR::Dim2 const dim, + std::list const &it + ); + +std::pair namedview_dim_snap_list_scale(SPNamedView const *nv, + Inkscape::Snapper::PointType t, const std::vector &p, + NR::Point const &norm, double const sx, + NR::Dim2 const dim, + std::list const &it); + +NR::Coord namedview_dim_snap_list_skew(SPNamedView const *nv, Inkscape::Snapper::PointType t, + const std::vector &p, + NR::Point const &norm, double const sx, NR::Dim2 const dim); + + +#endif /* !SEEN_SNAP_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 : diff --git a/src/snapped-point.cpp b/src/snapped-point.cpp new file mode 100644 index 000000000..3aebb086a --- /dev/null +++ b/src/snapped-point.cpp @@ -0,0 +1,61 @@ +/** + * \file src/snapped-point.cpp + * \brief SnappedPoint class. + * + * Authors: + * Mathieu Dimanche + * + * Released under GNU GPL, read the file 'COPYING' for more information. + */ + +#include "snapped-point.h" + +Inkscape::SnappedPoint::SnappedPoint(NR::Point p, NR::Coord d) + : _distance(d), _point(p) +{ + +} + +Inkscape::SnappedPoint::~SnappedPoint() +{ + /// TODO : empty the _hightlight_groups vector and destroy the + /// HighlightGroup items it holds +} + + +void Inkscape::SnappedPoint::addHighlightGroup(HighlightGroup *group) +{ + /// TODO +} + +void Inkscape::SnappedPoint::addHighlightGroups(std::vector *groups) +{ + /// TODO +} + +NR::Coord Inkscape::SnappedPoint::getDistance() const +{ + return _distance; +} + +NR::Point Inkscape::SnappedPoint::getPoint() const +{ + return _point; +} + +std::vector Inkscape::SnappedPoint::getHighlightGroups() const +{ + return _hightlight_groups; +} + + +/* + 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/snapped-point.h b/src/snapped-point.h new file mode 100644 index 000000000..89eceb991 --- /dev/null +++ b/src/snapped-point.h @@ -0,0 +1,56 @@ +#ifndef SEEN_SNAPPEDPOINT_H +#define SEEN_SNAPPEDPOINT_H + +/** + * \file src/snapped-point.h + * \brief SnappedPoint class. + * + * Authors: + * Mathieu Dimanche + * + * Released under GNU GPL, read the file 'COPYING' for more information. + */ + +#include +#include "libnr/nr-coord.h" +#include "libnr/nr-point.h" + +namespace Inkscape +{ + +class HighlightGroup; + +/// Class describing the result of an attempt to snap. +class SnappedPoint +{ +public: + SnappedPoint() {} + SnappedPoint(NR::Point p, NR::Coord d); + ~SnappedPoint(); + + void addHighlightGroup(HighlightGroup *group); + void addHighlightGroups(std::vector *groups); + + NR::Coord getDistance() const; + NR::Point getPoint() const; + std::vector getHighlightGroups() const; + +private: + NR::Coord _distance; + NR::Point _point; + std::vector _hightlight_groups; +}; + +} +#endif /* !SEEN_SNAPPEDPOINT_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 : diff --git a/src/snapper.cpp b/src/snapper.cpp new file mode 100644 index 000000000..27531b50b --- /dev/null +++ b/src/snapper.cpp @@ -0,0 +1,180 @@ +/** + * \file src/snapper.cpp + * \brief Snapper class. + * + * Authors: + * Carl Hetherington + * + * Released under GNU GPL, read the file 'COPYING' for more information. + */ + +#include "libnr/nr-values.h" +#include "sp-namedview.h" + +Inkscape::Snapper::PointType const Inkscape::Snapper::BBOX_POINT = 0x1; +Inkscape::Snapper::PointType const Inkscape::Snapper::SNAP_POINT = 0x2; + +/** + * Construct new Snapper for named view. + * \param nv Named view. + * \param d Snap distance. + */ +Inkscape::Snapper::Snapper(SPNamedView const *nv, NR::Coord const d) : _named_view(nv), _distance(d) +{ + g_assert(_named_view != NULL); + g_assert(SP_IS_NAMEDVIEW(_named_view)); + + setSnapTo(BBOX_POINT, true); +} + +/** + * Set snap distance. + * \param d New snap distance (desktop coordinates) + */ +void Inkscape::Snapper::setDistance(NR::Coord const d) +{ + _distance = d; +} + +/** + * \return Snap distance (desktop coordinates) + */ +NR::Coord Inkscape::Snapper::getDistance() const +{ + return _distance; +} + +/** + * Turn on/off snapping of specific point types. + * \param t Point type. + * \param s true to snap to this point type, otherwise false; + */ +void Inkscape::Snapper::setSnapTo(PointType t, bool s) +{ + if (s) { + _snap_to |= t; + } else { + _snap_to &= ~t; + } +} + +/** + * \param t Point type. + * \return true if snapper will snap this type of point, otherwise false. + */ +bool Inkscape::Snapper::getSnapTo(PointType t) const +{ + return (_snap_to & t); +} + +/** + * \return true if this Snapper will snap at least one kind of point. + */ +bool Inkscape::Snapper::willSnapSomething() const +{ + return (_snap_to != 0); +} + + + +/** + * Try to snap a point to whatever this snapper is interested in. Any + * snap that occurs will be to the nearest "interesting" thing (e.g. a + * grid or guide line) + * + * \param t Point type. + * \param p Point to snap (desktop coordinates). + * \param it Item that should not be snapped to. + * \return Snapped point. + */ + +Inkscape::SnappedPoint Inkscape::Snapper::freeSnap(PointType t, + NR::Point const &p, + SPItem const *it) const +{ + std::list lit; + lit.push_back(it); + return freeSnap(t, p, lit); +} + + +/** + * Try to snap a point to whatever this snapper is interested in. Any + * snap that occurs will be to the nearest "interesting" thing (e.g. a + * grid or guide line) + * + * \param t Point type. + * \param p Point to snap (desktop coordinates). + * \param it Items that should not be snapped to. + * \return Snapped point. + */ + +Inkscape::SnappedPoint Inkscape::Snapper::freeSnap(PointType t, + NR::Point const &p, + std::list const &it) const +{ + if (getSnapTo(t) == false) { + return SnappedPoint(p, NR_HUGE); + } + + return _doFreeSnap(p, it); +} + + + + +/** + * Try to snap a point to whatever this snapper is interested in, where + * the snap point is constrained to lie along a specified vector from the + * original point. + * + * \param p Point to snap (desktop coordinates). + * \param c Vector to constrain the snap to. + * \param it Items that should not be snapped to. + * \return Snapped point. + */ + +Inkscape::SnappedPoint Inkscape::Snapper::constrainedSnap(PointType t, + NR::Point const &p, + NR::Point const &c, + SPItem const *it) const +{ + std::list lit; + lit.push_back(it); + return constrainedSnap(t, p, c, lit); +} + + +/** + * Try to snap a point to whatever this snapper is interested in, where + * the snap point is constrained to lie along a specified vector from the + * original point. + * + * \param p Point to snap (desktop coordinates). + * \param c Vector to constrain the snap to. + * \param it Items that should not be snapped to. + * \return Snapped point. + */ + +Inkscape::SnappedPoint Inkscape::Snapper::constrainedSnap(PointType t, + NR::Point const &p, + NR::Point const &c, + std::list const &it) const +{ + if (getSnapTo(t) == false) { + return SnappedPoint(p, NR_HUGE); + } + + return _doConstrainedSnap(p, c, it); +} + +/* + 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/snapper.h b/src/snapper.h new file mode 100644 index 000000000..4c71be7f2 --- /dev/null +++ b/src/snapper.h @@ -0,0 +1,113 @@ +#ifndef SEEN_SNAPPER_H +#define SEEN_SNAPPER_H + +/** + * \file src/snapper.h + * \brief Snapper class. + * + * Authors: + * Carl Hetherington + * + * Released under GNU GPL, read the file 'COPYING' for more information. + */ + +#include +#include +#include "libnr/nr-coord.h" +#include "libnr/nr-point.h" +#include "snapped-point.h" + +struct SPNamedView; +struct SPItem; + +namespace Inkscape +{ + +/// Parent for classes that can snap points to something +class Snapper +{ +public: + Snapper(SPNamedView const *nv, NR::Coord const d); + virtual ~Snapper() {} + + /// Point types to snap. + typedef int PointType; + static const PointType SNAP_POINT; + static const PointType BBOX_POINT; + + typedef std::pair PointWithType; + + void setSnapTo(PointType t, bool s); + void setDistance(NR::Coord d); + + bool getSnapTo(PointType t) const; + NR::Coord getDistance() const; + + bool willSnapSomething() const; + + SnappedPoint freeSnap(PointType t, + NR::Point const &p, + SPItem const *it) const; + + SnappedPoint freeSnap(PointType t, + NR::Point const &p, + std::list const &it) const; + + SnappedPoint constrainedSnap(PointType t, + NR::Point const &p, + NR::Point const &c, + SPItem const *it) const; + + SnappedPoint constrainedSnap(PointType t, + NR::Point const &p, + NR::Point const &c, + std::list const &it) const; +protected: + SPNamedView const *_named_view; + +private: + + /** + * Try to snap a point to whatever this snapper is interested in. Any + * snap that occurs will be to the nearest "interesting" thing (e.g. a + * grid or guide line) + * + * \param p Point to snap (desktop coordinates). + * \param it Items that should not be snapped to. + * \return Snapped point. + */ + virtual SnappedPoint _doFreeSnap(NR::Point const &p, + std::list const &it) const = 0; + + /** + * Try to snap a point to whatever this snapper is interested in, where + * the snap point is constrained to lie along a specified vector from the + * original point. + * + * \param p Point to snap (desktop coordinates). + * \param c Vector to constrain the snap to. + * \param it Items that should not be snapped to. + * \return Snapped point. + */ + virtual SnappedPoint _doConstrainedSnap(NR::Point const &p, + NR::Point const &c, + std::list const &it) const = 0; + + NR::Coord _distance; ///< snap distance (desktop coordinates) + int _snap_to; ///< bitmap of point types that we will snap to +}; + +} + +#endif /* !SEEN_SNAPPER_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 : diff --git a/src/sp-anchor.cpp b/src/sp-anchor.cpp new file mode 100644 index 000000000..5d61d543b --- /dev/null +++ b/src/sp-anchor.cpp @@ -0,0 +1,220 @@ +#define __SP_ANCHOR_C__ + +/* + * SVG element implementation + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define noSP_ANCHOR_VERBOSE + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include "xml/quote.h" +#include "xml/repr.h" +#include "attributes.h" +#include "sp-anchor.h" +#include "ui/view/view.h" + +static void sp_anchor_class_init(SPAnchorClass *ac); +static void sp_anchor_init(SPAnchor *anchor); + +static void sp_anchor_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); +static void sp_anchor_release(SPObject *object); +static void sp_anchor_set(SPObject *object, unsigned int key, const gchar *value); +static Inkscape::XML::Node *sp_anchor_write(SPObject *object, Inkscape::XML::Node *repr, guint flags); + +static gchar *sp_anchor_description(SPItem *item); +static gint sp_anchor_event(SPItem *item, SPEvent *event); + +static SPGroupClass *parent_class; + +GType sp_anchor_get_type(void) +{ + static GType type = 0; + + if (!type) { + GTypeInfo info = { + sizeof(SPAnchorClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_anchor_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof(SPAnchor), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_anchor_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_GROUP, "SPAnchor", &info, (GTypeFlags) 0); + } + + return type; +} + +static void sp_anchor_class_init(SPAnchorClass *ac) +{ + SPObjectClass *sp_object_class = (SPObjectClass *) ac; + SPItemClass *item_class = (SPItemClass *) ac; + + parent_class = (SPGroupClass *) g_type_class_ref(SP_TYPE_GROUP); + + sp_object_class->build = sp_anchor_build; + sp_object_class->release = sp_anchor_release; + sp_object_class->set = sp_anchor_set; + sp_object_class->write = sp_anchor_write; + + item_class->description = sp_anchor_description; + item_class->event = sp_anchor_event; +} + +static void sp_anchor_init(SPAnchor *anchor) +{ + anchor->href = NULL; +} + +static void sp_anchor_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + if (((SPObjectClass *) (parent_class))->build) { + ((SPObjectClass *) (parent_class))->build(object, document, repr); + } + + sp_object_read_attr(object, "xlink:type"); + sp_object_read_attr(object, "xlink:role"); + sp_object_read_attr(object, "xlink:arcrole"); + sp_object_read_attr(object, "xlink:title"); + sp_object_read_attr(object, "xlink:show"); + sp_object_read_attr(object, "xlink:actuate"); + sp_object_read_attr(object, "xlink:href"); + sp_object_read_attr(object, "target"); +} + +static void sp_anchor_release(SPObject *object) +{ + SPAnchor *anchor = SP_ANCHOR(object); + + if (anchor->href) { + g_free(anchor->href); + anchor->href = NULL; + } + + if (((SPObjectClass *) parent_class)->release) { + ((SPObjectClass *) parent_class)->release(object); + } +} + +static void sp_anchor_set(SPObject *object, unsigned int key, const gchar *value) +{ + SPAnchor *anchor = SP_ANCHOR(object); + + switch (key) { + case SP_ATTR_XLINK_HREF: + g_free(anchor->href); + anchor->href = g_strdup(value); + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_XLINK_TYPE: + case SP_ATTR_XLINK_ROLE: + case SP_ATTR_XLINK_ARCROLE: + case SP_ATTR_XLINK_TITLE: + case SP_ATTR_XLINK_SHOW: + case SP_ATTR_XLINK_ACTUATE: + case SP_ATTR_TARGET: + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + default: + if (((SPObjectClass *) (parent_class))->set) { + ((SPObjectClass *) (parent_class))->set(object, key, value); + } + break; + } +} + + +#define COPY_ATTR(rd,rs,key) (rd)->setAttribute((key), rs->attribute(key)); + +static Inkscape::XML::Node *sp_anchor_write(SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + SPAnchor *anchor = SP_ANCHOR(object); + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = sp_repr_new("svg:a"); + } + + repr->setAttribute("xlink:href", anchor->href); + + if (repr != SP_OBJECT_REPR(object)) { + COPY_ATTR(repr, object->repr, "xlink:type"); + COPY_ATTR(repr, object->repr, "xlink:role"); + COPY_ATTR(repr, object->repr, "xlink:arcrole"); + COPY_ATTR(repr, object->repr, "xlink:title"); + COPY_ATTR(repr, object->repr, "xlink:show"); + COPY_ATTR(repr, object->repr, "xlink:actuate"); + COPY_ATTR(repr, object->repr, "target"); + } + + if (((SPObjectClass *) (parent_class))->write) { + ((SPObjectClass *) (parent_class))->write(object, repr, flags); + } + + return repr; +} + +static gchar *sp_anchor_description(SPItem *item) +{ + SPAnchor *anchor = SP_ANCHOR(item); + if (anchor->href) { + char *quoted_href = xml_quote_strdup(anchor->href); + char *ret = g_strdup_printf(_("Link to %s"), quoted_href); + g_free(quoted_href); + return ret; + } else { + return g_strdup (_("Link without URI")); + } +} + +/* fixme: We should forward event to appropriate container/view */ + +static gint sp_anchor_event(SPItem *item, SPEvent *event) +{ + SPAnchor *anchor = SP_ANCHOR(item); + + switch (event->type) { + case SP_EVENT_ACTIVATE: + if (anchor->href) { + g_print("Activated xlink:href=\"%s\"\n", anchor->href); + return TRUE; + } + break; + case SP_EVENT_MOUSEOVER: + (static_cast(event->data))->mouseover(); + break; + case SP_EVENT_MOUSEOUT: + (static_cast(event->data))->mouseout(); + break; + default: + break; + } + + return FALSE; +} + +/* + 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/sp-anchor.h b/src/sp-anchor.h new file mode 100644 index 000000000..3c6481d94 --- /dev/null +++ b/src/sp-anchor.h @@ -0,0 +1,34 @@ +#ifndef __SP_ANCHOR_H__ +#define __SP_ANCHOR_H__ + +/* + * SVG element implementation + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-item-group.h" + +#define SP_TYPE_ANCHOR (sp_anchor_get_type ()) +#define SP_ANCHOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_ANCHOR, SPAnchor)) +#define SP_ANCHOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_ANCHOR, SPAnchorClass)) +#define SP_IS_ANCHOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_ANCHOR)) +#define SP_IS_ANCHOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_ANCHOR)) + +struct SPAnchor : public SPGroup { + gchar *href; +}; + +struct SPAnchorClass { + SPGroupClass parent_class; +}; + +GType sp_anchor_get_type (void); + +#endif diff --git a/src/sp-animation.cpp b/src/sp-animation.cpp new file mode 100644 index 000000000..8a2a02bdc --- /dev/null +++ b/src/sp-animation.cpp @@ -0,0 +1,290 @@ +#define __SP_ANIMATION_C__ + +/** \file + * SVG implementation. + * + * N.B. This file is currently just a stub file with no meaningful implementation. + */ +/* + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-animation.h" + +#if 0 +/* Feel free to remove this function and its calls. */ +static void +log_set_attr(char const *const classname, unsigned int const key, char const *const value) +{ + unsigned char const *const attr_name = sp_attribute_name(key); + if (value) { + g_print("%s: Set %s=%s\n", classname, attr_name, value); + } else { + g_print("%s: unset %s.\n", classname, attr_name); + } +} +#else +# define log_set_attr(_classname, _key, _value) static_cast(0) +#endif + +/* Animation base class */ + +static void sp_animation_class_init(SPAnimationClass *klass); +static void sp_animation_init(SPAnimation *animation); + +static void sp_animation_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); +static void sp_animation_release(SPObject *object); +static void sp_animation_set(SPObject *object, unsigned int key, gchar const *value); + +static SPObjectClass *animation_parent_class; + +GType +sp_animation_get_type(void) +{ + static GType animation_type = 0; + + if (!animation_type) { + GTypeInfo animation_info = { + sizeof(SPAnimationClass), + NULL, NULL, + (GClassInitFunc) sp_animation_class_init, + NULL, NULL, + sizeof(SPAnimation), + 16, + (GInstanceInitFunc) sp_animation_init, + NULL, /* value_table */ + }; + animation_type = g_type_register_static(SP_TYPE_OBJECT, "SPAnimation", &animation_info, (GTypeFlags)0); + } + return animation_type; +} + +static void +sp_animation_class_init(SPAnimationClass *klass) +{ + //GObjectClass *gobject_class = (GObjectClass *) klass; + SPObjectClass *sp_object_class = (SPObjectClass *) klass; + + animation_parent_class = (SPObjectClass*)g_type_class_peek_parent(klass); + + sp_object_class->build = sp_animation_build; + sp_object_class->release = sp_animation_release; + sp_object_class->set = sp_animation_set; +} + +static void +sp_animation_init(SPAnimation *animation) +{ +} + + +static void +sp_animation_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + if (((SPObjectClass *) animation_parent_class)->build) + ((SPObjectClass *) animation_parent_class)->build(object, document, repr); + + sp_object_read_attr(object, "xlink:href"); + sp_object_read_attr(object, "attributeName"); + sp_object_read_attr(object, "attributeType"); + sp_object_read_attr(object, "begin"); + sp_object_read_attr(object, "dur"); + sp_object_read_attr(object, "end"); + sp_object_read_attr(object, "min"); + sp_object_read_attr(object, "max"); + sp_object_read_attr(object, "restart"); + sp_object_read_attr(object, "repeatCount"); + sp_object_read_attr(object, "repeatDur"); + sp_object_read_attr(object, "fill"); +} + +static void +sp_animation_release(SPObject *object) +{ +} + +static void +sp_animation_set(SPObject *object, unsigned int key, gchar const *value) +{ + //SPAnimation *animation = SP_ANIMATION(object); + + log_set_attr("SPAnimation", key, value); + + if (((SPObjectClass *) animation_parent_class)->set) + ((SPObjectClass *) animation_parent_class)->set(object, key, value); +} + +/* Interpolated animation base class */ + +static void sp_ianimation_class_init(SPIAnimationClass *klass); +static void sp_ianimation_init(SPIAnimation *animation); + +static void sp_ianimation_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); +static void sp_ianimation_release(SPObject *object); +static void sp_ianimation_set(SPObject *object, unsigned int key, gchar const *value); + +static SPObjectClass *ianimation_parent_class; + +GType +sp_ianimation_get_type(void) +{ + static GType type = 0; + + if (!type) { + GTypeInfo info = { + sizeof(SPIAnimationClass), + NULL, NULL, + (GClassInitFunc) sp_ianimation_class_init, + NULL, NULL, + sizeof(SPIAnimation), + 16, + (GInstanceInitFunc) sp_ianimation_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_OBJECT, "SPIAnimation", &info, (GTypeFlags)0); + } + return type; +} + +static void +sp_ianimation_class_init(SPIAnimationClass *klass) +{ + //GObjectClass *gobject_class = (GObjectClass *) klass; + SPObjectClass *sp_object_class = (SPObjectClass *) klass; + + ianimation_parent_class = (SPObjectClass*)g_type_class_peek_parent(klass); + + sp_object_class->build = sp_ianimation_build; + sp_object_class->release = sp_ianimation_release; + sp_object_class->set = sp_ianimation_set; +} + +static void +sp_ianimation_init(SPIAnimation *animation) +{ +} + + +static void +sp_ianimation_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + if (((SPObjectClass *) ianimation_parent_class)->build) + ((SPObjectClass *) ianimation_parent_class)->build(object, document, repr); + + sp_object_read_attr(object, "calcMode"); + sp_object_read_attr(object, "values"); + sp_object_read_attr(object, "keyTimes"); + sp_object_read_attr(object, "keySplines"); + sp_object_read_attr(object, "from"); + sp_object_read_attr(object, "to"); + sp_object_read_attr(object, "by"); + sp_object_read_attr(object, "additive"); + sp_object_read_attr(object, "accumulate"); +} + +static void +sp_ianimation_release(SPObject *object) +{ +} + +static void +sp_ianimation_set(SPObject *object, unsigned int key, gchar const *value) +{ + //SPIAnimation *ianimation = SP_IANIMATION(object); + + log_set_attr("SPIAnimation", key, value); + + if (((SPObjectClass *) ianimation_parent_class)->set) + ((SPObjectClass *) ianimation_parent_class)->set(object, key, value); +} + +/* SVG */ + +static void sp_animate_class_init(SPAnimateClass *klass); +static void sp_animate_init(SPAnimate *animate); + +static void sp_animate_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); +static void sp_animate_release(SPObject *object); +static void sp_animate_set(SPObject *object, unsigned int key, gchar const *value); + +static SPIAnimationClass *animate_parent_class; + +GType +sp_animate_get_type(void) +{ + static GType type = 0; + + if (!type) { + GTypeInfo info = { + sizeof(SPAnimateClass), + NULL, NULL, + (GClassInitFunc) sp_animate_class_init, + NULL, NULL, + sizeof(SPAnimate), + 16, + (GInstanceInitFunc) sp_animate_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_IANIMATION, "SPAnimate", &info, (GTypeFlags)0); + } + return type; +} + +static void +sp_animate_class_init(SPAnimateClass *klass) +{ + //GObjectClass *gobject_class = (GObjectClass *) klass; + SPObjectClass *sp_object_class = (SPObjectClass *) klass; + + animate_parent_class = (SPIAnimationClass*)g_type_class_peek_parent(klass); + + sp_object_class->build = sp_animate_build; + sp_object_class->release = sp_animate_release; + sp_object_class->set = sp_animate_set; +} + +static void +sp_animate_init(SPAnimate *animate) +{ +} + + +static void +sp_animate_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + if (((SPObjectClass *) animate_parent_class)->build) + ((SPObjectClass *) animate_parent_class)->build(object, document, repr); +} + +static void +sp_animate_release(SPObject *object) +{ +} + +static void +sp_animate_set(SPObject *object, unsigned int key, gchar const *value) +{ + //SPAnimate *animate = SP_ANIMATE(object); + + log_set_attr("SPAnimate", key, value); + + if (((SPObjectClass *) animate_parent_class)->set) + ((SPObjectClass *) animate_parent_class)->set(object, key, value); +} + + +/* + 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 : diff --git a/src/sp-animation.h b/src/sp-animation.h new file mode 100644 index 000000000..407419f5b --- /dev/null +++ b/src/sp-animation.h @@ -0,0 +1,75 @@ +#ifndef __SP_ANIMATION_H__ +#define __SP_ANIMATION_H__ + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-object.h" + + + +/* Animation base class */ + +#define SP_TYPE_ANIMATION (sp_animation_get_type ()) +#define SP_ANIMATION(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), SP_TYPE_ANIMATION, SPAnimation)) +#define SP_IS_ANIMATION(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), SP_TYPE_ANIMATION)) + +class SPAnimation; +class SPAnimationClass; + +struct SPAnimation : public SPObject { +}; + +struct SPAnimationClass { + SPObjectClass parent_class; +}; + +GType sp_animation_get_type (void); + +/* Interpolated animation base class */ + +#define SP_TYPE_IANIMATION (sp_ianimation_get_type ()) +#define SP_IANIMATION(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), SP_TYPE_IANIMATION, SPIAnimation)) +#define SP_IS_IANIMATION(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), SP_TYPE_IANIMATION)) + +class SPIAnimation; +class SPIAnimationClass; + +struct SPIAnimation : public SPAnimation { +}; + +struct SPIAnimationClass { + SPAnimationClass parent_class; +}; + +GType sp_ianimation_get_type (void); + +/* SVG */ + +#define SP_TYPE_ANIMATE (sp_animate_get_type ()) +#define SP_ANIMATE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), SP_TYPE_ANIMATE, SPAnimate)) +#define SP_IS_ANIMATE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), SP_TYPE_ANIMATE)) + +class SPAnimate; +class SPAnimateClass; + +struct SPAnimate : public SPIAnimation { +}; + +struct SPAnimateClass { + SPIAnimationClass parent_class; +}; + +GType sp_animate_get_type (void); + + + +#endif diff --git a/src/sp-clippath.cpp b/src/sp-clippath.cpp new file mode 100644 index 000000000..d8d2acc9f --- /dev/null +++ b/src/sp-clippath.cpp @@ -0,0 +1,408 @@ +#define __SP_CLIPPATH_C__ + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 authors + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + + +#include "display/nr-arena.h" +#include "display/nr-arena-group.h" +#include "xml/repr.h" + +#include "enums.h" +#include "attributes.h" +#include "document.h" +#include "sp-item.h" + +#include "sp-clippath.h" + +struct SPClipPathView { + SPClipPathView *next; + unsigned int key; + NRArenaItem *arenaitem; + NRRect bbox; +}; + +static void sp_clippath_class_init(SPClipPathClass *klass); +static void sp_clippath_init(SPClipPath *clippath); + +static void sp_clippath_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); +static void sp_clippath_release(SPObject * object); +static void sp_clippath_set(SPObject *object, unsigned int key, gchar const *value); +static void sp_clippath_child_added(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref); +static void sp_clippath_update(SPObject *object, SPCtx *ctx, guint flags); +static void sp_clippath_modified(SPObject *object, guint flags); +static Inkscape::XML::Node *sp_clippath_write(SPObject *object, Inkscape::XML::Node *repr, guint flags); + +SPClipPathView *sp_clippath_view_new_prepend(SPClipPathView *list, unsigned int key, NRArenaItem *arenaitem); +SPClipPathView *sp_clippath_view_list_remove(SPClipPathView *list, SPClipPathView *view); + +static SPObjectGroupClass *parent_class; + +GType +sp_clippath_get_type(void) +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPClipPathClass), + NULL, NULL, + (GClassInitFunc) sp_clippath_class_init, + NULL, NULL, + sizeof(SPClipPath), + 16, + (GInstanceInitFunc) sp_clippath_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_OBJECTGROUP, "SPClipPath", &info, (GTypeFlags)0); + } + return type; +} + +static void +sp_clippath_class_init(SPClipPathClass *klass) +{ + GObjectClass *gobject_class; + SPObjectClass *sp_object_class; + + gobject_class = (GObjectClass *) klass; + sp_object_class = (SPObjectClass *) klass; + + parent_class = (SPObjectGroupClass*)g_type_class_ref(SP_TYPE_OBJECTGROUP); + + sp_object_class->build = sp_clippath_build; + sp_object_class->release = sp_clippath_release; + sp_object_class->set = sp_clippath_set; + sp_object_class->child_added = sp_clippath_child_added; + sp_object_class->update = sp_clippath_update; + sp_object_class->modified = sp_clippath_modified; + sp_object_class->write = sp_clippath_write; +} + +static void +sp_clippath_init(SPClipPath *cp) +{ + cp->clipPathUnits_set = FALSE; + cp->clipPathUnits = SP_CONTENT_UNITS_USERSPACEONUSE; + + cp->display = NULL; +} + +static void +sp_clippath_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + SPClipPath *cp; + + cp = SP_CLIPPATH(object); + + if (((SPObjectClass *) parent_class)->build) + ((SPObjectClass *) parent_class)->build(object, document, repr); + + sp_object_read_attr(object, "clipPathUnits"); + + /* Register ourselves */ + sp_document_add_resource(document, "clipPath", object); +} + +static void +sp_clippath_release(SPObject * object) +{ + SPClipPath *cp; + + cp = SP_CLIPPATH(object); + + if (SP_OBJECT_DOCUMENT(object)) { + /* Unregister ourselves */ + sp_document_remove_resource(SP_OBJECT_DOCUMENT(object), "clipPath", object); + } + + while (cp->display) { + /* We simply unref and let item to manage this in handler */ + cp->display = sp_clippath_view_list_remove(cp->display, cp->display); + } + + if (((SPObjectClass *) (parent_class))->release) + ((SPObjectClass *) parent_class)->release(object); +} + +static void +sp_clippath_set(SPObject *object, unsigned int key, gchar const *value) +{ + SPClipPath *cp; + + cp = SP_CLIPPATH(object); + + switch (key) { + case SP_ATTR_CLIPPATHUNITS: + cp->clipPathUnits = SP_CONTENT_UNITS_USERSPACEONUSE; + cp->clipPathUnits_set = FALSE; + if (value) { + if (!strcmp(value, "userSpaceOnUse")) { + cp->clipPathUnits_set = TRUE; + } else if (!strcmp(value, "objectBoundingBox")) { + cp->clipPathUnits = SP_CONTENT_UNITS_OBJECTBOUNDINGBOX; + cp->clipPathUnits_set = TRUE; + } + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + default: + if (((SPObjectClass *) parent_class)->set) + ((SPObjectClass *) parent_class)->set(object, key, value); + break; + } +} + +static void +sp_clippath_child_added(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref) +{ + SPClipPath *cp; + SPObject *ochild; + + cp = SP_CLIPPATH(object); + + /* Invoke SPObjectGroup implementation */ + ((SPObjectClass *) (parent_class))->child_added(object, child, ref); + + /* Show new object */ + ochild = SP_OBJECT_DOCUMENT(object)->getObjectByRepr(child); + if (SP_IS_ITEM(ochild)) { + SPClipPathView *v; + for (v = cp->display; v != NULL; v = v->next) { + NRArenaItem *ac; + ac = sp_item_invoke_show(SP_ITEM(ochild), NR_ARENA_ITEM_ARENA(v->arenaitem), v->key, SP_ITEM_REFERENCE_FLAGS); + if (ac) { + nr_arena_item_add_child(v->arenaitem, ac, NULL); + nr_arena_item_unref(ac); + } + } + } +} + +static void +sp_clippath_update(SPObject *object, SPCtx *ctx, guint flags) +{ + SPObjectGroup *og; + SPClipPath *cp; + SPObject *child; + SPClipPathView *v; + GSList *l; + + og = SP_OBJECTGROUP(object); + cp = SP_CLIPPATH(object); + + if (flags & SP_OBJECT_MODIFIED_FLAG) flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + flags &= SP_OBJECT_MODIFIED_CASCADE; + + l = NULL; + for (child = sp_object_first_child(SP_OBJECT(og)); child != NULL; child = SP_OBJECT_NEXT(child)) { + g_object_ref(G_OBJECT(child)); + l = g_slist_prepend(l, child); + } + l = g_slist_reverse(l); + while (l) { + child = SP_OBJECT(l->data); + l = g_slist_remove(l, child); + if (flags || (child->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->updateDisplay(ctx, flags); + } + g_object_unref(G_OBJECT(child)); + } + + for (v = cp->display; v != NULL; v = v->next) { + if (cp->clipPathUnits == SP_CONTENT_UNITS_OBJECTBOUNDINGBOX) { + NRMatrix t; + nr_matrix_set_scale(&t, v->bbox.x1 - v->bbox.x0, v->bbox.y1 - v->bbox.y0); + t.c[4] = v->bbox.x0; + t.c[5] = v->bbox.y0; + nr_arena_group_set_child_transform(NR_ARENA_GROUP(v->arenaitem), &t); + } else { + nr_arena_group_set_child_transform(NR_ARENA_GROUP(v->arenaitem), NULL); + } + } +} + +static void +sp_clippath_modified(SPObject *object, guint flags) +{ + SPObjectGroup *og; + SPClipPath *cp; + SPObject *child; + GSList *l; + + og = SP_OBJECTGROUP(object); + cp = SP_CLIPPATH(object); + + if (flags & SP_OBJECT_MODIFIED_FLAG) flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + flags &= SP_OBJECT_MODIFIED_CASCADE; + + l = NULL; + for (child = sp_object_first_child(SP_OBJECT(og)); child != NULL; child = SP_OBJECT_NEXT(child)) { + g_object_ref(G_OBJECT(child)); + l = g_slist_prepend(l, child); + } + l = g_slist_reverse(l); + while (l) { + child = SP_OBJECT(l->data); + l = g_slist_remove(l, child); + if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->emitModified(flags); + } + g_object_unref(G_OBJECT(child)); + } +} + +static Inkscape::XML::Node * +sp_clippath_write(SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + SPClipPath *cp; + + cp = SP_CLIPPATH(object); + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = sp_repr_new("svg:clipPath"); + } + + if (((SPObjectClass *) (parent_class))->write) + ((SPObjectClass *) (parent_class))->write(object, repr, flags); + + return repr; +} + +NRArenaItem * +sp_clippath_show(SPClipPath *cp, NRArena *arena, unsigned int key) +{ + NRArenaItem *ai, *ac; + SPObject *child; + + g_return_val_if_fail(cp != NULL, NULL); + g_return_val_if_fail(SP_IS_CLIPPATH(cp), NULL); + g_return_val_if_fail(arena != NULL, NULL); + g_return_val_if_fail(NR_IS_ARENA(arena), NULL); + + ai = NRArenaGroup::create(arena); + cp->display = sp_clippath_view_new_prepend(cp->display, key, ai); + + for (child = sp_object_first_child(SP_OBJECT(cp)) ; child != NULL; child = SP_OBJECT_NEXT(child)) { + if (SP_IS_ITEM(child)) { + ac = sp_item_invoke_show(SP_ITEM(child), arena, key, SP_ITEM_REFERENCE_FLAGS); + if (ac) { + /* The order is not important in clippath */ + nr_arena_item_add_child(ai, ac, NULL); + nr_arena_item_unref(ac); + } + } + } + + if (cp->clipPathUnits == SP_CONTENT_UNITS_OBJECTBOUNDINGBOX) { + NRMatrix t; + nr_matrix_set_scale(&t, cp->display->bbox.x1 - cp->display->bbox.x0, cp->display->bbox.y1 - cp->display->bbox.y0); + t.c[4] = cp->display->bbox.x0; + t.c[5] = cp->display->bbox.y0; + nr_arena_group_set_child_transform(NR_ARENA_GROUP(ai), &t); + } + + return ai; +} + +void +sp_clippath_hide(SPClipPath *cp, unsigned int key) +{ + SPClipPathView *v; + SPObject *child; + + g_return_if_fail(cp != NULL); + g_return_if_fail(SP_IS_CLIPPATH(cp)); + + for (child = sp_object_first_child(SP_OBJECT(cp)) ; child != NULL; child = SP_OBJECT_NEXT(child)) { + if (SP_IS_ITEM(child)) { + sp_item_invoke_hide(SP_ITEM(child), key); + } + } + + for (v = cp->display; v != NULL; v = v->next) { + if (v->key == key) { + /* We simply unref and let item to manage this in handler */ + cp->display = sp_clippath_view_list_remove(cp->display, v); + return; + } + } + + g_assert_not_reached(); +} + +void +sp_clippath_set_bbox(SPClipPath *cp, unsigned int key, NRRect *bbox) +{ + SPClipPathView *v; + + for (v = cp->display; v != NULL; v = v->next) { + if (v->key == key) { + if (!NR_DF_TEST_CLOSE(v->bbox.x0, bbox->x0, NR_EPSILON) || + !NR_DF_TEST_CLOSE(v->bbox.y0, bbox->y0, NR_EPSILON) || + !NR_DF_TEST_CLOSE(v->bbox.x1, bbox->x1, NR_EPSILON) || + !NR_DF_TEST_CLOSE(v->bbox.y1, bbox->y1, NR_EPSILON)) { + v->bbox = *bbox; + SP_OBJECT(cp)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + } +} + +/* ClipPath views */ + +SPClipPathView * +sp_clippath_view_new_prepend(SPClipPathView *list, unsigned int key, NRArenaItem *arenaitem) +{ + SPClipPathView *new_path_view; + + new_path_view = g_new(SPClipPathView, 1); + + new_path_view->next = list; + new_path_view->key = key; + new_path_view->arenaitem = nr_arena_item_ref(arenaitem); + new_path_view->bbox.x0 = new_path_view->bbox.x1 = 0.0; + new_path_view->bbox.y0 = new_path_view->bbox.y1 = 0.0; + + return new_path_view; +} + +SPClipPathView * +sp_clippath_view_list_remove(SPClipPathView *list, SPClipPathView *view) +{ + if (view == list) { + list = list->next; + } else { + SPClipPathView *prev; + prev = list; + while (prev->next != view) prev = prev->next; + prev->next = view->next; + } + + nr_arena_item_unref(view->arenaitem); + g_free(view); + + return list; +} + + +/* + 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 : diff --git a/src/sp-clippath.h b/src/sp-clippath.h new file mode 100644 index 000000000..1f5571cc1 --- /dev/null +++ b/src/sp-clippath.h @@ -0,0 +1,61 @@ +#ifndef __SP_CLIPPATH_H__ +#define __SP_CLIPPATH_H__ + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 authors + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define SP_TYPE_CLIPPATH (sp_clippath_get_type ()) +#define SP_CLIPPATH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_CLIPPATH, SPClipPath)) +#define SP_CLIPPATH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_CLIPPATH, SPClipPathClass)) +#define SP_IS_CLIPPATH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_CLIPPATH)) +#define SP_IS_CLIPPATH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_CLIPPATH)) + +class SPClipPathView; + +#include "display/nr-arena-forward.h" +#include "sp-object-group.h" +#include "uri-references.h" +#include + +struct SPClipPath : public SPObjectGroup { + class Reference; + + unsigned int clipPathUnits_set : 1; + unsigned int clipPathUnits : 1; + + SPClipPathView *display; +}; + +struct SPClipPathClass { + SPObjectGroupClass parent_class; +}; + +GType sp_clippath_get_type (void); + +class SPClipPathReference : public Inkscape::URIReference { +public: + SPClipPathReference(SPObject *obj) : URIReference(obj) {} + SPClipPath *getObject() const { + return (SPClipPath *)URIReference::getObject(); + } +protected: + virtual bool _acceptObject(SPObject *obj) const { + return SP_IS_CLIPPATH(obj); + } +}; + +NRArenaItem *sp_clippath_show(SPClipPath *cp, NRArena *arena, unsigned int key); +void sp_clippath_hide(SPClipPath *cp, unsigned int key); + +void sp_clippath_set_bbox(SPClipPath *cp, unsigned int key, NRRect *bbox); + +#endif diff --git a/src/sp-conn-end-pair.cpp b/src/sp-conn-end-pair.cpp new file mode 100644 index 000000000..ff1005a16 --- /dev/null +++ b/src/sp-conn-end-pair.cpp @@ -0,0 +1,275 @@ +/* + * A class for handling connector endpoint movement and libavoid interaction. + * + * Authors: + * Peter Moulder + * Michael Wybrow + * + * * Copyright (C) 2004-2005 Monash University + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "attributes.h" +#include "sp-conn-end.h" +#include "uri.h" +#include "display/curve.h" +#include "xml/repr.h" +#include "sp-path.h" +#include "libavoid/vertices.h" + + +SPConnEndPair::SPConnEndPair(SPPath *const owner) + : _invalid_path_connection() + , _path(owner) + , _connRef(NULL) + , _connType(SP_CONNECTOR_NOAVOID) + , _transformed_connection() +{ + for (unsigned handle_ix = 0; handle_ix <= 1; ++handle_ix) { + this->_connEnd[handle_ix] = new SPConnEnd(SP_OBJECT(owner)); + this->_connEnd[handle_ix]->_changed_connection + = this->_connEnd[handle_ix]->ref.changedSignal() + .connect(sigc::bind(sigc::ptr_fun(sp_conn_end_href_changed), + this->_connEnd[handle_ix], owner, handle_ix)); + } +} + +SPConnEndPair::~SPConnEndPair() +{ + for (unsigned handle_ix = 0; handle_ix < 2; ++handle_ix) { + delete this->_connEnd[handle_ix]; + this->_connEnd[handle_ix] = NULL; + } + if (_connRef) { + _connRef->removeFromGraph(); + delete _connRef; + _connRef = NULL; + } + + _invalid_path_connection.disconnect(); + _transformed_connection.disconnect(); +} + +void +SPConnEndPair::release() +{ + for (unsigned handle_ix = 0; handle_ix < 2; ++handle_ix) { + this->_connEnd[handle_ix]->_changed_connection.disconnect(); + this->_connEnd[handle_ix]->_delete_connection.disconnect(); + this->_connEnd[handle_ix]->_transformed_connection.disconnect(); + g_free(this->_connEnd[handle_ix]->href); + this->_connEnd[handle_ix]->href = NULL; + this->_connEnd[handle_ix]->ref.detach(); + } +} + +void +sp_conn_end_pair_build(SPObject *object) +{ + sp_object_read_attr(object, "inkscape:connector-type"); + sp_object_read_attr(object, "inkscape:connection-start"); + sp_object_read_attr(object, "inkscape:connection-end"); +} + + +static void +avoid_conn_move(NR::Matrix const *mp, SPItem *moved_item) +{ + // Detach from objects if attached. + sp_conn_end_detach(moved_item, 0); + sp_conn_end_detach(moved_item, 1); + // Reroute connector + SPPath *path = SP_PATH(moved_item); + path->connEndPair.makePathInvalid(); + sp_conn_adjust_invalid_path(path); +} + + +void +SPConnEndPair::setAttr(unsigned const key, gchar const *const value) +{ + if (key == SP_ATTR_CONNECTOR_TYPE) { + if (value && (strcmp(value, "polyline") == 0)) { + _connType = SP_CONNECTOR_POLYLINE; + + GQuark itemID = g_quark_from_string(SP_OBJECT(_path)->id); + _connRef = new Avoid::ConnRef(itemID); + _invalid_path_connection = connectInvalidPath( + sigc::ptr_fun(&sp_conn_adjust_invalid_path)); + _transformed_connection = _path->connectTransformed( + sigc::ptr_fun(&avoid_conn_move)); + } + else { + _connType = SP_CONNECTOR_NOAVOID; + + if (_connRef) { + _connRef->removeFromGraph(); + delete _connRef; + _connRef = NULL; + _invalid_path_connection.disconnect(); + _transformed_connection.disconnect(); + } + } + return; + + } + + unsigned const handle_ix = key - SP_ATTR_CONNECTION_START; + g_assert( handle_ix <= 1 ); + this->_connEnd[handle_ix]->setAttacherHref(value); +} + +void +SPConnEndPair::writeRepr(Inkscape::XML::Node *const repr) const +{ + for (unsigned handle_ix = 0; handle_ix < 2; ++handle_ix) { + if (this->_connEnd[handle_ix]->ref.getURI()) { + char const * const attr_strs[] = {"inkscape:connection-start", + "inkscape:connection-end"}; + gchar *uri_string = this->_connEnd[handle_ix]->ref.getURI()->toString(); + repr->setAttribute(attr_strs[handle_ix], uri_string); + g_free(uri_string); + } + } +} + +void +SPConnEndPair::getAttachedItems(SPItem *h2attItem[2]) const { + for (unsigned h = 0; h < 2; ++h) { + h2attItem[h] = this->_connEnd[h]->ref.getObject(); + } +} + +void +SPConnEndPair::getEndpoints(NR::Point endPts[]) const { + SPCurve *curve = _path->curve; + SPItem *h2attItem[2]; + getAttachedItems(h2attItem); + + for (unsigned h = 0; h < 2; ++h) { + if ( h2attItem[h] ) { + NR::Rect const bbox = h2attItem[h]->invokeBbox(sp_item_i2doc_affine(h2attItem[h])); + endPts[h] = bbox.midpoint(); + } + else + { + if (h == 0) { + endPts[h] = sp_curve_first_point(curve); + } + else { + endPts[h] = sp_curve_last_point(curve); + } + } + } +} + +sigc::connection +SPConnEndPair::connectInvalidPath(sigc::slot slot) +{ + return _invalid_path_signal.connect(slot); +} + +static void emitPathInvalidationNotification(void *ptr) +{ + // We emit a signal here rather than just calling the reroute function + // since this allows all the movement action computation to happen, + // then all connectors (that require it) will be rerouted. Otherwise, + // one connector could get rerouted several times as a result of + // dragging a couple of shapes. + + SPPath *path = SP_PATH(ptr); + path->connEndPair._invalid_path_signal.emit(path); +} + +void +SPConnEndPair::rerouteFromManipulation(void) +{ + _connRef->makePathInvalid(); + sp_conn_adjust_path(_path); +} + +void +SPConnEndPair::reroute(void) +{ + sp_conn_adjust_path(_path); +} + +// Called from sp_path_update to initialise the endpoints. +void +SPConnEndPair::update(void) +{ + if (_connType != SP_CONNECTOR_NOAVOID) { + g_assert(_connRef != NULL); + if (!(_connRef->isInitialised())) { + NR::Point endPt[2]; + getEndpoints(endPt); + + Avoid::Point src = { endPt[0][NR::X], endPt[0][NR::Y] }; + Avoid::Point dst = { endPt[1][NR::X], endPt[1][NR::Y] }; + + _connRef->lateSetup(src, dst); + _connRef->setCallback(&emitPathInvalidationNotification, _path); + } + } +} + + +bool +SPConnEndPair::isAutoRoutingConn(void) +{ + if (_connType != SP_CONNECTOR_NOAVOID) { + return true; + } + return false; +} + +void +SPConnEndPair::makePathInvalid(void) +{ + _connRef->makePathInvalid(); +} + +void +SPConnEndPair::reroutePath(void) +{ + if (!isAutoRoutingConn()) { + // Do nothing + return; + } + + SPCurve *curve = _path->curve; + + NR::Point endPt[2]; + getEndpoints(endPt); + + Avoid::Point src = { endPt[0][NR::X], endPt[0][NR::Y] }; + Avoid::Point dst = { endPt[1][NR::X], endPt[1][NR::Y] }; + + _connRef->updateEndPoint(Avoid::VertID::src, src); + _connRef->updateEndPoint(Avoid::VertID::tar, dst); + + _connRef->generatePath(src, dst); + + Avoid::PolyLine route = _connRef->route(); + _connRef->calcRouteDist(); + + sp_curve_reset(curve); + sp_curve_moveto(curve, endPt[0]); + + for (int i = 1; i < route.pn; ++i) { + NR::Point p(route.ps[i].x, route.ps[i].y); + sp_curve_lineto(curve, 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 : diff --git a/src/sp-conn-end-pair.h b/src/sp-conn-end-pair.h new file mode 100644 index 000000000..9807b4f14 --- /dev/null +++ b/src/sp-conn-end-pair.h @@ -0,0 +1,91 @@ +#ifndef SEEN_SP_CONN_END_PAIR +#define SEEN_SP_CONN_END_PAIR + +/* + * A class for handling connector endpoint movement and libavoid interaction. + * + * Authors: + * Peter Moulder + * + * * Copyright (C) 2004 Monash University + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ +#include + +#include "forward.h" +#include "libnr/nr-point.h" +#include +#include +#include +#include "libavoid/connector.h" + + +class SPConnEnd; +namespace Inkscape { +namespace XML { +class Node; +} +} + + +class SPConnEndPair { +public: + SPConnEndPair(SPPath *); + ~SPConnEndPair(); + void release(); + void setAttr(unsigned const key, gchar const *const value); + void writeRepr(Inkscape::XML::Node *const repr) const; + void getAttachedItems(SPItem *[2]) const; + void getEndpoints(NR::Point endPts[]) const; + void reroutePath(void); + void makePathInvalid(void); + void update(void); + bool isAutoRoutingConn(void); + void rerouteFromManipulation(void); + void reroute(void); + sigc::connection connectInvalidPath(sigc::slot slot); + + // A signal emited by a call back from libavoid. Used to let + // connectors know when they need to reroute themselves. + sigc::signal _invalid_path_signal; + // A sigc connection to listen for connector path invalidation. + sigc::connection _invalid_path_connection; + +private: + SPConnEnd *_connEnd[2]; + + SPPath *_path; + + // libavoid's internal representation of the item. + Avoid::ConnRef *_connRef; + + int _connType; + + // A sigc connection for transformed signal. + sigc::connection _transformed_connection; +}; + + +void sp_conn_end_pair_build(SPObject *object); + + +// _connType options: +enum { + SP_CONNECTOR_NOAVOID, // Basic connector - a straight line. + SP_CONNECTOR_POLYLINE // Object avoiding polyline. +}; + + +#endif /* !SEEN_SP_CONN_END_PAIR */ + +/* + 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/sp-conn-end.cpp b/src/sp-conn-end.cpp new file mode 100644 index 000000000..af8692358 --- /dev/null +++ b/src/sp-conn-end.cpp @@ -0,0 +1,298 @@ + +#include "display/curve.h" +#include "libnr/nr-matrix-div.h" +#include "libnr/nr-matrix-fns.h" +#include "xml/repr.h" +#include "sp-conn-end.h" +#include "sp-path.h" +#include "uri.h" +#include "document.h" + + +static void change_endpts(SPCurve *const curve, NR::Point const h2endPt[2]); +static NR::Point calc_bbox_conn_pt(NR::Rect const &bbox, NR::Point const &p); +static double signed_one(double const x); + +SPConnEnd::SPConnEnd(SPObject *const owner) : + ref(owner), + href(NULL), + _changed_connection(), + _delete_connection(), + _transformed_connection() +{ +} + +static SPObject const * +get_nearest_common_ancestor(SPObject const *const obj, SPItem const *const objs[2]) { + SPObject const *anc_sofar = obj; + for (unsigned i = 0; i < 2; ++i) { + if ( objs[i] != NULL ) { + anc_sofar = anc_sofar->nearestCommonAncestor(objs[i]); + } + } + return anc_sofar; +} + +static void +sp_conn_end_move_compensate(NR::Matrix const *mp, SPItem *moved_item, + SPPath *const path, + bool const updatePathRepr = true) +{ + // TODO: SPItem::invokeBbox gives the wrong result for some objects + // that have internal representations that are updated later + // by the sp_*_update functions, e.g., text. + sp_document_ensure_up_to_date(path->document); + + // Get the new route around obstacles. + path->connEndPair.reroutePath(); + + SPItem *h2attItem[2]; + path->connEndPair.getAttachedItems(h2attItem); + if ( !h2attItem[0] && !h2attItem[1] ) { + if (updatePathRepr) { + path->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + path->updateRepr(); + } + return; + } + + SPItem const *const path_item = SP_ITEM(path); + SPObject const *const ancestor = get_nearest_common_ancestor(path_item, h2attItem); + NR::Matrix const path2anc(i2anc_affine(path_item, ancestor)); + + if (h2attItem[0] != NULL && h2attItem[1] != NULL) { + /* Initial end-points: centre of attached object. */ + NR::Point h2endPt_icoordsys[2]; + NR::Matrix h2i2anc[2]; + NR::Rect h2bbox_icoordsys[2] = { + h2attItem[0]->invokeBbox(NR::identity()), + h2attItem[1]->invokeBbox(NR::identity()) + }; + NR::Point last_seg_endPt[2] = { + sp_curve_second_point(path->curve), + sp_curve_penultimate_point(path->curve) + }; + for (unsigned h = 0; h < 2; ++h) { + h2i2anc[h] = i2anc_affine(h2attItem[h], ancestor); + h2endPt_icoordsys[h] = h2bbox_icoordsys[h].midpoint(); + } + + // For each attached object, change the corresponding point to be + // on the edge of the bbox. + NR::Point h2endPt_pcoordsys[2]; + for (unsigned h = 0; h < 2; ++h) { + h2endPt_icoordsys[h] = calc_bbox_conn_pt(h2bbox_icoordsys[h], + ( last_seg_endPt[h] / h2i2anc[h] )); + h2endPt_pcoordsys[h] = h2endPt_icoordsys[h] * h2i2anc[h] / path2anc; + } + change_endpts(path->curve, h2endPt_pcoordsys); + } else { + // We leave the unattached endpoint where it is, and adjust the + // position of the attached endpoint to be on the edge of the bbox. + unsigned ind; + NR::Point other_endpt; + NR::Point last_seg_pt; + if (h2attItem[0] != NULL) { + other_endpt = sp_curve_last_point(path->curve); + last_seg_pt = sp_curve_second_point(path->curve); + ind = 0; + } + else { + other_endpt = sp_curve_first_point(path->curve); + last_seg_pt = sp_curve_penultimate_point(path->curve); + ind = 1; + } + NR::Point h2endPt_icoordsys[2]; + NR::Matrix h2i2anc; + + NR::Rect otherpt_rect = NR::Rect(other_endpt, other_endpt); + NR::Rect h2bbox_icoordsys[2] = { otherpt_rect, otherpt_rect }; + h2bbox_icoordsys[ind] = h2attItem[ind]->invokeBbox(NR::identity()); + + h2i2anc = i2anc_affine(h2attItem[ind], ancestor); + h2endPt_icoordsys[ind] = h2bbox_icoordsys[ind].midpoint(); + + h2endPt_icoordsys[!ind] = other_endpt; + + // For the attached object, change the corresponding point to be + // on the edge of the bbox. + NR::Point h2endPt_pcoordsys[2]; + h2endPt_icoordsys[ind] = calc_bbox_conn_pt(h2bbox_icoordsys[ind], + ( last_seg_pt / h2i2anc )); + h2endPt_pcoordsys[ind] = h2endPt_icoordsys[ind] * h2i2anc / path2anc; + + // Leave the other where it is. + h2endPt_pcoordsys[!ind] = other_endpt; + + change_endpts(path->curve, h2endPt_pcoordsys); + } + if (updatePathRepr) { + path->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + path->updateRepr(); + } +} + +// TODO: This triggering of makeInvalidPath could be cleaned up to be +// another option passed to move_compensate. +static void +sp_conn_end_shape_move_compensate(NR::Matrix const *mp, SPItem *moved_item, + SPPath *const path) +{ + if (path->connEndPair.isAutoRoutingConn()) { + path->connEndPair.makePathInvalid(); + } + sp_conn_end_move_compensate(mp, moved_item, path); +} + + +void +sp_conn_adjust_invalid_path(SPPath *const path) +{ + sp_conn_end_move_compensate(NULL, NULL, path); +} + +void +sp_conn_adjust_path(SPPath *const path) +{ + if (path->connEndPair.isAutoRoutingConn()) { + path->connEndPair.makePathInvalid(); + } + // Don't update the path repr or else connector dragging is slowed by + // constant update of values to the xml editor, and each step is also + // needlessly remembered by undo/redo. + bool const updatePathRepr = false; + sp_conn_end_move_compensate(NULL, NULL, path, updatePathRepr); +} + +static NR::Point +calc_bbox_conn_pt(NR::Rect const &bbox, NR::Point const &p) +{ + using NR::X; + using NR::Y; + NR::Point const ctr(bbox.midpoint()); + NR::Point const lengths(bbox.dimensions()); + if ( ctr == p ) { + /* Arbitrarily choose centre of right edge. */ + return NR::Point(ctr[X] + .5 * lengths[X], + ctr[Y]); + } + NR::Point const cp( p - ctr ); + NR::Dim2 const edgeDim = ( ( fabs(lengths[Y] * cp[X]) < + fabs(lengths[X] * cp[Y]) ) + ? Y + : X ); + NR::Dim2 const otherDim = (NR::Dim2) !edgeDim; + NR::Point offset; + offset[edgeDim] = (signed_one(cp[edgeDim]) + * lengths[edgeDim]); + offset[otherDim] = (lengths[edgeDim] + * cp[otherDim] + / fabs(cp[edgeDim])); + g_assert((offset[otherDim] >= 0) == (cp[otherDim] >= 0)); +#ifndef NDEBUG + for (unsigned d = 0; d < 2; ++d) { + g_assert(fabs(offset[d]) <= lengths[d] + .125); + } +#endif + return ctr + .5 * offset; +} + +static double signed_one(double const x) +{ + return (x < 0 + ? -1. + : 1.); +} + +static void +change_endpts(SPCurve *const curve, NR::Point const h2endPt[2]) +{ +#if 0 + sp_curve_reset(curve); + sp_curve_moveto(curve, h2endPt[0]); + sp_curve_lineto(curve, h2endPt[1]); +#else + sp_curve_move_endpoints(curve, h2endPt[0], h2endPt[1]); +#endif +} + +static void +sp_conn_end_deleted(SPObject *, SPObject *const owner, unsigned const handle_ix) +{ + // todo: The first argument is the deleted object, or just NULL if + // called by sp_conn_end_detach. + g_return_if_fail(handle_ix < 2); + char const *const attr_str[] = {"inkscape:connection-start", + "inkscape:connection-end"}; + SP_OBJECT_REPR(owner)->setAttribute(attr_str[handle_ix], NULL); + /* I believe this will trigger sp_conn_end_href_changed. */ +} + +void +sp_conn_end_detach(SPObject *const owner, unsigned const handle_ix) +{ + sp_conn_end_deleted(NULL, owner, handle_ix); +} + +void +SPConnEnd::setAttacherHref(gchar const *value) +{ + if ( value && href && ( strcmp(value, href) == 0 ) ) { + /* No change, do nothing. */ + } else { + g_free(href); + href = NULL; + if (value) { + // First, set the href field, because sp_conn_end_href_changed will need it. + href = g_strdup(value); + + // Now do the attaching, which emits the changed signal. + try { + ref.attach(Inkscape::URI(value)); + } catch (Inkscape::BadURIException &e) { + /* TODO: Proper error handling as per + * http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing. (Also needed for + * sp-use.) */ + g_warning("%s", e.what()); + ref.detach(); + } + } else { + ref.detach(); + } + } +} + +void +sp_conn_end_href_changed(SPObject *old_ref, SPObject *ref, + SPConnEnd *connEndPtr, SPPath *const path, unsigned const handle_ix) +{ + g_return_if_fail(connEndPtr != NULL); + SPConnEnd &connEnd = *connEndPtr; + connEnd._delete_connection.disconnect(); + connEnd._transformed_connection.disconnect(); + + if (connEnd.href) { + SPObject *refobj = connEnd.ref.getObject(); + if (refobj) { + connEnd._delete_connection + = SP_OBJECT(refobj)->connectDelete(sigc::bind(sigc::ptr_fun(&sp_conn_end_deleted), + SP_OBJECT(path), handle_ix)); + connEnd._transformed_connection + = SP_ITEM(refobj)->connectTransformed(sigc::bind(sigc::ptr_fun(&sp_conn_end_shape_move_compensate), + path)); + } + } +} + + + +/* + 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/sp-conn-end.h b/src/sp-conn-end.h new file mode 100644 index 000000000..a565b6404 --- /dev/null +++ b/src/sp-conn-end.h @@ -0,0 +1,51 @@ +#ifndef SEEN_SP_CONN_END +#define SEEN_SP_CONN_END + +#include +#include + +#include "sp-use-reference.h" + + +class SPConnEnd { +public: + SPConnEnd(SPObject *owner); + + SPUseReference ref; + gchar *href; + + /** Change of href string (not a modification of the attributes of the referrent). */ + sigc::connection _changed_connection; + + /** Called when the attached object gets deleted. */ + sigc::connection _delete_connection; + + /** A sigc connection for transformed signal, used to do move compensation. */ + sigc::connection _transformed_connection; + + void setAttacherHref(gchar const *); + +private: + SPConnEnd(SPConnEnd const &); + SPConnEnd &operator=(SPConnEnd const &); +}; + +void sp_conn_end_href_changed(SPObject *old_ref, SPObject *ref, + SPConnEnd *connEnd, SPPath *path, unsigned const handle_ix); +void sp_conn_adjust_invalid_path(SPPath *const path); +void sp_conn_adjust_path(SPPath *const path); +void sp_conn_end_detach(SPObject *const owner, unsigned const handle_ix); + + +#endif /* !SEEN_SP_CONN_END */ + +/* + 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/sp-cursor.cpp b/src/sp-cursor.cpp new file mode 100644 index 000000000..f59c63487 --- /dev/null +++ b/src/sp-cursor.cpp @@ -0,0 +1,115 @@ +#define __SP_CURSOR_C__ + +/* + * Some convenience stuff + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#include "sp-cursor.h" + +void sp_cursor_bitmap_and_mask_from_xpm (GdkBitmap **bitmap, GdkBitmap **mask, gchar **xpm) +{ + int height; + int width; + int colors; + int pix; + sscanf(xpm[0], "%d %d %d %d", &height, &width, &colors, &pix); + + g_return_if_fail (height == 32); + g_return_if_fail (width == 32); + g_return_if_fail (colors >= 3); + + int transparent_color = ' '; + int black_color = '.'; + + char pixmap_buffer[(32 * 32)/8]; + char mask_buffer[(32 * 32)/8]; + + for (int i = 0; i < colors; i++) { + + char const *p = xpm[1 + i]; + char const ccode = *p; + + p++; + while (isspace(*p)) { + p++; + } + p++; + while (isspace(*p)) { + p++; + } + + if (strcmp(p, "None") == 0) { + transparent_color = ccode; + } + + if (strcmp(p, "#000000") == 0) { + black_color = ccode; + } + } + + for (int y = 0; y < 32; y++) { + for (int x = 0; x < 32; ) { + + char value = 0; + char maskv = 0; + + for (int pix = 0; pix < 8; pix++, x++){ + if (xpm [4+y][x] != transparent_color) { + maskv |= 1 << pix; + + if (xpm [4+y][x] == black_color) { + value |= 1 << pix; + } + } + } + + pixmap_buffer[(y * 4 + x/8)-1] = value; + mask_buffer[(y * 4 + x/8)-1] = maskv; + } + } + + *bitmap = gdk_bitmap_create_from_data(NULL, pixmap_buffer, 32, 32); + *mask = gdk_bitmap_create_from_data(NULL, mask_buffer, 32, 32); +} + +GdkCursor *sp_cursor_new_from_xpm (gchar **xpm, gint hot_x, gint hot_y) +{ + GdkColor const fg = { 0, 0, 0, 0 }; + GdkColor const bg = { 0, 65535, 65535, 65535 }; + + GdkBitmap *bitmap = NULL; + GdkBitmap *mask = NULL; + + sp_cursor_bitmap_and_mask_from_xpm (&bitmap, &mask, xpm); + if ( bitmap != NULL && mask != NULL ) { + GdkCursor *new_cursor = gdk_cursor_new_from_pixmap (bitmap, mask, + &fg, &bg, + hot_x, hot_y); + g_object_unref (bitmap); + g_object_unref (mask); + return new_cursor; + } + + return NULL; +} + +/* + 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/sp-cursor.h b/src/sp-cursor.h new file mode 100644 index 000000000..36468aae8 --- /dev/null +++ b/src/sp-cursor.h @@ -0,0 +1,20 @@ +#ifndef SP_CURSOR_H +#define SP_CURSOR_H + +#include + +void sp_cursor_bitmap_and_mask_from_xpm(GdkBitmap **bitmap, GdkBitmap **mask, gchar **xpm); +GdkCursor *sp_cursor_new_from_xpm(gchar **xpm, gint hot_x, gint hot_y); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/sp-defs.cpp b/src/sp-defs.cpp new file mode 100644 index 000000000..ad6cfc578 --- /dev/null +++ b/src/sp-defs.cpp @@ -0,0 +1,172 @@ +#define __SP_DEFS_C__ + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 2000-2002 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +/* + * fixme: We should really check childrens validity - currently everything + * flips in + */ + +#include "sp-defs.h" +#include "xml/repr.h" + +static void sp_defs_class_init(SPDefsClass *dc); +static void sp_defs_init(SPDefs *defs); + +static void sp_defs_release(SPObject *object); +static void sp_defs_update(SPObject *object, SPCtx *ctx, guint flags); +static void sp_defs_modified(SPObject *object, guint flags); +static Inkscape::XML::Node *sp_defs_write(SPObject *object, Inkscape::XML::Node *repr, guint flags); + +static SPObjectClass *parent_class; + +GType sp_defs_get_type(void) +{ + static GType defs_type = 0; + + if (!defs_type) { + GTypeInfo defs_info = { + sizeof(SPDefsClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_defs_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof(SPDefs), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_defs_init, + NULL, /* value_table */ + }; + defs_type = g_type_register_static(SP_TYPE_OBJECT, "SPDefs", &defs_info, (GTypeFlags) 0); + } + + return defs_type; +} + +static void sp_defs_class_init(SPDefsClass *dc) +{ + parent_class = (SPObjectClass *) g_type_class_ref(SP_TYPE_OBJECT); + SPObjectClass *sp_object_class = (SPObjectClass *) dc; + + sp_object_class->release = sp_defs_release; + sp_object_class->update = sp_defs_update; + sp_object_class->modified = sp_defs_modified; + sp_object_class->write = sp_defs_write; +} + +static void sp_defs_init(SPDefs *defs) +{ + +} + +static void sp_defs_release(SPObject *object) +{ + if (((SPObjectClass *) (parent_class))->release) { + ((SPObjectClass *) (parent_class))->release(object); + } +} + +static void sp_defs_update(SPObject *object, SPCtx *ctx, guint flags) +{ + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + flags &= SP_OBJECT_MODIFIED_CASCADE; + + GSList *l = NULL; + for ( SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) { + g_object_ref(G_OBJECT(child)); + l = g_slist_prepend(l, child); + } + + l = g_slist_reverse(l); + + while (l) { + SPObject *child = SP_OBJECT(l->data); + l = g_slist_remove(l, child); + if (flags || (child->uflags & SP_OBJECT_MODIFIED_FLAG)) { + child->updateDisplay(ctx, flags); + } + g_object_unref(G_OBJECT(child)); + } +} + +static void sp_defs_modified(SPObject *object, guint flags) +{ + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + flags &= SP_OBJECT_MODIFIED_CASCADE; + + GSList *l = NULL; + for ( SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) { + g_object_ref(G_OBJECT(child)); + l = g_slist_prepend(l, child); + } + + l = g_slist_reverse(l); + + while (l) { + SPObject *child = SP_OBJECT(l->data); + l = g_slist_remove(l, child); + if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->emitModified(flags); + } + g_object_unref(G_OBJECT (child)); + } +} + +static Inkscape::XML::Node *sp_defs_write(SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + if (flags & SP_OBJECT_WRITE_BUILD) { + + if (!repr) { + repr = sp_repr_new("svg:defs"); + } + + GSList *l = NULL; + for ( SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) { + Inkscape::XML::Node *crepr = child->updateRepr(NULL, flags); + if (crepr) l = g_slist_prepend(l, crepr); + } + + while (l) { + repr->addChild((Inkscape::XML::Node *) l->data, NULL); + Inkscape::GC::release((Inkscape::XML::Node *) l->data); + l = g_slist_remove(l, l->data); + } + + } else { + for ( SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) { + child->updateRepr(flags); + } + } + + if (((SPObjectClass *) (parent_class))->write) { + (* ((SPObjectClass *) (parent_class))->write)(object, repr, flags); + } + + return repr; +} + +/* + 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/sp-defs.h b/src/sp-defs.h new file mode 100644 index 000000000..4b6f7a5b7 --- /dev/null +++ b/src/sp-defs.h @@ -0,0 +1,44 @@ +#ifndef SEEN_SP_DEFS_H +#define SEEN_SP_DEFS_H + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 2000-2002 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-object.h" + +#define SP_TYPE_DEFS (sp_defs_get_type()) +#define SP_DEFS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SP_TYPE_DEFS, SPDefs)) +#define SP_DEFS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SP_TYPE_DEFS, SPDefsClass)) +#define SP_IS_DEFS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SP_TYPE_DEFS)) +#define SP_IS_DEFS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SP_TYPE_DEFS)) + +struct SPDefs : public SPObject { +}; + +struct SPDefsClass { + SPObjectClass parent_class; +}; + +GType sp_defs_get_type(void); + + +#endif /* !SEEN_SP_DEFS_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 : diff --git a/src/sp-ellipse.cpp b/src/sp-ellipse.cpp new file mode 100644 index 000000000..b2b0f7b05 --- /dev/null +++ b/src/sp-ellipse.cpp @@ -0,0 +1,863 @@ +#define __SP_ELLIPSE_C__ + +/* + * SVG and related implementations + * + * Authors: + * Lauris Kaplinski + * Mitsuru Oka + * bulia byak + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + + +#include "libnr/n-art-bpath.h" +#include "libnr/nr-path.h" +#include "libnr/nr-matrix-fns.h" +#include "svg/svg.h" +#include "svg/stringstream.h" +#include "xml/repr.h" +#include "attributes.h" +#include "style.h" +#include "display/curve.h" +#include + +#include "sp-ellipse.h" + +#include "prefs-utils.h" + +/* Common parent class */ + +#define noELLIPSE_VERBOSE + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#define SP_2PI (2 * M_PI) + +#if 1 +/* Hmmm... shouldn't this also qualify */ +/* Whether it is faster or not, well, nobody knows */ +#define sp_round(v,m) (((v) < 0.0) ? ((ceil((v) / (m) - 0.5)) * (m)) : ((floor((v) / (m) + 0.5)) * (m))) +#else +/* we do not use C99 round(3) function yet */ +static double sp_round(double x, double y) +{ + double remain; + + g_assert(y > 0.0); + + /* return round(x/y) * y; */ + + remain = fmod(x, y); + + if (remain >= 0.5*y) + return x - remain + y; + else + return x - remain; +} +#endif + +static void sp_genericellipse_class_init(SPGenericEllipseClass *klass); +static void sp_genericellipse_init(SPGenericEllipse *ellipse); + +static void sp_genericellipse_update(SPObject *object, SPCtx *ctx, guint flags); + +static void sp_genericellipse_snappoints(SPItem const *item, SnapPointsIter p); + +static void sp_genericellipse_set_shape(SPShape *shape); +static Inkscape::XML::Node *sp_genericellipse_write(SPObject *object, Inkscape::XML::Node *repr, + guint flags); + +static gboolean sp_arc_set_elliptical_path_attribute(SPArc *arc, Inkscape::XML::Node *repr); + +static SPShapeClass *ge_parent_class; + +GType +sp_genericellipse_get_type(void) +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPGenericEllipseClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_genericellipse_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof(SPGenericEllipse), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_genericellipse_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_SHAPE, "SPGenericEllipse", &info, (GTypeFlags)0); + } + return type; +} + +static void sp_genericellipse_class_init(SPGenericEllipseClass *klass) +{ + SPObjectClass *sp_object_class = (SPObjectClass *) klass; + SPItemClass *item_class = (SPItemClass *) klass; + SPShapeClass *shape_class = (SPShapeClass *) klass; + + ge_parent_class = (SPShapeClass*) g_type_class_ref(SP_TYPE_SHAPE); + + sp_object_class->update = sp_genericellipse_update; + sp_object_class->write = sp_genericellipse_write; + + item_class->snappoints = sp_genericellipse_snappoints; + + shape_class->set_shape = sp_genericellipse_set_shape; +} + +static void +sp_genericellipse_init(SPGenericEllipse *ellipse) +{ + ellipse->cx.unset(); + ellipse->cy.unset(); + ellipse->rx.unset(); + ellipse->ry.unset(); + + ellipse->start = 0.0; + ellipse->end = SP_2PI; + ellipse->closed = TRUE; +} + +static void +sp_genericellipse_update(SPObject *object, SPCtx *ctx, guint flags) +{ + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + SPGenericEllipse *ellipse = (SPGenericEllipse *) object; + SPStyle const *style = object->style; + double const d = 1.0 / NR::expansion(((SPItemCtx const *) ctx)->i2vp); + double const em = style->font_size.computed; + double const ex = em * 0.5; // fixme: get from pango or libnrtype + ellipse->cx.update(em, ex, d); + ellipse->cy.update(em, ex, d); + ellipse->rx.update(em, ex, d); + ellipse->ry.update(em, ex, d); + sp_shape_set_shape((SPShape *) object); + } + + if (((SPObjectClass *) ge_parent_class)->update) + ((SPObjectClass *) ge_parent_class)->update(object, ctx, flags); +} + +#define C1 0.552 + +/* fixme: Think (Lauris) */ + +static void sp_genericellipse_set_shape(SPShape *shape) +{ + double cx, cy, rx, ry, s, e; + double x0, y0, x1, y1, x2, y2, x3, y3; + double len; + gint slice = FALSE; + gint i; + + SPGenericEllipse *ellipse = (SPGenericEllipse *) shape; + + if ((ellipse->rx.computed < 1e-18) || (ellipse->ry.computed < 1e-18)) return; + if (fabs(ellipse->end - ellipse->start) < 1e-9) return; + + sp_genericellipse_normalize(ellipse); + + cx = 0.0; + cy = 0.0; + rx = ellipse->rx.computed; + ry = ellipse->ry.computed; + + // figure out if we have a slice, guarding against rounding errors + len = fmod(ellipse->end - ellipse->start, SP_2PI); + if (len < 0.0) len += SP_2PI; + if (fabs(len) < 1e-8 || fabs(len - SP_2PI) < 1e-8) { + slice = FALSE; + ellipse->end = ellipse->start + SP_2PI; + } else { + slice = TRUE; + } + + NR::Matrix aff = NR::Matrix(NR::scale(rx, ry)); + aff[4] = ellipse->cx.computed; + aff[5] = ellipse->cy.computed; + + NArtBpath bpath[16]; + i = 0; + if (ellipse->closed) { + bpath[i].code = NR_MOVETO; + } else { + bpath[i].code = NR_MOVETO_OPEN; + } + bpath[i].x3 = cos(ellipse->start); + bpath[i].y3 = sin(ellipse->start); + i++; + + for (s = ellipse->start; s < ellipse->end; s += M_PI_2) { + e = s + M_PI_2; + if (e > ellipse->end) + e = ellipse->end; + len = C1 * (e - s) / M_PI_2; + x0 = cos(s); + y0 = sin(s); + x1 = x0 + len * cos(s + M_PI_2); + y1 = y0 + len * sin(s + M_PI_2); + x3 = cos(e); + y3 = sin(e); + x2 = x3 + len * cos(e - M_PI_2); + y2 = y3 + len * sin(e - M_PI_2); +#ifdef ELLIPSE_VERBOSE + g_print("step %d s %f e %f coords %f %f %f %f %f %f\n", + i, s, e, x1, y1, x2, y2, x3, y3); +#endif + bpath[i].code = NR_CURVETO; + bpath[i].x1 = x1; + bpath[i].y1 = y1; + bpath[i].x2 = x2; + bpath[i].y2 = y2; + bpath[i].x3 = x3; + bpath[i].y3 = y3; + i++; + } + + if (slice && ellipse->closed) { + bpath[i].code = NR_LINETO; + bpath[i].x3 = 0.0; + bpath[i].y3 = 0.0; + i++; + bpath[i].code = NR_LINETO; + bpath[i].x3 = bpath[0].x3; + bpath[i].y3 = bpath[0].y3; + i++; + } else if (ellipse->closed) { + bpath[i-1].x3 = bpath[0].x3; + bpath[i-1].y3 = bpath[0].y3; + } + + bpath[i].code = NR_END; + SPCurve *c = sp_curve_new_from_bpath(nr_artpath_affine(bpath, aff)); + g_assert(c != NULL); + + sp_shape_set_curve_insync((SPShape *) ellipse, c, TRUE); + sp_curve_unref(c); +} + +static void sp_genericellipse_snappoints(SPItem const *item, SnapPointsIter p) +{ + SPGenericEllipse const *ge = SP_GENERICELLIPSE(item); + + NR::Matrix const i2d = sp_item_i2d_affine(item); + + /* Add the centre */ + *p = NR::Point(ge->cx.computed, ge->cy.computed) * i2d; + + // TODO: add the ends of radii +} + +void +sp_genericellipse_normalize(SPGenericEllipse *ellipse) +{ + ellipse->start = fmod(ellipse->start, SP_2PI); + ellipse->end = fmod(ellipse->end, SP_2PI); + + if (ellipse->start < 0.0) + ellipse->start += SP_2PI; + double diff = ellipse->start - ellipse->end; + if (diff >= 0.0) + ellipse->end += diff - fmod(diff, SP_2PI) + SP_2PI; + + /* Now we keep: 0 <= start < end <= 2*PI */ +} + +static Inkscape::XML::Node *sp_genericellipse_write(SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + SPGenericEllipse *ellipse = SP_GENERICELLIPSE(object); + + if (flags & SP_OBJECT_WRITE_EXT) { + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = sp_repr_new("svg:path"); + } + + sp_repr_set_svg_double(repr, "sodipodi:cx", ellipse->cx.computed); + sp_repr_set_svg_double(repr, "sodipodi:cy", ellipse->cy.computed); + sp_repr_set_svg_double(repr, "sodipodi:rx", ellipse->rx.computed); + sp_repr_set_svg_double(repr, "sodipodi:ry", ellipse->ry.computed); + + if (SP_IS_ARC(ellipse)) + sp_arc_set_elliptical_path_attribute(SP_ARC(object), SP_OBJECT_REPR(object)); + } + + if (((SPObjectClass *) ge_parent_class)->write) + ((SPObjectClass *) ge_parent_class)->write(object, repr, flags); + + return repr; +} + +/* SVG element */ + +static void sp_ellipse_class_init(SPEllipseClass *klass); +static void sp_ellipse_init(SPEllipse *ellipse); + +static void sp_ellipse_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); +static Inkscape::XML::Node *sp_ellipse_write(SPObject *object, Inkscape::XML::Node *repr, guint flags); +static void sp_ellipse_set(SPObject *object, unsigned int key, gchar const *value); +static gchar *sp_ellipse_description(SPItem *item); + +static SPGenericEllipseClass *ellipse_parent_class; + +GType +sp_ellipse_get_type(void) +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPEllipseClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_ellipse_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof(SPEllipse), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_ellipse_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_GENERICELLIPSE, "SPEllipse", &info, (GTypeFlags)0); + } + return type; +} + +static void sp_ellipse_class_init(SPEllipseClass *klass) +{ + SPObjectClass *sp_object_class = (SPObjectClass *) klass; + SPItemClass *item_class = (SPItemClass *) klass; + + ellipse_parent_class = (SPGenericEllipseClass*) g_type_class_ref(SP_TYPE_GENERICELLIPSE); + + sp_object_class->build = sp_ellipse_build; + sp_object_class->write = sp_ellipse_write; + sp_object_class->set = sp_ellipse_set; + + item_class->description = sp_ellipse_description; +} + +static void +sp_ellipse_init(SPEllipse *ellipse) +{ + /* Nothing special */ +} + +static void +sp_ellipse_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + if (((SPObjectClass *) ellipse_parent_class)->build) + (* ((SPObjectClass *) ellipse_parent_class)->build) (object, document, repr); + + sp_object_read_attr(object, "cx"); + sp_object_read_attr(object, "cy"); + sp_object_read_attr(object, "rx"); + sp_object_read_attr(object, "ry"); +} + +static Inkscape::XML::Node * +sp_ellipse_write(SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + SPGenericEllipse *ellipse; + + ellipse = SP_GENERICELLIPSE(object); + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = sp_repr_new("svg:ellipse"); + } + + sp_repr_set_svg_double(repr, "cx", ellipse->cx.computed); + sp_repr_set_svg_double(repr, "cy", ellipse->cy.computed); + sp_repr_set_svg_double(repr, "rx", ellipse->rx.computed); + sp_repr_set_svg_double(repr, "ry", ellipse->ry.computed); + + if (((SPObjectClass *) ellipse_parent_class)->write) + (* ((SPObjectClass *) ellipse_parent_class)->write) (object, repr, flags); + + return repr; +} + +static void +sp_ellipse_set(SPObject *object, unsigned int key, gchar const *value) +{ + SPGenericEllipse *ellipse; + + ellipse = SP_GENERICELLIPSE(object); + + switch (key) { + case SP_ATTR_CX: + ellipse->cx.readOrUnset(value); + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_CY: + ellipse->cy.readOrUnset(value); + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_RX: + if (!ellipse->rx.read(value) || (ellipse->rx.value <= 0.0)) { + ellipse->rx.unset(); + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_RY: + if (!ellipse->ry.read(value) || (ellipse->ry.value <= 0.0)) { + ellipse->ry.unset(); + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + default: + if (((SPObjectClass *) ellipse_parent_class)->set) + ((SPObjectClass *) ellipse_parent_class)->set(object, key, value); + break; + } +} + +static gchar *sp_ellipse_description(SPItem *item) +{ + return g_strdup(_("Ellipse")); +} + + +void +sp_ellipse_position_set(SPEllipse *ellipse, gdouble x, gdouble y, gdouble rx, gdouble ry) +{ + SPGenericEllipse *ge; + + g_return_if_fail(ellipse != NULL); + g_return_if_fail(SP_IS_ELLIPSE(ellipse)); + + ge = SP_GENERICELLIPSE(ellipse); + + ge->cx.computed = x; + ge->cy.computed = y; + ge->rx.computed = rx; + ge->ry.computed = ry; + + ((SPObject *)ge)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +/* SVG element */ + +static void sp_circle_class_init(SPCircleClass *klass); +static void sp_circle_init(SPCircle *circle); + +static void sp_circle_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); +static Inkscape::XML::Node *sp_circle_write(SPObject *object, Inkscape::XML::Node *repr, guint flags); +static void sp_circle_set(SPObject *object, unsigned int key, gchar const *value); +static gchar *sp_circle_description(SPItem *item); + +static SPGenericEllipseClass *circle_parent_class; + +GType +sp_circle_get_type(void) +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPCircleClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_circle_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof(SPCircle), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_circle_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_GENERICELLIPSE, "SPCircle", &info, (GTypeFlags)0); + } + return type; +} + +static void +sp_circle_class_init(SPCircleClass *klass) +{ + SPObjectClass *sp_object_class = (SPObjectClass *) klass; + SPItemClass *item_class = (SPItemClass *) klass; + + circle_parent_class = (SPGenericEllipseClass*) g_type_class_ref(SP_TYPE_GENERICELLIPSE); + + sp_object_class->build = sp_circle_build; + sp_object_class->write = sp_circle_write; + sp_object_class->set = sp_circle_set; + + item_class->description = sp_circle_description; +} + +static void +sp_circle_init(SPCircle *circle) +{ + /* Nothing special */ +} + +static void +sp_circle_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + if (((SPObjectClass *) circle_parent_class)->build) + (* ((SPObjectClass *) circle_parent_class)->build)(object, document, repr); + + sp_object_read_attr(object, "cx"); + sp_object_read_attr(object, "cy"); + sp_object_read_attr(object, "r"); +} + +static Inkscape::XML::Node * +sp_circle_write(SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + SPGenericEllipse *ellipse; + + ellipse = SP_GENERICELLIPSE(object); + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = sp_repr_new("svg:circle"); + } + + sp_repr_set_svg_double(repr, "cx", ellipse->cx.computed); + sp_repr_set_svg_double(repr, "cy", ellipse->cy.computed); + sp_repr_set_svg_double(repr, "r", ellipse->rx.computed); + + if (((SPObjectClass *) circle_parent_class)->write) + ((SPObjectClass *) circle_parent_class)->write(object, repr, flags); + + return repr; +} + +static void +sp_circle_set(SPObject *object, unsigned int key, gchar const *value) +{ + SPGenericEllipse *ge; + + ge = SP_GENERICELLIPSE(object); + + switch (key) { + case SP_ATTR_CX: + ge->cx.readOrUnset(value); + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_CY: + ge->cy.readOrUnset(value); + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_R: + if (!ge->rx.read(value) || ge->rx.value <= 0.0) { + ge->rx.unset(); + } + ge->ry = ge->rx; + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + default: + if (((SPObjectClass *) circle_parent_class)->set) + ((SPObjectClass *) circle_parent_class)->set(object, key, value); + break; + } +} + +static gchar *sp_circle_description(SPItem *item) +{ + return g_strdup(_("Circle")); +} + +/* element */ + +static void sp_arc_class_init(SPArcClass *klass); +static void sp_arc_init(SPArc *arc); + +static void sp_arc_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); +static Inkscape::XML::Node *sp_arc_write(SPObject *object, Inkscape::XML::Node *repr, guint flags); +static void sp_arc_set(SPObject *object, unsigned int key, gchar const *value); +static void sp_arc_modified(SPObject *object, guint flags); + +static gchar *sp_arc_description(SPItem *item); + +static SPGenericEllipseClass *arc_parent_class; + +GType +sp_arc_get_type(void) +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPArcClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_arc_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof(SPArc), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_arc_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_GENERICELLIPSE, "SPArc", &info, (GTypeFlags)0); + } + return type; +} + +static void +sp_arc_class_init(SPArcClass *klass) +{ + SPObjectClass *sp_object_class = (SPObjectClass *) klass; + SPItemClass *item_class = (SPItemClass *) klass; + + arc_parent_class = (SPGenericEllipseClass*) g_type_class_ref(SP_TYPE_GENERICELLIPSE); + + sp_object_class->build = sp_arc_build; + sp_object_class->write = sp_arc_write; + sp_object_class->set = sp_arc_set; + sp_object_class->modified = sp_arc_modified; + + item_class->description = sp_arc_description; +} + +static void +sp_arc_init(SPArc *arc) +{ + /* Nothing special */ +} + +static void +sp_arc_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + if (((SPObjectClass *) arc_parent_class)->build) + (* ((SPObjectClass *) arc_parent_class)->build) (object, document, repr); + + Inkscape::Version version = sp_object_get_sodipodi_version(object); + + sp_object_read_attr(object, "sodipodi:cx"); + sp_object_read_attr(object, "sodipodi:cy"); + sp_object_read_attr(object, "sodipodi:rx"); + sp_object_read_attr(object, "sodipodi:ry"); + + sp_object_read_attr(object, "sodipodi:start"); + sp_object_read_attr(object, "sodipodi:end"); + sp_object_read_attr(object, "sodipodi:open"); +} + +/* + * sp_arc_set_elliptical_path_attribute: + * + * Convert center to endpoint parameterization and set it to repr. + * + * See SVG 1.0 Specification W3C Recommendation + * ``F.6 Ellptical arc implementation notes'' for more detail. + */ +static gboolean +sp_arc_set_elliptical_path_attribute(SPArc *arc, Inkscape::XML::Node *repr) +{ + gint fa, fs; + gdouble dt; + Inkscape::SVGOStringStream os; + + SPGenericEllipse *ge = SP_GENERICELLIPSE(arc); + + NR::Point p1 = sp_arc_get_xy(arc, ge->start); + NR::Point p2 = sp_arc_get_xy(arc, ge->end); + + dt = fmod(ge->end - ge->start, SP_2PI); + if (fabs(dt) < 1e-6) { + NR::Point ph = sp_arc_get_xy(arc, (ge->start + ge->end) / 2.0); + os << "M " << p1[NR::X] << " " << p1[NR::Y] + << " A " << ge->rx.computed << " " << ge->ry.computed + << " 0 1 1 " << " " << ph[NR::X] << "," << ph[NR::Y] + << " A " << ge->rx.computed << " " << ge->ry.computed + << " 0 1 1 " << " " << p2[NR::X] << " " << p2[NR::Y] << " z"; + } else { + fa = (fabs(dt) > M_PI) ? 1 : 0; + fs = (dt > 0) ? 1 : 0; +#ifdef ARC_VERBOSE + g_print("start:%g end:%g fa=%d fs=%d\n", ge->start, ge->end, fa, fs); +#endif + if (ge->closed) { + os << "M " << p1[NR::X] << "," << p1[NR::Y] + << " A " << ge->rx.computed << "," << ge->ry.computed + << " 0 " << fa << " " << fs << " " << p2[NR::X] << "," << p2[NR::Y] + << " L " << ge->cx.computed << "," << ge->cy.computed << " z"; + } else { + os << "M " << p1[NR::X] << "," << p1[NR::Y] + << " A " << ge->rx.computed << "," << ge->ry.computed + << " 0 " << fa << " " << fs << " " << p2[NR::X] << "," << p2[NR::Y]; + + } + } + repr->setAttribute("d", os.str().c_str()); + return true; +} + +static Inkscape::XML::Node * +sp_arc_write(SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + SPGenericEllipse *ge = SP_GENERICELLIPSE(object); + SPArc *arc = SP_ARC(object); + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = sp_repr_new("svg:path"); + } + + if (flags & SP_OBJECT_WRITE_EXT) { + repr->setAttribute("sodipodi:type", "arc"); + sp_repr_set_svg_double(repr, "sodipodi:cx", ge->cx.computed); + sp_repr_set_svg_double(repr, "sodipodi:cy", ge->cy.computed); + sp_repr_set_svg_double(repr, "sodipodi:rx", ge->rx.computed); + sp_repr_set_svg_double(repr, "sodipodi:ry", ge->ry.computed); + + // write start and end only if they are non-trivial; otherwise remove + gdouble len = fmod(ge->end - ge->start, SP_2PI); + if (len < 0.0) len += SP_2PI; + if (!(fabs(len) < 1e-8 || fabs(len - SP_2PI) < 1e-8)) { + sp_repr_set_svg_double(repr, "sodipodi:start", ge->start); + sp_repr_set_svg_double(repr, "sodipodi:end", ge->end); + repr->setAttribute("sodipodi:open", (!ge->closed) ? "true" : NULL); + } else { + repr->setAttribute("sodipodi:end", NULL); + repr->setAttribute("sodipodi:start", NULL); + repr->setAttribute("sodipodi:open", NULL); + } + } + + // write d= + sp_arc_set_elliptical_path_attribute(arc, repr); + + if (((SPObjectClass *) arc_parent_class)->write) + ((SPObjectClass *) arc_parent_class)->write(object, repr, flags); + + return repr; +} + +static void +sp_arc_set(SPObject *object, unsigned int key, gchar const *value) +{ + SPGenericEllipse *ge = SP_GENERICELLIPSE(object); + + switch (key) { + case SP_ATTR_SODIPODI_CX: + ge->cx.readOrUnset(value); + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SODIPODI_CY: + ge->cy.readOrUnset(value); + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SODIPODI_RX: + if (!ge->rx.read(value) || ge->rx.computed <= 0.0) { + ge->rx.unset(); + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SODIPODI_RY: + if (!ge->ry.read(value) || ge->ry.computed <= 0.0) { + ge->ry.unset(); + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SODIPODI_START: + if (value) { + sp_svg_number_read_d(value, &ge->start); + } else { + ge->start = 0; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SODIPODI_END: + if (value) { + sp_svg_number_read_d(value, &ge->end); + } else { + ge->end = 2 * M_PI; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SODIPODI_OPEN: + ge->closed = (!value); + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + default: + if (((SPObjectClass *) arc_parent_class)->set) + ((SPObjectClass *) arc_parent_class)->set(object, key, value); + break; + } +} + +static void +sp_arc_modified(SPObject *object, guint flags) +{ + if (flags & SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG) { + sp_shape_set_shape((SPShape *) object); + } + + if (((SPObjectClass *) arc_parent_class)->modified) + ((SPObjectClass *) arc_parent_class)->modified(object, flags); +} + +static gchar *sp_arc_description(SPItem *item) +{ + SPGenericEllipse *ge = SP_GENERICELLIPSE(item); + + gdouble len = fmod(ge->end - ge->start, SP_2PI); + if (len < 0.0) len += SP_2PI; + if (!(fabs(len) < 1e-8 || fabs(len - SP_2PI) < 1e-8)) { + if (ge->closed) { + return g_strdup(_("Segment")); + } else { + return g_strdup(_("Arc")); + } + } else { + return g_strdup(_("Ellipse")); + } +} + +void +sp_arc_position_set(SPArc *arc, gdouble x, gdouble y, gdouble rx, gdouble ry) +{ + g_return_if_fail(arc != NULL); + g_return_if_fail(SP_IS_ARC(arc)); + + SPGenericEllipse *ge = SP_GENERICELLIPSE(arc); + + ge->cx.computed = x; + ge->cy.computed = y; + ge->rx.computed = rx; + ge->ry.computed = ry; + if (prefs_get_double_attribute("tools.shapes.arc", "start", 0.0) != 0) + ge->start = prefs_get_double_attribute("tools.shapes.arc", "start", 0.0); + if (prefs_get_double_attribute("tools.shapes.arc", "end", 0.0) != 0) + ge->end = prefs_get_double_attribute("tools.shapes.arc", "end", 0.0); + if (!prefs_get_string_attribute("tools.shapes.arc", "open")) + ge->closed = 1; + else + ge->closed = 0; + + ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +NR::Point sp_arc_get_xy(SPArc *arc, gdouble arg) +{ + SPGenericEllipse *ge = SP_GENERICELLIPSE(arc); + + return NR::Point(ge->rx.computed * cos(arg) + ge->cx.computed, + ge->ry.computed * sin(arg) + ge->cy.computed); +} + + +/* + 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 : diff --git a/src/sp-ellipse.h b/src/sp-ellipse.h new file mode 100644 index 000000000..a1f79e6f5 --- /dev/null +++ b/src/sp-ellipse.h @@ -0,0 +1,105 @@ +#ifndef __SP_ELLIPSE_H__ +#define __SP_ELLIPSE_H__ + +/* + * SVG and related implementations + * + * Authors: + * Lauris Kaplinski + * Mitsuru Oka + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "svg/svg-length.h" +#include "sp-shape.h" + +/* Common parent class */ + +#define SP_TYPE_GENERICELLIPSE (sp_genericellipse_get_type ()) +#define SP_GENERICELLIPSE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_GENERICELLIPSE, SPGenericEllipse)) +#define SP_GENERICELLIPSE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_GENERICELLIPSE, SPGenericEllipseClass)) +#define SP_IS_GENERICELLIPSE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_GENERICELLIPSE)) +#define SP_IS_GENERICELLIPSE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_GENERICELLIPSE)) + +class SPGenericEllipse; +class SPGenericEllipseClass; + +struct SPGenericEllipse : public SPShape { + SVGLength cx; + SVGLength cy; + SVGLength rx; + SVGLength ry; + + unsigned int closed : 1; + double start, end; +}; + +struct SPGenericEllipseClass { + SPShapeClass parent_class; +}; + +GType sp_genericellipse_get_type (void); + +/* This is technically priate by we need this in object edit (Lauris) */ +void sp_genericellipse_normalize (SPGenericEllipse *ellipse); + +/* SVG element */ + +#define SP_TYPE_ELLIPSE (sp_ellipse_get_type ()) +#define SP_ELLIPSE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_ELLIPSE, SPEllipse)) +#define SP_ELLIPSE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_ELLIPSE, SPEllipseClass)) +#define SP_IS_ELLIPSE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_ELLIPSE)) +#define SP_IS_ELLIPSE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_ELLIPSE)) + +struct SPEllipse : public SPGenericEllipse { +}; + +struct SPEllipseClass { + SPGenericEllipseClass parent_class; +}; + +GType sp_ellipse_get_type (void); + +void sp_ellipse_position_set (SPEllipse * ellipse, gdouble x, gdouble y, gdouble rx, gdouble ry); + +/* SVG element */ + +#define SP_TYPE_CIRCLE (sp_circle_get_type ()) +#define SP_CIRCLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_CIRCLE, SPCircle)) +#define SP_CIRCLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_CIRCLE, SPCircleClass)) +#define SP_IS_CIRCLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_CIRCLE)) +#define SP_IS_CIRCLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_CIRCLE)) + +struct SPCircle : public SPGenericEllipse { +}; + +struct SPCircleClass { + SPGenericEllipseClass parent_class; +}; + +GType sp_circle_get_type (void); + +/* element */ + +#define SP_TYPE_ARC (sp_arc_get_type ()) +#define SP_ARC(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_ARC, SPArc)) +#define SP_ARC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_ARC, SPArcClass)) +#define SP_IS_ARC(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_ARC)) +#define SP_IS_ARC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_ARC)) + +struct SPArc : public SPGenericEllipse { +}; + +struct SPArcClass { + SPGenericEllipseClass parent_class; +}; + +GType sp_arc_get_type (void); +void sp_arc_position_set (SPArc * arc, gdouble x, gdouble y, gdouble rx, gdouble ry); +NR::Point sp_arc_get_xy (SPArc *ge, gdouble arg); + +#endif diff --git a/src/sp-flowdiv.cpp b/src/sp-flowdiv.cpp new file mode 100644 index 000000000..61a5ec430 --- /dev/null +++ b/src/sp-flowdiv.cpp @@ -0,0 +1,730 @@ +#define __SP_FLOWDIV_C__ + +/* + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "libnr/nr-matrix-ops.h" +#include "xml/repr.h" +//#include "svg/svg.h" + +//#include "style.h" + +#include "sp-flowdiv.h" +#include "sp-string.h" + +static void sp_flowdiv_class_init (SPFlowdivClass *klass); +static void sp_flowdiv_init (SPFlowdiv *group); +static void sp_flowdiv_release (SPObject *object); +static Inkscape::XML::Node *sp_flowdiv_write (SPObject *object, Inkscape::XML::Node *repr, guint flags); +static void sp_flowdiv_update (SPObject *object, SPCtx *ctx, unsigned int flags); +static void sp_flowdiv_modified (SPObject *object, guint flags); +static void sp_flowdiv_build (SPObject *object, SPDocument *doc, Inkscape::XML::Node *repr); +static void sp_flowdiv_set (SPObject *object, unsigned int key, const gchar *value); + +static void sp_flowtspan_class_init (SPFlowtspanClass *klass); +static void sp_flowtspan_init (SPFlowtspan *group); +static void sp_flowtspan_release (SPObject *object); +static Inkscape::XML::Node *sp_flowtspan_write (SPObject *object, Inkscape::XML::Node *repr, guint flags); +static void sp_flowtspan_update (SPObject *object, SPCtx *ctx, unsigned int flags); +static void sp_flowtspan_modified (SPObject *object, guint flags); +static void sp_flowtspan_build (SPObject *object, SPDocument *doc, Inkscape::XML::Node *repr); +static void sp_flowtspan_set (SPObject *object, unsigned int key, const gchar *value); + +static void sp_flowpara_class_init (SPFlowparaClass *klass); +static void sp_flowpara_init (SPFlowpara *group); +static void sp_flowpara_release (SPObject *object); +static Inkscape::XML::Node *sp_flowpara_write (SPObject *object, Inkscape::XML::Node *repr, guint flags); +static void sp_flowpara_update (SPObject *object, SPCtx *ctx, unsigned int flags); +static void sp_flowpara_modified (SPObject *object, guint flags); +static void sp_flowpara_build (SPObject *object, SPDocument *doc, Inkscape::XML::Node *repr); +static void sp_flowpara_set (SPObject *object, unsigned int key, const gchar *value); + +static void sp_flowline_class_init (SPFlowlineClass *klass); +static void sp_flowline_release (SPObject *object); +static void sp_flowline_init (SPFlowline *group); +static Inkscape::XML::Node *sp_flowline_write (SPObject *object, Inkscape::XML::Node *repr, guint flags); +static void sp_flowline_modified (SPObject *object, guint flags); + +static void sp_flowregionbreak_class_init (SPFlowregionbreakClass *klass); +static void sp_flowregionbreak_release (SPObject *object); +static void sp_flowregionbreak_init (SPFlowregionbreak *group); +static Inkscape::XML::Node *sp_flowregionbreak_write (SPObject *object, Inkscape::XML::Node *repr, guint flags); +static void sp_flowregionbreak_modified (SPObject *object, guint flags); + +static SPItemClass * flowdiv_parent_class; +static SPItemClass * flowtspan_parent_class; +static SPItemClass * flowpara_parent_class; +static SPObjectClass * flowline_parent_class; +static SPObjectClass * flowregionbreak_parent_class; + +GType +sp_flowdiv_get_type (void) +{ + static GType group_type = 0; + if (!group_type) { + GTypeInfo group_info = { + sizeof (SPFlowdivClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_flowdiv_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (SPFlowdiv), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_flowdiv_init, + NULL, /* value_table */ + }; + group_type = g_type_register_static (SP_TYPE_ITEM, "SPFlowdiv", &group_info, (GTypeFlags)0); + } + return group_type; +} + +static void +sp_flowdiv_class_init (SPFlowdivClass *klass) +{ + GObjectClass * object_class; + SPObjectClass * sp_object_class; + + object_class = (GObjectClass *) klass; + sp_object_class = (SPObjectClass *) klass; + + flowdiv_parent_class = (SPItemClass *)g_type_class_ref (SP_TYPE_ITEM); + + sp_object_class->build = sp_flowdiv_build; + sp_object_class->set = sp_flowdiv_set; + sp_object_class->release = sp_flowdiv_release; + sp_object_class->write = sp_flowdiv_write; + sp_object_class->update = sp_flowdiv_update; + sp_object_class->modified = sp_flowdiv_modified; +} + +static void +sp_flowdiv_init (SPFlowdiv *group) +{ +} + +static void +sp_flowdiv_release (SPObject *object) +{ + if (((SPObjectClass *) flowdiv_parent_class)->release) + ((SPObjectClass *) flowdiv_parent_class)->release (object); +} +static void +sp_flowdiv_update (SPObject *object, SPCtx *ctx, unsigned int flags) +{ + SPItemCtx *ictx=(SPItemCtx *) ctx; + SPItemCtx cctx=*ictx; + + if (((SPObjectClass *) (flowdiv_parent_class))->update) + ((SPObjectClass *) (flowdiv_parent_class))->update (object, ctx, flags); + + if (flags & SP_OBJECT_MODIFIED_FLAG) flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + flags &= SP_OBJECT_MODIFIED_CASCADE; + + GSList* l = NULL; + for (SPObject *child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) { + g_object_ref (G_OBJECT (child)); + l = g_slist_prepend (l, child); + } + l = g_slist_reverse (l); + while (l) { + SPObject *child = SP_OBJECT (l->data); + l = g_slist_remove (l, child); + if (flags || (child->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + if (SP_IS_ITEM (child)) { + SPItem const &chi = *SP_ITEM(child); + cctx.i2doc = chi.transform * ictx->i2doc; + cctx.i2vp = chi.transform * ictx->i2vp; + child->updateDisplay((SPCtx *)&cctx, flags); + } else { + child->updateDisplay(ctx, flags); + } + } + g_object_unref (G_OBJECT (child)); + } +} +static void +sp_flowdiv_modified (SPObject *object, guint flags) +{ + SPObject *child; + GSList *l; + + if (((SPObjectClass *) (flowdiv_parent_class))->modified) + ((SPObjectClass *) (flowdiv_parent_class))->modified (object, flags); + + if (flags & SP_OBJECT_MODIFIED_FLAG) flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + flags &= SP_OBJECT_MODIFIED_CASCADE; + + l = NULL; + for (child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) { + g_object_ref (G_OBJECT (child)); + l = g_slist_prepend (l, child); + } + l = g_slist_reverse (l); + while (l) { + child = SP_OBJECT (l->data); + l = g_slist_remove (l, child); + if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->emitModified(flags); + } + g_object_unref (G_OBJECT (child)); + } +} + +static void +sp_flowdiv_build (SPObject *object, SPDocument *doc, Inkscape::XML::Node *repr) +{ + object->_requireSVGVersion(Inkscape::Version(1, 2)); + + if (((SPObjectClass *) flowdiv_parent_class)->build) + ((SPObjectClass *) flowdiv_parent_class)->build (object, doc, repr); +} + +static void +sp_flowdiv_set (SPObject *object, unsigned int key, const gchar *value) +{ + if (((SPObjectClass *) flowdiv_parent_class)->set) + (((SPObjectClass *) flowdiv_parent_class)->set) (object, key, value); +} + +static Inkscape::XML::Node * +sp_flowdiv_write (SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ +// SPFlowdiv *group = SP_FLOWDIV (object); + + if ( flags&SP_OBJECT_WRITE_BUILD ) { + if ( repr == NULL ) repr = sp_repr_new ("svg:flowDiv"); + GSList *l = NULL; + for (SPObject* child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) { + Inkscape::XML::Node* c_repr=NULL; + if ( SP_IS_FLOWTSPAN (child) ) { + c_repr = child->updateRepr(NULL, flags); + } else if ( SP_IS_FLOWPARA(child) ) { + c_repr = child->updateRepr(NULL, flags); + } else if ( SP_IS_STRING(child) ) { + c_repr = sp_repr_new_text(SP_STRING(child)->string.c_str()); + } + if ( c_repr ) l = g_slist_prepend (l, c_repr); + } + while ( l ) { + repr->addChild((Inkscape::XML::Node *) l->data, NULL); + Inkscape::GC::release((Inkscape::XML::Node *) l->data); + l = g_slist_remove (l, l->data); + } + } else { + for (SPObject* child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) { + if ( SP_IS_FLOWTSPAN (child) ) { + child->updateRepr(flags); + } else if ( SP_IS_FLOWPARA(child) ) { + child->updateRepr(flags); + } else if ( SP_IS_STRING(child) ) { + SP_OBJECT_REPR (child)->setContent(SP_STRING(child)->string.c_str()); + } + } + } + + if (((SPObjectClass *) (flowdiv_parent_class))->write) + ((SPObjectClass *) (flowdiv_parent_class))->write (object, repr, flags); + + return repr; +} + + +/* + * + */ + +GType +sp_flowtspan_get_type (void) +{ + static GType group_type = 0; + if (!group_type) { + GTypeInfo group_info = { + sizeof (SPFlowtspanClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_flowtspan_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (SPFlowtspan), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_flowtspan_init, + NULL, /* value_table */ + }; + group_type = g_type_register_static (SP_TYPE_ITEM, "SPFlowtspan", &group_info, (GTypeFlags)0); + } + return group_type; +} + +static void +sp_flowtspan_class_init (SPFlowtspanClass *klass) +{ + GObjectClass * object_class; + SPObjectClass * sp_object_class; + + object_class = (GObjectClass *) klass; + sp_object_class = (SPObjectClass *) klass; + + flowtspan_parent_class = (SPItemClass *)g_type_class_ref (SP_TYPE_ITEM); + + sp_object_class->build = sp_flowtspan_build; + sp_object_class->set = sp_flowtspan_set; + sp_object_class->release = sp_flowtspan_release; + sp_object_class->write = sp_flowtspan_write; + sp_object_class->update = sp_flowtspan_update; + sp_object_class->modified = sp_flowtspan_modified; +} + +static void +sp_flowtspan_init (SPFlowtspan *group) +{ +} + +static void +sp_flowtspan_release (SPObject *object) +{ + if (((SPObjectClass *) flowtspan_parent_class)->release) + ((SPObjectClass *) flowtspan_parent_class)->release (object); +} +static void +sp_flowtspan_update (SPObject *object, SPCtx *ctx, unsigned int flags) +{ +// SPFlowtspan *group=SP_FLOWTSPAN (object); + SPItemCtx *ictx=(SPItemCtx *) ctx; + SPItemCtx cctx=*ictx; + + if (((SPObjectClass *) (flowtspan_parent_class))->update) + ((SPObjectClass *) (flowtspan_parent_class))->update (object, ctx, flags); + + if (flags & SP_OBJECT_MODIFIED_FLAG) flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + flags &= SP_OBJECT_MODIFIED_CASCADE; + + GSList* l = NULL; + for (SPObject *child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) { + g_object_ref (G_OBJECT (child)); + l = g_slist_prepend (l, child); + } + l = g_slist_reverse (l); + while (l) { + SPObject *child = SP_OBJECT (l->data); + l = g_slist_remove (l, child); + if (flags || (child->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + if (SP_IS_ITEM (child)) { + SPItem const &chi = *SP_ITEM(child); + cctx.i2doc = chi.transform * ictx->i2doc; + cctx.i2vp = chi.transform * ictx->i2vp; + child->updateDisplay((SPCtx *)&cctx, flags); + } else { + child->updateDisplay(ctx, flags); + } + } + g_object_unref (G_OBJECT (child)); + } +} +static void +sp_flowtspan_modified (SPObject *object, guint flags) +{ + SPObject *child; + GSList *l; + + if (((SPObjectClass *) (flowtspan_parent_class))->modified) + ((SPObjectClass *) (flowtspan_parent_class))->modified (object, flags); + + if (flags & SP_OBJECT_MODIFIED_FLAG) flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + flags &= SP_OBJECT_MODIFIED_CASCADE; + + l = NULL; + for (child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) { + g_object_ref (G_OBJECT (child)); + l = g_slist_prepend (l, child); + } + l = g_slist_reverse (l); + while (l) { + child = SP_OBJECT (l->data); + l = g_slist_remove (l, child); + if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->emitModified(flags); + } + g_object_unref (G_OBJECT (child)); + } +} +static void +sp_flowtspan_build (SPObject *object, SPDocument *doc, Inkscape::XML::Node *repr) +{ + if (((SPObjectClass *) flowtspan_parent_class)->build) + ((SPObjectClass *) flowtspan_parent_class)->build (object, doc, repr); +} +static void +sp_flowtspan_set (SPObject *object, unsigned int key, const gchar *value) +{ + if (((SPObjectClass *) flowtspan_parent_class)->set) + (((SPObjectClass *) flowtspan_parent_class)->set) (object, key, value); +} +static Inkscape::XML::Node * +sp_flowtspan_write (SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + if ( flags&SP_OBJECT_WRITE_BUILD ) { + if ( repr == NULL ) repr = sp_repr_new ("svg:flowSpan"); + GSList *l = NULL; + for (SPObject* child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) { + Inkscape::XML::Node* c_repr=NULL; + if ( SP_IS_FLOWTSPAN (child) ) { + c_repr = child->updateRepr(NULL, flags); + } else if ( SP_IS_FLOWPARA (child) ) { + c_repr = child->updateRepr(NULL, flags); + } else if ( SP_IS_STRING(child) ) { + c_repr = sp_repr_new_text(SP_STRING(child)->string.c_str()); + } + if ( c_repr ) l = g_slist_prepend (l, c_repr); + } + while ( l ) { + repr->addChild((Inkscape::XML::Node *) l->data, NULL); + Inkscape::GC::release((Inkscape::XML::Node *) l->data); + l = g_slist_remove (l, l->data); + } + } else { + for (SPObject* child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) { + if ( SP_IS_FLOWTSPAN (child) ) { + child->updateRepr(flags); + } else if ( SP_IS_FLOWPARA (child) ) { + child->updateRepr(flags); + } else if ( SP_IS_STRING(child) ) { + SP_OBJECT_REPR (child)->setContent(SP_STRING(child)->string.c_str()); + } + } + } + + if (((SPObjectClass *) (flowtspan_parent_class))->write) + ((SPObjectClass *) (flowtspan_parent_class))->write (object, repr, flags); + + return repr; +} + + + +/* + * + */ + +GType +sp_flowpara_get_type (void) +{ + static GType group_type = 0; + if (!group_type) { + GTypeInfo group_info = { + sizeof (SPFlowparaClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_flowpara_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (SPFlowpara), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_flowpara_init, + NULL, /* value_table */ + }; + group_type = g_type_register_static (SP_TYPE_ITEM, "SPFlowpara", &group_info, (GTypeFlags)0); + } + return group_type; +} + +static void +sp_flowpara_class_init (SPFlowparaClass *klass) +{ + GObjectClass * object_class; + SPObjectClass * sp_object_class; + + object_class = (GObjectClass *) klass; + sp_object_class = (SPObjectClass *) klass; + + flowpara_parent_class = (SPItemClass *)g_type_class_ref (SP_TYPE_ITEM); + + sp_object_class->build = sp_flowpara_build; + sp_object_class->set = sp_flowpara_set; + sp_object_class->release = sp_flowpara_release; + sp_object_class->write = sp_flowpara_write; + sp_object_class->update = sp_flowpara_update; + sp_object_class->modified = sp_flowpara_modified; +} + +static void +sp_flowpara_init (SPFlowpara *group) +{ +} +static void +sp_flowpara_release (SPObject *object) +{ + if (((SPObjectClass *) flowpara_parent_class)->release) + ((SPObjectClass *) flowpara_parent_class)->release (object); +} + +static void +sp_flowpara_update (SPObject *object, SPCtx *ctx, unsigned int flags) +{ + SPItemCtx *ictx=(SPItemCtx *) ctx; + SPItemCtx cctx=*ictx; + + if (((SPObjectClass *) (flowpara_parent_class))->update) + ((SPObjectClass *) (flowpara_parent_class))->update (object, ctx, flags); + + if (flags & SP_OBJECT_MODIFIED_FLAG) flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + flags &= SP_OBJECT_MODIFIED_CASCADE; + + GSList* l = NULL; + for (SPObject *child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) { + g_object_ref (G_OBJECT (child)); + l = g_slist_prepend (l, child); + } + l = g_slist_reverse (l); + while (l) { + SPObject *child = SP_OBJECT (l->data); + l = g_slist_remove (l, child); + if (flags || (child->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + if (SP_IS_ITEM (child)) { + SPItem const &chi = *SP_ITEM(child); + cctx.i2doc = chi.transform * ictx->i2doc; + cctx.i2vp = chi.transform * ictx->i2vp; + child->updateDisplay((SPCtx *)&cctx, flags); + } else { + child->updateDisplay(ctx, flags); + } + } + g_object_unref (G_OBJECT (child)); + } +} +static void +sp_flowpara_modified (SPObject *object, guint flags) +{ + SPObject *child; + GSList *l; + + if (((SPObjectClass *) (flowpara_parent_class))->modified) + ((SPObjectClass *) (flowpara_parent_class))->modified (object, flags); + + if (flags & SP_OBJECT_MODIFIED_FLAG) flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + flags &= SP_OBJECT_MODIFIED_CASCADE; + + l = NULL; + for (child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) { + g_object_ref (G_OBJECT (child)); + l = g_slist_prepend (l, child); + } + l = g_slist_reverse (l); + while (l) { + child = SP_OBJECT (l->data); + l = g_slist_remove (l, child); + if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->emitModified(flags); + } + g_object_unref (G_OBJECT (child)); + } +} +static void +sp_flowpara_build (SPObject *object, SPDocument *doc, Inkscape::XML::Node *repr) +{ + if (((SPObjectClass *) flowpara_parent_class)->build) + ((SPObjectClass *) flowpara_parent_class)->build (object, doc, repr); +} +static void +sp_flowpara_set (SPObject *object, unsigned int key, const gchar *value) +{ + if (((SPObjectClass *) flowpara_parent_class)->set) + (((SPObjectClass *) flowpara_parent_class)->set) (object, key, value); +} +static Inkscape::XML::Node * +sp_flowpara_write (SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + // SPFlowpara *group = SP_FLOWPARA (object); + + if ( flags&SP_OBJECT_WRITE_BUILD ) { + if ( repr == NULL ) repr = sp_repr_new ("svg:flowPara"); + GSList *l = NULL; + for (SPObject* child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) { + Inkscape::XML::Node* c_repr=NULL; + if ( SP_IS_FLOWTSPAN (child) ) { + c_repr = child->updateRepr(NULL, flags); + } else if ( SP_IS_FLOWPARA (child) ) { + c_repr = child->updateRepr(NULL, flags); + } else if ( SP_IS_STRING(child) ) { + c_repr = sp_repr_new_text(SP_STRING(child)->string.c_str()); + } + if ( c_repr ) l = g_slist_prepend (l, c_repr); + } + while ( l ) { + repr->addChild((Inkscape::XML::Node *) l->data, NULL); + Inkscape::GC::release((Inkscape::XML::Node *) l->data); + l = g_slist_remove (l, l->data); + } + } else { + for (SPObject* child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) { + if ( SP_IS_FLOWTSPAN (child) ) { + child->updateRepr(flags); + } else if ( SP_IS_FLOWPARA (child) ) { + child->updateRepr(flags); + } else if ( SP_IS_STRING(child) ) { + SP_OBJECT_REPR (child)->setContent(SP_STRING(child)->string.c_str()); + } + } + } + + if (((SPObjectClass *) (flowpara_parent_class))->write) + ((SPObjectClass *) (flowpara_parent_class))->write (object, repr, flags); + + return repr; +} + +/* + * + */ + +GType +sp_flowline_get_type (void) +{ + static GType group_type = 0; + if (!group_type) { + GTypeInfo group_info = { + sizeof (SPFlowlineClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_flowline_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (SPFlowline), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_flowline_init, + NULL, /* value_table */ + }; + group_type = g_type_register_static (SP_TYPE_OBJECT, "SPFlowline", &group_info, (GTypeFlags)0); + } + return group_type; +} + +static void +sp_flowline_class_init (SPFlowlineClass *klass) +{ + GObjectClass * object_class; + SPObjectClass * sp_object_class; + + object_class = (GObjectClass *) klass; + sp_object_class = (SPObjectClass *) klass; + + flowline_parent_class = (SPObjectClass *)g_type_class_ref (SP_TYPE_OBJECT); + + sp_object_class->release = sp_flowline_release; + sp_object_class->write = sp_flowline_write; + sp_object_class->modified = sp_flowline_modified; +} + +static void +sp_flowline_init (SPFlowline *group) +{ +} +static void +sp_flowline_release (SPObject *object) +{ + if (((SPObjectClass *) flowline_parent_class)->release) + ((SPObjectClass *) flowline_parent_class)->release (object); +} + +static void +sp_flowline_modified (SPObject *object, guint flags) +{ + if (((SPObjectClass *) (flowline_parent_class))->modified) + ((SPObjectClass *) (flowline_parent_class))->modified (object, flags); + + if (flags & SP_OBJECT_MODIFIED_FLAG) flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + flags &= SP_OBJECT_MODIFIED_CASCADE; +} +static Inkscape::XML::Node * +sp_flowline_write (SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + if ( flags&SP_OBJECT_WRITE_BUILD ) { + if ( repr == NULL ) repr = sp_repr_new ("svg:flowLine"); + } else { + } + + if (((SPObjectClass *) (flowline_parent_class))->write) + ((SPObjectClass *) (flowline_parent_class))->write (object, repr, flags); + + return repr; +} + +/* + * + */ + +GType +sp_flowregionbreak_get_type (void) +{ + static GType group_type = 0; + if (!group_type) { + GTypeInfo group_info = { + sizeof (SPFlowregionbreakClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_flowregionbreak_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (SPFlowregionbreak), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_flowregionbreak_init, + NULL, /* value_table */ + }; + group_type = g_type_register_static (SP_TYPE_OBJECT, "SPFlowregionbreak", &group_info, (GTypeFlags)0); + } + return group_type; +} + +static void +sp_flowregionbreak_class_init (SPFlowregionbreakClass *klass) +{ + GObjectClass * object_class; + SPObjectClass * sp_object_class; + + object_class = (GObjectClass *) klass; + sp_object_class = (SPObjectClass *) klass; + + flowregionbreak_parent_class = (SPObjectClass *)g_type_class_ref (SP_TYPE_OBJECT); + + sp_object_class->release = sp_flowregionbreak_release; + sp_object_class->write = sp_flowregionbreak_write; + sp_object_class->modified = sp_flowregionbreak_modified; +} + +static void +sp_flowregionbreak_init (SPFlowregionbreak *group) +{ +} +static void +sp_flowregionbreak_release (SPObject *object) +{ + if (((SPObjectClass *) flowregionbreak_parent_class)->release) + ((SPObjectClass *) flowregionbreak_parent_class)->release (object); +} + +static void +sp_flowregionbreak_modified (SPObject *object, guint flags) +{ + if (((SPObjectClass *) (flowregionbreak_parent_class))->modified) + ((SPObjectClass *) (flowregionbreak_parent_class))->modified (object, flags); + + if (flags & SP_OBJECT_MODIFIED_FLAG) flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + flags &= SP_OBJECT_MODIFIED_CASCADE; +} +static Inkscape::XML::Node * +sp_flowregionbreak_write (SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + if ( flags&SP_OBJECT_WRITE_BUILD ) { + if ( repr == NULL ) repr = sp_repr_new ("svg:flowLine"); + } else { + } + + if (((SPObjectClass *) (flowregionbreak_parent_class))->write) + ((SPObjectClass *) (flowregionbreak_parent_class))->write (object, repr, flags); + + return repr; +} diff --git a/src/sp-flowdiv.h b/src/sp-flowdiv.h new file mode 100644 index 000000000..c01ada3b0 --- /dev/null +++ b/src/sp-flowdiv.h @@ -0,0 +1,84 @@ +#ifndef __SP_ITEM_FLOWDIV_H__ +#define __SP_ITEM_FLOWDIV_H__ + +/* + */ + +#include "sp-object.h" +#include "sp-item.h" + +#define SP_TYPE_FLOWDIV (sp_flowdiv_get_type ()) +#define SP_FLOWDIV(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_FLOWDIV, SPFlowdiv)) +#define SP_FLOWDIV_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_FLOWDIV, SPFlowdivClass)) +#define SP_IS_FLOWDIV(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWDIV)) +#define SP_IS_FLOWDIV_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_FLOWDIV)) + +#define SP_TYPE_FLOWTSPAN (sp_flowtspan_get_type ()) +#define SP_FLOWTSPAN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_FLOWTSPAN, SPFlowtspan)) +#define SP_FLOWTSPAN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_FLOWTSPAN, SPFlowtspanClass)) +#define SP_IS_FLOWTSPAN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTSPAN)) +#define SP_IS_FLOWTSPAN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_FLOWTSPAN)) + +#define SP_TYPE_FLOWPARA (sp_flowpara_get_type ()) +#define SP_FLOWPARA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_FLOWPARA, SPFlowpara)) +#define SP_FLOWPARA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_FLOWPARA, SPFlowparaClass)) +#define SP_IS_FLOWPARA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWPARA)) +#define SP_IS_FLOWPARA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_FLOWPARA)) + +#define SP_TYPE_FLOWLINE (sp_flowline_get_type ()) +#define SP_FLOWLINE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_FLOWLINE, SPFlowline)) +#define SP_FLOWLINE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_FLOWLINE, SPFlowlineClass)) +#define SP_IS_FLOWLINE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWLINE)) +#define SP_IS_FLOWLINE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_FLOWLINE)) + +#define SP_TYPE_FLOWREGIONBREAK (sp_flowregionbreak_get_type ()) +#define SP_FLOWREGIONBREAK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_FLOWREGIONBREAK, SPFlowregionbreak)) +#define SP_FLOWREGIONBREAK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_FLOWREGIONBREAK, SPFlowregionbreakClass)) +#define SP_IS_FLOWREGIONBREAK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGIONBREAK)) +#define SP_IS_FLOWREGIONBREAK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_FLOWREGIONBREAK)) + +// these 3 are derivatives of SPItem to get the automatic style handling +struct SPFlowdiv : public SPItem { +}; + +struct SPFlowtspan : public SPItem { +}; + +struct SPFlowpara : public SPItem { +}; + +// these do not need any style +struct SPFlowline : public SPObject { +}; + +struct SPFlowregionbreak : public SPObject { +}; + + +struct SPFlowdivClass { + SPItemClass parent_class; +}; + +struct SPFlowtspanClass { + SPItemClass parent_class; +}; + +struct SPFlowparaClass { + SPItemClass parent_class; +}; + +struct SPFlowlineClass { + SPObjectClass parent_class; +}; + +struct SPFlowregionbreakClass { + SPObjectClass parent_class; +}; + +GType sp_flowdiv_get_type (void); +GType sp_flowtspan_get_type (void); +GType sp_flowpara_get_type (void); +GType sp_flowline_get_type (void); +GType sp_flowregionbreak_get_type (void); + +#endif diff --git a/src/sp-flowregion.cpp b/src/sp-flowregion.cpp new file mode 100644 index 000000000..4071e4d92 --- /dev/null +++ b/src/sp-flowregion.cpp @@ -0,0 +1,544 @@ +#define __SP_FLOWREGION_C__ + +/* + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include + +#include +#include "display/curve.h" +#include "sp-shape.h" +#include "sp-text.h" +#include "sp-use.h" +#include "style.h" + +#include "sp-flowregion.h" + +#include "display/canvas-bpath.h" + + +#include "livarot/Path.h" +#include "livarot/Shape.h" + +static void sp_flowregion_class_init (SPFlowregionClass *klass); +static void sp_flowregion_init (SPFlowregion *group); +static void sp_flowregion_dispose (GObject *object); + +static void sp_flowregion_child_added (SPObject * object, Inkscape::XML::Node * child, Inkscape::XML::Node * ref); +static void sp_flowregion_remove_child (SPObject * object, Inkscape::XML::Node * child); +static void sp_flowregion_update (SPObject *object, SPCtx *ctx, guint flags); +static void sp_flowregion_modified (SPObject *object, guint flags); +static Inkscape::XML::Node *sp_flowregion_write (SPObject *object, Inkscape::XML::Node *repr, guint flags); + +static gchar * sp_flowregion_description (SPItem * item); + +static SPItemClass * flowregion_parent_class; + +static void sp_flowregionexclude_class_init (SPFlowregionExcludeClass *klass); +static void sp_flowregionexclude_init (SPFlowregionExclude *group); +static void sp_flowregionexclude_dispose (GObject *object); + +static void sp_flowregionexclude_child_added (SPObject * object, Inkscape::XML::Node * child, Inkscape::XML::Node * ref); +static void sp_flowregionexclude_remove_child (SPObject * object, Inkscape::XML::Node * child); +static void sp_flowregionexclude_update (SPObject *object, SPCtx *ctx, guint flags); +static void sp_flowregionexclude_modified (SPObject *object, guint flags); +static Inkscape::XML::Node *sp_flowregionexclude_write (SPObject *object, Inkscape::XML::Node *repr, guint flags); + +static gchar * sp_flowregionexclude_description (SPItem * item); + +static SPItemClass * flowregionexclude_parent_class; + + +static void GetDest(SPObject* child,Shape **computed,NR::Matrix itr_mat); + +GType +sp_flowregion_get_type (void) +{ + static GType group_type = 0; + if (!group_type) { + GTypeInfo group_info = { + sizeof (SPFlowregionClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_flowregion_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (SPFlowregion), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_flowregion_init, + NULL, /* value_table */ + }; + group_type = g_type_register_static (SP_TYPE_ITEM, "SPFlowregion", &group_info, (GTypeFlags)0); + } + return group_type; +} + +static void +sp_flowregion_class_init (SPFlowregionClass *klass) +{ + GObjectClass * object_class; + SPObjectClass * sp_object_class; + SPItemClass * item_class; + + object_class = (GObjectClass *) klass; + sp_object_class = (SPObjectClass *) klass; + item_class = (SPItemClass *) klass; + + flowregion_parent_class = (SPItemClass *)g_type_class_ref (SP_TYPE_ITEM); + + object_class->dispose = sp_flowregion_dispose; + + sp_object_class->child_added = sp_flowregion_child_added; + sp_object_class->remove_child = sp_flowregion_remove_child; + sp_object_class->update = sp_flowregion_update; + sp_object_class->modified = sp_flowregion_modified; + sp_object_class->write = sp_flowregion_write; + + item_class->description = sp_flowregion_description; +} + +static void +sp_flowregion_init (SPFlowregion *group) +{ + new (&group->computed) std::vector; +} + +static void +sp_flowregion_dispose(GObject *object) +{ + SPFlowregion *group=(SPFlowregion *)object; + for (std::vector::iterator it = group->computed.begin() ; it != group->computed.end() ; it++) + delete *it; + group->computed.~vector(); +} + +static void +sp_flowregion_child_added (SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref) +{ + SPItem *item; + + item = SP_ITEM (object); + + if (((SPObjectClass *) (flowregion_parent_class))->child_added) + (* ((SPObjectClass *) (flowregion_parent_class))->child_added) (object, child, ref); + + object->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +/* fixme: hide (Lauris) */ + +static void +sp_flowregion_remove_child (SPObject * object, Inkscape::XML::Node * child) +{ + if (((SPObjectClass *) (flowregion_parent_class))->remove_child) + (* ((SPObjectClass *) (flowregion_parent_class))->remove_child) (object, child); + + object->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + + +static void +sp_flowregion_update (SPObject *object, SPCtx *ctx, unsigned int flags) +{ + SPFlowregion *group; + SPObject *child; + SPItemCtx *ictx, cctx; + GSList *l; + + group = SP_FLOWREGION (object); + ictx = (SPItemCtx *) ctx; + cctx = *ictx; + + if (((SPObjectClass *) (flowregion_parent_class))->update) + ((SPObjectClass *) (flowregion_parent_class))->update (object, ctx, flags); + + if (flags & SP_OBJECT_MODIFIED_FLAG) flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + flags &= SP_OBJECT_MODIFIED_CASCADE; + + l = NULL; + for (child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) { + g_object_ref (G_OBJECT (child)); + l = g_slist_prepend (l, child); + } + l = g_slist_reverse (l); + while (l) { + child = SP_OBJECT (l->data); + l = g_slist_remove (l, child); + if (flags || (child->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + if (SP_IS_ITEM (child)) { + SPItem const &chi = *SP_ITEM(child); + cctx.i2doc = chi.transform * ictx->i2doc; + cctx.i2vp = chi.transform * ictx->i2vp; + child->updateDisplay((SPCtx *)&cctx, flags); + } else { + child->updateDisplay(ctx, flags); + } + } + g_object_unref (G_OBJECT (child)); + } + + group->UpdateComputed(); +} + +void SPFlowregion::UpdateComputed(void) +{ + SPObject* object=SP_OBJECT(this); + + NR::Matrix itr_mat=sp_item_i2root_affine (SP_ITEM(object)); + itr_mat=itr_mat.inverse(); + + for (std::vector::iterator it = computed.begin() ; it != computed.end() ; it++) + delete *it; + computed.clear(); + + for (SPObject* child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) { + Shape *shape = NULL; + GetDest(child,&shape,itr_mat); + computed.push_back(shape); + } +} + +static void +sp_flowregion_modified (SPObject *object, guint flags) +{ + SPFlowregion *group; + SPObject *child; + GSList *l; + + group = SP_FLOWREGION (object); + + if (flags & SP_OBJECT_MODIFIED_FLAG) flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + flags &= SP_OBJECT_MODIFIED_CASCADE; + + l = NULL; + for (child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) { + g_object_ref (G_OBJECT (child)); + l = g_slist_prepend (l, child); + } + l = g_slist_reverse (l); + while (l) { + child = SP_OBJECT (l->data); + l = g_slist_remove (l, child); + if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->emitModified(flags); + } + g_object_unref (G_OBJECT (child)); + } +} + +static Inkscape::XML::Node * +sp_flowregion_write (SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + if (flags & SP_OBJECT_WRITE_BUILD) { + if ( repr == NULL ) repr = sp_repr_new ("svg:flowRegion"); + + GSList *l = NULL; + for ( SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) { + Inkscape::XML::Node *crepr = child->updateRepr(NULL, flags); + if (crepr) l = g_slist_prepend(l, crepr); + } + + while (l) { + repr->addChild((Inkscape::XML::Node *) l->data, NULL); + Inkscape::GC::release((Inkscape::XML::Node *) l->data); + l = g_slist_remove(l, l->data); + } + + } else { + for ( SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) { + child->updateRepr(flags); + } + } + + if (((SPObjectClass *) (flowregion_parent_class))->write) + ((SPObjectClass *) (flowregion_parent_class))->write (object, repr, flags); + + return repr; +} + + +static gchar *sp_flowregion_description(SPItem *item) +{ + // TRANSLATORS: "Flow region" is an area where text is allowed to flow + return g_strdup_printf(_("Flow region")); +} + +/* + * + */ + +GType +sp_flowregionexclude_get_type (void) +{ + static GType group_type = 0; + if (!group_type) { + GTypeInfo group_info = { + sizeof (SPFlowregionExcludeClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_flowregionexclude_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (SPFlowregionExclude), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_flowregionexclude_init, + NULL, /* value_table */ + }; + group_type = g_type_register_static (SP_TYPE_ITEM, "SPFlowregionExclude", &group_info, (GTypeFlags)0); + } + return group_type; +} + +static void +sp_flowregionexclude_class_init (SPFlowregionExcludeClass *klass) +{ + GObjectClass * object_class; + SPObjectClass * sp_object_class; + SPItemClass * item_class; + + object_class = (GObjectClass *) klass; + sp_object_class = (SPObjectClass *) klass; + item_class = (SPItemClass *) klass; + + flowregionexclude_parent_class = (SPItemClass *)g_type_class_ref (SP_TYPE_ITEM); + + object_class->dispose = sp_flowregionexclude_dispose; + + sp_object_class->child_added = sp_flowregionexclude_child_added; + sp_object_class->remove_child = sp_flowregionexclude_remove_child; + sp_object_class->update = sp_flowregionexclude_update; + sp_object_class->modified = sp_flowregionexclude_modified; + sp_object_class->write = sp_flowregionexclude_write; + + item_class->description = sp_flowregionexclude_description; +} + +static void +sp_flowregionexclude_init (SPFlowregionExclude *group) +{ + group->computed = NULL; +} + +static void +sp_flowregionexclude_dispose(GObject *object) +{ + SPFlowregionExclude *group=(SPFlowregionExclude *)object; + if (group->computed) { + delete group->computed; + group->computed = NULL; + } +} + +static void +sp_flowregionexclude_child_added (SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref) +{ + SPItem *item; + + item = SP_ITEM (object); + + if (((SPObjectClass *) (flowregionexclude_parent_class))->child_added) + (* ((SPObjectClass *) (flowregionexclude_parent_class))->child_added) (object, child, ref); + + object->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +/* fixme: hide (Lauris) */ + +static void +sp_flowregionexclude_remove_child (SPObject * object, Inkscape::XML::Node * child) +{ + if (((SPObjectClass *) (flowregionexclude_parent_class))->remove_child) + (* ((SPObjectClass *) (flowregionexclude_parent_class))->remove_child) (object, child); + + object->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + + +static void +sp_flowregionexclude_update (SPObject *object, SPCtx *ctx, unsigned int flags) +{ + SPFlowregionExclude *group; + SPObject *child; + SPItemCtx *ictx, cctx; + GSList *l; + + group = SP_FLOWREGIONEXCLUDE (object); + ictx = (SPItemCtx *) ctx; + cctx = *ictx; + + if (((SPObjectClass *) (flowregionexclude_parent_class))->update) + ((SPObjectClass *) (flowregionexclude_parent_class))->update (object, ctx, flags); + + if (flags & SP_OBJECT_MODIFIED_FLAG) flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + flags &= SP_OBJECT_MODIFIED_CASCADE; + + l = NULL; + for (child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) { + g_object_ref (G_OBJECT (child)); + l = g_slist_prepend (l, child); + } + l = g_slist_reverse (l); + while (l) { + child = SP_OBJECT (l->data); + l = g_slist_remove (l, child); + if (flags || (child->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + if (SP_IS_ITEM (child)) { + SPItem const &chi = *SP_ITEM(child); + cctx.i2doc = chi.transform * ictx->i2doc; + cctx.i2vp = chi.transform * ictx->i2vp; + child->updateDisplay((SPCtx *)&cctx, flags); + } else { + child->updateDisplay(ctx, flags); + } + } + g_object_unref (G_OBJECT (child)); + } + + group->UpdateComputed(); +} +void SPFlowregionExclude::UpdateComputed(void) +{ + SPObject* object=SP_OBJECT(this); + + if (computed) { + delete computed; + computed = NULL; + } + NR::Matrix itr_mat=sp_item_i2root_affine (SP_ITEM(object)); + itr_mat=itr_mat.inverse(); + + for (SPObject* child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) { + GetDest(child,&computed,itr_mat); + } +} + +static void +sp_flowregionexclude_modified (SPObject *object, guint flags) +{ + SPFlowregionExclude *group; + SPObject *child; + GSList *l; + + group = SP_FLOWREGIONEXCLUDE (object); + + if (flags & SP_OBJECT_MODIFIED_FLAG) flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + flags &= SP_OBJECT_MODIFIED_CASCADE; + + l = NULL; + for (child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) { + g_object_ref (G_OBJECT (child)); + l = g_slist_prepend (l, child); + } + l = g_slist_reverse (l); + while (l) { + child = SP_OBJECT (l->data); + l = g_slist_remove (l, child); + if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->emitModified(flags); + } + g_object_unref (G_OBJECT (child)); + } +} + +static Inkscape::XML::Node * +sp_flowregionexclude_write (SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + if (flags & SP_OBJECT_WRITE_BUILD) { + if ( repr == NULL ) repr = sp_repr_new ("svg:flowRegionExclude"); + + GSList *l = NULL; + for ( SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) { + Inkscape::XML::Node *crepr = child->updateRepr(NULL, flags); + if (crepr) l = g_slist_prepend(l, crepr); + } + + while (l) { + repr->addChild((Inkscape::XML::Node *) l->data, NULL); + Inkscape::GC::release((Inkscape::XML::Node *) l->data); + l = g_slist_remove(l, l->data); + } + + } else { + for ( SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) { + child->updateRepr(flags); + } + } + + if (((SPObjectClass *) (flowregionexclude_parent_class))->write) + ((SPObjectClass *) (flowregionexclude_parent_class))->write (object, repr, flags); + + return repr; +} + + +static gchar *sp_flowregionexclude_description(SPItem *item) +{ + /* TRANSLATORS: A region "cut out of" a flow region; text is not allowed to flow inside the + * flow excluded region. flowRegionExclude in SVG 1.2: see + * http://www.w3.org/TR/2004/WD-SVG12-20041027/flow.html#flowRegion-elem and + * http://www.w3.org/TR/2004/WD-SVG12-20041027/flow.html#flowRegionExclude-elem. */ + return g_strdup_printf(_("Flow excluded region")); +} + +/* + * + */ + +static void UnionShape(Shape **base_shape, Shape const *add_shape) +{ + if (*base_shape == NULL) + *base_shape = new Shape; + if ( (*base_shape)->hasEdges() == false ) { + (*base_shape)->Copy(const_cast(add_shape)); + } else if ( add_shape->hasEdges() ) { + Shape* temp=new Shape; + temp->Booleen(const_cast(add_shape), *base_shape, bool_op_union); + delete *base_shape; + *base_shape = temp; + } +} + +static void GetDest(SPObject* child,Shape **computed,NR::Matrix itr_mat) +{ + if ( child == NULL ) return; + + SPCurve *curve=NULL; + + SPObject* u_child=child; + if ( SP_IS_USE(u_child) ) { + u_child=SP_USE(u_child)->child; + } + if ( SP_IS_SHAPE (u_child) ) { + curve = sp_shape_get_curve (SP_SHAPE (u_child)); + } else if ( SP_IS_TEXT (u_child) ) { + curve = SP_TEXT (u_child)->getNormalizedBpath (); + } + + if ( curve ) { + Path* temp=new Path; + NR::Matrix tr_mat=sp_item_i2root_affine (SP_ITEM(u_child)); + tr_mat=itr_mat*tr_mat; + temp->LoadArtBPath(curve->bpath,tr_mat,true); + Shape* n_shp=new Shape; + temp->Convert(0.25); + temp->Fill(n_shp,0); + Shape* uncross=new Shape; + SPStyle* style=SP_OBJECT_STYLE(u_child); + if ( style && style->fill_rule.computed == SP_WIND_RULE_EVENODD ) { + uncross->ConvertToShape(n_shp,fill_oddEven); + } else { + uncross->ConvertToShape(n_shp,fill_nonZero); + } + UnionShape(computed, uncross); + delete uncross; + delete n_shp; + delete temp; + sp_curve_unref(curve); + } else { +// printf("no curve\n"); + } +} + diff --git a/src/sp-flowregion.h b/src/sp-flowregion.h new file mode 100644 index 000000000..46b584cf2 --- /dev/null +++ b/src/sp-flowregion.h @@ -0,0 +1,50 @@ +#ifndef __SP_ITEM_FLOWREGION_H__ +#define __SP_ITEM_FLOWREGION_H__ + +/* + */ + +#include "sp-item.h" + +#define SP_TYPE_FLOWREGION (sp_flowregion_get_type ()) +#define SP_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_FLOWREGION, SPFlowregion)) +#define SP_FLOWREGION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_FLOWREGION, SPFlowregionClass)) +#define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION)) +#define SP_IS_FLOWREGION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_FLOWREGION)) + +#define SP_TYPE_FLOWREGIONEXCLUDE (sp_flowregionexclude_get_type ()) +#define SP_FLOWREGIONEXCLUDE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_FLOWREGIONEXCLUDE, SPFlowregionExclude)) +#define SP_FLOWREGIONEXCLUDE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_FLOWREGIONEXCLUDE, SPFlowregionExcludeClass)) +#define SP_IS_FLOWREGIONEXCLUDE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGIONEXCLUDE)) +#define SP_IS_FLOWREGIONEXCLUDE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_FLOWREGIONEXCLUDE)) + +class Path; +class Shape; +class flow_dest; +class FloatLigne; + +struct SPFlowregion : public SPItem { + std::vector computed; + + void UpdateComputed(void); +}; + +struct SPFlowregionClass { + SPItemClass parent_class; +}; + +GType sp_flowregion_get_type (void); + +struct SPFlowregionExclude : public SPItem { + Shape *computed; + + void UpdateComputed(void); +}; + +struct SPFlowregionExcludeClass { + SPItemClass parent_class; +}; + +GType sp_flowregionexclude_get_type (void); + +#endif diff --git a/src/sp-flowtext.cpp b/src/sp-flowtext.cpp new file mode 100644 index 000000000..fbf0efabd --- /dev/null +++ b/src/sp-flowtext.cpp @@ -0,0 +1,681 @@ +/* + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include + +#include "attributes.h" +#include "xml/repr.h" +#include "style.h" +#include "inkscape.h" +#include "document.h" +#include "selection.h" +#include "desktop-handles.h" +#include "desktop.h" +#include "desktop-affine.h" + +#include "xml/repr.h" + +#include "sp-flowdiv.h" +#include "sp-flowregion.h" +#include "sp-flowtext.h" +#include "sp-string.h" +#include "sp-use.h" +#include "sp-rect.h" +#include "text-tag-attributes.h" + + +#include "livarot/Shape.h" + +#include "display/nr-arena-glyphs.h" + + +static void sp_flowtext_class_init(SPFlowtextClass *klass); +static void sp_flowtext_init(SPFlowtext *group); +static void sp_flowtext_dispose(GObject *object); + +static void sp_flowtext_child_added(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref); +static void sp_flowtext_remove_child(SPObject *object, Inkscape::XML::Node *child); +static void sp_flowtext_update(SPObject *object, SPCtx *ctx, guint flags); +static void sp_flowtext_modified(SPObject *object, guint flags); +static Inkscape::XML::Node *sp_flowtext_write(SPObject *object, Inkscape::XML::Node *repr, guint flags); +static void sp_flowtext_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); +static void sp_flowtext_set(SPObject *object, unsigned key, gchar const *value); + +static void sp_flowtext_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const flags); +static void sp_flowtext_print(SPItem *item, SPPrintContext *ctx); +static gchar *sp_flowtext_description(SPItem *item); +static NRArenaItem *sp_flowtext_show(SPItem *item, NRArena *arena, unsigned key, unsigned flags); +static void sp_flowtext_hide(SPItem *item, unsigned key); + +static SPItemClass *parent_class; + +GType +sp_flowtext_get_type(void) +{ + static GType group_type = 0; + if (!group_type) { + GTypeInfo group_info = { + sizeof(SPFlowtextClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_flowtext_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof(SPFlowtext), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_flowtext_init, + NULL, /* value_table */ + }; + group_type = g_type_register_static(SP_TYPE_ITEM, "SPFlowtext", &group_info, (GTypeFlags)0); + } + return group_type; +} + +static void +sp_flowtext_class_init(SPFlowtextClass *klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + SPObjectClass *sp_object_class = (SPObjectClass *) klass; + SPItemClass *item_class = (SPItemClass *) klass; + + parent_class = (SPItemClass *)g_type_class_ref(SP_TYPE_ITEM); + + object_class->dispose = sp_flowtext_dispose; + + sp_object_class->child_added = sp_flowtext_child_added; + sp_object_class->remove_child = sp_flowtext_remove_child; + sp_object_class->update = sp_flowtext_update; + sp_object_class->modified = sp_flowtext_modified; + sp_object_class->write = sp_flowtext_write; + sp_object_class->build = sp_flowtext_build; + sp_object_class->set = sp_flowtext_set; + + item_class->bbox = sp_flowtext_bbox; + item_class->print = sp_flowtext_print; + item_class->description = sp_flowtext_description; + item_class->show = sp_flowtext_show; + item_class->hide = sp_flowtext_hide; +} + +static void +sp_flowtext_init(SPFlowtext *group) +{ + new (&group->layout) Inkscape::Text::Layout(); +} + +static void +sp_flowtext_dispose(GObject *object) +{ + SPFlowtext *group = (SPFlowtext*)object; + + group->layout.~Layout(); +} + +static void +sp_flowtext_child_added(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref) +{ + if (((SPObjectClass *) (parent_class))->child_added) + (* ((SPObjectClass *) (parent_class))->child_added)(object, child, ref); + + object->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +/* fixme: hide (Lauris) */ + +static void +sp_flowtext_remove_child(SPObject *object, Inkscape::XML::Node *child) +{ + if (((SPObjectClass *) (parent_class))->remove_child) + (* ((SPObjectClass *) (parent_class))->remove_child)(object, child); + + object->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +static void +sp_flowtext_update(SPObject *object, SPCtx *ctx, unsigned flags) +{ + SPFlowtext *group = SP_FLOWTEXT(object); + SPItemCtx *ictx = (SPItemCtx *) ctx; + SPItemCtx cctx = *ictx; + + if (((SPObjectClass *) (parent_class))->update) + ((SPObjectClass *) (parent_class))->update(object, ctx, flags); + + if (flags & SP_OBJECT_MODIFIED_FLAG) flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + flags &= SP_OBJECT_MODIFIED_CASCADE; + + GSList *l = NULL; + for (SPObject *child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) { + g_object_ref(G_OBJECT(child)); + l = g_slist_prepend(l, child); + } + l = g_slist_reverse(l); + while (l) { + SPObject *child = SP_OBJECT(l->data); + l = g_slist_remove(l, child); + if (flags || (child->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + if (SP_IS_ITEM(child)) { + SPItem const &chi = *SP_ITEM(child); + cctx.i2doc = chi.transform * ictx->i2doc; + cctx.i2vp = chi.transform * ictx->i2vp; + child->updateDisplay((SPCtx *)&cctx, flags); + } else { + child->updateDisplay(ctx, flags); + } + } + g_object_unref(G_OBJECT(child)); + } + + group->rebuildLayout(); + + // pass the bbox of the flowtext object as paintbox (used for paintserver fills) + NRRect paintbox; + sp_item_invoke_bbox(group, &paintbox, NR::identity(), TRUE); + for (SPItemView *v = group->display; v != NULL; v = v->next) { + group->_clearFlow(NR_ARENA_GROUP(v->arenaitem)); + group->layout.show(NR_ARENA_GROUP(v->arenaitem), &paintbox); + } +} + +static void +sp_flowtext_modified(SPObject *object, guint flags) +{ + SPObject *ft = SP_FLOWTEXT (object); + SPObject *region = NULL; + + if (flags & SP_OBJECT_MODIFIED_FLAG) flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + flags &= SP_OBJECT_MODIFIED_CASCADE; + + for (SPObject *o = sp_object_first_child(SP_OBJECT(ft)) ; o != NULL ; o = SP_OBJECT_NEXT(o) ) { + if (SP_IS_FLOWREGION(o)) { + region = o; + break; + } + } + + if (!region) return; + + if (flags || (region->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + region->emitModified(flags); // pass down to the region only + } +} + +static void +sp_flowtext_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + object->_requireSVGVersion(Inkscape::Version(1, 2)); + + if (((SPObjectClass *) (parent_class))->build) { + (* ((SPObjectClass *) (parent_class))->build)(object, document, repr); + } + + sp_object_read_attr(object, "inkscape:layoutOptions"); // must happen after css has been read +} + +static void +sp_flowtext_set(SPObject *object, unsigned key, gchar const *value) +{ + SPFlowtext *group = (SPFlowtext *) object; + + switch (key) { + case SP_ATTR_LAYOUT_OPTIONS: { + // deprecated attribute, read for backward compatibility only + SPCSSAttr *opts = sp_repr_css_attr((SP_OBJECT(group))->repr, "inkscape:layoutOptions"); + { + gchar const *val = sp_repr_css_property(opts, "justification", NULL); + if (val != NULL && !object->style->text_align.set) { + if ( strcmp(val, "0") == 0 || strcmp(val, "false") == 0 ) { + object->style->text_align.value = SP_CSS_TEXT_ALIGN_LEFT; + } else { + object->style->text_align.value = SP_CSS_TEXT_ALIGN_JUSTIFY; + } + object->style->text_align.set = TRUE; + object->style->text_align.inherit = FALSE; + object->style->text_align.computed = object->style->text_align.value; + } + } + /* no equivalent css attribute for these two (yet) + { + gchar const *val = sp_repr_css_property(opts, "layoutAlgo", NULL); + if ( val == NULL ) { + group->algo = 0; + } else { + if ( strcmp(val, "better") == 0 ) { // knuth-plass, never worked for general cases + group->algo = 2; + } else if ( strcmp(val, "simple") == 0 ) { // greedy, but allowed lines to be compressed by up to 20% if it would make them fit + group->algo = 1; + } else if ( strcmp(val, "default") == 0 ) { // the same one we use, a standard greedy + group->algo = 0; + } + } + } + { // This would probably translate to padding-left, if SPStyle had it. + gchar const *val = sp_repr_css_property(opts, "par-indent", NULL); + if ( val == NULL ) { + group->par_indent = 0.0; + } else { + sp_repr_get_double((Inkscape::XML::Node*)opts, "par-indent", &group->par_indent); + } + } + */ + sp_repr_css_attr_unref(opts); + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + default: + if (((SPObjectClass *) (parent_class))->set) { + (* ((SPObjectClass *) (parent_class))->set)(object, key, value); + } + break; + } +} + +static Inkscape::XML::Node * +sp_flowtext_write(SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + if ( flags & SP_OBJECT_WRITE_BUILD ) { + if ( repr == NULL ) repr = sp_repr_new("svg:flowRoot"); + GSList *l = NULL; + for (SPObject *child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) { + Inkscape::XML::Node *c_repr = NULL; + if ( SP_IS_FLOWDIV(child) || SP_IS_FLOWPARA(child) || SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child)) { + c_repr = child->updateRepr(NULL, flags); + } + if ( c_repr ) l = g_slist_prepend(l, c_repr); + } + while ( l ) { + repr->addChild((Inkscape::XML::Node *) l->data, NULL); + Inkscape::GC::release((Inkscape::XML::Node *) l->data); + l = g_slist_remove(l, l->data); + } + } else { + for (SPObject *child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) { + if ( SP_IS_FLOWDIV(child) || SP_IS_FLOWPARA(child) || SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child) ) { + child->updateRepr(flags); + } + } + } + + if (((SPObjectClass *) (parent_class))->write) + ((SPObjectClass *) (parent_class))->write(object, repr, flags); + + return repr; +} + +static void +sp_flowtext_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const /*flags*/) +{ + SPFlowtext *group = SP_FLOWTEXT(item); + group->layout.getBoundingBox(bbox, transform); +} + +static void +sp_flowtext_print(SPItem *item, SPPrintContext *ctx) +{ + SPFlowtext *group = SP_FLOWTEXT(item); + + NRRect pbox; + sp_item_invoke_bbox(item, &pbox, NR::identity(), TRUE); + NRRect bbox; + sp_item_bbox_desktop(item, &bbox); + NRRect dbox; + dbox.x0 = 0.0; + dbox.y0 = 0.0; + dbox.x1 = sp_document_width(SP_OBJECT_DOCUMENT(item)); + dbox.y1 = sp_document_height(SP_OBJECT_DOCUMENT(item)); + NR::Matrix const ctm = sp_item_i2d_affine(item); + + group->layout.print(ctx, &pbox, &dbox, &bbox, ctm); +} + + +static gchar *sp_flowtext_description(SPItem *item) +{ + Inkscape::Text::Layout const &layout = SP_FLOWTEXT(item)->layout; + int const nChars = layout.iteratorToCharIndex(layout.end()); + if (SP_FLOWTEXT(item)->has_internal_frame()) + return g_strdup_printf(_("Flowed text (%d characters)"), nChars); + else + return g_strdup_printf(_("Linked flowed text (%d characters)"), nChars); +} + +static NRArenaItem * +sp_flowtext_show(SPItem *item, NRArena *arena, unsigned/* key*/, unsigned /*flags*/) +{ + SPFlowtext *group = (SPFlowtext *) item; + NRArenaGroup *flowed = NRArenaGroup::create(arena); + nr_arena_group_set_transparent(flowed, FALSE); + + // pass the bbox of the flowtext object as paintbox (used for paintserver fills) + NRRect paintbox; + sp_item_invoke_bbox(item, &paintbox, NR::identity(), TRUE); + group->layout.show(flowed, &paintbox); + + return flowed; +} + +static void +sp_flowtext_hide(SPItem *item, unsigned int key) +{ + if (((SPItemClass *) parent_class)->hide) + ((SPItemClass *) parent_class)->hide(item, key); +} + + +/* + * + */ + +void SPFlowtext::_buildLayoutInput(SPObject *root, Shape const *exclusion_shape, std::list *shapes, SPObject **pending_line_break_object) +{ + if (*pending_line_break_object) { + if (SP_IS_FLOWREGIONBREAK(*pending_line_break_object)) + layout.appendControlCode(Inkscape::Text::Layout::SHAPE_BREAK, *pending_line_break_object); + else + layout.appendControlCode(Inkscape::Text::Layout::PARAGRAPH_BREAK, *pending_line_break_object); + *pending_line_break_object = NULL; + } + + for (SPObject *child = sp_object_first_child(root) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) { + if (SP_IS_STRING(child)) { + if (*pending_line_break_object) { + if (SP_IS_FLOWREGIONBREAK(*pending_line_break_object)) + layout.appendControlCode(Inkscape::Text::Layout::SHAPE_BREAK, *pending_line_break_object); + else + layout.appendControlCode(Inkscape::Text::Layout::PARAGRAPH_BREAK, *pending_line_break_object); + *pending_line_break_object = NULL; + } + layout.appendText(SP_STRING(child)->string, root->style, child); + } else if (SP_IS_FLOWREGION(child)) { + std::vector const &computed = SP_FLOWREGION(child)->computed; + for (std::vector::const_iterator it = computed.begin() ; it != computed.end() ; it++) { + shapes->push_back(Shape()); + if (exclusion_shape->hasEdges()) + shapes->back().Booleen(*it, const_cast(exclusion_shape), bool_op_diff); + else + shapes->back().Copy(*it); + layout.appendWrapShape(&shapes->back()); + } + } + else if (!SP_IS_FLOWREGIONEXCLUDE(child)) + _buildLayoutInput(child, exclusion_shape, shapes, pending_line_break_object); + } + + if (SP_IS_FLOWDIV(root) || SP_IS_FLOWPARA(root) || SP_IS_FLOWREGIONBREAK(root) || SP_IS_FLOWLINE(root)) { + if (!root->hasChildren()) + layout.appendText("", root->style, root); + *pending_line_break_object = root; + } +} + +Shape* SPFlowtext::_buildExclusionShape() const +{ + Shape *shape = new Shape; + Shape *shape_temp = new Shape; + + for (SPObject *child = children ; child != NULL ; child = SP_OBJECT_NEXT(child) ) { + // RH: is it right that this shouldn't be recursive? + if ( SP_IS_FLOWREGIONEXCLUDE(child) ) { + SPFlowregionExclude *c_child = SP_FLOWREGIONEXCLUDE(child); + if (c_child->computed == NULL || !c_child->computed->hasEdges()) + continue; + if (shape->hasEdges()) { + shape_temp->Booleen(shape, c_child->computed, bool_op_union); + std::swap(shape, shape_temp); + } else + shape->Copy(c_child->computed); + } + } + delete shape_temp; + return shape; +} + +void SPFlowtext::rebuildLayout() +{ + std::list shapes; + + layout.clear(); + Shape *exclusion_shape = _buildExclusionShape(); + SPObject *pending_line_break_object = NULL; + _buildLayoutInput(this, exclusion_shape, &shapes, &pending_line_break_object); + delete exclusion_shape; + layout.calculateFlow(); + //g_print(layout.dumpAsText().c_str()); +} + +void SPFlowtext::_clearFlow(NRArenaGroup *in_arena) +{ + nr_arena_item_request_render(NR_ARENA_ITEM(in_arena)); + for (NRArenaItem *child = in_arena->children; child != NULL; ) { + NRArenaItem *nchild = child->next; + + nr_arena_glyphs_group_clear(NR_ARENA_GLYPHS_GROUP(child)); + nr_arena_item_remove_child(NR_ARENA_ITEM(in_arena), child); + + child = nchild; + } +} + +void SPFlowtext::convert_to_text() +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + Inkscape::Selection *selection = SP_DT_SELECTION(desktop); + SPItem *item = selection->singleItem(); + if (!SP_IS_FLOWTEXT(item)) return; + + SPFlowtext *group = SP_FLOWTEXT(item); + + if (!group->layout.outputExists()) return; + + Inkscape::XML::Node *repr = sp_repr_new("svg:text"); + repr->setAttribute("xml:space", "preserve"); + repr->setAttribute("style", SP_OBJECT_REPR(group)->attribute("style")); + NR::Point anchor_point = group->layout.characterAnchorPoint(group->layout.begin()); + sp_repr_set_svg_double(repr, "x", anchor_point[NR::X]); + sp_repr_set_svg_double(repr, "y", anchor_point[NR::Y]); + + for (Inkscape::Text::Layout::iterator it = group->layout.begin() ; it != group->layout.end() ; ) { + + Inkscape::XML::Node *line_tspan = sp_repr_new("svg:tspan"); + line_tspan->setAttribute("sodipodi:role", "line"); + + Inkscape::Text::Layout::iterator it_line_end = it; + it_line_end.nextStartOfLine(); + while (it != it_line_end) { + + Inkscape::XML::Node *span_tspan = sp_repr_new("svg:tspan"); + NR::Point anchor_point = group->layout.characterAnchorPoint(it); + // use kerning to simulate justification and whatnot + Inkscape::Text::Layout::iterator it_span_end = it; + it_span_end.nextStartOfSpan(); + Inkscape::Text::Layout::OptionalTextTagAttrs attrs; + group->layout.simulateLayoutUsingKerning(it, it_span_end, &attrs); + // set x,y attributes only when we need to + bool set_x = false; + bool set_y = false; + if (!item->transform.test_identity()) { + set_x = set_y = true; + } else { + Inkscape::Text::Layout::iterator it_chunk_start = it; + it_chunk_start.thisStartOfChunk(); + if (it == it_chunk_start) { + set_x = true; + // don't set y so linespacing adjustments and things will still work + } + Inkscape::Text::Layout::iterator it_shape_start = it; + it_shape_start.thisStartOfShape(); + if (it == it_shape_start) + set_y = true; + } + if (set_x && !attrs.dx.empty()) + attrs.dx[0] = 0.0; + TextTagAttributes(attrs).writeTo(span_tspan); + if (set_x) + sp_repr_set_svg_double(span_tspan, "x", anchor_point[NR::X]); // FIXME: this will pick up the wrong end of counter-directional runs + if (set_y) + sp_repr_set_svg_double(span_tspan, "y", anchor_point[NR::Y]); + + SPObject *source_obj; + Glib::ustring::iterator span_text_start_iter; + group->layout.getSourceOfCharacter(it, (void**)&source_obj, &span_text_start_iter); + gchar *style_text = sp_style_write_difference((SP_IS_STRING(source_obj) ? source_obj->parent : source_obj)->style, group->style); + if (style_text && *style_text) { + span_tspan->setAttribute("style", style_text); + g_free(style_text); + } + + if (SP_IS_STRING(source_obj)) { + Glib::ustring *string = &SP_STRING(source_obj)->string; + SPObject *span_end_obj; + Glib::ustring::iterator span_text_end_iter; + group->layout.getSourceOfCharacter(it_span_end, (void**)&span_end_obj, &span_text_end_iter); + if (span_end_obj != source_obj) { + if (it_span_end == group->layout.end()) { + span_text_end_iter = span_text_start_iter; + for (int i = group->layout.iteratorToCharIndex(it_span_end) - group->layout.iteratorToCharIndex(it) ; i ; --i) + ++span_text_end_iter; + } else + span_text_end_iter = string->end(); // spans will never straddle a source boundary + } + + if (span_text_start_iter != span_text_end_iter) { + Glib::ustring new_string; + while (span_text_start_iter != span_text_end_iter) + new_string += *span_text_start_iter++; // grr. no substr() with iterators + Inkscape::XML::Node *new_text = sp_repr_new_text(new_string.c_str()); + span_tspan->appendChild(new_text); + Inkscape::GC::release(new_text); + } + } + it = it_span_end; + + line_tspan->appendChild(span_tspan); + Inkscape::GC::release(span_tspan); + } + repr->appendChild(line_tspan); + Inkscape::GC::release(line_tspan); + } + + Inkscape::XML::Node *parent = SP_OBJECT_REPR(item)->parent(); + parent->appendChild(repr); + SPItem *new_item = (SPItem *) SP_DT_DOCUMENT(desktop)->getObjectByRepr(repr); + sp_item_write_transform(new_item, repr, item->transform); + SP_OBJECT(new_item)->updateRepr(); + + Inkscape::GC::release(repr); + selection->set(new_item); + item->deleteObject(); + + sp_document_done(SP_DT_DOCUMENT(desktop)); +} + +SPItem *SPFlowtext::get_frame(SPItem *after) +{ + SPObject *ft = SP_OBJECT (this); + SPObject *region = NULL; + + for (SPObject *o = sp_object_first_child(SP_OBJECT(ft)) ; o != NULL ; o = SP_OBJECT_NEXT(o) ) { + if (SP_IS_FLOWREGION(o)) { + region = o; + break; + } + } + + if (!region) return NULL; + + bool past = false; + SPItem *frame = NULL; + + for (SPObject *o = sp_object_first_child(SP_OBJECT(region)) ; o != NULL ; o = SP_OBJECT_NEXT(o) ) { + if (SP_IS_ITEM(o)) { + if (after == NULL || past) { + frame = SP_ITEM(o); + } else { + if (SP_ITEM(o) == after) { + past = true; + } + } + } + } + + if (!frame) return NULL; + + if (SP_IS_USE (frame)) { + return sp_use_get_original(SP_USE(frame)); + } else { + return frame; + } +} + +bool SPFlowtext::has_internal_frame() +{ + SPItem *frame = get_frame(NULL); + + return (frame && SP_OBJECT(this)->isAncestorOf(SP_OBJECT(frame)) && SP_IS_RECT(frame)); +} + + +SPItem *create_flowtext_with_internal_frame (SPDesktop *desktop, NR::Point p0, NR::Point p1) +{ + SPDocument *doc = SP_DT_DOCUMENT (desktop); + + Inkscape::XML::Node *root_repr = sp_repr_new("svg:flowRoot"); + root_repr->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create + SPItem *ft_item = SP_ITEM(desktop->currentLayer()->appendChildRepr(root_repr)); + SPObject *root_object = doc->getObjectByRepr(root_repr); + g_assert(SP_IS_FLOWTEXT(root_object)); + + Inkscape::XML::Node *region_repr = sp_repr_new("svg:flowRegion"); + root_repr->appendChild(region_repr); + SPObject *region_object = doc->getObjectByRepr(region_repr); + g_assert(SP_IS_FLOWREGION(region_object)); + + Inkscape::XML::Node *rect_repr = sp_repr_new("svg:rect"); // FIXME: use path!!! after rects are converted to use path + region_repr->appendChild(rect_repr); + + SPObject *rect = doc->getObjectByRepr(rect_repr); + + p0 = sp_desktop_dt2root_xy_point(desktop, p0); + p1 = sp_desktop_dt2root_xy_point(desktop, p1); + using NR::X; + using NR::Y; + NR::Coord const x0 = MIN(p0[X], p1[X]); + NR::Coord const y0 = MIN(p0[Y], p1[Y]); + NR::Coord const x1 = MAX(p0[X], p1[X]); + NR::Coord const y1 = MAX(p0[Y], p1[Y]); + NR::Coord const w = x1 - x0; + NR::Coord const h = y1 - y0; + + sp_rect_position_set(SP_RECT(rect), x0, y0, w, h); + SP_OBJECT(rect)->updateRepr(); + + Inkscape::XML::Node *para_repr = sp_repr_new("svg:flowPara"); + root_repr->appendChild(para_repr); + SPObject *para_object = doc->getObjectByRepr(para_repr); + g_assert(SP_IS_FLOWPARA(para_object)); + + Inkscape::XML::Node *text = sp_repr_new_text(""); + para_repr->appendChild(text); + + Inkscape::GC::release(root_repr); + Inkscape::GC::release(region_repr); + Inkscape::GC::release(para_repr); + Inkscape::GC::release(rect_repr); + + return ft_item; +} + + +/* + 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/sp-flowtext.h b/src/sp-flowtext.h new file mode 100644 index 000000000..9e6d711fa --- /dev/null +++ b/src/sp-flowtext.h @@ -0,0 +1,56 @@ +#ifndef __SP_ITEM_FLOWTEXT_H__ +#define __SP_ITEM_FLOWTEXT_H__ + +/* + */ + +#include "sp-item.h" + +#include "display/nr-arena-forward.h" + +#include "libnrtype/Layout-TNG.h" + +#define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ()) +#define SP_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_FLOWTEXT, SPFlowtext)) +#define SP_FLOWTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_FLOWTEXT, SPFlowtextClass)) +#define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT)) +#define SP_IS_FLOWTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_FLOWTEXT)) + +struct SPFlowtext : public SPItem { + /** Completely recalculates the layout. */ + void rebuildLayout(); + + /** Converts the current selection (which must be a flowroot) into + a \ tree, keeping all the formatting and positioning, but losing + the automatic wrapping ability. */ + static void convert_to_text(); + + SPItem *get_frame(SPItem *after); + + bool has_internal_frame(); + +//semiprivate: (need to be accessed by the C-style functions still) + Inkscape::Text::Layout layout; + + /** discards the NRArena objects representing this text. */ + void _clearFlow(NRArenaGroup* in_arena); + +private: + /** Recursively walks the xml tree adding tags and their contents. */ + void _buildLayoutInput(SPObject *root, Shape const *exclusion_shape, std::list *shapes, SPObject **pending_line_break_object); + + /** calculates the union of all the \ children + of this flowroot. */ + Shape* _buildExclusionShape() const; + +}; + +struct SPFlowtextClass { + SPItemClass parent_class; +}; + +GType sp_flowtext_get_type (void); + +SPItem *create_flowtext_with_internal_frame (SPDesktop *desktop, NR::Point p1, NR::Point p2); + +#endif diff --git a/src/sp-gradient-fns.h b/src/sp-gradient-fns.h new file mode 100644 index 000000000..9780db182 --- /dev/null +++ b/src/sp-gradient-fns.h @@ -0,0 +1,76 @@ +#ifndef SEEN_SP_GRADIENT_FNS_H +#define SEEN_SP_GRADIENT_FNS_H + +/** \file + * Macros and fn declarations related to gradients. + */ + +#include +#include +#include "libnr/nr-forward.h" +#include "sp-gradient-spread.h" +#include "sp-gradient-units.h" + +class SPGradient; + +namespace Inkscape { +namespace XML { +class Node; +} +} + +#define SP_TYPE_GRADIENT (sp_gradient_get_type()) +#define SP_GRADIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SP_TYPE_GRADIENT, SPGradient)) +#define SP_GRADIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SP_TYPE_GRADIENT, SPGradientClass)) +#define SP_IS_GRADIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SP_TYPE_GRADIENT)) +#define SP_IS_GRADIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SP_TYPE_GRADIENT)) + +#define SP_GRADIENT_STATE_IS_SET(g) (SP_GRADIENT(g)->state != SP_GRADIENT_STATE_UNKNOWN) +#define SP_GRADIENT_IS_VECTOR(g) (SP_GRADIENT(g)->state == SP_GRADIENT_STATE_VECTOR) +#define SP_GRADIENT_IS_PRIVATE(g) (SP_GRADIENT(g)->state == SP_GRADIENT_STATE_PRIVATE) +#define SP_GRADIENT_HAS_STOPS(g) (SP_GRADIENT(g)->has_stops) +#define SP_GRADIENT_SPREAD(g) (SP_GRADIENT(g)->spread) +#define SP_GRADIENT_UNITS(g) (SP_GRADIENT(g)->units) + +GType sp_gradient_get_type(); + +/** Forces vector to be built, if not present (i.e. changed) */ +void sp_gradient_ensure_vector(SPGradient *gradient); + +/** Ensures that color array is populated */ +void sp_gradient_ensure_colors(SPGradient *gradient); + +void sp_gradient_set_units(SPGradient *gr, SPGradientUnits units); +void sp_gradient_set_spread(SPGradient *gr, SPGradientSpread spread); + +SPGradient *sp_gradient_get_vector (SPGradient *gradient, bool force_private); +SPGradientSpread sp_gradient_get_spread (SPGradient *gradient); + +/* Gradient repr methods */ +void sp_gradient_repr_write_vector(SPGradient *gr); +void sp_gradient_repr_clear_vector(SPGradient *gr); + +void sp_gradient_render_vector_block_rgba(SPGradient *gr, guchar *px, gint w, gint h, gint rs, gint pos, gint span, bool horizontal); +void sp_gradient_render_vector_block_rgb(SPGradient *gr, guchar *px, gint w, gint h, gint rs, gint pos, gint span, bool horizontal); + +/** Transforms to/from gradient position space in given environment */ +NR::Matrix sp_gradient_get_g2d_matrix(SPGradient const *gr, NR::Matrix const &ctm, + NR::Rect const &bbox); +NR::Matrix sp_gradient_get_gs2d_matrix(SPGradient const *gr, NR::Matrix const &ctm, + NR::Rect const &bbox); +void sp_gradient_set_gs2d_matrix(SPGradient *gr, NR::Matrix const &ctm, NR::Rect const &bbox, + NR::Matrix const &gs2d); + + +#endif /* !SEEN_SP_GRADIENT_FNS_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:encoding=utf-8:textwidth=99 : diff --git a/src/sp-gradient-reference.cpp b/src/sp-gradient-reference.cpp new file mode 100644 index 000000000..618e1085a --- /dev/null +++ b/src/sp-gradient-reference.cpp @@ -0,0 +1,22 @@ +#include "sp-gradient-reference.h" +#include "sp-gradient-fns.h" + +bool +SPGradientReference::_acceptObject(SPObject *obj) const +{ + return SP_IS_GRADIENT(obj); + /* effic: Don't bother making this an inline function: _acceptObject is a virtual function, + typically called from a context where the runtime type is not known at compile time. */ +} + + +/* + 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 : diff --git a/src/sp-gradient-reference.h b/src/sp-gradient-reference.h new file mode 100644 index 000000000..8f368b825 --- /dev/null +++ b/src/sp-gradient-reference.h @@ -0,0 +1,31 @@ +#ifndef SEEN_SP_GRADIENT_REFERENCE_H +#define SEEN_SP_GRADIENT_REFERENCE_H + +#include "uri-references.h" +class SPObject; + +class SPGradientReference : public Inkscape::URIReference { +public: + SPGradientReference(SPObject *obj) : URIReference(obj) {} + + SPGradient *getObject() const { + return (SPGradient *)URIReference::getObject(); + } + +protected: + virtual bool _acceptObject(SPObject *obj) const; +}; + + +#endif /* !SEEN_SP_GRADIENT_REFERENCE_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:encoding=utf-8:textwidth=99 : diff --git a/src/sp-gradient-spread.h b/src/sp-gradient-spread.h new file mode 100644 index 000000000..537b6aad7 --- /dev/null +++ b/src/sp-gradient-spread.h @@ -0,0 +1,22 @@ +#ifndef SEEN_SP_GRADIENT_SPREAD_H +#define SEEN_SP_GRADIENT_SPREAD_H + +enum SPGradientSpread { + SP_GRADIENT_SPREAD_PAD, + SP_GRADIENT_SPREAD_REFLECT, + SP_GRADIENT_SPREAD_REPEAT +}; + + +#endif /* !SEEN_SP_GRADIENT_SPREAD_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:encoding=utf-8:textwidth=99 : diff --git a/src/sp-gradient-test.cpp b/src/sp-gradient-test.cpp new file mode 100644 index 000000000..051beb928 --- /dev/null +++ b/src/sp-gradient-test.cpp @@ -0,0 +1,139 @@ +#include "attributes.h" +#include "inkscape-private.h" +#include "sp-gradient.h" +#include "sp-object.h" +#include "document.h" +#include "libnr/nr-matrix.h" +#include "libnr/nr-matrix-fns.h" +#include "libnr/nr-matrix-ops.h" +#include "libnr/nr-rect.h" +#include "libnr/nr-rotate-fns.h" +#include "svg/svg.h" +#include "utest/utest.h" +#include "xml/repr.h" + +/// Dummy functions to keep linker happy +int sp_main_gui (int, char const**) { return 0; } +int sp_main_console (int, char const**) { return 0; } + +static bool +test_gradient() +{ + utest_start("gradient"); + UTEST_TEST("init") { + SPGradient *gr = static_cast(g_object_new(SP_TYPE_GRADIENT, NULL)); + UTEST_ASSERT(gr->gradientTransform.test_identity()); + UTEST_ASSERT(gr->gradientTransform == NR::identity()); + g_object_unref(gr); + } + + /* Create the global inkscape object. */ + static_cast(g_object_new(inkscape_get_type(), NULL)); + + + SPDocument *doc = sp_document_new_dummy(); + + UTEST_TEST("sp_object_set(\"gradientTransform\")") { + SPGradient *gr = static_cast(g_object_new(SP_TYPE_GRADIENT, NULL)); + SP_OBJECT(gr)->document = doc; + sp_object_set(SP_OBJECT(gr), SP_ATTR_GRADIENTTRANSFORM, "translate(5, 8)"); + UTEST_ASSERT(gr->gradientTransform == NR::Matrix(NR::translate(5, 8))); + sp_object_set(SP_OBJECT(gr), SP_ATTR_GRADIENTTRANSFORM, ""); + UTEST_ASSERT(gr->gradientTransform == NR::identity()); + sp_object_set(SP_OBJECT(gr), SP_ATTR_GRADIENTTRANSFORM, "rotate(90)"); + UTEST_ASSERT(gr->gradientTransform == NR::Matrix(rotate_degrees(90))); + g_object_unref(gr); + } + + UTEST_TEST("write") { + SPGradient *gr = static_cast(g_object_new(SP_TYPE_GRADIENT, NULL)); + SP_OBJECT(gr)->document = doc; + sp_object_set(SP_OBJECT(gr), SP_ATTR_GRADIENTTRANSFORM, "matrix(0, 1, -1, 0, 0, 0)"); + Inkscape::XML::Node *repr = sp_repr_new("svg:radialGradient"); + SP_OBJECT(gr)->updateRepr(repr, SP_OBJECT_WRITE_ALL); + { + gchar const *tr = repr->attribute("gradientTransform"); + NR::Matrix svd; + bool const valid = sp_svg_transform_read(tr, &svd); + UTEST_ASSERT(valid); + UTEST_ASSERT(svd == NR::Matrix(rotate_degrees(90))); + } + g_object_unref(gr); + } + + UTEST_TEST("get_g2d, get_gs2d, set_gs2d") { + SPGradient *gr = static_cast(g_object_new(SP_TYPE_GRADIENT, NULL)); + SP_OBJECT(gr)->document = doc; + NR::Matrix const grXform(2, 1, + 1, 3, + 4, 6); + gr->gradientTransform = grXform; + NR::Rect const unit_rect(NR::Point(0, 0), NR::Point(1, 1)); + { + NR::Matrix const g2d(sp_gradient_get_g2d_matrix(gr, NR::identity(), unit_rect)); + NR::Matrix const gs2d(sp_gradient_get_gs2d_matrix(gr, NR::identity(), unit_rect)); + UTEST_ASSERT(g2d == NR::identity()); + UTEST_ASSERT(NR::matrix_equalp(gs2d, gr->gradientTransform * g2d, 1e-12)); + + sp_gradient_set_gs2d_matrix(gr, NR::identity(), unit_rect, gs2d); + UTEST_ASSERT(NR::matrix_equalp(gr->gradientTransform, grXform, 1e-12)); + } + + gr->gradientTransform = grXform; + NR::Matrix const funny(2, 3, + 4, 5, + 6, 7); + { + NR::Matrix const g2d(sp_gradient_get_g2d_matrix(gr, funny, unit_rect)); + NR::Matrix const gs2d(sp_gradient_get_gs2d_matrix(gr, funny, unit_rect)); + UTEST_ASSERT(g2d == funny); + UTEST_ASSERT(NR::matrix_equalp(gs2d, gr->gradientTransform * g2d, 1e-12)); + + sp_gradient_set_gs2d_matrix(gr, funny, unit_rect, gs2d); + UTEST_ASSERT(NR::matrix_equalp(gr->gradientTransform, grXform, 1e-12)); + } + + gr->gradientTransform = grXform; + NR::Rect const larger_rect(NR::Point(5, 6), NR::Point(8, 10)); + { + NR::Matrix const g2d(sp_gradient_get_g2d_matrix(gr, funny, larger_rect)); + NR::Matrix const gs2d(sp_gradient_get_gs2d_matrix(gr, funny, larger_rect)); + UTEST_ASSERT(g2d == NR::Matrix(3, 0, + 0, 4, + 5, 6) * funny); + UTEST_ASSERT(NR::matrix_equalp(gs2d, gr->gradientTransform * g2d, 1e-12)); + + sp_gradient_set_gs2d_matrix(gr, funny, larger_rect, gs2d); + UTEST_ASSERT(NR::matrix_equalp(gr->gradientTransform, grXform, 1e-12)); + + sp_object_set(SP_OBJECT(gr), SP_ATTR_GRADIENTUNITS, "userSpaceOnUse"); + NR::Matrix const user_g2d(sp_gradient_get_g2d_matrix(gr, funny, larger_rect)); + NR::Matrix const user_gs2d(sp_gradient_get_gs2d_matrix(gr, funny, larger_rect)); + UTEST_ASSERT(user_g2d == funny); + UTEST_ASSERT(NR::matrix_equalp(user_gs2d, gr->gradientTransform * user_g2d, 1e-12)); + } + g_object_unref(gr); + } + + return utest_end(); +} + +int main() +{ + g_type_init(); + Inkscape::GC::init(); + return ( test_gradient() + ? EXIT_SUCCESS + : EXIT_FAILURE ); +} + +/* + 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 : diff --git a/src/sp-gradient-units.h b/src/sp-gradient-units.h new file mode 100644 index 000000000..f79b4c264 --- /dev/null +++ b/src/sp-gradient-units.h @@ -0,0 +1,21 @@ +#ifndef SEEN_SP_GRADIENT_UNITS_H +#define SEEN_SP_GRADIENT_UNITS_H + +enum SPGradientUnits { + SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX, + SP_GRADIENT_UNITS_USERSPACEONUSE +}; + + +#endif /* !SEEN_SP_GRADIENT_UNITS_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:encoding=utf-8:textwidth=99 : diff --git a/src/sp-gradient-vector.h b/src/sp-gradient-vector.h new file mode 100644 index 000000000..7bdfb51bf --- /dev/null +++ b/src/sp-gradient-vector.h @@ -0,0 +1,42 @@ +#ifndef SEEN_SP_GRADIENT_VECTOR_H +#define SEEN_SP_GRADIENT_VECTOR_H + +#include +#include +#include "color.h" + +/** + * Differs from SPStop in that SPStop mirrors the \ element in the document, whereas + * SPGradientStop shows more the effective stop color. + * + * For example, SPGradientStop has no currentColor option: currentColor refers to the color + * property value of the gradient where currentColor appears, so we interpret currentColor before + * copying from SPStop to SPGradientStop. + */ +struct SPGradientStop { + gdouble offset; + SPColor color; + gfloat opacity; +}; + +/** + * The effective gradient vector, after copying stops from the referenced gradient if necessary. + */ +struct SPGradientVector { + bool built; + std::vector stops; +}; + + +#endif /* !SEEN_SP_GRADIENT_VECTOR_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:encoding=utf-8:textwidth=99 : diff --git a/src/sp-gradient.cpp b/src/sp-gradient.cpp new file mode 100644 index 000000000..db2360225 --- /dev/null +++ b/src/sp-gradient.cpp @@ -0,0 +1,1809 @@ +#define __SP_GRADIENT_C__ + +/** \file + * SPGradient, SPStop, SPLinearGradient, SPRadialGradient. + */ +/* + * Authors: + * Lauris Kaplinski + * bulia byak + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2004 David Turner + * + * Released under GNU GPL, read the file 'COPYING' for more information + * + */ + +#define noSP_GRADIENT_VERBOSE + + +#include +#include +#include +#include +#include +#include "libnr/nr-scale-translate-ops.h" + + +#include "display/nr-gradient-gpl.h" +#include "svg/svg.h" +#include "svg/css-ostringstream.h" +#include "attributes.h" +#include "document-private.h" +#include "gradient-chemistry.h" +#include "sp-gradient-reference.h" +#include "sp-linear-gradient.h" +#include "sp-radial-gradient.h" +#include "sp-stop.h" +#include "streq.h" +#include "uri.h" +#include "xml/repr.h" + +#define SP_MACROS_SILENT +#include "macros.h" + +/// Has to be power of 2 +#define NCOLORS NR_GRADIENT_VECTOR_LENGTH + +static void sp_stop_class_init(SPStopClass *klass); +static void sp_stop_init(SPStop *stop); + +static void sp_stop_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); +static void sp_stop_set(SPObject *object, unsigned key, gchar const *value); +static Inkscape::XML::Node *sp_stop_write(SPObject *object, Inkscape::XML::Node *repr, guint flags); + +static SPObjectClass *stop_parent_class; + +/** + * Registers SPStop class and returns its type. + */ +GType +sp_stop_get_type() +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPStopClass), + NULL, NULL, + (GClassInitFunc) sp_stop_class_init, + NULL, NULL, + sizeof(SPStop), + 16, + (GInstanceInitFunc) sp_stop_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_OBJECT, "SPStop", &info, (GTypeFlags)0); + } + return type; +} + +/** + * Callback to initialize SPStop vtable. + */ +static void sp_stop_class_init(SPStopClass *klass) +{ + SPObjectClass *sp_object_class = (SPObjectClass *) klass; + + stop_parent_class = (SPObjectClass *) g_type_class_ref(SP_TYPE_OBJECT); + + sp_object_class->build = sp_stop_build; + sp_object_class->set = sp_stop_set; + sp_object_class->write = sp_stop_write; +} + +/** + * Callback to initialize SPStop object. + */ +static void +sp_stop_init(SPStop *stop) +{ + stop->offset = 0.0; + stop->currentColor = false; + sp_color_set_rgb_rgba32(&stop->specified_color, 0x000000ff); + stop->opacity = 1.0; +} + +/** + * Virtual build: set stop attributes from its associated XML node. + */ +static void sp_stop_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + if (((SPObjectClass *) stop_parent_class)->build) + (* ((SPObjectClass *) stop_parent_class)->build)(object, document, repr); + + sp_object_read_attr(object, "offset"); + sp_object_read_attr(object, "stop-color"); + sp_object_read_attr(object, "stop-opacity"); + sp_object_read_attr(object, "style"); +} + +/** + * Virtual set: set attribute to value. + */ +static void +sp_stop_set(SPObject *object, unsigned key, gchar const *value) +{ + SPStop *stop = SP_STOP(object); + + switch (key) { + case SP_ATTR_STYLE: { + /** \todo + * fixme: We are reading simple values 3 times during build (Lauris). + * \par + * We need presentation attributes etc. + * \par + * remove the hackish "style reading" from here: see comments in + * sp_object_get_style_property about the bugs in our current + * approach. However, note that SPStyle doesn't currently have + * stop-color and stop-opacity properties. + */ + { + gchar const *p = sp_object_get_style_property(object, "stop-color", "black"); + if (streq(p, "currentColor")) { + stop->currentColor = true; + } else { + guint32 const color = sp_svg_read_color(p, 0); + sp_color_set_rgb_rgba32(&stop->specified_color, color); + } + } + { + gchar const *p = sp_object_get_style_property(object, "stop-opacity", "1"); + gdouble opacity = sp_svg_read_percentage(p, stop->opacity); + stop->opacity = opacity; + } + object->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + break; + } + case SP_PROP_STOP_COLOR: { + { + gchar const *p = sp_object_get_style_property(object, "stop-color", "black"); + if (streq(p, "currentColor")) { + stop->currentColor = true; + } else { + stop->currentColor = false; + guint32 const color = sp_svg_read_color(p, 0); + sp_color_set_rgb_rgba32(&stop->specified_color, color); + } + } + object->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + break; + } + case SP_PROP_STOP_OPACITY: { + { + gchar const *p = sp_object_get_style_property(object, "stop-opacity", "1"); + gdouble opacity = sp_svg_read_percentage(p, stop->opacity); + stop->opacity = opacity; + } + object->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + break; + } + case SP_ATTR_OFFSET: { + stop->offset = sp_svg_read_percentage(value, 0.0); + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + default: { + if (((SPObjectClass *) stop_parent_class)->set) + (* ((SPObjectClass *) stop_parent_class)->set)(object, key, value); + break; + } + } +} + +/** + * Virtual write: write object attributes to repr. + */ +static Inkscape::XML::Node * +sp_stop_write(SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + SPStop *stop = SP_STOP(object); + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = sp_repr_new("svg:stop"); + } + + + Inkscape::CSSOStringStream os; + os << "stop-color:"; + if (stop->currentColor) { + os << "currentColor"; + } else { + gchar c[64]; + sp_svg_write_color(c, 64, sp_color_get_rgba32_ualpha(&stop->specified_color, 255)); + os << c; + } + os << ";stop-opacity:" << stop->opacity; + repr->setAttribute("style", os.str().c_str()); + repr->setAttribute("stop-color", NULL); + repr->setAttribute("stop-opacity", NULL); + sp_repr_set_css_double(repr, "offset", stop->offset); + /* strictly speaking, offset an SVG rather than a CSS one, but exponents make no sense + * for offset proportions. */ + + if (((SPObjectClass *) stop_parent_class)->write) + (* ((SPObjectClass *) stop_parent_class)->write)(object, repr, flags); + + return repr; +} + +/** + * Return stop's color as 32bit value. + */ +guint32 +sp_stop_get_rgba32(SPStop const *const stop) +{ + guint32 rgb0 = 0; + /* Default value: arbitrarily black. (SVG1.1 and CSS2 both say that the initial + * value depends on user agent, and don't give any further restrictions that I can + * see.) */ + if (stop->currentColor) { + char const *str = sp_object_get_style_property(stop, "color", NULL); + if (str) { + rgb0 = sp_svg_read_color(str, rgb0); + } + unsigned const alpha = static_cast(stop->opacity * 0xff + 0.5); + g_return_val_if_fail((alpha & ~0xff) == 0, + rgb0 | 0xff); + return rgb0 | alpha; + } else { + return sp_color_get_rgba32_falpha(&stop->specified_color, stop->opacity); + } +} + +/** + * Return stop's color as SPColor. + */ +static SPColor +sp_stop_get_color(SPStop const *const stop) +{ + if (stop->currentColor) { + char const *str = sp_object_get_style_property(stop, "color", NULL); + guint32 const dfl = 0; + /* Default value: arbitrarily black. (SVG1.1 and CSS2 both say that the initial + * value depends on user agent, and don't give any further restrictions that I can + * see.) */ + guint32 color = dfl; + if (str) { + color = sp_svg_read_color(str, dfl); + } + SPColor ret; + sp_color_set_rgb_rgba32(&ret, color); + return ret; + } else { + return stop->specified_color; + } +} + +/* + * Gradient + */ + +static void sp_gradient_class_init(SPGradientClass *klass); +static void sp_gradient_init(SPGradient *gr); + +static void sp_gradient_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); +static void sp_gradient_release(SPObject *object); +static void sp_gradient_set(SPObject *object, unsigned key, gchar const *value); +static void sp_gradient_child_added(SPObject *object, + Inkscape::XML::Node *child, + Inkscape::XML::Node *ref); +static void sp_gradient_remove_child(SPObject *object, Inkscape::XML::Node *child); +static void sp_gradient_modified(SPObject *object, guint flags); +static Inkscape::XML::Node *sp_gradient_write(SPObject *object, Inkscape::XML::Node *repr, + guint flags); + +static void gradient_ref_modified(SPObject *href, guint flags, SPGradient *gradient); + +static bool sp_gradient_invalidate_vector(SPGradient *gr); +static void sp_gradient_rebuild_vector(SPGradient *gr); + +static void gradient_ref_changed(SPObject *old_ref, SPObject *ref, SPGradient *gradient); + +static SPPaintServerClass *gradient_parent_class; + +/** + * Registers SPGradient class and returns its type. + */ +GType +sp_gradient_get_type() +{ + static GType gradient_type = 0; + if (!gradient_type) { + GTypeInfo gradient_info = { + sizeof(SPGradientClass), + NULL, NULL, + (GClassInitFunc) sp_gradient_class_init, + NULL, NULL, + sizeof(SPGradient), + 16, + (GInstanceInitFunc) sp_gradient_init, + NULL, /* value_table */ + }; + gradient_type = g_type_register_static(SP_TYPE_PAINT_SERVER, "SPGradient", + &gradient_info, (GTypeFlags)0); + } + return gradient_type; +} + +/** + * SPGradient vtable initialization. + */ +static void +sp_gradient_class_init(SPGradientClass *klass) +{ + SPObjectClass *sp_object_class = (SPObjectClass *) klass; + + gradient_parent_class = (SPPaintServerClass *)g_type_class_ref(SP_TYPE_PAINT_SERVER); + + sp_object_class->build = sp_gradient_build; + sp_object_class->release = sp_gradient_release; + sp_object_class->set = sp_gradient_set; + sp_object_class->child_added = sp_gradient_child_added; + sp_object_class->remove_child = sp_gradient_remove_child; + sp_object_class->modified = sp_gradient_modified; + sp_object_class->write = sp_gradient_write; +} + +/** + * Callback for SPGradient object initialization. + */ +static void +sp_gradient_init(SPGradient *gr) +{ + gr->ref = new SPGradientReference(SP_OBJECT(gr)); + gr->ref->changedSignal().connect(sigc::bind(sigc::ptr_fun(gradient_ref_changed), gr)); + + /** \todo + * Fixme: reprs being rearranged (e.g. via the XML editor) + * may require us to clear the state. + */ + gr->state = SP_GRADIENT_STATE_UNKNOWN; + + gr->units = SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX; + gr->units_set = FALSE; + + gr->gradientTransform = NR::identity(); + gr->gradientTransform_set = FALSE; + + gr->spread = SP_GRADIENT_SPREAD_PAD; + gr->spread_set = FALSE; + + gr->has_stops = FALSE; + + gr->vector.built = false; + gr->vector.stops.clear(); + + gr->color = NULL; +} + +/** + * Virtual build: set gradient attributes from its associated repr. + */ +static void +sp_gradient_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + SPGradient *gradient = SP_GRADIENT(object); + + if (((SPObjectClass *) gradient_parent_class)->build) + (* ((SPObjectClass *) gradient_parent_class)->build)(object, document, repr); + + SPObject *ochild; + for ( ochild = sp_object_first_child(object) ; ochild ; ochild = SP_OBJECT_NEXT(ochild) ) { + if (SP_IS_STOP(ochild)) { + gradient->has_stops = TRUE; + break; + } + } + + sp_object_read_attr(object, "gradientUnits"); + sp_object_read_attr(object, "gradientTransform"); + sp_object_read_attr(object, "spreadMethod"); + sp_object_read_attr(object, "xlink:href"); + + /* Register ourselves */ + sp_document_add_resource(document, "gradient", object); +} + +/** + * Virtual release of SPGradient members before destruction. + */ +static void +sp_gradient_release(SPObject *object) +{ + SPGradient *gradient = (SPGradient *) object; + +#ifdef SP_GRADIENT_VERBOSE + g_print("Releasing gradient %s\n", SP_OBJECT_ID(object)); +#endif + + if (SP_OBJECT_DOCUMENT(object)) { + /* Unregister ourselves */ + sp_document_remove_resource(SP_OBJECT_DOCUMENT(object), "gradient", SP_OBJECT(object)); + } + + if (gradient->ref) { + if (gradient->ref->getObject()) { + sp_signal_disconnect_by_data(gradient->ref->getObject(), gradient); + } + gradient->ref->detach(); + delete gradient->ref; + gradient->ref = NULL; + } + + if (gradient->color) { + g_free(gradient->color); + gradient->color = NULL; + } + + if (((SPObjectClass *) gradient_parent_class)->release) + ((SPObjectClass *) gradient_parent_class)->release(object); +} + +/** + * Set gradient attribute to value. + */ +static void +sp_gradient_set(SPObject *object, unsigned key, gchar const *value) +{ + SPGradient *gr = SP_GRADIENT(object); + + switch (key) { + case SP_ATTR_GRADIENTUNITS: + if (value) { + if (!strcmp(value, "userSpaceOnUse")) { + gr->units = SP_GRADIENT_UNITS_USERSPACEONUSE; + } else { + gr->units = SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX; + } + gr->units_set = TRUE; + } else { + gr->units = SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX; + gr->units_set = FALSE; + } + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_GRADIENTTRANSFORM: { + NR::Matrix t; + if (value && sp_svg_transform_read(value, &t)) { + gr->gradientTransform = t; + gr->gradientTransform_set = TRUE; + } else { + gr->gradientTransform = NR::identity(); + gr->gradientTransform_set = FALSE; + } + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + case SP_ATTR_SPREADMETHOD: + if (value) { + if (!strcmp(value, "reflect")) { + gr->spread = SP_GRADIENT_SPREAD_REFLECT; + } else if (!strcmp(value, "repeat")) { + gr->spread = SP_GRADIENT_SPREAD_REPEAT; + } else { + gr->spread = SP_GRADIENT_SPREAD_PAD; + } + gr->spread_set = TRUE; + } else { + gr->spread_set = FALSE; + } + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_XLINK_HREF: + if (value) { + try { + gr->ref->attach(Inkscape::URI(value)); + } catch (Inkscape::BadURIException &e) { + g_warning("%s", e.what()); + gr->ref->detach(); + } + } else { + gr->ref->detach(); + } + break; + default: + if (((SPObjectClass *) gradient_parent_class)->set) + ((SPObjectClass *) gradient_parent_class)->set(object, key, value); + break; + } +} + +/** + * Gets called when the gradient is (re)attached to another gradient. + */ +static void +gradient_ref_changed(SPObject *old_ref, SPObject *ref, SPGradient *gr) +{ + if (old_ref) { + sp_signal_disconnect_by_data(old_ref, gr); + } + if ( SP_IS_GRADIENT(ref) + && ref != gr ) + { + g_signal_connect(G_OBJECT(ref), "modified", G_CALLBACK(gradient_ref_modified), gr); + } + /// \todo Fixme: what should the flags (second) argument be? */ + gradient_ref_modified(ref, 0, gr); +} + +/** + * Callback for child_added event. + */ +static void +sp_gradient_child_added(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref) +{ + SPGradient *gr = SP_GRADIENT(object); + + sp_gradient_invalidate_vector(gr); + + if (((SPObjectClass *) gradient_parent_class)->child_added) + (* ((SPObjectClass *) gradient_parent_class)->child_added)(object, child, ref); + + SPObject *ochild = sp_object_get_child_by_repr(object, child); + if ( ochild && SP_IS_STOP(ochild) ) { + gr->has_stops = TRUE; + } + + /// \todo Fixme: should we schedule "modified" here? + object->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +/** + * Callback for remove_child event. + */ +static void +sp_gradient_remove_child(SPObject *object, Inkscape::XML::Node *child) +{ + SPGradient *gr = SP_GRADIENT(object); + + sp_gradient_invalidate_vector(gr); + + if (((SPObjectClass *) gradient_parent_class)->remove_child) + (* ((SPObjectClass *) gradient_parent_class)->remove_child)(object, child); + + gr->has_stops = FALSE; + SPObject *ochild; + for ( ochild = sp_object_first_child(object) ; ochild ; ochild = SP_OBJECT_NEXT(ochild) ) { + if (SP_IS_STOP(ochild)) { + gr->has_stops = TRUE; + break; + } + } + + /* Fixme: should we schedule "modified" here? */ + object->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +/** + * Callback for modified event. + */ +static void +sp_gradient_modified(SPObject *object, guint flags) +{ + SPGradient *gr = SP_GRADIENT(object); + + if (flags & SP_OBJECT_CHILD_MODIFIED_FLAG) { + sp_gradient_invalidate_vector(gr); + } + + if (flags & SP_OBJECT_MODIFIED_FLAG) flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + flags &= SP_OBJECT_MODIFIED_CASCADE; + + // FIXME: climb up the ladder of hrefs + GSList *l = NULL; + for (SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) { + g_object_ref(G_OBJECT(child)); + l = g_slist_prepend(l, child); + } + l = g_slist_reverse(l); + while (l) { + SPObject *child = SP_OBJECT(l->data); + l = g_slist_remove(l, child); + if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->emitModified(flags); + } + g_object_unref(G_OBJECT(child)); + } +} + +/** + * Write gradient attributes to repr. + */ +static Inkscape::XML::Node * +sp_gradient_write(SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + SPGradient *gr = SP_GRADIENT(object); + + if (((SPObjectClass *) gradient_parent_class)->write) + (* ((SPObjectClass *) gradient_parent_class)->write)(object, repr, flags); + + if (flags & SP_OBJECT_WRITE_BUILD) { + GSList *l = NULL; + for (SPObject *child = sp_object_first_child(object); child; child = SP_OBJECT_NEXT(child)) { + Inkscape::XML::Node *crepr; + crepr = child->updateRepr(NULL, flags); + if (crepr) l = g_slist_prepend(l, crepr); + } + while (l) { + repr->addChild((Inkscape::XML::Node *) l->data, NULL); + Inkscape::GC::release((Inkscape::XML::Node *) l->data); + l = g_slist_remove(l, l->data); + } + } + + if (gr->ref->getURI()) { + gchar *uri_string = gr->ref->getURI()->toString(); + repr->setAttribute("xlink:href", uri_string); + g_free(uri_string); + } + + if ((flags & SP_OBJECT_WRITE_ALL) || gr->units_set) { + switch (gr->units) { + case SP_GRADIENT_UNITS_USERSPACEONUSE: + repr->setAttribute("gradientUnits", "userSpaceOnUse"); + break; + default: + repr->setAttribute("gradientUnits", "objectBoundingBox"); + break; + } + } + + if ((flags & SP_OBJECT_WRITE_ALL) || gr->gradientTransform_set) { + gchar c[256]; + if (sp_svg_transform_write(c, 256, gr->gradientTransform)) { + repr->setAttribute("gradientTransform", c); + } else { + repr->setAttribute("gradientTransform", NULL); + } + } + + if ((flags & SP_OBJECT_WRITE_ALL) || gr->spread_set) { + /* FIXME: Ensure that gr->spread is the inherited value + * if !gr->spread_set. Not currently happening: see sp_gradient_modified. + */ + switch (gr->spread) { + case SP_GRADIENT_SPREAD_REFLECT: + repr->setAttribute("spreadMethod", "reflect"); + break; + case SP_GRADIENT_SPREAD_REPEAT: + repr->setAttribute("spreadMethod", "repeat"); + break; + default: + repr->setAttribute("spreadMethod", "pad"); + break; + } + } + + return repr; +} + +/** + * Forces the vector to be built, if not present (i.e., changed). + * + * \pre SP_IS_GRADIENT(gradient). + */ +void +sp_gradient_ensure_vector(SPGradient *gradient) +{ + g_return_if_fail(gradient != NULL); + g_return_if_fail(SP_IS_GRADIENT(gradient)); + + if (!gradient->vector.built) { + sp_gradient_rebuild_vector(gradient); + } +} + +/** + * Set units property of gradient and emit modified. + */ +void +sp_gradient_set_units(SPGradient *gr, SPGradientUnits units) +{ + if (units != gr->units) { + gr->units = units; + gr->units_set = TRUE; + SP_OBJECT(gr)->requestModified(SP_OBJECT_MODIFIED_FLAG); + } +} + +/** + * Set spread property of gradient and emit modified. + */ +void +sp_gradient_set_spread(SPGradient *gr, SPGradientSpread spread) +{ + if (spread != gr->spread) { + gr->spread = spread; + gr->spread_set = TRUE; + SP_OBJECT(gr)->requestModified(SP_OBJECT_MODIFIED_FLAG); + } +} + +/** + * Returns the first of {src, src-\>ref-\>getObject(), + * src-\>ref-\>getObject()-\>ref-\>getObject(),...} + * for which \a match is true, or NULL if none found. + * + * The raison d'être of this routine is that it correctly handles cycles in the href chain (e.g., if + * a gradient gives itself as its href, or if each of two gradients gives the other as its href). + * + * \pre SP_IS_GRADIENT(src). + */ +static SPGradient * +chase_hrefs(SPGradient *const src, bool (*match)(SPGradient const *)) +{ + g_return_val_if_fail(SP_IS_GRADIENT(src), NULL); + + /* Use a pair of pointers for detecting loops: p1 advances half as fast as p2. If there is a + loop, then once p1 has entered the loop, we'll detect it the next time the distance between + p1 and p2 is a multiple of the loop size. */ + SPGradient *p1 = src, *p2 = src; + bool do1 = false; + for (;;) { + if (match(p2)) { + return p2; + } + + p2 = p2->ref->getObject(); + if (!p2) { + return p2; + } + if (do1) { + p1 = p1->ref->getObject(); + } + do1 = !do1; + + if ( p2 == p1 ) { + /* We've been here before, so return NULL to indicate that no matching gradient found + * in the chain. */ + return NULL; + } + } +} + +/** + * True if gradient has stops. + */ +static bool +has_stops(SPGradient const *gr) +{ + return SP_GRADIENT_HAS_STOPS(gr); +} + +/** + * True if gradient has spread set. + */ +static bool +has_spread_set(SPGradient const *gr) +{ + return gr->spread_set; +} + + +/** + * Returns private vector of given gradient (the gradient at the end of the href chain which has + * stops), optionally normalizing it. + * + * \pre SP_IS_GRADIENT(gradient). + * \pre There exists a gradient in the chain that has stops. + */ +SPGradient * +sp_gradient_get_vector(SPGradient *gradient, bool force_vector) +{ + g_return_val_if_fail(gradient != NULL, NULL); + g_return_val_if_fail(SP_IS_GRADIENT(gradient), NULL); + + SPGradient *const src = chase_hrefs(gradient, has_stops); + return ( force_vector + ? sp_gradient_ensure_vector_normalized(src) + : src ); +} + +/** + * Returns the effective spread of given gradient (climbing up the refs chain if needed). + * + * \pre SP_IS_GRADIENT(gradient). + */ +SPGradientSpread +sp_gradient_get_spread(SPGradient *gradient) +{ + g_return_val_if_fail(SP_IS_GRADIENT(gradient), SP_GRADIENT_SPREAD_PAD); + + SPGradient const *src = chase_hrefs(gradient, has_spread_set); + return ( src + ? src->spread + : SP_GRADIENT_SPREAD_PAD ); // pad is the default +} + +/** + * Clears the gradient's svg:stop children from its repr. + */ +void +sp_gradient_repr_clear_vector(SPGradient *gr) +{ + Inkscape::XML::Node *repr = SP_OBJECT_REPR(gr); + + /* Collect stops from original repr */ + GSList *sl = NULL; + for (Inkscape::XML::Node *child = repr->firstChild() ; child != NULL; child = child->next() ) { + if (!strcmp(child->name(), "svg:stop")) { + sl = g_slist_prepend(sl, child); + } + } + /* Remove all stops */ + while (sl) { + /** \todo + * fixme: This should work, unless we make gradient + * into generic group. + */ + sp_repr_unparent((Inkscape::XML::Node *)sl->data); + sl = g_slist_remove(sl, sl->data); + } +} + +/** + * Writes the gradient's internal vector (whether from its own stops, or + * inherited from refs) into the gradient repr as svg:stop elements. + */ +void +sp_gradient_repr_write_vector(SPGradient *gr) +{ + g_return_if_fail(gr != NULL); + g_return_if_fail(SP_IS_GRADIENT(gr)); + + Inkscape::XML::Node *repr = SP_OBJECT_REPR(gr); + + /* We have to be careful, as vector may be our own, so construct repr list at first */ + GSList *cl = NULL; + + for (guint i = 0; i < gr->vector.stops.size(); i++) { + Inkscape::CSSOStringStream os; + Inkscape::XML::Node *child = sp_repr_new("svg:stop"); + sp_repr_set_css_double(child, "offset", gr->vector.stops[i].offset); + /* strictly speaking, offset an SVG rather than a CSS one, but exponents make no + * sense for offset proportions. */ + gchar c[64]; + sp_svg_write_color(c, 64, sp_color_get_rgba32_ualpha(&gr->vector.stops[i].color, 0x00)); + os << "stop-color:" << c << ";stop-opacity:" << gr->vector.stops[i].opacity; + child->setAttribute("style", os.str().c_str()); + /* Order will be reversed here */ + cl = g_slist_prepend(cl, child); + } + + sp_gradient_repr_clear_vector(gr); + + /* And insert new children from list */ + while (cl) { + Inkscape::XML::Node *child = static_cast(cl->data); + repr->addChild(child, NULL); + Inkscape::GC::release(child); + cl = g_slist_remove(cl, child); + } +} + + +static void +gradient_ref_modified(SPObject *href, guint flags, SPGradient *gradient) +{ + if (sp_gradient_invalidate_vector(gradient)) { + SP_OBJECT(gradient)->requestModified(SP_OBJECT_MODIFIED_FLAG); + /* Conditional to avoid causing infinite loop if there's a cycle in the href chain. */ + } +} + +/** Return true iff change made. */ +static bool +sp_gradient_invalidate_vector(SPGradient *gr) +{ + bool ret = false; + + if (gr->color != NULL) { + g_free(gr->color); + gr->color = NULL; + ret = true; + } + + if (gr->vector.built) { + gr->vector.built = false; + gr->vector.stops.clear(); + ret = true; + } + + return ret; +} + +/** Creates normalized color vector */ +static void +sp_gradient_rebuild_vector(SPGradient *gr) +{ + gint len = 0; + for ( SPObject *child = sp_object_first_child(SP_OBJECT(gr)) ; + child != NULL ; + child = SP_OBJECT_NEXT(child) ) { + if (SP_IS_STOP(child)) { + len ++; + } + } + + gr->has_stops = (len != 0); + + gr->vector.stops.clear(); + + SPGradient *ref = gr->ref->getObject(); + if ( !gr->has_stops && ref ) { + /* Copy vector from referenced gradient */ + gr->vector.built = true; // Prevent infinite recursion. + sp_gradient_ensure_vector(ref); + if (!ref->vector.stops.empty()) { + gr->vector.built = ref->vector.built; + gr->vector.stops.assign(ref->vector.stops.begin(), ref->vector.stops.end()); + return; + } + } + + for (SPObject *child = sp_object_first_child(SP_OBJECT(gr)) ; + child != NULL; + child = SP_OBJECT_NEXT(child) ) { + if (SP_IS_STOP(child)) { + SPStop *stop = SP_STOP(child); + + SPGradientStop gstop; + if (gr->vector.stops.size() > 0) { + // "Each gradient offset value is required to be equal to or greater than the + // previous gradient stop's offset value. If a given gradient stop's offset + // value is not equal to or greater than all previous offset values, then the + // offset value is adjusted to be equal to the largest of all previous offset + // values." + gstop.offset = MAX(stop->offset, gr->vector.stops.back().offset); + } else { + gstop.offset = stop->offset; + } + + // "Gradient offset values less than 0 (or less than 0%) are rounded up to + // 0%. Gradient offset values greater than 1 (or greater than 100%) are rounded + // down to 100%." + gstop.offset = CLAMP(gstop.offset, 0, 1); + + gstop.color = sp_stop_get_color(stop); + gstop.opacity = stop->opacity; + + gr->vector.stops.push_back(gstop); + } + } + + // Normalize per section 13.2.4 of SVG 1.1. + if (gr->vector.stops.size() == 0) { + /* "If no stops are defined, then painting shall occur as if 'none' were specified as the + * paint style." + */ + { + SPGradientStop gstop; + gstop.offset = 0.0; + sp_color_set_rgb_rgba32(&gstop.color, 0x00000000); + gstop.opacity = 0.0; + gr->vector.stops.push_back(gstop); + } + { + SPGradientStop gstop; + gstop.offset = 1.0; + sp_color_set_rgb_rgba32(&gstop.color, 0x00000000); + gstop.opacity = 0.0; + gr->vector.stops.push_back(gstop); + } + } else { + /* "If one stop is defined, then paint with the solid color fill using the color defined + * for that gradient stop." + */ + if (gr->vector.stops.front().offset > 0.0) { + // If the first one is not at 0, then insert a copy of the first at 0. + SPGradientStop gstop; + gstop.offset = 0.0; + sp_color_copy(&gstop.color, &gr->vector.stops.front().color); + gstop.opacity = gr->vector.stops.front().opacity; + gr->vector.stops.insert(gr->vector.stops.begin(), gstop); + } + if (gr->vector.stops.back().offset < 1.0) { + // If the last one is not at 1, then insert a copy of the last at 1. + SPGradientStop gstop; + gstop.offset = 1.0; + sp_color_copy(&gstop.color, &gr->vector.stops.back().color); + gstop.opacity = gr->vector.stops.back().opacity; + gr->vector.stops.push_back(gstop); + } + } + + gr->vector.built = true; +} + +/** + * The gradient's color array is newly created and set up from vector. + */ +void +sp_gradient_ensure_colors(SPGradient *gr) +{ + if (!gr->vector.built) { + sp_gradient_rebuild_vector(gr); + } + g_return_if_fail(!gr->vector.stops.empty()); + + /// \todo Where is the memory freed? + if (!gr->color) { + gr->color = g_new(guchar, 4 * NCOLORS); + } + + for (guint i = 0; i < gr->vector.stops.size() - 1; i++) { + guint32 color = sp_color_get_rgba32_falpha(&gr->vector.stops[i].color, + gr->vector.stops[i].opacity); + gint r0 = (color >> 24) & 0xff; + gint g0 = (color >> 16) & 0xff; + gint b0 = (color >> 8) & 0xff; + gint a0 = color & 0xff; + color = sp_color_get_rgba32_falpha(&gr->vector.stops[i + 1].color, + gr->vector.stops[i + 1].opacity); + gint r1 = (color >> 24) & 0xff; + gint g1 = (color >> 16) & 0xff; + gint b1 = (color >> 8) & 0xff; + gint a1 = color & 0xff; + gint o0 = (gint) floor(gr->vector.stops[i].offset * (NCOLORS - 0.001)); + gint o1 = (gint) floor(gr->vector.stops[i + 1].offset * (NCOLORS - 0.001)); + if (o1 > o0) { + gint dr = ((r1 - r0) << 16) / (o1 - o0); + gint dg = ((g1 - g0) << 16) / (o1 - o0); + gint db = ((b1 - b0) << 16) / (o1 - o0); + gint da = ((a1 - a0) << 16) / (o1 - o0); + gint r = r0 << 16; + gint g = g0 << 16; + gint b = b0 << 16; + gint a = a0 << 16; + for (int j = o0; j < o1 + 1; j++) { + gr->color[4 * j] = r >> 16; + gr->color[4 * j + 1] = g >> 16; + gr->color[4 * j + 2] = b >> 16; + gr->color[4 * j + 3] = a >> 16; + r += dr; + g += dg; + b += db; + a += da; + } + } + } +} + +/** + * Renders gradient vector to buffer as line. + * + * RGB buffer background should be set up beforehand. + * + * @param len,width,height,rowstride Buffer parameters (1 or 2 dimensional). + * @param span Full integer width of requested gradient. + * @param pos Buffer starting position in span. + */ +static void +sp_gradient_render_vector_line_rgba(SPGradient *const gradient, guchar *buf, + gint const len, gint const pos, gint const span) +{ + g_return_if_fail(gradient != NULL); + g_return_if_fail(SP_IS_GRADIENT(gradient)); + g_return_if_fail(buf != NULL); + g_return_if_fail(len > 0); + g_return_if_fail(pos >= 0); + g_return_if_fail(pos + len <= span); + g_return_if_fail(span > 0); + + if (!gradient->color) { + sp_gradient_ensure_colors(gradient); + } + + gint idx = (pos * 1024 << 8) / span; + gint didx = (1024 << 8) / span; + + for (gint x = 0; x < len; x++) { + /// \todo Can this be done with 4 byte copies? + *buf++ = gradient->color[4 * (idx >> 8)]; + *buf++ = gradient->color[4 * (idx >> 8) + 1]; + *buf++ = gradient->color[4 * (idx >> 8) + 2]; + *buf++ = gradient->color[4 * (idx >> 8) + 3]; + idx += didx; + } +} + +/** + * Render rectangular RGBA area from gradient vector. + */ +void +sp_gradient_render_vector_block_rgba(SPGradient *const gradient, guchar *buf, + gint const width, gint const height, gint const rowstride, + gint const pos, gint const span, bool const horizontal) +{ + g_return_if_fail(gradient != NULL); + g_return_if_fail(SP_IS_GRADIENT(gradient)); + g_return_if_fail(buf != NULL); + g_return_if_fail(width > 0); + g_return_if_fail(height > 0); + g_return_if_fail(pos >= 0); + g_return_if_fail((horizontal && (pos + width <= span)) || (!horizontal && (pos + height <= span))); + g_return_if_fail(span > 0); + + if (horizontal) { + sp_gradient_render_vector_line_rgba(gradient, buf, width, pos, span); + for (gint y = 1; y < height; y++) { + memcpy(buf + y * rowstride, buf, 4 * width); + } + } else { + guchar *tmp = (guchar *)alloca(4 * height); + sp_gradient_render_vector_line_rgba(gradient, tmp, height, pos, span); + for (gint y = 0; y < height; y++) { + guchar *b = buf + y * rowstride; + for (gint x = 0; x < width; x++) { + *b++ = tmp[0]; + *b++ = tmp[1]; + *b++ = tmp[2]; + *b++ = tmp[3]; + } + tmp += 4; + } + } +} + +/** + * Render rectangular RGB area from gradient vector. + */ +void +sp_gradient_render_vector_block_rgb(SPGradient *gradient, guchar *buf, + gint const width, gint const height, gint const rowstride, + gint const pos, gint const span, bool const horizontal) +{ + g_return_if_fail(gradient != NULL); + g_return_if_fail(SP_IS_GRADIENT(gradient)); + g_return_if_fail(buf != NULL); + g_return_if_fail(width > 0); + g_return_if_fail(height > 0); + g_return_if_fail(pos >= 0); + g_return_if_fail((horizontal && (pos + width <= span)) || (!horizontal && (pos + height <= span))); + g_return_if_fail(span > 0); + + if (horizontal) { + guchar *tmp = (guchar*)alloca(4 * width); + sp_gradient_render_vector_line_rgba(gradient, tmp, width, pos, span); + for (gint y = 0; y < height; y++) { + guchar *t = tmp; + for (gint x = 0; x < width; x++) { + gint a = t[3]; + gint fc = (t[0] - buf[0]) * a; + buf[0] = buf[0] + ((fc + (fc >> 8) + 0x80) >> 8); + fc = (t[1] - buf[1]) * a; + buf[1] = buf[1] + ((fc + (fc >> 8) + 0x80) >> 8); + fc = (t[2] - buf[2]) * a; + buf[2] = buf[2] + ((fc + (fc >> 8) + 0x80) >> 8); + buf += 3; + t += 4; + } + } + } else { + guchar *tmp = (guchar*)alloca(4 * height); + sp_gradient_render_vector_line_rgba(gradient, tmp, height, pos, span); + for (gint y = 0; y < height; y++) { + guchar *t = tmp + 4 * y; + for (gint x = 0; x < width; x++) { + gint a = t[3]; + gint fc = (t[0] - buf[0]) * a; + buf[0] = buf[0] + ((fc + (fc >> 8) + 0x80) >> 8); + fc = (t[1] - buf[1]) * a; + buf[1] = buf[1] + ((fc + (fc >> 8) + 0x80) >> 8); + fc = (t[2] - buf[2]) * a; + buf[2] = buf[2] + ((fc + (fc >> 8) + 0x80) >> 8); + } + } + } +} + +NR::Matrix +sp_gradient_get_g2d_matrix(SPGradient const *gr, NR::Matrix const &ctm, NR::Rect const &bbox) +{ + if (gr->units == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) { + return ( NR::scale(bbox.dimensions()) + * NR::translate(bbox.min()) + * ctm ); + } else { + return ctm; + } +} + +NR::Matrix +sp_gradient_get_gs2d_matrix(SPGradient const *gr, NR::Matrix const &ctm, NR::Rect const &bbox) +{ + if (gr->units == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) { + return ( gr->gradientTransform + * NR::scale(bbox.dimensions()) + * NR::translate(bbox.min()) + * ctm ); + } else { + return gr->gradientTransform * ctm; + } +} + +void +sp_gradient_set_gs2d_matrix(SPGradient *gr, NR::Matrix const &ctm, + NR::Rect const &bbox, NR::Matrix const &gs2d) +{ + gr->gradientTransform = gs2d / ctm; + if ( gr->units == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX ) { + gr->gradientTransform = ( gr->gradientTransform + / NR::translate(bbox.min()) + / NR::scale(bbox.dimensions()) ); + } + gr->gradientTransform_set = TRUE; + + SP_OBJECT(gr)->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +/* + * Linear Gradient + */ + +class SPLGPainter; + +/// A context with linear gradient, painter, and gradient renderer. +struct SPLGPainter { + SPPainter painter; + SPLinearGradient *lg; + + NRLGradientRenderer lgr; +}; + +static void sp_lineargradient_class_init(SPLinearGradientClass *klass); +static void sp_lineargradient_init(SPLinearGradient *lg); + +static void sp_lineargradient_build(SPObject *object, + SPDocument *document, + Inkscape::XML::Node *repr); +static void sp_lineargradient_set(SPObject *object, unsigned key, gchar const *value); +static Inkscape::XML::Node *sp_lineargradient_write(SPObject *object, Inkscape::XML::Node *repr, + guint flags); + +static SPPainter *sp_lineargradient_painter_new(SPPaintServer *ps, + NR::Matrix const &full_transform, + NR::Matrix const &parent_transform, + NRRect const *bbox); +static void sp_lineargradient_painter_free(SPPaintServer *ps, SPPainter *painter); + +static void sp_lg_fill(SPPainter *painter, NRPixBlock *pb); + +static SPGradientClass *lg_parent_class; + +/** + * Register SPLinearGradient class and return its type. + */ +GType +sp_lineargradient_get_type() +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPLinearGradientClass), + NULL, NULL, + (GClassInitFunc) sp_lineargradient_class_init, + NULL, NULL, + sizeof(SPLinearGradient), + 16, + (GInstanceInitFunc) sp_lineargradient_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_GRADIENT, "SPLinearGradient", &info, (GTypeFlags)0); + } + return type; +} + +/** + * SPLinearGradient vtable initialization. + */ +static void sp_lineargradient_class_init(SPLinearGradientClass *klass) +{ + SPObjectClass *sp_object_class = (SPObjectClass *) klass; + SPPaintServerClass *ps_class = (SPPaintServerClass *) klass; + + lg_parent_class = (SPGradientClass*)g_type_class_ref(SP_TYPE_GRADIENT); + + sp_object_class->build = sp_lineargradient_build; + sp_object_class->set = sp_lineargradient_set; + sp_object_class->write = sp_lineargradient_write; + + ps_class->painter_new = sp_lineargradient_painter_new; + ps_class->painter_free = sp_lineargradient_painter_free; +} + +/** + * Callback for SPLinearGradient object initialization. + */ +static void sp_lineargradient_init(SPLinearGradient *lg) +{ + lg->x1.unset(SVGLength::PERCENT, 0.0, 0.0); + lg->y1.unset(SVGLength::PERCENT, 0.5, 0.5); + lg->x2.unset(SVGLength::PERCENT, 1.0, 1.0); + lg->y2.unset(SVGLength::PERCENT, 0.5, 0.5); +} + +/** + * Callback: set attributes from associated repr. + */ +static void sp_lineargradient_build(SPObject *object, + SPDocument *document, + Inkscape::XML::Node *repr) +{ + if (((SPObjectClass *) lg_parent_class)->build) + (* ((SPObjectClass *) lg_parent_class)->build)(object, document, repr); + + sp_object_read_attr(object, "x1"); + sp_object_read_attr(object, "y1"); + sp_object_read_attr(object, "x2"); + sp_object_read_attr(object, "y2"); +} + +/** + * Callback: set attribute. + */ +static void +sp_lineargradient_set(SPObject *object, unsigned key, gchar const *value) +{ + SPLinearGradient *lg = SP_LINEARGRADIENT(object); + + switch (key) { + case SP_ATTR_X1: + lg->x1.readOrUnset(value, SVGLength::PERCENT, 0.0, 0.0); + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_Y1: + lg->y1.readOrUnset(value, SVGLength::PERCENT, 0.5, 0.5); + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_X2: + lg->x2.readOrUnset(value, SVGLength::PERCENT, 1.0, 1.0); + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_Y2: + lg->y2.readOrUnset(value, SVGLength::PERCENT, 0.5, 0.5); + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + default: + if (((SPObjectClass *) lg_parent_class)->set) + (* ((SPObjectClass *) lg_parent_class)->set)(object, key, value); + break; + } +} + +/** + * Callback: write attributes to associated repr. + */ +static Inkscape::XML::Node * +sp_lineargradient_write(SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + SPLinearGradient *lg = SP_LINEARGRADIENT(object); + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = sp_repr_new("svg:linearGradient"); + } + + if ((flags & SP_OBJECT_WRITE_ALL) || lg->x1._set) + sp_repr_set_svg_double(repr, "x1", lg->x1.computed); + if ((flags & SP_OBJECT_WRITE_ALL) || lg->y1._set) + sp_repr_set_svg_double(repr, "y1", lg->y1.computed); + if ((flags & SP_OBJECT_WRITE_ALL) || lg->x2._set) + sp_repr_set_svg_double(repr, "x2", lg->x2.computed); + if ((flags & SP_OBJECT_WRITE_ALL) || lg->y2._set) + sp_repr_set_svg_double(repr, "y2", lg->y2.computed); + + if (((SPObjectClass *) lg_parent_class)->write) + (* ((SPObjectClass *) lg_parent_class)->write)(object, repr, flags); + + return repr; +} + +/** + * Create linear gradient context. + * + * Basically we have to deal with transformations + * + * 1) color2norm - maps point in (0,NCOLORS) vector to (0,1) vector + * 2) norm2pos - maps (0,1) vector to x1,y1 - x2,y2 + * 2) gradientTransform + * 3) bbox2user + * 4) ctm == userspace to pixel grid + * + * See also (*) in sp-pattern about why we may need parent_transform. + * + * \todo (point 1 above) fixme: I do not know how to deal with start > 0 + * and end < 1. + */ +static SPPainter * +sp_lineargradient_painter_new(SPPaintServer *ps, + NR::Matrix const &full_transform, + NR::Matrix const &parent_transform, + NRRect const *bbox) +{ + SPLinearGradient *lg = SP_LINEARGRADIENT(ps); + SPGradient *gr = SP_GRADIENT(ps); + + if (!gr->color) sp_gradient_ensure_colors(gr); + + SPLGPainter *lgp = g_new(SPLGPainter, 1); + + lgp->painter.type = SP_PAINTER_IND; + lgp->painter.fill = sp_lg_fill; + + lgp->lg = lg; + + /** \todo + * Technically speaking, we map NCOLORS on line [start,end] onto line + * [0,1]. I almost think we should fill color array start and end in + * that case. The alternative would be to leave these just empty garbage + * or something similar. Originally I had 1023.9999 here - not sure + * whether we have really to cut out ceil int (Lauris). + */ + NR::Matrix color2norm(NR::identity()); + NR::Matrix color2px; + if (gr->units == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) { + NR::Matrix norm2pos(NR::identity()); + + /* BBox to user coordinate system */ + NR::Matrix bbox2user(bbox->x1 - bbox->x0, 0, 0, bbox->y1 - bbox->y0, bbox->x0, bbox->y0); + + NR::Matrix color2pos = color2norm * norm2pos; + NR::Matrix color2tpos = color2pos * gr->gradientTransform; + NR::Matrix color2user = color2tpos * bbox2user; + color2px = color2user * full_transform; + + } else { + /* Problem: What to do, if we have mixed lengths and percentages? */ + /* Currently we do ignore percentages at all, but that is not good (lauris) */ + + NR::Matrix norm2pos(NR::identity()); + NR::Matrix color2pos = color2norm * norm2pos; + NR::Matrix color2tpos = color2pos * gr->gradientTransform; + color2px = color2tpos * full_transform; + + } + + NRMatrix v2px; + color2px.copyto(&v2px); + + nr_lgradient_renderer_setup(&lgp->lgr, gr->color, sp_gradient_get_spread(gr), &v2px, + lg->x1.computed, lg->y1.computed, + lg->x2.computed, lg->y2.computed); + + return (SPPainter *) lgp; +} + +static void +sp_lineargradient_painter_free(SPPaintServer *ps, SPPainter *painter) +{ + g_free(painter); +} + +/** + * Directly set properties of linear gradient and request modified. + */ +void +sp_lineargradient_set_position(SPLinearGradient *lg, + gdouble x1, gdouble y1, + gdouble x2, gdouble y2) +{ + g_return_if_fail(lg != NULL); + g_return_if_fail(SP_IS_LINEARGRADIENT(lg)); + + /* fixme: units? (Lauris) */ + lg->x1.set(SVGLength::NONE, x1, x1); + lg->y1.set(SVGLength::NONE, y1, y1); + lg->x2.set(SVGLength::NONE, x2, x2); + lg->y2.set(SVGLength::NONE, y2, y2); + + SP_OBJECT(lg)->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +/** + * Callback when linear gradient object is rendered. + */ +static void +sp_lg_fill(SPPainter *painter, NRPixBlock *pb) +{ + SPLGPainter *lgp = (SPLGPainter *) painter; + + if (lgp->lg->color == NULL) { + sp_gradient_ensure_colors (lgp->lg); + lgp->lgr.vector = lgp->lg->color; + } + + nr_render((NRRenderer *) &lgp->lgr, pb, NULL); +} + +/* + * Radial Gradient + */ + +class SPRGPainter; + +/// A context with radial gradient, painter, and gradient renderer. +struct SPRGPainter { + SPPainter painter; + SPRadialGradient *rg; + NRRGradientRenderer rgr; +}; + +static void sp_radialgradient_class_init(SPRadialGradientClass *klass); +static void sp_radialgradient_init(SPRadialGradient *rg); + +static void sp_radialgradient_build(SPObject *object, + SPDocument *document, + Inkscape::XML::Node *repr); +static void sp_radialgradient_set(SPObject *object, unsigned key, gchar const *value); +static Inkscape::XML::Node *sp_radialgradient_write(SPObject *object, Inkscape::XML::Node *repr, + guint flags); + +static SPPainter *sp_radialgradient_painter_new(SPPaintServer *ps, + NR::Matrix const &full_transform, + NR::Matrix const &parent_transform, + NRRect const *bbox); +static void sp_radialgradient_painter_free(SPPaintServer *ps, SPPainter *painter); + +static void sp_rg_fill(SPPainter *painter, NRPixBlock *pb); + +static SPGradientClass *rg_parent_class; + +/** + * Register SPRadialGradient class and return its type. + */ +GType +sp_radialgradient_get_type() +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPRadialGradientClass), + NULL, NULL, + (GClassInitFunc) sp_radialgradient_class_init, + NULL, NULL, + sizeof(SPRadialGradient), + 16, + (GInstanceInitFunc) sp_radialgradient_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_GRADIENT, "SPRadialGradient", &info, (GTypeFlags)0); + } + return type; +} + +/** + * SPRadialGradient vtable initialization. + */ +static void sp_radialgradient_class_init(SPRadialGradientClass *klass) +{ + SPObjectClass *sp_object_class = (SPObjectClass *) klass; + SPPaintServerClass *ps_class = (SPPaintServerClass *) klass; + + rg_parent_class = (SPGradientClass*)g_type_class_ref(SP_TYPE_GRADIENT); + + sp_object_class->build = sp_radialgradient_build; + sp_object_class->set = sp_radialgradient_set; + sp_object_class->write = sp_radialgradient_write; + + ps_class->painter_new = sp_radialgradient_painter_new; + ps_class->painter_free = sp_radialgradient_painter_free; +} + +/** + * Callback for SPRadialGradient object initialization. + */ +static void +sp_radialgradient_init(SPRadialGradient *rg) +{ + rg->cx.unset(SVGLength::PERCENT, 0.5, 0.5); + rg->cy.unset(SVGLength::PERCENT, 0.5, 0.5); + rg->r.unset(SVGLength::PERCENT, 0.5, 0.5); + rg->fx.unset(SVGLength::PERCENT, 0.5, 0.5); + rg->fy.unset(SVGLength::PERCENT, 0.5, 0.5); +} + +/** + * Set radial gradient attributes from associated repr. + */ +static void +sp_radialgradient_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + if (((SPObjectClass *) rg_parent_class)->build) + (* ((SPObjectClass *) rg_parent_class)->build)(object, document, repr); + + sp_object_read_attr(object, "cx"); + sp_object_read_attr(object, "cy"); + sp_object_read_attr(object, "r"); + sp_object_read_attr(object, "fx"); + sp_object_read_attr(object, "fy"); +} + +/** + * Set radial gradient attribute. + */ +static void +sp_radialgradient_set(SPObject *object, unsigned key, gchar const *value) +{ + SPRadialGradient *rg = SP_RADIALGRADIENT(object); + + switch (key) { + case SP_ATTR_CX: + if (!rg->cx.read(value)) { + rg->cx.unset(SVGLength::PERCENT, 0.5, 0.5); + } + if (!rg->fx._set) { + rg->fx.value = rg->cx.value; + rg->fx.computed = rg->cx.computed; + } + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_CY: + if (!rg->cy.read(value)) { + rg->cy.unset(SVGLength::PERCENT, 0.5, 0.5); + } + if (!rg->fy._set) { + rg->fy.value = rg->cy.value; + rg->fy.computed = rg->cy.computed; + } + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_R: + if (!rg->r.read(value)) { + rg->r.unset(SVGLength::PERCENT, 0.5, 0.5); + } + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_FX: + if (!rg->fx.read(value)) { + rg->fx.unset(rg->cx.unit, rg->cx.value, rg->cx.computed); + } + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_FY: + if (!rg->fy.read(value)) { + rg->fy.unset(rg->cy.unit, rg->cy.value, rg->cy.computed); + } + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + default: + if (((SPObjectClass *) rg_parent_class)->set) + ((SPObjectClass *) rg_parent_class)->set(object, key, value); + break; + } +} + +/** + * Write radial gradient attributes to associated repr. + */ +static Inkscape::XML::Node * +sp_radialgradient_write(SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + SPRadialGradient *rg = SP_RADIALGRADIENT(object); + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = sp_repr_new("svg:radialGradient"); + } + + if ((flags & SP_OBJECT_WRITE_ALL) || rg->cx._set) sp_repr_set_svg_double(repr, "cx", rg->cx.computed); + if ((flags & SP_OBJECT_WRITE_ALL) || rg->cy._set) sp_repr_set_svg_double(repr, "cy", rg->cy.computed); + if ((flags & SP_OBJECT_WRITE_ALL) || rg->r._set) sp_repr_set_svg_double(repr, "r", rg->r.computed); + if ((flags & SP_OBJECT_WRITE_ALL) || rg->fx._set) sp_repr_set_svg_double(repr, "fx", rg->fx.computed); + if ((flags & SP_OBJECT_WRITE_ALL) || rg->fy._set) sp_repr_set_svg_double(repr, "fy", rg->fy.computed); + + if (((SPObjectClass *) rg_parent_class)->write) + (* ((SPObjectClass *) rg_parent_class)->write)(object, repr, flags); + + return repr; +} + +/** + * Create radial gradient context. + */ +static SPPainter * +sp_radialgradient_painter_new(SPPaintServer *ps, + NR::Matrix const &full_transform, + NR::Matrix const &parent_transform, + NRRect const *bbox) +{ + SPRadialGradient *rg = SP_RADIALGRADIENT(ps); + SPGradient *gr = SP_GRADIENT(ps); + + if (!gr->color) sp_gradient_ensure_colors(gr); + + SPRGPainter *rgp = g_new(SPRGPainter, 1); + + rgp->painter.type = SP_PAINTER_IND; + rgp->painter.fill = sp_rg_fill; + + rgp->rg = rg; + + NR::Matrix gs2px; + + if (gr->units == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) { + /** \todo + * fixme: We may try to normalize here too, look at + * linearGradient (Lauris) + */ + + /* BBox to user coordinate system */ + NR::Matrix bbox2user(bbox->x1 - bbox->x0, 0, 0, bbox->y1 - bbox->y0, bbox->x0, bbox->y0); + + NR::Matrix gs2user = gr->gradientTransform * bbox2user; + + gs2px = gs2user * full_transform; + } else { + /** \todo + * Problem: What to do, if we have mixed lengths and percentages? + * Currently we do ignore percentages at all, but that is not + * good (lauris) + */ + + gs2px = gr->gradientTransform * full_transform; + } + + NRMatrix gs2px_nr; + gs2px.copyto(&gs2px_nr); + + nr_rgradient_renderer_setup(&rgp->rgr, gr->color, sp_gradient_get_spread(gr), + &gs2px_nr, + rg->cx.computed, rg->cy.computed, + rg->fx.computed, rg->fy.computed, + rg->r.computed); + + return (SPPainter *) rgp; +} + +static void +sp_radialgradient_painter_free(SPPaintServer *ps, SPPainter *painter) +{ + g_free(painter); +} + +/** + * Directly set properties of radial gradient and request modified. + */ +void +sp_radialgradient_set_position(SPRadialGradient *rg, + gdouble cx, gdouble cy, gdouble fx, gdouble fy, gdouble r) +{ + g_return_if_fail(rg != NULL); + g_return_if_fail(SP_IS_RADIALGRADIENT(rg)); + + /* fixme: units? (Lauris) */ + rg->cx.set(SVGLength::NONE, cx, cx); + rg->cy.set(SVGLength::NONE, cy, cy); + rg->fx.set(SVGLength::NONE, fx, fx); + rg->fy.set(SVGLength::NONE, fy, fy); + rg->r.set(SVGLength::NONE, r, r); + + SP_OBJECT(rg)->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +/** + * Callback when radial gradient object is rendered. + */ +static void +sp_rg_fill(SPPainter *painter, NRPixBlock *pb) +{ + SPRGPainter *rgp = (SPRGPainter *) painter; + + if (rgp->rg->color == NULL) { + sp_gradient_ensure_colors (rgp->rg); + rgp->rgr.vector = rgp->rg->color; + } + + nr_render((NRRenderer *) &rgp->rgr, pb, NULL); +} + +/* + 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 : diff --git a/src/sp-gradient.h b/src/sp-gradient.h new file mode 100644 index 000000000..9c152598b --- /dev/null +++ b/src/sp-gradient.h @@ -0,0 +1,95 @@ +#ifndef __SP_GRADIENT_H__ +#define __SP_GRADIENT_H__ + +/** \file + * SVG and implementation + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include "libnr/nr-matrix.h" +#include "sp-paint-server.h" +#include "sp-gradient-spread.h" +#include "sp-gradient-units.h" +#include "sp-gradient-vector.h" + +struct SPGradientReference; + +typedef enum { + SP_GRADIENT_TYPE_UNKNOWN, + SP_GRADIENT_TYPE_LINEAR, + SP_GRADIENT_TYPE_RADIAL +} SPGradientType; + +typedef enum { + SP_GRADIENT_STATE_UNKNOWN, + SP_GRADIENT_STATE_VECTOR, + SP_GRADIENT_STATE_PRIVATE +} SPGradientState; + +typedef enum { + POINT_LG_P1, + POINT_LG_P2, + POINT_RG_CENTER, + POINT_RG_R1, + POINT_RG_R2, + POINT_RG_FOCUS +} GrPoint; + +/** + * Gradient + * + * Implement spread, stops list + * \todo fixme: Implement more here (Lauris) + */ +struct SPGradient : public SPPaintServer { + /** Reference (href) */ + SPGradientReference *ref; + /** State in Inkscape gradient system */ + guint state : 2; + /** gradientUnits attribute */ + SPGradientUnits units; + guint units_set : 1; + /** gradientTransform attribute */ + NR::Matrix gradientTransform; + guint gradientTransform_set : 1; + /** spreadMethod attribute */ + SPGradientSpread spread; + guint spread_set : 1; + /** Gradient stops */ + guint has_stops : 1; + /** Composed vector */ + SPGradientVector vector; + /** Rendered color array (4 * 1024 bytes) */ + guchar *color; +}; + +/** + * The SPGradient vtable. + */ +struct SPGradientClass { + SPPaintServerClass parent_class; +}; + + +#include "sp-gradient-fns.h" + +#endif /* !__SP_GRADIENT_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:encoding=utf-8:textwidth=99 : diff --git a/src/sp-guide-attachment.h b/src/sp-guide-attachment.h new file mode 100644 index 000000000..277b435b6 --- /dev/null +++ b/src/sp-guide-attachment.h @@ -0,0 +1,43 @@ +#ifndef __SP_GUIDE_ATTACHMENT_H__ +#define __SP_GUIDE_ATTACHMENT_H__ + +#include + +class SPGuideAttachment { +public: + SPItem *item; + int snappoint_ix; + +public: + SPGuideAttachment() : + item(static_cast(0)) + { } + + SPGuideAttachment(SPItem *i, int s) : + item(i), + snappoint_ix(s) + { } + + bool operator==(SPGuideAttachment const &o) const { + return ( ( item == o.item ) + && ( snappoint_ix == o.snappoint_ix ) ); + } + + bool operator!=(SPGuideAttachment const &o) const { + return !(*this == o); + } +}; + + +#endif /* !__SP_GUIDE_ATTACHMENT_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:encoding=utf-8:textwidth=99 : diff --git a/src/sp-guide-constraint.h b/src/sp-guide-constraint.h new file mode 100644 index 000000000..5444b8468 --- /dev/null +++ b/src/sp-guide-constraint.h @@ -0,0 +1,44 @@ +#ifndef __SP_GUIDE_CONSTRAINT_H__ +#define __SP_GUIDE_CONSTRAINT_H__ + +#include + +class SPGuideConstraint { +public: + SPGuide *g; + int snappoint_ix; + +public: + explicit SPGuideConstraint() : + g(static_cast(0)) + { } + + explicit SPGuideConstraint(SPGuide *g, int snappoint_ix) : + g(g), + snappoint_ix(snappoint_ix) + { } + + bool operator==(SPGuideConstraint const &o) const { + return ( ( g == o.g ) + && ( snappoint_ix == o.snappoint_ix ) ); + } + + bool operator!=(SPGuideConstraint const &o) const { + return !( *this == o ); + } +}; + + +#endif /* !__SP_GUIDE_CONSTRAINT_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:encoding=utf-8:textwidth=99 : diff --git a/src/sp-guide.cpp b/src/sp-guide.cpp new file mode 100644 index 000000000..98ca66092 --- /dev/null +++ b/src/sp-guide.cpp @@ -0,0 +1,326 @@ +#define __SP_GUIDE_C__ + +/* + * Inkscape guideline implementation + * + * Authors: + * Lauris Kaplinski + * Peter Moulder + * + * Copyright (C) 2000-2002 authors + * Copyright (C) 2004 Monash University + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include +#endif +#include "display/guideline.h" +#include "svg/svg.h" +#include "attributes.h" +#include "sp-guide.h" +#include +#include +#include +#include +#include +#include +using std::vector; + +enum { + PROP_0, + PROP_COLOR, + PROP_HICOLOR +}; + +static void sp_guide_class_init(SPGuideClass *gc); +static void sp_guide_init(SPGuide *guide); +static void sp_guide_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); +static void sp_guide_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); + +static void sp_guide_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); +static void sp_guide_release(SPObject *object); +static void sp_guide_set(SPObject *object, unsigned int key, const gchar *value); + +static SPObjectClass *parent_class; + +GType sp_guide_get_type(void) +{ + static GType guide_type = 0; + + if (!guide_type) { + GTypeInfo guide_info = { + sizeof(SPGuideClass), + NULL, NULL, + (GClassInitFunc) sp_guide_class_init, + NULL, NULL, + sizeof(SPGuide), + 16, + (GInstanceInitFunc) sp_guide_init, + NULL, /* value_table */ + }; + guide_type = g_type_register_static(SP_TYPE_OBJECT, "SPGuide", &guide_info, (GTypeFlags) 0); + } + + return guide_type; +} + +static void sp_guide_class_init(SPGuideClass *gc) +{ + GObjectClass *gobject_class = (GObjectClass *) gc; + SPObjectClass *sp_object_class = (SPObjectClass *) gc; + + parent_class = (SPObjectClass*) g_type_class_ref(SP_TYPE_OBJECT); + + gobject_class->set_property = sp_guide_set_property; + gobject_class->get_property = sp_guide_get_property; + + sp_object_class->build = sp_guide_build; + sp_object_class->release = sp_guide_release; + sp_object_class->set = sp_guide_set; + + g_object_class_install_property(gobject_class, + PROP_COLOR, + g_param_spec_uint("color", "Color", "Color", + 0, + 0xffffffff, + 0xff000000, + (GParamFlags) G_PARAM_READWRITE)); + + g_object_class_install_property(gobject_class, + PROP_HICOLOR, + g_param_spec_uint("hicolor", "HiColor", "HiColor", + 0, + 0xffffffff, + 0xff000000, + (GParamFlags) G_PARAM_READWRITE)); +} + +static void sp_guide_init(SPGuide *guide) +{ + guide->normal = component_vectors[NR::Y]; + /* constrain y coordinate; horizontal line. I doubt it ever matters what we initialize + this to. */ + guide->position = 0.0; + guide->color = 0x0000ff7f; + guide->hicolor = 0xff00007f; +} + +static void sp_guide_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + SPGuide &guide = *SP_GUIDE(object); + + switch (prop_id) { + case PROP_COLOR: + guide.color = g_value_get_uint(value); + for (GSList *l = guide.views; l != NULL; l = l->next) { + sp_guideline_set_color(SP_GUIDELINE(l->data), guide.color); + } + break; + + case PROP_HICOLOR: + guide.hicolor = g_value_get_uint(value); + break; + } +} + +static void sp_guide_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + SPGuide const &guide = *SP_GUIDE(object); + + switch (prop_id) { + case PROP_COLOR: + g_value_set_uint(value, guide.color); + break; + case PROP_HICOLOR: + g_value_set_uint(value, guide.hicolor); + break; + } +} + +static void sp_guide_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + if (((SPObjectClass *) (parent_class))->build) { + (* ((SPObjectClass *) (parent_class))->build)(object, document, repr); + } + + sp_object_read_attr(object, "orientation"); + sp_object_read_attr(object, "position"); +} + +static void sp_guide_release(SPObject *object) +{ + SPGuide *guide = (SPGuide *) object; + + while (guide->views) { + gtk_object_destroy(GTK_OBJECT(guide->views->data)); + guide->views = g_slist_remove(guide->views, guide->views->data); + } + + if (((SPObjectClass *) parent_class)->release) { + ((SPObjectClass *) parent_class)->release(object); + } +} + +static void sp_guide_set(SPObject *object, unsigned int key, const gchar *value) +{ + SPGuide *guide = SP_GUIDE(object); + + switch (key) { + case SP_ATTR_ORIENTATION: + if (value && !strcmp(value, "horizontal")) { + /* Visual representation of a horizontal line, constrain vertically (y coordinate). */ + guide->normal = component_vectors[NR::Y]; + } else { + guide->normal = component_vectors[NR::X]; + } + break; + case SP_ATTR_POSITION: + sp_svg_number_read_d(value, &guide->position); + // update position in non-committing way + // fixme: perhaps we need to add an update method instead, and request_update here + sp_guide_moveto(*guide, guide->position, false); + break; + default: + if (((SPObjectClass *) (parent_class))->set) { + ((SPObjectClass *) (parent_class))->set(object, key, value); + } + break; + } +} + +void sp_guide_show(SPGuide *guide, SPCanvasGroup *group, GCallback handler) +{ + bool const vertical_line_p = ( guide->normal == component_vectors[NR::X] ); + g_assert(( guide->normal == component_vectors[NR::X] ) || + ( guide->normal == component_vectors[NR::Y] ) ); + SPCanvasItem *item = sp_guideline_new(group, guide->position, vertical_line_p); + sp_guideline_set_color(SP_GUIDELINE(item), guide->color); + + g_signal_connect(G_OBJECT(item), "event", G_CALLBACK(handler), guide); + + guide->views = g_slist_prepend(guide->views, item); +} + +void sp_guide_hide(SPGuide *guide, SPCanvas *canvas) +{ + g_assert(guide != NULL); + g_assert(SP_IS_GUIDE(guide)); + g_assert(canvas != NULL); + g_assert(SP_IS_CANVAS(canvas)); + + for (GSList *l = guide->views; l != NULL; l = l->next) { + if (canvas == SP_CANVAS_ITEM(l->data)->canvas) { + gtk_object_destroy(GTK_OBJECT(l->data)); + guide->views = g_slist_remove(guide->views, l->data); + return; + } + } + + g_assert_not_reached(); +} + +void sp_guide_sensitize(SPGuide *guide, SPCanvas *canvas, gboolean sensitive) +{ + g_assert(guide != NULL); + g_assert(SP_IS_GUIDE(guide)); + g_assert(canvas != NULL); + g_assert(SP_IS_CANVAS(canvas)); + + for (GSList *l = guide->views; l != NULL; l = l->next) { + if (canvas == SP_CANVAS_ITEM(l->data)->canvas) { + sp_guideline_set_sensitive(SP_GUIDELINE(l->data), sensitive); + return; + } + } + + g_assert_not_reached(); +} + +double sp_guide_position_from_pt(SPGuide const *guide, NR::Point const &pt) +{ + return dot(guide->normal, pt); +} + +/** + * \arg commit False indicates temporary moveto in response to motion event while dragging, + * true indicates a "committing" version: in response to button release event after + * dragging a guideline, or clicking OK in guide editing dialog. + */ +void sp_guide_moveto(SPGuide const &guide, gdouble const position, bool const commit) +{ + g_assert(SP_IS_GUIDE(&guide)); + + for (GSList *l = guide.views; l != NULL; l = l->next) { + sp_guideline_set_position(SP_GUIDELINE(l->data), + position); + } + + /* Calling sp_repr_set_svg_double must precede calling sp_item_notify_moveto in the commit + case, so that the guide's new position is available for sp_item_rm_unsatisfied_cns. */ + if (commit) { + sp_repr_set_svg_double(SP_OBJECT(&guide)->repr, + "position", position); + } + + for (vector::const_iterator i(guide.attached_items.begin()), + iEnd(guide.attached_items.end()); + i != iEnd; ++i) + { + SPGuideAttachment const &att = *i; + sp_item_notify_moveto(*att.item, guide, att.snappoint_ix, position, commit); + } +} + +/** + * Returns a human-readable description of the guideline for use in dialog boxes and status bar. + * + * The caller is responsible for freeing the string. + */ +char *sp_guide_description(SPGuide const *guide) +{ + using NR::X; + using NR::Y; + + if ( guide->normal == component_vectors[X] ) { + return g_strdup(_("vertical guideline")); + } else if ( guide->normal == component_vectors[Y] ) { + return g_strdup(_("horizontal guideline")); + } else { + double const radians = atan2(guide->normal[X], + guide->normal[Y]); + /* flip y axis and rotate 90 degrees to convert to line angle */ + double const degrees = ( radians / M_PI ) * 180.0; + int const degrees_int = (int) floor( degrees + .5 ); + return g_strdup_printf("%d degree guideline", degrees_int); + /* Alternative suggestion: "angled guideline". */ + } +} + +void sp_guide_remove(SPGuide *guide) +{ + g_assert(SP_IS_GUIDE(guide)); + + for (vector::const_iterator i(guide->attached_items.begin()), + iEnd(guide->attached_items.end()); + i != iEnd; ++i) + { + SPGuideAttachment const &att = *i; + remove_last(att.item->constraints, SPGuideConstraint(guide, att.snappoint_ix)); + } + guide->attached_items.clear(); + + sp_repr_unparent(SP_OBJECT(guide)->repr); +} + +/* + 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/sp-guide.h b/src/sp-guide.h new file mode 100644 index 000000000..a3b876483 --- /dev/null +++ b/src/sp-guide.h @@ -0,0 +1,64 @@ +#ifndef SP_GUIDE_H +#define SP_GUIDE_H + +/* + * SPGuide + * + * A guideline + * + * Copyright (C) Lauris Kaplinski 2000 + * + */ + +#include + +#include "display/display-forward.h" +#include "libnr/nr-point.h" +#include "sp-object.h" +#include "sp-guide-attachment.h" + +#define SP_TYPE_GUIDE (sp_guide_get_type()) +#define SP_GUIDE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SP_TYPE_GUIDE, SPGuide)) +#define SP_GUIDE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SP_TYPE_GUIDE, SPGuideClass)) +#define SP_IS_GUIDE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SP_TYPE_GUIDE)) +#define SP_IS_GUIDE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SP_TYPE_GUIDE)) + +/* Represents the constraint on p that dot(g.direction, p) == g.position. */ +struct SPGuide : public SPObject { + NR::Point normal; + gdouble position; + guint32 color; + guint32 hicolor; + GSList *views; + std::vector attached_items; +}; + +struct SPGuideClass { + SPObjectClass parent_class; +}; + +GType sp_guide_get_type(); + +void sp_guide_show(SPGuide *guide, SPCanvasGroup *group, GCallback handler); +void sp_guide_hide(SPGuide *guide, SPCanvas *canvas); +void sp_guide_sensitize(SPGuide *guide, SPCanvas *canvas, gboolean sensitive); + +double sp_guide_position_from_pt(SPGuide const *guide, NR::Point const &pt); +void sp_guide_moveto(SPGuide const &guide, gdouble const position, bool const commit); +void sp_guide_remove(SPGuide *guide); + +char *sp_guide_description(SPGuide const *guide); + + +#endif /* !SP_GUIDE_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:encoding=utf-8:textwidth=99 : diff --git a/src/sp-image.cpp b/src/sp-image.cpp new file mode 100644 index 000000000..869981567 --- /dev/null +++ b/src/sp-image.cpp @@ -0,0 +1,1200 @@ +#define __SP_IMAGE_C__ + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * Edward Flick (EAF) + * + * Copyright (C) 1999-2005 Authors + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include + +//#define GDK_PIXBUF_ENABLE_BACKEND 1 +//#include +#include "display/nr-arena-image.h" + +//Added for preserveAspectRatio support -- EAF +#include "enums.h" +#include "attributes.h" + +#include "print.h" +#include "brokenimage.xpm" +#include "document.h" +#include "sp-image.h" +#include +#include "xml/quote.h" +#include + +#include "io/sys.h" +#include + +/* + * SPImage + */ + + +static void sp_image_class_init (SPImageClass * klass); +static void sp_image_init (SPImage * image); + +static void sp_image_build (SPObject * object, SPDocument * document, Inkscape::XML::Node * repr); +static void sp_image_release (SPObject * object); +static void sp_image_set (SPObject *object, unsigned int key, const gchar *value); +static void sp_image_update (SPObject *object, SPCtx *ctx, unsigned int flags); +static Inkscape::XML::Node *sp_image_write (SPObject *object, Inkscape::XML::Node *repr, guint flags); + +static void sp_image_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const flags); +static void sp_image_print (SPItem * item, SPPrintContext *ctx); +static gchar * sp_image_description (SPItem * item); +static void sp_image_snappoints(SPItem const *item, SnapPointsIter p); +static NRArenaItem *sp_image_show (SPItem *item, NRArena *arena, unsigned int key, unsigned int flags); +static NR::Matrix sp_image_set_transform (SPItem *item, NR::Matrix const &xform); + +GdkPixbuf * sp_image_repr_read_image (Inkscape::XML::Node * repr); +static GdkPixbuf *sp_image_pixbuf_force_rgba (GdkPixbuf * pixbuf); +static void sp_image_update_canvas_image (SPImage *image); +static GdkPixbuf * sp_image_repr_read_dataURI (const gchar * uri_data); +static GdkPixbuf * sp_image_repr_read_b64 (const gchar * uri_data); + +static SPItemClass *parent_class; + + +extern "C" +{ + void user_read_data( png_structp png_ptr, png_bytep data, png_size_t length ); + void user_write_data( png_structp png_ptr, png_bytep data, png_size_t length ); + void user_flush_data( png_structp png_ptr ); + +} + +namespace Inkscape { +namespace IO { + +class PushPull +{ +public: + gboolean first; + FILE* fp; + guchar* scratch; + gsize size; + gsize used; + gsize offset; + GdkPixbufLoader *loader; + + PushPull() : first(TRUE), + fp(0), + scratch(0), + size(0), + used(0), + offset(0), + loader(0) {}; + + gboolean readMore() + { + gboolean good = FALSE; + if ( offset ) + { + g_memmove( scratch, scratch + offset, used - offset ); + used -= offset; + offset = 0; + } + if ( used < size ) + { + gsize space = size - used; + gsize got = fread( scratch + used, 1, space, fp ); + if ( got ) + { + if ( loader ) + { + GError *err = NULL; + //g_message( " __read %d bytes", (int)got ); + if ( !gdk_pixbuf_loader_write( loader, scratch + used, got, &err ) ) + { + //g_message("_error writing pixbuf data"); + } + } + + used += got; + good = TRUE; + } + else + { + good = FALSE; + } + } + return good; + } + + gsize available() const + { + return (used - offset); + } + + gsize readOut( gpointer data, gsize length ) + { + gsize giving = available(); + if ( length < giving ) + { + giving = length; + } + g_memmove( data, scratch + offset, giving ); + offset += giving; + if ( offset >= used ) + { + offset = 0; + used = 0; + } + return giving; + } + + void clear() + { + offset = 0; + used = 0; + } + +private: + PushPull& operator = (const PushPull& other); + PushPull(const PushPull& other); +}; + +void user_read_data( png_structp png_ptr, png_bytep data, png_size_t length ) +{ +// g_message( "user_read_data(%d)", length ); + + PushPull* youme = (PushPull*)png_get_io_ptr(png_ptr); + + gsize filled = 0; + gboolean canRead = TRUE; + + while ( filled < length && canRead ) + { + gsize some = youme->readOut( data + filled, length - filled ); + filled += some; + if ( filled < length ) + { + canRead &= youme->readMore(); + } + } +// g_message("things out"); +} + +void user_write_data( png_structp png_ptr, png_bytep data, png_size_t length ) +{ + //g_message( "user_write_data(%d)", length ); +} + +void user_flush_data( png_structp png_ptr ) +{ + //g_message( "user_flush_data" ); +} + +GdkPixbuf* pixbuf_new_from_file( const char *filename, GError **error ) +{ + GdkPixbuf* buf = NULL; + PushPull youme; + gint dpiX = 0; + gint dpiY = 0; + + //buf = gdk_pixbuf_new_from_file( filename, error ); + dump_fopen_call( filename, "pixbuf_new_from_file" ); + FILE* fp = fopen_utf8name( filename, "r" ); + if ( fp ) + { + GdkPixbufLoader *loader = gdk_pixbuf_loader_new(); + if ( loader ) + { + GError *err = NULL; + + // short buffer + guchar scratch[1024]; + gboolean latter = FALSE; + gboolean isPng = FALSE; + png_structp pngPtr = NULL; + png_infop infoPtr = NULL; + //png_infop endPtr = NULL; + + youme.fp = fp; + youme.scratch = scratch; + youme.size = sizeof(scratch); + youme.used = 0; + youme.offset = 0; + youme.loader = loader; + + while ( !feof(fp) ) + { + if ( youme.readMore() ) + { + if ( youme.first ) + { + //g_message( "First data chunk" ); + youme.first = FALSE; + isPng = !png_sig_cmp( scratch + youme.offset, 0, youme.available() ); + //g_message( " png? %s", (isPng ? "Yes":"No") ); + if ( isPng ) + { + pngPtr = png_create_read_struct( PNG_LIBPNG_VER_STRING, + NULL,//(png_voidp)user_error_ptr, + NULL,//user_error_fn, + NULL//user_warning_fn + ); + if ( pngPtr ) + { + infoPtr = png_create_info_struct( pngPtr ); + //endPtr = png_create_info_struct( pngPtr ); + + png_set_read_fn( pngPtr, &youme, user_read_data ); + //g_message( "In" ); + + //png_read_info( pngPtr, infoPtr ); + png_read_png( pngPtr, infoPtr, PNG_TRANSFORM_IDENTITY, NULL ); + + //g_message("out"); + + //png_read_end(pngPtr, endPtr); + + /* + if ( png_get_valid( pngPtr, infoPtr, PNG_INFO_pHYs ) ) + { + g_message("pHYs chunk now valid" ); + } + if ( png_get_valid( pngPtr, infoPtr, PNG_INFO_sCAL ) ) + { + g_message("sCAL chunk now valid" ); + } + */ + + png_uint_32 res_x = 0; + png_uint_32 res_y = 0; + int unit_type = 0; + if ( png_get_pHYs( pngPtr, infoPtr, &res_x, &res_y, &unit_type) ) + { +// g_message( "pHYs yes (%d, %d) %d (%s)", (int)res_x, (int)res_y, unit_type, +// (unit_type == 1? "per meter" : "unknown") +// ); + +// g_message( " dpi: (%d, %d)", +// (int)(0.5 + ((double)res_x)/39.37), +// (int)(0.5 + ((double)res_y)/39.37) ); + if ( unit_type == PNG_RESOLUTION_METER ) + { + // TODO come up with a more accurate DPI setting + dpiX = (int)(0.5 + ((double)res_x)/39.37); + dpiY = (int)(0.5 + ((double)res_y)/39.37); + } + } + else + { +// g_message( "pHYs no" ); + } + +/* + double width = 0; + double height = 0; + int unit = 0; + if ( png_get_sCAL(pngPtr, infoPtr, &unit, &width, &height) ) + { + gchar* vals[] = { + "unknown", // PNG_SCALE_UNKNOWN + "meter", // PNG_SCALE_METER + "radian", // PNG_SCALE_RADIAN + "last", // + NULL + }; + + g_message( "sCAL: (%f, %f) %d (%s)", + width, height, unit, + ((unit >= 0 && unit < 3) ? vals[unit]:"???") + ); + } +*/ + + // now clean it up. + png_destroy_read_struct( &pngPtr, &infoPtr, NULL );//&endPtr ); + } + else + { + g_message("Error when creating PNG read struct"); + } + } + } + else if ( !latter ) + { + latter = TRUE; + //g_message(" READing latter"); + } + // Now clear out the buffer so we can read more. + // (dumping out unused) + youme.clear(); + } + } + + gboolean ok = gdk_pixbuf_loader_close(loader, &err); + if ( ok ) + { + buf = gdk_pixbuf_loader_get_pixbuf( loader ); + if ( buf ) + { + g_object_ref(buf); + + if ( dpiX ) + { + gchar *tmp = g_strdup_printf( "%d", dpiX ); + if ( tmp ) + { + //gdk_pixbuf_set_option( buf, "Inkscape::DpiX", tmp ); + g_free( tmp ); + } + } + if ( dpiY ) + { + gchar *tmp = g_strdup_printf( "%d", dpiY ); + if ( tmp ) + { + //gdk_pixbuf_set_option( buf, "Inkscape::DpiY", tmp ); + g_free( tmp ); + } + } + } + } + else + { + // do something + g_message("error loading pixbuf at close"); + } + + g_object_unref(loader); + } + else + { + g_message("error when creating pixbuf loader"); + } + fclose( fp ); + fp = NULL; + } + else + { + g_warning ("unable to open file: %s", filename); + } + +/* + if ( buf ) + { + const gchar* bloop = gdk_pixbuf_get_option( buf, "Inkscape::DpiX" ); + if ( bloop ) + { + g_message("DPI X is [%s]", bloop); + } + bloop = gdk_pixbuf_get_option( buf, "Inkscape::DpiY" ); + if ( bloop ) + { + g_message("DPI Y is [%s]", bloop); + } + } +*/ + + return buf; +} + +} +} + +GType +sp_image_get_type (void) +{ + static GType image_type = 0; + if (!image_type) { + GTypeInfo image_info = { + sizeof (SPImageClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_image_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (SPImage), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_image_init, + NULL, /* value_table */ + }; + image_type = g_type_register_static (sp_item_get_type (), "SPImage", &image_info, (GTypeFlags)0); + } + return image_type; +} + +static void +sp_image_class_init (SPImageClass * klass) +{ + GObjectClass * gobject_class; + SPObjectClass * sp_object_class; + SPItemClass * item_class; + + gobject_class = (GObjectClass *) klass; + sp_object_class = (SPObjectClass *) klass; + item_class = (SPItemClass *) klass; + + parent_class = (SPItemClass*)g_type_class_ref (sp_item_get_type ()); + + sp_object_class->build = sp_image_build; + sp_object_class->release = sp_image_release; + sp_object_class->set = sp_image_set; + sp_object_class->update = sp_image_update; + sp_object_class->write = sp_image_write; + + item_class->bbox = sp_image_bbox; + item_class->print = sp_image_print; + item_class->description = sp_image_description; + item_class->show = sp_image_show; + item_class->snappoints = sp_image_snappoints; + item_class->set_transform = sp_image_set_transform; +} + +static void +sp_image_init (SPImage *image) +{ + image->x.unset(); + image->y.unset(); + image->width.unset(); + image->height.unset(); + image->aspect_align = SP_ASPECT_NONE; +} + +static void +sp_image_build (SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + if (((SPObjectClass *) parent_class)->build) + ((SPObjectClass *) parent_class)->build (object, document, repr); + + sp_object_read_attr (object, "xlink:href"); + sp_object_read_attr (object, "x"); + sp_object_read_attr (object, "y"); + sp_object_read_attr (object, "width"); + sp_object_read_attr (object, "height"); + sp_object_read_attr (object, "preserveAspectRatio"); + + /* Register */ + sp_document_add_resource (document, "image", object); +} + +static void +sp_image_release (SPObject *object) +{ + SPImage *image; + + image = SP_IMAGE (object); + + if (SP_OBJECT_DOCUMENT (object)) { + /* Unregister ourselves */ + sp_document_remove_resource (SP_OBJECT_DOCUMENT (object), "image", SP_OBJECT (object)); + } + + if (image->href) { + g_free (image->href); + image->href = NULL; + } + + if (image->pixbuf) { + gdk_pixbuf_unref (image->pixbuf); + image->pixbuf = NULL; + } + + if (((SPObjectClass *) parent_class)->release) + ((SPObjectClass *) parent_class)->release (object); +} + +static void +sp_image_set (SPObject *object, unsigned int key, const gchar *value) +{ + SPImage *image; + + image = SP_IMAGE (object); + + switch (key) { + case SP_ATTR_XLINK_HREF: + g_free (image->href); + image->href = (value) ? g_strdup (value) : NULL; + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_IMAGE_HREF_MODIFIED_FLAG); + break; + case SP_ATTR_X: + if (!image->x.readAbsolute(value)) { + /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */ + image->x.unset(); + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_Y: + if (!image->y.readAbsolute(value)) { + /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */ + image->y.unset(); + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_WIDTH: + if (!image->width.readAbsolute(value)) { + /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */ + image->width.unset(); + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_HEIGHT: + if (!image->height.readAbsolute(value)) { + /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */ + image->height.unset(); + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_PRESERVEASPECTRATIO: + /* Do setup before, so we can use break to escape */ + image->aspect_align = SP_ASPECT_NONE; + image->aspect_clip = SP_ASPECT_MEET; + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); + if (value) { + int len; + gchar c[256]; + const gchar *p, *e; + unsigned int align, clip; + p = value; + while (*p && *p == 32) p += 1; + if (!*p) break; + e = p; + while (*e && *e != 32) e += 1; + len = e - p; + if (len > 8) break; + memcpy (c, value, len); + c[len] = 0; + /* Now the actual part */ + if (!strcmp (c, "none")) { + align = SP_ASPECT_NONE; + } else if (!strcmp (c, "xMinYMin")) { + align = SP_ASPECT_XMIN_YMIN; + } else if (!strcmp (c, "xMidYMin")) { + align = SP_ASPECT_XMID_YMIN; + } else if (!strcmp (c, "xMaxYMin")) { + align = SP_ASPECT_XMAX_YMIN; + } else if (!strcmp (c, "xMinYMid")) { + align = SP_ASPECT_XMIN_YMID; + } else if (!strcmp (c, "xMidYMid")) { + align = SP_ASPECT_XMID_YMID; + } else if (!strcmp (c, "xMaxYMid")) { + align = SP_ASPECT_XMAX_YMID; + } else if (!strcmp (c, "xMinYMax")) { + align = SP_ASPECT_XMIN_YMAX; + } else if (!strcmp (c, "xMidYMax")) { + align = SP_ASPECT_XMID_YMAX; + } else if (!strcmp (c, "xMaxYMax")) { + align = SP_ASPECT_XMAX_YMAX; + } else { + break; + } + clip = SP_ASPECT_MEET; + while (*e && *e == 32) e += 1; + if (e) { + if (!strcmp (e, "meet")) { + clip = SP_ASPECT_MEET; + } else if (!strcmp (e, "slice")) { + clip = SP_ASPECT_SLICE; + } else { + break; + } + } + image->aspect_align = align; + image->aspect_clip = clip; + } + break; + default: + if (((SPObjectClass *) (parent_class))->set) + ((SPObjectClass *) (parent_class))->set (object, key, value); + break; + } +} + +static void +sp_image_update (SPObject *object, SPCtx *ctx, unsigned int flags) +{ + SPImage *image; + + image = (SPImage *) object; + + if (((SPObjectClass *) (parent_class))->update) + ((SPObjectClass *) (parent_class))->update (object, ctx, flags); + + if (flags & SP_IMAGE_HREF_MODIFIED_FLAG) { + if (image->pixbuf) { + gdk_pixbuf_unref (image->pixbuf); + image->pixbuf = NULL; + } + if (image->href) { + GdkPixbuf *pixbuf; + pixbuf = sp_image_repr_read_image (object->repr); + if (pixbuf) { + pixbuf = sp_image_pixbuf_force_rgba (pixbuf); + image->pixbuf = pixbuf; + } + } + } + // preserveAspectRatio calculate bounds / clipping rectangle -- EAF + if (image->pixbuf && (image->aspect_align != SP_ASPECT_NONE)) { + int imagewidth, imageheight; + double x,y; + + imagewidth = gdk_pixbuf_get_width (image->pixbuf); + imageheight = gdk_pixbuf_get_height (image->pixbuf); + + switch (image->aspect_align) { + case SP_ASPECT_XMIN_YMIN: + x = 0.0; + y = 0.0; + break; + case SP_ASPECT_XMID_YMIN: + x = 0.5; + y = 0.0; + break; + case SP_ASPECT_XMAX_YMIN: + x = 1.0; + y = 0.0; + break; + case SP_ASPECT_XMIN_YMID: + x = 0.0; + y = 0.5; + break; + case SP_ASPECT_XMID_YMID: + x = 0.5; + y = 0.5; + break; + case SP_ASPECT_XMAX_YMID: + x = 1.0; + y = 0.5; + break; + case SP_ASPECT_XMIN_YMAX: + x = 0.0; + y = 1.0; + break; + case SP_ASPECT_XMID_YMAX: + x = 0.5; + y = 1.0; + break; + case SP_ASPECT_XMAX_YMAX: + x = 1.0; + y = 1.0; + break; + default: + x = 0.0; + y = 0.0; + break; + } + + if (image->aspect_clip == SP_ASPECT_SLICE) { + image->viewx = image->x.computed; + image->viewy = image->y.computed; + image->viewwidth = image->width.computed; + image->viewheight = image->height.computed; + if ((imagewidth*image->height.computed)>(image->width.computed*imageheight)) { + // Pixels aspect is wider than bounding box + image->trimheight = imageheight; + image->trimwidth = static_cast(static_cast(imageheight) * image->width.computed / image->height.computed); + image->trimy = 0; + image->trimx = static_cast(static_cast(imagewidth - image->trimwidth) * x); + } else { + // Pixels aspect is taller than bounding box + image->trimwidth = imagewidth; + image->trimheight = static_cast(static_cast(imagewidth) * image->height.computed / image->width.computed); + image->trimx = 0; + image->trimy = static_cast(static_cast(imageheight - image->trimheight) * y); + } + } else { + // Otherwise, assume SP_ASPECT_MEET + image->trimx = 0; + image->trimy = 0; + image->trimwidth = imagewidth; + image->trimheight = imageheight; + if ((imagewidth*image->height.computed)>(image->width.computed*imageheight)) { + // Pixels aspect is wider than bounding boz + image->viewwidth = image->width.computed; + image->viewheight = image->viewwidth * imageheight / imagewidth; + image->viewx=image->x.computed; + image->viewy=(image->height.computed - image->viewheight) * y + image->y.computed; + } else { + // Pixels aspect is taller than bounding box + image->viewheight = image->height.computed; + image->viewwidth = image->viewheight * imagewidth / imageheight; + image->viewy=image->y.computed; + image->viewx=(image->width.computed - image->viewwidth) * x + image->x.computed; + } + } + } + + sp_image_update_canvas_image ((SPImage *) object); +} + +static Inkscape::XML::Node * +sp_image_write (SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + SPImage *image; + + image = SP_IMAGE (object); + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = sp_repr_new ("svg:image"); + } + + repr->setAttribute("xlink:href", image->href); + /* fixme: Reset attribute if needed (Lauris) */ + if (image->x._set) sp_repr_set_svg_double(repr, "x", image->x.computed); + if (image->y._set) sp_repr_set_svg_double(repr, "y", image->y.computed); + if (image->width._set) sp_repr_set_svg_double(repr, "width", image->width.computed); + if (image->height._set) sp_repr_set_svg_double(repr, "height", image->height.computed); + repr->setAttribute("preserveAspectRatio", object->repr->attribute("preserveAspectRatio")); + + if (((SPObjectClass *) (parent_class))->write) + ((SPObjectClass *) (parent_class))->write (object, repr, flags); + + return repr; +} + +static void +sp_image_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const flags) +{ + SPImage const &image = *SP_IMAGE(item); + + if ((image.width.computed > 0.0) && (image.height.computed > 0.0)) { + double const x0 = image.x.computed; + double const y0 = image.y.computed; + double const x1 = x0 + image.width.computed; + double const y1 = y0 + image.height.computed; + + nr_rect_union_pt(bbox, NR::Point(x0, y0) * transform); + nr_rect_union_pt(bbox, NR::Point(x1, y0) * transform); + nr_rect_union_pt(bbox, NR::Point(x1, y1) * transform); + nr_rect_union_pt(bbox, NR::Point(x0, y1) * transform); + } +} + +static void +sp_image_print (SPItem *item, SPPrintContext *ctx) +{ + SPImage *image; + NRMatrix tp, ti, s, t; + guchar *px; + int w, h, rs, pixskip; + + image = SP_IMAGE (item); + + if (!image->pixbuf) return; + if ((image->width.computed <= 0.0) || (image->height.computed <= 0.0)) return; + + px = gdk_pixbuf_get_pixels (image->pixbuf); + w = gdk_pixbuf_get_width (image->pixbuf); + h = gdk_pixbuf_get_height (image->pixbuf); + rs = gdk_pixbuf_get_rowstride (image->pixbuf); + pixskip = gdk_pixbuf_get_n_channels (image->pixbuf) * gdk_pixbuf_get_bits_per_sample (image->pixbuf) / 8; + + if (image->aspect_align == SP_ASPECT_NONE) { + /* fixme: (Lauris) */ + nr_matrix_set_translate (&tp, image->x.computed, image->y.computed); + nr_matrix_set_scale (&s, image->width.computed, -image->height.computed); + nr_matrix_set_translate (&ti, 0.0, -1.0); + } else { // preserveAspectRatio + nr_matrix_set_translate (&tp, image->viewx, image->viewy); + nr_matrix_set_scale (&s, image->viewwidth, -image->viewheight); + nr_matrix_set_translate (&ti, 0.0, -1.0); + } + + nr_matrix_multiply (&t, &s, &tp); + nr_matrix_multiply (&t, &ti, &t); + + if (image->aspect_align == SP_ASPECT_NONE) + sp_print_image_R8G8B8A8_N (ctx, px, w, h, rs, &t, SP_OBJECT_STYLE (item)); + else // preserveAspectRatio + sp_print_image_R8G8B8A8_N (ctx, px + image->trimx*pixskip + image->trimy*rs, image->trimwidth, image->trimheight, rs, &t, SP_OBJECT_STYLE (item)); +} + +static gchar * +sp_image_description(SPItem *item) +{ + SPImage *image = SP_IMAGE(item); + char *href_desc; + if (image->href) { + href_desc = (strncmp(image->href, "data:", 5) == 0) + ? g_strdup(_("embedded")) + : xml_quote_strdup(image->href); + } else { + g_warning("Attempting to call strncmp() with a null pointer."); + href_desc = g_strdup(_("(null_pointer)")); // we call g_free() on href_desc + } + + char *ret = ( image->pixbuf == NULL + ? g_strdup_printf(_("Image with bad reference: %s"), href_desc) + : g_strdup_printf(_("Image %d × %d: %s"), + gdk_pixbuf_get_width(image->pixbuf), + gdk_pixbuf_get_height(image->pixbuf), + href_desc) ); + g_free(href_desc); + return ret; +} + +static NRArenaItem * +sp_image_show (SPItem *item, NRArena *arena, unsigned int key, unsigned int flags) +{ + int pixskip, rs; + SPImage * image; + NRArenaItem *ai; + + image = (SPImage *) item; + + ai = NRArenaImage::create(arena); + + if (image->pixbuf) { + pixskip = gdk_pixbuf_get_n_channels (image->pixbuf) * gdk_pixbuf_get_bits_per_sample (image->pixbuf) / 8; + rs = gdk_pixbuf_get_rowstride (image->pixbuf); + if (image->aspect_align == SP_ASPECT_NONE) + nr_arena_image_set_pixels (NR_ARENA_IMAGE (ai), + gdk_pixbuf_get_pixels (image->pixbuf), + gdk_pixbuf_get_width (image->pixbuf), + gdk_pixbuf_get_height (image->pixbuf), + rs); + else // preserveAspectRatio + nr_arena_image_set_pixels (NR_ARENA_IMAGE (ai), + gdk_pixbuf_get_pixels (image->pixbuf) + image->trimx*pixskip + image->trimy*rs, + image->trimwidth, + image->trimheight, + rs); + } else { + nr_arena_image_set_pixels (NR_ARENA_IMAGE (ai), NULL, 0, 0, 0); + } + if (image->aspect_align == SP_ASPECT_NONE) + nr_arena_image_set_geometry (NR_ARENA_IMAGE (ai), image->x.computed, image->y.computed, image->width.computed, image->height.computed); + else // preserveAspectRatio + nr_arena_image_set_geometry (NR_ARENA_IMAGE (ai), image->viewx, image->viewy, image->viewwidth, image->viewheight); + + return ai; +} + +/* + * utility function to try loading image from href + * + * docbase/relative_src + * absolute_src + * + */ + +GdkPixbuf * +sp_image_repr_read_image (Inkscape::XML::Node * repr) +{ + const gchar * filename, * docbase; + gchar * fullname; + GdkPixbuf * pixbuf; + + filename = repr->attribute("xlink:href"); + if (filename == NULL) filename = repr->attribute("href"); /* FIXME */ + if (filename != NULL) { + if (strncmp (filename,"file:",5) == 0) { + fullname = g_filename_from_uri(filename, NULL, NULL); + if (fullname) { + // TODO check this. Was doing a UTF-8 to filename conversion here. + pixbuf = Inkscape::IO::pixbuf_new_from_file (fullname, NULL); + if (pixbuf != NULL) return pixbuf; + } + } else if (strncmp (filename,"data:",5) == 0) { + /* data URI - embedded image */ + filename += 5; + pixbuf = sp_image_repr_read_dataURI (filename); + if (pixbuf != NULL) return pixbuf; + } else if (!g_path_is_absolute (filename)) { + /* try to load from relative pos */ + docbase = sp_repr_document_root (sp_repr_document (repr))->attribute("sodipodi:docbase"); + if (!docbase) docbase = "."; + fullname = g_build_filename(docbase, filename, NULL); + pixbuf = Inkscape::IO::pixbuf_new_from_file( fullname, NULL ); + g_free (fullname); + if (pixbuf != NULL) return pixbuf; + } else { + /* try absolute filename */ + pixbuf = Inkscape::IO::pixbuf_new_from_file( filename, NULL ); + if (pixbuf != NULL) return pixbuf; + } + } + /* at last try to load from sp absolute path name */ + filename = repr->attribute("sodipodi:absref"); + if (filename != NULL) { + pixbuf = Inkscape::IO::pixbuf_new_from_file( filename, NULL ); + if (pixbuf != NULL) return pixbuf; + } + /* Nope: We do not find any valid pixmap file :-( */ + pixbuf = gdk_pixbuf_new_from_xpm_data ((const gchar **) brokenimage_xpm); + + /* It should be included xpm, so if it still does not does load, */ + /* our libraries are broken */ + g_assert (pixbuf != NULL); + + return pixbuf; +} + +static GdkPixbuf * +sp_image_pixbuf_force_rgba (GdkPixbuf * pixbuf) +{ + GdkPixbuf * newbuf; + + if (gdk_pixbuf_get_has_alpha (pixbuf)) return pixbuf; + + newbuf = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0); + gdk_pixbuf_unref (pixbuf); + + return newbuf; +} + +/* We assert that realpixbuf is either NULL or identical size to pixbuf */ + +static void +sp_image_update_canvas_image (SPImage *image) +{ + int rs, pixskip; + SPItem *item; + SPItemView *v; + + item = SP_ITEM (image); + + if (image->pixbuf) { + /* fixme: We are slightly violating spec here (Lauris) */ + if (!image->width._set) { + image->width.computed = gdk_pixbuf_get_width (image->pixbuf); + } + if (!image->height._set) { + image->height.computed = gdk_pixbuf_get_height (image->pixbuf); + } + } + + for (v = item->display; v != NULL; v = v->next) { + pixskip = gdk_pixbuf_get_n_channels (image->pixbuf) * gdk_pixbuf_get_bits_per_sample (image->pixbuf) / 8; + rs = gdk_pixbuf_get_rowstride (image->pixbuf); + if (image->aspect_align == SP_ASPECT_NONE) { + nr_arena_image_set_pixels (NR_ARENA_IMAGE (v->arenaitem), + gdk_pixbuf_get_pixels (image->pixbuf), + gdk_pixbuf_get_width (image->pixbuf), + gdk_pixbuf_get_height (image->pixbuf), + rs); + nr_arena_image_set_geometry (NR_ARENA_IMAGE (v->arenaitem), + image->x.computed, image->y.computed, + image->width.computed, image->height.computed); + } else { // preserveAspectRatio + nr_arena_image_set_pixels (NR_ARENA_IMAGE (v->arenaitem), + gdk_pixbuf_get_pixels (image->pixbuf) + image->trimx*pixskip + image->trimy*rs, + image->trimwidth, + image->trimheight, + rs); + nr_arena_image_set_geometry (NR_ARENA_IMAGE (v->arenaitem), + image->viewx, image->viewy, + image->viewwidth, image->viewheight); + } + } +} + +static void sp_image_snappoints(SPItem const *item, SnapPointsIter p) +{ + if (((SPItemClass *) parent_class)->snappoints) { + ((SPItemClass *) parent_class)->snappoints (item, p); + } +} + +/* + * Initially we'll do: + * Transform x, y, set x, y, clear translation + */ + +static NR::Matrix +sp_image_set_transform(SPItem *item, NR::Matrix const &xform) +{ + SPImage *image = SP_IMAGE(item); + + /* Calculate position in parent coords. */ + NR::Point pos( NR::Point(image->x.computed, image->y.computed) * xform ); + + /* This function takes care of translation and scaling, we return whatever parts we can't + handle. */ + NR::Matrix ret(NR::transform(xform)); + NR::Point const scale(hypot(ret[0], ret[1]), + hypot(ret[2], ret[3])); + if ( scale[NR::X] > 1e-9 ) { + ret[0] /= scale[NR::X]; + ret[1] /= scale[NR::X]; + } else { + ret[0] = 1.0; + ret[1] = 0.0; + } + if ( scale[NR::Y] > 1e-9 ) { + ret[2] /= scale[NR::Y]; + ret[3] /= scale[NR::Y]; + } else { + ret[2] = 0.0; + ret[3] = 1.0; + } + + image->width = image->width.computed * scale[NR::X]; + image->height = image->height.computed * scale[NR::Y]; + + /* Find position in item coords */ + pos = pos * ret.inverse(); + image->x = pos[NR::X]; + image->y = pos[NR::Y]; + + item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + + return ret; +} + +static GdkPixbuf * +sp_image_repr_read_dataURI (const gchar * uri_data) +{ GdkPixbuf * pixbuf = NULL; + + gint data_is_image = 0; + gint data_is_base64 = 0; + + const gchar * data = uri_data; + + while (*data) { + if (strncmp (data,"base64",6) == 0) { + /* base64-encoding */ + data_is_base64 = 1; + data_is_image = 1; // Illustrator produces embedded images without MIME type, so we assume it's image no matter what + data += 6; + } + else if (strncmp (data,"image/png",9) == 0) { + /* PNG image */ + data_is_image = 1; + data += 9; + } + else if (strncmp (data,"image/jpg",9) == 0) { + /* JPEG image */ + data_is_image = 1; + data += 9; + } + else if (strncmp (data,"image/jpeg",10) == 0) { + /* JPEG image */ + data_is_image = 1; + data += 10; + } + else { /* unrecognized option; skip it */ + while (*data) { + if (((*data) == ';') || ((*data) == ',')) break; + data++; + } + } + if ((*data) == ';') { + data++; + continue; + } + if ((*data) == ',') { + data++; + break; + } + } + + if ((*data) && data_is_image && data_is_base64) { + pixbuf = sp_image_repr_read_b64 (data); + } + + return pixbuf; +} + +static GdkPixbuf * +sp_image_repr_read_b64 (const gchar * uri_data) +{ GdkPixbuf * pixbuf = NULL; + GdkPixbufLoader * loader = NULL; + + gint j; + gint k; + gint l; + gint b; + gint len; + gint eos = 0; + gint failed = 0; + + guint32 bits; + + static const gchar B64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + const gchar* btr = uri_data; + + gchar ud[4]; + + guchar bd[57]; + + loader = gdk_pixbuf_loader_new (); + + if (loader == NULL) return NULL; + + while (eos == 0) { + l = 0; + for (j = 0; j < 19; j++) { + len = 0; + for (k = 0; k < 4; k++) { + while (isspace ((int) (*btr))) { + if ((*btr) == '\0') break; + btr++; + } + if (eos) { + ud[k] = 0; + continue; + } + if (((*btr) == '\0') || ((*btr) == '=')) { + eos = 1; + ud[k] = 0; + continue; + } + ud[k] = 64; + for (b = 0; b < 64; b++) { /* There must a faster way to do this... ?? */ + if (B64[b] == (*btr)) { + ud[k] = (gchar) b; + break; + } + } + if (ud[k] == 64) { /* data corruption ?? */ + eos = 1; + ud[k] = 0; + continue; + } + btr++; + len++; + } + bits = (guint32) ud[0]; + bits = (bits << 6) | (guint32) ud[1]; + bits = (bits << 6) | (guint32) ud[2]; + bits = (bits << 6) | (guint32) ud[3]; + bd[l++] = (guchar) ((bits & 0xff0000) >> 16); + if (len > 2) { + bd[l++] = (guchar) ((bits & 0xff00) >> 8); + } + if (len > 3) { + bd[l++] = (guchar) (bits & 0xff); + } + } + + if (!gdk_pixbuf_loader_write (loader, (const guchar *) bd, (size_t) l, NULL)) { + failed = 1; + break; + } + } + + gdk_pixbuf_loader_close (loader, NULL); + + if (!failed) pixbuf = gdk_pixbuf_loader_get_pixbuf (loader); + + return pixbuf; +} + +/* + 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/sp-image.h b/src/sp-image.h new file mode 100644 index 000000000..500d9699e --- /dev/null +++ b/src/sp-image.h @@ -0,0 +1,60 @@ +#ifndef __SP_IMAGE_H__ +#define __SP_IMAGE_H__ + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * Edward Flick (EAF) + * + * Copyright (C) 1999-2005 Authors + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define SP_TYPE_IMAGE (sp_image_get_type ()) +#define SP_IMAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_IMAGE, SPImage)) +#define SP_IMAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_IMAGE, SPImageClass)) +#define SP_IS_IMAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_IMAGE)) +#define SP_IS_IMAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_IMAGE)) + +class SPImage; +class SPImageClass; + +/* SPImage */ + +#include +#include "svg/svg-length.h" +#include "sp-item.h" + +#define SP_IMAGE_HREF_MODIFIED_FLAG SP_OBJECT_USER_MODIFIED_FLAG_A + +struct SPImage : public SPItem { + SVGLength x; + SVGLength y; + SVGLength width; + SVGLength height; + + // Added by EAF + /* preserveAspectRatio */ + unsigned int aspect_align : 4; + unsigned int aspect_clip : 1; + int trimx, trimy, trimwidth, trimheight; + double viewx, viewy, viewwidth, viewheight; + + gchar *href; + + GdkPixbuf *pixbuf; +}; + +struct SPImageClass { + SPItemClass parent_class; +}; + +GType sp_image_get_type (void); + + + +#endif diff --git a/src/sp-item-group.cpp b/src/sp-item-group.cpp new file mode 100644 index 000000000..c093c569b --- /dev/null +++ b/src/sp-item-group.cpp @@ -0,0 +1,699 @@ +#define __SP_GROUP_C__ + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * bulia byak + * + * Copyright (C) 1999-2005 authors + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if defined(WIN32) || defined(__APPLE__) +# include +#endif + +#include "display/nr-arena-group.h" +#include "libnr/nr-matrix-ops.h" +#include "libnr/nr-matrix-fns.h" +#include "xml/repr.h" +#include "svg/svg.h" +#include "document.h" +#include "style.h" +#include "attributes.h" + +#include "sp-root.h" +#include "sp-use.h" +#include "prefs-utils.h" + +static void sp_group_class_init (SPGroupClass *klass); +static void sp_group_init (SPGroup *group); +static void sp_group_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); +static void sp_group_release(SPObject *object); +static void sp_group_dispose (GObject *object); + +static void sp_group_child_added (SPObject * object, Inkscape::XML::Node * child, Inkscape::XML::Node * ref); +static void sp_group_remove_child (SPObject * object, Inkscape::XML::Node * child); +static void sp_group_order_changed (SPObject * object, Inkscape::XML::Node * child, Inkscape::XML::Node * old_ref, Inkscape::XML::Node * new_ref); +static void sp_group_update (SPObject *object, SPCtx *ctx, guint flags); +static void sp_group_modified (SPObject *object, guint flags); +static Inkscape::XML::Node *sp_group_write (SPObject *object, Inkscape::XML::Node *repr, guint flags); +static void sp_group_set(SPObject *object, unsigned key, char const *value); + +static void sp_group_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const flags); +static void sp_group_print (SPItem * item, SPPrintContext *ctx); +static gchar * sp_group_description (SPItem * item); +static NRArenaItem *sp_group_show (SPItem *item, NRArena *arena, unsigned int key, unsigned int flags); +static void sp_group_hide (SPItem * item, unsigned int key); +static void sp_group_snappoints (SPItem const *item, SnapPointsIter p); + +static SPItemClass * parent_class; + +GType +sp_group_get_type (void) +{ + static GType group_type = 0; + if (!group_type) { + GTypeInfo group_info = { + sizeof (SPGroupClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_group_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (SPGroup), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_group_init, + NULL, /* value_table */ + }; + group_type = g_type_register_static (SP_TYPE_ITEM, "SPGroup", &group_info, (GTypeFlags)0); + } + return group_type; +} + +static void +sp_group_class_init (SPGroupClass *klass) +{ + GObjectClass * object_class; + SPObjectClass * sp_object_class; + SPItemClass * item_class; + + object_class = (GObjectClass *) klass; + sp_object_class = (SPObjectClass *) klass; + item_class = (SPItemClass *) klass; + + parent_class = (SPItemClass *)g_type_class_ref (SP_TYPE_ITEM); + + object_class->dispose = sp_group_dispose; + + sp_object_class->child_added = sp_group_child_added; + sp_object_class->remove_child = sp_group_remove_child; + sp_object_class->order_changed = sp_group_order_changed; + sp_object_class->update = sp_group_update; + sp_object_class->modified = sp_group_modified; + sp_object_class->set = sp_group_set; + sp_object_class->write = sp_group_write; + sp_object_class->release = sp_group_release; + sp_object_class->build = sp_group_build; + + item_class->bbox = sp_group_bbox; + item_class->print = sp_group_print; + item_class->description = sp_group_description; + item_class->show = sp_group_show; + item_class->hide = sp_group_hide; + item_class->snappoints = sp_group_snappoints; +} + +static void +sp_group_init (SPGroup *group) +{ + group->_layer_mode = SPGroup::GROUP; + new (&group->_display_modes) std::map(); +} + +static void sp_group_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + sp_object_read_attr(object, "inkscape:groupmode"); + + if (((SPObjectClass *)parent_class)->build) { + ((SPObjectClass *)parent_class)->build(object, document, repr); + } +} + +static void sp_group_release(SPObject *object) { + if ( SP_GROUP(object)->_layer_mode == SPGroup::LAYER ) { + sp_document_remove_resource(SP_OBJECT_DOCUMENT(object), "layer", object); + } + if (((SPObjectClass *)parent_class)->release) { + ((SPObjectClass *)parent_class)->release(object); + } +} + +static void +sp_group_dispose(GObject *object) +{ + SP_GROUP(object)->_display_modes.~map(); +} + +static void +sp_group_child_added (SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref) +{ + SPItem *item; + + item = SP_ITEM (object); + + if (((SPObjectClass *) (parent_class))->child_added) + (* ((SPObjectClass *) (parent_class))->child_added) (object, child, ref); + + SPObject *last_child = object->lastChild(); + if (last_child && SP_OBJECT_REPR(last_child) == child) { + // optimization for the common special case where the child is being added at the end + SPObject *ochild = last_child; + if ( SP_IS_ITEM(ochild) ) { + /* TODO: this should be moved into SPItem somehow */ + SPItemView *v; + NRArenaItem *ac; + + for (v = item->display; v != NULL; v = v->next) { + ac = sp_item_invoke_show (SP_ITEM (ochild), NR_ARENA_ITEM_ARENA (v->arenaitem), v->key, v->flags); + + if (ac) { + nr_arena_item_append_child (v->arenaitem, ac); + nr_arena_item_unref (ac); + } + } + } + } else { // general case + SPObject *ochild = sp_object_get_child_by_repr(object, child); + if ( ochild && SP_IS_ITEM(ochild) ) { + /* TODO: this should be moved into SPItem somehow */ + SPItemView *v; + NRArenaItem *ac; + + unsigned position = sp_item_pos_in_parent(SP_ITEM(ochild)); + + for (v = item->display; v != NULL; v = v->next) { + ac = sp_item_invoke_show (SP_ITEM (ochild), NR_ARENA_ITEM_ARENA (v->arenaitem), v->key, v->flags); + + if (ac) { + nr_arena_item_add_child (v->arenaitem, ac, NULL); + nr_arena_item_set_order (ac, position); + nr_arena_item_unref (ac); + } + } + } + } + + object->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +/* fixme: hide (Lauris) */ + +static void +sp_group_remove_child (SPObject * object, Inkscape::XML::Node * child) +{ + if (((SPObjectClass *) (parent_class))->remove_child) + (* ((SPObjectClass *) (parent_class))->remove_child) (object, child); + + object->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +static void +sp_group_order_changed (SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *old_ref, Inkscape::XML::Node *new_ref) +{ + if (((SPObjectClass *) (parent_class))->order_changed) + (* ((SPObjectClass *) (parent_class))->order_changed) (object, child, old_ref, new_ref); + + SPObject *ochild = sp_object_get_child_by_repr(object, child); + if ( ochild && SP_IS_ITEM(ochild) ) { + /* TODO: this should be moved into SPItem somehow */ + SPItemView *v; + unsigned position = sp_item_pos_in_parent(SP_ITEM(ochild)); + for ( v = SP_ITEM (ochild)->display ; v != NULL ; v = v->next ) { + nr_arena_item_set_order (v->arenaitem, position); + } + } + + object->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +static void +sp_group_update (SPObject *object, SPCtx *ctx, unsigned int flags) +{ + SPGroup *group; + SPObject *child; + SPItemCtx *ictx, cctx; + GSList *l; + + group = SP_GROUP (object); + ictx = (SPItemCtx *) ctx; + cctx = *ictx; + + if (((SPObjectClass *) (parent_class))->update) + ((SPObjectClass *) (parent_class))->update (object, ctx, flags); + + if (flags & SP_OBJECT_MODIFIED_FLAG) flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + flags &= SP_OBJECT_MODIFIED_CASCADE; + + l = NULL; + for (child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) { + g_object_ref (G_OBJECT (child)); + l = g_slist_prepend (l, child); + } + l = g_slist_reverse (l); + while (l) { + child = SP_OBJECT (l->data); + l = g_slist_remove (l, child); + if (flags || (child->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + if (SP_IS_ITEM (child)) { + SPItem const &chi = *SP_ITEM(child); + cctx.i2doc = chi.transform * ictx->i2doc; + cctx.i2vp = chi.transform * ictx->i2vp; + child->updateDisplay((SPCtx *)&cctx, flags); + } else { + child->updateDisplay(ctx, flags); + } + } + g_object_unref (G_OBJECT (child)); + } +} + +static void +sp_group_modified (SPObject *object, guint flags) +{ + SPGroup *group; + SPObject *child; + GSList *l; + + group = SP_GROUP (object); + + if (flags & SP_OBJECT_MODIFIED_FLAG) flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + flags &= SP_OBJECT_MODIFIED_CASCADE; + + l = NULL; + for (child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) { + g_object_ref (G_OBJECT (child)); + l = g_slist_prepend (l, child); + } + l = g_slist_reverse (l); + while (l) { + child = SP_OBJECT (l->data); + l = g_slist_remove (l, child); + if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->emitModified(flags); + } + g_object_unref (G_OBJECT (child)); + } +} + +static Inkscape::XML::Node * +sp_group_write (SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + SPGroup *group; + SPObject *child; + Inkscape::XML::Node *crepr; + + group = SP_GROUP (object); + + if (flags & SP_OBJECT_WRITE_BUILD) { + GSList *l; + if (!repr) repr = sp_repr_new ("svg:g"); + l = NULL; + for (child = sp_object_first_child(object); child != NULL; child = SP_OBJECT_NEXT(child) ) { + crepr = child->updateRepr(NULL, flags); + if (crepr) l = g_slist_prepend (l, crepr); + } + while (l) { + repr->addChild((Inkscape::XML::Node *) l->data, NULL); + Inkscape::GC::release((Inkscape::XML::Node *) l->data); + l = g_slist_remove (l, l->data); + } + } else { + for (child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) { + child->updateRepr(flags); + } + } + + if ( flags & SP_OBJECT_WRITE_EXT ) { + const char *value; + if ( group->_layer_mode == SPGroup::LAYER ) { + value = "layer"; + } else if ( flags & SP_OBJECT_WRITE_ALL ) { + value = "group"; + } else { + value = NULL; + } + repr->setAttribute("inkscape:groupmode", value); + } + + if (((SPObjectClass *) (parent_class))->write) + ((SPObjectClass *) (parent_class))->write (object, repr, flags); + + return repr; +} + +static void +sp_group_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const flags) +{ + for (SPObject *o = sp_object_first_child(SP_OBJECT(item)); o != NULL; o = SP_OBJECT_NEXT(o)) { + if (SP_IS_ITEM(o)) { + SPItem *child = SP_ITEM(o); + NR::Matrix const ct(child->transform * transform); + sp_item_invoke_bbox_full(child, bbox, ct, flags, FALSE); + } + } +} + +static void +sp_group_print (SPItem * item, SPPrintContext *ctx) +{ + SPGroup * group; + SPItem * child; + SPObject * o; + + group = SP_GROUP (item); + + for (o = sp_object_first_child(SP_OBJECT(item)) ; o != NULL ; o = SP_OBJECT_NEXT(o) ) { + if (SP_IS_ITEM (o)) { + child = SP_ITEM (o); + sp_item_invoke_print (SP_ITEM (o), ctx); + } + } +} + +static gchar * sp_group_description (SPItem * item) +{ + SPGroup * group; + SPObject * o; + gint len; + + group = SP_GROUP (item); + + len = 0; + for ( o = sp_object_first_child(SP_OBJECT(item)) ; o != NULL ; o = SP_OBJECT_NEXT(o) ) { + if (SP_IS_ITEM(o)) { + len += 1; + } + } + + return g_strdup_printf( + ngettext("Group of %d object", + "Group of %d objects", + len), len); +} + +static void sp_group_set(SPObject *object, unsigned key, char const *value) { + SPGroup *group=SP_GROUP(object); + + switch (key) { + case SP_ATTR_INKSCAPE_GROUPMODE: { + if ( value && !strcmp(value, "layer") ) { + group->setLayerMode(SPGroup::LAYER); + } else { + group->setLayerMode(SPGroup::GROUP); + } + } break; + default: { + if (((SPObjectClass *) (parent_class))->set) { + (* ((SPObjectClass *) (parent_class))->set)(object, key, value); + } + } + } +} + +static NRArenaItem * +sp_group_show (SPItem *item, NRArena *arena, unsigned int key, unsigned int flags) +{ + SPGroup *group; + NRArenaItem *ai, *ac, *ar; + SPItem * child; + SPObject * o; + + group = (SPGroup *) item; + + ai = NRArenaGroup::create(arena); + nr_arena_group_set_transparent(NR_ARENA_GROUP (ai), + group->effectiveLayerMode(key) == + SPGroup::LAYER); + + ar = NULL; + + for (o = sp_object_first_child(SP_OBJECT(item)) ; o != NULL; o = SP_OBJECT_NEXT(o) ) { + if (SP_IS_ITEM (o)) { + child = SP_ITEM (o); + ac = sp_item_invoke_show (child, arena, key, flags); + if (ac) { + nr_arena_item_add_child (ai, ac, ar); + ar = ac; + nr_arena_item_unref (ac); + } + } + } + + return ai; +} + +static void +sp_group_hide (SPItem *item, unsigned int key) +{ + SPGroup * group; + SPItem * child; + SPObject * o; + + group = (SPGroup *) item; + + for (o = sp_object_first_child(SP_OBJECT(item)) ; o != NULL; o = SP_OBJECT_NEXT(o) ) { + if (SP_IS_ITEM (o)) { + child = SP_ITEM (o); + sp_item_invoke_hide (child, key); + } + } + + if (((SPItemClass *) parent_class)->hide) + ((SPItemClass *) parent_class)->hide (item, key); +} + +static void sp_group_snappoints (SPItem const *item, SnapPointsIter p) +{ + for (SPObject const *o = sp_object_first_child(SP_OBJECT(item)); + o != NULL; + o = SP_OBJECT_NEXT(o)) + { + if (SP_IS_ITEM(o)) { + sp_item_snappoints(SP_ITEM(o), p); + } + } +} + + +void +sp_item_group_ungroup (SPGroup *group, GSList **children, bool do_done) +{ + g_return_if_fail (group != NULL); + g_return_if_fail (SP_IS_GROUP (group)); + + SPDocument *doc = SP_OBJECT_DOCUMENT (group); + SPObject *root = SP_DOCUMENT_ROOT (doc); + SPObject *defs = SP_OBJECT (SP_ROOT (root)->defs); + + SPItem *gitem = SP_ITEM (group); + Inkscape::XML::Node *grepr = SP_OBJECT_REPR (gitem); + + g_return_if_fail (!strcmp (grepr->name(), "svg:g") || !strcmp (grepr->name(), "svg:a") || !strcmp (grepr->name(), "svg:switch")); + + // this converts the gradient/pattern fill/stroke on the group, if any, to userSpaceOnUse + sp_item_adjust_paint_recursive (gitem, NR::identity(), NR::identity(), false); + + SPItem *pitem = SP_ITEM (SP_OBJECT_PARENT (gitem)); + Inkscape::XML::Node *prepr = SP_OBJECT_REPR (pitem); + + /* Step 1 - generate lists of children objects */ + GSList *items = NULL; + GSList *objects = NULL; + for (SPObject *child = sp_object_first_child(SP_OBJECT(group)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) { + + if (SP_IS_ITEM (child)) { + + SPItem *citem = SP_ITEM (child); + + /* Merging of style */ + // this converts the gradient/pattern fill/stroke, if any, to userSpaceOnUse; we need to do + // it here _before_ the new transform is set, so as to use the pre-transform bbox + sp_item_adjust_paint_recursive (citem, NR::identity(), NR::identity(), false); + + sp_style_merge_from_dying_parent(SP_OBJECT_STYLE(child), SP_OBJECT_STYLE(gitem)); + /* + * fixme: We currently make no allowance for the case where child is cloned + * and the group has any style settings. + * + * (This should never occur with documents created solely with the current + * version of inkscape without using the XML editor: we usually apply group + * style changes to children rather than to the group itself.) + * + * If the group has no style settings, then + * sp_style_merge_from_dying_parent should be a no-op. Otherwise (i.e. if + * we change the child's style to compensate for its parent going away) + * then those changes will typically be reflected in any clones of child, + * whereas we'd prefer for Ungroup not to affect the visual appearance. + * + * The only way of preserving styling appearance in general is for child to + * be put into a new group -- a somewhat surprising response to an Ungroup + * command. We could add a new groupmode:transparent that would mostly + * hide the existence of such groups from the user (i.e. editing behaves as + * if the transparent group's children weren't in a group), though that's + * extra complication & maintenance burden and this case is rare. + */ + + child->updateRepr(); + + Inkscape::XML::Node *nrepr = SP_OBJECT_REPR (child)->duplicate(); + + // Merging transform + NR::Matrix ctrans; + NR::Matrix const g(gitem->transform); + if (SP_IS_USE(citem) && sp_use_get_original (SP_USE(citem)) && + SP_OBJECT_PARENT (sp_use_get_original (SP_USE(citem))) == SP_OBJECT(group)) { + // make sure a clone's effective transform is the same as was under group + ctrans = g.inverse() * citem->transform * g; + } else { + ctrans = citem->transform * g; + } + + // FIXME: constructing a transform that would fully preserve the appearance of a + // textpath if it is ungrouped with its path seems to be impossible in general + // case. E.g. if the group was squeezed, to keep the ungrouped textpath squeezed + // as well, we'll need to relink it to some "virtual" path which is inversely + // stretched relative to the actual path, and then squeeze the textpath back so it + // would both fit the actual path _and_ be squeezed as before. It's a bummer. + + // This is just a way to temporarily remember the transform in repr. When repr is + // reattached outside of the group, the transform will be written more properly + // (i.e. optimized into the object if the corresponding preference is set) + gchar affinestr[80]; + if (sp_svg_transform_write(affinestr, 79, ctrans)) { + nrepr->setAttribute("transform", affinestr); + } else { + nrepr->setAttribute("transform", NULL); + } + + items = g_slist_prepend (items, nrepr); + + } else { + Inkscape::XML::Node *nrepr = SP_OBJECT_REPR (child)->duplicate(); + objects = g_slist_prepend (objects, nrepr); + } + } + + /* Step 2 - clear group */ + // remember the position of the group + gint pos = SP_OBJECT_REPR(group)->position(); + + // the group is leaving forever, no heir, clones should take note; its children however are going to reemerge + SP_OBJECT (group)->deleteObject(true, false); + + /* Step 3 - add nonitems */ + if (objects) { + Inkscape::XML::Node *last_def = SP_OBJECT_REPR(defs)->lastChild(); + while (objects) { + SP_OBJECT_REPR(defs)->addChild((Inkscape::XML::Node *) objects->data, last_def); + Inkscape::GC::release((Inkscape::XML::Node *) objects->data); + objects = g_slist_remove (objects, objects->data); + } + } + + /* Step 4 - add items */ + gint const preserve = prefs_get_int_attribute("options.preservetransform", "value", 0); + while (items) { + Inkscape::XML::Node *repr = (Inkscape::XML::Node *) items->data; + // add item + prepr->appendChild(repr); + // restore position; since the items list was prepended (i.e. reverse), we now add + // all children at the same pos, which inverts the order once again + repr->setPosition(pos > 0 ? pos : 0); + + // fill in the children list if non-null + SPItem *nitem = (SPItem *) doc->getObjectByRepr(repr); + + /* Optimize the transform matrix if requested. */ + // No compensations are required because this is supposed to be a non-transformation visually. + if (!preserve) { + NR::Matrix (*set_transform)(SPItem *, NR::Matrix const &) = ((SPItemClass *) G_OBJECT_GET_CLASS(nitem))->set_transform; + if (set_transform) { + sp_item_set_item_transform(nitem, set_transform(nitem, nitem->transform)); + nitem->updateRepr(); + } + } + + Inkscape::GC::release(repr); + if (children && SP_IS_ITEM (nitem)) + *children = g_slist_prepend (*children, nitem); + + items = g_slist_remove (items, items->data); + } + + if (do_done) sp_document_done (doc); +} + +/* + * some API for list aspect of SPGroup + */ + +GSList * +sp_item_group_item_list (SPGroup * group) +{ + GSList *s; + SPObject *o; + + g_return_val_if_fail (group != NULL, NULL); + g_return_val_if_fail (SP_IS_GROUP (group), NULL); + + s = NULL; + + for ( o = sp_object_first_child(SP_OBJECT(group)) ; o != NULL ; o = SP_OBJECT_NEXT(o) ) { + if (SP_IS_ITEM (o)) { + s = g_slist_prepend (s, o); + } + } + + return g_slist_reverse (s); +} + +SPObject * +sp_item_group_get_child_by_name (SPGroup *group, SPObject *ref, const gchar *name) +{ + SPObject *child; + child = (ref) ? SP_OBJECT_NEXT(ref) : sp_object_first_child(SP_OBJECT(group)); + while ( child && strcmp (SP_OBJECT_REPR(child)->name(), name) ) { + child = SP_OBJECT_NEXT(child); + } + return child; +} + +void SPGroup::setLayerMode(LayerMode mode) { + if ( _layer_mode != mode ) { + if ( mode == LAYER ) { + sp_document_add_resource(SP_OBJECT_DOCUMENT(this), "layer", this); + } else { + sp_document_remove_resource(SP_OBJECT_DOCUMENT(this), "layer", this); + } + _layer_mode = mode; + _updateLayerMode(); + } +} + +SPGroup::LayerMode SPGroup::layerDisplayMode(unsigned int dkey) const { + std::map::const_iterator iter; + iter = _display_modes.find(dkey); + if ( iter != _display_modes.end() ) { + return (*iter).second; + } else { + return GROUP; + } +} + +void SPGroup::setLayerDisplayMode(unsigned int dkey, SPGroup::LayerMode mode) { + if ( layerDisplayMode(dkey) != mode ) { + _display_modes[dkey] = mode; + _updateLayerMode(dkey); + } +} + +void SPGroup::_updateLayerMode(unsigned int display_key) { + SPItemView *view; + for ( view = this->display ; view ; view = view->next ) { + if ( !display_key || view->key == display_key ) { + NRArenaGroup *arena_group=NR_ARENA_GROUP(view->arenaitem); + if (arena_group) { + nr_arena_group_set_transparent(arena_group, effectiveLayerMode(view->key) == SPGroup::LAYER); + } + } + } +} diff --git a/src/sp-item-group.h b/src/sp-item-group.h new file mode 100644 index 000000000..578454c92 --- /dev/null +++ b/src/sp-item-group.h @@ -0,0 +1,61 @@ +#ifndef __SP_ITEM_GROUP_H__ +#define __SP_ITEM_GROUP_H__ + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 authors + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include "sp-item.h" + +#define SP_TYPE_GROUP (sp_group_get_type ()) +#define SP_GROUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_GROUP, SPGroup)) +#define SP_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_GROUP, SPGroupClass)) +#define SP_IS_GROUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_GROUP)) +#define SP_IS_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_GROUP)) + +struct SPGroup : public SPItem { + enum LayerMode { GROUP, LAYER }; + + LayerMode _layer_mode; + std::map _display_modes; + + LayerMode layerMode() const { return _layer_mode; } + void setLayerMode(LayerMode mode); + + LayerMode effectiveLayerMode(unsigned int display_key) const { + if ( _layer_mode == LAYER ) { + return LAYER; + } else { + return layerDisplayMode(display_key); + } + } + + LayerMode layerDisplayMode(unsigned int display_key) const; + void setLayerDisplayMode(unsigned int display_key, LayerMode mode); + +private: + void _updateLayerMode(unsigned int display_key=0); +}; + +struct SPGroupClass { + SPItemClass parent_class; +}; + +GType sp_group_get_type (void); + +void sp_item_group_ungroup (SPGroup *group, GSList **children, bool do_done = true); + + +GSList *sp_item_group_item_list (SPGroup *group); +SPObject *sp_item_group_get_child_by_name (SPGroup *group, SPObject *ref, const gchar *name); + +#endif diff --git a/src/sp-item-notify-moveto.cpp b/src/sp-item-notify-moveto.cpp new file mode 100644 index 000000000..c84792215 --- /dev/null +++ b/src/sp-item-notify-moveto.cpp @@ -0,0 +1,76 @@ +/** \file + * Implementation of sp_item_notify_moveto(). + */ + +#include +#include +#include +#include +using std::vector; + + +/** + * Called by sp_guide_moveto to indicate that the guide line corresponding to g has been moved, and + * that consequently this item should move with it. + * + * \pre exist [cn in item.constraints] g eq cn.g. + */ +void sp_item_notify_moveto(SPItem &item, SPGuide const &mv_g, int const snappoint_ix, + double const position, bool const commit) +{ + g_return_if_fail(SP_IS_ITEM(&item)); + g_return_if_fail( unsigned(snappoint_ix) < 8 ); + NR::Point const dir( mv_g.normal ); + double const dir_lensq(dot(dir, dir)); + g_return_if_fail( dir_lensq != 0 ); + + vector snappoints; + sp_item_snappoints(&item, SnapPointsIter(snappoints)); + g_return_if_fail( snappoint_ix < int(snappoints.size()) ); + + double const pos0 = dot(dir, snappoints[snappoint_ix]); + /// \todo effic: skip if mv_g is already satisfied. + + /* Translate along dir to make dot(dir, snappoints(item)[snappoint_ix]) == position. */ + + /* Calculation: + dot(dir, snappoints[snappoint_ix] + s * dir) = position. + dot(dir, snappoints[snappoint_ix]) + dot(dir, s * dir) = position. + pos0 + s * dot(dir, dir) = position. + s * lensq(dir) = position - pos0. + s = (position - pos0) / dot(dir, dir). */ + NR::translate const tr( ( position - pos0 ) + * ( dir / dir_lensq ) ); + sp_item_set_i2d_affine(&item, sp_item_i2d_affine(&item) * tr); + /// \todo Reget snappoints, check satisfied. + + if (commit) { + /// \todo Consider maintaining a set of dirty items. + + /* Commit repr. */ + { + sp_item_write_transform(&item, SP_OBJECT_REPR(&item), item.transform); + } + + sp_item_rm_unsatisfied_cns(item); +#if 0 /* nyi */ + move_cn_to_front(mv_g, snappoint_ix, item.constraints); + /** \note If the guideline is connected to multiple snappoints of + * this item, then keeping those cns in order requires that the + * guide send notifications in order of increasing importance. + */ +#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:encoding=utf-8:textwidth=99 : diff --git a/src/sp-item-notify-moveto.h b/src/sp-item-notify-moveto.h new file mode 100644 index 000000000..41bd9ef21 --- /dev/null +++ b/src/sp-item-notify-moveto.h @@ -0,0 +1,22 @@ +#ifndef __SP_ITEM_NOTIFY_MOVETO_H__ +#define __SP_ITEM_NOTIFY_MOVETO_H__ + +#include + +void sp_item_notify_moveto(SPItem &item, SPGuide const &g, int const snappoint_ix, + double position, bool const commit); + + +#endif /* !__SP_ITEM_NOTIFY_MOVETO_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:encoding=utf-8:textwidth=99 : diff --git a/src/sp-item-rm-unsatisfied-cns.cpp b/src/sp-item-rm-unsatisfied-cns.cpp new file mode 100644 index 000000000..d71c6fe0c --- /dev/null +++ b/src/sp-item-rm-unsatisfied-cns.cpp @@ -0,0 +1,40 @@ + +#include +#include +#include +#include +#include +using std::vector; + +void sp_item_rm_unsatisfied_cns(SPItem &item) +{ + if (item.constraints.empty()) { + return; + } + vector snappoints; + sp_item_snappoints(&item, SnapPointsIter(snappoints)); + for (unsigned i = item.constraints.size(); i--;) { + g_assert( i < item.constraints.size() ); + SPGuideConstraint const &cn = item.constraints[i]; + int const snappoint_ix = cn.snappoint_ix; + g_assert( snappoint_ix < int(snappoints.size()) ); + if (!approx_equal(dot(cn.g->normal, snappoints[snappoint_ix]), cn.g->position)) { + remove_last(cn.g->attached_items, SPGuideAttachment(&item, cn.snappoint_ix)); + g_assert( i < item.constraints.size() ); + vector::iterator const ei(&item.constraints[i]); + item.constraints.erase(ei); + } + } +} + + +/* + 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 : diff --git a/src/sp-item-rm-unsatisfied-cns.h b/src/sp-item-rm-unsatisfied-cns.h new file mode 100644 index 000000000..72a6e0c66 --- /dev/null +++ b/src/sp-item-rm-unsatisfied-cns.h @@ -0,0 +1,19 @@ +#ifndef __SP_ITEM_RM_UNSATISFIED_CNS_H__ +#define __SP_ITEM_RM_UNSATISFIED_CNS_H__ +#include + +void sp_item_rm_unsatisfied_cns(SPItem &item); + + +#endif /* !__SP_ITEM_RM_UNSATISFIED_CNS_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:encoding=utf-8:textwidth=99 : diff --git a/src/sp-item-transform.cpp b/src/sp-item-transform.cpp new file mode 100644 index 000000000..817bc1b44 --- /dev/null +++ b/src/sp-item-transform.cpp @@ -0,0 +1,148 @@ +#define __SP_ITEM_TRANSFORM_C__ + +/* + * Transforming single items + * + * Authors: + * Lauris Kaplinski + * Frank Felfe + * bulia byak + * + * Copyright (C) 1999-2005 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include "libnr/nr-matrix-rotate-ops.h" +#include "libnr/nr-matrix-scale-ops.h" +#include "libnr/nr-matrix-translate-ops.h" +#include "sp-item.h" + +static NR::translate inverse(NR::translate const m) +{ + /* TODO: Move this to nr-matrix-fns.h or the like. */ + return NR::translate(-m[0], -m[1]); +} + +void +sp_item_rotate_rel(SPItem *item, NR::rotate const &rotation) +{ + NR::translate const s(sp_item_bbox_desktop(item).midpoint()); + + // Rotate item. + sp_item_set_i2d_affine(item, + sp_item_i2d_affine(item) * inverse(s) * rotation * s); + + // Use each item's own transform writer, consistent with sp_selection_apply_affine() + sp_item_write_transform(item, SP_OBJECT_REPR(item), item->transform); +} + +void +sp_item_scale_rel (SPItem *item, NR::scale const &scale) +{ + NR::translate const s(sp_item_bbox_desktop(item).midpoint()); + + sp_item_set_i2d_affine(item, + sp_item_i2d_affine(item) * inverse(s) * scale * s); + sp_item_write_transform(item, SP_OBJECT_REPR(item), item->transform); +} + +void +sp_item_skew_rel (SPItem *item, double skewX, double skewY) +{ + NR::Rect bbox(sp_item_bbox_desktop(item)); + + NR::translate const s(bbox.midpoint()); + + NR::Matrix const skew(1, skewY, skewX, 1, 0, 0); + + sp_item_set_i2d_affine(item, + sp_item_i2d_affine(item) * inverse(s) * skew * s); + sp_item_write_transform(item, SP_OBJECT_REPR(item), item->transform); +} + +void sp_item_move_rel(SPItem *item, NR::translate const &tr) +{ + sp_item_set_i2d_affine(item, sp_item_i2d_affine(item) * tr); + + sp_item_write_transform(item, SP_OBJECT_REPR(item), item->transform); +} + +/* +** Returns the matrix you need to apply to an object with given bbox and strokewidth to +scale/move it to the new box x0/y0/x1/y1. Takes into account the "scale stroke" +preference value passed to it. Has to solve a quadratic equation to make sure +the goal is met exactly and the stroke scaling is obeyed. +*/ + +NR::Matrix +get_scale_transform_with_stroke (NR::Rect &bbox_param, gdouble strokewidth, bool transform_stroke, gdouble x0, gdouble y0, gdouble x1, gdouble y1) +{ + NR::Rect bbox (bbox_param); + + NR::Matrix p2o = NR::Matrix (NR::translate (-bbox.min())); + NR::Matrix o2n = NR::Matrix (NR::translate (x0, y0)); + + NR::Matrix scale = NR::Matrix (NR::scale (1, 1)); // scale component + NR::Matrix unbudge = NR::Matrix (NR::translate (0, 0)); // move component to compensate for the drift caused by stroke width change + + gdouble w0 = bbox.extent(NR::X); + gdouble h0 = bbox.extent(NR::Y); + gdouble w1 = x1 - x0; + gdouble h1 = y1 - y0; + gdouble r0 = strokewidth; + + if (bbox.isEmpty() || bbox.extent(NR::X) < 1e-06 || bbox.extent(NR::Y) < 1e-06 || + fabs(w0 - r0) < 1e-6 || fabs(h0 - r0) < 1e-6 || + (!transform_stroke && (fabs(w1 - r0) < 1e-6 || fabs(h1 - r0) < 1e-6)) + ) { + NR::Matrix move = NR::Matrix(NR::translate(x0 - bbox.min()[NR::X], y0 - bbox.min()[NR::Y])); + return (move); // sorry, cannot scale from or to empty boxes, so only translate + } + + NR::Matrix direct = NR::Matrix (NR::scale(w1 / w0, h1 / h0)); + gdouble ratio_x = (w1 - r0) / (w0 - r0); + gdouble ratio_y = (h1 - r0) / (h0 - r0); + NR::Matrix direct_constant_r = NR::Matrix (NR::scale(ratio_x, ratio_y)); + + if (transform_stroke && r0 != 0 && r0 != NR_HUGE) { // there's stroke, and we need to scale it + // These coefficients are obtained from the assumption that scaling applies to the + // non-stroked "shape proper" and that stroke scale is scaled by the expansion of that + // matrix + gdouble A = -(w0 *h0) + r0*(w0 + h0); + gdouble B = -(w1 + h1) * r0*r0; + gdouble C = w1 * h1 * r0*r0; + if (B*B - 4*A*C > 0) { + gdouble r1 = (-B - sqrt (B*B - 4*A*C))/(2*A); + //gdouble r2 = (-B + sqrt (B*B - 4*A*C))/(2*A); + //std::cout << "r0" << r0 << " r1" << r1 << " r2" << r2 << "\n"; + gdouble scale_x = (w1 - r1)/(w0 - r0); + gdouble scale_y = (h1 - r1)/(h0 - r0); + scale *= NR::scale(scale_x, scale_y); + unbudge *= NR::translate (-0.5 * (r0 * scale_x - r1), -0.5 * (r0 * scale_y - r1)); + } else { + scale *= direct; + } + } else { + if (r0 == 0 || r0 == NR_HUGE) { // no stroke to scale + scale *= direct; + } else {// nonscaling strokewidth + scale *= direct_constant_r; + unbudge *= NR::translate (0.5 * r0 * (1 - ratio_x), 0.5 * r0 * (1 - ratio_y)); + } + } + + return (p2o * scale * unbudge * o2n); +} + +/* + 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 : diff --git a/src/sp-item-transform.h b/src/sp-item-transform.h new file mode 100644 index 000000000..23d96987f --- /dev/null +++ b/src/sp-item-transform.h @@ -0,0 +1,29 @@ +#ifndef SP_ITEM_TRANSFORM_H +#define SP_ITEM_TRANSFORM_H + +#include "forward.h" +namespace NR { +class translate; +class rotate; +class Rect; +} + +void sp_item_rotate_rel(SPItem *item, NR::rotate const &rotation); +void sp_item_scale_rel (SPItem *item, NR::scale const &scale); +void sp_item_skew_rel (SPItem *item, double skewX, double skewY); +void sp_item_move_rel(SPItem *item, NR::translate const &tr); + +NR::Matrix get_scale_transform_with_stroke (NR::Rect &bbox, gdouble strokewidth, bool transform_stroke, gdouble x0, gdouble y0, gdouble x1, gdouble y1); + +#endif /* !SP_ITEM_TRANSFORM_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:encoding=utf-8:textwidth=99 : diff --git a/src/sp-item-update-cns.cpp b/src/sp-item-update-cns.cpp new file mode 100644 index 000000000..e1857dcca --- /dev/null +++ b/src/sp-item-update-cns.cpp @@ -0,0 +1,45 @@ + +#include "satisfied-guide-cns.h" +#include "sp-guide-constraint.h" +#include +#include +using std::find; +using std::vector; + +void sp_item_update_cns(SPItem &item, SPDesktop const &desktop) +{ + vector snappoints; + sp_item_snappoints(&item, SnapPointsIter(snappoints)); + /* TODO: Implement the ordering. */ + vector found_cns; + satisfied_guide_cns(desktop, snappoints, found_cns); + /* effic: It might be nice to avoid an n^2 algorithm, but in practice n will be + small enough that it's still usually more efficient. */ + + for (vector::const_iterator fi(found_cns.begin()), + fiEnd(found_cns.end()); + fi != fiEnd; ++fi) + { + SPGuideConstraint const &cn = *fi; + if ( find(item.constraints.begin(), + item.constraints.end(), + cn) + == item.constraints.end() ) + { + item.constraints.push_back(cn); + cn.g->attached_items.push_back(SPGuideAttachment(&item, cn.snappoint_ix)); + } + } +} + + +/* + 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 : diff --git a/src/sp-item-update-cns.h b/src/sp-item-update-cns.h new file mode 100644 index 000000000..74bb1f8cd --- /dev/null +++ b/src/sp-item-update-cns.h @@ -0,0 +1,19 @@ +#ifndef __SP_ITEM_UPDATE_CNS_H__ +#define __SP_ITEM_UPDATE_CNS_H__ +#include + +void sp_item_update_cns(SPItem &item, SPDesktop const &desktop); + + +#endif /* !__SP_ITEM_UPDATE_CNS_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:encoding=utf-8:textwidth=99 : diff --git a/src/sp-item.cpp b/src/sp-item.cpp new file mode 100644 index 000000000..33bbe9e05 --- /dev/null +++ b/src/sp-item.cpp @@ -0,0 +1,1320 @@ +#define __SP_ITEM_C__ + +/** \file + * Base class for visual SVG elements + */ +/* + * Authors: + * Lauris Kaplinski + * bulia byak + * + * Copyright (C) 2001-2005 authors + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +/** \class SPItem + * + * SPItem is an abstract base class for all graphic (visible) SVG nodes. It + * is a subclass of SPObject, with great deal of specific functionality. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + + + +#include "svg/svg.h" +#include "print.h" +#include "display/nr-arena.h" +#include "display/nr-arena-item.h" +#include "attributes.h" +#include "document.h" +#include "uri.h" + +#include "style.h" +#include +#include "sp-root.h" +#include "sp-clippath.h" +#include "sp-mask.h" +#include "sp-rect.h" +#include "sp-use.h" +#include "sp-text.h" +#include "sp-item-rm-unsatisfied-cns.h" +#include "sp-pattern.h" +#include "gradient-chemistry.h" +#include "prefs-utils.h" +#include "conn-avoid-ref.h" + +#include "libnr/nr-matrix-div.h" +#include "libnr/nr-matrix-fns.h" +#include "libnr/nr-matrix-scale-ops.h" +#include "libnr/nr-matrix-translate-ops.h" +#include "libnr/nr-scale-translate-ops.h" +#include "libnr/nr-translate-scale-ops.h" +#include "algorithms/find-last-if.h" +#include "util/reverse-list.h" + +#include "xml/repr.h" + +#define noSP_ITEM_DEBUG_IDLE + +static void sp_item_class_init(SPItemClass *klass); +static void sp_item_init(SPItem *item); + +static void sp_item_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); +static void sp_item_release(SPObject *object); +static void sp_item_set(SPObject *object, unsigned key, gchar const *value); +static void sp_item_update(SPObject *object, SPCtx *ctx, guint flags); +static Inkscape::XML::Node *sp_item_write(SPObject *object, Inkscape::XML::Node *repr, guint flags); + +static gchar *sp_item_private_description(SPItem *item); +static void sp_item_private_snappoints(SPItem const *item, SnapPointsIter p); + +static SPItemView *sp_item_view_new_prepend(SPItemView *list, SPItem *item, unsigned flags, unsigned key, NRArenaItem *arenaitem); +static SPItemView *sp_item_view_list_remove(SPItemView *list, SPItemView *view); + +static SPObjectClass *parent_class; + +static void clip_ref_changed(SPObject *old_clip, SPObject *clip, SPItem *item); +static void mask_ref_changed(SPObject *old_clip, SPObject *clip, SPItem *item); + +/** + * Registers SPItem class and returns its type number. + */ +GType +sp_item_get_type(void) +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPItemClass), + NULL, NULL, + (GClassInitFunc) sp_item_class_init, + NULL, NULL, + sizeof(SPItem), + 16, + (GInstanceInitFunc) sp_item_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_OBJECT, "SPItem", &info, (GTypeFlags)0); + } + return type; +} + +/** + * SPItem vtable initialization. + */ +static void +sp_item_class_init(SPItemClass *klass) +{ + SPObjectClass *sp_object_class = (SPObjectClass *) klass; + + parent_class = (SPObjectClass *)g_type_class_ref(SP_TYPE_OBJECT); + + sp_object_class->build = sp_item_build; + sp_object_class->release = sp_item_release; + sp_object_class->set = sp_item_set; + sp_object_class->update = sp_item_update; + sp_object_class->write = sp_item_write; + + klass->description = sp_item_private_description; + klass->snappoints = sp_item_private_snappoints; +} + +/** + * Callback for SPItem object initialization. + */ +static void +sp_item_init(SPItem *item) +{ + SPObject *object = SP_OBJECT(item); + + item->sensitive = TRUE; + + item->r_cx = 0; + item->r_cx = 0; + + item->transform = NR::identity(); + + item->display = NULL; + + item->clip_ref = new SPClipPathReference(SP_OBJECT(item)); + { + sigc::signal cs1=item->clip_ref->changedSignal(); + sigc::slot2 sl1=sigc::bind(sigc::ptr_fun(clip_ref_changed), item); + cs1.connect(sl1); + } + + item->mask_ref = new SPMaskReference(SP_OBJECT(item)); + sigc::signal cs2=item->mask_ref->changedSignal(); + sigc::slot2 sl2=sigc::bind(sigc::ptr_fun(mask_ref_changed), item); + cs2.connect(sl2); + + if (!object->style) object->style = sp_style_new_from_object(SP_OBJECT(item)); + + item->avoidRef = new SPAvoidRef(item); + + new (&item->_transformed_signal) sigc::signal(); +} + +bool SPItem::isVisibleAndUnlocked() const { + return (!isHidden() && !isLocked()); +} + +bool SPItem::isVisibleAndUnlocked(unsigned display_key) const { + return (!isHidden(display_key) && !isLocked()); +} + +bool SPItem::isLocked() const { + for (SPObject *o = SP_OBJECT(this); o != NULL; o = SP_OBJECT_PARENT(o)) { + if (SP_IS_ITEM(o) && !(SP_ITEM(o)->sensitive)) + return true; + } + return false; +} + +void SPItem::setLocked(bool locked) { + SP_OBJECT_REPR(this)->setAttribute("sodipodi:insensitive", + ( locked ? "1" : NULL )); + updateRepr(); +} + +bool SPItem::isHidden() const { + return style->display.computed == SP_CSS_DISPLAY_NONE; +} + +void SPItem::setHidden(bool hide) { + style->display.set = TRUE; + style->display.value = ( hide ? SP_CSS_DISPLAY_NONE : SP_CSS_DISPLAY_INLINE ); + style->display.computed = style->display.value; + style->display.inherit = FALSE; + updateRepr(); +} + +bool SPItem::isHidden(unsigned display_key) const { + for ( SPItemView *view(display) ; view ; view = view->next ) { + if ( view->key == display_key ) { + g_assert(view->arenaitem != NULL); + for ( NRArenaItem *arenaitem = view->arenaitem ; + arenaitem ; arenaitem = arenaitem->parent ) + { + if (!arenaitem->visible) { + return true; + } + } + return false; + } + } + return true; +} + +/** + * Returns something suitable for the `Hide' checkbox in the Object Properties dialog box. + * Corresponds to setExplicitlyHidden. + */ +bool +SPItem::isExplicitlyHidden() const +{ + return (this->style->display.set + && this->style->display.value == SP_CSS_DISPLAY_NONE); +} + +/** + * Sets the display CSS property to `hidden' if \a val is true, + * otherwise makes it unset + */ +void +SPItem::setExplicitlyHidden(bool const val) { + this->style->display.set = val; + this->style->display.value = ( val ? SP_CSS_DISPLAY_NONE : SP_CSS_DISPLAY_INLINE ); + this->style->display.computed = this->style->display.value; + this->updateRepr(); +} + +namespace { + +bool is_item(SPObject const &object) { + return SP_IS_ITEM(&object); +} + +} + +void SPItem::raiseToTop() { + using Inkscape::Algorithms::find_last_if; + + SPObject *topmost=find_last_if( + SP_OBJECT_NEXT(this), NULL, &is_item + ); + if (topmost) { + Inkscape::XML::Node *repr=SP_OBJECT_REPR(this); + sp_repr_parent(repr)->changeOrder(repr, SP_OBJECT_REPR(topmost)); + } +} + +void SPItem::raiseOne() { + SPObject *next_higher=std::find_if( + SP_OBJECT_NEXT(this), NULL, &is_item + ); + if (next_higher) { + Inkscape::XML::Node *repr=SP_OBJECT_REPR(this); + Inkscape::XML::Node *ref=SP_OBJECT_REPR(next_higher); + sp_repr_parent(repr)->changeOrder(repr, ref); + } +} + +void SPItem::lowerOne() { + using Inkscape::Util::MutableList; + using Inkscape::Util::reverse_list; + + MutableList next_lower=std::find_if( + reverse_list( + SP_OBJECT_PARENT(this)->firstChild(), this + ), + MutableList(), + &is_item + ); + if (next_lower) { + ++next_lower; + Inkscape::XML::Node *repr=SP_OBJECT_REPR(this); + Inkscape::XML::Node *ref=( next_lower ? SP_OBJECT_REPR(&*next_lower) : NULL ); + sp_repr_parent(repr)->changeOrder(repr, ref); + } +} + +void SPItem::lowerToBottom() { + using Inkscape::Algorithms::find_last_if; + using Inkscape::Util::MutableList; + using Inkscape::Util::reverse_list; + + MutableList bottom=find_last_if( + reverse_list( + SP_OBJECT_PARENT(this)->firstChild(), this + ), + MutableList(), + &is_item + ); + if (bottom) { + ++bottom; + Inkscape::XML::Node *repr=SP_OBJECT_REPR(this); + Inkscape::XML::Node *ref=( bottom ? SP_OBJECT_REPR(&*bottom) : NULL ); + sp_repr_parent(repr)->changeOrder(repr, ref); + } +} + +static void +sp_item_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + sp_object_read_attr(object, "style"); + sp_object_read_attr(object, "transform"); + sp_object_read_attr(object, "clip-path"); + sp_object_read_attr(object, "mask"); + sp_object_read_attr(object, "sodipodi:insensitive"); + sp_object_read_attr(object, "sodipodi:nonprintable"); + sp_object_read_attr(object, "inkscape:r_cx"); + sp_object_read_attr(object, "inkscape:r_cy"); + sp_object_read_attr(object, "inkscape:connector-avoid"); + + if (((SPObjectClass *) (parent_class))->build) { + (* ((SPObjectClass *) (parent_class))->build)(object, document, repr); + } +} + +static void +sp_item_release(SPObject *object) +{ + SPItem *item = (SPItem *) object; + + if (item->clip_ref) { + item->clip_ref->detach(); + delete item->clip_ref; + item->clip_ref = NULL; + } + + if (item->mask_ref) { + item->mask_ref->detach(); + delete item->mask_ref; + item->mask_ref = NULL; + } + + if (item->avoidRef) { + delete item->avoidRef; + item->avoidRef = NULL; + } + + if (((SPObjectClass *) (parent_class))->release) { + ((SPObjectClass *) parent_class)->release(object); + } + + while (item->display) { + nr_arena_item_unparent(item->display->arenaitem); + item->display = sp_item_view_list_remove(item->display, item->display); + } + + item->_transformed_signal.~signal(); +} + +static void +sp_item_set(SPObject *object, unsigned key, gchar const *value) +{ + SPItem *item = (SPItem *) object; + + switch (key) { + case SP_ATTR_TRANSFORM: { + NR::Matrix t; + if (value && sp_svg_transform_read(value, &t)) { + sp_item_set_item_transform(item, t); + } else { + sp_item_set_item_transform(item, NR::identity()); + } + break; + } + case SP_PROP_CLIP_PATH: { + gchar *uri = Inkscape::parse_css_url(value); + if (uri) { + try { + item->clip_ref->attach(Inkscape::URI(uri)); + } catch (Inkscape::BadURIException &e) { + g_warning("%s", e.what()); + item->clip_ref->detach(); + } + g_free(uri); + } else { + item->clip_ref->detach(); + } + + break; + } + case SP_PROP_MASK: { + gchar *uri=Inkscape::parse_css_url(value); + if (uri) { + try { + item->mask_ref->attach(Inkscape::URI(uri)); + } catch (Inkscape::BadURIException &e) { + g_warning("%s", e.what()); + item->mask_ref->detach(); + } + g_free(uri); + } else { + item->mask_ref->detach(); + } + + break; + } + case SP_ATTR_SODIPODI_INSENSITIVE: + item->sensitive = !value; + for (SPItemView *v = item->display; v != NULL; v = v->next) { + nr_arena_item_set_sensitive(v->arenaitem, item->sensitive); + } + break; + case SP_ATTR_STYLE: + sp_style_read_from_object(object->style, object); + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + break; + case SP_ATTR_CONNECTOR_AVOID: + item->avoidRef->setAvoid(value); + break; + default: + if (SP_ATTRIBUTE_IS_CSS(key)) { + sp_style_read_from_object(object->style, object); + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + } else { + if (((SPObjectClass *) (parent_class))->set) { + (* ((SPObjectClass *) (parent_class))->set)(object, key, value); + } + } + break; + } +} + +static void +clip_ref_changed(SPObject *old_clip, SPObject *clip, SPItem *item) +{ + if (old_clip) { + SPItemView *v; + /* Hide clippath */ + for (v = item->display; v != NULL; v = v->next) { + sp_clippath_hide(SP_CLIPPATH(old_clip), NR_ARENA_ITEM_GET_KEY(v->arenaitem)); + nr_arena_item_set_clip(v->arenaitem, NULL); + } + } + if (SP_IS_CLIPPATH(clip)) { + NRRect bbox; + sp_item_invoke_bbox(item, &bbox, NR::identity(), TRUE); + for (SPItemView *v = item->display; v != NULL; v = v->next) { + if (!v->arenaitem->key) { + NR_ARENA_ITEM_SET_KEY(v->arenaitem, sp_item_display_key_new(3)); + } + NRArenaItem *ai = sp_clippath_show(SP_CLIPPATH(clip), + NR_ARENA_ITEM_ARENA(v->arenaitem), + NR_ARENA_ITEM_GET_KEY(v->arenaitem)); + nr_arena_item_set_clip(v->arenaitem, ai); + nr_arena_item_unref(ai); + sp_clippath_set_bbox(SP_CLIPPATH(clip), NR_ARENA_ITEM_GET_KEY(v->arenaitem), &bbox); + } + } +} + +static void +mask_ref_changed(SPObject *old_mask, SPObject *mask, SPItem *item) +{ + if (old_mask) { + /* Hide mask */ + for (SPItemView *v = item->display; v != NULL; v = v->next) { + sp_mask_hide(SP_MASK(old_mask), NR_ARENA_ITEM_GET_KEY(v->arenaitem)); + nr_arena_item_set_mask(v->arenaitem, NULL); + } + } + if (SP_IS_MASK(mask)) { + NRRect bbox; + sp_item_invoke_bbox(item, &bbox, NR::identity(), TRUE); + for (SPItemView *v = item->display; v != NULL; v = v->next) { + if (!v->arenaitem->key) { + NR_ARENA_ITEM_SET_KEY(v->arenaitem, sp_item_display_key_new(3)); + } + NRArenaItem *ai = sp_mask_show(SP_MASK(mask), + NR_ARENA_ITEM_ARENA(v->arenaitem), + NR_ARENA_ITEM_GET_KEY(v->arenaitem)); + nr_arena_item_set_mask(v->arenaitem, ai); + nr_arena_item_unref(ai); + sp_mask_set_bbox(SP_MASK(mask), NR_ARENA_ITEM_GET_KEY(v->arenaitem), &bbox); + } + } +} + +static void +sp_item_update(SPObject *object, SPCtx *ctx, guint flags) +{ + SPItem *item = SP_ITEM(object); + + if (((SPObjectClass *) (parent_class))->update) + (* ((SPObjectClass *) (parent_class))->update)(object, ctx, flags); + + if (flags & (SP_OBJECT_CHILD_MODIFIED_FLAG | SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG)) { + if (flags & SP_OBJECT_MODIFIED_FLAG) { + for (SPItemView *v = item->display; v != NULL; v = v->next) { + nr_arena_item_set_transform(v->arenaitem, item->transform); + } + } + + SPClipPath *clip_path = item->clip_ref->getObject(); + SPMask *mask = item->mask_ref->getObject(); + + if ( clip_path || mask ) { + NRRect bbox; + sp_item_invoke_bbox(item, &bbox, NR::identity(), TRUE); + if (clip_path) { + for (SPItemView *v = item->display; v != NULL; v = v->next) { + sp_clippath_set_bbox(clip_path, NR_ARENA_ITEM_GET_KEY(v->arenaitem), &bbox); + } + } + if (mask) { + for (SPItemView *v = item->display; v != NULL; v = v->next) { + sp_mask_set_bbox(mask, NR_ARENA_ITEM_GET_KEY(v->arenaitem), &bbox); + } + } + } + + if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) { + for (SPItemView *v = item->display; v != NULL; v = v->next) { + nr_arena_item_set_opacity(v->arenaitem, SP_SCALE24_TO_FLOAT(object->style->opacity.value)); + nr_arena_item_set_visible(v->arenaitem, !item->isHidden()); + } + } + } + + // Update libavoid with item geometry (for connector routing). + item->avoidRef->handleSettingChange(); +} + +static Inkscape::XML::Node * +sp_item_write(SPObject *const object, Inkscape::XML::Node *repr, guint flags) +{ + SPItem *item = SP_ITEM(object); + + gchar c[256]; + if (sp_svg_transform_write(c, 256, item->transform)) { + repr->setAttribute("transform", c); + } else { + repr->setAttribute("transform", NULL); + } + + SPObject const *const parent = SP_OBJECT_PARENT(object); + /** \todo Can someone please document why this is conditional on having + * a parent? The only parentless thing I can think of is the top-level + * element (SPRoot). SPRoot is derived from SPGroup, and can have + * style. I haven't looked at callers. + */ + if (parent) { + SPStyle const *const obj_style = SP_OBJECT_STYLE(object); + if (obj_style) { + gchar *s = sp_style_write_string(obj_style, SP_STYLE_FLAG_IFSET); + repr->setAttribute("style", ( *s ? s : NULL )); + g_free(s); + } else { + /** \todo I'm not sure what to do in this case. Bug #1165868 + * suggests that it can arise, but the submitter doesn't know + * how to do so reliably. The main two options are either + * leave repr's style attribute unchanged, or explicitly clear it. + * Must also consider what to do with property attributes for + * the element; see below. + */ + char const *style_str = repr->attribute("style"); + if (!style_str) { + style_str = "NULL"; + } + g_warning("Item's style is NULL; repr style attribute is %s", style_str); + } + + /** \note We treat object->style as authoritative. Its effects have + * been written to the style attribute above; any properties that are + * unset we take to be deliberately unset (e.g. so that clones can + * override the property). + * + * Note that the below has an undesirable consequence of changing the + * appearance on renderers that lack CSS support (e.g. SVG tiny); + * possibly we should write property attributes instead of a style + * attribute. + */ + sp_style_unset_property_attrs (object); + } + + if (flags & SP_OBJECT_WRITE_EXT) { + repr->setAttribute("sodipodi:insensitive", ( item->sensitive ? NULL : "true" )); + repr->setAttribute("inkscape:r_cx", ( item->r_cx ? NULL : "true" )); + repr->setAttribute("inkscape:r_cy", ( item->r_cy ? NULL : "true" )); + } + + if (((SPObjectClass *) (parent_class))->write) { + ((SPObjectClass *) (parent_class))->write(object, repr, flags); + } + + return repr; +} + +NR::Rect SPItem::invokeBbox(NR::Matrix const &transform) const +{ + NRRect r; + sp_item_invoke_bbox_full(this, &r, transform, 0, TRUE); + return NR::Rect(r); +} + +void +sp_item_invoke_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const clear) +{ + sp_item_invoke_bbox_full(item, bbox, transform, 0, clear); +} + +void +sp_item_invoke_bbox_full(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const flags, unsigned const clear) +{ + g_assert(item != NULL); + g_assert(SP_IS_ITEM(item)); + g_assert(bbox != NULL); + + if (clear) { + bbox->x0 = bbox->y0 = 1e18; + bbox->x1 = bbox->y1 = -1e18; + } + + if (((SPItemClass *) G_OBJECT_GET_CLASS(item))->bbox) { + ((SPItemClass *) G_OBJECT_GET_CLASS(item))->bbox(item, bbox, transform, flags); + } +} + +unsigned sp_item_pos_in_parent(SPItem *item) +{ + g_assert(item != NULL); + g_assert(SP_IS_ITEM(item)); + + SPObject *parent = SP_OBJECT_PARENT(item); + g_assert(parent != NULL); + g_assert(SP_IS_OBJECT(parent)); + + SPObject *object = SP_OBJECT(item); + + unsigned pos=0; + for ( SPObject *iter = sp_object_first_child(parent) ; iter ; iter = SP_OBJECT_NEXT(iter)) { + if ( iter == object ) { + return pos; + } + if (SP_IS_ITEM(iter)) { + pos++; + } + } + + g_assert_not_reached(); + return 0; +} + +void +sp_item_bbox_desktop(SPItem *item, NRRect *bbox) +{ + g_assert(item != NULL); + g_assert(SP_IS_ITEM(item)); + g_assert(bbox != NULL); + + sp_item_invoke_bbox(item, bbox, sp_item_i2d_affine(item), TRUE); +} + +NR::Rect sp_item_bbox_desktop(SPItem *item) +{ + NRRect ret; + sp_item_bbox_desktop(item, &ret); + return NR::Rect(ret); +} + +static void sp_item_private_snappoints(SPItem const *item, SnapPointsIter p) +{ + NR::Rect const bbox = item->invokeBbox(sp_item_i2d_affine(item)); + /* Just a pair of opposite corners of the bounding box suffices given that we don't yet + support angled guide lines. */ + + *p = bbox.min(); + *p = bbox.max(); +} + +void sp_item_snappoints(SPItem const *item, SnapPointsIter p) +{ + g_assert (item != NULL); + g_assert (SP_IS_ITEM(item)); + + SPItemClass const &item_class = *(SPItemClass const *) G_OBJECT_GET_CLASS(item); + if (item_class.snappoints) { + item_class.snappoints(item, p); + } +} + +void +sp_item_invoke_print(SPItem *item, SPPrintContext *ctx) +{ + if (!item->isHidden()) { + if (((SPItemClass *) G_OBJECT_GET_CLASS(item))->print) { + if (!item->transform.test_identity() + || SP_OBJECT_STYLE(item)->opacity.value != SP_SCALE24_MAX) + { + sp_print_bind(ctx, item->transform, SP_SCALE24_TO_FLOAT(SP_OBJECT_STYLE(item)->opacity.value)); + ((SPItemClass *) G_OBJECT_GET_CLASS(item))->print(item, ctx); + sp_print_release(ctx); + } else { + ((SPItemClass *) G_OBJECT_GET_CLASS(item))->print(item, ctx); + } + } + } +} + +static gchar * +sp_item_private_description(SPItem *item) +{ + return g_strdup(_("Object")); +} + +/** + * Returns a string suitable for status bar, formatted in pango markup language. + * + * Must be freed by caller. + */ +gchar * +sp_item_description(SPItem *item) +{ + g_assert(item != NULL); + g_assert(SP_IS_ITEM(item)); + + if (((SPItemClass *) G_OBJECT_GET_CLASS(item))->description) { + return ((SPItemClass *) G_OBJECT_GET_CLASS(item))->description(item); + } + + g_assert_not_reached(); + return NULL; +} + +/** + * Allocates unique integer keys. + * \param numkeys Number of keys required. + * \return First allocated key; hence if the returned key is n + * you can use n, n + 1, ..., n + (numkeys - 1) + */ +unsigned +sp_item_display_key_new(unsigned numkeys) +{ + static unsigned dkey = 0; + + dkey += numkeys; + + return dkey - numkeys; +} + +NRArenaItem * +sp_item_invoke_show(SPItem *item, NRArena *arena, unsigned key, unsigned flags) +{ + g_assert(item != NULL); + g_assert(SP_IS_ITEM(item)); + g_assert(arena != NULL); + g_assert(NR_IS_ARENA(arena)); + + NRArenaItem *ai = NULL; + if (((SPItemClass *) G_OBJECT_GET_CLASS(item))->show) { + ai = ((SPItemClass *) G_OBJECT_GET_CLASS(item))->show(item, arena, key, flags); + } + + if (ai != NULL) { + item->display = sp_item_view_new_prepend(item->display, item, flags, key, ai); + nr_arena_item_set_transform(ai, item->transform); + nr_arena_item_set_opacity(ai, SP_SCALE24_TO_FLOAT(SP_OBJECT_STYLE(item)->opacity.value)); + nr_arena_item_set_visible(ai, !item->isHidden()); + nr_arena_item_set_sensitive(ai, item->sensitive); + if (item->clip_ref->getObject()) { + NRArenaItem *ac; + if (!item->display->arenaitem->key) NR_ARENA_ITEM_SET_KEY(item->display->arenaitem, sp_item_display_key_new(3)); + ac = sp_clippath_show(item->clip_ref->getObject(), arena, NR_ARENA_ITEM_GET_KEY(item->display->arenaitem)); + nr_arena_item_set_clip(ai, ac); + nr_arena_item_unref(ac); + } + if (item->mask_ref->getObject()) { + NRArenaItem *ac; + if (!item->display->arenaitem->key) NR_ARENA_ITEM_SET_KEY(item->display->arenaitem, sp_item_display_key_new(3)); + ac = sp_mask_show(item->mask_ref->getObject(), arena, NR_ARENA_ITEM_GET_KEY(item->display->arenaitem)); + nr_arena_item_set_mask(ai, ac); + nr_arena_item_unref(ac); + } + NR_ARENA_ITEM_SET_DATA(ai, item); + } + + return ai; +} + +void +sp_item_invoke_hide(SPItem *item, unsigned key) +{ + g_assert(item != NULL); + g_assert(SP_IS_ITEM(item)); + + if (((SPItemClass *) G_OBJECT_GET_CLASS(item))->hide) { + ((SPItemClass *) G_OBJECT_GET_CLASS(item))->hide(item, key); + } + + SPItemView *ref = NULL; + SPItemView *v = item->display; + while (v != NULL) { + SPItemView *next = v->next; + if (v->key == key) { + if (item->clip_ref->getObject()) { + sp_clippath_hide(item->clip_ref->getObject(), NR_ARENA_ITEM_GET_KEY(v->arenaitem)); + nr_arena_item_set_clip(v->arenaitem, NULL); + } + if (item->mask_ref->getObject()) { + sp_mask_hide(item->mask_ref->getObject(), NR_ARENA_ITEM_GET_KEY(v->arenaitem)); + nr_arena_item_set_mask(v->arenaitem, NULL); + } + if (!ref) { + item->display = v->next; + } else { + ref->next = v->next; + } + nr_arena_item_unparent(v->arenaitem); + nr_arena_item_unref(v->arenaitem); + g_free(v); + } else { + ref = v; + } + v = next; + } +} + +// Adjusters + +void +sp_item_adjust_pattern (SPItem *item, NR::Matrix const &postmul, bool set) +{ + SPStyle *style = SP_OBJECT_STYLE (item); + + if (style && (style->fill.type == SP_PAINT_TYPE_PAINTSERVER)) { + SPObject *server = SP_OBJECT_STYLE_FILL_SERVER (item); + if (SP_IS_PATTERN (server)) { + SPPattern *pattern = sp_pattern_clone_if_necessary (item, SP_PATTERN (server), "fill"); + sp_pattern_transform_multiply (pattern, postmul, set); + } + } + + if (style && (style->stroke.type == SP_PAINT_TYPE_PAINTSERVER)) { + SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER (item); + if (SP_IS_PATTERN (server)) { + SPPattern *pattern = sp_pattern_clone_if_necessary (item, SP_PATTERN (server), "stroke"); + sp_pattern_transform_multiply (pattern, postmul, set); + } + } + +} + +void +sp_item_adjust_gradient (SPItem *item, NR::Matrix const &postmul, bool set) +{ + SPStyle *style = SP_OBJECT_STYLE (item); + + if (style && (style->fill.type == SP_PAINT_TYPE_PAINTSERVER)) { + SPObject *server = SP_OBJECT_STYLE_FILL_SERVER(item); + if (SP_IS_GRADIENT (server)) { + + /** + * \note Bbox units for a gradient are generally a bad idea because + * with them, you cannot preserve the relative position of the + * object and its gradient after rotation or skew. So now we + * convert them to userspace units which are easy to keep in sync + * just by adding the object's transform to gradientTransform. + * \todo FIXME: convert back to bbox units after transforming with + * the item, so as to preserve the original units. + */ + SPGradient *gradient = sp_gradient_convert_to_userspace (SP_GRADIENT (server), item, "fill"); + + sp_gradient_transform_multiply (gradient, postmul, set); + } + } + + if (style && (style->stroke.type == SP_PAINT_TYPE_PAINTSERVER)) { + SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER(item); + if (SP_IS_GRADIENT (server)) { + SPGradient *gradient = sp_gradient_convert_to_userspace (SP_GRADIENT (server), item, "stroke"); + sp_gradient_transform_multiply (gradient, postmul, set); + } + } +} + +void +sp_item_adjust_stroke (SPItem *item, gdouble ex) +{ + SPStyle *style = SP_OBJECT_STYLE (item); + + if (style && style->stroke.type != SP_PAINT_TYPE_NONE && !NR_DF_TEST_CLOSE (ex, 1.0, NR_EPSILON)) { + + style->stroke_width.computed *= ex; + + if (style->stroke_dash.n_dash != 0) { + int i; + for (i = 0; i < style->stroke_dash.n_dash; i++) { + style->stroke_dash.dash[i] *= ex; + } + style->stroke_dash.offset *= ex; + } + + SP_OBJECT(item)->updateRepr(); + } +} + +/** + * Find out the inverse of previous transform of an item (from its repr) + */ +NR::Matrix +sp_item_transform_repr (SPItem *item) +{ + NR::Matrix t_old(NR::identity()); + gchar const *t_attr = SP_OBJECT_REPR(item)->attribute("transform"); + if (t_attr) { + NR::Matrix t; + if (sp_svg_transform_read(t_attr, &t)) { + t_old = t; + } + } + + return t_old; +} + + +/** + * Recursively scale stroke width in \a item and its children by \a expansion. + */ +void +sp_item_adjust_stroke_width_recursive(SPItem *item, double expansion) +{ + sp_item_adjust_stroke (item, expansion); + + for (SPObject *o = SP_OBJECT(item)->children; o != NULL; o = o->next) { + if (SP_IS_ITEM(o)) + sp_item_adjust_stroke_width_recursive(SP_ITEM(o), expansion); + } +} + +/** + * Recursively adjust rx and ry of rects. + */ +void +sp_item_adjust_rects_recursive(SPItem *item, NR::Matrix advertized_transform) +{ + if (SP_IS_RECT (item)) { + sp_rect_compensate_rxry (SP_RECT(item), advertized_transform); + } + + for (SPObject *o = SP_OBJECT(item)->children; o != NULL; o = o->next) { + if (SP_IS_ITEM(o)) + sp_item_adjust_rects_recursive(SP_ITEM(o), advertized_transform); + } +} + +/** + * Recursively compensate pattern or gradient transform. + */ +void +sp_item_adjust_paint_recursive (SPItem *item, NR::Matrix advertized_transform, NR::Matrix t_ancestors, bool is_pattern) +{ +// A clone must not touch the style (it's that of its parent!) and has no children, so quit now + if (item && SP_IS_USE(item)) + return; + +// _Before_ full pattern/gradient transform: t_paint * t_item * t_ancestors +// _After_ full pattern/gradient transform: t_paint_new * t_item * t_ancestors * advertised_transform +// By equating these two expressions we get t_paint_new = t_paint * paint_delta, where: + NR::Matrix t_item = sp_item_transform_repr (item); + NR::Matrix paint_delta = t_item * t_ancestors * advertized_transform * t_ancestors.inverse() * t_item.inverse(); + + if (is_pattern) + sp_item_adjust_pattern (item, paint_delta); + else + sp_item_adjust_gradient (item, paint_delta); + +// Within text, we do not fork gradients, and so must not recurse to avoid double compensation + if (item && SP_IS_TEXT(item)) + return; + + for (SPObject *o = SP_OBJECT(item)->children; o != NULL; o = o->next) { + if (SP_IS_ITEM(o)) { +// At the level of the transformed item, t_ancestors is identity; +// below it, it is the accmmulated chain of transforms from this level to the top level + sp_item_adjust_paint_recursive (SP_ITEM(o), advertized_transform, t_item * t_ancestors, is_pattern); + } + } +} + +/** + * A temporary wrapper for the next function accepting the NRMatrix + * instead of NR::Matrix + */ +void +sp_item_write_transform(SPItem *item, Inkscape::XML::Node *repr, NRMatrix const *transform, NR::Matrix const *adv) +{ + if (transform == NULL) + sp_item_write_transform(item, repr, NR::identity(), adv); + else + sp_item_write_transform(item, repr, NR::Matrix (transform), adv); +} + +/** + * Set a new transform on an object. + * + * Compensate for stroke scaling and gradient/pattern fill transform, if + * necessary. Call the object's set_transform method if transforms are + * stored optimized. Send _transformed_signal. Invoke _write method so that + * the repr is updated with the new transform. + */ +void +sp_item_write_transform(SPItem *item, Inkscape::XML::Node *repr, NR::Matrix const &transform, NR::Matrix const *adv) +{ + g_return_if_fail(item != NULL); + g_return_if_fail(SP_IS_ITEM(item)); + g_return_if_fail(repr != NULL); + + // calculate the relative transform, if not given by the adv attribute + NR::Matrix advertized_transform; + if (adv != NULL) { + advertized_transform = *adv; + } else { + advertized_transform = sp_item_transform_repr (item).inverse() * transform; + } + + // recursively compensate for stroke scaling, depending on user preference + if (prefs_get_int_attribute("options.transform", "stroke", 1) == 0) { + double const expansion = 1. / NR::expansion(advertized_transform); + sp_item_adjust_stroke_width_recursive(item, expansion); + } + + // recursively compensate rx/ry of a rect if requested + if (prefs_get_int_attribute("options.transform", "rectcorners", 1) == 0) { + sp_item_adjust_rects_recursive(item, advertized_transform); + } + + // recursively compensate pattern fill if it's not to be transformed + if (prefs_get_int_attribute("options.transform", "pattern", 1) == 0) { + sp_item_adjust_paint_recursive (item, advertized_transform.inverse(), NR::identity(), true); + } + /// \todo FIXME: add the same else branch as for gradients below, to convert patterns to userSpaceOnUse as well + /// recursively compensate gradient fill if it's not to be transformed + if (prefs_get_int_attribute("options.transform", "gradient", 1) == 0) { + sp_item_adjust_paint_recursive (item, advertized_transform.inverse(), NR::identity(), false); + } else { + // this converts the gradient/pattern fill/stroke, if any, to userSpaceOnUse; we need to do + // it here _before_ the new transform is set, so as to use the pre-transform bbox + sp_item_adjust_paint_recursive (item, NR::identity(), NR::identity(), false); + } + + // run the object's set_transform if transforms are stored optimized + gint preserve = prefs_get_int_attribute("options.preservetransform", "value", 0); + NR::Matrix transform_attr (transform); + if (((SPItemClass *) G_OBJECT_GET_CLASS(item))->set_transform && !preserve) { + transform_attr = ((SPItemClass *) G_OBJECT_GET_CLASS(item))->set_transform(item, transform); + } + sp_item_set_item_transform(item, transform_attr); + + // Note: updateRepr comes before emitting the transformed signal since + // it causes clone SPUse's copy of the original object to brought up to + // date with the original. Otherwise, sp_use_bbox returns incorrect + // values if called in code handling the transformed signal. + SP_OBJECT(item)->updateRepr(); + + // send the relative transform with a _transformed_signal + item->_transformed_signal.emit(&advertized_transform, item); +} + +gint +sp_item_event(SPItem *item, SPEvent *event) +{ + g_return_val_if_fail(item != NULL, FALSE); + g_return_val_if_fail(SP_IS_ITEM(item), FALSE); + g_return_val_if_fail(event != NULL, FALSE); + + if (((SPItemClass *) G_OBJECT_GET_CLASS(item))->event) + return ((SPItemClass *) G_OBJECT_GET_CLASS(item))->event(item, event); + + return FALSE; +} + +/** + * Sets item private transform (not propagated to repr), without compensating stroke widths, + * gradients, patterns as sp_item_write_transform does. + */ +void +sp_item_set_item_transform(SPItem *item, NR::Matrix const &transform) +{ + g_return_if_fail(item != NULL); + g_return_if_fail(SP_IS_ITEM(item)); + + if (!matrix_equalp(transform, item->transform, NR_EPSILON)) { + item->transform = transform; + /* The SP_OBJECT_USER_MODIFIED_FLAG_B is used to mark the fact that it's only a + transformation. It's apparently not used anywhere else. */ + item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_USER_MODIFIED_FLAG_B); + sp_item_rm_unsatisfied_cns(*item); + } +} + + +/** + * \pre \a ancestor really is an ancestor (\>=) of \a object, or NULL. + * ("Ancestor (\>=)" here includes as far as \a object itself.) + */ +NR::Matrix +i2anc_affine(SPObject const *object, SPObject const *const ancestor) { + NR::Matrix ret(NR::identity()); + g_return_val_if_fail(object != NULL, ret); + + /* stop at first non-renderable ancestor */ + while ( object != ancestor && SP_IS_ITEM(object) ) { + if (SP_IS_ROOT(object)) { + ret *= SP_ROOT(object)->c2p; + } + ret *= SP_ITEM(object)->transform; + object = SP_OBJECT_PARENT(object); + } + return ret; +} + +NR::Matrix +i2i_affine(SPObject const *src, SPObject const *dest) { + g_return_val_if_fail(src != NULL && dest != NULL, NR::identity()); + SPObject const *ancestor = src->nearestCommonAncestor(dest); + return i2anc_affine(src, ancestor) / i2anc_affine(dest, ancestor); +} + +NR::Matrix SPItem::getRelativeTransform(SPObject const *dest) const { + return i2i_affine(this, dest); +} + +/** + * Returns the accumulated transformation of the item and all its ancestors, including root's viewport. + * \pre (item != NULL) and SP_IS_ITEM(item). + */ +NR::Matrix sp_item_i2doc_affine(SPItem const *item) +{ + return i2anc_affine(item, NULL); +} + +/** + * Returns the accumulated transformation of the item and all its ancestors, but excluding root's viewport. + * Used in path operations mostly. + * \pre (item != NULL) and SP_IS_ITEM(item). + */ +NR::Matrix sp_item_i2root_affine(SPItem const *item) +{ + g_assert(item != NULL); + g_assert(SP_IS_ITEM(item)); + + NR::Matrix ret(NR::identity()); + g_assert(ret.test_identity()); + while ( NULL != SP_OBJECT_PARENT(item) ) { + ret *= item->transform; + item = SP_ITEM(SP_OBJECT_PARENT(item)); + } + g_assert(SP_IS_ROOT(item)); + + ret *= item->transform; + + return ret; +} + +/* fixme: This is EVIL!!! */ + +NR::Matrix sp_item_i2d_affine(SPItem const *item) +{ + g_assert(item != NULL); + g_assert(SP_IS_ITEM(item)); + + NR::Matrix const ret( sp_item_i2doc_affine(item) + * NR::scale(1, -1) + * NR::translate(0, sp_document_height(SP_OBJECT_DOCUMENT(item))) ); + return ret; +} + +// same as i2d but with i2root instead of i2doc +NR::Matrix sp_item_i2r_affine(SPItem const *item) +{ + g_assert(item != NULL); + g_assert(SP_IS_ITEM(item)); + + NR::Matrix const ret( sp_item_i2root_affine(item) + * NR::scale(1, -1) + * NR::translate(0, sp_document_height(SP_OBJECT_DOCUMENT(item))) ); + return ret; +} + +/** + * Converts a matrix \a m into the desktop coords of the \a item. + * Will become a noop when we eliminate the coordinate flipping. + */ +NR::Matrix matrix_to_desktop(NR::Matrix const m, SPItem const *item) +{ + NR::Matrix const ret(m + * NR::translate(0, -sp_document_height(SP_OBJECT_DOCUMENT(item))) + * NR::scale(1, -1)); + return ret; +} + +/** + * Converts a matrix \a m from the desktop coords of the \a item. + * Will become a noop when we eliminate the coordinate flipping. + */ +NR::Matrix matrix_from_desktop(NR::Matrix const m, SPItem const *item) +{ + NR::Matrix const ret(NR::scale(1, -1) + * NR::translate(0, sp_document_height(SP_OBJECT_DOCUMENT(item))) + * m); + return ret; +} + +void sp_item_set_i2d_affine(SPItem *item, NR::Matrix const &i2dt) +{ + g_return_if_fail( item != NULL ); + g_return_if_fail( SP_IS_ITEM(item) ); + + NR::Matrix dt2p; /* desktop to item parent transform */ + if (SP_OBJECT_PARENT(item)) { + dt2p = sp_item_i2d_affine((SPItem *) SP_OBJECT_PARENT(item)).inverse(); + } else { + dt2p = ( NR::translate(0, -sp_document_height(SP_OBJECT_DOCUMENT(item))) + * NR::scale(1, -1) ); + } + + NR::Matrix const i2p( i2dt * dt2p ); + sp_item_set_item_transform(item, i2p); +} + + +NR::Matrix +sp_item_dt2i_affine(SPItem const *item) +{ + /* fixme: Implement the right way (Lauris) */ + return sp_item_i2d_affine(item).inverse(); +} + +/* Item views */ + +static SPItemView * +sp_item_view_new_prepend(SPItemView *list, SPItem *item, unsigned flags, unsigned key, NRArenaItem *arenaitem) +{ + SPItemView *new_view; + + g_assert(item != NULL); + g_assert(SP_IS_ITEM(item)); + g_assert(arenaitem != NULL); + g_assert(NR_IS_ARENA_ITEM(arenaitem)); + + new_view = g_new(SPItemView, 1); + + new_view->next = list; + new_view->flags = flags; + new_view->key = key; + new_view->arenaitem = nr_arena_item_ref(arenaitem); + + return new_view; +} + +static SPItemView * +sp_item_view_list_remove(SPItemView *list, SPItemView *view) +{ + if (view == list) { + list = list->next; + } else { + SPItemView *prev; + prev = list; + while (prev->next != view) prev = prev->next; + prev->next = view->next; + } + + nr_arena_item_unref(view->arenaitem); + g_free(view); + + return list; +} + +/** + * Return the arenaitem corresponding to the given item in the display + * with the given key + */ +NRArenaItem * +sp_item_get_arenaitem(SPItem *item, unsigned key) +{ + for ( SPItemView *iv = item->display ; iv ; iv = iv->next ) { + if ( iv->key == key ) { + return iv->arenaitem; + } + } + + return NULL; +} + +int +sp_item_repr_compare_position(SPItem *first, SPItem *second) +{ + return sp_repr_compare_position(SP_OBJECT_REPR(first), + SP_OBJECT_REPR(second)); +} + +SPItem * +sp_item_first_item_child (SPObject *obj) +{ + for ( SPObject *iter = sp_object_first_child(obj) ; iter ; iter = SP_OBJECT_NEXT(iter)) { + if (SP_IS_ITEM (iter)) + return SP_ITEM (iter); + } + return NULL; +} + + +/* + 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/sp-item.h b/src/sp-item.h new file mode 100644 index 000000000..326ea2759 --- /dev/null +++ b/src/sp-item.h @@ -0,0 +1,246 @@ +#ifndef __SP_ITEM_H__ +#define __SP_ITEM_H__ + +/** \file + * Some things pertinent to all visible shapes: SPItem, SPItemView, SPItemCtx, SPItemClass, SPEvent. + */ + +/* + * Authors: + * Lauris Kaplinski + * bulia byak + * + * Copyright (C) 1999-2005 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * Copyright (C) 2004 Monash University + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include + +#include "display/nr-arena-forward.h" +#include "forward.h" +#include "sp-object.h" +#include + +namespace Inkscape { class URIReference; } +class SPGuideConstraint; + +enum { + SP_EVENT_INVALID, + SP_EVENT_NONE, + SP_EVENT_ACTIVATE, + SP_EVENT_MOUSEOVER, + SP_EVENT_MOUSEOUT +}; + +/** + * Event structure. + * + * \todo This is just placeholder. Plan: + * We do extensible event structure, that hold applicable (ui, non-ui) + * data pointers. So it is up to given object/arena implementation + * to process correct ones in meaningful way. + * Also, this probably goes to SPObject base class. + * + */ +struct SPEvent { + unsigned int type; + gpointer data; +}; + +class SPItemView; + +/// SPItemView +struct SPItemView { + SPItemView *next; + unsigned int flags; + unsigned int key; + NRArenaItem *arenaitem; +}; + +/* flags */ + +#define SP_ITEM_BBOX_VISUAL 1 + +#define SP_ITEM_SHOW_DISPLAY (1 << 0) + +/** + * Flag for referenced views (i.e. clippaths, masks and patterns); always display + */ +#define SP_ITEM_REFERENCE_FLAGS SP_ITEM_SHOW_DISPLAY + +class SPItemCtx; + +/// Contains transformations to document/viewport and the viewport size. +struct SPItemCtx { + SPCtx ctx; + /** Item to document transformation */ + NR::Matrix i2doc; + /** Viewport size */ + NRRect vp; + /** Item to viewport transformation */ + NR::Matrix i2vp; +}; + +/** Abstract base class for all visible shapes. */ +struct SPItem : public SPObject { + unsigned int sensitive : 1; + unsigned int stop_paint: 1; + double r_cx; + double r_cy; + + NR::Matrix transform; + + SPClipPathReference *clip_ref; + SPMaskReference *mask_ref; + + // Used for object-avoiding connectors + SPAvoidRef *avoidRef; + + SPItemView *display; + + std::vector constraints; + + sigc::signal _transformed_signal; + + bool isLocked() const; + void setLocked(bool lock); + + bool isHidden() const; + void setHidden(bool hidden); + + bool isHidden(unsigned display_key) const; + + bool isExplicitlyHidden() const; + + void setExplicitlyHidden(bool val); + + bool isVisibleAndUnlocked() const; + + bool isVisibleAndUnlocked(unsigned display_key) const; + + NR::Matrix getRelativeTransform(SPObject const *obj) const; + + void raiseOne(); + void lowerOne(); + void raiseToTop(); + void lowerToBottom(); + + NR::Rect invokeBbox(NR::Matrix const &transform) const; + + sigc::connection connectTransformed(sigc::slot slot) { + return _transformed_signal.connect(slot); + } +}; + +typedef std::back_insert_iterator > SnapPointsIter; + +/// The SPItem vtable. +struct SPItemClass { + SPObjectClass parent_class; + + /** BBox union in given coordinate system */ + void (* bbox) (SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const flags); + + /** Printing method. Assumes ctm is set to item affine matrix */ + /* \todo Think about it, and maybe implement generic export method instead (Lauris) */ + void (* print) (SPItem *item, SPPrintContext *ctx); + + /** Give short description of item (for status display) */ + gchar * (* description) (SPItem * item); + + NRArenaItem * (* show) (SPItem *item, NRArena *arena, unsigned int key, unsigned int flags); + void (* hide) (SPItem *item, unsigned int key); + + /** Write to an iterator the points that should be considered for snapping + * as the item's `nodes'. + */ + void (* snappoints) (SPItem const *item, SnapPointsIter p); + + /** Apply the transform optimally, and return any residual transformation */ + NR::Matrix (* set_transform)(SPItem *item, NR::Matrix const &transform); + + /** Emit event, if applicable */ + gint (* event) (SPItem *item, SPEvent *event); +}; + +/* Flag testing macros */ + +#define SP_ITEM_STOP_PAINT(i) (SP_ITEM (i)->stop_paint) + +/* Methods */ + +void sp_item_invoke_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const clear); +void sp_item_invoke_bbox_full(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const flags, unsigned const clear); + +unsigned sp_item_pos_in_parent(SPItem *item); + +gchar *sp_item_description(SPItem * item); +void sp_item_invoke_print(SPItem *item, SPPrintContext *ctx); + +/** Shows/Hides item on given arena display list */ +unsigned int sp_item_display_key_new(unsigned int numkeys); +NRArenaItem *sp_item_invoke_show(SPItem *item, NRArena *arena, unsigned int key, unsigned int flags); +void sp_item_invoke_hide(SPItem *item, unsigned int key); + +void sp_item_snappoints(SPItem const *item, SnapPointsIter p); + +void sp_item_adjust_pattern(SPItem *item, /* NR::Matrix const &premul, */ NR::Matrix const &postmul, bool set = false); +void sp_item_adjust_gradient(SPItem *item, /* NR::Matrix const &premul, */ NR::Matrix const &postmul, bool set = false); +void sp_item_adjust_stroke(SPItem *item, gdouble ex); +void sp_item_adjust_stroke_width_recursive(SPItem *item, gdouble ex); +void sp_item_adjust_paint_recursive(SPItem *item, NR::Matrix advertized_transform, NR::Matrix t_ancestors, bool is_pattern); + +void sp_item_write_transform(SPItem *item, Inkscape::XML::Node *repr, NRMatrix const *transform, NR::Matrix const *adv = NULL); +void sp_item_write_transform(SPItem *item, Inkscape::XML::Node *repr, NR::Matrix const &transform, NR::Matrix const *adv = NULL); + +void sp_item_set_item_transform(SPItem *item, NR::Matrix const &transform); + +gint sp_item_event (SPItem *item, SPEvent *event); + +/* Utility */ + +NRArenaItem *sp_item_get_arenaitem(SPItem *item, unsigned int key); + +void sp_item_bbox_desktop(SPItem *item, NRRect *bbox) __attribute__ ((deprecated)); +NR::Rect sp_item_bbox_desktop(SPItem *item); + +NR::Matrix i2anc_affine(SPObject const *item, SPObject const *ancestor); +NR::Matrix i2i_affine(SPObject const *src, SPObject const *dest); + +NR::Matrix sp_item_i2doc_affine(SPItem const *item); +NR::Matrix sp_item_i2root_affine(SPItem const *item); + +NR::Matrix matrix_to_desktop (NR::Matrix m, SPItem const *item); +NR::Matrix matrix_from_desktop (NR::Matrix m, SPItem const *item); + +/* fixme: - these are evil, but OK */ + +/* Fill *TRANSFORM with the item-to-desktop transform. See doc/coordinates.txt + * for a description of `Desktop coordinates'; though see also mental's comment + * at the top of that file. + * + * \return TRANSFORM. + */ +NR::Matrix sp_item_i2d_affine(SPItem const *item); +NR::Matrix sp_item_i2r_affine(SPItem const *item); +void sp_item_set_i2d_affine(SPItem *item, NR::Matrix const &transform); +NR::Matrix sp_item_dt2i_affine(SPItem const *item); +int sp_item_repr_compare_position(SPItem *first, SPItem *second); +SPItem *sp_item_first_item_child (SPObject *obj); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/sp-line.cpp b/src/sp-line.cpp new file mode 100644 index 000000000..cca470530 --- /dev/null +++ b/src/sp-line.cpp @@ -0,0 +1,227 @@ +#define __SP_LINE_C__ + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include +#endif +#include "attributes.h" +#include "style.h" +#include "sp-line.h" +#include "display/curve.h" +#include +#include +#include + +static void sp_line_class_init (SPLineClass *klass); +static void sp_line_init (SPLine *line); + +static void sp_line_build (SPObject * object, SPDocument * document, Inkscape::XML::Node * repr); +static void sp_line_set (SPObject *object, unsigned int key, const gchar *value); +static Inkscape::XML::Node *sp_line_write (SPObject *object, Inkscape::XML::Node *repr, guint flags); + +static gchar *sp_line_description (SPItem * item); +static NR::Matrix sp_line_set_transform(SPItem *item, NR::Matrix const &xform); + +static void sp_line_update (SPObject *object, SPCtx *ctx, guint flags); +static void sp_line_set_shape (SPShape *shape); + +static SPShapeClass *parent_class; + +GType +sp_line_get_type (void) +{ + static GType line_type = 0; + + if (!line_type) { + GTypeInfo line_info = { + sizeof (SPLineClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_line_class_init, + NULL, /* klass_finalize */ + NULL, /* klass_data */ + sizeof (SPLine), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_line_init, + NULL, /* value_table */ + }; + line_type = g_type_register_static (SP_TYPE_SHAPE, "SPLine", &line_info, (GTypeFlags)0); + } + return line_type; +} + +static void +sp_line_class_init (SPLineClass *klass) +{ + parent_class = (SPShapeClass *) g_type_class_ref (SP_TYPE_SHAPE); + + SPObjectClass *sp_object_class = (SPObjectClass *) klass; + sp_object_class->build = sp_line_build; + sp_object_class->set = sp_line_set; + sp_object_class->write = sp_line_write; + + SPItemClass *item_class = (SPItemClass *) klass; + item_class->description = sp_line_description; + item_class->set_transform = sp_line_set_transform; + + sp_object_class->update = sp_line_update; + + SPShapeClass *shape_class = (SPShapeClass *) klass; + shape_class->set_shape = sp_line_set_shape; +} + +static void +sp_line_init (SPLine * line) +{ + line->x1.unset(); + line->y1.unset(); + line->x2.unset(); + line->y2.unset(); +} + + +static void +sp_line_build (SPObject * object, SPDocument * document, Inkscape::XML::Node * repr) +{ + if (((SPObjectClass *) parent_class)->build) { + ((SPObjectClass *) parent_class)->build (object, document, repr); + } + + sp_object_read_attr (object, "x1"); + sp_object_read_attr (object, "y1"); + sp_object_read_attr (object, "x2"); + sp_object_read_attr (object, "y2"); +} + +static void +sp_line_set (SPObject *object, unsigned int key, const gchar *value) +{ + SPLine * line = SP_LINE (object); + + /* fixme: we should really collect updates */ + + switch (key) { + case SP_ATTR_X1: + line->x1.readOrUnset(value); + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_Y1: + line->y1.readOrUnset(value); + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_X2: + line->x2.readOrUnset(value); + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_Y2: + line->y2.readOrUnset(value); + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + default: + if (((SPObjectClass *) parent_class)->set) + ((SPObjectClass *) parent_class)->set (object, key, value); + break; + } +} + +static void +sp_line_update (SPObject *object, SPCtx *ctx, guint flags) +{ + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + SPLine *line = SP_LINE (object); + + SPStyle const *style = object->style; + double const d = 1.0 / NR::expansion(((SPItemCtx const *) ctx)->i2vp); + double const em = style->font_size.computed; + double const ex = em * 0.5; // fixme: get from pango or libnrtype. + line->x1.update(em, ex, d); + line->x2.update(em, ex, d); + line->y1.update(em, ex, d); + line->y2.update(em, ex, d); + + sp_shape_set_shape ((SPShape *) object); + } + + if (((SPObjectClass *) parent_class)->update) + ((SPObjectClass *) parent_class)->update (object, ctx, flags); +} + + +static Inkscape::XML::Node * +sp_line_write (SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + SPLine *line = SP_LINE (object); + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = sp_repr_new ("svg:line"); + } + + if (repr != SP_OBJECT_REPR (object)) { + repr->mergeFrom(SP_OBJECT_REPR (object), "id"); + } + + sp_repr_set_svg_double(repr, "x1", line->x1.computed); + sp_repr_set_svg_double(repr, "y1", line->y1.computed); + sp_repr_set_svg_double(repr, "x2", line->x2.computed); + sp_repr_set_svg_double(repr, "y2", line->y2.computed); + + if (((SPObjectClass *) (parent_class))->write) + ((SPObjectClass *) (parent_class))->write (object, repr, flags); + + return repr; +} + +static gchar * +sp_line_description(SPItem *item) +{ + return g_strdup(_("Line")); +} + +static NR::Matrix +sp_line_set_transform (SPItem *item, NR::Matrix const &xform) +{ + SPLine *line = SP_LINE (item); + NR::Point points[2]; + + points[0] = NR::Point(line->x1.computed, line->y1.computed); + points[1] = NR::Point(line->x2.computed, line->y2.computed); + + points[0] *= xform; + points[1] *= xform; + + line->x1.computed = points[0][NR::X]; + line->y1.computed = points[0][NR::Y]; + line->x2.computed = points[1][NR::X]; + line->y2.computed = points[1][NR::Y]; + + sp_item_adjust_stroke(item, NR::expansion(xform)); + + SP_OBJECT (item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + + return NR::identity(); +} + +static void +sp_line_set_shape (SPShape *shape) +{ + SPLine *line = SP_LINE (shape); + + SPCurve *c = sp_curve_new (); + + sp_curve_moveto (c, line->x1.computed, line->y1.computed); + sp_curve_lineto (c, line->x2.computed, line->y2.computed); + + sp_shape_set_curve_insync (shape, c, TRUE); // *_insync does not call update, avoiding infinite recursion when set_shape is called by update + + sp_curve_unref (c); +} diff --git a/src/sp-line.h b/src/sp-line.h new file mode 100644 index 000000000..cc6802b7f --- /dev/null +++ b/src/sp-line.h @@ -0,0 +1,44 @@ +#ifndef __SP_LINE_H__ +#define __SP_LINE_H__ + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "svg/svg-length.h" +#include "sp-shape.h" + + + +#define SP_TYPE_LINE (sp_line_get_type ()) +#define SP_LINE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_LINE, SPLine)) +#define SP_LINE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_LINE, SPLineClass)) +#define SP_IS_LINE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_LINE)) +#define SP_IS_LINE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_LINE)) + +class SPLine; +class SPLineClass; + +struct SPLine : public SPShape { + SVGLength x1; + SVGLength y1; + SVGLength x2; + SVGLength y2; +}; + +struct SPLineClass { + SPShapeClass parent_class; +}; + +GType sp_line_get_type (void); + + + +#endif diff --git a/src/sp-linear-gradient-fns.h b/src/sp-linear-gradient-fns.h new file mode 100644 index 000000000..0962bae35 --- /dev/null +++ b/src/sp-linear-gradient-fns.h @@ -0,0 +1,40 @@ +#ifndef SP_LINEAR_GRADIENT_FNS_H +#define SP_LINEAR_GRADIENT_FNS_H + +/** \file + * Macros and fn declarations related to linear gradients. + */ + +#include +#include + +namespace Inkscape { +namespace XML { +class Node; +} +} + +class SPLinearGradient; + +#define SP_TYPE_LINEARGRADIENT (sp_lineargradient_get_type()) +#define SP_LINEARGRADIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SP_TYPE_LINEARGRADIENT, SPLinearGradient)) +#define SP_LINEARGRADIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SP_TYPE_LINEARGRADIENT, SPLinearGradientClass)) +#define SP_IS_LINEARGRADIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SP_TYPE_LINEARGRADIENT)) +#define SP_IS_LINEARGRADIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SP_TYPE_LINEARGRADIENT)) + +GType sp_lineargradient_get_type(); + +void sp_lineargradient_set_position(SPLinearGradient *lg, gdouble x1, gdouble y1, gdouble x2, gdouble y2); + +#endif /* !SP_LINEAR_GRADIENT_FNS_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:encoding=utf-8:textwidth=99 : diff --git a/src/sp-linear-gradient.h b/src/sp-linear-gradient.h new file mode 100644 index 000000000..eabf7f308 --- /dev/null +++ b/src/sp-linear-gradient.h @@ -0,0 +1,36 @@ +#ifndef SP_LINEAR_GRADIENT_H +#define SP_LINEAR_GRADIENT_H + +/** \file + * SPLinearGradient: SVG implementation + */ + +#include "sp-gradient.h" +#include "svg/svg-length.h" +#include "sp-linear-gradient-fns.h" + +/** Linear gradient. */ +struct SPLinearGradient : public SPGradient { + SVGLength x1; + SVGLength y1; + SVGLength x2; + SVGLength y2; +}; + +/// The SPLinearGradient vtable. +struct SPLinearGradientClass { + SPGradientClass parent_class; +}; + +#endif /* !SP_LINEAR_GRADIENT_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:encoding=utf-8:textwidth=99 : diff --git a/src/sp-marker-loc.h b/src/sp-marker-loc.h new file mode 100644 index 000000000..1b763f724 --- /dev/null +++ b/src/sp-marker-loc.h @@ -0,0 +1,29 @@ +#ifndef SEEN_SP_MARKER_LOC_H +#define SEEN_SP_MARKER_LOC_H + +/** + * These enums are to allow us to have 4-element arrays that represent a set of marker locations + * (all, start, mid, and end). This allows us to iterate through the array in places where we need + * to do a process across all of the markers, instead of separate code stanzas for each. + */ +enum SPMarkerLoc { + SP_MARKER_LOC, + SP_MARKER_LOC_START, + SP_MARKER_LOC_MID, + SP_MARKER_LOC_END, + SP_MARKER_LOC_QTY +}; + + +#endif /* !SEEN_SP_MARKER_LOC_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 : diff --git a/src/sp-marker.cpp b/src/sp-marker.cpp new file mode 100644 index 000000000..0c4c9215b --- /dev/null +++ b/src/sp-marker.cpp @@ -0,0 +1,639 @@ +#define __SP_MARKER_C__ + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2003 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "config.h" + + +#include "libnr/nr-matrix-fns.h" +#include "libnr/nr-matrix-ops.h" +#include "libnr/nr-scale-matrix-ops.h" +#include "libnr/nr-rotate-fns.h" +#include "svg/svg.h" +#include "display/nr-arena-group.h" +#include "xml/repr.h" +#include "attributes.h" +#include "sp-marker.h" + +struct SPMarkerView { + SPMarkerView *next; + unsigned int key; + unsigned int size; + NRArenaItem *items[1]; +}; + +static void sp_marker_class_init (SPMarkerClass *klass); +static void sp_marker_init (SPMarker *marker); + +static void sp_marker_build (SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); +static void sp_marker_release (SPObject *object); +static void sp_marker_set (SPObject *object, unsigned int key, const gchar *value); +static void sp_marker_update (SPObject *object, SPCtx *ctx, guint flags); +static Inkscape::XML::Node *sp_marker_write (SPObject *object, Inkscape::XML::Node *repr, guint flags); + +static NRArenaItem *sp_marker_private_show (SPItem *item, NRArena *arena, unsigned int key, unsigned int flags); +static void sp_marker_private_hide (SPItem *item, unsigned int key); +static void sp_marker_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const flags); +static void sp_marker_print (SPItem *item, SPPrintContext *ctx); + +static void sp_marker_view_remove (SPMarker *marker, SPMarkerView *view, unsigned int destroyitems); + +static SPGroupClass *parent_class; + +GType +sp_marker_get_type (void) +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof (SPMarkerClass), + NULL, NULL, + (GClassInitFunc) sp_marker_class_init, + NULL, NULL, + sizeof (SPMarker), + 16, + (GInstanceInitFunc) sp_marker_init, + NULL, /* value_table */ + }; + type = g_type_register_static (SP_TYPE_GROUP, "SPMarker", &info, (GTypeFlags)0); + } + return type; +} + +static void +sp_marker_class_init (SPMarkerClass *klass) +{ + GObjectClass *object_class; + SPObjectClass *sp_object_class; + SPItemClass *sp_item_class; + + object_class = G_OBJECT_CLASS (klass); + sp_object_class = (SPObjectClass *) klass; + sp_item_class = (SPItemClass *) klass; + + parent_class = (SPGroupClass *)g_type_class_ref (SP_TYPE_GROUP); + + sp_object_class->build = sp_marker_build; + sp_object_class->release = sp_marker_release; + sp_object_class->set = sp_marker_set; + sp_object_class->update = sp_marker_update; + sp_object_class->write = sp_marker_write; + + sp_item_class->show = sp_marker_private_show; + sp_item_class->hide = sp_marker_private_hide; + sp_item_class->bbox = sp_marker_bbox; + sp_item_class->print = sp_marker_print; +} + +static void +sp_marker_init (SPMarker *marker) +{ + marker->viewBox_set = FALSE; + + nr_matrix_set_identity (&marker->c2p); +} + +static void +sp_marker_build (SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + SPGroup *group; + SPMarker *marker; + + group = (SPGroup *) object; + marker = (SPMarker *) object; + + sp_object_read_attr (object, "markerUnits"); + sp_object_read_attr (object, "refX"); + sp_object_read_attr (object, "refY"); + sp_object_read_attr (object, "markerWidth"); + sp_object_read_attr (object, "markerHeight"); + sp_object_read_attr (object, "orient"); + sp_object_read_attr (object, "viewBox"); + sp_object_read_attr (object, "preserveAspectRatio"); + + if (((SPObjectClass *) parent_class)->build) + ((SPObjectClass *) parent_class)->build (object, document, repr); +} + +static void +sp_marker_release (SPObject *object) +{ + SPMarker *marker; + + marker = (SPMarker *) object; + + while (marker->views) { + /* Destroy all NRArenaitems etc. */ + /* Parent class ::hide method */ + ((SPItemClass *) parent_class)->hide ((SPItem *) marker, marker->views->key); + sp_marker_view_remove (marker, marker->views, TRUE); + } + + if (((SPObjectClass *) parent_class)->release) + ((SPObjectClass *) parent_class)->release (object); +} + +static void +sp_marker_set (SPObject *object, unsigned int key, const gchar *value) +{ + SPItem *item; + SPMarker *marker; + + item = SP_ITEM (object); + marker = SP_MARKER (object); + + switch (key) { + case SP_ATTR_MARKERUNITS: + marker->markerUnits_set = FALSE; + marker->markerUnits = SP_MARKER_UNITS_STROKEWIDTH; + if (value) { + if (!strcmp (value, "strokeWidth")) { + marker->markerUnits_set = TRUE; + } else if (!strcmp (value, "userSpaceOnUse")) { + marker->markerUnits = SP_MARKER_UNITS_USERSPACEONUSE; + marker->markerUnits_set = TRUE; + } + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); + break; + case SP_ATTR_REFX: + marker->refX.readOrUnset(value); + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_REFY: + marker->refY.readOrUnset(value); + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_MARKERWIDTH: + marker->markerWidth.readOrUnset(value, SVGLength::NONE, 3.0, 3.0); + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_MARKERHEIGHT: + marker->markerHeight.readOrUnset(value, SVGLength::NONE, 3.0, 3.0); + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_ORIENT: + marker->orient_set = FALSE; + marker->orient_auto = FALSE; + marker->orient = 0.0; + if (value) { + if (!strcmp (value, "auto")) { + marker->orient_auto = TRUE; + marker->orient_set = TRUE; + } else if (sp_svg_number_read_f (value, &marker->orient)) { + marker->orient_set = TRUE; + } + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_VIEWBOX: + marker->viewBox_set = FALSE; + if (value) { + double x, y, width, height; + char *eptr; + /* fixme: We have to take original item affine into account */ + /* fixme: Think (Lauris) */ + eptr = (gchar *) value; + x = g_ascii_strtod (eptr, &eptr); + while (*eptr && ((*eptr == ',') || (*eptr == ' '))) eptr++; + y = g_ascii_strtod (eptr, &eptr); + while (*eptr && ((*eptr == ',') || (*eptr == ' '))) eptr++; + width = g_ascii_strtod (eptr, &eptr); + while (*eptr && ((*eptr == ',') || (*eptr == ' '))) eptr++; + height = g_ascii_strtod (eptr, &eptr); + while (*eptr && ((*eptr == ',') || (*eptr == ' '))) eptr++; + if ((width > 0) && (height > 0)) { + /* Set viewbox */ + marker->viewBox.x0 = x; + marker->viewBox.y0 = y; + marker->viewBox.x1 = x + width; + marker->viewBox.y1 = y + height; + marker->viewBox_set = TRUE; + } + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); + break; + case SP_ATTR_PRESERVEASPECTRATIO: + /* Do setup before, so we can use break to escape */ + marker->aspect_set = FALSE; + marker->aspect_align = SP_ASPECT_NONE; + marker->aspect_clip = SP_ASPECT_MEET; + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); + if (value) { + int len; + gchar c[256]; + const gchar *p, *e; + unsigned int align, clip; + p = value; + while (*p && *p == 32) p += 1; + if (!*p) break; + e = p; + while (*e && *e != 32) e += 1; + len = e - p; + if (len > 8) break; + memcpy (c, value, len); + c[len] = 0; + /* Now the actual part */ + if (!strcmp (c, "none")) { + align = SP_ASPECT_NONE; + } else if (!strcmp (c, "xMinYMin")) { + align = SP_ASPECT_XMIN_YMIN; + } else if (!strcmp (c, "xMidYMin")) { + align = SP_ASPECT_XMID_YMIN; + } else if (!strcmp (c, "xMaxYMin")) { + align = SP_ASPECT_XMAX_YMIN; + } else if (!strcmp (c, "xMinYMid")) { + align = SP_ASPECT_XMIN_YMID; + } else if (!strcmp (c, "xMidYMid")) { + align = SP_ASPECT_XMID_YMID; + } else if (!strcmp (c, "xMaxYMin")) { + align = SP_ASPECT_XMAX_YMID; + } else if (!strcmp (c, "xMinYMax")) { + align = SP_ASPECT_XMIN_YMAX; + } else if (!strcmp (c, "xMidYMax")) { + align = SP_ASPECT_XMID_YMAX; + } else if (!strcmp (c, "xMaxYMax")) { + align = SP_ASPECT_XMAX_YMAX; + } else { + break; + } + clip = SP_ASPECT_MEET; + while (*e && *e == 32) e += 1; + if (e) { + if (!strcmp (e, "meet")) { + clip = SP_ASPECT_MEET; + } else if (!strcmp (e, "slice")) { + clip = SP_ASPECT_SLICE; + } else { + break; + } + } + marker->aspect_set = TRUE; + marker->aspect_align = align; + marker->aspect_clip = clip; + } + break; + default: + if (((SPObjectClass *) parent_class)->set) + ((SPObjectClass *) parent_class)->set (object, key, value); + break; + } +} + +/* + * Updating - we are not renderable anyways, so + * we as well cascade with identity transforms + */ + +static void +sp_marker_update (SPObject *object, SPCtx *ctx, guint flags) +{ + SPItem *item; + SPMarker *marker; + SPItemCtx rctx; + NRRect *vb; + double x, y, width, height; + NRMatrix q; + SPMarkerView *v; + + item = SP_ITEM (object); + marker = SP_MARKER (object); + + /* fixme: We have to set up clip here too */ + + /* Copy parent context */ + rctx.ctx = *ctx; + /* Initialize tranformations */ + rctx.i2doc = NR::identity(); + rctx.i2vp = NR::identity(); + /* Set up viewport */ + rctx.vp.x0 = 0.0; + rctx.vp.y0 = 0.0; + rctx.vp.x1 = marker->markerWidth.computed; + rctx.vp.y1 = marker->markerHeight.computed; + + /* Start with identity transform */ + nr_matrix_set_identity (&marker->c2p); + + /* Viewbox is always present, either implicitly or explicitly */ + if (marker->viewBox_set) { + vb = &marker->viewBox; + } else { + vb = &rctx.vp; + } + /* Now set up viewbox transformation */ + /* Determine actual viewbox in viewport coordinates */ + if (marker->aspect_align == SP_ASPECT_NONE) { + x = 0.0; + y = 0.0; + width = rctx.vp.x1 - rctx.vp.x0; + height = rctx.vp.y1 - rctx.vp.y0; + } else { + double scalex, scaley, scale; + /* Things are getting interesting */ + scalex = (rctx.vp.x1 - rctx.vp.x0) / (vb->x1 - vb->x0); + scaley = (rctx.vp.y1 - rctx.vp.y0) / (vb->y1 - vb->y0); + scale = (marker->aspect_clip == SP_ASPECT_MEET) ? MIN (scalex, scaley) : MAX (scalex, scaley); + width = (vb->x1 - vb->x0) * scale; + height = (vb->y1 - vb->y0) * scale; + /* Now place viewbox to requested position */ + switch (marker->aspect_align) { + case SP_ASPECT_XMIN_YMIN: + x = 0.0; + y = 0.0; + break; + case SP_ASPECT_XMID_YMIN: + x = 0.5 * ((rctx.vp.x1 - rctx.vp.x0) - width); + y = 0.0; + break; + case SP_ASPECT_XMAX_YMIN: + x = 1.0 * ((rctx.vp.x1 - rctx.vp.x0) - width); + y = 0.0; + break; + case SP_ASPECT_XMIN_YMID: + x = 0.0; + y = 0.5 * ((rctx.vp.y1 - rctx.vp.y0) - height); + break; + case SP_ASPECT_XMID_YMID: + x = 0.5 * ((rctx.vp.x1 - rctx.vp.x0) - width); + y = 0.5 * ((rctx.vp.y1 - rctx.vp.y0) - height); + break; + case SP_ASPECT_XMAX_YMID: + x = 1.0 * ((rctx.vp.x1 - rctx.vp.x0) - width); + y = 0.5 * ((rctx.vp.y1 - rctx.vp.y0) - height); + break; + case SP_ASPECT_XMIN_YMAX: + x = 0.0; + y = 1.0 * ((rctx.vp.y1 - rctx.vp.y0) - height); + break; + case SP_ASPECT_XMID_YMAX: + x = 0.5 * ((rctx.vp.x1 - rctx.vp.x0) - width); + y = 1.0 * ((rctx.vp.y1 - rctx.vp.y0) - height); + break; + case SP_ASPECT_XMAX_YMAX: + x = 1.0 * ((rctx.vp.x1 - rctx.vp.x0) - width); + y = 1.0 * ((rctx.vp.y1 - rctx.vp.y0) - height); + break; + default: + x = 0.0; + y = 0.0; + break; + } + } + /* Compose additional transformation from scale and position */ + q.c[0] = width / (vb->x1 - vb->x0); + q.c[1] = 0.0; + q.c[2] = 0.0; + q.c[3] = height / (vb->y1 - vb->y0); + q.c[4] = -vb->x0 * q.c[0] + x; + q.c[5] = -vb->y0 * q.c[3] + y; + /* Append viewbox transformation */ + nr_matrix_multiply (&marker->c2p, &q, &marker->c2p); + + + /* Append reference translation */ + /* fixme: lala (Lauris) */ + nr_matrix_set_translate (&q, -marker->refX.computed, -marker->refY.computed); + nr_matrix_multiply (&marker->c2p, &q, &marker->c2p); + + rctx.i2doc = marker->c2p * rctx.i2doc; + + /* If viewBox is set reinitialize child viewport */ + /* Otherwise it already correct */ + if (marker->viewBox_set) { + rctx.vp.x0 = marker->viewBox.x0; + rctx.vp.y0 = marker->viewBox.y0; + rctx.vp.x1 = marker->viewBox.x1; + rctx.vp.y1 = marker->viewBox.y1; + rctx.i2vp = NR::identity(); + } + + /* And invoke parent method */ + if (((SPObjectClass *) (parent_class))->update) + ((SPObjectClass *) (parent_class))->update (object, (SPCtx *) &rctx, flags); + + /* As last step set additional transform of arena group */ + for (v = marker->views; v != NULL; v = v->next) { + for (unsigned i = 0 ; i < v->size ; i++) { + if (v->items[i]) { + nr_arena_group_set_child_transform(NR_ARENA_GROUP(v->items[i]), &marker->c2p); + } + } + } +} + +static Inkscape::XML::Node * +sp_marker_write (SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + SPMarker *marker; + + marker = SP_MARKER (object); + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = sp_repr_new ("svg:marker"); + } + + if (marker->markerUnits_set) { + if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) { + repr->setAttribute("markerUnits", "strokeWidth"); + } else { + repr->setAttribute("markerUnits", "userSpaceOnUse"); + } + } else { + repr->setAttribute("markerUnits", NULL); + } + if (marker->refX._set) { + sp_repr_set_svg_double(repr, "refX", marker->refX.computed); + } else { + repr->setAttribute("refX", NULL); + } + if (marker->refY._set) { + sp_repr_set_svg_double (repr, "refY", marker->refY.computed); + } else { + repr->setAttribute("refY", NULL); + } + if (marker->markerWidth._set) { + sp_repr_set_svg_double (repr, "markerWidth", marker->markerWidth.computed); + } else { + repr->setAttribute("markerWidth", NULL); + } + if (marker->markerHeight._set) { + sp_repr_set_svg_double (repr, "markerHeight", marker->markerHeight.computed); + } else { + repr->setAttribute("markerHeight", NULL); + } + if (marker->orient_set) { + if (marker->orient_auto) { + repr->setAttribute("orient", "auto"); + } else { + sp_repr_set_css_double(repr, "orient", marker->orient); + } + } else { + repr->setAttribute("orient", NULL); + } + /* fixme: */ + repr->setAttribute("viewBox", object->repr->attribute("viewBox")); + repr->setAttribute("preserveAspectRatio", object->repr->attribute("preserveAspectRatio")); + + if (((SPObjectClass *) (parent_class))->write) + ((SPObjectClass *) (parent_class))->write (object, repr, flags); + + return repr; +} + +static NRArenaItem * +sp_marker_private_show (SPItem *item, NRArena *arena, unsigned int key, unsigned int flags) +{ + /* Break propagation */ + return NULL; +} + +static void +sp_marker_private_hide (SPItem *item, unsigned int key) +{ + /* Break propagation */ +} + +static void +sp_marker_bbox(SPItem const *, NRRect *, NR::Matrix const &, unsigned const) +{ + /* Break propagation */ +} + +static void +sp_marker_print (SPItem *item, SPPrintContext *ctx) +{ + /* Break propagation */ +} + +/* fixme: Remove link if zero-sized (Lauris) */ + +/** +* First of all, removes any SPMarkerViews that a marker has with a specific key. +* Set up the NRArenaItem array's size in the specified SPMarker's SPMarkerView. +* \param marker Marker to create views in. +* \param key Key to give each SPMarkerView. +* \param size Number of NRArenaItems to put in the SPMarkerView. +*/ +void +sp_marker_show_dimension (SPMarker *marker, unsigned int key, unsigned int size) +{ + SPMarkerView *view; + unsigned int i; + + for (view = marker->views; view != NULL; view = view->next) { + if (view->key == key) break; + } + if (view && (view->size != size)) { + /* Free old view and allocate new */ + /* Parent class ::hide method */ + ((SPItemClass *) parent_class)->hide ((SPItem *) marker, key); + sp_marker_view_remove (marker, view, TRUE); + view = NULL; + } + if (!view) { + view = (SPMarkerView *)malloc (sizeof (SPMarkerView) + (size) * sizeof (NRArenaItem *)); + for (i = 0; i < size; i++) view->items[i] = NULL; + view->next = marker->views; + marker->views = view; + view->key = key; + view->size = size; + } +} + +NRArenaItem * +sp_marker_show_instance (SPMarker *marker, NRArenaItem *parent, + unsigned int key, unsigned int pos, + NR::Matrix const &base, float linewidth) +{ + for (SPMarkerView *v = marker->views; v != NULL; v = v->next) { + if (v->key == key) { + if (pos >= v->size) { + return NULL; + } + if (!v->items[pos]) { + /* Parent class ::show method */ + v->items[pos] = ((SPItemClass *) parent_class)->show ((SPItem *) marker, + parent->arena, key, + SP_ITEM_REFERENCE_FLAGS); + if (v->items[pos]) { + /* fixme: Position (Lauris) */ + nr_arena_item_add_child (parent, v->items[pos], NULL); + /* nr_arena_item_unref (v->items[pos]); */ + nr_arena_group_set_child_transform((NRArenaGroup *) v->items[pos], &marker->c2p); + } + } + if (v->items[pos]) { + NR::Matrix m; + if (marker->orient_auto) { + m = base; + } else { + /* fixme: Orient units (Lauris) */ + m = NR::Matrix(rotate_degrees(marker->orient)); + m *= get_translation(base); + } + if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) { + m = NR::scale(linewidth) * m; + } + + nr_arena_item_set_transform(v->items[pos], m); + } + return v->items[pos]; + } + } + + return NULL; +} + +/* This replaces SPItem implementation because we have own views */ + +/** +* \param key SPMarkerView key to hide. +*/ +void +sp_marker_hide (SPMarker *marker, unsigned int key) +{ + SPMarkerView *v; + + v = marker->views; + while (v != NULL) { + SPMarkerView *next; + next = v->next; + if (v->key == key) { + /* Parent class ::hide method */ + ((SPItemClass *) parent_class)->hide ((SPItem *) marker, key); + sp_marker_view_remove (marker, v, TRUE); + return; + } + v = next; + } +} + +static void +sp_marker_view_remove (SPMarker *marker, SPMarkerView *view, unsigned int destroyitems) +{ + unsigned int i; + if (view == marker->views) { + marker->views = view->next; + } else { + SPMarkerView *v; + for (v = marker->views; v->next != view; v = v->next) if (!v->next) return; + v->next = view->next; + } + if (destroyitems) { + for (i = 0; i < view->size; i++) { + /* We have to walk through the whole array because there may be hidden items */ + if (view->items[i]) nr_arena_item_unref (view->items[i]); + } + } + g_free (view); +} diff --git a/src/sp-marker.h b/src/sp-marker.h new file mode 100644 index 000000000..1fba52d36 --- /dev/null +++ b/src/sp-marker.h @@ -0,0 +1,93 @@ +#ifndef __SP_MARKER_H__ +#define __SP_MARKER_H__ + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2003 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +/* + * This is quite similar in logic to + * Maybe we should merge them somehow (Lauris) + */ + +#define SP_TYPE_MARKER (sp_marker_get_type ()) +#define SP_MARKER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), SP_TYPE_MARKER, SPMarker)) +#define SP_IS_MARKER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), SP_TYPE_MARKER)) + +class SPMarker; +class SPMarkerClass; +class SPMarkerView; + +#include +#include +#include "svg/svg-length.h" +#include "enums.h" +#include "sp-item-group.h" +#include "sp-marker-loc.h" +#include "uri-references.h" + +struct SPMarker : public SPGroup { + /* units */ + unsigned int markerUnits_set : 1; + unsigned int markerUnits : 1; + + /* reference point */ + SVGLength refX; + SVGLength refY; + + /* dimensions */ + SVGLength markerWidth; + SVGLength markerHeight; + + /* orient */ + unsigned int orient_set : 1; + unsigned int orient_auto : 1; + float orient; + + /* viewBox; */ + unsigned int viewBox_set : 1; + NRRect viewBox; + + /* preserveAspectRatio */ + unsigned int aspect_set : 1; + unsigned int aspect_align : 4; + unsigned int aspect_clip : 1; + + /* Child to parent additional transform */ + NRMatrix c2p; + + /* Private views */ + SPMarkerView *views; +}; + +struct SPMarkerClass { + SPGroupClass parent_class; +}; + +GType sp_marker_get_type (void); + +class SPMarkerReference : public Inkscape::URIReference { + SPMarkerReference(SPObject *obj) : URIReference(obj) {} + SPMarker *getObject() const { + return (SPMarker *)URIReference::getObject(); + } +protected: + virtual bool _acceptObject(SPObject *obj) const { + return SP_IS_MARKER(obj); + } +}; + +void sp_marker_show_dimension (SPMarker *marker, unsigned int key, unsigned int size); +NRArenaItem *sp_marker_show_instance (SPMarker *marker, NRArenaItem *parent, + unsigned int key, unsigned int pos, + NR::Matrix const &base, float linewidth); +void sp_marker_hide (SPMarker *marker, unsigned int key); + +#endif diff --git a/src/sp-mask.cpp b/src/sp-mask.cpp new file mode 100644 index 000000000..1f2d531b7 --- /dev/null +++ b/src/sp-mask.cpp @@ -0,0 +1,375 @@ +#define __SP_MASK_C__ + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 2003 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + +#include "display/nr-arena.h" +#include "display/nr-arena-group.h" +#include + +#include "enums.h" +#include "attributes.h" +#include "document.h" +#include "sp-item.h" + +#include "sp-mask.h" + +struct SPMaskView { + SPMaskView *next; + unsigned int key; + NRArenaItem *arenaitem; + NRRect bbox; +}; + +static void sp_mask_class_init (SPMaskClass *klass); +static void sp_mask_init (SPMask *mask); + +static void sp_mask_build (SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); +static void sp_mask_release (SPObject * object); +static void sp_mask_set (SPObject *object, unsigned int key, const gchar *value); +static void sp_mask_child_added (SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref); +static void sp_mask_update (SPObject *object, SPCtx *ctx, guint flags); +static void sp_mask_modified (SPObject *object, guint flags); +static Inkscape::XML::Node *sp_mask_write (SPObject *object, Inkscape::XML::Node *repr, guint flags); + +SPMaskView *sp_mask_view_new_prepend (SPMaskView *list, unsigned int key, NRArenaItem *arenaitem); +SPMaskView *sp_mask_view_list_remove (SPMaskView *list, SPMaskView *view); + +static SPObjectGroupClass *parent_class; + +GType +sp_mask_get_type (void) +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof (SPMaskClass), + NULL, NULL, + (GClassInitFunc) sp_mask_class_init, + NULL, NULL, + sizeof (SPMask), + 16, + (GInstanceInitFunc) sp_mask_init, + NULL, /* value_table */ + }; + type = g_type_register_static (SP_TYPE_OBJECTGROUP, "SPMask", &info, (GTypeFlags)0); + } + return type; +} + +static void +sp_mask_class_init (SPMaskClass *klass) +{ + parent_class = (SPObjectGroupClass*) g_type_class_ref (SP_TYPE_OBJECTGROUP); + + SPObjectClass *sp_object_class = (SPObjectClass *) klass; + sp_object_class->build = sp_mask_build; + sp_object_class->release = sp_mask_release; + sp_object_class->set = sp_mask_set; + sp_object_class->child_added = sp_mask_child_added; + sp_object_class->update = sp_mask_update; + sp_object_class->modified = sp_mask_modified; + sp_object_class->write = sp_mask_write; +} + +static void +sp_mask_init (SPMask *mask) +{ + mask->maskUnits_set = FALSE; + mask->maskUnits = SP_CONTENT_UNITS_OBJECTBOUNDINGBOX; + + mask->maskUnits_set = FALSE; + mask->maskUnits = SP_CONTENT_UNITS_USERSPACEONUSE; + + mask->display = NULL; +} + +static void +sp_mask_build (SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + if (((SPObjectClass *) parent_class)->build) { + ((SPObjectClass *) parent_class)->build (object, document, repr); + } + + sp_object_read_attr (object, "maskUnits"); + sp_object_read_attr (object, "maskContentUnits"); + + /* Register ourselves */ + sp_document_add_resource (document, "mask", object); +} + +static void +sp_mask_release (SPObject * object) +{ + if (SP_OBJECT_DOCUMENT (object)) { + /* Unregister ourselves */ + sp_document_remove_resource (SP_OBJECT_DOCUMENT (object), "mask", object); + } + + SPMask *cp = SP_MASK (object); + while (cp->display) { + /* We simply unref and let item to manage this in handler */ + cp->display = sp_mask_view_list_remove (cp->display, cp->display); + } + + if (((SPObjectClass *) (parent_class))->release) { + ((SPObjectClass *) parent_class)->release (object); + } +} + +static void +sp_mask_set (SPObject *object, unsigned int key, const gchar *value) +{ + SPMask *mask = SP_MASK (object); + + switch (key) { + case SP_ATTR_MASKUNITS: + mask->maskUnits = SP_CONTENT_UNITS_OBJECTBOUNDINGBOX; + mask->maskUnits_set = FALSE; + if (value) { + if (!strcmp (value, "userSpaceOnUse")) { + mask->maskUnits = SP_CONTENT_UNITS_USERSPACEONUSE; + mask->maskUnits_set = TRUE; + } else if (!strcmp (value, "objectBoundingBox")) { + mask->maskUnits_set = TRUE; + } + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_MASKCONTENTUNITS: + mask->maskContentUnits = SP_CONTENT_UNITS_USERSPACEONUSE; + mask->maskContentUnits_set = FALSE; + if (value) { + if (!strcmp (value, "userSpaceOnUse")) { + mask->maskContentUnits_set = TRUE; + } else if (!strcmp (value, "objectBoundingBox")) { + mask->maskContentUnits = SP_CONTENT_UNITS_OBJECTBOUNDINGBOX; + mask->maskContentUnits_set = TRUE; + } + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + default: + if (((SPObjectClass *) parent_class)->set) + ((SPObjectClass *) parent_class)->set (object, key, value); + break; + } +} + +static void +sp_mask_child_added (SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref) +{ + /* Invoke SPObjectGroup implementation */ + ((SPObjectClass *) (parent_class))->child_added (object, child, ref); + + /* Show new object */ + SPObject *ochild = SP_OBJECT_DOCUMENT (object)->getObjectByRepr(child); + if (SP_IS_ITEM (ochild)) { + SPMask *cp = SP_MASK (object); + for (SPMaskView *v = cp->display; v != NULL; v = v->next) { + NRArenaItem *ac = sp_item_invoke_show (SP_ITEM (ochild), + NR_ARENA_ITEM_ARENA (v->arenaitem), + v->key, + SP_ITEM_REFERENCE_FLAGS); + if (ac) { + nr_arena_item_add_child (v->arenaitem, ac, NULL); + nr_arena_item_unref (ac); + } + } + } +} + +static void +sp_mask_update (SPObject *object, SPCtx *ctx, guint flags) +{ + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + flags &= SP_OBJECT_MODIFIED_CASCADE; + + SPObjectGroup *og = SP_OBJECTGROUP (object); + GSList *l = NULL; + for (SPObject *child = sp_object_first_child(SP_OBJECT(og)); child != NULL; child = SP_OBJECT_NEXT(child)) { + g_object_ref (G_OBJECT (child)); + l = g_slist_prepend (l, child); + } + l = g_slist_reverse (l); + while (l) { + SPObject *child = SP_OBJECT (l->data); + l = g_slist_remove (l, child); + if (flags || (child->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->updateDisplay(ctx, flags); + } + g_object_unref (G_OBJECT (child)); + } + + SPMask *mask = SP_MASK (object); + for (SPMaskView *v = mask->display; v != NULL; v = v->next) { + if (mask->maskContentUnits == SP_CONTENT_UNITS_OBJECTBOUNDINGBOX) { + NRMatrix t; + nr_matrix_set_scale (&t, v->bbox.x1 - v->bbox.x0, v->bbox.y1 - v->bbox.y0); + t.c[4] = v->bbox.x0; + t.c[5] = v->bbox.y0; + nr_arena_group_set_child_transform (NR_ARENA_GROUP (v->arenaitem), &t); + } else { + nr_arena_group_set_child_transform (NR_ARENA_GROUP (v->arenaitem), NULL); + } + } +} + +static void +sp_mask_modified (SPObject *object, guint flags) +{ + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + flags &= SP_OBJECT_MODIFIED_CASCADE; + + SPObjectGroup *og = SP_OBJECTGROUP (object); + GSList *l = NULL; + for (SPObject *child = sp_object_first_child(SP_OBJECT(og)); child != NULL; child = SP_OBJECT_NEXT(child)) { + g_object_ref (G_OBJECT (child)); + l = g_slist_prepend (l, child); + } + l = g_slist_reverse (l); + while (l) { + SPObject *child = SP_OBJECT (l->data); + l = g_slist_remove (l, child); + if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->emitModified(flags); + } + g_object_unref (G_OBJECT (child)); + } +} + +static Inkscape::XML::Node * +sp_mask_write (SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = sp_repr_new ("svg:mask"); + } + + if (((SPObjectClass *) (parent_class))->write) + ((SPObjectClass *) (parent_class))->write (object, repr, flags); + + return repr; +} + +NRArenaItem * +sp_mask_show (SPMask *mask, NRArena *arena, unsigned int key) +{ + g_return_val_if_fail (mask != NULL, NULL); + g_return_val_if_fail (SP_IS_MASK (mask), NULL); + g_return_val_if_fail (arena != NULL, NULL); + g_return_val_if_fail (NR_IS_ARENA (arena), NULL); + + NRArenaItem *ai = NRArenaGroup::create(arena); + mask->display = sp_mask_view_new_prepend (mask->display, key, ai); + + for (SPObject *child = sp_object_first_child(SP_OBJECT(mask)) ; child != NULL; child = SP_OBJECT_NEXT(child)) { + if (SP_IS_ITEM (child)) { + NRArenaItem *ac = sp_item_invoke_show (SP_ITEM (child), arena, key, SP_ITEM_REFERENCE_FLAGS); + if (ac) { + /* The order is not important in mask */ + nr_arena_item_add_child (ai, ac, NULL); + nr_arena_item_unref (ac); + } + } + } + + if (mask->maskContentUnits == SP_CONTENT_UNITS_OBJECTBOUNDINGBOX) { + NRMatrix t; + nr_matrix_set_scale (&t, mask->display->bbox.x1 - mask->display->bbox.x0, mask->display->bbox.y1 - mask->display->bbox.y0); + t.c[4] = mask->display->bbox.x0; + t.c[5] = mask->display->bbox.y0; + nr_arena_group_set_child_transform (NR_ARENA_GROUP (ai), &t); + } + + return ai; +} + +void +sp_mask_hide (SPMask *cp, unsigned int key) +{ + g_return_if_fail (cp != NULL); + g_return_if_fail (SP_IS_MASK (cp)); + + for (SPObject *child = sp_object_first_child(SP_OBJECT(cp)); child != NULL; child = SP_OBJECT_NEXT(child)) { + if (SP_IS_ITEM (child)) { + sp_item_invoke_hide (SP_ITEM (child), key); + } + } + + for (SPMaskView *v = cp->display; v != NULL; v = v->next) { + if (v->key == key) { + /* We simply unref and let item to manage this in handler */ + cp->display = sp_mask_view_list_remove (cp->display, v); + return; + } + } + + g_assert_not_reached (); +} + +void +sp_mask_set_bbox (SPMask *mask, unsigned int key, NRRect *bbox) +{ + for (SPMaskView *v = mask->display; v != NULL; v = v->next) { + if (v->key == key) { + if (!NR_DF_TEST_CLOSE (v->bbox.x0, bbox->x0, NR_EPSILON) || + !NR_DF_TEST_CLOSE (v->bbox.y0, bbox->y0, NR_EPSILON) || + !NR_DF_TEST_CLOSE (v->bbox.x1, bbox->x1, NR_EPSILON) || + !NR_DF_TEST_CLOSE (v->bbox.y1, bbox->y1, NR_EPSILON)) { + v->bbox = *bbox; + SP_OBJECT(mask)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + } +} + +/* Mask views */ + +SPMaskView * +sp_mask_view_new_prepend (SPMaskView *list, unsigned int key, NRArenaItem *arenaitem) +{ + SPMaskView *new_mask_view = g_new (SPMaskView, 1); + + new_mask_view->next = list; + new_mask_view->key = key; + new_mask_view->arenaitem = nr_arena_item_ref(arenaitem); + new_mask_view->bbox.x0 = new_mask_view->bbox.x1 = 0.0; + new_mask_view->bbox.y0 = new_mask_view->bbox.y1 = 0.0; + + return new_mask_view; +} + +SPMaskView * +sp_mask_view_list_remove (SPMaskView *list, SPMaskView *view) +{ + if (view == list) { + list = list->next; + } else { + SPMaskView *prev; + prev = list; + while (prev->next != view) prev = prev->next; + prev->next = view->next; + } + + nr_arena_item_unref (view->arenaitem); + g_free (view); + + return list; +} + diff --git a/src/sp-mask.h b/src/sp-mask.h new file mode 100644 index 000000000..23239f55d --- /dev/null +++ b/src/sp-mask.h @@ -0,0 +1,63 @@ +#ifndef __SP_MASK_H__ +#define __SP_MASK_H__ + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 2003 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define SP_TYPE_MASK (sp_mask_get_type ()) +#define SP_MASK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_MASK, SPMask)) +#define SP_MASK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_MASK, SPMaskClass)) +#define SP_IS_MASK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_MASK)) +#define SP_IS_MASK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_MASK)) + +class SPMask; +class SPMaskClass; +class SPMaskView; + +#include "display/nr-arena-forward.h" +#include "libnr/nr-forward.h" +#include "sp-object-group.h" +#include "uri-references.h" + +struct SPMask : public SPObjectGroup { + unsigned int maskUnits_set : 1; + unsigned int maskUnits : 1; + + unsigned int maskContentUnits_set : 1; + unsigned int maskContentUnits : 1; + + SPMaskView *display; +}; + +struct SPMaskClass { + SPObjectGroupClass parent_class; +}; + +GType sp_mask_get_type (void); + +class SPMaskReference : public Inkscape::URIReference { +public: + SPMaskReference(SPObject *obj) : URIReference(obj) {} + SPMask *getObject() const { + return (SPMask *)URIReference::getObject(); + } +protected: + virtual bool _acceptObject(SPObject *obj) const { + return SP_IS_MASK(obj); + } +}; + +NRArenaItem *sp_mask_show (SPMask *mask, NRArena *arena, unsigned int key); +void sp_mask_hide (SPMask *mask, unsigned int key); + +void sp_mask_set_bbox (SPMask *mask, unsigned int key, NRRect *bbox); + +#endif diff --git a/src/sp-metadata.cpp b/src/sp-metadata.cpp new file mode 100644 index 000000000..0f4b1fb1d --- /dev/null +++ b/src/sp-metadata.cpp @@ -0,0 +1,223 @@ +#define __SP_METADATA_C__ + +/* + * SVG implementation + * + * Authors: + * Kees Cook + * + * Copyright (C) 2004 Kees Cook + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "sp-metadata.h" +#include "xml/node-iterators.h" +#include "document.h" + +#include "sp-item-group.h" + +#define noDEBUG_METADATA +#ifdef DEBUG_METADATA +# define debug(f, a...) { g_print("%s(%d) %s:", \ + __FILE__,__LINE__,__FUNCTION__); \ + g_print(f, ## a); \ + g_print("\n"); \ + } +#else +# define debug(f, a...) /**/ +#endif + +/* Metadata base class */ + +static void sp_metadata_class_init (SPMetadataClass *klass); +static void sp_metadata_init (SPMetadata *metadata); + +static void sp_metadata_build (SPObject * object, SPDocument * document, Inkscape::XML::Node * repr); +static void sp_metadata_release (SPObject *object); +static void sp_metadata_set (SPObject *object, unsigned int key, const gchar *value); +static void sp_metadata_update(SPObject *object, SPCtx *ctx, guint flags); +static Inkscape::XML::Node *sp_metadata_write(SPObject *object, Inkscape::XML::Node *repr, guint flags); + +static SPObjectClass *metadata_parent_class; + +GType +sp_metadata_get_type (void) +{ + static GType metadata_type = 0; + + if (!metadata_type) { + GTypeInfo metadata_info = { + sizeof (SPMetadataClass), + NULL, NULL, + (GClassInitFunc) sp_metadata_class_init, + NULL, NULL, + sizeof (SPMetadata), + 16, + (GInstanceInitFunc) sp_metadata_init, + NULL, /* value_table */ + }; + metadata_type = g_type_register_static (SP_TYPE_OBJECT, "SPMetadata", &metadata_info, (GTypeFlags)0); + } + return metadata_type; +} + +static void +sp_metadata_class_init (SPMetadataClass *klass) +{ + //GObjectClass *gobject_class = (GObjectClass *)klass; + SPObjectClass *sp_object_class = (SPObjectClass *)klass; + + metadata_parent_class = (SPObjectClass*)g_type_class_peek_parent (klass); + + sp_object_class->build = sp_metadata_build; + sp_object_class->release = sp_metadata_release; + sp_object_class->write = sp_metadata_write; + sp_object_class->set = sp_metadata_set; + sp_object_class->update = sp_metadata_update; +} + +static void +sp_metadata_init (SPMetadata *metadata) +{ + debug("0x%08x",(unsigned int)metadata); +} + +namespace { + +void strip_ids_recursively(Inkscape::XML::Node *node) { + using Inkscape::XML::NodeSiblingIterator; + if ( node->type() == Inkscape::XML::ELEMENT_NODE ) { + node->setAttribute("id", NULL); + } + for ( NodeSiblingIterator iter=node->firstChild() ; iter ; ++iter ) { + strip_ids_recursively(iter); + } +} + +} + +/* + * \brief Reads the Inkscape::XML::Node, and initializes SPMetadata variables. + * For this to get called, our name must be associated with + * a repr via "sp_object_type_register". Best done through + * sp-object-repr.cpp's repr_name_entries array. + */ +static void +sp_metadata_build (SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + using Inkscape::XML::NodeSiblingIterator; + + debug("0x%08x",(unsigned int)object); + + /* clean up our mess from earlier versions; elements under rdf:RDF should not + * have id= attributes... */ + static GQuark const rdf_root_name=g_quark_from_static_string("rdf:RDF"); + for ( NodeSiblingIterator iter=repr->firstChild() ; iter ; ++iter ) { + if ( (GQuark)iter->code() == rdf_root_name ) { + strip_ids_recursively(iter); + } + } + + if (((SPObjectClass *) metadata_parent_class)->build) + ((SPObjectClass *) metadata_parent_class)->build (object, document, repr); +} + +/* + * \brief Drops any allocated memory + */ +static void +sp_metadata_release (SPObject *object) +{ + debug("0x%08x",(unsigned int)object); + + /* handle ourself */ + + if (((SPObjectClass *) metadata_parent_class)->release) + ((SPObjectClass *) metadata_parent_class)->release (object); +} + +/* + * \brief Sets a specific value in the SPMetadata + */ +static void +sp_metadata_set (SPObject *object, unsigned int key, const gchar *value) +{ + debug("0x%08x %s(%u): '%s'",(unsigned int)object, + sp_attribute_name(key),key,value); + SPMetadata * metadata; + + metadata = SP_METADATA (object); + + /* see if any parents need this value */ + if (((SPObjectClass *) metadata_parent_class)->set) + ((SPObjectClass *) metadata_parent_class)->set (object, key, value); +} + +/* + * \brief Receives update notifications + */ +static void +sp_metadata_update(SPObject *object, SPCtx *ctx, guint flags) +{ + debug("0x%08x",(unsigned int)object); + //SPMetadata *metadata = SP_METADATA(object); + + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | + SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + + /* do something? */ + + } + + if (((SPObjectClass *) metadata_parent_class)->update) + ((SPObjectClass *) metadata_parent_class)->update(object, ctx, flags); +} + +/* + * \brief Writes it's settings to an incoming repr object, if any + */ +static Inkscape::XML::Node * +sp_metadata_write(SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + debug("0x%08x",(unsigned int)object); + //SPMetadata *metadata = SP_METADATA(object); + + // only create a repr when we're writing out an Inkscape SVG + if ( flags & SP_OBJECT_WRITE_EXT && repr != SP_OBJECT_REPR(object) ) { + if (repr) { + repr->mergeFrom(SP_OBJECT_REPR (object), "id"); + } else { + repr = SP_OBJECT_REPR (object)->duplicate(); + } + } + + if (((SPObjectClass *) metadata_parent_class)->write) + ((SPObjectClass *) metadata_parent_class)->write(object, repr, flags); + + return repr; +} + +/* + * \brief Retrieves the metadata object associated with a document + */ +SPMetadata * +sp_document_metadata (SPDocument *document) +{ + SPObject *nv; + + g_return_val_if_fail (document != NULL, NULL); + + nv = sp_item_group_get_child_by_name ((SPGroup *) document->root, NULL, + "metadata"); + g_assert (nv != NULL); + + return (SPMetadata *)nv; +} + + +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/sp-metadata.h b/src/sp-metadata.h new file mode 100644 index 000000000..82b7c4fc5 --- /dev/null +++ b/src/sp-metadata.h @@ -0,0 +1,39 @@ +#ifndef __SP_METADATA_H__ +#define __SP_METADATA_H__ + +/* + * SVG implementation + * + * Authors: + * Kees Cook + * + * Copyright (C) 2004 Kees Cook + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-object.h" + + +/* Metadata base class */ + +#define SP_TYPE_METADATA (sp_metadata_get_type ()) +#define SP_METADATA(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), SP_TYPE_METADATA, SPMetadata)) +#define SP_IS_METADATA(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), SP_TYPE_METADATA)) + +class SPMetadata; +class SPMetadataClass; + +struct SPMetadata : public SPObject { +}; + +struct SPMetadataClass { + SPObjectClass parent_class; +}; + +GType sp_metadata_get_type (void); + +SPMetadata * sp_document_metadata (SPDocument *document); + +#endif +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/sp-metric.h b/src/sp-metric.h new file mode 100644 index 000000000..76db44710 --- /dev/null +++ b/src/sp-metric.h @@ -0,0 +1,26 @@ +#ifndef INKSCAPE_SP_METRIC_H +#define INKSCAPE_SP_METRIC_H + +/** Known metrics so far. (I don't know why this doesn't include pica.) */ +enum SPMetric { + NONE, + SP_MM, + SP_CM, + SP_IN, + SP_PT, + SP_PX, + SP_M +}; + +#endif /* !INKSCAPE_SP_METRIC_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:encoding=utf-8:textwidth=99 : diff --git a/src/sp-metrics.cpp b/src/sp-metrics.cpp new file mode 100644 index 000000000..27d65422f --- /dev/null +++ b/src/sp-metrics.cpp @@ -0,0 +1,108 @@ +#include "sp-metrics.h" +#include "unit-constants.h" + +/* + * SPMetric handling and stuff + * I hope this will be usefull :-) + */ + +gdouble +sp_absolute_metric_to_metric (gdouble length_src, const SPMetric metric_src, const SPMetric metric_dst) +{ + gdouble src = 1; + gdouble dst = 1; + + switch (metric_src) { + case SP_M: + src = M_PER_IN; + break; + case SP_MM: + src = MM_PER_IN; + break; + case SP_CM: + src = CM_PER_IN; + break; + case SP_IN: + src = IN_PER_IN; + break; + case SP_PT: + src = PT_PER_IN; + break; + case SP_PX: + src = PX_PER_IN; + break; + case NONE: + src = 1; + break; + } + + switch (metric_dst) { + case SP_M: + dst = M_PER_IN; + break; + case SP_MM: + dst = MM_PER_IN; + break; + case SP_CM: + dst = CM_PER_IN; + break; + case SP_IN: + dst = IN_PER_IN; + break; + case SP_PT: + dst = PT_PER_IN; + break; + case SP_PX: + dst = PX_PER_IN; + break; + case NONE: + dst = 1; + break; + } + + return length_src * (dst/src); +} + +/** + * Create a human-readable string suitable for status-bar display. + */ +GString * +sp_metric_to_metric_string(gdouble const length, + SPMetric const metric_src, SPMetric const metric_dst, + gboolean const m) +{ + gdouble const len = sp_absolute_metric_to_metric(length, metric_src, metric_dst); + GString *str = g_string_new(""); + g_string_printf(str, "%0.02f", len); + /* We need a fixed number of fractional digits, because otherwise the live statusbar display of + * lengths will be too jerky */ + + if (m) { + char const *unit_str; + switch (metric_dst) { + case SP_M: unit_str = " m"; break; + case SP_MM: unit_str = " mm"; break; + case SP_CM: unit_str = " cm"; break; + case SP_IN: unit_str = "\""; break; + case SP_PT: unit_str = " pt"; break; + case SP_PX: unit_str = " px"; break; + default: unit_str = NULL; break; + } + if (unit_str) { + g_string_append(str, unit_str); + } + } + return str; +} + + +/* + 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 : diff --git a/src/sp-metrics.h b/src/sp-metrics.h new file mode 100644 index 000000000..23c1b6c13 --- /dev/null +++ b/src/sp-metrics.h @@ -0,0 +1,21 @@ +#ifndef SP_METRICS_H +#define SP_METRICS_H + +#include +#include +#include "sp-metric.h" + +gdouble sp_absolute_metric_to_metric (gdouble length_src, const SPMetric metric_src, const SPMetric metric_dst); +GString * sp_metric_to_metric_string (gdouble length, const SPMetric metric_src, const SPMetric metric_dst, gboolean m); + +// convenience since we mostly deal with points +#define SP_METRIC_TO_PT(l,m) sp_absolute_metric_to_metric(l,m,SP_PT); +#define SP_PT_TO_METRIC(l,m) sp_absolute_metric_to_metric(l,SP_PT,m); + +#define SP_PT_TO_METRIC_STRING(l,m) sp_metric_to_metric_string(l, SP_PT, m, TRUE) +#define SP_PT_TO_STRING(l,m) sp_metric_to_metric_string(l, SP_PT, m, FALSE) + +#define SP_PX_TO_METRIC_STRING(l,m) sp_metric_to_metric_string(l, SP_PX, m, TRUE) +#define SP_PX_TO_STRING(l,m) sp_metric_to_metric_string(l, SP_PX, m, FALSE) + +#endif diff --git a/src/sp-namedview.cpp b/src/sp-namedview.cpp new file mode 100644 index 000000000..c6e6b872b --- /dev/null +++ b/src/sp-namedview.cpp @@ -0,0 +1,989 @@ +#define __SP_NAMEDVIEW_C__ + +/* + * implementation + * + * Authors: + * Lauris Kaplinski + * bulia byak + * + * Copyright (C) 1999-2005 Authors + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "config.h" + + + + +#include "display/canvas-grid.h" +#include "helper/units.h" +#include "svg/svg.h" +#include "xml/repr.h" +#include "attributes.h" +#include "document.h" +#include "desktop-events.h" +#include "desktop-handles.h" +#include "sp-guide.h" +#include "sp-item-group.h" +#include "sp-namedview.h" +#include "prefs-utils.h" +#include "desktop.h" + +#include "isnan.h" //temp fox for isnan(). include last + +#define DEFAULTTOLERANCE 0.4 +#define DEFAULTGRIDCOLOR 0x3f3fff25 +#define DEFAULTGRIDEMPCOLOR 0x3f3fff60 +#define DEFAULTGRIDEMPSPACING 5 +#define DEFAULTGUIDECOLOR 0x0000ff7f +#define DEFAULTGUIDEHICOLOR 0xff00007f +#define DEFAULTBORDERCOLOR 0x000000ff +#define DEFAULTPAGECOLOR 0xffffff00 + +static void sp_namedview_class_init(SPNamedViewClass *klass); +static void sp_namedview_init(SPNamedView *namedview); + +static void sp_namedview_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); +static void sp_namedview_release(SPObject *object); +static void sp_namedview_set(SPObject *object, unsigned int key, const gchar *value); +static void sp_namedview_child_added(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref); +static void sp_namedview_remove_child(SPObject *object, Inkscape::XML::Node *child); +static Inkscape::XML::Node *sp_namedview_write(SPObject *object, Inkscape::XML::Node *repr, guint flags); + +static void sp_namedview_setup_guides(SPNamedView * nv); + +static void sp_namedview_setup_grid(SPNamedView * nv); +static void sp_namedview_setup_grid_item(SPNamedView * nv, SPCanvasItem * item); + +static gboolean sp_str_to_bool(const gchar *str); +static gboolean sp_nv_read_length(const gchar *str, guint base, gdouble *val, const SPUnit **unit); +static gboolean sp_nv_read_opacity(const gchar *str, guint32 *color); + +static SPObjectGroupClass * parent_class; + +GType +sp_namedview_get_type() +{ + static GType namedview_type = 0; + if (!namedview_type) { + GTypeInfo namedview_info = { + sizeof(SPNamedViewClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_namedview_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof(SPNamedView), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_namedview_init, + NULL, /* value_table */ + }; + namedview_type = g_type_register_static(SP_TYPE_OBJECTGROUP, "SPNamedView", &namedview_info, (GTypeFlags)0); + } + return namedview_type; +} + +static void sp_namedview_class_init(SPNamedViewClass * klass) +{ + GObjectClass * gobject_class; + SPObjectClass * sp_object_class; + + gobject_class = (GObjectClass *) klass; + sp_object_class = (SPObjectClass *) klass; + + parent_class = (SPObjectGroupClass*) g_type_class_ref(SP_TYPE_OBJECTGROUP); + + sp_object_class->build = sp_namedview_build; + sp_object_class->release = sp_namedview_release; + sp_object_class->set = sp_namedview_set; + sp_object_class->child_added = sp_namedview_child_added; + sp_object_class->remove_child = sp_namedview_remove_child; + sp_object_class->write = sp_namedview_write; +} + +static void sp_namedview_init(SPNamedView *nv) +{ + nv->editable = TRUE; + nv->showgrid = FALSE; + nv->showguides = TRUE; + nv->showborder = TRUE; + nv->showpageshadow = TRUE; + + nv->guides = NULL; + nv->viewcount = 0; + + nv->default_layer_id = 0; + + new (&nv->grid_snapper) Inkscape::GridSnapper(nv, 0); + new (&nv->guide_snapper) Inkscape::GuideSnapper(nv, 0); + new (&nv->object_snapper) Inkscape::ObjectSnapper(nv, 0); +} + +static void sp_namedview_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + SPNamedView *nv = (SPNamedView *) object; + SPObjectGroup *og = (SPObjectGroup *) object; + + if (((SPObjectClass *) (parent_class))->build) { + (* ((SPObjectClass *) (parent_class))->build)(object, document, repr); + } + + sp_object_read_attr(object, "inkscape:document-units"); + sp_object_read_attr(object, "viewonly"); + sp_object_read_attr(object, "showgrid"); + sp_object_read_attr(object, "showguides"); + sp_object_read_attr(object, "gridtolerance"); + sp_object_read_attr(object, "guidetolerance"); + sp_object_read_attr(object, "objecttolerance"); + sp_object_read_attr(object, "inkscape:has_abs_tolerance"); + sp_object_read_attr(object, "gridoriginx"); + sp_object_read_attr(object, "gridoriginy"); + sp_object_read_attr(object, "gridspacingx"); + sp_object_read_attr(object, "gridspacingy"); + sp_object_read_attr(object, "gridempspacing"); + sp_object_read_attr(object, "gridcolor"); + sp_object_read_attr(object, "gridempcolor"); + sp_object_read_attr(object, "gridopacity"); + sp_object_read_attr(object, "gridempopacity"); + sp_object_read_attr(object, "guidecolor"); + sp_object_read_attr(object, "guideopacity"); + sp_object_read_attr(object, "guidehicolor"); + sp_object_read_attr(object, "guidehiopacity"); + sp_object_read_attr(object, "showborder"); + sp_object_read_attr(object, "inkscape:showpageshadow"); + sp_object_read_attr(object, "borderlayer"); + sp_object_read_attr(object, "bordercolor"); + sp_object_read_attr(object, "borderopacity"); + sp_object_read_attr(object, "pagecolor"); + sp_object_read_attr(object, "inkscape:pageopacity"); + sp_object_read_attr(object, "inkscape:pageshadow"); + sp_object_read_attr(object, "inkscape:zoom"); + sp_object_read_attr(object, "inkscape:cx"); + sp_object_read_attr(object, "inkscape:cy"); + sp_object_read_attr(object, "inkscape:window-width"); + sp_object_read_attr(object, "inkscape:window-height"); + sp_object_read_attr(object, "inkscape:window-x"); + sp_object_read_attr(object, "inkscape:window-y"); + sp_object_read_attr(object, "inkscape:grid-bbox"); + sp_object_read_attr(object, "inkscape:guide-bbox"); + sp_object_read_attr(object, "inkscape:object-bbox"); + sp_object_read_attr(object, "inkscape:grid-points"); + sp_object_read_attr(object, "inkscape:guide-points"); + sp_object_read_attr(object, "inkscape:object-points"); + sp_object_read_attr(object, "inkscape:object-paths"); + sp_object_read_attr(object, "inkscape:object-nodes"); + sp_object_read_attr(object, "inkscape:current-layer"); + + /* Construct guideline list */ + + for (SPObject *o = sp_object_first_child(SP_OBJECT(og)) ; o != NULL; o = SP_OBJECT_NEXT(o) ) { + if (SP_IS_GUIDE(o)) { + SPGuide * g = SP_GUIDE(o); + nv->guides = g_slist_prepend(nv->guides, g); + g_object_set(G_OBJECT(g), "color", nv->guidecolor, "hicolor", nv->guidehicolor, NULL); + } + } +} + +static void sp_namedview_release(SPObject *object) +{ + SPNamedView *namedview = (SPNamedView *) object; + + if (namedview->guides) { + g_slist_free(namedview->guides); + namedview->guides = NULL; + } + + while (namedview->gridviews) { + gtk_object_unref(GTK_OBJECT(namedview->gridviews->data)); + namedview->gridviews = g_slist_remove(namedview->gridviews, namedview->gridviews->data); + } + + namedview->grid_snapper.~GridSnapper(); + namedview->guide_snapper.~GuideSnapper(); + namedview->object_snapper.~ObjectSnapper(); + + if (((SPObjectClass *) parent_class)->release) { + ((SPObjectClass *) parent_class)->release(object); + } +} + +static void sp_namedview_set(SPObject *object, unsigned int key, const gchar *value) +{ + SPNamedView *nv = SP_NAMEDVIEW(object); + SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX); + + switch (key) { + case SP_ATTR_VIEWONLY: + nv->editable = (!value); + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SHOWGRID: + nv->showgrid = sp_str_to_bool(value); + sp_namedview_setup_grid(nv); + if (!nv->showgrid) { // grid goes off, disable snaps even if they are turned on + nv->grid_snapper.setSnapTo(Inkscape::Snapper::BBOX_POINT, false); + nv->grid_snapper.setSnapTo(Inkscape::Snapper::SNAP_POINT, false); + } else { // grid goes on, enable snaps if they are turned on + nv->grid_snapper.setSnapTo(Inkscape::Snapper::BBOX_POINT, nv->snap_grid_bbox); + nv->grid_snapper.setSnapTo(Inkscape::Snapper::SNAP_POINT, nv->snap_grid_point); + } + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SHOWGUIDES: + if (!value) { // show guides if not specified, for backwards compatibility + nv->showguides = TRUE; + } else { + nv->showguides = sp_str_to_bool(value); + } + if (!nv->showguides) { // guides go off, disable snaps even if they are turned on + nv->guide_snapper.setSnapTo(Inkscape::Snapper::BBOX_POINT, false); + nv->guide_snapper.setSnapTo(Inkscape::Snapper::SNAP_POINT, false); + } else { // guides go on, enable snaps if they are turned on + nv->guide_snapper.setSnapTo(Inkscape::Snapper::BBOX_POINT, nv->snap_guide_bbox); + nv->guide_snapper.setSnapTo(Inkscape::Snapper::SNAP_POINT, nv->snap_guide_point); + } + sp_namedview_setup_guides(nv); + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_GRIDTOLERANCE: + nv->gridtoleranceunit = &px; + nv->gridtolerance = DEFAULTTOLERANCE; + if (value) { + sp_nv_read_length(value, SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE, &nv->gridtolerance, &nv->gridtoleranceunit); + } + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_GUIDETOLERANCE: + nv->guidetoleranceunit = &px; + nv->guidetolerance = DEFAULTTOLERANCE; + if (value) { + sp_nv_read_length(value, SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE, &nv->guidetolerance, &nv->guidetoleranceunit); + } + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_OBJECTTOLERANCE: + nv->objecttoleranceunit = &px; + nv->objecttolerance = DEFAULTTOLERANCE; + if (value) { + sp_nv_read_length(value, SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE, &nv->objecttolerance, &nv->objecttoleranceunit); + } + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_ABS_TOLERANCE: + if (!value) + nv->has_abs_tolerance = true; + else + nv->has_abs_tolerance = (sp_str_to_bool (value) == TRUE); + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_GRIDORIGINX: + case SP_ATTR_GRIDORIGINY: + { + unsigned const d = (key == SP_ATTR_GRIDORIGINY); + nv->gridunit = nv->doc_units; + nv->gridorigin[d] = 0.0; + if (value) { + sp_nv_read_length(value, SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE, &nv->gridorigin[d], &nv->gridunit); + } + nv->gridorigin[d] = sp_units_get_pixels(nv->gridorigin[d], *(nv->gridunit)); + sp_namedview_setup_grid(nv); + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + case SP_ATTR_GRIDSPACINGX: + case SP_ATTR_GRIDSPACINGY: + { + unsigned const d = (key == SP_ATTR_GRIDSPACINGY); + nv->gridunit = nv->doc_units; + nv->gridspacing[d] = 1.0; + if (value) { + sp_nv_read_length(value, SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE, &nv->gridspacing[d], &nv->gridunit); + } + nv->gridspacing[d] = sp_units_get_pixels(nv->gridspacing[d], *(nv->gridunit)); + sp_namedview_setup_grid(nv); + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + case SP_ATTR_GRIDCOLOR: + nv->gridcolor = (nv->gridcolor & 0xff) | (DEFAULTGRIDCOLOR & 0xffffff00); + if (value) { + nv->gridcolor = (nv->gridcolor & 0xff) | sp_svg_read_color(value, nv->gridcolor); + } + sp_namedview_setup_grid(nv); + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_GRIDEMPCOLOR: + nv->gridempcolor = (nv->gridempcolor & 0xff) | (DEFAULTGRIDEMPCOLOR & 0xffffff00); + if (value) { + nv->gridempcolor = (nv->gridempcolor & 0xff) | sp_svg_read_color(value, nv->gridempcolor); + } + sp_namedview_setup_grid(nv); + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_GRIDOPACITY: + nv->gridcolor = (nv->gridcolor & 0xffffff00) | (DEFAULTGRIDCOLOR & 0xff); + sp_nv_read_opacity(value, &nv->gridcolor); + sp_namedview_setup_grid(nv); + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_GRIDEMPOPACITY: + nv->gridempcolor = (nv->gridempcolor & 0xffffff00) | (DEFAULTGRIDEMPCOLOR & 0xff); + sp_nv_read_opacity(value, &nv->gridempcolor); + sp_namedview_setup_grid(nv); + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_GRIDEMPSPACING: + nv->gridempspacing = DEFAULTGRIDEMPSPACING; + if (value != NULL) + nv->gridempspacing = atoi(value); + sp_namedview_setup_grid(nv); + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_GUIDECOLOR: + nv->guidecolor = (nv->guidecolor & 0xff) | (DEFAULTGUIDECOLOR & 0xffffff00); + if (value) { + nv->guidecolor = (nv->guidecolor & 0xff) | sp_svg_read_color(value, nv->guidecolor); + } + for (GSList *l = nv->guides; l != NULL; l = l->next) { + g_object_set(G_OBJECT(l->data), "color", nv->guidecolor, NULL); + } + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_GUIDEOPACITY: + nv->guidecolor = (nv->guidecolor & 0xffffff00) | (DEFAULTGUIDECOLOR & 0xff); + sp_nv_read_opacity(value, &nv->guidecolor); + for (GSList *l = nv->guides; l != NULL; l = l->next) { + g_object_set(G_OBJECT(l->data), "color", nv->guidecolor, NULL); + } + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_GUIDEHICOLOR: + nv->guidehicolor = (nv->guidehicolor & 0xff) | (DEFAULTGUIDEHICOLOR & 0xffffff00); + if (value) { + nv->guidehicolor = (nv->guidehicolor & 0xff) | sp_svg_read_color(value, nv->guidehicolor); + } + for (GSList *l = nv->guides; l != NULL; l = l->next) { + g_object_set(G_OBJECT(l->data), "hicolor", nv->guidehicolor, NULL); + } + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_GUIDEHIOPACITY: + nv->guidehicolor = (nv->guidehicolor & 0xffffff00) | (DEFAULTGUIDEHICOLOR & 0xff); + sp_nv_read_opacity(value, &nv->guidehicolor); + for (GSList *l = nv->guides; l != NULL; l = l->next) { + g_object_set(G_OBJECT(l->data), "hicolor", nv->guidehicolor, NULL); + } + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SHOWBORDER: + nv->showborder = (value) ? sp_str_to_bool (value) : TRUE; + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_BORDERLAYER: + nv->borderlayer = SP_BORDER_LAYER_BOTTOM; + if (value && !strcasecmp(value, "true")) nv->borderlayer = SP_BORDER_LAYER_TOP; + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_BORDERCOLOR: + nv->bordercolor = (nv->bordercolor & 0xff) | (DEFAULTBORDERCOLOR & 0xffffff00); + if (value) { + nv->bordercolor = (nv->bordercolor & 0xff) | sp_svg_read_color (value, nv->bordercolor); + } + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_BORDEROPACITY: + nv->bordercolor = (nv->bordercolor & 0xffffff00) | (DEFAULTBORDERCOLOR & 0xff); + sp_nv_read_opacity(value, &nv->bordercolor); + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_PAGECOLOR: + nv->pagecolor = (nv->pagecolor & 0xff) | (DEFAULTPAGECOLOR & 0xffffff00); + if (value) { + nv->pagecolor = (nv->pagecolor & 0xff) | sp_svg_read_color(value, nv->pagecolor); + } + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_PAGEOPACITY: + nv->pagecolor = (nv->pagecolor & 0xffffff00) | (DEFAULTPAGECOLOR & 0xff); + sp_nv_read_opacity(value, &nv->pagecolor); + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_PAGESHADOW: + nv->pageshadow = value? atoi(value) : 2; // 2 is the default + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SHOWPAGESHADOW: + nv->showpageshadow = (value) ? sp_str_to_bool(value) : TRUE; + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_ZOOM: + nv->zoom = value ? g_ascii_strtod(value, NULL) : 0; // zero means not set + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_CX: + nv->cx = value ? g_ascii_strtod(value, NULL) : HUGE_VAL; // HUGE_VAL means not set + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_CY: + nv->cy = value ? g_ascii_strtod(value, NULL) : HUGE_VAL; // HUGE_VAL means not set + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_WINDOW_WIDTH: + nv->window_width = value? atoi(value) : -1; // -1 means not set + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_WINDOW_HEIGHT: + nv->window_height = value ? atoi(value) : -1; // -1 means not set + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_WINDOW_X: + nv->window_x = value ? atoi(value) : -1; // -1 means not set + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_WINDOW_Y: + nv->window_y = value ? atoi(value) : -1; // -1 means not set + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_GRID_BBOX: + nv->snap_grid_bbox = (value) ? sp_str_to_bool(value) : TRUE; + nv->grid_snapper.setSnapTo(Inkscape::Snapper::BBOX_POINT, nv->showgrid && nv->snap_grid_bbox); + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_GRID_POINTS: + nv->snap_grid_point = (value) ? sp_str_to_bool(value) : FALSE; + nv->grid_snapper.setSnapTo(Inkscape::Snapper::SNAP_POINT, nv->showgrid && nv->snap_grid_point); + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_GUIDE_BBOX: + nv->snap_guide_bbox = (value) ? sp_str_to_bool(value) : TRUE; + nv->guide_snapper.setSnapTo(Inkscape::Snapper::BBOX_POINT, nv->showguides && nv->snap_guide_bbox); + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_GUIDE_POINTS: + nv->snap_guide_point = (value) ? sp_str_to_bool(value) : FALSE; + nv->guide_snapper.setSnapTo(Inkscape::Snapper::SNAP_POINT, nv->showguides && nv->snap_guide_point); + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_OBJECT_BBOX: + nv->snap_object_bbox = (value) ? sp_str_to_bool(value) : FALSE; + nv->object_snapper.setSnapTo(Inkscape::Snapper::BBOX_POINT, nv->snap_object_bbox); + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_OBJECT_POINTS: + nv->snap_object_point = (value) ? sp_str_to_bool(value) : FALSE; + nv->object_snapper.setSnapTo(Inkscape::Snapper::SNAP_POINT, nv->snap_object_point); + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_OBJECT_PATHS: + nv->snap_object_paths = (value) ? sp_str_to_bool(value) : TRUE; + nv->object_snapper.setSnapToPaths(nv->snap_object_paths); + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_OBJECT_NODES: + nv->snap_object_nodes = (value) ? sp_str_to_bool(value) : TRUE; + nv->object_snapper.setSnapToNodes(nv->snap_object_nodes); + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_CURRENT_LAYER: + nv->default_layer_id = value ? g_quark_from_string(value) : 0; + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_DOCUMENT_UNITS: { + /* The default unit if the document doesn't override this: e.g. for files saved as + * `plain SVG', or non-inkscape files, or files created by an inkscape 0.40 & + * earlier. + * + * Here we choose `px': useful for screen-destined SVGs, and fewer bug reports + * about "not the same numbers as what's in the SVG file" (at least for documents + * without a viewBox attribute on the root element). Similarly, it's also + * the most reliable unit (i.e. least likely to be wrong in different viewing + * conditions) for viewBox-less SVG files given that it's the unit that inkscape + * uses for all coordinates. + * + * For documents that do have a viewBox attribute on the root element, it + * might be better if we used either viewBox coordinates or if we used the unit of + * say the width attribute of the root element. However, these pose problems + * in that they aren't in general absolute units as currently required by + * doc_units. + */ + SPUnit const *new_unit = &sp_unit_get_by_id(SP_UNIT_PX); + + if (value) { + SPUnit const *const req_unit = sp_unit_get_by_abbreviation(value); + if ( req_unit == NULL ) { + g_warning("Unrecognized unit `%s'", value); + /* fixme: Document errors should be reported in the status bar or + * the like (e.g. as per + * http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing); g_log + * should be only for programmer errors. */ + } else if ( req_unit->base == SP_UNIT_ABSOLUTE || + req_unit->base == SP_UNIT_DEVICE ) { + new_unit = req_unit; + } else { + g_warning("Document units must be absolute like `mm', `pt' or `px', but found `%s'", + value); + /* fixme: Don't use g_log (see above). */ + } + } + nv->doc_units = new_unit; + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + default: + if (((SPObjectClass *) (parent_class))->set) { + ((SPObjectClass *) (parent_class))->set(object, key, value); + } + break; + } +} + +static void sp_namedview_child_added(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref) +{ + SPNamedView *nv = (SPNamedView *) object; + + if (((SPObjectClass *) (parent_class))->child_added) { + (* ((SPObjectClass *) (parent_class))->child_added)(object, child, ref); + } + + const gchar *id = child->attribute("id"); + SPObject *no = object->document->getObjectById(id); + g_assert(SP_IS_OBJECT(no)); + + if (SP_IS_GUIDE(no)) { + SPGuide *g = (SPGuide *) no; + nv->guides = g_slist_prepend(nv->guides, g); + g_object_set(G_OBJECT(g), "color", nv->guidecolor, "hicolor", nv->guidehicolor, NULL); + if (nv->editable) { + for (GSList *l = nv->views; l != NULL; l = l->next) { + sp_guide_show(g, static_cast(l->data)->guides, (GCallback) sp_dt_guide_event); + if (static_cast(l->data)->guides_active) + sp_guide_sensitize(g, + SP_DT_CANVAS(static_cast (l->data)), + TRUE); + if (nv->showguides) { + for (GSList *v = SP_GUIDE(g)->views; v != NULL; v = v->next) { + sp_canvas_item_show(SP_CANVAS_ITEM(v->data)); + } + } else { + for (GSList *v = SP_GUIDE(g)->views; v != NULL; v = v->next) { + sp_canvas_item_hide(SP_CANVAS_ITEM(v->data)); + } + } + } + } + } +} + +static void sp_namedview_remove_child(SPObject *object, Inkscape::XML::Node *child) +{ + SPNamedView *nv = (SPNamedView *) object; + + GSList **ref = &nv->guides; + for ( GSList *iter = nv->guides ; iter ; iter = iter->next ) { + if ( SP_OBJECT_REPR((SPObject *)iter->data) == child ) { + *ref = iter->next; + iter->next = NULL; + g_slist_free_1(iter); + break; + } + ref = &iter->next; + } + + if (((SPObjectClass *) (parent_class))->remove_child) { + (* ((SPObjectClass *) (parent_class))->remove_child)(object, child); + } +} + +static Inkscape::XML::Node *sp_namedview_write(SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + if ( ( flags & SP_OBJECT_WRITE_EXT ) && + repr != SP_OBJECT_REPR(object) ) + { + if (repr) { + repr->mergeFrom(SP_OBJECT_REPR(object), "id"); + } else { + repr = SP_OBJECT_REPR(object)->duplicate(); + } + } + + return repr; +} + +void SPNamedView::show(SPDesktop *desktop) +{ + for (GSList *l = guides; l != NULL; l = l->next) { + sp_guide_show(SP_GUIDE(l->data), desktop->guides, (GCallback) sp_dt_guide_event); + if (desktop->guides_active) { + sp_guide_sensitize(SP_GUIDE(l->data), SP_DT_CANVAS(desktop), TRUE); + } + if (showguides) { + for (GSList *v = SP_GUIDE(l->data)->views; v != NULL; v = v->next) { + sp_canvas_item_show(SP_CANVAS_ITEM(v->data)); + } + } else { + for (GSList *v = SP_GUIDE(l->data)->views; v != NULL; v = v->next) { + sp_canvas_item_hide(SP_CANVAS_ITEM(v->data)); + } + } + } + + views = g_slist_prepend(views, desktop); + + SPCanvasItem *item = sp_canvas_item_new(SP_DT_GRID(desktop), SP_TYPE_CGRID, NULL); + // since we're keeping a copy, we need to bump up the ref count + gtk_object_ref(GTK_OBJECT(item)); + gridviews = g_slist_prepend(gridviews, item); + sp_namedview_setup_grid_item(this, item); +} + +/* + * Restores window geometry from the document settings + */ +void sp_namedview_window_from_document(SPDesktop *desktop) +{ + SPNamedView *nv = desktop->namedview; + gint save_geometry = prefs_get_int_attribute("options.savewindowgeometry", "value", 0); + + // restore window size and position + if (save_geometry) { + if (nv->window_width != -1 && nv->window_height != -1) + desktop->setWindowSize(nv->window_width, nv->window_height); + if (nv->window_x != -1 && nv->window_y != -1) + desktop->setWindowPosition(NR::Point(nv->window_x, nv->window_y)); + } + + // restore zoom and view + if (nv->zoom != 0 && nv->zoom != HUGE_VAL && !isNaN(nv->zoom) + && nv->cx != HUGE_VAL && !isNaN(nv->cx) + && nv->cy != HUGE_VAL && !isNaN(nv->cy)) { + desktop->zoom_absolute(nv->cx, nv->cy, nv->zoom); + } else if (SP_DT_DOCUMENT(desktop)) { // document without saved zoom, zoom to its page + desktop->zoom_page(); + } + + // cancel any history of zooms up to this point + if (desktop->zooms_past) { + g_list_free(desktop->zooms_past); + desktop->zooms_past = NULL; + } + + SPObject *layer = NULL; + SPDocument *document = desktop->doc(); + if ( nv->default_layer_id != 0 ) { + layer = document->getObjectById(g_quark_to_string(nv->default_layer_id)); + } + // don't use that object if it's not at least group + if ( !layer || !SP_IS_GROUP(layer) ) { + layer = NULL; + } + // if that didn't work out, look for the topmost layer + if (!layer) { + SPObject *iter = sp_object_first_child(SP_DOCUMENT_ROOT(document)); + for ( ; iter ; iter = SP_OBJECT_NEXT(iter) ) { + if (desktop->isLayer(iter)) { + layer = iter; + } + } + } + if (layer) { + desktop->setCurrentLayer(layer); + } +} + +void sp_namedview_document_from_window(SPDesktop *desktop) +{ + gint save_geometry = prefs_get_int_attribute("options.savewindowgeometry", "value", 0); + Inkscape::XML::Node *view = SP_OBJECT_REPR(desktop->namedview); + NR::Rect const r = desktop->get_display_area(); + + // saving window geometry is not undoable + gboolean saved = sp_document_get_undo_sensitive(SP_DT_DOCUMENT(desktop)); + sp_document_set_undo_sensitive(SP_DT_DOCUMENT(desktop), FALSE); + + sp_repr_set_svg_double(view, "inkscape:zoom", desktop->current_zoom()); + sp_repr_set_svg_double(view, "inkscape:cx", r.midpoint()[NR::X]); + sp_repr_set_svg_double(view, "inkscape:cy", r.midpoint()[NR::Y]); + + if (save_geometry) { + gint w, h, x, y; + desktop->getWindowGeometry(x, y, w, h); + sp_repr_set_int(view, "inkscape:window-width", w); + sp_repr_set_int(view, "inkscape:window-height", h); + sp_repr_set_int(view, "inkscape:window-x", x); + sp_repr_set_int(view, "inkscape:window-y", y); + } + + view->setAttribute("inkscape:current-layer", SP_OBJECT_ID(desktop->currentLayer())); + + // restore undoability + sp_document_set_undo_sensitive(SP_DT_DOCUMENT(desktop), saved); +} + +void SPNamedView::hide(SPDesktop const *desktop) +{ + g_assert(desktop != NULL); + g_assert(g_slist_find(views, desktop)); + + for (GSList *l = guides; l != NULL; l = l->next) { + sp_guide_hide(SP_GUIDE(l->data), SP_DT_CANVAS(desktop)); + } + + views = g_slist_remove(views, desktop); + + GSList *l; + for (l = gridviews; l != NULL; l = l->next) { + if (SP_CANVAS_ITEM(l->data)->canvas == SP_DT_CANVAS(desktop)) { + break; + } + } + + g_assert(l); + + sp_canvas_item_hide(SP_CANVAS_ITEM(l->data)); + gtk_object_unref(GTK_OBJECT(l->data)); + gridviews = g_slist_remove(gridviews, l->data); +} + +void SPNamedView::activateGuides(gpointer desktop, gboolean active) +{ + g_assert(desktop != NULL); + g_assert(g_slist_find(views, desktop)); + + SPDesktop *dt = static_cast(desktop); + + for (GSList *l = guides; l != NULL; l = l->next) { + sp_guide_sensitize(SP_GUIDE(l->data), SP_DT_CANVAS(dt), active); + } +} + +static void sp_namedview_setup_guides(SPNamedView *nv) +{ + for (GSList *l = nv->guides; l != NULL; l = l->next) { + if (nv->showguides) { + for (GSList *v = SP_GUIDE(l->data)->views; v != NULL; v = v->next) { + sp_canvas_item_show(SP_CANVAS_ITEM(v->data)); + } + } else { + for (GSList *v = SP_GUIDE(l->data)->views; v != NULL; v = v->next) { + sp_canvas_item_hide(SP_CANVAS_ITEM(v->data)); + } + } + } +} + +void sp_namedview_toggle_guides(SPDocument *doc, Inkscape::XML::Node *repr) +{ + unsigned int v; + unsigned int set = sp_repr_get_boolean(repr, "showguides", &v); + if (!set) { // hide guides if not specified, for backwards compatibility + v = FALSE; + } else { + v = !v; + } + + gboolean saved = sp_document_get_undo_sensitive(doc); + sp_document_set_undo_sensitive(doc, FALSE); + + sp_repr_set_boolean(repr, "showguides", v); + + doc->rroot->setAttribute("sodipodi:modified", "true"); + sp_document_set_undo_sensitive(doc, saved); +} + +void sp_namedview_toggle_grid(SPDocument *doc, Inkscape::XML::Node *repr) +{ + unsigned int v; + sp_repr_get_boolean(repr, "showgrid", &v); + v = !v; + + gboolean saved = sp_document_get_undo_sensitive(doc); + sp_document_set_undo_sensitive(doc, FALSE); + + sp_repr_set_boolean(repr, "showgrid", v); + + doc->rroot->setAttribute("sodipodi:modified", "true"); + sp_document_set_undo_sensitive(doc, saved); +} + +static void sp_namedview_setup_grid(SPNamedView *nv) +{ + for (GSList *l = nv->gridviews; l != NULL; l = l->next) { + sp_namedview_setup_grid_item(nv, SP_CANVAS_ITEM(l->data)); + } +} + +static void sp_namedview_setup_grid_item(SPNamedView *nv, SPCanvasItem *item) +{ + if (nv->showgrid) { + sp_canvas_item_show(item); + } else { + sp_canvas_item_hide(item); + } + + sp_canvas_item_set((GtkObject *) item, + "color", nv->gridcolor, + "originx", nv->gridorigin[NR::X], + "originy", nv->gridorigin[NR::Y], + "spacingx", nv->gridspacing[NR::X], + "spacingy", nv->gridspacing[NR::Y], + "empcolor", nv->gridempcolor, + "empspacing", nv->gridempspacing, + NULL); +} + +gchar const *SPNamedView::getName() const +{ + SPException ex; + SP_EXCEPTION_INIT(&ex); + return sp_object_getAttribute(SP_OBJECT(this), "id", &ex); +} + +guint SPNamedView::getViewCount() +{ + return ++viewcount; +} + +GSList const *SPNamedView::getViewList() const +{ + return views; +} + +/* This should be moved somewhere */ + +static gboolean sp_str_to_bool(const gchar *str) +{ + if (str) { + if (!g_strcasecmp(str, "true") || + !g_strcasecmp(str, "yes") || + !g_strcasecmp(str, "y") || + (atoi(str) != 0)) { + return TRUE; + } + } + + return FALSE; +} + +/* fixme: Collect all these length parsing methods and think common sane API */ + +static gboolean sp_nv_read_length(const gchar *str, guint base, gdouble *val, const SPUnit **unit) +{ + if (!str) { + return FALSE; + } + + gchar *u; + gdouble v = g_ascii_strtod(str, &u); + if (!u) { + return FALSE; + } + while (isspace(*u)) { + u += 1; + } + + if (!*u) { + /* No unit specified - keep default */ + *val = v; + return TRUE; + } + + if (base & SP_UNIT_DEVICE) { + if (u[0] && u[1] && !isalnum(u[2]) && !strncmp(u, "px", 2)) { + *unit = &sp_unit_get_by_id(SP_UNIT_PX); + *val = v; + return TRUE; + } + } + + if (base & SP_UNIT_ABSOLUTE) { + if (!strncmp(u, "pt", 2)) { + *unit = &sp_unit_get_by_id(SP_UNIT_PT); + } else if (!strncmp(u, "mm", 2)) { + *unit = &sp_unit_get_by_id(SP_UNIT_MM); + } else if (!strncmp(u, "cm", 2)) { + *unit = &sp_unit_get_by_id(SP_UNIT_CM); + } else if (!strncmp(u, "m", 1)) { + *unit = &sp_unit_get_by_id(SP_UNIT_M); + } else if (!strncmp(u, "in", 2)) { + *unit = &sp_unit_get_by_id(SP_UNIT_IN); + } else { + return FALSE; + } + *val = v; + return TRUE; + } + + return FALSE; +} + +static gboolean sp_nv_read_opacity(const gchar *str, guint32 *color) +{ + if (!str) { + return FALSE; + } + + gchar *u; + gdouble v = strtod(str, &u); + if (!u) { + return FALSE; + } + v = CLAMP(v, 0.0, 1.0); + + *color = (*color & 0xffffff00) | (guint32) floor(v * 255.9999); + + return TRUE; +} + +SPNamedView *sp_document_namedview(SPDocument *document, const gchar *id) +{ + g_return_val_if_fail(document != NULL, NULL); + + SPObject *nv = sp_item_group_get_child_by_name((SPGroup *) document->root, NULL, "sodipodi:namedview"); + g_assert(nv != NULL); + + if (id == NULL) { + return (SPNamedView *) nv; + } + + while (nv && strcmp(nv->id, id)) { + nv = sp_item_group_get_child_by_name((SPGroup *) document->root, nv, "sodipodi:namedview"); + } + + return (SPNamedView *) nv; +} + +/** + * Returns namedview's default metric. + */ +SPMetric SPNamedView::getDefaultMetric() const +{ + if (doc_units) { + return sp_unit_get_metric(doc_units); + } else { + return SP_PT; + } +} + +SPNamedView::SnapperList SPNamedView::getSnappers() const +{ + SnapperList s; + s.push_back(&grid_snapper); + s.push_back(&guide_snapper); + s.push_back(&object_snapper); + return s; +} + +/* + 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/sp-namedview.h b/src/sp-namedview.h new file mode 100644 index 000000000..f96207794 --- /dev/null +++ b/src/sp-namedview.h @@ -0,0 +1,136 @@ +#ifndef INKSCAPE_SP_NAMEDVIEW_H +#define INKSCAPE_SP_NAMEDVIEW_H + +/* + * implementation + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) Lauris Kaplinski 2000-2002 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define SP_TYPE_NAMEDVIEW (sp_namedview_get_type()) +#define SP_NAMEDVIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SP_TYPE_NAMEDVIEW, SPNamedView)) +#define SP_NAMEDVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SP_TYPE_NAMEDVIEW, SPNamedViewClass)) +#define SP_IS_NAMEDVIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SP_TYPE_NAMEDVIEW)) +#define SP_IS_NAMEDVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SP_TYPE_NAMEDVIEW)) + +#include "helper/helper-forward.h" +#include "sp-object-group.h" +#include "libnr/nr-point.h" +#include "sp-metric.h" +#include "grid-snapper.h" +#include "guide-snapper.h" +#include "object-snapper.h" + + +enum { + SP_BORDER_LAYER_BOTTOM, + SP_BORDER_LAYER_TOP +}; + +struct SPNamedView : public SPObjectGroup { + unsigned int editable : 1; + unsigned int showgrid : 1; + unsigned int showguides : 1; + unsigned int showborder : 1; + unsigned int showpageshadow : 1; + unsigned int borderlayer : 2; + + int snap_grid_bbox; + int snap_grid_point; + int snap_guide_bbox; + int snap_guide_point; + int snap_object_bbox; + int snap_object_point; + int snap_object_paths; + int snap_object_nodes; + + double zoom; + double cx; + double cy; + gint window_width; + gint window_height; + gint window_x; + gint window_y; + + Inkscape::GridSnapper grid_snapper; + Inkscape::GuideSnapper guide_snapper; + Inkscape::ObjectSnapper object_snapper; + + SPUnit const *gridunit; + /* Grid data is in points regardless of unit */ + NR::Point gridorigin; + gdouble gridspacing[2]; + gint gridempspacing; + + SPUnit const *doc_units; + + SPUnit const *gridtoleranceunit; + gdouble gridtolerance; + + SPUnit const *guidetoleranceunit; + gdouble guidetolerance; + + SPUnit const *objecttoleranceunit; + gdouble objecttolerance; + + bool has_abs_tolerance; + + GQuark default_layer_id; + + guint32 gridcolor; + guint32 gridempcolor; + guint32 guidecolor; + guint32 guidehicolor; + guint32 bordercolor; + guint32 pagecolor; + guint32 pageshadow; + + GSList *guides; + GSList *views; + GSList *gridviews; + gint viewcount; + + void show(SPDesktop *desktop); + void hide(SPDesktop const *desktop); + void activateGuides(gpointer desktop, gboolean active); + gchar const *getName() const; + guint getViewCount(); + GSList const *getViewList() const; + SPMetric getDefaultMetric() const; + + typedef std::list SnapperList; + SnapperList getSnappers() const; +}; + +struct SPNamedViewClass { + SPObjectGroupClass parent_class; +}; + +GType sp_namedview_get_type(); + +SPNamedView *sp_document_namedview(SPDocument *document, gchar const *name); + +void sp_namedview_window_from_document(SPDesktop *desktop); +void sp_namedview_document_from_window(SPDesktop *desktop); + +void sp_namedview_toggle_guides(SPDocument *doc, Inkscape::XML::Node *repr); +void sp_namedview_toggle_grid(SPDocument *doc, Inkscape::XML::Node *repr); + +#endif /* !INKSCAPE_SP_NAMEDVIEW_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:encoding=utf-8:textwidth=99 : diff --git a/src/sp-object-group.cpp b/src/sp-object-group.cpp new file mode 100644 index 000000000..ec698592f --- /dev/null +++ b/src/sp-object-group.cpp @@ -0,0 +1,132 @@ +#define __SP_OBJECTGROUP_C__ + +/* + * Abstract base class for non-item groups + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2003 Authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-object-group.h" +#include "xml/repr.h" + +static void sp_objectgroup_class_init (SPObjectGroupClass *klass); +static void sp_objectgroup_init (SPObjectGroup *objectgroup); + +static void sp_objectgroup_child_added (SPObject * object, Inkscape::XML::Node * child, Inkscape::XML::Node * ref); +static void sp_objectgroup_remove_child (SPObject * object, Inkscape::XML::Node * child); +static void sp_objectgroup_order_changed (SPObject * object, Inkscape::XML::Node * child, Inkscape::XML::Node * old_ref, Inkscape::XML::Node * new_ref); +static Inkscape::XML::Node *sp_objectgroup_write (SPObject *object, Inkscape::XML::Node *repr, guint flags); + +static SPObjectClass *parent_class; + +GType +sp_objectgroup_get_type (void) +{ + static GType objectgroup_type = 0; + if (!objectgroup_type) { + GTypeInfo objectgroup_info = { + sizeof (SPObjectGroupClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_objectgroup_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (SPObjectGroup), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_objectgroup_init, + NULL, /* value_table */ + }; + objectgroup_type = g_type_register_static (SP_TYPE_OBJECT, "SPObjectGroup", &objectgroup_info, (GTypeFlags)0); + } + return objectgroup_type; +} + +static void +sp_objectgroup_class_init (SPObjectGroupClass *klass) +{ + GObjectClass * object_class; + SPObjectClass * sp_object_class; + + object_class = (GObjectClass *) klass; + sp_object_class = (SPObjectClass *) klass; + + parent_class = (SPObjectClass *)g_type_class_ref (SP_TYPE_OBJECT); + + sp_object_class->child_added = sp_objectgroup_child_added; + sp_object_class->remove_child = sp_objectgroup_remove_child; + sp_object_class->order_changed = sp_objectgroup_order_changed; + sp_object_class->write = sp_objectgroup_write; +} + +static void +sp_objectgroup_init (SPObjectGroup *objectgroup) +{ +} + +static void +sp_objectgroup_child_added (SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref) +{ + if (((SPObjectClass *) (parent_class))->child_added) + (* ((SPObjectClass *) (parent_class))->child_added) (object, child, ref); + + object->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +static void +sp_objectgroup_remove_child (SPObject *object, Inkscape::XML::Node *child) +{ + if (((SPObjectClass *) (parent_class))->remove_child) + (* ((SPObjectClass *) (parent_class))->remove_child) (object, child); + + object->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +static void +sp_objectgroup_order_changed (SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *old_ref, Inkscape::XML::Node *new_ref) +{ + if (((SPObjectClass *) (parent_class))->order_changed) + (* ((SPObjectClass *) (parent_class))->order_changed) (object, child, old_ref, new_ref); + + object->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +static Inkscape::XML::Node * +sp_objectgroup_write (SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + SPObjectGroup *group; + SPObject *child; + Inkscape::XML::Node *crepr; + + group = SP_OBJECTGROUP (object); + + if (flags & SP_OBJECT_WRITE_BUILD) { + GSList *l; + if (!repr) repr = sp_repr_new ("svg:g"); + l = NULL; + for ( child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) { + crepr = child->updateRepr(NULL, flags); + if (crepr) l = g_slist_prepend (l, crepr); + } + while (l) { + repr->addChild((Inkscape::XML::Node *) l->data, NULL); + Inkscape::GC::release((Inkscape::XML::Node *) l->data); + l = g_slist_remove (l, l->data); + } + } else { + for ( child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) { + child->updateRepr(flags); + } + } + + if (((SPObjectClass *) (parent_class))->write) + ((SPObjectClass *) (parent_class))->write (object, repr, flags); + + return repr; +} + diff --git a/src/sp-object-group.h b/src/sp-object-group.h new file mode 100644 index 000000000..5d67df6fe --- /dev/null +++ b/src/sp-object-group.h @@ -0,0 +1,33 @@ +#ifndef __SP_OBJECTGROUP_H__ +#define __SP_OBJECTGROUP_H__ + +/* + * Abstract base class for non-item groups + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 1999-2003 Authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-object.h" + +#define SP_TYPE_OBJECTGROUP (sp_objectgroup_get_type ()) +#define SP_OBJECTGROUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_OBJECTGROUP, SPObjectGroup)) +#define SP_OBJECTGROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_OBJECTGROUP, SPObjectGroupClass)) +#define SP_IS_OBJECTGROUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_OBJECTGROUP)) +#define SP_IS_OBJECTGROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_OBJECTGROUP)) + +struct SPObjectGroup : public SPObject { +}; + +struct SPObjectGroupClass { + SPObjectClass parent_class; +}; + +GType sp_objectgroup_get_type (void); + +#endif diff --git a/src/sp-object-repr.cpp b/src/sp-object-repr.cpp new file mode 100644 index 000000000..4b631f3d5 --- /dev/null +++ b/src/sp-object-repr.cpp @@ -0,0 +1,208 @@ +#define __SP_OBJECT_REPR_C__ + +/* + * Object type dictionary and build frontend + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2003 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-defs.h" +#include "sp-symbol.h" +#include "sp-marker.h" +#include "sp-use.h" +#include "sp-root.h" +#include "sp-image.h" +#include "sp-linear-gradient-fns.h" +#include "sp-path.h" +#include "sp-radial-gradient-fns.h" +#include "sp-rect.h" +#include "sp-ellipse.h" +#include "sp-star.h" +#include "sp-stop-fns.h" +#include "sp-spiral.h" +#include "sp-offset.h" +#include "sp-line.h" +#include "sp-metadata.h" +#include "sp-polyline.h" +#include "sp-textpath.h" +#include "sp-tspan.h" +#include "sp-pattern.h" +#include "sp-clippath.h" +#include "sp-mask.h" +#include "sp-anchor.h" +//#include "sp-animation.h" +#include "sp-flowdiv.h" +#include "sp-flowregion.h" +#include "sp-flowtext.h" +#include "sp-style-elem.h" +#include "xml/repr.h" + +enum NameType { REPR_NAME, SODIPODI_TYPE }; +static unsigned const N_NAME_TYPES = SODIPODI_TYPE + 1; + +static GType name_to_gtype(NameType name_type, gchar const *name); + +/** + * Construct an SPRoot and all its descendents from the given repr. + */ +SPObject * +sp_object_repr_build_tree(SPDocument *document, Inkscape::XML::Node *repr) +{ + g_assert(document != NULL); + g_assert(repr != NULL); + + gchar const * const name = repr->name(); + g_assert(name != NULL); + GType const type = name_to_gtype(REPR_NAME, name); + g_assert(g_type_is_a(type, SP_TYPE_ROOT)); + gpointer newobj = g_object_new(type, 0); + g_assert(newobj != NULL); + SPObject *const object = SP_OBJECT(newobj); + g_assert(object != NULL); + sp_object_invoke_build(object, document, repr, FALSE); + + return object; +} + +GType +sp_repr_type_lookup(Inkscape::XML::Node *repr) +{ + if ( repr->type() == Inkscape::XML::TEXT_NODE ) { + return SP_TYPE_STRING; + } else if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) { + gchar const * const type_name = repr->attribute("sodipodi:type"); + return ( type_name + ? name_to_gtype(SODIPODI_TYPE, type_name) + : name_to_gtype(REPR_NAME, repr->name()) ); + } else { + return 0; + } +} + +static GHashTable *t2dtable[N_NAME_TYPES] = {NULL}; + +static void +populate_dtables() +{ + struct NameTypeEntry { char const *const name; GType const type_id; }; + NameTypeEntry const repr_name_entries[] = { + { "svg:a", SP_TYPE_ANCHOR }, + //{ "svg:animate", SP_TYPE_ANIMATE }, + { "svg:circle", SP_TYPE_CIRCLE }, + { "svg:clipPath", SP_TYPE_CLIPPATH }, + { "svg:defs", SP_TYPE_DEFS }, + { "svg:ellipse", SP_TYPE_ELLIPSE }, + /* Note: flow* elements are proposed additions for SVG 1.2, they aren't in + SVG 1.1. */ + { "svg:flowDiv", SP_TYPE_FLOWDIV }, + { "svg:flowLine", SP_TYPE_FLOWLINE }, + { "svg:flowPara", SP_TYPE_FLOWPARA }, + { "svg:flowRegion", SP_TYPE_FLOWREGION }, + { "svg:flowRegionBreak", SP_TYPE_FLOWREGIONBREAK }, + { "svg:flowRegionExclude", SP_TYPE_FLOWREGIONEXCLUDE }, + { "svg:flowRoot", SP_TYPE_FLOWTEXT }, + { "svg:flowSpan", SP_TYPE_FLOWTSPAN }, + { "svg:g", SP_TYPE_GROUP }, + { "svg:image", SP_TYPE_IMAGE }, + { "svg:line", SP_TYPE_LINE }, + { "svg:linearGradient", SP_TYPE_LINEARGRADIENT }, + { "svg:marker", SP_TYPE_MARKER }, + { "svg:mask", SP_TYPE_MASK }, + { "svg:metadata", SP_TYPE_METADATA }, + { "svg:path", SP_TYPE_PATH }, + { "svg:pattern", SP_TYPE_PATTERN }, + { "svg:polygon", SP_TYPE_POLYGON }, + { "svg:polyline", SP_TYPE_POLYLINE }, + { "svg:radialGradient", SP_TYPE_RADIALGRADIENT }, + { "svg:rect", SP_TYPE_RECT }, + { "svg:stop", SP_TYPE_STOP }, + { "svg:svg", SP_TYPE_ROOT }, + { "svg:style", SP_TYPE_STYLE_ELEM }, + { "svg:switch", SP_TYPE_GROUP }, + { "svg:symbol", SP_TYPE_SYMBOL }, + { "svg:text", SP_TYPE_TEXT }, + { "svg:textPath", SP_TYPE_TEXTPATH }, + { "svg:tspan", SP_TYPE_TSPAN }, + { "svg:use", SP_TYPE_USE } + }; + NameTypeEntry const sodipodi_name_entries[] = { + { "arc", SP_TYPE_ARC }, + { "inkscape:offset", SP_TYPE_OFFSET }, + { "spiral", SP_TYPE_SPIRAL }, + { "star", SP_TYPE_STAR } + }; + + NameTypeEntry const *const t2entries[] = { + repr_name_entries, + sodipodi_name_entries + }; + unsigned const t2n_entries[] = { + G_N_ELEMENTS(repr_name_entries), + G_N_ELEMENTS(sodipodi_name_entries) + }; + + for (unsigned nt = 0; nt < N_NAME_TYPES; ++nt) { + NameTypeEntry const *const entries = t2entries[nt]; + unsigned const n_entries = t2n_entries[nt]; + GHashTable *&dtable = t2dtable[nt]; + + dtable = g_hash_table_new(g_str_hash, g_str_equal); + for (unsigned i = 0; i < n_entries; ++i) { + g_hash_table_insert(dtable, + (void *)entries[i].name, + (gpointer) entries[i].type_id); + } + } +} + +static inline void +ensure_dtables_populated() +{ + if (!*t2dtable) { + populate_dtables(); + } +} + +static GType +name_to_gtype(NameType const name_type, gchar const *name) +{ + ensure_dtables_populated(); + + gpointer const data = g_hash_table_lookup(t2dtable[name_type], name); + return ( ( data == NULL ) + ? SP_TYPE_OBJECT + : (GType) data ); +} + +void +sp_object_type_register(gchar const *name, GType const gtype) +{ + GType const current = name_to_gtype(REPR_NAME, name); + if (current == SP_TYPE_OBJECT) { + g_hash_table_insert(t2dtable[REPR_NAME], + const_cast(name), + (gpointer) gtype); + } else { + /* Already registered. */ + if (current != gtype) { + g_warning("repr type `%s' already registered as type #%lu, ignoring attempt to re-register as #%lu.", + name, current, gtype); + } + } +} + +/* + 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 : diff --git a/src/sp-object-repr.h b/src/sp-object-repr.h new file mode 100644 index 000000000..f3a80f83c --- /dev/null +++ b/src/sp-object-repr.h @@ -0,0 +1,41 @@ +#ifndef __SP_OBJECT_REPR_H__ +#define __SP_OBJECT_REPR_H__ + +/* + * Object type dictionary and build frontend + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2003 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "forward.h" + +namespace Inkscape { +namespace XML { +class Node; +} +} + + +SPObject *sp_object_repr_build_tree (SPDocument *document, Inkscape::XML::Node *repr); + +GType sp_repr_type_lookup (Inkscape::XML::Node *repr); + +void sp_object_type_register(gchar const *name, GType type); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/sp-object.cpp b/src/sp-object.cpp new file mode 100644 index 000000000..d23a4d640 --- /dev/null +++ b/src/sp-object.cpp @@ -0,0 +1,1548 @@ +#define __SP_OBJECT_C__ +/** \file + * SPObject implementation. + * + * Authors: + * Lauris Kaplinski + * bulia byak + * + * Copyright (C) 1999-2005 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +/** \class SPObject + * + * SPObject is an abstract base class of all of the document nodes at the + * SVG document level. Each SPObject subclass implements a certain SVG + * element node type, or is an abstract base class for different node + * types. The SPObject layer is bound to the SPRepr layer, closely + * following the SPRepr mutations via callbacks. During creation, + * SPObject parses and interprets all textual attributes and CSS style + * strings of the SPRepr, and later updates the internal state whenever + * it receives a signal about a change. The opposite is not true - there + * are methods manipulating SPObjects directly and such changes do not + * propagate to the SPRepr layer. This is important for implementation of + * the undo stack, animations and other features. + * + * SPObjects are bound to the higher-level container SPDocument, which + * provides document level functionality such as the undo stack, + * dictionary and so on. Source: doc/architecture.txt + */ + + +#include "helper/sp-marshal.h" +#include "xml/node-event-vector.h" +#include "attributes.h" +#include "document.h" +#include "style.h" +#include "sp-object-repr.h" +#include "sp-root.h" +#include "streq.h" +#include "strneq.h" +#include "xml/repr.h" +#include "xml/node-fns.h" +#include "debug/event-tracker.h" + +#include "algorithms/longest-common-suffix.h" +using std::memcpy; +using std::strchr; +using std::strcmp; +using std::strlen; +using std::strstr; + +#define noSP_OBJECT_DEBUG_CASCADE + +#define noSP_OBJECT_DEBUG + +#ifdef SP_OBJECT_DEBUG +# define debug(f, a...) { g_print("%s(%d) %s:", \ + __FILE__,__LINE__,__FUNCTION__); \ + g_print(f, ## a); \ + g_print("\n"); \ + } +#else +# define debug(f, a...) /**/ +#endif + +static void sp_object_class_init(SPObjectClass *klass); +static void sp_object_init(SPObject *object); +static void sp_object_finalize(GObject *object); + +static void sp_object_child_added(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref); +static void sp_object_remove_child(SPObject *object, Inkscape::XML::Node *child); +static void sp_object_order_changed(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *old_ref, Inkscape::XML::Node *new_ref); + +static void sp_object_release(SPObject *object); +static void sp_object_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); + +static void sp_object_private_set(SPObject *object, unsigned int key, gchar const *value); +static Inkscape::XML::Node *sp_object_private_write(SPObject *object, Inkscape::XML::Node *repr, guint flags); + +/* Real handlers of repr signals */ + +static void sp_object_repr_attr_changed(Inkscape::XML::Node *repr, gchar const *key, gchar const *oldval, gchar const *newval, bool is_interactive, gpointer data); + +static void sp_object_repr_content_changed(Inkscape::XML::Node *repr, gchar const *oldcontent, gchar const *newcontent, gpointer data); + +static void sp_object_repr_child_added(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, gpointer data); +static void sp_object_repr_child_removed(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, void *data); + +static void sp_object_repr_order_changed(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *old, Inkscape::XML::Node *newer, gpointer data); + +static gchar *sp_object_get_unique_id(SPObject *object, gchar const *defid); + +guint update_in_progress = 0; // guard against update-during-update + +enum {RELEASE, MODIFIED, LAST_SIGNAL}; + +Inkscape::XML::NodeEventVector object_event_vector = { + sp_object_repr_child_added, + sp_object_repr_child_removed, + sp_object_repr_attr_changed, + sp_object_repr_content_changed, + sp_object_repr_order_changed +}; + +static GObjectClass *parent_class; +static guint object_signals[LAST_SIGNAL] = {0}; + +/** + * Registers the SPObject class with Gdk and returns its type number. + */ +GType +sp_object_get_type(void) +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPObjectClass), + NULL, NULL, + (GClassInitFunc) sp_object_class_init, + NULL, NULL, + sizeof(SPObject), + 16, + (GInstanceInitFunc) sp_object_init, + NULL + }; + type = g_type_register_static(G_TYPE_OBJECT, "SPObject", &info, (GTypeFlags)0); + } + return type; +} + +/** + * Initializes the SPObject vtable. + */ +static void +sp_object_class_init(SPObjectClass *klass) +{ + GObjectClass *object_class; + + object_class = (GObjectClass *) klass; + + parent_class = (GObjectClass *) g_type_class_ref(G_TYPE_OBJECT); + + object_signals[RELEASE] = g_signal_new("release", + G_TYPE_FROM_CLASS(klass), + (GSignalFlags)(G_SIGNAL_RUN_CLEANUP | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS), + G_STRUCT_OFFSET(SPObjectClass, release), + NULL, NULL, + sp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + object_signals[MODIFIED] = g_signal_new("modified", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET(SPObjectClass, modified), + NULL, NULL, + sp_marshal_NONE__UINT, + G_TYPE_NONE, 1, G_TYPE_UINT); + + object_class->finalize = sp_object_finalize; + + klass->child_added = sp_object_child_added; + klass->remove_child = sp_object_remove_child; + klass->order_changed = sp_object_order_changed; + + klass->release = sp_object_release; + + klass->build = sp_object_build; + + klass->set = sp_object_private_set; + klass->write = sp_object_private_write; +} + +/** + * Callback to initialize the SPObject object. + */ +static void +sp_object_init(SPObject *object) +{ + debug("id=%x, typename=%s",object, g_type_name_from_instance((GTypeInstance*)object)); + + object->hrefcount = 0; + object->_total_hrefcount = 0; + object->document = NULL; + object->children = object->_last_child = NULL; + object->parent = object->next = NULL; + object->repr = NULL; + object->id = NULL; + object->style = NULL; + + object->_collection_policy = SPObject::COLLECT_WITH_PARENT; + + new (&object->_delete_signal) sigc::signal(); + object->_successor = NULL; + + object->_label = NULL; + object->_default_label = NULL; +} + +/** + * Callback to destroy all members and connections of object and itself. + */ +static void +sp_object_finalize(GObject *object) +{ + SPObject *spobject = (SPObject *)object; + + g_free(spobject->_label); + g_free(spobject->_default_label); + spobject->_label = NULL; + spobject->_default_label = NULL; + + if (spobject->_successor) { + sp_object_unref(spobject->_successor, NULL); + spobject->_successor = NULL; + } + + if (((GObjectClass *) (parent_class))->finalize) { + (* ((GObjectClass *) (parent_class))->finalize)(object); + } + + spobject->_delete_signal.~signal(); +} + +namespace { + +Inkscape::Util::SharedCStringPtr stringify(SPObject *obj) { + char *temp=g_strdup_printf("%p", obj); + Inkscape::Util::SharedCStringPtr result=Inkscape::Util::SharedCStringPtr::copy(temp); + g_free(temp); + return result; +} + +Inkscape::Util::SharedCStringPtr stringify(unsigned n) { + char *temp=g_strdup_printf("%u", n); + Inkscape::Util::SharedCStringPtr result=Inkscape::Util::SharedCStringPtr::copy(temp); + g_free(temp); + return result; +} + +class RefEvent : public Inkscape::Debug::Event { +public: + enum Type { REF, UNREF }; + + RefEvent(SPObject *object, Type type) + : _object(stringify(object)), _refcount(G_OBJECT(object)->ref_count), + _type(type) + {} + + static Category category() { return REFCOUNT; } + + Inkscape::Util::SharedCStringPtr name() const { + if ( _type == REF) { + return Inkscape::Util::SharedCStringPtr::coerce("sp-object-ref"); + } else { + return Inkscape::Util::SharedCStringPtr::coerce("sp-object-unref"); + } + } + unsigned propertyCount() const { return 2; } + PropertyPair property(unsigned index) const { + switch (index) { + case 0: + return PropertyPair("object", _object); + case 1: + return PropertyPair("refcount", stringify( _type == REF ? _refcount + 1 : _refcount - 1 )); + default: + return PropertyPair(); + } + } + +private: + Inkscape::Util::SharedCStringPtr _object; + unsigned _refcount; + Type _type; +}; + +} + +/** + * Increase reference count of object, with possible debugging. + * + * \param owner If non-NULL, make debug log entry. + * \return object, NULL is error. + * \pre object points to real object + */ +SPObject * +sp_object_ref(SPObject *object, SPObject *owner) +{ + g_return_val_if_fail(object != NULL, NULL); + g_return_val_if_fail(SP_IS_OBJECT(object), NULL); + g_return_val_if_fail(!owner || SP_IS_OBJECT(owner), NULL); + + Inkscape::Debug::EventTracker<> tracker; + tracker.set(object, RefEvent::REF); + + g_object_ref(G_OBJECT(object)); + + return object; +} + +/** + * Decrease reference count of object, with possible debugging and + * finalization. + * + * \param owner If non-NULL, make debug log entry. + * \return always NULL + * \pre object points to real object + */ +SPObject * +sp_object_unref(SPObject *object, SPObject *owner) +{ + g_return_val_if_fail(object != NULL, NULL); + g_return_val_if_fail(SP_IS_OBJECT(object), NULL); + g_return_val_if_fail(!owner || SP_IS_OBJECT(owner), NULL); + + Inkscape::Debug::EventTracker<> tracker; + tracker.set(object, RefEvent::UNREF); + + g_object_unref(G_OBJECT(object)); + + return NULL; +} + +/** + * Increase weak refcount. + * + * Hrefcount is used for weak references, for example, to + * determine whether any graphical element references a certain gradient + * node. + * \param owner Ignored. + * \return object, NULL is error + * \pre object points to real object + */ +SPObject * +sp_object_href(SPObject *object, gpointer owner) +{ + g_return_val_if_fail(object != NULL, NULL); + g_return_val_if_fail(SP_IS_OBJECT(object), NULL); + + object->hrefcount++; + object->_updateTotalHRefCount(1); + + return object; +} + +/** + * Decrease weak refcount. + * + * Hrefcount is used for weak references, for example, to determine whether + * any graphical element references a certain gradient node. + * \param owner Ignored. + * \return always NULL + * \pre object points to real object and hrefcount>0 + */ +SPObject * +sp_object_hunref(SPObject *object, gpointer owner) +{ + g_return_val_if_fail(object != NULL, NULL); + g_return_val_if_fail(SP_IS_OBJECT(object), NULL); + g_return_val_if_fail(object->hrefcount > 0, NULL); + + object->hrefcount--; + object->_updateTotalHRefCount(-1); + + return NULL; +} + +/** + * Adds increment to _total_hrefcount of object and its parents. + */ +void +SPObject::_updateTotalHRefCount(int increment) { + SPObject *topmost_collectable = NULL; + for ( SPObject *iter = this ; iter ; iter = SP_OBJECT_PARENT(iter) ) { + iter->_total_hrefcount += increment; + if ( iter->_total_hrefcount < iter->hrefcount ) { + g_critical("HRefs overcounted"); + } + if ( iter->_total_hrefcount == 0 && + iter->_collection_policy != COLLECT_WITH_PARENT ) + { + topmost_collectable = iter; + } + } + if (topmost_collectable) { + topmost_collectable->requestOrphanCollection(); + } +} + +/** + * True if object is non-NULL and this is some in/direct parent of object. + */ +bool +SPObject::isAncestorOf(SPObject const *object) const { + g_return_val_if_fail(object != NULL, false); + object = SP_OBJECT_PARENT(object); + while (object) { + if ( object == this ) { + return true; + } + object = SP_OBJECT_PARENT(object); + } + return false; +} + +namespace { + +bool same_objects(SPObject const &a, SPObject const &b) { + return &a == &b; +} + +} + +/** + * Returns youngest object being parent to this and object. + */ +SPObject const * +SPObject::nearestCommonAncestor(SPObject const *object) const { + g_return_val_if_fail(object != NULL, NULL); + + using Inkscape::Algorithms::longest_common_suffix; + return longest_common_suffix(this, object, NULL, &same_objects); +} + +SPObject const *AncestorSon(SPObject const *obj, SPObject const *ancestor) { + if (obj == NULL || ancestor == NULL) + return NULL; + if (SP_OBJECT_PARENT(obj) == ancestor) + return obj; + return AncestorSon(SP_OBJECT_PARENT(obj), ancestor); +} + +/** + * Compares height of objects in tree. + * + * Works for different-parent objects, so long as they have a common ancestor. + * \return \verbatim + * 0 positions are equivalent + * 1 first object's position is greater than the second + * -1 first object's position is less than the second \endverbatim + */ +int +sp_object_compare_position(SPObject const *first, SPObject const *second) +{ + if (first == second) return 0; + + SPObject const *ancestor = first->nearestCommonAncestor(second); + if (ancestor == NULL) return 0; // cannot compare, no common ancestor! + + // we have an object and its ancestor (should not happen when sorting selection) + if (ancestor == first) + return 1; + if (ancestor == second) + return -1; + + SPObject const *to_first = AncestorSon(first, ancestor); + SPObject const *to_second = AncestorSon(second, ancestor); + + g_assert(SP_OBJECT_PARENT(to_second) == SP_OBJECT_PARENT(to_first)); + + return sp_repr_compare_position(SP_OBJECT_REPR(to_first), SP_OBJECT_REPR(to_second)); +} + + +/** + * Append repr as child of this object. + * \pre this is not a cloned object + */ +SPObject * +SPObject::appendChildRepr(Inkscape::XML::Node *repr) { + if (!SP_OBJECT_IS_CLONED(this)) { + SP_OBJECT_REPR(this)->appendChild(repr); + return SP_OBJECT_DOCUMENT(this)->getObjectByRepr(repr); + } else { + g_critical("Attempt to append repr as child of cloned object"); + return NULL; + } +} + +/** Gets the label property for the object or a default if no label + * is defined. + */ +gchar const * +SPObject::label() const { + return _label; +} + +/** Returns a default label property for the object. */ +gchar const * +SPObject::defaultLabel() const { + if (_label) { + return _label; + } else { + if (!_default_label) { + gchar const *id=SP_OBJECT_ID(this); + if (id) { + _default_label = g_strdup_printf("#%s", id); + } else { + _default_label = g_strdup_printf("<%s>", SP_OBJECT_REPR(this)->name()); + } + } + return _default_label; + } +} + +/** Sets the label property for the object */ +void +SPObject::setLabel(gchar const *label) { + SP_OBJECT_REPR(this)->setAttribute("inkscape:label", label, false); +} + + +/** Queues the object for orphan collection */ +void +SPObject::requestOrphanCollection() { + g_return_if_fail(document != NULL); + document->queueForOrphanCollection(this); + + /** \todo + * This is a temporary hack added to make fill&stroke rebuild its + * gradient list when the defs are vacuumed. gradient-vector.cpp + * listens to the modified signal on defs, and now we give it that + * signal. Mental says that this should be made automatic by + * merging SPObjectGroup with SPObject; SPObjectGroup would issue + * this signal automatically. Or maybe just derive SPDefs from + * SPObjectGroup? + */ + + this->requestModified(SP_OBJECT_CHILD_MODIFIED_FLAG); +} + +/** Sends the delete signal to all children of this object recursively */ +void +SPObject::_sendDeleteSignalRecursive() { + for (SPObject *child = sp_object_first_child(this); child; child = SP_OBJECT_NEXT(child)) { + child->_delete_signal.emit(child); + child->_sendDeleteSignalRecursive(); + } +} + +/** + * Deletes the object reference, unparenting it from its parent. + * + * If the \a propagate parameter is set to true, it emits a delete + * signal. If the \a propagate_descendants parameter is true, it + * recursively sends the delete signal to children. + */ +void +SPObject::deleteObject(bool propagate, bool propagate_descendants) +{ + sp_object_ref(this, NULL); + if (propagate) { + _delete_signal.emit(this); + } + if (propagate_descendants) { + this->_sendDeleteSignalRecursive(); + } + + Inkscape::XML::Node *repr=SP_OBJECT_REPR(this); + if (repr && sp_repr_parent(repr)) { + sp_repr_unparent(repr); + } + + if (_successor) { + _successor->deleteObject(propagate, propagate_descendants); + } + sp_object_unref(this, NULL); +} + +/** + * Put object into object tree, under parent, and behind prev; + * also update object's XML space. + */ +void +sp_object_attach(SPObject *parent, SPObject *object, SPObject *prev) +{ + g_return_if_fail(parent != NULL); + g_return_if_fail(SP_IS_OBJECT(parent)); + g_return_if_fail(object != NULL); + g_return_if_fail(SP_IS_OBJECT(object)); + g_return_if_fail(!prev || SP_IS_OBJECT(prev)); + g_return_if_fail(!prev || prev->parent == parent); + g_return_if_fail(!object->parent); + + sp_object_ref(object, parent); + object->parent = parent; + parent->_updateTotalHRefCount(object->_total_hrefcount); + + SPObject *next; + if (prev) { + next = prev->next; + prev->next = object; + } else { + next = parent->children; + parent->children = object; + } + object->next = next; + if (!next) { + parent->_last_child = object; + } + if (!object->xml_space.set) + object->xml_space.value = parent->xml_space.value; +} + +/** + * In list of object's siblings, move object behind prev. + */ +void +sp_object_reorder(SPObject *object, SPObject *prev) { + g_return_if_fail(object != NULL); + g_return_if_fail(SP_IS_OBJECT(object)); + g_return_if_fail(object->parent != NULL); + g_return_if_fail(object != prev); + g_return_if_fail(!prev || SP_IS_OBJECT(prev)); + g_return_if_fail(!prev || prev->parent == object->parent); + + SPObject *const parent=object->parent; + + SPObject *old_prev=NULL; + for ( SPObject *child = parent->children ; child && child != object ; + child = child->next ) + { + old_prev = child; + } + + SPObject *next=object->next; + if (old_prev) { + old_prev->next = next; + } else { + parent->children = next; + } + if (!next) { + parent->_last_child = old_prev; + } + if (prev) { + next = prev->next; + prev->next = object; + } else { + next = parent->children; + parent->children = object; + } + object->next = next; + if (!next) { + parent->_last_child = object; + } +} + +/** + * Remove object from parent's children, release and unref it. + */ +void +sp_object_detach(SPObject *parent, SPObject *object) { + g_return_if_fail(parent != NULL); + g_return_if_fail(SP_IS_OBJECT(parent)); + g_return_if_fail(object != NULL); + g_return_if_fail(SP_IS_OBJECT(object)); + g_return_if_fail(object->parent == parent); + + SPObject *prev=NULL; + for ( SPObject *child = parent->children ; child && child != object ; + child = child->next ) + { + prev = child; + } + + SPObject *next=object->next; + if (prev) { + prev->next = next; + } else { + parent->children = next; + } + if (!next) { + parent->_last_child = prev; + } + + object->next = NULL; + object->parent = NULL; + + sp_object_invoke_release(object); + parent->_updateTotalHRefCount(-object->_total_hrefcount); + sp_object_unref(object, parent); +} + +/** + * Return object's child whose node pointer equals repr. + */ +SPObject * +sp_object_get_child_by_repr(SPObject *object, Inkscape::XML::Node *repr) +{ + g_return_val_if_fail(object != NULL, NULL); + g_return_val_if_fail(SP_IS_OBJECT(object), NULL); + g_return_val_if_fail(repr != NULL, NULL); + + if (object->_last_child && SP_OBJECT_REPR(object->_last_child) == repr) + return object->_last_child; // optimization for common scenario + for ( SPObject *child = object->children ; child ; child = child->next ) { + if ( SP_OBJECT_REPR(child) == repr ) { + return child; + } + } + + return NULL; +} + +/** + * Callback for child_added event. + * Invoked whenever the given mutation event happens in the XML tree. + */ +static void +sp_object_child_added(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref) +{ + GType type = sp_repr_type_lookup(child); + if (!type) { + return; + } + SPObject *ochild = SP_OBJECT(g_object_new(type, 0)); + SPObject *prev = ref ? sp_object_get_child_by_repr(object, ref) : NULL; + sp_object_attach(object, ochild, prev); + sp_object_unref(ochild, NULL); + + sp_object_invoke_build(ochild, object->document, child, SP_OBJECT_IS_CLONED(object)); +} + +/** + * Removes, releases and unrefs all children of object. + * + * This is the opposite of build. It has to be invoked as soon as the + * object is removed from the tree, even if it is still alive according + * to reference count. The frontend unregisters the object from the + * document and releases the SPRepr bindings; implementations should free + * state data and release all child objects. Invoking release on + * SPRoot destroys the whole document tree. + * \see sp_object_build() + */ +static void sp_object_release(SPObject *object) +{ + debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object)); + while (object->children) { + sp_object_detach(object, object->children); + } +} + +/** + * Remove object's child whose node equals repr, release and + * unref it. + * + * Invoked whenever the given mutation event happens in the XML + * tree, BEFORE removal from the XML tree happens, so grouping + * objects can safely release the child data. + */ +static void +sp_object_remove_child(SPObject *object, Inkscape::XML::Node *child) +{ + debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object)); + SPObject *ochild = sp_object_get_child_by_repr(object, child); + g_return_if_fail(ochild != NULL); + sp_object_detach(object, ochild); +} + +/** + * Move object corresponding to child after sibling object corresponding + * to new_ref. + * Invoked whenever the given mutation event happens in the XML tree. + * \param old_ref Ignored + */ +static void sp_object_order_changed(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *old_ref, + Inkscape::XML::Node *new_ref) +{ + SPObject *ochild = sp_object_get_child_by_repr(object, child); + g_return_if_fail(ochild != NULL); + SPObject *prev = new_ref ? sp_object_get_child_by_repr(object, new_ref) : NULL; + sp_object_reorder(ochild, prev); +} + +/** + * Virtual build callback. + * + * This has to be invoked immediately after creation of an SPObject. The + * frontend method ensures that the new object is properly attached to + * the document and repr; implementation then will parse all of the attributes, + * generate the children objects and so on. Invoking build on the SPRoot + * object results in creation of the whole document tree (this is, what + * SPDocument does after the creation of the XML tree). + * \see sp_object_release() + */ +static void +sp_object_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + /* Nothing specific here */ + debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object)); + + sp_object_read_attr(object, "xml:space"); + sp_object_read_attr(object, "inkscape:label"); + sp_object_read_attr(object, "inkscape:collect"); + + for (Inkscape::XML::Node *rchild = repr->firstChild() ; rchild != NULL; rchild = rchild->next()) { + GType type = sp_repr_type_lookup(rchild); + if (!type) { + continue; + } + SPObject *child = SP_OBJECT(g_object_new(type, 0)); + sp_object_attach(object, child, object->lastChild()); + sp_object_unref(child, NULL); + sp_object_invoke_build(child, document, rchild, SP_OBJECT_IS_CLONED(object)); + } +} + +void +sp_object_invoke_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr, unsigned int cloned) +{ + debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object)); + + g_assert(object != NULL); + g_assert(SP_IS_OBJECT(object)); + g_assert(document != NULL); + g_assert(repr != NULL); + + g_assert(object->document == NULL); + g_assert(object->repr == NULL); + g_assert(object->id == NULL); + + /* Bookkeeping */ + + object->document = document; + object->repr = repr; + Inkscape::GC::anchor(repr); + object->cloned = cloned; + + if (!SP_OBJECT_IS_CLONED(object)) { + object->document->bindObjectToRepr(object->repr, object); + + if (Inkscape::XML::id_permitted(object->repr)) { + /* If we are not cloned, force unique id */ + gchar const *id = object->repr->attribute("id"); + gchar *realid = sp_object_get_unique_id(object, id); + g_assert(realid != NULL); + + object->document->bindObjectToId(realid, object); + object->id = realid; + + /* Redefine ID, if required */ + if ((id == NULL) || (strcmp(id, realid) != 0)) { + gboolean undo_sensitive=sp_document_get_undo_sensitive(document); + sp_document_set_undo_sensitive(document, FALSE); + object->repr->setAttribute("id", realid); + sp_document_set_undo_sensitive(document, undo_sensitive); + } + } + } else { + g_assert(object->id == NULL); + } + + /* Invoke derived methods, if any */ + + if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->build) { + (*((SPObjectClass *) G_OBJECT_GET_CLASS(object))->build)(object, document, repr); + } + + /* Signalling (should be connected AFTER processing derived methods */ + sp_repr_add_listener(repr, &object_event_vector, object); +} + +void +sp_object_invoke_release(SPObject *object) +{ + g_assert(object != NULL); + g_assert(SP_IS_OBJECT(object)); + + // we need to remember our parent + // g_assert(!object->parent); + g_assert(!object->next); + g_assert(object->document); + g_assert(object->repr); + + sp_repr_remove_listener_by_data(object->repr, object); + + g_signal_emit(G_OBJECT(object), object_signals[RELEASE], 0); + + /* all hrefs should be released by the "release" handlers */ + g_assert(object->hrefcount == 0); + + if (!SP_OBJECT_IS_CLONED(object)) { + if (object->id) { + object->document->bindObjectToId(object->id, NULL); + } + g_free(object->id); + object->id = NULL; + + g_free(object->_default_label); + object->_default_label = NULL; + + object->document->bindObjectToRepr(object->repr, NULL); + } else { + g_assert(!object->id); + } + + if (object->style) { + object->style = sp_style_unref(object->style); + } + + Inkscape::GC::release(object->repr); + + object->document = NULL; + object->repr = NULL; +} + +/** + * Callback for child_added node event. + */ +static void +sp_object_repr_child_added(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, gpointer data) +{ + SPObject *object = SP_OBJECT(data); + + if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->child_added) + (*((SPObjectClass *)G_OBJECT_GET_CLASS(object))->child_added)(object, child, ref); +} + +/** + * Callback for remove_child node event. + */ +static void +sp_object_repr_child_removed(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, gpointer data) +{ + SPObject *object = SP_OBJECT(data); + + if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->remove_child) { + (* ((SPObjectClass *)G_OBJECT_GET_CLASS(object))->remove_child)(object, child); + } +} + +/** + * Callback for order_changed node event. + * + * \todo fixme: + */ +static void +sp_object_repr_order_changed(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *old, Inkscape::XML::Node *newer, gpointer data) +{ + SPObject *object = SP_OBJECT(data); + + if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->order_changed) { + (* ((SPObjectClass *)G_OBJECT_GET_CLASS(object))->order_changed)(object, child, old, newer); + } +} + +/** + * Callback for set event. + */ +static void +sp_object_private_set(SPObject *object, unsigned int key, gchar const *value) +{ + g_assert(key != SP_ATTR_INVALID); + + switch (key) { + case SP_ATTR_ID: + if ( !SP_OBJECT_IS_CLONED(object) && object->repr->type() == Inkscape::XML::ELEMENT_NODE ) { + SPDocument *document=object->document; + SPObject *conflict=NULL; + + if (value) { + conflict = document->getObjectById((char const *)value); + } + if ( conflict && conflict != object ) { + sp_object_ref(conflict, NULL); + // give the conflicting object a new ID + gchar *new_conflict_id = sp_object_get_unique_id(conflict, NULL); + SP_OBJECT_REPR(conflict)->setAttribute("id", new_conflict_id); + g_free(new_conflict_id); + sp_object_unref(conflict, NULL); + } + + if (object->id) { + document->bindObjectToId(object->id, NULL); + g_free(object->id); + } + + if (value) { + object->id = g_strdup((char const*)value); + document->bindObjectToId(object->id, object); + } else { + object->id = NULL; + } + + g_free(object->_default_label); + object->_default_label = NULL; + } + break; + case SP_ATTR_INKSCAPE_LABEL: + g_free(object->_label); + if (value) { + object->_label = g_strdup(value); + } else { + object->_label = NULL; + } + g_free(object->_default_label); + object->_default_label = NULL; + break; + case SP_ATTR_INKSCAPE_COLLECT: + if ( value && !strcmp(value, "always") ) { + object->setCollectionPolicy(SPObject::ALWAYS_COLLECT); + } else { + object->setCollectionPolicy(SPObject::COLLECT_WITH_PARENT); + } + break; + case SP_ATTR_XML_SPACE: + if (value && !strcmp(value, "preserve")) { + object->xml_space.value = SP_XML_SPACE_PRESERVE; + object->xml_space.set = TRUE; + } else if (value && !strcmp(value, "default")) { + object->xml_space.value = SP_XML_SPACE_DEFAULT; + object->xml_space.set = TRUE; + } else if (object->parent) { + SPObject *parent; + parent = object->parent; + object->xml_space.value = parent->xml_space.value; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + break; + default: + break; + } +} + +/** + * Call virtual set() function of object. + */ +void +sp_object_set(SPObject *object, unsigned int key, gchar const *value) +{ + g_assert(object != NULL); + g_assert(SP_IS_OBJECT(object)); + + if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->set) { + ((SPObjectClass *) G_OBJECT_GET_CLASS(object))->set(object, key, value); + } +} + +/** + * Read value of key attribute from XML node into object. + */ +void +sp_object_read_attr(SPObject *object, gchar const *key) +{ + g_assert(object != NULL); + g_assert(SP_IS_OBJECT(object)); + g_assert(key != NULL); + + g_assert(object->repr != NULL); + + unsigned int keyid = sp_attribute_lookup(key); + if (keyid != SP_ATTR_INVALID) { + /* Retrieve the 'key' attribute from the object's XML representation */ + gchar const *value = object->repr->attribute(key); + + sp_object_set(object, keyid, value); + } +} + +/** + * Callback for attr_changed node event. + */ +static void +sp_object_repr_attr_changed(Inkscape::XML::Node *repr, gchar const *key, gchar const *oldval, gchar const *newval, bool is_interactive, gpointer data) +{ + SPObject *object = SP_OBJECT(data); + + sp_object_read_attr(object, key); + + // manual changes to extension attributes require the normal + // attributes, which depend on them, to be updated immediately + if (is_interactive) { + object->updateRepr(repr, 0); + } +} + +/** + * Callback for content_changed node event. + */ +static void +sp_object_repr_content_changed(Inkscape::XML::Node *repr, gchar const *oldcontent, gchar const *newcontent, gpointer data) +{ + SPObject *object = SP_OBJECT(data); + + if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->read_content) + (*((SPObjectClass *) G_OBJECT_GET_CLASS(object))->read_content)(object); +} + +/** + * Return string representation of space value. + */ +static gchar const* +sp_xml_get_space_string(unsigned int space) +{ + switch (space) { + case SP_XML_SPACE_DEFAULT: + return "default"; + case SP_XML_SPACE_PRESERVE: + return "preserve"; + default: + return NULL; + } +} + +/** + * Callback for write event. + */ +static Inkscape::XML::Node * +sp_object_private_write(SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + if (!repr && (flags & SP_OBJECT_WRITE_BUILD)) { + repr = SP_OBJECT_REPR(object)->duplicate(); + if (!( flags & SP_OBJECT_WRITE_EXT )) { + repr->setAttribute("inkscape:collect", NULL); + } + } else { + repr->setAttribute("id", object->id); + + if (object->xml_space.set) { + char const *xml_space; + xml_space = sp_xml_get_space_string(object->xml_space.value); + repr->setAttribute("xml:space", xml_space); + } + + if ( flags & SP_OBJECT_WRITE_EXT && + object->collectionPolicy() == SPObject::ALWAYS_COLLECT ) + { + repr->setAttribute("inkscape:collect", "always"); + } else { + repr->setAttribute("inkscape:collect", NULL); + } + } + + return repr; +} + +/** + * Update this object's XML node with flags value. + */ +Inkscape::XML::Node * +SPObject::updateRepr(unsigned int flags) { + if (!SP_OBJECT_IS_CLONED(this)) { + Inkscape::XML::Node *repr=SP_OBJECT_REPR(this); + if (repr) { + return updateRepr(repr, flags); + } else { + g_critical("Attempt to update non-existent repr"); + return NULL; + } + } else { + /* cloned objects have no repr */ + return NULL; + } +} + +Inkscape::XML::Node * +SPObject::updateRepr(Inkscape::XML::Node *repr, unsigned int flags) { + if (SP_OBJECT_IS_CLONED(this)) { + /* cloned objects have no repr */ + return NULL; + } + if (((SPObjectClass *) G_OBJECT_GET_CLASS(this))->write) { + if (!(flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = SP_OBJECT_REPR(this); + } + return ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->write(this, repr, flags); + } else { + g_warning("Class %s does not implement ::write", G_OBJECT_TYPE_NAME(this)); + if (!repr) { + if (flags & SP_OBJECT_WRITE_BUILD) { + repr = SP_OBJECT_REPR(this)->duplicate(); + } + /// \todo fixme: else probably error (Lauris) */ + } else { + repr->mergeFrom(SP_OBJECT_REPR(this), "id"); + } + return repr; + } +} + +/* Modification */ + +/** + * Add \a flags to \a object's as dirtiness flags, and + * recursively add CHILD_MODIFIED flag to + * parent and ancestors (as far up as necessary). + */ +void +SPObject::requestDisplayUpdate(unsigned int flags) +{ + if (update_in_progress) { + g_print("WARNING: Requested update while update in progress, counter = %d\n", update_in_progress); + } + + g_return_if_fail(!(flags & SP_OBJECT_PARENT_MODIFIED_FLAG)); + g_return_if_fail((flags & SP_OBJECT_MODIFIED_FLAG) || (flags & SP_OBJECT_CHILD_MODIFIED_FLAG)); + g_return_if_fail(!((flags & SP_OBJECT_MODIFIED_FLAG) && (flags & SP_OBJECT_CHILD_MODIFIED_FLAG))); + + /* Check for propagate before we set any flags */ + /* Propagate means, that this is not passed through by modification request cascade yet */ + unsigned int propagate = (!(this->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))); + + /* Just set this flags safe even if some have been set before */ + this->uflags |= flags; + + if (propagate) { + if (this->parent) { + this->parent->requestDisplayUpdate(SP_OBJECT_CHILD_MODIFIED_FLAG); + } else { + sp_document_request_modified(this->document); + } + } +} + +void +SPObject::updateDisplay(SPCtx *ctx, unsigned int flags) +{ + g_return_if_fail(!(flags & ~SP_OBJECT_MODIFIED_CASCADE)); + + update_in_progress ++; + +#ifdef SP_OBJECT_DEBUG_CASCADE + g_print("Update %s:%s %x %x %x\n", g_type_name_from_instance((GTypeInstance *) this), SP_OBJECT_ID(this), flags, this->uflags, this->mflags); +#endif + + /* Get this flags */ + flags |= this->uflags; + /* Copy flags to modified cascade for later processing */ + this->mflags |= this->uflags; + /* We have to clear flags here to allow rescheduling update */ + this->uflags = 0; + + // Merge style if we have good reasons to think that parent style is changed */ + /** \todo + * I am not sure whether we should check only propagated + * flag. We are currently assuming that style parsing is + * done immediately. I think this is correct (Lauris). + */ + if ((flags & SP_OBJECT_STYLE_MODIFIED_FLAG) && (flags & SP_OBJECT_PARENT_MODIFIED_FLAG)) { + if (this->style && this->parent) { + sp_style_merge_from_parent(this->style, this->parent->style); + } + } + + if (((SPObjectClass *) G_OBJECT_GET_CLASS(this))->update) + ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->update(this, ctx, flags); + + update_in_progress --; +} + +void +SPObject::requestModified(unsigned int flags) +{ + g_return_if_fail( this->document != NULL ); + + /* PARENT_MODIFIED is computed later on and is not intended to be + * "manually" queued */ + g_return_if_fail(!(flags & SP_OBJECT_PARENT_MODIFIED_FLAG)); + + /* we should be setting either MODIFIED or CHILD_MODIFIED... */ + g_return_if_fail((flags & SP_OBJECT_MODIFIED_FLAG) || (flags & SP_OBJECT_CHILD_MODIFIED_FLAG)); + + /* ...but not both */ + g_return_if_fail(!((flags & SP_OBJECT_MODIFIED_FLAG) && (flags & SP_OBJECT_CHILD_MODIFIED_FLAG))); + + unsigned int old_mflags=this->mflags; + this->mflags |= flags; + + /* If we already had MODIFIED or CHILD_MODIFIED queued, we will + * have already queued CHILD_MODIFIED with our ancestors and + * need not disturb them again. + */ + if (!( old_mflags & ( SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG ) )) { + SPObject *parent=SP_OBJECT_PARENT(this); + if (parent) { + parent->requestModified(SP_OBJECT_CHILD_MODIFIED_FLAG); + } else { + sp_document_request_modified(SP_OBJECT_DOCUMENT(this)); + } + } +} + +void +SPObject::emitModified(unsigned int flags) +{ + /* only the MODIFIED_CASCADE flag is legal here */ + g_return_if_fail(!(flags & ~SP_OBJECT_MODIFIED_CASCADE)); + +#ifdef SP_OBJECT_DEBUG_CASCADE + g_print("Modified %s:%s %x %x %x\n", g_type_name_from_instance((GTypeInstance *) this), SP_OBJECT_ID(this), flags, this->uflags, this->mflags); +#endif + + flags |= this->mflags; + /* We have to clear mflags beforehand, as signal handlers may + * make changes and therefore queue new modification notifications + * themselves. */ + this->mflags = 0; + + g_object_ref(G_OBJECT(this)); + g_signal_emit(G_OBJECT(this), object_signals[MODIFIED], 0, flags); + g_object_unref(G_OBJECT(this)); +} + +/* + * Get and set descriptive parameters + * + * These are inefficent, so they are not intended to be used interactively + */ + +gchar const * +sp_object_title_get(SPObject *object) +{ + return NULL; +} + +gchar const * +sp_object_description_get(SPObject *object) +{ + return NULL; +} + +unsigned int +sp_object_title_set(SPObject *object, gchar const *title) +{ + return FALSE; +} + +unsigned int +sp_object_description_set(SPObject *object, gchar const *desc) +{ + return FALSE; +} + +gchar const * +sp_object_tagName_get(SPObject const *object, SPException *ex) +{ + /* If exception is not clear, return */ + if (!SP_EXCEPTION_IS_OK(ex)) { + return NULL; + } + + /// \todo fixme: Exception if object is NULL? */ + return object->repr->name(); +} + +gchar const * +sp_object_getAttribute(SPObject const *object, gchar const *key, SPException *ex) +{ + /* If exception is not clear, return */ + if (!SP_EXCEPTION_IS_OK(ex)) { + return NULL; + } + + /// \todo fixme: Exception if object is NULL? */ + return (gchar const *) object->repr->attribute(key); +} + +void +sp_object_setAttribute(SPObject *object, gchar const *key, gchar const *value, SPException *ex) +{ + /* If exception is not clear, return */ + g_return_if_fail(SP_EXCEPTION_IS_OK(ex)); + + /// \todo fixme: Exception if object is NULL? */ + if (!sp_repr_set_attr(object->repr, key, value)) { + ex->code = SP_NO_MODIFICATION_ALLOWED_ERR; + } +} + +void +sp_object_removeAttribute(SPObject *object, gchar const *key, SPException *ex) +{ + /* If exception is not clear, return */ + g_return_if_fail(SP_EXCEPTION_IS_OK(ex)); + + /// \todo fixme: Exception if object is NULL? */ + if (!sp_repr_set_attr(object->repr, key, NULL)) { + ex->code = SP_NO_MODIFICATION_ALLOWED_ERR; + } +} + +/* Helper */ + +static gchar * +sp_object_get_unique_id(SPObject *object, gchar const *id) +{ + static unsigned long count = 0; + + g_assert(SP_IS_OBJECT(object)); + + count++; + + gchar const *name = object->repr->name(); + g_assert(name != NULL); + + gchar const *local = strchr(name, ':'); + if (local) { + name = local + 1; + } + + if (id != NULL) { + if (object->document->getObjectById(id) == NULL) { + return g_strdup(id); + } + } + + size_t const name_len = strlen(name); + size_t const buflen = name_len + (sizeof(count) * 10 / 4) + 1; + gchar *const buf = (gchar *) g_malloc(buflen); + memcpy(buf, name, name_len); + gchar *const count_buf = buf + name_len; + size_t const count_buflen = buflen - name_len; + do { + ++count; + g_snprintf(count_buf, count_buflen, "%lu", count); + } while ( object->document->getObjectById(buf) != NULL ); + return buf; +} + +/* Style */ + +/** + * Returns an object style property. + * + * \todo + * fixme: Use proper CSS parsing. The current version is buggy + * in a number of situations where key is a substring of the + * style string other than as a property name (including + * where key is a substring of a property name), and is also + * buggy in its handling of inheritance for properties that + * aren't inherited by default. It also doesn't allow for + * the case where the property is specified but with an invalid + * value (in which case I believe the CSS2 error-handling + * behaviour applies, viz. behave as if the property hadn't + * been specified). Also, the current code doesn't use CRSelEng + * stuff to take a value from stylesheets. Also, we aren't + * setting any hooks to force an update for changes in any of + * the inputs (i.e., in any of the elements that this function + * queries). + * + * \par + * Given that the default value for a property depends on what + * property it is (e.g., whether to inherit or not), and given + * the above comment about ignoring invalid values, and that the + * repr parent isn't necessarily the right element to inherit + * from (e.g., maybe we need to inherit from the referencing + * element instead), we should probably make the caller + * responsible for ascending the repr tree as necessary. + */ +gchar const * +sp_object_get_style_property(SPObject const *object, gchar const *key, gchar const *def) +{ + g_return_val_if_fail(object != NULL, NULL); + g_return_val_if_fail(SP_IS_OBJECT(object), NULL); + g_return_val_if_fail(key != NULL, NULL); + + gchar const *style = object->repr->attribute("style"); + if (style) { + size_t const len = strlen(key); + char const *p; + while ( (p = strstr(style, key)) + != NULL ) + { + p += len; + while ((*p <= ' ') && *p) p++; + if (*p++ != ':') break; + while ((*p <= ' ') && *p) p++; + size_t const inherit_len = sizeof("inherit") - 1; + if (*p + && !(strneq(p, "inherit", inherit_len) + && (p[inherit_len] == '\0' + || p[inherit_len] == ';' + || g_ascii_isspace(p[inherit_len])))) { + return p; + } + } + } + gchar const *val = object->repr->attribute(key); + if (val && !streq(val, "inherit")) { + return val; + } + if (object->parent) { + return sp_object_get_style_property(object->parent, key, def); + } + + return def; +} + +/** + * Lifts SVG version of all root objects to version. + */ +void +SPObject::_requireSVGVersion(Inkscape::Version version) { + for ( SPObject::ParentIterator iter=this ; iter ; ++iter ) { + SPObject *object=iter; + if (SP_IS_ROOT(object)) { + SPRoot *root=SP_ROOT(object); + if ( root->version.svg < version ) { + root->version.svg = version; + } + } + } +} + +/** + * Return sodipodi version of first root ancestor or (0,0). + */ +Inkscape::Version +sp_object_get_sodipodi_version(SPObject *object) +{ + static Inkscape::Version const zero_version(0, 0); + + while (object) { + if (SP_IS_ROOT(object)) { + return SP_ROOT(object)->version.sodipodi; + } + object = SP_OBJECT_PARENT(object); + } + + return zero_version; +} + +/** + * Returns previous object in sibling list or NULL. + */ +SPObject * +sp_object_prev(SPObject *child) +{ + SPObject *parent = SP_OBJECT_PARENT(child); + for ( SPObject *i = sp_object_first_child(parent); i; i = SP_OBJECT_NEXT(i) ) { + if (SP_OBJECT_NEXT(i) == child) + return i; + } + return NULL; +} + + +/* + 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 : diff --git a/src/sp-object.h b/src/sp-object.h new file mode 100644 index 000000000..767f8b978 --- /dev/null +++ b/src/sp-object.h @@ -0,0 +1,539 @@ +#ifndef __SP_OBJECT_H__ +#define __SP_OBJECT_H__ + +/** \file + * Abstract base class for all nodes + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +/* SPObject flags */ + +/* Async modification flags */ +#define SP_OBJECT_MODIFIED_FLAG (1 << 0) +#define SP_OBJECT_CHILD_MODIFIED_FLAG (1 << 1) +#define SP_OBJECT_PARENT_MODIFIED_FLAG (1 << 2) +#define SP_OBJECT_STYLE_MODIFIED_FLAG (1 << 3) +#define SP_OBJECT_VIEWPORT_MODIFIED_FLAG (1 << 4) +#define SP_OBJECT_USER_MODIFIED_FLAG_A (1 << 5) +#define SP_OBJECT_USER_MODIFIED_FLAG_B (1 << 6) +#define SP_OBJECT_USER_MODIFIED_FLAG_C (1 << 7) + +/* Conveneience */ +#define SP_OBJECT_FLAGS_ALL 0xff + +/* Flags that mark object as modified */ +/* Object, Child, Style, Viewport, User */ +#define SP_OBJECT_MODIFIED_STATE (SP_OBJECT_FLAGS_ALL & ~(SP_OBJECT_PARENT_MODIFIED_FLAG)) + +/* Flags that will propagate downstreams */ +/* Parent, Style, Viewport, User */ +#define SP_OBJECT_MODIFIED_CASCADE (SP_OBJECT_FLAGS_ALL & ~(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG)) + +/* Generic */ +#define SP_OBJECT_IS_CLONED(o) (((SPObject *) (o))->cloned) + +/* Write flags */ +#define SP_OBJECT_WRITE_BUILD (1 << 0) +#define SP_OBJECT_WRITE_EXT (1 << 1) +#define SP_OBJECT_WRITE_ALL (1 << 2) + +/* Convenience stuff */ +#define SP_OBJECT_ID(o) (((SPObject *) (o))->id) +#define SP_OBJECT_REPR(o) (((SPObject *) (o))->repr) +#define SP_OBJECT_DOCUMENT(o) (((SPObject *) (o))->document) +#define SP_OBJECT_PARENT(o) (((SPObject *) (o))->parent) +#define SP_OBJECT_NEXT(o) (((SPObject *) (o))->next) +#define SP_OBJECT_PREV(o) (sp_object_prev((SPObject *) (o))) +#define SP_OBJECT_HREFCOUNT(o) (((SPObject *) (o))->hrefcount) +#define SP_OBJECT_STYLE(o) (((SPObject *) (o))->style) +#define SP_OBJECT_TITLE(o) sp_object_title_get((SPObject *) (o)) +#define SP_OBJECT_DESCRIPTION(o) sp_object_description_get((SPObject *) (o)) + + +#include +#include +#include +#include + +#include "forward.h" +#include "version.h" +#include "util/forward-pointer-iterator.h" + +namespace Inkscape { +namespace XML { +class Node; +} +} + + +typedef enum { + SP_NO_EXCEPTION, + SP_INDEX_SIZE_ERR, + SP_DOMSTRING_SIZE_ERR, + SP_HIERARCHY_REQUEST_ERR, + SP_WRONG_DOCUMENT_ERR, + SP_INVALID_CHARACTER_ERR, + SP_NO_DATA_ALLOWED_ERR, + SP_NO_MODIFICATION_ALLOWED_ERR, + SP_NOT_FOUND_ERR, + SP_NOT_SUPPORTED_ERR, + SP_INUSE_ATTRIBUTE_ERR, + SP_INVALID_STATE_ERR, + SP_SYNTAX_ERR, + SP_INVALID_MODIFICATION_ERR, + SP_NAMESPACE_ERR, + SP_INVALID_ACCESS_ERR +} SPExceptionType; + +class SPException; + +/// An attempt to implement exceptions, unused? +struct SPException { + SPExceptionType code; +}; + +#define SP_EXCEPTION_INIT(ex) {(ex)->code = SP_NO_EXCEPTION;} +#define SP_EXCEPTION_IS_OK(ex) (!(ex) || ((ex)->code == SP_NO_EXCEPTION)) + +class SPCtx; + +/// Unused +struct SPCtx { + unsigned int flags; +}; + +enum { + SP_XML_SPACE_DEFAULT, + SP_XML_SPACE_PRESERVE +}; + +class SPIXmlSpace; + +/// Internal class consisting of two bits. +struct SPIXmlSpace { + guint set : 1; + guint value : 1; +}; + +class SPObject; + +/* + * Refcounting + * + * Owner is here for debug reasons, you can set it to NULL safely + * Ref should return object, NULL is error, unref return always NULL + */ + +SPObject *sp_object_ref(SPObject *object, SPObject *owner=NULL); +SPObject *sp_object_unref(SPObject *object, SPObject *owner=NULL); + +SPObject *sp_object_href(SPObject *object, gpointer owner); +SPObject *sp_object_hunref(SPObject *object, gpointer owner); + +/// A refcounting tree node object. +struct SPObject : public GObject { + enum CollectionPolicy { + COLLECT_WITH_PARENT, + ALWAYS_COLLECT + }; + + unsigned int cloned : 1; + unsigned int uflags : 8; + unsigned int mflags : 8; + SPIXmlSpace xml_space; + unsigned int hrefcount; /* number of xlink:href references */ + unsigned int _total_hrefcount; /* our hrefcount + total descendants */ + SPDocument *document; /* Document we are part of */ + SPObject *parent; /* Our parent (only one allowed) */ + SPObject *children; /* Our children */ + SPObject *_last_child; /* Remembered last child */ + SPObject *next; /* Next object in linked list */ + Inkscape::XML::Node *repr; /* Our xml representation */ + gchar *id; /* Our very own unique id */ + + /** + * Represents the style properties, whether from presentation attributes, the style + * attribute, or inherited. + * + * sp_object_private_set doesn't handle SP_ATTR_STYLE or any presentation attributes at the + * time of writing, so this is probably NULL for all SPObject's that aren't an SPItem. + * + * However, this gives rise to the bugs mentioned in sp_object_get_style_property. + * Note that some non-SPItem SPObject's, such as SPStop, do need styling information, + * and need to inherit properties even through other non-SPItem parents like \. + */ + SPStyle *style; + + /// Switch containing next() method. + struct ParentIteratorStrategy { + static SPObject const *next(SPObject const *object) { + return object->parent; + } + }; + /// Switch containing next() method. + struct SiblingIteratorStrategy { + static SPObject const *next(SPObject const *object) { + return object->next; + } + }; + + typedef Inkscape::Util::ForwardPointerIterator ParentIterator; + typedef Inkscape::Util::ForwardPointerIterator ConstParentIterator; + typedef Inkscape::Util::ForwardPointerIterator SiblingIterator; + typedef Inkscape::Util::ForwardPointerIterator ConstSiblingIterator; + + bool isSiblingOf(SPObject const *object) const { + g_return_val_if_fail(object != NULL, false); + return this->parent && this->parent == object->parent; + } + bool isAncestorOf(SPObject const *object) const; + + SPObject const *nearestCommonAncestor(SPObject const *object) const; + /* A non-const version can be similarly constructed if you want one. + * (Don't just cast away the constness, which would be ill-formed.) */ + + bool hasChildren() const { return ( children != NULL ); } + + SPObject *firstChild() { return children; } + SPObject const *firstChild() const { return children; } + SPObject *lastChild() { return _last_child; } + SPObject const *lastChild() const { return _last_child; } + + SPObject *appendChildRepr(Inkscape::XML::Node *repr); + + /** @brief Gets the author-visible label for this object. */ + gchar const *label() const; + /** @brief Returns a default label for this object. */ + gchar const *defaultLabel() const; + /** @brief Sets the author-visible label for this object. + * + * Sets the author-visible label for the object. + * + * @param label the new label + */ + void setLabel(gchar const *label); + + /** Retrieves the title of this object */ + gchar const *title() const { return NULL; /* TODO */ } + /** Sets the title of this object */ + void setTitle(gchar const *title) { /* TODO */ } + + /** Retrieves the description of this object */ + gchar const *desc() const { return NULL; /* TODO */ } + /** Sets the description of this object */ + void setDesc(gchar const *desc) { /* TODO */ } + + /** @brief Set the policy under which this object will be + * orphan-collected. + * + * Orphan-collection is the process of deleting all objects which no longer have + * hyper-references pointing to them. The policy determines when this happens. Many objects + * should not be deleted simply because they are no longer referred to; other objects (like + * "intermediate" gradients) are more or less throw-away and should always be collected when no + * longer in use. + * + * Along these lines, there are currently two orphan-collection policies: + * + * COLLECT_WITH_PARENT - don't worry about the object's hrefcount; + * if its parent is collected, this object + * will be too + * + * COLLECT_ALWAYS - always collect the object as soon as its + * hrefcount reaches zero + * + * @returns the current collection policy in effect for this object + */ + CollectionPolicy collectionPolicy() const { return _collection_policy; } + + /** @brief Sets the orphan-collection policy in effect for this object. + * + * @see SPObject::collectionPolicy + * + * @param policy the new policy to adopt + */ + void setCollectionPolicy(CollectionPolicy policy) { + _collection_policy = policy; + } + + /** @brief Requests a later automatic call to collectOrphan(). + * + * This method requests that collectOrphan() be called during the document update cycle, + * deleting the object if it is no longer used. + * + * If the current collection policy is COLLECT_WITH_PARENT, this function has no effect. + * + * @see SPObject::collectOrphan + */ + void requestOrphanCollection(); + + /** @brief Unconditionally delete the object if it is not referenced. + * + * Unconditionally delete the object if there are no outstanding hyper-references to it. + * Observers are not notified of the object's deletion (at the SPObject level; XML tree + * notifications still fire). + * + * @see SPObject::deleteObject + */ + void collectOrphan() { + if ( _total_hrefcount == 0 ) { + deleteObject(false); + } + } + + /** @brief Deletes an object. + * + * Detaches the object's repr, and optionally sends notification that the object has been + * deleted. + * + * @param propagate notify observers that the object has been deleted? + * + * @param propagate_descendants notify observers of children that they have been deleted? + */ + void deleteObject(bool propagate, bool propagate_descendants); + + /** @brief Deletes on object. + * + * @param propagate Notify observers of this object and its children that they have been + * deleted? + */ + void deleteObject(bool propagate=true) { + deleteObject(propagate, propagate); + } + + /** @brief Connects a slot to be called when an object is deleted. + * + * This connects a slot to an object's internal delete signal, which is invoked when the object + * is deleted + * + * The signal is mainly useful for e.g. knowing when to break hrefs or dissociate clones. + * + * @param slot the slot to connect + * + * @see SPObject::deleteObject + */ + sigc::connection connectDelete(sigc::slot slot) { + return _delete_signal.connect(slot); + } + + /** @brief Returns the object which supercedes this one (if any). + * + * This is mainly useful for ensuring we can correctly perform a series of moves or deletes, + * even if the objects in question have been replaced in the middle of the sequence. + */ + SPObject *successor() { return _successor; } + + /** @brief Indicates that another object supercedes this one. */ + void setSuccessor(SPObject *successor) { + g_assert(successor != NULL); + g_assert(_successor == NULL); + g_assert(successor->_successor == NULL); + sp_object_ref(successor, NULL); + _successor = successor; + } + + /* modifications; all three sets of methods should probably ultimately be protected, as they + * are not really part of its public interface. However, other parts of the code to + * occasionally use them at present. */ + + /* the no-argument version of updateRepr() is intended to be a bit more public, however -- it + * essentially just flushes any changes back to the backing store (the repr layer); maybe it + * should be called something else and made public at that point. */ + + /** @brief Updates the object's repr based on the object's state. + * + * This method updates the the repr attached to the object to reflect the object's current + * state; see the two-argument version for details. + * + * @param flags object write flags that apply to this update + * + * @return the updated repr + */ + Inkscape::XML::Node *updateRepr(unsigned int flags=SP_OBJECT_WRITE_EXT); + + /** @brief Updates the given repr based on the object's state. + * + * This method updates the given repr to reflect the object's current state. There are + * several flags that affect this: + * + * SP_OBJECT_WRITE_BUILD - create new reprs + * + * SP_OBJECT_WRITE_EXT - write elements and attributes + * which are not part of pure SVG + * (i.e. the Inkscape and Sodipodi + * namespaces) + * + * SP_OBJECT_WRITE_ALL - create all nodes and attributes, + * even those which might be redundant + * + * @param repr the repr to update + * @param flags object write flags that apply to this update + * + * @return the updated repr + */ + Inkscape::XML::Node *updateRepr(Inkscape::XML::Node *repr, unsigned int flags); + + /** @brief Queues an deferred update of this object's display. + * + * This method sets flags to indicate updates to be performed later, during the idle loop. + * + * There are several flags permitted here: + * + * SP_OBJECT_MODIFIED_FLAG - the object has been modified + * + * SP_OBJECT_CHILD_MODIFIED_FLAG - a child of the object has been + * modified + * + * SP_OBJECT_STYLE_MODIFIED_FLAG - the object's style has been + * modified + * + * There are also some subclass-specific modified flags which are hardly ever used. + * + * One of either MODIFIED or CHILD_MODIFIED is required. + * + * @param flags flags indicating what to update + */ + void requestDisplayUpdate(unsigned int flags); + + /** @brief Updates the object's display immediately + * + * This method is called during the idle loop by SPDocument in order to update the object's + * display. + * + * One additional flag is legal here: + * + * SP_OBJECT_PARENT_MODIFIED_FLAG - the parent has been + * modified + * + * @param ctx an SPCtx which accumulates various state + * during the recursive update -- beware! some + * subclasses try to cast this to an SPItemCtx * + * + * @param flags flags indicating what to update (in addition + * to any already set flags) + */ + void updateDisplay(SPCtx *ctx, unsigned int flags); + + /** @brief Requests that a modification notification signal + * be emitted later (e.g. during the idle loop) + * + * @param flags flags indicating what has been modified + */ + void requestModified(unsigned int flags); + + /** @brief Emits a modification notification signal + * + * @param flags indicating what has been modified + */ + void emitModified(unsigned int flags); + + void _sendDeleteSignalRecursive(); + void _updateTotalHRefCount(int increment); + + void _requireSVGVersion(unsigned major, unsigned minor) { + _requireSVGVersion(Inkscape::Version(major, minor)); + } + void _requireSVGVersion(Inkscape::Version version); + + sigc::signal _delete_signal; + SPObject *_successor; + CollectionPolicy _collection_policy; + gchar *_label; + mutable gchar *_default_label; +}; + +/// The SPObject vtable. +struct SPObjectClass { + GObjectClass parent_class; + + void (* build) (SPObject *object, SPDocument *doc, Inkscape::XML::Node *repr); + void (* release) (SPObject *object); + + /* Virtual handlers of repr signals */ + void (* child_added) (SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref); + void (* remove_child) (SPObject *object, Inkscape::XML::Node *child); + + void (* order_changed) (SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *old, Inkscape::XML::Node *new_repr); + + void (* set) (SPObject *object, unsigned int key, gchar const *value); + + void (* read_content) (SPObject *object); + + /* Update handler */ + void (* update) (SPObject *object, SPCtx *ctx, unsigned int flags); + /* Modification handler */ + void (* modified) (SPObject *object, unsigned int flags); + + Inkscape::XML::Node * (* write) (SPObject *object, Inkscape::XML::Node *repr, unsigned int flags); +}; + + +/* + * Attaching/detaching + */ + +void sp_object_attach(SPObject *parent, SPObject *object, SPObject *prev); +void sp_object_reorder(SPObject *object, SPObject *prev); +void sp_object_detach(SPObject *parent, SPObject *object); + +inline SPObject *sp_object_first_child(SPObject *parent) { + return parent->firstChild(); +} +SPObject *sp_object_get_child_by_repr(SPObject *object, Inkscape::XML::Node *repr); + +void sp_object_invoke_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr, unsigned int cloned); +void sp_object_invoke_release(SPObject *object); + +void sp_object_set(SPObject *object, unsigned int key, gchar const *value); + +void sp_object_read_attr(SPObject *object, gchar const *key); + +/* + * Get and set descriptive parameters. + * + * These are inefficent, so they are not intended to be used interactively. + */ + +gchar const *sp_object_title_get(SPObject *object); +gchar const *sp_object_description_get(SPObject *object); +unsigned int sp_object_title_set(SPObject *object, gchar const *title); +unsigned int sp_object_description_set(SPObject *object, gchar const *desc); + +/* Public */ + +gchar const *sp_object_tagName_get(SPObject const *object, SPException *ex); +gchar const *sp_object_getAttribute(SPObject const *object, gchar const *key, SPException *ex); +void sp_object_setAttribute(SPObject *object, gchar const *key, gchar const *value, SPException *ex); +void sp_object_removeAttribute(SPObject *object, gchar const *key, SPException *ex); + +/* Style */ + +gchar const *sp_object_get_style_property(SPObject const *object, + gchar const *key, gchar const *def); + +Inkscape::Version sp_object_get_sodipodi_version(SPObject *object); + +int sp_object_compare_position(SPObject const *first, SPObject const *second); + +SPObject *sp_object_prev(SPObject *child); + + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/sp-offset.cpp b/src/sp-offset.cpp new file mode 100644 index 000000000..5c79eb334 --- /dev/null +++ b/src/sp-offset.cpp @@ -0,0 +1,1238 @@ +#define __SP_OFFSET_C__ + +/** \file + * Implementation of . + */ + +/* + * Authors: (of the sp-spiral.c upon which this file was constructed): + * Mitsuru Oka + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + + +#include "svg/svg.h" +#include "attributes.h" +#include "display/curve.h" +#include + +#include "livarot/Path.h" +#include "livarot/Shape.h" + +#include "enums.h" +#include "prefs-utils.h" +#include "sp-text.h" +#include "sp-offset.h" +#include "sp-use-reference.h" +#include "uri.h" + +#include "libnr/n-art-bpath.h" +#include + +#include "xml/repr.h" + +class SPDocument; + +#define noOFFSET_VERBOSE + +/** \note + * SPOffset is a derivative of SPShape, much like the SPSpiral or SPRect. + * The goal is to have a source shape (= originalPath), an offset (= radius) + * and compute the offset of the source by the radius. To get it to work, + * one needs to know what the source is and what the radius is, and how it's + * stored in the xml representation. The object itself is a "path" element, + * to get lots of shape functionality for free. The source is the easy part: + * it's stored in a "inkscape:original" attribute in the path. In case of + * "linked" offset, as they've been dubbed, there is an additional + * "inkscape:href" that contains the id of an element of the svg. + * When built, the object will attach a listener vector to that object and + * rebuild the "inkscape:original" whenever the href'd object changes. This + * is of course grossly inefficient, and also does not react to changes + * to the href'd during context stuff (like changing the shape of a star by + * dragging control points) unless the path of that object is changed during + * the context (seems to be the case for SPEllipse). The computation of the + * offset is done in sp_offset_set_shape(), a function that is called whenever + * a change occurs to the offset (change of source or change of radius). + * just like the sp-star and other, this path derivative can make control + * points, or more precisely one control point, that's enough to define the + * radius (look in object-edit). + */ + +static void sp_offset_class_init (SPOffsetClass * klass); +static void sp_offset_init (SPOffset * offset); +static void sp_offset_finalize(GObject *obj); + +static void sp_offset_build (SPObject * object, SPDocument * document, + Inkscape::XML::Node * repr); +static Inkscape::XML::Node *sp_offset_write (SPObject * object, Inkscape::XML::Node * repr, + guint flags); +static void sp_offset_set (SPObject * object, unsigned int key, + const gchar * value); +static void sp_offset_update (SPObject * object, SPCtx * ctx, guint flags); +static void sp_offset_release (SPObject * object); + +static gchar *sp_offset_description (SPItem * item); +static void sp_offset_snappoints(SPItem const *item, SnapPointsIter p); +static void sp_offset_set_shape (SPShape * shape); + +Path *bpath_to_liv_path (NArtBpath * bpath); + +static void refresh_offset_source(SPOffset* offset); + +static void sp_offset_start_listening(SPOffset *offset,SPObject* to); +static void sp_offset_quit_listening(SPOffset *offset); +static void sp_offset_href_changed(SPObject *old_ref, SPObject *ref, SPOffset *offset); +static void sp_offset_move_compensate(NR::Matrix const *mp, SPItem *original, SPOffset *self); +static void sp_offset_delete_self(SPObject *deleted, SPOffset *self); +static void sp_offset_source_modified (SPObject *iSource, guint flags, SPItem *item); + + +// slow= source path->polygon->offset of polygon->polygon->path +// fast= source path->offset of source path->polygon->path +// fast is not mathematically correct, because computing the offset of a single +// cubic bezier patch is not trivial; in particular, there are problems with holes +// reappearing in offset when the radius becomes too large +static bool use_slow_but_correct_offset_method=false; + + +// nothing special here, same for every class in sodipodi/inkscape +static SPShapeClass *parent_class; + +/** + * Register SPOffset class and return its type number. + */ +GType +sp_offset_get_type (void) +{ + static GType offset_type = 0; + + if (!offset_type) + { + GTypeInfo offset_info = { + sizeof (SPOffsetClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_offset_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (SPOffset), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_offset_init, + NULL, /* value_table */ + }; + offset_type = + g_type_register_static (SP_TYPE_SHAPE, "SPOffset", &offset_info, + (GTypeFlags) 0); + } + return offset_type; +} + +/** + * SPOffset vtable initialization. + */ +static void +sp_offset_class_init(SPOffsetClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + SPObjectClass *sp_object_class = (SPObjectClass *) klass; + SPItemClass *item_class = (SPItemClass *) klass; + SPShapeClass *shape_class = (SPShapeClass *) klass; + + parent_class = (SPShapeClass *) g_type_class_ref (SP_TYPE_SHAPE); + + gobject_class->finalize = sp_offset_finalize; + + sp_object_class->build = sp_offset_build; + sp_object_class->write = sp_offset_write; + sp_object_class->set = sp_offset_set; + sp_object_class->update = sp_offset_update; + sp_object_class->release = sp_offset_release; + + item_class->description = sp_offset_description; + item_class->snappoints = sp_offset_snappoints; + + shape_class->set_shape = sp_offset_set_shape; +} + +/** + * Callback for SPOffset object initialization. + */ +static void +sp_offset_init(SPOffset *offset) +{ + offset->rad = 1.0; + offset->original = NULL; + offset->originalPath = NULL; + offset->knotSet = false; + offset->sourceDirty=false; + offset->isUpdating=false; + // init various connections + offset->sourceHref = NULL; + offset->sourceRepr = NULL; + offset->sourceObject = NULL; + new (&offset->_delete_connection) sigc::connection(); + new (&offset->_changed_connection) sigc::connection(); + new (&offset->_transformed_connection) sigc::connection(); + // set up the uri reference + offset->sourceRef = new SPUseReference(SP_OBJECT(offset)); + offset->_changed_connection = offset->sourceRef->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_offset_href_changed), offset)); +} + +/** + * Callback for SPOffset finalization. + */ +static void +sp_offset_finalize(GObject *obj) +{ + SPOffset *offset = (SPOffset *) obj; + + delete offset->sourceRef; + offset->_delete_connection.~connection(); + offset->_changed_connection.~connection(); + offset->_transformed_connection.~connection(); +} + +/** + * Virtual build: set offset attributes from corresponding repr. + */ +static void +sp_offset_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + if (((SPObjectClass *) parent_class)->build) + ((SPObjectClass *) parent_class)->build (object, document, repr); + + if (object->repr->attribute("inkscape:radius")) { + sp_object_read_attr (object, "inkscape:radius"); + } else { + gchar const *oldA = object->repr->attribute("sodipodi:radius"); + object->repr->setAttribute("inkscape:radius",oldA); + object->repr->setAttribute("sodipodi:radius",NULL); + + sp_object_read_attr (object, "inkscape:radius"); + } + if (object->repr->attribute("inkscape:original")) { + sp_object_read_attr (object, "inkscape:original"); + } else { + gchar const *oldA = object->repr->attribute("sodipodi:original"); + object->repr->setAttribute("inkscape:original",oldA); + object->repr->setAttribute("sodipodi:original",NULL); + + sp_object_read_attr (object, "inkscape:original"); + } + if (object->repr->attribute("xlink:href")) { + sp_object_read_attr(object, "xlink:href"); + } else { + gchar const *oldA = object->repr->attribute("inkscape:href"); + if (oldA) { + size_t lA = strlen(oldA); + char *nA=(char*)malloc((lA+1)*sizeof(char)); + memcpy(nA+1,oldA,lA*sizeof(char)); + nA[0]='#'; + nA[lA+1]=0; + object->repr->setAttribute("xlink:href",nA); + free(nA); + object->repr->setAttribute("inkscape:href",NULL); + } + sp_object_read_attr (object, "xlink:href"); + } +} + +/** + * Virtual write: write offset attributes to corresponding repr. + */ +static Inkscape::XML::Node * +sp_offset_write(SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + SPOffset *offset = SP_OFFSET (object); + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = sp_repr_new ("svg:path"); + } + + if (flags & SP_OBJECT_WRITE_EXT) { + /** \todo + * Fixme: we may replace these attributes by + * inkscape:offset="cx cy exp revo rad arg t0" + */ + repr->setAttribute("sodipodi:type", "inkscape:offset"); + sp_repr_set_svg_double(repr, "inkscape:radius", offset->rad); + repr->setAttribute("inkscape:original", offset->original); + repr->setAttribute("inkscape:href", offset->sourceHref); + } + + + // Make sure the object has curve + SPCurve *curve = sp_shape_get_curve (SP_SHAPE (offset)); + if (curve == NULL) { + sp_offset_set_shape (SP_SHAPE (offset)); + } + + // write that curve to "d" + char *d = sp_svg_write_path (((SPShape *) offset)->curve->bpath); + repr->setAttribute("d", d); + g_free (d); + + if (((SPObjectClass *) (parent_class))->write) + ((SPObjectClass *) (parent_class))->write (object, repr, + flags | SP_SHAPE_WRITE_PATH); + + return repr; +} + +/** + * Virtual release callback. + */ +static void +sp_offset_release(SPObject *object) +{ + SPOffset *offset = (SPOffset *) object; + + if (offset->original) free (offset->original); + if (offset->originalPath) delete ((Path *) offset->originalPath); + offset->original = NULL; + offset->originalPath = NULL; + + sp_offset_quit_listening(offset); + + offset->_changed_connection.disconnect(); + g_free(offset->sourceHref); + offset->sourceHref = NULL; + offset->sourceRef->detach(); + + if (((SPObjectClass *) parent_class)->release) { + ((SPObjectClass *) parent_class)->release (object); + } + +} + +/** + * Set callback: the function that is called whenever a change is made to + * the description of the object. + */ +static void +sp_offset_set(SPObject *object, unsigned key, gchar const *value) +{ + SPOffset *offset = SP_OFFSET (object); + + if ( offset->sourceDirty ) refresh_offset_source(offset); + + /* fixme: we should really collect updates */ + switch (key) + { + case SP_ATTR_INKSCAPE_ORIGINAL: + case SP_ATTR_SODIPODI_ORIGINAL: + if (value == NULL) { + } else { + if (offset->original) { + free (offset->original); + delete ((Path *) offset->originalPath); + offset->original = NULL; + offset->originalPath = NULL; + } + NArtBpath *bpath; + SPCurve *curve; + + offset->original = strdup (value); + + bpath = sp_svg_read_path (offset->original); + curve = sp_curve_new_from_bpath (bpath); // curve se chargera de detruire bpath + g_assert (curve != NULL); + offset->originalPath = bpath_to_liv_path (curve->bpath); + sp_curve_unref (curve); + + offset->knotSet = false; + if ( offset->isUpdating == false ) object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } + break; + case SP_ATTR_INKSCAPE_RADIUS: + case SP_ATTR_SODIPODI_RADIUS: + if (!sp_svg_length_read_computed_absolute (value, &offset->rad)) { + if (fabs (offset->rad) < 0.01) + offset->rad = (offset->rad < 0) ? -0.01 : 0.01; + offset->knotSet = false; // knotset=false because it's not set from the context + } + if ( offset->isUpdating == false ) object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_HREF: + case SP_ATTR_XLINK_HREF: + if ( value == NULL ) { + sp_offset_quit_listening(offset); + if ( offset->sourceHref ) g_free(offset->sourceHref); + offset->sourceHref = NULL; + offset->sourceRef->detach(); + } else { + if ( offset->sourceHref && ( strcmp(value, offset->sourceHref) == 0 ) ) { + } else { + if ( offset->sourceHref ) g_free(offset->sourceHref); + offset->sourceHref = g_strdup(value); + try { + offset->sourceRef->attach(Inkscape::URI(value)); + } catch (Inkscape::BadURIException &e) { + g_warning("%s", e.what()); + offset->sourceRef->detach(); + } + } + } + break; + default: + if (((SPObjectClass *) parent_class)->set) + ((SPObjectClass *) parent_class)->set (object, key, value); + break; + } +} + +/** + * Update callback: the object has changed, recompute its shape. + */ +static void +sp_offset_update(SPObject *object, SPCtx *ctx, guint flags) +{ + SPOffset* offset = SP_OFFSET(object); + offset->isUpdating=true; // prevent sp_offset_set from requesting updates + if ( offset->sourceDirty ) refresh_offset_source(offset); + if (flags & + (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | + SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + sp_shape_set_shape ((SPShape *) object); + } + offset->isUpdating=false; + + if (((SPObjectClass *) parent_class)->update) + ((SPObjectClass *) parent_class)->update (object, ctx, flags); +} + +/** + * Returns a textual description of object. + */ +static gchar * +sp_offset_description(SPItem *item) +{ + SPOffset *offset = SP_OFFSET (item); + + if ( offset->sourceHref ) { + // TRANSLATORS COMMENT: %s is either "outset" or "inset" depending on sign + return g_strdup_printf(_("Linked offset, %s by %f pt"), + (offset->rad >= 0)? _("outset") : _("inset"), fabs (offset->rad)); + } else { + // TRANSLATORS COMMENT: %s is either "outset" or "inset" depending on sign + return g_strdup_printf(_("Dynamic offset, %s by %f pt"), + (offset->rad >= 0)? _("outset") : _("inset"), fabs (offset->rad)); + } +} + +/** + * Converts an NArtBpath (like the one stored in a SPCurve) into a + * livarot Path. Duplicate of splivarot. + */ +Path * +bpath_to_liv_path(NArtBpath *bpath) +{ + if (bpath == NULL) + return NULL; + + Path *dest = new Path; + dest->SetBackData (false); + { + int i; + bool closed = false; + float lastX = 0.0; + float lastY = 0.0; + + for (i = 0; bpath[i].code != NR_END; i++) + { + switch (bpath[i].code) + { + case NR_LINETO: + lastX = bpath[i].x3; + lastY = bpath[i].y3; + { + NR::Point tmp(lastX,lastY); + dest->LineTo (tmp); + } + break; + + case NR_CURVETO: + { + NR::Point tmp(bpath[i].x3, bpath[i].y3); + NR::Point tms; + tms[0]=3 * (bpath[i].x1 - lastX); + tms[1]=3 * (bpath[i].y1 - lastY); + NR::Point tme; + tme[0]=3 * (bpath[i].x3 - bpath[i].x2); + tme[1]= 3 * (bpath[i].y3 - bpath[i].y2); + dest->CubicTo (tmp,tms,tme); + } + lastX = bpath[i].x3; + lastY = bpath[i].y3; + break; + + case NR_MOVETO_OPEN: + case NR_MOVETO: + if (closed) + dest->Close (); + closed = (bpath[i].code == NR_MOVETO); + lastX = bpath[i].x3; + lastY = bpath[i].y3; + { + NR::Point tmp(lastX,lastY); + dest->MoveTo(tmp); + } + break; + default: + break; + } + } + if (closed) + dest->Close (); + } + + return dest; +} + +/** + * Compute and set shape's offset. + */ +static void +sp_offset_set_shape(SPShape *shape) +{ + SPOffset *offset = SP_OFFSET (shape); + + if ( offset->originalPath == NULL ) { + // oops : no path?! (the offset object should do harakiri) + return; + } +#ifdef OFFSET_VERBOSE + g_print ("rad=%g\n", offset->rad); +#endif + // au boulot + + if ( fabs(offset->rad) < 0.01 ) { + // grosso modo: 0 + // just put the source shape as the offseted one, no one will notice + // it's also useless to compute the offset with a 0 radius + + const char *res_d = SP_OBJECT(shape)->repr->attribute("inkscape:original"); + if ( res_d ) { + NArtBpath *bpath = sp_svg_read_path (res_d); + SPCurve *c = sp_curve_new_from_bpath (bpath); + g_assert(c != NULL); + sp_shape_set_curve_insync ((SPShape *) offset, c, TRUE); + sp_curve_unref (c); + } + return; + } + + // extra paraniac careful check. the preceding if () should take care of this case + if (fabs (offset->rad) < 0.01) + offset->rad = (offset->rad < 0) ? -0.01 : 0.01; + + Path *orig = new Path; + orig->Copy ((Path *) offset->originalPath); + + if ( use_slow_but_correct_offset_method == false ) { + // version par outline + Shape *theShape = new Shape; + Shape *theRes = new Shape; + Path *originaux[1]; + Path *res = new Path; + res->SetBackData (false); + + // and now: offset + float o_width; + if (offset->rad >= 0) + { + o_width = offset->rad; + orig->OutsideOutline (res, o_width, join_round, butt_straight, 20.0); + } + else + { + o_width = -offset->rad; + orig->OutsideOutline (res, -o_width, join_round, butt_straight, 20.0); + } + + if (o_width >= 1.0) + { + // res->ConvertForOffset (1.0, orig, offset->rad); + res->ConvertWithBackData (1.0); + } + else + { + // res->ConvertForOffset (o_width, orig, offset->rad); + res->ConvertWithBackData (o_width); + } + res->Fill (theShape, 0); + theRes->ConvertToShape (theShape, fill_positive); + originaux[0] = res; + + theRes->ConvertToForme (orig, 1, originaux); + + SPItem *item = shape; + NR::Rect bbox = sp_item_bbox_desktop (item); + if (!bbox.isEmpty()) { + gdouble size = L2(bbox.dimensions()); + gdouble const exp = NR::expansion(item->transform); + if (exp != 0) + size /= exp; + orig->Coalesce (size * 0.001); + //g_print ("coa %g exp %g item %p\n", size * 0.001, exp, item); + } + + + // if (o_width >= 1.0) + // { + // orig->Coalesce (0.1); // small treshhold, since we only want to get rid of small segments + // the curve should already be computed by the Outline() function + // orig->ConvertEvenLines (1.0); + // orig->Simplify (0.5); + // } + // else + // { + // orig->Coalesce (0.1*o_width); + // orig->ConvertEvenLines (o_width); + // orig->Simplify (0.5 * o_width); + // } + + delete theShape; + delete theRes; + delete res; + } else { + // version par makeoffset + Shape *theShape = new Shape; + Shape *theRes = new Shape; + + + // and now: offset + float o_width; + if (offset->rad >= 0) + { + o_width = offset->rad; + } + else + { + o_width = -offset->rad; + } + + // one has to have a measure of the details + if (o_width >= 1.0) + { + orig->ConvertWithBackData (0.5); + } + else + { + orig->ConvertWithBackData (0.5*o_width); + } + orig->Fill (theShape, 0); + theRes->ConvertToShape (theShape, fill_positive); + Path *originaux[1]; + originaux[0]=orig; + Path *res = new Path; + theRes->ConvertToForme (res, 1, originaux); + int nbPart=0; + Path** parts=res->SubPaths(nbPart,true); + char *holes=(char*)malloc(nbPart*sizeof(char)); + // we offset contours separately, because we can. + // this way, we avoid doing a unique big ConvertToShape when dealing with big shapes with lots of holes + { + Shape* onePart=new Shape; + Shape* oneCleanPart=new Shape; + theShape->Reset(); + for (int i=0;iSurface(); + parts[i]->Convert(1.0); + { + // raffiner si besoin + double bL,bT,bR,bB; + parts[i]->PolylineBoundingBox(bL,bT,bR,bB); + double mesure=((bR-bL)+(bB-bT))*0.5; + if ( mesure < 10.0 ) { + parts[i]->Convert(0.02*mesure); + } + } + if ( partSurf < 0 ) { // inverse par rapport a la realite + // plein + holes[i]=0; + parts[i]->Fill(oneCleanPart,0); + onePart->ConvertToShape(oneCleanPart,fill_positive); // there aren't intersections in that one, but maybe duplicate points and null edges + oneCleanPart->MakeOffset(onePart,offset->rad,join_round,20.0); + onePart->ConvertToShape(oneCleanPart,fill_positive); + + onePart->CalcBBox(); + double typicalSize=0.5*((onePart->rightX-onePart->leftX)+(onePart->bottomY-onePart->topY)); + if ( typicalSize < 0.05 ) typicalSize=0.05; + typicalSize*=0.01; + if ( typicalSize > 1.0 ) typicalSize=1.0; + onePart->ConvertToForme (parts[i]); + parts[i]->ConvertEvenLines (typicalSize); + parts[i]->Simplify (typicalSize); + double nPartSurf=parts[i]->Surface(); + if ( nPartSurf >= 0 ) { + // inversion de la surface -> disparait + delete parts[i]; + parts[i]=NULL; + } else { + } +/* int firstP=theShape->nbPt; + for (int j=0;jnbPt;j++) theShape->AddPoint(onePart->pts[j].x); + for (int j=0;jnbAr;j++) theShape->AddEdge(firstP+onePart->aretes[j].st,firstP+onePart->aretes[j].en);*/ + } else { + // trou + holes[i]=1; + parts[i]->Fill(oneCleanPart,0,false,true,true); + onePart->ConvertToShape(oneCleanPart,fill_positive); + oneCleanPart->MakeOffset(onePart,-offset->rad,join_round,20.0); + onePart->ConvertToShape(oneCleanPart,fill_positive); +// for (int j=0;jnbAr;j++) onePart->Inverse(j); // pas oublier de reinverser + + onePart->CalcBBox(); + double typicalSize=0.5*((onePart->rightX-onePart->leftX)+(onePart->bottomY-onePart->topY)); + if ( typicalSize < 0.05 ) typicalSize=0.05; + typicalSize*=0.01; + if ( typicalSize > 1.0 ) typicalSize=1.0; + onePart->ConvertToForme (parts[i]); + parts[i]->ConvertEvenLines (typicalSize); + parts[i]->Simplify (typicalSize); + double nPartSurf=parts[i]->Surface(); + if ( nPartSurf >= 0 ) { + // inversion de la surface -> disparait + delete parts[i]; + parts[i]=NULL; + } else { + } + + /* int firstP=theShape->nbPt; + for (int j=0;jnbPt;j++) theShape->AddPoint(onePart->pts[j].x); + for (int j=0;jnbAr;j++) theShape->AddEdge(firstP+onePart->aretes[j].en,firstP+onePart->aretes[j].st);*/ + } +// delete parts[i]; + } +// theShape->MakeOffset(theRes,offset->rad,join_round,20.0); + delete onePart; + delete oneCleanPart; + } + if ( nbPart > 1 ) { + theShape->Reset(); + for (int i=0;iConvertWithBackData(1.0); + if ( holes[i] ) { + parts[i]->Fill(theShape,i,true,true,true); + } else { + parts[i]->Fill(theShape,i,true,true,false); + } + } + } + theRes->ConvertToShape (theShape, fill_positive); + theRes->ConvertToForme (orig,nbPart,parts); + for (int i=0;iCopy(parts[0]); + for (int i=0;iReset(); + } +// theRes->ConvertToShape (theShape, fill_positive); +// theRes->ConvertToForme (orig); + +/* if (o_width >= 1.0) { + orig->ConvertEvenLines (1.0); + orig->Simplify (1.0); + } else { + orig->ConvertEvenLines (1.0*o_width); + orig->Simplify (1.0 * o_width); + }*/ + + if ( parts ) free(parts); + if ( holes ) free(holes); + delete res; + delete theShape; + delete theRes; + } + { + char *res_d = NULL; + if (orig->descr_cmd.size() <= 1) + { + // Aie.... nothing left. + res_d = strdup ("M 0 0 L 0 0 z"); + //printf("%s\n",res_d); + } + else + { + + res_d = orig->svg_dump_path (); + } + delete orig; + + NArtBpath *bpath = sp_svg_read_path (res_d); + SPCurve *c = sp_curve_new_from_bpath (bpath); + g_assert(c != NULL); + sp_shape_set_curve_insync ((SPShape *) offset, c, TRUE); + sp_curve_unref (c); + + free (res_d); + } +} + +/** + * Virtual snappoints function. + */ +static void sp_offset_snappoints(SPItem const *item, SnapPointsIter p) +{ + if (((SPItemClass *) parent_class)->snappoints) { + ((SPItemClass *) parent_class)->snappoints (item, p); + } +} + + +// utilitaires pour les poignees +// used to get the distance to the shape: distance to polygon give the fabs(radius), we still need +// the sign. for edges, it's easy to determine which side the point is on, for points of the polygon +// it's trickier: we need to identify which angle the point is in; to that effect, we take each +// successive clockwise angle (A,C) and check if the vector B given by the point is in the angle or +// outside. +// another method would be to use the Winding() function to test whether the point is inside or outside +// the polygon (it would be wiser to do so, in fact, but i like being stupid) + +/** + * + * \todo + * FIXME: This can be done using linear operations, more stably and + * faster. method: transform A and C into B's space, A should be + * negative and B should be positive in the orthogonal component. I + * think this is equivalent to + * dot(A, rot90(B))*dot(C, rot90(B)) == -1. + * -- njh + */ +bool +vectors_are_clockwise (NR::Point A, NR::Point B, NR::Point C) +{ + using NR::rot90; + double ab_s = dot(A, rot90(B)); + double ab_c = dot(A, B); + double bc_s = dot(B, rot90(C)); + double bc_c = dot(B, C); + double ca_s = dot(C, rot90(A)); + double ca_c = dot(C, A); + + double ab_a = acos (ab_c); + if (ab_c <= -1.0) + ab_a = M_PI; + if (ab_c >= 1.0) + ab_a = 0; + if (ab_s < 0) + ab_a = 2 * M_PI - ab_a; + double bc_a = acos (bc_c); + if (bc_c <= -1.0) + bc_a = M_PI; + if (bc_c >= 1.0) + bc_a = 0; + if (bc_s < 0) + bc_a = 2 * M_PI - bc_a; + double ca_a = acos (ca_c); + if (ca_c <= -1.0) + ca_a = M_PI; + if (ca_c >= 1.0) + ca_a = 0; + if (ca_s < 0) + ca_a = 2 * M_PI - ca_a; + + double lim = 2 * M_PI - ca_a; + + if (ab_a < lim) + return true; + return false; +} + +/** + * Distance to the original path; that function is called from object-edit + * to set the radius when the control knot moves. + * + * The sign of the result is the radius we're going to offset the shape with, + * so result > 0 ==outset and result < 0 ==inset. thus result<0 means + * 'px inside source'. + */ +double +sp_offset_distance_to_original (SPOffset * offset, NR::Point px) +{ + if (offset == NULL || offset->originalPath == NULL + || ((Path *) offset->originalPath)->descr_cmd.size() <= 1) + return 1.0; + double dist = 1.0; + Shape *theShape = new Shape; + Shape *theRes = new Shape; + + /** \todo + * Awfully damn stupid method: uncross the source path EACH TIME you + * need to compute the distance. The good way to do this would be to + * store the uncrossed source path somewhere, and delete it when the + * context is finished. Hopefully this part is much faster than actually + * computing the offset (which happen just after), so the time spent in + * this function should end up being negligible with respect to the + * delay of one context. + */ + // move + ((Path *) offset->originalPath)->Convert (1.0); + ((Path *) offset->originalPath)->Fill (theShape, 0); + theRes->ConvertToShape (theShape, fill_oddEven); + + if (theRes->numberOfEdges() <= 1) + { + + } + else + { + double ptDist = -1.0; + bool ptSet = false; + double arDist = -1.0; + bool arSet = false; + // first get the minimum distance to the points + for (int i = 0; i < theRes->numberOfPoints(); i++) + { + if (theRes->getPoint(i).totalDegree() > 0) + { + NR::Point nx = theRes->getPoint(i).x; + NR::Point nxpx = px-nx; + double ndist = sqrt (dot(nxpx,nxpx)); + if (ptSet == false || fabs (ndist) < fabs (ptDist)) + { + // we have a new minimum distance + // now we need to wheck if px is inside or outside (for the sign) + nx = px - theRes->getPoint(i).x; + double nlen = sqrt (dot(nx , nx)); + nx /= nlen; + int pb, cb, fb; + fb = theRes->getPoint(i).incidentEdge[LAST]; + pb = theRes->getPoint(i).incidentEdge[LAST]; + cb = theRes->getPoint(i).incidentEdge[FIRST]; + do + { + // one angle + NR::Point prx, nex; + prx = theRes->getEdge(pb).dx; + nlen = sqrt (dot(prx, prx)); + prx /= nlen; + nex = theRes->getEdge(cb).dx; + nlen = sqrt (dot(nex , nex)); + nex /= nlen; + if (theRes->getEdge(pb).en == i) + { + prx = -prx; + } + if (theRes->getEdge(cb).en == i) + { + nex = -nex; + } + + if (vectors_are_clockwise (nex, nx, prx)) + { + // we're in that angle. set the sign, and exit that loop + if (theRes->getEdge(cb).st == i) + { + ptDist = -ndist; + ptSet = true; + } + else + { + ptDist = ndist; + ptSet = true; + } + break; + } + pb = cb; + cb = theRes->NextAt (i, cb); + } + while (cb >= 0 && pb >= 0 && pb != fb); + } + } + } + // loop over the edges to try to improve the distance + for (int i = 0; i < theRes->numberOfEdges(); i++) + { + NR::Point sx = theRes->getPoint(theRes->getEdge(i).st).x; + NR::Point ex = theRes->getPoint(theRes->getEdge(i).en).x; + NR::Point nx = ex - sx; + double len = sqrt (dot(nx,nx)); + if (len > 0.0001) + { + NR::Point pxsx=px-sx; + double ab = dot(nx,pxsx); + if (ab > 0 && ab < len * len) + { + // we're in the zone of influence of the segment + double ndist = (cross(pxsx,nx)) / len; + if (arSet == false || fabs (ndist) < fabs (arDist)) + { + arDist = ndist; + arSet = true; + } + } + } + } + if (arSet || ptSet) + { + if (arSet == false) + arDist = ptDist; + if (ptSet == false) + ptDist = arDist; + if (fabs (ptDist) < fabs (arDist)) + dist = ptDist; + else + dist = arDist; + } + } + + delete theShape; + delete theRes; + + return dist; +} + +/** + * Computes a point on the offset; used to set a "seed" position for + * the control knot. + * + * \return the topmost point on the offset. + */ +void +sp_offset_top_point (SPOffset * offset, NR::Point *px) +{ + (*px) = NR::Point(0, 0); + if (offset == NULL) + return; + + if (offset->knotSet) + { + (*px) = offset->knot; + return; + } + + SPCurve *curve = sp_shape_get_curve (SP_SHAPE (offset)); + if (curve == NULL) + { + sp_offset_set_shape (SP_SHAPE (offset)); + curve = sp_shape_get_curve (SP_SHAPE (offset)); + if (curve == NULL) + return; + } + + Path *finalPath = bpath_to_liv_path (curve->bpath); + if (finalPath == NULL) + { + sp_curve_unref (curve); + return; + } + + Shape *theShape = new Shape; + + finalPath->Convert (1.0); + finalPath->Fill (theShape, 0); + + if (theShape->hasPoints()) + { + theShape->SortPoints (); + *px = theShape->getPoint(0).x; + } + + delete theShape; + delete finalPath; + sp_curve_unref (curve); +} + +// the listening functions +static void sp_offset_start_listening(SPOffset *offset,SPObject* to) +{ + if ( to == NULL ) + return; + + offset->sourceObject = to; + offset->sourceRepr = SP_OBJECT_REPR(to); + + offset->_delete_connection = SP_OBJECT(to)->connectDelete(sigc::bind(sigc::ptr_fun(&sp_offset_delete_self), offset)); + offset->_transformed_connection = SP_ITEM(to)->connectTransformed(sigc::bind(sigc::ptr_fun(&sp_offset_move_compensate), offset)); + offset->_modified_connection = g_signal_connect (G_OBJECT (to), "modified", G_CALLBACK (sp_offset_source_modified), offset); +} + +static void sp_offset_quit_listening(SPOffset *offset) +{ + if ( offset->sourceObject == NULL ) + return; + + g_signal_handler_disconnect (offset->sourceObject, offset->_modified_connection); + offset->_delete_connection.disconnect(); + offset->_transformed_connection.disconnect(); + + offset->sourceRepr = NULL; + offset->sourceObject = NULL; +} + +static void +sp_offset_href_changed(SPObject */*old_ref*/, SPObject */*ref*/, SPOffset *offset) +{ + sp_offset_quit_listening(offset); + if (offset->sourceRef) { + SPItem *refobj = offset->sourceRef->getObject(); + if (refobj) sp_offset_start_listening(offset,refobj); + offset->sourceDirty=true; + SP_OBJECT(offset)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } +} + +static void +sp_offset_move_compensate(NR::Matrix const *mp, SPItem *original, SPOffset *self) +{ + guint mode = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_PARALLEL); + if (mode == SP_CLONE_COMPENSATION_NONE) return; + + NR::Matrix m(*mp); + if (!(m.is_translation())) return; + + // calculate the compensation matrix and the advertized movement matrix + SPItem *item = SP_ITEM(self); + + NR::Matrix compensate; + NR::Matrix advertized_move; + + if (mode == SP_CLONE_COMPENSATION_UNMOVED) { + compensate = NR::identity(); + advertized_move.set_identity(); + } else if (mode == SP_CLONE_COMPENSATION_PARALLEL) { + compensate = m; + advertized_move = m; + } else { + g_assert_not_reached(); + } + + item->transform *= compensate; + + // commit the compensation + sp_item_write_transform(item, SP_OBJECT_REPR(item), item->transform, &advertized_move); + SP_OBJECT(item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +static void +sp_offset_delete_self(SPObject */*deleted*/, SPOffset *offset) +{ + guint const mode = prefs_get_int_attribute("options.cloneorphans", "value", SP_CLONE_ORPHANS_UNLINK); + + if (mode == SP_CLONE_ORPHANS_UNLINK) { + // leave it be. just forget about the source + sp_offset_quit_listening(offset); + if ( offset->sourceHref ) g_free(offset->sourceHref); + offset->sourceHref = NULL; + offset->sourceRef->detach(); + } else if (mode == SP_CLONE_ORPHANS_DELETE) { + SP_OBJECT(offset)->deleteObject(); + } +} + +static void +sp_offset_source_modified (SPObject *iSource, guint flags, SPItem *item) +{ + SPOffset *offset = SP_OFFSET(item); + offset->sourceDirty=true; + refresh_offset_source(offset); + sp_shape_set_shape ((SPShape *) offset); +} + +static void +refresh_offset_source(SPOffset* offset) +{ + if ( offset == NULL ) return; + offset->sourceDirty=false; + Path *orig = NULL; + + // le mauvais cas: pas d'attribut d => il faut verifier que c'est une SPShape puis prendre le contour + // The bad case: no d attribute. Must check that it's an SPShape and then take the outline. + SPObject *refobj=offset->sourceObject; + if ( refobj == NULL ) return; + SPItem *item = SP_ITEM (refobj); + + SPCurve *curve=NULL; + if (!SP_IS_SHAPE (item) && !SP_IS_TEXT (item)) return; + if (SP_IS_SHAPE (item)) { + curve = sp_shape_get_curve (SP_SHAPE (item)); + if (curve == NULL) + return; + } + if (SP_IS_TEXT (item)) { + curve = SP_TEXT (item)->getNormalizedBpath (); + if (curve == NULL) + return; + } + orig = bpath_to_liv_path (curve->bpath); + sp_curve_unref (curve); + + + // Finish up. + { + SPCSSAttr *css; + const gchar *val; + Shape *theShape = new Shape; + Shape *theRes = new Shape; + + orig->ConvertWithBackData (1.0); + orig->Fill (theShape, 0); + + css = sp_repr_css_attr (offset->sourceRepr , "style"); + val = sp_repr_css_property (css, "fill-rule", NULL); + if (val && strcmp (val, "nonzero") == 0) + { + theRes->ConvertToShape (theShape, fill_nonZero); + } + else if (val && strcmp (val, "evenodd") == 0) + { + theRes->ConvertToShape (theShape, fill_oddEven); + } + else + { + theRes->ConvertToShape (theShape, fill_nonZero); + } + + Path *originaux[1]; + originaux[0] = orig; + Path *res = new Path; + theRes->ConvertToForme (res, 1, originaux); + + delete theShape; + delete theRes; + + char *res_d = res->svg_dump_path (); + delete res; + delete orig; + + SP_OBJECT (offset)->repr->setAttribute("inkscape:original", res_d); + + free (res_d); + } +} + +SPItem * +sp_offset_get_source (SPOffset *offset) +{ + if (offset && offset->sourceRef) { + SPItem *refobj = offset->sourceRef->getObject(); + if (SP_IS_ITEM (refobj)) + return (SPItem *) refobj; + } + return NULL; +} + + +/* + 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 : diff --git a/src/sp-offset.h b/src/sp-offset.h new file mode 100644 index 000000000..52c793d0e --- /dev/null +++ b/src/sp-offset.h @@ -0,0 +1,108 @@ +#ifndef __SP_OFFSET_H__ +#define __SP_OFFSET_H__ + +/** \file + * SPOffset class. + * + * Authors: + * Mitsuru Oka + * Lauris Kaplinski + * (of the sp-spiral.h upon which this file was created) + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-shape.h" + +#include + +#define SP_TYPE_OFFSET (sp_offset_get_type ()) +#define SP_OFFSET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_OFFSET, SPOffset)) +#define SP_OFFSET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_OFFSET, SPOffsetClass)) +#define SP_IS_OFFSET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_OFFSET)) +#define SP_IS_OFFSET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_OFFSET)) + +class SPOffset; +class SPOffsetClass; +class SPUseReference; + +/** + * SPOffset class. + * + * An offset is defined by curve and radius. The original curve is kept as + * a path in a sodipodi:original attribute. It's not possible to change + * the original curve. + * + * SPOffset is a derivative of SPShape, much like the SPSpiral or SPRect. + * The goal is to have a source shape (= originalPath), an offset (= radius) + * and compute the offset of the source by the radius. To get it to work, + * one needs to know what the source is and what the radius is, and how it's + * stored in the xml representation. The object itself is a "path" element, + * to get lots of shape functionality for free. The source is the easy part: + * it's stored in a "inkscape:original" attribute in the path. In case of + * "linked" offset, as they've been dubbed, there is an additional + * "inkscape:href" that contains the id of an element of the svg. + * When built, the object will attach a listener vector to that object and + * rebuild the "inkscape:original" whenever the href'd object changes. This + * is of course grossly inefficient, and also does not react to changes + * to the href'd during context stuff (like changing the shape of a star by + * dragging control points) unless the path of that object is changed during + * the context (seems to be the case for SPEllipse). The computation of the + * offset is done in sp_offset_set_shape(), a function that is called whenever + * a change occurs to the offset (change of source or change of radius). + * just like the sp-star and other, this path derivative can make control + * points, or more precisely one control point, that's enough to define the + * radius (look in object-edit). + */ +struct SPOffset : public SPShape { + void *originalPath; ///< will be a livarot Path, just don't declare it here to please the gcc linker + char *original; ///< SVG description of the source path + float rad; ///< offset radius + + /// for interactive setting of the radius + bool knotSet; + NR::Point knot; + + bool sourceDirty; + bool isUpdating; + + gchar *sourceHref; + SPUseReference *sourceRef; + Inkscape::XML::Node *sourceRepr; ///< the repr associated with that id + SPObject *sourceObject; + + gulong _modified_connection; + sigc::connection _delete_connection; + sigc::connection _changed_connection; + sigc::connection _transformed_connection; +}; + +/// The SPOffset vtable. +struct SPOffsetClass +{ + SPShapeClass parent_class; +}; + + +/* Standard Gtk function */ +GType sp_offset_get_type (void); + +double sp_offset_distance_to_original (SPOffset * offset, NR::Point px); +void sp_offset_top_point (SPOffset * offset, NR::Point *px); + +SPItem *sp_offset_get_source (SPOffset *offset); + +#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:encoding=utf-8:textwidth=99 : diff --git a/src/sp-paint-server.cpp b/src/sp-paint-server.cpp new file mode 100644 index 000000000..24727628c --- /dev/null +++ b/src/sp-paint-server.cpp @@ -0,0 +1,163 @@ +#define __SP_PAINT_SERVER_C__ + +/* + * Base class for gradients and patterns + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include "sp-paint-server.h" + +static void sp_paint_server_class_init(SPPaintServerClass *psc); +static void sp_paint_server_init(SPPaintServer *ps); + +static void sp_paint_server_release(SPObject *object); + +static void sp_painter_stale_fill(SPPainter *painter, NRPixBlock *pb); + +static SPObjectClass *parent_class; +static GSList *stale_painters = NULL; + +GType sp_paint_server_get_type (void) +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPPaintServerClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_paint_server_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof(SPPaintServer), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_paint_server_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_OBJECT, "SPPaintServer", &info, (GTypeFlags) 0); + } + return type; +} + +static void sp_paint_server_class_init(SPPaintServerClass *psc) +{ + SPObjectClass *sp_object_class = (SPObjectClass *) psc; + sp_object_class->release = sp_paint_server_release; + parent_class = (SPObjectClass *) g_type_class_ref(SP_TYPE_OBJECT); +} + +static void sp_paint_server_init(SPPaintServer *ps) +{ + ps->painters = NULL; +} + +static void sp_paint_server_release(SPObject *object) +{ + SPPaintServer *ps = SP_PAINT_SERVER(object); + + while (ps->painters) { + SPPainter *painter = ps->painters; + ps->painters = painter->next; + stale_painters = g_slist_prepend(stale_painters, painter); + painter->next = NULL; + painter->server = NULL; + painter->fill = sp_painter_stale_fill; + } + + if (((SPObjectClass *) parent_class)->release) { + ((SPObjectClass *) parent_class)->release(object); + } +} + +SPPainter *sp_paint_server_painter_new(SPPaintServer *ps, + NR::Matrix const &full_transform, + NR::Matrix const &parent_transform, + const NRRect *bbox) +{ + g_return_val_if_fail(ps != NULL, NULL); + g_return_val_if_fail(SP_IS_PAINT_SERVER(ps), NULL); + g_return_val_if_fail(bbox != NULL, NULL); + + SPPainter *painter = NULL; + SPPaintServerClass *psc = (SPPaintServerClass *) G_OBJECT_GET_CLASS(ps); + if ( psc->painter_new ) { + painter = (*psc->painter_new)(ps, full_transform, parent_transform, bbox); + } + + if (painter) { + painter->next = ps->painters; + painter->server = ps; + painter->type = (SPPainterType) G_OBJECT_TYPE(ps); + ps->painters = painter; + } + + return painter; +} + +static void sp_paint_server_painter_free(SPPaintServer *ps, SPPainter *painter) +{ + g_return_if_fail(ps != NULL); + g_return_if_fail(SP_IS_PAINT_SERVER(ps)); + g_return_if_fail(painter != NULL); + + SPPaintServerClass *psc = (SPPaintServerClass *) G_OBJECT_GET_CLASS(ps); + + SPPainter *r = NULL; + for (SPPainter *p = ps->painters; p != NULL; p = p->next) { + if (p == painter) { + if (r) { + r->next = p->next; + } else { + ps->painters = p->next; + } + p->next = NULL; + if (psc->painter_free) { + (*psc->painter_free) (ps, painter); + } + return; + } + r = p; + } + + g_assert_not_reached(); +} + +SPPainter *sp_painter_free(SPPainter *painter) +{ + g_return_val_if_fail(painter != NULL, NULL); + + if (painter->server) { + sp_paint_server_painter_free(painter->server, painter); + } else { + SPPaintServerClass *psc = (SPPaintServerClass *) g_type_class_ref(painter->type); + if (psc->painter_free) + (*psc->painter_free)(NULL, painter); + stale_painters = g_slist_remove(stale_painters, painter); + } + + return NULL; +} + +static void sp_painter_stale_fill(SPPainter *painter, NRPixBlock *pb) +{ + nr_pixblock_render_gray_noise(pb, NULL); +} + + +/* + 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/sp-paint-server.h b/src/sp-paint-server.h new file mode 100644 index 000000000..9589b04d3 --- /dev/null +++ b/src/sp-paint-server.h @@ -0,0 +1,66 @@ +#ifndef __SP_PAINT_SERVER_H__ +#define __SP_PAINT_SERVER_H__ + +/* + * Base class for gradients and patterns + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include "sp-object.h" + + + +class SPPainter; + +#define SP_TYPE_PAINT_SERVER (sp_paint_server_get_type ()) +#define SP_PAINT_SERVER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_PAINT_SERVER, SPPaintServer)) +#define SP_PAINT_SERVER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_PAINT_SERVER, SPPaintServerClass)) +#define SP_IS_PAINT_SERVER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_PAINT_SERVER)) +#define SP_IS_PAINT_SERVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_PAINT_SERVER)) + +typedef enum { + SP_PAINTER_IND, + SP_PAINTER_DEP +} SPPainterType; + +typedef void (* SPPainterFillFunc) (SPPainter *painter, NRPixBlock *pb); + +/* fixme: I do not like that class thingie (Lauris) */ +struct SPPainter { + SPPainter *next; + SPPaintServer *server; + GType server_type; + SPPainterType type; + SPPainterFillFunc fill; +}; + +struct SPPaintServer : public SPObject { + /* List of paints */ + SPPainter *painters; +}; + +struct SPPaintServerClass { + SPObjectClass sp_object_class; + /* Get SPPaint instance */ + SPPainter * (* painter_new) (SPPaintServer *ps, NR::Matrix const &full_transform, NR::Matrix const &parent_transform, const NRRect *bbox); + /* Free SPPaint instance */ + void (* painter_free) (SPPaintServer *ps, SPPainter *painter); +}; + +GType sp_paint_server_get_type (void); + +SPPainter *sp_paint_server_painter_new (SPPaintServer *ps, NR::Matrix const &full_transform, NR::Matrix const &parent_transform, const NRRect *bbox); + +SPPainter *sp_painter_free (SPPainter *painter); + + + +#endif diff --git a/src/sp-path.cpp b/src/sp-path.cpp new file mode 100644 index 000000000..9fe2f54c5 --- /dev/null +++ b/src/sp-path.cpp @@ -0,0 +1,325 @@ +#define __SP_PATH_C__ + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * David Turner + * + * Copyright (C) 2004 David Turner + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#if defined(WIN32) || defined(__APPLE__) +# include +#endif + +#include +#include +#include +#include + +#include "svg/svg.h" +#include "xml/repr.h" +#include "attributes.h" + +#include "sp-path.h" + +#define noPATH_VERBOSE + +static void sp_path_class_init(SPPathClass *klass); +static void sp_path_init(SPPath *path); +static void sp_path_finalize(GObject *obj); +static void sp_path_release(SPObject *object); + +static void sp_path_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); +static void sp_path_set(SPObject *object, unsigned key, gchar const *value); + +static Inkscape::XML::Node *sp_path_write(SPObject *object, Inkscape::XML::Node *repr, guint flags); +static NR::Matrix sp_path_set_transform(SPItem *item, NR::Matrix const &xform); +static gchar * sp_path_description(SPItem *item); + +static void sp_path_update(SPObject *object, SPCtx *ctx, guint flags); + +static SPShapeClass *parent_class; + +/** + * Gets the GType object for SPPathClass + */ +GType +sp_path_get_type(void) +{ + static GType type = 0; + + if (!type) { + GTypeInfo info = { + sizeof(SPPathClass), + NULL, NULL, + (GClassInitFunc) sp_path_class_init, + NULL, NULL, + sizeof(SPPath), + 16, + (GInstanceInitFunc) sp_path_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_SHAPE, "SPPath", &info, (GTypeFlags)0); + } + return type; +} + +/** + * Does the object-oriented work of initializing the class structure + * including parent class, and registers function pointers for + * the functions build, set, write, and set_transform. + */ +static void +sp_path_class_init(SPPathClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + SPObjectClass *sp_object_class = (SPObjectClass *) klass; + SPItemClass *item_class = (SPItemClass *) klass; + + parent_class = (SPShapeClass *)g_type_class_peek_parent(klass); + + gobject_class->finalize = sp_path_finalize; + + sp_object_class->build = sp_path_build; + sp_object_class->release = sp_path_release; + sp_object_class->set = sp_path_set; + sp_object_class->write = sp_path_write; + sp_object_class->update = sp_path_update; + + item_class->description = sp_path_description; + item_class->set_transform = sp_path_set_transform; +} + + +gint +sp_nodes_in_path(SPPath *path) +{ + SPCurve *curve = SP_SHAPE(path)->curve; + if (!curve) return 0; + gint r = curve->end; + gint i = curve->length - 1; + if (i > r) i = r; // sometimes after switching from node editor length is wrong, e.g. f6 - draw - f2 - tab - f1, this fixes it + for (; i >= 0; i --) + if ((curve->bpath + i) -> code == NR_MOVETO) + r --; + return r; +} + +static gchar * +sp_path_description(SPItem * item) +{ + int count = sp_nodes_in_path(SP_PATH(item)); + return g_strdup_printf(ngettext("Path (%i node)", + "Path (%i nodes)",count), count); +} + +/** + * Initializes an SPPath. Currently does nothing. + */ +static void +sp_path_init(SPPath *path) +{ + new (&path->connEndPair) SPConnEndPair(path); +} + +static void +sp_path_finalize(GObject *obj) +{ + SPPath *path = (SPPath *) obj; + + path->connEndPair.~SPConnEndPair(); +} + +/** + * Given a repr, this sets the data items in the path object such as + * fill & style attributes, markers, and CSS properties. + */ +static void +sp_path_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + sp_object_read_attr(object, "d"); + + /* d is a required attribute */ + gchar const *d = sp_object_getAttribute(object, "d", NULL); + if (d == NULL) { + sp_object_set(object, sp_attribute_lookup("d"), ""); + } + + /* Are these calls actually necessary? */ + sp_object_read_attr(object, "marker"); + sp_object_read_attr(object, "marker-start"); + sp_object_read_attr(object, "marker-mid"); + sp_object_read_attr(object, "marker-end"); + + sp_conn_end_pair_build(object); + + if (((SPObjectClass *) parent_class)->build) { + ((SPObjectClass *) parent_class)->build(object, document, repr); + } +} + +static void +sp_path_release(SPObject *object) +{ + SPPath *path = SP_PATH(object); + + path->connEndPair.release(); + + if (((SPObjectClass *) parent_class)->release) { + ((SPObjectClass *) parent_class)->release(object); + } +} + +/** + * Sets a value in the path object given by 'key', to 'value'. This is used + * for setting attributes and markers on a path object. + */ +static void +sp_path_set(SPObject *object, unsigned int key, gchar const *value) +{ + SPPath *path = (SPPath *) object; + + switch (key) { + case SP_ATTR_D: + if (value) { + NArtBpath *bpath = sp_svg_read_path(value); + SPCurve *curve = sp_curve_new_from_bpath(bpath); + if (curve) { + sp_shape_set_curve((SPShape *) path, curve, TRUE); + sp_curve_unref(curve); + } + } else { + sp_shape_set_curve((SPShape *) path, NULL, TRUE); + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_PROP_MARKER: + case SP_PROP_MARKER_START: + case SP_PROP_MARKER_MID: + case SP_PROP_MARKER_END: + sp_shape_set_marker(object, key, value); + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_CONNECTOR_TYPE: + case SP_ATTR_CONNECTION_START: + case SP_ATTR_CONNECTION_END: + path->connEndPair.setAttr(key, value); + break; + default: + if (((SPObjectClass *) parent_class)->set) { + ((SPObjectClass *) parent_class)->set(object, key, value); + } + break; + } +} + +/** + * + * Writes the path object into a Inkscape::XML::Node + */ +static Inkscape::XML::Node * +sp_path_write(SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + SPShape *shape = (SPShape *) object; + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = sp_repr_new("svg:path"); + } + + if ( shape->curve != NULL ) { + NArtBpath *abp = sp_curve_first_bpath(shape->curve); + if (abp) { + gchar *str = sp_svg_write_path(abp); + repr->setAttribute("d", str); + g_free(str); + } else { + repr->setAttribute("d", ""); + } + } else { + repr->setAttribute("d", NULL); + } + + SP_PATH(shape)->connEndPair.writeRepr(repr); + + if (((SPObjectClass *)(parent_class))->write) { + ((SPObjectClass *)(parent_class))->write(object, repr, flags); + } + + return repr; +} + +static void +sp_path_update(SPObject *object, SPCtx *ctx, guint flags) +{ + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + flags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B; // since we change the description, it's not a "just translation" anymore + } + + if (((SPObjectClass *) parent_class)->update) { + ((SPObjectClass *) parent_class)->update(object, ctx, flags); + } + + SPPath *path = SP_PATH(object); + path->connEndPair.update(); +} + + +/** + * Writes the given transform into the repr for the given item. + */ +static NR::Matrix +sp_path_set_transform(SPItem *item, NR::Matrix const &xform) +{ + SPShape *shape = (SPShape *) item; + + if (!shape->curve) { // 0 nodes, nothing to transform + return NR::identity(); + } + + /* Transform the path */ + NRBPath dpath, spath; + spath.path = shape->curve->bpath; + nr_path_duplicate_transform(&dpath, &spath, xform); + SPCurve *curve = sp_curve_new_from_bpath(dpath.path); + if (curve) { + sp_shape_set_curve(shape, curve, TRUE); + sp_curve_unref(curve); + } + + // Adjust stroke + sp_item_adjust_stroke(item, NR::expansion(xform)); + + // Adjust pattern fill + sp_item_adjust_pattern(item, xform); + + // Adjust gradient fill + sp_item_adjust_gradient(item, xform); + + item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + + // nothing remains - we've written all of the transform, so return identity + return NR::identity(); +} + + +/* + 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/sp-path.h b/src/sp-path.h new file mode 100644 index 000000000..7c2af5eec --- /dev/null +++ b/src/sp-path.h @@ -0,0 +1,46 @@ +#ifndef __SP_PATH_H__ +#define __SP_PATH_H__ + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-shape.h" +#include "sp-conn-end-pair.h" + + +#define SP_TYPE_PATH (sp_path_get_type ()) +#define SP_PATH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_PATH, SPPath)) +#define SP_IS_PATH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_PATH)) + +struct SPPath : public SPShape { + SPConnEndPair connEndPair; +}; + +struct SPPathClass { + SPShapeClass shape_class; +}; + +GType sp_path_get_type (void); + + +#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 : diff --git a/src/sp-pattern.cpp b/src/sp-pattern.cpp new file mode 100644 index 000000000..beea89b02 --- /dev/null +++ b/src/sp-pattern.cpp @@ -0,0 +1,979 @@ +#define __SP_PATTERN_C__ + +/* + * SVG implementation + * + * Author: + * Lauris Kaplinski + * bulia byak + * + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "config.h" + +#include +#include "libnr/nr-matrix-fns.h" +#include +#include "macros.h" +#include "svg/svg.h" +#include "display/nr-arena.h" +#include "display/nr-arena-group.h" +#include "attributes.h" +#include "document-private.h" +#include "uri.h" +#include "sp-pattern.h" +#include "xml/repr.h" + +/* + * Pattern + */ + +class SPPatPainter; + +struct SPPatPainter { + SPPainter painter; + SPPattern *pat; + + NRMatrix ps2px; + NRMatrix px2ps; + NRMatrix pcs2px; + + NRArena *arena; + unsigned int dkey; + NRArenaItem *root; + + bool use_cached_tile; + NRMatrix ca2pa; + NRMatrix pa2ca; + NRRectL cached_bbox; + NRPixBlock cached_tile; +}; + +static void sp_pattern_class_init (SPPatternClass *klass); +static void sp_pattern_init (SPPattern *gr); + +static void sp_pattern_build (SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); +static void sp_pattern_release (SPObject *object); +static void sp_pattern_set (SPObject *object, unsigned int key, const gchar *value); +static void sp_pattern_child_added (SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref); +static void sp_pattern_update (SPObject *object, SPCtx *ctx, unsigned int flags); +static void sp_pattern_modified (SPObject *object, unsigned int flags); + +static void pattern_ref_changed(SPObject *old_ref, SPObject *ref, SPPattern *pat); +static void pattern_ref_modified (SPObject *ref, guint flags, SPPattern *pattern); + +static SPPainter *sp_pattern_painter_new (SPPaintServer *ps, NR::Matrix const &full_transform, NR::Matrix const &parent_transform, const NRRect *bbox); +static void sp_pattern_painter_free (SPPaintServer *ps, SPPainter *painter); + +static SPPaintServerClass * pattern_parent_class; + +GType +sp_pattern_get_type (void) +{ + static GType pattern_type = 0; + if (!pattern_type) { + GTypeInfo pattern_info = { + sizeof (SPPatternClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_pattern_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (SPPattern), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_pattern_init, + NULL, /* value_table */ + }; + pattern_type = g_type_register_static (SP_TYPE_PAINT_SERVER, "SPPattern", &pattern_info, (GTypeFlags)0); + } + return pattern_type; +} + +static void +sp_pattern_class_init (SPPatternClass *klass) +{ + SPObjectClass *sp_object_class; + SPPaintServerClass *ps_class; + + sp_object_class = (SPObjectClass *) klass; + ps_class = (SPPaintServerClass *) klass; + + pattern_parent_class = (SPPaintServerClass*)g_type_class_ref (SP_TYPE_PAINT_SERVER); + + sp_object_class->build = sp_pattern_build; + sp_object_class->release = sp_pattern_release; + sp_object_class->set = sp_pattern_set; + sp_object_class->child_added = sp_pattern_child_added; + sp_object_class->update = sp_pattern_update; + sp_object_class->modified = sp_pattern_modified; + + // do we need _write? seems to work without it + + ps_class->painter_new = sp_pattern_painter_new; + ps_class->painter_free = sp_pattern_painter_free; +} + +static void +sp_pattern_init (SPPattern *pat) +{ + pat->ref = new SPPatternReference(SP_OBJECT(pat)); + pat->ref->changedSignal().connect(sigc::bind(sigc::ptr_fun(pattern_ref_changed), pat)); + + pat->patternUnits = SP_PATTERN_UNITS_OBJECTBOUNDINGBOX; + pat->patternUnits_set = FALSE; + + pat->patternContentUnits = SP_PATTERN_UNITS_USERSPACEONUSE; + pat->patternContentUnits_set = FALSE; + + pat->patternTransform = NR::identity(); + pat->patternTransform_set = FALSE; + + pat->x.unset(); + pat->y.unset(); + pat->width.unset(); + pat->height.unset(); + + pat->viewBox_set = FALSE; +} + +static void +sp_pattern_build (SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + if (((SPObjectClass *) pattern_parent_class)->build) + (* ((SPObjectClass *) pattern_parent_class)->build) (object, document, repr); + + sp_object_read_attr (object, "patternUnits"); + sp_object_read_attr (object, "patternContentUnits"); + sp_object_read_attr (object, "patternTransform"); + sp_object_read_attr (object, "x"); + sp_object_read_attr (object, "y"); + sp_object_read_attr (object, "width"); + sp_object_read_attr (object, "height"); + sp_object_read_attr (object, "viewBox"); + sp_object_read_attr (object, "xlink:href"); + + /* Register ourselves */ + sp_document_add_resource (document, "pattern", object); +} + +static void +sp_pattern_release (SPObject *object) +{ + SPPattern *pat; + + pat = (SPPattern *) object; + + if (SP_OBJECT_DOCUMENT (object)) { + /* Unregister ourselves */ + sp_document_remove_resource (SP_OBJECT_DOCUMENT (object), "pattern", SP_OBJECT (object)); + } + + if (pat->ref) { + if (pat->ref->getObject()) + sp_signal_disconnect_by_data(pat->ref->getObject(), pat); + pat->ref->detach(); + delete pat->ref; + pat->ref = NULL; + } + + if (((SPObjectClass *) pattern_parent_class)->release) + ((SPObjectClass *) pattern_parent_class)->release (object); +} + +static void +sp_pattern_set (SPObject *object, unsigned int key, const gchar *value) +{ + SPPattern *pat = SP_PATTERN (object); + + switch (key) { + case SP_ATTR_PATTERNUNITS: + if (value) { + if (!strcmp (value, "userSpaceOnUse")) { + pat->patternUnits = SP_PATTERN_UNITS_USERSPACEONUSE; + } else { + pat->patternUnits = SP_PATTERN_UNITS_OBJECTBOUNDINGBOX; + } + pat->patternUnits_set = TRUE; + } else { + pat->patternUnits_set = FALSE; + } + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_PATTERNCONTENTUNITS: + if (value) { + if (!strcmp (value, "userSpaceOnUse")) { + pat->patternContentUnits = SP_PATTERN_UNITS_USERSPACEONUSE; + } else { + pat->patternContentUnits = SP_PATTERN_UNITS_OBJECTBOUNDINGBOX; + } + pat->patternContentUnits_set = TRUE; + } else { + pat->patternContentUnits_set = FALSE; + } + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_PATTERNTRANSFORM: { + NR::Matrix t; + if (value && sp_svg_transform_read (value, &t)) { + pat->patternTransform = t; + pat->patternTransform_set = TRUE; + } else { + pat->patternTransform = NR::identity(); + pat->patternTransform_set = FALSE; + } + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + case SP_ATTR_X: + pat->x.readOrUnset(value); + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_Y: + pat->y.readOrUnset(value); + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_WIDTH: + pat->width.readOrUnset(value); + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_HEIGHT: + pat->height.readOrUnset(value); + object->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_VIEWBOX: { + /* fixme: Think (Lauris) */ + double x, y, width, height; + char *eptr; + + if (value) { + eptr = (gchar *) value; + x = g_ascii_strtod (eptr, &eptr); + while (*eptr && ((*eptr == ',') || (*eptr == ' '))) eptr++; + y = g_ascii_strtod (eptr, &eptr); + while (*eptr && ((*eptr == ',') || (*eptr == ' '))) eptr++; + width = g_ascii_strtod (eptr, &eptr); + while (*eptr && ((*eptr == ',') || (*eptr == ' '))) eptr++; + height = g_ascii_strtod (eptr, &eptr); + while (*eptr && ((*eptr == ',') || (*eptr == ' '))) eptr++; + if ((width > 0) && (height > 0)) { + pat->viewBox.x0 = x; + pat->viewBox.y0 = y; + pat->viewBox.x1 = x + width; + pat->viewBox.y1 = y + height; + pat->viewBox_set = TRUE; + } else { + pat->viewBox_set = FALSE; + } + } else { + pat->viewBox_set = FALSE; + } + object->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); + break; + } + case SP_ATTR_XLINK_HREF: + if ( value && pat->href && ( strcmp(value, pat->href) == 0 ) ) { + /* Href unchanged, do nothing. */ + } else { + g_free(pat->href); + pat->href = NULL; + if (value) { + // First, set the href field; it's only used in the "unchanged" check above. + pat->href = g_strdup(value); + // Now do the attaching, which emits the changed signal. + if (value) { + try { + pat->ref->attach(Inkscape::URI(value)); + } catch (Inkscape::BadURIException &e) { + g_warning("%s", e.what()); + pat->ref->detach(); + } + } else { + pat->ref->detach(); + } + } + } + break; + default: + if (((SPObjectClass *) pattern_parent_class)->set) + ((SPObjectClass *) pattern_parent_class)->set (object, key, value); + break; + } +} + +static void +sp_pattern_child_added (SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref) +{ + SPPattern *pat = SP_PATTERN (object); + + if (((SPObjectClass *) (pattern_parent_class))->child_added) + (* ((SPObjectClass *) (pattern_parent_class))->child_added) (object, child, ref); + + SPObject *ochild = sp_object_get_child_by_repr(object, child); + if (SP_IS_ITEM (ochild)) { + + SPPaintServer *ps = SP_PAINT_SERVER (pat); + unsigned position = sp_item_pos_in_parent(SP_ITEM(ochild)); + + for (SPPainter *p = ps->painters; p != NULL; p = p->next) { + + SPPatPainter *pp = (SPPatPainter *) p; + NRArenaItem *ai = sp_item_invoke_show (SP_ITEM (ochild), pp->arena, pp->dkey, SP_ITEM_REFERENCE_FLAGS); + + if (ai) { + nr_arena_item_add_child (pp->root, ai, NULL); + nr_arena_item_set_order (ai, position); + nr_arena_item_unref (ai); + } + } + } +} + +/* TODO: do we need a ::remove_child handler? */ + +/* fixme: We need ::order_changed handler too (Lauris) */ + +GSList * +pattern_getchildren (SPPattern *pat) +{ + GSList *l = NULL; + + for (SPPattern *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) { + if (sp_object_first_child(SP_OBJECT(pat_i))) { // find the first one with children + for (SPObject *child = sp_object_first_child(SP_OBJECT (pat)) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) { + l = g_slist_prepend (l, child); + } + break; // do not go further up the chain if children are found + } + } + + return l; +} + +static void +sp_pattern_update (SPObject *object, SPCtx *ctx, unsigned int flags) +{ + SPPattern *pat = SP_PATTERN (object); + + if (flags & SP_OBJECT_MODIFIED_FLAG) flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + flags &= SP_OBJECT_MODIFIED_CASCADE; + + GSList *l = pattern_getchildren (pat); + l = g_slist_reverse (l); + + while (l) { + SPObject *child = SP_OBJECT (l->data); + sp_object_ref (child, NULL); + l = g_slist_remove (l, child); + if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->updateDisplay(ctx, flags); + } + sp_object_unref (child, NULL); + } +} + +static void +sp_pattern_modified (SPObject *object, guint flags) +{ + SPPattern *pat = SP_PATTERN (object); + + if (flags & SP_OBJECT_MODIFIED_FLAG) flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + flags &= SP_OBJECT_MODIFIED_CASCADE; + + GSList *l = pattern_getchildren (pat); + l = g_slist_reverse (l); + + while (l) { + SPObject *child = SP_OBJECT (l->data); + sp_object_ref (child, NULL); + l = g_slist_remove (l, child); + if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->emitModified(flags); + } + sp_object_unref (child, NULL); + } +} + +/** +Gets called when the pattern is reattached to another +*/ +static void +pattern_ref_changed(SPObject *old_ref, SPObject *ref, SPPattern *pat) +{ + if (old_ref) { + sp_signal_disconnect_by_data(old_ref, pat); + } + if (SP_IS_PATTERN (ref)) { + g_signal_connect(G_OBJECT (ref), "modified", G_CALLBACK (pattern_ref_modified), pat); + } + + pattern_ref_modified (ref, 0, pat); +} + +/** +Gets called when the referenced is changed +*/ +static void +pattern_ref_modified (SPObject *ref, guint flags, SPPattern *pattern) +{ + if (SP_IS_OBJECT (pattern)) + SP_OBJECT (pattern)->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +guint +pattern_users (SPPattern *pattern) +{ + return SP_OBJECT (pattern)->hrefcount; +} + +SPPattern * +pattern_chain (SPPattern *pattern) +{ + SPDocument *document = SP_OBJECT_DOCUMENT (pattern); + Inkscape::XML::Node *defsrepr = SP_OBJECT_REPR (SP_DOCUMENT_DEFS (document)); + + Inkscape::XML::Node *repr = sp_repr_new ("svg:pattern"); + repr->setAttribute("inkscape:collect", "always"); + gchar *parent_ref = g_strconcat ("#", SP_OBJECT_REPR(pattern)->attribute("id"), NULL); + repr->setAttribute("xlink:href", parent_ref); + g_free (parent_ref); + + defsrepr->addChild(repr, NULL); + const gchar *child_id = repr->attribute("id"); + SPObject *child = document->getObjectById(child_id); + g_assert (SP_IS_PATTERN (child)); + + return SP_PATTERN (child); +} + +SPPattern * +sp_pattern_clone_if_necessary (SPItem *item, SPPattern *pattern, const gchar *property) +{ + if (pattern_users(pattern) > 1) { + pattern = pattern_chain (pattern); + gchar *href = g_strconcat ("url(#", SP_OBJECT_REPR (pattern)->attribute("id"), ")", NULL); + + SPCSSAttr *css = sp_repr_css_attr_new (); + sp_repr_css_set_property (css, property, href); + sp_repr_css_change_recursive (SP_OBJECT_REPR (item), css, "style"); + } + return pattern; +} + +void +sp_pattern_transform_multiply (SPPattern *pattern, NR::Matrix postmul, bool set) +{ + // this formula is for a different interpretation of pattern transforms as described in (*) in sp-pattern.cpp + // for it to work, we also need sp_object_read_attr (SP_OBJECT (item), "transform"); + //pattern->patternTransform = premul * item->transform * pattern->patternTransform * item->transform.inverse() * postmul; + + // otherwise the formula is much simpler + if (set) { + pattern->patternTransform = postmul; + } else { + pattern->patternTransform = pattern_patternTransform(pattern) * postmul; + } + pattern->patternTransform_set = TRUE; + + gchar c[256]; + if (sp_svg_transform_write(c, 256, pattern->patternTransform)) { + SP_OBJECT_REPR(pattern)->setAttribute("patternTransform", c); + } else { + SP_OBJECT_REPR(pattern)->setAttribute("patternTransform", NULL); + } +} + +const gchar * +pattern_tile (GSList *reprs, NR::Rect bounds, SPDocument *document, NR::Matrix transform, NR::Matrix move) +{ + Inkscape::XML::Node *defsrepr = SP_OBJECT_REPR (SP_DOCUMENT_DEFS (document)); + + Inkscape::XML::Node *repr = sp_repr_new ("svg:pattern"); + repr->setAttribute("patternUnits", "userSpaceOnUse"); + sp_repr_set_svg_double(repr, "width", bounds.extent(NR::X)); + sp_repr_set_svg_double(repr, "height", bounds.extent(NR::Y)); + + gchar t[256]; + if (sp_svg_transform_write(t, 256, transform)) { + repr->setAttribute("patternTransform", t); + } else { + repr->setAttribute("patternTransform", NULL); + } + + + defsrepr->appendChild(repr); + const gchar *pat_id = repr->attribute("id"); + SPObject *pat_object = document->getObjectById(pat_id); + + for (GSList *i = reprs; i != NULL; i = i->next) { + Inkscape::XML::Node *node = (Inkscape::XML::Node *)(i->data); + SPItem *copy = SP_ITEM(pat_object->appendChildRepr(node)); + + NR::Matrix dup_transform; + if (!sp_svg_transform_read (node->attribute("transform"), &dup_transform)) + dup_transform = NR::identity(); + dup_transform *= move; + + sp_item_write_transform(copy, SP_OBJECT_REPR(copy), dup_transform); + } + + Inkscape::GC::release(repr); + return pat_id; +} + +SPPattern * +pattern_getroot (SPPattern *pat) +{ + for (SPPattern *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) { + if (sp_object_first_child(SP_OBJECT(pat_i))) { // find the first one with children + return pat_i; + } + } + return pat; // document is broken, we can't get to root; but at least we can return pat which is supposedly a valid pattern +} + + + +// Access functions that look up fields up the chain of referenced patterns and return the first one which is set + +guint pattern_patternUnits (SPPattern *pat) +{ + for (SPPattern *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) { + if (pat_i->patternUnits_set) + return pat_i->patternUnits; + } + return pat->patternUnits; +} + +guint pattern_patternContentUnits (SPPattern *pat) +{ + for (SPPattern *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) { + if (pat_i->patternContentUnits_set) + return pat_i->patternContentUnits; + } + return pat->patternContentUnits; +} + +NR::Matrix const &pattern_patternTransform(SPPattern const *pat) +{ + for (SPPattern const *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) { + if (pat_i->patternTransform_set) + return pat_i->patternTransform; + } + return pat->patternTransform; +} + +gdouble pattern_x (SPPattern *pat) +{ + for (SPPattern *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) { + if (pat_i->x._set) + return pat_i->x.computed; + } + return 0; +} + +gdouble pattern_y (SPPattern *pat) +{ + for (SPPattern *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) { + if (pat_i->y._set) + return pat_i->y.computed; + } + return 0; +} + +gdouble pattern_width (SPPattern *pat) +{ + for (SPPattern *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) { + if (pat_i->width._set) + return pat_i->width.computed; + } + return 0; +} + +gdouble pattern_height (SPPattern *pat) +{ + for (SPPattern *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) { + if (pat_i->height._set) + return pat_i->height.computed; + } + return 0; +} + +NRRect *pattern_viewBox (SPPattern *pat) +{ + for (SPPattern *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) { + if (pat_i->viewBox_set) + return &(pat_i->viewBox); + } + return &(pat->viewBox); +} + +bool pattern_hasItemChildren (SPPattern *pat) +{ + for (SPObject *child = sp_object_first_child(SP_OBJECT(pat)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) { + if (SP_IS_ITEM (child)) { + return true; + } + } + return false; +} + + + +/* Painter */ + +static void sp_pat_fill (SPPainter *painter, NRPixBlock *pb); + +/** +Creates a painter (i.e. the thing that does actual filling at the given zoom). +See (*) below for why the parent_transform may be necessary. +*/ +static SPPainter * +sp_pattern_painter_new (SPPaintServer *ps, NR::Matrix const &full_transform, NR::Matrix const &parent_transform, const NRRect *bbox) +{ + SPPattern *pat = SP_PATTERN (ps); + SPPatPainter *pp = g_new (SPPatPainter, 1); + + pp->painter.type = SP_PAINTER_IND; + pp->painter.fill = sp_pat_fill; + + pp->pat = pat; + + if (pattern_patternUnits (pat) == SP_PATTERN_UNITS_OBJECTBOUNDINGBOX) { + /* BBox to user coordinate system */ + NR::Matrix bbox2user (bbox->x1 - bbox->x0, 0.0, 0.0, bbox->y1 - bbox->y0, bbox->x0, bbox->y0); + + // the final patternTransform, taking into account bbox + NR::Matrix const ps2user(pattern_patternTransform(pat) * bbox2user); + + // see (*) comment below + NR::Matrix ps2px = ps2user * full_transform; + + ps2px.copyto (&pp->ps2px); + + } else { + /* Problem: What to do, if we have mixed lengths and percentages? */ + /* Currently we do ignore percentages at all, but that is not good (lauris) */ + + /* fixme: We may try to normalize here too, look at linearGradient (Lauris) */ + + // (*) The spec says, "This additional transformation matrix [patternTransform] is + // post-multiplied to (i.e., inserted to the right of) any previously defined + // transformations, including the implicit transformation necessary to convert from + // object bounding box units to user space." To me, this means that the order should be: + // item_transform * patternTransform * parent_transform + // However both Batik and Adobe plugin use: + // patternTransform * item_transform * parent_transform + // So here I comply with the majority opinion, but leave my interpretation commented out below. + // (To get item_transform, I subtract parent from full.) + + //NR::Matrix ps2px = (full_transform / parent_transform) * pattern_patternTransform(pat) * parent_transform; + NR::Matrix ps2px = pattern_patternTransform(pat) * full_transform; + + ps2px.copyto (&pp->ps2px); + } + + nr_matrix_invert (&pp->px2ps, &pp->ps2px); + + if (pat->viewBox_set) { + gdouble tmp_x = (pattern_viewBox(pat)->x1 - pattern_viewBox(pat)->x0) / pattern_width (pat); + gdouble tmp_y = (pattern_viewBox(pat)->y1 - pattern_viewBox(pat)->y0) / pattern_height (pat); + + // FIXME: preserveAspectRatio must be taken into account here too! + NR::Matrix vb2ps (tmp_x, 0.0, 0.0, tmp_y, pattern_x(pat) - pattern_viewBox(pat)->x0, pattern_y(pat) - pattern_viewBox(pat)->y0); + + NR::Matrix vb2us = vb2ps * pattern_patternTransform(pat); + + // see (*) + NR::Matrix pcs2px = vb2us * full_transform; + + pcs2px.copyto (&pp->pcs2px); + } else { + NR::Matrix pcs2px; + + /* No viewbox, have to parse units */ + if (pattern_patternContentUnits (pat) == SP_PATTERN_UNITS_OBJECTBOUNDINGBOX) { + /* BBox to user coordinate system */ + NR::Matrix bbox2user (bbox->x1 - bbox->x0, 0.0, 0.0, bbox->y1 - bbox->y0, bbox->x0, bbox->y0); + + NR::Matrix pcs2user = pattern_patternTransform(pat) * bbox2user; + + // see (*) + pcs2px = pcs2user * full_transform; + } else { + // see (*) + //pcs2px = (full_transform / parent_transform) * pattern_patternTransform(pat) * parent_transform; + pcs2px = pattern_patternTransform(pat) * full_transform; + } + + pcs2px = NR::translate (pattern_x (pat), pattern_y (pat)) * pcs2px; + + pcs2px.copyto (&pp->pcs2px); + } + + /* Create arena */ + pp->arena = NRArena::create(); + + pp->dkey = sp_item_display_key_new (1); + + /* Create group */ + pp->root = NRArenaGroup::create(pp->arena); + + /* Show items */ + for (SPPattern *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) { + if (pat_i && SP_IS_OBJECT (pat_i) && pattern_hasItemChildren(pat_i)) { // find the first one with item children + for (SPObject *child = sp_object_first_child(SP_OBJECT(pat_i)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) { + if (SP_IS_ITEM (child)) { + NRArenaItem *cai; + cai = sp_item_invoke_show (SP_ITEM (child), pp->arena, pp->dkey, SP_ITEM_REFERENCE_FLAGS); + nr_arena_item_append_child (pp->root, cai); + nr_arena_item_unref (cai); + } + } + break; // do not go further up the chain if children are found + } + } + + { + NRRect one_tile,tr_tile; + one_tile.x0=pattern_x(pp->pat); + one_tile.y0=pattern_y(pp->pat); + one_tile.x1=one_tile.x0+pattern_width (pp->pat); + one_tile.y1=one_tile.y0+pattern_height (pp->pat); + nr_rect_d_matrix_transform (&tr_tile, &one_tile, &pp->ps2px); + int tr_width=(int)ceil(1.3*(tr_tile.x1-tr_tile.x0)); + int tr_height=(int)ceil(1.3*(tr_tile.y1-tr_tile.y0)); +// if ( tr_width < 10000 && tr_height < 10000 && tr_width*tr_height < 1000000 ) { + pp->use_cached_tile=false;//true; + if ( tr_width > 1000 ) tr_width=1000; + if ( tr_height > 1000 ) tr_height=1000; + pp->cached_bbox.x0=0; + pp->cached_bbox.y0=0; + pp->cached_bbox.x1=tr_width; + pp->cached_bbox.y1=tr_height; + + if (pp->use_cached_tile) { + nr_pixblock_setup (&pp->cached_tile,NR_PIXBLOCK_MODE_R8G8B8A8N, pp->cached_bbox.x0, pp->cached_bbox.y0, pp->cached_bbox.x1, pp->cached_bbox.y1,TRUE); + } + + pp->pa2ca.c[0]=((double)tr_width)/(one_tile.x1-one_tile.x0); + pp->pa2ca.c[1]=0; + pp->pa2ca.c[2]=0; + pp->pa2ca.c[3]=((double)tr_height)/(one_tile.y1-one_tile.y0); + pp->pa2ca.c[4]=-one_tile.x0*pp->pa2ca.c[0]; + pp->pa2ca.c[5]=-one_tile.y0*pp->pa2ca.c[1]; + pp->ca2pa.c[0]=(one_tile.x1-one_tile.x0)/((double)tr_width); + pp->ca2pa.c[1]=0; + pp->ca2pa.c[2]=0; + pp->ca2pa.c[3]=(one_tile.y1-one_tile.y0)/((double)tr_height); + pp->ca2pa.c[4]=one_tile.x0; + pp->ca2pa.c[5]=one_tile.y0; +// } else { +// pp->use_cached_tile=false; +// } + } + + NRGC gc(NULL); + if ( pp->use_cached_tile ) { + gc.transform=pp->pa2ca; + } else { + gc.transform = pp->pcs2px; + } + nr_arena_item_invoke_update (pp->root, NULL, &gc, NR_ARENA_ITEM_STATE_ALL, NR_ARENA_ITEM_STATE_ALL); + if ( pp->use_cached_tile ) { + nr_arena_item_invoke_render (pp->root, &pp->cached_bbox, &pp->cached_tile, 0); + } else { + // nothing to do now + } + + return (SPPainter *) pp; +} + +static void +sp_pattern_painter_free (SPPaintServer *ps, SPPainter *painter) +{ + SPPatPainter *pp = (SPPatPainter *) painter; + SPPattern *pat = pp->pat; + + for (SPPattern *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) { + if (pat_i && SP_IS_OBJECT (pat_i) && pattern_hasItemChildren(pat_i)) { // find the first one with item children + for (SPObject *child = sp_object_first_child(SP_OBJECT(pat_i)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) { + if (SP_IS_ITEM (child)) { + sp_item_invoke_hide (SP_ITEM (child), pp->dkey); + } + } + break; // do not go further up the chain if children are found + } + } + if ( pp->use_cached_tile ) nr_pixblock_release(&pp->cached_tile); + g_free (pp); +} + +void +get_cached_tile_pixel(SPPatPainter* pp,double x,double y,unsigned char &r,unsigned char &g,unsigned char &b,unsigned char &a) +{ + int ca_h=(int)floor(x); + int ca_v=(int)floor(y); + int r_x=(int)floor(16*(x-floor(x))); + int r_y=(int)floor(16*(y-floor(y))); + unsigned int tl_m=(16-r_x)*(16-r_y); + unsigned int bl_m=(16-r_x)*r_y; + unsigned int tr_m=r_x*(16-r_y); + unsigned int br_m=r_x*r_y; + int cb_h=ca_h+1; + int cb_v=ca_v+1; + if ( cb_h >= pp->cached_bbox.x1 ) cb_h=0; + if ( cb_v >= pp->cached_bbox.y1 ) cb_v=0; + + unsigned char* tlx=NR_PIXBLOCK_PX(&pp->cached_tile)+(ca_v*pp->cached_tile.rs)+4*ca_h; + unsigned char* trx=NR_PIXBLOCK_PX(&pp->cached_tile)+(ca_v*pp->cached_tile.rs)+4*cb_h; + unsigned char* blx=NR_PIXBLOCK_PX(&pp->cached_tile)+(cb_v*pp->cached_tile.rs)+4*ca_h; + unsigned char* brx=NR_PIXBLOCK_PX(&pp->cached_tile)+(cb_v*pp->cached_tile.rs)+4*cb_h; + + unsigned int tl_c=tlx[0]; + unsigned int tr_c=trx[0]; + unsigned int bl_c=blx[0]; + unsigned int br_c=brx[0]; + unsigned int f_c=(tl_m*tl_c+tr_m*tr_c+bl_m*bl_c+br_m*br_c)>>8; + r=f_c; + tl_c=tlx[1]; + tr_c=trx[1]; + bl_c=blx[1]; + br_c=brx[1]; + f_c=(tl_m*tl_c+tr_m*tr_c+bl_m*bl_c+br_m*br_c)>>8; + g=f_c; + tl_c=tlx[2]; + tr_c=trx[2]; + bl_c=blx[2]; + br_c=brx[2]; + f_c=(tl_m*tl_c+tr_m*tr_c+bl_m*bl_c+br_m*br_c)>>8; + b=f_c; + tl_c=tlx[3]; + tr_c=trx[3]; + bl_c=blx[3]; + br_c=brx[3]; + f_c=(tl_m*tl_c+tr_m*tr_c+bl_m*bl_c+br_m*br_c)>>8; + a=f_c; +} + +static void +sp_pat_fill (SPPainter *painter, NRPixBlock *pb) +{ + SPPatPainter *pp; + NRRect ba, psa; + NRRectL area; + double x, y; + + pp = (SPPatPainter *) painter; + + if (pattern_width (pp->pat) < NR_EPSILON) return; + if (pattern_height (pp->pat) < NR_EPSILON) return; + + /* Find buffer area in gradient space */ + /* fixme: This is suboptimal (Lauris) */ + + if ( pp->use_cached_tile ) { + double pat_w=pattern_width (pp->pat); + double pat_h=pattern_height (pp->pat); + if ( pb->mode == NR_PIXBLOCK_MODE_R8G8B8A8N || pb->mode == NR_PIXBLOCK_MODE_R8G8B8A8P ) { // same thing because it's filling an empty pixblock + unsigned char* lpx=NR_PIXBLOCK_PX(pb); + double px_y=pb->area.y0; + for (int j=pb->area.y0;jarea.y1;j++) { + unsigned char* cpx=lpx; + double px_x = pb->area.x0; + + double ps_x=pp->px2ps.c[0]*px_x+pp->px2ps.c[2]*px_y+pp->px2ps.c[4]; + double ps_y=pp->px2ps.c[1]*px_x+pp->px2ps.c[3]*px_y+pp->px2ps.c[5]; + for (int i=pb->area.x0;iarea.x1;i++) { + while ( ps_x > pat_w ) ps_x-=pat_w; + while ( ps_x < 0 ) ps_x+=pat_w; + while ( ps_y > pat_h ) ps_y-=pat_h; + while ( ps_y < 0 ) ps_y+=pat_h; + double ca_x=pp->pa2ca.c[0]*ps_x+pp->pa2ca.c[2]*ps_y+pp->pa2ca.c[4]; + double ca_y=pp->pa2ca.c[1]*ps_x+pp->pa2ca.c[3]*ps_y+pp->pa2ca.c[5]; + unsigned char n_a,n_r,n_g,n_b; + get_cached_tile_pixel(pp,ca_x,ca_y,n_r,n_g,n_b,n_a); + cpx[0]=n_r; + cpx[1]=n_g; + cpx[2]=n_b; + cpx[3]=n_a; + + px_x+=1.0; + ps_x+=pp->px2ps.c[0]; + ps_y+=pp->px2ps.c[1]; + cpx+=4; + } + px_y+=1.0; + lpx+=pb->rs; + } + } else if ( pb->mode == NR_PIXBLOCK_MODE_R8G8B8 ) { + unsigned char* lpx=NR_PIXBLOCK_PX(pb); + double px_y=pb->area.y0; + for (int j=pb->area.y0;jarea.y1;j++) { + unsigned char* cpx=lpx; + double px_x = pb->area.x0; + + double ps_x=pp->px2ps.c[0]*px_x+pp->px2ps.c[2]*px_y+pp->px2ps.c[4]; + double ps_y=pp->px2ps.c[1]*px_x+pp->px2ps.c[3]*px_y+pp->px2ps.c[5]; + for (int i=pb->area.x0;iarea.x1;i++) { + while ( ps_x > pat_w ) ps_x-=pat_w; + while ( ps_x < 0 ) ps_x+=pat_w; + while ( ps_y > pat_h ) ps_y-=pat_h; + while ( ps_y < 0 ) ps_y+=pat_h; + double ca_x=pp->pa2ca.c[0]*ps_x+pp->pa2ca.c[2]*ps_y+pp->pa2ca.c[4]; + double ca_y=pp->pa2ca.c[1]*ps_x+pp->pa2ca.c[3]*ps_y+pp->pa2ca.c[5]; + unsigned char n_a,n_r,n_g,n_b; + get_cached_tile_pixel(pp,ca_x,ca_y,n_r,n_g,n_b,n_a); + cpx[0]=n_r; + cpx[1]=n_g; + cpx[2]=n_b; + + px_x+=1.0; + ps_x+=pp->px2ps.c[0]; + ps_y+=pp->px2ps.c[1]; + cpx+=4; + } + px_y+=1.0; + lpx+=pb->rs; + } + } + } else { + ba.x0 = pb->area.x0; + ba.y0 = pb->area.y0; + ba.x1 = pb->area.x1; + ba.y1 = pb->area.y1; + nr_rect_d_matrix_transform (&psa, &ba, &pp->px2ps); + + psa.x0 = floor ((psa.x0 - pattern_x (pp->pat)) / pattern_width (pp->pat)) -1; + psa.y0 = floor ((psa.y0 - pattern_y (pp->pat)) / pattern_height (pp->pat)) -1; + psa.x1 = ceil ((psa.x1 - pattern_x (pp->pat)) / pattern_width (pp->pat)) +1; + psa.y1 = ceil ((psa.y1 - pattern_y (pp->pat)) / pattern_height (pp->pat)) +1; + + for (y = psa.y0; y < psa.y1; y++) { + for (x = psa.x0; x < psa.x1; x++) { + NRPixBlock ppb; + double psx, psy; + + psx = x * pattern_width (pp->pat); + psy = y * pattern_height (pp->pat); + + area.x0 = (gint32)(pb->area.x0 - (pp->ps2px.c[0] * psx + pp->ps2px.c[2] * psy)); + area.y0 = (gint32)(pb->area.y0 - (pp->ps2px.c[1] * psx + pp->ps2px.c[3] * psy)); + area.x1 = area.x0 + pb->area.x1 - pb->area.x0; + area.y1 = area.y0 + pb->area.y1 - pb->area.y0; + + // We do not update here anymore + + // Set up buffer + // fixme: (Lauris) + nr_pixblock_setup_extern (&ppb, pb->mode, area.x0, area.y0, area.x1, area.y1, NR_PIXBLOCK_PX (pb), pb->rs, FALSE, FALSE); + + nr_arena_item_invoke_render (pp->root, &area, &ppb, 0); + + nr_pixblock_release (&ppb); + } + } + } +} diff --git a/src/sp-pattern.h b/src/sp-pattern.h new file mode 100644 index 000000000..42cac7a7e --- /dev/null +++ b/src/sp-pattern.h @@ -0,0 +1,98 @@ +#ifndef __SP_PATTERN_H__ +#define __SP_PATTERN_H__ + +/* + * SVG implementation + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +#include "forward.h" + +#define SP_TYPE_PATTERN (sp_pattern_get_type ()) +#define SP_PATTERN(o) (GTK_CHECK_CAST ((o), SP_TYPE_PATTERN, SPPattern)) +#define SP_PATTERN_CLASS(k) (GTK_CHECK_CLASS_CAST ((k), SP_TYPE_PATTERN, SPPatternClass)) +#define SP_IS_PATTERN(o) (GTK_CHECK_TYPE ((o), SP_TYPE_PATTERN)) +#define SP_IS_PATTERN_CLASS(k) (GTK_CHECK_CLASS_TYPE ((k), SP_TYPE_PATTERN)) + +GType sp_pattern_get_type (void); + +class SPPatternClass; + +#include +#include +#include "svg/svg-length.h" +#include "sp-paint-server.h" +#include "uri-references.h" + +class SPPatternReference : public Inkscape::URIReference { +public: + SPPatternReference (SPObject *obj) : URIReference(obj) {} + SPPattern *getObject() const { + return (SPPattern *)URIReference::getObject(); + } +protected: + virtual bool _acceptObject(SPObject *obj) const { + return SP_IS_PATTERN (obj); + } +}; + +enum { + SP_PATTERN_UNITS_USERSPACEONUSE, + SP_PATTERN_UNITS_OBJECTBOUNDINGBOX +}; + +struct SPPattern : public SPPaintServer { + /* Reference (href) */ + gchar *href; + SPPatternReference *ref; + + /* patternUnits and patternContentUnits attribute */ + guint patternUnits : 1; + guint patternUnits_set : 1; + guint patternContentUnits : 1; + guint patternContentUnits_set : 1; + /* patternTransform attribute */ + NR::Matrix patternTransform; + guint patternTransform_set : 1; + /* Tile rectangle */ + SVGLength x; + SVGLength y; + SVGLength width; + SVGLength height; + /* VieBox */ + NRRect viewBox; + guint viewBox_set : 1; +}; + +struct SPPatternClass { + SPPaintServerClass parent_class; +}; + +guint pattern_users (SPPattern *pattern); +SPPattern *pattern_chain (SPPattern *pattern); +SPPattern *sp_pattern_clone_if_necessary (SPItem *item, SPPattern *pattern, const gchar *property); +void sp_pattern_transform_multiply (SPPattern *pattern, NR::Matrix postmul, bool set); + +const gchar *pattern_tile (GSList *reprs, NR::Rect bounds, SPDocument *document, NR::Matrix transform, NR::Matrix move); + +SPPattern *pattern_getroot (SPPattern *pat); + +guint pattern_patternUnits (SPPattern *pat); +guint pattern_patternContentUnits (SPPattern *pat); +NR::Matrix const &pattern_patternTransform(SPPattern const *pat); +gdouble pattern_x (SPPattern *pat); +gdouble pattern_y (SPPattern *pat); +gdouble pattern_width (SPPattern *pat); +gdouble pattern_height (SPPattern *pat); +NRRect *pattern_viewBox (SPPattern *pat); + + +#endif diff --git a/src/sp-polygon.cpp b/src/sp-polygon.cpp new file mode 100644 index 000000000..eb5efc176 --- /dev/null +++ b/src/sp-polygon.cpp @@ -0,0 +1,225 @@ +#define __SP_POLYGON_C__ + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "config.h" + +#include "attributes.h" +#include "sp-polygon.h" +#include "display/curve.h" +#include +#include "libnr/n-art-bpath.h" +#include "svg/stringstream.h" +#include "xml/repr.h" + +static void sp_polygon_class_init(SPPolygonClass *pc); +static void sp_polygon_init(SPPolygon *polygon); + +static void sp_polygon_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); +static Inkscape::XML::Node *sp_polygon_write(SPObject *object, Inkscape::XML::Node *repr, guint flags); +static void sp_polygon_set(SPObject *object, unsigned int key, const gchar *value); + +static gchar *sp_polygon_description(SPItem *item); + +static SPShapeClass *parent_class; + +GType sp_polygon_get_type(void) +{ + static GType polygon_type = 0; + + if (!polygon_type) { + GTypeInfo polygon_info = { + sizeof(SPPolygonClass), + NULL, NULL, + (GClassInitFunc) sp_polygon_class_init, + NULL, NULL, + sizeof(SPPolygon), + 16, + (GInstanceInitFunc) sp_polygon_init, + NULL, /* value_table */ + }; + polygon_type = g_type_register_static(SP_TYPE_SHAPE, "SPPolygon", &polygon_info, (GTypeFlags) 0); + } + + return polygon_type; +} + +static void sp_polygon_class_init(SPPolygonClass *pc) +{ + SPObjectClass *sp_object_class = (SPObjectClass *) pc; + SPItemClass *item_class = (SPItemClass *) pc; + + parent_class = (SPShapeClass *) g_type_class_ref(SP_TYPE_SHAPE); + + sp_object_class->build = sp_polygon_build; + sp_object_class->write = sp_polygon_write; + sp_object_class->set = sp_polygon_set; + + item_class->description = sp_polygon_description; +} + +static void sp_polygon_init(SPPolygon *polygon) +{ + /* Nothing here */ +} + +static void sp_polygon_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + if (((SPObjectClass *) parent_class)->build) { + ((SPObjectClass *) parent_class)->build(object, document, repr); + } + + sp_object_read_attr(object, "points"); +} + + +/* + * sp_svg_write_polygon: Write points attribute for polygon tag. + * @bpath: + * + * Return value: points attribute string. + */ +static gchar *sp_svg_write_polygon(const NArtBpath *bpath) +{ + g_return_val_if_fail(bpath != NULL, NULL); + + Inkscape::SVGOStringStream os; + + for (int i = 0; bpath[i].code != NR_END; i++) { + switch (bpath [i].code) { + case NR_LINETO: + case NR_MOVETO: + case NR_MOVETO_OPEN: + os << bpath [i].x3 << "," << bpath [i].y3 << " "; + break; + + case NR_CURVETO: + default: + g_assert_not_reached(); + } + } + + return g_strdup(os.str().c_str()); +} + +static Inkscape::XML::Node *sp_polygon_write(SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + SPShape *shape = SP_SHAPE(object); + // Tolerable workaround: we need to update the object's curve before we set points= + // because it's out of sync when e.g. some extension attrs of the polygon or star are changed in XML editor + sp_shape_set_shape(shape); + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = sp_repr_new("svg:polygon"); + } + + /* We can safely write points here, because all subclasses require it too (Lauris) */ + NArtBpath *abp = sp_curve_first_bpath(shape->curve); + gchar *str = sp_svg_write_polygon(abp); + repr->setAttribute("points", str); + g_free(str); + + if (((SPObjectClass *) (parent_class))->write) { + ((SPObjectClass *) (parent_class))->write(object, repr, flags); + } + + return repr; +} + + +static gboolean polygon_get_value(gchar const **p, gdouble *v) +{ + while (**p != '\0' && (**p == ',' || **p == '\x20' || **p == '\x9' || **p == '\xD' || **p == '\xA')) { + (*p)++; + } + + if (*p == '\0') { + return false; + } + + gchar *e = NULL; + *v = g_ascii_strtod(*p, &e); + if (e == *p) { + return false; + } + + *p = e; + return true; +} + + +static void sp_polygon_set(SPObject *object, unsigned int key, const gchar *value) +{ + SPPolygon *polygon = SP_POLYGON(object); + + switch (key) { + case SP_ATTR_POINTS: { + if (!value) { + break; + } + SPCurve *curve = sp_curve_new(); + gboolean hascpt = FALSE; + + const gchar *cptr = value; + + while (TRUE) { + gdouble x; + if (!polygon_get_value(&cptr, &x)) { + break; + } + + gdouble y; + if (!polygon_get_value(&cptr, &y)) { + break; + } + + if (hascpt) { + sp_curve_lineto(curve, x, y); + } else { + sp_curve_moveto(curve, x, y); + hascpt = TRUE; + } + } + + /* TODO: if *cptr != '\0' or if the break came after parsing an x without a y then + * there's an error, which should be handled according to + * http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing. */ + + sp_curve_closepath(curve); + sp_shape_set_curve(SP_SHAPE(polygon), curve, TRUE); + sp_curve_unref(curve); + break; + } + default: + if (((SPObjectClass *) parent_class)->set) { + ((SPObjectClass *) parent_class)->set(object, key, value); + } + break; + } +} + +static gchar *sp_polygon_description(SPItem *item) +{ + return g_strdup(_("Polygon")); +} + +/* + 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/sp-polygon.h b/src/sp-polygon.h new file mode 100644 index 000000000..0da720fc2 --- /dev/null +++ b/src/sp-polygon.h @@ -0,0 +1,33 @@ +#ifndef __SP_POLYGON_H__ +#define __SP_POLYGON_H__ + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-shape.h" + +#define SP_TYPE_POLYGON (sp_polygon_get_type ()) +#define SP_POLYGON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_POLYGON, SPPolygon)) +#define SP_POLYGON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_POLYGON, SPPolygonClass)) +#define SP_IS_POLYGON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_POLYGON)) +#define SP_IS_POLYGON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_POLYGON)) + +struct SPPolygon : public SPShape { +}; + +struct SPPolygonClass { + SPShapeClass parent_class; +}; + +GType sp_polygon_get_type (void); + +#endif diff --git a/src/sp-polyline.cpp b/src/sp-polyline.cpp new file mode 100644 index 000000000..54a38ccd1 --- /dev/null +++ b/src/sp-polyline.cpp @@ -0,0 +1,177 @@ +#define __SP_POLYLINE_C__ + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "config.h" + +#include "attributes.h" +#include "sp-polyline.h" +#include "display/curve.h" +#include +#include "xml/repr.h" + +static void sp_polyline_class_init (SPPolyLineClass *klass); +static void sp_polyline_init (SPPolyLine *polyline); + +static void sp_polyline_build (SPObject * object, SPDocument * document, Inkscape::XML::Node * repr); +static void sp_polyline_set (SPObject *object, unsigned int key, const gchar *value); +static Inkscape::XML::Node *sp_polyline_write (SPObject *object, Inkscape::XML::Node *repr, guint flags); + +static gchar * sp_polyline_description (SPItem * item); + +static SPShapeClass *parent_class; + +GType +sp_polyline_get_type (void) +{ + static GType polyline_type = 0; + + if (!polyline_type) { + GTypeInfo polyline_info = { + sizeof (SPPolyLineClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_polyline_class_init, + NULL, /* klass_finalize */ + NULL, /* klass_data */ + sizeof (SPPolyLine), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_polyline_init, + NULL, /* value_table */ + }; + polyline_type = g_type_register_static (SP_TYPE_SHAPE, "SPPolyLine", &polyline_info, (GTypeFlags)0); + } + return polyline_type; +} + +static void +sp_polyline_class_init (SPPolyLineClass *klass) +{ + GObjectClass * gobject_class; + SPObjectClass * sp_object_class; + SPItemClass * item_class; + + gobject_class = (GObjectClass *) klass; + sp_object_class = (SPObjectClass *) klass; + item_class = (SPItemClass *) klass; + + parent_class = (SPShapeClass *)g_type_class_ref (SP_TYPE_SHAPE); + + sp_object_class->build = sp_polyline_build; + sp_object_class->set = sp_polyline_set; + sp_object_class->write = sp_polyline_write; + + item_class->description = sp_polyline_description; +} + +static void +sp_polyline_init (SPPolyLine * polyline) +{ + /* Nothing here */ +} + +static void +sp_polyline_build (SPObject * object, SPDocument * document, Inkscape::XML::Node * repr) +{ + + if (((SPObjectClass *) parent_class)->build) + ((SPObjectClass *) parent_class)->build (object, document, repr); + + sp_object_read_attr (object, "points"); +} + +static void +sp_polyline_set (SPObject *object, unsigned int key, const gchar *value) +{ + SPPolyLine *polyline; + + polyline = SP_POLYLINE (object); + + switch (key) { + case SP_ATTR_POINTS: { + SPCurve * curve; + const gchar * cptr; + char * eptr; + gboolean hascpt; + + if (!value) break; + curve = sp_curve_new (); + hascpt = FALSE; + + cptr = value; + eptr = NULL; + + while (TRUE) { + gdouble x, y; + + while (*cptr != '\0' && (*cptr == ',' || *cptr == '\x20' || *cptr == '\x9' || *cptr == '\xD' || *cptr == '\xA')) { + cptr++; + } + if (!*cptr) break; + + x = g_ascii_strtod (cptr, &eptr); + if (eptr == cptr) break; + cptr = eptr; + + while (*cptr != '\0' && (*cptr == ',' || *cptr == '\x20' || *cptr == '\x9' || *cptr == '\xD' || *cptr == '\xA')) { + cptr++; + } + if (!*cptr) break; + + y = g_ascii_strtod (cptr, &eptr); + if (eptr == cptr) break; + cptr = eptr; + if (hascpt) { + sp_curve_lineto (curve, x, y); + } else { + sp_curve_moveto (curve, x, y); + hascpt = TRUE; + } + } + + sp_shape_set_curve (SP_SHAPE (polyline), curve, TRUE); + sp_curve_unref (curve); + break; + } + default: + if (((SPObjectClass *) parent_class)->set) + ((SPObjectClass *) parent_class)->set (object, key, value); + break; + } +} + +static Inkscape::XML::Node * +sp_polyline_write (SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + SPPolyLine *polyline; + + polyline = SP_POLYLINE (object); + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = sp_repr_new ("svg:polyline"); + } + + if (repr != SP_OBJECT_REPR (object)) { + repr->mergeFrom(SP_OBJECT_REPR (object), "id"); + } + + if (((SPObjectClass *) (parent_class))->write) + ((SPObjectClass *) (parent_class))->write (object, repr, flags); + + return repr; +} + +static gchar * +sp_polyline_description(SPItem *item) +{ + return g_strdup(_("Polyline")); +} diff --git a/src/sp-polyline.h b/src/sp-polyline.h new file mode 100644 index 000000000..3ee18d6a5 --- /dev/null +++ b/src/sp-polyline.h @@ -0,0 +1,28 @@ +#ifndef SP_POLYLINE_H +#define SP_POLYLINE_H + +#include "sp-shape.h" + + + +#define SP_TYPE_POLYLINE (sp_polyline_get_type ()) +#define SP_POLYLINE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_POLYLINE, SPPolyLine)) +#define SP_POLYLINE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_POLYLINE, SPPolyLineClass)) +#define SP_IS_POLYLINE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_POLYLINE)) +#define SP_IS_POLYLINE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_POLYLINE)) + +class SPPolyLine; +class SPPolyLineClass; + +struct SPPolyLine : public SPShape { +}; + +struct SPPolyLineClass { + SPShapeClass parent_class; +}; + +GType sp_polyline_get_type (void); + + + +#endif diff --git a/src/sp-radial-gradient-fns.h b/src/sp-radial-gradient-fns.h new file mode 100644 index 000000000..6d38e1605 --- /dev/null +++ b/src/sp-radial-gradient-fns.h @@ -0,0 +1,40 @@ +#ifndef SP_RADIAL_GRADIENT_FNS_H +#define SP_RADIAL_GRADIENT_FNS_H + +/** \file + * Macros and fn definitions related to radial gradients. + */ + +#include + +namespace Inkscape { +namespace XML { +class Node; +} +} + +class SPRadialGradient; + +#define SP_TYPE_RADIALGRADIENT (sp_radialgradient_get_type()) +#define SP_RADIALGRADIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SP_TYPE_RADIALGRADIENT, SPRadialGradient)) +#define SP_RADIALGRADIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SP_TYPE_RADIALGRADIENT, SPRadialGradientClass)) +#define SP_IS_RADIALGRADIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SP_TYPE_RADIALGRADIENT)) +#define SP_IS_RADIALGRADIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SP_TYPE_RADIALGRADIENT)) + + +GType sp_radialgradient_get_type(); + +void sp_radialgradient_set_position(SPRadialGradient *rg, gdouble cx, gdouble cy, gdouble fx, gdouble fy, gdouble r); + +#endif /* !SP_RADIAL_GRADIENT_FNS_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:encoding=utf-8:textwidth=99 : diff --git a/src/sp-radial-gradient.h b/src/sp-radial-gradient.h new file mode 100644 index 000000000..bec6cbe00 --- /dev/null +++ b/src/sp-radial-gradient.h @@ -0,0 +1,39 @@ +#ifndef SP_RADIAL_GRADIENT_H +#define SP_RADIAL_GRADIENT_H + +/** \file + * SPRadialGradient: SVG implementtion. + */ + +#include +#include "sp-gradient.h" +#include "svg/svg-length.h" +#include "sp-radial-gradient-fns.h" + +/** Radial gradient. */ +struct SPRadialGradient : public SPGradient { + SVGLength cx; + SVGLength cy; + SVGLength r; + SVGLength fx; + SVGLength fy; +}; + +/// The SPRadialGradient vtable. +struct SPRadialGradientClass { + SPGradientClass parent_class; +}; + + +#endif /* !SP_RADIAL_GRADIENT_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:encoding=utf-8:textwidth=99 : diff --git a/src/sp-rect.cpp b/src/sp-rect.cpp new file mode 100644 index 000000000..edcea21a2 --- /dev/null +++ b/src/sp-rect.cpp @@ -0,0 +1,561 @@ +#define __SP_RECT_C__ + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * bulia byak + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + + +#include +#include +#include + +#include "attributes.h" +#include "style.h" +#include "sp-rect.h" +#include +#include "xml/repr.h" + +#define noRECT_VERBOSE + +static void sp_rect_class_init(SPRectClass *klass); +static void sp_rect_init(SPRect *rect); + +static void sp_rect_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); +static void sp_rect_set(SPObject *object, unsigned key, gchar const *value); +static void sp_rect_update(SPObject *object, SPCtx *ctx, guint flags); +static Inkscape::XML::Node *sp_rect_write(SPObject *object, Inkscape::XML::Node *repr, guint flags); + +static gchar *sp_rect_description(SPItem *item); +static NR::Matrix sp_rect_set_transform(SPItem *item, NR::Matrix const &xform); + +static void sp_rect_set_shape(SPShape *shape); + +static SPShapeClass *parent_class; + +GType +sp_rect_get_type(void) +{ + static GType type = 0; + + if (!type) { + GTypeInfo info = { + sizeof(SPRectClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_rect_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof(SPRect), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_rect_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_SHAPE, "SPRect", &info, (GTypeFlags)0); + } + return type; +} + +static void +sp_rect_class_init(SPRectClass *klass) +{ + SPObjectClass *sp_object_class = (SPObjectClass *) klass; + SPItemClass *item_class = (SPItemClass *) klass; + SPShapeClass *shape_class = (SPShapeClass *) klass; + + parent_class = (SPShapeClass *)g_type_class_ref(SP_TYPE_SHAPE); + + sp_object_class->build = sp_rect_build; + sp_object_class->write = sp_rect_write; + sp_object_class->set = sp_rect_set; + sp_object_class->update = sp_rect_update; + + item_class->description = sp_rect_description; + item_class->set_transform = sp_rect_set_transform; + + shape_class->set_shape = sp_rect_set_shape; +} + +static void +sp_rect_init(SPRect *rect) +{ + /* Initializing to zero is automatic */ + /* sp_svg_length_unset(&rect->x, SP_SVG_UNIT_NONE, 0.0, 0.0); */ + /* sp_svg_length_unset(&rect->y, SP_SVG_UNIT_NONE, 0.0, 0.0); */ + /* sp_svg_length_unset(&rect->width, SP_SVG_UNIT_NONE, 0.0, 0.0); */ + /* sp_svg_length_unset(&rect->height, SP_SVG_UNIT_NONE, 0.0, 0.0); */ + /* sp_svg_length_unset(&rect->rx, SP_SVG_UNIT_NONE, 0.0, 0.0); */ + /* sp_svg_length_unset(&rect->ry, SP_SVG_UNIT_NONE, 0.0, 0.0); */ +} + +static void +sp_rect_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + SPRect *rect = SP_RECT(object); + + if (((SPObjectClass *) parent_class)->build) + ((SPObjectClass *) parent_class)->build(object, document, repr); + + sp_object_read_attr(object, "x"); + sp_object_read_attr(object, "y"); + sp_object_read_attr(object, "width"); + sp_object_read_attr(object, "height"); + sp_object_read_attr(object, "rx"); + sp_object_read_attr(object, "ry"); + + Inkscape::Version const version = sp_object_get_sodipodi_version(object); + + if ( version.major == 0 && version.minor == 29 ) { + if (rect->rx._set && rect->ry._set) { + /* 0.29 treated 0.0 radius as missing value */ + if ((rect->rx.value != 0.0) && (rect->ry.value == 0.0)) { + repr->setAttribute("ry", NULL); + sp_object_read_attr(object, "ry"); + } else if ((rect->ry.value != 0.0) && (rect->rx.value == 0.0)) { + repr->setAttribute("rx", NULL); + sp_object_read_attr(object, "rx"); + } + } + } +} + +static void +sp_rect_set(SPObject *object, unsigned key, gchar const *value) +{ + SPRect *rect = SP_RECT(object); + + /* fixme: We need real error processing some time */ + + switch (key) { + case SP_ATTR_X: + rect->x.readOrUnset(value); + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_Y: + rect->y.readOrUnset(value); + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_WIDTH: + if (!rect->width.read(value) || rect->width.value < 0.0) { + rect->width.unset(); + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_HEIGHT: + if (!rect->height.read(value) || rect->height.value < 0.0) { + rect->height.unset(); + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_RX: + if (!rect->rx.read(value) || rect->rx.value < 0.0) { + rect->rx.unset(); + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_RY: + if (!rect->ry.read(value) || rect->ry.value < 0.0) { + rect->ry.unset(); + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + default: + if (((SPObjectClass *) parent_class)->set) + ((SPObjectClass *) parent_class)->set(object, key, value); + break; + } +} + +static void +sp_rect_update(SPObject *object, SPCtx *ctx, guint flags) +{ + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + SPRect *rect = (SPRect *) object; + SPStyle *style = object->style; + SPItemCtx const *ictx = (SPItemCtx const *) ctx; + double const d = NR::expansion(ictx->i2vp); + double const w = (ictx->vp.x1 - ictx->vp.x0) / d; + double const h = (ictx->vp.y1 - ictx->vp.y0) / d; + double const em = style->font_size.computed; + double const ex = 0.5 * em; // fixme: get x height from pango or libnrtype. + rect->x.update(em, ex, w); + rect->y.update(em, ex, h); + rect->width.update(em, ex, w); + rect->height.update(em, ex, h); + rect->rx.update(em, ex, w); + rect->ry.update(em, ex, h); + sp_shape_set_shape((SPShape *) object); + flags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B; // since we change the description, it's not a "just translation" anymore + } + + if (((SPObjectClass *) parent_class)->update) + ((SPObjectClass *) parent_class)->update(object, ctx, flags); +} + +static Inkscape::XML::Node * +sp_rect_write(SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + SPRect *rect = SP_RECT(object); + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = sp_repr_new("svg:rect"); + } + + sp_repr_set_svg_double(repr, "width", rect->width.computed); + sp_repr_set_svg_double(repr, "height", rect->height.computed); + if (rect->rx._set) sp_repr_set_svg_double(repr, "rx", rect->rx.computed); + if (rect->ry._set) sp_repr_set_svg_double(repr, "ry", rect->ry.computed); + sp_repr_set_svg_double(repr, "x", rect->x.computed); + sp_repr_set_svg_double(repr, "y", rect->y.computed); + + if (((SPObjectClass *) parent_class)->write) + ((SPObjectClass *) parent_class)->write(object, repr, flags); + + return repr; +} + +static gchar * +sp_rect_description(SPItem *item) +{ + g_return_val_if_fail(SP_IS_RECT(item), NULL); + + return g_strdup(_("Rectangle")); +} + +#define C1 0.554 + +static void +sp_rect_set_shape(SPShape *shape) +{ + SPRect *rect = (SPRect *) shape; + + if ((rect->height.computed < 1e-18) || (rect->width.computed < 1e-18)) return; + + SPCurve *c = sp_curve_new(); + + double const x = rect->x.computed; + double const y = rect->y.computed; + double const w = rect->width.computed; + double const h = rect->height.computed; + double const w2 = w / 2; + double const h2 = h / 2; + double const rx = std::min(( rect->rx._set + ? rect->rx.computed + : ( rect->ry._set + ? rect->ry.computed + : 0.0 ) ), + .5 * rect->width.computed); + double const ry = std::min(( rect->ry._set + ? rect->ry.computed + : ( rect->rx._set + ? rect->rx.computed + : 0.0 ) ), + .5 * rect->height.computed); + /* TODO: Handle negative rx or ry as per + * http://www.w3.org/TR/SVG11/shapes.html#RectElementRXAttribute once Inkscape has proper error + * handling (see http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing). + */ + + /* We don't use proper circular/elliptical arcs, but bezier curves can approximate a 90-degree + * arc fairly well. + */ + if ((rx > 1e-18) && (ry > 1e-18)) { + sp_curve_moveto(c, x + rx, y); + if (rx < w2) sp_curve_lineto(c, x + w - rx, y); + sp_curve_curveto(c, x + w - rx * (1 - C1), y, x + w, y + ry * (1 - C1), x + w, y + ry); + if (ry < h2) sp_curve_lineto(c, x + w, y + h - ry); + sp_curve_curveto(c, x + w, y + h - ry * (1 - C1), x + w - rx * (1 - C1), y + h, x + w - rx, y + h); + if (rx < w2) sp_curve_lineto(c, x + rx, y + h); + sp_curve_curveto(c, x + rx * (1 - C1), y + h, x, y + h - ry * (1 - C1), x, y + h - ry); + if (ry < h2) sp_curve_lineto(c, x, y + ry); + sp_curve_curveto(c, x, y + ry * (1 - C1), x + rx * (1 - C1), y, x + rx, y); + } else { + sp_curve_moveto(c, x + 0.0, y + 0.0); + sp_curve_lineto(c, x + w, y + 0.0); + sp_curve_lineto(c, x + w, y + h); + sp_curve_lineto(c, x + 0.0, y + h); + sp_curve_lineto(c, x + 0.0, y + 0.0); + } + + sp_curve_closepath_current(c); + sp_shape_set_curve_insync(SP_SHAPE(rect), c, TRUE); + sp_curve_unref(c); +} + +/* fixme: Think (Lauris) */ + +void +sp_rect_position_set(SPRect *rect, gdouble x, gdouble y, gdouble width, gdouble height) +{ + g_return_if_fail(rect != NULL); + g_return_if_fail(SP_IS_RECT(rect)); + + rect->x.computed = x; + rect->y.computed = y; + rect->width.computed = width; + rect->height.computed = height; + + SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +void +sp_rect_set_rx(SPRect *rect, gboolean set, gdouble value) +{ + g_return_if_fail(rect != NULL); + g_return_if_fail(SP_IS_RECT(rect)); + + rect->rx._set = set; + if (set) rect->rx.computed = value; + + SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +void +sp_rect_set_ry(SPRect *rect, gboolean set, gdouble value) +{ + g_return_if_fail(rect != NULL); + g_return_if_fail(SP_IS_RECT(rect)); + + rect->ry._set = set; + if (set) rect->ry.computed = value; + + SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +/* + * Initially we'll do: + * Transform x, y, set x, y, clear translation + */ + +/* fixme: Use preferred units somehow (Lauris) */ +/* fixme: Alternately preserve whatever units there are (lauris) */ + +static NR::Matrix +sp_rect_set_transform(SPItem *item, NR::Matrix const &xform) +{ + SPRect *rect = SP_RECT(item); + + /* Calculate rect start in parent coords. */ + NR::Point pos( NR::Point(rect->x.computed, rect->y.computed) * xform ); + + /* This function takes care of translation and scaling, we return whatever parts we can't + handle. */ + NR::Matrix ret(NR::transform(xform)); + gdouble const sw = hypot(ret[0], ret[1]); + gdouble const sh = hypot(ret[2], ret[3]); + if (sw > 1e-9) { + ret[0] /= sw; + ret[1] /= sw; + } else { + ret[0] = 1.0; + ret[1] = 0.0; + } + if (sh > 1e-9) { + ret[2] /= sh; + ret[3] /= sh; + } else { + ret[2] = 0.0; + ret[3] = 1.0; + } + + /* fixme: Would be nice to preserve units here */ + rect->width = rect->width.computed * sw; + rect->height = rect->height.computed * sh; + if (rect->rx._set) { + rect->rx = rect->rx.computed * sw; + } + if (rect->ry._set) { + rect->ry = rect->ry.computed * sh; + } + + /* Find start in item coords */ + pos = pos * ret.inverse(); + rect->x = pos[NR::X]; + rect->y = pos[NR::Y]; + + sp_rect_set_shape(rect); + + // Adjust stroke width + sp_item_adjust_stroke(item, sqrt(fabs(sw * sh))); + + // Adjust pattern fill + sp_item_adjust_pattern(item, xform / ret); + + // Adjust gradient fill + sp_item_adjust_gradient(item, xform / ret); + + item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + + return ret; +} + + +/** +Returns the ratio in which the vector from p0 to p1 is stretched by transform + */ +static gdouble +vector_stretch(NR::Point p0, NR::Point p1, NR::Matrix xform) +{ + if (p0 == p1) + return 0; + return (NR::distance(p0 * xform, p1 * xform) / NR::distance(p0, p1)); +} + +void +sp_rect_set_visible_rx(SPRect *rect, gdouble rx) +{ + if (rx == 0) { + rect->rx.computed = 0; + rect->rx._set = false; + } else { + rect->rx.computed = rx / vector_stretch( + NR::Point(rect->x.computed + 1, rect->y.computed), + NR::Point(rect->x.computed, rect->y.computed), + SP_ITEM(rect)->transform); + rect->rx._set = true; + } + SP_OBJECT(rect)->updateRepr(); +} + +void +sp_rect_set_visible_ry(SPRect *rect, gdouble ry) +{ + if (ry == 0) { + rect->ry.computed = 0; + rect->ry._set = false; + } else { + rect->ry.computed = ry / vector_stretch( + NR::Point(rect->x.computed, rect->y.computed + 1), + NR::Point(rect->x.computed, rect->y.computed), + SP_ITEM(rect)->transform); + rect->ry._set = true; + } + SP_OBJECT(rect)->updateRepr(); +} + +gdouble +sp_rect_get_visible_rx(SPRect *rect) +{ + if (!rect->rx._set) + return 0; + return rect->rx.computed * vector_stretch( + NR::Point(rect->x.computed + 1, rect->y.computed), + NR::Point(rect->x.computed, rect->y.computed), + SP_ITEM(rect)->transform); +} + +gdouble +sp_rect_get_visible_ry(SPRect *rect) +{ + if (!rect->ry._set) + return 0; + return rect->ry.computed * vector_stretch( + NR::Point(rect->x.computed, rect->y.computed + 1), + NR::Point(rect->x.computed, rect->y.computed), + SP_ITEM(rect)->transform); +} + +void +sp_rect_compensate_rxry(SPRect *rect, NR::Matrix xform) +{ + if (rect->rx.computed == 0 && rect->ry.computed == 0) + return; // nothing to compensate + + // test unit vectors to find out compensation: + NR::Point c(rect->x.computed, rect->y.computed); + NR::Point cx = c + NR::Point(1, 0); + NR::Point cy = c + NR::Point(0, 1); + + // apply previous transform if any + c *= SP_ITEM(rect)->transform; + cx *= SP_ITEM(rect)->transform; + cy *= SP_ITEM(rect)->transform; + + // find out stretches that we need to compensate + gdouble eX = vector_stretch(cx, c, xform); + gdouble eY = vector_stretch(cy, c, xform); + + // If only one of the radii is set, set both radii so they have the same visible length + // This is needed because if we just set them the same length in SVG, they might end up unequal because of transform + if ((rect->rx._set && !rect->ry._set) || (rect->ry._set && !rect->rx._set)) { + gdouble r = MAX(rect->rx.computed, rect->ry.computed); + rect->rx.computed = r / eX; + rect->ry.computed = r / eY; + } else { + rect->rx.computed = rect->rx.computed / eX; + rect->ry.computed = rect->ry.computed / eY; + } + + // Note that a radius may end up larger than half-side if the rect is scaled down; + // that's ok because this preserves the intended radii in case the rect is enlarged again, + // and set_shape will take care of trimming too large radii when generating d= + + rect->rx._set = rect->ry._set = true; +} + +void +sp_rect_set_visible_width(SPRect *rect, gdouble width) +{ + rect->width.computed = width / vector_stretch( + NR::Point(rect->x.computed + 1, rect->y.computed), + NR::Point(rect->x.computed, rect->y.computed), + SP_ITEM(rect)->transform); + rect->width._set = true; + SP_OBJECT(rect)->updateRepr(); +} + +void +sp_rect_set_visible_height(SPRect *rect, gdouble height) +{ + rect->height.computed = height / vector_stretch( + NR::Point(rect->x.computed, rect->y.computed + 1), + NR::Point(rect->x.computed, rect->y.computed), + SP_ITEM(rect)->transform); + rect->height._set = true; + SP_OBJECT(rect)->updateRepr(); +} + +gdouble +sp_rect_get_visible_width(SPRect *rect) +{ + if (!rect->width._set) + return 0; + return rect->width.computed * vector_stretch( + NR::Point(rect->x.computed + 1, rect->y.computed), + NR::Point(rect->x.computed, rect->y.computed), + SP_ITEM(rect)->transform); +} + +gdouble +sp_rect_get_visible_height(SPRect *rect) +{ + if (!rect->height._set) + return 0; + return rect->height.computed * vector_stretch( + NR::Point(rect->x.computed, rect->y.computed + 1), + NR::Point(rect->x.computed, rect->y.computed), + SP_ITEM(rect)->transform); +} + +/* + 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/sp-rect.h b/src/sp-rect.h new file mode 100644 index 000000000..4cf3b24ba --- /dev/null +++ b/src/sp-rect.h @@ -0,0 +1,65 @@ +#ifndef __SP_RECT_H__ +#define __SP_RECT_H__ + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "svg/svg-length.h" +#include "sp-shape.h" + + + +#define SP_TYPE_RECT (sp_rect_get_type ()) +#define SP_RECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_RECT, SPRect)) +#define SP_RECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_RECT, SPRectClass)) +#define SP_IS_RECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_RECT)) +#define SP_IS_RECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_RECT)) + +class SPRect; +class SPRectClass; + +struct SPRect : public SPShape { + SVGLength x; + SVGLength y; + SVGLength width; + SVGLength height; + SVGLength rx; + SVGLength ry; +}; + +struct SPRectClass { + SPShapeClass parent_class; +}; + + +/* Standard GType function */ +GType sp_rect_get_type (void); + +void sp_rect_position_set (SPRect * rect, gdouble x, gdouble y, gdouble width, gdouble height); + +/* If SET if FALSE, VALUE is just ignored */ +void sp_rect_set_rx(SPRect * rect, gboolean set, gdouble value); +void sp_rect_set_ry(SPRect * rect, gboolean set, gdouble value); + +void sp_rect_set_visible_rx (SPRect *rect, gdouble rx); +void sp_rect_set_visible_ry (SPRect *rect, gdouble ry); +gdouble sp_rect_get_visible_rx (SPRect *rect); +gdouble sp_rect_get_visible_ry (SPRect *rect); + +void sp_rect_set_visible_width (SPRect *rect, gdouble rx); +void sp_rect_set_visible_height (SPRect *rect, gdouble ry); +gdouble sp_rect_get_visible_width (SPRect *rect); +gdouble sp_rect_get_visible_height (SPRect *rect); + +void sp_rect_compensate_rxry (SPRect *rect, NR::Matrix xform); + +#endif diff --git a/src/sp-root.cpp b/src/sp-root.cpp new file mode 100644 index 000000000..1bb77ccc7 --- /dev/null +++ b/src/sp-root.cpp @@ -0,0 +1,678 @@ +#define __SP_ROOT_C__ + +/** \file + * SVG \ implementation. + */ +/* + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "config.h" + +#include "svg/svg.h" +#include "display/nr-arena-group.h" +#include "attributes.h" +#include "print.h" +#include "document.h" +#include "sp-defs.h" +#include "sp-root.h" +#include +#include +#include +#include +#include +#include +#include "svg/stringstream.h" +#include "inkscape_version.h" + +class SPDesktop; + +static void sp_root_class_init(SPRootClass *klass); +static void sp_root_init(SPRoot *root); + +static void sp_root_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); +static void sp_root_release(SPObject *object); +static void sp_root_set(SPObject *object, unsigned int key, gchar const *value); +static void sp_root_child_added(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref); +static void sp_root_remove_child(SPObject *object, Inkscape::XML::Node *child); +static void sp_root_update(SPObject *object, SPCtx *ctx, guint flags); +static void sp_root_modified(SPObject *object, guint flags); +static Inkscape::XML::Node *sp_root_write(SPObject *object, Inkscape::XML::Node *repr, guint flags); + +static NRArenaItem *sp_root_show(SPItem *item, NRArena *arena, unsigned int key, unsigned int flags); +static void sp_root_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const flags); +static void sp_root_print(SPItem *item, SPPrintContext *ctx); + +static SPGroupClass *parent_class; + +/** + * Returns the type info of sp_root, including its class sizes and initialization routines. + */ +GType +sp_root_get_type(void) +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPRootClass), + NULL, NULL, + (GClassInitFunc) sp_root_class_init, + NULL, NULL, + sizeof(SPRoot), + 16, + (GInstanceInitFunc) sp_root_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_GROUP, "SPRoot", &info, (GTypeFlags)0); + } + return type; +} + +/** + * Initializes an SPRootClass object by setting its class and parent class objects, and registering + * function pointers (i.e.\ gobject-style virtual functions) for various operations. + */ +static void +sp_root_class_init(SPRootClass *klass) +{ + GObjectClass *object_class; + SPObjectClass *sp_object_class; + SPItemClass *sp_item_class; + + object_class = G_OBJECT_CLASS(klass); + sp_object_class = (SPObjectClass *) klass; + sp_item_class = (SPItemClass *) klass; + + parent_class = (SPGroupClass *)g_type_class_ref(SP_TYPE_GROUP); + + sp_object_class->build = sp_root_build; + sp_object_class->release = sp_root_release; + sp_object_class->set = sp_root_set; + sp_object_class->child_added = sp_root_child_added; + sp_object_class->remove_child = sp_root_remove_child; + sp_object_class->update = sp_root_update; + sp_object_class->modified = sp_root_modified; + sp_object_class->write = sp_root_write; + + sp_item_class->show = sp_root_show; + sp_item_class->bbox = sp_root_bbox; + sp_item_class->print = sp_root_print; +} + +/** + * Initializes an SPRoot object by setting its default parameter values. + */ +static void +sp_root_init(SPRoot *root) +{ + static Inkscape::Version const zero_version(0, 0); + + sp_version_from_string(SVG_VERSION, &root->original.svg); + root->version.svg = root->original.svg; + root->version.inkscape = root->original.inkscape = + root->version.sodipodi = root->original.sodipodi = zero_version; + + root->x.unset(); + root->y.unset(); + root->width.unset(SVGLength::PERCENT, 1.0, 1.0); + root->height.unset(SVGLength::PERCENT, 1.0, 1.0); + + /* nr_matrix_set_identity(&root->viewbox); */ + root->viewBox_set = FALSE; + + root->c2p.set_identity(); + + root->defs = NULL; +} + +/** + * Fills in the data for an SPObject from its Inkscape::XML::Node object. + * It fills in data such as version, x, y, width, height, etc. + * It then calls the object's parent class object's build function. + */ +static void +sp_root_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + SPGroup *group = (SPGroup *) object; + SPRoot *root = (SPRoot *) object; + + if (repr->attribute("sodipodi:docname") || repr->attribute("SP-DOCNAME")) { + /* so we have a nonzero initial version */ + root->original.sodipodi.major = 0; + root->original.sodipodi.minor = 1; + } + sp_object_read_attr(object, "version"); + sp_object_read_attr(object, "sodipodi:version"); + sp_object_read_attr(object, "inkscape:version"); + /* It is important to parse these here, so objects will have viewport build-time */ + sp_object_read_attr(object, "x"); + sp_object_read_attr(object, "y"); + sp_object_read_attr(object, "width"); + sp_object_read_attr(object, "height"); + sp_object_read_attr(object, "viewBox"); + sp_object_read_attr(object, "preserveAspectRatio"); + + if (((SPObjectClass *) parent_class)->build) + (* ((SPObjectClass *) parent_class)->build) (object, document, repr); + + /* Search for first node */ + for (SPObject *o = sp_object_first_child(SP_OBJECT(group)) ; o != NULL; o = SP_OBJECT_NEXT(o) ) { + if (SP_IS_DEFS(o)) { + root->defs = SP_DEFS(o); + break; + } + } + + // clear transform, if any was read in - SVG does not allow transform= on + SP_ITEM(object)->transform = NR::identity(); +} + +/** + * This is a destructor routine for SPRoot objects. It de-references any \ items and calls + * the parent class destructor. + */ +static void +sp_root_release(SPObject *object) +{ + SPRoot *root = (SPRoot *) object; + root->defs = NULL; + + if (((SPObjectClass *) parent_class)->release) + ((SPObjectClass *) parent_class)->release(object); +} + +/** + * Sets the attribute given by key for SPRoot objects to the value specified by value. + */ +static void +sp_root_set(SPObject *object, unsigned int key, gchar const *value) +{ + SPRoot *root = SP_ROOT(object); + + switch (key) { + case SP_ATTR_VERSION: + if (!sp_version_from_string(value, &root->version.svg)) { + root->version.svg = root->original.svg; + } + break; + case SP_ATTR_SODIPODI_VERSION: + if (!sp_version_from_string(value, &root->version.sodipodi)) { + root->version.sodipodi = root->original.sodipodi; + } + case SP_ATTR_INKSCAPE_VERSION: + if (!sp_version_from_string(value, &root->version.inkscape)) { + root->version.inkscape = root->original.inkscape; + } + break; + case SP_ATTR_X: + if (!root->x.readAbsolute(value)) { + /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */ + root->x.unset(); + } + /* fixme: I am almost sure these do not require viewport flag (Lauris) */ + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); + break; + case SP_ATTR_Y: + if (!root->y.readAbsolute(value)) { + /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */ + root->y.unset(); + } + /* fixme: I am almost sure these do not require viewport flag (Lauris) */ + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); + break; + case SP_ATTR_WIDTH: + if (!root->width.readAbsolute(value) || !(root->width.computed > 0.0)) { + /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */ + root->width.unset(SVGLength::PERCENT, 1.0, 1.0); + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); + break; + case SP_ATTR_HEIGHT: + if (!root->height.readAbsolute(value) || !(root->height.computed > 0.0)) { + /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */ + root->height.unset(SVGLength::PERCENT, 1.0, 1.0); + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); + break; + case SP_ATTR_VIEWBOX: + if (value) { + double x, y, width, height; + char *eptr; + /* fixme: We have to take original item affine into account */ + /* fixme: Think (Lauris) */ + eptr = (gchar *) value; + x = g_ascii_strtod(eptr, &eptr); + while (*eptr && ((*eptr == ',') || (*eptr == ' '))) eptr++; + y = g_ascii_strtod(eptr, &eptr); + while (*eptr && ((*eptr == ',') || (*eptr == ' '))) eptr++; + width = g_ascii_strtod(eptr, &eptr); + while (*eptr && ((*eptr == ',') || (*eptr == ' '))) eptr++; + height = g_ascii_strtod(eptr, &eptr); + while (*eptr && ((*eptr == ',') || (*eptr == ' '))) eptr++; + if ((width > 0) && (height > 0)) { + /* Set viewbox */ + root->viewBox.x0 = x; + root->viewBox.y0 = y; + root->viewBox.x1 = x + width; + root->viewBox.y1 = y + height; + root->viewBox_set = TRUE; + } else { + root->viewBox_set = FALSE; + } + } else { + root->viewBox_set = FALSE; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); + break; + case SP_ATTR_PRESERVEASPECTRATIO: + /* Do setup before, so we can use break to escape */ + root->aspect_set = FALSE; + root->aspect_align = SP_ASPECT_XMID_YMID; + root->aspect_clip = SP_ASPECT_MEET; + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); + if (value) { + int len; + gchar c[256]; + gchar const *p, *e; + unsigned int align, clip; + p = value; + while (*p && *p == 32) p += 1; + if (!*p) break; + e = p; + while (*e && *e != 32) e += 1; + len = e - p; + if (len > 8) break; + memcpy(c, value, len); + c[len] = 0; + /* Now the actual part */ + if (!strcmp(c, "none")) { + align = SP_ASPECT_NONE; + } else if (!strcmp(c, "xMinYMin")) { + align = SP_ASPECT_XMIN_YMIN; + } else if (!strcmp(c, "xMidYMin")) { + align = SP_ASPECT_XMID_YMIN; + } else if (!strcmp(c, "xMaxYMin")) { + align = SP_ASPECT_XMAX_YMIN; + } else if (!strcmp(c, "xMinYMid")) { + align = SP_ASPECT_XMIN_YMID; + } else if (!strcmp(c, "xMidYMid")) { + align = SP_ASPECT_XMID_YMID; + } else if (!strcmp(c, "xMaxYMin")) { + align = SP_ASPECT_XMAX_YMID; + } else if (!strcmp(c, "xMinYMax")) { + align = SP_ASPECT_XMIN_YMAX; + } else if (!strcmp(c, "xMidYMax")) { + align = SP_ASPECT_XMID_YMAX; + } else if (!strcmp(c, "xMaxYMax")) { + align = SP_ASPECT_XMAX_YMAX; + } else { + break; + } + clip = SP_ASPECT_MEET; + while (*e && *e == 32) e += 1; + if (e) { + if (!strcmp(e, "meet")) { + clip = SP_ASPECT_MEET; + } else if (!strcmp(e, "slice")) { + clip = SP_ASPECT_SLICE; + } else { + break; + } + } + root->aspect_set = TRUE; + root->aspect_align = align; + root->aspect_clip = clip; + } + break; + default: + /* Pass the set event to the parent */ + if (((SPObjectClass *) parent_class)->set) { + ((SPObjectClass *) parent_class)->set(object, key, value); + } + break; + } +} + +/** + * This routine is for adding a child SVG object to an SPRoot object. + * The SPRoot object is taken to be an SPGroup. + */ +static void +sp_root_child_added(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref) +{ + SPRoot *root = (SPRoot *) object; + SPGroup *group = (SPGroup *) object; + + if (((SPObjectClass *) (parent_class))->child_added) + (* ((SPObjectClass *) (parent_class))->child_added)(object, child, ref); + + SPObject *co = object->document->getObjectByRepr(child); + g_assert(co != NULL); + + if (SP_IS_DEFS(co)) { + SPObject *c; + /* We search for first node - it is not beautiful, but works */ + for (c = sp_object_first_child(SP_OBJECT(group)) ; c != NULL; c = SP_OBJECT_NEXT(c) ) { + if (SP_IS_DEFS(c)) { + root->defs = SP_DEFS(c); + break; + } + } + } +} + +/** + * Removes the given child from this SPRoot object. + */ +static void sp_root_remove_child(SPObject *object, Inkscape::XML::Node *child) +{ + SPRoot *root = (SPRoot *) object; + + if ( root->defs && SP_OBJECT_REPR(root->defs) == child ) { + SPObject *iter; + /* We search for first remaining node - it is not beautiful, but works */ + for ( iter = sp_object_first_child(object) ; iter ; iter = SP_OBJECT_NEXT(iter) ) { + if ( SP_IS_DEFS(iter) && (SPDefs *)iter != root->defs ) { + root->defs = (SPDefs *)iter; + break; + } + } + if (!iter) { + /* we should probably create a new here? */ + g_critical("Last removed"); + root->defs = NULL; + } + } + + if (((SPObjectClass *) (parent_class))->remove_child) + (* ((SPObjectClass *) (parent_class))->remove_child)(object, child); +} + +/** + * This callback routine updates the SPRoot object when its attributes have been changed. + */ +static void +sp_root_update(SPObject *object, SPCtx *ctx, guint flags) +{ + SPItemView *v; + + SPItem *item = SP_ITEM(object); + SPRoot *root = SP_ROOT(object); + SPItemCtx *ictx = (SPItemCtx *) ctx; + + /* fixme: This will be invoked too often (Lauris) */ + /* fixme: We should calculate only if parent viewport has changed (Lauris) */ + /* If position is specified as percentage, calculate actual values */ + if (root->x.unit == SVGLength::PERCENT) { + root->x.computed = root->x.value * (ictx->vp.x1 - ictx->vp.x0); + } + if (root->y.unit == SVGLength::PERCENT) { + root->y.computed = root->y.value * (ictx->vp.y1 - ictx->vp.y0); + } + if (root->width.unit == SVGLength::PERCENT) { + root->width.computed = root->width.value * (ictx->vp.x1 - ictx->vp.x0); + } + if (root->height.unit == SVGLength::PERCENT) { + root->height.computed = root->height.value * (ictx->vp.y1 - ictx->vp.y0); + } + + /* Create copy of item context */ + SPItemCtx rctx = *ictx; + + /* Calculate child to parent transformation */ + root->c2p.set_identity(); + + if (object->parent) { + /* + * fixme: I am not sure whether setting x and y does or does not + * fixme: translate the content of inner SVG. + * fixme: Still applying translation and setting viewport to width and + * fixme: height seems natural, as this makes the inner svg element + * fixme: self-contained. The spec is vague here. + */ + root->c2p = NR::Matrix(NR::translate(root->x.computed, + root->y.computed)); + } + + if (root->viewBox_set) { + double x, y, width, height; + /* Determine actual viewbox in viewport coordinates */ + if (root->aspect_align == SP_ASPECT_NONE) { + x = 0.0; + y = 0.0; + width = root->width.computed; + height = root->height.computed; + } else { + double scalex, scaley, scale; + /* Things are getting interesting */ + scalex = root->width.computed / (root->viewBox.x1 - root->viewBox.x0); + scaley = root->height.computed / (root->viewBox.y1 - root->viewBox.y0); + scale = (root->aspect_clip == SP_ASPECT_MEET) ? MIN(scalex, scaley) : MAX(scalex, scaley); + width = (root->viewBox.x1 - root->viewBox.x0) * scale; + height = (root->viewBox.y1 - root->viewBox.y0) * scale; + /* Now place viewbox to requested position */ + /* todo: Use an array lookup to find the 0.0/0.5/1.0 coefficients, + as is done for dialogs/align.cpp. */ + switch (root->aspect_align) { + case SP_ASPECT_XMIN_YMIN: + x = 0.0; + y = 0.0; + break; + case SP_ASPECT_XMID_YMIN: + x = 0.5 * (root->width.computed - width); + y = 0.0; + break; + case SP_ASPECT_XMAX_YMIN: + x = 1.0 * (root->width.computed - width); + y = 0.0; + break; + case SP_ASPECT_XMIN_YMID: + x = 0.0; + y = 0.5 * (root->height.computed - height); + break; + case SP_ASPECT_XMID_YMID: + x = 0.5 * (root->width.computed - width); + y = 0.5 * (root->height.computed - height); + break; + case SP_ASPECT_XMAX_YMID: + x = 1.0 * (root->width.computed - width); + y = 0.5 * (root->height.computed - height); + break; + case SP_ASPECT_XMIN_YMAX: + x = 0.0; + y = 1.0 * (root->height.computed - height); + break; + case SP_ASPECT_XMID_YMAX: + x = 0.5 * (root->width.computed - width); + y = 1.0 * (root->height.computed - height); + break; + case SP_ASPECT_XMAX_YMAX: + x = 1.0 * (root->width.computed - width); + y = 1.0 * (root->height.computed - height); + break; + default: + x = 0.0; + y = 0.0; + break; + } + } + + /* Compose additional transformation from scale and position */ + NR::Point const viewBox_min(root->viewBox.x0, + root->viewBox.y0); + NR::Point const viewBox_max(root->viewBox.x1, + root->viewBox.y1); + NR::scale const viewBox_length( viewBox_max - viewBox_min ); + NR::scale const new_length(width, height); + + /* Append viewbox transformation */ + /* TODO: The below looks suspicious to me (pjrm): I wonder whether the RHS + expression should have c2p at the beginning rather than at the end. Test it. */ + root->c2p = NR::translate(-viewBox_min) * ( new_length / viewBox_length ) * NR::translate(x, y) * root->c2p; + } + + rctx.i2doc = root->c2p * rctx.i2doc; + + /* Initialize child viewport */ + if (root->viewBox_set) { + rctx.vp.x0 = root->viewBox.x0; + rctx.vp.y0 = root->viewBox.y0; + rctx.vp.x1 = root->viewBox.x1; + rctx.vp.y1 = root->viewBox.y1; + } else { + /* fixme: I wonder whether this logic is correct (Lauris) */ + if (object->parent) { + rctx.vp.x0 = root->x.computed; + rctx.vp.y0 = root->y.computed; + } else { + rctx.vp.x0 = 0.0; + rctx.vp.y0 = 0.0; + } + rctx.vp.x1 = root->width.computed; + rctx.vp.y1 = root->height.computed; + } + + rctx.i2vp = NR::identity(); + + /* And invoke parent method */ + if (((SPObjectClass *) (parent_class))->update) + ((SPObjectClass *) (parent_class))->update(object, (SPCtx *) &rctx, flags); + + /* As last step set additional transform of arena group */ + for (v = item->display; v != NULL; v = v->next) { + nr_arena_group_set_child_transform(NR_ARENA_GROUP(v->arenaitem), root->c2p); + } +} + +/** + * Calls the modified routine of the SPRoot object's parent class. + * Also, if the viewport has been modified, it sets the document size to the new + * height and width. + */ +static void +sp_root_modified(SPObject *object, guint flags) +{ + SPRoot *root = SP_ROOT(object); + + if (((SPObjectClass *) (parent_class))->modified) + (* ((SPObjectClass *) (parent_class))->modified)(object, flags); + + /* fixme: (Lauris) */ + if (!object->parent && (flags & SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + sp_document_resized_signal_emit (SP_OBJECT_DOCUMENT(root), root->width.computed, root->height.computed); + } +} + +/** + * Writes the object into the repr object, then calls the parent's write routine. + */ +static Inkscape::XML::Node * +sp_root_write(SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + SPRoot *root = SP_ROOT(object); + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = sp_repr_new("svg:svg"); + } + + if (flags & SP_OBJECT_WRITE_EXT) { + repr->setAttribute("sodipodi:version", SODIPODI_VERSION); + repr->setAttribute("inkscape:version", INKSCAPE_VERSION); + } + + repr->setAttribute("version", SVG_VERSION); + + if (fabs(root->x.computed) > 1e-9) + sp_repr_set_svg_double(repr, "x", root->x.computed); + if (fabs(root->y.computed) > 1e-9) + sp_repr_set_svg_double(repr, "y", root->y.computed); + + /* Unlike all other SPObject, here we want to preserve absolute units too (and only here, + * according to the recommendation in http://www.w3.org/TR/SVG11/coords.html#Units). + */ + repr->setAttribute("width", sp_svg_length_write_with_units(root->width).c_str()); + repr->setAttribute("height", sp_svg_length_write_with_units(root->height).c_str()); + + if (root->viewBox_set) { + Inkscape::SVGOStringStream os; + os << root->viewBox.x0 << " " << root->viewBox.y0 << " " << root->viewBox.x1 - root->viewBox.x0 << " " << root->viewBox.y1 - root->viewBox.y0; + repr->setAttribute("viewBox", os.str().c_str()); + } + + if (((SPObjectClass *) (parent_class))->write) + ((SPObjectClass *) (parent_class))->write(object, repr, flags); + + return repr; +} + +/** + * Displays the SPRoot item on the NRArena. + */ +static NRArenaItem * +sp_root_show(SPItem *item, NRArena *arena, unsigned int key, unsigned int flags) +{ + SPRoot *root = SP_ROOT(item); + + NRArenaItem *ai; + if (((SPItemClass *) (parent_class))->show) { + ai = ((SPItemClass *) (parent_class))->show(item, arena, key, flags); + if (ai) { + nr_arena_group_set_child_transform(NR_ARENA_GROUP(ai), root->c2p); + } + } else { + ai = NULL; + } + + return ai; +} + +/** + * Virtual bbox callback. + */ +static void +sp_root_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const flags) +{ + SPRoot const *root = SP_ROOT(item); + + if (((SPItemClass *) (parent_class))->bbox) { + NR::Matrix const product( root->c2p * transform ); + ((SPItemClass *) (parent_class))->bbox(item, bbox, + product, + flags); + } +} + +/** + * Virtual print callback. + */ +static void +sp_root_print(SPItem *item, SPPrintContext *ctx) +{ + SPRoot *root = SP_ROOT(item); + + sp_print_bind(ctx, root->c2p, 1.0); + + if (((SPItemClass *) (parent_class))->print) { + ((SPItemClass *) (parent_class))->print(item, ctx); + } + + sp_print_release(ctx); +} + + +/* + 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 : diff --git a/src/sp-root.h b/src/sp-root.h new file mode 100644 index 000000000..8a6a4ed57 --- /dev/null +++ b/src/sp-root.h @@ -0,0 +1,81 @@ +#ifndef SP_ROOT_H_SEEN +#define SP_ROOT_H_SEEN + +/** \file + * SPRoot: SVG \ implementation. + */ +/* + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define SP_TYPE_ROOT (sp_root_get_type()) +#define SP_ROOT(o) (G_TYPE_CHECK_INSTANCE_CAST((o), SP_TYPE_ROOT, SPRoot)) +#define SP_ROOT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), SP_TYPE_ROOT, SPRootClass)) +#define SP_IS_ROOT(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), SP_TYPE_ROOT)) +#define SP_IS_ROOT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE((k), SP_TYPE_ROOT)) + +#include +#include "version.h" +#include "svg/svg-length.h" +#include "enums.h" +#include "sp-item-group.h" + +/** \ element */ +struct SPRoot : public SPGroup { + struct { + Inkscape::Version svg; + Inkscape::Version sodipodi; + Inkscape::Version inkscape; + } version, original; + + SVGLength x; + SVGLength y; + SVGLength width; + SVGLength height; + + /* viewBox; */ + unsigned int viewBox_set : 1; + NRRect viewBox; + + /* preserveAspectRatio */ + unsigned int aspect_set : 1; + unsigned int aspect_align : 4; + unsigned int aspect_clip : 1; + + /** Child to parent additional transform. */ + NR::Matrix c2p; + + /** + * Primary \ element where we put new defs (patterns, gradients etc.). + * + * At the time of writing, this is chosen as the first \ child of + * this \ element: see writers of this member in sp-root.cpp. + */ + SPDefs *defs; +}; + +struct SPRootClass { + SPGroupClass parent_class; +}; + +GType sp_root_get_type(); + + +#endif /* !SP_ROOT_H_SEEN */ + +/* + 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 : diff --git a/src/sp-shape.cpp b/src/sp-shape.cpp new file mode 100644 index 000000000..2ae76ef2e --- /dev/null +++ b/src/sp-shape.cpp @@ -0,0 +1,919 @@ +#define __SP_SHAPE_C__ + +/* + * Base class for shapes, including element + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2004 John Cliff + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + + +#include +#include +#include +#include +#include + + +#include "macros.h" +#include "display/nr-arena-shape.h" +#include "print.h" +#include "document.h" +#include "marker-status.h" +#include "style.h" +#include "sp-marker.h" +#include "sp-path.h" +#include "prefs-utils.h" + +#define noSHAPE_VERBOSE + +static void sp_shape_class_init (SPShapeClass *klass); +static void sp_shape_init (SPShape *shape); + +static void sp_shape_build (SPObject * object, SPDocument * document, Inkscape::XML::Node * repr); +static void sp_shape_release (SPObject *object); + +static void sp_shape_update (SPObject *object, SPCtx *ctx, unsigned int flags); +static void sp_shape_modified (SPObject *object, unsigned int flags); + +static void sp_shape_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const flags); +void sp_shape_print (SPItem * item, SPPrintContext * ctx); +static NRArenaItem *sp_shape_show (SPItem *item, NRArena *arena, unsigned int key, unsigned int flags); +static void sp_shape_hide (SPItem *item, unsigned int key); +static void sp_shape_snappoints (SPItem const *item, SnapPointsIter p); + +static void sp_shape_update_marker_view (SPShape *shape, NRArenaItem *ai); +static int sp_shape_has_markers (SPShape const *shape); +static int sp_shape_number_of_markers (SPShape* Shape, int type); + +static SPItemClass *parent_class; + +GType +sp_shape_get_type (void) +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof (SPShapeClass), + NULL, NULL, + (GClassInitFunc) sp_shape_class_init, + NULL, NULL, + sizeof (SPShape), + 16, + (GInstanceInitFunc) sp_shape_init, + NULL, /* value_table */ + }; + type = g_type_register_static (SP_TYPE_ITEM, "SPShape", &info, (GTypeFlags)0); + } + return type; +} + +static void +sp_shape_class_init (SPShapeClass *klass) +{ + SPObjectClass *sp_object_class; + SPItemClass * item_class; + SPPathClass * path_class; + + sp_object_class = (SPObjectClass *) klass; + item_class = (SPItemClass *) klass; + path_class = (SPPathClass *) klass; + + parent_class = (SPItemClass *)g_type_class_peek_parent (klass); + + sp_object_class->build = sp_shape_build; + sp_object_class->release = sp_shape_release; + sp_object_class->update = sp_shape_update; + sp_object_class->modified = sp_shape_modified; + + item_class->bbox = sp_shape_bbox; + item_class->print = sp_shape_print; + item_class->show = sp_shape_show; + item_class->hide = sp_shape_hide; + item_class->snappoints = sp_shape_snappoints; +} + +static void +sp_shape_init (SPShape *shape) +{ + /* Nothing here */ +} + +static void +sp_shape_build (SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + if (((SPObjectClass *) (parent_class))->build) { + (*((SPObjectClass *) (parent_class))->build) (object, document, repr); + } +} + +static void +sp_shape_release (SPObject *object) +{ + SPItem *item; + SPShape *shape; + SPItemView *v; + int i; + + item = (SPItem *) object; + shape = (SPShape *) object; + + for (i=SP_MARKER_LOC_START; imarker[i]) { + sp_signal_disconnect_by_data (shape->marker[i], object); + for (v = item->display; v != NULL; v = v->next) { + sp_marker_hide ((SPMarker *) shape->marker[i], NR_ARENA_ITEM_GET_KEY (v->arenaitem) + i); + } + shape->marker[i] = sp_object_hunref (shape->marker[i], object); + } + } + if (shape->curve) { + shape->curve = sp_curve_unref (shape->curve); + } + + if (((SPObjectClass *) parent_class)->release) { + ((SPObjectClass *) parent_class)->release (object); + } +} + +static void +sp_shape_update (SPObject *object, SPCtx *ctx, unsigned int flags) +{ + SPItem *item = (SPItem *) object; + SPShape *shape = (SPShape *) object; + + if (((SPObjectClass *) (parent_class))->update) { + (* ((SPObjectClass *) (parent_class))->update) (object, ctx, flags); + } + + /* This stanza checks that an object's marker style agrees with + * the marker objects it has allocated. sp_shape_set_marker ensures + * that the appropriate marker objects are present (or absent) to + * match the style. + */ + /* TODO: It would be nice if this could be done at an earlier level */ + for (int i = 0 ; i < SP_MARKER_LOC_QTY ; i++) { + sp_shape_set_marker (object, i, object->style->marker[i].value); + } + + if (flags & (SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + SPStyle *style; + style = SP_OBJECT_STYLE (object); + if (style->stroke_width.unit == SP_CSS_UNIT_PERCENT) { + SPItemCtx *ictx = (SPItemCtx *) ctx; + double const aw = 1.0 / NR::expansion(ictx->i2vp); + style->stroke_width.computed = style->stroke_width.value * aw; + for (SPItemView *v = ((SPItem *) (shape))->display; v != NULL; v = v->next) { + nr_arena_shape_set_style ((NRArenaShape *) v->arenaitem, style); + } + } + } + + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG)) { + /* This is suboptimal, because changing parent style schedules recalculation */ + /* But on the other hand - how can we know that parent does not tie style and transform */ + NR::Rect const paintbox = SP_ITEM(object)->invokeBbox(NR::identity()); + for (SPItemView *v = SP_ITEM (shape)->display; v != NULL; v = v->next) { + NRArenaShape * const s = NR_ARENA_SHAPE(v->arenaitem); + if (flags & SP_OBJECT_MODIFIED_FLAG) { + nr_arena_shape_set_path(s, shape->curve, (flags & SP_OBJECT_USER_MODIFIED_FLAG_B)); + } + s->setPaintBox(paintbox); + } + } + + if (sp_shape_has_markers (shape)) { + + /* Dimension marker views */ + for (SPItemView *v = item->display; v != NULL; v = v->next) { + + if (!v->arenaitem->key) { + /* Get enough keys for all, start, mid and end marker types, + ** and set this view's arenaitem key to the first of these keys. + */ + NR_ARENA_ITEM_SET_KEY ( + v->arenaitem, + sp_item_display_key_new (SP_MARKER_LOC_QTY) + ); + } + + for (int i = 0 ; i < SP_MARKER_LOC_QTY ; i++) { + if (shape->marker[i]) { + sp_marker_show_dimension ((SPMarker *) shape->marker[i], + NR_ARENA_ITEM_GET_KEY (v->arenaitem) + i - SP_MARKER_LOC, + sp_shape_number_of_markers (shape, i)); + } + } + } + + /* Update marker views */ + for (SPItemView *v = item->display; v != NULL; v = v->next) { + sp_shape_update_marker_view (shape, v->arenaitem); + } + } +} + + +/** +* Works out whether a marker of a given type is required at a particular +* point on a shape. +* +* \param shape Shape of interest. +* \param m Marker type (e.g. SP_MARKER_LOC_START) +* \param bp Path segment. +* \return 1 if a marker is required here, otherwise 0. +*/ +static bool +sp_shape_marker_required(SPShape const *shape, int const m, NArtBpath *bp) +{ + if (shape->marker[m] == NULL) { + return false; + } + + if (bp == shape->curve->bpath) + return m == SP_MARKER_LOC_START; + else if (bp[1].code == NR_END) + return m == SP_MARKER_LOC_END; + else + return m == SP_MARKER_LOC_MID; +} + +static bool +is_moveto(NRPathcode const c) +{ + return c == NR_MOVETO || c == NR_MOVETO_OPEN; +} + +/** \pre The bpath[] containing bp begins with a moveto. */ +static NArtBpath const * +first_seg_in_subpath(NArtBpath const *bp) +{ + while (!is_moveto(bp->code)) { + --bp; + } + return bp; +} + +static NArtBpath const * +last_seg_in_subpath(NArtBpath const *bp) +{ + for(;;) { + ++bp; + switch (bp->code) { + case NR_MOVETO: + case NR_MOVETO_OPEN: + case NR_END: + --bp; + return bp; + + default: continue; + } + } +} + + +/* A subpath begins with a moveto and ends immediately before the next moveto or NR_END. + * (`moveto' here means either NR_MOVETO or NR_MOVETO_OPEN.) I'm assuming that non-empty + * paths always begin with a moveto. + * + * The control points of the subpath are the control points of the path elements of the subpath. + * + * As usual, the control points of a moveto or NR_LINETO are {c(3)}, and + * the control points of a NR_CURVETO are {c(1), c(2), c(3)}. + * (It follows from the definition that NR_END isn't part of a subpath.) + * + * The initial control point is bpath[bi0].c(3). + * + * Reference: http://www.w3.org/TR/SVG11/painting.html#MarkerElement, the `orient' attribute. + * Reference for behaviour of zero-length segments: + * http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes + */ + +static double const no_tangent = 128.0; /* arbitrarily-chosen value outside the range of atan2, i.e. outside of [-pi, pi]. */ + +/** \pre The bpath[] containing bp0 begins with a moveto. */ +static double +outgoing_tangent(NArtBpath const *bp0) +{ + /* See notes in comment block above. */ + + g_assert(bp0->code != NR_END); + NR::Point const &p0 = bp0->c(3); + NR::Point other; + for (NArtBpath const *bp = bp0;;) { + ++bp; + switch (bp->code) { + case NR_LINETO: + other = bp->c(3); + if (other != p0) { + goto found; + } + break; + + case NR_CURVETO: + for (unsigned ci = 1; ci <= 3; ++ci) { + other = bp->c(ci); + if (other != p0) { + goto found; + } + } + break; + + case NR_MOVETO_OPEN: + case NR_END: + case NR_MOVETO: + bp = first_seg_in_subpath(bp0); + if (bp == bp0) { + /* Gone right around the subpath without finding any different point since the + * initial moveto. */ + return no_tangent; + } + if (bp->code != NR_MOVETO) { + /* Open subpath. */ + return no_tangent; + } + other = bp->c(3); + if (other != p0) { + goto found; + } + break; + } + + if (bp == bp0) { + /* Back where we started, so zero-length subpath. */ + return no_tangent; + + /* Note: this test must come after we've looked at element bp, in case bp0 is a curve: + * we must look at c(1) and c(2). (E.g. single-curve subpath.) + */ + } + } + +found: + return atan2( other - p0 ); +} + +/** \pre The bpath[] containing bp0 begins with a moveto. */ +static double +incoming_tangent(NArtBpath const *bp0) +{ + /* See notes in comment block before outgoing_tangent. */ + + g_assert(bp0->code != NR_END); + NR::Point const &p0 = bp0->c(3); + NR::Point other; + for (NArtBpath const *bp = bp0;;) { + switch (bp->code) { + case NR_LINETO: + other = bp->c(3); + if (other != p0) { + goto found; + } + --bp; + break; + + case NR_CURVETO: + for (unsigned ci = 3; ci != 0; --ci) { + other = bp->c(ci); + if (other != p0) { + goto found; + } + } + --bp; + break; + + case NR_MOVETO: + case NR_MOVETO_OPEN: + other = bp->c(3); + if (other != p0) { + goto found; + } + if (bp->code != NR_MOVETO) { + /* Open subpath. */ + return no_tangent; + } + bp = last_seg_in_subpath(bp0); + break; + + default: /* includes NR_END */ + g_error("Found invalid path code %u in middle of path.", bp->code); + return no_tangent; + } + + if (bp == bp0) { + /* Back where we started from: zero-length subpath. */ + return no_tangent; + } + } + +found: + return atan2( p0 - other ); +} + + +/** +* Calculate the transform required to get a marker's path object in the +* right place for particular path segment on a shape. You should +* call sp_shape_marker_required first to see if a marker is required +* at this point. +* +* \see sp_shape_marker_required. +* +* \param shape Shape which the marker is for. +* \param m Marker type (e.g. SP_MARKER_LOC_START) +* \param bp Path segment which the arrow is for. +* \return Transform matrix. +*/ + +static NR::Matrix +sp_shape_marker_get_transform(SPShape const *shape, NArtBpath const *bp) +{ + g_return_val_if_fail(( is_moveto(shape->curve->bpath[0].code) + && ( 0 < shape->curve->end ) + && ( shape->curve->bpath[shape->curve->end].code == NR_END ) ), + NR::Matrix(NR::translate(bp->c(3)))); + double const angle1 = incoming_tangent(bp); + double const angle2 = outgoing_tangent(bp); + + /* angle1 and angle2 are now each either unset (i.e. still 100 from their initialization) or in + [-pi, pi] from atan2. */ + g_assert((-3.15 < angle1 && angle1 < 3.15) || (angle1 == no_tangent)); + g_assert((-3.15 < angle2 && angle2 < 3.15) || (angle2 == no_tangent)); + + double ret_angle; + if (angle1 == no_tangent) { + /* First vertex of an open subpath. */ + ret_angle = ( angle2 == no_tangent + ? 0. + : angle2 ); + } else if (angle2 == no_tangent) { + /* Last vertex of an open subpath. */ + ret_angle = angle1; + } else { + ret_angle = .5 * (angle1 + angle2); + + if ( fabs( angle2 - angle1 ) > M_PI ) { + /* ret_angle is in the middle of the larger of the two sectors between angle1 and + * angle2, so flip it by 180degrees to force it to the middle of the smaller sector. + * + * (Imagine a circle with rays drawn at angle1 and angle2 from the centre of the + * circle. Those two rays divide the circle into two sectors.) + */ + ret_angle += M_PI; + } + } + + return NR::Matrix(NR::rotate(ret_angle)) * NR::translate(bp->c(3)); +} + +/* Marker views have to be scaled already */ + +static void +sp_shape_update_marker_view (SPShape *shape, NRArenaItem *ai) +{ + SPStyle *style = ((SPObject *) shape)->style; + + marker_status("sp_shape_update_marker_view: Updating views of markers"); + + for (int i = SP_MARKER_LOC_START; i < SP_MARKER_LOC_QTY; i++) { + if (shape->marker[i] == NULL) { + continue; + } + + int n = 0; + + for (NArtBpath *bp = shape->curve->bpath; bp->code != NR_END; bp++) { + if (sp_shape_marker_required (shape, i, bp)) { + NR::Matrix const m(sp_shape_marker_get_transform(shape, bp)); + sp_marker_show_instance ((SPMarker* ) shape->marker[i], ai, + NR_ARENA_ITEM_GET_KEY(ai) + i, n, m, + style->stroke_width.computed); + n++; + } + } + } +} + +static void +sp_shape_modified (SPObject *object, unsigned int flags) +{ + SPShape *shape = SP_SHAPE (object); + + if (((SPObjectClass *) (parent_class))->modified) { + (* ((SPObjectClass *) (parent_class))->modified) (object, flags); + } + + if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) { + for (SPItemView *v = SP_ITEM (shape)->display; v != NULL; v = v->next) { + nr_arena_shape_set_style (NR_ARENA_SHAPE (v->arenaitem), object->style); + } + } +} + +static void sp_shape_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const flags) +{ + SPShape const *shape = SP_SHAPE (item); + + if (shape->curve) { + + NRRect cbbox; + NRBPath bp; + + bp.path = SP_CURVE_BPATH (shape->curve); + + cbbox.x0 = cbbox.y0 = NR_HUGE; + cbbox.x1 = cbbox.y1 = -NR_HUGE; + + nr_path_matrix_bbox_union(&bp, transform, &cbbox); + + SPStyle* style=SP_OBJECT_STYLE (item); + if (style->stroke.type != SP_PAINT_TYPE_NONE) { + double const scale = expansion(transform); + if ( fabs(style->stroke_width.computed * scale) > 0.01 ) { // sinon c'est 0=oon veut pas de bord + double const width = MAX(0.125, style->stroke_width.computed * scale); + if ( fabs(cbbox.x1-cbbox.x0) > -0.00001 && fabs(cbbox.y1-cbbox.y0) > -0.00001 ) { + cbbox.x0-=0.5*width; + cbbox.x1+=0.5*width; + cbbox.y0-=0.5*width; + cbbox.y1+=0.5*width; + } + } + } + + // Union with bboxes of the markers, if any + if (sp_shape_has_markers (shape)) { + for (NArtBpath* bp = shape->curve->bpath; bp->code != NR_END; bp++) { + for (int m = SP_MARKER_LOC_START; m < SP_MARKER_LOC_QTY; m++) { + if (sp_shape_marker_required (shape, m, bp)) { + + SPMarker* marker = SP_MARKER (shape->marker[m]); + SPItem* marker_item = sp_item_first_item_child (SP_OBJECT (shape->marker[m])); + + NR::Matrix tr(sp_shape_marker_get_transform(shape, bp)); + + if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) { + tr = NR::scale(style->stroke_width.computed) * tr; + } + + // total marker transform + tr = marker_item->transform * marker->c2p * tr * transform; + + // get bbox of the marker with that transform + NRRect marker_bbox; + sp_item_invoke_bbox (marker_item, &marker_bbox, tr, true); + // union it with the shape bbox + nr_rect_d_union (&cbbox, &cbbox, &marker_bbox); + } + } + } + } + + if ( fabs(cbbox.x1-cbbox.x0) > -0.00001 && fabs(cbbox.y1-cbbox.y0) > -0.00001 ) { + NRRect tbbox=*bbox; + nr_rect_d_union (bbox, &cbbox, &tbbox); + } + } +} + +void +sp_shape_print (SPItem *item, SPPrintContext *ctx) +{ + NRRect pbox, dbox, bbox; + + SPShape *shape = SP_SHAPE(item); + + if (!shape->curve) return; + + gint add_comments = prefs_get_int_attribute_limited ("printing.debug", "add-label-comments", 0, 0, 1); + if (add_comments) { + gchar * comment = g_strdup_printf("begin '%s'", + SP_OBJECT(item)->defaultLabel()); + sp_print_comment(ctx, comment); + g_free(comment); + } + + /* fixme: Think (Lauris) */ + sp_item_invoke_bbox(item, &pbox, NR::identity(), TRUE); + dbox.x0 = 0.0; + dbox.y0 = 0.0; + dbox.x1 = sp_document_width (SP_OBJECT_DOCUMENT (item)); + dbox.y1 = sp_document_height (SP_OBJECT_DOCUMENT (item)); + sp_item_bbox_desktop (item, &bbox); + NR::Matrix const i2d = sp_item_i2d_affine(item); + + SPStyle* style = SP_OBJECT_STYLE (item); + + if (style->fill.type != SP_PAINT_TYPE_NONE) { + NRBPath bp; + bp.path = shape->curve->bpath; + sp_print_fill (ctx, &bp, i2d, style, &pbox, &dbox, &bbox); + } + + if (style->stroke.type != SP_PAINT_TYPE_NONE) { + NRBPath bp; + bp.path = shape->curve->bpath; + sp_print_stroke (ctx, &bp, i2d, style, &pbox, &dbox, &bbox); + } + + for (NArtBpath* bp = shape->curve->bpath; bp->code != NR_END; bp++) { + for (int m = SP_MARKER_LOC_START; m < SP_MARKER_LOC_QTY; m++) { + if (sp_shape_marker_required (shape, m, bp)) { + + SPMarker* marker = SP_MARKER (shape->marker[m]); + SPItem* marker_item = sp_item_first_item_child (SP_OBJECT (shape->marker[m])); + + NR::Matrix tr(sp_shape_marker_get_transform(shape, bp)); + + if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) { + tr = NR::scale(style->stroke_width.computed) * tr; + } + + tr = marker_item->transform * marker->c2p * tr; + + NR::Matrix old_tr = marker_item->transform; + marker_item->transform = tr; + sp_item_invoke_print (marker_item, ctx); + marker_item->transform = old_tr; + } + } + } + + if (add_comments) { + gchar * comment = g_strdup_printf("end '%s'", + SP_OBJECT(item)->defaultLabel()); + sp_print_comment(ctx, comment); + g_free(comment); + } +} + +static NRArenaItem * +sp_shape_show (SPItem *item, NRArena *arena, unsigned int key, unsigned int flags) +{ + SPObject *object = SP_OBJECT(item); + SPShape *shape = SP_SHAPE(item); + + NRArenaItem *arenaitem = NRArenaShape::create(arena); + NRArenaShape * const s = NR_ARENA_SHAPE(arenaitem); + nr_arena_shape_set_style(s, object->style); + nr_arena_shape_set_path(s, shape->curve, false); + NR::Rect const paintbox = item->invokeBbox(NR::identity()); + s->setPaintBox(paintbox); + + if (sp_shape_has_markers (shape)) { + + /* Dimension marker views */ + if (!arenaitem->key) { + NR_ARENA_ITEM_SET_KEY (arenaitem, sp_item_display_key_new (SP_MARKER_LOC_QTY)); + } + + for (int i = 0; i < SP_MARKER_LOC_QTY; i++) { + if (shape->marker[i]) { + sp_marker_show_dimension ((SPMarker *) shape->marker[i], + NR_ARENA_ITEM_GET_KEY (arenaitem) + i - SP_MARKER_LOC, + sp_shape_number_of_markers (shape, i)); + } + } + + + /* Update marker views */ + sp_shape_update_marker_view (shape, arenaitem); + } + + return arenaitem; +} + +static void +sp_shape_hide (SPItem *item, unsigned int key) +{ + SPShape *shape; + SPItemView *v; + int i; + + shape = (SPShape *) item; + + for (i=0; imarker[i]) { + for (v = item->display; v != NULL; v = v->next) { + if (key == v->key) { + sp_marker_hide ((SPMarker *) shape->marker[i], + NR_ARENA_ITEM_GET_KEY (v->arenaitem) + i); + } + } + } + } + + if (((SPItemClass *) parent_class)->hide) { + ((SPItemClass *) parent_class)->hide (item, key); + } +} + +/* Marker stuff */ + +/** +* \param shape Shape. +* \return TRUE if the shape has any markers, or FALSE if not. +*/ +static int +sp_shape_has_markers (SPShape const *shape) +{ + /* Note, we're ignoring 'marker' settings, which technically should apply for + all three settings. This should be fixed later such that if 'marker' is + specified, then all three should appear. */ + + return ( + shape->curve && + (shape->marker[SP_MARKER_LOC_START] || + shape->marker[SP_MARKER_LOC_MID] || + shape->marker[SP_MARKER_LOC_END]) + ); +} + + +/** +* \param shape Shape. +* \param type Marker type (e.g. SP_MARKER_LOC_START) +* \return Number of markers that the shape has of this type. +*/ +static int +sp_shape_number_of_markers (SPShape *shape, int type) +{ + int n = 0; + for (NArtBpath* bp = shape->curve->bpath; bp->code != NR_END; bp++) { + if (sp_shape_marker_required (shape, type, bp)) { + n++; + } + } + + return n; +} + +static void +sp_shape_marker_release (SPObject *marker, SPShape *shape) +{ + SPItem *item; + int i; + + item = (SPItem *) shape; + + marker_status("sp_shape_marker_release: Releasing markers"); + for (i = SP_MARKER_LOC_START; i < SP_MARKER_LOC_QTY; i++) { + if (marker == shape->marker[i]) { + SPItemView *v; + /* Hide marker */ + for (v = item->display; v != NULL; v = v->next) { + sp_marker_hide ((SPMarker *) (shape->marker[i]), NR_ARENA_ITEM_GET_KEY (v->arenaitem) + i); + /* fixme: Do we need explicit remove here? (Lauris) */ + /* nr_arena_item_set_mask (v->arenaitem, NULL); */ + } + /* Detach marker */ + sp_signal_disconnect_by_data (shape->marker[i], item); + shape->marker[i] = sp_object_hunref (shape->marker[i], item); + } + } +} + +static void +sp_shape_marker_modified (SPObject *marker, guint flags, SPItem *item) +{ + /* I think mask does update automagically */ + /* g_warning ("Item %s mask %s modified", SP_OBJECT_ID (item), SP_OBJECT_ID (mask)); */ +} + +void +sp_shape_set_marker (SPObject *object, unsigned int key, const gchar *value) +{ + SPItem *item = (SPItem *) object; + SPShape *shape = (SPShape *) object; + + if (key < SP_MARKER_LOC_START || key > SP_MARKER_LOC_END) { + return; + } + + SPObject *mrk = sp_uri_reference_resolve (SP_OBJECT_DOCUMENT (object), value); + if (mrk != shape->marker[key]) { + if (shape->marker[key]) { + SPItemView *v; + + /* Detach marker */ + g_signal_handler_disconnect (shape->marker[key], shape->release_connect[key]); + g_signal_handler_disconnect (shape->marker[key], shape->modified_connect[key]); + + /* Hide marker */ + for (v = item->display; v != NULL; v = v->next) { + sp_marker_hide ((SPMarker *) (shape->marker[key]), + NR_ARENA_ITEM_GET_KEY (v->arenaitem) + key); + /* fixme: Do we need explicit remove here? (Lauris) */ + /* nr_arena_item_set_mask (v->arenaitem, NULL); */ + } + + /* Unref marker */ + shape->marker[key] = sp_object_hunref (shape->marker[key], object); + } + if (SP_IS_MARKER (mrk)) { + shape->marker[key] = sp_object_href (mrk, object); + shape->release_connect[key] = g_signal_connect (G_OBJECT (shape->marker[key]), "release", + G_CALLBACK (sp_shape_marker_release), shape); + shape->modified_connect[key] = g_signal_connect (G_OBJECT (shape->marker[key]), "modified", + G_CALLBACK (sp_shape_marker_modified), shape); + } + } +} + + + +/* Shape section */ + +void +sp_shape_set_shape (SPShape *shape) +{ + g_return_if_fail (shape != NULL); + g_return_if_fail (SP_IS_SHAPE (shape)); + + if (SP_SHAPE_CLASS (G_OBJECT_GET_CLASS (shape))->set_shape) { + SP_SHAPE_CLASS (G_OBJECT_GET_CLASS (shape))->set_shape (shape); + } +} + +void +sp_shape_set_curve (SPShape *shape, SPCurve *curve, unsigned int owner) +{ + if (shape->curve) { + shape->curve = sp_curve_unref (shape->curve); + } + if (curve) { + if (owner) { + shape->curve = sp_curve_ref (curve); + } else { + shape->curve = sp_curve_copy (curve); + } + } + SP_OBJECT(shape)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +/* Return duplicate of curve or NULL */ +SPCurve * +sp_shape_get_curve (SPShape *shape) +{ + if (shape->curve) { + return sp_curve_copy (shape->curve); + } + return NULL; +} + +/* NOT FOR GENERAL PUBLIC UNTIL SORTED OUT (Lauris) */ +void +sp_shape_set_curve_insync (SPShape *shape, SPCurve *curve, unsigned int owner) +{ + if (shape->curve) { + shape->curve = sp_curve_unref (shape->curve); + } + if (curve) { + if (owner) { + shape->curve = sp_curve_ref (curve); + } else { + shape->curve = sp_curve_copy (curve); + } + } +} + +static void sp_shape_snappoints(SPItem const *item, SnapPointsIter p) +{ + g_assert(item != NULL); + g_assert(SP_IS_SHAPE(item)); + + SPShape const *shape = SP_SHAPE(item); + if (shape->curve == NULL) { + return; + } + + NR::Matrix const i2d (sp_item_i2d_affine (item)); + + /* Use the end points of each segment of the path */ + NArtBpath const *bp = shape->curve->bpath; + while (bp->code != NR_END) { + *p = bp->c(3) * i2d; + bp++; + } +} + + +/* + 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 : diff --git a/src/sp-shape.h b/src/sp-shape.h new file mode 100644 index 000000000..547928436 --- /dev/null +++ b/src/sp-shape.h @@ -0,0 +1,62 @@ +#ifndef __SP_SHAPE_H__ +#define __SP_SHAPE_H__ + +/* + * Base class for shapes, including element + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "display/display-forward.h" +#include "sp-item.h" +#include "sp-marker-loc.h" + + + +#define SP_TYPE_SHAPE (sp_shape_get_type ()) +#define SP_SHAPE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_SHAPE, SPShape)) +#define SP_SHAPE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_SHAPE, SPShapeClass)) +#define SP_IS_SHAPE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_SHAPE)) +#define SP_IS_SHAPE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_SHAPE)) + +#define SP_SHAPE_WRITE_PATH (1 << 2) + +struct SPShape : public SPItem { + SPCurve *curve; + + SPObject *marker[SP_MARKER_LOC_QTY]; + gulong release_connect [SP_MARKER_LOC_QTY]; + gulong modified_connect [SP_MARKER_LOC_QTY]; +}; + +struct SPShapeClass { + SPItemClass item_class; + + /* Build bpath from extra shape attributes */ + void (* set_shape) (SPShape *shape); +}; + +GType sp_shape_get_type (void); + +void sp_shape_set_shape (SPShape *shape); + +/* Return duplicate of curve or NULL */ +SPCurve *sp_shape_get_curve (SPShape *shape); + +void sp_shape_set_curve (SPShape *shape, SPCurve *curve, unsigned int owner); + +/* NOT FOR GENERAL PUBLIC UNTIL SORTED OUT (Lauris) */ +void sp_shape_set_curve_insync (SPShape *shape, SPCurve *curve, unsigned int owner); + +/* PROTECTED */ +void sp_shape_set_marker (SPObject *object, unsigned int key, const gchar *value); + + + +#endif diff --git a/src/sp-skeleton.cpp b/src/sp-skeleton.cpp new file mode 100644 index 000000000..f45ff1fda --- /dev/null +++ b/src/sp-skeleton.cpp @@ -0,0 +1,215 @@ +#define __SP_SKELETON_CPP__ + +/** \file + * SVG implementation, used as an example for a base starting class + * when implementing new sp-objects. + * + * In vi, three global search-and-replaces will let you rename everything + * in this and the .h file: + * + * :%s/SKELETON/YOURNAME/g + * :%s/Skeleton/Yourname/g + * :%s/skeleton/yourname/g + */ +/* + * Authors: + * Kees Cook + * + * Copyright (C) 2004 Kees Cook + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "attributes.h" +#include "sp-skeleton.h" +#include "xml/repr.h" + +#define DEBUG_SKELETON +#ifdef DEBUG_SKELETON +# define debug(f, a...) { g_print("%s(%d) %s:", \ + __FILE__,__LINE__,__FUNCTION__); \ + g_print(f, ## a); \ + g_print("\n"); \ + } +#else +# define debug(f, a...) /**/ +#endif + +/* Skeleton base class */ + +static void sp_skeleton_class_init(SPSkeletonClass *klass); +static void sp_skeleton_init(SPSkeleton *skeleton); + +static void sp_skeleton_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); +static void sp_skeleton_release(SPObject *object); +static void sp_skeleton_set(SPObject *object, unsigned int key, gchar const *value); +static void sp_skeleton_update(SPObject *object, SPCtx *ctx, guint flags); +static Inkscape::XML::Node *sp_skeleton_write(SPObject *object, Inkscape::XML::Node *repr, guint flags); + +static SPObjectClass *skeleton_parent_class; + +GType +sp_skeleton_get_type() +{ + static GType skeleton_type = 0; + + if (!skeleton_type) { + GTypeInfo skeleton_info = { + sizeof(SPSkeletonClass), + NULL, NULL, + (GClassInitFunc) sp_skeleton_class_init, + NULL, NULL, + sizeof(SPSkeleton), + 16, + (GInstanceInitFunc) sp_skeleton_init, + NULL, /* value_table */ + }; + skeleton_type = g_type_register_static(SP_TYPE_OBJECT, "SPSkeleton", &skeleton_info, (GTypeFlags)0); + } + return skeleton_type; +} + +static void +sp_skeleton_class_init(SPSkeletonClass *klass) +{ + //GObjectClass *gobject_class = (GObjectClass *)klass; + SPObjectClass *sp_object_class = (SPObjectClass *)klass; + + skeleton_parent_class = (SPObjectClass*)g_type_class_peek_parent(klass); + + sp_object_class->build = sp_skeleton_build; + sp_object_class->release = sp_skeleton_release; + sp_object_class->write = sp_skeleton_write; + sp_object_class->set = sp_skeleton_set; + sp_object_class->update = sp_skeleton_update; +} + +static void +sp_skeleton_init(SPSkeleton *skeleton) +{ + debug("0x%p",skeleton); +} + +/** + * Reads the Inkscape::XML::Node, and initializes SPSkeleton variables. For this to get called, + * our name must be associated with a repr via "sp_object_type_register". Best done through + * sp-object-repr.cpp's repr_name_entries array. + */ +static void +sp_skeleton_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + debug("0x%p",object); + if (((SPObjectClass *) skeleton_parent_class)->build) { + ((SPObjectClass *) skeleton_parent_class)->build(object, document, repr); + } + + /* + Pay attention to certain settings here + + sp_object_read_attr(object, "xlink:href"); + sp_object_read_attr(object, "attributeName"); + sp_object_read_attr(object, "attributeType"); + sp_object_read_attr(object, "begin"); + sp_object_read_attr(object, "dur"); + sp_object_read_attr(object, "end"); + sp_object_read_attr(object, "min"); + sp_object_read_attr(object, "max"); + sp_object_read_attr(object, "restart"); + sp_object_read_attr(object, "repeatCount"); + sp_object_read_attr(object, "repeatDur"); + sp_object_read_attr(object, "fill"); + */ +} + +/** + * Drops any allocated memory. + */ +static void +sp_skeleton_release(SPObject *object) +{ + debug("0x%p",object); + + /* deal with our children and our selves here */ + + if (((SPObjectClass *) skeleton_parent_class)->release) + ((SPObjectClass *) skeleton_parent_class)->release(object); +} + +/** + * Sets a specific value in the SPSkeleton. + */ +static void +sp_skeleton_set(SPObject *object, unsigned int key, gchar const *value) +{ + debug("0x%p %s(%u): '%s'",object, + sp_attribute_name(key),key,value); + SPSkeleton *skeleton = SP_SKELETON(object); + + /* See if any parents need this value. */ + if (((SPObjectClass *) skeleton_parent_class)->set) { + ((SPObjectClass *) skeleton_parent_class)->set(object, key, value); + } +} + +/** + * Receives update notifications. + */ +static void +sp_skeleton_update(SPObject *object, SPCtx *ctx, guint flags) +{ + debug("0x%p",object); + //SPSkeleton *skeleton = SP_SKELETON(object); + + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | + SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + + /* do something to trigger redisplay, updates? */ + + } + + if (((SPObjectClass *) skeleton_parent_class)->update) { + ((SPObjectClass *) skeleton_parent_class)->update(object, ctx, flags); + } +} + +/** + * Writes its settings to an incoming repr object, if any. + */ +static Inkscape::XML::Node * +sp_skeleton_write(SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + debug("0x%p",object); + //SPSkeleton *skeleton = SP_SKELETON(object); + + // Inkscape-only object, not copied during an "plain SVG" dump: + if (flags & SP_OBJECT_WRITE_EXT) { + if (repr) { + // is this sane? + repr->mergeFrom(SP_OBJECT_REPR(object), "id"); + } else { + repr = SP_OBJECT_REPR(object)->duplicate(); + } + } + + if (((SPObjectClass *) skeleton_parent_class)->write) { + ((SPObjectClass *) skeleton_parent_class)->write(object, repr, flags); + } + + return repr; +} + + +/* + 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 : diff --git a/src/sp-skeleton.h b/src/sp-skeleton.h new file mode 100644 index 000000000..fc706aacd --- /dev/null +++ b/src/sp-skeleton.h @@ -0,0 +1,48 @@ +#ifndef SP_SKELETON_H_SEEN +#define SP_SKELETON_H_SEEN + +/** \file + * SVG implementation, see sp-skeleton.cpp. + */ +/* + * Authors: + * Kees Cook + * + * Copyright (C) 2004 Kees Cook + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-object.h" + +/* Skeleton base class */ + +#define SP_TYPE_SKELETON (sp_skeleton_get_type()) +#define SP_SKELETON(o) (G_TYPE_CHECK_INSTANCE_CAST((o), SP_TYPE_SKELETON, SPSkeleton)) +#define SP_IS_SKELETON(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), SP_TYPE_SKELETON)) + +class SPSkeleton; +class SPSkeletonClass; + +struct SPSkeleton : public SPObject { +}; + +struct SPSkeletonClass { + SPObjectClass parent_class; +}; + +GType sp_skeleton_get_type(); + + +#endif /* !SP_SKELETON_H_SEEN */ + +/* + 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 : diff --git a/src/sp-spiral.cpp b/src/sp-spiral.cpp new file mode 100644 index 000000000..aced4ec53 --- /dev/null +++ b/src/sp-spiral.cpp @@ -0,0 +1,617 @@ +#define __SP_SPIRAL_C__ + +/** \file + * implementation + */ +/* + * Authors: + * Mitsuru Oka + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "config.h" + + +#include "svg/svg.h" +#include "attributes.h" +#include "display/bezier-utils.h" +#include "display/curve.h" +#include +#include "xml/repr.h" + +#include "sp-spiral.h" + +static void sp_spiral_class_init (SPSpiralClass *klass); +static void sp_spiral_init (SPSpiral *spiral); + +static void sp_spiral_build (SPObject * object, SPDocument * document, Inkscape::XML::Node * repr); +static Inkscape::XML::Node *sp_spiral_write (SPObject *object, Inkscape::XML::Node *repr, guint flags); +static void sp_spiral_set (SPObject *object, unsigned int key, const gchar *value); +static void sp_spiral_update (SPObject *object, SPCtx *ctx, guint flags); + +static gchar * sp_spiral_description (SPItem * item); +static void sp_spiral_snappoints(SPItem const *item, SnapPointsIter p); +static void sp_spiral_set_shape (SPShape *shape); + +static NR::Point sp_spiral_get_tangent (SPSpiral const *spiral, gdouble t); + +static SPShapeClass *parent_class; + +/** + * Register SPSpiral class and return its type number. + */ +GType +sp_spiral_get_type (void) +{ + static GType spiral_type = 0; + + if (!spiral_type) { + GTypeInfo spiral_info = { + sizeof (SPSpiralClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_spiral_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (SPSpiral), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_spiral_init, + NULL, /* value_table */ + }; + spiral_type = g_type_register_static (SP_TYPE_SHAPE, "SPSpiral", &spiral_info, (GTypeFlags)0); + } + return spiral_type; +} + +/** + * SPSpiral vtable initialization. + */ +static void +sp_spiral_class_init (SPSpiralClass *klass) +{ + GObjectClass * gobject_class; + SPObjectClass * sp_object_class; + SPItemClass * item_class; + SPShapeClass *shape_class; + + gobject_class = (GObjectClass *) klass; + sp_object_class = (SPObjectClass *) klass; + item_class = (SPItemClass *) klass; + shape_class = (SPShapeClass *) klass; + + parent_class = (SPShapeClass *)g_type_class_ref (SP_TYPE_SHAPE); + + sp_object_class->build = sp_spiral_build; + sp_object_class->write = sp_spiral_write; + sp_object_class->set = sp_spiral_set; + sp_object_class->update = sp_spiral_update; + + item_class->description = sp_spiral_description; + item_class->snappoints = sp_spiral_snappoints; + + shape_class->set_shape = sp_spiral_set_shape; +} + +/** + * Callback for SPSpiral object initialization. + */ +static void +sp_spiral_init (SPSpiral * spiral) +{ + spiral->cx = 0.0; + spiral->cy = 0.0; + spiral->exp = 1.0; + spiral->revo = 3.0; + spiral->rad = 1.0; + spiral->arg = 0.0; + spiral->t0 = 0.0; +} + +/** + * Virtual build: set spiral properties from corresponding repr. + */ +static void +sp_spiral_build (SPObject * object, SPDocument * document, Inkscape::XML::Node * repr) +{ + if (((SPObjectClass *) parent_class)->build) + ((SPObjectClass *) parent_class)->build (object, document, repr); + + sp_object_read_attr (object, "sodipodi:cx"); + sp_object_read_attr (object, "sodipodi:cy"); + sp_object_read_attr (object, "sodipodi:expansion"); + sp_object_read_attr (object, "sodipodi:revolution"); + sp_object_read_attr (object, "sodipodi:radius"); + sp_object_read_attr (object, "sodipodi:argument"); + sp_object_read_attr (object, "sodipodi:t0"); +} + +/** + * Virtual write: write spiral attributes to corresponding repr. + */ +static Inkscape::XML::Node * +sp_spiral_write (SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + SPSpiral *spiral = SP_SPIRAL (object); + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = sp_repr_new ("svg:path"); + } + + if (flags & SP_OBJECT_WRITE_EXT) { + /* Fixme: we may replace these attributes by + * sodipodi:spiral="cx cy exp revo rad arg t0" + */ + repr->setAttribute("sodipodi:type", "spiral"); + sp_repr_set_svg_double(repr, "sodipodi:cx", spiral->cx); + sp_repr_set_svg_double(repr, "sodipodi:cy", spiral->cy); + sp_repr_set_svg_double(repr, "sodipodi:expansion", spiral->exp); + sp_repr_set_svg_double(repr, "sodipodi:revolution", spiral->revo); + sp_repr_set_svg_double(repr, "sodipodi:radius", spiral->rad); + sp_repr_set_svg_double(repr, "sodipodi:argument", spiral->arg); + sp_repr_set_svg_double(repr, "sodipodi:t0", spiral->t0); + } + + //Duplicate the path + SPCurve *curve = ((SPShape *) spiral)->curve; + //Nulls might be possible if this called iteratively + if ( !curve ) { + //g_warning("sp_spiral_write(): No path to copy\n"); + return NULL; + } + NArtBpath *bpath = curve->bpath; + if ( !bpath ) { + //g_warning("sp_spiral_write(): No path to copy\n"); + return NULL; + } + char *d = sp_svg_write_path ( bpath ); + repr->setAttribute("d", d); + g_free (d); + + if (((SPObjectClass *) (parent_class))->write) + ((SPObjectClass *) (parent_class))->write (object, repr, flags | SP_SHAPE_WRITE_PATH); + + return repr; +} + +/** + * Virtual set: change spiral object attribute. + */ +static void +sp_spiral_set (SPObject *object, unsigned int key, const gchar *value) +{ + SPSpiral *spiral; + SPShape *shape; + + spiral = SP_SPIRAL (object); + shape = SP_SHAPE (object); + + /// \todo fixme: we should really collect updates + switch (key) { + case SP_ATTR_SODIPODI_CX: + if (!sp_svg_length_read_computed_absolute (value, &spiral->cx)) { + spiral->cx = 0.0; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SODIPODI_CY: + if (!sp_svg_length_read_computed_absolute (value, &spiral->cy)) { + spiral->cy = 0.0; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SODIPODI_EXPANSION: + if (value) { + /** \todo + * FIXME: check that value looks like a (finite) + * number. Create a routine that uses strtod, and + * accepts a default value (if strtod finds an error). + * N.B. atof/sscanf/strtod consider "nan" and "inf" + * to be valid numbers. + */ + spiral->exp = g_ascii_strtod (value, NULL); + spiral->exp = CLAMP (spiral->exp, 0.0, 1000.0); + } else { + spiral->exp = 1.0; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SODIPODI_REVOLUTION: + if (value) { + spiral->revo = g_ascii_strtod (value, NULL); + spiral->revo = CLAMP (spiral->revo, 0.05, 1024.0); + } else { + spiral->revo = 3.0; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SODIPODI_RADIUS: + if (!sp_svg_length_read_computed_absolute (value, &spiral->rad)) { + spiral->rad = MAX (spiral->rad, 0.001); + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SODIPODI_ARGUMENT: + if (value) { + spiral->arg = g_ascii_strtod (value, NULL); + /** \todo + * FIXME: We still need some bounds on arg, for + * numerical reasons. E.g., we don't want inf or NaN, + * nor near-infinite numbers. I'm inclined to take + * modulo 2*pi. If so, then change the knot editors, + * which use atan2 - revo*2*pi, which typically + * results in very negative arg. + */ + } else { + spiral->arg = 0.0; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SODIPODI_T0: + if (value) { + spiral->t0 = g_ascii_strtod (value, NULL); + spiral->t0 = CLAMP (spiral->t0, 0.0, 0.999); + /** \todo + * Have shared constants for the allowable bounds for + * attributes. There was a bug here where we used -1.0 + * as the minimum (which leads to NaN via, e.g., + * pow(-1.0, 0.5); see sp_spiral_get_xy for + * requirements. + */ + } else { + spiral->t0 = 0.0; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + default: + if (((SPObjectClass *) parent_class)->set) + ((SPObjectClass *) parent_class)->set (object, key, value); + break; + } +} + +/** + * Virtual update callback. + */ +static void +sp_spiral_update (SPObject *object, SPCtx *ctx, guint flags) +{ + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + sp_shape_set_shape ((SPShape *) object); + } + + if (((SPObjectClass *) parent_class)->update) + ((SPObjectClass *) parent_class)->update (object, ctx, flags); +} + +/** + * Return textual description of spiral. + */ +static gchar * +sp_spiral_description (SPItem * item) +{ + // TRANSLATORS: since turn count isn't an integer, please adjust the + // string as needed to deal with an localized plural forms. + return g_strdup_printf (_("Spiral with %3f turns"), SP_SPIRAL(item)->revo); +} + + +/** + * Fit beziers together to spiral and draw it. + * + * \pre dstep \> 0. + * \pre is_unit_vector(*hat1). + * \post is_unit_vector(*hat2). + **/ +static void +sp_spiral_fit_and_draw (SPSpiral const *spiral, + SPCurve *c, + double dstep, + NR::Point darray[], + NR::Point const &hat1, + NR::Point &hat2, + double *t) +{ +#define BEZIER_SIZE 4 +#define FITTING_MAX_BEZIERS 4 +#define BEZIER_LENGTH (BEZIER_SIZE * FITTING_MAX_BEZIERS) + g_assert (dstep > 0); + g_assert (is_unit_vector (hat1)); + + NR::Point bezier[BEZIER_LENGTH]; + double d; + int depth, i; + + for (d = *t, i = 0; i <= SAMPLE_SIZE; d += dstep, i++) { + darray[i] = sp_spiral_get_xy(spiral, d); + + /* Avoid useless adjacent dups. (Otherwise we can have all of darray filled with + the same value, which upsets chord_length_parameterize.) */ + if ((i != 0) + && (darray[i] == darray[i - 1]) + && (d < 1.0)) { + i--; + d += dstep; + /** We mustn't increase dstep for subsequent values of + * i: for large spiral.exp values, rate of growth + * increases very rapidly. + */ + /** \todo + * Get the function itself to decide what value of d + * to use next: ensure that we move at least 0.25 * + * stroke width, for example. The derivative (as used + * for get_tangent before normalization) would be + * useful for estimating the appropriate d value. Or + * perhaps just start with a small dstep and scale by + * some small number until we move >= 0.25 * + * stroke_width. Must revert to the original dstep + * value for next iteration to avoid the problem + * mentioned above. + */ + } + } + + double const next_t = d - 2 * dstep; + /* == t + (SAMPLE_SIZE - 1) * dstep, in absence of dups. */ + + hat2 = -sp_spiral_get_tangent (spiral, next_t); + + /** \todo + * We should use better algorithm to specify maximum error. + */ + depth = sp_bezier_fit_cubic_full (bezier, NULL, darray, SAMPLE_SIZE, + hat1, hat2, + SPIRAL_TOLERANCE*SPIRAL_TOLERANCE, + FITTING_MAX_BEZIERS); + g_assert(depth * BEZIER_SIZE <= gint(G_N_ELEMENTS(bezier))); +#ifdef SPIRAL_DEBUG + if (*t == spiral->t0 || *t == 1.0) + g_print ("[%s] depth=%d, dstep=%g, t0=%g, t=%g, arg=%g\n", + debug_state, depth, dstep, spiral->t0, *t, spiral->arg); +#endif + if (depth != -1) { + for (i = 0; i < 4*depth; i += 4) { + sp_curve_curveto (c, + bezier[i + 1], + bezier[i + 2], + bezier[i + 3]); + } + } else { +#ifdef SPIRAL_VERBOSE + g_print ("cant_fit_cubic: t=%g\n", *t); +#endif + for (i = 1; i < SAMPLE_SIZE; i++) + sp_curve_lineto (c, darray[i]); + } + *t = next_t; + g_assert (is_unit_vector (hat2)); +} + +static void +sp_spiral_set_shape (SPShape *shape) +{ + NR::Point darray[SAMPLE_SIZE + 1]; + double t; + + SPSpiral *spiral = SP_SPIRAL(shape); + + SP_OBJECT (spiral)->requestModified(SP_OBJECT_MODIFIED_FLAG); + + SPCurve *c = sp_curve_new (); + +#ifdef SPIRAL_VERBOSE + g_print ("cx=%g, cy=%g, exp=%g, revo=%g, rad=%g, arg=%g, t0=%g\n", + spiral->cx, + spiral->cy, + spiral->exp, + spiral->revo, + spiral->rad, + spiral->arg, + spiral->t0); +#endif + + /* Initial moveto. */ + sp_curve_moveto(c, sp_spiral_get_xy(spiral, spiral->t0)); + + double const tstep = SAMPLE_STEP / spiral->revo; + double const dstep = tstep / (SAMPLE_SIZE - 1); + + NR::Point hat1 = sp_spiral_get_tangent (spiral, spiral->t0); + NR::Point hat2; + for (t = spiral->t0; t < (1.0 - tstep);) { + sp_spiral_fit_and_draw (spiral, c, dstep, darray, hat1, hat2, &t); + + hat1 = -hat2; + } + if ((1.0 - t) > SP_EPSILON) + sp_spiral_fit_and_draw (spiral, c, (1.0 - t)/(SAMPLE_SIZE - 1.0), + darray, hat1, hat2, &t); + + sp_shape_set_curve_insync ((SPShape *) spiral, c, TRUE); + sp_curve_unref (c); +} + +/** + * Set spiral properties and update display. + */ +void +sp_spiral_position_set (SPSpiral *spiral, + gdouble cx, + gdouble cy, + gdouble exp, + gdouble revo, + gdouble rad, + gdouble arg, + gdouble t0) +{ + g_return_if_fail (spiral != NULL); + g_return_if_fail (SP_IS_SPIRAL (spiral)); + + /** \todo + * Consider applying CLAMP or adding in-bounds assertions for + * some of these parameters. + */ + spiral->cx = cx; + spiral->cy = cy; + spiral->exp = exp; + spiral->revo = revo; + spiral->rad = MAX (rad, 0.001); + spiral->arg = arg; + spiral->t0 = CLAMP(t0, 0.0, 0.999); + + ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +/** + * Virtual snappoints callback. + */ +static void sp_spiral_snappoints(SPItem const *item, SnapPointsIter p) +{ + if (((SPItemClass *) parent_class)->snappoints) { + ((SPItemClass *) parent_class)->snappoints (item, p); + } +} + +/** + * Return one of the points on the spiral. + * + * \param t specifies how far along the spiral. + * \pre \a t in [0.0, 2.03]. (It doesn't make sense for t to be much more + * than 1.0, though some callers go slightly beyond 1.0 for curve-fitting + * purposes.) + */ +NR::Point sp_spiral_get_xy (SPSpiral const *spiral, gdouble t) +{ + g_assert (spiral != NULL); + g_assert (SP_IS_SPIRAL(spiral)); + g_assert (spiral->exp >= 0.0); + /* Otherwise we get NaN for t==0. */ + g_assert (spiral->exp <= 1000.0); + /* Anything much more results in infinities. Even allowing 1000 is somewhat overkill. */ + g_assert (t >= 0.0); + /* Any callers passing -ve t will have a bug for non-integral values of exp. */ + + double const rad = spiral->rad * pow(t, (double) spiral->exp); + double const arg = 2.0 * M_PI * spiral->revo * t + spiral->arg; + + return NR::Point(rad * cos (arg) + spiral->cx, + rad * sin (arg) + spiral->cy); +} + + +/** + * Returns the derivative of sp_spiral_get_xy with respect to t, + * scaled to a unit vector. + * + * \pre spiral != 0. + * \pre 0 \<= t. + * \pre p != NULL. + * \post is_unit_vector(*p). + */ +static NR::Point +sp_spiral_get_tangent (SPSpiral const *spiral, gdouble t) +{ + NR::Point ret(1.0, 0.0); + g_return_val_if_fail (( ( spiral != NULL ) + && SP_IS_SPIRAL(spiral) ), + ret); + g_assert (t >= 0.0); + g_assert (spiral->exp >= 0.0); + /* See above for comments on these assertions. */ + + double const t_scaled = 2.0 * M_PI * spiral->revo * t; + double const arg = t_scaled + spiral->arg; + double const s = sin (arg); + double const c = cos (arg); + + if (spiral->exp == 0.0) { + ret = NR::Point(-s, c); + } else if (t_scaled == 0.0) { + ret = NR::Point(c, s); + } else { + NR::Point unrotated(spiral->exp, t_scaled); + double const s_len = L2 (unrotated); + g_assert (s_len != 0); + /** \todo + * Check that this isn't being too hopeful of the hypot + * function. E.g. test with numbers around 2**-1070 + * (denormalized numbers), preferably on a few different + * platforms. However, njh says that the usual implementation + * does handle both very big and very small numbers. + */ + unrotated /= s_len; + + /* ret = spiral->exp * (c, s) + t_scaled * (-s, c); + alternatively ret = (spiral->exp, t_scaled) * (( c, s), + (-s, c)).*/ + ret = NR::Point(dot(unrotated, NR::Point(c, -s)), + dot(unrotated, NR::Point(s, c))); + /* ret should already be approximately normalized: the + matrix ((c, -s), (s, c)) is orthogonal (it just + rotates by arg), and unrotated has been normalized, + so ret is already of unit length other than numerical + error in the above matrix multiplication. */ + + /** \todo + * I haven't checked how important it is for ret to be very + * near unit length; we could get rid of the below. + */ + + ret.normalize(); + /* Proof that ret length is non-zero: see above. (Should be near 1.) */ + } + + g_assert (is_unit_vector (ret)); + return ret; +} + +/** + * Compute rad and/or arg for point on spiral. + */ +void +sp_spiral_get_polar (SPSpiral const *spiral, gdouble t, gdouble *rad, gdouble *arg) +{ + g_return_if_fail (spiral != NULL); + g_return_if_fail (SP_IS_SPIRAL(spiral)); + + if (rad) + *rad = spiral->rad * pow(t, (double) spiral->exp); + if (arg) + *arg = 2.0 * M_PI * spiral->revo * t + spiral->arg; +} + +/** + * Return true if spiral has properties that make it invalid. + */ +bool +sp_spiral_is_invalid (SPSpiral const *spiral) +{ + gdouble rad; + + sp_spiral_get_polar (spiral, 0.0, &rad, NULL); + if (rad < 0.0 || rad > SP_HUGE) { + g_print ("rad(t=0)=%g\n", rad); + return TRUE; + } + sp_spiral_get_polar (spiral, 1.0, &rad, NULL); + if (rad < 0.0 || rad > SP_HUGE) { + g_print ("rad(t=1)=%g\n", rad); + return TRUE; + } + return FALSE; +} + +/* + 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 : diff --git a/src/sp-spiral.h b/src/sp-spiral.h new file mode 100644 index 000000000..62c916e82 --- /dev/null +++ b/src/sp-spiral.h @@ -0,0 +1,91 @@ +#ifndef __SP_SPIRAL_H__ +#define __SP_SPIRAL_H__ + +/** \file + * SPSpiral: implementation + * + * Authors: + * Mitsuru Oka + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-shape.h" + +#define noSPIRAL_VERBOSE + +#define SP_EPSILON 1e-5 +#define SP_EPSILON_2 (SP_EPSILON * SP_EPSILON) +#define SP_HUGE 1e5 + +#define SPIRAL_TOLERANCE 3.0 +#define SAMPLE_STEP (1.0/4.0) ///< step per 2PI +#define SAMPLE_SIZE 8 ///< sample size per one bezier + +#define SP_TYPE_SPIRAL (sp_spiral_get_type ()) +#define SP_SPIRAL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_SPIRAL, SPSpiral)) +#define SP_SPIRAL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_SPIRAL, SPSpiralClass)) +#define SP_IS_SPIRAL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_SPIRAL)) +#define SP_IS_SPIRAL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_SPIRAL)) + +class SPSpiral; +class SPSpiralClass; + +/** + * A spiral Shape. + * + * The Spiral shape is defined as: + * \verbatim + x(t) = rad * t^exp cos(2 * Pi * revo*t + arg) + cx + y(t) = rad * t^exp sin(2 * Pi * revo*t + arg) + cy \endverbatim + * where spiral curve is drawn for {t | t0 <= t <= 1}. The rad and arg + * parameters can also be represented by transformation. + * + * \todo Should I remove these attributes? + */ +struct SPSpiral : public SPShape { + float cx, cy; + float exp; ///< Spiral expansion factor + float revo; ///< Spiral revolution factor + float rad; ///< Spiral radius + float arg; ///< Spiral argument + float t0; +}; + +/// The SPSpiral vtable. +struct SPSpiralClass { + SPShapeClass parent_class; +}; + + +/* Standard Gtk function */ +GType sp_spiral_get_type (void); + +/* Lowlevel interface */ +void sp_spiral_position_set (SPSpiral *spiral, + gdouble cx, + gdouble cy, + gdouble exp, + gdouble revo, + gdouble rad, + gdouble arg, + gdouble t0); + +NR::Point sp_spiral_get_xy (SPSpiral const *spiral, + gdouble t); + +void sp_spiral_get_polar (SPSpiral const *spiral, + gdouble t, + gdouble *rad, + gdouble *arg); + +bool sp_spiral_is_invalid (SPSpiral const *spiral); + + + + +#endif diff --git a/src/sp-star.cpp b/src/sp-star.cpp new file mode 100644 index 000000000..dfa34be95 --- /dev/null +++ b/src/sp-star.cpp @@ -0,0 +1,544 @@ +#define __SP_STAR_C__ + +/* + * implementation + * + * Authors: + * Mitsuru Oka + * Lauris Kaplinski + * bulia byak + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "config.h" + +#if defined(WIN32) || defined(__APPLE__) +# include +#endif + +#include "svg/svg.h" +#include "attributes.h" +#include "display/curve.h" +#include "xml/repr.h" + +#include "sp-star.h" + +static void sp_star_class_init (SPStarClass *klass); +static void sp_star_init (SPStar *star); + +static void sp_star_build (SPObject * object, SPDocument * document, Inkscape::XML::Node * repr); +static Inkscape::XML::Node *sp_star_write (SPObject *object, Inkscape::XML::Node *repr, guint flags); +static void sp_star_set (SPObject *object, unsigned int key, const gchar *value); +static void sp_star_update (SPObject *object, SPCtx *ctx, guint flags); + +static gchar * sp_star_description (SPItem * item); +static void sp_star_snappoints(SPItem const *item, SnapPointsIter p); + +static void sp_star_set_shape (SPShape *shape); + +static SPShapeClass *parent_class; + +GType +sp_star_get_type (void) +{ + static GType type = 0; + + if (!type) { + GTypeInfo info = { + sizeof (SPStarClass), + NULL, NULL, + (GClassInitFunc) sp_star_class_init, + NULL, NULL, + sizeof (SPStar), + 16, + (GInstanceInitFunc) sp_star_init, + NULL, /* value_table */ + }; + type = g_type_register_static (SP_TYPE_SHAPE, "SPStar", &info, (GTypeFlags)0); + } + return type; +} + +static void +sp_star_class_init (SPStarClass *klass) +{ + GObjectClass * gobject_class; + SPObjectClass * sp_object_class; + SPItemClass * item_class; + SPPathClass * path_class; + SPShapeClass * shape_class; + + gobject_class = (GObjectClass *) klass; + sp_object_class = (SPObjectClass *) klass; + item_class = (SPItemClass *) klass; + path_class = (SPPathClass *) klass; + shape_class = (SPShapeClass *) klass; + + parent_class = (SPShapeClass *)g_type_class_ref (SP_TYPE_SHAPE); + + sp_object_class->build = sp_star_build; + sp_object_class->write = sp_star_write; + sp_object_class->set = sp_star_set; + sp_object_class->update = sp_star_update; + + item_class->description = sp_star_description; + item_class->snappoints = sp_star_snappoints; + + shape_class->set_shape = sp_star_set_shape; +} + +static void +sp_star_init (SPStar * star) +{ + star->sides = 5; + star->center = NR::Point(0, 0); + star->r[0] = 1.0; + star->r[1] = 0.001; + star->arg[0] = star->arg[1] = 0.0; + star->flatsided = 0; + star->rounded = 0.0; + star->randomized = 0.0; +} + +static void +sp_star_build (SPObject * object, SPDocument * document, Inkscape::XML::Node * repr) +{ + if (((SPObjectClass *) parent_class)->build) + ((SPObjectClass *) parent_class)->build (object, document, repr); + + sp_object_read_attr (object, "sodipodi:cx"); + sp_object_read_attr (object, "sodipodi:cy"); + sp_object_read_attr (object, "sodipodi:sides"); + sp_object_read_attr (object, "sodipodi:r1"); + sp_object_read_attr (object, "sodipodi:r2"); + sp_object_read_attr (object, "sodipodi:arg1"); + sp_object_read_attr (object, "sodipodi:arg2"); + sp_object_read_attr (object, "inkscape:flatsided"); + sp_object_read_attr (object, "inkscape:rounded"); + sp_object_read_attr (object, "inkscape:randomized"); +} + +static Inkscape::XML::Node * +sp_star_write (SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + SPStar *star = SP_STAR (object); + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = sp_repr_new ("svg:path"); + } + + if (flags & SP_OBJECT_WRITE_EXT) { + repr->setAttribute("sodipodi:type", "star"); + sp_repr_set_int (repr, "sodipodi:sides", star->sides); + sp_repr_set_svg_double(repr, "sodipodi:cx", star->center[NR::X]); + sp_repr_set_svg_double(repr, "sodipodi:cy", star->center[NR::Y]); + sp_repr_set_svg_double(repr, "sodipodi:r1", star->r[0]); + sp_repr_set_svg_double(repr, "sodipodi:r2", star->r[1]); + sp_repr_set_svg_double(repr, "sodipodi:arg1", star->arg[0]); + sp_repr_set_svg_double(repr, "sodipodi:arg2", star->arg[1]); + sp_repr_set_boolean (repr, "inkscape:flatsided", star->flatsided); + sp_repr_set_svg_double(repr, "inkscape:rounded", star->rounded); + sp_repr_set_svg_double(repr, "inkscape:randomized", star->randomized); + } + + sp_star_set_shape ((SPShape *) star); + char *d = sp_svg_write_path (((SPShape *) star)->curve->bpath); + repr->setAttribute("d", d); + g_free (d); + + if (((SPObjectClass *) (parent_class))->write) + ((SPObjectClass *) (parent_class))->write (object, repr, flags); + + return repr; +} + +static void +sp_star_set (SPObject *object, unsigned int key, const gchar *value) +{ + SVGLength::Unit unit; + + SPStar *star = SP_STAR (object); + + /* fixme: we should really collect updates */ + switch (key) { + case SP_ATTR_SODIPODI_SIDES: + if (value) { + star->sides = atoi (value); + star->sides = CLAMP (star->sides, 3, 1024); + } else { + star->sides = 5; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SODIPODI_CX: + if (!sp_svg_length_read_ldd (value, &unit, NULL, &star->center[NR::X]) || + (unit == SVGLength::EM) || + (unit == SVGLength::EX) || + (unit == SVGLength::PERCENT)) { + star->center[NR::X] = 0.0; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SODIPODI_CY: + if (!sp_svg_length_read_ldd (value, &unit, NULL, &star->center[NR::Y]) || + (unit == SVGLength::EM) || + (unit == SVGLength::EX) || + (unit == SVGLength::PERCENT)) { + star->center[NR::Y] = 0.0; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SODIPODI_R1: + if (!sp_svg_length_read_ldd (value, &unit, NULL, &star->r[0]) || + (unit == SVGLength::EM) || + (unit == SVGLength::EX) || + (unit == SVGLength::PERCENT)) { + star->r[0] = 1.0; + } + /* fixme: Need CLAMP (Lauris) */ + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SODIPODI_R2: + if (!sp_svg_length_read_ldd (value, &unit, NULL, &star->r[1]) || + (unit == SVGLength::EM) || + (unit == SVGLength::EX) || + (unit == SVGLength::PERCENT)) { + star->r[1] = 0.0; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + return; + case SP_ATTR_SODIPODI_ARG1: + if (value) { + star->arg[0] = g_ascii_strtod (value, NULL); + } else { + star->arg[0] = 0.0; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SODIPODI_ARG2: + if (value) { + star->arg[1] = g_ascii_strtod (value, NULL); + } else { + star->arg[1] = 0.0; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_FLATSIDED: + if (value && !strcmp (value, "true")) + star->flatsided = true; + else star->flatsided = false; + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_ROUNDED: + if (value) { + star->rounded = g_ascii_strtod (value, NULL); + } else { + star->rounded = 0.0; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_RANDOMIZED: + if (value) { + star->randomized = g_ascii_strtod (value, NULL); + } else { + star->randomized = 0.0; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + default: + if (((SPObjectClass *) parent_class)->set) + ((SPObjectClass *) parent_class)->set (object, key, value); + break; + } +} + +static void +sp_star_update (SPObject *object, SPCtx *ctx, guint flags) +{ + if (flags & (SP_OBJECT_MODIFIED_FLAG | + SP_OBJECT_STYLE_MODIFIED_FLAG | + SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + sp_shape_set_shape ((SPShape *) object); + } + + if (((SPObjectClass *) parent_class)->update) + ((SPObjectClass *) parent_class)->update (object, ctx, flags); +} + +static gchar * +sp_star_description (SPItem *item) +{ + SPStar *star = SP_STAR (item); + + // while there will never be less than 3 vertices, we still need to + // make calls to ngettext because the pluralization may be different + // for various numbers >=3. The singular form is used as the index. + if (star->flatsided == false ) + return g_strdup_printf (ngettext("Star with %d vertex", + "Star with %d vertices", + star->sides), star->sides); + else + return g_strdup_printf (ngettext("Polygon with %d vertex", + "Polygon with %d vertices", + star->sides), star->sides); +} + +/** +Returns a unit-length vector at 90 degrees to the direction from o to n + */ +static NR::Point +rot90_rel (NR::Point o, NR::Point n) +{ + return ((1/NR::L2(n - o)) * NR::Point ((n - o)[NR::Y], (o - n)[NR::X])); +} + +/** +Returns a unique 32 bit int for a given point. +Obvious (but acceptable for my purposes) limits to uniqueness: +- returned value for x,y repeats for x+n*1024,y+n*1024 +- returned value is unchanged when the point is moved by less than 1/1024 of px +*/ +static guint32 +point_unique_int (NR::Point o) +{ + return ((guint32) + 65536 * + (((int) floor (o[NR::X] * 64)) % 1024 + ((int) floor (o[NR::X] * 1024)) % 64) + + + (((int) floor (o[NR::Y] * 64)) % 1024 + ((int) floor (o[NR::Y] * 1024)) % 64) + ); +} + +/** +Returns the next pseudorandom value using the Linear Congruential Generator algorithm (LCG) +with the parameters (m = 2^32, a = 69069, b = 1). These parameters give a full-period generator, +i.e. it is guaranteed to go through all integers < 2^32 (see http://random.mat.sbg.ac.at/~charly/server/server.html) +*/ +static inline guint32 +lcg_next(guint32 const prev) +{ + return (guint32) ( 69069 * prev + 1 ); +} + +/** +Returns a random number in the range [-0.5, 0.5) from the given seed, stepping the given number of steps from the seed. +*/ +static double +rnd (guint32 const seed, unsigned steps) { + guint32 lcg = seed; + for (; steps > 0; steps --) + lcg = lcg_next (lcg); + + return ( lcg / 4294967296. ) - 0.5; +} + +static NR::Point +sp_star_get_curvepoint (SPStar *star, SPStarPoint point, gint index, bool previ) +{ + // the point whose neighboring curve handle we're calculating + NR::Point o = sp_star_get_xy (star, point, index); + + // indices of previous and next points + gint pi = (index > 0)? (index - 1) : (star->sides - 1); + gint ni = (index < star->sides - 1)? (index + 1) : 0; + + // the other point type + SPStarPoint other = (point == SP_STAR_POINT_KNOT2? SP_STAR_POINT_KNOT1 : SP_STAR_POINT_KNOT2); + + // the neighbors of o; depending on flatsided, they're either the same type (polygon) or the other type (star) + NR::Point prev = (star->flatsided? sp_star_get_xy (star, point, pi) : sp_star_get_xy (star, other, point == SP_STAR_POINT_KNOT2? index : pi)); + NR::Point next = (star->flatsided? sp_star_get_xy (star, point, ni) : sp_star_get_xy (star, other, point == SP_STAR_POINT_KNOT1? index : ni)); + + // prev-next midpoint + NR::Point mid = 0.5 * (prev + next); + + // point to which we direct the bissector of the curve handles; + // it's far enough outside the star on the perpendicular to prev-next through mid + NR::Point biss = mid + 100000 * rot90_rel (mid, next); + + // lengths of vectors to prev and next + gdouble prev_len = NR::L2 (prev - o); + gdouble next_len = NR::L2 (next - o); + + // unit-length vector perpendicular to o-biss + NR::Point rot = rot90_rel (o, biss); + + // multiply rot by star->rounded coefficient and the distance to the star point; flip for next + NR::Point ret; + if (previ) { + ret = (star->rounded * prev_len) * rot; + } else { + ret = (star->rounded * next_len * -1) * rot; + } + + if (star->randomized == 0) { + // add the vector to o to get the final curvepoint + return o + ret; + } else { + // the seed corresponding to the exact point + guint32 seed = point_unique_int (o); + + // randomly rotate (by step 3 from the seed) and scale (by step 4) the vector + ret = ret * NR::Matrix (NR::rotate (star->randomized * M_PI * rnd (seed, 3))); + ret *= ( 1 + star->randomized * rnd (seed, 4)); + + // the randomized corner point + NR::Point o_randomized = sp_star_get_xy (star, point, index, true); + + return o_randomized + ret; + } +} + + +#define NEXT false +#define PREV true + +static void +sp_star_set_shape (SPShape *shape) +{ + SPStar *star = SP_STAR (shape); + + SPCurve *c = sp_curve_new (); + + gint sides = star->sides; + bool not_rounded = (fabs (star->rounded) < 1e-4); + + // note that we pass randomized=true to sp_star_get_xy, because the curve must be randomized; + // other places that call that function (e.g. the knotholder) need the exact point + + // draw 1st segment + sp_curve_moveto (c, sp_star_get_xy (star, SP_STAR_POINT_KNOT1, 0, true)); + if (star->flatsided == false) { + if (not_rounded) { + sp_curve_lineto (c, sp_star_get_xy (star, SP_STAR_POINT_KNOT2, 0, true)); + } else { + sp_curve_curveto (c, + sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT1, 0, NEXT), + sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT2, 0, PREV), + sp_star_get_xy (star, SP_STAR_POINT_KNOT2, 0, true)); + } + } + + // draw all middle segments + for (gint i = 1; i < sides; i++) { + if (not_rounded) { + sp_curve_lineto (c, sp_star_get_xy (star, SP_STAR_POINT_KNOT1, i, true)); + } else { + if (star->flatsided == false) { + sp_curve_curveto (c, + sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT2, i - 1, NEXT), + sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT1, i, PREV), + sp_star_get_xy (star, SP_STAR_POINT_KNOT1, i, true)); + } else { + sp_curve_curveto (c, + sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT1, i - 1, NEXT), + sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT1, i, PREV), + sp_star_get_xy (star, SP_STAR_POINT_KNOT1, i, true)); + } + } + if (star->flatsided == false) { + + if (not_rounded) { + sp_curve_lineto (c, sp_star_get_xy (star, SP_STAR_POINT_KNOT2, i, true)); + } else { + sp_curve_curveto (c, + sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT1, i, NEXT), + sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT2, i, PREV), + sp_star_get_xy (star, SP_STAR_POINT_KNOT2, i, true)); + } + } + } + + // draw last segment + if (not_rounded) { + sp_curve_lineto (c, sp_star_get_xy (star, SP_STAR_POINT_KNOT1, 0, true)); + } else { + if (star->flatsided == false) { + sp_curve_curveto (c, + sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT2, sides - 1, NEXT), + sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT1, 0, PREV), + sp_star_get_xy (star, SP_STAR_POINT_KNOT1, 0, true)); + } else { + sp_curve_curveto (c, + sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT1, sides - 1, NEXT), + sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT1, 0, PREV), + sp_star_get_xy (star, SP_STAR_POINT_KNOT1, 0, true)); + } + } + + sp_curve_closepath (c); + sp_shape_set_curve_insync (SP_SHAPE (star), c, TRUE); + sp_curve_unref (c); +} + +void +sp_star_position_set (SPStar *star, gint sides, NR::Point center, gdouble r1, gdouble r2, gdouble arg1, gdouble arg2, bool isflat, double rounded, double randomized) +{ + g_return_if_fail (star != NULL); + g_return_if_fail (SP_IS_STAR (star)); + + star->sides = CLAMP (sides, 3, 1024); + star->center = center; + star->r[0] = MAX (r1, 0.001); + if (isflat == false) { + star->r[1] = CLAMP (r2, 0.0, star->r[0]); + } else { + star->r[1] = CLAMP ( r1*cos(M_PI/sides) ,0.0, star->r[0] ); + } + star->arg[0] = arg1; + star->arg[1] = arg2; + star->flatsided = isflat; + star->rounded = rounded; + star->randomized = randomized; + SP_OBJECT(star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +/* fixme: We should use all corners of star (Lauris) */ + +static void sp_star_snappoints(SPItem const *item, SnapPointsIter p) +{ + if (((SPItemClass *) parent_class)->snappoints) { + ((SPItemClass *) parent_class)->snappoints (item, p); + } +} + +/** + * sp_star_get_xy: Get X-Y value as item coordinate system + * @star: star item + * @point: point type to obtain X-Y value + * @index: index of vertex + * @p: pointer to store X-Y value + * @randomized: false (default) if you want to get exact, not randomized point + * + * Initial item coordinate system is same as document coordinate system. + */ + +NR::Point +sp_star_get_xy (SPStar *star, SPStarPoint point, gint index, bool randomized) +{ + gdouble darg = 2.0 * M_PI / (double) star->sides; + + double arg = star->arg[point]; + arg += index * darg; + + NR::Point xy = star->r[point] * NR::Point(cos(arg), sin(arg)) + star->center; + + if (!randomized || star->randomized == 0) { + // return the exact point + return xy; + } else { // randomize the point + // find out the seed, unique for this point so that randomization is the same so long as the original point is stationary + guint32 seed = point_unique_int (xy); + // the full range (corresponding to star->randomized == 1.0) is equal to the star's diameter + double range = 2 * MAX (star->r[0], star->r[1]); + // find out the random displacement; x is controlled by step 1 from the seed, y by the step 2 + NR::Point shift (star->randomized * range * rnd (seed, 1), star->randomized * range * rnd (seed, 2)); + // add the shift to the exact point + return xy + shift; + } +} + diff --git a/src/sp-star.h b/src/sp-star.h new file mode 100644 index 000000000..f6b70df22 --- /dev/null +++ b/src/sp-star.h @@ -0,0 +1,59 @@ +#ifndef __SP_STAR_H__ +#define __SP_STAR_H__ + +/* + * implementation + * + * Authors: + * Mitsuru Oka + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-polygon.h" + + + +#define SP_TYPE_STAR (sp_star_get_type ()) +#define SP_STAR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_STAR, SPStar)) +#define SP_STAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_STAR, SPStarClass)) +#define SP_IS_STAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_STAR)) +#define SP_IS_STAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_STAR)) + +class SPStar; +class SPStarClass; + +typedef enum { + SP_STAR_POINT_KNOT1, + SP_STAR_POINT_KNOT2 +} SPStarPoint; + +struct SPStar : public SPPolygon { + gint sides; + + NR::Point center; + double r[2]; + double arg[2]; + bool flatsided; + + double rounded; + double randomized; +}; + +struct SPStarClass { + SPPolygonClass parent_class; +}; + +GType sp_star_get_type (void); + +void sp_star_position_set (SPStar *star, gint sides, NR::Point center, gdouble r1, gdouble r2, gdouble arg1, gdouble arg2, bool isflat, double rounded, double randomized); + +NR::Point sp_star_get_xy (SPStar *star, SPStarPoint point, gint index, bool randomized = false); + + + +#endif diff --git a/src/sp-stop-fns.h b/src/sp-stop-fns.h new file mode 100644 index 000000000..9903359e9 --- /dev/null +++ b/src/sp-stop-fns.h @@ -0,0 +1,17 @@ +#ifndef SEEN_SP_STOP_FNS_H +#define SEEN_SP_STOP_FNS_H + +#include +struct SPStop; +struct SPStopClass; + +#define SP_TYPE_STOP (sp_stop_get_type()) +#define SP_STOP(o) (G_TYPE_CHECK_INSTANCE_CAST((o), SP_TYPE_STOP, SPStop)) +#define SP_STOP_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), SP_TYPE_STOP, SPStopClass)) +#define SP_IS_STOP(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), SP_TYPE_STOP)) +#define SP_IS_STOP_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE((k), SP_TYPE_STOP)) + +GType sp_stop_get_type(); + + +#endif /* !SEEN_SP_STOP_FNS_H */ diff --git a/src/sp-stop.h b/src/sp-stop.h new file mode 100644 index 000000000..20f72a3f5 --- /dev/null +++ b/src/sp-stop.h @@ -0,0 +1,56 @@ +#ifndef SEEN_SP_STOP_H +#define SEEN_SP_STOP_H + +/** \file + * + * SPStop: SVG implementation. + * + * Authors? + */ + +#include +//#include +//#include "sp-object.h" +//#include "color.h" +#include "sp-stop-fns.h" + +class SPObjectClass; +class SPColor; + +/** Gradient stop. */ +struct SPStop : public SPObject { + /// \todo fixme: Should be SPSVGPercentage + gfloat offset; + + bool currentColor; + + /** \note + * N.B.\ Meaningless if currentColor is true. Use sp_stop_get_rgba32 or sp_stop_get_color + * (currently static in sp-gradient.cpp) if you want the effective color. + */ + SPColor specified_color; + + /// \todo fixme: Implement SPSVGNumber or something similar. + gfloat opacity; +}; + +/// The SPStop vtable. +struct SPStopClass { + SPObjectClass parent_class; +}; + +guint32 sp_stop_get_rgba32(SPStop const *); + + +#endif /* !SEEN_SP_STOP_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:encoding=utf-8:textwidth=99 : diff --git a/src/sp-string.cpp b/src/sp-string.cpp new file mode 100644 index 000000000..ba3df3ef2 --- /dev/null +++ b/src/sp-string.cpp @@ -0,0 +1,178 @@ +#define __SP_STRING_C__ + +/* + * SVG and implementation + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +/* + * fixme: + * + * These subcomponents should not be items, or alternately + * we have to invent set of flags to mark, whether standard + * attributes are applicable to given item (I even like this + * idea somewhat - Lauris) + * + */ + + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + + +#include "sp-string.h" +#include "xml/repr.h" + + +/*##################################################### +# SPSTRING +#####################################################*/ + +static void sp_string_class_init(SPStringClass *classname); +static void sp_string_init(SPString *string); + +static void sp_string_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); +static void sp_string_release(SPObject *object); +static void sp_string_read_content(SPObject *object); +static void sp_string_update(SPObject *object, SPCtx *ctx, unsigned flags); + +static void sp_string_calculate_dimensions(SPString *string); + +static SPObjectClass *string_parent_class; + +GType +sp_string_get_type() +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPStringClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_string_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof(SPString), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_string_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_OBJECT, "SPString", &info, (GTypeFlags)0); + } + return type; +} + +static void +sp_string_class_init(SPStringClass *classname) +{ + SPObjectClass *sp_object_class; + SPItemClass *item_class; + + sp_object_class = (SPObjectClass *) classname; + item_class = (SPItemClass *) classname; + + string_parent_class = (SPObjectClass*)g_type_class_ref(SP_TYPE_OBJECT); + + sp_object_class->build = sp_string_build; + sp_object_class->release = sp_string_release; + sp_object_class->read_content = sp_string_read_content; + sp_object_class->update = sp_string_update; +} + +static void +sp_string_init(SPString *string) +{ + new (&string->string) Glib::ustring(); +} + +static void +sp_string_build(SPObject *object, SPDocument *doc, Inkscape::XML::Node *repr) +{ + sp_string_read_content(object); + + if (((SPObjectClass *) string_parent_class)->build) + ((SPObjectClass *) string_parent_class)->build(object, doc, repr); +} + +static void +sp_string_release(SPObject *object) +{ + SPString *string = SP_STRING(object); + + string->string.~ustring(); + + if (((SPObjectClass *) string_parent_class)->release) + ((SPObjectClass *) string_parent_class)->release(object); +} + +static void +sp_string_read_content(SPObject *object) +{ + SPString *string = SP_STRING(object); + + string->string.clear(); + gchar const *xml_string = string->repr->content(); + // see algorithms described in svg 1.1 section 10.15 + if (object->xml_space.value == SP_XML_SPACE_PRESERVE) { + for ( ; *xml_string ; xml_string = g_utf8_next_char(xml_string) ) { + gunichar c = g_utf8_get_char(xml_string); + if (c == '\n' || c == '\t') c = ' '; + string->string += c; + } + } + else { + bool whitespace = false; + for ( ; *xml_string ; xml_string = g_utf8_next_char(xml_string) ) { + gunichar c = g_utf8_get_char(xml_string); + if (c == '\n') continue; + if (c == ' ' || c == '\t') whitespace = true; + else { + if (whitespace && (!string->string.empty() || SP_OBJECT_PREV(object) != NULL)) + string->string += ' '; + string->string += c; + whitespace = false; + } + } + if (whitespace && SP_OBJECT_REPR(object)->next() != NULL) // can't use SP_OBJECT_NEXT() when the SPObject tree is still being built + string->string += ' '; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +static void +sp_string_update(SPObject *object, SPCtx *ctx, unsigned flags) +{ + if (((SPObjectClass *) string_parent_class)->update) + ((SPObjectClass *) string_parent_class)->update(object, ctx, flags); + + if (flags & (SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_MODIFIED_FLAG)) { + /* Parent style or we ourselves changed, so recalculate */ + flags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B; // won't be "just a transformation" anymore, we're going to recompute "x" and "y" attributes + sp_string_calculate_dimensions(SP_STRING(object)); + } +} + +static void +sp_string_calculate_dimensions(SPString *) +{ +} + +/* + 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 : diff --git a/src/sp-string.h b/src/sp-string.h new file mode 100644 index 000000000..7242589c6 --- /dev/null +++ b/src/sp-string.h @@ -0,0 +1,30 @@ +#ifndef __SP_STRING_H__ +#define __SP_STRING_H__ + +/* + * string elements + * extracted from sp-text + */ + +#include + +#include "sp-object.h" + +#define SP_TYPE_STRING (sp_string_get_type ()) +#define SP_STRING(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_STRING, SPString)) +#define SP_STRING_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_STRING, SPStringClass)) +#define SP_IS_STRING(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_STRING)) +#define SP_IS_STRING_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_STRING)) + + +struct SPString : public SPObject { + Glib::ustring string; +}; + +struct SPStringClass { + SPObjectClass parent_class; +}; + +GType sp_string_get_type (); + +#endif diff --git a/src/sp-style-elem-test.cpp b/src/sp-style-elem-test.cpp new file mode 100644 index 000000000..97be4fead --- /dev/null +++ b/src/sp-style-elem-test.cpp @@ -0,0 +1,118 @@ +#include "attributes.h" +#include "document.h" +#include "inkscape-private.h" +#include "sp-style-elem.h" +#include "streq.h" +#include "utest/utest.h" +#include "xml/repr.h" + +/// Dummy functions to keep linker happy +int sp_main_gui (int, char const**) { return 0; } +int sp_main_console (int, char const**) { return 0; } + +static bool +test_style_elem() +{ + utest_start("SPStyleElem"); +//#if 0 + UTEST_TEST("init") { + SPStyleElem *style_elem = static_cast(g_object_new(SP_TYPE_STYLE_ELEM, NULL)); + UTEST_ASSERT(!style_elem->is_css); + UTEST_ASSERT(style_elem->media.print); + UTEST_ASSERT(style_elem->media.screen); + g_object_unref(style_elem); + } +//#endif + + /* Create the global inkscape object. */ + static_cast(g_object_new(inkscape_get_type(), NULL)); + +//#if 0 + SPDocument *doc = sp_document_new_dummy(); + + UTEST_TEST("sp_object_set(\"type\")") { + SPStyleElem *style_elem = static_cast(g_object_new(SP_TYPE_STYLE_ELEM, NULL)); + SP_OBJECT(style_elem)->document = doc; + sp_object_set(SP_OBJECT(style_elem), SP_ATTR_TYPE, "something unrecognized"); + UTEST_ASSERT(!style_elem->is_css); + sp_object_set(SP_OBJECT(style_elem), SP_ATTR_TYPE, "text/css"); + UTEST_ASSERT(style_elem->is_css); + sp_object_set(SP_OBJECT(style_elem), SP_ATTR_TYPE, "atext/css"); + UTEST_ASSERT(!style_elem->is_css); + sp_object_set(SP_OBJECT(style_elem), SP_ATTR_TYPE, "text/cssx"); + UTEST_ASSERT(!style_elem->is_css); + g_object_unref(style_elem); + } + + UTEST_TEST("write") { + SPStyleElem *style_elem = SP_STYLE_ELEM(g_object_new(SP_TYPE_STYLE_ELEM, NULL)); + SP_OBJECT(style_elem)->document = doc; + sp_object_set(SP_OBJECT(style_elem), SP_ATTR_TYPE, "text/css"); + Inkscape::XML::Node *repr = sp_repr_new("svg:style"); + SP_OBJECT(style_elem)->updateRepr(repr, SP_OBJECT_WRITE_ALL); + { + gchar const *typ = repr->attribute("type"); + UTEST_ASSERT(streq(typ, "text/css")); + } + g_object_unref(style_elem); + } + + UTEST_TEST("build") { + SPStyleElem &style_elem = *SP_STYLE_ELEM(g_object_new(SP_TYPE_STYLE_ELEM, NULL)); + Inkscape::XML::Node *const repr = sp_repr_new("svg:style"); + repr->setAttribute("type", "text/css"); + sp_object_invoke_build(&style_elem, doc, repr, false); + UTEST_ASSERT(style_elem.is_css); + UTEST_ASSERT(style_elem.media.print); + UTEST_ASSERT(style_elem.media.screen); + + /* Some checks relevant to the read_content test below. */ + { + g_assert(doc->style_cascade); + CRStyleSheet const *const stylesheet = cr_cascade_get_sheet(doc->style_cascade, ORIGIN_AUTHOR); + g_assert(stylesheet); + g_assert(stylesheet->statements == NULL); + } + + g_object_unref(&style_elem); + Inkscape::GC::release(repr); + } + + UTEST_TEST("read_content") { + SPStyleElem &style_elem = *SP_STYLE_ELEM(g_object_new(SP_TYPE_STYLE_ELEM, NULL)); + Inkscape::XML::Node *const repr = sp_repr_new("svg:style"); + repr->setAttribute("type", "text/css"); + Inkscape::XML::Node *const content_repr = sp_repr_new_text(".myclass { }"); + repr->addChild(content_repr, NULL); + sp_object_invoke_build(&style_elem, doc, repr, false); + UTEST_ASSERT(style_elem.is_css); + UTEST_ASSERT(doc->style_cascade); + CRStyleSheet const *const stylesheet = cr_cascade_get_sheet(doc->style_cascade, ORIGIN_AUTHOR); + UTEST_ASSERT(stylesheet != NULL); + UTEST_ASSERT(stylesheet->statements != NULL); + g_object_unref(&style_elem); + Inkscape::GC::release(repr); + } +//#endif + return utest_end(); +} + +int main() +{ + g_type_init(); + Inkscape::GC::init(); + return ( test_style_elem() + ? EXIT_SUCCESS + : EXIT_FAILURE ); +} + +/* + 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 : diff --git a/src/sp-style-elem.cpp b/src/sp-style-elem.cpp new file mode 100644 index 000000000..ff27f3de0 --- /dev/null +++ b/src/sp-style-elem.cpp @@ -0,0 +1,407 @@ +#include +#include "xml/node-event-vector.h" +#include "xml/repr.h" +#include "document.h" +#include "sp-style-elem.h" +#include "attributes.h" +using Inkscape::XML::TEXT_NODE; + +static void sp_style_elem_init(SPStyleElem *style_elem); +static void sp_style_elem_class_init(SPStyleElemClass *klass); +static void sp_style_elem_build(SPObject *object, SPDocument *doc, Inkscape::XML::Node *repr); +static void sp_style_elem_set(SPObject *object, unsigned const key, gchar const *const value); +static void sp_style_elem_read_content(SPObject *); +static Inkscape::XML::Node *sp_style_elem_write(SPObject *, Inkscape::XML::Node *, guint flags); + +static SPObjectClass *parent_class; + +GType +sp_style_elem_get_type() +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPStyleElemClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_style_elem_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof(SPStyleElem), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_style_elem_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_OBJECT, "SPStyleElem", &info, (GTypeFlags) 0); + } + + return type; +} + +static void +sp_style_elem_class_init(SPStyleElemClass *klass) +{ + parent_class = (SPObjectClass *)g_type_class_ref(SP_TYPE_OBJECT); + /* FIXME */ + + klass->build = sp_style_elem_build; + klass->set = sp_style_elem_set; + klass->read_content = sp_style_elem_read_content; + klass->write = sp_style_elem_write; +} + +static void +sp_style_elem_init(SPStyleElem *style_elem) +{ + media_set_all(style_elem->media); + style_elem->is_css = false; +} + +static void +sp_style_elem_set(SPObject *object, unsigned const key, gchar const *const value) +{ + g_return_if_fail(object); + SPStyleElem &style_elem = *SP_STYLE_ELEM(object); + + switch (key) { + case SP_ATTR_TYPE: { + if (!value) { + /* TODO: `type' attribute is required. Give error message as per + http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing. */ + style_elem.is_css = false; + } else { + /* fixme: determine what whitespace is allowed. Will probably need to ask on SVG + * list; though the relevant RFC may give info on its lexer. */ + style_elem.is_css = ( g_ascii_strncasecmp(value, "text/css", 8) == 0 + && ( value[8] == '\0' || + value[8] == ';' ) ); + } + break; + } + +#if 0 /* unfinished */ + case SP_ATTR_MEDIA: { + parse_media(style_elem, value); + break; + } +#endif + + /* title is ignored. */ + default: { + if (parent_class->set) { + parent_class->set(object, key, value); + } + break; + } + } +} + +static void +child_add_rm_cb(Inkscape::XML::Node *, Inkscape::XML::Node *, Inkscape::XML::Node *, + void *const data) +{ + sp_style_elem_read_content(static_cast(data)); +} + +static void +content_changed_cb(Inkscape::XML::Node *, gchar const *, gchar const *, + void *const data) +{ + sp_style_elem_read_content(static_cast(data)); +} + +static void +child_order_changed_cb(Inkscape::XML::Node *, Inkscape::XML::Node *, + Inkscape::XML::Node *, Inkscape::XML::Node *, + void *const data) +{ + sp_style_elem_read_content(static_cast(data)); +} + +static Inkscape::XML::Node * +sp_style_elem_write(SPObject *const object, Inkscape::XML::Node *repr, guint const flags) +{ + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = sp_repr_new("svg:style"); + } + + g_return_val_if_fail(object, repr); + SPStyleElem &style_elem = *SP_STYLE_ELEM(object); + if (flags & SP_OBJECT_WRITE_BUILD) { + g_warning("nyi: Forming